diff --git a/frontend/src/pages/Deploy/Application/List/index.tsx b/frontend/src/pages/Deploy/Application/List/index.tsx index 5254e2c7..b0c8cc8f 100644 --- a/frontend/src/pages/Deploy/Application/List/index.tsx +++ b/frontend/src/pages/Deploy/Application/List/index.tsx @@ -1,8 +1,7 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef, useMemo } from 'react'; import { PageContainer } from '@/components/ui/page-container'; import { CodeOutlined, - GithubOutlined, JavaOutlined, NodeIndexOutlined, PythonOutlined, @@ -15,75 +14,37 @@ import type { ApplicationCategoryResponse } from '../Category/types'; import ApplicationModal from './components/ApplicationModal'; import DeleteDialog from './components/DeleteDialog'; import CategoryManageDialog from './components/CategoryManageDialog'; -import { - Table, - TableHeader, - TableBody, - TableHead, - TableRow, - TableCell, -} from '@/components/ui/table'; -import { - Card, - CardContent, - CardHeader, - CardTitle, - CardDescription, -} from '@/components/ui/card'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; import { Badge } from '@/components/ui/badge'; -import { useToast } from '@/components/ui/use-toast'; -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { searchFormSchema, type SearchFormValues } from './schema'; -import { DataTablePagination } from '@/components/ui/pagination'; -import { Plus, Edit, Trash2, Server, Activity, Database, ExternalLink, Settings } from 'lucide-react'; +import { PaginatedTable, type ColumnDef, type SearchFieldDef, type PaginatedTableRef } from '@/components/ui/paginated-table'; +import { Plus, Edit, Trash2, Server, Activity, Database, Settings } from 'lucide-react'; -interface Column { - accessorKey?: keyof Application; - id?: string; - header: string; - size: number; - cell?: (props: { row: { original: Application } }) => React.ReactNode; -} - -const DEFAULT_PAGE_SIZE = 10; +// 获取开发语言信息 +const getLanguageInfo = (language: DevelopmentLanguageTypeEnum) => { + switch (language) { + case DevelopmentLanguageTypeEnum.JAVA: + return { label: 'Java', icon: , color: '#E76F00' }; + case DevelopmentLanguageTypeEnum.NODE_JS: + return { label: 'NodeJS', icon: , color: '#339933' }; + case DevelopmentLanguageTypeEnum.PYTHON: + return { label: 'Python', icon: , color: '#3776AB' }; + case DevelopmentLanguageTypeEnum.GO: + return { label: 'Go', icon: , color: '#00ADD8' }; + default: + return { label: language || '未知', icon: , color: '#666666' }; + } +}; const ApplicationList: React.FC = () => { + const tableRef = useRef>(null); const [modalVisible, setModalVisible] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [categoryDialogOpen, setCategoryDialogOpen] = useState(false); const [currentApplication, setCurrentApplication] = useState(); - const [list, setList] = useState([]); const [categories, setCategories] = useState([]); - const [loading, setLoading] = useState(false); - const [selectedCategoryId, setSelectedCategoryId] = useState(); - const [pagination, setPagination] = useState({ - pageNum: 0, - pageSize: DEFAULT_PAGE_SIZE, - totalElements: 0, - }); const [stats, setStats] = useState({ total: 0, enabled: 0, disabled: 0 }); - const { toast } = useToast(); - - const form = useForm({ - resolver: zodResolver(searchFormSchema), - defaultValues: { - appCode: '', - appName: '', - language: undefined, - enabled: undefined, - }, - }); // 加载分类列表 const loadCategories = async () => { @@ -99,50 +60,19 @@ const ApplicationList: React.FC = () => { loadCategories(); }, []); - const loadData = async (params?: ApplicationQuery) => { - setLoading(true); - try { - const queryParams: ApplicationQuery = { - ...params, - applicationCategoryId: selectedCategoryId, - pageNum: pagination.pageNum, - pageSize: pagination.pageSize, - }; - const data = await getApplicationPage(queryParams); - setList(data.content || []); - setPagination({ - ...pagination, - totalElements: data.totalElements, - }); - // 计算统计数据 - const all = data.content || []; - setStats({ - total: all.length, - enabled: all.filter((item) => item.enabled).length, - disabled: all.filter((item) => !item.enabled).length, - }); - } catch (error) { - toast({ - variant: 'destructive', - title: '获取应用列表失败', - duration: 3000, - }); - } finally { - setLoading(false); - } - }; - - const handlePageChange = (page: number) => { - setPagination({ - ...pagination, - pageNum: page, + // 包装 fetchFn,同时更新统计数据 + const fetchData = async (query: ApplicationQuery) => { + const result = await getApplicationPage(query); + // 更新统计 + const all = result.content || []; + setStats({ + total: all.length, + enabled: all.filter((item) => item.enabled).length, + disabled: all.filter((item) => !item.enabled).length, }); + return result; }; - useEffect(() => { - loadData(form.getValues()); - }, [selectedCategoryId, pagination.pageNum, pagination.pageSize]); - const handleAdd = () => { setCurrentApplication(undefined); setModalVisible(true); @@ -161,87 +91,71 @@ const ApplicationList: React.FC = () => { const handleSuccess = () => { setModalVisible(false); setCurrentApplication(undefined); - loadData(form.getValues()); + tableRef.current?.refresh(); }; - // 获取开发语言信息 - const getLanguageInfo = (language: DevelopmentLanguageTypeEnum) => { - switch (language) { - case DevelopmentLanguageTypeEnum.JAVA: - return { - label: 'Java', - icon: , - color: '#E76F00', - }; - case DevelopmentLanguageTypeEnum.NODE_JS: - return { - label: 'NodeJS', - icon: , - color: '#339933', - }; - case DevelopmentLanguageTypeEnum.PYTHON: - return { - label: 'Python', - icon: , - color: '#3776AB', - }; - case DevelopmentLanguageTypeEnum.GO: - return { - label: 'Go', - icon: , - color: '#00ADD8', - }; - default: - return { - label: language || '未知', - icon: , - color: '#666666', - }; - } - }; + // 搜索字段定义 + const searchFields: SearchFieldDef[] = useMemo(() => [ + { key: 'appCode', type: 'input', placeholder: '应用编码', width: 'w-[180px]' }, + { key: 'appName', type: 'input', placeholder: '应用名称', width: 'w-[180px]' }, + { + key: 'applicationCategoryId', + type: 'select', + placeholder: '全部分类', + width: 'w-[180px]', + options: categories.map(c => ({ label: c.name, value: c.id })), + }, + { + key: 'language', + type: 'select', + placeholder: '开发语言', + width: 'w-[150px]', + options: [ + { label: 'Java', value: DevelopmentLanguageTypeEnum.JAVA }, + { label: 'NodeJS', value: DevelopmentLanguageTypeEnum.NODE_JS }, + { label: 'Python', value: DevelopmentLanguageTypeEnum.PYTHON }, + { label: 'Go', value: DevelopmentLanguageTypeEnum.GO }, + ], + }, + { + key: 'enabled', + type: 'select', + placeholder: '状态', + width: 'w-[120px]', + options: [ + { label: '启用', value: 'true' }, + { label: '禁用', value: 'false' }, + ], + }, + ], [categories]); - const columns: Column[] = [ + // 列定义 + const columns: ColumnDef[] = useMemo(() => [ { - id: 'category', - header: '应用分类', - size: 120, - cell: ({ row }) => ( -
- {row.original.applicationCategory?.name || '-'} -
- ), + key: 'category', + title: '应用分类', + width: '120px', + render: (_, record) => record.applicationCategory?.name || '-', }, + { key: 'appCode', title: '应用编码', dataIndex: 'appCode', width: '200px' }, + { key: 'appName', title: '应用名称', dataIndex: 'appName', width: '130px' }, + { key: 'appDesc', title: '应用描述', dataIndex: 'appDesc', width: '180px' }, { - accessorKey: 'appCode', - header: '应用编码', - size: 200, - }, - { - accessorKey: 'appName', - header: '应用名称', - size: 130, - }, - { - accessorKey: 'appDesc', - header: '应用描述', - size: 180, - }, - { - id: 'teamCount', - header: '团队使用数量', - size: 120, - cell: ({ row }) => ( + key: 'teamCount', + title: '团队使用数量', + width: '120px', + render: (_, record) => (
- {row.original.teamCount || 0} + {record.teamCount || 0}
), }, { - accessorKey: 'language', - header: '开发语言', - size: 120, - cell: ({ row }) => { - const langInfo = getLanguageInfo(row.original.language); + key: 'language', + title: '开发语言', + width: '120px', + render: (_, record) => { + const langInfo = getLanguageInfo(record.language); return ( {langInfo.icon} @@ -251,46 +165,53 @@ const ApplicationList: React.FC = () => { }, }, { - accessorKey: 'enabled', - header: '状态', - size: 100, - cell: ({ row }) => ( - - {row.original.enabled ? '启用' : '禁用'} + key: 'enabled', + title: '状态', + width: '100px', + render: (_, record) => ( + + {record.enabled ? '启用' : '禁用'} ), }, + { key: 'sort', title: '排序', dataIndex: 'sort', width: '80px' }, { - accessorKey: 'sort', - header: '排序', - size: 80, - }, - { - id: 'actions', - header: '操作', - size: 180, - cell: ({ row }) => ( + key: 'actions', + title: '操作', + width: '180px', + sticky: true, + render: (_, record) => (
-
), }, - ]; + ], []); + + // 工具栏 + const toolbar = ( + <> + + + + ); return ( @@ -331,167 +252,24 @@ const ApplicationList: React.FC = () => { {/* 应用管理 */} -
-
- 应用管理 - - 创建和管理应用,支持Git集成和分类管理 - -
-
- - -
+
+ 应用管理 + + 创建和管理应用,支持Git集成和分类管理 +
- {/* 搜索过滤 */} -
- form.setValue('appCode', e.target.value)} - className="max-w-[200px]" - /> - form.setValue('appName', e.target.value)} - className="max-w-[200px]" - /> - - - - - -
- - {/* 数据表格 */} -
- - - - {columns.map((column) => ( - - {column.header} - - ))} - - - - {loading ? ( - - -
-
- 加载中... -
- - - ) : list.length === 0 ? ( - - - 暂无数据 - - - ) : ( - list.map((item) => ( - - {columns.map((column) => { - let cellContent; - if (column.cell) { - cellContent = column.cell({ row: { original: item } }); - } else if (column.accessorKey) { - const value = item[column.accessorKey]; - cellContent = value != null ? String(value) : ''; - } else { - cellContent = ''; - } - return ( - - {cellContent} - - ); - })} - - )) - )} - -
-
- -
-
+ + ref={tableRef} + fetchFn={fetchData} + columns={columns} + searchFields={searchFields} + toolbar={toolbar} + rowKey="id" + minWidth="1400px" + />