3 Commits

Author SHA1 Message Date
francis-fh
76b1a65f9f bug修复 2026-07-16 12:10:52 +08:00
francis-fh
4e649401cd bug修复 2026-07-16 12:06:02 +08:00
francis-fh
a804e443f3 11 2026-07-16 01:06:02 +08:00
6 changed files with 321 additions and 77 deletions

16
.codegraph/.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
# CodeGraph data files
# These are local to each machine and should not be committed
# Database
*.db
*.db-wal
*.db-shm
# Cache
cache/
# Logs
*.log
# Hook markers
.dirty

6
.codegraph/daemon.pid Normal file
View File

@@ -0,0 +1,6 @@
{
"pid": 37252,
"version": "0.9.9",
"socketPath": "\\\\.\\pipe\\codegraph-27b009ff71560217",
"startedAt": 1784169684502
}

View File

@@ -2,24 +2,136 @@
<view v-if="show" class="filter-container">
<!-- 头部 -->
<view class="filter-header">
<text class="header-title">筛选</text>
<view class="header-close-btn btn-feel" @click="handleClose">
<uni-icons type="closeempty" size="18" color="#666"></uni-icons>
<text class="close-text">关闭</text>
<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-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 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>
@@ -120,9 +232,7 @@
<button class="footer-btn confirm-btn" @click="handleConfirm">确认</button>
</view>
</view>
</view>
</view><!-- end filter-body -->
</view>
</template>
@@ -196,21 +306,40 @@ watch(() => props.show, (newVal) => {
}
});
// 处理选项选择(单选)
// 处理单选选项选择
const handleSelect = (key, e) => {
selectedValues[key] = e.detail.value;
};
// 处理地区多选
const handleAreaSelect = (e) => {
selectedValues.area = e.detail.value;
// 处理多选选项选择(地区)
// 兼容不同平台H5 返回逗号分隔字符串,小程序返回数组,统一规范化为逗号分隔字符串
const handleCheckboxSelect = (key, e) => {
let val = e.detail.value;
if (typeof val === 'string') {
// H5 平台返回逗号分隔字符串,直接使用
selectedValues[key] = val || '';
} else if (Array.isArray(val)) {
// 小程序返回数组,转为逗号分隔字符串
selectedValues[key] = val ? val.join(',') : '';
} else {
selectedValues[key] = '';
}
};
// 判断地区选项是否被选中area 统一为逗号分隔字符串)
const isAreaSelected = (value) => {
const areaVal = selectedValues.area;
if (typeof areaVal === 'string' && areaVal) {
return areaVal.split(',').includes(value);
}
return false;
};
// 清除所有选择
const handleClear = () => {
Object.keys(selectedValues).forEach((key) => {
if (key === 'area') {
selectedValues[key] = [];
selectedValues[key] = '';
} else {
selectedValues[key] = '';
}
@@ -219,8 +348,25 @@ const handleClear = () => {
// 确认筛选
const handleConfirm = () => {
console.log('selectedValues:', selectedValues);
emit('confirm', selectedValues);
const payload = { ...selectedValues };
// 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();
};
@@ -242,25 +388,70 @@ const handleClose = () => {
z-index: 9999;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.filter-header {
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
justify-content: center;
padding: 0 32rpx;
border-bottom: 1rpx solid #eee;
background-color: #fff;
flex-shrink: 0;
position: relative;
.header-title {
font-size: 48rpx;
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);
}
}
}
.header-close-btn {
display: flex;
align-items: center;
@@ -289,6 +480,13 @@ const handleClose = () => {
overflow: hidden;
}
.filter-body {
flex: 1;
display: flex;
flex-direction: row;
overflow: hidden;
}
.filter-tabs {
display: flex;
flex-direction: column;

View File

@@ -21,14 +21,8 @@
</view>
<view class="card-bottom">
<view>{{ job.postingDate }}</view>
<view>
<convert-distance
:alat="job.latitude"
:along="job.longitude"
:blat="latitude"
:blong="longitude"
></convert-distance>
{{ job.regionName || config.appInfo.areaName }}
<view class="card-bottom-right">
<text v-if="job.regionName">地区{{ job.regionName }}</text>
</view>
</view>
</view>

View File

@@ -46,7 +46,7 @@
:class="{ 'hidden-height': shouldHideTop }"
>
<view class="container-search" v-if="shouldShowJobSeekerContent">
<view class="search-input button-click" @click="goToSearch">
<view class="search-input button-click" @click="navToSearch">
<uni-icons class="iconsearch" color="#666666" type="search" size="18"></uni-icons>
<text class="inpute">职位名称薪资要求等</text>
</view>
@@ -279,15 +279,14 @@
{{ item.text }}
</view>
</view>
<view class="btm-right-group">
<view class="btm-right-wrap">
<view class="button-click clear-btn" v-if="hasFilterConditions" @click="clearFilter">
清除筛选
</view>
<view class="btm-right button-click" @click="openFilter">
筛选
<image class="right-sx" :class="{ active: showFilter }" src="@/static/icon/shaixun.png"></image>
</view>
<view class="btm-clear button-click" v-if="hasActiveFilters" @click="clearFilters">
<uni-icons type="trash" size="18" color="#256BFA"></uni-icons>
<text>清除</text>
</view>
</view>
</view>
</view>
@@ -585,6 +584,12 @@ const shouldShowCompanyContent = computed(() => {
return userType === 0;
});
// 判断是否有筛选条件(用于显示/隐藏清除按钮)
const hasFilterConditions = computed(() => {
const searchKeys = Object.keys(pageState.search).filter((k) => k !== 'order' && pageState.search[k]);
return searchKeys.length > 0 || Object.keys(conditionSearch.value).length > 0 || Boolean(selectedCity.value.code);
});
// 判断当前用户是否为招聘者(企业用户)
const isRecruiter = computed(() => {
if (!hasLogin.value) {
@@ -1116,14 +1121,36 @@ function openFilter() {
emits('onShowTabbar', false);
}
function clearFilters() {
// 清除筛选弹窗条件,保留 order
pageState.search = { order: pageState.search.order };
// 跳转到搜索页,并携带筛选条件
function navToSearch() {
const query = {};
// 携带筛选弹窗中选择的地区(确保是逗号分隔字符串)
const areaVal = pageState.search.area;
if (areaVal && String(areaVal)) {
query.area = String(areaVal);
}
// 携带城市选择器中选择的regionCode
if (conditionSearch.value.regionCode) {
query.regionCode = String(conditionSearch.value.regionCode);
}
console.log('[navToSearch] query params:', query);
navTo('/pages/search/search', { query });
}
// 清除所有筛选条件
function clearFilter() {
// 保留 order 排序方式
const currentOrder = pageState.search.order;
pageState.search = { order: currentOrder };
// 清除城市选择
selectedCity.value = { code: '', name: '' };
conditionSearch.value = {};
selectedCity.value = { code: '', name: '' };
// 刷新列表
getJobRecommend('refresh');
if (state.tabIndex === 'all') {
getJobRecommend('refresh');
} else {
getJobList('refresh');
}
}
function handleNewFilterConfirm(values) {
@@ -1134,9 +1161,13 @@ function handleNewFilterConfirm(values) {
// 特殊处理岗位类型,直接传递数字值
if (key === 'jobType') {
pageState.search.type = value;
} else if (key === 'area' && Array.isArray(value) && value.length > 0) {
// 地区多选,转为逗号分隔字符串
pageState.search[key] = value.join(',');
} else if (key === 'area') {
// 地区多选,转为逗号分隔字符串;未选择时设为空字符串
if (Array.isArray(value) && value.length > 0) {
pageState.search[key] = value.join(',');
} else {
pageState.search[key] = '';
}
} else if (value) {
pageState.search[key] = value;
} else {
@@ -2025,31 +2056,26 @@ defineExpose({ loadData });
font-weight: 500;
font-size: 66rpx;
color: #256BFA;
.btm-right-group
.btm-right-wrap
display: flex
align-items: center
.btm-right
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 400;
font-size: 66rpx;
color: #6C7282;
.right-sx
width: 32rpx;
height: 32rpx;
.active
transform: rotate(180deg)
.btm-clear
display: flex
align-items: center
gap: 4rpx
padding: 10rpx 16rpx
margin-left: 12rpx
background: #EBF0FF
border: 1rpx solid #B3C6FF
border-radius: 10rpx
font-size: 36rpx
color: #256BFA
white-space: nowrap
.clear-btn
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 400;
font-size: 28rpx;
color: #999;
margin-right: 20rpx;
white-space: nowrap;
.btm-right
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 400;
font-size: 32rpx;
color: #6C7282;
.right-sx
width: 26rpx;
height: 26rpx;
.active
transform: rotate(180deg)
.table-list
background: #F4F4F4
flex: 1

View File

@@ -1,7 +1,7 @@
<template>
<view class="container safe-area-top">
<!-- 自定义头部搜索框 -->
<view class="custom-header">
<view class="custom-header" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="top">
<image class="btnback button-click" src="@/static/icon/back.png" @click="navBack"></image>
<view class="search-box">
@@ -80,11 +80,14 @@ onLoad((query) => {
historyList.value = uni.getStorageSync('searchList');
}
const {job, area, regionCode} = query;
// 从首页筛选条件传入的地区和区域编码
console.log('[search-page] onLoad query:', JSON.stringify(query));
if (area) {
pageState.search.area = area;
pageState.search.area = String(area);
console.log('[search-page] set area:', pageState.search.area);
}
if (regionCode) {
pageState.search.regionCode = regionCode;
pageState.search.regionCode = String(regionCode);
}
if (job) {
searchValue.value = job;
@@ -167,6 +170,7 @@ function getJobList(type = 'add') {
...pageState.search,
jobTitle: searchValue.value,
};
console.log('[search-page] getJobList params:', JSON.stringify(params));
$api.createRequest('/app/job/list', params, 'GET', true).then((resData) => {
const { rows, total } = resData;