|
|
@@ -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')
|
|
|
},
|