فهرست منبع

fix(backend): 推荐服务商品状态改 on_shelf 修复 dimension-products 空数组; feat(frontend): 推荐阅读三列书封比例/推荐商品自加载/会员级别样式

asus 1 ماه پیش
والد
کامیت
9c8908fe52

+ 2 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/ProductRecommendationService.java

@@ -77,7 +77,7 @@ public class ProductRecommendationService {
                 List<Product> mapped = productMapper.selectList(
                         new LambdaQueryWrapper<Product>()
                                 .in(Product::getId, mappedProductIds)
-                                .eq(Product::getStatus, "上架")
+                                .eq(Product::getStatus, "on_shelf")
                                 .gt(Product::getStock, 0)
                                 .orderByAsc(Product::getSortOrder)
                 );
@@ -108,7 +108,7 @@ public class ProductRecommendationService {
         // fallback: 从 Product.domain 匹配
         if (candidates.isEmpty()) {
             LambdaQueryWrapper<Product> domainQw = new LambdaQueryWrapper<Product>()
-                    .eq(Product::getStatus, "上架")
+                    .eq(Product::getStatus, "on_shelf")
                     .gt(Product::getStock, 0)
                     .orderByAsc(Product::getSortOrder);
             if (dimensionCode != null && !dimensionCode.isEmpty()) {

+ 2 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/RecommendationService.java

@@ -131,7 +131,7 @@ public class RecommendationService {
 
             List<Product> products = productMapper.selectList(
                     new LambdaQueryWrapper<Product>()
-                            .eq(Product::getStatus, "上架")
+                            .eq(Product::getStatus, "on_shelf")
                             .gt(Product::getStock, 0)
                             .and(w -> w.like(Product::getName, keyword)
                                     .or()
@@ -248,7 +248,7 @@ public class RecommendationService {
 
         for (ProductDimensionMapping m : mappings) {
             Product product = productMapper.selectById(m.getProductId());
-            if (product == null || !"上架".equals(product.getStatus()) || product.getStock() <= 0) {
+            if (product == null || !"on_shelf".equals(product.getStatus()) || product.getStock() <= 0) {
                 continue;
             }
 

+ 19 - 10
cfc-frontend/components/ArticleBookshelf.vue

@@ -8,7 +8,7 @@
     <!-- 骨架屏 -->
     <view v-else-if="loading && (!articles || articles.length === 0)" class="bs-shelf-skeleton">
       <view class="bs-skeleton-grid">
-        <view v-for="n in 4" :key="n" class="bs-skeleton-card">
+        <view v-for="n in 3" :key="n" class="bs-skeleton-card">
           <view class="bs-skeleton-cover"></view>
           <view class="bs-skeleton-line"></view>
         </view>
@@ -94,9 +94,9 @@ export default {
           { title: '亲子沟通的心理学智慧', category: '心理养育', summary: '有效的倾听与表达技巧' },
           { title: '规划师推荐的书单', category: '成长规划', summary: '培养未来领导力的阅读路径' },
           { title: '儿童营养均衡完全指南', category: '营养健康', summary: '0-12岁各阶段营养需求' }
-        ].slice(0, 4)
+        ].slice(0, 3)
       }
-      return this.articles.slice(0, 4)
+      return this.articles.slice(0, 3)
     }
   },
   methods: {
@@ -183,16 +183,17 @@ export default {
   padding-top: 16rpx;
 }
 .bs-skeleton-card {
-  width: calc(25% - 12rpx);
+  width: calc(33.33% - 12rpx);
   margin-right: 16rpx;
   margin-bottom: 16rpx;
 }
-.bs-skeleton-card:nth-child(4n) {
+.bs-skeleton-card:nth-child(3n) {
   margin-right: 0;
 }
 .bs-skeleton-cover {
   width: 100%;
-  height: 160rpx;
+  height: 0;
+  padding-top: 133.33%; /* 3:4 书封比例 */
   border-radius: 12rpx;
   background: linear-gradient(180deg, #e8e8e8 0%, #f0f0f0 50%, #e8e8e8 100%);
   background-size: 200% 100%;
@@ -211,28 +212,30 @@ export default {
   100% { background-position: -200% 0; }
 }
 
-/* ---- 列文章网格 ---- */
+/* ---- 列文章网格 ---- */
 .bs-grid {
   display: flex;
   flex-wrap: wrap;
 }
 .bs-card {
-  width: calc(25% - 12rpx);
+  width: calc(33.33% - 12rpx);
   margin-right: 16rpx;
   margin-bottom: 16rpx;
 }
-.bs-card:nth-child(4n) {
+.bs-card:nth-child(3n) {
   margin-right: 0;
 }
 .bs-card-cover {
   width: 100%;
-  height: 160rpx;
+  height: 0;
+  padding-top: 133.33%; /* 3:4 书封比例 */
   border-radius: 12rpx;
   position: relative;
   display: flex;
   align-items: center;
   justify-content: center;
   overflow: hidden;
+  box-sizing: border-box;
   box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.12);
   transition: transform 0.18s, box-shadow 0.18s;
 }
@@ -243,12 +246,18 @@ export default {
 
 /* 封面图 */
 .bs-cover-img {
+  position: absolute;
+  top: 0;
+  left: 0;
   width: 100%;
   height: 100%;
 }
 
 /* 无封面图时:渐变底 + 书名 */
 .bs-cover-text {
+  position: absolute;
+  top: 0;
+  left: 0;
   width: 100%;
   height: 100%;
   display: flex;

+ 10 - 3
cfc-frontend/components/DimensionProducts.vue

@@ -55,14 +55,21 @@ export default {
     }
   },
   mounted: function() {
-    // 自加载模式:有 familyId 且父组件未传入 products 时才请求推荐接口
-    if (this.familyId && (!this.products || this.products.length === 0)) {
+    // 自加载模式:已登录且父组件未传入 products 时才请求推荐接口
+    // (familyId 由后端从 JWT 派生,前端无需传入)
+    if (this.isLoggedIn && (!this.products || this.products.length === 0)) {
       this.loadDimensionProducts()
     }
   },
   watch: {
-    familyId: function(val) {
+    familyId: function() {
       // 切换孩子/登录态变化时重新加载
+      if (this.isLoggedIn && (!this.products || this.products.length === 0)) {
+        this.loadDimensionProducts()
+      }
+    },
+    isLoggedIn: function(val) {
+      // 登录后触发加载
       if (val && (!this.products || this.products.length === 0)) {
         this.loadDimensionProducts()
       }

+ 8 - 25
cfc-frontend/pages/profile-main/profile.vue

@@ -42,7 +42,6 @@
             <text class="points-num">{{ totalPoints }}</text>
             <text class="points-label">积分</text>
           </view>
-          <button v-if="!isSwitchedChild" class="btn-switch" @click="onSwitchMode">切换</button>
         </view>
       </view>
 
@@ -732,14 +731,16 @@ export default {
   gap: 12rpx;
 }
 .level-badge {
-  font-size: 22rpx;
-  padding: 4rpx 16rpx;
+  font-size: 26rpx;
+  padding: 6rpx 20rpx;
   border-radius: 20rpx;
-  font-weight: 600;
+  font-weight: bold;
+  box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.15);
+  border: 2rpx solid rgba(255,255,255,0.3);
 }
-.level-GOLD { background: #F59E0B; color: #fff; }
-.level-PLATINUM { background: #6366F1; color: #fff; }
-.level-DIAMOND { background: #EC4899; color: #fff; }
+.level-GOLD { background: linear-gradient(135deg, #F59E0B, #D97706); color: #fff; }
+.level-PLATINUM { background: linear-gradient(135deg, #6366F1, #4F46E5); color: #fff; }
+.level-DIAMOND { background: linear-gradient(135deg, #EC4899, #DB2777); color: #fff; }
 .days-left {
   font-size: 24rpx;
   color: rgba(255,255,255,0.8);
@@ -756,24 +757,6 @@ export default {
   padding: 16rpx 24rpx;
   border-radius: 16rpx;
 }
-.points-num {
-  font-size: 44rpx;
-  font-weight: bold;
-  color: #fff;
-  display: block;
-}
-.points-label {
-  font-size: 22rpx;
-  color: rgba(255,255,255,0.7);
-}
-.btn-switch {
-  background: rgba(255,255,255,0.2);
-  color: #fff;
-  font-size: 24rpx;
-  padding: 10rpx 32rpx;
-  border-radius: 30rpx;
-  border: none;
-}
 
 /* 数据概览 */
 .stats-bar {