Files
my-mom-system/erp-frontend-vue/src/views/MasterData/Bom/list.vue
panchengyong 34cbf08144 feat: EBOM选择物料弹窗增加左侧分类树(issues-0311-3)
- form.vue: 选择物料弹窗从el-select远程搜索改为左右布局
  左侧el-tree加载物料分类树(getItemTypeTreeselect)
  右侧el-table展示物料列表,支持编码/名称搜索
  点击树节点按分类过滤物料,参考Item管理页实现
- form.vue: 表格和输入框去掉size=small,恢复默认高度
- list.vue: 列表列宽从固定width改为min-width,增加show-overflow-tooltip

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:42:06 +08:00

439 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="bom-page">
<el-card class="search-card" shadow="never">
<el-form :model="queryParams" :inline="true" @submit.prevent="handleQuery">
<el-form-item label="">
<el-input v-model="queryParams.bomCode" placeholder="单据编码" clearable style="width: 140px" />
</el-form-item>
<el-form-item label="">
<el-input v-model="queryParams.itemCode" placeholder="物料编码" clearable style="width: 140px" />
</el-form-item>
<el-form-item label="">
<el-input v-model="queryParams.itemName" placeholder="物料名称" clearable style="width: 140px" />
</el-form-item>
<template v-if="viewMode === 'document'">
<el-form-item label="">
<el-tree-select
v-model="queryParams.itemTypeId"
:data="itemTypeOptions"
:props="{ value: 'id', label: 'label', children: 'children' }"
placeholder="物料分类"
clearable
check-strictly
style="width: 140px"
/>
</el-form-item>
</template>
<template v-else>
<el-form-item label="">
<el-select v-model="queryParams.status" placeholder="单据状态" clearable style="width: 140px">
<el-option label="审核" value="APPROVED" />
<el-option label="开立" value="DRAFT" />
<el-option label="退回" value="REJECTED" />
<el-option label="作废" value="OBSOLETE" />
</el-select>
</el-form-item>
<el-form-item label="">
<el-select v-model="queryParams.businessStatus" placeholder="业务状态" clearable style="width: 140px">
<el-option label="正常" value="NORMAL" />
<el-option label="停用" value="DISABLED" />
</el-select>
</el-form-item>
</template>
<el-form-item label="">
<el-date-picker
v-model="queryParams.dateRange"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 240px"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="table-card" shadow="never">
<div class="toolbar">
<div class="toolbar-left">
<el-button :type="viewMode === 'document' ? 'primary' : ''" @click="switchView('document')">单据</el-button>
<el-button :type="viewMode === 'detail' ? 'primary' : ''" @click="switchView('detail')">明细</el-button>
</div>
<div class="toolbar-right" v-if="viewMode === 'document'">
<el-button type="primary" :icon="Plus" @click="goNew">新增</el-button>
<el-button type="danger" plain @click="resetQuery">清空</el-button>
<el-button plain @click="handleImport">导入</el-button>
<el-button type="warning" plain :icon="Download" @click="handleExport">导出</el-button>
</div>
<div class="toolbar-right" v-else>
<el-button type="primary" :icon="Plus" @click="goNew">新增</el-button>
<el-button type="danger" plain :disabled="ids.length === 0" @click="handleDeleteSelected">删除</el-button>
<el-button type="success" plain :disabled="ids.length === 0" @click="handleApproveSelected">审核</el-button>
<el-button plain disabled>反审核</el-button>
</div>
</div>
<el-table
v-if="viewMode === 'document'"
v-loading="loading"
:data="bomList"
border
stripe
style="width: 100%"
@row-dblclick="goView"
>
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column prop="bomCode" label="单据编码" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
<el-link type="primary" @click="goView(row)">{{ row.bomCode }}</el-link>
</template>
</el-table-column>
<el-table-column prop="itemCode" label="物料编码" width="130" />
<el-table-column prop="itemName" label="物料名称" min-width="220" show-overflow-tooltip />
<el-table-column prop="baseQty" label="母件基数" width="90" align="center">
<template #default="{ row }">{{ row.baseQty ?? 1 }}</template>
</el-table-column>
<el-table-column prop="version" label="版本号" width="90" align="center">
<template #default="{ row }">{{ row.version || '1.00' }}</template>
</el-table-column>
<el-table-column prop="versionDesc" label="版本说明" min-width="120" show-overflow-tooltip />
<el-table-column prop="drawingNo" label="图纸号" min-width="150" show-overflow-tooltip />
<el-table-column prop="status" label="单据状态" width="100" align="center" show-overflow-tooltip>
<template #default="{ row }">
<span :class="getStatusClass(row.status)">{{ getStatusLabel(row.status) }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="单据日期" width="110" align="center">
<template #default="{ row }">{{ formatDate(row.createTime) }}</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="handleMultiLevel(row)">多阶</el-button>
<el-button type="success" link size="small" @click="handleValidate(row)">效验</el-button>
<el-button type="warning" link size="small" @click="handleExportSingle(row)">导出</el-button>
</template>
</el-table-column>
</el-table>
<el-table
v-else
v-loading="loading"
:data="bomList"
border
stripe
style="width: 100%"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column prop="bomCode" label="单据编码" min-width="200" align="center" show-overflow-tooltip />
<el-table-column prop="createTime" label="单据日期" width="110" align="center">
<template #default="{ row }">{{ formatDate(row.createTime) }}</template>
</el-table-column>
<el-table-column prop="status" label="单据状态" width="100" align="center" show-overflow-tooltip>
<template #default="{ row }">
<span :class="getStatusClass(row.status)">{{ getStatusLabel(row.status) }}</span>
</template>
</el-table-column>
<el-table-column prop="businessType" label="业务类型" min-width="120" align="center" />
<el-table-column prop="businessDept" label="业务部门" min-width="120" align="center" />
<el-table-column prop="businessUser" label="业务人员" min-width="120" align="center" />
<el-table-column prop="businessStatus" label="业务状态" min-width="120" align="center" />
<el-table-column prop="approveTime" label="审核日期" width="110" align="center" />
<el-table-column label="操作" width="140" align="center" fixed="right">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="goView(row)">查看</el-button>
<el-button type="success" link size="small" @click="goEdit(row)" :disabled="row.status === 'APPROVED'">编辑</el-button>
<el-button type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrapper">
<span class="total-text"> {{ total }} </span>
<el-pagination
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
layout="sizes, prev, pager, next, jumper"
@size-change="getList"
@current-change="getList"
/>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Download, Plus, Search } from '@element-plus/icons-vue'
import { listBomHeader, delBomHeader } from '@/api/masterdata/bom'
import { getItemTypeTreeselect } from '@/api/masterdata/itemtype'
const router = useRouter()
const loading = ref(false)
const bomList = ref<any[]>([])
const total = ref(0)
const itemTypeOptions = ref<any[]>([])
const ids = ref<number[]>([])
const viewMode = ref<'document' | 'detail'>('document')
const queryParams = reactive({
bomCode: '',
itemCode: '',
itemName: '',
itemTypeId: undefined as number | undefined,
status: '' as string,
businessStatus: '' as string,
dateRange: [] as string[],
pageNum: 1,
pageSize: 20
})
function switchView(mode: 'document' | 'detail') {
viewMode.value = mode
queryParams.pageNum = 1
if (mode === 'document') {
queryParams.status = ''
queryParams.businessStatus = ''
} else {
queryParams.itemTypeId = undefined
}
getList()
}
async function loadItemTypes() {
try {
const res = await getItemTypeTreeselect()
itemTypeOptions.value = res.data
} catch (e) {
console.error(e)
}
}
async function getList() {
loading.value = true
try {
const params: any = {
bomCode: queryParams.bomCode,
itemCode: queryParams.itemCode,
itemName: queryParams.itemName,
itemOrProduct: 'PRODUCT',
pageNum: queryParams.pageNum,
pageSize: queryParams.pageSize
}
if (viewMode.value === 'document') {
if (queryParams.itemTypeId) params.itemTypeId = queryParams.itemTypeId
} else {
if (queryParams.status) params.status = queryParams.status
// businessStatus 仅用于页面对齐展示,后端不一定支持,因此不传
}
if (queryParams.dateRange && queryParams.dateRange.length === 2) {
params.startDate = queryParams.dateRange[0]
params.endDate = queryParams.dateRange[1]
}
const res = await listBomHeader(params)
bomList.value = res.rows || []
total.value = res.total || 0
} finally {
loading.value = false
}
}
function handleQuery() {
queryParams.pageNum = 1
getList()
}
function resetQuery() {
queryParams.bomCode = ''
queryParams.itemCode = ''
queryParams.itemName = ''
queryParams.dateRange = []
queryParams.status = ''
queryParams.businessStatus = ''
queryParams.itemTypeId = undefined
handleQuery()
}
function handleSelectionChange(selection: any[]) {
ids.value = selection.map(it => it.bomId).filter(Boolean)
}
function goNew() {
router.push({ name: 'BomNew' })
}
function goView(row: any) {
router.push({ name: 'BomView', params: { id: row.bomId } })
}
function goEdit(row: any) {
router.push({ name: 'BomEdit', params: { id: row.bomId } })
}
async function handleDelete(row: any) {
try {
await ElMessageBox.confirm('确认删除该BOM', '警告', { type: 'warning' })
await delBomHeader(row.bomId)
ElMessage.success('删除成功')
getList()
} catch (e: any) {
if (e !== 'cancel') console.error(e)
}
}
async function handleDeleteSelected() {
try {
await ElMessageBox.confirm('确认删除选中的BOM', '警告', { type: 'warning' })
await delBomHeader(ids.value)
ElMessage.success('删除成功')
ids.value = []
getList()
} catch (e: any) {
if (e !== 'cancel') console.error(e)
}
}
function handleApproveSelected() {
ElMessage.info('审核功能需后端支持批量审核接口(开发中)')
}
function handleImport() {
ElMessage.info('导入功能开发中')
}
function handleExport() {
ElMessage.info('导出功能开发中')
}
function handleMultiLevel(row: any) {
ElMessage.info(`查看多阶BOM: ${row.bomCode}`)
}
function handleValidate(row: any) {
ElMessage.info(`效验BOM: ${row.bomCode}`)
}
function handleExportSingle(row: any) {
ElMessage.info(`导出BOM: ${row.bomCode}`)
}
function formatDate(dateStr?: string) {
if (!dateStr) return ''
return String(dateStr).split(' ')[0]
}
function getStatusClass(status?: string) {
switch (status) {
case 'APPROVED':
return 'status-approved'
case 'DRAFT':
return 'status-draft'
case 'REJECTED':
return 'status-rejected'
case 'OBSOLETE':
return 'status-obsolete'
default:
return 'status-draft'
}
}
function getStatusLabel(status?: string) {
switch (status) {
case 'APPROVED':
return '审核'
case 'DRAFT':
return '开立'
case 'REJECTED':
return '退回'
case 'OBSOLETE':
return '作废'
default:
return '开立'
}
}
onMounted(() => {
loadItemTypes()
getList()
})
</script>
<style scoped>
.bom-page {
padding: 12px;
}
.search-card {
margin-bottom: 12px;
}
.search-card :deep(.el-card__body) {
padding: 12px 12px 0;
}
.table-card :deep(.el-card__body) {
padding: 12px;
}
.toolbar {
margin-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
}
.toolbar-left,
.toolbar-right {
display: flex;
gap: 8px;
}
.pagination-wrapper {
margin-top: 12px;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 16px;
}
.total-text {
color: #606266;
font-size: 13px;
}
.status-approved {
color: #67c23a;
font-weight: 500;
}
.status-draft {
color: #faad14;
font-weight: 500;
}
.status-rejected {
color: #909399;
font-weight: 500;
}
.status-obsolete {
color: #f56c6c;
font-weight: 500;
}
</style>