diff --git a/components.d.ts b/components.d.ts index 6b640a2..331186e 100644 --- a/components.d.ts +++ b/components.d.ts @@ -31,12 +31,15 @@ declare module 'vue' { JobGoalDialog: typeof import('./src/components/JobGoalDialog.vue')['default'] JobPageHeader: typeof import('./src/components/JobPageHeader.vue')['default'] JobResumeCustomDialog: typeof import('./src/components/JobResumeCustomDialog.vue')['default'] + JobResumeCustomEditPanel: typeof import('./src/components/JobResumeCustomEditPanel.vue')['default'] JobResumeTemplate: typeof import('./src/components/JobResumeTemplate.vue')['default'] LoginDialog: typeof import('./src/components/LoginDialog.vue')['default'] MemberDialog: typeof import('./src/components/MemberDialog.vue')['default'] ProfileEditDrawer: typeof import('./src/components/ProfileEditDrawer.vue')['default'] ProfilePageContent: typeof import('./src/components/ProfilePageContent.vue')['default'] RegionSelector: typeof import('./src/components/tools/RegionSelector.vue')['default'] + ResumeAnalysisReportDrawer: typeof import('./src/components/ResumeAnalysisReportDrawer.vue')['default'] + ResumeIssueFixDrawer: typeof import('./src/components/ResumeIssueFixDrawer.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] SettingsDialog: typeof import('./src/components/SettingsDialog.vue')['default'] diff --git a/src/api/jobs.ts b/src/api/jobs.ts index 74d7f76..66a0ff7 100644 --- a/src/api/jobs.ts +++ b/src/api/jobs.ts @@ -1,5 +1,7 @@ import request from '@/utils/request' +import aiService from '@/utils/aiRequest' import type { ApiResult } from '@/api/auth' +import type { AiResult } from '@/utils/aiRequest' // ==================== 匹配详情 ==================== @@ -363,3 +365,254 @@ export function fetchJobDetail(jobId: string) { params: { jobId }, }) } + +// ==================== 技能差距分析(AI 接口) ==================== + +/** 技能差距分析 — 岗位信息 */ +export interface SkillGapJob { + /** 岗位 ID */ + jobId: string + /** 岗位标题 */ + title: string + /** 技能标签列表 */ + skillTags: string[] +} + +/** 技能差距分析 — 简历信息 */ +export interface SkillGapResume { + /** 简历 ID */ + resumeId: string + /** 简历名称 */ + resumeName: string + /** 目标岗位 */ + targetPosition: string +} + +/** 技能差距分析返回数据 */ +export interface SkillGapData { + /** 匹配度分数 */ + score: number + /** 岗位信息 */ + job: SkillGapJob + /** 简历信息 */ + resume: SkillGapResume + /** 缺少的技能列表 */ + missingSkills: string[] +} + +/** + * 技能差距分析(AI 接口) + * POST /job/skill-gap + * @param jobId 岗位 ID(字符串,避免大整数精度丢失) + */ +export function fetchSkillGap(jobId: string) { + // jobId 作为字符串发送,避免 JS 大整数精度丢失 + return aiService.post }>('/job/skill-gap', { jobId }, { + transformResponse: [(data: string) => { + try { + const processed = data.replace(/:\s*(\d{16,})/g, ':"$1"') + return JSON.parse(processed) + } catch { + return data + } + }], + }).then(res => res.data) +} + +// ==================== 定制简历(AI 接口) ==================== + +/** 定制简历接口返回的简历数据 */ +export interface CustomizeResumeData { + /** 简历基本信息 */ + resume: { + avatarUrl?: string + name?: string + email?: string + mobileNumber?: string + city?: string + wechatNumber?: string + portfolioUrl?: string + skills?: string[] + certificates?: string[] + summary?: string + } + /** 教育经历 */ + education?: Array<{ + id?: string + school?: string + major?: string + degree?: string + studyType?: string + startDate?: string + endDate?: string + description?: Array<{ id?: string; text?: string }> + }> + /** 工作经历 */ + work?: Array<{ + id?: string + companyName?: string + position?: string + startDate?: string + endDate?: string + description?: Array<{ id?: string; text?: string }> + }> + /** 实习经历 */ + internship?: Array<{ + id?: string + companyName?: string + position?: string + startDate?: string + endDate?: string + description?: Array<{ id?: string; text?: string }> + }> + /** 项目经历 */ + project?: Array<{ + id?: string + projectName?: string + companyName?: string + role?: string + startDate?: string + endDate?: string + description?: Array<{ id?: string; text?: string }> + }> + /** 竞赛经历 */ + competition?: Array<{ + id?: string + competitionName?: string + award?: string + awardDate?: string + description?: Array<{ id?: string; text?: string }> + }> +} + +/** + * 查询定制简历结果(AI 接口) + * GET /job/customize-resume + */ +export function fetchCustomizeResume() { + return aiService.get }>('/job/customize-resume', { + transformResponse: [(data: string) => { + try { + const processed = data.replace(/:\s*(\d{16,})/g, ':"$1"') + return JSON.parse(processed) + } catch { + return data + } + }], + }).then(res => res.data) +} + +/** 生成定制简历请求参数 */ +export interface GenerateCustomizeResumeParams { + /** 岗位 ID(字符串,避免大整数精度丢失) */ + jobId: string + /** 简历 ID(字符串,避免大整数精度丢失) */ + resumeId: string + /** 要优化的模块列表 */ + optimizeModules: string[] + /** 要新增的技能关键词 */ + addSkills?: string[] +} + +/** + * 生成定制简历(AI 接口) + * POST /job/customize-resume + * @param params 生成参数 + */ +export function generateCustomizeResume(params: GenerateCustomizeResumeParams) { + return aiService.post }>('/job/customize-resume', params, { + transformResponse: [(data: string) => { + try { + const processed = data.replace(/:\s*(\d{16,})/g, ':"$1"') + return JSON.parse(processed) + } catch { + return data + } + }], + }).then(res => res.data) +} + + +// ==================== AI对话编辑简历(AI 接口) ==================== + +/** AI对话编辑简历的聊天记录项 */ +export interface AiEditChatMessage { + /** 角色:user-用户 assistant-AI助手 */ + role: 'user' | 'assistant' + /** 消息内容 */ + content: string +} + +/** AI对话编辑简历请求参数 */ +export interface AiEditResumeParams { + /** 岗位 ID(字符串,避免大整数精度丢失) */ + jobId: string + /** 用户输入的指令 */ + instruction: string + /** 对话历史记录 */ + chatHistory?: AiEditChatMessage[] +} + +/** AI对话编辑简历返回数据 */ +export interface AiEditResumeResponse { + /** 返回类型:message-对话消息 updated-简历已更新 */ + type: 'message' | 'updated' + /** AI回复的消息内容 */ + message: string +} + +/** + * AI对话编辑简历(AI 接口) + * POST /job/customize-resume/ai-edit + * @param params 请求参数 + */ +export function aiEditResume(params: AiEditResumeParams) { + // 强制确保 jobId 为字符串,避免大整数精度丢失 + const safeParams = { ...params, jobId: String(params.jobId) } + return aiService.post }>('/job/customize-resume/ai-edit', safeParams, { + transformResponse: [(data: string) => { + try { + const processed = data.replace(/:\s*(\d{16,})/g, ':"$1"') + return JSON.parse(processed) + } catch { + return data + } + }], + }).then(res => res.data) +} + +/** + * 撤销AI对话编辑简历的修改(AI 接口) + * POST /job/customize-resume/rollback + */ +export function rollbackCustomizeResume() { + return aiService.post }>('/job/customize-resume/rollback', null, { + transformResponse: [(data: string) => { + try { + const processed = data.replace(/:\s*(\d{16,})/g, ':"$1"') + return JSON.parse(processed) + } catch { + return data + } + }], + }).then(res => res.data) +} + +/** + * 修改定制简历(AI 接口) + * PUT /job/customize-resume + * 输入框失焦或选择器选中后自动调用 + * @param data 定制简历完整数据 + */ +export function updateCustomizeResume(data: CustomizeResumeData) { + return aiService.put }>('/job/customize-resume', data, { + transformResponse: [(raw: string) => { + try { + const processed = raw.replace(/:\s*(\d{16,})/g, ':"$1"') + return JSON.parse(processed) + } catch { + return raw + } + }], + }).then(res => res.data) +} diff --git a/src/api/menu.ts b/src/api/menu.ts index d11a7e9..52bbfb7 100644 --- a/src/api/menu.ts +++ b/src/api/menu.ts @@ -27,9 +27,9 @@ export interface MenuItemRaw { export async function fetchUserRoutes(): Promise { // TODO: 替换为真实接口 → return axios.get('/api/user/menus').then(res => res.data) return [ - { path: '/agent', name: 'Agent', component: 'Agent', meta: { label: 'AI助手', icon: 'nav-agent-icon', badge: 'NEW' } }, - { path: '/profile', name: 'Profile', component: 'Profile', meta: { label: '个人资料', icon: 'nav-profile-icon' } }, { path: '/resume', name: 'Resume', component: 'Resume', meta: { label: '简历', icon: 'nav-resume-icon' } }, + { path: '/profile', name: 'Profile', component: 'Profile', meta: { label: '个人资料', icon: 'nav-profile-icon' } }, + { path: '/agent', name: 'Agent', component: 'Agent', meta: { label: 'AI助手', icon: 'nav-agent-icon', badge: 'NEW' } }, { path: '/settings', name: 'Settings', component: 'Settings', meta: { label: '设置', icon: 'nav-setting-icon', position: 'footer' } }, ] } diff --git a/src/api/resume.ts b/src/api/resume.ts index 29e7027..2d5d950 100644 --- a/src/api/resume.ts +++ b/src/api/resume.ts @@ -1,5 +1,7 @@ import request from '@/utils/request' +import aiService from '@/utils/aiRequest' import type { ApiResult } from '@/api/auth' +import type { AiResult } from '@/utils/aiRequest' // ==================== 简历列表相关 ==================== @@ -370,3 +372,185 @@ export interface SaveResumeCompetitionItem { export function saveResumeCompetition(resumeId: string, data: SaveResumeCompetitionItem[]) { return request.post('/resume/competition', { resumeId, items: data }) } + +/** + * 删除简历 + * POST /resume/delete + */ +export function deleteResume(resumeId: string) { + return request.post('/resume/delete', null, { + params: { resumeId }, + }) +} + +// ==================== 简历诊断相关 ==================== + +/** 诊断报告数据 */ +export interface DiagnosisReport { + /** 报告 ID */ + id?: string + /** 简历 ID */ + resumeId?: string + /** 评级 A/B/C/D */ + grade?: string + /** AI 整体评价 */ + summary?: string + /** 紧急修复总数 */ + urgentTotal?: number + /** 重点优化总数 */ + importantTotal?: number + /** 表达提升总数 */ + expressionTotal?: number + /** 创建时间 */ + createTime?: string +} + +/** 诊断问题子类型计数 */ +export interface IssueSubCounts { + [key: string]: number +} + +/** 诊断问题项 */ +export interface DiagnosisIssue { + /** 问题 ID */ + id?: string + /** 模块类型: summary/education/work/internship/project/competition */ + moduleType?: string + /** 模块记录 ID,summary 时为 resume_id */ + moduleRecordId?: string + /** 诊断发现 */ + finding?: string + /** 为什么重要 */ + importance?: string + /** 改进建议 */ + suggestion?: string + /** 紧急修复子类型计数 */ + urgentIssues?: IssueSubCounts + /** 重点优化子类型计数 */ + importantIssues?: IssueSubCounts + /** 表达提升子类型计数 */ + expressionIssues?: IssueSubCounts + /** AI 改写后的内容 */ + optimizedContent?: any + /** 0=待处理 1=已处理 */ + status?: number + /** 0=未评价 1=符合 2=不符合 */ + userFeedback?: number +} + +/** 诊断接口返回的 data 结构 */ +export interface DiagnosisData { + /** 诊断报告 */ + report: DiagnosisReport + /** 诊断问题列表 */ + issues: DiagnosisIssue[] +} + +/** + * 查询简历诊断报告(AI 接口) + * GET /resume/diagnose/{resume_id} + */ +export function fetchResumeDiagnosis(resumeId: string) { + return aiService.get>(`/resume/diagnose/${resumeId}`, { + transformResponse: [(data: string) => { + try { + // 大整数安全解析 + const processed = data.replace(/:\s*(\d{16,})/g, ':"$1"') + return JSON.parse(processed) + } catch { + return data + } + }], + }).then(res => res.data) +} + +/** 触发诊断返回的 data 结构 */ +export interface TriggerDiagnosisData { + /** 诊断报告 ID */ + reportId?: string +} + +/** + * 触发简历诊断(AI 接口) + * POST /resume/diagnose + */ +export function triggerResumeDiagnosis(resumeId: string) { + return aiService.post>('/resume/diagnose', { resumeId }, { + transformResponse: [(data: string) => { + try { + const processed = data.replace(/:\s*(\d{16,})/g, ':"$1"') + return JSON.parse(processed) + } catch { + return data + } + }], + }).then(res => res.data) +} + +// ==================== 问题润色(AI 优化用户改写内容) ==================== + +/** 润色接口返回的 data 结构 */ +export interface PolishIssueData { + /** AI 润色后的文本段落数组 */ + content: string[] +} + +/** + * 对诊断问题进行 AI 润色(AI 接口) + * POST /resume/diagnose/issue/{issue_id}/polish + * @param issueId 问题 ID + * @param content 用户编辑后的文本段落数组 + */ +export function polishDiagnosisIssue(issueId: string, content: string[]) { + return aiService.post>(`/resume/diagnose/issue/${issueId}/polish`, { content }, { + transformResponse: [(data: string) => { + try { + const processed = data.replace(/:\s*(\d{16,})/g, ':"$1"') + return JSON.parse(processed) + } catch { + return data + } + }], + }).then(res => res.data) +} + +// ==================== 问题用户评价 ==================== + +/** + * 提交诊断问题的用户评价(AI 接口) + * PUT /resume/diagnose/issue/{issue_id}/feedback + * @param issueId 问题 ID + * @param userFeedback 1=符合(有用) 2=不符合(无用) + */ +export function feedbackDiagnosisIssue(issueId: string, userFeedback: number) { + return aiService.put>(`/resume/diagnose/issue/${issueId}/feedback`, { userFeedback }, { + transformResponse: [(data: string) => { + try { + const processed = data.replace(/:\s*(\d{16,})/g, ':"$1"') + return JSON.parse(processed) + } catch { + return data + } + }], + }).then(res => res.data) +} + +// ==================== 标记问题已处理 ==================== + +/** + * 标记诊断问题为已处理(AI 接口) + * PUT /resume/diagnose/issue/{issue_id}/resolve + * @param issueId 问题 ID + */ +export function resolveDiagnosisIssue(issueId: string) { + return aiService.put>(`/resume/diagnose/issue/${issueId}/resolve`, null, { + transformResponse: [(data: string) => { + try { + const processed = data.replace(/:\s*(\d{16,})/g, ':"$1"') + return JSON.parse(processed) + } catch { + return data + } + }], + }).then(res => res.data) +} diff --git a/src/assets/images/万千毕业生的信赖之选.png b/src/assets/images/万千毕业生的信赖之选.png new file mode 100644 index 0000000..9330eb9 Binary files /dev/null and b/src/assets/images/万千毕业生的信赖之选.png differ diff --git a/src/assets/images/岗位专属简历step0.png b/src/assets/images/岗位专属简历step0.png new file mode 100644 index 0000000..fd62383 Binary files /dev/null and b/src/assets/images/岗位专属简历step0.png differ diff --git a/src/assets/images/岗位专属简历step1.png b/src/assets/images/岗位专属简历step1.png new file mode 100644 index 0000000..1a56eb9 Binary files /dev/null and b/src/assets/images/岗位专属简历step1.png differ diff --git a/src/assets/images/岗位专属简历step2.png b/src/assets/images/岗位专属简历step2.png new file mode 100644 index 0000000..a71cebe Binary files /dev/null and b/src/assets/images/岗位专属简历step2.png differ diff --git a/src/assets/images/岗位专属简历step3-01.png b/src/assets/images/岗位专属简历step3-01.png new file mode 100644 index 0000000..23ac5c2 Binary files /dev/null and b/src/assets/images/岗位专属简历step3-01.png differ diff --git a/src/assets/images/简历分析报告.png b/src/assets/images/简历分析报告.png new file mode 100644 index 0000000..3bf3927 Binary files /dev/null and b/src/assets/images/简历分析报告.png differ diff --git a/src/assets/images/简历评估问题修复.png b/src/assets/images/简历评估问题修复.png new file mode 100644 index 0000000..e630006 Binary files /dev/null and b/src/assets/images/简历评估问题修复.png differ diff --git a/src/assets/images/设计稿截图/个人资料-作品集编辑.png b/src/assets/images/设计稿截图/个人资料-作品集编辑.png new file mode 100644 index 0000000..4aa5225 Binary files /dev/null and b/src/assets/images/设计稿截图/个人资料-作品集编辑.png differ diff --git a/src/assets/images/首页.png b/src/assets/images/首页.png new file mode 100644 index 0000000..e9ab9ec Binary files /dev/null and b/src/assets/images/首页.png differ diff --git a/src/assets/styles/components/job-resume-custom-dialog.scss b/src/assets/styles/components/job-resume-custom-dialog.scss index 756f4ea..14d6728 100644 --- a/src/assets/styles/components/job-resume-custom-dialog.scss +++ b/src/assets/styles/components/job-resume-custom-dialog.scss @@ -919,7 +919,6 @@ } &__ai-msg { - margin-bottom: 0.1rem; display: flex; &--ai { @@ -942,6 +941,7 @@ &__ai-msg--ai &__ai-msg-bubble { background: $bg-main; color: $text-dark; + border: 1px solid $border-color; } &__ai-msg--user &__ai-msg-bubble { @@ -949,6 +949,133 @@ color: $bg-white; } + // AI正在思考中的加载气泡 + &__ai-msg-bubble--loading { + color: $text-middle; + font-style: italic; + } + + // 消息包裹容器(含撤销气泡) + &__ai-msg-wrap { + margin-bottom: 0.1rem; + } + + // 撤销修改气泡区域 + &__ai-rollback { + display: flex; + justify-content: flex-start; + padding: 0.06rem 0 0 0; + } + + // 撤销修改按钮 + &__ai-rollback-btn { + display: inline-flex; + align-items: center; + gap: 0.04rem; + background: $bg-white; + border: 1px solid $accent; + border-radius: 0.14rem; + padding: 0.05rem 0.12rem; + font-size: 0.11rem; + color: $accent; + cursor: pointer; + transition: all 0.2s; + + &:hover { + background: $theme-color; + border-color: $accent-hover; + color: $accent-hover; + } + } + + // 撤销修改图标 + &__ai-rollback-icon { + width: 0.13rem; + height: 0.13rem; + flex-shrink: 0; + } + + // 已撤销状态文字 + &__ai-rollback-done { + display: inline-flex; + align-items: center; + gap: 0.04rem; + font-size: 0.11rem; + color: $text-middle; + padding: 0.05rem 0.12rem; + background: $bg-main; + border-radius: 0.14rem; + border: 1px solid $border-color; + } + + // ==================== 撤销确认弹窗 ==================== + &__rollback-confirm-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: $overlay-bg; + z-index: 3000; + display: flex; + align-items: center; + justify-content: center; + } + + &__rollback-confirm { + background: $bg-white; + border-radius: 0.12rem; + padding: 0.32rem 0.36rem; + min-width: 3.2rem; + text-align: center; + box-shadow: 0 0.08rem 0.24rem rgba(0, 0, 0, 0.15); + } + + &__rollback-confirm-text { + font-size: 0.15rem; + color: $text-dark; + margin: 0 0 0.28rem 0; + line-height: 1.5; + } + + &__rollback-confirm-actions { + display: flex; + gap: 0.12rem; + justify-content: center; + } + + &__rollback-confirm-cancel { + flex: 1; + height: 0.4rem; + background: $bg-white; + border: 1px solid $border-color; + border-radius: 0.2rem; + font-size: 0.14rem; + color: $text-dark; + cursor: pointer; + transition: all 0.2s; + + &:hover { + background: $bg-main; + } + } + + &__rollback-confirm-ok { + flex: 1; + height: 0.4rem; + background: $btn-dark; + border: none; + border-radius: 0.2rem; + font-size: 0.14rem; + color: $bg-white; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: $btn-dark-hover; + } + } + // AI输入框区域 &__ai-input-area { display: flex; @@ -1002,12 +1129,13 @@ height: 0.16rem; } - // 编辑tab占位 + // 编辑tab内容区 &__preview-edit { flex: 1; display: flex; - align-items: center; - justify-content: center; + flex-direction: column; + min-height: 0; + overflow: hidden; } // ==================== 步骤四:底部按钮区 ==================== diff --git a/src/assets/styles/components/job-resume-custom-edit-panel.scss b/src/assets/styles/components/job-resume-custom-edit-panel.scss new file mode 100644 index 0000000..7beccf6 --- /dev/null +++ b/src/assets/styles/components/job-resume-custom-edit-panel.scss @@ -0,0 +1,366 @@ +@use '../variables' as *; +@use 'sass:color'; + +// ==================== 定制简历编辑面板(折叠手风琴式) ==================== +.job-resume-edit { + font-size: 14px; + line-height: 1.5; + overflow-y: auto; + flex: 1; + padding: 0.12rem 0; + + // 单个折叠模块 + &__section { + margin-bottom: 0.08rem; + } + + // 模块标题栏(可点击展开/收起) + &__section-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.14rem 0.16rem; + background: $bg-main; + border-radius: 0.08rem; + cursor: pointer; + transition: background 0.2s; + user-select: none; + + &:hover { + background: color.adjust($bg-main, $lightness: -2%); + } + } + + &__section-title { + font-size: 0.14rem; + font-weight: 600; + color: $text-dark; + } + + // 展开/收起箭头 + &__section-arrow { + width: 0.14rem; + height: 0.14rem; + color: $text-middle; + transition: transform 0.25s ease; + flex-shrink: 0; + } + + &__section-arrow--open { + transform: rotate(180deg); + } + + // 模块内容区(展开时显示) + &__section-body { + padding: 0.16rem 0.12rem 0.08rem; + } + + // 字段容器 + &__field { + margin-bottom: 0.14rem; + } + + // 字段标签 + &__label { + display: block; + font-size: 0.12rem; + font-weight: 600; + color: $text-dark; + margin-bottom: 0.04rem; + } + + // 输入框 + &__input { + width: 100%; + box-sizing: border-box; + padding: 0.08rem 0.12rem; + font-size: 0.12rem; + color: $text-dark; + background: $bg-main; + border: 1px solid transparent; + border-radius: 0.06rem; + outline: none; + transition: all 0.2s; + + &::placeholder { + color: $text-light; + } + + &:focus { + border-color: $accent; + background: $bg-white; + } + } + + // 多行文本 + &__textarea { + width: 100%; + box-sizing: border-box; + padding: 0.08rem 0.12rem; + font-size: 0.12rem; + color: $text-dark; + background: $bg-main; + border: 1px solid transparent; + border-radius: 0.06rem; + outline: none; + transition: all 0.2s; + resize: vertical; + font-family: inherit; + line-height: 1.6; + + &::placeholder { + color: $text-light; + } + + &:focus { + border-color: $accent; + background: $bg-white; + } + + // 带删除按钮时右侧留空 + &--with-remove { + padding-right: 0.3rem; + } + } + + // 横排字段 + &__row { + display: flex; + gap: 0.1rem; + } + + &__field--half { + flex: 1; + min-width: 0; + } + + // 经历卡片 + &__card { + padding-bottom: 0.14rem; + margin-bottom: 0.14rem; + border-bottom: 1px solid $border-color; + + &:last-of-type { + border-bottom: none; + } + } + + // 经历卡片头部 + &__card-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.1rem; + } + + &__card-index { + display: flex; + align-items: center; + gap: 0.04rem; + font-size: 0.12rem; + font-weight: 600; + color: $text-dark; + } + + &__card-index-dot { + width: 0.08rem; + height: 0.08rem; + color: $accent; + } + + // 删除按钮 + &__card-delete { + background: none; + border: none; + padding: 0.03rem; + cursor: pointer; + color: $text-light; + display: flex; + align-items: center; + border-radius: 0.04rem; + transition: all 0.2s; + + &:hover { + color: $danger; + background: rgba($danger, 0.06); + } + } + + &__card-delete-icon { + width: 0.13rem; + height: 0.13rem; + } + + // 描述段落容器 + &__desc-item { + position: relative; + margin-bottom: 0.06rem; + } + + // 描述段落删除按钮 + &__desc-remove { + position: absolute; + right: 0.06rem; + top: 0.06rem; + background: none; + border: none; + padding: 0.02rem; + cursor: pointer; + color: $text-light; + display: flex; + align-items: center; + border-radius: 0.03rem; + transition: all 0.2s; + + &:hover { + color: $danger; + } + } + + &__desc-remove-icon { + width: 0.1rem; + height: 0.1rem; + } + + // 新增按钮 + &__add-btn { + text-align: center; + background: none; + border: 1px solid $border-color; + border-radius: 0.16rem; + padding: 0.04rem 0.12rem; + font-size: 0.11rem; + color: $text-dark; + cursor: pointer; + transition: all 0.2s; + + &:hover { + border-color: $accent; + color: $accent; + } + } + + &__add-btn--full { + width: 100%; + margin-top: 0.06rem; + margin-bottom: 0.1rem; + } + + &__add-icon { + font-size: 0.12rem; + font-weight: 600; + } + + // 技能/证书标签列表 + &__tags { + display: flex; + flex-wrap: wrap; + gap: 0.06rem; + margin-bottom: 0.1rem; + min-height: 0.3rem; + } + + &__tag { + display: inline-flex; + align-items: center; + gap: 0.04rem; + padding: 0.04rem 0.08rem 0.04rem 0.1rem; + background: $bg-main; + border-radius: 0.12rem; + font-size: 0.11rem; + color: $text-dark; + transition: all 0.2s; + + &:hover { + background: color.adjust($bg-main, $lightness: -3%); + } + } + + &__tag-remove { + background: none; + border: none; + padding: 0.01rem; + cursor: pointer; + color: $text-light; + display: flex; + align-items: center; + border-radius: 50%; + transition: all 0.2s; + + &:hover { + color: $danger; + } + } + + &__tag-remove-icon { + width: 0.1rem; + height: 0.1rem; + } + + // 日期选择器样式覆盖 + &__date-picker { + width: 100% !important; + + .el-input__wrapper { + background: $bg-main !important; + border: 1px solid transparent !important; + border-radius: 6px !important; + box-shadow: none !important; + padding: 3px 10px !important; + font-size: 12px !important; + line-height: 1.5 !important; + height: auto !important; + + &:hover { + border-color: $border-color !important; + } + + &.is-focus { + border-color: $accent !important; + background: $bg-white !important; + } + } + + .el-input__inner { + font-size: 12px !important; + color: $text-dark !important; + height: auto !important; + line-height: 1.5 !important; + + &::placeholder { + color: $text-light !important; + } + } + + .el-input__prefix, + .el-input__suffix { + font-size: 12px !important; + } + + .el-input__icon { + width: 12px !important; + height: 12px !important; + font-size: 12px !important; + } + } + + // 下拉选择器 + &__select { + width: 100%; + box-sizing: border-box; + padding: 0.08rem 0.12rem; + font-size: 0.12rem; + color: $text-dark; + background: $bg-main; + border: 1px solid transparent; + border-radius: 0.06rem; + outline: none; + transition: all 0.2s; + cursor: pointer; + + &:focus { + border-color: $accent; + background: $bg-white; + } + } +} diff --git a/src/assets/styles/components/job-resume-template.scss b/src/assets/styles/components/job-resume-template.scss index 3cc8a68..74890ef 100644 --- a/src/assets/styles/components/job-resume-template.scss +++ b/src/assets/styles/components/job-resume-template.scss @@ -146,4 +146,12 @@ &__skill-label { font-weight: 600; } + + // ==================== 差异对比高亮样式 ==================== + &__diff-highlight { + background-color: #D4EDDA; + color: #155724; + border-radius: 2px; + padding: 0 1px; + } } diff --git a/src/assets/styles/components/resume-analysis-report-drawer.scss b/src/assets/styles/components/resume-analysis-report-drawer.scss new file mode 100644 index 0000000..b818cd9 --- /dev/null +++ b/src/assets/styles/components/resume-analysis-report-drawer.scss @@ -0,0 +1,278 @@ +@use '../variables' as *; + +// ==================== 简历分析报告抽屉 ==================== + +// 遮罩层 +.report-drawer-overlay { + position: fixed; + inset: 0; + background: $overlay-bg; + z-index: 2000; +} + +// 遮罩层过渡动画 +.report-drawer-overlay-enter-active, +.report-drawer-overlay-leave-active { + transition: opacity 0.3s ease; +} +.report-drawer-overlay-enter-from, +.report-drawer-overlay-leave-to { + opacity: 0; +} + +// 抽屉主体 +.report-drawer { + position: fixed; + top: 0; + right: 0; + width: 8rem; + height: 100vh; + background: $bg-main; + z-index: 2001; + display: flex; + flex-direction: column; + box-shadow: -4px 0 24px rgba(0, 0, 0, 0.08); + font-size: 0.14rem; + + // ---- 顶部栏 ---- + &__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.16rem 0.24rem; + border-bottom: 1px solid $border-color; + background: $bg-white; + flex-shrink: 0; + } + + &__header-left { + display: flex; + align-items: center; + gap: 0.1rem; + } + + &__close-btn { + width: 0.28rem; + height: 0.28rem; + border-radius: 50%; + border: none; + background: none; + color: $text-dark; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + + &:hover { + background: $bg-main; + color: $accent; + } + } + + &__close-icon { + width: 0.16rem; + height: 0.16rem; + } + + &__title { + font-size: 0.16rem; + font-weight: 700; + color: $text-dark; + margin: 0; + } + + &__update-time { + font-size: 0.12rem; + color: $text-light; + } + + // ---- 可滚动内容区 ---- + &__body { + flex: 1; + overflow-y: auto; + padding: 0.2rem 0.24rem; + } + + // ---- 评分概览 ---- + &__score-section { + display: flex; + align-items: center; + justify-content: space-between; + background: $bg-white; + border-radius: 0.1rem; + padding: 0.2rem 0.24rem; + } + + &__score-left { + display: flex; + align-items: center; + gap: 0.1rem; + } + + &__grade-avatar { + width: 0.48rem; + height: 0.48rem; + border-radius: 0.08rem; + display: flex; + align-items: center; + justify-content: center; + color: $text-dark; + font-size: 0.24rem; + font-weight: 700; + background: $bg-main; + flex-shrink: 0; + } + + &__grade-badge { + font-size: 0.12rem; + font-weight: 600; + color: $accent; + display: flex; + align-items: center; + gap: 0.04rem; + background: $theme-color; + padding: 0.06rem 0.12rem; + border-radius: 0.08rem; + cursor: pointer; + } + + &__grade-dot { + font-size: 0.06rem; + color: $accent; + } + + &__score-right { + display: flex; + align-items: center; + gap: 0.28rem; + } + + &__score-item { + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.02rem; + } + + &__score-num { + font-size: 0.24rem; + font-weight: 700; + color: $text-dark; + line-height: 1.2; + } + + &__score-label { + font-size: 0.1rem; + color: $text-light; + white-space: nowrap; + } + + // ---- 评级提示语 ---- + &__grade-hint { + font-size: 0.12rem; + color: $text-middle; + line-height: 1.6; + margin: 0.12rem 0 0.2rem 0; + padding: 0 0.04rem; + } + + // ---- 结论 ---- + &__conclusion { + margin-bottom: 0.24rem; + margin-top: 0.3rem; + } + + &__section-title { + font-size: 0.16rem; + font-weight: 700; + color: $text-dark; + margin: 0 0 0.12rem 0; + } + + &__conclusion-text { + font-size: 0.13rem; + color: $text-dark; + line-height: 1.8; + margin: 0; + } + + // ---- 核心问题 ---- + &__issues { + margin-bottom: 0.2rem; + } + + &__issue-card { + background: $bg-white; + border-radius: 0.1rem; + padding: 0.2rem; + margin-bottom: 0.12rem; + } + + &__issue-title { + font-size: 0.14rem; + font-weight: 700; + color: $text-dark; + margin: 0 0 0.08rem 0; + } + + &__issue-detail { + font-size: 0.13rem; + color: $text-middle; + line-height: 1.8; + margin: 0 0 0.14rem 0; + } + + &__issue-why-label { + font-size: 0.13rem; + font-weight: 600; + font-style: italic; + color: $text-dark; + margin: 0 0 0.06rem 0; + } + + &__issue-importance { + font-size: 0.13rem; + color: $text-middle; + line-height: 1.8; + margin: 0; + } + + // ---- 底部按钮 ---- + &__footer { + padding: 0.16rem 0.24rem; + display: flex; + justify-content: center; + flex-shrink: 0; + background: $bg-main; + border-top: 1px solid $border-color; + } + + &__optimize-btn { + width: 2.4rem; + padding: 0.12rem 0; + font-size: 0.15rem; + font-weight: 600; + color: $bg-white; + background: $btn-dark; + border: none; + border-radius: 0.24rem; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: $btn-dark-hover; + } + } +} + +// 抽屉滑入动画 +.report-drawer-slide-enter-active, +.report-drawer-slide-leave-active { + transition: transform 0.3s ease; +} +.report-drawer-slide-enter-from, +.report-drawer-slide-leave-to { + transform: translateX(100%); +} diff --git a/src/assets/styles/components/resume-issue-fix-drawer.scss b/src/assets/styles/components/resume-issue-fix-drawer.scss new file mode 100644 index 0000000..78433f8 --- /dev/null +++ b/src/assets/styles/components/resume-issue-fix-drawer.scss @@ -0,0 +1,322 @@ +@use '../variables' as *; + +// ==================== 简历评估问题修复抽屉 ==================== + +// 遮罩层 +.fix-drawer-overlay { + position: fixed; + inset: 0; + background: $overlay-bg; + z-index: 2000; +} + +// 遮罩层过渡动画 +.fix-drawer-overlay-enter-active, +.fix-drawer-overlay-leave-active { + transition: opacity 0.3s ease; +} +.fix-drawer-overlay-enter-from, +.fix-drawer-overlay-leave-to { + opacity: 0; +} + +// 抽屉主体 +.fix-drawer { + position: fixed; + top: 0; + right: 0; + width: 8rem; + height: 100vh; + background: $bg-main; + z-index: 2001; + display: flex; + flex-direction: column; + box-shadow: -4px 0 24px rgba(0, 0, 0, 0.08); + font-size: 0.14rem; + // ---- 顶部栏 ---- + &__header { + display: flex; + align-items: center; + gap: 0.1rem; + padding: 0.16rem 0.24rem; + border-bottom: 1px solid $border-color; + background: $bg-white; + flex-shrink: 0; + } + + &__close-btn { + width: 0.28rem; + height: 0.28rem; + border-radius: 50%; + border: none; + background: none; + color: $text-dark; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + + &:hover { + background: $bg-main; + color: $accent; + } + } + + &__close-icon { + width: 0.16rem; + height: 0.16rem; + } + + &__title { + font-size: 0.18rem; + font-weight: 700; + color: $text-dark; + margin: 0; + } + + // ---- 可滚动内容区 ---- + &__body { + flex: 1; + overflow-y: auto; + padding: 0.2rem 0.24rem 0.4rem; + } + + // ---- 通用区块 ---- + &__section { + margin-bottom: 0.2rem; + } + + &__section-title { + font-size: 0.14rem; + font-weight: 600; + color: $text-dark; + margin: 0 0 0.1rem 0; + } + + &__card { + background: $bg-white; + border-radius: 0.1rem; + padding: 0.2rem; + } + + // ---- 原文 ---- + &__original-text { + font-size: 0.13rem; + color: $text-dark; + line-height: 1.8; + margin: 0; + white-space: pre-wrap; + word-break: break-all; + } + + // ---- 问题检查 ---- + &__label { + font-size: 0.13rem; + font-weight: 700; + color: $text-dark; + margin: 0.14rem 0 0.06rem 0; + + &:first-child { + margin-top: 0; + } + } + + &__text { + font-size: 0.13rem; + color: $text-dark; + line-height: 1.8; + margin: 0; + white-space: pre-wrap; + word-break: break-all; + } + + // ---- AI 改进后的版本(差异高亮) ---- + &__diff-text { + font-size: 0.13rem; + color: $text-dark; + line-height: 1.8; + margin: 0; + white-space: pre-wrap; + word-break: break-all; + } + + // 差异高亮样式 — 品牌色背景 + &__highlight { + background-color: rgba($accent, 0.2); + border-radius: 2px; + padding: 0 1px; + } + + // ---- 自己改写 ---- + &__textarea { + width: 100%; + min-height: 0.5rem; + font-size: 0.13rem; + color: $text-dark; + line-height: 1.8; + border: none; + border-bottom: 1px solid $border-color; + outline: none; + resize: none; + overflow: hidden; + background: transparent; + font-family: inherit; + box-sizing: border-box; + padding: 0.1rem 0; + // 让 textarea 根据内容自动撑高 + field-sizing: content; + + &:last-of-type { + border-bottom: none; + } + + &::placeholder { + color: $text-light; + } + } + + &__textarea-footer { + display: flex; + justify-content: flex-end; + margin-top: 0.1rem; + } + + &__ai-btn { + font-size: 0.13rem; + font-weight: 500; + color: $bg-white; + background: $btn-dark; + border: none; + border-radius: 0.16rem; + padding: 0.06rem 0.2rem; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: $btn-dark-hover; + } + } + + // ---- AI 润色结果 ---- + &__polish-text { + font-size: 0.13rem; + color: $text-dark; + line-height: 1.8; + margin: 0; + padding: 0.08rem 0; + border-bottom: 1px solid $border-color; + white-space: pre-wrap; + word-break: break-all; + + &:last-of-type { + border-bottom: none; + } + } + + &__replace-btn { + font-size: 0.13rem; + font-weight: 500; + color: $bg-white; + background: $btn-dark; + border: none; + border-radius: 0.16rem; + padding: 0.06rem 0.2rem; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: $btn-dark-hover; + } + } + + // ---- 评价卡片(始终显示,独立盒子) ---- + &__feedback-card { + display: flex; + align-items: center; + justify-content: space-between; + } + + &__feedback-label { + font-size: 0.12rem; + color: $text-middle; + flex-shrink: 0; + } + + &__feedback-actions { + display: flex; + align-items: center; + gap: 0.1rem; + } + + &__feedback-btn { + display: flex; + align-items: center; + gap: 0.03rem; + font-size: 0.12rem; + color: $text-light; + background: none; + border: none; + cursor: pointer; + padding: 0.03rem 0.06rem; + border-radius: 0.04rem; + transition: color 0.2s; + + &:hover { + color: $text-dark; + } + + // 选中态 + &--active { + color: $accent; + } + } + + &__feedback-icon { + width: 0.14rem; + height: 0.14rem; + flex-shrink: 0; + + // 无用按钮的图标翻转朝下 + &--down { + transform: rotate(180deg); + } + } + + // ---- 底部提交按钮 ---- + &__footer { + padding: 0.16rem 0.24rem; + display: flex; + justify-content: center; + flex-shrink: 0; + background: $bg-main; + border-top: 1px solid $border-color; + } + + &__submit-btn { + width: 2.4rem; + padding: 0.12rem 0; + font-size: 0.15rem; + font-weight: 600; + color: $bg-white; + background: $btn-dark; + border: none; + border-radius: 0.24rem; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: $btn-dark-hover; + } + } +} + +// 抽屉滑入动画 +.fix-drawer-slide-enter-active, +.fix-drawer-slide-leave-active { + transition: transform 0.3s ease; +} +.fix-drawer-slide-enter-from, +.fix-drawer-slide-leave-to { + transform: translateX(100%); +} diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 62422b4..f8345c7 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -24,6 +24,9 @@ @use './components/region-selector.scss'; @use './components/job-resume-custom-dialog.scss'; @use './components/job-resume-template.scss'; +@use './components/job-resume-custom-edit-panel.scss'; +@use './components/resume-analysis-report-drawer.scss'; +@use './components/resume-issue-fix-drawer.scss'; // 全局样式(优先级最高) @use './auto.scss'; @@ -56,9 +59,33 @@ } } -// ==================== Element Plus Loading 品牌色覆盖 ==================== -.el-loading-spinner { - .circular .path { - stroke: #4FC2C9 !important; +// ==================== Element Plus Loading rem 适配修正 + 品牌色覆盖 ==================== +.el-loading-mask { + // 全屏 loading 遮罩层 z-index 确保最高 + z-index: 2100 !important; + + .el-loading-spinner { + // 修正 spinner 容器定位(默认 top: 50% + margin-top 用了 rem,会被 100px 放大) + top: 50% !important; + margin-top: -21px !important; + width: 100% !important; + + // 修正旋转圆圈尺寸 + .circular { + width: 42px !important; + height: 42px !important; + + .path { + stroke: #4FC2C9 !important; + } + } + + // 修正加载文字 + .el-loading-text { + font-size: 14px !important; + margin: 8px 0 !important; + color: #4FC2C9 !important; + line-height: 1.4 !important; + } } } diff --git a/src/assets/styles/pages/resume-detail.scss b/src/assets/styles/pages/resume-detail.scss index a35ab07..6c2158e 100644 --- a/src/assets/styles/pages/resume-detail.scss +++ b/src/assets/styles/pages/resume-detail.scss @@ -202,6 +202,18 @@ &:hover { background: $btn-dark-hover; } + + // 开始诊断按钮 — 稍大一些,更醒目 + &--start { + padding: 0.1rem 0.28rem; + font-size: 0.14rem; + border-radius: 0.08rem; + background: $gradient-bg; + + &:hover { + background: $accent-hover; + } + } } // ---- 简历主体 ---- diff --git a/src/components/JobResumeCustomDialog.vue b/src/components/JobResumeCustomDialog.vue index 1724487..9c98fd1 100644 --- a/src/components/JobResumeCustomDialog.vue +++ b/src/components/JobResumeCustomDialog.vue @@ -10,7 +10,7 @@ -
你与该岗位的匹配度较低,简历可能无法通过机筛。
+
你与该岗位的匹配度较低,简历可能无法通过机筛。
@@ -52,9 +52,9 @@

生成你的岗位专属简历

- + -
+
1差距分析
@@ -71,8 +71,8 @@ @@ -73,7 +103,15 @@
    -
  • {{ desc.text }}
  • +
  • + + +
@@ -91,7 +129,15 @@
    -
  • {{ desc.text }}
  • +
  • + + +
@@ -106,7 +152,15 @@ {{ comp.awardDate }}
    -
  • {{ desc.text }}
  • +
  • + + +
@@ -118,11 +172,27 @@
技能: - {{ resumeData.skills.join('、') }} + + {{ resumeData.skills.join('、') }}
证书: - {{ resumeData.certificates.join('、') }} + + {{ resumeData.certificates.join('、') }}
@@ -132,6 +202,7 @@ diff --git a/src/components/ResumeAnalysisReportDrawer.vue b/src/components/ResumeAnalysisReportDrawer.vue new file mode 100644 index 0000000..3500c53 --- /dev/null +++ b/src/components/ResumeAnalysisReportDrawer.vue @@ -0,0 +1,168 @@ + + + diff --git a/src/components/ResumeIssueFixDrawer.vue b/src/components/ResumeIssueFixDrawer.vue new file mode 100644 index 0000000..5b414a4 --- /dev/null +++ b/src/components/ResumeIssueFixDrawer.vue @@ -0,0 +1,405 @@ +