|
|
|
@ -1,54 +1,32 @@ |
|
|
|
<script setup lang="ts"> |
|
|
|
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; |
|
|
|
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; |
|
|
|
import { getDicts } from '@/api/system/dict/data'; |
|
|
|
import { listHighCheckArea, listHighCheckDomain, type HighFrequencyRow } from '@/api/efcode/report/high-frequency'; |
|
|
|
|
|
|
|
/** 维度:area=各区域 field=各领域 */ |
|
|
|
const props = defineProps<{ |
|
|
|
dimension: 'area' | 'field'; |
|
|
|
}>(); |
|
|
|
|
|
|
|
/** 高频检查预警企业行数据 */ |
|
|
|
interface HighFrequencyRow { |
|
|
|
id: number; |
|
|
|
enterpriseName: string; |
|
|
|
areaName: string; |
|
|
|
streetName: string; |
|
|
|
enforcementCount: number | null; |
|
|
|
} |
|
|
|
|
|
|
|
// ======================== 查询条件 ======================== |
|
|
|
const now = new Date(); |
|
|
|
const queryParams = reactive({ |
|
|
|
enterpriseName: '' as string, |
|
|
|
area: undefined as string | undefined, |
|
|
|
field: undefined as string | undefined, |
|
|
|
street: undefined as string | undefined |
|
|
|
pageNum: 1, |
|
|
|
pageSize: 20, |
|
|
|
district: undefined as string | undefined, // 区域 tab 用 |
|
|
|
domainName: undefined as string | undefined, // 领域 tab 用 |
|
|
|
year: String(now.getFullYear()), |
|
|
|
month: `${String(now.getFullYear())}-${String(now.getMonth() + 1).padStart(2, '0')}` |
|
|
|
}); |
|
|
|
|
|
|
|
// 时段类型:week=周度 month=月度 quarter=季度 year=年度 |
|
|
|
const periodType = ref('month'); |
|
|
|
const periodOptions = [ |
|
|
|
// { label: '周度', value: 'week' }, |
|
|
|
{ label: '月度', value: 'month' }, |
|
|
|
// { label: '季度', value: 'quarter' }, |
|
|
|
// { label: '年度', value: 'year' } |
|
|
|
]; |
|
|
|
const periodDate = ref<string>('2026-09'); |
|
|
|
|
|
|
|
/** 根据时段切换日期选择器类型 */ |
|
|
|
const pickerType = computed<'week' | 'month' | 'year'>(() => { |
|
|
|
if (periodType.value === 'week') return 'week'; |
|
|
|
if (periodType.value === 'year') return 'year'; |
|
|
|
return 'month'; |
|
|
|
}); |
|
|
|
const pickerFormat = computed(() => { |
|
|
|
if (pickerType.value === 'year') return 'YYYY'; |
|
|
|
if (pickerType.value === 'week') return 'YYYY-MM-DD'; |
|
|
|
return 'YYYY-MM'; |
|
|
|
}); |
|
|
|
/** 年份选项(近 5 年) */ |
|
|
|
const yearOptions = (() => { |
|
|
|
const curr = now.getFullYear(); |
|
|
|
return Array.from({ length: 5 }, (_, i) => String(curr - i)); |
|
|
|
})(); |
|
|
|
|
|
|
|
// ======================== 字典 ======================== |
|
|
|
const areaOptions = ref<any[]>([]); |
|
|
|
const streetOptions = ref<any[]>([]); |
|
|
|
const fieldOptions = [ |
|
|
|
{ label: '食品安全', value: 'food' }, |
|
|
|
{ label: '药品安全', value: 'drug' }, |
|
|
|
@ -67,67 +45,54 @@ const loadAreaDict = async () => { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** 切换所属区时联动加载所属街道 */ |
|
|
|
const handleAreaChange = async (val: string) => { |
|
|
|
queryParams.street = undefined; |
|
|
|
streetOptions.value = []; |
|
|
|
if (!val) return; |
|
|
|
try { |
|
|
|
const res = await getDicts(val); |
|
|
|
streetOptions.value = res.data || []; |
|
|
|
} catch (e) { |
|
|
|
console.error('加载所属街道字典失败', e); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// ======================== 表格数据 ======================== |
|
|
|
const loading = ref(false); |
|
|
|
const tableData = ref<HighFrequencyRow[]>([]); |
|
|
|
const total = ref(0); |
|
|
|
/** 当前预警数 */ |
|
|
|
const warningCount = ref(0); |
|
|
|
const currentPage = ref(1); |
|
|
|
const pageSize = ref(20); |
|
|
|
const selectedRows = ref<HighFrequencyRow[]>([]); |
|
|
|
|
|
|
|
// 表格高度(与 efcode/report/registration/indexList.vue 实现一致) |
|
|
|
const tableHeight = ref<number | undefined>(undefined); |
|
|
|
const tableRef = ref<HTMLElement>(); |
|
|
|
|
|
|
|
/** 计算表格高度:视口高度 - 表格顶部偏移 - 分页区高度(~52px) - 底部边距(16px) */ |
|
|
|
/** 计算表格高度 */ |
|
|
|
const calcTableHeight = () => { |
|
|
|
if (!tableRef.value) return; |
|
|
|
const rect = tableRef.value.getBoundingClientRect(); |
|
|
|
tableHeight.value = window.innerHeight - rect.top - 84; |
|
|
|
}; |
|
|
|
|
|
|
|
/** 构造设计稿中的占位数据(前2条有数据,其余为空行) */ |
|
|
|
const buildMockRows = (): HighFrequencyRow[] => { |
|
|
|
const rows: HighFrequencyRow[] = [ |
|
|
|
{ id: 1, enterpriseName: '深圳市宝安区西乡佳创电子厂', areaName: '宝安区', streetName: '光明街道', enforcementCount: 20 }, |
|
|
|
{ id: 2, enterpriseName: '深圳市黄金屋真空科技有限公司', areaName: '龙华区', streetName: '福海街道', enforcementCount: 10 } |
|
|
|
]; |
|
|
|
for (let i = 3; i <= 7; i++) { |
|
|
|
rows.push({ id: i, enterpriseName: '', areaName: '', streetName: '', enforcementCount: null }); |
|
|
|
} |
|
|
|
return rows; |
|
|
|
}; |
|
|
|
|
|
|
|
const getList = async () => { |
|
|
|
loading.value = true; |
|
|
|
try { |
|
|
|
// TODO: 接入真实接口后替换为接口返回数据 |
|
|
|
// const res = await listHighFrequency({ |
|
|
|
// dimension: props.dimension, |
|
|
|
// ...queryParams, |
|
|
|
// periodType: periodType.value, |
|
|
|
// periodDate: periodDate.value, |
|
|
|
// pageNum: currentPage.value, |
|
|
|
// pageSize: pageSize.value |
|
|
|
// }); |
|
|
|
tableData.value = buildMockRows(); |
|
|
|
total.value = 658; |
|
|
|
warningCount.value = 20; |
|
|
|
let rows: HighFrequencyRow[] = []; |
|
|
|
let totalNum = 0; |
|
|
|
if (props.dimension === 'area') { |
|
|
|
// 区域接口:district + enforcementTime(直接用 month 的 YYYY-MM 值) |
|
|
|
const res = await listHighCheckArea({ |
|
|
|
pageNum: queryParams.pageNum, |
|
|
|
pageSize: queryParams.pageSize, |
|
|
|
district: queryParams.district, |
|
|
|
enforcementTime: queryParams.month |
|
|
|
}); |
|
|
|
rows = (res as any)?.rows || []; |
|
|
|
totalNum = (res as any)?.total || 0; |
|
|
|
} else { |
|
|
|
// 领域接口:domainName + year + month(month 传值和 area 一样,但后端参数名不同) |
|
|
|
const res = await listHighCheckDomain({ |
|
|
|
pageNum: queryParams.pageNum, |
|
|
|
pageSize: queryParams.pageSize, |
|
|
|
domainName: queryParams.domainName, |
|
|
|
year: queryParams.year, |
|
|
|
month: queryParams.month |
|
|
|
}); |
|
|
|
rows = (res as any)?.rows || []; |
|
|
|
totalNum = (res as any)?.total || 0; |
|
|
|
} |
|
|
|
tableData.value = rows; |
|
|
|
total.value = totalNum; |
|
|
|
warningCount.value = totalNum; |
|
|
|
} finally { |
|
|
|
loading.value = false; |
|
|
|
} |
|
|
|
@ -135,21 +100,18 @@ const getList = async () => { |
|
|
|
|
|
|
|
/** 搜索 */ |
|
|
|
const handleSearch = () => { |
|
|
|
currentPage.value = 1; |
|
|
|
queryParams.pageNum = 1; |
|
|
|
getList(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 重置 */ |
|
|
|
const handleReset = () => { |
|
|
|
queryParams.enterpriseName = ''; |
|
|
|
queryParams.area = undefined; |
|
|
|
queryParams.field = undefined; |
|
|
|
queryParams.street = undefined; |
|
|
|
streetOptions.value = []; |
|
|
|
periodType.value = 'month'; |
|
|
|
periodDate.value = '2026-09'; |
|
|
|
currentPage.value = 1; |
|
|
|
pageSize.value = 20; |
|
|
|
queryParams.district = undefined; |
|
|
|
queryParams.domainName = undefined; |
|
|
|
queryParams.year = String(new Date().getFullYear()); |
|
|
|
queryParams.month = `${String(new Date().getFullYear())}-${String(new Date().getMonth() + 1).padStart(2, '0')}`; |
|
|
|
queryParams.pageNum = 1; |
|
|
|
queryParams.pageSize = 20; |
|
|
|
getList(); |
|
|
|
}; |
|
|
|
|
|
|
|
@ -158,6 +120,11 @@ const handleExport = () => { |
|
|
|
// TODO: 接入导出接口后实现文件下载 |
|
|
|
}; |
|
|
|
|
|
|
|
/** 多选 */ |
|
|
|
const handleSelectionChange = (selection: HighFrequencyRow[]) => { |
|
|
|
selectedRows.value = selection; |
|
|
|
}; |
|
|
|
|
|
|
|
// ======================== 详情弹窗 ======================== |
|
|
|
interface EnforcementDetailRow { |
|
|
|
id: number; |
|
|
|
@ -176,54 +143,17 @@ const detailTotal = ref(0); |
|
|
|
const detailPage = ref(1); |
|
|
|
const detailPageSize = ref(20); |
|
|
|
|
|
|
|
/** 获取详情 mock 数据 */ |
|
|
|
const buildDetailMockRows = (enterprise: HighFrequencyRow, periodDate: string): EnforcementDetailRow[] => { |
|
|
|
const rows: EnforcementDetailRow[] = []; |
|
|
|
const enforcerPool = ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十']; |
|
|
|
const bureauPool = [ |
|
|
|
`${enterprise.areaName}市场监督管理局`, |
|
|
|
`${enterprise.areaName}市场监管局${enterprise.streetName}监管所` |
|
|
|
]; |
|
|
|
const categoryPool = ['行政检查', '行政处罚', '专项整治', '日常监管', '投诉举报']; |
|
|
|
const itemPool = ['特种设备安全检查', '产品质量抽查', '计量器具检定', '食品安全监督抽检', '药品经营许可检查']; |
|
|
|
|
|
|
|
const count = enterprise.enforcementCount ?? 5; |
|
|
|
for (let i = 1; i <= Math.min(count, 12); i++) { |
|
|
|
const hour = String(8 + (i % 11)).padStart(2, '0'); |
|
|
|
const minute = String((i * 17) % 60).padStart(2, '0'); |
|
|
|
rows.push({ |
|
|
|
id: i, |
|
|
|
enforcementTime: `${periodDate}-${String(i % 28 + 1).padStart(2, '0')} ${hour}:${minute}:00`, |
|
|
|
bureauDeptName: bureauPool[i % bureauPool.length], |
|
|
|
categoryText: categoryPool[i % categoryPool.length], |
|
|
|
lawEnforcer: enforcerPool[i % enforcerPool.length], |
|
|
|
itemText: itemPool[i % itemPool.length] |
|
|
|
}); |
|
|
|
} |
|
|
|
return rows; |
|
|
|
}; |
|
|
|
|
|
|
|
const getDetailList = async () => { |
|
|
|
detailLoading.value = true; |
|
|
|
try { |
|
|
|
if (!detailEnterprise.value) return; |
|
|
|
// TODO: 接入真实接口后替换为接口返回数据 |
|
|
|
// const res = await listHighFrequencyDetail({ |
|
|
|
// enterpriseId: detailEnterprise.value.id, |
|
|
|
// enterpriseName: detailEnterprise.value.enterpriseName, |
|
|
|
// periodType: periodType.value, |
|
|
|
// periodDate: periodDate.value, |
|
|
|
// pageNum: detailPage.value, |
|
|
|
// pageSize: detailPageSize.value |
|
|
|
// }); |
|
|
|
detailTableData.value = buildDetailMockRows(detailEnterprise.value, periodDate.value); |
|
|
|
detailTotal.value = detailEnterprise.value.enforcementCount ?? 0; |
|
|
|
// TODO: 接入真实详情接口 listHighFrequencyDetail |
|
|
|
} finally { |
|
|
|
detailLoading.value = false; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** 查看详情 — 打开弹窗并加载当前时段的执法明细 */ |
|
|
|
/** 查看详情 */ |
|
|
|
const handleDetail = (row: HighFrequencyRow) => { |
|
|
|
if (!row.enterpriseName) return; |
|
|
|
detailEnterprise.value = row; |
|
|
|
@ -243,24 +173,6 @@ const handleDetailPageChange = (page: number) => { |
|
|
|
getDetailList(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 多选 */ |
|
|
|
const handleSelectionChange = (selection: HighFrequencyRow[]) => { |
|
|
|
selectedRows.value = selection; |
|
|
|
}; |
|
|
|
|
|
|
|
/** 切换每页条数 */ |
|
|
|
const handleSizeChange = (size: number) => { |
|
|
|
pageSize.value = size; |
|
|
|
currentPage.value = 1; |
|
|
|
getList(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 翻页 */ |
|
|
|
const handlePageChange = (page: number) => { |
|
|
|
currentPage.value = page; |
|
|
|
getList(); |
|
|
|
}; |
|
|
|
|
|
|
|
onMounted(() => { |
|
|
|
loadAreaDict(); |
|
|
|
getList(); |
|
|
|
@ -271,6 +183,8 @@ onMounted(() => { |
|
|
|
onUnmounted(() => { |
|
|
|
window.removeEventListener('resize', calcTableHeight); |
|
|
|
}); |
|
|
|
|
|
|
|
defineExpose({ calcTableHeight }); |
|
|
|
</script> |
|
|
|
|
|
|
|
<template> |
|
|
|
@ -278,58 +192,38 @@ onUnmounted(() => { |
|
|
|
<!-- ==================== 搜索区域 ==================== --> |
|
|
|
<div class="search-panel"> |
|
|
|
<el-form :inline="true" label-colon> |
|
|
|
<el-form-item label="企业名称"> |
|
|
|
<el-input |
|
|
|
v-model="queryParams.enterpriseName" |
|
|
|
placeholder="请输入关键字" |
|
|
|
class="search-input" |
|
|
|
clearable |
|
|
|
@keyup.enter="handleSearch" |
|
|
|
/> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item v-if="dimension === 'area'" label="所属区"> |
|
|
|
<el-select |
|
|
|
v-model="queryParams.area" |
|
|
|
v-model="queryParams.district" |
|
|
|
placeholder="请选择" |
|
|
|
class="search-select" |
|
|
|
clearable |
|
|
|
@change="handleAreaChange" |
|
|
|
> |
|
|
|
<el-option v-for="item in areaOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" /> |
|
|
|
<el-option v-for="item in areaOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictLabel" /> |
|
|
|
</el-select> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item v-else label="所属领域"> |
|
|
|
<el-select |
|
|
|
v-model="queryParams.field" |
|
|
|
placeholder="请选择" |
|
|
|
class="search-select" |
|
|
|
clearable |
|
|
|
> |
|
|
|
<el-option v-for="item in fieldOptions" :key="item.value" :label="item.label" :value="item.value" /> |
|
|
|
</el-select> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item v-if="dimension === 'area'" label="所属街道"> |
|
|
|
<el-select |
|
|
|
v-model="queryParams.street" |
|
|
|
v-model="queryParams.domainName" |
|
|
|
placeholder="请选择" |
|
|
|
class="search-select" |
|
|
|
clearable |
|
|
|
> |
|
|
|
<el-option v-for="item in streetOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" /> |
|
|
|
<el-option v-for="item in fieldOptions" :key="item.value" :label="item.label" :value="item.label" /> |
|
|
|
</el-select> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item label="时段"> |
|
|
|
<el-select v-model="periodType" class="period-select"> |
|
|
|
<el-option v-for="item in periodOptions" :key="item.value" :label="item.label" :value="item.value" /> |
|
|
|
<el-form-item label="年份"> |
|
|
|
<el-select v-model="queryParams.year" class="period-select"> |
|
|
|
<el-option v-for="y in yearOptions" :key="y" :label="y + '年'" :value="y" /> |
|
|
|
</el-select> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item> |
|
|
|
<el-form-item label="月份"> |
|
|
|
<el-date-picker |
|
|
|
v-model="periodDate" |
|
|
|
:type="pickerType" |
|
|
|
:format="pickerFormat" |
|
|
|
:value-format="pickerFormat" |
|
|
|
v-model="queryParams.month" |
|
|
|
type="month" |
|
|
|
value-format="YYYY-MM" |
|
|
|
class="period-date" |
|
|
|
placeholder="选择月份" |
|
|
|
/> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item> |
|
|
|
@ -340,7 +234,7 @@ onUnmounted(() => { |
|
|
|
</el-form> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- ==================== 表格区域(表格样式对齐 /statistics/administrative/enterprise-enforcement) ==================== --> |
|
|
|
<!-- ==================== 表格区域 ==================== --> |
|
|
|
<div class="table-panel"> |
|
|
|
<!-- 当前预警数 --> |
|
|
|
<div class="warning-summary">当前预警数:{{ warningCount }}条</div> |
|
|
|
@ -356,20 +250,16 @@ onUnmounted(() => { |
|
|
|
@selection-change="handleSelectionChange" |
|
|
|
> |
|
|
|
<el-table-column type="selection" width="55" align="center" /> |
|
|
|
<el-table-column label="序号" width="90" align="center"> |
|
|
|
<template #default="{ $index }"> |
|
|
|
{{ tableData[$index].enterpriseName ? $index + 1 : '' }} |
|
|
|
</template> |
|
|
|
</el-table-column> |
|
|
|
<el-table-column label="序号" width="90" align="center" type="index" /> |
|
|
|
<el-table-column prop="enterpriseName" label="企业名称" min-width="300" align="center" /> |
|
|
|
<el-table-column prop="areaName" label="所属区" width="160" align="center" /> |
|
|
|
<el-table-column prop="streetName" label="所属街道" width="160" align="center" /> |
|
|
|
<el-table-column label="行政执法次数" width="180" align="center"> |
|
|
|
<template #default="{ row }"> |
|
|
|
<span v-if="row.enforcementCount !== null">{{ row.enforcementCount }}次</span> |
|
|
|
<span v-if="row.enforcementCount !== null && row.enforcementCount !== undefined">{{ row.enforcementCount }}次</span> |
|
|
|
</template> |
|
|
|
</el-table-column> |
|
|
|
<el-table-column label="操作" width="80" align="center"> |
|
|
|
<el-table-column label="操作" width="80" align="center" fixed="right"> |
|
|
|
<template #default="{ row }"> |
|
|
|
<el-button |
|
|
|
v-if="row.enterpriseName" |
|
|
|
@ -387,14 +277,14 @@ onUnmounted(() => { |
|
|
|
|
|
|
|
<!-- ==================== 分页 ==================== --> |
|
|
|
<el-pagination |
|
|
|
v-model:current-page="currentPage" |
|
|
|
v-model:page-size="pageSize" |
|
|
|
v-model:current-page="queryParams.pageNum" |
|
|
|
v-model:page-size="queryParams.pageSize" |
|
|
|
:page-sizes="[10, 20, 50, 100]" |
|
|
|
:total="total" |
|
|
|
layout="total, sizes, prev, pager, next, jumper" |
|
|
|
class="mt-4" |
|
|
|
@size-change="handleSizeChange" |
|
|
|
@current-change="handlePageChange" |
|
|
|
@size-change="getList" |
|
|
|
@current-change="getList" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
|
|
|
|
@ -402,7 +292,7 @@ onUnmounted(() => { |
|
|
|
<el-dialog |
|
|
|
v-model="detailDialogVisible" |
|
|
|
width="1100px" |
|
|
|
:title="`${detailEnterprise?.enterpriseName || ''} — ${periodDate} 高频检查明细`" |
|
|
|
:title="`${detailEnterprise?.enterpriseName || ''} — ${queryParams.year}年${queryParams.month} 高频检查明细`" |
|
|
|
append-to-body |
|
|
|
top="5vh" |
|
|
|
destroy-on-close |
|
|
|
@ -412,7 +302,7 @@ onUnmounted(() => { |
|
|
|
:closable="false" |
|
|
|
show-icon |
|
|
|
type="warning" |
|
|
|
:title="`预警企业 | 所属区:${detailEnterprise.areaName} | 所属街道:${detailEnterprise.streetName} | 行政执法次数:${detailEnterprise.enforcementCount ?? 0}次 | 时段:${periodType} / ${periodDate}`" |
|
|
|
:title="`预警企业 | 所属区:${detailEnterprise.areaName || '-'} | 所属街道:${detailEnterprise.streetName || '-'} | 行政执法次数:${detailEnterprise.enforcementCount ?? 0}次 | 时段:${queryParams.year}年${queryParams.month}`" |
|
|
|
class="mb-4" |
|
|
|
/> |
|
|
|
|
|
|
|
@ -464,10 +354,6 @@ onUnmounted(() => { |
|
|
|
color: #333; |
|
|
|
} |
|
|
|
|
|
|
|
.search-input { |
|
|
|
width: 210px; |
|
|
|
} |
|
|
|
|
|
|
|
.search-select { |
|
|
|
width: 220px; |
|
|
|
} |
|
|
|
@ -482,9 +368,6 @@ onUnmounted(() => { |
|
|
|
} |
|
|
|
|
|
|
|
/* ==================== 表格面板 ==================== */ |
|
|
|
/* el-table 不做任何自定义皮肤覆盖,与 |
|
|
|
efcode/report/registration/indexList.vue(/statistics/administrative/enterprise-enforcement) |
|
|
|
保持 100% 一致:原生 border + stripe + 动态高度 */ |
|
|
|
.table-panel { |
|
|
|
background: #fff; |
|
|
|
border: 1px solid #e4e7ed; |
|
|
|
@ -501,12 +384,9 @@ onUnmounted(() => { |
|
|
|
color: #303133; |
|
|
|
} |
|
|
|
|
|
|
|
/* ==================== 分页(元素合在一起靠右,与 key-enterprise 页一致) ==================== */ |
|
|
|
/* ==================== 分页 ==================== */ |
|
|
|
.mt-4 { |
|
|
|
margin-top: 16px; |
|
|
|
justify-content: flex-end; |
|
|
|
} |
|
|
|
.mb-4 { |
|
|
|
margin-bottom: 16px; |
|
|
|
} |
|
|
|
</style> |
|
|
|
|