| | |
| | | <ElCard class="wh-mat-page__sidebar-card"> |
| | | <div class="mb-3 flex items-center justify-between gap-3"> |
| | | <div> |
| | | <div class="text-base font-medium text-[var(--art-text-primary)]">{{ t('pages.basicInfo.whMat.title') }}</div> |
| | | <div class="text-base font-medium text-[var(--art-text-primary)]">{{ |
| | | t('pages.basicInfo.whMat.title') |
| | | }}</div> |
| | | <div class="text-xs text-[var(--art-text-secondary)]"> |
| | | {{ selectedGroupLabel }} |
| | | </div> |
| | |
| | | <div v-if="groupTreeLoading" class="py-6"> |
| | | <ElSkeleton :rows="10" animated /> |
| | | </div> |
| | | <ElEmpty v-else-if="!groupTreeData.length" :description="t('pages.basicInfo.whMat.messages.emptyGroups')" /> |
| | | <ElEmpty |
| | | v-else-if="!groupTreeData.length" |
| | | :description="t('pages.basicInfo.whMat.messages.emptyGroups')" |
| | | /> |
| | | <ElTree |
| | | v-else |
| | | :data="groupTreeData" |
| | | :props="treeProps" |
| | | node-key="id" |
| | | highlight-current |
| | | default-expand-all |
| | | :default-expanded-keys="defaultExpandedGroupKeys" |
| | | :current-node-key="selectedGroupId" |
| | | @node-click="handleGroupNodeClick" |
| | | > |
| | | <template #default="{ data }"> |
| | | <div class="flex items-center gap-2"> |
| | | <span class="font-medium">{{ data.name || t('common.placeholder.empty') }}</span> |
| | | <span class="text-xs text-[var(--art-text-secondary)]">{{ data.code || t('common.placeholder.empty') }}</span> |
| | | <span class="text-xs text-[var(--art-text-secondary)]">{{ |
| | | data.code || t('common.placeholder.empty') |
| | | }}</span> |
| | | </div> |
| | | </template> |
| | | </ElTree> |
| | |
| | | /> |
| | | |
| | | <ElCard class="art-table-card"> |
| | | <ArtTableHeader :loading="loading" v-model:columns="columnChecks" @refresh="loadMatnrList" /> |
| | | <ArtTableHeader |
| | | :loading="loading" |
| | | v-model:columns="columnChecks" |
| | | @refresh="handleRefresh" |
| | | > |
| | | <template #left> |
| | | <ElSpace wrap> |
| | | <ElButton v-auth="'add'" @click="handleShowDialog('add')" v-ripple> |
| | | {{ t('pages.basicInfo.whMat.actions.add') }} |
| | | </ElButton> |
| | | <ElButton |
| | | v-if="showBatchActionButtons" |
| | | v-auth="'update'" |
| | | :disabled="selectedRows.length === 0" |
| | | @click="openBatchGroupDialog" |
| | | v-ripple |
| | | > |
| | | {{ t('pages.basicInfo.whMat.actions.batchGroup') }} |
| | | </ElButton> |
| | | <ElButton |
| | | v-if="showBatchActionButtons" |
| | | v-auth="'update'" |
| | | :disabled="selectedRows.length === 0" |
| | | @click="openBatchDialog('validWarn')" |
| | | v-ripple |
| | | > |
| | | {{ t('pages.basicInfo.whMat.actions.batchWarn') }} |
| | | </ElButton> |
| | | <ElButton |
| | | v-if="showBatchActionButtons" |
| | | v-auth="'update'" |
| | | :disabled="selectedRows.length === 0" |
| | | @click="openBatchDialog('flagCheck')" |
| | | v-ripple |
| | | > |
| | | {{ t('pages.basicInfo.whMat.actions.batchFlagCheck') }} |
| | | </ElButton> |
| | | <ElButton |
| | | v-if="showBatchActionButtons" |
| | | v-auth="'update'" |
| | | :disabled="selectedRows.length === 0" |
| | | @click="openBatchDialog('status')" |
| | | v-ripple |
| | | > |
| | | {{ t('pages.basicInfo.whMat.actions.batchStatus') }} |
| | | </ElButton> |
| | | <ElButton |
| | | v-if="showBatchActionButtons" |
| | | v-auth="'update'" |
| | | :disabled="selectedRows.length === 0" |
| | | @click="openBatchDialog('stockLevel')" |
| | | v-ripple |
| | | > |
| | | {{ t('pages.basicInfo.whMat.actions.batchStockLevel') }} |
| | | </ElButton> |
| | | <ElButton |
| | | v-if="showBatchActionButtons" |
| | | v-auth="'update'" |
| | | :disabled="selectedRows.length === 0" |
| | | @click="openBindLocDialog" |
| | | v-ripple |
| | | > |
| | | {{ t('pages.basicInfo.whMat.actions.bindLoc') }} |
| | | </ElButton> |
| | | <ElButton |
| | | v-auth="'delete'" |
| | | type="danger" |
| | | :disabled="selectedRows.length === 0" |
| | | @click="handleBatchDelete" |
| | | v-ripple |
| | | > |
| | | {{ t('common.actions.batchDelete') }} |
| | | </ElButton> |
| | | <div v-auth="'update'"> |
| | | <ElUpload |
| | | :auto-upload="false" |
| | | :show-file-list="false" |
| | | accept=".xlsx,.xls" |
| | | @change="handleImportFileChange" |
| | | > |
| | | <ElButton :loading="importing" v-ripple> |
| | | {{ t('pages.basicInfo.whMat.actions.import') }} |
| | | </ElButton> |
| | | </ElUpload> |
| | | </div> |
| | | <ElButton :loading="templateDownloading" @click="handleDownloadTemplate" v-ripple> |
| | | {{ t('pages.basicInfo.whMat.actions.downloadTemplate') }} |
| | | </ElButton> |
| | | <ListExportPrint |
| | | class="inline-flex" |
| | | :preview-visible="previewVisible" |
| | | @update:previewVisible="handlePreviewVisibleChange" |
| | | :report-title="reportTitle" |
| | | :selected-rows="selectedRows" |
| | | :query-params="reportQueryParams" |
| | | :columns="columns" |
| | | :preview-rows="previewRows" |
| | | :preview-meta="resolvedPreviewMeta" |
| | | :total="pagination.total" |
| | | :disabled="loading" |
| | | @export="handleExport" |
| | | @print="handlePrint" |
| | | /> |
| | | </ElSpace> |
| | | </template> |
| | | </ArtTableHeader> |
| | | |
| | | <ArtTable |
| | | :loading="loading" |
| | | :data="tableData" |
| | | :columns="columns" |
| | | :pagination="pagination" |
| | | row-key="id" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination:size-change="handleSizeChange" |
| | | @pagination:current-change="handleCurrentChange" |
| | | > |
| | | <template #action="{ row }"> |
| | | <ArtButtonTable icon="ri:eye-line" @click="openDetailDrawer(row)" /> |
| | | </template> |
| | | </ArtTable> |
| | | /> |
| | | </ElCard> |
| | | </div> |
| | | </div> |
| | | |
| | | <WhMatDialog |
| | | v-model:visible="dialogVisible" |
| | | :dialog-type="dialogType" |
| | | :material-data="currentMaterialData" |
| | | :group-options="groupOptions" |
| | | :serial-rule-options="serialRuleOptions" |
| | | @submit="handleDialogSubmit" |
| | | /> |
| | | |
| | | <WhMatBatchDialog |
| | | v-model:visible="batchDialogVisible" |
| | | :action-type="batchDialogType" |
| | | @submit="handleBatchDialogSubmit" |
| | | /> |
| | | |
| | | <WhMatBatchGroupDialog |
| | | v-model:visible="batchGroupDialogVisible" |
| | | :group-options="groupOptions" |
| | | @submit="handleBatchGroupSubmit" |
| | | /> |
| | | |
| | | <WhMatBindLocDialog |
| | | v-model:visible="bindLocDialogVisible" |
| | | :area-mat-options="areaMatOptions" |
| | | :area-options="areaOptions" |
| | | :loc-options="locOptions" |
| | | @submit="handleBindLocSubmit" |
| | | /> |
| | | |
| | | <WhMatDetailDrawer |
| | | v-model:visible="detailDrawerVisible" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ElMessage } from 'element-plus' |
| | | import { computed, onMounted, reactive, ref } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useI18n } from 'vue-i18n' |
| | | import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue' |
| | | import ListExportPrint from '@/components/biz/list-export-print/index.vue' |
| | | import { useAuth } from '@/hooks/core/useAuth' |
| | | import { useTableColumns } from '@/hooks/core/useTableColumns' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import { fetchSerialRulePage } from '@/api/system-manage' |
| | | import { fetchWarehouseAreasList } from '@/api/warehouse-areas' |
| | | import { fetchLocPage } from '@/api/loc' |
| | | import { fetchLocAreaMatList } from '@/api/loc-area-mat' |
| | | import { fetchBindLocAreaMatRelaByMatnr } from '@/api/loc-area-mat-rela' |
| | | import { defaultResponseAdapter } from '@/utils/table/tableUtils' |
| | | import { guardRequestWithMessage } from '@/utils/sys/requestGuard' |
| | | import { fetchMatnrDetail, fetchMatnrGroupTree, fetchMatnrPage } from '@/api/wh-mat' |
| | | import { useCrudPage } from '@/views/system/common/useCrudPage' |
| | | import { usePrintExportPage } from '@/views/system/common/usePrintExportPage' |
| | | import { |
| | | fetchBatchUpdateMatnr, |
| | | fetchBindMatnrGroup, |
| | | fetchDeleteMatnr, |
| | | fetchDownloadMatnrTemplate, |
| | | fetchEnabledFields, |
| | | fetchExportMatnrReport, |
| | | fetchGetMatnrMany, |
| | | fetchImportMatnr, |
| | | fetchMatnrDetail, |
| | | fetchMatnrGroupTree, |
| | | fetchMatnrPage, |
| | | fetchSaveMatnr, |
| | | fetchUpdateMatnr |
| | | } from '@/api/wh-mat' |
| | | import WhMatBatchDialog from './modules/wh-mat-batch-dialog.vue' |
| | | import WhMatBatchGroupDialog from './modules/wh-mat-batch-group-dialog.vue' |
| | | import WhMatBindLocDialog from './modules/wh-mat-bind-loc-dialog.vue' |
| | | import WhMatDialog from './modules/wh-mat-dialog.vue' |
| | | import WhMatDetailDrawer from './modules/wh-mat-detail-drawer.vue' |
| | | import { createWhMatTableColumns } from './whMatTable.columns' |
| | | import { |
| | | WH_MAT_REPORT_STYLE, |
| | | WH_MAT_REPORT_TITLE, |
| | | buildMatnrGroupTreeQueryParams, |
| | | buildMatnrPageQueryParams, |
| | | buildWhMatDialogModel, |
| | | buildWhMatPrintRows, |
| | | buildWhMatReportMeta, |
| | | buildWhMatSavePayload, |
| | | createWhMatSearchState, |
| | | getWhMatDynamicFieldKey, |
| | | getWhMatFlagLabelManageOptions, |
| | | getWhMatFlagCheckOptions, |
| | | getWhMatStockLevelOptions, |
| | | getWhMatStatusOptions, |
| | | getWhMatTreeNodeLabel, |
| | | normalizeMatnrDetail, |
| | | normalizeWhMatEnabledFields, |
| | | normalizeMatnrGroupTreeRows, |
| | | normalizeMatnrRow |
| | | normalizeMatnrRow, |
| | | resolveWhMatGroupOptions, |
| | | resolveWhMatSerialRuleOptions |
| | | } from './whMatPage.helpers' |
| | | |
| | | defineOptions({ name: 'WhMat' }) |
| | | const { t } = useI18n() |
| | | |
| | | const { t } = useI18n() |
| | | const { hasAuth } = useAuth() |
| | | const userStore = useUserStore() |
| | | |
| | | const showBatchActionButtons = false |
| | | const loading = ref(false) |
| | | const groupTreeLoading = ref(false) |
| | | const detailDrawerVisible = ref(false) |
| | | const detailLoading = ref(false) |
| | | const batchDialogVisible = ref(false) |
| | | const batchGroupDialogVisible = ref(false) |
| | | const bindLocDialogVisible = ref(false) |
| | | const bindLocOptionsLoading = ref(false) |
| | | const importing = ref(false) |
| | | const templateDownloading = ref(false) |
| | | const tableData = ref([]) |
| | | const groupTreeData = ref([]) |
| | | const detailData = ref({}) |
| | | const enabledFields = ref([]) |
| | | const serialRuleOptions = ref([]) |
| | | const areaOptions = ref([]) |
| | | const areaMatOptions = ref([]) |
| | | const locOptions = ref([]) |
| | | const selectedGroupId = ref(null) |
| | | const groupSearch = ref('') |
| | | const batchDialogType = ref('status') |
| | | const searchForm = ref(createWhMatSearchState()) |
| | | let handleDeleteAction = null |
| | | |
| | | const pagination = reactive({ |
| | | current: 1, |
| | |
| | | label: 'name', |
| | | children: 'children' |
| | | } |
| | | |
| | | const reportTitle = WH_MAT_REPORT_TITLE |
| | | const groupOptions = computed(() => resolveWhMatGroupOptions(groupTreeData.value)) |
| | | const defaultExpandedGroupKeys = computed(() => collectExpandedGroupKeys(groupTreeData.value)) |
| | | const reportQueryParams = computed(() => |
| | | buildMatnrPageQueryParams({ |
| | | ...searchForm.value, |
| | | groupId: searchForm.value?.groupId || selectedGroupId.value, |
| | | current: 1, |
| | | pageSize: pagination.size |
| | | }) |
| | | ) |
| | | |
| | | const searchItems = computed(() => [ |
| | | { |
| | |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.groupId'), |
| | | key: 'groupId', |
| | | type: 'treeselect', |
| | | props: { |
| | | data: groupOptions.value, |
| | | props: { |
| | | label: 'displayLabel', |
| | | value: 'value', |
| | | children: 'children' |
| | | }, |
| | | checkStrictly: true, |
| | | defaultExpandAll: true, |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.groupIdPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.platCode'), |
| | | key: 'platCode', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.platCodePlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.spec'), |
| | | key: 'spec', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.specPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.model'), |
| | | key: 'model', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.modelPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.color'), |
| | | key: 'color', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.colorPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.size'), |
| | | key: 'size', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.sizePlaceholder') |
| | | } |
| | | }, |
| | | { |
| | |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.barcodePlaceholder') |
| | | } |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.unit'), |
| | | key: 'unit', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.unitPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.purUnit'), |
| | | key: 'purUnit', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.purUnitPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.stockUnit'), |
| | | key: 'stockUnit', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.stockUnitPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.describle'), |
| | | key: 'describle', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.describlePlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.rglarId'), |
| | | key: 'rglarId', |
| | | type: 'select', |
| | | props: { |
| | | clearable: true, |
| | | filterable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.rglarIdPlaceholder'), |
| | | options: serialRuleOptions.value |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.weight'), |
| | | key: 'weight', |
| | | type: 'number', |
| | | props: { |
| | | min: 0, |
| | | controlsPosition: 'right', |
| | | valueOnClear: null, |
| | | placeholder: t('pages.basicInfo.whMat.search.weightPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.nromNum'), |
| | | key: 'nromNum', |
| | | type: 'number', |
| | | props: { |
| | | min: 0, |
| | | controlsPosition: 'right', |
| | | valueOnClear: null, |
| | | placeholder: t('pages.basicInfo.whMat.search.nromNumPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.stockLevel'), |
| | | key: 'stockLevel', |
| | | type: 'select', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.stockLevelPlaceholder'), |
| | | options: getWhMatStockLevelOptions() |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.flagLabelMange'), |
| | | key: 'flagLabelMange', |
| | | type: 'select', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.flagLabelMangePlaceholder'), |
| | | options: getWhMatFlagLabelManageOptions(t) |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.safeQty'), |
| | | key: 'safeQty', |
| | | type: 'number', |
| | | props: { |
| | | min: 0, |
| | | controlsPosition: 'right', |
| | | valueOnClear: null, |
| | | placeholder: t('pages.basicInfo.whMat.search.safeQtyPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.minQty'), |
| | | key: 'minQty', |
| | | type: 'number', |
| | | props: { |
| | | min: 0, |
| | | controlsPosition: 'right', |
| | | valueOnClear: null, |
| | | placeholder: t('pages.basicInfo.whMat.search.minQtyPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.maxQty'), |
| | | key: 'maxQty', |
| | | type: 'number', |
| | | props: { |
| | | min: 0, |
| | | controlsPosition: 'right', |
| | | valueOnClear: null, |
| | | placeholder: t('pages.basicInfo.whMat.search.maxQtyPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.stagn'), |
| | | key: 'stagn', |
| | | type: 'number', |
| | | props: { |
| | | min: 0, |
| | | controlsPosition: 'right', |
| | | valueOnClear: null, |
| | | placeholder: t('pages.basicInfo.whMat.search.stagnPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.valid'), |
| | | key: 'valid', |
| | | type: 'number', |
| | | props: { |
| | | min: 0, |
| | | controlsPosition: 'right', |
| | | valueOnClear: null, |
| | | placeholder: t('pages.basicInfo.whMat.search.validPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.validWarn'), |
| | | key: 'validWarn', |
| | | type: 'number', |
| | | props: { |
| | | min: 0, |
| | | controlsPosition: 'right', |
| | | valueOnClear: null, |
| | | placeholder: t('pages.basicInfo.whMat.search.validWarnPlaceholder') |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.flagCheck'), |
| | | key: 'flagCheck', |
| | | type: 'select', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.flagCheckPlaceholder'), |
| | | options: getWhMatFlagCheckOptions(t) |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.status'), |
| | | key: 'status', |
| | | type: 'select', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.statusPlaceholder'), |
| | | options: getWhMatStatusOptions(t) |
| | | } |
| | | }, |
| | | { |
| | | label: t('pages.basicInfo.whMat.search.memo'), |
| | | key: 'memo', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.memoPlaceholder') |
| | | } |
| | | }, |
| | | ...enabledFields.value.map((field) => ({ |
| | | label: field.fieldsAlise, |
| | | key: getWhMatDynamicFieldKey(field.fields), |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: t('pages.basicInfo.whMat.search.dynamicPlaceholder', { |
| | | field: field.fieldsAlise |
| | | }) |
| | | } |
| | | })) |
| | | ]) |
| | | |
| | | const { columnChecks, columns } = useTableColumns(() => |
| | | const { columnChecks, columns, resetColumns } = useTableColumns(() => |
| | | createWhMatTableColumns({ |
| | | t, |
| | | handleViewDetail: openDetailDrawer |
| | | enabledFields: enabledFields.value, |
| | | handleViewDetail: openDetailDrawer, |
| | | handleEdit: hasAuth('update') ? openEditDialog : null, |
| | | handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null, |
| | | handlePrint: (row) => handlePrint({ ids: [row.id] }), |
| | | canEdit: hasAuth('update'), |
| | | canDelete: hasAuth('delete'), |
| | | t |
| | | }) |
| | | ) |
| | | |
| | |
| | | } |
| | | } |
| | | return null |
| | | } |
| | | |
| | | function collectExpandedGroupKeys(nodes, depth = 1, maxExpandedDepth = 1) { |
| | | if (!Array.isArray(nodes) || depth > maxExpandedDepth) { |
| | | return [] |
| | | } |
| | | |
| | | return nodes.flatMap((node) => { |
| | | const currentId = node?.id !== undefined && node?.id !== null ? [node.id] : [] |
| | | return [ |
| | | ...currentId, |
| | | ...collectExpandedGroupKeys(node?.children || [], depth + 1, maxExpandedDepth) |
| | | ] |
| | | }) |
| | | } |
| | | |
| | | function normalizeOptionText(value) { |
| | | return String(value ?? '').trim() |
| | | } |
| | | |
| | | function buildOption(value, label, extra = {}) { |
| | | return { |
| | | value, |
| | | label, |
| | | ...extra |
| | | } |
| | | } |
| | | |
| | | async function loadEnabledFieldDefinitions() { |
| | | const fields = await guardRequestWithMessage(fetchEnabledFields(), [], { |
| | | timeoutMessage: t('pages.basicInfo.whMat.messages.enabledFieldsTimeout') |
| | | }) |
| | | enabledFields.value = normalizeWhMatEnabledFields(fields) |
| | | enabledFields.value.forEach((field) => { |
| | | const dynamicKey = getWhMatDynamicFieldKey(field.fields) |
| | | if (searchForm.value[dynamicKey] === undefined) { |
| | | searchForm.value[dynamicKey] = '' |
| | | } |
| | | }) |
| | | resetColumns() |
| | | } |
| | | |
| | | function updatePaginationState(target, response, fallbackCurrent, fallbackSize) { |
| | |
| | | } |
| | | } |
| | | |
| | | async function loadSerialRuleOptions() { |
| | | try { |
| | | const response = await guardRequestWithMessage( |
| | | fetchSerialRulePage({ current: 1, pageSize: 200 }), |
| | | { records: [] }, |
| | | { timeoutMessage: t('pages.basicInfo.whMat.messages.serialRuleTimeout') } |
| | | ) |
| | | serialRuleOptions.value = resolveWhMatSerialRuleOptions( |
| | | defaultResponseAdapter(response).records |
| | | ) |
| | | } catch (error) { |
| | | serialRuleOptions.value = [] |
| | | ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.serialRuleLoadFailed')) |
| | | } |
| | | } |
| | | |
| | | async function ensureBindLocOptionsLoaded(force = false) { |
| | | if ( |
| | | !force && |
| | | areaOptions.value.length && |
| | | areaMatOptions.value.length && |
| | | locOptions.value.length |
| | | ) { |
| | | return |
| | | } |
| | | if (bindLocOptionsLoading.value) { |
| | | return |
| | | } |
| | | |
| | | bindLocOptionsLoading.value = true |
| | | try { |
| | | const [areasResponse, areaMatResponse, locResponse] = await Promise.all([ |
| | | guardRequestWithMessage(fetchWarehouseAreasList(), [], { |
| | | timeoutMessage: t('pages.basicInfo.whMat.messages.bindLocTimeout') |
| | | }), |
| | | guardRequestWithMessage(fetchLocAreaMatList(), [], { |
| | | timeoutMessage: t('pages.basicInfo.whMat.messages.bindLocTimeout') |
| | | }), |
| | | guardRequestWithMessage( |
| | | fetchLocPage({ current: 1, pageSize: 1000 }), |
| | | { records: [] }, |
| | | { |
| | | timeoutMessage: t('pages.basicInfo.whMat.messages.bindLocTimeout') |
| | | } |
| | | ) |
| | | ]) |
| | | |
| | | areaOptions.value = defaultResponseAdapter(areasResponse) |
| | | .records.map((item) => |
| | | buildOption( |
| | | Number(item.id), |
| | | [normalizeOptionText(item.name), normalizeOptionText(item.code)] |
| | | .filter(Boolean) |
| | | .join(' · ') || t('common.placeholder.empty'), |
| | | { |
| | | areaId: Number(item.id), |
| | | warehouseId: item.warehouseId !== undefined ? Number(item.warehouseId) : void 0 |
| | | } |
| | | ) |
| | | ) |
| | | .filter((item) => Number.isFinite(item.value)) |
| | | |
| | | areaMatOptions.value = defaultResponseAdapter(areaMatResponse) |
| | | .records.map((item) => |
| | | buildOption( |
| | | Number(item.id), |
| | | [normalizeOptionText(item.code), normalizeOptionText(item.depict || item.name)] |
| | | .filter(Boolean) |
| | | .join(' · ') || t('common.placeholder.empty'), |
| | | { |
| | | areaMatId: Number(item.id), |
| | | areaId: item.areaId !== undefined ? Number(item.areaId) : void 0 |
| | | } |
| | | ) |
| | | ) |
| | | .filter((item) => Number.isFinite(item.value)) |
| | | |
| | | locOptions.value = defaultResponseAdapter(locResponse) |
| | | .records.map((item) => |
| | | buildOption( |
| | | Number(item.id), |
| | | normalizeOptionText(item.code) || t('common.placeholder.empty'), |
| | | { |
| | | areaId: item.areaId !== undefined ? Number(item.areaId) : void 0 |
| | | } |
| | | ) |
| | | ) |
| | | .filter((item) => Number.isFinite(item.value)) |
| | | } finally { |
| | | bindLocOptionsLoading.value = false |
| | | } |
| | | } |
| | | |
| | | async function loadMatnrList() { |
| | | loading.value = true |
| | | try { |
| | |
| | | fetchMatnrPage( |
| | | buildMatnrPageQueryParams({ |
| | | ...searchForm.value, |
| | | groupId: selectedGroupId.value, |
| | | groupId: searchForm.value?.groupId || selectedGroupId.value, |
| | | current: pagination.current, |
| | | pageSize: pagination.size |
| | | }) |
| | |
| | | { timeoutMessage: t('pages.basicInfo.whMat.messages.listTimeout') } |
| | | ) |
| | | tableData.value = Array.isArray(response?.records) |
| | | ? response.records.map((record) => normalizeMatnrRow(record, t)) |
| | | ? response.records.map((record) => normalizeMatnrRow(record, t, enabledFields.value)) |
| | | : [] |
| | | updatePaginationState(pagination, response, pagination.current, pagination.size) |
| | | } catch (error) { |
| | |
| | | } |
| | | } |
| | | |
| | | async function loadMatnrDetail(id) { |
| | | return await guardRequestWithMessage( |
| | | fetchMatnrDetail(id), |
| | | {}, |
| | | { |
| | | timeoutMessage: t('pages.basicInfo.whMat.messages.detailTimeout') |
| | | } |
| | | ) |
| | | } |
| | | |
| | | async function openDetailDrawer(row) { |
| | | detailDrawerVisible.value = true |
| | | detailLoading.value = true |
| | | try { |
| | | detailData.value = normalizeMatnrDetail( |
| | | await guardRequestWithMessage(fetchMatnrDetail(row.id), {}, { |
| | | timeoutMessage: t('pages.basicInfo.whMat.messages.detailTimeout') |
| | | }), |
| | | t |
| | | ) |
| | | detailData.value = normalizeMatnrDetail(await loadMatnrDetail(row.id), t, enabledFields.value) |
| | | } catch (error) { |
| | | detailDrawerVisible.value = false |
| | | detailData.value = {} |
| | |
| | | } |
| | | } |
| | | |
| | | async function openEditDialog(row) { |
| | | try { |
| | | const detail = await loadMatnrDetail(row.id) |
| | | showDialog('edit', detail) |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.detailLoadFailed')) |
| | | } |
| | | } |
| | | |
| | | const { |
| | | dialogVisible, |
| | | dialogType, |
| | | currentRecord: currentMaterialData, |
| | | selectedRows, |
| | | handleSelectionChange, |
| | | showDialog, |
| | | handleDialogSubmit, |
| | | handleDelete, |
| | | handleBatchDelete |
| | | } = useCrudPage({ |
| | | createEmptyModel: () => |
| | | buildWhMatDialogModel({ groupId: selectedGroupId.value || searchForm.value?.groupId || '' }), |
| | | buildEditModel: (record) => buildWhMatDialogModel(record), |
| | | buildSavePayload: (formData) => buildWhMatSavePayload(formData), |
| | | saveRequest: fetchSaveMatnr, |
| | | updateRequest: fetchUpdateMatnr, |
| | | deleteRequest: fetchDeleteMatnr, |
| | | entityName: t('pages.basicInfo.whMat.entity'), |
| | | resolveRecordLabel: (record) => record?.name || record?.code || record?.id, |
| | | refreshCreate: loadMatnrList, |
| | | refreshUpdate: loadMatnrList, |
| | | refreshRemove: loadMatnrList |
| | | }) |
| | | handleDeleteAction = handleDelete |
| | | |
| | | const getSelectedIds = () => |
| | | selectedRows.value.map((item) => Number(item?.id)).filter((id) => Number.isFinite(id)) |
| | | |
| | | const ensureSelectedRows = () => { |
| | | const ids = getSelectedIds() |
| | | if (!ids.length) { |
| | | ElMessage.warning(t('pages.basicInfo.whMat.messages.selectAtLeastOne')) |
| | | return [] |
| | | } |
| | | return ids |
| | | } |
| | | |
| | | function openBatchDialog(type) { |
| | | if (!ensureSelectedRows().length) { |
| | | return |
| | | } |
| | | batchDialogType.value = type |
| | | batchDialogVisible.value = true |
| | | } |
| | | |
| | | function openBatchGroupDialog() { |
| | | if (!ensureSelectedRows().length) { |
| | | return |
| | | } |
| | | batchGroupDialogVisible.value = true |
| | | } |
| | | |
| | | async function openBindLocDialog() { |
| | | if (!ensureSelectedRows().length) { |
| | | return |
| | | } |
| | | try { |
| | | await ensureBindLocOptionsLoaded() |
| | | bindLocDialogVisible.value = true |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.bindLocLoadFailed')) |
| | | } |
| | | } |
| | | |
| | | async function handleBatchDialogSubmit(formData) { |
| | | const ids = ensureSelectedRows() |
| | | if (!ids.length) { |
| | | batchDialogVisible.value = false |
| | | return |
| | | } |
| | | |
| | | try { |
| | | await fetchBatchUpdateMatnr({ |
| | | ids, |
| | | matnr: formData |
| | | }) |
| | | ElMessage.success(t('crud.messages.updateSuccess')) |
| | | batchDialogVisible.value = false |
| | | selectedRows.value = [] |
| | | await loadMatnrList() |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || t('crud.messages.submitFailed')) |
| | | } |
| | | } |
| | | |
| | | async function handleBatchGroupSubmit(formData) { |
| | | const ids = ensureSelectedRows() |
| | | if (!ids.length) { |
| | | batchGroupDialogVisible.value = false |
| | | return |
| | | } |
| | | |
| | | try { |
| | | await fetchBindMatnrGroup({ |
| | | ids, |
| | | groupId: formData.groupId |
| | | }) |
| | | ElMessage.success(t('crud.messages.updateSuccess')) |
| | | batchGroupDialogVisible.value = false |
| | | selectedRows.value = [] |
| | | await loadMatnrList() |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || t('crud.messages.submitFailed')) |
| | | } |
| | | } |
| | | |
| | | async function handleBindLocSubmit(formData) { |
| | | const ids = ensureSelectedRows() |
| | | if (!ids.length) { |
| | | bindLocDialogVisible.value = false |
| | | return |
| | | } |
| | | |
| | | try { |
| | | await fetchBindLocAreaMatRelaByMatnr({ |
| | | ...formData, |
| | | matnrId: ids |
| | | }) |
| | | ElMessage.success(t('crud.messages.updateSuccess')) |
| | | bindLocDialogVisible.value = false |
| | | selectedRows.value = [] |
| | | await loadMatnrList() |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || t('crud.messages.submitFailed')) |
| | | } |
| | | } |
| | | |
| | | const buildPreviewMeta = (rows) => { |
| | | const now = new Date() |
| | | return { |
| | | reportDate: now.toLocaleDateString('zh-CN'), |
| | | printedAt: now.toLocaleString('zh-CN', { hour12: false }), |
| | | operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '', |
| | | count: rows.length, |
| | | reportStyle: { ...WH_MAT_REPORT_STYLE } |
| | | } |
| | | } |
| | | |
| | | const resolvePrintRecords = async (payload) => { |
| | | if (Array.isArray(payload?.ids) && payload.ids.length > 0) { |
| | | return defaultResponseAdapter(await fetchGetMatnrMany(payload.ids)).records |
| | | } |
| | | return tableData.value |
| | | } |
| | | |
| | | const { |
| | | previewVisible, |
| | | previewRows, |
| | | previewMeta, |
| | | handlePreviewVisibleChange, |
| | | handleExport, |
| | | handlePrint |
| | | } = usePrintExportPage({ |
| | | downloadFileName: 'matnr.xlsx', |
| | | requestExport: (payload) => |
| | | fetchExportMatnrReport(payload, { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | }), |
| | | resolvePrintRecords, |
| | | buildPreviewRows: (records) => buildWhMatPrintRows(records, t), |
| | | buildPreviewMeta |
| | | }) |
| | | |
| | | const resolvedPreviewMeta = computed(() => |
| | | buildWhMatReportMeta({ |
| | | previewMeta: previewMeta.value, |
| | | count: previewRows.value.length, |
| | | orientation: previewMeta.value?.reportStyle?.orientation || WH_MAT_REPORT_STYLE.orientation |
| | | }) |
| | | ) |
| | | |
| | | async function downloadFile(response, fallbackName) { |
| | | if (!response?.ok) { |
| | | throw new Error( |
| | | t('crud.messages.exportFailedWithStatus', { status: response?.status || '-' }) |
| | | ) |
| | | } |
| | | const blob = await response.blob() |
| | | const downloadUrl = window.URL.createObjectURL(blob) |
| | | const link = document.createElement('a') |
| | | link.href = downloadUrl |
| | | link.download = fallbackName |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | link.remove() |
| | | window.URL.revokeObjectURL(downloadUrl) |
| | | } |
| | | |
| | | function handleShowDialog(type) { |
| | | showDialog(type) |
| | | } |
| | | |
| | | function handleSearch(params) { |
| | | searchForm.value = { |
| | | ...searchForm.value, |
| | | ...params |
| | | ...params, |
| | | orderBy: searchForm.value?.orderBy || 'create_time desc' |
| | | } |
| | | pagination.current = 1 |
| | | if (searchForm.value.groupId) { |
| | | selectedGroupId.value = null |
| | | } |
| | | loadMatnrList() |
| | | } |
| | | |
| | | async function handleReset() { |
| | | searchForm.value = createWhMatSearchState() |
| | | enabledFields.value.forEach((field) => { |
| | | searchForm.value[getWhMatDynamicFieldKey(field.fields)] = '' |
| | | }) |
| | | pagination.current = 1 |
| | | selectedGroupId.value = null |
| | | groupSearch.value = '' |
| | | await Promise.all([loadGroupTree(), loadMatnrList()]) |
| | | } |
| | | |
| | | function handleRefresh() { |
| | | loadMatnrList() |
| | | } |
| | | |
| | | function handleSizeChange(size) { |
| | |
| | | |
| | | function handleGroupNodeClick(data) { |
| | | selectedGroupId.value = data?.id ?? null |
| | | delete searchForm.value.groupId |
| | | pagination.current = 1 |
| | | loadMatnrList() |
| | | } |
| | |
| | | await Promise.all([loadGroupTree(), loadMatnrList()]) |
| | | } |
| | | |
| | | async function handleImportFileChange(uploadFile) { |
| | | if (!uploadFile?.raw) { |
| | | return |
| | | } |
| | | importing.value = true |
| | | try { |
| | | await fetchImportMatnr(uploadFile.raw) |
| | | ElMessage.success(t('pages.basicInfo.whMat.messages.importSuccess')) |
| | | await loadMatnrList() |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.importFailed')) |
| | | } finally { |
| | | importing.value = false |
| | | } |
| | | } |
| | | |
| | | async function handleDownloadTemplate() { |
| | | templateDownloading.value = true |
| | | try { |
| | | const response = await fetchDownloadMatnrTemplate( |
| | | {}, |
| | | { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | } |
| | | ) |
| | | await downloadFile(response, 'matnr-template.xlsx') |
| | | ElMessage.success(t('pages.basicInfo.whMat.messages.templateDownloadSuccess')) |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.templateDownloadFailed')) |
| | | } finally { |
| | | templateDownloading.value = false |
| | | } |
| | | } |
| | | |
| | | onMounted(async () => { |
| | | await Promise.all([loadGroupTree(), loadMatnrList()]) |
| | | await Promise.allSettled([ |
| | | loadEnabledFieldDefinitions(), |
| | | loadGroupTree(), |
| | | loadSerialRuleOptions() |
| | | ]) |
| | | await loadMatnrList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .wh-mat-page-root { |
| | | height: 100%; |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .wh-mat-page { |
| | | display: flex; |
| | | flex-direction: row; |
| | | align-items: flex-start; |
| | | align-items: stretch; |
| | | gap: 16px; |
| | | flex: 1 1 auto; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .wh-mat-page__sidebar { |
| | | width: 320px; |
| | | flex: 0 0 320px; |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .wh-mat-page__sidebar-card { |
| | | position: sticky; |
| | | top: 16px; |
| | | height: 100%; |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .wh-mat-page__sidebar-card :deep(.el-card__body) { |
| | | height: 100%; |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .wh-mat-page__tree-scroll { |
| | | height: calc(100vh - 320px); |
| | | min-height: 420px; |
| | | flex: 1 1 auto; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .wh-mat-page__content { |
| | | height: 100%; |
| | | min-width: 0; |
| | | min-height: 0; |
| | | flex: 1 1 auto; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .wh-mat-page__content > * + * { |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .wh-mat-page__content > :deep(.art-search-bar) { |
| | | flex: 0 0 auto; |
| | | } |
| | | |
| | | .wh-mat-page__content > :deep(.art-table-card) { |
| | | flex: 1 1 auto; |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .wh-mat-page__content > :deep(.art-table-card .el-card__body) { |
| | | display: flex; |
| | | min-height: 0; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .wh-mat-page__content > :deep(.art-table-card #art-table-header) { |
| | | flex: 0 0 auto; |
| | | } |
| | | |
| | | .wh-mat-page__content > :deep(.art-table-card .art-table) { |
| | | flex: 1 1 auto; |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .wh-mat-page__content > :deep(.art-table-card .art-table .el-table) { |
| | | flex: 1 1 auto; |
| | | min-height: 0; |
| | | height: auto; |
| | | } |
| | | |
| | | .wh-mat-page__content > :deep(.art-table-card .art-table .pagination) { |
| | | flex: 0 0 auto; |
| | | } |
| | | |
| | | @media (max-width: 1024px) { |
| | | .wh-mat-page { |
| | | flex-direction: column; |
| | | flex: none; |
| | | } |
| | | |
| | | .wh-mat-page__sidebar { |
| | |
| | | } |
| | | |
| | | .wh-mat-page__sidebar-card { |
| | | position: static; |
| | | height: auto; |
| | | } |
| | | |
| | | .wh-mat-page__tree-scroll { |