|
|
|
@ -1,33 +1,60 @@ |
|
|
|
<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'; |
|
|
|
|
|
|
|
/** 表格行数据(执法人员维度) */ |
|
|
|
interface EnforcerRow { |
|
|
|
id: number; |
|
|
|
enforcerName: string; |
|
|
|
enforceCount: number; |
|
|
|
areaName: string; |
|
|
|
bureauName: string; |
|
|
|
adminEnforceCount: number; |
|
|
|
adminRank: number; |
|
|
|
adminPercent: string; |
|
|
|
nonAdminEnforceCount: number; |
|
|
|
nonAdminRank: number; |
|
|
|
nonAdminPercent: string; |
|
|
|
} |
|
|
|
|
|
|
|
// ======================== 查询条件 ======================== |
|
|
|
const queryParams = reactive({ |
|
|
|
enforcerName: '' as string, |
|
|
|
bureauName: '' as string |
|
|
|
area: undefined as string | undefined, |
|
|
|
field: undefined as string | undefined, |
|
|
|
periodType: 'year' as string, |
|
|
|
year: String(new Date().getFullYear()), |
|
|
|
orderByColumn: '' as string, |
|
|
|
isAsc: '' as string |
|
|
|
}); |
|
|
|
|
|
|
|
const periodType = ref('month'); |
|
|
|
const periodOptions = [ |
|
|
|
{ label: '月度', value: 'month' } |
|
|
|
{ label: '周度', value: 'week' }, |
|
|
|
{ label: '月度', value: 'month' }, |
|
|
|
{ label: '季度', value: 'quarter' }, |
|
|
|
{ label: '年度', value: 'year' } |
|
|
|
]; |
|
|
|
const periodDate = ref<string>('2026-09'); |
|
|
|
|
|
|
|
const pickerType = computed<'week' | 'month' | 'year'>(() => 'month'); |
|
|
|
const pickerFormat = computed(() => 'YYYY-MM'); |
|
|
|
/** 年份选项(近 5 年) */ |
|
|
|
const yearOptions = (() => { |
|
|
|
const curr = new Date().getFullYear(); |
|
|
|
return Array.from({ length: 5 }, (_, i) => String(curr - i)); |
|
|
|
})(); |
|
|
|
|
|
|
|
// ======================== 字典 ======================== |
|
|
|
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 loadAreaDict = async () => { |
|
|
|
try { |
|
|
|
const res = await getDicts('address_type'); |
|
|
|
areaOptions.value = res.data || []; |
|
|
|
} catch (e) { |
|
|
|
console.error('加载所属区字典失败', e); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// ======================== 表格数据 ======================== |
|
|
|
const loading = ref(false); |
|
|
|
@ -48,18 +75,13 @@ const calcTableHeight = () => { |
|
|
|
|
|
|
|
const buildMockRows = (): EnforcerRow[] => { |
|
|
|
return [ |
|
|
|
{ id: 1, enforcerName: '张三', bureauName: '宝安区市场监督管理局', adminEnforceCount: 128, adminRank: 1, adminPercent: '12.5%', nonAdminEnforceCount: 15, nonAdminRank: 3, nonAdminPercent: '8.2%' }, |
|
|
|
{ id: 2, enforcerName: '李四', bureauName: '龙岗区市场监督管理局', adminEnforceCount: 115, adminRank: 2, adminPercent: '11.2%', nonAdminEnforceCount: 22, nonAdminRank: 1, nonAdminPercent: '12.0%' }, |
|
|
|
{ id: 3, enforcerName: '王五', bureauName: '福田区市场监督管理局', adminEnforceCount: 98, adminRank: 3, adminPercent: '9.6%', nonAdminEnforceCount: 18, nonAdminRank: 2, nonAdminPercent: '9.8%' }, |
|
|
|
{ id: 4, enforcerName: '赵六', bureauName: '南山区市场监督管理局', adminEnforceCount: 87, adminRank: 4, adminPercent: '8.5%', nonAdminEnforceCount: 12, nonAdminRank: 5, nonAdminPercent: '6.5%' }, |
|
|
|
{ id: 5, enforcerName: '钱七', bureauName: '龙华区市场监督管理局', adminEnforceCount: 76, adminRank: 5, adminPercent: '7.4%', nonAdminEnforceCount: 14, nonAdminRank: 4, nonAdminPercent: '7.6%' }, |
|
|
|
{ id: 6, enforcerName: '孙八', bureauName: '宝安区市场监督管理局', adminEnforceCount: 65, adminRank: 6, adminPercent: '6.3%', nonAdminEnforceCount: 8, nonAdminRank: 7, nonAdminPercent: '4.3%' }, |
|
|
|
{ id: 7, enforcerName: '周九', bureauName: '龙岗区市场监督管理局', adminEnforceCount: 58, adminRank: 7, adminPercent: '5.7%', nonAdminEnforceCount: 10, nonAdminRank: 6, nonAdminPercent: '5.4%' }, |
|
|
|
{ id: 8, enforcerName: '吴十', bureauName: '福田区市场监督管理局', adminEnforceCount: 52, adminRank: 8, adminPercent: '5.1%', nonAdminEnforceCount: 6, nonAdminRank: 8, nonAdminPercent: '3.3%' }, |
|
|
|
{ id: 9, enforcerName: '郑十一', bureauName: '南山区市场监督管理局', adminEnforceCount: 45, adminRank: 9, adminPercent: '4.4%', nonAdminEnforceCount: 5, nonAdminRank: 9, nonAdminPercent: '2.7%' }, |
|
|
|
{ id: 10, enforcerName: '冯十二', bureauName: '龙华区市场监督管理局', adminEnforceCount: 38, adminRank: 10, adminPercent: '3.7%', nonAdminEnforceCount: 4, nonAdminRank: 10, nonAdminPercent: '2.2%' }, |
|
|
|
{ id: 11, enforcerName: '陈十三', bureauName: '宝安区市场监督管理局', adminEnforceCount: 32, adminRank: 11, adminPercent: '3.1%', nonAdminEnforceCount: 3, nonAdminRank: 11, nonAdminPercent: '1.6%' }, |
|
|
|
{ id: 12, enforcerName: '褚十四', bureauName: '龙岗区市场监督管理局', adminEnforceCount: 28, adminRank: 12, adminPercent: '2.7%', nonAdminEnforceCount: 2, nonAdminRank: 12, nonAdminPercent: '1.1%' } |
|
|
|
{ id: 1, enforcerName: '张清风', enforceCount: 14421, areaName: '深圳市', bureauName: '深圳市应急管理局' }, |
|
|
|
{ id: 2, enforcerName: '周志南', enforceCount: 11415, areaName: '宝安区', bureauName: '深圳市宝安区生态环境局' }, |
|
|
|
{ id: 3, enforcerName: '吴栩曦', enforceCount: 7040, areaName: '盐田区', bureauName: '深圳市盐田区住房和城乡建设局' }, |
|
|
|
{ id: 4, enforcerName: '段子曦', enforceCount: 6135, areaName: '光明区', bureauName: '深圳市光明区交通运输局' }, |
|
|
|
{ id: 5, enforcerName: '吴海羽', enforceCount: 5424, areaName: '龙岗区', bureauName: '深圳市龙岗区生态环境局' }, |
|
|
|
{ id: 6, enforcerName: '张文业', enforceCount: 5064, areaName: '龙华区', bureauName: '深圳市龙华区交通运输局' }, |
|
|
|
{ id: 7, enforcerName: '段宝腾', enforceCount: 4245, areaName: '坪山区', bureauName: '深圳市坪山区交通运输局' } |
|
|
|
]; |
|
|
|
}; |
|
|
|
|
|
|
|
@ -68,7 +90,7 @@ const getList = async () => { |
|
|
|
try { |
|
|
|
// TODO: 接入真实接口 listEnforcerScanData |
|
|
|
tableData.value = buildMockRows(); |
|
|
|
total.value = 186; |
|
|
|
total.value = 658; |
|
|
|
} finally { |
|
|
|
loading.value = false; |
|
|
|
} |
|
|
|
@ -81,9 +103,12 @@ const handleSearch = () => { |
|
|
|
|
|
|
|
const handleReset = () => { |
|
|
|
queryParams.enforcerName = ''; |
|
|
|
queryParams.bureauName = ''; |
|
|
|
periodType.value = 'month'; |
|
|
|
periodDate.value = '2026-09'; |
|
|
|
queryParams.area = undefined; |
|
|
|
queryParams.field = undefined; |
|
|
|
queryParams.periodType = 'year'; |
|
|
|
queryParams.year = String(new Date().getFullYear()); |
|
|
|
queryParams.orderByColumn = ''; |
|
|
|
queryParams.isAsc = ''; |
|
|
|
currentPage.value = 1; |
|
|
|
pageSize.value = 20; |
|
|
|
getList(); |
|
|
|
@ -108,14 +133,27 @@ const handlePageChange = (page: number) => { |
|
|
|
getList(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 执法次数列排序 */ |
|
|
|
const handleSortChange = (sortObj: { prop: string; order: 'ascending' | 'descending' | null }) => { |
|
|
|
if (sortObj.prop === 'enforceCount') { |
|
|
|
queryParams.orderByColumn = 'enforce_count'; |
|
|
|
queryParams.isAsc = sortObj.order === 'ascending' ? 'asc' : 'desc'; |
|
|
|
} else { |
|
|
|
queryParams.orderByColumn = ''; |
|
|
|
queryParams.isAsc = ''; |
|
|
|
} |
|
|
|
currentPage.value = 1; |
|
|
|
getList(); |
|
|
|
}; |
|
|
|
|
|
|
|
// ======================== 详情弹窗 ======================== |
|
|
|
interface ScanDetailRow { |
|
|
|
id: number; |
|
|
|
enterpriseName: string; |
|
|
|
bureauDeptName: string; |
|
|
|
lawEnforcer: string; |
|
|
|
scanTime: string; |
|
|
|
scanType: string; |
|
|
|
enterpriseName: string; |
|
|
|
enforcementItem: string; |
|
|
|
areaName: string; |
|
|
|
} |
|
|
|
|
|
|
|
const detailDialogVisible = ref(false); |
|
|
|
@ -126,26 +164,15 @@ const detailTotal = ref(0); |
|
|
|
const detailPage = ref(1); |
|
|
|
const detailPageSize = ref(20); |
|
|
|
|
|
|
|
const buildDetailMockRows = (enforcer: EnforcerRow, count: number): ScanDetailRow[] => { |
|
|
|
const rows: ScanDetailRow[] = []; |
|
|
|
const scanTypePool = ['行政执法', '非行政执法']; |
|
|
|
const itemPool = ['特种设备安全检查', '产品质量抽查', '食品安全监督抽检', '计量器具检定', '广告监督检查']; |
|
|
|
const enterprisePool = ['深圳市某某科技有限公司', '深圳市某某电子厂', '深圳市某某贸易有限公司']; |
|
|
|
const areaPool = ['宝安区', '龙岗区', '福田区', '南山区', '龙华区']; |
|
|
|
|
|
|
|
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, |
|
|
|
enterpriseName: enterprisePool[i % enterprisePool.length], |
|
|
|
scanTime: `${periodDate.value}-${String(i % 28 + 1).padStart(2, '0')} ${hour}:${minute}:00`, |
|
|
|
scanType: scanTypePool[i % scanTypePool.length], |
|
|
|
enforcementItem: itemPool[i % itemPool.length], |
|
|
|
areaName: areaPool[i % areaPool.length] |
|
|
|
}); |
|
|
|
} |
|
|
|
return rows; |
|
|
|
/** 构造执法详情 mock 数据(与设计稿一致) */ |
|
|
|
const buildDetailMockRows = (): ScanDetailRow[] => { |
|
|
|
return [ |
|
|
|
{ id: 1, bureauDeptName: '深圳市罗湖区住房和建设局', lawEnforcer: '王启鹏, 杨飞', scanTime: '2026-09-18 11:33:17', enterpriseName: '上海建工集团股份有限公司', enforcementItem: '排水检查' }, |
|
|
|
{ id: 2, bureauDeptName: '深圳市罗湖区住房和建设局', lawEnforcer: '王启鹏, 张清风', scanTime: '2026-09-18 11:22:40', enterpriseName: '深圳市海恒酒店管理有限公司', enforcementItem: '治安检查' }, |
|
|
|
{ id: 3, bureauDeptName: '深圳市罗湖区住房和建设局', lawEnforcer: '王启鹏, 杨明', scanTime: '2026-09-17 09:44:58', enterpriseName: '深圳市京九节能环保科技有限公司', enforcementItem: '治安检查' }, |
|
|
|
{ id: 4, bureauDeptName: '深圳市罗湖区住房和建设局', lawEnforcer: '王启鹏, 杨明', scanTime: '2026-09-16 21:25:10', enterpriseName: '深圳市京九节能环保科技有限公司', enforcementItem: '治安检查' }, |
|
|
|
{ id: 5, bureauDeptName: '深圳市罗湖区住房和建设局', lawEnforcer: '王启鹏, 李云', scanTime: '2026-09-16 21:25:10', enterpriseName: '深圳市海恒酒店管理有限公司', enforcementItem: '治安检查' } |
|
|
|
]; |
|
|
|
}; |
|
|
|
|
|
|
|
const getDetailList = async () => { |
|
|
|
@ -153,9 +180,8 @@ const getDetailList = async () => { |
|
|
|
try { |
|
|
|
if (!detailEnforcer.value) return; |
|
|
|
// TODO: 接入真实接口 listEnforcerScanDetail |
|
|
|
const combinedCount = detailEnforcer.value.adminEnforceCount + detailEnforcer.value.nonAdminEnforceCount; |
|
|
|
detailTableData.value = buildDetailMockRows(detailEnforcer.value, combinedCount); |
|
|
|
detailTotal.value = combinedCount; |
|
|
|
detailTableData.value = buildDetailMockRows(); |
|
|
|
detailTotal.value = 658; |
|
|
|
} finally { |
|
|
|
detailLoading.value = false; |
|
|
|
} |
|
|
|
@ -180,6 +206,7 @@ const handleDetailPageChange = (page: number) => { |
|
|
|
}; |
|
|
|
|
|
|
|
onMounted(() => { |
|
|
|
loadAreaDict(); |
|
|
|
getList(); |
|
|
|
nextTick(() => calcTableHeight()); |
|
|
|
window.addEventListener('resize', calcTableHeight); |
|
|
|
@ -199,34 +226,41 @@ onUnmounted(() => { |
|
|
|
<el-form-item label="执法人员"> |
|
|
|
<el-input |
|
|
|
v-model="queryParams.enforcerName" |
|
|
|
placeholder="请输入姓名" |
|
|
|
placeholder="请输入关键字" |
|
|
|
class="search-input" |
|
|
|
clearable |
|
|
|
@keyup.enter="handleSearch" |
|
|
|
/> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item label="所属单位"> |
|
|
|
<el-input |
|
|
|
v-model="queryParams.bureauName" |
|
|
|
placeholder="请输入单位" |
|
|
|
class="search-input" |
|
|
|
<el-form-item label="所属区"> |
|
|
|
<el-select |
|
|
|
v-model="queryParams.area" |
|
|
|
placeholder="请选择" |
|
|
|
class="search-select" |
|
|
|
clearable |
|
|
|
@keyup.enter="handleSearch" |
|
|
|
/> |
|
|
|
> |
|
|
|
<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.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-select v-model="queryParams.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-select v-model="queryParams.year" class="year-select"> |
|
|
|
<el-option v-for="y in yearOptions" :key="y" :label="`${y}年`" :value="y" /> |
|
|
|
</el-select> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item> |
|
|
|
<el-button type="primary" @click="handleSearch">搜索</el-button> |
|
|
|
@ -247,18 +281,15 @@ onUnmounted(() => { |
|
|
|
style="width: 100%" |
|
|
|
:height="tableHeight" |
|
|
|
@selection-change="handleSelectionChange" |
|
|
|
@sort-change="handleSortChange" |
|
|
|
> |
|
|
|
<el-table-column type="selection" width="55" align="center" /> |
|
|
|
<el-table-column label="序号" width="80" align="center" type="index" /> |
|
|
|
<el-table-column prop="enforcerName" label="执法人员" width="120" align="center" /> |
|
|
|
<el-table-column prop="bureauName" label="所属单位" min-width="220" align="center" /> |
|
|
|
<el-table-column prop="adminEnforceCount" label="行政执法次数" width="130" align="center" /> |
|
|
|
<el-table-column prop="adminRank" label="行政执法排名" width="130" align="center" /> |
|
|
|
<el-table-column prop="adminPercent" label="行政执法占比(%)" width="150" align="center" /> |
|
|
|
<el-table-column prop="nonAdminEnforceCount" label="非行政执法次数" width="140" align="center" /> |
|
|
|
<el-table-column prop="nonAdminRank" label="非行政执法排名" width="140" align="center" /> |
|
|
|
<el-table-column prop="nonAdminPercent" label="非行政执法占比(%)" width="160" align="center" /> |
|
|
|
<el-table-column label="操作" width="80" align="center" fixed="right"> |
|
|
|
<el-table-column label="序号" width="90" align="center" type="index" /> |
|
|
|
<el-table-column prop="enforcerName" label="执法人员" min-width="160" align="center" /> |
|
|
|
<el-table-column prop="enforceCount" label="执法次数" width="140" align="center" sortable="custom" /> |
|
|
|
<el-table-column prop="areaName" label="所属区(市)" min-width="140" align="center" /> |
|
|
|
<el-table-column prop="bureauName" label="所属单位" min-width="260" align="center" show-overflow-tooltip /> |
|
|
|
<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> |
|
|
|
@ -279,24 +310,15 @@ onUnmounted(() => { |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- ==================== 扫码详情弹窗 ==================== --> |
|
|
|
<!-- ==================== 执法详情弹窗 ==================== --> |
|
|
|
<el-dialog |
|
|
|
v-model="detailDialogVisible" |
|
|
|
width="1000px" |
|
|
|
:title="`${detailEnforcer?.enforcerName || ''} — ${periodDate} 执法明细`" |
|
|
|
width="1100px" |
|
|
|
title="执法详情" |
|
|
|
append-to-body |
|
|
|
top="5vh" |
|
|
|
destroy-on-close |
|
|
|
> |
|
|
|
<el-alert |
|
|
|
v-if="detailEnforcer" |
|
|
|
:closable="false" |
|
|
|
show-icon |
|
|
|
:title="`所属单位:${detailEnforcer.bureauName} | 行政执法:${detailEnforcer.adminEnforceCount}次 | 非行政执法:${detailEnforcer.nonAdminEnforceCount}次 | 时段:${periodDate}`" |
|
|
|
type="info" |
|
|
|
class="mb-4" |
|
|
|
/> |
|
|
|
|
|
|
|
<el-table |
|
|
|
v-loading="detailLoading" |
|
|
|
:data="detailTableData" |
|
|
|
@ -306,11 +328,11 @@ onUnmounted(() => { |
|
|
|
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="enforcementItem" min-width="200" align="center" /> |
|
|
|
<el-table-column label="所属区" prop="areaName" width="100" align="center" /> |
|
|
|
<el-table-column label="执法单位" prop="bureauDeptName" min-width="220" align="center" show-overflow-tooltip /> |
|
|
|
<el-table-column label="执法人员" prop="lawEnforcer" min-width="160" align="center" /> |
|
|
|
<el-table-column label="执法时间" prop="scanTime" min-width="180" align="center" /> |
|
|
|
<el-table-column label="被执法企业名称" prop="enterpriseName" min-width="260" align="center" show-overflow-tooltip /> |
|
|
|
<el-table-column label="执法事项" prop="enforcementItem" width="120" align="center" /> |
|
|
|
</el-table> |
|
|
|
|
|
|
|
<el-pagination |
|
|
|
@ -358,12 +380,16 @@ onUnmounted(() => { |
|
|
|
width: 210px; |
|
|
|
} |
|
|
|
|
|
|
|
.search-select { |
|
|
|
width: 220px; |
|
|
|
} |
|
|
|
|
|
|
|
.period-select { |
|
|
|
width: 110px; |
|
|
|
} |
|
|
|
|
|
|
|
.period-date { |
|
|
|
width: 150px; |
|
|
|
.year-select { |
|
|
|
width: 110px; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|