diff --git a/src/assets/styles/components/job-resume-custom-dialog.scss b/src/assets/styles/components/job-resume-custom-dialog.scss index b071f27..fcbe174 100644 --- a/src/assets/styles/components/job-resume-custom-dialog.scss +++ b/src/assets/styles/components/job-resume-custom-dialog.scss @@ -174,18 +174,19 @@ &__match-score { font-size: 0.18rem; font-weight: 700; - color: $text-dark; + color: #52CAD1; position: relative; z-index: 1; } &__match-label { - font-size: 0.1rem; - color: $text-middle; + font-size: 0.12rem; + color: $bg-white; margin-top: 0.04rem; - background: $bg-main; + background: $text-dark; border-radius: 0.04rem; padding: 0.02rem 0.08rem; + line-height: 0.16rem; } // 缺少技能区域 @@ -886,33 +887,38 @@ min-height: 0; } - // Tab 切换栏 + // Tab 切换栏(胶囊风格) &__preview-tabs { display: flex; - border-bottom: 1px solid $border-color; + align-items: center; + padding: 0.09rem 0.16rem; flex-shrink: 0; + gap: 0.12rem; + justify-content: space-between; } &__preview-tab { - flex: 1; - padding: 0.12rem 0; - font-size: 0.14rem; - color: $text-middle; + width: 1.48rem; + height: 0.4rem; + line-height: 0.4rem; + font-size: 0.16rem; + font-weight: 500; + color: $text-light; background: none; border: none; - border-bottom: 2px solid transparent; + border-radius: 0.28rem; cursor: pointer; transition: all 0.2s; text-align: center; &--active { - color: $text-dark; - font-weight: 600; - border-bottom-color: $text-dark; + color: $bg-white; + background: #52CAD1; } - &:hover { + &:hover:not(&--active) { color: $text-dark; + background: $bg-main; } } @@ -923,100 +929,139 @@ flex-direction: column; min-height: 0; overflow: hidden; - border-bottom: 0.01rem solid $border-color; + background: $bg-white; } - // AI优化结果卡片 - &__ai-result { + // 匹配度提升整体容器(左卡片+右圆环) + &__ai-congrats-wrap { display: flex; - align-items: flex-start; - justify-content: space-between; - padding: 0.2rem; - gap: 0.16rem; + align-items: center; + gap: 0.12rem; + padding: 0.16rem; flex-shrink: 0; } - &__ai-result-text { + // 左侧绿色边框卡片 + &__ai-congrats { flex: 1; min-width: 0; + padding: 0.16rem; + background: #F4FBFB; + border: 1px solid $accent; + border-radius: 0.12rem; } - &__ai-result-title { - font-size: 0.15rem; + &__ai-congrats-title { + font-size: 0.14rem; + font-weight: 450; + color: $text-dark; + margin: 0; + line-height: 0.26rem; + } + + // 分数加粗黑色 + &__ai-congrats-bold { font-weight: 700; - color: $text-dark; - margin: 0 0 0.12rem 0; - line-height: 1.5; + color: $accent; + font-size: 0.18rem; } - &__ai-result-detail { - margin: 0; - } - - &__ai-result-subtitle { - font-size: 0.12rem; - font-weight: 600; - color: $text-dark; - margin: 0 0 0.06rem 0; - line-height: 1.4; - } - - &__ai-result-list { - list-style: none; - margin: 0; - padding: 0; - - li { - font-size: 0.12rem; - color: $text-dark; - line-height: 1.6; - } - } - - &__ai-result-score { + // 右侧圆环+标签区域(卡片外面) + &__ai-congrats-score { display: flex; flex-direction: column; align-items: center; flex-shrink: 0; } - // 大号匹配度环 - &__match-ring--large { - width: 0.64rem; - height: 0.64rem; + // 改写说明区域(卡片外平铺) + &__ai-optimize-detail { + padding: 0 0.16rem; + flex-shrink: 0; } - // 高匹配标签 + &__ai-optimize-subtitle { + font-size: 0.14rem; + font-weight: 600; + color: $text-light; + margin: 0 0 0.12rem 0; + line-height: 0.19rem; + } + + &__ai-optimize-list { + margin: 0; + } + + &__ai-optimize-item { + font-size: 0.14rem; + font-weight: 330; + color: $text-light; + margin: 0 0 0.08rem 0; + line-height: 0.22rem; + } + + // 大号匹配度环(设计稿60x60,粗线圈) + &__match-ring--large { + width: 0.66rem; + height: 0.66rem; + } + + // 匹配度分数文字 + &__match-score { + font-size: 0.18rem; + font-weight: 700; + color: #52CAD1; + position: relative; + z-index: 1; + } + + // 高匹配标签(黑底白字圆角) &__match-label--high { background: $text-dark; color: $bg-white; - font-weight: 600; + font-size: 0.12rem; + font-weight: 400; + border-radius: 0.04rem; + padding: 0.02rem 0.08rem; + margin-top: -0.12rem; + line-height: 0.16rem; + position: relative; + z-index: 10; } - // 快捷操作按钮区 + // 快捷操作按钮区(用户消息风格,右对齐) &__ai-quick-actions { display: flex; flex-direction: column; align-items: flex-end; gap: 0.08rem; - padding: 0 0.2rem 0.16rem; + padding: 0.12rem 0.16rem; flex-shrink: 0; } + // 无左右padding的快捷操作(prl0类复写) + &__ai-quick-actions.prl0 { + padding-left: 0.16rem; + padding-right: 0.16rem; + } + &__ai-quick-btn { - background: $bg-white; - border: 1px solid $border-color; - border-radius: 0.16rem; - padding: 0.08rem 0.16rem; - font-size: 0.12rem; - color: $text-dark; + background: #52CAD1; + border: none; + border-radius: 0.12rem 0.04rem 0.12rem 0.12rem; + padding: 0.08rem 0.12rem; + font-size: 0.14rem; + font-weight: 305; + color: $bg-white; cursor: pointer; transition: all 0.2s; white-space: nowrap; + max-width: 2.38rem; + line-height: 0.24rem; + box-shadow: 0 0.01rem 0.04rem rgba(0, 0, 0, 0.04); &:hover { - background: $bg-main; - border-color: $accent; + background: #47b8bf; } } @@ -1024,7 +1069,6 @@ &__ai-messages { flex: 1; overflow-y: auto; - padding: 0 0.2rem; min-height: 0; } @@ -1041,22 +1085,29 @@ } &__ai-msg-bubble { - border-radius: 0.1rem; - padding: 0.1rem 0.14rem; - font-size: 0.12rem; - line-height: 1.6; - max-width: 85%; + border-radius: 0.12rem; + padding: 0.08rem 0.12rem; + font-size: 0.14rem; + font-weight: 330; + line-height: 0.24rem; + max-width: 2.38rem; } + // AI消息气泡(品牌色边框+阴影) &__ai-msg--ai &__ai-msg-bubble { - background: $bg-main; + background: $bg-white; color: $text-dark; - border: 1px solid $border-color; + border: 1px solid #AEE6EA; + box-shadow: 0 0.02rem 0.04rem rgba(82, 202, 209, 0.1); + border-radius: 0.04rem 0.12rem 0.12rem 0.12rem; } + // 用户消息气泡(品牌色填充) &__ai-msg--user &__ai-msg-bubble { - background: $text-dark; + background: #52CAD1; color: $bg-white; + border-radius: 0.12rem 0.04rem 0.12rem 0.12rem; + box-shadow: 0 0.01rem 0.04rem rgba(0, 0, 0, 0.04); } // AI正在思考中的加载气泡 @@ -1199,36 +1250,37 @@ &__ai-input-area { display: flex; align-items: center; + justify-content: space-between; + padding: 0.08rem 0.12rem; + margin: 0.12rem 0.16rem; gap: 0.08rem; - padding: 0.12rem 0.2rem; - border-top: 1px solid $border-color; + background: rgba(255, 255, 255, 0.95); + border: 1px solid #AEE6EA; + border-radius: 0.12rem; + backdrop-filter: blur(12px); flex-shrink: 0; } &__ai-input { flex: 1; - background: $bg-main; - border: 1px solid $border-color; - border-radius: 0.2rem; - padding: 0.1rem 0.14rem; + background: none; + border: none; + padding: 0.04rem 0; font-size: 0.12rem; color: $text-dark; outline: none; + line-height: 0.15rem; &::placeholder { - color: $text-light; - } - - &:focus { - border-color: $accent; + color: $text-middle; } } &__ai-send-btn { - width: 0.34rem; - height: 0.34rem; - border-radius: 50%; - background: $text-dark; + width: 0.32rem; + height: 0.32rem; + border-radius: 0.08rem; + background: #52CAD1; border: none; color: $bg-white; cursor: pointer; @@ -1239,13 +1291,13 @@ flex-shrink: 0; &:hover { - background: $btn-dark-hover; + background: #47b8bf; } } &__ai-send-icon { - width: 0.16rem; - height: 0.16rem; + width: 0.2rem; + height: 0.2rem; } // 编辑tab内容区 diff --git a/src/assets/styles/components/job-resume-custom-edit-panel.scss b/src/assets/styles/components/job-resume-custom-edit-panel.scss index 7beccf6..ba07ed8 100644 --- a/src/assets/styles/components/job-resume-custom-edit-panel.scss +++ b/src/assets/styles/components/job-resume-custom-edit-panel.scss @@ -156,15 +156,13 @@ &__card-index { display: flex; align-items: center; - gap: 0.04rem; - font-size: 0.12rem; - font-weight: 600; - color: $text-dark; + font-size: 0.14rem; + color: $accent; } &__card-index-dot { - width: 0.08rem; - height: 0.08rem; + width: 0.0rem; + height: 0.0rem; color: $accent; } @@ -174,7 +172,7 @@ border: none; padding: 0.03rem; cursor: pointer; - color: $text-light; + color: $accent; display: flex; align-items: center; border-radius: 0.04rem; diff --git a/src/assets/styles/components/job-resume-template.scss b/src/assets/styles/components/job-resume-template.scss index 8f879bd..14eb36f 100644 --- a/src/assets/styles/components/job-resume-template.scss +++ b/src/assets/styles/components/job-resume-template.scss @@ -12,13 +12,15 @@ box-sizing: border-box; } -// 简历HTML内容区 +// 简历HTML内容区(默认预览模式用小padding,导出模式用大padding) .resume-html { - padding: 50px 60px; + padding: 10px; font-family: 'SimSun', 'Songti SC', serif; color: $text-dark; line-height: 1.6; + // 导出模式:恢复完整内边距,去除差异样式 → 见底部统一 &--export 块 + // 姓名 &__name { font-size: 28px; @@ -150,9 +152,30 @@ // ==================== 差异对比高亮样式 ==================== &__diff-highlight { - background-color: #D4EDDA; - color: #155724; - border-radius: 2px; - padding: 0 1px; + font-weight: 700; + color: #52CAD1; + text-decoration: underline; + text-decoration-color: #52CAD1; + text-underline-offset: 2px; + } + + // 含有差异文字的经历块/个人概述背景色(仅预览模式) + &__diff-block { + background-color: #EDF9FA; + } + + // 导出模式下去除差异相关样式 + &--export { + padding: 50px 60px; + + .resume-html__diff-highlight { + font-weight: inherit; + color: inherit; + text-decoration: none; + } + + .resume-html__diff-block { + background-color: transparent; + } } } diff --git a/src/assets/styles/variables.scss b/src/assets/styles/variables.scss index 60bd7b7..e8650b2 100644 --- a/src/assets/styles/variables.scss +++ b/src/assets/styles/variables.scss @@ -21,18 +21,18 @@ $bg-white: #FFFFFF; $text-dark: #1A1A1A; // 浅色文字(副标题、占位符、辅助信息) -$text-light: #BFBFBF; +$text-light: #99A1AF; // 中间色文字(使用优先级高于浅色文字,稍微要深色一点的副标题、占位符、辅助信息) $text-middle: #6A7282; // 强调色 / 品牌色 //$accent: #4FC2C9; -//$accent: #52CAD1; -$accent: #12C7BE; +//$accent: #12C7BE; +$accent: #52CAD1; // 强调色悬停态 -$accent-hover: #11b5ac; +$accent-hover: #42A8B3; // 危险色 / 警告色(删除、错误等) $danger: #E85635; @@ -44,10 +44,10 @@ $border-color: #E8E8E8; $overlay-bg: rgba(0, 0, 0, 0.5); // 主按钮颜色背景(确认提交等) -$btn-dark: #12C7BE; +$btn-dark: #52CAD1; // 主按钮颜色悬停态 -$btn-dark-hover: #11b5ac; +$btn-dark-hover: #42A8B3; // 次要深色按钮背景(除非特意指定使用否则不用这个按钮色) //$btn-dark: #1A1A2E; @@ -56,5 +56,5 @@ $btn-dark-hover: #11b5ac; //$btn-dark-hover: #2E3142; // 渐变色背景 -//$gradient-bg: linear-gradient(to right, #52CAD1, #42A8B3); -$gradient-bg: linear-gradient(to right, #12C7BE, #11b5ac); +$gradient-bg: linear-gradient(to right, #52CAD1, #42A8B3); +//$gradient-bg: linear-gradient(to right, #12C7BE, #11b5ac); diff --git a/src/components/JobResumeCustomDialog.vue b/src/components/JobResumeCustomDialog.vue index 034f9db..0256813 100644 --- a/src/components/JobResumeCustomDialog.vue +++ b/src/components/JobResumeCustomDialog.vue @@ -13,7 +13,7 @@

生成你的岗位专属简历

-
+
@@ -207,29 +208,32 @@
-
-
-

恭喜!你的简历匹配值从
{{ jobInfo.matchScore }}分提升到了{{ cachedOptimizedScore }}分!

-
-

做了哪些优化?

-
    -
  • ·{{ item }}
  • -
-
+
+ +
+

🎉 恭喜!你的简历匹配值
{{ jobInfo.matchScore }}分 提升到了 {{ cachedOptimizedScore }}分

-
+ +
- - + + {{ cachedOptimizedScore.toFixed(1) }}
{{ cachedOptimizedScore >= 9 ? '非常匹配' : cachedOptimizedScore >= 6 ? '高匹配度' : '低匹配度' }}
+ +
+

改写说明·共{{ aiOptimizeResults.length }}处

+
+

{{ i + 1 }}、{{ item }}

+
+
-
+
@@ -736,6 +747,9 @@ function selectResume(r: ResumeOption) { /** 简历模板组件引用 */ const resumeTemplateRef = ref() +/** 简历模板模式:preview=预览,export=导出 */ +const resumeTemplateMode = ref<'preview' | 'export'>('preview') + /** 简历模板数据 */ const resumeTemplateData = ref({ name: '', @@ -1013,9 +1027,14 @@ function toggleDownloadMenu() { async function handleDownload(type: 'pdf' | 'word') { showDownloadMenu.value = false + // 切换到导出模式(大padding) + resumeTemplateMode.value = 'export' + await nextTick() + const element = resumeTemplateRef.value?.resumeRef if (!element) { console.error('[下载简历] 无法获取简历模板DOM') + resumeTemplateMode.value = 'preview' return } @@ -1029,6 +1048,9 @@ async function handleDownload(type: 'pdf' | 'word') { } } catch (err) { console.error('[下载简历] 导出失败', err) + } finally { + // 导出完成后恢复预览模式 + resumeTemplateMode.value = 'preview' } } diff --git a/src/components/JobResumeCustomEditPanel.vue b/src/components/JobResumeCustomEditPanel.vue index 2704c7b..a29f054 100644 --- a/src/components/JobResumeCustomEditPanel.vue +++ b/src/components/JobResumeCustomEditPanel.vue @@ -34,7 +34,12 @@
教育经历{{ idx + 1 }} - +
@@ -48,11 +53,7 @@
-
- - -
- +
@@ -63,7 +64,12 @@
实习经历{{ idx + 1 }} - +
@@ -73,11 +79,7 @@
-
- - -
- +
@@ -88,7 +90,12 @@
工作经历{{ idx + 1 }} - +
@@ -98,11 +105,7 @@
-
- - -
- +
@@ -113,7 +116,12 @@
项目经历{{ idx + 1 }} - +
@@ -124,11 +132,7 @@
-
- - -
- +
@@ -138,8 +142,14 @@ - - -
@@ -269,14 +272,13 @@ const sectionList = [ { key: 'competition', label: '竞赛' }, { key: 'certificate', label: '证书' }, { key: 'skills', label: '技能' }, - { key: 'portfolio', label: '作品集' }, ] /** 各模块展开状态 */ const openSections = reactive>({ info: false, summary: false, education: false, internship: false, work: false, project: false, competition: false, certificate: false, - skills: false, portfolio: false, + skills: false, }) /** 切换模块展开/收起 */ @@ -347,9 +349,45 @@ function initEditData(data: CustomizeResumeData) { })) } +// ==================== 描述文本映射(合并编辑用) ==================== + +/** 每个模块每条经历的描述文本(将description数组用换行拼成单个字符串) */ +const descTextMap = reactive>({ + education: [], + work: [], + internship: [], + project: [], +}) + +/** 从 editData 的 description 数组生成合并文本 */ +function buildDescTextMap() { + descTextMap.education = editData.education.map(e => e.description.map(d => d.text).join('\n')) + descTextMap.work = editData.work.map(w => w.description.map(d => d.text).join('\n')) + descTextMap.internship = editData.internship.map(i => i.description.map(d => d.text).join('\n')) + descTextMap.project = editData.project.map(p => p.description.map(d => d.text).join('\n')) +} + +/** 将文本框内容按换行拆分回 description 数组,并触发自动保存 */ +function syncDescFromText(type: 'education' | 'work' | 'internship' | 'project', itemIdx: number) { + const text = descTextMap[type][itemIdx] || '' + const lines = text.split('\n').filter(s => s.trim()) + const oldDesc = editData[type][itemIdx].description + // 保留原有id,超出的生成新id + const newDesc: DescParagraph[] = lines.map((line, i) => ({ + id: i < oldDesc.length ? oldDesc[i].id : genId(), + text: line, + })) + // 如果全部为空则保留一个空描述 + editData[type][itemIdx].description = newDesc.length ? newDesc : [{ id: genId(), text: '' }] + autoSave() +} + /** 监听 props 变化,初始化编辑数据 */ watch(() => props.resumeData, (val) => { - if (val) initEditData(val) + if (val) { + initEditData(val) + buildDescTextMap() + } }, { immediate: true, deep: true }) // ==================== 自动保存(失焦/选择后触发PUT接口) ==================== @@ -360,7 +398,9 @@ let saveTimer: ReturnType | null = null /** 将编辑数据转换为接口所需的 CustomizeResumeData 格式 */ function buildSavePayload(): CustomizeResumeData { return { + // 主表字段:以原始数据为基础,覆盖编辑数据,确保不丢失任何字段 resume: { + ...(props.resumeData?.resume || {}), avatarUrl: editData.resume.avatarUrl, name: editData.resume.name, email: editData.resume.email, @@ -423,12 +463,16 @@ function addItem(type: 'education' | 'work' | 'internship' | 'project' | 'compet const emptyDesc: DescParagraph[] = [{ id: genId(), text: '' }] if (type === 'education') { editData.education.push({ id, school: '', major: '', degree: '本科', studyType: '全日制', startDate: '', endDate: '', description: emptyDesc }) + descTextMap.education.push('') } else if (type === 'work') { editData.work.push({ id, companyName: '', position: '', startDate: '', endDate: '', description: emptyDesc }) + descTextMap.work.push('') } else if (type === 'internship') { editData.internship.push({ id, companyName: '', position: '', startDate: '', endDate: '', description: emptyDesc }) + descTextMap.internship.push('') } else if (type === 'project') { editData.project.push({ id, companyName: '', projectName: '', role: '', startDate: '', endDate: '', description: emptyDesc }) + descTextMap.project.push('') } else if (type === 'competition') { editData.competition.push({ id, competitionName: '', award: '', awardDate: '', description: emptyDesc }) } @@ -438,17 +482,10 @@ function addItem(type: 'education' | 'work' | 'internship' | 'project' | 'compet /** 删除一条经历记录 */ function removeItem(type: 'education' | 'work' | 'internship' | 'project' | 'competition', idx: number) { editData[type].splice(idx, 1) - autoSave() -} - -/** 新增一条描述段落 */ -function addDesc(type: 'education' | 'work' | 'internship' | 'project', itemIdx: number) { - editData[type][itemIdx].description.push({ id: genId(), text: '' }) -} - -/** 删除一条描述段落 */ -function removeDesc(type: 'education' | 'work' | 'internship' | 'project', itemIdx: number, descIdx: number) { - editData[type][itemIdx].description.splice(descIdx, 1) + // 同步更新描述文本映射 + if (type !== 'competition' && descTextMap[type]) { + descTextMap[type].splice(idx, 1) + } autoSave() } diff --git a/src/components/JobResumeTemplate.vue b/src/components/JobResumeTemplate.vue index e102172..3728492 100644 --- a/src/components/JobResumeTemplate.vue +++ b/src/components/JobResumeTemplate.vue @@ -1,7 +1,7 @@