diff --git a/api/modules/asset.js b/api/modules/asset.js index 11bd69c..689c168 100644 --- a/api/modules/asset.js +++ b/api/modules/asset.js @@ -25,7 +25,7 @@ export const normalizeAssetInfo = (data = {}) => ({ const normalizePackageHistoryNode = (node = {}) => ({ ...node, children: Array.isArray(node.children) - ? node.children.map(normalizePackageHistoryNode) + ? node.children.filter(child => Number(child?.status) !== 4).map(normalizePackageHistoryNode) : [], expand_by_default: node.expand_by_default === true || node.expand_by_default === 1 }); @@ -33,7 +33,7 @@ const normalizePackageHistoryNode = (node = {}) => ({ export const normalizeAssetPackageHistory = (data = {}) => ({ ...data, items: Array.isArray(data.items) - ? data.items.map(normalizePackageHistoryNode) + ? data.items.filter(node => Number(node?.status) !== 4).map(normalizePackageHistoryNode) : [], page: Number(data.page), size: Number(data.size), diff --git a/pages/asset-package-history/asset-package-history.vue b/pages/asset-package-history/asset-package-history.vue index cd0b73c..103144b 100644 --- a/pages/asset-package-history/asset-package-history.vue +++ b/pages/asset-package-history/asset-package-history.vue @@ -46,24 +46,28 @@ if (loading.value || (append && noMore.value)) return; loading.value = true; - const requestedPage = page.value; - try { - const data = await assetApi.getPackageHistory( - userStore.state.identifier, - requestedPage, - pageSize - ); - const groups = data.items; - const responsePage = Number.isInteger(data.page) && data.page > 0 - ? data.page - : requestedPage; - const responseSize = Number.isInteger(data.size) && data.size > 0 - ? data.size - : pageSize; - const responseTotal = Number.isFinite(data.total) && data.total >= 0 - ? data.total - : 0; + let groups; + do { + const requestedPage = page.value; + const data = await assetApi.getPackageHistory( + userStore.state.identifier, + requestedPage, + pageSize + ); + groups = data.items; + const responsePage = Number.isInteger(data.page) && data.page > 0 + ? Math.max(data.page, requestedPage) + : requestedPage; + const responseSize = Number.isInteger(data.size) && data.size > 0 + ? data.size + : pageSize; + total.value = Number.isFinite(data.total) && data.total >= 0 ? data.total : 0; + // Total and pagination describe the unfiltered backend pages. Skip a + // page containing only invalidated records so later records stay reachable. + noMore.value = responsePage * responseSize >= total.value; + page.value = responsePage + 1; + } while (groups.length === 0 && !noMore.value); if (append) { packageList.value.push(...groups); @@ -71,9 +75,6 @@ packageList.value = groups; } - total.value = responseTotal; - noMore.value = responsePage * responseSize >= total.value; - page.value = responsePage + 1; } catch (error) { // The shared request layer handles the API's unified error response and authentication errors. console.error('加载套餐历史失败', error); diff --git a/tests/october-h5.test.mjs b/tests/october-h5.test.mjs index e4e0f31..3b04fa2 100644 --- a/tests/october-h5.test.mjs +++ b/tests/october-h5.test.mjs @@ -124,6 +124,70 @@ test('phone binding modes and success updates distinguish all four cases', async assert.equal(success({}, '13900000002', 'customer-asset').assetPhoneBound, true); }); +test('package history hides numeric/string status 4 recursively without changing pagination', async () => { + const r = runtime(); + const { normalizeAssetPackageHistory } = await r.load('api/modules/asset.js'); + const input = { + page: 1, size: 10, total: 30, + items: [ + { package_usage_id: 1, status: 4 }, + { package_usage_id: 2, status: '4' }, + { package_usage_id: 3, status: 1, expand_by_default: 1, children: [ + { package_usage_id: 4, status: 4 }, + { package_usage_id: 5, status: 2, children: [ + { package_usage_id: 6, status: '4' }, { package_usage_id: 7, status: 0 } + ] } + ] }, + { package_usage_id: 8, status: 3 } + ] + }; + const result = normalizeAssetPackageHistory(input); + assert.deepEqual(plain(result.items).map(item => item.package_usage_id), [3, 8]); + assert.deepEqual(plain(result.items[0].children).map(item => item.package_usage_id), [5]); + assert.deepEqual(plain(result.items[0].children[0].children).map(item => item.package_usage_id), [7]); + assert.equal(result.items[0].expand_by_default, true); + assert.equal(result.total, 30); + assert.equal(result.page, 1); + assert.equal(input.items.length, 4); + assert.equal(input.items[2].children.length, 2); +}); + +test('package-history page skips all-invalid pages and keeps subsequent pages reachable', async () => { + const r = runtime(); + const { normalizeAssetPackageHistory } = await r.load('api/modules/asset.js'); + const requestedPages = []; + r.api.assetApi = { getPackageHistory: async (identifier, page) => { + assert.equal(identifier, 'A'); requestedPages.push(page); + return normalizeAssetPackageHistory({ page, size: 10, total: 40, + items: [{ package_usage_id: page, status: page === 2 || page === 4 ? 1 : 4 }] }); + } }; + const page = await r.load('pages/asset-package-history/asset-package-history.vue', ['loadPackageList', 'packageList', 'noMore']); + await page.loadPackageList(); + assert.deepEqual(requestedPages, [1, 2]); + assert.deepEqual(plain(page.packageList.value).map(item => item.package_usage_id), [2]); + assert.equal(page.noMore.value, false); + await page.loadPackageList(true); + assert.deepEqual(requestedPages, [1, 2, 3, 4]); + assert.deepEqual(plain(page.packageList.value).map(item => item.package_usage_id), [2, 4]); + assert.equal(page.noMore.value, true); +}); + +test('package history with only invalidated records ends with an empty state', async () => { + const r = runtime(); + const { normalizeAssetPackageHistory } = await r.load('api/modules/asset.js'); + const requestedPages = []; + r.api.assetApi = { getPackageHistory: async (identifier, page) => { + requestedPages.push(page); + return normalizeAssetPackageHistory({ page, size: 10, total: 20, items: [{ status: 4 }] }); + } }; + const page = await r.load('pages/asset-package-history/asset-package-history.vue', ['loadPackageList', 'packageList', 'noMore', 'historyLoaded']); + await page.loadPackageList(); + assert.deepEqual(requestedPages, [1, 2]); + assert.equal(page.packageList.value.length, 0); + assert.equal(page.noMore.value, true); + assert.equal(page.historyLoaded.value, true); +}); + test('login facts are persisted per token/asset; customer phone changes never migrate assets', async () => { const r = runtime({ phoneBindStatus: { A: { bound: true, phone: '13900000002' } } }); const user = (await r.load('store/user.js')).useUserStore();