From 58e4cf27439d699c028089a0dc21012c20fd40cf Mon Sep 17 00:00:00 2001 From: asp_ly Date: Fri, 27 Dec 2024 21:51:01 +0800 Subject: [PATCH] 1 --- frontend/src/components/ui/table.tsx | 114 +++++++ .../pages/Deploy/ProjectGroup/List/index.tsx | 301 +++++++++--------- frontend/src/pages/System/User/index.tsx | 171 +++++----- frontend/src/pages/System/User/types.ts | 63 ++-- 4 files changed, 392 insertions(+), 257 deletions(-) create mode 100644 frontend/src/components/ui/table.tsx diff --git a/frontend/src/components/ui/table.tsx b/frontend/src/components/ui/table.tsx new file mode 100644 index 00000000..7e9b20f6 --- /dev/null +++ b/frontend/src/components/ui/table.tsx @@ -0,0 +1,114 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLTableProps +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLTableSectionProps +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLTableSectionProps +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLTableSectionProps +>(({ className, ...props }, ref) => ( + +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLTableRowProps +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} \ No newline at end of file diff --git a/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx b/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx index 377631d0..0cca560d 100644 --- a/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx +++ b/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx @@ -14,21 +14,52 @@ import type {ProjectGroup, ProjectGroupQueryParams} from './types'; import {ProjectGroupTypeEnum} from './types'; import {getProjectTypeInfo} from './utils'; import ProjectGroupModal from './components/ProjectGroupModal'; -import {ProTable} from '@ant-design/pro-components'; -import type {ProColumns, ActionType} from '@ant-design/pro-components'; +import { + Table, + TableHeader, + TableBody, + TableHead, + TableRow, + TableCell, +} from "@/components/ui/table"; + +interface Column { + accessorKey?: keyof ProjectGroup; + id?: string; + header: string; + size: number; + cell?: (props: { row: { original: ProjectGroup } }) => React.ReactNode; +} const ProjectGroupList: React.FC = () => { const [modalVisible, setModalVisible] = useState(false); const [currentProject, setCurrentProject] = useState(); - const actionRef = React.useRef(); + const [list, setList] = useState([]); + const [loading, setLoading] = useState(false); const [searchForm] = Form.useForm(); const {message: messageApi} = App.useApp(); + const loadData = async (params?: ProjectGroupQueryParams) => { + setLoading(true); + try { + const data = await getProjectGroupPage(params); + setList(data.content); + } catch (error) { + messageApi.error('加载数据失败'); + } finally { + setLoading(false); + } + }; + + React.useEffect(() => { + loadData(); + }, []); + const handleDelete = async (id: number) => { try { await deleteProjectGroup(id); messageApi.success('删除成功'); - actionRef.current?.reload(); + loadData(searchForm.getFieldsValue()); } catch (error) { messageApi.error('删除失败'); } @@ -52,45 +83,35 @@ const ProjectGroupList: React.FC = () => { const handleSuccess = () => { setModalVisible(false); setCurrentProject(undefined); - actionRef.current?.reload(); + loadData(searchForm.getFieldsValue()); }; const handleSearch = async (values: any) => { - actionRef.current?.reload(); + loadData(values); }; - const columns: ProColumns[] = [ + const columns: Column[] = [ { - title: '项目组编码', - dataIndex: 'projectGroupCode', - width: 120, - copyable: true, - ellipsis: true, - fixed: 'left', - filters: true, - filterSearch: true, - onFilter: (value: string, record) => record.projectGroupCode.toLowerCase().includes(value.toLowerCase()), + accessorKey: 'projectGroupCode', + header: '项目组编码', + size: 120, }, { - title: '项目组名称', - dataIndex: 'projectGroupName', - width: 150, - ellipsis: true, - filters: true, - filterSearch: true, - onFilter: (value: string, record) => record.projectGroupName.toLowerCase().includes(value.toLowerCase()), + accessorKey: 'projectGroupName', + header: '项目组名称', + size: 150, }, { - title: '项目组描述', - dataIndex: 'projectGroupDesc', - ellipsis: true, + accessorKey: 'projectGroupDesc', + header: '项目组描述', + size: 200, }, { - title: '项目组类型', - dataIndex: 'type', - width: 150, - render: (type) => { - const typeInfo = getProjectTypeInfo(type as ProjectGroupTypeEnum); + accessorKey: 'type', + header: '项目组类型', + size: 150, + cell: ({ row }) => { + const typeInfo = getProjectTypeInfo(row.original.type); return ( @@ -100,84 +121,76 @@ const ProjectGroupList: React.FC = () => { ); }, - filters: [ - {text: '产品型', value: ProjectGroupTypeEnum.PRODUCT}, - {text: '项目型', value: ProjectGroupTypeEnum.PROJECT}, - ], - filterMode: 'menu', - filtered: false, }, { - title: '状态', - dataIndex: 'enabled', - width: 100, - valueEnum: { - true: {text: '启用', status: 'Success'}, - false: {text: '禁用', status: 'Default'}, - }, + accessorKey: 'enabled', + header: '状态', + size: 100, + cell: ({ row }) => ( + + {row.original.enabled ? '启用' : '禁用'} + + ), }, { - title: '环境数量', - dataIndex: 'environments', - width: 100, - render: (_, record) => ( + accessorKey: 'totalEnvironments', + header: '环境数量', + size: 100, + cell: ({ row }) => ( - {record?.totalEnvironments || 0} + {row.original.totalEnvironments || 0} ), }, { - title: '项目数量', - dataIndex: 'applications', - width: 100, - render: (_, record) => ( + accessorKey: 'totalApplications', + header: '项目数量', + size: 100, + cell: ({ row }) => ( - {record?.totalApplications || 0} + {row.original.totalApplications || 0} ), }, { - title: '排序', - dataIndex: 'sort', - width: 80, - sorter: true, + accessorKey: 'sort', + header: '排序', + size: 80, }, { - title: '操作', - width: 180, - key: 'action', - valueType: 'option', - fixed: 'right', - render: (_, record) => [ - , - handleDelete(record.id)} - > + id: 'actions', + header: '操作', + size: 180, + cell: ({ row }) => ( + - - ], + handleDelete(row.original.id)} + > + + + + ), }, ]; @@ -233,75 +246,57 @@ const ProjectGroupList: React.FC = () => { - - columns={columns} - actionRef={actionRef} - scroll={{x: 'max-content'}} - cardBordered - rowKey="id" - search={false} - options={{ - setting: false, - density: false, - fullScreen: false, - reload: false, - }} - toolbar={{ - actions: [ - - ], - }} - form={{ - syncToUrl: true, - ignoreRules: false, - }} - pagination={{ - pageSize: 10, - showQuickJumper: true, - }} - request={async (params) => { - try { - const formValues = searchForm.getFieldsValue(); - const queryParams: ProjectGroupQueryParams = { - pageSize: params.pageSize, - pageNum: params.current, - projectGroupCode: formValues.projectGroupCode?.trim(), - projectGroupName: formValues.projectGroupName?.trim(), - type: formValues.type, - enabled: formValues.enabled, - }; - const data = await getProjectGroupPage(queryParams); - return { - data: data.content || [], - success: true, - total: data.totalElements || 0, - }; - } catch (error) { - messageApi.error('获取项目组列表失败'); - return { - data: [], - success: false, - total: 0, - }; - } - }} - /> - {modalVisible && ( - - )} + +
+ +
+ +
+ + + + {columns.map((column) => ( + + {column.header} + + ))} + + + + {list.map((row) => ( + + {columns.map((column) => ( + + {column.cell + ? column.cell({ row: { original: row } }) + : column.accessorKey + ? String(row[column.accessorKey]) + : null} + + ))} + + ))} + +
+
+
+ + ); }; diff --git a/frontend/src/pages/System/User/index.tsx b/frontend/src/pages/System/User/index.tsx index 2d0f7753..e9e67360 100644 --- a/frontend/src/pages/System/User/index.tsx +++ b/frontend/src/pages/System/User/index.tsx @@ -1,13 +1,28 @@ import React, { useEffect, useState } from 'react'; -import { Card, Table, Button, Modal, Form, Input, Space, message, Switch, TreeSelect, Select, Tag, Dropdown } from 'antd'; +import { Button, Modal, Form, Input, Space, message, Switch, TreeSelect, Select, Tag, Dropdown } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, TeamOutlined, MoreOutlined } from '@ant-design/icons'; -import type { ColumnsType } from 'antd/es/table'; import type { MenuProps } from 'antd'; import { useTableData } from '@/hooks/useTableData'; import * as service from './service'; import type { UserResponse, UserRequest, UserQuery, Role } from './types'; import type { DepartmentResponse } from '../Department/types'; import { getDepartmentTree } from '../Department/service'; +import { + Table, + TableHeader, + TableBody, + TableHead, + TableRow, + TableCell, +} from "@/components/ui/table"; + +interface Column { + accessorKey?: keyof UserResponse; + id?: string; + header: string; + size: number; + cell?: (props: { row: { original: UserResponse } }) => React.ReactNode; +} interface TreeNode { title: string; @@ -150,75 +165,68 @@ const UserPage: React.FC = () => { } }; - const columns: ColumnsType = [ + const columns: Column[] = [ { - title: 'ID', - dataIndex: 'id', - width: 60, - fixed: 'left', - sorter: true + accessorKey: 'id', + header: 'ID', + size: 60, }, { - title: '用户名', - dataIndex: 'username', - width: 100, - sorter: true + accessorKey: 'username', + header: '用户名', + size: 100, }, { - title: '昵称', - dataIndex: 'nickname', - width: 100, + accessorKey: 'nickname', + header: '昵称', + size: 100, }, { - title: '邮箱', - dataIndex: 'email', - width: 200, + accessorKey: 'email', + header: '邮箱', + size: 200, }, { - title: '部门', - dataIndex: 'departmentName', - width: 150, + accessorKey: 'departmentName', + header: '部门', + size: 150, }, { - title: '状态', - dataIndex: 'enabled', - width: 100, - render: (enabled: boolean) => ( - - {enabled ? '启用' : '禁用'} + accessorKey: 'enabled', + header: '状态', + size: 100, + cell: ({ row }) => ( + + {row.original.enabled ? '启用' : '禁用'} ), }, { - title: '手机号', - dataIndex: 'phone', - width: 120, + accessorKey: 'phone', + header: '手机号', + size: 120, }, { - title: '角色', - dataIndex: 'roles', - width: 120, - ellipsis: true, - render: (roles: Role[]) => roles?.map(role => role.name).join(', ') || '-' + header: '角色', + size: 120, + cell: ({ row }) => row.original.roles?.map(role => role.name).join(', ') || '-', }, { - title: '创建时间', - dataIndex: 'createTime', - width: 150, - sorter: true, + accessorKey: 'createTime', + header: '创建时间', + size: 150, }, { - title: '更新时间', - dataIndex: 'updateTime', - width: 150, - sorter: true + accessorKey: 'updateTime', + header: '更新时间', + size: 150, }, { - title: '操作', - key: 'action', - width: 180, - fixed: 'right', - render: (_, record) => { + id: 'actions', + header: '操作', + size: 180, + cell: ({ row }) => { + const record = row.original; const items: MenuProps['items'] = [ { key: 'resetPassword', @@ -235,7 +243,6 @@ const UserPage: React.FC = () => { } ]; - // 如果不是 admin 用户,添加删除选项 if (record.username !== 'admin') { items.push({ key: 'delete', @@ -255,40 +262,58 @@ const UserPage: React.FC = () => { > 编辑 - - - +
+
+ + + {columns.map((column) => ( + + {column.header} + + ))} + + + + {list.map((row) => ( + + {columns.map((column) => ( + + {column.cell + ? column.cell({ row: { original: row } }) + : column.accessorKey + ? String(row[column.accessorKey]) + : null} + + ))} + + ))} + +
+ { - + ); }; diff --git a/frontend/src/pages/System/User/types.ts b/frontend/src/pages/System/User/types.ts index 8ffb59f0..d52da2bd 100644 --- a/frontend/src/pages/System/User/types.ts +++ b/frontend/src/pages/System/User/types.ts @@ -1,46 +1,47 @@ -import type { BaseResponse } from '@/types/base/response'; -import {BaseQuery} from "@/types/base"; +import type { BaseResponse } from '@/types/base'; // 用户查询参数 -export interface UserQuery extends BaseQuery { - username?: string; - nickname?: string; - email?: string; - enabled?: boolean; +export interface UserQuery { + username?: string; + nickname?: string; + email?: string; + enabled?: boolean; + pageNum?: number; + pageSize?: number; + sortField?: string; + sortOrder?: string; } // 用户请求参数 export interface UserRequest { - username: string; - nickname?: string; - email?: string; - phone?: string; - password?: string; - enabled?: boolean; - departmentId?: number; + username: string; + nickname?: string; + email?: string; + phone?: string; + password?: string; + enabled?: boolean; + departmentId?: number; } // 角色类型定义 export interface Role { - id: number; - code: string; - name: string; - description?: string; - type: number; - sort: number; - enabled: boolean; + id: number; + code: string; + name: string; + description?: string; + type: number; + sort: number; + enabled: boolean; } // 用户响应类型 export interface UserResponse extends BaseResponse { - username: string; - nickname?: string; - email?: string; - phone?: string; - enabled: boolean; - roles: Role[]; - departmentId?: number; - departmentName?: string; - createTime: string; - updateTime: string; + username: string; + nickname?: string; + email?: string; + phone?: string; + enabled: boolean; + roles: Role[]; + departmentId?: number; + departmentName?: string; } \ No newline at end of file