baimacms/pages/search/search.vue

993 lines
23 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-container">
<!-- 头部搜索栏 -->
<view class="search-header">
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="header-main">
<view class="back-btn" @click="goBack" hover-class="back-btn-hover">
<text class="back-arrow"></text>
</view>
<view class="search-input-wrap">
<text class="search-icon">🔍</text>
<input class="search-input" type="text" v-model="keyword" placeholder="输入关键词或粘贴商品标题"
confirm-type="search" @confirm="onSearch(keyword)" :focus="true" />
</view>
<view class="search-btn-red" @click="onSearch(keyword)">搜索</view>
</view>
<!-- 平台切换Tab -->
<view class="platform-tabs">
<view class="tab-item" :class="{ 'active': activePlatform === 0 }" @click="activePlatform = 0">淘宝</view>
<view class="tab-item" :class="{ 'active': activePlatform === 1 }" @click="activePlatform = 1">京东</view>
<view class="tab-item" :class="{ 'active': activePlatform === 2 }" @click="activePlatform = 2">拼多多</view>
<view class="tab-item" :class="{ 'active': activePlatform === 3 }" @click="activePlatform = 3">抖音电商</view>
<view class="active-line" :style="{ left: tabLineLeft + '%' }"></view>
</view>
</view>
<scroll-view scroll-y class="search-body" :style="{ paddingTop: (statusBarHeight + 96) + 'px' }"
@scrolltolower="onScrollBottom">
<!-- 搜索结果列表 -->
<block v-if="isSearching">
<!-- 排序栏 -->
<view class="filter-bar">
<view class="filter-item" :class="{ 'active': sortType === 1 }" @click="changeSort(1)">综合</view>
<view class="filter-item" :class="{ 'active': sortType === 2 }" @click="changeSort(2)">销量</view>
<view class="filter-item" :class="{ 'active': sortType === 3 }" @click="changeSort(3)">
券后价
<view class="price-arrows">
<text class="up" :class="{ 'hl': sortType === 3 && priceOrder === 'asc' }">▲</text>
<text class="down" :class="{ 'hl': sortType === 3 && priceOrder === 'desc' }">▼</text>
</view>
</view>
</view>
<!-- 高级筛选 -->
<view class="sub-filter-row">
<view class="sub-filter-item" :class="{ 'active': filterHasCoupon }" @click="toggleFilter('coupon')">有券</view>
<view class="sub-filter-item" :class="{ 'active': filterIsFlagship }" @click="toggleFilter('flagship')">旗舰店</view>
<view class="sub-filter-item" :class="{ 'active': filterIsTmall }" @click="toggleFilter('tmall')">天猫</view>
<view class="sub-filter-item" :class="{ 'active': filterIsBrand }" @click="toggleFilter('brand')">品牌</view>
</view>
<!-- 商品列表 -->
<view class="goods-list">
<view class="goods-item" v-for="(goods, idx) in searchResults" :key="idx" @click="goToDetail(goods.id)">
<view class="g-img-left">
<image class="goods-img" :src="goods.image" mode="aspectFill"></image>
</view>
<view class="goods-info">
<view class="goods-title-row">
<text class="shop-type-tag" :class="goods.shopType === '天猫' ? 'tag-tmall' : 'tag-taobao'">{{ goods.shopType }}</text>
<text class="g-title-text">{{ goods.title }}</text>
</view>
<view class="labels-row" v-if="goods.labels && goods.labels.length > 0">
<text class="label-tag" v-for="(label, lIdx) in goods.labels.slice(0, 2)" :key="lIdx">{{ label }}</text>
</view>
<view class="goods-price-section">
<view class="price-main">
<text class="price-tip">{{ goods.couponValue > 0 ? '券后' : '抢购价' }}</text>
<text class="price-symbol">¥</text>
<text class="price-integer">{{ goods.finalPrice }}</text>
<view class="coupon-box" v-if="goods.couponValue > 0">
<text class="coupon-icon">券</text>
<text class="coupon-txt">{{ goods.couponValue }}元</text>
</view>
</view>
</view>
<view class="goods-bottom-info">
<text class="sales">已售{{ goods.sales }}件</text>
<text class="split-line">|</text>
<text class="shop-name">店铺:{{ goods.shopName }}</text>
</view>
</view>
</view>
</view>
<view class="loading-status">
<view v-if="loading" class="loading-text">加载中...</view>
<view v-else-if="searchResults.length === 0" class="empty-text">暂无相关商品</view>
<view v-else-if="finished" class="finished-text">-- 已经到底啦 --</view>
</view>
<view class="footer-placeholder"></view>
</block>
<block v-else>
<!-- 历史搜索 -->
<view class="section history-section" v-if="historyList.length > 0">
<view class="section-title">
<text>历史搜索</text>
<text class="clear-history" @click="clearHistory">清空</text>
</view>
<view class="tag-list">
<view class="tag-item" v-for="(word, index) in historyList" :key="index" @click="onSearch(word)">
{{ word }}
</view>
</view>
</view>
<!-- 轻松省钱三部曲 -->
<view class="steps-tip">
<text class="steps-title">轻松省钱三部曲</text>
<view class="steps-row">
<text class="step-item"><text class="step-num">1.</text>输入关键词或商品标题</text>
<text class="step-arrow">▶</text>
<text class="step-item"><text class="step-num">2.</text>点击搜索查询</text>
<text class="step-arrow">▶</text>
<text class="step-item"><text class="step-num">3.</text>领券省钱购物</text>
</view>
</view>
<!-- 热门搜索 (固定的词条文字) -->
<view class="section hot-tags-section">
<view class="section-title">热门搜索</view>
<view class="tag-list">
<view class="tag-item" v-for="(word, index) in hotTags" :key="index" @click="onSearch(word)">
{{ word }}
</view>
</view>
</view>
<!-- 榜单与话题双栏 -->
<view class="dual-columns">
<!-- 全网热搜 (左侧) -->
<view class="column-card rank-card">
<view class="card-header">
<text class="card-icon">📢</text>
<text class="card-title">全网热搜</text>
<text class="card-sub">今日热搜全知道</text>
</view>
<view class="rank-list">
<view class="rank-item" v-for="(item, index) in rankingList" :key="index" @click="onSearch(item.keyword)">
<view class="rank-num">
<image v-if="index < 3" :src="'https://img.bc.haodanku.com/cms_web/rank_' + (index + 1) + '.png'" mode="aspectFit" class="rank-icon-img"></image>
<text v-else class="num-txt">{{ index + 1 }}</text>
</view>
<image class="rank-img" :src="item.itempic" mode="aspectFill"></image>
<text class="rank-name">{{ item.keyword }}</text>
</view>
</view>
</view>
<!-- 热点话题 (右侧) -->
<view class="column-card topic-card">
<view class="card-header">
<text class="card-icon">💬</text>
<text class="card-title">热点话题</text>
<text class="card-sub">热点资讯</text>
</view>
<view class="topic-list">
<view class="topic-item" v-for="(item, index) in hotThemes" :key="index">
<view class="topic-num" :class="'num-' + (index + 1)">{{ index + 1 }}</view>
<text class="topic-title">{{ item.title }}</text>
</view>
</view>
</view>
</view>
<view class="copyright">
<text>© 粉丝福利购专属优惠商城</text>
</view>
<view class="footer-placeholder"></view>
</block>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
statusBarHeight: 44,
keyword: '',
activePlatform: 0,
historyList: [],
hotTags: ['风扇', '牙膏', '面膜', '洗发水', '保温杯', '蚊香液', '洗脸巾', '四件套'],
rankingList: [],
hotThemes: [],
// 搜索结果相关
isSearching: false,
searchResults: [],
page: 1,
pageSize: 20,
numPage: 0,
finished: false,
loading: false,
currentKeyword: '',
// 排序
sortType: 1, // 1:综合 2:销量 3:券后价
priceOrder: 'desc',
// 筛选
filterHasCoupon: false,
filterIsFlagship: false,
filterIsTmall: false,
filterIsBrand: false
}
},
watch: {
keyword(val) {
// 清空输入框时回到默认视图
if (!val) {
this.isSearching = false;
this.searchResults = [];
this.currentKeyword = '';
}
},
activePlatform() {
// 切换平台时若已有关键词,重新搜索
if (this.isSearching && this.currentKeyword) {
this.doSearch(this.currentKeyword, true);
}
}
},
computed: {
tabLineLeft() {
// 淘宝 京东 拼多多 抖音 对应的中心点位置
const positions = [12.5, 37.5, 62.5, 87.5];
return positions[this.activePlatform];
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync();
this.statusBarHeight = sysInfo.statusBarHeight || 44;
this.loadHistory();
this.getRankingList();
this.getHotThemes();
},
methods: {
goBack() {
uni.navigateBack();
},
loadHistory() {
const history = uni.getStorageSync('search_history');
if (history) {
this.historyList = JSON.parse(history);
}
},
saveHistory(word) {
if (!word.trim()) return;
let list = [...this.historyList];
const idx = list.indexOf(word);
if (idx !== -1) {
list.splice(idx, 1);
}
list.unshift(word);
if (list.length > 10) list = list.slice(0, 10);
this.historyList = list;
uni.setStorageSync('search_history', JSON.stringify(list));
},
getRankingList() {
uni.request({
url: 'https://api.cmspro.haodanku.com/search/searchRankingList?cid=qOstW90',
success: (res) => {
if (res.data && res.data.code === 200 && res.data.data.ranking_list) {
this.rankingList = res.data.data.ranking_list.slice(0, 10);
}
}
});
},
getHotThemes() {
uni.request({
url: 'https://api.cmspro.haodanku.com/index/hotTheme?cid=qOstW90',
success: (res) => {
if (res.data && res.data.code === 200) {
this.hotThemes = res.data.data.slice(0, 10);
}
}
});
},
onSearch(word) {
if (!word || !word.trim()) {
uni.showToast({ title: '请输入搜索内容', icon: 'none' });
return;
}
const trimmed = word.trim();
this.keyword = trimmed;
this.saveHistory(trimmed);
this.doSearch(trimmed, true);
},
doSearch(word, reset) {
if (this.loading) return;
if (reset) {
this.page = 1;
this.numPage = 0;
this.finished = false;
this.searchResults = [];
this.currentKeyword = word;
this.isSearching = true;
} else if (this.finished) {
return;
}
this.loading = true;
// 排序参数0:综合 3:销量 8:券后价升序 9:券后价降序
let sortParam = 0;
if (this.sortType === 2) sortParam = 3;
else if (this.sortType === 3) sortParam = this.priceOrder === 'asc' ? 8 : 9;
// 筛选参数
const data = {
page: this.page,
page_size: this.pageSize,
keyword: encodeURIComponent(word), // 双重编码data 序列化时会再次 encode
sort: sortParam,
cid: 'qOstW90'
};
if (this.filterHasCoupon) data.filtrate_type = 16;
if (this.filterIsBrand) data.is_brand = 1;
const shopTypes = [];
if (this.filterIsFlagship) shopTypes.push(1);
if (this.filterIsTmall) shopTypes.push(2);
if (shopTypes.length > 0) data.shoptype = shopTypes.join(',');
uni.request({
url: 'https://api.cmspro.haodanku.com/find/allItemList',
data,
success: (res) => {
const body = res.data || {};
if (body.code === 200 && body.data) {
if (body.data.num_page) this.numPage = body.data.num_page;
const rawList = body.data.item_info || [];
const list = rawList.map(item => ({
id: item.id,
itemid: item.itemid,
image: item.itempic,
title: item.itemshorttitle && item.itemshorttitle.length > 18
? item.itemshorttitle.substring(0, 18) + '...'
: (item.itemshorttitle || item.itemtitle),
finalPrice: item.itemendprice,
couponValue: item.couponmoney || 0,
sales: item.itemsale >= 10000
? (item.itemsale / 10000).toFixed(1) + '万'
: item.itemsale,
shopType: item.shoptype === 'B' ? '天猫' : '淘宝',
shopName: item.shopname,
labels: item.label || []
}));
if (list.length === 0) {
this.finished = true;
} else {
this.searchResults = this.searchResults.concat(list);
this.page += 1;
// 通过 num_page 或返回数量判断是否结束
if (this.numPage > 0 && this.page > this.numPage) {
this.finished = true;
} else if (list.length < this.pageSize) {
this.finished = true;
}
}
} else {
this.finished = true;
if (body.code !== 200) {
uni.showToast({ title: body.msg || '搜索失败', icon: 'none' });
}
}
},
fail: () => {
uni.showToast({ title: '网络错误', icon: 'none' });
},
complete: () => {
this.loading = false;
}
});
},
onScrollBottom() {
if (this.isSearching && !this.finished && !this.loading && this.currentKeyword) {
this.doSearch(this.currentKeyword, false);
}
},
changeSort(type) {
if (type === 3 && this.sortType === 3) {
this.priceOrder = this.priceOrder === 'asc' ? 'desc' : 'asc';
} else {
this.sortType = type;
if (type === 3) this.priceOrder = 'desc';
}
if (this.currentKeyword) this.doSearch(this.currentKeyword, true);
},
toggleFilter(type) {
if (type === 'coupon') this.filterHasCoupon = !this.filterHasCoupon;
else if (type === 'flagship') this.filterIsFlagship = !this.filterIsFlagship;
else if (type === 'tmall') this.filterIsTmall = !this.filterIsTmall;
else if (type === 'brand') this.filterIsBrand = !this.filterIsBrand;
if (this.currentKeyword) this.doSearch(this.currentKeyword, true);
},
goToDetail(id) {
uni.navigateTo({
url: `/pages/detail/detail?id=${id}`
});
},
clearHistory() {
uni.showModal({
title: '提示',
content: '确定清空搜索历史?',
success: (r) => {
if (r.confirm) {
this.historyList = [];
uni.removeStorageSync('search_history');
}
}
});
}
}
}
</script>
<style scoped>
.search-container {
width: 100%;
height: 100vh;
background-color: #f8f8f8;
}
/* 头部 */
.search-header {
position: fixed;
top: 0;
left: 0;
right: 0;
background: #ffffff;
z-index: 100;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.02);
}
.header-main {
height: 88rpx;
display: flex;
align-items: center;
padding: 0 24rpx;
}
.back-btn {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: background-color 0.2s;
}
.back-btn-hover {
background-color: #e8e8e8;
}
.back-arrow {
font-size: 44rpx;
color: #333;
line-height: 1;
font-weight: 500;
margin-top: -4rpx;
margin-left: -2rpx;
}
.search-input-wrap {
flex: 1;
height: 72rpx;
background-color: #ffffff;
border: 2rpx solid #ff4d6d;
border-radius: 36rpx;
display: flex;
align-items: center;
padding: 0 24rpx;
margin: 0 20rpx;
}
.search-icon {
font-size: 28rpx;
color: #ff4d6d;
margin-right: 12rpx;
}
.search-input {
flex: 1;
height: 100%;
font-size: 26rpx;
color: #333;
}
.search-btn-red {
width: 120rpx;
height: 64rpx;
background: linear-gradient(to right, #ff715a, #ff416c);
color: #ffffff;
font-size: 28rpx;
font-weight: bold;
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* 平台Tab */
.platform-tabs {
height: 80rpx;
display: flex;
position: relative;
border-bottom: 1rpx solid #f2f2f2;
}
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #666;
transition: all 0.3s;
}
.tab-item.active {
color: #ff416c;
font-weight: bold;
font-size: 30rpx;
}
.active-line {
position: absolute;
bottom: 8rpx;
width: 40rpx;
height: 4rpx;
background-color: #ff416c;
border-radius: 4rpx;
transform: translateX(-50%);
transition: left 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
/* 内容区 */
.search-body {
height: 100%;
box-sizing: border-box;
}
.section {
padding: 30rpx 24rpx 10rpx;
background-color: #ffffff;
}
.history-section {
padding-top: 20rpx;
}
.section-title {
font-size: 26rpx;
color: #999;
margin-bottom: 20rpx;
}
.tag-list {
display: flex;
flex-wrap: wrap;
}
.tag-item {
padding: 10rpx 30rpx;
background-color: #f1f1f1;
border-radius: 30rpx;
font-size: 24rpx;
color: #333;
margin-right: 20rpx;
margin-bottom: 20rpx;
}
/* 双栏卡片 */
.dual-columns {
display: flex;
padding: 20rpx;
justify-content: space-between;
}
.column-card {
width: 48.5%;
background: linear-gradient(180deg, #fff4eb 0%, #ffffff 40%);
border: 2rpx solid #ffe1cc;
border-radius: 16rpx;
padding: 20rpx;
box-sizing: border-box;
}
.topic-card {
background: linear-gradient(180deg, #fff1ee 0%, #ffffff 40%);
border-color: #ffd8d0;
}
.card-header {
display: flex;
align-items: center;
margin-bottom: 24rpx;
}
.card-icon {
font-size: 30rpx;
margin-right: 8rpx;
}
.card-title {
font-size: 28rpx;
font-weight: bold;
color: #d16d3f;
white-space: nowrap;
}
.topic-card .card-title {
color: #e66b5b;
}
.card-sub {
font-size: 20rpx;
color: #d1a38f;
margin-left: 8rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 轻松省钱三部曲 */
.steps-tip {
margin: 16rpx 20rpx 0;
padding: 16rpx 20rpx;
background: linear-gradient(135deg, #fff7f0, #fff0f3);
border-radius: 12rpx;
border: 2rpx solid #ffe1d6;
}
.steps-title {
display: block;
font-size: 24rpx;
font-weight: bold;
color: #ff6b3d;
margin-bottom: 10rpx;
}
.steps-row {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.step-item {
font-size: 22rpx;
color: #888;
}
.step-num {
color: #ff6b3d;
font-weight: bold;
margin-right: 4rpx;
}
.step-arrow {
font-size: 18rpx;
color: #ffb199;
margin: 0 8rpx;
}
/* 全网热搜列表 */
.rank-item {
display: flex;
align-items: center;
margin-bottom: 30rpx;
}
.rank-num {
width: 40rpx;
display: flex;
justify-content: center;
margin-right: 10rpx;
}
.rank-icon-img {
width: 32rpx;
height: 32rpx;
}
.num-txt {
font-size: 26rpx;
font-style: italic;
font-weight: bold;
color: #ff9d00;
}
.rank-img {
width: 80rpx;
height: 80rpx;
border-radius: 8rpx;
margin-right: 16rpx;
background-color: #f5f5f5;
}
.rank-name {
font-size: 26rpx;
color: #333;
font-weight: bold;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 热点话题列表 */
.topic-item {
display: flex;
align-items: center;
margin-bottom: 36rpx;
}
.topic-num {
width: 36rpx;
height: 36rpx;
font-size: 24rpx;
font-weight: bold;
margin-right: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.topic-num.num-1 { color: #ffb800; font-size: 32rpx; }
.topic-num.num-2 { color: #c0c0c0; font-size: 32rpx; }
.topic-num.num-3 { color: #d1a38f; font-size: 32rpx; }
.topic-num.num-4 { color: #ff9d00; }
.topic-num.num-5 { color: #ff9d00; }
.topic-title {
font-size: 26rpx;
color: #333;
font-weight: bold;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.copyright {
text-align: center;
padding: 40rpx 0;
font-size: 24rpx;
color: #ccc;
}
.footer-placeholder {
height: 60rpx;
}
/* 历史搜索标题行 */
.history-section .section-title {
display: flex;
justify-content: space-between;
align-items: center;
}
.clear-history {
font-size: 24rpx;
color: #bbb;
padding: 6rpx 12rpx;
}
/* 排序栏 */
.filter-bar {
display: flex;
background: #ffffff;
height: 80rpx;
align-items: center;
border-bottom: 1rpx solid #f1f1f1;
}
.filter-bar .filter-item {
flex: 1;
text-align: center;
font-size: 26rpx;
color: #666;
display: flex;
align-items: center;
justify-content: center;
}
.filter-bar .filter-item.active {
color: #ff416c;
font-weight: bold;
}
.price-arrows {
display: flex;
flex-direction: column;
margin-left: 6rpx;
line-height: 1;
}
.price-arrows text {
font-size: 16rpx;
color: #ccc;
}
.price-arrows text.hl {
color: #ff416c;
}
/* 高级筛选 */
.sub-filter-row {
display: flex;
background: #ffffff;
padding: 10rpx 20rpx 20rpx;
align-items: center;
}
.sub-filter-item {
padding: 6rpx 20rpx;
background: #f5f6f8;
color: #333;
font-size: 24rpx;
border-radius: 8rpx;
margin-right: 16rpx;
transition: all 0.2s;
}
.sub-filter-item.active {
background: linear-gradient(to right, #ff758c, #ff416c);
color: #ffffff;
font-weight: 500;
}
/* 商品列表 */
.goods-list {
background: #ffffff;
}
.goods-item {
display: flex;
padding: 24rpx;
border-bottom: 1rpx solid #f8f8f8;
background: #ffffff;
}
.g-img-left {
flex-shrink: 0;
width: 240rpx;
height: 240rpx;
border-radius: 12rpx;
overflow: hidden;
background: #f0f0f0;
}
.goods-img {
width: 100%;
height: 100%;
}
.goods-info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 4rpx 0;
min-width: 0;
}
.goods-title-row {
display: flex;
align-items: center;
line-height: 1.4;
}
.shop-type-tag {
font-size: 18rpx;
padding: 0 8rpx;
border-radius: 4rpx;
margin-right: 8rpx;
color: #fff;
flex-shrink: 0;
line-height: 28rpx;
}
.tag-tmall {
background: #ff0036;
}
.tag-taobao {
background: #ff7800;
}
.g-title-text {
font-size: 26rpx;
color: #333;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.labels-row {
display: flex;
flex-wrap: wrap;
margin-top: 12rpx;
}
.label-tag {
font-size: 20rpx;
color: #ff416c;
background: #fff5f7;
border: 1rpx solid #ffd6de;
padding: 2rpx 12rpx;
border-radius: 20rpx;
margin-right: 12rpx;
margin-bottom: 8rpx;
}
.goods-price-section {
margin-top: auto;
padding-bottom: 8rpx;
}
.price-main {
display: flex;
align-items: baseline;
}
.price-tip {
font-size: 22rpx;
color: #ff416c;
margin-right: 4rpx;
}
.price-symbol {
font-size: 24rpx;
color: #ff416c;
font-weight: bold;
}
.price-integer {
font-size: 40rpx;
color: #ff416c;
font-weight: bold;
margin-right: 12rpx;
}
.coupon-box {
display: flex;
align-items: center;
background: #ff416c;
border-radius: 4rpx;
overflow: hidden;
height: 32rpx;
}
.coupon-icon {
font-size: 20rpx;
color: #ffffff;
background: rgba(255, 255, 255, 0.2);
padding: 0 6rpx;
height: 100%;
display: flex;
align-items: center;
}
.coupon-txt {
font-size: 20rpx;
color: #ffffff;
padding: 0 8rpx;
}
.goods-bottom-info {
display: flex;
align-items: center;
font-size: 22rpx;
color: #999;
margin-top: 8rpx;
}
.split-line {
margin: 0 10rpx;
color: #eee;
}
.loading-status {
text-align: center;
padding: 40rpx 0;
font-size: 24rpx;
color: #999;
}
</style>