Browse Source

执法历史查询

dev-V1.10.2-temp
wangmingyong@163.com 3 months ago
parent
commit
8880acc45c
  1. 254
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/components/EnforcementRecordList.vue
  2. 424
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/query/enforcementQuery.vue

254
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/components/EnforcementRecordList.vue

@ -13,29 +13,40 @@
<!-- 执法记录列表 -->
<div class="record-list">
<van-loading v-if="loading" class="loading-state" type="spinner">加载中...</van-loading>
<template v-else>
<div v-for="(item, index) in cityRecords" :key="index" class="record-card-item">
<div class="record-header">
<div class="record-date">{{ item.createTime }}</div>
<div class="record-company">{{ enterpriseName }}</div>
<div class="record-count">{{ item.caseType || '执法检查' }}</div>
<template v-else-if="cityRecords.length > 0">
<div class="record-card">
<!-- 卡片头部 -->
<div class="record-card-header">
<div class="header-time">{{ cityRecords[0].createTime }}</div>
<div class="header-company">{{ enterpriseName }}</div>
<div class="header-count">被执法次数:{{ cityRecords.length }}次</div>
</div>
<div class="record-timeline" v-if="isRecordExpanded">
<div class="timeline-item">
<!-- 时间轴记录 -->
<div class="record-card-body" v-if="isExpanded">
<div v-for="(item, index) in cityRecords" :key="index" class="record-timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-line" v-if="index < cityRecords.length - 1"></div>
<div class="timeline-content">
<div class="timeline-time">{{ item.createTime }}</div>
<div class="timeline-unit"><span class="unit-label">执法单位:</span>{{
item.enforcementUnit ||
item.unitName }}</div>
<div class="record-time">{{ item.createTime }}</div>
<div class="record-unit"><span class="unit-label">执法单位:</span>{{ item.bureauDeptName || item.unitName }}</div>
</div>
</div>
</div>
<div class="record-expand" @click="$emit('toggle-expand')">
<van-icon :name="isRecordExpanded ? 'arrow-up' : 'arrow-down'" />
<!-- 展开/收起按钮 -->
<div class="card-toggle-btn" @click="isExpanded = !isExpanded">
<van-icon :name="isExpanded ? 'arrow-up' : 'arrow-down'" />
</div>
<!-- 查看更多 -->
<div class="card-load-more" v-if="isExpanded && hasMore" @click="loadMore">
<span v-if="!loadingMore">查看更多</span>
<span v-else class="loading-text">加载中...</span>
</div>
<div class="card-no-more" v-else-if="isExpanded && cityRecords.length > 0">
<span>没有更多了</span>
</div>
</div>
</template>
<van-empty v-else description="暂无执法记录" />
</div>
</div>
</template>
@ -56,31 +67,38 @@ const props = defineProps({
enterpriseNumber: {
type: String,
default: ''
},
isRecordExpanded: {
type: Boolean,
default: false
}
});
defineEmits(['toggle-expand']);
// ========== 数据 ==========
const recordTab = ref(0);
const cityRecords = ref([]);
const loading = ref(false);
const isExpanded = ref(true);
// ========== 分页 ==========
const pageNum = ref(1);
const pageSize = ref(10);
const hasMore = ref(true);
const loadingMore = ref(false);
// ========== 获取执法记录数据 ==========
const fetchEnfRegistrationList = () => {
const fetchEnfRegistrationList = (isLoadMore = false) => {
if (!props.enterpriseNumber) {
cityRecords.value = [];
return;
}
loading.value = true;
if (isLoadMore) {
loadingMore.value = true;
} else {
loading.value = true;
}
// warnType:1=全市,0=本单位(与后端 AppEnforcementRegistrationController 一致)
const params = {
warnType: recordTab.value === 0 ? 1 : 0,
enterpriseNumber: props.enterpriseNumber,
pageNum: pageNum.value,
pageSize: pageSize.value,
};
// 本单位:传执法单位 id,优先 bureauUnitId,否则用当前用户 deptId
if (recordTab.value === 1) {
@ -90,27 +108,61 @@ const fetchEnfRegistrationList = () => {
}
}
queryEnfRegistrationList(params, (res) => {
loading.value = false;
if (isLoadMore) {
loadingMore.value = false;
} else {
loading.value = false;
}
if (res.code === 200) {
const payload = res.data || {};
const rows = payload.list || payload.rows || (Array.isArray(payload) ? payload : []);
cityRecords.value = (Array.isArray(rows) ? rows : []).filter(
const pageData = (Array.isArray(rows) ? rows : []).filter(
item => item !== null && item !== undefined
);
if (isLoadMore) {
cityRecords.value = [...cityRecords.value, ...pageData];
} else {
cityRecords.value = pageData;
}
// 判断是否还有更多数据
const total = payload.total || 0;
if (pageData.length === 0 || pageData.length < pageSize.value || (total > 0 && cityRecords.value.length >= total)) {
hasMore.value = false;
} else {
hasMore.value = true;
pageNum.value++;
}
} else {
cityRecords.value = [];
if (!isLoadMore) {
cityRecords.value = [];
}
console.error('获取执法记录失败:', res.msg);
}
}, (err) => {
loading.value = false;
cityRecords.value = [];
if (isLoadMore) {
loadingMore.value = false;
} else {
loading.value = false;
cityRecords.value = [];
}
hasMore.value = false;
console.error('获取执法记录接口失败', err);
});
};
// ========== 加载更多 ==========
const loadMore = () => {
if (loadingMore.value || !hasMore.value) return;
fetchEnfRegistrationList(true);
};
// ========== 切换执法记录 Tab ==========
const handleRecordTabChange = (tabIndex) => {
recordTab.value = tabIndex;
pageNum.value = 1;
hasMore.value = true;
cityRecords.value = [];
fetchEnfRegistrationList();
};
@ -119,6 +171,8 @@ watch(
() => props.enterpriseNumber,
(val) => {
if (val) {
pageNum.value = 1;
hasMore.value = true;
fetchEnfRegistrationList();
} else {
cityRecords.value = [];
@ -177,94 +231,137 @@ watch(
}
}
.record-card-item {
.record-card {
background: #fff;
border-radius: 8px;
padding: 0.3rem;
margin: 10px;
margin-top: 20px;
box-shadow: 0 0px 8px #e5e5e5;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.record-header {
margin-bottom: 0.3rem;
.record-card-header {
padding: 16px;
.record-date {
font-size: 14px;
.header-time {
font-size: 12px;
color: #999;
margin-bottom: 0.15rem;
margin-bottom: 6px;
}
.record-company {
.header-company {
font-size: 16px;
color: #000;
font-weight: 600;
padding: 10px 0px 10px 20px;
letter-spacing: 0.05rem;
color: #333;
margin-bottom: 6px;
}
.record-count {
font-size: 14px;
color: #393939dd;
padding-left: 20px;
letter-spacing: 0.02rem;
.header-count {
font-size: 13px;
color: #666;
}
}
.record-timeline {
position: relative;
margin-left: 28px;
margin-top: 26px;
.record-card-body {
padding: 16px;
padding-bottom: 24px;
}
.timeline-item {
.card-toggle-btn {
display: flex;
align-items: flex-start;
padding: 0.1rem 0;
justify-content: center;
align-items: center;
padding: 8px 0 12px;
color: #999;
font-size: 18px;
cursor: pointer;
&:active {
color: #666;
}
}
.card-load-more {
display: flex;
justify-content: center;
align-items: center;
padding: 8px 0 16px;
font-size: 14px;
color: #1367fe;
cursor: pointer;
text-decoration: underline;
&:active {
color: #0d5ce0;
}
.loading-text {
color: #999;
text-decoration: none;
pointer-events: none;
}
}
.card-no-more {
display: flex;
justify-content: center;
align-items: center;
padding: 8px 0 16px;
font-size: 12px;
color: #999;
}
.record-timeline-item {
position: relative;
padding-left: 24px;
padding-bottom: 16px;
&:last-child {
padding-bottom: 0;
.timeline-line {
display: none;
}
}
.timeline-dot {
width: 10px;
height: 10px;
position: absolute;
left: 6px;
top: 6px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #1367fe;
margin-right: 0.2rem;
flex-shrink: 0;
position: relative;
z-index: 1;
top: 4px;
}
.timeline-line {
position: absolute;
left: 9px;
top: 14px;
bottom: 0;
width: 2px;
background: #e0e0e0;
}
.timeline-content {
flex: 1;
min-width: 0;
padding-left: 8px;
.timeline-time {
font-size: 16px;
color: #ccc;
font-weight: 400;
.record-time {
font-size: 14px;
color: #333;
margin-bottom: 4px;
}
.timeline-unit {
font-size: 14px;
color: #999;
padding: 10px 0px;
.record-unit {
font-size: 12px;
color: #666;
.unit-label {
color: #000;
color: #999;
}
}
}
}
.record-expand {
display: flex;
justify-content: center;
padding: 0.2rem 0;
color: #ccc;
font-size: 0.4rem;
}
.loading-state {
display: flex;
justify-content: center;
@ -274,6 +371,7 @@ watch(
.record-list {
height: calc(100vh - 150px);
overflow-y: auto;
}
}
</style>

424
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/query/enforcementQuery.vue

@ -24,40 +24,42 @@
<!-- 可滚动列表区域 -->
<div class="list-container">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list v-model:loading="loading" :finished="finished" finished-text="" @load="onLoad" :offset="50">
<!-- 空状态 -->
<van-empty v-if="finished && list.length === 0" description="暂无数据" />
<!-- 列表项 -->
<div class="enterprise-card" v-for="item in list" :key="item.id" @click="toggleExpand(item.id)">
<div class="enterprise-header">
<span class="enterprise-date">{{ item.enforcementTime || item.createTime || '-' }}</span>
</div>
<div class="enterprise-body">
<div class="enterprise-name">{{ item.enterpriseName || item.name || '-' }}</div>
<div class="enterprise-count">被执法次数:{{ item.enforceCount || item.count || 0 }}次</div>
</div>
<div class="enterprise-arrow">
<van-icon :name="expandedIds.includes(item.id) ? 'arrow-up' : 'arrow-down'" />
</div>
<!-- 展开内容
<div class="enterprise-detail" v-if="expandedIds.includes(item.id)">
<div class="detail-item">
<span class="detail-label">执法单位:</span>
<span>{{ item.lawEnforcement || '-' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">执法人员:</span>
<span>{{ getEnforcerDisplay(item) }}</span>
</div>
<div class="detail-item">
<span class="detail-label">执法项目:</span>
<span>{{ item.lawEnforcementItemText || '-' }}</span>
<!-- 未搜索状态 -->
<van-empty v-if="!hasSearched" description="请输入企业名称进行查询" />
<!-- 已搜索但无数据 -->
<van-empty v-else-if="groupedList.length === 0" description="暂无数据" />
<!-- 企业卡片(按企业分组) -->
<div class="enterprise-card" v-for="group in groupedList" :key="group.enterpriseNumber || group.enterpriseName">
<div class="expand-btn" @click="toggleGroup(group.enterpriseNumber || group.enterpriseName)">
<van-icon :name="expandedGroups.includes(group.enterpriseNumber || group.enterpriseName) ? 'arrow-up' : 'arrow-down'" />
</div>
<div class="enterprise-header">
<div class="enterprise-name">{{ group.enterpriseName || '-' }}</div>
<div class="enterprise-count">被执法次数:{{ group.records.length }}次</div>
</div>
<!-- 该企业的多条执法记录(时间轴风格) -->
<div class="record-timeline" v-if="expandedGroups.includes(group.enterpriseNumber || group.enterpriseName)">
<div class="record-item" v-for="(item, idx) in group.records" :key="item.id">
<div class="timeline-dot"></div>
<div class="timeline-line" v-if="idx < group.records.length - 1"></div>
<div class="timeline-content">
<div class="record-time">{{ item.createTime || item.enforcementTime || '-' }}</div>
<div class="record-unit">执法单位:{{ item.bureauDeptName || item.unitName || '-' }}</div>
</div>
</div> -->
</div>
</div>
</van-list>
</div>
<!-- 查看更多按钮 -->
<div class="load-more-wrapper" v-if="hasSearched && groupedList.length > 0 && !finished" @click="loadMore">
<span class="load-more-link" :class="{ loading: loading }">
{{ loading ? '加载中...' : '查看更多' }}
</span>
</div>
<div class="load-more-wrapper" v-else-if="hasSearched && groupedList.length > 0 && finished">
<span class="no-more-text">没有更多了</span>
</div>
</van-pull-refresh>
</div>
<van-calendar v-model:show="dateShow" type="range" @confirm="onConfirm" :min-date="minDate"
@ -81,13 +83,15 @@
</template>
<script setup name="enforcementQuery">
import { ref, computed, onMounted } from 'vue';
import { ref, computed } from 'vue';
import { showToast } from 'vant';
import 'vant/es/toast/style';
import FooterBar from '@/components/FooterBar.vue';
import { queryEnfRegistrationList } from '@/api/enforcement/index';
// ========== 列表状态 ==========
const list = ref([]);
const loading = ref(false);
const finished = ref(false);
const refreshing = ref(false);
const searchText = ref('');
@ -95,6 +99,12 @@ const searchText = ref('');
const pageNum = ref(1);
const pageSize = ref(10);
// ========== 是否已搜索 ==========
const hasSearched = ref(false);
// ========== 列表是否结束(未搜索时默认为true,防止触发加载)==========
const finished = ref(true);
// ========== 日期筛选 ==========
const dateType = ref(null);
const dateSearch = ref(null);
@ -109,18 +119,43 @@ const countMin = ref('');
const countMax = ref('');
// ========== 展开状态 ==========
const expandedIds = ref([]);
const expandedGroups = ref([]);
// ========== 切换展开/收起 ==========
const toggleExpand = (id) => {
const index = expandedIds.value.indexOf(id);
// ========== 切换展开/收起企业 ==========
const toggleGroup = (key) => {
const index = expandedGroups.value.indexOf(key);
if (index > -1) {
expandedIds.value.splice(index, 1);
expandedGroups.value.splice(index, 1);
} else {
expandedIds.value.push(id);
expandedGroups.value.push(key);
}
};
// ========== 按企业分组数据 ==========
const groupedList = computed(() => {
const groups = {};
list.value.forEach(item => {
const key = item.enterpriseNumber || item.enterpriseName || '未知企业';
if (!groups[key]) {
groups[key] = {
enterpriseNumber: item.enterpriseNumber,
enterpriseName: item.enterpriseName,
records: [],
};
}
groups[key].records.push(item);
});
// 按时间倒序排列每个企业的记录
Object.values(groups).forEach(group => {
group.records.sort((a, b) => {
const timeA = new Date(a.createTime || a.enforcementTime || 0);
const timeB = new Date(b.createTime || b.enforcementTime || 0);
return timeB - timeA;
});
});
return Object.values(groups);
});
// ========== 提示信息文本 ==========
const noticeText = computed(() => {
if (dateSearchDisplay.value) {
@ -155,127 +190,47 @@ const getEnforcerDisplay = (item) => {
return names.length ? names.join('、') : '-';
};
// ========== 模拟数据 ==========
const mockData = [
{
id: 1,
enforcementTime: '2026-04-28',
enterpriseName: '深圳市地铁集团有限公司',
enforceCount: 53,
lawEnforcement: '市交通运输局',
lawEnforcerDisplay: '张三、李四',
lawEnforcementItemText: '行政检查'
},
{
id: 2,
enforcementTime: '2026-05-19',
enterpriseName: '中建科工集团有限公司',
enforceCount: 13,
lawEnforcement: '市住房和建设局',
lawEnforcerDisplay: '王五、赵六',
lawEnforcementItemText: '行政处罚'
},
{
id: 3,
enforcementTime: '2026-05-08',
enterpriseName: '深圳市建工集团股份有限公司',
enforceCount: 9,
lawEnforcement: '市应急管理局',
lawEnforcerDisplay: '孙七、周八',
lawEnforcementItemText: '行政强制'
},
{
id: 4,
enforcementTime: '2026-03-15',
enterpriseName: '深圳市科技创新有限公司',
enforceCount: 25,
lawEnforcement: '市市场监督管理局',
lawEnforcerDisplay: '吴九、郑十',
lawEnforcementItemText: '行政许可'
},
{
id: 5,
enforcementTime: '2026-04-22',
enterpriseName: '深圳市商贸发展有限公司',
enforceCount: 7,
lawEnforcement: '市商务局',
lawEnforcerDisplay: '冯十一、陈十二',
lawEnforcementItemText: '行政检查'
},
{
id: 6,
enforcementTime: '2026-02-10',
enterpriseName: '深圳市制造业有限公司',
enforceCount: 31,
lawEnforcement: '市工业和信息化局',
lawEnforcerDisplay: '褚十三、卫十四',
lawEnforcementItemText: '行政处罚'
},
{
id: 7,
enforcementTime: '2026-01-20',
enterpriseName: '深圳市文化创意有限公司',
enforceCount: 5,
lawEnforcement: '市文化广电旅游体育局',
lawEnforcerDisplay: '蒋十五、沈十六',
lawEnforcementItemText: '行政确认'
},
{
id: 8,
enforcementTime: '2025-12-08',
enterpriseName: '深圳市教育培训有限公司',
enforceCount: 18,
lawEnforcement: '市教育局',
lawEnforcerDisplay: '韩十七、杨十八',
lawEnforcementItemText: '行政检查'
}
];
// ========== 加载数据(使用模拟数据)==========
// ========== 加载数据 ==========
const fetchList = () => {
loading.value = true;
setTimeout(() => {
const startIndex = (pageNum.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
let filteredData = [...mockData];
// 关键字搜索过滤
if (searchText.value) {
filteredData = filteredData.filter(item =>
item.enterpriseName.includes(searchText.value)
);
}
// 次数范围过滤
if (countMin.value || countMax.value) {
const min = countMin.value ? parseInt(countMin.value) : 0;
const max = countMax.value ? parseInt(countMax.value) : Infinity;
filteredData = filteredData.filter(item =>
item.enforceCount >= min && item.enforceCount <= max
);
}
// 分页切片
const pageData = filteredData.slice(startIndex, endIndex);
if (refreshing.value) {
list.value = pageData;
refreshing.value = false;
const params = {
warnType: 1,
pageNum: pageNum.value,
pageSize: pageSize.value,
};
if (searchText.value) {
params.enterpriseName = searchText.value;
}
queryEnfRegistrationList(params, (res) => {
if (res.code === 200) {
const payload = res.data || {};
const rows = payload.list || payload.rows || (Array.isArray(payload) ? payload : []);
const pageData = Array.isArray(rows) ? rows : [];
if (refreshing.value) {
list.value = pageData;
refreshing.value = false;
} else {
list.value = [...list.value, ...pageData];
}
// 判断是否加载完毕
const total = payload.total || 0;
if (pageData.length === 0 || (total > 0 && list.value.length >= total) || pageData.length < pageSize.value) {
finished.value = true;
} else {
pageNum.value++;
}
} else {
list.value = [...list.value, ...pageData];
}
// 判断是否加载完毕
if (pageData.length === 0 || list.value.length >= filteredData.length) {
finished.value = true;
} else {
pageNum.value++;
showToast(res.msg || '加载失败');
}
loading.value = false;
}, 500);
}, (err) => {
loading.value = false;
finished.value = true;
showToast('加载失败');
});
};
// ========== 日历确认(格式化 yyyy-MM-DD)==========
const onConfirm = (dates) => {
@ -292,14 +247,18 @@ const onConfirm = (dates) => {
dateShow.value = false;
onSearch();
};
// ========== 触底加载更多 ==========
const onLoad = () => {
if (refreshing.value) return;
// ========== 点击查看更多 ==========
const loadMore = () => {
if (loading.value || finished.value) return;
fetchList();
};
// ========== 下拉刷新 ==========
const onRefresh = () => {
if (!hasSearched.value) {
refreshing.value = false;
return;
}
pageNum.value = 1;
list.value = [];
finished.value = false;
@ -309,6 +268,11 @@ const onRefresh = () => {
// ========== 搜索 ==========
const onSearch = () => {
if (!searchText.value.trim()) {
showToast('请输入企业名称');
return;
}
hasSearched.value = true;
pageNum.value = 1;
list.value = [];
finished.value = false;
@ -319,6 +283,7 @@ const onSearch = () => {
// ========== 次数筛选查询 ==========
const onFilterSearch = () => {
showDatePicker.value = false;
hasSearched.value = true;
pageNum.value = 1;
list.value = [];
finished.value = false;
@ -329,25 +294,22 @@ const onFilterSearch = () => {
// ========== 清空搜索 ==========
const onClear = () => {
searchText.value = '';
onSearch();
hasSearched.value = false;
pageNum.value = 1;
list.value = [];
finished.value = true;
};
// ========== 日期类型切换 ==========
const searchDateType = (val) => {
dateType.value = val;
// ========== 打开日历选择日期 ==========
const searchDateType = (type) => {
dateType.value = type;
dateShow.value = true;
onSearch();
};
// ========== 打开月份选择器 ==========
// ========== 打开次数筛选弹窗 ==========
const openDatePicker = () => {
dateType.value = 9;
showDatePicker.value = true;
};
onMounted(() => {
fetchList();
});
</script>
<style lang="less" scoped>
@ -428,67 +390,95 @@ onMounted(() => {
margin-bottom: 12px;
padding: 16px;
position: relative;
}
.expand-btn {
position: absolute;
right: 12px;
top: 12px;
width: 28px;
height: 28px;
border-radius: 50%;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
color: #666;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
z-index: 10;
&:active {
background: #f8f9fa;
background: #e0e0e0;
}
}
.enterprise-header {
margin-bottom: 8px;
.enterprise-date {
color: #999;
font-size: 14px;
}
}
.enterprise-body {
margin-left: 10px;
padding: 10px;
.enterprise-name {
font-size: 16px;
font-weight: 560;
color: #333;
margin-bottom: 6px;
line-height: 1.4;
padding: 10px 0px;
}
.enterprise-count {
font-size: 13px;
color: #666;
margin-bottom: 20px;
}
}
.enterprise-arrow {
position: absolute;
left: 45%;
bottom: 16px;
color: #999;
font-size: 14px;
/* ===== 时间轴样式 ===== */
.record-timeline {
border-top: 1px solid #f0f0f0;
padding-top: 12px;
}
.enterprise-detail {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #f0f0f0;
.record-item {
position: relative;
padding-left: 24px;
padding-bottom: 16px;
.detail-item {
display: flex;
font-size: 13px;
color: #666;
line-height: 1.8;
&:last-child {
padding-bottom: 0;
.detail-label {
color: #999;
white-space: nowrap;
margin-right: 8px;
min-width: 70px;
.timeline-line {
display: none;
}
}
.timeline-dot {
position: absolute;
left: 6px;
top: 6px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #1367fe;
}
.timeline-line {
position: absolute;
left: 9px;
top: 16px;
width: 2px;
height: calc(100% - 8px);
background: #e0e0e0;
}
.timeline-content {
padding-left: 8px;
}
.record-time {
font-size: 14px;
color: #333;
margin-bottom: 4px;
}
.record-unit {
font-size: 12px;
color: #999;
}
}
/* ===== Vant 搜索框样式覆盖 ===== */
@ -558,4 +548,30 @@ onMounted(() => {
height: 40px;
}
}
/* ===== 查看更多按钮 ===== */
.load-more-wrapper {
display: flex;
justify-content: center;
align-items: center;
padding: 16px 0;
.load-more-link {
font-size: 14px;
color: #1367fe;
cursor: pointer;
text-decoration: underline;
&.loading {
color: #999;
text-decoration: none;
pointer-events: none;
}
}
.no-more-text {
font-size: 13px;
color: #999;
}
}
</style>
Loading…
Cancel
Save