image.png

    1. <template>
    2. <div class="query-view-detail">
    3. <DetailHeader
    4. @saveModalHandleOpen="saveModalHandleOpen"
    5. @powerModalHandleOpen="powerModalHandleOpen"
    6. @deleteModalHandleOpen="deleteModalHandleOpen"
    7. @handleSelectWarnTypes="handleSelectWarnTypes"
    8. @exportExcel="exportExcel"
    9. />
    10. <main>
    11. <SearchBar
    12. ref="searchBarRef"
    13. @handleSearch="handleSearch"
    14. @updateFilter="updateFilter"
    15. :filter="filter"
    16. :frameFilters="frameFilters"
    17. :currentTab="currentTab"
    18. />
    19. <TableList
    20. ref="tableListRef"
    21. :loading="refreshTable.loading"
    22. @changeTab="changeTab" // 改变tab
    23. @changePagination="changePagination"
    24. :currentTab="currentTab" // 有注册信息和批次信息两个tab
    25. :tableListData="tableListData" // 传递的表格数据
    26. @openStockInModal="openStockInModal"
    27. @openStockOutModal="openStockOutModal" // 出库
    28. />
    29. </main>
    30. <!-- Modals -->
    31. <SaveModal
    32. ref="saveModalRef"
    33. :filter="filter"
    34. @saveModalMakeViewHandleOk="saveModalMakeViewHandleOk"
    35. @saveModalUpdateViewHandleOk="saveModalUpdateViewHandleOk"
    36. />
    37. <PowerModal ref="powerModalRef" @powerModalHandleOk="powerModalHandleOk" />
    38. <DeleteModal ref="deleteModalRef" @deleteModalHandleOk="deleteModalHandleOk" />
    39. <StockInModal v-model:visible="stockInModalVisible" @refreshCurrentTabTable="refreshCurrentTabTable" />
    40. <!-- 打开出库模态框:在TableList中添加数据点击出库,发送自定义事件给父组件-->
    41. <StockOutModal v-model:visible="stockOutModalVisible" @refreshCurrentTabTable="refreshCurrentTabTable" />
    42. </div>
    43. </template>
    44. <script lang="ts">
    45. export type FrameFilter = {
    46. fieldId: string | null
    47. fieldKey?: string
    48. fieldName: string
    49. fieldType: FilterOriginType
    50. filterType: TableColsTypeEnum
    51. preDefinedValues: string[]
    52. }
    53. </script>
    54. <script setup lang="ts">
    55. //components
    56. import TableList, { MaterialData } from './components/TableList.vue'
    57. import DetailHeader from './components/DetailHeader.vue'
    58. import SaveModal from './components/SaveModal.vue'
    59. import PowerModal from './components/PowerModal.vue'
    60. import DeleteModal from './components/DeleteModal.vue'
    61. import SearchBar from './components/SearchBar.vue'
    62. import { Message } from '@arco-design/web-vue'
    63. // import ResidualQuantityWarn from './components/ResidualQuantityWarn.vue'
    64. import StockInModal from './components/stockInModal/index.vue'
    65. import StockOutModal from './components/stockOutModel/index.vue'
    66. //utils
    67. import { ref, reactive, computed, inject, toRaw, shallowRef, provide } from 'vue'
    68. import {
    69. initSearchInfo,
    70. // 批次还是注册信息的枚举
    71. TabsEnum,
    72. buildColumns,
    73. TreeNodeTypeEnum,
    74. deBuildColumns,
    75. SaveViewModeEnmu
    76. } from '@/views/register/registration-and-batch-info-query/utils'
    77. import { useFetch } from '@/hooks'
    78. //api
    79. import {
    80. // 搜索批次信息的接口
    81. searchBatchInfoList,
    82. // 搜索注册信息的接口
    83. searchProductInfoList,
    84. updateSearchView,
    85. saveSearchView,
    86. deleteSearchView,
    87. getProductFrameFilters,
    88. getExportColumnsData,
    89. getExportProd
    90. } from '@/api/material'
    91. //types
    92. import { Filter, FilterOriginType, SearchInfo, TableColsTypeEnum } from '../types'
    93. import { exportExcelModeEnum, searchSourceEnum } from '@/views/inventory/components/register-search/types'
    94. import { SearchTypeEnmu } from '@/common/enum'
    95. //plugin
    96. import { useI18n } from 'vue-i18n'
    97. import _, { cloneDeep } from 'lodash'
    98. const { t } = useI18n()
    99. const RootData = inject('rootData')
    100. const { treeNodeData } = RootData as any
    101. const Emiter = defineEmits(['saveView', 'updateView', 'deleteView', 'powerModalHandleOk'])
    102. const globalSearchInfo = ref(initSearchInfo())
    103. const setGlobalSearchInfo = (searchInfo) => (globalSearchInfo.value = searchInfo)
    104. const tableListRef = ref()
    105. const stockInModalVisible = ref<boolean>(false)
    106. const stockOutModalVisible = ref<boolean>(false)
    107. const materialDataList = shallowRef<MaterialData[] | undefined>(void 0)
    108. provide('materialDataList', materialDataList)
    109. const openStockInModal = (data: MaterialData[]) => {
    110. stockInModalVisible.value = true
    111. materialDataList.value = data
    112. }
    113. // 打开出库模态框,
    114. const openStockOutModal = (data: MaterialData[]) => {
    115. stockOutModalVisible.value = true
    116. materialDataList.value = data
    117. }
    118. const searchBarRef = ref()
    119. /**
    120. * DetailHeader 相关业务
    121. */
    122. const saveModalRef = ref()
    123. const powerModalRef = ref()
    124. const deleteModalRef = ref()
    125. const saveModalHandleOpen = () => {
    126. saveModalRef.value.saveModalHandleOpen(SaveViewModeEnmu.CREATE_VIEW, {})
    127. }
    128. const powerModalHandleOpen = () => {
    129. powerModalRef.value.powerModalHandleOpen()
    130. }
    131. const deleteModalHandleOpen = () => {
    132. deleteModalRef.value.deleteModalHandleOpen()
    133. }
    134. const handleSelectWarnTypes = async (selectWarnTypes) => {
    135. const newFilter = Object.assign(globalSearchInfo.value.filter, { alarmTypes: selectWarnTypes })
    136. const searchInfo = globalSearchInfo.value
    137. await refreshAllTabTable({ searchInfo, searchSource: searchSourceEnum.FILTER })
    138. }
    139. /**
    140. * 根据不同的tab,映射不同的接口,获取对应的数据,这里要根据筛选条件去后端搜索
    141. */
    142. const refreshTabTableMaps = {
    143. // 如果是注册表格
    144. [TabsEnum.REGISTER]: async ({
    145. searchInfo,
    146. searchSource
    147. }: {
    148. searchInfo: SearchInfo
    149. searchSource: searchSourceEnum
    150. }) => {
    151. let ProductTableInfo = await searchProductInfoList(searchInfo)
    152. //拼接展示列的信息
    153. switch (searchSource) {
    154. case searchSourceEnum.SELECT_TREE_NODE: {
    155. ProductTableInfo.allColumns = buildColumns(TabsEnum.REGISTER, searchInfo.productShowCols)
    156. break
    157. }
    158. default: {
    159. ProductTableInfo.allColumns = tableListRef.value.allCols.productShowCols
    160. // ProductTableInfo.showColumns = tableListRef.value.showCols.productShowCols
    161. }
    162. }
    163. // ProductTableInfo.allColumns = buildColumns(TabsEnum.REGISTER, searchInfo.productShowCols)
    164. ProductTableInfo.data = parseCustomParams(ProductTableInfo.data)
    165. tableListData[TabsEnum.REGISTER] = ProductTableInfo
    166. setGlobalSearchInfo(cloneDeep(toRaw(searchInfo)))
    167. filter.value = cloneDeep(toRaw(searchInfo.filter))
    168. },
    169. // 如果是批次信息
    170. [TabsEnum.BATCH]: async ({
    171. searchInfo,
    172. searchSource
    173. }: {
    174. searchInfo: SearchInfo
    175. searchSource: searchSourceEnum
    176. }) => {
    177. let BatchTableInfo = await searchBatchInfoList(searchInfo)
    178. //拼接展示列的信息
    179. switch (searchSource) {
    180. case searchSourceEnum.SELECT_TREE_NODE: {
    181. BatchTableInfo.allColumns = buildColumns(TabsEnum.BATCH, searchInfo.batchShowCols)
    182. break
    183. }
    184. default: {
    185. BatchTableInfo.allColumns = tableListRef.value.allCols.batchShowCols
    186. // BatchTableInfo.showColumns = tableListRef.value.showCols.batchShowCols
    187. }
    188. }
    189. // BatchTableInfo.allColumns = buildColumns(TabsEnum.BATCH, searchInfo.batchShowCols)
    190. BatchTableInfo.data = parseCustomParams(BatchTableInfo.data)
    191. tableListData[TabsEnum.BATCH] = BatchTableInfo
    192. setGlobalSearchInfo(cloneDeep(toRaw(searchInfo)))
    193. filter.value = cloneDeep(toRaw(searchInfo.filter))
    194. }
    195. }
    196. //将一些自定义字段解析到每个 dataItem 的外部
    197. function parseCustomParams(data) {
    198. return data.map((row, index) => {
    199. let batchParams = row?.batchParams || []
    200. let productParams = row?.productParams || []
    201. const params = [...batchParams, ...productParams]
    202. params.map((v) => {
    203. row[v.fieldId] = v
    204. })
    205. //标记序号
    206. row.index = index + 1
    207. return row
    208. })
    209. }
    210. /**
    211. * 刷新所有tab数据
    212. */
    213. const refreshAllTabTable = async ({
    214. searchInfo,
    215. searchSource
    216. }: {
    217. searchInfo: SearchInfo
    218. searchSource: searchSourceEnum
    219. }) => {
    220. refreshTable.value.loading.value = true
    221. tableListRef.value.clearCacheData() //清除之前表单的缓存数据
    222. await Promise.all(
    223. Object.values(refreshTabTableMaps).map((refreshFunc) => refreshFunc({ searchInfo, searchSource }))
    224. ).finally(() => {
    225. refreshTable.value.loading.value = false
    226. })
    227. //回显对应的 keyword
    228. searchBarRef.value.setKeywrod(searchInfo.keyword)
    229. }
    230. /**
    231. * 刷新当前tab数据
    232. */
    233. const refreshCurrentTabTable = () => {
    234. refreshTabTableMaps[currentTab.value]({ searchInfo: globalSearchInfo.value, searchSource: searchSourceEnum.FILTER })
    235. tableListRef.value.initialSelectedRowKeys()
    236. }
    237. /**
    238. * tab 相关
    239. */
    240. const currentTab = reactive<{ value: TabsEnum }>({ value: TabsEnum.REGISTER })
    241. // computed返回的是ref,之后访问时需要通过value
    242. const refreshTable = computed(() => {
    243. return useFetch(refreshTabTableMaps[currentTab.value], {
    244. isLazy: true
    245. })
    246. })
    247. // 切换tab事件
    248. const changeTab = async ({ type, paginationInfo }) => {
    249. currentTab.value = Number(type)
    250. const { pageNum, pageSize } = paginationInfo
    251. await refreshTable.value.fetchResource({
    252. searchInfo: Object.assign(globalSearchInfo.value, { pageNum, pageSize }),
    253. searchSource: searchSourceEnum.CHANGE_TAB
    254. })
    255. }
    256. //表单总数
    257. const tableListData = reactive({
    258. [TabsEnum.REGISTER]: {},
    259. [TabsEnum.BATCH]: {}
    260. })
    261. const filter = ref<Filter>({})
    262. /**
    263. * 搜索按钮事件
    264. * TODO
    265. * 根据筛选条件进行搜索
    266. */
    267. const handleSearch = ({ keyword = '' }: { keyword: string }) => {
    268. // console.log('搜索', keyWord, tableListRef.value.selectedKeys)
    269. //根据不同的节点类型,拿到正确的frameId
    270. let frameId
    271. switch (treeNodeData.value.nodeType) {
    272. case TreeNodeTypeEnum.PRODUCT_FRAM: {
    273. frameId = treeNodeData.value.id
    274. }
    275. case TreeNodeTypeEnum.VIEW: {
    276. frameId = treeNodeData.value.parentId
    277. }
    278. default: {
    279. frameId = treeNodeData.value.id
    280. }
    281. }
    282. //初始化分页数据
    283. tableListRef.value.initialPresetPagination()
    284. const pageNum = tableListRef.value.paginationInfo.pageNum
    285. //刷新列表
    286. const targetParams = { ...globalSearchInfo.value, keyword, pageNum }
    287. refreshTable.value.fetchResource({ searchInfo: targetParams, searchSource: searchSourceEnum.FILTER })
    288. //清空选择列
    289. tableListRef.value.initialSelectedRowKeys()
    290. }
    291. /**
    292. * 筛选条件更新到globalSearchInfo
    293. */
    294. const updateFilter = (filter: Filter) => {
    295. Object.assign(globalSearchInfo.value.filter, filter)
    296. }
    297. /**
    298. * 导出接口
    299. */
    300. const exportInfo = {
    301. [TabsEnum.REGISTER]: {
    302. method: getExportProd,
    303. prefixName: t('register.registrationAndBatchInfoQuery.queryViewDetail.product')
    304. },
    305. [TabsEnum.BATCH]: {
    306. method: getExportColumnsData,
    307. prefixName: t('register.registrationAndBatchInfoQuery.queryViewDetail.batch')
    308. }
    309. }
    310. /**
    311. * 导出
    312. */
    313. const exportExcel = async (mode, callback: Function) => {
    314. let condition = {}
    315. let seletCols: {
    316. productShowCols: any[]
    317. batchShowCols: any[]
    318. } = {
    319. productShowCols: [],
    320. batchShowCols: []
    321. }
    322. switch (mode) {
    323. case exportExcelModeEnum.CURRENT_SHOW: {
    324. const { productShowCols, batchShowCols } = tableListRef.value.showCols
    325. seletCols.productShowCols = deBuildColumns(productShowCols)
    326. seletCols.batchShowCols = deBuildColumns(batchShowCols)
    327. condition = Object.assign({}, globalSearchInfo.value, seletCols)
    328. break
    329. }
    330. default: {
    331. condition = Object.assign({}, globalSearchInfo.value, tableListRef.value.currentAllCols)
    332. break
    333. }
    334. }
    335. const { data } = await exportInfo[currentTab.value].method(condition)
    336. callback(data, { fileName: `${exportInfo[currentTab.value].prefixName}_${treeNodeData.value.name}` })
    337. }
    338. /**
    339. * 改变分页
    340. */
    341. const changePagination = async ({ paginationInfo }) => {
    342. const { pageNum, pageSize } = paginationInfo
    343. // const { productShowCols, batchShowCols } = tableListRef.value.showCols
    344. await refreshTable.value.fetchResource({
    345. searchInfo: Object.assign(globalSearchInfo.value, {
    346. pageNum,
    347. pageSize
    348. }),
    349. searchSource: searchSourceEnum.FILTER
    350. })
    351. // const targetColsPropName = currentTab.value === TabsEnum.REGISTER ? 'productShowCols' : 'batchShowCols'
    352. // const allColumns = cloneDeep(globalSearchInfo.value[targetColsPropName])
    353. // await refreshTable.value.fetchResource({
    354. // searchInfo: Object.assign(globalSearchInfo.value, {
    355. // pageNum,
    356. // pageSize,
    357. // productShowCols: deBuildColumns(productShowCols),
    358. // batchShowCols: deBuildColumns(batchShowCols)
    359. // }),
    360. // allColumns
    361. // })
    362. }
    363. /**
    364. * Modal相关
    365. */
    366. //打包主要的视图信息
    367. const buildViewMainInfo = ({ viewInfo }) => {
    368. //保存的视图信息
    369. const { viewName, viewDesc } = viewInfo
    370. //筛选条件
    371. const { productShowCols, batchShowCols } = tableListRef.value.showCols
    372. globalSearchInfo.value.productShowCols = deBuildColumns(productShowCols)
    373. globalSearchInfo.value.batchShowCols = deBuildColumns(batchShowCols)
    374. const condition = globalSearchInfo.value
    375. return { viewName, viewDesc, condition }
    376. }
    377. //保存视图
    378. const saveModalMakeViewHandleOk = async (info, closeSaveModal, extraData) => {
    379. const { viewName, viewDesc, condition } = buildViewMainInfo({ viewInfo: info })
    380. //其他信息
    381. const searchType = SearchTypeEnmu.PRODUCT_INFO
    382. const frameId = extraData?.frameId
    383. let referenceId
    384. if (treeNodeData.value.nodeType === TreeNodeTypeEnum.PRODUCT_FRAM) {
    385. referenceId = frameId || treeNodeData.value.key
    386. }
    387. if (treeNodeData.value.nodeType === TreeNodeTypeEnum.VIEW) {
    388. referenceId = frameId || treeNodeData.value.parentId
    389. }
    390. //传递的总数居
    391. const data = { viewName, viewDesc, condition, searchType, referenceId }
    392. // console.log('保存时,传递的总数居', data)
    393. await saveSearchView(data)
    394. //告知外部,让其刷新树列表
    395. Emiter('saveView', referenceId)
    396. closeSaveModal()
    397. }
    398. //更新视图
    399. const saveModalUpdateViewHandleOk = async (info, closeSaveModal) => {
    400. const { viewName, viewDesc, condition } = buildViewMainInfo({ viewInfo: info })
    401. //其他信息
    402. const searchType = SearchTypeEnmu.PRODUCT_INFO
    403. const frameId = treeNodeData.value.parentId
    404. const id = treeNodeData.value.key
    405. //传递的总数居
    406. const data = { viewName, viewDesc, condition, searchType, id }
    407. // console.log('更新时,传递的总数居', data)
    408. try {
    409. await updateSearchView(data)
    410. } catch (error) {
    411. /**
    412. * 若保存失败,则要回溯对 globalSearchInfo 的更改
    413. * (buildViewMainInfo中,操作了globalSearchInfo)
    414. * */
    415. const { productShowCols, batchShowCols } = tableListRef.value.allCols
    416. globalSearchInfo.value.productShowCols = deBuildColumns(productShowCols)
    417. globalSearchInfo.value.batchShowCols = deBuildColumns(batchShowCols)
    418. }
    419. //告知外部,让其刷新树列表
    420. Emiter('updateView', { frameId, viewName, viewDesc, condition })
    421. closeSaveModal()
    422. }
    423. //删除视图
    424. const deleteModalHandleOk = async (closeDeleteModal) => {
    425. await deleteSearchView(treeNodeData.value.id)
    426. Message.success('删除成功!')
    427. closeDeleteModal()
    428. const frameId = treeNodeData.value.parentId
    429. Emiter('deleteView', frameId)
    430. }
    431. //保存视图权限
    432. const powerModalHandleOk = () => {
    433. //权限相关信息
    434. Emiter('powerModalHandleOk')
    435. }
    436. const frameFilters = ref<FrameFilter[]>([])
    437. const getFrameFilters = async (frameId: string) => {
    438. const res = await getProductFrameFilters(frameId)
    439. frameFilters.value = res.data
    440. }
    441. /**
    442. * 查看余量告警信息
    443. */
    444. // async function handleClickWatchQuantityWarn() {
    445. // const newFilter = Object.assign(globalSearchInfo.value.filter, { alarmOn: SystemYesNoEnum.YES })
    446. // const searchInfo = globalSearchInfo.value
    447. // await refreshAllTabTable(searchInfo)
    448. // }
    449. /**
    450. * 暴露给外部的state
    451. * */
    452. const exposeSaveHandleOpen = computed(() => saveModalRef.value?.saveModalHandleOpen)
    453. const initialTablePresetPagination = computed(() => tableListRef.value?.initialPresetPagination)
    454. const initialTableSelectedRowKeys = computed(() => tableListRef.value?.initialSelectedRowKeys)
    455. defineExpose({
    456. refreshAllTabTable,
    457. getFrameFilters,
    458. saveModalHandleOpen: exposeSaveHandleOpen,
    459. initialTablePresetPagination,
    460. initialTableSelectedRowKeys
    461. })
    462. </script>
    463. <style scoped lang="scss">
    464. .query-view-detail {
    465. background-color: #fff;
    466. main {
    467. padding: 0px 20px 24px;
    468. }
    469. }
    470. .pointer {
    471. cursor: pointer;
    472. }
    473. </style>

    index.vue里面按照筛选条件获取相应的数据,把数据和当前的tab通过props带到TableList里,在TableList里展示选择的tab的数据的数量并通过自定义事件切换Tab。

    1. <!--
    2. * @Author: Ashun
    3. * @Date: 2022-06-20 18:23:06
    4. * @LastEditors: zzd993
    5. * @LastEditTime: 2022-08-12 17:11:07
    6. * @FilePath: \elabnote-front-main\src\views\inventory\components\register-search\query-view-detail\components\TableList.vue
    7. * Copyright (c) 2022 by BMY, All Rights Reserved.
    8. -->
    9. <template>
    10. <div class="table-list-wrapper">
    11. <div class="tabs-bar">
    12. <div
    13. class="tab-btn pointer"
    14. :class="currentTab.value === Number(type) ? 'tab-active' : ''"
    15. v-for="(item, type) in TabOptions"
    16. :key="type"
    17. @click="changeTab(item, type)"
    18. >
    19. {{ item.title }}({{ item.totalSum }})
    20. </div>
    21. </div>
    22. <div class="table-wrapper">
    23. <BasicTable
    24. ref="tableRef"
    25. rowKey="id"
    26. v-model:columns="TabMapDataSource[currentTab.value].showColumns" // 注册或者批次的表格头
    27. :data="showData" // 表格的数据
    28. :isShowFliter="true"
    29. :columnsAll="currentAllCols"
    30. :row-selection="rowSelection"
    31. v-model:selectedRowKeys="selectedRowKeys"
    32. :pagination="pagination"
    33. :scroll="{ y: 600 }"
    34. @select="handleSelected"
    35. @selectAll="handleSelectAll"
    36. column-resizable
    37. :bordered="{ headerCell: true }"
    38. :loading="loading"
    39. >
    40. <template #customTitle>
    41. <a-space>
    42. <span>已选定 {{ selectedRowKeys.length }} 个</span>
    43. <a-button @click="openStockInModal" size="mini">入库</a-button>
    44. <!-- 出库按钮,点击后调用方法触发自定义事件,将打勾选择的数据发送给父组件,父组件通过
    45. provide派发给子组件-->
    46. <a-button @click="openStockOutModal" size="mini">出库</a-button>
    47. </a-space>
    48. </template>
    49. <!-- 序号列 -->
    50. <template #cell-serialNumber="{ item }">
    51. <a-tooltip :mini="true" :position="toolTipPosition" content-class="tool-tip-content">
    52. <template #content>
    53. <span v-for="msg in item.record.alarmMsgs" :key="msg">{{ msg }}</span>
    54. </template>
    55. <icon-exclamation-circle-fill v-if="item.record.alarmOn === 1" :style="{ color: '#ff7d00' }" />
    56. {{ computedShowSerialNumber(item.record.index) }}
    57. </a-tooltip>
    58. </template>
    59. <!-- 产品/批次附加字段 -->
    60. <template #cell-customParams="{ item }">
    61. <a-tooltip :mini="true" :position="toolTipPosition" content-class="tool-tip-content">
    62. <template #content>
    63. <p
    64. v-for="(filed, filedKey) in showAdditionalFields(item.record[item.column.dataIndex], 'array')"
    65. :key="filedKey"
    66. class="tool-tip-content-item"
    67. >
    68. {{ filed }}
    69. </p>
    70. </template>
    71. <a-tag v-if="!isShowEmpty(showAdditionalFields(item.record[item.column.dataIndex], 'string'))">
    72. <span class="pointer">{{ showAdditionalFields(item.record[item.column.dataIndex], 'string') }}</span>
    73. </a-tag>
    74. </a-tooltip>
    75. </template>
    76. <!-- 以 link 形式展示的字段 -->
    77. <template #cell-link="{ item }">
    78. <a-link v-if="item.column.dataIndex === 'batchNum'">
    79. 批次数:
    80. {{ customShowByColType(item.column.type, item.column.paramType, item.record[item.column.dataIndex]) }}
    81. </a-link>
    82. <!--如果是产品tab,点击产品名称就跳转到注册详情页面,-->
    83. <a-link
    84. v-else-if="item.column.dataIndex === 'productName'"
    85. @click="JumpToRegister(item.record[item.column.dataIndex], item.record.id)"
    86. >
    87. {{ customShowByColType(item.column.type, item.column.paramType, item.record[item.column.dataIndex]) }}
    88. </a-link>
    89. <a-link v-else>
    90. {{ customShowByColType(item.column.type, item.column.paramType, item.record[item.column.dataIndex]) }}
    91. </a-link>
    92. </template>
    93. <!-- 以 tag 形式展示的字段 -->
    94. <template #cell-inStockNum="{ item }">
    95. <a-tooltip
    96. :position="toolTipPosition"
    97. :popup-visible="!stockDetialLoading && item.record.id === mouseenterStockInfoCellId"
    98. >
    99. <template #content>
    100. <a-spin :loading="stockDetialLoading" @mouseenter="showStock" @mouseleave="hideStock">
    101. <span class="col-stock-detial-title">库存信息</span>
    102. <p v-for="(str, key) in stockDetialInfoArr" :key="key" class="col-stock-detial-item">{{ str }}</p>
    103. <a-link
    104. v-if="!item.record.batchName"
    105. class="col-stock-detial-item"
    106. // 带着产品名称和编号跳转到库存搜索页面并筛选出对应的库存
    107. @click="JumpToStock(item.record.productName, item.record.productPrefix)"
    108. >跳转至库存</a-link
    109. >
    110. <a-link v-else class="col-stock-detial-item"
    111. @click="JumpToStock(item.record.batchName, item.record.productPrefix)"
    112. >跳转至库存</a-link
    113. >
    114. </a-spin>
    115. </template>
    116. <a-tag class="pointer" @mouseenter="getStockInfo(item.record)" @mouseout="initMouseenterStockInfoCellId">
    117. 在库数量:
    118. {{ customShowByColType(item.column.type, item.column.paramType, item.record[item.column.dataIndex]) }}
    119. </a-tag>
    120. </a-tooltip>
    121. </template>
    122. <template #cell-default="{ item }">
    123. {{ customShowByColType(item.column.type, item.column.paramType, item.record[item.column.dataIndex]) }}
    124. </template>
    125. </BasicTable>
    126. </div>
    127. </div>
    128. </template>
    129. <script lang="ts">
    130. // 根据tab的不同切换展示的表头列信息
    131. type TabMapDataSource = {
    132. [key in TabsEnum]: {
    133. //展示的cols
    134. showColumns: any[]
    135. //编辑的cols信息 (dataIndexArr)
    136. selectShowColumns: string[]
    137. }
    138. }
    139. interface StockDetialInfo {
    140. inStockNum: string | number
    141. inStockAmount: string | number
    142. totalNum: string | number
    143. totalAmount: string | number
    144. }
    145. </script>
    146. <script setup lang="ts">
    147. //api
    148. import { getStockDetialInfo } from '@/api/material'
    149. //components
    150. import BasicTable from '@/components/BasicTable/index.vue'
    151. //utils
    152. import { ref, computed, Ref, watch, toRaw, reactive, nextTick, ShallowRef, shallowRef } from 'vue'
    153. import _, { isEmpty } from 'lodash'
    154. import moment from 'moment'
    155. import { SERIAL_NUMBER } from '@/common/constant'
    156. import { useFetch, useSelectedRows } from '@/hooks'
    157. import { debounce } from '@/util/tools/index'
    158. //types
    159. import { TableRowSelection } from '@arco-design/web-vue'
    160. import { TabsEnum } from '@/views/register/registration-and-batch-info-query/utils'
    161. import { TableColsTypeEnum } from '@/views/register/registration-and-batch-info-query/types'
    162. import { ParameterType } from '@/common/enum'
    163. import { useTagViewStore } from '@/store/modules/tag-view'
    164. import { ComponentTypeEnum } from '@/common/enum'
    165. import { useRouter } from 'vue-router'
    166. import { getViewDataMapCipher } from '@/api/material'
    167. export type MaterialData = {
    168. id: string
    169. frameId: string
    170. frameName: string
    171. batchName?: string
    172. productName: string
    173. productId: string
    174. batchNum: number
    175. [key: string]: any
    176. }
    177. const store = useTagViewStore()
    178. const router = useRouter()
    179. // const RootData = inject('rootData')
    180. // const { productFrameInfo } = RootData as any
    181. // const getProductFrameName = computed(() => productFrameInfo.value?.name)
    182. const { currentTab, tableListData, loading } = defineProps<{
    183. // 当前的tab和表格数据
    184. currentTab: { value: number }
    185. tableListData: any
    186. loading: Ref<boolean>
    187. }>()
    188. const Emiter = defineEmits(['changeTab', 'changePagination', 'openStockInModal', 'openStockOutModal'])
    189. const tableRef = ref()
    190. const rowSelection: TableRowSelection = reactive({
    191. type: 'checkbox',
    192. showCheckedAll: true
    193. })
    194. // tab信息
    195. const TabOptions = computed(() => {
    196. return {
    197. [TabsEnum.REGISTER]: {
    198. title: '注册信息',
    199. totalSum: tableListData[TabsEnum.REGISTER].totalRecords || 0
    200. },
    201. [TabsEnum.BATCH]: {
    202. title: '批次信息',
    203. totalSum: tableListData[TabsEnum.BATCH].totalRecords || 0
    204. }
    205. }
    206. })
    207. //组件内部维护的不同 tab 对应的信息
    208. let TabMapDataSource = reactive<TabMapDataSource>({
    209. [TabsEnum.REGISTER]: {
    210. //展示的cols
    211. showColumns: [],
    212. //编辑的cols信息 (dataIndexArr)
    213. selectShowColumns: []
    214. },
    215. [TabsEnum.BATCH]: {
    216. showColumns: [],
    217. selectShowColumns: []
    218. }
    219. })
    220. const isChangeTabMapDataSource = ref(true)
    221. watch(
    222. TabMapDataSource,
    223. () => {
    224. if (isChangeTabMapDataSource.value) {
    225. if (TabMapDataSource[TabsEnum.REGISTER].showColumns?.[0]) {
    226. ;(TabMapDataSource[TabsEnum.REGISTER].showColumns[0] as any).customTitle = true
    227. }
    228. isChangeTabMapDataSource.value = false
    229. }
    230. nextTick(() => {
    231. isChangeTabMapDataSource.value = true
    232. })
    233. },
    234. {
    235. immediate: false
    236. }
    237. )
    238. const showData = computed(() => {
    239. return _.isEmpty(TabMapDataSource[currentTab.value].selectShowColumns) ? ([] as []) : (data.value as [])
    240. })
    241. /**
    242. * 索引当前的列表信息
    243. * */
    244. const data = computed<MaterialData[]>(() => {
    245. refreshTableScroll()
    246. return tableListData[currentTab.value].data
    247. })
    248. //当前总的cols
    249. const currentAllCols = computed(() => tableListData[currentTab.value].allColumns)
    250. // const currentAllCols = ref<any[]>([])
    251. // watch(
    252. // () => tableListData[currentTab.value].allColumns,
    253. // (newAllColumns) => {
    254. // console.log(newAllColumns)
    255. // currentAllCols.value = newAllColumns || []
    256. // }
    257. // )
    258. //map形式的缓存
    259. const currentAllColsToMap = ref({})
    260. //初始化缓存
    261. watch(currentAllCols, (newCurrentCols) => {
    262. currentAllColsToMap.value = {}
    263. newCurrentCols?.map?.((v) => {
    264. currentAllColsToMap.value[v.dataIndex] = v
    265. })
    266. })
    267. //初始化展示的 cols
    268. watch(currentAllCols, () => {
    269. // 所有tab一起初始化,因为如果没有进入某个tab(未编辑),外面在保存时也能拿到该tab所有的列
    270. for (const type in TabMapDataSource) {
    271. if (_.isEmpty(TabMapDataSource[type].showColumns)) {
    272. TabMapDataSource[type].showColumns = tableListData[type].allColumns
    273. // console.log(tableListData[type].showColumns)
    274. }
    275. }
    276. })
    277. /**
    278. * 设置展示列相关
    279. */
    280. //初始化选项(选中全部)
    281. watch(currentAllCols, (newCurrentCols) => {
    282. if (_.isEmpty(TabMapDataSource[currentTab.value].selectShowColumns)) {
    283. const targetShowCols = tableListData[currentTab.value].showColumns || newCurrentCols
    284. TabMapDataSource[currentTab.value].selectShowColumns = targetShowCols.map((v) => v.dataIndex)
    285. // console.log(TabMapDataSource[currentTab.value].selectShowColumns )
    286. }
    287. })
    288. //(编辑展示列) newSelectShowColumns 就是一个 dataIndexArray
    289. watch(
    290. () => TabMapDataSource[currentTab.value].selectShowColumns,
    291. (newSelectShowColumns) => {
    292. TabMapDataSource[currentTab.value].showColumns = newSelectShowColumns.map((v) => currentAllColsToMap.value[v])
    293. addFixedCol()
    294. }
    295. )
    296. //追加固定列
    297. const addFixedCol = () => {
    298. TabMapDataSource[currentTab.value].showColumns.unshift({
    299. title: '序号',
    300. dataIndex: SERIAL_NUMBER,
    301. customTitle: true,
    302. isShowFliter: false
    303. })
    304. }
    305. /**
    306. * 选择表格数据相关
    307. */
    308. const selectedRowKeys: ShallowRef<string[]> = shallowRef([])
    309. const { selectedRaws: selectedMaterial, handleSelected, handleSelectAll } = useSelectedRows<MaterialData>(data)
    310. const initialSelectedRowKeys = () => {
    311. selectedRowKeys.value = []
    312. selectedMaterial.value = []
    313. tableRef.value.clearSelectedKeysCache()
    314. }
    315. const openStockInModal = () => {
    316. Emiter('openStockInModal', selectedMaterial.value)
    317. }
    318. const openStockOutModal = () => {
    319. // 通过发送自定义事件并将选中的数据发送给父组件index.vue,父组件执行自定义事件打开模态框并通过provide暴露给StockOutModal组件,它用inject接收展示选中的数据
    320. Emiter('openStockOutModal', selectedMaterial.value)
    321. }
    322. /**
    323. * 切换tab
    324. */
    325. const changeTab = (item, type) => {
    326. initialPresetPagination()
    327. initialSelectedRowKeys()
    328. Emiter('changeTab', { type, paginationInfo })
    329. /**
    330. * TODO
    331. * 发送网络请求、刷新列表(可以在外部做)
    332. */
    333. }
    334. /**
    335. * 分页相关
    336. */
    337. let paginationInfo = reactive({
    338. pageNum: 1,
    339. pageSize: 10
    340. })
    341. const pagination = computed(() => {
    342. const { pageNum, pageSize } = paginationInfo
    343. return {
    344. total: tableListData[currentTab.value].totalRecords,
    345. showPageSize: true,
    346. current: pageNum,
    347. pageSize,
    348. //页码改变时触发
    349. onChange: (current) => {
    350. paginationInfo.pageNum = current
    351. Emiter('changePagination', { paginationInfo })
    352. },
    353. //数据条数改变时触发
    354. onPageSizeChange: (pageSize) => {
    355. paginationInfo.pageNum = 1
    356. paginationInfo.pageSize = pageSize
    357. Emiter('changePagination', { paginationInfo })
    358. }
    359. }
    360. })
    361. const initialPresetPagination = () => {
    362. paginationInfo.pageNum = 1
    363. paginationInfo.pageSize = 10
    364. }
    365. /**
    366. * 附加信息的合理展示
    367. */
    368. function showAdditionalFields(fieldArray, type) {
    369. fieldArray = fieldArray || []
    370. const array = fieldArray.map((field) => {
    371. const name = field.fieldName
    372. const values = customShowByColType(666, field.fieldType, field)
    373. return `${name}:${values}`
    374. })
    375. return type === 'string' ? array.join('、') : array
    376. }
    377. /**
    378. * 根据当前列的字段种类合理展示字段
    379. */
    380. function customShowByColType(type: TableColsTypeEnum, paramType, data) {
    381. //基础列,直接展示
    382. if (type === TableColsTypeEnum.BASIC_BATCH || type === TableColsTypeEnum.BASIC_Product) {
    383. return data
    384. }
    385. /**
    386. * 自定义字段
    387. */
    388. let paramValues = data?.paramValues
    389. paramValues = _.isEmpty(paramValues) ? [] : paramValues
    390. const filterValues = paramValues.map((v) => v.showName)
    391. switch (paramType) {
    392. //时间类型
    393. case ParameterType.MOMENT: {
    394. const showValues = filterValues.map((v) => moment(v).format('YYYY-MM-DD')).join('、')
    395. return showValues
    396. }
    397. default: {
    398. const showValues = filterValues.join('、')
    399. return showValues
    400. }
    401. }
    402. }
    403. const isShowEmpty = (content) => {
    404. return isEmpty(content)
    405. }
    406. /**
    407. * 合理展示序号列
    408. */
    409. function computedShowSerialNumber(num) {
    410. const { pageNum, pageSize } = paginationInfo
    411. const showNum = (pageNum - 1) * pageSize + num
    412. return showNum
    413. }
    414. /**
    415. * 切换 tab 后,更新滚动条位置
    416. */
    417. watch(
    418. () => currentTab.value,
    419. () => {
    420. refreshTableScroll()
    421. }
    422. )
    423. function refreshTableScroll() {
    424. nextTick(() => {
    425. const tableBody = document.querySelector('.arco-table-body')
    426. tableBody?.scroll({ left: 1, behavior: 'smooth' })
    427. })
    428. }
    429. /**
    430. * 判断是否展示 table 自带的 tooltip
    431. */
    432. type Postion = 'left' | 'top' | 'tl' | 'tr' | 'bottom' | 'bl' | 'br' | 'right' | 'lt' | 'lb' | 'rt' | 'rb' | undefined
    433. const toolTipPosition: Postion = 'left'
    434. // const isShowTableToolTip = (col) => {
    435. // // col.dataIndex !== 'productCustomParams' ? { position: 'bottom' } :false
    436. // return col.dataIndex !== 'productCustomParams' && col.dataIndex !== 'batchCustomParams'
    437. // ? { position: toolTipPosition, mini: true }
    438. // : false
    439. // }
    440. /**
    441. * 鼠标 hover 到库存信息列时 getStockInfo
    442. */
    443. const {
    444. loading: stockDetialLoading,
    445. fetchResource: getStockDetialInfoFetchHook,
    446. result: stockDetialRes
    447. } = useFetch(getStockDetialInfo, { // 这个函数封装了发送请求的逻辑,在请求接口需要用到 loading 状态的时候,直接用内部返回的 loading 状态,
    448. // 内部处理了重复调用接口 loading 状态失效的问题
    449. isLazy: true
    450. })
    451. const stockDetialInfoArr = ref<string[]>()
    452. //记录当前 hover 的库存信息 cell
    453. const mouseenterStockInfoCellId = ref<string>()
    454. const initMouseenterStockInfoCellId = debounce(() => {
    455. if (showFlag == false) {
    456. mouseenterStockInfoCellId.value = ''
    457. }
    458. }, 600)
    459. //获取库位详情信息
    460. const getStockInfo = debounce(async (record) => {
    461. await getStockDetialInfoFetchHook(record.id)
    462. updateStockDetialInfoArr(stockDetialRes.value.data)
    463. mouseenterStockInfoCellId.value = record.id
    464. }, 500)
    465. // 停留展示Stock信息
    466. let showFlag: boolean
    467. const showStock = () => {
    468. showFlag = true
    469. }
    470. const hideStock = () => {
    471. showFlag = false
    472. initMouseenterStockInfoCellId()
    473. }
    474. //构建展示库位详情信息的array
    475. const updateStockDetialInfoArr = (data: StockDetialInfo) => {
    476. const propNameMapPrefix = {
    477. inStockNum: '在库数量:',
    478. inStockAmount: '在库总量:',
    479. totalNum: '入库数量:',
    480. totalAmount: '入库总量:'
    481. }
    482. const detialItems = Object.entries(propNameMapPrefix).map(([key, prefix]) => `${prefix}${data[key] || 0}`)
    483. stockDetialInfoArr.value = detialItems
    484. }
    485. /**
    486. * 暴露给外部的state
    487. */
    488. const initTabMapDataSource = () => {
    489. for (const key in TabMapDataSource) {
    490. TabMapDataSource[key].showColumns = []
    491. TabMapDataSource[key].selectShowColumns = []
    492. }
    493. }
    494. const clearCacheData = () => {
    495. initTabMapDataSource()
    496. }
    497. const showCols = computed(() => {
    498. return {
    499. productShowCols: toRaw(TabMapDataSource[TabsEnum.REGISTER].showColumns),
    500. batchShowCols: toRaw(TabMapDataSource[TabsEnum.BATCH].showColumns)
    501. }
    502. })
    503. const allCols = computed(() => {
    504. return {
    505. productShowCols: toRaw(tableListData[TabsEnum.REGISTER].allColumns),
    506. batchShowCols: toRaw(tableListData[TabsEnum.BATCH].allColumns)
    507. }
    508. })
    509. // 跳转到注册详情页
    510. const JumpToRegister = (title: string, key: string) => {
    511. // console.log(title)
    512. // console.log(key)
    513. store.changeTagView({
    514. title: title,
    515. key: key,
    516. type: ComponentTypeEnum.PRODUCT_DETIAL
    517. })
    518. router.push('/main/register')
    519. }
    520. // 跳转到库存
    521. const JumpToStock = async (names: string) => {
    522. const { data } = await getViewDataMapCipher({
    523. currentNodeType: 'STOCK_STATUS',
    524. searchInfo: {
    525. showCols: [],
    526. pageNum: 1,
    527. pageSize: 10,
    528. keyword: '',
    529. filter: { productNames: [names], perception: 10, perfixes: [productPerfix] }
    530. },
    531. stockStatusNodeData: {
    532. id: 10,
    533. name: '在库',
    534. nodeType: 'STOCK_STATUS'
    535. },
    536. viewNodeData: {}
    537. })
    538. // console.log(names)
    539. // console.log(data)
    540. router.push(`/main/_inventory/search/${data}`)
    541. }
    542. defineExpose({
    543. selectedRowKeys,
    544. showCols,
    545. allCols,
    546. clearCacheData,
    547. currentAllCols,
    548. //分页相关
    549. paginationInfo,
    550. initialPresetPagination,
    551. //初始化选择项
    552. initialSelectedRowKeys
    553. })
    554. </script>
    555. <style scoped lang="scss">
    556. .table-list-wrapper {
    557. margin-top: 25px;
    558. background-color: #fff;
    559. .tabs-bar {
    560. margin: 16px 0px 8px 0px;
    561. display: flex;
    562. .tab-btn {
    563. width: 142px;
    564. height: 32px;
    565. display: flex;
    566. justify-content: center;
    567. align-items: center;
    568. background-color: #fff;
    569. border-radius: 32px;
    570. }
    571. .tab-active {
    572. color: #3358ff;
    573. background-color: #f2f3f5;
    574. }
    575. }
    576. .table-wrapper {
    577. position: relative;
    578. .editor-show-columns-btn {
    579. position: absolute;
    580. top: 0px;
    581. right: 0px;
    582. z-index: 1;
    583. display: flex;
    584. justify-content: center;
    585. align-items: center;
    586. width: 40px;
    587. height: 40px;
    588. font-size: 15px;
    589. border: 1px solid #e5e6eb;
    590. background-color: #f2f3f5;
    591. }
    592. }
    593. }
    594. .tool-tip-content-item {
    595. margin: 2px 0px;
    596. }
    597. .col-stock-detial-title {
    598. font-size: 15px;
    599. }
    600. .col-stock-detial-item {
    601. font-size: 13px;
    602. }
    603. .pointer {
    604. cursor: pointer;
    605. }
    606. .arco-tag.arco-tag-size-medium.arco-tag-checked {
    607. display: inline-block;
    608. max-width: 100%;
    609. span {
    610. width: 100%;
    611. display: inline-block;
    612. overflow: hidden;
    613. text-overflow: ellipsis;
    614. white-space: nowrap;
    615. }
    616. }
    617. </style>
    618. <style lang="scss">
    619. .tool-tip-content {
    620. display: flex;
    621. flex-direction: column;
    622. }
    623. </style>

    封装的useFetch。

    1. import { Ref, ref } from 'vue'
    2. interface IFetchResult {
    3. loading: Ref<boolean>
    4. error: Ref<any>
    5. result: Ref<any>
    6. fetchResource: (...args: any) => {}
    7. }
    8. interface IOptions<T> {
    9. params?: T
    10. isLazy?: boolean
    11. }
    12. /**
    13. * 包装请求接口的 hook,内部处理了 loading 状态,
    14. * 在请求接口需要用到 loading 状态的时候,直接用内部返回的 loading 状态,
    15. * 内部处理了重复调用接口 loading 状态失效的问题
    16. * @param {Function} api 需要请求的接口
    17. * @param {{ params: any, isLazy: boolean }} options 配置项 params: 参数信息, isLazy: 是否惰性触发(默认不惰性触发)
    18. * @returns {Object}
    19. */
    20. export const useFetch = <T, P>(
    21. api: Function,
    22. options: IOptions<T> = { params: undefined, isLazy: false }
    23. ): IFetchResult => {
    24. const loading = ref(false)
    25. const result = ref<P | null>(null)
    26. const error = ref(null)
    27. const { params, isLazy } = options
    28. const fetchResource = async (params) => {
    29. loading.value = true
    30. const data = await api(params)
    31. .then((res) => {
    32. loading.value = false
    33. result.value = res
    34. })
    35. .catch((e) => {
    36. if (e !== 'ERR_CANCELED') {
    37. loading.value = false
    38. }
    39. error.value = e
    40. })
    41. return data
    42. }
    43. !isLazy && fetchResource(params)
    44. return {
    45. loading,
    46. error,
    47. result,
    48. fetchResource
    49. }
    50. }

    image.png
    我写的出库页面,有权限的数据可以展示库存信息,直接跳转至库存;没有权限的数据需要申请出库,再打开个模态框,跳转到申请出库。通过watch监视父组件传过来的materialDataList并取出需要的字段拼成tableListData。再用watch监视tableListData,把它分成有权限的数组和没有权限的数组,当tableListData数据有变化时,对每个数据,取出后端接口需要的信息,统一将他们放到一个数组里,然后向后端发送请求,得到每个数据是否有权限的数组,再遍历变化的数据,为其添加一条hasStockPermission属性,判断该数据的hasStockPermission属性,将其加入不同的数组。

    1. <!--
    2. * @Author: zzd993
    3. * @Date: 2022-08-11 11:46:14
    4. * @LastEditors: zzd993
    5. * @LastEditTime: 2022-08-22 16:02:59
    6. * @FilePath: \elabnote-front-main\src\views\inventory\components\register-search\query-view-detail\components\stockOutModel\index.vue
    7. * Copyright (c) 2022 by BMY, All Rights Reserved.
    8. -->
    9. <template>
    10. <a-modal :width="900" :visible="visible" title-align="start" title="出库" @cancel="handleClose">
    11. <div class="warn">
    12. 全部{{ tableListData?.length }}项,其中{{
    13. applyArr.length
    14. }}项需向所属人或负责人<span>申请出库</span>出库,其余支持跳转至库存出库
    15. </div>
    16. <!-- 此处使用inject接收的materialDataList然后将其取出需要的字段拼成tableListData-->
    17. <BasicTable :columns="materialTableConfig" :data="tableListData" :pagination="false">
    18. <template #cell-productName="{ item }">
    19. <ALink>
    20. {{ item.record[item.column.dataIndex] }}
    21. </ALink>
    22. </template>
    23. <!-- 以 tag 形式展示的字段 -->
    24. <template #cell-inStockNum="{ item }">
    25. <template v-if="item.record.hasStockPermission">
    26. <!-- <a-tag @click="showInfo(item)"> -->
    27. <ATag @click="showInfo(item)">
    28. 在库数量:
    29. {{ item.record[item.column.dataIndex] }}
    30. </ATag>
    31. </template>
    32. <template v-else>
    33. <SvgIcon iconName="warning1" iconSize="24" />
    34. </template>
    35. </template>
    36. </BasicTable>
    37. <!--出库申请的模态框,传递props是需要申请出库的数据,子组件用emits通过父组件更新数据-->
    38. <ApplyStockOut ref="ApplyStockOutRef" v-model:applyArr="applyArr" @clear="clear" />
    39. <!--arco的插槽,允许自定义页脚-->
    40. <template #footer>
    41. <a-button @click="JumpStock">跳转至库存</a-button>
    42. <a-button type="primary" @click="handleOk">申请出库</a-button>
    43. </template>
    44. </a-modal>
    45. </template>
    46. <script lang="ts">
    47. type MaterialTableListItem = {
    48. productPrefix: string
    49. productName: string
    50. batchName: string | undefined
    51. id: string
    52. batchNum: number
    53. inStockNum: number
    54. owner: string | undefined
    55. [key: string]: any
    56. }
    57. </script>
    58. <script setup lang="ts">
    59. import _ from 'lodash'
    60. import BasicTable from '@/components/BasicTable/index.vue'
    61. import ApplyStockOut from './ApplyStockOut.vue'
    62. import { inject, ShallowRef, ref, watchEffect, watch, reactive } from 'vue'
    63. import { MaterialTypeEnum } from '@/common/enum'
    64. //types
    65. import type { MaterialData } from '../TableList.vue'
    66. // import type { TableData } from '@arco-design/web-vue'
    67. //utils
    68. //api
    69. import { getViewDataMapCipher } from '@/api/material'
    70. import { checkProductPermission } from '@/api/material'
    71. import { ReferenceList } from '@/api/material'
    72. //store
    73. import { materialTableConfig } from './tableConfig'
    74. import { useRouter } from 'vue-router'
    75. const router = useRouter()
    76. // 表格数据
    77. const materialDataList: ShallowRef<MaterialData[]> | undefined = inject('materialDataList')
    78. const tableListData = ref<MaterialTableListItem[] | undefined>(void 0)
    79. const visible = ref<boolean>(false)
    80. const emits = defineEmits(['update:visible', 'refreshCurrentTabTable'])
    81. // 跳转至库存的数组
    82. const jumpArr = reactive([] as any)
    83. // 申请出库的数组
    84. const applyArr = reactive([] as any)
    85. const showInfo = (item) => {
    86. // console.log(item)
    87. }
    88. // 请求的权限列表
    89. const referenceList: ReferenceList[] = reactive([])
    90. watch(tableListData, async (newVal) => {
    91. jumpArr.length = 0
    92. applyArr.length = 0
    93. referenceList.length = 0
    94. newVal?.forEach((item) => {
    95. if (!item.batchName) {
    96. item.referenceType = MaterialTypeEnum.PRODUCT
    97. } else {
    98. item.referenceType = MaterialTypeEnum.PRODUCT_BATCH
    99. }
    100. let referenceitem = {
    101. referenceType: item.referenceType,
    102. referenceId: item.id
    103. }
    104. referenceList.push(referenceitem)
    105. })
    106. const { data } = await checkProductPermission({ referenceList: referenceList })
    107. newVal?.forEach((item, index) => {
    108. item.hasStockPermission = data[index].hasStockPermission
    109. if (item.hasStockPermission) {
    110. jumpArr.push(item)
    111. // applyArr.push(item)
    112. } else {
    113. applyArr.push(item)
    114. }
    115. })
    116. // console.log('newVal', newVal)
    117. })
    118. //
    119. const clear = () => {
    120. if (tableListData.value) {
    121. // let newArr = tableListData.value.filter((item) => !applyArr.some((i) => item.id === i.id))
    122. // console.log(newArr)
    123. // console.log(jumpArr)
    124. tableListData.value = _.cloneDeep(jumpArr)
    125. // console.log(tableListData.value)
    126. }
    127. }
    128. // 监听materialDataList取出数据放到tableListData
    129. watchEffect(() => {
    130. if (materialDataList?.value) {
    131. // console.log('materialDataList', materialDataList.value)
    132. tableListData.value = materialDataList.value.map((item) => {
    133. const { id, productPrefix, owner, productName, batchName, batchNum, inStockNum, frameName, productId } = item
    134. return {
    135. id,
    136. productPrefix,
    137. owner,
    138. productName,
    139. batchName,
    140. batchNum,
    141. inStockNum,
    142. frameName,
    143. productId
    144. }
    145. })
    146. // console.log(tableListData.value)
    147. }
    148. })
    149. // 跳转到库存
    150. const JumpToStock = async () => {
    151. const productNamesArr = [] as any
    152. const prefixArr = [] as any
    153. jumpArr.forEach((item) => {
    154. productNamesArr.push(item.productName)
    155. prefixArr.push(item.productPrefix)
    156. })
    157. const { data } = await getViewDataMapCipher({
    158. currentNodeType: 'STOCK_STATUS',
    159. searchInfo: {
    160. showCols: [],
    161. pageNum: 1,
    162. pageSize: 10,
    163. keyword: '',
    164. filter: { productNames: productNamesArr, perception: 10, perfixes: prefixArr }
    165. },
    166. stockStatusNodeData: {
    167. id: 10,
    168. name: '在库',
    169. nodeType: 'STOCK_STATUS'
    170. },
    171. viewNodeData: {}
    172. })
    173. router.push(`/main/_inventory/search/${data}`)
    174. }
    175. const ApplyStockOutRef = ref<InstanceType<typeof ApplyStockOut>>()
    176. // 取消
    177. const handleClose = () => {
    178. emits('update:visible', false)
    179. }
    180. // 跳转到库存
    181. const JumpStock = () => {
    182. emits('update:visible', false)
    183. JumpToStock()
    184. }
    185. /**
    186. * 确定,打开申请出库模态框
    187. */
    188. const handleOk = () => {
    189. ApplyStockOutRef.value!.openModal()
    190. }
    191. </script>
    192. <style scoped lang="scss">
    193. :deep(.arco-form-item-label) {
    194. color: $font3Color;
    195. font-size: 14px;
    196. margin-top: 8px;
    197. }
    198. :deep(.arco-form-item) {
    199. margin-bottom: 0;
    200. }
    201. .warn {
    202. width: 858px;
    203. height: 40px;
    204. text-align: center;
    205. line-height: 40px;
    206. background: rgb(250, 155, 67);
    207. margin-bottom: 10px;
    208. span {
    209. color: blue;
    210. }
    211. }
    212. </style>
    1. /*
    2. * @Author: zzd993
    3. * @Date: 2022-08-12 18:04:02
    4. * @LastEditors: zzd993
    5. * @LastEditTime: 2022-08-22 16:03:52
    6. * @FilePath: \elabnote-front-main\src\views\inventory\components\register-search\query-view-detail\components\stockOutModel\tableConfig.ts
    7. * Copyright (c) 2022 by BMY, All Rights Reserved.
    8. */
    9. import { TableColumnData } from '@arco-design/web-vue/es/table/interface'
    10. export const materialTableConfig: TableColumnData[] = [
    11. {
    12. title: '样本编号',
    13. dataIndex: 'productPrefix',
    14. slotName: 'cell-productPrefix'
    15. },
    16. {
    17. title: '样本名称',
    18. dataIndex: 'productName',
    19. slotName: 'cell-productName'
    20. },
    21. {
    22. title: '批次号',
    23. dataIndex: 'batchName',
    24. slotName: 'cell-batchName'
    25. },
    26. {
    27. title: '库存信息',
    28. dataIndex: 'inStockNum',
    29. slotName: 'cell-inStockNum'
    30. },
    31. {
    32. title: '所属人',
    33. dataIndex: 'owner'
    34. // slotName: 'cell-owner'
    35. }
    36. ]

    image.png

    1. <template>
    2. <a-modal
    3. :width="900"
    4. :visible="visible"
    5. title-align="start"
    6. title="申请出库"
    7. @cancel="handleCancel"
    8. @ok="handleOk"
    9. okText="确定"
    10. unmount-on-close
    11. >
    12. <BasicTable :columns="columns" :data="applyArr" :pagination="false">
    13. <template #productName="{ item }">
    14. <a-link> {{ item.record.productName }} </a-link>
    15. </template>
    16. <!-- 申请量 -->
    17. <template #total="{ item }">
    18. <a-input-number :min="0" type="text" v-model="item.record.approveNum" size="mini" />
    19. </template>
    20. <!-- 量单位 -->
    21. <template #unitName="{ item }">
    22. <a-select
    23. :options="unitsOptions"
    24. :field-names="{ label: 'name', value: 'id' }"
    25. size="mini"
    26. default-value=""
    27. // id是每次变化时拿到的单位的id,这里不能破坏arco组件,所以想要再传递item信息需要返回一个函数
    28. @change="(id) => onChange(id, item)"
    29. />
    30. </template>
    31. <!-- 备注 -->
    32. <template #remarks="{ item }">
    33. <a-input type="text" v-model="item.record.remarks" placeholder="请填写备注" />
    34. </template>
    35. </BasicTable>
    36. <a-form ref="FormRef" :model="form" :style="{ width: '300px' }" layout="vertical">
    37. <a-form-item field="recallLocale" label="请选择出库地点">
    38. <a-select v-model="form.recallLocale" style="width: 100%">
    39. <a-option
    40. v-for="(label, localeItem) of recallLocaleOptions"
    41. :key="localeItem"
    42. :value="localeItem"
    43. :label="label"
    44. />
    45. </a-select>
    46. </a-form-item>
    47. <div class="recall-detial" v-if="form.recallLocale == RecallLocale.IN_PRIVATE">
    48. <SelectOutStockProject ref="SelectOutStockProjectRef" />
    49. <a-form-item field="recallDate" label="如果需要返库,请选择返库时间">
    50. <a-date-picker v-model="form.recallDate" style="width: 100%" />
    51. </a-form-item>
    52. </div>
    53. </a-form>
    54. </a-modal>
    55. </template>
    56. <script lang="ts">
    57. import { RecallLocale } from '@/api/material'
    58. import { isEmpty } from 'lodash'
    59. const recallLocaleOptions = Object.freeze({
    60. [RecallLocale.IN_PRIVATE]: '研发',
    61. [RecallLocale.GUGANGZHOU_WITHDRAWAL]: '广州&退仓'
    62. })
    63. interface FormInfo {
    64. recallLocale: RecallLocale
    65. recallDate: Date | string
    66. projectIds: string[]
    67. }
    68. </script>
    69. <script setup lang="ts">
    70. //components
    71. import SelectOutStockProject from '@/views/inventory/components/search/query-view-detail/components/SelectOutStockProject.vue'
    72. //api
    73. import { outApprove } from '@/api/material'
    74. //utils
    75. import { computed, reactive, ref } from 'vue'
    76. //type
    77. import type { TableColumnData, Form } from '@arco-design/web-vue'
    78. import { IoutApproveData } from '@/api/material'
    79. import { useUserStore } from '@/store/modules/user'
    80. const visible = ref<boolean>(false)
    81. interface ItableListData {
    82. productPrefix: string
    83. productName: string
    84. batchName: string | undefined
    85. id: string
    86. batchNum: number
    87. inStockNum: number
    88. owner: string | undefined
    89. [x: string]: any
    90. }
    91. const props = defineProps<{
    92. applyArr: ItableListData[] | undefined
    93. }>()
    94. const onChange = (id, item) => {
    95. unitsOptions.forEach((i) => {
    96. if (i.id === id) {
    97. item.record.unitId = i.id
    98. item.record.unitName = i.name
    99. }
    100. })
    101. // console.log(item)
    102. }
    103. const emits = defineEmits(['update: applyArr', 'clear'])
    104. const FormRef = ref<InstanceType<typeof Form> | null>(null)
    105. const form = reactive<FormInfo>({
    106. recallLocale: RecallLocale.IN_PRIVATE,
    107. recallDate: undefined as unknown as Date,
    108. projectIds: []
    109. })
    110. // 初始化表单
    111. const initFormInfo = () => {
    112. // 出库地点
    113. form.recallLocale = RecallLocale.IN_PRIVATE
    114. // 返库日期
    115. form.recallDate = ''
    116. // 项目id
    117. form.projectIds = []
    118. }
    119. const userStore = useUserStore()
    120. // 在store中取出单位
    121. const unitsOptions = userStore.volumeUnits.map((unit) => ({ id: unit.id, name: unit.unitName }))
    122. const STOCK_COLUMNS: Array<TableColumnData> = [
    123. {
    124. title: '序号',
    125. width: 80,
    126. render: ({ rowIndex }) => rowIndex + 1
    127. },
    128. {
    129. title: '样本编号',
    130. dataIndex: 'productPrefix',
    131. width: 80,
    132. ellipsis: true,
    133. tooltip: true
    134. },
    135. {
    136. title: '样本名称',
    137. dataIndex: 'productName',
    138. slotName: 'productName',
    139. width: 80,
    140. ellipsis: true,
    141. tooltip: true
    142. },
    143. {
    144. title: '批次号',
    145. dataIndex: 'batchName',
    146. slotName: 'batchName',
    147. width: 80,
    148. ellipsis: true,
    149. tooltip: true
    150. },
    151. {
    152. title: '申请量',
    153. dataIndex: 'total',
    154. slotName: 'total',
    155. width: 80,
    156. ellipsis: true,
    157. tooltip: true
    158. },
    159. {
    160. title: '量单位',
    161. dataIndex: 'unitName',
    162. slotName: 'unitName',
    163. width: 80,
    164. ellipsis: true,
    165. tooltip: true
    166. },
    167. {
    168. title: '备注',
    169. dataIndex: 'remarks',
    170. slotName: 'remarks',
    171. width: 120,
    172. ellipsis: true,
    173. tooltip: true
    174. }
    175. ]
    176. const columns = computed(() => {
    177. return STOCK_COLUMNS
    178. })
    179. const handleCancel = () => {
    180. visible.value = false
    181. }
    182. /**
    183. * 确定
    184. */
    185. const SelectOutStockProjectRef = ref()
    186. function handleConfirmCancelModal() {
    187. handleCancel()
    188. }
    189. async function handleOutStock(outApproveData: IoutApproveData) {
    190. await outApprove(outApproveData)
    191. if (props.applyArr) {
    192. props.applyArr.length = 0
    193. }
    194. emits('clear')
    195. handleConfirmCancelModal()
    196. }
    197. function confirmOperate() {
    198. FormRef.value!.validate(async (error) => {
    199. if (!isEmpty(error)) return
    200. let productOrBatchStockOutList
    201. const directStockOut = form.recallLocale
    202. let outStockData = { directStockOut: directStockOut }
    203. if (directStockOut === RecallLocale.IN_PRIVATE) {
    204. if (props.applyArr) {
    205. // 拼接要给后端的数据
    206. productOrBatchStockOutList = props.applyArr.map((item) => ({
    207. productId: item.productId as string,
    208. productNo: item.productPrefix as string,
    209. productName: item.productName as string,
    210. batchId: item.id,
    211. batchNo: item.batchName,
    212. approveNum: item.approveNum,
    213. measuringUnitId: item.unitId,
    214. measuringUnitName: item.unitName,
    215. remarks: item.remarks as string
    216. }))
    217. const { isValidate, projectIds } = await SelectOutStockProjectRef.value.validate()
    218. if (!isValidate) return
    219. form.projectIds = projectIds
    220. Object.assign(outStockData, {
    221. productOrBatchStockOutList,
    222. projectIds: form.projectIds,
    223. recallDate: form.recallDate as unknown as string,
    224. isLocked: 2
    225. })
    226. await handleOutStock(outStockData)
    227. }
    228. } else {
    229. if (props.applyArr) {
    230. productOrBatchStockOutList = props.applyArr.map((item) => ({
    231. productId: item.productId as string,
    232. productNo: item.productPrefix as string,
    233. productName: item.productName as string,
    234. batchId: item.id,
    235. batchNo: item.batchName,
    236. approveNum: item.approveNum,
    237. measuringUnitId: item.unitId,
    238. measuringUnitName: item.unitName,
    239. remarks: item.remarks as string
    240. }))
    241. Object.assign(outStockData, {
    242. productOrBatchStockOutList,
    243. projectIds: null,
    244. recallDate: null,
    245. isLocked: 2
    246. })
    247. await handleOutStock(outStockData)
    248. }
    249. }
    250. })
    251. return true
    252. }
    253. const handleOk = async () => {
    254. confirmOperate()
    255. }
    256. defineExpose({
    257. // 打开模态框初始化表单,并给props的每个数据加入属性:申请量、单位id、单位名称、备注
    258. openModal: () => {
    259. initFormInfo()
    260. if (props && props.applyArr) {
    261. props.applyArr.map((item) => ({
    262. ...item,
    263. approveNum: 0,
    264. unitId: '',
    265. unitName: '',
    266. remarks: ''
    267. }))
    268. }
    269. visible.value = true
    270. }
    271. })
    272. </script>
    273. <style scoped lang="scss">
    274. :deep(.arco-form-item-label) {
    275. color: $font3Color;
    276. font-size: 14px;
    277. margin-top: 8px;
    278. }
    279. :deep(.arco-form-item) {
    280. margin-bottom: 0;
    281. }
    282. </style>