SystemConfig.tsx 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134
  1. import React, { useState } from 'react';
  2. import { Plus, Search, Edit2, Trash2, MoreVertical, ChevronRight, ChevronDown } from 'lucide-react';
  3. import { Button } from 'antd';
  4. import { useTranslation } from 'react-i18next';
  5. import { Button as UIButton } from './ui/button';
  6. import DepartmentManagement from './DepartmentManagement';
  7. import MenuManagement from './MenuManagement';
  8. import PostManagement from './PostManagement';
  9. import RoleManagement from './RoleManagement';
  10. import SystemLockCabinetManagement from './lockCabinet/SystemLockCabinetManagement';
  11. import DictTypeManagement from './DictTypeManagement';
  12. interface TableRow {
  13. id: number;
  14. [key: string]: any;
  15. }
  16. interface SystemConfigProps {
  17. subMenu: string;
  18. }
  19. export default function SystemConfig({ subMenu }: SystemConfigProps) {
  20. const { t } = useTranslation();
  21. console.log('SystemConfig: 组件渲染,subMenu =', subMenu, '类型:', typeof subMenu);
  22. const [searchTerm, setSearchTerm] = useState('');
  23. const [showAddModal, setShowAddModal] = useState(false);
  24. const [editingItem, setEditingItem] = useState<TableRow | null>(null);
  25. const [showDictionaryDetail, setShowDictionaryDetail] = useState(false);
  26. const [currentDictionary, setCurrentDictionary] = useState<string>('');
  27. const [dictionarySearchTerm, setDictionarySearchTerm] = useState('');
  28. const [dictionaryFormMode, setDictionaryFormMode] = useState<'list' | 'form'>('list');
  29. const [editingDictionaryItem, setEditingDictionaryItem] = useState<TableRow | null>(null);
  30. const [expandedMenuIds, setExpandedMenuIds] = useState<number[]>([1, 2, 3, 4, 5, 6]); // 默认展开所有一级菜单
  31. const [expandedDeptIds, setExpandedDeptIds] = useState<number[]>([1]); // 部门树展开的节点
  32. const [selectedDeptId, setSelectedDeptId] = useState<number | null>(null); // 选中的部门ID
  33. // 树形菜单数据
  34. const menuTreeData = [
  35. {
  36. id: 1,
  37. name: '驾驶舱',
  38. path: '/cockpit',
  39. icon: '仪表盘',
  40. order: 1,
  41. status: '启用',
  42. createTime: '2025-01-01',
  43. children: []
  44. },
  45. {
  46. id: 2,
  47. name: '系统配置',
  48. path: '/system',
  49. icon: '设置',
  50. order: 2,
  51. status: '启用',
  52. createTime: '2025-01-02',
  53. children: [
  54. { id: 21, name: '菜单管理', path: '/system/menu', icon: '菜单', order: 1, status: '启用', createTime: '2025-01-02', parentId: 2 },
  55. { id: 22, name: '部门管理', path: '/system/dept', icon: '部门', order: 2, status: '启用', createTime: '2025-01-02', parentId: 2 },
  56. { id: 22, name: '岗位管理', path: '/system/post', icon: '岗位', order: 2, status: '启用', createTime: '2025-01-02', parentId: 2 },
  57. { id: 23, name: '角色管理', path: '/system/role', icon: '角色', order: 3, status: '启用', createTime: '2025-01-02', parentId: 2 },
  58. { id: 24, name: '字典管理', path: '/system/dict', icon: '字典', order: 4, status: '启用', createTime: '2025-01-02', parentId: 2 },
  59. { id: 25, name: '机柜管理', path: '/system/cabinet', icon: '机柜', order: 5, status: '启用', createTime: '2025-01-02', parentId: 2 },
  60. ]
  61. },
  62. {
  63. id: 3,
  64. name: '用户管理',
  65. path: '/users',
  66. icon: '用户',
  67. order: 3,
  68. status: '启用',
  69. createTime: '2025-01-03',
  70. children: [
  71. { id: 31, name: '用户列表', path: '/users/list', icon: '列表', order: 1, status: '启用', createTime: '2025-01-03', parentId: 3 },
  72. { id: 32, name: '通知管理', path: '/users/notification', icon: '通知', order: 2, status: '启用', createTime: '2025-01-03', parentId: 3 },
  73. ]
  74. },
  75. {
  76. id: 4,
  77. name: '硬件管理',
  78. path: '/hardware',
  79. icon: '硬件',
  80. order: 4,
  81. status: '启用',
  82. createTime: '2025-01-04',
  83. children: [
  84. { id: 41, name: '机柜', path: '/hardware/cabinet', icon: '机柜', order: 1, status: '启用', createTime: '2025-01-04', parentId: 4 },
  85. { id: 42, name: '钥匙', path: '/hardware/key', icon: '钥匙', order: 2, status: '启用', createTime: '2025-01-04', parentId: 4 },
  86. { id: 43, name: '挂锁', path: '/hardware/lock', icon: '锁', order: 3, status: '启用', createTime: '2025-01-04', parentId: 4 },
  87. { id: 44, name: '便携式', path: '/hardware/portable', icon: '便携', order: 4, status: '启用', createTime: '2025-01-04', parentId: 4 },
  88. ]
  89. },
  90. {
  91. id: 5,
  92. name: '点位管理',
  93. path: '/points',
  94. icon: '点位',
  95. order: 5,
  96. status: '启用',
  97. createTime: '2025-01-05',
  98. children: [
  99. { id: 51, name: '点位列表', path: '/points/list', icon: '列表', order: 1, status: '启用', createTime: '2025-01-05', parentId: 5 },
  100. { id: 52, name: '点位分组', path: '/points/group', icon: '分组', order: 2, status: '启用', createTime: '2025-01-05', parentId: 5 },
  101. ]
  102. },
  103. {
  104. id: 6,
  105. name: '隔离作业',
  106. path: '/isolation',
  107. icon: '作业',
  108. order: 6,
  109. status: '启用',
  110. createTime: '2025-01-06',
  111. children: [
  112. { id: 61, name: '作业列表', path: '/isolation/list', icon: '列表', order: 1, status: '启用', createTime: '2025-01-06', parentId: 6 },
  113. { id: 62, name: '作业审批', path: '/isolation/approval', icon: '审批', order: 2, status: '启用', createTime: '2025-01-06', parentId: 6 },
  114. { id: 63, name: '作业记录', path: '/isolation/record', icon: '记录', order: 3, status: '启用', createTime: '2025-01-06', parentId: 6 },
  115. ]
  116. },
  117. ];
  118. // 部门树形数据
  119. const departmentTreeData = [
  120. {
  121. id: 1,
  122. name: '总公司',
  123. manager: '李总',
  124. phone: '13800138000',
  125. email: 'ceo@company.com',
  126. memberCount: 120,
  127. createTime: '2024-01-01',
  128. children: [
  129. {
  130. id: 2,
  131. name: '技术中心',
  132. manager: '张三',
  133. phone: '13800138001',
  134. email: 'tech@company.com',
  135. memberCount: 45,
  136. createTime: '2024-01-05',
  137. parentId: 1,
  138. children: [
  139. {
  140. id: 21,
  141. name: '研发部',
  142. manager: '王研',
  143. phone: '13800138011',
  144. email: 'rd@company.com',
  145. memberCount: 25,
  146. createTime: '2024-01-10',
  147. parentId: 2
  148. },
  149. {
  150. id: 22,
  151. name: '测试部',
  152. manager: '赵测',
  153. phone: '13800138012',
  154. email: 'qa@company.com',
  155. memberCount: 12,
  156. createTime: '2024-01-10',
  157. parentId: 2
  158. },
  159. {
  160. id: 23,
  161. name: '运维部',
  162. manager: '李运',
  163. phone: '13800138013',
  164. email: 'ops@company.com',
  165. memberCount: 8,
  166. createTime: '2024-01-10',
  167. parentId: 2
  168. }
  169. ]
  170. },
  171. {
  172. id: 3,
  173. name: '市场中心',
  174. manager: '李四',
  175. phone: '13800138002',
  176. email: 'market@company.com',
  177. memberCount: 30,
  178. createTime: '2024-01-05',
  179. parentId: 1,
  180. children: [
  181. {
  182. id: 31,
  183. name: '市场部',
  184. manager: '钱市',
  185. phone: '13800138021',
  186. email: 'marketing@company.com',
  187. memberCount: 18,
  188. createTime: '2024-01-10',
  189. parentId: 3
  190. },
  191. {
  192. id: 32,
  193. name: '销售部',
  194. manager: '孙销',
  195. phone: '13800138022',
  196. email: 'sales@company.com',
  197. memberCount: 12,
  198. createTime: '2024-01-10',
  199. parentId: 3
  200. }
  201. ]
  202. },
  203. {
  204. id: 4,
  205. name: '行政中心',
  206. manager: '王五',
  207. phone: '13800138003',
  208. email: 'admin@company.com',
  209. memberCount: 25,
  210. createTime: '2024-01-05',
  211. parentId: 1,
  212. children: [
  213. {
  214. id: 41,
  215. name: '人力资源部',
  216. manager: '周人',
  217. phone: '13800138031',
  218. email: 'hr@company.com',
  219. memberCount: 10,
  220. createTime: '2024-01-10',
  221. parentId: 4
  222. },
  223. {
  224. id: 42,
  225. name: '财务部',
  226. manager: '吴财',
  227. phone: '13800138032',
  228. email: 'finance@company.com',
  229. memberCount: 8,
  230. createTime: '2024-01-10',
  231. parentId: 4
  232. },
  233. {
  234. id: 43,
  235. name: '行政部',
  236. manager: '郑行',
  237. phone: '13800138033',
  238. email: 'office@company.com',
  239. memberCount: 7,
  240. createTime: '2024-01-10',
  241. parentId: 4
  242. }
  243. ]
  244. },
  245. {
  246. id: 5,
  247. name: '安全部',
  248. manager: '赵六',
  249. phone: '13800138004',
  250. email: 'security@company.com',
  251. memberCount: 20,
  252. createTime: '2024-01-05',
  253. parentId: 1
  254. }
  255. ]
  256. }
  257. ];
  258. // 切换展开/收起
  259. const toggleExpand = (id: number) => {
  260. setExpandedMenuIds(prev =>
  261. prev.includes(id) ? prev.filter(item => item !== id) : [...prev, id]
  262. );
  263. };
  264. // 切换部门树展开/收起
  265. const toggleDeptExpand = (id: number) => {
  266. setExpandedDeptIds(prev =>
  267. prev.includes(id) ? prev.filter(item => item !== id) : [...prev, id]
  268. );
  269. };
  270. // 模拟数据
  271. const menuData: TableRow[] = [
  272. { id: 1, name: '系统配置', path: '/system', icon: '设置', order: 1, status: '启用', createTime: '2025-01-01' },
  273. { id: 2, name: '用户管理', path: '/users', icon: '用户', order: 2, status: '启用', createTime: '2025-01-02' },
  274. { id: 3, name: '硬件管理', path: '/hardware', icon: '硬件', order: 3, status: '启用', createTime: '2025-01-03' },
  275. { id: 4, name: '点位管理', path: '/points', icon: '点位', order: 4, status: '启用', createTime: '2025-01-04' },
  276. { id: 5, name: '隔离作业', path: '/isolation', icon: '作业', order: 5, status: '启用', createTime: '2025-01-05' },
  277. ];
  278. const departmentData: TableRow[] = [
  279. { id: 1, name: '技术部', manager: '张三', phone: '13800138001', email: 'tech@example.com', memberCount: 15, createTime: '2025-01-01' },
  280. { id: 2, name: '运维部', manager: '李四', phone: '13800138002', email: 'ops@example.com', memberCount: 10, createTime: '2025-01-02' },
  281. { id: 3, name: '安全部', manager: '王五', phone: '13800138003', email: 'security@example.com', memberCount: 8, createTime: '2025-01-03' },
  282. { id: 4, name: '测试部', manager: '赵六', phone: '13800138004', email: 'test@example.com', memberCount: 12, createTime: '2025-01-04' },
  283. ];
  284. const roleData: TableRow[] = [
  285. { id: 1, name: '超级管理员', code: 'admin', description: '拥有系统所有权限', userCount: 2, status: '启用', createTime: '2025-01-01' },
  286. { id: 2, name: '部门管理员', code: 'dept_admin', description: '管理部门用户和资源', userCount: 5, status: '启用', createTime: '2025-01-02' },
  287. { id: 3, name: '操作员', code: 'operator', description: '执行日常操作任务', userCount: 20, status: '启用', createTime: '2025-01-03' },
  288. { id: 4, name: '审计员', code: 'auditor', description: '查看系统日志和报表', userCount: 3, status: '启用', createTime: '2025-01-04' },
  289. ];
  290. const cabinetData: TableRow[] = [
  291. { id: 1, name: '1号机柜', location: 'A区-1排', model: 'CAB-42U', capacity: 42, used: 28, temperature: '23°C', status: '正常', createTime: '2025-01-01' },
  292. { id: 2, name: '2号机柜', location: 'A区-2排', model: 'CAB-42U', capacity: 42, used: 35, temperature: '24°C', status: '正常', createTime: '2025-01-02' },
  293. { id: 3, name: '3号机柜', location: 'B区-1排', model: 'CAB-48U', capacity: 48, used: 40, temperature: '25°C', status: '告警', createTime: '2025-01-03' },
  294. { id: 4, name: '4号机柜', location: 'B区-2排', model: 'CAB-48U', capacity: 48, used: 30, temperature: '22°C', status: '正常', createTime: '2025-01-04' },
  295. ];
  296. // 字典管理 - 使用卡片分组展示
  297. const dictionaryCategories = [
  298. {
  299. name: '用户分组',
  300. description: '用户分类管理',
  301. count: 12,
  302. items: ['管理员组', '普通用户组', '审计组', '访客组']
  303. },
  304. {
  305. name: '隔离方式',
  306. description: '能量隔离方式定义',
  307. count: 8,
  308. items: ['物理隔离', '逻辑隔离', '远程隔离', '手动隔离']
  309. },
  310. {
  311. name: '点位分组',
  312. description: '监控点位分类',
  313. count: 15,
  314. items: ['A区点位', 'B区点位', 'C区点位', 'D区点位']
  315. },
  316. {
  317. name: '设备类型',
  318. description: '设备分类字典',
  319. count: 20,
  320. items: ['变压器', '配电柜', '开关', '传感器']
  321. },
  322. ];
  323. // 根据当前子菜单获取数据和列配置
  324. const getTableConfig = () => {
  325. switch (subMenu) {
  326. case '菜单管理':
  327. return {
  328. data: menuData,
  329. columns: [
  330. { key: 'name', label: '菜单名称', width: '15%' },
  331. { key: 'path', label: '路径', width: '20%' },
  332. { key: 'icon', label: '图标', width: '10%' },
  333. { key: 'order', label: '排序', width: '10%' },
  334. { key: 'status', label: '状态', width: '10%' },
  335. { key: 'createTime', label: '创建时间', width: '15%' },
  336. ],
  337. };
  338. case '部门管理':
  339. return {
  340. data: departmentData,
  341. columns: [
  342. { key: 'name', label: '部门名称', width: '15%' },
  343. { key: 'manager', label: '负责人', width: '10%' },
  344. { key: 'phone', label: '联系电话', width: '15%' },
  345. { key: 'email', label: '邮箱', width: '20%' },
  346. { key: 'memberCount', label: '成员数', width: '10%' },
  347. { key: 'createTime', label: '创建时间', width: '15%' },
  348. ],
  349. };
  350. case '角色管理':
  351. return {
  352. data: roleData,
  353. columns: [
  354. { key: 'name', label: '角色名称', width: '15%' },
  355. { key: 'code', label: '角色编码', width: '15%' },
  356. { key: 'description', label: '描述', width: '25%' },
  357. { key: 'userCount', label: '用户数', width: '10%' },
  358. { key: 'status', label: '状态', width: '10%' },
  359. { key: 'createTime', label: '创建时间', width: '15%' },
  360. ],
  361. };
  362. case '机柜管理':
  363. return {
  364. data: cabinetData,
  365. columns: [
  366. { key: 'name', label: '机柜名称', width: '12%' },
  367. { key: 'location', label: '位置', width: '12%' },
  368. { key: 'model', label: '型号', width: '10%' },
  369. { key: 'capacity', label: '容量', width: '8%' },
  370. { key: 'used', label: '已用', width: '8%' },
  371. { key: 'temperature', label: '温度', width: '10%' },
  372. { key: 'status', label: '状态', width: '10%' },
  373. { key: 'createTime', label: '创建时间', width: '15%' },
  374. ],
  375. };
  376. default:
  377. return { data: [], columns: [] };
  378. }
  379. };
  380. const { data, columns } = getTableConfig();
  381. // 过滤数据
  382. const filteredData = data.filter((item) =>
  383. Object.values(item).some((value) =>
  384. String(value).toLowerCase().includes(searchTerm.toLowerCase())
  385. )
  386. );
  387. const handleDelete = (id: number) => {
  388. if (confirm('确定要删除这条数据吗?')) {
  389. console.log('删除:', id);
  390. }
  391. };
  392. const handleEdit = (item: TableRow) => {
  393. setEditingItem(item);
  394. setShowAddModal(true);
  395. };
  396. // 菜单管理使用专门的组件
  397. if (subMenu === '菜单管理' || subMenu === 'menuManagement') {
  398. return <MenuManagement />;
  399. }
  400. // 岗位管理使用专门的组件
  401. // 支持多种可能的 subMenu 值:'岗位管理'、'positionManagement'、'post'
  402. const isPostManagement = subMenu === '岗位管理' ||
  403. subMenu === 'positionManagement' ||
  404. subMenu === 'post';
  405. console.log('SystemConfig: 检查岗位管理条件,subMenu =', subMenu, '是否匹配:', isPostManagement);
  406. if (isPostManagement) {
  407. console.log('SystemConfig: ✅ 匹配成功,渲染岗位管理组件');
  408. return <PostManagement />;
  409. } else {
  410. console.log('SystemConfig: ❌ 不匹配,继续执行其他逻辑');
  411. }
  412. // 角色管理使用专门的组件
  413. // 支持多种可能的 subMenu 值:'角色管理'、'roleManagement'、'role'
  414. const isRoleManagement = subMenu === '角色管理' ||
  415. subMenu === 'roleManagement' ||
  416. subMenu === 'role';
  417. console.log('SystemConfig: 检查角色管理条件,subMenu =', subMenu, '是否匹配:', isRoleManagement);
  418. if (isRoleManagement) {
  419. console.log('SystemConfig: ✅ 匹配成功,渲染角色管理组件');
  420. return <RoleManagement />;
  421. } else {
  422. console.log('SystemConfig: ❌ 不匹配,继续执行其他逻辑');
  423. }
  424. // 字典管理使用专门的组件
  425. // 支持多种可能的 subMenu 值:'字典管理'、'dictionaryManagement'、'dictManagement'、'dict'
  426. const isDictManagement = subMenu === '字典管理' ||
  427. subMenu === 'dictionaryManagement' ||
  428. subMenu === 'dictManagement' ||
  429. subMenu === 'dict';
  430. console.log('SystemConfig: 检查字典管理条件,subMenu =', subMenu, '是否匹配:', isDictManagement);
  431. if (isDictManagement) {
  432. console.log('SystemConfig: ✅ 匹配成功,渲染字典管理组件');
  433. return <DictTypeManagement />;
  434. } else {
  435. console.log('SystemConfig: ❌ 不匹配,继续执行其他逻辑');
  436. }
  437. // 旧的字典管理代码(已废弃,保留作为备份)
  438. if (false && subMenu === '字典管理') {
  439. return (
  440. <>
  441. <div className="space-y-6">
  442. {/* 工具栏 */}
  443. <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm p-4">
  444. <div className="flex items-center justify-between">
  445. <div className="relative w-80">
  446. <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
  447. <input
  448. type="text"
  449. placeholder="搜索字典..."
  450. value={searchTerm}
  451. onChange={(e) => setSearchTerm(e.target.value)}
  452. 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"
  453. />
  454. </div>
  455. <button
  456. onClick={() => setShowAddModal(true)}
  457. 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"
  458. >
  459. <Plus className="w-4 h-4" strokeWidth={2.5} />
  460. <span className="text-sm">新增字典</span>
  461. </button>
  462. </div>
  463. </div>
  464. {/* 字典分类卡片 */}
  465. <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
  466. {dictionaryCategories.map((category, index) => (
  467. <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">
  468. <div className="p-6">
  469. <div className="flex items-start justify-between mb-4">
  470. <div>
  471. <h3 className="text-lg text-gray-900 mb-1">{category.name}</h3>
  472. <p className="text-sm text-gray-500">{category.description}</p>
  473. </div>
  474. <div className="px-3 py-1 bg-blue-100 text-blue-700 text-xs rounded-lg">
  475. {category.count}项
  476. </div>
  477. </div>
  478. <div className="space-y-2 mb-4">
  479. {category.items.map((item, idx) => (
  480. <div key={idx} className="flex items-center justify-between p-2 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer">
  481. <span className="text-sm text-gray-700">{item}</span>
  482. <div className="flex gap-1">
  483. <button className="p-1 hover:bg-white rounded transition-colors">
  484. <Edit2 className="w-3 h-3 text-blue-600" />
  485. </button>
  486. <button className="p-1 hover:bg-white rounded transition-colors">
  487. <Trash2 className="w-3 h-3 text-red-600" />
  488. </button>
  489. </div>
  490. </div>
  491. ))}
  492. </div>
  493. <button
  494. onClick={() => {
  495. setCurrentDictionary(category.name);
  496. setShowDictionaryDetail(true);
  497. }}
  498. 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"
  499. >
  500. 查看全部
  501. </button>
  502. </div>
  503. </div>
  504. ))}
  505. </div>
  506. </div>
  507. {/* 字典详情弹窗 */}
  508. {showDictionaryDetail && (
  509. <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">
  510. <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">
  511. {/* 弹窗头部 */}
  512. <div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between flex-shrink-0">
  513. <div>
  514. <h3 className="text-xl text-gray-900 mb-1">{currentDictionary}</h3>
  515. <p className="text-sm text-gray-500">管理 {currentDictionary} 的所有字典项</p>
  516. </div>
  517. <button
  518. onClick={() => setShowDictionaryDetail(false)}
  519. className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
  520. >
  521. <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  522. <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
  523. </svg>
  524. </button>
  525. </div>
  526. {/* 工具栏 */}
  527. <div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between flex-shrink-0">
  528. {dictionaryFormMode === 'list' ? (
  529. <>
  530. <div className="relative w-80">
  531. <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
  532. <input
  533. type="text"
  534. placeholder="搜索字典项..."
  535. value={dictionarySearchTerm}
  536. onChange={(e) => setDictionarySearchTerm(e.target.value)}
  537. 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"
  538. />
  539. </div>
  540. <button
  541. onClick={() => {
  542. setEditingDictionaryItem(null);
  543. setDictionaryFormMode('form');
  544. }}
  545. 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"
  546. >
  547. <Plus className="w-4 h-4" strokeWidth={2.5} />
  548. <span className="text-sm">新增字典项</span>
  549. </button>
  550. </>
  551. ) : (
  552. <button
  553. onClick={() => setDictionaryFormMode('list')}
  554. className="flex items-center gap-2 px-4 py-2.5 text-gray-600 hover:bg-gray-100 rounded-xl transition-colors"
  555. >
  556. <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  557. <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
  558. </svg>
  559. <span className="text-sm">返回列表</span>
  560. </button>
  561. )}
  562. </div>
  563. {/* 内容区域 - 根据模式切换 */}
  564. {dictionaryFormMode === 'list' ? (
  565. // 表格视图
  566. <div className="flex-1">
  567. <table className="w-full">
  568. <thead className="sticky top-0 bg-white z-10">
  569. <tr className="bg-gradient-to-r from-gray-50 to-gray-100/50 border-b border-gray-200">
  570. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[5%]">序号</th>
  571. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[15%]">字典键</th>
  572. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[20%]">字典值</th>
  573. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[10%]">排序</th>
  574. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[10%]">状态</th>
  575. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[20%]">备注</th>
  576. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider w-[15%]">创建时间</th>
  577. <th className="px-6 py-4 text-center text-xs text-gray-600 uppercase tracking-wider w-[10%]">操作</th>
  578. </tr>
  579. </thead>
  580. <tbody className="divide-y divide-gray-100">
  581. {[
  582. { id: 1, key: 'admin_group', value: '管理员组', order: 1, status: '启用', remark: '系统管理员', createTime: '2025-01-01' },
  583. { id: 2, key: 'user_group', value: '普通用户组', order: 2, status: '启用', remark: '普通用户', createTime: '2025-01-02' },
  584. { id: 3, key: 'audit_group', value: '审计组', order: 3, status: '启用', remark: '审计人员', createTime: '2025-01-03' },
  585. { id: 4, key: 'guest_group', value: '访客组', order: 4, status: '禁用', remark: '临时访客', createTime: '2025-01-04' },
  586. { id: 5, key: 'test_group', value: '测试组', order: 5, status: '启用', remark: '测试人员', createTime: '2025-01-05' },
  587. { id: 6, key: 'dev_group', value: '开发组', order: 6, status: '启用', remark: '开发人员', createTime: '2025-01-06' },
  588. { id: 7, key: 'ops_group', value: '运维组', order: 7, status: '启用', remark: '运维人员', createTime: '2025-01-07' },
  589. { id: 8, key: 'security_group', value: '安全组', order: 8, status: '启用', remark: '安全人员', createTime: '2025-01-08' },
  590. { id: 9, key: 'manager_group', value: '经理组', order: 9, status: '启用', remark: '部门经理', createTime: '2025-01-09' },
  591. { id: 10, key: 'director_group', value: '总监组', order: 10, status: '启用', remark: '部门总监', createTime: '2025-01-10' },
  592. { id: 11, key: 'vp_group', value: 'VP组', order: 11, status: '启用', remark: '副总裁', createTime: '2025-01-11' },
  593. { id: 12, key: 'ceo_group', value: 'CEO组', order: 12, status: '启用', remark: '首席执行官', createTime: '2025-01-12' },
  594. ].map((item, index) => (
  595. <tr key={item.id} className="hover:bg-blue-50/30 transition-colors">
  596. <td className="px-6 py-4 text-sm text-gray-900">{item.id}</td>
  597. <td className="px-6 py-4 text-sm text-gray-900">{item.key}</td>
  598. <td className="px-6 py-4 text-sm text-gray-900">{item.value}</td>
  599. <td className="px-6 py-4 text-sm text-gray-900">{item.order}</td>
  600. <td className="px-6 py-4">
  601. <span className={`inline-flex px-3 py-1 rounded-lg text-xs ${
  602. item.status === '启用' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-700'
  603. }`}>
  604. {item.status}
  605. </span>
  606. </td>
  607. <td className="px-6 py-4 text-sm text-gray-500">{item.remark}</td>
  608. <td className="px-6 py-4 text-sm text-gray-900">{item.createTime}</td>
  609. <td className="px-6 py-4">
  610. <div className="flex items-center justify-center gap-2">
  611. <button
  612. onClick={() => {
  613. setEditingDictionaryItem(item);
  614. setDictionaryFormMode('form');
  615. }}
  616. className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
  617. title="编辑"
  618. >
  619. <Edit2 className="w-4 h-4" />
  620. </button>
  621. <button
  622. onClick={() => handleDelete(item.id)}
  623. className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
  624. title="删除"
  625. >
  626. <Trash2 className="w-4 h-4" />
  627. </button>
  628. </div>
  629. </td>
  630. </tr>
  631. ))}
  632. </tbody>
  633. </table>
  634. </div>
  635. ) : (
  636. // 表单视图
  637. <div className="flex-1 px-6 py-6">
  638. <div className="max-w-3xl mx-auto">
  639. <div className="grid grid-cols-2 gap-6">
  640. <div>
  641. <label className="block text-sm text-gray-700 mb-2">字典键 *</label>
  642. <input
  643. type="text"
  644. 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"
  645. placeholder="请输入字典键"
  646. defaultValue={editingDictionaryItem?.key || ''}
  647. />
  648. </div>
  649. <div>
  650. <label className="block text-sm text-gray-700 mb-2">字典值 *</label>
  651. <input
  652. type="text"
  653. 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"
  654. placeholder="请输入字典值"
  655. defaultValue={editingDictionaryItem?.value || ''}
  656. />
  657. </div>
  658. <div>
  659. <label className="block text-sm text-gray-700 mb-2">排序</label>
  660. <input
  661. type="number"
  662. 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"
  663. placeholder="请输入排序号"
  664. defaultValue={editingDictionaryItem?.order || ''}
  665. />
  666. </div>
  667. <div>
  668. <label className="block text-sm text-gray-700 mb-2">状态</label>
  669. <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">
  670. <option value="启用">启用</option>
  671. <option value="禁用">禁用</option>
  672. </select>
  673. </div>
  674. <div className="col-span-2">
  675. <label className="block text-sm text-gray-700 mb-2">备注</label>
  676. <textarea
  677. rows={3}
  678. 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"
  679. placeholder="请输入备注信息"
  680. defaultValue={editingDictionaryItem?.remark || ''}
  681. ></textarea>
  682. </div>
  683. </div>
  684. {/* 表单按钮 */}
  685. <div className="flex justify-end gap-3 mt-8 pt-6 border-t border-gray-200">
  686. <button
  687. onClick={() => setDictionaryFormMode('list')}
  688. 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"
  689. >
  690. 取消
  691. </button>
  692. <button
  693. onClick={() => {
  694. console.log('保存字典项:', editingDictionaryItem);
  695. setDictionaryFormMode('list');
  696. }}
  697. 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"
  698. >
  699. {editingDictionaryItem ? '保存修改' : '确认新增'}
  700. </button>
  701. </div>
  702. </div>
  703. </div>
  704. )}
  705. {/* 底部分页 */}
  706. <div className="px-6 py-4 bg-gray-50/50 border-t border-gray-200 flex items-center justify-between flex-shrink-0">
  707. <div className="text-sm text-gray-600">
  708. {t('common.total')} <span className="text-blue-600">12</span> {t('common.records')}
  709. </div>
  710. <div className="flex gap-2">
  711. <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">
  712. {t('common.prevPage')}
  713. </button>
  714. <button className="px-4 py-2 text-sm text-white bg-blue-500 rounded-lg hover:bg-blue-600 transition-colors">
  715. 1
  716. </button>
  717. <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">
  718. {t('common.nextPage')}
  719. </button>
  720. </div>
  721. </div>
  722. </div>
  723. </div>
  724. )}
  725. </>
  726. );
  727. }
  728. // 部门管理使用左右分栏布局
  729. // 支持通过 key 或 name 来判断
  730. if (subMenu === '部门管理' || subMenu === 'departmentManagement' || subMenu === 'dept') {
  731. console.log('渲染部门管理组件,subMenu:', subMenu);
  732. return <DepartmentManagement key="department-management" />;
  733. }
  734. // 机柜管理
  735. if (subMenu === '机柜管理' || subMenu === 'cabinetManagement' || subMenu === 'cabinet') {
  736. console.log('渲染机柜管理组件,subMenu:', subMenu);
  737. return <SystemLockCabinetManagement key="system-lock-cabinet-management" />;
  738. }
  739. // 其他菜单使用表格展示
  740. return (
  741. <div className="space-y-6">
  742. {/* 工具栏 */}
  743. <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm p-4">
  744. <div className="flex items-center justify-between">
  745. <div className="relative w-80">
  746. <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
  747. <input
  748. type="text"
  749. placeholder="搜索..."
  750. value={searchTerm}
  751. onChange={(e) => setSearchTerm(e.target.value)}
  752. 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"
  753. />
  754. </div>
  755. <div className="flex gap-3">
  756. <button
  757. onClick={() => {
  758. setEditingItem(null);
  759. setShowAddModal(true);
  760. }}
  761. 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"
  762. >
  763. <Plus className="w-4 h-4" strokeWidth={2.5} />
  764. <span className="text-sm">新增</span>
  765. </button>
  766. </div>
  767. </div>
  768. </div>
  769. {/* 菜单管理使用树形结构,其他使用表格 */}
  770. {subMenu === '菜单管理' ? (
  771. // 树形结构
  772. <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm overflow-hidden">
  773. <div className="overflow-x-auto">
  774. <table className="w-full">
  775. <thead>
  776. <tr className="bg-gradient-to-r from-gray-50 to-gray-100/50 border-b border-gray-200">
  777. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '30%' }}>
  778. 菜单名称
  779. </th>
  780. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '20%' }}>
  781. 路径
  782. </th>
  783. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '10%' }}>
  784. 图标
  785. </th>
  786. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '10%' }}>
  787. 排序
  788. </th>
  789. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '10%' }}>
  790. 状态
  791. </th>
  792. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '15%' }}>
  793. 创建时间
  794. </th>
  795. <th className="px-6 py-4 text-center text-xs text-gray-600 uppercase tracking-wider" style={{ width: '15%' }}>
  796. 操作
  797. </th>
  798. </tr>
  799. </thead>
  800. <tbody className="divide-y divide-gray-100">
  801. {menuTreeData.map((parentMenu) => (
  802. <React.Fragment key={parentMenu.id}>
  803. {/* 一级菜单 */}
  804. <tr className="hover:bg-blue-50/30 transition-colors bg-gray-50/50">
  805. <td className="px-6 py-4">
  806. <div className="flex items-center gap-2">
  807. {parentMenu.children && parentMenu.children.length > 0 ? (
  808. <button
  809. onClick={() => toggleExpand(parentMenu.id)}
  810. className="p-1 hover:bg-gray-200 rounded transition-colors"
  811. >
  812. {expandedMenuIds.includes(parentMenu.id) ? (
  813. <ChevronDown className="w-4 h-4 text-gray-600" />
  814. ) : (
  815. <ChevronRight className="w-4 h-4 text-gray-600" />
  816. )}
  817. </button>
  818. ) : (
  819. <span className="w-6"></span>
  820. )}
  821. <span className="text-sm text-gray-900">{parentMenu.name}</span>
  822. </div>
  823. </td>
  824. <td className="px-6 py-4 text-sm text-gray-900">{parentMenu.path}</td>
  825. <td className="px-6 py-4 text-sm text-gray-900">{parentMenu.icon}</td>
  826. <td className="px-6 py-4 text-sm text-gray-900">{parentMenu.order}</td>
  827. <td className="px-6 py-4">
  828. <span className={`inline-flex px-3 py-1 rounded-lg text-xs ${
  829. parentMenu.status === '启用' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-700'
  830. }`}>
  831. {parentMenu.status}
  832. </span>
  833. </td>
  834. <td className="px-6 py-4 text-sm text-gray-900">{parentMenu.createTime}</td>
  835. <td className="px-6 py-4">
  836. <div className="flex items-center justify-center gap-2">
  837. <button
  838. onClick={() => handleEdit(parentMenu)}
  839. className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
  840. title="编辑"
  841. >
  842. <Edit2 className="w-4 h-4" />
  843. </button>
  844. <button
  845. onClick={() => handleDelete(parentMenu.id)}
  846. className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
  847. title="删除"
  848. >
  849. <Trash2 className="w-4 h-4" />
  850. </button>
  851. <button
  852. className="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
  853. title="更多"
  854. >
  855. <MoreVertical className="w-4 h-4" />
  856. </button>
  857. </div>
  858. </td>
  859. </tr>
  860. {/* 二级菜单 */}
  861. {expandedMenuIds.includes(parentMenu.id) && parentMenu.children && parentMenu.children.map((childMenu: any) => (
  862. <tr key={childMenu.id} className="hover:bg-blue-50/20 transition-colors">
  863. <td className="px-6 py-4">
  864. <div className="flex items-center gap-2 pl-10">
  865. <div className="w-4 h-px bg-gray-300"></div>
  866. <span className="text-sm text-gray-700">{childMenu.name}</span>
  867. </div>
  868. </td>
  869. <td className="px-6 py-4 text-sm text-gray-700">{childMenu.path}</td>
  870. <td className="px-6 py-4 text-sm text-gray-700">{childMenu.icon}</td>
  871. <td className="px-6 py-4 text-sm text-gray-700">{childMenu.order}</td>
  872. <td className="px-6 py-4">
  873. <span className={`inline-flex px-3 py-1 rounded-lg text-xs ${
  874. childMenu.status === '启用' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-700'
  875. }`}>
  876. {childMenu.status}
  877. </span>
  878. </td>
  879. <td className="px-6 py-4 text-sm text-gray-700">{childMenu.createTime}</td>
  880. <td className="px-6 py-4">
  881. <div className="flex items-center justify-center gap-2">
  882. <button
  883. onClick={() => handleEdit(childMenu)}
  884. className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
  885. title="编辑"
  886. >
  887. <Edit2 className="w-4 h-4" />
  888. </button>
  889. <button
  890. onClick={() => handleDelete(childMenu.id)}
  891. className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
  892. title="删除"
  893. >
  894. <Trash2 className="w-4 h-4" />
  895. </button>
  896. <button
  897. className="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
  898. title="更多"
  899. >
  900. <MoreVertical className="w-4 h-4" />
  901. </button>
  902. </div>
  903. </td>
  904. </tr>
  905. ))}
  906. </React.Fragment>
  907. ))}
  908. </tbody>
  909. </table>
  910. </div>
  911. {/* 分页 */}
  912. <div className="px-6 py-4 bg-gray-50/50 border-t border-gray-200">
  913. <div className="flex items-center justify-between">
  914. <div className="text-sm text-gray-600">
  915. {t('common.total')} <span className="text-blue-600">{menuTreeData.length}</span> {t('common.firstLevelMenus')}
  916. </div>
  917. </div>
  918. </div>
  919. </div>
  920. ) : (
  921. // 表格展示(其他子菜单)
  922. <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm overflow-hidden">
  923. <div className="overflow-x-auto">
  924. <table className="w-full">
  925. <thead>
  926. <tr className="bg-gradient-to-r from-gray-50 to-gray-100/50 border-b border-gray-200">
  927. <th className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider" style={{ width: '5%' }}>
  928. 序号
  929. </th>
  930. {columns.map((column) => (
  931. <th
  932. key={column.key}
  933. className="px-6 py-4 text-left text-xs text-gray-600 uppercase tracking-wider"
  934. style={{ width: column.width }}
  935. >
  936. {column.label}
  937. </th>
  938. ))}
  939. <th className="px-6 py-4 text-center text-xs text-gray-600 uppercase tracking-wider" style={{ width: '15%' }}>
  940. 操作
  941. </th>
  942. </tr>
  943. </thead>
  944. <tbody className="divide-y divide-gray-100">
  945. {filteredData.map((row, index) => (
  946. <tr
  947. key={row.id}
  948. className="hover:bg-blue-50/30 transition-colors"
  949. >
  950. <td className="px-6 py-4 text-sm text-gray-900">
  951. {index + 1}
  952. </td>
  953. {columns.map((column) => (
  954. <td key={column.key} className="px-6 py-4 text-sm text-gray-900">
  955. {column.key === 'status' ? (
  956. <span
  957. className={`inline-flex px-3 py-1 rounded-lg text-xs ${
  958. row[column.key] === '启用' || row[column.key] === '正常'
  959. ? 'bg-green-100 text-green-700'
  960. : row[column.key] === '告警'
  961. ? 'bg-orange-100 text-orange-700'
  962. : 'bg-gray-100 text-gray-700'
  963. }`}
  964. >
  965. {row[column.key]}
  966. </span>
  967. ) : (
  968. row[column.key]
  969. )}
  970. </td>
  971. ))}
  972. <td className="px-6 py-4">
  973. <div className="flex items-center justify-center gap-2">
  974. <Button
  975. variant="ghost"
  976. size="sm"
  977. onClick={() => handleEdit(row)}
  978. className="h-8 px-2"
  979. >
  980. <Edit2 className="w-4 h-4" />
  981. <span className="ml-1">编辑</span>
  982. </Button>
  983. <Button
  984. variant="ghost"
  985. size="sm"
  986. onClick={() => handleDelete(row.id)}
  987. className="h-8 px-2 text-red-600 hover:text-red-700"
  988. >
  989. <Trash2 className="w-4 h-4" />
  990. <span className="ml-1">删除</span>
  991. </Button>
  992. <Button
  993. variant="ghost"
  994. size="sm"
  995. className="h-8 px-2"
  996. >
  997. <MoreVertical className="w-4 h-4" />
  998. <span className="ml-1">更多</span>
  999. </Button>
  1000. </div>
  1001. </td>
  1002. </tr>
  1003. ))}
  1004. </tbody>
  1005. </table>
  1006. </div>
  1007. {/* 分页 */}
  1008. {filteredData.length > 0 && (
  1009. <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
  1010. <div className="flex items-center justify-between">
  1011. <div className="text-sm text-gray-600">
  1012. {t('common.total')} <span className="text-blue-600 font-medium">{filteredData.length}</span> {t('common.records')}
  1013. </div>
  1014. <div className="flex gap-2">
  1015. <Button
  1016. disabled={true}
  1017. >
  1018. {t('common.prevPage')}
  1019. </Button>
  1020. <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
  1021. 1 / 1
  1022. </span>
  1023. <Button
  1024. disabled={true}
  1025. >
  1026. {t('common.nextPage')}
  1027. </Button>
  1028. </div>
  1029. </div>
  1030. </div>
  1031. )}
  1032. </div>
  1033. )}
  1034. {/* 新增/编辑弹窗 */}
  1035. {showAddModal && (
  1036. <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 animate-in fade-in duration-200">
  1037. <div className="bg-white rounded-2xl shadow-2xl w-full max-w-2xl mx-4 animate-in zoom-in duration-200">
  1038. {/* 弹窗标题 */}
  1039. <div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
  1040. <h3 className="text-lg text-gray-900">
  1041. {editingItem ? `编辑${subMenu}` : `新增${subMenu}`}
  1042. </h3>
  1043. <button
  1044. onClick={() => setShowAddModal(false)}
  1045. className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
  1046. >
  1047. <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  1048. <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
  1049. </svg>
  1050. </button>
  1051. </div>
  1052. {/* 弹窗内容 */}
  1053. <div className="px-6 py-6">
  1054. <div className="grid grid-cols-2 gap-4">
  1055. {columns.slice(0, 4).map((column) => (
  1056. <div key={column.key}>
  1057. <label className="block text-sm text-gray-700 mb-2">
  1058. {column.label}
  1059. </label>
  1060. <input
  1061. type="text"
  1062. 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"
  1063. placeholder={`请输入${column.label}`}
  1064. defaultValue={editingItem?.[column.key] || ''}
  1065. />
  1066. </div>
  1067. ))}
  1068. </div>
  1069. </div>
  1070. {/* 弹窗底部 */}
  1071. <div className="px-6 py-4 bg-gray-50/50 border-t border-gray-200 flex justify-end gap-3 rounded-b-2xl">
  1072. <button
  1073. onClick={() => setShowAddModal(false)}
  1074. 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"
  1075. >
  1076. 取消
  1077. </button>
  1078. <button
  1079. onClick={() => {
  1080. console.log('保存:', editingItem);
  1081. setShowAddModal(false);
  1082. }}
  1083. 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"
  1084. >
  1085. 确定
  1086. </button>
  1087. </div>
  1088. </div>
  1089. </div>
  1090. )}
  1091. </div>
  1092. );
  1093. }