<template>
|
<div class="report-generation">
|
<!-- 页面头部 -->
|
<div class="page-header">
|
<h2>项目总结报告生成</h2>
|
<div class="header-actions">
|
<el-button v-if="!reportGenerated" type="primary" @click="generateReport" :loading="generating">
|
<el-icon><Document /></el-icon>
|
生成报告
|
</el-button>
|
<el-button v-if="reportGenerated" type="primary" @click="resetConfig">
|
<el-icon><Refresh /></el-icon>
|
生成新报告
|
</el-button>
|
<el-button @click="exportReport" :disabled="!reportGenerated">
|
<el-icon><Download /></el-icon>
|
导出报告
|
</el-button>
|
</div>
|
</div>
|
|
<!-- 报告配置区域 -->
|
<el-card class="config-card" v-if="!reportGenerated">
|
<template #header>
|
<span>报告配置</span>
|
</template>
|
<el-form :model="reportConfig" label-width="120px">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="项目名称">
|
<el-select v-model="reportConfig.projectId" placeholder="请选择项目">
|
<el-option
|
v-for="project in projectList"
|
:key="project.id"
|
:label="project.name"
|
:value="project.id"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="报告周期">
|
<el-date-picker
|
v-model="reportConfig.dateRange"
|
type="daterange"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
</el-card>
|
|
<!-- 报告内容展示区域 -->
|
<div v-if="reportGenerated" class="report-content">
|
<!-- 项目基本信息 -->
|
<el-card class="report-section">
|
<template #header>
|
<span>项目基本信息</span>
|
</template>
|
<el-descriptions :column="2" border>
|
<el-descriptions-item label="项目名称">{{ reportData.projectInfo.name }}</el-descriptions-item>
|
<el-descriptions-item label="项目经理">{{ reportData.projectInfo.manager }}</el-descriptions-item>
|
<el-descriptions-item label="开始时间">{{ reportData.projectInfo.startDate }}</el-descriptions-item>
|
<el-descriptions-item label="结束时间">{{ reportData.projectInfo.endDate }}</el-descriptions-item>
|
<el-descriptions-item label="项目状态">
|
<el-tag :type="getStatusType(reportData.projectInfo.status)">
|
{{ reportData.projectInfo.status }}
|
</el-tag>
|
</el-descriptions-item>
|
<el-descriptions-item label="总预算">{{ reportData.projectInfo.budget }}</el-descriptions-item>
|
</el-descriptions>
|
</el-card>
|
|
<!-- 任务完成率统计 -->
|
<el-card class="report-section">
|
<template #header>
|
<span>任务完成率统计</span>
|
</template>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<div class="completion-stats">
|
<div class="stat-item">
|
<div class="stat-label">总任务数</div>
|
<div class="stat-value">{{ reportData.taskStats.total }}</div>
|
</div>
|
<div class="stat-item">
|
<div class="stat-label">已完成</div>
|
<div class="stat-value completed">{{ reportData.taskStats.completed }}</div>
|
</div>
|
<div class="stat-item">
|
<div class="stat-label">进行中</div>
|
<div class="stat-value in-progress">{{ reportData.taskStats.inProgress }}</div>
|
</div>
|
<div class="stat-item">
|
<div class="stat-label">未开始</div>
|
<div class="stat-value pending">{{ reportData.taskStats.pending }}</div>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="completion-rate">
|
<el-progress
|
type="circle"
|
:percentage="reportData.taskStats.completionRate"
|
:width="150"
|
:stroke-width="8"
|
>
|
<template #default="{ percentage }">
|
<span class="percentage-value">{{ percentage }}%</span>
|
<div class="percentage-label">完成率</div>
|
</template>
|
</el-progress>
|
</div>
|
</el-col>
|
</el-row>
|
</el-card>
|
|
<!-- 问题记录统计 -->
|
<el-card class="report-section">
|
<template #header>
|
<span>问题记录统计</span>
|
</template>
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<div class="issue-summary">
|
<div class="summary-item">
|
<div class="summary-label">总问题数</div>
|
<div class="summary-value">{{ reportData.issueStats.total }}</div>
|
</div>
|
<div class="summary-item">
|
<div class="summary-label">已解决</div>
|
<div class="summary-value resolved">{{ reportData.issueStats.resolved }}</div>
|
</div>
|
<div class="summary-item">
|
<div class="summary-label">待解决</div>
|
<div class="summary-value pending">{{ reportData.issueStats.pending }}</div>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="16">
|
<el-table :data="reportData.issueStats.topIssues" size="small">
|
<el-table-column prop="title" label="主要问题" />
|
<el-table-column prop="severity" label="严重程度" width="100">
|
<template #default="{ row }">
|
<el-tag :type="getSeverityType(row.severity)" size="small">
|
{{ row.severity }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="status" label="状态" width="80">
|
<template #default="{ row }">
|
<el-tag :type="row.status === '已解决' ? 'success' : 'warning'" size="small">
|
{{ row.status }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
</el-table>
|
</el-col>
|
</el-row>
|
</el-card>
|
|
<!-- 延误分析 -->
|
<el-card class="report-section">
|
<template #header>
|
<span>延误分析</span>
|
</template>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<div class="delay-stats">
|
<div class="delay-item">
|
<div class="delay-label">延误任务数</div>
|
<div class="delay-value">{{ reportData.delayAnalysis.delayedTasks }}</div>
|
</div>
|
<div class="delay-item">
|
<div class="delay-label">平均延误天数</div>
|
<div class="delay-value">{{ reportData.delayAnalysis.avgDelayDays }}</div>
|
</div>
|
<div class="delay-item">
|
<div class="delay-label">最大延误天数</div>
|
<div class="delay-value">{{ reportData.delayAnalysis.maxDelayDays }}</div>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="delay-reasons">
|
<h4>主要延误原因</h4>
|
<ul>
|
<li v-for="reason in reportData.delayAnalysis.reasons" :key="reason.reason">
|
{{ reason.reason }} ({{ reason.count }}次)
|
</li>
|
</ul>
|
</div>
|
</el-col>
|
</el-row>
|
</el-card>
|
|
<!-- 团队绩效 -->
|
<el-card class="report-section">
|
<template #header>
|
<span>团队绩效</span>
|
</template>
|
<el-table :data="reportData.teamPerformance" size="small">
|
<el-table-column prop="name" label="成员姓名" />
|
<el-table-column prop="completedTasks" label="完成任务数" />
|
<el-table-column prop="completionRate" label="完成率" width="100">
|
<template #default="{ row }">
|
<el-progress :percentage="row.completionRate" :show-text="false" />
|
<span style="margin-left: 10px;">{{ row.completionRate }}%</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="performance" label="绩效评级" width="100">
|
<template #default="{ row }">
|
<el-tag :type="getPerformanceType(row.performance)">
|
{{ row.performance }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
</el-table>
|
</el-card>
|
|
<!-- 总结与建议 -->
|
<el-card class="report-section">
|
<template #header>
|
<span>总结与建议</span>
|
</template>
|
<div class="summary-content">
|
<h4>项目总结</h4>
|
<p>{{ reportData.summary.conclusion }}</p>
|
|
<h4>改进建议</h4>
|
<ul>
|
<li v-for="suggestion in reportData.summary.suggestions" :key="suggestion">
|
{{ suggestion }}
|
</li>
|
</ul>
|
</div>
|
</el-card>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted } from 'vue'
|
import { Document, Download, Refresh } from '@element-plus/icons-vue'
|
import { ElMessage } from 'element-plus'
|
|
// 响应式数据
|
const generating = ref(false)
|
const reportGenerated = ref(false)
|
|
// 报告配置
|
const reportConfig = reactive({
|
projectId: '',
|
dateRange: []
|
})
|
|
// 项目列表
|
const projectList = ref([
|
{ id: 1, name: '产品库存管理系统' },
|
{ id: 2, name: '客户关系管理平台' },
|
{ id: 3, name: '财务管理系统升级' },
|
{ id: 4, name: '移动端应用开发' }
|
])
|
|
// 报告数据
|
const reportData = ref({})
|
|
// 模拟报告数据
|
const mockReportData = {
|
projectInfo: {
|
name: '产品库存管理系统',
|
manager: '张三',
|
startDate: '2024-01-01',
|
endDate: '2024-03-31',
|
status: '已完成',
|
budget: '¥500,000'
|
},
|
taskStats: {
|
total: 45,
|
completed: 38,
|
inProgress: 5,
|
pending: 2,
|
completionRate: 84
|
},
|
issueStats: {
|
total: 12,
|
resolved: 9,
|
pending: 3,
|
topIssues: [
|
{ title: '数据库连接超时', severity: '高', status: '已解决' },
|
{ title: '前端页面响应慢', severity: '中', status: '已解决' },
|
{ title: '权限验证异常', severity: '高', status: '待解决' },
|
{ title: '报表导出功能缺失', severity: '中', status: '待解决' }
|
]
|
},
|
delayAnalysis: {
|
delayedTasks: 8,
|
avgDelayDays: 3.5,
|
maxDelayDays: 12,
|
reasons: [
|
{ reason: '需求变更', count: 3 },
|
{ reason: '技术难题', count: 2 },
|
{ reason: '资源不足', count: 2 },
|
{ reason: '外部依赖', count: 1 }
|
]
|
},
|
teamPerformance: [
|
{ name: '李四', completedTasks: 12, completionRate: 92, performance: '优秀' },
|
{ name: '王五', completedTasks: 10, completionRate: 85, performance: '良好' },
|
{ name: '赵六', completedTasks: 8, completionRate: 78, performance: '良好' },
|
{ name: '钱七', completedTasks: 8, completionRate: 72, performance: '一般' }
|
],
|
summary: {
|
conclusion: '本项目整体执行情况良好,任务完成率达到84%,团队协作效率较高。主要问题集中在技术实现和需求变更方面,通过及时沟通和技术攻关,大部分问题已得到解决。',
|
suggestions: [
|
'加强需求分析阶段的工作,减少后期需求变更',
|
'建立技术难题预警机制,提前识别和解决技术风险',
|
'优化团队资源配置,提高整体工作效率',
|
'完善项目管理流程,加强过程监控'
|
]
|
}
|
}
|
|
// 生成报告
|
const generateReport = async () => {
|
if (!reportConfig.projectId) {
|
ElMessage.warning('请选择项目')
|
return
|
}
|
|
generating.value = true
|
|
// 模拟生成报告的过程
|
setTimeout(() => {
|
reportData.value = mockReportData
|
reportGenerated.value = true
|
generating.value = false
|
ElMessage.success('报告生成成功')
|
}, 2000)
|
}
|
|
// 导出报告
|
const exportReport = () => {
|
ElMessage.success('报告导出功能开发中...')
|
}
|
|
// 重置配置,生成新报告
|
const resetConfig = () => {
|
reportGenerated.value = false
|
reportData.value = {}
|
// 重置配置为默认值
|
reportConfig.projectId = 1
|
const endDate = new Date()
|
const startDate = new Date()
|
startDate.setDate(startDate.getDate() - 30)
|
reportConfig.dateRange = [
|
startDate.toISOString().split('T')[0],
|
endDate.toISOString().split('T')[0]
|
]
|
ElMessage.success('已重置配置,可以生成新报告')
|
}
|
|
// 获取状态类型
|
const getStatusType = (status) => {
|
const statusMap = {
|
'已完成': 'success',
|
'进行中': 'warning',
|
'未开始': 'info',
|
'已暂停': 'danger'
|
}
|
return statusMap[status] || 'info'
|
}
|
|
// 获取严重程度类型
|
const getSeverityType = (severity) => {
|
const severityMap = {
|
'高': 'danger',
|
'中': 'warning',
|
'低': 'success'
|
}
|
return severityMap[severity] || 'info'
|
}
|
|
// 获取绩效类型
|
const getPerformanceType = (performance) => {
|
const performanceMap = {
|
'优秀': 'success',
|
'良好': 'primary',
|
'一般': 'warning',
|
'待改进': 'danger'
|
}
|
return performanceMap[performance] || 'info'
|
}
|
|
// 组件挂载时初始化
|
onMounted(() => {
|
// 设置默认项目
|
reportConfig.projectId = 1
|
// 设置默认日期范围(最近30天)
|
const endDate = new Date()
|
const startDate = new Date()
|
startDate.setDate(startDate.getDate() - 30)
|
reportConfig.dateRange = [
|
startDate.toISOString().split('T')[0],
|
endDate.toISOString().split('T')[0]
|
]
|
})
|
</script>
|
|
<style scoped>
|
.report-generation {
|
padding: 20px;
|
}
|
|
.page-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
}
|
|
.page-header h2 {
|
margin: 0;
|
color: #303133;
|
}
|
|
.header-actions {
|
display: flex;
|
gap: 10px;
|
}
|
|
.config-card {
|
margin-bottom: 20px;
|
}
|
|
.report-content {
|
display: flex;
|
flex-direction: column;
|
gap: 20px;
|
}
|
|
.report-section {
|
margin-bottom: 20px;
|
}
|
|
.completion-stats {
|
display: flex;
|
justify-content: space-around;
|
padding: 20px 0;
|
}
|
|
.stat-item {
|
text-align: center;
|
}
|
|
.stat-label {
|
font-size: 14px;
|
color: #909399;
|
margin-bottom: 8px;
|
}
|
|
.stat-value {
|
font-size: 24px;
|
font-weight: bold;
|
color: #303133;
|
}
|
|
.stat-value.completed {
|
color: #67c23a;
|
}
|
|
.stat-value.in-progress {
|
color: #e6a23c;
|
}
|
|
.stat-value.pending {
|
color: #909399;
|
}
|
|
.completion-rate {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
height: 200px;
|
}
|
|
.percentage-value {
|
font-size: 20px;
|
font-weight: bold;
|
color: #409eff;
|
}
|
|
.percentage-label {
|
font-size: 12px;
|
color: #909399;
|
margin-top: 4px;
|
}
|
|
.issue-summary {
|
display: flex;
|
flex-direction: column;
|
gap: 20px;
|
padding: 20px 0;
|
}
|
|
.summary-item {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.summary-label {
|
font-size: 14px;
|
color: #606266;
|
}
|
|
.summary-value {
|
font-size: 18px;
|
font-weight: bold;
|
color: #303133;
|
}
|
|
.summary-value.resolved {
|
color: #67c23a;
|
}
|
|
.summary-value.pending {
|
color: #e6a23c;
|
}
|
|
.delay-stats {
|
display: flex;
|
flex-direction: column;
|
gap: 20px;
|
padding: 20px 0;
|
}
|
|
.delay-item {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.delay-label {
|
font-size: 14px;
|
color: #606266;
|
}
|
|
.delay-value {
|
font-size: 18px;
|
font-weight: bold;
|
color: #e6a23c;
|
}
|
|
.delay-reasons h4 {
|
margin: 0 0 15px 0;
|
color: #303133;
|
}
|
|
.delay-reasons ul {
|
margin: 0;
|
padding-left: 20px;
|
}
|
|
.delay-reasons li {
|
margin-bottom: 8px;
|
color: #606266;
|
}
|
|
.summary-content h4 {
|
margin: 0 0 10px 0;
|
color: #303133;
|
}
|
|
.summary-content p {
|
line-height: 1.6;
|
color: #606266;
|
margin-bottom: 20px;
|
}
|
|
.summary-content ul {
|
margin: 0;
|
padding-left: 20px;
|
}
|
|
.summary-content li {
|
margin-bottom: 8px;
|
color: #606266;
|
line-height: 1.5;
|
}
|
</style>
|