feat(前端):用户、终端

master
shaot 2025-08-14 14:18:36 +08:00
parent b73913d78e
commit f17de4dc33
12 changed files with 207 additions and 80 deletions

View File

@ -53,3 +53,6 @@ export const PRIOPRITY_OPTIONS = [
{ value: 2, label: '二级' },
{ value: 3, label: '三级' },
];
export const DEFAULT_PASSWORD="a123456"
export const DEFAULT_BLICK_TAB="黑名单"

View File

@ -1,5 +1,9 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import { DEVICE_TYPE_MAP, ERROR_CODE } from '@/constants/constants';
import {
DEFAULT_BLICK_TAB,
DEVICE_TYPE_MAP,
ERROR_CODE,
} from '@/constants/constants';
import CustomTree from '@/pages/components/customTree';
import CreatGroup from '@/pages/userList/mod/group';
import { deleteDevice, getTerminalList } from '@/services/terminal';
@ -31,6 +35,7 @@ import EditModal from './mod/eidtDevice';
const UserListPage: React.FC = () => {
const [orgTreeData, setOrgTreeData] = useState<User.OrganizationNode[]>([]);
const [selectedOrg, setSelectedOrg] = useState<number>();
const [selectedOrgNode, setSelectedOrgNode] = useState<any>();
const [dataSource, setDataSource] = useState<Termial.TermialItem[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [searchText, setSearchText] = useState<string>('');
@ -307,12 +312,30 @@ const UserListPage: React.FC = () => {
title: '操作',
key: 'actions',
align: 'center',
width: 150,
width: 230,
fixed: 'right',
render: (_, record) => (
<div style={{ display: 'flex', alignItems: 'center' }}>
<Button type="link" onClick={() => handleEditInfo(record)}>
<Button
type="link"
size="small"
onClick={() => {
setBindUserData({
recordData: record,
visible: true,
});
}}
>
</Button>
<Button
type="link"
size="small"
onClick={() => {
setBindImageDta({ recordData: record, visible: true });
}}
>
</Button>
<Popover
placement="bottomRight"
@ -329,26 +352,8 @@ const UserListPage: React.FC = () => {
<Button type="link"></Button>
</Popconfirm>
<div>
<Button
type="link"
onClick={() => {
setBindUserData({
recordData: record,
visible: true,
});
}}
>
</Button>
</div>
<div>
<Button
type="link"
onClick={() => {
setBindImageDta({ recordData: record, visible: true });
}}
>
<Button type="link" onClick={() => handleEditInfo(record)}>
</Button>
</div>
</div>
@ -364,10 +369,12 @@ const UserListPage: React.FC = () => {
},
];
const onOrgSelect = (selectedKeys: React.Key[]) => {
const onOrgSelect = (selectedKeys: React.Key[], e: any) => {
const { node } = e || {};
if (selectedKeys.length > 0) {
setSelectedOrg(selectedKeys[0] as number);
setCurrentPage(1);
setSelectedOrgNode(node);
}
};
@ -382,7 +389,6 @@ const UserListPage: React.FC = () => {
};
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
console.log('selectedRowKeys changed: ', newSelectedRowKeys);
setSelectedRowKeys(newSelectedRowKeys as any);
};
const onDeleteGroup = async () => {
@ -399,7 +405,7 @@ const UserListPage: React.FC = () => {
const { data } = result || {};
const { total = 0 } = data || {};
if (total > 0) {
message.info("该分组下有终端,请先删除该分组下的所有终端");
message.info('该分组下有终端,请先删除该分组下的所有终端');
} else {
onDeleteGroupSave();
}
@ -470,6 +476,9 @@ const UserListPage: React.FC = () => {
type="text"
style={{ marginRight: '8px', fontSize: '16px' }}
icon={<PlusOutlined />}
disabled={
selectedOrgNode && selectedOrgNode.name === DEFAULT_BLICK_TAB
}
onClick={() => setVisible(true)}
/>
<Popconfirm
@ -479,13 +488,19 @@ const UserListPage: React.FC = () => {
// onCancel={cancel}
okText="删除"
cancelText="取消"
disabled={!selectedOrg}
disabled={
!selectedOrg ||
(selectedOrgNode && selectedOrgNode.is_deleted === 0)
}
>
<Button
type="text"
style={{ fontSize: '16px' }}
icon={<DeleteOutlined />}
disabled={!selectedOrg}
disabled={
!selectedOrg ||
(selectedOrgNode && selectedOrgNode.is_deleted === 0)
}
/>
</Popconfirm>
</div>

View File

@ -7,6 +7,7 @@ import CustomTable from '../selectedTable/table';
import SelectConponents from './table';
interface SelectedTableProps {
loading?: boolean;
placement?: PopoverProps['placement'];
onCancel?: () => void;
onOk?: (values: any) => void;
@ -17,6 +18,7 @@ interface SelectedTableProps {
const SelectedTable: React.FC<SelectedTableProps> = (props) => {
const {
placement = 'bottomLeft',
loading,
value: formValue,
onChange: onBindImageChange,
} = props;
@ -56,6 +58,7 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
list.push({
id: item.id,
image_name: item.image_name,
image_file_name: item.image_file_name,
});
});
setTableData(list);
@ -100,7 +103,7 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
};
const accessPopover = (
<div style={{ width: '600px' }}>
<div style={{ width: '700px' }}>
<SelectConponents
onUserTableSelect={onUserTableSelect}
selectedRowKeys={selectedRowKeys}
@ -139,8 +142,17 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
title: '镜像名称',
dataIndex: 'image_name',
key: 'image_name',
ellipsis: true,
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
return <Tooltip title={text || '--'}>{text || '--'}</Tooltip>;
},
},
{
title: '源文件名',
ellipsis: true,
dataIndex: 'image_file_name',
render: (text) => {
return <Tooltip title={text || '--'}>{text || '--'}</Tooltip>;
},
},
{
@ -157,9 +169,7 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
okText="删除"
cancelText="取消"
>
<Button type="link">
</Button>
<Button type="link"></Button>
</Popconfirm>
),
},
@ -201,6 +211,7 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
</Popconfirm>
</Space>
<CustomTable
loading={loading}
dataSource={tableData}
onDelete={handleDelete}
columns={getColumns()}

View File

@ -65,8 +65,18 @@ const TablePage: React.FC<TableProps> = ({
{
title: '镜像名称',
dataIndex: 'image_name',
ellipsis: true,
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
return <Tooltip title={text || '--'}>{text || '--'}</Tooltip>;
},
},
{
title: '源文件名',
ellipsis: true,
width: 300,
dataIndex: 'image_file_name',
render: (text) => {
return <Tooltip title={text || '--'}>{text || '--'}</Tooltip>;
},
},
];
@ -104,7 +114,7 @@ const TablePage: React.FC<TableProps> = ({
rowKey="id"
loading={loading}
rowSelection={{
selectedRowKeys:selectedRowKeys,
selectedRowKeys: selectedRowKeys,
preserveSelectedRowKeys: true,
onChange: onUserTableSelect,
}}

View File

@ -1,8 +1,10 @@
// index.tsx
import { ERROR_CODE } from '@/constants/constants';
import { getBindImageAdd, getBindImageList } from '@/services/terminal';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Button, Form, message, Modal } from 'antd';
import React, { useEffect, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
import SelectedTable from '../ImageSelectedTable/index';
import styles from './index.less';
@ -22,11 +24,16 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
const { recordData, visible } = dataDetial || {};
const { device_id } = recordData || {};
const [form] = Form.useForm();
const [loading, setLoading] = useState<boolean>(false);
const [dataSource, setDataSource] = useState<any[]>([]);
const [currentId, setCurrentId] = useState<any>(uuidv4());
useEffect(() => {
if (!device_id) return;
setLoading(true);
const params = { device_id: device_id };
getBindImageList(params).then((res: any) => {
getBindImageList(params)
.then((res: any) => {
const { code, data = [] } = res || {};
if (code === ERROR_CODE) {
if (data && data.length) {
@ -35,6 +42,7 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
imageList.push({
id: item.image_id,
image_name: item.image_name,
image_file_name: item.image_file_name,
// current_id: item.id,
});
});
@ -43,15 +51,19 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
form.setFieldsValue(initialValues);
}
}
})
.finally(() => {
setLoading(false);
});
}, [visible, form, recordData]);
}, [visible, form, recordData, currentId]);
const onBind = (payload: any) => {
getBindImageAdd(payload).then((res: any) => {
const { code } = res || {};
if (code === ERROR_CODE) {
message.success('绑定成功');
if (onOk) onOk();
setCurrentId(uuidv4());
// if (onOk) onOk();
} else {
message.error('绑定失败');
}
@ -79,7 +91,7 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
});
const payload: any = {
data: list,
device_id:device_id,
device_id: device_id,
};
onBind(payload);
} catch (error) {
@ -87,6 +99,25 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
}
};
// const onCancelHandle = async () => {
// const values = await form.validateFields();
// const { image_list = [] } = values || {};
// if (dataSource.length === image_list.length) {
// onCancel();
// } else {
// Modal.confirm({
// title: '镜像绑定信息有更新未保存,请确认是关闭弹窗',
// icon: <ExclamationCircleOutlined />,
// content: '',
// onOk() {
// onCancel();
// },
// okText: '确认',
// cancelText: '取消',
// });
// }
// };
return (
<Modal
title={'绑定镜像'}
@ -130,7 +161,7 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
label="选择镜像"
rules={[{ required: false, message: '请输入终端型号' }]}
>
<SelectedTable />
<SelectedTable loading={loading} />
</Form.Item>
{/* <Form.Item
name="description"

View File

@ -6,6 +6,7 @@ import { getGroupTree } from '@/services/userList';
import { Button, Form, message, Modal } from 'antd';
import React, { useEffect, useState } from 'react';
import SelectedTable from '../selectedTable';
import { v4 as uuidv4 } from 'uuid';
import styles from './index.less';
interface UserEditModalProps {
@ -25,8 +26,10 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
}) => {
const { recordData, visible } = dataDetial || {};
const { device_id, device_group_id } = recordData || {};
const [loading, setLoading] = useState<boolean>(false);
const [orgTreeData, setOrgTreeData] = useState<User.OrganizationNode[]>([]);
const [userDataSource, setUserDataSource] = useState<any[]>([]);
const [currentId,setCurrentId] = useState<any>(uuidv4());
// const [groupDataSource, setGroupDataSource] = useState<any[]>([]);
const [form] = Form.useForm();
@ -57,6 +60,7 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
useEffect(() => {
if (!device_id) return;
setLoading(true);
const payload = { device_id: device_id };
getBindUserList(payload).then((res: any) => {
const { code, data = [] } = res || {};
@ -90,8 +94,10 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
form.setFieldsValue(initialValues);
}
}
}).finally(() => {
setLoading(false);
});
}, [visible, form, recordData]);
}, [visible, form, recordData,currentId]);
const onBind = (payload: any) => {
bindUserAdd(payload)
@ -99,7 +105,8 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
const { code } = res || {};
if (code === ERROR_CODE) {
message.success('绑定成功');
onOk();
// onOk();
setCurrentId(uuidv4())
} else {
message.error('绑定失败');
}
@ -199,7 +206,7 @@ const BindUserModal: React.FC<UserEditModalProps> = ({
label="选择用户"
rules={[{ required: false, message: '请选择绑定用户' }]}
>
<SelectedTable orgTreeData={orgTreeData} />
<SelectedTable orgTreeData={orgTreeData} loading={loading}/>
</Form.Item>
{/* <Form.Item
name="description"

View File

@ -114,7 +114,7 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
label="终端标识"
rules={[{ required: true, message: '请输入终端标识' }]}
>
<Input />
<Input disabled={id ? true : false} />
</Form.Item>
<Form.Item

View File

@ -2,13 +2,14 @@
import CustomTree from '@/pages/components/customTree';
import { TeamOutlined } from '@ant-design/icons';
import type { PopoverProps } from 'antd';
import { Alert, Button, Popconfirm, Popover, Space, Tabs,Tooltip } from 'antd';
import { Alert, Button, Popconfirm, Popover, Space, Tabs, Tooltip } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import React, { useEffect, useState } from 'react';
import SelectConponents from '../selectConponents';
import CustomTable from './table';
interface SelectedTableProps {
loading?: boolean;
placement?: PopoverProps['placement'];
selectedOrg?: number;
onCancel?: () => void;
@ -22,6 +23,7 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
const {
placement = 'bottomLeft',
orgTreeData,
loading,
value: formValue,
onChange: onBindUserChange,
} = props;
@ -141,8 +143,9 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
dataIndex: 'name',
key: 'name',
align: 'center',
ellipsis: true,
render: (text) => {
return <Tooltip>{text || '--'}</Tooltip>;
return <Tooltip title={text || '--'}>{text || '--'}</Tooltip>;
},
},
{
@ -256,6 +259,7 @@ const SelectedTable: React.FC<SelectedTableProps> = (props) => {
</Popconfirm>
</Space>
<CustomTable
loading={loading}
dataSource={tableData}
onDelete={handleDelete}
scrollY={400}

View File

@ -3,6 +3,7 @@ import type { ColumnsType } from 'antd/es/table';
import React from 'react';
interface DeletableTableProps {
loading?: boolean;
dataSource: any[];
isSerial?: boolean;
isAction?: boolean;
@ -16,6 +17,7 @@ interface DeletableTableProps {
const DeletableTable: React.FC<DeletableTableProps> = (props) => {
const {
loading,
dataSource,
onDelete,
isSerial = true,

View File

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import {
DEFAULT_BLICK_TAB,
ERROR_CODE,
GENDER_MAP,
PRIORITY_MAP,
@ -30,6 +31,7 @@ import PasswordResetModal from './mod/passwordEdit';
const UserListPage: React.FC = () => {
const [orgTreeData, setOrgTreeData] = useState<User.OrganizationNode[]>([]);
const [selectedOrg, setSelectedOrg] = useState<any>();
const [selectedOrgNode, setSelectedOrgNode] = useState<any>();
// 用户列表
const [dataSource, setDataSource] = useState<User.UserItem[]>([]);
@ -333,16 +335,24 @@ const UserListPage: React.FC = () => {
title: '操作',
key: 'actions',
align: 'center',
width: 160,
width: 200,
fixed: 'right',
render: (_, record) => (
<div style={{ display: 'flex' }}>
{/* <Button type="link" onClick={() => handlePassword(record)}>
</Button> */}
<Button type="link" onClick={() => handleEditUserInfo(record)}>
<Button
size="small"
type="link"
onClick={() => handleEditUserInfo(record)}
>
</Button>
<Button
size="small"
type="link"
onClick={() => handlePassword(record)}
>
</Button>
<Popconfirm
title=""
description="删除操作不可逆,请确认是否删除?"
@ -351,17 +361,21 @@ const UserListPage: React.FC = () => {
okText="删除"
cancelText="取消"
>
<Button type="link"></Button>
<Button size="small" type="link">
</Button>
</Popconfirm>
</div>
),
},
];
const onOrgSelect = (selectedKeys: React.Key[]) => {
const onOrgSelect = (selectedKeys: React.Key[], e: any) => {
const { node } = e || {};
if (selectedKeys.length > 0) {
setSelectedOrg(selectedKeys[0]);
setCurrentPage(1);
setSelectedOrgNode(node);
}
};
@ -462,6 +476,9 @@ const UserListPage: React.FC = () => {
style={{ marginRight: '8px', fontSize: '16px' }}
icon={<PlusOutlined />}
onClick={() => setVisible(true)}
disabled={
selectedOrgNode && selectedOrgNode.name === DEFAULT_BLICK_TAB
}
title="新增"
/>
<Popconfirm
@ -471,14 +488,20 @@ const UserListPage: React.FC = () => {
// onCancel={cancel}
okText="删除"
cancelText="取消"
disabled={!selectedOrg}
disabled={
!selectedOrg ||
(selectedOrgNode && selectedOrgNode.is_deleted === 0)
}
>
<Button
type="text"
style={{ fontSize: '16px' }}
icon={<DeleteOutlined />}
title="删除"
disabled={!selectedOrg}
disabled={
!selectedOrg ||
(selectedOrgNode && selectedOrgNode.is_deleted === 0)
}
/>
</Popconfirm>
</div>
@ -630,13 +653,16 @@ const UserListPage: React.FC = () => {
)}
<PasswordResetModal
visible={eidtPassword.visible}
recordData={eidtPassword.recordData}
onCancel={() => {
setEidtPassword({
recordData: {},
visible: false,
});
}}
onOk={() => {}}
onOk={() => {
getDataSource();
}}
/>
</div>
);

View File

@ -1,4 +1,5 @@
import {
DEFAULT_PASSWORD,
ERROR_CODE,
GENDER_OPTIONS,
PRIOPRITY_OPTIONS,
@ -54,6 +55,7 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
const initialValues = {
user_group_id: selectedOrg ? selectedOrg : null,
status: 1,
password: DEFAULT_PASSWORD,
};
form.setFieldsValue(initialValues);
}
@ -176,7 +178,7 @@ const UserEditModal: React.FC<UserEditModalProps> = ({
help="密码必须包含数字和字母,字母区分大小写,且至少6位"
style={{ marginBottom: '30px' }}
>
<Input.Password placeholder="请输入密码" />
<Input.Password disabled={!!id} placeholder="请输入密码" />
</Form.Item>
<Form.Item

View File

@ -1,13 +1,15 @@
// index.tsx
import { DEFAULT_PASSWORD, ERROR_CODE } from '@/constants/constants';
import { editUser } from '@/services/userList';
import { Button, Form, Input, message, Modal } from 'antd';
import React, { useEffect } from 'react';
interface PasswordResetModalProps {
visible: boolean;
recordData?: any;
onCancel: () => void;
onOk: (values: { password: string }) => void;
onOk: (values?: { password: string }) => void;
confirmLoading?: boolean;
username?: string;
}
const PasswordResetModal: React.FC<PasswordResetModalProps> = ({
@ -15,20 +17,34 @@ const PasswordResetModal: React.FC<PasswordResetModalProps> = ({
onCancel,
onOk,
confirmLoading = false,
username,
recordData,
}) => {
const [form] = Form.useForm();
const { id } = recordData || {};
useEffect(() => {
if (!visible) {
form.resetFields();
form.setFieldsValue({ password: DEFAULT_PASSWORD });
}
}, [visible, form]);
const handleOk = async () => {
try {
const values = await form.validateFields();
onOk(values);
const { password } = values || {};
const params = {
id: id,
password: password,
};
const result: any = await editUser(params);
const { code } = result || {};
if (code === ERROR_CODE) {
message.success('密码重置成功');
onCancel();
onOk();
} else {
message.error('密码重置失败');
}
} catch (error) {
message.error('请检查表单字段');
}
@ -65,7 +81,7 @@ const PasswordResetModal: React.FC<PasswordResetModalProps> = ({
return (
<Modal
title={username ? `重置密码 - ${username}` : '重置密码'}
title={`重置密码`}
open={visible}
onCancel={onCancel}
onOk={handleOk}