<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>
|