重构消息通知弹窗
This commit is contained in:
parent
9275f5d4ae
commit
44ea1353b2
@ -1,29 +1,36 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useState, useEffect, useRef, useCallback, useImperativeHandle } from 'react';
|
import { useState, useEffect, useRef, useCallback, useImperativeHandle } from 'react';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2, Search, RotateCcw } from 'lucide-react';
|
||||||
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from './table';
|
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from './table';
|
||||||
import { DataTablePagination } from './pagination';
|
import { DataTablePagination } from './pagination';
|
||||||
|
import { Button } from './button';
|
||||||
|
import { Input } from './input';
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './select';
|
||||||
import type { Page } from '@/types/base';
|
import type { Page } from '@/types/base';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
||||||
|
|
||||||
// 列定义
|
// 列定义
|
||||||
export interface ColumnDef<T> {
|
export interface ColumnDef<T> {
|
||||||
/** 列标识 */
|
|
||||||
key: string;
|
key: string;
|
||||||
/** 列标题 */
|
|
||||||
title: React.ReactNode;
|
title: React.ReactNode;
|
||||||
/** 列宽度 */
|
|
||||||
width?: string;
|
width?: string;
|
||||||
/** 是否固定列 */
|
|
||||||
sticky?: boolean;
|
sticky?: boolean;
|
||||||
/** 自定义渲染 */
|
|
||||||
render?: (value: any, record: T, index: number) => React.ReactNode;
|
render?: (value: any, record: T, index: number) => React.ReactNode;
|
||||||
/** 数据字段路径,支持嵌套如 'user.name' */
|
|
||||||
dataIndex?: string;
|
dataIndex?: string;
|
||||||
/** 单元格类名 */
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索字段定义
|
||||||
|
export interface SearchFieldDef {
|
||||||
|
key: string;
|
||||||
|
type: 'input' | 'select';
|
||||||
|
placeholder?: string;
|
||||||
|
options?: { label: string; value: string | number }[];
|
||||||
|
width?: string;
|
||||||
|
/** 是否支持回车搜索(仅 input 类型) */
|
||||||
|
enterSearch?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// 查询参数基础类型
|
// 查询参数基础类型
|
||||||
export interface BaseQuery {
|
export interface BaseQuery {
|
||||||
pageNum?: number;
|
pageNum?: number;
|
||||||
@ -33,47 +40,40 @@ export interface BaseQuery {
|
|||||||
|
|
||||||
// 组件 Props
|
// 组件 Props
|
||||||
export interface PaginatedTableProps<T, Q extends BaseQuery = BaseQuery> {
|
export interface PaginatedTableProps<T, Q extends BaseQuery = BaseQuery> {
|
||||||
/** 数据获取函数 */
|
|
||||||
fetchFn: (query: Q) => Promise<Page<T>>;
|
fetchFn: (query: Q) => Promise<Page<T>>;
|
||||||
/** 列定义 */
|
|
||||||
columns: ColumnDef<T>[];
|
columns: ColumnDef<T>[];
|
||||||
/** 查询参数(不含分页) */
|
/** 搜索字段定义(简化模式) */
|
||||||
query?: Omit<Q, 'pageNum' | 'pageSize'>;
|
searchFields?: SearchFieldDef[];
|
||||||
/** 行唯一标识字段 */
|
/** 自定义搜索栏(完全自定义模式) */
|
||||||
|
searchBar?: React.ReactNode;
|
||||||
|
/** 工具栏(新建按钮等) */
|
||||||
|
toolbar?: React.ReactNode;
|
||||||
|
/** 初始查询参数 */
|
||||||
|
initialQuery?: Omit<Q, 'pageNum' | 'pageSize'>;
|
||||||
rowKey?: keyof T | ((record: T) => string | number);
|
rowKey?: keyof T | ((record: T) => string | number);
|
||||||
/** 表格最小宽度 */
|
|
||||||
minWidth?: string;
|
minWidth?: string;
|
||||||
/** 初始页大小 */
|
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
/** 空数据提示 */
|
|
||||||
emptyText?: React.ReactNode;
|
emptyText?: React.ReactNode;
|
||||||
/** 加载中提示 */
|
|
||||||
loadingText?: React.ReactNode;
|
loadingText?: React.ReactNode;
|
||||||
/** 是否在 query 变化时自动刷新 */
|
/** 搜索栏和表格之间的间距 */
|
||||||
autoRefresh?: boolean;
|
searchGap?: string;
|
||||||
/** 额外的依赖项,变化时触发刷新 */
|
|
||||||
deps?: any[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给外部的方法
|
// 暴露给外部的方法
|
||||||
export interface PaginatedTableRef<T> {
|
export interface PaginatedTableRef<T> {
|
||||||
/** 刷新数据 */
|
|
||||||
refresh: (showLoading?: boolean) => Promise<void>;
|
refresh: (showLoading?: boolean) => Promise<void>;
|
||||||
/** 重置到第一页并刷新 */
|
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
/** 获取当前数据 */
|
search: () => void;
|
||||||
getData: () => Page<T> | null;
|
getData: () => Page<T> | null;
|
||||||
/** 局部更新某条记录 */
|
getQuery: () => Record<string, any>;
|
||||||
updateRecord: (id: string | number, updates: Partial<T>) => void;
|
updateRecord: (id: string | number, updates: Partial<T>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取嵌套属性值
|
|
||||||
function getNestedValue(obj: any, path?: string): any {
|
function getNestedValue(obj: any, path?: string): any {
|
||||||
if (!path) return undefined;
|
if (!path) return undefined;
|
||||||
return path.split('.').reduce((acc, part) => acc?.[part], obj);
|
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 {
|
function getRowKey<T>(record: T, rowKey: keyof T | ((record: T) => string | number) | undefined, index: number): string | number {
|
||||||
if (!rowKey) return index;
|
if (!rowKey) return index;
|
||||||
if (typeof rowKey === 'function') return rowKey(record);
|
if (typeof rowKey === 'function') return rowKey(record);
|
||||||
@ -87,34 +87,31 @@ function PaginatedTableInner<T extends Record<string, any>, Q extends BaseQuery
|
|||||||
const {
|
const {
|
||||||
fetchFn,
|
fetchFn,
|
||||||
columns,
|
columns,
|
||||||
query = {} as Omit<Q, 'pageNum' | 'pageSize'>,
|
searchFields,
|
||||||
|
searchBar,
|
||||||
|
toolbar,
|
||||||
|
initialQuery = {} as Omit<Q, 'pageNum' | 'pageSize'>,
|
||||||
rowKey,
|
rowKey,
|
||||||
minWidth,
|
minWidth,
|
||||||
pageSize: initialPageSize = DEFAULT_PAGE_SIZE,
|
pageSize: initialPageSize = DEFAULT_PAGE_SIZE,
|
||||||
emptyText = '暂无数据',
|
emptyText = '暂无数据',
|
||||||
loadingText = '加载中...',
|
loadingText = '加载中...',
|
||||||
autoRefresh = true,
|
searchGap = 'mb-4',
|
||||||
deps = [],
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [data, setData] = useState<Page<T> | null>(null);
|
const [data, setData] = useState<Page<T> | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(true); // 首次加载显示 loading
|
||||||
const [pageNum, setPageNum] = useState(DEFAULT_PAGE_NUM);
|
const [pageNum, setPageNum] = useState(DEFAULT_PAGE_NUM);
|
||||||
const [pageSize] = useState(initialPageSize);
|
const [pageSize] = useState(initialPageSize);
|
||||||
|
const [searchValues, setSearchValues] = useState<Record<string, any>>(initialQuery);
|
||||||
|
|
||||||
// 是否首次加载
|
|
||||||
const isFirstLoad = useRef(true);
|
const isFirstLoad = useRef(true);
|
||||||
// 是否需要显示 loading
|
|
||||||
const shouldShowLoading = useRef(true);
|
|
||||||
// 上一次的 query(用于检测变化)
|
|
||||||
const prevQueryRef = useRef(query);
|
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据(不再控制 loading 状态,只有首次加载显示)
|
||||||
const loadData = useCallback(async (showLoading = true) => {
|
const loadData = useCallback(async () => {
|
||||||
if (showLoading) setLoading(true);
|
|
||||||
try {
|
try {
|
||||||
const result = await fetchFn({
|
const result = await fetchFn({
|
||||||
...query,
|
...searchValues,
|
||||||
pageNum,
|
pageNum,
|
||||||
pageSize,
|
pageSize,
|
||||||
} as Q);
|
} as Q);
|
||||||
@ -122,47 +119,47 @@ function PaginatedTableInner<T extends Record<string, any>, Q extends BaseQuery
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('PaginatedTable 加载数据失败:', error);
|
console.error('PaginatedTable 加载数据失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
if (isFirstLoad.current) {
|
||||||
|
setLoading(false);
|
||||||
|
isFirstLoad.current = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [fetchFn, query, pageNum, pageSize]);
|
}, [fetchFn, searchValues, pageNum, pageSize]);
|
||||||
|
|
||||||
// 监听分页变化
|
// 监听分页变化
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData(shouldShowLoading.current || isFirstLoad.current);
|
loadData();
|
||||||
isFirstLoad.current = false;
|
}, [pageNum, pageSize]);
|
||||||
shouldShowLoading.current = false;
|
|
||||||
}, [pageNum, pageSize, ...deps]);
|
|
||||||
|
|
||||||
// 监听 query 变化(自动刷新)
|
// 搜索
|
||||||
useEffect(() => {
|
const handleSearch = useCallback(() => {
|
||||||
if (!autoRefresh) return;
|
if (pageNum === DEFAULT_PAGE_NUM) {
|
||||||
|
loadData();
|
||||||
const queryChanged = JSON.stringify(query) !== JSON.stringify(prevQueryRef.current);
|
} else {
|
||||||
prevQueryRef.current = query;
|
|
||||||
|
|
||||||
if (queryChanged && !isFirstLoad.current) {
|
|
||||||
shouldShowLoading.current = true;
|
|
||||||
setPageNum(DEFAULT_PAGE_NUM);
|
setPageNum(DEFAULT_PAGE_NUM);
|
||||||
}
|
}
|
||||||
}, [query, autoRefresh]);
|
}, [pageNum, loadData]);
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
setSearchValues(initialQuery);
|
||||||
|
setPageNum(DEFAULT_PAGE_NUM);
|
||||||
|
}, [initialQuery]);
|
||||||
|
|
||||||
// 分页切换
|
// 分页切换
|
||||||
const handlePageChange = useCallback((newPageNum: number) => {
|
const handlePageChange = useCallback((newPageNum: number) => {
|
||||||
shouldShowLoading.current = false;
|
|
||||||
setPageNum(newPageNum);
|
setPageNum(newPageNum);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 暴露方法给外部
|
// 暴露方法
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
refresh: async (showLoading = false) => {
|
refresh: async () => {
|
||||||
shouldShowLoading.current = showLoading;
|
await loadData();
|
||||||
await loadData(showLoading);
|
|
||||||
},
|
|
||||||
reset: () => {
|
|
||||||
shouldShowLoading.current = true;
|
|
||||||
setPageNum(DEFAULT_PAGE_NUM);
|
|
||||||
},
|
},
|
||||||
|
reset: handleReset,
|
||||||
|
search: handleSearch,
|
||||||
getData: () => data,
|
getData: () => data,
|
||||||
|
getQuery: () => searchValues,
|
||||||
updateRecord: (id, updates) => {
|
updateRecord: (id, updates) => {
|
||||||
setData(prev => {
|
setData(prev => {
|
||||||
if (!prev) return prev;
|
if (!prev) return prev;
|
||||||
@ -174,9 +171,49 @@ function PaginatedTableInner<T extends Record<string, any>, Q extends BaseQuery
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}), [data, loadData, rowKey]);
|
}), [data, loadData, handleReset, handleSearch, searchValues, rowKey]);
|
||||||
|
|
||||||
// 渲染单元格内容
|
// 渲染搜索字段
|
||||||
|
const renderSearchField = (field: SearchFieldDef) => {
|
||||||
|
const value = searchValues[field.key];
|
||||||
|
|
||||||
|
if (field.type === 'input') {
|
||||||
|
return (
|
||||||
|
<div key={field.key} className={field.width || 'flex-1 max-w-md'}>
|
||||||
|
<Input
|
||||||
|
placeholder={field.placeholder}
|
||||||
|
value={value || ''}
|
||||||
|
onChange={(e) => setSearchValues(prev => ({ ...prev, [field.key]: e.target.value }))}
|
||||||
|
onKeyDown={(e) => field.enterSearch !== false && e.key === 'Enter' && handleSearch()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.type === 'select') {
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
key={field.key}
|
||||||
|
value={value?.toString() || 'all'}
|
||||||
|
onValueChange={(v) => setSearchValues(prev => ({ ...prev, [field.key]: v !== 'all' ? v : undefined }))}
|
||||||
|
>
|
||||||
|
<SelectTrigger className={field.width || 'w-[180px]'}>
|
||||||
|
<SelectValue placeholder={field.placeholder} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">{field.placeholder || '全部'}</SelectItem>
|
||||||
|
{field.options?.map(opt => (
|
||||||
|
<SelectItem key={opt.value} value={opt.value.toString()}>{opt.label}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染单元格
|
||||||
const renderCell = (column: ColumnDef<T>, record: T, index: number) => {
|
const renderCell = (column: ColumnDef<T>, record: T, index: number) => {
|
||||||
if (column.render) {
|
if (column.render) {
|
||||||
const value = column.dataIndex ? getNestedValue(record, column.dataIndex) : undefined;
|
const value = column.dataIndex ? getNestedValue(record, column.dataIndex) : undefined;
|
||||||
@ -190,9 +227,29 @@ function PaginatedTableInner<T extends Record<string, any>, Q extends BaseQuery
|
|||||||
|
|
||||||
const pageCount = data?.totalPages || 0;
|
const pageCount = data?.totalPages || 0;
|
||||||
const hasData = data && data.content && data.content.length > 0;
|
const hasData = data && data.content && data.content.length > 0;
|
||||||
|
const showSearchBar = searchFields && searchFields.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-0">
|
<div className="space-y-0">
|
||||||
|
{/* 搜索栏 */}
|
||||||
|
{(showSearchBar || searchBar || toolbar) && (
|
||||||
|
<div className={`flex flex-wrap items-center gap-4 ${searchGap}`}>
|
||||||
|
{searchBar || (
|
||||||
|
<>
|
||||||
|
{searchFields?.map(renderSearchField)}
|
||||||
|
<Button type="button" onClick={handleSearch}>
|
||||||
|
<Search className="h-4 w-4 mr-2" />查询
|
||||||
|
</Button>
|
||||||
|
<Button type="button" variant="outline" onClick={handleReset}>
|
||||||
|
<RotateCcw className="h-4 w-4 mr-2" />重置
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{toolbar && <div className="ml-auto flex items-center gap-2">{toolbar}</div>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 表格 */}
|
||||||
<div className="rounded-md border">
|
<div className="rounded-md border">
|
||||||
<Table minWidth={minWidth}>
|
<Table minWidth={minWidth}>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
@ -205,7 +262,7 @@ function PaginatedTableInner<T extends Record<string, any>, Q extends BaseQuery
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{loading && isFirstLoad.current ? (
|
{loading ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
@ -218,12 +275,7 @@ function PaginatedTableInner<T extends Record<string, any>, Q extends BaseQuery
|
|||||||
data.content.map((record, index) => (
|
data.content.map((record, index) => (
|
||||||
<TableRow key={getRowKey(record, rowKey, index)}>
|
<TableRow key={getRowKey(record, rowKey, index)}>
|
||||||
{columns.map(col => (
|
{columns.map(col => (
|
||||||
<TableCell
|
<TableCell key={col.key} width={col.width} sticky={col.sticky} className={col.className}>
|
||||||
key={col.key}
|
|
||||||
width={col.width}
|
|
||||||
sticky={col.sticky}
|
|
||||||
className={col.className}
|
|
||||||
>
|
|
||||||
{renderCell(col, record, index)}
|
{renderCell(col, record, index)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
@ -240,6 +292,7 @@ function PaginatedTableInner<T extends Record<string, any>, Q extends BaseQuery
|
|||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 分页 */}
|
||||||
{pageCount > 0 && (
|
{pageCount > 0 && (
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={pageNum}
|
pageIndex={pageNum}
|
||||||
@ -252,7 +305,6 @@ function PaginatedTableInner<T extends Record<string, any>, Q extends BaseQuery
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 forwardRef 包装,支持泛型
|
|
||||||
export const PaginatedTable = React.forwardRef(PaginatedTableInner) as <
|
export const PaginatedTable = React.forwardRef(PaginatedTableInner) as <
|
||||||
T extends Record<string, any>,
|
T extends Record<string, any>,
|
||||||
Q extends BaseQuery = BaseQuery
|
Q extends BaseQuery = BaseQuery
|
||||||
|
|||||||
@ -2,18 +2,15 @@ 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 { 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import {
|
import {
|
||||||
Plus, Search, Edit, Trash2, Play, Pause,
|
Plus, 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 { PaginatedTable, type ColumnDef, type PaginatedTableRef } from '@/components/ui/paginated-table';
|
import { PaginatedTable, type ColumnDef, type PaginatedTableRef, type SearchFieldDef } from '@/components/ui/paginated-table';
|
||||||
import { getScheduleJobs, getJobCategoryList, pauseJob, resumeJob, triggerJob, disableJob, deleteScheduleJob, enableJob } from './service';
|
import { getScheduleJobs, getJobCategoryList, pauseJob, resumeJob, triggerJob, disableJob, deleteScheduleJob, enableJob } from './service';
|
||||||
import type { ScheduleJobResponse, ScheduleJobQuery, JobCategoryResponse, JobStatus } from './types';
|
import type { ScheduleJobResponse, ScheduleJobQuery, JobCategoryResponse, JobStatus } from './types';
|
||||||
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';
|
||||||
@ -31,13 +28,6 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
// 分类数据
|
// 分类数据
|
||||||
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);
|
||||||
@ -61,6 +51,13 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
loadCategories();
|
loadCategories();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 搜索字段定义
|
||||||
|
const searchFields: SearchFieldDef[] = useMemo(() => [
|
||||||
|
{ key: 'jobName', type: 'input', placeholder: '搜索任务名称...', enterSearch: true },
|
||||||
|
{ key: 'categoryId', type: 'select', placeholder: '全部分类', width: 'w-[200px]', options: categories.map(c => ({ label: c.name, value: c.id })) },
|
||||||
|
{ key: 'status', type: 'select', placeholder: '全部状态', width: 'w-[150px]', options: [{ label: '启用', value: 'ENABLED' }, { label: '禁用', value: 'DISABLED' }, { label: '暂停', value: 'PAUSED' }] },
|
||||||
|
], [categories]);
|
||||||
|
|
||||||
// 状态徽章
|
// 状态徽章
|
||||||
const getStatusBadge = (status: JobStatus) => {
|
const getStatusBadge = (status: JobStatus) => {
|
||||||
const statusMap: Record<JobStatus, { variant: 'default' | 'secondary' | 'destructive' | 'outline'; text: string; icon: React.ElementType }> = {
|
const statusMap: Record<JobStatus, { variant: 'default' | 'secondary' | 'destructive' | 'outline'; text: string; icon: React.ElementType }> = {
|
||||||
@ -186,17 +183,6 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
return { total, enabledCount, pausedCount };
|
return { total, enabledCount, pausedCount };
|
||||||
}, [tableRef.current?.getData()]);
|
}, [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">
|
||||||
@ -254,40 +240,13 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{/* 搜索栏 */}
|
|
||||||
<div className="flex flex-wrap items-center gap-4 mb-4">
|
|
||||||
<div className="flex-1 max-w-md">
|
|
||||||
<Input placeholder="搜索任务名称..." value={searchParams.jobName} onChange={(e) => setSearchParams({ ...searchParams, jobName: e.target.value })} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} />
|
|
||||||
</div>
|
|
||||||
<Select value={searchParams.categoryId?.toString() || 'all'} onValueChange={(value) => setSearchParams({ ...searchParams, categoryId: value !== 'all' ? Number(value) : undefined })}>
|
|
||||||
<SelectTrigger className="w-[200px]"><SelectValue placeholder="全部分类" /></SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="all">全部分类</SelectItem>
|
|
||||||
{categories.map((cat) => (<SelectItem key={cat.id} value={cat.id.toString()}>{cat.name}</SelectItem>))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Select value={searchParams.status || 'all'} onValueChange={(value) => setSearchParams({ ...searchParams, status: value !== 'all' ? value as JobStatus : undefined })}>
|
|
||||||
<SelectTrigger className="w-[150px]"><SelectValue placeholder="全部状态" /></SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="all">全部状态</SelectItem>
|
|
||||||
<SelectItem value="ENABLED">启用</SelectItem>
|
|
||||||
<SelectItem value="DISABLED">禁用</SelectItem>
|
|
||||||
<SelectItem value="PAUSED">暂停</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Button type="button" onClick={handleSearch}><Search className="h-4 w-4 mr-2" />查询</Button>
|
|
||||||
<Button type="button" variant="outline" onClick={handleReset}>重置</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 表格 */}
|
|
||||||
<PaginatedTable<ScheduleJobResponse, ScheduleJobQuery>
|
<PaginatedTable<ScheduleJobResponse, ScheduleJobQuery>
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
fetchFn={getScheduleJobs}
|
fetchFn={getScheduleJobs}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
query={searchParams}
|
searchFields={searchFields}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
minWidth="1600px"
|
minWidth="1600px"
|
||||||
pageSize={DEFAULT_PAGE_SIZE}
|
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user