Files
ks-app-employment-service/packageA/pages/complete-info/skill-search.vue

378 lines
11 KiB
Vue
Raw Normal View History

2025-11-10 15:27:34 +08:00
<template>
<AppLayout>
<view class="skill-search-container">
<!-- 固定顶部区域 -->
<view class="fixed-header">
<!-- 搜索框 -->
<view class="search-box">
<input
class="search-input"
v-model="searchKeyword"
placeholder="请输入技能名称进行搜索"
@input="handleSearch"
confirm-type="search"
/>
<view class="search-icon" @click="handleSearch">搜索</view>
</view>
<!-- 已选技能提示 -->
<view class="selected-tip" v-if="selectedSkill">
已选择技能{{ selectedSkill }}
</view>
2025-11-10 15:27:34 +08:00
</view>
<!-- 可滚动内容区域 -->
<scroll-view class="scroll-content" scroll-y>
<view class="scroll-inner">
<!-- 搜索结果列表 -->
<view class="result-list" v-if="searchResults.length > 0">
<view
class="result-item"
v-for="(item, index) in searchResults"
:key="index"
@click="toggleSelect(item)"
>
<view class="item-content">
<text class="item-name">{{ item.name }}</text>
</view>
<view
class="item-checkbox"
:class="{ 'checked': isSelected(item) }"
>
<text v-if="isSelected(item)" class="check-icon"></text>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="!isSearching && searchResults.length === 0 && searchKeyword">
<text class="empty-text">未找到相关技能</text>
</view>
<!-- 初始提示 -->
<view class="empty-state" v-if="!isSearching && searchResults.length === 0 && !searchKeyword">
<text class="empty-text">请输入技能名称进行搜索</text>
</view>
</view>
</scroll-view>
<!-- 固定底部操作栏 -->
<view class="bottom-bar">
<view class="selected-skills" v-if="selectedSkill">
<view class="skill-tag">
<text class="tag-text">{{ selectedSkill }}</text>
<text class="tag-close" @click.stop="removeSkill">×</text>
2025-11-10 15:27:34 +08:00
</view>
</view>
<view class="action-buttons">
<button class="btn-cancel" @click="handleCancel">取消</button>
<button
class="btn-confirm"
:class="{ 'disabled': !selectedSkill }"
2025-11-10 15:27:34 +08:00
@click="handleConfirm"
:disabled="!selectedSkill"
2025-11-10 15:27:34 +08:00
>
确定
</button>
</view>
</view>
</view>
</AppLayout>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { inject } from 'vue';
const { $api } = inject('globalFunction');
const searchKeyword = ref('');
const searchResults = ref([]);
const selectedSkill = ref(''); // 改为单选,存储单个技能名称
2025-11-10 15:27:34 +08:00
const isSearching = ref(false);
let searchTimer = null;
onLoad((options) => {
// 接收已选中的技能(单选模式,只接收单个技能)
2025-11-10 15:27:34 +08:00
if (options.selected) {
try {
const skills = JSON.parse(decodeURIComponent(options.selected));
if (Array.isArray(skills) && skills.length > 0) {
selectedSkill.value = skills[0]; // 只取第一个技能
2025-11-10 15:27:34 +08:00
}
} catch (e) {
console.error('解析已选技能失败:', e);
}
}
});
// 搜索处理(防抖)
function handleSearch() {
const keyword = searchKeyword.value.trim();
if (!keyword) {
searchResults.value = [];
return;
}
// 清除之前的定时器
if (searchTimer) {
clearTimeout(searchTimer);
}
// 防抖处理500ms后执行搜索
searchTimer = setTimeout(() => {
performSearch(keyword);
}, 500);
}
// 执行搜索
async function performSearch(keyword) {
if (!keyword.trim()) {
searchResults.value = [];
return;
}
isSearching.value = true;
try {
const response = await $api.createRequest('/cms/dict/jobCategory', { name: keyword }, 'GET');
// 处理接口返回的数据,支持多种可能的返回格式
let results = [];
if (response) {
if (Array.isArray(response)) {
// 如果直接返回数组
results = response;
} else if (response.data) {
// 如果返回 { data: [...] }
results = Array.isArray(response.data) ? response.data : [];
} else if (response.list) {
// 如果返回 { list: [...] }
results = Array.isArray(response.list) ? response.list : [];
}
}
// 确保每个结果都有name字段
searchResults.value = results.map(item => {
if (typeof item === 'string') {
return { name: item };
}
return item;
});
} catch (error) {
console.error('搜索技能失败:', error);
$api.msg('搜索失败,请稍后重试');
searchResults.value = [];
} finally {
isSearching.value = false;
}
}
// 判断技能是否已选中
function isSelected(item) {
return selectedSkill.value === item.name;
2025-11-10 15:27:34 +08:00
}
// 切换技能选择状态(单选模式)
2025-11-10 15:27:34 +08:00
function toggleSelect(item) {
const skillName = item.name;
if (selectedSkill.value === skillName) {
2025-11-10 15:27:34 +08:00
// 已选中,取消选择
selectedSkill.value = '';
2025-11-10 15:27:34 +08:00
} else {
// 选择新的技能
selectedSkill.value = skillName;
2025-11-10 15:27:34 +08:00
}
}
// 移除技能(单选模式,直接清空)
function removeSkill() {
selectedSkill.value = '';
2025-11-10 15:27:34 +08:00
}
// 取消操作
function handleCancel() {
uni.navigateBack();
}
// 确定操作
function handleConfirm() {
if (!selectedSkill.value) {
$api.msg('请选择一个技能');
2025-11-10 15:27:34 +08:00
return;
}
// 通过事件总线传递选中的技能(单选模式,传递单个技能名称)
uni.$emit('skillSelected', selectedSkill.value);
2025-11-10 15:27:34 +08:00
// 返回上一页
uni.navigateBack();
}
onUnmounted(() => {
// 清理定时器
if (searchTimer) {
clearTimeout(searchTimer);
}
});
</script>
<style lang="stylus" scoped>
.skill-search-container
display: flex
flex-direction: column
height: 100%
background-color: #f5f5f5
.fixed-header
flex-shrink: 0
background-color: #fff
2026-03-12 17:10:34 +08:00
border-bottom: 3rpx solid #ebebeb
2025-11-10 15:27:34 +08:00
.search-box
display: flex
align-items: center
2026-03-12 17:10:34 +08:00
padding: 36rpx
2025-11-10 15:27:34 +08:00
background-color: #fff
.search-input
flex: 1
2026-03-12 17:10:34 +08:00
height: 108rpx
padding: 0 36rpx
2025-11-10 15:27:34 +08:00
background-color: #f5f5f5
2026-03-12 17:10:34 +08:00
border-radius: 54rpx
font-size: 42rpx
2025-11-10 15:27:34 +08:00
color: #333
.search-icon
2026-03-12 17:10:34 +08:00
margin-left: 36rpx
padding: 24rpx 48rpx
2025-11-10 15:27:34 +08:00
background-color: #256bfa
color: #fff
2026-03-12 17:10:34 +08:00
border-radius: 54rpx
font-size: 42rpx
2025-11-10 15:27:34 +08:00
.selected-tip
2026-03-12 17:10:34 +08:00
padding: 24rpx 36rpx
2025-11-10 15:27:34 +08:00
background-color: #fff3cd
color: #856404
2026-03-12 17:10:34 +08:00
font-size: 36rpx
2025-11-10 15:27:34 +08:00
text-align: center
.scroll-content
flex: 1
overflow: hidden
height: 0 // 关键让flex布局正确计算高度
background-color: #fff
.scroll-inner
2026-03-12 17:10:34 +08:00
padding-bottom: 60rpx
2025-11-10 15:27:34 +08:00
.result-list
background-color: #fff
2026-03-12 17:10:34 +08:00
padding: 0 36rpx
2025-11-10 15:27:34 +08:00
.result-item
display: flex
align-items: center
justify-content: space-between
2026-03-12 17:10:34 +08:00
padding: 48rpx 0
border-bottom: 3rpx solid #ebebeb
2025-11-10 15:27:34 +08:00
.item-content
flex: 1
.item-name
2026-03-12 17:10:34 +08:00
font-size: 48rpx
2025-11-10 15:27:34 +08:00
color: #333
.item-checkbox
2026-03-12 17:10:34 +08:00
width: 72rpx
height: 72rpx
border: 3rpx solid #ddd
2025-11-10 15:27:34 +08:00
border-radius: 50%
display: flex
align-items: center
justify-content: center
2026-03-12 17:10:34 +08:00
margin-left: 36rpx
2025-11-10 15:27:34 +08:00
&.checked
background-color: #256bfa
border-color: #256bfa
.check-icon
color: #fff
2026-03-12 17:10:34 +08:00
font-size: 48rpx
2025-11-10 15:27:34 +08:00
font-weight: bold
.empty-state
display: flex
align-items: center
justify-content: center
2026-03-12 17:10:34 +08:00
min-height: 600rpx
2025-11-10 15:27:34 +08:00
.empty-text
2026-03-12 17:10:34 +08:00
font-size: 42rpx
2025-11-10 15:27:34 +08:00
color: #999
.bottom-bar
flex-shrink: 0
background-color: #fff
2026-03-12 17:10:34 +08:00
border-top: 3rpx solid #ebebeb
padding: 36rpx
box-shadow: 0 -4rpx 18rpx rgba(0, 0, 0, 0.05) // 添加阴影,让底部栏更明显
2025-11-10 15:27:34 +08:00
.selected-skills
display: flex
flex-wrap: wrap
2026-03-12 17:10:34 +08:00
margin-bottom: 36rpx
min-height: 90rpx
2025-11-10 15:27:34 +08:00
.skill-tag
display: inline-flex
align-items: center
2026-03-12 17:10:34 +08:00
padding: 18rpx 36rpx
margin-right: 24rpx
margin-bottom: 24rpx
2025-11-10 15:27:34 +08:00
background-color: #e8f4ff
2026-03-12 17:10:34 +08:00
border-radius: 48rpx
border: 3rpx solid #256bfa
2025-11-10 15:27:34 +08:00
.tag-text
2026-03-12 17:10:34 +08:00
font-size: 39rpx
2025-11-10 15:27:34 +08:00
color: #256bfa
2026-03-12 17:10:34 +08:00
margin-right: 18rpx
2025-11-10 15:27:34 +08:00
.tag-close
2026-03-12 17:10:34 +08:00
font-size: 48rpx
2025-11-10 15:27:34 +08:00
color: #256bfa
line-height: 1
cursor: pointer
.action-buttons
display: flex
2026-03-12 17:10:34 +08:00
gap: 36rpx
2025-11-10 15:27:34 +08:00
.btn-cancel, .btn-confirm
flex: 1
2026-03-12 17:10:34 +08:00
height: 132rpx
border-radius: 18rpx
font-size: 48rpx
2025-11-10 15:27:34 +08:00
border: none
.btn-cancel
background-color: #f5f5f5
color: #666
.btn-confirm
background-color: #256bfa
color: #fff
&.disabled
background-color: #ccc
color: #999
</style>