|
@@ -241,19 +241,44 @@
|
|
|
</ContentWrap>
|
|
|
|
|
|
<!-- Timeline 时间线 Dialog - 已修改为 el-steps -->
|
|
|
- <el-dialog v-model="timelineDialogVisible" :title="`任务进度 - ${currentTaskRow ? currentTaskRow.wellName : ''}`" :width="dialogWidth">
|
|
|
- <div v-if="stepsData.length > 0">
|
|
|
- <el-steps direction="horizontal" :active="currentStepIndex" finish-status="success">
|
|
|
- <el-step
|
|
|
- v-for="(step, index) in stepsData"
|
|
|
- :key="index"
|
|
|
- :title="step.title"
|
|
|
- :description="step.description"
|
|
|
- :status="step.status"
|
|
|
- />
|
|
|
- </el-steps>
|
|
|
+ <el-dialog v-model="timelineDialogVisible"
|
|
|
+ :title="`任务进度 - ${currentTaskRow ? currentTaskRow.wellName : ''}`"
|
|
|
+ :width="dialogWidth">
|
|
|
+ <div class="progress-container">
|
|
|
+ <!-- 计划进度 -->
|
|
|
+ <div class="progress-section">
|
|
|
+ <h3 class="progress-title">计划进度</h3>
|
|
|
+ <div v-if="stepsData.length > 0">
|
|
|
+ <el-steps direction="horizontal" :active="currentStepIndex" finish-status="success">
|
|
|
+ <el-step
|
|
|
+ v-for="(step, index) in stepsData"
|
|
|
+ :key="index"
|
|
|
+ :title="step.title"
|
|
|
+ :description="step.description"
|
|
|
+ :status="step.status"
|
|
|
+ />
|
|
|
+ </el-steps>
|
|
|
+ </div>
|
|
|
+ <el-empty v-else description="暂无计划进度数据" :image-size="80" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 实际进度 -->
|
|
|
+ <div class="progress-section">
|
|
|
+ <h3 class="progress-title">实际进度</h3>
|
|
|
+ <div v-if="actualStepsData.length > 0">
|
|
|
+ <el-steps direction="horizontal" :active="actualStepsData.length - 1" finish-status="success">
|
|
|
+ <el-step
|
|
|
+ v-for="(step, index) in actualStepsData"
|
|
|
+ :key="index"
|
|
|
+ :title="step.title"
|
|
|
+ :description="step.description"
|
|
|
+ :status="step.status"
|
|
|
+ />
|
|
|
+ </el-steps>
|
|
|
+ </div>
|
|
|
+ <el-empty v-else description="暂无实际进度数据" :image-size="80" />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <el-empty v-else description="暂无进度数据" :image-size="100" />
|
|
|
<template #footer>
|
|
|
<span class="dialog-footer">
|
|
|
<el-button @click="timelineDialogVisible = false">关闭</el-button>
|
|
@@ -275,6 +300,7 @@ import * as UserApi from "@/api/system/user"; // 引入用户API
|
|
|
import * as DeptApi from "@/api/system/dept"; // 引入部门API
|
|
|
import { handleTree } from "@/utils/tree"; // 引入树形处理工具
|
|
|
import { IotProjectTaskScheduleApi } from '@/api/pms/iotprojecttaskschedule'
|
|
|
+import { IotRhDailyReportApi } from '@/api/pms/iotrhdailyreport'
|
|
|
import dayjs from 'dayjs' // 引入 dayjs 用于时间格式化
|
|
|
import { ref, reactive, onMounted, computed, nextTick, watch } from 'vue'
|
|
|
|
|
@@ -298,6 +324,7 @@ const timelineDialogVisible = ref(false) // 控制时间线弹窗显示
|
|
|
const currentTaskRow = ref<any>(null) // 当前选中的任务行数据
|
|
|
|
|
|
const stepsData = ref<Array<{title: string, description?: string, status?: string}>>([]) // 步骤数据
|
|
|
+const actualStepsData = ref<Array<{title: string, description?: string, status?: string}>>([]) // 实际步骤数据
|
|
|
const currentStepIndex = ref(0) // 当前步骤索引
|
|
|
|
|
|
// 表格引用
|
|
@@ -570,13 +597,16 @@ const getDeptNames = (deptIds) => {
|
|
|
|
|
|
// 添加计算属性计算对话框宽度
|
|
|
const dialogWidth = computed(() => {
|
|
|
- if (stepsData.value.length === 0) return '700px';
|
|
|
+ // if (stepsData.value.length === 0) return '700px';
|
|
|
|
|
|
// 根据步骤数量计算宽度,每个步骤大约需要 200px
|
|
|
- const baseWidth = stepsData.value.length * 200;
|
|
|
+ const baseWidth = Math.max(
|
|
|
+ stepsData.value.length * 200,
|
|
|
+ actualStepsData.value.length * 200
|
|
|
+ );
|
|
|
|
|
|
// 限制最小和最大宽度
|
|
|
- const minWidth = 400;
|
|
|
+ const minWidth = 900;
|
|
|
const maxWidth = window.innerWidth * 0.9; // 最大为视口宽度的90%
|
|
|
|
|
|
// 应用限制
|
|
@@ -693,6 +723,7 @@ const openTimelineDialog = async (row: any) => {
|
|
|
currentTaskRow.value = row
|
|
|
timelineDialogVisible.value = true
|
|
|
stepsData.value = [] // 清空旧数据
|
|
|
+ actualStepsData.value = [] // 清空实际进度数据
|
|
|
currentStepIndex.value = -1 // 初始化为-1,不选中任何步骤
|
|
|
|
|
|
try {
|
|
@@ -701,73 +732,57 @@ const openTimelineDialog = async (row: any) => {
|
|
|
await getTaskScheduleDictOptions()
|
|
|
}
|
|
|
|
|
|
- // 获取时间线数据
|
|
|
- const params = { taskId: row.id } // 假设根据 taskId 获取
|
|
|
+ // 获取计划进度数据
|
|
|
+ const params = { taskId: row.id }
|
|
|
const response = await IotProjectTaskScheduleApi.getIotProjectTaskSchedules(params)
|
|
|
|
|
|
+ // 获取实际进度数据
|
|
|
+ const actualProgress = await IotRhDailyReportApi.taskActualProgress(params)
|
|
|
+
|
|
|
+ // 处理计划进度数据
|
|
|
if (response && response.length > 0) {
|
|
|
- // 处理数据:转换时间戳、匹配字典label
|
|
|
const sortedSchedules = response.sort((a, b) => a.status - b.status);
|
|
|
-
|
|
|
- // 生成步骤数据
|
|
|
stepsData.value = sortedSchedules.map((item: any) => {
|
|
|
- // 格式化时间戳 (假设 startTime 是毫秒时间戳)
|
|
|
const formattedTimestamp = item.startTime ? dayjs(item.startTime).format('YYYY-MM-DD HH:mm') : '时间未设置'
|
|
|
-
|
|
|
- // 查找 status 对应的字典 label
|
|
|
const dictItem = taskScheduleDictOptions.value.find(dict => dict.value === item.status)
|
|
|
const statusLabel = dictItem ? dictItem.label : `未知状态 (${item.status})`
|
|
|
|
|
|
return {
|
|
|
title: `${formattedTimestamp} ${statusLabel}`,
|
|
|
- description: '', // 初始为空
|
|
|
- status: undefined // 初始状态为任何特殊状态
|
|
|
+ description: '',
|
|
|
+ status: undefined
|
|
|
}
|
|
|
})
|
|
|
+ } else {
|
|
|
+ stepsData.value = []
|
|
|
+ }
|
|
|
|
|
|
- // 只有当任务有明确状态时才计算当前步骤
|
|
|
- if (row.status !== null && row.status !== undefined && row.status !== '') {
|
|
|
- const currentStatus = row.status;
|
|
|
- let activeIndex = -1;
|
|
|
-
|
|
|
- // 找到第一个状态大于当前任务状态的计划,当前步骤就是前一个
|
|
|
- for (let i = 0; i < sortedSchedules.length; i++) {
|
|
|
- if (currentStatus < sortedSchedules[i].status) {
|
|
|
- activeIndex = i - 0.5; // 中间状态
|
|
|
- break;
|
|
|
- } else if (currentStatus === sortedSchedules[i].status) {
|
|
|
- activeIndex = i; // 正好在这个节点上
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 如果当前状态大于所有计划进度状态,则显示最后一步已完成
|
|
|
- if (activeIndex === -1 && sortedSchedules.length > 0) {
|
|
|
- if (currentStatus > sortedSchedules[sortedSchedules.length - 1].status) {
|
|
|
- activeIndex = sortedSchedules.length;
|
|
|
- } else {
|
|
|
- activeIndex = sortedSchedules.length - 1;
|
|
|
- }
|
|
|
- }
|
|
|
+ // 处理实际进度数据
|
|
|
+ if (actualProgress && actualProgress.length > 0) {
|
|
|
+ const sortedActualProgress = actualProgress.sort((a, b) =>
|
|
|
+ (a.constructionStartDate || 0) - (b.constructionStartDate || 0)
|
|
|
+ );
|
|
|
|
|
|
- currentStepIndex.value = Math.max(0, activeIndex);
|
|
|
+ actualStepsData.value = sortedActualProgress.map((item: any) => {
|
|
|
+ const formattedTimestamp = item.constructionStartDate ?
|
|
|
+ dayjs(item.constructionStartDate).format('YYYY-MM-DD HH:mm') : '时间未设置'
|
|
|
+ const dictItem = taskScheduleDictOptions.value.find(dict => dict.value === item.constructionStatus)
|
|
|
+ const statusLabel = dictItem ? dictItem.label : `未知状态 (${item.constructionStatus})`
|
|
|
|
|
|
- // 更新步骤数据,设置当前步骤的描述和状态
|
|
|
- if (currentStepIndex.value >= 0 && currentStepIndex.value < stepsData.value.length) {
|
|
|
- stepsData.value = stepsData.value.map((step, index) => ({
|
|
|
- ...step,
|
|
|
- description: index === currentStepIndex.value ? '当前进度' : '',
|
|
|
- status: index === currentStepIndex.value ? 'process' : undefined
|
|
|
- }))
|
|
|
+ return {
|
|
|
+ title: `${formattedTimestamp} ${statusLabel}`,
|
|
|
+ description: '',
|
|
|
+ status: undefined
|
|
|
}
|
|
|
- }
|
|
|
+ })
|
|
|
} else {
|
|
|
- stepsData.value = []
|
|
|
+ actualStepsData.value = []
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('获取任务进度时间线失败:', error)
|
|
|
message.error('获取任务进度失败')
|
|
|
stepsData.value = []
|
|
|
+ actualStepsData.value = []
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -978,4 +993,54 @@ watch(list, () => {
|
|
|
text-overflow: clip !important;
|
|
|
}
|
|
|
|
|
|
+/* 进度展示容器样式 */
|
|
|
+.progress-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-section {
|
|
|
+ border: 1px solid #e6e6e6;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 16px;
|
|
|
+ background-color: #fafafa;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-section:first-child {
|
|
|
+ border-color: #409eff;
|
|
|
+ background-color: #f0f7ff;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-section:last-child {
|
|
|
+ border-color: #67c23a;
|
|
|
+ background-color: #f0f9eb;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-title {
|
|
|
+ margin: 0 0 16px 0;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-section:first-child .progress-title {
|
|
|
+ color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-section:last-child .progress-title {
|
|
|
+ color: #67c23a;
|
|
|
+}
|
|
|
+
|
|
|
+/* 调整空状态显示 */
|
|
|
+:deep(.progress-section .el-empty) {
|
|
|
+ padding: 20px 0;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.progress-section .el-empty__description) {
|
|
|
+ margin-top: 8px;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
</style>
|