| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771 |
- 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;
|