Files
ks-app-employment-service/pages/index/index.vue
2025-04-07 09:10:55 +08:00

576 lines
20 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>
<view class="app-container">
<view class="index-AI">AI+就业服务程序</view>
<view class="index-option">
<view class="option-left">
<view class="left-item" @click="navTo('/pages/nearby/nearby')">附近</view>
<view class="left-item" @click="navTo('/packageA/pages/choiceness/choiceness')">精选</view>
<view class="left-item">职业图谱</view>
</view>
<view class="option-right">
<input
class="uni-input right-input"
adjust-position="false"
confirm-type="search"
v-model="inputText"
@confirm="searchJobTitle"
/>
<uni-icons
class="iconsearch"
color="#FFFFFF"
type="search"
size="20"
@click="searchJobTitle"
></uni-icons>
</view>
</view>
<!-- tab -->
<view class="tab-options">
<scroll-view :scroll-x="true" :show-scrollbar="false" class="tab-scroll">
<view class="tab-op-left">
<view
class="tab-list"
:class="{ tabchecked: state.tabIndex === 'all' }"
@click="choosePosition('all')"
>
全部
</view>
<view
class="tab-list"
:class="{ tabchecked: state.tabIndex === index }"
v-for="(item, index) in userInfo.jobTitle"
:key="index"
@click="choosePosition(index)"
>
{{ item }}
</view>
</view>
</scroll-view>
<view class="tab-op-right">
<uni-icons type="plusempty" style="margin-right: 10rpx" size="20" @click="editojobs"></uni-icons>
<view class="tab-recommend">
<latestHotestStatus @confirm="handelHostestSearch"></latestHotestStatus>
</view>
<view class="tab-filter" @click="showFilter = true">
<view class="tab-number" v-show="pageState.total">{{ formatTotal(pageState.total) }}</view>
<image class="image" src="/static/icon/filter.png"></image>
</view>
</view>
</view>
<!-- waterfalls -->
<scroll-view :scroll-y="true" class="falls-scroll" @scrolltolower="scrollBottom">
<view class="falls">
<custom-waterfalls-flow ref="waterfallsFlowRef" :value="list">
<template v-slot:default="job">
<view class="item" v-if="!job.recommend">
<view class="falls-card" @click="nextDetail(job)">
<view class="falls-card-title">{{ job.jobTitle }}</view>
<view class="falls-card-pay">
<view class="pay-text">
<Salary-Expectation
:max-salary="job.maxSalary"
:min-salary="job.minSalary"
:is-month="true"
></Salary-Expectation>
</view>
<image v-if="job.isHot" class="flame" src="/static/icon/flame.png"></image>
</view>
<view class="falls-card-education" v-if="job.education">
<dict-Label dictType="education" :value="job.education"></dict-Label>
</view>
<view class="falls-card-experience" v-if="job.experience">
<dict-Label dictType="experience" :value="job.experience"></dict-Label>
</view>
<view class="falls-card-company">{{ job.companyName }}</view>
<view class="falls-card-company">
青岛
<dict-Label dictType="area" :value="job.jobLocationAreaCode"></dict-Label>
</view>
<view class="falls-card-pepleNumber">
<view>{{ job.postingDate || '发布日期' }}</view>
<view>{{ vacanciesTo(job.vacancies) }}</view>
</view>
<view class="falls-card-matchingrate">
<view class=""><matchingDegree :job="job"></matchingDegree></view>
<uni-icons type="star" size="30"></uni-icons>
<!-- <uni-icons type="star-filled" color="#FFCB47" size="30"></uni-icons> -->
</view>
</view>
</view>
<view class="item" v-else>
<view class="recommend-card">
<view class="card-content">
<view class="recommend-card-title">在找{{ job.jobCategory }}工作吗</view>
<view class="recommend-card-tip">{{ job.tip }}</view>
<view class="recommend-card-controll">
<view class="controll-yes" @click="findJob(job)">是的</view>
<view class="controll-no">不是</view>
</view>
</view>
</view>
</view>
</template>
</custom-waterfalls-flow>
<loadmore ref="loadmoreRef"></loadmore>
</view>
</scroll-view>
<screeningJobRequirementsVue
v-model:show="showFilter"
@confirm="handleFilterConfirm"
></screeningJobRequirementsVue>
<!-- 岗位推荐组件 -->
<modify-expected-position-vue v-model:show="showModel" :jobList="jobList" />
</view>
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted, getCurrentInstance } from 'vue';
import img from '@/static/icon/filter.png';
import dictLabel from '@/components/dict-Label/dict-Label.vue';
const { $api, navTo, vacanciesTo, formatTotal } = inject('globalFunction');
import { onLoad, onShow } from '@dcloudio/uni-app';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { userInfo } = storeToRefs(useUserStore());
import useDictStore from '@/stores/useDictStore';
const { getTransformChildren } = useDictStore();
import screeningJobRequirementsVue from '@/components/screening-job-requirements/screening-job-requirements.vue';
import modifyExpectedPositionVue from '@/components/modifyExpectedPosition/modifyExpectedPosition.vue';
import { useRecommedIndexedDBStore, jobRecommender } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore();
const waterfallsFlowRef = ref(null);
const loadmoreRef = ref(null);
const state = reactive({
tabIndex: 'all',
search: '',
});
const list = ref([]);
const pageState = reactive({
page: 0,
total: 0,
maxPage: 2,
pageSize: 10,
search: {
order: 0,
},
});
const inputText = ref('');
const showFilter = ref(false);
const showModel = ref(false);
const jobList = ref([
{ name: '销售顾问', highlight: true },
{ name: '销售管理', highlight: true },
{ name: '销售工程师', highlight: true },
{ name: '算法工程师', highlight: false },
{ name: '生产经理', highlight: false },
{ name: '市场策划', highlight: false },
{ name: '商务服务', highlight: false },
{ name: '客服', highlight: false },
{ name: '创意总监', highlight: false },
]);
onLoad(() => {
getJobRecommend('refresh');
});
function nextDetail(job) {
// 记录岗位类型,用作数据分析
if (job.jobCategory) {
const recordData = recommedIndexDb.JobParameter(job);
recommedIndexDb.addRecord(recordData);
}
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
}
function handleModelConfirm(val) {
console.log(val);
}
function findJob(item) {
console.log(item);
}
function handleFilterConfirm(val) {
pageState.search = {
order: pageState.search.order,
};
for (const [key, value] of Object.entries(val)) {
pageState.search[key] = value.join(',');
}
getJobList('refresh');
}
function handelHostestSearch(val) {
pageState.search.order = val.value;
getJobList('refresh');
}
function scrollBottom() {
loadmoreRef.value.change('loading');
if (state.tabIndex === 'all') {
getJobRecommend();
} else {
getJobList();
}
}
function editojobs() {
console.log('jobs');
showModel.value = true;
}
function choosePosition(index) {
state.tabIndex = index;
list.value = [];
if (index === 'all') {
pageState.search = {};
inputText.value = '';
getJobRecommend('refresh');
} else {
// const id = useUserStore().userInfo.jobTitleId.split(',')[index];
pageState.search.jobTitle = useUserStore().userInfo.jobTitle[index];
inputText.value = '';
getJobList('refresh');
}
}
function searchJobTitle() {
state.tabIndex = '-1';
pageState.search = {
jobTitle: inputText.value,
};
getJobList('refresh');
}
function changeRecommend() {}
function changeLatestHotestStatus(e) {}
function getJobRecommend(type = 'add') {
if (type === 'refresh') {
list.value = [];
if (waterfallsFlowRef.value) waterfallsFlowRef.value.refresh();
}
let params = {
pageSize: pageState.pageSize,
sessionId: useUserStore().seesionId,
...pageState.search,
};
let comd = { recommend: true, jobCategory: '', tip: '确认你的兴趣,为您推荐更多合适的岗位' };
$api.createRequest('/app/job/recommend', params).then((resData) => {
const { data, total } = resData;
pageState.total = 0;
if (type === 'add') {
// 记录系统
recommedIndexDb.getRecord().then((res) => {
if (res.length) {
// 数据分析系统
const resultData = recommedIndexDb.analyzer(res);
const { sort, result } = resultData;
// 岗位询问系统
const conditionCounts = Object.fromEntries(
sort.filter((item) => item[1] > 1) // 过滤掉次数为 1 的项
);
jobRecommender.updateConditions(conditionCounts);
const question = jobRecommender.getNextQuestion();
if (question) {
comd.jobCategory = question;
data.unshift(comd);
}
}
const reslist = dataToImg(data);
list.value.push(...reslist);
});
} else {
list.value = dataToImg(data);
}
// 切换状态
if (data.length < pageState.pageSize) {
loadmoreRef.value.change('noMore');
} else {
loadmoreRef.value.change('more');
}
// 当没有岗位刷新sessionId重新啦
if (!data.length) {
useUserStore().initSeesionId();
}
});
}
function getJobList(type = 'add') {
if (type === 'add' && pageState.page < pageState.maxPage) {
pageState.page += 1;
}
if (type === 'refresh') {
list.value = [];
pageState.page = 1;
pageState.maxPage = 2;
waterfallsFlowRef.value.refresh();
}
let params = {
current: pageState.page,
pageSize: pageState.pageSize,
...pageState.search,
};
$api.createRequest('/app/job/list', params).then((resData) => {
const { rows, total } = resData;
if (type === 'add') {
const str = pageState.pageSize * (pageState.page - 1);
const end = list.value.length;
const reslist = dataToImg(rows);
list.value.splice(str, end, ...reslist);
} else {
list.value = dataToImg(rows);
}
pageState.total = resData.total;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
if (rows.length < pageState.pageSize) {
loadmoreRef.value?.change('noMore');
} else {
loadmoreRef.value?.change('more');
}
});
}
function dataToImg(data) {
return data.map((item) => ({
...item,
image: img,
hide: true,
}));
}
</script>
<style lang="stylus" scoped>
// 推荐卡片
.recommend-card::before
position: absolute
left: 0
top: 0
content: ''
height: 60rpx
width: 100%
background: linear-gradient(to bottom, #FFAD47, #FFFFFF)
.recommend-card::after
position: absolute
right: -20rpx
top: 40rpx
content: ''
height: 100rpx
width: 200rpx
background: url('@/static/icon/Group1.png') center center no-repeat
background-size: 100rpx 140rpx
.recommend-card
padding: 20rpx
.card-content
position: relative;
z-index: 2;
.recommend-card-title
color: #333333
font-size: 32rpx
.recommend-card-tip
font-size: 24rpx;
color: #606060;
margin-top: 10rpx
.recommend-card-controll
display: flex
align-items: center
justify-content: space-around
margin-top: 40rpx
.controll-yes
border-radius: 39rpx
padding: 0rpx 30rpx
background: rgba(255, 173, 71, 1)
border: 2rpx solid rgba(255, 173, 71, 1)
color: #FFFFFF
.controll-no
border-radius: 39rpx
border: 2rpx solid rgba(255, 173, 71, 1)
padding: 0rpx 30rpx
color: rgba(255, 173, 71, 1)
.controll-yes:active, .controll-no:active
background: #e8e8e8
border: 2rpx solid #e8e8e8
.app-container
width: 100%;
height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
background: linear-gradient( 180deg, #4778EC 0%, #002979 100%);
// background: linear-gradient(180deg, rgba(255,255,255,0.2) 0%, #fff 100%);
display: flex;
flex-direction: column;
position: fixed
.index-AI
height: 42rpx;
font-family: Inter, Inter;
font-weight: 400;
font-size: 35rpx;
color: #FFFFFF;
line-height: 41rpx;
padding: 85rpx 0 0 30rpx;
.index-option
margin-top: 27rpx;
display: flex;
.option-left
display: flex;
width: 427rpx;
height: 56rpx;
background: #4778EC;
border-radius: 0rpx 17rpx 17rpx 0rpx;
align-items: center;
.left-item
width: 117rpx;
text-align: center;
line-height: 47rpx;
height: 47rpx;
margin-right: 27rpx;
color: #FFFFFF;
.left-item:active
color: blue;
.option-right
flex: 1;
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 0 49rpx;
.right-input
width: 100%;
height: 45rpx;
background: rgba(255,255,255,0.5);
border-radius: 17rpx 17rpx 17rpx 17rpx;
border: 3rpx solid #FFFFFF;
padding: 0 50rpx 0 10rpx;
color: #FFFFFF;
.iconsearch
position: absolute;
right: 60rpx;
.tab-options
margin-top: 10rpx;
display: flex;
align-items: center;
justify-content: space-between;
height: 77rpx;
background: #FFFFFF;
border-radius: 17rpx 17rpx 0rpx 0rpx;
padding: 0 24rpx;
overflow: hidden;
.tab-scroll
height: 77rpx;
line-height: 77rpx;
flex: 1;
overflow: hidden;
padding-right: 10rpx;
.tab-op-left
display: flex;
align-items: center;
flex-wrap: nowrap;
.tab-list
text-align: center;
white-space: nowrap;
margin-right: 30rpx;
font-size: 28rpx;
color: #606060;
.tab-op-right
display: flex;
align-items: center;
.tab-recommend
white-space: nowrap;
width: fit-content;
padding: 0 10rpx;
height: 42rpx;
background: #4778EC;
border-radius: 17rpx 17rpx 0rpx 17rpx;
text-align: center;
color: #FFFFFF;
font-size: 21rpx;
line-height: 42rpx;
margin-right: 14rpx;
.tab-number
font-size: 21rpx;
color: #606060;
line-height: 25rpx;
text-align: center;
.tab-filter
display: flex;
.image
width: 28rpx;
height: 27rpx;
.falls-scroll
flex: 1;
overflow: hidden;
background: linear-gradient(180deg, rgba(255,255,255,0.2) 0%, #fff 100%)
.falls
padding: 20rpx 40rpx;
.item
position: relative;
// background: linear-gradient( 180deg, rgba(19, 197, 124, 0.4) 0%, rgba(255, 255, 255, 0) 30%), rgba(255, 255, 255, 0);
.falls-card
padding: 30rpx;
.falls-card-title
// height: 49rpx;
font-size: 42rpx;
color: #606060;
line-height: 49rpx;
text-align: left;
word-break:break-all
.falls-card-pay
// height: 50rpx;
word-break:break-all
font-size: 34rpx;
color: #002979;
line-height: 50rpx;
text-align: left;
display: flex;
align-items: end;
position: relative
.pay-text
color: #002979;
padding-right: 10rpx
.flame
position: absolute
bottom: 0
right: -10rpx
transform: translate(0, -30%)
width: 24rpx
height: 31rpx
.falls-card-education,.falls-card-experience
width: fit-content;
height: 30rpx;
background: #13C57C;
border-radius: 17rpx 17rpx 17rpx 17rpx;
padding: 0 10rpx;
line-height: 30rpx;
font-weight: 400;
font-size: 21rpx;
color: #FFFFFF;
text-align: center;
margin-top: 14rpx;
.falls-card-company,.falls-card-pepleNumber
margin-top: 14rpx;
font-size: 21rpx;
color: #606060;
line-height: 25rpx;
text-align: left;
.falls-card-pepleNumber
display: flex;
justify-content: space-between;
align-items: center;
.falls-card-matchingrate
margin-top: 10rpx;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 21rpx;
color: #4778EC;
text-align: left;
.logo
width: 200rpx;
height: 200rpx;
.tabchecked
color: #4778EC !important
</style>