新增供应商账号管理功能,包括供应商用户列表、用户信息更新和密码重置功能。

This commit is contained in:
刘倡
2025-07-26 11:29:50 +08:00
parent 0733b0cfaa
commit 0699196eb8
7 changed files with 480 additions and 52 deletions

View File

@ -140,6 +140,12 @@ export default [
path: '/biddingAnnouncement/BiddingAnnoStructureForm',
component: './Bid/BiddingAnnouncement/structure/BiddingAnnoStructureForm',
},
//供应商账号管理
{
name: '供应商',
path: '/SupplierUser',
component: './System/SupplierUser',
},
//==============================================================引入的业务路由
...approvalForm,//审批单
...juryRoom,//评标室内所有路由

View File

@ -0,0 +1,99 @@
import React from 'react';
import { Modal, Form, Input, Select } from 'antd';
import type { SupplierUserListItem } from '../data.d';
export interface UpdateFormProps {
onCancel: () => void;
onSubmit: (values: SupplierUserListItem) => void;
updateModalVisible: boolean;
values: Partial<SupplierUserListItem>;
}
const FormItem = Form.Item;
const { Option } = Select;
const UpdateForm: React.FC<UpdateFormProps> = (props) => {
const [form] = Form.useForm();
const { updateModalVisible, onCancel, onSubmit, values } = props;
const handleSubmit = async () => {
const fieldsValue = await form.validateFields();
onSubmit({ ...values, ...fieldsValue });
};
return (
<Modal
destroyOnClose
title="修改供应商用户"
visible={updateModalVisible}
onCancel={onCancel}
onOk={handleSubmit}
>
<Form
form={form}
initialValues={{
username: values.username,
name: values.name,
mobile: values.mobile,
status: values.status,
email: values.email,
remark: values.remark,
}}
layout="vertical"
>
<FormItem
name="username"
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input disabled placeholder="请输入用户名" />
</FormItem>
<FormItem
name="name"
label="姓名"
rules={[{ required: true, message: '请输入姓名' }]}
>
<Input placeholder="请输入姓名" />
</FormItem>
<FormItem
name="mobile"
label="手机号"
rules={[
{ required: true, message: '请输入手机号' },
{ pattern: /^1\d{10}$/, message: '手机号格式不正确' },
]}
>
<Input placeholder="请输入手机号" />
</FormItem>
<FormItem
name="email"
label="邮箱"
rules={[
{ required: false },
{ type: 'email', message: '邮箱格式不正确' },
]}
>
<Input placeholder="请输入邮箱" />
</FormItem>
<FormItem
name="status"
label="状态"
rules={[{ required: true, message: '请选择状态' }]}
>
<Select placeholder="请选择状态">
<Option value={1}></Option>
<Option value={0}></Option>
</Select>
</FormItem>
<FormItem
name="remark"
label="备注"
>
<Input.TextArea placeholder="请输入备注" rows={4} />
</FormItem>
</Form>
</Modal>
);
};
export default UpdateForm;

30
src/pages/System/SupplierUser/data.d.ts vendored Normal file
View File

@ -0,0 +1,30 @@
export interface SupplierUserListItem {
userId: string;
username: string;
name: string;
mobile: string;
supplierName: string;
creditCode: string;
status: number;
email?: string;
remark?: string;
}
export interface TableListPagination {
total: number;
pageSize: number;
current: number;
}
export interface TableListData {
list: SupplierUserListItem[];
pagination: Partial<TableListPagination>;
}
export interface TableListParams {
pageSize?: number;
current?: number;
keyword?: string;
status?: number;
sorter?: string;
}

View File

@ -0,0 +1,178 @@
import { PlusOutlined } from '@ant-design/icons';
import { Button, message, Popconfirm } from 'antd';
import React, { useState, useRef, useEffect } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import type { ProColumns, ActionType } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
import type { SupplierUserListItem } from './data.d';
import { getSupplierUserList, updateSupplierUser, resetPassword } from './service';
import UpdateForm from './components/UpdateForm';
const SupplierUser: React.FC = () => {
const actionRef = useRef<ActionType>();
const [updateModalVisible, handleUpdateModalVisible] = useState<boolean>(false);
const [formValues, setFormValues] = useState({});
// 从URL查询参数获取并保存必要的参数
useEffect(() => {
const query = new URLSearchParams(window.location.search);
const token = query.get('token');
const roleCode = query.get('roleCode');
const mall3Check = query.get('mall3Check');
// 如果参数存在保存到sessionStorage中
if (token) {
sessionStorage.setItem('Authorization', token);
}
if (roleCode) {
// 保存roleCode到sessionStorage
try {
const currentRoleData = sessionStorage.getItem('roleData');
if (currentRoleData) {
const roleDataObj = JSON.parse(currentRoleData);
roleDataObj.roleCode = roleCode;
sessionStorage.setItem('roleData', JSON.stringify(roleDataObj));
} else {
// 如果没有roleData创建一个基本对象
sessionStorage.setItem('roleData', JSON.stringify({ roleCode }));
}
} catch (e) {
// 如果解析失败,直接创建新对象
sessionStorage.setItem('roleData', JSON.stringify({ roleCode }));
}
}
if (mall3Check) {
sessionStorage.setItem('Mall3Check', mall3Check);
}
}, []);
const handleUpdate = async (fields: SupplierUserListItem) => {
const hide = message.loading('正在更新');
try {
await updateSupplierUser(fields);
hide();
message.success('更新成功');
return true;
} catch (error) {
hide();
message.error('更新失败请重试!');
return false;
}
};
const handleResetPassword = async (username: string) => {
const hide = message.loading('正在重置密码');
try {
await resetPassword(username);
hide();
message.success('密码重置成功,新密码已通过邮件发送,请注意查收');
return true;
} catch (error) {
hide();
message.error('密码重置失败,请重试');
return false;
}
};
const columns: ProColumns<SupplierUserListItem>[] = [
{
title: '用户名',
dataIndex: 'username',
sorter: true,
},
{
title: '姓名',
dataIndex: 'name',
sorter: true,
},
{
title: '供应商名称',
dataIndex: 'supplierName',
sorter: true,
},
{
title: '统一信用代码',
dataIndex: 'creditCode',
},
{
title: '手机号',
dataIndex: 'mobile',
},
{
title: '状态',
dataIndex: 'status',
valueEnum: {
0: { text: '无效', status: 'Default' },
1: { text: '有效', status: 'Processing' },
},
},
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
render: (_, record) => [
<a
key="config"
onClick={() => {
handleUpdateModalVisible(true);
setFormValues(record);
}}
>
</a>,
<Popconfirm
title="确认重置该用户的密码?"
onConfirm={() => handleResetPassword(record.username)}
key="reset"
>
<a></a>
</Popconfirm>,
],
},
];
return (
<PageContainer>
<ProTable<SupplierUserListItem>
headerTitle="供应商用户列表"
actionRef={actionRef}
rowKey="userId"
search={{
labelWidth: 120,
}}
request={(params) =>
getSupplierUserList(params).then((res) => {
return {
data: res.data.records,
success: true,
total: res.data.total,
};
})
}
columns={columns}
/>
<UpdateForm
onSubmit={async (value) => {
const success = await handleUpdate(value);
if (success) {
handleUpdateModalVisible(false);
setFormValues({});
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
onCancel={() => {
handleUpdateModalVisible(false);
setFormValues({});
}}
updateModalVisible={updateModalVisible}
values={formValues}
/>
</PageContainer>
);
};
export default SupplierUser;

View File

@ -0,0 +1,23 @@
import request from '@/utils/request';
import type { SupplierUserListItem, TableListParams } from './data.d';
export async function getSupplierUserList(params: TableListParams) {
return request('/sys/supplier/user/list', {
method: 'GET',
params,
});
}
export async function updateSupplierUser(params: SupplierUserListItem) {
return request('/sys/supplier/user/update', {
method: 'POST',
data: params,
});
}
export async function resetPassword(username: string) {
return request('/sys/supplier/user/reset-password', {
method: 'POST',
data: { username },
});
}

View File

@ -1,10 +1,12 @@
import React, { useState, useRef, useMemo, useEffect } from 'react';
import { message, Modal, Input, Form, PageHeader, Button, Spin, Tree, Checkbox, Row, Col } from 'antd';
import { message, Modal, Input, Form, PageHeader, Button, Spin, Tree, Checkbox, Row, Col, Upload } from 'antd';
import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table';
import tableProps from '@/utils/tableProps';
import { getPage, getDataById, allocationIF, assignsRoles, updateRole } from './service';
import { fetchAllDepartment } from '../Department/service';
import { getDicData } from '@/utils/session';
import { fetchIamUsers, syncIamUser } from './service';
import { UploadOutlined, DownloadOutlined } from '@ant-design/icons';
const { Search } = Input;
const entrust: React.FC<{}> = () => {
const [roleModalForm] = Form.useForm();
@ -21,6 +23,12 @@ const entrust: React.FC<{}> = () => {
labelCol: { span: 6 },
wrapperCol: { span: 13 },
};
const [syncModalOpen, setSyncModalOpen] = useState(false);
const [iamUsers, setIamUsers] = useState<any[]>([]);
const [iamLoading, setIamLoading] = useState(false);
const [iamTotal, setIamTotal] = useState(0);
const [iamPage, setIamPage] = useState({ pageNo: 1, pageSize: 10 });
const [iamSearch, setIamSearch] = useState('');
useEffect(() => {
getDepartmentList();
}, []);
@ -168,7 +176,7 @@ const entrust: React.FC<{}> = () => {
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some((item) => item.key === key)) {
if (node.children.some((item: { key: any }) => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
@ -191,7 +199,7 @@ const entrust: React.FC<{}> = () => {
const loop = (data: any): any[] => {
console.log('data', data);
let res: any[] = [];
data.map((item: any, i: any) => {
data.map((item: { title: string; key: any; children?: any[] }, i: number) => {
const strTitle = item.title as string;
const index = strTitle.indexOf(searchValue);
const beforeStr = strTitle.substring(0, index);
@ -223,6 +231,48 @@ const entrust: React.FC<{}> = () => {
return allData;
}
}, [searchValue, allData]);
// IAM用户加载
const loadIamUsers = async (pageNo: number, pageSize: number, search: string) => {
setIamLoading(true);
try {
const res = await fetchIamUsers({
pageNo,
pageSize,
search,
});
setIamUsers(res.data || []);
setIamTotal(res.total || 0);
} finally {
setIamLoading(false);
}
};
useEffect(() => {
if (syncModalOpen) {
loadIamUsers(iamPage.pageNo, iamPage.pageSize, iamSearch);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [syncModalOpen, iamPage.pageNo, iamPage.pageSize, iamSearch]);
const handleSyncUser = async (user: any) => {
Modal.confirm({
title: '确认同步该用户信息到主表?',
content: `姓名:${user.name},工号:${user.employeeNo}`,
okText: '确认',
cancelText: '取消',
onOk: async () => {
try {
await syncIamUser(user);
message.success('同步成功');
loadIamUsers(iamPage.pageNo, iamPage.pageSize, iamSearch);
// 同步后刷新主表
actionRef.current?.reload();
} catch (e) {
message.error('同步失败');
}
},
});
};
return (
<Spin spinning={spin}>
<div style={{ maxHeight: innerHeight - 130, height: innerHeight - 130 }} className='xsy-entrust bgCWhite'>
@ -266,11 +316,36 @@ const entrust: React.FC<{}> = () => {
})
}
toolBarRender={() => [
// <Button onClick={() => { handleAdd() }} type="primary">
// 新增
// </Button>,
]
}
<Button onClick={() => setSyncModalOpen(true)} type="primary"></Button>,
<Button
type="primary"
icon={<DownloadOutlined />}
style={{ marginRight: 8 }}
onClick={() => {
window.open('/user_import_template.xlsx');
}}
>
</Button>,
<Upload
name="file"
showUploadList={false}
accept=".xls,.xlsx"
action="/api/sys-manager-ebtp-project/v1/sysuser/import"
headers={{ /* 如有token可加Authorization */ }}
onChange={info => {
if (info.file.status === 'done') {
message.success('导入成功');
//导入后刷新主表
actionRef.current?.reload();
} else if (info.file.status === 'error') {
message.error('导入失败');
}
}}
>
<Button icon={<UploadOutlined />}>Excel</Button>
</Upload>
]}
pagination={{
...tableProps.pagination,
onChange: (page, pageSize) => pageDataSet({ pageNo: page, pageSize: pageSize }),
@ -279,10 +354,56 @@ const entrust: React.FC<{}> = () => {
onReset={() => { pageDataSet({ pageNo: 1, pageSize: 10 }); setOrgId(''); }}
/>
{setRoleModal}
{/* IAM用户同步Modal */}
<Modal
title="同步用户信息"
visible={syncModalOpen}
onCancel={() => setSyncModalOpen(false)}
footer={null}
width={900}
>
<Input.Search
placeholder="请输入姓名/工号"
onSearch={v => { setIamSearch(v); setIamPage({ ...iamPage, pageNo: 1 }); }}
style={{ marginBottom: 16, width: 300 }}
allowClear
/>
<ProTable
rowKey="employeeNo"
columns={[
{ title: '工号', dataIndex: 'employeeNo' },
{ title: '姓名', dataIndex: 'name' },
{ title: '状态', dataIndex: 'employeeStatusDesc' },
{ title: '公司', dataIndex: 'companyName' },
{ title: '岗位', dataIndex: 'positionName' },
{ title: '部门', dataIndex: 'departmentCode' },
{ title: '手机号', dataIndex: 'mobile' },
{ title: '邮箱', dataIndex: 'email' },
{
title: '操作',
render: (_: any, record: any) => (
<Button type="link" onClick={() => handleSyncUser(record)}>
</Button>
),
},
]}
dataSource={iamUsers}
loading={iamLoading}
pagination={{
current: iamPage.pageNo,
pageSize: iamPage.pageSize,
total: iamTotal,
onChange: (page, pageSize) => setIamPage({ pageNo: page, pageSize }),
}}
search={false}
options={false}
size="small"
/>
</Modal>
</div>
</div>
</div>
{/* 查看 */}
</Spin >
)
};

View File

@ -43,47 +43,18 @@ export async function assignsRoles(params: any) {
data: params,
});
}
// import { TreeDataNode } from 'antd';
// IAM用户列表查询
export async function fetchIamUsers(params: any) {
return request('/api/sys-manager-ebtp-project/v1/sysuser/iam/users', {
method: 'post',
data: params,
});
}
// export function userList(options?: { [key: string]: any }): Promise<{ data: any }> {
// return request('/api/system/userList', {
// method: 'GET',
// ...(options || {}),
// });
// }
// export function treeData(options?: { [key: string]: any }): Promise<{ data: TreeDataNode[] }> {
// return request('/api/system/treeData', {
// method: 'GET',
// ...(options || {}),
// });
// }
// export function fetchUserList(options?: any) {
// return request('/api/sysuser/getPage', {
// method: 'POST',
// data: options,
// });
// }
// //获取用户数据
// export function getUser(userId?: any) {
// return request(`/api/sysuser/${userId}`, {
// method: 'get',
// });
// }
// export function assignsRoles(options?: { [key: string]: any }): Promise<any> {
// return request('/api/sysuser/assignsRoles', {
// method: 'post',
// ...(options || {}),
// });
// }
// //查询用户详情
// export function getOneUserAll(options?: { [key: string]: any }): Promise<{ data: TreeDataNode[] }> {
// return request(`/api/sysuser/getOneUserAll/${options?.id}`, {
// method: 'GET',
// params: options,
// ...(options || {}),
// });
// }
// 同步IAM用户到主表
export async function syncIamUser(user: any) {
return request('/api/sys-manager-ebtp-project/v1/sysuser/sync', {
method: 'post',
data: user,
});
}