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 (
+
+ {/* 顶部概览卡片 */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 基础配置 */}
+
+
+
+
+ {/* 通知与告警策略 */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 用户与权限预览 */}
+
+ {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 (
+
+ {/* 固定在当前组件内部顶部,不随滚动 */}
+
+
+
+ {/* 内容区域可滚动 */}
+
+ {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 (
+
+
+ {/* 整体卡片 */}
+
+
+
+ {/* ========== 用户管理 ========== */}
+
+
+
+
} onClick={handleAddUser}>新增用户
+
+
+ (r.roles || []).map(i => (
+ {i.roleName || i.roleKey}
+ ))
+ },
+ {
+ title: '状态', render: (s) => s == '0' ?
+ 正常 : 停用
+ },
+ {
+ title: '操作', render: (_, r) => (
+
+ } onClick={() => handleEditUser(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"
+ />
+
+
+ {/* ========== 角色管理 ========== */}
+
+
+
+
} onClick={handleAddRole}>新增角色
+
+
+
+ {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: (