feat: 优化定时任务管理与日志体验
- 增加工作流选项、时间范围筛选和日志详情展示 - 支持可配置自动刷新、轻量局部更新与响应式布局
This commit is contained in:
@@ -122,6 +122,109 @@ describe('page data recovery', () => {
|
||||
expect(get).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('does not let a lightweight refresh supersede an active page query', async () => {
|
||||
let resolveInitialRequest: (value: {
|
||||
data: { records: { id: string }[]; totalRow: number };
|
||||
}) => void = () => {};
|
||||
const initialRequest = new Promise<{
|
||||
data: { records: { id: string }[]; totalRow: number };
|
||||
}>((resolve) => {
|
||||
resolveInitialRequest = resolve;
|
||||
});
|
||||
const get = vi.fn().mockReturnValueOnce(initialRequest);
|
||||
const wrapper = mount(PageData, {
|
||||
global: {
|
||||
directives: { loading: {} },
|
||||
},
|
||||
props: {
|
||||
pageUrl: '/page',
|
||||
refreshUrl: '/refresh',
|
||||
requestClient: { get },
|
||||
},
|
||||
});
|
||||
|
||||
const refreshRequest = (wrapper.vm as any).reload({
|
||||
lightweight: true,
|
||||
silent: true,
|
||||
});
|
||||
expect(get).toHaveBeenCalledTimes(1);
|
||||
|
||||
resolveInitialRequest({
|
||||
data: { records: [{ id: 'counted-result' }], totalRow: 21 },
|
||||
});
|
||||
await refreshRequest;
|
||||
await flushPromises();
|
||||
|
||||
expect(get).toHaveBeenCalledTimes(1);
|
||||
expect(wrapper.emitted('loadSuccess')?.at(-1)?.[0]).toMatchObject({
|
||||
lightweight: false,
|
||||
recordCount: 1,
|
||||
});
|
||||
expect(wrapper.find('.el-pagination').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('uses the lightweight refresh endpoint without replacing the total', async () => {
|
||||
const get = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
data: { records: [{ id: 'initial' }], totalRow: 35 },
|
||||
})
|
||||
.mockResolvedValueOnce({ data: [{ id: 'latest' }] });
|
||||
const wrapper = mount(PageData, {
|
||||
global: {
|
||||
directives: { loading: {} },
|
||||
},
|
||||
props: {
|
||||
pageUrl: '/page',
|
||||
refreshUrl: '/refresh',
|
||||
requestClient: { get },
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
await (wrapper.vm as any).reload({ lightweight: true, silent: true });
|
||||
await flushPromises();
|
||||
|
||||
expect(get).toHaveBeenLastCalledWith('/refresh', {
|
||||
params: { pageNumber: 1, pageSize: 10 },
|
||||
});
|
||||
expect(wrapper.find('.el-pagination').exists()).toBe(true);
|
||||
expect(wrapper.emitted('loadSuccess')?.at(-1)?.[0]).toMatchObject({
|
||||
lightweight: true,
|
||||
pageNumber: 1,
|
||||
recordCount: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to the counted page endpoint outside the first page', async () => {
|
||||
const get = vi
|
||||
.fn()
|
||||
.mockResolvedValue({ data: { records: [], totalRow: 30 } });
|
||||
const wrapper = mount(PageData, {
|
||||
global: {
|
||||
directives: { loading: {} },
|
||||
},
|
||||
props: {
|
||||
initialPageNumber: 2,
|
||||
pageUrl: '/page',
|
||||
refreshUrl: '/refresh',
|
||||
requestClient: { get },
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
await (wrapper.vm as any).reload({ lightweight: true, silent: true });
|
||||
await flushPromises();
|
||||
|
||||
expect(get).toHaveBeenLastCalledWith('/page', {
|
||||
params: { pageNumber: 2, pageSize: 10 },
|
||||
});
|
||||
expect(wrapper.emitted('loadSuccess')?.at(-1)?.[0]).toMatchObject({
|
||||
lightweight: false,
|
||||
pageNumber: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('restores a mounted list to a new route state with one target request', async () => {
|
||||
const get = vi
|
||||
.fn()
|
||||
|
||||
@@ -10,6 +10,7 @@ import { getEmptyStateImageUrl } from '#/utils/assets';
|
||||
|
||||
interface PageDataProps {
|
||||
pageUrl: string;
|
||||
refreshUrl?: string;
|
||||
pageSize?: number;
|
||||
pageSizes?: number[];
|
||||
extraQueryParams?: Record<string, any>;
|
||||
@@ -29,23 +30,38 @@ interface PageDataRestoreState extends PageDataState {
|
||||
}
|
||||
|
||||
interface PageDataReloadOptions {
|
||||
lightweight?: boolean;
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
interface PageDataRequest {
|
||||
lightweight: boolean;
|
||||
silent: boolean;
|
||||
version: number;
|
||||
}
|
||||
|
||||
interface PageDataLoadEvent {
|
||||
lightweight: boolean;
|
||||
pageNumber: number;
|
||||
recordCount: number;
|
||||
}
|
||||
|
||||
interface PageDataLoadErrorEvent extends PageDataLoadEvent {
|
||||
error: unknown;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<PageDataProps>(), {
|
||||
pageSize: 10,
|
||||
pageSizes: () => [10, 20, 50, 100],
|
||||
refreshUrl: undefined,
|
||||
extraQueryParams: () => ({}),
|
||||
initialPageNumber: 1,
|
||||
initialQueryParams: () => ({}),
|
||||
requestClient: () => api,
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
(e: 'loadError', event: PageDataLoadErrorEvent): void;
|
||||
(e: 'loadSuccess', event: PageDataLoadEvent): void;
|
||||
(e: 'stateChange', state: PageDataState): void;
|
||||
}>();
|
||||
|
||||
@@ -67,10 +83,10 @@ const pageInfo = reactive({
|
||||
});
|
||||
|
||||
// 模拟 API 调用 - 这里需要根据你的实际 API 调用方式调整
|
||||
const doGet = async (params: Record<string, any>) => {
|
||||
const doGet = async (url: string, params: Record<string, any>) => {
|
||||
// 这里替换为你的实际 API 调用
|
||||
// 例如:return await api.get(props.pageUrl, { params })
|
||||
const response = await props.requestClient.get(`${props.pageUrl}`, {
|
||||
const response = await props.requestClient.get(url, {
|
||||
params,
|
||||
});
|
||||
const data = await response.data;
|
||||
@@ -78,14 +94,28 @@ const doGet = async (params: Record<string, any>) => {
|
||||
};
|
||||
|
||||
const loadPageListOnce = async (request: PageDataRequest) => {
|
||||
const lightweight = Boolean(
|
||||
request.lightweight && props.refreshUrl && pageInfo.pageNumber === 1,
|
||||
);
|
||||
try {
|
||||
const res = await doGet({
|
||||
const res = await doGet(lightweight ? props.refreshUrl! : props.pageUrl, {
|
||||
pageNumber: pageInfo.pageNumber,
|
||||
pageSize: pageInfo.pageSize,
|
||||
...props.extraQueryParams,
|
||||
...queryParams.value,
|
||||
});
|
||||
if (request.version === pageRequestVersion) {
|
||||
if (lightweight) {
|
||||
pageList.value = Array.isArray(res.data)
|
||||
? res.data
|
||||
: res.data?.records || [];
|
||||
emit('loadSuccess', {
|
||||
lightweight: true,
|
||||
pageNumber: pageInfo.pageNumber,
|
||||
recordCount: pageList.value.length,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const rawTotal = Number(res.data?.totalRow || 0);
|
||||
const total = Number.isFinite(rawTotal) ? Math.max(0, rawTotal) : 0;
|
||||
const lastPage = Math.max(1, Math.ceil(total / pageInfo.pageSize));
|
||||
@@ -96,6 +126,11 @@ const loadPageListOnce = async (request: PageDataRequest) => {
|
||||
return;
|
||||
}
|
||||
pageList.value = res.data?.records || [];
|
||||
emit('loadSuccess', {
|
||||
lightweight: false,
|
||||
pageNumber: pageInfo.pageNumber,
|
||||
recordCount: pageList.value.length,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (request.version === pageRequestVersion) {
|
||||
@@ -104,12 +139,24 @@ const loadPageListOnce = async (request: PageDataRequest) => {
|
||||
pageList.value = [];
|
||||
pageInfo.total = 0;
|
||||
}
|
||||
emit('loadError', {
|
||||
error,
|
||||
lightweight,
|
||||
pageNumber: pageInfo.pageNumber,
|
||||
recordCount: pageList.value.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const requestPageList = (silent: boolean) => {
|
||||
const requestPageList = (silent: boolean, lightweight = false) => {
|
||||
// 自动刷新只补充最新行;已有请求能够提供同等或更完整的数据时直接复用,
|
||||
// 避免它使正在进行的分页查询失效或额外排队。
|
||||
if (activePageRequest && lightweight) {
|
||||
return activePageRequest;
|
||||
}
|
||||
const request: PageDataRequest = {
|
||||
lightweight,
|
||||
silent: silent && pageList.value.length > 0,
|
||||
version: ++pageRequestVersion,
|
||||
};
|
||||
@@ -120,6 +167,7 @@ const requestPageList = (silent: boolean) => {
|
||||
if (activePageRequest) {
|
||||
pendingPageRequest = pendingPageRequest
|
||||
? {
|
||||
lightweight: pendingPageRequest.lightweight && request.lightweight,
|
||||
silent: pendingPageRequest.silent && request.silent,
|
||||
version: request.version,
|
||||
}
|
||||
@@ -143,10 +191,10 @@ const requestPageList = (silent: boolean) => {
|
||||
};
|
||||
|
||||
// 获取页面数据
|
||||
const getPageList = () => requestPageList(false);
|
||||
const getPageList = () => requestPageList(false, false);
|
||||
|
||||
const reload = (options: PageDataReloadOptions = {}) =>
|
||||
requestPageList(Boolean(options.silent));
|
||||
requestPageList(Boolean(options.silent), Boolean(options.lightweight));
|
||||
|
||||
// 分页事件处理
|
||||
const handleSizeChange = (newSize: number) => {
|
||||
|
||||
Reference in New Issue
Block a user