建立生成器功能
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
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:
@@ -6,6 +6,7 @@ import { PlusOutlined, SendOutlined, StarOutlined, StarFilled, FileTextOutlined
|
|||||||
import { getDictValueEnum } from '@/services/system/dict';
|
import { getDictValueEnum } from '@/services/system/dict';
|
||||||
import DictTag from '@/components/DictTag';
|
import DictTag from '@/components/DictTag';
|
||||||
import { applyAgree, delHrTalentCollect, exportCmsAppUserExport, getCmsAppUserList, getHrTalentCollectList } from '@/services/mobileusers/list';
|
import { applyAgree, delHrTalentCollect, exportCmsAppUserExport, getCmsAppUserList, getHrTalentCollectList } from '@/services/mobileusers/list';
|
||||||
|
import { getJobTitleTreeSelect } from '@/services/common/jobTitle';
|
||||||
import InterviewInvite from '@/pages/Management/List/ResumeRecommend/InterviewInvite';
|
import InterviewInvite from '@/pages/Management/List/ResumeRecommend/InterviewInvite';
|
||||||
import CollectTalent from './CollectTalent';
|
import CollectTalent from './CollectTalent';
|
||||||
|
|
||||||
@@ -36,6 +37,8 @@ function ManagementList() {
|
|||||||
const [sexEnum, setSexEnum] = useState<any>([]);
|
const [sexEnum, setSexEnum] = useState<any>([]);
|
||||||
const [hotEnum, setHotEnum] = useState<any>([]);
|
const [hotEnum, setHotEnum] = useState<any>([]);
|
||||||
const [politicalEnum, setPoliticalEnum] = useState<any>([]);
|
const [politicalEnum, setPoliticalEnum] = useState<any>([]);
|
||||||
|
const [nationEnum, setNationEnum] = useState<any>([]);
|
||||||
|
const [userTypeEnum, setUserTypeEnum] = useState<any>([]);
|
||||||
const [currentRow, setCurrentRow] = useState<API.MobileUser.ListRow>();
|
const [currentRow, setCurrentRow] = useState<API.MobileUser.ListRow>();
|
||||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||||
const [inviteVisible, setInviteVisible] = useState<boolean>(false);
|
const [inviteVisible, setInviteVisible] = useState<boolean>(false);
|
||||||
@@ -62,6 +65,12 @@ function ManagementList() {
|
|||||||
getDictValueEnum('is_publish', true).then((data) => {
|
getDictValueEnum('is_publish', true).then((data) => {
|
||||||
setIsPublishEnum(data);
|
setIsPublishEnum(data);
|
||||||
});
|
});
|
||||||
|
getDictValueEnum('nation', false, true).then((data) => {
|
||||||
|
setNationEnum(data);
|
||||||
|
});
|
||||||
|
getDictValueEnum('user_type', false, true).then((data) => {
|
||||||
|
setUserTypeEnum(data);
|
||||||
|
});
|
||||||
// getDictValueEnum('job_hot',true).then((data) => {
|
// getDictValueEnum('job_hot',true).then((data) => {
|
||||||
// setHotEnum(data)
|
// setHotEnum(data)
|
||||||
// })
|
// })
|
||||||
@@ -145,6 +154,25 @@ function ManagementList() {
|
|||||||
valueType: 'text',
|
valueType: 'text',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '求职期望',
|
||||||
|
dataIndex: 'jobTitleId',
|
||||||
|
valueType: 'treeSelect',
|
||||||
|
hideInTable: true,
|
||||||
|
width: 240,
|
||||||
|
fieldProps: {
|
||||||
|
showSearch: true,
|
||||||
|
filterTreeNode: (input: string, node: any) => node.label?.includes(input),
|
||||||
|
treeNodeFilterProp: 'label',
|
||||||
|
placeholder: '请选择求职期望',
|
||||||
|
style: { width: 240 },
|
||||||
|
fieldNames: { label: 'label', value: 'id', children: 'children' },
|
||||||
|
},
|
||||||
|
request: async () => {
|
||||||
|
const res = await getJobTitleTreeSelect();
|
||||||
|
return res?.data || [];
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '期望薪资',
|
title: '期望薪资',
|
||||||
dataIndex: 'minSalary',
|
dataIndex: 'minSalary',
|
||||||
@@ -166,6 +194,57 @@ function ManagementList() {
|
|||||||
format: 'YYYY-MM-DD',
|
format: 'YYYY-MM-DD',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '年龄',
|
||||||
|
dataIndex: 'age',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '毕业院校',
|
||||||
|
dataIndex: 'schoolName',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '毕业时间',
|
||||||
|
dataIndex: 'endTime',
|
||||||
|
valueType: 'date',
|
||||||
|
align: 'center',
|
||||||
|
fieldProps: {
|
||||||
|
format: 'YYYY-MM-DD',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '民族',
|
||||||
|
dataIndex: 'nation',
|
||||||
|
valueType: 'select',
|
||||||
|
align: 'center',
|
||||||
|
valueEnum: nationEnum,
|
||||||
|
render: (_, record) => {
|
||||||
|
return <DictTag enums={nationEnum} value={record.nation} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '个人标签',
|
||||||
|
dataIndex: 'userType',
|
||||||
|
valueType: 'select',
|
||||||
|
align: 'center',
|
||||||
|
valueEnum: userTypeEnum,
|
||||||
|
render: (_, record) => {
|
||||||
|
return <DictTag enums={userTypeEnum} value={record.userType} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '工作经验',
|
||||||
|
dataIndex: 'workExperience',
|
||||||
|
valueType: 'select',
|
||||||
|
align: 'center',
|
||||||
|
valueEnum: experienceEnum,
|
||||||
|
render: (_, record) => {
|
||||||
|
return <DictTag enums={experienceEnum} value={record.workExperience} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '学历要求',
|
title: '学历要求',
|
||||||
dataIndex: 'education',
|
dataIndex: 'education',
|
||||||
@@ -302,6 +381,7 @@ function ManagementList() {
|
|||||||
rowKey="applyId"
|
rowKey="applyId"
|
||||||
key="index"
|
key="index"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
search={{ defaultCollapsed: false }}
|
||||||
request={(params) => {
|
request={(params) => {
|
||||||
const { current, pageSize, ...searchParams } = params;
|
const { current, pageSize, ...searchParams } = params;
|
||||||
// 过滤空值,避免空字符串干扰后端查询
|
// 过滤空值,避免空字符串干扰后端查询
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ function AppUserList() {
|
|||||||
const [areaEnum, setAreaEnum] = useState<Record<string, any>>({});
|
const [areaEnum, setAreaEnum] = useState<Record<string, any>>({});
|
||||||
const [politicalEnum, setPoliticalEnum] = useState<Record<string, any>>({});
|
const [politicalEnum, setPoliticalEnum] = useState<Record<string, any>>({});
|
||||||
const [nationEnum, setNationEnum] = 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 状态 */
|
/** 修改身份证 Modal 状态 */
|
||||||
const [idCardModalOpen, setIdCardModalOpen] = useState(false);
|
const [idCardModalOpen, setIdCardModalOpen] = useState(false);
|
||||||
@@ -40,6 +42,8 @@ function AppUserList() {
|
|||||||
getDictValueEnum('area', true, true).then(setAreaEnum);
|
getDictValueEnum('area', true, true).then(setAreaEnum);
|
||||||
getDictValueEnum('political_affiliation', true, true).then(setPoliticalEnum);
|
getDictValueEnum('political_affiliation', true, true).then(setPoliticalEnum);
|
||||||
getDictValueEnum('nation', false, true).then(setNationEnum);
|
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>[] = [
|
const columns: ProColumns<API.CmsAppUser.AppUserRow>[] = [
|
||||||
@@ -80,7 +84,14 @@ function AppUserList() {
|
|||||||
valueType: 'text',
|
valueType: 'text',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 70,
|
width: 70,
|
||||||
hideInSearch: true,
|
},
|
||||||
|
{
|
||||||
|
title: '出生年月',
|
||||||
|
dataIndex: 'birthDate',
|
||||||
|
valueType: 'date',
|
||||||
|
align: 'center',
|
||||||
|
width: 120,
|
||||||
|
fieldProps: { format: 'YYYY-MM-DD' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '学历',
|
title: '学历',
|
||||||
@@ -91,6 +102,21 @@ function AppUserList() {
|
|||||||
valueEnum: educationEnum,
|
valueEnum: educationEnum,
|
||||||
render: (_, record) => <DictTag enums={educationEnum} value={record.education} />,
|
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: '地区',
|
title: '地区',
|
||||||
dataIndex: 'area',
|
dataIndex: 'area',
|
||||||
@@ -118,6 +144,24 @@ function AppUserList() {
|
|||||||
valueEnum: nationEnum,
|
valueEnum: nationEnum,
|
||||||
render: (_, record) => <DictTag enums={nationEnum} value={record.nation ?? undefined} />,
|
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: '期望薪资',
|
title: '期望薪资',
|
||||||
dataIndex: 'salaryRange',
|
dataIndex: 'salaryRange',
|
||||||
@@ -267,7 +311,7 @@ function AppUserList() {
|
|||||||
success: true,
|
success: true,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
search={{ labelWidth: 'auto' }}
|
search={{ labelWidth: 'auto', defaultCollapsed: false }}
|
||||||
scroll={{ x: 'max-content' }}
|
scroll={{ x: 'max-content' }}
|
||||||
pagination={{ defaultPageSize: 10, showSizeChanger: true }}
|
pagination={{ defaultPageSize: 10, showSizeChanger: true }}
|
||||||
headerTitle="个人用户管理"
|
headerTitle="个人用户管理"
|
||||||
|
|||||||
@@ -80,6 +80,22 @@ function toGeneratorFormat(data: API.CmsAppUser.ResumeData, dicts: { sex: DictMa
|
|||||||
? `${data.salaryMin || ''}${data.salaryMax ? '-' + data.salaryMax : ''}`
|
? `${data.salaryMin || ''}${data.salaryMax ? '-' + data.salaryMax : ''}`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
// 将生成器不支持的标准字段放入 customSections,确保用户可见
|
||||||
|
const supplementaryItems: any[] = [];
|
||||||
|
let itemIndex = 0;
|
||||||
|
if (nationLabel) supplementaryItems.push({ id: `sup_${itemIndex++}`, label: '民族', value: nationLabel });
|
||||||
|
if (data.birthDate) supplementaryItems.push({ id: `sup_${itemIndex++}`, label: '出生日期', value: data.birthDate });
|
||||||
|
if (data.age) supplementaryItems.push({ id: `sup_${itemIndex++}`, label: '年龄', value: data.age });
|
||||||
|
if (politicalLabel) supplementaryItems.push({ id: `sup_${itemIndex++}`, label: '政治面貌', value: politicalLabel });
|
||||||
|
if (salaryStr) supplementaryItems.push({ id: `sup_${itemIndex++}`, label: '期望薪资', value: salaryStr });
|
||||||
|
if (data.area) supplementaryItems.push({ id: `sup_${itemIndex++}`, label: '所在区域', value: dicts.area[data.area] || data.area });
|
||||||
|
|
||||||
|
const customSections: any[] = [];
|
||||||
|
if (supplementaryItems.length > 0) {
|
||||||
|
customSections.push({ id: 'supplementary', title: '补充信息', items: supplementaryItems });
|
||||||
|
sections.push({ id: 'supplementary', type: 'customSection', title: '补充信息', hidden: false });
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
basics: {
|
basics: {
|
||||||
name: data.name || '',
|
name: data.name || '',
|
||||||
@@ -87,17 +103,17 @@ function toGeneratorFormat(data: API.CmsAppUser.ResumeData, dicts: { sex: DictMa
|
|||||||
email: data.email || '',
|
email: data.email || '',
|
||||||
avatarUrl: data.avatar || '',
|
avatarUrl: data.avatar || '',
|
||||||
title: data.jobTitle?.[0] || '',
|
title: data.jobTitle?.[0] || '',
|
||||||
ethnicity: nationLabel,
|
location: data.area ? (dicts.area[data.area] || data.area) : '',
|
||||||
birthDate: data.birthDate || '',
|
|
||||||
age: data.age || '',
|
|
||||||
politicalStatus: politicalLabel,
|
|
||||||
expectedPosition: data.jobTitle?.[0] || '',
|
|
||||||
expectedSalary: salaryStr,
|
|
||||||
summary: summaryParts.join(' | '),
|
summary: summaryParts.join(' | '),
|
||||||
// 以下为自定义扩展字段,生成器 Zod schema 会丢弃,但 save 时从 summary 回读
|
// 以下为自定义扩展字段,生成器 Zod schema 会丢弃,但 save 时从 summary 回读
|
||||||
_gender: sexLabel,
|
_gender: sexLabel,
|
||||||
_educationLevel: educationLabel,
|
_educationLevel: educationLabel,
|
||||||
_yearsOfExperience: data.workExperience || '',
|
_yearsOfExperience: data.workExperience || '',
|
||||||
|
_politicalStatus: politicalLabel,
|
||||||
|
_ethnicity: nationLabel,
|
||||||
|
_age: data.age || '',
|
||||||
|
_birthDate: data.birthDate || '',
|
||||||
|
_expectedSalary: salaryStr,
|
||||||
},
|
},
|
||||||
education,
|
education,
|
||||||
experience,
|
experience,
|
||||||
@@ -105,7 +121,7 @@ function toGeneratorFormat(data: API.CmsAppUser.ResumeData, dicts: { sex: DictMa
|
|||||||
projects: [],
|
projects: [],
|
||||||
skills,
|
skills,
|
||||||
certificates: [],
|
certificates: [],
|
||||||
customSections: [],
|
customSections,
|
||||||
settings: {
|
settings: {
|
||||||
templateId: 'modern',
|
templateId: 'modern',
|
||||||
primaryColor: '#0f766e',
|
primaryColor: '#0f766e',
|
||||||
@@ -129,6 +145,14 @@ function fromGeneratorFormat(resume: any, dicts: { sex: DictMap; education: Dict
|
|||||||
} {
|
} {
|
||||||
const basics = resume?.basics || {};
|
const basics = resume?.basics || {};
|
||||||
|
|
||||||
|
// 从 customSections 中按 label 查找值(作为 Zod 丢弃字段后的回退方案)
|
||||||
|
const getCustomSectionValue = (label: string): string => {
|
||||||
|
const supSection = (resume?.customSections || []).find((s: any) => s.id === 'supplementary');
|
||||||
|
if (!supSection?.items) return '';
|
||||||
|
const item = supSection.items.find((i: any) => i.label === label);
|
||||||
|
return item?.value || '';
|
||||||
|
};
|
||||||
|
|
||||||
// 反向 lookup:汉字 → code
|
// 反向 lookup:汉字 → code
|
||||||
const reverseLookup = (map: DictMap, text: string): string => {
|
const reverseLookup = (map: DictMap, text: string): string => {
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
@@ -145,8 +169,11 @@ function fromGeneratorFormat(resume: any, dicts: { sex: DictMap; education: Dict
|
|||||||
|
|
||||||
// 学历:优先从 _educationLevel,回退到 summary 中的学历标签
|
// 学历:优先从 _educationLevel,回退到 summary 中的学历标签
|
||||||
const educationText = basics._educationLevel || '';
|
const educationText = basics._educationLevel || '';
|
||||||
const politicalText = basics.politicalStatus || '';
|
const politicalText = basics._politicalStatus || getCustomSectionValue('政治面貌') || '';
|
||||||
const nationText = basics.ethnicity || '';
|
const nationText = basics._ethnicity || getCustomSectionValue('民族') || '';
|
||||||
|
const ageText = basics._age || getCustomSectionValue('年龄') || '';
|
||||||
|
const birthDateText = basics._birthDate || getCustomSectionValue('出生日期') || '';
|
||||||
|
const salaryText = basics._expectedSalary || getCustomSectionValue('期望薪资') || '';
|
||||||
|
|
||||||
// 工作年限:从 _yearsOfExperience 或 summary 中匹配 "X年工作经验"
|
// 工作年限:从 _yearsOfExperience 或 summary 中匹配 "X年工作经验"
|
||||||
const yearsFromField = basics._yearsOfExperience || '';
|
const yearsFromField = basics._yearsOfExperience || '';
|
||||||
@@ -160,13 +187,15 @@ function fromGeneratorFormat(resume: any, dicts: { sex: DictMap; education: Dict
|
|||||||
email: basics.email || undefined,
|
email: basics.email || undefined,
|
||||||
avatar: basics.avatarUrl || undefined,
|
avatar: basics.avatarUrl || undefined,
|
||||||
sex: reverseLookup(dicts.sex, sexFinal) || (sexFinal === '男' ? '0' : sexFinal === '女' ? '1' : '') || undefined,
|
sex: reverseLookup(dicts.sex, sexFinal) || (sexFinal === '男' ? '0' : sexFinal === '女' ? '1' : '') || undefined,
|
||||||
age: basics.age || undefined,
|
age: ageText || undefined,
|
||||||
birthDate: basics.birthDate || undefined,
|
birthDate: birthDateText || undefined,
|
||||||
education: reverseLookup(dicts.education, educationText) || educationText || undefined,
|
education: reverseLookup(dicts.education, educationText) || educationText || undefined,
|
||||||
workExperience: workExperienceFinal || undefined,
|
workExperience: workExperienceFinal || undefined,
|
||||||
politicalAffiliation: reverseLookup(dicts.political, politicalText) || politicalText || undefined,
|
politicalAffiliation: reverseLookup(dicts.political, politicalText) || politicalText || undefined,
|
||||||
nation: reverseLookup(dicts.nation, nationText) || nationText || undefined,
|
nation: reverseLookup(dicts.nation, nationText) || nationText || undefined,
|
||||||
address: basics.location || undefined,
|
address: basics.location || undefined,
|
||||||
|
salaryMin: salaryText ? salaryText.split('-')[0] : undefined,
|
||||||
|
salaryMax: salaryText ? salaryText.split('-')[1] : undefined,
|
||||||
},
|
},
|
||||||
appUserEducations: (resume?.education || []).map((e: any) => ({
|
appUserEducations: (resume?.education || []).map((e: any) => ({
|
||||||
id: e.id ? Number(e.id) : undefined,
|
id: e.id ? Number(e.id) : undefined,
|
||||||
@@ -278,6 +307,8 @@ function AppUserResume() {
|
|||||||
} else {
|
} else {
|
||||||
console.warn('简历data为空,该用户可能没有简历信息');
|
console.warn('简历data为空,该用户可能没有简历信息');
|
||||||
resumeDataRef.current = null;
|
resumeDataRef.current = null;
|
||||||
|
setResumeData(null);
|
||||||
|
message.info('该用户暂无简历信息,生成器将显示空白模板');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message.error('获取简历数据失败: ' + (res?.msg || '未知错误'));
|
message.error('获取简历数据失败: ' + (res?.msg || '未知错误'));
|
||||||
@@ -290,9 +321,11 @@ function AppUserResume() {
|
|||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, [userId]);
|
}, [userId]);
|
||||||
|
|
||||||
// 初始化 iframe-js 通信(独立于数据加载)
|
// 初始化 iframe-js 通信(通过 ref callback 在 DOM 插入时同步执行,确保监听器在 iframe 加载前就位)
|
||||||
const initIframe = useCallback(() => {
|
const initHost = useCallback((el: HTMLIFrameElement) => {
|
||||||
if (!iframeRef.current || hostRef.current) return;
|
if (!el || hostRef.current) return;
|
||||||
|
|
||||||
|
iframeRef.current = el;
|
||||||
|
|
||||||
const generatorUrl = window.location.origin + '/resumegenerator';
|
const generatorUrl = window.location.origin + '/resumegenerator';
|
||||||
console.log('[Resume] 开始创建iframe通信,URL:', generatorUrl);
|
console.log('[Resume] 开始创建iframe通信,URL:', generatorUrl);
|
||||||
@@ -300,7 +333,7 @@ function AppUserResume() {
|
|||||||
let host;
|
let host;
|
||||||
try {
|
try {
|
||||||
host = new Iframe({
|
host = new Iframe({
|
||||||
container: iframeRef.current,
|
container: el,
|
||||||
url: generatorUrl,
|
url: generatorUrl,
|
||||||
whiteList: [location.origin],
|
whiteList: [location.origin],
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
@@ -373,19 +406,43 @@ function AppUserResume() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 超时保护:15秒内未收到 ready 则报错
|
||||||
|
const readyTimeout = setTimeout(() => {
|
||||||
|
if (!hostRef.current) return;
|
||||||
|
console.error('[Resume] 简历生成器加载超时(15秒)');
|
||||||
|
message.error('简历生成器加载失败,请检查网络连接后刷新页面重试');
|
||||||
|
setGeneratorReady(false);
|
||||||
|
}, 15000);
|
||||||
|
|
||||||
// 生成器就绪
|
// 生成器就绪
|
||||||
|
let readyFired = false;
|
||||||
host.action('resume:ready', () => {
|
host.action('resume:ready', () => {
|
||||||
|
if (readyFired) return; // 防止重复触发
|
||||||
|
readyFired = true;
|
||||||
|
clearTimeout(readyTimeout);
|
||||||
|
|
||||||
console.log('[Resume] 简历生成器已就绪');
|
console.log('[Resume] 简历生成器已就绪');
|
||||||
setGeneratorReady(true);
|
setGeneratorReady(true);
|
||||||
// 有数据就加载
|
|
||||||
const data = resumeDataRef.current;
|
// 推送数据到生成器(如果数据已加载)
|
||||||
if (data) {
|
const tryLoadData = (attempts: number = 0) => {
|
||||||
const genData = toGeneratorFormat(data, dictsRef.current);
|
const data = resumeDataRef.current;
|
||||||
console.log('加载简历数据到生成器:', genData);
|
if (data) {
|
||||||
host.callRemote('resume:load', { resume: genData }, 8000).catch((err: any) => {
|
const genData = toGeneratorFormat(data, dictsRef.current);
|
||||||
console.error('[Resume] resume:load 失败:', err);
|
console.log('[Resume] 加载简历数据到生成器:', genData);
|
||||||
});
|
host.callRemote('resume:load', { resume: genData }, 8000).catch((err: any) => {
|
||||||
}
|
console.error('[Resume] resume:load 失败:', err);
|
||||||
|
message.warning('简历数据加载失败,请点击"重新加载"按钮重试');
|
||||||
|
});
|
||||||
|
} else if (attempts < 20) {
|
||||||
|
// 数据还没加载完,轮询等待(最多 10 秒,每 500ms 一次)
|
||||||
|
setTimeout(() => tryLoadData(attempts + 1), 500);
|
||||||
|
} else {
|
||||||
|
console.warn('[Resume] 等待简历数据超时(10秒),生成器将显示空模板');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tryLoadData();
|
||||||
|
|
||||||
if (readOnly) {
|
if (readOnly) {
|
||||||
host.callRemote('resume:setReadOnly', { readOnly: true }, 8000).catch((err: any) => {
|
host.callRemote('resume:setReadOnly', { readOnly: true }, 8000).catch((err: any) => {
|
||||||
console.error('[Resume] resume:setReadOnly 失败:', err);
|
console.error('[Resume] resume:setReadOnly 失败:', err);
|
||||||
@@ -426,7 +483,7 @@ function AppUserResume() {
|
|||||||
host.action('resume:change', (event: any) => {
|
host.action('resume:change', (event: any) => {
|
||||||
console.log('简历内容已更新', event?.data?.resume);
|
console.log('简历内容已更新', event?.data?.resume);
|
||||||
});
|
});
|
||||||
}, [readOnly, userId]);
|
}, []); // 仅在首次挂载时初始化,readOnly 和 userId 通过 ref 获取最新值
|
||||||
|
|
||||||
// 当 resumeData 变化且生成器已就绪时,推送新数据到生成器
|
// 当 resumeData 变化且生成器已就绪时,推送新数据到生成器
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -439,22 +496,15 @@ function AppUserResume() {
|
|||||||
}
|
}
|
||||||
}, [resumeData, generatorReady]);
|
}, [resumeData, generatorReady]);
|
||||||
|
|
||||||
// 页面加载时初始化 iframe
|
// 页面卸载时清理 iframe-js 实例
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 先清理旧实例
|
|
||||||
if (hostRef.current) {
|
|
||||||
try { hostRef.current.destroy(); } catch (e) {}
|
|
||||||
hostRef.current = null;
|
|
||||||
}
|
|
||||||
console.log('[Resume] useEffect触发 initIframe');
|
|
||||||
initIframe();
|
|
||||||
return () => {
|
return () => {
|
||||||
if (hostRef.current) {
|
if (hostRef.current) {
|
||||||
try { hostRef.current.destroy(); } catch (e) {}
|
try { hostRef.current.destroy(); } catch (e) {}
|
||||||
hostRef.current = null;
|
hostRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []); // 只在 mount 时执行一次
|
}, []);
|
||||||
|
|
||||||
// 切换只读模式
|
// 切换只读模式
|
||||||
const toggleReadOnly = async (checked: boolean) => {
|
const toggleReadOnly = async (checked: boolean) => {
|
||||||
@@ -511,14 +561,25 @@ function AppUserResume() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 简历生成器 iframe 区域 */}
|
{/* 简历生成器 iframe 区域 */}
|
||||||
{loading && <div style={{ textAlign: 'center', padding: 20 }}>正在加载简历数据...</div>}
|
<div style={{ position: 'relative', width: '100%', height: 'calc(100vh - 110px)' }}>
|
||||||
<iframe
|
{loading && (
|
||||||
ref={iframeRef}
|
<div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#fff', zIndex: 1 }}>
|
||||||
id="resume-generator"
|
正在加载简历数据...
|
||||||
title="简历生成器"
|
</div>
|
||||||
src="/resumegenerator"
|
)}
|
||||||
style={{ width: '100%', height: 'calc(100vh - 110px)', border: '1px solid #f0f0f0', borderRadius: 4 }}
|
{!generatorReady && !loading && (
|
||||||
/>
|
<div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#fff', zIndex: 1, color: '#999' }}>
|
||||||
|
正在加载简历生成器...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<iframe
|
||||||
|
ref={initHost}
|
||||||
|
id="resume-generator"
|
||||||
|
title="简历生成器"
|
||||||
|
src="/resumegenerator"
|
||||||
|
style={{ width: '100%', height: '100%', border: '1px solid #f0f0f0', borderRadius: 4 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,14 +44,16 @@ function patchRouteItems(route: any, menu: any, parentPath: string) {
|
|||||||
if (path.length > 0) {
|
if (path.length > 0) {
|
||||||
path += '/';
|
path += '/';
|
||||||
}
|
}
|
||||||
if (name !== 'index') {
|
// 不区分大小写判断 "index",避免服务端返回 "Index"(大写I)时在 Linux 上找不到模块
|
||||||
path += name.at(0)?.toUpperCase() + name.substr(1);
|
// 统一输出小写 "index" 以匹配 webpack 模块注册路径
|
||||||
|
if (name.toLowerCase() === 'index') {
|
||||||
|
path += 'index';
|
||||||
} else {
|
} else {
|
||||||
path += name;
|
path += name.at(0)?.toUpperCase() + name.substr(1);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
if (!path.endsWith('.tsx')) {
|
if (!path.endsWith('.tsx')) {
|
||||||
path += '.tsx'
|
path += '.tsx';
|
||||||
}
|
}
|
||||||
if (route.routes === undefined) {
|
if (route.routes === undefined) {
|
||||||
route.routes = [];
|
route.routes = [];
|
||||||
|
|||||||
9
src/types/cms/appuser.d.ts
vendored
9
src/types/cms/appuser.d.ts
vendored
@@ -38,6 +38,8 @@ declare namespace API.CmsAppUser {
|
|||||||
unionid: string | null;
|
unionid: string | null;
|
||||||
nation: string | null;
|
nation: string | null;
|
||||||
workExperience: string | null;
|
workExperience: string | null;
|
||||||
|
schoolName: string | null;
|
||||||
|
endTime: string | null;
|
||||||
company: string | null;
|
company: string | null;
|
||||||
experiencesList: any;
|
experiencesList: any;
|
||||||
appSkillsList: any;
|
appSkillsList: any;
|
||||||
@@ -59,10 +61,17 @@ declare namespace API.CmsAppUser {
|
|||||||
name?: string;
|
name?: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
sex?: string;
|
sex?: string;
|
||||||
|
age?: string;
|
||||||
|
birthDate?: string;
|
||||||
education?: string;
|
education?: string;
|
||||||
|
schoolName?: string;
|
||||||
|
endTime?: string;
|
||||||
area?: string;
|
area?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
politicalAffiliation?: string;
|
politicalAffiliation?: string;
|
||||||
|
nation?: string;
|
||||||
|
userType?: string;
|
||||||
|
workExperience?: string;
|
||||||
idCard?: string;
|
idCard?: string;
|
||||||
orderByColumn?: string;
|
orderByColumn?: string;
|
||||||
isAsc?: string;
|
isAsc?: string;
|
||||||
|
|||||||
12
src/types/mobileusers/list.d.ts
vendored
12
src/types/mobileusers/list.d.ts
vendored
@@ -33,6 +33,11 @@ declare namespace API.MobileUser {
|
|||||||
idCard?:string;
|
idCard?:string;
|
||||||
interviewStatus?: string;
|
interviewStatus?: string;
|
||||||
interviewId?: number;
|
interviewId?: number;
|
||||||
|
schoolName?: string;
|
||||||
|
endTime?: string;
|
||||||
|
nation?: string;
|
||||||
|
userType?: string;
|
||||||
|
workExperience?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListParams {
|
export interface ListParams {
|
||||||
@@ -44,5 +49,12 @@ declare namespace API.MobileUser {
|
|||||||
politicalAffiliation?: string;
|
politicalAffiliation?: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
jobName?: string;
|
jobName?: string;
|
||||||
|
jobTitleId?: string;
|
||||||
|
schoolName?: string;
|
||||||
|
endTime?: string;
|
||||||
|
nation?: string;
|
||||||
|
userType?: string;
|
||||||
|
workExperience?: string;
|
||||||
|
area?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user