feat(前端):终端列表

master
shaot 2025-08-11 17:19:58 +08:00
parent af9e040f01
commit e1fc19aeab
18 changed files with 671 additions and 319 deletions

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

View File

@ -15,6 +15,7 @@
"@umijs/max": "^4.4.11",
"antd": "^5.4.0",
"dayjs": "^1.11.13",
"moment": "^2.30.1",
"spark-md5": "^3.0.2",
"uuid": "^11.1.0"
},

View File

@ -23,9 +23,9 @@ export const GENDER_MAP = {
// priority
export const PRIORITY_MAP = {
1: '',
2: '',
3: '',
1: '一级',
2: '二级',
3: '三级',
} as const;
export const USER_TYPE_OPTIONS = [
@ -42,3 +42,9 @@ export const GENDER_OPTIONS = [
{ value: 1, label: '女' },
{ value: 2, label: '男' },
];
export const PRIOPRITY_OPTIONS = [
{ value: 1, label: '一级' },
{ value: 2, label: '二级' },
{ value: 3, label: '三级' },
];

View File

@ -16,6 +16,7 @@ import {
message,
Popconfirm,
Popover,
Spin,
Table,
Tooltip,
} from 'antd';
@ -34,6 +35,7 @@ const UserListPage: React.FC = () => {
const [searchText, setSearchText] = useState<string>('');
const [selectedRowKeys, setSelectedRowKeys] = useState<[]>([]);
const [orgSearchText, setOrgSearchText] = useState<string>('');
const [spinning, setSpinning] = useState<boolean>(false);
// 分页参数
const [currentPage, setCurrentPage] = useState<number>(1);
@ -42,7 +44,7 @@ const UserListPage: React.FC = () => {
// 添加分组
const [visible, setVisible] = useState<boolean>(false);
// 编辑用户
const [currentUserInfo, setCurrentUserInfo] = useState<any>({
const [currentDeviceInfo, setCurrentDeviceInfo] = useState<any>({
visible: false,
});
const [bindUserDta, setBindUserData] = useState<any>({
@ -55,7 +57,7 @@ const UserListPage: React.FC = () => {
// 获取用户分组组织树
useEffect(() => {
// getGroupList();
getGroupList();
}, []);
const filterTreeData = (
@ -91,28 +93,34 @@ const UserListPage: React.FC = () => {
// 获取用户列表数据
useEffect(() => {
// if (selectedOrg) {
// getDataSource();
// }
getDataSource();
}, [searchText, selectedOrg, currentPage, pageSize]);
const getGroupList = async () => {
setSpinning(true);
const params = {
type: 2,
};
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);
console.log('result=====', result);
const { code, data = [] } = result || {};
if (code === ERROR_CODE) {
if (data.length > 0) {
const { children = [] } = data[0] || {};
setOrgTreeData(children);
// if (children.length > 0) {
// setSelectedOrg(children[0].id);
// }
}
setSpinning(false);
} else {
setSpinning(false);
message.error(result.message || '获取终端分组失败');
}
} catch (err) {
message.error('获取终端分组失败');
setLoading(false);
setSpinning(false);
}
};
@ -121,15 +129,21 @@ const UserListPage: React.FC = () => {
page_size: pageSize,
page_num: currentPage,
};
if (selectedOrg) {
params.device_group_id = selectedOrg;
}
if (searchText) {
params.keywords = searchText;
params.device_name = 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);
const result: any = await getTerminalList(params);
console.log('result=====', 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 || '获取终端列表失败');
@ -145,11 +159,11 @@ const UserListPage: React.FC = () => {
try {
const { id } = device || {};
const payload = {
ids: id ? [id] : selectedRowKeys,
ids: id ? id : selectedRowKeys,
};
const res = await deleteDevice(payload);
const { error_code } = res || {};
if (error_code === ERROR_CODE) {
const res: any = await deleteDevice(payload);
const { code } = res || {};
if (code === ERROR_CODE) {
message.success('终端删除成功');
setSelectedRowKeys([]);
getDataSource();
@ -159,8 +173,8 @@ const UserListPage: React.FC = () => {
}
};
const handleEditUserInfo = (device: Termial.TermialItem) => {
setCurrentUserInfo({
const handleEditInfo = (device: Termial.TermialItem) => {
setCurrentDeviceInfo({
recordData: { ...device },
visible: true,
});
@ -186,9 +200,9 @@ const UserListPage: React.FC = () => {
},
},
{
title: '终端分组',
dataIndex: 'device_group_name',
key: 'device_group_name',
title: '序列号',
dataIndex: 'device_id',
key: 'device_id',
width: 220,
align: 'center',
render: (text) => {
@ -196,20 +210,10 @@ const UserListPage: React.FC = () => {
},
},
{
title: 'IP地址',
dataIndex: 'ip_addr',
key: 'ip_addr',
width: 150,
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
},
{
title: '终端标识',
dataIndex: 'mac_addr',
key: 'mac_addr',
ellipsis: true,
title: '终端分组',
dataIndex: 'device_group_name',
key: 'device_group_name',
width: 220,
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
@ -226,6 +230,46 @@ const UserListPage: React.FC = () => {
return <Tooltip>{DEVICE_TYPE_MAP[key] || '--'}</Tooltip>;
},
},
{
title: '型号',
dataIndex: 'model',
key: 'model',
width: 150,
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
},
{
title: 'IP地址',
dataIndex: 'ip_addr',
key: 'ip_addr',
width: 150,
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
},
{
title: 'MAC地址',
dataIndex: 'mac_addr',
key: 'mac_addr',
ellipsis: true,
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
},
{
title: '备注',
dataIndex: 'description',
key: 'description',
ellipsis: true,
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
},
{
title: '操作',
key: 'actions',
@ -234,7 +278,7 @@ const UserListPage: React.FC = () => {
fixed: 'right',
render: (_, record) => (
<div style={{ display: 'flex', alignItems: 'center' }}>
<Button type="link" onClick={() => handleEditUserInfo(record)}>
<Button type="link" onClick={() => handleEditInfo(record)}>
</Button>
<Popover
@ -315,8 +359,8 @@ const UserListPage: React.FC = () => {
id: selectedOrg,
};
const res = await deleteUserGroup(params);
const { error_code } = res || {};
if (error_code === ERROR_CODE) {
const { code } = res || {};
if (code === ERROR_CODE) {
message.success('分组删除成功');
getGroupList();
}
@ -326,6 +370,26 @@ const UserListPage: React.FC = () => {
}
};
const onSaveGroup = () => {
getGroupList();
};
const onDeviceSave = () => {
setCurrentDeviceInfo({
recordData: {},
visible: false,
});
getDataSource();
};
const onBindUserSave = () => {
setBindUserData({
recordData: {},
visible: false,
});
getDataSource();
};
return (
<div className={styles.user_content}>
<div className={styles.left_content}>
@ -361,16 +425,18 @@ const UserListPage: React.FC = () => {
/>
</div>
<div className={styles.tree_box}>
<CustomTree
treeData={filteredOrgTreeData}
titleField="name"
keyField="id"
childrenField="children"
defaultExpandAll={true}
onSelect={onOrgSelect}
selectedKeys={selectedOrg ? [selectedOrg] : []}
icon={<TeamOutlined style={{ fontSize: '15px' }} />}
/>
<Spin spinning={spinning} delay={100}>
<CustomTree
treeData={filteredOrgTreeData}
titleField="name"
keyField="id"
childrenField="children"
defaultExpandAll={true}
onSelect={onOrgSelect}
selectedKeys={selectedOrg ? [selectedOrg] : []}
icon={<TeamOutlined style={{ fontSize: '15px' }} />}
/>
</Spin>
</div>
</div>
<div className={styles.right_content}>
@ -383,7 +449,7 @@ const UserListPage: React.FC = () => {
}}
>
<div>
<Popconfirm
{/* <Popconfirm
title=""
description="删除操作不可逆,请确认是否删除?"
onConfirm={() => onDelete()}
@ -398,7 +464,7 @@ const UserListPage: React.FC = () => {
>
</Button>
</Popconfirm>
</Popconfirm> */}
<Button style={{ marginRight: '8px' }} onClick={getDataSource}>
</Button>
@ -424,10 +490,10 @@ const UserListPage: React.FC = () => {
dataSource={dataSource}
loading={loading}
rowKey="id"
rowSelection={{
selectedRowKeys,
onChange: onSelectChange,
}}
// rowSelection={{
// selectedRowKeys,
// onChange: onSelectChange,
// }}
pagination={{
current: currentPage,
pageSize: pageSize,
@ -446,40 +512,47 @@ const UserListPage: React.FC = () => {
</div>
</div>
</div>
<CreatGroup
visible={visible}
type={2}
title="新增终端分组"
onCancel={() => {
setVisible(false);
}}
selectedOrg={selectedOrg}
onOk={() => {}}
orgTreeData={orgTreeData}
/>
<EditModal
selectedOrg={selectedOrg}
orgTreeData={orgTreeData}
currentUserInfo={currentUserInfo}
onCancel={() => {
setCurrentUserInfo({
recordData: {},
visible: false,
});
}}
onOk={() => {}}
/>
{visible && (
<CreatGroup
visible={visible}
type={2}
title="新增终端分组"
onCancel={() => {
setVisible(false);
}}
selectedOrg={selectedOrg}
onOk={() => {
onSaveGroup();
}}
orgTreeData={orgTreeData}
/>
)}
{currentDeviceInfo.visible && (
<EditModal
selectedOrg={selectedOrg}
orgTreeData={orgTreeData}
currentDeviceInfo={currentDeviceInfo}
onCancel={() => {
setCurrentDeviceInfo({
recordData: {},
visible: false,
});
}}
onOk={() => {
onDeviceSave();
}}
/>
)}
{bindUserDta.visible && (
<BindUserModal
dataDetial={bindUserDta}
orgTreeData={orgTreeData}
onCancel={() => {
setBindUserData({
recordData: {},
visible: false,
});
}}
onOk={() => {}}
onOk={() => {onBindUserSave()}}
/>
)}
{bindImageDta.visible && (

View File

@ -8,10 +8,8 @@ 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;
}
@ -19,7 +17,6 @@ interface SelectedTableProps {
const SelectedTable: React.FC<SelectedTableProps> = (props) => {
const {
placement = 'bottomLeft',
orgTreeData,
value: formValue,
onChange: onBindImageChange,
} = props;
@ -95,7 +92,6 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
const accessPopover = (
<div style={{ width: '600px' }}>
<SelectConponents
treeData={orgTreeData}
onUserTableSelect={onUserTableSelect}
selectedRowKeys={selectedRowKeys}
/>

View File

@ -1,14 +1,12 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import { IMAGES_TYPE_MAP } from '@/constants/images.constants';
import { ERROR_CODE } from '@/constants/constants';
import { getImagesList } from '@/services/images';
import { Input, Table, message,Tooltip } from 'antd';
import { getImageList } from '@/services/terminal';
import { Input, message, Table } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import React, { useEffect, useState } from 'react';
import styles from './index.less';
interface TableProps {
treeData?: User.OrganizationNode[];
selectedRowKeys?: any[];
onUserTableSelect?: (keys: any[], row: any[]) => void;
}
@ -38,10 +36,13 @@ const TablePage: React.FC<TableProps> = ({
}
setLoading(true);
try {
const imagesRes = await getImagesList(params);
if (imagesRes.error_code === ERROR_CODE) {
setData(imagesRes.data.data || []);
setTotal(imagesRes.data.paging.total || 0);
const imagesRes: any = await getImageList(params);
console.log('imagesRes=========', imagesRes);
const { code, data } = imagesRes || {};
const { data: list = [], total = 0 } = data || {};
if (code === ERROR_CODE) {
setData(list);
setTotal(total);
setLoading(false);
} else {
message.error(imagesRes.message || '获取镜像列表失败');
@ -66,16 +67,6 @@ const TablePage: React.FC<TableProps> = ({
dataIndex: 'image_name',
render: (text: any) => <span>{text || ''}</span>,
},
{
title: '桌面类型',
dataIndex: 'image_type',
key: 'image_type',
width: 200,
render: (text: number) => {
const key = text as keyof typeof IMAGES_TYPE_MAP;
return <Tooltip>{IMAGES_TYPE_MAP[key] || '--'}</Tooltip>;
},
},
];
const handleSearch = (value: string) => {

View File

@ -1,5 +1,7 @@
// index.tsx
import { Button, Form, Input, message, Modal } from 'antd';
import { ERROR_CODE } from '@/constants/constants';
import { getBindImageList } from '@/services/terminal';
import { Button, Form, message, Modal } from 'antd';
import React, { useEffect } from 'react';
import SelectedTable from '../ImageSelectedTable/index';
import styles from './index.less';
@ -19,11 +21,20 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
dataDetial,
}) => {
const { recordData, visible, selectedOrg } = dataDetial || {};
const { id: device_id } = recordData || {};
const [form] = Form.useForm();
useEffect(() => {
// const initialValues = { user_group_id: [selectedOrg], status: 1 };
// form.setFieldsValue(initialValues);
const params = { device_id: device_id };
getBindImageList(params).then((res: any) => {
const { code, data = [] } = res || {};
if (code === ERROR_CODE) {
if (data && data.length) {
const initialValues = { image_list: data };
form.setFieldsValue(initialValues);
}
}
});
}, [visible, form, recordData, selectedOrg]);
const handleOk = async () => {
@ -73,19 +84,19 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
style={{ paddingTop: '20px', paddingBottom: '20px' }}
>
<Form.Item
name="user_list"
name="image_list"
label="选择镜像"
rules={[{ required: true, message: '请输入终端型号' }]}
>
<SelectedTable />
</Form.Item>
<Form.Item
{/* <Form.Item
name="description"
label="描述内容"
rules={[{ required: false, message: '请输入描述内容' }]}
>
<Input.TextArea rows={4} />
</Form.Item>
</Form.Item> */}
</Form>
</div>
</Modal>

View File

@ -1,39 +1,129 @@
/* eslint-disable array-callback-return */
// index.tsx
import { Button, Form, Input, message, Modal } from 'antd';
import React, { useEffect } from 'react';
import { ERROR_CODE } from '@/constants/constants';
import { bindUserAdd, getBindUserList } from '@/services/terminal';
import { getGroupTree } from '@/services/userList';
import { Button, Form, message, Modal } from 'antd';
import React, { useEffect, useState } from 'react';
import SelectedTable from '../selectedTable';
import styles from './index.less';
interface UserEditModalProps {
// visible: boolean;
orgTreeData?: User.OrganizationNode[];
// orgTreeData?: User.OrganizationNode[];
onCancel: () => void;
onOk: (values: any) => void;
onOk: (values?: any) => void;
confirmLoading?: boolean;
dataDetial?: Termial.ModalBaseNode;
selectedOrg?: number;
}
const BindUserModal: React.FC<UserEditModalProps> = ({
orgTreeData,
// orgTreeData,
onCancel,
onOk,
confirmLoading = false,
dataDetial,
}) => {
const { recordData, visible, selectedOrg } = dataDetial || {};
const { recordData, visible } = dataDetial || {};
const { device_id, device_group_id } = recordData || {};
const [orgTreeData, setOrgTreeData] = useState<User.OrganizationNode[]>([]);
const [form] = Form.useForm();
const getGroupList = async () => {
const params = {
type: 1,
};
try {
const result = await getGroupTree(params);
console.log('result=====', result);
const { code, data = [] } = result || {};
if (code === ERROR_CODE) {
if (data.length > 0) {
const { children = [] } = data[0] || {};
setOrgTreeData(children);
}
} else {
message.error(result.message || '获取终端分组失败');
}
} catch (err) {
message.error('获取终端分组失败');
}
};
useEffect(() => {
// const initialValues = { user_group_id: [selectedOrg], status: 1 };
// form.setFieldsValue(initialValues);
}, [visible, form, recordData, selectedOrg]);
getGroupList();
}, [visible]);
useEffect(() => {
if (!device_id) return;
const payload = { device_id: device_id };
getBindUserList(payload).then((res: any) => {
console.log('res========', res);
const { code, data = [] } = res || {};
if (code === ERROR_CODE) {
if (data && data.length > 0) {
const list: any[] = [];
data.map((item: any) => {
const { type, user_id, user_name, user_group_id, user_group_name } =
item || {};
if (type === 1) {
list.push({ record_id: user_id, name: user_name, ...item });
} else {
list.push({
record_id: user_group_id,
name: user_group_name,
...item,
});
}
});
const initialValues = { user_list: list, status: 1 };
form.setFieldsValue(initialValues);
}
}
});
}, [visible, form, recordData]);
const handleOk = async () => {
try {
const values = await form.validateFields();
console.log("values=====",values)
onOk(values);
const { user_list = [] } = values || {};
const list: any[] = [];
if (user_list && user_list.length > 0) {
user_list.forEach((item: any) => {
const { record_id, type, id } = item || {};
if (type === 1) {
// 用户
list.push({
user_id: record_id,
id: id,
device_id,
device_group_id,
type: type,
});
} else {
//用户分组
list.push({
user_group_id: record_id,
id: id,
device_id: device_id,
device_group_id,
type: type,
});
}
});
const payload = {
data: list,
};
bindUserAdd(payload)
.then(() => {
message.success('绑定成功');
onOk();
})
.catch(() => {});
}
console.log('list=====', list);
onOk(list);
} catch (error) {
message.error('请检查表单字段');
}
@ -78,17 +168,17 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
<Form.Item
name="user_list"
label="选择用户"
rules={[{ required: true, message: '请输入终端型号' }]}
rules={[{ required: true, message: '请选择绑定用户' }]}
>
<SelectedTable orgTreeData={orgTreeData} />
</Form.Item>
<Form.Item
{/* <Form.Item
name="description"
label="描述内容"
rules={[{ required: false, message: '请输入描述内容' }]}
>
<Input.TextArea rows={4} />
</Form.Item>
</Form.Item> */}
</Form>
</div>
</Modal>

View File

@ -1,16 +1,15 @@
// index.tsx
import { DEVICE_TYPE_OPTIONS,ERROR_CODE } from '@/constants/constants';
import { } from '@/constants/constants';
import { saveDevice } from '@/services/terminal';
import { DEVICE_TYPE_OPTIONS, ERROR_CODE } from '@/constants/constants';
import { getDevieDetial, saveDevice } from '@/services/terminal';
import { Button, Form, Input, message, Modal, Select, TreeSelect } from 'antd';
import React, { useEffect } from 'react';
interface UserEditModalProps {
orgTreeData: User.OrganizationNode[];
onCancel: () => void;
onOk?: (values: any) => void;
onOk?: (values?: any) => void;
confirmLoading?: boolean;
currentUserInfo?: Termial.ModalBaseNode;
currentDeviceInfo?: Termial.ModalBaseNode;
selectedOrg?: number;
}
@ -19,27 +18,34 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
onCancel,
onOk,
confirmLoading = false,
currentUserInfo,
currentDeviceInfo,
}) => {
const { recordData, visible, selectedOrg } = currentUserInfo || {};
const { user_id } = recordData || {};
const { recordData, visible, selectedOrg } = currentDeviceInfo || {};
const { id, device_name } = recordData || {};
const [form] = Form.useForm();
useEffect(() => {
const initialValues = { user_group_id: [selectedOrg], status: 1 };
form.setFieldsValue(initialValues);
const params = { id: id };
getDevieDetial(params).then((res: any) => {
console.log('res=======', res);
const { code, data } = res || {};
if (code === ERROR_CODE) {
const initialValues = { ...data };
form.setFieldsValue(initialValues);
}
});
}, [visible, form, recordData, selectedOrg]);
const handleOk = async () => {
try {
const values = await form.validateFields();
const params = { ...values, user_id };
const res = await saveDevice(params);
const { error_code } = res || {};
if (error_code === ERROR_CODE) {
const params = { ...values, id };
const res: any = await saveDevice(params);
const { code } = res || {};
if (code === ERROR_CODE) {
message.success('保存成功');
onOk();
}
} catch (error) {
message.error('保存失败');
}
@ -58,7 +64,7 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
return (
<Modal
title={user_id ? '编辑用户信息' : '新增用户'}
title={id ? `${device_name}终端信息修改` : '新增终端'}
open={visible}
onCancel={onCancel}
onOk={handleOk}
@ -124,24 +130,19 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
/>
</Form.Item>
<Form.Item
name="device_typ"
name="device_type"
label="终端类型"
rules={[{ required: false, message: '请输入终端型号' }]}
rules={[{ required: true, message: '请输入终端型号' }]}
>
<Select options={DEVICE_TYPE_OPTIONS} />
</Form.Item>
{/* <Form.Item
name="status"
label="认证方式"
rules={[{ required: false, message: '请选择认证方式' }]}
<Form.Item
name="model"
label="型号"
rules={[{ required: false, message: '请输入显示名称' }]}
>
<Radio.Group optionType="button">
<Radio value={1}></Radio>
<Radio value={2}></Radio>
</Radio.Group>
</Form.Item> */}
<Input />
</Form.Item>
<Form.Item
name="ip_addr"
label="IP地址"
@ -150,20 +151,12 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
<Input placeholder="请输入IP地址" />
</Form.Item>
<Form.Item
name="mac addr"
name="mac_addr"
label="MAC地址"
rules={[{ required: false, message: '请输入MAC地址' }]}
>
<Input />
</Form.Item>
{/* <Form.Item
name="终端厂商"
label="mac_addr"
rules={[{ required: true, message: '请输入终端厂商' }]}
>
<Input />
</Form.Item> */}
<Form.Item
name="description"
label="描述"

View File

@ -1,4 +1,7 @@
import { Input, Table, TreeSelect } from 'antd';
import { ERROR_CODE } from '@/constants/constants';
import { getUserList } from '@/services/userList';
import { Input, Table, Tooltip, TreeSelect, message } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import React, { useEffect, useState } from 'react';
import styles from './index.less';
@ -21,30 +24,64 @@ const TablePage: React.FC<TableProps> = ({
selectedRowKeys,
}) => {
const [data, setData] = useState<any[]>([]);
// const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [selectedOrg, setSelectedOrg] = 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}`,
});
const getDataSource = async () => {
const params: any = {
page_size: pageSize,
page_num: currentPage,
};
if (selectedOrg) {
params.user_group_id = selectedOrg;
}
setData(mockData);
setTotal(mockData.length);
}, []);
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) {
setData(list || []);
setTotal(total);
setLoading(false);
} else {
message.error(result.message || '获取用户列表失败');
setLoading(false);
}
} catch (err) {
message.error('获取用户列表失败');
}
};
const columns: TableProps<DataType>['columns'] = [
useEffect(() => {
getDataSource();
}, [selectedOrg, searchText]);
const columns: ColumnsType<User.UserItem> = [
{
title: 'Name',
dataIndex: 'name',
sorter: (a, b) => a.name.localeCompare(b.name),
title: '序号',
dataIndex: 'order',
key: 'order',
width: 80,
align: 'center',
render: (_: any, record: any, index: number) => <span>{index + 1}</span>,
},
{
title: '用户名',
dataIndex: 'user_name',
key: 'user_name',
align: 'center',
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
},
];
@ -63,16 +100,21 @@ const TablePage: React.FC<TableProps> = ({
setPageSize(size);
};
const onTreeChange = (newValue: any) => {
setSelectedOrg(newValue);
};
return (
<div className={styles.content_wrap}>
<div className={styles.search_wrap}>
<TreeSelect
style={{ width: '300px' }}
showSearch
allowClear={false}
allowClear={true}
treeDefaultExpandAll
placeholder="请选择终端分组"
placeholder="请选择用户分组"
treeData={treeData}
onChange={onTreeChange}
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
/>
<Input.Search
@ -85,9 +127,9 @@ const TablePage: React.FC<TableProps> = ({
</div>
<Table
// rowSelection={rowSelection}
columns={columns}
dataSource={data}
loading={loading}
rowKey="id"
rowSelection={{
selectedRowKeys,

View File

@ -47,12 +47,13 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
formValue.forEach((item: any) => {
if (item.type === 1) {
userList.push(item);
userId.push(item.id);
userId.push(item.record_id);
} else {
groupList.push(item);
groupId.push(item.id);
groupId.push(item.record_id);
}
});
setTableData(formValue);
setSelectedRowKeys(userId);
setSelectedRows(userList);
setCheckedKeys(groupId);
@ -63,10 +64,10 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
// const onOrgSelect = (selectedKeys: React.Key[], info: any) => {};
const onCheckChange = (keys: any[], info: any) => {
const { checkedNodes } = info;
// const { checkedNodes } = info;
console.log('info=========', info);
setCheckedKeys(keys);
setCheckedRow(checkedNodes);
setCheckedRow(info);
};
const onHnadleCancel = () => {
@ -82,15 +83,15 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
// 用户
(selectedRows || []).forEach((item) => {
list.push({
id: item.id,
name: item.name,
record_id: item.id,
name: item.user_name,
type: 1,
});
});
// 用户组
checkedRow.forEach((item) => {
(checkedRow || []).forEach((item) => {
list.push({
id: item.id,
record_id: item.id,
name: item.name,
type: 2,
});
@ -116,13 +117,13 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
console.log('record=====handleDelete', record);
if (record) {
// 单个删除
const { id } = record || {};
const newData = tableData.filter((item) => item.id !== id);
const { record_id } = record || {};
const newData = tableData.filter((item) => item.record_id !== record_id);
setTableData(newData);
} else {
// 批量删除
const newData = tableData.filter(
(item) => !bindTableKeys.includes(item.id),
(item) => !bindTableKeys.includes(item.record_id),
);
setTableData(newData);
setSelectedRowKeys([]);
@ -131,39 +132,39 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
};
const getColumns = (): ColumnsType<any> => {
const columns: ColumnsType<any> = [
{
title: '序号',
dataIndex: 'order',
key: 'order',
width: 80,
render: (_, record, index) => <span>{index + 1}</span>,
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
render: (text) => (
<span>{text == 1 ? '用户' : text == 2 ? '用户组' : ''}</span>
),
},
{
title: '操作',
key: 'action',
render: (_, record) => (
<Button type="link" danger onClick={() => handleDelete(record)}>
</Button>
),
}
];
return columns;
};
const columns: ColumnsType<any> = [
{
title: '序号',
dataIndex: 'order',
key: 'order',
width: 80,
render: (_, record, index) => <span>{index + 1}</span>,
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
render: (text) => (
<span>{text == 1 ? '用户' : text == 2 ? '用户组' : ''}</span>
),
},
{
title: '操作',
key: 'action',
render: (_, record) => (
<Button type="link" onClick={() => handleDelete(record)}>
</Button>
),
},
];
return columns;
};
const accessPopover = (
<div style={{ width: '600px' }}>
@ -178,6 +179,7 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
<CustomTree
checkable={true}
multiple={true}
checkStrictly={true}
treeData={orgTreeData}
titleField="name"
keyField="id"
@ -240,12 +242,12 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
<Popconfirm
title=""
description="删除操作不可逆,请确认是否删除?"
onConfirm={()=>handleDelete()}
onConfirm={() => handleDelete()}
disabled={bindTableKeys.length === 0}
okText="删除"
cancelText="取消"
>
<Button disabled={bindTableKeys.length === 0}></Button>
<Button disabled={bindTableKeys.length === 0}></Button>
</Popconfirm>
</div>
</Popover>
@ -254,9 +256,9 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
dataSource={tableData}
onDelete={handleDelete}
scrollY={400}
rowKey="id"
rowKey="record_id"
pagination={false}
columns={getColumns()}
columns={getColumns()}
rowSelection={{
bindTableKeys,
onChange: onSelectChange,

View File

@ -62,13 +62,13 @@ const UserListPage: React.FC = () => {
// 获取用户列表数据
useEffect(() => {
getDataSource();
}, [searchText,selectedOrg, currentPage, pageSize]);
}, [searchText, selectedOrg, currentPage, pageSize]);
const getUserGroupList = async () => {
setSpinning(true);
const params = {
type: 1,
};
setSpinning(true);
try {
const result = await getGroupTree(params);
console.log('result=====', result);
@ -144,24 +144,24 @@ const UserListPage: React.FC = () => {
setTotal(total);
setLoading(false);
} else {
message.error(result.message || '获取终端列表失败');
message.error(result.message || '获取用户列表失败');
setLoading(false);
}
} catch (err) {
message.error('获取终端列表失败');
message.error('获取用户列表失败');
setLoading(false);
}
};
const onDelete = (user?: User.UserItem) => {
const { user_id } = user || {};
const { id } = user || {};
const payload = {
ids: user_id ? [user_id] : selectedRowKeys,
id: id ? id : selectedRowKeys,
};
deleteUser(payload as any).then((res) => {
console.log('res=====', res);
const { success } = res || {};
if (success) {
const { code } = res || {};
if (code === ERROR_CODE) {
message.success('删除成功');
setSelectedRowKeys([]);
getDataSource();
@ -267,7 +267,7 @@ const UserListPage: React.FC = () => {
key: 'cell_phone',
width: 150,
align: 'center',
render: (text:any) => {
render: (text: any) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
},
@ -277,7 +277,7 @@ const UserListPage: React.FC = () => {
key: 'birthday',
width: 150,
align: 'center',
render: (text:any) => {
render: (text: any) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
},
@ -287,7 +287,7 @@ const UserListPage: React.FC = () => {
key: 'identity_no',
width: 150,
align: 'center',
render: (text:any) => {
render: (text: any) => {
return <Tooltip>{text || '--'}</Tooltip>;
},
},
@ -299,9 +299,9 @@ const UserListPage: React.FC = () => {
fixed: 'right',
render: (_, record) => (
<div style={{ display: 'flex' }}>
<Button type="link" onClick={() => handlePassword(record)}>
{/* <Button type="link" onClick={() => handlePassword(record)}>
</Button>
</Button> */}
<Button type="link" onClick={() => handleEditUserInfo(record)}>
</Button>
@ -346,6 +346,14 @@ const UserListPage: React.FC = () => {
getUserGroupList();
};
const onSaveUserCallback = () => {
setCurrentUserInfo({
recordData: {},
visible: false,
});
getDataSource();
};
const onDeleteGroup = async () => {
if (selectedOrg) {
try {
@ -440,7 +448,7 @@ const UserListPage: React.FC = () => {
>
</Button>
<Popconfirm
{/* <Popconfirm
title=""
description="删除操作不可逆,请确认是否删除?"
onConfirm={() => onDelete()}
@ -452,10 +460,11 @@ const UserListPage: React.FC = () => {
<Button
disabled={selectedRowKeys.length === 0}
style={{ marginRight: '8px' }}
icon={<DeleteOutlined />}
>
</Button>
</Popconfirm>
</Popconfirm> */}
<Button style={{ marginRight: '8px' }} onClick={getDataSource}>
</Button>
@ -494,38 +503,44 @@ const UserListPage: React.FC = () => {
return `${total}条数据`;
},
}}
rowSelection={{
selectedRowKeys,
onChange: onSelectChange,
}}
// rowSelection={{
// selectedRowKeys,
// onChange: onSelectChange,
// }}
scroll={{ x: 'max-content', y: 55 * 12 }}
/>
</div>
</div>
</div>
<CreatGroup
visible={visible}
type={1}
title="新增用户分组"
selectedOrg={selectedOrg}
onCancel={() => {
setVisible(false);
}}
onOk={onSaveGroup}
orgTreeData={orgTreeData}
/>
<UserEditModal
selectedOrg={selectedOrg}
orgTreeData={orgTreeData}
currentUserInfo={currentUserInfo}
onCancel={() => {
setCurrentUserInfo({
recordData: {},
visible: false,
});
}}
onOk={() => {}}
/>
{visible && (
<CreatGroup
visible={visible}
type={1}
title="新增用户分组"
selectedOrg={selectedOrg}
onCancel={() => {
setVisible(false);
}}
onOk={onSaveGroup}
orgTreeData={orgTreeData}
/>
)}
{currentUserInfo.visible && (
<UserEditModal
selectedOrg={selectedOrg}
orgTreeData={orgTreeData}
currentUserInfo={currentUserInfo}
onCancel={() => {
setCurrentUserInfo({
recordData: {},
visible: false,
});
}}
onOk={() => {
onSaveUserCallback();
}}
/>
)}
<PasswordResetModal
visible={eidtPassword.visible}
onCancel={() => {

View File

@ -1,6 +1,10 @@
// index.tsx
import { GENDER_OPTIONS, USER_TYPE_OPTIONS } from '@/constants/constants';
import { idIDIntl } from '@ant-design/pro-components';
import {
ERROR_CODE,
GENDER_OPTIONS,
PRIOPRITY_OPTIONS,
USER_TYPE_OPTIONS,
} from '@/constants/constants';
import { addUser, editUser, getUserById } from '@/services/userList';
import {
Button,
Form,
@ -10,6 +14,7 @@ import {
Radio,
Select,
TreeSelect,
DatePicker
} from 'antd';
import React, { useEffect } from 'react';
@ -17,7 +22,7 @@ interface UserEditModalProps {
// visible: boolean;
orgTreeData: User.OrganizationNode[];
onCancel: () => void;
onOk: (values: any) => void;
onOk: (values?: any) => void;
confirmLoading?: boolean;
currentUserInfo?: User.UserModalBaseNode;
selectedOrg?: number;
@ -35,14 +40,31 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
const [form] = Form.useForm();
useEffect(() => {
const initialValues = { user_group_id: [selectedOrg], status: 1 };
form.setFieldsValue(initialValues);
if (id) {
getUserById({ id }).then((res) => {
const { data } = res;
form.setFieldsValue(data);
});
} else {
const initialValues = { user_group_id: [selectedOrg], status: 1 };
form.setFieldsValue(initialValues);
}
}, [visible, form, recordData, selectedOrg]);
const handleOk = async () => {
const values = await form.validateFields();
try {
const values = await form.validateFields();
onOk(values);
const params = { ...values };
if (id) {
params.id = id;
}
const result: any = id ? await editUser(params) : await addUser(params);
const { code } = result || {};
if (code === ERROR_CODE) {
onOk();
} else {
message.error('保存失败');
}
} catch (error) {
message.error('请检查表单字段');
}
@ -53,13 +75,41 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
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}',
},
};
// 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 (
<Modal
title={id ? '编辑用户信息' : '新增用户'}
@ -98,24 +148,23 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
validateMessages={validateMessages}
>
<Form.Item
name="loginName"
label="登录名"
rules={[
{ required: true, message: '请输入登录名' },
{ min: 3, message: '登录名至少3个字符' },
]}
>
<Input placeholder="请输入登录名" />
</Form.Item>
<Form.Item
name="userName"
name="user_name"
label="用户姓名"
rules={[{ required: true, message: '请输入用户姓名' }]}
>
<Input placeholder="请输入用户姓名" />
</Form.Item>
<Form.Item
name="password"
label="密码"
rules={[{ required: true, validator: passwordValidator }]}
help="密码必须包含数字和字母,字母区分大小写,且至少6位"
style={{ marginBottom: '30px' }}
>
<Input.Password placeholder="请输入密码" />
</Form.Item>
<Form.Item
name="user_group_id"
label="用户分组"
@ -149,6 +198,16 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
>
<Select placeholder="请选择用户类别" options={USER_TYPE_OPTIONS} />
</Form.Item>
<Form.Item name="birthday" label="出生日期" >
<DatePicker style={{width:"414px"}}/>
</Form.Item>
<Form.Item
name="priority"
label="优先级"
rules={[{ required: false, message: '请选择用户优先级' }]}
>
<Select placeholder="请选择用户类别" options={PRIOPRITY_OPTIONS} />
</Form.Item>
<Form.Item
name="gender"
label="性别"

View File

@ -6,7 +6,7 @@ import React, { useEffect, useState } from 'react';
interface CreatGroupProps {
title: string;
type:number;
type: number;
visible: boolean;
selectedOrg?: number;
onCancel: () => void;
@ -22,14 +22,14 @@ const CreatGroup: React.FC<CreatGroupProps> = (props) => {
onOk,
orgTreeData,
selectedOrg,
type
type,
} = props;
const [loading, setLoading] = useState<boolean>(false);
const [form] = Form.useForm();
useEffect(() => {
if (selectedOrg) {
const initialValues = { parent_id: [selectedOrg] };
const initialValues = { parent_id: selectedOrg };
form.setFieldsValue(initialValues);
}
}, [visible, form, selectedOrg]);
@ -49,10 +49,11 @@ const CreatGroup: React.FC<CreatGroupProps> = (props) => {
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 params: any = { name, type: type, parent_id: parent_id };
// if (parent_id) {
// params.parent_id =
// parent_id && parent_id.length > 0 ? parent_id[0] : null;
// }
const res = await addUserGroup(params);
setLoading(false);
const { code } = res || {};

View File

@ -1,27 +1,73 @@
import { request } from '@umijs/max';
const BASE_URL = '/api/v1/terminal';
const BASE_URL = '/api/nex/v1';
// 根据终端序列号查询镜像列表
export async function getTerminalList(params:any) {
// console.log('终端列表 params', params);
return request<Termial.Termial_ListInfo>(`${BASE_URL}/query/devicelist`, {
export async function getTerminalList(params: any) {
return request<Termial.Termial_ListInfo>(`${BASE_URL}/device/select/page`, {
method: 'POST',
data: params,
});
}
export async function deleteDevice(params:any) {
// console.log('终端列表 params', params);
return request<Termial.Termial_ListInfo>(`${BASE_URL}/delete/device`, {
// 根据id查询终端信息
export async function getDevieDetial(params: any) {
return request<Termial.Termial_ListInfo>(`${BASE_URL}/device/query`, {
method: 'POST',
data: params,
});
}
export async function saveDevice(params:any) {
// console.log('终端列表 params', params);
return request<Termial.Termial_ListInfo>(`${BASE_URL}/save/device`, {
//删除终端
export async function deleteDevice(params: any) {
return request<Termial.Termial_ListInfo>(`${BASE_URL}/device/delete`, {
method: 'POST',
data: params,
});
}
// 终端编辑保存
export async function saveDevice(params: any) {
return request<Termial.Termial_ListInfo>(`${BASE_URL}/device/update`, {
method: 'POST',
data: params,
});
}
// 终端绑定用户
export async function bindUserAdd(params: any) {
return request<Termial.Termial_ListInfo>(`${BASE_URL}/device/user/mapping/add`, {
method: 'POST',
data: params,
});
}
export async function getBindUserList(params: any) {
return request<Termial.Termial_ListInfo>(`${BASE_URL}/device/user/mapping/select`, {
method: 'POST',
data: params,
});
}
// 镜像列表
export async function getImageList(params: any) {
return request<Termial.Termial_ListInfo>(`${BASE_URL}/image/select/page`, {
method: 'POST',
data: params,
});
}
// 终端绑定镜像列表
export async function getBindImageList(params: any) {
return request<Termial.Termial_ListInfo>(`${BASE_URL}/device/image/mapping/select`, {
method: 'POST',
data: params,
});
}
// 终端绑定镜像列表
export async function getBindImageAdd(params: any) {
return request<Termial.Termial_ListInfo>(`${BASE_URL}/device/image/mapping/add`, {
method: 'POST',
data: params,
});

View File

@ -58,7 +58,7 @@ export async function getUserById(body?: any) {
//删除用户
export async function deleteUser(body?: any) {
return request<any>(`${BASE_URL}/user/delete`, {
method: 'DELETE',
method: 'POST',
data: body,
});
}

View File

@ -15,6 +15,7 @@ declare namespace User {
}
interface UserItem {
id?:number;
user_id?: number;
user_group_id?: number;
user_group_name?: number;

View File

@ -6665,7 +6665,7 @@ minimist-options@4.1.0:
resolved "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
moment@^2.24.0, moment@^2.29.2, moment@^2.29.4:
moment@^2.24.0, moment@^2.29.2, moment@^2.29.4, moment@^2.30.1:
version "2.30.1"
resolved "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
@ -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==
@ -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==