diff --git a/package-lock.json b/package-lock.json index 5a5feda..03fc59e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "dependencies": { "@ant-design/charts": "^2.6.7", - "@ant-design/icons": "^6.1.1", + "@ant-design/icons": "^6.2.2", "@reduxjs/toolkit": "^2.11.2", "@tailwindcss/vite": "^4.1.14", "@vitejs/plugin-react": "^5.0.4", @@ -141,14 +141,14 @@ } }, "node_modules/@ant-design/icons": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.1.1.tgz", - "integrity": "sha512-AMT4N2y++TZETNHiM77fs4a0uPVCJGuL5MTonk13Pvv7UN7sID1cNEZOc1qNqx6zLKAOilTEFAdAoAFKa0U//Q==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.2.2.tgz", + "integrity": "sha512-zlJtE7AMbG12TeYVPhtBXwNpFInNy8mjLzcIm+0BPw16/b8ODG87YJ1G37VIF5VFscdgfsf6EweAFPTobu/3iQ==", "license": "MIT", "dependencies": { - "@ant-design/colors": "^8.0.0", - "@ant-design/icons-svg": "^4.4.0", - "@rc-component/util": "^1.3.0", + "@ant-design/colors": "^8.0.1", + "@ant-design/icons-svg": "^4.4.2", + "@rc-component/util": "^1.10.1", "clsx": "^2.1.1" }, "engines": { diff --git a/package.json b/package.json index 64c0c73..64b61ac 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@ant-design/charts": "^2.6.7", - "@ant-design/icons": "^6.1.1", + "@ant-design/icons": "^6.2.2", "@reduxjs/toolkit": "^2.11.2", "@tailwindcss/vite": "^4.1.14", "@vitejs/plugin-react": "^5.0.4", diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 4829cb8..dc2c774 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -63,7 +63,8 @@ export default function AppLayout() { { key: '/roles', icon: , label: '角色管理', roles: ['admin'] }, { key: '/menuList', icon: , label: '菜单管理', roles: ['admin'] }, { key: '/organization', icon: , label: '组织管理', roles: ['admin'] }, - { key: '/station', icon: , label: '场站管理', roles: ['admin'] } + { key: '/station', icon: , label: '场站管理', roles: ['admin'] }, + { key: '/system', icon: , label: '系统设置', roles: ['admin'] } ]; const filteredMenu = menuItems; diff --git a/src/pages/SystemSetting.tsx b/src/pages/SystemSetting.tsx new file mode 100644 index 0000000..48e33c2 --- /dev/null +++ b/src/pages/SystemSetting.tsx @@ -0,0 +1,575 @@ +import React, { useState } from 'react'; +import { + Layout, Menu, Card, Row, Col, Button, Form, Input, Select, Switch, Tag, Space, Typography, Statistic, message, Table, Divider +} from 'antd'; +import { + SettingOutlined, UserOutlined, BellOutlined, LinkOutlined, SafetyOutlined, SyncOutlined, + CloudOutlined, TeamOutlined, FileTextOutlined, DatabaseOutlined, CalendarOutlined, + CheckCircleOutlined, CloseCircleOutlined, ReloadOutlined, DownloadOutlined, DeleteOutlined, + KeyOutlined, + AppstoreOutlined, + BankOutlined +} from '@ant-design/icons'; +import UserRolePage from './UserPage'; +import MenuList from './MenuList'; +import Organization from './Organization'; +import StationManage from './StationManage'; + +const { Header, Content } = Layout; +const { Title, Text } = Typography; +const { Option } = Select; +const AntdCard = Card as any; + +// 子页面组件,你可以替换成自己的 +const BasicSettings = () => { + const [form] = Form.useForm(); + + const handleSave = () => { + message.success('设置已保存'); + }; + + return ( +
+ {/* 顶部概览卡片 */} + + + + +
+ +
+
+
当前版本
+
v2.8.1 最新版
+
+
+
+ + + + +
+ +
+
+
在线用户
+
12 ↑ 2
+
+
+
+ + + + +
+ +
+
+
告警通道
+
3 / 4 正常
+
+
+
+ + + + +
+ +
+
+
数据备份状态
+
正常 ●
+
+
+
+ + + + +
+ +
+
+
最近更新
+
2025-05-20
+
+
+
+ +
+ + {/* 基础配置 */} + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + {/* 通知与告警策略 */} + + + + +
+ +
+
+
+ 短信 + +
+ 已启用 +
+
+ + + +
+ +
+
+
+ 邮件 + +
+ 已启用 +
+
+ + + +
+ +
+
+
+ Telegram + +
+ 已启用 +
+
+ + + +
+ +
+
+
+ 系统通知 + +
+ 已启用 +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + {/* 用户与权限预览 */} + + {t} }, + { title: '手机号', dataIndex: 'phone' }, + { title: '邮箱', dataIndex: 'email' }, + { title: '状态', dataIndex: 'status', render: () => ● 在线 }, + { title: '操作', render: () => } + ]} + dataSource={[ + { name: '张运维', role: '超级管理员', phone: '138****0001', email: 'zhangyw@pvsrmart.com' }, + { name: '李工程师', role: '运维工程师', phone: '139****0002', email: 'li.gongcheng@pvsrmart.com' }, + { name: '王班长', role: '巡检主管', phone: '137****0003', email: 'wang.banzhang@pvsrmart.com' }, + { name: '赵分析师', role: '数据分析师', phone: '136****0004', email: 'zhao.fx@pvsrmart.com' }, + ]} + /> + + + ); +}; + +// 右侧面板组件 +const RightPanel = () => { + return ( +
+ {/* 角色权限概览 */} + + +
+ + +
+
+ +
+ 超级管理员 +
+ 用户2人 | 权限28项 + + 全部权限 + 系统管理 + +
+
+ + + + +
+
+ +
+ 运维工程师 +
+ 用户5人 | 权限18项 + + 设备管理 + 工单管理 + +
+
+ + + + +
+
+ +
+ 巡检主管 +
+ 用户8人 | 权限12项 + + 巡检管理 + 告警处理 + +
+
+ + + + +
+
+ +
+ 数据分析师 +
+ 用户3人 | 权限15项 + + 数据分析 + 报表查看 + +
+
+ + + + + {/* 设备接入状态 */} + 查看详情 }> + + + +
+ 逆变器 + 5 / 5 ● 正常 +
+
+ 汇流箱 + 20 / 20 ● 正常 +
+
+ 气象站 + 1 / 1 ● 正常 +
+
+ 摄像头 + 12 / 12 ● 正常 +
+
+ 无人机 + 2 / 2 ● 正常 +
+
+ 机器人 + 5 / 5 ● 正常 +
+
+ API接口 + 正常 ● 正常 +
+
+ Modbus + 正常 ● 正常 +
+
+ + + {/* 这里可以放拓扑图,先用占位 */} +
+
+ +
设备拓扑图
+
+
+ + + + + {/* 数据与安全 */} + + + + +
+ 数据保留 + 3年 +
+
+ 每日自动备份 + 02:00 +
+
+ 最近备份时间 + 2025-05-24 02:00 +
+
+ + + +
+ 登录审计 + 已开启 +
+
+ API密钥管理 + 正常 +
+
+ SSL证书 + 有效期至 2026-12-31 +
+
+ 双因素认证 + 已开启 +
+
+ + + + + {/* 系统维护 */} + 操作需管理员权限}> + + + + + + + + + + + + + + + + + ); +}; + +// 主页面组件 +export default function SystemSettings() { + const [activeTab, setActiveTab] = useState('basic'); + + const tabItems = [ + { key: 'basic', label: '基础设置', icon: }, + { key: 'user-role', label: '用户与角色', icon: }, + { key: 'menu', label: '菜单管理', icon: }, + { key: 'org', label: '组织管理', icon: }, // + { key: 'station', label: '场站管理', icon: }, + { key: 'notification', label: '通知策略', icon: }, + { key: 'device', label: '设备接入', icon: }, + { key: 'security', label: '数据与安全', icon: }, + { key: 'maintenance', label: '系统维护', icon: }, + ]; + + const renderTabContent = () => { + switch (activeTab) { + case 'basic': + return ( + + + + + ); + case 'user-role': + return ; + case 'menu': + return ; // 菜单管理 + case 'org': + return ; // 组织管理 + case 'station': + return ; // 场站管理 + case 'notification': + return
通知策略页面
; + case 'device': + return
设备接入页面
; + case 'security': + return
数据与安全页面
; + case 'maintenance': + return
系统维护页面
; + default: + return null; + } + }; + + return ( + + {/* 固定在当前组件内部顶部,不随滚动 */} +
+ setActiveTab(key)} + items={tabItems} + style={{ border: 'none', height: 64, lineHeight: '64px', fontSize: 14 }} + /> +
+ + {/* 内容区域可滚动 */} + + {renderTabContent()} + +
+ ); +} diff --git a/src/pages/UserPage.tsx b/src/pages/UserPage.tsx new file mode 100644 index 0000000..7f51b58 --- /dev/null +++ b/src/pages/UserPage.tsx @@ -0,0 +1,338 @@ +import React, { useState, useEffect } from 'react'; +import { + Layout, Tabs, Card, Row, Col, Button, Typography, Space, Modal, Form, Tree, message, + Input, Popconfirm, Divider, InputNumber, Switch, Pagination, Table, Select, + Tag +} from 'antd'; +import { + PlusOutlined, EditOutlined, DeleteOutlined, UserOutlined +} from '@ant-design/icons'; +import { getMenuList } from '../api/mune'; +import { buildPermTree, buildTreeFromFlat } from '../lib/utils'; +import { addRole, deleteRole, editRoleApi, getRoleList } from '../api/role'; +import { addUser, deleteUser, editUser, getUser } from '../api/user'; +import EmptyData from '../components/Empty'; + +const { Text } = Typography; +const { TabPane } = Tabs; +const AntdCard = Card as any; + +// 用户 + 角色 合并页面 +export default function UserRolePage() { + // ====================== 角色管理状态 ====================== + const [roles, setRoles] = useState([]); + const [menuTree, setMenuTree] = useState([]); + const [checkedKeys, setCheckedKeys] = useState([]); + const [roleModalVisible, setRoleModalVisible] = useState(false); + const [roleForm] = Form.useForm(); + const [editRole, setEditRole] = useState(null); + + // ====================== 用户管理状态 ====================== + const [userList, setUserList] = useState([]); + const [modalVisible, setModalVisible] = useState(false); + const [form] = Form.useForm(); + const [currentUser, setCurrentUser] = useState(null); + const [rolesOption, setRolesOption] = useState([]); + + // ====================== 统一分页 ====================== + const [rolePagination, setRolePagination] = useState({ + pageNum: 1, pageSize: 12, total: 0 + }); + const [userPagination, setUserPagination] = useState({ + pageNum: 1, pageSize: 10, total: 0 + }); + + // ====================== 获取角色列表 ====================== + const getRoleData = () => { + const params = { + pageNum: rolePagination.pageNum, + pageSize: rolePagination.pageSize + }; + getRoleList(params).then(res => { + if (res.code === 200) { + setRoles(res.data || []); + setRolePagination({ + ...rolePagination, + total: res.total || res.data?.length || 0 + }); + } + }); + }; + + // ====================== 获取菜单树 ====================== + const getMenuData = () => { + getMenuList({ pageNum: 1, pageSize: 1000 }).then(res => { + if (res.code === 200) { + const treeData = buildTreeFromFlat(res.data || []); + setMenuTree(buildPermTree(treeData)); + } + }); + }; + + // ====================== 获取用户列表 ====================== + const getUserData = () => { + const params = { + pageNum: userPagination.pageNum, + pageSize: userPagination.pageSize + }; + getUser(params).then(res => { + if (res.code === 200) { + setUserList(res.rows || []); + setUserPagination({ + ...userPagination, + total: res.total || res.rows?.length || 0 + }); + } + }); + }; + + // ====================== 获取角色下拉框 ====================== + const getRoleForSelect = () => { + getRoleList({ pageNum: 1, pageSize: 1000 }).then(res => { + if (res.code === 200) setRolesOption(res.data || []); + }); + }; + + // ====================== 加载数据 ====================== + useEffect(() => { + getRoleData(); + getMenuData(); + }, [rolePagination.pageNum, rolePagination.pageSize]); + + useEffect(() => { + getUserData(); + getRoleForSelect(); + }, [userPagination.pageNum, userPagination.pageSize]); + + // ====================== 角色操作 ====================== + const handleAddRole = () => { + roleForm.resetFields(); + setCheckedKeys([]); + setEditRole(null); + roleForm.setFieldsValue({ status: true, roleSort: 1 }); + setRoleModalVisible(true); + }; + + const handleEditRole = (record) => { + setEditRole(record); + roleForm.setFieldsValue({ ...record, status: record.status == 1 }); + setCheckedKeys((record.menuIds || []).map(Number)); + setRoleModalVisible(true); + }; + + const handleDeleteRole = (roleId) => { + deleteRole([roleId]).then(res => { + if (res.code === 200) { message.success('删除成功'); getRoleData(); } + else message.error(res.msg || '删除失败'); + }); + }; + + const handleSaveRole = () => { + roleForm.validateFields().then(values => { + const params = { + ...values, + menuIds: checkedKeys, + status: values.status ? 1 : 0, + roleSort: values.roleSort || 1, + }; + if (editRole) params.roleId = editRole.roleId; + + const api = editRole ? editRoleApi : addRole; + api(params).then(res => { + if (res.code === 200) { + message.success(editRole ? '编辑成功' : '新增成功'); + setRoleModalVisible(false); + getRoleData(); + getRoleForSelect(); + } else message.error(res.msg || '保存失败'); + }); + }); + }; + + // ====================== 用户操作 ====================== + const handleAddUser = () => { + form.resetFields(); + setCurrentUser(null); + setModalVisible(true); + }; + + const handleEditUser = (record) => { + setCurrentUser(record); + form.setFieldsValue({ ...record, roleIds: record.roles?.map(i => i.roleId) || [] }); + setModalVisible(true); + }; + + const handleDeleteUser = (userId) => { + deleteUser(userId).then(res => { + if (res.code === 200) { message.success('删除成功'); getUserData(); } + else message.error(res.msg || '删除失败'); + }); + }; + + const handleSaveUser = () => { + form.validateFields().then(values => { + const params = { ...values, roleIds: values.roleIds || [] }; + if (currentUser) params.userId = currentUser.userId; + + const api = currentUser ? editUser : addUser; + api(params).then(res => { + if (res.code === 200) { + message.success(currentUser ? '编辑成功' : '新增成功'); + setModalVisible(false); + getUserData(); + } else message.error(res.msg || '保存失败'); + }); + }); + }; + + // ====================== 页面主体 ====================== + return ( +
+ + {/* 整体卡片 */} + + + + {/* ========== 用户管理 ========== */} + +
+
+ +
+ +
(r.roles || []).map(i => ( + {i.roleName || i.roleKey} + )) + }, + { + title: '状态', render: (s) => s == '0' ? + 正常 : 停用 + }, + { + title: '操作', render: (_, r) => ( + + + handleDeleteUser(r.userId)} title="确定删除?"> + + + + ) + } + ]} + dataSource={userList} + pagination={{ + current: userPagination.pageNum, + pageSize: userPagination.pageSize, + total: userPagination.total, + showSizeChanger: true, + showTotal: t => `共 ${t} 条` + }} + onChange={(p) => setUserPagination({ ...userPagination, pageNum: p.current, pageSize: p.pageSize })} + bordered + size="middle" + /> + + + {/* ========== 角色管理 ========== */} + +
+
+ +
+ + + {roles.length > 0 ? roles.map(r => ( +
+ } onClick={() => handleEditRole(r)}>编辑, + handleDeleteRole(r.roleId)} title="确定删除?"> + + + ]} + > + {r.roleName}} + description={ + + 权限:{r.roleKey} + 状态:{r.status == 1 ? '正常' : '停用'} + {r.admin && 超级管理员} + + } + /> + + + )) : } + + +
+ `共 ${t} 条`} + onChange={(p, s) => setRolePagination({ pageNum: p, pageSize: s, total: rolePagination.total })} + /> +
+ + + + + {/* ========== 角色弹窗 ========== */} + setRoleModalVisible(false)} onOk={handleSaveRole} width={560}> +
+ + + + + 菜单权限 + + +
+ + {/* ========== 用户弹窗 ========== */} + setModalVisible(false)} onOk={handleSaveUser} width={680} destroyOnClose> +
+ +
+ + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/pages/Users.tsx b/src/pages/Users.tsx index be5948e..1dba169 100644 --- a/src/pages/Users.tsx +++ b/src/pages/Users.tsx @@ -1,12 +1,13 @@ import React, { useState, useEffect } from 'react'; import { Card, Table, Button, Typography, Space, Modal, Form, - Input, Select, Popconfirm, message, Tag, Row, Col, Switch + Input, Select, Popconfirm, message, Tag, Row, Col } from 'antd'; import { - PlusOutlined, EditOutlined, DeleteOutlined, UserOutlined + PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'; import { addUser, deleteUser, editUser, getUser } from '../api/user'; +import { getRoleList } from '../api/role'; const { Title } = Typography; const { Option } = Select; @@ -17,6 +18,7 @@ export default function UserList() { const [form] = Form.useForm(); const [currentUser, setCurrentUser] = useState(null); const [messageApi, contextHolder] = message.useMessage(); + const [roles, setRoles] = useState([]); // 统一分页 const [pagination, setPagination] = useState({ @@ -25,6 +27,16 @@ export default function UserList() { total: 0 }); + // 获取角色列表 + const getRole = () => { + const params = { pageNum: 1, pageSize: 1000 }; + getRoleList(params).then(res => { + if (res.code === 200) { + setRoles(res.data || []); + } + }); + }; + // 获取用户列表 const getUserList = () => { const params = { @@ -53,6 +65,7 @@ export default function UserList() { useEffect(() => { getUserList(); + getRole(); }, [pagination.pageNum, pagination.pageSize]); // 新增 @@ -65,8 +78,11 @@ export default function UserList() { // 编辑 const handleEdit = (record) => { setCurrentUser(record); + const roleIds = record.roles?.map(item => item.roleId) || []; + form.setFieldsValue({ - ...record + ...record, + roleIds: roleIds, // 回填多选角色 }); setModalVisible(true); }; @@ -80,62 +96,66 @@ export default function UserList() { } else { messageApi.error(res.msg || '删除失败'); } - }); + }).catch(() => { + messageApi.error('删除失败'); + }) }; - // 提交保存(完全匹配后端字段) + // 提交保存 const handleSubmit = () => { form.validateFields().then(values => { - // 构建后端需要的完整参数 const params = { ...values, - roleIds: values.roleId ? [values.roleId] : [] // 角色ID数组 + roleIds: values.roleIds || [], // 直接提交数组 }; if (currentUser) { - // 编辑 + params.userId = currentUser.userId; editUser(params).then(res => { if (res.code === 200) { messageApi.success('编辑成功'); getUserList(); setModalVisible(false); + } else { + messageApi.error(res.msg || '编辑失败'); } + }).catch(() => { + messageApi.error('编辑失败'); }); } else { - // 新增 addUser(params).then(res => { if (res.code === 200) { messageApi.success('新增成功'); getUserList(); setModalVisible(false); + } else { + messageApi.error(res.msg || '新增失败'); } - }); + }).catch(() => { + messageApi.error('新增失败'); + }) } }); }; - // 表格列 + // 表格列(支持多角色展示) const columns = [ - { - title: '用户账号', - dataIndex: 'userName' - }, - { - title: '用户昵称', - dataIndex: 'nickName' - }, - { - title: '手机号', - dataIndex: 'phonenumber' - }, - { - title: '邮箱', - dataIndex: 'email' - }, + { title: '用户账号', dataIndex: 'userName' }, + { title: '用户昵称', dataIndex: 'nickName' }, + { title: '手机号', dataIndex: 'phonenumber' }, + { title: '邮箱', dataIndex: 'email' }, { title: '角色', - dataIndex: 'roleNames', - render: (t) => {t || '未分配'} + render: (_, record) => { + const roles = record.roles || []; + if (roles.length === 0) return 未分配; + + return roles.map(item => ( + + {item.roleName || item.roleKey || '角色'} + + )); + } }, { title: '状态', @@ -183,7 +203,7 @@ export default function UserList() { /> - {/* 新增/编辑弹窗(完全匹配后端字段) */} + {/* 新增/编辑弹窗 */} - - + + - - + {/* 多角色选择 */} + + @@ -248,19 +278,6 @@ export default function UserList() { - - - - - - - - - - - - - diff --git a/src/router/index.tsx b/src/router/index.tsx index 11f5e46..0d3c02f 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -17,6 +17,8 @@ const MenuList = lazy(() => import('../pages/MenuList')); const Organization = lazy(() => import('../pages/Organization')); const StationManage = lazy(() => import('../pages/StationManage')); const SerialNumGenerate = lazy(() => import('../pages/SerialNumGenerate')); +const SystemSetting = lazy(() => import('../pages/SystemSetting')); + function PageFallback() { return ( @@ -78,6 +80,7 @@ const routes: RouteObject[] = [ { path: 'menuList', element: }, { path: 'organization', element: }, { path: 'station', element: }, + { path: 'system', element: }, { path: '*', element: (