From 08b9097d3fa3d61835275883c18fddbb9aeee819 Mon Sep 17 00:00:00 2001 From: shaot Date: Wed, 6 Aug 2025 18:06:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=89=8D=E7=AB=AF):=E7=BB=88=E7=AB=AF?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web-fe/src/pages/components/Layout/index.tsx | 4 + .../src/pages/components/customTree/index.tsx | 43 ++ web-fe/src/pages/terminal/contast.ts | 20 + web-fe/src/pages/terminal/index.less | 45 ++ web-fe/src/pages/terminal/index.tsx | 382 ++++++++++++++++ .../pages/terminal/mod/eidtDevice/index.tsx | 183 ++++++++ web-fe/src/pages/terminal/mod/group/index.tsx | 109 +++++ web-fe/src/pages/userList/contast.ts | 20 + web-fe/src/pages/userList/index.less | 18 +- web-fe/src/pages/userList/index.tsx | 406 +++++++++++------- .../src/pages/userList/mod/eidtUser/index.tsx | 209 +++++++++ .../src/pages/userList/mod/group/index.less | 0 web-fe/src/pages/userList/mod/group/index.tsx | 107 +++-- .../pages/userList/mod/passwordEdit/index.tsx | 114 +++++ web-fe/src/services/userList/index.ts | 89 ++++ web-fe/src/services/userList/typings.d.ts | 68 +++ web-fe/yarn.lock | 37 +- 17 files changed, 1650 insertions(+), 204 deletions(-) create mode 100644 web-fe/src/pages/components/customTree/index.tsx create mode 100644 web-fe/src/pages/terminal/contast.ts create mode 100644 web-fe/src/pages/terminal/mod/eidtDevice/index.tsx create mode 100644 web-fe/src/pages/terminal/mod/group/index.tsx create mode 100644 web-fe/src/pages/userList/contast.ts create mode 100644 web-fe/src/pages/userList/mod/eidtUser/index.tsx delete mode 100644 web-fe/src/pages/userList/mod/group/index.less create mode 100644 web-fe/src/pages/userList/mod/passwordEdit/index.tsx create mode 100644 web-fe/src/services/userList/index.ts create mode 100644 web-fe/src/services/userList/typings.d.ts diff --git a/web-fe/src/pages/components/Layout/index.tsx b/web-fe/src/pages/components/Layout/index.tsx index aa6964b..9ff7238 100644 --- a/web-fe/src/pages/components/Layout/index.tsx +++ b/web-fe/src/pages/components/Layout/index.tsx @@ -8,6 +8,8 @@ import { import { Avatar, Button, Dropdown, Layout, Menu, message } from 'antd'; import React, { useEffect, useState } from 'react'; import { history, Outlet, useLocation } from 'umi'; +import { ConfigProvider } from 'antd'; +import zhCN from 'antd/lib/locale/zh_CN'; import './index.less'; const { Header, Sider, Content } = Layout; @@ -68,6 +70,7 @@ const MainLayout: React.FC = () => { }; return ( + { + ); }; diff --git a/web-fe/src/pages/components/customTree/index.tsx b/web-fe/src/pages/components/customTree/index.tsx new file mode 100644 index 0000000..1c6911e --- /dev/null +++ b/web-fe/src/pages/components/customTree/index.tsx @@ -0,0 +1,43 @@ +// custom-tree.tsx +import React from 'react'; +import { Tree } from 'antd'; +import type { DataNode, TreeProps } from 'antd/es/tree'; + +interface CustomTreeProps extends Omit { + treeData: any[]; + titleField: string; + keyField: string; + childrenField?: string; +} + +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; \ No newline at end of file diff --git a/web-fe/src/pages/terminal/contast.ts b/web-fe/src/pages/terminal/contast.ts new file mode 100644 index 0000000..af00509 --- /dev/null +++ b/web-fe/src/pages/terminal/contast.ts @@ -0,0 +1,20 @@ +export interface User { + id: string; + user_id:string, + username: string; + loginName: string; + userGroup: string; + userType: string; +} + +export interface OrganizationNode { + id: string; + name: string; + children?: OrganizationNode[]; +} + +export interface ModalBaseNode { + visible:boolean; + recordData: any; + selectedOrg:string; +} diff --git a/web-fe/src/pages/terminal/index.less b/web-fe/src/pages/terminal/index.less index e69de29..6b26fb9 100644 --- a/web-fe/src/pages/terminal/index.less +++ b/web-fe/src/pages/terminal/index.less @@ -0,0 +1,45 @@ +.user_content { + display: flex; + width: 100%; + height: 100%; + background-color: #f7f8fa; + .left_content { + width: 400px; + height: 100%; + padding: 8px; + background-color: #fff; + .search { + width: 100%; + height: 70px; + } + .tree_box { + width: 100%; + height: calc(100% - 70px); + overflow: auto; + padding-top: 10px; + } + } + .right_content { + width: calc(100% - 400px); + height: 100%; + padding-left: 10px; + .teble_content { + width: 100%; + height: 100%; + background-color: #fff; + padding: 8px; + .teble_box { + width: 100%; + height: calc(100% - 40px); + overflow: hidden; + } + } + } + :global { + :where(.css-dev-only-do-not-override-1vjf2v5).ant-pagination + .ant-pagination-total-text { + position: absolute; + left: 5px; + } + } +} diff --git a/web-fe/src/pages/terminal/index.tsx b/web-fe/src/pages/terminal/index.tsx index e69de29..10926fd 100644 --- a/web-fe/src/pages/terminal/index.tsx +++ b/web-fe/src/pages/terminal/index.tsx @@ -0,0 +1,382 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ +import CustomTree from '@/pages/components/customTree'; +import { deleteUser } from '@/services/userList'; +import { + DeleteOutlined, + DownOutlined, + PlusOutlined, + TeamOutlined, +} from '@ant-design/icons'; +import { Button, Input, message, Popconfirm, Popover, Table } from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import React, { useEffect, useState } from 'react'; +import { OrganizationNode, Device } from './contast'; +import styles from './index.less'; +import EditModal from './mod/eidtDevice'; +import CreatGroup from './mod/group'; + +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 [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [total, setTotal] = useState(0); + // 编辑用户 + const [currentUserInfo, setCurrentUserInfo] = useState({ + visible: false, + }); + + // 添加分组 + const [visible, setVisible] = useState(false); + + // 获取用户分组组织树 + useEffect(() => { + getGroupList(); + }, []); + + // 获取用户列表数据 + useEffect(() => { + getDataSource(); + }, [selectedOrg, currentPage, pageSize]); + + const getGroupList = () => { + const mockOrgData: OrganizationNode[] = [ + { + name: 'Headquartershdy', + 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 string); + setOrgTreeData(mockOrgData); + }; + + const getDataSource = () => { + setLoading(true); + setTimeout(() => { + const startIndex = (currentPage - 1) * pageSize; + const mockUsers: Device[] = Array.from( + { length: pageSize }, + (_, index) => ({ + id: `${startIndex + index + 1}`, + username: `User ${startIndex + index + 1}`, + loginName: `login${startIndex + index + 1}`, + userGroup: + index % 3 === 0 ? 'Admin' : index % 3 === 1 ? 'Manager' : 'User', + userType: + index % 4 === 0 + ? 'Full-time' + : index % 4 === 1 + ? 'Part-time' + : index % 4 === 2 + ? 'Contractor' + : 'Intern', + }), + ); + + setDataSource(mockUsers); + setTotal(100); + setLoading(false); + }, 300); + }; + + const onDelete = (device?: Device) => { + const { user_id } = device || {}; + const payload = { + ids: user_id ? [user_id] : selectedRowKeys, + }; + deleteUser(payload as any).then((res) => { + console.log('res=====', res); + const { success } = res || {}; + if (success) { + message.success('删除成功'); + setSelectedRowKeys([]); + getDataSource(); + } + }); + }; + + const handleEditUserInfo = (device: Device) => { + setCurrentUserInfo({ + recordData: { ...device }, + visible: true, + }); + }; + + const columns: ColumnsType = [ + { + title: '显示名称', + dataIndex: 'username', + key: 'username', + }, + { + title: '终端名称', + dataIndex: 'loginName', + key: 'loginName', + }, + { + title: '终端分组', + dataIndex: 'userGroup', + key: 'userGroup', + }, + { + title: 'IP地址', + dataIndex: 'sex', + key: 'sex', + }, + { + title: '终端标识', + dataIndex: 'mac_addr', + key: 'mac_addr', + }, + { + title: '终端类型', + dataIndex: 'device_type', + key: 'device_type', + }, + { + title: '操作', + key: 'actions', + align: 'center', + width: 150, + render: (_, record) => ( +
+ + + onDelete(record)} + // onCancel={cancel} + okText="删除" + cancelText="取消" + > + + +
+ +
+
+ +
+
+ } + > + e.preventDefault()}> + 更多 + + + + + ), + }, + ]; + + const onOrgSelect = (selectedKeys: React.Key[]) => { + if (selectedKeys.length > 0) { + setSelectedOrg(selectedKeys[0] as string); + setCurrentPage(1); + } + }; + + const handlePageChange = (page: number, size: number) => { + setCurrentPage(page); + setPageSize(size); + }; + + const handlePageSizeChange = (current: number, size: number) => { + setCurrentPage(1); + setPageSize(size); + }; + + const onSelectChange = (newSelectedRowKeys: React.Key[]) => { + console.log('selectedRowKeys changed: ', newSelectedRowKeys); + setSelectedRowKeys(newSelectedRowKeys as any); + }; + + const onDeleteGroup = () => {}; + + return ( +
+
+
+
+
+ console.log('Search org:', value)} + /> +
+
+ } + /> +
+
+
+
+
+
+ onDelete()} + // onCancel={cancel} + okText="删除" + cancelText="取消" + disabled={selectedRowKeys.length === 0} + > + + + +
+
+
+ setSearchText(e.target.value)} + style={{ width: 300 }} + onSearch={(value) => { + console.log('Search user:', value); + setCurrentPage(1); // Reset to first page when searching + }} + /> +
+
+
+
+ { + return `共${total}条数据`; + }, + }} + scroll={{ x: 'max-content', y: 55 * 12 }} + /> + + + + { + setVisible(false); + }} + selectedOrg={selectedOrg} + onOk={() => {}} + orgTreeData={orgTreeData} + /> + { + setCurrentUserInfo({ + recordData: {}, + visible: false, + }); + }} + onOk={() => {}} + /> + + ); +}; + +export default UserListPage; diff --git a/web-fe/src/pages/terminal/mod/eidtDevice/index.tsx b/web-fe/src/pages/terminal/mod/eidtDevice/index.tsx new file mode 100644 index 0000000..d2696a2 --- /dev/null +++ b/web-fe/src/pages/terminal/mod/eidtDevice/index.tsx @@ -0,0 +1,183 @@ +// index.tsx +import { + Button, + Form, + Input, + message, + Modal, + Radio, + Select, + TreeSelect, +} from 'antd'; +import React, { useEffect } from 'react'; +import { ModalBaseNode, OrganizationNode } from '../../contast'; + +const { Option } = Select; + +interface UserEditModalProps { + // visible: boolean; + orgTreeData: OrganizationNode[]; + onCancel: () => void; + onOk: (values: any) => void; + confirmLoading?: boolean; + currentUserInfo?: ModalBaseNode; + selectedOrg?: string; +} + +const UserEditModal: React.FC = ({ + orgTreeData, + onCancel, + onOk, + confirmLoading = false, + currentUserInfo, +}) => { + const { recordData, visible, selectedOrg } = currentUserInfo || {}; + const { user_id } = recordData || {}; + const [form] = Form.useForm(); + + useEffect(() => { + const initialValues = { user_group_id: [selectedOrg], status: 1 }; + form.setFieldsValue(initialValues); + }, [visible, form, recordData, selectedOrg]); + + const handleOk = async () => { + try { + const values = await form.validateFields(); + onOk(values); + } catch (error) { + message.error('请检查表单字段'); + } + }; + + const validateMessages = { + required: '${label} is required!', + 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}', + }, + }; + + return ( + + + + + } + > +
+ + + + + + + + + + + + + + + 用户认证 + 终端认证 + + + + + + + + + + {/* + + */} + + + + + + + +
+ ); +}; + +export default UserEditModal; diff --git a/web-fe/src/pages/terminal/mod/group/index.tsx b/web-fe/src/pages/terminal/mod/group/index.tsx new file mode 100644 index 0000000..8de23d9 --- /dev/null +++ b/web-fe/src/pages/terminal/mod/group/index.tsx @@ -0,0 +1,109 @@ +// d:\VDI\vdi\web-fe\src\pages\userList\mod\group\index.tsx +import { Button, Form, Input, Modal, TreeSelect } from 'antd'; +import React, { useEffect } from 'react'; +// import type { DataNode } from 'antd/es/tree'; +import { OrganizationNode } from '../../contast'; + +interface CreatGroupProps { + visible: boolean; + selectedOrg: string; + onCancel: () => void; + onOk: (values: any) => void; + orgTreeData: OrganizationNode[]; +} + +const CreatGroup: React.FC = (props) => { + const { visible, onCancel, onOk, orgTreeData, selectedOrg } = props; + const [form] = Form.useForm(); + + useEffect(() => { + 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(); + }; + + const handleCancel = () => { + form.resetFields(); + onCancel(); + }; + + const handleFinish = (values: any) => { + onOk(values); + form.resetFields(); + }; + + return ( + + + + + } + > +
+
+ + + + + + + + +
+
+ ); +}; + +export default CreatGroup; diff --git a/web-fe/src/pages/userList/contast.ts b/web-fe/src/pages/userList/contast.ts new file mode 100644 index 0000000..af00509 --- /dev/null +++ b/web-fe/src/pages/userList/contast.ts @@ -0,0 +1,20 @@ +export interface User { + id: string; + user_id:string, + username: string; + loginName: string; + userGroup: string; + userType: string; +} + +export interface OrganizationNode { + id: string; + name: string; + children?: OrganizationNode[]; +} + +export interface ModalBaseNode { + visible:boolean; + recordData: any; + selectedOrg:string; +} diff --git a/web-fe/src/pages/userList/index.less b/web-fe/src/pages/userList/index.less index 1f0d41e..6b26fb9 100644 --- a/web-fe/src/pages/userList/index.less +++ b/web-fe/src/pages/userList/index.less @@ -1,9 +1,10 @@ .user_content { + display: flex; width: 100%; height: 100%; background-color: #f7f8fa; .left_content { - width: 100%; + width: 400px; height: 100%; padding: 8px; background-color: #fff; @@ -19,13 +20,26 @@ } } .right_content { - width: 100%; + width: calc(100% - 400px); height: 100%; + padding-left: 10px; .teble_content { width: 100%; height: 100%; background-color: #fff; padding: 8px; + .teble_box { + width: 100%; + height: calc(100% - 40px); + overflow: hidden; + } + } + } + :global { + :where(.css-dev-only-do-not-override-1vjf2v5).ant-pagination + .ant-pagination-total-text { + position: absolute; + left: 5px; } } } diff --git a/web-fe/src/pages/userList/index.tsx b/web-fe/src/pages/userList/index.tsx index ea23cce..2cbc853 100644 --- a/web-fe/src/pages/userList/index.tsx +++ b/web-fe/src/pages/userList/index.tsx @@ -1,89 +1,90 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ +import CustomTree from '@/pages/components/customTree'; +import { deleteUser } from '@/services/userList'; import { DeleteOutlined, PlusOutlined, TeamOutlined } from '@ant-design/icons'; -import { Button, Col, Input, Pagination, Row, Table, Tree } from 'antd'; +import { Button, Input, Popconfirm, Table, message } from 'antd'; import type { ColumnsType } from 'antd/es/table'; -import type { DataNode } from 'antd/es/tree'; import React, { useEffect, useState } from 'react'; -import CreatGroup from './mod/group'; +import { OrganizationNode, User } from './contast'; import styles from './index.less'; - -interface User { - id: string; - username: string; - loginName: string; - userGroup: string; - userType: string; -} - -interface OrganizationNode { - id: string; - name: string; - children?: OrganizationNode[]; -} +import UserEditModal from './mod/eidtUser'; +import CreatGroup from './mod/group'; +import PasswordResetModal from './mod/passwordEdit'; const UserListPage: React.FC = () => { - // State for organization tree - const [orgTreeData, setOrgTreeData] = useState([]); + const [orgTreeData, setOrgTreeData] = useState([]); const [selectedOrg, setSelectedOrg] = useState(''); - // State for user list + // 用户列表 const [users, setUsers] = useState([]); const [loading, setLoading] = useState(false); const [searchText, setSearchText] = useState(''); - const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [selectedRowKeys, setSelectedRowKeys] = useState<[]>([]); - // State for pagination + // 分页参数 const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); const [totalUsers, setTotalUsers] = useState(0); + // 编辑用户 + const [currentUserInfo, setCurrentUserInfo] = useState({ + visible: false, + }); + // 重置密码 + const [eidtPassword, setEidtPassword] = useState({ + visible: false, + }); // 添加分组 - const [visible, setVisible] = useState(false); + const [visible, setVisible] = useState(false); - // Mock data for organization tree + // 获取用户分组组织树 useEffect(() => { - // In a real application, this would come from an API - const mockOrgData: DataNode[] = [ + getUserGroupList(); + }, []); + + // 获取用户列表数据 + useEffect(() => { + getUserListInit(); + }, [selectedOrg, currentPage, pageSize]); + + const getUserGroupList = () => { + const mockOrgData: OrganizationNode[] = [ { - title: 'Headquarters', - key: '1', + name: 'Headquarters', + id: '1', children: [ { - title: 'HR Department', - key: '2', + name: 'HR Department', + id: '2', }, { - title: 'IT Department', - key: '3', + name: 'IT Department', + id: '3', children: [ { - title: 'Frontend Team', - key: '4', + name: 'Frontend Team', + id: '4', }, { - title: 'Backend Team', - key: '5', + name: 'Backend Team', + id: '5', }, ], }, { - title: 'Finance Department', - key: '6', + name: 'Finance Department', + id: '6', }, ], }, ]; - setSelectedOrg(mockOrgData[0].key as string); + setSelectedOrg(mockOrgData[0].id as string); setOrgTreeData(mockOrgData); - }, []); + }; - // Mock data for users with pagination - useEffect(() => { - // In a real application, this would come from an API based on selected organization + const getUserListInit = () => { setLoading(true); - - // Simulate API call delay setTimeout(() => { - // Generate mock users based on current page and page size const startIndex = (currentPage - 1) * pageSize; const mockUsers: User[] = Array.from( { length: pageSize }, @@ -105,12 +106,47 @@ const UserListPage: React.FC = () => { ); setUsers(mockUsers); - setTotalUsers(100); // Mock total count + setTotalUsers(100); setLoading(false); }, 300); - }, [selectedOrg, currentPage, pageSize]); + }; + + const onDelete = (user?: User) => { + const { user_id } = user || {}; + const payload = { + ids: user_id ? [user_id] : selectedRowKeys, + }; + deleteUser(payload as any).then((res) => { + console.log('res=====', res); + const { success } = res || {}; + if (success) { + message.success('删除成功'); + setSelectedRowKeys([]); + getUserListInit(); + } + }); + }; + + const handlePassword = (user: User) => { + setEidtPassword({ + recordData: { ...user }, + visible: true, + }); + }; + + const handleCreatUserInfo = () => { + setCurrentUserInfo({ + visible: true, + }); + }; + + const handleEditUserInfo = (user: User) => { + setCurrentUserInfo({ + recordData: { ...user }, + visible: true, + }); + }; - // Define columns for the user table const columns: ColumnsType = [ { title: '登录名', @@ -145,31 +181,33 @@ const UserListPage: React.FC = () => { { title: '操作', key: 'actions', + align: 'center', render: (_, record) => ( -
- - - + onDelete(record)} + // onCancel={cancel} + okText="删除" + cancelText="取消" + > + +
), }, ]; - const handleUserAction = (user: User) => { - console.log('Editing user:', user); - // Implement user edit logic here - }; - const onOrgSelect = (selectedKeys: React.Key[]) => { if (selectedKeys.length > 0) { setSelectedOrg(selectedKeys[0] as string); - // Reset to first page when organization changes setCurrentPage(1); } }; @@ -180,118 +218,172 @@ const UserListPage: React.FC = () => { }; const handlePageSizeChange = (current: number, size: number) => { - setCurrentPage(1); // Reset to first page when page size changes + setCurrentPage(1); setPageSize(size); }; const onSelectChange = (newSelectedRowKeys: React.Key[]) => { console.log('selectedRowKeys changed: ', newSelectedRowKeys); - setSelectedRowKeys(newSelectedRowKeys); + setSelectedRowKeys(newSelectedRowKeys as any); }; + const onDeleteGroup = () => {}; + return (
- -
-
-
-
-
- console.log('Search org:', value)} +
+
+
+
-
- } - /> -
+
- -
-
-
-
console.log('Search org:', value)} + /> +
+
+ } + /> +
+
+
+
+
+
+ - -
-
-
- setSearchText(e.target.value)} - style={{ width: 300 }} - onSearch={(value) => { - console.log('Search user:', value); - setCurrentPage(1); // Reset to first page when searching - }} - /> -
-
-
- -
- -
- - `Showing ${range[0]}-${range[1]} of ${total} items` - } + 新建用户 + + onDelete()} + // onCancel={cancel} + okText="删除" + cancelText="取消" + disabled={selectedRowKeys.length === 0} + > + + + +
+
+
+ setSearchText(e.target.value)} + style={{ width: 300 }} + onSearch={(value) => { + console.log('Search user:', value); + setCurrentPage(1); // Reset to first page when searching + }} />
- - - {setVisible(false)}} onOk={()=>{}} orgTreeData={[]}/> +
+
{ + return `共${total}条数据`; + }, + }} + rowSelection={{ + selectedRowKeys, + onChange: onSelectChange, + }} + scroll={{ x: 'max-content', y: 55 * 12 }} + /> + + + + { + setVisible(false); + }} + onOk={() => {}} + orgTreeData={orgTreeData} + /> + { + setCurrentUserInfo({ + recordData: {}, + visible: false, + }); + }} + onOk={() => {}} + /> + { + setEidtPassword({ + recordData: {}, + visible: false, + }); + }} + onOk={() => {}} + /> ); }; diff --git a/web-fe/src/pages/userList/mod/eidtUser/index.tsx b/web-fe/src/pages/userList/mod/eidtUser/index.tsx new file mode 100644 index 0000000..9136453 --- /dev/null +++ b/web-fe/src/pages/userList/mod/eidtUser/index.tsx @@ -0,0 +1,209 @@ +// index.tsx +import { + Button, + Form, + Input, + message, + Modal, + Radio, + Select, + TreeSelect, +} from 'antd'; +import React, { useEffect } from 'react'; +import { ModalBaseNode, OrganizationNode } from '../../contast'; + +const { Option } = Select; + +interface UserEditModalProps { + // visible: boolean; + orgTreeData: OrganizationNode[]; + onCancel: () => void; + onOk: (values: any) => void; + confirmLoading?: boolean; + currentUserInfo?: ModalBaseNode; + selectedOrg?: string; +} + +const UserEditModal: React.FC = ({ + orgTreeData, + onCancel, + onOk, + confirmLoading = false, + currentUserInfo, +}) => { + const { recordData, visible, selectedOrg } = currentUserInfo || {}; + const { user_id } = recordData || {}; + const [form] = Form.useForm(); + + useEffect(() => { + const initialValues = { user_group_id: [selectedOrg], status: 1 }; + form.setFieldsValue(initialValues); + }, [visible, form,recordData, selectedOrg]); + + const handleOk = async () => { + try { + const values = await form.validateFields(); + onOk(values); + } catch (error) { + message.error('请检查表单字段'); + } + }; + + const validateMessages = { + required: '${label} is required!', + 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}', + }, + }; + + return ( + + + + + } + > +
+ + + + + + + + + + + + + + + 启用 + 禁用 + + + + + + + + + + + + + + {/* 电话号码 */} + + + + {/* 邮箱 */} + + + + +
+ ); +}; + +export default UserEditModal; diff --git a/web-fe/src/pages/userList/mod/group/index.less b/web-fe/src/pages/userList/mod/group/index.less deleted file mode 100644 index e69de29..0000000 diff --git a/web-fe/src/pages/userList/mod/group/index.tsx b/web-fe/src/pages/userList/mod/group/index.tsx index 07bc560..b7a81ca 100644 --- a/web-fe/src/pages/userList/mod/group/index.tsx +++ b/web-fe/src/pages/userList/mod/group/index.tsx @@ -1,31 +1,38 @@ // d:\VDI\vdi\web-fe\src\pages\userList\mod\group\index.tsx -import React from 'react'; -import { Modal, Form, Input, Select } from 'antd'; -import type { DataNode } from 'antd/es/tree'; +import { Button, Form, Input, Modal, TreeSelect } from 'antd'; +import React, { useEffect } from 'react'; +// import type { DataNode } from 'antd/es/tree'; +import { OrganizationNode } from '../../contast'; interface CreatGroupProps { visible: boolean; + selectedOrg: string; onCancel: () => void; onOk: (values: any) => void; - orgTreeData: DataNode[]; + orgTreeData: OrganizationNode[]; } const CreatGroup: React.FC = (props) => { - const { visible, onCancel, onOk, orgTreeData } = props; + const { visible, onCancel, onOk, orgTreeData, selectedOrg } = props; const [form] = Form.useForm(); - // Flatten tree data for parent group selection - const flattenTreeData = (data: DataNode[], result: { value: string; label: string }[] = []) => { - data.forEach(item => { - result.push({ value: item.key as string, label: item.title as string }); - if (item.children) { - flattenTreeData(item.children, result); - } - }); - return result; - }; + useEffect(() => { + const initialValues = { parent_id: [selectedOrg] }; + form.setFieldsValue(initialValues); + }, [visible, form, selectedOrg]); - const parentGroupOptions = flattenTreeData(orgTreeData); + // 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(); @@ -49,32 +56,54 @@ const CreatGroup: React.FC = (props) => { onCancel={handleCancel} okText="确认" cancelText="取消" + centered={true} + destroyOnHidden={true} + footer={ +
+ + +
+ } > -
- + - - - - - + + + + + + + ); }; -export default CreatGroup; \ No newline at end of file +export default CreatGroup; diff --git a/web-fe/src/pages/userList/mod/passwordEdit/index.tsx b/web-fe/src/pages/userList/mod/passwordEdit/index.tsx new file mode 100644 index 0000000..3b4617a --- /dev/null +++ b/web-fe/src/pages/userList/mod/passwordEdit/index.tsx @@ -0,0 +1,114 @@ +// index.tsx +import { Button, Form, Input, message, Modal } from 'antd'; +import React, { useEffect } from 'react'; + +interface PasswordResetModalProps { + visible: boolean; + onCancel: () => void; + onOk: (values: { password: string }) => void; + confirmLoading?: boolean; + username?: string; +} + +const PasswordResetModal: React.FC = ({ + visible, + onCancel, + onOk, + confirmLoading = false, + username, +}) => { + const [form] = Form.useForm(); + + useEffect(() => { + if (!visible) { + form.resetFields(); + } + }, [visible, form]); + + const handleOk = async () => { + try { + const values = await form.validateFields(); + onOk(values); + } catch (error) { + message.error('请检查表单字段'); + } + }; + + // Password validation rule + const passwordValidator = (_: any, value: string) => { + if (!value) { + return Promise.reject('请输入新密码'); + } + + if (value.length < 6) { + return Promise.reject('密码长度至少6位'); + } + + const hasNumber = /\d/.test(value); + const hasLetter = /[a-zA-Z]/.test(value); + // const hasSpecialChar = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(value); + + if (!hasNumber) { + return Promise.reject('密码必须包含数字'); + } + + if (!hasLetter) { + return Promise.reject('密码必须包含字母'); + } + + // if (!hasSpecialChar) { + // return Promise.reject('密码必须包含特殊字符'); + // } + + return Promise.resolve(); + }; + + return ( + + + + + } + > +
+ + + + +
+ ); +}; + +export default PasswordResetModal; diff --git a/web-fe/src/services/userList/index.ts b/web-fe/src/services/userList/index.ts new file mode 100644 index 0000000..fd5558d --- /dev/null +++ b/web-fe/src/services/userList/index.ts @@ -0,0 +1,89 @@ +import { request } from '@umijs/max'; + +// 新建用户分组 +export async function addUserGroup(body?: APIS.UserInfoVO) { + return request('/api/v1/user', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + }); +} +// 获取用户分组 +export async function getUserGroup(body?: APIS.UserInfoVO) { + return request('/api/v1/user', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + }); +} + +// 删除用户分组 +export async function deleteUserGroup(body?: APIS.UserInfoVO) { + return request('/api/v1/user', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + }); +} + +// 查询用户分组 +export async function getUserList(body?: APIS.UserInfoVO) { + return request('/api/v1/user', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + }); +} + +// 新建用户 +export async function addUser(body?: APIS.UserInfoVO) { + return request('/api/v1/user', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + }); +} + +// 编辑用户 +export async function editUser(body?: APIS.UserInfoVO) { + return request('/api/v1/user', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + }); +} + +//获取某个用户信息 +export async function getUserById(body?: APIS.UserInfoVO) { + return request('/api/v1/user', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + }); +} + +//删除用户 +export async function deleteUser(body?: APIS.UserInfoVO) { + return request('/api/v1/user', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + }); +} + diff --git a/web-fe/src/services/userList/typings.d.ts b/web-fe/src/services/userList/typings.d.ts new file mode 100644 index 0000000..2c8ac02 --- /dev/null +++ b/web-fe/src/services/userList/typings.d.ts @@ -0,0 +1,68 @@ +/* eslint-disable */ +// 该文件由 OneAPI 自动生成,请勿手动修改! + +declare namespace APIS { + interface PageInfo { + /** +1 */ + current?: number; + pageSize?: number; + total?: number; + list?: Array>; + } + + interface PageInfo_UserInfo_ { + /** +1 */ + current?: number; + pageSize?: number; + total?: number; + list?: Array; + } + + interface Result { + success?: boolean; + errorMessage?: string; + data?: Record; + } + + interface Result_PageInfo_UserInfo__ { + success?: boolean; + errorMessage?: string; + data?: PageInfo_UserInfo_; + } + + interface Result_UserInfo_ { + success?: boolean; + errorMessage?: string; + data?: UserInfo; + } + + interface Result_string_ { + success?: boolean; + errorMessage?: string; + data?: string; + } + + type UserGenderEnum = 'MALE' | 'FEMALE'; + + interface UserInfo { + id?: string; + name?: string; + /** nick */ + nickName?: string; + /** email */ + email?: string; + gender?: UserGenderEnum; + } + + interface UserInfoVO { + name?: string; + /** nick */ + nickName?: string; + /** email */ + email?: string; + } + + type definitions_0 = null; +} diff --git a/web-fe/yarn.lock b/web-fe/yarn.lock index acf0dc9..e6c39b7 100644 --- a/web-fe/yarn.lock +++ b/web-fe/yarn.lock @@ -1898,7 +1898,7 @@ "@types/spark-md5@^3.0.5": version "3.0.5" - resolved "http://10.208.10.36:8080/repository/npm/@types/spark-md5/-/spark-md5-3.0.5.tgz#eddec8639217e518c26e9e221ff56bf5f5f5c900" + resolved "https://registry.npmmirror.com/@types/spark-md5/-/spark-md5-3.0.5.tgz#eddec8639217e518c26e9e221ff56bf5f5f5c900" integrity sha512-lWf05dnD42DLVKQJZrDHtWFidcLrHuip01CtnC2/S6AMhX4t9ZlEUj4iuRlAnts0PQk7KESOqKxeGE/b6sIPGg== "@types/stylis@^4.0.2": @@ -9278,7 +9278,7 @@ source-map@^0.7.3, source-map@^0.7.4: spark-md5@^3.0.2: version "3.0.2" - resolved "http://10.208.10.36:8080/repository/npm/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc" + resolved "https://registry.npmmirror.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc" integrity sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw== spdx-correct@^3.0.0: @@ -9412,7 +9412,16 @@ string-convert@^0.2.0: resolved "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -9502,7 +9511,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -10102,7 +10118,7 @@ utils-merge@1.0.1: uuid@^11.1.0: version "11.1.0" - resolved "http://10.208.10.36:8080/repository/npm/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912" + resolved "https://registry.npmmirror.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912" integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A== v8-compile-cache@^2.3.0: @@ -10260,7 +10276,16 @@ word-wrap@^1.2.5: resolved "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==