Explorar o código

docs: 补充推荐组件移除维度设计细节

Xiaogang Liao hai 1 mes
pai
achega
54ec5c6eb4

+ 1 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminProductController.java

@@ -43,6 +43,7 @@ public class AdminProductController {
 
         Page<Product> pageParam = new Page<>(page, size);
         LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<Product>()
+                .orderByAsc(Product::getSortOrder)
                 .orderByDesc(Product::getCreatedAt);
 
         if (status != null && !status.isEmpty()) {

+ 1 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/SupplierProductController.java

@@ -58,6 +58,7 @@ public class SupplierProductController {
         Page<Product> pageParam = new Page<>(page, size);
         LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<Product>()
                 .eq(Product::getDistributionSystemId, system.getId())
+                .orderByAsc(Product::getSortOrder)
                 .orderByDesc(Product::getCreatedAt);
 
         if (status != null && !status.isEmpty()) {

+ 2 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/ProductDTO.java

@@ -37,6 +37,7 @@ public class ProductDTO {
     private Integer effectivePpoint;     // 有效P点(基点,如1000=10%)
     private String ppointSource;         // config=管理员配置,default=商品默认
     private Integer profitRate;         // 商品默认利润率(千分比,备用)
+    private Integer sortOrder;           // 排序(值越小越靠前)
 
     // 测评商品扩展信息
     private String assessmentType;
@@ -95,6 +96,7 @@ public class ProductDTO {
         d.memberEligible = p.getMemberEligible();
         d.rejectReason = p.getRejectReason();
         d.deliveryMethod = p.getDeliveryMethod();
+        d.sortOrder = p.getSortOrder();
         d.createdAt = p.getCreatedAt();
         d.updatedAt = p.getUpdatedAt();
         // 会员体系重构:商品对所有用户统一定价(活动侧保留会员价,商品侧停用)

+ 4 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/Product.java

@@ -51,6 +51,9 @@ public class Product implements Serializable {
     // 佣金系统 - 平台利润率(千分比)
     private Integer profitRate;
 
+    // 排序(值越小越靠前)
+    private Integer sortOrder;
+
     // 电商扩展字段
     private Long categoryId;               // 商品类目ID
     private Long distributionSystemId;     // 销售体系ID
@@ -83,6 +86,7 @@ public class Product implements Serializable {
     public String getRejectReason() { return rejectReason; }
     public Integer getDeliveryMethod() { return deliveryMethod; }
     public Integer getMinQuantity() { return minQuantity; }
+    public Integer getSortOrder() { return sortOrder; }
     public Date getCreatedAt() { return createdAt; }
     public Date getUpdatedAt() { return updatedAt; }
 }

+ 1 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/ContentRecommendService.java

@@ -253,6 +253,7 @@ private List<RecommendItemVO> recommendProducts(String dimensionCode, Long userI
                                                       Long familyId, Set<Long> excludeIds) {
         LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<Product>()
                 .eq(Product::getStatus, "on_shelf")
+                .orderByAsc(Product::getSortOrder)
                 .orderByDesc(Product::getSalesCount)
                 .last("LIMIT 50");
 

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

@@ -94,6 +94,8 @@ public class ProductCategoryService {
             new LambdaQueryWrapper<Product>()
                 .eq(Product::getCategoryId, categoryId)
                 .eq(Product::getStatus, "on_shelf")
+                .orderByAsc(Product::getSortOrder)
+                .orderByDesc(Product::getCreatedAt)
         );
         List<Map<String, Object>> result = products.stream().map(p -> {
             Map<String, Object> m = new LinkedHashMap<>();

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

@@ -78,6 +78,7 @@ public class ProductRecommendationService {
                                 .in(Product::getId, mappedProductIds)
                                 .eq(Product::getStatus, "上架")
                                 .gt(Product::getStock, 0)
+                                .orderByAsc(Product::getSortOrder)
                 );
 
                 // 构建 productId → mapping 得分
@@ -111,6 +112,7 @@ public class ProductRecommendationService {
                             .eq(Product::getStatus, "上架")
                             .gt(Product::getStock, 0)
                             .notIn(!purchasedIds.isEmpty(), Product::getId, purchasedIds)
+                            .orderByAsc(Product::getSortOrder)
             );
             candidates.addAll(domainMatched);
         }

+ 8 - 7
cfc-backend/src/main/java/com/etotem/cfc/service/ProductService.java

@@ -57,7 +57,7 @@ public class ProductService {
         Page<Product> page = new Page<>(query.getPage(), query.getSize());
         LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<Product>()
             .eq(Product::getStatus, "on_shelf");
-        // growthRelated=true: 仅返回成长档案关联商品,按 sort_order 升序展示
+        // growthRelated=true: 仅返回成长档案关联商品,按 products.sort_order 升序展示
         if (Boolean.TRUE.equals(query.getGrowthRelated())) {
             List<Long> productIds = growthArchiveProductService.getActiveRules().stream()
                 .map(GrowthArchiveProduct::getProductId)
@@ -70,13 +70,11 @@ public class ProductService {
                 empty.put("size", page.getSize());
                 return Result.success(empty);
             }
-            wrapper.in(Product::getId, productIds);
-            String ids = productIds.stream()
-                .map(String::valueOf)
-                .collect(Collectors.joining(","));
-            wrapper.last("ORDER BY FIELD(id, " + ids + ")");
+wrapper.in(Product::getId, productIds)
+                   .orderByAsc(Product::getSortOrder);
         } else {
-            wrapper.orderByDesc(Product::getCreatedAt);
+            wrapper.orderByAsc(Product::getSortOrder)
+                   .orderByDesc(Product::getCreatedAt);
         }
         // 游客过滤:排除会员专属商品
         if (userId == null) {
@@ -176,6 +174,7 @@ public class ProductService {
         List<Product> list = productMapper.selectList(
             new LambdaQueryWrapper<Product>()
                 .eq(Product::getVendorId, vendorId)
+                .orderByAsc(Product::getSortOrder)
                 .orderByDesc(Product::getCreatedAt)
         );
         List<ProductDTO> dtos = list.stream().map(ProductDTO::from).collect(Collectors.toList());
@@ -242,6 +241,7 @@ public class ProductService {
 
     public Result<List<ProductDTO>> adminList(String status) {
         LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<Product>()
+            .orderByAsc(Product::getSortOrder)
             .orderByDesc(Product::getCreatedAt);
         if (status != null && !status.isEmpty()) {
             wrapper.eq(Product::getStatus, status);
@@ -417,6 +417,7 @@ public class ProductService {
         copy.setDistributionSystemId(original.getDistributionSystemId());
         copy.setDeliveryMethod(original.getDeliveryMethod());
         copy.setMinQuantity(original.getMinQuantity());
+        copy.setSortOrder(original.getSortOrder());
         copy.setCreatedAt(new Date());
         copy.setUpdatedAt(new Date());
         productMapper.insert(copy);

+ 59 - 92
cfc-frontend/pages/tasks/tasks.vue

@@ -130,30 +130,8 @@
 
         <view class="form-item">
           <view class="form-label">分配给 <text class="required">*</text></view>
-          <view class="assignee-tabs">
-            <view class="assignee-tab" :class="{ active: newTask.executorType === 'child' }" @click="newTask.executorType = 'child'">
-              <text>孩子</text>
-            </view>
-            <view class="assignee-tab" :class="{ active: newTask.executorType === 'member' }" @click="newTask.executorType = 'member'">
-              <text>家庭成员</text>
-            </view>
-          </view>
-        </view>
-
-        <view class="form-item" v-if="newTask.executorType === 'child'">
-          <view class="form-label">选择孩子 <text class="required">*</text></view>
-          <checkbox-group @change="onChildSelectChange">
-            <label v-for="child in children" :key="child.id">
-              <checkbox :value="String(child.id)" :checked="newTask.memberIds.includes(String(child.id))" />
-              <text>{{ child.nickname }}</text>
-            </label>
-          </checkbox-group>
-        </view>
-
-        <view class="form-item" v-if="newTask.executorType === 'member'">
-          <view class="form-label">选择家庭成员 <text class="required">*</text></view>
-          <checkbox-group @change="onMemberSelectChange">
-            <label v-for="member in familyMembers" :key="member.id">
+          <checkbox-group @change="onFamilyMemberSelectChange">
+            <label v-for="member in familyMembers" :key="member.id" :class="{ 'label-checked': newTask.memberIds.includes(String(member.id)) }">
               <checkbox :value="String(member.id)" :checked="newTask.memberIds.includes(String(member.id))" />
               <text>{{ member.nickname }}</text>
             </label>
@@ -170,7 +148,7 @@
 </template>
 
 <script>
-import { getMyPendingTasks, acceptTask as acceptTaskApi, rejectTask as rejectTaskApi, getTodayTasks, completeTask as completeTaskApi, createTask as createTaskApi, getChildren, getVisibleFamilyMembers } from '../../utils/api.js'
+import { getMyPendingTasks, acceptTask as acceptTaskApi, rejectTask as rejectTaskApi, getTodayTasks, completeTask as completeTaskApi, createTask as createTaskApi, getVisibleFamilyMembers } from '../../utils/api.js'
 import { parseDate } from '../../utils/format.js'
 
 export default {
@@ -184,7 +162,6 @@ export default {
       isParent: false,
       currentMemberId: null,
       currentUserId: null,
-      children: [],
       familyMembers: [],
       showAddModal: false,
       showRejectModalFlag: false,
@@ -195,7 +172,6 @@ export default {
         category: '',
         points: 2,
         deadline: '',
-        executorType: 'child',
         memberIds: []
       },
       categories: ['学习类', '生活类', '运动类', '其他']
@@ -218,11 +194,7 @@ export default {
     async loadInitialData() {
       this.loading = true
       try {
-        const [childrenRes, membersRes] = await Promise.all([
-          getChildren(),
-          getVisibleFamilyMembers()
-        ])
-        this.children = childrenRes.data || []
+        const membersRes = await getVisibleFamilyMembers()
         this.familyMembers = membersRes.data || []
         this.loadTodayTasks()
         this.loadPendingTasks()
@@ -322,10 +294,8 @@ export default {
           category: this.newTask.category,
           points: parseInt(this.newTask.points) || 2,
           deadline: this.newTask.deadline ? new Date(this.newTask.deadline).getTime() : null,
-          executorType: this.newTask.executorType,
-          executorId: this.newTask.executorType === 'member' ? Number(this.newTask.memberIds[0]) : null,
-          memberId: this.newTask.executorType === 'child' ? Number(this.newTask.memberIds[0]) : null,
-          memberIds: this.newTask.executorType === 'child' ? this.newTask.memberIds.map(id => Number(id)) : []
+          executorType: 'member',
+          memberIds: this.newTask.memberIds.map(id => Number(id))
         })
         uni.showToast({ title: '创建成功', icon: 'success' })
         this.showAddModal = false
@@ -344,7 +314,6 @@ export default {
         category: '',
         points: 2,
         deadline: '',
-        executorType: 'child',
         memberIds: []
       }
     },
@@ -354,10 +323,7 @@ export default {
     onDeadlineChange(e) {
       this.newTask.deadline = e.detail.value
     },
-    onChildSelectChange(e) {
-      this.newTask.memberIds = e.detail.value
-    },
-    onMemberSelectChange(e) {
+    onFamilyMemberSelectChange(e) {
       this.newTask.memberIds = e.detail.value
     },
     formatTime(dateStr) {
@@ -591,11 +557,12 @@ export default {
 
 .modal {
   background: #fff;
-  border-radius: 20rpx;
+  border-radius: 28rpx;
   padding: 40rpx;
   width: 85%;
   max-height: 80vh;
   overflow-y: auto;
+  box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.12);
 }
 
 .modal-large {
@@ -603,9 +570,9 @@ export default {
 }
 
 .modal-title {
-  font-size: 32rpx;
+  font-size: 34rpx;
   font-weight: bold;
-  margin-bottom: 30rpx;
+  margin-bottom: 32rpx;
   text-align: center;
   color: #333;
 }
@@ -626,11 +593,12 @@ export default {
 
 .modal-btns button {
   flex: 1;
-  height: 80rpx;
-  line-height: 80rpx;
-  border-radius: 40rpx;
+  height: 88rpx;
+  line-height: 88rpx;
+  border-radius: 44rpx;
   font-size: 28rpx;
   border: none;
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
 }
 
 .modal-btns button:first-child {
@@ -639,13 +607,13 @@ export default {
 }
 
 .form-item {
-  margin-bottom: 24rpx;
+  margin-bottom: 28rpx;
 }
 
 .form-label {
   font-size: 28rpx;
   color: #333;
-  margin-bottom: 12rpx;
+  margin-bottom: 14rpx;
   font-weight: 500;
 }
 
@@ -653,69 +621,68 @@ export default {
   color: #F97316;
 }
 
-.form-item input, .form-item .picker {
-  border: 1rpx solid #ddd;
-  border-radius: 10rpx;
-  padding: 20rpx;
-  font-size: 28rpx;
-  width: 100%;
-  box-sizing: border-box;
-  background: #fafafa;
-}
-
-.form-item .picker {
-  color: #333;
+.form-item checkbox-group {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16rpx;
 }
 
-.textarea {
-  min-height: 120rpx;
-  width: 100%;
-  box-sizing: border-box;
+.form-item label {
+  display: flex;
+  align-items: center;
+  gap: 10rpx;
   font-size: 28rpx;
-  padding: 20rpx;
+  color: #333;
+  padding: 18rpx 28rpx;
   border: 1rpx solid #ddd;
-  border-radius: 10rpx;
+  border-radius: 16rpx;
   background: #fafafa;
+  transition: all 0.25s ease;
 }
 
-.assignee-tabs {
-  display: flex;
-  gap: 20rpx;
+.form-item label.label-checked {
+  border-color: #F97316;
+  background: #FFF7ED;
+  color: #F97316;
 }
 
-.assignee-tab {
-  flex: 1;
-  padding: 20rpx;
-  text-align: center;
-  border-radius: 10rpx;
-  border: 2rpx solid #ddd;
+.form-item label:active {
+  transform: scale(0.97);
+}
+
+.form-item input, .form-item .picker {
+  border: 1rpx solid #ddd;
+  border-radius: 16rpx;
+  padding: 24rpx 28rpx;
   font-size: 28rpx;
-  color: #666;
+  width: 100%;
+  box-sizing: border-box;
   background: #fafafa;
+  min-height: 88rpx;
+  transition: all 0.25s ease;
 }
 
-.assignee-tab.active {
+.form-item input:focus {
   border-color: #F97316;
-  background: #FFF7ED;
-  color: #F97316;
-  font-weight: bold;
+  background: #fff;
+  box-shadow: 0 0 0 4rpx rgba(249, 115, 22, 0.1);
 }
 
-.form-item checkbox-group {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 20rpx;
+.form-item .picker {
+  color: #333;
 }
 
-.form-item label {
-  display: flex;
-  align-items: center;
-  gap: 10rpx;
+.textarea {
+  min-height: 160rpx;
+  width: 100%;
+  box-sizing: border-box;
   font-size: 28rpx;
-  color: #333;
-  padding: 16rpx 24rpx;
+  padding: 24rpx 28rpx;
   border: 1rpx solid #ddd;
-  border-radius: 10rpx;
+  border-radius: 16rpx;
   background: #fafafa;
+  transition: all 0.25s ease;
 }
+
+
 </style>

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-8f64c7a65f1c5d33690aad7242bdb9d898ae4947
+04ce3625608218520b80db7367ec66bf1bead775

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.892",
+  "version": "1.0.891",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 30 - 7
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,12 +4,6 @@
 
 ---
 
-## v1.0.892 (2026-08-09)
-
-### Bug 修复
-- 添加家庭成员数量校验 - 任务分配需≥2人/挑战需≥2参与者/互动双方不能同一人
-
-
 ## v1.0.891 (2026-08-09)
 
 ### 新功能
@@ -26,6 +20,7 @@
 - 新增订单确认页 pay.vue(明细/优惠券/拆单提示/虚拟支付逐期)
 - /api/membership/plans 返回原价字段(originalYearly/Monthly/Quarterly)
 - 会员原价配置(sys_config 新增4键: family/premium 年费月费原价)
+- 商城首页搜索栏加购物车入口(带角标) 财富页移除购物车服务项
 
 ### 重构
 - upgrade页移除支付/优惠券逻辑,改下一步跳转pay,修复会员状态判断bug,支持划线原价
@@ -40,8 +35,11 @@
 
 ## v1.0.889 (2026-08-09)
 
+### 样式
+- 添加商城订单快捷和推广中心快捷样式
+
 ### 新功能
-- 商城首页搜索栏加购物车入口(带角标) 财富页移除购物车服务项
+- 商城订单快捷+推广中心快捷+购物车/订单角标
 
 
 ## v1.0.888 (2026-08-09)
@@ -60,6 +58,31 @@
 - 重设计'我的'页面为信息集散中心 - 用户卡片+数据概览+五维能量+待办提醒+功能菜单
 
 
+## v1.0.886 (2026-08-09)
+
+### Bug 修复
+- modifyColumnToText 跳过不存在的表,消除启动日志WARN
+- unify section headers + quick bar + task shortcuts; fix(tab-bar): center button position; fix(backend): ClassCastException in purchase field save
+- 修复DBI启动日志WARN(RENAME语法+不存在表/列的迁移)
+
+### 其他
+- - 8 个不存在的表(user_operation_log/daily_health_checkin等)不再产生噪声
+- 
+-   → 改用 tableExists() 前置检查 + RENAME TABLE(无 IF EXISTS)
+- - 修复 1 处 CHANGE COLUMN min_quantity(列已不存在时报错)
+-   → 改用 columnExists() 前置检查,不存在则跳过
+- - 新增 tableExists() + columnExists() 辅助方法
+- - 不影响业务,仅消除启动日志噪声
+- 
+
+### 重构
+- 重设计家庭挑战主页 - 移除重复的快速创建区,模板移至弹窗内,简化布局
+
+### 新功能
+- 任务首页重设计 - 三Tab布局 + 家庭成员任务分配 + 接受/拒绝 + 关系分数调整
+- 任务首页重设计 - 支持分配任务给家庭成员,接受/拒绝机制及关系分数调整
+
+
 ## v1.0.885 (2026-08-09)
 
 ### Bug 修复

+ 31 - 8
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.892
+> 当前版本: v1.0.891
 
 ## 历史版本
 
@@ -8,12 +8,6 @@
 
 ---
 
-## v1.0.892 (2026-08-09)
-
-### Bug 修复
-- 添加家庭成员数量校验 - 任务分配需≥2人/挑战需≥2参与者/互动双方不能同一人
-
-
 ## v1.0.891 (2026-08-09)
 
 ### 新功能
@@ -30,6 +24,7 @@
 - 新增订单确认页 pay.vue(明细/优惠券/拆单提示/虚拟支付逐期)
 - /api/membership/plans 返回原价字段(originalYearly/Monthly/Quarterly)
 - 会员原价配置(sys_config 新增4键: family/premium 年费月费原价)
+- 商城首页搜索栏加购物车入口(带角标) 财富页移除购物车服务项
 
 ### 重构
 - upgrade页移除支付/优惠券逻辑,改下一步跳转pay,修复会员状态判断bug,支持划线原价
@@ -44,8 +39,11 @@
 
 ## v1.0.889 (2026-08-09)
 
+### 样式
+- 添加商城订单快捷和推广中心快捷样式
+
 ### 新功能
-- 商城首页搜索栏加购物车入口(带角标) 财富页移除购物车服务项
+- 商城订单快捷+推广中心快捷+购物车/订单角标
 
 
 ## v1.0.888 (2026-08-09)
@@ -64,6 +62,31 @@
 - 重设计'我的'页面为信息集散中心 - 用户卡片+数据概览+五维能量+待办提醒+功能菜单
 
 
+## v1.0.886 (2026-08-09)
+
+### Bug 修复
+- modifyColumnToText 跳过不存在的表,消除启动日志WARN
+- unify section headers + quick bar + task shortcuts; fix(tab-bar): center button position; fix(backend): ClassCastException in purchase field save
+- 修复DBI启动日志WARN(RENAME语法+不存在表/列的迁移)
+
+### 其他
+- - 8 个不存在的表(user_operation_log/daily_health_checkin等)不再产生噪声
+- 
+-   → 改用 tableExists() 前置检查 + RENAME TABLE(无 IF EXISTS)
+- - 修复 1 处 CHANGE COLUMN min_quantity(列已不存在时报错)
+-   → 改用 columnExists() 前置检查,不存在则跳过
+- - 新增 tableExists() + columnExists() 辅助方法
+- - 不影响业务,仅消除启动日志噪声
+- 
+
+### 重构
+- 重设计家庭挑战主页 - 移除重复的快速创建区,模板移至弹窗内,简化布局
+
+### 新功能
+- 任务首页重设计 - 三Tab布局 + 家庭成员任务分配 + 接受/拒绝 + 关系分数调整
+- 任务首页重设计 - 支持分配任务给家庭成员,接受/拒绝机制及关系分数调整
+
+
 ## v1.0.885 (2026-08-09)
 
 ### Bug 修复

+ 10 - 8
cfc-web/src/views/admin/ProductEdit.vue

@@ -63,15 +63,16 @@
           />
           <span style="margin-left: 10px; font-size: 12px; color: #999;">在成长档案上传页推荐该商品</span>
         </el-form-item>
-        <el-form-item label="排序值">
-          <el-input-number v-model="growthArchive.sortOrder" :min="0" :precision="0" style="width: 200px;" controls />
-          <span style="margin-left: 10px; font-size: 12px; color: #999;">越小越靠前</span>
-        </el-form-item>
 
         <el-form-item label="商品简介" prop="intro">
           <el-input v-model="form.intro" type="textarea" :rows="2" placeholder="简短描述" maxlength="200" />
         </el-form-item>
 
+        <el-form-item label="排序值">
+          <el-input-number v-model="form.sortOrder" :min="0" :precision="0" style="width: 200px;" controls />
+          <span style="margin-left: 10px; font-size: 12px; color: #999;">越小越靠前</span>
+        </el-form-item>
+
         <el-form-item label="商品描述" prop="description">
           <div class="rich-editor">
             <Toolbar :editor="editorInstance" :defaultConfig="toolbarConfig" />
@@ -325,7 +326,8 @@ export default {
         assessmentType: 'single',
         totalSessions: 1,
         validityDays: 365,
-        guideScope: 'all'
+        guideScope: 'all',
+        sortOrder: 0
       },
       servicePersons: [], // 线上服务者(deliveryMethod=4): [{userId,name,title,intro,avatar,price,originalPrice,slots:[{startTime,endTime,maxCapacity}]}]
       onlineSlots: [], // 单人线上时段(deliveryMethod=5): [{startTime,endTime,maxCapacity}]
@@ -471,7 +473,8 @@ export default {
             assessmentType: p.assessmentType || 'single',
             totalSessions: p.totalSessions || 1,
             validityDays: p.validityDays || 365,
-            guideScope: p.guideScope || 'all'
+            guideScope: p.guideScope || 'all',
+            sortOrder: p.sortOrder != null ? p.sortOrder : 0
           }
           if (p.images) {
             try {
@@ -786,8 +789,7 @@ async loadGiftRule(productId) {
               try {
                 await saveGrowthArchiveProduct({
                   productId: this.form.id,
-                  status: this.growthArchive.enabled ? 1 : 0,
-                  sortOrder: this.growthArchive.sortOrder
+                  status: this.growthArchive.enabled ? 1 : 0
                 })
               } catch (e) {
                 this.$message.error('保存成长档案关联失败')

+ 5 - 0
cfc-web/src/views/admin/ProductManage.vue

@@ -78,6 +78,11 @@
           <el-tag :type="statusType(row.status)" size="mini">{{ statusLabel(row.status) }}</el-tag>
         </template>
       </el-table-column>
+      <el-table-column prop="sortOrder" label="排序" width="80">
+        <template slot-scope="{ row }">
+          {{ row.sortOrder != null ? row.sortOrder : '-' }}
+        </template>
+      </el-table-column>
       <el-table-column prop="createdAt" label="创建时间" width="160">
         <template slot-scope="{ row }">
           {{ formatTime(row.createdAt) }}

+ 9 - 6
docs/superpowers/specs/2026-08-09-dimension-recommendation-remove-design.md

@@ -15,7 +15,7 @@
 | `DimensionProducts.vue` | 接收 `dimensionCode` prop,传给 `/api/recommend/dimension-products` | 移除 dimensionCode prop,自加载时不传 dimensionCode,后端返回全量推荐 |
 | `DimensionArticles.vue` | 接收 `dimensionCode` prop(required 但未实际使用),数据由父页面传入 | 移除 dimensionCode prop,新增自加载 `getFeaturedArticles({ size: 3 })` |
 | `DimensionActivities.vue` | 接收 `dimensionCode` prop 用于显示维度图标,数据由父页面传入 | 移除 dimensionCode prop,固定图标为默认,新增自加载 `getActivityList({ page: 1, size: 3 })` |
-| `DimensionTasks.vue` | 接收 `dimensionCode` prop,传给 `getTodayTasksByCategory(memberId, category)` | 移除 dimensionCode prop,改为调用 `getTodayTasks(memberId)`(不传 category) |
+| `DimensionTasks.vue` | 接收 `dimensionCode` prop,传给 `getTodayTasksByCategory(memberId, category)` | 移除 dimensionCode prop,改为调用 `getTodayTasks(memberId)`(已存在该 API,不传 category 返回全部今日任务) |
 
 ### 后端(1 个)
 
@@ -24,7 +24,8 @@
 - 改为:`dimensionCode` 为空时,调用 `getDimensionRecommendations(null, ...)` 查询全量映射
 
 `ProductRecommendationService.getDimensionRecommendations()`:
-- `dimensionCode` 为 null 时,查询所有 `dimension_mapping` 记录(去掉 `.eq(dimensionCode)` 条件)
+- `dimensionCode` 为 null 时,查询所有 `dimension_mapping` 记录(去掉 `.eq(dimensionCode)` 条件),按 `matchScore` 排序返回
+- 当前的 `applyDimensionBoost` / `calculateMatchScore` / `buildRecommendationReason` 在 `dimensionCode` 为 null 时无法计算维度分数 → 改为使用成员历史得分最低的维度作为"推荐维度"计算
 
 ### 父页面(11 个)
 
@@ -125,11 +126,13 @@ props: {
   memberId: { type: Number, default: null }
 }
 
-// 改为不传 category
+// 改为调用 getTodayTasks(不传 category,返回全部)
 loadTasks: function() {
-  if (!this.memberId) return
-  api.getTodayTasks({ memberId: self.memberId }).then(...)
-  // 或者直接调用 getTodayTasksByCategory(memberId, null)
+  var self = this
+  if (!self.memberId) return
+  api.getTodayTasks(self.memberId).then(function(res) {
+    self.tasksData = res.data || []
+  }).catch(function() {})
 }
 ```