
<template><div class="query-view-detail"><DetailHeader@saveModalHandleOpen="saveModalHandleOpen"@powerModalHandleOpen="powerModalHandleOpen"@deleteModalHandleOpen="deleteModalHandleOpen"@handleSelectWarnTypes="handleSelectWarnTypes"@exportExcel="exportExcel"/><main><SearchBarref="searchBarRef"@handleSearch="handleSearch"@updateFilter="updateFilter":filter="filter":frameFilters="frameFilters":currentTab="currentTab"/><TableListref="tableListRef":loading="refreshTable.loading"@changeTab="changeTab" // 改变tab@changePagination="changePagination":currentTab="currentTab" // 有注册信息和批次信息两个tab:tableListData="tableListData" // 传递的表格数据@openStockInModal="openStockInModal"@openStockOutModal="openStockOutModal" // 出库/></main><!-- Modals --><SaveModalref="saveModalRef":filter="filter"@saveModalMakeViewHandleOk="saveModalMakeViewHandleOk"@saveModalUpdateViewHandleOk="saveModalUpdateViewHandleOk"/><PowerModal ref="powerModalRef" @powerModalHandleOk="powerModalHandleOk" /><DeleteModal ref="deleteModalRef" @deleteModalHandleOk="deleteModalHandleOk" /><StockInModal v-model:visible="stockInModalVisible" @refreshCurrentTabTable="refreshCurrentTabTable" /><!-- 打开出库模态框:在TableList中添加数据点击出库,发送自定义事件给父组件--><StockOutModal v-model:visible="stockOutModalVisible" @refreshCurrentTabTable="refreshCurrentTabTable" /></div></template><script lang="ts">export type FrameFilter = {fieldId: string | nullfieldKey?: stringfieldName: stringfieldType: FilterOriginTypefilterType: TableColsTypeEnumpreDefinedValues: string[]}</script><script setup lang="ts">//componentsimport TableList, { MaterialData } from './components/TableList.vue'import DetailHeader from './components/DetailHeader.vue'import SaveModal from './components/SaveModal.vue'import PowerModal from './components/PowerModal.vue'import DeleteModal from './components/DeleteModal.vue'import SearchBar from './components/SearchBar.vue'import { Message } from '@arco-design/web-vue'// import ResidualQuantityWarn from './components/ResidualQuantityWarn.vue'import StockInModal from './components/stockInModal/index.vue'import StockOutModal from './components/stockOutModel/index.vue'//utilsimport { ref, reactive, computed, inject, toRaw, shallowRef, provide } from 'vue'import {initSearchInfo,// 批次还是注册信息的枚举TabsEnum,buildColumns,TreeNodeTypeEnum,deBuildColumns,SaveViewModeEnmu} from '@/views/register/registration-and-batch-info-query/utils'import { useFetch } from '@/hooks'//apiimport {// 搜索批次信息的接口searchBatchInfoList,// 搜索注册信息的接口searchProductInfoList,updateSearchView,saveSearchView,deleteSearchView,getProductFrameFilters,getExportColumnsData,getExportProd} from '@/api/material'//typesimport { Filter, FilterOriginType, SearchInfo, TableColsTypeEnum } from '../types'import { exportExcelModeEnum, searchSourceEnum } from '@/views/inventory/components/register-search/types'import { SearchTypeEnmu } from '@/common/enum'//pluginimport { useI18n } from 'vue-i18n'import _, { cloneDeep } from 'lodash'const { t } = useI18n()const RootData = inject('rootData')const { treeNodeData } = RootData as anyconst Emiter = defineEmits(['saveView', 'updateView', 'deleteView', 'powerModalHandleOk'])const globalSearchInfo = ref(initSearchInfo())const setGlobalSearchInfo = (searchInfo) => (globalSearchInfo.value = searchInfo)const tableListRef = ref()const stockInModalVisible = ref<boolean>(false)const stockOutModalVisible = ref<boolean>(false)const materialDataList = shallowRef<MaterialData[] | undefined>(void 0)provide('materialDataList', materialDataList)const openStockInModal = (data: MaterialData[]) => {stockInModalVisible.value = truematerialDataList.value = data}// 打开出库模态框,const openStockOutModal = (data: MaterialData[]) => {stockOutModalVisible.value = truematerialDataList.value = data}const searchBarRef = ref()/*** DetailHeader 相关业务*/const saveModalRef = ref()const powerModalRef = ref()const deleteModalRef = ref()const saveModalHandleOpen = () => {saveModalRef.value.saveModalHandleOpen(SaveViewModeEnmu.CREATE_VIEW, {})}const powerModalHandleOpen = () => {powerModalRef.value.powerModalHandleOpen()}const deleteModalHandleOpen = () => {deleteModalRef.value.deleteModalHandleOpen()}const handleSelectWarnTypes = async (selectWarnTypes) => {const newFilter = Object.assign(globalSearchInfo.value.filter, { alarmTypes: selectWarnTypes })const searchInfo = globalSearchInfo.valueawait refreshAllTabTable({ searchInfo, searchSource: searchSourceEnum.FILTER })}/*** 根据不同的tab,映射不同的接口,获取对应的数据,这里要根据筛选条件去后端搜索*/const refreshTabTableMaps = {// 如果是注册表格[TabsEnum.REGISTER]: async ({searchInfo,searchSource}: {searchInfo: SearchInfosearchSource: searchSourceEnum}) => {let ProductTableInfo = await searchProductInfoList(searchInfo)//拼接展示列的信息switch (searchSource) {case searchSourceEnum.SELECT_TREE_NODE: {ProductTableInfo.allColumns = buildColumns(TabsEnum.REGISTER, searchInfo.productShowCols)break}default: {ProductTableInfo.allColumns = tableListRef.value.allCols.productShowCols// ProductTableInfo.showColumns = tableListRef.value.showCols.productShowCols}}// ProductTableInfo.allColumns = buildColumns(TabsEnum.REGISTER, searchInfo.productShowCols)ProductTableInfo.data = parseCustomParams(ProductTableInfo.data)tableListData[TabsEnum.REGISTER] = ProductTableInfosetGlobalSearchInfo(cloneDeep(toRaw(searchInfo)))filter.value = cloneDeep(toRaw(searchInfo.filter))},// 如果是批次信息[TabsEnum.BATCH]: async ({searchInfo,searchSource}: {searchInfo: SearchInfosearchSource: searchSourceEnum}) => {let BatchTableInfo = await searchBatchInfoList(searchInfo)//拼接展示列的信息switch (searchSource) {case searchSourceEnum.SELECT_TREE_NODE: {BatchTableInfo.allColumns = buildColumns(TabsEnum.BATCH, searchInfo.batchShowCols)break}default: {BatchTableInfo.allColumns = tableListRef.value.allCols.batchShowCols// BatchTableInfo.showColumns = tableListRef.value.showCols.batchShowCols}}// BatchTableInfo.allColumns = buildColumns(TabsEnum.BATCH, searchInfo.batchShowCols)BatchTableInfo.data = parseCustomParams(BatchTableInfo.data)tableListData[TabsEnum.BATCH] = BatchTableInfosetGlobalSearchInfo(cloneDeep(toRaw(searchInfo)))filter.value = cloneDeep(toRaw(searchInfo.filter))}}//将一些自定义字段解析到每个 dataItem 的外部function parseCustomParams(data) {return data.map((row, index) => {let batchParams = row?.batchParams || []let productParams = row?.productParams || []const params = [...batchParams, ...productParams]params.map((v) => {row[v.fieldId] = v})//标记序号row.index = index + 1return row})}/*** 刷新所有tab数据*/const refreshAllTabTable = async ({searchInfo,searchSource}: {searchInfo: SearchInfosearchSource: searchSourceEnum}) => {refreshTable.value.loading.value = truetableListRef.value.clearCacheData() //清除之前表单的缓存数据await Promise.all(Object.values(refreshTabTableMaps).map((refreshFunc) => refreshFunc({ searchInfo, searchSource }))).finally(() => {refreshTable.value.loading.value = false})//回显对应的 keywordsearchBarRef.value.setKeywrod(searchInfo.keyword)}/*** 刷新当前tab数据*/const refreshCurrentTabTable = () => {refreshTabTableMaps[currentTab.value]({ searchInfo: globalSearchInfo.value, searchSource: searchSourceEnum.FILTER })tableListRef.value.initialSelectedRowKeys()}/*** tab 相关*/const currentTab = reactive<{ value: TabsEnum }>({ value: TabsEnum.REGISTER })// computed返回的是ref,之后访问时需要通过valueconst refreshTable = computed(() => {return useFetch(refreshTabTableMaps[currentTab.value], {isLazy: true})})// 切换tab事件const changeTab = async ({ type, paginationInfo }) => {currentTab.value = Number(type)const { pageNum, pageSize } = paginationInfoawait refreshTable.value.fetchResource({searchInfo: Object.assign(globalSearchInfo.value, { pageNum, pageSize }),searchSource: searchSourceEnum.CHANGE_TAB})}//表单总数const tableListData = reactive({[TabsEnum.REGISTER]: {},[TabsEnum.BATCH]: {}})const filter = ref<Filter>({})/*** 搜索按钮事件* TODO* 根据筛选条件进行搜索*/const handleSearch = ({ keyword = '' }: { keyword: string }) => {// console.log('搜索', keyWord, tableListRef.value.selectedKeys)//根据不同的节点类型,拿到正确的frameIdlet frameIdswitch (treeNodeData.value.nodeType) {case TreeNodeTypeEnum.PRODUCT_FRAM: {frameId = treeNodeData.value.id}case TreeNodeTypeEnum.VIEW: {frameId = treeNodeData.value.parentId}default: {frameId = treeNodeData.value.id}}//初始化分页数据tableListRef.value.initialPresetPagination()const pageNum = tableListRef.value.paginationInfo.pageNum//刷新列表const targetParams = { ...globalSearchInfo.value, keyword, pageNum }refreshTable.value.fetchResource({ searchInfo: targetParams, searchSource: searchSourceEnum.FILTER })//清空选择列tableListRef.value.initialSelectedRowKeys()}/*** 筛选条件更新到globalSearchInfo*/const updateFilter = (filter: Filter) => {Object.assign(globalSearchInfo.value.filter, filter)}/*** 导出接口*/const exportInfo = {[TabsEnum.REGISTER]: {method: getExportProd,prefixName: t('register.registrationAndBatchInfoQuery.queryViewDetail.product')},[TabsEnum.BATCH]: {method: getExportColumnsData,prefixName: t('register.registrationAndBatchInfoQuery.queryViewDetail.batch')}}/*** 导出*/const exportExcel = async (mode, callback: Function) => {let condition = {}let seletCols: {productShowCols: any[]batchShowCols: any[]} = {productShowCols: [],batchShowCols: []}switch (mode) {case exportExcelModeEnum.CURRENT_SHOW: {const { productShowCols, batchShowCols } = tableListRef.value.showColsseletCols.productShowCols = deBuildColumns(productShowCols)seletCols.batchShowCols = deBuildColumns(batchShowCols)condition = Object.assign({}, globalSearchInfo.value, seletCols)break}default: {condition = Object.assign({}, globalSearchInfo.value, tableListRef.value.currentAllCols)break}}const { data } = await exportInfo[currentTab.value].method(condition)callback(data, { fileName: `${exportInfo[currentTab.value].prefixName}_${treeNodeData.value.name}` })}/*** 改变分页*/const changePagination = async ({ paginationInfo }) => {const { pageNum, pageSize } = paginationInfo// const { productShowCols, batchShowCols } = tableListRef.value.showColsawait refreshTable.value.fetchResource({searchInfo: Object.assign(globalSearchInfo.value, {pageNum,pageSize}),searchSource: searchSourceEnum.FILTER})// const targetColsPropName = currentTab.value === TabsEnum.REGISTER ? 'productShowCols' : 'batchShowCols'// const allColumns = cloneDeep(globalSearchInfo.value[targetColsPropName])// await refreshTable.value.fetchResource({// searchInfo: Object.assign(globalSearchInfo.value, {// pageNum,// pageSize,// productShowCols: deBuildColumns(productShowCols),// batchShowCols: deBuildColumns(batchShowCols)// }),// allColumns// })}/*** Modal相关*///打包主要的视图信息const buildViewMainInfo = ({ viewInfo }) => {//保存的视图信息const { viewName, viewDesc } = viewInfo//筛选条件const { productShowCols, batchShowCols } = tableListRef.value.showColsglobalSearchInfo.value.productShowCols = deBuildColumns(productShowCols)globalSearchInfo.value.batchShowCols = deBuildColumns(batchShowCols)const condition = globalSearchInfo.valuereturn { viewName, viewDesc, condition }}//保存视图const saveModalMakeViewHandleOk = async (info, closeSaveModal, extraData) => {const { viewName, viewDesc, condition } = buildViewMainInfo({ viewInfo: info })//其他信息const searchType = SearchTypeEnmu.PRODUCT_INFOconst frameId = extraData?.frameIdlet referenceIdif (treeNodeData.value.nodeType === TreeNodeTypeEnum.PRODUCT_FRAM) {referenceId = frameId || treeNodeData.value.key}if (treeNodeData.value.nodeType === TreeNodeTypeEnum.VIEW) {referenceId = frameId || treeNodeData.value.parentId}//传递的总数居const data = { viewName, viewDesc, condition, searchType, referenceId }// console.log('保存时,传递的总数居', data)await saveSearchView(data)//告知外部,让其刷新树列表Emiter('saveView', referenceId)closeSaveModal()}//更新视图const saveModalUpdateViewHandleOk = async (info, closeSaveModal) => {const { viewName, viewDesc, condition } = buildViewMainInfo({ viewInfo: info })//其他信息const searchType = SearchTypeEnmu.PRODUCT_INFOconst frameId = treeNodeData.value.parentIdconst id = treeNodeData.value.key//传递的总数居const data = { viewName, viewDesc, condition, searchType, id }// console.log('更新时,传递的总数居', data)try {await updateSearchView(data)} catch (error) {/*** 若保存失败,则要回溯对 globalSearchInfo 的更改* (buildViewMainInfo中,操作了globalSearchInfo)* */const { productShowCols, batchShowCols } = tableListRef.value.allColsglobalSearchInfo.value.productShowCols = deBuildColumns(productShowCols)globalSearchInfo.value.batchShowCols = deBuildColumns(batchShowCols)}//告知外部,让其刷新树列表Emiter('updateView', { frameId, viewName, viewDesc, condition })closeSaveModal()}//删除视图const deleteModalHandleOk = async (closeDeleteModal) => {await deleteSearchView(treeNodeData.value.id)Message.success('删除成功!')closeDeleteModal()const frameId = treeNodeData.value.parentIdEmiter('deleteView', frameId)}//保存视图权限const powerModalHandleOk = () => {//权限相关信息Emiter('powerModalHandleOk')}const frameFilters = ref<FrameFilter[]>([])const getFrameFilters = async (frameId: string) => {const res = await getProductFrameFilters(frameId)frameFilters.value = res.data}/*** 查看余量告警信息*/// async function handleClickWatchQuantityWarn() {// const newFilter = Object.assign(globalSearchInfo.value.filter, { alarmOn: SystemYesNoEnum.YES })// const searchInfo = globalSearchInfo.value// await refreshAllTabTable(searchInfo)// }/*** 暴露给外部的state* */const exposeSaveHandleOpen = computed(() => saveModalRef.value?.saveModalHandleOpen)const initialTablePresetPagination = computed(() => tableListRef.value?.initialPresetPagination)const initialTableSelectedRowKeys = computed(() => tableListRef.value?.initialSelectedRowKeys)defineExpose({refreshAllTabTable,getFrameFilters,saveModalHandleOpen: exposeSaveHandleOpen,initialTablePresetPagination,initialTableSelectedRowKeys})</script><style scoped lang="scss">.query-view-detail {background-color: #fff;main {padding: 0px 20px 24px;}}.pointer {cursor: pointer;}</style>
index.vue里面按照筛选条件获取相应的数据,把数据和当前的tab通过props带到TableList里,在TableList里展示选择的tab的数据的数量并通过自定义事件切换Tab。
<!--* @Author: Ashun* @Date: 2022-06-20 18:23:06* @LastEditors: zzd993* @LastEditTime: 2022-08-12 17:11:07* @FilePath: \elabnote-front-main\src\views\inventory\components\register-search\query-view-detail\components\TableList.vue* Copyright (c) 2022 by BMY, All Rights Reserved.--><template><div class="table-list-wrapper"><div class="tabs-bar"><divclass="tab-btn pointer":class="currentTab.value === Number(type) ? 'tab-active' : ''"v-for="(item, type) in TabOptions":key="type"@click="changeTab(item, type)">{{ item.title }}({{ item.totalSum }})</div></div><div class="table-wrapper"><BasicTableref="tableRef"rowKey="id"v-model:columns="TabMapDataSource[currentTab.value].showColumns" // 注册或者批次的表格头:data="showData" // 表格的数据:isShowFliter="true":columnsAll="currentAllCols":row-selection="rowSelection"v-model:selectedRowKeys="selectedRowKeys":pagination="pagination":scroll="{ y: 600 }"@select="handleSelected"@selectAll="handleSelectAll"column-resizable:bordered="{ headerCell: true }":loading="loading"><template #customTitle><a-space><span>已选定 {{ selectedRowKeys.length }} 个</span><a-button @click="openStockInModal" size="mini">入库</a-button><!-- 出库按钮,点击后调用方法触发自定义事件,将打勾选择的数据发送给父组件,父组件通过provide派发给子组件--><a-button @click="openStockOutModal" size="mini">出库</a-button></a-space></template><!-- 序号列 --><template #cell-serialNumber="{ item }"><a-tooltip :mini="true" :position="toolTipPosition" content-class="tool-tip-content"><template #content><span v-for="msg in item.record.alarmMsgs" :key="msg">{{ msg }}</span></template><icon-exclamation-circle-fill v-if="item.record.alarmOn === 1" :style="{ color: '#ff7d00' }" />{{ computedShowSerialNumber(item.record.index) }}</a-tooltip></template><!-- 产品/批次附加字段 --><template #cell-customParams="{ item }"><a-tooltip :mini="true" :position="toolTipPosition" content-class="tool-tip-content"><template #content><pv-for="(filed, filedKey) in showAdditionalFields(item.record[item.column.dataIndex], 'array')":key="filedKey"class="tool-tip-content-item">{{ filed }}</p></template><a-tag v-if="!isShowEmpty(showAdditionalFields(item.record[item.column.dataIndex], 'string'))"><span class="pointer">{{ showAdditionalFields(item.record[item.column.dataIndex], 'string') }}</span></a-tag></a-tooltip></template><!-- 以 link 形式展示的字段 --><template #cell-link="{ item }"><a-link v-if="item.column.dataIndex === 'batchNum'">批次数:{{ customShowByColType(item.column.type, item.column.paramType, item.record[item.column.dataIndex]) }}</a-link><!--如果是产品tab,点击产品名称就跳转到注册详情页面,--><a-linkv-else-if="item.column.dataIndex === 'productName'"@click="JumpToRegister(item.record[item.column.dataIndex], item.record.id)">{{ customShowByColType(item.column.type, item.column.paramType, item.record[item.column.dataIndex]) }}</a-link><a-link v-else>{{ customShowByColType(item.column.type, item.column.paramType, item.record[item.column.dataIndex]) }}</a-link></template><!-- 以 tag 形式展示的字段 --><template #cell-inStockNum="{ item }"><a-tooltip:position="toolTipPosition":popup-visible="!stockDetialLoading && item.record.id === mouseenterStockInfoCellId"><template #content><a-spin :loading="stockDetialLoading" @mouseenter="showStock" @mouseleave="hideStock"><span class="col-stock-detial-title">库存信息</span><p v-for="(str, key) in stockDetialInfoArr" :key="key" class="col-stock-detial-item">{{ str }}</p><a-linkv-if="!item.record.batchName"class="col-stock-detial-item"// 带着产品名称和编号跳转到库存搜索页面并筛选出对应的库存@click="JumpToStock(item.record.productName, item.record.productPrefix)">跳转至库存</a-link><a-link v-else class="col-stock-detial-item"@click="JumpToStock(item.record.batchName, item.record.productPrefix)">跳转至库存</a-link></a-spin></template><a-tag class="pointer" @mouseenter="getStockInfo(item.record)" @mouseout="initMouseenterStockInfoCellId">在库数量:{{ customShowByColType(item.column.type, item.column.paramType, item.record[item.column.dataIndex]) }}</a-tag></a-tooltip></template><template #cell-default="{ item }">{{ customShowByColType(item.column.type, item.column.paramType, item.record[item.column.dataIndex]) }}</template></BasicTable></div></div></template><script lang="ts">// 根据tab的不同切换展示的表头列信息type TabMapDataSource = {[key in TabsEnum]: {//展示的colsshowColumns: any[]//编辑的cols信息 (dataIndexArr)selectShowColumns: string[]}}interface StockDetialInfo {inStockNum: string | numberinStockAmount: string | numbertotalNum: string | numbertotalAmount: string | number}</script><script setup lang="ts">//apiimport { getStockDetialInfo } from '@/api/material'//componentsimport BasicTable from '@/components/BasicTable/index.vue'//utilsimport { ref, computed, Ref, watch, toRaw, reactive, nextTick, ShallowRef, shallowRef } from 'vue'import _, { isEmpty } from 'lodash'import moment from 'moment'import { SERIAL_NUMBER } from '@/common/constant'import { useFetch, useSelectedRows } from '@/hooks'import { debounce } from '@/util/tools/index'//typesimport { TableRowSelection } from '@arco-design/web-vue'import { TabsEnum } from '@/views/register/registration-and-batch-info-query/utils'import { TableColsTypeEnum } from '@/views/register/registration-and-batch-info-query/types'import { ParameterType } from '@/common/enum'import { useTagViewStore } from '@/store/modules/tag-view'import { ComponentTypeEnum } from '@/common/enum'import { useRouter } from 'vue-router'import { getViewDataMapCipher } from '@/api/material'export type MaterialData = {id: stringframeId: stringframeName: stringbatchName?: stringproductName: stringproductId: stringbatchNum: number[key: string]: any}const store = useTagViewStore()const router = useRouter()// const RootData = inject('rootData')// const { productFrameInfo } = RootData as any// const getProductFrameName = computed(() => productFrameInfo.value?.name)const { currentTab, tableListData, loading } = defineProps<{// 当前的tab和表格数据currentTab: { value: number }tableListData: anyloading: Ref<boolean>}>()const Emiter = defineEmits(['changeTab', 'changePagination', 'openStockInModal', 'openStockOutModal'])const tableRef = ref()const rowSelection: TableRowSelection = reactive({type: 'checkbox',showCheckedAll: true})// tab信息const TabOptions = computed(() => {return {[TabsEnum.REGISTER]: {title: '注册信息',totalSum: tableListData[TabsEnum.REGISTER].totalRecords || 0},[TabsEnum.BATCH]: {title: '批次信息',totalSum: tableListData[TabsEnum.BATCH].totalRecords || 0}}})//组件内部维护的不同 tab 对应的信息let TabMapDataSource = reactive<TabMapDataSource>({[TabsEnum.REGISTER]: {//展示的colsshowColumns: [],//编辑的cols信息 (dataIndexArr)selectShowColumns: []},[TabsEnum.BATCH]: {showColumns: [],selectShowColumns: []}})const isChangeTabMapDataSource = ref(true)watch(TabMapDataSource,() => {if (isChangeTabMapDataSource.value) {if (TabMapDataSource[TabsEnum.REGISTER].showColumns?.[0]) {;(TabMapDataSource[TabsEnum.REGISTER].showColumns[0] as any).customTitle = true}isChangeTabMapDataSource.value = false}nextTick(() => {isChangeTabMapDataSource.value = true})},{immediate: false})const showData = computed(() => {return _.isEmpty(TabMapDataSource[currentTab.value].selectShowColumns) ? ([] as []) : (data.value as [])})/*** 索引当前的列表信息* */const data = computed<MaterialData[]>(() => {refreshTableScroll()return tableListData[currentTab.value].data})//当前总的colsconst currentAllCols = computed(() => tableListData[currentTab.value].allColumns)// const currentAllCols = ref<any[]>([])// watch(// () => tableListData[currentTab.value].allColumns,// (newAllColumns) => {// console.log(newAllColumns)// currentAllCols.value = newAllColumns || []// }// )//map形式的缓存const currentAllColsToMap = ref({})//初始化缓存watch(currentAllCols, (newCurrentCols) => {currentAllColsToMap.value = {}newCurrentCols?.map?.((v) => {currentAllColsToMap.value[v.dataIndex] = v})})//初始化展示的 colswatch(currentAllCols, () => {// 所有tab一起初始化,因为如果没有进入某个tab(未编辑),外面在保存时也能拿到该tab所有的列for (const type in TabMapDataSource) {if (_.isEmpty(TabMapDataSource[type].showColumns)) {TabMapDataSource[type].showColumns = tableListData[type].allColumns// console.log(tableListData[type].showColumns)}}})/*** 设置展示列相关*///初始化选项(选中全部)watch(currentAllCols, (newCurrentCols) => {if (_.isEmpty(TabMapDataSource[currentTab.value].selectShowColumns)) {const targetShowCols = tableListData[currentTab.value].showColumns || newCurrentColsTabMapDataSource[currentTab.value].selectShowColumns = targetShowCols.map((v) => v.dataIndex)// console.log(TabMapDataSource[currentTab.value].selectShowColumns )}})//(编辑展示列) newSelectShowColumns 就是一个 dataIndexArraywatch(() => TabMapDataSource[currentTab.value].selectShowColumns,(newSelectShowColumns) => {TabMapDataSource[currentTab.value].showColumns = newSelectShowColumns.map((v) => currentAllColsToMap.value[v])addFixedCol()})//追加固定列const addFixedCol = () => {TabMapDataSource[currentTab.value].showColumns.unshift({title: '序号',dataIndex: SERIAL_NUMBER,customTitle: true,isShowFliter: false})}/*** 选择表格数据相关*/const selectedRowKeys: ShallowRef<string[]> = shallowRef([])const { selectedRaws: selectedMaterial, handleSelected, handleSelectAll } = useSelectedRows<MaterialData>(data)const initialSelectedRowKeys = () => {selectedRowKeys.value = []selectedMaterial.value = []tableRef.value.clearSelectedKeysCache()}const openStockInModal = () => {Emiter('openStockInModal', selectedMaterial.value)}const openStockOutModal = () => {// 通过发送自定义事件并将选中的数据发送给父组件index.vue,父组件执行自定义事件打开模态框并通过provide暴露给StockOutModal组件,它用inject接收展示选中的数据Emiter('openStockOutModal', selectedMaterial.value)}/*** 切换tab*/const changeTab = (item, type) => {initialPresetPagination()initialSelectedRowKeys()Emiter('changeTab', { type, paginationInfo })/*** TODO* 发送网络请求、刷新列表(可以在外部做)*/}/*** 分页相关*/let paginationInfo = reactive({pageNum: 1,pageSize: 10})const pagination = computed(() => {const { pageNum, pageSize } = paginationInforeturn {total: tableListData[currentTab.value].totalRecords,showPageSize: true,current: pageNum,pageSize,//页码改变时触发onChange: (current) => {paginationInfo.pageNum = currentEmiter('changePagination', { paginationInfo })},//数据条数改变时触发onPageSizeChange: (pageSize) => {paginationInfo.pageNum = 1paginationInfo.pageSize = pageSizeEmiter('changePagination', { paginationInfo })}}})const initialPresetPagination = () => {paginationInfo.pageNum = 1paginationInfo.pageSize = 10}/*** 附加信息的合理展示*/function showAdditionalFields(fieldArray, type) {fieldArray = fieldArray || []const array = fieldArray.map((field) => {const name = field.fieldNameconst values = customShowByColType(666, field.fieldType, field)return `${name}:${values}`})return type === 'string' ? array.join('、') : array}/*** 根据当前列的字段种类合理展示字段*/function customShowByColType(type: TableColsTypeEnum, paramType, data) {//基础列,直接展示if (type === TableColsTypeEnum.BASIC_BATCH || type === TableColsTypeEnum.BASIC_Product) {return data}/*** 自定义字段*/let paramValues = data?.paramValuesparamValues = _.isEmpty(paramValues) ? [] : paramValuesconst filterValues = paramValues.map((v) => v.showName)switch (paramType) {//时间类型case ParameterType.MOMENT: {const showValues = filterValues.map((v) => moment(v).format('YYYY-MM-DD')).join('、')return showValues}default: {const showValues = filterValues.join('、')return showValues}}}const isShowEmpty = (content) => {return isEmpty(content)}/*** 合理展示序号列*/function computedShowSerialNumber(num) {const { pageNum, pageSize } = paginationInfoconst showNum = (pageNum - 1) * pageSize + numreturn showNum}/*** 切换 tab 后,更新滚动条位置*/watch(() => currentTab.value,() => {refreshTableScroll()})function refreshTableScroll() {nextTick(() => {const tableBody = document.querySelector('.arco-table-body')tableBody?.scroll({ left: 1, behavior: 'smooth' })})}/*** 判断是否展示 table 自带的 tooltip*/type Postion = 'left' | 'top' | 'tl' | 'tr' | 'bottom' | 'bl' | 'br' | 'right' | 'lt' | 'lb' | 'rt' | 'rb' | undefinedconst toolTipPosition: Postion = 'left'// const isShowTableToolTip = (col) => {// // col.dataIndex !== 'productCustomParams' ? { position: 'bottom' } :false// return col.dataIndex !== 'productCustomParams' && col.dataIndex !== 'batchCustomParams'// ? { position: toolTipPosition, mini: true }// : false// }/*** 鼠标 hover 到库存信息列时 getStockInfo*/const {loading: stockDetialLoading,fetchResource: getStockDetialInfoFetchHook,result: stockDetialRes} = useFetch(getStockDetialInfo, { // 这个函数封装了发送请求的逻辑,在请求接口需要用到 loading 状态的时候,直接用内部返回的 loading 状态,// 内部处理了重复调用接口 loading 状态失效的问题isLazy: true})const stockDetialInfoArr = ref<string[]>()//记录当前 hover 的库存信息 cellconst mouseenterStockInfoCellId = ref<string>()const initMouseenterStockInfoCellId = debounce(() => {if (showFlag == false) {mouseenterStockInfoCellId.value = ''}}, 600)//获取库位详情信息const getStockInfo = debounce(async (record) => {await getStockDetialInfoFetchHook(record.id)updateStockDetialInfoArr(stockDetialRes.value.data)mouseenterStockInfoCellId.value = record.id}, 500)// 停留展示Stock信息let showFlag: booleanconst showStock = () => {showFlag = true}const hideStock = () => {showFlag = falseinitMouseenterStockInfoCellId()}//构建展示库位详情信息的arrayconst updateStockDetialInfoArr = (data: StockDetialInfo) => {const propNameMapPrefix = {inStockNum: '在库数量:',inStockAmount: '在库总量:',totalNum: '入库数量:',totalAmount: '入库总量:'}const detialItems = Object.entries(propNameMapPrefix).map(([key, prefix]) => `${prefix}${data[key] || 0}`)stockDetialInfoArr.value = detialItems}/*** 暴露给外部的state*/const initTabMapDataSource = () => {for (const key in TabMapDataSource) {TabMapDataSource[key].showColumns = []TabMapDataSource[key].selectShowColumns = []}}const clearCacheData = () => {initTabMapDataSource()}const showCols = computed(() => {return {productShowCols: toRaw(TabMapDataSource[TabsEnum.REGISTER].showColumns),batchShowCols: toRaw(TabMapDataSource[TabsEnum.BATCH].showColumns)}})const allCols = computed(() => {return {productShowCols: toRaw(tableListData[TabsEnum.REGISTER].allColumns),batchShowCols: toRaw(tableListData[TabsEnum.BATCH].allColumns)}})// 跳转到注册详情页const JumpToRegister = (title: string, key: string) => {// console.log(title)// console.log(key)store.changeTagView({title: title,key: key,type: ComponentTypeEnum.PRODUCT_DETIAL})router.push('/main/register')}// 跳转到库存const JumpToStock = async (names: string) => {const { data } = await getViewDataMapCipher({currentNodeType: 'STOCK_STATUS',searchInfo: {showCols: [],pageNum: 1,pageSize: 10,keyword: '',filter: { productNames: [names], perception: 10, perfixes: [productPerfix] }},stockStatusNodeData: {id: 10,name: '在库',nodeType: 'STOCK_STATUS'},viewNodeData: {}})// console.log(names)// console.log(data)router.push(`/main/_inventory/search/${data}`)}defineExpose({selectedRowKeys,showCols,allCols,clearCacheData,currentAllCols,//分页相关paginationInfo,initialPresetPagination,//初始化选择项initialSelectedRowKeys})</script><style scoped lang="scss">.table-list-wrapper {margin-top: 25px;background-color: #fff;.tabs-bar {margin: 16px 0px 8px 0px;display: flex;.tab-btn {width: 142px;height: 32px;display: flex;justify-content: center;align-items: center;background-color: #fff;border-radius: 32px;}.tab-active {color: #3358ff;background-color: #f2f3f5;}}.table-wrapper {position: relative;.editor-show-columns-btn {position: absolute;top: 0px;right: 0px;z-index: 1;display: flex;justify-content: center;align-items: center;width: 40px;height: 40px;font-size: 15px;border: 1px solid #e5e6eb;background-color: #f2f3f5;}}}.tool-tip-content-item {margin: 2px 0px;}.col-stock-detial-title {font-size: 15px;}.col-stock-detial-item {font-size: 13px;}.pointer {cursor: pointer;}.arco-tag.arco-tag-size-medium.arco-tag-checked {display: inline-block;max-width: 100%;span {width: 100%;display: inline-block;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}}</style><style lang="scss">.tool-tip-content {display: flex;flex-direction: column;}</style>
封装的useFetch。
import { Ref, ref } from 'vue'interface IFetchResult {loading: Ref<boolean>error: Ref<any>result: Ref<any>fetchResource: (...args: any) => {}}interface IOptions<T> {params?: TisLazy?: boolean}/*** 包装请求接口的 hook,内部处理了 loading 状态,* 在请求接口需要用到 loading 状态的时候,直接用内部返回的 loading 状态,* 内部处理了重复调用接口 loading 状态失效的问题* @param {Function} api 需要请求的接口* @param {{ params: any, isLazy: boolean }} options 配置项 params: 参数信息, isLazy: 是否惰性触发(默认不惰性触发)* @returns {Object}*/export const useFetch = <T, P>(api: Function,options: IOptions<T> = { params: undefined, isLazy: false }): IFetchResult => {const loading = ref(false)const result = ref<P | null>(null)const error = ref(null)const { params, isLazy } = optionsconst fetchResource = async (params) => {loading.value = trueconst data = await api(params).then((res) => {loading.value = falseresult.value = res}).catch((e) => {if (e !== 'ERR_CANCELED') {loading.value = false}error.value = e})return data}!isLazy && fetchResource(params)return {loading,error,result,fetchResource}}

我写的出库页面,有权限的数据可以展示库存信息,直接跳转至库存;没有权限的数据需要申请出库,再打开个模态框,跳转到申请出库。通过watch监视父组件传过来的materialDataList并取出需要的字段拼成tableListData。再用watch监视tableListData,把它分成有权限的数组和没有权限的数组,当tableListData数据有变化时,对每个数据,取出后端接口需要的信息,统一将他们放到一个数组里,然后向后端发送请求,得到每个数据是否有权限的数组,再遍历变化的数据,为其添加一条hasStockPermission属性,判断该数据的hasStockPermission属性,将其加入不同的数组。
<!--* @Author: zzd993* @Date: 2022-08-11 11:46:14* @LastEditors: zzd993* @LastEditTime: 2022-08-22 16:02:59* @FilePath: \elabnote-front-main\src\views\inventory\components\register-search\query-view-detail\components\stockOutModel\index.vue* Copyright (c) 2022 by BMY, All Rights Reserved.--><template><a-modal :width="900" :visible="visible" title-align="start" title="出库" @cancel="handleClose"><div class="warn">全部{{ tableListData?.length }}项,其中{{applyArr.length}}项需向所属人或负责人<span>申请出库</span>出库,其余支持跳转至库存出库</div><!-- 此处使用inject接收的materialDataList然后将其取出需要的字段拼成tableListData--><BasicTable :columns="materialTableConfig" :data="tableListData" :pagination="false"><template #cell-productName="{ item }"><ALink>{{ item.record[item.column.dataIndex] }}</ALink></template><!-- 以 tag 形式展示的字段 --><template #cell-inStockNum="{ item }"><template v-if="item.record.hasStockPermission"><!-- <a-tag @click="showInfo(item)"> --><ATag @click="showInfo(item)">在库数量:{{ item.record[item.column.dataIndex] }}</ATag></template><template v-else><SvgIcon iconName="warning1" iconSize="24" /></template></template></BasicTable><!--出库申请的模态框,传递props是需要申请出库的数据,子组件用emits通过父组件更新数据--><ApplyStockOut ref="ApplyStockOutRef" v-model:applyArr="applyArr" @clear="clear" /><!--arco的插槽,允许自定义页脚--><template #footer><a-button @click="JumpStock">跳转至库存</a-button><a-button type="primary" @click="handleOk">申请出库</a-button></template></a-modal></template><script lang="ts">type MaterialTableListItem = {productPrefix: stringproductName: stringbatchName: string | undefinedid: stringbatchNum: numberinStockNum: numberowner: string | undefined[key: string]: any}</script><script setup lang="ts">import _ from 'lodash'import BasicTable from '@/components/BasicTable/index.vue'import ApplyStockOut from './ApplyStockOut.vue'import { inject, ShallowRef, ref, watchEffect, watch, reactive } from 'vue'import { MaterialTypeEnum } from '@/common/enum'//typesimport type { MaterialData } from '../TableList.vue'// import type { TableData } from '@arco-design/web-vue'//utils//apiimport { getViewDataMapCipher } from '@/api/material'import { checkProductPermission } from '@/api/material'import { ReferenceList } from '@/api/material'//storeimport { materialTableConfig } from './tableConfig'import { useRouter } from 'vue-router'const router = useRouter()// 表格数据const materialDataList: ShallowRef<MaterialData[]> | undefined = inject('materialDataList')const tableListData = ref<MaterialTableListItem[] | undefined>(void 0)const visible = ref<boolean>(false)const emits = defineEmits(['update:visible', 'refreshCurrentTabTable'])// 跳转至库存的数组const jumpArr = reactive([] as any)// 申请出库的数组const applyArr = reactive([] as any)const showInfo = (item) => {// console.log(item)}// 请求的权限列表const referenceList: ReferenceList[] = reactive([])watch(tableListData, async (newVal) => {jumpArr.length = 0applyArr.length = 0referenceList.length = 0newVal?.forEach((item) => {if (!item.batchName) {item.referenceType = MaterialTypeEnum.PRODUCT} else {item.referenceType = MaterialTypeEnum.PRODUCT_BATCH}let referenceitem = {referenceType: item.referenceType,referenceId: item.id}referenceList.push(referenceitem)})const { data } = await checkProductPermission({ referenceList: referenceList })newVal?.forEach((item, index) => {item.hasStockPermission = data[index].hasStockPermissionif (item.hasStockPermission) {jumpArr.push(item)// applyArr.push(item)} else {applyArr.push(item)}})// console.log('newVal', newVal)})//const clear = () => {if (tableListData.value) {// let newArr = tableListData.value.filter((item) => !applyArr.some((i) => item.id === i.id))// console.log(newArr)// console.log(jumpArr)tableListData.value = _.cloneDeep(jumpArr)// console.log(tableListData.value)}}// 监听materialDataList取出数据放到tableListDatawatchEffect(() => {if (materialDataList?.value) {// console.log('materialDataList', materialDataList.value)tableListData.value = materialDataList.value.map((item) => {const { id, productPrefix, owner, productName, batchName, batchNum, inStockNum, frameName, productId } = itemreturn {id,productPrefix,owner,productName,batchName,batchNum,inStockNum,frameName,productId}})// console.log(tableListData.value)}})// 跳转到库存const JumpToStock = async () => {const productNamesArr = [] as anyconst prefixArr = [] as anyjumpArr.forEach((item) => {productNamesArr.push(item.productName)prefixArr.push(item.productPrefix)})const { data } = await getViewDataMapCipher({currentNodeType: 'STOCK_STATUS',searchInfo: {showCols: [],pageNum: 1,pageSize: 10,keyword: '',filter: { productNames: productNamesArr, perception: 10, perfixes: prefixArr }},stockStatusNodeData: {id: 10,name: '在库',nodeType: 'STOCK_STATUS'},viewNodeData: {}})router.push(`/main/_inventory/search/${data}`)}const ApplyStockOutRef = ref<InstanceType<typeof ApplyStockOut>>()// 取消const handleClose = () => {emits('update:visible', false)}// 跳转到库存const JumpStock = () => {emits('update:visible', false)JumpToStock()}/*** 确定,打开申请出库模态框*/const handleOk = () => {ApplyStockOutRef.value!.openModal()}</script><style scoped lang="scss">:deep(.arco-form-item-label) {color: $font3Color;font-size: 14px;margin-top: 8px;}:deep(.arco-form-item) {margin-bottom: 0;}.warn {width: 858px;height: 40px;text-align: center;line-height: 40px;background: rgb(250, 155, 67);margin-bottom: 10px;span {color: blue;}}</style>
/** @Author: zzd993* @Date: 2022-08-12 18:04:02* @LastEditors: zzd993* @LastEditTime: 2022-08-22 16:03:52* @FilePath: \elabnote-front-main\src\views\inventory\components\register-search\query-view-detail\components\stockOutModel\tableConfig.ts* Copyright (c) 2022 by BMY, All Rights Reserved.*/import { TableColumnData } from '@arco-design/web-vue/es/table/interface'export const materialTableConfig: TableColumnData[] = [{title: '样本编号',dataIndex: 'productPrefix',slotName: 'cell-productPrefix'},{title: '样本名称',dataIndex: 'productName',slotName: 'cell-productName'},{title: '批次号',dataIndex: 'batchName',slotName: 'cell-batchName'},{title: '库存信息',dataIndex: 'inStockNum',slotName: 'cell-inStockNum'},{title: '所属人',dataIndex: 'owner'// slotName: 'cell-owner'}]

<template><a-modal:width="900":visible="visible"title-align="start"title="申请出库"@cancel="handleCancel"@ok="handleOk"okText="确定"unmount-on-close><BasicTable :columns="columns" :data="applyArr" :pagination="false"><template #productName="{ item }"><a-link> {{ item.record.productName }} </a-link></template><!-- 申请量 --><template #total="{ item }"><a-input-number :min="0" type="text" v-model="item.record.approveNum" size="mini" /></template><!-- 量单位 --><template #unitName="{ item }"><a-select:options="unitsOptions":field-names="{ label: 'name', value: 'id' }"size="mini"default-value=""// id是每次变化时拿到的单位的id,这里不能破坏arco组件,所以想要再传递item信息需要返回一个函数@change="(id) => onChange(id, item)"/></template><!-- 备注 --><template #remarks="{ item }"><a-input type="text" v-model="item.record.remarks" placeholder="请填写备注" /></template></BasicTable><a-form ref="FormRef" :model="form" :style="{ width: '300px' }" layout="vertical"><a-form-item field="recallLocale" label="请选择出库地点"><a-select v-model="form.recallLocale" style="width: 100%"><a-optionv-for="(label, localeItem) of recallLocaleOptions":key="localeItem":value="localeItem":label="label"/></a-select></a-form-item><div class="recall-detial" v-if="form.recallLocale == RecallLocale.IN_PRIVATE"><SelectOutStockProject ref="SelectOutStockProjectRef" /><a-form-item field="recallDate" label="如果需要返库,请选择返库时间"><a-date-picker v-model="form.recallDate" style="width: 100%" /></a-form-item></div></a-form></a-modal></template><script lang="ts">import { RecallLocale } from '@/api/material'import { isEmpty } from 'lodash'const recallLocaleOptions = Object.freeze({[RecallLocale.IN_PRIVATE]: '研发',[RecallLocale.GUGANGZHOU_WITHDRAWAL]: '广州&退仓'})interface FormInfo {recallLocale: RecallLocalerecallDate: Date | stringprojectIds: string[]}</script><script setup lang="ts">//componentsimport SelectOutStockProject from '@/views/inventory/components/search/query-view-detail/components/SelectOutStockProject.vue'//apiimport { outApprove } from '@/api/material'//utilsimport { computed, reactive, ref } from 'vue'//typeimport type { TableColumnData, Form } from '@arco-design/web-vue'import { IoutApproveData } from '@/api/material'import { useUserStore } from '@/store/modules/user'const visible = ref<boolean>(false)interface ItableListData {productPrefix: stringproductName: stringbatchName: string | undefinedid: stringbatchNum: numberinStockNum: numberowner: string | undefined[x: string]: any}const props = defineProps<{applyArr: ItableListData[] | undefined}>()const onChange = (id, item) => {unitsOptions.forEach((i) => {if (i.id === id) {item.record.unitId = i.iditem.record.unitName = i.name}})// console.log(item)}const emits = defineEmits(['update: applyArr', 'clear'])const FormRef = ref<InstanceType<typeof Form> | null>(null)const form = reactive<FormInfo>({recallLocale: RecallLocale.IN_PRIVATE,recallDate: undefined as unknown as Date,projectIds: []})// 初始化表单const initFormInfo = () => {// 出库地点form.recallLocale = RecallLocale.IN_PRIVATE// 返库日期form.recallDate = ''// 项目idform.projectIds = []}const userStore = useUserStore()// 在store中取出单位const unitsOptions = userStore.volumeUnits.map((unit) => ({ id: unit.id, name: unit.unitName }))const STOCK_COLUMNS: Array<TableColumnData> = [{title: '序号',width: 80,render: ({ rowIndex }) => rowIndex + 1},{title: '样本编号',dataIndex: 'productPrefix',width: 80,ellipsis: true,tooltip: true},{title: '样本名称',dataIndex: 'productName',slotName: 'productName',width: 80,ellipsis: true,tooltip: true},{title: '批次号',dataIndex: 'batchName',slotName: 'batchName',width: 80,ellipsis: true,tooltip: true},{title: '申请量',dataIndex: 'total',slotName: 'total',width: 80,ellipsis: true,tooltip: true},{title: '量单位',dataIndex: 'unitName',slotName: 'unitName',width: 80,ellipsis: true,tooltip: true},{title: '备注',dataIndex: 'remarks',slotName: 'remarks',width: 120,ellipsis: true,tooltip: true}]const columns = computed(() => {return STOCK_COLUMNS})const handleCancel = () => {visible.value = false}/*** 确定*/const SelectOutStockProjectRef = ref()function handleConfirmCancelModal() {handleCancel()}async function handleOutStock(outApproveData: IoutApproveData) {await outApprove(outApproveData)if (props.applyArr) {props.applyArr.length = 0}emits('clear')handleConfirmCancelModal()}function confirmOperate() {FormRef.value!.validate(async (error) => {if (!isEmpty(error)) returnlet productOrBatchStockOutListconst directStockOut = form.recallLocalelet outStockData = { directStockOut: directStockOut }if (directStockOut === RecallLocale.IN_PRIVATE) {if (props.applyArr) {// 拼接要给后端的数据productOrBatchStockOutList = props.applyArr.map((item) => ({productId: item.productId as string,productNo: item.productPrefix as string,productName: item.productName as string,batchId: item.id,batchNo: item.batchName,approveNum: item.approveNum,measuringUnitId: item.unitId,measuringUnitName: item.unitName,remarks: item.remarks as string}))const { isValidate, projectIds } = await SelectOutStockProjectRef.value.validate()if (!isValidate) returnform.projectIds = projectIdsObject.assign(outStockData, {productOrBatchStockOutList,projectIds: form.projectIds,recallDate: form.recallDate as unknown as string,isLocked: 2})await handleOutStock(outStockData)}} else {if (props.applyArr) {productOrBatchStockOutList = props.applyArr.map((item) => ({productId: item.productId as string,productNo: item.productPrefix as string,productName: item.productName as string,batchId: item.id,batchNo: item.batchName,approveNum: item.approveNum,measuringUnitId: item.unitId,measuringUnitName: item.unitName,remarks: item.remarks as string}))Object.assign(outStockData, {productOrBatchStockOutList,projectIds: null,recallDate: null,isLocked: 2})await handleOutStock(outStockData)}}})return true}const handleOk = async () => {confirmOperate()}defineExpose({// 打开模态框初始化表单,并给props的每个数据加入属性:申请量、单位id、单位名称、备注openModal: () => {initFormInfo()if (props && props.applyArr) {props.applyArr.map((item) => ({...item,approveNum: 0,unitId: '',unitName: '',remarks: ''}))}visible.value = true}})</script><style scoped lang="scss">:deep(.arco-form-item-label) {color: $font3Color;font-size: 14px;margin-top: 8px;}:deep(.arco-form-item) {margin-bottom: 0;}</style>
