| | |
| | | <template>s</template> |
| | | <template> |
| | | <!-- 总览现场巡查卡片 --> |
| | | <el-card class="mb-4"> |
| | | <!-- <template #header> |
| | | <div class="card-header"> |
| | | <span>现场巡查总览</span> |
| | | <el-form :model="params" label-position="left" label-width="70px"> |
| | | <FYOptionTime |
| | | :initValue="false" |
| | | type="daterange" |
| | | v-model:value="params.timeRange" |
| | | style="width: 300px" |
| | | :shortcuts="shortcuts" |
| | | ></FYOptionTime> |
| | | <FYOptionLocation |
| | | :allOption="false" |
| | | :level="3" |
| | | :checkStrictly="false" |
| | | :initValue="false" |
| | | v-model:value="params.locations" |
| | | style="width: 300px; margin-bottom: 0px" |
| | | ></FYOptionLocation> |
| | | </el-form> |
| | | </div> |
| | | </template> --> |
| | | |
| | | <el-scrollbar> |
| | | <!-- 统计数据区域 --> |
| | | <div class="stats-sections"> |
| | | <!-- 左侧:已巡查店铺率、巡查点次、复查点次 --> |
| | | <div class="stats-section left-section"> |
| | | <h3>巡查概况</h3> |
| | | <el-row justify="space-between"> |
| | | <div class="chart-item"> |
| | | <div class="progress-container"> |
| | | <el-progress |
| | | type="dashboard" |
| | | :percentage="parseFloat(inspectionStats.inspectedRate)" |
| | | :color="['#409EFF', '#67C23A']" |
| | | :width="120" |
| | | /> |
| | | <div class="progress-label">已巡查店铺率</div> |
| | | <div class="progress-value"> |
| | | {{ `${inspectionStats.inspectedShops}/${inspectionStats.totalShops}` }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="stats-grid m-l-16"> |
| | | <el-statistic |
| | | class="stat-item" |
| | | :value="inspectionStats.inspectionPoints" |
| | | title="巡查点次" |
| | | /> |
| | | <el-statistic |
| | | class="stat-item" |
| | | :value="inspectionStats.reviewPoints" |
| | | title="复查点次" |
| | | /> |
| | | </div> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- 右侧:问题数、问题整改数、问题整改率统计图 --> |
| | | <div class="stats-section right-section"> |
| | | <h3>问题整改概况</h3> |
| | | <div class="stats-grid"> |
| | | <el-row justify="space-around"> |
| | | <el-text>问题数:{{ inspectionStats.problemCount }}</el-text> |
| | | <el-text>整改数:{{ inspectionStats.rectifiedProblems }}</el-text> |
| | | </el-row> |
| | | <!-- <el-statistic class="stat-item" :value="inspectionStats.problemCount" title="问题数" /> |
| | | <el-statistic |
| | | class="stat-item" |
| | | :value="inspectionStats.rectifiedProblems" |
| | | title="问题整改数" |
| | | /> --> |
| | | </div> |
| | | <!-- <div class="chart-item"> --> |
| | | <div ref="rectificationRateChart" class="chart"></div> |
| | | <!-- </div> --> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 其他图表展示 --> |
| | | <div class="chart-container"> |
| | | <div class="chart-item"> |
| | | <h3>巡查完成情况趋势</h3> |
| | | <div ref="inspectionTrendChart" class="chart"></div> |
| | | </div> |
| | | <div class="chart-item"> |
| | | <h3>问题类型分布</h3> |
| | | <div ref="problemTypeChart" class="chart"></div> |
| | | </div> |
| | | </div> |
| | | </el-scrollbar> |
| | | </el-card> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onUnmounted, watch, computed } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | import { Icon } from '@iconify/vue' |
| | | import dayjs from 'dayjs' |
| | | import { ElMessage } from 'element-plus' |
| | | |
| | | // 总览现场巡查数据 |
| | | const dayStart = dayjs('2025-08-01').startOf('date') |
| | | const dayEnd = dayStart.endOf('date') |
| | | const shortcuts = [ |
| | | { |
| | | text: '今天', |
| | | value: [dayStart.toDate(), dayEnd.toDate()], |
| | | }, |
| | | { |
| | | text: '本周', |
| | | value: [dayStart.startOf('week').toDate(), dayEnd.endOf('week').toDate()], |
| | | }, |
| | | { |
| | | text: '上周', |
| | | value: [dayStart.day(-7).toDate(), dayEnd.day(-1).toDate()], |
| | | }, |
| | | { |
| | | text: '本月', |
| | | value: [dayStart.startOf('month').toDate(), dayEnd.endOf('month').toDate()], |
| | | }, |
| | | { |
| | | text: '上月', |
| | | value: [ |
| | | dayStart.subtract(1, 'month').startOf('month').toDate(), |
| | | dayEnd.subtract(1, 'month').endOf('month').toDate(), |
| | | ], |
| | | }, |
| | | { |
| | | text: '本季度', |
| | | value: [dayStart.startOf('quarter').toDate(), dayEnd.endOf('quarter').toDate()], |
| | | }, |
| | | { |
| | | text: '上季度', |
| | | value: [ |
| | | dayStart.subtract(1, 'quarter').startOf('quarter').toDate(), |
| | | dayEnd.subtract(1, 'quarter').endOf('quarter').toDate(), |
| | | ], |
| | | }, |
| | | { |
| | | text: '去年', |
| | | value: [ |
| | | dayStart.subtract(1, 'year').startOf('year').toDate(), |
| | | dayEnd.subtract(1, 'year').endOf('year').toDate(), |
| | | ], |
| | | }, |
| | | { |
| | | text: '今年', |
| | | value: [dayStart.startOf('year').toDate(), dayEnd.endOf('year').toDate()], |
| | | }, |
| | | ] |
| | | const params = ref({ |
| | | prodBaseTypes: [], |
| | | prodCheck: '', |
| | | scenetype: '', |
| | | topTask: '', |
| | | locations: { |
| | | aCode: null, |
| | | aName: null, |
| | | cCode: '3100', |
| | | cName: '上海市', |
| | | dCode: '310104', |
| | | dName: '徐汇区', |
| | | mCode: null, |
| | | mName: null, |
| | | pCode: '31', |
| | | pName: '上海市', |
| | | tCode: null, |
| | | tName: null, |
| | | }, |
| | | timeRange: [dayStart.startOf('month').toDate(), dayEnd.endOf('month').toDate()], |
| | | }) |
| | | const inspectionStats = ref({ |
| | | // totalShops: 1250, |
| | | // inspectedShops: 980, |
| | | // inspectedRate: '78.4%', |
| | | // inspectionPoints: 2350, |
| | | // reviewPoints: 450, |
| | | // problemCount: 320, |
| | | // rectifiedProblems: 280, |
| | | // sameDayRectificationRate: '65.2%', |
| | | // effectiveRectificationRate: '78.5%', |
| | | // comprehensiveRectificationRate: '82.3%', |
| | | // auditPassRate: '87.5%', |
| | | }) |
| | | |
| | | // 图表引用 |
| | | const inspectionTrendChart = ref(null) |
| | | const problemTypeChart = ref(null) |
| | | const rectificationRateChart = ref(null) |
| | | |
| | | const refreshInspectionData = () => { |
| | | // 模拟刷新数据 |
| | | console.log('刷新巡查数据', params.value) |
| | | // 生成新的模拟数据 |
| | | const totalShops = 90 + Math.floor(Math.random() * 10) |
| | | const inspectedShops = 70 + Math.floor(Math.random() * 10) |
| | | const inspectedRate = ((inspectedShops / totalShops) * 100).toFixed(1) + '%' |
| | | |
| | | // 计算时间范围 |
| | | const startTime = params.value.timeRange[0] |
| | | const endTime = params.value.timeRange[1] |
| | | const startDate = dayjs(startTime) |
| | | const endDate = dayjs(endTime) |
| | | const daysDiff = endDate.diff(startDate, 'day') |
| | | const months = daysDiff / 30 |
| | | |
| | | // 根据时间范围调整数据量级 |
| | | const inspectionPoints = Math.floor(100 * months) + Math.floor(Math.random() * 20) |
| | | const problemCount = Math.floor(200 * months) + Math.floor(Math.random() * 30) |
| | | const reviewPoints = Math.floor(inspectionPoints * 0.2) + Math.floor(Math.random() * 10) |
| | | const rectifiedProblems = Math.floor(problemCount * 0.8) + Math.floor(Math.random() * 20) |
| | | |
| | | inspectionStats.value = { |
| | | totalShops, |
| | | inspectedShops, |
| | | inspectedRate, |
| | | inspectionPoints, |
| | | reviewPoints, |
| | | problemCount, |
| | | rectifiedProblems, |
| | | sameDayRectificationRate: (60 + Math.random() * 20).toFixed(1) + '%', |
| | | effectiveRectificationRate: (70 + Math.random() * 20).toFixed(1) + '%', |
| | | comprehensiveRectificationRate: (75 + Math.random() * 15).toFixed(1) + '%', |
| | | auditPassRate: (80 + Math.random() * 15).toFixed(1) + '%', |
| | | } |
| | | // 重新初始化图表以更新数据 |
| | | initCharts() |
| | | } |
| | | |
| | | const handleExceed = (files, fileList) => { |
| | | ElMessage.warning('只能上传一个文件') |
| | | } |
| | | |
| | | // 初始化图表 |
| | | const initCharts = () => { |
| | | // 巡查完成情况趋势图 |
| | | if (inspectionTrendChart.value) { |
| | | const chart = echarts.init(inspectionTrendChart.value) |
| | | |
| | | // 计算时间范围并生成x轴数据 |
| | | const startTime = params.value.timeRange[0] |
| | | const endTime = params.value.timeRange[1] |
| | | const startDate = dayjs(startTime) |
| | | const endDate = dayjs(endTime) |
| | | const daysDiff = endDate.diff(startDate, 'day') |
| | | |
| | | let xAxisData = [] |
| | | let seriesData = [] |
| | | |
| | | if (daysDiff <= 30) { |
| | | // 同一个月内,按日显示 |
| | | for (let i = 0; i <= daysDiff; i++) { |
| | | const date = startDate.add(i, 'day') |
| | | xAxisData.push(date.format('MM/DD')) |
| | | seriesData.push(60 + Math.floor(Math.random() * 30)) // 生成随机数据 |
| | | } |
| | | } else { |
| | | // 超过一个月,按月显示 |
| | | const startMonth = startDate.startOf('month') |
| | | const endMonth = endDate.endOf('month') |
| | | const monthsDiff = endMonth.diff(startMonth, 'month') |
| | | |
| | | for (let i = 0; i <= monthsDiff; i++) { |
| | | const date = startMonth.add(i, 'month') |
| | | xAxisData.push(date.format('YYYY/MM')) |
| | | seriesData.push(60 + Math.floor(Math.random() * 30)) // 生成随机数据 |
| | | } |
| | | } |
| | | |
| | | chart.setOption({ |
| | | xAxis: { |
| | | type: 'category', |
| | | data: xAxisData, |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLabel: { |
| | | formatter: '{value}%', |
| | | }, |
| | | }, |
| | | series: [ |
| | | { |
| | | data: seriesData, |
| | | type: 'bar', |
| | | }, |
| | | ], |
| | | }) |
| | | } |
| | | |
| | | // 问题类型分布图 |
| | | if (problemTypeChart.value) { |
| | | const chart = echarts.init(problemTypeChart.value) |
| | | chart.setOption({ |
| | | series: [ |
| | | { |
| | | type: 'pie', |
| | | radius: ['20%', '45%'], |
| | | center: ['50%', '45%'], |
| | | data: [ |
| | | { value: 30, name: '油烟在线监测设备' }, |
| | | { value: 25, name: '油烟净化设施设备' }, |
| | | { value: 20, name: '油烟在线监测设备维护' }, |
| | | { value: 15, name: '油烟净化设施设备维护' }, |
| | | { value: 10, name: '油烟在线监测数据量级' }, |
| | | { value: 20, name: '空调和风机噪声' }, |
| | | { value: 18, name: '台账管理' }, |
| | | { value: 22, name: '信用承诺自评' }, |
| | | ], |
| | | label: { |
| | | show: true, |
| | | formatter: '{b}: {d}%', |
| | | }, |
| | | }, |
| | | ], |
| | | }) |
| | | } |
| | | |
| | | // 问题整改率统计图 |
| | | if (rectificationRateChart.value) { |
| | | const chart = echarts.init(rectificationRateChart.value) |
| | | chart.setOption({ |
| | | xAxis: { |
| | | type: 'category', |
| | | data: [ |
| | | '当日整改率', |
| | | // '48小时内整改率', |
| | | '综合整改率', |
| | | '审核通过率', |
| | | ], |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLabel: { |
| | | formatter: '{value}%', |
| | | }, |
| | | }, |
| | | series: [ |
| | | { |
| | | data: [ |
| | | parseFloat(inspectionStats.value.sameDayRectificationRate), |
| | | // parseFloat(inspectionStats.value.effectiveRectificationRate), |
| | | parseFloat(inspectionStats.value.comprehensiveRectificationRate), |
| | | parseFloat(inspectionStats.value.auditPassRate), |
| | | ], |
| | | type: 'bar', |
| | | itemStyle: { |
| | | color: function (params) { |
| | | const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C'] |
| | | return colors[params.dataIndex] |
| | | }, |
| | | }, |
| | | }, |
| | | ], |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // 监听窗口大小变化 |
| | | const handleResize = () => { |
| | | // 重新调整图表大小 |
| | | if (inspectionTrendChart.value) { |
| | | echarts.init(inspectionTrendChart.value).resize() |
| | | } |
| | | if (problemTypeChart.value) { |
| | | echarts.init(problemTypeChart.value).resize() |
| | | } |
| | | if (rectificationRateChart.value) { |
| | | echarts.init(rectificationRateChart.value).resize() |
| | | } |
| | | } |
| | | |
| | | // 监听params变化 |
| | | watch( |
| | | () => params.value, |
| | | () => { |
| | | refreshInspectionData() |
| | | }, |
| | | { deep: true }, |
| | | ) |
| | | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | refreshInspectionData() |
| | | initCharts() |
| | | window.addEventListener('resize', handleResize) |
| | | }) |
| | | |
| | | // 组件卸载时清理事件监听 |
| | | onUnmounted(() => { |
| | | cleanup() |
| | | }) |
| | | |
| | | // 清理 |
| | | const cleanup = () => { |
| | | window.removeEventListener('resize', handleResize) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | :deep() .el-card__body { |
| | | padding: 8px; |
| | | } |
| | | .mb-4 { |
| | | /* width: 600px; */ |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .filter-group { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | width: 100%; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .mr-2 { |
| | | margin-right: 10px; |
| | | } |
| | | |
| | | .stats-sections { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .stats-section { |
| | | flex: 1; |
| | | background-color: #fff; |
| | | border-radius: 8px; |
| | | /* border: 1px solid #e4e7ed; */ |
| | | } |
| | | |
| | | .stats-section h3 { |
| | | margin-bottom: 15px; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .stats-grid { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .stat-item { |
| | | flex: 1; |
| | | background-color: #fff; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .progress-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | height: 100%; |
| | | } |
| | | |
| | | .progress-label { |
| | | margin-top: 10px; |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #606266; |
| | | } |
| | | |
| | | .progress-value { |
| | | margin-top: 5px; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .chart-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .chart-item { |
| | | flex: 1; |
| | | background-color: #fff; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .chart-item h3 { |
| | | margin-bottom: 15px; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .chart-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .chart-summary { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .chart { |
| | | width: 100%; |
| | | height: 250px; |
| | | } |
| | | |
| | | .pagination-container { |
| | | margin-top: 20px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .import-container { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .import-tip { |
| | | margin-bottom: 20px; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .upload-demo { |
| | | margin-bottom: 20px; |
| | | } |
| | | </style> |