<template>
|
<div class="monitor-control">
|
<!-- 总览现场巡查卡片 -->
|
<el-card class="mb-4">
|
<template #header>
|
<div class="card-header">
|
<span>现场巡查总览</span>
|
<div class="filter-group">
|
<FYOptionTime
|
:initValue="false"
|
type="daterange"
|
v-model:value="params.timeRange"
|
style="width: 300px; margin-bottom: 0px"
|
:shortcuts="shortcuts"
|
></FYOptionTime>
|
<!-- 区县 -->
|
<FYOptionLocation
|
class="m-l-8"
|
:allOption="false"
|
:level="3"
|
:checkStrictly="false"
|
:initValue="false"
|
v-model:value="params.locations"
|
style="width: 300px; margin-bottom: 0px"
|
></FYOptionLocation>
|
</div>
|
</div>
|
</template>
|
|
<!-- 统计数据区域 -->
|
<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-card>
|
|
<!-- 餐饮店铺行政处罚卡片 -->
|
<el-card class="mb-4">
|
<template #header>
|
<div class="card-header">
|
<span>餐饮店铺行政处罚</span>
|
<div class="filter-group">
|
<FYOptionTime
|
class="m-r-8"
|
:initValue="false"
|
type="daterange"
|
v-model:value="punishmentDateRange"
|
style="width: 300px; margin-bottom: 0px"
|
:shortcuts="shortcuts"
|
></FYOptionTime>
|
<el-button type="success" icon="Plus" @click="addPunishment">新增处罚</el-button>
|
<el-button type="info" icon="Upload" @click="importPunishment">批量导入</el-button>
|
</div>
|
</div>
|
</template>
|
|
<!-- 图表展示 -->
|
<div class="chart-container">
|
<div class="chart-item">
|
<div class="chart-header">
|
<h3>处罚数趋势</h3>
|
<div class="chart-summary">处罚总数: {{ punishmentStats.totalCount }}</div>
|
</div>
|
<div ref="dailyPunishmentChart" class="chart"></div>
|
</div>
|
<div class="chart-item">
|
<h3>店铺类型处罚分布</h3>
|
<div ref="shopTypePunishmentChart" class="chart"></div>
|
</div>
|
</div>
|
|
<!-- 处罚记录表格 -->
|
<el-table
|
:data="filteredPunishmentData"
|
table-layout="fixed"
|
:show-overflow-tooltip="true"
|
height="400px"
|
border
|
>
|
<el-table-column prop="shopName" label="处罚店铺" />
|
<el-table-column prop="punishmentItem" label="处罚事项" width="180" />
|
<el-table-column prop="punishmentTime" label="处罚时间" width="180" />
|
<el-table-column prop="punishmentReason" label="处罚理由" width="200" />
|
<el-table-column prop="punishmentResult" label="处罚结果" width="150" />
|
<el-table-column prop="punishmentDepartment" label="处罚部门" width="150" />
|
<el-table-column width="250">
|
<template #header>
|
<el-input v-model="punishmentKeyword" placeholder="关键字搜索" style="width: 120px" />
|
</template>
|
<template #default="scope">
|
<el-button size="small" type="primary" @click="editPunishment(scope.row)"
|
>编辑</el-button
|
>
|
<el-button size="small" type="danger" @click="deletePunishment(scope.row.id)"
|
>删除</el-button
|
>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<!-- 分页 -->
|
<div class="pagination-container">
|
<el-pagination
|
v-model:current-page="punishmentPagination.currentPage"
|
v-model:page-size="punishmentPagination.pageSize"
|
:page-sizes="[10, 20, 50, 100]"
|
layout="total, sizes, prev, pager, next, jumper"
|
:total="punishmentPagination.total"
|
@size-change="handlePunishmentSizeChange"
|
@current-change="handlePunishmentCurrentChange"
|
/>
|
</div>
|
</el-card>
|
|
<!-- 餐饮店铺信访投诉卡片 -->
|
<el-card class="mb-4">
|
<template #header>
|
<div class="card-header">
|
<span>餐饮店铺信访投诉</span>
|
<div class="filter-group">
|
<FYOptionTime
|
class="m-r-8"
|
:initValue="false"
|
type="daterange"
|
v-model:value="complaintDateRange"
|
style="width: 300px; margin-bottom: 0px"
|
:shortcuts="shortcuts"
|
></FYOptionTime>
|
<el-button type="success" icon="Plus" @click="addComplaint">新增投诉</el-button>
|
<el-button type="info" icon="Upload" @click="importComplaint">批量导入</el-button>
|
</div>
|
</div>
|
</template>
|
|
<!-- 图表展示 -->
|
<div class="chart-container">
|
<div class="chart-item">
|
<div class="chart-header">
|
<h3>投诉数趋势</h3>
|
<div class="chart-summary">投诉总数: {{ complaintStats.totalCount }}</div>
|
</div>
|
<div ref="dailyComplaintChart" class="chart"></div>
|
</div>
|
<div class="chart-item">
|
<h3>投诉来源分布</h3>
|
<div ref="sourceComplaintChart" class="chart"></div>
|
</div>
|
</div>
|
|
<!-- 投诉记录表格 -->
|
<el-table
|
:data="filteredComplaintData"
|
table-layout="fixed"
|
:show-overflow-tooltip="true"
|
height="400px"
|
border
|
>
|
<el-table-column prop="shopName" label="投诉店铺" />
|
<el-table-column prop="complaintReason" label="投诉原因" width="180" />
|
<el-table-column prop="complaintRequest" label="投诉诉求" width="180" />
|
<el-table-column prop="complaintTime" label="投诉时间" width="180" />
|
<el-table-column prop="complaintSource" label="投诉来源" width="150" />
|
<el-table-column prop="handlingDepartment" label="处理部门" width="150" />
|
<el-table-column prop="complaintResult" label="投诉结果" width="150" />
|
<el-table-column width="250">
|
<template #header>
|
<el-input v-model="complaintKeyword" placeholder="关键字搜索" style="width: 120px" />
|
</template>
|
<template #default="scope">
|
<el-button size="small" type="primary" @click="editComplaint(scope.row)"
|
>编辑</el-button
|
>
|
<el-button size="small" type="danger" @click="deleteComplaint(scope.row.id)"
|
>删除</el-button
|
>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<!-- 分页 -->
|
<div class="pagination-container">
|
<el-pagination
|
v-model:current-page="complaintPagination.currentPage"
|
v-model:page-size="complaintPagination.pageSize"
|
:page-sizes="[10, 20, 50, 100]"
|
layout="total, sizes, prev, pager, next, jumper"
|
:total="complaintPagination.total"
|
@size-change="handleComplaintSizeChange"
|
@current-change="handleComplaintCurrentChange"
|
/>
|
</div>
|
</el-card>
|
|
<!-- 新增/编辑处罚对话框 -->
|
<el-dialog v-model="punishmentDialogVisible" title="处罚信息" width="600px">
|
<el-form :model="punishmentForm" label-width="100px">
|
<el-form-item label="处罚店铺">
|
<el-input v-model="punishmentForm.shopName" />
|
</el-form-item>
|
<el-form-item label="处罚事项">
|
<el-input v-model="punishmentForm.punishmentItem" />
|
</el-form-item>
|
<el-form-item label="处罚时间">
|
<el-date-picker v-model="punishmentForm.punishmentTime" type="datetime" />
|
</el-form-item>
|
<el-form-item label="处罚理由">
|
<el-input v-model="punishmentForm.punishmentReason" type="textarea" />
|
</el-form-item>
|
<el-form-item label="处罚结果">
|
<el-input v-model="punishmentForm.punishmentResult" />
|
</el-form-item>
|
<el-form-item label="处罚部门">
|
<el-input v-model="punishmentForm.punishmentDepartment" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="punishmentDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="savePunishment">保存</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
|
<!-- 新增/编辑投诉对话框 -->
|
<el-dialog v-model="complaintDialogVisible" title="投诉信息" width="600px">
|
<el-form :model="complaintForm" label-width="100px">
|
<el-form-item label="投诉店铺">
|
<el-input v-model="complaintForm.shopName" />
|
</el-form-item>
|
<el-form-item label="投诉原因">
|
<el-input v-model="complaintForm.complaintReason" />
|
</el-form-item>
|
<el-form-item label="投诉诉求">
|
<el-input v-model="complaintForm.complaintRequest" type="textarea" />
|
</el-form-item>
|
<el-form-item label="投诉时间">
|
<el-date-picker v-model="complaintForm.complaintTime" type="datetime" />
|
</el-form-item>
|
<el-form-item label="投诉来源">
|
<el-input v-model="complaintForm.complaintSource" />
|
</el-form-item>
|
<el-form-item label="处理部门">
|
<el-input v-model="complaintForm.handlingDepartment" />
|
</el-form-item>
|
<el-form-item label="投诉结果">
|
<el-input v-model="complaintForm.complaintResult" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="complaintDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="saveComplaint">保存</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
|
<!-- 处罚批量导入对话框 -->
|
<el-dialog v-model="punishmentImportDialogVisible" title="处罚批量导入" width="600px">
|
<div class="import-container">
|
<p class="import-tip">请选择要导入的Excel文件</p>
|
<el-upload
|
class="upload-demo"
|
action="#"
|
:auto-upload="false"
|
:on-change="handlePunishmentFileChange"
|
:file-list="punishmentImportFileList"
|
accept=".xlsx,.xls"
|
:limit="1"
|
:on-exceed="handleExceed"
|
>
|
<el-button type="primary">选择文件</el-button>
|
<template #tip>
|
<div class="el-upload__tip">只能上传Excel文件,且不超过5MB</div>
|
</template>
|
</el-upload>
|
</div>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="punishmentImportDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="confirmPunishmentImport">导入</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
|
<!-- 投诉批量导入对话框 -->
|
<el-dialog v-model="complaintImportDialogVisible" title="投诉批量导入" width="600px">
|
<div class="import-container">
|
<p class="import-tip">请选择要导入的Excel文件</p>
|
<el-upload
|
class="upload-demo"
|
action="#"
|
:auto-upload="false"
|
:on-change="handleComplaintFileChange"
|
:file-list="complaintImportFileList"
|
accept=".xlsx,.xls"
|
:limit="1"
|
:on-exceed="handleExceed"
|
>
|
<el-button type="primary">选择文件</el-button>
|
<template #tip>
|
<div class="el-upload__tip">只能上传Excel文件,且不超过5MB</div>
|
</template>
|
</el-upload>
|
</div>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="complaintImportDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="confirmComplaintImport">导入</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
</div>
|
</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 dailyPunishmentChart = ref(null)
|
const shopTypePunishmentChart = ref(null)
|
const dailyComplaintChart = ref(null)
|
const sourceComplaintChart = ref(null)
|
|
// 行政处罚数据
|
const punishmentDateRange = ref([
|
dayStart.startOf('month').toDate(),
|
dayEnd.endOf('month').toDate(),
|
])
|
const punishmentKeyword = ref('')
|
const punishmentStats = ref({
|
totalCount: 120,
|
})
|
const punishmentTableData = ref([
|
// {
|
// id: 1,
|
// shopName: '味美餐厅',
|
// punishmentItem: '油烟超标排放',
|
// punishmentTime: '2026-03-10 14:30',
|
// punishmentReason: '未安装油烟净化设备',
|
// punishmentResult: '罚款5000元',
|
// punishmentDepartment: '徐汇区环保局',
|
// },
|
// {
|
// id: 2,
|
// shopName: '香辣小龙虾',
|
// punishmentItem: '油烟超标排放',
|
// punishmentTime: '2026-03-08 10:15',
|
// punishmentReason: '油烟净化设备未正常运行',
|
// punishmentResult: '罚款3000元',
|
// punishmentDepartment: '徐汇区环保局',
|
// },
|
])
|
const punishmentPagination = ref({
|
currentPage: 1,
|
pageSize: 10,
|
total: 2,
|
})
|
|
// 过滤后的处罚数据
|
const filteredPunishmentData = computed(() => {
|
if (!punishmentKeyword.value) {
|
return punishmentTableData.value
|
}
|
const keyword = punishmentKeyword.value.toLowerCase()
|
return punishmentTableData.value.filter((item) => {
|
return Object.values(item).some((value) => {
|
return String(value).toLowerCase().includes(keyword)
|
})
|
})
|
})
|
|
// 信访投诉数据
|
const complaintDateRange = ref([dayStart.startOf('month').toDate(), dayEnd.endOf('month').toDate()])
|
const complaintKeyword = ref('')
|
const complaintStats = ref({
|
totalCount: 85,
|
})
|
const complaintTableData = ref([
|
// {
|
// id: 1,
|
// shopName: '鲜味馆',
|
// complaintReason: '油烟扰民',
|
// complaintRequest: '要求安装油烟净化设备',
|
// complaintTime: '2026-03-12 09:20',
|
// complaintSource: '12345热线',
|
// handlingDepartment: '徐汇区环保局',
|
// complaintResult: '已处理',
|
// },
|
// {
|
// id: 2,
|
// shopName: '烧烤达人',
|
// complaintReason: '夜间油烟污染',
|
// complaintRequest: '要求整改',
|
// complaintTime: '2026-03-10 22:30',
|
// complaintSource: '居民投诉',
|
// handlingDepartment: '徐汇区环保局',
|
// complaintResult: '处理中',
|
// },
|
])
|
const complaintPagination = ref({
|
currentPage: 1,
|
pageSize: 10,
|
total: 2,
|
})
|
|
// 过滤后的投诉数据
|
const filteredComplaintData = computed(() => {
|
if (!complaintKeyword.value) {
|
return complaintTableData.value
|
}
|
const keyword = complaintKeyword.value.toLowerCase()
|
return complaintTableData.value.filter((item) => {
|
return Object.values(item).some((value) => {
|
return String(value).toLowerCase().includes(keyword)
|
})
|
})
|
})
|
|
// 对话框状态
|
const punishmentDialogVisible = ref(false)
|
const complaintDialogVisible = ref(false)
|
const punishmentImportDialogVisible = ref(false)
|
const complaintImportDialogVisible = ref(false)
|
|
// 导入文件列表
|
const punishmentImportFileList = ref([])
|
const complaintImportFileList = ref([])
|
|
// 表单数据
|
const punishmentForm = ref({
|
id: '',
|
shopName: '',
|
punishmentItem: '',
|
punishmentTime: '',
|
punishmentReason: '',
|
punishmentResult: '',
|
punishmentDepartment: '',
|
})
|
|
const complaintForm = ref({
|
id: '',
|
shopName: '',
|
complaintReason: '',
|
complaintRequest: '',
|
complaintTime: '',
|
complaintSource: '',
|
handlingDepartment: '',
|
complaintResult: '',
|
})
|
|
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 searchPunishment = () => {
|
// 模拟搜索处罚数据
|
console.log('搜索处罚数据', {
|
dateRange: punishmentDateRange.value,
|
keyword: punishmentKeyword.value,
|
})
|
|
// 计算时间范围
|
const startTime = punishmentDateRange.value[0]
|
const endTime = punishmentDateRange.value[1]
|
const startDate = dayjs(startTime)
|
const endDate = dayjs(endTime)
|
const daysDiff = endDate.diff(startDate, 'day')
|
const months = daysDiff / 30
|
|
// 生成新的模拟数据
|
const totalCount = Math.floor(10 * months) + Math.floor(Math.random() * 3)
|
punishmentStats.value = {
|
totalCount,
|
}
|
|
// 生成新的处罚记录
|
const newData = []
|
const shopNames = [
|
'味美餐厅',
|
'香辣小龙虾',
|
'鲜味馆',
|
'烧烤达人',
|
'川菜馆',
|
'西餐厅',
|
'日料店',
|
'火锅店',
|
]
|
const punishmentItems = ['油烟超标排放', '未安装油烟净化设备', '设备未正常运行', '噪声污染']
|
const departments = ['徐汇区环保局', '长宁区环保局', '静安区环保局', '普陀区环保局']
|
|
for (let i = 0; i < totalCount; i++) {
|
// 生成在时间范围内的随机时间
|
const randomDays = Math.floor(Math.random() * (daysDiff + 1))
|
const randomTime = startDate
|
.add(randomDays, 'day')
|
.add(Math.floor(Math.random() * 24), 'hour')
|
.add(Math.floor(Math.random() * 60), 'minute')
|
|
newData.push({
|
id: i + 1,
|
shopName: shopNames[Math.floor(Math.random() * shopNames.length)],
|
punishmentItem: punishmentItems[Math.floor(Math.random() * punishmentItems.length)],
|
punishmentTime: randomTime.format('YYYY-MM-DD HH:mm'),
|
punishmentReason:
|
punishmentItems[Math.floor(Math.random() * punishmentItems.length)] + '的违规行为',
|
punishmentResult: '罚款' + (Math.floor(Math.random() * 5) + 1) * 1000 + '元',
|
punishmentDepartment: departments[Math.floor(Math.random() * departments.length)],
|
})
|
}
|
|
punishmentTableData.value = newData
|
punishmentPagination.value.total = newData.length
|
// 重新初始化图表以更新数据
|
initCharts()
|
}
|
|
const searchComplaint = () => {
|
// 模拟搜索投诉数据
|
console.log('搜索投诉数据', {
|
dateRange: complaintDateRange.value,
|
keyword: complaintKeyword.value,
|
})
|
|
// 计算时间范围
|
const startTime = complaintDateRange.value[0]
|
const endTime = complaintDateRange.value[1]
|
const startDate = dayjs(startTime)
|
const endDate = dayjs(endTime)
|
const daysDiff = endDate.diff(startDate, 'day')
|
const months = daysDiff / 30
|
|
// 生成新的模拟数据
|
const totalCount = Math.floor(20 * months) + Math.floor(Math.random() * 5)
|
complaintStats.value = {
|
totalCount,
|
}
|
|
// 生成新的投诉记录
|
const newData = []
|
const shopNames = [
|
'鲜味馆',
|
'烧烤达人',
|
'味美餐厅',
|
'香辣小龙虾',
|
'川菜馆',
|
'西餐厅',
|
'日料店',
|
'火锅店',
|
]
|
const complaintReasons = ['油烟扰民', '夜间噪声', '异味污染', '卫生问题']
|
const sources = ['12345热线', '居民投诉', '网络平台', '其他']
|
const departments = ['徐汇区环保局', '长宁区环保局', '静安区环保局', '普陀区环保局']
|
const results = ['已处理', '处理中', '未处理']
|
|
for (let i = 0; i < totalCount; i++) {
|
// 生成在时间范围内的随机时间
|
const randomDays = Math.floor(Math.random() * (daysDiff + 1))
|
const randomTime = startDate
|
.add(randomDays, 'day')
|
.add(Math.floor(Math.random() * 24), 'hour')
|
.add(Math.floor(Math.random() * 60), 'minute')
|
|
newData.push({
|
id: i + 1,
|
shopName: shopNames[Math.floor(Math.random() * shopNames.length)],
|
complaintReason: complaintReasons[Math.floor(Math.random() * complaintReasons.length)],
|
complaintRequest:
|
'要求整改' + complaintReasons[Math.floor(Math.random() * complaintReasons.length)],
|
complaintTime: randomTime.format('YYYY-MM-DD HH:mm'),
|
complaintSource: sources[Math.floor(Math.random() * sources.length)],
|
handlingDepartment: departments[Math.floor(Math.random() * departments.length)],
|
complaintResult: results[Math.floor(Math.random() * results.length)],
|
})
|
}
|
|
complaintTableData.value = newData
|
complaintPagination.value.total = newData.length
|
// 重新初始化图表以更新数据
|
initCharts()
|
}
|
|
// 监听处罚日期范围变化
|
watch(
|
() => punishmentDateRange.value,
|
() => {
|
searchPunishment()
|
},
|
{ deep: true },
|
)
|
|
// 监听投诉日期范围变化
|
watch(
|
() => complaintDateRange.value,
|
() => {
|
searchComplaint()
|
},
|
{ deep: true },
|
)
|
|
const addPunishment = () => {
|
// 重置表单
|
punishmentForm.value = {
|
id: '',
|
shopName: '',
|
punishmentItem: '',
|
punishmentTime: '',
|
punishmentReason: '',
|
punishmentResult: '',
|
punishmentDepartment: '',
|
}
|
punishmentDialogVisible.value = true
|
}
|
|
const editPunishment = (row) => {
|
// 填充表单
|
punishmentForm.value = { ...row }
|
punishmentDialogVisible.value = true
|
}
|
|
const savePunishment = () => {
|
// 模拟保存数据
|
console.log('保存处罚数据', punishmentForm.value)
|
punishmentDialogVisible.value = false
|
// 这里可以添加实际的数据保存逻辑
|
}
|
|
const deletePunishment = (id) => {
|
// 模拟删除数据
|
console.log('删除处罚数据', id)
|
// 这里可以添加实际的数据删除逻辑
|
}
|
|
const importPunishment = () => {
|
// 打开导入对话框
|
punishmentImportFileList.value = []
|
punishmentImportDialogVisible.value = true
|
}
|
|
const handlePunishmentFileChange = (file, fileList) => {
|
punishmentImportFileList.value = fileList
|
console.log('选择的处罚文件:', file)
|
}
|
|
const confirmPunishmentImport = () => {
|
if (punishmentImportFileList.value.length === 0) {
|
ElMessage.warning('请选择要导入的文件')
|
return
|
}
|
|
const file = punishmentImportFileList.value[0]
|
console.log('开始导入处罚数据:', file.name)
|
|
// 预留导入逻辑
|
// 这里将实现实际的文件解析和数据导入
|
|
punishmentImportDialogVisible.value = false
|
ElMessage.success('导入操作已触发,预留导入逻辑')
|
}
|
|
const addComplaint = () => {
|
// 重置表单
|
complaintForm.value = {
|
id: '',
|
shopName: '',
|
complaintReason: '',
|
complaintRequest: '',
|
complaintTime: '',
|
complaintSource: '',
|
handlingDepartment: '',
|
complaintResult: '',
|
}
|
complaintDialogVisible.value = true
|
}
|
|
const editComplaint = (row) => {
|
// 填充表单
|
complaintForm.value = { ...row }
|
complaintDialogVisible.value = true
|
}
|
|
const saveComplaint = () => {
|
// 模拟保存数据
|
console.log('保存投诉数据', complaintForm.value)
|
complaintDialogVisible.value = false
|
// 这里可以添加实际的数据保存逻辑
|
}
|
|
const deleteComplaint = (id) => {
|
// 模拟删除数据
|
console.log('删除投诉数据', id)
|
// 这里可以添加实际的数据删除逻辑
|
}
|
|
const importComplaint = () => {
|
// 打开导入对话框
|
complaintImportFileList.value = []
|
complaintImportDialogVisible.value = true
|
}
|
|
const handleComplaintFileChange = (file, fileList) => {
|
complaintImportFileList.value = fileList
|
console.log('选择的投诉文件:', file)
|
}
|
|
const confirmComplaintImport = () => {
|
if (complaintImportFileList.value.length === 0) {
|
ElMessage.warning('请选择要导入的文件')
|
return
|
}
|
|
const file = complaintImportFileList.value[0]
|
console.log('开始导入投诉数据:', file.name)
|
|
// 预留导入逻辑
|
// 这里将实现实际的文件解析和数据导入
|
|
complaintImportDialogVisible.value = false
|
ElMessage.success('导入操作已触发,预留导入逻辑')
|
}
|
|
const handleExceed = (files, fileList) => {
|
ElMessage.warning('只能上传一个文件')
|
}
|
|
const handlePunishmentSizeChange = (size) => {
|
punishmentPagination.value.pageSize = size
|
// 这里可以添加实际的分页逻辑
|
}
|
|
const handlePunishmentCurrentChange = (current) => {
|
punishmentPagination.value.currentPage = current
|
// 这里可以添加实际的分页逻辑
|
}
|
|
const handleComplaintSizeChange = (size) => {
|
complaintPagination.value.pageSize = size
|
// 这里可以添加实际的分页逻辑
|
}
|
|
const handleComplaintCurrentChange = (current) => {
|
complaintPagination.value.currentPage = current
|
// 这里可以添加实际的分页逻辑
|
}
|
|
// 初始化图表
|
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',
|
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 (dailyPunishmentChart.value && punishmentDateRange.value.length > 0) {
|
const chart = echarts.init(dailyPunishmentChart.value)
|
|
// 计算时间范围并生成x轴数据
|
const startTime = punishmentDateRange.value[0]
|
const endTime = punishmentDateRange.value[1]
|
const startDate = dayjs(startTime)
|
const endDate = dayjs(endTime)
|
const daysDiff = endDate.diff(startDate, 'day')
|
|
let xAxisData = []
|
let seriesData = []
|
|
// 处理处罚数据
|
const punishmentData = punishmentTableData.value
|
const dateFormat = daysDiff <= 30 ? 'MM/DD' : 'YYYY/MM'
|
|
// 生成日期范围
|
if (daysDiff <= 30) {
|
// 同一个月内,按日显示
|
for (let i = 0; i <= daysDiff; i++) {
|
const date = startDate.add(i, 'day')
|
xAxisData.push(date.format(dateFormat))
|
// 计算该日期的处罚数量
|
const count = punishmentData.filter((item) => {
|
const itemDate = dayjs(item.punishmentTime)
|
return itemDate.format(dateFormat) === date.format(dateFormat)
|
}).length
|
seriesData.push(count)
|
}
|
} 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(dateFormat))
|
// 计算该月份的处罚数量
|
const count = punishmentData.filter((item) => {
|
const itemDate = dayjs(item.punishmentTime)
|
return itemDate.format(dateFormat) === date.format(dateFormat)
|
}).length
|
seriesData.push(count)
|
}
|
}
|
|
chart.setOption({
|
xAxis: {
|
type: 'category',
|
data: xAxisData,
|
},
|
yAxis: {
|
type: 'value',
|
},
|
series: [
|
{
|
data: seriesData,
|
type: 'bar',
|
},
|
],
|
})
|
}
|
|
// 店铺类型处罚分布图
|
if (shopTypePunishmentChart.value) {
|
const chart = echarts.init(shopTypePunishmentChart.value)
|
chart.setOption({
|
series: [
|
{
|
type: 'pie',
|
data: [
|
{ value: 50, name: '中餐' },
|
{ value: 30, name: '烧烤' },
|
{ value: 20, name: '西餐' },
|
{ value: 15, name: '其他' },
|
],
|
label: {
|
show: true,
|
formatter: '{b}: {d}%',
|
},
|
},
|
],
|
})
|
}
|
|
// 每日投诉数量图
|
if (dailyComplaintChart.value && complaintDateRange.value.length > 0) {
|
const chart = echarts.init(dailyComplaintChart.value)
|
|
// 计算时间范围并生成x轴数据
|
const startTime = complaintDateRange.value[0]
|
const endTime = complaintDateRange.value[1]
|
const startDate = dayjs(startTime)
|
const endDate = dayjs(endTime)
|
const daysDiff = endDate.diff(startDate, 'day')
|
|
let xAxisData = []
|
let seriesData = []
|
|
// 处理投诉数据
|
const complaintData = complaintTableData.value
|
const dateFormat = daysDiff <= 30 ? 'MM/DD' : 'YYYY/MM'
|
|
// 生成日期范围
|
if (daysDiff <= 30) {
|
// 同一个月内,按日显示
|
for (let i = 0; i <= daysDiff; i++) {
|
const date = startDate.add(i, 'day')
|
xAxisData.push(date.format(dateFormat))
|
// 计算该日期的投诉数量
|
const count = complaintData.filter((item) => {
|
const itemDate = dayjs(item.complaintTime)
|
return itemDate.format(dateFormat) === date.format(dateFormat)
|
}).length
|
seriesData.push(count)
|
}
|
} 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(dateFormat))
|
// 计算该月份的投诉数量
|
const count = complaintData.filter((item) => {
|
const itemDate = dayjs(item.complaintTime)
|
return itemDate.format(dateFormat) === date.format(dateFormat)
|
}).length
|
seriesData.push(count)
|
}
|
}
|
|
chart.setOption({
|
xAxis: {
|
type: 'category',
|
data: xAxisData,
|
},
|
yAxis: {
|
type: 'value',
|
},
|
series: [
|
{
|
data: seriesData,
|
type: 'bar',
|
},
|
],
|
})
|
}
|
|
// 投诉来源分布图
|
if (sourceComplaintChart.value) {
|
const chart = echarts.init(sourceComplaintChart.value)
|
chart.setOption({
|
series: [
|
{
|
type: 'pie',
|
data: [
|
{ value: 40, name: '12345热线' },
|
{ value: 25, name: '居民投诉' },
|
{ value: 15, name: '网络平台' },
|
{ value: 5, 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()
|
}
|
if (dailyPunishmentChart.value) {
|
echarts.init(dailyPunishmentChart.value).resize()
|
}
|
if (shopTypePunishmentChart.value) {
|
echarts.init(shopTypePunishmentChart.value).resize()
|
}
|
if (dailyComplaintChart.value) {
|
echarts.init(dailyComplaintChart.value).resize()
|
}
|
if (sourceComplaintChart.value) {
|
echarts.init(sourceComplaintChart.value).resize()
|
}
|
}
|
|
// 监听params变化
|
watch(
|
() => params.value,
|
() => {
|
refreshInspectionData()
|
},
|
{ deep: true },
|
)
|
|
// 生命周期
|
onMounted(() => {
|
refreshInspectionData()
|
searchPunishment()
|
searchComplaint()
|
initCharts()
|
window.addEventListener('resize', handleResize)
|
})
|
|
// 组件卸载时清理事件监听
|
onUnmounted(() => {
|
cleanup()
|
})
|
|
// 清理
|
const cleanup = () => {
|
window.removeEventListener('resize', handleResize)
|
}
|
</script>
|
|
<style scoped>
|
.monitor-control {
|
padding: 20px;
|
background-color: #f5f7fa;
|
min-height: 100vh;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.filter-group {
|
display: flex;
|
align-items: center;
|
}
|
|
.mr-2 {
|
margin-right: 10px;
|
}
|
|
.stats-sections {
|
display: flex;
|
gap: 20px;
|
margin-bottom: 30px;
|
}
|
|
.stats-section {
|
flex: 1;
|
background-color: #fff;
|
padding: 20px;
|
border-radius: 8px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
}
|
|
.stats-section h3 {
|
margin-bottom: 15px;
|
font-size: 16px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.stats-grid {
|
display: grid;
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
gap: 20px;
|
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: grid;
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
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: 300px;
|
}
|
|
.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>
|