Selaa lähdekoodia

feat: TaskDetailDialog功能测试任务通过/不通过及刷新后关闭

1. 功能测试任务(type=30)显示通过/不通过按钮替代完成按钮,符合与index.vue一致的行为\n2. 通过/不通过→CompleteDialog→handleCompleteSuccess流程,不通过时打开TaskEditDialog创建BUG\n3. handleRefreshData增加getById响应数据键名归一化(snake_case→camelCase),确保computed属性正确重求\n4. 保存操作后自动关闭详情弹窗,不通过→BUG流程保持弹窗打开

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
程健 3 viikkoa sitten
vanhempi
commit
e8771d3f6f
1 muutettua tiedostoa jossa 144 lisäystä ja 9 poistoa
  1. 144 9
      src/views/devops/software/components/TaskDetailDialog.vue

+ 144 - 9
src/views/devops/software/components/TaskDetailDialog.vue

@@ -31,19 +31,26 @@
             <el-button v-if="isProcessingTask && isWorkHourAllowedType" size="small" @click="handleAddWorkHour">
               工时
             </el-button>
+            <!-- 功能测试任务:通过/不通过 -->
+            <template v-if="isProcessingTask && isFunctionalTestTask">
+              <el-button size="small" type="success" @click="handlePassTask">通过</el-button>
+              <el-button size="small" type="danger" @click="handleFailTask">不通过</el-button>
+            </template>
+            <!-- 系统发版任务 -->
             <el-button
-              v-if="isProcessingTask && !isReleaseTask"
+              v-if="isProcessingTask && isReleaseTask && !isFunctionalTestTask"
               size="small"
               type="primary"
-              @click="handleCompleteTask">
-              完成
+              @click="handleReleaseCompleteTask">
+              发版完成
             </el-button>
+            <!-- 其他处理中任务 -->
             <el-button
-              v-if="isProcessingTask && isReleaseTask"
+              v-if="isProcessingTask && !isReleaseTask && !isFunctionalTestTask"
               size="small"
               type="primary"
-              @click="handleReleaseCompleteTask">
-              发版完成
+              @click="handleCompleteTask">
+              完成
             </el-button>
             <el-button size="small" @click="handleClose">关闭</el-button>
           </template>
@@ -515,9 +522,10 @@
     <complete-dialog
       :task-data="detailData"
       :task-id="detailData && detailData.id"
-      :test-result="detailData && detailData.testResult"
+      :test-result="currentTestResult"
       :visible.sync="completeDialogVisible"
-      @refresh="handleRefreshData" />
+      @refresh="handleRefreshData"
+      @success="handleCompleteSuccess" />
 
     <!-- 发版完成弹窗 -->
     <release-complete-dialog
@@ -525,6 +533,9 @@
       :task-id="detailData && detailData.id"
       :visible.sync="releaseCompleteDialogVisible"
       @refresh="handleRefreshData" />
+
+    <!-- 不通过后创建BUG任务弹窗 -->
+    <task-edit-dialog :task-data="bugEditData" :visible.sync="editDialogVisible" @refresh="handleRefreshData" />
   </div>
 </template>
 
@@ -543,6 +554,7 @@
   import WorkHourDialog from './WorkHourDialog.vue'
   import CompleteDialog from './CompleteDialog.vue'
   import ReleaseCompleteDialog from './ReleaseCompleteDialog.vue'
+  import TaskEditDialog from './TaskEditDialog.vue'
 
   export default {
     name: 'TaskDetailDialog',
@@ -554,6 +566,7 @@
       WorkHourDialog,
       CompleteDialog,
       ReleaseCompleteDialog,
+      TaskEditDialog,
     },
     props: {
       visible: {
@@ -580,6 +593,12 @@
         workHourDialogVisible: false,
         completeDialogVisible: false,
         releaseCompleteDialogVisible: false,
+        // 功能测试通过/不通过
+        currentTestResult: '',
+        isOpeningFailTask: false,
+        failTaskRow: null,
+        bugEditData: null,
+        editDialogVisible: false,
         // 参会人员列表(需求评审类型)
         participantList: [],
         // 快速登记相关
@@ -670,6 +689,9 @@
       isReleaseTask() {
         return this.detailData && String(this.detailData.taskType) === '38'
       },
+      isFunctionalTestTask() {
+        return this.detailData && String(this.detailData.taskType) === '30'
+      },
       // 是否为需求评审类型
       isRequirementReviewType() {
         return this.detailData && String(this.detailData.taskType) === '10'
@@ -687,6 +709,13 @@
           this.initDialogData()
         }
       },
+      completeDialogVisible(val) {
+        if (!val) {
+          this.currentTestResult = ''
+          this.isOpeningFailTask = false
+          this.failTaskRow = null
+        }
+      },
       detailData() {
         if (this.visible && this.detailData && this.detailData.id) {
           this.initDialogData()
@@ -733,19 +762,125 @@
         this.workHourDialogVisible = true
       },
       handleCompleteTask() {
+        this.currentTestResult = ''
         this.completeDialogVisible = true
       },
       handleReleaseCompleteTask() {
         this.releaseCompleteDialogVisible = true
       },
-      handleRefreshData() {
+      handlePassTask() {
+        this.currentTestResult = 'pass'
+        this.completeDialogVisible = true
+      },
+      async handleFailTask() {
+        this.isOpeningFailTask = true
+        this.failTaskRow = { ...this.detailData }
+        this.currentTestResult = 'fail'
+        this.completeDialogVisible = true
+      },
+      async handleCompleteSuccess() {
+        this.currentTestResult = ''
+        if (this.isOpeningFailTask && this.failTaskRow) {
+          const failRow = this.failTaskRow // capture before watcher clears it
+          const owner = await this.fetchParentTaskOwner(failRow.id)
+          const ownerUserId = owner.opsUserId || failRow.opsUserId
+          this.bugEditData = {
+            taskParentId: failRow.id,
+            projectId: failRow.projectId,
+            taskTitle: failRow.taskTitle || '',
+            taskDesc: failRow.taskDesc || '',
+            functionName: failRow.functionName || '',
+            taskType: '35',
+            isBugTask: true,
+            opsUserId: ownerUserId ? parseInt(ownerUserId) : null,
+            opsUserName: owner.opsUserName || failRow.opsUserName || '',
+          }
+          this.isOpeningFailTask = false
+          this.failTaskRow = null
+          this.editDialogVisible = true
+        }
+      },
+      async fetchParentTaskOwner(currentTaskId) {
+        if (!currentTaskId) return { opsUserId: null, opsUserName: '' }
+        try {
+          const curRes = await opsEventTaskApi.getById(currentTaskId)
+          if (curRes.code !== 200 || !curRes.data) return { opsUserId: null, opsUserName: '' }
+          const taskDetail = curRes.data && curRes.data.data ? curRes.data.data : curRes.data
+          const firstPid = taskDetail.taskParentId || taskDetail.task_parent_id
+          if (!firstPid) return { opsUserId: null, opsUserName: '' }
+          const parentRes = await opsEventTaskApi.getById(firstPid)
+          if (parentRes.code !== 200 || !parentRes.data) return { opsUserId: null, opsUserName: '' }
+          const parentDetail = parentRes.data && parentRes.data.data ? parentRes.data.data : parentRes.data
+          const secondPid = parentDetail.taskParentId || parentDetail.task_parent_id
+          if (!secondPid) return { opsUserId: null, opsUserName: '' }
+          const grandRes = await opsEventTaskApi.getById(secondPid)
+          if (grandRes.code === 200 && grandRes.data) {
+            const grandDetail = grandRes.data && grandRes.data.data ? grandRes.data.data : grandRes.data
+            return {
+              opsUserId: grandDetail.opsUserId || grandDetail.ops_user_id || null,
+              opsUserName: grandDetail.opsUserName || grandDetail.ops_user_name || '',
+            }
+          }
+        } catch (error) {
+          console.error('获取父任务负责人失败:', error)
+        }
+        return { opsUserId: null, opsUserName: '' }
+      },
+      async handleRefreshData() {
         this.workHourDialogVisible = false
         this.completeDialogVisible = false
         this.releaseCompleteDialogVisible = false
         if (this.detailData && this.detailData.id) {
+          // Re-fetch task detail to get updated status after complete/work-hour operations
+          try {
+            const res = await opsEventTaskApi.getById(this.detailData.id)
+            if (res.code === 200 && res.data) {
+              const newData = res.data
+              // Normalize: getById may return snake_case keys (task_status) while
+              // detailData was initialized with camelCase keys (taskStatus) from list API.
+              // The $set loop preserves unknown keys; normalization ensures computed
+              // properties (isProcessingTask, etc.) react to the updated state.
+              const knownCamelKeys = [
+                'taskStatus',
+                'taskType',
+                'taskTitle',
+                'taskDesc',
+                'taskNo',
+                'opsUserId',
+                'opsUserName',
+                'actualWorkHour',
+                'estimateWorkHour',
+                'planStartTime',
+                'planEndTime',
+                'completeTime',
+                'parentId',
+                'testResult',
+                'priority',
+                'testUserId',
+              ]
+              knownCamelKeys.forEach((camelKey) => {
+                const snakeKey = camelKey.replace(/([A-Z])/g, '_$1').toLowerCase()
+                if (newData[camelKey] === undefined && newData[snakeKey] !== undefined) {
+                  newData[camelKey] = newData[snakeKey]
+                }
+              })
+              Object.keys(newData).forEach((key) => {
+                this.$set(this.detailData, key, newData[key])
+              })
+            }
+          } catch (e) {
+            console.error('刷新任务详情失败:', e)
+          }
           this.fetchRecordList()
           this.fetchAttachmentList()
         }
+        // Close the detail dialog after save (pass/complete/work-hour).
+        // In the fail→bug flow, skipClose keeps the dialog open so TaskEditDialog
+        // can use the parent context; on TaskEditDialog's subsequent @refresh it closes.
+        const skipClose = this.isOpeningFailTask
+        if (!skipClose) {
+          this.$emit('update:visible', false)
+        }
         // Emit refresh so parent (e.g. dashboard) can reload too
         this.$emit('refresh')
       },