From 1d95b134d85c3c60cf0e72739888c9741a0bb1ee Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期五, 10 四月 2026 13:20:39 +0800
Subject: [PATCH] #页面优化
---
rsf-design/src/views/basic-info/wh-mat/index.vue | 996 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 961 insertions(+), 35 deletions(-)
diff --git a/rsf-design/src/views/basic-info/wh-mat/index.vue b/rsf-design/src/views/basic-info/wh-mat/index.vue
index fb852a1..6d28ea1 100644
--- a/rsf-design/src/views/basic-info/wh-mat/index.vue
+++ b/rsf-design/src/views/basic-info/wh-mat/index.vue
@@ -5,7 +5,9 @@
<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>
@@ -28,21 +30,26 @@
<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>
@@ -60,23 +67,155 @@
/>
<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"
@@ -87,38 +226,97 @@
</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,
@@ -130,6 +328,18 @@
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(() => [
{
@@ -160,12 +370,65 @@
}
},
{
+ 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')
}
},
{
@@ -176,13 +439,214 @@
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
})
)
@@ -208,6 +672,46 @@
}
}
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) {
@@ -237,6 +741,99 @@
}
}
+ 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 {
@@ -244,7 +841,7 @@
fetchMatnrPage(
buildMatnrPageQueryParams({
...searchForm.value,
- groupId: selectedGroupId.value,
+ groupId: searchForm.value?.groupId || selectedGroupId.value,
current: pagination.current,
pageSize: pagination.size
})
@@ -258,7 +855,7 @@
{ 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) {
@@ -269,16 +866,21 @@
}
}
+ 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 = {}
@@ -288,21 +890,236 @@
}
}
+ 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) {
@@ -318,6 +1135,7 @@
function handleGroupNodeClick(data) {
selectedGroupId.value = data?.id ?? null
+ delete searchForm.value.groupId
pagination.current = 1
loadMatnrList()
}
@@ -335,46 +1153,154 @@
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 {
@@ -383,7 +1309,7 @@
}
.wh-mat-page__sidebar-card {
- position: static;
+ height: auto;
}
.wh-mat-page__tree-scroll {
--
Gitblit v1.9.1