Compare commits

...

2 Commits

  1. 5
      zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/report/enterprises-inspected-noft.ts
  2. 2
      zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/report/registration-statistics.ts
  3. 17
      zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/report/registration.ts
  4. 31
      zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/report/statistics-complaint.ts
  5. 16
      zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/report/statistics-inspection-plan.ts
  6. 31
      zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/enterprises_inspected_noft/index.vue
  7. 73
      zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/registration/indexList.vue
  8. 36
      zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/registration_statistics/index.vue
  9. 126
      zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/statistics_enforcement_complaint/index.vue
  10. 69
      zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/statistics_inspection_plan/index.vue

5
zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/report/enterprises-inspected-noft.ts

@ -8,7 +8,10 @@ export interface EINOFTQuery {
pageNum?: number; pageNum?: number;
pageSize?: number; pageSize?: number;
enterpriseName?: string; enterpriseName?: string;
num?: string; /** 执法次数下限 */
numMin?: string;
/** 执法次数上限 */
numMax?: string;
beginTime?: string; beginTime?: string;
endTime?: string; endTime?: string;
enterpriseNumber?: string; enterpriseNumber?: string;

2
zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/report/registration-statistics.ts

@ -21,6 +21,8 @@ export interface AnalysisQuery {
numRight?: string; numRight?: string;
num?: string; num?: string;
enterpriseNumber?: string; enterpriseNumber?: string;
orderByColumn?: string;
isAsc?: string;
} }
/** 执法分析行VO */ /** 执法分析行VO */

17
zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/report/registration.ts

@ -170,3 +170,20 @@ export const exportAnalysis = (params: RegistrationReportQuery): AxiosPromise<Bl
responseType: 'blob' responseType: 'blob'
}); });
}; };
/** 联合执法明细行 */
export interface UnionRow {
bureauDeptName?: string;
enterpriseName?: string;
enforcementTime?: string;
[key: string]: any;
}
/** 联合执法详情:/code/registration/unionList?unionNo=xxx */
export const listUnionList = (unionNo: string | number): AxiosPromise<UnionRow[]> => {
return request({
url: '/code/registration/unionList',
method: 'get',
params: { unionNo }
});
};

31
zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/report/statistics-complaint.ts

@ -52,6 +52,28 @@ export interface ComplaintDetailVo {
enforceType: number; enforceType: number;
enforceTypeText: string; enforceTypeText: string;
complaintTime: string; complaintTime: string;
/** 投诉内容 */
complaintContent?: string;
/** 涉事地址 */
enterpriseAddress?: string;
/** 附件 */
attachment?: string;
/** 授权状态:1-同意 0-不同意 */
authStatus?: number | string;
/** 联系人 */
contactPerson?: string;
/** 联系电话 */
contactPhone?: string;
}
/** 处置进度VO */
export interface ComplaintHandleVo {
/** 处理部门 */
handleDepartment: string;
/** 处理意见 */
handleOpinion: string;
/** 处理时间 */
handleTime: string;
} }
/** Tab2 类型统计VO */ /** Tab2 类型统计VO */
@ -88,6 +110,15 @@ export const getDetailList = (params: ComplaintQuery): AxiosPromise => {
}); });
}; };
/** 投诉举报详情(含处置进度) */
export const getComplaintDetail = (id: number | string): AxiosPromise => {
return request({
url: `${BASE_URL}/detail`,
method: 'get',
params: { id }
});
};
/** Tab2-投诉类型统计 */ /** Tab2-投诉类型统计 */
export const getTypeStatList = (params: ComplaintQuery): AxiosPromise => { export const getTypeStatList = (params: ComplaintQuery): AxiosPromise => {
return request({ return request({

16
zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/report/statistics-inspection-plan.ts

@ -52,6 +52,12 @@ export interface InspectionPlanDetailVo {
bureauDeptName: string; bureauDeptName: string;
enforcementArea: string; enforcementArea: string;
enforcementTime: string; enforcementTime: string;
/** 附件名称 */
attachment?: string;
/** 附件ID/路径,用于下载 */
attachmentId?: string | number;
/** 附件完整URL(直链预览用) */
attachmentUrl?: string;
} }
/** 排行VO */ /** 排行VO */
@ -139,3 +145,13 @@ export const exportGroupList = (data: InspectionPlanQuery): AxiosPromise<Blob> =
responseType: 'blob' responseType: 'blob'
}); });
}; };
/** 下载执法计划附件 */
export const downloadAttachment = (id: string): AxiosPromise<Blob> => {
return request({
url: `${BASE_URL}/downloadAttachment`,
method: 'get',
params: { id },
responseType: 'blob'
});
};

31
zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/enterprises_inspected_noft/index.vue

@ -11,7 +11,8 @@ const queryParams = reactive<EINOFTQuery>({
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
enterpriseName: undefined, enterpriseName: undefined,
num: undefined, numMin: undefined,
numMax: undefined,
beginTime: `${yearNow}-01-01`, beginTime: `${yearNow}-01-01`,
endTime: undefined endTime: undefined
}); });
@ -77,7 +78,8 @@ const resetQuery = () => {
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
enterpriseName: undefined, enterpriseName: undefined,
num: undefined, numMin: undefined,
numMax: undefined,
beginTime: undefined, beginTime: undefined,
endTime: undefined endTime: undefined
}); });
@ -131,12 +133,23 @@ const loadStatistics = async () => {
/** 排序变更 */ /** 排序变更 */
const handleSortChange = (sortObj: { prop: string; order: 'ascending' | 'descending' | null }) => { const handleSortChange = (sortObj: { prop: string; order: 'ascending' | 'descending' | null }) => {
if (sortObj.prop === 'enforcementTime') { if (!sortObj.prop || !sortObj.order) {
queryParams.orderByColumn = 'enforcement_time'; queryParams.orderByColumn = undefined;
queryParams.isAsc = sortObj.order === 'ascending' ? 'asc' : 'desc'; queryParams.isAsc = undefined;
queryParams.pageNum = 1; queryParams.pageNum = 1;
getList(); getList();
return;
} }
if (sortObj.prop === 'enforcementTime') {
queryParams.orderByColumn = 'enforcement_time';
} else if (sortObj.prop === 'num') {
queryParams.orderByColumn = 'num';
} else {
return;
}
queryParams.isAsc = sortObj.order === 'ascending' ? 'asc' : 'desc';
queryParams.pageNum = 1;
getList();
}; };
/** 点击详情 */ /** 点击详情 */
@ -171,8 +184,10 @@ const handleShowDetail = async (row: EnterprisesInspectedVo) => {
<el-form-item label="企业名称" prop="enterpriseName"> <el-form-item label="企业名称" prop="enterpriseName">
<el-input v-model="queryParams.enterpriseName" placeholder="请输入企业名称" clearable @keyup.enter="handleQuery" style="width: 200px" /> <el-input v-model="queryParams.enterpriseName" placeholder="请输入企业名称" clearable @keyup.enter="handleQuery" style="width: 200px" />
</el-form-item> </el-form-item>
<el-form-item label="执法次数" prop="num"> <el-form-item label="执法次数">
<el-input v-model="queryParams.num" placeholder="请输入所需大于等于次数" clearable @keyup.enter="handleQuery" style="width: 180px" /> <el-input v-model="queryParams.numMin" placeholder="最小次数" clearable @keyup.enter="handleQuery" style="width: 100px" />
<span style="margin: 0 4px">-</span>
<el-input v-model="queryParams.numMax" placeholder="最大次数" clearable @keyup.enter="handleQuery" style="width: 100px" />
</el-form-item> </el-form-item>
<el-form-item label="执法时间"> <el-form-item label="执法时间">
<el-date-picker v-model="queryParams.beginTime" type="date" placeholder="开始日期" value-format="YYYY-MM-DD" style="width: 140px" /> <el-date-picker v-model="queryParams.beginTime" type="date" placeholder="开始日期" value-format="YYYY-MM-DD" style="width: 140px" />
@ -202,7 +217,7 @@ const handleShowDetail = async (row: EnterprisesInspectedVo) => {
<el-card shadow="hover"> <el-card shadow="hover">
<el-table ref="tableRef" v-loading="loading" :data="tableData" :height="tableHeight" border stripe @sort-change="handleSortChange"> <el-table ref="tableRef" v-loading="loading" :data="tableData" :height="tableHeight" border stripe @sort-change="handleSortChange">
<el-table-column label="企业名称" prop="enterpriseName" :show-overflow-tooltip="true" align="center" /> <el-table-column label="企业名称" prop="enterpriseName" :show-overflow-tooltip="true" align="center" />
<el-table-column label="执法次数" prop="num" align="center" /> <el-table-column label="执法次数" prop="num" align="center" sortable="custom" />
<el-table-column label="最后一次执法时间" prop="enforcementTime" align="center" sortable="custom" /> <el-table-column label="最后一次执法时间" prop="enforcementTime" align="center" sortable="custom" />
<el-table-column label="操作" width="100" align="center" fixed="right"> <el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="{ row }"> <template #default="{ row }">

73
zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/registration/indexList.vue

@ -2,9 +2,9 @@
import { ref, reactive, onMounted, onUnmounted, computed, nextTick } from 'vue'; import { ref, reactive, onMounted, onUnmounted, computed, nextTick } from 'vue';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { import {
getStatistics, getRegistrationList, exportRegistrationList getStatistics, getRegistrationList, exportRegistrationList, listUnionList
} from '@/api/efcode/report/registration'; } from '@/api/efcode/report/registration';
import type { RegistrationReportQuery, RegistrationStatisticsVo } from '@/api/efcode/report/registration'; import type { RegistrationReportQuery, RegistrationStatisticsVo, UnionRow } from '@/api/efcode/report/registration';
import { getDicts } from '@/api/system/dict/data'; import { getDicts } from '@/api/system/dict/data';
// 查询参数 // 查询参数
@ -334,6 +334,27 @@ const closeDetailDialog = () => {
detailDialogVisible.value = false; detailDialogVisible.value = false;
detailRow.value = null; detailRow.value = null;
}; };
// ======================== 联合执法详情弹窗 ========================
const unionDialogVisible = ref(false);
const unionLoading = ref(false);
const unionRows = ref<UnionRow[]>([]);
const unionCurrentNo = ref<string | number | undefined>(undefined);
const handleUnionClick = async (row: any) => {
if (!row.unionNo) return;
unionCurrentNo.value = row.unionNo;
unionDialogVisible.value = true;
unionLoading.value = true;
try {
const res = await listUnionList(row.unionNo);
unionRows.value = (res as any)?.rows || (Array.isArray(res) ? res : []);
} catch (e) {
unionRows.value = [];
} finally {
unionLoading.value = false;
}
};
</script> </script>
<template> <template>
@ -472,7 +493,13 @@ const closeDetailDialog = () => {
</el-table-column> </el-table-column>
<el-table-column label="联合执法" width="90" align="center"> <el-table-column label="联合执法" width="90" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-tag v-if="row.hasUnion === 1 && row.unionNo" type="success" size="small">是</el-tag> <el-tag
v-if="row.hasUnion === 1 && row.unionNo"
type="success"
size="small"
class="clickable"
@click.stop="handleUnionClick(row)"
>是</el-tag>
<span v-else>否</span> <span v-else>否</span>
</template> </template>
</el-table-column> </el-table-column>
@ -553,6 +580,31 @@ const closeDetailDialog = () => {
<el-descriptions-item width="150" label="创建时间">{{ (detailRow.createTime) }}</el-descriptions-item> <el-descriptions-item width="150" label="创建时间">{{ (detailRow.createTime) }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-dialog> </el-dialog>
<!-- 联合执法详情弹窗 -->
<el-dialog
v-model="unionDialogVisible"
title="联合执法详情"
width="900px"
append-to-body
destroy-on-close
>
<el-table
v-loading="unionLoading"
:data="unionRows"
border
stripe
style="width: 100%"
>
<el-table-column label="序号" type="index" width="70" align="center" />
<el-table-column label="执法单位" prop="bureauDeptName" min-width="260" align="center" show-overflow-tooltip />
<el-table-column label="被执法企业名称" prop="enterpriseName" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="执法时间" width="210" align="center">
<template #default="{ row }">{{ formatDate(row) }}</template>
</el-table-column>
</el-table>
<div v-if="unionRows.length" class="union-total">共 {{ unionRows.length }} 条记录</div>
</el-dialog>
</div> </div>
</template> </template>
@ -591,4 +643,19 @@ const closeDetailDialog = () => {
.mt-4 { .mt-4 {
margin-top: 16px; margin-top: 16px;
} }
.clickable {
cursor: pointer;
&:hover {
opacity: 0.85;
}
}
.union-total {
margin-top: 12px;
font-size: 13px;
color: #909399;
text-align: right;
}
</style> </style>

36
zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/registration_statistics/index.vue

@ -17,10 +17,14 @@ const queryParams = reactive<AnalysisQuery>({
lawEnforcementItemText: undefined, lawEnforcementItemText: undefined,
district: undefined, district: undefined,
street: undefined, street: undefined,
numLeft: undefined,
numRight: undefined,
dateType: undefined, dateType: undefined,
dateSearch: undefined, dateSearch: undefined,
beginTime: undefined, beginTime: undefined,
endTime: undefined endTime: undefined,
orderByColumn: undefined,
isAsc: undefined
}); });
const loading = ref(false); const loading = ref(false);
@ -183,6 +187,8 @@ const resetQuery = () => {
lawEnforcementItemText: undefined, lawEnforcementItemText: undefined,
district: undefined, district: undefined,
street: undefined, street: undefined,
numLeft: undefined,
numRight: undefined,
dateType: undefined, dateType: undefined,
dateSearch: undefined, dateSearch: undefined,
beginTime: undefined, beginTime: undefined,
@ -209,6 +215,25 @@ const getList = async () => {
} }
}; };
/** 排序变更 */
const handleSortChange = (sortObj: { prop: string; order: 'ascending' | 'descending' | null }) => {
if (!sortObj.prop || !sortObj.order) {
queryParams.orderByColumn = undefined;
queryParams.isAsc = undefined;
queryParams.pageNum = 1;
getList();
return;
}
if (sortObj.prop === 'totalCount') {
queryParams.orderByColumn = 'total_count';
} else {
return;
}
queryParams.isAsc = sortObj.order === 'ascending' ? 'asc' : 'desc';
queryParams.pageNum = 1;
getList();
};
/** 格式化日期为 x年x月x日 */ /** 格式化日期为 x年x月x日 */
const formatDateCN = (dateStr: string | undefined) => { const formatDateCN = (dateStr: string | undefined) => {
if (!dateStr) return ''; if (!dateStr) return '';
@ -296,6 +321,11 @@ const getLevelCellStyle = (level: string) => {
<el-option v-for="item in streetOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictLabel" /> <el-option v-for="item in streetOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictLabel" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="执法次数">
<el-input v-model="queryParams.numLeft" placeholder="最小" clearable @keyup.enter="handleQuery" style="width: 70px" />
<span style="margin: 0 4px">-</span>
<el-input v-model="queryParams.numRight" placeholder="最大" clearable @keyup.enter="handleQuery" style="width: 70px" />
</el-form-item>
<div style="width: 100%; height: 0;"></div> <div style="width: 100%; height: 0;"></div>
<el-form-item label="时段"> <el-form-item label="时段">
<el-select v-model="timeMode" style="width: 90px" @change="handleTimeModeChange"> <el-select v-model="timeMode" style="width: 90px" @change="handleTimeModeChange">
@ -328,7 +358,7 @@ const getLevelCellStyle = (level: string) => {
<!-- 表格区域 --> <!-- 表格区域 -->
<el-card shadow="hover"> <el-card shadow="hover">
<el-table ref="tableRef" v-loading="loading" :data="tableData" :height="tableHeight" border stripe> <el-table ref="tableRef" v-loading="loading" :data="tableData" :height="tableHeight" border stripe @sort-change="handleSortChange">
<el-table-column label="区域" prop="district" width="100" align="center" /> <el-table-column label="区域" prop="district" width="100" align="center" />
<el-table-column label="街道" prop="street" width="120" align="center" /> <el-table-column label="街道" prop="street" width="120" align="center" />
<el-table-column label="企业名称" prop="enterpriseName" min-width="200" :show-overflow-tooltip="true" /> <el-table-column label="企业名称" prop="enterpriseName" min-width="200" :show-overflow-tooltip="true" />
@ -336,7 +366,7 @@ const getLevelCellStyle = (level: string) => {
<el-table-column label="执法人员" prop="lawEnforcer" min-width="120" :show-overflow-tooltip="true" /> <el-table-column label="执法人员" prop="lawEnforcer" min-width="120" :show-overflow-tooltip="true" />
<el-table-column label="执法事项" prop="lawEnforcementItemText" min-width="150" :show-overflow-tooltip="true" /> <el-table-column label="执法事项" prop="lawEnforcementItemText" min-width="150" :show-overflow-tooltip="true" />
<el-table-column label="执法时间" prop="enforcementTime" width="110" align="center" /> <el-table-column label="执法时间" prop="enforcementTime" width="110" align="center" />
<el-table-column label="执法次数" width="100" align="center"> <el-table-column label="执法次数" width="120" align="center" sortable="custom">
<template #default="{ row }"> <template #default="{ row }">
<el-link type="success" :underline="false" @click="handleShowDetail(row)"> <el-link type="success" :underline="false" @click="handleShowDetail(row)">
{{ row.totalCount }} {{ row.totalCount }}

126
zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/statistics_enforcement_complaint/index.vue

@ -4,11 +4,11 @@ import { ElMessage } from 'element-plus';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { getDicts } from '@/api/system/dict/data'; import { getDicts } from '@/api/system/dict/data';
import { import {
getOverview, getGroupList, getDetailList, getOverview, getGroupList, getDetailList, getComplaintDetail,
getTypeStatList, getAreaStatList, getStreetStatList, getTypeStatList, getAreaStatList, getStreetStatList,
exportGroupList, exportTypeStat, exportAreaStat, exportStreetStat exportGroupList, exportTypeStat, exportAreaStat, exportStreetStat
} from '@/api/efcode/report/statistics-complaint'; } from '@/api/efcode/report/statistics-complaint';
import type { ComplaintQuery, ComplaintGroupVo, ComplaintDetailVo, ComplaintTypeStatVo } from '@/api/efcode/report/statistics-complaint'; import type { ComplaintQuery, ComplaintGroupVo, ComplaintDetailVo, ComplaintTypeStatVo, ComplaintHandleVo } from '@/api/efcode/report/statistics-complaint';
// 当前Tab // 当前Tab
const activeTab = ref('tab1'); const activeTab = ref('tab1');
@ -76,6 +76,13 @@ const detailPageNum = ref(1);
const detailPageSize = ref(10); const detailPageSize = ref(10);
const currentDeptId = ref(''); const currentDeptId = ref('');
// 投诉举报详情弹窗
const complaintDetailVisible = ref(false);
const complaintDetailLoading = ref(false);
const complaintDetailTab = ref('progress'); // progress / info
const complaintDetailData = ref<ComplaintDetailVo | null>(null);
const complaintHandleList = ref<ComplaintHandleVo[]>([]);
// 字典 // 字典
const caseTypeOptions = ref<any[]>([]); const caseTypeOptions = ref<any[]>([]);
const addressTypeOptions = ref<any[]>([]); const addressTypeOptions = ref<any[]>([]);
@ -216,6 +223,32 @@ const handleDetailPageChange = () => {
loadDetailData(); loadDetailData();
}; };
/** 点击详情按钮 → 弹出投诉举报详情 */
const handleShowComplaintDetail = async (row: ComplaintDetailVo) => {
complaintDetailTab.value = 'progress';
complaintDetailVisible.value = true;
complaintDetailLoading.value = true;
try {
const res = await getComplaintDetail(row.id);
complaintDetailData.value = (res.data?.detail || res.data) as ComplaintDetailVo;
complaintHandleList.value = (res.data?.handleList || res.data?.progress || []) as ComplaintHandleVo[];
} catch (e) {
// 接口未连通,降级使用当前行数据 + mock 兜底
complaintDetailData.value = { ...row };
complaintHandleList.value = mockHandleList(row);
} finally {
complaintDetailLoading.value = false;
}
};
/** mock 处置进度兜底数据(接口未连通时使用) */
const mockHandleList = (row: ComplaintDetailVo): ComplaintHandleVo[] => [
{ handleDepartment: '系统自动评价', handleOpinion: '系统自动评价', handleTime: row.complaintTime },
{ handleDepartment: '市统一受理分拨平台', handleOpinion: '复核通过,办结工单。', handleTime: row.complaintTime },
{ handleDepartment: row.complaintDepartment, handleOpinion: '已受理,正在核实处理中。', handleTime: row.complaintTime },
{ handleDepartment: '市统一受理分拨平台', handleOpinion: '工单已派发至相关部门,请及时跟进处理。', handleTime: row.complaintTime }
];
/** Tab1导出 */ /** Tab1导出 */
const handleExportTab1 = async () => { const handleExportTab1 = async () => {
try { try {
@ -409,11 +442,79 @@ const downloadBlob = (res: any, filename: string) => {
<el-table-column label="被投诉对象" prop="complaintDepartment" min-width="160" :show-overflow-tooltip="true" /> <el-table-column label="被投诉对象" prop="complaintDepartment" min-width="160" :show-overflow-tooltip="true" />
<el-table-column label="投诉类型" prop="enforceTypeText" width="140" align="center" /> <el-table-column label="投诉类型" prop="enforceTypeText" width="140" align="center" />
<el-table-column label="投诉时间" prop="complaintTime" width="170" align="center" /> <el-table-column label="投诉时间" prop="complaintTime" width="170" align="center" />
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="{ row }">
<el-button type="primary" link :underline="false" @click="handleShowComplaintDetail(row)">详情</el-button>
</template>
</el-table-column>
</el-table> </el-table>
<pagination v-show="detailTotal > 0" <pagination v-show="detailTotal > 0"
v-model:page="detailPageNum" v-model:limit="detailPageSize" v-model:page="detailPageNum" v-model:limit="detailPageSize"
:total="detailTotal" @pagination="handleDetailPageChange" /> :total="detailTotal" @pagination="handleDetailPageChange" />
</el-dialog> </el-dialog>
<!-- 投诉举报详情弹窗 -->
<el-dialog v-model="complaintDetailVisible" title="投诉举报详情" width="680px" destroy-on-close append-to-body>
<div v-loading="complaintDetailLoading">
<el-tabs v-model="complaintDetailTab">
<!-- 处置进度 -->
<el-tab-pane label="处置进度" name="progress">
<el-timeline v-if="complaintHandleList.length > 0" class="complaint-timeline">
<el-timeline-item
v-for="(item, index) in complaintHandleList"
:key="index"
:type="index === 0 ? 'primary' : 'success'"
:hollow="index === 0"
>
<div class="timeline-title">{{ item.handleDepartment }}</div>
<div class="timeline-row"><span class="timeline-label">处理部门:</span>{{ item.handleDepartment }}</div>
<div class="timeline-row"><span class="timeline-label">处理意见:</span>{{ item.handleOpinion }}</div>
<div class="timeline-row"><span class="timeline-label">处理时间:</span>{{ item.handleTime }}</div>
</el-timeline-item>
</el-timeline>
<el-empty v-else description="暂无处置进度" />
</el-tab-pane>
<!-- 投诉详情 -->
<el-tab-pane label="投诉详情" name="info">
<el-descriptions
v-if="complaintDetailData"
:column="1"
border
:label-style="{ width: '120px', textAlign: 'right', backgroundColor: '#f5f7fa' }"
>
<el-descriptions-item label="投诉部门">{{ complaintDetailData.complaintDepartment || '-' }}</el-descriptions-item>
<el-descriptions-item label="投诉时间">{{ complaintDetailData.complaintTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="投诉类型">{{ complaintDetailData.enforceTypeText || '-' }}</el-descriptions-item>
<el-descriptions-item label="投诉内容">{{ complaintDetailData.complaintContent || '-' }}</el-descriptions-item>
<el-descriptions-item label="涉事地址">{{ complaintDetailData.enterpriseAddress || '-' }}</el-descriptions-item>
<el-descriptions-item label="附件">
<span v-if="complaintDetailData.attachment">
<el-link type="primary" :underline="false">{{ complaintDetailData.attachment }}</el-link>
</span>
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="授权状态">
<el-checkbox
:model-value="complaintDetailData.authStatus == 1"
disabled
>同意</el-checkbox>
<el-checkbox
:model-value="complaintDetailData.authStatus != 1"
disabled
>不同意</el-checkbox>
</el-descriptions-item>
<el-descriptions-item label="联系人">{{ complaintDetailData.contactPerson || '-' }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ complaintDetailData.contactPhone || '-' }}</el-descriptions-item>
</el-descriptions>
<el-empty v-else description="暂无详情数据" />
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<el-button @click="complaintDetailVisible = false">关闭</el-button>
</template>
</el-dialog>
</el-tab-pane> </el-tab-pane>
<!-- ==================== Tab2: 投诉类型统计 ==================== --> <!-- ==================== Tab2: 投诉类型统计 ==================== -->
@ -494,4 +595,25 @@ const downloadBlob = (res: any, filename: string) => {
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.complaint-timeline {
padding: 16px 12px;
.timeline-title {
font-weight: 600;
font-size: 15px;
margin-bottom: 8px;
color: #303133;
}
.timeline-row {
font-size: 13px;
color: #606266;
line-height: 1.8;
word-break: break-all;
}
.timeline-label {
color: #909399;
}
}
</style> </style>

69
zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/report/statistics_inspection_plan/index.vue

@ -5,7 +5,7 @@ import * as echarts from 'echarts';
import { import {
getOverview, getGroupList, getDetailList, getOverview, getGroupList, getDetailList,
getRankByYear, getRankByQuarter, getRankByMonth, getRankByYear, getRankByQuarter, getRankByMonth,
exportGroupList exportGroupList, downloadAttachment
} from '@/api/efcode/report/statistics-inspection-plan'; } from '@/api/efcode/report/statistics-inspection-plan';
import type { import type {
InspectionPlanQuery, InspectionPlanOverviewVo, InspectionPlanGroupVo, InspectionPlanQuery, InspectionPlanOverviewVo, InspectionPlanGroupVo,
@ -194,6 +194,54 @@ const handleDetailPageChange = () => {
loadDetailData(); loadDetailData();
}; };
/** 根据附件名判断是否为浏览器可直接预览的类型 */
const canPreview = (filename?: string) => {
if (!filename) return false;
const ext = filename.split('.').pop()?.toLowerCase();
return ['pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'txt', 'mp4', 'webm', 'ogg'].includes(ext || '');
};
/** 预览附件:图片/PDF等浏览器原生支持的直接打开,其他类型降级为下载 */
const handlePreview = async (row: InspectionPlanDetailVo) => {
if (!row.attachment) {
ElMessage.warning('暂无附件');
return;
}
// 优先使用后端返回的直链 URL
if (row.attachmentUrl) {
window.open(row.attachmentUrl, '_blank');
return;
}
// 通过下载接口获取 blob 后用 blob URL 打开(支持跨域)
try {
const res = await downloadAttachment(row.id);
const url = window.URL.createObjectURL(res as Blob);
const win = window.open(url, '_blank');
if (!win) {
ElMessage.warning('浏览器阻止了新窗口打开,请允许弹窗后重试');
}
// 窗口关闭后释放
win?.addEventListener('beforeunload', () => window.URL.revokeObjectURL(url));
} catch (e) {
ElMessage.error('预览失败');
}
};
/** 下载附件 */
const handleDownload = async (row: InspectionPlanDetailVo) => {
if (!row.attachment) {
ElMessage.warning('暂无附件');
return;
}
try {
const res = await downloadAttachment(row.id);
// blob 响应拦截器已返回 res.data(Blob 对象),直接下载
downloadBlob(res, row.attachment);
} catch (e) {
ElMessage.error('下载失败');
}
};
/** Tab1导出 */ /** Tab1导出 */
const handleExportTab1 = async () => { const handleExportTab1 = async () => {
try { try {
@ -410,12 +458,21 @@ const downloadBlob = (res: any, filename: string) => {
</el-card> </el-card>
<!-- 明细弹窗 --> <!-- 明细弹窗 -->
<el-dialog v-model="detailDialogVisible" :title="detailTitle" width="750px" destroy-on-close> <el-dialog v-model="detailDialogVisible" :title="detailTitle" width="820px" destroy-on-close>
<el-table :data="detailTableData" border stripe max-height="400"> <el-table :data="detailTableData" border stripe max-height="400">
<el-table-column label="计划名称" prop="planName" min-width="280" :show-overflow-tooltip="true" /> <el-table-column label="计划名称" prop="planName" min-width="240" :show-overflow-tooltip="true" />
<el-table-column label="执法单位" prop="bureauDeptName" width="160" :show-overflow-tooltip="true" /> <el-table-column label="执法单位" prop="bureauDeptName" width="140" :show-overflow-tooltip="true" />
<el-table-column label="执法区域" prop="enforcementArea" width="120" align="center" /> <el-table-column label="执法区域" prop="enforcementArea" width="110" align="center" />
<el-table-column label="发布时间" prop="enforcementTime" width="170" align="center" /> <el-table-column label="发布时间" prop="enforcementTime" width="165" align="center" />
<el-table-column label="附件" width="100" align="center" fixed="right">
<template #default="{ row }">
<template v-if="row.attachment">
<el-button type="primary" link :underline="false" @click="handlePreview(row)">预览</el-button>
<el-button type="primary" link :underline="false" @click="handleDownload(row)">下载</el-button>
</template>
<span v-else class="text-gray-400">-</span>
</template>
</el-table-column>
</el-table> </el-table>
<pagination v-show="detailTotal > 0" <pagination v-show="detailTotal > 0"
v-model:page="detailPageNum" v-model:limit="detailPageSize" v-model:page="detailPageNum" v-model:limit="detailPageSize"

Loading…
Cancel
Save