<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)]">物料分组</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="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="暂无物料分组" />
|
<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>
|
|
<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="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>
|
</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>
|
|
<style scoped>
|
.wh-mat-page {
|
display: flex;
|
flex-direction: row;
|
align-items: flex-start;
|
gap: 16px;
|
}
|
|
.wh-mat-page__sidebar {
|
width: 320px;
|
flex: 0 0 320px;
|
}
|
|
.wh-mat-page__sidebar-card {
|
position: sticky;
|
top: 16px;
|
}
|
|
.wh-mat-page__tree-scroll {
|
height: calc(100vh - 320px);
|
min-height: 420px;
|
}
|
|
.wh-mat-page__content {
|
min-width: 0;
|
flex: 1 1 auto;
|
}
|
|
.wh-mat-page__content > * + * {
|
margin-top: 16px;
|
}
|
|
@media (max-width: 1024px) {
|
.wh-mat-page {
|
flex-direction: column;
|
}
|
|
.wh-mat-page__sidebar {
|
width: 100%;
|
flex-basis: auto;
|
}
|
|
.wh-mat-page__sidebar-card {
|
position: static;
|
}
|
|
.wh-mat-page__tree-scroll {
|
height: 320px;
|
min-height: 320px;
|
}
|
}
|
</style>
|