gaoluyang
2025-09-24 a6e09eeeb5bb9694a79276a71bc92b21778a65e1
过程追踪
已添加1个文件
498 ■■■■■ 文件已修改
src/views/collaborativeApproval/processTracking/index.vue 498 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/processTracking/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,498 @@
<template>
  <div class="process-tracking">
    <div class="header">
      <h2>过程追踪</h2>
      <el-button type="primary" @click="refreshData">刷新数据</el-button>
    </div>
    <!-- é¡¹ç›®çŠ¶æ€ç»Ÿè®¡ -->
    <div class="status-cards">
      <el-row :gutter="20">
        <el-col :span="6" v-for="(item, index) in statusStats" :key="index">
          <el-card class="status-card" :class="item.type">
            <div class="card-content">
              <div class="card-icon">
                <el-icon :size="24">
                  <component :is="item.icon" />
                </el-icon>
              </div>
              <div class="card-info">
                <div class="card-title">{{ item.label }}</div>
                <div class="card-count">{{ item.count }}</div>
              </div>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- é¡¹ç›®åˆ—表 -->
    <el-card class="project-list">
      <template #header>
        <div class="card-header">
          <span>项目列表</span>
          <el-button type="text" @click="toggleView">
            {{ viewMode === 'table' ? '切换到甘特图' : '切换到列表' }}
          </el-button>
        </div>
      </template>
      <!-- è¡¨æ ¼è§†å›¾ -->
      <div v-if="viewMode === 'table'">
        <el-table :data="projectList" style="width: 100%">
          <el-table-column prop="name" label="项目名称"  />
          <el-table-column prop="manager" label="负责人"/>
          <el-table-column label="状态" >
            <template #default="{ row }">
              <el-tag :type="getStatusType(row.status)">
                {{ getStatusText(row.status) }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column label="进度" width="150">
            <template #default="{ row }">
              <el-progress :percentage="row.progress" :status="getProgressStatus(row.status)" />
            </template>
          </el-table-column>
          <el-table-column prop="startDate" label="开始时间" width="120" />
          <el-table-column prop="endDate" label="结束时间" width="120" />
          <el-table-column label="操作" width="150">
            <template #default="{ row }">
              <el-button type="text" @click="updateStatus(row)">更新状态</el-button>
              <el-button type="text" @click="viewDetails(row)">详情</el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <!-- ç”˜ç‰¹å›¾è§†å›¾ -->
      <div v-else class="gantt-container">
        <div ref="ganttChart" style="width: 100%; height: 400px;"></div>
      </div>
    </el-card>
    <!-- çŠ¶æ€æ›´æ–°å¯¹è¯æ¡† -->
    <el-dialog v-model="statusDialogVisible" title="更新项目状态" width="400px">
      <el-form :model="statusForm" label-width="80px">
        <el-form-item label="项目名称">
          <el-input v-model="statusForm.name" disabled />
        </el-form-item>
        <el-form-item label="当前状态">
          <el-select v-model="statusForm.status" placeholder="请选择状态">
            <el-option label="未开始" value="not_started" />
            <el-option label="进行中" value="in_progress" />
            <el-option label="已完成" value="completed" />
            <el-option label="延期" value="delayed" />
          </el-select>
        </el-form-item>
        <el-form-item label="进度">
          <el-slider v-model="statusForm.progress" :max="100" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="statusDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="confirmStatusUpdate">确认</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { Clock, Loading, Check, Warning } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
// å“åº”式数据
const viewMode = ref('table')
const statusDialogVisible = ref(false)
const ganttChart = ref(null)
let chartInstance = null
// çŠ¶æ€ç»Ÿè®¡æ•°æ®
const statusStats = reactive([
  { label: '未开始', count: 3, type: 'not-started', icon: Clock },
  { label: '进行中', count: 5, type: 'in-progress', icon: Loading },
  { label: '已完成', count: 8, type: 'completed', icon: Check },
  { label: '延期', count: 2, type: 'delayed', icon: Warning }
])
// é¡¹ç›®åˆ—表数据
const projectList = reactive([
  {
    id: 1,
    name: '产品需求分析',
    manager: '张三',
    status: 'completed',
    progress: 100,
    startDate: '2024-01-01',
    endDate: '2024-01-15'
  },
  {
    id: 2,
    name: '系统架构设计',
    manager: '李四',
    status: 'in_progress',
    progress: 75,
    startDate: '2024-01-10',
    endDate: '2024-01-25'
  },
  {
    id: 3,
    name: '前端开发',
    manager: '王五',
    status: 'in_progress',
    progress: 60,
    startDate: '2024-01-20',
    endDate: '2024-02-10'
  },
  {
    id: 4,
    name: '后端开发',
    manager: '赵六',
    status: 'in_progress',
    progress: 45,
    startDate: '2024-01-25',
    endDate: '2024-02-15'
  },
  {
    id: 5,
    name: '数据库设计',
    manager: '孙七',
    status: 'delayed',
    progress: 30,
    startDate: '2024-01-15',
    endDate: '2024-01-30'
  },
  {
    id: 6,
    name: '测试用例编写',
    manager: '周八',
    status: 'not_started',
    progress: 0,
    startDate: '2024-02-01',
    endDate: '2024-02-20'
  }
])
// çŠ¶æ€æ›´æ–°è¡¨å•
const statusForm = reactive({
  id: null,
  name: '',
  status: '',
  progress: 0
})
// èŽ·å–çŠ¶æ€ç±»åž‹
const getStatusType = (status) => {
  const typeMap = {
    not_started: 'info',
    in_progress: 'warning',
    completed: 'success',
    delayed: 'danger'
  }
  return typeMap[status] || 'info'
}
// èŽ·å–çŠ¶æ€æ–‡æœ¬
const getStatusText = (status) => {
  const textMap = {
    not_started: '未开始',
    in_progress: '进行中',
    completed: '已完成',
    delayed: '延期'
  }
  return textMap[status] || '未知'
}
// èŽ·å–è¿›åº¦çŠ¶æ€
const getProgressStatus = (status) => {
  if (status === 'completed') return 'success'
  if (status === 'delayed') return 'exception'
  return null
}
// åˆ‡æ¢è§†å›¾æ¨¡å¼
const toggleView = () => {
  viewMode.value = viewMode.value === 'table' ? 'gantt' : 'table'
  if (viewMode.value === 'gantt') {
    nextTick(() => {
      initGanttChart()
    })
  }
}
// åˆå§‹åŒ–甘特图
const initGanttChart = () => {
  if (!ganttChart.value) return
  if (chartInstance) {
    chartInstance.dispose()
  }
  chartInstance = echarts.init(ganttChart.value)
  // å‡†å¤‡ç”˜ç‰¹å›¾æ•°æ®
  const data = projectList.map((project, index) => ({
    name: project.name,
    value: [
      index,
      new Date(project.startDate).getTime(),
      new Date(project.endDate).getTime(),
      project.progress
    ],
    itemStyle: {
      color: getGanttColor(project.status)
    }
  }))
  const option = {
    title: {
      text: '项目甘特图',
      left: 'center'
    },
    tooltip: {
      formatter: (params) => {
        const project = projectList[params.value[0]]
        return `
          <div>
            <strong>${project.name}</strong><br/>
            è´Ÿè´£äºº: ${project.manager}<br/>
            çŠ¶æ€: ${getStatusText(project.status)}<br/>
            è¿›åº¦: ${project.progress}%<br/>
            å¼€å§‹æ—¶é—´: ${project.startDate}<br/>
            ç»“束时间: ${project.endDate}
          </div>
        `
      }
    },
    grid: {
      left: '15%',
      right: '10%',
      top: '15%',
      bottom: '15%'
    },
    xAxis: {
      type: 'time',
      axisLabel: {
        formatter: (value) => {
          return echarts.format.formatTime('MM-dd', value)
        }
      }
    },
    yAxis: {
      type: 'category',
      data: projectList.map(p => p.name),
      inverse: true
    },
    series: [{
      type: 'custom',
      renderItem: (params, api) => {
        const categoryIndex = api.value(0)
        const start = api.coord([api.value(1), categoryIndex])
        const end = api.coord([api.value(2), categoryIndex])
        const height = api.size([0, 1])[1] * 0.6
        return {
          type: 'rect',
          shape: {
            x: start[0],
            y: start[1] - height / 2,
            width: end[0] - start[0],
            height: height
          },
          style: api.style()
        }
      },
      data: data
    }]
  }
  chartInstance.setOption(option)
}
// èŽ·å–ç”˜ç‰¹å›¾é¢œè‰²
const getGanttColor = (status) => {
  const colorMap = {
    not_started: '#909399',
    in_progress: '#E6A23C',
    completed: '#67C23A',
    delayed: '#F56C6C'
  }
  return colorMap[status] || '#909399'
}
// æ›´æ–°çŠ¶æ€
const updateStatus = (project) => {
  statusForm.id = project.id
  statusForm.name = project.name
  statusForm.status = project.status
  statusForm.progress = project.progress
  statusDialogVisible.value = true
}
// ç¡®è®¤çŠ¶æ€æ›´æ–°
const confirmStatusUpdate = () => {
  const project = projectList.find(p => p.id === statusForm.id)
  if (project) {
    project.status = statusForm.status
    project.progress = statusForm.progress
    // æ›´æ–°ç»Ÿè®¡æ•°æ®
    updateStatusStats()
    // å¦‚果是甘特图视图,重新渲染
    if (viewMode.value === 'gantt') {
      nextTick(() => {
        initGanttChart()
      })
    }
    ElMessage.success('状态更新成功')
  }
  statusDialogVisible.value = false
}
// æ›´æ–°çŠ¶æ€ç»Ÿè®¡
const updateStatusStats = () => {
  const stats = {
    not_started: 0,
    in_progress: 0,
    completed: 0,
    delayed: 0
  }
  projectList.forEach(project => {
    stats[project.status]++
  })
  statusStats[0].count = stats.not_started
  statusStats[1].count = stats.in_progress
  statusStats[2].count = stats.completed
  statusStats[3].count = stats.delayed
}
// æŸ¥çœ‹è¯¦æƒ…
const viewDetails = (project) => {
  ElMessage.info(`查看项目详情: ${project.name}`)
}
// åˆ·æ–°æ•°æ®
const refreshData = () => {
  // æ¨¡æ‹Ÿå®žæ—¶æ›´æ–°
  const randomProject = projectList[Math.floor(Math.random() * projectList.length)]
  if (randomProject.status === 'in_progress') {
    randomProject.progress = Math.min(100, randomProject.progress + Math.floor(Math.random() * 10))
    if (randomProject.progress === 100) {
      randomProject.status = 'completed'
    }
  }
  updateStatusStats()
  if (viewMode.value === 'gantt') {
    nextTick(() => {
      initGanttChart()
    })
  }
  ElMessage.success('数据已刷新')
}
onMounted(() => {
  updateStatusStats()
})
</script>
<style scoped>
.process-tracking {
  padding: 20px;
}
.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}
.header h2 {
  margin: 0;
  color: #303133;
}
.status-cards {
  margin-bottom: 20px;
}
.status-card {
  cursor: pointer;
  transition: all 0.3s;
}
.status-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.card-content {
  display: flex;
  align-items: center;
}
.card-icon {
  margin-right: 15px;
  padding: 10px;
  border-radius: 8px;
}
.status-card.not-started .card-icon {
  background-color: #f4f4f5;
  color: #909399;
}
.status-card.in-progress .card-icon {
  background-color: #fdf6ec;
  color: #E6A23C;
}
.status-card.completed .card-icon {
  background-color: #f0f9ff;
  color: #67C23A;
}
.status-card.delayed .card-icon {
  background-color: #fef0f0;
  color: #F56C6C;
}
.card-info {
  flex: 1;
}
.card-title {
  font-size: 14px;
  color: #909399;
  margin-bottom: 5px;
}
.card-count {
  font-size: 24px;
  font-weight: bold;
  color: #303133;
}
.project-list {
  margin-top: 20px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.gantt-container {
  min-height: 400px;
}
</style>