|
|
|
@ -1,11 +1,22 @@ |
|
|
|
<script setup lang="ts"> |
|
|
|
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; |
|
|
|
import { ElMessage } from 'element-plus'; |
|
|
|
import { getDicts } from '@/api/system/dict/data'; |
|
|
|
import { |
|
|
|
listScanDomain, |
|
|
|
listScanDomainDetails, |
|
|
|
exportScanDomain, |
|
|
|
type ScanDetailRow, |
|
|
|
type ScanTimeParams |
|
|
|
} from '@/api/efcode/report/scan-data-situation'; |
|
|
|
import { getDomainInfo, type DashboardDomainVo } from '@/api/efcode/dashboard/area-info'; |
|
|
|
|
|
|
|
/** 表格行数据 */ |
|
|
|
interface FieldRow { |
|
|
|
id: number; |
|
|
|
fieldName: string; |
|
|
|
domainId?: string | number; |
|
|
|
domainName?: string; |
|
|
|
adminEnforceCount: number; |
|
|
|
adminPercent: string; |
|
|
|
nonAdminEnforceCount: number; |
|
|
|
@ -35,15 +46,7 @@ const yearOptions = (() => { |
|
|
|
|
|
|
|
// ======================== 字典 ======================== |
|
|
|
const areaOptions = ref<any[]>([]); |
|
|
|
const fieldOptions = ref([ |
|
|
|
{ label: '生态环境领域', value: 'ecology' }, |
|
|
|
{ label: '卫生健康领域', value: 'health' }, |
|
|
|
{ label: '应急管理领域', value: 'emergency' }, |
|
|
|
{ label: '交通运输领域', value: 'transport' }, |
|
|
|
{ label: '市场监管领域', value: 'market' }, |
|
|
|
{ label: '消防救援领域', value: 'fire' }, |
|
|
|
{ label: '规划和自然资源领域', value: 'planning' } |
|
|
|
]); |
|
|
|
const fieldOptions = ref<DashboardDomainVo[]>([]); |
|
|
|
|
|
|
|
const loadAreaDict = async () => { |
|
|
|
try { |
|
|
|
@ -54,6 +57,15 @@ const loadAreaDict = async () => { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const loadFieldDict = async () => { |
|
|
|
try { |
|
|
|
const res = await getDomainInfo(); |
|
|
|
fieldOptions.value = res.data || []; |
|
|
|
} catch (e) { |
|
|
|
console.error('加载所属领域字典失败', e); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// ======================== 表格数据 ======================== |
|
|
|
const loading = ref(false); |
|
|
|
const tableData = ref<FieldRow[]>([]); |
|
|
|
@ -71,10 +83,49 @@ const calcTableHeight = () => { |
|
|
|
tableHeight.value = window.innerHeight - rect.top - 84; |
|
|
|
}; |
|
|
|
|
|
|
|
/** 按当前时段类型拼装接口时间参数 */ |
|
|
|
const buildTimeParams = (): ScanTimeParams => { |
|
|
|
const year = queryParams.year; |
|
|
|
const n = new Date(); |
|
|
|
if (queryParams.periodType === 'quarter') { |
|
|
|
return { year, quarter: String(Math.ceil((n.getMonth() + 1) / 3)) }; |
|
|
|
} |
|
|
|
if (queryParams.periodType === 'month') { |
|
|
|
return { year, month: String(n.getMonth() + 1) }; |
|
|
|
} |
|
|
|
if (queryParams.periodType === 'week') { |
|
|
|
// 周度:取本周一 ~ 本周日 |
|
|
|
const day = n.getDay() || 7; |
|
|
|
const monday = new Date(n); |
|
|
|
monday.setDate(n.getDate() - (day - 1)); |
|
|
|
const sunday = new Date(monday); |
|
|
|
sunday.setDate(monday.getDate() + 6); |
|
|
|
const fmt = (d: Date) => |
|
|
|
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; |
|
|
|
return { startTime: fmt(monday), endTime: fmt(sunday) }; |
|
|
|
} |
|
|
|
return { year }; |
|
|
|
}; |
|
|
|
|
|
|
|
/** 当前选中的所属领域(domainId→domainId,domainName→domainName) */ |
|
|
|
const currentField = () => { |
|
|
|
const item = fieldOptions.value.find((i) => i.domainId === queryParams.field); |
|
|
|
return { domainId: queryParams.field, domainName: item?.domainName }; |
|
|
|
}; |
|
|
|
|
|
|
|
const getList = async () => { |
|
|
|
loading.value = true; |
|
|
|
try { |
|
|
|
// TODO: 接入真实接口 listFieldScanData |
|
|
|
const res = await listScanDomain({ |
|
|
|
...buildTimeParams(), |
|
|
|
pageNum: currentPage.value, |
|
|
|
pageSize: pageSize.value, |
|
|
|
...currentField() |
|
|
|
}); |
|
|
|
tableData.value = (res as any)?.rows || []; |
|
|
|
total.value = (res as any)?.total || 0; |
|
|
|
} catch (e) { |
|
|
|
console.warn('[scan-field] 列表接口调用失败', e); |
|
|
|
} finally { |
|
|
|
loading.value = false; |
|
|
|
} |
|
|
|
@ -95,8 +146,24 @@ const handleReset = () => { |
|
|
|
getList(); |
|
|
|
}; |
|
|
|
|
|
|
|
const handleExport = () => { |
|
|
|
// TODO: 接入导出接口 |
|
|
|
const handleExport = async () => { |
|
|
|
try { |
|
|
|
const res = await exportScanDomain({ |
|
|
|
...buildTimeParams(), |
|
|
|
...currentField() |
|
|
|
}); |
|
|
|
const blob = new Blob([res as any], { |
|
|
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' |
|
|
|
}); |
|
|
|
const url = window.URL.createObjectURL(blob); |
|
|
|
const link = document.createElement('a'); |
|
|
|
link.href = url; |
|
|
|
link.download = '扫码情况领域.xlsx'; |
|
|
|
link.click(); |
|
|
|
window.URL.revokeObjectURL(url); |
|
|
|
} catch (e) { |
|
|
|
ElMessage.error('导出失败'); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const handleSelectionChange = (selection: FieldRow[]) => { |
|
|
|
@ -114,8 +181,56 @@ const handlePageChange = (page: number) => { |
|
|
|
getList(); |
|
|
|
}; |
|
|
|
|
|
|
|
// ======================== 详情弹窗 ======================== |
|
|
|
const detailDialogVisible = ref(false); |
|
|
|
const detailField = ref<FieldRow | null>(null); |
|
|
|
const detailLoading = ref(false); |
|
|
|
const detailTableData = ref<ScanDetailRow[]>([]); |
|
|
|
const detailTotal = ref(0); |
|
|
|
const detailPage = ref(1); |
|
|
|
const detailPageSize = ref(20); |
|
|
|
|
|
|
|
const getDetailList = async () => { |
|
|
|
detailLoading.value = true; |
|
|
|
try { |
|
|
|
if (!detailField.value) return; |
|
|
|
const res = await listScanDomainDetails({ |
|
|
|
...buildTimeParams(), |
|
|
|
pageNum: detailPage.value, |
|
|
|
pageSize: detailPageSize.value, |
|
|
|
domainId: detailField.value.domainId || queryParams.field, |
|
|
|
domainName: detailField.value.domainName || detailField.value.fieldName || currentField().domainName |
|
|
|
}); |
|
|
|
detailTableData.value = (res as any)?.rows || []; |
|
|
|
detailTotal.value = (res as any)?.total || 0; |
|
|
|
} catch (e) { |
|
|
|
console.warn('[scan-field] 详情接口调用失败', e); |
|
|
|
} finally { |
|
|
|
detailLoading.value = false; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const handleDetail = (row: FieldRow) => { |
|
|
|
detailField.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(); |
|
|
|
}; |
|
|
|
|
|
|
|
onMounted(() => { |
|
|
|
loadAreaDict(); |
|
|
|
loadFieldDict(); |
|
|
|
getList(); |
|
|
|
nextTick(() => calcTableHeight()); |
|
|
|
window.addEventListener('resize', calcTableHeight); |
|
|
|
@ -149,7 +264,7 @@ onUnmounted(() => { |
|
|
|
class="search-select" |
|
|
|
clearable |
|
|
|
> |
|
|
|
<el-option v-for="item in fieldOptions" :key="item.value" :label="item.label" :value="item.value" /> |
|
|
|
<el-option v-for="item in fieldOptions" :key="item.domainId" :label="item.domainName" :value="item.domainId" /> |
|
|
|
</el-select> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item label="时段"> |
|
|
|
@ -184,11 +299,18 @@ onUnmounted(() => { |
|
|
|
> |
|
|
|
<el-table-column type="selection" width="55" align="center" /> |
|
|
|
<el-table-column label="序号" width="90" align="center" type="index" /> |
|
|
|
<el-table-column prop="fieldName" label="所属领域" min-width="200" align="center" /> |
|
|
|
<el-table-column prop="adminEnforceCount" label="行政执法数量(条)" min-width="160" align="center" /> |
|
|
|
<el-table-column prop="adminPercent" label="行政执法数量占比(%)" min-width="190" align="center" /> |
|
|
|
<el-table-column prop="nonAdminEnforceCount" label="非行政执法数量(条)" min-width="170" align="center" /> |
|
|
|
<el-table-column prop="nonAdminPercent" label="非行政执法数量占比(%)" min-width="200" align="center" /> |
|
|
|
<el-table-column prop="domainName" label="所属领域" min-width="200" align="center" /> |
|
|
|
<el-table-column prop="xzzfCount" label="行政执法数量(条)" min-width="160" align="center" /> |
|
|
|
<el-table-column prop="xzzfslzb" label="行政执法数量占比(%)" min-width="190" align="center" /> |
|
|
|
<el-table-column prop="xzzfslpm" label="行政执法数量排名" min-width="190" align="center" /> |
|
|
|
<el-table-column prop="fxzzfsl" label="非行政执法数量(条)" min-width="170" align="center" /> |
|
|
|
<el-table-column prop="fxzzfslpm" label="非行政执法数量排名" min-width="200" align="center" /> |
|
|
|
<el-table-column prop="fxzzfslzb" label="非行政执法数量占比(%)" min-width="200" align="center" /> |
|
|
|
<el-table-column label="操作" width="90" align="center" fixed="right"> |
|
|
|
<template #default="{ row }"> |
|
|
|
<el-button type="primary" link size="small" @click="handleDetail(row)">详情</el-button> |
|
|
|
</template> |
|
|
|
</el-table-column> |
|
|
|
</el-table> |
|
|
|
</div> |
|
|
|
|
|
|
|
@ -204,6 +326,44 @@ onUnmounted(() => { |
|
|
|
/> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- ==================== 扫码明细弹窗 ==================== --> |
|
|
|
<el-dialog |
|
|
|
v-model="detailDialogVisible" |
|
|
|
width="1100px" |
|
|
|
:title="`${detailField?.fieldName || ''} — 扫码明细`" |
|
|
|
append-to-body |
|
|
|
top="5vh" |
|
|
|
destroy-on-close |
|
|
|
> |
|
|
|
<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="enterpriseName" min-width="220" align="center" /> |
|
|
|
<el-table-column label="扫码时间" prop="scanTime" min-width="180" align="center" /> |
|
|
|
<el-table-column label="扫码类型" prop="scanType" width="120" align="center" /> |
|
|
|
<el-table-column label="执法单位" prop="bureauDeptName" min-width="220" align="center" /> |
|
|
|
<el-table-column label="执法人员" prop="lawEnforcer" width="120" align="center" /> |
|
|
|
<el-table-column label="执法事项" prop="enforcementItem" min-width="200" 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> |
|
|
|
|
|
|
|
@ -215,10 +375,8 @@ onUnmounted(() => { |
|
|
|
|
|
|
|
.page-card { |
|
|
|
background: #fff; |
|
|
|
border: 1px solid #dcdfe6; |
|
|
|
border-radius: 4px; |
|
|
|
box-sizing: border-box; |
|
|
|
padding: 16px; |
|
|
|
min-height: calc(100vh - 116px); |
|
|
|
} |
|
|
|
|
|
|
|
|