diff --git a/web-admin/scripts/quota-statistics.test.ts b/web-admin/scripts/quota-statistics.test.ts index d711c1d6e..574282959 100644 --- a/web-admin/scripts/quota-statistics.test.ts +++ b/web-admin/scripts/quota-statistics.test.ts @@ -126,7 +126,7 @@ function harness(fixed?: string) { const state = scope.run(() => new Function( ...Object.keys(env), - `${code}; return { rows, fixedDetail, details, load, loadDetail, allowed, canEdit, keyword, setFilterState }` + `${code}; return { rows, fixedDetail, details, load, loadDetail, allowed, canEdit, keyword, setFilterState, page, total }` )(...Object.values(env)) ) return { @@ -490,3 +490,84 @@ test('closing dialog invalidates pending searches including their finally state' assert.equal(s.searching.value, false) h.stop() }) + +// Mount the real Element Plus pager. Its page-count watcher must remain active. +import ElPagination from 'element-plus/es/components/pagination/src/pagination.mjs' +test('real pagination keeps page two while loading and preserves count after request failure', async () => { + const h = harness(), + s = h.state + h.permissions[0].resolve({ all: true }) + await flush() + h.calls[0].resolve({ list: [{ id: 'first' }], total: 45 }) + await flush() + const renderer = vue.createRenderer({ + createElement: () => ({}), + createText: () => ({}), + createComment: () => ({}), + setText: () => {}, + setElementText: () => {}, + parentNode: () => null, + nextSibling: () => null, + insert: () => {}, + remove: () => {}, + patchProp: () => {} + }) + const app = renderer.createApp({ + render: () => + vue.h(ElPagination, { + currentPage: s.page.value, + total: s.total.value, + pageSize: 20, + layout: '', + 'onUpdate:current-page': (value: number) => { + s.page.value = value + }, + onCurrentChange: () => { + void s.load() + } + }) + }) + app.mount({}) + s.page.value = 2 + const pending = s.load() + await vue.nextTick() + await flush() + assert.equal(s.page.value, 2) + assert.equal(h.calls.length, 2) + assert.equal(h.calls[1].args[0].page, 2) + h.calls[1].resolve({ list: [{ id: 'second' }], total: 45 }) + await pending + await vue.nextTick() + assert.equal(s.page.value, 2) + assert.equal(s.rows.value[0].id, 'second') + const failed = s.load() + h.calls[2].reject(Error('failed')) + await failed + await vue.nextTick() + assert.equal(s.page.value, 2) + assert.equal(s.total.value, 45) + assert.deepEqual(s.rows.value, []) + h.user.getUserInfo.tenantNo = 'next' + await vue.nextTick() + assert.equal(s.page.value, 1) + assert.equal(s.total.value, 0) + app.unmount() + h.stop() +}) + +test('refresh and search while permission request is pending do not cancel initialization', async () => { + const h = harness(), + s = h.state + await s.load() + s.keyword.value = '目标' + s.setFilterState('contractNo', { text: 'HT' }) + assert.equal(h.calls.length, 0) + h.permissions[0].resolve({ menuCodes: ['pl:quota-cert:list'] }) + await flush() + assert.equal(h.calls.length, 1) + assert.deepEqual(h.calls[0].args[0], { page: 1, pageSize: 20, keyword: '目标', contractNo: 'HT' }) + h.calls[0].resolve({ list: [{ id: 'loaded' }], total: 1 }) + await flush() + assert.equal(s.rows.value[0].id, 'loaded') + h.stop() +}) diff --git a/web-admin/src/views/pl/quota-statistics/StatisticsPanel.vue b/web-admin/src/views/pl/quota-statistics/StatisticsPanel.vue index a269028c5..096fb9f31 100644 --- a/web-admin/src/views/pl/quota-statistics/StatisticsPanel.vue +++ b/web-admin/src/views/pl/quota-statistics/StatisticsPanel.vue @@ -229,19 +229,23 @@ { onChange: search } ) watchVisibleColumns(visibleColumns) - function clear() { + function clearResults() { loader.invalidate() epoch++ rows.value = [] fixedDetail.value = undefined - total.value = 0 loading.value = false error.value = '' Object.keys(details).forEach((k) => delete details[k]) } + function clear() { + clearResults() + total.value = 0 + page.value = 1 + } async function load() { - clear() if (!allowed.value || !props.active) return + clearResults() const current = epoch loading.value = true try {