| | |
| | | import request from '@/utils/http' |
| | | |
| | | export function buildWarehouseStockPageParams(params = {}) { |
| | | const matnrCode = typeof params.matnrCode === 'string' ? params.matnrCode.trim() : params.matnrCode |
| | | const matnrCode = |
| | | typeof params.matnrCode === 'string' ? params.matnrCode.trim() : params.matnrCode |
| | | const maktx = typeof params.maktx === 'string' ? params.maktx.trim() : params.maktx |
| | | const batch = typeof params.batch === 'string' ? params.batch.trim() : params.batch |
| | | return { |
| | |
| | | ...Object.fromEntries( |
| | | Object.entries(params).filter( |
| | | ([key, value]) => |
| | | !['current', 'pageSize', 'size', 'aggType', 'matnrCode', 'maktx', 'batch'].includes(key) && |
| | | !['current', 'pageSize', 'size', 'aggType', 'matnrCode', 'maktx', 'batch'].includes( |
| | | key |
| | | ) && |
| | | value !== undefined && |
| | | value !== '' |
| | | ) |
| | |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20, |
| | | ...(params.aggType !== undefined ? { aggType: params.aggType } : {}), |
| | | ...(params.orderBy !== undefined ? { orderBy: params.orderBy } : {}), |
| | | ...(params.stock !== undefined ? { stock: params.stock } : {}) |
| | | } |
| | | } |
| | | |
| | | export function fetchWarehouseStockPage(params = {}) { |
| | | return request.post({ url: '/warehouse/stock/page', params: buildWarehouseStockPageParams(params) }) |
| | | return request.post({ |
| | | url: '/warehouse/stock/page', |
| | | params: buildWarehouseStockPageParams(params) |
| | | }) |
| | | } |
| | | |
| | | export function fetchWarehouseStockInfoPage(params = {}) { |
| | | return request.post({ url: '/warehouse/stock/info', params: buildWarehouseStockInfoParams(params) }) |
| | | return request.post({ |
| | | url: '/warehouse/stock/info', |
| | | params: buildWarehouseStockInfoParams(params) |
| | | }) |
| | | } |
| | | |
| | | export function fetchWarehouseStockHistoriesPage(params = {}) { |
| | |
| | | <template> |
| | | <div class="loc-preview-page art-full-height"> |
| | | <ArtSearchBar |
| | | v-model="searchForm" |
| | | :items="searchItems" |
| | | :showExpand="false" |
| | | @search="handleSearch" |
| | | @reset="handleReset" |
| | | /> |
| | | |
| | | <ElCard class="art-table-card"> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData" /> |
| | | |
| | | <ArtTable |
| | | :loading="loading" |
| | | :data="tableData" |
| | | :columns="columns" |
| | | :pagination="pagination" |
| | | @pagination:size-change="handleSizeChange" |
| | | @pagination:current-change="handleCurrentChange" |
| | | > |
| | | <template #action="{ row }"> |
| | | <ArtButtonTable icon="ri:eye-line" @click="openDetailDrawer(row)" /> |
| | | </template> |
| | | </ArtTable> |
| | | </ElCard> |
| | | |
| | | <LocPreviewDetailDrawer |
| | | v-model:visible="detailDrawerVisible" |
| | | :loading="detailLoading" |
| | | :detail="activeLocDetail" |
| | | :data="detailTableData" |
| | | :columns="detailColumns" |
| | | :pagination="detailPagination" |
| | | @refresh="loadDetailResources" |
| | | @size-change="handleDetailSizeChange" |
| | | @current-change="handleDetailCurrentChange" |
| | | /> |
| | | </div> |
| | | <LocPreviewItemsPage /> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, onMounted, reactive, ref } from 'vue' |
| | | import { useI18n } from 'vue-i18n' |
| | | import { useTableColumns } from '@/hooks/core/useTableColumns' |
| | | import { guardRequestWithMessage } from '@/utils/sys/requestGuard' |
| | | import { |
| | | fetchEnabledFields, |
| | | fetchLocPreviewDetail, |
| | | fetchLocPreviewItemsPage, |
| | | fetchLocPreviewPage |
| | | } from '@/api/loc-preview' |
| | | import LocPreviewDetailDrawer from './modules/loc-preview-detail-drawer.vue' |
| | | import { createLocPreviewTableColumns } from './locPreviewTable.columns' |
| | | import { |
| | | buildLocPreviewPageQueryParams, |
| | | createLocPreviewSearchState, |
| | | getLocPreviewDynamicFieldKey, |
| | | normalizeLocPreviewDetail, |
| | | normalizeLocPreviewEnabledFields, |
| | | normalizeLocPreviewItemRow, |
| | | normalizeLocPreviewRow |
| | | } from './locPreviewPage.helpers' |
| | | import LocPreviewItemsPage from './modules/loc-preview-items-page.vue' |
| | | |
| | | defineOptions({ name: 'LocPreview' }) |
| | | const { t } = useI18n() |
| | | |
| | | const loading = ref(false) |
| | | const detailLoading = ref(false) |
| | | const tableData = ref([]) |
| | | const detailTableData = ref([]) |
| | | const detailDrawerVisible = ref(false) |
| | | const activeLocRow = ref(null) |
| | | const activeLocDetail = ref({}) |
| | | const enabledFields = ref([]) |
| | | const searchForm = ref(createLocPreviewSearchState()) |
| | | |
| | | const pagination = reactive({ |
| | | current: 1, |
| | | size: 20, |
| | | total: 0 |
| | | }) |
| | | |
| | | const detailPagination = reactive({ |
| | | current: 1, |
| | | size: 20, |
| | | total: 0 |
| | | }) |
| | | |
| | | const searchItems = computed(() => [ |
| | | { |
| | | label: t('pages.manager.locPreview.search.condition'), |
| | | key: 'condition', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.manager.locPreview.search.conditionPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locPreview.search.code'), |
| | | key: 'code', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.manager.locPreview.search.codePlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locPreview.search.barcode'), |
| | | key: 'barcode', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.manager.locPreview.search.barcodePlaceholder') |
| | | } |
| | | } |
| | | ]) |
| | | |
| | | function createDetailColumns() { |
| | | return [ |
| | | { |
| | | prop: 'locCode', |
| | | label: t('pages.manager.locPreview.table.locCode'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'wareArea', |
| | | label: t('pages.manager.locPreview.table.areaLabel'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'orderCode', |
| | | label: t('pages.orders.common.orderCode'), |
| | | minWidth: 180, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'matnrCode', |
| | | label: t('table.materialCode'), |
| | | minWidth: 160, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'maktx', |
| | | label: t('table.materialName'), |
| | | minWidth: 220, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'batch', |
| | | label: t('table.batch'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'trackCode', |
| | | label: t('pages.orders.common.trackCode'), |
| | | minWidth: 150, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'unit', |
| | | label: t('table.unit'), |
| | | width: 100 |
| | | }, |
| | | { |
| | | prop: 'anfme', |
| | | label: t('pages.manager.freeze.table.anfme'), |
| | | width: 120 |
| | | }, |
| | | { |
| | | prop: 'qty', |
| | | label: t('pages.manager.freeze.table.qty'), |
| | | width: 120 |
| | | }, |
| | | { |
| | | prop: 'workQty', |
| | | label: t('pages.manager.freeze.table.workQty'), |
| | | width: 120 |
| | | }, |
| | | ...enabledFields.value.map((field) => ({ |
| | | prop: getLocPreviewDynamicFieldKey(field.fields), |
| | | label: field.fieldsAlise, |
| | | minWidth: 140, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row[getLocPreviewDynamicFieldKey(field.fields)] || '-' |
| | | })), |
| | | { |
| | | prop: 'createTimeText', |
| | | label: t('table.createTime'), |
| | | minWidth: 180, |
| | | showOverflowTooltip: true |
| | | } |
| | | ] |
| | | } |
| | | |
| | | const detailColumns = computed(() => createDetailColumns()) |
| | | |
| | | const { columns, columnChecks } = useTableColumns(() => |
| | | createLocPreviewTableColumns({ |
| | | handleViewDetail: openDetailDrawer |
| | | }) |
| | | ) |
| | | |
| | | function updatePaginationState(target, response, fallbackCurrent, fallbackSize) { |
| | | target.total = Number(response?.total || 0) |
| | | target.current = Number(response?.current || fallbackCurrent || 1) |
| | | target.size = Number(response?.size || fallbackSize || target.size || 20) |
| | | } |
| | | |
| | | async function loadEnabledFieldDefinitions() { |
| | | const fields = await guardRequestWithMessage(fetchEnabledFields(), [], { |
| | | timeoutMessage: t('pages.manager.locPreview.messages.fieldsTimeout') |
| | | }) |
| | | enabledFields.value = normalizeLocPreviewEnabledFields(fields) |
| | | } |
| | | |
| | | async function loadPageData() { |
| | | loading.value = true |
| | | try { |
| | | const response = await guardRequestWithMessage( |
| | | fetchLocPreviewPage( |
| | | buildLocPreviewPageQueryParams({ |
| | | ...searchForm.value, |
| | | current: pagination.current, |
| | | pageSize: pagination.size |
| | | }) |
| | | ), |
| | | { |
| | | records: [], |
| | | total: 0, |
| | | current: pagination.current, |
| | | size: pagination.size |
| | | }, |
| | | { timeoutMessage: t('pages.manager.locPreview.messages.pageTimeout') } |
| | | ) |
| | | tableData.value = Array.isArray(response?.records) |
| | | ? response.records.map((record) => normalizeLocPreviewRow(record)) |
| | | : [] |
| | | updatePaginationState(pagination, response, pagination.current, pagination.size) |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | async function loadDetailResources() { |
| | | if (!activeLocRow.value?.id) { |
| | | return |
| | | } |
| | | |
| | | detailLoading.value = true |
| | | try { |
| | | const [detailResponse, itemResponse] = await Promise.all([ |
| | | guardRequestWithMessage(fetchLocPreviewDetail(activeLocRow.value.id), {}, { |
| | | timeoutMessage: t('pages.manager.locPreview.messages.detailTimeout') |
| | | }), |
| | | guardRequestWithMessage( |
| | | fetchLocPreviewItemsPage({ |
| | | current: detailPagination.current, |
| | | pageSize: detailPagination.size, |
| | | locId: activeLocRow.value.id |
| | | }), |
| | | { |
| | | records: [], |
| | | total: 0, |
| | | current: detailPagination.current, |
| | | size: detailPagination.size |
| | | }, |
| | | { timeoutMessage: t('pages.manager.locPreview.messages.itemPageTimeout') } |
| | | ) |
| | | ]) |
| | | |
| | | activeLocDetail.value = normalizeLocPreviewDetail(detailResponse) |
| | | detailTableData.value = Array.isArray(itemResponse?.records) |
| | | ? itemResponse.records.map((record) => normalizeLocPreviewItemRow(record, enabledFields.value)) |
| | | : [] |
| | | updatePaginationState(detailPagination, itemResponse, detailPagination.current, detailPagination.size) |
| | | } finally { |
| | | detailLoading.value = false |
| | | } |
| | | } |
| | | |
| | | function openDetailDrawer(row) { |
| | | activeLocRow.value = row |
| | | detailPagination.current = 1 |
| | | detailDrawerVisible.value = true |
| | | loadDetailResources() |
| | | } |
| | | |
| | | function handleSearch(params) { |
| | | searchForm.value = { |
| | | ...searchForm.value, |
| | | ...params |
| | | } |
| | | pagination.current = 1 |
| | | loadPageData() |
| | | } |
| | | |
| | | function handleReset() { |
| | | searchForm.value = createLocPreviewSearchState() |
| | | pagination.current = 1 |
| | | pagination.size = 20 |
| | | loadPageData() |
| | | } |
| | | |
| | | function handleSizeChange(size) { |
| | | pagination.size = size |
| | | pagination.current = 1 |
| | | loadPageData() |
| | | } |
| | | |
| | | function handleCurrentChange(current) { |
| | | pagination.current = current |
| | | loadPageData() |
| | | } |
| | | |
| | | function handleDetailSizeChange(size) { |
| | | detailPagination.size = size |
| | | detailPagination.current = 1 |
| | | loadDetailResources() |
| | | } |
| | | |
| | | function handleDetailCurrentChange(current) { |
| | | detailPagination.current = current |
| | | loadDetailResources() |
| | | } |
| | | |
| | | onMounted(async () => { |
| | | await loadEnabledFieldDefinitions() |
| | | await loadPageData() |
| | | }) |
| | | </script> |
| New file |
| | |
| | | import { $t } from '@/locales' |
| | | |
| | | export function createLocPreviewItemTableColumns({ enabledFields = [] }) { |
| | | return [ |
| | | { |
| | | prop: 'locId', |
| | | label: $t('pages.manager.locItem.table.locId'), |
| | | width: 110 |
| | | }, |
| | | { |
| | | prop: 'wareArea', |
| | | label: $t('pages.manager.locItem.table.wareArea'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'locCode', |
| | | label: $t('pages.manager.locItem.table.locCode'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'typeText', |
| | | label: $t('pages.manager.locItem.table.type'), |
| | | minWidth: 120, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'wkTypeText', |
| | | label: $t('pages.manager.locItem.table.wkType'), |
| | | minWidth: 120, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'orderId', |
| | | label: $t('pages.manager.locItem.table.orderId'), |
| | | minWidth: 120 |
| | | }, |
| | | { |
| | | prop: 'orderItemId', |
| | | label: $t('pages.manager.locItem.table.orderItemId'), |
| | | minWidth: 130 |
| | | }, |
| | | { |
| | | prop: 'matnrId', |
| | | label: $t('pages.manager.locItem.table.matnrId'), |
| | | minWidth: 110 |
| | | }, |
| | | { |
| | | prop: 'matnrCode', |
| | | label: $t('pages.manager.locItem.table.matnrCode'), |
| | | minWidth: 160, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'maktx', |
| | | label: $t('pages.manager.locItem.table.maktx'), |
| | | minWidth: 220, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'spec', |
| | | label: $t('pages.manager.locItem.table.spec'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'model', |
| | | label: $t('pages.manager.locItem.table.model'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'splrBatch', |
| | | label: $t('table.supplierBatch'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'batch', |
| | | label: $t('pages.manager.locItem.table.batch'), |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'trackCode', |
| | | label: $t('pages.manager.locItem.table.trackCode'), |
| | | minWidth: 150, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'unit', |
| | | label: $t('table.unit'), |
| | | width: 100 |
| | | }, |
| | | { |
| | | prop: 'anfme', |
| | | label: $t('pages.manager.locItem.table.anfme'), |
| | | width: 110, |
| | | align: 'right' |
| | | }, |
| | | { |
| | | prop: 'qty', |
| | | label: $t('pages.manager.locItem.table.qty'), |
| | | width: 110, |
| | | align: 'right' |
| | | }, |
| | | { |
| | | prop: 'workQty', |
| | | label: $t('pages.manager.locItem.table.workQty'), |
| | | width: 120, |
| | | align: 'right' |
| | | }, |
| | | ...enabledFields.map((field) => ({ |
| | | prop: field.prop, |
| | | label: field.label, |
| | | minWidth: 140, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row[field.prop] || '-' |
| | | })), |
| | | { |
| | | prop: 'statusText', |
| | | label: $t('table.status'), |
| | | width: 90 |
| | | }, |
| | | { |
| | | prop: 'updateByText', |
| | | label: $t('table.updateBy'), |
| | | minWidth: 120, |
| | | showOverflowTooltip: true, |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'updateTimeText', |
| | | label: $t('table.updateTime'), |
| | | minWidth: 180, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'createByText', |
| | | label: $t('table.createBy'), |
| | | minWidth: 120, |
| | | showOverflowTooltip: true, |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'createTimeText', |
| | | label: $t('table.createTime'), |
| | | minWidth: 180, |
| | | showOverflowTooltip: true, |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'memo', |
| | | label: $t('table.memo'), |
| | | minWidth: 180, |
| | | showOverflowTooltip: true, |
| | | visible: false |
| | | } |
| | | ] |
| | | } |
| New file |
| | |
| | | <template> |
| | | <div class="loc-item-page art-full-height"> |
| | | <ArtSearchBar |
| | | v-model="searchForm" |
| | | :items="searchItems" |
| | | :showExpand="true" |
| | | @search="handleSearch" |
| | | @reset="handleReset" |
| | | /> |
| | | |
| | | <ElCard class="art-table-card"> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" /> |
| | | |
| | | <ArtTable |
| | | :loading="loading" |
| | | :data="data" |
| | | :columns="columns" |
| | | :pagination="pagination" |
| | | @pagination:size-change="handleSizeChange" |
| | | @pagination:current-change="handleCurrentChange" |
| | | /> |
| | | </ElCard> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, onMounted, ref } from 'vue' |
| | | import { useRoute } from 'vue-router' |
| | | import { useI18n } from 'vue-i18n' |
| | | import { useTable } from '@/hooks/core/useTable' |
| | | import { guardRequestWithMessage } from '@/utils/sys/requestGuard' |
| | | import { useTableColumns } from '@/hooks/core/useTableColumns' |
| | | import { fetchEnabledFields, fetchLocItemPage } from '@/api/loc-item' |
| | | import { createLocPreviewItemTableColumns } from './loc-preview-item-table.columns' |
| | | import { |
| | | buildLocItemPageQueryParams, |
| | | buildLocItemSearchParams, |
| | | createLocItemSearchState, |
| | | getLocItemDynamicFieldKey, |
| | | getLocItemPaginationKey, |
| | | getLocItemStatusOptions, |
| | | normalizeLocItemEnabledFields, |
| | | normalizeLocItemRow |
| | | } from '@/views/manager/loc-item/locItemPage.helpers' |
| | | |
| | | defineOptions({ name: 'LocPreviewItemsPage' }) |
| | | |
| | | const route = useRoute() |
| | | const { t } = useI18n() |
| | | |
| | | const searchForm = ref(createLocItemSearchState()) |
| | | const enabledFields = ref([]) |
| | | |
| | | const searchItems = computed(() => [ |
| | | { |
| | | label: t('pages.manager.locItem.search.condition'), |
| | | key: 'condition', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.manager.locItem.search.conditionPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locItem.search.timeStart'), |
| | | key: 'timeStart', |
| | | type: 'date', |
| | | props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locItem.search.timeEnd'), |
| | | key: 'timeEnd', |
| | | type: 'date', |
| | | props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locItem.search.locId'), |
| | | key: 'locId', |
| | | type: 'inputNumber', |
| | | props: { |
| | | min: 0, |
| | | controlsPosition: 'right', |
| | | placeholder: t('pages.manager.locItem.search.locIdPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locItem.search.orderId'), |
| | | key: 'orderId', |
| | | type: 'inputNumber', |
| | | props: { |
| | | min: 0, |
| | | controlsPosition: 'right', |
| | | placeholder: t('pages.manager.locItem.search.orderIdPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locItem.search.type'), |
| | | key: 'type', |
| | | type: 'input', |
| | | props: { clearable: true, placeholder: t('pages.manager.locItem.search.typePlaceholder') } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locItem.search.wkType'), |
| | | key: 'wkType', |
| | | type: 'inputNumber', |
| | | props: { |
| | | min: 0, |
| | | controlsPosition: 'right', |
| | | placeholder: t('pages.manager.locItem.search.wkTypePlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locItem.search.matnrCode'), |
| | | key: 'matnrCode', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.manager.locItem.search.matnrCodePlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locItem.search.maktx'), |
| | | key: 'maktx', |
| | | type: 'input', |
| | | props: { clearable: true, placeholder: t('pages.manager.locItem.search.maktxPlaceholder') } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locItem.search.trackCode'), |
| | | key: 'trackCode', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.manager.locItem.search.trackCodePlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locItem.search.batch'), |
| | | key: 'batch', |
| | | type: 'input', |
| | | props: { clearable: true, placeholder: t('pages.manager.locItem.search.batchPlaceholder') } |
| | | }, |
| | | { |
| | | label: t('pages.manager.locItem.search.splrBatch'), |
| | | key: 'splrBatch', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.manager.locItem.search.splrBatchPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('table.status'), |
| | | key: 'status', |
| | | type: 'select', |
| | | props: { clearable: true, options: getLocItemStatusOptions() } |
| | | } |
| | | ]) |
| | | |
| | | const buildFieldConfigs = () => |
| | | enabledFields.value.map((field) => ({ |
| | | prop: getLocItemDynamicFieldKey(field.fields), |
| | | label: field.fieldsAlise |
| | | })) |
| | | |
| | | const { columns, columnChecks, resetColumns } = useTableColumns(() => |
| | | createLocPreviewItemTableColumns({ |
| | | enabledFields: buildFieldConfigs() |
| | | }) |
| | | ) |
| | | |
| | | const { |
| | | data, |
| | | loading, |
| | | pagination, |
| | | getData, |
| | | replaceSearchParams, |
| | | resetSearchParams, |
| | | handleSizeChange, |
| | | handleCurrentChange, |
| | | refreshData |
| | | } = useTable({ |
| | | core: { |
| | | apiFn: (params) => |
| | | guardRequestWithMessage( |
| | | fetchLocItemPage(params), |
| | | { |
| | | records: [], |
| | | total: 0, |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20 |
| | | }, |
| | | { timeoutMessage: t('pages.manager.locItem.messages.pageTimeout') } |
| | | ), |
| | | apiParams: buildLocItemPageQueryParams(searchForm.value), |
| | | immediate: false, |
| | | paginationKey: getLocItemPaginationKey() |
| | | }, |
| | | transform: { |
| | | dataTransformer: (records) => |
| | | Array.isArray(records) |
| | | ? records.map((item) => normalizeLocItemRow(item, enabledFields.value)) |
| | | : [] |
| | | } |
| | | }) |
| | | |
| | | async function loadEnabledFieldDefinitions() { |
| | | const fields = await guardRequestWithMessage(fetchEnabledFields(), [], { |
| | | timeoutMessage: t('pages.manager.locItem.messages.fieldsTimeout') |
| | | }) |
| | | enabledFields.value = normalizeLocItemEnabledFields(fields) |
| | | resetColumns() |
| | | } |
| | | |
| | | function handleSearch(params) { |
| | | replaceSearchParams(buildLocItemSearchParams(params)) |
| | | getData() |
| | | } |
| | | |
| | | function handleReset() { |
| | | const nextState = createLocItemSearchState() |
| | | if (route.query.locId) { |
| | | nextState.locId = route.query.locId |
| | | } |
| | | Object.assign(searchForm.value, nextState) |
| | | resetSearchParams() |
| | | } |
| | | |
| | | onMounted(async () => { |
| | | if (route.query.locId) { |
| | | searchForm.value.locId = route.query.locId |
| | | replaceSearchParams(buildLocItemSearchParams(searchForm.value)) |
| | | } |
| | | await loadEnabledFieldDefinitions() |
| | | await getData() |
| | | }) |
| | | </script> |
| | |
| | | /> |
| | | |
| | | <ElCard class="art-table-card"> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" /> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData"> |
| | | <template #left> |
| | | <ElSpace wrap> |
| | | <ElButton |
| | | v-auth="'list'" |
| | | :loading="exportLoading" |
| | | :disabled="loading || exportLoading || pagination.total === 0" |
| | | @click="handleExport" |
| | | v-ripple |
| | | > |
| | | 导出 |
| | | </ElButton> |
| | | </ElSpace> |
| | | </template> |
| | | </ArtTableHeader> |
| | | |
| | | <ArtTable |
| | | :loading="loading" |
| | |
| | | |
| | | <WarehouseStockHistoriesDrawer |
| | | v-model:visible="historiesDrawerVisible" |
| | | v-model:column-checks="historiesColumnChecks" |
| | | :loading="historiesLoading" |
| | | :summary="activeStockSummary" |
| | | :data="historiesTableData" |
| | |
| | | |
| | | <script setup> |
| | | import { computed, onMounted, reactive, ref } from 'vue' |
| | | import * as XLSX from 'xlsx' |
| | | import FileSaver from 'file-saver' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useTableColumns } from '@/hooks/core/useTableColumns' |
| | | import { guardRequestWithMessage } from '@/utils/sys/requestGuard' |
| | | import { |
| | |
| | | |
| | | const searchForm = ref(createWarehouseStockSearchState()) |
| | | const loading = ref(false) |
| | | const exportLoading = ref(false) |
| | | const tableData = ref([]) |
| | | const enabledFields = ref([]) |
| | | const activeStockSummary = ref({}) |
| | |
| | | function createHistoriesColumns() { |
| | | return [ |
| | | { |
| | | prop: 'id', |
| | | label: 'ID', |
| | | width: 96, |
| | | align: 'center', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'orderId', |
| | | label: '单据ID', |
| | | width: 100, |
| | | align: 'center', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'sourceItemId', |
| | | label: '来源明细ID', |
| | | width: 120, |
| | | align: 'center', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'matnrId', |
| | | label: '物料ID', |
| | | width: 100, |
| | | align: 'center', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'stockCode', |
| | | label: '单据编号', |
| | | minWidth: 180, |
| | |
| | | formatter: (row) => row.batch || '-' |
| | | }, |
| | | { |
| | | prop: 'splrName', |
| | | label: '供应商', |
| | | minWidth: 180, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row.splrName || '-' |
| | | }, |
| | | { |
| | | prop: 'trackCode', |
| | | label: '跟踪号', |
| | | minWidth: 160, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row.trackCode || '-' |
| | | }, |
| | | { |
| | | prop: 'prodTime', |
| | | label: '生产日期', |
| | | minWidth: 140, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row.prodTime || '-' |
| | | }, |
| | | { |
| | | prop: 'packName', |
| | | label: '包装', |
| | | minWidth: 140, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row.packName || '-' |
| | | }, |
| | | { |
| | | prop: 'anfme', |
| | | label: '库存数量', |
| | | width: 120, |
| | |
| | | width: 100, |
| | | formatter: (row) => row.stockUnit || '-' |
| | | }, |
| | | { |
| | | prop: 'barcode', |
| | | label: '条码', |
| | | minWidth: 150, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row.barcode || '-', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'splrCode', |
| | | label: '供应商编码', |
| | | minWidth: 140, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row.splrCode || '-', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'splrBatch', |
| | | label: '供应商批次', |
| | | minWidth: 150, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row.splrBatch || '-', |
| | | visible: false |
| | | }, |
| | | ...createDynamicFieldColumns(), |
| | | { |
| | | prop: 'updateByText', |
| | | label: '更新人', |
| | | minWidth: 120, |
| | | formatter: (row) => row.updateByText || '-', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'updateTimeText', |
| | | label: '更新时间', |
| | | minWidth: 180, |
| | | formatter: (row) => row.updateTimeText || '-' |
| | | }, |
| | | { |
| | | prop: 'createByText', |
| | | label: '创建人', |
| | | minWidth: 120, |
| | | formatter: (row) => row.createByText || '-', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'createTimeText', |
| | | label: '创建时间', |
| | | minWidth: 180, |
| | | formatter: (row) => row.createTimeText || '-' |
| | | }, |
| | | { |
| | | prop: 'memo', |
| | | label: '备注', |
| | | minWidth: 180, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row.memo || '-', |
| | | visible: false |
| | | } |
| | | ] |
| | | } |
| | | |
| | | const detailColumns = computed(() => createDetailColumns()) |
| | | const historiesColumns = computed(() => createHistoriesColumns()) |
| | | const { |
| | | columns: historiesColumns, |
| | | columnChecks: historiesColumnChecks, |
| | | resetColumns: resetHistoriesColumns |
| | | } = useTableColumns(() => createHistoriesColumns()) |
| | | |
| | | function openDetailDrawer(row) { |
| | | activeStockSummary.value = row |
| | |
| | | } |
| | | }) |
| | | resetColumns() |
| | | resetHistoriesColumns() |
| | | } |
| | | |
| | | function updatePaginationState(target, response, fallbackCurrent, fallbackSize) { |
| | |
| | | ) |
| | | |
| | | detailTableData.value = Array.isArray(response?.records) |
| | | ? response.records.map((record) => normalizeWarehouseStockDetailRow(record, enabledFields.value)) |
| | | ? response.records.map((record) => |
| | | normalizeWarehouseStockDetailRow(record, enabledFields.value) |
| | | ) |
| | | : [] |
| | | updatePaginationState(detailPagination, response, detailPagination.current, detailPagination.size) |
| | | updatePaginationState( |
| | | detailPagination, |
| | | response, |
| | | detailPagination.current, |
| | | detailPagination.size |
| | | ) |
| | | } finally { |
| | | detailLoading.value = false |
| | | } |
| | |
| | | ) |
| | | |
| | | historiesTableData.value = Array.isArray(response?.records) |
| | | ? response.records.map((record) => normalizeWarehouseStockHistoryRow(record, enabledFields.value)) |
| | | ? response.records.map((record) => |
| | | normalizeWarehouseStockHistoryRow(record, enabledFields.value) |
| | | ) |
| | | : [] |
| | | updatePaginationState( |
| | | historiesPagination, |
| | |
| | | await loadHistoriesData() |
| | | } |
| | | |
| | | function resolveExportColumns() { |
| | | return columns.value.filter((column) => column?.prop && column.prop !== 'operation') |
| | | } |
| | | |
| | | function resolveExportCellValue(column, row) { |
| | | if (typeof column.formatter === 'function') { |
| | | const value = column.formatter(row) |
| | | return typeof value === 'string' || typeof value === 'number' ? value : '' |
| | | } |
| | | return row[column.prop] ?? '' |
| | | } |
| | | |
| | | function buildExportRows(records, exportColumns) { |
| | | return records.map((row) => |
| | | exportColumns.reduce((result, column) => { |
| | | result[column.label || column.prop] = resolveExportCellValue(column, row) |
| | | return result |
| | | }, {}) |
| | | ) |
| | | } |
| | | |
| | | async function handleExport() { |
| | | exportLoading.value = true |
| | | try { |
| | | const exportSize = Number(pagination.total) > 0 ? Number(pagination.total) : 1000 |
| | | const response = await guardRequestWithMessage( |
| | | fetchWarehouseStockPage( |
| | | buildWarehouseStockPageQueryParams({ |
| | | ...searchForm.value, |
| | | current: 1, |
| | | pageSize: exportSize |
| | | }) |
| | | ), |
| | | { records: [] }, |
| | | { |
| | | timeoutMessage: '即时库存导出超时,已停止等待' |
| | | } |
| | | ) |
| | | const records = Array.isArray(response?.records) |
| | | ? response.records.map((record) => normalizeWarehouseStockRow(record, enabledFields.value)) |
| | | : [] |
| | | if (records.length === 0) { |
| | | ElMessage.warning('暂无可导出的即时库存数据') |
| | | return |
| | | } |
| | | |
| | | const exportColumns = resolveExportColumns() |
| | | const exportRows = buildExportRows(records, exportColumns) |
| | | const worksheet = XLSX.utils.json_to_sheet(exportRows) |
| | | const workbook = XLSX.utils.book_new() |
| | | XLSX.utils.book_append_sheet(workbook, worksheet, '即时库存') |
| | | const excelBuffer = XLSX.write(workbook, { |
| | | bookType: 'xlsx', |
| | | type: 'array', |
| | | compression: true |
| | | }) |
| | | FileSaver.saveAs( |
| | | new Blob([excelBuffer], { |
| | | type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' |
| | | }), |
| | | 'warehouse-stock.xlsx' |
| | | ) |
| | | ElMessage.success('导出成功') |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || '导出失败') |
| | | } finally { |
| | | exportLoading.value = false |
| | | } |
| | | } |
| | | |
| | | function handleSearch(params) { |
| | | searchForm.value = { |
| | | ...searchForm.value, |
| | |
| | | <ElDescriptionsItem label="批次">{{ summary.batch || '--' }}</ElDescriptionsItem> |
| | | </ElDescriptions> |
| | | |
| | | <div class="flex justify-end"> |
| | | <ElButton :loading="loading" @click="$emit('refresh')">刷新</ElButton> |
| | | </div> |
| | | <ArtTableHeader |
| | | :columns="columnChecks" |
| | | :loading="loading" |
| | | @update:columns="emit('update:columnChecks', $event)" |
| | | @refresh="$emit('refresh')" |
| | | /> |
| | | |
| | | <ArtTable |
| | | :loading="loading" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import ArtTableHeader from '@/components/core/tables/art-table-header/index.vue' |
| | | |
| | | defineProps({ |
| | | visible: { type: Boolean, default: false }, |
| | | loading: { type: Boolean, default: false }, |
| | | summary: { type: Object, default: () => ({}) }, |
| | | data: { type: Array, default: () => [] }, |
| | | columns: { type: Array, default: () => [] }, |
| | | columnChecks: { type: Array, default: () => [] }, |
| | | pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) } |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change']) |
| | | const emit = defineEmits([ |
| | | 'update:visible', |
| | | 'update:columnChecks', |
| | | 'refresh', |
| | | 'size-change', |
| | | 'current-change' |
| | | ]) |
| | | |
| | | function handleVisibleChange(visible) { |
| | | emit('update:visible', visible) |
| | |
| | | export const WAREHOUSE_STOCK_REPORT_TITLE = '即时库存报表' |
| | | export const WAREHOUSE_STOCK_DYNAMIC_FIELD_PREFIX = 'extendField__' |
| | | export const DEFAULT_WAREHOUSE_STOCK_ORDER_BY = 'create_time desc' |
| | | |
| | | const AGG_TYPE_OPTIONS = [ |
| | | { label: '按物料汇总', value: 'matnr' }, |
| | |
| | | return { |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20, |
| | | orderBy: normalizeText(params.orderBy) || DEFAULT_WAREHOUSE_STOCK_ORDER_BY, |
| | | ...buildWarehouseStockSearchParams(params) |
| | | } |
| | | } |
| | |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20, |
| | | aggType: normalizeText(params.aggType) || 'matnr', |
| | | orderBy: normalizeText(params.orderBy) || DEFAULT_WAREHOUSE_STOCK_ORDER_BY, |
| | | stock: params.stock || {} |
| | | } |
| | | } |
| | | |
| | | export function attachWarehouseStockDynamicFields(record = {}, enabledFields = []) { |
| | | const extendFields = record.extendFields && typeof record.extendFields === 'object' ? record.extendFields : {} |
| | | const extendFields = |
| | | record.extendFields && typeof record.extendFields === 'object' ? record.extendFields : {} |
| | | const dynamicValues = {} |
| | | enabledFields.forEach((field) => { |
| | | dynamicValues[getWarehouseStockDynamicFieldKey(field.fields)] = extendFields[field.fields] || '' |
| | |
| | | return attachWarehouseStockDynamicFields( |
| | | { |
| | | ...record, |
| | | id: normalizeNumber(record.id), |
| | | locId: normalizeNumber(record.locId), |
| | | locCode: record.locCode || '', |
| | | orderId: normalizeNumber(record.orderId), |
| | | orderItemId: normalizeNumber(record.orderItemId), |
| | | matnrId: normalizeNumber(record.matnrId), |
| | | warehouseLabel: record['warehouse$'] || record.warehouse || '', |
| | | matnrCode: record.matnrCode || '', |
| | | maktx: record.maktx || '', |
| | |
| | | anfme: normalizeNumber(record.anfme), |
| | | qty: normalizeNumber(record.qty), |
| | | workQty: normalizeNumber(record.workQty), |
| | | updateByText: record['updateBy$'] || record.updateBy || '', |
| | | updateTimeText: record['updateTime$'] || record.updateTime || '' |
| | | }, |
| | | enabledFields |
| | |
| | | return attachWarehouseStockDynamicFields( |
| | | { |
| | | ...record, |
| | | id: normalizeNumber(record.id), |
| | | orderId: normalizeNumber(record.orderId), |
| | | sourceItemId: normalizeNumber(record.sourceItemId), |
| | | matnrId: normalizeNumber(record.matnrId), |
| | | stockCode: record.stockCode || record.orderCode || '', |
| | | orderCode: record.orderCode || '', |
| | | matnrCode: record.matnrCode || '', |
| | | maktx: record.maktx || '', |
| | | batch: record.batch || '', |
| | | splrCode: record.splrCode || '', |
| | | splrBatch: record.splrBatch || '', |
| | | splrName: record.splrName || '', |
| | | trackCode: record.trackCode || '', |
| | | barcode: record.barcode || '', |
| | | prodTime: record.prodTime || '', |
| | | packName: record.packName || '', |
| | | stockUnit: record.stockUnit || record.unit || '', |
| | | qty: normalizeNumber(record.qty), |
| | | workQty: normalizeNumber(record.workQty), |
| | | updateByText: record['updateBy$'] || record.updateBy || '', |
| | | createByText: record['createBy$'] || record.createBy || '', |
| | | memo: record.memo || '', |
| | | createTimeText: record['createTime$'] || record.createTime || '', |
| | | updateTimeText: record['updateTime$'] || record.updateTime || '' |
| | | }, |
| | |
| | | |
| | | return [ |
| | | { |
| | | prop: 'id', |
| | | label: 'ID', |
| | | width: 96, |
| | | align: 'center', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'locId', |
| | | label: '库位ID', |
| | | width: 100, |
| | | align: 'center', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'locCode', |
| | | label: '库位编码', |
| | | minWidth: 140, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row.locCode || '-', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'orderId', |
| | | label: '单据ID', |
| | | width: 100, |
| | | align: 'center', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'orderItemId', |
| | | label: '单据明细ID', |
| | | width: 120, |
| | | align: 'center', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'matnrId', |
| | | label: '物料ID', |
| | | width: 100, |
| | | align: 'center', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'matnrCode', |
| | | label: '物料编码', |
| | | minWidth: 160, |
| | |
| | | formatter: (row) => row.unit || '-' |
| | | }, |
| | | { |
| | | prop: 'spec', |
| | | label: '规格', |
| | | minWidth: 140, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row.spec || '-', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'model', |
| | | label: '型号', |
| | | minWidth: 140, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row.model || '-', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'fieldsIndex', |
| | | label: '扩展索引', |
| | | minWidth: 160, |
| | | showOverflowTooltip: true, |
| | | formatter: (row) => row.fieldsIndex || '-', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'anfme', |
| | | label: '可用库存', |
| | | width: 120, |
| | |
| | | }, |
| | | ...dynamicColumns, |
| | | { |
| | | prop: 'updateByText', |
| | | label: '更新人', |
| | | minWidth: 110, |
| | | formatter: (row) => row.updateByText || '-', |
| | | visible: false |
| | | }, |
| | | { |
| | | prop: 'updateTimeText', |
| | | label: '更新时间', |
| | | minWidth: 180, |
| | |
| | | { |
| | | prop: 'operation', |
| | | label: '历史记录', |
| | | width: 130, |
| | | width: 140, |
| | | fixed: 'right', |
| | | formatter: (row) => |
| | | h('div', { class: 'flex justify-end gap-2' }, [ |
| | | h(ArtButtonTable, { |
| | | type: 'view', |
| | | text: '库存详情', |
| | | onClick: () => handleViewDetail(row) |
| | | }), |
| | | h(ArtButtonTable, { |
| | | type: 'view', |
| | | text: '历史记录', |
| | | icon: 'ri:history-line', |
| | | iconClass: 'bg-warning/12 text-warning', |
| | | onClick: () => handleViewHistories(row) |
| | | }) |
| | | ]) |