fix: BOM子件下拉修复回显丢失和索引错位问题
This commit is contained in:
@ -164,7 +164,7 @@
|
||||
|
||||
<el-table :data="filteredChildren" border style="width: 100%; margin-bottom: 15px" max-height="300">
|
||||
<el-table-column label="子件物料" min-width="250" v-if="hasFormFieldPermission('child_id')">
|
||||
<template #default="{ row, $index }">
|
||||
<template #default="{ row }">
|
||||
<!-- ====== 改造:子件下拉 - 远程搜索 + 懒加载 ====== -->
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<el-select
|
||||
@ -174,14 +174,14 @@
|
||||
remote
|
||||
reserve-keyword
|
||||
style="flex: 1;"
|
||||
:remote-method="(q: string) => handleRemoteSearch(q, 'child', $index)"
|
||||
:remote-method="(q: string) => handleRemoteSearch(q, 'child', row.rowKey)"
|
||||
:loading="selectLoading"
|
||||
:loading-text="`正在加载第 ${childQueryParams.page} 页...`"
|
||||
:popper-class="`bom-loadmore-popper child-popper-${$index}`"
|
||||
@visible-change="(visible: boolean) => handleVisibleChange(visible, 'child', $index)"
|
||||
:popper-class="`bom-loadmore-popper child-popper-${row.rowKey}`"
|
||||
@visible-change="(visible: boolean) => handleVisibleChange(visible, 'child', row.rowKey)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getChildOptions($index)"
|
||||
v-for="item in getChildOptions(row.rowKey)"
|
||||
:key="item.id"
|
||||
:label="`${item.name} (${item.spec})`"
|
||||
:value="item.id"
|
||||
@ -197,7 +197,7 @@
|
||||
type="primary"
|
||||
link
|
||||
:icon="EditPen"
|
||||
@click.stop="openMaterialInNewTab(row.child_id, getChildSpec($index))"
|
||||
@click.stop="openMaterialInNewTab(row.child_id, getChildSpec(row.rowKey))"
|
||||
style="font-size: 16px; padding: 4px;"
|
||||
/>
|
||||
</el-tooltip>
|
||||
@ -218,8 +218,8 @@
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="60" align="center" v-if="userStore.hasPermission('bom_manage:operation')">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" link @click="removeChild($index)">删</el-button>
|
||||
<template #default="{ row }">
|
||||
<el-button type="danger" link @click="removeChild(row.rowKey)">删</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -266,6 +266,7 @@ interface MaterialBase {
|
||||
spec: string
|
||||
}
|
||||
interface ChildRow {
|
||||
rowKey: number // 唯一标识,替代 $index 作为 Map key
|
||||
child_id: number | null
|
||||
dosage: number
|
||||
remark: string
|
||||
@ -321,15 +322,15 @@ const getChildOptions = (index: number): MaterialBase[] => {
|
||||
// ============================================================
|
||||
const fetchMaterialOptions = async (
|
||||
type: 'parent' | 'child',
|
||||
index?: number,
|
||||
rowKey?: number,
|
||||
isLoadMore = false
|
||||
) => {
|
||||
// 子件行需要 index
|
||||
if (type === 'child' && index === undefined) return
|
||||
// 子件行需要 rowKey(唯一标识,不再依赖数组索引)
|
||||
if (type === 'child' && rowKey === undefined) return
|
||||
|
||||
const params = type === 'parent'
|
||||
? parentQueryParams
|
||||
: childDropdownStates.value.get(index!)?.queryParams
|
||||
: childDropdownStates.value.get(rowKey!)?.queryParams
|
||||
|
||||
if (!params) return
|
||||
|
||||
@ -353,12 +354,19 @@ const fetchMaterialOptions = async (
|
||||
const newItems = list.filter(m => !existingIds.has(m.id))
|
||||
parentOptions.value.push(...newItems)
|
||||
} else {
|
||||
parentOptions.value = list
|
||||
// ★ 修复回显丢失:先检查当前选中项是否在列表中,不在则从原 options 保留
|
||||
const selectedId = form.parent_id
|
||||
let finalList = [...list]
|
||||
if (selectedId && !list.find(m => m.id === selectedId)) {
|
||||
const existing = parentOptions.value.find(m => m.id === selectedId)
|
||||
if (existing) finalList.unshift(existing)
|
||||
}
|
||||
parentOptions.value = finalList
|
||||
}
|
||||
// 判断是否还有更多数据
|
||||
parentHasMore.value = parentOptions.value.length < total
|
||||
} else {
|
||||
const state = childDropdownStates.value.get(index!)
|
||||
const state = childDropdownStates.value.get(rowKey!)
|
||||
if (!state) return
|
||||
|
||||
if (isLoadMore) {
|
||||
@ -366,7 +374,15 @@ const fetchMaterialOptions = async (
|
||||
const newItems = list.filter(m => !existingIds.has(m.id))
|
||||
state.options.push(...newItems)
|
||||
} else {
|
||||
state.options = list
|
||||
// ★ 修复回显丢失:先通过 rowKey 精准找到当前行,再检查选中项是否在列表中
|
||||
const currentRow = form.children.find(c => c.rowKey === rowKey)
|
||||
const currentSelectedId = currentRow?.child_id
|
||||
let finalList = [...list]
|
||||
if (currentSelectedId && !list.find(m => m.id === currentSelectedId)) {
|
||||
const existing = state.options.find(m => m.id === currentSelectedId)
|
||||
if (existing) finalList.unshift(existing)
|
||||
}
|
||||
state.options = finalList
|
||||
}
|
||||
state.hasMore = state.options.length < total
|
||||
}
|
||||
@ -384,27 +400,27 @@ const fetchMaterialOptions = async (
|
||||
const handleRemoteSearch = (
|
||||
query: string,
|
||||
type: 'parent' | 'child',
|
||||
index?: number
|
||||
rowKey?: number
|
||||
) => {
|
||||
if (type === 'parent') {
|
||||
parentQueryParams.keyword = query
|
||||
parentQueryParams.page = 1
|
||||
parentHasMore.value = true
|
||||
fetchMaterialOptions('parent')
|
||||
} else if (type === 'child' && index !== undefined) {
|
||||
const state = childDropdownStates.value.get(index)
|
||||
} else if (type === 'child' && rowKey !== undefined) {
|
||||
const state = childDropdownStates.value.get(rowKey)
|
||||
if (!state) return
|
||||
state.queryParams.keyword = query
|
||||
state.queryParams.page = 1
|
||||
state.hasMore = true
|
||||
fetchMaterialOptions('child', index)
|
||||
fetchMaterialOptions('child', rowKey)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 【改造】下拉框展开/收起处理(重置分页 + 预加载第一页)
|
||||
// ============================================================
|
||||
const handleVisibleChange = (visible: boolean, type: 'parent' | 'child', index?: number) => {
|
||||
const handleVisibleChange = (visible: boolean, type: 'parent' | 'child', rowKey?: number) => {
|
||||
if (!visible) return
|
||||
|
||||
if (type === 'parent') {
|
||||
@ -412,20 +428,20 @@ const handleVisibleChange = (visible: boolean, type: 'parent' | 'child', index?:
|
||||
parentQueryParams.keyword = ''
|
||||
parentHasMore.value = true
|
||||
fetchMaterialOptions('parent')
|
||||
} else if (type === 'child' && index !== undefined) {
|
||||
} else if (type === 'child' && rowKey !== undefined) {
|
||||
// 确保该行下拉状态已初始化
|
||||
if (!childDropdownStates.value.has(index)) {
|
||||
childDropdownStates.value.set(index, {
|
||||
if (!childDropdownStates.value.has(rowKey)) {
|
||||
childDropdownStates.value.set(rowKey, {
|
||||
options: [],
|
||||
queryParams: { page: 1, limit: PAGE_SIZE, keyword: '' },
|
||||
hasMore: true
|
||||
})
|
||||
}
|
||||
const state = childDropdownStates.value.get(index)!
|
||||
const state = childDropdownStates.value.get(rowKey)!
|
||||
state.queryParams.page = 1
|
||||
state.queryParams.keyword = ''
|
||||
state.hasMore = true
|
||||
fetchMaterialOptions('child', index)
|
||||
fetchMaterialOptions('child', rowKey)
|
||||
}
|
||||
|
||||
// 延迟 50ms 等待弹窗 DOM 完全渲染
|
||||
@ -433,7 +449,7 @@ const handleVisibleChange = (visible: boolean, type: 'parent' | 'child', index?:
|
||||
// 动态拼接精确的选择器
|
||||
const exactSelector = type === 'parent'
|
||||
? '.parent-popper .el-select-dropdown__wrap'
|
||||
: `.child-popper-${index} .el-select-dropdown__wrap`;
|
||||
: `.child-popper-${rowKey} .el-select-dropdown__wrap`;
|
||||
|
||||
const popperWrap = document.querySelector(exactSelector) as HTMLElement;
|
||||
|
||||
@ -450,9 +466,9 @@ const handleVisibleChange = (visible: boolean, type: 'parent' | 'child', index?:
|
||||
if (scrollHeight - scrollTop - clientHeight <= 10) {
|
||||
if (type === 'parent') {
|
||||
loadMoreParent();
|
||||
} else if (type === 'child' && index !== undefined) {
|
||||
} else if (type === 'child' && rowKey !== undefined) {
|
||||
// 触发子件加载
|
||||
loadMoreChild(popperWrap, index);
|
||||
loadMoreChild(popperWrap, rowKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -470,20 +486,20 @@ const loadMoreParent = () => {
|
||||
fetchMaterialOptions('parent', undefined, true)
|
||||
}
|
||||
|
||||
const loadMoreChild = (_el: HTMLElement, index: number) => {
|
||||
const state = childDropdownStates.value.get(index)
|
||||
const loadMoreChild = (_el: HTMLElement, rowKey: number) => {
|
||||
const state = childDropdownStates.value.get(rowKey)
|
||||
if (!state) return
|
||||
if (selectLoading.value || !state.hasMore) return
|
||||
state.queryParams.page++
|
||||
fetchMaterialOptions('child', index, true)
|
||||
fetchMaterialOptions('child', rowKey, true)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 【改造】初始化子件行下拉状态
|
||||
// ============================================================
|
||||
const initChildDropdownState = (index: number) => {
|
||||
if (!childDropdownStates.value.has(index)) {
|
||||
childDropdownStates.value.set(index, {
|
||||
const initChildDropdownState = (rowKey: number) => {
|
||||
if (!childDropdownStates.value.has(rowKey)) {
|
||||
childDropdownStates.value.set(rowKey, {
|
||||
options: [],
|
||||
queryParams: { page: 1, limit: PAGE_SIZE, keyword: '' },
|
||||
hasMore: true
|
||||
@ -500,7 +516,7 @@ const filteredChildren = computed(() => {
|
||||
}
|
||||
const kw = childSearchKeyword.value.toLowerCase()
|
||||
return form.children.filter(child => {
|
||||
const state = childDropdownStates.value.get(form.children.indexOf(child))
|
||||
const state = childDropdownStates.value.get(child.rowKey)
|
||||
const material = state?.options.find(m => m.id === child.child_id)
|
||||
if (!material) return false
|
||||
const name = (material.name || '').toLowerCase()
|
||||
@ -510,10 +526,11 @@ const filteredChildren = computed(() => {
|
||||
})
|
||||
|
||||
// 获取子件规格(从 childDropdownStates 缓存中查找)
|
||||
const getChildSpec = (index: number): string => {
|
||||
const state = childDropdownStates.value.get(index)
|
||||
if (!state || !form.children[index]?.child_id) return ''
|
||||
const material = state.options.find((m: MaterialBase) => m.id === form.children[index].child_id)
|
||||
const getChildSpec = (rowKey: number): string => {
|
||||
const state = childDropdownStates.value.get(rowKey)
|
||||
const row = form.children.find(c => c.rowKey === rowKey)
|
||||
if (!state || !row?.child_id) return ''
|
||||
const material = state.options.find((m: MaterialBase) => m.id === row.child_id)
|
||||
return material?.spec || ''
|
||||
}
|
||||
|
||||
@ -677,8 +694,9 @@ const loadDetail = async (bomNo: string, version: string) => {
|
||||
const res = await getBomDetail(bomNo, version)
|
||||
if (res.code === 200) {
|
||||
const data = res.data
|
||||
// 1. 映射子件基本数据
|
||||
form.children = data.children.map((child: any) => ({
|
||||
// 1. 映射子件基本数据(使用 idx 生成唯一 rowKey)
|
||||
form.children = data.children.map((child: any, idx: number) => ({
|
||||
rowKey: idx, // 用数组索引作为唯一标识(编辑场景下不会增删行)
|
||||
child_id: child.child_id,
|
||||
dosage: child.dosage,
|
||||
remark: child.remark || ''
|
||||
@ -686,7 +704,7 @@ const loadDetail = async (bomNo: string, version: string) => {
|
||||
|
||||
// 2. 初始化子件下拉状态,并预填充 options 解决回显显示 ID 的问题
|
||||
form.children.forEach((child, idx) => {
|
||||
initChildDropdownState(idx)
|
||||
initChildDropdownState(idx) // rowKey === idx(编辑场景下唯一)
|
||||
|
||||
if (child.child_id) {
|
||||
const state = childDropdownStates.value.get(idx)!
|
||||
@ -755,26 +773,17 @@ const resetForm = () => {
|
||||
}
|
||||
|
||||
const addChild = () => {
|
||||
const idx = form.children.length
|
||||
form.children.push({ child_id: null, dosage: 0, remark: '' })
|
||||
initChildDropdownState(idx)
|
||||
const rowKey = Date.now() // 生成唯一标识,不再使用数组长度
|
||||
form.children.push({ rowKey, child_id: null, dosage: 0, remark: '' })
|
||||
initChildDropdownState(rowKey)
|
||||
}
|
||||
|
||||
const removeChild = (idx: number) => {
|
||||
form.children.splice(idx, 1)
|
||||
// 清理该行下拉状态(需要重新索引后续行的状态)
|
||||
rebuildChildDropdownStates()
|
||||
}
|
||||
|
||||
// 重建子件下拉状态索引(删除行后需要重新编号)
|
||||
const rebuildChildDropdownStates = () => {
|
||||
const newMap = new Map<number, ChildDropdownState>()
|
||||
form.children.forEach((_, idx) => {
|
||||
if (childDropdownStates.value.has(idx)) {
|
||||
newMap.set(idx, childDropdownStates.value.get(idx)!)
|
||||
}
|
||||
})
|
||||
childDropdownStates.value = newMap
|
||||
const removeChild = (rowKey: number) => {
|
||||
// 通过 rowKey 找到并删除该行(不再依赖数组索引)
|
||||
const idx = form.children.findIndex(c => c.rowKey === rowKey)
|
||||
if (idx !== -1) form.children.splice(idx, 1)
|
||||
// 直接删除该行的下拉状态(无需重建索引)
|
||||
childDropdownStates.value.delete(rowKey)
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
|
||||
Reference in New Issue
Block a user