- 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>
439 lines
14 KiB
Vue
439 lines
14 KiB
Vue
<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>
|
||
|