增加三方接口管理

This commit is contained in:
戚辰先生 2024-12-02 23:37:30 +08:00
parent 9d825f3354
commit 9b16146cd0
10 changed files with 909 additions and 628 deletions

View File

@ -1,4 +1,6 @@
import React from 'react';
import * as AntdIcons from '@ant-design/icons';
import type { AntdIconProps } from '@ant-design/icons/lib/components/AntdIcon';
// 图标名称映射配置
export const iconMap: Record<string, string> = {
@ -15,7 +17,7 @@ export const iconMap: Record<string, string> = {
};
// 获取图标组件的通用函数
export const getIconComponent = (iconName: string | undefined) => {
export const getIconComponent = (iconName: string | undefined): React.ReactNode => {
if (!iconName) return null;
// 如果在映射中存在,使用映射的名称
@ -26,8 +28,8 @@ export const getIconComponent = (iconName: string | undefined) => {
? mappedName.charAt(0).toUpperCase() + mappedName.slice(1)
: `${mappedName.charAt(0).toUpperCase() + mappedName.slice(1)}Outlined`;
const Icon = (AntdIcons as any)[iconKey];
return Icon ? <Icon/> : null;
const Icon = (AntdIcons as Record<string, React.FC<AntdIconProps>>)[iconKey];
return Icon ? React.createElement(Icon) : null;
};
// 获取所有可用的图标列表
@ -36,6 +38,6 @@ export const getAvailableIcons = () => {
.filter(key => key.endsWith('Outlined'))
.map(key => ({
name: key,
component: (AntdIcons as any)[key]
component: (AntdIcons as Record<string, React.FC<AntdIconProps>>)[key]
}));
};

View File

@ -1,5 +1,6 @@
import React from 'react';
import * as AntdIcons from '@ant-design/icons';
import type { ReactNode } from 'react';
// 图标名称映射配置
export const iconMap: Record<string, string> = {
@ -12,12 +13,11 @@ export const iconMap: Record<string, string> = {
'department': 'TeamOutlined',
'role': 'UserSwitchOutlined',
'external': 'ApiOutlined',
'system': 'SettingOutlined',
'dashboard': 'DashboardOutlined'
'system': 'SettingOutlined'
};
// 获取图标组件的通用函数
export const getIconComponent = (iconName: string | undefined) => {
export const getIconComponent = (iconName: string | undefined): ReactNode => {
if (!iconName) return null;
// 如果在映射中存在,使用映射的名称
@ -29,7 +29,7 @@ export const getIconComponent = (iconName: string | undefined) => {
: `${mappedName.charAt(0).toUpperCase() + mappedName.slice(1)}Outlined`;
const Icon = (AntdIcons as any)[iconKey];
return Icon ? React.createElement(Icon) : null;
return Icon ? <Icon /> : null;
};
// 获取所有可用的图标列表

View File

@ -1,143 +1,202 @@
import {useState, useCallback, useEffect} from 'react';
import {message, Modal} from 'antd';
import type {TableState} from '@/types/table';
import {createInitialState} from '@/utils/table';
import request from '@/utils/request';
import type {Page} from '@/types/base/page';
import { useState, useCallback, useEffect } 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';
// 修改TableService接口
export interface TableService<T, P = any> {
baseUrl: string; // 基础URL
// 表格服务接口
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 SortParams {
sortField?: string;
sortOrder?: 'ascend' | 'descend';
// 表格状态
interface TableState<T> {
list: T[];
pagination: TablePaginationConfig;
loading: boolean;
selectedRowKeys?: React.Key[];
selectedRows?: T[];
}
export function useTableData<T extends { id: number }, P extends SortParams>({
service,
defaultParams,
config = {}
// 表格配置
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, P>;
defaultParams?: Partial<P>;
config?: any;
service: TableService<T, Q, C, U>;
defaultParams?: Partial<Q>;
config?: TableConfig;
}) {
const [state, setState] = useState<TableState<T>>(() =>
createInitialState(config.defaultPageSize || 10)
);
// 初始化状态
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
});
// 加载数据
const loadData = useCallback(async (params?: Partial<P>) => {
setState(prev => ({ ...prev, loading: true }));
try {
const pageData = await request.get<Page<T>>(`${service.baseUrl}/page`, {
params: {
...defaultParams,
...params,
pageNum: state.pagination.current,
pageSize: state.pagination.pageSize,
sortField: params?.sortField,
sortOrder: params?.sortOrder === 'ascend' ? 'asc' : params?.sortOrder === 'descend' ? 'desc' : undefined
}
});
// 加载数据
const loadData = useCallback(async (params?: Partial<Q>) => {
setState(prev => ({ ...prev, loading: true }));
try {
const pageData = await service.list({
...defaultParams,
...params,
pageNum: state.pagination.current,
pageSize: state.pagination.pageSize
});
setState(prev => ({
...prev,
list: pageData.content || [],
pagination: {
...prev.pagination,
total: pageData.totalElements || 0
},
loading: false
}));
} catch (error) {
setState(prev => ({ ...prev, loading: false }));
}
}, [service, defaultParams, state.pagination]);
setState(prev => ({
...prev,
list: pageData.content || [],
pagination: {
...prev.pagination,
total: pageData.totalElements || 0
},
loading: false
}));
} catch (error) {
setState(prev => ({ ...prev, loading: false }));
throw error;
}
}, [service, defaultParams, state.pagination]);
// CRUD操作
const handleCreate = useCallback(async (data: Partial<T>) => {
try {
await request.post<T>(service.baseUrl, data);
message.success("创建成功")
await loadData();
return true;
} catch (error) {
return false;
}
}, [service, loadData]);
// 表格变化处理
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 handleUpdate = useCallback(async (id: number, data: Partial<T>) => {
try {
await request.put<T>(`${service.baseUrl}/${id}`, data);
message.success("更新成功")
await loadData();
return true;
} catch (error) {
return false;
}
}, [service, loadData]);
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';
}
}
const handleDelete = useCallback(async (id: number) => {
return new Promise((resolve) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除该记录吗?',
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk: async () => {
try {
await request.delete(`${service.baseUrl}/${id}`);
message.success("删除成功")
loadData();
resolve(true);
} catch (error) {
resolve(false);
}
},
onCancel: () => resolve(false)
});
});
}, [service, loadData]);
// 处理筛选
Object.entries(filters).forEach(([key, value]) => {
if (value) {
params[key] = value;
}
});
// 分页变化
const onPageChange = useCallback((page: number, pageSize?: number) => {
setState(prev => ({
...prev,
pagination: {
...prev.pagination,
current: page,
pageSize: pageSize || prev.pagination.pageSize
}
}));
}, []);
loadData(params as Partial<Q>);
};
// 选择行
const onSelectChange = useCallback((selectedRowKeys: React.Key[], selectedRows: T[]) => {
if (!config.selection) return;
setState(prev => ({
...prev,
selectedRowKeys,
selectedRows
}));
}, [config.selection]);
// 创建
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) {
return false;
}
};
// 监听分页变化自动加载数据
useEffect(() => {
loadData();
}, [state.pagination.current, state.pagination.pageSize]);
// 更新
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) {
return false;
}
};
return {
...state,
loadData,
onPageChange,
handleCreate,
handleUpdate,
handleDelete,
onSelectChange,
reset: () => setState(createInitialState(config.defaultPageSize || 10))
};
// 删除
const handleDelete = async (id: number) => {
if (!service.delete) return false;
return new Promise<boolean>((resolve) => {
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) {
resolve(false);
}
},
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()
};
}

View File

@ -156,22 +156,13 @@ const BasicLayout: React.FC = () => {
// 将菜单数据转换为antd Menu需要的格式
const getMenuItems = (menuList: MenuResponse[]): MenuProps['items'] => {
return menuList
?.filter(menu => menu.type !== MenuTypeEnum.BUTTON && !menu.hidden) // 过滤掉按钮类型和隐藏的菜单
?.filter(menu => menu.type !== MenuTypeEnum.BUTTON && !menu.hidden)
.map(menu => {
// 确保path存在否则使用id
const key = menu.path || `menu-${menu.id}`;
// 如果有子菜单,递归处理子菜单
const children = menu.children && menu.children.length > 0
? getMenuItems(menu.children.filter(child =>
child.type !== MenuTypeEnum.BUTTON && !child.hidden))
: undefined;
return {
key,
icon: getIcon(menu.icon),
key: menu.path || String(menu.id),
icon: getIconComponent(menu.icon),
label: menu.name,
children: children
children: menu.children ? getMenuItems(menu.children) : undefined
};
});
};
@ -207,8 +198,6 @@ const BasicLayout: React.FC = () => {
);
}
console.log('Current location:', location.pathname);
console.log('Current menus:', menus);
return (
<Layout style={{minHeight: '100vh'}}>

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { Table, Button, Modal, Form, Input, Space, InputNumber, Switch, TreeSelect, Select, message } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { Table, Button, Modal, Form, Input, Space, InputNumber, Switch, TreeSelect, Select, message, Card } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, CaretDownOutlined, CaretRightOutlined } from '@ant-design/icons';
import type { DepartmentResponse } from './types';
import { getDepartmentTree } from './service';
import type { UserResponse } from '@/pages/System/User/types';
@ -23,12 +23,21 @@ const DepartmentPage: React.FC = () => {
const [editingDepartment, setEditingDepartment] = useState<DepartmentResponse | null>(null);
const [form] = Form.useForm();
// 处理树形数据,添加 hasChildren 属性
const processTreeData = (departments: DepartmentResponse[]) => {
return departments.map(dept => ({
...dept,
hasChildren: Boolean(dept.children?.length),
children: dept.children ? processTreeData(dept.children) : undefined
}));
};
// 加载部门树数据
const loadDepartmentTree = async () => {
setLoading(true);
try {
const response = await getDepartmentTree();
setDepartmentTree(response || []);
setDepartmentTree(processTreeData(response || []));
} catch (error) {
message.error('加载部门数据失败');
} finally {
@ -191,9 +200,9 @@ const DepartmentPage: React.FC = () => {
];
return (
<div style={{padding: '24px'}}>
<div style={{marginBottom: 16}}>
<Button type="primary" icon={<PlusOutlined/>} onClick={handleAdd}>
<Card>
<div style={{ marginBottom: 16 }}>
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
</Button>
</div>
@ -203,11 +212,24 @@ const DepartmentPage: React.FC = () => {
columns={columns}
dataSource={departmentTree}
rowKey="id"
scroll={{x: 1200}}
scroll={{ x: 1200 }}
pagination={false}
size="middle"
bordered
defaultExpandAllRows
expandable={{
showIcon: true,
expandIcon: ({ expanded, onExpand, record }) => {
if (!record.hasChildren) {
return null;
}
return expanded ? (
<CaretDownOutlined onClick={e => onExpand(record, e)} />
) : (
<CaretRightOutlined onClick={e => onExpand(record, e)} />
);
}
}}
/>
<Modal
@ -317,7 +339,7 @@ const DepartmentPage: React.FC = () => {
</Form.Item>
</Form>
</Modal>
</div>
</Card>
);
};

View File

@ -1,32 +1,116 @@
import React from 'react';
import { Card, Table, Button, Space, Modal, Form, Input, message } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import React, { useState } from 'react';
import { Card, Table, Button, Space, Modal, Form, Input, message, Select, InputNumber, Switch, Tag, Tooltip } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, SyncOutlined, CheckCircleOutlined, CloseCircleOutlined, LinkOutlined, MinusCircleOutlined } from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
interface ExternalSystem {
id: number;
name: string;
url: string;
description?: string;
createTime: string;
updateTime?: string;
}
import { useTableData } from '@/hooks/useTableData';
import * as service from './service';
import { SystemType, AuthType, SyncStatus, ExternalSystemResponse, ExternalSystemRequest, ExternalSystemQuery } from './types';
const ExternalPage: React.FC = () => {
const [loading, setLoading] = React.useState(false);
const [data, setData] = React.useState<ExternalSystem[]>([]);
const [form] = Form.useForm();
const [modalVisible, setModalVisible] = useState(false);
const [editingSystem, setEditingSystem] = useState<ExternalSystemResponse | null>(null);
const columns: ColumnsType<ExternalSystem> = [
const {
list,
loading,
pagination,
handleTableChange,
handleCreate,
handleUpdate,
handleDelete,
refresh
} = useTableData<ExternalSystemResponse, ExternalSystemQuery, ExternalSystemRequest, ExternalSystemRequest>({
service: {
list: service.getExternalSystems,
create: service.createExternalSystem,
update: service.updateExternalSystem,
delete: service.deleteExternalSystem
},
config: {
message: {
createSuccess: '创建系统成功',
updateSuccess: '更新系统成功',
deleteSuccess: '删除系统成功'
}
}
});
const handleAdd = () => {
setEditingSystem(null);
form.resetFields();
form.setFieldsValue({
enabled: true,
sort: 1,
authType: AuthType.BASIC
});
setModalVisible(true);
};
const handleEdit = (record: ExternalSystemResponse) => {
setEditingSystem(record);
form.setFieldsValue({
...record,
password: undefined // 不显示密码
});
setModalVisible(true);
};
const handleTestConnection = async (id: number) => {
try {
const success = await service.testConnection(id);
message.success(success ? '连接成功' : '连接失败');
} catch (error) {
message.error('测试连接失败');
}
};
const handleStatusChange = async (id: number, enabled: boolean) => {
try {
await service.updateStatus(id, enabled);
message.success('更新状态成功');
refresh();
} catch (error) {
message.error('更新状态失败');
}
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
if (editingSystem) {
await handleUpdate(editingSystem.id, values);
} else {
await handleCreate(values);
}
setModalVisible(false);
} catch (error) {
console.error('操作失败:', error);
}
};
const columns: ColumnsType<ExternalSystemResponse> = [
{
title: '系统名称',
dataIndex: 'name',
key: 'name',
width: 200,
},
{
title: '系统类型',
dataIndex: 'type',
width: 120,
render: (type: SystemType) => {
const typeMap = {
[SystemType.JENKINS]: 'Jenkins',
[SystemType.GIT]: 'Git',
[SystemType.ZENTAO]: '禅道'
};
return typeMap[type];
}
},
{
title: '系统地址',
dataIndex: 'url',
key: 'url',
width: 300,
render: (url: string) => (
<a href={url} target="_blank" rel="noopener noreferrer">
@ -35,41 +119,75 @@ const ExternalPage: React.FC = () => {
),
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
ellipsis: true,
title: '认证方式',
dataIndex: 'authType',
width: 120,
render: (authType: AuthType) => {
const authTypeMap = {
[AuthType.BASIC]: '用户名密码',
[AuthType.TOKEN]: '令牌',
[AuthType.OAUTH]: 'OAuth2'
};
return authTypeMap[authType];
}
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 180,
title: '同步状态',
dataIndex: 'syncStatus',
width: 120,
render: (status: SyncStatus, record) => {
const statusConfig = {
[SyncStatus.SUCCESS]: { icon: <CheckCircleOutlined />, color: 'success', text: '成功' },
[SyncStatus.FAILED]: { icon: <CloseCircleOutlined />, color: 'error', text: '失败' },
[SyncStatus.RUNNING]: { icon: <SyncOutlined spin />, color: 'processing', text: '同步中' },
NONE: { icon: <MinusCircleOutlined />, color: 'default', text: '未同步' }
};
const config = statusConfig[status] || statusConfig.NONE;
return (
<Tooltip title={record.lastSyncTime || '未同步'}>
<Tag icon={config.icon} color={config.color}>
{config.text}
</Tag>
</Tooltip>
);
}
},
{
title: '更新时间',
dataIndex: 'updateTime',
key: 'updateTime',
width: 180,
title: '状态',
dataIndex: 'enabled',
width: 100,
render: (enabled: boolean, record) => (
<Switch
checked={enabled}
onChange={(checked) => handleStatusChange(record.id, checked)}
/>
)
},
{
title: '操作',
key: 'action',
width: 180,
width: 280,
render: (_, record) => (
<Space>
<Button
type="link"
icon={<EditOutlined />}
onClick={() => message.info('编辑功能开发中')}
onClick={() => handleEdit(record)}
>
</Button>
<Button
type="link"
icon={<LinkOutlined />}
onClick={() => handleTestConnection(record.id)}
>
</Button>
<Button
type="link"
danger
icon={<DeleteOutlined />}
onClick={() => message.info('删除功能开发中')}
onClick={() => handleDelete(record.id)}
>
</Button>
@ -85,24 +203,140 @@ const ExternalPage: React.FC = () => {
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => message.info('新增功能开发中')}
onClick={handleAdd}
>
</Button>
</div>
<Table
columns={columns}
dataSource={data}
dataSource={list}
rowKey="id"
loading={loading}
pagination={{
total: data.length,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
}}
pagination={pagination}
onChange={handleTableChange}
/>
</Card>
<Modal
title={editingSystem ? '编辑系统' : '新增系统'}
open={modalVisible}
onOk={handleSubmit}
onCancel={() => setModalVisible(false)}
width={600}
>
<Form
form={form}
layout="vertical"
>
<Form.Item
name="name"
label="系统名称"
rules={[{ required: true, message: '请输入系统名称' }]}
>
<Input placeholder="请输入系统名称" />
</Form.Item>
<Form.Item
name="type"
label="系统类型"
rules={[{ required: true, message: '请选择系统类型' }]}
>
<Select>
<Select.Option value={SystemType.JENKINS}>Jenkins</Select.Option>
<Select.Option value={SystemType.GIT}>Git</Select.Option>
<Select.Option value={SystemType.ZENTAO}></Select.Option>
</Select>
</Form.Item>
<Form.Item
name="url"
label="系统地址"
rules={[
{ required: true, message: '请输入系统地址' },
{ type: 'url', message: '请输入有效的URL' }
]}
>
<Input placeholder="请输入系统地址" />
</Form.Item>
<Form.Item
name="authType"
label="认证方式"
rules={[{ required: true, message: '请选择认证方式' }]}
>
<Select>
<Select.Option value={AuthType.BASIC}></Select.Option>
<Select.Option value={AuthType.TOKEN}></Select.Option>
<Select.Option value={AuthType.OAUTH}>OAuth2</Select.Option>
</Select>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.authType !== currentValues.authType}
>
{({ getFieldValue }) => {
const authType = getFieldValue('authType');
if (authType === AuthType.BASIC) {
return (
<>
<Form.Item
name="username"
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input placeholder="请输入用户名" />
</Form.Item>
<Form.Item
name="password"
label="密码"
rules={[{ required: !editingSystem, message: '请输入密码' }]}
>
<Input.Password placeholder={editingSystem ? '不修改请留空' : '请输入密码'} />
</Form.Item>
</>
);
}
if (authType === AuthType.TOKEN) {
return (
<Form.Item
name="token"
label="访问令牌"
rules={[{ required: !editingSystem, message: '请输入访问令牌' }]}
>
<Input.Password placeholder={editingSystem ? '不修改请留空' : '请输入访问令牌'} />
</Form.Item>
);
}
return null;
}}
</Form.Item>
<Form.Item
name="sort"
label="显示排序"
rules={[{ required: true, message: '请输入显示排序' }]}
>
<InputNumber style={{ width: '100%' }} min={0} placeholder="请输入显示排序" />
</Form.Item>
<Form.Item
name="remark"
label="备注"
>
<Input.TextArea rows={4} placeholder="请输入备注" />
</Form.Item>
<Form.Item
name="enabled"
label="状态"
valuePropName="checked"
>
<Switch checkedChildren="启用" unCheckedChildren="禁用" />
</Form.Item>
</Form>
</Modal>
</div>
);
};

View File

@ -0,0 +1,29 @@
import request from '@/utils/request';
import type { Page } from '@/types/base/page';
import type { ExternalSystemResponse, ExternalSystemRequest, ExternalSystemQuery } from './types';
const BASE_URL = '/api/v1/external-system';
// 获取三方系统列表
export const getExternalSystems = (params: ExternalSystemQuery) =>
request.get<Page<ExternalSystemResponse>>(`${BASE_URL}/page`, { params });
// 创建三方系统
export const createExternalSystem = (data: ExternalSystemRequest) =>
request.post<ExternalSystemResponse>(BASE_URL, data);
// 更新三方系统
export const updateExternalSystem = (id: number, data: ExternalSystemRequest) =>
request.put<ExternalSystemResponse>(`${BASE_URL}/${id}`, data);
// 删除三方系统
export const deleteExternalSystem = (id: number) =>
request.delete(`${BASE_URL}/${id}`);
// 测试连接
export const testConnection = (id: number) =>
request.get<boolean>(`${BASE_URL}/${id}/test-connection`);
// 更新状态
export const updateStatus = (id: number, enabled: boolean) =>
request.put(`${BASE_URL}/${id}/status`, null, { params: { enabled } });

View File

@ -0,0 +1,62 @@
import { BaseResponse } from '@/types/base/response';
import { BaseQuery } from '@/types/base/query';
// 系统类型枚举
export enum SystemType {
JENKINS = 'JENKINS',
GIT = 'GIT',
ZENTAO = 'ZENTAO'
}
// 认证方式枚举
export enum AuthType {
BASIC = 'BASIC',
TOKEN = 'TOKEN',
OAUTH = 'OAUTH'
}
// 同步状态枚举
export enum SyncStatus {
SUCCESS = 'SUCCESS',
FAILED = 'FAILED',
RUNNING = 'RUNNING'
}
// 查询参数接口
export interface ExternalSystemQuery extends BaseQuery {
name?: string;
type?: SystemType;
enabled?: boolean;
}
// 响应数据接口
export interface ExternalSystemResponse extends BaseResponse {
name: string;
type: SystemType;
url: string;
remark?: string;
sort: number;
enabled: boolean;
authType: AuthType;
username?: string;
password?: string;
token?: string;
syncStatus: SyncStatus;
lastSyncTime?: string;
config?: string;
}
// 请求数据接口
export interface ExternalSystemRequest {
name: string;
type: SystemType;
url: string;
remark?: string;
sort: number;
enabled: boolean;
authType: AuthType;
username?: string;
password?: string;
token?: string;
config?: string;
}

View File

@ -1,58 +1,46 @@
import React, { useEffect, useState } from 'react';
import { Table, Button, Modal, Form, Input, Space, Switch, Select, TreeSelect, Tooltip, InputNumber, Tree } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, QuestionCircleOutlined, FolderOutlined, MenuOutlined, ToolOutlined, CaretRightOutlined } from '@ant-design/icons';
import type { TablePaginationConfig } from 'antd/es/table';
import type { FilterValue, SorterResult } from 'antd/es/table/interface';
import { getMenuTree, getCurrentUserMenus } from './service';
import type { MenuResponse } from './types';
import { MenuTypeEnum } from './types';
import { Card, Table, Button, Modal, Form, Input, Space, message, Select, TreeSelect, Tooltip, Switch } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import * as service from './service';
import { MenuTypeEnum, MenuResponse, MenuRequest } from './types';
import IconSelect from '@/components/IconSelect';
import { useTableData } from '@/hooks/useTableData';
import * as AntdIcons from '@ant-design/icons';
import {FixedType} from "rc-table/lib/interface";
import { getIconComponent } from '@/config/icons.tsx';
import { useDispatch } from 'react-redux';
import { setMenus } from '@/store/userSlice';
import { message } from 'antd';
import { getIconComponent } from '@/config/icons.tsx';
const MenuPage: React.FC = () => {
const dispatch = useDispatch();
const {
list: menus,
loading,
loadData: fetchMenus,
handleCreate,
handleUpdate,
handleDelete
} = useTableData({
service: {
baseUrl: '/api/v1/menu'
},
defaultParams: {
sortField: 'sort',
sortOrder: 'asc'
}
});
const [form] = Form.useForm();
const [modalVisible, setModalVisible] = useState(false);
const [editingMenu, setEditingMenu] = useState<MenuResponse | null>(null);
const [menuTree, setMenuTree] = useState<MenuResponse[]>([]);
const [iconSelectVisible, setIconSelectVisible] = useState(false);
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [menuTree, setMenuTree] = useState<MenuResponse[]>([]);
// 加载菜单树
const loadMenuTree = async () => {
try {
setLoading(true);
const data = await service.getMenuTree();
setMenuTree(data);
} catch (error) {
message.error('加载菜单数据失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
getMenuTree().then(menus => setMenuTree(menus));
loadMenuTree();
}, []);
const handleAdd = () => {
setEditingMenu(null);
form.resetFields();
const maxSort = Math.max(0, ...menus.map(menu => menu.sort));
form.setFieldsValue({
type: MenuTypeEnum.MENU,
sort: maxSort + 10,
sort: 1,
hidden: false
});
setModalVisible(true);
@ -67,45 +55,50 @@ const MenuPage: React.FC = () => {
setModalVisible(true);
};
const handleTableChange = (
pagination: TablePaginationConfig,
filters: Record<string, FilterValue | null>,
sorter: SorterResult<MenuResponse> | SorterResult<MenuResponse>[]
) => {
const { field, order } = Array.isArray(sorter) ? sorter[0] : sorter;
fetchMenus({
sortField: field as string,
sortOrder: order
});
const handleDelete = async (id: number) => {
try {
await service.deleteMenu(id);
message.success('删除成功');
loadMenuTree();
await updateReduxMenus();
} catch (error) {
message.error('删除失败');
}
};
const buildMenuTree = (menuList: MenuResponse[]): MenuResponse[] => {
const menuMap = new Map<number, MenuResponse>();
const result: MenuResponse[] = [];
menuList.forEach(menu => {
menuMap.set(menu.id, { ...menu });
});
menuList.forEach(menu => {
const node = menuMap.get(menu.id)!;
if (menu.parentId === 0 || !menuMap.has(menu.parentId)) {
result.push(node);
const handleSubmit = async () => {
try {
const values = await form.validateFields();
if (editingMenu) {
await service.updateMenu(editingMenu.id, {
...values,
version: editingMenu.version
});
message.success('更新成功');
} else {
const parent = menuMap.get(menu.parentId)!;
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
await service.createMenu(values);
message.success('创建成功');
}
});
return result;
setModalVisible(false);
loadMenuTree();
await updateReduxMenus();
} catch (error) {
message.error('操作失败');
}
};
const getTreeSelectData = () => {
const menuTree = buildMenuTree(menus);
return menuTree.map(menu => ({
// 更新 Redux 中的菜单数据
const updateReduxMenus = async () => {
try {
const menus = await service.getCurrentUserMenus();
dispatch(setMenus(menus));
} catch (error) {
console.error('更新菜单数据失败:', error);
}
};
const getTreeSelectData = (menuList: MenuResponse[]) => {
return menuList.map(menu => ({
title: menu.name,
value: menu.id,
children: menu.children?.map(child => ({
@ -116,79 +109,34 @@ const MenuPage: React.FC = () => {
}));
};
const updateReduxMenus = async () => {
try {
const menus = await getCurrentUserMenus();
dispatch(setMenus(menus));
} catch (error) {
console.error('更新菜单数据失败:', error);
}
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
if (editingMenu) {
const success = await handleUpdate(editingMenu.id, {
...values,
version: editingMenu.version
});
if (success) {
setModalVisible(false);
fetchMenus();
await updateReduxMenus();
message.success('更新成功');
}
} else {
const success = await handleCreate(values);
if (success) {
setModalVisible(false);
fetchMenus();
await updateReduxMenus();
message.success('创建成功');
}
}
} catch (error) {
console.error('操作失败:', error);
}
};
const handleMenuDelete = async (id: number) => {
try {
const success = await handleDelete(id);
if (success) {
fetchMenus();
await updateReduxMenus();
message.success('删除成功');
}
} catch (error) {
console.error('删除失败:', error);
}
};
const getIcon = getIconComponent;
const columns = [
const columns: ColumnsType<MenuResponse> = [
{
title: '菜单名称',
dataIndex: 'name',
key: 'name',
width: 250,
fixed: 'left' as FixedType,
sorter: true
width: 200
},
{
title: '图标',
dataIndex: 'icon',
key: 'icon',
width: 80,
render: (icon: string) => getIcon(icon)
width: 100,
render: (icon: string) => {
return getIconComponent(icon);
}
},
{
title: '类型',
title: '路由地址',
dataIndex: 'path',
width: 200
},
{
title: '组件路径',
dataIndex: 'component',
width: 200
},
{
title: '菜单类型',
dataIndex: 'type',
key: 'type',
width: 100,
width: 120,
render: (type: MenuTypeEnum) => {
const typeMap = {
[MenuTypeEnum.DIRECTORY]: '目录',
@ -199,63 +147,28 @@ const MenuPage: React.FC = () => {
}
},
{
title: '路由地址',
dataIndex: 'path',
key: 'path',
width: 200,
ellipsis: true
},
{
title: '组件路径',
dataIndex: 'component',
key: 'component',
width: 200,
ellipsis: true
},
{
title: '权限标识',
dataIndex: 'permission',
key: 'permission',
width: 150,
ellipsis: true
},
{
title: '排序',
title: '显示排序',
dataIndex: 'sort',
key: 'sort',
width: 80,
sorter: true
},
{
title: '状态',
dataIndex: 'enabled',
key: 'enabled',
width: 80,
render: (enabled: boolean) => (
<Switch checked={enabled} disabled size="small"/>
)
width: 100
},
{
title: '操作',
key: 'action',
width: 160,
fixed: 'right' as FixedType,
render: (_: any, record: MenuResponse) => (
<Space size={0}>
width: 200,
render: (_, record) => (
<Space>
<Button
type="link"
size="small"
icon={<EditOutlined/>}
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
</Button>
<Button
type="link"
size="small"
danger
icon={<DeleteOutlined/>}
onClick={() => handleMenuDelete(record.id)}
icon={<DeleteOutlined />}
onClick={() => handleDelete(record.id)}
disabled={record.children?.length > 0}
>
@ -266,18 +179,22 @@ const MenuPage: React.FC = () => {
];
return (
<div style={{padding: '24px'}}>
<div style={{marginBottom: 16}}>
<Button type="primary" icon={<PlusOutlined/>} onClick={handleAdd}>
<Card>
<div style={{ marginBottom: 16 }}>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={handleAdd}
>
</Button>
</div>
<Table
loading={loading}
columns={columns}
dataSource={menuTree}
rowKey={(record) => String(record.id)}
rowKey="id"
loading={loading}
pagination={false}
size="middle"
bordered
@ -290,21 +207,11 @@ const MenuPage: React.FC = () => {
onOk={handleSubmit}
onCancel={() => setModalVisible(false)}
width={600}
destroyOnClose
>
<Form
form={form}
layout="vertical"
initialValues={{ type: MenuTypeEnum.MENU, sort: 0 }}
>
<Form.Item
name="name"
label="菜单名称"
rules={[{ required: true, message: '请输入菜单名称' }]}
>
<Input placeholder="请输入菜单名称" />
</Form.Item>
<Form.Item
name="type"
label="菜单类型"
@ -317,6 +224,14 @@ const MenuPage: React.FC = () => {
</Select>
</Form.Item>
<Form.Item
name="name"
label="菜单名称"
rules={[{ required: true, message: '请输入菜单名称' }]}
>
<Input placeholder="请输入菜单名称" />
</Form.Item>
<Form.Item
name="parentId"
label={
@ -329,7 +244,7 @@ const MenuPage: React.FC = () => {
}
>
<TreeSelect
treeData={getTreeSelectData()}
treeData={getTreeSelectData(menuTree)}
placeholder="不选择则为顶级菜单"
allowClear
treeDefaultExpandAll
@ -339,62 +254,64 @@ const MenuPage: React.FC = () => {
</Form.Item>
<Form.Item
name="path"
label="路由地址"
rules={[{ required: true, message: '请输入路由地址' }]}
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.type !== currentValues.type}
>
<Input placeholder="请输入路由地址" />
</Form.Item>
{({ getFieldValue }) => {
const type = getFieldValue('type');
if (type === MenuTypeEnum.BUTTON) {
return null;
}
return (
<>
<Form.Item
name="icon"
label="菜单图标"
>
<Input
placeholder="请选择图标"
readOnly
onClick={() => setIconSelectVisible(true)}
suffix={getIconComponent(form.getFieldValue('icon'))}
/>
</Form.Item>
<Form.Item
name="component"
label="组件路径"
>
<Input placeholder="请输入组件路径" />
</Form.Item>
<Form.Item
name="path"
label="路由地址"
rules={[{ required: true, message: '请输入路由地址' }]}
>
<Input placeholder="请输入路由地址" />
</Form.Item>
<Form.Item
name="permission"
label="权限标识"
>
<Input placeholder="请输入权限标识" />
</Form.Item>
<Form.Item
name="icon"
label="图标"
>
<Input
placeholder="请选择图标"
readOnly
onClick={() => setIconSelectVisible(true)}
suffix={form.getFieldValue('icon') && getIcon(form.getFieldValue('icon'))}
/>
{type === MenuTypeEnum.MENU && (
<Form.Item
name="component"
label="组件路径"
rules={[{ required: true, message: '请输入组件路径' }]}
>
<Input placeholder="请输入组件路径" />
</Form.Item>
)}
</>
);
}}
</Form.Item>
<Form.Item
name="sort"
label="显示排序"
tooltip="值越大排序越靠后,默认为当前最大值+10"
rules={[{ required: true, message: '请输显示排序' }]}
rules={[{ required: true, message: '请输入显示排序' }]}
>
<InputNumber
style={{ width: '100%' }}
min={0}
placeholder="请输入显示排序"
/>
<Input type="number" placeholder="请输入显示排序" />
</Form.Item>
<Form.Item
name="hidden"
label="隐藏菜单"
label="是否隐藏"
valuePropName="checked"
tooltip="设置为是则该菜单不会显示在导航栏中"
>
<Switch
checkedChildren="是"
unCheckedChildren="否"
/>
<Switch checkedChildren="是" unCheckedChildren="否" />
</Form.Item>
</Form>
</Modal>
@ -408,7 +325,7 @@ const MenuPage: React.FC = () => {
setIconSelectVisible(false);
}}
/>
</div>
</Card>
);
};

View File

@ -1,15 +1,13 @@
import React, {useEffect, useState} from 'react';
import {Table, Button, Modal, Form, Input, Space, message, Switch, TreeSelect, Select, Tag} from 'antd';
import {PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, TeamOutlined} from '@ant-design/icons';
import type {UserResponse, Role} from './types';
import type {DepartmentDTO} from '../Department/types';
import {resetPassword, assignRoles, getAllRoles} from './service';
import {useTableData} from '@/hooks/useTableData';
import type {FixedType, AlignType, SortOrder} from 'rc-table/lib/interface';
import {Response} from "@/utils/request.ts";
import {TablePaginationConfig} from "antd/es/table";
import {FilterValue, SorterResult} from "antd/es/table/interface";
import { getDepartmentTree } from '../Department/service'; // 导入部门树接口
import React, { useEffect, useState } from 'react';
import { Card, Table, 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';
interface TreeNode {
title: string;
@ -18,39 +16,68 @@ interface TreeNode {
}
const UserPage: React.FC = () => {
const {
list: users,
pagination,
loading,
loadData: fetchUsers,
handleCreate,
handleUpdate,
handleDelete
} = useTableData({
service: {
baseUrl: '/api/v1/user'
},
defaultParams: {
sortField: 'createTime',
sortOrder: 'descend'
}
});
const [departments, setDepartments] = useState<DepartmentDTO[]>([]);
const [form] = Form.useForm();
const [passwordForm] = Form.useForm();
const [modalVisible, setModalVisible] = useState(false);
const [resetPasswordModalVisible, setResetPasswordModalVisible] = useState(false);
const [editingUser, setEditingUser] = useState<UserResponse | null>(null);
const [form] = Form.useForm();
const [passwordForm] = Form.useForm();
const [departments, setDepartments] = useState<DepartmentResponse[]>([]);
const [roleModalVisible, setRoleModalVisible] = useState(false);
const [selectedUser, setSelectedUser] = useState<UserResponse | null>(null);
const [selectedRoles, setSelectedRoles] = useState<number[]>([]);
const [allRoles, setAllRoles] = useState<RoleResponse[]>([]);
const [allRoles, setAllRoles] = useState<Role[]>([]);
const {
list,
loading,
pagination,
handleTableChange,
handleCreate,
handleUpdate,
handleDelete,
refresh
} = useTableData<UserResponse, UserQuery, UserRequest, UserRequest>({
service: {
list: service.getUsers,
create: service.createUser,
update: service.updateUser,
delete: service.deleteUser
},
defaultParams: {
sortField: 'createTime',
sortOrder: 'desc'
},
config: {
message: {
createSuccess: '创建用户成功',
updateSuccess: '更新用户成功',
deleteSuccess: '删除用户成功'
}
}
});
useEffect(() => {
getAllRoles().then(roles => setAllRoles(roles));
service.getAllRoles().then(roles => setAllRoles(roles));
loadDepartmentTree();
}, []);
const loadDepartmentTree = async () => {
try {
const data = await getDepartmentTree();
setDepartments(data);
} catch (error) {
message.error('加载部门数据失败');
}
};
const getTreeData = (departments: DepartmentResponse[]): TreeNode[] => {
return departments.map(dept => ({
title: dept.name,
value: dept.id,
children: dept.children ? getTreeData(dept.children) : undefined
}));
};
const handleAdd = () => {
setEditingUser(null);
form.resetFields();
@ -63,7 +90,8 @@ const UserPage: React.FC = () => {
const handleEdit = (record: UserResponse) => {
setEditingUser(record);
form.setFieldsValue({
...record
...record,
password: undefined // 不显示密码
});
setModalVisible(true);
};
@ -78,64 +106,31 @@ const UserPage: React.FC = () => {
try {
const values = await passwordForm.validateFields();
if (editingUser) {
await resetPassword(editingUser.id, values.password);
await service.resetPassword(editingUser.id, values.password);
message.success('密码重置成功');
setResetPasswordModalVisible(false);
refresh();
}
} catch (error: any) {
message.error(error.message || '密码重置失败');
}
};
const handleSubmit = async (values: any) => {
const handleSubmit = async () => {
try {
const values = await form.validateFields();
if (editingUser) {
const success = await handleUpdate(editingUser.id, {
...values,
enabled: values.enabled ?? true
});
if (success) {
message.success('更新成功');
setModalVisible(false);
fetchUsers();
}
await handleUpdate(editingUser.id, values);
} else {
const success = await handleCreate({
...values,
enabled: values.enabled ?? true
});
if (success) {
message.success('创建成功');
setModalVisible(false);
fetchUsers();
}
await handleCreate(values);
}
setModalVisible(false);
refresh();
} catch (error: any) {
message.error(error.message || '操作失败');
}
};
const loadDepartmentTree = async () => {
try {
const data = await getDepartmentTree();
setDepartments(data);
} catch (error) {
message.error('加载部门数据失败');
}
};
useEffect(() => {
loadDepartmentTree();
}, []);
const getTreeData = (departments: DepartmentDTO[]): TreeNode[] => {
return departments.map(dept => ({
title: dept.name,
value: dept.id,
children: dept.children ? getTreeData(dept.children) : undefined
}));
};
const handleAssignRoles = (record: UserResponse) => {
setSelectedUser(record);
setSelectedRoles(record.roles?.map(role => role.id) || []);
@ -145,56 +140,48 @@ const UserPage: React.FC = () => {
const handleAssignRoleSubmit = async () => {
if (selectedUser) {
try {
await assignRoles(selectedUser.id, selectedRoles);
message.success("角色分配成功")
await service.assignRoles(selectedUser.id, selectedRoles);
message.success('角色分配成功');
setRoleModalVisible(false);
fetchUsers();
refresh();
} catch (error) {
message.error("角色分配失败")
console.log(error);
message.error('角色分配失败');
}
}
};
const columns = [
const columns: ColumnsType<UserResponse> = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 60,
fixed: 'left' as FixedType,
fixed: 'left',
sorter: true
},
{
title: '用户名',
dataIndex: 'username',
key: 'username',
width: 100,
sorter: true
},
{
title: '昵称',
dataIndex: 'nickname',
key: 'nickname',
width: 100,
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email',
width: 200,
},
{
title: '部门',
dataIndex: 'departmentName',
key: 'departmentName',
width: 150,
},
{
title: '状态',
dataIndex: 'enabled',
key: 'enabled',
width: 100,
render: (enabled: boolean) => (
<Tag color={enabled ? 'success' : 'error'}>
@ -205,13 +192,11 @@ const UserPage: React.FC = () => {
{
title: '手机号',
dataIndex: 'phone',
key: 'phone',
width: 120,
},
{
title: '角色',
dataIndex: 'roles',
key: 'roles',
width: 120,
ellipsis: true,
render: (roles: Role[]) => roles?.map(role => role.name).join(', ') || '-'
@ -219,104 +204,98 @@ const UserPage: React.FC = () => {
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 150,
sorter: true,
defaultSortOrder: 'descend' as SortOrder
},
{
title: '更新时间',
dataIndex: 'updateTime',
key: 'updateTime',
width: 150,
sorter: true
},
{
title: '操作',
key: 'action',
width: 320,
fixed: 'right' as FixedType,
render: (_: any, record: UserResponse) => (
<Space size={0}>
<Button
type="link"
size="small"
icon={<EditOutlined/>}
onClick={() => handleEdit(record)}
>
</Button>
<Button
type="link"
size="small"
icon={<KeyOutlined/>}
onClick={() => handleResetPassword(record)}
>
</Button>
<Button
type="link"
size="small"
danger
icon={<DeleteOutlined/>}
onClick={() => handleDelete(record.id)}
disabled={record.username === 'admin'}
>
</Button>
<Button
type="link"
size="small"
icon={<TeamOutlined/>}
onClick={() => handleAssignRoles(record)}
disabled={record.username === 'admin'}
>
</Button>
</Space>
),
width: 180,
fixed: 'right',
render: (_, record) => {
const items: MenuProps['items'] = [
{
key: 'resetPassword',
icon: <KeyOutlined />,
label: '重置密码',
onClick: () => handleResetPassword(record)
},
{
key: 'assignRoles',
icon: <TeamOutlined />,
label: '分配角色',
onClick: () => handleAssignRoles(record),
disabled: record.username === 'admin'
}
];
// 如果不是 admin 用户,添加删除选项
if (record.username !== 'admin') {
items.push({
key: 'delete',
icon: <DeleteOutlined />,
label: '删除',
danger: true,
onClick: () => handleDelete(record.id)
});
}
return (
<Space>
<Button
type="link"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
</Button>
<Dropdown
menu={{ items }}
placement="bottomRight"
trigger={['click']}
>
<Button
type="text"
icon={<MoreOutlined />}
style={{ padding: '4px 8px' }}
/>
</Dropdown>
</Space>
);
}
},
];
const handleTableChange = (
pagination: TablePaginationConfig,
filters: Record<string, FilterValue | null>,
sorter: SorterResult<UserResponse> | SorterResult<UserResponse>[]
) => {
const {field, order} = Array.isArray(sorter) ? sorter[0] : sorter;
fetchUsers({
sortField: field as string,
sortOrder: order
});
};
return (
<div style={{padding: '24px'}}>
<div style={{marginBottom: 16}}>
<Button type="primary" icon={<PlusOutlined/>} onClick={handleAdd}>
<Card>
<div style={{ marginBottom: 16 }}>
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
</Button>
</div>
<Table
loading={loading}
columns={columns}
dataSource={users}
dataSource={list}
rowKey="id"
scroll={{x: 1500}}
loading={loading}
scroll={{ x: 1500 }}
pagination={pagination}
onChange={handleTableChange}
size="middle"
bordered
/>
<Modal
title={editingUser ? '编辑用户' : '新增用户'}
open={modalVisible}
onOk={() => form.validateFields().then(handleSubmit)}
onOk={handleSubmit}
onCancel={() => setModalVisible(false)}
width={600}
destroyOnClose
>
<Form
form={form}
@ -325,9 +304,9 @@ const UserPage: React.FC = () => {
<Form.Item
name="username"
label="用户名"
rules={[{required: true, message: '请输入用户名'}]}
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input placeholder="请输入用户名" disabled={!!editingUser}/>
<Input placeholder="请输入用户名" disabled={!!editingUser} />
</Form.Item>
{!editingUser && (
@ -335,11 +314,11 @@ const UserPage: React.FC = () => {
name="password"
label="密码"
rules={[
{required: true, message: '请输入密码'},
{min: 6, message: '密码长度不能小于6位'}
{ required: true, message: '请输入密码' },
{ min: 6, message: '密码长度不能小于6位' }
]}
>
<Input.Password placeholder="请输入密码"/>
<Input.Password placeholder="请输入密码" />
</Form.Item>
)}
@ -347,22 +326,28 @@ const UserPage: React.FC = () => {
name="nickname"
label="昵称"
>
<Input placeholder="请输入昵称"/>
<Input placeholder="请输入昵称" />
</Form.Item>
<Form.Item
name="email"
label="邮箱"
rules={[{type: 'email', message: '请输入正确的邮箱格式'}]}
rules={[{ type: 'email', message: '请输入正确的邮箱格式' }]}
>
<Input placeholder="请输入邮箱"/>
<Input placeholder="请输入邮箱" />
</Form.Item>
<Form.Item
name="phone"
label="手机号"
>
<Input placeholder="请输入手机号"/>
<Input placeholder="请输入手机号" />
</Form.Item>
<Form.Item name="departmentId" label="所属部门">
<Form.Item
name="departmentId"
label="所属部门"
>
<TreeSelect
treeData={getTreeData(departments)}
placeholder="请选择所属部门"
@ -372,41 +357,23 @@ const UserPage: React.FC = () => {
treeNodeFilterProp="title"
/>
</Form.Item>
<Form.Item
name="enabled"
label="状态"
valuePropName="checked"
initialValue={true}
>
<Switch/>
<Switch checkedChildren="启用" unCheckedChildren="禁用" />
</Form.Item>
</Form>
</Modal>
{/* RoleModal
{selectedUser && (
<RoleModal
userId={selectedUser.id}
visible={roleModalVisible}
onCancel={() => {
setRoleModalVisible(false);
setSelectedUser(null);
}}
onSuccess={() => {
setRoleModalVisible(false);
setSelectedUser(null);
fetchUsers();
}}
/>
)}
*/}
<Modal
title="重置密码"
open={resetPasswordModalVisible}
onOk={handleResetPasswordSubmit}
onCancel={() => setResetPasswordModalVisible(false)}
destroyOnClose
>
<Form
form={passwordForm}
@ -416,19 +383,20 @@ const UserPage: React.FC = () => {
name="password"
label="新密码"
rules={[
{required: true, message: '请输入新密码'},
{min: 6, message: '密码长度不能小于6位'}
{ required: true, message: '请输入新密码' },
{ min: 6, message: '密码长度不能小于6位' }
]}
>
<Input.Password placeholder="请输入新密码"/>
<Input.Password placeholder="请输入新密码" />
</Form.Item>
<Form.Item
name="confirmPassword"
label="确认密码"
dependencies={['password']}
rules={[
{required: true, message: '请确认新密码'},
({getFieldValue}) => ({
{ required: true, message: '请确认新密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
@ -438,7 +406,7 @@ const UserPage: React.FC = () => {
}),
]}
>
<Input.Password placeholder="请确认新密码"/>
<Input.Password placeholder="请确认新密码" />
</Form.Item>
</Form>
</Modal>
@ -449,7 +417,6 @@ const UserPage: React.FC = () => {
onOk={handleAssignRoleSubmit}
onCancel={() => setRoleModalVisible(false)}
width={600}
destroyOnClose
>
<Form layout="vertical">
<Form.Item label="选择角色">
@ -458,7 +425,7 @@ const UserPage: React.FC = () => {
placeholder="请选择角色"
value={selectedRoles}
onChange={setSelectedRoles}
style={{width: '100%'}}
style={{ width: '100%' }}
>
{allRoles.map(role => (
<Select.Option key={role.id} value={role.id}>
@ -469,7 +436,7 @@ const UserPage: React.FC = () => {
</Form.Item>
</Form>
</Modal>
</div>
</Card>
);
};