feat: 统一恢复管理端列表上下文

- 统一列表路由状态、分页初始化与安全返回契约

- 接入知识库、工作流、插件、审批、Skill、Agent、Bot、反馈和定时任务链路

- 补齐公共能力与关键返回路径自动化测试
This commit is contained in:
2026-08-10 16:12:43 +08:00
parent 9d2fa39a2d
commit 6bfd440214
42 changed files with 2359 additions and 570 deletions

View File

@@ -8,7 +8,7 @@ describe('page data recovery', () => {
it('loads the restored page and query without requesting the first page', async () => {
const get = vi
.fn()
.mockResolvedValue({ data: { records: [], totalRow: 30 } });
.mockResolvedValue({ data: { records: [], totalRow: 50 } });
const wrapper = mount(PageData, {
global: {
directives: { loading: {} },
@@ -60,6 +60,37 @@ describe('page data recovery', () => {
expect(wrapper.text()).not.toContain('数据加载失败,请重试');
});
it('contracts an out-of-range page to the latest page with one retry', async () => {
const get = vi
.fn()
.mockResolvedValueOnce({ data: { records: [], totalRow: 21 } })
.mockResolvedValueOnce({
data: { records: [{ id: 'last-record' }], totalRow: 21 },
});
const wrapper = mount(PageData, {
global: {
directives: { loading: {} },
},
props: {
initialPageNumber: 4,
pageSize: 10,
pageUrl: '/page',
requestClient: { get },
},
});
await flushPromises();
expect(get).toHaveBeenCalledTimes(2);
expect(get.mock.calls.map((call) => call[1].params.pageNumber)).toEqual([
4, 3,
]);
expect((wrapper.vm as any).getPageState()).toEqual({
pageNumber: 3,
pageSize: 10,
});
});
it('coalesces repeated reloads while a page request is still running', async () => {
let resolveInitialRequest: (value: {
data: { records: never[]; totalRow: number };
@@ -90,4 +121,85 @@ describe('page data recovery', () => {
expect(get).toHaveBeenCalledTimes(2);
});
it('restores a mounted list to a new route state with one target request', async () => {
const get = vi
.fn()
.mockResolvedValue({ data: { records: [], totalRow: 100 } });
const wrapper = mount(PageData, {
global: {
directives: { loading: {} },
},
props: { pageUrl: '/page', requestClient: { get } },
});
await flushPromises();
expect(
(wrapper.vm as any).restoreState({
pageNumber: 4,
pageSize: 20,
queryParams: { title: '月报' },
}),
).toBe(true);
await flushPromises();
expect(get).toHaveBeenCalledTimes(2);
expect(get).toHaveBeenLastCalledWith('/page', {
params: { pageNumber: 4, pageSize: 20, title: '月报' },
});
});
it('does not request again when the restored state is unchanged', async () => {
const get = vi
.fn()
.mockResolvedValue({ data: { records: [], totalRow: 50 } });
const wrapper = mount(PageData, {
global: {
directives: { loading: {} },
},
props: {
initialPageNumber: 3,
initialQueryParams: { categoryId: undefined, title: '月报' },
pageSize: 20,
pageUrl: '/page',
requestClient: { get },
},
});
await flushPromises();
expect(
(wrapper.vm as any).restoreState({
pageNumber: 3,
pageSize: 20,
queryParams: { title: '月报' },
}),
).toBe(false);
await flushPromises();
expect(get).toHaveBeenCalledTimes(1);
});
it('detaches query state from the caller reactive object', async () => {
const get = vi
.fn()
.mockResolvedValue({ data: { records: [], totalRow: 0 } });
const wrapper = mount(PageData, {
global: {
directives: { loading: {} },
},
props: { pageUrl: '/page', requestClient: { get } },
});
await flushPromises();
const query = { title: '月报' };
(wrapper.vm as any).setQuery(query);
await flushPromises();
query.title = '未提交草稿';
await flushPromises();
expect(get).toHaveBeenCalledTimes(2);
expect(get).toHaveBeenLastCalledWith('/page', {
params: { pageNumber: 1, pageSize: 10, title: '月报' },
});
});
});