import type { DataNode } from 'antd/es/tree'; import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons'; import { ModalForm, PageContainer, ProFormDigit, ProFormRadio, ProFormText, ProFormTreeSelect, ProTable, type ActionType, type ProColumns } from '@ant-design/pro-components'; import { useBoolean } from 'ahooks'; import { Button, Form, message, Modal, Space, Tag, Tree } from 'antd'; import { useMemo, useRef, useState } from 'react'; import type { MenuForm, MenuQuery, MenuTreeOption, MenuVO } from '@/api/system/menu/types'; import { addMenu, cascadeDelMenu, delMenu, getMenu, listMenu, treeselect, updateMenu } from '@/api/system/menu'; import DictTag from '@/components/common/DictTag'; import EllipsisText from '@/components/common/EllipsisText'; import IconSelect from '@/components/common/IconSelect'; import RowActions from '@/components/common/RowActions'; import { useDict } from '@/hooks/useDict'; import { useLoading } from '@/hooks/useLoading'; import { useTableScroll } from '@/hooks/useTableScroll'; import { useUserStore } from '@/stores/userStore'; import { routeIcon } from '@/utils/menu'; import { dictOptions } from '@/utils/dict'; import { hasPermi } from '@/utils/permission'; import { handleTree } from '@/utils/ruoyi'; const defaultMenuForm: MenuForm = { parentId: 0, menuType: 'M', orderNum: 1, isFrame: 'N', isCache: 'Y', visible: '0', status: '0' }; function menuTypeMeta(row: MenuVO) { if (row.menuType === 'F') return { label: '按钮', color: 'orange' }; if (row.isFrame === 'Y') return { label: '外链', color: 'red' }; if (row.menuType === 'M') return { label: '目录', color: 'blue' }; return { label: '菜单', color: 'green' }; } function toTreeNodes(nodes: MenuTreeOption[]): DataNode[] { return nodes.map(node => ({ title: node.label, key: node.id, children: node.children ? toTreeNodes(node.children) : undefined })); } export default function SystemMenuPage() { const actionRef = useRef(undefined); const { tableScroll } = useTableScroll(1120); const [form] = Form.useForm(); const userInfo = useUserStore(state => state.userInfo); const dicts = useDict('sys_show_hide', 'sys_normal_disable', 'sys_yes_no'); const [menuOptions, setMenuOptions] = useState([]); const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false); const [modalTitle, setModalTitle] = useState(''); const [cascadeOpen, { setTrue: openCascadeModal, setFalse: closeCascadeModal }] = useBoolean(false); const [cascadeKeys, setCascadeKeys] = useState>([]); const { loading: cascadeLoading, withLoading: withCascadeLoading } = useLoading(); const canAdd = hasPermi(userInfo, ['system:menu:add']); const canEdit = hasPermi(userInfo, ['system:menu:edit']); const canRemove = hasPermi(userInfo, ['system:menu:remove']); const menuType = Form.useWatch('menuType', form) || defaultMenuForm.menuType; const visible = Form.useWatch('visible', form) || defaultMenuForm.visible; const treeNodes = useMemo(() => toTreeNodes(menuOptions), [menuOptions]); const loadMenuTree = async () => { const res = await treeselect(); setMenuOptions([{ id: 0, label: '主类目', parentId: 0, weight: 0, children: res.data || [] }]); }; const openAdd = async (row?: MenuVO) => { await loadMenuTree(); form.resetFields(); form.setFieldsValue({ ...defaultMenuForm, parentId: row?.menuId || 0 }); setModalTitle('添加菜单'); openModal(); }; const openEdit = async (row: MenuVO) => { await loadMenuTree(); const res = await getMenu(row.menuId); form.resetFields(); form.setFieldsValue(res.data); setModalTitle('修改菜单'); openModal(); }; const submitForm = async (values: MenuForm) => { values.menuId ? await updateMenu(values) : await addMenu(values); message.success('操作成功'); form.resetFields(); actionRef.current?.reload(); return true; }; const remove = async (row: MenuVO) => { await delMenu(row.menuId); message.success('删除成功'); actionRef.current?.reload(); }; const openCascadeDelete = async () => { await loadMenuTree(); setCascadeKeys([]); openCascadeModal(); }; const submitCascadeDelete = async () => { if (!cascadeKeys.length) { message.warning('请选择要删除的菜单'); return; } await withCascadeLoading(async () => { await cascadeDelMenu(cascadeKeys); message.success('删除成功'); closeCascadeModal(); actionRef.current?.reload(); }); }; const columns: ProColumns[] = [ { title: '菜单名称', dataIndex: 'menuName', width: 220, render: (_, row) => ( {row.icon && {routeIcon(row.icon)}} {row.menuName} ) }, { title: '类型', dataIndex: 'menuType', search: false, width: 90, render: (_, row) => {menuTypeMeta(row).label} }, { title: '排序', dataIndex: 'orderNum', search: false, width: 90 }, { title: '权限标识', dataIndex: 'perms', search: false, width: 200, render: (_, row) => }, { title: '组件路径', dataIndex: 'component', search: false, width: 220, render: (_, row) => }, { title: '状态', dataIndex: 'status', valueType: 'select', fieldProps: { options: dictOptions(dicts.sys_normal_disable) }, width: 90, render: (_, row) => }, { title: '操作', valueType: 'option', width: 130, fixed: 'right', render: (_, row) => ( , onClick: () => openEdit(row) }, canAdd && { key: 'add', label: '新增', icon: , onClick: () => openAdd(row) }, canRemove && { key: 'delete', label: '删除', icon: , danger: true, confirm: `是否确认删除名称为"${row.menuName}"的数据项?`, onClick: () => remove(row) } ]} /> ) } ]; return ( actionRef={actionRef} rowKey="menuId" columns={columns} scroll={tableScroll} pagination={false} search={{ labelWidth: 90 }} expandable={{ defaultExpandAllRows: false }} request={async params => { const res = await listMenu({ menuName: params.menuName, status: params.status }); const rows = handleTree(res.data || [], 'menuId'); return { data: rows, total: rows.length, success: true }; }} toolbar={{ title: '菜单列表' }} toolBarRender={() => [ canAdd && ( ), canRemove && ( ) ]} /> title={modalTitle} open={modalOpen} width={760} form={form} layout="vertical" initialValues={defaultMenuForm} modalProps={{ destroyOnHidden: true, onCancel: closeModal }} onOpenChange={open => !open && closeModal()} onFinish={submitForm} > ); }