From c3d3ab36e396544de52e0892356e5e38225acc62 Mon Sep 17 00:00:00 2001 From: shaot Date: Fri, 8 Aug 2025 18:09:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=89=8D=E7=AB=AF):=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E3=80=81=E7=BB=88=E7=AB=AF=E7=BB=84=E7=BB=87=E6=A0=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- node_modules/.cache/logger/umi.log | 0 web-fe/.umirc.ts | 6 + web-fe/mock/terminal.ts | 120 +++++++ web-fe/src/constants/constants.ts | 44 +++ .../src/pages/components/customTree/index.tsx | 64 +++- web-fe/src/pages/terminal/index.tsx | 247 ++++++++----- .../terminal/mod/ImageSelectedTable/index.tsx | 66 ++-- .../terminal/mod/ImageSelectedTable/table.tsx | 82 +++-- .../pages/terminal/mod/eidtDevice/index.tsx | 50 +-- web-fe/src/pages/terminal/mod/group/index.tsx | 64 ++-- .../terminal/mod/selectedTable/index.tsx | 37 ++ .../terminal/mod/selectedTable/table.tsx | 79 ++-- web-fe/src/pages/userList/index.tsx | 336 +++++++++++++----- .../src/pages/userList/mod/eidtUser/index.tsx | 43 +-- web-fe/src/pages/userList/mod/group/index.tsx | 74 ++-- web-fe/src/services/terminal/index.ts | 28 ++ web-fe/src/services/userList/index.ts | 22 +- 17 files changed, 981 insertions(+), 381 deletions(-) create mode 100644 node_modules/.cache/logger/umi.log create mode 100644 web-fe/mock/terminal.ts create mode 100644 web-fe/src/constants/constants.ts create mode 100644 web-fe/src/services/terminal/index.ts diff --git a/node_modules/.cache/logger/umi.log b/node_modules/.cache/logger/umi.log new file mode 100644 index 0000000..e69de29 diff --git a/web-fe/.umirc.ts b/web-fe/.umirc.ts index bed3df5..e0101c2 100644 --- a/web-fe/.umirc.ts +++ b/web-fe/.umirc.ts @@ -43,5 +43,11 @@ export default defineConfig({ }, ], npmClient: 'pnpm', + proxy: { + '/api/nex/v1/': { + target: 'http://10.100.51.85:8112', + // changeOrigin: true, + }, + }, }); diff --git a/web-fe/mock/terminal.ts b/web-fe/mock/terminal.ts new file mode 100644 index 0000000..9ebabda --- /dev/null +++ b/web-fe/mock/terminal.ts @@ -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); + }, +}; diff --git a/web-fe/src/constants/constants.ts b/web-fe/src/constants/constants.ts new file mode 100644 index 0000000..d518cf0 --- /dev/null +++ b/web-fe/src/constants/constants.ts @@ -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: '男' }, +]; diff --git a/web-fe/src/pages/components/customTree/index.tsx b/web-fe/src/pages/components/customTree/index.tsx index 050dceb..beabe6b 100644 --- a/web-fe/src/pages/components/customTree/index.tsx +++ b/web-fe/src/pages/components/customTree/index.tsx @@ -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 { +// treeData: any[]; +// titleField: string; +// keyField: string; +// childrenField?: string; +// onCheck?: (checkedKeys?: Key[], rows?: any[], info?: any) => void; +// } + +// const CustomTree: React.FC = ({ +// 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 ; +// }; + +// export default CustomTree; + // 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 { - // treeData: User.OrganizationNode[]; +interface CustomTreeProps extends Omit { treeData: any[]; titleField: string; keyField: string; childrenField?: string; + onCheck?: (checkedKeys?: Key[], rows?: any[], info?: any) => void; } const CustomTree: React.FC = ({ @@ -16,6 +63,7 @@ const CustomTree: React.FC = ({ titleField, keyField, childrenField = 'children', + onCheck, ...restProps }) => { // Transform the tree data to match Ant Design's expected structure @@ -38,7 +86,17 @@ const CustomTree: React.FC = ({ const transformedData = transformTreeData(treeData); - return ; + 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 ; }; export default CustomTree; \ No newline at end of file diff --git a/web-fe/src/pages/terminal/index.tsx b/web-fe/src/pages/terminal/index.tsx index 0bafe94..4151e05 100644 --- a/web-fe/src/pages/terminal/index.tsx +++ b/web-fe/src/pages/terminal/index.tsx @@ -1,6 +1,9 @@ /* eslint-disable @typescript-eslint/no-use-before-define */ +import { DEVICE_TYPE_MAP, ERROR_CODE } from '@/constants/constants'; 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 { DeleteOutlined, DownOutlined, @@ -19,20 +22,18 @@ import { import type { ColumnsType } from 'antd/es/table'; import React, { useEffect, useState } from 'react'; import styles from './index.less'; +import BindImage from './mod/bindImage'; import BindUserModal from './mod/bindUser'; import EditModal from './mod/eidtDevice'; -import CreatGroup from './mod/group'; -import BindImage from './mod/bindImage'; const UserListPage: React.FC = () => { const [orgTreeData, setOrgTreeData] = useState([]); const [selectedOrg, setSelectedOrg] = useState(); - - // 用户列表 const [dataSource, setDataSource] = useState([]); const [loading, setLoading] = useState(false); const [searchText, setSearchText] = useState(''); const [selectedRowKeys, setSelectedRowKeys] = useState<[]>([]); + const [orgSearchText, setOrgSearchText] = useState(''); // 分页参数 const [currentPage, setCurrentPage] = useState(1); @@ -54,83 +55,108 @@ const UserListPage: React.FC = () => { // 获取用户分组组织树 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(() => { - getDataSource(); - }, [selectedOrg, currentPage, pageSize]); + // if (selectedOrg) { + // getDataSource(); + // } + }, [searchText, selectedOrg, currentPage, pageSize]); - const getGroupList = () => { - const mockOrgData: User.OrganizationNode[] = [ - { - 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, + const getGroupList = async () => { + const params = { + type: 2, }; - deleteUser(payload as any).then((res) => { - console.log('res=====', res); - const { success } = res || {}; - if (success) { - message.success('删除成功'); + try { + const result = await getGroupTree(params); + if (result.error_code === ERROR_CODE) { + setOrgTreeData(result.data.data || []); + 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([]); getDataSource(); } - }); + } catch (error) { + message.error('终端删除失败'); + } }; const handleEditUserInfo = (device: Termial.TermialItem) => { @@ -142,42 +168,62 @@ const UserListPage: React.FC = () => { const columns: ColumnsType = [ { - title: '显示名称', - dataIndex: 'username', - key: 'username', + title: '序号', + dataIndex: 'order', + key: 'order', + width: 80, + align: 'center', + render: (_: any, record: any, index: number) => {index + 1}, }, { title: '终端名称', dataIndex: 'device_name', key: 'device_name', + width: 220, + align: 'center', + render: (text) => { + return {text || '--'}; + }, }, { title: '终端分组', dataIndex: 'device_group_name', key: 'device_group_name', + width: 220, + align: 'center', + render: (text) => { + return {text || '--'}; + }, }, { title: 'IP地址', dataIndex: 'ip_addr', key: 'ip_addr', + width: 150, + align: 'center', + render: (text) => { + return {text || '--'}; + }, }, { title: '终端标识', dataIndex: 'mac_addr', key: 'mac_addr', + ellipsis: true, + align: 'center', + render: (text) => { + return {text || '--'}; + }, }, { title: '终端类型', dataIndex: 'device_type', key: 'device_type', - render: (text) => { - return ( -
- - {text} - -
- ); + width: 150, + align: 'center', + render: (text: number) => { + const key = text as keyof typeof DEVICE_TYPE_MAP; + return {DEVICE_TYPE_MAP[key] || '--'}; }, }, { @@ -262,8 +308,23 @@ const UserListPage: React.FC = () => { console.log('selectedRowKeys changed: ', newSelectedRowKeys); setSelectedRowKeys(newSelectedRowKeys as any); }; - - const onDeleteGroup = () => {}; + const onDeleteGroup = async () => { + 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 (
@@ -293,18 +354,19 @@ const UserListPage: React.FC = () => {
console.log('Search org:', value)} + onSearch={(value) => setOrgSearchText(value)} + onChange={(e) => setOrgSearchText(e.target.value)} />
} @@ -344,7 +406,7 @@ const UserListPage: React.FC = () => {
setSearchText(e.target.value)} style={{ width: 300 }} @@ -374,6 +436,7 @@ const UserListPage: React.FC = () => { onShowSizeChange: handlePageSizeChange, showSizeChanger: true, showQuickJumper: true, + pageSizeOptions: ['10', '20', '50', '100'], showTotal: (total) => { return `共${total}条数据`; }, @@ -385,6 +448,8 @@ const UserListPage: React.FC = () => {
{ setVisible(false); }} diff --git a/web-fe/src/pages/terminal/mod/ImageSelectedTable/index.tsx b/web-fe/src/pages/terminal/mod/ImageSelectedTable/index.tsx index 51a9995..29576ae 100644 --- a/web-fe/src/pages/terminal/mod/ImageSelectedTable/index.tsx +++ b/web-fe/src/pages/terminal/mod/ImageSelectedTable/index.tsx @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-use-before-define */ 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 CustomTable from '../selectedTable/table'; import SelectConponents from './table'; @@ -12,7 +13,7 @@ interface SelectedTableProps { onOk?: (values: any) => void; orgTreeData?: User.OrganizationNode[]; value?: any; - onChange?: (values: any) => void; + onChange: (values: any) => void; } const SelectedTable: React.FC = (props) => { @@ -20,7 +21,7 @@ const SelectedTable: React.FC = (props) => { placement = 'bottomLeft', orgTreeData, value: formValue, - onChange: onBindUserChange, + onChange: onBindImageChange, } = props; const [open, setOpen] = useState(false); const [tableData, setTableData] = useState([]); @@ -32,16 +33,15 @@ const SelectedTable: React.FC = (props) => { const [bindTableKeys, setBindTableKeys] = useState([]); useEffect(() => { - console.log('formValue==========', formValue); - const userList: any = []; - const userId: any = []; + const list: any = []; + const idList: any = []; if (formValue && formValue.length > 0) { formValue.forEach((item: any) => { - userList.push(item); - userId.push(item.id); + list.push(item); + idList.push(item.id); }); - setSelectedRowKeys(userId); - setSelectedRows(userList); + setSelectedRowKeys(idList); + setSelectedRows(list); } }, [formValue]); @@ -56,30 +56,26 @@ const SelectedTable: React.FC = (props) => { // 用户 (selectedRows || []).forEach((item) => { list.push({ - id: item.id, - name: item.name, - type: 1, + id: item.image_id, + name: item.image_name, }); }); - + setTableData(list); + onBindImageChange(list); onHnadleCancel(); }; const onUserTableSelect = (keys: any[], row: any[]) => { - console.log('selectedRowKeys changed: ', keys); - console.log('selectedRowKeys rows: ', row); setSelectedRowKeys(keys as any); setSelectedRows(row); }; const onSelectChange = (keys: any[]) => { - console.log('selectedRowKeys changed: ', keys); setBindTableKeys(keys as any); }; const handleDelete = (record?: any) => { - console.log('record=====handleDelete', record); if (record) { // 单个删除 const { id } = record || {}; @@ -98,12 +94,6 @@ const SelectedTable: React.FC = (props) => { const accessPopover = (
- {/* */} = (props) => {
); + const getColumns = (): ColumnsType => { + const columns: ColumnsType = [ + { + title: '序号', + dataIndex: 'order', + key: 'order', + width: 80, + render: (_, record, index) => {index + 1}, + }, + { + title: '镜像名称', + dataIndex: 'image_name', + key: 'image_name', + }, + { + title: '操作', + key: 'action', + render: (_, record) => ( + + ), + }, + ]; + return columns; + }; + return ( <> @@ -166,6 +183,7 @@ const SelectedTable: React.FC = (props) => { void; } -interface DataType { - key: React.Key; - name: string; - age: number; - address: string; -} - const TablePage: React.FC = ({ onUserTableSelect, selectedRowKeys, }) => { + const [loading, setLoading] = useState(false); const [data, setData] = useState([]); - const [searchText, setSearchText] = useState(''); + const [searchText, setSearchText] = useState(); const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(20); const [total, setTotal] = useState(0); - // Mock data generation useEffect(() => { - const mockData: DataType[] = []; - for (let i = 0; i < 100; i++) { - mockData.push({ - id: i + 99, - name: `User ${i}`, - }); - } - setData(mockData); - setTotal(mockData.length); - }, []); + loadImages(); + }, [searchText, currentPage, pageSize]); - const columns: TableProps['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 = [ { - title: 'Name', - dataIndex: 'name', - sorter: (a, b) => a.name.localeCompare(b.name), + title: '序号', + dataIndex: 'order', + key: 'order', + width: 80, + render: (_: any, record: any, index: number) => {index + 1}, }, + { + title: '镜像名称', + dataIndex: 'image_name', + render: (text: any) => {text || ''}, + }, + { + title: '桌面类型', + dataIndex: 'image_type', + key: 'image_type', + width: 200, + render: (text: number) => { + const key = text as keyof typeof IMAGES_TYPE_MAP; + return {IMAGES_TYPE_MAP[key] || '--'}; + }, + }, ]; const handleSearch = (value: string) => { @@ -74,10 +105,10 @@ const TablePage: React.FC = ({
= ({ onShowSizeChange: handlePageSizeChange, showSizeChanger: false, showQuickJumper: false, + pageSizeOptions: ['10', '20', '50', '100'], }} scroll={{ x: 'max-content', y: 300 }} /> diff --git a/web-fe/src/pages/terminal/mod/eidtDevice/index.tsx b/web-fe/src/pages/terminal/mod/eidtDevice/index.tsx index dbc3bcf..eb1e30b 100644 --- a/web-fe/src/pages/terminal/mod/eidtDevice/index.tsx +++ b/web-fe/src/pages/terminal/mod/eidtDevice/index.tsx @@ -1,17 +1,11 @@ // index.tsx -import { - Button, - Form, - Input, - message, - Modal, - Radio, - TreeSelect, -} from 'antd'; +import { DEVICE_TYPE_OPTIONS } from '@/constants/constants'; +import { ERROR_CODE } from '@/constants/images.constants'; +import { saveDevice } from '@/services/terminal'; +import { Button, Form, Input, message, Modal, Select, TreeSelect } from 'antd'; import React, { useEffect } from 'react'; interface UserEditModalProps { - // visible: boolean; orgTreeData: User.OrganizationNode[]; onCancel: () => void; onOk: (values: any) => void; @@ -39,9 +33,15 @@ const UserEditModal: React.FC = ({ const handleOk = async () => { try { 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) { - message.error('请检查表单字段'); + message.error('保存失败'); } }; @@ -50,7 +50,6 @@ const UserEditModal: React.FC = ({ types: { email: '${label} is not a valid email!', number: '${label} is not a valid number!', - cell_phone: '${label} is not a valid cell phone number!', }, number: { range: '${label} must be between ${min} and ${max}', @@ -95,8 +94,8 @@ const UserEditModal: React.FC = ({ validateMessages={validateMessages} > @@ -111,7 +110,7 @@ const UserEditModal: React.FC = ({ @@ -124,8 +123,15 @@ const UserEditModal: React.FC = ({ fieldNames={{ label: 'name', value: 'id', children: 'children' }} /> - + */} - - - + void; - onOk: (values: any) => void; + onOk: (values?: any) => void; orgTreeData: User.OrganizationNode[]; } const CreatGroup: React.FC = (props) => { const { visible, onCancel, onOk, orgTreeData, selectedOrg } = props; + const [loading, setLoading] = useState(false); const [form] = Form.useForm(); useEffect(() => { - const initialValues = { parent_device_group_id: [selectedOrg] }; - form.setFieldsValue(initialValues); + if (selectedOrg) { + const initialValues = { parent_device_group_id: [selectedOrg] }; + form.setFieldsValue(initialValues); + } }, [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 = () => { form.submit(); }; @@ -40,14 +32,34 @@ const CreatGroup: React.FC = (props) => { onCancel(); }; - const handleFinish = (values: any) => { - onOk(values); - form.resetFields(); + const handleFinish = async () => { + const values: any = await form.validateFields(); + 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 ( = (props) => { cancelText="取消" centered={true} destroyOnHidden={true} + width={600} footer={
@@ -80,17 +94,17 @@ const CreatGroup: React.FC = (props) => { style={{ paddingTop: '20px', paddingBottom: '20px' }} > - + = (props) => { } }; + const getColumns = (): ColumnsType => { + const columns: ColumnsType = [ + { + title: '序号', + dataIndex: 'order', + key: 'order', + width: 80, + render: (_, record, index) => {index + 1}, + }, + { + title: '名称', + dataIndex: 'name', + key: 'name', + }, + { + title: '类型', + dataIndex: 'type', + key: 'type', + render: (text) => ( + {text == 1 ? '用户' : text == 2 ? '用户组' : ''} + ), + }, + { + title: '操作', + key: 'action', + render: (_, record) => ( + + ), + } + ]; + return columns; + }; + const accessPopover = (
= (props) => { scrollY={400} rowKey="id" pagination={false} + columns={getColumns()} rowSelection={{ bindTableKeys, onChange: onSelectChange, diff --git a/web-fe/src/pages/terminal/mod/selectedTable/table.tsx b/web-fe/src/pages/terminal/mod/selectedTable/table.tsx index 5f0dd18..d86e3da 100644 --- a/web-fe/src/pages/terminal/mod/selectedTable/table.tsx +++ b/web-fe/src/pages/terminal/mod/selectedTable/table.tsx @@ -11,6 +11,7 @@ interface DeletableTableProps { rowKey?: string; rowSelection?: any; pagination?:any; + columns: ColumnsType; } const DeletableTable: React.FC = (props) => { @@ -23,52 +24,52 @@ const DeletableTable: React.FC = (props) => { ...restProps } = props; console.log("datasource=====",dataSource); - const getColumns = (): ColumnsType => { - const columns: ColumnsType = [ - { - title: '名称', - dataIndex: 'name', - key: 'name', - }, - { - title: '类型', - dataIndex: 'type', - key: 'type', - render: (text) => ( - {text == 1 ? '用户' : text == 2 ? '用户组' : ''} - ), - }, - ]; + // const getColumns = (): ColumnsType => { + // const columns: ColumnsType = [ + // { + // title: '名称', + // dataIndex: 'name', + // key: 'name', + // }, + // { + // title: '类型', + // dataIndex: 'type', + // key: 'type', + // render: (text) => ( + // {text == 1 ? '用户' : text == 2 ? '用户组' : ''} + // ), + // }, + // ]; - if (isSerial) { - columns.unshift({ - title: '序号', - dataIndex: 'order', - key: 'order', - width: 80, - render: (_, record, index) => {index + 1}, - }); - } + // if (isSerial) { + // columns.unshift({ + // title: '序号', + // dataIndex: 'order', + // key: 'order', + // width: 80, + // render: (_, record, index) => {index + 1}, + // }); + // } - if (isAction) { - columns.push({ - title: '操作', - key: 'action', - render: (_, record) => ( - - ), - }); - } + // if (isAction) { + // columns.push({ + // title: '操作', + // key: 'action', + // render: (_, record) => ( + // + // ), + // }); + // } - return columns; - }; + // return columns; + // }; return (
{ const [orgTreeData, setOrgTreeData] = useState([]); - const [selectedOrg, setSelectedOrg] = useState(); + const [selectedOrg, setSelectedOrg] = useState(); // 用户列表 - const [users, setUsers] = useState([]); + const [dataSource, setDataSource] = useState([]); const [loading, setLoading] = useState(false); + const [spinning, setSpinning] = useState(false); const [searchText, setSearchText] = useState(''); const [selectedRowKeys, setSelectedRowKeys] = useState<[]>([]); + const [orgSearchText, setOrgSearchText] = useState(''); // 分页参数 const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); - const [totalUsers, setTotalUsers] = useState(0); + const [total, setTotal] = useState(0); // 编辑用户 const [currentUserInfo, setCurrentUserInfo] = useState({ visible: false, @@ -43,60 +61,96 @@ const UserListPage: React.FC = () => { // 获取用户列表数据 useEffect(() => { - getUserListInit(); - }, [selectedOrg, currentPage, pageSize]); + getDataSource(); + }, [searchText,selectedOrg, currentPage, pageSize]); - const getUserGroupList = () => { - const mockOrgData: User.OrganizationNode[] = [ - { - 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 getUserGroupList = async () => { + const params = { + type: 1, + }; + setSpinning(true); + try { + const result = await getGroupTree(params); + console.log('result=====', result); + const { code, data = [] } = result || {}; + setSpinning(false); + if (code === ERROR_CODE) { + if (data.length > 0) { + const { children = [] } = data[0] || {}; + setOrgTreeData(children); + // if (children.length > 0) { + // setSelectedOrg(children[0].id); + // } + } + } else { + message.error(result.message || '获取用户分组失败'); + } + } catch (err) { + message.error('获取用户分组失败'); + setSpinning(false); + } }; - const getUserListInit = () => { - setLoading(true); - setTimeout(() => { - const startIndex = (currentPage - 1) * pageSize; - const mockUsers: User.UserItem[] = Array.from( - { length: pageSize }, - (_, index) => ({ - user_id: startIndex + index + 1, - user_name: 'string', - }), - ); + const filterTreeData = ( + data: User.OrganizationNode[], + searchTerm: string, + ): User.OrganizationNode[] => { + if (!searchTerm) return data; - setUsers(mockUsers); - setTotalUsers(100); + 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); + + 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); - }, 300); + } }; const onDelete = (user?: User.UserItem) => { @@ -110,7 +164,7 @@ const UserListPage: React.FC = () => { if (success) { message.success('删除成功'); setSelectedRowKeys([]); - getUserListInit(); + getDataSource(); } }); }; @@ -137,34 +191,105 @@ const UserListPage: React.FC = () => { const columns: ColumnsType = [ { - title: '登录名', - dataIndex: 'user_id', - key: 'user_id', + title: '序号', + dataIndex: 'order', + key: 'order', + width: 80, + align: 'center', + render: (_: any, record: any, index: number) => {index + 1}, }, { - title: '用户姓名', + title: '用户名', dataIndex: 'user_name', key: 'user_name', + width: 150, + align: 'center', + render: (text) => { + return {text || '--'}; + }, }, { title: '用户分组', - dataIndex: 'userGroup', - key: 'userGroup', - }, - { - title: '性别', - dataIndex: 'sex', - key: 'sex', + dataIndex: 'user_group_name', + key: 'user_group_name', + width: 150, + align: 'center', + render: (text) => { + return {text || '--'}; + }, }, { title: '状态', dataIndex: 'status', key: 'status', + width: 150, + align: 'center', + render: (text) => { + return {text || '--'}; + }, }, { title: '用户类别', - dataIndex: 'userType', - key: 'userType', + dataIndex: 'user_type', + key: 'user_type', + width: 150, + align: 'center', + render: (text: number) => { + const key = text as keyof typeof USER_TYPE_MAP; + return {USER_TYPE_MAP[key] || '--'}; + }, + }, + { + title: '优先级', + dataIndex: 'priority', + key: 'priority', + width: 150, + align: 'center', + render: (text: number) => { + const key = text as keyof typeof PRIORITY_MAP; + return {PRIORITY_MAP[key] || '--'}; + }, + }, + { + title: '性别', + dataIndex: 'gender', + key: 'gender', + width: 150, + align: 'center', + render: (text: number) => { + const key = text as keyof typeof GENDER_MAP; + return {GENDER_MAP[key] || '--'}; + }, + }, + { + title: '电话', + dataIndex: 'cell_phone', + key: 'cell_phone', + width: 150, + align: 'center', + render: (text:any) => { + return {text || '--'}; + }, + }, + { + title: '出生日期', + dataIndex: 'birthday', + key: 'birthday', + width: 150, + align: 'center', + render: (text:any) => { + return {text || '--'}; + }, + }, + { + title: '身份证号', + dataIndex: 'identity_no', + key: 'identity_no', + width: 150, + align: 'center', + render: (text:any) => { + return {text || '--'}; + }, }, { title: '操作', @@ -197,7 +322,7 @@ const UserListPage: React.FC = () => { const onOrgSelect = (selectedKeys: React.Key[]) => { if (selectedKeys.length > 0) { - setSelectedOrg(selectedKeys[0] as number); + setSelectedOrg(selectedKeys[0]); setCurrentPage(1); } }; @@ -217,18 +342,47 @@ const UserListPage: React.FC = () => { 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 (
+
console.log('Search org:', value)} + onSearch={(value) => setOrgSearchText(value)} + onChange={(e) => setOrgSearchText(e.target.value)} />
- } - /> + + } + /> +
@@ -297,14 +456,14 @@ const UserListPage: React.FC = () => { 删除 -
setSearchText(e.target.value)} style={{ width: 300 }} @@ -319,17 +478,18 @@ const UserListPage: React.FC = () => {
{ return `共${total}条数据`; }, @@ -345,11 +505,13 @@ const UserListPage: React.FC = () => { { setVisible(false); }} - onOk={() => {}} + onOk={onSaveGroup} orgTreeData={orgTreeData} /> = ({ currentUserInfo, }) => { const { recordData, visible, selectedOrg } = currentUserInfo || {}; - const { user_id } = recordData || {}; + const { id } = recordData || {}; const [form] = Form.useForm(); useEffect(() => { const initialValues = { user_group_id: [selectedOrg], status: 1 }; form.setFieldsValue(initialValues); - }, [visible, form,recordData, selectedOrg]); + }, [visible, form, recordData, selectedOrg]); const handleOk = async () => { try { @@ -61,7 +62,7 @@ const UserEditModal: React.FC = ({ return ( = ({ - + + + = ({ - - - {/* 电话号码 */} void; - onOk: (values: any) => void; + onOk: (values?: any) => void; orgTreeData: User.OrganizationNode[]; } const CreatGroup: React.FC = (props) => { - const { visible, onCancel, onOk, orgTreeData, selectedOrg } = props; + const { + title = '新增分组', + visible, + onCancel, + onOk, + orgTreeData, + selectedOrg, + type + } = props; + const [loading, setLoading] = useState(false); const [form] = Form.useForm(); useEffect(() => { - const initialValues = { parent_id: [selectedOrg] }; - form.setFieldsValue(initialValues); + if (selectedOrg) { + const initialValues = { parent_id: [selectedOrg] }; + form.setFieldsValue(initialValues); + } }, [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 = () => { form.submit(); }; @@ -41,14 +43,34 @@ const CreatGroup: React.FC = (props) => { onCancel(); }; - const handleFinish = (values: any) => { - onOk(values); - form.resetFields(); + const handleFinish = async () => { + const values: any = await form.validateFields(); + 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 ( = (props) => { cancelText="取消" centered={true} destroyOnHidden={true} + width={600} footer={
@@ -81,7 +105,7 @@ const CreatGroup: React.FC = (props) => { style={{ paddingTop: '20px', paddingBottom: '20px' }} > @@ -91,7 +115,7 @@ const CreatGroup: React.FC = (props) => { (`${BASE_URL}/query/devicelist`, { + method: 'POST', + data: params, + }); +} + +export async function deleteDevice(params:any) { + // console.log('终端列表 params', params); + return request(`${BASE_URL}/delete/device`, { + method: 'POST', + data: params, + }); +} + +export async function saveDevice(params:any) { + // console.log('终端列表 params', params); + return request(`${BASE_URL}/save/device`, { + method: 'POST', + data: params, + }); +} \ No newline at end of file diff --git a/web-fe/src/services/userList/index.ts b/web-fe/src/services/userList/index.ts index 1eb3376..73253c8 100644 --- a/web-fe/src/services/userList/index.ts +++ b/web-fe/src/services/userList/index.ts @@ -1,15 +1,15 @@ import { request } from '@umijs/max'; -// 新建用户分组 +const BASE_URL = '/api/nex/v1'; export async function addUserGroup(body?: any) { - return request('/api/v1/user', { + return request(`${BASE_URL}/user/device/group/add`, { method: 'POST', data: body, }); } // 获取用户分组 -export async function getUserGroup(body?: any) { - return request('/api/v1/user', { +export async function getGroupTree(body?: any) { + return request(`${BASE_URL}/user/device/group/query`, { method: 'POST', data: body, }); @@ -17,15 +17,15 @@ export async function getUserGroup(body?: any) { // 删除用户分组 export async function deleteUserGroup(body?: any) { - return request('/api/v1/user', { + return request(`${BASE_URL}/user/device/group/delete`, { method: 'POST', data: body, }); } -// 查询用户分组 +// 查询用户 export async function getUserList(body?: any) { - return request('/api/v1/user', { + return request(`${BASE_URL}/user/select/page`, { method: 'POST', data: body, }); @@ -33,7 +33,7 @@ export async function getUserList(body?: any) { // 新建用户 export async function addUser(body?: any) { - return request('/api/v1/user', { + return request(`${BASE_URL}/user/add`, { method: 'POST', data: body, }); @@ -41,7 +41,7 @@ export async function addUser(body?: any) { // 编辑用户 export async function editUser(body?: any) { - return request('/api/v1/user', { + return request(`${BASE_URL}/user/update`, { method: 'POST', data: body, }); @@ -49,7 +49,7 @@ export async function editUser(body?: any) { //获取某个用户信息 export async function getUserById(body?: any) { - return request('/api/v1/user', { + return request(`${BASE_URL}/user/query`, { method: 'POST', data: body, }); @@ -57,7 +57,7 @@ export async function getUserById(body?: any) { //删除用户 export async function deleteUser(body?: any) { - return request('/api/v1/user', { + return request(`${BASE_URL}/user/delete`, { method: 'DELETE', data: body, });