|
@@ -0,0 +1,313 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="search-container">
|
|
|
|
|
+ <!-- 搜索输入框 -->
|
|
|
|
|
+ <view class="search-header">
|
|
|
|
|
+ <view class="search-input-wrap">
|
|
|
|
|
+ <text class="search-icon">🔍</text>
|
|
|
|
|
+ <input
|
|
|
|
|
+ class="search-input"
|
|
|
|
|
+ v-model="keyword"
|
|
|
|
|
+ :focus="true"
|
|
|
|
|
+ placeholder="输入商品名称"
|
|
|
|
|
+ @confirm="onSearch"
|
|
|
|
|
+ @input="onInput"
|
|
|
|
|
+ />
|
|
|
|
|
+ <text v-if="keyword" class="clear-btn" @click="clearKeyword">✕</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text class="search-cancel" @click="goBack">取消</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 搜索建议 / 历史记录 -->
|
|
|
|
|
+ <view v-if="!searched" class="suggest-wrap">
|
|
|
|
|
+ <view class="suggest-section" v-if="history.length">
|
|
|
|
|
+ <view class="suggest-header">
|
|
|
|
|
+ <text class="suggest-title">搜索历史</text>
|
|
|
|
|
+ <text class="clear-history" @click="clearHistory">清除</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="history-tags">
|
|
|
|
|
+ <text
|
|
|
|
|
+ v-for="(item, idx) in history"
|
|
|
|
|
+ :key="idx"
|
|
|
|
|
+ class="history-tag"
|
|
|
|
|
+ @click="searchKeyword(item)"
|
|
|
|
|
+ >{{ item }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 搜索结果 -->
|
|
|
|
|
+ <view v-else class="result-wrap">
|
|
|
|
|
+ <view v-if="results.length === 0" class="empty-wrap">
|
|
|
|
|
+ <text class="empty-icon">📦</text>
|
|
|
|
|
+ <text class="empty-text">没有找到相关商品</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-else class="product-grid">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="item in results"
|
|
|
|
|
+ :key="item.id"
|
|
|
|
|
+ class="product-card"
|
|
|
|
|
+ @click="goDetail(item.id)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <image
|
|
|
|
|
+ class="cover"
|
|
|
|
|
+ :src="item.image || item.coverImage || '/static/default-product.png'"
|
|
|
|
|
+ mode="aspectFill"
|
|
|
|
|
+ />
|
|
|
|
|
+ <view class="card-body">
|
|
|
|
|
+ <text class="product-name">{{ item.name }}</text>
|
|
|
|
|
+ <view class="price-row">
|
|
|
|
|
+ <text class="price">{{ formatPriceWithSymbol(item.price) }}</text>
|
|
|
|
|
+ <text v-if="item.memberPrice" class="member-price">会员{{ formatPriceWithSymbol(item.memberPrice) }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="loadingMore" class="loading-more">
|
|
|
|
|
+ <text class="loading-text">加载中...</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import { productList } from '@/utils/api.js'
|
|
|
|
|
+export default {
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ keyword: '',
|
|
|
|
|
+ searched: false,
|
|
|
|
|
+ results: [],
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ size: 20,
|
|
|
|
|
+ loadingMore: false,
|
|
|
|
|
+ noMore: false,
|
|
|
|
|
+ history: []
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onLoad() {
|
|
|
|
|
+ var historyStr = uni.getStorageSync('searchHistory')
|
|
|
|
|
+ this.history = historyStr ? JSON.parse(historyStr) : []
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ onInput(e) {
|
|
|
|
|
+ this.keyword = e.detail.value
|
|
|
|
|
+ },
|
|
|
|
|
+ onSearch() {
|
|
|
|
|
+ if (!this.keyword.trim()) return
|
|
|
|
|
+ this.searched = true
|
|
|
|
|
+ this.page = 1
|
|
|
|
|
+ this.results = []
|
|
|
|
|
+ this.noMore = false
|
|
|
|
|
+ this.saveHistory()
|
|
|
|
|
+ this.loadResults()
|
|
|
|
|
+ },
|
|
|
|
|
+ searchKeyword(keyword) {
|
|
|
|
|
+ this.keyword = keyword
|
|
|
|
|
+ this.onSearch()
|
|
|
|
|
+ },
|
|
|
|
|
+ loadResults() {
|
|
|
|
|
+ if (this.loadingMore) return
|
|
|
|
|
+ this.loadingMore = true
|
|
|
|
|
+ var params = { page: this.page, size: this.size, keyword: this.keyword.trim() }
|
|
|
|
|
+ productList(params).then(res => {
|
|
|
|
|
+ this.loadingMore = false
|
|
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
|
|
+ var records = res.data.records || []
|
|
|
|
|
+ if (this.page === 1) {
|
|
|
|
|
+ this.results = records
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.results = this.results.concat(records)
|
|
|
|
|
+ }
|
|
|
|
|
+ var total = res.data.total || 0
|
|
|
|
|
+ this.noMore = this.results.length >= total
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.loadingMore = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ saveHistory() {
|
|
|
|
|
+ var kw = this.keyword.trim()
|
|
|
|
|
+ if (!kw) return
|
|
|
|
|
+ var list = this.history.filter(function(h) { return h !== kw })
|
|
|
|
|
+ list.unshift(kw)
|
|
|
|
|
+ if (list.length > 10) list = list.slice(0, 10)
|
|
|
|
|
+ this.history = list
|
|
|
|
|
+ uni.setStorageSync('searchHistory', JSON.stringify(list))
|
|
|
|
|
+ },
|
|
|
|
|
+ clearHistory() {
|
|
|
|
|
+ this.history = []
|
|
|
|
|
+ uni.removeStorageSync('searchHistory')
|
|
|
|
|
+ },
|
|
|
|
|
+ clearKeyword() {
|
|
|
|
|
+ this.keyword = ''
|
|
|
|
|
+ this.searched = false
|
|
|
|
|
+ this.results = []
|
|
|
|
|
+ },
|
|
|
|
|
+ goBack() {
|
|
|
|
|
+ uni.navigateBack()
|
|
|
|
|
+ },
|
|
|
|
|
+ goDetail(id) {
|
|
|
|
|
+ uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + id })
|
|
|
|
|
+ },
|
|
|
|
|
+ formatPriceWithSymbol(price) {
|
|
|
|
|
+ if (price === null || price === undefined) return ''
|
|
|
|
|
+ return '¥' + parseFloat(price).toFixed(2)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.search-container {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+}
|
|
|
|
|
+.search-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 16rpx 20rpx;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+.search-input-wrap {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+ border-radius: 50rpx;
|
|
|
|
|
+ padding: 0 24rpx;
|
|
|
|
|
+ height: 72rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.search-icon {
|
|
|
|
|
+ font-size: 32rpx;
|
|
|
|
|
+ margin-right: 12rpx;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.search-input {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ height: 72rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.clear-btn {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ padding: 8rpx;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.search-cancel {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ margin-left: 20rpx;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.suggest-wrap {
|
|
|
|
|
+ padding: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.suggest-section {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ margin-bottom: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.suggest-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.suggest-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: #f5f5f5;
|
|
|
|
|
+ border-radius: 30rpx;
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+}
|
|
|
|
|
+.result-wrap {
|
|
|
|
|
+ padding: 0 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.product-grid {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ padding: 20rpx 0;
|
|
|
|
|
+}
|
|
|
|
|
+.product-card {
|
|
|
|
|
+ width: 49%;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ margin-bottom: 20rpx;
|
|
|
|
|
+ box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
|
|
|
|
|
+}
|
|
|
|
|
+.cover {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 320rpx;
|
|
|
|
|
+ background: #f0f0f0;
|
|
|
|
|
+}
|
|
|
|
|
+.card-body {
|
|
|
|
|
+ padding: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.product-name {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ lines: 2;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ display: -webkit-box;
|
|
|
|
|
+ -webkit-line-clamp: 2;
|
|
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
|
|
+ margin-bottom: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.price-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: baseline;
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.price {
|
|
|
|
|
+ font-size: 32rpx;
|
|
|
|
|
+ color: #F97316;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ margin-right: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.member-price {
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+}
|
|
|
|
|
+.empty-wrap {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding-top: 200rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.empty-icon {
|
|
|
|
|
+ font-size: 100rpx;
|
|
|
|
|
+ margin-bottom: 30rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.empty-text {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+}
|
|
|
|
|
+.loading-more {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ padding: 30rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.loading-text {
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|