561 lines
18 KiB
Vue
561 lines
18 KiB
Vue
<template>
|
||
<view class="career-planning-page">
|
||
<!-- 提醒弹窗 -->
|
||
<RemindPopup
|
||
ref="remindPopup"
|
||
:remind-list="remindList"
|
||
@cancel="handleCancel"
|
||
@confirm="handleConfirm"
|
||
/>
|
||
|
||
<!-- 技能详情弹出层 -->
|
||
<SkillDetailPopup
|
||
ref="skillDetailPopup"
|
||
:job-title="selectedJobTitle"
|
||
:possessed-skills="selectedJobPossessedSkills"
|
||
:improvement-skills="selectedJobImprovementSkills"
|
||
@close="handleSkillPopupClose"
|
||
/>
|
||
|
||
<!-- 页面内容 -->
|
||
<view class="page-content" v-if="showContent">
|
||
<!-- #ifdef MP-WEIXIN -->
|
||
<!-- 小程序背景图片 -->
|
||
<image class="mp-background" src="/static/icon/background2.png" mode="aspectFill"></image>
|
||
<!-- #endif -->
|
||
|
||
<!-- 头部区域 -->
|
||
<PageHeader
|
||
:active-tab="activeTab"
|
||
@tab-change="switchTab"
|
||
@search-click="handleSearchClick"
|
||
@menu-click="handleMenuClick"
|
||
@more-click="handleMoreClick"
|
||
/>
|
||
|
||
<!-- 内容区域 -->
|
||
<scroll-view scroll-y class="content-scroll">
|
||
<CareerRecommend
|
||
v-if="activeTab === 0"
|
||
:current-job-id="currentJobId"
|
||
:current-job-name="currentJobName"
|
||
@job-card-click="handleJobCardClick"
|
||
@skills-updated="handleRecommendSkillsUpdated"
|
||
/>
|
||
<CareerPath
|
||
v-else-if="activeTab === 1"
|
||
:current-job-name="currentJobName"
|
||
@path-data-updated="handlePathDataUpdated"
|
||
/>
|
||
<SkillDevelopment
|
||
v-else
|
||
:current-job-name="currentJobName"
|
||
@path-data-updated="handlePathDataUpdated"
|
||
/>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 底部导航栏 -->
|
||
<view class="tabbar-wrapper" v-if="showContent">
|
||
<CustomTabBar :currentPage="0" />
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, inject, nextTick, onMounted } from 'vue';
|
||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||
import { appUserInfo } from '@/apiRc/user/user.js';
|
||
import RemindPopup from './components/RemindPopup.vue';
|
||
import PageHeader from './components/PageHeader.vue';
|
||
import SkillDetailPopup from './components/SkillDetailPopup.vue';
|
||
import CareerRecommend from './components/CareerRecommend.vue';
|
||
import CareerPath from './components/CareerPath.vue';
|
||
import SkillDevelopment from './components/SkillDevelopment.vue';
|
||
import CustomTabBar from '@/components/CustomTabBar/CustomTabBar.vue';
|
||
|
||
const { navBack, navTo } = inject('globalFunction');
|
||
|
||
// 弹窗引用
|
||
const remindPopup = ref(null);
|
||
const skillDetailPopup = ref(null);
|
||
// 提醒列表(由接口返回)
|
||
const remindList = ref([]);
|
||
// 是否显示页面内容
|
||
const showContent = ref(false);
|
||
// 当前激活的tab
|
||
const activeTab = ref(0);
|
||
// 选中的职位信息
|
||
const selectedJobTitle = ref('');
|
||
const selectedJobPossessedSkills = ref([]);
|
||
const selectedJobImprovementSkills = ref([]);
|
||
const isLoadingJobSkill = ref(false);
|
||
const currentJobId = ref(null);
|
||
const currentJobName = ref('');
|
||
|
||
// 技能发展所需的数据
|
||
const recommendSkillsData = ref({
|
||
currentJobSkills: [],
|
||
recommendedJobs: []
|
||
});
|
||
|
||
const pathSkillsData = ref({
|
||
pathData: {
|
||
start: { title: '', skills: [] },
|
||
steps: [],
|
||
end: { title: '', skills: [] }
|
||
},
|
||
targetCareer: ''
|
||
});
|
||
|
||
// 打开弹窗
|
||
function openRemindPopup() {
|
||
nextTick(() => {
|
||
if (remindPopup.value) {
|
||
try {
|
||
remindPopup.value.open();
|
||
} catch (error) {
|
||
// 静默处理错误
|
||
}
|
||
} else {
|
||
setTimeout(() => {
|
||
if (remindPopup.value) {
|
||
try {
|
||
remindPopup.value.open();
|
||
} catch (error) {
|
||
// 静默处理错误
|
||
}
|
||
} else {
|
||
setTimeout(() => {
|
||
if (remindPopup.value) {
|
||
try {
|
||
remindPopup.value.open();
|
||
} catch (error) {
|
||
// 静默处理错误
|
||
}
|
||
}
|
||
}, 500);
|
||
}
|
||
}, 500);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 检查用户是否完善了个人信息(调用接口获取)
|
||
let hasCheckedRemindInfo = false;
|
||
|
||
async function getRemindInfo() {
|
||
if (hasCheckedRemindInfo) {
|
||
return;
|
||
}
|
||
|
||
hasCheckedRemindInfo = true;
|
||
|
||
try {
|
||
const response = await appUserInfo();
|
||
const userInfo = response?.data || {};
|
||
|
||
// 检查 idCard(身份证)- 必须项
|
||
let idCard = userInfo?.resume?.idCard ?? userInfo?.idCard ?? null;
|
||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||
if (!idCard || idCard === null || idCard === '') {
|
||
idCard = cachedUserInfo?.resume?.idCard ?? cachedUserInfo?.idCard ?? null;
|
||
}
|
||
const hasIdCard = idCard !== null && idCard !== undefined && idCard !== '';
|
||
|
||
// 检查职位信息:优先从 jobTitles 数组获取
|
||
const jobTitles = Array.isArray(userInfo?.jobTitles) ? userInfo.jobTitles : [];
|
||
const hasJobTitle = jobTitles.length > 0;
|
||
|
||
// 如果 jobTitles 为空,尝试从其他字段获取
|
||
let jobName = '';
|
||
if (!hasJobTitle) {
|
||
jobName = userInfo?.jobName ??
|
||
userInfo?.currentJobName ??
|
||
userInfo?.resume?.jobName ??
|
||
userInfo?.resume?.currentJobName ??
|
||
'';
|
||
}
|
||
const hasJobInfo = hasJobTitle || (jobName && jobName.trim() !== '');
|
||
|
||
// 检查技能标签:从 appSkillsList 获取
|
||
const appSkillsList = Array.isArray(userInfo?.appSkillsList) ? userInfo.appSkillsList : [];
|
||
// 检查是否有有效的技能(name 或 nameStr 不为空)
|
||
const hasSkills = appSkillsList.some(skill => {
|
||
const skillName = skill?.name || skill?.nameStr;
|
||
return skillName && skillName.trim() !== '';
|
||
});
|
||
|
||
// 判断信息是否完整(idCard、职位信息、技能标签都必须有)
|
||
const isComplete = hasIdCard && hasJobInfo && hasSkills;
|
||
|
||
if (!isComplete) {
|
||
// 收集缺失的信息提示
|
||
const missingItems = [];
|
||
if (!hasIdCard) {
|
||
missingItems.push('身份证信息');
|
||
}
|
||
if (!hasJobInfo) {
|
||
missingItems.push('职位信息');
|
||
}
|
||
if (!hasSkills) {
|
||
missingItems.push('技能标签');
|
||
}
|
||
remindList.value = [`请完善${missingItems.join('、')}`];
|
||
} else {
|
||
// 信息完整,设置职位信息
|
||
if (hasJobTitle) {
|
||
currentJobName.value = jobTitles[0];
|
||
} else {
|
||
currentJobName.value = jobName;
|
||
currentJobId.value = userInfo?.jobId ??
|
||
userInfo?.currentJobId ??
|
||
userInfo?.resume?.jobId ??
|
||
userInfo?.resume?.currentJobId ??
|
||
null;
|
||
}
|
||
// 信息完整,直接显示页面内容
|
||
showContent.value = true;
|
||
return;
|
||
}
|
||
|
||
setTimeout(() => {
|
||
openRemindPopup();
|
||
}, 500);
|
||
} catch (error) {
|
||
// 接口调用失败时,使用缓存作为降级方案
|
||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||
|
||
// 检查 idCard
|
||
const idCard = cachedUserInfo?.resume?.idCard ?? cachedUserInfo?.idCard ?? null;
|
||
const hasIdCard = idCard !== null && idCard !== undefined && idCard !== '';
|
||
|
||
// 检查职位信息
|
||
const cachedJobTitles = Array.isArray(cachedUserInfo?.jobTitles) ? cachedUserInfo.jobTitles : [];
|
||
const hasJobTitle = cachedJobTitles.length > 0;
|
||
let jobName = '';
|
||
if (!hasJobTitle) {
|
||
jobName = cachedUserInfo?.jobName ??
|
||
cachedUserInfo?.currentJobName ??
|
||
cachedUserInfo?.resume?.jobName ??
|
||
cachedUserInfo?.resume?.currentJobName ??
|
||
'';
|
||
}
|
||
const hasJobInfo = hasJobTitle || (jobName && jobName.trim() !== '');
|
||
|
||
// 检查技能标签
|
||
const cachedAppSkillsList = Array.isArray(cachedUserInfo?.appSkillsList) ? cachedUserInfo.appSkillsList : [];
|
||
const hasSkills = cachedAppSkillsList.some(skill => {
|
||
const skillName = skill?.name || skill?.nameStr;
|
||
return skillName && skillName.trim() !== '';
|
||
});
|
||
|
||
// 判断信息是否完整(idCard、职位信息、技能标签都必须有)
|
||
const isComplete = hasIdCard && hasJobInfo && hasSkills;
|
||
|
||
if (!isComplete) {
|
||
// 收集缺失的信息提示
|
||
const missingItems = [];
|
||
if (!hasIdCard) {
|
||
missingItems.push('身份证信息');
|
||
}
|
||
if (!hasJobInfo) {
|
||
missingItems.push('职位信息');
|
||
}
|
||
if (!hasSkills) {
|
||
missingItems.push('技能标签');
|
||
}
|
||
remindList.value = [`请完善${missingItems.join('、')}`];
|
||
} else {
|
||
// 信息完整,设置职位信息
|
||
if (hasJobTitle) {
|
||
currentJobName.value = cachedJobTitles[0];
|
||
} else {
|
||
currentJobName.value = jobName;
|
||
currentJobId.value = cachedUserInfo?.jobId ??
|
||
cachedUserInfo?.currentJobId ??
|
||
cachedUserInfo?.resume?.jobId ??
|
||
cachedUserInfo?.resume?.currentJobId ??
|
||
null;
|
||
}
|
||
// 信息完整,直接显示页面内容
|
||
showContent.value = true;
|
||
return;
|
||
}
|
||
|
||
setTimeout(() => {
|
||
openRemindPopup();
|
||
}, 500);
|
||
}
|
||
}
|
||
|
||
// 取消按钮
|
||
function handleCancel() {
|
||
remindPopup.value?.close();
|
||
navBack();
|
||
}
|
||
|
||
// 确认按钮
|
||
async function handleConfirm() {
|
||
remindPopup.value?.close();
|
||
|
||
// 跳转到完善信息页面
|
||
navTo('/packageA/pages/personalInfo/personalInfo');
|
||
}
|
||
|
||
// 切换tab
|
||
function switchTab(index) {
|
||
activeTab.value = index;
|
||
|
||
if (index === 0 && !currentJobId.value) {
|
||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||
|
||
// 优先从缓存中的 jobTitles 数组获取职位信息(取第一个)
|
||
const cachedJobTitles = Array.isArray(cachedUserInfo?.jobTitles) ? cachedUserInfo.jobTitles : [];
|
||
let newJobName = '';
|
||
|
||
if (cachedJobTitles.length > 0) {
|
||
newJobName = cachedJobTitles[0];
|
||
} else {
|
||
// 如果缓存中没有 jobTitles,从其他字段获取
|
||
newJobName = currentJobName.value ||
|
||
(cachedUserInfo?.jobName ??
|
||
cachedUserInfo?.currentJobName ??
|
||
cachedUserInfo?.resume?.jobName ??
|
||
cachedUserInfo?.resume?.currentJobName ??
|
||
'市场专员');
|
||
}
|
||
|
||
const newJobId = cachedUserInfo?.jobId ??
|
||
cachedUserInfo?.currentJobId ??
|
||
cachedUserInfo?.resume?.jobId ??
|
||
cachedUserInfo?.resume?.currentJobId ??
|
||
null;
|
||
|
||
currentJobId.value = newJobId;
|
||
currentJobName.value = newJobName;
|
||
}
|
||
}
|
||
|
||
// 搜索点击
|
||
function handleSearchClick() {
|
||
navTo('/pages/search/search');
|
||
}
|
||
|
||
// 菜单点击
|
||
function handleMenuClick() {
|
||
// TODO: 实现菜单功能
|
||
}
|
||
|
||
// 更多点击
|
||
function handleMoreClick() {
|
||
// TODO: 实现更多功能
|
||
}
|
||
|
||
function normalizeSkillLevel(score) {
|
||
const numericScore = Number(score);
|
||
if (Number.isNaN(numericScore)) {
|
||
return 0;
|
||
}
|
||
const rounded = Math.round(numericScore);
|
||
return Math.max(1, Math.min(6, rounded));
|
||
}
|
||
|
||
function splitSkillListByScore(skills = []) {
|
||
if (!Array.isArray(skills) || skills.length === 0) {
|
||
return {
|
||
possessed: [],
|
||
improvement: []
|
||
};
|
||
}
|
||
|
||
const sorted = [...skills].sort((a, b) => (Number(b.skillScore) || 0) - (Number(a.skillScore) || 0));
|
||
const midpoint = Math.ceil(sorted.length / 2);
|
||
const mapSkill = (item) => ({
|
||
name: item?.skillName || '',
|
||
level: normalizeSkillLevel(item?.skillScore)
|
||
});
|
||
|
||
return {
|
||
possessed: sorted.slice(0, midpoint).map(mapSkill),
|
||
improvement: sorted.slice(midpoint).map(mapSkill)
|
||
};
|
||
}
|
||
|
||
// 处理职位卡片点击
|
||
async function handleJobCardClick(job) {
|
||
if (!job) {
|
||
return;
|
||
}
|
||
|
||
selectedJobTitle.value = job.title || job.jobName || '';
|
||
selectedJobPossessedSkills.value = [];
|
||
selectedJobImprovementSkills.value = [];
|
||
|
||
if (isLoadingJobSkill.value) {
|
||
return;
|
||
}
|
||
|
||
isLoadingJobSkill.value = true;
|
||
uni.showLoading({
|
||
title: '加载中...',
|
||
mask: true
|
||
});
|
||
|
||
try {
|
||
// 从 appUserInfo 接口获取技能数据
|
||
const response = await appUserInfo();
|
||
const userInfo = response?.data || {};
|
||
const appSkillsList = Array.isArray(userInfo?.appSkillsList) ? userInfo.appSkillsList : [];
|
||
|
||
// 将 appSkillsList 转换为 splitSkillListByScore 需要的格式
|
||
const skillList = appSkillsList.map(item => ({
|
||
skillName: item?.name || item?.nameStr || '',
|
||
skillScore: item?.levels || item?.levelStr || 0
|
||
})).filter(item => item.skillName);
|
||
|
||
const { possessed, improvement } = splitSkillListByScore(skillList);
|
||
|
||
if (possessed.length === 0 && improvement.length === 0) {
|
||
// 如果 appUserInfo 中没有技能数据,尝试使用推荐职位数据中的技能信息
|
||
const fallbackSkills = Array.isArray(job?.rawSkills) ? job.rawSkills : [];
|
||
if (fallbackSkills.length === 0) {
|
||
uni.showToast({
|
||
title: '暂无技能数据',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
const fallbackSplit = splitSkillListByScore(fallbackSkills);
|
||
selectedJobPossessedSkills.value = fallbackSplit.possessed;
|
||
selectedJobImprovementSkills.value = fallbackSplit.improvement;
|
||
} else {
|
||
selectedJobPossessedSkills.value = possessed;
|
||
selectedJobImprovementSkills.value = improvement;
|
||
}
|
||
skillDetailPopup.value?.open();
|
||
} catch (error) {
|
||
// 接口调用失败,尝试使用推荐职位数据中的技能信息
|
||
const fallbackSkills = Array.isArray(job?.rawSkills) ? job.rawSkills : [];
|
||
if (fallbackSkills.length > 0) {
|
||
const fallbackSplit = splitSkillListByScore(fallbackSkills);
|
||
selectedJobPossessedSkills.value = fallbackSplit.possessed;
|
||
selectedJobImprovementSkills.value = fallbackSplit.improvement;
|
||
skillDetailPopup.value?.open();
|
||
} else {
|
||
uni.showToast({
|
||
title: '获取技能信息失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
} finally {
|
||
isLoadingJobSkill.value = false;
|
||
uni.hideLoading();
|
||
}
|
||
}
|
||
|
||
// 处理技能弹出层关闭
|
||
function handleSkillPopupClose() {
|
||
// 可以在这里处理关闭后的逻辑
|
||
}
|
||
|
||
// 处理职业推荐技能数据更新
|
||
function handleRecommendSkillsUpdated(data) {
|
||
recommendSkillsData.value = {
|
||
currentJobSkills: data.currentJobSkills || [],
|
||
recommendedJobs: data.recommendedJobs || []
|
||
};
|
||
}
|
||
|
||
// 处理职业路径数据更新
|
||
function handlePathDataUpdated(data) {
|
||
pathSkillsData.value = {
|
||
pathData: data.pathData || {
|
||
start: { title: '', skills: [] },
|
||
steps: [],
|
||
end: { title: '', skills: [] }
|
||
},
|
||
targetCareer: data.targetCareer || ''
|
||
};
|
||
}
|
||
|
||
onLoad(() => {
|
||
getRemindInfo();
|
||
});
|
||
|
||
onShow(() => {
|
||
// 返回本页后,如果之前因为信息缺失未展示内容,则重新检查
|
||
if (!showContent.value) {
|
||
hasCheckedRemindInfo = false;
|
||
getRemindInfo();
|
||
}
|
||
});
|
||
|
||
onMounted(() => {
|
||
if (remindList.value.length > 0 && !showContent.value) {
|
||
setTimeout(() => {
|
||
if (remindPopup.value) {
|
||
openRemindPopup();
|
||
}
|
||
}, 300);
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.career-planning-page {
|
||
width: 100vw;
|
||
/* #ifdef H5 */
|
||
height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
|
||
background: url('@/static/icon/background2.png') 0 0 no-repeat;
|
||
background-size: 100% 728rpx;
|
||
/* #endif */
|
||
/* #ifdef MP-WEIXIN */
|
||
height: 100vh;
|
||
position: relative;
|
||
/* #endif */
|
||
background-color: #FFFFFF;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: relative;
|
||
}
|
||
|
||
/* #ifdef MP-WEIXIN */
|
||
.mp-background {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 728rpx;
|
||
z-index: 0;
|
||
}
|
||
/* #endif */
|
||
|
||
.page-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: relative;
|
||
z-index: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
|
||
.content-scroll {
|
||
flex: 1;
|
||
height: 0;
|
||
width: 100%;
|
||
padding-bottom: calc(88rpx + env(safe-area-inset-bottom));
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.tabbar-wrapper {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 999;
|
||
}
|
||
</style>
|