Browse Source

Merge remote-tracking branch 'origin/dev-V1.10.0' into dev-V1.10.0

dev-V1.10.1
niujiyi 3 weeks ago
parent
commit
c16575b35a
  1. 4
      zdxt-modules/zdxt-enforcement-nos/src/main/java/com/zdxt/enforcementnos/common/commonenum/CheckResultEnum.java
  2. 16
      zdxt-modules/zdxt-enforcement-nos/src/main/java/com/zdxt/enforcementnos/controller/app/enforcement/EnfNosLawEnforcementTaskController.java
  3. 2
      zdxt-modules/zdxt-enforcement-nos/src/main/java/com/zdxt/enforcementnos/service/NosLawEnforcementTaskService.java
  4. 12
      zdxt-modules/zdxt-enforcement-nos/src/main/java/com/zdxt/enforcementnos/service/impl/NosLawEnforcementTaskServiceImpl.java
  5. 226
      zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/trend/scan-enforcer/index.vue
  6. 243
      zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/trend/scan-field/index.vue

4
zdxt-modules/zdxt-enforcement-nos/src/main/java/com/zdxt/enforcementnos/common/commonenum/CheckResultEnum.java

@ -13,8 +13,8 @@ public enum CheckResultEnum {
COMPLETED(0, "已完成"),
PENDING_VERIFICATION(1, "转为待核实处置案件"),
RETURNED_FOR_CORRECTION(2, "退回企业补正材料");
RETURNED_FOR_CORRECTION(2, "退回企业补正材料"),
HANDLED_ON_SITE(3, "已线下处置");
private final Integer code;
private final String description;

16
zdxt-modules/zdxt-enforcement-nos/src/main/java/com/zdxt/enforcementnos/controller/app/enforcement/EnfNosLawEnforcementTaskController.java

@ -6,6 +6,7 @@ import com.zdxt.enforcementnos.domain.bo.NosLawEnforcementTaskApproveBO;
import com.zdxt.enforcementnos.domain.bo.NosLawEnforcementTaskDigitalVoiceLoggingBO;
import com.zdxt.enforcementnos.domain.bo.NosLawEnforcementTaskPageBO;
import com.zdxt.enforcementnos.domain.bo.NosLawEnforcementTaskSaveBO;
import com.zdxt.enforcementnos.domain.bo.NosLawEnforcementTaskVerifyBO;
import com.zdxt.enforcementnos.domain.vo.NosLawEnforcementCommentTaskPageVO;
import com.zdxt.enforcementnos.domain.vo.NosLawEnforcementTaskDetailVO;
import com.zdxt.enforcementnos.domain.vo.NosLawEnforcementTaskDigitalVoiceLoggingRelationVO;
@ -101,6 +102,8 @@ public class EnfNosLawEnforcementTaskController {
}
/**
* 查询已评价的任务
* @param pageParam 分页参数
@ -150,4 +153,17 @@ public class EnfNosLawEnforcementTaskController {
public R<Boolean> confirmDigitalVoiceLoggingMaterials(@RequestBody @Valid NosLawEnforcementTaskDigitalVoiceLoggingBO bo) {
return R.ok(nosLawEnforcementTaskService.confirmDigitalVoiceLoggingMaterials(bo));
}
/**
* 核实案件
* @param bo 核实参数
* @return 是否成功
*/
@Operation(summary = "核实案件")
@PostMapping("/verify-case")
public R<Boolean> verifyCase(@RequestBody @Valid NosLawEnforcementTaskVerifyBO bo) {
return R.ok(nosLawEnforcementTaskService.verifyCase(bo));
}
}

2
zdxt-modules/zdxt-enforcement-nos/src/main/java/com/zdxt/enforcementnos/service/NosLawEnforcementTaskService.java

@ -28,6 +28,8 @@ public interface NosLawEnforcementTaskService extends IService<NosLawEnforcement
Boolean completeVerificationTask(String taskId);
Boolean verifyCase(@Valid NosLawEnforcementTaskVerifyBO bo);
TableDataInfo<NosLawEnforcementTaskByCommentStatusPageVO> queryByCommentStatus(Integer commentStatus, PageParam pageParam);
TableDataInfo<NosLawEnforcementCommentTaskPageVO> pageHasCommentTask(@Valid PageParam pageParam);

12
zdxt-modules/zdxt-enforcement-nos/src/main/java/com/zdxt/enforcementnos/service/impl/NosLawEnforcementTaskServiceImpl.java

@ -554,6 +554,18 @@ public class NosLawEnforcementTaskServiceImpl extends ServiceImpl<NosLawEnforcem
return this.updateById(task);
}
@Override
public Boolean verifyCase(NosLawEnforcementTaskVerifyBO bo) {
NosLawEnforcementTask task = this.getById(bo.getTaskId());
if(task == null) {
throw new ServiceException("该任务不存在");
}
task.setTaskStatus(NosLawEnforcementTaskStatusEnum.COMPLETED.getCode());
task.setCheckResult(CheckResultEnum.HANDLED_ON_SITE.getCode());
task.setUpdateBy(LoginHelper.getUserId());
return this.updateById(task);
}
@Override
public TableDataInfo<NosLawEnforcementTaskByCommentStatusPageVO> queryByCommentStatus(Integer commentStatus, PageParam pageParam) {

226
zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/trend/scan-enforcer/index.vue

@ -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;
}
}

243
zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/trend/scan-field/index.vue

@ -1,45 +1,59 @@
<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 FieldRow {
id: number;
fieldName: string;
enterpriseCount: number;
adminEnforceCount: number;
adminRank: number;
adminPercent: string;
nonAdminEnforceCount: number;
nonAdminRank: number;
nonAdminPercent: string;
}
// ======================== 查询条件 ========================
const queryParams = reactive({
field: undefined as string | undefined
area: undefined as string | undefined,
field: undefined as string | undefined,
periodType: 'month' as string,
year: String(new Date().getFullYear())
});
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: 'special_equipment' },
{ label: '产品质量', value: 'product_quality' },
{ label: '食品安全', value: 'food_safety' },
{ label: '计量器具', value: 'measurement' },
{ label: '药品监管', value: 'drug' },
{ label: '广告监管', value: 'advertising' },
{ label: '知识产权', value: 'ipr' },
{ label: '网络交易', value: 'internet' }
{ 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);
const tableData = ref<FieldRow[]>([]);
@ -59,14 +73,13 @@ const calcTableHeight = () => {
const buildMockRows = (): FieldRow[] => {
return [
{ id: 1, fieldName: '特种设备', enterpriseCount: 125600, adminEnforceCount: 28421, adminRank: 1, adminPercent: '68.5%', nonAdminEnforceCount: 3200, nonAdminRank: 1, nonAdminPercent: '58%' },
{ id: 2, fieldName: '产品质量', enterpriseCount: 98450, adminEnforceCount: 15230, adminRank: 2, adminPercent: '62.3%', nonAdminEnforceCount: 2150, nonAdminRank: 2, nonAdminPercent: '52%' },
{ id: 3, fieldName: '食品安全', enterpriseCount: 132800, adminEnforceCount: 12680, adminRank: 3, adminPercent: '55.8%', nonAdminEnforceCount: 1820, nonAdminRank: 3, nonAdminPercent: '48%' },
{ id: 4, fieldName: '计量器具', enterpriseCount: 42300, adminEnforceCount: 8950, adminRank: 4, adminPercent: '50.2%', nonAdminEnforceCount: 1200, nonAdminRank: 4, nonAdminPercent: '45%' },
{ id: 5, fieldName: '药品监管', enterpriseCount: 18600, adminEnforceCount: 6420, adminRank: 5, adminPercent: '72.1%', nonAdminEnforceCount: 880, nonAdminRank: 5, nonAdminPercent: '65%' },
{ id: 6, fieldName: '广告监管', enterpriseCount: 35200, adminEnforceCount: 4180, adminRank: 6, adminPercent: '48.6%', nonAdminEnforceCount: 620, nonAdminRank: 6, nonAdminPercent: '42%' },
{ id: 7, fieldName: '知识产权', enterpriseCount: 28900, adminEnforceCount: 3250, adminRank: 7, adminPercent: '45.3%', nonAdminEnforceCount: 520, nonAdminRank: 7, nonAdminPercent: '40%' },
{ id: 8, fieldName: '网络交易', enterpriseCount: 56700, adminEnforceCount: 2150, adminRank: 8, adminPercent: '38.2%', nonAdminEnforceCount: 380, nonAdminRank: 8, nonAdminPercent: '35%' }
{ id: 1, fieldName: '生态环境领域', adminEnforceCount: 14421, adminPercent: '60%', nonAdminEnforceCount: 50384, nonAdminPercent: '60%' },
{ id: 2, fieldName: '卫生健康领域', adminEnforceCount: 11415, adminPercent: '56.64%', nonAdminEnforceCount: 22017, nonAdminPercent: '56.64%' },
{ id: 3, fieldName: '应急管理领域', adminEnforceCount: 7040, adminPercent: '70%', nonAdminEnforceCount: 9961, nonAdminPercent: '70%' },
{ id: 4, fieldName: '交通运输领域', adminEnforceCount: 6135, adminPercent: '80%', nonAdminEnforceCount: 888, nonAdminPercent: '80%' },
{ id: 5, fieldName: '市场监管领域', adminEnforceCount: 5424, adminPercent: '67.5%', nonAdminEnforceCount: 389, nonAdminPercent: '67.5%' },
{ id: 6, fieldName: '消防救援领域', adminEnforceCount: 5064, adminPercent: '63.2%', nonAdminEnforceCount: 184, nonAdminPercent: '63.2%' },
{ id: 7, fieldName: '规划和自然资源领域', adminEnforceCount: 4245, adminPercent: '78.34%', nonAdminEnforceCount: 137, nonAdminPercent: '78.34%' }
];
};
@ -75,7 +88,7 @@ const getList = async () => {
try {
// TODO: 接入真实接口 listFieldScanData
tableData.value = buildMockRows();
total.value = 256;
total.value = 658;
} finally {
loading.value = false;
}
@ -87,9 +100,10 @@ const handleSearch = () => {
};
const handleReset = () => {
queryParams.area = undefined;
queryParams.field = undefined;
periodType.value = 'month';
periodDate.value = '2026-09';
queryParams.periodType = 'month';
queryParams.year = String(new Date().getFullYear());
currentPage.value = 1;
pageSize.value = 20;
getList();
@ -114,79 +128,8 @@ const handlePageChange = (page: number) => {
getList();
};
// ======================== 详情弹窗 ========================
interface ScanDetailRow {
id: number;
enterpriseName: string;
scanTime: string;
scanType: string;
bureauDeptName: string;
lawEnforcer: string;
enforcementItem: string;
}
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 buildDetailMockRows = (field: FieldRow, count: number): ScanDetailRow[] => {
const rows: ScanDetailRow[] = [];
const enforcerPool = ['张三', '李四', '王五', '赵六', '钱七', '孙八'];
const bureauPool = ['深圳市市场监督管理局', '深圳市市场监管局执法支队', '各区市场监管局'];
const scanTypePool = ['行政执法', '非行政执法'];
const enterprisePool = ['深圳市某某科技有限公司', '深圳市某某电子厂', '深圳市某某贸易有限公司'];
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],
bureauDeptName: bureauPool[i % bureauPool.length],
lawEnforcer: enforcerPool[i % enforcerPool.length],
enforcementItem: `${field.fieldName}相关检查`
});
}
return rows;
};
const getDetailList = async () => {
detailLoading.value = true;
try {
if (!detailField.value) return;
// TODO: 接入真实接口 listFieldScanDetail
detailTableData.value = buildDetailMockRows(detailField.value, detailField.value.adminEnforceCount);
detailTotal.value = detailField.value.adminEnforceCount + detailField.value.nonAdminEnforceCount;
} 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();
getList();
nextTick(() => calcTableHeight());
window.addEventListener('resize', calcTableHeight);
@ -203,6 +146,16 @@ onUnmounted(() => {
<!-- ==================== 搜索区域 ==================== -->
<div class="search-panel">
<el-form :inline="true" label-colon>
<el-form-item 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 label="所属领域">
<el-select
v-model="queryParams.field"
@ -214,18 +167,14 @@ onUnmounted(() => {
</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>
@ -248,20 +197,12 @@ onUnmounted(() => {
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" width="80" align="center" type="index" />
<el-table-column prop="fieldName" label="所属领域" width="140" align="center" />
<el-table-column prop="enterpriseCount" label="商事主体数量(户)" min-width="150" align="center" />
<el-table-column prop="adminEnforceCount" label="行政执法数量(条)" min-width="150" align="center" />
<el-table-column prop="adminRank" label="行政执法数量排名" min-width="150" align="center" />
<el-table-column prop="adminPercent" label="行政执法数量占比(%)" min-width="170" align="center" />
<el-table-column prop="nonAdminEnforceCount" label="非行政执法数量" min-width="140" align="center" />
<el-table-column prop="nonAdminRank" label="非行政执法数量排名" min-width="160" align="center" />
<el-table-column prop="nonAdminPercent" label="非行政执法数量占比(%)" min-width="180" align="center" />
<el-table-column label="操作" width="80" 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-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>
</div>
@ -277,53 +218,6 @@ onUnmounted(() => {
/>
</div>
</div>
<!-- ==================== 扫码详情弹窗 ==================== -->
<el-dialog
v-model="detailDialogVisible"
width="1100px"
:title="`${detailField?.fieldName || ''} — ${periodDate} 扫码明细`"
append-to-body
top="5vh"
destroy-on-close
>
<el-alert
v-if="detailField"
:closable="false"
show-icon
:title="`商事主体:${detailField.enterpriseCount}户 | 行政执法:${detailField.adminEnforceCount}条 | 非行政执法:${detailField.nonAdminEnforceCount}条 | 时段:${periodDate}`"
type="info"
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="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>
@ -362,8 +256,8 @@ onUnmounted(() => {
width: 110px;
}
.period-date {
width: 150px;
.year-select {
width: 110px;
}
}
@ -379,7 +273,4 @@ onUnmounted(() => {
margin-top: 16px;
justify-content: flex-end;
}
.mb-4 {
margin-bottom: 16px;
}
</style>

Loading…
Cancel
Save