Browse Source

feat(task): 前端统一任务中心——tasks.vue聚合进度型任务,daily-tasks.vue重定向

E2E Test Bot 1 month ago
parent
commit
b7644aa090

+ 8 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/Task.java

@@ -107,6 +107,14 @@ private Date acceptDeadline;
     /** 进度目标值(is_daily_progress=1 时有效;原 growth_task.targetValue) */
     private Integer targetValue;
 
+    /** 当前进度(进度型任务,由查询时回填,不入库) */
+    @com.baomidou.mybatisplus.annotation.TableField(exist = false)
+    private Integer progress;
+
+    /** 是否已完成今日进度(进度型任务,由查询时回填,不入库) */
+    @com.baomidou.mybatisplus.annotation.TableField(exist = false)
+    private Integer todayDone;
+
     /** 1=会员任务(方案/套餐生成, 执行需会员); 0=普通任务(自建可免费执行) */
     private Integer memberOnly;
 

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

@@ -326,9 +326,16 @@ public List<Task> getTodayTasks(Long memberId, String dimensionCode) {
 
         LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<Task>()
             .eq(Task::getFamilyMemberId, memberId)
-            .ge(Task::getDeadline, startOfDay)
-            .lt(Task::getDeadline, endOfDay)
             .in(Task::getStatus, Arrays.asList("pending", "completed", "locked", "pending_review"))
+            .and(w -> w
+                // 普通任务:按 deadline 判断今日
+                .and(w2 -> w2.isNotNull(Task::getDeadline)
+                        .ge(Task::getDeadline, startOfDay)
+                        .lt(Task::getDeadline, endOfDay))
+                // 进度型任务:按 createdAt 判断今日(deadline 为 NULL)
+                .or(w3 -> w3.eq(Task::getIsDailyProgress, 1)
+                        .ge(Task::getCreatedAt, startOfDay)
+                        .lt(Task::getCreatedAt, endOfDay)))
             .orderByAsc(Task::getDeadline);
 
         if (dimensionCode != null && !dimensionCode.isEmpty()) {
@@ -340,6 +347,7 @@ public List<Task> getTodayTasks(Long memberId, String dimensionCode) {
 
         SortUtil.applySort(wrapper);
         List<Task> all = taskMapper.selectList(wrapper);
+        fillProgress(all, memberId);
         return filterLockedChildren(all);
     }
 
@@ -1527,6 +1535,32 @@ return 0; // 达到每日扣分上限
 
     // ==================== 统一任务系统:成长任务迁移(原 GrowthTaskService) ====================
 
+    /**
+     * 回填进度型任务的当前进度(progress/todayDone,不入库)
+     */
+    private void fillProgress(List<Task> tasks, Long memberId) {
+        if (tasks == null || tasks.isEmpty()) {
+            return;
+        }
+        for (Task t : tasks) {
+            if (t.getIsDailyProgress() != null && t.getIsDailyProgress() == 1) {
+                TaskExecution exec = taskExecutionMapper.selectOne(new LambdaQueryWrapper<TaskExecution>()
+                        .eq(TaskExecution::getTaskId, t.getId())
+                        .eq(TaskExecution::getMemberId, memberId)
+                        .last("LIMIT 1"));
+                int progress = 0;
+                if (exec != null && exec.getExtra() != null) {
+                    try {
+                        progress = JSON.parseObject(exec.getExtra()).getIntValue("progress");
+                    } catch (Exception ignore) {
+                    }
+                }
+                t.setProgress(progress);
+                t.setTodayDone("completed".equals(t.getStatus()) ? 1 : 0);
+            }
+        }
+    }
+
     /**
      * 统一任务系统:更新每日进度型任务的进度(原 growth_task updateProgress)
      * 按 (userId→familyMemberId, taskKey) 找当日任务实例,累计进度,达标自动完成+发奖

+ 2 - 2
cfc-frontend/pages/tasks/daily-tasks.vue

@@ -97,8 +97,8 @@ export default {
     }
   },
   onLoad() {
-    this.loadTasks()
-    this._onLoadFired = true
+    // 统一任务系统:成长任务已并入 tasks.vue 统一任务中心,重定向
+    uni.redirectTo({ url: '/pages/tasks/tasks?tab=today' })
   },
   onShow() {
     if (this._onLoadFired) {

+ 45 - 0
cfc-frontend/pages/tasks/tasks.vue

@@ -66,6 +66,13 @@
             <text class="points">+{{ task.points }}分</text>
             <text class="deadline">截止 {{ formatTime(task.deadline) }}</text>
           </view>
+          <!-- 进度型任务:展示进度条 -->
+          <view class="progress-wrap" v-if="task.isDailyProgress === 1">
+            <view class="progress-bar">
+              <view class="progress-fill" :style="{ width: getProgressPercent(task) + '%' }"></view>
+            </view>
+            <text class="progress-text">{{ task.progress || 0 }}/{{ task.targetValue || 1 }}</text>
+          </view>
           <text v-if="task.status === 'locked'" class="lock-tip">🔒 需先完成前置任务后解锁</text>
         </view>
         <view class="task-right" v-if="task.status === 'pending' || task.status === 'in_progress'">
@@ -229,6 +236,9 @@ export default {
     }
   },
   onLoad(options) {
+    if (options && options.tab) {
+      this.activeTab = options.tab
+    }
     if (options && options.memberId) {
       var mid = parseInt(options.memberId, 10)
       if (!isNaN(mid)) {
@@ -549,6 +559,13 @@ export default {
       var min = String(d.getMinutes()).padStart(2, '0')
       return y + '-' + m + '-' + day + ' ' + h + ':' + min
     },
+    getProgressPercent(task) {
+      var progress = task.progress || 0
+      var target = task.targetValue || 1
+      if (target <= 0) return 0
+      var pct = Math.round((progress / target) * 100)
+      return pct > 100 ? 100 : pct
+    },
     async createTask() {
       if (!this.newTask.title) {
         uni.showToast({ title: '请输入标题', icon: 'none' })
@@ -740,6 +757,34 @@ export default {
   color: #9CA3AF;
 }
 
+.progress-wrap {
+  display: flex;
+  align-items: center;
+  margin-top: 12rpx;
+}
+
+.progress-bar {
+  flex: 1;
+  height: 12rpx;
+  background: #F3F4F6;
+  border-radius: 6rpx;
+  overflow: hidden;
+  margin-right: 12rpx;
+}
+
+.progress-fill {
+  height: 100%;
+  background: linear-gradient(135deg, #F97316, #FB923C);
+  border-radius: 6rpx;
+  transition: width 0.3s ease;
+}
+
+.progress-text {
+  font-size: 22rpx;
+  color: #F97316;
+  white-space: nowrap;
+}
+
 .task-right {
   flex-shrink: 0;
 }