| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |