<template>
|
<div class="wh-mat-page-root">
|
<div class="wh-mat-page art-full-height">
|
<div class="wh-mat-page__sidebar">
|
<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-xs text-[var(--art-text-secondary)]">
|
{{ selectedGroupLabel }}
|
</div>
|
</div>
|
<ElButton text @click="handleResetGroup">{{ t('common.actions.viewAll') }}</ElButton>
|
</div>
|
|
<div class="mb-3 flex items-center gap-2">
|
<ElInput
|
v-model.trim="groupSearch"
|
clearable
|
:placeholder="t('pages.basicInfo.whMat.search.groupKeywordPlaceholder')"
|
@clear="handleGroupSearch"
|
@keyup.enter="handleGroupSearch"
|
/>
|
<ElButton @click="handleGroupSearch">{{ t('common.actions.search') }}</ElButton>
|
</div>
|
|
<ElScrollbar class="wh-mat-page__tree-scroll pr-1">
|
<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')"
|
/>
|
<ElTree
|
v-else
|
:data="groupTreeData"
|
:props="treeProps"
|
node-key="id"
|
highlight-current
|
: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>
|
</div>
|
</template>
|
</ElTree>
|
</ElScrollbar>
|
</ElCard>
|
</div>
|
|
<div class="wh-mat-page__content">
|
<ArtSearchBar
|
v-model="searchForm"
|
:items="searchItems"
|
:showExpand="true"
|
@search="handleSearch"
|
@reset="handleReset"
|
/>
|
|
<ElCard class="art-table-card">
|
<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"
|
/>
|
</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"
|
:loading="detailLoading"
|
:detail="detailData"
|
/>
|
</div>
|
</template>
|
|
<script setup>
|
import { computed, onMounted, reactive, ref } from 'vue'
|
import { ElMessage } from 'element-plus'
|
import { useI18n } from 'vue-i18n'
|
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 { 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,
|
resolveWhMatGroupOptions,
|
resolveWhMatSerialRuleOptions
|
} from './whMatPage.helpers'
|
|
defineOptions({ name: 'WhMat' })
|
|
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,
|
size: 20,
|
total: 0
|
})
|
|
const treeProps = {
|
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.condition'),
|
key: 'condition',
|
type: 'input',
|
props: {
|
clearable: true,
|
placeholder: t('pages.basicInfo.whMat.search.conditionPlaceholder')
|
}
|
},
|
{
|
label: t('pages.basicInfo.whMat.search.code'),
|
key: 'code',
|
type: 'input',
|
props: {
|
clearable: true,
|
placeholder: t('pages.basicInfo.whMat.search.codePlaceholder')
|
}
|
},
|
{
|
label: t('pages.basicInfo.whMat.search.name'),
|
key: 'name',
|
type: 'input',
|
props: {
|
clearable: true,
|
placeholder: t('pages.basicInfo.whMat.search.namePlaceholder')
|
}
|
},
|
{
|
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')
|
}
|
},
|
{
|
label: t('pages.basicInfo.whMat.search.barcode'),
|
key: 'barcode',
|
type: 'input',
|
props: {
|
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, resetColumns } = useTableColumns(() =>
|
createWhMatTableColumns({
|
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
|
})
|
)
|
|
const selectedGroupLabel = computed(() => {
|
if (!selectedGroupId.value) {
|
return t('pages.basicInfo.whMat.labels.allMaterials')
|
}
|
const found = findGroupNode(groupTreeData.value, selectedGroupId.value)
|
return found ? getWhMatTreeNodeLabel(found) : t('pages.basicInfo.whMat.labels.allMaterials')
|
})
|
|
function findGroupNode(nodes, targetId) {
|
const normalizedTarget = String(targetId || '')
|
for (const node of nodes || []) {
|
if (String(node.id) === normalizedTarget) {
|
return node
|
}
|
if (node.children?.length) {
|
const child = findGroupNode(node.children, normalizedTarget)
|
if (child) {
|
return child
|
}
|
}
|
}
|
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) {
|
target.total = Number(response?.total || 0)
|
target.current = Number(response?.current || fallbackCurrent || 1)
|
target.size = Number(response?.size || fallbackSize || target.size || 20)
|
}
|
|
async function loadGroupTree() {
|
groupTreeLoading.value = true
|
try {
|
const records = await guardRequestWithMessage(
|
fetchMatnrGroupTree(buildMatnrGroupTreeQueryParams({ condition: groupSearch.value })),
|
[],
|
{ timeoutMessage: t('pages.basicInfo.whMat.messages.groupTimeout') }
|
)
|
const normalizedTree = normalizeMatnrGroupTreeRows(Array.isArray(records) ? records : [], t)
|
groupTreeData.value = normalizedTree
|
if (selectedGroupId.value && !findGroupNode(normalizedTree, selectedGroupId.value)) {
|
selectedGroupId.value = null
|
}
|
} catch (error) {
|
groupTreeData.value = []
|
ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.groupLoadFailed'))
|
} finally {
|
groupTreeLoading.value = false
|
}
|
}
|
|
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 {
|
const response = await guardRequestWithMessage(
|
fetchMatnrPage(
|
buildMatnrPageQueryParams({
|
...searchForm.value,
|
groupId: searchForm.value?.groupId || selectedGroupId.value,
|
current: pagination.current,
|
pageSize: pagination.size
|
})
|
),
|
{
|
records: [],
|
total: 0,
|
current: pagination.current,
|
size: pagination.size
|
},
|
{ timeoutMessage: t('pages.basicInfo.whMat.messages.listTimeout') }
|
)
|
tableData.value = Array.isArray(response?.records)
|
? response.records.map((record) => normalizeMatnrRow(record, t, enabledFields.value))
|
: []
|
updatePaginationState(pagination, response, pagination.current, pagination.size)
|
} catch (error) {
|
tableData.value = []
|
ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.listLoadFailed'))
|
} finally {
|
loading.value = false
|
}
|
}
|
|
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 loadMatnrDetail(row.id), t, enabledFields.value)
|
} catch (error) {
|
detailDrawerVisible.value = false
|
detailData.value = {}
|
ElMessage.error(error?.message || t('pages.basicInfo.whMat.messages.detailLoadFailed'))
|
} finally {
|
detailLoading.value = false
|
}
|
}
|
|
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,
|
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) {
|
pagination.size = size
|
pagination.current = 1
|
loadMatnrList()
|
}
|
|
function handleCurrentChange(current) {
|
pagination.current = current
|
loadMatnrList()
|
}
|
|
function handleGroupNodeClick(data) {
|
selectedGroupId.value = data?.id ?? null
|
delete searchForm.value.groupId
|
pagination.current = 1
|
loadMatnrList()
|
}
|
|
async function handleResetGroup() {
|
selectedGroupId.value = null
|
pagination.current = 1
|
groupSearch.value = ''
|
await Promise.all([loadGroupTree(), loadMatnrList()])
|
}
|
|
async function handleGroupSearch() {
|
selectedGroupId.value = null
|
pagination.current = 1
|
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.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: 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 {
|
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 {
|
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 {
|
width: 100%;
|
flex-basis: auto;
|
}
|
|
.wh-mat-page__sidebar-card {
|
height: auto;
|
}
|
|
.wh-mat-page__tree-scroll {
|
height: 320px;
|
min-height: 320px;
|
}
|
}
|
</style>
|