feat(前端):用户、终端组织树

master
shaot 2025-08-08 18:09:16 +08:00
parent b92552f653
commit c3d3ab36e3
17 changed files with 981 additions and 381 deletions

0
node_modules/.cache/logger/umi.log generated vendored 100644
View File

View File

@ -43,5 +43,11 @@ export default defineConfig({
}, },
], ],
npmClient: 'pnpm', npmClient: 'pnpm',
proxy: {
'/api/nex/v1/': {
target: 'http://10.100.51.85:8112',
// changeOrigin: true,
},
},
}); });

View File

@ -0,0 +1,120 @@
export default {
'POST /api/v1/terminal/query/devicelist': (req: any, res: any) => {
const { page_size, page_num } = req.body;
const data = [];
for (let i = 1; i <= page_size; i++) {
data.push({
id: i,
device_id: i,
device_name: `终端${(page_num - 1) * page_size + i}`,
device_group_id: 1,
device_group_name: '分组名称',
device_type: 1,
ip_addr: '10.10.10.10',
mac_addr: 'fd:12:12:12:12:12',
model: 'model',
description: '描述',
});
}
const result = {
error_code: '0000000000',
message: '操作成功',
data: {
paging: {
total: 520,
total_num: 520,
page_num: page_num,
page_size: page_size,
},
data: data,
},
};
setTimeout(() => {
res.send(result);
}, 500);
},
'POST /api/v1/tree/query': (req: any, res: any) => {
const data = [
{
name: '总分组',
id: 1,
children: [
{
name: '组别1',
id: 2,
},
{
name: '组别2',
id: 3,
children: [
{
name: '子分组1',
id: 4,
},
{
name: '子分组1',
id: 5,
},
],
},
{
name: '分组3',
id: 6,
},
],
},
];
const result = {
error_code: '0000000000',
message: '操作成功',
data: {
data: data,
},
};
setTimeout(() => {
res.send(result);
}, 500);
},
'POST /api/v1/user/query/list': (req: any, res: any) => {
const { page_size, page_num } = req.body;
const data = [];
for (let i = 1; i <= page_size; i++) {
data.push({
user_id: i,
user_group_id: i,
user_group_name: '分组',
user_name: `用户${(page_num - 1) * page_size + i}`,
password: '1111111',
birthday: '2022-01-01',
cell_phone: '12345678901',
email: '1111111111@emial.com',
gender: 1,
identity_no: '111111184736524352622',
priority: 1,
user_type: 1,
status: 1,
description: 'stringNumber',
});
}
const result = {
error_code: '0000000000',
message: '操作成功',
data: {
paging: {
total: 520,
total_num: 520,
page_num: page_num,
page_size: page_size,
},
data: data,
},
};
setTimeout(() => {
res.send(result);
}, 500);
},
};

View File

@ -0,0 +1,44 @@
export const ERROR_CODE = '200';
export const DEVICE_TYPE_MAP = {
1: 'VDI',
3: 'VOI',
} as const;
export const RESTORE_TYPE_MAP = {
1: '全盘还原',
2: '数据盘还原',
3: '定时还原',
0: '不还原',
} as const;
export const USER_TYPE_MAP = {
1: '域用户',
0: '本地用户',
} as const;
export const GENDER_MAP = {
1: '女',
2: '男',
} as const;
// priority
export const PRIORITY_MAP = {
1: '低',
2: '中',
3: '高',
} as const;
export const USER_TYPE_OPTIONS = [
{ value: 1, label: '域用户' },
{ value: 0, label: '本地用户' },
];
export const DEVICE_TYPE_OPTIONS = [
{ value: 1, label: 'VDI' },
{ value: 3, label: 'VOI' },
];
export const GENDER_OPTIONS = [
{ value: 1, label: '女' },
{ value: 2, label: '男' },
];

View File

@ -1,14 +1,61 @@
// // custom-tree.tsx
// import React from 'react';
// import { Tree } from 'antd';
// import type { DataNode, TreeProps } from 'antd/es/tree';
// import type { Key } from 'react';
// interface CustomTreeProps extends Omit<TreeProps, 'treeData' | 'onCheck'> {
// treeData: any[];
// titleField: string;
// keyField: string;
// childrenField?: string;
// onCheck?: (checkedKeys?: Key[], rows?: any[], info?: any) => void;
// }
// const CustomTree: React.FC<CustomTreeProps> = ({
// treeData,
// titleField,
// keyField,
// childrenField = 'children',
// ...restProps
// }) => {
// // Transform the tree data to match Ant Design's expected structure
// const transformTreeData = (data: any[]): DataNode[] => {
// return data.map(item => {
// const node: DataNode = {
// title: item[titleField],
// key: item[keyField],
// ...item
// };
// // Handle children if they exist
// if (item[childrenField] && Array.isArray(item[childrenField])) {
// node.children = transformTreeData(item[childrenField]);
// }
// return node;
// });
// };
// const transformedData = transformTreeData(treeData);
// return <Tree treeData={transformedData} {...restProps} />;
// };
// export default CustomTree;
// custom-tree.tsx // custom-tree.tsx
import React from 'react'; import React from 'react';
import { Tree } from 'antd'; import { Tree } from 'antd';
import type { DataNode, TreeProps } from 'antd/es/tree'; import type { DataNode, TreeProps } from 'antd/es/tree';
import type { Key } from 'react';
interface CustomTreeProps extends Omit<TreeProps, 'treeData'> { interface CustomTreeProps extends Omit<TreeProps, 'treeData' | 'onCheck'> {
// treeData: User.OrganizationNode[];
treeData: any[]; treeData: any[];
titleField: string; titleField: string;
keyField: string; keyField: string;
childrenField?: string; childrenField?: string;
onCheck?: (checkedKeys?: Key[], rows?: any[], info?: any) => void;
} }
const CustomTree: React.FC<CustomTreeProps> = ({ const CustomTree: React.FC<CustomTreeProps> = ({
@ -16,6 +63,7 @@ const CustomTree: React.FC<CustomTreeProps> = ({
titleField, titleField,
keyField, keyField,
childrenField = 'children', childrenField = 'children',
onCheck,
...restProps ...restProps
}) => { }) => {
// Transform the tree data to match Ant Design's expected structure // Transform the tree data to match Ant Design's expected structure
@ -38,7 +86,17 @@ const CustomTree: React.FC<CustomTreeProps> = ({
const transformedData = transformTreeData(treeData); const transformedData = transformTreeData(treeData);
return <Tree treeData={transformedData} {...restProps} />; const handleCheck: TreeProps['onCheck'] = (checked, info) => {
let checkedKeys: Key[] = [];
if (Array.isArray(checked)) {
checkedKeys = checked;
} else {
checkedKeys = checked.checked;
}
onCheck?.(checkedKeys, info.checkedNodes, info);
};
return <Tree treeData={transformedData} onCheck={handleCheck} {...restProps} />;
}; };
export default CustomTree; export default CustomTree;

View File

@ -1,6 +1,9 @@
/* eslint-disable @typescript-eslint/no-use-before-define */ /* eslint-disable @typescript-eslint/no-use-before-define */
import { DEVICE_TYPE_MAP, ERROR_CODE } from '@/constants/constants';
import CustomTree from '@/pages/components/customTree'; import CustomTree from '@/pages/components/customTree';
import { deleteUser } from '@/services/userList'; import CreatGroup from '@/pages/userList/mod/group';
import { deleteDevice, getTerminalList } from '@/services/terminal';
import { deleteUserGroup, getGroupTree } from '@/services/userList';
import { import {
DeleteOutlined, DeleteOutlined,
DownOutlined, DownOutlined,
@ -19,20 +22,18 @@ import {
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import styles from './index.less'; import styles from './index.less';
import BindImage from './mod/bindImage';
import BindUserModal from './mod/bindUser'; import BindUserModal from './mod/bindUser';
import EditModal from './mod/eidtDevice'; import EditModal from './mod/eidtDevice';
import CreatGroup from './mod/group';
import BindImage from './mod/bindImage';
const UserListPage: React.FC = () => { const UserListPage: React.FC = () => {
const [orgTreeData, setOrgTreeData] = useState<User.OrganizationNode[]>([]); const [orgTreeData, setOrgTreeData] = useState<User.OrganizationNode[]>([]);
const [selectedOrg, setSelectedOrg] = useState<number>(); const [selectedOrg, setSelectedOrg] = useState<number>();
// 用户列表
const [dataSource, setDataSource] = useState<Termial.TermialItem[]>([]); const [dataSource, setDataSource] = useState<Termial.TermialItem[]>([]);
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [searchText, setSearchText] = useState<string>(''); const [searchText, setSearchText] = useState<string>('');
const [selectedRowKeys, setSelectedRowKeys] = useState<[]>([]); const [selectedRowKeys, setSelectedRowKeys] = useState<[]>([]);
const [orgSearchText, setOrgSearchText] = useState<string>('');
// 分页参数 // 分页参数
const [currentPage, setCurrentPage] = useState<number>(1); const [currentPage, setCurrentPage] = useState<number>(1);
@ -54,83 +55,108 @@ const UserListPage: React.FC = () => {
// 获取用户分组组织树 // 获取用户分组组织树
useEffect(() => { useEffect(() => {
getGroupList(); // getGroupList();
}, []); }, []);
const filterTreeData = (
data: User.OrganizationNode[],
searchTerm: string,
): User.OrganizationNode[] => {
if (!searchTerm) return data;
return data.reduce((acc: User.OrganizationNode[], node) => {
const isMatch = node.name
?.toLowerCase()
.includes(searchTerm.toLowerCase());
if (isMatch) {
// If current node matches, include it
acc.push({ ...node });
} else if (node.children && node.children.length > 0) {
// If current node doesn't match, check children
const filteredChildren = filterTreeData(node.children, searchTerm);
if (filteredChildren.length > 0) {
// If any children match, include current node with filtered children
acc.push({
...node,
children: filteredChildren,
});
}
}
return acc;
}, []);
};
const filteredOrgTreeData = filterTreeData(orgTreeData, orgSearchText);
// 获取用户列表数据 // 获取用户列表数据
useEffect(() => { useEffect(() => {
getDataSource(); // if (selectedOrg) {
}, [selectedOrg, currentPage, pageSize]); // getDataSource();
// }
}, [searchText, selectedOrg, currentPage, pageSize]);
const getGroupList = () => { const getGroupList = async () => {
const mockOrgData: User.OrganizationNode[] = [ const params = {
{ type: 2,
name: 'Headquarters',
id: 1,
children: [
{
name: 'HR Department',
id: 2,
},
{
name: 'IT Department',
id: 3,
children: [
{
name: 'Frontend Team',
id: 4,
},
{
name: 'Backend Team',
id: 5,
},
],
},
{
name: 'Finance Department',
id: 6,
},
],
},
];
setSelectedOrg(mockOrgData[0].id as number);
setOrgTreeData(mockOrgData);
};
const getDataSource = () => {
setLoading(true);
setTimeout(() => {
const mockUsers: Termial.TermialItem[] = Array.from(
{ length: pageSize },
(_, index) => ({
id: index + 1,
device_id: 'string',
device_name: 'string',
device_group_id: 1,
device_group_name: 'string',
device_type: 1,
}),
);
setDataSource(mockUsers);
setTotal(100);
setLoading(false);
}, 300);
};
const onDelete = (device?: Termial.TermialItem) => {
const { id } = device || {};
const payload = {
ids: id ? [id] : selectedRowKeys,
}; };
deleteUser(payload as any).then((res) => { try {
console.log('res=====', res); const result = await getGroupTree(params);
const { success } = res || {}; if (result.error_code === ERROR_CODE) {
if (success) { setOrgTreeData(result.data.data || []);
message.success('删除成功'); if (result.data.data.length > 0) {
setSelectedOrg(result.data.data[0].id as number);
}
} else {
message.error(result.message || '获取终端分组失败');
}
} catch (err) {
message.error('获取终端分组失败');
setLoading(false);
}
};
const getDataSource = async () => {
const params: any = {
page_size: pageSize,
page_num: currentPage,
};
if (searchText) {
params.keywords = searchText;
}
setLoading(true);
try {
const result = await getTerminalList(params);
if (result.error_code === ERROR_CODE) {
setDataSource(result.data.data || []);
setTotal(result.data.paging.total || 0);
setLoading(false);
} else {
message.error(result.message || '获取终端列表失败');
setLoading(false);
}
} catch (err) {
message.error('获取终端列表失败');
setLoading(false);
}
};
const onDelete = async (device?: Termial.TermialItem) => {
try {
const { id } = device || {};
const payload = {
ids: id ? [id] : selectedRowKeys,
};
const res = await deleteDevice(payload);
const { error_code } = res || {};
if (error_code === ERROR_CODE) {
message.success('终端删除成功');
setSelectedRowKeys([]); setSelectedRowKeys([]);
getDataSource(); getDataSource();
} }
}); } catch (error) {
message.error('终端删除失败');
}
}; };
const handleEditUserInfo = (device: Termial.TermialItem) => { const handleEditUserInfo = (device: Termial.TermialItem) => {
@ -142,42 +168,62 @@ const UserListPage: React.FC = () => {
const columns: ColumnsType<Termial.TermialItem> = [ const columns: ColumnsType<Termial.TermialItem> = [
{ {
title: '显示名称', title: '序号',
dataIndex: 'username', dataIndex: 'order',
key: 'username', key: 'order',
width: 80,
align: 'center',
render: (_: any, record: any, index: number) => <span>{index + 1}</span>,
}, },
{ {
title: '终端名称', title: '终端名称',
dataIndex: 'device_name', dataIndex: 'device_name',
key: 'device_name', key: 'device_name',
width: 220,
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
}, },
{ {
title: '终端分组', title: '终端分组',
dataIndex: 'device_group_name', dataIndex: 'device_group_name',
key: 'device_group_name', key: 'device_group_name',
width: 220,
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
}, },
{ {
title: 'IP地址', title: 'IP地址',
dataIndex: 'ip_addr', dataIndex: 'ip_addr',
key: 'ip_addr', key: 'ip_addr',
width: 150,
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
}, },
{ {
title: '终端标识', title: '终端标识',
dataIndex: 'mac_addr', dataIndex: 'mac_addr',
key: 'mac_addr', key: 'mac_addr',
ellipsis: true,
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
}, },
{ {
title: '终端类型', title: '终端类型',
dataIndex: 'device_type', dataIndex: 'device_type',
key: 'device_type', key: 'device_type',
render: (text) => { width: 150,
return ( align: 'center',
<div> render: (text: number) => {
<Tooltip placement="top" title={text}> const key = text as keyof typeof DEVICE_TYPE_MAP;
{text} return <Tooltip>{DEVICE_TYPE_MAP[key] || '--'}</Tooltip>;
</Tooltip>
</div>
);
}, },
}, },
{ {
@ -262,8 +308,23 @@ const UserListPage: React.FC = () => {
console.log('selectedRowKeys changed: ', newSelectedRowKeys); console.log('selectedRowKeys changed: ', newSelectedRowKeys);
setSelectedRowKeys(newSelectedRowKeys as any); setSelectedRowKeys(newSelectedRowKeys as any);
}; };
const onDeleteGroup = async () => {
const onDeleteGroup = () => {}; if (selectedOrg) {
try {
const params = {
id: selectedOrg,
};
const res = await deleteUserGroup(params);
const { error_code } = res || {};
if (error_code === ERROR_CODE) {
message.success('分组删除成功');
getGroupList();
}
} catch (error) {
message.error('分组删除失败');
}
}
};
return ( return (
<div className={styles.user_content}> <div className={styles.user_content}>
@ -293,18 +354,19 @@ const UserListPage: React.FC = () => {
</Popconfirm> </Popconfirm>
</div> </div>
<Input.Search <Input.Search
placeholder="请输入" placeholder="请输入分组名"
style={{ marginBottom: 6 }} style={{ marginBottom: 6 }}
onSearch={(value) => console.log('Search org:', value)} onSearch={(value) => setOrgSearchText(value)}
onChange={(e) => setOrgSearchText(e.target.value)}
/> />
</div> </div>
<div className={styles.tree_box}> <div className={styles.tree_box}>
<CustomTree <CustomTree
treeData={orgTreeData} treeData={filteredOrgTreeData}
titleField="name" titleField="name"
keyField="id" keyField="id"
childrenField="children" childrenField="children"
defaultExpandAll defaultExpandAll={true}
onSelect={onOrgSelect} onSelect={onOrgSelect}
selectedKeys={selectedOrg ? [selectedOrg] : []} selectedKeys={selectedOrg ? [selectedOrg] : []}
icon={<TeamOutlined style={{ fontSize: '15px' }} />} icon={<TeamOutlined style={{ fontSize: '15px' }} />}
@ -344,7 +406,7 @@ const UserListPage: React.FC = () => {
<div> <div>
<div> <div>
<Input.Search <Input.Search
placeholder="Search users" placeholder="终端名称"
value={searchText} value={searchText}
onChange={(e) => setSearchText(e.target.value)} onChange={(e) => setSearchText(e.target.value)}
style={{ width: 300 }} style={{ width: 300 }}
@ -374,6 +436,7 @@ const UserListPage: React.FC = () => {
onShowSizeChange: handlePageSizeChange, onShowSizeChange: handlePageSizeChange,
showSizeChanger: true, showSizeChanger: true,
showQuickJumper: true, showQuickJumper: true,
pageSizeOptions: ['10', '20', '50', '100'],
showTotal: (total) => { showTotal: (total) => {
return `${total}条数据`; return `${total}条数据`;
}, },
@ -385,6 +448,8 @@ const UserListPage: React.FC = () => {
</div> </div>
<CreatGroup <CreatGroup
visible={visible} visible={visible}
type={2}
title="新增终端分组"
onCancel={() => { onCancel={() => {
setVisible(false); setVisible(false);
}} }}

View File

@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-use-before-define */ /* eslint-disable @typescript-eslint/no-use-before-define */
import type { PopoverProps } from 'antd'; import type { PopoverProps } from 'antd';
import { Alert, Button, Popconfirm, Popover, Space } from 'antd'; import { Button, Popconfirm, Popover, Space } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import CustomTable from '../selectedTable/table'; import CustomTable from '../selectedTable/table';
import SelectConponents from './table'; import SelectConponents from './table';
@ -12,7 +13,7 @@ interface SelectedTableProps {
onOk?: (values: any) => void; onOk?: (values: any) => void;
orgTreeData?: User.OrganizationNode[]; orgTreeData?: User.OrganizationNode[];
value?: any; value?: any;
onChange?: (values: any) => void; onChange: (values: any) => void;
} }
const SelectedTable: React.FC<SelectedTableProps> = (props) => { const SelectedTable: React.FC<SelectedTableProps> = (props) => {
@ -20,7 +21,7 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
placement = 'bottomLeft', placement = 'bottomLeft',
orgTreeData, orgTreeData,
value: formValue, value: formValue,
onChange: onBindUserChange, onChange: onBindImageChange,
} = props; } = props;
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
const [tableData, setTableData] = useState<any[]>([]); const [tableData, setTableData] = useState<any[]>([]);
@ -32,16 +33,15 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
const [bindTableKeys, setBindTableKeys] = useState<any[]>([]); const [bindTableKeys, setBindTableKeys] = useState<any[]>([]);
useEffect(() => { useEffect(() => {
console.log('formValue==========', formValue); const list: any = [];
const userList: any = []; const idList: any = [];
const userId: any = [];
if (formValue && formValue.length > 0) { if (formValue && formValue.length > 0) {
formValue.forEach((item: any) => { formValue.forEach((item: any) => {
userList.push(item); list.push(item);
userId.push(item.id); idList.push(item.id);
}); });
setSelectedRowKeys(userId); setSelectedRowKeys(idList);
setSelectedRows(userList); setSelectedRows(list);
} }
}, [formValue]); }, [formValue]);
@ -56,30 +56,26 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
// 用户 // 用户
(selectedRows || []).forEach((item) => { (selectedRows || []).forEach((item) => {
list.push({ list.push({
id: item.id, id: item.image_id,
name: item.name, name: item.image_name,
type: 1,
}); });
}); });
setTableData(list); setTableData(list);
onBindImageChange(list);
onHnadleCancel(); onHnadleCancel();
}; };
const onUserTableSelect = (keys: any[], row: any[]) => { const onUserTableSelect = (keys: any[], row: any[]) => {
console.log('selectedRowKeys changed: ', keys);
console.log('selectedRowKeys rows: ', row);
setSelectedRowKeys(keys as any); setSelectedRowKeys(keys as any);
setSelectedRows(row); setSelectedRows(row);
}; };
const onSelectChange = (keys: any[]) => { const onSelectChange = (keys: any[]) => {
console.log('selectedRowKeys changed: ', keys);
setBindTableKeys(keys as any); setBindTableKeys(keys as any);
}; };
const handleDelete = (record?: any) => { const handleDelete = (record?: any) => {
console.log('record=====handleDelete', record);
if (record) { if (record) {
// 单个删除 // 单个删除
const { id } = record || {}; const { id } = record || {};
@ -98,12 +94,6 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
const accessPopover = ( const accessPopover = (
<div style={{ width: '600px' }}> <div style={{ width: '600px' }}>
{/* <Alert
message="可切换选择多个不同类型进行绑定"
type="info"
showIcon
closeIcon
/> */}
<SelectConponents <SelectConponents
treeData={orgTreeData} treeData={orgTreeData}
onUserTableSelect={onUserTableSelect} onUserTableSelect={onUserTableSelect}
@ -130,6 +120,33 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
</div> </div>
); );
const getColumns = (): ColumnsType<any> => {
const columns: ColumnsType<any> = [
{
title: '序号',
dataIndex: 'order',
key: 'order',
width: 80,
render: (_, record, index) => <span>{index + 1}</span>,
},
{
title: '镜像名称',
dataIndex: 'image_name',
key: 'image_name',
},
{
title: '操作',
key: 'action',
render: (_, record) => (
<Button type="link" danger onClick={() => handleDelete(record)}>
</Button>
),
},
];
return columns;
};
return ( return (
<> <>
<Space style={{ marginBottom: '10px' }}> <Space style={{ marginBottom: '10px' }}>
@ -166,6 +183,7 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
<CustomTable <CustomTable
dataSource={tableData} dataSource={tableData}
onDelete={handleDelete} onDelete={handleDelete}
columns={getColumns()}
scrollY={400} scrollY={400}
rowKey="id" rowKey="id"
pagination={false} pagination={false}

View File

@ -1,4 +1,8 @@
import { Input, Table } from 'antd'; /* eslint-disable @typescript-eslint/no-use-before-define */
import { ERROR_CODE,IMAGES_TYPE_MAP } from '@/constants/images.constants';
import { getImagesList } from '@/services/images';
import { Input, Table, message,Tooltip } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import styles from './index.less'; import styles from './index.less';
@ -8,42 +12,69 @@ interface TableProps {
onUserTableSelect?: (keys: any[], row: any[]) => void; onUserTableSelect?: (keys: any[], row: any[]) => void;
} }
interface DataType {
key: React.Key;
name: string;
age: number;
address: string;
}
const TablePage: React.FC<TableProps> = ({ const TablePage: React.FC<TableProps> = ({
onUserTableSelect, onUserTableSelect,
selectedRowKeys, selectedRowKeys,
}) => { }) => {
const [loading, setLoading] = useState(false);
const [data, setData] = useState<any[]>([]); const [data, setData] = useState<any[]>([]);
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState<any>();
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(20); const [pageSize, setPageSize] = useState(20);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
// Mock data generation
useEffect(() => { useEffect(() => {
const mockData: DataType[] = []; loadImages();
for (let i = 0; i < 100; i++) { }, [searchText, currentPage, pageSize]);
mockData.push({
id: i + 99,
name: `User ${i}`,
});
}
setData(mockData);
setTotal(mockData.length);
}, []);
const columns: TableProps<DataType>['columns'] = [ const loadImages = async () => {
const params: any = {
page_size: pageSize,
page_num: currentPage,
};
if (searchText) {
params.keywords = searchText;
}
setLoading(true);
try {
const imagesRes = await getImagesList(params);
if (imagesRes.error_code === ERROR_CODE) {
setData(imagesRes.data.data || []);
setTotal(imagesRes.data.paging.total || 0);
setLoading(false);
} else {
message.error(imagesRes.message || '获取镜像列表失败');
setLoading(false);
}
} catch (err) {
message.error('获取镜像列表失败');
setLoading(false);
}
};
const columns: ColumnsType<any> = [
{ {
title: 'Name', title: '序号',
dataIndex: 'name', dataIndex: 'order',
sorter: (a, b) => a.name.localeCompare(b.name), key: 'order',
width: 80,
render: (_: any, record: any, index: number) => <span>{index + 1}</span>,
}, },
{
title: '镜像名称',
dataIndex: 'image_name',
render: (text: any) => <span>{text || ''}</span>,
},
{
title: '桌面类型',
dataIndex: 'image_type',
key: 'image_type',
width: 200,
render: (text: number) => {
const key = text as keyof typeof IMAGES_TYPE_MAP;
return <Tooltip>{IMAGES_TYPE_MAP[key] || '--'}</Tooltip>;
},
},
]; ];
const handleSearch = (value: string) => { const handleSearch = (value: string) => {
@ -74,10 +105,10 @@ const TablePage: React.FC<TableProps> = ({
</div> </div>
<Table <Table
// rowSelection={rowSelection}
columns={columns} columns={columns}
dataSource={data} dataSource={data}
rowKey="id" rowKey="id"
loading={loading}
rowSelection={{ rowSelection={{
selectedRowKeys, selectedRowKeys,
preserveSelectedRowKeys: true, preserveSelectedRowKeys: true,
@ -92,6 +123,7 @@ const TablePage: React.FC<TableProps> = ({
onShowSizeChange: handlePageSizeChange, onShowSizeChange: handlePageSizeChange,
showSizeChanger: false, showSizeChanger: false,
showQuickJumper: false, showQuickJumper: false,
pageSizeOptions: ['10', '20', '50', '100'],
}} }}
scroll={{ x: 'max-content', y: 300 }} scroll={{ x: 'max-content', y: 300 }}
/> />

View File

@ -1,17 +1,11 @@
// index.tsx // index.tsx
import { import { DEVICE_TYPE_OPTIONS } from '@/constants/constants';
Button, import { ERROR_CODE } from '@/constants/images.constants';
Form, import { saveDevice } from '@/services/terminal';
Input, import { Button, Form, Input, message, Modal, Select, TreeSelect } from 'antd';
message,
Modal,
Radio,
TreeSelect,
} from 'antd';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
interface UserEditModalProps { interface UserEditModalProps {
// visible: boolean;
orgTreeData: User.OrganizationNode[]; orgTreeData: User.OrganizationNode[];
onCancel: () => void; onCancel: () => void;
onOk: (values: any) => void; onOk: (values: any) => void;
@ -39,9 +33,15 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
const handleOk = async () => { const handleOk = async () => {
try { try {
const values = await form.validateFields(); const values = await form.validateFields();
onOk(values); const params = { ...values, user_id };
const res = await saveDevice(params);
const { error_code } = res || {};
if (error_code === ERROR_CODE) {
message.success('保存成功');
}
} catch (error) { } catch (error) {
message.error('请检查表单字段'); message.error('保存失败');
} }
}; };
@ -50,7 +50,6 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
types: { types: {
email: '${label} is not a valid email!', email: '${label} is not a valid email!',
number: '${label} is not a valid number!', number: '${label} is not a valid number!',
cell_phone: '${label} is not a valid cell phone number!',
}, },
number: { number: {
range: '${label} must be between ${min} and ${max}', range: '${label} must be between ${min} and ${max}',
@ -95,8 +94,8 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
validateMessages={validateMessages} validateMessages={validateMessages}
> >
<Form.Item <Form.Item
name="loginName" name="device_name"
label="显示名称" label="终端名称"
rules={[{ required: true, message: '请输入显示名称' }]} rules={[{ required: true, message: '请输入显示名称' }]}
> >
<Input /> <Input />
@ -111,7 +110,7 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="user_group_id" name="device_group_id"
label="终端分组" label="终端分组"
rules={[{ required: true, message: '请选择终端分组' }]} rules={[{ required: true, message: '请选择终端分组' }]}
> >
@ -124,8 +123,15 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
fieldNames={{ label: 'name', value: 'id', children: 'children' }} fieldNames={{ label: 'name', value: 'id', children: 'children' }}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="device_typ"
label="终端类型"
rules={[{ required: false, message: '请输入终端型号' }]}
>
<Select options={DEVICE_TYPE_OPTIONS} />
</Form.Item>
{/* <Form.Item
name="status" name="status"
label="认证方式" label="认证方式"
rules={[{ required: false, message: '请选择认证方式' }]} rules={[{ required: false, message: '请选择认证方式' }]}
@ -134,7 +140,7 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
<Radio value={1}></Radio> <Radio value={1}></Radio>
<Radio value={2}></Radio> <Radio value={2}></Radio>
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item> */}
<Form.Item <Form.Item
name="ip_addr" name="ip_addr"
@ -157,13 +163,7 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
> >
<Input /> <Input />
</Form.Item> */} </Form.Item> */}
<Form.Item
name="model"
label="终端型号"
rules={[{ required: false, message: '请输入终端型号' }]}
>
<Input />
</Form.Item>
<Form.Item <Form.Item
name="description" name="description"
label="描述" label="描述"

View File

@ -1,36 +1,28 @@
import { Button, Form, Input, Modal, TreeSelect } from 'antd'; import { ERROR_CODE } from '@/constants/constants';
import React, { useEffect } from 'react'; import { addUserGroup } from '@/services/userList';
import { Button, Form, Input, Modal, TreeSelect, message } from 'antd';
import React, { useEffect, useState } from 'react';
interface CreatGroupProps { interface CreatGroupProps {
visible: boolean; visible: boolean;
selectedOrg?: number; selectedOrg?: number;
onCancel: () => void; onCancel: () => void;
onOk: (values: any) => void; onOk: (values?: any) => void;
orgTreeData: User.OrganizationNode[]; orgTreeData: User.OrganizationNode[];
} }
const CreatGroup: React.FC<CreatGroupProps> = (props) => { const CreatGroup: React.FC<CreatGroupProps> = (props) => {
const { visible, onCancel, onOk, orgTreeData, selectedOrg } = props; const { visible, onCancel, onOk, orgTreeData, selectedOrg } = props;
const [loading, setLoading] = useState<boolean>(false);
const [form] = Form.useForm(); const [form] = Form.useForm();
useEffect(() => { useEffect(() => {
const initialValues = { parent_device_group_id: [selectedOrg] }; if (selectedOrg) {
form.setFieldsValue(initialValues); const initialValues = { parent_device_group_id: [selectedOrg] };
form.setFieldsValue(initialValues);
}
}, [visible, form, selectedOrg]); }, [visible, form, selectedOrg]);
// Flatten tree data for parent group selection
// const flattenTreeData = (data: OrganizationNode[], result: { value: string; label: string }[] = []) => {
// data.forEach(item => {
// result.push({ value: item.id as string, label: item.name as string });
// if (item.children) {
// flattenTreeData(item.children, result);
// }
// });
// return result;
// };
// const parentGroupOptions = flattenTreeData(orgTreeData);
const handleOk = () => { const handleOk = () => {
form.submit(); form.submit();
}; };
@ -40,14 +32,34 @@ const CreatGroup: React.FC<CreatGroupProps> = (props) => {
onCancel(); onCancel();
}; };
const handleFinish = (values: any) => { const handleFinish = async () => {
onOk(values); const values: any = await form.validateFields();
form.resetFields(); setLoading(true);
try {
console.log('values=====', values);
const { name, parent_id } = values || {};
const params: any = { name, type: 2 };
if (parent_id) {
params.parent_id = parent_id;
}
const res = await addUserGroup(params);
setLoading(false);
const { code } = res || {};
if (code === ERROR_CODE) {
message.success('添加成功');
form.resetFields();
onCancel();
onOk();
}
} catch (error) {
setLoading(false);
message.error('创建用户分组失败');
}
}; };
return ( return (
<Modal <Modal
title="添加用户分组" title="添加终端分组"
open={visible} open={visible}
onOk={handleOk} onOk={handleOk}
onCancel={handleCancel} onCancel={handleCancel}
@ -55,12 +67,14 @@ const CreatGroup: React.FC<CreatGroupProps> = (props) => {
cancelText="取消" cancelText="取消"
centered={true} centered={true}
destroyOnHidden={true} destroyOnHidden={true}
width={600}
footer={ footer={
<div style={{ display: 'flex', justifyContent: 'right' }}> <div style={{ display: 'flex', justifyContent: 'right' }}>
<Button <Button
type="primary" type="primary"
onClick={handleOk} onClick={handleOk}
style={{ marginRight: '20px' }} style={{ marginRight: '20px' }}
loading={loading}
> >
</Button> </Button>
@ -80,17 +94,17 @@ const CreatGroup: React.FC<CreatGroupProps> = (props) => {
style={{ paddingTop: '20px', paddingBottom: '20px' }} style={{ paddingTop: '20px', paddingBottom: '20px' }}
> >
<Form.Item <Form.Item
name="device_group_name:" name="name:"
label="分组名" label="分组名"
rules={[{ required: true, message: '请输入分组名!' }]} rules={[{ required: true, message: '请输入分组名!' }]}
> >
<Input placeholder="请输入分组名" /> <Input placeholder="请输入分组名" />
</Form.Item> </Form.Item>
<Form.Item name="parent_device_group_id" label="父分组名"> <Form.Item name="parent_id" label="父分组名">
<TreeSelect <TreeSelect
showSearch showSearch
allowClear={false} allowClear={true}
treeDefaultExpandAll treeDefaultExpandAll
placeholder="请选择用户分组" placeholder="请选择用户分组"
treeData={orgTreeData} treeData={orgTreeData}

View File

@ -3,6 +3,7 @@ import CustomTree from '@/pages/components/customTree';
import { TeamOutlined } from '@ant-design/icons'; import { TeamOutlined } from '@ant-design/icons';
import type { PopoverProps } from 'antd'; import type { PopoverProps } from 'antd';
import { Alert, Button, Popconfirm, Popover, Space, Tabs } from 'antd'; import { Alert, Button, Popconfirm, Popover, Space, Tabs } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import SelectConponents from '../selectConponents'; import SelectConponents from '../selectConponents';
import CustomTable from './table'; import CustomTable from './table';
@ -129,6 +130,41 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
} }
}; };
const getColumns = (): ColumnsType<any> => {
const columns: ColumnsType<any> = [
{
title: '序号',
dataIndex: 'order',
key: 'order',
width: 80,
render: (_, record, index) => <span>{index + 1}</span>,
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
render: (text) => (
<span>{text == 1 ? '用户' : text == 2 ? '用户组' : ''}</span>
),
},
{
title: '操作',
key: 'action',
render: (_, record) => (
<Button type="link" danger onClick={() => handleDelete(record)}>
</Button>
),
}
];
return columns;
};
const accessPopover = ( const accessPopover = (
<div style={{ width: '600px' }}> <div style={{ width: '600px' }}>
<Alert <Alert
@ -220,6 +256,7 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
scrollY={400} scrollY={400}
rowKey="id" rowKey="id"
pagination={false} pagination={false}
columns={getColumns()}
rowSelection={{ rowSelection={{
bindTableKeys, bindTableKeys,
onChange: onSelectChange, onChange: onSelectChange,

View File

@ -11,6 +11,7 @@ interface DeletableTableProps {
rowKey?: string; rowKey?: string;
rowSelection?: any; rowSelection?: any;
pagination?:any; pagination?:any;
columns: ColumnsType<any>;
} }
const DeletableTable: React.FC<DeletableTableProps> = (props) => { const DeletableTable: React.FC<DeletableTableProps> = (props) => {
@ -23,52 +24,52 @@ const DeletableTable: React.FC<DeletableTableProps> = (props) => {
...restProps ...restProps
} = props; } = props;
console.log("datasource=====",dataSource); console.log("datasource=====",dataSource);
const getColumns = (): ColumnsType<any> => { // const getColumns = (): ColumnsType<any> => {
const columns: ColumnsType<any> = [ // const columns: ColumnsType<any> = [
{ // {
title: '名称', // title: '名称',
dataIndex: 'name', // dataIndex: 'name',
key: 'name', // key: 'name',
}, // },
{ // {
title: '类型', // title: '类型',
dataIndex: 'type', // dataIndex: 'type',
key: 'type', // key: 'type',
render: (text) => ( // render: (text) => (
<span>{text == 1 ? '用户' : text == 2 ? '用户组' : ''}</span> // <span>{text == 1 ? '用户' : text == 2 ? '用户组' : ''}</span>
), // ),
}, // },
]; // ];
if (isSerial) { // if (isSerial) {
columns.unshift({ // columns.unshift({
title: '序号', // title: '序号',
dataIndex: 'order', // dataIndex: 'order',
key: 'order', // key: 'order',
width: 80, // width: 80,
render: (_, record, index) => <span>{index + 1}</span>, // render: (_, record, index) => <span>{index + 1}</span>,
}); // });
} // }
if (isAction) { // if (isAction) {
columns.push({ // columns.push({
title: '操作', // title: '操作',
key: 'action', // key: 'action',
render: (_, record) => ( // render: (_, record) => (
<Button type="link" danger onClick={() => onDelete(record)}> // <Button type="link" danger onClick={() => onDelete(record)}>
// 删除
</Button> // </Button>
), // ),
}); // });
} // }
return columns; // return columns;
}; // };
return ( return (
<div> <div>
<Table <Table
columns={getColumns()} // columns={getColumns()}
dataSource={dataSource} dataSource={dataSource}
scroll={{ y: scrollY }} scroll={{ y: scrollY }}
{...restProps} {...restProps}

View File

@ -1,8 +1,24 @@
/* eslint-disable @typescript-eslint/no-use-before-define */ /* eslint-disable @typescript-eslint/no-use-before-define */
import {
ERROR_CODE,
GENDER_MAP,
PRIORITY_MAP,
USER_TYPE_MAP,
} from '@/constants/constants';
import CustomTree from '@/pages/components/customTree'; import CustomTree from '@/pages/components/customTree';
import { deleteUser } from '@/services/userList'; import {
import { DeleteOutlined, PlusOutlined, TeamOutlined } from '@ant-design/icons'; deleteUser,
import { Button, Input, Popconfirm, Table, message } from 'antd'; deleteUserGroup,
getGroupTree,
getUserList,
} from '@/services/userList';
import {
DeleteOutlined,
PlusOutlined,
RedoOutlined,
TeamOutlined,
} from '@ant-design/icons';
import { Button, Input, message, Popconfirm, Spin, Table, Tooltip } from 'antd';
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import styles from './index.less'; import styles from './index.less';
@ -12,18 +28,20 @@ import PasswordResetModal from './mod/passwordEdit';
const UserListPage: React.FC = () => { const UserListPage: React.FC = () => {
const [orgTreeData, setOrgTreeData] = useState<User.OrganizationNode[]>([]); const [orgTreeData, setOrgTreeData] = useState<User.OrganizationNode[]>([]);
const [selectedOrg, setSelectedOrg] = useState<number>(); const [selectedOrg, setSelectedOrg] = useState<any>();
// 用户列表 // 用户列表
const [users, setUsers] = useState<User.UserItem[]>([]); const [dataSource, setDataSource] = useState<User.UserItem[]>([]);
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [spinning, setSpinning] = useState<boolean>(false);
const [searchText, setSearchText] = useState<string>(''); const [searchText, setSearchText] = useState<string>('');
const [selectedRowKeys, setSelectedRowKeys] = useState<[]>([]); const [selectedRowKeys, setSelectedRowKeys] = useState<[]>([]);
const [orgSearchText, setOrgSearchText] = useState<string>('');
// 分页参数 // 分页参数
const [currentPage, setCurrentPage] = useState<number>(1); const [currentPage, setCurrentPage] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10); const [pageSize, setPageSize] = useState<number>(10);
const [totalUsers, setTotalUsers] = useState<number>(0); const [total, setTotal] = useState<number>(0);
// 编辑用户 // 编辑用户
const [currentUserInfo, setCurrentUserInfo] = useState<any>({ const [currentUserInfo, setCurrentUserInfo] = useState<any>({
visible: false, visible: false,
@ -43,60 +61,96 @@ const UserListPage: React.FC = () => {
// 获取用户列表数据 // 获取用户列表数据
useEffect(() => { useEffect(() => {
getUserListInit(); getDataSource();
}, [selectedOrg, currentPage, pageSize]); }, [searchText,selectedOrg, currentPage, pageSize]);
const getUserGroupList = () => { const getUserGroupList = async () => {
const mockOrgData: User.OrganizationNode[] = [ const params = {
{ type: 1,
name: 'Headquarters', };
id: 1, setSpinning(true);
children: [ try {
{ const result = await getGroupTree(params);
name: 'HR Department', console.log('result=====', result);
id: 2, const { code, data = [] } = result || {};
}, setSpinning(false);
{ if (code === ERROR_CODE) {
name: 'IT Department', if (data.length > 0) {
id: 3, const { children = [] } = data[0] || {};
children: [ setOrgTreeData(children);
{ // if (children.length > 0) {
name: 'Frontend Team', // setSelectedOrg(children[0].id);
id: 4, // }
}, }
{ } else {
name: 'Backend Team', message.error(result.message || '获取用户分组失败');
id: 5, }
}, } catch (err) {
], message.error('获取用户分组失败');
}, setSpinning(false);
{ }
name: 'Finance Department',
id: 6,
},
],
},
];
setSelectedOrg(mockOrgData[0].id as number);
setOrgTreeData(mockOrgData);
}; };
const getUserListInit = () => { const filterTreeData = (
setLoading(true); data: User.OrganizationNode[],
setTimeout(() => { searchTerm: string,
const startIndex = (currentPage - 1) * pageSize; ): User.OrganizationNode[] => {
const mockUsers: User.UserItem[] = Array.from( if (!searchTerm) return data;
{ length: pageSize },
(_, index) => ({
user_id: startIndex + index + 1,
user_name: 'string',
}),
);
setUsers(mockUsers); return data.reduce((acc: User.OrganizationNode[], node) => {
setTotalUsers(100); const isMatch = node.name
?.toLowerCase()
.includes(searchTerm.toLowerCase());
if (isMatch) {
// If current node matches, include it
acc.push({ ...node });
} else if (node.children && node.children.length > 0) {
// If current node doesn't match, check children
const filteredChildren = filterTreeData(node.children, searchTerm);
if (filteredChildren.length > 0) {
// If any children match, include current node with filtered children
acc.push({
...node,
children: filteredChildren,
});
}
}
return acc;
}, []);
};
const filteredOrgTreeData = filterTreeData(orgTreeData, orgSearchText);
const getDataSource = async () => {
const params: any = {
page_size: pageSize,
page_num: currentPage,
};
if (selectedOrg) {
params.user_group_id = selectedOrg;
}
if (searchText) {
params.user_name = searchText;
}
setLoading(true);
try {
const result = await getUserList(params);
console.log('res======', result);
const { code, data = {} } = result || {};
const { data: list, total = 0 } = data || {};
if (code === ERROR_CODE) {
setDataSource(list || []);
setTotal(total);
setLoading(false);
} else {
message.error(result.message || '获取终端列表失败');
setLoading(false);
}
} catch (err) {
message.error('获取终端列表失败');
setLoading(false); setLoading(false);
}, 300); }
}; };
const onDelete = (user?: User.UserItem) => { const onDelete = (user?: User.UserItem) => {
@ -110,7 +164,7 @@ const UserListPage: React.FC = () => {
if (success) { if (success) {
message.success('删除成功'); message.success('删除成功');
setSelectedRowKeys([]); setSelectedRowKeys([]);
getUserListInit(); getDataSource();
} }
}); });
}; };
@ -137,34 +191,105 @@ const UserListPage: React.FC = () => {
const columns: ColumnsType<User.UserItem> = [ const columns: ColumnsType<User.UserItem> = [
{ {
title: '登录名', title: '序号',
dataIndex: 'user_id', dataIndex: 'order',
key: 'user_id', key: 'order',
width: 80,
align: 'center',
render: (_: any, record: any, index: number) => <span>{index + 1}</span>,
}, },
{ {
title: '用户姓名', title: '用户名',
dataIndex: 'user_name', dataIndex: 'user_name',
key: 'user_name', key: 'user_name',
width: 150,
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
}, },
{ {
title: '用户分组', title: '用户分组',
dataIndex: 'userGroup', dataIndex: 'user_group_name',
key: 'userGroup', key: 'user_group_name',
}, width: 150,
{ align: 'center',
title: '性别', render: (text) => {
dataIndex: 'sex', return <Tooltip>{text || '--'}</Tooltip>;
key: 'sex', },
}, },
{ {
title: '状态', title: '状态',
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
width: 150,
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
}, },
{ {
title: '用户类别', title: '用户类别',
dataIndex: 'userType', dataIndex: 'user_type',
key: 'userType', key: 'user_type',
width: 150,
align: 'center',
render: (text: number) => {
const key = text as keyof typeof USER_TYPE_MAP;
return <Tooltip>{USER_TYPE_MAP[key] || '--'}</Tooltip>;
},
},
{
title: '优先级',
dataIndex: 'priority',
key: 'priority',
width: 150,
align: 'center',
render: (text: number) => {
const key = text as keyof typeof PRIORITY_MAP;
return <Tooltip>{PRIORITY_MAP[key] || '--'}</Tooltip>;
},
},
{
title: '性别',
dataIndex: 'gender',
key: 'gender',
width: 150,
align: 'center',
render: (text: number) => {
const key = text as keyof typeof GENDER_MAP;
return <Tooltip>{GENDER_MAP[key] || '--'}</Tooltip>;
},
},
{
title: '电话',
dataIndex: 'cell_phone',
key: 'cell_phone',
width: 150,
align: 'center',
render: (text:any) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
},
{
title: '出生日期',
dataIndex: 'birthday',
key: 'birthday',
width: 150,
align: 'center',
render: (text:any) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
},
{
title: '身份证号',
dataIndex: 'identity_no',
key: 'identity_no',
width: 150,
align: 'center',
render: (text:any) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
}, },
{ {
title: '操作', title: '操作',
@ -197,7 +322,7 @@ const UserListPage: React.FC = () => {
const onOrgSelect = (selectedKeys: React.Key[]) => { const onOrgSelect = (selectedKeys: React.Key[]) => {
if (selectedKeys.length > 0) { if (selectedKeys.length > 0) {
setSelectedOrg(selectedKeys[0] as number); setSelectedOrg(selectedKeys[0]);
setCurrentPage(1); setCurrentPage(1);
} }
}; };
@ -217,18 +342,47 @@ const UserListPage: React.FC = () => {
setSelectedRowKeys(newSelectedRowKeys as any); setSelectedRowKeys(newSelectedRowKeys as any);
}; };
const onDeleteGroup = () => {}; const onSaveGroup = () => {
getUserGroupList();
};
const onDeleteGroup = async () => {
if (selectedOrg) {
try {
const params = {
id: selectedOrg,
};
const res = await deleteUserGroup(params);
const { code } = res || {};
if (code === ERROR_CODE) {
message.success('分组删除成功');
setSelectedOrg(null);
getUserGroupList();
}
} catch (error) {
message.error('分组删除失败');
}
}
};
return ( return (
<div className={styles.user_content}> <div className={styles.user_content}>
<div className={styles.left_content}> <div className={styles.left_content}>
<div className={styles.search}> <div className={styles.search}>
<div style={{ paddingBottom: '5px' }}> <div style={{ paddingBottom: '5px' }}>
<Button
type="text"
style={{ marginRight: '8px', fontSize: '16px' }}
icon={<RedoOutlined />}
onClick={() => getUserGroupList()}
title="刷新"
/>
<Button <Button
type="text" type="text"
style={{ marginRight: '8px', fontSize: '16px' }} style={{ marginRight: '8px', fontSize: '16px' }}
icon={<PlusOutlined />} icon={<PlusOutlined />}
onClick={() => setVisible(true)} onClick={() => setVisible(true)}
title="新增"
/> />
<Popconfirm <Popconfirm
title="" title=""
@ -243,26 +397,31 @@ const UserListPage: React.FC = () => {
type="text" type="text"
style={{ fontSize: '16px' }} style={{ fontSize: '16px' }}
icon={<DeleteOutlined />} icon={<DeleteOutlined />}
title="删除"
disabled={!selectedOrg}
/> />
</Popconfirm> </Popconfirm>
</div> </div>
<Input.Search <Input.Search
placeholder="请输入名称" placeholder="请输入名称"
style={{ marginBottom: 6 }} style={{ marginBottom: 6 }}
onSearch={(value) => console.log('Search org:', value)} onSearch={(value) => setOrgSearchText(value)}
onChange={(e) => setOrgSearchText(e.target.value)}
/> />
</div> </div>
<div className={styles.tree_box}> <div className={styles.tree_box}>
<CustomTree <Spin spinning={spinning} delay={100}>
treeData={orgTreeData} <CustomTree
titleField="name" treeData={filteredOrgTreeData}
keyField="id" titleField="name"
childrenField="children" keyField="id"
defaultExpandAll childrenField="children"
onSelect={onOrgSelect} defaultExpandAll
selectedKeys={selectedOrg ? [selectedOrg] : []} onSelect={onOrgSelect}
icon={<TeamOutlined style={{ fontSize: '15px' }} />} selectedKeys={selectedOrg ? [selectedOrg] : []}
/> icon={<TeamOutlined style={{ fontSize: '15px' }} />}
/>
</Spin>
</div> </div>
</div> </div>
<div className={styles.right_content}> <div className={styles.right_content}>
@ -297,14 +456,14 @@ const UserListPage: React.FC = () => {
</Button> </Button>
</Popconfirm> </Popconfirm>
<Button style={{ marginRight: '8px' }} onClick={getUserListInit}> <Button style={{ marginRight: '8px' }} onClick={getDataSource}>
</Button> </Button>
</div> </div>
<div> <div>
<div> <div>
<Input.Search <Input.Search
placeholder="Search users" placeholder="用户名"
value={searchText} value={searchText}
onChange={(e) => setSearchText(e.target.value)} onChange={(e) => setSearchText(e.target.value)}
style={{ width: 300 }} style={{ width: 300 }}
@ -319,17 +478,18 @@ const UserListPage: React.FC = () => {
<div className={styles.teble_box}> <div className={styles.teble_box}>
<Table <Table
columns={columns} columns={columns}
dataSource={users} dataSource={dataSource}
loading={loading} loading={loading}
rowKey="id" rowKey="id"
pagination={{ pagination={{
current: currentPage, current: currentPage,
pageSize: pageSize, pageSize: pageSize,
total: totalUsers, total: total,
onChange: handlePageChange, onChange: handlePageChange,
onShowSizeChange: handlePageSizeChange, onShowSizeChange: handlePageSizeChange,
showSizeChanger: true, showSizeChanger: true,
showQuickJumper: true, showQuickJumper: true,
pageSizeOptions: ['10', '20', '50', '100'],
showTotal: (total) => { showTotal: (total) => {
return `${total}条数据`; return `${total}条数据`;
}, },
@ -345,11 +505,13 @@ const UserListPage: React.FC = () => {
</div> </div>
<CreatGroup <CreatGroup
visible={visible} visible={visible}
type={1}
title="新增用户分组"
selectedOrg={selectedOrg} selectedOrg={selectedOrg}
onCancel={() => { onCancel={() => {
setVisible(false); setVisible(false);
}} }}
onOk={() => {}} onOk={onSaveGroup}
orgTreeData={orgTreeData} orgTreeData={orgTreeData}
/> />
<UserEditModal <UserEditModal

View File

@ -1,4 +1,6 @@
// index.tsx // index.tsx
import { GENDER_OPTIONS, USER_TYPE_OPTIONS } from '@/constants/constants';
import { idIDIntl } from '@ant-design/pro-components';
import { import {
Button, Button,
Form, Form,
@ -10,7 +12,6 @@ import {
TreeSelect, TreeSelect,
} from 'antd'; } from 'antd';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
const { Option } = Select;
interface UserEditModalProps { interface UserEditModalProps {
// visible: boolean; // visible: boolean;
@ -30,13 +31,13 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
currentUserInfo, currentUserInfo,
}) => { }) => {
const { recordData, visible, selectedOrg } = currentUserInfo || {}; const { recordData, visible, selectedOrg } = currentUserInfo || {};
const { user_id } = recordData || {}; const { id } = recordData || {};
const [form] = Form.useForm(); const [form] = Form.useForm();
useEffect(() => { useEffect(() => {
const initialValues = { user_group_id: [selectedOrg], status: 1 }; const initialValues = { user_group_id: [selectedOrg], status: 1 };
form.setFieldsValue(initialValues); form.setFieldsValue(initialValues);
}, [visible, form,recordData, selectedOrg]); }, [visible, form, recordData, selectedOrg]);
const handleOk = async () => { const handleOk = async () => {
try { try {
@ -61,7 +62,7 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
return ( return (
<Modal <Modal
title={user_id ? '编辑用户信息' : '新增用户'} title={id ? '编辑用户信息' : '新增用户'}
open={visible} open={visible}
onCancel={onCancel} onCancel={onCancel}
onOk={handleOk} onOk={handleOk}
@ -142,22 +143,24 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="userType" name="user_type"
label="用户类别" label="用户类别"
rules={[{ required: true, message: '请选择用户类别' }]} rules={[{ required: false, message: '请选择用户类别' }]}
> >
<Select placeholder="请选择用户类别"> <Select placeholder="请选择用户类别" options={USER_TYPE_OPTIONS} />
<Option value="internal"></Option>
<Option value="external"></Option>
<Option value="partner"></Option>
<Option value="temporary"></Option>
</Select>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="idCard" name="gender"
label="性别"
rules={[{ required: false, message: '请选择性别' }]}
>
<Select placeholder="请选择性别" options={GENDER_OPTIONS} />
</Form.Item>
<Form.Item
name="identity_no"
label="身份证号" label="身份证号"
rules={[ rules={[
{ required: true, message: '请输入身份证号' }, { required: false, message: '请输入身份证号' },
{ {
pattern: pattern:
/^[1-9]\d{5}(18|19|20|21|22|23|24|25|26|27|28|29|30|31)\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20)$/, /^[1-9]\d{5}(18|19|20|21|22|23|24|25|26|27|28|29|30|31)\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20)$/,
@ -168,18 +171,6 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
<Input placeholder="请输入身份证号" /> <Input placeholder="请输入身份证号" />
</Form.Item> </Form.Item>
<Form.Item
name="gender"
label="性别"
rules={[{ required: false, message: '请选择性别' }]}
>
<Select placeholder="请选择性别">
<Option value="male"></Option>
<Option value="female"></Option>
<Option value="other"></Option>
<Option value="prefer-not-to-say"></Option>
</Select>
</Form.Item>
{/* 电话号码 */} {/* 电话号码 */}
<Form.Item <Form.Item
name="cell_phone" name="cell_phone"

View File

@ -1,37 +1,39 @@
// d:\VDI\vdi\web-fe\src\pages\userList\mod\group\index.tsx // d:\VDI\vdi\web-fe\src\pages\userList\mod\group\index.tsx
import { Button, Form, Input, Modal, TreeSelect } from 'antd'; import { ERROR_CODE } from '@/constants/constants';
import React, { useEffect } from 'react'; import { addUserGroup } from '@/services/userList';
import { Button, Form, Input, Modal, TreeSelect, message } from 'antd';
import React, { useEffect, useState } from 'react';
interface CreatGroupProps { interface CreatGroupProps {
title: string;
type:number;
visible: boolean; visible: boolean;
selectedOrg?: number; selectedOrg?: number;
onCancel: () => void; onCancel: () => void;
onOk: (values: any) => void; onOk: (values?: any) => void;
orgTreeData: User.OrganizationNode[]; orgTreeData: User.OrganizationNode[];
} }
const CreatGroup: React.FC<CreatGroupProps> = (props) => { const CreatGroup: React.FC<CreatGroupProps> = (props) => {
const { visible, onCancel, onOk, orgTreeData, selectedOrg } = props; const {
title = '新增分组',
visible,
onCancel,
onOk,
orgTreeData,
selectedOrg,
type
} = props;
const [loading, setLoading] = useState<boolean>(false);
const [form] = Form.useForm(); const [form] = Form.useForm();
useEffect(() => { useEffect(() => {
const initialValues = { parent_id: [selectedOrg] }; if (selectedOrg) {
form.setFieldsValue(initialValues); const initialValues = { parent_id: [selectedOrg] };
form.setFieldsValue(initialValues);
}
}, [visible, form, selectedOrg]); }, [visible, form, selectedOrg]);
// Flatten tree data for parent group selection
// const flattenTreeData = (data: OrganizationNode[], result: { value: string; label: string }[] = []) => {
// data.forEach(item => {
// result.push({ value: item.id as string, label: item.name as string });
// if (item.children) {
// flattenTreeData(item.children, result);
// }
// });
// return result;
// };
// const parentGroupOptions = flattenTreeData(orgTreeData);
const handleOk = () => { const handleOk = () => {
form.submit(); form.submit();
}; };
@ -41,14 +43,34 @@ const CreatGroup: React.FC<CreatGroupProps> = (props) => {
onCancel(); onCancel();
}; };
const handleFinish = (values: any) => { const handleFinish = async () => {
onOk(values); const values: any = await form.validateFields();
form.resetFields(); setLoading(true);
try {
console.log('values=====', values);
const { name, parent_id } = values || {};
const params: any = { name, type: type };
if (parent_id) {
params.parent_id = parent_id;
}
const res = await addUserGroup(params);
setLoading(false);
const { code } = res || {};
if (code === ERROR_CODE) {
message.success('添加成功');
form.resetFields();
onCancel();
onOk();
}
} catch (error) {
setLoading(false);
message.error('创建用户分组失败');
}
}; };
return ( return (
<Modal <Modal
title="添加用户分组" title={title}
open={visible} open={visible}
onOk={handleOk} onOk={handleOk}
onCancel={handleCancel} onCancel={handleCancel}
@ -56,12 +78,14 @@ const CreatGroup: React.FC<CreatGroupProps> = (props) => {
cancelText="取消" cancelText="取消"
centered={true} centered={true}
destroyOnHidden={true} destroyOnHidden={true}
width={600}
footer={ footer={
<div style={{ display: 'flex', justifyContent: 'right' }}> <div style={{ display: 'flex', justifyContent: 'right' }}>
<Button <Button
type="primary" type="primary"
onClick={handleOk} onClick={handleOk}
style={{ marginRight: '20px' }} style={{ marginRight: '20px' }}
loading={loading}
> >
</Button> </Button>
@ -81,7 +105,7 @@ const CreatGroup: React.FC<CreatGroupProps> = (props) => {
style={{ paddingTop: '20px', paddingBottom: '20px' }} style={{ paddingTop: '20px', paddingBottom: '20px' }}
> >
<Form.Item <Form.Item
name="groupName" name="name"
label="分组名" label="分组名"
rules={[{ required: true, message: '请输入分组名!' }]} rules={[{ required: true, message: '请输入分组名!' }]}
> >
@ -91,7 +115,7 @@ const CreatGroup: React.FC<CreatGroupProps> = (props) => {
<Form.Item name="parent_id" label="父分组名"> <Form.Item name="parent_id" label="父分组名">
<TreeSelect <TreeSelect
showSearch showSearch
allowClear={false} allowClear={true}
treeDefaultExpandAll treeDefaultExpandAll
placeholder="请选择父分组名" placeholder="请选择父分组名"
treeData={orgTreeData} treeData={orgTreeData}

View File

@ -0,0 +1,28 @@
import { request } from '@umijs/max';
const BASE_URL = '/api/v1/terminal';
// 根据终端序列号查询镜像列表
export async function getTerminalList(params:any) {
// console.log('终端列表 params', params);
return request<Termial.Termial_ListInfo>(`${BASE_URL}/query/devicelist`, {
method: 'POST',
data: params,
});
}
export async function deleteDevice(params:any) {
// console.log('终端列表 params', params);
return request<Termial.Termial_ListInfo>(`${BASE_URL}/delete/device`, {
method: 'POST',
data: params,
});
}
export async function saveDevice(params:any) {
// console.log('终端列表 params', params);
return request<Termial.Termial_ListInfo>(`${BASE_URL}/save/device`, {
method: 'POST',
data: params,
});
}

View File

@ -1,15 +1,15 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';
// 新建用户分组 const BASE_URL = '/api/nex/v1';
export async function addUserGroup(body?: any) { export async function addUserGroup(body?: any) {
return request<any>('/api/v1/user', { return request<any>(`${BASE_URL}/user/device/group/add`, {
method: 'POST', method: 'POST',
data: body, data: body,
}); });
} }
// 获取用户分组 // 获取用户分组
export async function getUserGroup(body?: any) { export async function getGroupTree(body?: any) {
return request<any>('/api/v1/user', { return request<any>(`${BASE_URL}/user/device/group/query`, {
method: 'POST', method: 'POST',
data: body, data: body,
}); });
@ -17,15 +17,15 @@ export async function getUserGroup(body?: any) {
// 删除用户分组 // 删除用户分组
export async function deleteUserGroup(body?: any) { export async function deleteUserGroup(body?: any) {
return request<any>('/api/v1/user', { return request<any>(`${BASE_URL}/user/device/group/delete`, {
method: 'POST', method: 'POST',
data: body, data: body,
}); });
} }
// 查询用户分组 // 查询用户
export async function getUserList(body?: any) { export async function getUserList(body?: any) {
return request<any>('/api/v1/user', { return request<any>(`${BASE_URL}/user/select/page`, {
method: 'POST', method: 'POST',
data: body, data: body,
}); });
@ -33,7 +33,7 @@ export async function getUserList(body?: any) {
// 新建用户 // 新建用户
export async function addUser(body?: any) { export async function addUser(body?: any) {
return request<any>('/api/v1/user', { return request<any>(`${BASE_URL}/user/add`, {
method: 'POST', method: 'POST',
data: body, data: body,
}); });
@ -41,7 +41,7 @@ export async function addUser(body?: any) {
// 编辑用户 // 编辑用户
export async function editUser(body?: any) { export async function editUser(body?: any) {
return request<any>('/api/v1/user', { return request<any>(`${BASE_URL}/user/update`, {
method: 'POST', method: 'POST',
data: body, data: body,
}); });
@ -49,7 +49,7 @@ export async function editUser(body?: any) {
//获取某个用户信息 //获取某个用户信息
export async function getUserById(body?: any) { export async function getUserById(body?: any) {
return request<any>('/api/v1/user', { return request<any>(`${BASE_URL}/user/query`, {
method: 'POST', method: 'POST',
data: body, data: body,
}); });
@ -57,7 +57,7 @@ export async function getUserById(body?: any) {
//删除用户 //删除用户
export async function deleteUser(body?: any) { export async function deleteUser(body?: any) {
return request<any>('/api/v1/user', { return request<any>(`${BASE_URL}/user/delete`, {
method: 'DELETE', method: 'DELETE',
data: body, data: body,
}); });