Переглянути джерело

chore: auto bump version and changelog [skip ci]

iwt 3 місяців тому
батько
коміт
e25dd93566

+ 6 - 0
cfc-frontend/pages.json

@@ -433,6 +433,12 @@
             "navigationBarTitleText": "商品首页"
           }
         },
+        {
+          "path": "search/search",
+          "style": {
+            "navigationBarTitleText": "搜索商品"
+          }
+        },
         {
           "path": "order-list/order-list",
           "style": {

+ 31 - 3
cfc-frontend/pages/shop/index/index.vue

@@ -12,6 +12,12 @@
       <text class="member-banner-action">立即开通 ›</text>
     </view>
 
+    <!-- 搜索栏 -->
+    <view class="search-bar" @click="goSearch">
+      <text class="search-icon">🔍</text>
+      <text class="search-placeholder">搜索商品</text>
+    </view>
+
     <!-- Level 1 分类:2列横排网格 -->
     <view class="cat-level1">
       <view
@@ -233,6 +239,9 @@ export default {
     },
     goDetail(id) {
       uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + id })
+    },
+    goSearch() {
+      uni.navigateTo({ url: '/pages/shop/search/search' })
     }
   }
 }
@@ -255,14 +264,14 @@ export default {
   border-bottom: 1rpx solid #eee;
 }
 .cat1-item {
-  width: 50%;
+  width: 25%;
   box-sizing: border-box;
-  padding: 8rpx;
+  padding: 16rpx 8rpx;
   display: flex;
   flex-direction: column;
   align-items: center;
   text-align: center;
-  border-radius: 12rpx;
+  border-radius: 16rpx;
   margin-bottom: 8rpx;
 }
 .cat1-item.active {
@@ -303,6 +312,25 @@ export default {
   font-weight: bold;
 }
 
+/* 搜索栏 */
+.search-bar {
+  display: flex;
+  align-items: center;
+  background: #fff;
+  margin: 16rpx 16rpx 0;
+  padding: 16rpx 24rpx;
+  border-radius: 50rpx;
+  border: 1rpx solid #eee;
+}
+.search-icon {
+  font-size: 32rpx;
+  margin-right: 12rpx;
+}
+.search-placeholder {
+  font-size: 26rpx;
+  color: #bbb;
+}
+
 /* Member Upgrade Banner */
 .member-banner {
   display: flex;

+ 313 - 0
cfc-frontend/pages/shop/search/search.vue

@@ -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>

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-5c83cfdc129bef443e607f8f1aa45c5b673223bb
+4a9f195a2678e716900e45aae66259e9544ffa6a