餐饮油烟智能监测与监管一体化平台
riku
2026-03-17 b1a0d701cf898c8b7812e66a808a1c91f2bae6cc
src/views/inspection/MonitorControl.vue
@@ -1 +1,1417 @@
<template>s</template>
<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>