优化个人中心页面
This commit is contained in:
388
pages/complete-info/skill-search.vue
Normal file
388
pages/complete-info/skill-search.vue
Normal file
@@ -0,0 +1,388 @@
|
||||
<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="selectedSkills.length > 0">
|
||||
已选择 {{ selectedSkills.length }}/3 个技能
|
||||
</view>
|
||||
</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="selectedSkills.length > 0">
|
||||
<view
|
||||
class="skill-tag"
|
||||
v-for="(skill, index) in selectedSkills"
|
||||
:key="index"
|
||||
>
|
||||
<text class="tag-text">{{ skill }}</text>
|
||||
<text class="tag-close" @click.stop="removeSkill(index)">×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-buttons">
|
||||
<button class="btn-cancel" @click="handleCancel">取消</button>
|
||||
<button
|
||||
class="btn-confirm"
|
||||
:class="{ 'disabled': selectedSkills.length === 0 }"
|
||||
@click="handleConfirm"
|
||||
:disabled="selectedSkills.length === 0"
|
||||
>
|
||||
确定
|
||||
</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 selectedSkills = ref([]);
|
||||
const isSearching = ref(false);
|
||||
let searchTimer = null;
|
||||
|
||||
onLoad((options) => {
|
||||
// 接收已选中的技能
|
||||
if (options.selected) {
|
||||
try {
|
||||
const skills = JSON.parse(decodeURIComponent(options.selected));
|
||||
if (Array.isArray(skills)) {
|
||||
selectedSkills.value = skills;
|
||||
}
|
||||
} 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 selectedSkills.value.includes(item.name);
|
||||
}
|
||||
|
||||
// 切换技能选择状态
|
||||
function toggleSelect(item) {
|
||||
const skillName = item.name;
|
||||
const index = selectedSkills.value.indexOf(skillName);
|
||||
|
||||
if (index > -1) {
|
||||
// 已选中,取消选择
|
||||
selectedSkills.value.splice(index, 1);
|
||||
} else {
|
||||
// 未选中,检查是否已达到最大数量
|
||||
if (selectedSkills.value.length >= 3) {
|
||||
$api.msg('最多只能选择3个技能');
|
||||
return;
|
||||
}
|
||||
// 添加选择
|
||||
selectedSkills.value.push(skillName);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除技能
|
||||
function removeSkill(index) {
|
||||
selectedSkills.value.splice(index, 1);
|
||||
}
|
||||
|
||||
// 取消操作
|
||||
function handleCancel() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
|
||||
// 确定操作
|
||||
function handleConfirm() {
|
||||
if (selectedSkills.value.length === 0) {
|
||||
$api.msg('请至少选择一个技能');
|
||||
return;
|
||||
}
|
||||
|
||||
// 通过事件总线传递选中的技能(技能字段值传name)
|
||||
uni.$emit('skillSelected', selectedSkills.value);
|
||||
|
||||
// 返回上一页
|
||||
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
|
||||
border-bottom: 2rpx solid #ebebeb
|
||||
|
||||
.search-box
|
||||
display: flex
|
||||
align-items: center
|
||||
padding: 24rpx
|
||||
background-color: #fff
|
||||
|
||||
.search-input
|
||||
flex: 1
|
||||
height: 72rpx
|
||||
padding: 0 24rpx
|
||||
background-color: #f5f5f5
|
||||
border-radius: 36rpx
|
||||
font-size: 28rpx
|
||||
color: #333
|
||||
|
||||
.search-icon
|
||||
margin-left: 24rpx
|
||||
padding: 16rpx 32rpx
|
||||
background-color: #256bfa
|
||||
color: #fff
|
||||
border-radius: 36rpx
|
||||
font-size: 28rpx
|
||||
|
||||
.selected-tip
|
||||
padding: 16rpx 24rpx
|
||||
background-color: #fff3cd
|
||||
color: #856404
|
||||
font-size: 24rpx
|
||||
text-align: center
|
||||
|
||||
.scroll-content
|
||||
flex: 1
|
||||
overflow: hidden
|
||||
height: 0 // 关键:让flex布局正确计算高度
|
||||
background-color: #fff
|
||||
|
||||
.scroll-inner
|
||||
padding-bottom: 40rpx
|
||||
|
||||
.result-list
|
||||
background-color: #fff
|
||||
padding: 0 24rpx
|
||||
|
||||
.result-item
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: space-between
|
||||
padding: 32rpx 0
|
||||
border-bottom: 2rpx solid #ebebeb
|
||||
|
||||
.item-content
|
||||
flex: 1
|
||||
|
||||
.item-name
|
||||
font-size: 32rpx
|
||||
color: #333
|
||||
|
||||
.item-checkbox
|
||||
width: 48rpx
|
||||
height: 48rpx
|
||||
border: 2rpx solid #ddd
|
||||
border-radius: 50%
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
margin-left: 24rpx
|
||||
|
||||
&.checked
|
||||
background-color: #256bfa
|
||||
border-color: #256bfa
|
||||
|
||||
.check-icon
|
||||
color: #fff
|
||||
font-size: 32rpx
|
||||
font-weight: bold
|
||||
|
||||
.empty-state
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
min-height: 400rpx
|
||||
|
||||
.empty-text
|
||||
font-size: 28rpx
|
||||
color: #999
|
||||
|
||||
.bottom-bar
|
||||
flex-shrink: 0
|
||||
background-color: #fff
|
||||
border-top: 2rpx solid #ebebeb
|
||||
padding: 24rpx
|
||||
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.05) // 添加阴影,让底部栏更明显
|
||||
|
||||
.selected-skills
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
margin-bottom: 24rpx
|
||||
min-height: 60rpx
|
||||
|
||||
.skill-tag
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
padding: 12rpx 24rpx
|
||||
margin-right: 16rpx
|
||||
margin-bottom: 16rpx
|
||||
background-color: #e8f4ff
|
||||
border-radius: 32rpx
|
||||
border: 2rpx solid #256bfa
|
||||
|
||||
.tag-text
|
||||
font-size: 26rpx
|
||||
color: #256bfa
|
||||
margin-right: 12rpx
|
||||
|
||||
.tag-close
|
||||
font-size: 32rpx
|
||||
color: #256bfa
|
||||
line-height: 1
|
||||
cursor: pointer
|
||||
|
||||
.action-buttons
|
||||
display: flex
|
||||
gap: 24rpx
|
||||
|
||||
.btn-cancel, .btn-confirm
|
||||
flex: 1
|
||||
height: 88rpx
|
||||
border-radius: 12rpx
|
||||
font-size: 32rpx
|
||||
border: none
|
||||
|
||||
.btn-cancel
|
||||
background-color: #f5f5f5
|
||||
color: #666
|
||||
|
||||
.btn-confirm
|
||||
background-color: #256bfa
|
||||
color: #fff
|
||||
|
||||
&.disabled
|
||||
background-color: #ccc
|
||||
color: #999
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user