837390164@qq.com 2 месяцев назад
Родитель
Сommit
31e633aab8
2 измененных файлов с 1489 добавлено и 0 удалено
  1. 718 0
      src/pages/UserManger/Menus.tsx
  2. 771 0
      src/pages/UserManger/Roles.tsx

+ 718 - 0
src/pages/UserManger/Menus.tsx

@@ -0,0 +1,718 @@
+import React, { useState, useEffect } from 'react';
+import {
+  Card,
+  Table,
+  Button,
+  Input,
+  Modal,
+  message,
+  Space,
+  Popconfirm,
+  Row,
+  Col,
+  Form,
+  TreeSelect,
+  Typography,
+  Radio,
+  Switch
+} from 'antd';
+import {
+  PlusOutlined,
+  SearchOutlined,
+  ReloadOutlined,
+  EditOutlined,
+  DeleteOutlined,
+  MenuOutlined
+} from '@ant-design/icons';
+import type { ColumnsType } from 'antd/es/table';
+import type { DataNode } from 'antd/es/tree';
+import { getMenuTree, saveMenu, deleteMenu as deleteMenuApi, initDefaultMenus } from '../../api/menu';
+// import CustomPagination from '../../components/CustomPagination'; // 暂时不使用,已注释
+
+const { Title } = Typography;
+
+// 菜单项定义(兼容新旧格式)
+interface MenuItemLocal {
+  id?: number;
+  menuID?: number;
+  parentID?: number;
+  parentCode?: string;
+  menuCode?: string;
+  code?: string;
+  menuName?: string;
+  label?: string;
+  menuType?: 'module' | 'page' | 'button';
+  menuIcon?: string;
+  icon?: string;
+  menuPath?: string;
+  linkPath?: string;
+  routePath?: string;
+  sortNo?: number;
+  seqNo?: number;
+  active?: string;
+  isActive?: string;
+  isVisible?: string;
+  menuGroup?: string;
+  level?: number;  // 新增:层级信息
+  children?: MenuItemLocal[];
+}
+
+// 分页参数
+interface PaginationParams {
+  current: number;
+  pageSize: number;
+  total: number;
+}
+
+const Menus: React.FC = () => {
+  const [modalForm] = Form.useForm();
+  const [menuData, setMenuData] = useState<MenuItemLocal[]>([]);
+  const [mainTableData, setMainTableData] = useState<MenuItemLocal[]>([]); // 主表:一级菜单
+  const [subTableData, setSubTableData] = useState<MenuItemLocal[]>([]);   // 副表:当前选中菜单的子菜单
+  const [selectedMainMenuItem, setSelectedMainMenuItem] = useState<MenuItemLocal | null>(null);
+  const [loading, setLoading] = useState(false);
+  // 由于分主副表显示,暂时不使用分页
+  // const [pagination, setPagination] = useState<PaginationParams>({
+  //   current: 1,
+  //   pageSize: 20,
+  //   total: 0
+  // });
+  const [modalVisible, setModalVisible] = useState(false);
+  const [modalTitle, setModalTitle] = useState('新增菜单');
+  const [editingMenu, setEditingMenu] = useState<MenuItemLocal | null>(null);
+  const [searchName, setSearchName] = useState('');
+  const [initLoading, setInitLoading] = useState(false);
+
+  // 转换菜单数据格式
+  const convertMenu = (item: any, parentCode: string = ''): MenuItemLocal => {
+    const hasChildren = item.children && item.children.length > 0;
+    
+    // 计算当前菜单的层级
+    const currentLevel = item.parentCode ? 2 : 1;
+    
+    return {
+      id: item.id || item.menuDetailID,
+      menuID: item.id || item.menuDetailID,
+      code: item.code,
+      menuCode: item.code,
+      label: item.label,
+      menuName: item.label,
+      icon: item.icon,
+      menuIcon: item.icon,
+      linkPath: item.routePath,
+      routePath: item.routePath,
+      menuPath: item.routePath,
+      seqNo: item.sortNo ? parseInt(item.sortNo) : 1,
+      sortNo: item.sortNo ? parseInt(item.sortNo) : 1,
+      parentCode: item.parentCode || parentCode || '',
+      isActive: item.isActive,
+      active: item.isActive,
+      isVisible: item.isVisible,
+      menuGroup: hasChildren ? 'Y' : 'N',
+      level: currentLevel,
+      children: item.children?.map((child: any) => convertMenu(child, item.code)) || []
+    };
+  };
+
+  // 加载菜单数据
+  const fetchMenus = async () => {
+    setLoading(true);
+    try {
+      console.log('开始获取菜单数据...');
+      // 调用后端API获取菜单树
+      const res = await getMenuTree({});
+      const errorCode = res.errorCode as string | number;
+      
+      // 后端可能返回 data 或 result 字段,需要兼容处理
+      const responseData = res as any;
+      const menuList = (responseData.data || responseData.result) as any[];
+      console.log('后端返回的完整响应:', responseData);
+      console.log('后端返回的菜单数据:', menuList);
+      
+      if ((errorCode === 0 || errorCode === '0') && menuList) {
+        const menus = menuList.map(item => convertMenu(item));
+        setMenuData(menus);
+        
+        // 分离主表数据(一级菜单)和副表数据(二级菜单)
+        const mainData: MenuItemLocal[] = []; // 主表:一级菜单
+        const allSubData: MenuItemLocal[] = []; // 所有二级菜单
+        
+        menus.forEach(menu => {
+          // 一级菜单加入主表
+          const mainItem: MenuItemLocal = {
+            ...menu,
+            children: undefined
+          };
+          mainData.push(mainItem);
+          
+          // 二级菜单加入副表
+          if (menu.children && menu.children.length > 0) {
+            menu.children.forEach((child: MenuItemLocal) => {
+              const subItem: MenuItemLocal = {
+                ...child,
+                children: undefined,
+                parentCode: menu.code,
+                level: 2
+              };
+              allSubData.push(subItem);
+            });
+          }
+        });
+        
+        console.log('主表数据(一级菜单):', mainData);
+        console.log('所有副表数据(二级菜单):', allSubData);
+        
+        // 设置主表数据
+        setMainTableData(mainData);
+        
+        // 处理搜索过滤
+        let filteredMainData = mainData;
+        if (searchName) {
+          filteredMainData = mainData.filter(item => {
+            const menuName = (item.menuName || item.label || '').toLowerCase();
+            const searchTerm = searchName.toLowerCase();
+            return menuName.includes(searchTerm);
+          });
+        }
+        
+        // 设置主表数据(支持搜索)
+        setMainTableData(filteredMainData);
+        
+        // 默认选中第一个主表项
+        if (filteredMainData.length > 0) {
+          setSelectedMainMenuItem(filteredMainData[0]);
+          const subData = allSubData.filter(item => item.parentCode === filteredMainData[0].code);
+          setSubTableData(subData);
+        } else {
+          setSelectedMainMenuItem(null);
+          setSubTableData([]);
+        }
+        
+        // 由于分主副表显示,暂时不使用分页
+        // 设置分页总数为主表数据数量
+        // setPagination(prev => ({ ...prev, total: filteredMainData.length }));
+      } else {
+        console.error('获取菜单失败:', res.errorMessage);
+        message.error(res.errorMessage || '获取菜单列表失败');
+      }
+    } catch (error) {
+      console.error('加载菜单数据失败:', error);
+      message.error('加载菜单数据失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 初始化默认菜单
+  const handleInitMenus = async () => {
+    setInitLoading(true);
+    try {
+      const res = await initDefaultMenus({});
+      const errorCode = res.errorCode as string | number;
+      if (errorCode === 0 || errorCode === '0') {
+        message.success('菜单初始化成功');
+        fetchMenus();
+      } else {
+        message.error(res.errorMessage || '菜单初始化失败');
+      }
+    } catch (error) {
+      console.error('菜单初始化失败:', error);
+      message.error('菜单初始化失败');
+    } finally {
+      setInitLoading(false);
+    }
+  };
+
+  // 处理主表项选择
+  const handleMainTableSelect = (record: MenuItemLocal) => {
+    setSelectedMainMenuItem(record);
+    
+    // 从所有菜单数据中过滤出当前选中菜单的子菜单
+    const allSubData: MenuItemLocal[] = [];
+    menuData.forEach(menu => {
+      if (menu.children && menu.children.length > 0) {
+        menu.children.forEach((child: MenuItemLocal) => {
+          const subItem: MenuItemLocal = {
+            ...child,
+            children: undefined,
+            parentCode: menu.code,
+            level: 2
+          };
+          allSubData.push(subItem);
+        });
+      }
+    });
+    
+    const subData = allSubData.filter(item => item.parentCode === record.code);
+    setSubTableData(subData);
+    console.log('选中主表项:', record.menuName, '对应的副表数据:', subData);
+  };
+
+  // 处理菜单状态切换
+  const handleStatusChange = async (record: MenuItemLocal, checked: boolean) => {
+    try {
+      console.log('切换菜单状态:', record.menuName, '当前状态:', record.active, '新状态:', checked ? 'Y' : 'N');
+      
+      // 构建更新参数,格式要与saveMenu期望的一致
+      const params = {
+        menuID: record.menuID || record.id ? String(record.menuID || record.id) : undefined,
+        code: record.code || record.menuCode || '',
+        label: record.label || record.menuName || '',
+        parentCode: record.parentCode || '',
+        menuLevel: record.parentCode ? 2 : 1,
+        sortNo: record.sortNo || record.seqNo || 1,
+        icon: record.icon || record.menuIcon || '',
+        routePath: record.linkPath || record.routePath || record.menuPath || '',
+        isVisible: 'Y',
+        isActive: checked ? 'Y' : 'N'
+      };
+      
+      // 调用保存菜单API
+      const res = await saveMenu(params);
+      const errorCode = res.errorCode as string | number;
+      
+      if (errorCode === 0 || errorCode === '0') {
+        message.success(checked ? '已启用' : '已停用');
+        
+        // 更新本地数据
+        const updatedSubTableData = subTableData.map(item => {
+          if (item.code === record.code) {
+            return { ...item, active: checked ? 'Y' : 'N', isActive: checked ? 'Y' : 'N' };
+          }
+          return item;
+        });
+        
+        // 更新主表数据
+        const updatedMainTableData = mainTableData.map(item => {
+          if (item.code === record.code) {
+            return { ...item, active: checked ? 'Y' : 'N', isActive: checked ? 'Y' : 'N' };
+          }
+          return item;
+        });
+        
+        setSubTableData(updatedSubTableData);
+        setMainTableData(updatedMainTableData);
+        
+        // 如果更新的是当前选中的主表项,也更新其状态
+        if (selectedMainMenuItem && selectedMainMenuItem.code === record.code) {
+          setSelectedMainMenuItem({
+            ...selectedMainMenuItem,
+            active: checked ? 'Y' : 'N',
+            isActive: checked ? 'Y' : 'N'
+          });
+        }
+        
+        // 重新加载数据确保一致性
+        fetchMenus();
+      } else {
+        message.error(res.errorMessage || '状态更新失败');
+      }
+    } catch (error) {
+      console.error('切换菜单状态失败:', error);
+      message.error('切换菜单状态失败');
+    }
+  };
+
+  // 主表列定义(只显示序号、菜单编码、菜单名称)
+  const mainTableColumns: ColumnsType<MenuItemLocal> = [
+    {
+      title: '序号',
+      key: 'index',
+      width: 80,
+      render: (_, __, index) => (
+        <div style={{ textAlign: 'center' }}>{index + 1}</div>
+      )
+    },
+    {
+      title: '菜单编码',
+      dataIndex: 'menuCode',
+      key: 'menuCode',
+      width: 150
+    },
+    {
+      title: '菜单名称',
+      dataIndex: 'menuName',
+      key: 'menuName',
+      width: 200,
+      render: (text: string) => (
+        <span style={{ fontWeight: 'bold', fontSize: '14px' }}>{text}</span>
+      )
+    }
+  ];
+
+  // 副表列定义(显示二级菜单)
+  const subTableColumns: ColumnsType<MenuItemLocal> = [
+    {
+      title: '序号',
+      key: 'index',
+      width: 60,
+      render: (_, __, index) => index + 1
+    },
+    {
+      title: '菜单编码',
+      dataIndex: 'menuCode',
+      key: 'menuCode',
+      width: 150
+    },
+    {
+      title: '菜单名称',
+      dataIndex: 'menuName',
+      key: 'menuName',
+      width: 180
+    },
+    {
+      title: '父菜单',
+      dataIndex: 'parentCode',
+      key: 'parentCode',
+      width: 120,
+      render: (parentCode: string) => parentCode || '-'
+    },
+    {
+      title: '菜单路径',
+      dataIndex: 'menuPath',
+      key: 'menuPath',
+      width: 200,
+      ellipsis: true
+    },
+    {
+      title: '排序号',
+      dataIndex: 'sortNo',
+      key: 'sortNo',
+      width: 80
+    },
+    {
+      title: '状态',
+      dataIndex: 'active',
+      key: 'active',
+      width: 100,
+      render: (active: string, record: MenuItemLocal) => (
+        <Switch
+          checked={active === 'Y' || active === '1'}
+          checkedChildren="启用"
+          unCheckedChildren="停用"
+          onChange={(checked) => handleStatusChange(record, checked)}
+        />
+      )
+    },
+    {
+      title: '操作',
+      key: 'action',
+      width: 120,
+      render: (_, record) => (
+        <Space size="small">
+          <Button
+            type="link"
+            size="small"
+            icon={<EditOutlined />}
+            onClick={() => handleEdit(record)}
+          >
+            编辑
+          </Button>
+          <Popconfirm
+            title="确认删除"
+            description={`确定要删除菜单"${record.menuName}"吗?`}
+            onConfirm={() => handleDelete(record)}
+            okText="确定"
+            cancelText="取消"
+          >
+            <Button type="link" size="small" danger icon={<DeleteOutlined />}>
+              删除
+            </Button>
+          </Popconfirm>
+        </Space>
+      )
+    }
+  ];
+
+
+
+  // 构建树形选择器数据
+  const buildTreeSelectData = (menus: MenuItemLocal[]): DataNode[] => {
+    return menus.map(menu => ({
+      title: `${menu.menuName || menu.label} (${menu.menuCode || menu.code})`,
+      value: menu.code || menu.menuCode || '',
+      key: menu.code || menu.menuCode || '',
+      children: menu.children && menu.children.length > 0 ? buildTreeSelectData(menu.children) : undefined
+    }));
+  };
+
+  // 新增菜单
+  const handleAdd = () => {
+    setEditingMenu(null);
+    setModalTitle('新增菜单');
+    modalForm.resetFields();
+    modalForm.setFieldsValue({
+      active: 'Y',
+      sortNo: 1,
+      menuType: 'page'
+    });
+    setModalVisible(true);
+  };
+
+  // 编辑菜单
+  const handleEdit = (record: MenuItemLocal) => {
+    setEditingMenu(record);
+    setModalTitle('编辑菜单');
+    modalForm.setFieldsValue({
+      menuCode: record.code || record.menuCode,
+      menuName: record.label || record.menuName,
+      menuType: record.menuType || 'page',
+      menuIcon: record.icon || record.menuIcon,
+      menuPath: record.linkPath || record.routePath || record.menuPath,
+      parentCode: record.parentCode,
+      sortNo: record.sortNo || record.seqNo,
+      active: record.active || record.isActive || 'Y'
+    });
+    setModalVisible(true);
+  };
+
+  // 删除菜单
+  const handleDelete = async (record: MenuItemLocal) => {
+    try {
+      const code = record.code || record.menuCode;
+      const res = await deleteMenuApi({ code });
+      const errorCode = res.errorCode as string | number;
+      if (errorCode === 0 || errorCode === '0') {
+        message.success('删除成功');
+        fetchMenus();
+      } else {
+        message.error(res.errorMessage || '删除失败');
+      }
+    } catch (error) {
+      console.error('删除菜单失败:', error);
+      message.error('删除菜单失败');
+    }
+  };
+
+  // 保存菜单 - 使用operatetable模式
+  const handleSave = async () => {
+    try {
+      const values = await modalForm.validateFields();
+      
+      // 获取菜单ID用于编辑
+      const menuID = editingMenu ? (editingMenu.menuID || editingMenu.id)?.toString() : '';
+      
+      const params = {
+        menuID: menuID,
+        code: values.menuCode,
+        label: values.menuName,
+        parentCode: values.parentCode || '',
+        menuLevel: values.parentCode ? 2 : 1,
+        sortNo: values.sortNo || 1,
+        icon: values.menuIcon || '',
+        routePath: values.menuPath || '',
+        isVisible: 'Y',
+        isActive: values.active
+      };
+      
+      const res = await saveMenu(params);
+      const errorCode = res.errorCode as string | number;
+      if (errorCode === 0 || errorCode === '0') {
+        message.success(editingMenu ? '修改成功' : '新增成功');
+        setModalVisible(false);
+        fetchMenus();
+      } else {
+        message.error(res.errorMessage || '保存失败');
+      }
+    } catch (error) {
+      console.error('保存菜单失败:', error);
+      message.error('保存菜单失败');
+    }
+  };
+
+  // 搜索
+  const handleSearch = () => {
+    fetchMenus();
+  };
+
+  // 重置
+  const handleReset = () => {
+    setSearchName('');
+    setSelectedMainMenuItem(null);
+    fetchMenus();
+  };
+
+  // 初始化加载
+  useEffect(() => {
+    fetchMenus();
+  }, []);
+
+  return (
+    <div style={{ padding: 16 }}>
+      <Title level={4}>菜单配置</Title>
+      
+      {/* 查询条件 */}
+      <Card size="small" style={{ marginBottom: 16 }}>
+        <Row gutter={16} align="middle">
+          <Col>
+            <Input
+              placeholder="菜单名称"
+              value={searchName}
+              onChange={e => setSearchName(e.target.value)}
+              onPressEnter={handleSearch}
+              style={{ width: 200 }}
+              allowClear
+            />
+          </Col>
+          <Col>
+            <Space>
+              <Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>查询</Button>
+              <Button icon={<ReloadOutlined />} onClick={handleReset}>重置</Button>
+              <Button 
+                icon={<MenuOutlined />} 
+                onClick={handleInitMenus}
+                loading={initLoading}
+              >
+                初始化菜单
+              </Button>
+              <Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>新增菜单</Button>
+            </Space>
+          </Col>
+        </Row>
+      </Card>
+      
+      <div style={{ display: 'flex', gap: 16 }}>
+        {/* 主表:一级菜单 */}
+        <Card 
+          size="small" 
+          style={{ width: '40%', marginBottom: 16 }}
+          title="主表 - 一级菜单"
+        >
+          <Table
+            columns={mainTableColumns}
+            dataSource={mainTableData}
+            rowKey="code"
+            loading={loading}
+            scroll={{ x: 800 }}
+            size="small"
+            pagination={false}
+            onRow={(record) => ({
+              onClick: () => handleMainTableSelect(record),
+              style: { 
+                cursor: 'pointer',
+                backgroundColor: selectedMainMenuItem?.code === record.code ? '#f0f0f0' : 'transparent'
+              }
+            })}
+          />
+        </Card>
+
+        {/* 副表:二级菜单 */}
+        <Card 
+          size="small" 
+          style={{ width: '60%', marginBottom: 16 }}
+          title={selectedMainMenuItem ? `副表 - ${selectedMainMenuItem.menuName} 的子菜单` : '副表 - 子菜单'}
+        >
+          <Table
+            columns={subTableColumns}
+            dataSource={subTableData}
+            rowKey="code"
+            loading={loading}
+            scroll={{ x: 1000 }}
+            size="small"
+            pagination={false}
+          />
+        </Card>
+      </div>
+
+      {/* 新增/编辑弹窗 */}
+      <Modal
+        title={modalTitle}
+        open={modalVisible}
+        onOk={handleSave}
+        onCancel={() => setModalVisible(false)}
+        okText="确定"
+        cancelText="取消"
+        width={600}
+
+      >
+        <Form
+          form={modalForm}
+          layout="vertical"
+          preserve={false}
+        >
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="menuCode"
+                label="菜单编码"
+                rules={[{ required: true, message: '请输入菜单编码' }]}
+              >
+                <Input placeholder="请输入菜单编码" maxLength={50} disabled={!!editingMenu} />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="menuName"
+                label="菜单名称"
+                rules={[{ required: true, message: '请输入菜单名称' }]}
+              >
+                <Input placeholder="请输入菜单名称" maxLength={100} />
+              </Form.Item>
+            </Col>
+          </Row>
+
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="parentCode"
+                label="上级菜单"
+              >
+                <TreeSelect
+                  placeholder="请选择上级菜单(不选则为顶级)"
+                  treeData={buildTreeSelectData(menuData)}
+                  allowClear
+                  treeDefaultExpandAll
+                />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="sortNo"
+                label="排序号"
+                rules={[{ required: true, message: '请输入排序号' }]}
+              >
+                <Input type="number" placeholder="请输入排序号" min={1} />
+              </Form.Item>
+            </Col>
+          </Row>
+
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="menuIcon"
+                label="菜单图标"
+              >
+                <Input placeholder="请输入图标名称(如: UserOutlined)" maxLength={50} />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="active"
+                label="状态"
+                rules={[{ required: true, message: '请选择状态' }]}
+              >
+                <Radio.Group>
+                  <Radio value="Y">启用</Radio>
+                  <Radio value="N">停用</Radio>
+                </Radio.Group>
+              </Form.Item>
+            </Col>
+          </Row>
+
+          <Row gutter={16}>
+            <Col span={24}>
+              <Form.Item
+                name="menuPath"
+                label="菜单路径/路由"
+              >
+                <Input placeholder="请输入菜单路径(如: /system/users)" maxLength={200} />
+              </Form.Item>
+            </Col>
+          </Row>
+        </Form>
+      </Modal>
+    </div>
+  );
+};
+
+export default Menus;

+ 771 - 0
src/pages/UserManger/Roles.tsx

@@ -0,0 +1,771 @@
+import React, { useState, useEffect } from 'react';
+import {
+  Card,
+  Table,
+  Button,
+  Input,
+  Modal,
+  message,
+  Space,
+  Row,
+  Col,
+  Form,
+  Tree,
+  Tabs,
+  Tag,
+  Radio,
+  Switch,
+} from 'antd';
+import {
+  PlusOutlined,
+  SearchOutlined,
+  ReloadOutlined,
+  EditOutlined,
+  SafetyOutlined,
+} from '@ant-design/icons';
+import type { ColumnsType } from 'antd/es/table';
+import type { DataNode } from 'antd/es/tree';
+import CustomPagination from '../../components/CustomPagination';
+import { queryGroupList, saveGroup, getGroupMenuDetail, saveGroupMenu, type GroupItem } from '../../api/system';
+import { getMenuTree } from '../../api/menu';
+
+const { TabPane } = Tabs;
+
+/**
+ * 检查响应是否成功(兼容字符串 '0' 和数字 0)
+ */
+const isSuccess = (errorCode: string | number | undefined): boolean => {
+  return errorCode === '0' || errorCode === 0;
+};
+
+// 分页参数
+interface PaginationParams {
+  current: number;
+  pageSize: number;
+  total: number;
+}
+
+// 安全级别选项
+const safeClassOptions = [
+  { value: '1', label: '普通' },
+  { value: '2', label: '重要' },
+  { value: '3', label: '核心' },
+];
+
+// 默认菜单类型选项
+const menuTypeOptions = [
+  { value: '1', label: '类型1' },
+  { value: '2', label: '类型2' },
+];
+
+const Roles: React.FC = () => {
+  const [form] = Form.useForm();
+  const [data, setData] = useState<GroupItem[]>([]);
+  const [loading, setLoading] = useState(false);
+  const [pagination, setPagination] = useState<PaginationParams>({
+    current: 1,
+    pageSize: 10,
+    total: 0
+  });
+  
+  // 查询条件
+  const [searchCode, setSearchCode] = useState('');
+  const [searchName, setSearchName] = useState('');
+
+  // 角色编码序号(从100000开始)
+  const [roleCodeCounter, setRoleCodeCounter] = useState<number>(100000);
+
+  // 弹窗状态
+  const [modalVisible, setModalVisible] = useState(false);
+  const [modalTitle, setModalTitle] = useState('新增角色');
+  const [editingRecord, setEditingRecord] = useState<GroupItem | null>(null);
+  
+  // 权限配置弹窗
+  const [authVisible, setAuthVisible] = useState(false);
+  const [authRecord, setAuthRecord] = useState<GroupItem | null>(null);
+  const [menuTreeData, setMenuTreeData] = useState<DataNode[]>([]);
+  const [selectedMenuIds, setSelectedMenuIds] = useState<string[]>([]);
+  const [activeTab, setActiveTab] = useState('menu');
+  const [authLoading, setAuthLoading] = useState(false);
+
+  // 表格列定义
+  const columns: ColumnsType<GroupItem> = [
+    {
+      title: '序号',
+      key: 'index',
+      width: 60,
+      render: (_, __, index) => (pagination.current - 1) * pagination.pageSize + index + 1
+    },
+    {
+      title: '角色编码',
+      dataIndex: 'code',
+      key: 'code',
+      width: 120
+    },
+    {
+      title: '角色名称',
+      dataIndex: 'descripts',
+      key: 'descripts',
+      width: 150
+    },
+    {
+      title: '英文名称',
+      dataIndex: 'enDesc',
+      key: 'enDesc',
+      width: 150
+    },
+    {
+      title: '安全级别',
+      dataIndex: 'safeClassificat',
+      key: 'safeClassificat',
+      width: 100,
+      render: (value: string) => {
+        const option = safeClassOptions.find(o => o.value === value);
+        return option?.label || value || '-';
+      }
+    },
+    {
+      title: '允许列编辑',
+      dataIndex: 'columnEdit',
+      key: 'columnEdit',
+      width: 100,
+      render: (value: string) => (
+        <Tag color={value === 'Y' ? 'green' : 'default'}>
+          {value === 'Y' ? '是' : '否'}
+        </Tag>
+      )
+    },
+    {
+      title: '允许布局编辑',
+      dataIndex: 'layoutEdit',
+      key: 'layoutEdit',
+      width: 105,
+      render: (value: string) => (
+        <Tag color={value === 'Y' ? 'green' : 'default'}>
+          {value === 'Y' ? '是' : '否'}
+        </Tag>
+      )
+    },    
+    {
+      title: '生效日期',
+      dataIndex: 'startDate',
+      key: 'startDate',
+      width: 120,
+      render: (value) => value ? new Date(value).toLocaleDateString() : '-'
+    },
+    {
+      title: '状态',
+      key: 'status',
+      width: 80,
+      render: (_, record) => {
+        // 根据生效日期和失效日期判断状态
+        const now = new Date();
+        const startDate = record.startDate ? new Date(record.startDate) : null;
+        const endDate = record.endDate ? new Date(record.endDate) : null;
+        
+        const isActive = (!startDate || startDate <= now) && (!endDate || endDate >= now);
+        
+        return (
+          <Tag color={isActive ? 'green' : 'red'}>
+            {isActive ? '在用' : '停用'}
+          </Tag>
+        );
+      }
+    },
+    {
+      title: '失效日期',
+      dataIndex: 'endDate',
+      key: 'endDate',
+      width: 120,
+      render: (value) => value ? new Date(value).toLocaleDateString() : '-'
+    },
+
+    {
+      title: '操作',
+      key: 'action',
+      fixed: 'right',
+      width: 150,
+      render: (_, record) => (
+        <Space size="small">
+          <Button
+            type="link"
+            size="small"
+            icon={<SafetyOutlined />}
+            onClick={() => handleAuth(record)}
+          >
+            权限
+          </Button>
+          <Button
+            type="link"
+            size="small"
+            icon={<EditOutlined />}
+            onClick={() => handleEdit(record)}
+          >
+            编辑
+          </Button>
+        </Space>
+      )
+    }
+  ];
+
+  // 查询角色列表
+  const fetchData = async (page = pagination.current, size = pagination.pageSize) => {
+    setLoading(true);
+    try {
+      const res = await queryGroupList(
+        { code: searchCode, descripts: searchName },
+        { pageSize: size, currentPage: page }
+      );
+      
+      // 兼容字符串和数字类型的 errorCode
+      if (isSuccess(res.errorCode) && res.result) {
+        const resultData = res.result as any;
+        setData(resultData.rows || []);
+        setPagination(prev => ({
+          ...prev,
+          total: resultData.total || 0
+        }));
+      } else {
+        message.error(res.errorMessage || '获取角色列表失败');
+      }
+    } catch (error) {
+      console.error('查询角色失败:', error);
+      message.error('查询角色失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 初始化加载
+  useEffect(() => {
+    fetchData(1, pagination.pageSize);
+  }, []);
+
+  // 搜索
+  const handleSearch = () => {
+    setPagination(prev => ({ ...prev, current: 1 }));
+    fetchData(1, pagination.pageSize);
+  };
+
+  // 重置
+  const handleReset = () => {
+    setSearchCode('');
+    setSearchName('');
+    setPagination(prev => ({ ...prev, current: 1 }));
+    fetchData(1, pagination.pageSize);
+  };
+
+  // 按序号生成6位数字编码
+  const generateCode = () => {
+    // 获取当前序号
+    const currentCounter = roleCodeCounter;
+    
+    // 确保编码不超过999999
+    if (currentCounter > 999999) {
+      message.error('角色编码已达到最大值999999');
+      return '999999';
+    }
+    
+    // 直接使用当前序号作为编码
+    const code = currentCounter.toString();
+    
+    console.log('生成的顺序编码:', code, '当前序号:', currentCounter);
+    
+    // 递增序号
+    setRoleCodeCounter(prev => prev + 1);
+    
+    return code;
+  };
+
+  // 新增
+  const handleAdd = () => {
+    setEditingRecord(null);
+    setModalTitle('新增角色');
+    form.resetFields();
+    
+    // 先生成角色编码
+    const newCode = generateCode();
+    
+    // 设置默认日期:生效日期为今天,失效日期为空
+    const today = new Date();
+    const todayStr = today.toISOString().split('T')[0];
+    
+    setModalVisible(true);
+    
+    // 使用 nextTick 确保在弹框渲染完成后设置表单值
+    setTimeout(() => {
+      form.setFieldsValue({
+        code: newCode,
+        operCodeTable: 'N',
+        sendMsgToAllUser: 'N',
+        safeClassificat: '1',
+        columnEdit: 'N',
+        layoutEdit: 'N',
+        startDate: todayStr,
+        endDate: undefined
+      });
+    }, 0);
+  };
+
+  // 编辑
+  const handleEdit = (record: GroupItem) => {
+    setEditingRecord(record);
+    setModalTitle('编辑角色');
+    form.resetFields();
+    setModalVisible(true);
+    setTimeout(() => {
+      form.setFieldsValue({
+        id: record.id,
+        code: record.code,
+        descripts: record.descripts,
+        enDesc: record.enDesc,
+        mainInterface: record.mainInterface,
+        operCodeTable: record.operCodeTable || 'N',
+        sendMsgToAllUser: record.sendMsgToAllUser || 'N',
+        safeClassificat: record.safeClassificat || '1',
+        columnEdit: record.columnEdit || 'N',
+        layoutEdit: record.layoutEdit || 'N',
+        defaultMenuType: record.defaultMenuType,
+        startDate: record.startDate,
+        endDate: record.endDate
+      });
+    }, 0);
+  };
+
+
+
+  // 更新角色状态(在用/停用)
+  const handleStatusChange = async (record: GroupItem, checked: boolean) => {
+    const now = new Date();
+    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+    
+    try {
+      const params = {
+        id: String(record.id),
+        code: record.code,
+        descripts: record.descripts,
+        startDate: record.startDate,
+        endDate: checked ? undefined : today.toISOString().split('T')[0] // 停用时设置失效日期为今天
+      };
+      
+      const res = await saveGroup(params as any);
+      // 兼容字符串和数字类型的 errorCode
+      if (isSuccess(res.errorCode)) {
+        message.success(checked ? '已启用' : '已停用');
+        fetchData(pagination.current, pagination.pageSize);
+      } else {
+        message.error(res.errorMessage || '状态更新失败');
+      }
+    } catch (error) {
+      console.error('更新角色状态失败:', error);
+      message.error('更新角色状态失败');
+    }
+  };
+
+  // 保存角色
+  const handleSave = async () => {
+    try {
+      const values = await form.validateFields();
+      
+      const params = {
+        id: editingRecord ? String(editingRecord.id) : undefined,
+        code: values.code,
+        descripts: values.descripts,
+        enDesc: values.enDesc,
+        mainInterface: values.mainInterface,
+        operCodeTable: values.operCodeTable,
+        sendMsgToAllUser: values.sendMsgToAllUser,
+        safeClassificat: values.safeClassificat,
+        columnEdit: values.columnEdit,
+        layoutEdit: values.layoutEdit,
+        defaultMenuType: values.defaultMenuType,
+        startDate: values.startDate,
+        endDate: values.endDate || undefined,
+      };
+      
+      const res = await saveGroup(params);
+      // 兼容字符串和数字类型的 errorCode
+      if (isSuccess(res.errorCode)) {
+        message.success(editingRecord ? '修改成功' : '新增成功');
+        setModalVisible(false);
+        fetchData(pagination.current, pagination.pageSize);
+      } else {
+        message.error(res.errorMessage || '保存失败');
+      }
+    } catch (error) {
+      console.error('保存角色失败:', error);
+      message.error('保存角色失败');
+    }
+  };
+
+  // 打开权限配置弹窗
+  const handleAuth = async (record: GroupItem) => {
+    setAuthRecord(record);
+    setAuthVisible(true);
+    setActiveTab('menu');
+    setSelectedMenuIds([]);
+    setMenuTreeData([]); // 清空旧数据
+    setAuthLoading(true);
+    
+    try {
+      // 先加载菜单树
+      const menuRes = await getMenuTree({});
+      console.log('菜单树响应:', menuRes);
+      console.log('errorCode类型:', typeof menuRes.errorCode, '值:', menuRes.errorCode);
+      console.log('result:', menuRes.result);
+      
+      // 兼容字符串和数字类型的 errorCode
+      if (isSuccess(menuRes.errorCode) && menuRes.result) {
+        const menuList = menuRes.result as any[];
+        console.log('菜单列表长度:', menuList.length);
+        
+        const convertToTreeData = (menus: any[]): DataNode[] => {
+          return menus.map(menu => ({
+            key: String(menu.id || ''),
+            title: menu.label || '',
+            children: menu.children && menu.children.length > 0 
+              ? convertToTreeData(menu.children) 
+              : undefined
+          }));
+        };
+        
+        const treeData = convertToTreeData(menuList);
+        console.log('转换后的树数据:', treeData);
+        setMenuTreeData(treeData);
+        
+        // 再获取角色已配置的菜单
+        const res = await getGroupMenuDetail({ groupID: String(record.id), type: '1' });
+        console.log('角色菜单详情:', res);
+        
+        // 兼容字符串和数字类型的 errorCode
+        if (isSuccess(res.errorCode) && res.result) {
+          const resultData = res.result as any;
+          if (resultData.rows && resultData.rows.length > 0) {
+            const selectedIds = resultData.rows.map((r: any) => String(r.menuDetailID));
+            console.log('已选中菜单IDs:', selectedIds);
+            setSelectedMenuIds(selectedIds);
+          }
+        }
+      } else {
+        message.warning('获取菜单树失败: ' + (menuRes.errorMessage || '未知错误'));
+      }
+    } catch (error) {
+      console.error('加载权限数据失败:', error);
+      message.error('加载权限数据失败');
+    } finally {
+      setAuthLoading(false);
+    }
+  };
+
+  // 保存权限配置
+  const handleSaveAuth = async () => {
+    if (!authRecord) return;
+    try {
+      const menuDetail = selectedMenuIds.map(menuId => ({
+        menuDetailID: menuId,
+        seqNo: '',
+        preMenuGroupID: ''
+      }));
+      
+      const res = await saveGroupMenu({
+        groupID: String(authRecord.id),
+        type: '1',
+        preMenuGroupID: '',
+        menuDetail
+      });
+      
+      // 兼容字符串和数字类型的 errorCode
+      if (isSuccess(res.errorCode)) {
+        message.success('权限配置保存成功');
+        setAuthVisible(false);
+        
+        // 提示用户重新登录以使权限生效
+        Modal.confirm({
+          title: '权限已更新',
+          content: `角色"${authRecord.descripts}"的权限配置已保存。请重新登录使新权限生效。`,
+          okText: '立即重新登录',
+          cancelText: '稍后登录',
+          onOk: () => {
+            // 清除 session 并刷新页面回到登录页
+            localStorage.removeItem('drg_session');
+            window.location.href = '/';
+          }
+        });
+      } else {
+        message.error(res.errorMessage || '保存权限失败');
+      }
+    } catch (error) {
+      console.error('保存权限失败:', error);
+      message.error('保存权限失败');
+    }
+  };
+
+  // Tree复选事件处理
+  const handleTreeCheck = (checkedKeys: any) => {
+    if (Array.isArray(checkedKeys)) {
+      setSelectedMenuIds(checkedKeys.map(String));
+    } else if (checkedKeys && typeof checkedKeys === 'object' && 'checked' in checkedKeys) {
+      setSelectedMenuIds((checkedKeys.checked || []).map(String));
+    }
+  };
+
+  return (
+    <div style={{ padding: 16 }}>
+      {/* 查询条件 */}
+      <Card size="small" style={{ marginBottom: 16 }}>
+        <Row gutter={16} align="middle">
+          <Col>
+            <Input
+              placeholder="角色编码"
+              value={searchCode}
+              onChange={e => setSearchCode(e.target.value)}
+              style={{ width: 140 }}
+              allowClear
+            />
+          </Col>
+          <Col>
+            <Input
+              placeholder="角色名称"
+              value={searchName}
+              onChange={e => setSearchName(e.target.value)}
+              style={{ width: 140 }}
+              allowClear
+            />
+          </Col>
+          <Col>
+            <Space>
+              <Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>查询</Button>
+              <Button icon={<ReloadOutlined />} onClick={handleReset}>重置</Button>
+            </Space>
+          </Col>
+        </Row>
+      </Card>
+
+      {/* 工具栏 + 表格 */}
+      <Card size="small">
+        <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 12 }}>
+          <Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>新增角色</Button>
+          <span>共 {pagination.total} 条记录</span>
+        </div>
+        <Table
+          columns={columns}
+          dataSource={data}
+          rowKey="id"
+          loading={loading}
+          scroll={{ x: 1200 }}
+          size="small"
+          pagination={false}
+        />
+        <div style={{ marginTop: 12, display: 'flex', justifyContent: 'flex-end' }}>
+          <CustomPagination
+            current={pagination.current}
+            pageSize={pagination.pageSize}
+            total={pagination.total}
+            onChange={(page, size) => {
+              setPagination(prev => ({ ...prev, current: page, pageSize: size }));
+              fetchData(page, size);
+            }}
+          />
+        </div>
+      </Card>
+
+      {/* 新增/编辑弹窗 */}
+      <Modal
+        title={modalTitle}
+        open={modalVisible}
+        onOk={handleSave}
+        onCancel={() => setModalVisible(false)}
+        okText="确定"
+        cancelText="取消"
+        width={700}
+      >
+        <Form form={form} layout="vertical">
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="code"
+                label="角色编码"
+                rules={[{ required: true, message: '请输入角色编码' }]}
+              >
+                <Input placeholder="系统自动生成6位数字编码" maxLength={6} disabled readOnly />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="descripts"
+                label="角色名称"
+                rules={[{ required: true, message: '请输入角色名称' }]}
+              >
+                <Input placeholder="请输入角色名称" maxLength={50} />
+              </Form.Item>
+            </Col>
+          </Row>
+
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item name="enDesc" label="英文名称">
+                <Input placeholder="请输入英文名称" maxLength={50} />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="safeClassificat"
+                label="安全级别"
+                rules={[{ required: true, message: '请选择安全级别' }]}
+              >
+                <Radio.Group>
+                  {safeClassOptions.map(opt => (
+                    <Radio key={opt.value} value={opt.value}>{opt.label}</Radio>
+                  ))}
+                </Radio.Group>
+              </Form.Item>
+            </Col>
+          </Row>
+
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="columnEdit"
+                label="允许列编辑"
+                rules={[{ required: true, message: '请选择是否允许列编辑' }]}
+              >
+                <Radio.Group>
+                  <Radio value="Y">是</Radio>
+                  <Radio value="N">否</Radio>
+                </Radio.Group>
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="layoutEdit"
+                label="允许布局编辑"
+                rules={[{ required: true, message: '请选择是否允许布局编辑' }]}
+              >
+                <Radio.Group>
+                  <Radio value="Y">是</Radio>
+                  <Radio value="N">否</Radio>
+                </Radio.Group>
+              </Form.Item>
+            </Col>
+          </Row>
+
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item
+                name="operCodeTable"
+                label="操作码表"
+                rules={[{ required: true, message: '请选择' }]}
+              >
+                <Radio.Group>
+                  <Radio value="Y">是</Radio>
+                  <Radio value="N">否</Radio>
+                </Radio.Group>
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item
+                name="sendMsgToAllUser"
+                label="发送消息给所有用户"
+                rules={[{ required: true, message: '请选择' }]}
+              >
+                <Radio.Group>
+                  <Radio value="Y">是</Radio>
+                  <Radio value="N">否</Radio>
+                </Radio.Group>
+              </Form.Item>
+            </Col>
+          </Row>
+
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item name="defaultMenuType" label="默认菜单类型">
+                <Radio.Group>
+                  {menuTypeOptions.map(opt => (
+                    <Radio key={opt.value} value={opt.value}>{opt.label}</Radio>
+                  ))}
+                </Radio.Group>
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item name="mainInterface" label="主界面">
+                <Input placeholder="请输入主界面" maxLength={50} />
+              </Form.Item>
+            </Col>
+          </Row>
+
+          <Row gutter={16}>
+            <Col span={12}>
+              <Form.Item 
+                name="startDate" 
+                label="生效日期"
+                rules={[{ required: true, message: '请选择生效日期' }]}
+              >
+                <Input type="date" placeholder="请选择生效日期" />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item name="endDate" label="失效日期">
+                <Input type="date" placeholder="请选择失效日期(可选)" />
+              </Form.Item>
+            </Col>
+          </Row>
+        </Form>
+      </Modal>
+
+      {/* 权限配置弹窗 */}
+      <Modal
+        title={`配置权限 - ${authRecord?.descripts || ''}`}
+        open={authVisible}
+        onOk={handleSaveAuth}
+        onCancel={() => setAuthVisible(false)}
+        okText="保存"
+        cancelText="取消"
+        width={600}
+      >
+        <Tabs activeKey={activeTab} onChange={setActiveTab}>
+          <TabPane tab="菜单权限" key="menu">
+            <div style={{ maxHeight: 400, overflow: 'auto', minHeight: 200 }}>
+              {/* 调试信息 */}
+              <div style={{ fontSize: 12, color: '#999', padding: '5px 0' }}>
+                调试: loading={authLoading.toString()}, menuTreeData.length={menuTreeData.length}
+              </div>
+              {authLoading ? (
+                <div style={{ textAlign: 'center', padding: '40px 0' }}>
+                  <span>加载中...</span>
+                </div>
+              ) : menuTreeData.length === 0 ? (
+                <div style={{ textAlign: 'center', padding: '40px 0', color: '#999' }}>
+                  <p>暂无菜单数据</p>
+                  <p style={{ fontSize: 12 }}>请确保后端 IRIS 服务正常运行,并已初始化菜单数据</p>
+                  <Button type="primary" size="small" onClick={() => handleAuth(authRecord!)} style={{ marginTop: 10 }}>
+                    重新加载
+                  </Button>
+                </div>
+              ) : (
+                <Tree
+                  checkable
+                  treeData={menuTreeData}
+                  checkedKeys={selectedMenuIds}
+                  onCheck={handleTreeCheck}
+                  selectable={false}
+                />
+              )}
+            </div>
+          </TabPane>
+          <TabPane tab="数据权限" key="data">
+            <div style={{ padding: '20px 0', textAlign: 'center', color: '#999' }}>
+              <p>数据权限配置功能开发中...</p>
+            </div>
+          </TabPane>
+          <TabPane tab="操作权限" key="operation">
+            <div style={{ padding: '20px 0', textAlign: 'center', color: '#999' }}>
+              <p>操作权限配置功能开发中...</p>
+            </div>
+          </TabPane>
+        </Tabs>
+      </Modal>
+    </div>
+  );
+};
+
+export default Roles;