简历集成、简历预览、简历编辑、修改密码等功能开发
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

This commit is contained in:
francis-fh
2026-07-20 10:22:51 +08:00
parent d2311d0c5b
commit dfe22c6307
19 changed files with 6098 additions and 4935 deletions

View File

@@ -1,22 +1,45 @@
import React, { useEffect, useRef, useState } from 'react';
import { Tag } from 'antd';
import { Tag, Button, Switch, Modal, Form, Input, message } from 'antd';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { getAppUserList } from '@/services/cms/appuser';
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>>({});
/** 修改身份证 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);
}, []);
const columns: ProColumns<API.CmsAppUser.AppUserRow>[] = [
@@ -86,6 +109,15 @@ function AppUserList() {
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: 'salaryRange',
@@ -135,6 +167,77 @@ function AppUserList() {
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 (
@@ -156,6 +259,8 @@ function AppUserList() {
pageNum: current,
pageSize,
...filteredParams,
orderByColumn: 'createTime',
isAsc: 'desc',
}).then((res) => ({
data: res.rows || [],
total: res.total,
@@ -167,6 +272,122 @@ function AppUserList() {
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>
);
}