Files
shz-admin/src/pages/Usermgmt/Appuser/List/index.tsx
francis-fh cd9a5b4c35
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled
建立生成器功能
2026-07-23 12:30:28 +08:00

440 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useRef, useState } from 'react';
import { Tag, Button, Switch, Modal, Form, Input, message } from 'antd';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { useNavigate } from '@umijs/max';
import { FileTextOutlined } from '@ant-design/icons';
import {
getAppUserList,
updateIdCard,
changeResumeStatus,
resetPassword,
} from '@/services/cms/appuser';
import { getDictValueEnum } from '@/services/system/dict';
import DictTag from '@/components/DictTag';
/** 身份证校验规则18位 */
const ID_CARD_REGEX = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
function AppUserList() {
const actionRef = useRef<ActionType>();
const navigate = useNavigate();
const [educationEnum, setEducationEnum] = useState<Record<string, any>>({});
const [sexEnum, setSexEnum] = useState<Record<string, any>>({});
const [areaEnum, setAreaEnum] = useState<Record<string, any>>({});
const [politicalEnum, setPoliticalEnum] = useState<Record<string, any>>({});
const [nationEnum, setNationEnum] = useState<Record<string, any>>({});
const [userTypeEnum, setUserTypeEnum] = useState<Record<string, any>>({});
const [workExperienceEnum, setWorkExperienceEnum] = useState<Record<string, any>>({});
/** 修改身份证 Modal 状态 */
const [idCardModalOpen, setIdCardModalOpen] = useState(false);
const [idCardRecord, setIdCardRecord] = useState<API.CmsAppUser.AppUserRow | null>(null);
const [idCardForm] = Form.useForm();
/** 修改密码 Modal 状态 */
const [passwordModalOpen, setPasswordModalOpen] = useState(false);
const [passwordRecord, setPasswordRecord] = useState<API.CmsAppUser.AppUserRow | null>(null);
const [passwordForm] = Form.useForm();
useEffect(() => {
getDictValueEnum('education', true, true).then(setEducationEnum);
getDictValueEnum('sys_user_sex', true).then(setSexEnum);
getDictValueEnum('area', true, true).then(setAreaEnum);
getDictValueEnum('political_affiliation', true, true).then(setPoliticalEnum);
getDictValueEnum('nation', false, true).then(setNationEnum);
getDictValueEnum('user_type', false, true).then(setUserTypeEnum);
getDictValueEnum('experience', false, true).then(setWorkExperienceEnum);
}, []);
const columns: ProColumns<API.CmsAppUser.AppUserRow>[] = [
{
title: '用户ID',
dataIndex: 'userId',
valueType: 'text',
align: 'center',
width: 80,
hideInSearch: true,
},
{
title: '姓名',
dataIndex: 'name',
valueType: 'text',
align: 'center',
width: 120,
},
{
title: '手机号',
dataIndex: 'phone',
valueType: 'text',
align: 'center',
width: 140,
},
{
title: '性别',
dataIndex: 'sex',
valueType: 'select',
align: 'center',
width: 80,
valueEnum: sexEnum,
render: (_, record) => <DictTag enums={sexEnum} value={record.sex} />,
},
{
title: '年龄',
dataIndex: 'age',
valueType: 'text',
align: 'center',
width: 70,
},
{
title: '出生年月',
dataIndex: 'birthDate',
valueType: 'date',
align: 'center',
width: 120,
fieldProps: { format: 'YYYY-MM-DD' },
},
{
title: '学历',
dataIndex: 'education',
valueType: 'select',
align: 'center',
width: 90,
valueEnum: educationEnum,
render: (_, record) => <DictTag enums={educationEnum} value={record.education} />,
},
{
title: '毕业院校',
dataIndex: 'schoolName',
valueType: 'text',
align: 'center',
width: 160,
},
{
title: '毕业时间',
dataIndex: 'endTime',
valueType: 'date',
align: 'center',
width: 120,
fieldProps: { format: 'YYYY-MM-DD' },
},
{
title: '地区',
dataIndex: 'area',
valueType: 'select',
align: 'center',
width: 100,
valueEnum: areaEnum,
render: (_, record) => <DictTag enums={areaEnum} value={record.area} />,
},
{
title: '政治面貌',
dataIndex: 'politicalAffiliation',
valueType: 'select',
align: 'center',
width: 110,
valueEnum: politicalEnum,
render: (_, record) => <DictTag enums={politicalEnum} value={record.politicalAffiliation} />,
},
{
title: '民族',
dataIndex: 'nation',
valueType: 'select',
align: 'center',
width: 90,
valueEnum: nationEnum,
render: (_, record) => <DictTag enums={nationEnum} value={record.nation ?? undefined} />,
},
{
title: '个人标签',
dataIndex: 'userType',
valueType: 'select',
align: 'center',
width: 110,
valueEnum: userTypeEnum,
render: (_, record) => <DictTag enums={userTypeEnum} value={record.userType ?? undefined} />,
},
{
title: '工作经验',
dataIndex: 'workExperience',
valueType: 'select',
align: 'center',
width: 110,
valueEnum: workExperienceEnum,
render: (_, record) => <DictTag enums={workExperienceEnum} value={record.workExperience ?? undefined} />,
},
{
title: '期望薪资',
dataIndex: 'salaryRange',
hideInSearch: true,
align: 'center',
width: 140,
render: (_, record) => {
if (!record.salaryMin && !record.salaryMax) return '-';
return `${record.salaryMin ?? '-'} ~ ${record.salaryMax ?? '-'}`;
},
},
{
title: '身份证号',
dataIndex: 'idCard',
valueType: 'text',
align: 'center',
width: 170,
render: (_, record) => {
if (!record.idCard) return '-';
const idCard = record.idCard.toString();
if (idCard.length < 8) return idCard;
return `${idCard.slice(0, 4)}${'*'.repeat(idCard.length - 8)}${idCard.slice(-4)}`;
},
},
{
title: '最后登录',
dataIndex: 'loginDate',
valueType: 'date',
align: 'center',
width: 110,
hideInSearch: true,
fieldProps: { format: 'YYYY-MM-DD' },
},
{
title: '创建时间',
dataIndex: 'createTime',
valueType: 'dateTime',
align: 'center',
hideInSearch: true,
width: 160,
},
{
title: '更新时间',
dataIndex: 'updateTime',
valueType: 'dateTime',
align: 'center',
hideInSearch: true,
width: 160,
},
{
title: '简历状态',
dataIndex: 'resumeStatus',
align: 'center',
width: 90,
fixed: 'right',
hideInSearch: true,
render: (_, record) => (
<Switch
checked={record.resumeStatus === '1'}
checkedChildren="公开"
unCheckedChildren="保密"
onChange={async (checked) => {
try {
const newStatus = checked ? '1' : '2';
await changeResumeStatus({
userId: record.userId,
resumeStatus: newStatus,
});
message.success(checked ? '简历已设置成公开!' : '简历已设置成保密!');
actionRef.current?.reload();
} catch {
message.error('操作失败');
}
}}
/>
),
},
{
title: '操作',
valueType: 'option',
align: 'center',
width: 240,
fixed: 'right',
hideInSearch: true,
render: (_, record) => [
<Button
key="resume"
type="link"
size="small"
icon={<FileTextOutlined />}
onClick={() => navigate(`/usermgmt/appuser/resume/${record.userId}`)}
>
</Button>,
<Button
key="idCard"
type="link"
size="small"
onClick={() => {
setIdCardRecord(record);
idCardForm.resetFields();
setIdCardModalOpen(true);
}}
>
</Button>,
<Button
key="password"
type="link"
size="small"
onClick={() => {
setPasswordRecord(record);
passwordForm.resetFields();
setPasswordModalOpen(true);
}}
>
</Button>,
],
},
];
return (
<div style={{ width: '100%' }}>
<ProTable<API.CmsAppUser.AppUserRow>
actionRef={actionRef}
rowKey="userId"
columns={columns}
request={(params) => {
const { current, pageSize, ...searchParams } = params;
const filteredParams: Record<string, any> = {};
Object.keys(searchParams).forEach((key) => {
const val = searchParams[key];
if (val !== undefined && val !== null && val !== '') {
filteredParams[key] = val;
}
});
return getAppUserList({
pageNum: current,
pageSize,
...filteredParams,
orderByColumn: 'createTime',
isAsc: 'desc',
}).then((res) => ({
data: res.rows || [],
total: res.total,
success: true,
}));
}}
search={{ labelWidth: 'auto', defaultCollapsed: false }}
scroll={{ x: 'max-content' }}
pagination={{ defaultPageSize: 10, showSizeChanger: true }}
headerTitle="个人用户管理"
/>
{/* 修改身份证 Modal */}
<Modal
title="修改身份证号"
open={idCardModalOpen}
onCancel={() => setIdCardModalOpen(false)}
onOk={() => idCardForm.submit()}
destroyOnHidden
>
<Form
form={idCardForm}
layout="vertical"
onFinish={async (values) => {
try {
await updateIdCard({
userId: idCardRecord!.userId,
idCard: values.idCard,
});
setIdCardModalOpen(false);
actionRef.current?.reload();
} catch {
message.error('修改失败');
}
}}
>
<Form.Item label="用户姓名" style={{ marginBottom: 8 }}>
<span>{idCardRecord?.name || '-'}</span>
</Form.Item>
<Form.Item
label="新身份证号"
name="idCard"
rules={[
{ required: true, message: '请输入身份证号' },
{
pattern: ID_CARD_REGEX,
message: '身份证号格式不正确',
},
]}
>
<Input placeholder="请输入18位身份证号" maxLength={18} />
</Form.Item>
</Form>
</Modal>
{/* 修改密码 Modal */}
<Modal
title="修改密码"
open={passwordModalOpen}
onCancel={() => setPasswordModalOpen(false)}
onOk={() => passwordForm.submit()}
destroyOnHidden
>
<Form
form={passwordForm}
layout="vertical"
onFinish={async (values) => {
try {
await resetPassword({
userId: passwordRecord!.userId,
password: values.password,
});
message.success('密码修改成功');
setPasswordModalOpen(false);
} catch {
message.error('密码修改失败');
}
}}
>
<Form.Item label="用户姓名" style={{ marginBottom: 8 }}>
<span>{passwordRecord?.name || '-'}</span>
</Form.Item>
<Form.Item
label="新密码"
name="password"
rules={[
{ required: true, message: '请输入新密码' },
{ min: 8, max: 20, message: '密码长度在 8 到 20 个字符' },
{
validator: (_, value) => {
if (!value) return Promise.resolve();
const hasUpper = /[A-Z]/.test(value);
const hasLower = /[a-z]/.test(value);
const hasDigit = /\d/.test(value);
const specialChars = value.replace(/[a-zA-Z0-9]/g, '');
const hasTwoSpecial = specialChars.length >= 2;
if (!hasUpper) return Promise.reject(new Error('密码需包含大写字母'));
if (!hasLower) return Promise.reject(new Error('密码需包含小写字母'));
if (!hasDigit) return Promise.reject(new Error('密码需包含数字'));
if (!hasTwoSpecial) return Promise.reject(new Error('密码需包含至少2个特殊字符'));
return Promise.resolve();
},
},
]}
>
<Input.Password placeholder="请输入新密码" />
</Form.Item>
<Form.Item
label="确认密码"
name="confirmPassword"
dependencies={['password']}
rules={[
{ required: true, message: '请确认新密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('两次输入的密码不一致'));
},
}),
]}
>
<Input.Password placeholder="请再次输入新密码" />
</Form.Item>
</Form>
</Modal>
</div>
);
}
export default AppUserList;