Files
ks-app-employment-service/components/new-filter-page/new-filter-page.vue
2026-07-16 12:36:56 +08:00

558 lines
17 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 v-if="show" class="filter-container">
<!-- 头部 -->
<view class="filter-header">
<view class="header-title">筛选</view>
<view class="header-close" @click="handleClose">
<view class="close-icon"></view>
</view>
</view>
<!-- 主体内容 -->
<view class="filter-body">
<!-- 左侧标签页 -->
<view class="filter-tabs">
<view
v-for="(tab, index) in tabs"
:key="index"
class="tab-item"
:class="{ active: activeTab === tab.key }"
@click="activeTab = tab.key"
>
{{ tab.label }}
</view>
</view>
<!-- 右侧内容区域 -->
<view class="filter-right">
<!-- 内容区域 -->
<view class="filter-content">
<!-- 学历要求 -->
<view v-if="activeTab === 'education'" class="content-section">
<radio-group @change="(e) => handleSelect('education', e)">
<label
v-for="option in educationOptions"
:key="option.value"
class="radio-item"
:class="{ checked: selectedValues['education'] === String(option.value) }"
>
<radio
:value="String(option.value)"
:checked="selectedValues['education'] === String(option.value)"
/>
<text class="option-label">{{ option.label }}</text>
</label>
</radio-group>
</view>
<!-- 工作经验 -->
<view v-if="activeTab === 'experience'" class="content-section">
<radio-group @change="(e) => handleSelect('experience', e)">
<label
v-for="option in experienceOptions"
:key="option.value"
class="radio-item"
:class="{ checked: selectedValues['experience'] === String(option.value) }"
>
<radio
:value="String(option.value)"
:checked="selectedValues['experience'] === String(option.value)"
/>
<text class="option-label">{{ option.label }}</text>
</label>
</radio-group>
</view>
<!-- 薪资 -->
<view v-if="activeTab === 'salary'" class="content-section">
<radio-group @change="(e) => handleSelect('salary', e)">
<label
v-for="option in salaryOptions"
:key="option.value"
class="radio-item"
:class="{ checked: selectedValues['salary'] === String(option.value) }"
>
<radio
:value="String(option.value)"
:checked="selectedValues['salary'] === String(option.value)"
/>
<text class="option-label">{{ option.label }}</text>
</label>
</radio-group>
</view>
<!-- 公司规模 -->
<view v-if="activeTab === 'scale'" class="content-section">
<radio-group @change="(e) => handleSelect('scale', e)">
<label
v-for="option in scaleOptions"
:key="option.value"
class="radio-item"
:class="{ checked: selectedValues['scale'] === String(option.value) }"
>
<radio
:value="String(option.value)"
:checked="selectedValues['scale'] === String(option.value)"
/>
<text class="option-label">{{ option.label }}</text>
</label>
</radio-group>
</view>
<!-- 地区多选 -->
<view v-if="activeTab === 'area'" class="content-section">
<checkbox-group @change="(e) => handleCheckboxSelect('area', e)">
<label
v-for="option in areaOptions"
:key="option.value"
class="radio-item"
:class="{ checked: isAreaSelected(String(option.value)) }"
>
<checkbox
:value="String(option.value)"
:checked="isAreaSelected(String(option.value))"
/>
<text class="option-label">{{ option.label }}</text>
</label>
</checkbox-group>
</view>
<!-- 岗位类型 -->
<view v-if="activeTab === 'jobType'" class="content-section">
<radio-group @change="(e) => handleSelect('jobType', e)">
<label
v-for="option in jobTypeOptions"
:key="option.value"
class="radio-item"
:class="{ checked: selectedValues['jobType'] === String(option.value) }"
>
<radio
:value="String(option.value)"
:checked="selectedValues['jobType'] === String(option.value)"
/>
<text class="option-label">{{ option.label }}</text>
</label>
</radio-group>
</view>
</view>
<!-- 底部按钮 -->
<view class="filter-footer">
<button class="footer-btn clear-btn" @click="handleClear">清除</button>
<button class="footer-btn confirm-btn" @click="handleConfirm">确认</button>
</view>
</view>
</view><!-- end filter-body -->
</view>
</template>
<script setup>
import { ref, reactive, onBeforeMount } from 'vue';
import useDictStore from '@/stores/useDictStore';
const dictStore = useDictStore();
const { getTransformChildren } = dictStore;
const props = defineProps({
show: Boolean,
});
const emit = defineEmits(['confirm', 'close', 'update:show']);
// 岗位类型数据
const getJobTypeData = () => {
return [
{ label: '常规岗位', value: 0, text: '常规岗位' },
{ label: '就业见习岗位', value: 1, text: '就业见习岗位' },
{ label: '实习实训岗位', value: 2, text: '实习实训岗位' },
{ label: '社区实践岗位', value: 3, text: '社区实践岗位' }
];
};
// 标签页数据
const tabs = [
{ key: 'education', label: '学历要求' },
{ key: 'experience', label: '工作经验' },
{ key: 'salary', label: '薪资' },
{ key: 'scale', label: '公司规模' },
{ key: 'jobType', label: '岗位类型' },
{ key: 'area', label: '地区' }
];
// 当前激活的标签
const activeTab = ref('education');
// 存储已选中的值
const selectedValues = reactive({
education: '',
experience: '',
salary: '',
scale: '',
area: [],
jobType: ''
});
// 从字典获取的选项数据
const educationOptions = ref([]);
const experienceOptions = ref([]);
const scaleOptions = ref([]);
const areaOptions = ref([]);
const jobTypeOptions = ref([]);
// 薪资区间数据(单位:元/月min/max 为空表示该侧不设限
const salaryOptions = ref([
{ label: '3K以下', value: '0-3000', min: '', max: 3000 },
{ label: '3-5K', value: '3000-5000', min: 3000, max: 5000 },
{ label: '5-10K', value: '5000-10000', min: 5000, max: 10000 },
{ label: '10-20K', value: '10000-20000', min: 10000, max: 20000 },
{ label: '20-50K', value: '20000-50000', min: 20000, max: 50000 },
{ label: '50K以上', value: '50000-0', min: 50000, max: '' },
]);
// 加载状态
const loading = ref(true);
// 初始化获取数据
onBeforeMount(async () => {
try {
// 先获取字典数据
await dictStore.getDictData();
// 再初始化选项数据
educationOptions.value = getTransformChildren('education', '学历要求').options || [];
experienceOptions.value = getTransformChildren('experience', '工作经验').options || [];
scaleOptions.value = getTransformChildren('scale', '公司规模').options || [];
areaOptions.value = getTransformChildren('area', '地区').options || [];
jobTypeOptions.value = getJobTypeData();
} catch (error) {
console.error('获取字典数据失败:', error);
} finally {
loading.value = false;
}
});
// 处理单选选项选择
const handleSelect = (key, e) => {
selectedValues[key] = e.detail.value;
};
// 处理多选选项选择(地区)
// 兼容不同平台H5 可能返回逗号分隔字符串,小程序返回数组
const handleCheckboxSelect = (key, e) => {
let val = e.detail.value;
if (typeof val === 'string') {
selectedValues[key] = val ? val.split(',').map((s) => s.trim()) : [];
} else if (Array.isArray(val)) {
selectedValues[key] = val.map((v) => String(v));
} else {
selectedValues[key] = [];
}
};
// 判断地区选项是否被选中(兼容数组和字符串)
const isAreaSelected = (value) => {
const areaVal = selectedValues.area;
if (Array.isArray(areaVal)) {
return areaVal.includes(value);
}
if (typeof areaVal === 'string' && areaVal) {
return areaVal.split(',').includes(value);
}
return false;
};
// 清除所有选择
const handleClear = () => {
Object.keys(selectedValues).forEach((key) => {
if (key === 'area') {
selectedValues[key] = [];
} else {
selectedValues[key] = '';
}
});
};
// 确认筛选
const handleConfirm = () => {
const payload = { ...selectedValues };
// 将多选的地区数组转为逗号分隔字符串(兼容各种格式)
if (Array.isArray(payload.area) && payload.area.length > 0) {
payload.area = payload.area.map((v) => String(v)).join(',');
} else if (typeof payload.area === 'string' && payload.area) {
// 如果已经是逗号分隔字符串,保持原样
payload.area = payload.area;
} else {
payload.area = '';
}
console.log('[new-filter-page] area value to emit:', payload.area);
// 将选中的薪资区间转换为 minSalary / maxSalary 参数
// 始终带上 minSalary / maxSalary含空值便于父组件在清除时移除旧值
let minSalary = '';
let maxSalary = '';
if (payload.salary) {
const selected = salaryOptions.value.find((item) => item.value === payload.salary);
if (selected) {
minSalary = selected.min;
maxSalary = selected.max;
}
}
payload.minSalary = minSalary;
payload.maxSalary = maxSalary;
delete payload.salary;
console.log('selectedValues:', payload);
emit('confirm', payload);
handleClose();
};
// 关闭弹窗
const handleClose = () => {
emit('update:show', false);
emit('close');
};
</script>
<style lang="scss" scoped>
.filter-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #fff;
z-index: 9999;
display: flex;
flex-direction: column;
}
.filter-header {
height: 96rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 32rpx;
border-bottom: 1rpx solid #eee;
background-color: #fff;
flex-shrink: 0;
position: relative;
.header-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
.header-close {
position: absolute;
right: 32rpx;
top: 50%;
transform: translateY(-50%);
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 10rpx;
border-radius: 50%;
transition: all 0.3s ease;
&:active {
background-color: rgba(37, 107, 250, 0.1);
}
.close-icon {
position: relative;
width: 28rpx;
height: 28rpx;
&::before,
&::after {
content: '';
position: absolute;
left: 50%;
top: 50%;
width: 4rpx;
height: 28rpx;
border-radius: 2rpx;
background: #5A5A68;
}
&::before {
transform: translate(-50%, -50%) rotate(45deg);
}
&::after {
transform: translate(-50%, -50%) rotate(-45deg);
}
}
}
}
.filter-body {
flex: 1;
display: flex;
flex-direction: row;
overflow: hidden;
}
.filter-tabs {
width: 200rpx;
border-right: 1rpx solid #eee;
background-color: #f8f8f8;
display: flex;
flex-direction: column;
.tab-item {
height: 100rpx;
line-height: 100rpx;
text-align: center;
font-size: 28rpx;
color: #666;
position: relative;
transition: all 0.3s ease;
&.active {
color: #256BFA;
font-weight: 600;
background-color: #fff;
&::after {
content: '';
position: absolute;
left: 0;
top: 25%;
width: 4rpx;
height: 50%;
background-color: #256BFA;
border-radius: 2rpx;
}
}
&:active {
background-color: rgba(37, 107, 250, 0.05);
}
}
}
.filter-right {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
}
.filter-content {
flex: 1;
padding: 40rpx 32rpx;
overflow-y: auto;
}
.content-section {
.radio-item {
display: flex;
align-items: center;
padding: 30rpx 0;
border-bottom: 1rpx solid #f0f0f0;
transition: all 0.3s ease;
position: relative;
&:last-child {
border-bottom: none;
}
&:active {
background-color: rgba(37, 107, 250, 0.05);
}
radio {
width: 28rpx;
height: 28rpx;
margin-right: 24rpx;
transform: scale(1);
display: flex;
align-items: center;
justify-content: center;
}
radio .wx-radio-input {
width: 28rpx;
height: 28rpx;
border-radius: 50%;
border: 2rpx solid #ccc;
background: transparent;
}
radio .wx-radio-input.wx-radio-input-checked {
border-color: #256BFA !important;
background: #256BFA !important;
}
radio .wx-radio-input::before {
width: 16rpx;
height: 16rpx;
line-height: 16rpx;
text-align: center;
font-size: 12rpx;
color: #fff;
background: transparent;
transform: translate(-50%, -50%) scale(0);
-webkit-transform: translate(-50%, -50%) scale(0);
}
radio .wx-radio-input.wx-radio-input-checked::before {
transform: translate(-50%, -50%) scale(1);
-webkit-transform: translate(-50%, -50%) scale(1);
}
.option-label {
font-size: 30rpx;
color: #333;
flex: 1;
font-weight: 400;
line-height: 40rpx;
}
}
}
.filter-footer {
height: 160rpx;
display: flex;
border-top: 1rpx solid #eee;
background-color: #fff;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
padding: 20rpx 32rpx 100rpx;
flex-shrink: 0;
position: relative;
z-index: 10;
.footer-btn {
flex: 1;
margin: 0;
border-radius: 16rpx;
height: 80rpx;
line-height: 80rpx;
font-size: 32rpx;
transition: all 0.3s ease;
font-weight: 500;
&:first-child {
margin-right: 20rpx;
background-color: #f5f5f5;
color: #666;
border: 1rpx solid #e0e0e0;
&:active {
background-color: #e0e0e0;
transform: scale(0.98);
}
}
&:last-child {
background-color: #256BFA;
color: #fff;
border: none;
&:active {
background-color: #1a56d9;
transform: scale(0.98);
}
}
}
}
</style>