<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>
|
<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-t-16">
|
<el-statistic
|
class="stat-item"
|
:value="inspectionStats.inspectionPoints"
|
title="巡查点次"
|
/>
|
<el-statistic
|
class="stat-item"
|
:value="inspectionStats.reviewPoints"
|
title="复查点次"
|
/>
|
</div>
|
</div>
|
|
<!-- 右侧:问题数、问题整改数、问题整改率统计图 -->
|
<div class="stats-section right-section">
|
<h3>问题整改概况</h3>
|
<div class="stats-grid">
|
<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('2023-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>
|
.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;
|
margin-bottom: 30px;
|
}
|
|
.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: grid;
|
grid-template-columns: 1fr 1fr;
|
gap: 10px;
|
margin-bottom: 20px;
|
}
|
|
.stat-item {
|
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 {
|
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>
|