feat: complete wh-mat page migration
| New file |
| | |
| | | import request from '@/utils/http' |
| | | |
| | | function normalizeText(value) { |
| | | return String(value ?? '').trim() |
| | | } |
| | | |
| | | function normalizeQueryParams(params = {}) { |
| | | const result = { |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20 |
| | | } |
| | | |
| | | ;['condition', 'code', 'name', 'spec', 'model', 'color', 'size', 'barcode', 'groupId'].forEach((key) => { |
| | | const value = params[key] |
| | | if (value === undefined || value === null || value === '') { |
| | | return |
| | | } |
| | | if (typeof value === 'string') { |
| | | const trimmed = normalizeText(value) |
| | | if (trimmed) { |
| | | result[key] = trimmed |
| | | } |
| | | return |
| | | } |
| | | result[key] = value |
| | | }) |
| | | |
| | | return result |
| | | } |
| | | |
| | | function normalizeGroupTreeParams(params = {}) { |
| | | return { |
| | | condition: normalizeText(params.condition) |
| | | } |
| | | } |
| | | |
| | | export function fetchMatnrPage(params = {}) { |
| | | return request.post({ |
| | | url: '/matnr/page', |
| | | params: normalizeQueryParams(params) |
| | | }) |
| | | } |
| | | |
| | | export function fetchMatnrDetail(id) { |
| | | return request.get({ url: `/matnr/${id}` }) |
| | | } |
| | | |
| | | export function fetchMatnrGroupTree(params = {}) { |
| | | return request.post({ |
| | | url: '/matnrGroup/tree', |
| | | params: normalizeGroupTreeParams(params) |
| | | }) |
| | | } |
| | | |
| | |
| | | console: '/dashboard/console', |
| | | user: '/system/user', |
| | | role: '/system/role', |
| | | aiParam: '/system/ai-param', |
| | | aiPrompt: '/system/ai-prompt', |
| | | aiCallLog: '/system/ai-observe', |
| | | aiMcpMount: '/system/ai-mcp-mount', |
| | | dept: '/system/dept', |
| | | tenant: '/system/tenant', |
| | | host: '/system/host', |
| | | menu: '/system/menu', |
| | | config: '/system/config', |
| | | dictType: '/system/dict-type', |
| | | fields: '/system/fields', |
| | | fieldsItem: '/system/fields-item', |
| | | whMat: '/basic-info/wh-mat', |
| | | matnr: '/basic-info/wh-mat', |
| | | warehouseStock: '/stock/warehouse-stock', |
| | | warehouseAreasItem: '/stock/warehouse-areas-item', |
| | | qlyInspect: '/manager/qly-inspect', |
| | | locRevise: '/manager/loc-revise', |
| | | freeze: '/manager/freeze', |
| | | stock: '/manager/stock', |
| | | task: '/manager/task', |
| | | locPreview: '/manager/loc-preview', |
| | | waveRule: '/manager/wave-rule', |
| | | menuPda: '/manager/menu-pda', |
| | | serialRule: '/system/serial-rule', |
| | | operationRecord: '/system/operation-record', |
| | | userLogin: '/system/user-login' |
| | | } |
| | | |
| | |
| | | return '' |
| | | } |
| | | |
| | | if (hasChildren && normalizedKey && !PHASE_1_COMPONENTS[normalizedKey]) { |
| | | return '' |
| | | } |
| | | |
| | | if (!normalizedKey) { |
| | | return normalizeComponentPath(fullRoutePath) |
| | | } |
| | |
| | | // meta: { title: 'menus.dashboard.title' } |
| | | // }, |
| | | { |
| | | path: '/dashboard', |
| | | component: () => import('@views/index/index.vue'), |
| | | name: 'Dashboard', |
| | | meta: { title: 'menus.dashboard.title' }, |
| | | children: [ |
| | | { |
| | | path: 'console', |
| | | name: 'Console', |
| | | component: () => import('@views/dashboard/console/index.vue'), |
| | | meta: { |
| | | title: 'menus.dashboard.console', |
| | | icon: 'ri:home-smile-2-line', |
| | | keepAlive: false, |
| | | fixedTab: true |
| | | } |
| | | } |
| | | ] |
| | | }, |
| | | { |
| | | path: '/basic-info', |
| | | component: () => import('@views/index/index.vue'), |
| | | name: 'BasicInfo', |
| | | meta: { title: 'menu.basicInfo' }, |
| | | children: [ |
| | | { |
| | | path: 'wh-mat', |
| | | name: 'WhMat', |
| | | component: () => import('@views/basic-info/wh-mat/index.vue'), |
| | | meta: { |
| | | title: 'menu.matnr', |
| | | icon: 'ri:bill-line', |
| | | keepAlive: false |
| | | } |
| | | } |
| | | ] |
| | | }, |
| | | { |
| | | path: '/auth/login', |
| | | name: 'Login', |
| | | component: () => import('@views/auth/login/index.vue'), |
| New file |
| | |
| | | <template> |
| | | <div class="wh-mat-page art-full-height flex flex-col gap-4 xl:flex-row"> |
| | | <ElCard class="w-full shrink-0 xl:w-[320px]"> |
| | | <div class="mb-3 flex items-center justify-between gap-3"> |
| | | <div> |
| | | <div class="text-base font-medium text-[var(--art-text-primary)]">物料分组</div> |
| | | <div class="text-xs text-[var(--art-text-secondary)]"> |
| | | {{ selectedGroupLabel }} |
| | | </div> |
| | | </div> |
| | | <ElButton text @click="handleResetGroup">全部</ElButton> |
| | | </div> |
| | | |
| | | <div class="mb-3 flex items-center gap-2"> |
| | | <ElInput |
| | | v-model.trim="groupSearch" |
| | | clearable |
| | | placeholder="搜索物料分组" |
| | | @clear="handleGroupSearch" |
| | | @keyup.enter="handleGroupSearch" |
| | | /> |
| | | <ElButton @click="handleGroupSearch">搜索</ElButton> |
| | | </div> |
| | | |
| | | <ElScrollbar class="h-[calc(100vh-260px)] pr-1"> |
| | | <div v-if="groupTreeLoading" class="py-6"> |
| | | <ElSkeleton :rows="10" animated /> |
| | | </div> |
| | | <ElEmpty v-else-if="!groupTreeData.length" description="暂无物料分组" /> |
| | | <ElTree |
| | | v-else |
| | | :data="groupTreeData" |
| | | :props="treeProps" |
| | | node-key="id" |
| | | highlight-current |
| | | default-expand-all |
| | | :current-node-key="selectedGroupId" |
| | | @node-click="handleGroupNodeClick" |
| | | > |
| | | <template #default="{ data }"> |
| | | <div class="flex items-center gap-2"> |
| | | <span class="font-medium">{{ data.name || '--' }}</span> |
| | | <span class="text-xs text-[var(--art-text-secondary)]">{{ data.code || '--' }}</span> |
| | | </div> |
| | | </template> |
| | | </ElTree> |
| | | </ElScrollbar> |
| | | </ElCard> |
| | | |
| | | <div class="min-w-0 flex-1 space-y-4"> |
| | | <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="loadMatnrList" /> |
| | | |
| | | <ArtTable |
| | | :loading="loading" |
| | | :data="tableData" |
| | | :columns="columns" |
| | | :pagination="pagination" |
| | | @pagination:size-change="handleSizeChange" |
| | | @pagination:current-change="handleCurrentChange" |
| | | > |
| | | <template #action="{ row }"> |
| | | <ArtButtonTable icon="ri:eye-line" @click="openDetailDrawer(row)" /> |
| | | </template> |
| | | </ArtTable> |
| | | </ElCard> |
| | | </div> |
| | | |
| | | <WhMatDetailDrawer v-model:visible="detailDrawerVisible" :loading="detailLoading" :detail="detailData" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ElMessage } from 'element-plus' |
| | | import { computed, onMounted, reactive, ref } from 'vue' |
| | | import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue' |
| | | import { useTableColumns } from '@/hooks/core/useTableColumns' |
| | | import { guardRequestWithMessage } from '@/utils/sys/requestGuard' |
| | | import { fetchMatnrDetail, fetchMatnrGroupTree, fetchMatnrPage } from '@/api/wh-mat' |
| | | import WhMatDetailDrawer from './modules/wh-mat-detail-drawer.vue' |
| | | import { createWhMatTableColumns } from './whMatTable.columns' |
| | | import { |
| | | buildMatnrGroupTreeQueryParams, |
| | | buildMatnrPageQueryParams, |
| | | createWhMatSearchState, |
| | | getWhMatTreeNodeLabel, |
| | | normalizeMatnrDetail, |
| | | normalizeMatnrGroupTreeRows, |
| | | normalizeMatnrRow |
| | | } from './whMatPage.helpers' |
| | | |
| | | defineOptions({ name: 'WhMat' }) |
| | | |
| | | const loading = ref(false) |
| | | const groupTreeLoading = ref(false) |
| | | const detailDrawerVisible = ref(false) |
| | | const detailLoading = ref(false) |
| | | const tableData = ref([]) |
| | | const groupTreeData = ref([]) |
| | | const detailData = ref({}) |
| | | const selectedGroupId = ref(null) |
| | | const groupSearch = ref('') |
| | | const searchForm = ref(createWhMatSearchState()) |
| | | |
| | | const pagination = reactive({ |
| | | current: 1, |
| | | size: 20, |
| | | total: 0 |
| | | }) |
| | | |
| | | const treeProps = { |
| | | label: 'name', |
| | | children: 'children' |
| | | } |
| | | |
| | | const searchItems = computed(() => [ |
| | | { |
| | | label: '关键字', |
| | | key: 'condition', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: '请输入物料编码/物料名称' |
| | | } |
| | | }, |
| | | { |
| | | label: '物料编码', |
| | | key: 'code', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: '请输入物料编码' |
| | | } |
| | | }, |
| | | { |
| | | label: '物料名称', |
| | | key: 'name', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: '请输入物料名称' |
| | | } |
| | | }, |
| | | { |
| | | label: '规格', |
| | | key: 'spec', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: '请输入规格' |
| | | } |
| | | }, |
| | | { |
| | | label: '条码', |
| | | key: 'barcode', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: '请输入条码' |
| | | } |
| | | } |
| | | ]) |
| | | |
| | | const { columnChecks, columns } = useTableColumns(() => |
| | | createWhMatTableColumns({ |
| | | handleViewDetail: openDetailDrawer |
| | | }) |
| | | ) |
| | | |
| | | const selectedGroupLabel = computed(() => { |
| | | if (!selectedGroupId.value) { |
| | | return '全部物料' |
| | | } |
| | | const found = findGroupNode(groupTreeData.value, selectedGroupId.value) |
| | | return found ? getWhMatTreeNodeLabel(found) : '全部物料' |
| | | }) |
| | | |
| | | 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 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: '物料分组加载超时,已停止等待' } |
| | | ) |
| | | const normalizedTree = normalizeMatnrGroupTreeRows(Array.isArray(records) ? records : []) |
| | | groupTreeData.value = normalizedTree |
| | | if (selectedGroupId.value && !findGroupNode(normalizedTree, selectedGroupId.value)) { |
| | | selectedGroupId.value = null |
| | | } |
| | | } catch (error) { |
| | | groupTreeData.value = [] |
| | | ElMessage.error(error?.message || '物料分组加载失败') |
| | | } finally { |
| | | groupTreeLoading.value = false |
| | | } |
| | | } |
| | | |
| | | async function loadMatnrList() { |
| | | loading.value = true |
| | | try { |
| | | const response = await guardRequestWithMessage( |
| | | fetchMatnrPage( |
| | | buildMatnrPageQueryParams({ |
| | | ...searchForm.value, |
| | | groupId: selectedGroupId.value, |
| | | current: pagination.current, |
| | | pageSize: pagination.size |
| | | }) |
| | | ), |
| | | { |
| | | records: [], |
| | | total: 0, |
| | | current: pagination.current, |
| | | size: pagination.size |
| | | }, |
| | | { timeoutMessage: '物料列表加载超时,已停止等待' } |
| | | ) |
| | | tableData.value = Array.isArray(response?.records) |
| | | ? response.records.map((record) => normalizeMatnrRow(record)) |
| | | : [] |
| | | updatePaginationState(pagination, response, pagination.current, pagination.size) |
| | | } catch (error) { |
| | | tableData.value = [] |
| | | ElMessage.error(error?.message || '物料列表加载失败') |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | async function openDetailDrawer(row) { |
| | | detailDrawerVisible.value = true |
| | | detailLoading.value = true |
| | | try { |
| | | detailData.value = normalizeMatnrDetail( |
| | | await guardRequestWithMessage(fetchMatnrDetail(row.id), {}, { |
| | | timeoutMessage: '物料详情加载超时,已停止等待' |
| | | }) |
| | | ) |
| | | } catch (error) { |
| | | detailDrawerVisible.value = false |
| | | detailData.value = {} |
| | | ElMessage.error(error?.message || '获取物料详情失败') |
| | | } finally { |
| | | detailLoading.value = false |
| | | } |
| | | } |
| | | |
| | | function handleSearch(params) { |
| | | searchForm.value = { |
| | | ...searchForm.value, |
| | | ...params |
| | | } |
| | | pagination.current = 1 |
| | | loadMatnrList() |
| | | } |
| | | |
| | | async function handleReset() { |
| | | searchForm.value = createWhMatSearchState() |
| | | pagination.current = 1 |
| | | selectedGroupId.value = null |
| | | groupSearch.value = '' |
| | | await Promise.all([loadGroupTree(), 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 |
| | | 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()]) |
| | | } |
| | | |
| | | onMounted(async () => { |
| | | await Promise.all([loadGroupTree(), loadMatnrList()]) |
| | | }) |
| | | </script> |
| New file |
| | |
| | | <template> |
| | | <ElDrawer |
| | | :model-value="visible" |
| | | title="物料详情" |
| | | size="960px" |
| | | destroy-on-close |
| | | @update:model-value="handleVisibleChange" |
| | | > |
| | | <ElScrollbar class="h-[calc(100vh-180px)] pr-1"> |
| | | <div v-if="loading" class="py-6"> |
| | | <ElSkeleton :rows="12" animated /> |
| | | </div> |
| | | <div v-else class="space-y-4"> |
| | | <ElDescriptions title="基础信息" :column="2" border> |
| | | <ElDescriptionsItem label="物料编码">{{ detail.code || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="物料名称">{{ detail.name || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="物料分组">{{ detail.groupName || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="货主">{{ detail.shipperName || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="条码">{{ detail.barcode || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="规格">{{ detail.spec || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="型号">{{ detail.model || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="颜色">{{ detail.color || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="尺寸">{{ detail.size || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="描述">{{ detail.describle || '--' }}</ElDescriptionsItem> |
| | | </ElDescriptions> |
| | | |
| | | <ElDescriptions title="库存属性" :column="2" border> |
| | | <ElDescriptionsItem label="单位">{{ detail.unit || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="采购单位">{{ detail.purUnit || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="库位单位">{{ detail.stockUnit || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="出入库优先级">{{ detail.stockLevelText || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="是否标签管理">{{ detail.flagLabelManageText || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="是否免检">{{ detail.flagCheckText || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="安全库存">{{ detail.safeQty ?? '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="最小库存预警">{{ detail.minQty ?? '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="最大库存预警">{{ detail.maxQty ?? '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="停滞天数">{{ detail.stagn ?? '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="保质期天数">{{ detail.valid ?? '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="效期预警阈值">{{ detail.validWarn ?? '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="状态">{{ detail.statusText || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="基础单位">{{ detail.baseUnit || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="使用组织">{{ detail.useOrgName || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="ERP分类">{{ detail.erpClsId || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="备注">{{ detail.memo || '--' }}</ElDescriptionsItem> |
| | | </ElDescriptions> |
| | | |
| | | <ElDescriptions title="审计信息" :column="2" border> |
| | | <ElDescriptionsItem label="创建人">{{ detail.createByText || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="创建时间">{{ detail.createTimeText || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="更新人">{{ detail.updateByText || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="更新时间">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem> |
| | | </ElDescriptions> |
| | | |
| | | <ElDescriptions v-if="extendFieldEntries.length" title="扩展字段" :column="2" border> |
| | | <ElDescriptionsItem |
| | | v-for="entry in extendFieldEntries" |
| | | :key="entry.key" |
| | | :label="entry.key" |
| | | > |
| | | {{ entry.value || '--' }} |
| | | </ElDescriptionsItem> |
| | | </ElDescriptions> |
| | | </div> |
| | | </ElScrollbar> |
| | | </ElDrawer> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed } from 'vue' |
| | | |
| | | const props = defineProps({ |
| | | visible: { type: Boolean, default: false }, |
| | | loading: { type: Boolean, default: false }, |
| | | detail: { type: Object, default: () => ({}) } |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:visible']) |
| | | |
| | | const visible = computed({ |
| | | get: () => props.visible, |
| | | set: (value) => emit('update:visible', value) |
| | | }) |
| | | |
| | | const extendFieldEntries = computed(() => { |
| | | const fields = props.detail?.extendFields |
| | | if (!fields || typeof fields !== 'object') { |
| | | return [] |
| | | } |
| | | return Object.entries(fields) |
| | | .map(([key, value]) => ({ |
| | | key, |
| | | value: String(value ?? '').trim() |
| | | })) |
| | | .filter((item) => item.key) |
| | | }) |
| | | |
| | | function handleVisibleChange(value) { |
| | | visible.value = value |
| | | } |
| | | </script> |
| New file |
| | |
| | | function normalizeText(value) { |
| | | return String(value ?? '').trim() |
| | | } |
| | | |
| | | function normalizeNumber(value, fallback = 0) { |
| | | if (value === '' || value === null || value === undefined) { |
| | | return fallback |
| | | } |
| | | const numericValue = Number(value) |
| | | return Number.isFinite(numericValue) ? numericValue : fallback |
| | | } |
| | | |
| | | function normalizeNullableNumber(value) { |
| | | if (value === '' || value === null || value === undefined) { |
| | | return null |
| | | } |
| | | const numericValue = Number(value) |
| | | return Number.isFinite(numericValue) ? numericValue : null |
| | | } |
| | | |
| | | export function createWhMatSearchState() { |
| | | return { |
| | | condition: '', |
| | | code: '', |
| | | name: '', |
| | | spec: '', |
| | | model: '', |
| | | barcode: '' |
| | | } |
| | | } |
| | | |
| | | export function buildWhMatPageQueryParams(params = {}) { |
| | | const result = { |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20 |
| | | } |
| | | |
| | | ;['condition', 'code', 'name', 'spec', 'model', 'barcode'].forEach((key) => { |
| | | const value = normalizeText(params[key]) |
| | | if (value) { |
| | | result[key] = value |
| | | } |
| | | }) |
| | | |
| | | if (params.groupId !== undefined && params.groupId !== null && params.groupId !== '') { |
| | | result.groupId = String(params.groupId) |
| | | } |
| | | |
| | | return result |
| | | } |
| | | |
| | | export function buildWhMatGroupTreeQueryParams(params = {}) { |
| | | return { |
| | | condition: normalizeText(params.condition) |
| | | } |
| | | } |
| | | |
| | | export function normalizeWhMatGroupTreeRows(records = []) { |
| | | if (!Array.isArray(records)) { |
| | | return [] |
| | | } |
| | | |
| | | return records.map((item) => { |
| | | const children = normalizeWhMatGroupTreeRows(item?.children || []) |
| | | const id = normalizeNullableNumber(item?.id) |
| | | const code = normalizeText(item?.code) |
| | | const name = normalizeText(item?.name) |
| | | const label = [name, code].filter(Boolean).join(' · ') || '-' |
| | | |
| | | return { |
| | | ...item, |
| | | id, |
| | | parentId: normalizeNumber(item?.parentId, 0), |
| | | code, |
| | | name, |
| | | label, |
| | | displayLabel: label, |
| | | status: normalizeNullableNumber(item?.status), |
| | | statusText: normalizeNumber(item?.status, 1) === 1 ? '正常' : '冻结', |
| | | statusType: normalizeNumber(item?.status, 1) === 1 ? 'success' : 'danger', |
| | | memo: normalizeText(item?.memo) || '-', |
| | | children |
| | | } |
| | | }) |
| | | } |
| | | |
| | | export function normalizeWhMatRow(record = {}) { |
| | | const statusValue = normalizeNullableNumber(record?.status) |
| | | return { |
| | | ...record, |
| | | code: normalizeText(record?.code) || '-', |
| | | name: normalizeText(record?.name) || '-', |
| | | groupName: normalizeText(record?.groupId$ || record?.groupCode) || '-', |
| | | shipperName: normalizeText(record?.shipperId$ || record?.shipperName) || '-', |
| | | barcode: normalizeText(record?.barcode) || '-', |
| | | spec: normalizeText(record?.spec) || '-', |
| | | model: normalizeText(record?.model) || '-', |
| | | color: normalizeText(record?.color) || '-', |
| | | size: normalizeText(record?.size) || '-', |
| | | unit: normalizeText(record?.unit) || '-', |
| | | purUnit: normalizeText(record?.purUnit) || '-', |
| | | stockUnit: normalizeText(record?.stockUnit) || '-', |
| | | stockLevelText: normalizeText(record?.stockLeval$) || '-', |
| | | flagLabelManageText: normalizeText(record?.flagLabelMange$) || '-', |
| | | flagCheckText: |
| | | record?.flagCheck === 1 || record?.flagCheck === '1' |
| | | ? '是' |
| | | : record?.flagCheck === 0 || record?.flagCheck === '0' |
| | | ? '否' |
| | | : '-', |
| | | statusText: normalizeText(record?.status$) || (statusValue === 1 ? '正常' : statusValue === 0 ? '冻结' : '-'), |
| | | statusType: statusValue === 1 ? 'success' : statusValue === 0 ? 'danger' : 'info', |
| | | safeQty: record?.safeQty ?? '-', |
| | | minQty: record?.minQty ?? '-', |
| | | maxQty: record?.maxQty ?? '-', |
| | | valid: record?.valid ?? '-', |
| | | validWarn: record?.validWarn ?? '-', |
| | | stagn: record?.stagn ?? '-', |
| | | describle: normalizeText(record?.describle) || '-', |
| | | baseUnit: normalizeText(record?.baseUnit) || '-', |
| | | useOrgName: normalizeText(record?.useOrgName) || '-', |
| | | erpClsId: normalizeText(record?.erpClsId) || '-', |
| | | memo: normalizeText(record?.memo) || '-', |
| | | updateByText: normalizeText(record?.updateBy$) || '-', |
| | | createByText: normalizeText(record?.createBy$) || '-', |
| | | updateTimeText: normalizeText(record?.updateTime$ || record?.updateTime) || '-', |
| | | createTimeText: normalizeText(record?.createTime$ || record?.createTime) || '-', |
| | | extendFields: |
| | | record?.extendFields && typeof record.extendFields === 'object' && !Array.isArray(record.extendFields) |
| | | ? record.extendFields |
| | | : {} |
| | | } |
| | | } |
| | | |
| | | export function normalizeWhMatDetail(record = {}) { |
| | | return normalizeWhMatRow(record) |
| | | } |
| | | |
| | | export function getWhMatTreeNodeLabel(node = {}) { |
| | | const name = normalizeText(node?.name) |
| | | const code = normalizeText(node?.code) |
| | | return [name, code].filter(Boolean).join(' · ') || '-' |
| | | } |
| | | |
| | | export const buildMatnrPageQueryParams = buildWhMatPageQueryParams |
| | | export const buildMatnrGroupTreeQueryParams = buildWhMatGroupTreeQueryParams |
| | | export const normalizeMatnrGroupTreeRows = normalizeWhMatGroupTreeRows |
| | | export const normalizeMatnrRow = normalizeWhMatRow |
| | | export const normalizeMatnrDetail = normalizeWhMatDetail |
| | | export const createMatnrSearchState = createWhMatSearchState |
| | | export const getMatnrTreeNodeLabel = getWhMatTreeNodeLabel |
| New file |
| | |
| | | import { h } from 'vue' |
| | | import { ElTag } from 'element-plus' |
| | | |
| | | export function createWhMatTableColumns({ handleViewDetail }) { |
| | | return [ |
| | | { |
| | | prop: 'code', |
| | | label: '物料编码', |
| | | minWidth: 150, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'name', |
| | | label: '物料名称', |
| | | minWidth: 220, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'groupName', |
| | | label: '物料分组', |
| | | minWidth: 160, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'barcode', |
| | | label: '条码', |
| | | minWidth: 160, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'spec', |
| | | label: '规格', |
| | | minWidth: 150, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'model', |
| | | label: '型号', |
| | | minWidth: 150, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'unit', |
| | | label: '单位', |
| | | width: 100 |
| | | }, |
| | | { |
| | | prop: 'status', |
| | | label: '状态', |
| | | width: 100, |
| | | align: 'center', |
| | | formatter: (row) => |
| | | h(ElTag, { type: row.statusType || 'info', effect: 'light' }, () => row.statusText || '-') |
| | | }, |
| | | { |
| | | prop: 'updateTimeText', |
| | | label: '更新时间', |
| | | minWidth: 180, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'action', |
| | | label: '操作', |
| | | width: 100, |
| | | fixed: 'right', |
| | | align: 'center', |
| | | useSlot: true |
| | | } |
| | | ] |
| | | } |
| New file |
| | |
| | | import assert from 'node:assert/strict' |
| | | import test from 'node:test' |
| | | |
| | | test('builds matnr page params with trimmed filters and group ids', async () => { |
| | | const { buildMatnrPageQueryParams } = await import( |
| | | '../src/views/basic-info/wh-mat/whMatPage.helpers.js' |
| | | ) |
| | | |
| | | assert.deepEqual( |
| | | buildMatnrPageQueryParams({ |
| | | current: 2, |
| | | pageSize: 30, |
| | | condition: ' 半成品 ', |
| | | code: ' RM001 ', |
| | | name: ' 物料A ', |
| | | spec: '', |
| | | groupId: 19 |
| | | }), |
| | | { |
| | | current: 2, |
| | | pageSize: 30, |
| | | condition: '半成品', |
| | | code: 'RM001', |
| | | name: '物料A', |
| | | groupId: '19' |
| | | } |
| | | ) |
| | | }) |
| | | |
| | | test('normalizes matnr group trees for el-tree rendering', async () => { |
| | | const { normalizeMatnrGroupTreeRows } = await import( |
| | | '../src/views/basic-info/wh-mat/whMatPage.helpers.js' |
| | | ) |
| | | |
| | | const tree = normalizeMatnrGroupTreeRows([ |
| | | { |
| | | id: 1, |
| | | parentId: 0, |
| | | name: '半成品', |
| | | code: 'RM', |
| | | status: 1, |
| | | children: [ |
| | | { |
| | | id: 2, |
| | | parentId: 1, |
| | | name: '半成品A', |
| | | code: 'RM-A', |
| | | status: 0 |
| | | } |
| | | ] |
| | | } |
| | | ]) |
| | | |
| | | assert.equal(tree[0].displayLabel, '半成品 · RM') |
| | | assert.equal(tree[0].children[0].statusText, '冻结') |
| | | }) |
| | | |
| | | test('normalizes matnr detail fields for detail drawer display', async () => { |
| | | const { normalizeMatnrDetail } = await import('../src/views/basic-info/wh-mat/whMatPage.helpers.js') |
| | | |
| | | const detail = normalizeMatnrDetail({ |
| | | id: 8, |
| | | code: 'RM001', |
| | | name: '半成品', |
| | | groupId$: '原材料', |
| | | status: 1, |
| | | stockLeval$: ' A', |
| | | flagLabelMange$: ' 是', |
| | | extendFields: { |
| | | batch: 'B001' |
| | | } |
| | | }) |
| | | |
| | | assert.equal(detail.code, 'RM001') |
| | | assert.equal(detail.groupName, '原材料') |
| | | assert.equal(detail.statusText, '正常') |
| | | assert.equal(detail.extendFields.batch, 'B001') |
| | | }) |
| | | |