mrr.sj.front/pages/search_common/search.vue

363 lines
9.5 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 class="search-page">
<view class="search-header" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="header-content">
<image class="back-icon" src="/static/images/back.png" @tap="goBack"></image>
<view class="search-box">
<input
class="search-input"
v-model="keyword"
:placeholder="placeholderText"
placeholder-class="search-placeholder"
focus
confirm-type="search"
@confirm="handleSearch"
/>
<!-- <view v-if="keyword" class="clear-icon" @tap="clearSearch">✕</view> -->
<text class="search-btn" @tap="handleSearch">搜索</text>
</view>
</view>
</view>
<view class="nav-placeholder" :style="{ height: navHeight + 'px' }"></view>
<view class="result-container">
<view v-if="loading && page === 1" class="loading-tips">加载中...</view>
<view v-else-if="searchType === 'notice' && resultList.length > 0">
<notice-card
v-for="item in resultList"
:key="item.id"
:item="item"
@click="goToDetail(item)"
/>
</view>
<view v-else-if="(searchType === 'brand' || searchType === 'collection_brand') && resultList.length > 0" class="brand-grid">
<brand-card
class="brand-card-wrap"
v-for="item in resultList"
:key="item.id"
:item="item"
:tagKeys="item.tagKeys || []"
@click="goToDetail(item)"
/>
</view>
<view v-else-if="(searchType === 'skill' || searchType === 'collection_course') && resultList.length > 0" class="course-list">
<skill-card
v-for="item in resultList"
:key="item.id"
:item="item"
@click="goToDetail(item)"
/>
</view>
<view v-else-if="!loading && hasSearched && resultList.length === 0" class="empty-state">
<image class="empty-icon" src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/4e4320d1-cc5c-4312-890c-f1eb8381fdd2.png"></image>
<text>未搜索到相关内容哦~</text>
</view>
<view v-if="resultList.length > 0" class="load-more-text">
<text v-if="loading && page > 1">正在加载更多...</text>
<text v-else-if="!hasMore">没有更多了</text>
</view>
</view>
</view>
</template>
<script>
import BrandCard from "@/pages/home/components/brand-card.vue";
import SkillCard from "@/pages/home/components/skill-card.vue";
import NoticeCard from "@/pages/home/components/notice-card.vue";
import request from '@/utils/request';
export default {
components: {
BrandCard,
SkillCard,
NoticeCard
},
data() {
return {
statusBarHeight: 0,
navHeight: 0,
searchType: '',
keyword: '',
resultList: [],
loading: false,
hasSearched: false,
// 分页相关
page: 1,
limit: 10,
hasMore: true
};
},
computed: {
placeholderText() {
const map = {
'notice': '搜索公告通知',
'brand': '搜索精选品牌',
'skill': '搜索技能课程',
'collection_brand': '搜索收藏品牌的内容',
'collection_course': '搜索收藏课程的内容'
};
return map[this.searchType] || '请输入搜索内容';
}
},
onLoad(options) {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight;
this.navHeight = this.statusBarHeight + 44;
if (options.type) {
this.searchType = options.type;
}
},
onReachBottom() {
if (this.hasMore && !this.loading) {
this.page++;
this.loadData(false);
}
},
methods: {
goBack() {
uni.navigateBack();
},
clearSearch() {
this.keyword = '';
this.resultList = [];
this.hasSearched = false;
this.page = 1;
this.hasMore = true;
},
handleSearch() {
if (!this.keyword.trim()) {
return uni.showToast({ title: '请输入搜索内容', icon: 'none' });
}
this.hasSearched = true;
this.page = 1;
this.hasMore = true;
this.loadData(true);
},
async loadData(isReset) {
this.loading = true;
try {
let apiUrl = '';
let params = { page: this.page, limit: this.limit };
switch (this.searchType) {
case 'notice':
apiUrl = '/sj/notice/list';
params.title = this.keyword;
break;
case 'brand':
apiUrl = '/sj/brand/list';
params.name = this.keyword;
break;
case 'skill':
apiUrl = '/sj/skill/list';
params.name = this.keyword;
break;
case 'collection_brand':
apiUrl = '/sj/brand/collectList';
params.name = this.keyword;
break;
case 'collection_course':
apiUrl = '/sj/skill/collectList';
params.name = this.keyword;
break;
default:
throw new Error('未知的搜索类型');
}
const res = await request.post(apiUrl, params);
if (res.code === 200 && res.data) {
const list = res.data.list || [];
// 文档里总数叫 count不是 total
const totalCount = res.data.count || 0;
let formattedList = list;
if (this.searchType === 'brand' || this.searchType === 'collection_brand') {
formattedList = list.map(item => {
let tags = [];
if (item.franchise_state === 1) tags.push('franchise');
if (item.student_state === 1) tags.push('recruit');
if (tags.length === 0) tags.push('more');
return {
...item,
name: item.name,
bgSrc: item.cover_img || 'https://dummyimage.com/300x200/ccc/fff.png',
avatar: item.logo_img || 'https://dummyimage.com/100x100/ccc/fff.png',
tagKeys: tags
};
});
} else if (this.searchType === 'skill' || this.searchType === 'collection_course') {
formattedList = list.map(item => ({
...item,
title: item.name,
coverSrc: item.cover_img || 'https://dummyimage.com/300x200/ccc/fff.png',
type: item.link_name || (item.link_type === 1 ? '平台课程' : '品牌课程'),
views: item.browse_num || 0,
typeIcon: item.link_logo || ''
}));
}
if (isReset) {
this.resultList = formattedList;
} else {
this.resultList = [...this.resultList, ...formattedList];
}
// 根据接口文档返回的 count 来判断是否还有更多
this.hasMore = this.resultList.length < totalCount;
} else {
if (isReset) this.resultList = [];
this.hasMore = false;
}
} catch (e) {
console.error('搜索异常:', e);
uni.showToast({ title: '搜索失败,请重试', icon: 'none' });
} finally {
this.loading = false;
}
},
goToDetail(item) {
if (!item.id) return;
if (this.searchType === 'notice') {
uni.navigateTo({ url: `/pages/notice/detail?id=${item.id}` });
} else if (this.searchType === 'brand' || this.searchType === 'collection_brand') {
uni.navigateTo({ url: `/pages/brand/detail?id=${item.id}` });
} else if (this.searchType === 'skill' || this.searchType === 'collection_course') {
uni.navigateTo({ url: `/pages/skill/detail?id=${item.id}` });
}
}
}
};
</script>
<style lang="scss" scoped>
.search-page {
min-height: 100vh;
background: #F5F5F5;
}
.search-header {
position: fixed;
top: 0;
left: 0;
right: 0;
background-color: #F5F5F5;
z-index: 999;
.header-content {
height: 44px;
display: flex;
align-items: center;
padding: 0 24rpx;
.back-icon {
width: 28rpx;
height: 28rpx;
padding-right: 20rpx;
}
.search-box {
flex: 1;
height: 74rpx;
background: #fff;
border-radius: 37rpx;
display: flex;
align-items: center;
padding: 0 6rpx 0 24rpx;
.search-input {
flex: 1;
font-size: 28rpx;
}
.search-placeholder {
color: #CCCCCC;
}
.clear-icon {
font-size: 28rpx;
color: #999;
padding: 10rpx 20rpx;
}
}
.search-btn {
width: 110rpx;
height: 62rpx;
line-height: 62rpx;
background: #FF4767;
border-radius: 31rpx;
font-size: 28rpx;
color: #fff;
text-align: center;
}
}
}
.result-container {
padding: 24rpx;
}
.loading-tips {
text-align: center;
padding: 100rpx 0;
color: #999;
font-size: 28rpx;
}
.brand-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.brand-card-wrap { width: 48.5%; margin-bottom: 24rpx; }
}
.course-list {
display: flex;
flex-direction: column;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
.empty-icon {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
text {
font-size: 28rpx;
color: #999;
}
}
.load-more-text {
text-align: center;
padding: 30rpx 0;
font-size: 24rpx;
color: #999;
}
</style>