merge: 合并 CareerMap 分支到 main
This commit is contained in:
46
apiRc/jobPath.js
Normal file
46
apiRc/jobPath.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* @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) {
|
||||||
|
const requestParams = {};
|
||||||
|
if (params?.jobPathId !== undefined && params?.jobPathId !== null && params?.jobPathId !== '') {
|
||||||
|
requestParams.id = params.jobPathId;
|
||||||
|
} else if (params?.id !== undefined && params?.id !== null && params?.id !== '') {
|
||||||
|
requestParams.id = params.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requestParams.id) {
|
||||||
|
return Promise.reject('缺少必需的 id 参数');
|
||||||
|
}
|
||||||
|
|
||||||
|
return request({
|
||||||
|
url: '/jobPath/getJobPathById',
|
||||||
|
method: 'get',
|
||||||
|
params: requestParams,
|
||||||
|
baseUrlType: 'zytp'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取职业路径数量
|
||||||
|
export function getJobPathNum() {
|
||||||
|
return request({
|
||||||
|
url: '/jobPath/getJobPathNum',
|
||||||
|
method: 'get',
|
||||||
|
baseUrlType: 'zytp'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
33
apiRc/jobRecommend.js
Normal file
33
apiRc/jobRecommend.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* @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 params = {};
|
||||||
|
if (data?.jobName !== undefined && data?.jobName !== null && data?.jobName !== '') {
|
||||||
|
params.jobName = String(data.jobName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return request({
|
||||||
|
url: '/job/recommendJobByJobName',
|
||||||
|
method: 'get',
|
||||||
|
params: params,
|
||||||
|
baseUrlType: 'zytp'
|
||||||
|
})
|
||||||
|
}
|
||||||
45
apiRc/jobSkill.js
Normal file
45
apiRc/jobSkill.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* @Date: 2025-11-12
|
||||||
|
* @Description: 职业技能相关接口
|
||||||
|
*/
|
||||||
|
import request from '@/utilsRc/request'
|
||||||
|
|
||||||
|
export function getJobSkillDetail(params) {
|
||||||
|
return request({
|
||||||
|
url: '/jobSkillDet/getJobSkillDet',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
baseUrlType: 'zytp'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂未使用 - 如果需要在 CareerPath.vue 中点击路径职位查看详细技能信息时使用
|
||||||
|
// 使用场景:获取职业路径中某个职位的详细技能信息(包含技能分数、类型等)
|
||||||
|
// 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'
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
14
apiRc/user/user.js
Normal file
14
apiRc/user/user.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Date: 2025-01-XX
|
||||||
|
* @LastEditors:
|
||||||
|
* @LastEditTime:
|
||||||
|
*/
|
||||||
|
import request from '@/utilsRc/request'
|
||||||
|
|
||||||
|
// 获取用户信息(职业规划推荐用)
|
||||||
|
export function appUserInfo() {
|
||||||
|
return request({
|
||||||
|
url: '/app/user/appUserInfo',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -4,7 +4,8 @@
|
|||||||
class="tabbar-item"
|
class="tabbar-item"
|
||||||
v-for="(item, index) in tabbarList"
|
v-for="(item, index) in tabbarList"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="switchTab(item, index)"
|
@click.stop="switchTab(item, index)"
|
||||||
|
@tap.stop="switchTab(item, index)"
|
||||||
>
|
>
|
||||||
<view class="tabbar-icon">
|
<view class="tabbar-icon">
|
||||||
<image
|
<image
|
||||||
@@ -154,6 +155,8 @@ watch(() => userInfo.value, (newUserInfo, oldUserInfo) => {
|
|||||||
|
|
||||||
// 切换tab
|
// 切换tab
|
||||||
const switchTab = (item, index) => {
|
const switchTab = (item, index) => {
|
||||||
|
console.log('switchTab called', item, index);
|
||||||
|
|
||||||
// 检查是否为"发布岗位"页面,需要判断企业信息是否完整
|
// 检查是否为"发布岗位"页面,需要判断企业信息是否完整
|
||||||
if (item.path === '/pages/job/publishJob') {
|
if (item.path === '/pages/job/publishJob') {
|
||||||
// 检查用户是否已登录
|
// 检查用户是否已登录
|
||||||
@@ -270,8 +273,9 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
z-index: 999;
|
z-index: 9999;
|
||||||
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
|
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabbar-item {
|
.tabbar-item {
|
||||||
@@ -285,6 +289,8 @@ onMounted(() => {
|
|||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabbar-icon {
|
.tabbar-icon {
|
||||||
|
|||||||
237
docs/职业图谱接口说明.md
Normal file
237
docs/职业图谱接口说明.md
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
# 职业图谱功能接口说明
|
||||||
|
|
||||||
|
## 📋 目录
|
||||||
|
- [一、职业路径相关接口](#一职业路径相关接口)
|
||||||
|
- [二、职业技能相关接口](#二职业技能相关接口)
|
||||||
|
- [三、职业推荐相关接口](#三职业推荐相关接口)
|
||||||
|
- [四、接口使用场景](#四接口使用场景)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、职业路径相关接口
|
||||||
|
|
||||||
|
### 1. `getJobPathPage` - 根据职业名称获取路径列表
|
||||||
|
- **接口路径**: `/jobPath/getJobPathPage`
|
||||||
|
- **请求方法**: `GET`
|
||||||
|
- **接口类型**: `zytp`
|
||||||
|
- **参数**:
|
||||||
|
- `jobName` (可选): 职业名称,用于搜索
|
||||||
|
- `pageNo`: 页码
|
||||||
|
- `pageSize`: 每页数量
|
||||||
|
- **返回数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"startJobId": 1,
|
||||||
|
"startJob": "AI产品经理",
|
||||||
|
"endJobId": 1,
|
||||||
|
"endJob": "推荐算法",
|
||||||
|
"jobOrder": "AI产品经理,AI训练师,图像识别,导航算法,推荐算法",
|
||||||
|
"jobCount": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **使用场景**:
|
||||||
|
- ✅ **CareerPath.vue**: 获取目标职业选项列表(下拉选择器)
|
||||||
|
- ✅ **CareerRecommend.vue**: 通过 `jobName` 查询获取 `jobId`(当缺少 jobId 时)
|
||||||
|
- ✅ **CareerPath.vue**: 查询时如果没有 jobPathId,通过 jobName 查找
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. ``getJobPathById`- 根据职业路径ID获取详情
|
||||||
|
- **接口路径**: `/jobPath/getJobPathById`
|
||||||
|
- **请求方法**: `GET`
|
||||||
|
- **接口类型**: `zytp`
|
||||||
|
- **参数**:
|
||||||
|
- `jobPathId`: 职业路径ID
|
||||||
|
- **返回数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "职位名称",
|
||||||
|
"skillNameList": "技能1,技能2,技能3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **使用场景**:
|
||||||
|
- ✅ **CareerPath.vue**: 获取职业路径的详细信息(起点、中间步骤、终点及每个职位的技能列表)
|
||||||
|
- 用于显示完整的职业发展路径时间线
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. `getJobPathNum` - 获取职业路径数量
|
||||||
|
- **接口路径**: `/jobPath/getJobPathNum`
|
||||||
|
- **请求方法**: `GET`
|
||||||
|
- **接口类型**: `zytp`
|
||||||
|
- **参数**: 无
|
||||||
|
- **返回数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": 16 // 路径总数
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **使用场景**:
|
||||||
|
- ✅ **CareerPath.vue**: 显示"系统已收录 X 条职业路径"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、职业技能相关接口
|
||||||
|
|
||||||
|
### 4. `getJobSkillDetail` - 获取职位技能详情
|
||||||
|
- **接口路径**: `/jobSkillDet/getJobSkillDet`
|
||||||
|
- **请求方法**: `GET`
|
||||||
|
- **接口类型**: `zytp`
|
||||||
|
- **参数**:
|
||||||
|
- `jobId` (必需): 职位ID
|
||||||
|
- `jobName` (可选): 职位名称
|
||||||
|
- **返回数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"skillName": "技能名称",
|
||||||
|
"skillScore": "技能分数"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **使用场景**:
|
||||||
|
- ✅ **career-planning.vue**: 点击推荐职位卡片时,获取该职位的技能详情(用于技能详情弹窗 `SkillDetailPopup`)
|
||||||
|
- **调用位置**: `handleJobCardClick` 函数中
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. `getJobPathSkill` - 获取职业路径技能(已隐藏,暂未使用)
|
||||||
|
- **接口路径**: `/jobSkillDet/getJobPathSkill`
|
||||||
|
- **请求方法**: `POST`
|
||||||
|
- **接口类型**: `zytp`
|
||||||
|
- **参数**:
|
||||||
|
- `pathId`: 路径ID
|
||||||
|
- `currentJobName`: 当前职位名称
|
||||||
|
- **返回数据**: `List<JobSkillDetVO>` (包含技能分数、类型等详细信息)
|
||||||
|
- **使用场景**:
|
||||||
|
- ❌ 暂未在代码中使用
|
||||||
|
- 💡 **潜在使用场景**: 在 `CareerPath.vue` 中,如果用户点击路径中的某个职位,需要查看该职位在路径中的详细技能信息(包含技能分数、类型等)时,可以使用此接口
|
||||||
|
- **当前状态**: 接口已注释隐藏,如需使用请取消注释
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、职业推荐相关接口
|
||||||
|
|
||||||
|
### 8. `recommendJob` - 推荐职位
|
||||||
|
- **接口路径**: `/job/recommendJobByJobName`
|
||||||
|
- **请求方法**: `GET`
|
||||||
|
- **接口类型**: `zytp`
|
||||||
|
- **参数**:
|
||||||
|
- `jobName` (必需): 当前职位名称(从缓存中获取),通过 URL 参数传递,格式:`/job/recommendJobByJobName?jobName=职位名称`
|
||||||
|
- **返回数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"jobId": 123,
|
||||||
|
"jobName": "推荐职位名称",
|
||||||
|
"skillList": [
|
||||||
|
{
|
||||||
|
"skillName": "技能名称"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **使用场景**:
|
||||||
|
- ✅ **CareerRecommend.vue**: 根据当前职位名称(从缓存中获取),获取相似推荐职位列表(显示在"相似推荐职位"区域)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、接口使用场景
|
||||||
|
|
||||||
|
### 📍 页面结构
|
||||||
|
```
|
||||||
|
career-planning.vue (主页面)
|
||||||
|
├── Tab 0: 职业推荐 (CareerRecommend.vue)
|
||||||
|
├── Tab 1: 职业路径 (CareerPath.vue)
|
||||||
|
└── Tab 2: 技能发展 (SkillDevelopment.vue)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📍 职业推荐 Tab (CareerRecommend.vue)
|
||||||
|
1. **进入页面时**:
|
||||||
|
- `getJobSkillDetail` - 获取当前职位的技能标签(需要 `jobId`,如果没有 `jobId` 则跳过)
|
||||||
|
- `recommendJob` - 获取相似推荐职位(需要 `jobId`)
|
||||||
|
|
||||||
|
2. **点击职位卡片时**:
|
||||||
|
- `getJobSkillDetail` - 获取该职位的技能详情(显示在技能详情弹窗中)
|
||||||
|
|
||||||
|
**注意**: `getJobPathPage` 接口**只在职业路径流程中使用**,不在职业推荐流程中使用。
|
||||||
|
|
||||||
|
### 📍 职业路径 Tab (CareerPath.vue)
|
||||||
|
1. **进入页面时**:
|
||||||
|
- `getJobPathPage` - 获取所有职业路径列表(填充目标职业下拉选择器)
|
||||||
|
- `getJobPathNum` - 获取职业路径总数(显示"系统已收录 X 条职业路径")
|
||||||
|
|
||||||
|
2. **选择目标职业并点击查询时**:
|
||||||
|
- `getJobPathPage` - 如果没有 `jobPathId`,通过 `jobName` 查找
|
||||||
|
- `getJobPathDetail` (实际: `getJobPathById`) - 获取职业路径详情(显示完整路径时间线)
|
||||||
|
|
||||||
|
### 📍 技能发展 Tab (SkillDevelopment.vue)
|
||||||
|
1. **进入页面时**:
|
||||||
|
- 暂无接口调用
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔑 关键接口调用流程
|
||||||
|
|
||||||
|
### 流程1: 获取当前职位技能
|
||||||
|
```
|
||||||
|
CareerRecommend.vue
|
||||||
|
└── getJobSkillDetail(jobId, jobName) → 获取技能列表
|
||||||
|
(如果没有 jobId,则跳过获取技能)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 流程2: 查询职业路径
|
||||||
|
```
|
||||||
|
CareerPath.vue
|
||||||
|
├── getJobPathPage() → 获取路径列表(填充下拉选择器)
|
||||||
|
├── 用户选择目标职业
|
||||||
|
└── getJobPathById(jobPathId) → 获取路径详情(显示时间线)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 流程3: 获取推荐职位
|
||||||
|
```
|
||||||
|
CareerRecommend.vue
|
||||||
|
└── recommendJob(jobId) → 获取相似推荐职位列表
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
1. **接口返回码**:
|
||||||
|
- `code: 0` 和 `code: 200` 都表示成功
|
||||||
|
- 已在 `request.js` 中处理
|
||||||
|
|
||||||
|
2. **参数要求**:
|
||||||
|
- `getJobSkillDetail` 必须提供 `jobId`,如果没有 `jobId` 则跳过获取技能
|
||||||
|
- `getJobPathPage` **只在职业路径流程中使用**,不在职业推荐流程中使用
|
||||||
|
|
||||||
|
3. **数据格式**:
|
||||||
|
- `getJobPathPage` 返回的 `list` 在 `response.data.list` 中
|
||||||
|
- `getJobPathById` 返回的详情在 `response.data` 中(数组格式)
|
||||||
|
|
||||||
|
4. **接口使用范围**:
|
||||||
|
- `getJobPathPage` **只在职业路径流程(CareerPath.vue)中使用**
|
||||||
|
- 职业推荐流程(CareerRecommend.vue)**不使用** `getJobPathPage` 接口
|
||||||
|
|
||||||
|
5. **接口前缀**:
|
||||||
|
- 所有 `zytp` 类型的接口使用: `http://ks.zhaopinzao8dian.com/api/ks_zytp/admin-api/zytp`
|
||||||
|
|
||||||
4540
package-lock.json
generated
4540
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
1096
pages.json
1096
pages.json
File diff suppressed because it is too large
Load Diff
@@ -177,7 +177,7 @@ footer-height = 98rpx
|
|||||||
/* 页面容器 */
|
/* 页面容器 */
|
||||||
.container {
|
.container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1000;
|
z-index: 1;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: calc(100% - var(--window-bottom));
|
height: calc(100% - var(--window-bottom));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -106,17 +106,17 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="service-title">题库和考试</view>
|
<view class="service-title">题库和考试</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="service-item press-button" @click="goCa()">
|
<view class="service-item press-button" @click="handleServiceClick('quality-assessment')">
|
||||||
<view class="service-icon service-icon-8">
|
<view class="service-icon service-icon-8">
|
||||||
<IconfontIcon name="suzhicepingtiku" :size="48" color="#FFFFFF" />
|
<IconfontIcon name="suzhicepingtiku" :size="48" color="#FFFFFF" />
|
||||||
</view>
|
</view>
|
||||||
<view class="service-title">素质测评</view>
|
<view class="service-title">素质测评</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="service-item press-button" @click="goAiAu()">
|
<view class="service-item press-button" @click="handleServiceClick('ai-interview')">
|
||||||
<view class="service-icon service-icon-9">
|
<view class="service-icon service-icon-9">
|
||||||
<IconfontIcon name="ai" :size="68" color="#FFFFFF" />
|
<IconfontIcon name="ai" :size="68" color="#FFFFFF" />
|
||||||
</view>
|
</view>
|
||||||
<view class="service-title">虚拟面试</view>
|
<view class="service-title">AI智能面试</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="service-item press-button" style="justify-content:normal" @click="goRc()">
|
<view class="service-item press-button" style="justify-content:normal" @click="goRc()">
|
||||||
<view class="service-icon service-icon-9">
|
<view class="service-icon service-icon-9">
|
||||||
@@ -124,11 +124,17 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="service-title" style="overflow:unset">高校毕业生<br/>智慧就业服务</view>
|
<view class="service-title" style="overflow:unset">高校毕业生<br/>智慧就业服务</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="service-item press-button" @click="navToPage">
|
<view class="service-item press-button" @click="handleServiceClick('career-planning')">
|
||||||
|
<view class="service-icon service-icon-11">
|
||||||
|
<image class="service-icon-img" src="/static/icon/antOutline.png" mode="aspectFit"></image>
|
||||||
|
</view>
|
||||||
|
<view class="service-title">职业规划推荐</view>
|
||||||
|
</view>
|
||||||
|
<view class="service-item press-button" @click="navToTestPage">
|
||||||
<view class="service-icon service-icon-10">
|
<view class="service-icon service-icon-10">
|
||||||
<uni-icons type="gear-filled" size="32" color="#FFFFFF"></uni-icons>
|
<uni-icons type="gear-filled" size="32" color="#FFFFFF"></uni-icons>
|
||||||
</view>
|
</view>
|
||||||
<view class="service-title">帮扶</view>
|
<view class="service-title">测试页面</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -593,6 +599,8 @@ const { columnCount, columnSpace } = useColumnCount(() => {
|
|||||||
const getCompanyInfo = () => {
|
const getCompanyInfo = () => {
|
||||||
try {
|
try {
|
||||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||||
|
console.log('缓存中的userInfo:', cachedUserInfo);
|
||||||
|
|
||||||
// 重置企业信息
|
// 重置企业信息
|
||||||
companyInfo.name = '';
|
companyInfo.name = '';
|
||||||
companyInfo.avatar = '';
|
companyInfo.avatar = '';
|
||||||
@@ -699,49 +707,15 @@ const handleServiceClick = (serviceType) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理直播按钮点击 - 跳转微信视频号
|
// 处理直播按钮点击
|
||||||
const handleLiveClick = () => {
|
const handleLiveClick = () => {
|
||||||
// #ifdef MP-WEIXIN
|
$api.msg('该功能正在开发中');
|
||||||
const feedId = 'sphKH1AEeLfTJJE';
|
|
||||||
|
|
||||||
// 使用微信原生 API 打开视频号直播
|
|
||||||
if (typeof wx !== 'undefined' && wx.openChannelsUserProfile) {
|
|
||||||
wx.openChannelsUserProfile({
|
|
||||||
// feedId: feedId,
|
|
||||||
finderUserName: feedId, // 视频号 finderUserName,如果feedId足够可以留空
|
|
||||||
success: (res) => {
|
|
||||||
console.log('打开视频号成功', res);
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('打开视频号失败', err);
|
|
||||||
$api.msg(err.errMsg || '无法打开直播,请稍后重试');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 如果 API 不存在,尝试使用 uni API
|
|
||||||
uni.openChannelsLive({
|
|
||||||
feedId: feedId,
|
|
||||||
success: (res) => {
|
|
||||||
console.log('打开视频号成功', res);
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('打开视频号失败', err);
|
|
||||||
$api.msg('无法打开直播,请检查微信版本或稍后重试');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
// 非微信小程序环境提示
|
|
||||||
$api.msg('该功能仅在微信小程序中可用');
|
|
||||||
// #endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const navToPage = () =>{
|
// 跳转到测试页面
|
||||||
// navTo('/packageB/login?flag=nw');
|
const navToTestPage = () => {
|
||||||
navTo('/packageB/priority/helpFilter');
|
navTo('/pages/test/homepage-test');
|
||||||
}
|
};
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
try {
|
try {
|
||||||
@@ -1034,49 +1008,17 @@ function dataToImg(data) {
|
|||||||
|
|
||||||
// import { loginRc } from '@/apiRc/login/login.js';
|
// import { loginRc } from '@/apiRc/login/login.js';
|
||||||
import storeRc from '@/utilsRc/store/index.js';
|
import storeRc from '@/utilsRc/store/index.js';
|
||||||
import { getToken } from '@/utilsRc/auth.js';
|
|
||||||
// 跳转到高校毕业页面
|
// 跳转到高校毕业页面
|
||||||
function goRc(){
|
function goRc(){
|
||||||
if (checkLogin()) {
|
if (checkLogin()) {
|
||||||
let token = getToken();
|
let userInfo = uni.getStorageSync('userInfo')
|
||||||
console.log(token,'t123312')
|
console.log(uni.getStorageSync('userInfo'), "uni.getStorageSync('userInfo')");
|
||||||
if(!token){
|
storeRc.dispatch('LoginByUserInfo', userInfo).then(res => {
|
||||||
console.log(111)
|
// console.log(res, "'res");
|
||||||
let userInfo = uni.getStorageSync('userInfo')
|
navTo('/packageRc/pages/index/index');
|
||||||
storeRc.dispatch('LoginByUserInfo', userInfo).then(res => {
|
});
|
||||||
// console.log(res, "'res");
|
|
||||||
storeRc.dispatch('GetInfo').then(res => {
|
|
||||||
if(res.data.user.userType == 'person'){
|
|
||||||
navTo('/packageRc/pages/index/index');
|
|
||||||
}else{
|
|
||||||
navTo('/packageRc/pages/daiban/daiban');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
if(storeRc.state.user.type == 'person'){
|
|
||||||
navTo('/packageRc/pages/index/index');
|
|
||||||
}else{
|
|
||||||
navTo('/packageRc/pages/daiban/daiban');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 跳转素质测评页面(在线测评,职业库,职业生涯规划)
|
|
||||||
function goCa(){
|
|
||||||
if (checkLogin()) {
|
|
||||||
const userInfo = uni.getStorageSync('userInfo')
|
|
||||||
navTo(`/packageCa/search/search?userId=${userInfo.userId}&name=${userInfo.name}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 跳转AI智能面试
|
|
||||||
function goAiAu(){
|
|
||||||
if (checkLogin()) {
|
|
||||||
const userInfo = uni.getStorageSync('userInfo')
|
|
||||||
navTo(`/packageCa/search/AIAudition?userId=${userInfo.userId}&name=${userInfo.name}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
defineExpose({ loadData });
|
defineExpose({ loadData });
|
||||||
@@ -1383,15 +1325,17 @@ defineExpose({ loadData });
|
|||||||
align-items: center
|
align-items: center
|
||||||
justify-content: center
|
justify-content: center
|
||||||
.service-icon-11
|
.service-icon-11
|
||||||
background: linear-gradient(135deg, #FF9800 0%, #FFB74D 100%)
|
background-color: #72A4FC
|
||||||
position: relative
|
position: relative
|
||||||
&::before
|
display: flex
|
||||||
content: '📈'
|
align-items: center
|
||||||
position: absolute
|
justify-content: center
|
||||||
top: 50%
|
.service-icon-img
|
||||||
left: 50%
|
width: 42rpx
|
||||||
transform: translate(-50%, -50%)
|
height: 42rpx
|
||||||
font-size: 32rpx
|
position: relative
|
||||||
|
z-index: 1
|
||||||
|
filter: brightness(1.8) contrast(1.4)
|
||||||
.service-icon-12
|
.service-icon-12
|
||||||
background: linear-gradient(135deg, #4CAF50 0%, #81C784 100%)
|
background: linear-gradient(135deg, #4CAF50 0%, #81C784 100%)
|
||||||
position: relative
|
position: relative
|
||||||
|
|||||||
531
pages/service/career-planning.vue
Normal file
531
pages/service/career-planning.vue
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
<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-skills="recommendSkillsData.currentJobSkills"
|
||||||
|
:recommended-jobs="recommendSkillsData.recommendedJobs"
|
||||||
|
:path-data="pathSkillsData.pathData"
|
||||||
|
:target-career="pathSkillsData.targetCareer"
|
||||||
|
/>
|
||||||
|
</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 } from '@dcloudio/uni-app';
|
||||||
|
import { getJobSkillDetail } from '@/apiRc/jobSkill.js';
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 如果接口返回的数据中没有 idCard,则从缓存中读取
|
||||||
|
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||||
|
if (!idCard || idCard === null || idCard === '') {
|
||||||
|
idCard = cachedUserInfo?.resume?.idCard ?? cachedUserInfo?.idCard ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先从接口数据中获取职位信息
|
||||||
|
currentJobId.value = userInfo?.jobId ??
|
||||||
|
userInfo?.currentJobId ??
|
||||||
|
userInfo?.resume?.jobId ??
|
||||||
|
userInfo?.resume?.currentJobId ??
|
||||||
|
null;
|
||||||
|
currentJobName.value = userInfo?.jobName ??
|
||||||
|
userInfo?.currentJobName ??
|
||||||
|
userInfo?.resume?.jobName ??
|
||||||
|
userInfo?.resume?.currentJobName ??
|
||||||
|
'';
|
||||||
|
|
||||||
|
// 如果接口数据中没有职位信息,从缓存中读取
|
||||||
|
if (!currentJobId.value && !currentJobName.value) {
|
||||||
|
currentJobId.value = cachedUserInfo?.jobId ??
|
||||||
|
cachedUserInfo?.currentJobId ??
|
||||||
|
cachedUserInfo?.resume?.jobId ??
|
||||||
|
cachedUserInfo?.resume?.currentJobId ??
|
||||||
|
null;
|
||||||
|
currentJobName.value = cachedUserInfo?.jobName ??
|
||||||
|
cachedUserInfo?.currentJobName ??
|
||||||
|
cachedUserInfo?.resume?.jobName ??
|
||||||
|
cachedUserInfo?.resume?.currentJobName ??
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果还是没有职位信息,使用默认值
|
||||||
|
if (!currentJobName.value) {
|
||||||
|
currentJobName.value = '市场专员';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断 idCard 是否存在(包括空字符串、undefined、null 的情况)
|
||||||
|
const hasIdCard = idCard !== null && idCard !== undefined && idCard !== '';
|
||||||
|
|
||||||
|
if (!hasIdCard) {
|
||||||
|
remindList.value = ['请完善个人信息'];
|
||||||
|
} else {
|
||||||
|
remindList.value = ['暂无待完善信息'];
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
openRemindPopup();
|
||||||
|
}, 500);
|
||||||
|
} catch (error) {
|
||||||
|
// 接口调用失败时,使用缓存作为降级方案
|
||||||
|
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||||
|
const idCard = cachedUserInfo?.resume?.idCard ?? cachedUserInfo?.idCard ?? null;
|
||||||
|
|
||||||
|
// 从缓存中获取职位信息
|
||||||
|
currentJobId.value = cachedUserInfo?.jobId ??
|
||||||
|
cachedUserInfo?.currentJobId ??
|
||||||
|
cachedUserInfo?.resume?.jobId ??
|
||||||
|
cachedUserInfo?.resume?.currentJobId ??
|
||||||
|
null;
|
||||||
|
currentJobName.value = cachedUserInfo?.jobName ??
|
||||||
|
cachedUserInfo?.currentJobName ??
|
||||||
|
cachedUserInfo?.resume?.jobName ??
|
||||||
|
cachedUserInfo?.resume?.currentJobName ??
|
||||||
|
'';
|
||||||
|
// 如果缓存中没有职位信息,使用默认值
|
||||||
|
if (!currentJobName.value) {
|
||||||
|
currentJobName.value = '市场专员';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!idCard || idCard === null || idCard === '') {
|
||||||
|
remindList.value = ['请完善个人信息'];
|
||||||
|
} else {
|
||||||
|
remindList.value = ['暂无待完善信息'];
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
openRemindPopup();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消按钮
|
||||||
|
function handleCancel() {
|
||||||
|
remindPopup.value?.close();
|
||||||
|
navBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认按钮
|
||||||
|
async function handleConfirm() {
|
||||||
|
remindPopup.value?.close();
|
||||||
|
|
||||||
|
// 直接从缓存中读取 idCard(因为接口返回的数据中没有 idCard)
|
||||||
|
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||||
|
let idCard = cachedUserInfo?.resume?.idCard ?? cachedUserInfo?.idCard ?? null;
|
||||||
|
|
||||||
|
// 如果缓存中也没有,尝试从接口获取(虽然接口通常也没有)
|
||||||
|
if (!idCard || idCard === null || idCard === '') {
|
||||||
|
try {
|
||||||
|
const response = await appUserInfo();
|
||||||
|
const userInfo = response?.data || {};
|
||||||
|
idCard = userInfo?.resume?.idCard ?? userInfo?.idCard ?? null;
|
||||||
|
} catch (error) {
|
||||||
|
// 接口调用失败,继续使用缓存数据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 idCard 为空,才跳转到完善信息页面
|
||||||
|
if (!idCard || idCard === null || idCard === '') {
|
||||||
|
navTo('/pages/complete-info/complete-info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 idCard 存在,说明已经完善了信息,直接显示页面内容
|
||||||
|
// 从缓存中更新职位信息
|
||||||
|
currentJobId.value = cachedUserInfo?.jobId ??
|
||||||
|
cachedUserInfo?.currentJobId ??
|
||||||
|
cachedUserInfo?.resume?.jobId ??
|
||||||
|
cachedUserInfo?.resume?.currentJobId ??
|
||||||
|
null;
|
||||||
|
currentJobName.value = cachedUserInfo?.jobName ??
|
||||||
|
cachedUserInfo?.currentJobName ??
|
||||||
|
cachedUserInfo?.resume?.jobName ??
|
||||||
|
cachedUserInfo?.resume?.currentJobName ??
|
||||||
|
'';
|
||||||
|
if (!currentJobName.value) {
|
||||||
|
currentJobName.value = '市场专员';
|
||||||
|
}
|
||||||
|
|
||||||
|
showContent.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换tab
|
||||||
|
function switchTab(index) {
|
||||||
|
activeTab.value = index;
|
||||||
|
|
||||||
|
if (index === 0 && !currentJobId.value) {
|
||||||
|
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||||
|
|
||||||
|
const newJobId = cachedUserInfo?.jobId ??
|
||||||
|
cachedUserInfo?.currentJobId ??
|
||||||
|
cachedUserInfo?.resume?.jobId ??
|
||||||
|
cachedUserInfo?.resume?.currentJobId ??
|
||||||
|
null;
|
||||||
|
|
||||||
|
const newJobName = currentJobName.value ||
|
||||||
|
(cachedUserInfo?.jobName ??
|
||||||
|
cachedUserInfo?.currentJobName ??
|
||||||
|
cachedUserInfo?.resume?.jobName ??
|
||||||
|
cachedUserInfo?.resume?.currentJobName ??
|
||||||
|
'市场专员');
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
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>
|
||||||
561
pages/service/components/CareerPath.vue
Normal file
561
pages/service/components/CareerPath.vue
Normal file
@@ -0,0 +1,561 @@
|
|||||||
|
<template>
|
||||||
|
<view class="career-path">
|
||||||
|
<!-- 职业路径查询区域 -->
|
||||||
|
<view class="query-section">
|
||||||
|
<view class="section-title">
|
||||||
|
<uni-icons type="search" size="18" color="#286BFA"></uni-icons>
|
||||||
|
<text class="title-text">职业路径查询</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="input-group">
|
||||||
|
<view class="input-item">
|
||||||
|
<text class="input-label">当前职位</text>
|
||||||
|
<input
|
||||||
|
class="input-field"
|
||||||
|
:value="currentPosition"
|
||||||
|
placeholder="市场专员"
|
||||||
|
placeholder-style="color: #999999"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="input-item">
|
||||||
|
<text class="input-label">目标职业</text>
|
||||||
|
<picker
|
||||||
|
mode="selector"
|
||||||
|
:range="targetCareerOptions"
|
||||||
|
range-key="label"
|
||||||
|
:value="selectedTargetIndex"
|
||||||
|
@change="handleTargetChange"
|
||||||
|
>
|
||||||
|
<view class="picker-field">
|
||||||
|
<text :class="selectedTargetIndex >= 0 ? 'picker-text' : 'picker-placeholder'">
|
||||||
|
{{ selectedTargetIndex >= 0 ? targetCareerOptions[selectedTargetIndex].label : '请选择目标职业' }}
|
||||||
|
</text>
|
||||||
|
<uni-icons type="arrowdown" size="16" color="#999999"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<button class="query-btn" @click="handleQuery">
|
||||||
|
<text>查询职业发展路径</text>
|
||||||
|
<uni-icons type="search" size="18" color="#FFFFFF"></uni-icons>
|
||||||
|
</button>
|
||||||
|
<view v-if="totalPathCount > 0" class="path-summary">
|
||||||
|
系统已收录 {{ totalPathCount }} 条职业路径
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 职业发展路径区域 -->
|
||||||
|
<view class="path-section">
|
||||||
|
<view class="section-title">
|
||||||
|
<uni-icons type="person-filled" size="24" color="#000000"></uni-icons>
|
||||||
|
<text class="title-text">职业发展路径</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="timeline">
|
||||||
|
<!-- 起点 -->
|
||||||
|
<view class="timeline-item start">
|
||||||
|
<view class="timeline-marker start-marker"></view>
|
||||||
|
<view class="timeline-content">
|
||||||
|
<view class="step-title">起点: {{ pathData.start.title }}</view>
|
||||||
|
<view class="skill-tags">
|
||||||
|
<view
|
||||||
|
class="skill-tag"
|
||||||
|
v-for="(skill, index) in pathData.start.skills"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
{{ skill }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 步骤 -->
|
||||||
|
<view
|
||||||
|
class="timeline-item step"
|
||||||
|
v-for="(step, index) in pathData.steps"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<view class="timeline-marker step-marker"></view>
|
||||||
|
<view class="timeline-content">
|
||||||
|
<view class="step-title">第{{ index + 1 }}步: {{ step.title }}</view>
|
||||||
|
<view class="skill-tags">
|
||||||
|
<view
|
||||||
|
class="skill-tag"
|
||||||
|
v-for="(skill, sIndex) in step.skills"
|
||||||
|
:key="sIndex"
|
||||||
|
>
|
||||||
|
{{ skill }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 终点 -->
|
||||||
|
<view class="timeline-item end">
|
||||||
|
<view class="timeline-marker end-marker"></view>
|
||||||
|
<view class="timeline-content">
|
||||||
|
<view class="step-title">终点: {{ pathData.end.title }}</view>
|
||||||
|
<view class="skill-tags">
|
||||||
|
<view
|
||||||
|
class="skill-tag"
|
||||||
|
v-for="(skill, index) in pathData.end.skills"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
{{ skill }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue';
|
||||||
|
import { getJobPathPage, getJobPathDetail, getJobPathNum } from '@/apiRc/jobPath.js';
|
||||||
|
|
||||||
|
// 接收父组件传递的当前职位名称
|
||||||
|
const props = defineProps({
|
||||||
|
currentJobName: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['path-data-updated']);
|
||||||
|
|
||||||
|
// 当前职位(从父组件获取,统一使用)
|
||||||
|
const currentPosition = computed(() => props.currentJobName || '市场专员');
|
||||||
|
|
||||||
|
// 目标职业选项列表
|
||||||
|
const targetCareerOptions = ref([]);
|
||||||
|
const selectedTargetIndex = ref(-1);
|
||||||
|
const selectedJobPathId = ref(null);
|
||||||
|
|
||||||
|
// 职业路径数量
|
||||||
|
const totalPathCount = ref(0);
|
||||||
|
|
||||||
|
// 初始路径数据
|
||||||
|
const emptyPathData = {
|
||||||
|
start: {
|
||||||
|
title: '暂无数据',
|
||||||
|
skills: []
|
||||||
|
},
|
||||||
|
steps: [],
|
||||||
|
end: {
|
||||||
|
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 || response?.list || [];
|
||||||
|
|
||||||
|
targetCareerOptions.value = list.map(item => ({
|
||||||
|
label: item.endJob || item.startJob || '未知职位',
|
||||||
|
value: item.id,
|
||||||
|
startJob: item.startJob,
|
||||||
|
endJob: item.endJob,
|
||||||
|
jobOrder: item.jobOrder
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (targetCareerOptions.value.length === 0) {
|
||||||
|
selectedTargetIndex.value = -1;
|
||||||
|
selectedJobPathId.value = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
targetCareerOptions.value = [];
|
||||||
|
selectedTargetIndex.value = -1;
|
||||||
|
selectedJobPathId.value = null;
|
||||||
|
uni.showToast({
|
||||||
|
title: '职业路径列表获取失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPathCount() {
|
||||||
|
try {
|
||||||
|
const response = await getJobPathNum();
|
||||||
|
totalPathCount.value = response?.data ?? 0;
|
||||||
|
} catch (error) {
|
||||||
|
totalPathCount.value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPathDetail(jobPathId) {
|
||||||
|
if (!jobPathId || jobPathId === null || jobPathId === undefined || jobPathId === '') {
|
||||||
|
uni.showToast({
|
||||||
|
title: '职业路径ID无效',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
resetPathData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestParams = {
|
||||||
|
jobPathId: jobPathId
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await getJobPathDetail(requestParams);
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
// 通知父组件路径数据已更新
|
||||||
|
emit('path-data-updated', {
|
||||||
|
pathData: pathData.value,
|
||||||
|
targetCareer: targetCareerOptions.value[selectedTargetIndex.value]?.label || ''
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '获取路径详情失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
resetPathData();
|
||||||
|
emit('path-data-updated', {
|
||||||
|
pathData: { ...emptyPathData },
|
||||||
|
targetCareer: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTargetChange(e) {
|
||||||
|
const index = Number(e.detail.value);
|
||||||
|
selectedTargetIndex.value = index;
|
||||||
|
const option = targetCareerOptions.value[index];
|
||||||
|
selectedJobPathId.value = option ? option.value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleQuery() {
|
||||||
|
if (selectedTargetIndex.value < 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请选择目标职业',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = targetCareerOptions.value[selectedTargetIndex.value];
|
||||||
|
if (!option) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '目标职业数据异常',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.career-path {
|
||||||
|
padding: 10rpx 28rpx 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-section {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 28rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #157DF0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #000000;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
border: 1rpx solid #E0E0E0;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-field {
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
border: 1rpx solid #E0E0E0;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-placeholder {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: linear-gradient(180deg, rgba(18, 125, 240, 1) 0%, rgba(59, 14, 123, 0.71) 100%);
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
|
font-size: 28rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-family: '阿里巴巴普惠体3.0-regular', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
|
||||||
|
border: 2rpx solid rgba(187, 187, 187, 1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-summary {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666666;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-section {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 28rpx;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child)::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -32rpx;
|
||||||
|
top: 0;
|
||||||
|
width: 2rpx;
|
||||||
|
height: 100%;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
#E0E0E0 0,
|
||||||
|
#E0E0E0 6rpx,
|
||||||
|
transparent 6rpx,
|
||||||
|
transparent 12rpx
|
||||||
|
);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-marker {
|
||||||
|
position: absolute;
|
||||||
|
left: -32rpx;
|
||||||
|
top: 0;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-marker {
|
||||||
|
background-color: #FF4444;
|
||||||
|
border: 4rpx solid #FFFFFF;
|
||||||
|
box-shadow: 0 0 0 2rpx #FF4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-marker {
|
||||||
|
background-color: #286BFA;
|
||||||
|
border: 4rpx solid #FFFFFF;
|
||||||
|
box-shadow: 0 0 0 2rpx #286BFA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.end-marker {
|
||||||
|
background-color: #52C41A;
|
||||||
|
border: 4rpx solid #FFFFFF;
|
||||||
|
box-shadow: 0 0 0 2rpx #52C41A;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-content {
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-left: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #000000;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tag {
|
||||||
|
background-color: #F0F0F0;
|
||||||
|
color: #286BFA;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
button::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
388
pages/service/components/CareerRecommend.vue
Normal file
388
pages/service/components/CareerRecommend.vue
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
<template>
|
||||||
|
<view class="career-recommend">
|
||||||
|
<!-- 当前职位信息卡片 -->
|
||||||
|
<view class="info-card">
|
||||||
|
<view class="card-title">当前职位信息</view>
|
||||||
|
<view class="card-content">
|
||||||
|
<text class="label">当前职位</text>
|
||||||
|
<text class="value">{{ currentJobDisplay }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 我的技能标签卡片 -->
|
||||||
|
<view class="info-card">
|
||||||
|
<view class="card-title">我的技能标签</view>
|
||||||
|
<view class="skill-tags">
|
||||||
|
<view
|
||||||
|
class="skill-tag"
|
||||||
|
v-for="(skill, index) in skillTags"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
{{ skill }}
|
||||||
|
</view>
|
||||||
|
<text v-if="!skillTags.length && !isLoadingSkillTags" class="empty-text">暂无技能数据</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 相似推荐职位 -->
|
||||||
|
<view class="section-title">
|
||||||
|
相似推荐职位
|
||||||
|
</view>
|
||||||
|
<view v-if="!isLoadingRecommend && recommendedJobs.length === 0" class="empty-text">暂无推荐职位</view>
|
||||||
|
<view
|
||||||
|
class="job-item-card"
|
||||||
|
v-for="(job, index) in recommendedJobs"
|
||||||
|
:key="index"
|
||||||
|
@click="handleJobCardClick(job)"
|
||||||
|
>
|
||||||
|
<button class="search-btn" @click.stop="handleJobSearch(job)">职位搜索</button>
|
||||||
|
<view class="job-header">
|
||||||
|
<text class="job-title">{{ job.title }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="job-skills">
|
||||||
|
<view
|
||||||
|
class="job-skill-tag"
|
||||||
|
v-for="(skill, skillIndex) in job.skills"
|
||||||
|
:key="skillIndex"
|
||||||
|
>
|
||||||
|
{{ skill }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch, onMounted } from 'vue';
|
||||||
|
import { recommendJob } from '@/apiRc/jobRecommend.js';
|
||||||
|
import { getJobSkillDetail } from '@/apiRc/jobSkill.js';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
currentJobId: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
currentJobName: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['job-card-click', 'skills-updated']);
|
||||||
|
|
||||||
|
// 数据状态
|
||||||
|
const skillTags = ref([]);
|
||||||
|
const recommendedJobs = ref([]);
|
||||||
|
const isLoadingSkillTags = ref(false);
|
||||||
|
const isLoadingRecommend = ref(false);
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const currentJobDisplay = computed(() => props.currentJobName || '市场专员');
|
||||||
|
|
||||||
|
// 从技能列表中提取技能名称用于显示
|
||||||
|
function extractSkillNames(skillList = []) {
|
||||||
|
return (Array.isArray(skillList) ? skillList : [])
|
||||||
|
.map(item => item?.skillName || '')
|
||||||
|
.filter(name => !!name && name.trim().length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前职位的技能标签
|
||||||
|
async function fetchCurrentJobSkills() {
|
||||||
|
// 如果没有职位名称,从缓存中获取技能标签
|
||||||
|
if (!props.currentJobName || props.currentJobName === '市场专员') {
|
||||||
|
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||||
|
const cachedSkills = cachedUserInfo?.resume?.skillList ??
|
||||||
|
cachedUserInfo?.skillList ??
|
||||||
|
cachedUserInfo?.appSkillsList ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
// 如果缓存中有技能数据,使用缓存的数据
|
||||||
|
if (Array.isArray(cachedSkills) && cachedSkills.length > 0) {
|
||||||
|
skillTags.value = extractSkillNames(cachedSkills);
|
||||||
|
} else {
|
||||||
|
skillTags.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知父组件技能数据已更新
|
||||||
|
emit('skills-updated', {
|
||||||
|
currentJobSkills: skillTags.value,
|
||||||
|
recommendedJobs: recommendedJobs.value
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先调用接口获取技能数据
|
||||||
|
isLoadingSkillTags.value = true;
|
||||||
|
try {
|
||||||
|
const response = await getJobSkillDetail({
|
||||||
|
jobName: props.currentJobName
|
||||||
|
});
|
||||||
|
|
||||||
|
const skillList = Array.isArray(response?.data) ? response.data : [];
|
||||||
|
const apiSkills = extractSkillNames(skillList);
|
||||||
|
|
||||||
|
// 如果接口返回了技能数据,使用接口数据
|
||||||
|
if (apiSkills.length > 0) {
|
||||||
|
skillTags.value = apiSkills;
|
||||||
|
} else {
|
||||||
|
// 如果接口没有返回技能数据,从缓存中读取
|
||||||
|
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||||
|
const cachedSkills = cachedUserInfo?.resume?.skillList ??
|
||||||
|
cachedUserInfo?.skillList ??
|
||||||
|
cachedUserInfo?.appSkillsList ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
if (Array.isArray(cachedSkills) && cachedSkills.length > 0) {
|
||||||
|
skillTags.value = extractSkillNames(cachedSkills);
|
||||||
|
} else {
|
||||||
|
skillTags.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知父组件技能数据已更新
|
||||||
|
emit('skills-updated', {
|
||||||
|
currentJobSkills: skillTags.value,
|
||||||
|
recommendedJobs: recommendedJobs.value
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// 接口调用失败时,从缓存中读取
|
||||||
|
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||||
|
const cachedSkills = cachedUserInfo?.resume?.skillList ??
|
||||||
|
cachedUserInfo?.skillList ??
|
||||||
|
cachedUserInfo?.appSkillsList ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
if (Array.isArray(cachedSkills) && cachedSkills.length > 0) {
|
||||||
|
skillTags.value = extractSkillNames(cachedSkills);
|
||||||
|
} else {
|
||||||
|
skillTags.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('skills-updated', {
|
||||||
|
currentJobSkills: skillTags.value,
|
||||||
|
recommendedJobs: recommendedJobs.value
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
isLoadingSkillTags.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取推荐职位列表
|
||||||
|
async function fetchRecommendedJobs() {
|
||||||
|
isLoadingRecommend.value = true;
|
||||||
|
try {
|
||||||
|
const response = await recommendJob({
|
||||||
|
jobName: props.currentJobName
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = Array.isArray(response?.data) ? response.data : [];
|
||||||
|
|
||||||
|
recommendedJobs.value = list.map((item, index) => {
|
||||||
|
const skillList = Array.isArray(item?.skillList) ? item.skillList : [];
|
||||||
|
const skillNames = extractSkillNames(skillList);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: item?.jobId ?? index,
|
||||||
|
jobId: item?.jobId ?? null,
|
||||||
|
title: item?.jobName || `推荐职位${index + 1}`,
|
||||||
|
jobName: item?.jobName || '',
|
||||||
|
skills: skillNames,
|
||||||
|
rawSkills: skillList
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通知父组件推荐职位数据已更新
|
||||||
|
emit('skills-updated', {
|
||||||
|
currentJobSkills: skillTags.value,
|
||||||
|
recommendedJobs: recommendedJobs.value
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
recommendedJobs.value = [];
|
||||||
|
emit('skills-updated', {
|
||||||
|
currentJobSkills: skillTags.value,
|
||||||
|
recommendedJobs: []
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
isLoadingRecommend.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时检查并调用
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (props.currentJobName) {
|
||||||
|
fetchCurrentJobSkills();
|
||||||
|
fetchRecommendedJobs();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听 props 变化,自动获取推荐职位和技能标签
|
||||||
|
watch(
|
||||||
|
() => [props.currentJobId, props.currentJobName],
|
||||||
|
() => {
|
||||||
|
fetchCurrentJobSkills();
|
||||||
|
fetchRecommendedJobs();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 事件处理
|
||||||
|
function handleJobSearch(job) {
|
||||||
|
// TODO: 实现职位搜索跳转
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleJobCardClick(job) {
|
||||||
|
emit('job-card-click', job);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.career-recommend {
|
||||||
|
padding: 10rpx 28rpx 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 28rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #000000;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 34rpx;
|
||||||
|
color: rgba(154, 154, 154, 1);
|
||||||
|
text-align: left;
|
||||||
|
font-family: 'PingFangSC-Bold', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 46rpx;
|
||||||
|
color: rgb(16, 16, 16);
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: left;
|
||||||
|
font-family: 'PingFangSC-Bold', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tag {
|
||||||
|
background-color: #EAEFFE;
|
||||||
|
color: rgba(44, 101, 247, 1);
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: 24rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #000000;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-item-card {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 28rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||||
|
border-left: 6rpx solid #409EFF;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 16rpx;
|
||||||
|
right: 16rpx;
|
||||||
|
background-color: #FFDAB9;
|
||||||
|
color: #FF6347;
|
||||||
|
font-size: 20rpx;
|
||||||
|
padding: 6rpx 14rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
border: 1rpx solid #FFA07A;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 10;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-header {
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
padding-right: 100rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgb(16, 16, 16);
|
||||||
|
text-align: left;
|
||||||
|
font-family: 'PingFangSC-Bold', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
|
||||||
|
line-height: 46rpx;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-skills {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-skill-tag {
|
||||||
|
width: 130rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
line-height: 34rpx;
|
||||||
|
background-color: rgba(49, 100, 239, 0.1);
|
||||||
|
color: rgba(44, 101, 247, 1);
|
||||||
|
font-size: 24rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
218
pages/service/components/PageHeader.vue
Normal file
218
pages/service/components/PageHeader.vue
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page-header">
|
||||||
|
<view class="header-top">
|
||||||
|
<view class="title-section">
|
||||||
|
<text class="main-title">职业规划推荐指导平台</text>
|
||||||
|
<text class="sub-title">智能匹配·精准推荐·职业发展</text>
|
||||||
|
</view>
|
||||||
|
<view class="header-icons">
|
||||||
|
<view class="icon-dot" @click="handleMenuClick">
|
||||||
|
<view class="dot"></view>
|
||||||
|
<view class="dot"></view>
|
||||||
|
<view class="dot"></view>
|
||||||
|
</view>
|
||||||
|
<view class="icon-single-dot" @click="handleMoreClick"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 搜索栏 -->
|
||||||
|
<view class="search-bar" @click.stop="handleSearchClick" @tap.stop="handleSearchClick">
|
||||||
|
<uni-icons class="search-icon" type="search" size="18" color="#999999"></uni-icons>
|
||||||
|
<text class="search-placeholder">职位名称、薪资要求等</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Tab导航 -->
|
||||||
|
<view class="tab-nav-wrapper">
|
||||||
|
<view class="tab-nav">
|
||||||
|
<view
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ 'active': activeTab === 0 }"
|
||||||
|
@click.stop="handleTabClick(0)"
|
||||||
|
@tap.stop="handleTabClick(0)"
|
||||||
|
>
|
||||||
|
职业推荐
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ 'active': activeTab === 1 }"
|
||||||
|
@click.stop="handleTabClick(1)"
|
||||||
|
@tap.stop="handleTabClick(1)"
|
||||||
|
>
|
||||||
|
职业路径
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ 'active': activeTab === 2 }"
|
||||||
|
@click.stop="handleTabClick(2)"
|
||||||
|
@tap.stop="handleTabClick(2)"
|
||||||
|
>
|
||||||
|
技能发展
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { defineProps, defineEmits } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
activeTab: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['tab-change', 'search-click', 'menu-click', 'more-click']);
|
||||||
|
|
||||||
|
function handleTabClick(index) {
|
||||||
|
emit('tab-change', index);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSearchClick() {
|
||||||
|
emit('search-click');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMenuClick() {
|
||||||
|
emit('menu-click');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMoreClick() {
|
||||||
|
emit('more-click');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-header {
|
||||||
|
padding: 40rpx 28rpx 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-top {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 15rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-section {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-title {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #000000;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
color: rgba(51, 51, 51, 1);
|
||||||
|
text-align: left;
|
||||||
|
font-family: 'PingFangSC-Bold', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-icons {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-dot {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4rpx;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 6rpx;
|
||||||
|
height: 6rpx;
|
||||||
|
background-color: #666666;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-single-dot {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
background-color: #EBF1FF;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
padding: 0 28rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
height: 88rpx;
|
||||||
|
box-shadow: none;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: auto;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-placeholder {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-nav-wrapper {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 6rpx 20rpx;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12rpx 24rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #000000;
|
||||||
|
background-color: transparent;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s;
|
||||||
|
position: relative;
|
||||||
|
pointer-events: auto;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active {
|
||||||
|
background: linear-gradient(135deg, #9974FD 0%, #286BFA 100%);
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
144
pages/service/components/RemindPopup.vue
Normal file
144
pages/service/components/RemindPopup.vue
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<uni-popup
|
||||||
|
ref="popupRef"
|
||||||
|
type="center"
|
||||||
|
borderRadius="10px 10px 10px 10px"
|
||||||
|
background-color="#FFFFFF"
|
||||||
|
:mask-click="false"
|
||||||
|
>
|
||||||
|
<view class="remind-popup-content">
|
||||||
|
<view class="remind-title">提醒</view>
|
||||||
|
<view class="remind-message">请先完善您的个人信息:</view>
|
||||||
|
<view class="remind-list">
|
||||||
|
<view
|
||||||
|
class="remind-item"
|
||||||
|
v-for="(item, index) in remindList"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
{{ index + 1 }}、{{ item }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="remind-btns">
|
||||||
|
<button class="remind-btn cancel-btn" @click="handleCancel">取消</button>
|
||||||
|
<button class="remind-btn confirm-btn" @click="handleConfirm">确认</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineExpose } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
remindList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['cancel', 'confirm']);
|
||||||
|
|
||||||
|
const popupRef = ref(null);
|
||||||
|
|
||||||
|
// 打开弹窗
|
||||||
|
function open() {
|
||||||
|
if (popupRef.value) {
|
||||||
|
try {
|
||||||
|
popupRef.value.open();
|
||||||
|
} catch (error) {
|
||||||
|
// 静默处理错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
function close() {
|
||||||
|
popupRef.value?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消按钮
|
||||||
|
function handleCancel() {
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认按钮
|
||||||
|
function handleConfirm() {
|
||||||
|
emit('confirm');
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open,
|
||||||
|
close
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.remind-popup-content {
|
||||||
|
padding: 40rpx;
|
||||||
|
width: 630rpx;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remind-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #000000;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remind-message {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #000000;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remind-list {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
min-height: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remind-item {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 40rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remind-btns {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remind-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
color: #666666;
|
||||||
|
border: 1rpx solid #E5E7EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #256BFA;
|
||||||
|
color: #FFFFFF;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
223
pages/service/components/SkillDetailPopup.vue
Normal file
223
pages/service/components/SkillDetailPopup.vue
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<template>
|
||||||
|
<uni-popup
|
||||||
|
ref="popupRef"
|
||||||
|
type="bottom"
|
||||||
|
:borderRadius="'20rpx 20rpx 0 0'"
|
||||||
|
background-color="#FFFFFF"
|
||||||
|
maskBackgroundColor="rgba(255, 255, 255, 0.6)"
|
||||||
|
:isMaskClick="true"
|
||||||
|
@maskClick="handleClose"
|
||||||
|
>
|
||||||
|
<view class="skill-popup-content">
|
||||||
|
<!-- 头部:标题和关闭按钮 -->
|
||||||
|
<view class="popup-header">
|
||||||
|
<text class="popup-title">{{ jobTitle }}</text>
|
||||||
|
<view class="close-btn" @click="handleClose">
|
||||||
|
<uni-icons type="closeempty" size="20" color="#000000"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<scroll-view scroll-y class="popup-body" :show-scrollbar="false">
|
||||||
|
<view
|
||||||
|
class="skill-section"
|
||||||
|
v-for="(section, sIndex) in skillSections"
|
||||||
|
:key="sIndex"
|
||||||
|
>
|
||||||
|
<view class="section-header">
|
||||||
|
<view class="section-icon"></view>
|
||||||
|
<text class="section-title">{{ section.title }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="skill-list">
|
||||||
|
<view
|
||||||
|
class="skill-item"
|
||||||
|
v-for="(skill, index) in section.skills"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<text class="skill-name">{{ skill.name }}</text>
|
||||||
|
<view class="skill-dots">
|
||||||
|
<view
|
||||||
|
class="dot"
|
||||||
|
:class="{ 'active': i - 1 < skill.level }"
|
||||||
|
v-for="i in 6"
|
||||||
|
:key="i"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, defineExpose } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
jobTitle: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
possessedSkills: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
improvementSkills: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
|
const popupRef = ref(null);
|
||||||
|
|
||||||
|
// 技能区块数据
|
||||||
|
const skillSections = computed(() => [
|
||||||
|
{ title: '已具备技能', skills: props.possessedSkills },
|
||||||
|
{ title: '需要提升的技能', skills: props.improvementSkills }
|
||||||
|
]);
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
popupRef.value?.open('bottom');
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
popupRef.value?.close('bottom');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
close();
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open, close });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.skill-popup-content {
|
||||||
|
height: calc(100vh - 330rpx);
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 20rpx 20rpx 0 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 32rpx 28rpx;
|
||||||
|
border-bottom: 1rpx solid #F0F0F0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #000000;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
position: absolute;
|
||||||
|
right: 28rpx;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-body {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 28rpx 28rpx;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-section {
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-icon {
|
||||||
|
width: 24rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #9974FD 0%, #286BFA 100%);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #286BFA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-item {
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 64rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #000000;
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-dots {
|
||||||
|
display: flex;
|
||||||
|
gap: 8rpx;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 112rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #E0E0E0;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #286BFA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
285
pages/service/components/SkillDevelopment.vue
Normal file
285
pages/service/components/SkillDevelopment.vue
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
<template>
|
||||||
|
<view class="skill-development">
|
||||||
|
<view class="content-section">
|
||||||
|
<view class="section-title">
|
||||||
|
<uni-icons type="person-filled" size="18" color="#000000"></uni-icons>
|
||||||
|
<text class="title-text">技能发展路径</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="intro-text">
|
||||||
|
基于您的当前职业和目标职业,以下是您需要重点发展的技能:
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="skill-list">
|
||||||
|
<view
|
||||||
|
class="skill-item"
|
||||||
|
v-for="(skill, index) in skillList"
|
||||||
|
:key="index"
|
||||||
|
@click="handleSkillItemClick(skill)"
|
||||||
|
>
|
||||||
|
<view class="skill-header">
|
||||||
|
<text class="skill-name">{{ skill.name }}</text>
|
||||||
|
<text class="skill-weight">权重: {{ skill.weight }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="skill-tags">
|
||||||
|
<view
|
||||||
|
class="skill-tag"
|
||||||
|
v-for="(tag, tagIndex) in skill.tags"
|
||||||
|
:key="tagIndex"
|
||||||
|
>
|
||||||
|
{{ tag }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 技能权重弹出层 -->
|
||||||
|
<SkillWeightPopup
|
||||||
|
ref="skillWeightPopup"
|
||||||
|
:skill-name="selectedSkillName"
|
||||||
|
:weight="selectedSkillWeight"
|
||||||
|
:current-level="selectedSkillLevel"
|
||||||
|
@close="handlePopupClose"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import SkillWeightPopup from './SkillWeightPopup.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 来自职业推荐 tab 的数据
|
||||||
|
currentJobSkills: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
recommendedJobs: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
// 来自职业路径 tab 的数据
|
||||||
|
pathData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
start: { title: '', skills: [] },
|
||||||
|
steps: [],
|
||||||
|
end: { title: '', skills: [] }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
targetCareer: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 综合技能列表(基于前两个 tab 的数据)
|
||||||
|
const skillList = computed(() => {
|
||||||
|
const skillMap = new Map();
|
||||||
|
|
||||||
|
// 1. 收集当前职位技能(已有技能,权重较低)
|
||||||
|
props.currentJobSkills.forEach(skill => {
|
||||||
|
if (skill && skill.trim()) {
|
||||||
|
const existing = skillMap.get(skill) || { name: skill, weight: 0, tags: [], currentLevel: 0 };
|
||||||
|
existing.weight = Math.max(existing.weight, 0.3); // 已有技能权重较低
|
||||||
|
existing.tags.push('当前技能');
|
||||||
|
skillMap.set(skill, existing);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 收集推荐职位的技能(需要发展的技能,权重较高)
|
||||||
|
props.recommendedJobs.forEach(job => {
|
||||||
|
if (Array.isArray(job.skills)) {
|
||||||
|
job.skills.forEach(skill => {
|
||||||
|
if (skill && skill.trim()) {
|
||||||
|
const existing = skillMap.get(skill) || { name: skill, weight: 0, tags: [], currentLevel: 0 };
|
||||||
|
existing.weight = Math.max(existing.weight, 0.7); // 推荐职位技能权重较高
|
||||||
|
if (!existing.tags.includes('推荐技能')) {
|
||||||
|
existing.tags.push('推荐技能');
|
||||||
|
}
|
||||||
|
skillMap.set(skill, existing);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 收集职业路径中的技能(发展路径技能,权重最高)
|
||||||
|
const pathSkills = [];
|
||||||
|
if (props.pathData.start && Array.isArray(props.pathData.start.skills)) {
|
||||||
|
pathSkills.push(...props.pathData.start.skills);
|
||||||
|
}
|
||||||
|
if (Array.isArray(props.pathData.steps)) {
|
||||||
|
props.pathData.steps.forEach(step => {
|
||||||
|
if (Array.isArray(step.skills)) {
|
||||||
|
pathSkills.push(...step.skills);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (props.pathData.end && Array.isArray(props.pathData.end.skills)) {
|
||||||
|
pathSkills.push(...props.pathData.end.skills);
|
||||||
|
}
|
||||||
|
|
||||||
|
pathSkills.forEach(skill => {
|
||||||
|
if (skill && skill.trim()) {
|
||||||
|
const existing = skillMap.get(skill) || { name: skill, weight: 0, tags: [], currentLevel: 0 };
|
||||||
|
existing.weight = Math.max(existing.weight, 0.9); // 路径技能权重最高
|
||||||
|
if (!existing.tags.includes('路径技能')) {
|
||||||
|
existing.tags.push('路径技能');
|
||||||
|
}
|
||||||
|
skillMap.set(skill, existing);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 转换为数组并按权重排序
|
||||||
|
const result = Array.from(skillMap.values())
|
||||||
|
.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
weight: item.weight.toFixed(2),
|
||||||
|
tags: [...new Set(item.tags)], // 去重
|
||||||
|
currentLevel: item.currentLevel || Math.floor(item.weight * 5) + 1 // 根据权重计算等级
|
||||||
|
}))
|
||||||
|
.sort((a, b) => parseFloat(b.weight) - parseFloat(a.weight)); // 按权重降序
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 弹出层引用
|
||||||
|
const skillWeightPopup = ref(null);
|
||||||
|
|
||||||
|
// 选中的技能信息
|
||||||
|
const selectedSkillName = ref('');
|
||||||
|
const selectedSkillWeight = ref('');
|
||||||
|
const selectedSkillLevel = ref(0);
|
||||||
|
|
||||||
|
// 处理技能项点击
|
||||||
|
function handleSkillItemClick(skill) {
|
||||||
|
selectedSkillName.value = skill.name || '';
|
||||||
|
selectedSkillWeight.value = skill.weight || '0';
|
||||||
|
selectedSkillLevel.value = skill.currentLevel || 0;
|
||||||
|
skillWeightPopup.value?.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理弹出层关闭
|
||||||
|
function handlePopupClose() {
|
||||||
|
// 可以在这里处理关闭后的逻辑
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.skill-development {
|
||||||
|
padding: 10rpx 0 20rpx;
|
||||||
|
background-color: #EBF4FF;
|
||||||
|
min-height:95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-section {
|
||||||
|
background-color: #EBF4FF;
|
||||||
|
padding: 28rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
margin-top: -20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #167CF1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 34rpx;
|
||||||
|
color: rgba(154, 154, 154, 1);
|
||||||
|
text-align: left;
|
||||||
|
font-family: 'PingFangSC-Bold', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 90rpx;
|
||||||
|
width: 672rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-item {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 796rpx;
|
||||||
|
min-height: 162rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background-color: rgba(239, 239, 239, 1);
|
||||||
|
color: rgba(16, 16, 16, 1);
|
||||||
|
font-size: 28rpx;
|
||||||
|
text-align: center;
|
||||||
|
padding: 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: visible;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-name {
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 46rpx;
|
||||||
|
color: rgb(16, 16, 16);
|
||||||
|
text-align: left;
|
||||||
|
font-family: 'PingFangSC-Bold', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-weight {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 34rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
background-color: rgba(49, 100, 239, 0.1);
|
||||||
|
color: rgba(44, 101, 247, 1);
|
||||||
|
text-align: center;
|
||||||
|
padding: 6rpx 12rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-tag {
|
||||||
|
background-color: rgba(49, 100, 239, 0.1);
|
||||||
|
color: rgba(44, 101, 247, 1);
|
||||||
|
padding: 6rpx 12rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 34rpx;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
212
pages/service/components/SkillWeightPopup.vue
Normal file
212
pages/service/components/SkillWeightPopup.vue
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
<template>
|
||||||
|
<uni-popup
|
||||||
|
ref="popupRef"
|
||||||
|
type="bottom"
|
||||||
|
:borderRadius="'20rpx 20rpx 0 0'"
|
||||||
|
background-color="#FFFFFF"
|
||||||
|
maskBackgroundColor="rgba(255, 255, 255, 0.6)"
|
||||||
|
:isMaskClick="true"
|
||||||
|
@maskClick="handleClose"
|
||||||
|
>
|
||||||
|
<view class="skill-weight-popup-content">
|
||||||
|
<!-- 头部:标题和关闭按钮 -->
|
||||||
|
<view class="popup-header">
|
||||||
|
<text class="popup-title">{{ skillName }}</text>
|
||||||
|
<view class="close-btn" @click="handleClose">
|
||||||
|
<uni-icons type="closeempty" size="20" color="#000000"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<scroll-view scroll-y class="popup-body" :show-scrollbar="false">
|
||||||
|
<!-- 技能权重 -->
|
||||||
|
<view class="info-section">
|
||||||
|
<view class="section-header">
|
||||||
|
<view class="section-icon"></view>
|
||||||
|
<text class="section-title">技能权重</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="info-label">{{ skillName }}</text>
|
||||||
|
<text class="info-value">{{ weight }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 当前水平 -->
|
||||||
|
<view class="info-section">
|
||||||
|
<view class="section-header">
|
||||||
|
<view class="section-icon"></view>
|
||||||
|
<text class="section-title">当前水平</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="info-label">能力等级</text>
|
||||||
|
<view class="level-dots">
|
||||||
|
<view
|
||||||
|
class="dot"
|
||||||
|
:class="{ 'active': i - 1 < currentLevel }"
|
||||||
|
v-for="i in 5"
|
||||||
|
:key="i"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineExpose } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
skillName: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
weight: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
currentLevel: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
|
const popupRef = ref(null);
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
popupRef.value?.open('bottom');
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
popupRef.value?.close('bottom');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
close();
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open, close });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.skill-weight-popup-content {
|
||||||
|
height: calc(100vh - 330rpx);
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 20rpx 20rpx 0 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 32rpx 28rpx;
|
||||||
|
border-bottom: 1rpx solid #F0F0F0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #000000;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
position: absolute;
|
||||||
|
right: 28rpx;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-body {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 28rpx 28rpx;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-icon {
|
||||||
|
width: 24rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #9974FD 0%, #286BFA 100%);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #286BFA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #000000;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #286BFA;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-dots {
|
||||||
|
display: flex;
|
||||||
|
gap: 8rpx;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #E0E0E0;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #286BFA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
35
project.config.json
Normal file
35
project.config.json
Normal 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"
|
||||||
|
}
|
||||||
22
project.private.config.json
Normal file
22
project.private.config.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
static/icon/antOutline.png
Normal file
BIN
static/icon/antOutline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 308 B |
53
unpackage/dist/dev/mp-weixin/project.config.json
vendored
53
unpackage/dist/dev/mp-weixin/project.config.json
vendored
@@ -1,36 +1,35 @@
|
|||||||
{
|
{
|
||||||
"description": "项目配置文件。",
|
|
||||||
"packOptions": {
|
|
||||||
"ignore": []
|
|
||||||
},
|
|
||||||
"setting": {
|
"setting": {
|
||||||
"urlCheck": false,
|
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"postcss": true,
|
"postcss": true,
|
||||||
"minified": true,
|
"minified": true,
|
||||||
"newFeature": true,
|
"uglifyFileName": false,
|
||||||
"bigPackageSizeSupport": true
|
"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",
|
"compileType": "miniprogram",
|
||||||
"libVersion": "3.5.7",
|
"simulatorPluginLibVersion": {},
|
||||||
|
"packOptions": {
|
||||||
|
"ignore": [],
|
||||||
|
"include": []
|
||||||
|
},
|
||||||
"appid": "wx4aa34488b965a331",
|
"appid": "wx4aa34488b965a331",
|
||||||
"projectname": "qingdao-employment-service",
|
"editorSetting": {},
|
||||||
"condition": {
|
"libVersion": "3.11.1"
|
||||||
"search": {
|
|
||||||
"current": -1,
|
|
||||||
"list": []
|
|
||||||
},
|
|
||||||
"conversation": {
|
|
||||||
"current": -1,
|
|
||||||
"list": []
|
|
||||||
},
|
|
||||||
"game": {
|
|
||||||
"current": -1,
|
|
||||||
"list": []
|
|
||||||
},
|
|
||||||
"miniprogram": {
|
|
||||||
"current": -1,
|
|
||||||
"list": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* @Date: 2023-07-27 15:55:08
|
* @Date: 2023-07-27 15:55:08
|
||||||
* @LastEditors: lip
|
* @LastEditors: lip
|
||||||
*/
|
*/
|
||||||
const TokenKey = 'App-Token'
|
const TokenKey = 'token' // 与微信登录时存储的 key 保持一致
|
||||||
|
|
||||||
export function getToken() {
|
export function getToken() {
|
||||||
return uni.getStorageSync(TokenKey)
|
return uni.getStorageSync(TokenKey)
|
||||||
|
|||||||
@@ -5,9 +5,25 @@
|
|||||||
* @LastEditors: lip
|
* @LastEditors: lip
|
||||||
*/
|
*/
|
||||||
// 应用全局配置
|
// 应用全局配置
|
||||||
|
import config from '@/config.js'
|
||||||
|
|
||||||
let exports = {
|
let exports = {
|
||||||
baseUrl: 'http://222.80.110.161:11111/sdrc-api', // 正式环境在济南人才上部署(不要轻易连接)
|
// 引用根目录config.js的baseUrl,避免重复配置
|
||||||
// baseUrl: 'http://172.20.0.177:8903', // 正式环境在济南人才上部署(不要轻易连接)
|
baseUrl: config.baseUrl,
|
||||||
|
|
||||||
|
// baseUrl: 'http://127.0.0.1:8903', // 本地开发
|
||||||
|
|
||||||
|
// baseUrl: 'http://172.20.0.104:8903', // xubaiqi 地址
|
||||||
|
|
||||||
|
// baseUrl: 'http://172.20.1.76:8903', // 演示环境内网
|
||||||
|
|
||||||
|
// baseUrl: 'http://36.140.162.216:8904/prod-api', // 演示环境外网
|
||||||
|
|
||||||
|
// baseUrl: 'http://10.160.0.5:8903', // 演示环境外网
|
||||||
|
|
||||||
|
// baseUrl: 'http://111.34.80.140:8081/prod-api', // 正式环境(不要轻易连接)
|
||||||
|
// baseUrl: 'http://ks.zhaopinzao8dian.com/api/ks', // 已从根目录config.js引用,不再重复配置
|
||||||
|
zytpBaseUrl: 'http://ks.zhaopinzao8dian.com/api/ks_zytp/admin-api/zytp',
|
||||||
|
|
||||||
// 应用信息
|
// 应用信息
|
||||||
appInfo: {
|
appInfo: {
|
||||||
|
|||||||
@@ -12,30 +12,59 @@ import { toast, showConfirm, tansParams } from '@/utilsRc/common'
|
|||||||
|
|
||||||
let timeout = 10000
|
let timeout = 10000
|
||||||
const baseUrl = configRc.baseUrl
|
const baseUrl = configRc.baseUrl
|
||||||
|
const zytpBaseUrl = configRc.zytpBaseUrl || ''
|
||||||
|
|
||||||
const request = config => {
|
const request = config => {
|
||||||
// 是否需要设置 token
|
// 是否需要设置 token
|
||||||
const isToken = (config.headers || {}).isToken === false
|
const isToken = (config.headers || {}).isToken === false
|
||||||
config.header = config.header || {}
|
config.header = config.header || {}
|
||||||
if (getToken() && !isToken) {
|
// 从存储中获取微信登录的 token
|
||||||
config.header['Authorization'] = 'Bearer ' + getToken()
|
const token = getToken()
|
||||||
|
if (token && !isToken) {
|
||||||
|
config.header['Authorization'] = 'Bearer ' + token
|
||||||
}
|
}
|
||||||
// config.header['Authorization'] = 'Bearer ' + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJzeXNfdXNlcjoxIiwicm5TdHIiOiJGUVl3YmRUalAzQ1BMNGFVc0RxeGJmdjAyT3JMZllDVSIsInVzZXJJZCI6MX0.kSOXY2QJQPbfjE0Yx2R3S8yQciA33OZBS9xJtr7cQ1A'
|
|
||||||
// get请求映射params参数
|
// 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) {
|
if (config.params) {
|
||||||
let url = config.url + '?' + tansParams(config.params)
|
let url = tansParams(config.params)
|
||||||
url = url.slice(0, -1)
|
url = url.slice(0, -1)
|
||||||
config.url = url
|
if (url) {
|
||||||
|
requestUrl += (requestUrl.includes('?') ? '&' : '?') + url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是 getJobPathById 接口,打印完整 URL
|
||||||
|
if (config.url && config.url.includes('getJobPathById')) {
|
||||||
|
console.log('[请求URL] getJobPathById 完整请求URL:', requestUrl);
|
||||||
|
console.log('[请求URL] baseUrl:', requestBaseUrl);
|
||||||
|
console.log('[请求URL] 接口路径:', config.url);
|
||||||
|
console.log('[请求URL] 请求参数:', config.params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是 recommendJob 接口,打印详细信息
|
||||||
|
if (config.url && config.url.includes('recommendJob')) {
|
||||||
|
console.log('[请求URL] recommendJob 完整请求URL:', requestUrl);
|
||||||
|
console.log('[请求URL] baseUrl:', requestBaseUrl);
|
||||||
|
console.log('[请求URL] 接口路径:', config.url);
|
||||||
|
console.log('[请求URL] 请求方法:', config.method);
|
||||||
|
console.log('[请求URL] 请求参数 (params):', config.params);
|
||||||
|
console.log('[请求URL] 请求数据 (data):', config.data);
|
||||||
|
console.log('[请求URL] Content-Type:', config.header?.['content-type']);
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
uni.request({
|
uni.request({
|
||||||
method: config.method || 'get',
|
method: config.method || 'get',
|
||||||
timeout: config.timeout || timeout,
|
timeout: config.timeout || timeout,
|
||||||
url: baseUrl + config.url,
|
url: requestUrl,
|
||||||
// url: 'https://gccrcdh.sd-talent.cn:80/zhq' + config.url,
|
// url: 'https://gccrcdh.sd-talent.cn:80/zhq' + config.url,
|
||||||
data: config.data,
|
data: config.data,
|
||||||
header: config.header,
|
header: config.header,
|
||||||
dataType: 'json'
|
dataType: 'json',
|
||||||
|
responseType: config.responseType || 'text'
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
let res = response.data
|
let res = response.data
|
||||||
let error = response.errMsg!='request:ok'
|
let error = response.errMsg!='request:ok'
|
||||||
@@ -44,10 +73,11 @@ const request = config => {
|
|||||||
reject('网络出小差,请稍后再试')
|
reject('网络出小差,请稍后再试')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const code = res.code || 200
|
// 处理 code,0 和 200 都表示成功
|
||||||
|
const code = res.code !== undefined && res.code !== null ? res.code : 200
|
||||||
const msg = errorCode[code] || res.msg || errorCode['default']
|
const msg = errorCode[code] || res.msg || errorCode['default']
|
||||||
const isShowModel = getApp().globalData.isShowModel
|
const isShowModel = getApp().globalData.isShowModel
|
||||||
if (code == 200) {
|
if (code === 200 || code === 0) {
|
||||||
resolve(res)
|
resolve(res)
|
||||||
}else if (code === 401) {
|
}else if (code === 401) {
|
||||||
if(isShowModel === false) {
|
if(isShowModel === false) {
|
||||||
@@ -80,12 +110,15 @@ const request = config => {
|
|||||||
else if (code === 500) {
|
else if (code === 500) {
|
||||||
toast(msg)
|
toast(msg)
|
||||||
reject('500')
|
reject('500')
|
||||||
} else if (code !== 200 && code !== 9999) {
|
} else if (code !== 200 && code !== 0 && code !== 9999) {
|
||||||
// 9999是正常的业务状态码,不应该被当作错误处理
|
// 9999是正常的业务状态码,不应该被当作错误处理
|
||||||
|
// 0 和 200 都表示成功
|
||||||
toast(msg)
|
toast(msg)
|
||||||
reject(code)
|
reject(code)
|
||||||
}
|
}
|
||||||
resolve(res.data)
|
// 如果 code 是 0 或 200,返回完整响应对象,而不是 res.data
|
||||||
|
// 因为有些接口的 data 在 res.data 中,有些在 res 中
|
||||||
|
resolve(res)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
let message = error.errMsg
|
let message = error.errMsg
|
||||||
|
|||||||
Reference in New Issue
Block a user