diff --git a/src/api/api.ts b/src/api/api.ts index 64dbe5e..9879bce 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -11,16 +11,16 @@ import type { AbilityAssessment, AITool, Article, - AssessmentTool, Badge, - Category, + CompareData, Conversation, + OfferCompareData, DimensionScore, EmployeeReview, EnterpriseEntry, HollandAssessment, - HomeAITool, - HotJob, + HollandIntro, + HollandType, Interview, Job, JobDetail, @@ -28,17 +28,89 @@ import type { JobListParams, JobOverallScore, JobPreference, - MatchData, MatchResult, MenuItem, Notification, OverallScore, + ConversationDetail, + ChatMessage, + RankOverview, + Resume, + ResumeExportFormat, + ResumeExportSettings, + ResumeOptimizeResult, SalaryData, - UserInfo, + SalaryBracket, + SalaryCalcInput, + SalaryCalcResult, + SalaryInsuranceItem, + SalaryStructureItem, + SalaryTaxItem, UserProfile, UserStats, + WeightDimension, + WeightSettings, + WeightTemplate, + SkillCloudData, + SkillCloudFilter, + SkillWord, + SkillCategory, + TopSkill, + ChecklistGroup, + OnboardingChecklist, + JobProgress, + IdentityOption, + AIInterviewData, + InterviewSession, + HREvaluation, + EvaluationAppealForm, + AppealDimension, + GameLevel, + GameLevelResult, + GameProgress, + SubmitGameLevelPayload, + LegalBrand, + LegalConversation, + LegalFAQ, + LegalMessage, + LegalPlan, + LegalService, + LegalTemplate, + ToggleSetting, + ActionSetting, + AboutInfo, + UpdateTogglePayload, + PrivacyCenter, + PersonalDataExport, + CandidateAbility, + CandidateDetail, + CandidateReview, + EnterpriseOverview, + EnterpriseQuickAction, + EnterpriseStat, + InterviewScoreData, + InterviewScoreDimension, + InterviewScoreTag, + JobEvaluationDimension, + JobEvaluationManagement, + JobEvaluationSummary, + JobEvaluationEmptyState, + JobPublishFormData, + JobQualityEstimate, + JobReview, + JobManagement, + TalentRecommendation, + EnterpriseCommunication, + EnterpriseCertificationBenefit, + EnterpriseCertificationData, + EnterpriseCertificationForm, + EnterpriseCertificationStatus, + MatchModeOption, + OperationRules, + AuditReport, } from '../types'; import { MOCK_DELAY, SALARY_RANGES } from '../constants'; +import { colors } from '../theme'; /** 接口地址(通过 expo-constants 从 app.config.js 的 extra 读取) */ export const API_BASE_URL: string = @@ -46,18 +118,6 @@ export const API_BASE_URL: string = /* ===================== 模拟数据(真实后端就绪后删除) ===================== */ -/** 岗位分类(8 个,首页两行四列网格) */ -const MOCK_CATEGORIES: Category[] = [ - { id: 'it', name: '互联网/IT', icon: 'laptop-outline' }, - { id: 'finance', name: '金融', icon: 'cash-outline' }, - { id: 'edu', name: '教育', icon: 'school-outline' }, - { id: 'health', name: '医疗健康', icon: 'medkit-outline' }, - { id: 'mfg', name: '制造业', icon: 'construct-outline' }, - { id: 'house', name: '房地产', icon: 'home-outline' }, - { id: 'retail', name: '零售', icon: 'cart-outline' }, - { id: 'media', name: '文化传媒', icon: 'film-outline' }, -]; - /** 岗位列表(原始数据,不含 level;level 由接口层按分数计算) */ const MOCK_JOBS: Omit[] = [ { @@ -188,92 +248,6 @@ const MOCK_JOBS: Omit[] = [ }, ]; -/** 岗位匹配度 */ -const MOCK_MATCH_DATA: MatchData = { - position: '高级前端工程师', - matchRate: 95, - subtitle: '双向匹配成功·立即沟通', -}; - -/** 高分优质岗位(首页横向卡片;薪资/评分文案已由接口层格式化) */ -const MOCK_HOT_JOBS: HotJob[] = [ - { - id: 'hot-1', - title: '高级前端工程师', - score: 92, - scoreLabel: '优秀', - salary: '25-40K·15薪', - company: '字节跳动', - location: '北京', - experience: '3-5年', - benefits: ['React', 'TypeScript'], - }, - { - id: 'hot-2', - title: '产品经理', - score: 90, - scoreLabel: '优秀', - salary: '20-35K·14薪', - company: '阿里巴巴', - location: '杭州', - experience: '3-5年', - benefits: ['B端产品', 'SaaS'], - }, - { - id: 'hot-3', - title: 'UI/UX设计师', - score: 85, - scoreLabel: '优秀', - salary: '18-30K·13薪', - company: '腾讯', - location: '深圳', - experience: '3-5年', - benefits: ['弹性工作', 'Figma·交互设计'], - }, -]; - -/** 成长测评工具(首页横向卡片) */ -const MOCK_ASSESSMENT_TOOLS: AssessmentTool[] = [ - { - id: 'assess-holland', - title: '霍兰德职业兴趣测评', - description: '发现你的职业兴趣方向', - icon: 'compass-outline', - action: '立即测评 →', - }, - { - id: 'assess-ability', - title: '四维能力测评', - description: '全方位评估你的职场能力', - icon: 'bar-chart-outline', - action: '立即测评 →', - }, -]; - -/** AI 智能工具(首页横向卡片) */ -const MOCK_HOME_AI_TOOLS: HomeAITool[] = [ - { - id: 'home-ai-resume', - title: 'AI简历优化', - description: '智能提升简历竞争力', - icon: 'document-text-outline', - action: '立即测评 →', - }, - { - id: 'home-ai-interview', - title: '模拟面试', - description: 'AI面试官真实场景演练', - icon: 'chatbubble-ellipses-outline', - action: '立即测评 →', - }, -]; - -/** 当前用户 */ -const MOCK_USER_INFO: UserInfo = { - name: '李明', - slogan: '精准高效·全链条一站式求职服务', -}; - /** 会话列表 */ const MOCK_CONVERSATIONS: Conversation[] = [ { @@ -380,19 +354,188 @@ const MOCK_MATCH_RESULT: MatchResult = { companies: ['字节跳动', '腾讯科技', '阿里巴巴'], }; +/** 霍兰德六维名称映射 */ +const HOLLAND_NAMES: Record = { + R: '现实型', + I: '研究型', + A: '艺术型', + S: '社会型', + E: '企业型', + C: '常规型', +}; + /** 霍兰德职业兴趣测评 */ const MOCK_HOLLAND: HollandAssessment = { totalParticipants: 12846, + currentQuestionIndex: 11, + totalQuestions: 60, + question: { + id: 'hq-12', + order: 12, + text: '我喜欢动手操作机械和工具', + }, + options: [ + { value: 1, label: '非常不喜欢' }, + { value: 2, label: '不喜欢' }, + { value: 3, label: '一般' }, + { value: 4, label: '喜欢' }, + { value: 5, label: '非常喜欢' }, + ], + dimensions: [ + { type: 'R', name: HOLLAND_NAMES.R, score: 72, percent: 72 }, + { type: 'I', name: HOLLAND_NAMES.I, score: 88, percent: 88 }, + { type: 'A', name: HOLLAND_NAMES.A, score: 45, percent: 45 }, + { type: 'S', name: HOLLAND_NAMES.S, score: 85, percent: 85 }, + { type: 'E', name: HOLLAND_NAMES.E, score: 60, percent: 60 }, + { type: 'C', name: HOLLAND_NAMES.C, score: 50, percent: 50 }, + ], + result: { + code: 'ISR', + name: '研究型 + 社会型 + 现实型', + dimensions: [ + { type: 'I', name: HOLLAND_NAMES.I, score: 88, percent: 88 }, + { type: 'S', name: HOLLAND_NAMES.S, score: 85, percent: 85 }, + { type: 'R', name: HOLLAND_NAMES.R, score: 72, percent: 72 }, + { type: 'E', name: HOLLAND_NAMES.E, score: 60, percent: 60 }, + { type: 'C', name: HOLLAND_NAMES.C, score: 50, percent: 50 }, + { type: 'A', name: HOLLAND_NAMES.A, score: 45, percent: 45 }, + ], + analyses: [ + { + type: 'I', + name: HOLLAND_NAMES.I, + score: 88, + description: '你喜欢思考与分析,偏好独立研究、动手实验。适合技术类、研发类、数据分析类工作。', + careers: ['研发工程师', '数据分析师', '科学家'], + }, + { + type: 'S', + name: HOLLAND_NAMES.S, + score: 85, + description: '你喜欢与人交流,乐于助人,关心他人感受。适合教育、培训、咨询、社工等岗位。', + careers: ['教师', '咨询师', 'HRBP'], + }, + { + type: 'R', + name: HOLLAND_NAMES.R, + score: 72, + description: '你喜欢动手操作,注重实际效果,偏好具体任务。适合工程师、技术员、设计师等。', + careers: ['工程师', '技术员', '设计师'], + }, + ], + jobs: [ + { + id: '1', + title: '数据分析师', + companyName: '字节跳动', + salary: '25-40K', + city: '北京', + tags: ['数据分析', 'SQL', 'Python'], + match: 95, + }, + { + id: '2', + title: '前端开发工程师', + companyName: '腾讯科技', + salary: '22-35K', + city: '深圳', + tags: ['React', 'TypeScript'], + match: 92, + }, + { + id: '3', + title: '产品经理', + companyName: '阿里巴巴', + salary: '20-35K', + city: '杭州', + tags: ['B端产品', 'SaaS'], + match: 88, + }, + ], + actions: [ + { id: 'a1', title: '完善技能图谱,精准匹配岗位', desc: '根据测评结果补充优势技能标签', icon: 'construct-outline', route: '/resume' }, + { id: 'a2', title: '优化简历,提升投递成功率', desc: '突出研究型与社会型项目经历', icon: 'document-text-outline', route: '/resume' }, + { id: 'a3', title: '查看行业新服务,合理定价', desc: '了解目标岗位市场薪资区间', icon: 'cash-outline', route: '/tools/salary-calculator' }, + ], + }, }; /** 四维能力测评(原始数据,不含 level;level 由接口层按总分计算) */ const MOCK_ABILITY: Omit = { overallScore: 86, + percentile: 78, dimensions: [ - { label: '专业能力', value: 82 }, - { label: '项目经验', value: 75 }, - { label: '技能广度', value: 88 }, - { label: '成长潜力', value: 90 }, + { + label: '专业能力', + value: 88, + percentileText: '超过 80% 同岗位求职者', + subItems: [ + { id: 'pro-1', label: '核心技术掌握度', score: 90, maxScore: 100 }, + { id: 'pro-2', label: '工程化能力', score: 86, maxScore: 100 }, + { id: 'pro-3', label: '算法基础', score: 75, maxScore: 100 }, + ], + }, + { + label: '项目经验', + value: 75, + percentileText: '超过 62% 同岗位求职者', + subItems: [ + { id: 'exp-1', label: '项目规模', score: 70, maxScore: 100 }, + { id: 'exp-2', label: '业务理解', score: 78, maxScore: 100 }, + { id: 'exp-3', label: '技术深度', score: 76, maxScore: 100 }, + ], + }, + { + label: '技能广度', + value: 82, + percentileText: '超过 75% 同岗位求职者', + subItems: [ + { id: 'skill-1', label: '前端技术栈', score: 90, maxScore: 100 }, + { id: 'skill-2', label: '后端协作', score: 75, maxScore: 100 }, + { id: 'skill-3', label: '设计能力', score: 70, maxScore: 100 }, + ], + }, + { + label: '成长潜力', + value: 90, + percentileText: '超过 85% 同岗位求职者', + subItems: [ + { id: 'grow-1', label: '学习敏锐度', score: 92, maxScore: 100 }, + { id: 'grow-2', label: '问题解决', score: 88, maxScore: 100 }, + { id: 'grow-3', label: '创新意识', score: 85, maxScore: 100 }, + ], + }, + ], + trends: [ + { month: '3月', professional: 68, project: 62, skill: 70, growth: 72 }, + { month: '4月', professional: 72, project: 65, skill: 74, growth: 76 }, + { month: '5月', professional: 76, project: 68, skill: 76, growth: 80 }, + { month: '6月', professional: 80, project: 70, skill: 78, growth: 82 }, + { month: '7月', professional: 84, project: 72, skill: 80, growth: 86 }, + { month: '8月', professional: 88, project: 75, skill: 82, growth: 90 }, + ], + improvements: [ + { + id: 'imp-1', + title: '增加项目端到端经验', + description: '建议参与中大型项目全流程,提升技术深度和团队协作经验。', + icon: 'git-merge-outline', + route: '/game', + }, + { + id: 'imp-2', + title: '夯实算法基础', + description: '每天刷 1-2 道算法题,重点关注数组、字符串与常见数据结构。', + icon: 'code-slash-outline', + route: '/game', + }, + { + id: 'imp-3', + title: '补充跨端/全栈技能', + description: '学习 React Native 或 Node.js,拓展技能广度,提升全栈协作能力。', + icon: 'layers-outline', + route: '/game', + }, ], }; @@ -460,13 +603,34 @@ const MOCK_USER_STATS: UserStats = { offers: 3, }; -/** 功能菜单列表 */ +/** 功能菜单列表(设置类入口已移至设置页,这里只保留业务功能) */ const MOCK_MENU_ITEMS: MenuItem[] = [ { id: 'resume', title: '我的简历', icon: 'document-text-outline', route: '/resume' }, { id: 'favorites', title: '我的收藏', icon: 'heart-outline', route: '/favorites' }, { id: 'history', title: '浏览历史', icon: 'time-outline', route: '/history' }, + { id: 'compare', title: '岗位对比', icon: 'git-compare-outline', route: '/jobs/compare' }, + { id: 'offer-compare', title: 'Offer对比', icon: 'gift-outline', route: '/jobs/offer-compare' }, { id: 'assessment', title: '我的测评', icon: 'bar-chart-outline', route: '/assessment' }, - { id: 'settings', title: '设置', icon: 'settings-outline', route: '/settings' }, + { id: 'interview', title: 'AI模拟面试', icon: 'videocam-outline', route: '/interview' }, + { id: 'rank', title: '竞争力排名', icon: 'podium-outline', route: '/jobs/rank' }, + { id: 'hr-evaluation', title: 'HR评价详情', icon: 'ribbon-outline', route: '/hr-evaluation' }, + { id: 'onboarding', title: '入职准备清单', icon: 'checkmark-done-outline', route: '/onboarding' }, + { id: 'skill-cloud', title: '行业技能词云', icon: 'cloud-outline', route: '/tools/skill-cloud' }, + { id: 'salary', title: '薪资计算器', icon: 'calculator-outline', route: '/tools/salary-calculator' }, + { id: 'progress', title: '求职进度', icon: 'list-outline', route: '/progress' }, + { id: 'game', title: '通关小游戏', icon: 'trophy-outline', route: '/game' }, + { id: 'legal', title: '阿布阿诺法律服务', icon: 'shield-checkmark-outline', route: '/legal' }, + { id: 'enterprise-overview', title: '企业数据总览', icon: 'business-outline', route: '/enterprise-overview' }, + { id: 'job-publish', title: '发布岗位', icon: 'add-circle-outline', route: '/job-publish' }, + { id: 'job-evaluations', title: '岗位评价管理', icon: 'chatbubbles-outline', route: '/job-evaluations' }, + { id: 'candidate-detail', title: '候选人详情', icon: 'person-outline', route: '/candidate-detail' }, + { id: 'interview-score', title: '面试评分', icon: 'create-outline', route: '/interview-score' }, + { id: 'job-management', title: '岗位管理', icon: 'business-outline', route: '/job-management' }, + { id: 'talent-recommendation', title: '人才推荐', icon: 'people-outline', route: '/talent-recommendation' }, + { id: 'enterprise-chat', title: '企业沟通', icon: 'chatbubble-outline', route: '/enterprise-chat' }, + { id: 'operation-rules', title: '运营规则配置', icon: 'options-outline', route: '/operation-rules' }, + { id: 'audit-report', title: '数据审计报告', icon: 'shield-checkmark-outline', route: '/audit-report' }, + { id: 'enterprise-certification', title: '企业认证', icon: 'business-outline', route: '/enterprise-certification' }, ]; /** 企业端入口 */ @@ -527,18 +691,20 @@ const MOCK_JOB_DETAILS: Record> = { }, }; -/** 岗位综合评分(6 维度,供雷达图) */ +/** 岗位综合评分(8 维度,供雷达图) */ const MOCK_JOB_OVERALL_SCORE: JobOverallScore = { totalScore: 92, level: '优秀', rank: '超过91%同行业岗位', dimensions: [ - { id: 'auth', name: '岗位真实性', value: 95 }, - { id: 'salary', name: '薪资竞争力', value: 88 }, - { id: 'growth', name: '发展前景', value: 92 }, - { id: 'company', name: '公司实力', value: 90 }, - { id: 'env', name: '工作环境', value: 85 }, - { id: 'benefit', name: '福利待遇', value: 82 }, + { id: 'salary', name: '薪资竞争力', value: 92 }, + { id: 'growth', name: '发展空间', value: 90 }, + { id: 'atmosphere', name: '工作氛围', value: 86 }, + { id: 'benefit', name: '福利待遇', value: 84 }, + { id: 'team', name: '团队水平', value: 88 }, + { id: 'culture', name: '企业文化', value: 85 }, + { id: 'intensity', name: '工作强度', value: 78 }, + { id: 'authenticity', name: '岗位真实性', value: 95 }, ], }; @@ -582,6 +748,42 @@ const MOCK_EMPLOYEE_REVIEWS: EmployeeReview[] = [ }, ]; +/** 岗位对比数据(3 个岗位:九大维度评分 + 基本信息 + 福利) */ +const MOCK_COMPARE_DATA: CompareData = { + jobs: [ + { id: '1', title: '高级前端工程师', company: '字节跳动', salary: '25-40K', color: colors.primary }, + { id: '2', title: '产品经理', company: '腾讯科技', salary: '20-35K', color: colors.compareRed }, + { id: '3', title: 'UI设计师', company: '阿里巴巴', salary: '15-25K', color: colors.compareOrange }, + ], + dimensionScores: [ + { dimension: '综合评分', scores: [92, 85, 78] }, + { dimension: '薪资竞争力', scores: [88, 82, 70] }, + { dimension: '发展空间', scores: [90, 86, 75] }, + { dimension: '工作氛围', scores: [85, 88, 90] }, + { dimension: '福利待遇', scores: [86, 80, 72] }, + { dimension: '团队水平', scores: [91, 84, 76] }, + { dimension: '企业文化', scores: [84, 87, 80] }, + { dimension: '工作强度', scores: [72, 78, 85] }, + { dimension: '岗位真实性', scores: [95, 90, 88] }, + ], + basicInfo: { + fields: ['公司规模', '所在行业', '工作地点', '经验要求', '学历要求', '工作类型'], + values: [ + ['10000人以上', '10000人以上', '10000人以上'], + ['互联网/IT', '互联网/IT', '互联网/IT'], + ['北京', '深圳', '杭州'], + ['3-5年', '3-5年', '3-5年'], + ['本科', '本科', '本科'], + ['全职', '全职', '全职'], + ], + }, + benefits: [ + ['五险一金', '年终奖金', '弹性工作', '免费三餐'], + ['五险一金', '年终奖金', '弹性工作', '股票期权'], + ['五险一金', '年终奖金', '弹性工作'], + ], +}; + /* ===================== 业务规则 ===================== */ /** 分数 → 等级映射(业务规则,接口层计算) */ @@ -704,72 +906,6 @@ export async function fetchJobList( }); } -/** - * 获取岗位分类列表 - * @method GET - * @url /api/categories - * @returns Promise 分类数组 - */ -export async function fetchCategories(): Promise { - // TODO: const res = await fetch(`${API_BASE_URL}/api/categories`, { method: 'GET' }); return res.json(); - return mockRequest(MOCK_CATEGORIES); -} - -/** - * 获取当前用户与岗位的匹配度 - * @method GET - * @url /api/match/position - * @returns Promise 匹配度信息 - */ -export async function fetchMatchData(): Promise { - // TODO: const res = await fetch(`${API_BASE_URL}/api/match/position`, { method: 'GET' }); return res.json(); - return mockRequest(MOCK_MATCH_DATA); -} - -/** - * 获取高分优质岗位列表(首页横向卡片) - * @method GET - * @url /api/home/hot-jobs - * @returns Promise 热门岗位数组 - */ -export async function fetchHotJobs(): Promise { - // TODO: const res = await fetch(`${API_BASE_URL}/api/home/hot-jobs`, { method: 'GET' }); return res.json(); - return mockRequest(MOCK_HOT_JOBS); -} - -/** - * 获取成长测评工具列表(首页横向卡片) - * @method GET - * @url /api/home/assessment-tools - * @returns Promise 测评工具数组 - */ -export async function fetchAssessmentTools(): Promise { - // TODO: const res = await fetch(`${API_BASE_URL}/api/home/assessment-tools`, { method: 'GET' }); return res.json(); - return mockRequest(MOCK_ASSESSMENT_TOOLS); -} - -/** - * 获取 AI 智能工具列表(首页横向卡片) - * @method GET - * @url /api/home/ai-tools - * @returns Promise AI 工具数组 - */ -export async function fetchHomeAITools(): Promise { - // TODO: const res = await fetch(`${API_BASE_URL}/api/home/ai-tools`, { method: 'GET' }); return res.json(); - return mockRequest(MOCK_HOME_AI_TOOLS); -} - -/** - * 获取当前登录用户信息 - * @method GET - * @url /api/user/profile - * @returns Promise 用户信息 - */ -export async function fetchUserInfo(): Promise { - // TODO: const res = await fetch(`${API_BASE_URL}/api/user/profile`, { method: 'GET' }); return res.json(); - return mockRequest(MOCK_USER_INFO); -} - /** * 获取会话列表 * @method GET @@ -825,6 +961,36 @@ export async function fetchHollandAssessment(): Promise { return mockRequest(MOCK_HOLLAND); } +/** + * 提交霍兰德测评答案(mock 阶段返回下一题或最终结果) + * @method POST + * @url /api/growth/holland/answers + * @param questionId {string} 题目 id + * @param value {number} 选项分值 + * @returns Promise 更新后的测评进度或结果 + */ +export async function submitHollandAnswer( + _questionId: string, + _value: number, +): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/growth/holland/answers`, { method: 'POST', body: JSON.stringify({ questionId, value }) }); return res.json(); + return mockRequest(MOCK_HOLLAND); +} + +/** + * 获取霍兰德测评结果 + * @method GET + * @url /api/growth/holland/result + * @returns Promise> 测评结果 + */ +export async function fetchHollandResult(): Promise> { + // TODO: const res = await fetch(`${API_BASE_URL}/api/growth/holland/result`, { method: 'GET' }); return res.json(); + if (!MOCK_HOLLAND.result) { + throw new Error('暂无测评结果'); + } + return mockRequest(MOCK_HOLLAND.result); +} + /** * 获取四维能力测评结果(等级由总分在接口层计算) * @method GET @@ -978,7 +1144,7 @@ export async function fetchJobDetail(id: string): Promise { } /** - * 获取岗位综合评分(含 6 维度雷达图数据) + * 获取岗位综合评分(含 8 维度雷达图数据) * @method GET * @url /api/jobs/:id/score * @param id { string } 岗位 id @@ -1015,3 +1181,3139 @@ export async function fetchEmployeeReviews(id: string): Promise 岗位对比数据 + */ +export async function fetchCompareData(jobIds: string[]): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/jobs/compare?jobIds=${jobIds.join(',')}`, { method: 'GET' }); return res.json(); + + // 按传入 jobIds 过滤对比数据(无匹配时兜底返回全部,避免空页面) + const selected = MOCK_COMPARE_DATA.jobs + .map((job, index) => (jobIds.includes(job.id) ? index : -1)) + .filter((index) => index >= 0); + const indices = + selected.length > 0 ? selected : MOCK_COMPARE_DATA.jobs.map((_, index) => index); + + const data: CompareData = { + jobs: indices.map((index) => MOCK_COMPARE_DATA.jobs[index]), + dimensionScores: MOCK_COMPARE_DATA.dimensionScores.map((dim) => ({ + dimension: dim.dimension, + scores: indices.map((index) => dim.scores[index]), + })), + basicInfo: { + fields: MOCK_COMPARE_DATA.basicInfo.fields, + values: MOCK_COMPARE_DATA.basicInfo.values.map((row) => indices.map((index) => row[index])), + }, + benefits: indices.map((index) => MOCK_COMPARE_DATA.benefits[index]), + }; + + return mockRequest(data); +} + +/* ===================== Offer 对比 ===================== */ + +/** Offer 对比 mock 数据(字节跳动 vs 腾讯科技) */ +const MOCK_OFFER_COMPARE_DATA: OfferCompareData = { + offers: [ + { + id: 'offer-1', + company: '字节跳动', + position: '高级前端', + salary: '25-40K', + selected: true, + color: colors.primary, + }, + { + id: 'offer-2', + company: '腾讯科技', + position: '产品经理', + salary: '20-35K', + selected: true, + color: colors.compareRed, + }, + ], + scores: [ + { score: 92, level: '优秀', stars: 5 }, + { score: 85, level: '良好', stars: 4 }, + ], + benefitGroups: [ + { + title: '薪资', + rows: [ + { label: '基本工资', values: ['25K', '22K'], highlights: [true, false] }, + { label: '年终奖', values: ['2-4个月', '3-6个月'] }, + { label: '股票期权', values: ['有(RSU)', '有(期权)'] }, + { label: '签字费', values: ['5万', '3万'], highlights: [true, false] }, + { label: '年度调薪', values: ['有', '有'] }, + ], + }, + { + title: '福利', + rows: [ + { label: '五险一金', values: ['全额', '全额'] }, + { label: '补充医疗', values: ['有', '有'] }, + { label: '带薪年假', values: ['15天', '18天'], highlights: [false, true] }, + { label: '免费三餐', values: ['有', '有'] }, + { label: '健身房', values: ['有', '有'] }, + { label: '住房补贴', values: ['有(1500/月)', '无'], highlights: [true, false] }, + ], + }, + { + title: '发展', + rows: [ + { label: '晋升通道', values: ['技术/管理双线', '技术为主'], highlights: [true, false] }, + { label: '培训体系', values: ['完善', '完善'] }, + { label: '转岗机会', values: ['多', '一般'], highlights: [true, false] }, + ], + }, + ], + workLifeBalance: [ + { label: '平均工时', values: ['9.5小时', '10.2小时'], highlights: [true, false] }, + { label: '出差频率', values: ['经常', '偶尔'] }, + { label: '周末加班', values: ['大小周', '双休'], highlights: [false, true] }, + ], + aiSuggestion: { + summary: '综合薪资、福利、发展空间等多维度评估,字节跳动的 Offer 综合评分更高。', + detail: + '但腾讯的工作生活平衡更好,建议根据个人职业规划选择。若追求短期收入与快速成长,字节更优;若看重工作节奏与长期稳定,腾讯更合适。', + reportAction: '查看完整分析报告', + }, + actions: { + exportLabel: '导出对比报告', + acceptLabel: '接受字节Offer', + acceptOfferId: 'offer-1', + }, +}; + +/** + * 获取 Offer 对比数据 + * @method GET + * @url /api/offers/compare + * @param offerIds { string[] } 参与对比的 Offer id 数组 + * @returns Promise Offer 对比数据 + */ +export async function fetchOfferCompareData(offerIds?: string[]): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/offers/compare?offerIds=${offerIds?.join(',') ?? ''}`, { method: 'GET' }); return res.json(); + + if (!offerIds || offerIds.length === 0) { + return mockRequest(MOCK_OFFER_COMPARE_DATA); + } + + const filteredOffers = MOCK_OFFER_COMPARE_DATA.offers.filter((o) => offerIds.includes(o.id)); + const indices = MOCK_OFFER_COMPARE_DATA.offers + .map((o, index) => (offerIds.includes(o.id) ? index : -1)) + .filter((index) => index >= 0); + + const data: OfferCompareData = { + offers: filteredOffers.length > 0 ? filteredOffers : MOCK_OFFER_COMPARE_DATA.offers, + scores: indices.length > 0 ? indices.map((i) => MOCK_OFFER_COMPARE_DATA.scores[i]) : MOCK_OFFER_COMPARE_DATA.scores, + benefitGroups: MOCK_OFFER_COMPARE_DATA.benefitGroups.map((group) => ({ + title: group.title, + rows: group.rows.map((row) => ({ + label: row.label, + values: indices.length > 0 ? indices.map((i) => row.values[i]) : row.values, + highlights: row.highlights ? (indices.length > 0 ? indices.map((i) => row.highlights![i]) : row.highlights) : undefined, + })), + })), + workLifeBalance: MOCK_OFFER_COMPARE_DATA.workLifeBalance.map((row) => ({ + label: row.label, + values: indices.length > 0 ? indices.map((i) => row.values[i]) : row.values, + highlights: row.highlights ? (indices.length > 0 ? indices.map((i) => row.highlights![i]) : row.highlights) : undefined, + })), + aiSuggestion: MOCK_OFFER_COMPARE_DATA.aiSuggestion, + actions: MOCK_OFFER_COMPARE_DATA.actions, + }; + + return mockRequest(data); +} + +/* ===================== 评分权重设置 ===================== */ + +/** 默认权重维度(9 个,总和 100%) */ +const MOCK_WEIGHT_DIMENSIONS: WeightDimension[] = [ + { id: 'salary', name: '薪资竞争力', weight: 20 }, + { id: 'growth', name: '发展空间', weight: 15 }, + { id: 'atmosphere', name: '工作氛围', weight: 12 }, + { id: 'benefit', name: '福利待遇', weight: 12 }, + { id: 'team', name: '团队水平', weight: 10 }, + { id: 'culture', name: '企业文化', weight: 10 }, + { id: 'intensity', name: '工作强度', weight: 8, isReversed: true }, + { id: 'location', name: '地理位置', weight: 8 }, + { id: 'authenticity', name: '岗位真实性', weight: 5 }, +]; + +/** 预设权重模板(3 个,各维度权重按维度名称匹配) */ +const MOCK_WEIGHT_TEMPLATES: WeightTemplate[] = [ + { + id: 'salary-first', + name: '薪资优先', + description: '薪资30% · 发展15%', + weights: { + 薪资竞争力: 30, + 发展空间: 15, + 工作氛围: 10, + 福利待遇: 12, + 团队水平: 10, + 企业文化: 8, + 工作强度: 5, + 地理位置: 5, + 岗位真实性: 5, + }, + }, + { + id: 'growth-first', + name: '成长优先', + description: '发展30% · 团队15%', + weights: { + 薪资竞争力: 12, + 发展空间: 30, + 工作氛围: 10, + 福利待遇: 10, + 团队水平: 15, + 企业文化: 8, + 工作强度: 5, + 地理位置: 5, + 岗位真实性: 5, + }, + }, + { + id: 'stable-first', + name: '稳定优先', + description: '福利25%', + weights: { + 薪资竞争力: 12, + 发展空间: 12, + 工作氛围: 12, + 福利待遇: 25, + 团队水平: 8, + 企业文化: 10, + 工作强度: 6, + 地理位置: 10, + 岗位真实性: 5, + }, + }, +]; + +/** + * 获取评分权重设置(含维度 + 预设模板) + * @method GET + * @url /api/jobs/weight-settings + * @returns Promise 权重设置对象 + */ +export async function fetchWeightSettings(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/jobs/weight-settings`, { method: 'GET' }); return res.json(); + return mockRequest({ + dimensions: MOCK_WEIGHT_DIMENSIONS.map((d) => ({ ...d })), + templates: MOCK_WEIGHT_TEMPLATES, + }); +} + +/** + * 保存评分权重设置(各维度权重总和应为 100%) + * @method POST + * @url /api/jobs/weight-settings + * @param dimensions { WeightDimension[] } 完整维度权重列表 + * @returns Promise 保存后的维度列表 + */ +export async function saveWeightSettings( + dimensions: WeightDimension[], +): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/jobs/weight-settings`, { + // method: 'POST', + // headers: { 'Content-Type': 'application/json' }, + // body: JSON.stringify({ dimensions }), + // }); + // return res.json(); + + return mockRequest(dimensions.map((d) => ({ ...d }))); +} + +/* ===================== 同岗位竞争力排名 ===================== */ + +/** + * 获取同岗位竞争力排名数据(综合排名 + 六维度百分位 + TOP10 候选人 + 提升建议) + * @method GET + * @url /api/jobs/rank + * @param jobId { string } 岗位 id + * @returns Promise 排名页完整数据(含数据态与空状态) + */ +export async function fetchRankData(jobId: string): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/jobs/rank?jobId=${jobId}`, { method: 'GET' }); return res.json(); + // 当前为 mock 阶段,jobId 仅用于标注真实接口入参,避免未使用参数告警 + void jobId; + return mockRequest({ + hasData: false, + emptyState: { + title: '暂无排名数据', + subtitle: '完善简历和测评信息后,系统将为你计算竞争力排名', + actionText: '去完善简历', + resumeProgress: [ + { id: 'basic', label: '基本信息', completed: true }, + { id: 'education', label: '教育经历', completed: true }, + { id: 'work', label: '工作经历', completed: false, tip: '添加工作经历可提升 20% 排名准确度' }, + { id: 'project', label: '项目经历', completed: false }, + { id: 'skill', label: '技能标签', completed: false }, + ], + }, + }); +} + +/* ===================== 行业技能词云 ===================== */ + +/** 技能分类(全部分类 + 5 个具体分类) */ +const MOCK_SKILL_CATEGORIES: SkillCategory[] = [ + { id: 'all', label: '全部' }, + { id: 'frontend', label: '前端' }, + { id: 'backend', label: '后端' }, + { id: 'devops', label: '工程工具' }, + { id: 'database', label: '数据库' }, + { id: 'design', label: '设计' }, +]; + +/** 词云筛选可选项 */ +const MOCK_SKILL_FILTERS: SkillCloudFilter = { + industries: ['互联网/IT', '金融', '教育', '医疗', '制造', '房地产'], + positions: ['高级前端工程师', '前端开发工程师', '全栈工程师', 'Java开发工程师'], + cities: ['全国', '北京', '上海', '深圳', '杭州'], + experiences: ['应届生', '1-3年', '3-5年', '5-10年'], +}; + +/** 各行业技能词(权重 0-100) */ +const MOCK_SKILL_WORDS: Record = { + '互联网/IT': [ + { id: 's1', text: 'React', weight: 96, group: 'tech' }, + { id: 's2', text: 'TypeScript', weight: 92, group: 'tech' }, + { id: 's3', text: 'Node.js', weight: 85, group: 'tech' }, + { id: 's4', text: 'Vue', weight: 78, group: 'tech' }, + { id: 's5', text: 'Webpack', weight: 64, group: 'tool' }, + { id: 's6', text: '数据分析', weight: 88, group: 'domain' }, + { id: 's7', text: '用户体验', weight: 72, group: 'soft' }, + { id: 's8', text: '需求拆解', weight: 70, group: 'soft' }, + { id: 's9', text: 'Git', weight: 90, group: 'tool' }, + { id: 's10', text: '性能优化', weight: 75, group: 'domain' }, + { id: 's11', text: '云服务', weight: 68, group: 'tech' }, + { id: 's12', text: '敏捷协作', weight: 66, group: 'soft' }, + { id: 's13', text: '前端工程化', weight: 82, group: 'tech' }, + { id: 's14', text: '跨端开发', weight: 73, group: 'tech' }, + ], + '金融': [ + { id: 'f1', text: '财务建模', weight: 94, group: 'domain' }, + { id: 'f2', text: 'Python', weight: 88, group: 'tech' }, + { id: 'f3', text: '风险管理', weight: 86, group: 'domain' }, + { id: 'f4', text: 'Excel', weight: 90, group: 'tool' }, + { id: 'f5', text: '行业研究', weight: 80, group: 'soft' }, + { id: 'f6', text: '估值', weight: 76, group: 'domain' }, + { id: 'f7', text: '沟通表达', weight: 72, group: 'soft' }, + ], + '教育': [ + { id: 'e1', text: '课程设计', weight: 90, group: 'domain' }, + { id: 'e2', text: '演讲表达', weight: 84, group: 'soft' }, + { id: 'e3', text: '社群运营', weight: 78, group: 'soft' }, + { id: 'e4', text: 'Office', weight: 86, group: 'tool' }, + { id: 'e5', text: '用户洞察', weight: 74, group: 'domain' }, + ], +}; + +/** 热门技能 TOP 10 */ +const MOCK_TOP_SKILLS: TopSkill[] = [ + { id: 't1', name: 'React', rank: 1, change: 12, demand: 96 }, + { id: 't2', name: 'TypeScript', rank: 2, change: 18, demand: 92 }, + { id: 't3', name: 'Node.js', rank: 3, change: 8, demand: 85 }, + { id: 't4', name: '前端工程化', rank: 4, change: 15, demand: 82 }, + { id: 't5', name: 'Vue', rank: 5, change: -3, demand: 78 }, + { id: 't6', name: '性能优化', rank: 6, change: 6, demand: 75 }, + { id: 't7', name: '跨端开发', rank: 7, change: 22, demand: 73 }, + { id: 't8', name: 'Webpack', rank: 8, change: -5, demand: 64 }, + { id: 't9', name: '云服务', rank: 9, change: 10, demand: 68 }, + { id: 't10', name: '敏捷协作', rank: 10, change: 4, demand: 66 }, +]; + +/** 默认行业(词云列表无匹配时兜底) */ +const DEFAULT_SKILL_INDUSTRY = '互联网/IT'; + +/** + * 获取行业技能词云 + * @method GET + * @url /api/tools/skill-cloud + * @param industry { string } 行业名称 + * @returns Promise 技能词云数据 + */ +export async function fetchSkillCloud(industry: string): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/tools/skill-cloud?industry=${encodeURIComponent(industry)}`, { method: 'GET' }); return res.json(); + const words = MOCK_SKILL_WORDS[industry] ?? MOCK_SKILL_WORDS[DEFAULT_SKILL_INDUSTRY]; + const resolvedIndustry = MOCK_SKILL_WORDS[industry] ? industry : DEFAULT_SKILL_INDUSTRY; + return mockRequest({ + industry: resolvedIndustry, + position: '高级前端工程师', + city: '全国', + experience: '3-5年', + activeCategoryId: 'all', + filters: MOCK_SKILL_FILTERS, + categories: MOCK_SKILL_CATEGORIES, + words, + topSkills: MOCK_TOP_SKILLS, + matchPercent: 80, + updatedAt: '数据更新于 2026-08', + }); +} + +/* ===================== 薪资计算器 ===================== */ + +/** 各行业平均月薪参考(计算器对比用) */ +const MOCK_SALARY_BRACKETS: SalaryBracket[] = [ + { industry: '互联网/IT', avgMonthly: 28, sampleCount: 2846 }, + { industry: '金融', avgMonthly: 35, sampleCount: 1320 }, + { industry: '教育', avgMonthly: 14, sampleCount: 980 }, + { industry: '医疗健康', avgMonthly: 18, sampleCount: 760 }, + { industry: '制造业', avgMonthly: 22, sampleCount: 1540 }, +]; + +/** + * 获取各行业平均月薪参考 + * @method GET + * @url /api/tools/salary-brackets + * @returns Promise 行业薪资参考数组 + */ +export async function fetchSalaryBrackets(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/tools/salary-brackets`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_SALARY_BRACKETS); +} + +/** + * 薪资计算(业务规则:五险一金 + 个税估算,接口层纯函数)。 + * 计算器为实时交互,故同步计算,避免 300ms mock 延迟影响输入体验。 + * @param input 薪资计算输入(元/百分比) + * @returns SalaryCalcResult 计算结果(元) + */ +export function computeSalaryResult(input: SalaryCalcInput): SalaryCalcResult { + const monthlyGross = input.monthlyGross; + // 年终奖摊入月度,得到月度税前合计 + const monthlyTotal = monthlyGross * (1 + input.bonusMonths / 12); + + // 社保个人缴纳比例:养老 8% + 医疗 2% + 失业 0.5% + const pension = input.socialBase * 0.08; + const medical = input.socialBase * 0.02; + const unemployment = input.socialBase * 0.005; + const providentFund = input.socialBase * input.providentFundRate; + const monthlySocial = pension + medical + unemployment + providentFund; + + // 个税:月度应纳税所得额 = 税前 - 五险一金 - 5000 起征点 - 专项附加扣除 + const taxable = Math.max( + 0, + monthlyTotal - monthlySocial - 5000 - input.specialDeduction, + ); + const monthlyTax = calcMonthlyTax(taxable); + const monthlyNet = Math.max(0, monthlyTotal - monthlySocial - monthlyTax); + const annualGross = monthlyGross * 12 + monthlyGross * input.bonusMonths; + const annualNet = monthlyNet * 12; + + const totalYearlyGross = annualGross; + const totalYearlyNet = annualNet; + const totalYearlySocial = monthlySocial * 12; + const totalYearlyTax = monthlyTax * 12; + + const insuranceItems: SalaryInsuranceItem[] = [ + { label: '养老保险', rate: 0.08, amount: Math.round(pension) }, + { label: '医疗保险', rate: 0.02, amount: Math.round(medical) }, + { label: '失业保险', rate: 0.005, amount: Math.round(unemployment) }, + { label: '住房公积金', rate: input.providentFundRate, amount: Math.round(providentFund) }, + ]; + + const taxItems: SalaryTaxItem[] = [ + { label: '应纳税所得额', amount: Math.round(taxable) }, + { label: '适用税率', amount: monthlyTax > 0 ? getTaxRate(taxable) : 0, rate: monthlyTax > 0 ? getTaxRate(taxable) : 0 }, + { label: '月度个税', amount: Math.round(monthlyTax) }, + { label: '年度个税', amount: Math.round(monthlyTax * 12) }, + ]; + + const structure: SalaryStructureItem[] = [ + { label: '税前工资', value: Math.round(totalYearlyGross), percent: 100, color: 'primary' }, + { label: '五险一金', value: Math.round(totalYearlySocial), percent: Math.round((totalYearlySocial / totalYearlyGross) * 100), color: 'info' }, + { label: '个人所得税', value: Math.round(totalYearlyTax), percent: Math.round((totalYearlyTax / totalYearlyGross) * 100), color: 'action' }, + { label: '实际到手', value: Math.round(totalYearlyNet), percent: Math.round((totalYearlyNet / totalYearlyGross) * 100), color: 'success' }, + ]; + + return { + input, + monthlyGross, + bonusMonths: input.bonusMonths, + monthlyTotal: Math.round(monthlyTotal), + monthlySocial: Math.round(monthlySocial), + monthlyTax: Math.round(monthlyTax), + monthlyNet: Math.round(monthlyNet), + annualGross: Math.round(annualGross), + annualNet: Math.round(annualNet), + insuranceItems, + taxItems, + structure, + }; +} + +/** 月度超额累进个税(单位元) */ +function calcMonthlyTax(t: number): number { + if (t <= 0) return 0; + if (t <= 3000) return t * 0.03; + if (t <= 12000) return t * 0.1 - 210; + if (t <= 25000) return t * 0.2 - 1410; + if (t <= 35000) return t * 0.25 - 2660; + if (t <= 55000) return t * 0.3 - 4410; + if (t <= 80000) return t * 0.35 - 7160; + return t * 0.45 - 15160; +} + +/** 获取当前应纳税所得额适用税率 */ +function getTaxRate(t: number): number { + if (t <= 0) return 0; + if (t <= 3000) return 0.03; + if (t <= 12000) return 0.1; + if (t <= 25000) return 0.2; + if (t <= 35000) return 0.25; + if (t <= 55000) return 0.3; + if (t <= 80000) return 0.35; + return 0.45; +} + +/* ===================== 求职进度 ===================== */ + +/** 求职进度(完整版:统计 / Offer / 公司流程 / 月度统计) */ +const MOCK_JOB_PROGRESS: JobProgress = { + deliveryCount: 28, + interviewCount: 6, + offerCount: 2, + stats: { + total: 28, + pending: 6, + interviewing: 2, + offer: 2, + ended: 20, + }, + offers: [ + { + id: 'offer-1', + companyName: '字节跳动', + positionName: '高级前端工程师', + salary: '25-40K·15薪', + deadline: '8月31日截止', + daysLeft: 3, + }, + { + id: 'offer-2', + companyName: '腾讯科技', + positionName: '产品经理', + salary: '20-35K·14薪', + deadline: '9月2日截止', + daysLeft: 5, + }, + ], + companies: [ + { + id: 'c1', + companyName: '字节跳动', + positionName: '高级前端工程师', + salary: '25-40K', + stage: '面试中', + stageLabel: '终面', + nextAction: '明天 14:00 · 视频面试', + date: '08-28', + }, + { + id: 'c2', + companyName: '腾讯科技', + positionName: '产品经理', + salary: '20-35K', + stage: 'Offer', + stageLabel: 'Offer 沟通中', + nextAction: 'HR 等待你的确认', + date: '08-27', + }, + { + id: 'c3', + companyName: '阿里巴巴', + positionName: '前端开发工程师', + salary: '18-30K', + stage: '待反馈', + stageLabel: '简历筛选中', + nextAction: '预计 3 天内反馈', + date: '08-26', + }, + { + id: 'c4', + companyName: '美团', + positionName: 'Java 开发工程师', + salary: '20-30K', + stage: '已结束', + stageLabel: '流程已结束', + nextAction: '岗位已关闭', + date: '08-20', + }, + ], + monthlyStats: [ + { month: '3月', deliveries: 4, interviews: 1, offers: 0 }, + { month: '4月', deliveries: 6, interviews: 2, offers: 0 }, + { month: '5月', deliveries: 8, interviews: 2, offers: 1 }, + { month: '6月', deliveries: 10, interviews: 3, offers: 0 }, + { month: '7月', deliveries: 12, interviews: 4, offers: 1 }, + { month: '8月', deliveries: 12, interviews: 4, offers: 1 }, + ], + steps: [ + { id: 'p1', title: '已投递', desc: '向 28 家企业投递简历', status: 'done', date: '2026-08-01' }, + { id: 'p2', title: '简历被查看', desc: '12 家企业查看了你的简历', status: 'done', date: '2026-08-05' }, + { id: 'p3', title: '面试邀约', desc: '收到 6 场面试邀请', status: 'done', date: '2026-08-12' }, + { id: 'p4', title: '面试进行中', desc: '字节跳动终面 / 腾讯二面待完成', status: 'active' }, + { id: 'p5', title: 'Offer 阶段', desc: '已收获 2 个 Offer,待决策', status: 'todo' }, + { id: 'p6', title: '入职准备', desc: '背调 / 合同签订 / 入职清单', status: 'todo' }, + ], +}; + +/** + * 获取求职进度时间轴 + * @method GET + * @url /api/tools/job-progress + * @returns Promise 求职进度数据 + */ +export async function fetchJobProgress(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/tools/job-progress`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_JOB_PROGRESS); +} + +/* ===================== 简历 ===================== */ + +/** 简历 mock 数据 */ +const MOCK_RESUME: Resume = { + score: { + score: 72, + maxScore: 100, + optimizePoints: [ + { id: 'opt-1', title: '项目经历描述可量化', score: 8, optimized: false }, + { id: 'opt-2', title: '缺少技能关键词', score: 5, optimized: false }, + { id: 'opt-3', title: '自我评价太空泛', score: 3, optimized: false }, + { id: 'opt-4', title: '工作经历缺少成果数据', score: 4, optimized: false }, + { id: 'opt-5', title: '求职意向可更明确', score: 2, optimized: false }, + ], + }, + basicInfo: { + name: '李明', + avatar: '李', + phone: '138-8888-8888', + email: 'liming@email.com', + location: '北京 · 朝阳区', + }, + jobIntention: { + position: '高级前端工程师', + salary: '25-35K', + city: '北京', + status: 'active', + statusLabel: '积极求职中', + }, + education: [ + { + id: 'edu-1', + school: '北京大学', + major: '计算机科学与技术', + degree: '本科', + startYear: '2018', + endYear: '2022', + }, + ], + experience: [ + { + id: 'exp-1', + company: '字节跳动', + position: '前端开发工程师', + startDate: '2022.07', + endDate: '至今', + description: '负责公司核心产品的前端开发工作,参与了多个项目的开发。', + aiOptimized: false, + }, + ], + projects: [ + { + id: 'proj-1', + name: 'XX电商平台重构', + role: '前端负责人', + description: '使用React重构了整个电商平台,将首屏加载时间从3.5s优化至1.2s,页面性能提升65%,日活用户增长40%。', + aiOptimized: true, + }, + ], + skills: [ + { name: 'Vue' }, + { name: 'React' }, + { name: 'TypeScript' }, + { name: 'Webpack' }, + { name: 'CSS3' }, + { name: 'Git' }, + ], + selfEvaluation: { + content: '本人性格开朗,工作认真负责,具有良好的团队合作精神。熟悉前端开发流程,有较强的学习能力和问题解决能力。', + }, +}; + +/** AI 优化后的简历 */ +const MOCK_OPTIMIZED_RESUME: Resume = { + ...MOCK_RESUME, + score: { + score: 92, + maxScore: 100, + optimizePoints: MOCK_RESUME.score.optimizePoints.map((p) => ({ ...p, optimized: true })), + }, + experience: [ + { + ...MOCK_RESUME.experience[0], + description: + '负责公司核心产品的前端架构设计与开发,主导 React 18 + TypeScript 技术栈升级;通过代码拆分、懒加载与性能监控,将首屏加载时间从 3.5s 优化至 1.2s,核心页面性能提升 65%;推动组件库建设,沉淀 30+ 可复用业务组件。', + aiOptimized: true, + }, + ], + selfEvaluation: { + content: + '3 年前端开发经验,具备完整的大型 Web 项目架构与性能优化能力。擅长 React / TypeScript 技术栈,对工程化、组件化、性能监控有深入实践,能够独立推动技术方案落地并持续提升团队开发效率。', + }, +}; + +/** 默认导出设置 */ +const DEFAULT_EXPORT_SETTINGS: ResumeExportSettings = { + format: 'pdf', + templateId: 'classic', + hideContact: false, + exportPassword: false, + paperSize: 'A4', + orientation: 'portrait', + fileName: '李明_前端工程师简历', + includeCover: false, + includeRecommendation: false, +}; + +/** + * 获取我的简历 + * @method GET + * @url /api/resume + * @returns Promise 简历数据 + */ +export async function fetchResume(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/resume`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_RESUME); +} + +/** + * AI 一键优化简历 + * @method POST + * @url /api/resume/optimize + * @returns Promise 优化后的简历 + */ +export async function optimizeResume(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/resume/optimize`, { method: 'POST' }); return res.json(); + return mockRequest({ score: MOCK_OPTIMIZED_RESUME.score.score, resume: MOCK_OPTIMIZED_RESUME }); +} + +/** + * 获取简历导出设置 + * @method GET + * @url /api/resume/export-settings + * @returns Promise 导出设置 + */ +export async function fetchResumeExportSettings(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/resume/export-settings`, { method: 'GET' }); return res.json(); + return mockRequest({ ...DEFAULT_EXPORT_SETTINGS }); +} + +/** + * 保存简历导出设置 + * @method PUT + * @url /api/resume/export-settings + * @param settings 导出设置 + */ +export async function saveResumeExportSettings(settings: ResumeExportSettings): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/resume/export-settings`, { method: 'PUT', body: JSON.stringify(settings) }); return res.json(); + Object.assign(DEFAULT_EXPORT_SETTINGS, settings); + return mockRequest(undefined); +} + +/** + * 导出简历 + * @method POST + * @url /api/resume/export + * @param settings 导出设置 + * @returns Promise<{ url: string }> 导出文件地址 + */ +export async function exportResume(settings: ResumeExportSettings): Promise<{ url: string }> { + // TODO: const res = await fetch(`${API_BASE_URL}/api/resume/export`, { method: 'POST', body: JSON.stringify(settings) }); return res.json(); + const formatMap: Record = { + pdf: 'pdf', + word: 'docx', + image: 'png', + }; + return mockRequest({ + url: `https://cdn.example.com/resume/${settings.fileName}.${formatMap[settings.format]}`, + }); +} + +/* ===================== 入职准备清单 ===================== */ + +/** 入职准备清单分组(材料 / 手续 / 学习 / 生活 / 每日提醒) */ +const MOCK_CHECKLIST_GROUPS: ChecklistGroup[] = [ + { + id: 'material', + title: '材料准备', + icon: 'folder-outline', + expanded: true, + items: [ + { id: 'm1', title: '身份证原件及复印件', done: true, required: true }, + { id: 'm2', title: '学历学位证书', done: true, required: true }, + { id: 'm3', title: '离职证明', done: true, required: true }, + { id: 'm4', title: '入职体检报告', done: false, required: true }, + { id: 'm5', title: '银行卡(工资卡)', done: false, required: true }, + { id: 'm6', title: '一寸/二寸证件照', done: false }, + ], + }, + { + id: 'procedure', + title: '手续办理', + icon: 'document-text-outline', + expanded: true, + items: [ + { id: 'p1', title: '签订劳动合同', done: false, required: true }, + { id: 'p2', title: '社保公积金转移', done: false, required: true }, + { id: 'p3', title: '入职信息登记', done: false, required: true }, + { id: 'p4', title: '领取工牌与办公设备', done: false }, + ], + }, + { + id: 'study', + title: '学习准备', + icon: 'book-outline', + expanded: false, + items: [ + { id: 's1', title: '熟悉公司业务与产品线', done: false }, + { id: 's2', title: '了解团队成员与协作流程', done: false }, + { id: 's3', title: '技术栈预习(React / TypeScript)', done: false }, + { id: 's4', title: '搭建本地开发环境', done: false }, + ], + }, + { + id: 'life', + title: '生活准备', + icon: 'home-outline', + expanded: false, + items: [ + { id: 'l1', title: '租房 / 通勤路线规划', done: false }, + { id: 'l2', title: '准备办公用品', done: false }, + { id: 'l3', title: '准备入职着装', done: false }, + { id: 'l4', title: '调整作息时间', done: false }, + ], + }, +]; + +/** 入职准备清单 */ +const MOCK_ONBOARDING_CHECKLIST: OnboardingChecklist = { + daysLeft: 7, + position: '高级前端工程师', + company: '字节跳动', + total: 18, + completed: 3, + onboardingDate: '2026-09-06', + groups: MOCK_CHECKLIST_GROUPS, + dailyReminder: { + enabled: true, + time: '09:00', + }, + tips: [ + { + id: 'tip-1', + title: '提前 15 分钟到公司', + desc: '首日建议提前到达,留出办理入职手续的时间', + icon: 'time-outline', + }, + { + id: 'tip-2', + title: '主动认识团队成员', + desc: '主动自我介绍,快速融入团队氛围', + icon: 'people-outline', + }, + { + id: 'tip-3', + title: '准备自我介绍', + desc: '准备 1 分钟自我介绍,包含背景与擅长方向', + icon: 'chatbubble-outline', + }, + ], +}; + +/** + * 获取入职准备清单 + * @method GET + * @url /api/tools/onboarding-checklist + * @returns Promise 入职准备清单数据 + */ +export async function fetchOnboardingChecklist(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/tools/onboarding-checklist`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_ONBOARDING_CHECKLIST); +} + +/** + * 切换入职待办完成状态 + * @method PATCH + * @url /api/tools/onboarding-checklist/items/:id + * @param itemId { string } 待办 id + * @param done { boolean } 是否完成 + * @returns Promise 更新后的清单 + */ +export async function toggleChecklistItem( + itemId: string, + done: boolean, +): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/tools/onboarding-checklist/items/${itemId}`, { method: 'PATCH', body: JSON.stringify({ done }) }); return res.json(); + const groups = MOCK_CHECKLIST_GROUPS.map((g) => ({ + ...g, + items: g.items.map((item) => (item.id === itemId ? { ...item, done } : item)), + })); + const completed = groups.reduce( + (sum, g) => sum + g.items.filter((i) => i.done).length, + 0, + ); + const total = groups.reduce((sum, g) => sum + g.items.length, 0); + return mockRequest({ ...MOCK_ONBOARDING_CHECKLIST, groups, completed, total }); +} + +/* ===================== AI 模拟面试 ===================== */ + +/** AI 模拟面试配置 */ +const MOCK_AI_INTERVIEW: AIInterviewData = { + mode: 'full', + modes: [ + { id: 'full', label: '全真模拟', desc: '完整流程,高仿真', icon: 'videocam-outline' }, + { id: 'special', label: '专项突破', desc: '针对弱项强化训练', icon: 'fitness-outline' }, + { id: 'fast', label: '快速问答', desc: '10分钟快速练习', icon: 'flash-outline' }, + ], + jobInfo: { + position: '高级前端工程师', + company: '字节跳动', + difficulty: '中等', + duration: 30, + }, + questionTypes: [ + { id: 'tech', label: '技术面', color: 'primary', selected: true }, + { id: 'behavior', label: '行为面', color: 'success', selected: true }, + { id: 'logic', label: '智力题', color: 'action', selected: false }, + ], + specials: [ + { id: 'sp1', label: '前端框架原理', selected: false }, + { id: 'sp2', label: '工程化实践', selected: false }, + { id: 'sp3', label: '性能优化', selected: false }, + { id: 'sp4', label: '项目深挖', selected: false }, + { id: 'sp5', label: '算法与数据结构', selected: false }, + ], + improvements: [ + { id: 'imp1', label: '技术表达', percent: 10, color: 'primary' }, + { id: 'imp2', label: '应变能力', percent: 12, color: 'success' }, + ], + recent: [ + { id: 'r1', date: '3月15日', title: '前端开发模拟面试', result: '良好', resultColor: 'success' }, + { id: 'r2', date: '3月10日', title: '技术专项突破', result: '优秀', resultColor: 'primary' }, + ], +}; + +/** + * 获取 AI 模拟面试配置 + * @method GET + * @url /api/interview/config + * @returns Promise 模拟面试配置 + */ +export async function fetchAIInterviewData(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/interview/config`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_AI_INTERVIEW); +} + +/** + * 开始模拟面试 + * @method POST + * @url /api/interview/start + * @returns Promise 面试会话 + */ +export async function startInterview(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/interview/start`, { method: 'POST' }); return res.json(); + return mockRequest({ id: `session-${Date.now()}`, total: 10, currentIndex: 1, remainingSeconds: 1800 }); +} + +/* ===================== HR 评价 ===================== */ + +/** HR 评价详情 */ +const MOCK_HR_EVALUATION: HREvaluation = { + overallScore: 88, + percentile: 78, + trend: [ + { month: '3月', score: 82 }, + { month: '4月', score: 84 }, + { month: '5月', score: 83 }, + { month: '6月', score: 85 }, + { month: '7月', score: 86 }, + { month: '8月', score: 88 }, + ], + dimensions: [ + { id: 'd1', name: '技术能力', score: 92, maxScore: 100, level: '优秀', change: 2 }, + { id: 'd2', name: '项目经验', score: 88, maxScore: 100, level: '优秀', change: 1 }, + { id: 'd3', name: '沟通表达', score: 85, maxScore: 100, level: '良好', change: 3 }, + { id: 'd4', name: '团队协作', score: 90, maxScore: 100, level: '优秀', change: 4 }, + { id: 'd5', name: '学习能力', score: 86, maxScore: 100, level: '良好', change: 2 }, + { id: 'd6', name: '稳定性', score: 80, maxScore: 100, level: '良好', change: -1 }, + ], + reviews: [ + { + id: 'rv1', + company: '字节跳动', + rating: 5, + date: '2026-08-20', + content: '技术基础扎实,对 React 与 TypeScript 理解深入,工程化实践经验丰富。沟通顺畅,能够快速理解业务需求并给出技术方案。', + tags: ['技术扎实', '沟通顺畅', '工程能力强'], + status: 'accepted', + }, + { + id: 'rv2', + company: '腾讯科技', + rating: 4, + date: '2026-08-12', + content: '项目经验较为丰富,主导过中大型前端项目。在性能优化方向有一定积累,建议进一步补充跨端开发经验。', + tags: ['项目丰富', '性能优化', '可补充跨端'], + status: 'accepted', + }, + { + id: 'rv3', + company: '阿里巴巴', + rating: 4, + date: '2026-08-05', + content: '学习能力较强,能够快速上手新技术栈。团队协作意识好,建议在未来加强系统设计与架构能力的沉淀。', + tags: ['学习能力强', '协作好', '可加强架构'], + status: 'accepted', + }, + ], + rules: [ + '综合评分由六维能力加权计算得出,技术能力权重最高', + '评价数据来自已完成面试流程的企业 HR,确保真实有效', + '评分每月更新一次,反映你近期的求职表现', + ], +}; + +/** + * 获取 HR 评价详情 + * @method GET + * @url /api/evaluation/hr + * @returns Promise HR 评价详情 + */ +export async function fetchHREvaluation(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/evaluation/hr`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_HR_EVALUATION); +} + +/* ===================== 评价申诉 ===================== */ + +/** 申诉维度(六维能力) */ +const MOCK_APPEAL_DIMENSIONS: AppealDimension[] = [ + { id: 'd1', name: '技术能力', selected: false }, + { id: 'd2', name: '项目经验', selected: false }, + { id: 'd3', name: '沟通表达', selected: false }, + { id: 'd4', name: '团队协作', selected: false }, + { id: 'd5', name: '学习能力', selected: false }, + { id: 'd6', name: '稳定性', selected: false }, +]; + +/** 评价申诉表单初始数据 */ +const MOCK_APPEAL_FORM: EvaluationAppealForm = { + type: 'overall', + dimensions: MOCK_APPEAL_DIMENSIONS, + reason: 'untrue', + reasonText: '', + evidence: [], + contactPhone: '', + contactEmail: '', + agree: false, +}; + +/** + * 获取评价申诉表单数据 + * @method GET + * @url /api/evaluation/appeal + * @returns Promise 申诉表单初始数据 + */ +export async function fetchAppealForm(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/evaluation/appeal`, { method: 'GET' }); return res.json(); + return mockRequest({ ...MOCK_APPEAL_FORM }); +} + +/** + * 提交评价申诉 + * @method POST + * @url /api/evaluation/appeal + * @param payload { EvaluationAppealForm } 申诉表单 + * @returns Promise<{ appealNo: string; estimatedDays: number }> 申诉结果 + */ +export async function submitAppeal( + payload: EvaluationAppealForm, +): Promise<{ appealNo: string; estimatedDays: number }> { + // TODO: const res = await fetch(`${API_BASE_URL}/api/evaluation/appeal`, { method: 'POST', body: JSON.stringify(payload) }); return res.json(); + void payload; + return mockRequest({ + appealNo: `AP${Date.now().toString().slice(-8)}`, + estimatedDays: 3, + }); +} + +/* ===================== 企业数据总览 ===================== */ + +/** 企业总览快捷入口 */ +const MOCK_ENTERPRISE_QUICK_ACTIONS: EnterpriseQuickAction[] = [ + { id: 'publish', label: '发布岗位', icon: 'add-circle-outline', route: '/job-publish', color: 'primary' }, + { id: 'jobs', label: '岗位管理', icon: 'business-outline', route: '/job-management', color: 'primary' }, + { id: 'evaluations', label: '评价管理', icon: 'chatbubbles-outline', route: '/job-evaluations', color: 'success' }, + { id: 'candidates', label: '人才库', icon: 'people-outline', route: '/talent-recommendation', color: 'info' }, + { id: 'communication', label: '沟通', icon: 'chatbubble-outline', route: '/enterprise-chat', color: 'action' }, + { id: 'interviews', label: '面试日程', icon: 'calendar-outline', route: '/interview-score', color: 'action' }, +]; + +/** 企业总览统计 */ +const MOCK_ENTERPRISE_STATS: EnterpriseStat[] = [ + { id: 'jobs', label: '在招岗位', value: 1280, change: 12, icon: 'briefcase-outline', color: 'primary' }, + { id: 'interviews', label: '本月面试', value: 156, change: 8, icon: 'videocam-outline', color: 'info' }, + { id: 'reviews', label: '累计评价', value: 28, change: -3, icon: 'chatbubble-outline', color: 'success' }, + { id: 'appeals', label: '待处理申诉', value: 12, change: 2, icon: 'alert-circle-outline', color: 'action' }, +]; + +/** 岗位评价摘要 */ +const MOCK_JOB_EVALUATION_SUMMARIES: JobEvaluationSummary[] = [ + { id: 'je1', positionName: '高级前端工程师', department: '技术部', score: 4.9, reviewCount: 42, change: 0.2 }, + { id: 'je2', positionName: '产品经理', department: '产品部', score: 4.7, reviewCount: 38, change: 0.1 }, + { id: 'je3', positionName: 'UI设计师', department: '设计部', score: 4.6, reviewCount: 25, change: -0.1 }, + { id: 'je4', positionName: '后端开发工程师', department: '技术部', score: 4.5, reviewCount: 31, change: 0.3 }, +]; + +/** 企业总览数据 */ +const MOCK_ENTERPRISE_OVERVIEW: EnterpriseOverview = { + id: 'ent-1', + name: '北京智联科技有限公司', + logo: '智', + industry: '互联网/IT', + scale: '1000-9999人', + stage: 'B轮', + overallScore: 4.8, + reviewCount: 1280, + stats: MOCK_ENTERPRISE_STATS, + jobEvaluations: MOCK_JOB_EVALUATION_SUMMARIES, + quickActions: MOCK_ENTERPRISE_QUICK_ACTIONS, +}; + +/** + * 获取企业数据总览 + * @method GET + * @url /api/enterprise/overview + * @returns Promise 企业总览数据 + */ +export async function fetchEnterpriseOverview(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/enterprise/overview`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_ENTERPRISE_OVERVIEW); +} + +/* ===================== 发布岗位 ===================== */ + +/** 默认发布岗位表单 */ +const DEFAULT_JOB_PUBLISH_FORM: JobPublishFormData = { + positionName: '高级前端工程师', + department: '技术部', + headcount: 2, + city: '北京', + address: '朝阳区望京SOHO T3', + workType: 'full-time', + education: '本科', + experience: '3-5年', + salaryMin: 25, + salaryMax: 40, + salaryMonths: 14, + benefits: ['五险一金', '年终奖金', '弹性工作', '带薪年假', '股票期权'], + responsibilities: + '1. 负责公司核心产品前端架构设计与开发\n2. 参与前端工程化建设,提升研发效率\n3. 与产品、设计、后端紧密配合,保证交付质量', + requirements: + '1. 本科及以上学历,3年以上前端开发经验\n2. 精通 React / TypeScript,熟悉现代前端工程化\n3. 具备良好的沟通能力和团队协作精神', + skills: ['React', 'TypeScript', 'Node.js', 'Webpack'], + visibility: 'all', + urgent: true, + acceptFresh: false, + deadline: '2026-12-31', +}; + +/** 岗位质量预估 */ +const MOCK_JOB_QUALITY_ESTIMATE: JobQualityEstimate = { + score: 92, + level: '优秀', + suggestions: [ + '岗位职责描述清晰,建议补充团队规模信息', + '薪资范围具有竞争力,可突出股权激励', + ], +}; + +/** + * 获取发布岗位表单初始数据 + * @method GET + * @url /api/enterprise/jobs/publish-form + * @returns Promise 发布岗位表单 + */ +export async function fetchJobPublishForm(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/enterprise/jobs/publish-form`, { method: 'GET' }); return res.json(); + return mockRequest({ ...DEFAULT_JOB_PUBLISH_FORM }); +} + +/** + * 预估岗位质量 + * @method POST + * @url /api/enterprise/jobs/quality-estimate + * @param form { JobPublishFormData } 岗位表单 + * @returns Promise 质量预估结果 + */ +export async function estimateJobQuality(form: JobPublishFormData): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/enterprise/jobs/quality-estimate`, { method: 'POST', body: JSON.stringify(form) }); return res.json(); + void form; + return mockRequest({ ...MOCK_JOB_QUALITY_ESTIMATE }); +} + +/** + * 发布岗位 + * @method POST + * @url /api/enterprise/jobs + * @param form { JobPublishFormData } 岗位表单 + * @returns Promise<{ jobId: string }> 发布结果 + */ +export async function publishJob(form: JobPublishFormData): Promise<{ jobId: string }> { + // TODO: const res = await fetch(`${API_BASE_URL}/api/enterprise/jobs`, { method: 'POST', body: JSON.stringify(form) }); return res.json(); + void form; + return mockRequest({ jobId: `job-${Date.now().toString().slice(-8)}` }); +} + +/* ===================== 岗位评价管理 ===================== */ + +/** 岗位评价维度 */ +const MOCK_JOB_EVALUATION_DIMENSIONS: JobEvaluationDimension[] = [ + { id: 'jd1', name: '岗位真实性', score: 4.9, maxScore: 5, change: 0.1 }, + { id: 'jd2', name: '薪资透明度', score: 4.7, maxScore: 5, change: 0.2 }, + { id: 'jd3', name: '面试体验', score: 4.6, maxScore: 5, change: -0.1 }, + { id: 'jd4', name: '发展空间', score: 4.5, maxScore: 5, change: 0.3 }, + { id: 'jd5', name: '工作强度', score: 4.3, maxScore: 5, change: 0.0 }, + { id: 'jd6', name: '团队氛围', score: 4.8, maxScore: 5, change: 0.2 }, +]; + +/** 岗位评价列表 */ +const MOCK_JOB_REVIEWS: JobReview[] = [ + { + id: 'jr1', + userName: '张**', + userRole: '在职员工,前端开发', + positionName: '高级前端工程师', + rating: 5, + date: '2026-08-20', + content: '面试流程规范,HR 反馈及时。岗位描述与实际工作基本一致,团队技术氛围很好。', + tags: ['面试规范', '反馈及时', '氛围好'], + replied: true, + }, + { + id: 'jr2', + userName: '李**', + userRole: '求职者,产品经理', + positionName: '产品经理', + rating: 4, + date: '2026-08-15', + content: '整体体验不错,面试官专业。但面试等待时间稍长,希望可以优化。', + tags: ['面试官专业', '等待时间长'], + replied: false, + }, + { + id: 'jr3', + userName: '王**', + userRole: '在职员工,UI设计', + positionName: 'UI设计师', + rating: 5, + date: '2026-08-10', + content: '薪资透明,福利完善,工作节奏合理。非常推荐加入这家公司。', + tags: ['薪资透明', '福利好'], + replied: true, + }, +]; + +/** 岗位评价管理数据 */ +const MOCK_JOB_EVALUATION_MANAGEMENT: JobEvaluationManagement = { + overallScore: 4.8, + change: 0.1, + reviewCount: 128, + positiveRate: 85, + replyRate: 72, + complaintRate: 8, + trend: [ + { month: '3月', score: 4.5 }, + { month: '4月', score: 4.6 }, + { month: '5月', score: 4.6 }, + { month: '6月', score: 4.7 }, + { month: '7月', score: 4.7 }, + { month: '8月', score: 4.8 }, + ], + dimensions: MOCK_JOB_EVALUATION_DIMENSIONS, + jobScores: MOCK_JOB_EVALUATION_SUMMARIES, + reviews: MOCK_JOB_REVIEWS, +}; + +/** + * 获取岗位评价管理数据 + * @method GET + * @url /api/enterprise/evaluations + * @returns Promise 岗位评价管理数据 + */ +export async function fetchJobEvaluationManagement(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/enterprise/evaluations`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_JOB_EVALUATION_MANAGEMENT); +} + +/** + * 回复岗位评价 + * @method POST + * @url /api/enterprise/evaluations/:id/reply + * @param reviewId { string } 评价 id + * @param reply { string } 回复内容 + * @returns Promise 回复后的评价 + */ +export async function replyJobReview( + reviewId: string, + reply: string, +): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/enterprise/evaluations/${reviewId}/reply`, { method: 'POST', body: JSON.stringify({ reply }) }); return res.json(); + const review = MOCK_JOB_REVIEWS.find((r) => r.id === reviewId) ?? MOCK_JOB_REVIEWS[0]; + void reply; + return mockRequest({ ...review, replied: true }); +} + +/* ===================== 候选人详情 ===================== */ + +/** 候选人能力维度 */ +const MOCK_CANDIDATE_ABILITIES: CandidateAbility[] = [ + { id: 'ca1', name: '专业技能', score: 92, maxScore: 100 }, + { id: 'ca2', name: '项目经验', score: 85, maxScore: 100 }, + { id: 'ca3', name: '逻辑思维', score: 88, maxScore: 100 }, + { id: 'ca4', name: '沟通表达', score: 92, maxScore: 100 }, + { id: 'ca5', name: '团队协作', score: 80, maxScore: 100 }, + { id: 'ca6', name: '学习能力', score: 95, maxScore: 100 }, + { id: 'ca7', name: '抗压能力', score: 75, maxScore: 100 }, + { id: 'ca8', name: '文化匹配', score: 88, maxScore: 100 }, +]; + +/** 候选人评价 */ +const MOCK_CANDIDATE_REVIEWS: CandidateReview[] = [ + { + id: 'cr1', + interviewer: '王经理', + position: '高级前端工程师', + date: '2026-08-25', + rating: 5, + content: '技术基础扎实,对 React 与 TypeScript 理解深入,工程化实践经验丰富。沟通顺畅。', + tags: ['技术扎实', '沟通顺畅', '工程能力强'], + }, + { + id: 'cr2', + interviewer: '李总监', + position: '前端负责人', + date: '2026-08-20', + rating: 4, + content: '项目经验丰富,主导过中大型前端项目。在性能优化方向有一定积累。', + tags: ['项目丰富', '性能优化'], + }, +]; + +/** 候选人详情 */ +const MOCK_CANDIDATE_DETAIL: CandidateDetail = { + id: 'candidate-1', + basicInfo: { + name: '张明远', + age: 28, + gender: '男', + experience: '5年经验', + education: '本科', + city: '北京', + phone: '138****8888', + email: 'zhang****@email.com', + jobStatus: '在职看机会', + }, + matchPercent: 96, + overallScore: 88, + percentile: 78, + trend: [ + { month: '3月', score: 82 }, + { month: '4月', score: 84 }, + { month: '5月', score: 83 }, + { month: '6月', score: 85 }, + { month: '7月', score: 86 }, + { month: '8月', score: 88 }, + ], + abilities: MOCK_CANDIDATE_ABILITIES, + reviews: MOCK_CANDIDATE_REVIEWS, + hireSuggestions: [ + { type: 'strong', title: '强烈推荐录用', desc: '综合匹配度高,建议尽快发起 offer 流程', selected: true }, + { type: 'consider', title: '可以考虑', desc: '技术能力达标,建议进一步考察项目管理能力' }, + { type: 'reject', title: '不推荐', desc: '与岗位匹配度较低,建议再看看其他候选人' }, + ], + salarySuggestion: { + range: '30-33K', + level: 'P6', + note: '建议按 P6 职级定薪,可结合候选人当前薪资适当上浮', + }, + invited: false, +}; + +/** + * 获取候选人详情 + * @method GET + * @url /api/enterprise/candidates/:id + * @param candidateId { string } 候选人 id + * @returns Promise 候选人详情 + */ +export async function fetchCandidateDetail(candidateId: string): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/enterprise/candidates/${candidateId}`, { method: 'GET' }); return res.json(); + void candidateId; + return mockRequest({ ...MOCK_CANDIDATE_DETAIL }); +} + +/** + * 邀请候选人面试 + * @method POST + * @url /api/enterprise/candidates/:id/invite + * @param candidateId { string } 候选人 id + * @returns Promise<{ success: boolean }> 邀请结果 + */ +export async function inviteCandidate(candidateId: string): Promise<{ success: boolean }> { + // TODO: const res = await fetch(`${API_BASE_URL}/api/enterprise/candidates/${candidateId}/invite`, { method: 'POST' }); return res.json(); + void candidateId; + return mockRequest({ success: true }); +} + +/* ===================== 面试评分 ===================== */ + +/** 面试评分维度 */ +const MOCK_INTERVIEW_SCORE_DIMENSIONS: InterviewScoreDimension[] = [ + { id: 'is1', name: '专业技能', score: 90, maxScore: 100 }, + { id: 'is2', name: '项目经验', score: 85, maxScore: 100 }, + { id: 'is3', name: '逻辑思维', score: 88, maxScore: 100 }, + { id: 'is4', name: '沟通表达', score: 92, maxScore: 100 }, + { id: 'is5', name: '团队协作', score: 80, maxScore: 100 }, + { id: 'is6', name: '学习能力', score: 95, maxScore: 100 }, + { id: 'is7', name: '抗压能力', score: 75, maxScore: 100 }, + { id: 'is8', name: '文化匹配', score: 88, maxScore: 100 }, +]; + +/** 面试评价标签 */ +const MOCK_INTERVIEW_SCORE_TAGS: InterviewScoreTag[] = [ + { id: 'tag1', label: '技术扎实', selected: true, color: 'primary' }, + { id: 'tag2', label: '表达清晰', selected: true, color: 'success' }, + { id: 'tag3', label: '项目经验丰富', selected: false, color: 'info' }, + { id: 'tag4', label: '应变能力强', selected: false, color: 'action' }, + { id: 'tag5', label: '需加强架构', selected: false, color: 'danger' }, +]; + +/** 面试评分数据 */ +const MOCK_INTERVIEW_SCORE_DATA: InterviewScoreData = { + candidateId: 'candidate-1', + candidateName: '张明远', + positionName: '高级前端工程师', + interviewTime: '2026-08-30 14:00', + overallScore: 87, + dimensions: MOCK_INTERVIEW_SCORE_DIMENSIONS, + tags: MOCK_INTERVIEW_SCORE_TAGS, + comment: + '候选人整体表现优秀,技术功底扎实,有大型项目经验。沟通表达流畅,学习能力强。对 React 底层原理有深入理解,建议录用。', + hireSuggestions: [ + { type: 'strong', title: '强烈推荐录用', desc: '综合素质优秀,建议尽快定薪发 offer', selected: true }, + { type: 'consider', title: '可以考虑', desc: '基本符合要求,建议二面进一步确认' }, + { type: 'reject', title: '不推荐', desc: '与岗位要求存在明显差距' }, + ], + salarySuggestion: { + range: '30-33K', + level: 'P6', + note: '建议按 P6 职级定薪,可结合候选人期望薪资协商', + }, +}; + +/** + * 获取面试评分数据 + * @method GET + * @url /api/enterprise/interviews/:id/score + * @param interviewId { string } 面试 id + * @returns Promise 面试评分数据 + */ +export async function fetchInterviewScore(interviewId: string): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/enterprise/interviews/${interviewId}/score`, { method: 'GET' }); return res.json(); + void interviewId; + return mockRequest({ ...MOCK_INTERVIEW_SCORE_DATA }); +} + +/** + * 提交面试评分 + * @method POST + * @url /api/enterprise/interviews/:id/score + * @param interviewId { string } 面试 id + * @param payload { InterviewScoreData } 评分数据 + * @returns Promise<{ success: boolean }> 提交结果 + */ +export async function submitInterviewScore( + interviewId: string, + payload: InterviewScoreData, +): Promise<{ success: boolean }> { + // TODO: const res = await fetch(`${API_BASE_URL}/api/enterprise/interviews/${interviewId}/score`, { method: 'POST', body: JSON.stringify(payload) }); return res.json(); + void interviewId; + void payload; + return mockRequest({ success: true }); +} + +/* ===================== 运营规则配置 ===================== */ + +/** 匹配模式选项 */ +const MOCK_MATCH_MODE_OPTIONS: MatchModeOption[] = [ + { + id: 'strict', + name: '严格模式', + desc: '仅推荐高度匹配的候选人,降低误配风险,适合对岗位要求精确匹配', + }, + { + id: 'recommended', + name: '推荐模式', + desc: '综合技能、经验、期望进行智能推荐,推荐质量与数量较平衡', + recommended: true, + }, + { + id: 'broad', + name: '广泛模式', + desc: '更多候选人,需要人工筛选较多,适合招聘需求量大或新兴岗位', + }, +]; + +/** 默认运营规则配置 */ +const MOCK_OPERATION_RULES: OperationRules = { + matchMode: 'recommended', + matchModeOptions: MOCK_MATCH_MODE_OPTIONS, + matchThreshold: 70, + visibilityThreshold: 65, + dailyRecommendLimit: 20, + autoFilters: [ + { id: 'af-1', name: '学历硬性筛选', description: '过滤不满足学历要求的候选人', enabled: true }, + { id: 'af-2', name: '经验硬性筛选', description: '过滤不满足经验年限的候选人', enabled: true }, + { id: 'af-3', name: '薪资期望筛选', description: '过滤薪资期望超出预算上限的候选人', enabled: false }, + { id: 'af-4', name: '地域匹配筛选', description: '优先推荐期望城市与岗位城市一致的候选人', enabled: true }, + { id: 'af-5', name: '黑名单过滤', description: '自动过滤存在诚信问题的候选人', enabled: true }, + ], + urgentPriority: true, + campusPriority: false, + notifyNewDelivery: true, +}; + +/** + * 获取运营规则配置 + * @method GET + * @url /api/operation/rules + * @returns Promise 运营规则配置 + */ +export async function fetchOperationRules(): Promise { + return mockRequest({ ...MOCK_OPERATION_RULES }); +} + +/** + * 保存运营规则配置 + * @method POST + * @url /api/operation/rules + * @param rules { OperationRules } 运营规则配置 + * @returns Promise 保存后的配置 + */ +export async function saveOperationRules(rules: OperationRules): Promise { + return mockRequest({ ...rules }); +} + +/* ===================== 数据审计报告 ===================== */ + +/** 数据审计报告 mock */ +const MOCK_AUDIT_REPORT: AuditReport = { + period: '2024.09.01 - 2024.09.30', + grade: 'A', + gradeDesc: '平台整体风险可控,今日拦截异常 3 次', + todayBlocked: 3, + kpis: [ + { id: 'kpi-1', label: '风险识别数', value: 47, change: 12, unit: '个', icon: 'alert-circle-outline', color: colors.danger }, + { id: 'kpi-2', label: '审计覆盖记录', value: 128, change: 8, unit: '万', icon: 'document-text-outline', color: colors.primary }, + { id: 'kpi-3', label: '待处理异常', value: 15, change: -3, unit: '个', icon: 'time-outline', color: colors.compareOrange }, + { id: 'kpi-4', label: '合规评分', value: 98, change: 2, unit: '分', icon: 'shield-checkmark-outline', color: colors.success }, + ], + threatTrend: [ + { date: '09/01', riskCount: 4, handledCount: 3 }, + { date: '09/05', riskCount: 6, handledCount: 5 }, + { date: '09/09', riskCount: 3, handledCount: 3 }, + { date: '09/13', riskCount: 8, handledCount: 6 }, + { date: '09/17', riskCount: 5, handledCount: 5 }, + { date: '09/21', riskCount: 7, handledCount: 7 }, + { date: '09/25', riskCount: 4, handledCount: 4 }, + { date: '09/29', riskCount: 6, handledCount: 6 }, + ], + riskDistribution: [ + { id: 'rd-1', label: '虚假简历', percent: 35, color: colors.primary }, + { id: 'rd-2', label: '违规岗位', percent: 25, color: colors.danger }, + { id: 'rd-3', label: '异常沟通', percent: 20, color: colors.compareOrange }, + { id: 'rd-4', label: '刷量行为', percent: 15, color: colors.info }, + { id: 'rd-5', label: '其他', percent: 5, color: colors.textHint }, + ], +}; + +/** + * 获取数据审计报告 + * @method GET + * @url /api/operation/audit-report + * @returns Promise 数据审计报告 + */ +export async function fetchAuditReport(): Promise { + return mockRequest({ ...MOCK_AUDIT_REPORT }); +} + +/* ===================== 企业认证 ===================== */ + +/** 企业认证权益 */ +const MOCK_ENTERPRISE_CERTIFICATION_BENEFITS: EnterpriseCertificationBenefit[] = [ + { id: 'b1', text: '主页展示企业认证标识,提升信任度' }, + { id: 'b2', text: '发布岗位数量上限提升 50%' }, + { id: 'b3', text: '优先获得优质候选人推荐' }, + { id: 'b4', text: '解锁数据分析与风控报告' }, + { id: 'b5', text: '专属客服 1 对 1 服务' }, +]; + +/** 企业认证默认数据 */ +const MOCK_ENTERPRISE_CERTIFICATION: EnterpriseCertificationData = { + status: 'unverified', + statusText: '完成企业认证,享受更多招聘权益', + benefits: MOCK_ENTERPRISE_CERTIFICATION_BENEFITS, + form: { + companyName: '', + creditCode: '', + contactName: '', + contactPhone: '', + }, +}; + +/** + * 获取企业认证信息 + * @method GET + * @url /api/enterprise/certification + * @returns Promise 企业认证数据 + */ +export async function fetchEnterpriseCertification(): Promise { + return mockRequest({ ...MOCK_ENTERPRISE_CERTIFICATION }); +} + +/** + * 提交企业认证 + * @method POST + * @url /api/enterprise/certification + * @param form { EnterpriseCertificationForm } 认证表单 + * @returns Promise<{ success: boolean; status: EnterpriseCertificationStatus }> 提交结果 + */ +export async function submitEnterpriseCertification( + form: EnterpriseCertificationForm, +): Promise<{ success: boolean; status: EnterpriseCertificationStatus }> { + void form; + return mockRequest({ success: true, status: 'pending' }); +} + +/* ===================== 霍兰德测评介绍 ===================== */ + +const MOCK_HOLLAND_INTRO: HollandIntro = { + title: '霍兰德职业兴趣测评', + subtitle: '发现你的职业兴趣类型,找到最适合你的职业方向', + totalParticipants: 124580, + duration: '约10分钟', + dimensions: [ + { type: 'R', name: '现实型', desc: '喜欢动手操作与具体任务' }, + { type: 'I', name: '研究型', desc: '喜欢分析思考与探索未知' }, + { type: 'A', name: '艺术型', desc: '喜欢创意表达与自由创作' }, + { type: 'S', name: '社会型', desc: '喜欢与人交往帮助他人' }, + { type: 'E', name: '企业型', desc: '喜欢组织领导与影响他人' }, + { type: 'C', name: '常规型', desc: '喜欢规范有序的事务处理' }, + ], + features: [ + { id: 'dim', icon: 'apps-outline', title: '6大兴趣维度', desc: '现实型、研究型、艺术型、社会型、企业型、常规型' }, + { id: 'time', icon: 'time-outline', title: '约10分钟', desc: '60道题目,利用碎片时间即可完成' }, + { id: 'rec', icon: 'locate-outline', title: '精准推荐', desc: '基于测评结果推荐匹配度高的岗位' }, + ], + advantages: [ + '专业心理学理论支撑', + '个性化职业推荐报告', + '不限次数免费重做', + '结果永久保存', + ], +}; + +/** + * 获取霍兰德职业兴趣测评介绍 + * @method GET + * @url /api/growth/holland-intro + * @returns Promise 测评介绍 + */ +export async function fetchHollandIntro(): Promise { + return mockRequest(MOCK_HOLLAND_INTRO); +} + +/* ===================== 岗位管理(企业端) ===================== */ + +const MOCK_JOB_MANAGEMENT: JobManagement = { + companyName: '智联科技', + hasData: true, + stats: [ + { id: 'active', label: '在招岗位', value: 12, change: '+2', icon: 'briefcase-outline', color: 'primary' }, + { id: 'today', label: '今日新增投递', value: 48, change: '+23%', icon: 'mail-outline', color: 'success' }, + { id: 'pending', label: '待处理简历', value: 23, change: '-5', icon: 'document-text-outline', color: 'action' }, + { id: 'interview', label: '即将面试', value: 8, change: '已安排', icon: 'calendar-outline', color: 'info' }, + ], + tabs: [ + { key: 'all', label: '全部', count: 12 }, + { key: 'active', label: '招聘中', count: 8 }, + { key: 'offline', label: '已下线', count: 3 }, + { key: 'draft', label: '草稿', count: 1 }, + ], + jobs: [ + { + id: 'job-1', + title: '高级前端工程师', + salary: '25-35K', + salaryMonths: 14, + city: '北京', + experience: '3-5年', + education: '本科', + tags: ['React', 'TypeScript', 'Node.js'], + status: 'active', + department: '技术部', + headcount: 2, + overallScore: 88, + rankText: '同岗排名 Top 3%', + applicants: 128, + views: 2300, + matches: 56, + }, + { + id: 'job-2', + title: '产品经理', + salary: '20-30K', + salaryMonths: 13, + city: '北京', + experience: '3-5年', + education: '本科', + tags: ['B端', '数据产品'], + status: 'active', + department: '产品部', + headcount: 1, + overallScore: 75, + rankText: '同岗排名 Top 25%', + applicants: 86, + views: 1800, + matches: 32, + }, + { + id: 'job-3', + title: 'Java后端开发', + salary: '30-45K', + salaryMonths: 15, + city: '北京', + experience: '5-10年', + education: '本科', + tags: ['Java', 'Spring Boot', 'MySQL'], + status: 'active', + department: '技术部', + headcount: 3, + overallScore: 92, + rankText: '同岗排名 Top 2%', + applicants: 156, + views: 3100, + matches: 78, + }, + ], + benefits: [ + '接收候选人主动投递', + '使用AI智能匹配推荐人才', + '在线沟通与面试安排', + '查看岗位竞争力排名', + '获取招聘数据看板', + ], +}; + +/** + * 获取企业端岗位管理数据 + * @method GET + * @url /api/enterprise/job-management + * @returns Promise 岗位管理数据 + */ +export async function fetchJobManagement(): Promise { + return mockRequest(MOCK_JOB_MANAGEMENT); +} + +/* ===================== 人才推荐(企业端) ===================== */ + +const MOCK_TALENT_RECOMMENDATION: TalentRecommendation = { + companyName: '智联科技', + hasData: true, + todayCount: 15, + tabs: [ + { key: 'all', label: '全部', count: 12 }, + { key: 'high', label: '匹配度高', count: 8 }, + { key: '985211', label: '985/211', count: 5 }, + { key: '5years', label: '5年以上', count: 5 }, + { key: 'master', label: '硕士及以上', count: 3 }, + ], + candidates: [ + { + id: 'cand-1', + name: '张明远', + avatar: '张', + title: '高级前端工程师 · 5年经验', + experience: '5年经验', + age: 28, + school: '北京大学', + city: '北京', + expectedSalary: '期望薪资 30-35K', + matchPercent: 96, + overallScore: 88, + percentile: 99, + tags: ['React', 'TypeScript', 'Node.js'], + }, + { + id: 'cand-2', + name: '李雨桐', + avatar: '李', + title: '产品经理 · 3年经验', + experience: '3年经验', + age: 26, + school: '上海交通大学', + city: '北京', + expectedSalary: '期望薪资 25-30K', + matchPercent: 82, + overallScore: 75, + percentile: 75, + tags: ['大厂背景', '项目经验', '沟通能力强'], + }, + { + id: 'cand-3', + name: '王建国', + avatar: '王', + title: 'Java后端开发 · 7年经验', + experience: '7年经验', + age: 33, + school: '浙江大学', + city: '北京', + expectedSalary: '期望薪资 35-45K', + matchPercent: 91, + overallScore: 92, + percentile: 92, + tags: ['技术扎实', '经验丰富', '高绩效'], + }, + ], + poolStats: [ + { id: 'active', value: '12,580', label: '活跃候选人' }, + { id: 'new', value: '326', label: '今日新增' }, + { id: 'matched', value: '4,820', label: '匹配人才' }, + ], + recommendedActions: [ + '降低学历要求,候选人数 +35%', + '放宽经验要求,候选人数 +50%', + '开启广泛模式,获取更多推荐', + '发布更多岗位信息,吸引主动投递', + ], + dataReference: [ + { label: '主动联系简历数比被动接收高', value: '60%' }, + { label: '24小时内回复的面试转化率提升', value: '2.3倍' }, + { label: '完善企业资料可提升候选人信任度', value: '45%' }, + ], +}; + +/** + * 获取企业端人才推荐数据 + * @method GET + * @url /api/enterprise/talent-recommendation + * @returns Promise 人才推荐数据 + */ +export async function fetchTalentRecommendation(): Promise { + return mockRequest(MOCK_TALENT_RECOMMENDATION); +} + +/* ===================== 企业沟通 ===================== */ + +const MOCK_ENTERPRISE_COMMUNICATION: EnterpriseCommunication = { + companyName: '智联科技', + hasData: true, + pendingTasks: [ + { id: 'reply', label: '待回复', count: 3, hint: '24小时内未回复', icon: 'chatbubble-ellipses-outline', color: 'action' }, + { id: 'score', label: '待评分', count: 2, hint: '面试结束待及时评分', icon: 'star-outline', color: 'compareOrange' }, + ], + tabs: [ + { key: 'all', label: '全部', count: 12 }, + { key: 'pending', label: '未回复', count: 3 }, + { key: 'interview', label: '面试邀请', count: 2 }, + { key: 'offer', label: 'Offer沟通', count: 1 }, + { key: 'closed', label: '已归档', count: 4 }, + ], + chats: [ + { + id: 'chat-1', + userId: 'u-1', + name: '张明远', + avatar: '张', + title: '高级前端工程师 · 最近活跃', + lastMessage: '你的简历很符合我们的要求,方便下周面试吗?', + time: '10分钟前', + unreadCount: 1, + labels: [], + }, + { + id: 'chat-2', + userId: 'u-2', + name: '李雨桐', + avatar: '李', + title: '产品经理', + lastMessage: '你好,我看贵司的产品经理岗位很匹配,想进一步沟通。', + time: '30分钟前', + unreadCount: 0, + labels: [], + }, + { + id: 'chat-3', + userId: 'u-3', + name: '王建国', + avatar: '王', + title: 'Java后端开发', + lastMessage: '已收到面试邀请,确认参加。', + time: '2小时前', + unreadCount: 0, + labels: ['面试待评分'], + }, + ], + dataReference: [ + { label: '主动联系简历数比被动接收高', value: '60%' }, + { label: '24小时内回复的面试转化率提升', value: '2.3倍' }, + { label: '完善企业资料可提升候选人信任度', value: '45%' }, + ], +}; + +/** + * 获取企业端沟通数据 + * @method GET + * @url /api/enterprise/communication + * @returns Promise 沟通数据 + */ +export async function fetchEnterpriseCommunication(): Promise { + return mockRequest(MOCK_ENTERPRISE_COMMUNICATION); +} + +/* ===================== 岗位评价管理空状态 ===================== */ + +const MOCK_JOB_EVALUATION_EMPTY: JobEvaluationEmptyState = { + hasData: false, + tabs: [ + { key: 'all', label: '全部', count: 0 }, + { key: 'positive', label: '好评', count: 0 }, + { key: 'neutral', label: '中评', count: 0 }, + { key: 'negative', label: '差评', count: 0 }, + { key: 'pending', label: '待回复', count: 0 }, + ], + emptyTitle: '还没有收到评价', + emptySubtitle: '候选人面试后可以对岗位进行评价,评价越多,企业可信度越高', + tips: [ + '及时回复候选人消息', + '面试安排专业有序', + '薪资福利透明公开', + '真诚对待每一位候选人', + ], +}; + +/** + * 获取岗位评价管理空状态数据 + * @method GET + * @url /api/enterprise/job-evaluation-empty + * @returns Promise 空状态数据 + */ +export async function fetchJobEvaluationEmptyState(): Promise { + return mockRequest(MOCK_JOB_EVALUATION_EMPTY); +} + +/* ===================== 选择身份 ===================== */ + +/** 身份选项(C 端求职者 / 企业端 HR) */ +const MOCK_IDENTITY_OPTIONS: IdentityOption[] = [ + { + id: 'candidate', + title: '我是求职者', + desc: '浏览岗位、查看公司评分、对比 Offer、提升竞争力', + icon: 'school-outline', + role: 'candidate', + }, + { + id: 'hr', + title: '我是企业 / HR', + desc: '发布岗位、管理评价、人才推荐、品牌透明度建设', + icon: 'briefcase-outline', + role: 'hr', + }, +]; + +/** + * 获取身份选项(首次启动 / 身份切换) + * @method GET + * @url /api/tools/identity-options + * @returns Promise 身份选项数组 + */ +export async function fetchIdentityOptions(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/tools/identity-options`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_IDENTITY_OPTIONS); +} + +/* ===================== 通关小游戏 ===================== */ + +/** 通关小游戏 7 关配置 */ +const MOCK_GAME_LEVELS: GameLevel[] = [ + { + id: 'self', + order: 1, + title: '认识自己', + subtitle: '性格与基本信息', + icon: 'person-outline', + description: '收集性格、年龄、学历、专业、学校、住址、兴趣爱好等基本信息,建立求职画像。', + nextLevel: 'ability', + questions: [ + { + id: 'self-1', + title: '你目前最高学历是?', + type: 'single', + options: [ + { id: 'a', text: '高中/中专及以下', isCorrect: false }, + { id: 'b', text: '大专', isCorrect: false }, + { id: 'c', text: '本科', isCorrect: true }, + { id: 'd', text: '硕士及以上', isCorrect: true }, + ], + explanation: '学历是岗位筛选的重要门槛,准确填写有助于平台匹配对应层级的岗位。', + tags: ['学历'], + }, + { + id: 'self-2', + title: '你希望未来 3 年在哪个城市工作?', + type: 'input', + placeholder: '例如:北京、上海、杭州', + explanation: '城市选择直接影响薪资预期与生活成本,建议结合行业聚集地进行选择。', + tags: ['城市'], + }, + ], + }, + { + id: 'ability', + order: 2, + title: '认识能力', + subtitle: '技能与成果', + icon: 'barbell-outline', + description: '明确自身具备的技能与可提升空间,包括项目开发、竞赛比赛、奖项、技能等维度。', + nextLevel: 'job', + questions: [ + { + id: 'ability-1', + title: '以下哪些属于可写入简历的硬技能证明?(多选)', + type: 'multiple', + options: [ + { id: 'a', text: 'GitHub 开源项目', isCorrect: true }, + { id: 'b', text: '学科竞赛获奖', isCorrect: true }, + { id: 'c', text: '微信朋友圈活跃', isCorrect: false }, + { id: 'd', text: '专业认证/资格证书', isCorrect: true }, + ], + explanation: '开源项目、竞赛获奖、专业认证都是简历中极具说服力的硬技能证明。', + tags: ['技能证明'], + }, + ], + }, + { + id: 'job', + order: 3, + title: '认识岗位', + subtitle: '职责与发展', + icon: 'briefcase-outline', + description: '了解不同岗位的职责要求、发展路径和薪资水平,明确目标岗位。', + nextLevel: 'company', + questions: [ + { + id: 'job-1', + title: '在筛选岗位时,最应优先关注的是?', + type: 'single', + options: [ + { id: 'a', text: '公司离地铁站的距离', isCorrect: false }, + { id: 'b', text: '岗位 JD 中的核心技能要求', isCorrect: true }, + { id: 'c', text: '办公室装修风格', isCorrect: false }, + { id: 'd', text: '招聘人员的头像', isCorrect: false }, + ], + explanation: '岗位 JD 中的核心技能要求决定了你是否能胜任,是筛选岗位的首要依据。', + tags: ['岗位理解'], + }, + ], + }, + { + id: 'company', + order: 4, + title: '认识企业', + subtitle: '文化与前景', + icon: 'business-outline', + description: '了解目标企业的背景、文化、发展前景,学会通过公开信息判断企业是否靠谱。', + nextLevel: 'resume', + questions: [ + { + id: 'company-1', + title: '判断一家企业是否靠谱,最不值得参考的是?', + type: 'single', + options: [ + { id: 'a', text: '工商注册信息与经营年限', isCorrect: false }, + { id: 'b', text: '员工真实评价与离职率', isCorrect: false }, + { id: 'c', text: '公司官网口号与宣传视频', isCorrect: true }, + { id: 'd', text: '融资阶段与核心业务', isCorrect: false }, + ], + explanation: '官网口号往往经过包装,工商信息、员工评价、业务健康度才是判断企业的硬指标。', + tags: ['企业判断'], + }, + ], + }, + { + id: 'resume', + order: 5, + title: '认识简历', + subtitle: '制作与优化', + icon: 'document-text-outline', + description: '借助 AI 助手了解简历制作的过程及要求,学习如何写出高通过率的简历。', + nextLevel: 'interview', + questions: [ + { + id: 'resume-1', + title: '简历中描述项目经历的最佳写法是?', + type: 'single', + options: [ + { id: 'a', text: '负责前端开发', isCorrect: false }, + { id: 'b', text: '使用 STAR 法则,量化结果', isCorrect: true }, + { id: 'c', text: '罗列所有课程名称', isCorrect: false }, + { id: 'd', text: '写满三页纸', isCorrect: false }, + ], + explanation: 'STAR 法则(情境-任务-行动-结果)加量化数据,是项目经历最说服人的写法。', + tags: ['简历'], + }, + ], + }, + { + id: 'interview', + order: 6, + title: '认识面试', + subtitle: '模拟与技巧', + icon: 'chatbubbles-outline', + description: '借助 AI 助手开展模拟面试,提前熟悉真实面试场景和常见问题。', + nextLevel: 'offer', + questions: [ + { + id: 'interview-1', + title: '面试中被问到「你最大的缺点是什么」,合适的回答是?', + type: 'single', + options: [ + { id: 'a', text: '我没有缺点', isCorrect: false }, + { id: 'b', text: '选择一个真实但无关岗位核心能力的缺点,并说明改进措施', isCorrect: true }, + { id: 'c', text: '抱怨上一家公司', isCorrect: false }, + { id: 'd', text: '沉默不答', isCorrect: false }, + ], + explanation: '坦诚展示自我认知与改进计划,能体现你的成长型思维。', + tags: ['面试'], + }, + ], + }, + { + id: 'offer', + order: 7, + title: '认识签约', + subtitle: 'Offer 与入职', + icon: 'file-tray-full-outline', + description: '学习 Offer 选择、入职准备、试用期注意事项、合同签订等关键环节。', + questions: [ + { + id: 'offer-1', + title: '签订劳动合同前,必须确认的关键条款不包括?', + type: 'single', + options: [ + { id: 'a', text: '薪资结构与发放时间', isCorrect: false }, + { id: 'b', text: '试用期时长与转正条件', isCorrect: false }, + { id: 'c', text: '公司团建频率', isCorrect: true }, + { id: 'd', text: '工作岗位与工作地点', isCorrect: false }, + ], + explanation: '团建频率不属于劳动合同必备条款,薪资、试用期、岗位地点才是核心。', + tags: ['签约'], + }, + ], + }, +]; + +/** 默认游戏进度:第一关可玩 */ +const DEFAULT_GAME_PROGRESS: GameProgress = { + currentLevel: 'self', + unlockedLevels: ['self'], + results: [], + totalPercent: 0, +}; + +/** 内存中的游戏进度(mock 阶段用,刷新后重置) */ +let MOCK_GAME_PROGRESS: GameProgress = { ...DEFAULT_GAME_PROGRESS }; + +/** + * 获取所有关卡配置 + * @method GET + * @url /api/game/levels + * @returns Promise 关卡数组 + */ +export async function fetchGameLevels(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/game/levels`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_GAME_LEVELS); +} + +/** + * 获取当前游戏进度 + * @method GET + * @url /api/game/progress + * @returns Promise 游戏进度 + */ +export async function fetchGameProgress(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/game/progress`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_GAME_PROGRESS); +} + +/** + * 提交关卡答案并计算结果 + * @method POST + * @url /api/game/levels/:levelId/submit + * @param payload {SubmitGameLevelPayload} 关卡 id 与作答 + * @returns Promise 更新后的游戏进度 + */ +export async function submitGameLevel( + payload: SubmitGameLevelPayload, +): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/game/levels/${payload.levelId}/submit`, { method: 'POST', body: JSON.stringify(payload) }); return res.json(); + const level = MOCK_GAME_LEVELS.find((l) => l.id === payload.levelId); + if (!level) { + return mockRequest(MOCK_GAME_PROGRESS); + } + + let correctCount = 0; + payload.answers.forEach((answer) => { + const question = level.questions.find((q) => q.id === answer.questionId); + if (!question) return; + if (question.type === 'input') { + // 输入题只要有内容即算完成(不判定对错) + if (answer.inputValue && answer.inputValue.trim().length > 0) correctCount += 1; + } else { + const correctIds = + question.options?.filter((o) => o.isCorrect).map((o) => o.id) ?? []; + const isCorrect = + correctIds.length > 0 && + correctIds.length === answer.selectedIds.length && + correctIds.every((id) => answer.selectedIds.includes(id)); + if (isCorrect) correctCount += 1; + answer.isCorrect = isCorrect; + } + }); + + const score = level.questions.length > 0 ? Math.round((correctCount / level.questions.length) * 100) : 100; + const completed = score >= 60; + + const existingIndex = MOCK_GAME_PROGRESS.results.findIndex((r) => r.levelId === payload.levelId); + const result: GameLevelResult = { + levelId: payload.levelId, + completed, + score, + answers: payload.answers, + completedAt: completed ? new Date().toISOString() : undefined, + }; + + if (existingIndex >= 0) { + MOCK_GAME_PROGRESS.results[existingIndex] = result; + } else { + MOCK_GAME_PROGRESS.results.push(result); + } + + if (completed && level.nextLevel && !MOCK_GAME_PROGRESS.unlockedLevels.includes(level.nextLevel)) { + MOCK_GAME_PROGRESS.unlockedLevels.push(level.nextLevel); + MOCK_GAME_PROGRESS.currentLevel = level.nextLevel; + } + + const completedCount = MOCK_GAME_PROGRESS.results.filter((r) => r.completed).length; + MOCK_GAME_PROGRESS.totalPercent = Math.round((completedCount / MOCK_GAME_LEVELS.length) * 100); + + return mockRequest(MOCK_GAME_PROGRESS); +} + +/** + * 重置游戏进度(仅 mock 阶段方便演示) + * @method POST + * @url /api/game/reset + * @returns Promise 重置后的进度 + */ +export async function resetGameProgress(): Promise { + MOCK_GAME_PROGRESS = { ...DEFAULT_GAME_PROGRESS }; + return mockRequest(MOCK_GAME_PROGRESS); +} + +/* ===================== 阿布阿诺 AI 法律服务助手 ===================== */ + +/** 阿布阿诺品牌信息 */ +const MOCK_LEGAL_BRAND: LegalBrand = { + name: '阿布阿诺', + company: '阿布阿诺人工智能科技有限公司', + slogan: '求职路上,法律护航', + logo: '🛡️', +}; + +/** 法律服务入口列表 */ +const MOCK_LEGAL_SERVICES: LegalService[] = [ + { + id: 'free-consult', + title: '免费法律咨询', + description: '劳动权益、劳动合同、求职陷阱等基础法律问题快速解答。', + icon: 'chatbubble-ellipses-outline', + type: 'free', + action: 'chat', + }, + { + id: 'free-templates', + title: '文书模板下载', + description: '劳动合同审查清单、离职申请书、劳动仲裁申请书等常用模板。', + icon: 'document-attach-outline', + type: 'free', + action: 'templates', + }, + { + id: 'free-faq', + title: '常见问题速查', + description: '试用期被辞退、拖欠工资、不签劳动合同等高频问题。', + icon: 'help-circle-outline', + type: 'free', + action: 'faq', + }, + { + id: 'member-lawyer', + title: '一对一律师咨询', + description: '专业劳动法律师在线答疑,定制解决方案。', + icon: 'people-outline', + type: 'member', + action: 'upgrade', + }, + { + id: 'member-review', + title: '合同/文书审核', + description: '劳动合同、竞业协议、保密协议等深度审核与风险提示。', + icon: 'reader-outline', + type: 'member', + action: 'upgrade', + }, + { + id: 'member-advisor', + title: '专属法律顾问', + description: '全年专属法律顾问,随时响应求职与职场法律问题。', + icon: 'shield-checkmark-outline', + type: 'member', + action: 'upgrade', + }, +]; + +/** 法律常见问题 */ +const MOCK_LEGAL_FAQS: LegalFAQ[] = [ + { + id: 'faq-1', + question: '试用期被辞退,公司需要赔偿吗?', + answer: '如果公司无法证明你「不符合录用条件」,属于违法解除,可要求赔偿金(2N)或继续履行合同。', + category: '试用期', + }, + { + id: 'faq-2', + question: '入职后公司一直不签劳动合同怎么办?', + answer: '自用工之日起超过一个月不满一年未签合同的,劳动者可主张每月二倍工资差额。', + category: '劳动合同', + }, + { + id: 'faq-3', + question: '公司拖欠工资,我可以直接离职吗?', + answer: '用人单位未及时足额支付劳动报酬的,劳动者可以解除劳动合同并要求经济补偿金(N)。', + category: '工资', + }, + { + id: 'faq-4', + question: 'Offer 上写的薪资和合同不一致,以哪个为准?', + answer: '一般以劳动合同为准,但 Offer、聊天记录、邮件等也可作为佐证。建议入职前要求书面确认薪资结构。', + category: 'Offer', + }, +]; + +/** 法律文书模板 */ +const MOCK_LEGAL_TEMPLATES: LegalTemplate[] = [ + { + id: 'tmpl-1', + name: '劳动合同审查清单', + description: '入职前必查 12 项关键条款', + icon: 'checkbox-outline', + url: 'https://dev.api.com/legal/templates/contract-checklist', + }, + { + id: 'tmpl-2', + name: '离职申请书', + description: '规范离职通知,保留证据', + icon: 'exit-outline', + url: 'https://dev.api.com/legal/templates/resignation-letter', + }, + { + id: 'tmpl-3', + name: '劳动仲裁申请书', + description: '劳动争议维权起步文书', + icon: 'gavel-outline', + url: 'https://dev.api.com/legal/templates/arbitration-application', + }, +]; + +/** 会员套餐 */ +const MOCK_LEGAL_PLANS: LegalPlan[] = [ + { + id: 'plan-month', + name: '月度会员', + price: 29, + period: '/月', + benefits: ['3 次一对一律师咨询', '1 份文书审核', '常见问题不限次答疑'], + }, + { + id: 'plan-year', + name: '年度会员', + price: 199, + period: '/年', + recommended: true, + benefits: ['不限次一对一律师咨询', '不限次文书审核', '专属法律顾问', '优先响应'], + }, +]; + +/** 法律咨询默认欢迎会话 */ +const MOCK_LEGAL_CONVERSATION: LegalConversation = { + id: 'default', + title: '阿布阿诺法律咨询', + messages: [ + { + id: 'm1', + role: 'assistant', + content: '你好,我是阿布阿诺 AI 法律助手。我可以帮你解答求职过程中的劳动权益、劳动合同、试用期辞退等法律问题。请问你遇到什么问题了?', + timestamp: new Date().toISOString(), + }, + ], +}; + +/** + * 获取阿布阿诺品牌信息 + * @method GET + * @url /api/legal/brand + * @returns Promise 品牌信息 + */ +export async function fetchLegalBrand(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/legal/brand`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_LEGAL_BRAND); +} + +/** + * 获取法律服务入口列表 + * @method GET + * @url /api/legal/services + * @returns Promise 服务列表 + */ +export async function fetchLegalServices(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/legal/services`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_LEGAL_SERVICES); +} + +/** + * 获取法律常见问题 + * @method GET + * @url /api/legal/faqs + * @returns Promise FAQ 列表 + */ +export async function fetchLegalFAQs(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/legal/faqs`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_LEGAL_FAQS); +} + +/** + * 获取法律文书模板 + * @method GET + * @url /api/legal/templates + * @returns Promise 模板列表 + */ +export async function fetchLegalTemplates(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/legal/templates`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_LEGAL_TEMPLATES); +} + +/** + * 获取会员套餐 + * @method GET + * @url /api/legal/plans + * @returns Promise 套餐列表 + */ +export async function fetchLegalPlans(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/legal/plans`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_LEGAL_PLANS); +} + +/** + * 获取默认法律咨询服务会话 + * @method GET + * @url /api/legal/conversations/default + * @returns Promise 咨询会话 + */ +export async function fetchLegalConversation(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/legal/conversations/default`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_LEGAL_CONVERSATION); +} + +/** + * 发送法律咨询消息(mock 阶段模拟 AI 回复) + * @method POST + * @url /api/legal/conversations/default/messages + * @param content {string} 用户输入 + * @returns Promise 最新两条消息(用户 + 助手) + */ +export async function sendLegalMessage(content: string): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/legal/conversations/default/messages`, { method: 'POST', body: JSON.stringify({ content }) }); return res.json(); + const now = new Date().toISOString(); + const userMessage: LegalMessage = { + id: `u-${Date.now()}`, + role: 'user', + content, + timestamp: now, + }; + const assistantMessage: LegalMessage = { + id: `a-${Date.now()}`, + role: 'assistant', + content: + '收到你的问题。作为阿布阿诺 AI 法律助手,我建议你先保留相关证据(聊天记录、合同、Offer、工资流水等)。如情况复杂,可升级到一对一律师咨询获取专业方案。', + timestamp: now, + }; + return mockRequest([userMessage, assistantMessage]); +} + +/* ===================== 设置:隐私设置 ===================== */ + +/** 隐私开关(内存态,mock 阶段刷新后重置) */ +let MOCK_PRIVACY_SETTINGS: ToggleSetting[] = [ + { + id: 'resume-visible', + title: '简历对 HR 可见', + description: '关闭后企业将无法搜索到你的简历', + enabled: true, + }, + { + id: 'anonymous-browse', + title: '匿名浏览企业', + description: '浏览公司主页时不留下访问记录', + enabled: false, + }, + { + id: 'show-job-status', + title: '展示求职状态', + description: '在个人主页公开「在职-考虑机会」等状态', + enabled: true, + }, + { + id: 'show-salary', + title: '展示薪资期望', + description: '允许企业在推荐列表中看到你的期望薪资', + enabled: false, + }, + { + id: 'allow-bgcheck', + title: '允许背景调查', + description: '入职前授权企业进行雇佣背景核实', + enabled: true, + }, + { + id: 'personalized', + title: '个性化岗位推荐', + description: '基于你的浏览与投递行为推荐匹配岗位', + enabled: true, + }, +]; + +/** + * 获取隐私设置开关列表 + * @method GET + * @url /api/settings/privacy + * @returns Promise 隐私开关列表 + */ +export async function fetchPrivacySettings(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/settings/privacy`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_PRIVACY_SETTINGS); +} + +/** + * 更新单个隐私开关 + * @method PATCH + * @url /api/settings/privacy/:id + * @param payload {UpdateTogglePayload} 开关 id 与目标状态 + * @returns Promise 更新后的开关列表 + */ +export async function updatePrivacySetting( + payload: UpdateTogglePayload, +): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/settings/privacy/${payload.id}`, { method: 'PATCH', body: JSON.stringify(payload) }); return res.json(); + const target = MOCK_PRIVACY_SETTINGS.find((item) => item.id === payload.id); + if (target) { + target.enabled = payload.enabled; + } + return mockRequest(MOCK_PRIVACY_SETTINGS); +} + +/* ===================== 隐私中心(截图版) ===================== */ + +const MOCK_PRIVACY_CENTER: PrivacyCenter = { + privacyLevel: { + level: 'high', + label: '较高', + description: '你的数据保护状态良好,但仍有可调整空间。进一步提升隐私', + }, + sections: [ + { + id: 'account', + title: '账号隐私', + items: [ + { + id: 'real-name-visible', + title: '真实姓名可见性', + type: 'switch', + enabled: true, + route: '/settings/privacy/real-name', + }, + { + id: 'contact-visible', + title: '联系方式可见性', + type: 'switch', + enabled: true, + route: '/settings/privacy/contact', + }, + { id: 'avatar-public', title: '头像公开', type: 'switch', enabled: true }, + { id: 'searchable', title: '允许被搜索', type: 'switch', enabled: true }, + { id: 'resume-view-notify', title: '简历被浏览通知', type: 'switch', enabled: true }, + ], + }, + { + id: 'authorization', + title: '数据使用授权', + items: [ + { id: 'personalized-recommend', title: '个性化推荐', type: 'switch', enabled: true }, + { + id: 'algo-training', + title: '数据用于算法训练', + description: '匿名数据用于提升匹配效果', + type: 'switch', + enabled: false, + route: '/settings/privacy/algo', + }, + { + id: 'third-party-analysis', + title: '允许第三方数据分析', + type: 'switch', + enabled: false, + route: '/settings/privacy/third-party', + }, + { + id: 'location-auth', + title: '位置信息授权', + description: '仅用于岗位推荐', + type: 'switch', + enabled: true, + route: '/settings/privacy/location', + }, + { + id: 'contacts-auth', + title: '通讯录权限', + type: 'switch', + enabled: false, + route: '/settings/privacy/contacts', + }, + ], + }, + { + id: 'anti-harassment', + title: '求职防骚扰', + items: [ + { + id: 'block-unmatched', + title: '屏蔽未匹配企业联系', + type: 'switch', + enabled: true, + }, + { id: 'block-headhunter', title: '屏蔽猎头/中介', type: 'switch', enabled: false }, + { + id: 'dnd-mode', + title: '勿扰模式', + description: '已屏蔽 3 个关键词', + type: 'switch', + enabled: true, + route: '/settings/privacy/dnd', + }, + { + id: 'keyword-block', + title: '关键词屏蔽', + description: '已屏蔽 3 个关键词', + type: 'value', + value: '3个', + route: '/settings/privacy/keywords', + }, + ], + }, + { + id: 'data-management', + title: '数据管理', + items: [ + { + id: 'personal-data-export', + title: '个人数据导出', + type: 'link', + route: '/personal-data-export', + }, + { id: 'account-cancel', title: '账号注销', type: 'link', route: '/settings/account-cancel' }, + { + id: 'delete-all-data', + title: '删除全部数据', + type: 'danger-link', + route: '/settings/delete-data', + }, + { id: 'privacy-policy', title: '隐私政策', type: 'link', route: '/settings/privacy-policy' }, + { + id: 'user-agreement', + title: '用户协议', + type: 'link', + route: '/settings/user-agreement', + }, + ], + }, + ], + explanation: + '我们承诺严格保护你的个人隐私,所有数据使用均符合《个人信息保护法》。你可以随时调整隐私设置,我们不会向第三方出售或泄露你的个人信息。', +}; + +/** + * 获取隐私中心聚合数据 + * @method GET + * @url /api/settings/privacy-center + * @returns Promise 隐私中心数据 + */ +export async function fetchPrivacyCenter(): Promise { + return mockRequest(MOCK_PRIVACY_CENTER); +} + +/** + * 更新隐私中心开关项 + * @method PATCH + * @url /api/settings/privacy-center/:id + * @param payload {UpdateTogglePayload} 开关 id 与目标状态 + * @returns Promise 更新后的隐私中心数据 + */ +export async function updatePrivacyCenterSetting( + payload: UpdateTogglePayload, +): Promise { + for (const section of MOCK_PRIVACY_CENTER.sections) { + const target = section.items.find((item) => item.id === payload.id); + if (target && target.type === 'switch') { + target.enabled = payload.enabled; + } + } + return mockRequest(MOCK_PRIVACY_CENTER); +} + +/* ===================== 个人数据导出 ===================== */ + +const MOCK_PERSONAL_DATA_EXPORT: PersonalDataExport = { + title: '导出你的个人数据', + description: + '你可以导出在平台产生的所有个人数据,包括基本信息、简历数据、求职记录、测评结果、消息记录、行为数据等。数据将以结构化的方式导出,便于你备份或迁移。', + categories: [ + { + id: 'basic', + label: '基本信息', + description: '姓名、电话、邮箱、头像等', + size: '约20KB', + items: [{ id: 'basic-info', label: '基本信息', size: '约20KB', checked: true }], + }, + { + id: 'resume', + label: '简历数据', + description: '简历内容、投递记录、收藏等', + size: '约30KB', + items: [{ id: 'resume-data', label: '简历数据', size: '约30KB', checked: true }], + }, + { + id: 'job', + label: '求职记录', + description: '投递、沟通、面试记录、Offer记录等', + size: '约100KB', + items: [{ id: 'job-records', label: '求职记录', size: '约100KB', checked: true }], + }, + { + id: 'assessment', + label: '测评数据', + description: '能力测评、职业兴趣测评结果等', + size: '约30KB', + items: [{ id: 'assessment-data', label: '测评数据', size: '约30KB', checked: true }], + }, + { + id: 'message', + label: '消息记录', + description: '聊天消息、系统通知等', + size: '约200KB', + items: [{ id: 'message-data', label: '消息记录', size: '约200KB', checked: true }], + }, + { + id: 'behavior', + label: '行为数据', + description: '浏览、搜索、点击记录等', + size: '约500KB', + items: [{ id: 'behavior-data', label: '行为数据', size: '约500KB', checked: true }], + }, + ], + formats: [ + { + id: 'json', + label: 'JSON', + icon: 'logo-nodejs', + description: '结构化数据,便于开发', + }, + { + id: 'excel', + label: 'Excel', + icon: 'grid-outline', + description: '表格格式,便于查看分析', + }, + ], + timeRanges: [ + { id: 'all', label: '全部数据' }, + { id: 'year', label: '近一年' }, + { id: 'quarter', label: '近三个月' }, + { id: 'custom', label: '自定义' }, + ], + selectedFormat: 'json', + selectedTimeRange: 'all', + preview: { + categoryCount: 4, + totalSize: '约200KB', + estimatedTime: '1-2分钟', + validity: '导出后7天', + }, + recentExports: [ + { + id: 'r1', + name: '完整数据导出.zip', + date: '2024-08-01 14:30', + size: '25KB', + status: 'expired', + }, + { + id: 'r2', + name: '简历数据.xlsx', + date: '2024-07-31 10:12', + size: '80KB', + status: 'completed', + }, + { + id: 'r3', + name: '测评数据.json', + date: '2024-07-25 09:08', + size: '30KB', + status: 'expired', + }, + ], +}; + +/** + * 获取个人数据导出配置 + * @method GET + * @url /api/settings/personal-data-export + * @returns Promise 个人数据导出数据 + */ +export async function fetchPersonalDataExport(): Promise { + return mockRequest(MOCK_PERSONAL_DATA_EXPORT); +} + +/** + * 申请导出个人数据 + * @method POST + * @url /api/settings/personal-data-export + * @returns Promise<{ success: boolean; message: string }> 申请结果 + */ +export async function requestPersonalDataExport(): Promise<{ success: boolean; message: string }> { + return mockRequest({ success: true, message: '导出申请已提交,预计 1-2 分钟后可在最近导出中下载。' }); +} + +/* ===================== 设置:消息通知 ===================== */ + +/** 通知开关(内存态) */ +let MOCK_NOTIFICATION_SETTINGS: ToggleSetting[] = [ + { + id: 'interview-remind', + title: '面试提醒', + description: '面试开始前 1 小时推送提醒', + enabled: true, + }, + { + id: 'offer-notify', + title: 'Offer 通知', + description: '收到 Offer 或录用意向时通知我', + enabled: true, + }, + { + id: 'job-recommend', + title: '岗位推荐', + description: '每日推送匹配度高的新岗位', + enabled: true, + }, + { + id: 'hr-reply', + title: '企业回复', + description: 'HR 查看简历或回复消息时通知我', + enabled: true, + }, + { + id: 'system-msg', + title: '系统消息', + description: '平台公告、规则更新等系统通知', + enabled: true, + }, + { + id: 'marketing', + title: '活动与优惠', + description: '会员活动、法律服务优惠等营销推送', + enabled: false, + }, +]; + +/** + * 获取消息通知开关列表 + * @method GET + * @url /api/settings/notification + * @returns Promise 通知开关列表 + */ +export async function fetchNotificationSettings(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/settings/notification`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_NOTIFICATION_SETTINGS); +} + +/** + * 更新单个通知开关 + * @method PATCH + * @url /api/settings/notification/:id + * @param payload {UpdateTogglePayload} 开关 id 与目标状态 + * @returns Promise 更新后的开关列表 + */ +export async function updateNotificationSetting( + payload: UpdateTogglePayload, +): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/settings/notification/${payload.id}`, { method: 'PATCH', body: JSON.stringify(payload) }); return res.json(); + const target = MOCK_NOTIFICATION_SETTINGS.find((item) => item.id === payload.id); + if (target) { + target.enabled = payload.enabled; + } + return mockRequest(MOCK_NOTIFICATION_SETTINGS); +} + +/* ===================== 设置:账号与安全 ===================== */ + +/** 账号与安全操作项 */ +const MOCK_ACCOUNT_SECURITY: ActionSetting[] = [ + { + id: 'phone', + title: '手机号', + description: '用于登录与接收重要通知', + value: '138****8888', + variant: 'normal', + }, + { + id: 'wechat', + title: '微信账号', + description: '绑定后可使用微信快捷登录', + value: '未绑定', + variant: 'normal', + }, + { + id: 'password', + title: '修改登录密码', + description: '建议定期更新密码以保护账号安全', + variant: 'normal', + }, + { + id: 'devices', + title: '登录设备管理', + description: '查看并移除已登录的设备', + variant: 'normal', + }, + { + id: 'deactivate', + title: '注销账号', + description: '注销后账号数据将被永久删除,不可恢复', + variant: 'danger', + }, +]; + +/** + * 获取账号与安全操作项列表 + * @method GET + * @url /api/settings/account-security + * @returns Promise 操作项列表 + */ +export async function fetchAccountSecurity(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/settings/account-security`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_ACCOUNT_SECURITY); +} + +/* ===================== 设置:关于我们 ===================== */ + +/** 关于我们信息 */ +const MOCK_ABOUT_INFO: AboutInfo = { + appName: '职来', + version: 'v1.0.0', + company: '阿布阿诺人工智能科技有限公司', + description: + '职来是面向全社会青年求职者的双向透明求职生态平台,提供公司调研、真实评价、行业声誉与岗位评价分级,帮助用户找到更适合自己的工作。', + links: [ + { id: 'privacy-policy', title: '隐私政策' }, + { id: 'terms', title: '用户服务协议' }, + { id: 'legal', title: '阿布阿诺法律服务', route: '/legal' }, + ], +}; + +/** + * 获取关于我们信息 + * @method GET + * @url /api/settings/about + * @returns Promise 关于我们信息 + */ +export async function fetchAboutInfo(): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/settings/about`, { method: 'GET' }); return res.json(); + return mockRequest(MOCK_ABOUT_INFO); +} + +/* ===================== HR 聊天详情 ===================== */ + +/** HR 单聊会话 mock 数据(对应用户截图:张HR / 字节跳动 / 高级前端 / 95%匹配) */ +const MOCK_CONVERSATION_DETAIL: ConversationDetail = { + id: 'zhang-hr', + contactName: '张HR', + companyName: '字节跳动', + positionName: '高级前端', + avatar: '张', + online: true, + matchInfo: { score: 95 }, + messages: [ + { + id: 'm0', + role: 'system', + content: '你们已经成为好友,开始聊天吧', + }, + { + id: 'm1', + role: 'hr', + content: '您好,我是字节跳动的张HR,看到您的简历了,非常匹配我们的高级前端岗位。', + timestamp: '10:30', + }, + { + id: 'm2', + role: 'hr', + content: '方便约个时间面试吗?我们这边流程比较快~', + timestamp: '10:30', + }, + { + id: 'm3', + role: 'user', + content: '您好张HR,感谢关注!请问这个岗位的具体要求是什么?', + timestamp: '10:32', + }, + { + id: 'm4', + role: 'hr', + content: '主要考察React、TypeScript和工程化能力,您可以先看看面试题准备一下~', + timestamp: '10:35', + }, + { + id: 'm5', + role: 'hr', + timestamp: '10:35', + card: { + type: 'interview', + title: '面试题预测 · 前端高频', + items: [ + 'React Hooks 原理及常见坑', + '前端性能优化方案', + '微前端架构实践', + ], + actionText: '查看全部 12 道预测题', + }, + }, + { + id: 'm6', + role: 'hr', + timestamp: '10:35', + card: { + type: 'salary', + title: '市场薪资参考', + subtitle: '该岗位 3-5 年经验市场薪资:22-38K\n你的期望薪资 30K 处于合理区间', + actionText: '查看详细薪资报告', + }, + }, + { + id: 'm7', + role: 'hr', + card: { + type: 'offer', + title: '你收到了新Offer,点击对比', + subtitle: '字节跳动 vs 腾讯科技,2个Offer', + actionText: '立即对比', + }, + }, + ], +}; + +/** + * 获取 HR 单聊会话详情 + * @method GET + * @url /api/chat/conversations/:id + * @param id {string} 会话 id + * @returns Promise 会话详情 + */ +export async function fetchConversation(id: string): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/chat/conversations/${id}`, { method: 'GET' }); return res.json(); + return mockRequest({ ...MOCK_CONVERSATION_DETAIL, id }); +} + +/** + * 发送聊天消息(mock 阶段自动追加用户消息 + HR 自动回复) + * @method POST + * @url /api/chat/conversations/:id/messages + * @param conversationId {string} 会话 id + * @param content {string} 用户输入 + * @returns Promise 新增的两条消息(用户 + 助手) + */ +export async function sendChatMessage( + _conversationId: string, + content: string, +): Promise { + // TODO: const res = await fetch(`${API_BASE_URL}/api/chat/conversations/${conversationId}/messages`, { method: 'POST', body: JSON.stringify({ content }) }); return res.json(); + const now = new Date(); + const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`; + const userMessage: ChatMessage = { + id: `u-${Date.now()}`, + role: 'user', + content, + timestamp: time, + }; + const hrReply: ChatMessage = { + id: `a-${Date.now()}`, + role: 'hr', + content: '收到,我会尽快查看并回复你。', + timestamp: time, + }; + return mockRequest([userMessage, hrReply]); +} diff --git a/src/api/index.ts b/src/api/index.ts index 7874e4f..cc22818 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -2,31 +2,92 @@ * 接口统一导出 */ export { + fetchAboutInfo, fetchAbilityAssessment, + fetchAccountSecurity, + fetchAIInterviewData, fetchAITools, + fetchAppealForm, fetchArticles, - fetchAssessmentTools, + fetchAuditReport, fetchBadges, - fetchCategories, + fetchCandidateDetail, + fetchCompareData, fetchConversations, + fetchConversation, fetchDimensionScores, + sendChatMessage, + fetchEnterpriseCertification, + fetchEnterpriseCommunication, + fetchEnterpriseOverview, + fetchGameLevels, + fetchGameProgress, + fetchHollandIntro, + fetchInterviewScore, + fetchJobEvaluationEmptyState, + fetchJobEvaluationManagement, + fetchJobManagement, + fetchJobPublishForm, + fetchTalentRecommendation, + fetchLegalBrand, + fetchLegalConversation, + fetchLegalFAQs, + fetchLegalPlans, + fetchLegalServices, + fetchLegalTemplates, fetchEmployeeReviews, fetchEnterpriseEntry, fetchHollandAssessment, - fetchHomeAITools, - fetchHotJobs, + fetchHollandResult, + fetchHREvaluation, + fetchIdentityOptions, fetchInterviews, fetchJobDetail, fetchJobList, + fetchOfferCompareData, fetchJobOverallScore, fetchJobPreference, - fetchMatchData, + fetchJobProgress, fetchMatchResult, fetchMenuItems, fetchNotifications, + fetchNotificationSettings, + fetchOnboardingChecklist, + fetchOperationRules, fetchOverallScore, + fetchPrivacySettings, + fetchPrivacyCenter, + updatePrivacyCenterSetting, + fetchPersonalDataExport, + requestPersonalDataExport, + fetchRankData, + fetchResume, + fetchResumeExportSettings, + fetchSalaryBrackets, fetchSalaryData, - fetchUserInfo, + fetchSkillCloud, fetchUserProfile, fetchUserStats, + fetchWeightSettings, + inviteCandidate, + estimateJobQuality, + optimizeResume, + publishJob, + replyJobReview, + saveOperationRules, + saveResumeExportSettings, + exportResume, + saveWeightSettings, + submitEnterpriseCertification, + submitInterviewScore, + computeSalaryResult, + resetGameProgress, + sendLegalMessage, + startInterview, + submitAppeal, + submitGameLevel, + submitHollandAnswer, + toggleChecklistItem, + updateNotificationSetting, + updatePrivacySetting, } from './api'; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index f8de3f0..de9f7ab 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,13 +1,48 @@ /** * hooks 统一导出 */ +export { useAbilityAssessment } from './useAbilityAssessment'; +export { useAboutInfo } from './useAboutInfo'; +export { useAccountSecurity } from './useAccountSecurity'; +export { useAIInterview } from './useAIInterview'; +export { useAppealForm } from './useAppealForm'; export { useAsyncData } from './useAsyncData'; -export { useCategoryData } from './useCategoryData'; +export { useAuditReport } from './useAuditReport'; +export { useCandidateDetail } from './useCandidateDetail'; +export { useCompareData } from './useCompareData'; +export { useConversation } from './useConversation'; +export { useEnterpriseCertification } from './useEnterpriseCertification'; +export { useEnterpriseCommunication } from './useEnterpriseCommunication'; +export { useEnterpriseOverview } from './useEnterpriseOverview'; +export { useGameData } from './useGameData'; export { useGrowthData } from './useGrowthData'; -export { useHomeData } from './useHomeData'; +export { useHollandAssessment } from './useHollandAssessment'; +export { useHollandIntro } from './useHollandIntro'; +export { useHREvaluation } from './useHREvaluation'; +export { useIdentityOptions } from './useIdentityOptions'; +export { useInterviewScore } from './useInterviewScore'; export { useJobDetail } from './useJobDetail'; +export { useJobEvaluationEmptyState } from './useJobEvaluationEmptyState'; +export { useJobEvaluations } from './useJobEvaluations'; export { useJobList } from './useJobList'; -export { useMatchData } from './useMatchData'; +export { useJobManagement } from './useJobManagement'; +export { useJobProgress } from './useJobProgress'; +export { useJobPublish } from './useJobPublish'; +export { useLegalData } from './useLegalData'; export { useMessageData } from './useMessageData'; +export { useNotificationSettings } from './useNotificationSettings'; +export { useOfferCompareData } from './useOfferCompareData'; +export { useOnboardingChecklist } from './useOnboardingChecklist'; +export { useOperationRules } from './useOperationRules'; +export { usePrivacyCenter } from './usePrivacyCenter'; +export { usePrivacySettings } from './usePrivacySettings'; export { useProfileData } from './useProfileData'; -export { useUserProfile } from './useUserProfile'; +export { usePersonalDataExport } from './usePersonalDataExport'; +export { useRankData } from './useRankData'; +export { useResume } from './useResume'; +export { useResumeExport } from './useResumeExport'; +export { useSalaryBrackets } from './useSalaryBrackets'; +export { useSalaryCalculator } from './useSalaryCalculator'; +export { useSkillCloud } from './useSkillCloud'; +export { useTalentRecommendation } from './useTalentRecommendation'; +export { useWeightSettings } from './useWeightSettings'; diff --git a/src/hooks/useAIInterview.ts b/src/hooks/useAIInterview.ts new file mode 100644 index 0000000..3dcf15e --- /dev/null +++ b/src/hooks/useAIInterview.ts @@ -0,0 +1,64 @@ +/** + * AI 模拟面试 hook:拉取面试配置 + 开始面试。 + */ +import { useCallback, useState } from 'react'; +import { fetchAIInterviewData, startInterview } from '../api'; +import type { AIInterviewData, InterviewMode, InterviewQuestionTypeCode, InterviewSession } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useAIInterview() { + const fetcher = useCallback(() => fetchAIInterviewData(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + /** 本地交互状态:模式 / 题型 / 专项 */ + const [mode, setMode] = useState(null); + const [questionTypes, setQuestionTypes] = useState([]); + const [specials, setSpecials] = useState([]); + const [starting, setStarting] = useState(false); + + const resolvedMode = mode ?? data?.mode ?? 'full'; + const resolvedTypes = questionTypes.length + ? questionTypes + : (data?.questionTypes.filter((t) => t.selected).map((t) => t.id) ?? []); + const resolvedSpecials = specials.length + ? specials + : (data?.specials.filter((s) => s.selected).map((s) => s.id) ?? []); + + const toggleQuestionType = useCallback((id: InterviewQuestionTypeCode) => { + setQuestionTypes((prev) => { + const base = + prev.length > 0 + ? prev + : ([] as InterviewQuestionTypeCode[]); + return base.includes(id) ? base.filter((t) => t !== id) : [...base, id]; + }); + }, []); + + const toggleSpecial = useCallback((id: string) => { + setSpecials((prev) => (prev.includes(id) ? prev.filter((s) => s !== id) : [...prev, id])); + }, []); + + const start = useCallback(async (): Promise => { + setStarting(true); + try { + return await startInterview(); + } finally { + setStarting(false); + } + }, []); + + return { + data: data ?? null, + loading, + error, + refresh, + mode: resolvedMode, + setMode, + questionTypes: resolvedTypes, + toggleQuestionType, + specials: resolvedSpecials, + toggleSpecial, + start, + starting, + }; +} diff --git a/src/hooks/useAbilityAssessment.ts b/src/hooks/useAbilityAssessment.ts new file mode 100644 index 0000000..7cfc678 --- /dev/null +++ b/src/hooks/useAbilityAssessment.ts @@ -0,0 +1,19 @@ +/** + * 四维能力测评 hook:拉取能力测评结果,统一管理 loading / error / refresh。 + */ +import { useCallback } from 'react'; +import { fetchAbilityAssessment } from '../api'; +import { useAsyncData } from './useAsyncData'; + +export function useAbilityAssessment() { + const fetcher = useCallback(() => fetchAbilityAssessment(), []); + + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + ability: data ?? null, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useAboutInfo.ts b/src/hooks/useAboutInfo.ts new file mode 100644 index 0000000..eb464de --- /dev/null +++ b/src/hooks/useAboutInfo.ts @@ -0,0 +1,30 @@ +/** + * 关于我们 hook:拉取应用版本、公司主体与协议链接。 + */ +import { useCallback } from 'react'; +import { fetchAboutInfo } from '../api'; +import type { AboutInfo } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface AboutInfoState { + /** 关于我们信息 */ + about: AboutInfo | null; + /** 是否加载中 */ + loading: boolean; + /** 错误对象 */ + error: Error | null; + /** 刷新数据 */ + refresh: () => void; +} + +export function useAboutInfo(): AboutInfoState { + const fetcher = useCallback(() => fetchAboutInfo(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + about: (data as AboutInfo | null) ?? null, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useAccountSecurity.ts b/src/hooks/useAccountSecurity.ts new file mode 100644 index 0000000..e174ce0 --- /dev/null +++ b/src/hooks/useAccountSecurity.ts @@ -0,0 +1,30 @@ +/** + * 账号与安全 hook:拉取账号安全操作项列表(绑定手机 / 微信 / 改密 / 注销等)。 + */ +import { useCallback } from 'react'; +import { fetchAccountSecurity } from '../api'; +import type { ActionSetting } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface AccountSecurityState { + /** 操作项列表 */ + items: ActionSetting[]; + /** 是否加载中 */ + loading: boolean; + /** 错误对象 */ + error: Error | null; + /** 刷新数据 */ + refresh: () => void; +} + +export function useAccountSecurity(): AccountSecurityState { + const fetcher = useCallback(() => fetchAccountSecurity(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + items: (data as ActionSetting[] | null) ?? [], + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useAppealForm.ts b/src/hooks/useAppealForm.ts new file mode 100644 index 0000000..88b0ef2 --- /dev/null +++ b/src/hooks/useAppealForm.ts @@ -0,0 +1,79 @@ +/** + * 评价申诉 hook:拉取申诉表单 + 提交申诉。 + */ +import { useCallback, useEffect, useState } from 'react'; +import { fetchAppealForm, submitAppeal } from '../api'; +import type { AppealReason, AppealType, EvaluationAppealForm } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useAppealForm() { + const fetcher = useCallback(() => fetchAppealForm(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + /** 本地表单状态 */ + const [type, setType] = useState('overall'); + const [dimensions, setDimensions] = useState([]); + const [reason, setReason] = useState(null); + const [reasonText, setReasonText] = useState(''); + const [contactPhone, setContactPhone] = useState(''); + const [contactEmail, setContactEmail] = useState(''); + const [agree, setAgree] = useState(false); + const [submitting, setSubmitting] = useState(false); + + useEffect(() => { + if (data && dimensions.length === 0) { + setType(data.type); + setReason(data.reason); + } + }, [data, dimensions.length]); + + const toggleDimension = useCallback((id: string) => { + setDimensions((prev) => (prev.includes(id) ? prev.filter((d) => d !== id) : [...prev, id])); + }, []); + + const submit = useCallback(async () => { + if (!data) return null; + setSubmitting(true); + try { + const payload: EvaluationAppealForm = { + ...data, + type, + dimensions: data.dimensions.map((d) => ({ + ...d, + selected: dimensions.includes(d.id), + })), + reason: reason ?? data.reason, + reasonText, + contactPhone, + contactEmail, + agree, + }; + return await submitAppeal(payload); + } finally { + setSubmitting(false); + } + }, [data, type, dimensions, reason, reasonText, contactPhone, contactEmail, agree]); + + return { + form: data ?? null, + loading, + error, + refresh, + type, + setType, + dimensions, + toggleDimension, + reason, + setReason, + reasonText, + setReasonText, + contactPhone, + setContactPhone, + contactEmail, + setContactEmail, + agree, + setAgree, + submit, + submitting, + }; +} diff --git a/src/hooks/useAuditReport.ts b/src/hooks/useAuditReport.ts new file mode 100644 index 0000000..2e15243 --- /dev/null +++ b/src/hooks/useAuditReport.ts @@ -0,0 +1,18 @@ +/** + * 数据审计报告 hook + */ +import { useCallback } from 'react'; +import { fetchAuditReport } from '../api'; +import { useAsyncData } from './useAsyncData'; + +export function useAuditReport() { + const fetcher = useCallback(() => fetchAuditReport(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + report: data, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useCandidateDetail.ts b/src/hooks/useCandidateDetail.ts new file mode 100644 index 0000000..9a308eb --- /dev/null +++ b/src/hooks/useCandidateDetail.ts @@ -0,0 +1,36 @@ +/** + * 候选人详情 hook:拉取候选人详情 + 邀请面试。 + */ +import { useCallback, useState } from 'react'; +import { fetchCandidateDetail, inviteCandidate } from '../api'; +import type { CandidateDetail } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useCandidateDetail(candidateId: string) { + const fetcher = useCallback(() => fetchCandidateDetail(candidateId), [candidateId]); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const [inviting, setInviting] = useState(false); + const [invited, setInvited] = useState(false); + + const invite = useCallback(async () => { + setInviting(true); + try { + await inviteCandidate(candidateId); + setInvited(true); + return true; + } finally { + setInviting(false); + } + }, [candidateId]); + + return { + candidate: data ?? null, + loading, + error, + refresh, + invite, + inviting, + invited, + }; +} diff --git a/src/hooks/useCompareData.ts b/src/hooks/useCompareData.ts new file mode 100644 index 0000000..2a05fcc --- /dev/null +++ b/src/hooks/useCompareData.ts @@ -0,0 +1,26 @@ +/** + * 岗位对比数据 hook + */ +import { useCallback } from 'react'; +import { fetchCompareData } from '../api'; +import type { CompareData } from '../types'; +import { useAsyncData } from './useAsyncData'; + +/** 默认对比岗位 id(未显式传入时使用,保持引用稳定避免重复请求) */ +const DEFAULT_COMPARE_JOB_IDS = ['1', '2', '3']; + +export function useCompareData(jobIds: string[] = DEFAULT_COMPARE_JOB_IDS) { + const fetcher = useCallback(() => fetchCompareData(jobIds), [jobIds]); + + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + jobs: data?.jobs ?? [], + dimensionScores: data?.dimensionScores ?? [], + basicInfo: data?.basicInfo ?? { fields: [], values: [] }, + benefits: data?.benefits ?? [], + loading, + error, + refresh, + }; +} \ No newline at end of file diff --git a/src/hooks/useConversation.ts b/src/hooks/useConversation.ts new file mode 100644 index 0000000..3d382ae --- /dev/null +++ b/src/hooks/useConversation.ts @@ -0,0 +1,59 @@ +/** + * HR 单聊会话详情 hook:拉取会话详情,并提供发送消息能力。 + * 发送消息后自动刷新会话,把新消息追加到本地列表。 + */ +import { useCallback, useState } from 'react'; +import { fetchConversation, sendChatMessage } from '../api'; +import type { ConversationDetail } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface ConversationState { + /** 会话详情 */ + detail: ConversationDetail | null; + /** 是否加载中 */ + loading: boolean; + /** 错误对象 */ + error: Error | null; + /** 刷新数据 */ + refresh: () => void; + /** 发送消息 */ + sendMessage: (content: string) => Promise; + /** 发送中状态 */ + sending: boolean; +} + +export function useConversation(conversationId: string): ConversationState { + const [sending, setSending] = useState(false); + + const fetcher = useCallback(() => fetchConversation(conversationId), [conversationId]); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const sendMessage = useCallback( + async (content: string) => { + setSending(true); + try { + const newMessages = await sendChatMessage(conversationId, content); + // mock 阶段:直接把新消息合并到本地数据,避免重新拉取整屏 + const current = data as ConversationDetail | null; + if (current) { + current.messages.push(...newMessages); + refresh(); + } else { + refresh(); + } + } finally { + setSending(false); + } + }, + [conversationId, data, refresh], + ); + + return { + detail: (data as ConversationDetail | null) ?? null, + loading, + error, + refresh, + sendMessage, + sending, + }; +} diff --git a/src/hooks/useEnterpriseCertification.ts b/src/hooks/useEnterpriseCertification.ts new file mode 100644 index 0000000..e13b31b --- /dev/null +++ b/src/hooks/useEnterpriseCertification.ts @@ -0,0 +1,35 @@ +/** + * 企业认证 hook + */ +import { useCallback, useState } from 'react'; +import { fetchEnterpriseCertification, submitEnterpriseCertification } from '../api'; +import type { EnterpriseCertificationForm, EnterpriseCertificationStatus } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useEnterpriseCertification() { + const fetcher = useCallback(() => fetchEnterpriseCertification(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + const [submitting, setSubmitting] = useState(false); + const [status, setStatus] = useState(null); + + const submit = useCallback(async (form: EnterpriseCertificationForm) => { + setSubmitting(true); + try { + const res = await submitEnterpriseCertification(form); + setStatus(res.status); + return res; + } finally { + setSubmitting(false); + } + }, []); + + return { + certification: data, + status, + loading, + error, + submitting, + refresh, + submit, + }; +} diff --git a/src/hooks/useEnterpriseCommunication.ts b/src/hooks/useEnterpriseCommunication.ts new file mode 100644 index 0000000..8eeaa66 --- /dev/null +++ b/src/hooks/useEnterpriseCommunication.ts @@ -0,0 +1,19 @@ +/** + * 企业端沟通数据 hook。 + */ +import { useCallback } from 'react'; +import { fetchEnterpriseCommunication } from '../api'; +import type { EnterpriseCommunication } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useEnterpriseCommunication() { + const fetcher = useCallback(async (): Promise => fetchEnterpriseCommunication(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + communication: data ?? null, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useEnterpriseOverview.ts b/src/hooks/useEnterpriseOverview.ts new file mode 100644 index 0000000..cdb6794 --- /dev/null +++ b/src/hooks/useEnterpriseOverview.ts @@ -0,0 +1,19 @@ +/** + * 企业数据总览 hook:拉取企业总览数据。 + */ +import { useCallback } from 'react'; +import { fetchEnterpriseOverview } from '../api'; +import type { EnterpriseOverview } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useEnterpriseOverview() { + const fetcher = useCallback(() => fetchEnterpriseOverview(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + overview: data ?? null, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useGameData.ts b/src/hooks/useGameData.ts new file mode 100644 index 0000000..b4b9fbb --- /dev/null +++ b/src/hooks/useGameData.ts @@ -0,0 +1,84 @@ +/** + * 通关小游戏 hook:获取关卡配置与进度,并提供提交答案、重置进度能力。 + */ +import { useCallback, useState } from 'react'; +import { fetchGameLevels, fetchGameProgress, resetGameProgress, submitGameLevel } from '../api'; +import type { GameLevel, GameProgress, SubmitGameLevelPayload } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface GameDataState { + /** 关卡配置列表 */ + levels: GameLevel[]; + /** 当前游戏进度 */ + progress: GameProgress | null; + /** 是否加载中 */ + loading: boolean; + /** 错误对象 */ + error: Error | null; + /** 刷新数据 */ + refresh: () => void; + /** 提交关卡答案 */ + submitLevel: (payload: SubmitGameLevelPayload) => Promise; + /** 提交中状态 */ + submitting: boolean; + /** 重置进度 */ + reset: () => Promise; +} + +export function useGameData(): GameDataState { + const [submitting, setSubmitting] = useState(false); + + const levelsFetcher = useCallback(() => fetchGameLevels(), []); + const progressFetcher = useCallback(() => fetchGameProgress(), []); + + const { + data: levelsData, + loading: levelsLoading, + error: levelsError, + refresh: refreshLevels, + } = useAsyncData(levelsFetcher); + + const { + data: progressData, + loading: progressLoading, + error: progressError, + refresh: refreshProgress, + } = useAsyncData(progressFetcher); + + const refresh = useCallback(() => { + refreshLevels(); + refreshProgress(); + }, [refreshLevels, refreshProgress]); + + const submitLevel = useCallback( + async (payload: SubmitGameLevelPayload): Promise => { + setSubmitting(true); + try { + const next = await submitGameLevel(payload); + refreshProgress(); + return next; + } catch (err) { + return null; + } finally { + setSubmitting(false); + } + }, + [refreshProgress], + ); + + const reset = useCallback(async () => { + await resetGameProgress(); + refreshProgress(); + }, [refreshProgress]); + + return { + levels: (levelsData as GameLevel[] | null) ?? [], + progress: (progressData as GameProgress | null) ?? null, + loading: levelsLoading || progressLoading, + error: levelsError ?? progressError, + refresh, + submitLevel, + submitting, + reset, + }; +} diff --git a/src/hooks/useHREvaluation.ts b/src/hooks/useHREvaluation.ts new file mode 100644 index 0000000..5eca08e --- /dev/null +++ b/src/hooks/useHREvaluation.ts @@ -0,0 +1,19 @@ +/** + * HR 评价详情 hook:拉取评价详情。 + */ +import { useCallback } from 'react'; +import { fetchHREvaluation } from '../api'; +import type { HREvaluation } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useHREvaluation() { + const fetcher = useCallback(() => fetchHREvaluation(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + evaluation: data ?? null, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useHollandAssessment.ts b/src/hooks/useHollandAssessment.ts new file mode 100644 index 0000000..331ac6d --- /dev/null +++ b/src/hooks/useHollandAssessment.ts @@ -0,0 +1,38 @@ +/** + * 霍兰德职业兴趣测评 hook:拉取测评进度与结果,支持提交答案。 + */ +import { useCallback, useState } from 'react'; +import { fetchHollandAssessment, fetchHollandResult, submitHollandAnswer } from '../api'; +import { useAsyncData } from './useAsyncData'; + +export function useHollandAssessment() { + const fetcher = useCallback(() => fetchHollandAssessment(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + const [submitting, setSubmitting] = useState(false); + + const submit = useCallback( + async (questionId: string, value: number) => { + setSubmitting(true); + try { + const next = await submitHollandAnswer(questionId, value); + refresh(); + return next; + } finally { + setSubmitting(false); + } + }, + [refresh], + ); + + const loadResult = useCallback(async () => fetchHollandResult(), []); + + return { + assessment: data ?? null, + loading, + error, + refresh, + submit, + submitting, + loadResult, + }; +} diff --git a/src/hooks/useHollandIntro.ts b/src/hooks/useHollandIntro.ts new file mode 100644 index 0000000..93c1424 --- /dev/null +++ b/src/hooks/useHollandIntro.ts @@ -0,0 +1,19 @@ +/** + * 霍兰德职业兴趣测评介绍页数据 hook。 + */ +import { useCallback } from 'react'; +import { fetchHollandIntro } from '../api'; +import type { HollandIntro } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useHollandIntro() { + const fetcher = useCallback(async (): Promise => fetchHollandIntro(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + intro: data ?? null, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useIdentityOptions.ts b/src/hooks/useIdentityOptions.ts new file mode 100644 index 0000000..70b3780 --- /dev/null +++ b/src/hooks/useIdentityOptions.ts @@ -0,0 +1,20 @@ +/** + * 选择身份 hook:拉取身份选项(求职者 / 企业 HR),统一管理 loading / error / refresh。 + */ +import { useCallback } from 'react'; +import { fetchIdentityOptions } from '../api'; +import type { IdentityOption } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useIdentityOptions() { + const fetcher = useCallback(() => fetchIdentityOptions(), []); + + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + options: (data as IdentityOption[] | null) ?? [], + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useInterviewScore.ts b/src/hooks/useInterviewScore.ts new file mode 100644 index 0000000..9d2ca4a --- /dev/null +++ b/src/hooks/useInterviewScore.ts @@ -0,0 +1,73 @@ +/** + * 面试评分 hook:拉取评分数据 + 本地编辑 + 提交评分。 + */ +import { useCallback, useEffect, useState } from 'react'; +import { fetchInterviewScore, submitInterviewScore } from '../api'; +import type { HireSuggestion, InterviewScoreData } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useInterviewScore(interviewId: string) { + const fetcher = useCallback(() => fetchInterviewScore(interviewId), [interviewId]); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const [score, setScore] = useState(null); + const [submitting, setSubmitting] = useState(false); + + useEffect(() => { + if (data && !score) { + setScore(data); + } + }, [data, score]); + + const updateDimensionScore = useCallback((id: string, value: number) => { + setScore((prev) => { + if (!prev) return prev; + const dimensions = prev.dimensions.map((d) => (d.id === id ? { ...d, score: value } : d)); + const overallScore = Math.round(dimensions.reduce((sum, d) => sum + d.score, 0) / dimensions.length); + return { ...prev, dimensions, overallScore }; + }); + }, []); + + const toggleTag = useCallback((id: string) => { + setScore((prev) => { + if (!prev) return prev; + const tags = prev.tags.map((t) => (t.id === id ? { ...t, selected: !t.selected } : t)); + return { ...prev, tags }; + }); + }, []); + + const updateComment = useCallback((comment: string) => { + setScore((prev) => (prev ? { ...prev, comment } : prev)); + }, []); + + const selectHireSuggestion = useCallback((type: HireSuggestion['type']) => { + setScore((prev) => { + if (!prev) return prev; + const hireSuggestions = prev.hireSuggestions.map((s) => ({ ...s, selected: s.type === type })); + return { ...prev, hireSuggestions }; + }); + }, []); + + const submit = useCallback(async () => { + if (!score) return null; + setSubmitting(true); + try { + return await submitInterviewScore(interviewId, score); + } finally { + setSubmitting(false); + } + }, [interviewId, score]); + + return { + score, + loading, + error, + refresh, + updateDimensionScore, + toggleTag, + updateComment, + selectHireSuggestion, + submit, + submitting, + }; +} diff --git a/src/hooks/useJobEvaluationEmptyState.ts b/src/hooks/useJobEvaluationEmptyState.ts new file mode 100644 index 0000000..dcceb48 --- /dev/null +++ b/src/hooks/useJobEvaluationEmptyState.ts @@ -0,0 +1,19 @@ +/** + * 岗位评价管理空状态数据 hook。 + */ +import { useCallback } from 'react'; +import { fetchJobEvaluationEmptyState } from '../api'; +import type { JobEvaluationEmptyState } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useJobEvaluationEmptyState() { + const fetcher = useCallback(async (): Promise => fetchJobEvaluationEmptyState(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + state: data ?? null, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useJobEvaluations.ts b/src/hooks/useJobEvaluations.ts new file mode 100644 index 0000000..b914d9f --- /dev/null +++ b/src/hooks/useJobEvaluations.ts @@ -0,0 +1,32 @@ +/** + * 岗位评价管理 hook:拉取评价管理数据 + 回复评价。 + */ +import { useCallback, useState } from 'react'; +import { fetchJobEvaluationManagement, replyJobReview } from '../api'; +import type { JobEvaluationManagement } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useJobEvaluations() { + const fetcher = useCallback(() => fetchJobEvaluationManagement(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const [replyingId, setReplyingId] = useState(null); + + const reply = useCallback(async (reviewId: string, content: string) => { + setReplyingId(reviewId); + try { + await replyJobReview(reviewId, content); + } finally { + setReplyingId(null); + } + }, []); + + return { + management: data ?? null, + loading, + error, + refresh, + reply, + replyingId, + }; +} diff --git a/src/hooks/useJobManagement.ts b/src/hooks/useJobManagement.ts new file mode 100644 index 0000000..661623c --- /dev/null +++ b/src/hooks/useJobManagement.ts @@ -0,0 +1,19 @@ +/** + * 企业端岗位管理数据 hook。 + */ +import { useCallback } from 'react'; +import { fetchJobManagement } from '../api'; +import type { JobManagement } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useJobManagement() { + const fetcher = useCallback(async (): Promise => fetchJobManagement(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + management: data ?? null, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useJobProgress.ts b/src/hooks/useJobProgress.ts new file mode 100644 index 0000000..dcbcbbe --- /dev/null +++ b/src/hooks/useJobProgress.ts @@ -0,0 +1,20 @@ +/** + * 求职进度 hook:拉取求职进度时间轴,统一管理 loading / error / refresh。 + */ +import { useCallback } from 'react'; +import { fetchJobProgress } from '../api'; +import type { JobProgress } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useJobProgress() { + const fetcher = useCallback(() => fetchJobProgress(), []); + + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + progress: (data as JobProgress | null) ?? null, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useJobPublish.ts b/src/hooks/useJobPublish.ts new file mode 100644 index 0000000..f458a3f --- /dev/null +++ b/src/hooks/useJobPublish.ts @@ -0,0 +1,96 @@ +/** + * 发布岗位 hook:拉取表单、更新字段、预估质量、发布岗位。 + */ +import { useCallback, useEffect, useState } from 'react'; +import { estimateJobQuality, fetchJobPublishForm, publishJob } from '../api'; +import type { JobPublishFormData, JobQualityEstimate } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useJobPublish() { + const fetcher = useCallback(() => fetchJobPublishForm(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const [form, setForm] = useState(null); + const [estimate, setEstimate] = useState(null); + const [estimating, setEstimating] = useState(false); + const [publishing, setPublishing] = useState(false); + + useEffect(() => { + if (data && !form) { + setForm(data); + } + }, [data, form]); + + const updateField = useCallback(( + field: K, + value: JobPublishFormData[K], + ) => { + setForm((prev) => (prev ? { ...prev, [field]: value } : prev)); + }, []); + + const updateStringField = useCallback((field: keyof JobPublishFormData, value: string) => { + setForm((prev) => (prev ? { ...prev, [field]: value } : prev)); + }, []); + + const updateNumberField = useCallback((field: keyof JobPublishFormData, value: string) => { + setForm((prev) => (prev ? { ...prev, [field]: Number(value) || 0 } : prev)); + }, []); + + const toggleBenefit = useCallback((benefit: string) => { + setForm((prev) => { + if (!prev) return prev; + const benefits = prev.benefits.includes(benefit) + ? prev.benefits.filter((b) => b !== benefit) + : [...prev.benefits, benefit]; + return { ...prev, benefits }; + }); + }, []); + + const toggleSkill = useCallback((skill: string) => { + setForm((prev) => { + if (!prev) return prev; + const skills = prev.skills.includes(skill) + ? prev.skills.filter((s) => s !== skill) + : [...prev.skills, skill]; + return { ...prev, skills }; + }); + }, []); + + const runEstimate = useCallback(async () => { + if (!form) return; + setEstimating(true); + try { + const result = await estimateJobQuality(form); + setEstimate(result); + } finally { + setEstimating(false); + } + }, [form]); + + const publish = useCallback(async () => { + if (!form) return null; + setPublishing(true); + try { + return await publishJob(form); + } finally { + setPublishing(false); + } + }, [form]); + + return { + form, + loading, + error, + refresh, + updateField, + updateStringField, + updateNumberField, + toggleBenefit, + toggleSkill, + estimate, + estimating, + runEstimate, + publishing, + publish, + }; +} diff --git a/src/hooks/useLegalData.ts b/src/hooks/useLegalData.ts new file mode 100644 index 0000000..5619fce --- /dev/null +++ b/src/hooks/useLegalData.ts @@ -0,0 +1,111 @@ +/** + * 阿布阿诺 AI 法律服务助手 hook:聚合获取品牌、服务、FAQ、模板、会员套餐, + * 并提供法律咨询对话消息发送能力。 + */ +import { useCallback } from 'react'; +import { + fetchLegalBrand, + fetchLegalConversation, + fetchLegalFAQs, + fetchLegalPlans, + fetchLegalServices, + fetchLegalTemplates, + sendLegalMessage, +} from '../api'; +import type { + LegalBrand, + LegalConversation, + LegalFAQ, + LegalPlan, + LegalService, + LegalTemplate, +} from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface LegalDataState { + /** 阿布阿诺品牌信息 */ + brand: LegalBrand | null; + /** 法律服务入口列表 */ + services: LegalService[]; + /** 常见问题 */ + faqs: LegalFAQ[]; + /** 文书模板 */ + templates: LegalTemplate[]; + /** 会员套餐 */ + plans: LegalPlan[]; + /** 当前咨询会话 */ + conversation: LegalConversation | null; + /** 是否加载中 */ + loading: boolean; + /** 错误对象 */ + error: Error | null; + /** 刷新全部数据 */ + refresh: () => void; + /** 发送咨询消息 */ + sendMessage: (content: string) => Promise; +} + +interface LegalStaticData { + brand: LegalBrand; + services: LegalService[]; + faqs: LegalFAQ[]; + templates: LegalTemplate[]; + plans: LegalPlan[]; +} + +export function useLegalData(): LegalDataState { + const staticFetcher = useCallback(async (): Promise => { + const [brand, services, faqs, templates, plans] = await Promise.all([ + fetchLegalBrand(), + fetchLegalServices(), + fetchLegalFAQs(), + fetchLegalTemplates(), + fetchLegalPlans(), + ]); + return { brand, services, faqs, templates, plans }; + }, []); + + const conversationFetcher = useCallback(() => fetchLegalConversation(), []); + + const { + data: staticData, + loading: staticLoading, + error: staticError, + refresh: refreshStatic, + } = useAsyncData(staticFetcher); + + const { + data: conversationData, + loading: conversationLoading, + error: conversationError, + refresh: refreshConversation, + } = useAsyncData(conversationFetcher); + + const refresh = useCallback(() => { + refreshStatic(); + refreshConversation(); + }, [refreshStatic, refreshConversation]); + + const sendMessage = useCallback( + async (content: string) => { + await sendLegalMessage(content); + refreshConversation(); + }, + [refreshConversation], + ); + + const resolved = (staticData as LegalStaticData | null) ?? null; + + return { + brand: resolved?.brand ?? null, + services: resolved?.services ?? [], + faqs: resolved?.faqs ?? [], + templates: resolved?.templates ?? [], + plans: resolved?.plans ?? [], + conversation: (conversationData as LegalConversation | null) ?? null, + loading: staticLoading || conversationLoading, + error: staticError ?? conversationError, + refresh, + sendMessage, + }; +} diff --git a/src/hooks/useNotificationSettings.ts b/src/hooks/useNotificationSettings.ts new file mode 100644 index 0000000..18a31a8 --- /dev/null +++ b/src/hooks/useNotificationSettings.ts @@ -0,0 +1,9 @@ +/** + * 消息通知设置 hook:拉取通知开关列表并提供切换能力。 + */ +import { fetchNotificationSettings, updateNotificationSetting } from '../api'; +import { useToggleSettings, type ToggleSettingsState } from './useToggleSettings'; + +export function useNotificationSettings(): ToggleSettingsState { + return useToggleSettings(fetchNotificationSettings, updateNotificationSetting); +} diff --git a/src/hooks/useOfferCompareData.ts b/src/hooks/useOfferCompareData.ts new file mode 100644 index 0000000..58cf6d9 --- /dev/null +++ b/src/hooks/useOfferCompareData.ts @@ -0,0 +1,26 @@ +/** + * Offer 对比数据 hook + */ +import { useCallback } from 'react'; +import { fetchOfferCompareData } from '../api'; +import type { OfferCompareData } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useOfferCompareData(offerIds?: string[]) { + const fetcher = useCallback(() => fetchOfferCompareData(offerIds), [offerIds?.join(',')]); + + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + data, + offers: data?.offers ?? [], + scores: data?.scores ?? [], + benefitGroups: data?.benefitGroups ?? [], + workLifeBalance: data?.workLifeBalance ?? [], + aiSuggestion: data?.aiSuggestion, + actions: data?.actions, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useOnboardingChecklist.ts b/src/hooks/useOnboardingChecklist.ts new file mode 100644 index 0000000..5861ab6 --- /dev/null +++ b/src/hooks/useOnboardingChecklist.ts @@ -0,0 +1,34 @@ +/** + * 入职准备清单 hook:拉取清单 + 切换待办完成状态。 + */ +import { useCallback, useState } from 'react'; +import { fetchOnboardingChecklist, toggleChecklistItem } from '../api'; +import type { OnboardingChecklist } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useOnboardingChecklist() { + const fetcher = useCallback(() => fetchOnboardingChecklist(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + const [togglingId, setTogglingId] = useState(null); + + const toggle = useCallback( + async (itemId: string, done: boolean) => { + setTogglingId(itemId); + try { + return await toggleChecklistItem(itemId, done); + } finally { + setTogglingId(null); + } + }, + [], + ); + + return { + checklist: data ?? null, + loading, + error, + refresh, + toggle, + togglingId, + }; +} diff --git a/src/hooks/useOperationRules.ts b/src/hooks/useOperationRules.ts new file mode 100644 index 0000000..8f5dc6d --- /dev/null +++ b/src/hooks/useOperationRules.ts @@ -0,0 +1,33 @@ +/** + * 运营规则配置 hook + */ +import { useCallback, useState } from 'react'; +import { fetchOperationRules, saveOperationRules } from '../api'; +import type { OperationRules } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useOperationRules() { + const fetcher = useCallback(() => fetchOperationRules(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + const [saving, setSaving] = useState(false); + + const updateRules = useCallback(async (updater: (prev: OperationRules) => OperationRules) => { + if (!data) return; + const next = updater(data); + setSaving(true); + try { + await saveOperationRules(next); + } finally { + setSaving(false); + } + }, [data]); + + return { + rules: data, + loading, + error, + saving, + refresh, + updateRules, + }; +} diff --git a/src/hooks/usePersonalDataExport.ts b/src/hooks/usePersonalDataExport.ts new file mode 100644 index 0000000..e95fc95 --- /dev/null +++ b/src/hooks/usePersonalDataExport.ts @@ -0,0 +1,40 @@ +/** + * 个人数据导出 hook。 + */ +import { useCallback, useState } from 'react'; +import { fetchPersonalDataExport, requestPersonalDataExport } from '../api'; +import type { PersonalDataExport } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface PersonalDataExportState { + exportData: PersonalDataExport | null; + loading: boolean; + error: Error | null; + refresh: () => void; + submit: () => Promise<{ success: boolean; message: string }>; + submitting: boolean; +} + +export function usePersonalDataExport(): PersonalDataExportState { + const fetcher = useCallback(async (): Promise => fetchPersonalDataExport(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + const [submitting, setSubmitting] = useState(false); + + const submit = useCallback(async () => { + setSubmitting(true); + try { + return await requestPersonalDataExport(); + } finally { + setSubmitting(false); + } + }, []); + + return { + exportData: data ?? null, + loading, + error, + refresh, + submit, + submitting, + }; +} diff --git a/src/hooks/usePrivacyCenter.ts b/src/hooks/usePrivacyCenter.ts new file mode 100644 index 0000000..64380f7 --- /dev/null +++ b/src/hooks/usePrivacyCenter.ts @@ -0,0 +1,41 @@ +/** + * 隐私中心(截图版)hook。 + */ +import { useCallback } from 'react'; +import { fetchPrivacyCenter, updatePrivacyCenterSetting } from '../api'; +import type { PrivacyCenter, UpdateTogglePayload } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface PrivacyCenterState { + privacyCenter: PrivacyCenter | null; + loading: boolean; + error: Error | null; + refresh: () => void; + toggle: (id: string, enabled: boolean) => Promise; +} + +export function usePrivacyCenter(): PrivacyCenterState { + const fetcher = useCallback(async (): Promise => fetchPrivacyCenter(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const toggle = useCallback( + async (id: string, enabled: boolean) => { + const payload: UpdateTogglePayload = { id, enabled }; + try { + await updatePrivacyCenterSetting(payload); + refresh(); + } catch (err) { + console.error('[usePrivacyCenter] toggle error', err); + } + }, + [refresh], + ); + + return { + privacyCenter: data ?? null, + loading, + error, + refresh, + toggle, + }; +} diff --git a/src/hooks/usePrivacySettings.ts b/src/hooks/usePrivacySettings.ts new file mode 100644 index 0000000..e940ae3 --- /dev/null +++ b/src/hooks/usePrivacySettings.ts @@ -0,0 +1,9 @@ +/** + * 隐私设置 hook:拉取隐私开关列表并提供切换能力。 + */ +import { fetchPrivacySettings, updatePrivacySetting } from '../api'; +import { useToggleSettings, type ToggleSettingsState } from './useToggleSettings'; + +export function usePrivacySettings(): ToggleSettingsState { + return useToggleSettings(fetchPrivacySettings, updatePrivacySetting); +} diff --git a/src/hooks/useProfileData.ts b/src/hooks/useProfileData.ts index 781505e..f1fcfd6 100644 --- a/src/hooks/useProfileData.ts +++ b/src/hooks/useProfileData.ts @@ -1,53 +1,68 @@ /** - * 个人中心数据 hook:并行组合用户信息、综合评分、维度评分、求职偏好、勋章五类数据, + * 个人中心数据 hook:并行组合用户信息、综合评分、维度评分、求职偏好、勋章、菜单六类数据, * 统一管理 loading / error / refresh 三态。 */ import { useCallback } from 'react'; import { fetchBadges, fetchDimensionScores, + fetchEnterpriseEntry, fetchJobPreference, + fetchMenuItems, fetchOverallScore, fetchUserProfile, + fetchUserStats, } from '../api'; import type { Badge, DimensionScore, + EnterpriseEntry, JobPreference, + MenuItem, OverallScore, UserProfile, + UserStats, } from '../types'; import { useAsyncData } from './useAsyncData'; /** 个人中心聚合数据 */ interface ProfileData { profile: UserProfile; + userStats: UserStats; overallScore: OverallScore; dimensions: DimensionScore[]; preference: JobPreference; badges: Badge[]; + menuItems: MenuItem[]; + enterpriseEntry: EnterpriseEntry; } export function useProfileData() { const fetcher = useCallback(async (): Promise => { - const [profile, overallScore, dimensions, preference, badges] = await Promise.all([ + const [profile, userStats, overallScore, dimensions, preference, badges, menuItems, enterpriseEntry] = await Promise.all([ fetchUserProfile(), + fetchUserStats(), fetchOverallScore(), fetchDimensionScores(), fetchJobPreference(), fetchBadges(), + fetchMenuItems(), + fetchEnterpriseEntry(), ]); - return { profile, overallScore, dimensions, preference, badges }; + return { profile, userStats, overallScore, dimensions, preference, badges, menuItems, enterpriseEntry }; }, []); const { data, loading, error, refresh } = useAsyncData(fetcher); return { profile: data?.profile ?? null, + userStats: data?.userStats ?? null, overallScore: data?.overallScore ?? null, dimensions: data?.dimensions ?? [], preference: data?.preference ?? null, badges: data?.badges ?? [], + menuItems: data?.menuItems ?? [], + enterpriseEntry: data?.enterpriseEntry ?? null, loading, error, refresh, diff --git a/src/hooks/useRankData.ts b/src/hooks/useRankData.ts new file mode 100644 index 0000000..ace7329 --- /dev/null +++ b/src/hooks/useRankData.ts @@ -0,0 +1,75 @@ +/** + * 同岗位竞争力排名数据 hook:加载综合排名 + 六维度百分位 + TOP10 候选人 + 提升建议。 + * 支持数据态与空状态。 + * + * 数据链路:页面 → useRankData → API 层 + */ +import { useCallback, useEffect, useState } from 'react'; +import { fetchRankData } from '../api'; +import type { + DimensionPercentile, + ImprovementTip, + RankData, + RankEmptyState, + TopCandidate, +} from '../types'; + +export function useRankData(jobId: string) { + const [rankData, setRankData] = useState(null); + const [dimensions, setDimensions] = useState([]); + const [candidates, setCandidates] = useState([]); + const [tips, setTips] = useState([]); + const [emptyState, setEmptyState] = useState(null); + const [hasData, setHasData] = useState(false); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchData = useCallback(async () => { + if (!jobId) { + return; + } + setLoading(true); + setError(null); + try { + const data = await fetchRankData(jobId); + setHasData(data.hasData); + if (data.hasData) { + setRankData(data.rankData ?? null); + setDimensions(data.dimensions ?? []); + setCandidates(data.candidates ?? []); + setTips(data.tips ?? []); + setEmptyState(null); + } else { + setRankData(null); + setDimensions([]); + setCandidates([]); + setTips([]); + setEmptyState(data.emptyState ?? null); + } + } catch (err) { + setError(err instanceof Error ? err : new Error('加载失败')); + } finally { + setLoading(false); + } + }, [jobId]); + + const refresh = useCallback(() => { + void fetchData(); + }, [fetchData]); + + useEffect(() => { + void fetchData(); + }, [fetchData]); + + return { + rankData, + dimensions, + candidates, + tips, + emptyState, + hasData, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useResume.ts b/src/hooks/useResume.ts new file mode 100644 index 0000000..624d1a3 --- /dev/null +++ b/src/hooks/useResume.ts @@ -0,0 +1,25 @@ +/** + * 简历数据 hook:拉取简历 + AI 一键优化。 + */ +import { useCallback } from 'react'; +import { fetchResume, optimizeResume } from '../api'; +import type { Resume } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useResume() { + const fetcher = useCallback(() => fetchResume(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const optimize = useCallback(async () => { + const result = await optimizeResume(); + return result.resume; + }, []); + + return { + resume: data ?? null, + loading, + error, + refresh, + optimize, + }; +} diff --git a/src/hooks/useResumeExport.ts b/src/hooks/useResumeExport.ts new file mode 100644 index 0000000..a31c46f --- /dev/null +++ b/src/hooks/useResumeExport.ts @@ -0,0 +1,48 @@ +/** + * 简历导出 hook:拉取/保存导出设置,执行导出。 + */ +import { useCallback, useState } from 'react'; +import { + exportResume, + fetchResumeExportSettings, + saveResumeExportSettings, +} from '../api'; +import type { ResumeExportSettings } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useResumeExport() { + const fetcher = useCallback(() => fetchResumeExportSettings(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + const [exporting, setExporting] = useState(false); + + const save = useCallback( + async (settings: ResumeExportSettings) => { + await saveResumeExportSettings(settings); + await refresh(); + }, + [refresh], + ); + + const exportFile = useCallback( + async (settings: ResumeExportSettings) => { + setExporting(true); + try { + const result = await exportResume(settings); + return result.url; + } finally { + setExporting(false); + } + }, + [], + ); + + return { + settings: data ?? null, + loading, + error, + refresh, + save, + exportFile, + exporting, + }; +} diff --git a/src/hooks/useSalaryBrackets.ts b/src/hooks/useSalaryBrackets.ts new file mode 100644 index 0000000..f6d5f4e --- /dev/null +++ b/src/hooks/useSalaryBrackets.ts @@ -0,0 +1,21 @@ +/** + * 行业薪资参考 hook:拉取各行业平均月薪,用于薪资计算器对比。 + * 统一管理 loading / error / refresh。 + */ +import { useCallback } from 'react'; +import { fetchSalaryBrackets } from '../api'; +import type { SalaryBracket } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useSalaryBrackets() { + const fetcher = useCallback(() => fetchSalaryBrackets(), []); + + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + brackets: (data as SalaryBracket[] | null) ?? [], + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useSalaryCalculator.ts b/src/hooks/useSalaryCalculator.ts new file mode 100644 index 0000000..0ab611e --- /dev/null +++ b/src/hooks/useSalaryCalculator.ts @@ -0,0 +1,24 @@ +/** + * 薪资计算器 hook:本地维护输入(月薪 / 城市 / 社保基数 / 公积金比例 / 专项扣除 / 年终奖), + * 通过接口层纯函数 computeSalaryResult 实时派生结果(业务规则在 api 层)。 + * 计算为同步派生,避免 mock 延迟影响实时输入体验。 + */ +import { useMemo, useState } from 'react'; +import { computeSalaryResult } from '../api'; +import type { SalaryCalcInput, SalaryCalcResult } from '../types'; + +export function useSalaryCalculator(initial?: Partial) { + const [inputs, setInputs] = useState({ + monthlyGross: 30000, + city: '北京', + socialBase: 25000, + providentFundRate: 0.12, + specialDeduction: 2000, + bonusMonths: 0, + ...initial, + }); + + const result: SalaryCalcResult = useMemo(() => computeSalaryResult(inputs), [inputs]); + + return { inputs, setInputs, result }; +} diff --git a/src/hooks/useSkillCloud.ts b/src/hooks/useSkillCloud.ts new file mode 100644 index 0000000..33b9264 --- /dev/null +++ b/src/hooks/useSkillCloud.ts @@ -0,0 +1,24 @@ +/** + * 行业技能词云 hook:按行业拉取技能词数据,统一管理 loading / error / refresh。 + * 切换行业时自动重新取数(fetcher 依赖 industry)。 + */ +import { useCallback, useState } from 'react'; +import { fetchSkillCloud } from '../api'; +import type { SkillCloudData } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useSkillCloud(initialIndustry = '互联网/IT') { + const [industry, setIndustry] = useState(initialIndustry); + const fetcher = useCallback(() => fetchSkillCloud(industry), [industry]); + + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + industry, + setIndustry, + cloud: (data as SkillCloudData | null) ?? null, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useTalentRecommendation.ts b/src/hooks/useTalentRecommendation.ts new file mode 100644 index 0000000..c7711c8 --- /dev/null +++ b/src/hooks/useTalentRecommendation.ts @@ -0,0 +1,19 @@ +/** + * 企业端人才推荐数据 hook。 + */ +import { useCallback } from 'react'; +import { fetchTalentRecommendation } from '../api'; +import type { TalentRecommendation } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export function useTalentRecommendation() { + const fetcher = useCallback(async (): Promise => fetchTalentRecommendation(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + return { + recommendation: data ?? null, + loading, + error, + refresh, + }; +} diff --git a/src/hooks/useToggleSettings.ts b/src/hooks/useToggleSettings.ts new file mode 100644 index 0000000..7c3ecbf --- /dev/null +++ b/src/hooks/useToggleSettings.ts @@ -0,0 +1,54 @@ +/** + * 通用开关型设置 hook:拉取开关列表并提供切换能力。 + * 供 usePrivacySettings、useNotificationSettings 复用,避免重复实现三态与提交逻辑。 + */ +import { useCallback, useState } from 'react'; +import type { ToggleSetting, UpdateTogglePayload } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface ToggleSettingsState { + /** 开关列表 */ + settings: ToggleSetting[]; + /** 是否加载中 */ + loading: boolean; + /** 错误对象 */ + error: Error | null; + /** 刷新数据 */ + refresh: () => void; + /** 切换某个开关 */ + toggle: (id: string, enabled: boolean) => Promise; + /** 正在更新的开关 id(用于禁用交互,避免连点) */ + updatingId: string | null; +} + +export function useToggleSettings( + fetchFn: () => Promise, + updateFn: (payload: UpdateTogglePayload) => Promise, +): ToggleSettingsState { + const [updatingId, setUpdatingId] = useState(null); + + const fetcher = useCallback(() => fetchFn(), [fetchFn]); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const toggle = useCallback( + async (id: string, enabled: boolean) => { + setUpdatingId(id); + try { + await updateFn({ id, enabled }); + refresh(); + } finally { + setUpdatingId(null); + } + }, + [updateFn, refresh], + ); + + return { + settings: (data as ToggleSetting[] | null) ?? [], + loading, + error, + refresh, + toggle, + updatingId, + }; +} diff --git a/src/hooks/useWeightSettings.ts b/src/hooks/useWeightSettings.ts new file mode 100644 index 0000000..efd45ed --- /dev/null +++ b/src/hooks/useWeightSettings.ts @@ -0,0 +1,91 @@ +/** + * 评分权重设置 hook:拉取维度与预设模板,提供权重调整、模板应用与保存能力。 + * 业务规则(权重总和校验)放在 api 层,本层只负责本地编辑态与远端数据的合并。 + */ +import { useCallback, useMemo, useState } from 'react'; +import { fetchWeightSettings, saveWeightSettings } from '../api'; +import type { WeightDimension, WeightSettings } from '../types'; +import { useAsyncData } from './useAsyncData'; + +export interface WeightSettingsState { + /** 远端原始设置(含预设模板) */ + settings: WeightSettings | null; + /** 当前生效的维度列表(本地编辑态优先,未编辑时回退远端) */ + dimensions: WeightDimension[]; + /** 各维度权重总和,正常应为 100 */ + totalWeight: number; + loading: boolean; + error: Error | null; + saving: boolean; + refresh: () => void; + /** 调整单个维度权重 */ + updateWeight: (id: string, weight: number) => void; + /** 应用预设模板(按维度名称匹配权重) */ + applyTemplate: (templateId: string) => void; + /** 保存当前维度列表 */ + save: () => Promise; +} + +export function useWeightSettings(): WeightSettingsState { + const fetcher = useCallback(() => fetchWeightSettings(), []); + const { data, loading, error, refresh } = useAsyncData(fetcher); + + const [localDimensions, setLocalDimensions] = useState(null); + const [saving, setSaving] = useState(false); + + const remoteDimensions = data?.dimensions ?? []; + const dimensions = localDimensions ?? remoteDimensions; + + const totalWeight = useMemo( + () => dimensions.reduce((sum, item) => sum + item.weight, 0), + [dimensions], + ); + + const updateWeight = useCallback( + (id: string, weight: number) => { + setLocalDimensions((prev) => { + const base = prev ?? remoteDimensions; + return base.map((item) => (item.id === id ? { ...item, weight } : item)); + }); + }, + [remoteDimensions], + ); + + const applyTemplate = useCallback( + (templateId: string) => { + const template = data?.templates.find((item) => item.id === templateId); + if (!template) return; + setLocalDimensions((prev) => { + const base = prev ?? remoteDimensions; + return base.map((item) => ({ + ...item, + weight: template.weights[item.name] ?? item.weight, + })); + }); + }, + [data, remoteDimensions], + ); + + const save = useCallback(async () => { + setSaving(true); + try { + const saved = await saveWeightSettings(dimensions); + setLocalDimensions(saved); + } finally { + setSaving(false); + } + }, [dimensions]); + + return { + settings: data ?? null, + dimensions, + totalWeight, + loading, + error, + saving, + refresh, + updateWeight, + applyTemplate, + save, + }; +} diff --git a/src/types/compare.ts b/src/types/compare.ts new file mode 100644 index 0000000..07b8b4d --- /dev/null +++ b/src/types/compare.ts @@ -0,0 +1,41 @@ +/** + * 岗位对比相关类型定义 + */ + +/** 岗位对比 - 单个岗位 */ +export interface CompareJob { + id: string; + /** 岗位名称,如 "高级前端工程师" */ + title: string; + /** 公司名,如 "字节跳动" */ + company: string; + /** 薪资范围,如 "25-40K" */ + salary: string; + /** 雷达图系列颜色(从 theme 导入) */ + color: string; +} + +/** 岗位对比 - 单个维度评分(一行:维度名 + 各岗位评分) */ +export interface CompareDimensionScore { + /** 维度名称,如 "综合评分" */ + dimension: string; + /** 各岗位评分,[92, 85, 78] 与 jobs 顺序一一对应 */ + scores: number[]; +} + +/** 岗位对比 - 完整数据 */ +export interface CompareData { + /** 参与对比的岗位列表(2-3 个) */ + jobs: CompareJob[]; + /** 九大维度评分 */ + dimensionScores: CompareDimensionScore[]; + /** 基本信息(字段名 + 各岗位对应值) */ + basicInfo: { + /** 字段名,如 ["公司规模", "所在行业", ...] */ + fields: string[]; + /** 各字段对应各岗位的值,如 [["10000人以上", ...], ...] */ + values: string[][]; + }; + /** 各岗位福利标签,如 [["五险一金", ...], ...] */ + benefits: string[][]; +} \ No newline at end of file diff --git a/src/types/data-export.ts b/src/types/data-export.ts new file mode 100644 index 0000000..bbaba0c --- /dev/null +++ b/src/types/data-export.ts @@ -0,0 +1,101 @@ +/** + * 个人数据导出领域模型 + * + * 包含: + * - 可导出数据分类与勾选项 + * - 导出格式(JSON / Excel) + * - 时间范围 + * - 导出预览(大小 / 数量 / 处理时间) + * - 最近导出记录 + */ + +/** 单个可导出数据项 */ +export interface DataExportItem { + /** 项 id */ + id: string; + /** 展示文案 */ + label: string; + /** 预估大小 */ + size: string; + /** 是否默认选中 */ + checked: boolean; +} + +/** 数据分类(如基本信息 / 简历数据 / 求职记录) */ +export interface DataExportCategory { + /** 分类 id */ + id: string; + /** 分类名称 */ + label: string; + /** 分类描述 */ + description: string; + /** 分类总大小 */ + size: string; + /** 分类下数据项 */ + items: DataExportItem[]; +} + +/** 导出格式选项 */ +export interface DataExportFormat { + /** 格式 id */ + id: string; + /** 展示名称 */ + label: string; + /** 图标名(Ionicons) */ + icon: string; + /** 格式说明 */ + description: string; +} + +/** 导出时间范围选项 */ +export interface DataExportTimeRange { + /** 选项 id */ + id: string; + /** 展示文案 */ + label: string; +} + +/** 最近导出记录 */ +export interface RecentExportRecord { + /** 记录 id */ + id: string; + /** 导出文件名 */ + name: string; + /** 导出日期 */ + date: string; + /** 文件大小 */ + size: string; + /** 状态:已完成 / 已过期 */ + status: 'completed' | 'expired'; +} + +/** 个人数据导出聚合数据 */ +export interface PersonalDataExport { + /** 页面标题 */ + title: string; + /** 功能说明 */ + description: string; + /** 可导出数据分类 */ + categories: DataExportCategory[]; + /** 导出格式选项 */ + formats: DataExportFormat[]; + /** 时间范围选项 */ + timeRanges: DataExportTimeRange[]; + /** 当前选中的导出格式 id */ + selectedFormat: string; + /** 当前选中的时间范围 id */ + selectedTimeRange: string; + /** 导出预览 */ + preview: { + /** 已选分类数 */ + categoryCount: number; + /** 预估总大小 */ + totalSize: string; + /** 预计处理时间 */ + estimatedTime: string; + /** 有效期说明 */ + validity: string; + }; + /** 最近导出记录 */ + recentExports: RecentExportRecord[]; +} diff --git a/src/types/enterprise.ts b/src/types/enterprise.ts new file mode 100644 index 0000000..d8f4d2d --- /dev/null +++ b/src/types/enterprise.ts @@ -0,0 +1,637 @@ +/** + * 企业端 / HR 端领域模型 + * + * 包含: + * - 企业数据总览(公司主页) + * - 发布岗位 + * - 岗位评价管理 + * - 候选人详情 + * - 面试评分 + */ + +/* ===================== 企业数据总览 ===================== */ + +/** 企业核心统计项 */ +export interface EnterpriseStat { + id: string; + /** 统计名称 */ + label: string; + /** 数值 */ + value: number; + /** 较上期变化(可为负) */ + change: number; + /** Ionicons 图标名 */ + icon: string; + /** 颜色 token */ + color: string; +} + +/** 岗位评价摘要(企业总览展示) */ +export interface JobEvaluationSummary { + id: string; + /** 岗位名称 */ + positionName: string; + /** 部门 */ + department: string; + /** 综合评分 */ + score: number; + /** 评价数 */ + reviewCount: number; + /** 趋势变化 */ + change: number; +} + +/** 企业数据总览 */ +export interface EnterpriseOverview { + /** 企业 id */ + id: string; + /** 企业名称 */ + name: string; + /** 企业 Logo 字符 */ + logo: string; + /** 所属行业 */ + industry: string; + /** 企业规模 */ + scale: string; + /** 企业阶段 */ + stage: string; + /** 综合评分 */ + overallScore: number; + /** 评分人数 */ + reviewCount: number; + /** 统计项 */ + stats: EnterpriseStat[]; + /** 岗位评价摘要列表 */ + jobEvaluations: JobEvaluationSummary[]; + /** 快捷入口 */ + quickActions: EnterpriseQuickAction[]; +} + +/** 企业总览快捷入口 */ +export interface EnterpriseQuickAction { + id: string; + /** 名称 */ + label: string; + /** 图标 */ + icon: string; + /** 路由 */ + route: string; + /** 颜色 token */ + color: string; +} + +/* ===================== 发布岗位 ===================== */ + +/** 工作类型 */ +export type JobWorkType = 'full-time' | 'part-time' | 'intern' | 'remote'; + +/** 学历要求 */ +export type JobEducationRequirement = '不限' | '大专' | '本科' | '硕士' | '博士'; + +/** 经验要求 */ +export type JobExperienceRequirement = + | '应届生' + | '1年以内' + | '1-3年' + | '3-5年' + | '5-10年' + | '10年以上'; + +/** 发布岗位表单数据 */ +export interface JobPublishFormData { + /** 岗位名称 */ + positionName: string; + /** 所属部门 */ + department: string; + /** 招聘人数 */ + headcount: number; + /** 工作城市 */ + city: string; + /** 详细地址 */ + address: string; + /** 工作类型 */ + workType: JobWorkType; + /** 学历要求 */ + education: JobEducationRequirement; + /** 经验要求 */ + experience: JobExperienceRequirement; + /** 最低薪资(K) */ + salaryMin: number; + /** 最高薪资(K) */ + salaryMax: number; + /** 薪资月数 */ + salaryMonths: number; + /** 福利标签 */ + benefits: string[]; + /** 岗位职责 */ + responsibilities: string; + /** 岗位要求 */ + requirements: string; + /** 技能要求 */ + skills: string[]; + /** 岗位可见范围 */ + visibility: 'all' | 'internal' | 'campus'; + /** 是否急招 */ + urgent: boolean; + /** 是否接受应届生 */ + acceptFresh: boolean; + /** 截止日期 */ + deadline: string; +} + +/** 岗位质量预估结果 */ +export interface JobQualityEstimate { + /** 预估分数 0-100 */ + score: number; + /** 等级 */ + level: string; + /** 优化建议 */ + suggestions: string[]; +} + +/* ===================== 岗位评价管理 ===================== */ + +/** 岗位评价维度 */ +export interface JobEvaluationDimension { + id: string; + /** 维度名称 */ + name: string; + /** 得分 */ + score: number; + /** 满分 */ + maxScore: number; + /** 较上期变化 */ + change: number; +} + +/** 单条岗位评价 */ +export interface JobReview { + id: string; + /** 评价者(脱敏) */ + userName: string; + /** 身份标签 */ + userRole: string; + /** 岗位名 */ + positionName: string; + /** 评分 */ + rating: number; + /** 评价日期 */ + date: string; + /** 评价内容 */ + content: string; + /** 标签 */ + tags: string[]; + /** 是否已回复 */ + replied: boolean; +} + +/** 岗位评价管理聚合数据 */ +export interface JobEvaluationManagement { + /** 企业综合评分 */ + overallScore: number; + /** 较上期变化 */ + change: number; + /** 评价总数 */ + reviewCount: number; + /** 好评率 0-100 */ + positiveRate: number; + /** 回复率 0-100 */ + replyRate: number; + /** 投诉率 0-100 */ + complaintRate: number; + /** 评分趋势(月份 -> 分数) */ + trend: { month: string; score: number }[]; + /** 六维评分 */ + dimensions: JobEvaluationDimension[]; + /** 各岗位评分 */ + jobScores: JobEvaluationSummary[]; + /** 评价列表 */ + reviews: JobReview[]; +} + +/* ===================== 候选人详情 ===================== */ + +/** 候选人基本信息 */ +export interface CandidateBasicInfo { + /** 姓名 */ + name: string; + /** 年龄 */ + age: number; + /** 性别 */ + gender: string; + /** 工作年限 */ + experience: string; + /** 学历 */ + education: string; + /** 所在城市 */ + city: string; + /** 手机号(脱敏) */ + phone: string; + /** 邮箱(脱敏) */ + email: string; + /** 求职状态 */ + jobStatus: string; +} + +/** 候选人能力维度 */ +export interface CandidateAbility { + id: string; + /** 能力名称 */ + name: string; + /** 得分 */ + score: number; + /** 满分 */ + maxScore: number; +} + +/** 候选人评价 */ +export interface CandidateReview { + id: string; + /** 面试官 */ + interviewer: string; + /** 岗位 */ + position: string; + /** 日期 */ + date: string; + /** 评分 */ + rating: number; + /** 内容 */ + content: string; + /** 标签 */ + tags: string[]; +} + +/** 录用建议 */ +export interface HireSuggestion { + /** 建议类型:strong / consider / reject */ + type: 'strong' | 'consider' | 'reject'; + /** 建议标题 */ + title: string; + /** 建议描述 */ + desc: string; + /** 是否已选中 */ + selected?: boolean; +} + +/** 薪资建议 */ +export interface SalarySuggestion { + /** 建议薪资范围 */ + range: string; + /** 职级 */ + level: string; + /** 说明 */ + note: string; +} + +/** 候选人详情 */ +export interface CandidateDetail { + /** 候选人 id */ + id: string; + /** 基本信息 */ + basicInfo: CandidateBasicInfo; + /** 匹配度 0-100 */ + matchPercent: number; + /** 综合评分 */ + overallScore: number; + /** 超过同岗位百分比 */ + percentile: number; + /** 评分趋势 */ + trend: { month: string; score: number }[]; + /** 八维能力评分 */ + abilities: CandidateAbility[]; + /** 面试评价 */ + reviews: CandidateReview[]; + /** 录用建议 */ + hireSuggestions: HireSuggestion[]; + /** 薪资建议 */ + salarySuggestion: SalarySuggestion; + /** 是否已发送面试邀请 */ + invited: boolean; +} + +/* ===================== 面试评分 ===================== */ + +/** 面试评分维度 */ +export interface InterviewScoreDimension { + id: string; + /** 维度名称 */ + name: string; + /** 得分 */ + score: number; + /** 满分 */ + maxScore: number; +} + +/** 面试评价标签 */ +export interface InterviewScoreTag { + id: string; + /** 标签名 */ + label: string; + /** 是否已选中 */ + selected: boolean; + /** 颜色 token */ + color: string; +} + +/** 面试评分聚合数据 */ +export interface InterviewScoreData { + /** 候选人 id */ + candidateId: string; + /** 候选人姓名 */ + candidateName: string; + /** 面试岗位 */ + positionName: string; + /** 面试时间 */ + interviewTime: string; + /** 综合评分 */ + overallScore: number; + /** 维度评分 */ + dimensions: InterviewScoreDimension[]; + /** 评价标签 */ + tags: InterviewScoreTag[]; + /** 文字评价 */ + comment: string; + /** 录用建议选项 */ + hireSuggestions: HireSuggestion[]; + /** 薪资建议 */ + salarySuggestion: SalarySuggestion; +} + +/* ===================== 岗位管理 ===================== */ + +/** 岗位管理 Tab 项 */ +export interface JobManagementTab { + /** tab key */ + key: string; + /** 展示文案 */ + label: string; + /** 数量 */ + count: number; +} + +/** 岗位管理顶部数据概览项 */ +export interface JobManagementStat { + /** 统计 id */ + id: string; + /** 名称 */ + label: string; + /** 数值 */ + value: number; + /** 较上期变化文案(如 '+2' / '-5' / '+23%') */ + change: string; + /** Ionicons 图标名 */ + icon: string; + /** 颜色 token */ + color: string; +} + +/** 岗位状态 */ +export type JobManagementJobStatus = 'active' | 'offline' | 'draft'; + +/** 岗位管理列表项 */ +export interface JobManagementJob { + /** 岗位 id */ + id: string; + /** 岗位名称 */ + title: string; + /** 薪资范围文案(如 '25-35K') */ + salary: string; + /** 薪资月数 */ + salaryMonths: number; + /** 工作城市 */ + city: string; + /** 经验要求 */ + experience: string; + /** 学历要求 */ + education: string; + /** 岗位标签 */ + tags: string[]; + /** 岗位状态 */ + status: JobManagementJobStatus; + /** 所属部门 */ + department: string; + /** 招聘人数 */ + headcount: number; + /** 综合评分 */ + overallScore: number; + /** 同岗排名文案 */ + rankText: string; + /** 投递数 */ + applicants: number; + /** 查看数 */ + views: number; + /** 匹配数 */ + matches: number; +} + +/** 岗位管理聚合数据 */ +export interface JobManagement { + /** 企业名称 */ + companyName: string; + /** 是否有数据(true=展示数据态,false=展示空态) */ + hasData: boolean; + /** 数据概览 */ + stats: JobManagementStat[]; + /** tab 列表 */ + tabs: JobManagementTab[]; + /** 岗位列表 */ + jobs: JobManagementJob[]; + /** 发布岗位后可享受的权益(空态用) */ + benefits: string[]; +} + +/* ===================== 人才推荐 ===================== */ + +/** 人才推荐 Tab 项 */ +export interface TalentRecommendationTab { + key: string; + label: string; + count: number; +} + +/** 数据参考项 */ +export interface DataReferenceItem { + label: string; + value: string; +} + +/** 人才池统计项 */ +export interface TalentPoolStat { + /** 统计 id */ + id: string; + /** 数值 */ + value: string; + /** 名称 */ + label: string; +} + +/** 人才推荐候选人 */ +export interface TalentCandidate { + /** 候选人 id */ + id: string; + /** 姓名 */ + name: string; + /** 头像字符 */ + avatar: string; + /** 当前职位 */ + title: string; + /** 工作经验 */ + experience: string; + /** 年龄 */ + age: number; + /** 学校 */ + school: string; + /** 城市 */ + city: string; + /** 期望薪资 */ + expectedSalary: string; + /** 匹配度 0-100 */ + matchPercent: number; + /** 综合评分 */ + overallScore: number; + /** 超越同岗百分比 */ + percentile: number; + /** 技能 / 优势标签 */ + tags: string[]; +} + +/** 人才推荐聚合数据 */ +export interface TalentRecommendation { + companyName: string; + /** 是否有数据 */ + hasData: boolean; + /** 今日推荐人数 */ + todayCount: number; + tabs: TalentRecommendationTab[]; + /** 推荐候选人列表 */ + candidates: TalentCandidate[]; + /** 人才池统计 */ + poolStats: TalentPoolStat[]; + /** 推荐操作 */ + recommendedActions: string[]; + /** 数据参考 */ + dataReference: DataReferenceItem[]; +} + +/* ===================== 企业沟通 ===================== */ + +/** 企业沟通 Tab 项 */ +export interface EnterpriseCommunicationTab { + key: string; + label: string; + count: number; +} + +/** 待处理任务项 */ +export interface PendingTask { + /** 任务 id */ + id: string; + /** 任务标题 */ + label: string; + /** 数量 */ + count: number; + /** 提示说明 */ + hint?: string; + /** Ionicons 图标名 */ + icon: string; + /** 颜色 token */ + color: string; +} + +/** 企业沟通会话项 */ +export interface EnterpriseChatItem { + /** 会话 id */ + id: string; + /** 用户 id */ + userId: string; + /** 姓名 */ + name: string; + /** 头像字符 */ + avatar: string; + /** 当前职位 */ + title: string; + /** 最后一条消息 */ + lastMessage: string; + /** 时间 */ + time: string; + /** 未读数 */ + unreadCount: number; + /** 标签 */ + labels: string[]; +} + +/** 企业沟通聚合数据 */ +export interface EnterpriseCommunication { + companyName: string; + /** 是否有数据 */ + hasData: boolean; + /** 待处理任务 */ + pendingTasks: PendingTask[]; + tabs: EnterpriseCommunicationTab[]; + /** 会话列表 */ + chats: EnterpriseChatItem[]; + /** 数据参考 */ + dataReference: DataReferenceItem[]; +} + +/* ===================== 岗位评价管理空状态 ===================== */ + +/** 岗位评价管理 Tab 项 */ +export interface JobEvaluationEmptyTab { + key: string; + label: string; + count: number; +} + +/** 岗位评价管理空状态 */ +export interface JobEvaluationEmptyState { + /** 是否有评价数据 */ + hasData: boolean; + /** tab 列表 */ + tabs: JobEvaluationEmptyTab[]; + /** 空状态标题 */ + emptyTitle: string; + /** 空状态副标题 */ + emptySubtitle: string; + /** 如何获得更多好评 */ + tips: string[]; +} + +/* ===================== 企业认证 ===================== */ + +/** 企业认证状态 */ +export type EnterpriseCertificationStatus = 'unverified' | 'pending' | 'verified' | 'rejected'; + +/** 认证权益项 */ +export interface EnterpriseCertificationBenefit { + /** 权益 id */ + id: string; + /** 权益文案 */ + text: string; +} + +/** 企业认证表单数据 */ +export interface EnterpriseCertificationForm { + /** 企业名称 */ + companyName: string; + /** 统一社会信用代码 */ + creditCode: string; + /** 营业执照图片 URL */ + licenseUrl?: string; + /** 联系人姓名 */ + contactName: string; + /** 联系人手机号 */ + contactPhone: string; +} + +/** 企业认证聚合数据 */ +export interface EnterpriseCertificationData { + /** 当前认证状态 */ + status: EnterpriseCertificationStatus; + /** 状态说明 */ + statusText: string; + /** 认证权益列表 */ + benefits: EnterpriseCertificationBenefit[]; + /** 表单数据 */ + form: EnterpriseCertificationForm; +} diff --git a/src/types/evaluation.ts b/src/types/evaluation.ts new file mode 100644 index 0000000..e778231 --- /dev/null +++ b/src/types/evaluation.ts @@ -0,0 +1,92 @@ +/** + * HR 评价 / 评价申诉相关类型定义 + */ + +/** 评价维度 */ +export interface EvaluationDimension { + id: string; + /** 维度名称 */ + name: string; + /** 得分 */ + score: number; + /** 满分 */ + maxScore: number; + /** 评级 */ + level: string; + /** 相比上次变化 */ + change?: number; +} + +/** 评分趋势点 */ +export interface EvaluationTrendPoint { + /** 月份 */ + month: string; + /** 综合评分 */ + score: number; +} + +/** 单条 HR 评价 */ +export interface EvaluationReview { + id: string; + /** 评价公司 */ + company: string; + /** 评分 */ + rating: number; + /** 评价日期 */ + date: string; + /** 评价内容 */ + content: string; + /** 标签 */ + tags: string[]; + /** 状态 */ + status: 'accepted' | 'appealed'; +} + +/** HR 评价详情 */ +export interface HREvaluation { + /** 综合评分 */ + overallScore: number; + /** 超过同岗位百分比 */ + percentile: number; + /** 评分趋势 */ + trend: EvaluationTrendPoint[]; + /** 六维能力评分 */ + dimensions: EvaluationDimension[]; + /** 评价列表 */ + reviews: EvaluationReview[]; + /** 评分规则说明 */ + rules: string[]; +} + +/** 申诉类型 */ +export type AppealType = 'overall' | 'single'; + +/** 申诉原因 */ +export type AppealReason = 'malicious' | 'untrue' | 'privacy' | 'other'; + +/** 申诉维度 */ +export interface AppealDimension { + id: string; + name: string; + selected: boolean; +} + +/** 评价申诉表单 */ +export interface EvaluationAppealForm { + /** 申诉类型 */ + type: AppealType; + /** 申诉维度(单条申诉时选择) */ + dimensions: AppealDimension[]; + /** 申诉原因 */ + reason: AppealReason; + /** 原因描述 */ + reasonText: string; + /** 证据图片 URL */ + evidence: string[]; + /** 联系电话 */ + contactPhone: string; + /** 联系邮箱 */ + contactEmail: string; + /** 是否同意申诉须知 */ + agree: boolean; +} diff --git a/src/types/game.ts b/src/types/game.ts new file mode 100644 index 0000000..e3c2eb8 --- /dev/null +++ b/src/types/game.ts @@ -0,0 +1,114 @@ +/** + * 通关小游戏领域模型。 + * 针对求职者角色,通过 7 关小游戏帮助其认识自身、能力、岗位、企业、简历、面试、签约。 + */ + +/** 关卡类型:对应求职准备的 7 个环节 */ +export type GameLevelType = + | 'self' + | 'ability' + | 'job' + | 'company' + | 'resume' + | 'interview' + | 'offer'; + +/** 关卡状态 */ +export type GameLevelStatus = 'locked' | 'available' | 'completed'; + +/** 单道题目类型 */ +export type GameQuestionType = 'single' | 'multiple' | 'boolean' | 'input'; + +/** 单道题目选项 */ +export interface GameOption { + /** 选项 id */ + id: string; + /** 选项文案 */ + text: string; + /** 是否正确/推荐选项 */ + isCorrect: boolean; +} + +/** 单个关卡题目 */ +export interface GameQuestion { + /** 题目 id */ + id: string; + /** 题干 */ + title: string; + /** 题目类型 */ + type: GameQuestionType; + /** 选项(单选/多选/判断必有) */ + options?: GameOption[]; + /** 输入题默认值提示 */ + placeholder?: string; + /** 题目解析(答完后展示) */ + explanation: string; + /** 本题对应的能力标签(用于后续岗位推荐) */ + tags: string[]; +} + +/** 单个关卡 */ +export interface GameLevel { + /** 关卡 id */ + id: GameLevelType; + /** 关卡序号 1-7 */ + order: number; + /** 关卡标题 */ + title: string; + /** 关卡副标题 */ + subtitle: string; + /** 图标名(Ionicons) */ + icon: string; + /** 关卡描述 */ + description: string; + /** 题目列表 */ + questions: GameQuestion[]; + /** 完成本关后解锁的下一关 id */ + nextLevel?: GameLevelType; +} + +/** 用户在某关的作答记录 */ +export interface GameAnswer { + /** 题目 id */ + questionId: string; + /** 用户选择的选项 id 列表(单选/判断为 1 个) */ + selectedIds: string[]; + /** 输入题文本答案 */ + inputValue?: string; + /** 是否正确(接口层判定) */ + isCorrect: boolean; +} + +/** 用户在单关的完成结果 */ +export interface GameLevelResult { + /** 关卡 id */ + levelId: GameLevelType; + /** 是否完成 */ + completed: boolean; + /** 得分(0-100) */ + score: number; + /** 作答记录 */ + answers: GameAnswer[]; + /** 完成时间 ISO 字符串 */ + completedAt?: string; +} + +/** 通关小游戏总进度 */ +export interface GameProgress { + /** 当前可用/进行中的关卡 id */ + currentLevel: GameLevelType; + /** 各关完成结果 */ + results: GameLevelResult[]; + /** 已解锁关卡 id */ + unlockedLevels: GameLevelType[]; + /** 总进度百分比 0-100 */ + totalPercent: number; +} + +/** 提交关卡答案的请求体 */ +export interface SubmitGameLevelPayload { + /** 关卡 id */ + levelId: GameLevelType; + /** 作答记录 */ + answers: GameAnswer[]; +} diff --git a/src/types/growth.ts b/src/types/growth.ts index 89f89d1..80030d1 100644 --- a/src/types/growth.ts +++ b/src/types/growth.ts @@ -3,12 +3,191 @@ */ import type { JobLevel } from './job'; +/** 霍兰德六维类型 */ +export type HollandType = 'R' | 'I' | 'A' | 'S' | 'E' | 'C'; + +/** 霍兰德维度得分 */ +export interface HollandDimension { + /** 类型代码 */ + type: HollandType; + /** 类型中文名,如 "现实型" */ + name: string; + /** 原始得分 */ + score: number; + /** 百分位 0-100 */ + percent: number; +} + +/** 霍兰德测评题目 */ +export interface HollandQuestion { + id: string; + /** 题号(从 1 开始) */ + order: number; + /** 题目描述 */ + text: string; +} + +/** 霍兰德选项 */ +export interface HollandOption { + /** 分值 */ + value: number; + /** 展示文案 */ + label: string; +} + +/** 霍兰德主导类型解析 */ +export interface HollandTypeAnalysis { + /** 类型代码 */ + type: HollandType; + /** 类型中文名 */ + name: string; + /** 得分 */ + score: number; + /** 类型特征描述 */ + description: string; + /** 适合职业 */ + careers: string[]; +} + +/** 霍兰德推荐岗位 */ +export interface HollandJobRecommendation { + id: string; + /** 岗位名 */ + title: string; + /** 公司名 */ + companyName: string; + /** 薪资 */ + salary: string; + /** 城市 */ + city: string; + /** 标签 */ + tags: string[]; + /** 匹配度 0-100 */ + match: number; +} + +/** 霍兰德结果行动建议 */ +export interface HollandAction { + id: string; + /** 标题 */ + title: string; + /** 描述 */ + desc: string; + /** Ionicons 图标名 */ + icon: string; + /** 跳转路由 */ + route?: string; +} + +/** 霍兰德六维类型简介 */ +export interface HollandDimensionIntro { + /** 类型代码 */ + type: HollandType; + /** 类型中文名 */ + name: string; + /** 一句话描述 */ + desc: string; +} + +/** 霍兰德职业兴趣测评介绍页 */ +export interface HollandIntro { + /** 测评标题 */ + title: string; + /** 测评副标题 */ + subtitle: string; + /** 累计完成人数 */ + totalParticipants: number; + /** 预计用时 */ + duration: string; + /** 六维类型简介 */ + dimensions: HollandDimensionIntro[]; + /** 核心亮点 */ + features: { + id: string; + icon: string; + title: string; + desc: string; + }[]; + /** 测评优势 */ + advantages: string[]; +} + +/** 霍兰德职业兴趣测评 */ +export interface HollandAssessment { + /** 累计完成人数 */ + totalParticipants: number; + /** 当前题目索引(从 0 开始) */ + currentQuestionIndex: number; + /** 题目总数 */ + totalQuestions: number; + /** 当前题目 */ + question: HollandQuestion; + /** 选项 */ + options: HollandOption[]; + /** 当前六维分布(答题过程中实时更新) */ + dimensions: HollandDimension[]; + /** 测评结果(完成时返回) */ + result?: { + /** 类型代码,如 "ISR" */ + code: string; + /** 组合名称 */ + name: string; + /** 六维得分 */ + dimensions: HollandDimension[]; + /** 主导类型解析 */ + analyses: HollandTypeAnalysis[]; + /** 推荐岗位 */ + jobs: HollandJobRecommendation[]; + /** 下一步行动 */ + actions: HollandAction[]; + }; +} + /** 能力维度(四维能力测评的单条维度) */ export interface AbilityDimension { /** 维度名称,如 "专业能力" */ label: string; /** 维度得分(0-100) */ value: number; + /** 百分位描述(如 "超过 78% 同岗位求职者") */ + percentileText?: string; + /** 该维度下子项得分明细 */ + subItems?: AbilitySubItem[]; +} + +/** 四维能力测评子项 */ +export interface AbilitySubItem { + id: string; + label: string; + score: number; + maxScore: number; +} + +/** 能力成长趋势点 */ +export interface AbilityTrendPoint { + /** 月份 */ + month: string; + /** 专业能力 */ + professional: number; + /** 项目经验 */ + project: number; + /** 技能广度 */ + skill: number; + /** 成长潜力 */ + growth: number; +} + +/** 能力提升建议 */ +export interface AbilityImprovement { + id: string; + /** 建议标题 */ + title: string; + /** 建议描述 */ + description: string; + /** Ionicons 图标名 */ + icon: string; + /** 跳转路由 */ + route?: string; } /** 四维能力测评结果 */ @@ -19,6 +198,12 @@ export interface AbilityAssessment { level: JobLevel; /** 4 个能力维度 */ dimensions: AbilityDimension[]; + /** 超过同岗位求职者百分比 */ + percentile?: number; + /** 能力成长趋势(最近 6 个月) */ + trends: AbilityTrendPoint[]; + /** 针对性提升建议 */ + improvements: AbilityImprovement[]; } /** AI 求职工具 */ @@ -50,9 +235,3 @@ export interface Article { /** Ionicons 图标名 */ icon: string; } - -/** 霍兰德职业兴趣测评 */ -export interface HollandAssessment { - /** 累计完成人数 */ - totalParticipants: number; -} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index e483632..b30a762 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -2,7 +2,6 @@ * 类型统一导出 */ export type { - Category, EmployeeReview, Job, JobDetail, @@ -10,7 +9,6 @@ export type { JobLevel, JobListParams, JobOverallScore, - MatchData, SalaryData, SalaryRange, SortType, @@ -18,12 +16,106 @@ export type { export type { AbilityAssessment, AbilityDimension, + AbilityImprovement, + AbilitySubItem, + AbilityTrendPoint, AITool, Article, + HollandAction, HollandAssessment, + HollandDimension, + HollandDimensionIntro, + HollandIntro, + HollandJobRecommendation, + HollandOption, + HollandQuestion, + HollandType, + HollandTypeAnalysis, } from './growth'; -export type { AssessmentTool, HomeAITool, HomeTool, HotJob } from './home'; -export type { Conversation, Interview, MatchResult, Notification } from './message'; +export type { + CandidateAbility, + CandidateBasicInfo, + CandidateDetail, + CandidateReview, + DataReferenceItem, + EnterpriseCertificationBenefit, + EnterpriseCertificationData, + EnterpriseCertificationForm, + EnterpriseCertificationStatus, + EnterpriseCommunication, + EnterpriseCommunicationTab, + EnterpriseChatItem, + EnterpriseOverview, + EnterpriseQuickAction, + EnterpriseStat, + HireSuggestion, + InterviewScoreData, + InterviewScoreDimension, + InterviewScoreTag, + JobEducationRequirement, + JobEvaluationDimension, + JobEvaluationEmptyState, + JobEvaluationEmptyTab, + JobEvaluationManagement, + JobEvaluationSummary, + JobExperienceRequirement, + JobManagement, + JobManagementJob, + JobManagementJobStatus, + JobManagementStat, + JobManagementTab, + JobPublishFormData, + JobQualityEstimate, + JobReview, + JobWorkType, + PendingTask, + SalarySuggestion, + TalentCandidate, + TalentPoolStat, + TalentRecommendation, + TalentRecommendationTab, +} from './enterprise'; +export type { + AuditGrade, + AuditKPI, + AuditReport, + AuditTrendPoint, + AutoFilterRule, + MatchMode, + MatchModeOption, + OperationRules, + RiskDistributionItem, +} from './operation'; +export type { + ChatCard, + ChatCardType, + ChatMessage, + ChatMessageRole, + Conversation, + ConversationDetail, + Interview, + MatchResult, + Notification, +} from './message'; +export type { CompareData, CompareDimensionScore, CompareJob } from './compare'; +export type { + OfferCompareData, + OfferItem, + OfferScore, + OfferBenefitGroup, + OfferBenefitRow, + WorkLifeBalanceRow, + OfferAISuggestion, +} from './offer-compare'; +export type { + DimensionPercentile, + ImprovementTip, + RankData, + RankEmptyState, + RankOverview, + ResumeProgressStep, + TopCandidate, +} from './rank'; export type { Badge, DimensionScore, @@ -31,7 +123,116 @@ export type { JobPreference, MenuItem, OverallScore, - UserInfo, UserProfile, UserStats, } from './user'; +export type { WeightDimension, WeightSettings, WeightTemplate } from './weight'; +export type { + Resume, + ResumeBasicInfo, + ResumeEducation, + ResumeExperience, + ResumeExportFormat, + ResumeExportSettings, + ResumeJobIntention, + ResumeOptimizePoint, + ResumeOptimizeResult, + ResumeProject, + ResumeScore, + ResumeSelfEvaluation, + ResumeSkill, + ResumeTemplate, +} from './resume'; +export type { + ChecklistCategory, + ChecklistGroup, + ChecklistItem, + IdentityOption, + JobProgress, + JobProgressCompany, + JobProgressMonthly, + JobProgressOffer, + JobProgressStage, + JobProgressStats, + OnboardingChecklist, + OnboardingTip, + ProgressStatus, + ProgressStep, + SalaryBracket, + SalaryCalcInput, + SalaryCalcResult, + SalaryInsuranceItem, + SalaryStructureItem, + SalaryTaxItem, + SalaryToolLink, + SkillCategory, + SkillCloudData, + SkillCloudFilter, + SkillWord, + TopSkill, +} from './tools'; +export type { + GameAnswer, + GameLevel, + GameLevelResult, + GameLevelStatus, + GameLevelType, + GameOption, + GameProgress, + GameQuestion, + SubmitGameLevelPayload, +} from './game'; +export type { + LegalBrand, + LegalConversation, + LegalFAQ, + LegalMessage, + LegalPlan, + LegalService, + LegalServiceType, + LegalTemplate, +} from './legal'; +export type { + AboutInfo, + AboutLink, + ActionSetting, + ActionSettingVariant, + PrivacyCenter, + PrivacyLevel, + PrivacyLevelInfo, + PrivacySection, + PrivacySettingItem, + PrivacySettingItemType, + ToggleSetting, + UpdateTogglePayload, +} from './settings'; +export type { + AIInterviewData, + InterviewAbilityImprovement, + InterviewJobInfo, + InterviewMode, + InterviewModeOption, + InterviewQuestionTypeCode, + InterviewQuestionTypeItem, + InterviewRecent, + InterviewSession, + InterviewSpecial, +} from './interview'; +export type { + AppealDimension, + AppealReason, + AppealType, + EvaluationAppealForm, + EvaluationDimension, + EvaluationReview, + EvaluationTrendPoint, + HREvaluation, +} from './evaluation'; +export type { + DataExportCategory, + DataExportFormat, + DataExportItem, + DataExportTimeRange, + PersonalDataExport, + RecentExportRecord, +} from './data-export'; diff --git a/src/types/interview.ts b/src/types/interview.ts new file mode 100644 index 0000000..4ac9ff9 --- /dev/null +++ b/src/types/interview.ts @@ -0,0 +1,105 @@ +/** + * AI 模拟面试相关类型定义 + */ + +/** 面试模式:全真模拟 / 专项突破 / 快速问答 */ +export type InterviewMode = 'full' | 'special' | 'fast'; + +/** 面试模式选项 */ +export interface InterviewModeOption { + id: InterviewMode; + /** 模式名称 */ + label: string; + /** 模式描述 */ + desc: string; + /** Ionicons 图标名 */ + icon: string; +} + +/** 面试岗位/公司/难度/时长信息 */ +export interface InterviewJobInfo { + /** 面试岗位 */ + position: string; + /** 面试公司 */ + company: string; + /** 面试难度 */ + difficulty: string; + /** 面试时长(分钟) */ + duration: number; +} + +/** 题型号 */ +export type InterviewQuestionTypeCode = 'tech' | 'behavior' | 'logic'; + +/** 题型设置项 */ +export interface InterviewQuestionTypeItem { + id: InterviewQuestionTypeCode; + /** 题型名称 */ + label: string; + /** 颜色 token(页面映射为 colors[color]) */ + color: string; + /** 是否选中 */ + selected: boolean; +} + +/** 本次预估可提升能力项 */ +export interface InterviewAbilityImprovement { + id: string; + /** 能力名称 */ + label: string; + /** 提升百分比 */ + percent: number; + /** 颜色 token */ + color: string; +} + +/** 最近练习记录 */ +export interface InterviewRecent { + id: string; + /** 练习日期 */ + date: string; + /** 练习名称 */ + title: string; + /** 结果评级 */ + result: string; + /** 结果颜色 token */ + resultColor: string; +} + +/** 专项突破选项(专项突破模式下选择弱项) */ +export interface InterviewSpecial { + id: string; + /** 专项名称 */ + label: string; + /** 是否选中 */ + selected: boolean; +} + +/** 面试会话(已开始) */ +export interface InterviewSession { + id: string; + /** 总题数 */ + total: number; + /** 当前题号(从 1 开始) */ + currentIndex: number; + /** 剩余时间(秒) */ + remainingSeconds: number; +} + +/** AI 模拟面试页面聚合数据 */ +export interface AIInterviewData { + /** 当前模式 */ + mode: InterviewMode; + /** 可选模式 */ + modes: InterviewModeOption[]; + /** 岗位/公司/难度/时长 */ + jobInfo: InterviewJobInfo; + /** 题型设置 */ + questionTypes: InterviewQuestionTypeItem[]; + /** 专项突破选项 */ + specials: InterviewSpecial[]; + /** 本次预估可提升能力 */ + improvements: InterviewAbilityImprovement[]; + /** 最近练习记录 */ + recent: InterviewRecent[]; +} diff --git a/src/types/job.ts b/src/types/job.ts index f66ba3e..d1163c8 100644 --- a/src/types/job.ts +++ b/src/types/job.ts @@ -2,14 +2,6 @@ * 岗位相关类型定义 */ -/** 岗位分类 */ -export interface Category { - id: string; - name: string; - /** Ionicons 图标名(类型层不依赖 UI 库,渲染时再断言) */ - icon: string; -} - /** 岗位评级等级 */ export type JobLevel = 'excellent' | 'good' | 'medium'; @@ -42,16 +34,6 @@ export interface Job { benefits: string[]; } -/** 岗位匹配度 */ -export interface MatchData { - /** 岗位名称 */ - position: string; - /** 匹配度(0-100) */ - matchRate: number; - /** 副标题/行动文案,如 "双向匹配成功·立即沟通" */ - subtitle: string; -} - /** 岗位排序方式 */ export type SortType = 'comprehensive' | 'latest' | 'salary'; @@ -89,7 +71,7 @@ export interface JobDetail extends Job { salaryMonths: number; } -/** 岗位详情维度评分(雷达图 6 维度,与用户端 DimensionScore 区分命名) */ +/** 岗位详情维度评分(雷达图 8 维度,与用户端 DimensionScore 区分命名) */ export interface JobDimensionScore { id: string; /** 维度名称,如 "岗位真实性" */ @@ -106,7 +88,7 @@ export interface JobOverallScore { level: string; /** 排名文案,如 "超过91%同行业岗位" */ rank: string; - /** 6 个维度评分 */ + /** 8 个维度评分 */ dimensions: JobDimensionScore[]; } diff --git a/src/types/legal.ts b/src/types/legal.ts new file mode 100644 index 0000000..509cef0 --- /dev/null +++ b/src/types/legal.ts @@ -0,0 +1,103 @@ +/** + * 阿布阿诺 AI 法律服务助手领域模型。 + * 为大学生求职者提供求职劳动权益、劳动合同、不公平辞退等法律问题的基础与付费法律服务。 + */ + +/** 法律服务分类 */ +export type LegalServiceType = 'free' | 'member'; + +/** 单条法律服务项 */ +export interface LegalService { + /** 服务 id */ + id: string; + /** 服务标题 */ + title: string; + /** 服务描述 */ + description: string; + /** 图标名(Ionicons) */ + icon: string; + /** 服务分类:免费 / 会员 */ + type: LegalServiceType; + /** 跳转动作:chat=法律咨询对话 / templates=文书模板 / faq=常见问题 / upgrade=开通会员 */ + action: 'chat' | 'templates' | 'faq' | 'upgrade'; +} + +/** 法律常见问题 */ +export interface LegalFAQ { + /** FAQ id */ + id: string; + /** 问题 */ + question: string; + /** 答案 */ + answer: string; + /** 所属分类 */ + category: string; +} + +/** 法律文书模板 */ +export interface LegalTemplate { + /** 模板 id */ + id: string; + /** 模板名称 */ + name: string; + /** 模板描述 */ + description: string; + /** 图标名(Ionicons) */ + icon: string; + /** 下载/预览地址(mock 阶段为占位) */ + url: string; +} + +/** 会员套餐 */ +export interface LegalPlan { + /** 套餐 id */ + id: string; + /** 套餐名称 */ + name: string; + /** 价格(元/周期) */ + price: number; + /** 周期描述,如「/月」「/年」 */ + period: string; + /** 权益列表 */ + benefits: string[]; + /** 是否推荐 */ + recommended?: boolean; +} + +/** 法律咨询单条消息 */ +export interface LegalMessage { + /** 消息 id */ + id: string; + /** 消息角色 */ + role: 'user' | 'assistant'; + /** 消息内容 */ + content: string; + /** 时间戳 */ + timestamp: string; + /** 是否为富卡片(如律师推荐、文书入口) */ + card?: 'lawyer' | 'template' | 'plan'; + /** 卡片附加数据 */ + cardData?: Record; +} + +/** 法律咨询会话 */ +export interface LegalConversation { + /** 会话 id */ + id: string; + /** 会话标题 */ + title: string; + /** 消息列表 */ + messages: LegalMessage[]; +} + +/** 阿布阿诺品牌信息 */ +export interface LegalBrand { + /** 品牌名 */ + name: string; + /** 公司全称 */ + company: string; + /** 品牌口号 */ + slogan: string; + /** 品牌 Logo(mock 阶段用 emoji/图标占位) */ + logo: string; +} diff --git a/src/types/message.ts b/src/types/message.ts index 2fcc178..9c82640 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -60,3 +60,66 @@ export interface MatchResult { /** 企业名称列表 */ companies: string[]; } + +/* ===================== 聊天详情(HR 单聊会话) ===================== */ + +/** 聊天消息发送方 */ +export type ChatMessageRole = 'user' | 'hr' | 'system'; + +/** 富消息卡片类型 */ +export type ChatCardType = 'interview' | 'salary' | 'offer'; + +/** 富消息卡片 */ +export interface ChatCard { + /** 卡片类型 */ + type: ChatCardType; + /** 卡片标题 */ + title: string; + /** 卡片副标题/说明 */ + subtitle?: string; + /** 列表项(面试题、薪资项等) */ + items?: string[]; + /** 操作按钮文案 */ + actionText: string; + /** 跳转路由 */ + route?: string; +} + +/** 单条聊天消息 */ +export interface ChatMessage { + /** 消息 id */ + id: string; + /** 发送方 */ + role: ChatMessageRole; + /** 文本内容(卡片消息可空) */ + content?: string; + /** 富消息卡片 */ + card?: ChatCard; + /** 发送时间,如 "10:30" */ + timestamp?: string; + /** 是否已读 */ + read?: boolean; +} + +/** 聊天会话详情 */ +export interface ConversationDetail { + /** 会话 id */ + id: string; + /** HR 姓名 */ + contactName: string; + /** 公司名 */ + companyName: string; + /** 岗位名 */ + positionName: string; + /** HR 头像占位 */ + avatar: string; + /** 在线状态 */ + online: boolean; + /** 双向匹配信息 */ + matchInfo?: { + /** 匹配度百分比 0-100 */ + score: number; + }; + /** 消息列表(时间顺序) */ + messages: ChatMessage[]; +} diff --git a/src/types/offer-compare.ts b/src/types/offer-compare.ts new file mode 100644 index 0000000..667edd7 --- /dev/null +++ b/src/types/offer-compare.ts @@ -0,0 +1,83 @@ +/** + * Offer 对比相关类型定义 + */ + +/** 单个 Offer */ +export interface OfferItem { + id: string; + /** 公司名 */ + company: string; + /** 岗位名称 */ + position: string; + /** 薪资范围 */ + salary: string; + /** 是否已选中参与对比 */ + selected: boolean; + /** 主题色(用于图表/高亮) */ + color: string; +} + +/** 评分项 */ +export interface OfferScore { + /** 分数 */ + score: number; + /** 等级,如 "优秀" / "良好" */ + level: string; + /** 星级 0-5 */ + stars: number; +} + +/** 薪资福利对比 - 单行 */ +export interface OfferBenefitRow { + /** 行标签 */ + label: string; + /** 每个 Offer 的值 */ + values: (string | null)[]; + /** 是否高亮该行的每个值(如更高的基本工资) */ + highlights?: boolean[]; +} + +/** 薪资福利对比 - 分组 */ +export interface OfferBenefitGroup { + /** 分组标题,如 "薪资" / "福利" / "发展" */ + title: string; + /** 分组下的行 */ + rows: OfferBenefitRow[]; +} + +/** 工作生活平衡对比 */ +export interface WorkLifeBalanceRow { + label: string; + values: string[]; + highlights?: boolean[]; +} + +/** AI 智能建议 */ +export interface OfferAISuggestion { + /** 一句话总结 */ + summary: string; + /** 详细分析 */ + detail: string; + /** 查看报告按钮文案 */ + reportAction: string; +} + +/** Offer 对比 - 完整数据 */ +export interface OfferCompareData { + /** 参与对比的 Offer 列表 */ + offers: OfferItem[]; + /** 综合评分对比 */ + scores: OfferScore[]; + /** 薪资福利对比分组 */ + benefitGroups: OfferBenefitGroup[]; + /** 工作生活平衡对比 */ + workLifeBalance: WorkLifeBalanceRow[]; + /** AI 智能建议 */ + aiSuggestion: OfferAISuggestion; + /** 底部操作 */ + actions: { + exportLabel: string; + acceptLabel: string; + acceptOfferId: string; + }; +} diff --git a/src/types/operation.ts b/src/types/operation.ts new file mode 100644 index 0000000..a660b09 --- /dev/null +++ b/src/types/operation.ts @@ -0,0 +1,121 @@ +/** + * 运营 / 风控 / 平台治理领域模型 + * + * 包含: + * - 运营规则配置(双向匹配规则、匹配模式、阈值、自动筛选等) + * - 数据审计报告(风控审计、核心数据看板、威胁趋势、风险类型分布) + */ + +/* ===================== 运营规则配置 ===================== */ + +/** 匹配模式 */ +export type MatchMode = 'strict' | 'recommended' | 'broad'; + +/** 匹配模式选项 */ +export interface MatchModeOption { + /** 模式 id */ + id: MatchMode; + /** 模式名称 */ + name: string; + /** 模式说明 */ + desc: string; + /** 是否推荐 */ + recommended?: boolean; +} + +/** 自动筛选规则项 */ +export interface AutoFilterRule { + /** 规则 id */ + id: string; + /** 规则名称 */ + name: string; + /** 规则说明 */ + description: string; + /** 是否启用 */ + enabled: boolean; +} + +/** 双向匹配规则 / 运营规则配置 */ +export interface OperationRules { + /** 当前匹配模式 */ + matchMode: MatchMode; + /** 可选匹配模式 */ + matchModeOptions: MatchModeOption[]; + /** 双向匹配阈值 0-100 */ + matchThreshold: number; + /** 候选人可见度阈值 0-100 */ + visibilityThreshold: number; + /** 每日推荐数量上限 */ + dailyRecommendLimit: number; + /** 自动筛选规则列表 */ + autoFilters: AutoFilterRule[]; + /** 推荐规则:允许急招岗位优先 */ + urgentPriority: boolean; + /** 推荐规则:允许校园岗位优先 */ + campusPriority: boolean; + /** 通知设置:新候选人投递通知 */ + notifyNewDelivery: boolean; +} + +/* ===================== 数据审计报告 ===================== */ + +/** 审计等级 */ +export type AuditGrade = 'A' | 'B' | 'C' | 'D'; + +/** 审计核心指标 */ +export interface AuditKPI { + /** 指标 id */ + id: string; + /** 指标名称 */ + label: string; + /** 数值 */ + value: number; + /** 较上期变化(可为负) */ + change: number; + /** 单位 */ + unit?: string; + /** Ionicons 图标名 */ + icon: string; + /** 颜色 token */ + color: string; +} + +/** 威胁趋势点 */ +export interface AuditTrendPoint { + /** 日期 / 月份 */ + date: string; + /** 风险事件数 */ + riskCount: number; + /** 处理数 */ + handledCount: number; +} + +/** 风险类型分布项 */ +export interface RiskDistributionItem { + /** 类型 id */ + id: string; + /** 类型名称 */ + label: string; + /** 占比 0-100 */ + percent: number; + /** 颜色 token */ + color: string; +} + +/** 数据审计报告 */ +export interface AuditReport { + /** 审计周期 */ + period: string; + /** 综合等级 */ + grade: AuditGrade; + /** 等级说明 */ + gradeDesc: string; + /** 今日拦截异常数 */ + todayBlocked: number; + /** 核心指标 */ + kpis: AuditKPI[]; + /** 威胁趋势 */ + threatTrend: AuditTrendPoint[]; + /** 风险类型分布 */ + riskDistribution: RiskDistributionItem[]; +} diff --git a/src/types/rank.ts b/src/types/rank.ts new file mode 100644 index 0000000..7d4c002 --- /dev/null +++ b/src/types/rank.ts @@ -0,0 +1,96 @@ +/** + * 同岗位竞争力排名相关类型定义 + */ + +/** 排名总览(岗位维度头信息 + 综合排名指标) */ +export interface RankData { + /** 当前排名名次,如 127 */ + rank: number; + /** 岗位名称,如 "高级前端工程师" */ + jobTitle: string; + /** 公司名,如 "字节跳动" */ + company: string; + /** 候选人总数,如 1286 */ + totalCandidates: number; + /** 超越的候选人百分比,如 90.1 */ + percentile: number; + /** 综合评分,如 88 */ + overallScore: number; + /** 评分等级,如 "优秀" */ + scoreLevel: string; + /** 百分位排名文案,如 "前 9.9%" */ + rankPosition: string; +} + +/** 维度百分位 */ +export interface DimensionPercentile { + id: string; + /** 维度名称,如 "专业技能" */ + name: string; + /** 百分位排名文案,如 "前 15%" */ + percentile: string; + /** 该项评分,如 92 */ + score: number; +} + +/** TOP 候选人 */ +export interface TopCandidate { + id: string; + /** 候选人名称,如 "候选人 A" */ + name: string; + /** 分数,如 96 */ + score: number; + /** 候选标签,如 ["清华硕士", "5 年大厂"] */ + tags: string[]; +} + +/** 提升建议 */ +export interface ImprovementTip { + id: string; + /** 建议主标题,如 "补充项目经验可提升 5-8 个百分位" */ + title: string; + /** 建议描述,如 "完善你的项目经历描述" */ + description: string; + /** tip=普通建议,action=可点击操作 */ + type: 'tip' | 'action'; +} + +/** 简历完善进度步骤 */ +export interface ResumeProgressStep { + /** 步骤 id */ + id: string; + /** 步骤名称 */ + label: string; + /** 是否已完成 */ + completed: boolean; + /** 补充提示 */ + tip?: string; +} + +/** 竞争力排名空状态 */ +export interface RankEmptyState { + /** 空状态标题 */ + title: string; + /** 空状态副标题 */ + subtitle: string; + /** 按钮文案 */ + actionText: string; + /** 简历完善进度 */ + resumeProgress: ResumeProgressStep[]; +} + +/** 竞争力排名聚合数据(含数据态与空状态) */ +export interface RankOverview { + /** 是否有排名数据 */ + hasData: boolean; + /** 数据态:排名总览 */ + rankData?: RankData; + /** 数据态:维度百分位 */ + dimensions?: DimensionPercentile[]; + /** 数据态:TOP 候选人 */ + candidates?: TopCandidate[]; + /** 数据态:提升建议 */ + tips?: ImprovementTip[]; + /** 空状态 */ + emptyState?: RankEmptyState; +} \ No newline at end of file diff --git a/src/types/resume.ts b/src/types/resume.ts new file mode 100644 index 0000000..d7b854e --- /dev/null +++ b/src/types/resume.ts @@ -0,0 +1,171 @@ +/** + * 简历相关类型定义 + */ + +/** 简历基本信息 */ +export interface ResumeBasicInfo { + /** 姓名 */ + name: string; + /** 头像展示字符 */ + avatar: string; + /** 手机号 */ + phone: string; + /** 邮箱 */ + email: string; + /** 所在城市 */ + location: string; +} + +/** 求职意向 */ +export interface ResumeJobIntention { + /** 目标职位 */ + position: string; + /** 期望薪资 */ + salary: string; + /** 期望城市 */ + city: string; + /** 求职状态 */ + status: 'active' | 'inactive'; + /** 状态展示文案 */ + statusLabel: string; +} + +/** 教育背景 */ +export interface ResumeEducation { + id: string; + /** 学校 */ + school: string; + /** 专业 */ + major: string; + /** 学历 */ + degree: string; + /** 入学年份 */ + startYear: string; + /** 毕业年份 */ + endYear: string; +} + +/** 工作经历 */ +export interface ResumeExperience { + id: string; + /** 公司 */ + company: string; + /** 职位 */ + position: string; + /** 开始时间 */ + startDate: string; + /** 结束时间 */ + endDate: string; + /** 工作内容 */ + description: string; + /** 是否已 AI 优化 */ + aiOptimized: boolean; +} + +/** 项目经历 */ +export interface ResumeProject { + id: string; + /** 项目名称 */ + name: string; + /** 项目角色 */ + role: string; + /** 项目描述 */ + description: string; + /** 是否已 AI 优化 */ + aiOptimized: boolean; +} + +/** 技能标签 */ +export interface ResumeSkill { + name: string; +} + +/** 自我评价 */ +export interface ResumeSelfEvaluation { + content: string; +} + +/** 简历优化点 */ +export interface ResumeOptimizePoint { + id: string; + /** 优化项标题 */ + title: string; + /** 预计提升分数 */ + score: number; + /** 是否已优化 */ + optimized: boolean; +} + +/** 简历评分 */ +export interface ResumeScore { + /** 当前分数 */ + score: number; + /** 满分 */ + maxScore: number; + /** 可优化项列表 */ + optimizePoints: ResumeOptimizePoint[]; +} + +/** 完整简历数据 */ +export interface Resume { + /** 简历评分 */ + score: ResumeScore; + /** 基本信息 */ + basicInfo: ResumeBasicInfo; + /** 求职意向 */ + jobIntention: ResumeJobIntention; + /** 教育背景 */ + education: ResumeEducation[]; + /** 工作经历 */ + experience: ResumeExperience[]; + /** 项目经历 */ + projects: ResumeProject[]; + /** 技能特长 */ + skills: ResumeSkill[]; + /** 自我评价 */ + selfEvaluation: ResumeSelfEvaluation; +} + +/** 简历导出格式 */ +export type ResumeExportFormat = 'pdf' | 'word' | 'image'; + +/** 简历模板 */ +export interface ResumeTemplate { + id: string; + /** 模板名称 */ + name: string; + /** 缩略图颜色 token(页面映射为 colors[color]) */ + color: string; +} + +/** 简历导出设置 */ +export interface ResumeExportSettings { + /** 导出格式 */ + format: ResumeExportFormat; + /** 模板 id */ + templateId: string; + /** 隐藏联系方式 */ + hideContact: boolean; + /** 导出密码保护 */ + exportPassword: boolean; + /** 纸型 */ + paperSize: 'A4' | 'letter'; + /** 方向 */ + orientation: 'portrait' | 'landscape'; + /** 文件名 */ + fileName: string; + /** 包含封面页 */ + includeCover: boolean; + /** 包含自荐信 */ + includeRecommendation: boolean; + /** 定向优化:粘贴岗位 JD */ + targetJobDescription?: string; +} + +/** AI 简历优化结果 */ +export interface ResumeOptimizeResult { + /** 优化后总分 */ + score: number; + /** 优化后的简历数据 */ + resume: Resume; +} diff --git a/src/types/settings.ts b/src/types/settings.ts new file mode 100644 index 0000000..38dc488 --- /dev/null +++ b/src/types/settings.ts @@ -0,0 +1,123 @@ +/** + * 设置页领域模型(隐私设置 / 消息通知 / 账号与安全 / 关于我们)。 + * 开关型与操作型设置分离建模,页面只负责渲染,状态变更由 API 层持久化。 + */ + +/** 开关型设置项(隐私开关、通知开关等) */ +export interface ToggleSetting { + /** 设置项 id */ + id: string; + /** 标题 */ + title: string; + /** 说明文案 */ + description: string; + /** 是否开启 */ + enabled: boolean; +} + +/** 操作型设置项展示变体 */ +export type ActionSettingVariant = 'normal' | 'danger'; + +/** 操作型设置项(绑定手机号、修改密码、注销账号、跳转协议等) */ +export interface ActionSetting { + /** 设置项 id */ + id: string; + /** 标题 */ + title: string; + /** 说明文案 */ + description: string; + /** 右侧展示值(如已绑定的手机号 138****8888) */ + value?: string; + /** 展示变体:normal 普通 / danger 危险操作(红色) */ + variant: ActionSettingVariant; + /** 跳转路由(未配置则为待开发项) */ + route?: string; +} + +/** 关于我们:可跳转的协议/信息链接 */ +export interface AboutLink { + /** 链接 id */ + id: string; + /** 标题 */ + title: string; + /** 跳转路由(未配置则为待开发项) */ + route?: string; +} + +/** 关于我们信息 */ +export interface AboutInfo { + /** 应用名 */ + appName: string; + /** 版本号 */ + version: string; + /** 公司主体 */ + company: string; + /** 应用简介 */ + description: string; + /** 协议与信息链接 */ + links: AboutLink[]; +} + +/** 更新开关型设置的请求体 */ +export interface UpdateTogglePayload { + /** 设置项 id */ + id: string; + /** 目标开关状态 */ + enabled: boolean; +} + +/* ===================== 隐私中心(截图版) ===================== */ + +/** 隐私等级 */ +export type PrivacyLevel = 'low' | 'medium' | 'high'; + +/** 隐私等级信息 */ +export interface PrivacyLevelInfo { + /** 等级 key */ + level: PrivacyLevel; + /** 展示文案 */ + label: string; + /** 等级说明 */ + description: string; +} + +/** 隐私设置项类型 */ +export type PrivacySettingItemType = 'switch' | 'link' | 'danger-link' | 'value'; + +/** 隐私中心单条设置项 */ +export interface PrivacySettingItem { + /** 设置项 id */ + id: string; + /** 标题 */ + title: string; + /** 说明文案 */ + description?: string; + /** 类型 */ + type: PrivacySettingItemType; + /** 开关状态(type=switch 时有效) */ + enabled?: boolean; + /** 右侧展示值(type=value 时有效) */ + value?: string; + /** 跳转路由 */ + route?: string; +} + +/** 隐私设置分组 */ +export interface PrivacySection { + /** 分组 id */ + id: string; + /** 分组标题 */ + title: string; + /** 分组项 */ + items: PrivacySettingItem[]; +} + +/** 隐私中心聚合数据 */ +export interface PrivacyCenter { + /** 当前隐私等级 */ + privacyLevel: PrivacyLevelInfo; + /** 分组列表 */ + sections: PrivacySection[]; + /** 底部权限说明 */ + explanation: string; +} diff --git a/src/types/tools.ts b/src/types/tools.ts new file mode 100644 index 0000000..2826a61 --- /dev/null +++ b/src/types/tools.ts @@ -0,0 +1,348 @@ +/** + * 工具类页面领域模型(行业技能词云 / 薪资计算器 / 求职进度 / 选择身份 / 入职准备清单)。 + * 与职来设计稿中「行业技能词云」「薪资计算器」「求职进度」「选择身份」「入职准备清单」五屏对应。 + */ + +/** 入职准备清单分类 */ +export type ChecklistCategory = 'material' | 'procedure' | 'study' | 'life' | 'daily'; + +/** 入职准备清单单项 */ +export interface ChecklistItem { + id: string; + /** 待办标题 */ + title: string; + /** 是否已完成 */ + done: boolean; + /** 是否必填 */ + required?: boolean; +} + +/** 入职准备清单分组 */ +export interface ChecklistGroup { + id: ChecklistCategory; + /** 分组标题 */ + title: string; + /** Ionicons 图标名 */ + icon: string; + /** 是否展开 */ + expanded: boolean; + /** 分组待办 */ + items: ChecklistItem[]; +} + +/** 入职小贴士 */ +export interface OnboardingTip { + id: string; + /** 标题 */ + title: string; + /** 描述 */ + desc: string; + /** Ionicons 图标名 */ + icon: string; +} + +/** 入职准备清单数据 */ +export interface OnboardingChecklist { + /** 距离入职天数 */ + daysLeft: number; + /** 岗位名 */ + position: string; + /** 公司名 */ + company: string; + /** 总待办数 */ + total: number; + /** 已完成数 */ + completed: number; + /** 分组清单 */ + groups: ChecklistGroup[]; + /** 入职日期 */ + onboardingDate: string; + /** 每日提醒 */ + dailyReminder: { + enabled: boolean; + time: string; + }; + /** 入职小贴士 */ + tips: OnboardingTip[]; +} + +/** 行业技能词云:单个技能词(权重决定字号与色深) */ +export interface SkillWord { + /** 唯一 id */ + id: string; + /** 技能名称(如 React、数据分析) */ + text: string; + /** 热度权重 0-100,决定字号与色深 */ + weight: number; + /** 所属技能分组(用于配色区分) */ + group: 'tech' | 'soft' | 'domain' | 'tool'; +} + +/** 技能分类 */ +export interface SkillCategory { + id: string; + label: string; +} + +/** 热门技能 TOP N */ +export interface TopSkill { + id: string; + /** 技能名 */ + name: string; + /** 排名 */ + rank: number; + /** 热度变化百分比 */ + change: number; + /** 需求热度 0-100 */ + demand: number; +} + +/** 词云筛选标签 */ +export interface SkillCloudFilter { + /** 行业选项 */ + industries: string[]; + /** 岗位选项 */ + positions: string[]; + /** 城市选项 */ + cities: string[]; + /** 经验选项 */ + experiences: string[]; +} + +/** 行业技能词云数据 */ +export interface SkillCloudData { + /** 行业名称 */ + industry: string; + /** 岗位名称(展示用) */ + position: string; + /** 城市 */ + city: string; + /** 经验要求 */ + experience: string; + /** 当前选中的技能分类 id */ + activeCategoryId: string; + /** 筛选可选项 */ + filters: SkillCloudFilter; + /** 技能分类 */ + categories: SkillCategory[]; + /** 技能词列表 */ + words: SkillWord[]; + /** 热门技能 TOP 10 */ + topSkills: TopSkill[]; + /** 个人技能匹配度 0-100 */ + matchPercent: number; + /** 数据更新时间(展示用) */ + updatedAt: string; +} + +/** 薪资计算器输入参数 */ +export interface SalaryCalcInput { + /** 税前月薪(元) */ + monthlyGross: number; + /** 所在城市(决定社保公积金上下限,mock 阶段仅展示) */ + city: string; + /** 社保缴费基数(元) */ + socialBase: number; + /** 公积金缴纳比例 0-1(如 0.12) */ + providentFundRate: number; + /** 专项附加扣除(元/月) */ + specialDeduction: number; + /** 年终奖月数 */ + bonusMonths: number; +} + +/** 五险一金单项明细 */ +export interface SalaryInsuranceItem { + /** 险种名称 */ + label: string; + /** 个人缴纳比例(0-1) */ + rate: number; + /** 个人缴纳金额(元/月) */ + amount: number; +} + +/** 个人所得税明细项 */ +export interface SalaryTaxItem { + /** 税项名称 */ + label: string; + /** 金额(元) */ + amount: number; + /** 适用税率(可选) */ + rate?: number; +} + +/** 薪资结构饼图单项 */ +export interface SalaryStructureItem { + /** 分类名称 */ + label: string; + /** 金额(元) */ + value: number; + /** 占比 0-100 */ + percent: number; + /** 主题色 token(页面中映射为 colors[color]) */ + color: string; +} + +/** 薪资计算器更多工具入口 */ +export interface SalaryToolLink { + id: string; + /** 工具名称 */ + title: string; + /** 工具描述 */ + desc: string; + /** Ionicons 图标名 */ + icon: string; + /** 跳转路由 */ + route?: string; +} + +/** 薪资计算器结果(接口层计算,页面只渲染) */ +export interface SalaryCalcResult { + /** 输入参数快照 */ + input: SalaryCalcInput; + /** 税前月薪(元) */ + monthlyGross: number; + /** 年终奖月数 */ + bonusMonths: number; + /** 月度税前合计(月薪 + 年终摊入) */ + monthlyTotal: number; + /** 月度五险一金个人缴纳合计(元) */ + monthlySocial: number; + /** 月度个人所得税(元) */ + monthlyTax: number; + /** 月到手(元) */ + monthlyNet: number; + /** 年税前合计(元) */ + annualGross: number; + /** 年到手合计(元) */ + annualNet: number; + /** 五险一金明细 */ + insuranceItems: SalaryInsuranceItem[]; + /** 个税明细 */ + taxItems: SalaryTaxItem[]; + /** 薪资结构(税前/五险一金/个税/到手) */ + structure: SalaryStructureItem[]; +} + +/** 行业薪资参考(计算器对比用) */ +export interface SalaryBracket { + /** 行业名称 */ + industry: string; + /** 该行业平均月薪(K) */ + avgMonthly: number; + /** 样本量 */ + sampleCount: number; +} + +/** 求职进度统计卡 */ +export interface JobProgressStats { + /** 投递总数 */ + total: number; + /** 待反馈数 */ + pending: number; + /** 面试中 */ + interviewing: number; + /** Offer 数 */ + offer: number; + /** 已结束 */ + ended: number; +} + +/** 求职进度:即将到期 Offer */ +export interface JobProgressOffer { + id: string; + /** 公司名 */ + companyName: string; + /** 岗位名 */ + positionName: string; + /** 薪资 */ + salary: string; + /** 截止日期展示文本 */ + deadline: string; + /** 剩余天数 */ + daysLeft: number; +} + +/** 求职进度:公司流程条目 */ +export type JobProgressStage = '投递' | '待反馈' | '面试中' | 'Offer' | '已结束'; + +export interface JobProgressCompany { + id: string; + /** 公司名 */ + companyName: string; + /** 岗位名 */ + positionName: string; + /** 薪资 */ + salary: string; + /** 当前阶段 */ + stage: JobProgressStage; + /** 阶段展示标签 */ + stageLabel: string; + /** 下一步动作或状态 */ + nextAction: string; + /** 更新时间 */ + date: string; + /** 未读数 */ + unread?: number; +} + +/** 求职进度:月度统计 */ +export interface JobProgressMonthly { + /** 月份,如「8月」 */ + month: string; + /** 投递数 */ + deliveries: number; + /** 面试数 */ + interviews: number; + /** Offer 数 */ + offers: number; +} + +/** 求职进度步骤状态(派生联合类型,禁止页面硬编码判断) */ +export type ProgressStatus = 'done' | 'active' | 'todo'; + +/** 求职进度单个步骤 */ +export interface ProgressStep { + id: string; + /** 阶段标题(如 已投递) */ + title: string; + /** 阶段描述 */ + desc: string; + /** 状态:已完成 / 进行中 / 未开始 */ + status: ProgressStatus; + /** 完成时间(已完成步骤展示,进行中/未开始为空) */ + date?: string; +} + +/** 求职进度数据 */ +export interface JobProgress { + /** 统计概览 */ + stats: JobProgressStats; + /** 即将到期 Offer */ + offers: JobProgressOffer[]; + /** 公司流程列表 */ + companies: JobProgressCompany[]; + /** 月度统计 */ + monthlyStats: JobProgressMonthly[]; + /** 时间轴步骤(可选,保留旧结构) */ + steps?: ProgressStep[]; + /** 面试中数 */ + interviewCount?: number; + /** 总投递数 */ + deliveryCount?: number; + /** 已拿 Offer 数 */ + offerCount?: number; +} + +/** 身份选项(C 端求职者 / 企业端 HR) */ +export interface IdentityOption { + id: string; + /** 身份标题 */ + title: string; + /** 身份描述 */ + desc: string; + /** 图标名(Ionicons) */ + icon: string; + /** 角色:求职者 / HR */ + role: 'candidate' | 'hr'; +} diff --git a/src/types/user.ts b/src/types/user.ts index d7cbd74..4f519f7 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -2,14 +2,6 @@ * 用户相关类型定义 */ -/** 用户信息 */ -export interface UserInfo { - /** 姓名 */ - name: string; - /** 个人 slogan */ - slogan: string; -} - /** 用户个人信息(我的页) */ export interface UserProfile { id: string; diff --git a/src/types/weight.ts b/src/types/weight.ts new file mode 100644 index 0000000..c655e15 --- /dev/null +++ b/src/types/weight.ts @@ -0,0 +1,31 @@ +/** + * 权重设置相关类型定义 + */ + +/** 权重模板 */ +export interface WeightTemplate { + id: string; + /** 模板名称,如 "薪资优先" */ + name: string; + /** 模板描述,如 "薪资30% · 发展15%" */ + description: string; + /** 模板各维度权重,键为维度名称,如 { "薪资竞争力": 30, "发展空间": 15 } */ + weights: Record; +} + +/** 权重维度 */ +export interface WeightDimension { + id: string; + /** 维度名称,如 "薪资竞争力" */ + name: string; + /** 权重百分比,如 20 */ + weight: number; + /** true 表示反向指标(值越低评分越高,如工作强度) */ + isReversed?: boolean; +} + +/** 权重设置 */ +export interface WeightSettings { + dimensions: WeightDimension[]; + templates: WeightTemplate[]; +} \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index e8b6bd5..be555dd 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -9,6 +9,16 @@ export function formatThousands(n: number): string { return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } +/** + * 金额格式化:30000 → "30,000";decimals 控制小数位,默认 0。 + */ +export function formatCurrency(n: number, decimals = 0): string { + const fixed = n.toFixed(decimals); + const parts = fixed.split('.'); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); + return decimals > 0 ? parts.join('.') : parts[0]; +} + /** * 阅读量格式化:23000 → "2.3万";不足一万返回原数字字符串(如 "8500")。 */