SystemConfig.tsx 50 KB

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