Files
ks-app-employment-service/packageA/pages/post/post.vue
2025-09-29 11:53:10 +08:00

743 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<AppLayout title="" backGorundColor="#F4F4F4">
<template #headerleft>
<view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image>
</view>
</template>
<template #headerright>
<!-- <view class="btnshare">
<image src="@/static/icon/share.png" @click="shareJob"></image>
</view> -->
<view class="btn mar_ri10">
<image src="@/static/icon/collect3.png" v-if="!jobInfo.isCollection" @click="jobCollection"></image>
<image src="@/static/icon/collect2.png" v-else @click="jobCollection"></image>
</view>
</template>
<view class="content" v-show="!isEmptyObject(jobInfo)">
<view class="content-top btn-feel">
<view class="top-salary">
<Salary-Expectation
:max-salary="jobInfo.maxSalary"
:min-salary="jobInfo.minSalary"
:is-month="true"
></Salary-Expectation>
</view>
<view class="top-name">{{ jobInfo.jobTitle }}</view>
<view class="top-info">
<view class="info-img"><image src="/static/icon/post12.png"></image></view>
<view class="info-text">
<dict-Label dictType="experience" :value="jobInfo.experience"></dict-Label>
</view>
<view class="info-img mar_le20"><image src="/static/icon/post13.png"></image></view>
<view class="info-text">
<dict-Label dictType="education" :value="jobInfo.education"></dict-Label>
</view>
</view>
<view class="position-source">
<text>来源&nbsp;</text>
{{ jobInfo.dataSource }}
</view>
</view>
<view class="ai-explain" v-if="jobInfo.isExplain">
<view class="exbg">
<view class="explain-left btn-shaky">
<view class="leftText">AI+智能讲解职位一听就懂</view>
<view class="leftdownText">懒得看文字我用AI讲给你听</view>
</view>
<view class="explain-right button-click" @click="seeExplain">点击查看</view>
</view>
</view>
<view class="content-card">
<view class="card-title">
<text class="title">职位描述</text>
</view>
<view class="description" :style="{ whiteSpace: 'pre-wrap' }">
{{ jobInfo.description }}
</view>
</view>
<!-- 公司信息 -->
<view class="content-card">
<view class="card-title">
<text class="title">公司信息</text>
<text
class="btntext button-click"
@click="navTo(`/packageA/pages/UnitDetails/UnitDetails?companyId=${jobInfo.company.companyId}`)"
>
单位详情
</text>
</view>
<view class="company-info">
<view class="companyinfo-left">
<image src="@/static/icon/companyIcon.png" mode=""></image>
</view>
<view class="companyinfo-right">
<view class="row1">{{ jobInfo.company?.name }}</view>
<view class="row2">
<dict-tree-Label
v-if="jobInfo.company?.industry"
dictType="industry"
:value="jobInfo.company?.industry"
></dict-tree-Label>
<span v-if="jobInfo.company?.industry">&nbsp;</span>
<dict-Label dictType="scale" :value="jobInfo.company?.scale"></dict-Label>
</view>
<view class="row2">
<text>在招</text>
<text style="color: #256bfa">{{ companyCount }}</text>
<text>个职位</text>
</view>
</view>
</view>
<view class="company-map" v-if="jobInfo.latitude && jobInfo.longitude">
<map
style="width: 100%; height: 100%"
:latitude="jobInfo.latitude"
:longitude="jobInfo.longitude"
:markers="mapCovers"
></map>
</view>
</view>
<view class="content-card" v-if="!userInfo.isCompanyUser">
<view class="card-title">
<text class="title">竞争力分析</text>
</view>
<view class="description">
三个月内共15位求职者申请你的简历匹配度为{{ raderData.matchScore }}排名位于第{{
raderData.rank
}}超过{{ raderData.percentile }}%的竞争者处在优秀位置
</view>
<RadarMap :value="raderData"></RadarMap>
<view class="card-footer">
<view class="footer-title">你与职位的匹配度</view>
<view class="footer-content">
<view class="progress-container">
<view
v-for="(item, index) in matchingDegree"
:key="index"
class="progress-item"
:class="getClass(index)"
/>
</view>
</view>
<view class="progress-text">
<view class="text-rpx" v-for="(item, index) in matchingDegree" :key="index">{{ item }}</view>
</view>
</view>
</view>
<view class="content-card" v-else>
<view class="card-title">
<view class="title">申请人列表</view>
</view>
<view class="applicant-list">
<view v-for="applicant in applicants" :key="applicant.userId" class="applicant-item">
<view class="item-header">
<view class="name">{{ applicant.name }}</view>
<view class="right-header">
<view class="matching-degree">匹配度{{ applicant.matchingDegree }}</view>
<button class="resume-button" @click="viewResume(applicant.userId)">查看简历</button>
</view>
</view>
<view class="item-details">
<view class="detail-text">
<view class="label">年龄{{ applicant.age }}</view>
</view>
<view class="detail-text">
<view class="label">
学历
<dict-Label dictType="education" :value="applicant.education"></dict-Label>
</view>
</view>
<view class="detail-text">
<view class="label">
经验
<dict-Label dictType="experience" :value="applicant.experience"></dict-Label>
</view>
</view>
<view class="detail-text">
<view class="label">
期望薪资
<Salary-Expectation
style="display: inline-block"
:max-salary="applicant.maxSalary"
:min-salary="applicant.minSalary"
:is-month="true"
></Salary-Expectation>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view style="height: 34px"></view>
<template #footer>
<view class="footer">
<view class="btn-wq button-click" @click="jobApply">立即前往</view>
</view>
</template>
<VideoPlayer ref="videoPalyerRef" />
</AppLayout>
</template>
<script setup>
import point from '@/static/icon/point.png';
import VideoPlayer from './component/videoPlayer.vue';
import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
import dictLabel from '@/components/dict-Label/dict-Label.vue';
import RadarMap from './component/radarMap.vue';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { userInfo } = storeToRefs(useUserStore());
const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction');
import config from '@/config.js';
const matchingDegree = ref(['一般', '良好', '优秀', '极好']);
const currentStep = ref(1);
const companyCount = ref(0);
const jobInfo = ref({});
const state = reactive({});
const mapCovers = ref([]);
const jobIdRef = ref();
const raderData = ref({});
const videoPalyerRef = ref(null);
const explainUrlRef = ref('');
const applicants = ref([
{
createTime: null,
userId: 1,
name: '青岛测试账号331',
age: '28', // 假设年龄有值
sex: '1',
birthDate: null,
education: '4',
politicalAffiliation: '',
phone: '',
avatar: '',
salaryMin: '10000',
salaryMax: '15000',
area: '3',
status: '0',
loginIp: '',
loginDate: null,
jobTitleId: '157,233,373',
experience: '3',
isRecommend: 1,
jobTitle: ['人力资源专员/助理', 'Java', '运维工程师'],
applyDate: '2025-09-26',
matchingDegree: 1,
},
]);
onLoad((option) => {
if (option.jobId) {
initLoad(option);
}
});
onShow(() => {
const option = parseQueryParams(); // 兼容微信内置浏览器
if (option.jobId) {
initLoad(option);
}
});
function initLoad(option) {
const jobId = atob(option.jobId);
if (jobId !== jobIdRef.value) {
jobIdRef.value = jobId;
getDetail(jobId);
}
}
function seeExplain() {
if (jobInfo.value.explainUrl) {
videoPalyerRef.value?.open(jobInfo.value.explainUrl);
// console.log(jobInfo.value.explainUrl);
// explainUrlRef.value = jobInfo.value.explainUrl;
}
}
function getDetail(jobId) {
return new Promise((reslove, reject) => {
$api.createRequest(`/app/job/${jobId}`).then((resData) => {
const { latitude, longitude, companyName, companyId, isCompanyUser } = resData.data;
jobInfo.value = resData.data;
reslove(resData.data);
getCompanyIsAJobs(companyId);
if (isCompanyUser) {
getCompetivetuveness(jobId);
}
// getCompetivetuveness(jobId);
if (latitude && longitude) {
mapCovers.value = [
{
latitude: latitude,
longitude: longitude,
iconPath: point,
label: {
content: companyName,
textAlign: 'center',
padding: 3,
fontSize: 12,
bgColor: '#FFFFFF',
anchorX: getTextWidth(companyName), // X 轴调整,负数向左
borderRadius: 5,
},
width: 34,
},
];
}
});
});
}
function getCompanyIsAJobs(companyId) {
$api.createRequest(`/app/company/count/${companyId}`).then((resData) => {
companyCount.value = resData.data;
});
}
function getTextWidth(text, size = 12) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.font = `${12}px Arial`;
return -(context.measureText(text).width / 2) - 20; // 计算文字中心点
}
function getCompetivetuveness(jobId) {
$api.createRequest(`/app/job/competitiveness/${jobId}`, {}, 'GET').then((resData) => {
raderData.value = resData.data;
currentStep.value = resData.data.matchScore * 0.04;
});
}
// 申请岗位
function jobApply() {
const jobId = jobInfo.value.jobId;
if (jobInfo.value.isApply) {
const jobUrl = jobInfo.value.jobUrl;
return window.open(jobUrl);
} else {
$api.createRequest(`/app/job/apply/${jobId}`, {}, 'GET').then((resData) => {
getDetail(jobId);
$api.msg('申请成功');
const jobUrl = jobInfo.value.jobUrl;
return window.open(jobUrl);
});
}
}
// 取消/收藏岗位
function jobCollection() {
const jobId = jobInfo.value.jobId;
if (jobInfo.value.isCollection) {
$api.createRequest(`/app/job/collection/${jobId}`, {}, 'DELETE').then((resData) => {
getDetail(jobId);
$api.msg('取消收藏成功');
});
} else {
$api.createRequest(`/app/job/collection/${jobId}`, {}, 'POST').then((resData) => {
getDetail(jobId);
$api.msg('收藏成功');
});
}
}
function getClass(index) {
const current = currentStep.value;
const floorIndex = Math.floor(current);
if (index < floorIndex) {
return 'active';
} else if (index === floorIndex) {
const decimal = current % 1;
const percent = Math.round(decimal * 100);
return `half${percent}`;
} else {
return '';
}
}
</script>
<style lang="stylus" scoped>
.btnback{
width: 64rpx;
height: 64rpx;
}
.btn {
display: flex;
justify-content: space-between;
align-items: center;
width: 52rpx;
height: 52rpx;
}
.btnshare {
width: 48rpx;
height: 48rpx;
margin-right: 46rpx;
}
image {
height: 100%;
width: 100%;
}
.progress-container {
display: flex;
align-items: center;
gap: 8rpx; /* 间距 */
margin-top: 24rpx
}
.progress-text{
margin-top: 8rpx
display: flex;
align-items: center;
gap: 8rpx; /* 间距 */
justify-content: space-around
width: 100%
font-weight: 400;
font-size: 20rpx;
color: #999999;
text-align: center;
}
.progress-item {
width: 25%;
height: 24rpx;
background-color: #eee;
border-radius: 24rpx;
overflow: hidden;
position: relative;
transition: background-color 0.3s;
}
/* 完整激活格子 */
.progress-item.active {
background: linear-gradient(to right, #256bfa, #8c68ff);
}
/* 当前进度进行中的格子 */
for i in 0..100
.progress-item.half{i}::before
content ''
position absolute
left 0
top 0
bottom 0
width 100%
background linear-gradient(to right, #256bfa (i)%, #eaeaea (i)%)
border-radius 24rpx
.card-footer{
.footer-title{
font-weight: 600;
font-size: 28rpx;
color: #000000;
}
.footer-content{
.content-line{
display: grid
grid-template-columns: repeat(4, 1fr)
background: linear-gradient( to left, #9E74FD 0%, #256BFA 100%);
border-radius: 10rpx
.line-pargrah{
height: 20rpx;
position: relative
}
.line-pargrah::after{
position: absolute;
content: '';
right: 10
top: 0
width: 6rpx
height: 20rpx
background: #FFFFFF
}
}
}
}
// ai
.ai-explain{
margin-top: 28rpx
background-color: #FFFFFF
border-radius: 20rpx 20rpx 20rpx 20rpx;
overflow: hidden
.exbg{
padding: 28rpx 40rpx;
display: flex
align-items: center
justify-content: space-between
background: url('@/static/icon/aibg.png') center center no-repeat;
background-size: 100% 100%
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}
.explain-left{
.leftText{
font-weight: 600;
font-size: 32rpx;
}
.leftdownText{
margin-top: 12rpx
font-weight: 400;
font-size: 28rpx;
color: #495265;
}
}
.explain-right{
width: 168rpx;
height: 72rpx;
border-radius: 40rpx 40rpx 40rpx 40rpx;
border: 2rpx solid #E7E9ED;
text-align: center;
line-height: 72rpx
font-weight: 400;
font-size: 28rpx;
color: #333333;
}
}
.content{
padding: 0 28rpx
height: 100%
.content-top{
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
padding: 52rpx 32rpx 34rpx 32rpx
position: relative
overflow: hidden
.top-salary{
font-weight: 500;
font-size: 32rpx;
color: #4C6EFB;
}
.top-name{
font-weight: 600;
font-size: 36rpx;
color: #000000;
margin-top: 8rpx
}
.top-info{
margin-top: 22rpx
display: flex;
align-items: center
.info-img{
width: 40rpx;
height: 40rpx
}
.info-text{
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
margin-left: 10rpx
}
}
.position-source{
position: absolute
top: 0
right: 0
width: fit-content;
height: 76rpx;
padding: 0 24rpx
background: rgba(37,107,250,0.1);
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
text-align: center
line-height: 76rpx
font-weight: 400;
font-size: 28rpx;
color: #64779F;
border-bottom-left-radius: 20rpx
}
}
.content-card{
padding: 24rpx
margin-top: 28rpx
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
.card-title{
font-weight: 600;
font-size: 32rpx;
color: #000000;
position: relative;
display: flex
justify-content: space-between
.title{
position: relative;
z-index: 2;
}
.btntext{
white-space: nowrap
font-weight: 400;
font-size: 28rpx;
color: #256BFA;
}
}
.card-title::before{
position: absolute
content: '';
left: -14rpx
bottom: 0
height: 16rpx;
width: 108rpx;
background: linear-gradient(to right, #CBDEFF, #FFFFFF);
border-radius: 8rpx;
z-index: 1;
}
.description{
margin-top: 30rpx
font-weight: 400;
font-size: 28rpx;
color: #495265;
}
.company-info{
padding-top: 30rpx
display: flex
flex-direction: row
flex-wrap: nowrap
.companyinfo-left{
width: 96rpx;
height: 96rpx;
margin-right: 24rpx
}
.companyinfo-right{
.row1{
font-weight: 500;
font-size: 32rpx;
color: #333333;
}
.row2{
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
line-height: 45rpx;
}
}
}
.company-map{
margin-top: 28rpx
height: 416rpx;
border-radius: 20rpx 20rpx 20rpx 20rpx;
overflow: hidden
}
}
}
.footer{
background: #FFFFFF;
box-shadow: 0rpx -4rpx 24rpx 0rpx rgba(11,44,112,0.12);
border-radius: 0rpx 0rpx 0rpx 0rpx;
padding: 40rpx 28rpx 20rpx 28rpx
.btn-wq{
height: 90rpx;
background: #256BFA;
border-radius: 12rpx 12rpx 12rpx 12rpx;
font-weight: 500;
font-size: 32rpx;
color: #FFFFFF;
text-align: center;
line-height: 90rpx
}
}
.content-card {
background-color: #fff;
border-radius: 8px;
margin: 10px;
padding: 15px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card-title {
padding-bottom: 10px;
border-bottom: 1px solid #eee;
margin-bottom: 10px;
}
.title {
font-size: 18px;
font-weight: bold;
color: #333;
}
.applicant-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.applicant-item {
padding: 15px;
background-color: #fafafa;
border-radius: 8px;
border: 1px solid #f0f0f0;
}
.item-header {
display: flex;
justify-content: space-between; /* 名字和右侧部分分开 */
align-items: center;
margin-bottom: 10px;
}
.right-header {
display: flex;
align-items: center;
gap: 10px;
}
.name {
font-size: 16px;
font-weight: bold;
max-width: 100px; /* 限制名字的最大宽度,根据你的布局调整 */
overflow: hidden; /* 隐藏超出部分 */
white-space: nowrap; /* 不换行 */
text-overflow: ellipsis; /* 显示省略号 */
}
.matching-degree {
font-size: 14px;
color: #4CAF50;
font-weight: 500;
}
.resume-button {
font-size: 12px;
padding: 5px 10px;
border-radius: 20px;
border: 1px solid #007aff;
color: #007aff;
background-color: transparent;
line-height: 1;
height: auto;
}
.resume-button::after {
border: none;
}
.item-details {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.detail-text {
font-size: 14px;
color: #666;
/* 确保标签和值在同一行 */
display: flex;
white-space: nowrap;
}
.label {
font-weight: bold;
color: #333;
}
</style>