import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useParams, useNavigate, useSearchParams } from '@umijs/max'; import { Button, Card, message, Space, Switch, Typography } from 'antd'; import { ArrowLeftOutlined, EyeOutlined } from '@ant-design/icons'; import Iframe from 'iframe-js'; import { getAppUserResume } from '@/services/cms/appuser'; import { getDictValueEnum } from '@/services/system/dict'; import { request } from '@umijs/max'; import './index.less'; const { Text } = Typography; /** 字典 code → 汉字 的映射表 */ type DictMap = Record; /** * 将后端简历数据转换为简历生成器需要的格式(匹配 EMBED_INTEGRATION.md 第6节 schema) */ function toGeneratorFormat(data: API.CmsAppUser.ResumeData, dicts: { sex: DictMap; education: DictMap; area: DictMap; political: DictMap; nation: DictMap }): Record { const sections: any[] = []; // 教育经历 const education = (data.educationsList || []).map((item) => ({ id: String(item.id), school: item.schoolName || '', degree: item.degree || '', major: item.major || '', startDate: item.startTime || '', endDate: item.endTime || '', description: item.content || '', })); // 工作经历 const experience = (data.experiencesList || []).map((item) => ({ id: String(item.id), company: item.companyName || '', position: item.position || '', startDate: item.startDate || '', endDate: item.endDate || '', description: item.description || '', })); // 技能 const skills = (data.appSkillsList || []).map((item) => ({ id: String(item.id), name: item.name || '', level: item.levels || '', })); // 培训经历(生成器原生 training 字段) const training = (data.trainsList || []).map((t) => ({ id: String(t.id), institution: t.trainOrg || '', course: t.trainCourse || '', startDate: t.startTime || '', endDate: t.endTime || '', description: t.trainContent || '', })); // sections 决定区块显示顺序和显隐 sections.push({ id: 'basics', type: 'basics', title: '基本信息', hidden: false }); if (education.length > 0) sections.push({ id: 'education', type: 'education', title: '教育经历', hidden: false }); if (experience.length > 0) sections.push({ id: 'experience', type: 'experience', title: '工作经历', hidden: false }); if (training.length > 0) sections.push({ id: 'training', type: 'training', title: '培训经历', hidden: false }); if (skills.length > 0) sections.push({ id: 'skills', type: 'skills', title: '技能特长', hidden: false }); // code → 汉字映射 const sexLabel = dicts.sex[data.sex] || ''; const educationLabel = dicts.education[data.education] || ''; const politicalLabel = dicts.political[data.politicalAffiliation] || ''; const nationLabel = dicts.nation[data.nation] || data.nation || ''; // summary:展示性别、学历、工作年限等补充信息(生成器 schema 仅 name/title/phone/email/location/summary 等标准字段,自定义字段会被 Zod 校验丢弃) const summaryParts: string[] = []; if (sexLabel) summaryParts.push(sexLabel); if (educationLabel) summaryParts.push(educationLabel); if (data.workExperience) summaryParts.push(`${data.workExperience}年工作经验`); const salaryStr = data.salaryMin || data.salaryMax ? `${data.salaryMin || ''}${data.salaryMax ? '-' + data.salaryMax : ''}` : ''; return { basics: { name: data.name || '', phone: data.phone || '', email: data.email || '', avatarUrl: data.avatar || '', title: data.jobTitle?.[0] || '', ethnicity: nationLabel, birthDate: data.birthDate || '', age: data.age || '', politicalStatus: politicalLabel, expectedPosition: data.jobTitle?.[0] || '', expectedSalary: salaryStr, summary: summaryParts.join(' | '), // 以下为自定义扩展字段,生成器 Zod schema 会丢弃,但 save 时从 summary 回读 _gender: sexLabel, _educationLevel: educationLabel, _yearsOfExperience: data.workExperience || '', }, education, experience, training, projects: [], skills, certificates: [], customSections: [], settings: { templateId: 'modern', primaryColor: '#0f766e', pageSize: 'A4', language: 'zh', fontFamily: '', sections, }, }; } /** * 将简历生成器返回的数据转为后端保存格式(汉字 → code 反向映射) */ function fromGeneratorFormat(resume: any, dicts: { sex: DictMap; education: DictMap; area: DictMap; political: DictMap; nation: DictMap }): { appUser: Record; appUserEducations: any[]; appUserTrains: any[]; experiencesList: any[]; appSkillsList: any[]; } { const basics = resume?.basics || {}; // 反向 lookup:汉字 → code const reverseLookup = (map: DictMap, text: string): string => { if (!text) return ''; for (const [code, label] of Object.entries(map)) { if (label === text) return code; } return text; // 找不到则原样返回 }; // 性别:优先从 _gender 读取,生成器丢弃后从 summary 回退 const sexText = basics._gender || ''; const sexFromSummary = !sexText ? ((basics.summary || '').includes('男') ? '男' : (basics.summary || '').includes('女') ? '女' : '') : ''; const sexFinal = sexText || sexFromSummary; // 学历:优先从 _educationLevel,回退到 summary 中的学历标签 const educationText = basics._educationLevel || ''; const politicalText = basics.politicalStatus || ''; const nationText = basics.ethnicity || ''; // 工作年限:从 _yearsOfExperience 或 summary 中匹配 "X年工作经验" const yearsFromField = basics._yearsOfExperience || ''; const yearsFromSummary = !yearsFromField ? ((basics.summary || '').match(/(\d+)年工作经验/)?.[1] || '') : ''; const workExperienceFinal = yearsFromField || yearsFromSummary; return { appUser: { name: basics.name || undefined, phone: basics.phone || undefined, email: basics.email || undefined, avatar: basics.avatarUrl || undefined, sex: reverseLookup(dicts.sex, sexFinal) || (sexFinal === '男' ? '0' : sexFinal === '女' ? '1' : '') || undefined, age: basics.age || undefined, birthDate: basics.birthDate || undefined, education: reverseLookup(dicts.education, educationText) || educationText || undefined, workExperience: workExperienceFinal || undefined, politicalAffiliation: reverseLookup(dicts.political, politicalText) || politicalText || undefined, nation: reverseLookup(dicts.nation, nationText) || nationText || undefined, address: basics.location || undefined, }, appUserEducations: (resume?.education || []).map((e: any) => ({ id: e.id ? Number(e.id) : undefined, schoolName: e.school || undefined, educationLevel: undefined, major: e.major || undefined, degree: e.degree || undefined, studyType: undefined, startTime: e.startDate || undefined, endTime: e.endDate || undefined, content: e.description || undefined, sort: 0, })), appUserTrains: (resume?.training || []).map((t: any) => ({ id: t.id ? Number(t.id) : undefined, trainOrg: t.institution || undefined, trainCourse: t.course || undefined, startTime: t.startDate || undefined, endTime: t.endDate || undefined, trainCert: undefined, trainContent: t.description || undefined, sort: 0, })), experiencesList: (resume?.experience || []).map((exp: any) => ({ id: exp.id ? Number(exp.id) : undefined, companyName: exp.company || undefined, position: exp.position || undefined, startDate: exp.startDate || undefined, endDate: exp.endDate || undefined, description: exp.description || undefined, })), appSkillsList: (resume?.skills || []).map((s: any) => ({ id: s.id ? Number(s.id) : undefined, name: s.name, levels: s.level, })), }; } function AppUserResume() { console.log('===== AppUserResume 组件渲染 ====='); const params = useParams<{ userId: string }>(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const forceReadOnly = searchParams.get('readOnly') === '1'; const userId = Number(params.userId); console.log('userId from params:', userId); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [readOnly, setReadOnly] = useState(forceReadOnly); const [resumeData, setResumeData] = useState(null); const [generatorReady, setGeneratorReady] = useState(false); const hostRef = useRef