|
|
@@ -18,10 +18,7 @@
|
|
|
collapse-tags
|
|
|
multiple
|
|
|
placeholder="请选择">
|
|
|
- <el-option label="需求评审" value="10" />
|
|
|
- <el-option label="功能开发" value="20" />
|
|
|
- <el-option label="功能测试" value="30" />
|
|
|
- <el-option label="系统发版" value="40" />
|
|
|
+ <el-option v-for="dict in taskTypeOptions" :key="dict.key" :label="dict.value" :value="dict.key" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="任务状态">
|
|
|
@@ -31,20 +28,25 @@
|
|
|
collapse-tags
|
|
|
multiple
|
|
|
placeholder="请选择">
|
|
|
- <el-option label="待处理" value="10" />
|
|
|
- <el-option label="处理中" value="20" />
|
|
|
- <el-option label="暂停" value="25" />
|
|
|
- <el-option label="已完成" value="30" />
|
|
|
- <el-option label="阻塞" value="70" />
|
|
|
- <el-option label="作废" value="90" />
|
|
|
+ <el-option v-for="dict in taskStatusOptions" :key="dict.key" :label="dict.value" :value="dict.key" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="负责人">
|
|
|
- <el-input
|
|
|
+ <el-select
|
|
|
v-model="queryForm.opsUserName"
|
|
|
- class="query-input query-input--compact"
|
|
|
+ class="query-input query-input--owner"
|
|
|
clearable
|
|
|
- placeholder="请输入" />
|
|
|
+ collapse-tags
|
|
|
+ filterable
|
|
|
+ :loading="opsUsersLoading"
|
|
|
+ multiple
|
|
|
+ placeholder="请选择"
|
|
|
+ remote
|
|
|
+ :remote-method="remoteFetchOpsUsers"
|
|
|
+ reserve-keyword
|
|
|
+ @visible-change="handleOpsUserVisibleChange">
|
|
|
+ <el-option v-for="u in opsUserOptions" :key="u.value" :label="u.label" :value="u.label" />
|
|
|
+ </el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="计划结束">
|
|
|
<el-date-picker
|
|
|
@@ -161,7 +163,7 @@
|
|
|
<span class="project-line-tag">{{ getProductLineLabel(project.productLine) }}</span>
|
|
|
</div>
|
|
|
<span :class="['project-status-tag', 'project-status-tag--' + project.status]">
|
|
|
- {{ getStatusLabel(project.status) }}
|
|
|
+ {{ selectDictLabel(projectStatusOptions, project.status) }}
|
|
|
</span>
|
|
|
</div>
|
|
|
<div class="project-card-title" :title="project.name">{{ project.name }}</div>
|
|
|
@@ -200,8 +202,7 @@
|
|
|
height="100%"
|
|
|
stripe
|
|
|
style="width: 100%"
|
|
|
- @row-click="handleRowClick"
|
|
|
- @sort-change="handleSortChange">
|
|
|
+ @row-click="handleRowClick">
|
|
|
<el-table-column align="center" type="index" width="50" />
|
|
|
<el-table-column v-if="isColumnVisible('taskNo')" label="任务编号" show-overflow-tooltip width="140">
|
|
|
<template slot-scope="{ row }">
|
|
|
@@ -212,9 +213,8 @@
|
|
|
v-if="isColumnVisible('taskTitle')"
|
|
|
label="任务标题"
|
|
|
min-width="200"
|
|
|
- prop="taskTitle"
|
|
|
- show-overflow-tooltip
|
|
|
- sortable="custom">
|
|
|
+ :render-header="renderSortableHeader('任务标题', 'taskTitle')"
|
|
|
+ show-overflow-tooltip>
|
|
|
<template slot-scope="{ row }">
|
|
|
<span class="task-title-text">{{ row.taskTitle || '-' }}</span>
|
|
|
</template>
|
|
|
@@ -222,9 +222,8 @@
|
|
|
<el-table-column
|
|
|
v-if="isColumnVisible('functionName')"
|
|
|
label="功能模块"
|
|
|
- prop="functionName"
|
|
|
+ :render-header="renderSortableHeader('功能模块', 'functionName')"
|
|
|
show-overflow-tooltip
|
|
|
- sortable="custom"
|
|
|
width="160">
|
|
|
<template slot-scope="{ row }">
|
|
|
{{ row.functionName || '-' }}
|
|
|
@@ -233,45 +232,41 @@
|
|
|
<el-table-column
|
|
|
v-if="isColumnVisible('taskType')"
|
|
|
label="任务类型"
|
|
|
- prop="taskType"
|
|
|
- sortable="custom"
|
|
|
+ :render-header="renderSortableHeader('任务类型', 'taskType')"
|
|
|
width="116">
|
|
|
<template slot-scope="{ row }">
|
|
|
<el-tag size="small" type="info">
|
|
|
- {{ getTaskTypeLabel(row.taskType) }}
|
|
|
+ {{ selectDictLabel(taskTypeOptions, row.taskType) }}
|
|
|
</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column
|
|
|
v-if="isColumnVisible('taskStatus')"
|
|
|
label="任务状态"
|
|
|
- prop="taskStatus"
|
|
|
- sortable="custom"
|
|
|
+ :render-header="renderSortableHeader('任务状态', 'taskStatus')"
|
|
|
width="116">
|
|
|
<template slot-scope="{ row }">
|
|
|
- <el-tag size="small" :type="getTaskStatusType(row.taskStatus)">
|
|
|
- {{ getTaskStatusLabel(row.taskStatus) }}
|
|
|
+ <el-tag size="small" :type="getTaskStatusTagType(row.taskStatus)">
|
|
|
+ {{ selectDictLabel(taskStatusOptions, row.taskStatus) }}
|
|
|
</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column
|
|
|
v-if="isColumnVisible('priority')"
|
|
|
label="优先级"
|
|
|
- prop="priority"
|
|
|
- sortable="custom"
|
|
|
+ :render-header="renderSortableHeader('优先级', 'priority')"
|
|
|
width="104">
|
|
|
<template slot-scope="{ row }">
|
|
|
- <el-tag size="small" :type="getPriorityType(row.priority)">
|
|
|
- {{ getPriorityLabel(row.priority) }}
|
|
|
+ <el-tag size="small" :type="getPriorityTagType(row.priority)">
|
|
|
+ {{ selectDictLabel(priorityOptions, row.priority) }}
|
|
|
</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column
|
|
|
v-if="isColumnVisible('opsUserName')"
|
|
|
label="负责人"
|
|
|
- prop="opsUserName"
|
|
|
+ :render-header="renderSortableHeader('负责人', 'opsUserName')"
|
|
|
show-overflow-tooltip
|
|
|
- sortable="custom"
|
|
|
width="120">
|
|
|
<template slot-scope="{ row }">
|
|
|
{{ row.opsUserName || '-' }}
|
|
|
@@ -280,28 +275,25 @@
|
|
|
<el-table-column
|
|
|
v-if="isColumnVisible('planStartTime')"
|
|
|
label="计划开始时间"
|
|
|
- prop="planStartTime"
|
|
|
- sortable="custom"
|
|
|
+ :render-header="renderSortableHeader('计划开始时间', 'planStartTime')"
|
|
|
width="170">
|
|
|
<template slot-scope="{ row }">
|
|
|
- {{ formatTime(row.planStartTime) }}
|
|
|
+ {{ row.planStartTime ? parseTime(row.planStartTime, '{y}-{m}-{d}') : '-' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column
|
|
|
v-if="isColumnVisible('planEndTime')"
|
|
|
label="计划结束时间"
|
|
|
- prop="planEndTime"
|
|
|
- sortable="custom"
|
|
|
+ :render-header="renderSortableHeader('计划结束时间', 'planEndTime')"
|
|
|
width="170">
|
|
|
<template slot-scope="{ row }">
|
|
|
- {{ formatTime(row.planEndTime) }}
|
|
|
+ {{ row.planEndTime ? parseTime(row.planEndTime, '{y}-{m}-{d}') : '-' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column
|
|
|
v-if="isColumnVisible('completeTime')"
|
|
|
label="完成时间"
|
|
|
- prop="completeTime"
|
|
|
- sortable="custom"
|
|
|
+ :render-header="renderSortableHeader('完成时间', 'completeTime')"
|
|
|
width="170">
|
|
|
<template slot-scope="{ row }">
|
|
|
{{ formatTime(row.completeTime) }}
|
|
|
@@ -329,15 +321,14 @@
|
|
|
</el-table-column>
|
|
|
<el-table-column v-if="isColumnVisible('defectType')" label="缺陷类型" width="100">
|
|
|
<template slot-scope="{ row }">
|
|
|
- {{ getDefectTypeLabel(row.defectType) }}
|
|
|
+ {{ selectDictLabel(defectTypeOptions, row.defectType) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column
|
|
|
v-if="isColumnVisible('releaseVersion')"
|
|
|
label="发布版本"
|
|
|
- prop="releaseVersion"
|
|
|
+ :render-header="renderSortableHeader('发布版本', 'releaseVersion')"
|
|
|
show-overflow-tooltip
|
|
|
- sortable="custom"
|
|
|
width="160">
|
|
|
<template slot-scope="{ row }">
|
|
|
<div class="release-version-cell">
|
|
|
@@ -363,8 +354,7 @@
|
|
|
<el-table-column
|
|
|
v-if="isColumnVisible('createdTime')"
|
|
|
label="创建时间"
|
|
|
- prop="createdTime"
|
|
|
- sortable="custom"
|
|
|
+ :render-header="renderSortableHeader('创建时间', 'createdTime')"
|
|
|
width="170">
|
|
|
<template slot-scope="{ row }">
|
|
|
{{ formatTime(row.createdTime) }}
|
|
|
@@ -490,6 +480,12 @@
|
|
|
:visible.sync="cancelDialogVisible"
|
|
|
@refresh="fetchData" />
|
|
|
|
|
|
+ <work-hour-dialog
|
|
|
+ v-if="workHourDialogVisible"
|
|
|
+ :task-id="currentTaskId"
|
|
|
+ :visible.sync="workHourDialogVisible"
|
|
|
+ @refresh="fetchData" />
|
|
|
+
|
|
|
<release-complete-dialog
|
|
|
v-if="releaseCompleteDialogVisible"
|
|
|
:project-id="currentRow ? currentRow.projectId : null"
|
|
|
@@ -514,6 +510,9 @@
|
|
|
<script>
|
|
|
import opsEventTaskApi from '@/api/devops/opsEventTask'
|
|
|
import deliveryProjectApi from '@/api/devops/deliveryProject'
|
|
|
+ import userApi from '@/api/system/user'
|
|
|
+ import dictApi from '@/api/system/dict'
|
|
|
+ import { DEVOPS_DEV_DEPT_ID } from '@/config/devops.config'
|
|
|
import TaskEditDialog from './components/TaskEditDialog'
|
|
|
import TaskDetailDialog from './components/TaskDetailDialog'
|
|
|
import CompleteDialog from './components/CompleteDialog'
|
|
|
@@ -521,8 +520,10 @@
|
|
|
import PauseDialog from './components/PauseDialog'
|
|
|
import BlockDialog from './components/BlockDialog'
|
|
|
import CancelDialog from './components/CancelDialog'
|
|
|
+ import WorkHourDialog from './components/WorkHourDialog'
|
|
|
import ProjectInfoDialog from '../components/ProjectInfoDialog'
|
|
|
import { parseTime } from '@/utils'
|
|
|
+ import { taskStatusTagTypes, priorityTagTypes, projectStatusTagTypes, getTagType } from '@/config/devopsTagTypes'
|
|
|
|
|
|
const COLUMN_STORAGE_KEY = 'opms-devops-software-visible-columns'
|
|
|
const TABLE_COLUMN_OPTIONS = [
|
|
|
@@ -555,6 +556,7 @@
|
|
|
PauseDialog,
|
|
|
BlockDialog,
|
|
|
CancelDialog,
|
|
|
+ WorkHourDialog,
|
|
|
ProjectInfoDialog,
|
|
|
},
|
|
|
data() {
|
|
|
@@ -566,14 +568,16 @@
|
|
|
taskTitle: '',
|
|
|
taskType: [],
|
|
|
taskStatus: [],
|
|
|
- opsUserName: '',
|
|
|
+ opsUserName: [],
|
|
|
planEndDateRange: [],
|
|
|
createdTimeRange: [],
|
|
|
completeTimeRange: [],
|
|
|
},
|
|
|
- // 排序
|
|
|
- sortField: '',
|
|
|
- sortOrder: '',
|
|
|
+ // 多列排序
|
|
|
+ sortFields: [],
|
|
|
+ // 负责人下拉
|
|
|
+ opsUserOptions: [],
|
|
|
+ opsUsersLoading: false,
|
|
|
columnOptions: TABLE_COLUMN_OPTIONS,
|
|
|
visibleColumnKeys: TABLE_COLUMN_OPTIONS.map((column) => column.key),
|
|
|
lastVisibleColumnKeys: TABLE_COLUMN_OPTIONS.map((column) => column.key),
|
|
|
@@ -588,6 +592,11 @@
|
|
|
selectedProject: '',
|
|
|
projects: [{ id: '', name: '全部' }],
|
|
|
productLineOptions: [],
|
|
|
+ taskTypeOptions: [],
|
|
|
+ taskStatusOptions: [],
|
|
|
+ priorityOptions: [],
|
|
|
+ defectTypeOptions: [],
|
|
|
+ projectStatusOptions: [],
|
|
|
// 弹窗控制
|
|
|
editDialogVisible: false,
|
|
|
detailDialogVisible: false,
|
|
|
@@ -597,6 +606,7 @@
|
|
|
pauseDialogVisible: false,
|
|
|
blockDialogVisible: false,
|
|
|
cancelDialogVisible: false,
|
|
|
+ workHourDialogVisible: false,
|
|
|
currentRow: null,
|
|
|
currentTaskId: null,
|
|
|
editData: null,
|
|
|
@@ -655,19 +665,29 @@
|
|
|
mounted() {
|
|
|
this.initVisibleColumns()
|
|
|
this.getOptions()
|
|
|
+ this.remoteFetchOpsUsers('')
|
|
|
this.fetchProjects()
|
|
|
- // 设置默认负责人为当前登录人
|
|
|
- const currentUserName = this.$store.getters['user/nickName'] || this.$store.getters['user/username']
|
|
|
- if (currentUserName) {
|
|
|
- this.queryForm.opsUserName = currentUserName
|
|
|
- }
|
|
|
this.fetchData()
|
|
|
},
|
|
|
methods: {
|
|
|
getOptions() {
|
|
|
- Promise.all([this.getDicts('sys_product_line')])
|
|
|
- .then(([productLine]) => {
|
|
|
- this.productLineOptions = productLine.data.values || []
|
|
|
+ dictApi
|
|
|
+ .getDictDataByTypes([
|
|
|
+ 'sys_product_line',
|
|
|
+ 'ops_task_type',
|
|
|
+ 'ops_task_status',
|
|
|
+ 'ops_priority',
|
|
|
+ 'ops_defect_type',
|
|
|
+ 'delivery_project_status',
|
|
|
+ ])
|
|
|
+ .then((res) => {
|
|
|
+ const dicts = res.data || {}
|
|
|
+ this.productLineOptions = (dicts.sys_product_line && dicts.sys_product_line.values) || []
|
|
|
+ this.taskTypeOptions = (dicts.ops_task_type && dicts.ops_task_type.values) || []
|
|
|
+ this.taskStatusOptions = (dicts.ops_task_status && dicts.ops_task_status.values) || []
|
|
|
+ this.priorityOptions = (dicts.ops_priority && dicts.ops_priority.values) || []
|
|
|
+ this.defectTypeOptions = (dicts.ops_defect_type && dicts.ops_defect_type.values) || []
|
|
|
+ this.projectStatusOptions = (dicts.delivery_project_status && dicts.delivery_project_status.values) || []
|
|
|
})
|
|
|
.catch((err) => console.log(err))
|
|
|
},
|
|
|
@@ -707,9 +727,9 @@
|
|
|
this.lastVisibleColumnKeys = [...validColumnKeys]
|
|
|
this.persistVisibleColumns()
|
|
|
|
|
|
- if (this.sortField && !validColumnKeys.includes(this.sortField)) {
|
|
|
- this.sortField = ''
|
|
|
- this.sortOrder = ''
|
|
|
+ const removed = this.sortFields.filter((s) => !validColumnKeys.includes(s.field))
|
|
|
+ if (removed.length) {
|
|
|
+ this.sortFields = this.sortFields.filter((s) => validColumnKeys.includes(s.field))
|
|
|
this.$nextTick(() => {
|
|
|
this.$refs.taskTable && this.$refs.taskTable.clearSort()
|
|
|
})
|
|
|
@@ -728,7 +748,13 @@
|
|
|
// 获取项目列表
|
|
|
async fetchProjects() {
|
|
|
try {
|
|
|
- const params = { pageNum: 1, pageSize: 999 }
|
|
|
+ const params = {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 999,
|
|
|
+ productLine: '10,20,30',
|
|
|
+ sortField: 'contract_no',
|
|
|
+ sortOrder: 'desc',
|
|
|
+ }
|
|
|
if (this.projectStatusFilter) {
|
|
|
const statusMap = {
|
|
|
undelivered: '10,20,30,40',
|
|
|
@@ -759,6 +785,8 @@
|
|
|
deliveryOwner: item.deliveryUserName || item.delivery_user_name || '',
|
|
|
status: String(item.projectStatus || item.project_status || ''),
|
|
|
}))
|
|
|
+ // 按合同编号倒序排列(兜底)
|
|
|
+ list.sort((a, b) => (b.contractNo || '').localeCompare(a.contractNo || ''))
|
|
|
this.projects = [{ id: '', name: '全部' }, ...list]
|
|
|
} catch (error) {
|
|
|
console.error('获取项目列表失败', error)
|
|
|
@@ -786,7 +814,7 @@
|
|
|
completeTimeStart: this.queryForm.completeTimeRange?.[0] || '',
|
|
|
completeTimeEnd: this.queryForm.completeTimeRange?.[1] || '',
|
|
|
// 排序
|
|
|
- sortFields: this.sortField && this.sortOrder ? [{ field: this.sortField, order: this.sortOrder }] : [],
|
|
|
+ sortFields: this.sortFields,
|
|
|
}
|
|
|
const res = await opsEventTaskApi.getList(params)
|
|
|
this.tableData = res.data?.list || []
|
|
|
@@ -798,6 +826,30 @@
|
|
|
this.loading = false
|
|
|
}
|
|
|
},
|
|
|
+ // 远程搜索负责人
|
|
|
+ async remoteFetchOpsUsers(search) {
|
|
|
+ this.opsUsersLoading = true
|
|
|
+ try {
|
|
|
+ const payload = { deptId: DEVOPS_DEV_DEPT_ID, pageNum: 1, pageSize: 999 }
|
|
|
+ if (search) payload.keyWords = search
|
|
|
+ const res = await userApi.getList(payload)
|
|
|
+ const list = res.data?.list || []
|
|
|
+ this.opsUserOptions = list.map((u) => ({
|
|
|
+ value: u.userId ?? u.user_id ?? u.id ?? null,
|
|
|
+ label: u.nickName ?? u.nick_name ?? u.name ?? '',
|
|
|
+ }))
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取负责人列表失败:', error)
|
|
|
+ this.opsUserOptions = []
|
|
|
+ } finally {
|
|
|
+ this.opsUsersLoading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handleOpsUserVisibleChange(visible) {
|
|
|
+ if (visible && !this.opsUsersLoading && !this.opsUserOptions.length) {
|
|
|
+ this.remoteFetchOpsUsers('')
|
|
|
+ }
|
|
|
+ },
|
|
|
// 查询
|
|
|
handleSearch() {
|
|
|
this.queryForm.pageNum = 1
|
|
|
@@ -812,11 +864,12 @@
|
|
|
taskTitle: '',
|
|
|
taskType: [],
|
|
|
taskStatus: [],
|
|
|
- opsUserName: '',
|
|
|
+ opsUserName: [],
|
|
|
planEndDateRange: [],
|
|
|
createdTimeRange: [],
|
|
|
completeTimeRange: [],
|
|
|
}
|
|
|
+ this.sortFields = []
|
|
|
this.selectedProject = ''
|
|
|
this.fetchData()
|
|
|
},
|
|
|
@@ -830,12 +883,46 @@
|
|
|
this.queryForm.pageNum = 1
|
|
|
this.fetchData()
|
|
|
},
|
|
|
- // 排序
|
|
|
- handleSortChange({ prop, order }) {
|
|
|
- this.sortField = prop || ''
|
|
|
- this.sortOrder = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : ''
|
|
|
+ // 多列排序:点击表头切换 无→升序→降序→无
|
|
|
+ toggleSort(field) {
|
|
|
+ const idx = this.sortFields.findIndex((s) => s.field === field)
|
|
|
+ if (idx === -1) {
|
|
|
+ if (this.sortFields.length >= 3) return
|
|
|
+ this.sortFields.push({ field, order: 'asc' })
|
|
|
+ } else if (this.sortFields[idx].order === 'asc') {
|
|
|
+ this.sortFields[idx].order = 'desc'
|
|
|
+ } else {
|
|
|
+ this.sortFields.splice(idx, 1)
|
|
|
+ }
|
|
|
this.fetchData()
|
|
|
},
|
|
|
+ getSortState(field) {
|
|
|
+ const sf = this.sortFields.find((s) => s.field === field)
|
|
|
+ return sf ? sf.order : ''
|
|
|
+ },
|
|
|
+ getSortPriority(field) {
|
|
|
+ const idx = this.sortFields.findIndex((s) => s.field === field)
|
|
|
+ return idx === -1 ? '' : String(idx + 1)
|
|
|
+ },
|
|
|
+ getColumnSortLabel(field) {
|
|
|
+ const option = this.columnOptions.find((c) => c.key === field)
|
|
|
+ return option ? option.label : field
|
|
|
+ },
|
|
|
+ renderSortableHeader(label, field) {
|
|
|
+ const vm = this
|
|
|
+ return function (h) {
|
|
|
+ const state = vm.getSortState(field)
|
|
|
+ const prio = vm.getSortPriority(field)
|
|
|
+ return h('div', { class: 'sortable-header', on: { click: () => vm.toggleSort(field) } }, [
|
|
|
+ h('span', { class: 'sortable-header-label' }, label),
|
|
|
+ h('span', { class: 'sort-arrows' }, [
|
|
|
+ h('i', { class: ['el-icon-caret-top', { 'sort-active': state === 'asc' }] }),
|
|
|
+ h('i', { class: ['el-icon-caret-bottom', { 'sort-active': state === 'desc' }] }),
|
|
|
+ ]),
|
|
|
+ prio ? h('span', { class: 'sort-priority' }, prio) : null,
|
|
|
+ ])
|
|
|
+ }
|
|
|
+ },
|
|
|
// 侧边栏
|
|
|
toggleSidebar() {
|
|
|
this.sidebarCollapsed = !this.sidebarCollapsed
|
|
|
@@ -869,37 +956,32 @@
|
|
|
getRowActions(row) {
|
|
|
const status = String(row.taskStatus)
|
|
|
const taskType = String(row.taskType)
|
|
|
- const editAction = { key: 'edit', label: '编辑' }
|
|
|
const actionMap = {
|
|
|
- 10: [editAction, { key: 'schedule', label: '排期' }],
|
|
|
+ 10: [{ key: 'schedule', label: '排期' }],
|
|
|
20: [
|
|
|
- editAction,
|
|
|
...(taskType === '30'
|
|
|
? [
|
|
|
{ key: 'pass', label: '通过' },
|
|
|
{ key: 'fail', label: '不通过', tone: 'warning' },
|
|
|
]
|
|
|
: [{ key: 'complete', label: '完成' }]),
|
|
|
+ { key: 'workHour', label: '工时' },
|
|
|
{ key: 'pause', label: '暂停' },
|
|
|
{ key: 'block', label: '阻塞', tone: 'warning' },
|
|
|
{ key: 'cancel', label: '作废', tone: 'danger' },
|
|
|
],
|
|
|
25: [
|
|
|
- editAction,
|
|
|
{ key: 'start', label: '开始' },
|
|
|
{ key: 'block', label: '阻塞', tone: 'warning' },
|
|
|
{ key: 'cancel', label: '作废', tone: 'danger' },
|
|
|
],
|
|
|
70: [
|
|
|
- editAction,
|
|
|
{ key: 'start', label: '开始' },
|
|
|
{ key: 'pause', label: '暂停' },
|
|
|
{ key: 'cancel', label: '作废', tone: 'danger' },
|
|
|
],
|
|
|
- 30: [editAction],
|
|
|
- 90: [editAction],
|
|
|
}
|
|
|
- return actionMap[status] || [editAction]
|
|
|
+ return actionMap[status] || []
|
|
|
},
|
|
|
getPrimaryActions(row) {
|
|
|
return this.getRowActions(row).slice(0, 3)
|
|
|
@@ -977,6 +1059,7 @@
|
|
|
edit: this.handleEdit,
|
|
|
schedule: this.handleSchedule,
|
|
|
complete: this.handleComplete,
|
|
|
+ workHour: this.handleWorkHour,
|
|
|
pass: () => {
|
|
|
this.handleComplete(row, 'pass')
|
|
|
}, // 通过按钮传参数
|
|
|
@@ -1000,10 +1083,10 @@
|
|
|
this.failTaskRow = null
|
|
|
}
|
|
|
},
|
|
|
- // 点击行显示详情
|
|
|
+ // 点击行显示详情(支持编辑)
|
|
|
handleRowClick(row) {
|
|
|
this.detailData = this.normalizeTaskDetail(row)
|
|
|
- this.detailDialogMode = 'view'
|
|
|
+ this.detailDialogMode = 'edit'
|
|
|
this.detailDialogVisible = true
|
|
|
},
|
|
|
// 查看发布版本关联任务详情
|
|
|
@@ -1089,6 +1172,11 @@
|
|
|
this.currentTaskId = row.id
|
|
|
this.pauseDialogVisible = true
|
|
|
},
|
|
|
+ handleWorkHour(row) {
|
|
|
+ this.currentRow = row
|
|
|
+ this.currentTaskId = row.id
|
|
|
+ this.workHourDialogVisible = true
|
|
|
+ },
|
|
|
handleBlock(row) {
|
|
|
this.currentRow = row
|
|
|
this.currentTaskId = row.id
|
|
|
@@ -1111,19 +1199,7 @@
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
- // 辅助方法
|
|
|
- getTaskTypeLabel(type) {
|
|
|
- const map = { 10: '需求评审', 20: '功能开发', 30: '功能测试', 40: '系统发版' }
|
|
|
- return map[type] || type
|
|
|
- },
|
|
|
- getTaskStatusLabel(status) {
|
|
|
- const map = { 10: '待处理', 20: '处理中', 25: '暂停', 30: '已完成', 70: '阻塞', 90: '作废' }
|
|
|
- return map[status] || status
|
|
|
- },
|
|
|
- getTaskStatusType(status) {
|
|
|
- const map = { 10: 'info', 20: 'primary', 25: 'warning', 30: 'success', 70: 'danger', 90: 'info' }
|
|
|
- return map[status] || 'info'
|
|
|
- },
|
|
|
+ // 辅助方法 — 标签映射已改用 getDicts + selectDictLabel,保留 badge class 映射(UI 呈现)
|
|
|
getTaskStatusBadgeClass(status) {
|
|
|
const map = {
|
|
|
10: 'data-badge--pending',
|
|
|
@@ -1135,14 +1211,6 @@
|
|
|
}
|
|
|
return map[status] || 'data-badge--neutral'
|
|
|
},
|
|
|
- getPriorityLabel(priority) {
|
|
|
- const map = { 10: '紧急', 20: '高', 30: '中', 40: '低' }
|
|
|
- return map[priority] || priority
|
|
|
- },
|
|
|
- getPriorityType(priority) {
|
|
|
- const map = { 10: 'danger', 20: 'warning', 30: 'primary', 40: 'info' }
|
|
|
- return map[priority] || 'info'
|
|
|
- },
|
|
|
getPriorityBadgeClass(priority) {
|
|
|
const map = {
|
|
|
10: 'data-badge--urgent',
|
|
|
@@ -1152,20 +1220,14 @@
|
|
|
}
|
|
|
return map[priority] || 'data-badge--neutral'
|
|
|
},
|
|
|
- getDefectTypeLabel(type) {
|
|
|
- const map = { 10: '前端', 20: '后端' }
|
|
|
- return map[type] || '-'
|
|
|
+ getTaskStatusTagType(status) {
|
|
|
+ return getTagType(taskStatusTagTypes, status, 'info')
|
|
|
},
|
|
|
- getStatusLabel(status) {
|
|
|
- const map = {
|
|
|
- 10: '待交付',
|
|
|
- 20: '交付中',
|
|
|
- 30: '暂停',
|
|
|
- 40: '交付完成',
|
|
|
- 50: '验收',
|
|
|
- 90: '作废',
|
|
|
- }
|
|
|
- return map[status] || status || '-'
|
|
|
+ getPriorityTagType(priority) {
|
|
|
+ return getTagType(priorityTagTypes, priority, 'info')
|
|
|
+ },
|
|
|
+ getProjectStatusTagType(status) {
|
|
|
+ return getTagType(projectStatusTagTypes, status, 'info')
|
|
|
},
|
|
|
formatTime(time) {
|
|
|
return time ? parseTime(time, '{y}-{m}-{d} {h}:{i}') : '-'
|
|
|
@@ -1300,6 +1362,46 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ ::v-deep .sortable-header {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ user-select: none;
|
|
|
+ white-space: nowrap;
|
|
|
+
|
|
|
+ .sort-arrows {
|
|
|
+ display: inline-flex;
|
|
|
+ flex-direction: column;
|
|
|
+ line-height: 1;
|
|
|
+
|
|
|
+ i {
|
|
|
+ font-size: 10px;
|
|
|
+ color: #c0c4cc;
|
|
|
+ transition: color 0.2s;
|
|
|
+
|
|
|
+ &.sort-active {
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-priority {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #409eff;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 10px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1;
|
|
|
+ margin-left: 2px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
.query-input {
|
|
|
::v-deep .el-input__inner,
|
|
|
::v-deep .el-range-input,
|
|
|
@@ -1316,6 +1418,10 @@
|
|
|
width: 112px;
|
|
|
}
|
|
|
|
|
|
+ .query-input--owner {
|
|
|
+ width: 180px;
|
|
|
+ }
|
|
|
+
|
|
|
.query-input--date-range {
|
|
|
width: 256px;
|
|
|
}
|