fix(drivers): refresh cached list and summary when returning from details

This commit is contained in:
2026-10-03 23:44:35 +08:00
parent 9ec1fb549e
commit 5c4a381d87
2 changed files with 48 additions and 1 deletions
@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils' import { flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils'
import { defineComponent, h, KeepAlive, ref } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { ApiResponseError } from '@/shared/api/http' import { ApiResponseError } from '@/shared/api/http'
import * as api from '../api/drivers' import * as api from '../api/drivers'
@@ -44,6 +45,47 @@ beforeEach(() => {
afterEach(() => { wrappers.splice(0).forEach(wrapper => wrapper.unmount()); vi.unstubAllGlobals() }) afterEach(() => { wrappers.splice(0).forEach(wrapper => wrapper.unmount()); vi.unstubAllGlobals() })
describe('driver page workflows', () => { describe('driver page workflows', () => {
it('refreshes a cached list once on return and retains applied filters and pagination', async () => {
const showingList = ref(true)
const host = defineComponent({ setup: () => () => h(KeepAlive, null, { default: () => showingList.value ? h(DriversView) : h('div', '司机详情') }) })
const wrapper = shallowMount(host, { global: { stubs: { ...stubs, KeepAlive: false, DriversView: false }, directives: { loading: () => undefined } } })
wrappers.push(wrapper)
await flushPromises()
expect(api.listDrivers).toHaveBeenCalledTimes(1)
const list = wrapper.findComponent(DriversView)
const toolbar = list.findComponent({ name: 'QueryToolbar' })
toolbar.vm.$emit('update:modelValue', { keyword: 'DR-TEST', licenseState: 'EXPIRING' })
toolbar.vm.$emit('search'); await flushPromises()
list.findComponent({ name: 'DataTable' }).vm.$emit('update:page', 2); await flushPromises()
expect(api.listDrivers).toHaveBeenCalledTimes(3)
showingList.value = false; await flushPromises()
const refreshed = { ...structuredClone(driver), recordVersion: 3, reviewStatus: 'APPROVED' as const }
vi.mocked(api.listDrivers).mockResolvedValueOnce({ records: [refreshed], page: 2, size: 20, total: 1, pages: 1, summary: { total: 1, owned: 1, dispatched: 0, expiringSoon: 1, expired: 0 } })
showingList.value = true; await flushPromises()
expect(api.listDrivers).toHaveBeenCalledTimes(4)
expect(api.listDrivers).toHaveBeenLastCalledWith({ keyword: 'DR-TEST', licenseState: 'EXPIRING', region: undefined, ownership: undefined, active: undefined, reviewStatus: undefined, page: 2, size: 20 })
expect(wrapper.findComponent({ name: 'DataTable' }).props('data')).toEqual([refreshed])
expect(wrapper.text()).toContain('司机总数1')
expect(api.getDriver).not.toHaveBeenCalled()
})
it('ignores a list response that arrives while its cached page is deactivated', async () => {
let resolveList!: (value: Awaited<ReturnType<typeof api.listDrivers>>) => void
vi.mocked(api.listDrivers).mockReturnValueOnce(new Promise(resolve => { resolveList = resolve }))
const showingList = ref(true)
const host = defineComponent({ setup: () => () => h(KeepAlive, null, { default: () => showingList.value ? h(DriversView) : h('div', '司机详情') }) })
const wrapper = shallowMount(host, { global: { stubs: { ...stubs, KeepAlive: false, DriversView: false }, directives: { loading: () => undefined } } })
wrappers.push(wrapper)
await flushPromises()
const list = wrapper.findComponent(DriversView)
showingList.value = false; await flushPromises()
resolveList({ records: [driver], page: 1, size: 20, total: 24, pages: 2, summary: { total: 24, owned: 20, dispatched: 4, expiringSoon: 3, expired: 1 } }); await flushPromises()
expect(list.findComponent({ name: 'DataTable' }).props('data')).toEqual([])
showingList.value = true; await flushPromises()
expect(api.listDrivers).toHaveBeenCalledTimes(2)
expect(wrapper.findComponent({ name: 'DataTable' }).props('data')).toEqual([driver])
})
it('loads one paged request with server summary and does not request each driver or file', async () => { it('loads one paged request with server summary and does not request each driver or file', async () => {
const wrapper = mount(DriversView) const wrapper = mount(DriversView)
await flushPromises() await flushPromises()
@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import '@/styles/adminDensity.css' import '@/styles/adminDensity.css'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue' import { onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@lucide/vue' import { Plus } from '@lucide/vue'
@@ -26,6 +26,7 @@ const rows = ref<DriverDetail[]>([]), total = ref(0), page = ref(1), pageSize =
const error = ref<ApiErrorDetails | null>(null), summary = ref<DriverSummary | null>(null) const error = ref<ApiErrorDetails | null>(null), summary = ref<DriverSummary | null>(null)
const filters = ref<QueryModel>({ keyword: '', region: '', ownership: '', active: '', reviewStatus: '', licenseState: '' }) const filters = ref<QueryModel>({ keyword: '', region: '', ownership: '', active: '', reviewStatus: '', licenseState: '' })
let applied: Omit<DriverQuery, 'page' | 'size'> = {}, request = 0 let applied: Omit<DriverQuery, 'page' | 'size'> = {}, request = 0
let refreshOnReturn = false
const editorOpen = ref(false), editing = ref<DriverDetail | null>(null), actionId = ref('') const editorOpen = ref(false), editing = ref<DriverDetail | null>(null), actionId = ref('')
const previewOpen = ref(false), preview = ref<DriverDetail | null>(null) const previewOpen = ref(false), preview = ref<DriverDetail | null>(null)
const fields: QueryField[] = [ const fields: QueryField[] = [
@@ -101,6 +102,10 @@ function saved(row: DriverDetail) {
watch(editorOpen, (value, previous) => { if (!value && previous) void load() }) watch(editorOpen, (value, previous) => { if (!value && previous) void load() })
watch([page, pageSize], () => void load()) watch([page, pageSize], () => void load())
onMounted(load) onMounted(load)
onDeactivated(() => { request++; refreshOnReturn = true })
onActivated(() => {
if (refreshOnReturn) { refreshOnReturn = false; void load() }
})
onBeforeUnmount(() => { request++ }) onBeforeUnmount(() => { request++ })
</script> </script>