8 changed files with 1406 additions and 501 deletions
@ -0,0 +1,512 @@ |
|||
<script setup lang="ts"> |
|||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; |
|||
import { getDicts } from '@/api/system/dict/data'; |
|||
|
|||
/** 维度:area=各区域 field=各领域 */ |
|||
const props = defineProps<{ |
|||
dimension: 'area' | 'field'; |
|||
}>(); |
|||
|
|||
/** 高频检查预警企业行数据 */ |
|||
interface HighFrequencyRow { |
|||
id: number; |
|||
enterpriseName: string; |
|||
areaName: string; |
|||
streetName: string; |
|||
enforcementCount: number | null; |
|||
} |
|||
|
|||
// ======================== 查询条件 ======================== |
|||
const queryParams = reactive({ |
|||
enterpriseName: '' as string, |
|||
area: undefined as string | undefined, |
|||
field: undefined as string | undefined, |
|||
street: undefined as string | undefined |
|||
}); |
|||
|
|||
// 时段类型: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'; |
|||
}); |
|||
|
|||
// ======================== 字典 ======================== |
|||
const areaOptions = ref<any[]>([]); |
|||
const streetOptions = ref<any[]>([]); |
|||
const fieldOptions = [ |
|||
{ label: '食品安全', value: 'food' }, |
|||
{ label: '药品安全', value: 'drug' }, |
|||
{ label: '特种设备', value: 'special_equipment' }, |
|||
{ label: '产品质量', value: 'product' }, |
|||
{ label: '计量', value: 'metrology' }, |
|||
{ label: '认证认可', value: 'certification' } |
|||
]; |
|||
|
|||
const loadAreaDict = async () => { |
|||
try { |
|||
const res = await getDicts('address_type'); |
|||
areaOptions.value = res.data || []; |
|||
} catch (e) { |
|||
console.error('加载所属区字典失败', e); |
|||
} |
|||
}; |
|||
|
|||
/** 切换所属区时联动加载所属街道 */ |
|||
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; |
|||
} finally { |
|||
loading.value = false; |
|||
} |
|||
}; |
|||
|
|||
/** 搜索 */ |
|||
const handleSearch = () => { |
|||
currentPage.value = 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; |
|||
getList(); |
|||
}; |
|||
|
|||
/** 导出 */ |
|||
const handleExport = () => { |
|||
// TODO: 接入导出接口后实现文件下载 |
|||
}; |
|||
|
|||
// ======================== 详情弹窗 ======================== |
|||
interface EnforcementDetailRow { |
|||
id: number; |
|||
enforcementTime: string; |
|||
bureauDeptName: string; |
|||
categoryText: string; |
|||
lawEnforcer: string; |
|||
itemText: string; |
|||
} |
|||
|
|||
const detailDialogVisible = ref(false); |
|||
const detailEnterprise = ref<HighFrequencyRow | null>(null); |
|||
const detailLoading = ref(false); |
|||
const detailTableData = ref<EnforcementDetailRow[]>([]); |
|||
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; |
|||
} finally { |
|||
detailLoading.value = false; |
|||
} |
|||
}; |
|||
|
|||
/** 查看详情 — 打开弹窗并加载当前时段的执法明细 */ |
|||
const handleDetail = (row: HighFrequencyRow) => { |
|||
if (!row.enterpriseName) return; |
|||
detailEnterprise.value = row; |
|||
detailPage.value = 1; |
|||
detailDialogVisible.value = true; |
|||
getDetailList(); |
|||
}; |
|||
|
|||
const handleDetailSizeChange = (size: number) => { |
|||
detailPageSize.value = size; |
|||
detailPage.value = 1; |
|||
getDetailList(); |
|||
}; |
|||
|
|||
const handleDetailPageChange = (page: number) => { |
|||
detailPage.value = page; |
|||
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(); |
|||
nextTick(() => calcTableHeight()); |
|||
window.addEventListener('resize', calcTableHeight); |
|||
}); |
|||
|
|||
onUnmounted(() => { |
|||
window.removeEventListener('resize', calcTableHeight); |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="freq-pane"> |
|||
<!-- ==================== 搜索区域 ==================== --> |
|||
<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" |
|||
placeholder="请选择" |
|||
class="search-select" |
|||
clearable |
|||
@change="handleAreaChange" |
|||
> |
|||
<el-option v-for="item in areaOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" /> |
|||
</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" |
|||
placeholder="请选择" |
|||
class="search-select" |
|||
clearable |
|||
> |
|||
<el-option v-for="item in streetOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" /> |
|||
</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-select> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-date-picker |
|||
v-model="periodDate" |
|||
:type="pickerType" |
|||
:format="pickerFormat" |
|||
:value-format="pickerFormat" |
|||
class="period-date" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button type="primary" @click="handleSearch">搜索</el-button> |
|||
<el-button type="primary" @click="handleReset">重置</el-button> |
|||
<el-button @click="handleExport">导出</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
<!-- ==================== 表格区域(表格样式对齐 /statistics/administrative/enterprise-enforcement) ==================== --> |
|||
<div class="table-panel"> |
|||
<!-- 当前预警数 --> |
|||
<div class="warning-summary">当前预警数:{{ warningCount }}条</div> |
|||
|
|||
<div ref="tableRef"> |
|||
<el-table |
|||
v-loading="loading" |
|||
:data="tableData" |
|||
border |
|||
stripe |
|||
style="width: 100%" |
|||
:height="tableHeight" |
|||
@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 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> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="操作" width="80" align="center"> |
|||
<template #default="{ row }"> |
|||
<el-button |
|||
v-if="row.enterpriseName" |
|||
type="primary" |
|||
link |
|||
size="small" |
|||
@click="handleDetail(row)" |
|||
> |
|||
详情 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</div> |
|||
|
|||
<!-- ==================== 分页 ==================== --> |
|||
<el-pagination |
|||
v-model:current-page="currentPage" |
|||
v-model:page-size="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" |
|||
/> |
|||
</div> |
|||
|
|||
<!-- ==================== 企业执法详情弹窗 ==================== --> |
|||
<el-dialog |
|||
v-model="detailDialogVisible" |
|||
width="1100px" |
|||
:title="`${detailEnterprise?.enterpriseName || ''} — ${periodDate} 高频检查明细`" |
|||
append-to-body |
|||
top="5vh" |
|||
destroy-on-close |
|||
> |
|||
<el-alert |
|||
v-if="detailEnterprise" |
|||
:closable="false" |
|||
show-icon |
|||
type="warning" |
|||
:title="`预警企业 | 所属区:${detailEnterprise.areaName} | 所属街道:${detailEnterprise.streetName} | 行政执法次数:${detailEnterprise.enforcementCount ?? 0}次 | 时段:${periodType} / ${periodDate}`" |
|||
class="mb-4" |
|||
/> |
|||
|
|||
<el-table |
|||
v-loading="detailLoading" |
|||
:data="detailTableData" |
|||
border |
|||
stripe |
|||
style="width: 100%" |
|||
max-height="55vh" |
|||
> |
|||
<el-table-column label="序号" type="index" width="80" align="center" /> |
|||
<el-table-column label="执法时间" prop="enforcementTime" min-width="180" align="center" /> |
|||
<el-table-column label="执法单位" prop="bureauDeptName" min-width="260" align="center" /> |
|||
<el-table-column label="执法类别" prop="categoryText" width="140" align="center" /> |
|||
<el-table-column label="执法人员" prop="lawEnforcer" width="140" align="center" /> |
|||
<el-table-column label="执法事项" prop="itemText" min-width="220" align="center" /> |
|||
</el-table> |
|||
|
|||
<el-pagination |
|||
v-model:current-page="detailPage" |
|||
v-model:page-size="detailPageSize" |
|||
:page-sizes="[10, 20, 50, 100]" |
|||
:total="detailTotal" |
|||
layout="total, sizes, prev, pager, next, jumper" |
|||
class="mt-4" |
|||
@size-change="handleDetailSizeChange" |
|||
@current-change="handleDetailPageChange" |
|||
/> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped lang="scss"> |
|||
.freq-pane { |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* ==================== 搜索面板 ==================== */ |
|||
.search-panel { |
|||
background: #fff; |
|||
border: 1px solid #e4e7ed; |
|||
border-radius: 4px; |
|||
padding: 18px 20px 0; |
|||
margin-bottom: 16px; |
|||
|
|||
:deep(.el-form-item__label) { |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.search-input { |
|||
width: 210px; |
|||
} |
|||
|
|||
.search-select { |
|||
width: 220px; |
|||
} |
|||
|
|||
.period-select { |
|||
width: 110px; |
|||
} |
|||
|
|||
.period-date { |
|||
width: 150px; |
|||
} |
|||
} |
|||
|
|||
/* ==================== 表格面板 ==================== */ |
|||
/* el-table 不做任何自定义皮肤覆盖,与 |
|||
efcode/report/registration/indexList.vue(/statistics/administrative/enterprise-enforcement) |
|||
保持 100% 一致:原生 border + stripe + 动态高度 */ |
|||
.table-panel { |
|||
background: #fff; |
|||
border: 1px solid #e4e7ed; |
|||
border-radius: 4px; |
|||
padding: 16px 20px; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* 当前预警数 */ |
|||
.warning-summary { |
|||
margin-bottom: 12px; |
|||
font-size: 15px; |
|||
font-weight: 700; |
|||
color: #303133; |
|||
} |
|||
|
|||
/* ==================== 分页(元素合在一起靠右,与 key-enterprise 页一致) ==================== */ |
|||
.mt-4 { |
|||
margin-top: 16px; |
|||
justify-content: flex-end; |
|||
} |
|||
.mb-4 { |
|||
margin-bottom: 16px; |
|||
} |
|||
</style> |
|||
@ -1,355 +1,38 @@ |
|||
<script setup lang="ts"> |
|||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; |
|||
import { getDicts } from '@/api/system/dict/data'; |
|||
import { nextTick, ref } from 'vue'; |
|||
import FreqPane from './components/FreqPane.vue'; |
|||
|
|||
/** 高频检查预警企业行数据 */ |
|||
interface HighFrequencyRow { |
|||
id: number; |
|||
enterpriseName: string; |
|||
areaName: string; |
|||
streetName: string; |
|||
enforcementCount: number | null; |
|||
} |
|||
|
|||
// ======================== 查询条件 ======================== |
|||
const queryParams = reactive({ |
|||
enterpriseName: '' as string, |
|||
area: undefined as string | undefined, |
|||
street: undefined as string | undefined |
|||
}); |
|||
|
|||
// 时段类型: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'; |
|||
}); |
|||
|
|||
// ======================== 区域/街道字典 ======================== |
|||
const areaOptions = ref<any[]>([]); |
|||
const streetOptions = ref<any[]>([]); |
|||
|
|||
const loadAreaDict = async () => { |
|||
try { |
|||
const res = await getDicts('address_type'); |
|||
areaOptions.value = res.data || []; |
|||
} catch (e) { |
|||
console.error('加载所属区字典失败', e); |
|||
} |
|||
}; |
|||
|
|||
/** 切换所属区时联动加载所属街道 */ |
|||
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(52); // 默认页码按设计稿 |
|||
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({ |
|||
// ...queryParams, |
|||
// periodType: periodType.value, |
|||
// periodDate: periodDate.value, |
|||
// pageNum: currentPage.value |
|||
// }); |
|||
tableData.value = buildMockRows(); |
|||
total.value = 658; |
|||
warningCount.value = 20; |
|||
} finally { |
|||
loading.value = false; |
|||
} |
|||
}; |
|||
|
|||
/** 搜索 */ |
|||
const handleSearch = () => { |
|||
currentPage.value = 1; |
|||
getList(); |
|||
}; |
|||
|
|||
/** 重置 */ |
|||
const handleReset = () => { |
|||
queryParams.enterpriseName = ''; |
|||
queryParams.area = undefined; |
|||
queryParams.street = undefined; |
|||
streetOptions.value = []; |
|||
periodType.value = 'month'; |
|||
periodDate.value = '2026-09'; |
|||
currentPage.value = 1; |
|||
getList(); |
|||
}; |
|||
|
|||
/** 导出 */ |
|||
const handleExport = () => { |
|||
// TODO: 接入导出接口后实现文件下载 |
|||
}; |
|||
// area=各区域 field=各领域 |
|||
const activeTab = ref<'area' | 'field'>('area'); |
|||
|
|||
/** 查看详情 */ |
|||
const handleDetail = (_row: HighFrequencyRow) => { |
|||
// TODO: 待详情页面创建后放开路由跳转 |
|||
/** 页签切换后通知表格重新计算高度(懒加载/隐藏页签中的尺寸需要重算) */ |
|||
const handleTabChange = () => { |
|||
nextTick(() => { |
|||
window.dispatchEvent(new Event('resize')); |
|||
}); |
|||
}; |
|||
|
|||
/** 多选 */ |
|||
const handleSelectionChange = (selection: HighFrequencyRow[]) => { |
|||
selectedRows.value = selection; |
|||
}; |
|||
|
|||
/** 翻页 */ |
|||
const handlePageChange = (page: number) => { |
|||
currentPage.value = page; |
|||
getList(); |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
loadAreaDict(); |
|||
getList(); |
|||
nextTick(() => calcTableHeight()); |
|||
window.addEventListener('resize', calcTableHeight); |
|||
}); |
|||
|
|||
onUnmounted(() => { |
|||
window.removeEventListener('resize', calcTableHeight); |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="high-frequency-container"> |
|||
<!-- ==================== 搜索区域 ==================== --> |
|||
<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 label="所属区"> |
|||
<el-select |
|||
v-model="queryParams.area" |
|||
placeholder="请选择" |
|||
class="search-select" |
|||
clearable |
|||
@change="handleAreaChange" |
|||
> |
|||
<el-option v-for="item in areaOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="所属街道"> |
|||
<el-select |
|||
v-model="queryParams.street" |
|||
placeholder="请选择" |
|||
class="search-select" |
|||
clearable |
|||
> |
|||
<el-option v-for="item in streetOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" /> |
|||
</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-select> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-date-picker |
|||
v-model="periodDate" |
|||
:type="pickerType" |
|||
:format="pickerFormat" |
|||
:value-format="pickerFormat" |
|||
class="period-date" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button type="primary" @click="handleSearch">搜索</el-button> |
|||
<el-button type="primary" @click="handleReset">重置</el-button> |
|||
<el-button @click="handleExport">导出</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
<!-- ==================== 表格区域 ==================== --> |
|||
<div class="table-panel"> |
|||
<!-- 当前预警数 --> |
|||
<div class="warning-summary">当前预警数:{{ warningCount }}条</div> |
|||
|
|||
<div ref="tableRef"> |
|||
<el-table |
|||
v-loading="loading" |
|||
:data="tableData" |
|||
border |
|||
stripe |
|||
style="width: 100%" |
|||
:height="tableHeight" |
|||
@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 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> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="操作" width="80" align="center"> |
|||
<template #default="{ row }"> |
|||
<el-button |
|||
v-if="row.enterpriseName" |
|||
type="primary" |
|||
link |
|||
size="small" |
|||
@click="handleDetail(row)" |
|||
> |
|||
详情 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</div> |
|||
|
|||
<!-- ==================== 分页 ==================== --> |
|||
<div class="pagination-bar"> |
|||
<span class="total-text">共 {{ total }} 项数据</span> |
|||
<el-pagination |
|||
:current-page="currentPage" |
|||
:page-size="10" |
|||
:total="total" |
|||
background |
|||
layout="prev, pager, next" |
|||
@current-change="handlePageChange" |
|||
/> |
|||
</div> |
|||
</div> |
|||
<div class="app-container"> |
|||
<el-tabs v-model="activeTab" type="border-card" @tab-change="handleTabChange"> |
|||
<el-tab-pane label="各区域" name="area" lazy> |
|||
<FreqPane dimension="area" /> |
|||
</el-tab-pane> |
|||
<el-tab-pane label="各领域" name="field" lazy> |
|||
<FreqPane dimension="field" /> |
|||
</el-tab-pane> |
|||
</el-tabs> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped lang="scss"> |
|||
.high-frequency-container { |
|||
.app-container { |
|||
padding: 16px; |
|||
background: #f5f7fa; |
|||
min-height: calc(100vh - 84px); |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* ==================== 搜索面板 ==================== */ |
|||
.search-panel { |
|||
background: #fff; |
|||
border: 1px solid #e4e7ed; |
|||
border-radius: 4px; |
|||
padding: 18px 20px 0; |
|||
margin-bottom: 16px; |
|||
|
|||
:deep(.el-form-item__label) { |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.search-input { |
|||
width: 210px; |
|||
} |
|||
|
|||
.search-select { |
|||
width: 220px; |
|||
} |
|||
|
|||
.period-select { |
|||
width: 110px; |
|||
} |
|||
|
|||
.period-date { |
|||
width: 150px; |
|||
} |
|||
} |
|||
|
|||
/* ==================== 表格面板 ==================== */ |
|||
.table-panel { |
|||
background: #fff; |
|||
border: 1px solid #e4e7ed; |
|||
border-radius: 4px; |
|||
padding: 16px 20px; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* 当前预警数 */ |
|||
.warning-summary { |
|||
margin-bottom: 12px; |
|||
font-size: 15px; |
|||
font-weight: 700; |
|||
color: #303133; |
|||
} |
|||
|
|||
/* ==================== 分页 ==================== */ |
|||
.pagination-bar { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-top: 16px; |
|||
} |
|||
|
|||
.total-text { |
|||
color: #909399; |
|||
font-size: 14px; |
|||
/* 卡片式页签(与 efcode/report/registration/index.vue、inspection-registration/index.vue 保持一致) */ |
|||
:deep(.el-tabs--border-card) { |
|||
box-shadow: none; |
|||
} |
|||
</style> |
|||
|
|||
@ -0,0 +1,333 @@ |
|||
<script setup lang="ts"> |
|||
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; |
|||
import { getDicts } from '@/api/system/dict/data'; |
|||
|
|||
/** 维度:area=各区域 field=各领域 */ |
|||
const props = defineProps<{ |
|||
dimension: 'area' | 'field'; |
|||
}>(); |
|||
|
|||
/** 态势趋势综合评分行 */ |
|||
interface SituationRow { |
|||
id: number; |
|||
dimensionName: string; |
|||
yearQuarter: string; // 如 2026Q3 |
|||
score: string | null; // 优良 / 良好 / 一般 |
|||
} |
|||
|
|||
// ======================== 查询条件 ======================== |
|||
const queryParams = reactive({ |
|||
area: undefined as string | undefined, |
|||
field: undefined as string | undefined |
|||
}); |
|||
|
|||
// 时段类型:week=周度 month=月度 quarter=季度 year=年度 |
|||
const periodType = ref('quarter'); |
|||
const periodOptions = [ |
|||
// { label: '周度', value: 'week' }, |
|||
// { label: '月度', value: 'month' }, |
|||
{ label: '季度', value: 'quarter' }, |
|||
// { label: '年度', value: 'year' } |
|||
]; |
|||
|
|||
// 年份(type="year" 时 el-date-picker 返回 'YYYY' 格式字符串) |
|||
const periodYear = ref<string>('2026'); |
|||
// 季度(1/2/3/4),仅 periodType === 'quarter' 时显示 |
|||
const periodQuarter = ref<string>('3'); |
|||
const quarterOptions = [ |
|||
{ label: '第一季度', value: '1' }, |
|||
{ label: '第二季度', value: '2' }, |
|||
{ label: '第三季度', value: '3' }, |
|||
{ label: '第四季度', value: '4' } |
|||
]; |
|||
|
|||
// ======================== 字典 ======================== |
|||
const areaOptions = ref<any[]>([]); |
|||
const fieldOptions = [ |
|||
{ label: '食品安全', value: 'food' }, |
|||
{ label: '药品安全', value: 'drug' }, |
|||
{ label: '特种设备', value: 'special_equipment' }, |
|||
{ label: '产品质量', value: 'product' }, |
|||
{ label: '计量', value: 'metrology' }, |
|||
{ label: '认证认可', value: 'certification' } |
|||
]; |
|||
|
|||
const loadAreaDict = async () => { |
|||
try { |
|||
const res = await getDicts('address_type'); |
|||
areaOptions.value = res.data || []; |
|||
} catch (e) { |
|||
console.error('加载所属区字典失败', e); |
|||
} |
|||
}; |
|||
|
|||
// ======================== 表格数据 ======================== |
|||
const loading = ref(false); |
|||
const tableData = ref<SituationRow[]>([]); |
|||
const total = ref(0); |
|||
const currentPage = ref(1); |
|||
const pageSize = ref(20); |
|||
const selectedRows = ref<SituationRow[]>([]); |
|||
|
|||
// 表格高度(与 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 = (): SituationRow[] => { |
|||
const isArea = props.dimension === 'area'; |
|||
const yearQuarter = `${periodYear.value}Q${periodQuarter.value}`; |
|||
const rows: SituationRow[] = [ |
|||
{ |
|||
id: 1, |
|||
dimensionName: isArea ? '宝安区' : '食品安全', |
|||
yearQuarter, |
|||
score: '优良' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
dimensionName: isArea ? '龙华区' : '特种设备', |
|||
yearQuarter, |
|||
score: '良好' |
|||
} |
|||
]; |
|||
for (let i = 3; i <= 7; i++) { |
|||
rows.push({ id: i, dimensionName: '', yearQuarter: '', score: null }); |
|||
} |
|||
return rows; |
|||
}; |
|||
|
|||
const getList = async () => { |
|||
loading.value = true; |
|||
try { |
|||
// TODO: 接入真实接口后替换为接口返回数据 |
|||
// const res = await listSituationTrend({ |
|||
// dimension: props.dimension, |
|||
// ...queryParams, |
|||
// periodType: periodType.value, |
|||
// periodYear: periodYear.value, |
|||
// periodQuarter: periodQuarter.value, |
|||
// pageNum: currentPage.value, |
|||
// pageSize: pageSize.value |
|||
// }); |
|||
tableData.value = buildMockRows(); |
|||
total.value = 658; |
|||
} finally { |
|||
loading.value = false; |
|||
} |
|||
}; |
|||
|
|||
/** 搜索 */ |
|||
const handleSearch = () => { |
|||
currentPage.value = 1; |
|||
getList(); |
|||
}; |
|||
|
|||
/** 重置 */ |
|||
const handleReset = () => { |
|||
queryParams.area = undefined; |
|||
queryParams.field = undefined; |
|||
periodType.value = 'quarter'; |
|||
periodYear.value = '2026'; |
|||
periodQuarter.value = '3'; |
|||
currentPage.value = 1; |
|||
pageSize.value = 20; |
|||
getList(); |
|||
}; |
|||
|
|||
/** 导出 */ |
|||
const handleExport = () => { |
|||
// TODO: 接入导出接口后实现文件下载 |
|||
}; |
|||
|
|||
/** 多选 */ |
|||
const handleSelectionChange = (selection: SituationRow[]) => { |
|||
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(); |
|||
nextTick(() => calcTableHeight()); |
|||
window.addEventListener('resize', calcTableHeight); |
|||
}); |
|||
|
|||
onUnmounted(() => { |
|||
window.removeEventListener('resize', calcTableHeight); |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="situation-pane"> |
|||
<!-- ==================== 搜索区域 ==================== --> |
|||
<div class="search-panel"> |
|||
<el-form :inline="true" label-colon> |
|||
<el-form-item v-if="dimension === 'area'" label="所属区"> |
|||
<el-select |
|||
v-model="queryParams.area" |
|||
placeholder="请选择" |
|||
class="search-select" |
|||
clearable |
|||
> |
|||
<el-option v-for="item in areaOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" /> |
|||
</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 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-select> |
|||
</el-form-item> |
|||
<el-form-item label="年"> |
|||
<el-date-picker |
|||
v-model="periodYear" |
|||
type="year" |
|||
value-format="YYYY" |
|||
format="YYYY" |
|||
class="year-picker" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item v-if="periodType === 'quarter'" label="季度"> |
|||
<el-select v-model="periodQuarter" class="quarter-select"> |
|||
<el-option v-for="item in quarterOptions" :key="item.value" :label="item.label" :value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button type="primary" @click="handleSearch">搜索</el-button> |
|||
<el-button type="primary" @click="handleReset">重置</el-button> |
|||
<el-button @click="handleExport">导出</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
<!-- ==================== 表格区域 ==================== --> |
|||
<div class="table-panel"> |
|||
<div ref="tableRef"> |
|||
<el-table |
|||
v-loading="loading" |
|||
:data="tableData" |
|||
border |
|||
stripe |
|||
style="width: 100%" |
|||
:height="tableHeight" |
|||
@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].dimensionName ? $index + 1 : '' }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
prop="dimensionName" |
|||
:label="dimension === 'area' ? '所属区' : '所属领域'" |
|||
min-width="220" |
|||
align="center" |
|||
/> |
|||
<el-table-column label="年+季度" width="160" align="center"> |
|||
<template #default="{ row }"> |
|||
{{ row.yearQuarter }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="行政执法综合评分" min-width="180" align="center"> |
|||
<template #default="{ row }"> |
|||
<span v-if="row.score !== null">{{ row.score }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</div> |
|||
|
|||
<!-- ==================== 分页(统一紧凑右对齐) ==================== --> |
|||
<el-pagination |
|||
v-model:current-page="currentPage" |
|||
v-model:page-size="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" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped lang="scss"> |
|||
.situation-pane { |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* ==================== 搜索面板 ==================== */ |
|||
.search-panel { |
|||
background: #fff; |
|||
border: 1px solid #e4e7ed; |
|||
border-radius: 4px; |
|||
padding: 18px 20px 0; |
|||
margin-bottom: 16px; |
|||
|
|||
:deep(.el-form-item__label) { |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.search-select { |
|||
width: 220px; |
|||
} |
|||
|
|||
.period-select { |
|||
width: 110px; |
|||
} |
|||
|
|||
.year-picker { |
|||
width: 130px; |
|||
} |
|||
|
|||
.quarter-select { |
|||
width: 150px; |
|||
} |
|||
} |
|||
|
|||
/* ==================== 表格面板 ==================== */ |
|||
.table-panel { |
|||
background: #fff; |
|||
border: 1px solid #e4e7ed; |
|||
border-radius: 4px; |
|||
padding: 16px 20px; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* ==================== 分页(元素合在一起靠右,与其他 trend 页一致) ==================== */ |
|||
.mt-4 { |
|||
margin-top: 16px; |
|||
justify-content: flex-end; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,339 @@ |
|||
<script setup lang="ts"> |
|||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; |
|||
import { getDicts } from '@/api/system/dict/data'; |
|||
|
|||
/** 维度:area=各区域 field=各领域 */ |
|||
const props = defineProps<{ |
|||
dimension: 'area' | 'field'; |
|||
}>(); |
|||
|
|||
/** 趋势行为监测行数据 */ |
|||
interface TrendBehaviorRow { |
|||
id: number; |
|||
dimensionName: string; |
|||
yearQuarter: string; // 如 2026Q3 |
|||
enforcementCount: number | null; |
|||
} |
|||
|
|||
// ======================== 查询条件 ======================== |
|||
const queryParams = reactive({ |
|||
area: undefined as string | undefined, |
|||
field: undefined as string | undefined |
|||
}); |
|||
|
|||
// 时段类型:week=周度 month=月度 quarter=季度 year=年度 |
|||
const periodType = ref('quarter'); |
|||
const periodOptions = [ |
|||
// { label: '周度', value: 'week' }, |
|||
// { label: '月度', value: 'month' }, |
|||
{ label: '季度', value: 'quarter' }, |
|||
// { label: '年度', value: 'year' } |
|||
]; |
|||
|
|||
// 年份(type="year" 时 el-date-picker 返回 'YYYY' 格式字符串) |
|||
const periodYear = ref<string>('2026'); |
|||
// 季度(1/2/3/4),仅 periodType === 'quarter' 时显示 |
|||
const periodQuarter = ref<string>('3'); |
|||
const quarterOptions = [ |
|||
{ label: '第一季度', value: '1' }, |
|||
{ label: '第二季度', value: '2' }, |
|||
{ label: '第三季度', value: '3' }, |
|||
{ label: '第四季度', value: '4' } |
|||
]; |
|||
|
|||
// ======================== 字典 ======================== |
|||
const areaOptions = ref<any[]>([]); |
|||
const fieldOptions = [ |
|||
{ label: '食品安全', value: 'food' }, |
|||
{ label: '药品安全', value: 'drug' }, |
|||
{ label: '特种设备', value: 'special_equipment' }, |
|||
{ label: '产品质量', value: 'product' }, |
|||
{ label: '计量', value: 'metrology' }, |
|||
{ label: '认证认可', value: 'certification' } |
|||
]; |
|||
|
|||
const loadAreaDict = async () => { |
|||
try { |
|||
const res = await getDicts('address_type'); |
|||
areaOptions.value = res.data || []; |
|||
} catch (e) { |
|||
console.error('加载所属区字典失败', e); |
|||
} |
|||
}; |
|||
|
|||
// ======================== 表格数据 ======================== |
|||
const loading = ref(false); |
|||
const tableData = ref<TrendBehaviorRow[]>([]); |
|||
const total = ref(0); |
|||
const currentPage = ref(1); |
|||
const pageSize = ref(20); |
|||
const selectedRows = ref<TrendBehaviorRow[]>([]); |
|||
|
|||
// 表格高度(与 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 = (): TrendBehaviorRow[] => { |
|||
const isArea = props.dimension === 'area'; |
|||
const yearQuarter = `${periodYear.value}Q${periodQuarter.value}`; |
|||
const rows: TrendBehaviorRow[] = [ |
|||
{ |
|||
id: 1, |
|||
dimensionName: isArea ? '宝安区' : '食品安全', |
|||
yearQuarter, |
|||
enforcementCount: 998 |
|||
}, |
|||
{ |
|||
id: 2, |
|||
dimensionName: isArea ? '龙华区' : '特种设备', |
|||
yearQuarter, |
|||
enforcementCount: 990 |
|||
} |
|||
]; |
|||
for (let i = 3; i <= 7; i++) { |
|||
rows.push({ id: i, dimensionName: '', yearQuarter: '', enforcementCount: null }); |
|||
} |
|||
return rows; |
|||
}; |
|||
|
|||
const getList = async () => { |
|||
loading.value = true; |
|||
try { |
|||
// TODO: 接入真实接口后替换为接口返回数据 |
|||
// const res = await listTrendBehavior({ |
|||
// dimension: props.dimension, |
|||
// ...queryParams, |
|||
// periodType: periodType.value, |
|||
// periodYear: periodYear.value, |
|||
// periodQuarter: periodQuarter.value, |
|||
// pageNum: currentPage.value, |
|||
// pageSize: pageSize.value |
|||
// }); |
|||
tableData.value = buildMockRows(); |
|||
total.value = 658; |
|||
} finally { |
|||
loading.value = false; |
|||
} |
|||
}; |
|||
|
|||
/** 搜索 */ |
|||
const handleSearch = () => { |
|||
currentPage.value = 1; |
|||
getList(); |
|||
}; |
|||
|
|||
/** 重置 */ |
|||
const handleReset = () => { |
|||
queryParams.area = undefined; |
|||
queryParams.field = undefined; |
|||
periodType.value = 'quarter'; |
|||
periodYear.value = '2026'; |
|||
periodQuarter.value = '3'; |
|||
currentPage.value = 1; |
|||
pageSize.value = 20; |
|||
getList(); |
|||
}; |
|||
|
|||
/** 导出 */ |
|||
const handleExport = () => { |
|||
// TODO: 接入导出接口后实现文件下载 |
|||
}; |
|||
|
|||
/** 多选 */ |
|||
const handleSelectionChange = (selection: TrendBehaviorRow[]) => { |
|||
selectedRows.value = selection; |
|||
}; |
|||
|
|||
/** 切换每页条数 */ |
|||
const handleSizeChange = (size: number) => { |
|||
pageSize.value = size; |
|||
currentPage.value = 1; |
|||
getList(); |
|||
}; |
|||
|
|||
/** 翻页 */ |
|||
const handlePageChange = (page: number) => { |
|||
currentPage.value = page; |
|||
getList(); |
|||
}; |
|||
|
|||
/** 年+季度列显示(为空时隐藏) */ |
|||
const formatYearQuarter = (row: TrendBehaviorRow) => { |
|||
if (!row.yearQuarter) return ''; |
|||
return row.yearQuarter; |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
loadAreaDict(); |
|||
getList(); |
|||
nextTick(() => calcTableHeight()); |
|||
window.addEventListener('resize', calcTableHeight); |
|||
}); |
|||
|
|||
onUnmounted(() => { |
|||
window.removeEventListener('resize', calcTableHeight); |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="trend-pane"> |
|||
<!-- ==================== 搜索区域 ==================== --> |
|||
<div class="search-panel"> |
|||
<el-form :inline="true" label-colon> |
|||
<el-form-item v-if="dimension === 'area'" label="所属区"> |
|||
<el-select |
|||
v-model="queryParams.area" |
|||
placeholder="请选择" |
|||
class="search-select" |
|||
clearable |
|||
> |
|||
<el-option v-for="item in areaOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" /> |
|||
</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 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-select> |
|||
</el-form-item> |
|||
<el-form-item label="年"> |
|||
<el-date-picker |
|||
v-model="periodYear" |
|||
type="year" |
|||
value-format="YYYY" |
|||
format="YYYY" |
|||
class="year-picker" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item v-if="periodType === 'quarter'" label="季度"> |
|||
<el-select v-model="periodQuarter" class="quarter-select"> |
|||
<el-option v-for="item in quarterOptions" :key="item.value" :label="item.label" :value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button type="primary" @click="handleSearch">搜索</el-button> |
|||
<el-button type="primary" @click="handleReset">重置</el-button> |
|||
<el-button @click="handleExport">导出</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
<!-- ==================== 表格区域 ==================== --> |
|||
<div class="table-panel"> |
|||
<div ref="tableRef"> |
|||
<el-table |
|||
v-loading="loading" |
|||
:data="tableData" |
|||
border |
|||
stripe |
|||
style="width: 100%" |
|||
:height="tableHeight" |
|||
@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].dimensionName ? $index + 1 : '' }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
prop="dimensionName" |
|||
:label="dimension === 'area' ? '所属区' : '所属领域'" |
|||
min-width="220" |
|||
align="center" |
|||
/> |
|||
<el-table-column label="年+季度" width="160" align="center"> |
|||
<template #default="{ row }"> |
|||
{{ formatYearQuarter(row) }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="行政执法次数" width="180" align="center"> |
|||
<template #default="{ row }"> |
|||
<span v-if="row.enforcementCount !== null">{{ row.enforcementCount }}次</span> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</div> |
|||
|
|||
<!-- ==================== 分页(与 key-enterprise / StatPane / FreqPane 完全一致) ==================== --> |
|||
<el-pagination |
|||
v-model:current-page="currentPage" |
|||
v-model:page-size="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" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped lang="scss"> |
|||
.trend-pane { |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* ==================== 搜索面板 ==================== */ |
|||
.search-panel { |
|||
background: #fff; |
|||
border: 1px solid #e4e7ed; |
|||
border-radius: 4px; |
|||
padding: 18px 20px 0; |
|||
margin-bottom: 16px; |
|||
|
|||
:deep(.el-form-item__label) { |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.search-select { |
|||
width: 220px; |
|||
} |
|||
|
|||
.period-select { |
|||
width: 110px; |
|||
} |
|||
|
|||
.year-picker { |
|||
width: 130px; |
|||
} |
|||
|
|||
.quarter-select { |
|||
width: 150px; |
|||
} |
|||
} |
|||
|
|||
/* ==================== 表格面板 ==================== */ |
|||
.table-panel { |
|||
background: #fff; |
|||
border: 1px solid #e4e7ed; |
|||
border-radius: 4px; |
|||
padding: 16px 20px; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* ==================== 分页(元素合在一起靠右,与其他 trend 页一致) ==================== */ |
|||
.mt-4 { |
|||
margin-top: 16px; |
|||
justify-content: flex-end; |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue