| | |
| | | <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> |
| | | <!-- 总览现场巡查卡片 --> |
| | | <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 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> |
| | | </el-scrollbar> |
| | | </el-card> |
| | | </template> |
| | | |
| | | <script setup> |
| | |
| | | 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 = () => { |
| | | // 模拟刷新数据 |
| | |
| | | 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 |
| | | // 这里可以添加实际的分页逻辑 |
| | | } |
| | | |
| | | // 初始化图表 |
| | |
| | | series: [ |
| | | { |
| | | type: 'pie', |
| | | radius: ['20%', '45%'], |
| | | center: ['50%', '45%'], |
| | | data: [ |
| | | { value: 30, name: '油烟在线监测设备' }, |
| | | { value: 25, 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, |
| | |
| | | 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变化 |
| | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | refreshInspectionData() |
| | | searchPunishment() |
| | | searchComplaint() |
| | | initCharts() |
| | | window.addEventListener('resize', handleResize) |
| | | }) |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .monitor-control { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: 100vh; |
| | | .mb-4 { |
| | | /* width: 600px; */ |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .filter-group { |
| | | display: flex; |
| | | align-items: center; |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | width: 100%; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .mr-2 { |
| | |
| | | |
| | | .stats-sections { |
| | | display: flex; |
| | | flex-direction: column; |
| | | 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); |
| | | /* border: 1px solid #e4e7ed; */ |
| | | } |
| | | |
| | | .stats-section h3 { |
| | |
| | | |
| | | .stats-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| | | gap: 20px; |
| | | grid-template-columns: 1fr 1fr; |
| | | gap: 10px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | .chart-container { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | margin-bottom: 30px; |
| | | } |
| | |
| | | |
| | | .chart { |
| | | width: 100%; |
| | | height: 300px; |
| | | height: 250px; |
| | | } |
| | | |
| | | .pagination-container { |