| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071 |
- import React, { useState } from 'react';
- import { Plus, Search, Edit2, Trash2, MoreVertical, ChevronRight, ChevronDown } from 'lucide-react';
- import DepartmentManagement from './DepartmentManagement';
- import PositionManagement from './PositionManagement';
- interface TableRow {
- id: number;
- [key: string]: any;
- }
- interface SystemConfigProps {
- subMenu: string;
- }
- export default function SystemConfig({ subMenu }: SystemConfigProps) {
- const [searchTerm, setSearchTerm] = useState('');
- const [showAddModal, setShowAddModal] = useState(false);
- const [editingItem, setEditingItem] = useState<TableRow | null>(null);
- const [showDictionaryDetail, setShowDictionaryDetail] = useState(false);
- const [currentDictionary, setCurrentDictionary] = useState<string>('');
- const [dictionarySearchTerm, setDictionarySearchTerm] = useState('');
- const [dictionaryFormMode, setDictionaryFormMode] = useState<'list' | 'form'>('list');
- const [editingDictionaryItem, setEditingDictionaryItem] = useState<TableRow | null>(null);
- const [expandedMenuIds, setExpandedMenuIds] = useState<number[]>([1, 2, 3, 4, 5, 6]); // 默认展开所有一级菜单
- const [expandedDeptIds, setExpandedDeptIds] = useState<number[]>([1]); // 部门树展开的节点
- const [selectedDeptId, setSelectedDeptId] = useState<number | null>(null); // 选中的部门ID
- // 树形菜单数据
- const menuTreeData = [
- {
- id: 1,
- name: '驾驶舱',
- path: '/cockpit',
- icon: '仪表盘',
- order: 1,
- status: '启用',
- createTime: '2025-01-01',
- children: []
- },
- {
- id: 2,
- name: '系统配置',
- path: '/system',
- icon: '设置',
- order: 2,
- status: '启用',
- createTime: '2025-01-02',
- children: [
- { id: 21, name: '菜单管理', path: '/system/menu', icon: '菜单', order: 1, status: '启用', createTime: '2025-01-02', parentId: 2 },
- { id: 22, name: '部门管理', path: '/system/dept', icon: '部门', order: 2, status: '启用', createTime: '2025-01-02', parentId: 2 },
- { id: 26, name: '岗位管理', path: '/system/position', icon: '岗位', order: 3, status: '启用', createTime: '2025-01-02', parentId: 2 },
- { id: 23, name: '角色管理', path: '/system/role', icon: '角色', order: 4, status: '启用', createTime: '2025-01-02', parentId: 2 },
- { id: 24, name: '字典管理', path: '/system/dict', icon: '字典', order: 5, status: '启用', createTime: '2025-01-02', parentId: 2 },
- { id: 25, name: '机柜管理', path: '/system/cabinet', icon: '机柜', order: 6, status: '启用', createTime: '2025-01-02', parentId: 2 },
- ]
- },
- {
- id: 3,
- name: '用户管理',
- path: '/users',
- icon: '用户',
- order: 3,
- status: '启用',
- createTime: '2025-01-03',
- children: [
- { id: 31, name: '用户列表', path: '/users/list', icon: '列表', order: 1, status: '启用', createTime: '2025-01-03', parentId: 3 },
- { id: 32, name: '通知管理', path: '/users/notification', icon: '通知', order: 2, status: '启用', createTime: '2025-01-03', parentId: 3 },
- ]
- },
- {
- id: 4,
- name: '硬件管理',
- path: '/hardware',
- icon: '硬件',
- order: 4,
- status: '启用',
- createTime: '2025-01-04',
- children: [
- { id: 41, name: '机柜', path: '/hardware/cabinet', icon: '机柜', order: 1, status: '启用', createTime: '2025-01-04', parentId: 4 },
- { id: 42, name: '钥匙', path: '/hardware/key', icon: '钥匙', order: 2, status: '启用', createTime: '2025-01-04', parentId: 4 },
- { id: 43, name: '挂锁', path: '/hardware/lock', icon: '锁', order: 3, status: '启用', createTime: '2025-01-04', parentId: 4 },
- { id: 44, name: '便携式', path: '/hardware/portable', icon: '便携', order: 4, status: '启用', createTime: '2025-01-04', parentId: 4 },
- ]
- },
- {
- id: 5,
- name: '点位管理',
- path: '/points',
- icon: '点位',
- order: 5,
- status: '启用',
- createTime: '2025-01-05',
- children: [
- { id: 51, name: '点位列表', path: '/points/list', icon: '列表', order: 1, status: '启用', createTime: '2025-01-05', parentId: 5 },
- { id: 52, name: '点位分组', path: '/points/group', icon: '分组', order: 2, status: '启用', createTime: '2025-01-05', parentId: 5 },
- ]
- },
- {
- id: 6,
- name: '隔离作业',
- path: '/isolation',
- icon: '作业',
- order: 6,
- status: '启用',
- createTime: '2025-01-06',
- children: [
- { id: 61, name: '作业列表', path: '/isolation/list', icon: '列表', order: 1, status: '启用', createTime: '2025-01-06', parentId: 6 },
- { id: 62, name: '作业审批', path: '/isolation/approval', icon: '审批', order: 2, status: '启用', createTime: '2025-01-06', parentId: 6 },
- { id: 63, name: '作业记录', path: '/isolation/record', icon: '记录', order: 3, status: '启用', createTime: '2025-01-06', parentId: 6 },
- ]
- },
- ];
- // 部门树形数据
- const departmentTreeData = [
- {
- id: 1,
- name: '总公司',
- manager: '李总',
- phone: '13800138000',
- email: 'ceo@company.com',
- memberCount: 120,
- createTime: '2024-01-01',
- children: [
- {
- id: 2,
- name: '技术中心',
- manager: '张三',
- phone: '13800138001',
- email: 'tech@company.com',
- memberCount: 45,
- createTime: '2024-01-05',
- parentId: 1,
- children: [
- {
- id: 21,
- name: '研发部',
- manager: '王研',
- phone: '13800138011',
- email: 'rd@company.com',
- memberCount: 25,
- createTime: '2024-01-10',
- parentId: 2
- },
- {
- id: 22,
- name: '测试部',
- manager: '赵测',
- phone: '13800138012',
- email: 'qa@company.com',
- memberCount: 12,
- createTime: '2024-01-10',
- parentId: 2
- },
- {
- id: 23,
- name: '运维部',
- manager: '李运',
- phone: '13800138013',
- email: 'ops@company.com',
- memberCount: 8,
- createTime: '2024-01-10',
- parentId: 2
- }
- ]
- },
- {
- id: 3,
- name: '市场中心',
- manager: '李四',
- phone: '13800138002',
- email: 'market@company.com',
- memberCount: 30,
- createTime: '2024-01-05',
- parentId: 1,
- children: [
- {
- id: 31,
- name: '市场部',
- manager: '钱市',
- phone: '13800138021',
- email: 'marketing@company.com',
- memberCount: 18,
- createTime: '2024-01-10',
- parentId: 3
- },
- {
- id: 32,
- name: '销售部',
- manager: '孙销',
- phone: '13800138022',
- email: 'sales@company.com',
- memberCount: 12,
- createTime: '2024-01-10',
- parentId: 3
- }
- ]
- },
- {
- id: 4,
- name: '行政中心',
- manager: '王五',
- phone: '13800138003',
- email: 'admin@company.com',
- memberCount: 25,
- createTime: '2024-01-05',
- parentId: 1,
- children: [
- {
- id: 41,
- name: '人力资源部',
- manager: '周人',
- phone: '13800138031',
- email: 'hr@company.com',
- memberCount: 10,
- createTime: '2024-01-10',
- parentId: 4
- },
- {
- id: 42,
- name: '财务部',
- manager: '吴财',
- phone: '13800138032',
- email: 'finance@company.com',
- memberCount: 8,
- createTime: '2024-01-10',
- parentId: 4
- },
- {
- id: 43,
- name: '行政部',
- manager: '郑行',
- phone: '13800138033',
- email: 'office@company.com',
- memberCount: 7,
- createTime: '2024-01-10',
- parentId: 4
- }
- ]
- },
- {
- id: 5,
- name: '安全部',
- manager: '赵六',
- phone: '13800138004',
- email: 'security@company.com',
- memberCount: 20,
- createTime: '2024-01-05',
- parentId: 1
- }
- ]
- }
- ];
- // 切换展开/收起
- const toggleExpand = (id: number) => {
- setExpandedMenuIds(prev =>
- prev.includes(id) ? prev.filter(item => item !== id) : [...prev, id]
- );
- };
- // 切换部门树展开/收起
- const toggleDeptExpand = (id: number) => {
- setExpandedDeptIds(prev =>
- prev.includes(id) ? prev.filter(item => item !== id) : [...prev, id]
- );
- };
- // 模拟数据
- const menuData: TableRow[] = [
- { id: 1, name: '系统配置', path: '/system', icon: '设置', order: 1, status: '启用', createTime: '2025-01-01' },
- { id: 2, name: '用户管理', path: '/users', icon: '用户', order: 2, status: '启用', createTime: '2025-01-02' },
- { id: 3, name: '硬件管理', path: '/hardware', icon: '硬件', order: 3, status: '启用', createTime: '2025-01-03' },
- { id: 4, name: '点位管理', path: '/points', icon: '点位', order: 4, status: '启用', createTime: '2025-01-04' },
- { id: 5, name: '隔离作业', path: '/isolation', icon: '作业', order: 5, status: '启用', createTime: '2025-01-05' },
- ];
- const departmentData: TableRow[] = [
- { id: 1, name: '技术部', manager: '张三', phone: '13800138001', email: 'tech@example.com', memberCount: 15, createTime: '2025-01-01' },
- { id: 2, name: '运维部', manager: '李四', phone: '13800138002', email: 'ops@example.com', memberCount: 10, createTime: '2025-01-02' },
- { id: 3, name: '安全部', manager: '王五', phone: '13800138003', email: 'security@example.com', memberCount: 8, createTime: '2025-01-03' },
- { id: 4, name: '测试部', manager: '赵六', phone: '13800138004', email: 'test@example.com', memberCount: 12, createTime: '2025-01-04' },
- ];
- const roleData: TableRow[] = [
- { id: 1, name: '超级管理员', code: 'admin', description: '拥有系统所有权限', userCount: 2, status: '启用', createTime: '2025-01-01' },
- { id: 2, name: '部门管理员', code: 'dept_admin', description: '管理部门用户和资源', userCount: 5, status: '启用', createTime: '2025-01-02' },
- { id: 3, name: '操作员', code: 'operator', description: '执行日常操作任务', userCount: 20, status: '启用', createTime: '2025-01-03' },
- { id: 4, name: '审计员', code: 'auditor', description: '查看系统日志和报表', userCount: 3, status: '启用', createTime: '2025-01-04' },
- ];
- const cabinetData: TableRow[] = [
- { id: 1, name: '1号机柜', location: 'A区-1排', model: 'CAB-42U', capacity: 42, used: 28, temperature: '23°C', status: '正常', createTime: '2025-01-01' },
- { id: 2, name: '2号机柜', location: 'A区-2排', model: 'CAB-42U', capacity: 42, used: 35, temperature: '24°C', status: '正常', createTime: '2025-01-02' },
- { id: 3, name: '3号机柜', location: 'B区-1排', model: 'CAB-48U', capacity: 48, used: 40, temperature: '25°C', status: '告警', createTime: '2025-01-03' },
- { id: 4, name: '4号机柜', location: 'B区-2排', model: 'CAB-48U', capacity: 48, used: 30, temperature: '22°C', status: '正常', createTime: '2025-01-04' },
- ];
- // 字典管理 - 使用卡片分组展示
- const dictionaryCategories = [
- {
- name: '用户分组',
- description: '用户分类管理',
- count: 12,
- items: ['管理员组', '普通用户组', '审计组', '访客组']
- },
- {
- name: '隔离方式',
- description: '能量隔离方式定义',
- count: 8,
- items: ['物理隔离', '逻辑隔离', '远程隔离', '手动隔离']
- },
- {
- name: '点位分组',
- description: '监控点位分类',
- count: 15,
- items: ['A区点位', 'B区点位', 'C区点位', 'D区点位']
- },
- {
- name: '设备类型',
- description: '设备分类字典',
- count: 20,
- items: ['变压器', '配电柜', '开关', '传感器']
- },
- ];
- // 根据当前子菜单获取数据和列配置
- const getTableConfig = () => {
- switch (subMenu) {
- case '菜单管理':
- return {
- data: menuData,
- columns: [
- { key: 'name', label: '菜单名称', width: '15%' },
- { key: 'path', label: '路径', width: '20%' },
- { key: 'icon', label: '图标', width: '10%' },
- { key: 'order', label: '排序', width: '10%' },
- { key: 'status', label: '状态', width: '10%' },
- { key: 'createTime', label: '创建时间', width: '15%' },
- ],
- };
- case '部门管理':
- return {
- data: departmentData,
- columns: [
- { key: 'name', label: '部门名称', width: '15%' },
- { key: 'manager', label: '负责人', width: '10%' },
- { key: 'phone', label: '联系电话', width: '15%' },
- { key: 'email', label: '邮箱', width: '20%' },
- { key: 'memberCount', label: '成员数', width: '10%' },
- { key: 'createTime', label: '创建时间', width: '15%' },
- ],
- };
- case '角色管理':
- return {
- data: roleData,
- columns: [
- { key: 'name', label: '角色名称', width: '15%' },
- { key: 'code', label: '角色编码', width: '15%' },
- { key: 'description', label: '描述', width: '25%' },
- { key: 'userCount', label: '用户数', width: '10%' },
- { key: 'status', label: '状态', width: '10%' },
- { key: 'createTime', label: '创建时间', width: '15%' },
- ],
- };
- case '机柜管理':
- return {
- data: cabinetData,
- columns: [
- { key: 'name', label: '机柜名称', width: '12%' },
- { key: 'location', label: '位置', width: '12%' },
- { key: 'model', label: '型号', width: '10%' },
- { key: 'capacity', label: '容量', width: '8%' },
- { key: 'used', label: '已用', width: '8%' },
- { key: 'temperature', label: '温度', width: '10%' },
- { key: 'status', label: '状态', width: '10%' },
- { key: 'createTime', label: '创建时间', width: '15%' },
- ],
- };
- default:
- return { data: [], columns: [] };
- }
- };
- const { data, columns } = getTableConfig();
- // 过滤数据
- const filteredData = data.filter((item) =>
- Object.values(item).some((value) =>
- String(value).toLowerCase().includes(searchTerm.toLowerCase())
- )
- );
- const handleDelete = (id: number) => {
- if (confirm('确定要删除这条数据吗?')) {
- console.log('删除:', id);
- }
- };
- const handleEdit = (item: TableRow) => {
- setEditingItem(item);
- setShowAddModal(true);
- };
- // 字典管理使用卡片展示
- if (subMenu === '字典管理') {
- return (
- <>
- <div className="space-y-6">
- {/* 工具栏 */}
- <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm p-4">
- <div className="flex items-center justify-between">
- <div className="relative w-80">
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
- <input
- type="text"
- placeholder="搜索字典..."
- value={searchTerm}
- onChange={(e) => setSearchTerm(e.target.value)}
- className="w-full h-10 pl-10 pr-4 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm"
- />
- </div>
- <button
- onClick={() => setShowAddModal(true)}
- className="flex items-center gap-2 px-4 py-2.5 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl hover:shadow-lg hover:shadow-blue-400/40 transition-all duration-300"
- >
- <Plus className="w-4 h-4" strokeWidth={2.5} />
- <span className="text-sm">新增字典</span>
- </button>
- </div>
- </div>
- {/* 字典分类卡片 */}
- <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
- {dictionaryCategories.map((category, index) => (
- <div key={index} className="bg-white rounded-2xl border border-gray-200/50 shadow-sm hover:shadow-lg hover:shadow-blue-100/50 transition-all duration-300 overflow-hidden">
- <div className="p-6">
- <div className="flex items-start justify-between mb-4">
- <div>
- <h3 className="text-lg text-gray-900 mb-1">{category.name}</h3>
- <p className="text-sm text-gray-500">{category.description}</p>
- </div>
- <div className="px-3 py-1 bg-blue-100 text-blue-700 text-xs rounded-lg">
- {category.count}项
- </div>
- </div>
- <div className="space-y-2 mb-4">
- {category.items.map((item, idx) => (
- <div key={idx} className="flex items-center justify-between p-2 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer">
- <span className="text-sm text-gray-700">{item}</span>
- <div className="flex gap-1">
- <button className="p-1 hover:bg-white rounded transition-colors">
- <Edit2 className="w-3 h-3 text-blue-600" />
- </button>
- <button className="p-1 hover:bg-white rounded transition-colors">
- <Trash2 className="w-3 h-3 text-red-600" />
- </button>
- </div>
- </div>
- ))}
- </div>
- <button
- onClick={() => {
- setCurrentDictionary(category.name);
- setShowDictionaryDetail(true);
- }}
- className="w-full py-2 text-sm text-blue-600 hover:text-blue-700 border border-blue-200 rounded-lg hover:bg-blue-50 transition-colors"
- >
- 查看全部
- </button>
- </div>
- </div>
- ))}
- </div>
- </div>
- {/* 字典详情弹窗 */}
- {showDictionaryDetail && (
- <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 animate-in fade-in duration-200 p-4">
- <div className="bg-white rounded-2xl shadow-2xl w-full max-w-5xl max-h-[90vh] flex flex-col animate-in zoom-in duration-200">
- {/* 弹窗头部 */}
- <div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between flex-shrink-0">
- <div>
- <h3 className="text-xl text-gray-900 mb-1">{currentDictionary}</h3>
- <p className="text-sm text-gray-500">管理 {currentDictionary} 的所有字典项</p>
- </div>
- <button
- onClick={() => setShowDictionaryDetail(false)}
- className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
- >
- <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
- </svg>
- </button>
- </div>
- {/* 工具栏 */}
- <div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between flex-shrink-0">
- {dictionaryFormMode === 'list' ? (
- <>
- <div className="relative w-80">
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
- <input
- type="text"
- placeholder="搜索字典项..."
- value={dictionarySearchTerm}
- onChange={(e) => setDictionarySearchTerm(e.target.value)}
- className="w-full h-10 pl-10 pr-4 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm"
- />
- </div>
- <button
- onClick={() => {
- setEditingDictionaryItem(null);
- setDictionaryFormMode('form');
- }}
- className="flex items-center gap-2 px-4 py-2.5 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl hover:shadow-lg hover:shadow-blue-400/40 transition-all duration-300"
- >
- <Plus className="w-4 h-4" strokeWidth={2.5} />
- <span className="text-sm">新增字典项</span>
- </button>
- </>
- ) : (
- <button
- onClick={() => setDictionaryFormMode('list')}
- className="flex items-center gap-2 px-4 py-2.5 text-gray-600 hover:bg-gray-100 rounded-xl transition-colors"
- >
- <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
- </svg>
- <span className="text-sm">返回列表</span>
- </button>
- )}
- </div>
- {/* 内容区域 - 根据模式切换 */}
- {dictionaryFormMode === 'list' ? (
- // 表格视图
- <div className="flex-1 overflow-auto">
- <table className="w-full">
- <thead className="sticky top-0 bg-white z-10">
- <tr className="bg-gradient-to-r from-gray-50 to-gray-100/50 border-b border-gray-200">
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[5%]">序号</th>
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[15%]">字典键</th>
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[20%]">字典值</th>
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[10%]">排序</th>
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[10%]">状态</th>
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[20%]">备注</th>
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[15%]">创建时间</th>
- <th className="px-6 py-4 text-center text-xs text-gray-600 uppercase tracking-wider w-[10%]">操作</th>
- </tr>
- </thead>
- <tbody className="divide-y divide-gray-100">
- {[
- { id: 1, key: 'admin_group', value: '管理员组', order: 1, status: '启用', remark: '系统管理员', createTime: '2025-01-01' },
- { id: 2, key: 'user_group', value: '普通用户组', order: 2, status: '启用', remark: '普通用户', createTime: '2025-01-02' },
- { id: 3, key: 'audit_group', value: '审计组', order: 3, status: '启用', remark: '审计人员', createTime: '2025-01-03' },
- { id: 4, key: 'guest_group', value: '访客组', order: 4, status: '禁用', remark: '临时访客', createTime: '2025-01-04' },
- { id: 5, key: 'test_group', value: '测试组', order: 5, status: '启用', remark: '测试人员', createTime: '2025-01-05' },
- { id: 6, key: 'dev_group', value: '开发组', order: 6, status: '启用', remark: '开发人员', createTime: '2025-01-06' },
- { id: 7, key: 'ops_group', value: '运维组', order: 7, status: '启用', remark: '运维人员', createTime: '2025-01-07' },
- { id: 8, key: 'security_group', value: '安全组', order: 8, status: '启用', remark: '安全人员', createTime: '2025-01-08' },
- { id: 9, key: 'manager_group', value: '经理组', order: 9, status: '启用', remark: '部门经理', createTime: '2025-01-09' },
- { id: 10, key: 'director_group', value: '总监组', order: 10, status: '启用', remark: '部门总监', createTime: '2025-01-10' },
- { id: 11, key: 'vp_group', value: 'VP组', order: 11, status: '启用', remark: '副总裁', createTime: '2025-01-11' },
- { id: 12, key: 'ceo_group', value: 'CEO组', order: 12, status: '启用', remark: '首席执行官', createTime: '2025-01-12' },
- ].map((item, index) => (
- <tr key={item.id} className="hover:bg-blue-50/30 transition-colors">
- <td className="px-6 py-4 text-sm text-gray-900">{item.id}</td>
- <td className="px-6 py-4 text-sm text-gray-900">{item.key}</td>
- <td className="px-6 py-4 text-sm text-gray-900">{item.value}</td>
- <td className="px-6 py-4 text-sm text-gray-900">{item.order}</td>
- <td className="px-6 py-4">
- <span className={`inline-flex px-3 py-1 rounded-lg text-xs ${
- item.status === '启用' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-700'
- }`}>
- {item.status}
- </span>
- </td>
- <td className="px-6 py-4 text-sm text-gray-500">{item.remark}</td>
- <td className="px-6 py-4 text-sm text-gray-900">{item.createTime}</td>
- <td className="px-6 py-4">
- <div className="flex items-center justify-center gap-2">
- <button
- onClick={() => {
- setEditingDictionaryItem(item);
- setDictionaryFormMode('form');
- }}
- className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
- title="编辑"
- >
- <Edit2 className="w-4 h-4" />
- </button>
- <button
- onClick={() => handleDelete(item.id)}
- className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
- title="删除"
- >
- <Trash2 className="w-4 h-4" />
- </button>
- </div>
- </td>
- </tr>
- ))}
- </tbody>
- </table>
- </div>
- ) : (
- // 表单视图
- <div className="flex-1 overflow-auto px-6 py-6">
- <div className="max-w-3xl mx-auto">
- <div className="grid grid-cols-2 gap-6">
- <div>
- <label className="block text-sm text-gray-700 mb-2">字典键 *</label>
- <input
- type="text"
- className="w-full px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm"
- placeholder="请输入字典键"
- defaultValue={editingDictionaryItem?.key || ''}
- />
- </div>
- <div>
- <label className="block text-sm text-gray-700 mb-2">字典值 *</label>
- <input
- type="text"
- className="w-full px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm"
- placeholder="请输入字典值"
- defaultValue={editingDictionaryItem?.value || ''}
- />
- </div>
- <div>
- <label className="block text-sm text-gray-700 mb-2">排序</label>
- <input
- type="number"
- className="w-full px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm"
- placeholder="请输入排序号"
- defaultValue={editingDictionaryItem?.order || ''}
- />
- </div>
- <div>
- <label className="block text-sm text-gray-700 mb-2">状态</label>
- <select className="w-full px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm">
- <option value="启用">启用</option>
- <option value="禁用">禁用</option>
- </select>
- </div>
- <div className="col-span-2">
- <label className="block text-sm text-gray-700 mb-2">备注</label>
- <textarea
- rows={3}
- className="w-full px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm resize-none"
- placeholder="请输入备注信息"
- defaultValue={editingDictionaryItem?.remark || ''}
- ></textarea>
- </div>
- </div>
- {/* 表单按钮 */}
- <div className="flex justify-end gap-3 mt-8 pt-6 border-t border-gray-200">
- <button
- onClick={() => setDictionaryFormMode('list')}
- className="px-5 py-2.5 text-sm text-gray-600 bg-white border border-gray-200 rounded-xl hover:bg-gray-50 transition-colors"
- >
- 取消
- </button>
- <button
- onClick={() => {
- console.log('保存字典项:', editingDictionaryItem);
- setDictionaryFormMode('list');
- }}
- className="px-5 py-2.5 text-sm text-white bg-gradient-to-r from-blue-500 to-blue-600 rounded-xl hover:shadow-lg hover:shadow-blue-400/40 transition-all"
- >
- {editingDictionaryItem ? '保存修改' : '确认新增'}
- </button>
- </div>
- </div>
- </div>
- )}
- {/* 底部分页 */}
- <div className="px-6 py-4 bg-gray-50/50 border-t border-gray-200 flex items-center justify-between flex-shrink-0">
- <div className="text-sm text-gray-600">
- 共 <span className="text-blue-600">12</span> 条记录
- </div>
- <div className="flex gap-2">
- <button className="px-4 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
- 上一页
- </button>
- <button className="px-4 py-2 text-sm text-white bg-blue-500 rounded-lg hover:bg-blue-600 transition-colors">
- 1
- </button>
- <button className="px-4 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
- 下一页
- </button>
- </div>
- </div>
- </div>
- </div>
- )}
- </>
- );
- }
- // 部门管理使用左右分栏布局
- if (subMenu === '部门管理' || subMenu === 'departmentManagement') {
- return <DepartmentManagement />;
- }
- // 岗位管理使用左右分栏布局
- if (subMenu === '岗位管理' || subMenu === 'positionManagement') {
- return <PositionManagement />;
- }
- // 其他菜单使用表格展示
- return (
- <div className="space-y-6">
- {/* 工具栏 */}
- <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm p-4">
- <div className="flex items-center justify-between">
- <div className="relative w-80">
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
- <input
- type="text"
- placeholder="搜索..."
- value={searchTerm}
- onChange={(e) => setSearchTerm(e.target.value)}
- className="w-full h-10 pl-10 pr-4 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm"
- />
- </div>
- <div className="flex gap-3">
- <button
- onClick={() => {
- setEditingItem(null);
- setShowAddModal(true);
- }}
- className="flex items-center gap-2 px-4 py-2.5 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl hover:shadow-lg hover:shadow-blue-400/40 transition-all duration-300"
- >
- <Plus className="w-4 h-4" strokeWidth={2.5} />
- <span className="text-sm">新增</span>
- </button>
- </div>
- </div>
- </div>
- {/* 菜单管理使用树形结构,其他使用表格 */}
- {subMenu === '菜单管理' ? (
- // 树形结构
- <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm overflow-hidden">
- <div className="overflow-x-auto">
- <table className="w-full">
- <thead>
- <tr className="bg-gradient-to-r from-gray-50 to-gray-100/50 border-b border-gray-200">
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '30%' }}>
- 菜单名称
- </th>
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '20%' }}>
- 路径
- </th>
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '10%' }}>
- 图标
- </th>
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '10%' }}>
- 排序
- </th>
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '10%' }}>
- 状态
- </th>
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '15%' }}>
- 创建时间
- </th>
- <th className="px-6 py-4 text-center text-xs text-gray-600 uppercase tracking-wider" style={{ width: '15%' }}>
- 操作
- </th>
- </tr>
- </thead>
- <tbody className="divide-y divide-gray-100">
- {menuTreeData.map((parentMenu) => (
- <React.Fragment key={parentMenu.id}>
- {/* 一级菜单 */}
- <tr className="hover:bg-blue-50/30 transition-colors bg-gray-50/50">
- <td className="px-6 py-4">
- <div className="flex items-center gap-2">
- {parentMenu.children && parentMenu.children.length > 0 ? (
- <button
- onClick={() => toggleExpand(parentMenu.id)}
- className="p-1 hover:bg-gray-200 rounded transition-colors"
- >
- {expandedMenuIds.includes(parentMenu.id) ? (
- <ChevronDown className="w-4 h-4 text-gray-600" />
- ) : (
- <ChevronRight className="w-4 h-4 text-gray-600" />
- )}
- </button>
- ) : (
- <span className="w-6"></span>
- )}
- <span className="text-sm text-gray-900">{parentMenu.name}</span>
- </div>
- </td>
- <td className="px-6 py-4 text-sm text-gray-900">{parentMenu.path}</td>
- <td className="px-6 py-4 text-sm text-gray-900">{parentMenu.icon}</td>
- <td className="px-6 py-4 text-sm text-gray-900">{parentMenu.order}</td>
- <td className="px-6 py-4">
- <span className={`inline-flex px-3 py-1 rounded-lg text-xs ${
- parentMenu.status === '启用' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-700'
- }`}>
- {parentMenu.status}
- </span>
- </td>
- <td className="px-6 py-4 text-sm text-gray-900">{parentMenu.createTime}</td>
- <td className="px-6 py-4">
- <div className="flex items-center justify-center gap-2">
- <button
- onClick={() => handleEdit(parentMenu)}
- className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
- title="编辑"
- >
- <Edit2 className="w-4 h-4" />
- </button>
- <button
- onClick={() => handleDelete(parentMenu.id)}
- className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
- title="删除"
- >
- <Trash2 className="w-4 h-4" />
- </button>
- <button
- className="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
- title="更多"
- >
- <MoreVertical className="w-4 h-4" />
- </button>
- </div>
- </td>
- </tr>
-
- {/* 二级菜单 */}
- {expandedMenuIds.includes(parentMenu.id) && parentMenu.children && parentMenu.children.map((childMenu: any) => (
- <tr key={childMenu.id} className="hover:bg-blue-50/20 transition-colors">
- <td className="px-6 py-4">
- <div className="flex items-center gap-2 pl-10">
- <div className="w-4 h-px bg-gray-300"></div>
- <span className="text-sm text-gray-700">{childMenu.name}</span>
- </div>
- </td>
- <td className="px-6 py-4 text-sm text-gray-700">{childMenu.path}</td>
- <td className="px-6 py-4 text-sm text-gray-700">{childMenu.icon}</td>
- <td className="px-6 py-4 text-sm text-gray-700">{childMenu.order}</td>
- <td className="px-6 py-4">
- <span className={`inline-flex px-3 py-1 rounded-lg text-xs ${
- childMenu.status === '启用' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-700'
- }`}>
- {childMenu.status}
- </span>
- </td>
- <td className="px-6 py-4 text-sm text-gray-700">{childMenu.createTime}</td>
- <td className="px-6 py-4">
- <div className="flex items-center justify-center gap-2">
- <button
- onClick={() => handleEdit(childMenu)}
- className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
- title="编辑"
- >
- <Edit2 className="w-4 h-4" />
- </button>
- <button
- onClick={() => handleDelete(childMenu.id)}
- className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
- title="删除"
- >
- <Trash2 className="w-4 h-4" />
- </button>
- <button
- className="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
- title="更多"
- >
- <MoreVertical className="w-4 h-4" />
- </button>
- </div>
- </td>
- </tr>
- ))}
- </React.Fragment>
- ))}
- </tbody>
- </table>
- </div>
- {/* 分页 */}
- <div className="px-6 py-4 bg-gray-50/50 border-t border-gray-200">
- <div className="flex items-center justify-between">
- <div className="text-sm text-gray-600">
- 共 <span className="text-blue-600">{menuTreeData.length}</span> 个一级菜单
- </div>
- </div>
- </div>
- </div>
- ) : (
- // 表格展示(其他子菜单)
- <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm overflow-hidden">
- <div className="overflow-x-auto">
- <table className="w-full">
- <thead>
- <tr className="bg-gradient-to-r from-gray-50 to-gray-100/50 border-b border-gray-200">
- <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '5%' }}>
- 序号
- </th>
- {columns.map((column) => (
- <th
- key={column.key}
- className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider"
- style={{ width: column.width }}
- >
- {column.label}
- </th>
- ))}
- <th className="px-6 py-4 text-center text-xs text-gray-600 uppercase tracking-wider" style={{ width: '15%' }}>
- 操作
- </th>
- </tr>
- </thead>
- <tbody className="divide-y divide-gray-100">
- {filteredData.map((row, index) => (
- <tr
- key={row.id}
- className="hover:bg-blue-50/30 transition-colors"
- >
- <td className="px-6 py-4 text-sm text-gray-900">
- {index + 1}
- </td>
- {columns.map((column) => (
- <td key={column.key} className="px-6 py-4 text-sm text-gray-900">
- {column.key === 'status' ? (
- <span
- className={`inline-flex px-3 py-1 rounded-lg text-xs ${
- row[column.key] === '启用' || row[column.key] === '正常'
- ? 'bg-green-100 text-green-700'
- : row[column.key] === '告警'
- ? 'bg-orange-100 text-orange-700'
- : 'bg-gray-100 text-gray-700'
- }`}
- >
- {row[column.key]}
- </span>
- ) : (
- row[column.key]
- )}
- </td>
- ))}
- <td className="px-6 py-4">
- <div className="flex items-center justify-center gap-2">
- <button
- onClick={() => handleEdit(row)}
- className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
- title="编辑"
- >
- <Edit2 className="w-4 h-4" />
- </button>
- <button
- onClick={() => handleDelete(row.id)}
- className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
- title="删除"
- >
- <Trash2 className="w-4 h-4" />
- </button>
- <button
- className="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
- title="更多"
- >
- <MoreVertical className="w-4 h-4" />
- </button>
- </div>
- </td>
- </tr>
- ))}
- </tbody>
- </table>
- </div>
- {/* 分页 */}
- <div className="px-6 py-4 bg-gray-50/50 border-t border-gray-200">
- <div className="flex items-center justify-between">
- <div className="text-sm text-gray-600">
- 共 <span className="text-blue-600">{filteredData.length}</span> 条记录
- </div>
- <div className="flex gap-2">
- <button className="px-4 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
- 上一页
- </button>
- <button className="px-4 py-2 text-sm text-white bg-blue-500 rounded-lg hover:bg-blue-600 transition-colors">
- 1
- </button>
- <button className="px-4 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
- 2
- </button>
- <button className="px-4 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
- 3
- </button>
- <button className="px-4 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
- 下一页
- </button>
- </div>
- </div>
- </div>
- </div>
- )}
- {/* 新增/编辑弹窗 */}
- {showAddModal && (
- <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 animate-in fade-in duration-200">
- <div className="bg-white rounded-2xl shadow-2xl w-full max-w-2xl mx-4 animate-in zoom-in duration-200">
- {/* 弹窗标题 */}
- <div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
- <h3 className="text-lg text-gray-900">
- {editingItem ? `编辑${subMenu}` : `新增${subMenu}`}
- </h3>
- <button
- onClick={() => setShowAddModal(false)}
- className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
- >
- <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
- </svg>
- </button>
- </div>
- {/* 弹窗内容 */}
- <div className="px-6 py-6">
- <div className="grid grid-cols-2 gap-4">
- {columns.slice(0, 4).map((column) => (
- <div key={column.key}>
- <label className="block text-sm text-gray-700 mb-2">
- {column.label}
- </label>
- <input
- type="text"
- className="w-full px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:border-blue-400 focus:ring-4 focus:ring-blue-100 transition-all text-sm"
- placeholder={`请输入${column.label}`}
- defaultValue={editingItem?.[column.key] || ''}
- />
- </div>
- ))}
- </div>
- </div>
- {/* 弹窗底部 */}
- <div className="px-6 py-4 bg-gray-50/50 border-t border-gray-200 flex justify-end gap-3 rounded-b-2xl">
- <button
- onClick={() => setShowAddModal(false)}
- className="px-5 py-2.5 text-sm text-gray-600 bg-white border border-gray-200 rounded-xl hover:bg-gray-50 transition-colors"
- >
- 取消
- </button>
- <button
- onClick={() => {
- console.log('保存:', editingItem);
- setShowAddModal(false);
- }}
- className="px-5 py-2.5 text-sm text-white bg-gradient-to-r from-blue-500 to-blue-600 rounded-xl hover:shadow-lg hover:shadow-blue-400/40 transition-all"
- >
- 确定
- </button>
- </div>
- </div>
- </div>
- )}
- </div>
- );
- }
|