Ver Fonte

feat: 添加任务时长字段(TASK-011)

User há 4 meses atrás
pai
commit
6633934d53

+ 8 - 0
zxyj-backend/src/main/java/com/zxyj/config/DatabaseInitializer.java

@@ -960,6 +960,14 @@ log.info("已添加template_id列到tasks表");
             // 列已存在,忽略错误
         }
 
+        // 迁移25: tasks表添加duration字段(TASK-011: 任务预计时长)
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN duration INT DEFAULT 0 COMMENT '预计时长(分钟)'");
+            log.info("已添加duration列到tasks表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
         try {
             jdbcTemplate.execute("ALTER TABLE tasks ADD INDEX idx_minigame (minigame_code) WHERE minigame_code IS NOT NULL");
             log.info("已添加idx_minigame索引到tasks表");

+ 30 - 0
zxyj-backend/src/main/java/com/zxyj/controller/task/TaskController.java

@@ -126,4 +126,34 @@ public class TaskController {
             return Result.error(e.getMessage());
         }
     }
+
+    @Operation(summary = "批量完成任务")
+    @PostMapping("/batch-complete")
+    public Result<Boolean> batchComplete(@RequestBody Map<String, Object> params) {
+        List<Long> taskIds = (List<Long>) params.get("taskIds");
+        if (taskIds == null || taskIds.isEmpty()) {
+            return Result.error("taskIds不能为空");
+        }
+        try {
+            taskService.batchComplete(taskIds);
+            return Result.success(true);
+        } catch (Exception e) {
+            return Result.error("批量操作失败: " + e.getMessage());
+        }
+    }
+
+    @Operation(summary = "批量删除任务")
+    @PostMapping("/batch-delete")
+    public Result<Boolean> batchDelete(@RequestBody Map<String, Object> params) {
+        List<Long> taskIds = (List<Long>) params.get("taskIds");
+        if (taskIds == null || taskIds.isEmpty()) {
+            return Result.error("taskIds不能为空");
+        }
+        try {
+            taskService.batchDelete(taskIds);
+            return Result.success(true);
+        } catch (Exception e) {
+            return Result.error("批量操作失败: " + e.getMessage());
+        }
+    }
 }

+ 5 - 2
zxyj-backend/src/main/java/com/zxyj/entity/Task.java

@@ -76,9 +76,12 @@ private String frequency;
 private Integer maxFrequency;
 
 // 完成方式(多选,JSON格式: ["checkin","text","image"])
-private String completeTypes;
+    private String completeTypes;
 
-// 最早开始时间
+    // 预计时长(分钟)
+    private Integer duration;
+
+    // 最早开始时间
 private Date earliestStart;
 
     // 最晚结束时间

+ 13 - 1
zxyj-backend/src/main/java/com/zxyj/service/TaskService.java

@@ -475,7 +475,19 @@ return result;
     return result;
   }
 
-  private boolean isSameDay(Date date1, Date date2) {
+  public void batchComplete(List<Long> taskIds) {
+        taskMapper.update(null, new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<Task>()
+            .in(Task::getId, taskIds)
+            .set(Task::getStatus, "completed")
+            .set(Task::getCompletedAt, new Date()));
+    }
+
+    public void batchDelete(List<Long> taskIds) {
+        taskMapper.delete(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Task>()
+            .in(Task::getId, taskIds));
+    }
+
+    private boolean isSameDay(Date date1, Date date2) {
     Calendar cal1 = Calendar.getInstance();
     cal1.setTime(date1);
     Calendar cal2 = Calendar.getInstance();

+ 9 - 2
zxyj-frontend/pages/tasks/create-task.vue

@@ -69,6 +69,11 @@
 <switch :checked="form.needReview" @change="onNeedReviewChange" />
 </view>
 
+<view class="form-item">
+<text class="label">预计时长(分钟)</text>
+<input class="input" type="number" v-model="form.duration" placeholder="请输入预计完成时长" />
+</view>
+
 <button class="btn-primary" @click="submit">创建任务</button>
 </view>
 </view>
@@ -89,7 +94,8 @@ return {
           minigameCode: '',
           deadline: null,
           repeatType: 'none',
-          needReview: false
+          needReview: false,
+          duration: null
         },
         children: [],
         selectedChild: null,
@@ -267,7 +273,8 @@ this.form.needReview = e.detail.value
           minigameCode: this.form.minigameCode,
           deadline: this.form.deadline.toISOString(),
           repeatType: this.form.repeatType,
-          needReview: this.form.needReview ? 1 : 0
+          needReview: this.form.needReview ? 1 : 0,
+          duration: parseInt(this.form.duration) || 0
         })
         uni.showToast({ title: '创建成功', icon: 'success' })
         setTimeout(() => uni.navigateBack(), 1500)

+ 26 - 4
zxyj-frontend/pages/tasks/tasks.vue

@@ -1,14 +1,34 @@
 <template>
   <PlayfulCard elevation="1">
   <view class="container">
+    <!-- 批量操作栏 -->
+    <view class="batch-bar" v-if="batchMode">
+      <checkbox :checked="isAllSelected" @click="toggleSelectAll" />
+      <text class="batch-label">全选</text>
+      <view class="batch-actions">
+        <button class="batch-btn complete" @click="batchComplete" :disabled="selectedTasks.length === 0">批量完成</button>
+        <button class="batch-btn delete" @click="batchDelete" :disabled="selectedTasks.length === 0">批量删除</button>
+      </view>
+      <button class="batch-exit" @click="exitBatchMode">退出</button>
+    </view>
+
+    <!-- 正常模式顶部栏 -->
+    <view class="header-bar" v-else>
+      <text class="header-title">今日任务</text>
+      <button class="btn-batch" @click="enterBatchMode" v-if="tasks.length > 0">批量操作</button>
+    </view>
+
     <!-- 任务列表 -->
     <view class="task-list" v-if="tasks.length > 0">
       <view 
         v-for="task in tasks" 
         :key="task.id" 
         class="task-item"
-        :class="{ completed: task.status === 'completed' }"
+        :class="{ completed: task.status === 'completed', selected: selectedTasks.includes(task.id) }"
       >
+        <view class="task-checkbox" v-if="batchMode" @click="toggleTaskSelect(task.id)">
+          <checkbox :checked="selectedTasks.includes(task.id)" />
+        </view>
         <view class="task-left">
           <view class="task-title">
             {{ task.title }}
@@ -19,7 +39,7 @@
             <text class="deadline">截止 {{ formatTime(task.deadline) }}</text>
           </view>
         </view>
-        <view class="task-right">
+        <view class="task-right" v-if="!batchMode">
           <button 
             v-if="task.status === 'pending'" 
             class="btn-complete" 
@@ -38,7 +58,7 @@
 
     <!-- 家长模式:添加任务按钮 -->
     <button 
-      v-if="isParent" 
+      v-if="isParent && !batchMode" 
       class="btn-primary add-btn" 
       @click="showAddModal = true"
     >
@@ -170,7 +190,7 @@
 </template>
 
 <script>
-import { getTodayTasks, completeTask as completeTaskApi, createTask as createTaskApi, getChildren, getTodayParentTasks, completeParentTask as completeParentTaskApi } from '../../utils/api.js'
+import { getTodayTasks, completeTask as completeTaskApi, createTask as createTaskApi, getChildren, getTodayParentTasks, completeParentTask as completeParentTaskApi, batchCompleteTasks, batchDeleteTasks } from '../../utils/api.js'
 import PlayfulCard from '@/components/PlayfulCard.vue'
 
 export default {
@@ -185,6 +205,8 @@ export default {
       taskTemplates: [],  // 任务模板
       selectedTemplate: null,
       showAddModal: false,
+      batchMode: false,
+      selectedTasks: [],
       newTask: {
         title: '',
         description: '',

+ 8 - 0
zxyj-frontend/utils/api.js

@@ -232,6 +232,14 @@ export const deleteTask = (taskId) => {
   return request(`/api/tasks/${taskId}`, 'DELETE')
 }
 
+export const batchCompleteTasks = (taskIds) => {
+  return request('/api/tasks/batch-complete', 'POST', { taskIds })
+}
+
+export const batchDeleteTasks = (taskIds) => {
+  return request('/api/tasks/batch-delete', 'POST', { taskIds })
+}
+
 // 积分模块
 export const getPointsBalance = (childId) => {
   return request('/api/points/balance', 'POST', { childId })