feat(前端):绑定用户和镜像

master
shaot 2025-08-07 18:15:11 +08:00
parent c166b50e9c
commit 6ef08e4404
24 changed files with 1233 additions and 257 deletions

View File

@ -4,6 +4,7 @@ import { Tree } from 'antd';
import type { DataNode, TreeProps } from 'antd/es/tree';
interface CustomTreeProps extends Omit<TreeProps, 'treeData'> {
// treeData: User.OrganizationNode[];
treeData: any[];
titleField: string;
keyField: string;

View File

@ -1,20 +0,0 @@
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;
}

View File

@ -7,35 +7,50 @@ import {
PlusOutlined,
TeamOutlined,
} from '@ant-design/icons';
import { Button, Input, message, Popconfirm, Popover, Table } from 'antd';
import {
Button,
Input,
message,
Popconfirm,
Popover,
Table,
Tooltip,
} 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 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<OrganizationNode[]>([]);
const [selectedOrg, setSelectedOrg] = useState<string>('');
const [orgTreeData, setOrgTreeData] = useState<User.OrganizationNode[]>([]);
const [selectedOrg, setSelectedOrg] = useState<number>();
// 用户列表
const [dataSource, setDataSource] = useState<Device[]>([]);
const [dataSource, setDataSource] = useState<Termial.TermialItem[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [searchText, setSearchText] = useState<string>('');
const [selectedRowKeys, setSelectedRowKeys] = useState<[]>([]);
// 分页参数
const [currentPage, setCurrentPage] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10);
const [pageSize, setPageSize] = useState<number>(20);
const [total, setTotal] = useState<number>(0);
// 添加分组
const [visible, setVisible] = useState<boolean>(false);
// 编辑用户
const [currentUserInfo, setCurrentUserInfo] = useState<any>({
visible: false,
});
const [bindUserDta, setBindUserData] = useState<any>({
visible: false,
});
// 添加分组
const [visible, setVisible] = useState<boolean>(false);
const [bindImageDta, setBindImageDta] = useState<any>({
visible: false,
});
// 获取用户分组组织树
useEffect(() => {
@ -48,73 +63,64 @@ const UserListPage: React.FC = () => {
}, [selectedOrg, currentPage, pageSize]);
const getGroupList = () => {
const mockOrgData: OrganizationNode[] = [
const mockOrgData: User.OrganizationNode[] = [
{
name: 'Headquartershdy',
id: '1',
name: 'Headquarters',
id: 1,
children: [
{
name: 'HR Department',
id: '2',
id: 2,
},
{
name: 'IT Department',
id: '3',
id: 3,
children: [
{
name: 'Frontend Team',
id: '4',
id: 4,
},
{
name: 'Backend Team',
id: '5',
id: 5,
},
],
},
{
name: 'Finance Department',
id: '6',
id: 6,
},
],
},
];
setSelectedOrg(mockOrgData[0].id as string);
setSelectedOrg(mockOrgData[0].id as number);
setOrgTreeData(mockOrgData);
};
const getDataSource = () => {
setLoading(true);
setTimeout(() => {
const startIndex = (currentPage - 1) * pageSize;
const mockUsers: Device[] = Array.from(
const mockUsers: Termial.TermialItem[] = 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',
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?: Device) => {
const { user_id } = device || {};
const onDelete = (device?: Termial.TermialItem) => {
const { id } = device || {};
const payload = {
ids: user_id ? [user_id] : selectedRowKeys,
ids: id ? [id] : selectedRowKeys,
};
deleteUser(payload as any).then((res) => {
console.log('res=====', res);
@ -127,14 +133,14 @@ const UserListPage: React.FC = () => {
});
};
const handleEditUserInfo = (device: Device) => {
const handleEditUserInfo = (device: Termial.TermialItem) => {
setCurrentUserInfo({
recordData: { ...device },
visible: true,
});
};
const columns: ColumnsType<Device> = [
const columns: ColumnsType<Termial.TermialItem> = [
{
title: '显示名称',
dataIndex: 'username',
@ -142,18 +148,18 @@ const UserListPage: React.FC = () => {
},
{
title: '终端名称',
dataIndex: 'loginName',
key: 'loginName',
dataIndex: 'device_name',
key: 'device_name',
},
{
title: '终端分组',
dataIndex: 'userGroup',
key: 'userGroup',
dataIndex: 'device_group_name',
key: 'device_group_name',
},
{
title: 'IP地址',
dataIndex: 'sex',
key: 'sex',
dataIndex: 'ip_addr',
key: 'ip_addr',
},
{
title: '终端标识',
@ -164,12 +170,22 @@ const UserListPage: React.FC = () => {
title: '终端类型',
dataIndex: 'device_type',
key: 'device_type',
render: (text) => {
return (
<div>
<Tooltip placement="top" title={text}>
{text}
</Tooltip>
</div>
);
},
},
{
title: '操作',
key: 'actions',
align: 'center',
width: 150,
fixed: 'right',
render: (_, record) => (
<div style={{ display: 'flex', alignItems: 'center' }}>
<Button type="link" onClick={() => handleEditUserInfo(record)}>
@ -190,12 +206,25 @@ const UserListPage: React.FC = () => {
<Button type="link"></Button>
</Popconfirm>
<div>
<Button type="link" onClick={() => {}}>
<Button
type="link"
onClick={() => {
setBindUserData({
recordData: record,
visible: true,
});
}}
>
</Button>
</div>
<div>
<Button type="link" onClick={() => {}}>
<Button
type="link"
onClick={() => {
setBindImageDta({ recordData: record, visible: true });
}}
>
</Button>
</div>
@ -214,7 +243,7 @@ const UserListPage: React.FC = () => {
const onOrgSelect = (selectedKeys: React.Key[]) => {
if (selectedKeys.length > 0) {
setSelectedOrg(selectedKeys[0] as string);
setSelectedOrg(selectedKeys[0] as number);
setCurrentPage(1);
}
};
@ -254,7 +283,7 @@ const UserListPage: React.FC = () => {
// onCancel={cancel}
okText="删除"
cancelText="取消"
disabled={selectedOrg === ''}
disabled={!selectedOrg}
>
<Button
type="text"
@ -277,7 +306,7 @@ const UserListPage: React.FC = () => {
childrenField="children"
defaultExpandAll
onSelect={onOrgSelect}
selectedKeys={[selectedOrg]}
selectedKeys={selectedOrg ? [selectedOrg] : []}
icon={<TeamOutlined style={{ fontSize: '15px' }} />}
/>
</div>
@ -296,7 +325,7 @@ const UserListPage: React.FC = () => {
title=""
description="删除操作不可逆,请确认是否删除?"
onConfirm={() => onDelete()}
// onCancel={cancel}
placement="bottom"
okText="删除"
cancelText="取消"
disabled={selectedRowKeys.length === 0}
@ -375,6 +404,31 @@ const UserListPage: React.FC = () => {
}}
onOk={() => {}}
/>
{bindUserDta.visible && (
<BindUserModal
dataDetial={bindUserDta}
orgTreeData={orgTreeData}
onCancel={() => {
setBindUserData({
recordData: {},
visible: false,
});
}}
onOk={() => {}}
/>
)}
{bindImageDta.visible && (
<BindImage
dataDetial={bindImageDta}
onCancel={() => {
setBindImageDta({
recordData: {},
visible: false,
});
}}
onOk={() => {}}
/>
)}
</div>
);
};

View File

@ -0,0 +1,17 @@
.content_wrap {
width: 100%;
height: 100%;
.search_wrap {
display: flex;
justify-content: flex-end;
margin-bottom: 5px;
padding-bottom: 5px;
}
:global {
:where(.css-dev-only-do-not-override-1vjf2v5).ant-pagination
.ant-pagination-total-text {
position: absolute;
left: 5px;
}
}
}

View File

@ -0,0 +1,181 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import type { PopoverProps } from 'antd';
import { Alert, Button, Popconfirm, Popover, Space } from 'antd';
import React, { useEffect, useState } from 'react';
import CustomTable from '../selectedTable/table';
import SelectConponents from './table';
interface SelectedTableProps {
placement?: PopoverProps['placement'];
selectedOrg?: number;
onCancel?: () => void;
onOk?: (values: any) => void;
orgTreeData?: User.OrganizationNode[];
value?: any;
onChange?: (values: any) => void;
}
const SelectedTable: React.FC<SelectedTableProps> = (props) => {
const {
placement = 'bottomLeft',
orgTreeData,
value: formValue,
onChange: onBindUserChange,
} = props;
const [open, setOpen] = useState<boolean>(false);
const [tableData, setTableData] = useState<any[]>([]);
//镜像选择
const [selectedRowKeys, setSelectedRowKeys] = useState<[]>([]);
const [selectedRows, setSelectedRows] = useState<any[]>([]);
// 已绑定用户列表选择
const [bindTableKeys, setBindTableKeys] = useState<any[]>([]);
useEffect(() => {
console.log('formValue==========', formValue);
const userList: any = [];
const userId: any = [];
if (formValue && formValue.length > 0) {
formValue.forEach((item: any) => {
userList.push(item);
userId.push(item.id);
});
setSelectedRowKeys(userId);
setSelectedRows(userList);
}
}, [formValue]);
const onHnadleCancel = () => {
setOpen(false);
setSelectedRowKeys([]);
setSelectedRows([]);
};
const onHandleOk = () => {
const list: any[] = [];
// 用户
(selectedRows || []).forEach((item) => {
list.push({
id: item.id,
name: item.name,
type: 1,
});
});
setTableData(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 || {};
const newData = tableData.filter((item) => item.id !== id);
setTableData(newData);
} else {
// 批量删除
const newData = tableData.filter(
(item) => !bindTableKeys.includes(item.id),
);
setTableData(newData);
setSelectedRowKeys([]);
setSelectedRows([]);
}
};
const accessPopover = (
<div style={{ width: '600px' }}>
{/* <Alert
message="可切换选择多个不同类型进行绑定"
type="info"
showIcon
closeIcon
/> */}
<SelectConponents
treeData={orgTreeData}
onUserTableSelect={onUserTableSelect}
selectedRowKeys={selectedRowKeys}
/>
<div
style={{
borderTop: '1px solid #e8e8e8',
marginTop: '10px',
paddingTop: '10px',
display: 'flex',
justifyContent: 'center',
}}
>
<Button
onClick={onHandleOk}
style={{ marginRight: '30px' }}
type={'primary'}
>
</Button>
<Button onClick={onHnadleCancel}></Button>
</div>
</div>
);
return (
<>
<Space style={{ marginBottom: '10px' }}>
<Popover
content={accessPopover}
title=""
trigger="click"
open={open}
// onOpenChange={setOpen}
placement={placement}
>
<div>
<Button
onClick={() => {
setOpen(true);
}}
style={{ marginRight: '15px' }}
>
</Button>
<Popconfirm
title=""
description="删除操作不可逆,请确认是否删除?"
onConfirm={() => handleDelete()}
disabled={bindTableKeys.length === 0}
okText="删除"
cancelText="取消"
>
<Button disabled={bindTableKeys.length === 0}></Button>
</Popconfirm>
</div>
</Popover>
</Space>
<CustomTable
dataSource={tableData}
onDelete={handleDelete}
scrollY={400}
rowKey="id"
pagination={false}
rowSelection={{
bindTableKeys,
onChange: onSelectChange,
}}
/>
</>
);
};
export default SelectedTable;

View File

@ -0,0 +1,102 @@
import { Input, Table } from 'antd';
import React, { useEffect, useState } from 'react';
import styles from './index.less';
interface TableProps {
treeData?: User.OrganizationNode[];
selectedRowKeys?: any[];
onUserTableSelect?: (keys: any[], row: any[]) => void;
}
interface DataType {
key: React.Key;
name: string;
age: number;
address: string;
}
const TablePage: React.FC<TableProps> = ({
onUserTableSelect,
selectedRowKeys,
}) => {
const [data, setData] = useState<any[]>([]);
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);
}, []);
const columns: TableProps<DataType>['columns'] = [
{
title: 'Name',
dataIndex: 'name',
sorter: (a, b) => a.name.localeCompare(b.name),
},
];
const handleSearch = (value: string) => {
setSearchText(value);
setCurrentPage(1);
};
const handlePageChange = (page: number, size: number) => {
setCurrentPage(page);
setPageSize(size);
};
const handlePageSizeChange = (current: number, size: number) => {
setCurrentPage(1);
setPageSize(size);
};
return (
<div className={styles.content_wrap}>
<div className={styles.search_wrap}>
<Input.Search
placeholder="镜像名称..."
onSearch={handleSearch}
enterButton
allowClear
style={{ width: '200px', marginLeft: '10px' }}
/>
</div>
<Table
// rowSelection={rowSelection}
columns={columns}
dataSource={data}
rowKey="id"
rowSelection={{
selectedRowKeys,
preserveSelectedRowKeys: true,
onChange: onUserTableSelect,
}}
pagination={{
current: currentPage,
pageSize: pageSize,
total: total,
simple: true,
onChange: handlePageChange,
onShowSizeChange: handlePageSizeChange,
showSizeChanger: false,
showQuickJumper: false,
}}
scroll={{ x: 'max-content', y: 300 }}
/>
</div>
);
};
export default TablePage;

View File

@ -0,0 +1,4 @@
.model_content {
width: 100%;
height: 650px;
}

View File

@ -0,0 +1,95 @@
// index.tsx
import { Button, Form, Input, message, Modal } from 'antd';
import React, { useEffect } from 'react';
import SelectedTable from '../ImageSelectedTable/index';
import styles from './index.less';
interface UserEditModalProps {
onCancel: () => void;
onOk: (values: any) => void;
confirmLoading?: boolean;
dataDetial?: Termial.ModalBaseNode;
selectedOrg?: number;
}
const BindUserModal: React.FC<UserEditModalProps> = ({
onCancel,
onOk,
confirmLoading = false,
dataDetial,
}) => {
const { recordData, visible, selectedOrg } = dataDetial || {};
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();
console.log('values=====', values);
onOk(values);
} catch (error) {
message.error('请检查表单字段');
}
};
return (
<Modal
title={'绑定镜像'}
open={visible}
onCancel={onCancel}
onOk={handleOk}
confirmLoading={confirmLoading}
width={1200}
maskClosable={false}
centered={true}
destroyOnHidden={true}
cancelText="取消"
okText="确定"
footer={
<div style={{ display: 'flex', justifyContent: 'right' }}>
<Button
type="primary"
onClick={handleOk}
style={{ marginRight: '20px' }}
>
</Button>
<Button onClick={onCancel} style={{ marginRight: '20px' }}>
</Button>
</div>
}
>
<div className={styles.model_content}>
<Form
form={form}
labelCol={{ span: 4 }}
wrapperCol={{ span: 19 }}
layout="horizontal"
style={{ paddingTop: '20px', paddingBottom: '20px' }}
>
<Form.Item
name="user_list"
label="选择镜像"
rules={[{ required: true, message: '请输入终端型号' }]}
>
<SelectedTable />
</Form.Item>
<Form.Item
name="description"
label="描述内容"
rules={[{ required: false, message: '请输入描述内容' }]}
>
<Input.TextArea rows={4} />
</Form.Item>
</Form>
</div>
</Modal>
);
};
export default BindUserModal;

View File

@ -0,0 +1,4 @@
.model_content {
width: 100%;
height: 650px;
}

View File

@ -0,0 +1,98 @@
// index.tsx
import { Button, Form, Input, message, Modal } from 'antd';
import React, { useEffect } from 'react';
import SelectedTable from '../selectedTable';
import styles from './index.less';
interface UserEditModalProps {
// visible: boolean;
orgTreeData?: User.OrganizationNode[];
onCancel: () => void;
onOk: (values: any) => void;
confirmLoading?: boolean;
dataDetial?: Termial.ModalBaseNode;
selectedOrg?: number;
}
const BindUserModal: React.FC<UserEditModalProps> = ({
orgTreeData,
onCancel,
onOk,
confirmLoading = false,
dataDetial,
}) => {
const { recordData, visible, selectedOrg } = dataDetial || {};
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();
console.log("values=====",values)
onOk(values);
} catch (error) {
message.error('请检查表单字段');
}
};
return (
<Modal
title={'绑定用户'}
open={visible}
onCancel={onCancel}
onOk={handleOk}
confirmLoading={confirmLoading}
width={1200}
maskClosable={false}
centered={true}
destroyOnHidden={true}
cancelText="取消"
okText="确定"
footer={
<div style={{ display: 'flex', justifyContent: 'right' }}>
<Button
type="primary"
onClick={handleOk}
style={{ marginRight: '20px' }}
>
</Button>
<Button onClick={onCancel} style={{ marginRight: '20px' }}>
</Button>
</div>
}
>
<div className={styles.model_content}>
<Form
form={form}
labelCol={{ span: 4 }}
wrapperCol={{ span: 19 }}
layout="horizontal"
style={{ paddingTop: '20px', paddingBottom: '20px' }}
>
<Form.Item
name="user_list"
label="选择用户"
rules={[{ required: true, message: '请输入终端型号' }]}
>
<SelectedTable orgTreeData={orgTreeData} />
</Form.Item>
<Form.Item
name="description"
label="描述内容"
rules={[{ required: false, message: '请输入描述内容' }]}
>
<Input.TextArea rows={4} />
</Form.Item>
</Form>
</div>
</Modal>
);
};
export default BindUserModal;

View File

@ -6,22 +6,18 @@ import {
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[];
orgTreeData: User.OrganizationNode[];
onCancel: () => void;
onOk: (values: any) => void;
confirmLoading?: boolean;
currentUserInfo?: ModalBaseNode;
selectedOrg?: string;
currentUserInfo?: Termial.ModalBaseNode;
selectedOrg?: number;
}
const UserEditModal: React.FC<UserEditModalProps> = ({

View File

@ -1,15 +1,12 @@
// 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;
selectedOrg?: number;
onCancel: () => void;
onOk: (values: any) => void;
orgTreeData: OrganizationNode[];
orgTreeData: User.OrganizationNode[];
}
const CreatGroup: React.FC<CreatGroupProps> = (props) => {

View File

@ -0,0 +1,17 @@
.content_wrap {
width: 100%;
height: 100%;
.search_wrap{
display: flex;
justify-content: flex-end;
margin-bottom: 5px;
padding-bottom: 5px;
}
:global {
:where(.css-dev-only-do-not-override-1vjf2v5).ant-pagination
.ant-pagination-total-text {
position: absolute;
left: 5px;
}
}
}

View File

@ -0,0 +1,113 @@
import { Input, Table, TreeSelect } from 'antd';
import React, { useEffect, useState } from 'react';
import styles from './index.less';
interface TableProps {
treeData?: User.OrganizationNode[];
selectedRowKeys?: any[];
onUserTableSelect?: (keys: any[], row: any[]) => void;
}
interface DataType {
key: React.Key;
name: string;
age: number;
address: string;
}
const TablePage: React.FC<TableProps> = ({
treeData,
onUserTableSelect,
selectedRowKeys,
}) => {
const [data, setData] = useState<any[]>([]);
// const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
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);
}, []);
const columns: TableProps<DataType>['columns'] = [
{
title: 'Name',
dataIndex: 'name',
sorter: (a, b) => a.name.localeCompare(b.name),
},
];
const handleSearch = (value: string) => {
setSearchText(value);
setCurrentPage(1);
};
const handlePageChange = (page: number, size: number) => {
setCurrentPage(page);
setPageSize(size);
};
const handlePageSizeChange = (current: number, size: number) => {
setCurrentPage(1);
setPageSize(size);
};
return (
<div className={styles.content_wrap}>
<div className={styles.search_wrap}>
<TreeSelect
style={{ width: '300px' }}
showSearch
allowClear={false}
treeDefaultExpandAll
placeholder="请选择终端分组"
treeData={treeData}
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
/>
<Input.Search
placeholder="用户名"
onSearch={handleSearch}
enterButton
allowClear
style={{ width: '200px', marginLeft: '10px' }}
/>
</div>
<Table
// rowSelection={rowSelection}
columns={columns}
dataSource={data}
rowKey="id"
rowSelection={{
selectedRowKeys,
preserveSelectedRowKeys: true,
onChange: onUserTableSelect,
}}
pagination={{
current: currentPage,
pageSize: pageSize,
total: total,
simple: true,
onChange: handlePageChange,
onShowSizeChange: handlePageSizeChange,
showSizeChanger: false,
showQuickJumper: false,
}}
scroll={{ x: 'max-content', y: 300 }}
/>
</div>
);
};
export default TablePage;

View File

@ -0,0 +1,232 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import CustomTree from '@/pages/components/customTree';
import { TeamOutlined } from '@ant-design/icons';
import type { PopoverProps } from 'antd';
import { Alert, Button, Popconfirm, Popover, Space, Tabs } from 'antd';
import React, { useEffect, useState } from 'react';
import SelectConponents from '../selectConponents';
import CustomTable from './table';
interface SelectedTableProps {
placement?: PopoverProps['placement'];
selectedOrg?: number;
onCancel?: () => void;
onOk?: (values: any) => void;
orgTreeData?: User.OrganizationNode[];
value?: any;
onChange?: (values: any) => void;
}
const SelectedTable: React.FC<SelectedTableProps> = (props) => {
const {
placement = 'bottomLeft',
orgTreeData,
value: formValue,
onChange: onBindUserChange,
} = props;
const [open, setOpen] = useState<boolean>(false);
const [tableData, setTableData] = useState<any[]>([]);
//用户选择列表
const [selectedRowKeys, setSelectedRowKeys] = useState<[]>([]);
const [selectedRows, setSelectedRows] = useState<any[]>([]);
// 用户组
const [checkedKeys, setCheckedKeys] = useState<any[]>([]);
const [checkedRow, setCheckedRow] = useState<any[]>([]);
// 已绑定用户列表选择
const [bindTableKeys, setBindTableKeys] = useState<any[]>([]);
useEffect(() => {
console.log('formValue==========', formValue);
const userList: any = [];
const groupList: any = [];
const userId: any = [];
const groupId: any = [];
if (formValue && formValue.length > 0) {
formValue.forEach((item: any) => {
if (item.type === 1) {
userList.push(item);
userId.push(item.id);
} else {
groupList.push(item);
groupId.push(item.id);
}
});
setSelectedRowKeys(userId);
setSelectedRows(userList);
setCheckedKeys(groupId);
setCheckedRow(groupList);
}
}, [formValue]);
// const onOrgSelect = (selectedKeys: React.Key[], info: any) => {};
const onCheckChange = (keys: any[], info: any) => {
const { checkedNodes } = info;
console.log('info=========', info);
setCheckedKeys(keys);
setCheckedRow(checkedNodes);
};
const onHnadleCancel = () => {
setOpen(false);
setSelectedRowKeys([]);
setSelectedRows([]);
setCheckedKeys([]);
setCheckedRow([]);
};
const onHandleOk = () => {
const list: any[] = [];
// 用户
(selectedRows || []).forEach((item) => {
list.push({
id: item.id,
name: item.name,
type: 1,
});
});
// 用户组
checkedRow.forEach((item) => {
list.push({
id: item.id,
name: item.name,
type: 2,
});
});
setTableData(list);
onBindUserChange(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 || {};
const newData = tableData.filter((item) => item.id !== id);
setTableData(newData);
} else {
// 批量删除
const newData = tableData.filter(
(item) => !bindTableKeys.includes(item.id),
);
setTableData(newData);
setSelectedRowKeys([]);
setSelectedRows([]);
}
};
const accessPopover = (
<div style={{ width: '600px' }}>
<Alert
message="可切换选择多个不同类型进行绑定"
type="info"
showIcon
closeIcon
/>
<Tabs>
<Tabs.TabPane tab="分组" key="1">
<CustomTree
checkable={true}
multiple={true}
treeData={orgTreeData}
titleField="name"
keyField="id"
childrenField="children"
defaultExpandAll
onCheck={onCheckChange}
checkedKeys={checkedKeys}
icon={<TeamOutlined style={{ fontSize: '15px' }} />}
/>
</Tabs.TabPane>
<Tabs.TabPane tab="用户" key="2">
<SelectConponents
treeData={orgTreeData}
onUserTableSelect={onUserTableSelect}
selectedRowKeys={selectedRowKeys}
/>
</Tabs.TabPane>
</Tabs>
<div
style={{
borderTop: '1px solid #e8e8e8',
marginTop: '10px',
paddingTop: '10px',
display: 'flex',
justifyContent: 'center',
}}
>
<Button
onClick={onHandleOk}
style={{ marginRight: '30px' }}
type={'primary'}
>
</Button>
<Button onClick={onHnadleCancel}></Button>
</div>
</div>
);
return (
<>
<Space style={{ marginBottom: '10px' }}>
<Popover
content={accessPopover}
title=""
trigger="click"
open={open}
// onOpenChange={setOpen}
placement={placement}
>
<div>
<Button
onClick={() => {
setOpen(true);
}}
style={{ marginRight: '15px' }}
>
</Button>
<Popconfirm
title=""
description="删除操作不可逆,请确认是否删除?"
onConfirm={()=>handleDelete()}
disabled={bindTableKeys.length === 0}
okText="删除"
cancelText="取消"
>
<Button disabled={bindTableKeys.length === 0}></Button>
</Popconfirm>
</div>
</Popover>
</Space>
<CustomTable
dataSource={tableData}
onDelete={handleDelete}
scrollY={400}
rowKey="id"
pagination={false}
rowSelection={{
bindTableKeys,
onChange: onSelectChange,
}}
/>
</>
);
};
export default SelectedTable;

View File

@ -0,0 +1,80 @@
import { Button, Table } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import React from 'react';
interface DeletableTableProps {
dataSource: any[];
isSerial?: boolean;
isAction?: boolean;
scrollY: number;
onDelete?: (recod: any) => void;
rowKey?: string;
rowSelection?: any;
pagination?:any;
}
const DeletableTable: React.FC<DeletableTableProps> = (props) => {
const {
dataSource,
onDelete,
isSerial = true,
isAction = true,
scrollY = 300,
...restProps
} = props;
console.log("datasource=====",dataSource);
const getColumns = (): ColumnsType<any> => {
const columns: ColumnsType<any> = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
render: (text) => (
<span>{text == 1 ? '用户' : text == 2 ? '用户组' : ''}</span>
),
},
];
if (isSerial) {
columns.unshift({
title: '序号',
dataIndex: 'order',
key: 'order',
width: 80,
render: (_, record, index) => <span>{index + 1}</span>,
});
}
if (isAction) {
columns.push({
title: '操作',
key: 'action',
render: (_, record) => (
<Button type="link" danger onClick={() => onDelete(record)}>
</Button>
),
});
}
return columns;
};
return (
<div>
<Table
columns={getColumns()}
dataSource={dataSource}
scroll={{ y: scrollY }}
{...restProps}
/>
</div>
);
};
export default DeletableTable;

View File

@ -1,20 +0,0 @@
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;
}

View File

@ -5,18 +5,17 @@ import { DeleteOutlined, PlusOutlined, TeamOutlined } from '@ant-design/icons';
import { Button, Input, Popconfirm, Table, message } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import React, { useEffect, useState } from 'react';
import { OrganizationNode, User } from './contast';
import styles from './index.less';
import UserEditModal from './mod/eidtUser';
import CreatGroup from './mod/group';
import PasswordResetModal from './mod/passwordEdit';
const UserListPage: React.FC = () => {
const [orgTreeData, setOrgTreeData] = useState<OrganizationNode[]>([]);
const [selectedOrg, setSelectedOrg] = useState<string>('');
const [orgTreeData, setOrgTreeData] = useState<User.OrganizationNode[]>([]);
const [selectedOrg, setSelectedOrg] = useState<number>();
// 用户列表
const [users, setUsers] = useState<User[]>([]);
const [users, setUsers] = useState<User.UserItem[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [searchText, setSearchText] = useState<string>('');
const [selectedRowKeys, setSelectedRowKeys] = useState<[]>([]);
@ -48,37 +47,37 @@ const UserListPage: React.FC = () => {
}, [selectedOrg, currentPage, pageSize]);
const getUserGroupList = () => {
const mockOrgData: OrganizationNode[] = [
const mockOrgData: User.OrganizationNode[] = [
{
name: 'Headquarters',
id: '1',
id: 1,
children: [
{
name: 'HR Department',
id: '2',
id: 2,
},
{
name: 'IT Department',
id: '3',
id: 3,
children: [
{
name: 'Frontend Team',
id: '4',
id: 4,
},
{
name: 'Backend Team',
id: '5',
id: 5,
},
],
},
{
name: 'Finance Department',
id: '6',
id: 6,
},
],
},
];
setSelectedOrg(mockOrgData[0].id as string);
setSelectedOrg(mockOrgData[0].id as number);
setOrgTreeData(mockOrgData);
};
@ -86,22 +85,11 @@ const UserListPage: React.FC = () => {
setLoading(true);
setTimeout(() => {
const startIndex = (currentPage - 1) * pageSize;
const mockUsers: User[] = Array.from(
const mockUsers: User.UserItem[] = 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',
user_id: startIndex + index + 1,
user_name: 'string',
}),
);
@ -111,7 +99,7 @@ const UserListPage: React.FC = () => {
}, 300);
};
const onDelete = (user?: User) => {
const onDelete = (user?: User.UserItem) => {
const { user_id } = user || {};
const payload = {
ids: user_id ? [user_id] : selectedRowKeys,
@ -127,7 +115,7 @@ const UserListPage: React.FC = () => {
});
};
const handlePassword = (user: User) => {
const handlePassword = (user: User.UserItem) => {
setEidtPassword({
recordData: { ...user },
visible: true,
@ -140,23 +128,23 @@ const UserListPage: React.FC = () => {
});
};
const handleEditUserInfo = (user: User) => {
const handleEditUserInfo = (user: User.UserItem) => {
setCurrentUserInfo({
recordData: { ...user },
visible: true,
});
};
const columns: ColumnsType<User> = [
const columns: ColumnsType<User.UserItem> = [
{
title: '登录名',
dataIndex: 'username',
key: 'username',
dataIndex: 'user_id',
key: 'user_id',
},
{
title: '用户姓名',
dataIndex: 'loginName',
key: 'loginName',
dataIndex: 'user_name',
key: 'user_name',
},
{
title: '用户分组',
@ -182,6 +170,8 @@ const UserListPage: React.FC = () => {
title: '操作',
key: 'actions',
align: 'center',
width: 150,
fixed: 'right',
render: (_, record) => (
<div style={{ display: 'flex' }}>
<Button type="link" onClick={() => handlePassword(record)}>
@ -207,7 +197,7 @@ const UserListPage: React.FC = () => {
const onOrgSelect = (selectedKeys: React.Key[]) => {
if (selectedKeys.length > 0) {
setSelectedOrg(selectedKeys[0] as string);
setSelectedOrg(selectedKeys[0] as number);
setCurrentPage(1);
}
};
@ -247,7 +237,7 @@ const UserListPage: React.FC = () => {
// onCancel={cancel}
okText="删除"
cancelText="取消"
disabled={selectedOrg === ''}
disabled={!selectedOrg}
>
<Button
type="text"
@ -270,7 +260,7 @@ const UserListPage: React.FC = () => {
childrenField="children"
defaultExpandAll
onSelect={onOrgSelect}
selectedKeys={[selectedOrg]}
selectedKeys={selectedOrg ? [selectedOrg] : []}
icon={<TeamOutlined style={{ fontSize: '15px' }} />}
/>
</div>

View File

@ -10,18 +10,16 @@ import {
TreeSelect,
} from 'antd';
import React, { useEffect } from 'react';
import { ModalBaseNode, OrganizationNode } from '../../contast';
const { Option } = Select;
interface UserEditModalProps {
// visible: boolean;
orgTreeData: OrganizationNode[];
orgTreeData: User.OrganizationNode[];
onCancel: () => void;
onOk: (values: any) => void;
confirmLoading?: boolean;
currentUserInfo?: ModalBaseNode;
selectedOrg?: string;
currentUserInfo?: User.UserModalBaseNode;
selectedOrg?: number;
}
const UserEditModal: React.FC<UserEditModalProps> = ({

View File

@ -1,15 +1,13 @@
// 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;
selectedOrg?: number;
onCancel: () => void;
onOk: (values: any) => void;
orgTreeData: OrganizationNode[];
orgTreeData: User.OrganizationNode[];
}
const CreatGroup: React.FC<CreatGroupProps> = (props) => {

View File

@ -1,89 +1,64 @@
import { request } from '@umijs/max';
// 新建用户分组
export async function addUserGroup(body?: APIS.UserInfoVO) {
return request<APIS.Result_UserInfo_>('/api/v1/user', {
export async function addUserGroup(body?: any) {
return request<any>('/api/v1/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
});
}
// 获取用户分组
export async function getUserGroup(body?: APIS.UserInfoVO) {
return request<APIS.Result_UserInfo_>('/api/v1/user', {
export async function getUserGroup(body?: any) {
return request<any>('/api/v1/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
});
}
// 删除用户分组
export async function deleteUserGroup(body?: APIS.UserInfoVO) {
return request<APIS.Result_UserInfo_>('/api/v1/user', {
export async function deleteUserGroup(body?: any) {
return request<any>('/api/v1/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
});
}
// 查询用户分组
export async function getUserList(body?: APIS.UserInfoVO) {
return request<APIS.Result_UserInfo_>('/api/v1/user', {
export async function getUserList(body?: any) {
return request<any>('/api/v1/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
});
}
// 新建用户
export async function addUser(body?: APIS.UserInfoVO) {
return request<APIS.Result_UserInfo_>('/api/v1/user', {
export async function addUser(body?: any) {
return request<any>('/api/v1/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
});
}
// 编辑用户
export async function editUser(body?: APIS.UserInfoVO) {
return request<APIS.Result_UserInfo_>('/api/v1/user', {
export async function editUser(body?: any) {
return request<any>('/api/v1/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
});
}
//获取某个用户信息
export async function getUserById(body?: APIS.UserInfoVO) {
return request<APIS.Result_UserInfo_>('/api/v1/user', {
export async function getUserById(body?: any) {
return request<any>('/api/v1/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
});
}
//删除用户
export async function deleteUser(body?: APIS.UserInfoVO) {
return request<APIS.Result_UserInfo_>('/api/v1/user', {
export async function deleteUser(body?: any) {
return request<any>('/api/v1/user', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
data: body,
});
}

View File

@ -1,68 +0,0 @@
/* eslint-disable */
// 该文件由 OneAPI 自动生成,请勿手动修改!
declare namespace APIS {
interface PageInfo {
/**
1 */
current?: number;
pageSize?: number;
total?: number;
list?: Array<Record<string, any>>;
}
interface PageInfo_UserInfo_ {
/**
1 */
current?: number;
pageSize?: number;
total?: number;
list?: Array<UserInfo>;
}
interface Result {
success?: boolean;
errorMessage?: string;
data?: Record<string, any>;
}
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;
}

74
web-fe/src/types/terminal.d.ts vendored 100644
View File

@ -0,0 +1,74 @@
declare namespace Termial {
interface TermialGroup {
device_group_id: number;
device_group_name: string;
parent_device_group_id?: number;
children?: TermialGroup[];
}
interface TermialItem {
id: number;
device_id: string;
device_name: string;
device_group_id: number;
device_group_name: string;
device_type: number;
ip_addr?: string;
mac_addr?: string;
model?: string;
description?: string;
}
interface TermialUser {
id: number;
device_id: string;
device_group_id: number;
user_id: number;
restore_type?: number;
description?: string;
}
interface ModalBaseNode {
visible: boolean;
recordData: any;
selectedOrg: number;
}
interface TermialImage {
id: number;
device_id: string;
device_group_id: number;
image_id: number;
restore_type?: number;
description?: string;
}
interface TableParams {
pagination: {
current: number;
pageSize: number;
};
filters?: Record<string, any>;
sortOrder?: 'ascend' | 'descend' | null;
sortField?: string | number | null;
keywords?: string; // 添加关键词字段
}
// interface DeviceParams{
// device_id: string;
// }
interface Termial_ListInfo {
error_code: string;
message: string;
data: {
paging: {
total: number;
total_num?: number;
page_num: number;
page_size: number;
};
data: TermialItem[];
};
}
}

58
web-fe/src/types/user.d.ts vendored 100644
View File

@ -0,0 +1,58 @@
declare namespace User {
interface UserModalBaseNode {
visible: boolean;
recordData: any;
selectedOrg: number;
}
interface OrganizationNode {
id: number;
name: string;
parent_id?: number;
parent_name?: string;
children?: OrganizationNode[];
}
interface UserItem {
user_id?: number;
user_group_id?: number;
user_group_name?: number;
user_name?: string;
password?: string;
birthday?: string;
cell_phone?: string;
email?: string;
gender?: number;
identity_no?: string;
priority?: number;
user_type?: number;
status?: number;
description?: string;
}
interface TableParams {
pagination: {
current: number;
pageSize: number;
};
filters?: Record<string, any>;
sortOrder?: 'ascend' | 'descend' | null;
sortField?: string | number | null;
keywords?: string; // 添加关键词字段
}
interface User_ListInfo {
error_code: string;
message: string;
data: {
paging: {
total: number;
total_num?: number;
page_num: number;
page_size: number;
};
data: UserItem[];
};
}
}