新版交互调整
This commit is contained in:
@@ -21,6 +21,8 @@ export interface AgentConfig {
|
||||
weeklyTarget?: number
|
||||
/** 投递时自动针对岗位优化简历 0=关闭 1=开启 */
|
||||
autoOptimizeResume?: number
|
||||
/** 自动匹配分析 0=关闭 1=开启 */
|
||||
autoMatchAnalysis?: number
|
||||
/** 是否愿意接受部门调剂 */
|
||||
acceptDeptTransfer?: string
|
||||
/** 是否接受地点调剂 */
|
||||
|
||||
@@ -303,6 +303,8 @@ export interface JobDetailData {
|
||||
salary: string
|
||||
/** 工作类型 0=全职 1=兼职 */
|
||||
employmentType: number
|
||||
/** 招聘分类 0=社招 1=校招 2=实习 3=其他 */
|
||||
recruitCategory: number
|
||||
/** 学历要求 0=不限 1=大专 2=本科 3=硕士 4=博士 */
|
||||
education: number
|
||||
/** 最低工作年限 */
|
||||
|
||||
+15
-6
@@ -531,6 +531,16 @@ export function deleteResume(resumeId: string) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设为默认简历
|
||||
* POST /resume/setDefault
|
||||
*/
|
||||
export function setDefaultResume(resumeId: string) {
|
||||
return request.post<any, ApiResult>('/resume/setDefault', null, {
|
||||
params: { resumeId },
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 简历诊断相关 ====================
|
||||
|
||||
/** 诊断报告数据 */
|
||||
@@ -644,13 +654,12 @@ export interface PolishIssueData {
|
||||
}
|
||||
|
||||
/**
|
||||
* 对诊断问题进行 AI 润色(AI 接口)
|
||||
* POST /resume/diagnose/issue/{issue_id}/polish
|
||||
* @param issueId 问题 ID
|
||||
* @param content 用户编辑后的文本段落数组
|
||||
* AI 润色简历段落(Python 后端接口)
|
||||
* POST /resume/polish
|
||||
* @param content 待润色的简历段落文本数组
|
||||
*/
|
||||
export function polishDiagnosisIssue(issueId: string, content: string[]) {
|
||||
return aiService.post<AiResult<PolishIssueData>>(`/resume/diagnose/issue/${issueId}/polish`, { content }, {
|
||||
export function polishDiagnosisIssue(content: string[]) {
|
||||
return aiService.post<AiResult<PolishIssueData>>('/resume/polish', { content }, {
|
||||
transformResponse: [(data: string) => {
|
||||
try {
|
||||
const processed = data.replace(/:\s*(\d{16,})/g, ':"$1"')
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 134 KiB |
@@ -1,19 +1,26 @@
|
||||
@use '../variables' as *;
|
||||
|
||||
// ==================== Agent会话岗位列表组件样式 ====================
|
||||
// ==================== Agent推荐岗位列表组件样式 ====================
|
||||
.agent-chat-job-list {
|
||||
background: $bg-middle;
|
||||
border-radius: 0.12rem;
|
||||
padding: 0.2rem;
|
||||
margin-top: 0.16rem;
|
||||
padding: 0.24rem;
|
||||
font-size: 0.14rem;
|
||||
|
||||
// 标题
|
||||
&__header {
|
||||
font-size: 0.18rem;
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
margin-bottom: 0.06rem;
|
||||
}
|
||||
|
||||
// 推荐说明文字
|
||||
&__summary {
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.14rem;
|
||||
line-height: 1.6;
|
||||
color: $text-middle;
|
||||
margin-bottom: 0.18rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
// 单个岗位项
|
||||
@@ -23,68 +30,82 @@
|
||||
justify-content: space-between;
|
||||
background: $bg-white;
|
||||
border-radius: 0.1rem;
|
||||
padding: 0.14rem 0.16rem;
|
||||
margin-bottom: 0.08rem;
|
||||
cursor: pointer;
|
||||
padding: 0.16rem 0.2rem;
|
||||
margin-bottom: 0.1rem;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 左侧信息区域
|
||||
// 左侧信息区域(可点击)
|
||||
&__info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.12rem;
|
||||
gap: 0.14rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// 公司 Logo
|
||||
&__logo {
|
||||
width: 0.36rem;
|
||||
height: 0.36rem;
|
||||
border-radius: 0.06rem;
|
||||
background: $bg-main;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// 匹配度环形容器
|
||||
&__score {
|
||||
position: relative;
|
||||
width: 0.48rem;
|
||||
height: 0.48rem;
|
||||
flex-shrink: 0;
|
||||
color: $text-middle;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
// 环形 SVG
|
||||
&__ring {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 0.2rem;
|
||||
height: 0.2rem;
|
||||
}
|
||||
// 匹配度数字
|
||||
&__score-text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 0.11rem;
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
// 岗位详情
|
||||
&__detail {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
// 公司名称
|
||||
&__company {
|
||||
font-size: 0.14rem;
|
||||
// 岗位标题行(岗位名 + 公司名)
|
||||
&__title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.08rem;
|
||||
margin-bottom: 0.04rem;
|
||||
}
|
||||
|
||||
// 岗位名称
|
||||
&__title {
|
||||
font-size: 0.15rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
// 岗位名称
|
||||
&__title {
|
||||
font-size: 0.12rem;
|
||||
// 公司名前的圆点
|
||||
&__company-dot {
|
||||
font-size: 0.08rem;
|
||||
color: $text-light;
|
||||
}
|
||||
|
||||
// 公司名称
|
||||
&__company {
|
||||
font-size: 0.13rem;
|
||||
color: $text-middle;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 0.04rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 标签行
|
||||
@@ -103,55 +124,109 @@
|
||||
border-radius: 0.03rem;
|
||||
}
|
||||
|
||||
// 匹配度环形容器
|
||||
&__score {
|
||||
position: relative;
|
||||
width: 0.44rem;
|
||||
height: 0.44rem;
|
||||
// 右侧操作按钮区域
|
||||
&__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.14rem;
|
||||
flex-shrink: 0;
|
||||
margin-left: 0.12rem;
|
||||
margin-left: 0.16rem;
|
||||
}
|
||||
|
||||
// 环形 SVG
|
||||
&__ring {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// 不再推荐按钮
|
||||
&__dismiss-btn {
|
||||
font-size: 0.12rem;
|
||||
color: $text-light;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: $text-middle;
|
||||
}
|
||||
}
|
||||
|
||||
// 匹配度数字
|
||||
&__score-text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 0.11rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
// 加入投递按钮
|
||||
&__add-btn {
|
||||
height: 0.32rem;
|
||||
padding: 0 0.16rem;
|
||||
background: $accent;
|
||||
color: $bg-white;
|
||||
border: none;
|
||||
border-radius: 0.16rem;
|
||||
font-size: 0.12rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s ease;
|
||||
line-height: 0.32rem;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
// 已加入状态
|
||||
&--added {
|
||||
background: $bg-main;
|
||||
color: $text-middle;
|
||||
border: 1px solid $border-color;
|
||||
}
|
||||
}
|
||||
|
||||
// 底部按钮区域
|
||||
&__footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 0.14rem;
|
||||
gap: 0.16rem;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
// 查看全部岗位按钮
|
||||
&__view-all-btn {
|
||||
height: 0.38rem;
|
||||
padding: 0 0.32rem;
|
||||
background: $btn-dark;
|
||||
color: $bg-white;
|
||||
border: none;
|
||||
border-radius: 0.19rem;
|
||||
font-size: 0.13rem;
|
||||
// 换一批按钮
|
||||
&__refresh-btn {
|
||||
height: 0.4rem;
|
||||
padding: 0 0.4rem;
|
||||
background: $bg-white;
|
||||
color: $text-dark;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.2rem;
|
||||
font-size: 0.14rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s ease;
|
||||
line-height: 0.38rem;
|
||||
transition: all 0.2s ease;
|
||||
line-height: 0.4rem;
|
||||
|
||||
&:hover {
|
||||
background: $btn-dark-hover;
|
||||
border-color: $accent;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
// 全部添加按钮
|
||||
&__add-all-btn {
|
||||
height: 0.4rem;
|
||||
padding: 0 0.4rem;
|
||||
background: $accent;
|
||||
color: $bg-white;
|
||||
border: none;
|
||||
border-radius: 0.2rem;
|
||||
font-size: 0.14rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
line-height: 0.4rem;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&:active {
|
||||
|
||||
@@ -1,326 +1,185 @@
|
||||
@use '../variables' as *;
|
||||
|
||||
/* Agent设置面板样式 */
|
||||
// ==================== Agent设置面板样式(精简版) ====================
|
||||
.agent-setting-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: $bg-white;
|
||||
border-radius: 0.2rem;
|
||||
overflow: hidden;
|
||||
background: $bg-middle;
|
||||
border-radius: 0.12rem;
|
||||
font-size: 0.14rem;
|
||||
overflow-y: auto;
|
||||
|
||||
/* 顶部标题栏 */
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.16rem 0.2rem;
|
||||
border-bottom: 1px solid $border-color;
|
||||
// 隐藏滚动条
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
&::-webkit-scrollbar { display: none; }
|
||||
|
||||
// 内容区域
|
||||
&__body {
|
||||
padding: 0.28rem 0.24rem;
|
||||
}
|
||||
|
||||
&__title {
|
||||
// 每个设置区块
|
||||
&__section {
|
||||
margin-bottom: 0.28rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 区块头部(标题 + 编辑按钮)
|
||||
&__section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.1rem;
|
||||
margin-bottom: 0.12rem;
|
||||
}
|
||||
|
||||
// 区块标题
|
||||
&__section-title {
|
||||
font-size: 0.16rem;
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
/* 关闭按钮 */
|
||||
&__close-btn {
|
||||
width: 0.28rem;
|
||||
height: 0.28rem;
|
||||
border: none;
|
||||
background: none;
|
||||
// 编辑按钮
|
||||
&__edit-btn {
|
||||
font-size: 0.13rem;
|
||||
color: $accent;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
// 个人资料信息
|
||||
&__profile-info {
|
||||
padding-left: 0.02rem;
|
||||
}
|
||||
|
||||
&__profile-name {
|
||||
font-size: 0.18rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
margin-bottom: 0.04rem;
|
||||
}
|
||||
|
||||
&__profile-sub {
|
||||
font-size: 0.13rem;
|
||||
color: $text-middle;
|
||||
}
|
||||
|
||||
// 求职目标标签
|
||||
&__goal-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.08rem;
|
||||
}
|
||||
|
||||
&__goal-tag {
|
||||
padding: 0.06rem 0.14rem;
|
||||
border: 1px solid $accent;
|
||||
border-radius: 0.16rem;
|
||||
font-size: 0.13rem;
|
||||
color: $accent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
// 空状态文字
|
||||
&__empty-text {
|
||||
font-size: 0.13rem;
|
||||
color: $text-light;
|
||||
}
|
||||
|
||||
// 简历选择下拉
|
||||
&__resume-select {
|
||||
width: 100%;
|
||||
margin-top: 0.06rem;
|
||||
|
||||
.el-input__wrapper {
|
||||
border-radius: 0.08rem;
|
||||
}
|
||||
}
|
||||
|
||||
// 投递偏好项
|
||||
&__pref-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
padding: 0.12rem 0;
|
||||
|
||||
& + & {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__pref-left {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.1rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
// 勾选图标
|
||||
&__pref-check {
|
||||
width: 0.2rem;
|
||||
height: 0.2rem;
|
||||
flex-shrink: 0;
|
||||
color: $text-light;
|
||||
margin-top: 0.02rem;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 内容区域(可滚动) */
|
||||
&__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
/* ========== 设置项区块 ========== */
|
||||
&__section {
|
||||
margin-bottom: 0.2rem;
|
||||
border-bottom: 1px solid $border-color;
|
||||
padding-bottom: 0.16rem;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
&--active {
|
||||
color: $text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
/* 区块标题行 */
|
||||
&__section-header {
|
||||
// 偏好文字
|
||||
&__pref-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
padding: 0.04rem 0;
|
||||
flex-direction: column;
|
||||
gap: 0.03rem;
|
||||
}
|
||||
|
||||
/* 区块标题 */
|
||||
&__section-title {
|
||||
font-size: 0.18rem;
|
||||
font-weight: 600;
|
||||
&__pref-label {
|
||||
font-size: 0.14rem;
|
||||
font-weight: 500;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
/* 编辑按钮区域(文字 + 箭头) */
|
||||
&__section-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.04rem;
|
||||
}
|
||||
|
||||
&__section-action-text {
|
||||
&__pref-desc {
|
||||
font-size: 0.12rem;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
/* 方向箭头 */
|
||||
&__arrow {
|
||||
width: 0.14rem;
|
||||
height: 0.14rem;
|
||||
color: $accent;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
/* 展开时箭头朝上 */
|
||||
&__arrow--up {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* 收起时的简要信息预览 */
|
||||
&__section-summary {
|
||||
margin-top: 0.1rem;
|
||||
font-size: 0.12rem;
|
||||
color: $text-middle;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
&__section-empty {
|
||||
color: $text-light;
|
||||
font-style: italic;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 展开时的内容区域 */
|
||||
&__section-content {
|
||||
margin-top: 0.14rem;
|
||||
}
|
||||
|
||||
/* ========== 求职目标标签 ========== */
|
||||
&__goal-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.08rem;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
&__goal-tag {
|
||||
display: inline-block;
|
||||
padding: 0.04rem 0.12rem;
|
||||
background: $theme-color;
|
||||
border: 1px solid $accent;
|
||||
border-radius: 0.14rem;
|
||||
font-size: 0.12rem;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
/* 编辑图标 */
|
||||
&__edit-icon {
|
||||
width: 0.13rem;
|
||||
height: 0.13rem;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
/* ========== 浏览器插件 ========== */
|
||||
// 浏览器按钮
|
||||
&__browser-btns {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.1rem;
|
||||
margin-top: 0.12rem;
|
||||
margin-top: 0.08rem;
|
||||
}
|
||||
|
||||
&__browser-btn {
|
||||
padding: 0.08rem 0.18rem;
|
||||
background: $bg-white;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.08rem;
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: $accent;
|
||||
background: $theme-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__download-wrap {
|
||||
margin-top: 0.16rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__download-btn {
|
||||
padding: 0.1rem 0.4rem;
|
||||
background: $gradient-bg;
|
||||
color: $bg-white;
|
||||
border: none;
|
||||
border-radius: 0.24rem;
|
||||
font-size: 0.14rem;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
/* 浏览器安装指引弹窗图片 */
|
||||
&__guide-slide {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 求职助手配置表单 ========== */
|
||||
&__config-group {
|
||||
margin-top: 0.16rem;
|
||||
}
|
||||
|
||||
&__config-label {
|
||||
font-size: 0.13rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
margin-bottom: 0.08rem;
|
||||
}
|
||||
|
||||
&__config-sub {
|
||||
font-size: 0.12rem;
|
||||
color: $text-middle;
|
||||
margin-bottom: 0.06rem;
|
||||
}
|
||||
|
||||
&__config-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.08rem;
|
||||
}
|
||||
|
||||
&__config-btn {
|
||||
padding: 0.07rem 0.16rem;
|
||||
padding: 0.08rem 0.2rem;
|
||||
background: $bg-white;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.2rem;
|
||||
font-size: 0.12rem;
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: $accent;
|
||||
}
|
||||
|
||||
&--active {
|
||||
border-color: $accent;
|
||||
background: $theme-color;
|
||||
color: $accent;
|
||||
}
|
||||
}
|
||||
|
||||
&__config-selects {
|
||||
display: flex;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
&__config-select {
|
||||
min-width: 1.2rem;
|
||||
|
||||
&--full {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 简历选择行 */
|
||||
&__resume-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.08rem;
|
||||
}
|
||||
|
||||
&__resume-icon {
|
||||
width: 0.18rem;
|
||||
height: 0.18rem;
|
||||
color: $text-middle;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 开关行 */
|
||||
&__switch-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
&__switch-text {
|
||||
font-size: 0.12rem;
|
||||
color: $text-dark;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
&__switch-sub {
|
||||
font-size: 0.11rem;
|
||||
color: $text-light;
|
||||
}
|
||||
|
||||
&__tip {
|
||||
font-size: 0.12rem;
|
||||
color: $text-light;
|
||||
cursor: help;
|
||||
margin-left: 0.04rem;
|
||||
}
|
||||
|
||||
/* 提交按钮 */
|
||||
&__submit-wrap {
|
||||
margin-top: 0.24rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__submit-btn {
|
||||
padding: 0.1rem 0.5rem;
|
||||
background: $gradient-bg;
|
||||
color: $bg-white;
|
||||
border: none;
|
||||
border-radius: 0.24rem;
|
||||
font-size: 0.14rem;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1180,6 +1180,14 @@
|
||||
border-radius: 0.08rem;
|
||||
}
|
||||
|
||||
/* 微信二维码 iframe */
|
||||
&__wechat-qr-iframe {
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border: none;
|
||||
border-radius: 0.08rem;
|
||||
}
|
||||
|
||||
&__qrcode-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -1,95 +1,50 @@
|
||||
@use '../variables' as *;
|
||||
@use 'sass:color';
|
||||
|
||||
// ==================== 个人资料编辑抽屉 ====================
|
||||
// ==================== 个人资料内联编辑组件 ====================
|
||||
|
||||
// 遮罩层
|
||||
.profile-drawer-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: $overlay-bg;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
// 抽屉主体 — 固定在右侧,占满屏幕高度
|
||||
// 重置 font-size + line-height 避免 html font-size:100px 导致行高撑大间距
|
||||
.profile-drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 6.2rem;
|
||||
height: var(--app-height, 100vh);
|
||||
background: $bg-white;
|
||||
z-index: 2001;
|
||||
box-shadow: -0.04rem 0 0.2rem rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// 内联编辑容器 — 直接嵌入页面卡片中
|
||||
.profile-edit-inline {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
background: #F7F7F7;
|
||||
border-radius: 0.12rem;
|
||||
padding: 0.1rem;
|
||||
box-sizing: border-box;
|
||||
|
||||
// 顶部栏
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.1rem;
|
||||
padding: 0.16rem 0.2rem;
|
||||
border-bottom: 1px solid $border-color;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0.04rem;
|
||||
cursor: pointer;
|
||||
color: $text-dark;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 0.04rem;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: $accent;
|
||||
background: $theme-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__close-icon {
|
||||
width: 0.16rem;
|
||||
height: 0.16rem;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 0.16rem;
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// 表单内容区 — 中间可滚动
|
||||
// 表单内容区
|
||||
&__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0.2rem 0.2rem 3rem;
|
||||
padding: 0.1rem 0;
|
||||
}
|
||||
|
||||
// 地区选择器下拉面板提升层级,避免被其他元素遮挡
|
||||
.region-selector__panel {
|
||||
z-index: 2100;
|
||||
}
|
||||
// 基本信息横排字段容器(姓名、电话、微信、邮箱、城市一行排列,允许换行)
|
||||
&__fields-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.16rem;
|
||||
}
|
||||
|
||||
// 横排中的单个字段项
|
||||
&__field-item {
|
||||
flex: 1;
|
||||
min-width: 1.6rem;
|
||||
}
|
||||
|
||||
// 作品集链接整行字段
|
||||
&__field-full {
|
||||
margin-top: 0.16rem;
|
||||
}
|
||||
|
||||
// 单个字段
|
||||
&__field {
|
||||
margin-bottom: 0.18rem;
|
||||
margin-bottom: 0.02rem;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 0.13rem;
|
||||
display: block;
|
||||
font-size: 0.12rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
color: $text-middle;
|
||||
margin-bottom: 0.06rem;
|
||||
}
|
||||
|
||||
@@ -101,12 +56,12 @@
|
||||
&__input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0.1rem 0.14rem;
|
||||
padding: 0.08rem 0.12rem;
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
background: $bg-main;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.06rem;
|
||||
background: $bg-white;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.04rem;
|
||||
outline: none;
|
||||
transition: all 0.2s;
|
||||
|
||||
@@ -120,21 +75,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 多行文本输入框(经历描述等)
|
||||
// 多行文本输入框
|
||||
&__textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0.1rem 0.14rem;
|
||||
padding: 0.08rem 0.12rem;
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
background: $bg-main;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.06rem;
|
||||
background: $bg-white;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.04rem;
|
||||
outline: none;
|
||||
transition: all 0.2s;
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
line-height: 1.6;
|
||||
padding-right: 0.3rem;
|
||||
|
||||
&::placeholder {
|
||||
color: $text-light;
|
||||
@@ -144,33 +100,26 @@
|
||||
border-color: $accent;
|
||||
background: $bg-white;
|
||||
}
|
||||
|
||||
// 矮版 textarea — 用于分段描述,右侧留出删除按钮空间
|
||||
&--short {
|
||||
padding-right: 0.36rem;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 描述段落编辑 ====================
|
||||
|
||||
// 单条描述段落容器 — textarea + 右侧删除按钮
|
||||
// 描述段落容器
|
||||
&__desc-item {
|
||||
position: relative;
|
||||
margin-bottom: 0.08rem;
|
||||
}
|
||||
|
||||
// 描述段落内的删除按钮(叉号)
|
||||
// 描述删除按钮
|
||||
&__desc-remove {
|
||||
position: absolute;
|
||||
right: 0.18rem;
|
||||
top: 0.08rem;
|
||||
right: 0.08rem;
|
||||
top: 0.06rem;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0.02rem;
|
||||
padding: 0.02rem 0.04rem;
|
||||
cursor: pointer;
|
||||
color: $text-light;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.14rem;
|
||||
line-height: 1;
|
||||
border-radius: 0.03rem;
|
||||
transition: all 0.2s;
|
||||
|
||||
@@ -179,91 +128,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__desc-remove-icon {
|
||||
width: 0.12rem;
|
||||
height: 0.12rem;
|
||||
}
|
||||
|
||||
// 小号新增按钮 — 用于"新增一段描述"
|
||||
&__add-btn--small {
|
||||
font-size: 0.11rem;
|
||||
padding: 0.04rem 0.12rem;
|
||||
margin-top: 0.04rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// 空状态占位
|
||||
&__empty {
|
||||
text-align: center;
|
||||
padding: 0.6rem 0;
|
||||
}
|
||||
|
||||
&__empty-text {
|
||||
font-size: 0.14rem;
|
||||
color: $text-light;
|
||||
}
|
||||
|
||||
// ==================== 教育经历模块 ====================
|
||||
|
||||
// 单条教育经历卡片
|
||||
&__edu-card {
|
||||
padding-bottom: 0.2rem;
|
||||
margin-bottom: 0.2rem;
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
// 教育经历标题栏
|
||||
&__edu-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.14rem;
|
||||
}
|
||||
|
||||
// 序号标识
|
||||
&__edu-index {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.06rem;
|
||||
font-size: 0.14rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
&__edu-index-icon {
|
||||
width: 0.1rem;
|
||||
height: 0.1rem;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
// 删除按钮
|
||||
&__edu-delete {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0.04rem;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
&__edu-delete-icon {
|
||||
width: 0.15rem;
|
||||
height: 0.15rem;
|
||||
}
|
||||
|
||||
// 横排字段容器(学院+专业、学历+GPA、入学+毕业时间)
|
||||
// 横排字段容器
|
||||
&__row {
|
||||
display: flex;
|
||||
gap: 0.12rem;
|
||||
@@ -275,60 +140,81 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
// 下拉选择器包裹(带箭头图标)
|
||||
&__select-wrap {
|
||||
position: relative;
|
||||
// ==================== 经历卡片模块 ====================
|
||||
|
||||
&__edu-card {
|
||||
padding-bottom: 0.16rem;
|
||||
margin-bottom: 0.16rem;
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__select-wrap &__input {
|
||||
padding-right: 0.3rem;
|
||||
&__edu-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.12rem;
|
||||
}
|
||||
|
||||
&__select-arrow {
|
||||
position: absolute;
|
||||
right: 0.1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 0.12rem;
|
||||
height: 0.12rem;
|
||||
&__edu-index {
|
||||
font-size: 0.13rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
&__edu-delete {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0.04rem 0.08rem;
|
||||
cursor: pointer;
|
||||
color: $text-light;
|
||||
pointer-events: none;
|
||||
font-size: 0.12rem;
|
||||
border-radius: 0.04rem;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: $danger;
|
||||
background: rgba($danger, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
// 新增教育经历按钮
|
||||
// 新增按钮
|
||||
&__add-btn {
|
||||
text-align: center;
|
||||
background: none;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.2rem;
|
||||
padding: 0.06rem 0.16rem;
|
||||
border: 1px dashed $border-color;
|
||||
border-radius: 0.04rem;
|
||||
padding: 0.04rem 0.12rem;
|
||||
font-size: 0.12rem;
|
||||
color: $text-dark;
|
||||
color: $text-middle;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
margin-top: 0.04rem;
|
||||
margin-top: 0.06rem;
|
||||
|
||||
&:hover {
|
||||
border-color: $accent;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
&--full {
|
||||
width: 100%;
|
||||
margin-top: 0.12rem;
|
||||
padding: 0.08rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__add-icon {
|
||||
font-size: 0.14rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
// ==================== 日期选择器样式覆盖 ====================
|
||||
// Element Plus 的 el-date-picker 在 html font-size:100px 下需要用 px 还原
|
||||
// ==================== 日期选择器 ====================
|
||||
|
||||
&__date-picker {
|
||||
width: 100% !important;
|
||||
|
||||
.el-input__wrapper {
|
||||
background: $bg-main !important;
|
||||
border: 1px solid transparent !important;
|
||||
border-radius: 6px !important;
|
||||
border: 1px solid $border-color !important;
|
||||
border-radius: 4px !important;
|
||||
box-shadow: none !important;
|
||||
padding: 4px 12px !important;
|
||||
font-size: 13px !important;
|
||||
@@ -336,7 +222,7 @@
|
||||
height: auto !important;
|
||||
|
||||
&:hover {
|
||||
border-color: $border-color !important;
|
||||
border-color: $accent !important;
|
||||
}
|
||||
|
||||
&.is-focus {
|
||||
@@ -368,26 +254,147 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 技能模块 ====================
|
||||
// 日期范围容器(开始-结束横排)
|
||||
&__date-range {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.04rem;
|
||||
}
|
||||
|
||||
&__date-sep {
|
||||
color: $text-light;
|
||||
font-size: 0.12rem;
|
||||
}
|
||||
|
||||
// ==================== AI润色功能 ====================
|
||||
|
||||
// 个人概述标题栏(含AI润色按钮)
|
||||
&__summary-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.12rem;
|
||||
margin-bottom: 0.08rem;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
// AI润色按钮
|
||||
&__ai-polish-btn {
|
||||
padding: 0.01rem 0.06rem;
|
||||
font-size: 0.11rem;
|
||||
background: $accent;
|
||||
color: $bg-white;
|
||||
border: 1px solid $accent;
|
||||
border-radius: 0.04rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
>span>span{
|
||||
color: #FCD34D;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $accent;
|
||||
color: $bg-white;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
// AI润色结果区域
|
||||
&__ai-result {
|
||||
margin-top: 0.16rem;
|
||||
}
|
||||
|
||||
&__ai-result-label {
|
||||
font-size: 0.12rem;
|
||||
font-weight: 600;
|
||||
color: $accent;
|
||||
margin-bottom: 0.08rem;
|
||||
}
|
||||
|
||||
&__ai-result-card {
|
||||
padding: 0.1rem 0.12rem;
|
||||
background: $theme-color;
|
||||
border: 1px solid rgba($accent, 0.2);
|
||||
border-radius: 0.06rem;
|
||||
}
|
||||
|
||||
&__ai-result-text {
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
line-height: 1.7;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
// AI结果操作按钮组
|
||||
&__ai-result-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.12rem;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
// 替换为此内容按钮
|
||||
&__ai-replace-btn {
|
||||
padding: 0.04rem 0.12rem;
|
||||
font-size: 0.12rem;
|
||||
color: #fff;
|
||||
background: $accent;
|
||||
border: 1px solid $accent;
|
||||
border-radius: 0.04rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: $accent;
|
||||
color: $bg-white;
|
||||
}
|
||||
}
|
||||
|
||||
// 重新生成按钮
|
||||
&__ai-regenerate-btn {
|
||||
padding: 0.04rem 0.12rem;
|
||||
font-size: 0.12rem;
|
||||
color: $accent;
|
||||
background: none;
|
||||
border: 1px solid $accent;
|
||||
border-radius: 0.04rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: $accent;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 技能/证书标签 ====================
|
||||
|
||||
// 技能标签列表容器
|
||||
&__skills-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.08rem;
|
||||
margin-bottom: 0.2rem;
|
||||
margin-bottom: 0.16rem;
|
||||
min-height: 0.4rem;
|
||||
}
|
||||
|
||||
// 单个技能标签
|
||||
&__skill-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.06rem;
|
||||
padding: 0.06rem 0.1rem 0.06rem 0.14rem;
|
||||
padding: 0.04rem 0.1rem 0.04rem 0.12rem;
|
||||
background: $bg-main;
|
||||
border-radius: 0.16rem;
|
||||
font-size: 0.13rem;
|
||||
border-radius: 0.14rem;
|
||||
font-size: 0.12rem;
|
||||
color: $text-dark;
|
||||
transition: all 0.2s;
|
||||
|
||||
@@ -396,12 +403,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 技能标签文本
|
||||
&__skill-text {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
// 技能标签删除按钮
|
||||
&__skill-remove {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -424,23 +429,23 @@
|
||||
height: 0.12rem;
|
||||
}
|
||||
|
||||
// 底部保存按钮
|
||||
// ==================== 底部操作按钮 ====================
|
||||
|
||||
&__footer {
|
||||
padding: 0.16rem 0.2rem;
|
||||
border-top: 1px solid $border-color;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.12rem;
|
||||
padding-top: 0.04rem;
|
||||
}
|
||||
|
||||
&__save-btn {
|
||||
width: 2.0rem;
|
||||
padding: 0.1rem 0;
|
||||
font-size: 0.14rem;
|
||||
padding: 0.06rem 0.2rem;
|
||||
font-size: 0.13rem;
|
||||
font-weight: 600;
|
||||
color: $bg-white;
|
||||
background: $btn-dark;
|
||||
border: none;
|
||||
border-radius: 0.24rem;
|
||||
border-radius: 0.2rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
@@ -448,11 +453,26 @@
|
||||
background: $btn-dark-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&__cancel-btn {
|
||||
padding: 0.06rem 0.2rem;
|
||||
font-size: 0.13rem;
|
||||
font-weight: 600;
|
||||
color: $accent;
|
||||
background: transparent;
|
||||
border: 1px solid $accent;
|
||||
border-radius: 0.2rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: $theme-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 过渡动画 ====================
|
||||
// ==================== 日期选择器弹出面板样式覆盖 ====================
|
||||
|
||||
// 日期选择器弹出面板样式覆盖(teleport 到 body,需在全局作用域)
|
||||
.profile-drawer-date-popper {
|
||||
font-size: 14px !important;
|
||||
line-height: 1.5 !important;
|
||||
@@ -474,25 +494,3 @@
|
||||
padding: 8px 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 遮罩层淡入淡出
|
||||
.profile-drawer-overlay-enter-active,
|
||||
.profile-drawer-overlay-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.profile-drawer-overlay-enter-from,
|
||||
.profile-drawer-overlay-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// 抽屉滑入滑出
|
||||
.profile-drawer-slide-enter-active,
|
||||
.profile-drawer-slide-leave-active {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.profile-drawer-slide-enter-from,
|
||||
.profile-drawer-slide-leave-to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
@@ -5,16 +5,66 @@
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.16rem;
|
||||
padding-bottom: 0.4rem;
|
||||
gap: 0;
|
||||
padding: 0.2rem 0.24rem 0;
|
||||
background: $bg-white;
|
||||
border-radius: 0.12rem;
|
||||
font-size: 0.14rem;
|
||||
overflow: hidden;
|
||||
|
||||
// 卡片通用
|
||||
// Tab 导航栏(固定不动)
|
||||
&__tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.24rem;
|
||||
padding-bottom: 0.14rem;
|
||||
border-bottom: 1px solid $border-color;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__tab-item {
|
||||
font-size: 0.14rem;
|
||||
color: $text-middle;
|
||||
cursor: pointer;
|
||||
padding-bottom: 0.06rem;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
&--active {
|
||||
color: $text-dark;
|
||||
font-weight: 600;
|
||||
border-bottom-color: $text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
// 内容滚动区域
|
||||
&__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 0.2rem;
|
||||
|
||||
// 隐藏滚动条
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
// 卡片通用(不再有独立背景,只做分隔)
|
||||
&__card {
|
||||
background: $bg-white;
|
||||
border-radius: 0.12rem;
|
||||
padding: 0.2rem 0.24rem;
|
||||
font-size: 14px;
|
||||
padding: 0.16rem 0;
|
||||
line-height: 1.5;
|
||||
border-radius: 0.08rem;
|
||||
|
||||
&:not(:last-child) {
|
||||
//border-bottom: 1px solid $border-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__card-header {
|
||||
@@ -22,6 +72,8 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.14rem;
|
||||
padding: 0.06rem 0.14rem;
|
||||
border-radius: 0.06rem;
|
||||
}
|
||||
|
||||
&__card-title {
|
||||
@@ -29,6 +81,21 @@
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
padding-bottom: 0.02rem;
|
||||
|
||||
// 底部装饰线
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0.02rem;
|
||||
width: 100%;
|
||||
height: 0.06rem;
|
||||
background: $accent;
|
||||
border-radius: 0.02rem;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
&__edit-btn {
|
||||
@@ -81,7 +148,6 @@
|
||||
padding: 0.12rem 0;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +236,7 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.08rem;
|
||||
padding: 0 0.14rem;
|
||||
}
|
||||
|
||||
&__tag {
|
||||
@@ -187,4 +254,77 @@
|
||||
background: $theme-color;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 新版内联编辑按钮(品牌色图标+文字,hover显示) ----
|
||||
&__edit-btn-inline {
|
||||
background: none;
|
||||
border: none;
|
||||
color: $accent;
|
||||
cursor: pointer;
|
||||
padding: 0.04rem 0.08rem;
|
||||
border-radius: 0.04rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.04rem;
|
||||
font-size: 0.13rem;
|
||||
font-weight: 500;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
|
||||
.el-icon {
|
||||
font-size: 0.15rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $theme-color;
|
||||
}
|
||||
}
|
||||
|
||||
// 卡片hover时显示编辑按钮(个人信息/技能/证书)
|
||||
&__card-hover:hover &__edit-btn-inline {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
&__card-hover:hover {
|
||||
background: #F7F7F7;
|
||||
}
|
||||
|
||||
// 标题行hover显示添加按钮(5大经历)
|
||||
&__header-hover:hover {
|
||||
background: #F7F7F7;
|
||||
border-radius: 0.06rem;
|
||||
}
|
||||
|
||||
&__header-hover:hover &__edit-btn-inline {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
// 经历条目hover显示编辑按钮
|
||||
&__section-item {
|
||||
padding: 0.12rem 0.14rem;
|
||||
border-radius: 0.08rem;
|
||||
|
||||
&:hover {
|
||||
background: #F7F7F7;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
}
|
||||
}
|
||||
|
||||
// 条目hover时显示编辑按钮
|
||||
&__section-item:hover &__item-edit-btn {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
// 条目内编辑按钮
|
||||
&__item-edit-btn {
|
||||
margin-left: 0.08rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,51 @@
|
||||
@use '../variables' as *;
|
||||
|
||||
/* 编辑简历名称弹窗样式 */
|
||||
/* 编辑简历名称弹窗 — 遮罩层 */
|
||||
.resume-edit-name-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: $overlay-bg;
|
||||
z-index: 2200;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 弹窗主体 */
|
||||
.resume-edit-name-dialog {
|
||||
.el-dialog__header {
|
||||
padding: 0.2rem 0.2rem 0;
|
||||
}
|
||||
width: 4.6rem;
|
||||
background: $bg-white;
|
||||
border-radius: 0.12rem;
|
||||
padding: 0.32rem 0.3rem;
|
||||
position: relative;
|
||||
font-size: 0.14rem;
|
||||
box-shadow: 0 0.04rem 0.2rem rgba(0, 0, 0, 0.15);
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 0 0.3rem 0.3rem;
|
||||
}
|
||||
/* 关闭按钮 */
|
||||
&__close {
|
||||
position: absolute;
|
||||
top: 0.14rem;
|
||||
right: 0.16rem;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 0.16rem;
|
||||
color: $text-light;
|
||||
cursor: pointer;
|
||||
padding: 0.04rem;
|
||||
line-height: 1;
|
||||
transition: color 0.2s;
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.24rem;
|
||||
&:hover {
|
||||
color: $text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表单字段 */
|
||||
&__field {
|
||||
width: 100%;
|
||||
margin-bottom: 0.24rem;
|
||||
}
|
||||
|
||||
/* 标签 */
|
||||
@@ -38,27 +65,28 @@
|
||||
|
||||
/* 输入框 */
|
||||
&__input {
|
||||
.el-input__wrapper {
|
||||
border-radius: 0.1rem;
|
||||
padding: 0.04rem 0.14rem;
|
||||
box-shadow: 0 0 0 1px $border-color;
|
||||
width: 100%;
|
||||
height: 0.44rem;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.1rem;
|
||||
padding: 0 0.14rem;
|
||||
font-size: 0.14rem;
|
||||
color: $text-dark;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.2s;
|
||||
|
||||
&::placeholder {
|
||||
color: $text-light;
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
font-size: 0.14rem;
|
||||
color: $text-dark;
|
||||
height: 0.36rem;
|
||||
line-height: normal;
|
||||
|
||||
&::placeholder {
|
||||
color: $text-light;
|
||||
}
|
||||
&:focus {
|
||||
border-color: $accent;
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部按钮区域 */
|
||||
&__footer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 0.16rem;
|
||||
margin-top: 0.08rem;
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
@use '../variables' as *;
|
||||
|
||||
// ==================== 一键修复抽屉(全模块) ====================
|
||||
|
||||
// 遮罩层
|
||||
.fix-all-drawer-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: $overlay-bg;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
// 遮罩层过渡动画
|
||||
.fix-all-drawer-overlay-enter-active,
|
||||
.fix-all-drawer-overlay-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.fix-all-drawer-overlay-enter-from,
|
||||
.fix-all-drawer-overlay-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// 抽屉主体
|
||||
.fix-all-drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 8rem;
|
||||
height: var(--app-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;
|
||||
}
|
||||
|
||||
// ---- 模块标题 ----
|
||||
&__module-title {
|
||||
font-size: 0.16rem;
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
margin: 0.24rem 0 0.12rem 0;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 单条经历记录 ----
|
||||
&__record {
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
&__record-label {
|
||||
font-size: 0.13rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
margin: 0 0 0.08rem 0;
|
||||
}
|
||||
|
||||
// ---- 卡片容器 ----
|
||||
&__card {
|
||||
background: $bg-white;
|
||||
border-radius: 0.1rem;
|
||||
padding: 0.16rem;
|
||||
border: 1px solid $border-color;
|
||||
|
||||
&--ai {
|
||||
border: 1px solid rgba($accent, 0.3);
|
||||
background: rgba($accent, 0.03);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 原文编辑 textarea ----
|
||||
&__textarea {
|
||||
width: 100%;
|
||||
min-height: 0.6rem;
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
line-height: 1.8;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
field-sizing: content;
|
||||
|
||||
&::placeholder {
|
||||
color: $text-light;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- AI 润色结果区 ----
|
||||
&__ai-section {
|
||||
margin-top: 0.12rem;
|
||||
}
|
||||
|
||||
&__ai-label {
|
||||
font-size: 0.13rem;
|
||||
font-weight: 600;
|
||||
color: $accent;
|
||||
margin: 0 0 0.08rem 0;
|
||||
}
|
||||
|
||||
&__ai-text {
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
line-height: 1.8;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
// ---- AI 操作按钮 ----
|
||||
&__ai-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.12rem;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
&__action-btn {
|
||||
font-size: 0.12rem;
|
||||
font-weight: 500;
|
||||
border: 1px solid $accent;
|
||||
border-radius: 0.14rem;
|
||||
padding: 0.04rem 0.12rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&--replace {
|
||||
color: $accent;
|
||||
background: $bg-white;
|
||||
|
||||
&:hover {
|
||||
background: rgba($accent, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
&--regenerate {
|
||||
color: $accent;
|
||||
background: $bg-white;
|
||||
|
||||
&:hover {
|
||||
background: rgba($accent, 0.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 底部按钮区 ----
|
||||
&__footer {
|
||||
padding: 0.16rem 0.24rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
flex-shrink: 0;
|
||||
background: $bg-white;
|
||||
border-top: 1px solid $border-color;
|
||||
}
|
||||
|
||||
&__footer-btn {
|
||||
padding: 0.1rem 0.3rem;
|
||||
font-size: 0.14rem;
|
||||
font-weight: 600;
|
||||
border-radius: 0.22rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&--use-ai {
|
||||
color: $accent;
|
||||
background: $bg-white;
|
||||
border: 1px solid $accent;
|
||||
|
||||
&:hover {
|
||||
background: rgba($accent, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
&--save {
|
||||
color: $bg-white;
|
||||
background: $btn-dark;
|
||||
border: 1px solid $btn-dark;
|
||||
|
||||
&:hover {
|
||||
background: $btn-dark-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 抽屉滑入动画
|
||||
.fix-all-drawer-slide-enter-active,
|
||||
.fix-all-drawer-slide-leave-active {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.fix-all-drawer-slide-enter-from,
|
||||
.fix-all-drawer-slide-leave-to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
@use './components/job-resume-custom-edit-panel.scss';
|
||||
@use './components/resume-analysis-report-drawer.scss';
|
||||
@use './components/resume-issue-fix-drawer.scss';
|
||||
@use './components/resume-issue-fix-all-drawer.scss';
|
||||
@use './components/resume-edit-name-dialog.scss';
|
||||
@use './components/agent-chat-job-list.scss';
|
||||
@use './components/agent-match-job-add.scss';
|
||||
|
||||
+310
-187
@@ -97,7 +97,7 @@
|
||||
|
||||
// 左侧说明引导区域
|
||||
&__left {
|
||||
width: 4.2rem;
|
||||
width: 3.0rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
&__intro-card {
|
||||
background: $bg-white;
|
||||
border-radius: 0.12rem;
|
||||
padding: 0.3rem;
|
||||
padding: 0.38rem;
|
||||
}
|
||||
|
||||
// 卡片头部:图标 + 标题
|
||||
@@ -214,23 +214,15 @@
|
||||
&__profile-wrapper {
|
||||
background: $bg-main;
|
||||
border-radius: 0.12rem;
|
||||
border: 1px solid $border-color;
|
||||
padding: 0.24rem;
|
||||
max-height: calc(var(--app-height, 100vh) - 1.4rem);
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
scrollbar-width: none; // Firefox
|
||||
-ms-overflow-style: none; // IE/Edge
|
||||
|
||||
// 自定义滚动条
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: $border-color;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
display: none; // Chrome/Safari
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,191 +591,309 @@
|
||||
&:active { transform: scale(0.98); }
|
||||
}
|
||||
|
||||
// ==================== 第3步:网申常见问题 ====================
|
||||
|
||||
// 第3步整体容器 — 左右两栏布局
|
||||
&__step3 {
|
||||
display: flex;
|
||||
gap: 0.24rem;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding-top: 0.36rem;
|
||||
// ==================== 第2步新增:求职偏好区域 ====================
|
||||
&__pref-section {
|
||||
background: $bg-white;
|
||||
border-radius: 0.12rem;
|
||||
padding: 0.24rem;
|
||||
margin-bottom: 0.2rem;
|
||||
border: 1px solid $border-color;
|
||||
}
|
||||
|
||||
// 右侧表单包裹
|
||||
&__form-wrapper {
|
||||
&__pref-section-title {
|
||||
font-size: 0.18rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
&__pref-section-desc {
|
||||
font-size: 0.13rem;
|
||||
color: $text-middle;
|
||||
margin-bottom: 0.16rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
&__pref-filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__pref-type-tag {
|
||||
padding: 0.06rem 0.16rem;
|
||||
background: $bg-main;
|
||||
border-radius: 0.04rem;
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
border: 1px solid $border-color;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
// 招聘分类下拉选择
|
||||
&__pref-type-dropdown {
|
||||
position: relative;
|
||||
padding: 0.1rem 0.28rem;
|
||||
background: #F5FAF9;
|
||||
border-radius: 0.24rem;
|
||||
font-size: 0.14rem;
|
||||
color: $text-dark;
|
||||
border: 1px solid $border-color;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.04rem;
|
||||
min-width: 0.9rem;
|
||||
height: 0.38rem;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
transition: border-color 0.2s ease;
|
||||
|
||||
&:hover { border-color: $accent; }
|
||||
}
|
||||
|
||||
&__pref-type-arrow {
|
||||
width: 0.12rem;
|
||||
height: 0.12rem;
|
||||
color: $text-middle;
|
||||
}
|
||||
|
||||
&__pref-type-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 0.04rem);
|
||||
left: 0;
|
||||
min-width: 1.2rem;
|
||||
background: $bg-white;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.06rem;
|
||||
box-shadow: 0 0.02rem 0.08rem rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
padding: 0.04rem 0;
|
||||
}
|
||||
|
||||
&__pref-type-menu-item {
|
||||
padding: 0.08rem 0.14rem;
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
|
||||
&:hover { background: $bg-main; }
|
||||
|
||||
&--active {
|
||||
color: $accent;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 第2步新增:简历设置区域 ====================
|
||||
&__resume-section {
|
||||
background: $bg-white;
|
||||
border-radius: 0.12rem;
|
||||
padding: 0.24rem;
|
||||
border: 1px solid $border-color;
|
||||
}
|
||||
|
||||
&__resume-sub-label {
|
||||
font-size: 0.13rem;
|
||||
color: $text-middle;
|
||||
margin-bottom: 0.12rem;
|
||||
}
|
||||
|
||||
&__resume-select-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.1rem;
|
||||
margin-bottom: 0.2rem;
|
||||
padding: 0.1rem 0.14rem;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.08rem;
|
||||
}
|
||||
|
||||
&__resume-check-icon {
|
||||
flex-shrink: 0;
|
||||
width: 0.16rem;
|
||||
height: 0.16rem;
|
||||
color: $text-middle;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__resume-select {
|
||||
flex: 1;
|
||||
|
||||
.el-input__wrapper {
|
||||
box-shadow: none !important;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__resume-switch-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.1rem 0;
|
||||
|
||||
& + & {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__resume-switch-text {
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
// ==================== 第3步:安装插件 ====================
|
||||
|
||||
// 安装教程整体容器
|
||||
&__install-guide {
|
||||
background: $bg-white;
|
||||
border-radius: 0.12rem;
|
||||
padding: 0.28rem;
|
||||
border: 1px solid $border-color;
|
||||
}
|
||||
|
||||
// 表单标题
|
||||
&__form-title {
|
||||
font-size: 0.15rem;
|
||||
// 安装教程标题
|
||||
&__install-guide-title {
|
||||
font-size: 0.18rem;
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
margin-bottom: 0.24rem;
|
||||
padding-bottom: 0.12rem;
|
||||
border-bottom: 1px solid $border-color;
|
||||
margin-bottom: 0.28rem;
|
||||
}
|
||||
|
||||
// 表单分组
|
||||
&__form-group {
|
||||
margin-bottom: 0.28rem;
|
||||
// 每个安装步骤
|
||||
&__install-step {
|
||||
margin-bottom: 0.3rem;
|
||||
|
||||
&:last-child { margin-bottom: 0; }
|
||||
}
|
||||
|
||||
// 表单标签
|
||||
&__form-label {
|
||||
// 步骤文字说明
|
||||
&__install-step-text {
|
||||
font-size: 0.14rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
margin-bottom: 0.12rem;
|
||||
text-align: center;
|
||||
margin-bottom: 0.16rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
// 选项按钮行
|
||||
&__form-options {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.12rem;
|
||||
}
|
||||
|
||||
// 单个选项按钮
|
||||
&__form-option {
|
||||
padding: 0.08rem 0.24rem;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.06rem;
|
||||
background: $bg-white;
|
||||
color: $text-dark;
|
||||
font-size: 0.13rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
line-height: 1.4;
|
||||
|
||||
&:hover {
|
||||
border-color: $text-middle;
|
||||
}
|
||||
|
||||
// 选中态
|
||||
&--active {
|
||||
background: $btn-dark;
|
||||
color: $bg-white;
|
||||
border-color: $btn-dark;
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉选择器行
|
||||
&__form-selects {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.12rem;
|
||||
}
|
||||
|
||||
// 统一下拉选择器样式覆盖
|
||||
&__form-select {
|
||||
width: 1.4rem;
|
||||
|
||||
// 覆盖 Element Plus 选择器样式以匹配页面风格
|
||||
:deep(.el-input__wrapper) {
|
||||
border-radius: 0.2rem;
|
||||
box-shadow: none;
|
||||
border: 1px solid $border-color;
|
||||
padding: 0.02rem 0.12rem;
|
||||
background: $bg-white;
|
||||
transition: border-color 0.2s ease;
|
||||
|
||||
&:hover,
|
||||
&.is-focus {
|
||||
border-color: $accent;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
text-align: center;
|
||||
|
||||
&::placeholder {
|
||||
color: $text-light;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-input__suffix) {
|
||||
color: $text-light;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 第3步下半部分:插件安装 ====================
|
||||
|
||||
// 安装步骤说明文字
|
||||
&__install-steps {
|
||||
font-size: 0.13rem;
|
||||
color: $text-middle;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 0.28rem;
|
||||
|
||||
p { margin: 0; }
|
||||
}
|
||||
|
||||
// 插件区域
|
||||
&__plugin-section {
|
||||
text-align: center;
|
||||
padding: 0.2rem 0;
|
||||
}
|
||||
|
||||
// 插件标题
|
||||
&__plugin-title {
|
||||
font-size: 0.18rem;
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
margin-bottom: 0.24rem;
|
||||
}
|
||||
|
||||
// 浏览器按钮行
|
||||
&__browser-btns {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
// 单个浏览器按钮
|
||||
&__browser-btn {
|
||||
padding: 0.08rem 0.2rem;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.06rem;
|
||||
background: $bg-white;
|
||||
color: $text-dark;
|
||||
font-size: 0.13rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: $accent;
|
||||
color: $accent;
|
||||
}
|
||||
}
|
||||
|
||||
// 指引图片幻灯片
|
||||
&__guide-slide {
|
||||
display: flex;
|
||||
// 下载按钮
|
||||
&__install-download-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
gap: 0.08rem;
|
||||
padding: 0.08rem 0.2rem;
|
||||
background: rgba($accent, 0.1);
|
||||
border: none;
|
||||
border-radius: 0.2rem;
|
||||
color: $accent;
|
||||
font-size: 0.13rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover { background: rgba($accent, 0.2); }
|
||||
|
||||
svg { flex-shrink: 0; }
|
||||
}
|
||||
|
||||
// 步骤2的内容区(图片+浏览器链接并排)
|
||||
&__install-step-content {
|
||||
display: flex;
|
||||
gap: 0.24rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
// 步骤中的图片区
|
||||
&__install-step-img {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: $bg-main;
|
||||
border-radius: 0.08rem;
|
||||
padding: 0.12rem;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 0.08rem;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 0.06rem;
|
||||
}
|
||||
}
|
||||
|
||||
// 浏览器链接区域
|
||||
&__install-browser-links {
|
||||
flex-shrink: 0;
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
&__install-browser-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.08rem;
|
||||
margin-bottom: 0.06rem;
|
||||
}
|
||||
|
||||
&__install-browser-icon {
|
||||
width: 0.2rem;
|
||||
height: 0.2rem;
|
||||
border-radius: 50%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
&__install-browser-name {
|
||||
font-size: 0.13rem;
|
||||
font-weight: 500;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
&__install-browser-url {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.04rem;
|
||||
font-size: 0.13rem;
|
||||
color: $accent;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
margin-bottom: 0.16rem;
|
||||
|
||||
&:hover { text-decoration: underline; }
|
||||
|
||||
svg { flex-shrink: 0; color: $text-middle; }
|
||||
}
|
||||
|
||||
// 步骤3/4的图片展示行
|
||||
&__install-step-img-row {
|
||||
display: flex;
|
||||
gap: 0.16rem;
|
||||
}
|
||||
|
||||
&__install-step-img-item {
|
||||
flex: 1;
|
||||
background: $bg-main;
|
||||
border-radius: 0.08rem;
|
||||
padding: 0.12rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 0.06rem;
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧卡片中的安装步骤列表
|
||||
&__install-steps-list {
|
||||
font-size: 0.13rem;
|
||||
color: $text-middle;
|
||||
line-height: 2;
|
||||
margin-bottom: 0.1rem;
|
||||
|
||||
p { margin: 0; }
|
||||
}
|
||||
|
||||
// ==================== 设置完成页 ====================
|
||||
|
||||
// 完成页容器 — 居中布局
|
||||
@@ -845,28 +955,34 @@
|
||||
.agent-main {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
// 有右侧面板时取消居中
|
||||
&--with-panel {
|
||||
//justify-content: flex-start;
|
||||
}
|
||||
|
||||
// 左侧主区域
|
||||
// 左侧主区域(60%)
|
||||
&__left {
|
||||
flex: 1;
|
||||
width: 60%;
|
||||
height: calc(var(--app-height, 100vh) - 0.6rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 8rem;
|
||||
}
|
||||
|
||||
// 右侧匹配岗位面板
|
||||
// 右侧面板(40%,常驻设置面板 + 覆盖层)
|
||||
&__right {
|
||||
flex: 1;
|
||||
width: 40%;
|
||||
height: calc(var(--app-height, 100vh) - 0.6rem);
|
||||
margin-left: 0.2rem;
|
||||
max-width: 8rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 右侧覆盖层 — 绝对定位覆盖在设置面板之上
|
||||
&__right-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
background: $bg-white;
|
||||
border-radius: 0.12rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -878,7 +994,6 @@
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
// 自定义滚动条
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
@@ -1142,7 +1257,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 中间聊天记录区域 ====================
|
||||
// ==================== 中间内容区域 ====================
|
||||
&__chat-area {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
@@ -1164,6 +1279,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 投递进度列表
|
||||
&__progress-list {
|
||||
margin-top: 0.16rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.12rem;
|
||||
}
|
||||
|
||||
// ==================== 对话消息样式 ====================
|
||||
|
||||
// 对话行
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
padding: 0.12rem 0.36rem;
|
||||
height: var(--app-height, 100vh);
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
background: $bg-main;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -50,6 +50,7 @@
|
||||
gap: 0.24rem;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 右侧边栏
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
&__content {
|
||||
margin-left: 2rem;
|
||||
flex: 1;
|
||||
padding: 0.12rem 0.36rem;
|
||||
padding-bottom: 0.24rem;
|
||||
height: var(--app-height, 100vh);
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
@@ -16,10 +16,14 @@
|
||||
}
|
||||
|
||||
&__page-title {
|
||||
font-size: 0.24rem;
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
|
||||
margin: 0 0 0.16rem 0;
|
||||
padding: 0 0.24rem;
|
||||
>h2{
|
||||
font-size: 0.24rem;
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 顶部操作栏 ----
|
||||
@@ -27,7 +31,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.16rem;
|
||||
margin-bottom: 0.15rem;
|
||||
padding: 0 0.24rem;
|
||||
}
|
||||
|
||||
&__toolbar-left {
|
||||
@@ -37,22 +42,18 @@
|
||||
}
|
||||
|
||||
&__back-btn {
|
||||
width: 0.32rem;
|
||||
height: 0.32rem;
|
||||
border-radius: 50%;
|
||||
border: 1px solid $border-color;
|
||||
background: $bg-white;
|
||||
background: none;
|
||||
border: none;
|
||||
color: $text-dark;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.13rem;
|
||||
|
||||
&:hover {
|
||||
background: $theme-color;
|
||||
border-color: $accent;
|
||||
color: $accent;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +84,8 @@
|
||||
gap: 0.04rem;
|
||||
background: none;
|
||||
border: none;
|
||||
color: $text-light;
|
||||
font-size: 0.12rem;
|
||||
color: $text-dark;
|
||||
font-size: 0.13rem;
|
||||
cursor: pointer;
|
||||
padding: 0.05rem 0.1rem;
|
||||
border-radius: 0.06rem;
|
||||
@@ -92,8 +93,8 @@
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
color: $text-dark;
|
||||
background: $bg-white;
|
||||
//color: $text-dark;
|
||||
//background: $bg-white;
|
||||
}
|
||||
|
||||
&--danger:hover {
|
||||
@@ -115,40 +116,79 @@
|
||||
background: $bg-white;
|
||||
border-radius: 0.12rem;
|
||||
padding: 0.18rem 0.24rem;
|
||||
padding-top: 0.4rem;
|
||||
margin-bottom: 0.16rem;
|
||||
|
||||
/* B/C/D 评级渐变背景 */
|
||||
&--grade-bcd {
|
||||
background: linear-gradient(to bottom, #FFF5E9, #FFF);
|
||||
}
|
||||
|
||||
/* A 评级渐变背景 */
|
||||
&--grade-a {
|
||||
background: linear-gradient(to bottom, #ECFDFC, #FFF);
|
||||
}
|
||||
}
|
||||
|
||||
&__score-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.12rem;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
&__score-avatar {
|
||||
width: 0.4rem;
|
||||
height: 0.4rem;
|
||||
border-radius: 50%;
|
||||
/* 星星+底座容器 */
|
||||
&__star-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 0.7rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* SVG 五角星 */
|
||||
&__star-svg {
|
||||
width: 0.63rem;
|
||||
height: 0.63rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 底座长方形 */
|
||||
&__star-base {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
margin-top: -0.08rem;
|
||||
width: 0.52rem;
|
||||
height: 0.22rem;
|
||||
border: 1.5px solid #858585;
|
||||
border-radius: 0.03rem;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: $text-dark;
|
||||
font-size: 0.18rem;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
background: $bg-main;
|
||||
|
||||
> span {
|
||||
font-size: 0.12rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&__score-badge {
|
||||
font-size: 0.11rem;
|
||||
font-weight: 600;
|
||||
color: $bg-white;
|
||||
background: $accent;
|
||||
border-radius: 0.1rem;
|
||||
padding: 0.03rem 0.1rem;
|
||||
/* 右侧操作按钮区 */
|
||||
&__score-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.16rem;
|
||||
}
|
||||
|
||||
/* 暂无等级提示文字 */
|
||||
&__score-hint {
|
||||
font-size: 0.13rem;
|
||||
color: $text-light;
|
||||
}
|
||||
|
||||
&__score-link {
|
||||
font-size: 0.12rem;
|
||||
font-size: 0.13rem;
|
||||
color: $text-light;
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -169,65 +209,69 @@
|
||||
&__score-item {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.02rem;
|
||||
gap: 0.06rem;
|
||||
}
|
||||
|
||||
&__score-num {
|
||||
font-size: 0.22rem;
|
||||
font-size: 0.30rem;
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
&__score-label {
|
||||
font-size: 0.1rem;
|
||||
font-size: 0.13rem;
|
||||
color: $text-light;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__diagnose-btn {
|
||||
background: $btn-dark;
|
||||
margin-left: 0.16rem;
|
||||
color: $bg-white;
|
||||
border: none;
|
||||
border-radius: 0.06rem;
|
||||
padding: 0.08rem 0.18rem;
|
||||
border-radius: 0.18rem;
|
||||
width: 0.93rem;
|
||||
height: 0.36rem;
|
||||
line-height: 0.37rem;
|
||||
font-size: 0.13rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
box-sizing: border-box;
|
||||
//transition: background 0.2s;
|
||||
white-space: nowrap;
|
||||
|
||||
&: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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 简历主体 ----
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.16rem;
|
||||
padding-bottom: 0.4rem;
|
||||
padding-bottom: 0.3rem;
|
||||
border-radius: 0.16rem;
|
||||
background: #fff;
|
||||
margin: 0 0.24rem;
|
||||
}
|
||||
|
||||
&__card {
|
||||
background: $bg-white;
|
||||
border-radius: 0.12rem;
|
||||
padding: 0.24rem;
|
||||
border-radius: 0.1rem;
|
||||
margin: 0.4rem 0.18rem;
|
||||
|
||||
|
||||
}
|
||||
|
||||
&__card-hover {
|
||||
padding: 0.1rem 0.14rem;
|
||||
&:hover {
|
||||
background: #F7F7F7;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---- 个人信息卡片 ----
|
||||
@@ -316,8 +360,9 @@
|
||||
&__section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.12rem;
|
||||
margin-bottom: 0.16rem;
|
||||
padding: 0.1rem 0 0.1rem 0.14rem;
|
||||
}
|
||||
|
||||
&__section-title {
|
||||
@@ -325,6 +370,24 @@
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
margin: 0 0 0.16rem 0;
|
||||
|
||||
// 底部加粗装饰线(层级低于文字,部分遮挡文字底部)
|
||||
&--underline {
|
||||
position: relative;
|
||||
padding-bottom: 0.02rem;
|
||||
background: transparent;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0.02rem;
|
||||
width: 100%;
|
||||
height: 0.06rem;
|
||||
background: #12C7BE;
|
||||
border-radius: 0.02rem;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__section-header &__section-title {
|
||||
@@ -343,6 +406,7 @@
|
||||
align-items: center;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
|
||||
&:hover {
|
||||
color: $accent;
|
||||
@@ -355,6 +419,63 @@
|
||||
height: 0.15rem;
|
||||
}
|
||||
|
||||
// ---- 内联编辑按钮(品牌色图标+文字,hover卡片时才显示) ----
|
||||
&__edit-btn-inline {
|
||||
background: none;
|
||||
border: none;
|
||||
color: $accent;
|
||||
cursor: pointer;
|
||||
padding: 0.04rem 0.08rem;
|
||||
border-radius: 0.04rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.04rem;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
font-size: 0.13rem;
|
||||
font-weight: 500;
|
||||
// 默认隐藏,hover卡片时显示
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
.el-icon {
|
||||
font-size: 0.15rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $theme-color;
|
||||
}
|
||||
}
|
||||
|
||||
// 卡片hover时显示编辑按钮(个人信息、个人概述)
|
||||
&__card-hover:hover &__edit-btn-inline {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
// 经历模块标题栏hover时显示添加按钮
|
||||
&__section-header:hover &__edit-btn-inline {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
// 经历模块标题栏hover时背景色
|
||||
&__section-header:hover {
|
||||
background: #F7F7F7;
|
||||
border-radius: 0.06rem;
|
||||
}
|
||||
|
||||
// 经历条目内的编辑按钮(hover exp-item 时显示)
|
||||
&__exp-edit-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&__exp-item:hover &__exp-edit-btn {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
// ---- 教育背景 ----
|
||||
&__edu-item {
|
||||
margin-top: 0.16rem;
|
||||
@@ -381,13 +502,13 @@
|
||||
// ---- 工作经历 ----
|
||||
&__exp-item {
|
||||
margin-top: 0.16rem;
|
||||
margin-left: 0.1rem;
|
||||
padding-left: 0.1rem;
|
||||
border-left: 2px solid $border-color;
|
||||
|
||||
&:not(:last-child) {
|
||||
//border-bottom: 1px solid $border-color;
|
||||
padding: 0.1rem 0.14rem;
|
||||
border-radius: 0.09rem;
|
||||
&:hover {
|
||||
background: #F7F7F7;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
&__exp-title {
|
||||
@@ -403,6 +524,112 @@
|
||||
margin-bottom: 0.06rem;
|
||||
}
|
||||
|
||||
// ---- 经历第一行(名称 + 身份 + 时间 + 修复标记) ----
|
||||
&__exp-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.12rem;
|
||||
margin-bottom: 0.06rem;
|
||||
}
|
||||
|
||||
&__exp-name {
|
||||
font-size: 0.14rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
&__exp-role {
|
||||
font-size: 0.13rem;
|
||||
font-weight: 400;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
&__exp-date {
|
||||
font-size: 0.12rem;
|
||||
font-weight: 400;
|
||||
color: $text-middle;
|
||||
}
|
||||
|
||||
// ---- 修复建议标记 ----
|
||||
&__fix-tag {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.05rem;
|
||||
font-size: 0.12rem;
|
||||
font-weight: 500;
|
||||
color: #FF8D0B;
|
||||
background: #FFF5E9;
|
||||
border-radius: 0.04rem;
|
||||
padding: 0.04rem 0.12rem;
|
||||
cursor: default;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background: #FFEDDA;
|
||||
}
|
||||
|
||||
// hover 时显示气泡
|
||||
&:hover .resume-detail__fix-tooltip {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&__fix-tag-icon {
|
||||
width: 0.14rem;
|
||||
height: 0.14rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// ---- 诊断详情 hover 气泡 ----
|
||||
&__fix-tooltip {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: calc(100% + 0.12rem);
|
||||
transform: translateY(-50%);
|
||||
min-width: 2.4rem;
|
||||
max-width: 3.5rem;
|
||||
padding: 0.14rem 0.18rem;
|
||||
background: linear-gradient(to bottom,#FFF5E9,#FFFFFF);
|
||||
color: $text-dark;
|
||||
font-size: 0.13rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.7;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
border-radius: 0.1rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
|
||||
border: 1px solid #FF8D0B;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s ease;
|
||||
z-index: 100;
|
||||
pointer-events: none;
|
||||
|
||||
// 左侧尖角 — 旋转方块方案,边框粗细与气泡一致
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: -0.05rem;
|
||||
margin-top: -0.05rem;
|
||||
width: 0.09rem;
|
||||
height: 0.09rem;
|
||||
background: #FFF8F2;
|
||||
border-left: 1px solid #FF8D0B;
|
||||
border-bottom: 1px solid #FF8D0B;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
// 不再需要内层伪元素
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__exp-desc {
|
||||
font-size: 0.12rem;
|
||||
color: $text-dark;
|
||||
@@ -419,9 +646,9 @@
|
||||
|
||||
&__skill-tag {
|
||||
font-size: 0.12rem;
|
||||
color: $text-dark;
|
||||
background: $bg-main;
|
||||
border: 1px solid $border-color;
|
||||
color: $accent;
|
||||
background: $bg-white;
|
||||
border: 1px solid $accent;
|
||||
border-radius: 0.04rem;
|
||||
padding: 0.05rem 0.14rem;
|
||||
transition: all 0.2s;
|
||||
@@ -429,7 +656,7 @@
|
||||
&:hover {
|
||||
border-color: $accent;
|
||||
color: $accent;
|
||||
background: $theme-color;
|
||||
background: $bg-white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
white-space: nowrap;
|
||||
|
||||
&--action {
|
||||
width: 0.5rem;
|
||||
width: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
&__name-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.1rem;
|
||||
gap: 0.06rem;
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
@@ -128,16 +128,71 @@
|
||||
}
|
||||
|
||||
&__default-tag {
|
||||
font-size: 0.1rem;
|
||||
color: $text-light;
|
||||
background: $bg-main;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.04rem;
|
||||
padding: 0.02rem 0.06rem;
|
||||
font-size: 0.09rem;
|
||||
color: $accent;
|
||||
background: #D9F5F0;
|
||||
border-radius: 0.14rem;
|
||||
padding: 0.01rem 0.06rem;
|
||||
padding-bottom: 0.02rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 更多按钮
|
||||
// 编辑名称岗位图标按钮
|
||||
&__edit-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: $text-dark;
|
||||
cursor: pointer;
|
||||
padding: 0.02rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
&__edit-icon {
|
||||
font-size: 0.13rem;
|
||||
}
|
||||
|
||||
// 操作按钮容器
|
||||
&__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.16rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
// 行内操作按钮
|
||||
&__action-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 0.12rem;
|
||||
color: $text-dark;
|
||||
cursor: pointer;
|
||||
padding: 0.04rem 0.08rem;
|
||||
border-radius: 0.04rem;
|
||||
white-space: nowrap;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
// 删除按钮红色
|
||||
&--delete {
|
||||
color: #FD6464;
|
||||
|
||||
&:hover {
|
||||
color: #e04545;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更多按钮(已移除,保留兼容)
|
||||
&__more-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -168,34 +223,6 @@
|
||||
color: $text-light;
|
||||
font-size: 0.14rem;
|
||||
}
|
||||
|
||||
// 弹出菜单
|
||||
&__popup {
|
||||
position: absolute;
|
||||
right: 0.46rem;
|
||||
top: 0.18rem;
|
||||
background: $bg-white;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.1rem;
|
||||
padding: 0.06rem 0;
|
||||
box-shadow: 0 0.06rem 0.2rem rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
min-width: 1.2rem;
|
||||
}
|
||||
|
||||
&__popup-item {
|
||||
padding: 0.08rem 0.2rem;
|
||||
font-size: 0.12rem;
|
||||
color: $text-dark;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: $theme-color;
|
||||
color: $accent-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 上传简历加载提示 ====================
|
||||
|
||||
@@ -28,10 +28,11 @@ $text-middle: #777777;
|
||||
|
||||
// 强调色 / 品牌色
|
||||
//$accent: #4FC2C9;
|
||||
$accent: #52CAD1;
|
||||
//$accent: #52CAD1;
|
||||
$accent: #12C7BE;
|
||||
|
||||
// 强调色悬停态
|
||||
$accent-hover: #42A8B3;
|
||||
$accent-hover: #11b5ac;
|
||||
|
||||
// 危险色 / 警告色(删除、错误等)
|
||||
$danger: #E85635;
|
||||
@@ -43,10 +44,10 @@ $border-color: #E8E8E8;
|
||||
$overlay-bg: rgba(0, 0, 0, 0.5);
|
||||
|
||||
// 主按钮颜色背景(确认提交等)
|
||||
$btn-dark: #52CAD1;
|
||||
$btn-dark: #12C7BE;
|
||||
|
||||
// 主按钮颜色悬停态
|
||||
$btn-dark-hover: #42A8B3;
|
||||
$btn-dark-hover: #11b5ac;
|
||||
|
||||
// 次要深色按钮背景(除非特意指定使用否则不用这个按钮色)
|
||||
//$btn-dark: #1A1A2E;
|
||||
@@ -55,4 +56,5 @@ $btn-dark-hover: #42A8B3;
|
||||
//$btn-dark-hover: #2E3142;
|
||||
|
||||
// 渐变色背景
|
||||
$gradient-bg: linear-gradient(to right, #52CAD1, #42A8B3);
|
||||
//$gradient-bg: linear-gradient(to right, #52CAD1, #42A8B3);
|
||||
$gradient-bg: linear-gradient(to right, #12C7BE, #11b5ac);
|
||||
|
||||
@@ -1,31 +1,41 @@
|
||||
<template>
|
||||
<!-- Agent会话岗位列表组件 — 聊天区域中显示推荐岗位卡片 -->
|
||||
<!-- Agent推荐岗位列表组件 — 展示推荐岗位卡片 -->
|
||||
<div class="agent-chat-job-list">
|
||||
<!-- 标题 -->
|
||||
<div class="agent-chat-job-list__header">为你推荐的岗位</div>
|
||||
<!-- 推荐说明文字 -->
|
||||
<div class="agent-chat-job-list__summary">{{ summary }}</div>
|
||||
<div class="agent-chat-job-list__summary">{{ summary || '根据求职目标和简历信息智能推荐' }}</div>
|
||||
|
||||
<!-- 岗位列表(只显示前3个) -->
|
||||
<!-- 岗位列表(显示前3个) -->
|
||||
<div
|
||||
v-for="job in displayJobs"
|
||||
:key="job.id"
|
||||
class="agent-chat-job-list__item"
|
||||
v-if="displayJobs.length>0"
|
||||
@click="handleClickJob(job)"
|
||||
>
|
||||
<!-- 左侧:公司图标 + 岗位信息 -->
|
||||
<div class="agent-chat-job-list__info">
|
||||
<!-- 公司 Logo -->
|
||||
<div class="agent-chat-job-list__logo">
|
||||
<img v-if="job.companyLogoUrl" :src="job.companyLogoUrl" :alt="job.companyShortName" />
|
||||
<svg v-else viewBox="0 0 24 24" fill="none">
|
||||
<path d="M3 21V7l9-4 9 4v14H3z" stroke="currentColor" stroke-width="1.5" />
|
||||
<path d="M9 21v-6h6v6" stroke="currentColor" stroke-width="1.5" />
|
||||
<!-- 左侧:匹配度环形 + 岗位信息 -->
|
||||
<div class="agent-chat-job-list__info" @click="handleClickJob(job)">
|
||||
<!-- 匹配度环形 -->
|
||||
<div class="agent-chat-job-list__score">
|
||||
<svg class="agent-chat-job-list__ring" viewBox="0 0 44 44">
|
||||
<circle cx="22" cy="22" r="18" fill="none" stroke="#E8E8E8" stroke-width="3" />
|
||||
<circle
|
||||
cx="22" cy="22" r="18" fill="none"
|
||||
stroke="#4FC2C9" stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
:stroke-dasharray="2 * Math.PI * 18"
|
||||
:stroke-dashoffset="2 * Math.PI * 18 * (1 - (job.matchScore || 0) / 100)"
|
||||
transform="rotate(-90 22 22)"
|
||||
/>
|
||||
</svg>
|
||||
<span class="agent-chat-job-list__score-text">{{ job.matchScore || 0 }}%</span>
|
||||
</div>
|
||||
<!-- 岗位详情 -->
|
||||
<div class="agent-chat-job-list__detail">
|
||||
<div class="agent-chat-job-list__company">{{ job.companyShortName || job.companyName }}</div>
|
||||
<div class="agent-chat-job-list__title">{{ job.title }}</div>
|
||||
<div class="agent-chat-job-list__title-row">
|
||||
<span class="agent-chat-job-list__title">{{ job.title }}</span>
|
||||
<span v-if="job.companyShortName || job.companyName" class="agent-chat-job-list__company-dot">●</span>
|
||||
<span class="agent-chat-job-list__company">{{ job.companyShortName || job.companyName }}</span>
|
||||
</div>
|
||||
<!-- 标签 -->
|
||||
<div class="agent-chat-job-list__tags">
|
||||
<span v-if="job.regionName" class="agent-chat-job-list__tag">{{ job.regionName }}</span>
|
||||
@@ -35,31 +45,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:匹配度环形 -->
|
||||
<div class="agent-chat-job-list__score">
|
||||
<svg class="agent-chat-job-list__ring" viewBox="0 0 44 44">
|
||||
<!-- 背景圆环 -->
|
||||
<circle cx="22" cy="22" r="18" fill="none" stroke="#E8E8E8" stroke-width="3" />
|
||||
<!-- 进度圆环 -->
|
||||
<circle
|
||||
cx="22" cy="22" r="18" fill="none"
|
||||
stroke="#4FC2C9" stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
:stroke-dasharray="2 * Math.PI * 18"
|
||||
:stroke-dashoffset="2 * Math.PI * 18 * (1 - (job.matchScore || 0) / 100)"
|
||||
transform="rotate(-90 22 22)"
|
||||
/>
|
||||
</svg>
|
||||
<span class="agent-chat-job-list__score-text">{{ job.matchScore || 0 }}%</span>
|
||||
<!-- 右侧:操作按钮 -->
|
||||
<div class="agent-chat-job-list__actions">
|
||||
<!-- 不再推荐 -->
|
||||
<span class="agent-chat-job-list__dismiss-btn" @click.stop="handleDismiss(job)">不再推荐</span>
|
||||
<!-- 添加/已加入按钮 -->
|
||||
<button
|
||||
v-if="job.applicationStatus === -1"
|
||||
class="agent-chat-job-list__add-btn agent-chat-job-list__add-btn--added"
|
||||
disabled
|
||||
>✓已加入</button>
|
||||
<button
|
||||
v-else
|
||||
class="agent-chat-job-list__add-btn"
|
||||
:disabled="loadingJobIds.includes(job.id)"
|
||||
@click.stop="handleAdd(job)"
|
||||
>+ 加入投递</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="color-9" v-if="displayJobs.length==0">
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div class="color-9" v-if="displayJobs.length === 0">
|
||||
暂无相关职位推荐
|
||||
</div>
|
||||
|
||||
<!-- 查看全部岗位按钮 -->
|
||||
<div v-if="displayJobs.length>0" class="agent-chat-job-list__footer">
|
||||
<button class="agent-chat-job-list__view-all-btn" @click="handleViewAll">查看全部岗位</button>
|
||||
<!-- 底部按钮区域 -->
|
||||
<div v-if="displayJobs.length > 0" class="agent-chat-job-list__footer">
|
||||
<!-- 换一批按钮 -->
|
||||
<button class="agent-chat-job-list__refresh-btn" :disabled="refreshing" @click="handleRefresh">
|
||||
{{ refreshing ? '加载中...' : '换一批' }}
|
||||
</button>
|
||||
<!-- 全部添加按钮 -->
|
||||
<button class="agent-chat-job-list__add-all-btn" @click="handleAddAll">全部添加</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -72,30 +89,57 @@ import type { AgentRecommendJob } from '@/api/agent'
|
||||
const props = defineProps<{
|
||||
/** 推荐说明文字 */
|
||||
summary: string
|
||||
/** 完整岗位列表数据 */
|
||||
/** 当前展示的岗位列表数据(父组件已随机抽取10个) */
|
||||
jobs: AgentRecommendJob[]
|
||||
/** 正在请求中的岗位 ID 列表 */
|
||||
loadingJobIds?: number[]
|
||||
/** 是否正在换一批加载中 */
|
||||
refreshing?: boolean
|
||||
}>()
|
||||
|
||||
/** 事件 */
|
||||
const emit = defineEmits<{
|
||||
/** 点击查看全部岗位 */
|
||||
/** 点击查看全部岗位(打开右侧面板) */
|
||||
(e: 'viewAll'): void
|
||||
/** 点击岗位查看详情 */
|
||||
(e: 'clickJob', job: AgentRecommendJob): void
|
||||
/** 添加单个岗位到待投递 */
|
||||
(e: 'add', job: AgentRecommendJob): void
|
||||
/** 全部添加 */
|
||||
(e: 'addAll'): void
|
||||
/** 换一批 */
|
||||
(e: 'refresh'): void
|
||||
/** 不再推荐 */
|
||||
(e: 'dismiss', job: AgentRecommendJob): void
|
||||
}>()
|
||||
|
||||
/** 只显示前3个岗位 */
|
||||
const displayJobs = computed(() => props.jobs.slice(0, 3))
|
||||
|
||||
/** 点击查看全部岗位 */
|
||||
function handleViewAll() {
|
||||
emit('viewAll')
|
||||
}
|
||||
|
||||
/** 点击岗位 — 通知父组件打开岗位预览 */
|
||||
function handleClickJob(job: AgentRecommendJob) {
|
||||
emit('clickJob', job)
|
||||
}
|
||||
|
||||
/** 添加岗位到待投递 */
|
||||
function handleAdd(job: AgentRecommendJob) {
|
||||
emit('add', job)
|
||||
}
|
||||
|
||||
/** 全部添加 */
|
||||
function handleAddAll() {
|
||||
emit('addAll')
|
||||
}
|
||||
|
||||
/** 换一批 */
|
||||
function handleRefresh() {
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
/** 不再推荐 */
|
||||
function handleDismiss(job: AgentRecommendJob) {
|
||||
emit('dismiss', job)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,207 +1,81 @@
|
||||
<template>
|
||||
<!-- Agent设置面板 — 右侧面板 -->
|
||||
<!-- Agent设置面板 — 右侧常驻面板(精简版) -->
|
||||
<div class="agent-setting-panel">
|
||||
<!-- 顶部标题栏 -->
|
||||
<div class="agent-setting-panel__header">
|
||||
<span class="agent-setting-panel__title">设置</span>
|
||||
<!-- 关闭按钮 -->
|
||||
<button class="agent-setting-panel__close-btn" @click="emit('close')">
|
||||
<svg viewBox="0 0 24 24" fill="none">
|
||||
<circle cx="12" cy="12" r="10" fill="#1A1A2E" />
|
||||
<path d="M8 8l8 8M16 8l-8 8" stroke="#fff" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域(可滚动) -->
|
||||
<div class="agent-setting-panel__body">
|
||||
|
||||
<!-- ========== 个人资料设置项 ========== -->
|
||||
<!-- ========== 个人资料 ========== -->
|
||||
<div class="agent-setting-panel__section">
|
||||
<!-- 个人资料标题行:标题 + 编辑按钮(含展开/收起箭头) -->
|
||||
<div class="agent-setting-panel__section-header" @click="toggleProfileExpand">
|
||||
<div class="agent-setting-panel__section-header">
|
||||
<span class="agent-setting-panel__section-title">个人资料</span>
|
||||
<div class="agent-setting-panel__section-action">
|
||||
<span class="agent-setting-panel__section-action-text">{{ profileExpanded ? '收起' : '编辑' }}</span>
|
||||
<!-- 方向箭头:展开时朝上,收起时朝下 -->
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
class="agent-setting-panel__arrow"
|
||||
:class="{ 'agent-setting-panel__arrow--up': profileExpanded }"
|
||||
>
|
||||
<path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="agent-setting-panel__edit-btn" @click="handleEditProfile">编辑 ✎</span>
|
||||
</div>
|
||||
|
||||
<!-- 收起时的简要信息预览 -->
|
||||
<div v-if="!profileExpanded" class="agent-setting-panel__section-summary">
|
||||
<span v-if="profile.name">{{ profile.name }}</span>
|
||||
<span v-if="profile.phone"> · {{ profile.phone }}</span>
|
||||
<span v-if="profile.email"> · {{ profile.email }}</span>
|
||||
<span v-if="!profile.name && !profile.phone && !profile.email" class="agent-setting-panel__section-empty">暂未填写个人资料</span>
|
||||
</div>
|
||||
|
||||
<!-- 展开时显示 ProfilePageContent 组件 -->
|
||||
<div v-if="profileExpanded" class="agent-setting-panel__section-content">
|
||||
<ProfilePageContent :profile="profile" @edit="handleEdit" />
|
||||
<!-- 简要信息展示 -->
|
||||
<div class="agent-setting-panel__profile-info">
|
||||
<div class="agent-setting-panel__profile-name">{{ profile.name || '未填写' }}</div>
|
||||
<div class="agent-setting-panel__profile-sub">{{ profileSubText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 其他设置项(待设计稿补充) ========== -->
|
||||
<!-- ========== 求职目标 ========== -->
|
||||
<div class="agent-setting-panel__section">
|
||||
<!-- 求职目标标题行 + 编辑按钮 -->
|
||||
<div class="agent-setting-panel__section-header" @click="showJobGoalDialog = true">
|
||||
<div class="agent-setting-panel__section-header">
|
||||
<span class="agent-setting-panel__section-title">求职目标</span>
|
||||
<div class="agent-setting-panel__section-action">
|
||||
<span class="agent-setting-panel__section-action-text">编辑</span>
|
||||
<svg viewBox="0 0 16 16" fill="none" class="agent-setting-panel__edit-icon">
|
||||
<path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="agent-setting-panel__edit-btn" @click="showJobGoalDialog = true">编辑 ✎</span>
|
||||
</div>
|
||||
<!-- 求职意向标签展示 -->
|
||||
<!-- 求职意向标签 -->
|
||||
<div class="agent-setting-panel__goal-tags">
|
||||
<span v-for="name in intentionCategoryNames" :key="'cat-' + name" class="agent-setting-panel__goal-tag">{{ name }}</span>
|
||||
<span v-for="name in intentionIndustryNames" :key="'ind-' + name" class="agent-setting-panel__goal-tag">{{ name }}</span>
|
||||
<span v-for="name in intentionRegionNames" :key="'reg-' + name" class="agent-setting-panel__goal-tag">{{ name }}</span>
|
||||
<span v-if="intentionEmploymentLabel" class="agent-setting-panel__goal-tag">{{ intentionEmploymentLabel }}</span>
|
||||
<!-- 无意向时的空状态 -->
|
||||
|
||||
<span v-for="name in intentionTags" :key="name" class="agent-setting-panel__goal-tag">{{ name }}</span>
|
||||
<span v-if="intentionTags.length === 0" class="agent-setting-panel__empty-text">暂未设置</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 投递简历 ========== -->
|
||||
<div class="agent-setting-panel__section">
|
||||
<div class="agent-setting-panel__section-header">
|
||||
<span class="agent-setting-panel__section-title">浏览器插件</span>
|
||||
<div class="agent-setting-panel__section-title">投递简历</div>
|
||||
<el-select v-model="selectedResumeId" placeholder="请选择简历" class="agent-setting-panel__resume-select" @change="handleResumeChange">
|
||||
<el-option v-for="r in resumeList" :key="r.id" :label="r.resumeName" :value="r.id || ''" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- ========== 投递偏好 ========== -->
|
||||
<div class="agent-setting-panel__section">
|
||||
<div class="agent-setting-panel__section-title">投递偏好</div>
|
||||
<!-- 简历针对性优化 -->
|
||||
<div class="agent-setting-panel__pref-item">
|
||||
<div class="agent-setting-panel__pref-left">
|
||||
<span class="agent-setting-panel__pref-check" :class="{ 'agent-setting-panel__pref-check--active': autoOptimizeSwitch }">
|
||||
<svg v-if="autoOptimizeSwitch" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.2"/><path d="M5 8l2 2 4-4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
<svg v-else viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.2"/></svg>
|
||||
</span>
|
||||
<div class="agent-setting-panel__pref-text">
|
||||
<span class="agent-setting-panel__pref-label">开启简历针对性优化</span>
|
||||
<span class="agent-setting-panel__pref-desc">结合岗位要求,自动优化简历表达</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-switch v-model="autoOptimizeSwitch" active-color="#4FC2C9" @change="handleSavePreference" />
|
||||
</div>
|
||||
<!-- 浏览器按钮列表 -->
|
||||
<!-- 深度人岗匹配分析 -->
|
||||
<div class="agent-setting-panel__pref-item">
|
||||
<div class="agent-setting-panel__pref-left">
|
||||
<span class="agent-setting-panel__pref-check" :class="{ 'agent-setting-panel__pref-check--active': autoMatchAnalysisSwitch }">
|
||||
<svg v-if="autoMatchAnalysisSwitch" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.2"/><path d="M5 8l2 2 4-4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
<svg v-else viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.2"/></svg>
|
||||
</span>
|
||||
<div class="agent-setting-panel__pref-text">
|
||||
<span class="agent-setting-panel__pref-label">开启深度人岗匹配分析</span>
|
||||
<span class="agent-setting-panel__pref-desc">根据岗位要求,分析你的匹配优势与差距</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-switch v-model="autoMatchAnalysisSwitch" active-color="#4FC2C9" @change="handleSavePreference" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 浏览器插件 ========== -->
|
||||
<div class="agent-setting-panel__section">
|
||||
<div class="agent-setting-panel__section-title">浏览器插件</div>
|
||||
<div class="agent-setting-panel__browser-btns">
|
||||
<button
|
||||
v-for="b in browserList"
|
||||
:key="b.key"
|
||||
class="agent-setting-panel__browser-btn"
|
||||
@click="openBrowserGuide(b.key)"
|
||||
>{{ b.label }}</button>
|
||||
</div>
|
||||
<!-- 下载插件按钮 -->
|
||||
<div class="agent-setting-panel__download-wrap">
|
||||
<button class="agent-setting-panel__download-btn" @click="downloadExtension">下载插件</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-setting-panel__section">
|
||||
<div class="agent-setting-panel__section-header">
|
||||
<span class="agent-setting-panel__section-title">求职助手配置</span>
|
||||
</div>
|
||||
|
||||
<!-- 网申常见问题 -->
|
||||
<div class="agent-setting-panel__config-group">
|
||||
<div class="agent-setting-panel__config-label">是否愿意接受部门调剂?</div>
|
||||
<div class="agent-setting-panel__config-options">
|
||||
<button class="agent-setting-panel__config-btn" :class="{ 'agent-setting-panel__config-btn--active': configForm.acceptDeptTransfer === '是,服从调剂' }" @click="configForm.acceptDeptTransfer = '是,服从调剂'">是,服从调剂</button>
|
||||
<button class="agent-setting-panel__config-btn" :class="{ 'agent-setting-panel__config-btn--active': configForm.acceptDeptTransfer === '否,不调剂' }" @click="configForm.acceptDeptTransfer = '否,不调剂'">否,不调剂</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-setting-panel__config-group">
|
||||
<div class="agent-setting-panel__config-label">是否接受地点调剂?</div>
|
||||
<div class="agent-setting-panel__config-options">
|
||||
<button class="agent-setting-panel__config-btn" :class="{ 'agent-setting-panel__config-btn--active': configForm.acceptLocationTransfer === '是' }" @click="configForm.acceptLocationTransfer = '是'">是</button>
|
||||
<button class="agent-setting-panel__config-btn" :class="{ 'agent-setting-panel__config-btn--active': configForm.acceptLocationTransfer === '否' }" @click="configForm.acceptLocationTransfer = '否'">否</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-setting-panel__config-group">
|
||||
<div class="agent-setting-panel__config-label">可以参加面试的方式?</div>
|
||||
<div class="agent-setting-panel__config-options">
|
||||
<button class="agent-setting-panel__config-btn" :class="{ 'agent-setting-panel__config-btn--active': configForm.interviewType.includes('线下面试') }" @click="toggleInterviewType('线下面试')">线下面试</button>
|
||||
<button class="agent-setting-panel__config-btn" :class="{ 'agent-setting-panel__config-btn--active': configForm.interviewType.includes('线上远程') }" @click="toggleInterviewType('线上远程')">线上远程</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-setting-panel__config-group">
|
||||
<div class="agent-setting-panel__config-label">你的语言能力?</div>
|
||||
<div class="agent-setting-panel__config-selects">
|
||||
<el-select v-model="configForm.languages[0].language" placeholder="语种" class="agent-setting-panel__config-select">
|
||||
<el-option v-for="lang in languageOptions" :key="lang" :label="lang" :value="lang" />
|
||||
</el-select>
|
||||
<el-select v-model="configForm.languages[0].proficiency" placeholder="掌握程度" class="agent-setting-panel__config-select">
|
||||
<el-option v-for="p in proficiencyOptions" :key="p" :label="p" :value="p" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-setting-panel__config-group">
|
||||
<div class="agent-setting-panel__config-label">预计到岗时间?</div>
|
||||
<div class="agent-setting-panel__config-selects">
|
||||
<el-select v-model="configForm.availableDate" placeholder="请选择" class="agent-setting-panel__config-select">
|
||||
<el-option v-for="d in availableDateOptions" :key="d" :label="d" :value="d" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实习相关(仅实习类型时显示) -->
|
||||
<template v-if="isInternship">
|
||||
<div class="agent-setting-panel__config-group">
|
||||
<div class="agent-setting-panel__config-label">每周可实习天数?</div>
|
||||
<div class="agent-setting-panel__config-selects">
|
||||
<el-select v-model="configForm.internDaysPerWeek" placeholder="请选择" class="agent-setting-panel__config-select">
|
||||
<el-option v-for="d in internDaysOptions" :key="d" :label="d" :value="d" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent-setting-panel__config-group">
|
||||
<div class="agent-setting-panel__config-label">预计实习时长?</div>
|
||||
<div class="agent-setting-panel__config-selects">
|
||||
<el-select v-model="configForm.internDuration" placeholder="请选择" class="agent-setting-panel__config-select">
|
||||
<el-option v-for="d in internDurationOptions" :key="d" :label="d" :value="d" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 简历设置 -->
|
||||
<div class="agent-setting-panel__config-group">
|
||||
<div class="agent-setting-panel__config-label">
|
||||
简历设置
|
||||
<el-tooltip content="选择投递时使用的默认简历" placement="top">
|
||||
<span class="agent-setting-panel__tip">ⓘ</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="agent-setting-panel__config-sub">设置默认简历</div>
|
||||
<div class="agent-setting-panel__resume-row">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="agent-setting-panel__resume-icon">
|
||||
<path d="M10 1H4a1.5 1.5 0 00-1.5 1.5v11A1.5 1.5 0 004 15h8a1.5 1.5 0 001.5-1.5V4.5L10 1z" stroke="currentColor" stroke-width="1"/>
|
||||
<path d="M10 1v3.5h3.5" stroke="currentColor" stroke-width="1"/>
|
||||
</svg>
|
||||
<el-select v-model="selectedResumeId" placeholder="请选择简历" class="agent-setting-panel__config-select agent-setting-panel__config-select--full">
|
||||
<el-option v-for="r in resumeList" :key="r.id" :label="r.resumeName" :value="r.id || ''" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自动优化简历开关 -->
|
||||
<div class="agent-setting-panel__config-group">
|
||||
<div class="agent-setting-panel__switch-row">
|
||||
<div class="agent-setting-panel__switch-text">
|
||||
<span>在投递时帮我针对岗位自动优化简历</span>
|
||||
<el-tooltip content="MVP只补充缺少技能" placement="top">
|
||||
<span class="agent-setting-panel__tip">ⓘ</span>
|
||||
</el-tooltip>
|
||||
<br/>
|
||||
<span class="agent-setting-panel__switch-sub">(MVP只补充缺少技能)</span>
|
||||
</div>
|
||||
<el-switch v-model="autoOptimizeSwitch" active-color="#4FC2C9" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提交设置按钮 -->
|
||||
<div class="agent-setting-panel__submit-wrap">
|
||||
<button class="agent-setting-panel__submit-btn" :disabled="configSaving" @click="handleSubmitConfig">
|
||||
{{ configSaving ? '保存中...' : '保存设置' }}
|
||||
</button>
|
||||
<button class="agent-setting-panel__browser-btn" @click="openBrowserGuide('chrome')">Chrome浏览器</button>
|
||||
<button class="agent-setting-panel__browser-btn" @click="openBrowserGuide('edge')">Edge浏览器</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -217,17 +91,6 @@
|
||||
|
||||
<!-- 求职目标设置弹窗 -->
|
||||
<JobGoalDialog v-model="showJobGoalDialog" />
|
||||
|
||||
<!-- 浏览器安装指引弹窗 -->
|
||||
<el-dialog v-model="showBrowserGuide" :title="currentBrowserLabel + ' 安装指引'" width="80%" top="5vh" destroy-on-close>
|
||||
<el-carousel :autoplay="false" indicator-position="outside" height="70vh" arrow="always">
|
||||
<el-carousel-item v-for="(img, i) in currentBrowserImages" :key="i">
|
||||
<div class="agent-setting-panel__guide-slide">
|
||||
<img :src="img" :alt="currentBrowserLabel + ' 安装步骤 ' + (i + 1)" />
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -235,240 +98,43 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { formatEmploymentType } from '@/stores/index'
|
||||
import ProfilePageContent from '@/components/ProfilePageContent.vue'
|
||||
import ProfileEditDrawer from '@/components/ProfileEditDrawer.vue'
|
||||
import JobGoalDialog from '@/components/JobGoalDialog.vue'
|
||||
import {
|
||||
saveProfile, saveEducation, saveWork, saveInternship, saveProject, saveCompetition,
|
||||
fetchProfile, fetchEducation, fetchWork, fetchInternship, fetchProject, fetchCompetition
|
||||
} from '@/api/profile'
|
||||
import type { SaveEducationItem, SaveWorkItem, SaveProjectItem, SaveCompetitionItem } from '@/api/profile'
|
||||
import { saveProfile, fetchProfile, fetchEducation } from '@/api/profile'
|
||||
import { resolveRegionName } from '@/utils/region'
|
||||
import { resolveIndustryName } from '@/utils/industry'
|
||||
import { resolveJobCategoryName } from '@/utils/jobCategory'
|
||||
import { fetchResumeList } from '@/api/resume'
|
||||
import { fetchResumeList, setDefaultResume } from '@/api/resume'
|
||||
import type { ResumeListItem } from '@/api/resume'
|
||||
import { fetchAgentConfig, saveAgentConfig } from '@/api/agent'
|
||||
|
||||
/** 事件 */
|
||||
const emit = defineEmits<{
|
||||
/** 关闭面板 */
|
||||
(e: 'close'): void
|
||||
}>()
|
||||
|
||||
const store = useStore()
|
||||
|
||||
// ==================== 个人资料展开/收起状态 ====================
|
||||
// ==================== 个人资料 ====================
|
||||
|
||||
/** 个人资料区域是否展开 */
|
||||
const profileExpanded = ref(false)
|
||||
|
||||
/** 切换个人资料展开/收起 */
|
||||
function toggleProfileExpand() {
|
||||
profileExpanded.value = !profileExpanded.value
|
||||
}
|
||||
|
||||
// ==================== 求职目标 ====================
|
||||
|
||||
/** 求职目标设置弹窗显隐 */
|
||||
const showJobGoalDialog = ref(false)
|
||||
|
||||
/** 求职意向 — 岗位类型名称列表 */
|
||||
const intentionCategoryNames = computed(() => (store.state.jobIntention?.categoryIds || []).map((id: number) => resolveJobCategoryName(id)).filter(Boolean))
|
||||
|
||||
/** 求职意向 — 行业名称列表 */
|
||||
const intentionIndustryNames = computed(() => (store.state.jobIntention?.industryIds || []).map((id: number) => resolveIndustryName(id)).filter(Boolean))
|
||||
|
||||
/** 求职意向 — 地区名称列表 */
|
||||
const intentionRegionNames = computed(() => (store.state.jobIntention?.regionCodes || []).map((code: string) => resolveRegionName(code)).filter(Boolean))
|
||||
|
||||
/** 求职意向 — 招聘分类文案 */
|
||||
const intentionEmploymentLabel = computed(() => formatEmploymentType(store.state.jobIntention?.recruitCategory))
|
||||
|
||||
// ==================== 浏览器插件 ====================
|
||||
|
||||
/** 插件下载地址 */
|
||||
const extensionDownloadUrl = 'https://offerpie.oss-cn-guangzhou.aliyuncs.com/extension/chrome-mv3-prod.rar'
|
||||
|
||||
/** 浏览器列表 */
|
||||
const browserList = [
|
||||
{ key: 'chrome', label: 'Chrome浏览器' },
|
||||
{ key: 'edge', label: 'Edge浏览器' },
|
||||
{ key: 'safari', label: 'Safari浏览器' },
|
||||
{ key: '360', label: '360浏览器' },
|
||||
{ key: 'qq', label: 'QQ浏览器' },
|
||||
]
|
||||
|
||||
/** 每个浏览器的指引图片数量 */
|
||||
const browserImageCount: Record<string, number> = { chrome: 3, edge: 3, safari: 3, '360': 3, qq: 3 }
|
||||
|
||||
/** 指引图片 OSS 基础路径 */
|
||||
const guidanceImageBase = 'http://offerpie.oss-cn-guangzhou.aliyuncs.com/extension/guidance_image'
|
||||
|
||||
/** 浏览器安装指引弹窗显隐 */
|
||||
const showBrowserGuide = ref(false)
|
||||
|
||||
/** 当前选中的浏览器 key */
|
||||
const currentBrowserKey = ref('')
|
||||
|
||||
/** 当前浏览器名称 */
|
||||
const currentBrowserLabel = computed(() => browserList.find(b => b.key === currentBrowserKey.value)?.label || '')
|
||||
|
||||
/** 当前浏览器的指引图片列表 */
|
||||
const currentBrowserImages = computed(() => {
|
||||
const key = currentBrowserKey.value
|
||||
if (!key) return []
|
||||
const count = browserImageCount[key] || 3
|
||||
return Array.from({ length: count }, (_, i) => `${guidanceImageBase}/guidance-image-${key}-${String(i + 1).padStart(2, '0')}.png`)
|
||||
})
|
||||
|
||||
/** 打开浏览器安装指引弹窗 */
|
||||
function openBrowserGuide(key: string) {
|
||||
currentBrowserKey.value = key
|
||||
showBrowserGuide.value = true
|
||||
}
|
||||
|
||||
/** 下载插件 */
|
||||
function downloadExtension() {
|
||||
window.open(extensionDownloadUrl, '_blank')
|
||||
}
|
||||
|
||||
// ==================== 求职助手配置 ====================
|
||||
|
||||
/** 是否为实习类型 */
|
||||
const isInternship = computed(() => store.state.jobIntention?.recruitCategory === 2)
|
||||
|
||||
/** 配置表单数据 */
|
||||
const configForm = ref({
|
||||
acceptDeptTransfer: '',
|
||||
acceptLocationTransfer: '',
|
||||
interviewType: [] as string[],
|
||||
languages: [{ language: '', proficiency: '' }] as Array<{ language: string; proficiency: string }>,
|
||||
availableDate: '',
|
||||
internDaysPerWeek: '',
|
||||
internDuration: '',
|
||||
})
|
||||
|
||||
/** 语种选项 */
|
||||
const languageOptions = ['英语', '日语', '法语', '德语', '韩语', '西班牙语', '俄语']
|
||||
|
||||
/** 掌握程度选项 */
|
||||
const proficiencyOptions = ['入门', '日常会话', '商务会话', '无障碍沟通', '母语']
|
||||
|
||||
/** 到岗时间选项 */
|
||||
const availableDateOptions = ['一周以内', '两周以内', '一个月以内', '一个月以上']
|
||||
|
||||
/** 实习天数选项 */
|
||||
const internDaysOptions = ['3天及以上', '4天及以上', '5天及以上']
|
||||
|
||||
/** 实习时长选项 */
|
||||
const internDurationOptions = ['3个月', '4个月', '5个月', '6个月及以上']
|
||||
|
||||
/** 切换面试方式(多选) */
|
||||
function toggleInterviewType(type: string) {
|
||||
const idx = configForm.value.interviewType.indexOf(type)
|
||||
idx >= 0 ? configForm.value.interviewType.splice(idx, 1) : configForm.value.interviewType.push(type)
|
||||
}
|
||||
|
||||
/** 简历列表 */
|
||||
const resumeList = ref<ResumeListItem[]>([])
|
||||
|
||||
/** 当前选中的简历 ID */
|
||||
const selectedResumeId = ref('')
|
||||
|
||||
/** 自动优化简历开关 */
|
||||
const autoOptimizeSwitch = ref(true)
|
||||
|
||||
/** 保存中状态 */
|
||||
const configSaving = ref(false)
|
||||
|
||||
/** 加载简历列表 */
|
||||
async function loadResumeList() {
|
||||
try {
|
||||
const res = await fetchResumeList()
|
||||
if (res.code === '0' && res.data) {
|
||||
resumeList.value = res.data
|
||||
const defaultResume = res.data.find(r => r.isDefault === 1)
|
||||
if (defaultResume && defaultResume.id) {
|
||||
selectedResumeId.value = defaultResume.id
|
||||
} else if (res.data.length > 0 && res.data[0].id) {
|
||||
selectedResumeId.value = res.data[0].id
|
||||
}
|
||||
}
|
||||
} catch { console.error('[AgentSettingPanel] 加载简历列表失败') }
|
||||
}
|
||||
|
||||
/** 加载已有的求职助手配置 — 填充表单 */
|
||||
async function loadAgentConfig() {
|
||||
try {
|
||||
const res = await fetchAgentConfig()
|
||||
if (res.code === '0' && res.data) {
|
||||
const cfg = res.data
|
||||
configForm.value.acceptDeptTransfer = cfg.acceptDeptTransfer || ''
|
||||
configForm.value.acceptLocationTransfer = cfg.acceptLocationTransfer || ''
|
||||
configForm.value.interviewType = cfg.interviewType ? [...cfg.interviewType] : []
|
||||
configForm.value.languages = cfg.languages?.length
|
||||
? cfg.languages.map(l => ({ language: l.language || '', proficiency: l.proficiency || '' }))
|
||||
: [{ language: '', proficiency: '' }]
|
||||
configForm.value.availableDate = cfg.availableDate || ''
|
||||
configForm.value.internDaysPerWeek = cfg.internDaysPerWeek || ''
|
||||
configForm.value.internDuration = cfg.internDuration || ''
|
||||
if (cfg.autoOptimizeResume !== undefined) autoOptimizeSwitch.value = cfg.autoOptimizeResume === 1
|
||||
}
|
||||
} catch { console.error('[AgentSettingPanel] 加载求职助手配置失败') }
|
||||
}
|
||||
|
||||
/** 提交设置 — 调用 saveAgentConfig 接口 */
|
||||
async function handleSubmitConfig() {
|
||||
configSaving.value = true
|
||||
try {
|
||||
await saveAgentConfig({
|
||||
acceptDeptTransfer: configForm.value.acceptDeptTransfer,
|
||||
acceptLocationTransfer: configForm.value.acceptLocationTransfer,
|
||||
interviewType: configForm.value.interviewType,
|
||||
languages: configForm.value.languages.filter(l => l.language),
|
||||
availableDate: configForm.value.availableDate,
|
||||
internDaysPerWeek: configForm.value.internDaysPerWeek,
|
||||
internDuration: configForm.value.internDuration,
|
||||
autoOptimizeResume: autoOptimizeSwitch.value ? 1 : 0,
|
||||
})
|
||||
ElMessage.success('设置保存成功')
|
||||
} catch {
|
||||
ElMessage.error('设置保存失败,请重试')
|
||||
} finally {
|
||||
configSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 个人资料数据 ====================
|
||||
|
||||
/** 个人档案响应式数据 */
|
||||
/** 个人资料数据 */
|
||||
const profile = ref({
|
||||
name: '', phone: '', email: '', idNumber: '', regionCode: '', portfolioUrl: '', wechat: '',
|
||||
skills: [] as string[], certificates: [] as string[],
|
||||
education: [] as Array<{ school: string; major: string; studyType: number; degree: number; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }>,
|
||||
works: [] as Array<{ companyName: string; position: string; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }>,
|
||||
internships: [] as Array<{ companyName: string; position: string; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }>,
|
||||
projects: [] as Array<{ projectName: string; companyName: string; role: string; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }>,
|
||||
competitions: [] as Array<{ competitionName: string; award: string; awardDate: string; description: Array<{ id: string; text: string }> }>,
|
||||
name: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
regionCode: '',
|
||||
wechat: '',
|
||||
skills: [] as string[],
|
||||
certificates: [] as string[],
|
||||
portfolioUrl: '',
|
||||
education: [] as Array<{ school: string; major: string; degree: number }>,
|
||||
})
|
||||
|
||||
// ==================== 加载个人资料 ====================
|
||||
|
||||
onMounted(async () => {
|
||||
if (!store.state.regions.length) store.dispatch('loadCommonData')
|
||||
store.dispatch('loadJobIntention')
|
||||
await loadProfile()
|
||||
await loadEducation()
|
||||
await loadWork()
|
||||
await loadInternship()
|
||||
await loadProject()
|
||||
await loadCompetition()
|
||||
await loadResumeList()
|
||||
await loadAgentConfig()
|
||||
/** 个人资料副标题(学校·学历) */
|
||||
const profileSubText = computed(() => {
|
||||
if (profile.value.education.length === 0) return ''
|
||||
const edu = profile.value.education[0]
|
||||
const degreeMap: Record<number, string> = { 1: '大专', 2: '本科', 3: '硕士', 4: '博士' }
|
||||
const parts = [edu.school, degreeMap[edu.degree]].filter(Boolean)
|
||||
return parts.join('·')
|
||||
})
|
||||
|
||||
/** 加载基本信息 */
|
||||
/** 加载个人资料 */
|
||||
async function loadProfile() {
|
||||
try {
|
||||
const res = await fetchProfile()
|
||||
@@ -477,181 +143,164 @@ async function loadProfile() {
|
||||
profile.value.name = d.name || ''
|
||||
profile.value.phone = d.mobileNumber || ''
|
||||
profile.value.email = d.email || ''
|
||||
profile.value.idNumber = d.idCard || ''
|
||||
profile.value.regionCode = d.regionCode || ''
|
||||
profile.value.wechat = d.wechatNumber || ''
|
||||
profile.value.skills = d.skills || []
|
||||
profile.value.certificates = d.certificates || []
|
||||
profile.value.portfolioUrl = d.portfolioUrl || ''
|
||||
}
|
||||
} catch { console.error('[AgentSettingPanel] 加载个人资料失败') }
|
||||
} catch { /* 静默 */ }
|
||||
}
|
||||
|
||||
/** 加载教育经历 */
|
||||
/** 加载教育经历(只取第一条显示) */
|
||||
async function loadEducation() {
|
||||
try {
|
||||
const res = await fetchEducation()
|
||||
if (res.code === '0' && res.data) {
|
||||
if (res.code === '0' && res.data && res.data.length > 0) {
|
||||
profile.value.education = res.data.map(item => ({
|
||||
school: item.school || '', major: item.major || '', studyType: item.studyType ?? 0,
|
||||
degree: item.degree ?? 2, startDate: item.startDate || '', endDate: item.endDate || '',
|
||||
description: (item.description || []).map(d => ({ id: d.id || '', text: d.text || '' }))
|
||||
school: item.school || '',
|
||||
major: item.major || '',
|
||||
degree: item.degree ?? 2,
|
||||
}))
|
||||
}
|
||||
} catch { console.error('[AgentSettingPanel] 加载教育经历失败') }
|
||||
}
|
||||
|
||||
/** 加载工作经历 */
|
||||
async function loadWork() {
|
||||
try {
|
||||
const res = await fetchWork()
|
||||
if (res.code === '0' && res.data) {
|
||||
profile.value.works = res.data.map(item => ({
|
||||
companyName: item.companyName || '', position: item.position || '',
|
||||
startDate: item.startDate || '', endDate: item.endDate || '',
|
||||
description: (item.description || []).map(d => ({ id: d.id || '', text: d.text || '' }))
|
||||
}))
|
||||
}
|
||||
} catch { console.error('[AgentSettingPanel] 加载工作经历失败') }
|
||||
}
|
||||
|
||||
/** 加载实习经历 */
|
||||
async function loadInternship() {
|
||||
try {
|
||||
const res = await fetchInternship()
|
||||
if (res.code === '0' && res.data) {
|
||||
profile.value.internships = res.data.map(item => ({
|
||||
companyName: item.companyName || '', position: item.position || '',
|
||||
startDate: item.startDate || '', endDate: item.endDate || '',
|
||||
description: (item.description || []).map(d => ({ id: d.id || '', text: d.text || '' }))
|
||||
}))
|
||||
}
|
||||
} catch { console.error('[AgentSettingPanel] 加载实习经历失败') }
|
||||
}
|
||||
|
||||
/** 加载项目经历 */
|
||||
async function loadProject() {
|
||||
try {
|
||||
const res = await fetchProject()
|
||||
if (res.code === '0' && res.data) {
|
||||
profile.value.projects = res.data.map(item => ({
|
||||
projectName: item.projectName || '', companyName: item.companyName || '',
|
||||
role: item.role || '', startDate: item.startDate || '', endDate: item.endDate || '',
|
||||
description: (item.description || []).map(d => ({ id: d.id || '', text: d.text || '' }))
|
||||
}))
|
||||
}
|
||||
} catch { console.error('[AgentSettingPanel] 加载项目经历失败') }
|
||||
}
|
||||
|
||||
/** 加载竞赛经历 */
|
||||
async function loadCompetition() {
|
||||
try {
|
||||
const res = await fetchCompetition()
|
||||
if (res.code === '0' && res.data) {
|
||||
profile.value.competitions = res.data.map(item => ({
|
||||
competitionName: item.competitionName || '', award: item.award || '',
|
||||
awardDate: item.awardDate || '',
|
||||
description: (item.description || []).map(d => ({ id: d.id || '', text: d.text || '' }))
|
||||
}))
|
||||
}
|
||||
} catch { console.error('[AgentSettingPanel] 加载竞赛经历失败') }
|
||||
} catch { /* 静默 */ }
|
||||
}
|
||||
|
||||
// ==================== 编辑抽屉 ====================
|
||||
|
||||
/** 编辑抽屉显隐 */
|
||||
const showEditDrawer = ref(false)
|
||||
|
||||
/** 当前编辑的模块名 */
|
||||
const editModule = ref('info')
|
||||
|
||||
/** 编辑抽屉初始数据 */
|
||||
const editInitialData = ref<Record<string, any>>({})
|
||||
|
||||
/** 保存中状态 */
|
||||
const saving = ref(false)
|
||||
|
||||
/** ProfilePageContent 点击编辑 — 打开对应模块的编辑抽屉 */
|
||||
function handleEdit(section: string) {
|
||||
editModule.value = section
|
||||
if (section === 'info') {
|
||||
editInitialData.value = { name: profile.value.name, email: profile.value.email, phone: profile.value.phone, location: profile.value.regionCode, wechat: profile.value.wechat }
|
||||
} else if (section === 'education') {
|
||||
editInitialData.value = { education: profile.value.education.map(edu => ({ ...edu, description: edu.description.map(d => ({ ...d })) })) }
|
||||
} else if (section === 'work') {
|
||||
editInitialData.value = { works: profile.value.works.map(exp => ({ ...exp, description: exp.description.map(d => ({ ...d })) })) }
|
||||
} else if (section === 'internship') {
|
||||
editInitialData.value = { internships: (profile.value.internships || []).map(exp => ({ ...exp, description: exp.description.map(d => ({ ...d })) })) }
|
||||
} else if (section === 'project') {
|
||||
editInitialData.value = { projects: (profile.value.projects || []).map(proj => ({ ...proj, description: proj.description.map(d => ({ ...d })) })) }
|
||||
} else if (section === 'competition') {
|
||||
editInitialData.value = { competitions: profile.value.competitions.map(comp => ({ ...comp, description: comp.description.map(d => ({ ...d })) })) }
|
||||
} else if (section === 'portfolio') {
|
||||
editInitialData.value = { portfolioUrl: profile.value.portfolioUrl }
|
||||
} else if (section === 'skills') {
|
||||
editInitialData.value = { skills: [...profile.value.skills] }
|
||||
} else if (section === 'certificate') {
|
||||
editInitialData.value = { certificates: [...(profile.value.certificates || [])] }
|
||||
} else {
|
||||
editInitialData.value = {}
|
||||
/** 打开编辑个人资料 */
|
||||
function handleEditProfile() {
|
||||
editModule.value = 'info'
|
||||
editInitialData.value = {
|
||||
name: profile.value.name,
|
||||
email: profile.value.email,
|
||||
phone: profile.value.phone,
|
||||
location: profile.value.regionCode,
|
||||
wechat: profile.value.wechat,
|
||||
}
|
||||
showEditDrawer.value = true
|
||||
}
|
||||
|
||||
/** 保存编辑数据 — 调用接口持久化 */
|
||||
/** 保存编辑 */
|
||||
async function handleSaveEdit(data: Record<string, any>) {
|
||||
saving.value = true
|
||||
try {
|
||||
if (editModule.value === 'info') {
|
||||
await saveProfile({ name: data.name, email: data.email, mobileNumber: data.phone, regionCode: data.location, wechatNumber: data.wechat })
|
||||
profile.value.name = data.name; profile.value.email = data.email
|
||||
profile.value.phone = data.phone; profile.value.wechat = data.wechat
|
||||
profile.value.regionCode = data.location || ''
|
||||
ElMessage.success('个人信息保存成功')
|
||||
} else if (editModule.value === 'education') {
|
||||
const payload: SaveEducationItem[] = data.education.map((edu: any) => ({ school: edu.school, major: edu.major, degree: edu.degree, studyType: edu.studyType, startDate: edu.startDate, endDate: edu.endDate, description: edu.description.map((d: any) => ({ id: d.id, text: d.text })) }))
|
||||
await saveEducation(payload)
|
||||
profile.value.education = data.education.map((edu: any) => ({ school: edu.school, major: edu.major, studyType: edu.studyType, degree: edu.degree, startDate: edu.startDate, endDate: edu.endDate, description: edu.description.map((d: any) => ({ ...d })) }))
|
||||
ElMessage.success('教育经历保存成功')
|
||||
} else if (editModule.value === 'work') {
|
||||
const payload: SaveWorkItem[] = data.works.map((w: any) => ({ companyName: w.companyName, position: w.position, startDate: w.startDate, endDate: w.endDate || '', description: w.description.map((d: any) => ({ id: d.id, text: d.text })) }))
|
||||
await saveWork(payload)
|
||||
profile.value.works = data.works.map((w: any) => ({ companyName: w.companyName, position: w.position, startDate: w.startDate, endDate: w.endDate, description: w.description.map((d: any) => ({ ...d })) }))
|
||||
ElMessage.success('工作经历保存成功')
|
||||
} else if (editModule.value === 'internship') {
|
||||
const payload: SaveWorkItem[] = data.internships.map((i: any) => ({ companyName: i.companyName, position: i.position, startDate: i.startDate, endDate: i.endDate || '', description: i.description.map((d: any) => ({ id: d.id, text: d.text })) }))
|
||||
await saveInternship(payload)
|
||||
profile.value.internships = data.internships.map((i: any) => ({ companyName: i.companyName, position: i.position, startDate: i.startDate, endDate: i.endDate, description: i.description.map((d: any) => ({ ...d })) }))
|
||||
ElMessage.success('实习经历保存成功')
|
||||
} else if (editModule.value === 'project') {
|
||||
const payload: SaveProjectItem[] = data.projects.map((p: any) => ({ projectName: p.projectName, companyName: p.companyName || '', role: p.role || '', startDate: p.startDate, endDate: p.endDate || '', description: p.description.map((d: any) => ({ id: d.id, text: d.text })) }))
|
||||
await saveProject(payload)
|
||||
profile.value.projects = data.projects.map((p: any) => ({ projectName: p.projectName, companyName: p.companyName, role: p.role, startDate: p.startDate, endDate: p.endDate, description: p.description.map((d: any) => ({ ...d })) }))
|
||||
ElMessage.success('项目经历保存成功')
|
||||
} else if (editModule.value === 'competition') {
|
||||
const payload: SaveCompetitionItem[] = data.competitions.map((c: any) => ({ competitionName: c.competitionName, award: c.award || '', awardDate: c.awardDate || '', description: c.description.map((d: any) => ({ id: d.id, text: d.text })) }))
|
||||
await saveCompetition(payload)
|
||||
profile.value.competitions = data.competitions.map((c: any) => ({ competitionName: c.competitionName, award: c.award, awardDate: c.awardDate, description: c.description.map((d: any) => ({ ...d })) }))
|
||||
ElMessage.success('竞赛经历保存成功')
|
||||
} else if (editModule.value === 'portfolio') {
|
||||
await saveProfile({ name: profile.value.name, email: profile.value.email, mobileNumber: profile.value.phone, regionCode: profile.value.regionCode, wechatNumber: profile.value.wechat, skills: profile.value.skills, certificates: profile.value.certificates, portfolioUrl: data.portfolioUrl })
|
||||
profile.value.portfolioUrl = data.portfolioUrl
|
||||
ElMessage.success('作品集保存成功')
|
||||
} else if (editModule.value === 'skills') {
|
||||
await saveProfile({ name: profile.value.name, email: profile.value.email, mobileNumber: profile.value.phone, regionCode: profile.value.regionCode, wechatNumber: profile.value.wechat, skills: [...data.skills], certificates: profile.value.certificates })
|
||||
profile.value.skills = [...data.skills]
|
||||
ElMessage.success('技能保存成功')
|
||||
} else if (editModule.value === 'certificate') {
|
||||
await saveProfile({ name: profile.value.name, email: profile.value.email, mobileNumber: profile.value.phone, regionCode: profile.value.regionCode, wechatNumber: profile.value.wechat, skills: profile.value.skills, certificates: [...data.certificates] })
|
||||
profile.value.certificates = [...data.certificates]
|
||||
ElMessage.success('证书保存成功')
|
||||
}
|
||||
await saveProfile({
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
mobileNumber: data.phone,
|
||||
regionCode: data.location,
|
||||
wechatNumber: data.wechat,
|
||||
})
|
||||
profile.value.name = data.name
|
||||
profile.value.email = data.email
|
||||
profile.value.phone = data.phone
|
||||
profile.value.wechat = data.wechat
|
||||
profile.value.regionCode = data.location || ''
|
||||
ElMessage.success('个人信息保存成功')
|
||||
} catch {
|
||||
ElMessage.error('保存失败,请重试')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 求职目标 ====================
|
||||
|
||||
const showJobGoalDialog = ref(false)
|
||||
|
||||
/** 求职意向标签(汇总岗位+行业+地区+招聘类型) */
|
||||
const intentionTags = computed(() => {
|
||||
const tags: string[] = []
|
||||
const catIds = store.state.jobIntention?.categoryIds || []
|
||||
const indIds = store.state.jobIntention?.industryIds || []
|
||||
const regCodes = store.state.jobIntention?.regionCodes || []
|
||||
catIds.forEach((id: number) => { const n = resolveJobCategoryName(id); if (n) tags.push(n) })
|
||||
indIds.forEach((id: number) => { const n = resolveIndustryName(id); if (n) tags.push(n) })
|
||||
regCodes.forEach((code: string) => { const n = resolveRegionName(code); if (n) tags.push(n) })
|
||||
const empLabel = formatEmploymentType(store.state.jobIntention?.recruitCategory)
|
||||
if (empLabel) tags.push(empLabel)
|
||||
return tags
|
||||
})
|
||||
|
||||
// ==================== 投递简历 ====================
|
||||
|
||||
const resumeList = ref<ResumeListItem[]>([])
|
||||
const selectedResumeId = ref('')
|
||||
|
||||
/** 加载简历列表 */
|
||||
async function loadResumeList() {
|
||||
try {
|
||||
const res = await fetchResumeList()
|
||||
if (res.code === '0' && res.data) {
|
||||
resumeList.value = res.data
|
||||
const defaultResume = res.data.find(r => r.isDefault === 1)
|
||||
selectedResumeId.value = defaultResume?.id || (res.data[0]?.id || '')
|
||||
}
|
||||
} catch { /* 静默 */ }
|
||||
}
|
||||
|
||||
/** 切换简历时设为默认 */
|
||||
async function handleResumeChange(newId: string) {
|
||||
if (!newId) return
|
||||
try {
|
||||
await setDefaultResume(newId)
|
||||
} catch { /* 静默 */ }
|
||||
}
|
||||
|
||||
// ==================== 投递偏好 ====================
|
||||
|
||||
const autoOptimizeSwitch = ref(true)
|
||||
const autoMatchAnalysisSwitch = ref(true)
|
||||
|
||||
/** 加载配置 */
|
||||
async function loadAgentConfigData() {
|
||||
try {
|
||||
const res = await fetchAgentConfig()
|
||||
if (res.code === '0' && res.data) {
|
||||
if (res.data.autoOptimizeResume !== undefined) autoOptimizeSwitch.value = res.data.autoOptimizeResume === 1
|
||||
if (res.data.autoMatchAnalysis !== undefined) autoMatchAnalysisSwitch.value = res.data.autoMatchAnalysis === 1
|
||||
}
|
||||
} catch { /* 静默 */ }
|
||||
}
|
||||
|
||||
/** 保存偏好设置 */
|
||||
async function handleSavePreference() {
|
||||
try {
|
||||
await saveAgentConfig({
|
||||
autoOptimizeResume: autoOptimizeSwitch.value ? 1 : 0,
|
||||
autoMatchAnalysis: autoMatchAnalysisSwitch.value ? 1 : 0,
|
||||
})
|
||||
} catch { /* 静默 */ }
|
||||
}
|
||||
|
||||
// ==================== 浏览器插件 ====================
|
||||
|
||||
/** 打开浏览器安装指引(简单跳转) */
|
||||
function openBrowserGuide(browser: string) {
|
||||
if (browser === 'chrome') {
|
||||
ElMessage.info('请复制地址 chrome://extensions 到浏览器打开')
|
||||
} else {
|
||||
ElMessage.info('请复制地址 edge://extensions/ 到浏览器打开')
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 初始化 ====================
|
||||
|
||||
onMounted(async () => {
|
||||
if (!store.state.regions.length) store.dispatch('loadCommonData')
|
||||
store.dispatch('loadJobIntention')
|
||||
await loadProfile()
|
||||
await loadEducation()
|
||||
await loadResumeList()
|
||||
await loadAgentConfigData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
+309
-252
@@ -31,183 +31,226 @@
|
||||
<div class="agent-page__main">
|
||||
<!-- ========== 第1步:确认个人资料 ========== -->
|
||||
<template v-if="currentStep === 1">
|
||||
<div class="agent-page__left">
|
||||
<div class="agent-page__intro-card">
|
||||
<div class="agent-page__intro-header">
|
||||
<div class="agent-page__intro-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg>
|
||||
</div>
|
||||
<h2 class="agent-page__intro-title">在开始之前请先确认你的个人资料是否无误</h2>
|
||||
</div>
|
||||
<p class="agent-page__intro-desc">请仔细对你的资料评估(包括个人信息、教育背景、工作履历及技能),确保资料齐全,让我能顺利为你开启求职之旅。</p>
|
||||
<div class="agent-page__import-row">
|
||||
<div class="agent-page__import-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</div>
|
||||
<span class="agent-page__import-text">{{ profile.name }}的个人资料</span>
|
||||
</div>
|
||||
<div class="dflex-center aliite-c">
|
||||
<button class="agent-page__confirm-btn" @click="handleNext">确认并进入</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent-page__right">
|
||||
<div class="agent-page__profile-wrapper">
|
||||
<div class="agent-page__profile-title">个人档案</div>
|
||||
<ProfilePageContent :profile="profile" @edit="handleEdit" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent-page__left">
|
||||
<div class="agent-page__intro-card">
|
||||
<div class="agent-page__intro-header por">
|
||||
<div v-if="false" class="agent-page__intro-icon poa l-22 t0">
|
||||
<svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg>
|
||||
</div>
|
||||
<h2 class="agent-page__intro-title">请先确认你的个人资料是否无误</h2>
|
||||
</div>
|
||||
<p class="agent-page__intro-desc mb50 ">请仔细对你的资料评估(包括个人信息、教育背景、工作履历及技能),确保资料齐全,让我能顺利为你开启求职之旅。</p>
|
||||
<div class="">
|
||||
<div class="tac"><button class="agent-page__confirm-btn dib" @click="handleNext">确认并进入</button></div>
|
||||
<div v-if="false" class="tac color-9 mt30 cursor-po" @click="handleBack">返回上一步</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<!-- ========== 第2步:确认目标 ========== -->
|
||||
<template v-if="currentStep === 2">
|
||||
<div class="agent-page__step2">
|
||||
<!-- 返回上一步按钮 -->
|
||||
<button class="agent-page__back-btn" @click="handleBack">
|
||||
<svg viewBox="0 0 24 24" fill="none" width="16" height="16"><path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
<span>返回上一步</span>
|
||||
</button>
|
||||
<div class="agent-page__chat">
|
||||
<div class="agent-page__chat-row">
|
||||
<div class="agent-page__chat-avatar"><svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg></div>
|
||||
<span class="agent-page__chat-text">接下来,我想确认一下我为你搜寻的职位方向是否准确。</span>
|
||||
</div>
|
||||
<div class="p20 bg-f border-ra10">
|
||||
<div class="agent-page__pref-card">
|
||||
<p class="agent-page__pref-desc">这些是你之前设定的求职偏好。如有需要,请随时修改。</p>
|
||||
<div class="agent-page__pref-tags">
|
||||
<span v-for="name in intentionCategoryNames" :key="'cat-' + name" class="agent-page__pref-tag">{{ name }}</span>
|
||||
<span v-for="name in intentionIndustryNames" :key="'ind-' + name" class="agent-page__pref-tag">{{ name }}</span>
|
||||
<span v-for="name in intentionRegionNames" :key="'reg-' + name" class="agent-page__pref-tag">{{ name }}</span>
|
||||
<span class="agent-page__pref-tag">{{ intentionEmploymentLabel }}</span>
|
||||
</div>
|
||||
<div class="agent-page__pref-actions">
|
||||
<button class="agent-page__pref-btn agent-page__pref-btn--edit" @click="showJobGoalDialog = true">编辑</button>
|
||||
<button class="agent-page__pref-btn agent-page__pref-btn--confirm" @click="handleNext">确认并继续</button>
|
||||
<!-- 左侧:求职偏好 + 简历设置 -->
|
||||
<div class="agent-page__right">
|
||||
<div class="agent-page__profile-wrapper">
|
||||
<!-- 求职偏好区域 -->
|
||||
<div class="agent-page__pref-section">
|
||||
<h3 class="agent-page__pref-section-title">求职偏好</h3>
|
||||
<p class="agent-page__pref-section-desc">这些是你之前设定的求职偏好。如有需要,请随时修改。</p>
|
||||
<div class="agent-page__pref-filters">
|
||||
<!-- 城市选择 -->
|
||||
<RegionSelector
|
||||
:regionCodes="step2RegionCodes"
|
||||
:level="2"
|
||||
:maxSelect="3"
|
||||
:triggerStyle="prefFilterTriggerStyle"
|
||||
:displayStyle="prefFilterDisplayStyle"
|
||||
@update:regionCodes="onStep2RegionChange"
|
||||
/>
|
||||
<!-- 岗位选择 -->
|
||||
<JobCategorySelector
|
||||
:categoryIds="step2CategoryIds"
|
||||
:maxSelect="3"
|
||||
:level="3"
|
||||
:allowParentSelect="true"
|
||||
:triggerStyle="prefFilterTriggerStyle"
|
||||
:displayStyle="prefFilterDisplayStyle"
|
||||
@update:categoryIds="onStep2CategoryChange"
|
||||
/>
|
||||
<!-- 行业选择 -->
|
||||
<IndustrySelector
|
||||
:industryIds="step2IndustryIds"
|
||||
:maxSelect="3"
|
||||
:level="2"
|
||||
:allowParentSelect="true"
|
||||
:triggerStyle="prefFilterTriggerStyle"
|
||||
:displayStyle="prefFilterDisplayStyle"
|
||||
@update:industryIds="onStep2IndustryChange"
|
||||
/>
|
||||
<!-- 招聘分类下拉选择 -->
|
||||
<div class="agent-page__pref-type-dropdown" @click.stop="showJobTypeDropdown = !showJobTypeDropdown">
|
||||
<span>{{ intentionEmploymentLabel || '类型' }}</span>
|
||||
<svg class="agent-page__pref-type-arrow" viewBox="0 0 12 12" fill="none">
|
||||
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<!-- 下拉菜单 -->
|
||||
<div v-if="showJobTypeDropdown" class="agent-page__pref-type-menu" @click.stop>
|
||||
<div
|
||||
v-for="option in jobTypeOptions"
|
||||
:key="option.value"
|
||||
class="agent-page__pref-type-menu-item"
|
||||
:class="{ 'agent-page__pref-type-menu-item--active': store.state.jobIntention.recruitCategory === option.value }"
|
||||
@click.stop="selectJobType(option)"
|
||||
>{{ option.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loadingMatchJobs" class="agent-page__chat-row">
|
||||
<div class="agent-page__chat-avatar"><svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg></div>
|
||||
<div class="agent-page__chat-typing"><span></span><span></span><span></span></div>
|
||||
</div>
|
||||
<div v-if="matchedJobs.length > 0" class="p20 bg-f border-ra10">
|
||||
<div class="agent-page__match-card">
|
||||
<p class="agent-page__match-desc">这些是为你匹配的岗位,请选择你想投递的岗位,如对某个岗位不满意请告诉我。</p>
|
||||
<div v-for="(job, i) in matchedJobs" :key="i" class="agent-page__match-item">
|
||||
<div class="agent-page__match-info">
|
||||
<div class="agent-page__match-icon"><svg viewBox="0 0 24 24" fill="none"><path d="M3 21h18M3 7v14M9 3v4M15 3v4M9 3h6M5 7h14a2 2 0 012 2v10H3V9a2 2 0 012-2z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
|
||||
<div class="agent-page__match-detail">
|
||||
<div class="agent-page__match-company">{{ job.companyShortName || job.companyName }}</div>
|
||||
<div class="agent-page__match-position">{{ job.title }}</div>
|
||||
<div class="agent-page__match-tags"><span v-for="(t, j) in job.tags" :key="j" class="agent-page__match-tag">{{ t }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent-page__match-score">
|
||||
<svg viewBox="0 0 40 40" class="agent-page__match-ring"><circle cx="20" cy="20" r="16" fill="none" stroke="#E8E8E8" stroke-width="3"/><circle cx="20" cy="20" r="16" fill="none" stroke="#000" stroke-width="3" :stroke-dasharray="100.53" :stroke-dashoffset="100.53 * (1 - job.matchScore / 100)" stroke-linecap="round" transform="rotate(-90 20 20)"/></svg>
|
||||
<span class="agent-page__match-score-text">{{ job.matchScore }}%</span>
|
||||
</div>
|
||||
<div class="agent-page__match-feedback">
|
||||
<button class="agent-page__match-fb-btn" :class="{ 'agent-page__match-fb-btn--active': job.feedback === 'like' }" @click="job.feedback = job.feedback === 'like' ? '' : 'like'"><svg viewBox="0 0 24 24" fill="none"><path d="M7 22V11l5-9 1.5 1 -1 5h6.5a2 2 0 012 2.2l-1.5 8A2 2 0 0117.6 20H7z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M4 11H2v11h2a1 1 0 001-1V12a1 1 0 00-1-1z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg></button>
|
||||
<button class="agent-page__match-fb-btn" :class="{ 'agent-page__match-fb-btn--active': job.feedback === 'dislike' }" @click="handleDislike(i)"><svg viewBox="0 0 24 24" fill="none"><path d="M17 2v11l-5 9-1.5-1 1-5H5a2 2 0 01-2-2.2l1.5-8A2 2 0 016.4 4H17z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M20 13h2V2h-2a1 1 0 00-1 1v9a1 1 0 001 1z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showDislikeInput" class="agent-page__dislike-input"><input v-model="dislikeReason" type="text" placeholder="你为什么对这个岗位不满意?" /></div>
|
||||
<!-- 简历设置区域 -->
|
||||
<div class="agent-page__resume-section">
|
||||
<h3 class="agent-page__pref-section-title">简历设置</h3>
|
||||
<div class="agent-page__resume-sub-label">设置默认简历</div>
|
||||
<div class="agent-page__resume-select-row">
|
||||
<span class="agent-page__resume-check-icon">
|
||||
<svg viewBox="0 0 16 16" fill="none"><rect x="2" y="2" width="12" height="12" rx="2" stroke="currentColor" stroke-width="1.2"/></svg>
|
||||
</span>
|
||||
<el-select v-model="selectedResumeId" placeholder="请选择简历" class="agent-page__resume-select">
|
||||
<el-option v-for="r in resumeList" :key="r.id" :label="r.resumeName" :value="r.id || ''" />
|
||||
</el-select>
|
||||
</div>
|
||||
<!-- 自动优化简历开关 -->
|
||||
<div class="agent-page__resume-switch-row">
|
||||
<span class="agent-page__resume-switch-text">在投递时帮我针对岗位自动优化简历。</span>
|
||||
<el-switch v-model="autoOptimizeSwitch" :active-color="'#4FC2C9'" />
|
||||
</div>
|
||||
<!-- 岗位匹配分析开关 -->
|
||||
<div class="agent-page__resume-switch-row">
|
||||
<span class="agent-page__resume-switch-text">在投递时帮我进行岗位匹配分析</span>
|
||||
<el-switch v-model="autoMatchAnalysisSwitch" :active-color="'#4FC2C9'" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="matchedJobs.length > 0" class="agent-page__step2-footer"><button class="agent-page__optimize-btn" @click="loadMatchedJobs">优化我的岗位匹配</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧:确认卡片 -->
|
||||
<div class="agent-page__left">
|
||||
<div class="agent-page__intro-card">
|
||||
<div class="agent-page__intro-header por">
|
||||
<div class="agent-page__intro-icon poa l-22 t0">
|
||||
<svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg>
|
||||
</div>
|
||||
<h2 class="agent-page__intro-title">接下来,我想确认一下我为你搜寻的职位方向是否准确。</h2>
|
||||
</div>
|
||||
<p class="agent-page__intro-desc mb50">请仔细对你的资料评估(包括个人信息、教育背景、工作履历及技能),确保资料齐全,让我能顺利为你开启求职之旅。</p>
|
||||
<div class="">
|
||||
<div class="tac"><button class="agent-page__confirm-btn dib" @click="handleNext">确认并进入</button></div>
|
||||
<div class="tac color-9 mt30 cursor-po" @click="handleBack">返回上一步</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ========== 第3步:网申常见问题 + 插件安装 ========== -->
|
||||
<!-- ========== 第3步:安装插件 ========== -->
|
||||
<template v-if="currentStep === 3">
|
||||
<!-- 上半部分:网申常见问题 -->
|
||||
<div v-if="step3Sub === 1" class="agent-page__step3">
|
||||
<!-- 返回上一步按钮 -->
|
||||
<button class="agent-page__back-btn" @click="handleBack">
|
||||
<svg viewBox="0 0 24 24" fill="none" width="16" height="16"><path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
<span>返回上一步</span>
|
||||
</button>
|
||||
<div class="agent-page__left">
|
||||
<div class="agent-page__intro-card">
|
||||
<div class="agent-page__intro-header"><div class="agent-page__intro-icon"><svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg></div><h2 class="agent-page__intro-title">现在我们来开启自动投递吧</h2></div>
|
||||
<p class="agent-page__intro-desc">以下是校招网申常见填写信息。<br/>现在填写一次,我即可在自动申请时帮你填写信息。</p>
|
||||
<div class="dflex-center aliite-c"><button class="agent-page__confirm-btn" @click="step3Sub = 2">确认并继续</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent-page__right">
|
||||
<div class="agent-page__form-wrapper">
|
||||
<div class="agent-page__form-title">网申常见问题</div>
|
||||
<div class="agent-page__form-group"><div class="agent-page__form-label">是否愿意接受部门调剂?</div><div class="agent-page__form-options"><button class="agent-page__form-option" :class="{ 'agent-page__form-option--active': step3Form.acceptDeptTransfer === '是,服从调剂' }" @click="step3Form.acceptDeptTransfer = '是,服从调剂'">是,服从调剂</button><button class="agent-page__form-option" :class="{ 'agent-page__form-option--active': step3Form.acceptDeptTransfer === '否,不调剂' }" @click="step3Form.acceptDeptTransfer = '否,不调剂'">否,不调剂</button></div></div>
|
||||
<div class="agent-page__form-group"><div class="agent-page__form-label">是否接受地点调剂?</div><div class="agent-page__form-options"><button class="agent-page__form-option" :class="{ 'agent-page__form-option--active': step3Form.acceptLocationTransfer === '是' }" @click="step3Form.acceptLocationTransfer = '是'">是</button><button class="agent-page__form-option" :class="{ 'agent-page__form-option--active': step3Form.acceptLocationTransfer === '否' }" @click="step3Form.acceptLocationTransfer = '否'">否</button></div></div>
|
||||
<div class="agent-page__form-group"><div class="agent-page__form-label">可以参加面试的方式?</div><div class="agent-page__form-options"><button class="agent-page__form-option" :class="{ 'agent-page__form-option--active': step3Form.interviewType.includes('线下面试') }" @click="toggleInterviewType('线下面试')">线下面试</button><button class="agent-page__form-option" :class="{ 'agent-page__form-option--active': step3Form.interviewType.includes('线上远程') }" @click="toggleInterviewType('线上远程')">线上远程</button></div></div>
|
||||
<div class="agent-page__form-group"><div class="agent-page__form-label">你的语言能力?</div><div class="agent-page__form-selects"><el-select v-model="step3Form.languages[0].language" placeholder="语种" class="agent-page__form-select"><el-option v-for="lang in languageOptions" :key="lang" :label="lang" :value="lang" /></el-select><el-select v-model="step3Form.languages[0].proficiency" placeholder="掌握程度" class="agent-page__form-select"><el-option v-for="p in proficiencyOptions" :key="p" :label="p" :value="p" /></el-select></div></div>
|
||||
<div class="agent-page__form-group"><div class="agent-page__form-label">预计到岗时间?</div><div class="agent-page__form-selects"><el-select v-model="step3Form.availableDate" placeholder="请选择" class="agent-page__form-select"><el-option v-for="d in availableDateOptions" :key="d" :label="d" :value="d" /></el-select></div></div>
|
||||
<template v-if="isInternship">
|
||||
<div class="agent-page__form-group"><div class="agent-page__form-label">每周可实习天数?</div><div class="agent-page__form-selects"><el-select v-model="step3Form.internDaysPerWeek" placeholder="请选择" class="agent-page__form-select"><el-option v-for="d in internDaysOptions" :key="d" :label="d" :value="d" /></el-select></div></div>
|
||||
<div class="agent-page__form-group"><div class="agent-page__form-label">预计实习时长?</div><div class="agent-page__form-selects"><el-select v-model="step3Form.internDuration" placeholder="请选择" class="agent-page__form-select"><el-option v-for="d in internDurationOptions" :key="d" :label="d" :value="d" /></el-select></div></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 下半部分:插件安装 -->
|
||||
<div v-if="step3Sub === 2" class="agent-page__step3">
|
||||
<!-- 返回上一阶段按钮 -->
|
||||
<button class="agent-page__back-btn" @click="handleBack">
|
||||
<svg viewBox="0 0 24 24" fill="none" width="16" height="16"><path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
<span>返回上一步</span>
|
||||
</button>
|
||||
<div class="agent-page__left">
|
||||
<div class="agent-page__intro-card">
|
||||
<div class="agent-page__intro-header"><div class="agent-page__intro-icon"><svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg></div><h2 class="agent-page__intro-title">现在我们来开启自动投递吧</h2></div>
|
||||
<p class="agent-page__intro-desc">简单三步安装 Chrome 扩展程序<br/>实现自动填表,并一站式追踪所有申请进度。</p>
|
||||
<div class="agent-page__install-steps"><p>1. 点击 [ 安装扩展程序 ],前往 Chrome 应用商店。</p><p>2. 点击 "添加至 Chrome" 并完成安装。</p><p>3. 刷新本页,然后点击下方按钮完成同步。</p></div>
|
||||
<div class="dflex-center aliite-c"><button class="agent-page__confirm-btn" @click="handleNext">我已安装</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent-page__right">
|
||||
<div class="agent-page__form-wrapper">
|
||||
<div class="agent-page__form-title">自动填写插件</div>
|
||||
<div class="agent-page__plugin-section">
|
||||
<h3 class="agent-page__plugin-title">自动填写插件</h3>
|
||||
<div class="agent-page__browser-btns"><button v-for="b in browserList" :key="b.key" class="agent-page__browser-btn" @click="openBrowserGuide(b.key)">{{ b.label }}</button></div>
|
||||
<div class="dflex-center aliite-c" style="margin-top: 0.24rem;"><button class="agent-page__confirm-btn" @click="downloadExtension">下载插件</button></div>
|
||||
<!-- 左侧:安装教程内容 -->
|
||||
<div class="agent-page__right">
|
||||
<div class="agent-page__profile-wrapper">
|
||||
<div class="agent-page__install-guide">
|
||||
<h3 class="agent-page__install-guide-title">安装自动填写插件</h3>
|
||||
|
||||
<!-- 步骤1:下载插件包 -->
|
||||
<div class="agent-page__install-step">
|
||||
<p class="agent-page__install-step-text">1、下载下方的插件包</p>
|
||||
<div class="agent-page__install-download">
|
||||
<button class="agent-page__install-download-btn" @click="downloadExtension">
|
||||
<span>Offer派自动投递助手.crx</span>
|
||||
<svg viewBox="0 0 16 16" fill="none" width="14" height="14"><path d="M8 2v8M8 10L5 7M8 10l3-3M3 13h10" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤2:复制地址进入扩展程序页面 -->
|
||||
<div class="agent-page__install-step">
|
||||
<p class="agent-page__install-step-text">2、复制右下方的蓝色地址,在浏览器地址栏粘贴并进入浏览器的扩展程序页面</p>
|
||||
<div class="agent-page__install-step-content">
|
||||
<div class="agent-page__install-step-img">
|
||||
<img src="@/assets/images/agent-setting/2-1.png" alt="地址栏示例" />
|
||||
</div>
|
||||
<div class="agent-page__install-browser-links">
|
||||
<div class="agent-page__install-browser-item">
|
||||
<img src="@/assets/images/agent-setting/2-2.png" style="width: 3rem;height: 0.3rem;" alt="Chrome" class="agent-page__install-browser-icon" />
|
||||
|
||||
</div>
|
||||
<a class="agent-page__install-browser-url" href="javascript:void(0)" @click="copyToClipboard('chrome://extensions')">
|
||||
chrome://extensions
|
||||
<svg viewBox="0 0 14 14" fill="none" width="12" height="12"><rect x="4" y="4" width="8" height="8" rx="1" stroke="currentColor" stroke-width="1"/><path d="M10 4V3a1 1 0 00-1-1H3a1 1 0 00-1 1v6a1 1 0 001 1h1" stroke="currentColor" stroke-width="1"/></svg>
|
||||
</a>
|
||||
<div class="agent-page__install-browser-item mt16">
|
||||
<img src="@/assets/images/agent-setting/2-3.png" style="width: 3rem;height: 0.3rem;" alt="Edge" class="agent-page__install-browser-icon" />
|
||||
|
||||
</div>
|
||||
<a class="agent-page__install-browser-url" href="javascript:void(0)" @click="copyToClipboard('edge://extensions/')">
|
||||
edge://extensions/
|
||||
<svg viewBox="0 0 14 14" fill="none" width="12" height="12"><rect x="4" y="4" width="8" height="8" rx="1" stroke="currentColor" stroke-width="1"/><path d="M10 4V3a1 1 0 00-1-1H3a1 1 0 00-1 1v6a1 1 0 001 1h1" stroke="currentColor" stroke-width="1"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤3:打开开发人员模式 -->
|
||||
<div class="agent-page__install-step">
|
||||
<p class="agent-page__install-step-text">3、在浏览器拓展页面中打开「开发人员模式」</p>
|
||||
<div class="agent-page__install-step-img-row">
|
||||
<div class="agent-page__install-step-img-item">
|
||||
<img src="@/assets/images/agent-setting/3-1.png" alt="打开开发人员模式" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤4:拖入插件包 -->
|
||||
<div class="agent-page__install-step">
|
||||
<p class="agent-page__install-step-text">4、将下载好的「离线插件包」使用鼠标拖入到浏览器的扩展程序页面</p>
|
||||
<div class="agent-page__install-step-img-row">
|
||||
<div class="agent-page__install-step-img-item">
|
||||
<img src="@/assets/images/agent-setting/4-1.png" alt="拖入插件包" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 浏览器安装指引弹窗 -->
|
||||
<el-dialog v-model="showBrowserGuide" :title="currentBrowserLabel + ' 安装指引'" width="80%" top="5vh" destroy-on-close>
|
||||
<el-carousel :autoplay="false" indicator-position="outside" height="70vh" arrow="always">
|
||||
<el-carousel-item v-for="(img, i) in currentBrowserImages" :key="i"><div class="agent-page__guide-slide"><img :src="img" :alt="currentBrowserLabel + ' 安装步骤 ' + (i + 1)" /></div></el-carousel-item>
|
||||
</el-carousel>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<!-- ========== 第4步:配置求职助手 ========== -->
|
||||
<template v-if="currentStep === 4">
|
||||
<div v-if="!setupComplete" class="agent-page__step3">
|
||||
<!-- 返回上一步按钮 -->
|
||||
<button class="agent-page__back-btn" @click="handleBack">
|
||||
<svg viewBox="0 0 24 24" fill="none" width="16" height="16"><path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
<span>返回上一步</span>
|
||||
</button>
|
||||
<div class="agent-page__left">
|
||||
<div class="agent-page__intro-card">
|
||||
<div class="agent-page__intro-header"><div class="agent-page__intro-icon"><svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg></div><h2 class="agent-page__intro-title">马上就好啦!请选择你想要的投递模式</h2></div>
|
||||
<p class="agent-page__intro-desc">我已为你准备了关于自动化程度、简历生成及求职目标的灵活选项。请完成配置并确认。</p>
|
||||
<div class="dflex-center aliite-c"><button class="agent-page__confirm-btn" @click="handleStep4Complete">设置完成</button></div>
|
||||
<!-- 右侧:确认卡片 -->
|
||||
<div class="agent-page__left">
|
||||
<div class="agent-page__intro-card">
|
||||
<div class="agent-page__intro-header por">
|
||||
<div class="agent-page__intro-icon poa l-22 t0">
|
||||
<svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg>
|
||||
</div>
|
||||
<h2 class="agent-page__intro-title">现在我们来开启自动投递吧</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent-page__right">
|
||||
<div class="agent-page__form-wrapper">
|
||||
<div class="agent-page__form-title">Agent设置</div>
|
||||
<AgentSettingsPanel ref="settingsPanelRef" :initial-config="initialConfig" />
|
||||
<p class="agent-page__intro-desc mb20">简单四步安装 Chrome 扩展程序<br/>实现自动填表,并一站式追踪所有申请进度。</p>
|
||||
<div class="agent-page__install-steps-list">
|
||||
<p>1. 下载offer派自动投递助手插件包</p>
|
||||
<p>2. 进入浏览器扩展程序界面</p>
|
||||
<p>3. 打开扩展程序开发人员模式</p>
|
||||
<p>4.拖入安装包,完成安装!</p>
|
||||
</div>
|
||||
<div class="mt30">
|
||||
<div class="tac"><button class="agent-page__confirm-btn dib" @click="handleNext">我已安装</button></div>
|
||||
<div class="tac color-9 mt30 cursor-po" @click="handleBack">返回上一步</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="agent-page__complete">
|
||||
</template>
|
||||
|
||||
<!-- ========== 第4步:完成配置 ========== -->
|
||||
<template v-if="currentStep === 4">
|
||||
<div class="agent-page__complete">
|
||||
<div class="agent-page__complete-icon"><svg viewBox="0 0 80 80" fill="none"><rect x="8" y="8" width="64" height="64" rx="8" fill="#E8E8E8"/><circle cx="40" cy="32" r="8" fill="#BFBFBF"/><path d="M20 60 L32 44 L44 52 L56 36 L64 44 L64 64 L20 64Z" fill="#BFBFBF"/></svg></div>
|
||||
<h2 class="agent-page__complete-title">恭喜你!你的求职助手已经准备好。</h2>
|
||||
<p class="agent-page__complete-desc">启用求职助手后你可以随时调整求职偏好及简历信息。</p>
|
||||
@@ -222,20 +265,19 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { formatEmploymentType } from '@/stores/index'
|
||||
import { formatEmploymentType, JOB_TYPE_OPTIONS } from '@/stores/index'
|
||||
import ProfilePageContent from '@/components/ProfilePageContent.vue'
|
||||
import ProfileEditDrawer from '@/components/ProfileEditDrawer.vue'
|
||||
import JobGoalDialog from '@/components/JobGoalDialog.vue'
|
||||
import AgentSettingsPanel from '@/components/AgentSettingsPanel.vue'
|
||||
import RegionSelector from '@/components/tools/RegionSelector.vue'
|
||||
import JobCategorySelector from '@/components/tools/JobCategorySelector.vue'
|
||||
import IndustrySelector from '@/components/tools/IndustrySelector.vue'
|
||||
import { saveProfile, saveEducation, saveWork, saveInternship, saveProject, saveCompetition, fetchProfile, fetchEducation, fetchWork, fetchInternship, fetchProject, fetchCompetition } from '@/api/profile'
|
||||
import type { SaveEducationItem, SaveWorkItem, SaveProjectItem, SaveCompetitionItem } from '@/api/profile'
|
||||
import { fetchJobList } from '@/api/jobs'
|
||||
import type { JobListItem } from '@/api/jobs'
|
||||
import { resolveRegionName } from '@/utils/region'
|
||||
import { resolveIndustryName } from '@/utils/industry'
|
||||
import { resolveJobCategoryName } from '@/utils/jobCategory'
|
||||
import { fetchResumeList, setDefaultResume } from '@/api/resume'
|
||||
import type { ResumeListItem } from '@/api/resume'
|
||||
import type { AgentConfig } from '@/api/agent'
|
||||
|
||||
const store = useStore()
|
||||
@@ -253,7 +295,7 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
// ==================== 步骤导航 ====================
|
||||
const steps = ['确认个人资料', '确认目标', '开启自动申请', '配置求职助手']
|
||||
const steps = ['确认个人资料', '确认目标', '安装插件', '配置求职助手']
|
||||
const currentStep = ref(1)
|
||||
|
||||
/** 进入下一步 */
|
||||
@@ -261,16 +303,9 @@ function handleNext() {
|
||||
if (currentStep.value < steps.length) currentStep.value++
|
||||
}
|
||||
|
||||
/** 返回上一步(含子阶段判断) */
|
||||
/** 返回上一步 */
|
||||
function handleBack() {
|
||||
if (currentStep.value === 3 && step3Sub.value === 2) {
|
||||
// 第3步的第2阶段 → 回到第3步第1阶段
|
||||
step3Sub.value = 1
|
||||
} else if (currentStep.value === 4) {
|
||||
// 第4步 → 回到第3步第2阶段(插件安装)
|
||||
currentStep.value = 3
|
||||
step3Sub.value = 2
|
||||
} else if (currentStep.value > 1) {
|
||||
if (currentStep.value > 1) {
|
||||
currentStep.value--
|
||||
}
|
||||
}
|
||||
@@ -400,106 +435,128 @@ async function handleSaveEdit(data: Record<string, any>) {
|
||||
|
||||
// ==================== 第2步:确认目标 ====================
|
||||
const showJobGoalDialog = ref(false)
|
||||
const intentionCategoryNames = computed(() => (store.state.jobIntention.categoryIds || []).map((id: number) => resolveJobCategoryName(id)))
|
||||
const intentionIndustryNames = computed(() => (store.state.jobIntention.industryIds || []).map((id: number) => resolveIndustryName(id)))
|
||||
const intentionRegionNames = computed(() => (store.state.jobIntention.regionCodes || []).map((code: string) => resolveRegionName(code)))
|
||||
const intentionEmploymentLabel = computed(() => formatEmploymentType(store.state.jobIntention.recruitCategory))
|
||||
|
||||
interface MatchedJobItem extends JobListItem { feedback: string }
|
||||
const matchedJobs = ref<MatchedJobItem[]>([])
|
||||
const loadingMatchJobs = ref(false)
|
||||
const showDislikeInput = ref(false)
|
||||
const dislikeReason = ref('')
|
||||
/** 招聘分类选项 */
|
||||
const jobTypeOptions = JOB_TYPE_OPTIONS
|
||||
/** 招聘分类下拉是否展开 */
|
||||
const showJobTypeDropdown = ref(false)
|
||||
|
||||
watch(showJobGoalDialog, (n, o) => { if (o === true && n === false) loadMatchedJobs() })
|
||||
watch(currentStep, (val) => { if (val === 2) loadMatchedJobs() })
|
||||
/** 选中招聘分类 */
|
||||
function selectJobType(option: { label: string; value: number }) {
|
||||
showJobTypeDropdown.value = false
|
||||
store.dispatch('saveJobIntention', {
|
||||
...store.state.jobIntention,
|
||||
recruitCategory: option.value,
|
||||
})
|
||||
}
|
||||
|
||||
async function loadMatchedJobs() {
|
||||
loadingMatchJobs.value = true; matchedJobs.value = []
|
||||
/** 第2步:求职偏好筛选组件 — 读写 store.jobIntention */
|
||||
const step2RegionCodes = computed<string[]>(() => store.state.jobIntention.regionCodes || [])
|
||||
const step2CategoryIds = computed<number[]>(() => store.state.jobIntention.categoryIds || [])
|
||||
const step2IndustryIds = computed<number[]>(() => store.state.jobIntention.industryIds || [])
|
||||
|
||||
/** 第2步:筛选组件统一触发按钮样式 — 大圆角胶囊按钮 */
|
||||
const prefFilterTriggerStyle = {
|
||||
padding: '0.1rem 0.28rem',
|
||||
borderRadius: '0.24rem',
|
||||
fontSize: '0.14rem',
|
||||
background: '#F5FAF9',
|
||||
border: '1px solid #e0e0e0',
|
||||
minWidth: '0.9rem',
|
||||
justifyContent: 'center',
|
||||
height: '0.38rem',
|
||||
boxSizing: 'border-box',
|
||||
}
|
||||
/** 第2步:筛选组件显示文字样式 */
|
||||
const prefFilterDisplayStyle = {
|
||||
maxWidth: 'none',
|
||||
}
|
||||
|
||||
/** 第2步:地区选择变更 */
|
||||
function onStep2RegionChange(codes: string[]) {
|
||||
store.dispatch('saveJobIntention', { ...store.state.jobIntention, regionCodes: codes })
|
||||
}
|
||||
|
||||
/** 第2步:岗位选择变更 */
|
||||
function onStep2CategoryChange(ids: number[]) {
|
||||
store.dispatch('saveJobIntention', { ...store.state.jobIntention, categoryIds: ids })
|
||||
}
|
||||
|
||||
/** 第2步:行业选择变更 */
|
||||
function onStep2IndustryChange(ids: number[]) {
|
||||
store.dispatch('saveJobIntention', { ...store.state.jobIntention, industryIds: ids })
|
||||
}
|
||||
|
||||
/** 第2步:简历列表 */
|
||||
const resumeList = ref<ResumeListItem[]>([])
|
||||
/** 选中的简历 ID */
|
||||
const selectedResumeId = ref('')
|
||||
/** 自动优化简历开关 */
|
||||
const autoOptimizeSwitch = ref(true)
|
||||
/** 岗位匹配分析开关 */
|
||||
const autoMatchAnalysisSwitch = ref(true)
|
||||
|
||||
/** 加载简历列表 */
|
||||
async function loadResumeList() {
|
||||
try {
|
||||
const intention = store.state.jobIntention
|
||||
const res = await fetchJobList({ pageNum: 1, pageSize: 30, regionCodes: intention.regionCodes?.length ? intention.regionCodes : undefined, categoryIds: intention.categoryIds?.length ? intention.categoryIds : undefined, industryIds: intention.industryIds?.length ? intention.industryIds : undefined, recruitCategory: intention.recruitCategory ?? undefined })
|
||||
if (res.code === '0' && res.data && res.data.list.length > 0) {
|
||||
const shuffled = [...res.data.list].sort(() => Math.random() - 0.5)
|
||||
matchedJobs.value = shuffled.slice(0, 3).map(item => ({ ...item, feedback: '' }))
|
||||
const res = await fetchResumeList()
|
||||
if (res.code === '0' && res.data) {
|
||||
resumeList.value = res.data
|
||||
const defaultResume = res.data.find(r => r.isDefault === 1)
|
||||
if (defaultResume && defaultResume.id) {
|
||||
selectedResumeId.value = defaultResume.id
|
||||
} else if (res.data.length > 0 && res.data[0].id) {
|
||||
selectedResumeId.value = res.data[0].id
|
||||
}
|
||||
}
|
||||
} catch (e) { console.error('[AgentSetupWizard] 加载匹配岗位失败', e) }
|
||||
finally { loadingMatchJobs.value = false }
|
||||
} catch { console.error('[AgentSetupWizard] 加载简历列表失败') }
|
||||
}
|
||||
|
||||
function handleDislike(index: number) {
|
||||
const job = matchedJobs.value[index]
|
||||
if (job.feedback === 'dislike') { job.feedback = ''; showDislikeInput.value = false }
|
||||
else { job.feedback = 'dislike'; showDislikeInput.value = true }
|
||||
}
|
||||
/** 进入第2步时加载简历列表 */
|
||||
watch(currentStep, (val) => { if (val === 2) loadResumeList() })
|
||||
|
||||
// ==================== 第3步:网申常见问题 ====================
|
||||
const isInternship = computed(() => store.state.jobIntention.recruitCategory === 2)
|
||||
const step3Sub = ref(1)
|
||||
const step3Form = reactive({
|
||||
acceptDeptTransfer: '', acceptLocationTransfer: '',
|
||||
interviewType: [] as string[],
|
||||
languages: [{ language: '', proficiency: '' }] as Array<{ language: string; proficiency: string }>,
|
||||
availableDate: '', internDaysPerWeek: '', internDuration: '',
|
||||
/** 选择简历后设为默认简历 */
|
||||
watch(selectedResumeId, async (newId, oldId) => {
|
||||
// 仅在用户主动切换时调用接口(非初始化加载)
|
||||
if (!newId || !oldId) return
|
||||
try {
|
||||
await setDefaultResume(newId)
|
||||
} catch { console.error('[AgentSetupWizard] 设置默认简历失败') }
|
||||
})
|
||||
|
||||
/** 监听初始配置数据 — 填充第3步表单 */
|
||||
watch(() => props.initialConfig, (cfg) => {
|
||||
if (!cfg) return
|
||||
step3Form.acceptDeptTransfer = cfg.acceptDeptTransfer || ''
|
||||
step3Form.acceptLocationTransfer = cfg.acceptLocationTransfer || ''
|
||||
step3Form.interviewType = cfg.interviewType ? [...cfg.interviewType] : []
|
||||
step3Form.languages = cfg.languages?.length ? cfg.languages.map(l => ({ language: l.language || '', proficiency: l.proficiency || '' })) : [{ language: '', proficiency: '' }]
|
||||
step3Form.availableDate = cfg.availableDate || ''
|
||||
step3Form.internDaysPerWeek = cfg.internDaysPerWeek || ''
|
||||
step3Form.internDuration = cfg.internDuration || ''
|
||||
}, { immediate: true })
|
||||
const languageOptions = ['英语', '日语', '法语', '德语', '韩语', '西班牙语', '俄语']
|
||||
const proficiencyOptions = ['入门', '日常会话', '商务会话', '无障碍沟通', '母语']
|
||||
const availableDateOptions = ['一周以内', '两周以内', '一个月以内', '一个月以上']
|
||||
const internDaysOptions = ['3天及以上', '4天及以上', '5天及以上']
|
||||
const internDurationOptions = ['3个月', '4个月', '5个月', '6个月及以上']
|
||||
function toggleInterviewType(type: string) { const idx = step3Form.interviewType.indexOf(type); idx >= 0 ? step3Form.interviewType.splice(idx, 1) : step3Form.interviewType.push(type) }
|
||||
|
||||
// ==================== 第3步下半部分:插件安装 ====================
|
||||
// ==================== 第3步:安装插件 ====================
|
||||
/** 插件下载地址 */
|
||||
const extensionDownloadUrl = 'https://offerpie.oss-cn-guangzhou.aliyuncs.com/extension/chrome-mv3-prod.rar'
|
||||
const browserList = [{ key: 'chrome', label: 'Chrome浏览器' }, { key: 'edge', label: 'Edge浏览器' }, { key: 'safari', label: 'Safari浏览器' }, { key: '360', label: '360浏览器' }, { key: 'qq', label: 'QQ浏览器' }]
|
||||
const browserImageCount: Record<string, number> = { chrome: 3, edge: 3, safari: 3, '360': 3, qq: 3 }
|
||||
const guidanceImageBase = 'http://offerpie.oss-cn-guangzhou.aliyuncs.com/extension/guidance_image'
|
||||
const showBrowserGuide = ref(false)
|
||||
const currentBrowserKey = ref('')
|
||||
const currentBrowserLabel = computed(() => browserList.find(b => b.key === currentBrowserKey.value)?.label || '')
|
||||
const currentBrowserImages = computed(() => { const key = currentBrowserKey.value; if (!key) return []; const count = browserImageCount[key] || 3; return Array.from({ length: count }, (_, i) => `${guidanceImageBase}/guidance-image-${key}-${String(i + 1).padStart(2, '0')}.png`) })
|
||||
function openBrowserGuide(key: string) { currentBrowserKey.value = key; showBrowserGuide.value = true }
|
||||
|
||||
/** 下载插件 */
|
||||
function downloadExtension() { window.open(extensionDownloadUrl, '_blank') }
|
||||
|
||||
// ==================== 第4步:配置求职助手 ====================
|
||||
const settingsPanelRef = ref<InstanceType<typeof AgentSettingsPanel> | null>(null)
|
||||
const setupComplete = ref(false)
|
||||
|
||||
/** 设置完成 — 收集第3步+第4步数据,通过 emit 传回父组件 */
|
||||
function handleStep4Complete() {
|
||||
const step4Data = settingsPanelRef.value?.getData()
|
||||
const allSettings = {
|
||||
jobType: store.state.jobIntention.recruitCategory === 2 ? 1 : 2,
|
||||
agentMode: step4Data?.agentMode ?? 1,
|
||||
weeklyTarget: step4Data?.weeklyTarget ?? 2,
|
||||
autoOptimizeResume: step4Data?.autoOptimizeResume ?? 1,
|
||||
acceptDeptTransfer: step3Form.acceptDeptTransfer,
|
||||
acceptLocationTransfer: step3Form.acceptLocationTransfer,
|
||||
interviewType: [...step3Form.interviewType],
|
||||
languages: step3Form.languages.filter(l => l.language),
|
||||
availableDate: step3Form.availableDate,
|
||||
internDaysPerWeek: step3Form.internDaysPerWeek,
|
||||
internDuration: step3Form.internDuration,
|
||||
status:1,
|
||||
}
|
||||
emit('complete', allSettings)
|
||||
setupComplete.value = true
|
||||
/** 复制文本到剪贴板 */
|
||||
function copyToClipboard(text: string) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
ElMessage.success('已复制到剪贴板')
|
||||
}).catch(() => {
|
||||
ElMessage.error('复制失败,请手动复制')
|
||||
})
|
||||
}
|
||||
|
||||
/** 启用求职助手 */
|
||||
function handleLaunchAgent() { emit('launch') }
|
||||
// ==================== 第4步:配置求职助手 ====================
|
||||
|
||||
/** 启用求职助手 — 收集第2步数据,通过 emit 传回父组件并触发 launch */
|
||||
function handleLaunchAgent() {
|
||||
const allSettings = {
|
||||
jobType: store.state.jobIntention.recruitCategory === 2 ? 1 : 2,
|
||||
agentMode: 1,
|
||||
weeklyTarget: 2,
|
||||
autoOptimizeResume: autoOptimizeSwitch.value ? 1 : 0,
|
||||
autoMatchAnalysis: autoMatchAnalysisSwitch.value ? 1 : 0,
|
||||
defaultResumeId: selectedResumeId.value,
|
||||
status: 1,
|
||||
}
|
||||
emit('complete', allSettings)
|
||||
emit('launch')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<!-- 用户评价 -->
|
||||
<div class="member-dialog__testimonial">
|
||||
<div class="member-dialog__testimonial-avatar">
|
||||
<img src="@/assets/images/home/avatar-temporary.png" alt="用户头像" />
|
||||
<img src="@/assets/images/home/alex-avatar.png" alt="用户头像" />
|
||||
</div>
|
||||
<div class="member-dialog__testimonial-content">
|
||||
<div class="member-dialog__testimonial-stars">★★★★★</div>
|
||||
@@ -107,7 +107,7 @@
|
||||
<div v-else-if="currentView === 'order1'" class="member-dialog__order">
|
||||
<!-- 顶部导航 -->
|
||||
<div class="member-dialog__order-header">
|
||||
<span class="member-dialog__order-back" @click="currentView = 'intro2'">‹ 返回会员介绍</span>
|
||||
<span class="melmber-dialog__order-back" @click="currentView = 'intro2'">‹ 返回会员介绍</span>
|
||||
<!-- 步骤条 — 二维码弹窗打开时显示第2步 -->
|
||||
<div class="member-dialog__steps">
|
||||
<div class="member-dialog__step" :class="showQrCode ? 'member-dialog__step--done' : 'member-dialog__step--active'">
|
||||
@@ -340,15 +340,23 @@
|
||||
<h3 class="member-dialog__qrcode-subtitle">{{ selectedPayment === 'alipay' ? '扫码完成支付' : '扫码完成支付' }}</h3>
|
||||
<p class="member-dialog__qrcode-desc">{{ selectedPayment === 'alipay' ? '请在支付宝页面完成支付,完成后点击下方按钮确认。' : '请使用微信 App 扫描二维码完成支付,完成后此窗口会自动关闭。' }}</p>
|
||||
<!-- 支付宝:iframe 渲染支付表单 -->
|
||||
<div class="member-dialog__qrcode-image">
|
||||
<div v-if="selectedPayment === 'alipay' && paymentFormHtml" class="member-dialog__qrcode-image">
|
||||
<iframe
|
||||
v-if="paymentFormHtml"
|
||||
:srcdoc="paymentFormHtml"
|
||||
frameborder="0"
|
||||
class="member-dialog__payment-iframe"
|
||||
scrolling="auto"
|
||||
></iframe>
|
||||
</div>
|
||||
<!-- 微信:使用 qrcode.js 渲染二维码 -->
|
||||
<div v-if="selectedPayment === 'wechat' && wechatCodeUrl" class="member-dialog__qrcode-image">
|
||||
<iframe
|
||||
:srcdoc="wechatQrHtml"
|
||||
frameborder="0"
|
||||
class="member-dialog__wechat-qr-iframe"
|
||||
scrolling="no"
|
||||
></iframe>
|
||||
</div>
|
||||
<!-- 金额 -->
|
||||
<div class="member-dialog__qrcode-amount">¥{{ currentPlan.price }}</div>
|
||||
</div>
|
||||
@@ -428,6 +436,15 @@ const showQrCode = ref(false)
|
||||
/** 支付宝表单 HTML(用于 iframe 渲染) */
|
||||
const paymentFormHtml = ref('')
|
||||
|
||||
/** 微信支付 code_url(用于生成二维码) */
|
||||
const wechatCodeUrl = ref('')
|
||||
|
||||
/** 微信二维码 iframe 的 srcdoc HTML */
|
||||
const wechatQrHtml = computed(() => {
|
||||
if (!wechatCodeUrl.value) return ''
|
||||
return `<!DOCTYPE html><html><head><meta charset="UTF-8"><script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"><\/script><style>body{display:flex;align-items:center;justify-content:center;margin:0;height:100%;}</style></head><body><div id="qrcode"></div><script>new QRCode(document.getElementById("qrcode"),{text:"${wechatCodeUrl.value}",width:200,height:200,colorDark:"#000000",colorLight:"#ffffff",correctLevel:QRCode.CorrectLevel.H});<\/script></body></html>`
|
||||
})
|
||||
|
||||
/** 当前订单ID(用于后续轮询支付状态) */
|
||||
const orderId = ref('')
|
||||
|
||||
@@ -442,6 +459,7 @@ watch(() => props.modelValue, (val) => {
|
||||
showQrCode.value = false
|
||||
agreeProtocol.value = false
|
||||
paymentFormHtml.value = ''
|
||||
wechatCodeUrl.value = ''
|
||||
orderId.value = ''
|
||||
// 清除轮询计时器
|
||||
if (pollTimer) {
|
||||
@@ -586,7 +604,15 @@ async function handleShowQrCode() {
|
||||
})
|
||||
if (res.data) {
|
||||
orderId.value = String(res.data.orderId)
|
||||
paymentFormHtml.value = res.data.payData
|
||||
if (selectedPayment.value === 'wechat') {
|
||||
// 微信支付 — payData 为 code_url,用于生成二维码
|
||||
wechatCodeUrl.value = res.data.payData
|
||||
paymentFormHtml.value = ''
|
||||
} else {
|
||||
// 支付宝 — payData 为 HTML 表单
|
||||
paymentFormHtml.value = res.data.payData
|
||||
wechatCodeUrl.value = ''
|
||||
}
|
||||
showQrCode.value = true
|
||||
// 创建订单成功后立即开始轮询支付状态
|
||||
confirmPayment()
|
||||
|
||||
+363
-1075
File diff suppressed because it is too large
Load Diff
@@ -1,232 +1,317 @@
|
||||
<template>
|
||||
<div class="profile-page-content">
|
||||
<!-- 个人信息 -->
|
||||
<div class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header">
|
||||
<h3 class="profile-page-content__card-title">个人信息</h3>
|
||||
<button class="profile-page-content__edit-btn" @click="handleEdit('info')">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="profile-page-content__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="profile-page-content__info-name">{{ profile.name }}</div>
|
||||
<div class="profile-page-content__info-contacts">
|
||||
<span class="profile-page-content__contact-item">📱 {{ profile.phone }}</span>
|
||||
<span class="profile-page-content__contact-item">✉️ {{ profile.email }}</span>
|
||||
<span class="profile-page-content__contact-item">📍 {{ resolveRegionName(profile.regionCode) }}</span>
|
||||
</div>
|
||||
<div class="profile-page-content__info-contacts" v-if="profile.wechat">
|
||||
<span class="profile-page-content__contact-item">💬 {{ profile.wechat }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 顶部 Tab 导航栏(固定不动) -->
|
||||
<div class="profile-page-content__tabs">
|
||||
<span
|
||||
v-for="tab in tabList"
|
||||
:key="tab.key"
|
||||
class="profile-page-content__tab-item"
|
||||
:class="{ 'profile-page-content__tab-item--active': activeTab === tab.key }"
|
||||
@click="scrollToSection(tab.key)"
|
||||
>{{ tab.label }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 作品集 -->
|
||||
<div class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header">
|
||||
<h3 class="profile-page-content__card-title">作品集</h3>
|
||||
<button class="profile-page-content__edit-btn" @click="handleEdit('portfolio')">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="profile-page-content__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 有链接时显示可点击链接 -->
|
||||
<a
|
||||
v-if="profile.portfolioUrl"
|
||||
:href="profile.portfolioUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="profile-page-content__portfolio-link"
|
||||
>{{ profile.portfolioUrl }}</a>
|
||||
<!-- 无链接时显示占位提示 -->
|
||||
<span v-else class="profile-page-content__portfolio-empty">暂未添加作品集链接</span>
|
||||
<!-- 内容滚动区域 -->
|
||||
<div ref="scrollContainerRef" class="profile-page-content__body">
|
||||
<!-- 个人信息 -->
|
||||
<div ref="infoRef" class="profile-page-content__card p10" :class="{ 'profile-page-content__card-hover': editingModule !== 'info' }">
|
||||
<template v-if="editingModule !== 'info'">
|
||||
<div class="profile-page-content__card-header pl0">
|
||||
<h3 class="profile-page-content__card-title">个人信息</h3>
|
||||
<button class="profile-page-content__edit-btn-inline" @click="startEdit('info')">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="profile-page-content__info-name">{{ profile.name }}</div>
|
||||
<div class="profile-page-content__info-contacts">
|
||||
<span class="profile-page-content__contact-item">📱 {{ profile.phone }}</span>
|
||||
<span class="profile-page-content__contact-item">✉️ {{ profile.email }}</span>
|
||||
<span class="profile-page-content__contact-item">📍 {{ resolveRegionName(profile.regionCode) }}</span>
|
||||
</div>
|
||||
<div class="profile-page-content__info-contacts" v-if="profile.wechat">
|
||||
<span class="profile-page-content__contact-item">💬 {{ profile.wechat }}</span>
|
||||
</div>
|
||||
<!-- 作品集链接显示在个人信息卡片内 -->
|
||||
<div v-if="profile.portfolioUrl" class="profile-page-content__info-contacts">
|
||||
<span class="profile-page-content__contact-item">🔗 <a :href="profile.portfolioUrl" target="_blank" rel="noopener noreferrer" class="profile-page-content__portfolio-link">{{ profile.portfolioUrl }}</a></span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 编辑模式 -->
|
||||
<ProfileEditDrawer
|
||||
v-if="editingModule === 'info'"
|
||||
v-model="editVisible"
|
||||
module="info"
|
||||
:initial-data="editInitialData"
|
||||
@save="handleSave"
|
||||
@cancel="cancelEdit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 教育经历 -->
|
||||
<div class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header">
|
||||
<h3 class="profile-page-content__card-title">教育经历</h3>
|
||||
<button class="profile-page-content__edit-btn" @click="handleEdit('education')">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="profile-page-content__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="(edu, i) in profile.education" :key="i" class="profile-page-content__section-item">
|
||||
<div class="profile-page-content__item-row">
|
||||
<div class="profile-page-content__item-left">
|
||||
<div class="profile-page-content__item-title">{{ edu.school }}</div>
|
||||
<div class="profile-page-content__item-sub">{{ getDegreeText(edu.studyType, edu.degree) }} · {{ edu.major }}</div>
|
||||
<div class="profile-page-content__item-date">{{ edu.startDate }} - {{ edu.endDate }}</div>
|
||||
</div>
|
||||
<!-- 教育经历 -->
|
||||
<div ref="educationRef" class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header profile-page-content__header-hover">
|
||||
<h3 class="profile-page-content__card-title">教育经历</h3>
|
||||
<button class="profile-page-content__edit-btn-inline" @click="startAdd('education')">
|
||||
<el-icon><Plus /></el-icon><span>添加</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 新增 -->
|
||||
<ResumeExpInlineEdit v-if="addingModule === 'education'" module-type="education" @save="handleExpAdd('education', $event)" @cancel="addingModule = ''" />
|
||||
<div v-for="(edu, i) in profile.education" :key="i" class="profile-page-content__section-item">
|
||||
<template v-if="editingExpIndex !== `education_${i}`">
|
||||
<div class="profile-page-content__item-row">
|
||||
<div class="profile-page-content__item-left">
|
||||
<div class="profile-page-content__item-title">{{ edu.school }}</div>
|
||||
<div class="profile-page-content__item-sub">{{ getDegreeText(edu.studyType, edu.degree) }} · {{ edu.major }}</div>
|
||||
<div class="profile-page-content__item-date">{{ edu.startDate }} - {{ edu.endDate }}</div>
|
||||
</div>
|
||||
<ul class="profile-page-content__item-list" v-if="edu.description?.length && edu.description[0]?.text">
|
||||
<li v-for="desc in edu.description" :key="desc.id">{{ desc.text }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工作经历 -->
|
||||
<div class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header">
|
||||
<h3 class="profile-page-content__card-title">工作经历</h3>
|
||||
<button class="profile-page-content__edit-btn" @click="handleEdit('work')">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="profile-page-content__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn" @click="startEditExp('education', i, edu)">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="(exp, i) in profile.works" :key="i" class="profile-page-content__section-item">
|
||||
<div class="profile-page-content__item-row">
|
||||
<div class="profile-page-content__item-left">
|
||||
<div class="profile-page-content__item-title">{{ exp.companyName }}</div>
|
||||
<div class="profile-page-content__item-sub">{{ exp.position }}</div>
|
||||
</div>
|
||||
<div class="profile-page-content__item-period">{{ exp.startDate }} - {{ exp.endDate || '至今' }}</div>
|
||||
<ul class="profile-page-content__item-list" v-if="edu.description?.length && edu.description[0]?.text">
|
||||
<li v-for="desc in edu.description" :key="desc.id">{{ desc.text }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
<ResumeExpInlineEdit v-else module-type="education" :initial-data="edu" @save="handleExpUpdate('education', i, $event)" @cancel="editingExpIndex = ''" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工作经历 -->
|
||||
<div ref="workRef" class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header profile-page-content__header-hover">
|
||||
<h3 class="profile-page-content__card-title">工作经历</h3>
|
||||
<button class="profile-page-content__edit-btn-inline" @click="startAdd('work')">
|
||||
<el-icon><Plus /></el-icon><span>添加</span>
|
||||
</button>
|
||||
</div>
|
||||
<ResumeExpInlineEdit v-if="addingModule === 'work'" module-type="work" @save="handleExpAdd('work', $event)" @cancel="addingModule = ''" />
|
||||
<div v-for="(exp, i) in profile.works" :key="i" class="profile-page-content__section-item">
|
||||
<template v-if="editingExpIndex !== `work_${i}`">
|
||||
<div class="profile-page-content__item-row">
|
||||
<div class="profile-page-content__item-left">
|
||||
<div class="profile-page-content__item-title">{{ exp.companyName }}</div>
|
||||
<div class="profile-page-content__item-sub">{{ exp.position }}</div>
|
||||
</div>
|
||||
<ul class="profile-page-content__item-list">
|
||||
<li v-for="desc in exp.description" :key="desc.id">{{ desc.text }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实习经历 -->
|
||||
<div class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header">
|
||||
<h3 class="profile-page-content__card-title">实习经历</h3>
|
||||
<button class="profile-page-content__edit-btn" @click="handleEdit('internship')">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="profile-page-content__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
<div class="profile-page-content__item-period">{{ exp.startDate }} - {{ exp.endDate || '至今' }}</div>
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn" @click="startEditExp('work', i, exp)">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="(exp, i) in profile.internships" :key="i" class="profile-page-content__section-item">
|
||||
<div class="profile-page-content__item-row">
|
||||
<div class="profile-page-content__item-left">
|
||||
<div class="profile-page-content__item-title">{{ exp.companyName }}</div>
|
||||
<div class="profile-page-content__item-sub">{{ exp.position }}</div>
|
||||
</div>
|
||||
<div class="profile-page-content__item-period">{{ exp.startDate }} - {{ exp.endDate }}</div>
|
||||
<ul class="profile-page-content__item-list">
|
||||
<li v-for="desc in exp.description" :key="desc.id">{{ desc.text }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
<ResumeExpInlineEdit v-else module-type="work" :initial-data="exp" @save="handleExpUpdate('work', i, $event)" @cancel="editingExpIndex = ''" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实习经历 -->
|
||||
<div ref="internshipRef" class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header profile-page-content__header-hover">
|
||||
<h3 class="profile-page-content__card-title">实习经历</h3>
|
||||
<button class="profile-page-content__edit-btn-inline" @click="startAdd('internship')">
|
||||
<el-icon><Plus /></el-icon><span>添加</span>
|
||||
</button>
|
||||
</div>
|
||||
<ResumeExpInlineEdit v-if="addingModule === 'internship'" module-type="internship" @save="handleExpAdd('internship', $event)" @cancel="addingModule = ''" />
|
||||
<div v-for="(exp, i) in profile.internships" :key="i" class="profile-page-content__section-item">
|
||||
<template v-if="editingExpIndex !== `internship_${i}`">
|
||||
<div class="profile-page-content__item-row">
|
||||
<div class="profile-page-content__item-left">
|
||||
<div class="profile-page-content__item-title">{{ exp.companyName }}</div>
|
||||
<div class="profile-page-content__item-sub">{{ exp.position }}</div>
|
||||
</div>
|
||||
<ul class="profile-page-content__item-list">
|
||||
<li v-for="desc in exp.description" :key="desc.id">{{ desc.text }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 项目经历 -->
|
||||
<div class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header">
|
||||
<h3 class="profile-page-content__card-title">项目经历</h3>
|
||||
<button class="profile-page-content__edit-btn" @click="handleEdit('project')">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="profile-page-content__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
<div class="profile-page-content__item-period">{{ exp.startDate }} - {{ exp.endDate }}</div>
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn" @click="startEditExp('internship', i, exp)">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="(proj, i) in profile.projects" :key="i" class="profile-page-content__section-item">
|
||||
<div class="profile-page-content__item-row">
|
||||
<div class="profile-page-content__item-left">
|
||||
<div class="profile-page-content__item-title">{{ proj.projectName }}</div>
|
||||
<div class="profile-page-content__item-sub">{{ proj.role }}</div>
|
||||
</div>
|
||||
<div class="profile-page-content__item-period">{{ proj.startDate }} - {{ proj.endDate }}</div>
|
||||
<ul class="profile-page-content__item-list">
|
||||
<li v-for="desc in exp.description" :key="desc.id">{{ desc.text }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
<ResumeExpInlineEdit v-else module-type="internship" :initial-data="exp" @save="handleExpUpdate('internship', i, $event)" @cancel="editingExpIndex = ''" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 项目经历 -->
|
||||
<div ref="projectRef" class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header profile-page-content__header-hover">
|
||||
<h3 class="profile-page-content__card-title">项目经历</h3>
|
||||
<button class="profile-page-content__edit-btn-inline" @click="startAdd('project')">
|
||||
<el-icon><Plus /></el-icon><span>添加</span>
|
||||
</button>
|
||||
</div>
|
||||
<ResumeExpInlineEdit v-if="addingModule === 'project'" module-type="project" @save="handleExpAdd('project', $event)" @cancel="addingModule = ''" />
|
||||
<div v-for="(proj, i) in profile.projects" :key="i" class="profile-page-content__section-item">
|
||||
<template v-if="editingExpIndex !== `project_${i}`">
|
||||
<div class="profile-page-content__item-row">
|
||||
<div class="profile-page-content__item-left">
|
||||
<div class="profile-page-content__item-title">{{ proj.projectName }}</div>
|
||||
<div class="profile-page-content__item-sub">{{ proj.role }}</div>
|
||||
</div>
|
||||
<ul class="profile-page-content__item-list">
|
||||
<li v-for="desc in proj.description" :key="desc.id">{{ desc.text }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 技能 -->
|
||||
<div class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header">
|
||||
<h3 class="profile-page-content__card-title">技能</h3>
|
||||
<button class="profile-page-content__edit-btn" @click="handleEdit('skills')">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="profile-page-content__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
<div class="profile-page-content__item-period">{{ proj.startDate }} - {{ proj.endDate }}</div>
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn" @click="startEditExp('project', i, proj)">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="profile-page-content__tags">
|
||||
<span v-for="(skill, i) in profile.skills" :key="i" class="profile-page-content__tag">{{ skill }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="profile-page-content__item-list">
|
||||
<li v-for="desc in proj.description" :key="desc.id">{{ desc.text }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
<ResumeExpInlineEdit v-else module-type="project" :initial-data="proj" @save="handleExpUpdate('project', i, $event)" @cancel="editingExpIndex = ''" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 竞赛 -->
|
||||
<div class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header">
|
||||
<h3 class="profile-page-content__card-title">竞赛</h3>
|
||||
<button class="profile-page-content__edit-btn" @click="handleEdit('competition')">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="profile-page-content__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="(comp, i) in profile.competitions" :key="i" class="profile-page-content__section-item">
|
||||
<div class="profile-page-content__item-row">
|
||||
<div class="profile-page-content__item-left">
|
||||
<div class="profile-page-content__item-title">{{ comp.competitionName }} · {{ comp.award }}</div>
|
||||
</div>
|
||||
<div class="profile-page-content__item-period">{{ comp.awardDate }}</div>
|
||||
<!-- 竞赛 -->
|
||||
<div ref="competitionRef" class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header profile-page-content__header-hover">
|
||||
<h3 class="profile-page-content__card-title">竞赛</h3>
|
||||
<button class="profile-page-content__edit-btn-inline" @click="startAdd('competition')">
|
||||
<el-icon><Plus /></el-icon><span>添加</span>
|
||||
</button>
|
||||
</div>
|
||||
<ResumeExpInlineEdit v-if="addingModule === 'competition'" module-type="competition" @save="handleExpAdd('competition', $event)" @cancel="addingModule = ''" />
|
||||
<div v-for="(comp, i) in profile.competitions" :key="i" class="profile-page-content__section-item">
|
||||
<template v-if="editingExpIndex !== `competition_${i}`">
|
||||
<div class="profile-page-content__item-row">
|
||||
<div class="profile-page-content__item-left">
|
||||
<div class="profile-page-content__item-title">{{ comp.competitionName }} · {{ comp.award }}</div>
|
||||
</div>
|
||||
<ul class="profile-page-content__item-list" v-if="comp.description[0]?.text">
|
||||
<li v-for="desc in comp.description" :key="desc.id">{{ desc.text }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 证书 -->
|
||||
<div class="profile-page-content__card">
|
||||
<div class="profile-page-content__card-header">
|
||||
<h3 class="profile-page-content__card-title">证书</h3>
|
||||
<button class="profile-page-content__edit-btn" @click="handleEdit('certificate')">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="profile-page-content__edit-icon"><path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||||
<div class="profile-page-content__item-period">{{ comp.awardDate }}</div>
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn" @click="startEditExp('competition', i, comp)">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="profile-page-content__tags">
|
||||
<span v-for="(cert, i) in profile.certificates" :key="i" class="profile-page-content__tag">{{ cert }}</span>
|
||||
</div>
|
||||
<ul class="profile-page-content__item-list" v-if="comp.description[0]?.text">
|
||||
<li v-for="desc in comp.description" :key="desc.id">{{ desc.text }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
<ResumeExpInlineEdit v-else module-type="competition" :initial-data="comp" @save="handleExpUpdate('competition', i, $event)" @cancel="editingExpIndex = ''" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 技能 -->
|
||||
<div ref="skillsRef" class="profile-page-content__card" :class="{ 'profile-page-content__card-hover': editingModule !== 'skills' }">
|
||||
<template v-if="editingModule !== 'skills'">
|
||||
<div class="profile-page-content__card-header">
|
||||
<h3 class="profile-page-content__card-title">技能</h3>
|
||||
<button class="profile-page-content__edit-btn-inline" @click="startEdit('skills')">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="profile-page-content__tags">
|
||||
<span v-for="(skill, i) in profile.skills" :key="i" class="profile-page-content__tag">{{ skill }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<ProfileEditDrawer
|
||||
v-if="editingModule === 'skills'"
|
||||
v-model="editVisible"
|
||||
module="skills"
|
||||
:initial-data="editInitialData"
|
||||
@save="handleSave"
|
||||
@cancel="cancelEdit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 证书 -->
|
||||
<div ref="certificateRef" class="profile-page-content__card" :class="{ 'profile-page-content__card-hover': editingModule !== 'certificate' }">
|
||||
<template v-if="editingModule !== 'certificate'">
|
||||
<div class="profile-page-content__card-header">
|
||||
<h3 class="profile-page-content__card-title">证书</h3>
|
||||
<button class="profile-page-content__edit-btn-inline" @click="startEdit('certificate')">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="profile-page-content__tags">
|
||||
<span v-for="(cert, i) in profile.certificates" :key="i" class="profile-page-content__tag">{{ cert }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<ProfileEditDrawer
|
||||
v-if="editingModule === 'certificate'"
|
||||
v-model="editVisible"
|
||||
module="certificate"
|
||||
:initial-data="editInitialData"
|
||||
@save="handleSave"
|
||||
@cancel="cancelEdit"
|
||||
/>
|
||||
</div>
|
||||
</div><!-- 内容滚动区域结束 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { EditPen, Plus } from '@element-plus/icons-vue'
|
||||
import { resolveRegionName } from '@/utils/region'
|
||||
import ProfileEditDrawer from '@/components/ProfileEditDrawer.vue'
|
||||
import ResumeExpInlineEdit from '@/components/ResumeExpInlineEdit.vue'
|
||||
|
||||
/** Tab 导航列表 */
|
||||
const tabList = [
|
||||
{ key: 'info', label: '个人信息' },
|
||||
{ key: 'education', label: '教育经历' },
|
||||
{ key: 'work', label: '工作经历' },
|
||||
{ key: 'internship', label: '实习经历' },
|
||||
{ key: 'project', label: '项目经历' },
|
||||
{ key: 'skills', label: '技能' },
|
||||
{ key: 'competition', label: '竞赛' },
|
||||
{ key: 'certificate', label: '证书' },
|
||||
]
|
||||
|
||||
/** 当前激活的 tab */
|
||||
const activeTab = ref('info')
|
||||
|
||||
/** 各模块的 DOM 引用 */
|
||||
const scrollContainerRef = ref<HTMLElement | null>(null)
|
||||
const infoRef = ref<HTMLElement | null>(null)
|
||||
const educationRef = ref<HTMLElement | null>(null)
|
||||
const workRef = ref<HTMLElement | null>(null)
|
||||
const internshipRef = ref<HTMLElement | null>(null)
|
||||
const projectRef = ref<HTMLElement | null>(null)
|
||||
const skillsRef = ref<HTMLElement | null>(null)
|
||||
const competitionRef = ref<HTMLElement | null>(null)
|
||||
const certificateRef = ref<HTMLElement | null>(null)
|
||||
|
||||
/** 模块 key 与 ref 的映射 */
|
||||
const sectionRefMap: Record<string, typeof infoRef> = {
|
||||
info: infoRef,
|
||||
education: educationRef,
|
||||
work: workRef,
|
||||
internship: internshipRef,
|
||||
project: projectRef,
|
||||
skills: skillsRef,
|
||||
competition: competitionRef,
|
||||
certificate: certificateRef,
|
||||
}
|
||||
|
||||
/** 点击 tab 滚动到对应模块 */
|
||||
function scrollToSection(key: string) {
|
||||
activeTab.value = key
|
||||
const targetRef = sectionRefMap[key]
|
||||
const container = scrollContainerRef.value
|
||||
if (targetRef?.value && container) {
|
||||
const offsetTop = targetRef.value.offsetTop - container.offsetTop
|
||||
container.scrollTo({ top: offsetTop, behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
/** 个人资料数据结构 */
|
||||
interface ProfileData {
|
||||
name: string
|
||||
phone: string
|
||||
email: string
|
||||
idNumber: string
|
||||
/** 所在城市编码 — 对应接口字段 regionCode */
|
||||
regionCode: string
|
||||
/** 作品集链接 — 对应接口字段 portfolioUrl */
|
||||
portfolioUrl: string
|
||||
wechat?: string
|
||||
education: Array<{
|
||||
school: string
|
||||
major: string
|
||||
studyType: number
|
||||
degree: number
|
||||
startDate: string
|
||||
endDate: string
|
||||
description: Array<{ id: string; text: string }>
|
||||
}>
|
||||
works: Array<{
|
||||
companyName: string
|
||||
position: string
|
||||
startDate: string
|
||||
endDate: string
|
||||
description: Array<{ id: string; text: string }>
|
||||
}>
|
||||
internships: Array<{
|
||||
companyName: string
|
||||
position: string
|
||||
startDate: string
|
||||
endDate: string
|
||||
description: Array<{ id: string; text: string }>
|
||||
}>
|
||||
projects: Array<{
|
||||
projectName: string
|
||||
companyName: string
|
||||
role: string
|
||||
startDate: string
|
||||
endDate: string
|
||||
description: Array<{ id: string; text: string }>
|
||||
}>
|
||||
education: Array<{ school: string; major: string; studyType: number; degree: number; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }>
|
||||
works: Array<{ companyName: string; position: string; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }>
|
||||
internships: Array<{ companyName: string; position: string; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }>
|
||||
projects: Array<{ projectName: string; companyName: string; role: string; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }>
|
||||
skills: string[]
|
||||
competitions: Array<{
|
||||
competitionName: string
|
||||
award: string
|
||||
awardDate: string
|
||||
description: Array<{ id: string; text: string }>
|
||||
}>
|
||||
competitions: Array<{ competitionName: string; award: string; awardDate: string; description: Array<{ id: string; text: string }> }>
|
||||
certificates: string[]
|
||||
}
|
||||
|
||||
@@ -235,23 +320,97 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
edit: [section: string]
|
||||
/** 模块编辑保存事件 */
|
||||
(e: 'edit', section: string): void
|
||||
/** 内联保存事件(模块名+数据) */
|
||||
(e: 'save', section: string, data: Record<string, any>): void
|
||||
/** 单条经历更新事件 */
|
||||
(e: 'expUpdate', moduleType: string, index: number, data: Record<string, any>): void
|
||||
/** 新增经历事件 */
|
||||
(e: 'expAdd', moduleType: string, data: Record<string, any>): void
|
||||
}>()
|
||||
|
||||
/** 获取学历文本 — 根据学历类型和学历代码生成显示文本 */
|
||||
/** 当前编辑的模块(info/skills/certificate) */
|
||||
const editingModule = ref('')
|
||||
|
||||
/** 编辑可见状态 */
|
||||
const editVisible = ref(false)
|
||||
|
||||
/** 编辑初始数据 */
|
||||
const editInitialData = ref<Record<string, any>>({})
|
||||
|
||||
/** 当前编辑的经历索引 key(格式:moduleType_index) */
|
||||
const editingExpIndex = ref('')
|
||||
|
||||
/** 当前添加经历的模块 */
|
||||
const addingModule = ref('')
|
||||
|
||||
/** 获取学历文本 */
|
||||
function getDegreeText(studyType: number, degree: number): string {
|
||||
const studyTypeText = studyType === 0 ? '全日制' : '非全日制'
|
||||
const degreeMap: Record<number, string> = {
|
||||
1: '大专',
|
||||
2: '本科',
|
||||
3: '硕士',
|
||||
4: '博士',
|
||||
}
|
||||
const degreeMap: Record<number, string> = { 1: '大专', 2: '本科', 3: '硕士', 4: '博士' }
|
||||
return `${studyTypeText}${degreeMap[degree] || ''}`
|
||||
}
|
||||
|
||||
function handleEdit(section: string) {
|
||||
emit('edit', section)
|
||||
/** 打开模块编辑(个人信息/技能/证书) */
|
||||
function startEdit(module: string) {
|
||||
editingModule.value = module
|
||||
editVisible.value = true
|
||||
editingExpIndex.value = ''
|
||||
addingModule.value = ''
|
||||
if (module === 'info') {
|
||||
editInitialData.value = {
|
||||
name: props.profile.name || '',
|
||||
email: props.profile.email || '',
|
||||
phone: props.profile.phone || '',
|
||||
location: props.profile.regionCode || '',
|
||||
wechat: props.profile.wechat || '',
|
||||
portfolioUrl: props.profile.portfolioUrl || '',
|
||||
}
|
||||
} else if (module === 'skills') {
|
||||
editInitialData.value = { skills: [...(props.profile.skills || [])] }
|
||||
} else if (module === 'certificate') {
|
||||
editInitialData.value = { certificates: [...(props.profile.certificates || [])] }
|
||||
}
|
||||
}
|
||||
|
||||
/** 取消编辑 */
|
||||
function cancelEdit() {
|
||||
editingModule.value = ''
|
||||
editVisible.value = false
|
||||
}
|
||||
|
||||
/** 保存编辑(个人信息/技能/证书) */
|
||||
function handleSave(data: Record<string, any>) {
|
||||
emit('save', editingModule.value, data)
|
||||
editingModule.value = ''
|
||||
editVisible.value = false
|
||||
}
|
||||
|
||||
/** 开始添加经历 */
|
||||
function startAdd(moduleType: string) {
|
||||
addingModule.value = moduleType
|
||||
editingExpIndex.value = ''
|
||||
editingModule.value = ''
|
||||
}
|
||||
|
||||
/** 开始编辑单条经历 */
|
||||
function startEditExp(_moduleType: string, index: number, _record: any) {
|
||||
editingExpIndex.value = `${_moduleType}_${index}`
|
||||
addingModule.value = ''
|
||||
editingModule.value = ''
|
||||
}
|
||||
|
||||
/** 保存经历更新 */
|
||||
function handleExpUpdate(moduleType: string, index: number, data: Record<string, any>) {
|
||||
emit('expUpdate', moduleType, index, data)
|
||||
editingExpIndex.value = ''
|
||||
}
|
||||
|
||||
/** 保存新增经历 */
|
||||
function handleExpAdd(moduleType: string, data: Record<string, any>) {
|
||||
emit('expAdd', moduleType, data)
|
||||
addingModule.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,54 +1,51 @@
|
||||
<template>
|
||||
<!-- 编辑简历名称与目标岗位弹窗 -->
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title=""
|
||||
width="4.6rem"
|
||||
:show-close="true"
|
||||
:close-on-click-modal="false"
|
||||
class="resume-edit-name-dialog"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="resume-edit-name-dialog__body">
|
||||
<!-- 简历名称 -->
|
||||
<div class="resume-edit-name-dialog__field">
|
||||
<label class="resume-edit-name-dialog__label">
|
||||
<span class="resume-edit-name-dialog__required">*</span>简历名称
|
||||
</label>
|
||||
<el-input
|
||||
v-model="form.resumeName"
|
||||
placeholder="请输入简历名称"
|
||||
maxlength="50"
|
||||
class="resume-edit-name-dialog__input"
|
||||
/>
|
||||
</div>
|
||||
<!-- 编辑简历名称与目标岗位弹窗 — Teleport 到 body 避免缩放影响 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="visible" class="resume-edit-name-overlay" @click.self="handleClose">
|
||||
<div class="resume-edit-name-dialog">
|
||||
<!-- 关闭按钮 -->
|
||||
<button class="resume-edit-name-dialog__close" @click="handleClose">✕</button>
|
||||
|
||||
<!-- 目标岗位 -->
|
||||
<div class="resume-edit-name-dialog__field">
|
||||
<label class="resume-edit-name-dialog__label">目标岗位</label>
|
||||
<el-input
|
||||
v-model="form.targetPosition"
|
||||
placeholder="请输入岗位名称"
|
||||
maxlength="50"
|
||||
class="resume-edit-name-dialog__input"
|
||||
/>
|
||||
</div>
|
||||
<!-- 简历名称 -->
|
||||
<div class="resume-edit-name-dialog__field">
|
||||
<label class="resume-edit-name-dialog__label">
|
||||
<span class="resume-edit-name-dialog__required">*</span>简历名称
|
||||
</label>
|
||||
<input
|
||||
v-model="form.resumeName"
|
||||
placeholder="请输入简历名称"
|
||||
maxlength="50"
|
||||
class="resume-edit-name-dialog__input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 按钮区域 -->
|
||||
<div class="resume-edit-name-dialog__footer">
|
||||
<button class="resume-edit-name-dialog__btn resume-edit-name-dialog__btn--cancel" @click="handleClose">
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
class="resume-edit-name-dialog__btn resume-edit-name-dialog__btn--save"
|
||||
:disabled="saving"
|
||||
@click="handleSave"
|
||||
>
|
||||
{{ saving ? '保存中...' : '保存' }}
|
||||
</button>
|
||||
<!-- 目标岗位 -->
|
||||
<div class="resume-edit-name-dialog__field">
|
||||
<label class="resume-edit-name-dialog__label">目标岗位</label>
|
||||
<input
|
||||
v-model="form.targetPosition"
|
||||
placeholder="请输入岗位名称"
|
||||
maxlength="50"
|
||||
class="resume-edit-name-dialog__input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 按钮区域 -->
|
||||
<div class="resume-edit-name-dialog__footer">
|
||||
<button class="resume-edit-name-dialog__btn resume-edit-name-dialog__btn--cancel" @click="handleClose">
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
class="resume-edit-name-dialog__btn resume-edit-name-dialog__btn--save"
|
||||
:disabled="saving"
|
||||
@click="handleSave"
|
||||
>
|
||||
{{ saving ? '保存中...' : '保存' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<!-- 单条经历内联编辑组件 -->
|
||||
<div class="profile-edit-inline">
|
||||
<div class="profile-edit-inline__body">
|
||||
<!-- 教育经历 -->
|
||||
<template v-if="moduleType === 'education'">
|
||||
<div class="profile-edit-inline__fields-row">
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">学校</label><input class="profile-edit-inline__input" placeholder="请输入学校名称" v-model="form.school" /></div>
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">专业</label><input class="profile-edit-inline__input" placeholder="请输入专业名称" v-model="form.major" /></div>
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">学历</label><select class="profile-edit-inline__input" v-model="form.degree"><option value="大专">大专</option><option value="本科">本科</option><option value="硕士">硕士</option><option value="博士">博士</option></select></div>
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">学历类型</label><select class="profile-edit-inline__input" v-model="form.studyType"><option value="全日制">全日制</option><option value="非全日制">非全日制</option></select></div>
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">就读时间</label>
|
||||
<div class="profile-edit-inline__date-range">
|
||||
<el-date-picker v-model="form.startDate" type="month" placeholder="开始" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||||
<span class="profile-edit-inline__date-sep">-</span>
|
||||
<el-date-picker v-model="form.endDate" type="month" placeholder="结束" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 工作经历 -->
|
||||
<template v-else-if="moduleType === 'work'">
|
||||
<div class="profile-edit-inline__fields-row">
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">公司名称</label><input class="profile-edit-inline__input" placeholder="请输入公司名称" v-model="form.companyName" /></div>
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">职位</label><input class="profile-edit-inline__input" placeholder="请输入职位" v-model="form.position" /></div>
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">时间</label>
|
||||
<div class="profile-edit-inline__date-range">
|
||||
<el-date-picker v-model="form.startDate" type="month" placeholder="开始" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||||
<span class="profile-edit-inline__date-sep">-</span>
|
||||
<el-date-picker v-model="form.endDate" type="month" placeholder="结束" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 实习经历 -->
|
||||
<template v-else-if="moduleType === 'internship'">
|
||||
<div class="profile-edit-inline__fields-row">
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">公司名称</label><input class="profile-edit-inline__input" placeholder="请输入公司名称" v-model="form.companyName" /></div>
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">职位</label><input class="profile-edit-inline__input" placeholder="请输入职位" v-model="form.position" /></div>
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">时间</label>
|
||||
<div class="profile-edit-inline__date-range">
|
||||
<el-date-picker v-model="form.startDate" type="month" placeholder="开始" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||||
<span class="profile-edit-inline__date-sep">-</span>
|
||||
<el-date-picker v-model="form.endDate" type="month" placeholder="结束" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 项目经历 -->
|
||||
<template v-else-if="moduleType === 'project'">
|
||||
<div class="profile-edit-inline__fields-row">
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">项目名称</label><input class="profile-edit-inline__input" placeholder="请输入项目名称" v-model="form.projectName" /></div>
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">项目角色</label><input class="profile-edit-inline__input" placeholder="请输入项目角色" v-model="form.role" /></div>
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">时间</label>
|
||||
<div class="profile-edit-inline__date-range">
|
||||
<el-date-picker v-model="form.startDate" type="month" placeholder="开始" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||||
<span class="profile-edit-inline__date-sep">-</span>
|
||||
<el-date-picker v-model="form.endDate" type="month" placeholder="结束" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 竞赛经历 -->
|
||||
<template v-else-if="moduleType === 'competition'">
|
||||
<div class="profile-edit-inline__fields-row">
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">竞赛名称</label><input class="profile-edit-inline__input" placeholder="请输入竞赛名称" v-model="form.competitionName" /></div>
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">获奖名次</label><input class="profile-edit-inline__input" placeholder="请输入获奖名次" v-model="form.award" /></div>
|
||||
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">获奖时间</label><el-date-picker v-model="form.awardDate" type="month" placeholder="请选择" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" /></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 经历描述 + AI润色 -->
|
||||
<div class="profile-edit-inline__summary-header">
|
||||
<label class="profile-edit-inline__label">经历描述</label>
|
||||
<button class="profile-edit-inline__ai-polish-btn" @click="handleAiPolish" :disabled="aiPolishing">
|
||||
<span>✦ AI润色</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="profile-edit-inline__field">
|
||||
<textarea class="profile-edit-inline__textarea" placeholder="请输入经历描述" v-model="descriptionText" rows="4"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<div class="profile-edit-inline__footer">
|
||||
<button class="profile-edit-inline__save-btn" @click="handleSave">保存</button>
|
||||
<button class="profile-edit-inline__cancel-btn" @click="handleCancel">取消</button>
|
||||
</div>
|
||||
|
||||
<!-- AI润色结果(保存取消下方) -->
|
||||
<div v-if="aiPolishResult" class="profile-edit-inline__ai-result">
|
||||
<p class="profile-edit-inline__ai-result-label">✦ AI润色结果</p>
|
||||
<div class="profile-edit-inline__ai-result-card">
|
||||
<p class="profile-edit-inline__ai-result-text">{{ aiPolishResult }}</p>
|
||||
</div>
|
||||
<div class="profile-edit-inline__ai-result-actions">
|
||||
<button class="profile-edit-inline__ai-replace-btn" @click="handleReplaceWithAi">+ 替换为此内容</button>
|
||||
<button class="profile-edit-inline__ai-regenerate-btn" @click="handleAiPolish" :disabled="aiPolishing">↻ 重新生成</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage, ElLoading } from 'element-plus'
|
||||
import { polishDiagnosisIssue } from '@/api/resume'
|
||||
|
||||
/** 描述段落结构 */
|
||||
interface DescriptionParagraph {
|
||||
id: string
|
||||
text: string
|
||||
}
|
||||
|
||||
/** Props */
|
||||
const props = defineProps<{
|
||||
/** 经历模块类型 */
|
||||
moduleType: string
|
||||
/** 初始数据(编辑已有经历时传入,新增时为空) */
|
||||
initialData?: Record<string, any>
|
||||
}>()
|
||||
|
||||
/** Emits */
|
||||
const emit = defineEmits<{
|
||||
(e: 'save', data: Record<string, any>): void
|
||||
(e: 'cancel'): void
|
||||
}>()
|
||||
|
||||
/** 表单数据 */
|
||||
const form = ref<Record<string, any>>({})
|
||||
|
||||
/** 经历描述文本(多段用换行拼接) */
|
||||
const descriptionText = ref('')
|
||||
|
||||
/** AI润色结果 */
|
||||
const aiPolishResult = ref('')
|
||||
|
||||
/** AI润色中 */
|
||||
const aiPolishing = ref(false)
|
||||
|
||||
/** 生成短ID */
|
||||
const generateId = (): string => Math.random().toString(36).substring(2, 8)
|
||||
|
||||
/** 初始化表单 */
|
||||
function initForm() {
|
||||
aiPolishResult.value = ''
|
||||
if (props.initialData) {
|
||||
form.value = { ...props.initialData }
|
||||
// 将 description 数组拼接为文本
|
||||
if (props.initialData.description?.length) {
|
||||
descriptionText.value = props.initialData.description.map((d: DescriptionParagraph) => d.text).join('\n')
|
||||
} else {
|
||||
descriptionText.value = ''
|
||||
}
|
||||
} else {
|
||||
// 新增模式 — 空表单
|
||||
form.value = {}
|
||||
descriptionText.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 立即初始化
|
||||
initForm()
|
||||
|
||||
// 监听 initialData 变化
|
||||
watch(() => props.initialData, () => { initForm() }, { deep: true })
|
||||
|
||||
/** AI润色 */
|
||||
async function handleAiPolish() {
|
||||
const text = descriptionText.value.trim()
|
||||
if (!text) {
|
||||
ElMessage.warning('请先输入经历描述')
|
||||
return
|
||||
}
|
||||
aiPolishing.value = true
|
||||
const loading = ElLoading.service({ lock: true, text: 'AI润色中…', background: 'rgba(0,0,0,0.5)' })
|
||||
try {
|
||||
const content = text.split('\n').filter(s => s.trim())
|
||||
const res = await polishDiagnosisIssue(content)
|
||||
if (res.code === 0 && res.data?.content) {
|
||||
aiPolishResult.value = res.data.content.join('\n')
|
||||
ElMessage.success('AI润色完成')
|
||||
} else {
|
||||
ElMessage.error(res.msg || '润色失败')
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('请求失败')
|
||||
} finally {
|
||||
aiPolishing.value = false
|
||||
loading.close()
|
||||
}
|
||||
}
|
||||
|
||||
/** 替换为AI润色内容 */
|
||||
function handleReplaceWithAi() {
|
||||
if (aiPolishResult.value) {
|
||||
descriptionText.value = aiPolishResult.value
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存 */
|
||||
function handleSave() {
|
||||
// 将描述文本按换行拆分为 description 数组
|
||||
const lines = descriptionText.value.split('\n').filter(s => s.trim())
|
||||
const description: DescriptionParagraph[] = lines.map(text => ({
|
||||
id: generateId(),
|
||||
text,
|
||||
}))
|
||||
// 如果有原始 description,保留原有的 id
|
||||
if (props.initialData?.description?.length) {
|
||||
props.initialData.description.forEach((d: DescriptionParagraph, i: number) => {
|
||||
if (i < description.length) {
|
||||
description[i].id = d.id
|
||||
}
|
||||
})
|
||||
}
|
||||
emit('save', { ...form.value, description })
|
||||
}
|
||||
|
||||
/** 取消 */
|
||||
function handleCancel() {
|
||||
emit('cancel')
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,403 @@
|
||||
<template>
|
||||
<!-- 一键修复抽屉 — 从右侧滑入,展示所有模块的诊断修复 -->
|
||||
<Teleport to="body">
|
||||
<!-- 遮罩层 -->
|
||||
<Transition name="fix-all-drawer-overlay">
|
||||
<div
|
||||
v-if="modelValue"
|
||||
class="fix-all-drawer-overlay"
|
||||
@click="handleClose"
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<!-- 抽屉主体 -->
|
||||
<Transition name="fix-all-drawer-slide">
|
||||
<div v-if="modelValue" class="fix-all-drawer" @click.stop>
|
||||
<!-- 顶部栏 -->
|
||||
<div class="fix-all-drawer__header">
|
||||
<button class="fix-all-drawer__close-btn" @click="handleClose" aria-label="收起">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="fix-all-drawer__close-icon">
|
||||
<path d="M10 3l-5 5 5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<h3 class="fix-all-drawer__title">一键修复</h3>
|
||||
</div>
|
||||
|
||||
<!-- 可滚动内容区 -->
|
||||
<div class="fix-all-drawer__body">
|
||||
<!-- 遍历每个有诊断数据的模块 -->
|
||||
<template v-for="module in moduleDataList" :key="module.moduleType">
|
||||
<!-- 模块标题 -->
|
||||
<h4 class="fix-all-drawer__module-title">{{ moduleTitleMap[module.moduleType] }}</h4>
|
||||
|
||||
<!-- 遍历该模块下每条经历/记录 -->
|
||||
<div
|
||||
v-for="record in module.records"
|
||||
:key="record.issueId"
|
||||
class="fix-all-drawer__record"
|
||||
>
|
||||
<!-- 经历标识(经历类模块显示标题,summary不显示) -->
|
||||
<p v-if="record.recordLabel" class="fix-all-drawer__record-label">{{ record.recordLabel }}</p>
|
||||
|
||||
<!-- 原文编辑区 -->
|
||||
<div class="fix-all-drawer__card">
|
||||
<textarea
|
||||
v-model="record.editText"
|
||||
class="fix-all-drawer__textarea"
|
||||
placeholder="原文内容…"
|
||||
@input="autoResizeTextarea($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- AI 润色结果区 -->
|
||||
<div class="fix-all-drawer__ai-section">
|
||||
<p class="fix-all-drawer__ai-label">✦ AI润色结果</p>
|
||||
<div class="fix-all-drawer__card fix-all-drawer__card--ai">
|
||||
<p class="fix-all-drawer__ai-text">{{ record.polishText }}</p>
|
||||
</div>
|
||||
<!-- 操作按钮 -->
|
||||
<div class="fix-all-drawer__ai-actions">
|
||||
<button class="fix-all-drawer__action-btn fix-all-drawer__action-btn--replace" @click="handleReplaceToOriginal(record)">+替换为此内容</button>
|
||||
<button class="fix-all-drawer__action-btn fix-all-drawer__action-btn--regenerate" @click="handleRegenerate(record)">↻ 重新生成</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 底部按钮区 -->
|
||||
<div class="fix-all-drawer__footer">
|
||||
<button class="fix-all-drawer__footer-btn fix-all-drawer__footer-btn--use-ai" @click="handleUseAllAi">一键使用AI润色结果</button>
|
||||
<button class="fix-all-drawer__footer-btn fix-all-drawer__footer-btn--save" @click="handleSaveCurrent">保存当前修改</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
import type { DiagnosisIssue, DescriptionParagraph } from '@/api/resume'
|
||||
import { polishDiagnosisIssue, resolveDiagnosisIssue } from '@/api/resume'
|
||||
|
||||
// ==================== Props ====================
|
||||
|
||||
/** 单条经历记录的原始数据(父组件传入) */
|
||||
export interface OriginalRecordData {
|
||||
/** 模块类型 */
|
||||
moduleType: string
|
||||
/** 经历记录 ID(summary 时为 resumeId) */
|
||||
recordId: string
|
||||
/** 经历显示标题(如"北京大学-经历描述") */
|
||||
recordLabel: string
|
||||
/** 原文内容:summary 为字符串,经历为 DescriptionParagraph[] */
|
||||
originalContent: string | DescriptionParagraph[]
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
/** 控制抽屉显示/隐藏 */
|
||||
modelValue: boolean
|
||||
/** 所有诊断问题列表 */
|
||||
issues: DiagnosisIssue[]
|
||||
/** 所有模块的原始经历数据 */
|
||||
originalRecords: OriginalRecordData[]
|
||||
}>()
|
||||
|
||||
|
||||
/** 提交结果的单条数据结构 */
|
||||
export interface FixAllSubmitItem {
|
||||
/** 模块类型 */
|
||||
moduleType: string
|
||||
/** 经历记录 ID */
|
||||
recordId: string
|
||||
/** 新的内容:summary 为字符串,经历为 DescriptionParagraph[] */
|
||||
newContent: string | DescriptionParagraph[]
|
||||
/** 关联的 issue ID */
|
||||
issueId: string
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', val: boolean): void
|
||||
/** 保存当前修改:携带有变化的模块数据 */
|
||||
(e: 'save', items: FixAllSubmitItem[]): void
|
||||
/** 一键使用AI润色结果:携带所有模块数据(强制更新) */
|
||||
(e: 'useAllAi', items: FixAllSubmitItem[]): void
|
||||
}>()
|
||||
|
||||
// ==================== 内部数据结构 ====================
|
||||
|
||||
/** 单条记录的内部状态 */
|
||||
interface RecordState {
|
||||
/** 关联的 issue ID */
|
||||
issueId: string
|
||||
/** 模块类型 */
|
||||
moduleType: string
|
||||
/** 经历记录 ID */
|
||||
recordId: string
|
||||
/** 经历显示标题 */
|
||||
recordLabel: string
|
||||
/** 原文编辑框内容(用户可编辑) */
|
||||
editText: string
|
||||
/** AI 润色结果文本 */
|
||||
polishText: string
|
||||
/** 原始内容(用于对比是否有变化) */
|
||||
originalText: string
|
||||
}
|
||||
|
||||
/** 模块分组数据 */
|
||||
interface ModuleGroup {
|
||||
/** 模块类型 */
|
||||
moduleType: string
|
||||
/** 该模块下所有记录 */
|
||||
records: RecordState[]
|
||||
}
|
||||
|
||||
// ==================== 模块标题映射 ====================
|
||||
|
||||
/** 模块类型到中文标题的映射 */
|
||||
const moduleTitleMap: Record<string, string> = {
|
||||
summary: '个人概述',
|
||||
education: '教育背景',
|
||||
work: '工作经历',
|
||||
internship: '实习经历',
|
||||
project: '项目经历',
|
||||
competition: '竞赛经历',
|
||||
}
|
||||
|
||||
/** 模块顺序 */
|
||||
const moduleOrder = ['summary', 'education', 'work', 'internship', 'project', 'competition']
|
||||
|
||||
// ==================== 响应式数据 ====================
|
||||
|
||||
/** 所有模块分组后的数据列表 */
|
||||
const moduleDataList = ref<ModuleGroup[]>([])
|
||||
|
||||
// ==================== 工具方法 ====================
|
||||
|
||||
/** 生成 8 位随机字母数字 ID */
|
||||
function generateId(): string {
|
||||
return Math.random().toString(36).substring(2, 10)
|
||||
}
|
||||
|
||||
/** 将 DescriptionParagraph[] 拼接为字符串(每段文字后加换行) */
|
||||
function descriptionToText(desc: DescriptionParagraph[]): string {
|
||||
return desc.map(d => d.text || '').join('\n')
|
||||
}
|
||||
|
||||
/** 将文本按换行拆分为 DescriptionParagraph[](每行生成新 id) */
|
||||
function textToDescription(text: string): DescriptionParagraph[] {
|
||||
const lines = text.split('\n').filter(line => line.trim() !== '')
|
||||
return lines.map(line => ({ id: generateId(), text: line }))
|
||||
}
|
||||
|
||||
/** 获取 issue 的 optimizedContent 转为文本 */
|
||||
function getOptimizedText(issue: DiagnosisIssue): string {
|
||||
const oc = issue.optimizedContent
|
||||
if (!oc) return ''
|
||||
// summary:optimizedContent 是字符串
|
||||
if (typeof oc === 'string') return oc
|
||||
// 经历模块:数组对象,取每个子元素的 optimizedContent 或 text 拼接
|
||||
if (Array.isArray(oc)) {
|
||||
return oc.map((item: any) => item.optimizedContent || item.text || '').join('\n')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// ==================== 初始化逻辑 ====================
|
||||
|
||||
/** 抽屉打开时初始化数据 */
|
||||
watch(() => props.modelValue, (val) => {
|
||||
if (val) {
|
||||
initData()
|
||||
}
|
||||
})
|
||||
|
||||
/** 根据 props 中的 issues 和 originalRecords 初始化内部数据 */
|
||||
function initData() {
|
||||
const groupMap: Record<string, RecordState[]> = {}
|
||||
|
||||
// 遍历每个 issue,找到对应的原始记录数据
|
||||
for (const issue of props.issues) {
|
||||
if (!issue.id || !issue.moduleType || issue.status !== 0) continue
|
||||
|
||||
const moduleType = issue.moduleType
|
||||
const recordId = issue.moduleRecordId || ''
|
||||
|
||||
// 找到对应的原始记录
|
||||
const originalRecord = props.originalRecords.find(
|
||||
r => r.moduleType === moduleType && r.recordId === recordId
|
||||
)
|
||||
|
||||
// 原文转文本
|
||||
let originalText = ''
|
||||
if (originalRecord) {
|
||||
if (typeof originalRecord.originalContent === 'string') {
|
||||
originalText = originalRecord.originalContent
|
||||
} else if (Array.isArray(originalRecord.originalContent)) {
|
||||
originalText = descriptionToText(originalRecord.originalContent)
|
||||
}
|
||||
}
|
||||
|
||||
// AI 润色结果:初始用 optimizedContent
|
||||
const polishText = getOptimizedText(issue)
|
||||
|
||||
const state: RecordState = {
|
||||
issueId: issue.id,
|
||||
moduleType,
|
||||
recordId,
|
||||
recordLabel: originalRecord?.recordLabel || '',
|
||||
editText: originalText,
|
||||
polishText,
|
||||
originalText,
|
||||
}
|
||||
|
||||
if (!groupMap[moduleType]) {
|
||||
groupMap[moduleType] = []
|
||||
}
|
||||
groupMap[moduleType].push(state)
|
||||
}
|
||||
|
||||
// 按模块顺序排列
|
||||
moduleDataList.value = moduleOrder
|
||||
.filter(mt => groupMap[mt]?.length)
|
||||
.map(mt => ({ moduleType: mt, records: groupMap[mt] }))
|
||||
|
||||
// 等 DOM 渲染完成后自动调整 textarea 高度
|
||||
nextTick(() => {
|
||||
const textareas = document.querySelectorAll('.fix-all-drawer__textarea')
|
||||
textareas.forEach(el => {
|
||||
const ta = el as HTMLTextAreaElement
|
||||
ta.style.height = 'auto'
|
||||
ta.style.height = ta.scrollHeight + 'px'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 事件处理 ====================
|
||||
|
||||
/** textarea 自适应高度 */
|
||||
function autoResizeTextarea(event: Event) {
|
||||
const el = event.target as HTMLTextAreaElement
|
||||
el.style.height = 'auto'
|
||||
el.style.height = el.scrollHeight + 'px'
|
||||
}
|
||||
|
||||
/** 替换为此内容 — 将 AI 润色结果替换到原文编辑框 */
|
||||
function handleReplaceToOriginal(record: RecordState) {
|
||||
record.editText = record.polishText
|
||||
// 触发 textarea 重新计算高度
|
||||
nextTick(() => {
|
||||
const textareas = document.querySelectorAll('.fix-all-drawer__textarea')
|
||||
textareas.forEach(el => {
|
||||
const ta = el as HTMLTextAreaElement
|
||||
ta.style.height = 'auto'
|
||||
ta.style.height = ta.scrollHeight + 'px'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** 重新生成 — 调用 AI 润色接口获取新结果 */
|
||||
async function handleRegenerate(record: RecordState) {
|
||||
if (!record.issueId) return
|
||||
|
||||
// 收集当前原文编辑框内容作为输入
|
||||
let content: string[]
|
||||
if (record.moduleType === 'summary') {
|
||||
content = [record.editText.trim()]
|
||||
} else {
|
||||
content = record.editText.split('\n').filter(line => line.trim() !== '')
|
||||
}
|
||||
|
||||
if (!content.length) {
|
||||
ElMessage.warning('原文内容为空,无法生成')
|
||||
return
|
||||
}
|
||||
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: 'AI 重新生成中…',
|
||||
background: 'rgba(0, 0, 0, 0.5)',
|
||||
customClass: 'resume-upload-loading',
|
||||
})
|
||||
|
||||
try {
|
||||
const res = await polishDiagnosisIssue(content)
|
||||
if (res.code === 0 && res.data?.content) {
|
||||
// 将返回的数组内容用换行拼接
|
||||
record.polishText = res.data.content.join('\n')
|
||||
ElMessage.success('重新生成完成')
|
||||
} else {
|
||||
ElMessage.error(res.msg || '生成失败,请稍后重试')
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('请求失败,请稍后重试')
|
||||
} finally {
|
||||
loading.close()
|
||||
}
|
||||
}
|
||||
|
||||
/** 一键使用AI润色结果 — 所有模块强制使用 AI 润色结果更新 */
|
||||
function handleUseAllAi() {
|
||||
const items: FixAllSubmitItem[] = []
|
||||
|
||||
for (const module of moduleDataList.value) {
|
||||
for (const record of module.records) {
|
||||
let newContent: string | DescriptionParagraph[]
|
||||
if (record.moduleType === 'summary') {
|
||||
// summary 直接用润色文本
|
||||
newContent = record.polishText
|
||||
} else {
|
||||
// 经历模块:按换行拆分为 DescriptionParagraph[]
|
||||
newContent = textToDescription(record.polishText)
|
||||
}
|
||||
items.push({
|
||||
moduleType: record.moduleType,
|
||||
recordId: record.recordId,
|
||||
newContent,
|
||||
issueId: record.issueId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
emit('useAllAi', items)
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
/** 保存当前修改 — 只提交内容有变化的模块 */
|
||||
function handleSaveCurrent() {
|
||||
const items: FixAllSubmitItem[] = []
|
||||
|
||||
for (const module of moduleDataList.value) {
|
||||
for (const record of module.records) {
|
||||
// 对比当前编辑内容和原始内容是否有变化
|
||||
if (record.editText.trim() === record.originalText.trim()) continue
|
||||
|
||||
let newContent: string | DescriptionParagraph[]
|
||||
if (record.moduleType === 'summary') {
|
||||
newContent = record.editText
|
||||
} else {
|
||||
newContent = textToDescription(record.editText)
|
||||
}
|
||||
items.push({
|
||||
moduleType: record.moduleType,
|
||||
recordId: record.recordId,
|
||||
newContent,
|
||||
issueId: record.issueId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!items.length) {
|
||||
ElMessage.info('没有检测到内容变化')
|
||||
return
|
||||
}
|
||||
|
||||
emit('save', items)
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
/** 关闭抽屉 */
|
||||
function handleClose() {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
</script>
|
||||
@@ -290,7 +290,7 @@ async function handleAiPolish() {
|
||||
})
|
||||
|
||||
try {
|
||||
const res = await polishDiagnosisIssue(issueId, content)
|
||||
const res = await polishDiagnosisIssue(content)
|
||||
if (res.code === 0 && res.data?.content) {
|
||||
polishResult.value = res.data.content
|
||||
ElMessage.success('润色完成')
|
||||
|
||||
+417
-909
File diff suppressed because it is too large
Load Diff
@@ -449,7 +449,7 @@ function fillJobData(data: JobDetailData) {
|
||||
job.title = data.jobTitle || ''
|
||||
job.location = data.regionName || ''
|
||||
job.experience = formatExperience(data.minExperience)
|
||||
job.type = formatEmploymentType(data.employmentType)
|
||||
job.type = formatEmploymentType(data.recruitCategory)
|
||||
job.education = formatEducation(data.education)
|
||||
job.salary = data.salary || ''
|
||||
job.matchScore = data.matchScore ?? 0
|
||||
|
||||
+71
-19
@@ -10,7 +10,7 @@
|
||||
@save="handleSaveEdit"
|
||||
/>
|
||||
|
||||
<!-- 欢迎上传简历弹窗(无个人资料时自动弹出) -->
|
||||
<!-- 欢迎上传简历弹窗(无个人资料主表记录时自动弹出) -->
|
||||
<ResumeUploadDialog v-model="showWelcomeDialog" @confirm="handleWelcomeUpload" />
|
||||
|
||||
<!-- 页面标题 -->
|
||||
@@ -27,27 +27,12 @@
|
||||
<ProfilePageContent
|
||||
:profile="profile"
|
||||
@edit="handleEdit"
|
||||
@save="handleInlineSave"
|
||||
@exp-update="handleExpUpdate"
|
||||
@exp-add="handleExpAdd"
|
||||
/>
|
||||
|
||||
<!-- 右侧入口 -->
|
||||
<div class="profile-page__sidebar">
|
||||
<!-- 提示卡片 -->
|
||||
<div class="profile-page__tip-card">
|
||||
<p class="profile-page__tip-text">
|
||||
非常棒!你的个人资料已经被完善了,自动填写信息也已变换,现在可以轻松开始投递简历啦!
|
||||
</p>
|
||||
<button class="profile-page__tip-btn" @click="goToJobs">去投递</button>
|
||||
</div>
|
||||
|
||||
<!-- 管理简历入口 -->
|
||||
<div class="profile-page__nav-entry" @click="goToResume">
|
||||
<span class="profile-page__nav-entry-icon">📄</span>
|
||||
<span class="profile-page__nav-entry-text">管理我的简历</span>
|
||||
<svg viewBox="0 0 16 16" fill="none" class="profile-page__nav-arrow">
|
||||
<path d="M6 4l4 4-4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -658,4 +643,71 @@ function goToJobs() {
|
||||
function goToResume() {
|
||||
router.push('/resume')
|
||||
}
|
||||
|
||||
/** 内联保存处理(个人信息/技能/证书模块从组件内直接保存) */
|
||||
async function handleInlineSave(section: string, data: Record<string, any>) {
|
||||
// 复用已有的 handleSaveEdit 逻辑
|
||||
editModule.value = section
|
||||
await handleSaveEdit(data)
|
||||
}
|
||||
|
||||
/** 单条经历更新 — 将编辑后的数据替换到完整列表中再整体保存 */
|
||||
async function handleExpUpdate(moduleType: string, index: number, data: Record<string, any>) {
|
||||
try {
|
||||
if (moduleType === 'education') {
|
||||
const list = profile.value.education.map((item, i) => i === index ? data : item) as any[]
|
||||
await saveEducation(list)
|
||||
await loadEducation()
|
||||
} else if (moduleType === 'work') {
|
||||
const list = profile.value.works.map((item, i) => i === index ? data : item) as any[]
|
||||
await saveWork(list)
|
||||
await loadWork()
|
||||
} else if (moduleType === 'internship') {
|
||||
const list = profile.value.internships.map((item, i) => i === index ? data : item) as any[]
|
||||
await saveInternship(list)
|
||||
await loadInternship()
|
||||
} else if (moduleType === 'project') {
|
||||
const list = profile.value.projects.map((item, i) => i === index ? data : item) as any[]
|
||||
await saveProject(list)
|
||||
await loadProject()
|
||||
} else if (moduleType === 'competition') {
|
||||
const list = profile.value.competitions.map((item, i) => i === index ? data : item) as any[]
|
||||
await saveCompetition(list)
|
||||
await loadCompetition()
|
||||
}
|
||||
ElMessage.success('保存成功')
|
||||
} catch {
|
||||
ElMessage.error('保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
/** 新增经历 */
|
||||
async function handleExpAdd(moduleType: string, data: Record<string, any>) {
|
||||
try {
|
||||
if (moduleType === 'education') {
|
||||
const list = [...profile.value.education, data] as any[]
|
||||
await saveEducation(list)
|
||||
await loadEducation()
|
||||
} else if (moduleType === 'work') {
|
||||
const list = [...profile.value.works, data] as any[]
|
||||
await saveWork(list)
|
||||
await loadWork()
|
||||
} else if (moduleType === 'internship') {
|
||||
const list = [...profile.value.internships, data] as any[]
|
||||
await saveInternship(list)
|
||||
await loadInternship()
|
||||
} else if (moduleType === 'project') {
|
||||
const list = [...profile.value.projects, data] as any[]
|
||||
await saveProject(list)
|
||||
await loadProject()
|
||||
} else if (moduleType === 'competition') {
|
||||
const list = [...profile.value.competitions, data] as any[]
|
||||
await saveCompetition(list)
|
||||
await loadCompetition()
|
||||
}
|
||||
ElMessage.success('添加成功')
|
||||
} catch {
|
||||
ElMessage.error('添加失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
+73
-79
@@ -25,7 +25,6 @@
|
||||
<th class="resume-page__th">简历</th>
|
||||
<th class="resume-page__th">目标岗位</th>
|
||||
<th class="resume-page__th">最近修改</th>
|
||||
<th class="resume-page__th">创建时间</th>
|
||||
<th class="resume-page__th resume-page__th--action"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -38,38 +37,39 @@
|
||||
>
|
||||
<td class="resume-page__td">
|
||||
<div class="resume-page__name-cell">
|
||||
<span class="resume-page__avatar" :style="{ background: item.avatarColor }">
|
||||
{{ item.avatarLetter }}
|
||||
</span>
|
||||
<!-- <span class="resume-page__avatar" :style="{ background: item.avatarColor }">-->
|
||||
<!-- {{ item.avatarLetter }}-->
|
||||
<!-- </span>-->
|
||||
<span class="resume-page__name">{{ item.name }}</span>
|
||||
<span v-if="item.isDefault" class="resume-page__default-tag">默认</span>
|
||||
<!-- 编辑名称岗位图标 -->
|
||||
<button
|
||||
class="resume-page__edit-btn"
|
||||
aria-label="编辑名称岗位"
|
||||
@click.stop="handleEditName(item)"
|
||||
>
|
||||
<el-icon class="resume-page__edit-icon"><EditPen /></el-icon>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="resume-page__td">{{ item.targetJob }}</td>
|
||||
<td class="resume-page__td">{{ item.updatedAt }}</td>
|
||||
<td class="resume-page__td">{{ item.createdAt }}</td>
|
||||
<!-- 操作按钮列 -->
|
||||
<td class="resume-page__td resume-page__td--action">
|
||||
<button
|
||||
class="resume-page__more-btn"
|
||||
aria-label="更多操作"
|
||||
@click.stop="toggleMenu(item.id)"
|
||||
>
|
||||
<svg viewBox="0 0 16 16" fill="currentColor" class="resume-page__more-svg">
|
||||
<circle cx="3" cy="8" r="1.5"/>
|
||||
<circle cx="8" cy="8" r="1.5"/>
|
||||
<circle cx="13" cy="8" r="1.5"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- 弹出菜单 -->
|
||||
<div v-if="activeMenuId === item.id" class="resume-page__popup" @click.stop>
|
||||
<div
|
||||
v-for="action in popupActions"
|
||||
:key="action"
|
||||
class="resume-page__popup-item"
|
||||
@click="handleAction(action, item.id)"
|
||||
>
|
||||
{{ action }}
|
||||
</div>
|
||||
<div class="resume-page__actions">
|
||||
<button
|
||||
v-if="!item.isDefault"
|
||||
class="resume-page__action-btn"
|
||||
@click.stop="handleSetDefault(item.id)"
|
||||
>设为默认</button>
|
||||
<button
|
||||
class="resume-page__action-btn"
|
||||
@click.stop="handleExport(item)"
|
||||
>导出</button>
|
||||
<button
|
||||
class="resume-page__action-btn resume-page__action-btn--delete"
|
||||
@click.stop="handleDelete(item.id)"
|
||||
>删除</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -104,13 +104,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { EditPen } from '@element-plus/icons-vue'
|
||||
import SideNav from '@/components/SideNav.vue'
|
||||
import ResumeEditNameDialog from '@/components/ResumeEditNameDialog.vue'
|
||||
import ResumeExportDialog from '@/components/ResumeExportDialog.vue'
|
||||
import ResumeUploadDialog from '@/components/ResumeUploadDialog.vue'
|
||||
import { uploadResume } from '@/utils/aiRequest'
|
||||
import {
|
||||
fetchResumeList, deleteResume, type ResumeListItem,
|
||||
fetchResumeList, deleteResume, setDefaultResume, type ResumeListItem,
|
||||
} from '@/api/resume'
|
||||
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
|
||||
// 命令式调用的组件,按需引入插件不会自动加载其样式,需手动引入
|
||||
@@ -203,9 +204,6 @@ onMounted(() => {
|
||||
|
||||
// ==================== 页面状态 ====================
|
||||
|
||||
/** 当前打开弹出菜单的简历 ID */
|
||||
const activeMenuId = ref<string | null>(null)
|
||||
|
||||
// ==================== 编辑简历名称弹窗状态 ====================
|
||||
|
||||
/** 编辑弹窗是否可见 */
|
||||
@@ -231,61 +229,57 @@ const exportResumeName = ref('')
|
||||
/** 上传弹窗是否可见 */
|
||||
const uploadDialogVisible = ref(false)
|
||||
|
||||
/** 弹出菜单操作项 */
|
||||
const popupActions = ['设为默认简历', '编辑名称岗位', '导出简历', '删除']
|
||||
|
||||
// ==================== 事件处理 ====================
|
||||
|
||||
/** 切换弹出菜单的显示/隐藏 */
|
||||
function toggleMenu(id: string) {
|
||||
activeMenuId.value = activeMenuId.value === id ? null : id
|
||||
/** 点击编辑图标 — 打开编辑名称岗位弹窗 */
|
||||
function handleEditName(item: ResumeItem) {
|
||||
editResumeId.value = item.id
|
||||
editResumeName.value = item.name
|
||||
editTargetPosition.value = item.targetJob
|
||||
editNameVisible.value = true
|
||||
}
|
||||
|
||||
/** 弹出菜单操作点击 */
|
||||
async function handleAction(action: string, id: string) {
|
||||
activeMenuId.value = null
|
||||
|
||||
if (action === '删除') {
|
||||
// 二次确认删除
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这份简历吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
|
||||
// 调用删除接口
|
||||
const res = await deleteResume(id)
|
||||
if (res.code === '0') {
|
||||
ElMessage.success('删除成功')
|
||||
// 刷新列表
|
||||
loadResumeList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
// 用户取消删除或接口报错
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('删除失败,请稍后重试')
|
||||
}
|
||||
/** 设为默认简历 */
|
||||
async function handleSetDefault(id: string) {
|
||||
try {
|
||||
const res = await setDefaultResume(id)
|
||||
if (res.code === '0') {
|
||||
ElMessage.success('已设为默认简历')
|
||||
loadResumeList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '设置失败')
|
||||
}
|
||||
} else if (action === '编辑名称岗位') {
|
||||
// 找到当前简历数据,打开编辑弹窗
|
||||
const target = resumeList.value.find(r => r.id === id)
|
||||
if (target) {
|
||||
editResumeId.value = id
|
||||
editResumeName.value = target.name
|
||||
editTargetPosition.value = target.targetJob
|
||||
editNameVisible.value = true
|
||||
} catch {
|
||||
ElMessage.error('设置失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
/** 导出简历 */
|
||||
function handleExport(item: ResumeItem) {
|
||||
exportResumeId.value = item.id
|
||||
exportResumeName.value = item.name || '简历'
|
||||
exportDialogVisible.value = true
|
||||
}
|
||||
|
||||
/** 删除简历 */
|
||||
async function handleDelete(id: string) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这份简历吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
const res = await deleteResume(id)
|
||||
if (res.code === '0') {
|
||||
ElMessage.success('删除成功')
|
||||
loadResumeList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('删除失败,请稍后重试')
|
||||
}
|
||||
} else if (action === '导出简历') {
|
||||
// 打开导出格式选择弹窗
|
||||
exportResumeId.value = id
|
||||
const target = resumeList.value.find(r => r.id === id)
|
||||
exportResumeName.value = target?.name || '简历'
|
||||
exportDialogVisible.value = true
|
||||
} else {
|
||||
console.log(action, id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+644
-285
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user