deploy-ease-platform/frontend/src/pages/System/Menu/index.tsx
2024-11-30 17:24:12 +08:00

357 lines
9.8 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { Table, Button, Modal, Form, Input, Space, message, Switch, Select, TreeSelect, Tooltip, InputNumber } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import * as AntdIcons from '@ant-design/icons';
import { getMenuTree, createMenu, updateMenu, deleteMenu } from './service';
import IconSelect from '@/components/IconSelect';
const MenuPage: React.FC = () => {
const [menus, setMenus] = useState<MenuDTO[]>([]);
const [menuTreeData, setMenuTreeData] = useState<MenuDTO[]>([]);
const [modalVisible, setModalVisible] = useState(false);
const [iconSelectVisible, setIconSelectVisible] = useState(false);
const [editingMenu, setEditingMenu] = useState<MenuDTO | null>(null);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const fetchData = async () => {
try {
setLoading(true);
const [menuList, treeData] = await Promise.all([
getMenuTree(),
getMenuTreeWithoutButtons()
]);
setMenus(menuList);
setMenuTreeData(treeData);
} catch (error) {
console.error('获取菜单列表失败:', error);
message.error('获取菜单列表失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
const getIcon = (iconName: string | undefined) => {
if (!iconName) return null;
const iconKey = `${iconName.charAt(0).toUpperCase() + iconName.slice(1)}Outlined`;
const IconComponent = (AntdIcons as any)[iconKey];
return IconComponent ? <IconComponent /> : null;
};
const handleAdd = () => {
setEditingMenu(null);
form.resetFields();
form.setFieldsValue({
type: MenuTypeEnum.MENU,
sort: 0,
hidden: false,
enabled: true
});
setModalVisible(true);
};
const handleEdit = (record: MenuDTO) => {
setEditingMenu(record);
form.setFieldsValue({
...record,
parentId: record.parentId === 0 ? undefined : record.parentId
});
setModalVisible(true);
};
const handleDelete = async (id: number) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这个菜单吗?',
onOk: async () => {
try {
await deleteMenu(id);
message.success('删除成功');
fetchData();
} catch (error) {
message.error('删除失败');
}
},
});
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
const submitData = {
...values,
parentId: values.parentId || 0
};
if (editingMenu) {
await updateMenu(editingMenu.id, submitData);
message.success('更新成功');
} else {
await createMenu(submitData);
message.success('创建成功');
}
setModalVisible(false);
fetchData();
} catch (error) {
message.error('操作失败');
}
};
const getTreeSelectData = (menus: MenuDTO[]) => {
const treeData = menus.map(menu => ({
title: menu.name,
value: menu.id,
children: menu.children?.map(child => ({
title: child.name,
value: child.id,
disabled: editingMenu?.id === child.id
}))
}));
return treeData;
};
const columns = [
{
title: '菜单名称',
dataIndex: 'name',
key: 'name',
width: '200px',
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
width: '100px',
render: (type: MenuTypeEnum) => MenuTypeNames[type],
},
{
title: '权限标识',
dataIndex: 'permission',
key: 'permission',
width: '150px',
},
{
title: '路由地址',
dataIndex: 'path',
key: 'path',
width: '150px',
},
{
title: '组件路径',
dataIndex: 'component',
key: 'component',
width: '150px',
},
{
title: '排序',
dataIndex: 'sort',
key: 'sort',
width: '80px',
},
{
title: '状态',
dataIndex: 'enabled',
key: 'enabled',
width: '80px',
render: (enabled: boolean) => (
<Switch checked={enabled} disabled />
),
},
{
title: '操作',
key: 'action',
width: '150px',
render: (_: any, record: MenuDTO) => (
<Space>
<Button
type="link"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
</Button>
<Button
type="link"
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(record.id)}
>
</Button>
</Space>
),
},
];
return (
<div style={{ padding: '24px' }}>
<div style={{ marginBottom: 16 }}>
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
</Button>
</div>
<Table
loading={loading}
columns={columns}
dataSource={menus}
rowKey="id"
pagination={false}
size="middle"
bordered
/>
<Modal
title={editingMenu ? '编辑菜单' : '新增菜单'}
open={modalVisible}
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="菜单类型"
rules={[{ required: true, message: '请选择菜单类型' }]}
>
<Select>
<Select.Option value={MenuTypeEnum.DIRECTORY}></Select.Option>
<Select.Option value={MenuTypeEnum.MENU}></Select.Option>
<Select.Option value={MenuTypeEnum.BUTTON}></Select.Option>
</Select>
</Form.Item>
<Form.Item
name="parentId"
label={
<span>
<Tooltip title="不选择则为顶级菜单">
<QuestionCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</span>
}
>
<TreeSelect
treeData={getTreeSelectData(menuTreeData)}
placeholder="不选择则为顶级菜单"
allowClear
treeDefaultExpandAll
showSearch
treeNodeFilterProp="title"
/>
</Form.Item>
{form.getFieldValue('type') !== MenuTypeEnum.BUTTON && (
<>
<Form.Item
name="path"
label={
<span>
<Tooltip title="目录示例: /system 菜单示例: /system/user">
<QuestionCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</span>
}
rules={[{ required: true, message: '请输入路由地址' }]}
>
<Input placeholder="请输入路由地址" />
</Form.Item>
<Form.Item
name="component"
label={
<span>
<Tooltip title="目录固定值: LAYOUT 菜单示例: /System/User/index">
<QuestionCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</span>
}
rules={[{ required: true, message: '请输入组件路径' }]}
>
<Input placeholder="请输入组件路径" />
</Form.Item>
<Form.Item
name="icon"
label="图标"
>
<Input
placeholder="请选择图标"
readOnly
onClick={() => setIconSelectVisible(true)}
suffix={form.getFieldValue('icon') && getIcon(form.getFieldValue('icon'))}
/>
</Form.Item>
</>
)}
{form.getFieldValue('type') === MenuTypeEnum.BUTTON && (
<Form.Item
name="permission"
label={
<span>
<Tooltip title="示例: system:user:add">
<QuestionCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</span>
}
rules={[{ required: true, message: '请输入权限标识' }]}
>
<Input placeholder="请输入权限标识" />
</Form.Item>
)}
<Form.Item
name="sort"
label="显示排序"
rules={[{ required: true, message: '请输入显示排序' }]}
>
<InputNumber style={{ width: '100%' }} min={0} placeholder="请输入显示排序" />
</Form.Item>
<Form.Item
name="hidden"
label="是否隐藏"
valuePropName="checked"
>
<Switch />
</Form.Item>
</Form>
</Modal>
<IconSelect
visible={iconSelectVisible}
onCancel={() => setIconSelectVisible(false)}
value={form.getFieldValue('icon')}
onChange={value => {
form.setFieldValue('icon', value);
setIconSelectVisible(false);
}}
/>
</div>
);
};
export default MenuPage;