组织架构优化
This commit is contained in:
@@ -36,6 +36,7 @@ const { Header, Sider, Content } = Layout;
|
||||
export default function AppLayout() {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [time, setTime] = useState(new Date().toLocaleString());
|
||||
const [menuList, setMenuList] = useState([]);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { userInfo } = useSelector((state: RootState) => state.user);
|
||||
@@ -43,7 +44,7 @@ export default function AppLayout() {
|
||||
const getMenu = () => {
|
||||
getMenuList().then(res => {
|
||||
if (res.code == 200) {
|
||||
message.success("获取菜单成功")
|
||||
setMenuList(res.data);
|
||||
} else {
|
||||
message.error("获取菜单失败:" + res.msg)
|
||||
}
|
||||
@@ -74,14 +75,28 @@ export default function AppLayout() {
|
||||
{ key: '/video', icon: <VideoCameraOutlined />, label: '在线视频', roles: ['admin', 'operator', 'viewer'] },
|
||||
{ key: '/alerts', icon: <BellOutlined />, label: '警报中心', roles: ['admin', 'operator'] },
|
||||
{ key: '/downloads', icon: <DownloadOutlined />, label: '视频下载', roles: ['admin', 'operator'] },
|
||||
{ key: '/users', icon: <TeamOutlined />, label: '用户管理', roles: ['admin'] },
|
||||
{ key: '/roles', icon: <UserOutlined />, label: '角色权限', roles: ['admin'] },
|
||||
{ key: '/serialNumGenerate', icon: <UserOutlined />, label: '序列号生成', roles: ['admin'] },
|
||||
{ key: '/users', icon: <TeamOutlined />, label: '用户管理', roles: ['admin'] },
|
||||
|
||||
{ key: '/roles', icon: <UserOutlined />, label: '角色管理', roles: ['admin'] },
|
||||
|
||||
{
|
||||
key: '/menuList',
|
||||
icon: <MenuOutlined />,
|
||||
label: '菜单管理',
|
||||
roles: ['admin']
|
||||
},
|
||||
{
|
||||
key: '/organization',
|
||||
icon: <MenuOutlined />,
|
||||
label: '组织管理',
|
||||
roles: ['admin']
|
||||
},
|
||||
{
|
||||
key: '/station',
|
||||
icon: <MenuOutlined />,
|
||||
label: '场站管理',
|
||||
roles: ['admin']
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -1,166 +1,160 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Table,
|
||||
Button,
|
||||
Modal,
|
||||
Form,
|
||||
Input,
|
||||
Select,
|
||||
TreeSelect,
|
||||
Space,
|
||||
Popconfirm,
|
||||
message,
|
||||
Card,
|
||||
Typography
|
||||
Table, Button, Typography, Space, Modal, Form, Tree, message,
|
||||
Input, Popconfirm, Select
|
||||
} from 'antd';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import type { MenuItem, RoleItem } from '@/types/menu';
|
||||
import {
|
||||
PlusOutlined, EditOutlined, DeleteOutlined, SettingOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Title } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
// 模拟系统角色
|
||||
const roleOptions: RoleItem[] = [
|
||||
{ label: '系统管理员', value: 'admin' },
|
||||
{ label: '操作员', value: 'operator' },
|
||||
{ label: '观察员', value: 'viewer' },
|
||||
];
|
||||
|
||||
// 模拟菜单数据
|
||||
const mockMenuData: MenuItem[] = [
|
||||
// 1. 完整菜单树
|
||||
const menuData = [
|
||||
{
|
||||
id: '1',
|
||||
name: '控制台',
|
||||
path: '/',
|
||||
parentId: null,
|
||||
sort: 1,
|
||||
roles: ['admin', 'operator', 'viewer'],
|
||||
children: [],
|
||||
id: 1,
|
||||
title: '控制台',
|
||||
path: '/dashboard',
|
||||
parentId: 0,
|
||||
children: [
|
||||
{ id: 11, title: '概览', path: '/dashboard/overview', parentId: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '设备管理',
|
||||
path: '/devices',
|
||||
parentId: null,
|
||||
sort: 2,
|
||||
roles: ['admin', 'operator'],
|
||||
children: [],
|
||||
id: 2,
|
||||
title: '设备管理',
|
||||
path: '/device',
|
||||
parentId: 0,
|
||||
children: [
|
||||
{ id: 21, title: '机器人管理', path: '/device/robot', parentId: 2 },
|
||||
{ id: 22, title: '视频监控', path: '/device/video', parentId: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '视频监控',
|
||||
path: '/video',
|
||||
parentId: '2',
|
||||
sort: 1,
|
||||
roles: ['admin', 'operator', 'viewer'],
|
||||
children: [],
|
||||
id: 3,
|
||||
title: '系统管理',
|
||||
path: '/system',
|
||||
parentId: 0,
|
||||
children: [
|
||||
{ id: 31, title: '用户管理', path: '/system/user', parentId: 3 },
|
||||
{ id: 32, title: '角色管理', path: '/system/role', parentId: 3 },
|
||||
{ id: 33, title: '菜单管理', path: '/system/menu', parentId: 3 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const MenuList: React.FC = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [menuList, setMenuList] = useState<MenuItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [title, setTitle] = useState('新增菜单');
|
||||
const [currentRecord, setCurrentRecord] = useState<MenuItem | null>(null);
|
||||
// 2. 角色列表
|
||||
const roleList = [
|
||||
{ name: '平台admin', key: 'admin' },
|
||||
{ name: '区域机构管理员', key: 'region_admin' },
|
||||
{ name: '单个场站管理员', key: 'site_admin' },
|
||||
{ name: '场站员工', key: 'site_staff' },
|
||||
];
|
||||
|
||||
// 加载菜单
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
setTimeout(() => {
|
||||
setMenuList(mockMenuData);
|
||||
setLoading(false);
|
||||
}, 300);
|
||||
}, []);
|
||||
|
||||
// 打开新增
|
||||
const handleAdd = () => {
|
||||
form.resetFields();
|
||||
setTitle('新增菜单');
|
||||
setCurrentRecord(null);
|
||||
setModalVisible(true);
|
||||
// 3. 扁平化菜单
|
||||
const flattenMenu = (menus) => {
|
||||
let arr = [];
|
||||
const loop = (list, parentPath = '') => {
|
||||
list.forEach(item => {
|
||||
const fullPath = parentPath ? `${parentPath}/${item.path}` : item.path;
|
||||
arr.push({ ...item, fullPath });
|
||||
if (item.children?.length) loop(item.children, item.path);
|
||||
});
|
||||
};
|
||||
loop(menus);
|
||||
return arr;
|
||||
};
|
||||
|
||||
// 存储角色权限
|
||||
const rolePermissionMap = {};
|
||||
|
||||
export default function Roles() {
|
||||
const [menus, setMenus] = useState(flattenMenu(menuData));
|
||||
const [menuTree, setMenuTree] = useState(menuData);
|
||||
|
||||
// 新增/编辑菜单
|
||||
const [menuModal, setMenuModal] = useState(false);
|
||||
const [menuForm] = Form.useForm();
|
||||
const [editMenu, setEditMenu] = useState(null);
|
||||
|
||||
// 权限配置
|
||||
const [permModal, setPermModal] = useState(false);
|
||||
const [selectedRole, setSelectedRole] = useState('');
|
||||
const [checkedKeys, setCheckedKeys] = useState([]);
|
||||
|
||||
// 打开编辑
|
||||
const handleEdit = (record: MenuItem) => {
|
||||
form.setFieldsValue(record);
|
||||
setTitle('编辑菜单');
|
||||
setCurrentRecord(record);
|
||||
setModalVisible(true);
|
||||
const handleEdit = (record) => {
|
||||
setEditMenu(record);
|
||||
menuForm.setFieldsValue(record);
|
||||
setMenuModal(true);
|
||||
};
|
||||
|
||||
// 删除
|
||||
const handleDelete = (id: string) => {
|
||||
setMenuList(prev => prev.filter(item => item.id !== id));
|
||||
message.success('删除成功');
|
||||
};
|
||||
|
||||
// 提交保存
|
||||
const handleSubmit = () => {
|
||||
form.validateFields().then(values => {
|
||||
const params: MenuItem = {
|
||||
...values,
|
||||
id: currentRecord?.id || Date.now().toString(),
|
||||
};
|
||||
|
||||
if (currentRecord) {
|
||||
// 保存菜单(新增+编辑)
|
||||
const handleSaveMenu = () => {
|
||||
menuForm.validateFields().then(values => {
|
||||
if (editMenu) {
|
||||
// 编辑
|
||||
setMenuList(prev => prev.map(m => m.id === currentRecord.id ? params : m));
|
||||
setMenus(menus.map(m => m.id === editMenu.id ? { ...m, ...values } : m));
|
||||
message.success('编辑成功');
|
||||
} else {
|
||||
// 新增
|
||||
setMenuList(prev => [...prev, params]);
|
||||
message.success('新增成功');
|
||||
const newMenu = {
|
||||
...values,
|
||||
id: Date.now(),
|
||||
parentId: 0,
|
||||
};
|
||||
setMenus([...menus, newMenu]);
|
||||
message.success('新增菜单成功');
|
||||
}
|
||||
setModalVisible(false);
|
||||
setMenuModal(false);
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列
|
||||
// 删除菜单
|
||||
const handleDelete = (id) => {
|
||||
setMenus(menus.filter(m => m.id !== id));
|
||||
message.success('删除成功');
|
||||
};
|
||||
|
||||
// 打开权限配置
|
||||
const handlePermOpen = () => {
|
||||
if (!selectedRole) {
|
||||
message.warning('请先选择角色');
|
||||
return;
|
||||
}
|
||||
// 回显已保存权限
|
||||
const saved = rolePermissionMap[selectedRole] || [];
|
||||
setCheckedKeys(saved);
|
||||
setPermModal(true);
|
||||
};
|
||||
|
||||
// 保存权限
|
||||
const handlePermSave = () => {
|
||||
rolePermissionMap[selectedRole] = checkedKeys;
|
||||
message.success(`【${selectedRole}】权限保存成功`);
|
||||
setPermModal(false);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '菜单名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '路由地址',
|
||||
dataIndex: 'path',
|
||||
key: 'path',
|
||||
},
|
||||
{
|
||||
title: '父级菜单',
|
||||
key: 'parentName',
|
||||
render: (_, record) => {
|
||||
if (!record.parentId) return '一级菜单';
|
||||
const p = menuList.find(m => m.id === record.parentId);
|
||||
return p?.name || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '关联角色',
|
||||
key: 'roles',
|
||||
render: (_, record) => (
|
||||
<div>
|
||||
{record.roles.map(r => {
|
||||
const ro = roleOptions.find(o => o.value === r);
|
||||
return <span key={r} style={{ marginRight: 8 }}>{ro?.label}</span>;
|
||||
})}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{ title: '菜单名称', dataIndex: 'title', key: 'title', width: 180 },
|
||||
{ title: '路由地址', dataIndex: 'fullPath', key: 'fullPath' },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 220,
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)}>编辑</Button>
|
||||
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)}>
|
||||
编辑
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="确定删除?"
|
||||
title="确定删除该菜单?"
|
||||
onConfirm={() => handleDelete(record.id)}
|
||||
>
|
||||
<Button type="link" danger icon={<DeleteOutlined />}>删除</Button>
|
||||
<Button type="link" danger icon={<DeleteOutlined />}>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
@@ -168,75 +162,87 @@ const MenuList: React.FC = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div className="flex justify-between mb-4">
|
||||
<Title level={5}>菜单管理</Title>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>新增菜单</Button>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<Title level={5} style={{ margin: 0 }}>菜单权限管理</Title>
|
||||
<Space>
|
||||
<Select
|
||||
placeholder="选择角色"
|
||||
style={{ width: 180 }}
|
||||
value={selectedRole}
|
||||
onChange={setSelectedRole}
|
||||
>
|
||||
{roleList.map(r => (
|
||||
<Option key={r.key} value={r.key}>{r.name}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SettingOutlined />}
|
||||
onClick={handlePermOpen}
|
||||
>
|
||||
配置权限
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setEditMenu(null);
|
||||
menuForm.resetFields();
|
||||
setMenuModal(true);
|
||||
}}
|
||||
>
|
||||
新增菜单
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
dataSource={menuList}
|
||||
dataSource={menus}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
bordered
|
||||
/>
|
||||
|
||||
{/* 菜单 新增/编辑 弹窗 */}
|
||||
<Modal
|
||||
title={title}
|
||||
open={modalVisible}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
onOk={handleSubmit}
|
||||
maskClosable={false}
|
||||
title={editMenu ? '编辑菜单' : '新增菜单'}
|
||||
open={menuModal}
|
||||
onCancel={() => setMenuModal(false)}
|
||||
onOk={handleSaveMenu}
|
||||
width={500}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="菜单名称"
|
||||
rules={[{ required: true, message: '请输入菜单名称' }]}
|
||||
>
|
||||
<Input placeholder="请输入" />
|
||||
<Form form={menuForm} layout="vertical">
|
||||
<Form.Item name="title" label="菜单名称" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入菜单名称" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="path"
|
||||
label="路由地址"
|
||||
rules={[{ required: true, message: '请输入路由' }]}
|
||||
>
|
||||
<Input placeholder="/devices" />
|
||||
<Form.Item name="path" label="路由地址" rules={[{ required: true }]}>
|
||||
<Input placeholder="/system/xxx" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="parentId" label="上级菜单">
|
||||
<TreeSelect
|
||||
placeholder="请选择"
|
||||
allowClear
|
||||
treeData={menuList.map(m => ({
|
||||
title: m.name,
|
||||
value: m.id,
|
||||
disabled: m.id === currentRecord?.id,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="roles"
|
||||
label="关联角色"
|
||||
rules={[{ required: true, message: '请选择至少一个角色' }]}
|
||||
>
|
||||
<Select mode="multiple" placeholder="请选择">
|
||||
{roleOptions.map(o => (
|
||||
<Option key={o.value} value={o.value}>{o.label}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="sort" label="排序号">
|
||||
<Input type="number" placeholder="数字越小越靠前" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuList;
|
||||
{/* 权限配置弹窗 */}
|
||||
<Modal
|
||||
title={`【${selectedRole}】菜单权限配置`}
|
||||
open={permModal}
|
||||
width={520}
|
||||
onCancel={() => setPermModal(false)}
|
||||
onOk={handlePermSave}
|
||||
>
|
||||
<Tree
|
||||
checkable
|
||||
treeData={menuTree}
|
||||
checkedKeys={checkedKeys}
|
||||
onCheck={setCheckedKeys}
|
||||
defaultExpandAll
|
||||
fieldNames={{ title: 'title', key: 'id', children: 'children' }}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
159
src/pages/Organization.tsx
Normal file
159
src/pages/Organization.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Card, Table, Button, Typography, Space, Modal, Form, Tree, message,
|
||||
Input, Popconfirm, Select, TreeSelect
|
||||
} from 'antd';
|
||||
import {
|
||||
PlusOutlined, EditOutlined, DeleteOutlined, BuildOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Title } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
// 模拟组织数据
|
||||
const orgData = [
|
||||
{
|
||||
id: 1,
|
||||
name: '华东区域总部',
|
||||
code: 'EAST_REGION',
|
||||
parentId: 0,
|
||||
status: '0',
|
||||
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '华北区域总部',
|
||||
code: 'NORTH_REGION',
|
||||
parentId: 0,
|
||||
status: '0',
|
||||
},
|
||||
];
|
||||
|
||||
// 转平数组用于表格
|
||||
const flatten = (list) => {
|
||||
let arr = [];
|
||||
const loop = (data) => {
|
||||
data.forEach(item => {
|
||||
arr.push(item);
|
||||
if (item.children) loop(item.children);
|
||||
});
|
||||
};
|
||||
loop(list);
|
||||
return arr;
|
||||
};
|
||||
|
||||
export default function Organization() {
|
||||
const [orgList, setOrgList] = useState(flatten(orgData));
|
||||
const [treeData] = useState(orgData);
|
||||
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [editRecord, setEditRecord] = useState(null);
|
||||
|
||||
// 打开新增
|
||||
const handleAdd = () => {
|
||||
form.resetFields();
|
||||
setEditRecord(null);
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
// 打开编辑
|
||||
const handleEdit = (record) => {
|
||||
form.setFieldsValue(record);
|
||||
setEditRecord(record);
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
// 删除
|
||||
const handleDelete = (id) => {
|
||||
setOrgList(orgList.filter(i => i.id !== id));
|
||||
message.success('删除成功');
|
||||
};
|
||||
|
||||
// 保存
|
||||
const handleSave = () => {
|
||||
form.validateFields().then(values => {
|
||||
if (editRecord) {
|
||||
setOrgList(orgList.map(i => i.id === editRecord.id ? { ...i, ...values } : i));
|
||||
message.success('编辑成功');
|
||||
} else {
|
||||
setOrgList([...orgList, { ...values, id: Date.now() }]);
|
||||
message.success('新增成功');
|
||||
}
|
||||
setModalVisible(false);
|
||||
});
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ title: '组织名称', dataIndex: 'name' },
|
||||
{ title: '组织编码', dataIndex: 'code' },
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
render: s => s === '0' ?
|
||||
<Button type="text" size="small">正常</Button> :
|
||||
<Button type="text" danger size="small">禁用</Button>
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
render: (_, r) => (
|
||||
<Space>
|
||||
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(r)}>编辑</Button>
|
||||
<Popconfirm title="确定删除?" onConfirm={() => handleDelete(r.id)}>
|
||||
<Button type="link" danger>删除</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<Title level={5}>组织管理</Title>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
|
||||
新增组织
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
dataSource={orgList}
|
||||
pagination={false}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
title={editRecord ? '编辑组织' : '新增组织'}
|
||||
open={modalVisible}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
onOk={handleSave}
|
||||
width={500}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="name" label="组织名称" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="code" label="组织编码" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="parentId" label="上级组织">
|
||||
<TreeSelect
|
||||
treeData={treeData}
|
||||
placeholder="选择上级"
|
||||
fieldNames={{ label: 'name', value: 'id' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="status" label="状态">
|
||||
<Select>
|
||||
<Option value="0">正常</Option>
|
||||
<Option value="1">禁用</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,103 +1,220 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Card,
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Typography,
|
||||
Tag,
|
||||
Space,
|
||||
Divider,
|
||||
List
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Card, Row, Col, Button, Typography, Space, Modal, Form, Tree, message,
|
||||
Input, Popconfirm, Divider
|
||||
} from 'antd';
|
||||
import {
|
||||
SafetyCertificateOutlined,
|
||||
LockOutlined,
|
||||
CheckCircleOutlined,
|
||||
PlusOutlined,
|
||||
TeamOutlined
|
||||
import {
|
||||
PlusOutlined, LockOutlined, SettingOutlined, EditOutlined, DeleteOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
|
||||
// 默认角色
|
||||
const defaultRoles = [
|
||||
{ name: '超级管理员', key: 'admin', desc: '系统全权限', level: 'Level 1' },
|
||||
{ name: '机构管理员', key: 'operator', desc: '设备/警报/视频管理', level: 'Level 2' },
|
||||
{ name: '场站管理员', key: 'site_admin', desc: '场站管理权限', level: 'Level 2' },
|
||||
{ name: '场站操作员', key: 'viewer', desc: '只读查看', level: 'Level 3' },
|
||||
];
|
||||
|
||||
// 模拟菜单树
|
||||
const defaultMenuTree = [
|
||||
{ title: '控制台', value: 'dashboard', key: '1' },
|
||||
{
|
||||
title: '设备管理', value: 'device', key: '2',
|
||||
children: [
|
||||
{ title: '机器人管理', value: 'robot', key: '2-1' },
|
||||
{ title: '视频监控', value: 'video', key: '2-2' },
|
||||
]
|
||||
},
|
||||
{ title: '告警管理', value: 'alarm', key: '3' },
|
||||
{ title: '用户管理', value: 'user', key: '4' },
|
||||
{ title: '角色管理', value: 'role', key: '5' },
|
||||
{ title: '菜单管理', value: 'menu', key: '6' },
|
||||
];
|
||||
|
||||
export default function Roles() {
|
||||
const roles = [
|
||||
{ name: '超级管理员', key: 'admin', desc: '系统全权限,支持资源分配与权限审计', level: 'Level 1' },
|
||||
{ name: '系统操作员', key: 'operator', desc: '设备策略下发,实时指令干预及警报处理', level: 'Level 2' },
|
||||
{ name: '访客观察员', key: 'viewer', desc: '只读权限,支持实时视频预览与报表查阅', level: 'Level 3' },
|
||||
];
|
||||
const [roles, setRoles] = useState(defaultRoles);
|
||||
const [menuTree, setMenuTree] = useState(defaultMenuTree);
|
||||
const [checkedKeys, setCheckedKeys] = useState([]);
|
||||
const [permVisible, setPermVisible] = useState(false);
|
||||
const [currentRole, setCurrentRole] = useState(null);
|
||||
|
||||
const permissions = [
|
||||
{ name: '设备控制权限', sectors: ['远程重启', '策略部署', '紧急停机'] },
|
||||
{ name: '视频管理权限', sectors: ['实时监控', '云台控制', '录像下载'] },
|
||||
{ name: '核心准入权限', sectors: ['审计日志', '成本报表', '用户配置'] },
|
||||
];
|
||||
// 新增/编辑角色弹窗
|
||||
const [roleModalVisible, setRoleModalVisible] = useState(false);
|
||||
const [roleForm] = Form.useForm();
|
||||
const [editRole, setEditRole] = useState(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Title level={5} style={{ margin: 0 }}>系统角色权限矩阵</Title>
|
||||
<Button type="primary" icon={<PlusOutlined />} style={{ borderRadius: 2 }}>
|
||||
新增角色组
|
||||
</Button>
|
||||
</div>
|
||||
// 打开权限配置
|
||||
const handleOpenPerm = async (role) => {
|
||||
setCurrentRole(role);
|
||||
setCheckedKeys([]);
|
||||
setPermVisible(true);
|
||||
};
|
||||
|
||||
<Row gutter={16}>
|
||||
{roles.map((role) => (
|
||||
<Col span={8} key={role.key}>
|
||||
<Card
|
||||
hoverable
|
||||
style={{ borderRadius: 2 }}
|
||||
actions={[
|
||||
<Button type="link" size="small" icon={<TeamOutlined />}>管理成员</Button>
|
||||
]}
|
||||
>
|
||||
<Card.Meta
|
||||
avatar={
|
||||
<div className="w-10 h-10 bg-blue-50 text-blue-500 rounded flex items-center justify-center border border-blue-100">
|
||||
<SafetyCertificateOutlined style={{ fontSize: 20 }} />
|
||||
</div>
|
||||
}
|
||||
title={<span style={{ fontSize: 14 }}>{role.name}</span>}
|
||||
description={
|
||||
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
||||
<Text type="secondary" style={{ fontSize: 10, fontFamily: 'monospace', textTransform: 'uppercase', fontStyle: 'italic' }}>{role.level}</Text>
|
||||
<Text type="secondary" style={{ fontSize: 12, lineHeight: '1.5', height: 40, display: 'block' }}>{role.desc}</Text>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
// 保存权限
|
||||
const handleSavePerm = () => {
|
||||
message.success(`【${currentRole.name}】菜单权限配置成功!`);
|
||||
setPermVisible(false);
|
||||
};
|
||||
|
||||
<Card
|
||||
size="small"
|
||||
title={<Space><LockOutlined style={{ color: 'rgba(0,0,0,0.45)' }} /><Text strong style={{ fontSize: 14 }}>功能模块权限分配 (细分原子操作)</Text></Space>}
|
||||
style={{ borderRadius: 2 }}
|
||||
>
|
||||
<Row gutter={24} style={{ padding: '8px 0' }}>
|
||||
{permissions.map((p) => (
|
||||
<Col span={8} key={p.name}>
|
||||
<div className="space-y-3">
|
||||
<Text strong style={{ fontSize: 11, borderLeft: '2px solid #1890ff', paddingLeft: 8, textTransform: 'uppercase', letterSpacing: 1 }}>{p.name}</Text>
|
||||
<List
|
||||
size="small"
|
||||
dataSource={p.sectors}
|
||||
renderItem={(item) => (
|
||||
<List.Item style={{ padding: '8px 4px', border: 'none' }}>
|
||||
<div className="flex items-center justify-between w-full p-2 bg-gray-50/50 rounded-sm hover:bg-gray-50 border border-transparent hover:border-gray-200 transition-all">
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>{item}</Text>
|
||||
<CheckCircleOutlined style={{ color: '#52c41a', fontSize: 12 }} />
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
// 打开新增角色
|
||||
const handleAddRole = () => {
|
||||
roleForm.resetFields();
|
||||
setCheckedKeys([]);
|
||||
setEditRole(null);
|
||||
setRoleModalVisible(true);
|
||||
};
|
||||
|
||||
// 打开编辑角色
|
||||
const handleEditRole = (record) => {
|
||||
setEditRole(record);
|
||||
roleForm.setFieldsValue(record);
|
||||
setCheckedKeys(record.menuKeys || []);
|
||||
setRoleModalVisible(true);
|
||||
};
|
||||
|
||||
// 删除角色
|
||||
const handleDeleteRole = (key) => {
|
||||
setRoles(roles.filter(item => item.key !== key));
|
||||
message.success('删除成功');
|
||||
};
|
||||
|
||||
// 提交保存角色
|
||||
const handleSaveRole = () => {
|
||||
roleForm.validateFields().then(values => {
|
||||
const roleData = {
|
||||
...values,
|
||||
menuKeys: checkedKeys, // 保存菜单权限
|
||||
level: editRole ? editRole.level : 'Level Custom'
|
||||
};
|
||||
|
||||
if (editRole) {
|
||||
// 编辑
|
||||
setRoles(roles.map(r => r.key === editRole.key ? { ...r, ...roleData } : r));
|
||||
message.success('编辑成功');
|
||||
} else {
|
||||
// 新增
|
||||
const newRole = {
|
||||
...roleData,
|
||||
key: values.key || Date.now().toString(),
|
||||
};
|
||||
setRoles([...roles, newRole]);
|
||||
message.success('新增角色成功');
|
||||
}
|
||||
setRoleModalVisible(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<Title level={5} style={{ margin: 0 }}>角色管理</Title>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleAddRole}>
|
||||
新增角色
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Row gutter={16}>
|
||||
{roles.map(role => (
|
||||
<Col span={8} key={role.key}>
|
||||
<Card
|
||||
hoverable
|
||||
actions={[
|
||||
<Button
|
||||
type="link"
|
||||
icon={<SettingOutlined />}
|
||||
onClick={() => handleOpenPerm(role)}
|
||||
>
|
||||
菜单权限
|
||||
</Button>,
|
||||
<Button
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleEditRole(role)}
|
||||
>
|
||||
编辑
|
||||
</Button>,
|
||||
<Popconfirm
|
||||
title="确定删除该角色?"
|
||||
onConfirm={() => handleDeleteRole(role.key)}
|
||||
>
|
||||
<Button type="link" danger icon={<DeleteOutlined />}>删除</Button>
|
||||
</Popconfirm>
|
||||
]}
|
||||
>
|
||||
<Card.Meta
|
||||
title={role.name}
|
||||
description={
|
||||
<Space direction="vertical" size={2}>
|
||||
<Text type="secondary" style={{ fontSize: 10 }}>{role.level}</Text>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>{role.desc}</Text>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
{/* ========== 菜单权限配置弹窗 ========== */}
|
||||
<Modal
|
||||
title={`【${currentRole?.name}】菜单权限`}
|
||||
open={permVisible}
|
||||
width={520}
|
||||
onCancel={() => setPermVisible(false)}
|
||||
onOk={handleSavePerm}
|
||||
>
|
||||
<Tree
|
||||
checkable
|
||||
treeData={menuTree}
|
||||
checkedKeys={checkedKeys}
|
||||
onCheck={setCheckedKeys}
|
||||
defaultExpandAll
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
{/* ========== 新增/编辑角色弹窗(带菜单选择) ========== */}
|
||||
<Modal
|
||||
title={editRole ? '编辑角色' : '新增角色'}
|
||||
open={roleModalVisible}
|
||||
onCancel={() => setRoleModalVisible(false)}
|
||||
onOk={handleSaveRole}
|
||||
maskClosable={false}
|
||||
width={550}
|
||||
>
|
||||
<Form form={roleForm} layout="vertical">
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="角色名称"
|
||||
rules={[{ required: true, message: '请输入角色名称' }]}
|
||||
>
|
||||
<Input placeholder="例如:运维管理员" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="key"
|
||||
label="角色标识"
|
||||
rules={[{ required: true, message: '请输入角色标识' }]}
|
||||
>
|
||||
<Input placeholder="例如:operation" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="desc" label="角色描述">
|
||||
<Input.TextArea rows={3} placeholder="描述该角色的权限范围" />
|
||||
</Form.Item>
|
||||
|
||||
{/* ========== 新增角色时直接选择菜单权限 ========== */}
|
||||
<Divider orientation="left">菜单权限配置</Divider>
|
||||
<Tree
|
||||
checkable
|
||||
treeData={menuTree}
|
||||
checkedKeys={checkedKeys}
|
||||
onCheck={setCheckedKeys}
|
||||
defaultExpandAll
|
||||
/>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
290
src/pages/StationManage.tsx
Normal file
290
src/pages/StationManage.tsx
Normal file
@@ -0,0 +1,290 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Card, Table, Button, Typography, Space, Modal, Form,
|
||||
Input, Popconfirm, Select, Switch, Divider, Upload, message,
|
||||
Row, Col, Tag
|
||||
} from 'antd';
|
||||
import {
|
||||
PlusOutlined, EditOutlined, DeleteOutlined,
|
||||
BgColorsOutlined, UserOutlined, SettingOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Title } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
// 模拟场站数据
|
||||
const stationData = [
|
||||
{
|
||||
id: 1,
|
||||
name: '上海浦东光伏场站',
|
||||
code: 'PD001',
|
||||
address: '上海市浦东新区光伏大道',
|
||||
status: 0,
|
||||
contact: '张三',
|
||||
phone: '13800138000',
|
||||
maintainCompany: '上海新能源运维公司',
|
||||
areaCount: 3,
|
||||
hasMap: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '苏州昆山储能电站',
|
||||
code: 'KS001',
|
||||
address: '苏州市昆山市能源路',
|
||||
status: 1,
|
||||
contact: '李四',
|
||||
phone: '13900139000',
|
||||
maintainCompany: '苏州智慧运维有限公司',
|
||||
areaCount: 2,
|
||||
hasMap: false,
|
||||
},
|
||||
];
|
||||
|
||||
// 场站分区模拟
|
||||
const areaData = [
|
||||
{ id: 1, stationId: 1, name: 'A区', type: '清扫区', status: 0 },
|
||||
{ id: 2, stationId: 1, name: 'B区', type: '巡检区', status: 0 },
|
||||
{ id: 3, stationId: 1, name: 'C区', type: '禁飞区', status: 1 },
|
||||
];
|
||||
|
||||
export default function StationManage() {
|
||||
const [stationList, setStationList] = useState(stationData);
|
||||
const [currentStation, setCurrentStation] = useState(null);
|
||||
|
||||
// 主弹窗
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [editRecord, setEditRecord] = useState(null);
|
||||
|
||||
// 分区弹窗
|
||||
const [areaVisible, setAreaVisible] = useState(false);
|
||||
// 边界绘制弹窗
|
||||
const [mapVisible, setMapVisible] = useState(false);
|
||||
|
||||
// 打开新增
|
||||
const handleAdd = () => {
|
||||
form.resetFields();
|
||||
setEditRecord(null);
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
// 打开编辑
|
||||
const handleEdit = (record) => {
|
||||
form.setFieldsValue(record);
|
||||
setEditRecord(record);
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
// 删除场站
|
||||
const handleDelete = (id) => {
|
||||
setStationList(stationList.filter(s => s.id !== id));
|
||||
message.success('删除成功');
|
||||
};
|
||||
|
||||
// 保存场站
|
||||
const handleSave = () => {
|
||||
form.validateFields().then(values => {
|
||||
const data = { ...values, id: editRecord?.id || Date.now() };
|
||||
if (editRecord) {
|
||||
setStationList(stationList.map(s => s.id === editRecord.id ? data : s));
|
||||
message.success('编辑成功');
|
||||
} else {
|
||||
setStationList([...stationList, data]);
|
||||
message.success('新增场站成功');
|
||||
}
|
||||
setModalVisible(false);
|
||||
});
|
||||
};
|
||||
|
||||
// 打开分区
|
||||
const handleOpenArea = (record) => {
|
||||
setCurrentStation(record);
|
||||
setAreaVisible(true);
|
||||
};
|
||||
|
||||
// 打开地图边界
|
||||
const handleOpenMap = (record) => {
|
||||
setCurrentStation(record);
|
||||
setMapVisible(true);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ title: '场站名称', dataIndex: 'name' },
|
||||
{ title: '场站编号', dataIndex: 'code' },
|
||||
{ title: '地址', dataIndex: 'address' },
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
render: (s) => s === 0 ?
|
||||
<Tag color="green">启用</Tag> :
|
||||
<Tag color="red">停用</Tag>
|
||||
},
|
||||
{ title: '负责人', dataIndex: 'contact' },
|
||||
{ title: '运维单位', dataIndex: 'maintainCompany' },
|
||||
{
|
||||
title: '分区数量',
|
||||
dataIndex: 'areaCount',
|
||||
},
|
||||
{
|
||||
title: '地图数据',
|
||||
render: (_, r) => r.hasMap ?
|
||||
<Tag color="blue">已维护</Tag> :
|
||||
<Tag>未维护</Tag>
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 400,
|
||||
render: (_, record) => (
|
||||
<Space wrap size="small">
|
||||
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)}>编辑</Button>
|
||||
|
||||
<Button type="link" icon={<BgColorsOutlined />} onClick={() => handleOpenArea(record)}>分区设置</Button>
|
||||
|
||||
<Button type="link" onClick={() => handleOpenMap(record)}>边界绘制</Button>
|
||||
|
||||
<Popconfirm title="确定删除?" onConfirm={() => handleDelete(record.id)}>
|
||||
<Button type="link" danger>删除</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<Title level={5}>场站管理</Title>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
|
||||
新增场站
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
dataSource={stationList}
|
||||
pagination={{ pageSize: 10 }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* ====================== 1. 新增/编辑场站 ====================== */}
|
||||
<Modal
|
||||
title={editRecord ? '编辑场站' : '新增场站'}
|
||||
open={modalVisible}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
onOk={handleSave}
|
||||
width={700}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item name="name" label="场站名称" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入场站名称" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="code" label="场站编号" rules={[{ required: true }]}>
|
||||
<Input placeholder="唯一编号" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item name="address" label="场站地址">
|
||||
<Input placeholder="详细地址" />
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">联系人与运维单位</Divider>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item name="contact" label="场站联系人">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="phone" label="联系电话">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item name="maintainCompany" label="运维单位">
|
||||
<Input placeholder="负责运维的公司" />
|
||||
</Form.Item>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Form.Item name="status" label="是否启用" valuePropName="checked">
|
||||
<Switch checkedChildren="启用" unCheckedChildren="停用" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* ====================== 2. 场站分区设置 ====================== */}
|
||||
<Modal
|
||||
title={`【${currentStation?.name}】分区设置`}
|
||||
open={areaVisible}
|
||||
width={600}
|
||||
onCancel={() => setAreaVisible(false)}
|
||||
footer={null}
|
||||
>
|
||||
<div className="mb-2">
|
||||
<Button size="small" type="primary">新增分区</Button>
|
||||
</div>
|
||||
<Table
|
||||
size="small"
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
columns={[
|
||||
{ title: '分区名称', dataIndex: 'name' },
|
||||
{ title: '类型', dataIndex: 'type' },
|
||||
{
|
||||
title: '状态', render: (_, r) => r.status === 0 ? '正常' : '禁用'
|
||||
},
|
||||
{
|
||||
title: '操作', render: () => (
|
||||
<Space size="small">
|
||||
<Button size="small" type="link">编辑</Button>
|
||||
<Button size="small" type="link" danger>删除</Button>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
]}
|
||||
dataSource={areaData.filter(a => a.stationId === currentStation?.id)}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
{/* ====================== 3. 场站边界绘制 & 地图维护 ====================== */}
|
||||
<Modal
|
||||
title={`【${currentStation?.name}】边界绘制 & 地图维护`}
|
||||
open={mapVisible}
|
||||
width={800}
|
||||
onCancel={() => setMapVisible(false)}
|
||||
footer={null}
|
||||
>
|
||||
<div style={{
|
||||
height: 400,
|
||||
background: '#f5f5f5',
|
||||
borderRadius: 8,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
gap: 16
|
||||
}}>
|
||||
{/*<MapOutlined style={{ fontSize: 48, color: '#1677ff' }} />*/}
|
||||
<div>地图组件 / 百度地图 / 高德地图 区域绘制</div>
|
||||
<div style={{ color: '#888' }}>支持:边界点绘制、电子围栏、导入KML、保存GeoJSON</div>
|
||||
|
||||
<Space>
|
||||
<Button type="primary">开始绘制</Button>
|
||||
<Button>清除边界</Button>
|
||||
<Button>保存地图数据</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,97 +1,288 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Table,
|
||||
Tag,
|
||||
Space,
|
||||
Button,
|
||||
Input,
|
||||
Typography,
|
||||
Card,
|
||||
Avatar
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Card, Table, Button, Typography, Space, Modal, Form,
|
||||
Input, Select, Popconfirm, message, Tag, TreeSelect, Divider
|
||||
} from 'antd';
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
UserOutlined
|
||||
import {
|
||||
PlusOutlined, EditOutlined, DeleteOutlined, UserOutlined,
|
||||
KeyOutlined, TeamOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { MANAGED_USERS } from '../constants';
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
const { Title } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
export default function Users() {
|
||||
const columns = [
|
||||
{
|
||||
title: '用户信息',
|
||||
key: 'userinfo',
|
||||
render: (record: any) => (
|
||||
<Space size="middle">
|
||||
<Avatar src={record.avatar} icon={<UserOutlined />} />
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text strong>{record.name}</Text>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>{record.id}</Text>
|
||||
</Space>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '系统权限',
|
||||
dataIndex: 'role',
|
||||
key: 'role',
|
||||
render: (role: string) => (
|
||||
<Tag color={role === 'admin' ? 'blue' : 'gray'} style={{ borderRadius: 2, fontWeight: 'bold', textTransform: 'uppercase' }}>
|
||||
{role}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '账户状态',
|
||||
key: 'status',
|
||||
render: () => <Tag color="success">ACTIVE</Tag>,
|
||||
},
|
||||
{
|
||||
title: '最近登录',
|
||||
key: 'lastLogin',
|
||||
render: () => <Text type="secondary" style={{ fontSize: 12, fontFamily: 'monospace' }}>2024-03-24 10:22:45</Text>,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'right' as const,
|
||||
render: () => (
|
||||
<Space>
|
||||
<Button icon={<EditOutlined />} size="small" type="link">编辑</Button>
|
||||
<Button icon={<DeleteOutlined />} size="small" type="link" danger>删除</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
// ---------------------- 1. 角色配置(和你的权限表对齐) ----------------------
|
||||
const roleOptions = [
|
||||
{
|
||||
label: '平台admin',
|
||||
value: 'admin',
|
||||
desc: '全局配置、机构/菜单/设备权限分配、核心参数配置、平台级异常审核'
|
||||
},
|
||||
{
|
||||
label: '区域机构管理员',
|
||||
value: 'region_admin',
|
||||
desc: '区域场站/账号管理、跨站机器人调度、区域报表统计'
|
||||
},
|
||||
{
|
||||
label: '单个场站管理员',
|
||||
value: 'site_admin',
|
||||
desc: '场站配置、设备/机器人管理、场站告警处置、运维记录审核'
|
||||
},
|
||||
{
|
||||
label: '场站员工',
|
||||
value: 'site_staff',
|
||||
desc: '机器人启停/参数调整、任务执行、巡检文件上传、故障处置'
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Title level={5} style={{ margin: 0 }}>用户管理权限控制</Title>
|
||||
<Space>
|
||||
<Input
|
||||
prefix={<SearchOutlined style={{ color: 'rgba(0,0,0,0.25)' }} />}
|
||||
placeholder="搜索用户名或工号..."
|
||||
style={{ width: 250, borderRadius: 2 }}
|
||||
/>
|
||||
<Button type="primary" icon={<PlusOutlined />} style={{ borderRadius: 2 }}>
|
||||
新增人员
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
// 模拟机构/场站数据(数据权限用)
|
||||
const orgTreeData = [
|
||||
{
|
||||
title: '华东区域',
|
||||
value: 'region_east',
|
||||
children: [
|
||||
{ title: '上海光伏场站', value: 'site_shanghai' },
|
||||
{ title: '苏州风电场站', value: 'site_suzhou' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '华北区域',
|
||||
value: 'region_north',
|
||||
children: [
|
||||
{ title: '张家口光伏场站', value: 'site_zhangjiakou' },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
<Card styles={{ body: { padding: 0 } }} className="overflow-hidden">
|
||||
<Table
|
||||
size="small"
|
||||
columns={columns}
|
||||
dataSource={MANAGED_USERS.map(u => ({ ...u, key: u.id }))}
|
||||
pagination={false}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
// 模拟用户数据(带角色+数据权限)
|
||||
const initUserList = [
|
||||
{
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
nickName: '平台超级管理员',
|
||||
role: 'admin',
|
||||
roleLabel: '平台admin',
|
||||
dataScope: ['region_east', 'region_north'],
|
||||
status: '0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'region_shanghai',
|
||||
nickName: '华东区域管理员',
|
||||
role: 'region_admin',
|
||||
roleLabel: '区域机构管理员',
|
||||
dataScope: ['region_east'],
|
||||
status: '0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
username: 'site_shanghai01',
|
||||
nickName: '上海场站管理员',
|
||||
role: 'site_admin',
|
||||
roleLabel: '单个场站管理员',
|
||||
dataScope: ['site_shanghai'],
|
||||
status: '0'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
username: 'staff_zhang',
|
||||
nickName: '上海场站运维员',
|
||||
role: 'site_staff',
|
||||
roleLabel: '场站员工',
|
||||
dataScope: ['site_shanghai'],
|
||||
status: '0'
|
||||
},
|
||||
];
|
||||
|
||||
export default function UserList() {
|
||||
const [userList, setUserList] = useState(initUserList);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 新增/编辑弹窗
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [currentUser, setCurrentUser] = useState(null);
|
||||
|
||||
// 打开新增
|
||||
const handleAdd = () => {
|
||||
form.resetFields();
|
||||
setCurrentUser(null);
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
// 打开编辑
|
||||
const handleEdit = (record) => {
|
||||
form.setFieldsValue(record);
|
||||
setCurrentUser(record);
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
// 删除用户
|
||||
const handleDelete = (id) => {
|
||||
setUserList(userList.filter(u => u.id !== id));
|
||||
message.success('删除成功');
|
||||
};
|
||||
|
||||
// 提交保存
|
||||
const handleSubmit = () => {
|
||||
form.validateFields().then(values => {
|
||||
// 角色标签回填,方便表格展示
|
||||
const roleInfo = roleOptions.find(r => r.value === values.role);
|
||||
const payload = {
|
||||
...values,
|
||||
roleLabel: roleInfo?.label
|
||||
};
|
||||
|
||||
if (currentUser) {
|
||||
// 编辑
|
||||
setUserList(userList.map(u => u.id === currentUser.id ? { ...u, ...payload } : u));
|
||||
message.success('编辑成功');
|
||||
} else {
|
||||
// 新增
|
||||
const newUser = { ...payload, id: Date.now() };
|
||||
setUserList([...userList, newUser]);
|
||||
message.success('新增用户成功');
|
||||
}
|
||||
setModalVisible(false);
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列(增强角色+数据权限展示)
|
||||
const columns = [
|
||||
{
|
||||
title: '用户信息',
|
||||
key: 'userInfo',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<UserOutlined style={{ fontSize: 16 }} />
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{record.nickName}</div>
|
||||
<div style={{ fontSize: 12, color: '#888' }}>账号:{record.username}</div>
|
||||
</div>
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'roleLabel',
|
||||
key: 'roleLabel',
|
||||
render: (text) => <Tag color="blue">{text}</Tag>
|
||||
},
|
||||
{
|
||||
title: '数据权限',
|
||||
dataIndex: 'dataScope',
|
||||
key: 'dataScope',
|
||||
render: (dataScope) => {
|
||||
if (!dataScope?.length) return '-';
|
||||
const count = dataScope.length;
|
||||
return <Tag color="cyan">已分配 {count} 个机构/场站</Tag>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (s) => s === '0' ?
|
||||
<Tag color="green">正常</Tag> :
|
||||
<Tag color="red">禁用</Tag>
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)}>编辑</Button>
|
||||
<Popconfirm title="确定删除该用户?" onConfirm={() => handleDelete(record.id)}>
|
||||
<Button type="link" danger icon={<DeleteOutlined />}>删除</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div className="flex justify-between mb-4">
|
||||
<Title level={5} style={{ margin: 0 }}>用户管理(角色+数据权限)</Title>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>新增用户</Button>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
dataSource={userList}
|
||||
pagination={{ pageSize: 10 }}
|
||||
/>
|
||||
|
||||
{/* 新增/编辑弹窗:角色+数据权限双配置 */}
|
||||
<Modal
|
||||
title={currentUser ? '编辑用户' : '新增用户'}
|
||||
open={modalVisible}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
onOk={handleSubmit}
|
||||
maskClosable={false}
|
||||
width={600}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="username"
|
||||
label="登录账号"
|
||||
rules={[{ required: true, message: '请输入账号' }]}
|
||||
>
|
||||
<Input placeholder="请输入登录账号" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="nickName"
|
||||
label="用户昵称"
|
||||
rules={[{ required: true, message: '请输入昵称' }]}
|
||||
>
|
||||
<Input placeholder="请输入用户昵称" />
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left" style={{ margin: '16px 0' }}>权限配置</Divider>
|
||||
|
||||
<Form.Item
|
||||
name="role"
|
||||
label="分配角色"
|
||||
rules={[{ required: true, message: '请选择角色' }]}
|
||||
extra="角色决定菜单和操作权限,数据权限决定可见的机构/场站"
|
||||
>
|
||||
<Select placeholder="选择用户角色">
|
||||
{roleOptions.map(item => (
|
||||
<Option key={item.value} value={item.value}>{item.label}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="dataScope"
|
||||
label="数据权限(可见机构/场站)"
|
||||
rules={[{ required: true, message: '请选择数据权限' }]}
|
||||
>
|
||||
<TreeSelect
|
||||
treeData={orgTreeData}
|
||||
placeholder="选择用户可见的机构/场站"
|
||||
multiple
|
||||
treeCheckable
|
||||
treeDefaultExpandAll
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="status"
|
||||
label="用户状态"
|
||||
rules={[{ required: true }]}
|
||||
initialValue="0"
|
||||
>
|
||||
<Select>
|
||||
<Option value="0">正常</Option>
|
||||
<Option value="1">禁用</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import Roles from '../pages/Roles';
|
||||
import Users from '../pages/Users';
|
||||
import SerialNumGenerate from '../pages/SerialNumGenerate';
|
||||
import MenuList from '../pages/MenuList';
|
||||
import Organization from '../pages/Organization';
|
||||
import StationManage from '../pages/StationManage';
|
||||
|
||||
export const router = createBrowserRouter([
|
||||
{
|
||||
@@ -61,6 +63,18 @@ export const router = createBrowserRouter([
|
||||
path: 'menuList',
|
||||
element: <MenuList />,
|
||||
},
|
||||
{
|
||||
path: 'menuList',
|
||||
element: <MenuList />,
|
||||
},
|
||||
{
|
||||
path: 'organization',
|
||||
element: <Organization />,
|
||||
},
|
||||
{
|
||||
path: 'station',
|
||||
element: <StationManage />,
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
element: <div className="p-10 text-center font-bold text-gray-400">模块开发中...</div>,
|
||||
|
||||
Reference in New Issue
Block a user