登陆页左侧新广告特效
This commit is contained in:
+6
-2
@@ -67,8 +67,10 @@ export interface ResumeMainData {
|
||||
email?: string
|
||||
/** 手机号码 */
|
||||
mobileNumber?: string
|
||||
/** 所在城市 */
|
||||
/** 所在城市(名称) */
|
||||
city?: string
|
||||
/** 所在城市编码 */
|
||||
regionCode?: string
|
||||
/** 微信号 */
|
||||
wechatNumber?: string
|
||||
/** 作品集链接 */
|
||||
@@ -251,8 +253,10 @@ export interface SaveResumeMainParams {
|
||||
email?: string
|
||||
/** 手机号码 */
|
||||
mobileNumber?: string
|
||||
/** 所在城市 */
|
||||
/** 所在城市(名称) */
|
||||
city?: string
|
||||
/** 所在城市编码 */
|
||||
regionCode?: string
|
||||
/** 微信号 */
|
||||
wechatNumber?: string
|
||||
/** 作品集链接 */
|
||||
|
||||
@@ -737,10 +737,13 @@ body:not(#_) {
|
||||
.ml100 {margin-left: 1rem;}
|
||||
.ml135 {margin-left: 1.35rem;}
|
||||
|
||||
.mlauto {margin-left: auto;}
|
||||
|
||||
/* margin-right */
|
||||
.mr0 {margin-right: 0rem;}
|
||||
.mr1 {margin-right: 0.01rem;} .mr2 {margin-right: 0.02rem;} .mr3 {margin-right: 0.03rem;} .mr4 {margin-right: 0.04rem;} .mr5 {margin-right: 0.05rem;} .mr6 {margin-right: 0.06rem;} .mr7 {margin-right: 0.07rem;} .mr8 {margin-right: 0.08rem;} .mr9 {margin-right: 0.09rem;} .mr10 {margin-right: 0.1rem;} .mr11 {margin-right: 0.11rem;} .mr12 {margin-right: 0.12rem;} .mr13 {margin-right: 0.13rem;} .mr14 {margin-right: 0.14rem;} .mr15 {margin-right: 0.15rem;} .mr16 {margin-right: 0.16rem;} .mr17 {margin-right: 0.17rem;} .mr18 {margin-right: 0.18rem;} .mr19 {margin-right: 0.19rem;} .mr20 {margin-right: 0.2rem;} .mr21 {margin-right: 0.21rem;} .mr22 {margin-right: 0.22rem;} .mr23 {margin-right: 0.23rem;} .mr24 {margin-right: 0.24rem;} .mr25 {margin-right: 0.25rem;} .mr26 {margin-right: 0.26rem;} .mr27 {margin-right: 0.27rem;} .mr28 {margin-right: 0.28rem;} .mr29 {margin-right: 0.29rem;} .mr30 {margin-right: 0.3rem;} .mr31 {margin-right: 0.31rem;} .mr32 {margin-right: 0.32rem;} .mr33 {margin-right: 0.33rem;} .mr34 {margin-right: 0.34rem;} .mr35 {margin-right: 0.35rem;} .mr36 {margin-right: 0.36rem;} .mr37 {margin-right: 0.37rem;} .mr38 {margin-right: 0.38rem;} .mr39 {margin-right: 0.39rem;} .mr40 {margin-right: 0.4rem;} .mr41 {margin-right: 0.41rem;} .mr42 {margin-right: 0.42rem;} .mr43 {margin-right: 0.43rem;} .mr44 {margin-right: 0.44rem;} .mr45 {margin-right: 0.45rem;} .mr46 {margin-right: 0.46rem;} .mr47 {margin-right: 0.47rem;} .mr48 {margin-right: 0.48rem;} .mr49 {margin-right: 0.49rem;} .mr50 {margin-right: 0.5rem;} .mr51 {margin-right: 0.51rem;} .mr52 {margin-right: 0.52rem;} .mr53 {margin-right: 0.53rem;} .mr54 {margin-right: 0.54rem;} .mr55 {margin-right: 0.55rem;} .mr56 {margin-right: 0.56rem;} .mr57 {margin-right: 0.57rem;} .mr58 {margin-right: 0.58rem;} .mr59 {margin-right: 0.59rem;} .mr60 {margin-right: 0.6rem;} .mr61 {margin-right: 0.61rem;} .mr62 {margin-right: 0.62rem;} .mr63 {margin-right: 0.63rem;} .mr64 {margin-right: 0.64rem;} .mr65 {margin-right: 0.65rem;} .mr66 {margin-right: 0.66rem;} .mr67 {margin-right: 0.67rem;} .mr68 {margin-right: 0.68rem;} .mr69 {margin-right: 0.69rem;} .mr70 {margin-right: 0.7rem;} .mr71 {margin-right: 0.71rem;} .mr72 {margin-right: 0.72rem;} .mr73 {margin-right: 0.73rem;} .mr74 {margin-right: 0.74rem;} .mr75 {margin-right: 0.75rem;} .mr76 {margin-right: 0.76rem;} .mr77 {margin-right: 0.77rem;} .mr78 {margin-right: 0.78rem;} .mr79 {margin-right: 0.79rem;} .mr80 {margin-right: 0.8rem;} .mr81 {margin-right: 0.81rem;} .mr82 {margin-right: 0.82rem;} .mr83 {margin-right: 0.83rem;} .mr84 {margin-right: 0.84rem;} .mr85 {margin-right: 0.85rem;} .mr86 {margin-right: 0.86rem;} .mr87 {margin-right: 0.87rem;} .mr88 {margin-right: 0.88rem;} .mr89 {margin-right: 0.89rem;} .mr90 {margin-right: 0.9rem;} .mr91 {margin-right: 0.91rem;} .mr92 {margin-right: 0.92rem;} .mr93 {margin-right: 0.93rem;} .mr94 {margin-right: 0.94rem;} .mr95 {margin-right: 0.95rem;} .mr96 {margin-right: 0.96rem;} .mr97 {margin-right: 0.97rem;} .mr98 {margin-right: 0.98rem;} .mr99 {margin-right: 0.99rem;}
|
||||
.mr100 {margin-right: 1rem;}
|
||||
.mrauto {margin-right: auto;}
|
||||
|
||||
/* margin-right-left */
|
||||
.mrl0 {margin-right: 0rem;margin-left: 0rem;}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
padding-bottom: 0.14rem;
|
||||
border-bottom: 1px solid $border-color;
|
||||
flex-shrink: 0;
|
||||
margin-bottom:0.14rem
|
||||
}
|
||||
|
||||
&__tab-item {
|
||||
@@ -62,6 +63,14 @@
|
||||
line-height: 1.5;
|
||||
border-radius: 0.08rem;
|
||||
|
||||
&:hover .profile-page-content__card-header{
|
||||
background: #F7F7F7;
|
||||
.profile-page-content__edit-btn-inline{
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
//border-bottom: 1px solid $border-color;
|
||||
}
|
||||
@@ -130,9 +139,9 @@
|
||||
|
||||
&__info-contacts {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.16rem;
|
||||
margin-bottom: 0.06rem;
|
||||
}
|
||||
|
||||
&__contact-item {
|
||||
@@ -160,18 +169,22 @@
|
||||
&__item-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
&__item-title {
|
||||
font-size: 0.14rem;
|
||||
font-size: 0.16rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
margin-bottom: 0.03rem;
|
||||
}
|
||||
|
||||
&__item-sub {
|
||||
font-size: 0.12rem;
|
||||
color: #666;
|
||||
font-size: 0.16rem;
|
||||
color: $text-dark;
|
||||
margin-bottom: 0.02rem;
|
||||
}
|
||||
|
||||
@@ -182,11 +195,10 @@
|
||||
}
|
||||
|
||||
&__item-period {
|
||||
font-size: 0.11rem;
|
||||
color: $text-light;
|
||||
font-size: 0.13rem;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
margin-left: 0.12rem;
|
||||
}
|
||||
|
||||
&__item-desc {
|
||||
@@ -261,7 +273,7 @@
|
||||
border: none;
|
||||
color: $accent;
|
||||
cursor: pointer;
|
||||
padding: 0.04rem 0.08rem;
|
||||
padding: 0 0.08rem;
|
||||
border-radius: 0.04rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -279,7 +291,6 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $theme-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,12 +306,10 @@
|
||||
|
||||
// 标题行hover显示添加按钮(5大经历)
|
||||
&__header-hover:hover {
|
||||
background: #F7F7F7;
|
||||
border-radius: 0.06rem;
|
||||
}
|
||||
|
||||
&__header-hover:hover &__edit-btn-inline {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,15 @@
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #ddf4f2;
|
||||
border-right: 0.08rem solid #4FC2C9;
|
||||
}
|
||||
|
||||
.login-jianshi-logo {
|
||||
position: absolute;
|
||||
right: 22px;
|
||||
bottom: 10px;
|
||||
width: 82px;
|
||||
height: 21px;
|
||||
right: 0.22rem;
|
||||
bottom: 0.1rem;
|
||||
width: 0.82rem;
|
||||
height: 0.21rem;
|
||||
object-fit: contain;
|
||||
pointer-events: auto;
|
||||
user-select: none;
|
||||
@@ -33,28 +34,28 @@
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 1100px;
|
||||
height: 900px;
|
||||
width: 11rem;
|
||||
height: 9rem;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1405px 1269px;
|
||||
background-position: -210px -117px;
|
||||
background-size: 14.05rem 12.69rem;
|
||||
background-position: -2.1rem -1.17rem;
|
||||
z-index: 1;
|
||||
transform-origin: top left;
|
||||
transform: translate3d(var(--stage-x, 0px), var(--stage-y, 0px), 0) scale(var(--stage-scale, 1));
|
||||
transform: translate3d(var(--stage-x, 0rem), var(--stage-y, 0rem), 0) scale(var(--stage-scale, 1));
|
||||
}
|
||||
|
||||
/* 标题 */
|
||||
.hero-title {
|
||||
position: absolute;
|
||||
left: 35px;
|
||||
top: 64px;
|
||||
width: 374px;
|
||||
height: 88px;
|
||||
left: 0.35rem;
|
||||
top: 0.64rem;
|
||||
width: 3.74rem;
|
||||
height: 0.88rem;
|
||||
color: #2a8186;
|
||||
font-size: 36px;
|
||||
font-size: 0.36rem;
|
||||
font-weight: 700;
|
||||
line-height: 44px;
|
||||
line-height: 0.44rem;
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
@@ -62,16 +63,16 @@
|
||||
|
||||
.hero-title__second-line {
|
||||
display: block;
|
||||
padding-left: 72px;
|
||||
padding-left: 0.72rem;
|
||||
}
|
||||
|
||||
/* Nova 核心机器人 */
|
||||
.nova-core {
|
||||
position: absolute;
|
||||
left: 183px;
|
||||
top: 259px;
|
||||
width: 592px;
|
||||
height: 443px;
|
||||
left: 1.83rem;
|
||||
top: 2.59rem;
|
||||
width: 5.92rem;
|
||||
height: 4.43rem;
|
||||
pointer-events: auto;
|
||||
z-index: 2;
|
||||
user-select: none;
|
||||
@@ -89,8 +90,8 @@
|
||||
|
||||
.nova-eye {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 44px;
|
||||
width: 0.2rem;
|
||||
height: 0.44rem;
|
||||
object-fit: contain;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
@@ -100,13 +101,13 @@
|
||||
}
|
||||
|
||||
.nova-eye-left {
|
||||
left: 253.77px;
|
||||
top: 177px;
|
||||
left: 2.5377rem;
|
||||
top: 1.77rem;
|
||||
}
|
||||
|
||||
.nova-eye-right {
|
||||
left: 310.77px;
|
||||
top: 177px;
|
||||
left: 3.1077rem;
|
||||
top: 1.77rem;
|
||||
}
|
||||
|
||||
.nova-eye.is-blinking {
|
||||
@@ -132,8 +133,8 @@
|
||||
pointer-events: none;
|
||||
object-fit: contain;
|
||||
will-change: opacity, transform, filter;
|
||||
transform: translate3d(0, 4px, 0) scale(.996);
|
||||
filter: drop-shadow(0 16px 28px rgba(56, 255, 202, .10)) brightness(1);
|
||||
transform: translate3d(0, 0.04rem, 0) scale(.996);
|
||||
filter: drop-shadow(0 0.16rem 0.28rem rgba(56, 255, 202, .10)) brightness(1);
|
||||
}
|
||||
|
||||
.node.is-running {
|
||||
@@ -144,18 +145,18 @@
|
||||
@keyframes node-life {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 4px, 0) scale(.996);
|
||||
filter: drop-shadow(0 16px 28px rgba(56, 255, 202, .10)) brightness(1);
|
||||
transform: translate3d(0, 0.04rem, 0) scale(.996);
|
||||
filter: drop-shadow(0 0.16rem 0.28rem rgba(56, 255, 202, .10)) brightness(1);
|
||||
}
|
||||
20.896%, 79.104% {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
filter: drop-shadow(0 16px 28px rgba(56, 255, 202, .20)) brightness(1.045);
|
||||
filter: drop-shadow(0 0.16rem 0.28rem rgba(56, 255, 202, .20)) brightness(1.045);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -4px, 0) scale(.996);
|
||||
filter: drop-shadow(0 16px 28px rgba(56, 255, 202, .10)) brightness(1);
|
||||
transform: translate3d(0, -0.04rem, 0) scale(.996);
|
||||
filter: drop-shadow(0 0.16rem 0.28rem rgba(56, 255, 202, .10)) brightness(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -262,6 +262,13 @@
|
||||
border-radius: 0.1rem;
|
||||
margin: 0.4rem 0.18rem;
|
||||
|
||||
&:hover .resume-detail__section-header {
|
||||
background: #F7F7F7;
|
||||
.resume-detail__edit-btn-inline {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -346,7 +353,7 @@
|
||||
align-items: center;
|
||||
gap: 0.06rem;
|
||||
font-size: 0.12rem;
|
||||
color: $text-dark;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&__contact-icon {
|
||||
@@ -363,6 +370,7 @@
|
||||
gap: 0.12rem;
|
||||
margin-bottom: 0.16rem;
|
||||
padding: 0.1rem 0 0.1rem 0.14rem;
|
||||
border-radius: 0.06rem;
|
||||
}
|
||||
|
||||
&__section-title {
|
||||
@@ -432,19 +440,18 @@
|
||||
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;
|
||||
margin-left: auto;
|
||||
|
||||
.el-icon {
|
||||
font-size: 0.15rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $theme-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,19 +463,18 @@
|
||||
|
||||
// 经历模块标题栏hover时显示添加按钮
|
||||
&__section-header:hover &__edit-btn-inline {
|
||||
opacity: 1;
|
||||
//opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
// 经历模块标题栏hover时背景色
|
||||
&__section-header:hover {
|
||||
background: #F7F7F7;
|
||||
//background: #F7F7F7;
|
||||
border-radius: 0.06rem;
|
||||
}
|
||||
|
||||
// 经历条目内的编辑按钮(hover exp-item 时显示)
|
||||
&__exp-edit-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&__exp-item:hover &__exp-edit-btn {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<template v-if="currentStep === 1">
|
||||
<div class="agent-page__right">
|
||||
<div class="agent-page__profile-wrapper">
|
||||
<ProfilePageContent :profile="profile" @edit="handleEdit" />
|
||||
<ProfilePageContent :profile="profile" @edit="handleEdit" @save="handleInlineSave" @exp-update="handleExpUpdate" @exp-add="handleExpAdd" @exp-delete="handleExpDelete" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent-page__left">
|
||||
@@ -433,6 +433,111 @@ async function handleSaveEdit(data: Record<string, any>) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 内联保存处理(个人信息/技能/证书模块从组件内直接保存) */
|
||||
async function handleInlineSave(section: string, data: Record<string, any>) {
|
||||
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)
|
||||
profile.value.education = list
|
||||
} else if (moduleType === 'work') {
|
||||
const list = profile.value.works.map((item, i) => i === index ? data : item) as any[]
|
||||
await saveWork(list)
|
||||
profile.value.works = list
|
||||
} else if (moduleType === 'internship') {
|
||||
const list = profile.value.internships.map((item, i) => i === index ? data : item) as any[]
|
||||
await saveInternship(list)
|
||||
profile.value.internships = list
|
||||
} else if (moduleType === 'project') {
|
||||
const list = profile.value.projects.map((item, i) => i === index ? data : item) as any[]
|
||||
await saveProject(list)
|
||||
profile.value.projects = list
|
||||
} else if (moduleType === 'competition') {
|
||||
const list = profile.value.competitions.map((item, i) => i === index ? data : item) as any[]
|
||||
await saveCompetition(list)
|
||||
profile.value.competitions = list
|
||||
}
|
||||
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)
|
||||
profile.value.education = list
|
||||
} else if (moduleType === 'work') {
|
||||
const list = [...profile.value.works, data] as any[]
|
||||
await saveWork(list)
|
||||
profile.value.works = list
|
||||
} else if (moduleType === 'internship') {
|
||||
const list = [...profile.value.internships, data] as any[]
|
||||
await saveInternship(list)
|
||||
profile.value.internships = list
|
||||
} else if (moduleType === 'project') {
|
||||
const list = [...profile.value.projects, data] as any[]
|
||||
await saveProject(list)
|
||||
profile.value.projects = list
|
||||
} else if (moduleType === 'competition') {
|
||||
const list = [...profile.value.competitions, data] as any[]
|
||||
await saveCompetition(list)
|
||||
profile.value.competitions = list
|
||||
}
|
||||
ElMessage.success('添加成功')
|
||||
} catch {
|
||||
ElMessage.error('添加失败')
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除单条经历 — 确认后从列表移除并全量保存 */
|
||||
async function handleExpDelete(moduleType: string, index: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这条经历吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (moduleType === 'education') {
|
||||
const list = profile.value.education.filter((_, i) => i !== index) as any[]
|
||||
await saveEducation(list)
|
||||
profile.value.education = list
|
||||
} else if (moduleType === 'work') {
|
||||
const list = profile.value.works.filter((_, i) => i !== index) as any[]
|
||||
await saveWork(list)
|
||||
profile.value.works = list
|
||||
} else if (moduleType === 'internship') {
|
||||
const list = profile.value.internships.filter((_, i) => i !== index) as any[]
|
||||
await saveInternship(list)
|
||||
profile.value.internships = list
|
||||
} else if (moduleType === 'project') {
|
||||
const list = profile.value.projects.filter((_, i) => i !== index) as any[]
|
||||
await saveProject(list)
|
||||
profile.value.projects = list
|
||||
} else if (moduleType === 'competition') {
|
||||
const list = profile.value.competitions.filter((_, i) => i !== index) as any[]
|
||||
await saveCompetition(list)
|
||||
profile.value.competitions = list
|
||||
}
|
||||
ElMessage.success('删除成功')
|
||||
} catch {
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 第2步:确认目标 ====================
|
||||
const showJobGoalDialog = ref(false)
|
||||
const intentionEmploymentLabel = computed(() => formatEmploymentType(store.state.jobIntention.recruitCategory))
|
||||
|
||||
@@ -492,7 +492,7 @@ async function handleAiPolish() {
|
||||
aiPolishing.value = true
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: 'AI润色中…',
|
||||
text: 'AI正在润色中……',
|
||||
background: 'rgba(0, 0, 0, 0.5)',
|
||||
customClass: 'resume-upload-loading',
|
||||
})
|
||||
|
||||
@@ -22,18 +22,27 @@
|
||||
<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-name dflex-start aliite-c"><span>{{ profile.name }}</span><span class="fs13 color-6 ml10 dflex-start aliite-c"><svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon">
|
||||
<circle cx="8" cy="6.5" r="2.5" stroke="currentColor" stroke-width="1.2"/>
|
||||
<path d="M8 14s-5-4-5-7.5a5 5 0 0110 0C13 10 8 14 8 14z" stroke="currentColor" stroke-width="1.2"/>
|
||||
</svg> {{ resolveRegionName(profile.regionCode) }}</span></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>
|
||||
<span class="profile-page-content__contact-item"><svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon">
|
||||
<path d="M4 2h2.5l1.5 3-1.5 1.5a8 8 0 003 3L11 8l3 1.5V12a2 2 0 01-2 2C6.5 14 2 9.5 2 4a2 2 0 012-2z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
</svg> {{ profile.phone }}</span>
|
||||
<span class="profile-page-content__contact-item"><svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon">
|
||||
<rect x="2" y="3" width="12" height="10" rx="1.5" stroke="currentColor" stroke-width="1.2"/>
|
||||
<path d="M2 5.5l6 4 6-4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
|
||||
</svg>{{ profile.email }}</span>
|
||||
|
||||
|
||||
<span class="profile-page-content__info-contacts" v-if="profile.wechat">
|
||||
<span class="profile-page-content__contact-item"><el-icon class="fs14 color-9"><ChatLineRound /></el-icon> {{ profile.wechat }}</span>
|
||||
</span>
|
||||
<!-- 作品集链接显示在个人信息卡片内 -->
|
||||
<div v-if="profile.portfolioUrl" class="profile-page-content__info-contacts">
|
||||
<span 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>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 编辑模式 -->
|
||||
@@ -57,7 +66,7 @@
|
||||
</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">
|
||||
<div v-for="(edu, i) in profile.education" :key="i" class="profile-page-content__section-item" @click.stop="startEditExp('education', i, edu)">
|
||||
<template v-if="editingExpIndex !== `education_${i}`">
|
||||
<div class="profile-page-content__item-row">
|
||||
<div class="profile-page-content__item-left">
|
||||
@@ -65,9 +74,13 @@
|
||||
<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>
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn" @click="startEditExp('education', i, edu)">
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
<!-- 删除按钮 -->
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn" @click.stop="handleExpDelete('education', i)" aria-label="删除">
|
||||
<el-icon><DeleteFilled /></el-icon><span>删除</span>
|
||||
</button>
|
||||
</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>
|
||||
@@ -86,17 +99,22 @@
|
||||
</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">
|
||||
<div v-for="(exp, i) in profile.works" :key="i" class="profile-page-content__section-item" @click.stop="startEditExp('work', i, exp)">
|
||||
<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>
|
||||
<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)">
|
||||
</div>
|
||||
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
<!-- 删除按钮 -->
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn" @click.stop="handleExpDelete('work', i)" aria-label="删除">
|
||||
<el-icon><DeleteFilled /></el-icon><span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="profile-page-content__item-list">
|
||||
<li v-for="desc in exp.description" :key="desc.id">{{ desc.text }}</li>
|
||||
@@ -115,17 +133,22 @@
|
||||
</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">
|
||||
<div v-for="(exp, i) in profile.internships" :key="i" class="profile-page-content__section-item" @click.stop="startEditExp('internship', i, exp)">
|
||||
<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>
|
||||
<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)">
|
||||
</div>
|
||||
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
<!-- 删除按钮 -->
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn" @click.stop="handleExpDelete('internship', i)" aria-label="删除">
|
||||
<el-icon><DeleteFilled /></el-icon><span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="profile-page-content__item-list">
|
||||
<li v-for="desc in exp.description" :key="desc.id">{{ desc.text }}</li>
|
||||
@@ -144,17 +167,22 @@
|
||||
</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">
|
||||
<div v-for="(proj, i) in profile.projects" :key="i" class="profile-page-content__section-item" @click.stop="startEditExp('project', i, proj)">
|
||||
<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>
|
||||
<div class="profile-page-content__item-sub" v-if="proj.role">{{ proj.role }}</div>
|
||||
<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)">
|
||||
</div>
|
||||
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
<!-- 删除按钮 -->
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn" @click.stop="handleExpDelete('project', i)" aria-label="删除">
|
||||
<el-icon><DeleteFilled /></el-icon><span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="profile-page-content__item-list">
|
||||
<li v-for="desc in proj.description" :key="desc.id">{{ desc.text }}</li>
|
||||
@@ -173,16 +201,21 @@
|
||||
</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">
|
||||
<div v-for="(comp, i) in profile.competitions" :key="i" class="profile-page-content__section-item" @click.stop="startEditExp('competition', i, comp)">
|
||||
<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>
|
||||
<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)">
|
||||
</div>
|
||||
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
<!-- 删除按钮 -->
|
||||
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn" @click.stop="handleExpDelete('competition', i)" aria-label="删除">
|
||||
<el-icon><DeleteFilled /></el-icon><span>删除</span>
|
||||
</button>
|
||||
</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>
|
||||
@@ -243,7 +276,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { EditPen, Plus } from '@element-plus/icons-vue'
|
||||
import {EditPen, Plus, DeleteFilled, ChatLineRound} from '@element-plus/icons-vue'
|
||||
import { resolveRegionName } from '@/utils/region'
|
||||
import ProfileEditDrawer from '@/components/ProfileEditDrawer.vue'
|
||||
import ResumeExpInlineEdit from '@/components/ResumeExpInlineEdit.vue'
|
||||
@@ -328,6 +361,8 @@ const emit = defineEmits<{
|
||||
(e: 'expUpdate', moduleType: string, index: number, data: Record<string, any>): void
|
||||
/** 新增经历事件 */
|
||||
(e: 'expAdd', moduleType: string, data: Record<string, any>): void
|
||||
/** 删除经历事件 */
|
||||
(e: 'expDelete', moduleType: string, index: number): void
|
||||
}>()
|
||||
|
||||
/** 当前编辑的模块(info/skills/certificate) */
|
||||
@@ -412,6 +447,11 @@ function handleExpAdd(moduleType: string, data: Record<string, any>) {
|
||||
emit('expAdd', moduleType, data)
|
||||
addingModule.value = ''
|
||||
}
|
||||
|
||||
/** 删除单条经历(直接触发,不弹确认框,由父组件处理确认) */
|
||||
function handleExpDelete(moduleType: string, index: number) {
|
||||
emit('expDelete', moduleType, index)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -87,8 +87,8 @@
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<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>
|
||||
<button class="profile-edit-inline__save-btn" @click.stop="handleSave">保存</button>
|
||||
<button class="profile-edit-inline__cancel-btn" @click.stop="handleCancel">取消</button>
|
||||
</div>
|
||||
|
||||
<!-- AI润色结果(保存取消下方) -->
|
||||
@@ -177,7 +177,7 @@ async function handleAiPolish() {
|
||||
return
|
||||
}
|
||||
aiPolishing.value = true
|
||||
const loading = ElLoading.service({ lock: true, text: 'AI润色中…', background: 'rgba(0,0,0,0.5)' })
|
||||
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)
|
||||
|
||||
@@ -332,6 +332,10 @@ function toggleDropdown() {
|
||||
visible.value = !visible.value
|
||||
if (visible.value) {
|
||||
searchText.value = ''
|
||||
// 如果 store 中地区数据为空(页面刷新等场景),主动请求加载
|
||||
if (!regions.value.length) {
|
||||
store.dispatch('loadCommonData')
|
||||
}
|
||||
if (regions.value.length && !activeProvinceCode.value) {
|
||||
activeProvinceCode.value = regions.value[0].code
|
||||
}
|
||||
@@ -390,5 +394,11 @@ watch(
|
||||
)
|
||||
|
||||
/** 树数据加载完成后重新同步选中项(解决并行加载时树数据晚于 prop 到达的问题) */
|
||||
watch(regions, () => syncFromProps())
|
||||
watch(regions, () => {
|
||||
syncFromProps()
|
||||
// 地区数据加载完成后,如果面板已打开且没有激活省份,默认选中第一个
|
||||
if (visible.value && regions.value.length && !activeProvinceCode.value) {
|
||||
activeProvinceCode.value = regions.value[0].code
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { UserInfo } from '@/api/auth'
|
||||
|
||||
/** 招聘分类选项:label → 接口参数 recruitCategory */
|
||||
export const JOB_TYPE_OPTIONS: { label: string; value: number }[] = [
|
||||
{ label: '全部', value: null },
|
||||
{ label: '社招', value: 0 },
|
||||
{ label: '校招', value: 1 },
|
||||
{ label: '实习', value: 2 },
|
||||
|
||||
+8
-6
@@ -562,8 +562,9 @@ function fitVisualStage() {
|
||||
const offsetX = (rect.width - baseWidth * scale) / 2
|
||||
const offsetY = (rect.height - baseHeight * scale) / 2
|
||||
stage.style.setProperty('--stage-scale', scale.toFixed(5))
|
||||
stage.style.setProperty('--stage-x', `${offsetX.toFixed(2)}px`)
|
||||
stage.style.setProperty('--stage-y', `${offsetY.toFixed(2)}px`)
|
||||
// translate 使用 rem(÷100)
|
||||
stage.style.setProperty('--stage-x', `${(offsetX / 100).toFixed(4)}rem`)
|
||||
stage.style.setProperty('--stage-y', `${(offsetY / 100).toFixed(4)}rem`)
|
||||
}
|
||||
|
||||
/** 创建一个浮动节点面板并播放动画 */
|
||||
@@ -575,10 +576,11 @@ function createPanel(spec: { src: string; x: number; y: number; w: number; h: nu
|
||||
img.src = spec.src
|
||||
img.alt = ''
|
||||
img.draggable = false
|
||||
img.style.setProperty('--x', `${spec.x}px`)
|
||||
img.style.setProperty('--y', `${spec.y}px`)
|
||||
img.style.setProperty('--w', `${spec.w}px`)
|
||||
img.style.setProperty('--h', `${spec.h}px`)
|
||||
// 使用 rem 单位(1rem = 100px)
|
||||
img.style.setProperty('--x', `${(spec.x / 100).toFixed(4)}rem`)
|
||||
img.style.setProperty('--y', `${(spec.y / 100).toFixed(4)}rem`)
|
||||
img.style.setProperty('--w', `${(spec.w / 100).toFixed(4)}rem`)
|
||||
img.style.setProperty('--h', `${(spec.h / 100).toFixed(4)}rem`)
|
||||
img.style.setProperty('--life-ms', `${animTiming.lifeMs}ms`)
|
||||
|
||||
let fallbackTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
+41
-1
@@ -30,6 +30,7 @@
|
||||
@save="handleInlineSave"
|
||||
@exp-update="handleExpUpdate"
|
||||
@exp-add="handleExpAdd"
|
||||
@exp-delete="handleExpDelete"
|
||||
/>
|
||||
|
||||
<!-- 右侧入口 -->
|
||||
@@ -48,7 +49,7 @@ import ProfilePageContent from '@/components/ProfilePageContent.vue'
|
||||
import ResumeUploadDialog from '@/components/ResumeUploadDialog.vue'
|
||||
import { saveProfile, fetchProfile, fetchEducation, saveEducation, fetchWork, saveWork, fetchInternship, saveInternship, fetchProject, saveProject, fetchCompetition, saveCompetition, syncProfileFromResume } from '@/api/profile'
|
||||
import { uploadResume } from '@/utils/aiRequest'
|
||||
import { ElMessage, ElLoading } from 'element-plus'
|
||||
import { ElMessage, ElLoading, ElMessageBox } from 'element-plus'
|
||||
import 'element-plus/es/components/loading/style/css'
|
||||
import type { SaveEducationItem, SaveWorkItem, SaveProjectItem, SaveCompetitionItem } from '@/api/profile'
|
||||
|
||||
@@ -710,4 +711,43 @@ async function handleExpAdd(moduleType: string, data: Record<string, any>) {
|
||||
ElMessage.error('添加失败')
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除单条经历 — 从列表中移除对应索引后全量保存 */
|
||||
async function handleExpDelete(moduleType: string, index: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这条经历吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
} catch {
|
||||
return // 用户取消
|
||||
}
|
||||
try {
|
||||
if (moduleType === 'education') {
|
||||
const list = profile.value.education.filter((_, i) => i !== index) as any[]
|
||||
await saveEducation(list)
|
||||
await loadEducation()
|
||||
} else if (moduleType === 'work') {
|
||||
const list = profile.value.works.filter((_, i) => i !== index) as any[]
|
||||
await saveWork(list)
|
||||
await loadWork()
|
||||
} else if (moduleType === 'internship') {
|
||||
const list = profile.value.internships.filter((_, i) => i !== index) as any[]
|
||||
await saveInternship(list)
|
||||
await loadInternship()
|
||||
} else if (moduleType === 'project') {
|
||||
const list = profile.value.projects.filter((_, i) => i !== index) as any[]
|
||||
await saveProject(list)
|
||||
await loadProject()
|
||||
} else if (moduleType === 'competition') {
|
||||
const list = profile.value.competitions.filter((_, i) => i !== index) as any[]
|
||||
await saveCompetition(list)
|
||||
await loadCompetition()
|
||||
}
|
||||
ElMessage.success('删除成功')
|
||||
} catch {
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
>导出</button>
|
||||
<button
|
||||
class="resume-page__action-btn resume-page__action-btn--delete"
|
||||
@click.stop="handleDelete(item.id)"
|
||||
@click.stop="handleDelete(item.id, item.isDefault)"
|
||||
>删除</button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -262,13 +262,19 @@ function handleExport(item: ResumeItem) {
|
||||
}
|
||||
|
||||
/** 删除简历 */
|
||||
async function handleDelete(id: string) {
|
||||
async function handleDelete(id: string, isDefault: boolean) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这份简历吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
|
||||
if(resumeList.value.length<=1||isDefault){
|
||||
ElMessage.warning('默认简历不可删除')
|
||||
return
|
||||
}
|
||||
|
||||
const res = await deleteResume(id)
|
||||
if (res.code === '0') {
|
||||
ElMessage.success('删除成功')
|
||||
|
||||
+114
-22
@@ -135,10 +135,17 @@
|
||||
<div class="resume-detail__card" :class="{ 'resume-detail__card-hover': editModule !== 'info' || !showEditDrawer }">
|
||||
<!-- 展示模式 -->
|
||||
<template v-if="editModule !== 'info' || !showEditDrawer">
|
||||
<div class="resume-detail__card-header">
|
||||
<div>
|
||||
<div class="resume-detail__card-header color-6">
|
||||
<div class="dflex-start aliite-c">
|
||||
<h3 class="resume-detail__user-name">{{ resumeMain.name || '未填写姓名' }}</h3>
|
||||
<p class="resume-detail__user-title">{{ resumeMain.targetPosition || '' }}</p>
|
||||
<span v-if="resumeMain.city" class="ml10 dflex-start aliite-c fs13">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon">
|
||||
<circle cx="8" cy="6.5" r="2.5" stroke="currentColor" stroke-width="1.2"/>
|
||||
<path d="M8 14s-5-4-5-7.5a5 5 0 0110 0C13 10 8 14 8 14z" stroke="currentColor" stroke-width="1.2"/>
|
||||
</svg>
|
||||
{{ resumeMain.city }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- 编辑按钮 — hover时才显示,品牌色图标+文字 -->
|
||||
<button class="resume-detail__edit-btn-inline" @click="openEditDrawer('info')" aria-label="编辑个人信息">
|
||||
@@ -162,20 +169,10 @@
|
||||
</svg>
|
||||
{{ resumeMain.mobileNumber }}
|
||||
</span>
|
||||
<!-- 所在城市 -->
|
||||
<span v-if="resumeMain.city" class="resume-detail__contact-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon">
|
||||
<circle cx="8" cy="6.5" r="2.5" stroke="currentColor" stroke-width="1.2"/>
|
||||
<path d="M8 14s-5-4-5-7.5a5 5 0 0110 0C13 10 8 14 8 14z" stroke="currentColor" stroke-width="1.2"/>
|
||||
</svg>
|
||||
{{ resumeMain.city }}
|
||||
</span>
|
||||
|
||||
<!-- 微信号 -->
|
||||
<span v-if="resumeMain.wechatNumber" class="resume-detail__contact-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon">
|
||||
<path d="M2 8a6 6 0 1112 0 6 6 0 01-12 0z" stroke="currentColor" stroke-width="1.2"/>
|
||||
<path d="M5.5 7.5c.5-1 1.5-1.5 2.5-1.5s2 .5 2.5 1.5M6 10s.8 1 2 1 2-1 2-1" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<el-icon class="fs14 color-9"><ChatLineRound /></el-icon>
|
||||
{{ resumeMain.wechatNumber }}
|
||||
</span>
|
||||
<!-- 作品集链接 -->
|
||||
@@ -250,7 +247,7 @@
|
||||
@save="handleExpAdd('education', $event)"
|
||||
@cancel="expAddingModule = ''"
|
||||
/>
|
||||
<div v-for="edu in educationList" :key="edu.id" class="resume-detail__exp-item">
|
||||
<div v-for="edu in educationList" :key="edu.id" class="resume-detail__exp-item" @click.stop="startEditExp('education', edu)">
|
||||
<!-- 展示模式 -->
|
||||
<template v-if="expEditingId !== edu.id">
|
||||
<div class="resume-detail__exp-row">
|
||||
@@ -262,11 +259,18 @@
|
||||
建议使用AI润色修复 >>
|
||||
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('education', edu.id!)!.finding }}</span>
|
||||
</span>
|
||||
<div class="mlauto dflex-end">
|
||||
<!-- 编辑按钮 — hover时显示 -->
|
||||
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('education', edu)" aria-label="编辑">
|
||||
<el-icon><EditPen /></el-icon>
|
||||
<span>编辑</span>
|
||||
</button>
|
||||
<!-- 删除按钮 -->
|
||||
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click.stop="handleExpDelete('education', edu.id!)" aria-label="删除">
|
||||
<el-icon><DeleteFilled /></el-icon>
|
||||
<span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="edu.description?.length" class="resume-detail__desc-list">
|
||||
<p v-for="p in edu.description" :key="p.id" class="resume-detail__desc-item">{{ p.text }}</p>
|
||||
@@ -293,7 +297,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<ResumeExpInlineEdit v-if="expAddingModule === 'work'" :module-type="'work'" @save="handleExpAdd('work', $event)" @cancel="expAddingModule = ''" />
|
||||
<div v-for="exp in workList" :key="exp.id" class="resume-detail__exp-item">
|
||||
<div v-for="exp in workList" :key="exp.id" class="resume-detail__exp-item" @click.stop="startEditExp('work', exp)">
|
||||
<template v-if="expEditingId !== exp.id">
|
||||
<div class="resume-detail__exp-row">
|
||||
<span class="resume-detail__exp-name">{{ exp.companyName }}</span>
|
||||
@@ -304,9 +308,14 @@
|
||||
建议使用AI润色修复 >>
|
||||
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('work', exp.id!)!.finding }}</span>
|
||||
</span>
|
||||
<div class="mlauto dflex-end">
|
||||
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('work', exp)" aria-label="编辑">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click.stop="handleExpDelete('work', exp.id!)" aria-label="删除">
|
||||
<el-icon><DeleteFilled /></el-icon><span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul v-if="exp.description?.length" class="resume-detail__desc-list pl16">
|
||||
<li v-for="p in exp.description" :key="p.id" class="resume-detail__desc-item">{{ p.text }}</li>
|
||||
@@ -325,7 +334,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<ResumeExpInlineEdit v-if="expAddingModule === 'internship'" :module-type="'internship'" @save="handleExpAdd('internship', $event)" @cancel="expAddingModule = ''" />
|
||||
<div v-for="intern in internshipList" :key="intern.id" class="resume-detail__exp-item">
|
||||
<div v-for="intern in internshipList" :key="intern.id" class="resume-detail__exp-item" @click.stop="startEditExp('internship', intern)">
|
||||
<template v-if="expEditingId !== intern.id">
|
||||
<div class="resume-detail__exp-row">
|
||||
<span class="resume-detail__exp-name">{{ intern.companyName }}</span>
|
||||
@@ -336,9 +345,14 @@
|
||||
建议使用AI润色修复 >>
|
||||
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('internship', intern.id!)!.finding }}</span>
|
||||
</span>
|
||||
<div class="mlauto dflex-end">
|
||||
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('internship', intern)" aria-label="编辑">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click.stop="handleExpDelete('internship', intern.id!)" aria-label="删除">
|
||||
<el-icon><DeleteFilled /></el-icon><span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="intern.description?.length" class="resume-detail__desc-list">
|
||||
<p v-for="p in intern.description" :key="p.id" class="resume-detail__desc-item">{{ p.text }}</p>
|
||||
@@ -357,7 +371,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<ResumeExpInlineEdit v-if="expAddingModule === 'project'" :module-type="'project'" @save="handleExpAdd('project', $event)" @cancel="expAddingModule = ''" />
|
||||
<div v-for="proj in projectList" :key="proj.id" class="resume-detail__exp-item">
|
||||
<div v-for="proj in projectList" :key="proj.id" class="resume-detail__exp-item" @click.stop="startEditExp('project', proj)">
|
||||
<template v-if="expEditingId !== proj.id">
|
||||
<div class="resume-detail__exp-row">
|
||||
<span class="resume-detail__exp-name">{{ proj.projectName }}</span>
|
||||
@@ -368,9 +382,14 @@
|
||||
建议使用AI润色修复 >>
|
||||
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('project', proj.id!)!.finding }}</span>
|
||||
</span>
|
||||
<div class="mlauto dflex-end">
|
||||
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('project', proj)" aria-label="编辑">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click.stop="handleExpDelete('project', proj.id!)" aria-label="删除">
|
||||
<el-icon><DeleteFilled /></el-icon><span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="proj.description?.length" class="resume-detail__desc-list">
|
||||
<p v-for="p in proj.description" :key="p.id" class="resume-detail__desc-item">{{ p.text }}</p>
|
||||
@@ -389,7 +408,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<ResumeExpInlineEdit v-if="expAddingModule === 'competition'" :module-type="'competition'" @save="handleExpAdd('competition', $event)" @cancel="expAddingModule = ''" />
|
||||
<div v-for="comp in competitionList" :key="comp.id" class="resume-detail__exp-item">
|
||||
<div v-for="comp in competitionList" :key="comp.id" class="resume-detail__exp-item" @click.stop="startEditExp('competition', comp)">
|
||||
<template v-if="expEditingId !== comp.id">
|
||||
<div class="resume-detail__exp-row">
|
||||
<span class="resume-detail__exp-name">{{ comp.competitionName }}</span>
|
||||
@@ -400,9 +419,14 @@
|
||||
建议使用AI润色修复 >>
|
||||
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('competition', comp.id!)!.finding }}</span>
|
||||
</span>
|
||||
<div class="mlauto dflex-end">
|
||||
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('competition', comp)" aria-label="编辑">
|
||||
<el-icon><EditPen /></el-icon><span>编辑</span>
|
||||
</button>
|
||||
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click.stop="handleExpDelete('competition', comp.id!)" aria-label="删除">
|
||||
<el-icon><DeleteFilled /></el-icon><span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="comp.description?.length" class="resume-detail__desc-list">
|
||||
<p v-for="p in comp.description" :key="p.id" class="resume-detail__desc-item">{{ p.text }}</p>
|
||||
@@ -480,8 +504,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useStore } from 'vuex'
|
||||
import SideNav from '@/components/SideNav.vue'
|
||||
import ProfileEditDrawer from '@/components/ProfileEditDrawer.vue'
|
||||
import ResumeExpInlineEdit from '@/components/ResumeExpInlineEdit.vue'
|
||||
@@ -531,7 +556,11 @@ import {
|
||||
type DiagnosisIssue,
|
||||
type DescriptionParagraph,
|
||||
} from '@/api/resume'
|
||||
import {ArrowLeft, EditPen, Plus} from "@element-plus/icons-vue";
|
||||
import {ArrowLeft, EditPen, Plus, DeleteFilled, ChatLineRound} from "@element-plus/icons-vue";
|
||||
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
|
||||
|
||||
// ==================== Store 实例 ====================
|
||||
const store = useStore()
|
||||
|
||||
// ==================== 诊断数据相关 ====================
|
||||
|
||||
@@ -748,6 +777,23 @@ const competitionList = ref<ResumeCompetition[]>([])
|
||||
|
||||
// ==================== 数据加载 ====================
|
||||
|
||||
/** 根据城市名称从地区树中反查城市编码(用于后端未返回 regionCode 时的 fallback) */
|
||||
function resolveRegionCodeByName(cityName: string): string {
|
||||
if (!cityName) return ''
|
||||
const regions = store.state.regions || []
|
||||
for (const province of regions) {
|
||||
for (const city of province.children) {
|
||||
if (city.name === cityName) return city.code
|
||||
if (city.children) {
|
||||
for (const district of city.children) {
|
||||
if (district.name === cityName) return district.code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 加载所有简历详情数据 */
|
||||
async function loadResumeDetail() {
|
||||
const [mainRes, eduRes, workRes, internRes, projRes, compRes] = await Promise.all([
|
||||
@@ -761,6 +807,10 @@ async function loadResumeDetail() {
|
||||
|
||||
if (mainRes.code === '0' && mainRes.data) {
|
||||
resumeMain.value = mainRes.data
|
||||
// 如果后端未返回 regionCode 但有 city 名称,尝试从地区树反查编码
|
||||
if (!resumeMain.value.regionCode && resumeMain.value.city) {
|
||||
resumeMain.value.regionCode = resolveRegionCodeByName(resumeMain.value.city)
|
||||
}
|
||||
}
|
||||
if (eduRes.code === '0' && eduRes.data) {
|
||||
educationList.value = eduRes.data
|
||||
@@ -779,12 +829,26 @@ async function loadResumeDetail() {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 地区数据反查 ====================
|
||||
|
||||
/** 当 store 中地区树数据加载完毕后,如果 resumeMain 缺少 regionCode 则尝试反查 */
|
||||
watch(
|
||||
() => store.state.regions,
|
||||
(regions) => {
|
||||
if (regions?.length && resumeMain.value.city && !resumeMain.value.regionCode) {
|
||||
resumeMain.value.regionCode = resolveRegionCodeByName(resumeMain.value.city)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// ==================== 生命周期 ====================
|
||||
|
||||
onMounted(() => {
|
||||
loadResumeDetail()
|
||||
// 加载诊断报告
|
||||
loadDiagnosis()
|
||||
// 确保地区分类数据可用(页面刷新后 store 可能为空)
|
||||
store.dispatch('loadCommonData')
|
||||
})
|
||||
|
||||
// ==================== 计算属性 ====================
|
||||
@@ -1286,6 +1350,32 @@ async function handleExpUpdate(moduleType: string, recordId: string, data: Recor
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除单条经历 */
|
||||
async function handleExpDelete(moduleType: string, recordId: string) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这条经历吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
const deleteMap: Record<string, (id: string) => Promise<any>> = {
|
||||
education: deleteResumeEducation,
|
||||
work: deleteResumeWork,
|
||||
internship: deleteResumeInternship,
|
||||
project: deleteResumeProject,
|
||||
competition: deleteResumeCompetition,
|
||||
}
|
||||
const deleteFn = deleteMap[moduleType]
|
||||
if (deleteFn) {
|
||||
await deleteFn(recordId)
|
||||
await loadResumeDetail()
|
||||
ElMessage.success('删除成功')
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存新增的经历 */
|
||||
async function handleExpAdd(moduleType: string, data: Record<string, any>) {
|
||||
try {
|
||||
@@ -1343,7 +1433,7 @@ function openEditDrawer(section: string) {
|
||||
name: resumeMain.value.name || '',
|
||||
email: resumeMain.value.email || '',
|
||||
phone: resumeMain.value.mobileNumber || '',
|
||||
location: '',
|
||||
location: resumeMain.value.regionCode || '',
|
||||
wechat: resumeMain.value.wechatNumber || '',
|
||||
portfolioUrl: resumeMain.value.portfolioUrl || '',
|
||||
}
|
||||
@@ -1446,6 +1536,7 @@ async function handleSaveEdit(data: Record<string, any>) {
|
||||
email: data.email,
|
||||
mobileNumber: data.phone,
|
||||
city: data.locationName || data.location || '',
|
||||
regionCode: data.location || '',
|
||||
wechatNumber: data.wechat,
|
||||
portfolioUrl: data.portfolioUrl,
|
||||
})
|
||||
@@ -1453,6 +1544,7 @@ async function handleSaveEdit(data: Record<string, any>) {
|
||||
resumeMain.value.email = data.email
|
||||
resumeMain.value.mobileNumber = data.phone
|
||||
resumeMain.value.city = data.locationName || data.location || ''
|
||||
resumeMain.value.regionCode = data.location || ''
|
||||
resumeMain.value.wechatNumber = data.wechat
|
||||
resumeMain.value.portfolioUrl = data.portfolioUrl
|
||||
} else if (editModule.value === 'summary') {
|
||||
|
||||
Reference in New Issue
Block a user