fix(pl): 隔离同合同产品加载请求并允许失败后重新选择。

This commit is contained in:
wuzilong committed 2026-11-01 22:43:12 +08:00
1 parent 136a85dda7
commit eb4a34cd5d
2 files changed
+91 -13

No files matched your search

+58 -2
View File
@@ -10,10 +10,12 @@ import { usePersistedListColumns } from '../src/composables/usePersistedListColu
function deferred() {
let resolve!: (value: any) => void
const promise = new Promise<any>((r) => {
let reject!: (reason: any) => void
const promise = new Promise<any>((r, j) => {
resolve = r
reject = j
})
return { promise, resolve }
return { promise, resolve, reject }
}
function harness() {
const reads: string[] = [],
@@ -354,6 +356,8 @@ test('产品勾选往返保留原范围ID;关闭重开后拒绝旧合同和产
try {
await s.edit()
s.addContractBinding('c')
h.productRequests[0].resolve([{ id: 'p' }, { id: 'p2' }])
await vue.nextTick()
s.form.value.contractBindings[0].products = [
{ id: 'scope', contractProductId: 'p', scopeStatus: 'inactive' }
]
@@ -436,6 +440,9 @@ test('新增多合同连同证面成功提交一次,移除全部提交明确
await s.edit()
s.addContractBinding('one')
s.addContractBinding('two')
h.productRequests[0].resolve([{ id: 'p1' }])
h.productRequests[1].resolve([{ id: 'p2' }])
await vue.nextTick()
s.setBindingProducts(0, ['p1'])
s.setBindingProducts(1, ['p2'])
s.form.value.certNo = 'MULTI'
@@ -456,3 +463,52 @@ test('新增多合同连同证面成功提交一次,移除全部提交明确
h.stop()
}
})
test('同合同产品后发请求优先,移除重加使移除前请求失效', async () => {
const h = harness(),
s = h.state
try {
await s.edit()
s.addContractBinding('c')
const second = s.loadBindingProducts('c')
h.productRequests[1].resolve([{ id: 'new', productName: '新产品' }])
await second
h.productRequests[0].resolve([{ id: 'old', productName: '旧产品' }])
await vue.nextTick()
assert.equal(s.bindingProducts.value.c[0].id, 'new')
const old = s.loadBindingProducts('c')
s.removeContractBinding(0)
s.addContractBinding('c')
h.productRequests[2].resolve([{ id: 'removed', productName: '移除前' }])
await old
assert.equal(s.bindingProducts.value.c, undefined)
assert.equal(s.bindingProductLoading.value.c, true)
h.productRequests[3].resolve([{ id: 'readded', productName: '重加后' }])
await vue.nextTick()
assert.equal(s.bindingProducts.value.c[0].id, 'readded')
} finally {
h.stop()
}
})
test('产品加载失败可再次展开重试,加载中不能改选择', async () => {
const h = harness(),
s = h.state
try {
await s.edit()
s.addContractBinding('c')
h.productRequests[0].reject(new Error('network'))
await vue.nextTick()
await vue.nextTick()
assert.equal(s.bindingProductLoading.value.c, false)
const retry = s.loadBindingProducts('c')
s.setBindingProducts(0, ['invalid'])
assert.deepEqual(s.form.value.contractBindings[0].products, [])
h.productRequests[1].resolve([{ id: 'p', productName: '产品' }])
await retry
s.setBindingProducts(0, ['p'])
assert.equal(s.form.value.contractBindings[0].products[0].contractProductId, 'p')
} finally {
h.stop()
}
})
+33 -11
View File
@@ -300,9 +300,8 @@
<el-select
multiple
:model-value="row.products.map((p: any) => p.contractProductId)"
:disabled="
saving || form.bindingsEditable === false || !bindingProducts[row.contractId]
"
:disabled="saving || form.bindingsEditable === false"
:loading="bindingProductLoading[row.contractId]"
placeholder="勾选适用产品"
style="width: 100%"
@change="(ids: string[]) => setBindingProducts($index, ids)"
@@ -312,6 +311,7 @@
v-for="product in bindingProductOptions(row)"
:key="product.id"
:value="product.id"
:disabled="bindingProductLoading[row.contractId]"
:label="
product.productName +
(product.model ? ' / ' + product.model : '') +
@@ -611,6 +611,8 @@
const bindingContracts = ref<ContractOption[]>([])
const bindingProducts = ref<Record<string, ContractProduct[]>>({})
const bindingSearching = ref(false)
const bindingProductEpochs = new Map<string, number>()
const bindingProductLoading = ref<Record<string, boolean>>({})
const removedBindings = new Map<string, CertificateContractBinding>()
const originalScopes = new Map<string, CertificateContractBinding['products']>()
function bindingContractLabel(id: string) {
@@ -654,16 +656,25 @@
}
async function loadBindingProducts(contractId: string) {
const session = editSession
const result = await quotaCertApi.products(contractId)
if (
session !== editSession ||
!formVisible.value ||
!form.value.contractBindings?.some(
const current = (bindingProductEpochs.get(contractId) || 0) + 1
bindingProductEpochs.set(contractId, current)
bindingProductLoading.value[contractId] = true
const isCurrent = () =>
session === editSession &&
current === bindingProductEpochs.get(contractId) &&
formVisible.value &&
form.value.contractBindings?.some(
(b: CertificateContractBinding) => b.contractId === contractId
)
)
return
bindingProducts.value = { ...bindingProducts.value, [contractId]: result }
try {
const result = await quotaCertApi.products(contractId)
if (!isCurrent()) return
bindingProducts.value = { ...bindingProducts.value, [contractId]: result }
} catch {
// 请求层已提示错误;允许再次打开产品下拉重试。
} finally {
if (isCurrent()) bindingProductLoading.value[contractId] = false
}
}
function addContractBinding(contractId: string) {
if (
@@ -685,10 +696,17 @@
if (saving.value || form.value.bindingsEditable === false) return
const [removed] = form.value.contractBindings.splice(index, 1)
removedBindings.set(removed.contractId, removed)
bindingProductEpochs.set(
removed.contractId,
(bindingProductEpochs.get(removed.contractId) || 0) + 1
)
delete bindingProducts.value[removed.contractId]
delete bindingProductLoading.value[removed.contractId]
}
function setBindingProducts(index: number, ids: string[]) {
if (saving.value || form.value.bindingsEditable === false) return
const binding = form.value.contractBindings[index] as CertificateContractBinding
if (bindingProductLoading.value[binding.contractId]) return
const previous = [...binding.products, ...(originalScopes.get(binding.contractId) || [])]
binding.products = ids.map(
(id) =>
@@ -747,6 +765,8 @@
recognitionImage.value = ''
bindingContracts.value = contracts
bindingProducts.value = {}
bindingProductLoading.value = {}
bindingProductEpochs.clear()
bindingSearching.value = false
originalScopes.clear()
removedBindings.clear()
@@ -809,6 +829,8 @@
}
bindingContracts.value = []
bindingProducts.value = {}
bindingProductLoading.value = {}
bindingProductEpochs.clear()
bindingSearching.value = false
originalScopes.clear()
removedBindings.clear()