feat(bom): BOM列表按物料类别分组展示,支持折叠展开与数量统计
This commit is contained in:
@ -3,6 +3,7 @@ from app.models.bom import BomTable
|
||||
from app.models.base import MaterialBase
|
||||
from app.models.inbound.buy import StockBuy
|
||||
from sqlalchemy import func, distinct, or_, case
|
||||
from collections import defaultdict
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@ -64,6 +65,7 @@ class BomService:
|
||||
BomTable.parent_id,
|
||||
MaterialBase.name.label('parent_name'),
|
||||
MaterialBase.spec_model.label('parent_spec'),
|
||||
MaterialBase.category.label('parent_category'),
|
||||
BomTable.is_enabled,
|
||||
func.count(BomTable.child_id).label('child_count')
|
||||
).join(
|
||||
@ -72,7 +74,7 @@ class BomService:
|
||||
BomTable.bom_no == bom_no,
|
||||
BomTable.version == version
|
||||
).group_by(
|
||||
BomTable.parent_id, MaterialBase.name, MaterialBase.spec_model, BomTable.is_enabled
|
||||
BomTable.parent_id, MaterialBase.name, MaterialBase.spec_model, MaterialBase.category, BomTable.is_enabled
|
||||
).first()
|
||||
|
||||
if summary:
|
||||
@ -82,12 +84,39 @@ class BomService:
|
||||
'parent_id': summary.parent_id,
|
||||
'parent_name': summary.parent_name,
|
||||
'parent_spec': summary.parent_spec or '',
|
||||
'parent_category': summary.parent_category or '',
|
||||
'is_enabled': summary.is_enabled,
|
||||
'child_count': summary.child_count
|
||||
})
|
||||
|
||||
results.sort(key=lambda x: (x['bom_no'], x['version']), reverse=True)
|
||||
return results
|
||||
|
||||
# 如果有关键词,过滤结果(keyword 匹配逻辑保持不变)
|
||||
if keyword:
|
||||
kw = f'%{keyword}%'
|
||||
results = [
|
||||
r for r in results
|
||||
if kw in (r.get('parent_name') or '')
|
||||
or kw in (r.get('parent_spec') or '')
|
||||
or kw in (r.get('bom_no') or '')
|
||||
or kw in (r.get('parent_category') or '')
|
||||
]
|
||||
|
||||
# 按 parent_category 分组
|
||||
grouped = defaultdict(list)
|
||||
for item in results:
|
||||
cat = item.get('parent_category') or '未分类'
|
||||
grouped[cat].append(item)
|
||||
|
||||
grouped_list = []
|
||||
for cat, items in sorted(grouped.items(), key=lambda x: x[0]):
|
||||
grouped_list.append({
|
||||
'category': cat,
|
||||
'count': len(items),
|
||||
'items': items
|
||||
})
|
||||
|
||||
return grouped_list
|
||||
|
||||
@staticmethod
|
||||
def get_bom_detail(bom_no, version=None):
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索 编号/名称/规格/子件..."
|
||||
style="width: 300px; margin-right: 15px;"
|
||||
style="width: 300px; margin-right: 10px;"
|
||||
clearable
|
||||
@clear="fetchBomList"
|
||||
@keyup.enter="fetchBomList"
|
||||
@ -17,36 +17,51 @@
|
||||
<el-button :icon="Search" @click="fetchBomList" />
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button @click="activeCategories = bomGroups.map((g: any) => g.category)" size="small" style="margin-right: 6px;">全部展开</el-button>
|
||||
<el-button @click="activeCategories = []" size="small" style="margin-right: 10px;">全部折叠</el-button>
|
||||
<el-button v-if="userStore.hasPermission('bom_manage:operation')" type="primary" :icon="Plus" @click="handleCreate">新建 BOM</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="bomList" border style="width: 100%">
|
||||
<el-table-column v-if="hasColumnPermission('bom_no')" prop="bom_no" label="BOM编号" min-width="180" sortable />
|
||||
<el-table-column v-if="hasColumnPermission('parent_name')" prop="parent_name" label="父件名称" min-width="150" />
|
||||
<el-table-column v-if="hasColumnPermission('parent_spec')" prop="parent_spec" label="父件规格" min-width="150" />
|
||||
<el-table-column v-if="hasColumnPermission('version')" prop="version" label="版本" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag>{{ row.version }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="hasColumnPermission('status')" label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.is_enabled ? 'success' : 'danger'">
|
||||
{{ row.is_enabled ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="hasColumnPermission('child_count')" prop="child_count" label="子件数" width="80" align="center" />
|
||||
<el-table-column v-if="userStore.hasPermission('bom_manage:operation')" label="操作" width="250" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="success" link @click="handleSaveAs(row)">另存为</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div v-loading="loading">
|
||||
<el-collapse v-model="activeCategories" class="bom-category-collapse">
|
||||
<el-collapse-item
|
||||
v-for="group in bomGroups"
|
||||
:key="group.category"
|
||||
:title="group.category + ' (' + group.count + ')'"
|
||||
:name="group.category"
|
||||
>
|
||||
<el-table :data="group.items" border style="width: 100%">
|
||||
<el-table-column v-if="hasColumnPermission('bom_no')" prop="bom_no" label="BOM编号" min-width="180" sortable>
|
||||
<template #default="{ row }">
|
||||
<span style="cursor: pointer; color: #409EFF;" @click="handleEdit(row)">{{ row.bom_no }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="hasColumnPermission('parent_name')" prop="parent_name" label="父件名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column v-if="hasColumnPermission('parent_spec')" prop="parent_spec" label="父件规格" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column v-if="hasColumnPermission('version')" label="版本" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag>{{ row.version }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="hasColumnPermission('status')" label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.is_enabled ? 'success' : 'danger'">{{ row.is_enabled ? '启用' : '禁用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="hasColumnPermission('child_count')" prop="child_count" label="子件数" width="80" align="center" />
|
||||
<el-table-column v-if="userStore.hasPermission('bom_manage:operation')" label="操作" width="250" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="success" link @click="handleSaveAs(row)">另存为</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="850px" destroy-on-close :close-on-click-modal="false">
|
||||
@ -268,7 +283,8 @@ let originalVersion = ''
|
||||
let currentBomNo = ''
|
||||
let originalChildren: ChildRow[] = []
|
||||
|
||||
const bomList = ref<BomItem[]>([])
|
||||
const bomGroups = ref([]) // 分组结构: [{category, count, items[]}]
|
||||
const activeCategories = ref([]) // 默认全部展开
|
||||
const searchKeyword = ref('')
|
||||
const childSearchKeyword = ref('')
|
||||
|
||||
@ -560,8 +576,9 @@ const pureBomNo = computed(() => form.bom_no)
|
||||
|
||||
const versionOptions = computed(() => {
|
||||
const ver = originalVersion || 'V1.0'
|
||||
const allItems = bomGroups.value.flatMap((g: any) => g.items)
|
||||
const occupiedVersions = new Set(
|
||||
bomList.value.filter(item => item.bom_no === currentBomNo).map(item => item.version)
|
||||
allItems.filter((item: any) => item.bom_no === currentBomNo).map((item: any) => item.version)
|
||||
)
|
||||
const getNextMinor = (baseMajor: number, baseMinor: number): string => {
|
||||
let minor = baseMinor + 1
|
||||
@ -597,7 +614,10 @@ const fetchBomList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getBomList({ keyword: searchKeyword.value })
|
||||
if (res.code === 200) bomList.value = res.data
|
||||
if (res.code === 200) {
|
||||
bomGroups.value = res.data
|
||||
activeCategories.value = res.data.map((g: any) => g.category)
|
||||
}
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user