فهرست منبع

feat: add purchase recommendation section to report-upload page

- Add productList API import for fetching assessment products
- Add recommendedProducts/loadingProducts data states
- Implement loadRecommendProducts method with REPORT_DOMAIN_MAP
- Add goToProduct and formatPriceWithSymbol utility methods
- Insert horizontal scrollable purchase card section below type selector
- Add scoped styles for purchase recommendation section
Xiaogang Liao 2 ماه پیش
والد
کامیت
35f11432ed
1فایلهای تغییر یافته به همراه136 افزوده شده و 2 حذف شده
  1. 136 2
      cfc-frontend/pages/health/report-upload.vue

+ 136 - 2
cfc-frontend/pages/health/report-upload.vue

@@ -89,6 +89,29 @@
         </view>
         </view>
       </view>
       </view>
 
 
+      <!-- 购买推荐 -->
+      <view class="purchase-section" v-if="recommendedProducts.length > 0">
+        <view class="purchase-header">
+          <text class="purchase-icon">📦</text>
+          <text class="purchase-title">为家人购买检测服务</text>
+        </view>
+        <scroll-view class="purchase-scroll" scroll-x enable-flex show-scrollbar="false">
+          <view
+            class="purchase-card"
+            v-for="item in recommendedProducts"
+            :key="item.id"
+            @click="goToProduct(item.id)"
+          >
+            <image class="purchase-cover" :src="item.coverImage || '/static/default-product.png'" mode="aspectFill" />
+            <text class="purchase-name">{{ item.name }}</text>
+            <text class="purchase-price">{{ formatPriceWithSymbol(item.price) }}</text>
+            <view class="purchase-btn">
+              <text class="purchase-btn-text">去购买</text>
+            </view>
+          </view>
+        </scroll-view>
+      </view>
+
       <!-- 舌诊拍照(仅 tongue 类型显示) -->
       <!-- 舌诊拍照(仅 tongue 类型显示) -->
       <view class="form-item" v-if="isTongue">
       <view class="form-item" v-if="isTongue">
         <text class="form-label">拍摄舌象</text>
         <text class="form-label">拍摄舌象</text>
@@ -186,9 +209,16 @@
 </template>
 </template>
 
 
 <script>
 <script>
-import { createHealthReport, parseReportPreview } from '../../utils/api.js'
+import { createHealthReport, parseReportPreview, productList } from '../../utils/api.js'
 import config from '@/config.js'
 import config from '@/config.js'
 
 
+// 报告类型 → 维度映射(用于推荐商品)
+var REPORT_DOMAIN_MAP = {
+  physical_exam: 'body',
+  gut_flora: 'body',
+  tongue: 'body'
+}
+
 export default {
 export default {
   data() {
   data() {
     return {
     return {
@@ -211,7 +241,10 @@ export default {
       familyMembers: [],
       familyMembers: [],
       // 舌诊
       // 舌诊
       photoPath: null,
       photoPath: null,
-      tongueResult: null
+      tongueResult: null,
+      // 购买推荐
+      recommendedProducts: [],
+      loadingProducts: false
     }
     }
   },
   },
   computed: {
   computed: {
@@ -231,6 +264,7 @@ export default {
     if (options.reportType && ['physical_exam', 'gut_flora', 'tongue'].indexOf(options.reportType) !== -1) {
     if (options.reportType && ['physical_exam', 'gut_flora', 'tongue'].indexOf(options.reportType) !== -1) {
       this.form.reportType = options.reportType
       this.form.reportType = options.reportType
     }
     }
+    this.loadRecommendProducts(this.form.reportType)
     // 不默认填充日期 — 待解析后从报告中提取
     // 不默认填充日期 — 待解析后从报告中提取
     this.form.reportDate = ''
     this.form.reportDate = ''
   },
   },
@@ -242,6 +276,7 @@ export default {
     selectType(type) {
     selectType(type) {
       if (this.form.reportType === type) return
       if (this.form.reportType === type) return
       this.form.reportType = type
       this.form.reportType = type
+      this.loadRecommendProducts(type)
       // 切类型时清理其他类型的状态
       // 切类型时清理其他类型的状态
       if (type === 'tongue') {
       if (type === 'tongue') {
         this.selectedFile = null
         this.selectedFile = null
@@ -421,6 +456,33 @@ export default {
       this.showMemberSelector = false
       this.showMemberSelector = false
       this.selectedMemberId = null
       this.selectedMemberId = null
     },
     },
+    loadRecommendProducts: function(reportType) {
+      var domain = REPORT_DOMAIN_MAP[reportType]
+      if (!domain) {
+        this.recommendedProducts = []
+        return
+      }
+      var self = this
+      self.loadingProducts = true
+      productList({ domain: domain, productType: 'assessment', size: 4 }).then(function(res) {
+        if (res && res.data && res.data.records) {
+          self.recommendedProducts = res.data.records
+        } else {
+          self.recommendedProducts = []
+        }
+      }).catch(function() {
+        self.recommendedProducts = []
+      }).finally(function() {
+        self.loadingProducts = false
+      })
+    },
+    goToProduct: function(productId) {
+      uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + productId })
+    },
+    formatPriceWithSymbol: function(cents) {
+      if (cents === null || cents === undefined) return '¥0'
+      return '¥' + (cents / 100).toFixed(2)
+    },
     formatFileSize(bytes) {
     formatFileSize(bytes) {
       if (!bytes) return ''
       if (!bytes) return ''
       if (bytes < 1024) return bytes + 'B'
       if (bytes < 1024) return bytes + 'B'
@@ -911,4 +973,76 @@ uni.showToast({ title: '提交成功', icon: 'success' })
 .bottom-spacer {
 .bottom-spacer {
   height: 60rpx;
   height: 60rpx;
 }
 }
+
+/* ===== 购买推荐 ===== */
+.purchase-section {
+  background: #F0F9FF;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin: 0 30rpx 20rpx;
+  border: 2rpx solid #BAE6FD;
+}
+.purchase-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+.purchase-icon {
+  font-size: 28rpx;
+  margin-right: 8rpx;
+}
+.purchase-title {
+  font-size: 26rpx;
+  font-weight: bold;
+  color: #0369A1;
+}
+.purchase-scroll {
+  display: flex;
+  flex-direction: row;
+  white-space: nowrap;
+}
+.purchase-card {
+  display: inline-flex;
+  flex-direction: column;
+  width: 220rpx;
+  background: #fff;
+  border-radius: 12rpx;
+  padding: 16rpx;
+  margin-right: 16rpx;
+  flex-shrink: 0;
+}
+.purchase-cover {
+  width: 188rpx;
+  height: 120rpx;
+  border-radius: 8rpx;
+  background: #F1F5F9;
+  margin-bottom: 8rpx;
+}
+.purchase-name {
+  font-size: 22rpx;
+  color: #333;
+  line-height: 1.3;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  margin-bottom: 4rpx;
+}
+.purchase-price {
+  font-size: 24rpx;
+  color: #EF4444;
+  font-weight: bold;
+  margin-bottom: 8rpx;
+}
+.purchase-btn {
+  background: #3B82F6;
+  border-radius: 8rpx;
+  padding: 8rpx 0;
+  text-align: center;
+}
+.purchase-btn-text {
+  font-size: 22rpx;
+  color: #fff;
+  font-weight: 500;
+}
 </style>
 </style>