重构消息通知弹窗
This commit is contained in:
parent
ba9192e8bb
commit
9275f5d4ae
263
frontend/src/components/ui/paginated-table.tsx
Normal file
263
frontend/src/components/ui/paginated-table.tsx
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useState, useEffect, useRef, useCallback, useImperativeHandle } from 'react';
|
||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from './table';
|
||||||
|
import { DataTablePagination } from './pagination';
|
||||||
|
import type { Page } from '@/types/base';
|
||||||
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
|
|
||||||
|
// 列定义
|
||||||
|
export interface ColumnDef<T> {
|
||||||
|
/** 列标识 */
|
||||||
|
key: string;
|
||||||
|
/** 列标题 */
|
||||||
|
title: React.ReactNode;
|
||||||
|
/** 列宽度 */
|
||||||
|
width?: string;
|
||||||
|
/** 是否固定列 */
|
||||||
|
sticky?: boolean;
|
||||||
|
/** 自定义渲染 */
|
||||||
|
render?: (value: any, record: T, index: number) => React.ReactNode;
|
||||||
|
/** 数据字段路径,支持嵌套如 'user.name' */
|
||||||
|
dataIndex?: string;
|
||||||
|
/** 单元格类名 */
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询参数基础类型
|
||||||
|
export interface BaseQuery {
|
||||||
|
pageNum?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件 Props
|
||||||
|
export interface PaginatedTableProps<T, Q extends BaseQuery = BaseQuery> {
|
||||||
|
/** 数据获取函数 */
|
||||||
|
fetchFn: (query: Q) => Promise<Page<T>>;
|
||||||
|
/** 列定义 */
|
||||||
|
columns: ColumnDef<T>[];
|
||||||
|
/** 查询参数(不含分页) */
|
||||||
|
query?: Omit<Q, 'pageNum' | 'pageSize'>;
|
||||||
|
/** 行唯一标识字段 */
|
||||||
|
rowKey?: keyof T | ((record: T) => string | number);
|
||||||
|
/** 表格最小宽度 */
|
||||||
|
minWidth?: string;
|
||||||
|
/** 初始页大小 */
|
||||||
|
pageSize?: number;
|
||||||
|
/** 空数据提示 */
|
||||||
|
emptyText?: React.ReactNode;
|
||||||
|
/** 加载中提示 */
|
||||||
|
loadingText?: React.ReactNode;
|
||||||
|
/** 是否在 query 变化时自动刷新 */
|
||||||
|
autoRefresh?: boolean;
|
||||||
|
/** 额外的依赖项,变化时触发刷新 */
|
||||||
|
deps?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露给外部的方法
|
||||||
|
export interface PaginatedTableRef<T> {
|
||||||
|
/** 刷新数据 */
|
||||||
|
refresh: (showLoading?: boolean) => Promise<void>;
|
||||||
|
/** 重置到第一页并刷新 */
|
||||||
|
reset: () => void;
|
||||||
|
/** 获取当前数据 */
|
||||||
|
getData: () => Page<T> | null;
|
||||||
|
/** 局部更新某条记录 */
|
||||||
|
updateRecord: (id: string | number, updates: Partial<T>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取嵌套属性值
|
||||||
|
function getNestedValue(obj: any, path?: string): any {
|
||||||
|
if (!path) return undefined;
|
||||||
|
return path.split('.').reduce((acc, part) => acc?.[part], obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取行 key
|
||||||
|
function getRowKey<T>(record: T, rowKey: keyof T | ((record: T) => string | number) | undefined, index: number): string | number {
|
||||||
|
if (!rowKey) return index;
|
||||||
|
if (typeof rowKey === 'function') return rowKey(record);
|
||||||
|
return record[rowKey] as string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaginatedTableInner<T extends Record<string, any>, Q extends BaseQuery = BaseQuery>(
|
||||||
|
props: PaginatedTableProps<T, Q>,
|
||||||
|
ref: React.ForwardedRef<PaginatedTableRef<T>>
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
fetchFn,
|
||||||
|
columns,
|
||||||
|
query = {} as Omit<Q, 'pageNum' | 'pageSize'>,
|
||||||
|
rowKey,
|
||||||
|
minWidth,
|
||||||
|
pageSize: initialPageSize = DEFAULT_PAGE_SIZE,
|
||||||
|
emptyText = '暂无数据',
|
||||||
|
loadingText = '加载中...',
|
||||||
|
autoRefresh = true,
|
||||||
|
deps = [],
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [data, setData] = useState<Page<T> | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [pageNum, setPageNum] = useState(DEFAULT_PAGE_NUM);
|
||||||
|
const [pageSize] = useState(initialPageSize);
|
||||||
|
|
||||||
|
// 是否首次加载
|
||||||
|
const isFirstLoad = useRef(true);
|
||||||
|
// 是否需要显示 loading
|
||||||
|
const shouldShowLoading = useRef(true);
|
||||||
|
// 上一次的 query(用于检测变化)
|
||||||
|
const prevQueryRef = useRef(query);
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
const loadData = useCallback(async (showLoading = true) => {
|
||||||
|
if (showLoading) setLoading(true);
|
||||||
|
try {
|
||||||
|
const result = await fetchFn({
|
||||||
|
...query,
|
||||||
|
pageNum,
|
||||||
|
pageSize,
|
||||||
|
} as Q);
|
||||||
|
setData(result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('PaginatedTable 加载数据失败:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [fetchFn, query, pageNum, pageSize]);
|
||||||
|
|
||||||
|
// 监听分页变化
|
||||||
|
useEffect(() => {
|
||||||
|
loadData(shouldShowLoading.current || isFirstLoad.current);
|
||||||
|
isFirstLoad.current = false;
|
||||||
|
shouldShowLoading.current = false;
|
||||||
|
}, [pageNum, pageSize, ...deps]);
|
||||||
|
|
||||||
|
// 监听 query 变化(自动刷新)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!autoRefresh) return;
|
||||||
|
|
||||||
|
const queryChanged = JSON.stringify(query) !== JSON.stringify(prevQueryRef.current);
|
||||||
|
prevQueryRef.current = query;
|
||||||
|
|
||||||
|
if (queryChanged && !isFirstLoad.current) {
|
||||||
|
shouldShowLoading.current = true;
|
||||||
|
setPageNum(DEFAULT_PAGE_NUM);
|
||||||
|
}
|
||||||
|
}, [query, autoRefresh]);
|
||||||
|
|
||||||
|
// 分页切换
|
||||||
|
const handlePageChange = useCallback((newPageNum: number) => {
|
||||||
|
shouldShowLoading.current = false;
|
||||||
|
setPageNum(newPageNum);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 暴露方法给外部
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
refresh: async (showLoading = false) => {
|
||||||
|
shouldShowLoading.current = showLoading;
|
||||||
|
await loadData(showLoading);
|
||||||
|
},
|
||||||
|
reset: () => {
|
||||||
|
shouldShowLoading.current = true;
|
||||||
|
setPageNum(DEFAULT_PAGE_NUM);
|
||||||
|
},
|
||||||
|
getData: () => data,
|
||||||
|
updateRecord: (id, updates) => {
|
||||||
|
setData(prev => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
content: prev.content.map(item =>
|
||||||
|
getRowKey(item, rowKey, -1) === id ? { ...item, ...updates } : item
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}), [data, loadData, rowKey]);
|
||||||
|
|
||||||
|
// 渲染单元格内容
|
||||||
|
const renderCell = (column: ColumnDef<T>, record: T, index: number) => {
|
||||||
|
if (column.render) {
|
||||||
|
const value = column.dataIndex ? getNestedValue(record, column.dataIndex) : undefined;
|
||||||
|
return column.render(value, record, index);
|
||||||
|
}
|
||||||
|
if (column.dataIndex) {
|
||||||
|
return getNestedValue(record, column.dataIndex);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pageCount = data?.totalPages || 0;
|
||||||
|
const hasData = data && data.content && data.content.length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-0">
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table minWidth={minWidth}>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
{columns.map(col => (
|
||||||
|
<TableHead key={col.key} width={col.width} sticky={col.sticky}>
|
||||||
|
{col.title}
|
||||||
|
</TableHead>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{loading && isFirstLoad.current ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
|
<div className="flex items-center justify-center gap-2">
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
<span className="text-muted-foreground">{loadingText}</span>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : hasData ? (
|
||||||
|
data.content.map((record, index) => (
|
||||||
|
<TableRow key={getRowKey(record, rowKey, index)}>
|
||||||
|
{columns.map(col => (
|
||||||
|
<TableCell
|
||||||
|
key={col.key}
|
||||||
|
width={col.width}
|
||||||
|
sticky={col.sticky}
|
||||||
|
className={col.className}
|
||||||
|
>
|
||||||
|
{renderCell(col, record, index)}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
|
<span className="text-muted-foreground">{emptyText}</span>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{pageCount > 0 && (
|
||||||
|
<DataTablePagination
|
||||||
|
pageIndex={pageNum}
|
||||||
|
pageSize={pageSize}
|
||||||
|
pageCount={pageCount}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 forwardRef 包装,支持泛型
|
||||||
|
export const PaginatedTable = React.forwardRef(PaginatedTableInner) as <
|
||||||
|
T extends Record<string, any>,
|
||||||
|
Q extends BaseQuery = BaseQuery
|
||||||
|
>(
|
||||||
|
props: PaginatedTableProps<T, Q> & { ref?: React.ForwardedRef<PaginatedTableRef<T>> }
|
||||||
|
) => React.ReactElement;
|
||||||
|
|
||||||
|
export default PaginatedTable;
|
||||||
@ -1,71 +0,0 @@
|
|||||||
import { useState, useCallback } from 'react';
|
|
||||||
import type { Page } from '@/types/base/page';
|
|
||||||
|
|
||||||
interface UsePageDataProps<T, P = any> {
|
|
||||||
service: (params: P) => Promise<Page<T>>;
|
|
||||||
defaultParams?: Partial<P>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PageState<T> {
|
|
||||||
list: T[];
|
|
||||||
pagination: {
|
|
||||||
current: number;
|
|
||||||
pageSize: number;
|
|
||||||
total: number;
|
|
||||||
};
|
|
||||||
loading: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePageData<T, P = any>({ service, defaultParams }: UsePageDataProps<T, P>) {
|
|
||||||
const [state, setState] = useState<PageState<T>>({
|
|
||||||
list: [],
|
|
||||||
pagination: {
|
|
||||||
current: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const loadData = useCallback(async (params?: Partial<P>) => {
|
|
||||||
setState(prev => ({ ...prev, loading: true }));
|
|
||||||
try {
|
|
||||||
const pageData = await service({
|
|
||||||
...defaultParams,
|
|
||||||
...params,
|
|
||||||
pageNum: state.pagination.current,
|
|
||||||
pageSize: state.pagination.pageSize
|
|
||||||
} as P);
|
|
||||||
|
|
||||||
setState(prev => ({
|
|
||||||
list: pageData?.content || [],
|
|
||||||
pagination: {
|
|
||||||
current: prev.pagination.current,
|
|
||||||
pageSize: prev.pagination.pageSize,
|
|
||||||
total: pageData?.totalElements || 0
|
|
||||||
},
|
|
||||||
loading: false
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
|
||||||
setState(prev => ({ ...prev, loading: false }));
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}, [service, defaultParams, state.pagination]);
|
|
||||||
|
|
||||||
const onPageChange = useCallback((page: number, pageSize?: number) => {
|
|
||||||
setState(prev => ({
|
|
||||||
...prev,
|
|
||||||
pagination: {
|
|
||||||
...prev.pagination,
|
|
||||||
current: page,
|
|
||||||
pageSize: pageSize || prev.pagination.pageSize
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
loadData,
|
|
||||||
onPageChange
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,217 +0,0 @@
|
|||||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
||||||
import { message, Modal } from 'antd';
|
|
||||||
import type { Page } from '@/types/base/page';
|
|
||||||
import type { TablePaginationConfig } from 'antd/es/table';
|
|
||||||
import type { FilterValue, SorterResult } from 'antd/es/table/interface';
|
|
||||||
|
|
||||||
// 表格服务接口
|
|
||||||
export interface TableService<T, Q = any, C = Partial<T>, U = Partial<T>> {
|
|
||||||
list: (params: Q & { pageNum?: number; pageSize?: number }) => Promise<Page<T>>;
|
|
||||||
create?: (data: C) => Promise<T>;
|
|
||||||
update?: (id: number, data: U) => Promise<T>;
|
|
||||||
delete?: (id: number) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表格状态
|
|
||||||
interface TableState<T> {
|
|
||||||
list: T[];
|
|
||||||
pagination: TablePaginationConfig;
|
|
||||||
loading: boolean;
|
|
||||||
selectedRowKeys?: React.Key[];
|
|
||||||
selectedRows?: T[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表格配置
|
|
||||||
interface TableConfig {
|
|
||||||
defaultPageSize?: number;
|
|
||||||
selection?: boolean;
|
|
||||||
message?: {
|
|
||||||
createSuccess?: string;
|
|
||||||
updateSuccess?: string;
|
|
||||||
deleteSuccess?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTableData<
|
|
||||||
T extends { id: number },
|
|
||||||
Q extends Record<string, any> = any,
|
|
||||||
C = any,
|
|
||||||
U = any
|
|
||||||
>({
|
|
||||||
service,
|
|
||||||
defaultParams,
|
|
||||||
config = {}
|
|
||||||
}: {
|
|
||||||
service: TableService<T, Q, C, U>;
|
|
||||||
defaultParams?: Partial<Q>;
|
|
||||||
config?: TableConfig;
|
|
||||||
}) {
|
|
||||||
// 初始化状态
|
|
||||||
const [state, setState] = useState<TableState<T>>({
|
|
||||||
list: [],
|
|
||||||
pagination: {
|
|
||||||
current: 1,
|
|
||||||
pageSize: config.defaultPageSize || 10,
|
|
||||||
total: 0,
|
|
||||||
showSizeChanger: true,
|
|
||||||
showQuickJumper: true,
|
|
||||||
showTotal: (total) => `共 ${total} 条`
|
|
||||||
},
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// 使用 ref 存储当前分页参数,避免循环依赖
|
|
||||||
const paginationRef = useRef({
|
|
||||||
current: 1,
|
|
||||||
pageSize: config.defaultPageSize || 10
|
|
||||||
});
|
|
||||||
|
|
||||||
// 同步 ref 和 state
|
|
||||||
useEffect(() => {
|
|
||||||
paginationRef.current = {
|
|
||||||
current: state.pagination.current || 1,
|
|
||||||
pageSize: state.pagination.pageSize || 10
|
|
||||||
};
|
|
||||||
}, [state.pagination.current, state.pagination.pageSize]);
|
|
||||||
|
|
||||||
// 加载数据 - 修复:使用 ref 避免依赖 state
|
|
||||||
const loadData = useCallback(async (params?: Partial<Q> & { pageNum?: number; pageSize?: number }) => {
|
|
||||||
setState(prev => ({ ...prev, loading: true }));
|
|
||||||
try {
|
|
||||||
const pageData = await service.list({
|
|
||||||
...defaultParams,
|
|
||||||
...params,
|
|
||||||
pageNum: params?.pageNum ?? paginationRef.current.current,
|
|
||||||
pageSize: params?.pageSize ?? paginationRef.current.pageSize
|
|
||||||
} as Q & { pageNum?: number; pageSize?: number });
|
|
||||||
|
|
||||||
setState(prev => ({
|
|
||||||
...prev,
|
|
||||||
list: pageData.content || [],
|
|
||||||
pagination: {
|
|
||||||
...prev.pagination,
|
|
||||||
total: pageData.totalElements || 0
|
|
||||||
},
|
|
||||||
loading: false
|
|
||||||
}));
|
|
||||||
return pageData;
|
|
||||||
} catch (error) {
|
|
||||||
setState(prev => ({ ...prev, loading: false }));
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}, [service, defaultParams]);
|
|
||||||
|
|
||||||
// 表格变化处理
|
|
||||||
const handleTableChange = (
|
|
||||||
pagination: TablePaginationConfig,
|
|
||||||
filters: Record<string, FilterValue | null>,
|
|
||||||
sorter: SorterResult<T> | SorterResult<T>[]
|
|
||||||
) => {
|
|
||||||
const { current, pageSize } = pagination;
|
|
||||||
setState(prev => ({
|
|
||||||
...prev,
|
|
||||||
pagination: {
|
|
||||||
...prev.pagination,
|
|
||||||
current,
|
|
||||||
pageSize
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const params: Record<string, any> = {};
|
|
||||||
|
|
||||||
// 处理排序
|
|
||||||
if (!Array.isArray(sorter)) {
|
|
||||||
const { field, order } = sorter;
|
|
||||||
if (field && order) {
|
|
||||||
params.sortField = field as string;
|
|
||||||
params.sortOrder = order === 'ascend' ? 'asc' : 'desc';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理筛选
|
|
||||||
Object.entries(filters).forEach(([key, value]) => {
|
|
||||||
if (value) {
|
|
||||||
params[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
loadData(params as Partial<Q>);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建
|
|
||||||
const handleCreate = async (data: C) => {
|
|
||||||
if (!service.create) return false;
|
|
||||||
try {
|
|
||||||
await service.create(data);
|
|
||||||
message.success(config.message?.createSuccess || '创建成功');
|
|
||||||
loadData();
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 更新
|
|
||||||
const handleUpdate = async (id: number, data: U) => {
|
|
||||||
if (!service.update) return false;
|
|
||||||
try {
|
|
||||||
await service.update(id, data);
|
|
||||||
message.success(config.message?.updateSuccess || '更新成功');
|
|
||||||
loadData();
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除
|
|
||||||
const handleDelete = async (id: number) => {
|
|
||||||
if (!service.delete) return false;
|
|
||||||
return new Promise<boolean>((resolve, reject) => {
|
|
||||||
Modal.confirm({
|
|
||||||
title: '确认删除',
|
|
||||||
content: '确定要删除该记录吗?',
|
|
||||||
okText: '确定',
|
|
||||||
okType: 'danger',
|
|
||||||
cancelText: '取消',
|
|
||||||
onOk: async () => {
|
|
||||||
try {
|
|
||||||
await service.delete(id);
|
|
||||||
message.success(config.message?.deleteSuccess || '删除成功');
|
|
||||||
loadData();
|
|
||||||
resolve(true);
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onCancel: () => resolve(false)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 选择行
|
|
||||||
const handleSelectChange = (selectedRowKeys: React.Key[], selectedRows: T[]) => {
|
|
||||||
if (!config.selection) return;
|
|
||||||
setState(prev => ({
|
|
||||||
...prev,
|
|
||||||
selectedRowKeys,
|
|
||||||
selectedRows
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 监听分页变化自动加载数据
|
|
||||||
useEffect(() => {
|
|
||||||
loadData();
|
|
||||||
}, [state.pagination.current, state.pagination.pageSize]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
loadData,
|
|
||||||
handleTableChange,
|
|
||||||
handleCreate,
|
|
||||||
handleUpdate,
|
|
||||||
handleDelete,
|
|
||||||
handleSelectChange,
|
|
||||||
refresh: () => loadData()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -30,7 +30,7 @@ import { Switch } from "@/components/ui/switch";
|
|||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Edit,
|
Edit,
|
||||||
@ -81,7 +81,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
const [deleteRecord, setDeleteRecord] = useState<ApplicationCategoryResponse | null>(null);
|
const [deleteRecord, setDeleteRecord] = useState<ApplicationCategoryResponse | null>(null);
|
||||||
|
|
||||||
// 分页状态
|
// 分页状态
|
||||||
const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1);
|
const [pageNum, setPageNum] = useState(DEFAULT_PAGE_NUM);
|
||||||
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
const form = useForm<ApplicationCategoryRequest>({
|
const form = useForm<ApplicationCategoryRequest>({
|
||||||
@ -336,7 +336,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
{data && data.totalElements > 0 && (
|
{data && data.totalElements > 0 && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={pageNum + 1}
|
pageIndex={pageNum}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
pageCount={Math.ceil((data.totalElements || 0) / pageSize)}
|
pageCount={Math.ceil((data.totalElements || 0) / pageSize)}
|
||||||
onPageChange={(page) => setPageNum(page - 1)}
|
onPageChange={(page) => setPageNum(page - 1)}
|
||||||
|
|||||||
@ -68,7 +68,7 @@ const ApplicationList: React.FC = () => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [selectedCategoryId, setSelectedCategoryId] = useState<number>();
|
const [selectedCategoryId, setSelectedCategoryId] = useState<number>();
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
pageNum: 1,
|
pageNum: 0,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
totalElements: 0,
|
totalElements: 0,
|
||||||
});
|
});
|
||||||
@ -374,7 +374,7 @@ const ApplicationList: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
setSelectedCategoryId(Number(value));
|
setSelectedCategoryId(Number(value));
|
||||||
}
|
}
|
||||||
setPagination({ ...pagination, pageNum: 1 });
|
setPagination({ ...pagination, pageNum: 0 });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="max-w-[200px]">
|
<SelectTrigger className="max-w-[200px]">
|
||||||
|
|||||||
@ -32,7 +32,7 @@ const EnvironmentList: React.FC = () => {
|
|||||||
const [list, setList] = useState<Environment[]>([]);
|
const [list, setList] = useState<Environment[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [query, setQuery] = useState<EnvironmentQueryParams>({
|
const [query, setQuery] = useState<EnvironmentQueryParams>({
|
||||||
pageNum: 1,
|
pageNum: 0,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
});
|
});
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
@ -72,13 +72,13 @@ const EnvironmentList: React.FC = () => {
|
|||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
setQuery({ ...query, pageNum: 1 });
|
setQuery({ ...query, pageNum: 0 });
|
||||||
loadData();
|
loadData();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置
|
// 重置
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setQuery({ pageNum: 1, pageSize: DEFAULT_PAGE_SIZE });
|
setQuery({ pageNum: 0, pageSize: DEFAULT_PAGE_SIZE });
|
||||||
loadData();
|
loadData();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -245,7 +245,7 @@ const EnvironmentList: React.FC = () => {
|
|||||||
</Table>
|
</Table>
|
||||||
<div className="flex justify-end border-t border-border bg-muted/40">
|
<div className="flex justify-end border-t border-border bg-muted/40">
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={query.pageNum || 1}
|
pageIndex={query.pageNum || 0}
|
||||||
pageSize={query.pageSize || DEFAULT_PAGE_SIZE}
|
pageSize={query.pageSize || DEFAULT_PAGE_SIZE}
|
||||||
pageCount={Math.ceil(total / (query.pageSize || DEFAULT_PAGE_SIZE))}
|
pageCount={Math.ceil(total / (query.pageSize || DEFAULT_PAGE_SIZE))}
|
||||||
onPageChange={(page) => setQuery({ ...query, pageNum: page })}
|
onPageChange={(page) => setQuery({ ...query, pageNum: page })}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Search,
|
Search,
|
||||||
@ -78,7 +78,7 @@ const NotificationChannelList: React.FC = () => {
|
|||||||
|
|
||||||
// 分页
|
// 分页
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
pageNum: DEFAULT_CURRENT,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
totalElements: 0,
|
totalElements: 0,
|
||||||
});
|
});
|
||||||
@ -126,7 +126,7 @@ const NotificationChannelList: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const res = await getChannelsPage({
|
const res = await getChannelsPage({
|
||||||
...query,
|
...query,
|
||||||
page: pagination.pageNum - 1, // 后端从0开始,前端从1开始
|
page: pagination.pageNum,
|
||||||
size: pagination.pageSize,
|
size: pagination.pageSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ const NotificationChannelList: React.FC = () => {
|
|||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
setPagination((prev) => ({ ...prev, pageNum: DEFAULT_CURRENT }));
|
setPagination((prev) => ({ ...prev, pageNum: DEFAULT_PAGE_NUM }));
|
||||||
loadData();
|
loadData();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ const NotificationChannelList: React.FC = () => {
|
|||||||
enabled: undefined,
|
enabled: undefined,
|
||||||
});
|
});
|
||||||
setPagination({
|
setPagination({
|
||||||
pageNum: DEFAULT_CURRENT,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
totalElements: 0,
|
totalElements: 0,
|
||||||
});
|
});
|
||||||
@ -168,7 +168,7 @@ const NotificationChannelList: React.FC = () => {
|
|||||||
|
|
||||||
// 分页切换
|
// 分页切换
|
||||||
const handlePageChange = (newPage: number) => {
|
const handlePageChange = (newPage: number) => {
|
||||||
setPagination({ ...pagination, pageNum: newPage + 1 });
|
setPagination({ ...pagination, pageNum: newPage });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建
|
// 创建
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Search,
|
Search,
|
||||||
@ -88,7 +88,7 @@ const NotificationTemplateList: React.FC = () => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [list, setList] = useState<NotificationTemplateDTO[]>([]);
|
const [list, setList] = useState<NotificationTemplateDTO[]>([]);
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
pageNum: DEFAULT_CURRENT,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
totalElements: 0,
|
totalElements: 0,
|
||||||
});
|
});
|
||||||
@ -181,7 +181,7 @@ const NotificationTemplateList: React.FC = () => {
|
|||||||
|
|
||||||
// 搜索处理
|
// 搜索处理
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
setPagination(prev => ({ ...prev, pageNum: DEFAULT_CURRENT }));
|
setPagination(prev => ({ ...prev, pageNum: DEFAULT_PAGE_NUM }));
|
||||||
loadData();
|
loadData();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import { Switch } from '@/components/ui/switch';
|
|||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Edit,
|
Edit,
|
||||||
@ -79,7 +79,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
const [deleteRecord, setDeleteRecord] = useState<JobCategoryResponse | null>(null);
|
const [deleteRecord, setDeleteRecord] = useState<JobCategoryResponse | null>(null);
|
||||||
|
|
||||||
// 分页状态
|
// 分页状态
|
||||||
const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1);
|
const [pageNum, setPageNum] = useState(DEFAULT_PAGE_NUM);
|
||||||
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
const form = useForm<JobCategoryRequest>({
|
const form = useForm<JobCategoryRequest>({
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import {
|
import {
|
||||||
@ -52,7 +52,7 @@ const JobLogDialog: React.FC<JobLogDialogProps> = ({
|
|||||||
const [data, setData] = useState<Page<ScheduleJobLogResponse> | null>(null);
|
const [data, setData] = useState<Page<ScheduleJobLogResponse> | null>(null);
|
||||||
const [selectedLog, setSelectedLog] = useState<ScheduleJobLogResponse | null>(null);
|
const [selectedLog, setSelectedLog] = useState<ScheduleJobLogResponse | null>(null);
|
||||||
const [query, setQuery] = useState<ScheduleJobLogQuery>({
|
const [query, setQuery] = useState<ScheduleJobLogQuery>({
|
||||||
pageNum: DEFAULT_CURRENT - 1,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
jobId: undefined,
|
jobId: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
|
|||||||
@ -1,21 +1,19 @@
|
|||||||
import React, {useState, useEffect, useMemo} from 'react';
|
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
import {Card, CardHeader, CardTitle, CardContent} from '@/components/ui/card';
|
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
|
||||||
import {Table, TableHeader, TableBody, TableRow, TableHead, TableCell} from '@/components/ui/table';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import {Badge} from '@/components/ui/badge';
|
import { Button } from '@/components/ui/button';
|
||||||
import {Button} from '@/components/ui/button';
|
import { Input } from '@/components/ui/input';
|
||||||
import {Input} from '@/components/ui/input';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import {DataTablePagination} from '@/components/ui/pagination';
|
|
||||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from '@/components/ui/tabs';
|
|
||||||
import {
|
import {
|
||||||
Loader2, Plus, Search, Edit, Trash2, Play, Pause,
|
Plus, Search, Edit, Trash2, Play, Pause,
|
||||||
Clock, Activity, CheckCircle2, XCircle, FolderKanban, PlayCircle, FileText, BarChart3, List, Ban
|
Clock, Activity, CheckCircle2, XCircle, FolderKanban, PlayCircle, FileText, BarChart3, List, Ban
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {useToast} from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import {getScheduleJobs, getJobCategoryList, pauseJob, resumeJob, triggerJob, disableJob, deleteScheduleJob, enableJob} from './service';
|
import { PaginatedTable, type ColumnDef, type PaginatedTableRef } from '@/components/ui/paginated-table';
|
||||||
import type {ScheduleJobResponse, ScheduleJobQuery, JobCategoryResponse, JobStatus} from './types';
|
import { getScheduleJobs, getJobCategoryList, pauseJob, resumeJob, triggerJob, disableJob, deleteScheduleJob, enableJob } from './service';
|
||||||
import type {Page} from '@/types/base';
|
import type { ScheduleJobResponse, ScheduleJobQuery, JobCategoryResponse, JobStatus } from './types';
|
||||||
import {DEFAULT_PAGE_SIZE, DEFAULT_CURRENT} from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE } from '@/utils/page';
|
||||||
import CategoryManageDialog from './components/CategoryManageDialog';
|
import CategoryManageDialog from './components/CategoryManageDialog';
|
||||||
import JobLogDialog from './components/JobLogDialog';
|
import JobLogDialog from './components/JobLogDialog';
|
||||||
import JobEditDialog from './components/JobEditDialog';
|
import JobEditDialog from './components/JobEditDialog';
|
||||||
@ -27,10 +25,20 @@ import dayjs from 'dayjs';
|
|||||||
* 定时任务列表页
|
* 定时任务列表页
|
||||||
*/
|
*/
|
||||||
const ScheduleJobList: React.FC = () => {
|
const ScheduleJobList: React.FC = () => {
|
||||||
const {toast} = useToast();
|
const { toast } = useToast();
|
||||||
const [loading, setLoading] = useState(false);
|
const tableRef = useRef<PaginatedTableRef<ScheduleJobResponse>>(null);
|
||||||
const [data, setData] = useState<Page<ScheduleJobResponse> | null>(null);
|
|
||||||
|
// 分类数据
|
||||||
const [categories, setCategories] = useState<JobCategoryResponse[]>([]);
|
const [categories, setCategories] = useState<JobCategoryResponse[]>([]);
|
||||||
|
|
||||||
|
// 查询条件(不含分页)
|
||||||
|
const [searchParams, setSearchParams] = useState({
|
||||||
|
jobName: '',
|
||||||
|
categoryId: undefined as number | undefined,
|
||||||
|
status: undefined as JobStatus | undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 弹窗状态
|
||||||
const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
|
const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
|
||||||
const [logDialogOpen, setLogDialogOpen] = useState(false);
|
const [logDialogOpen, setLogDialogOpen] = useState(false);
|
||||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||||
@ -38,39 +46,6 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
const [selectedJob, setSelectedJob] = useState<ScheduleJobResponse | null>(null);
|
const [selectedJob, setSelectedJob] = useState<ScheduleJobResponse | null>(null);
|
||||||
const [editJob, setEditJob] = useState<ScheduleJobResponse | null>(null);
|
const [editJob, setEditJob] = useState<ScheduleJobResponse | null>(null);
|
||||||
const [deleteJob, setDeleteJob] = useState<ScheduleJobResponse | null>(null);
|
const [deleteJob, setDeleteJob] = useState<ScheduleJobResponse | null>(null);
|
||||||
const [query, setQuery] = useState<ScheduleJobQuery>({
|
|
||||||
pageNum: DEFAULT_CURRENT,
|
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
|
||||||
jobName: '',
|
|
||||||
categoryId: undefined,
|
|
||||||
status: undefined
|
|
||||||
});
|
|
||||||
|
|
||||||
// 加载数据
|
|
||||||
const loadData = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const result = await getScheduleJobs(query);
|
|
||||||
setData(result);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载定时任务失败:', error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 局部更新单条记录(避免全量刷新导致滚动位置丢失)
|
|
||||||
const updateRecordInList = (id: number, updates: Partial<ScheduleJobResponse>) => {
|
|
||||||
setData(prevData => {
|
|
||||||
if (!prevData) return prevData;
|
|
||||||
return {
|
|
||||||
...prevData,
|
|
||||||
content: prevData.content.map(item =>
|
|
||||||
item.id === id ? { ...item, ...updates } : item
|
|
||||||
)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 加载分类
|
// 加载分类
|
||||||
const loadCategories = async () => {
|
const loadCategories = async () => {
|
||||||
@ -86,214 +61,162 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
loadCategories();
|
loadCategories();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadData();
|
|
||||||
}, [query]);
|
|
||||||
|
|
||||||
// 搜索
|
|
||||||
const handleSearch = () => {
|
|
||||||
setQuery(prev => ({...prev, pageNum: 1}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 重置
|
|
||||||
const handleReset = () => {
|
|
||||||
setQuery({
|
|
||||||
pageNum: 1,
|
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
|
||||||
jobName: '',
|
|
||||||
categoryId: undefined,
|
|
||||||
status: undefined
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 新建任务
|
|
||||||
const handleCreate = () => {
|
|
||||||
setEditJob(null);
|
|
||||||
setEditDialogOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 编辑任务
|
|
||||||
const handleEdit = (record: ScheduleJobResponse) => {
|
|
||||||
setEditJob(record);
|
|
||||||
setEditDialogOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 打开删除确认对话框
|
|
||||||
const handleDeleteClick = (record: ScheduleJobResponse) => {
|
|
||||||
setDeleteJob(record);
|
|
||||||
setDeleteDialogOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 确认删除
|
|
||||||
const confirmDelete = async () => {
|
|
||||||
if (!deleteJob) return;
|
|
||||||
await deleteScheduleJob(deleteJob.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 暂停任务
|
|
||||||
const handlePause = async (record: ScheduleJobResponse) => {
|
|
||||||
try {
|
|
||||||
await pauseJob(record.id);
|
|
||||||
toast({
|
|
||||||
title: '暂停成功',
|
|
||||||
description: `任务 "${record.jobName}" 已暂停`,
|
|
||||||
});
|
|
||||||
// 局部更新,避免页面滚动
|
|
||||||
updateRecordInList(record.id, { status: 'PAUSED' });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('暂停失败:', error);
|
|
||||||
toast({
|
|
||||||
variant: 'destructive',
|
|
||||||
title: '暂停失败',
|
|
||||||
description: error instanceof Error ? error.message : '未知错误',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 恢复任务
|
|
||||||
const handleResume = async (record: ScheduleJobResponse) => {
|
|
||||||
try {
|
|
||||||
await resumeJob(record.id);
|
|
||||||
toast({
|
|
||||||
title: '恢复成功',
|
|
||||||
description: `任务 "${record.jobName}" 已恢复`,
|
|
||||||
});
|
|
||||||
// 局部更新,避免页面滚动
|
|
||||||
updateRecordInList(record.id, { status: 'ENABLED' });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('恢复失败:', error);
|
|
||||||
toast({
|
|
||||||
variant: 'destructive',
|
|
||||||
title: '恢复失败',
|
|
||||||
description: error instanceof Error ? error.message : '未知错误',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 立即触发任务
|
|
||||||
const handleTrigger = async (record: ScheduleJobResponse) => {
|
|
||||||
try {
|
|
||||||
await triggerJob(record.id);
|
|
||||||
toast({
|
|
||||||
title: '触发成功',
|
|
||||||
description: `任务 "${record.jobName}" 已立即触发执行`,
|
|
||||||
});
|
|
||||||
// 立即执行不改变任务状态,无需更新列表
|
|
||||||
} catch (error) {
|
|
||||||
console.error('触发失败:', error);
|
|
||||||
toast({
|
|
||||||
variant: 'destructive',
|
|
||||||
title: '触发失败',
|
|
||||||
description: error instanceof Error ? error.message : '未知错误',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 禁用任务
|
|
||||||
const handleDisable = async (record: ScheduleJobResponse) => {
|
|
||||||
try {
|
|
||||||
await disableJob(record.id);
|
|
||||||
toast({
|
|
||||||
title: '禁用成功',
|
|
||||||
description: `任务 "${record.jobName}" 已禁用`,
|
|
||||||
});
|
|
||||||
// 局部更新,避免页面滚动
|
|
||||||
updateRecordInList(record.id, { status: 'DISABLED' });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('禁用失败:', error);
|
|
||||||
toast({
|
|
||||||
variant: 'destructive',
|
|
||||||
title: '禁用失败',
|
|
||||||
description: error instanceof Error ? error.message : '未知错误',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 启用(解除禁用)任务
|
|
||||||
const handleEnable = async (record: ScheduleJobResponse) => {
|
|
||||||
try {
|
|
||||||
await enableJob(record.id);
|
|
||||||
toast({
|
|
||||||
title: '启用成功',
|
|
||||||
description: `任务 "${record.jobName}" 已启用`,
|
|
||||||
});
|
|
||||||
// 局部更新,避免页面滚动
|
|
||||||
updateRecordInList(record.id, { status: 'ENABLED' });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('启用失败:', error);
|
|
||||||
toast({
|
|
||||||
variant: 'destructive',
|
|
||||||
title: '启用失败',
|
|
||||||
description: error instanceof Error ? error.message : '未知错误',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 查看日志
|
|
||||||
const handleViewLog = (record: ScheduleJobResponse) => {
|
|
||||||
setSelectedJob(record);
|
|
||||||
setLogDialogOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 状态徽章
|
// 状态徽章
|
||||||
const getStatusBadge = (status: JobStatus) => {
|
const getStatusBadge = (status: JobStatus) => {
|
||||||
const statusMap: Record<JobStatus, {
|
const statusMap: Record<JobStatus, { variant: 'default' | 'secondary' | 'destructive' | 'outline'; text: string; icon: React.ElementType }> = {
|
||||||
variant: 'default' | 'secondary' | 'destructive' | 'outline';
|
ENABLED: { variant: 'default', text: '启用', icon: CheckCircle2 },
|
||||||
text: string;
|
DISABLED: { variant: 'secondary', text: '禁用', icon: XCircle },
|
||||||
icon: React.ElementType
|
PAUSED: { variant: 'outline', text: '暂停', icon: Pause },
|
||||||
}> = {
|
|
||||||
ENABLED: {variant: 'default', text: '启用', icon: CheckCircle2},
|
|
||||||
DISABLED: {variant: 'secondary', text: '禁用', icon: XCircle},
|
|
||||||
PAUSED: {variant: 'outline', text: '暂停', icon: Pause},
|
|
||||||
};
|
};
|
||||||
const statusInfo = statusMap[status] || {variant: 'outline', text: status, icon: Clock};
|
const statusInfo = statusMap[status] || { variant: 'outline', text: status, icon: Clock };
|
||||||
const Icon = statusInfo.icon;
|
const Icon = statusInfo.icon;
|
||||||
return (
|
return (
|
||||||
<Badge variant={statusInfo.variant} className="inline-flex items-center gap-1">
|
<Badge variant={statusInfo.variant} className="inline-flex items-center gap-1">
|
||||||
<Icon className="h-3 w-3"/>
|
<Icon className="h-3 w-3" />
|
||||||
{statusInfo.text}
|
{statusInfo.text}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 操作处理函数
|
||||||
|
const handlePause = async (record: ScheduleJobResponse) => {
|
||||||
|
try {
|
||||||
|
await pauseJob(record.id);
|
||||||
|
toast({ title: '暂停成功', description: `任务 "${record.jobName}" 已暂停` });
|
||||||
|
tableRef.current?.updateRecord(record.id, { status: 'PAUSED' });
|
||||||
|
} catch (error) {
|
||||||
|
toast({ variant: 'destructive', title: '暂停失败', description: error instanceof Error ? error.message : '未知错误' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResume = async (record: ScheduleJobResponse) => {
|
||||||
|
try {
|
||||||
|
await resumeJob(record.id);
|
||||||
|
toast({ title: '恢复成功', description: `任务 "${record.jobName}" 已恢复` });
|
||||||
|
tableRef.current?.updateRecord(record.id, { status: 'ENABLED' });
|
||||||
|
} catch (error) {
|
||||||
|
toast({ variant: 'destructive', title: '恢复失败', description: error instanceof Error ? error.message : '未知错误' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTrigger = async (record: ScheduleJobResponse) => {
|
||||||
|
try {
|
||||||
|
await triggerJob(record.id);
|
||||||
|
toast({ title: '触发成功', description: `任务 "${record.jobName}" 已立即触发执行` });
|
||||||
|
} catch (error) {
|
||||||
|
toast({ variant: 'destructive', title: '触发失败', description: error instanceof Error ? error.message : '未知错误' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDisable = async (record: ScheduleJobResponse) => {
|
||||||
|
try {
|
||||||
|
await disableJob(record.id);
|
||||||
|
toast({ title: '禁用成功', description: `任务 "${record.jobName}" 已禁用` });
|
||||||
|
tableRef.current?.updateRecord(record.id, { status: 'DISABLED' });
|
||||||
|
} catch (error) {
|
||||||
|
toast({ variant: 'destructive', title: '禁用失败', description: error instanceof Error ? error.message : '未知错误' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEnable = async (record: ScheduleJobResponse) => {
|
||||||
|
try {
|
||||||
|
await enableJob(record.id);
|
||||||
|
toast({ title: '启用成功', description: `任务 "${record.jobName}" 已启用` });
|
||||||
|
tableRef.current?.updateRecord(record.id, { status: 'ENABLED' });
|
||||||
|
} catch (error) {
|
||||||
|
toast({ variant: 'destructive', title: '启用失败', description: error instanceof Error ? error.message : '未知错误' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 列定义
|
||||||
|
const columns: ColumnDef<ScheduleJobResponse>[] = useMemo(() => [
|
||||||
|
{ key: 'jobName', title: '任务名称', width: '180px', dataIndex: 'jobName', className: 'font-medium whitespace-nowrap' },
|
||||||
|
{ key: 'category', title: '任务分类', width: '120px', className: 'whitespace-nowrap', render: (_, record) => record.category?.name || '-' },
|
||||||
|
{ key: 'cronExpression', title: 'Cron表达式', width: '150px', className: 'whitespace-nowrap', render: (_, record) => <code className="text-xs bg-muted px-1 py-0.5 rounded">{record.cronExpression}</code> },
|
||||||
|
{ key: 'beanName', title: 'Bean名称', width: '120px', className: 'whitespace-nowrap', render: (_, record) => <code className="text-xs">{record.beanName}</code> },
|
||||||
|
{ key: 'methodName', title: '方法名称', width: '120px', className: 'whitespace-nowrap', render: (_, record) => <code className="text-xs">{record.methodName}</code> },
|
||||||
|
{ key: 'status', title: '状态', width: '100px', className: 'whitespace-nowrap', render: (_, record) => getStatusBadge(record.status) },
|
||||||
|
{ key: 'lastExecuteTime', title: '上次执行', width: '160px', className: 'text-xs text-muted-foreground whitespace-nowrap', render: (_, record) => record.lastExecuteTime ? dayjs(record.lastExecuteTime).format('YYYY-MM-DD HH:mm:ss') : '-' },
|
||||||
|
{ key: 'nextExecuteTime', title: '下次执行', width: '160px', className: 'text-xs text-muted-foreground whitespace-nowrap', render: (_, record) => record.nextExecuteTime ? dayjs(record.nextExecuteTime).format('YYYY-MM-DD HH:mm:ss') : '-' },
|
||||||
|
{ key: 'executeCount', title: '执行次数', width: '100px', className: 'text-center whitespace-nowrap', render: (_, record) => <Badge variant="outline">{record.executeCount || 0}</Badge> },
|
||||||
|
{
|
||||||
|
key: 'successRate', title: '成功率', width: '90px', className: 'whitespace-nowrap',
|
||||||
|
render: (_, record) => {
|
||||||
|
if (record.executeCount && record.executeCount > 0) {
|
||||||
|
const rate = record.successCount! / record.executeCount;
|
||||||
|
return <Badge variant={rate >= 0.9 ? 'default' : rate >= 0.7 ? 'outline' : 'destructive'}>{(rate * 100).toFixed(1)}%</Badge>;
|
||||||
|
}
|
||||||
|
return <span className="text-muted-foreground">-</span>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'actions', title: '操作', width: '240px', sticky: true,
|
||||||
|
render: (_, record) => (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{record.status === 'ENABLED' && (
|
||||||
|
<>
|
||||||
|
<Button type="button" variant="ghost" size="icon" className="h-7 w-7" onClick={() => handlePause(record)} title="暂停"><Pause className="h-4 w-4" /></Button>
|
||||||
|
<Button type="button" variant="ghost" size="icon" className="h-7 w-7 text-orange-600 hover:text-orange-700 hover:bg-orange-50 dark:text-orange-500 dark:hover:bg-orange-950/20" onClick={() => handleDisable(record)} title="禁用"><Ban className="h-4 w-4" /></Button>
|
||||||
|
<Button type="button" variant="ghost" size="icon" className="h-7 w-7 text-green-600 hover:text-green-700 hover:bg-green-50 dark:text-green-500 dark:hover:bg-green-950/20" onClick={() => handleTrigger(record)} title="立即执行"><PlayCircle className="h-4 w-4" /></Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{record.status === 'PAUSED' && (
|
||||||
|
<>
|
||||||
|
<Button type="button" variant="ghost" size="icon" className="h-7 w-7" onClick={() => handleResume(record)} title="恢复"><Play className="h-4 w-4" /></Button>
|
||||||
|
<Button type="button" variant="ghost" size="icon" className="h-7 w-7 text-orange-600 hover:text-orange-700 hover:bg-orange-50 dark:text-orange-500 dark:hover:bg-orange-950/20" onClick={() => handleDisable(record)} title="禁用"><Ban className="h-4 w-4" /></Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{record.status === 'DISABLED' && (
|
||||||
|
<Button type="button" variant="ghost" size="icon" className="h-7 w-7 text-blue-600 hover:text-blue-700 hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-950/20" onClick={() => handleEnable(record)} title="启用"><CheckCircle2 className="h-4 w-4" /></Button>
|
||||||
|
)}
|
||||||
|
<Button type="button" variant="ghost" size="icon" className="h-7 w-7 text-destructive hover:text-destructive" onClick={() => { setDeleteJob(record); setDeleteDialogOpen(true); }} title="删除"><Trash2 className="h-4 w-4" /></Button>
|
||||||
|
<Button type="button" variant="ghost" size="icon" className="h-7 w-7" onClick={() => { setSelectedJob(record); setLogDialogOpen(true); }} title="查看日志"><FileText className="h-4 w-4" /></Button>
|
||||||
|
<Button type="button" variant="ghost" size="icon" className="h-7 w-7" onClick={() => { setEditJob(record); setEditDialogOpen(true); }} title="编辑"><Edit className="h-4 w-4" /></Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
], []);
|
||||||
|
|
||||||
// 统计数据
|
// 统计数据
|
||||||
const stats = useMemo(() => {
|
const stats = useMemo(() => {
|
||||||
|
const data = tableRef.current?.getData();
|
||||||
const total = data?.totalElements || 0;
|
const total = data?.totalElements || 0;
|
||||||
const enabledCount = data?.content?.filter(d => d.status === 'ENABLED').length || 0;
|
const enabledCount = data?.content?.filter(d => d.status === 'ENABLED').length || 0;
|
||||||
const pausedCount = data?.content?.filter(d => d.status === 'PAUSED').length || 0;
|
const pausedCount = data?.content?.filter(d => d.status === 'PAUSED').length || 0;
|
||||||
return {total, enabledCount, pausedCount};
|
return { total, enabledCount, pausedCount };
|
||||||
}, [data]);
|
}, [tableRef.current?.getData()]);
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
tableRef.current?.reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const handleReset = () => {
|
||||||
|
setSearchParams({ jobName: '', categoryId: undefined, status: undefined });
|
||||||
|
tableRef.current?.reset();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<h1 className="text-3xl font-bold text-foreground">定时任务管理</h1>
|
<h1 className="text-3xl font-bold text-foreground">定时任务管理</h1>
|
||||||
<p className="text-muted-foreground mt-2">
|
<p className="text-muted-foreground mt-2">创建和管理定时任务,配置执行计划和参数。</p>
|
||||||
创建和管理定时任务,配置执行计划和参数。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 视图切换 */}
|
|
||||||
<Tabs defaultValue="list" className="space-y-6">
|
<Tabs defaultValue="list" className="space-y-6">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="list" className="gap-2">
|
<TabsTrigger value="list" className="gap-2"><List className="h-4 w-4" />列表视图</TabsTrigger>
|
||||||
<List className="h-4 w-4"/>
|
<TabsTrigger value="dashboard" className="gap-2"><BarChart3 className="h-4 w-4" />仪表盘</TabsTrigger>
|
||||||
列表视图
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="dashboard" className="gap-2">
|
|
||||||
<BarChart3 className="h-4 w-4"/>
|
|
||||||
仪表盘
|
|
||||||
</TabsTrigger>
|
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{/* 列表视图 */}
|
|
||||||
<TabsContent value="list" className="space-y-6">
|
<TabsContent value="list" className="space-y-6">
|
||||||
{/* 统计卡片 */}
|
{/* 统计卡片 */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
<Card className="bg-gradient-to-br from-blue-500/10 to-blue-500/5 border-blue-500/20">
|
<Card className="bg-gradient-to-br from-blue-500/10 to-blue-500/5 border-blue-500/20">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-blue-700">总任务</CardTitle>
|
<CardTitle className="text-sm font-medium text-blue-700">总任务</CardTitle>
|
||||||
<Activity className="h-4 w-4 text-blue-500"/>
|
<Activity className="h-4 w-4 text-blue-500" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">{stats.total}</div>
|
<div className="text-2xl font-bold">{stats.total}</div>
|
||||||
@ -303,7 +226,7 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
<Card className="bg-gradient-to-br from-green-500/10 to-green-500/5 border-green-500/20">
|
<Card className="bg-gradient-to-br from-green-500/10 to-green-500/5 border-green-500/20">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-green-700">运行中</CardTitle>
|
<CardTitle className="text-sm font-medium text-green-700">运行中</CardTitle>
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-500"/>
|
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">{stats.enabledCount}</div>
|
<div className="text-2xl font-bold">{stats.enabledCount}</div>
|
||||||
@ -313,7 +236,7 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
<Card className="bg-gradient-to-br from-yellow-500/10 to-yellow-500/5 border-yellow-500/20">
|
<Card className="bg-gradient-to-br from-yellow-500/10 to-yellow-500/5 border-yellow-500/20">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-yellow-700">已暂停</CardTitle>
|
<CardTitle className="text-sm font-medium text-yellow-700">已暂停</CardTitle>
|
||||||
<Pause className="h-4 w-4 text-yellow-500"/>
|
<Pause className="h-4 w-4 text-yellow-500" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">{stats.pausedCount}</div>
|
<div className="text-2xl font-bold">{stats.pausedCount}</div>
|
||||||
@ -326,50 +249,25 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<CardTitle>任务列表</CardTitle>
|
<CardTitle>任务列表</CardTitle>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button type="button" variant="outline" onClick={() => setCategoryDialogOpen(true)}>
|
<Button type="button" variant="outline" onClick={() => setCategoryDialogOpen(true)}><FolderKanban className="h-4 w-4 mr-2" />分类管理</Button>
|
||||||
<FolderKanban className="h-4 w-4 mr-2"/>
|
<Button type="button" onClick={() => { setEditJob(null); setEditDialogOpen(true); }}><Plus className="h-4 w-4 mr-2" />新建任务</Button>
|
||||||
分类管理
|
|
||||||
</Button>
|
|
||||||
<Button type="button" onClick={handleCreate}>
|
|
||||||
<Plus className="h-4 w-4 mr-2"/>
|
|
||||||
新建任务
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{/* 搜索栏 */}
|
{/* 搜索栏 */}
|
||||||
<div className="flex flex-wrap items-center gap-4 mb-4">
|
<div className="flex flex-wrap items-center gap-4 mb-4">
|
||||||
<div className="flex-1 max-w-md">
|
<div className="flex-1 max-w-md">
|
||||||
<Input
|
<Input placeholder="搜索任务名称..." value={searchParams.jobName} onChange={(e) => setSearchParams({ ...searchParams, jobName: e.target.value })} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} />
|
||||||
placeholder="搜索任务名称..."
|
|
||||||
value={query.jobName}
|
|
||||||
onChange={(e) => setQuery({...query, jobName: e.target.value})}
|
|
||||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<Select
|
<Select value={searchParams.categoryId?.toString() || 'all'} onValueChange={(value) => setSearchParams({ ...searchParams, categoryId: value !== 'all' ? Number(value) : undefined })}>
|
||||||
value={query.categoryId?.toString() || 'all'}
|
<SelectTrigger className="w-[200px]"><SelectValue placeholder="全部分类" /></SelectTrigger>
|
||||||
onValueChange={(value) => setQuery({...query, categoryId: value !== 'all' ? Number(value) : undefined, pageNum: 1})}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-[200px]">
|
|
||||||
<SelectValue placeholder="全部分类"/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">全部分类</SelectItem>
|
<SelectItem value="all">全部分类</SelectItem>
|
||||||
{categories.map((cat) => (
|
{categories.map((cat) => (<SelectItem key={cat.id} value={cat.id.toString()}>{cat.name}</SelectItem>))}
|
||||||
<SelectItem key={cat.id} value={cat.id.toString()}>
|
|
||||||
{cat.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Select
|
<Select value={searchParams.status || 'all'} onValueChange={(value) => setSearchParams({ ...searchParams, status: value !== 'all' ? value as JobStatus : undefined })}>
|
||||||
value={query.status || 'all'}
|
<SelectTrigger className="w-[150px]"><SelectValue placeholder="全部状态" /></SelectTrigger>
|
||||||
onValueChange={(value) => setQuery({...query, status: value !== 'all' ? value as JobStatus : undefined, pageNum: 1})}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-[150px]">
|
|
||||||
<SelectValue placeholder="全部状态"/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">全部状态</SelectItem>
|
<SelectItem value="all">全部状态</SelectItem>
|
||||||
<SelectItem value="ENABLED">启用</SelectItem>
|
<SelectItem value="ENABLED">启用</SelectItem>
|
||||||
@ -377,300 +275,53 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
<SelectItem value="PAUSED">暂停</SelectItem>
|
<SelectItem value="PAUSED">暂停</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Button type="button" onClick={handleSearch}>
|
<Button type="button" onClick={handleSearch}><Search className="h-4 w-4 mr-2" />查询</Button>
|
||||||
<Search className="h-4 w-4 mr-2"/>
|
<Button type="button" variant="outline" onClick={handleReset}>重置</Button>
|
||||||
查询
|
|
||||||
</Button>
|
|
||||||
<Button type="button" variant="outline" onClick={handleReset}>
|
|
||||||
重置
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 任务列表 */}
|
{/* 表格 */}
|
||||||
<div className="rounded-md border">
|
<PaginatedTable<ScheduleJobResponse, ScheduleJobQuery>
|
||||||
<Table minWidth="1600px">
|
ref={tableRef}
|
||||||
<TableHeader>
|
fetchFn={getScheduleJobs}
|
||||||
<TableRow>
|
columns={columns}
|
||||||
<TableHead width="180px">任务名称</TableHead>
|
query={searchParams}
|
||||||
<TableHead width="120px">任务分类</TableHead>
|
rowKey="id"
|
||||||
<TableHead width="150px">Cron表达式</TableHead>
|
minWidth="1600px"
|
||||||
<TableHead width="120px">Bean名称</TableHead>
|
pageSize={DEFAULT_PAGE_SIZE}
|
||||||
<TableHead width="120px">方法名称</TableHead>
|
|
||||||
<TableHead width="100px">状态</TableHead>
|
|
||||||
<TableHead width="160px">上次执行</TableHead>
|
|
||||||
<TableHead width="160px">下次执行</TableHead>
|
|
||||||
<TableHead width="100px">执行次数</TableHead>
|
|
||||||
<TableHead width="90px">成功率</TableHead>
|
|
||||||
<TableHead width="240px" sticky>操作</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{loading ? (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={11} className="h-24 text-center">
|
|
||||||
<div className="flex items-center justify-center gap-2">
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin"/>
|
|
||||||
<span className="text-muted-foreground">加载中...</span>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
) : data && data.content.length > 0 ? (
|
|
||||||
data.content.map((record) => (
|
|
||||||
<TableRow key={record.id}>
|
|
||||||
<TableCell className="font-medium whitespace-nowrap">{record.jobName}</TableCell>
|
|
||||||
<TableCell className="whitespace-nowrap">{record.category?.name || '-'}</TableCell>
|
|
||||||
<TableCell className="whitespace-nowrap">
|
|
||||||
<code className="text-xs bg-muted px-1 py-0.5 rounded">{record.cronExpression}</code>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="whitespace-nowrap">
|
|
||||||
<code className="text-xs">{record.beanName}</code>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="whitespace-nowrap">
|
|
||||||
<code className="text-xs">{record.methodName}</code>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="whitespace-nowrap">{getStatusBadge(record.status)}</TableCell>
|
|
||||||
<TableCell className="text-xs text-muted-foreground whitespace-nowrap">
|
|
||||||
{record.lastExecuteTime ? dayjs(record.lastExecuteTime).format('YYYY-MM-DD HH:mm:ss') : '-'}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-xs text-muted-foreground whitespace-nowrap">
|
|
||||||
{record.nextExecuteTime ? dayjs(record.nextExecuteTime).format('YYYY-MM-DD HH:mm:ss') : '-'}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-center whitespace-nowrap">
|
|
||||||
<Badge variant="outline">{record.executeCount || 0}</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="whitespace-nowrap">
|
|
||||||
{record.executeCount && record.executeCount > 0 ? (
|
|
||||||
<Badge variant={
|
|
||||||
(record.successCount! / record.executeCount) >= 0.9 ? 'default' :
|
|
||||||
(record.successCount! / record.executeCount) >= 0.7 ? 'outline' : 'destructive'
|
|
||||||
}>
|
|
||||||
{((record.successCount! / record.executeCount) * 100).toFixed(1)}%
|
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
<span className="text-muted-foreground">-</span>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell sticky width="280px">
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{/* ENABLED 按钮控制 */}
|
|
||||||
{record.status === 'ENABLED' && (
|
|
||||||
<>
|
|
||||||
{/* 暂停 */}
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-7 w-7"
|
|
||||||
onClick={() => handlePause(record)}
|
|
||||||
title="暂停"
|
|
||||||
>
|
|
||||||
<Pause className="h-4 w-4"/>
|
|
||||||
</Button>
|
|
||||||
{/* 禁用 */}
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-7 w-7 text-orange-600 hover:text-orange-700 hover:bg-orange-50 dark:text-orange-500 dark:hover:bg-orange-950/20"
|
|
||||||
onClick={() => handleDisable(record)}
|
|
||||||
title="禁用"
|
|
||||||
>
|
|
||||||
<Ban className="h-4 w-4"/>
|
|
||||||
</Button>
|
|
||||||
{/* 立即执行 */}
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-7 w-7 text-green-600 hover:text-green-700 hover:bg-green-50 dark:text-green-500 dark:hover:bg-green-950/20"
|
|
||||||
onClick={() => handleTrigger(record)}
|
|
||||||
title="立即执行"
|
|
||||||
>
|
|
||||||
<PlayCircle className="h-4 w-4"/>
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{/* PAUSED 按钮控制 */}
|
|
||||||
{record.status === 'PAUSED' && (
|
|
||||||
<>
|
|
||||||
{/* 恢复 */}
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-7 w-7"
|
|
||||||
onClick={() => handleResume(record)}
|
|
||||||
title="恢复"
|
|
||||||
>
|
|
||||||
<Play className="h-4 w-4"/>
|
|
||||||
</Button>
|
|
||||||
{/* 禁用 */}
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-7 w-7 text-orange-600 hover:text-orange-700 hover:bg-orange-50 dark:text-orange-500 dark:hover:bg-orange-950/20"
|
|
||||||
onClick={() => handleDisable(record)}
|
|
||||||
title="禁用"
|
|
||||||
>
|
|
||||||
<Ban className="h-4 w-4"/>
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{/* DISABLED 按钮控制 */}
|
|
||||||
{record.status === 'DISABLED' && (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-7 w-7 text-blue-600 hover:text-blue-700 hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-950/20"
|
|
||||||
onClick={() => handleEnable(record)}
|
|
||||||
title="启用"
|
|
||||||
>
|
|
||||||
<CheckCircle2 className="h-4 w-4"/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{/* 删除按钮 所有状态显示 */}
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-7 w-7 text-destructive hover:text-destructive"
|
|
||||||
onClick={() => handleDeleteClick(record)}
|
|
||||||
title="删除"
|
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4"/>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-7 w-7"
|
|
||||||
onClick={() => handleViewLog(record)}
|
|
||||||
title="查看日志"
|
|
||||||
>
|
|
||||||
<FileText className="h-4 w-4"/>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-7 w-7"
|
|
||||||
onClick={() => handleEdit(record)}
|
|
||||||
title="编辑"
|
|
||||||
>
|
|
||||||
<Edit className="h-4 w-4"/>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={11} className="h-24 text-center text-muted-foreground">
|
|
||||||
暂无数据
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 分页 */}
|
|
||||||
{data && data.content.length > 0 && (
|
|
||||||
<div className="mt-4">
|
|
||||||
<DataTablePagination
|
|
||||||
pageIndex={query.pageNum || 0}
|
|
||||||
pageSize={query.pageSize || DEFAULT_PAGE_SIZE}
|
|
||||||
pageCount={data.totalPages}
|
|
||||||
onPageChange={(pageIndex) => setQuery({...query, pageNum: pageIndex})}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
{/* 分类管理对话框 */}
|
<TabsContent value="dashboard">
|
||||||
<CategoryManageDialog
|
<Dashboard />
|
||||||
open={categoryDialogOpen}
|
</TabsContent>
|
||||||
onOpenChange={setCategoryDialogOpen}
|
</Tabs>
|
||||||
onSuccess={loadCategories}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 任务日志对话框 */}
|
{/* 弹窗 */}
|
||||||
<JobLogDialog
|
<CategoryManageDialog open={categoryDialogOpen} onOpenChange={setCategoryDialogOpen} onSuccess={loadCategories} />
|
||||||
open={logDialogOpen}
|
<JobLogDialog open={logDialogOpen} onOpenChange={setLogDialogOpen} job={selectedJob} />
|
||||||
onOpenChange={setLogDialogOpen}
|
<JobEditDialog open={editDialogOpen} onOpenChange={setEditDialogOpen} job={editJob} categories={categories} onSuccess={() => tableRef.current?.refresh()} />
|
||||||
job={selectedJob}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 任务编辑对话框 */}
|
|
||||||
<JobEditDialog
|
|
||||||
open={editDialogOpen}
|
|
||||||
onOpenChange={setEditDialogOpen}
|
|
||||||
job={editJob}
|
|
||||||
categories={categories}
|
|
||||||
onSuccess={loadData}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 删除确认对话框 */}
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
open={deleteDialogOpen}
|
open={deleteDialogOpen}
|
||||||
onOpenChange={setDeleteDialogOpen}
|
onOpenChange={setDeleteDialogOpen}
|
||||||
title="确认删除定时任务"
|
title="确认删除定时任务"
|
||||||
description="您确定要删除以下定时任务吗?此操作无法撤销。"
|
description="您确定要删除以下定时任务吗?此操作无法撤销。"
|
||||||
item={deleteJob}
|
item={deleteJob}
|
||||||
onConfirm={confirmDelete}
|
onConfirm={async () => { if (deleteJob) await deleteScheduleJob(deleteJob.id); }}
|
||||||
onSuccess={() => {
|
onSuccess={() => { toast({ title: '删除成功', description: `任务 "${deleteJob?.jobName}" 已删除` }); setDeleteJob(null); tableRef.current?.refresh(); }}
|
||||||
toast({
|
|
||||||
title: '删除成功',
|
|
||||||
description: `任务 "${deleteJob?.jobName}" 已删除`,
|
|
||||||
});
|
|
||||||
setDeleteJob(null);
|
|
||||||
loadData();
|
|
||||||
}}
|
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
confirmText="确认删除"
|
confirmText="确认删除"
|
||||||
>
|
>
|
||||||
{deleteJob && (
|
{deleteJob && (
|
||||||
<div className="rounded-md border p-3 space-y-2 bg-muted/50">
|
<div className="rounded-md border p-3 space-y-2 bg-muted/50">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2"><span className="text-sm font-medium text-muted-foreground">任务名称:</span><span className="font-medium">{deleteJob.jobName}</span></div>
|
||||||
<span className="text-sm font-medium text-muted-foreground">任务名称:</span>
|
<div className="flex items-center gap-2"><span className="text-sm font-medium text-muted-foreground">Bean名称:</span><code className="text-xs bg-background px-2 py-1 rounded">{deleteJob.beanName}</code></div>
|
||||||
<span className="font-medium">{deleteJob.jobName}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-sm font-medium text-muted-foreground">Bean名称:</span>
|
|
||||||
<code className="text-xs bg-background px-2 py-1 rounded">
|
|
||||||
{deleteJob.beanName}
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-sm font-medium text-muted-foreground">方法名称:</span>
|
|
||||||
<code className="text-xs bg-background px-2 py-1 rounded">
|
|
||||||
{deleteJob.methodName}
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
{deleteJob.cronExpression && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-sm font-medium text-muted-foreground">Cron表达式:</span>
|
|
||||||
<code className="text-xs bg-background px-2 py-1 rounded">
|
|
||||||
{deleteJob.cronExpression}
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* 仪表盘视图 */}
|
|
||||||
<TabsContent value="dashboard">
|
|
||||||
<Dashboard/>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ScheduleJobList;
|
export default ScheduleJobList;
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Trash2,
|
Trash2,
|
||||||
@ -85,7 +85,7 @@ const ApplicationManageDialog: React.FC<ApplicationManageDialogProps> = ({
|
|||||||
const [adding, setAdding] = useState(false);
|
const [adding, setAdding] = useState(false);
|
||||||
|
|
||||||
// 分页状态
|
// 分页状态
|
||||||
const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1);
|
const [pageNum, setPageNum] = useState(DEFAULT_PAGE_NUM);
|
||||||
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Edit,
|
Edit,
|
||||||
@ -92,7 +92,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
|||||||
const [editRole, setEditRole] = useState('');
|
const [editRole, setEditRole] = useState('');
|
||||||
|
|
||||||
// 分页状态
|
// 分页状态
|
||||||
const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1);
|
const [pageNum, setPageNum] = useState(DEFAULT_PAGE_NUM);
|
||||||
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,7 @@ import { useForm } from 'react-hook-form';
|
|||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { searchFormSchema, type SearchFormValues } from './schema';
|
import { searchFormSchema, type SearchFormValues } from './schema';
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import type { Page } from '@/types/base';
|
import type { Page } from '@/types/base';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
@ -72,7 +72,7 @@ type Column = {
|
|||||||
const TeamList: React.FC = () => {
|
const TeamList: React.FC = () => {
|
||||||
const [list, setList] = useState<TeamResponse[]>([]);
|
const [list, setList] = useState<TeamResponse[]>([]);
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
pageNum: DEFAULT_CURRENT,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
totalElements: 0,
|
totalElements: 0,
|
||||||
});
|
});
|
||||||
@ -106,7 +106,7 @@ const TeamList: React.FC = () => {
|
|||||||
const loadData = async (searchValues?: SearchFormValues) => {
|
const loadData = async (searchValues?: SearchFormValues) => {
|
||||||
try {
|
try {
|
||||||
const query: TeamQuery = {
|
const query: TeamQuery = {
|
||||||
pageNum: pagination.pageNum - 1,
|
pageNum: pagination.pageNum,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: pagination.pageSize,
|
||||||
teamCode: searchValues?.teamCode || form.getValues('teamCode') || undefined,
|
teamCode: searchValues?.teamCode || form.getValues('teamCode') || undefined,
|
||||||
teamName: searchValues?.teamName || form.getValues('teamName') || undefined,
|
teamName: searchValues?.teamName || form.getValues('teamName') || undefined,
|
||||||
@ -452,7 +452,7 @@ const TeamList: React.FC = () => {
|
|||||||
</Table>
|
</Table>
|
||||||
<div className="flex justify-end border-t border-border bg-muted/40">
|
<div className="flex justify-end border-t border-border bg-muted/40">
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={pagination.pageNum - 1}
|
pageIndex={pagination.pageNum}
|
||||||
pageSize={pagination.pageSize}
|
pageSize={pagination.pageSize}
|
||||||
pageCount={Math.ceil(pagination.totalElements / pagination.pageSize)}
|
pageCount={Math.ceil(pagination.totalElements / pagination.pageSize)}
|
||||||
onPageChange={handlePageChange}
|
onPageChange={handlePageChange}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import { getEnabledCategories } from '../../Category/service';
|
|||||||
import type { FormDataResponse, FormDataStatus, FormDataBusinessType } from './types';
|
import type { FormDataResponse, FormDataStatus, FormDataBusinessType } from './types';
|
||||||
import type { FormCategoryResponse } from '../../Category/types';
|
import type { FormCategoryResponse } from '../../Category/types';
|
||||||
import type { Page } from '@/types/base';
|
import type { Page } from '@/types/base';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,7 +29,7 @@ const FormDataList: React.FC = () => {
|
|||||||
const [data, setData] = useState<Page<FormDataResponse> | null>(null);
|
const [data, setData] = useState<Page<FormDataResponse> | null>(null);
|
||||||
const [categories, setCategories] = useState<FormCategoryResponse[]>([]);
|
const [categories, setCategories] = useState<FormCategoryResponse[]>([]);
|
||||||
const [query, setQuery] = useState({
|
const [query, setQuery] = useState({
|
||||||
pageNum: DEFAULT_CURRENT - 1,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
formDefinitionId: searchParams.get('formDefinitionId') ? Number(searchParams.get('formDefinitionId')) : undefined,
|
formDefinitionId: searchParams.get('formDefinitionId') ? Number(searchParams.get('formDefinitionId')) : undefined,
|
||||||
businessKey: '',
|
businessKey: '',
|
||||||
@ -389,7 +389,7 @@ const FormDataList: React.FC = () => {
|
|||||||
{/* 分页 */}
|
{/* 分页 */}
|
||||||
{pageCount > 1 && (
|
{pageCount > 1 && (
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={query.pageNum + 1}
|
pageIndex={query.pageNum}
|
||||||
pageSize={query.pageSize}
|
pageSize={query.pageSize}
|
||||||
pageCount={pageCount}
|
pageCount={pageCount}
|
||||||
onPageChange={(page) => setQuery(prev => ({
|
onPageChange={(page) => setQuery(prev => ({
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import { Switch } from "@/components/ui/switch";
|
|||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Edit,
|
Edit,
|
||||||
@ -78,7 +78,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
const [deleteRecord, setDeleteRecord] = useState<FormCategoryResponse | null>(null);
|
const [deleteRecord, setDeleteRecord] = useState<FormCategoryResponse | null>(null);
|
||||||
|
|
||||||
// 分页状态
|
// 分页状态
|
||||||
const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1);
|
const [pageNum, setPageNum] = useState(DEFAULT_PAGE_NUM);
|
||||||
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
const form = useForm<FormCategoryRequest>({
|
const form = useForm<FormCategoryRequest>({
|
||||||
@ -329,7 +329,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
{data && data.totalElements > 0 && (
|
{data && data.totalElements > 0 && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={pageNum + 1}
|
pageIndex={pageNum}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
pageCount={Math.ceil((data.totalElements || 0) / pageSize)}
|
pageCount={Math.ceil((data.totalElements || 0) / pageSize)}
|
||||||
onPageChange={(page) => setPageNum(page - 1)}
|
onPageChange={(page) => setPageNum(page - 1)}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import { getEnabledCategories } from '../../Category/service';
|
|||||||
import type { FormDefinitionResponse, FormDefinitionStatus } from './types';
|
import type { FormDefinitionResponse, FormDefinitionStatus } from './types';
|
||||||
import type { FormCategoryResponse } from '../../Category/types';
|
import type { FormCategoryResponse } from '../../Category/types';
|
||||||
import type { Page } from '@/types/base';
|
import type { Page } from '@/types/base';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import FormBasicInfoModal from './components/FormBasicInfoModal';
|
import FormBasicInfoModal from './components/FormBasicInfoModal';
|
||||||
import CategoryManageDialog from './components/CategoryManageDialog';
|
import CategoryManageDialog from './components/CategoryManageDialog';
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ const FormDefinitionList: React.FC = () => {
|
|||||||
const [data, setData] = useState<Page<FormDefinitionResponse> | null>(null);
|
const [data, setData] = useState<Page<FormDefinitionResponse> | null>(null);
|
||||||
const [categories, setCategories] = useState<FormCategoryResponse[]>([]);
|
const [categories, setCategories] = useState<FormCategoryResponse[]>([]);
|
||||||
const [query, setQuery] = useState({
|
const [query, setQuery] = useState({
|
||||||
pageNum: DEFAULT_CURRENT - 1,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
name: '',
|
name: '',
|
||||||
categoryId: undefined as number | undefined,
|
categoryId: undefined as number | undefined,
|
||||||
@ -508,7 +508,7 @@ const FormDefinitionList: React.FC = () => {
|
|||||||
{pageCount > 1 && (
|
{pageCount > 1 && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={query.pageNum + 1}
|
pageIndex={query.pageNum}
|
||||||
pageSize={query.pageSize}
|
pageSize={query.pageSize}
|
||||||
pageCount={pageCount}
|
pageCount={pageCount}
|
||||||
onPageChange={(page) => setQuery(prev => ({
|
onPageChange={(page) => setQuery(prev => ({
|
||||||
|
|||||||
@ -34,7 +34,7 @@ const ExternalPage: React.FC = () => {
|
|||||||
const [list, setList] = useState<ExternalSystemResponse[]>([]);
|
const [list, setList] = useState<ExternalSystemResponse[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [query, setQuery] = useState<ExternalSystemQuery>({
|
const [query, setQuery] = useState<ExternalSystemQuery>({
|
||||||
pageNum: 1,
|
pageNum: 0,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
});
|
});
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
@ -74,13 +74,13 @@ const ExternalPage: React.FC = () => {
|
|||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
setQuery({ ...query, pageNum: 1 });
|
setQuery({ ...query, pageNum: 0 });
|
||||||
loadData();
|
loadData();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置
|
// 重置
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setQuery({ pageNum: 1, pageSize: DEFAULT_PAGE_SIZE });
|
setQuery({ pageNum: 0, pageSize: DEFAULT_PAGE_SIZE });
|
||||||
loadData();
|
loadData();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -326,7 +326,7 @@ const ExternalPage: React.FC = () => {
|
|||||||
</Table>
|
</Table>
|
||||||
<div className="flex justify-end border-t border-border bg-muted/40">
|
<div className="flex justify-end border-t border-border bg-muted/40">
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={query.pageNum || 1}
|
pageIndex={query.pageNum || 0}
|
||||||
pageSize={query.pageSize || DEFAULT_PAGE_SIZE}
|
pageSize={query.pageSize || DEFAULT_PAGE_SIZE}
|
||||||
pageCount={Math.ceil(total / (query.pageSize || DEFAULT_PAGE_SIZE))}
|
pageCount={Math.ceil(total / (query.pageSize || DEFAULT_PAGE_SIZE))}
|
||||||
onPageChange={(page) => setQuery({ ...query, pageNum: page })}
|
onPageChange={(page) => setQuery({ ...query, pageNum: page })}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ import {
|
|||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import type { ServerCategoryResponse } from '../types';
|
import type { ServerCategoryResponse } from '../types';
|
||||||
import { serverCategoryFormSchema, type ServerCategoryFormValues } from '../schema';
|
import { serverCategoryFormSchema, type ServerCategoryFormValues } from '../schema';
|
||||||
import {
|
import {
|
||||||
@ -68,7 +68,7 @@ export const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
const [categoryToDelete, setCategoryToDelete] = useState<ServerCategoryResponse | null>(null);
|
const [categoryToDelete, setCategoryToDelete] = useState<ServerCategoryResponse | null>(null);
|
||||||
|
|
||||||
// 分页状态
|
// 分页状态
|
||||||
const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1);
|
const [pageNum, setPageNum] = useState(DEFAULT_PAGE_NUM);
|
||||||
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
const form = useForm<ServerCategoryFormValues>({
|
const form = useForm<ServerCategoryFormValues>({
|
||||||
@ -450,7 +450,7 @@ export const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
{data && data.totalElements > 0 && (
|
{data && data.totalElements > 0 && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={pageNum + 1}
|
pageIndex={pageNum}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
pageCount={Math.ceil((data.totalElements || 0) / pageSize)}
|
pageCount={Math.ceil((data.totalElements || 0) / pageSize)}
|
||||||
onPageChange={(page) => setPageNum(page - 1)}
|
onPageChange={(page) => setPageNum(page - 1)}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import { useToast } from '@/components/ui/use-toast';
|
|||||||
import { Plus, Edit, Trash2, Loader2 } from 'lucide-react';
|
import { Plus, Edit, Trash2, Loader2 } from 'lucide-react';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import type { MenuResponse } from '../types';
|
import type { MenuResponse } from '../types';
|
||||||
import {
|
import {
|
||||||
getPermissions,
|
getPermissions,
|
||||||
@ -49,7 +49,7 @@ const PermissionDialog: React.FC<PermissionDialogProps> = ({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [permissions, setPermissions] = useState<PermissionResponse[]>([]);
|
const [permissions, setPermissions] = useState<PermissionResponse[]>([]);
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
pageNum: DEFAULT_CURRENT,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
totalElements: 0,
|
totalElements: 0,
|
||||||
});
|
});
|
||||||
@ -62,7 +62,7 @@ const PermissionDialog: React.FC<PermissionDialogProps> = ({
|
|||||||
try {
|
try {
|
||||||
const response = await getPermissions({
|
const response = await getPermissions({
|
||||||
menuId: menu.id,
|
menuId: menu.id,
|
||||||
pageNum: pagination.pageNum - 1,
|
pageNum: pagination.pageNum,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: pagination.pageSize,
|
||||||
});
|
});
|
||||||
if (response) {
|
if (response) {
|
||||||
@ -88,7 +88,7 @@ const PermissionDialog: React.FC<PermissionDialogProps> = ({
|
|||||||
if (open) {
|
if (open) {
|
||||||
// 每次打开都重置到初始状态
|
// 每次打开都重置到初始状态
|
||||||
setPagination({
|
setPagination({
|
||||||
pageNum: DEFAULT_CURRENT,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
totalElements: 0,
|
totalElements: 0,
|
||||||
});
|
});
|
||||||
@ -144,8 +144,8 @@ const PermissionDialog: React.FC<PermissionDialogProps> = ({
|
|||||||
setEditDialogOpen(false);
|
setEditDialogOpen(false);
|
||||||
|
|
||||||
// 保存后重置分页到第一页
|
// 保存后重置分页到第一页
|
||||||
if (!editRecord && pagination.pageNum !== 1) {
|
if (!editRecord && pagination.pageNum !== 0) {
|
||||||
setPagination({ ...pagination, pageNum: 1 });
|
setPagination({ ...pagination, pageNum: 0 });
|
||||||
} else {
|
} else {
|
||||||
loadPermissions();
|
loadPermissions();
|
||||||
}
|
}
|
||||||
@ -296,7 +296,7 @@ const PermissionDialog: React.FC<PermissionDialogProps> = ({
|
|||||||
</Table>
|
</Table>
|
||||||
<div className="flex justify-end border-t border-border bg-muted/40">
|
<div className="flex justify-end border-t border-border bg-muted/40">
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={pagination.pageNum - 1}
|
pageIndex={pagination.pageNum}
|
||||||
pageSize={pagination.pageSize}
|
pageSize={pagination.pageSize}
|
||||||
pageCount={Math.ceil(pagination.totalElements / pagination.pageSize)}
|
pageCount={Math.ceil(pagination.totalElements / pagination.pageSize)}
|
||||||
onPageChange={handlePageChange}
|
onPageChange={handlePageChange}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { useToast } from '@/components/ui/use-toast';
|
|||||||
import { getRoleList, deleteRole, getRoleMenusAndPermissions, assignMenusAndPermissions } from './service';
|
import { getRoleList, deleteRole, getRoleMenusAndPermissions, assignMenusAndPermissions } from './service';
|
||||||
import type { RoleResponse, RoleQuery } from './types';
|
import type { RoleResponse, RoleQuery } from './types';
|
||||||
import type { Page } from '@/types/base';
|
import type { Page } from '@/types/base';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import EditDialog from './components/EditDialog';
|
import EditDialog from './components/EditDialog';
|
||||||
import DeleteDialog from './components/DeleteDialog';
|
import DeleteDialog from './components/DeleteDialog';
|
||||||
import PermissionDialog from './components/PermissionDialog';
|
import PermissionDialog from './components/PermissionDialog';
|
||||||
@ -39,7 +39,7 @@ const RolePage: React.FC = () => {
|
|||||||
const [defaultMenuIds, setDefaultMenuIds] = useState<number[]>([]);
|
const [defaultMenuIds, setDefaultMenuIds] = useState<number[]>([]);
|
||||||
const [defaultPermissionIds, setDefaultPermissionIds] = useState<number[]>([]);
|
const [defaultPermissionIds, setDefaultPermissionIds] = useState<number[]>([]);
|
||||||
const [query, setQuery] = useState<RoleQuery>({
|
const [query, setQuery] = useState<RoleQuery>({
|
||||||
pageNum: DEFAULT_CURRENT - 1,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
code: '',
|
code: '',
|
||||||
name: '',
|
name: '',
|
||||||
@ -370,7 +370,7 @@ const RolePage: React.FC = () => {
|
|||||||
{/* 分页 */}
|
{/* 分页 */}
|
||||||
{pageCount > 1 && (
|
{pageCount > 1 && (
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={(query.pageNum || 0) + 1}
|
pageIndex={query.pageNum || 0}
|
||||||
pageSize={query.pageSize || DEFAULT_PAGE_SIZE}
|
pageSize={query.pageSize || DEFAULT_PAGE_SIZE}
|
||||||
pageCount={pageCount}
|
pageCount={pageCount}
|
||||||
onPageChange={(page) => setQuery(prev => ({
|
onPageChange={(page) => setQuery(prev => ({
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import { getDepartmentTree } from '../../Department/List/service';
|
|||||||
import type { UserResponse, UserQuery, Role } from './types';
|
import type { UserResponse, UserQuery, Role } from './types';
|
||||||
import type { DepartmentResponse } from '../../Department/List/types';
|
import type { DepartmentResponse } from '../../Department/List/types';
|
||||||
import type { Page } from '@/types/base';
|
import type { Page } from '@/types/base';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import EditModal from './components/EditModal';
|
import EditModal from './components/EditModal';
|
||||||
import ResetPasswordDialog from './components/ResetPasswordDialog';
|
import ResetPasswordDialog from './components/ResetPasswordDialog';
|
||||||
import AssignRolesDialog from './components/AssignRolesDialog';
|
import AssignRolesDialog from './components/AssignRolesDialog';
|
||||||
@ -41,7 +41,7 @@ const UserPage: React.FC = () => {
|
|||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [deleteRecord, setDeleteRecord] = useState<UserResponse | null>(null);
|
const [deleteRecord, setDeleteRecord] = useState<UserResponse | null>(null);
|
||||||
const [query, setQuery] = useState<UserQuery>({
|
const [query, setQuery] = useState<UserQuery>({
|
||||||
pageNum: DEFAULT_CURRENT - 1,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
username: '',
|
username: '',
|
||||||
email: '',
|
email: '',
|
||||||
@ -431,7 +431,7 @@ const UserPage: React.FC = () => {
|
|||||||
{/* 分页 */}
|
{/* 分页 */}
|
||||||
{pageCount > 1 && (
|
{pageCount > 1 && (
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={(query.pageNum || 0) + 1}
|
pageIndex={query.pageNum || 0}
|
||||||
pageSize={query.pageSize || DEFAULT_PAGE_SIZE}
|
pageSize={query.pageSize || DEFAULT_PAGE_SIZE}
|
||||||
pageCount={pageCount}
|
pageCount={pageCount}
|
||||||
onPageChange={(page) => setQuery(prev => ({
|
onPageChange={(page) => setQuery(prev => ({
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import { Switch } from "@/components/ui/switch";
|
|||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Edit,
|
Edit,
|
||||||
@ -82,7 +82,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
const [deleteRecord, setDeleteRecord] = useState<WorkflowCategoryResponse | null>(null);
|
const [deleteRecord, setDeleteRecord] = useState<WorkflowCategoryResponse | null>(null);
|
||||||
|
|
||||||
// 分页状态
|
// 分页状态
|
||||||
const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1);
|
const [pageNum, setPageNum] = useState(DEFAULT_PAGE_NUM);
|
||||||
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
const form = useForm<WorkflowCategoryRequest>({
|
const form = useForm<WorkflowCategoryRequest>({
|
||||||
@ -375,7 +375,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
{data && data.totalElements > 0 && (
|
{data && data.totalElements > 0 && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={pageNum + 1}
|
pageIndex={pageNum}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
pageCount={Math.ceil((data.totalElements || 0) / pageSize)}
|
pageCount={Math.ceil((data.totalElements || 0) / pageSize)}
|
||||||
onPageChange={(page) => setPageNum(page - 1)}
|
onPageChange={(page) => setPageNum(page - 1)}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { useToast } from '@/components/ui/use-toast';
|
|||||||
import { getDefinitions, getWorkflowCategoryList, deleteDefinition, publishDefinition, startWorkflowInstance } from './service';
|
import { getDefinitions, getWorkflowCategoryList, deleteDefinition, publishDefinition, startWorkflowInstance } from './service';
|
||||||
import type { WorkflowDefinition, WorkflowDefinitionQuery, WorkflowCategoryResponse, WorkflowDefinitionStatus } from './types';
|
import type { WorkflowDefinition, WorkflowDefinitionQuery, WorkflowCategoryResponse, WorkflowDefinitionStatus } from './types';
|
||||||
import type { Page } from '@/types/base';
|
import type { Page } from '@/types/base';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import EditModal from './components/EditModal';
|
import EditModal from './components/EditModal';
|
||||||
import DeleteDialog from './components/DeleteDialog';
|
import DeleteDialog from './components/DeleteDialog';
|
||||||
import DeployDialog from './components/DeployDialog';
|
import DeployDialog from './components/DeployDialog';
|
||||||
@ -44,7 +44,7 @@ const WorkflowDefinitionList: React.FC = () => {
|
|||||||
const [startRecord, setStartRecord] = useState<WorkflowDefinition | null>(null);
|
const [startRecord, setStartRecord] = useState<WorkflowDefinition | null>(null);
|
||||||
const [formDefinition, setFormDefinition] = useState<FormDefinitionResponse | null>(null);
|
const [formDefinition, setFormDefinition] = useState<FormDefinitionResponse | null>(null);
|
||||||
const [query, setQuery] = useState<WorkflowDefinitionQuery>({
|
const [query, setQuery] = useState<WorkflowDefinitionQuery>({
|
||||||
pageNum: DEFAULT_CURRENT - 1,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
name: '',
|
name: '',
|
||||||
categoryId: undefined,
|
categoryId: undefined,
|
||||||
@ -561,7 +561,7 @@ const WorkflowDefinitionList: React.FC = () => {
|
|||||||
{/* 分页 */}
|
{/* 分页 */}
|
||||||
{pageCount > 1 && (
|
{pageCount > 1 && (
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={(query.pageNum || 0) + 1}
|
pageIndex={query.pageNum || 0}
|
||||||
pageSize={query.pageSize || DEFAULT_PAGE_SIZE}
|
pageSize={query.pageSize || DEFAULT_PAGE_SIZE}
|
||||||
pageCount={pageCount}
|
pageCount={pageCount}
|
||||||
onPageChange={(page) => setQuery(prev => ({
|
onPageChange={(page) => setQuery(prev => ({
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { Loader2, History } from 'lucide-react';
|
|||||||
import { WorkflowHistoricalInstance } from '../types';
|
import { WorkflowHistoricalInstance } from '../types';
|
||||||
import { getHistoricalInstances } from '../service';
|
import { getHistoricalInstances } from '../service';
|
||||||
import DetailModal from './DetailModal';
|
import DetailModal from './DetailModal';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
|
|
||||||
interface HistoryModalProps {
|
interface HistoryModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@ -21,7 +21,7 @@ const HistoryModal: React.FC<HistoryModalProps> = ({ visible, onCancel, workflow
|
|||||||
const [data, setData] = useState<WorkflowHistoricalInstance[]>([]);
|
const [data, setData] = useState<WorkflowHistoricalInstance[]>([]);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [query, setQuery] = useState({
|
const [query, setQuery] = useState({
|
||||||
pageNum: DEFAULT_CURRENT - 1,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
});
|
});
|
||||||
const [detailVisible, setDetailVisible] = useState(false);
|
const [detailVisible, setDetailVisible] = useState(false);
|
||||||
@ -136,7 +136,7 @@ const HistoryModal: React.FC<HistoryModalProps> = ({ visible, onCancel, workflow
|
|||||||
{pageCount > 1 && (
|
{pageCount > 1 && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={query.pageNum + 1}
|
pageIndex={query.pageNum}
|
||||||
pageSize={query.pageSize}
|
pageSize={query.pageSize}
|
||||||
pageCount={pageCount}
|
pageCount={pageCount}
|
||||||
onPageChange={(page) => setQuery(prev => ({
|
onPageChange={(page) => setQuery(prev => ({
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
import { getWorkflowInstances } from './service';
|
import { getWorkflowInstances } from './service';
|
||||||
import type { WorkflowTemplateWithInstances } from './types';
|
import type { WorkflowTemplateWithInstances } from './types';
|
||||||
import type { Page } from '@/types/base';
|
import type { Page } from '@/types/base';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
import HistoryModal from './components/HistoryModal';
|
import HistoryModal from './components/HistoryModal';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ const WorkflowInstanceList: React.FC = () => {
|
|||||||
const [historyVisible, setHistoryVisible] = useState(false);
|
const [historyVisible, setHistoryVisible] = useState(false);
|
||||||
const [selectedWorkflowDefinitionId, setSelectedWorkflowDefinitionId] = useState<number>();
|
const [selectedWorkflowDefinitionId, setSelectedWorkflowDefinitionId] = useState<number>();
|
||||||
const [query, setQuery] = useState({
|
const [query, setQuery] = useState({
|
||||||
pageNum: DEFAULT_CURRENT - 1,
|
pageNum: DEFAULT_PAGE_NUM,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
businessKey: '',
|
businessKey: '',
|
||||||
status: undefined as string | undefined,
|
status: undefined as string | undefined,
|
||||||
@ -297,7 +297,7 @@ const WorkflowInstanceList: React.FC = () => {
|
|||||||
{/* 分页 */}
|
{/* 分页 */}
|
||||||
{pageCount > 1 && (
|
{pageCount > 1 && (
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={query.pageNum + 1}
|
pageIndex={query.pageNum}
|
||||||
pageSize={query.pageSize}
|
pageSize={query.pageSize}
|
||||||
pageCount={pageCount}
|
pageCount={pageCount}
|
||||||
onPageChange={(page) => setQuery(prev => ({
|
onPageChange={(page) => setQuery(prev => ({
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import type {Page} from '@/types/base';
|
|||||||
|
|
||||||
// 默认分页参数
|
// 默认分页参数
|
||||||
export const DEFAULT_PAGE_SIZE = 10;
|
export const DEFAULT_PAGE_SIZE = 10;
|
||||||
export const DEFAULT_CURRENT = 1;
|
export const DEFAULT_PAGE_NUM = 0; // 页码从0开始
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换前端分页参数为后端分页参数
|
* 转换前端分页参数为后端分页参数
|
||||||
@ -13,7 +13,7 @@ export const convertToPageParams = (params?: {
|
|||||||
sortField?: string;
|
sortField?: string;
|
||||||
sortOrder?: string;
|
sortOrder?: string;
|
||||||
}): { sortOrder: any; sortField: string | undefined; pageSize: number; pageNum: number } => ({
|
}): { sortOrder: any; sortField: string | undefined; pageSize: number; pageNum: number } => ({
|
||||||
pageNum: Math.max(1, params?.current || DEFAULT_CURRENT) - 1, // 转换为从0开始的页码
|
pageNum: Math.max(0, (params?.current ?? 1) - 1), // 前端current从1开始,转换为从0开始的页码
|
||||||
pageSize: params?.pageSize || DEFAULT_PAGE_SIZE,
|
pageSize: params?.pageSize || DEFAULT_PAGE_SIZE,
|
||||||
sortField: params?.sortField,
|
sortField: params?.sortField,
|
||||||
sortOrder: params?.sortOrder?.replace('end', '')
|
sortOrder: params?.sortOrder?.replace('end', '')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user