餐饮油烟智能监测与监管一体化平台
feiyu02
2026-03-20 20cdb83586daabfb15fc056c4c97eb8e7ccaf928
src/views/inspection/MonitorControl.vue
@@ -1,75 +1,79 @@
<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 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}` }}
          <el-row justify="space-between">
            <div class="chart-item">
              <div class="progress-container">
                <el-progress
                  type="dashboard"
                  :percentage="parseFloat(inspectionStats.inspectedRate)"
                  :color="['#409EFF', '#67C23A']"
                  :width="120"
                />
                <div class="progress-label">已巡查店铺率</div>
                <div class="progress-value">
                  {{ `${inspectionStats.inspectedShops}/${inspectionStats.totalShops}` }}
                </div>
              </div>
            </div>
          </div>
          <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 class="stats-grid m-l-16">
              <el-statistic
                class="stat-item"
                :value="inspectionStats.inspectionPoints"
                title="巡查点次"
              />
              <el-statistic
                class="stat-item"
                :value="inspectionStats.reviewPoints"
                title="复查点次"
              />
            </div>
          </el-row>
        </div>
        <!-- 右侧:问题数、问题整改数、问题整改率统计图 -->
        <div class="stats-section right-section">
          <h3>问题整改概况</h3>
          <div class="stats-grid">
            <el-statistic class="stat-item" :value="inspectionStats.problemCount" title="问题数" />
            <el-row justify="space-around">
              <el-text>问题数:{{ inspectionStats.problemCount }}</el-text>
              <el-text>整改数:{{ inspectionStats.rectifiedProblems }}</el-text>
            </el-row>
            <!-- <el-statistic class="stat-item" :value="inspectionStats.problemCount" title="问题数" />
            <el-statistic
              class="stat-item"
              :value="inspectionStats.rectifiedProblems"
              title="问题整改数"
            />
            /> -->
          </div>
          <!-- <div class="chart-item"> -->
          <div ref="rectificationRateChart" class="chart"></div>
@@ -88,284 +92,8 @@
          <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>
@@ -464,137 +192,6 @@
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 = () => {
  // 模拟刷新数据
@@ -635,294 +232,8 @@
  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
  // 这里可以添加实际的分页逻辑
}
// 初始化图表
@@ -988,6 +299,8 @@
      series: [
        {
          type: 'pie',
          radius: ['20%', '45%'],
          center: ['50%', '45%'],
          data: [
            { value: 30, name: '油烟在线监测设备' },
            { value: 25, name: '油烟净化设施设备' },
@@ -1007,189 +320,18 @@
    })
  }
  // 每日处罚数量图
  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小时内整改率', '综合整改率', '审核通过率'],
        data: [
          '当日整改率',
          // '48小时内整改率',
          '综合整改率',
          '审核通过率',
        ],
      },
      yAxis: {
        type: 'value',
@@ -1201,7 +343,7 @@
        {
          data: [
            parseFloat(inspectionStats.value.sameDayRectificationRate),
            parseFloat(inspectionStats.value.effectiveRectificationRate),
            // parseFloat(inspectionStats.value.effectiveRectificationRate),
            parseFloat(inspectionStats.value.comprehensiveRectificationRate),
            parseFloat(inspectionStats.value.auditPassRate),
          ],
@@ -1230,18 +372,6 @@
  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变化
@@ -1256,8 +386,6 @@
// 生命周期
onMounted(() => {
  refreshInspectionData()
  searchPunishment()
  searchComplaint()
  initCharts()
  window.addEventListener('resize', handleResize)
})
@@ -1274,21 +402,26 @@
</script>
<style scoped>
.monitor-control {
  padding: 20px;
  background-color: #f5f7fa;
  min-height: 100vh;
:deep() .el-card__body {
  padding: 8px;
}
.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 {
@@ -1297,16 +430,15 @@
.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 {
@@ -1317,13 +449,13 @@
}
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 20px;
  margin-bottom: 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.stat-item {
  flex: 1;
  background-color: #fff;
  padding: 20px;
  border-radius: 8px;
@@ -1353,13 +485,14 @@
}
.chart-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
  display: flex;
  flex-direction: column;
  gap: 20px;
  margin-bottom: 30px;
}
.chart-item {
  flex: 1;
  background-color: #fff;
  padding: 20px;
  border-radius: 8px;
@@ -1387,7 +520,7 @@
.chart {
  width: 100%;
  height: 300px;
  height: 250px;
}
.pagination-container {