431 lines
13 KiB
Vue
431 lines
13 KiB
Vue
<template>
|
|
<view class="search-container">
|
|
<!-- 顶部搜索栏 -->
|
|
<view class="search-header">
|
|
<view class="search-box">
|
|
<text class="search-icon">🔍</text>
|
|
<u-input
|
|
v-model="searchKeyword"
|
|
placeholder="搜索课程、讲师..."
|
|
:clearable="true"
|
|
:focus="true"
|
|
@input="handleSearch"
|
|
@confirm="handleSearch"
|
|
/>
|
|
</view>
|
|
<text class="cancel-btn" @click="goBack">取消</text>
|
|
</view>
|
|
|
|
<!-- 搜索历史 -->
|
|
<view class="search-history" v-if="!searchKeyword && searchHistory.length > 0">
|
|
<view class="history-header">
|
|
<text class="history-title">搜索历史</text>
|
|
<text class="clear-history" @click="clearHistory">清空</text>
|
|
</view>
|
|
<view class="history-tags">
|
|
<view
|
|
v-for="(item, index) in searchHistory"
|
|
:key="index"
|
|
class="history-tag"
|
|
@click="selectHistory(item)"
|
|
>
|
|
<text>{{ item }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 搜索结果 -->
|
|
<view class="search-results" v-if="searchKeyword">
|
|
<view class="result-tabs">
|
|
<view
|
|
class="tab-item"
|
|
:class="{ active: activeTab === 'course' }"
|
|
@click="activeTab = 'course'"
|
|
>
|
|
<text>课程</text>
|
|
<text class="tab-count" v-if="courseResults.length > 0">({{ courseResults.length }})</text>
|
|
</view>
|
|
<view
|
|
class="tab-item"
|
|
:class="{ active: activeTab === 'exam' }"
|
|
@click="activeTab = 'exam'"
|
|
>
|
|
<text>考试</text>
|
|
<text class="tab-count" v-if="examResults.length > 0">({{ examResults.length }})</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 课程结果 -->
|
|
<view class="result-list" v-if="activeTab === 'course'">
|
|
<u-empty v-if="courseResults.length === 0 && !loading" mode="search" text="未找到相关课程"></u-empty>
|
|
<view
|
|
v-for="course in courseResults"
|
|
:key="course.id"
|
|
class="result-item"
|
|
@click="goToCourseDetail(course)"
|
|
>
|
|
<view class="item-content">
|
|
<text class="item-title">{{ course.courseName }}</text>
|
|
<text class="item-desc" v-if="course.description">{{ course.description }}</text>
|
|
<view class="item-meta">
|
|
<text class="meta-item" v-if="course.teacherName">讲师:{{ course.teacherName }}</text>
|
|
<text class="meta-item" v-if="course.subjectName">学科:{{ course.subjectName }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 考试结果 -->
|
|
<view class="result-list" v-if="activeTab === 'exam'">
|
|
<u-empty v-if="examResults.length === 0 && !loading" mode="search" text="未找到相关考试"></u-empty>
|
|
<view
|
|
v-for="exam in examResults"
|
|
:key="exam.id"
|
|
class="result-item"
|
|
@click="goToExamDetail(exam)"
|
|
>
|
|
<view class="item-content">
|
|
<text class="item-title">{{ exam.examName }}</text>
|
|
<view class="item-meta">
|
|
<text class="meta-item" v-if="exam.subjectName">学科:{{ exam.subjectName }}</text>
|
|
<text class="meta-item" v-if="exam.questionCount">题目数:{{ exam.questionCount }}题</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 热门搜索(无搜索关键词时显示) -->
|
|
<view class="hot-search" v-if="!searchKeyword && searchHistory.length === 0">
|
|
<view class="hot-title">热门搜索</view>
|
|
<view class="hot-tags">
|
|
<view
|
|
v-for="(item, index) in hotKeywords"
|
|
:key="index"
|
|
class="hot-tag"
|
|
@click="selectHotKeyword(item)"
|
|
>
|
|
<text>{{ item }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { getMyCourses } from '@/api/study/course.js'
|
|
import { getMyExams } from '@/api/study/exam.js'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
searchKeyword: '',
|
|
activeTab: 'course',
|
|
courseResults: [],
|
|
examResults: [],
|
|
loading: false,
|
|
searchHistory: [],
|
|
hotKeywords: ['课程', '考试', '学习', '练习']
|
|
}
|
|
},
|
|
onLoad(options) {
|
|
// 如果有传入关键词,直接搜索
|
|
if (options.keyword) {
|
|
this.searchKeyword = decodeURIComponent(options.keyword)
|
|
this.handleSearch()
|
|
}
|
|
// 加载搜索历史
|
|
this.loadSearchHistory()
|
|
},
|
|
methods: {
|
|
async handleSearch() {
|
|
if (!this.searchKeyword || this.searchKeyword.trim() === '') {
|
|
this.courseResults = []
|
|
this.examResults = []
|
|
return
|
|
}
|
|
|
|
const keyword = this.searchKeyword.trim()
|
|
|
|
// 保存搜索历史
|
|
this.saveSearchHistory(keyword)
|
|
|
|
this.loading = true
|
|
try {
|
|
// 并行搜索课程和考试
|
|
await Promise.all([
|
|
this.searchCourses(keyword),
|
|
this.searchExams(keyword)
|
|
])
|
|
} catch (error) {
|
|
console.error('搜索失败:', error)
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
|
|
async searchCourses(keyword) {
|
|
try {
|
|
const response = await getMyCourses()
|
|
if (response.code === 200) {
|
|
const courses = response.rows || response.data || []
|
|
// 本地搜索过滤
|
|
this.courseResults = courses.filter(course => {
|
|
const name = (course.courseName || '').toLowerCase()
|
|
const desc = (course.description || '').toLowerCase()
|
|
const teacher = (course.teacherName || '').toLowerCase()
|
|
const subject = (course.subjectName || '').toLowerCase()
|
|
const key = keyword.toLowerCase()
|
|
return name.includes(key) || desc.includes(key) || teacher.includes(key) || subject.includes(key)
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('搜索课程失败:', error)
|
|
this.courseResults = []
|
|
}
|
|
},
|
|
|
|
async searchExams(keyword) {
|
|
try {
|
|
const response = await getMyExams()
|
|
if (response.code === 200) {
|
|
const exams = response.rows || response.data || []
|
|
// 本地搜索过滤
|
|
this.examResults = exams.filter(exam => {
|
|
const name = (exam.examName || '').toLowerCase()
|
|
const subject = (exam.subjectName || '').toLowerCase()
|
|
const key = keyword.toLowerCase()
|
|
return name.includes(key) || subject.includes(key)
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('搜索考试失败:', error)
|
|
this.examResults = []
|
|
}
|
|
},
|
|
|
|
selectHistory(keyword) {
|
|
this.searchKeyword = keyword
|
|
this.handleSearch()
|
|
},
|
|
|
|
selectHotKeyword(keyword) {
|
|
this.searchKeyword = keyword
|
|
this.handleSearch()
|
|
},
|
|
|
|
saveSearchHistory(keyword) {
|
|
if (!keyword || keyword.trim() === '') return
|
|
|
|
let history = uni.getStorageSync('search_history') || []
|
|
// 移除重复项
|
|
history = history.filter(item => item !== keyword)
|
|
// 添加到开头
|
|
history.unshift(keyword)
|
|
// 最多保存10条
|
|
history = history.slice(0, 10)
|
|
uni.setStorageSync('search_history', history)
|
|
this.searchHistory = history
|
|
},
|
|
|
|
loadSearchHistory() {
|
|
this.searchHistory = uni.getStorageSync('search_history') || []
|
|
},
|
|
|
|
clearHistory() {
|
|
uni.removeStorageSync('search_history')
|
|
this.searchHistory = []
|
|
},
|
|
|
|
goToCourseDetail(course) {
|
|
uni.navigateTo({
|
|
url: `/pages/course/detail?id=${course.id}`
|
|
})
|
|
},
|
|
|
|
goToExamDetail(exam) {
|
|
uni.navigateTo({
|
|
url: `/pages/exam/detail?id=${exam.id}`
|
|
})
|
|
},
|
|
|
|
goBack() {
|
|
uni.navigateBack()
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.search-container {
|
|
padding: 20rpx;
|
|
padding-top: calc(20rpx + env(safe-area-inset-top));
|
|
background-color: #f5f7fa;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.search-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20rpx;
|
|
margin-bottom: 30rpx;
|
|
|
|
.search-box {
|
|
flex: 1;
|
|
background: #fff;
|
|
border-radius: 50rpx;
|
|
padding: 20rpx 30rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16rpx;
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
|
|
|
.search-icon {
|
|
font-size: 36rpx;
|
|
line-height: 1;
|
|
}
|
|
}
|
|
|
|
.cancel-btn {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
padding: 10rpx;
|
|
}
|
|
}
|
|
|
|
.search-history {
|
|
background: #fff;
|
|
border-radius: 20rpx;
|
|
padding: 30rpx;
|
|
margin-bottom: 30rpx;
|
|
|
|
.history-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20rpx;
|
|
|
|
.history-title {
|
|
font-size: 28rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
|
|
.clear-history {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
}
|
|
}
|
|
|
|
.history-tags {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 16rpx;
|
|
|
|
.history-tag {
|
|
padding: 12rpx 24rpx;
|
|
background: #f5f7fa;
|
|
border-radius: 30rpx;
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
}
|
|
}
|
|
}
|
|
|
|
.search-results {
|
|
.result-tabs {
|
|
display: flex;
|
|
background: #fff;
|
|
border-radius: 20rpx;
|
|
padding: 10rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
.tab-item {
|
|
flex: 1;
|
|
text-align: center;
|
|
padding: 20rpx;
|
|
border-radius: 12rpx;
|
|
font-size: 28rpx;
|
|
color: #666;
|
|
|
|
&.active {
|
|
background: linear-gradient(135deg, rgb(55 140 224) 0%, rgb(45 120 200) 100%);
|
|
color: #fff;
|
|
}
|
|
|
|
.tab-count {
|
|
font-size: 24rpx;
|
|
margin-left: 8rpx;
|
|
}
|
|
}
|
|
}
|
|
|
|
.result-list {
|
|
.result-item {
|
|
background: #fff;
|
|
border-radius: 20rpx;
|
|
padding: 30rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
.item-content {
|
|
.item-title {
|
|
display: block;
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
|
|
.item-desc {
|
|
display: block;
|
|
font-size: 26rpx;
|
|
color: #666;
|
|
margin-bottom: 12rpx;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.item-meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 20rpx;
|
|
|
|
.meta-item {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.hot-search {
|
|
background: #fff;
|
|
border-radius: 20rpx;
|
|
padding: 30rpx;
|
|
|
|
.hot-title {
|
|
font-size: 28rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.hot-tags {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 16rpx;
|
|
|
|
.hot-tag {
|
|
padding: 12rpx 24rpx;
|
|
background: linear-gradient(135deg, rgb(55 140 224) 0%, rgb(45 120 200) 100%);
|
|
border-radius: 30rpx;
|
|
font-size: 26rpx;
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|
|
|