Files
frontend-app/pages/rwa_package/insights/search-result.vue
T
2026-08-17 09:27:07 +08:00

530 lines
12 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="page-container">
<!-- 顶部状态栏安全高度 -->
<TopSafe bgColor="#ffffff"></TopSafe>
<!-- 1. 自定义导航栏:左侧返回箭头,中间“商品列表”标题 -->
<view class="custom-navbar">
<view class="nav-back flex-c" @click="onBack">
<u-icon name="arrow-left" size="36rpx" color="#333333"></u-icon>
</view>
<view class="nav-title">商品列表</view>
<view class="nav-right"></view>
</view>
<!-- 2. 搜索框区域 -->
<view class="search-bar-box">
<view class="search-inner">
<u-icon name="search" size="36rpx" color="#999999" class="search-icon"></u-icon>
<input class="search-input" v-model="searchInput" placeholder="请输入商品关键字"
placeholder-style="color: #999999; font-size: 28rpx;" confirm-type="search"
@confirm="onSearchBtnClick" />
<view class="search-btn flex-c" @click="onSearchBtnClick">
<text class="search-btn-text">搜索</text>
</view>
</view>
</view>
<!-- 3. 排序/筛选栏(右对齐:综合 / 价格) -->
<view class="filter-bar">
<view class="filter-item" :class="{ active: sortBy === 'relevance' }" @click="switchSort('relevance')">
<text class="filter-text">综合</text>
<view class="sort-icon-box">
<u-icon name="arrow-up-fill" size="12rpx"
:color="sortBy === 'relevance' && sortOrder === 'asc' ? '#7934f6' : '#cccccc'"
class="icon-up"></u-icon>
<u-icon name="arrow-down-fill" size="12rpx"
:color="sortBy === 'relevance' && sortOrder === 'desc' ? '#7934f6' : '#cccccc'"
class="icon-down"></u-icon>
</view>
</view>
<view class="filter-item" :class="{ active: sortBy === 'price' }" @click="switchSort('price')">
<text class="filter-text">价格</text>
<view class="sort-icon-box">
<u-icon name="arrow-up-fill" size="12rpx"
:color="sortBy === 'price' && sortOrder === 'asc' ? '#7934f6' : '#cccccc'"
class="icon-up"></u-icon>
<u-icon name="arrow-down-fill" size="12rpx"
:color="sortBy === 'price' && sortOrder === 'desc' ? '#7934f6' : '#cccccc'"
class="icon-down"></u-icon>
</view>
</view>
</view>
<!-- 4. 商品双列网格列表区域 -->
<scroll-view scroll-y class="product-scroll-view" @scrolltolower="loadMore" refresher-enabled
:refresher-triggered="isRefreshing" @refresherrefresh="onRefresh">
<view class="product-grid" v-if="dataList && dataList.length > 0">
<view class="product-card" v-for="(item, index) in dataList" :key="item.id || index"
@click="onJumpDetail(item)">
<!-- 商品大图 + 可选标签 -->
<view class="img-wrapper">
<image class="product-img"
:src="item.url || item.image || item.pic || '/static/common/default-goods.png'"
mode="aspectFill"></image>
<!-- <view class="image-tag" v-if="item.tagName || item.tag">
{{ item.tagName || item.tag }}
</view> -->
</view>
<!-- 商品信息 -->
<view class="card-info">
<view class="product-title">
{{ item.title || item.name || item.goodsName }}
</view>
<view class="price-sales-row">
<view class="price-box">
<text class="currency">¥</text>
<text class="price-val">{{ formatPrice(item.price || item.adPrice) }}</text>
</view>
<text class="sales-val"
v-if="item.sales !== undefined && item.sales !== null && item.sales !== ''">
全网销量{{ formatSales(item.sales) }}件
</text>
</view>
</view>
</view>
</view>
<!-- 加载状态 / 无数据提示 -->
<view class="empty-box" v-else-if="!loading">
<u-icon name="empty-data" size="140rpx" color="#cccccc"></u-icon>
<text class="empty-text">暂无相关商品</text>
</view>
<view class="loading-more-box" v-if="loading && dataList.length > 0">
<text class="loading-text">加载中...</text>
</view>
</scroll-view>
</view>
</template>
<script>
import TopSafe from '@/components/common/top-safe.nvue';
import { searchList } from '@/api/product.js';
export default {
components: {
TopSafe
},
data() {
return {
searchInput: '',
dataList: [],
loading: false,
isRefreshing: false,
sortBy: 'relevance', // 'relevance' | 'price'
sortOrder: 'asc', // 'asc' | 'desc'
page: 1,
pageSize: 20,
hasMore: true
};
},
onLoad(options) {
if (options && options.keyword) {
this.searchInput = decodeURIComponent(options.keyword);
}
this.fetchData();
},
methods: {
onBack() {
uni.navigateBack();
},
onSearchBtnClick() {
if (!this.searchInput || !this.searchInput.trim()) return;
this.saveSearchHistory(this.searchInput.trim());
this.page = 1;
this.hasMore = true;
this.fetchData();
},
saveSearchHistory(val) {
let historyStr = uni.getStorageSync('history') || '';
let historyList = historyStr ? historyStr.split(',').filter(item => item.trim() !== '') : [];
let idx = historyList.indexOf(val);
if (idx > -1) {
historyList.splice(idx, 1);
}
historyList.unshift(val);
if (historyList.length > 15) {
historyList.pop();
}
uni.setStorageSync('history', historyList.join(','));
},
async fetchData(isLoadMore = false) {
if (this.loading) return;
this.loading = true;
let params = {
keyword: this.searchInput.trim(),
page: this.page,
pageSize: this.pageSize,
sortBy: this.sortBy,
sortOrder: this.sortOrder
};
try {
const res = await searchList(params);
let list = [];
if (res && res.data) {
list = res.data;
} else if (Array.isArray(res)) {
list = res;
}
if (isLoadMore) {
this.dataList = [...this.dataList, ...list];
} else {
this.dataList = list;
}
if (list.length < this.pageSize) {
this.hasMore = false;
}
} catch (err) {
console.error('searchList error:', err);
} finally {
this.loading = false;
this.isRefreshing = false;
}
},
switchSort(type) {
if (type === 'relevance') {
if (this.sortBy === 'relevance') {
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
} else {
this.sortBy = 'relevance';
this.sortOrder = 'asc';
}
} else if (type === 'price') {
if (this.sortBy === 'price') {
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
} else {
this.sortBy = 'price';
this.sortOrder = 'asc';
}
}
this.page = 1;
this.hasMore = true;
this.fetchData();
},
onRefresh() {
this.isRefreshing = true;
this.page = 1;
this.hasMore = true;
this.fetchData();
},
loadMore() {
if (!this.hasMore || this.loading) return;
this.page++;
this.fetchData(true);
},
onJumpDetail(item) {
if (item && item.id) {
uni.navigateTo({
url: '/pages/other_package/productInfo/productInfo?id=' + item.id
});
}
},
formatPrice(val) {
if (val === undefined || val === null || val === '') return '0.00';
let num = parseFloat(val);
if (isNaN(num)) return val;
return num.toFixed(2);
},
formatSales(n) {
if (n === undefined || n === null || n === '') return '0';
let num = Number(n);
if (isNaN(num)) return n;
if (num >= 10000) {
return (num / 10000).toFixed(1).replace(/\.0$/, '') + '万+';
}
return String(num);
}
}
};
</script>
<style lang="scss" scoped>
.page-container {
width: 100%;
height: 100vh;
background-color: #f5f5f7;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
/* 导航栏 */
.custom-navbar {
width: 100%;
height: 88rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
box-sizing: border-box;
background-color: #ffffff;
position: relative;
flex-shrink: 0;
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: flex-start;
}
.nav-title {
font-size: 34rpx;
font-weight: bold;
color: #1a1a1a;
position: absolute;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
}
.nav-right {
width: 60rpx;
}
}
/* 搜索框区域 */
.search-bar-box {
width: 100%;
padding: 16rpx 32rpx;
box-sizing: border-box;
background-color: #ffffff;
flex-shrink: 0;
.search-inner {
width: 100%;
height: 80rpx;
background-color: #ffffff;
border-radius: 40rpx;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 8rpx 0 28rpx;
box-sizing: border-box;
border: 2rpx solid #efefef;
.search-icon {
margin-right: 12rpx;
}
.search-input {
flex: 1;
height: 100%;
font-size: 28rpx;
color: #333333;
background: transparent;
border: none;
}
.search-btn {
height: 64rpx;
padding: 0 32rpx;
background: linear-gradient(270deg, #b164fb 0%, #7934f6 100%);
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
.search-btn-text {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
}
}
}
}
/* 排序筛选栏 */
.filter-bar {
width: 100%;
padding: 20rpx 40rpx 16rpx;
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 48rpx;
background-color: #f5f5f7;
flex-shrink: 0;
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
.filter-text {
font-size: 28rpx;
color: #666666;
font-weight: 400;
margin-right: 8rpx;
}
&.active {
.filter-text {
color: #7934f6;
font-weight: bold;
}
}
.sort-icon-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
line-height: 1;
.icon-up {
margin-bottom: 2rpx;
}
}
}
}
/* 商品列表 Grid */
.product-scroll-view {
flex: 1;
height: 0;
width: 100%;
box-sizing: border-box;
}
.product-grid {
width: 100%;
padding: 0 24rpx 30rpx;
box-sizing: border-box;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
.product-card {
width: 342rpx;
margin-bottom: 20rpx;
background-color: #ffffff;
border-radius: 20rpx;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.02);
.img-wrapper {
width: 100%;
height: 342rpx;
position: relative;
background-color: #f8f8f8;
.product-img {
width: 100%;
height: 100%;
border-radius: 20rpx 20rpx 0 0;
}
.image-tag {
position: absolute;
top: 12rpx;
right: 12rpx;
background: rgba(255, 192, 110, 0.9);
color: #8c4a00;
font-size: 20rpx;
padding: 4rpx 12rpx;
border-radius: 8rpx;
max-width: 80%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
.card-info {
padding: 16rpx 20rpx 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
.product-title {
font-size: 28rpx;
color: #333333;
font-weight: bold;
line-height: 38rpx;
height: 76rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
margin-bottom: 16rpx;
}
.price-sales-row {
display: flex;
flex-direction: row;
align-items: baseline;
justify-content: space-between;
width: 100%;
.price-box {
display: flex;
align-items: baseline;
color: #7934f6;
.currency {
font-size: 24rpx;
font-weight: bold;
margin-right: 2rpx;
}
.price-val {
font-size: 32rpx;
font-weight: bold;
}
}
.sales-val {
font-size: 22rpx;
color: #999999;
font-weight: normal;
}
}
}
}
}
.empty-box {
width: 100%;
padding: 120rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.empty-text {
font-size: 28rpx;
color: #999999;
margin-top: 20rpx;
}
}
.loading-more-box {
width: 100%;
padding: 20rpx 0 40rpx;
text-align: center;
.loading-text {
font-size: 24rpx;
color: #999999;
}
}
</style>