增加三方接口管理
This commit is contained in:
parent
9d825f3354
commit
9b16146cd0
@ -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]
|
||||
}));
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
|
||||
// 获取所有可用的图标列表
|
||||
|
||||
@ -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()
|
||||
};
|
||||
}
|
||||
@ -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'}}>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
314
frontend/src/pages/System/External/index.tsx
vendored
314
frontend/src/pages/System/External/index.tsx
vendored
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
29
frontend/src/pages/System/External/service.ts
vendored
Normal file
29
frontend/src/pages/System/External/service.ts
vendored
Normal 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 } });
|
||||
62
frontend/src/pages/System/External/types.ts
vendored
Normal file
62
frontend/src/pages/System/External/types.ts
vendored
Normal 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;
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user