Bladeren bron

feat: 优化任务对话框及主页面功能

- CompleteDialog: 添加完成日期选择,优化工时计算逻辑

- ReleaseCompleteDialog: 优化发版任务完成流程

- TaskDetailDialog: 完善任务详情展示,增加排期相关信息

- index.vue: 增加排期状态筛选和多项目选择功能

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
程健 3 weken geleden
bovenliggende
commit
9ccfe56e55

+ 40 - 5
src/views/devops/software/components/CompleteDialog.vue

@@ -19,6 +19,15 @@
         </div>
       </el-form-item>
 
+      <el-form-item label="完成时间" prop="completionDate">
+        <el-date-picker
+          v-model="form.completionDate"
+          placeholder="选择完成日期"
+          style="width: 100%"
+          type="date"
+          value-format="yyyy-MM-dd" />
+      </el-form-item>
+
       <el-form-item label="备注" prop="remark">
         <el-input v-model="form.remark" placeholder="请输入完成备注..." :rows="4" type="textarea" />
       </el-form-item>
@@ -66,6 +75,7 @@
         form: {
           actualWorkHour: null,
           remark: '',
+          completionDate: '',
           attachments: [],
         },
         rules: {
@@ -74,11 +84,34 @@
         submitLoading: false,
       }
     },
-    mounted() {
-      // initialize from taskData if provided
-      if (this.taskData && this.taskData.actualWorkHour != null) {
-        this.form.actualWorkHour = this.taskData.actualWorkHour
-      }
+    watch: {
+      async visible(val) {
+        if (!val || !this.taskId) return
+        // set default completion date to today
+        if (!this.form.completionDate) {
+          const today = new Date()
+          const y = today.getFullYear()
+          const m = String(today.getMonth() + 1).padStart(2, '0')
+          const d = String(today.getDate()).padStart(2, '0')
+          this.form.completionDate = `${y}-${m}-${d}`
+        }
+        // pre-fill actualWorkHour from registered work hours
+        try {
+          const res = await opsEventTaskApi.getWorkHourList(this.taskId)
+          const workHourList = res.data?.list || res.data || []
+          const totalRegisteredHours = workHourList.reduce((sum, row) => sum + (Number(row.actualHour) || 0), 0)
+          if (totalRegisteredHours > 0) {
+            this.form.actualWorkHour = Math.round(totalRegisteredHours * 10) / 10
+            return
+          }
+        } catch (e) {
+          // fallback to taskData if query fails
+        }
+        // fallback: use taskData.actualWorkHour
+        if (this.taskData && this.taskData.actualWorkHour != null) {
+          this.form.actualWorkHour = this.taskData.actualWorkHour
+        }
+      },
     },
     methods: {
       addActualWorkHour(hours) {
@@ -114,6 +147,7 @@
               remark: this.formatRemark(this.form.remark),
               attachments,
               testResult,
+              completeDate: this.form.completionDate,
             }
             await opsEventTaskApi.complete(payload)
 
@@ -132,6 +166,7 @@
         // reset form and close
         this.form.actualWorkHour = null
         this.form.remark = ''
+        this.form.completionDate = ''
         this.resetAttachmentFiles()
         this.$emit('update:visible', false)
       },

+ 2 - 6
src/views/devops/software/components/ReleaseCompleteDialog.vue

@@ -36,7 +36,7 @@
           </el-table>
           <el-empty v-else description="暂无关联任务,请点击上方按钮添加" :image-size="60" />
         </div>
-        <div class="form-tip">选择本次发版包含的研发任务(仅显示未发版的任务)</div>
+        <div class="form-tip">选择本次发版包含的研发任务(选填,仅显示未发版的任务)</div>
       </el-form-item>
 
       <!-- 发版工时 -->
@@ -168,7 +168,7 @@
         submitLoading: false,
         rules: {
           actualWorkHour: [{ required: true, message: '请输入发版工时', trigger: 'blur' }],
-          devTaskIds: [{ required: true, message: '请选择关联的研发任务', trigger: 'change', type: 'array', min: 1 }],
+          devTaskIds: [],
         },
         // 任务选择弹窗
         taskSelectDialogVisible: false,
@@ -371,10 +371,6 @@
             this.$message.error('任务ID不能为空')
             return
           }
-          if (this.selectedDevTasks.length === 0) {
-            this.$message.error('请至少选择一个关联研发任务')
-            return
-          }
           this.submitLoading = true
           try {
             // 上传附件

+ 44 - 3
src/views/devops/software/components/TaskDetailDialog.vue

@@ -28,8 +28,23 @@
             <el-button :loading="submitLoading" size="small" type="primary" @click="handleEditSubmit">保存</el-button>
           </template>
           <template v-else>
-            <el-button v-if="isProcessingTask" size="small" @click="handleAddWorkHour">工时</el-button>
-            <el-button v-if="isProcessingTask" size="small" type="primary" @click="handleCompleteTask">完成</el-button>
+            <el-button v-if="isProcessingTask && isWorkHourAllowedType" size="small" @click="handleAddWorkHour">
+              工时
+            </el-button>
+            <el-button
+              v-if="isProcessingTask && !isReleaseTask"
+              size="small"
+              type="primary"
+              @click="handleCompleteTask">
+              完成
+            </el-button>
+            <el-button
+              v-if="isProcessingTask && isReleaseTask"
+              size="small"
+              type="primary"
+              @click="handleReleaseCompleteTask">
+              发版完成
+            </el-button>
             <el-button size="small" @click="handleClose">关闭</el-button>
           </template>
         </div>
@@ -500,8 +515,16 @@
     <complete-dialog
       :task-data="detailData"
       :task-id="detailData && detailData.id"
+      :test-result="detailData && detailData.testResult"
       :visible.sync="completeDialogVisible"
       @refresh="handleRefreshData" />
+
+    <!-- 发版完成弹窗 -->
+    <release-complete-dialog
+      :task-data="detailData"
+      :task-id="detailData && detailData.id"
+      :visible.sync="releaseCompleteDialogVisible"
+      @refresh="handleRefreshData" />
   </div>
 </template>
 
@@ -519,10 +542,19 @@
   import WorkHourListDialog from './WorkHourListDialog.vue'
   import WorkHourDialog from './WorkHourDialog.vue'
   import CompleteDialog from './CompleteDialog.vue'
+  import ReleaseCompleteDialog from './ReleaseCompleteDialog.vue'
 
   export default {
     name: 'TaskDetailDialog',
-    components: { Editor, Toolbar, ReleaseTaskListDialog, WorkHourListDialog, WorkHourDialog, CompleteDialog },
+    components: {
+      Editor,
+      Toolbar,
+      ReleaseTaskListDialog,
+      WorkHourListDialog,
+      WorkHourDialog,
+      CompleteDialog,
+      ReleaseCompleteDialog,
+    },
     props: {
       visible: {
         type: Boolean,
@@ -547,6 +579,7 @@
         workHourListDialogVisible: false,
         workHourDialogVisible: false,
         completeDialogVisible: false,
+        releaseCompleteDialogVisible: false,
         // 参会人员列表(需求评审类型)
         participantList: [],
         // 快速登记相关
@@ -644,6 +677,9 @@
       isProcessingTask() {
         return this.detailData && String(this.detailData.taskStatus) === '20'
       },
+      isWorkHourAllowedType() {
+        return this.detailData && ['20', '25', '30', '35'].includes(String(this.detailData.taskType))
+      },
     },
     watch: {
       visible(val) {
@@ -699,9 +735,13 @@
       handleCompleteTask() {
         this.completeDialogVisible = true
       },
+      handleReleaseCompleteTask() {
+        this.releaseCompleteDialogVisible = true
+      },
       handleRefreshData() {
         this.workHourDialogVisible = false
         this.completeDialogVisible = false
+        this.releaseCompleteDialogVisible = false
         if (this.detailData && this.detailData.id) {
           this.fetchRecordList()
           this.fetchAttachmentList()
@@ -778,6 +818,7 @@
         this.workHourListDialogVisible = false
         this.workHourDialogVisible = false
         this.completeDialogVisible = false
+        this.releaseCompleteDialogVisible = false
         this.isTaskDescExpanded = false
         // 重置参会人员
         this.participantList = []

+ 59 - 3
src/views/devops/software/index.vue

@@ -93,6 +93,27 @@
               type="daterange"
               value-format="yyyy-MM-dd" />
           </el-form-item>
+          <el-form-item label="产品线">
+            <el-select
+              v-model="queryForm.productLine"
+              class="query-input query-input--compact"
+              clearable
+              placeholder="请选择"
+              @change="handleProductLineChange">
+              <el-option v-for="dict in productLineOptions" :key="dict.key" :label="dict.value" :value="dict.key" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="排期状态">
+            <el-select
+              v-model="queryForm.scheduleStatus"
+              class="query-input query-input--compact"
+              clearable
+              placeholder="请选择">
+              <el-option label="全部" value="" />
+              <el-option label="已排期" value="scheduled" />
+              <el-option label="未排期" value="unscheduled" />
+            </el-select>
+          </el-form-item>
         </el-form>
       </div>
     </div>
@@ -578,6 +599,8 @@
           planEndDateRange: [],
           createdTimeRange: [],
           completeTimeRange: [],
+          productLine: '',
+          scheduleStatus: '',
         },
         // 多列排序
         sortFields: [],
@@ -770,7 +793,7 @@
           const params = {
             pageNum: 1,
             pageSize: 999,
-            productLine: '10,20,30',
+            productLine: this.queryForm.productLine || '10,20,30',
             sortField: 'contract_no',
             sortOrder: 'desc',
           }
@@ -833,8 +856,11 @@
             // 完成日期范围
             completeTimeStart: this.queryForm.completeTimeRange?.[0] || '',
             completeTimeEnd: this.queryForm.completeTimeRange?.[1] || '',
-            // 排序
-            sortFields: this.sortFields,
+            scheduleStatus: this.queryForm.scheduleStatus || '',
+          }
+          // 如果选了产品线,收集项目 ID
+          if (this.queryForm.productLine) {
+            params.projectIds = this.projects.filter((p) => p.id !== '').map((p) => parseInt(p.id))
           }
           const res = await opsEventTaskApi.getList(params)
           this.tableData = res.data?.list || []
@@ -870,6 +896,11 @@
           this.remoteFetchOpsUsers('')
         }
       },
+      // 产品线变更
+      handleProductLineChange() {
+        this.selectedProject = ''
+        this.fetchProjects()
+      },
       // 查询
       handleSearch() {
         this.queryForm.pageNum = 1
@@ -888,6 +919,8 @@
           planEndDateRange: [],
           createdTimeRange: [],
           completeTimeRange: [],
+          productLine: '',
+          scheduleStatus: '',
         }
         this.sortFields = []
         this.selectedProject = ''
@@ -1439,6 +1472,29 @@
     }
   }
 
+  .query-form-advanced {
+    padding-top: 8px;
+    border-top: 1px dashed #ebeef5;
+
+    ::v-deep .el-form {
+      display: flex;
+      flex-wrap: wrap;
+      align-items: center;
+      row-gap: 8px;
+    }
+
+    ::v-deep .el-form-item {
+      margin-bottom: 2px;
+      margin-right: 20px;
+    }
+
+    ::v-deep .el-form-item__label {
+      padding-right: 8px;
+      font-size: 13px;
+      color: #606266;
+    }
+  }
+
   .query-form-fields {
     flex: 1;
     min-width: 0;