Files
ks-app-employment-service/pages/nearby/components/three.vue
2025-12-05 13:31:15 +08:00

566 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="nearby-container">
<view class="three-head" @touchmove.stop.prevent>
<view class="one-picker">
<view class="oneleft button-click" @click="openFilterSubway">
<view class="uni-input">{{ inputText(state.subwayId) }}</view>
<view class="btm-right">
<image
class="right-sx"
:class="{ active: showFiltersubway }"
src="@/static/icon/shaixun.png"
></image>
</view>
</view>
<view class="oneright">{{ state.subwayStart.stationName }}-{{ state.subwayEnd.stationName }}</view>
</view>
<scroll-view class="scroll-head" :scroll-x="true" :show-scrollbar="false">
<view class="metro">
<view class="metro-three">
<view class="three-background">
<view class="three-items">
<view
class="three-item"
v-for="(item, index) in subwayCurrent.subwayStationList"
@click="selectSubwayStation(item, index)"
:key="index"
>
<view
class="item-dont"
:class="{
dontstart: index === 0,
dontend: index === subwayCurrent.subwayStationList.length - 1,
donted: index === state.dont,
}"
></view>
<view
class="item-text"
:class="{
textActive: index === state.dont,
}"
>
{{ item.stationName }}
</view>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 筛选项移到scroll-view外面 -->
<view class="nav-filter" @touchmove.stop.prevent>
<view class="filter-top">
<scroll-view :scroll-x="true" :show-scrollbar="false" class="tab-scroll">
<view class="jobs-left">
<view
class="job button-click"
:class="{ active: state.tabIndex === 'all' }"
@click="choosePosition('all')"
>
全部
</view>
<view
class="job button-click"
:class="{ active: state.tabIndex === index }"
v-for="(item, index) in userInfo.jobTitle"
:key="index"
@click="choosePosition(index)"
>
{{ item }}
</view>
</view>
</scroll-view>
<view class="jobs-add button-click" @click="navTo('/packageA/pages/addPosition/addPosition')">
<uni-icons class="iconsearch" color="#666D7F" type="plusempty" size="18"></uni-icons>
<text>添加</text>
</view>
</view>
<view class="filter-bottom">
<view class="btm-left">
<view
class="button-click filterbtm"
:class="{ active: pageState.search.order === item.value }"
v-for="item in rangeOptions"
@click="handelHostestSearch(item)"
:key="item.value"
>
{{ item.text }}
</view>
</view>
<view class="btm-right button-click" @click="openFilter">
筛选
<image class="right-sx" :class="{ active: showFilter }" src="@/static/icon/shaixun.png"></image>
</view>
</view>
</view>
<scroll-view :scroll-y="true" class="nearby-scroll" @scrolltolower="scrollBottom" lower-threshold="50">
<view class="nearby-list">
<view class="one-cards">
<renderJobs
v-if="list.length"
:list="list"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobs>
<empty v-else pdTop="60"></empty>
<loadmore ref="loadmoreRef"></loadmore>
</view>
</view>
</scroll-view>
<!-- 筛选 -->
<select-filter ref="selectFilterModel"></select-filter>
</view>
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted, onBeforeUnmount } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
const { $api, navTo, debounce, customSystem } = inject('globalFunction');
const openSelectPopup = inject('openSelectPopup');
import { storeToRefs } from 'pinia';
import useLocationStore from '@/stores/useLocationStore';
import useUserStore from '@/stores/useUserStore';
import useDictStore from '@/stores/useDictStore';
const { oneDictData } = useDictStore();
const { userInfo } = storeToRefs(useUserStore());
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
import point2 from '@/static/icon/point2.png';
import LocationPng from '@/static/icon/Location.png';
import selectFilter from '@/components/selectFilter/selectFilter.vue';
const emit = defineEmits(['onFilter']);
// status
const showFiltersubway = ref(false);
const showFilter = ref(false);
const selectFilterModel = ref();
const subwayCurrent = ref([]);
const isLoaded = ref(false);
const range = ref([]);
const state = reactive({
subwayList: [],
subwayStart: {},
subwayEnd: {},
value: 0,
subwayId: 0,
downup: true,
dont: 0,
dontObj: {},
tabIndex: 'all',
});
const pageState = reactive({
page: 0,
total: 0,
maxPage: 2,
pageSize: 10,
search: {
order: 0,
},
});
const list = ref([]);
const loadmoreRef = ref(null);
const rangeOptions = ref([
{ value: 0, text: '推荐' },
{ value: 1, text: '最热' },
{ value: 2, text: '最新发布' },
{ value: 3, text: '疆外' },
]);
onLoad(() => {
getSubway();
});
function navToPost(jobId) {
navTo(`/packageA/pages/post/post?jobId=${encodeURIComponent(jobId)}`);
}
async function loadData() {
try {
if (isLoaded.value) return;
getJobList('refresh');
isLoaded.value = true;
} catch (err) {
isLoaded.value = false; // 重置状态允许重试
throw err;
}
}
function openFilter() {
showFilter.value = true;
selectFilterModel.value?.open({
title: '筛选',
maskClick: true,
success: (values) => {
pageState.search = {
...pageState.search,
};
for (const [key, value] of Object.entries(values)) {
pageState.search[key] = value.join(',');
}
showFilter.value = false;
getJobList('refresh');
},
cancel: () => {
showFilter.value = false;
},
});
}
function openFilterSubway() {
const diti = state.subwayList.map((item) => ({ ...item, label: item.lineName, value: item.lineId }));
openSelectPopup({
title: '地铁',
maskClick: true,
data: [diti],
success: (_, [value]) => {
subwayCurrent.value = value;
state.subwayId = value.value;
state.value = value.value;
const points = value.subwayStationList;
state.downup = true;
if (points.length) {
state.dont = 0;
state.dontObj = points[0];
state.subwayStart = points[0];
state.subwayEnd = points[points.length - 1];
getJobList('refresh');
}
},
});
}
function scrollBottom() {
getJobList();
loadmoreRef.value.change('loading');
}
function choosePosition(index) {
state.tabIndex = index;
if (index === 'all') {
pageState.search.jobTitle = '';
} else {
pageState.search.jobTitle = userInfo.value.jobTitle[index];
}
getJobList('refresh');
}
function selectSubwayStation(point, index) {
console.log(point, index);
state.dont = index;
state.dontObj = point;
getJobList('refresh');
}
function inputText(id) {
if (id) {
const text = range.value.filter((item) => item.value === id)[0].text;
return text;
} else {
return '';
}
}
function bindPickerChange(e) {
const lineId = range.value[e.detail.value];
const value = state.subwayList.filter((iv) => iv.lineId === lineId.value)[0];
subwayCurrent.value = value;
state.value = e.detail.value;
state.subwayId = value.lineId;
const points = value.subwayStationList;
state.downup = true;
if (points.length) {
state.dont = 0;
state.dontObj = points[0];
state.subwayStart = points[0];
state.subwayEnd = points[points.length - 1];
}
}
function getSubway() {
$api.createRequest(`/app/common/subway`).then((resData) => {
state.subwayList = resData.data;
subwayCurrent.value = resData.data[0];
state.subwayId = resData.data[0].lineId;
state.value = 0;
state.dont = 0;
range.value = resData.data.map((iv) => ({ text: iv.lineName, value: iv.lineId }));
const points = resData.data[0].subwayStationList;
if (points.length) {
state.dont = 0;
state.dontObj = points[0];
state.subwayStart = points[0];
state.subwayEnd = points[points.length - 1];
}
});
}
function getJobList(type = 'add') {
if (type === 'add' && pageState.page < pageState.maxPage) {
pageState.page += 1;
}
if (type === 'refresh') {
pageState.page = 1;
pageState.maxPage = 2;
}
let params = {
current: pageState.page,
pageSize: pageState.pageSize,
subwayIds: [state.dontObj.stationId],
latitude: state.dontObj.latitude,
longitude: state.dontObj.longitude,
radius: 2,
...pageState.search,
};
$api.createRequest('/app/job/subway', params, 'POST').then((resData) => {
const { rows, total } = resData;
if (type === 'add') {
const str = pageState.pageSize * (pageState.page - 1);
const end = list.value.length;
const reslist = rows;
list.value.splice(str, end, ...reslist);
} else {
list.value = rows;
}
pageState.total = resData.total;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
if (loadmoreRef.value && typeof loadmoreRef.value.change === 'function') {
if (rows.length < pageState.pageSize) {
loadmoreRef.value.change('noMore');
} else {
loadmoreRef.value.change('more');
}
}
});
}
function handelHostestSearch(val) {
pageState.search.order = val.value;
getJobList('refresh');
}
function handleFilterConfirm(val) {
pageState.search = {
order: pageState.search.order,
};
for (const [key, value] of Object.entries(val)) {
pageState.search[key] = value.join(',');
}
getJobList('refresh');
}
defineExpose({ loadData, handleFilterConfirm });
</script>
<style lang="stylus" scoped>
.btm-right
font-weight: 400;
font-size: 32rpx;
color: #6C7282;
.right-sx
width: 26rpx;
height: 26rpx;
.active
transform: rotate(180deg)
.tabchecked
color: #4778EC !important;
.nearby-container
// 确保容器占据整个可用高度
height: 100%;
display: flex;
flex-direction: column;
.nearby-scroll
// 为scroll-view设置明确高度减去nav-filter的高度
height: calc(100vh - var(--window-top) - var(--status-bar-height) - 63rpx - 200rpx - 120rpx);
.three-head
margin: 24rpx 0 0 0;
padding: 26rpx 0 0 0;
border-radius: 17rpx 17rpx 17rpx 17rpx;
.one-picker
height: 100%
padding: 0 28rpx
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: space-between
.oneleft
display: flex;
flex-wrap: nowrap;
.oneright
display: flex;
flex-wrap: nowrap;
.scroll-head
width: 100%;
overflow: hidden;
.metro
width: 100%;
.metro-one
font-size: 28rpx;
color: #000000;
line-height: 33rpx;
width: 100%;
min-width: 100rpx;
.metro-two
font-size: 21rpx;
color: #606060;
line-height: 25rpx;
margin-top: 6rpx;
.metro-three
width: fit-content;
margin-top: 100rpx;
padding: 0 64rpx;
.three-background
position: relative;
.three-items
position: relative;
top: -17rpx;
display: flex;
justify-content: space-between;
z-index: 2;
height: 90rpx
.three-item
margin-right: 124rpx;
position: relative
.item-dont
width: 17rpx;
height: 17rpx;
background: #FFFFFF;
border-radius: 50%;
position: relative;
margin-bottom: 20rpx;
.item-dont::before
position: absolute;
content: '';
color: #FFFFFF;
font-size: 20rpx;
text-align: center;
left: 50%;
top: 50%;
transform: translate(-50%, -50%)
width: 27rpx;
height: 27rpx;
background: #F7B000;
border-radius: 50%;
.item-dont::after
position: absolute;
// content: '始';
content: '';
font-size: 20rpx;
text-align: center;
left: 50%;
top: 50%;
transform: translate(-50%, -50%)
width: 14rpx;
height: 14rpx;
background: #ffffff;
border-radius: 50%;
// .donted::after
// position: absolute;
// content: '';
// font-size: 20rpx;
// text-align: center;
// left: 50%;
// top: 50%;
// transform: translate(-50%, -50%)
// width: 14rpx;
// height: 14rpx;
// background: #F7B000 !important;
// border-radius: 50%;
.item-text
position: absolute
left: 0
width: fit-content;
font-weight: 400;
font-size: 28rpx;
color: #333333;
line-height: 25rpx;
text-align: center;
white-space: nowrap
transform: translate(-50% + 8rpx, 0)
.textActive
color: #F7B000
.three-item:nth-child(2n)
.item-text
margin-top: -90rpx;
.three-background::after
position: absolute;
content: '';
left: 0;
top: -17rpx;
width: 100%;
height: 17rpx;
background: #F7B000;
border-radius: 17rpx 17rpx 17rpx 17rpx;
z-index: 1;
.nearby-list
.one-cards{
display: flex;
flex-direction: column;
padding: 0 20rpx 20rpx 20rpx;
background: #f4f4f4
}
.nav-filter
border-top: 2rpx solid #EBEBEB;
padding: 16rpx 28rpx 0 28rpx;
margin-top: 125rpx;
.filter-top
display: flex
justify-content: space-between;
.tab-scroll
flex: 1;
overflow: hidden;
margin-right: 20rpx
white-space: nowrap;
overflow: hidden;
text-overflow: clip;
-webkit-mask-image: linear-gradient(to right, black 60%, transparent);
mask-image: linear-gradient(to right, black 60%, transparent);
.jobs-left
display: flex
flex-wrap: nowrap
.job
font-weight: 400;
font-size: 36rpx;
color: #666D7F;
margin-right: 32rpx;
white-space: nowrap
.active
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-weight: 500;
font-size: 36rpx;
color: #000000;
.jobs-add
display: flex
align-items: center;
justify-content: center;
font-weight: 400;
font-size: 32rpx;
color: #666D7F;
line-height: 38rpx;
.filter-bottom
display: flex
justify-content: space-between
padding: 24rpx 0
.btm-left
display: flex
.filterbtm
font-weight: 400;
font-size: 32rpx;
color: #666D7F;
margin-right: 40rpx
.active
font-weight: 500;
font-size: 32rpx;
color: #256BFA;
.btm-right
font-weight: 400;
font-size: 32rpx;
color: #6C7282;
.right-sx
width: 26rpx;
height: 26rpx;
.active
transform: rotate(180deg)
</style>