feat(bom): BOM 管理页 UI——子件版本下拉、只读启停、状态按钮组、公司筛选、手风琴、详情 loading
- 自制件子件行内版本下拉(必选,默认最新启用,外购件不显示);保存/草稿带引用版本 - 查看/只读态“是否启用”开关解耦,切换走独立启停接口 - 状态筛选改 el-radio-group 按钮组(默认启用) - 顶部接入 CompanySelector,父件/子件物料搜索联动公司并标注公司 - 分类手风琴(全部展开临时关手风琴)与详情弹窗 v-loading;列表默认只显示启用
This commit is contained in:
@ -17,6 +17,12 @@
|
|||||||
<el-button :icon="Search" @click="handleSearch" />
|
<el-button :icon="Search" @click="handleSearch" />
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
<company-selector v-model="bomCompany" @change="handleSearch" style="margin-right: 10px;" />
|
||||||
|
<el-radio-group v-model="bomStatusFilter" @change="handleSearch" style="margin-right: 15px;">
|
||||||
|
<el-radio-button :label="''">全部</el-radio-button>
|
||||||
|
<el-radio-button label="enabled">启用</el-radio-button>
|
||||||
|
<el-radio-button label="disabled">停用</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
<el-button @click="expandAllGroups" size="small" style="margin-right: 6px;">全部展开</el-button>
|
<el-button @click="expandAllGroups" size="small" style="margin-right: 6px;">全部展开</el-button>
|
||||||
<el-button @click="collapseAllGroups" size="small" style="margin-right: 10px;">全部折叠</el-button>
|
<el-button @click="collapseAllGroups" size="small" style="margin-right: 10px;">全部折叠</el-button>
|
||||||
<el-button v-if="userStore.hasPermission('bom_manage:operation')" type="success" plain :icon="Upload" @click="showImportDialog = true" style="margin-right:10px">导入BOM</el-button>
|
<el-button v-if="userStore.hasPermission('bom_manage:operation')" type="success" plain :icon="Upload" @click="showImportDialog = true" style="margin-right:10px">导入BOM</el-button>
|
||||||
@ -27,7 +33,7 @@
|
|||||||
|
|
||||||
<el-skeleton :rows="8" animated v-if="loading && groupSummary.length === 0" />
|
<el-skeleton :rows="8" animated v-if="loading && groupSummary.length === 0" />
|
||||||
<el-empty v-else-if="!loading && groupSummary.length === 0" description="暂无 BOM 数据" />
|
<el-empty v-else-if="!loading && groupSummary.length === 0" description="暂无 BOM 数据" />
|
||||||
<el-collapse v-else v-model="activeCategories" class="bom-category-collapse" @change="handleCollapseChange">
|
<el-collapse v-else v-model="activeCategories" :accordion="isAccordion" class="bom-category-collapse" @change="handleCollapseChange">
|
||||||
<el-collapse-item
|
<el-collapse-item
|
||||||
v-for="group in groupedData"
|
v-for="group in groupedData"
|
||||||
:key="group.category"
|
:key="group.category"
|
||||||
@ -68,7 +74,7 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="850px" destroy-on-close :close-on-click-modal="false" :close-on-press-escape="false">
|
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="850px" destroy-on-close :close-on-click-modal="false" :close-on-press-escape="false">
|
||||||
<el-form :model="form" label-width="120px" ref="formRef" :rules="rules">
|
<el-form :model="form" label-width="120px" ref="formRef" :rules="rules" v-loading="detailLoading">
|
||||||
|
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="16">
|
<el-col :span="16">
|
||||||
@ -93,6 +99,7 @@
|
|||||||
<div class="option-row">
|
<div class="option-row">
|
||||||
<span class="option-name">{{ item.name }}</span>
|
<span class="option-name">{{ item.name }}</span>
|
||||||
<span class="option-spec">{{ item.spec }}</span>
|
<span class="option-spec">{{ item.spec }}</span>
|
||||||
|
<span v-if="item.company_name" style="font-size:12px;color:#c45656;background:#fef0f0;border-radius:2px;padding:0 4px;margin-left:auto;flex-shrink:0;">{{ item.company_name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-autocomplete>
|
</el-autocomplete>
|
||||||
@ -112,7 +119,15 @@
|
|||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-form-item label="是否启用" prop="is_enabled" v-if="hasFormFieldPermission('is_enabled')">
|
<el-form-item label="是否启用" prop="is_enabled" v-if="hasFormFieldPermission('is_enabled')">
|
||||||
<el-switch v-model="form.is_enabled" active-text="启用" inactive-text="禁用" :disabled="isReadOnlyMode || !userStore.hasPermission('bom_manage:operation')" />
|
<!-- ★ disabled 仅与操作权限绑定,不再随只读视图全局禁用:
|
||||||
|
查看/只读态下切换会走 updateBomStatus 单独更新状态(见 onEnabledStatusChange) -->
|
||||||
|
<el-switch
|
||||||
|
v-model="form.is_enabled"
|
||||||
|
active-text="启用"
|
||||||
|
inactive-text="禁用"
|
||||||
|
:disabled="!userStore.hasPermission('bom_manage:operation')"
|
||||||
|
@change="onEnabledStatusChange"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="16"></el-col>
|
<el-col :span="16"></el-col>
|
||||||
@ -184,6 +199,7 @@
|
|||||||
<div class="option-row">
|
<div class="option-row">
|
||||||
<span class="option-name">{{ item.name }}</span>
|
<span class="option-name">{{ item.name }}</span>
|
||||||
<span class="option-spec">{{ item.spec }}</span>
|
<span class="option-spec">{{ item.spec }}</span>
|
||||||
|
<span v-if="item.company_name" style="font-size:12px;color:#c45656;background:#fef0f0;border-radius:2px;padding:0 4px;margin-left:auto;flex-shrink:0;">{{ item.company_name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-autocomplete>
|
</el-autocomplete>
|
||||||
@ -196,10 +212,29 @@
|
|||||||
style="font-size: 16px; padding: 4px;"
|
style="font-size: 16px; padding: 4px;"
|
||||||
/>
|
/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<!-- ★ 自制件子件:显示其对应 BOM 版本(仅识别用) -->
|
</div>
|
||||||
<el-tag v-if="row.child_bom_version" size="small" type="warning" style="margin-left: 4px;">
|
<!-- ★ 自制件子件:编辑态出现"版本"必选下拉;只读态显示其引用的 BOM 版本 -->
|
||||||
自制{{ row.child_bom_version }}
|
<div v-if="childIsSelfMade(row)" style="margin-top: 4px;">
|
||||||
|
<el-select
|
||||||
|
v-if="!isReadOnlyMode && row.bom_version_options && row.bom_version_options.length"
|
||||||
|
v-model="row.child_bom_key"
|
||||||
|
placeholder="请选择BOM版本"
|
||||||
|
style="width: 100%;"
|
||||||
|
size="small"
|
||||||
|
filterable
|
||||||
|
@change="() => onChildBomChange(row)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="opt in row.bom_version_options"
|
||||||
|
:key="bomKeyOf(opt.bom_no, opt.version)"
|
||||||
|
:label="opt.bom_no + ' ' + opt.version"
|
||||||
|
:value="bomKeyOf(opt.bom_no, opt.version)"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-tag v-else-if="row.child_bom_version" size="small" type="warning">
|
||||||
|
自制 {{ row.child_bom_no ? row.child_bom_no + ' ' : '' }}{{ row.child_bom_version }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
|
<div v-else-if="!isReadOnlyMode" style="font-size: 12px; color: #E6A23C;">请选择该自制件的 BOM 版本</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -266,8 +301,9 @@ import { useRoute } from 'vue-router'
|
|||||||
import { ElMessage, ElMessageBox, FormInstance, FormRules } from 'element-plus'
|
import { ElMessage, ElMessageBox, FormInstance, FormRules } from 'element-plus'
|
||||||
import { Plus, Search, EditPen, Upload } from '@element-plus/icons-vue'
|
import { Plus, Search, EditPen, Upload } from '@element-plus/icons-vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { getBomList, getBomSummary, getBomDetail, saveBom, deleteBom, getDraftDetail, saveDraft, publishDraft } from '@/api/bom'
|
import { getBomList, getBomSummary, getBomDetail, saveBom, deleteBom, getDraftDetail, saveDraft, publishDraft, getChildBomVersions, updateBomStatus } from '@/api/bom'
|
||||||
import ImportDialog from '@/components/ImportDialog.vue'
|
import ImportDialog from '@/components/ImportDialog.vue'
|
||||||
|
import CompanySelector from '@/components/CompanySelector.vue'
|
||||||
import { searchMaterialBase } from '@/api/inbound/buy'
|
import { searchMaterialBase } from '@/api/inbound/buy'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
|
||||||
@ -294,7 +330,64 @@ interface ChildRow {
|
|||||||
material_spec: string
|
material_spec: string
|
||||||
dosage: number
|
dosage: number
|
||||||
remark: string
|
remark: string
|
||||||
child_bom_version?: string // 自制件子件对应的 BOM 版本(仅展示识别)
|
child_bom_no?: string // 子件引用的自制 BOM 编号(必选:该物料作父件的配方)
|
||||||
|
child_bom_version?: string // 子件引用的自制 BOM 版本(必选,见 bom_version_options)
|
||||||
|
child_bom_key?: string // 下拉选中项编码 `${bom_no}|${version}`
|
||||||
|
bom_version_options?: { bom_no: string; version: string }[] // 该物料作父件的启用配方候选
|
||||||
|
}
|
||||||
|
|
||||||
|
// ★ 自制件子件"版本"候选与选中项工具
|
||||||
|
const BOM_KEY_SEP = '|'
|
||||||
|
const bomKeyOf = (no?: string, ver?: string) => (no || '') + BOM_KEY_SEP + (ver || '')
|
||||||
|
const bomKeyTo = (key?: string) => {
|
||||||
|
const s = String(key || '')
|
||||||
|
const i = s.indexOf(BOM_KEY_SEP)
|
||||||
|
if (i < 0) return ['', '']
|
||||||
|
return [s.slice(0, i), s.slice(i + 1)]
|
||||||
|
}
|
||||||
|
const childIsSelfMade = (row: any) =>
|
||||||
|
!!(row && ((row.bom_version_options && row.bom_version_options.length) || row.child_bom_no || row.child_bom_version))
|
||||||
|
|
||||||
|
// 拉取某子件物料作父件的启用配方列表,并维护选中项。
|
||||||
|
// - 有候选: 若当前已有合法选中(编辑存量行)则保留,否则默认选"最新启用配方"(列表第一项)
|
||||||
|
// - 无候选(外购件/无现行BOM): 清空版本相关字段
|
||||||
|
const loadChildBomVersions = async (row: any) => {
|
||||||
|
if (!row.child_id) { row.bom_version_options = []; return }
|
||||||
|
let list: { bom_no: string; version: string }[] = []
|
||||||
|
try {
|
||||||
|
const res: any = await getChildBomVersions(row.child_id)
|
||||||
|
if (res?.code === 200 && Array.isArray(res.data)) list = res.data
|
||||||
|
} catch (e) { list = [] }
|
||||||
|
row.bom_version_options = list
|
||||||
|
if (!list.length) {
|
||||||
|
row.child_bom_no = ''
|
||||||
|
row.child_bom_version = ''
|
||||||
|
row.child_bom_key = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const existKey = bomKeyOf(row.child_bom_no, row.child_bom_version)
|
||||||
|
if (row.child_bom_no && row.child_bom_version && list.some(o => bomKeyOf(o.bom_no, o.version) === existKey)) {
|
||||||
|
row.child_bom_key = existKey
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const first = list[0]
|
||||||
|
row.child_bom_no = first.bom_no
|
||||||
|
row.child_bom_version = first.version
|
||||||
|
row.child_bom_key = bomKeyOf(first.bom_no, first.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChildBomChange = (row: any) => {
|
||||||
|
const [no, ver] = bomKeyTo(row.child_bom_key)
|
||||||
|
row.child_bom_no = no
|
||||||
|
row.child_bom_version = ver
|
||||||
|
}
|
||||||
|
|
||||||
|
// 给现有 children(从详情/草稿/另存为载入的存量自制件子件)补齐版本候选,
|
||||||
|
// 使编辑态能渲染下拉;保留其已存储的引用版本。
|
||||||
|
const hydrateChildBomVersions = async () => {
|
||||||
|
for (const c of form.children) {
|
||||||
|
if (c.child_id) await loadChildBomVersions(c)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalDraftHash = ref('')
|
const originalDraftHash = ref('')
|
||||||
@ -303,7 +396,9 @@ const getDraftHash = () => {
|
|||||||
const children = form.children.map(c => ({
|
const children = form.children.map(c => ({
|
||||||
child_id: c.child_id,
|
child_id: c.child_id,
|
||||||
dosage: Number(c.dosage) || 0,
|
dosage: Number(c.dosage) || 0,
|
||||||
remark: c.remark || ''
|
remark: c.remark || '',
|
||||||
|
child_bom_no: c.child_bom_no || '',
|
||||||
|
child_bom_version: c.child_bom_version || ''
|
||||||
}))
|
}))
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
bom_no: form.bom_no,
|
bom_no: form.bom_no,
|
||||||
@ -331,6 +426,11 @@ let pendingDraftVersion = ''
|
|||||||
|
|
||||||
const searchKeyword = ref('')
|
const searchKeyword = ref('')
|
||||||
const childSearchKeyword = ref('')
|
const childSearchKeyword = ref('')
|
||||||
|
const bomStatusFilter = ref('enabled') // 默认只看"启用",可手动切全部/停用;enabled=启用 | disabled=停用 | ''=全部
|
||||||
|
const bomCompany = ref('') // 跨域用户按公司(父件物料公司)过滤;普通用户由后端强制隔离,此控件不显示
|
||||||
|
// ★ 手风琴开关:默认单开;"全部展开"时临时关闭手风琴以允许多开,"全部折叠"后恢复
|
||||||
|
const isAccordion = ref(true)
|
||||||
|
const detailLoading = ref(false) // 弹窗查看详情 loading(防"假死"反馈)
|
||||||
|
|
||||||
// ★ 懒加载分组架构
|
// ★ 懒加载分组架构
|
||||||
interface GroupSummary { category: string; count: number }
|
interface GroupSummary { category: string; count: number }
|
||||||
@ -378,7 +478,7 @@ const fetchParentSuggestions = (query: string, cb: (results: any[]) => void) =>
|
|||||||
const rawQuery = String(query || '')
|
const rawQuery = String(query || '')
|
||||||
const safeQuery = rawQuery.replace(/[\x00-\x1F\x7F-\x9F\u200B-\u200D\uFEFF]/g, '').trim()
|
const safeQuery = rawQuery.replace(/[\x00-\x1F\x7F-\x9F\u200B-\u200D\uFEFF]/g, '').trim()
|
||||||
searchLoading.value = true
|
searchLoading.value = true
|
||||||
searchMaterialBase(safeQuery).then((res: any) => {
|
searchMaterialBase(safeQuery, 1, bomCompany.value || '').then((res: any) => {
|
||||||
const items = res.data?.items || res.data || []
|
const items = res.data?.items || res.data || []
|
||||||
const formatted = items.map((i: any) => ({ ...i, name: i.name || i.material_name }))
|
const formatted = items.map((i: any) => ({ ...i, name: i.name || i.material_name }))
|
||||||
cb(formatted)
|
cb(formatted)
|
||||||
@ -404,7 +504,7 @@ const fetchChildSuggestions = (row: any, query: string, cb: (results: any[]) =>
|
|||||||
const rawQuery = String(query || '')
|
const rawQuery = String(query || '')
|
||||||
const safeQuery = rawQuery.replace(/[\x00-\x1F\x7F-\x9F\u200B-\u200D\uFEFF]/g, '').trim()
|
const safeQuery = rawQuery.replace(/[\x00-\x1F\x7F-\x9F\u200B-\u200D\uFEFF]/g, '').trim()
|
||||||
searchLoading.value = true
|
searchLoading.value = true
|
||||||
searchMaterialBase(safeQuery).then((res: any) => {
|
searchMaterialBase(safeQuery, 1, bomCompany.value || '').then((res: any) => {
|
||||||
const items = res.data?.items || res.data || []
|
const items = res.data?.items || res.data || []
|
||||||
const formatted = items.map((i: any) => ({ ...i, name: i.name || i.material_name }))
|
const formatted = items.map((i: any) => ({ ...i, name: i.name || i.material_name }))
|
||||||
cb(formatted)
|
cb(formatted)
|
||||||
@ -416,6 +516,10 @@ const onChildClear = (row: any) => {
|
|||||||
row.material_name = ''
|
row.material_name = ''
|
||||||
row.material_spec = ''
|
row.material_spec = ''
|
||||||
row.dosage = 0
|
row.dosage = 0
|
||||||
|
row.child_bom_no = ''
|
||||||
|
row.child_bom_version = ''
|
||||||
|
row.child_bom_key = ''
|
||||||
|
row.bom_version_options = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChildSelected = (row: any, item: any) => {
|
const onChildSelected = (row: any, item: any) => {
|
||||||
@ -425,12 +529,19 @@ const onChildSelected = (row: any, item: any) => {
|
|||||||
row.child_id = null
|
row.child_id = null
|
||||||
row.material_name = ''
|
row.material_name = ''
|
||||||
row.dosage = 0
|
row.dosage = 0
|
||||||
|
row.bom_version_options = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
row.child_id = item.id
|
row.child_id = item.id
|
||||||
row.material_name = item.name
|
row.material_name = item.name
|
||||||
row.material_spec = item.spec
|
row.material_spec = item.spec
|
||||||
row.dosage = 1
|
row.dosage = 1
|
||||||
|
row.child_bom_no = ''
|
||||||
|
row.child_bom_version = ''
|
||||||
|
row.child_bom_key = ''
|
||||||
|
row.bom_version_options = []
|
||||||
|
// 选中物料后判定:若其为自制件(存在启用BOM),拉取候选版本并默认选最新,否则清空。
|
||||||
|
loadChildBomVersions(row)
|
||||||
}
|
}
|
||||||
const getChildSpec = (rowKey: number): string => {
|
const getChildSpec = (rowKey: number): string => {
|
||||||
const row = form.children.find(c => c.rowKey === rowKey)
|
const row = form.children.find(c => c.rowKey === rowKey)
|
||||||
@ -533,6 +644,7 @@ const dialogTitle = ref('新建 BOM')
|
|||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
activeCategories.value = []
|
activeCategories.value = []
|
||||||
groupCache.value = new Map()
|
groupCache.value = new Map()
|
||||||
|
isAccordion.value = true // 搜索/筛选重置回默认手风琴模式
|
||||||
fetchBomSummary()
|
fetchBomSummary()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,6 +654,8 @@ const fetchBomSummary = async () => {
|
|||||||
try {
|
try {
|
||||||
const params: any = {}
|
const params: any = {}
|
||||||
if (searchKeyword.value) params.keyword = searchKeyword.value
|
if (searchKeyword.value) params.keyword = searchKeyword.value
|
||||||
|
if (bomStatusFilter.value) params.status = bomStatusFilter.value
|
||||||
|
if (bomCompany.value) params.company_name = bomCompany.value
|
||||||
const res: any = await getBomSummary(params)
|
const res: any = await getBomSummary(params)
|
||||||
if (res?.code === 200) {
|
if (res?.code === 200) {
|
||||||
groupSummary.value = res.data ?? []
|
groupSummary.value = res.data ?? []
|
||||||
@ -554,12 +668,17 @@ const fetchBomSummary = async () => {
|
|||||||
finally { loading.value = false }
|
finally { loading.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ★ 默认全部展开(用户点击按钮时)
|
// ★ 全部展开:先临时关闭手风琴(允许多开),再一次性展开+懒加载各分类
|
||||||
const expandAllGroups = () => {
|
const expandAllGroups = () => {
|
||||||
|
isAccordion.value = false
|
||||||
activeCategories.value = groupSummary.value.map(g => g.category)
|
activeCategories.value = groupSummary.value.map(g => g.category)
|
||||||
groupSummary.value.forEach(g => loadGroupItems(g.category))
|
groupSummary.value.forEach(g => loadGroupItems(g.category))
|
||||||
}
|
}
|
||||||
const collapseAllGroups = () => { activeCategories.value = [] }
|
// ★ 全部折叠:清空面板,并恢复手风琴模式
|
||||||
|
const collapseAllGroups = () => {
|
||||||
|
activeCategories.value = []
|
||||||
|
isAccordion.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// ★ 展开分组时懒加载该分类下的 BOM
|
// ★ 展开分组时懒加载该分类下的 BOM
|
||||||
const loadGroupItems = async (category: string) => {
|
const loadGroupItems = async (category: string) => {
|
||||||
@ -574,6 +693,8 @@ const loadGroupItems = async (category: string) => {
|
|||||||
pageSize: 9999 // 单分类内全量加载(分类内数据量可控)
|
pageSize: 9999 // 单分类内全量加载(分类内数据量可控)
|
||||||
}
|
}
|
||||||
if (searchKeyword.value) params.keyword = searchKeyword.value
|
if (searchKeyword.value) params.keyword = searchKeyword.value
|
||||||
|
if (bomStatusFilter.value) params.status = bomStatusFilter.value
|
||||||
|
if (bomCompany.value) params.company_name = bomCompany.value
|
||||||
const res: any = await getBomList(params)
|
const res: any = await getBomList(params)
|
||||||
if (res?.code === 200) {
|
if (res?.code === 200) {
|
||||||
groupCache.value.set(category, res.data?.items ?? [])
|
groupCache.value.set(category, res.data?.items ?? [])
|
||||||
@ -655,7 +776,8 @@ const executeSaveDraftRequest = async (targetBomNo: string) => {
|
|||||||
child_id: child.child_id,
|
child_id: child.child_id,
|
||||||
dosage: child.dosage,
|
dosage: child.dosage,
|
||||||
remark: child.remark || '',
|
remark: child.remark || '',
|
||||||
child_bom_version: child.child_bom_version || ''
|
child_bom_no: child.child_bom_no || '',
|
||||||
|
child_bom_version: child.child_bom_version || ''
|
||||||
}))
|
}))
|
||||||
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
@ -690,6 +812,7 @@ const restoreDraftToForm = (draftData: any) => {
|
|||||||
material_spec: child.child_spec || '',
|
material_spec: child.child_spec || '',
|
||||||
dosage: child.dosage,
|
dosage: child.dosage,
|
||||||
remark: child.remark || '',
|
remark: child.remark || '',
|
||||||
|
child_bom_no: child.child_bom_no || '',
|
||||||
child_bom_version: child.child_bom_version || ''
|
child_bom_version: child.child_bom_version || ''
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -705,6 +828,8 @@ const restoreDraftToForm = (draftData: any) => {
|
|||||||
pendingDraftVersion = draftData.version || pendingDraftVersion
|
pendingDraftVersion = draftData.version || pendingDraftVersion
|
||||||
|
|
||||||
originalDraftHash.value = getDraftHash()
|
originalDraftHash.value = getDraftHash()
|
||||||
|
// 草稿中存量自制件子件也要补齐版本候选(保留其已存版本),便于继续编辑
|
||||||
|
hydrateChildBomVersions()
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkUserDraft = async (bomNo: string, version: string) => {
|
const checkUserDraft = async (bomNo: string, version: string) => {
|
||||||
@ -773,12 +898,13 @@ const checkAndInterceptGlobalDraft = async (): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleView = async (row: BomItem) => {
|
const handleView = async (row: BomItem) => {
|
||||||
await loadDetail(row.bom_no, row.version)
|
resetForm() // 清空旧内容,避免加载期间闪现上一条数据
|
||||||
dialogTitle.value = '查看 BOM'
|
dialogTitle.value = '查看 BOM'
|
||||||
isEditMode.value = false
|
isEditMode.value = false
|
||||||
isSaveAsMode.value = false
|
isSaveAsMode.value = false
|
||||||
isReadOnlyMode.value = true
|
isReadOnlyMode.value = true
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true // 先开窗,让 detailLoading 骨架屏可见
|
||||||
|
await loadDetail(row.bom_no, row.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSaveAs = async (row: any) => {
|
const handleSaveAs = async (row: any) => {
|
||||||
@ -808,6 +934,7 @@ const handleSaveAs = async (row: any) => {
|
|||||||
material_spec: child.child_spec || '',
|
material_spec: child.child_spec || '',
|
||||||
dosage: child.dosage,
|
dosage: child.dosage,
|
||||||
remark: child.remark || '',
|
remark: child.remark || '',
|
||||||
|
child_bom_no: child.child_bom_no || '',
|
||||||
child_bom_version: child.child_bom_version || ''
|
child_bom_version: child.child_bom_version || ''
|
||||||
}))
|
}))
|
||||||
if (raw.parent_id) {
|
if (raw.parent_id) {
|
||||||
@ -825,6 +952,9 @@ const handleSaveAs = async (row: any) => {
|
|||||||
originalChildren = JSON.parse(JSON.stringify(form.children))
|
originalChildren = JSON.parse(JSON.stringify(form.children))
|
||||||
originalDraftHash.value = getDraftHash()
|
originalDraftHash.value = getDraftHash()
|
||||||
|
|
||||||
|
// 另存/升版编辑:存量自制件子件补齐版本候选(保留其已存储的引用版本)
|
||||||
|
hydrateChildBomVersions()
|
||||||
|
|
||||||
dialogTitle.value = '新增 BOM (版本升级)'
|
dialogTitle.value = '新增 BOM (版本升级)'
|
||||||
isEditMode.value = false
|
isEditMode.value = false
|
||||||
isSaveAsMode.value = true
|
isSaveAsMode.value = true
|
||||||
@ -833,6 +963,7 @@ const handleSaveAs = async (row: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadDetail = async (bomNo: string, version: string) => {
|
const loadDetail = async (bomNo: string, version: string) => {
|
||||||
|
detailLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getBomDetail(bomNo, version)
|
const res = await getBomDetail(bomNo, version)
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
@ -845,6 +976,7 @@ const loadDetail = async (bomNo: string, version: string) => {
|
|||||||
material_spec: child.child_spec || '',
|
material_spec: child.child_spec || '',
|
||||||
dosage: child.dosage,
|
dosage: child.dosage,
|
||||||
remark: child.remark || '',
|
remark: child.remark || '',
|
||||||
|
child_bom_no: child.child_bom_no || '',
|
||||||
child_bom_version: child.child_bom_version || ''
|
child_bom_version: child.child_bom_version || ''
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -859,8 +991,45 @@ const loadDetail = async (bomNo: string, version: string) => {
|
|||||||
form.bom_no = bomNo.split('/')[0].trim()
|
form.bom_no = bomNo.split('/')[0].trim()
|
||||||
}
|
}
|
||||||
form.remark = data.remark || ''
|
form.remark = data.remark || ''
|
||||||
|
// 查看/只读视图的独立启停需要版本与当前状态
|
||||||
|
form.version = version || data.version || 'V1.0'
|
||||||
|
form.is_enabled = data.is_enabled !== false
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
// 网络/数据异常:仅结束 loading,不弹全局错误,保持弹窗可操作
|
||||||
|
} finally {
|
||||||
|
detailLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ★ 启用/停用开关:查看/只读态下独立更新状态(不触发整表单保存);
|
||||||
|
// 新建/编辑/另存模式则仅记录到 form,随 submitForm 一并保存。
|
||||||
|
const onEnabledStatusChange = async () => {
|
||||||
|
if (!isReadOnlyMode.value) return
|
||||||
|
|
||||||
|
if (!pureBomNo.value || !form.version) {
|
||||||
|
ElMessage.warning('缺少 BOM 编号/版本,无法更新状态')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const target = !!form.is_enabled
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await updateBomStatus({ bom_no: pureBomNo.value, version: form.version, is_enabled: target })
|
||||||
|
if (res?.code === 200) {
|
||||||
|
ElMessage.success(target ? '已启用' : '已停用')
|
||||||
|
// 刷新左侧列表摘要(计数与状态列)
|
||||||
|
groupCache.value = new Map()
|
||||||
|
fetchBomSummary()
|
||||||
|
} else {
|
||||||
|
form.is_enabled = !target
|
||||||
|
ElMessage.error(res?.msg || '状态更新失败')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
form.is_enabled = !target
|
||||||
|
ElMessage.error('状态更新失败')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDelete = (row: BomItem) => {
|
const handleDelete = (row: BomItem) => {
|
||||||
@ -918,14 +1087,25 @@ const submitForm = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isSaveAsMode.value && originalChildren.length > 0) {
|
if (isSaveAsMode.value && originalChildren.length > 0) {
|
||||||
const currentSet = new Set(form.children.map(c => `${c.child_id}-${c.dosage}`))
|
const childKey = (c: any) => `${c.child_id}-${c.dosage}-${c.child_bom_no || ''}-${c.child_bom_version || ''}`
|
||||||
const originalSet = new Set(originalChildren.map(c => `${c.child_id}-${c.dosage}`))
|
const currentSet = new Set(form.children.map(childKey))
|
||||||
|
const originalSet = new Set(originalChildren.map(childKey))
|
||||||
const isIdentical = currentSet.size === originalSet.size && [...currentSet].every(item => originalSet.has(item))
|
const isIdentical = currentSet.size === originalSet.size && [...currentSet].every(item => originalSet.has(item))
|
||||||
if (isIdentical) {
|
if (isIdentical) {
|
||||||
return ElMessage.warning('您未修改任何子件,与原版本内容一致,请修改后再保存')
|
return ElMessage.warning('您未修改任何子件,与原版本内容一致,请修改后再保存')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 前置校验:自制件子件必须已选版本(后端仍会强校验兜底)
|
||||||
|
const missingVer = form.children.find(
|
||||||
|
(c: any) => c.child_id !== null &&
|
||||||
|
(c.child_bom_no || c.child_bom_version || (c.bom_version_options && c.bom_version_options.length)) &&
|
||||||
|
!(c.child_bom_no && c.child_bom_version)
|
||||||
|
)
|
||||||
|
if (missingVer) {
|
||||||
|
return ElMessage.warning(`子件「${missingVer.material_name || missingVer.child_id}」为自制件,请先选择其 BOM 版本`)
|
||||||
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
bom_no: pureBomNo.value,
|
bom_no: pureBomNo.value,
|
||||||
remark: form.remark,
|
remark: form.remark,
|
||||||
@ -937,7 +1117,9 @@ const submitForm = async () => {
|
|||||||
.map((c: any) => ({
|
.map((c: any) => ({
|
||||||
child_id: c.child_id,
|
child_id: c.child_id,
|
||||||
dosage: c.dosage,
|
dosage: c.dosage,
|
||||||
remark: c.remark || ''
|
remark: c.remark || '',
|
||||||
|
child_bom_no: c.child_bom_no || '',
|
||||||
|
child_bom_version: c.child_bom_version || ''
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user