This commit is contained in:
2025-11-12 12:20:58 +08:00
parent 8764849cd6
commit 1468002fe2
14 changed files with 856 additions and 217 deletions

56
apiRc/jobPath.js Normal file
View File

@@ -0,0 +1,56 @@
/*
* @Date: 2025-11-12
* @Description: 职业路径相关接口
*/
import request from '@/utilsRc/request'
// 根据职业名称获取路径列表
export function getJobPathPage(params) {
return request({
url: '/jobPath/getJobPathPage',
method: 'get',
params,
baseUrlType: 'zytp'
})
}
// 根据职业路径ID获取详情
export function getJobPathDetail(params) {
return request({
url: '/jobPath/getJobPathDetail',
method: 'get',
params,
baseUrlType: 'zytp'
})
}
// 获取职业路径数量
export function getJobPathNum() {
return request({
url: '/jobPath/getJobPathNum',
method: 'get',
baseUrlType: 'zytp'
})
}
// 导入职业路径
export function importJobPath(data) {
return request({
url: '/jobPath/importJobPath',
method: 'post',
data,
baseUrlType: 'zytp'
})
}
// 导出职业路径
export function exportJobPath(params) {
return request({
url: '/jobPath/exportJobPath',
method: 'get',
params,
baseUrlType: 'zytp',
responseType: 'arraybuffer'
})
}

64
apiRc/jobRecommend.js Normal file
View File

@@ -0,0 +1,64 @@
/*
* @Date: 2025-11-12
* @Description: 职业推荐相关接口
*/
import request from '@/utilsRc/request'
function createFormData(payload = {}) {
if (typeof FormData !== 'undefined') {
const formData = new FormData()
Object.keys(payload).forEach(key => {
const value = payload[key]
if (value !== undefined && value !== null && value !== '') {
formData.append(key, value)
}
})
return formData
}
return payload
}
export function recommendJob(data) {
const formData = createFormData({
jobId: data?.jobId
})
return request({
url: '/job/recommendJob',
method: 'post',
data: formData,
baseUrlType: 'zytp',
header: {
'content-type': 'multipart/form-data'
}
})
}
export function countJobRecommendRecords(data) {
const formData = createFormData({
jobId: data?.jobId,
jobName: data?.jobName,
recommendType: data?.recommendType,
startDate: data?.startDate,
endDate: data?.endDate
})
return request({
url: '/jobRecommendRecord/countJobRecommendRecords',
method: 'post',
data: formData,
baseUrlType: 'zytp',
header: {
'content-type': 'multipart/form-data'
}
})
}
export function getJobRecommendRecords(params) {
return request({
url: '/jobRecommendRecord/getJobRecommendRecords',
method: 'get',
params,
baseUrlType: 'zytp'
})
}

77
apiRc/jobSkill.js Normal file
View File

@@ -0,0 +1,77 @@
/*
* @Date: 2025-11-12
* @Description: 职业技能相关接口
*/
import request from '@/utilsRc/request'
export function getJobSkillDetail(params) {
return request({
url: '/jobSkillDet/getJobSkillDet',
method: 'get',
params,
baseUrlType: 'zytp'
})
}
export function getJobPathSkill(data) {
let formData
if (typeof FormData !== 'undefined') {
formData = new FormData()
if (data?.pathId !== undefined && data?.pathId !== null) {
formData.append('pathId', data.pathId)
}
if (data?.currentJobName !== undefined && data?.currentJobName !== null) {
formData.append('currentJobName', data.currentJobName)
}
} else {
formData = {
pathId: data?.pathId ?? '',
currentJobName: data?.currentJobName ?? ''
}
}
return request({
url: '/jobSkillDet/getJobPathSkill',
method: 'post',
data: formData,
baseUrlType: 'zytp',
header: {
'content-type': 'multipart/form-data'
}
})
}
// 获取技能热度分析列表
export function getSkillsHeatAnalysisList(params) {
return request({
url: '/skillsHeatAnalysis/list',
method: 'get',
params,
baseUrlType: 'zytp'
})
}
// 获取技能数量
export function getSkillNum(data) {
let formData
if (typeof FormData !== 'undefined') {
formData = new FormData()
if (data?.skillType !== undefined && data?.skillType !== null) {
formData.append('skillType', data.skillType)
}
} else {
formData = {
skillType: data?.skillType ?? ''
}
}
return request({
url: '/skill/getSkillNum',
method: 'post',
data: formData,
baseUrlType: 'zytp',
header: {
'content-type': 'multipart/form-data'
}
})
}

View File

@@ -8,7 +8,7 @@ import request from '@/utilsRc/request'
// 获取用户信息(职业规划推荐用)
export function appUserInfo() {
return request({
url: '/app/user/appUserInfo',
fullUrl: 'http://222.80.110.161:11111/api/ks/app/user/appUserInfo',
method: 'get'
})
}

View File

@@ -50,7 +50,7 @@
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wx9d1cbc11c8c40ba7",
"appid" : "wx4aa34488b965a331",
"setting" : {
"urlCheck" : false,
"es6" : true,

View File

@@ -35,10 +35,14 @@
<!-- 内容区域 -->
<scroll-view scroll-y class="content-scroll">
<component
:is="currentComponent"
<CareerRecommend
v-if="activeTab === 0"
:current-job-id="currentJobId"
:current-job-name="currentJobName"
@job-card-click="handleJobCardClick"
></component>
/>
<CareerPath v-else-if="activeTab === 1" />
<SkillDevelopment v-else />
</scroll-view>
</view>
@@ -50,9 +54,10 @@
</template>
<script setup>
import { ref, inject, nextTick, onMounted, computed } from 'vue';
import { ref, inject, nextTick, onMounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { appUserInfo } from '@/apiRc/user/user.js';
import { getJobSkillDetail } from '@/apiRc/jobSkill.js';
import RemindPopup from './components/RemindPopup.vue';
import PageHeader from './components/PageHeader.vue';
import SkillDetailPopup from './components/SkillDetailPopup.vue';
@@ -76,12 +81,9 @@ const activeTab = ref(0);
const selectedJobTitle = ref('');
const selectedJobPossessedSkills = ref([]);
const selectedJobImprovementSkills = ref([]);
// 当前组件
const currentComponent = computed(() => {
const components = [CareerRecommend, CareerPath, SkillDevelopment];
return components[activeTab.value];
});
const isLoadingJobSkill = ref(false);
const currentJobId = ref(null);
const currentJobName = ref('');
// 打开弹窗
function openRemindPopup() {
@@ -105,30 +107,57 @@ function openRemindPopup() {
// 获取提醒信息的接口
async function getRemindInfo() {
try {
// 接口已准备好,但暂时不使用(按用户要求)
// const response = await appUserInfo();
// if (response && response.code === 200) {
// const data = response.data || {};
// const jobTitles = data.jobTitles || [];
// const appSkillsList = data.appSkillsList || [];
//
// remindList.value = [];
// if (!jobTitles || jobTitles.length === 0) {
// remindList.value.push('求职期望;');
// }
//
// if (remindList.value.length > 0) {
// openRemindPopup();
// return;
// }
// }
const response = await appUserInfo();
if (response && response.code === 200) {
const data = response.data || {};
const reminders = [];
// 暂时使用测试数据显示弹窗
remindList.value = ['求职期望;'];
currentJobId.value = data?.jobId ?? data?.currentJobId ?? null;
currentJobName.value = data?.jobName ?? data?.currentJobName ?? '';
if (Array.isArray(data.remindList) && data.remindList.length > 0) {
reminders.push(
...data.remindList
.filter(item => typeof item === 'string' && item.trim().length > 0)
.map(item => item.trim())
);
} else {
const jobTitles = Array.isArray(data.jobTitles) ? data.jobTitles : [];
const appSkillsList = Array.isArray(data.appSkillsList) ? data.appSkillsList : [];
if (!currentJobName.value && jobTitles.length > 0) {
currentJobName.value = jobTitles[0];
}
if (jobTitles.length === 0) {
reminders.push('请完善求职期望');
}
if (appSkillsList.length === 0) {
reminders.push('请完善技能信息');
}
}
if (reminders.length === 0) {
reminders.push('暂无待完善信息');
}
remindList.value = reminders;
if (!currentJobName.value) {
currentJobName.value = '前端开发工程师';
}
openRemindPopup();
return;
}
throw new Error('接口返回异常');
} catch (error) {
console.error('获取提醒信息失败:', error);
remindList.value = ['求职期望;'];
currentJobId.value = null;
if (!currentJobName.value) {
currentJobName.value = '前端开发工程师';
}
remindList.value = ['获取提醒信息失败,请稍后重试'];
openRemindPopup();
}
}
@@ -166,12 +195,105 @@ function handleMoreClick() {
console.log('更多点击');
}
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)
};
}
// 处理职位卡片点击
function handleJobCardClick(job) {
selectedJobTitle.value = job.title || '';
selectedJobPossessedSkills.value = job.possessedSkills || [];
selectedJobImprovementSkills.value = job.improvementSkills || [];
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
});
const fallbackSkills = Array.isArray(job?.rawSkills) ? job.rawSkills : [];
const params = {};
if (job?.jobId) {
params.jobId = job.jobId;
}
if (job?.title) {
params.jobName = job.title;
} else if (job?.jobName) {
params.jobName = job.jobName;
}
try {
const response = await getJobSkillDetail(params);
const skillList = Array.isArray(response?.data) ? response.data : [];
const { possessed, improvement } = splitSkillListByScore(skillList);
if (possessed.length === 0 && improvement.length === 0) {
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) {
console.error('获取职位技能详情失败:', error);
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();
}
}
// 处理技能弹出层关闭

View File

@@ -42,6 +42,9 @@
<text>查询职业发展路径</text>
<uni-icons type="search" size="18" color="#FFFFFF"></uni-icons>
</button>
<view v-if="totalPathCount > 0" class="path-summary">
系统已收录 {{ totalPathCount }} 条职业路径
</view>
</view>
<!-- 职业发展路径区域 -->
@@ -113,75 +116,141 @@
<script setup>
import { ref, onMounted } from 'vue';
import { getJobPathPage, getJobPathDetail, getJobPathNum } from '@/apiRc/jobPath.js';
// 当前职位(从接口获取,只读)
const currentPosition = ref('前端开发工程师');
// 目标职业选项(根据当前职位动态获取)
const targetCareerOptions = ref([
{ label: '互联网/IT', value: 'internet_it' },
{ label: '高级前端开发工程师', value: 'senior_frontend' },
{ label: '前端架构师', value: 'frontend_architect' },
{ label: '技术总监', value: 'tech_director' }
]);
// 目标职业选项列表
const targetCareerOptions = ref([]);
const selectedTargetIndex = ref(-1);
const selectedJobPathId = ref(null);
// 职业路径数据(后续从接口获取)
const pathData = ref({
// 职业路径数
const totalPathCount = ref(0);
// 初始路径数据
const emptyPathData = {
start: {
title: '初级网络安全管理员',
skills: ['React', 'Node.js', 'Typescript', '数据库']
title: '暂无数据',
skills: []
},
steps: [
{
title: '中级网络安全工程师',
skills: ['Vue3', '微前端', '性能优化', '团队管理']
},
{
title: '资深安全顾问(横向发展)',
skills: ['架构设计', '技术选型', '工程化', '团队领导']
},
{
title: '高级网络安全架构师',
skills: ['架构设计', '技术选型', '工程化', '团队领导']
},
{
title: '技术部门经理',
skills: ['架构设计', '技术选型', '工程化', '团队领导']
}
],
steps: [],
end: {
title: 'IT 项目总监',
skills: ['架构设计', '技术选型', '工程化', '团队领导']
title: '暂无数据',
skills: []
}
};
const pathData = ref({ ...emptyPathData });
const isLoadingPath = ref(false);
function parseSkillList(skillString) {
if (!skillString) {
return [];
}
return skillString
.split(/[,]/)
.map(item => item.trim())
.filter(item => item.length > 0);
}
function resetPathData() {
pathData.value = {
start: { ...emptyPathData.start },
steps: [],
end: { ...emptyPathData.end }
};
}
async function fetchTargetCareerOptions(keyword = '') {
try {
const response = await getJobPathPage({
jobName: keyword,
pageNo: 1,
pageSize: 100
});
const list = response?.data?.list || [];
targetCareerOptions.value = list.map(item => ({
label: item.endJob || item.startJob || '未知职位',
value: item.id,
startJob: item.startJob,
endJob: item.endJob,
jobOrder: item.jobOrder
}));
// 获取当前职位信息(从接口获取)
async function getCurrentPosition() {
// TODO: 调用接口获取当前职位
// const response = await getCareerCurrentPosition();
// if (response && response.code === 200) {
// currentPosition.value = response.data?.position || '';
// // 根据当前职位获取目标职业选项
// await getTargetCareerOptions(currentPosition.value);
// }
if (targetCareerOptions.value.length === 0) {
selectedTargetIndex.value = -1;
selectedJobPathId.value = null;
}
} catch (error) {
console.error('获取职业路径列表失败:', error);
targetCareerOptions.value = [];
selectedTargetIndex.value = -1;
selectedJobPathId.value = null;
uni.showToast({
title: '职业路径列表获取失败',
icon: 'none'
});
}
}
// 根据当前职位获取目标职业选项
async function getTargetCareerOptions(position) {
// TODO: 调用接口获取目标职业选项
// const response = await getTargetCareers(position);
// if (response && response.code === 200) {
// targetCareerOptions.value = response.data || [];
// }
async function fetchPathCount() {
try {
const response = await getJobPathNum();
totalPathCount.value = response?.data ?? 0;
} catch (error) {
console.error('获取职业路径数量失败:', error);
totalPathCount.value = 0;
}
}
async function loadPathDetail(jobPathId) {
try {
const response = await getJobPathDetail({
jobPathId
});
const details = Array.isArray(response?.data) ? response.data : [];
if (details.length === 0) {
resetPathData();
uni.showToast({
title: '暂无职业路径数据',
icon: 'none'
});
return;
}
const normalized = details.map(item => ({
title: item?.name || '未命名职位',
skills: parseSkillList(item?.skillNameList)
}));
const start = normalized[0] || { title: '暂无数据', skills: [] };
const end = normalized[normalized.length - 1] || { title: '暂无数据', skills: [] };
const steps = normalized.slice(1, normalized.length - 1);
pathData.value = {
start,
steps,
end
};
} catch (error) {
console.error('获取职业路径详情失败:', error);
uni.showToast({
title: '获取路径详情失败',
icon: 'none'
});
}
}
function handleTargetChange(e) {
selectedTargetIndex.value = e.detail.value;
const index = Number(e.detail.value);
selectedTargetIndex.value = index;
const option = targetCareerOptions.value[index];
selectedJobPathId.value = option ? option.value : null;
}
function handleQuery() {
async function handleQuery() {
if (selectedTargetIndex.value < 0) {
uni.showToast({
title: '请选择目标职业',
@@ -189,13 +258,72 @@ function handleQuery() {
});
return;
}
const selectedTarget = targetCareerOptions.value[selectedTargetIndex.value];
console.log('查询职业发展路径', currentPosition.value, selectedTarget);
// TODO: 调用接口查询职业路径
const option = targetCareerOptions.value[selectedTargetIndex.value];
if (!option) {
uni.showToast({
title: '目标职业数据异常',
icon: 'none'
});
return;
}
onMounted(() => {
getCurrentPosition();
let jobPathId = option.value;
isLoadingPath.value = true;
uni.showLoading({
title: '加载中...',
mask: true
});
try {
if (!jobPathId) {
const response = await getJobPathPage({
jobName: option.label,
pageNo: 1,
pageSize: 100
});
jobPathId = response?.data?.list?.[0]?.id || null;
}
if (!jobPathId) {
uni.showToast({
title: '未找到职业路径',
icon: 'none'
});
resetPathData();
return;
}
selectedJobPathId.value = jobPathId;
await loadPathDetail(jobPathId);
} catch (error) {
console.error('查询职业路径失败:', error);
uni.showToast({
title: '查询失败,请重试',
icon: 'none'
});
} finally {
isLoadingPath.value = false;
uni.hideLoading();
}
}
// 获取当前职位信息(从接口获取)
async function getCurrentPosition() {
// TODO: 调用接口获取当前职位
// const response = await getCareerCurrentPosition();
// if (response && response.code === 200) {
// currentPosition.value = response.data?.position || '';
// }
}
onMounted(async () => {
await getCurrentPosition();
await Promise.all([
fetchTargetCareerOptions(),
fetchPathCount()
]);
});
</script>
@@ -291,6 +419,13 @@ onMounted(() => {
padding: 0;
}
.path-summary {
margin-top: 16rpx;
font-size: 24rpx;
color: #666666;
text-align: center;
}
.path-section {
background-color: #FFFFFF;
border-radius: 16rpx;

View File

@@ -5,7 +5,7 @@
<view class="card-title">当前职位信息</view>
<view class="card-content">
<text class="label">当前职位</text>
<text class="value">{{ currentJob || '前端开发工程师' }}</text>
<text class="value">{{ currentJobDisplay }}</text>
</view>
</view>
@@ -20,11 +20,16 @@
>
{{ skill }}
</view>
<text v-if="!skillTags.length && !isLoadingSkillTags" class="empty-text">暂无技能数据</text>
</view>
</view>
<!-- 相似推荐职位 -->
<view class="section-title">相似推荐职位</view>
<view class="section-title">
相似推荐职位
<text v-if="recommendRecordCount > 0" class="recommend-count">{{ recommendRecordCount }}条记录</text>
</view>
<view v-if="!isLoadingRecommend && recommendedJobs.length === 0" class="empty-text">暂无推荐职位</view>
<view
class="job-item-card"
v-for="(job, index) in recommendedJobs"
@@ -49,99 +54,139 @@
</template>
<script setup>
import { ref } from 'vue';
import { ref, computed, watch } from 'vue';
import { getJobSkillDetail } from '@/apiRc/jobSkill.js';
import { recommendJob, countJobRecommendRecords, getJobRecommendRecords } from '@/apiRc/jobRecommend.js';
const props = defineProps({
currentJobId: {
type: [Number, String],
default: null
},
currentJobName: {
type: String,
default: ''
}
});
const emit = defineEmits(['job-card-click']);
// 当前职位信息(后续从接口获取)
// TODO: 从接口获取当前职位信息
// async function getCurrentJob() {
// try {
// const response = await getCareerCurrentJob();
// if (response && response.code === 200) {
// currentJob.value = response.data?.jobTitle || '前端开发工程师';
// }
// } catch (error) {
// console.error('获取当前职位失败:', error);
// }
// }
const currentJob = ref('前端开发工程师'); // mock数据
const skillTags = ref([]);
const recommendedJobs = ref([]);
const recommendRecordCount = ref(0);
const recommendRecords = ref([]);
const isLoadingSkillTags = ref(false);
const isLoadingRecommend = ref(false);
const isLoadingRecords = ref(false);
// 技能标签列表(后续从接口获取)
const skillTags = ref(['JavaScript', 'React', 'Vue', 'CSS3', 'HTML5', 'Git', 'Webpack', 'TypeScript']);
const currentJobDisplay = computed(() => props.currentJobName || '前端开发工程师');
// 相似推荐职位列表(后续从接口获取)
const recommendedJobs = ref([
{
id: 1,
title: '全栈开发工程师',
skills: ['React', 'Node.js', 'TypeScript', '数据库'],
possessedSkills: [
{ name: 'React', level: 5 },
{ name: 'JavaScript', level: 5 },
{ name: 'HTML/CSS', level: 6 },
{ name: 'Git', level: 5 }
],
improvementSkills: [
{ name: 'Node.js', level: 3 },
{ name: '数据库设计', level: 3 },
{ name: 'RESTful API', level: 3 },
{ name: 'Docker', level: 3 }
]
},
{
id: 2,
title: '高级前端开发工程师',
skills: ['React', 'Vue', 'TypeScript', 'Webpack'],
possessedSkills: [
{ name: 'React', level: 5 },
{ name: 'Vue', level: 4 },
{ name: 'JavaScript', level: 5 }
],
improvementSkills: [
{ name: 'TypeScript', level: 3 },
{ name: 'Webpack', level: 3 }
]
},
{
id: 3,
title: '前端架构师',
skills: ['React', 'Vue', 'TypeScript', '架构设计'],
possessedSkills: [
{ name: 'React', level: 6 },
{ name: 'Vue', level: 5 }
],
improvementSkills: [
{ name: '架构设计', level: 3 },
{ name: '技术选型', level: 3 }
]
},
{
id: 4,
title: '前端架构师',
skills: ['架构设计', '技术选型', '工程化', '团队领导'],
possessedSkills: [
{ name: '工程化', level: 4 }
],
improvementSkills: [
{ name: '架构设计', level: 3 },
{ name: '技术选型', level: 3 },
{ name: '团队领导', level: 2 }
]
function normalizeSkillListForDisplay(skillList = []) {
return (Array.isArray(skillList) ? skillList : [])
.map(item => item?.skillName)
.filter(name => !!name);
}
]);
// TODO: 从接口获取推荐职位数据
// async function getRecommendedJobs() {
// try {
// const response = await getCareerRecommendJobs();
// if (response && response.code === 200) {
// recommendedJobs.value = response.data || [];
// }
// } catch (error) {
// console.error('获取推荐职位失败:', error);
// }
// }
async function fetchCurrentJobSkills() {
if (!props.currentJobId && !props.currentJobName) {
skillTags.value = [];
return;
}
isLoadingSkillTags.value = true;
try {
const params = {};
if (props.currentJobId !== null && props.currentJobId !== undefined && props.currentJobId !== '') {
params.jobId = props.currentJobId;
}
if (props.currentJobName) {
params.jobName = props.currentJobName;
}
const response = await getJobSkillDetail(params);
const list = Array.isArray(response?.data) ? response.data : [];
skillTags.value = normalizeSkillListForDisplay(list);
} catch (error) {
console.error('获取当前职位技能失败:', error);
skillTags.value = [];
} finally {
isLoadingSkillTags.value = false;
}
}
async function fetchRecommendedJobs() {
const hasJobId = props.currentJobId !== null && props.currentJobId !== undefined && props.currentJobId !== '';
if (!hasJobId) {
recommendedJobs.value = [];
return;
}
isLoadingRecommend.value = true;
try {
const response = await recommendJob({
jobId: props.currentJobId
});
const list = Array.isArray(response?.data) ? response.data : [];
recommendedJobs.value = list.map((item, index) => {
const rawSkills = Array.isArray(item?.skillList) ? item.skillList : [];
return {
id: item?.jobId ?? index,
jobId: item?.jobId ?? null,
title: item?.jobName || `推荐职位${index + 1}`,
jobName: item?.jobName || '',
skills: normalizeSkillListForDisplay(rawSkills),
rawSkills
};
});
} catch (error) {
console.error('获取推荐职位失败:', error);
recommendedJobs.value = [];
} finally {
isLoadingRecommend.value = false;
}
}
async function fetchRecommendRecordInfo() {
const hasJobId = props.currentJobId !== null && props.currentJobId !== undefined && props.currentJobId !== '';
const hasJobName = !!props.currentJobName;
if (!hasJobId && !hasJobName) {
recommendRecordCount.value = 0;
recommendRecords.value = [];
return;
}
isLoadingRecords.value = true;
try {
const [countRes, listRes] = await Promise.all([
countJobRecommendRecords({
jobId: hasJobId ? props.currentJobId : undefined,
jobName: props.currentJobName
}),
getJobRecommendRecords({
jobName: props.currentJobName || '',
pageNo: 1,
pageSize: 10
})
]);
recommendRecordCount.value = Number(countRes?.data) || 0;
recommendRecords.value = Array.isArray(listRes?.data?.list) ? listRes.data.list : [];
} catch (error) {
console.error('获取推荐记录失败:', error);
recommendRecordCount.value = 0;
recommendRecords.value = [];
} finally {
isLoadingRecords.value = false;
}
}
watch(
() => [props.currentJobId, props.currentJobName],
() => {
fetchCurrentJobSkills();
fetchRecommendedJobs();
fetchRecommendRecordInfo();
},
{ immediate: true }
);
function handleJobSearch(job) {
console.log('搜索职位:', job.title);
@@ -285,6 +330,19 @@ function handleJobCardClick(job) {
box-sizing: border-box;
}
.recommend-count {
margin-left: 12rpx;
font-size: 22rpx;
color: #666666;
font-weight: 400;
}
.empty-text {
font-size: 24rpx;
color: #999999;
line-height: 34rpx;
}
button::after {
border: none;
}

View File

@@ -48,6 +48,7 @@
<script setup>
import { ref, onMounted } from 'vue';
import SkillWeightPopup from './SkillWeightPopup.vue';
import { getSkillNum, getSkillsHeatAnalysisList } from '@/apiRc/jobSkill.js';
// 技能列表(后续从接口获取)
const skillList = ref([
@@ -77,6 +78,14 @@ const skillList = ref([
}
]);
// 技能数量
const skillCount = ref(0);
const isLoadingSkillCount = ref(false);
// 技能热度分析列表
const heatAnalysisList = ref([]);
const isLoadingHeatAnalysis = ref(false);
// 弹出层引用
const skillWeightPopup = ref(null);
@@ -85,6 +94,43 @@ const selectedSkillName = ref('');
const selectedSkillWeight = ref('');
const selectedSkillLevel = ref(0);
// 获取技能数量
async function fetchSkillNum(skillType = null) {
isLoadingSkillCount.value = true;
try {
const response = await getSkillNum({
skillType: skillType
});
skillCount.value = response?.data ?? 0;
} catch (error) {
console.error('获取技能数量失败:', error);
skillCount.value = 0;
} finally {
isLoadingSkillCount.value = false;
}
}
// 获取技能热度分析列表
async function fetchSkillsHeatAnalysis(startDate, endDate) {
isLoadingHeatAnalysis.value = true;
try {
const params = {};
if (startDate) {
params.startDate = startDate;
}
if (endDate) {
params.endDate = endDate;
}
const response = await getSkillsHeatAnalysisList(params);
heatAnalysisList.value = Array.isArray(response?.data) ? response.data : [];
} catch (error) {
console.error('获取技能热度分析失败:', error);
heatAnalysisList.value = [];
} finally {
isLoadingHeatAnalysis.value = false;
}
}
// 获取技能发展数据(从接口获取)
async function getSkillDevelopmentData() {
// TODO: 调用接口获取技能发展数据
@@ -109,6 +155,23 @@ function handlePopupClose() {
onMounted(() => {
getSkillDevelopmentData();
// 获取技能数量不传skillType获取全部
fetchSkillNum();
// 获取技能热度分析默认查询最近7天
const endDate = new Date();
const startDate = new Date();
startDate.setDate(startDate.getDate() - 7);
const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
fetchSkillsHeatAnalysis(formatDate(startDate), formatDate(endDate));
});
</script>

35
project.config.json Normal file
View File

@@ -0,0 +1,35 @@
{
"setting": {
"es6": true,
"postcss": true,
"minified": true,
"uglifyFileName": false,
"enhance": true,
"packNpmRelationList": [],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"useCompilerPlugins": false,
"minifyWXML": true,
"compileWorklet": false,
"uploadWithSourceMap": true,
"packNpmManually": false,
"minifyWXSS": true,
"localPlugins": false,
"disableUseStrict": false,
"condition": false,
"swc": false,
"disableSWC": true
},
"compileType": "miniprogram",
"simulatorPluginLibVersion": {},
"packOptions": {
"ignore": [],
"include": []
},
"appid": "wx4aa34488b965a331",
"editorSetting": {},
"libVersion": "3.11.1"
}

View File

@@ -0,0 +1,22 @@
{
"libVersion": "3.11.1",
"projectname": "ks-app-employment-service",
"setting": {
"urlCheck": true,
"coverView": true,
"lazyloadPlaceholderEnable": false,
"skylineRenderEnable": false,
"preloadBackgroundData": false,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"compileHotReLoad": true,
"useApiHook": true,
"useApiHostProcess": true,
"useStaticServer": false,
"useLanDebug": false,
"showES6CompileOption": false,
"checkInvalidKey": true,
"ignoreDevUnusedFiles": true,
"bigPackageSizeSupport": false
}
}

View File

@@ -1,36 +1,35 @@
{
"description": "项目配置文件。",
"packOptions": {
"ignore": []
},
"setting": {
"urlCheck": false,
"es6": true,
"postcss": true,
"minified": true,
"newFeature": true,
"bigPackageSizeSupport": true
"uglifyFileName": false,
"enhance": true,
"packNpmRelationList": [],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"useCompilerPlugins": false,
"minifyWXML": true,
"compileWorklet": false,
"uploadWithSourceMap": true,
"packNpmManually": false,
"minifyWXSS": true,
"localPlugins": false,
"disableUseStrict": false,
"condition": false,
"swc": false,
"disableSWC": true
},
"compileType": "miniprogram",
"libVersion": "3.5.7",
"appid": "wx9d1cbc11c8c40ba7",
"projectname": "qingdao-employment-service",
"condition": {
"search": {
"current": -1,
"list": []
"simulatorPluginLibVersion": {},
"packOptions": {
"ignore": [],
"include": []
},
"conversation": {
"current": -1,
"list": []
},
"game": {
"current": -1,
"list": []
},
"miniprogram": {
"current": -1,
"list": []
}
}
"appid": "wx4aa34488b965a331",
"editorSetting": {},
"libVersion": "3.11.1"
}

View File

@@ -19,8 +19,8 @@ let exports = {
// baseUrl: 'http://10.160.0.5:8903', // 演示环境外网
// baseUrl: 'http://111.34.80.140:8081/prod-api', // 正式环境(不要轻易连接)
baseUrl: 'http://10.160.0.5:8907', // 正式环境在济南人才上部署(不要轻易连接)
baseUrl: 'http://ks.zhaopinzao8dian.com/api/ks',
zytpBaseUrl: 'http://ks.zhaopinzao8dian.com/api/ks',

View File

@@ -12,6 +12,7 @@ import { toast, showConfirm, tansParams } from '@/utilsRc/common'
let timeout = 10000
const baseUrl = configRc.baseUrl
const zytpBaseUrl = configRc.zytpBaseUrl || ''
const request = config => {
// 是否需要设置 token
@@ -22,20 +23,27 @@ const request = config => {
}
config.header['Authorization'] = 'Bearer ' + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJzeXNfdXNlcjoxIiwicm5TdHIiOiJVMDRocERSZjdZMXJUbUxXb05uOUpzYUdDZzBNazJJQSIsInVzZXJJZCI6MX0.LZ29vvA4tK3b9Hki4nU9Jb1himXZM2AEOue3CMRY95w'
// get请求映射params参数
const baseType = config.baseUrlType
const requestBaseUrl = baseType === 'zytp' && zytpBaseUrl ? zytpBaseUrl : baseUrl
let requestUrl = config.fullUrl ? config.fullUrl : (requestBaseUrl + (config.url || ''))
if (config.params) {
let url = config.url + '?' + tansParams(config.params)
let url = tansParams(config.params)
url = url.slice(0, -1)
config.url = url
if (url) {
requestUrl += (requestUrl.includes('?') ? '&' : '?') + url
}
}
return new Promise((resolve, reject) => {
uni.request({
method: config.method || 'get',
timeout: config.timeout || timeout,
url: baseUrl + config.url,
url: requestUrl,
// url: 'https://gccrcdh.sd-talent.cn:80/zhq' + config.url,
data: config.data,
header: config.header,
dataType: 'json'
dataType: 'json',
responseType: config.responseType || 'text'
}).then(response => {
let res = response.data
let error = response.errMsg!='request:ok'