Browse Source

查询和计划页面的修改

dev-V1.0.1
wangmingyong 5 months ago
parent
commit
714b7d5b84
  1. 2
      zdxt-web-client/zdxt-efcode-enforcement-app/src/components/FooterBar.vue
  2. 11
      zdxt-web-client/zdxt-efcode-enforcement-app/src/router/enforcement-app/enf-main-router.js
  3. 0
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/enforce/enforcementList.vue
  4. 746
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/program/index.vue
  5. 552
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/query/enforcementQuery.vue
  6. 162
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/statistics/statisticsAnalysis.vue

2
zdxt-web-client/zdxt-efcode-enforcement-app/src/components/FooterBar.vue

@ -37,6 +37,8 @@ const tabs = [
path: '<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>' },
{ key: 'message', label: '消息', route: '/enforcement/message',
path: '<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/>' },
{ key: 'program', label: '计划', route: '/enforcement/program',
path: '<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/>' },
{ key: 'my', label: '我的', route: '/enforcement/my',
path: '<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>' },
];

11
zdxt-web-client/zdxt-efcode-enforcement-app/src/router/enforcement-app/enf-main-router.js

@ -63,6 +63,15 @@ const routes = [
isKeepAlive: true,
},
},
// 执法端-计划(tabbar)
{
path: "/enforcement/program",
name: "enforcementProgram",
component: () => import("@/views/enforcement/program"),
meta: {
isKeepAlive: true,
},
},
// 执法端-计划列表
{
path: "/enforcement/plan",
@ -94,7 +103,7 @@ const routes = [
{
path: "/enforcement/enforcementList",
name: "enforcementList",
component: () => import("@/views/enforcement/enforce/enforcementRegistrationList.vue"),
component: () => import("@/views/enforcement/enforce/enforcementList.vue"),
meta: {
isKeepAlive: false,
},

0
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/enforce/enforcementRegistrationList.vue → zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/enforce/enforcementList.vue

746
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/program/index.vue

@ -0,0 +1,746 @@
<template>
<div class="program-page">
<!-- 顶部Tab切换 -->
<div class="tab-header">
<div class="tab-item" :class="{ active: activeTab === 'create' }" @click="activeTab = 'create'">
创建执法计划
</div>
<div class="tab-item" :class="{ active: activeTab === 'query' }" @click="activeTab = 'query'">
查询执法计划
</div>
</div>
<!-- 内容区域 -->
<div class="content-wrapper">
<!-- 创建表单 -->
<div class="form-container" v-if="activeTab === 'create'">
<div class="form-wrapper">
<van-form @submit="submitForm">
<!-- 计划名称 -->
<van-field v-model="form.planName" label="计划名称" placeholder="请选择计划名称" required is-link readonly
@click="showYearPicker = true" :rules="[{ required: true, message: '请选择计划名称' }]" />
<!-- 计划周期 -->
<van-field v-model="form.planPeriod" label="计划周期 " placeholder="请选择年/季/F/" required is-link
readonly @click="showPeriodPicker = true" class="period-field"
:rules="[{ required: true, message: '请选择计划周期' }]" />
<!-- 提示信息 -->
<div class="notice-bar">
<van-icon name="info-o" />
<span>深圳市司法局行政执法计划</span>
</div>
<!-- 执法单位 -->
<van-field v-model="form.enforcementUnit" label="执法单位" placeholder="深圳市司法局" readonly
:rules="[{ required: true, message: '请选择执法单位' }]" required />
<!-- 上传时间 -->
<van-field v-model="form.uploadTime" label="上传时间" placeholder="" readonly
:rules="[{ required: true, message: '请选择上传时间' }]" required />
<!-- 执法区域 -->
<van-field v-model="form.enforcementArea" label="执法区域" placeholder="请输入"
:rules="[{ required: true, message: '请输入执法区域' }]" required />
<!-- 附件上传 -->
<div class="upload-section">
<div class="upload-label">
<span class="required">*</span>
附&nbsp;&nbsp;&nbsp;件
<span class="upload-hint">(仅限PDF文档,5MB以下)</span>
<a href="#" class="template-download" @click.prevent="downloadTemplate">
<van-icon name="down" /> 模板下载
</a>
</div>
<van-uploader v-model="fileList" :max-count="1" :max-size="5 * 1024 * 1024"
@oversize="onOversize" accept=".pdf" :after-read="afterRead">
<div class="upload-area">
<van-icon name="photograph" size="40" color="#dcdee0" />
<p>点击上传</p>
</div>
</van-uploader>
</div>
<!-- 提示信息 -->
<div class="notice-bar upload-notice">
<van-icon name="info-o" />
<span>仅支持上传5MB以下的PDF格式文档</span>
</div>
<div class="submit-section">
<van-button type="primary" block round class="submit-btn" native-type="submit">
发布
</van-button>
</div>
</van-form>
</div>
</div>
<!-- 发布按钮 -->
<!-- 查询列表 -->
<div class="query-container" v-if="activeTab === 'query'">
<!-- 搜索栏 -->
<van-search v-model="querySearchText" show-action placeholder="请输入计划名称/执法单位/区域查询" clearable
clear-trigger="always" @clear="onQueryClear" @search="onQuerySearch">
<template #action>
<div @click="onQuerySearch">搜索</div>
</template>
</van-search>
<!-- 日期快捷筛选按钮 -->
<div class="search-button-list">
<van-button plain hairline :class="queryDateType === null ? 'search-btn active' : 'search-btn'"
@click="querySearchDateType(null)" round type="primary" size="mini">全部</van-button>
<van-button plain hairline :class="queryDateType === 1 ? 'search-btn active' : 'search-btn'"
@click="querySearchDateType(1)" round type="primary" size="mini">本月</van-button>
<van-button plain hairline :class="queryDateType === 2 ? 'search-btn active' : 'search-btn'"
@click="querySearchDateType(2)" round type="primary" size="mini">本季</van-button>
<van-button plain hairline :class="queryDateType === 3 ? 'search-btn active' : 'search-btn'"
@click="querySearchDateType(3)" round type="primary" size="mini">本年</van-button>
<van-button plain hairline :class="queryDateType === 9 ? 'search-btn active' : 'search-btn'"
@click="openQueryDatePicker" round type="primary" size="mini">筛选</van-button>
</div>
<!-- 列表区域 -->
<div class="plan-list">
<van-pull-refresh v-model="queryRefreshing" @refresh="onQueryRefresh">
<van-list v-model:loading="queryLoading" :finished="queryFinished" finished-text=""
@load="onQueryLoad" :offset="50">
<van-empty v-if="queryFinished && queryList.length === 0" description="暂无数据" />
<div class="plan-card" v-for="item in queryList" :key="item.id">
<div class="plan-time">发布时间:{{ item.publishTime }}</div>
<div class="plan-title">{{ item.title }}</div>
<div class="plan-info">
<span class="info-label">执法单位:</span>
<span>{{ item.enforcementUnit }}</span>
</div>
<div class="plan-info">
<span class="info-label">执法区域:</span>
<span>{{ item.area }}</span>
</div>
<div class="plan-info">
<span class="info-label">附&nbsp;&nbsp;&nbsp;件:</span>
<a href="#" class="file-link" @click.prevent="downloadFile(item)">{{ item.fileName
}}</a>
</div>
<div class="plan-actions">
<a href="#" class="cancel-link" @click.prevent="cancelPublish(item)">
<van-icon name="close" /> 取消公开
</a>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
<!-- 日历选择器 -->
<van-calendar v-model:show="showQueryCalendar" type="range" @confirm="onQueryDateConfirm"
:min-date="queryMinDate" :max-date="queryMaxDate" />
</div>
</div>
<!-- 底部导航栏 -->
<FooterBar active="program" />
<!-- 年份选择器 -->
<van-popup v-model:show="showYearPicker" position="bottom" round>
<van-picker :columns="yearColumns" title="选择年份" @confirm="onYearConfirm" @cancel="showYearPicker = false" />
</van-popup>
<!-- 周期选择器 -->
<van-popup v-model:show="showPeriodPicker" position="bottom" round>
<van-picker :columns="periodColumns" title="选择周期" @confirm="onPeriodConfirm"
@cancel="showPeriodPicker = false" />
</van-popup>
</div>
</template>
<script setup name="enforcementProgram">
import { ref, reactive, onMounted } from 'vue';
import { showToast } from 'vant';
import FooterBar from '@/components/FooterBar.vue';
const activeTab = ref('create');
const form = reactive({
planName: '',
planPeriod: '',
enforcementUnit: '深圳市司法局',
uploadTime: new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).replace(/\//g, '-'),
enforcementArea: ''
});
const fileList = ref([]);
const showYearPicker = ref(false);
const showPeriodPicker = ref(false);
const yearColumns = [
{ text: '2026年', value: '2026' },
{ text: '2027年', value: '2027' },
{ text: '2028年', value: '2028' }
];
const periodColumns = [
{ text: '年度', value: 'year' },
{ text: '第一季度', value: 'Q1' },
{ text: '第二季度', value: 'Q2' },
{ text: '第三季度', value: 'Q3' },
{ text: '第四季度', value: 'Q4' }
];
const onYearConfirm = ({ selectedOptions }) => {
form.planName = selectedOptions[0]?.text || '';
showYearPicker.value = false;
};
const onPeriodConfirm = ({ selectedOptions }) => {
form.planPeriod = selectedOptions[0]?.text || '';
showPeriodPicker.value = false;
};
const afterRead = (file) => {
file.status = 'uploading';
file.message = '上传中...';
setTimeout(() => {
file.status = 'done';
file.message = '';
}, 1000);
};
const onOversize = () => {
showToast('文件大小不能超过 5MB');
};
const downloadTemplate = () => {
showToast('模板下载功能开发中');
};
const submitForm = () => {
if (!form.planName) {
showToast('请选择计划年份');
return;
}
if (!form.planPeriod) {
showToast('请选择计划周期');
return;
}
if (!form.enforcementArea) {
showToast('请输入执法区域');
return;
}
if (fileList.value.length === 0) {
showToast('请上传附件');
return;
}
showToast('发布成功');
};
// ========== 查询列表相关 ==========
const querySearchText = ref('');
const queryDateType = ref(null);
const showQueryCalendar = ref(false);
const queryMinDate = new Date(2024, 0, 1);
const queryMaxDate = new Date(2030, 11, 31);
// 列表状态
const queryList = ref([]);
const queryLoading = ref(false);
const queryFinished = ref(false);
const queryRefreshing = ref(false);
// 分页参数
const queryPageNum = ref(1);
const queryPageSize = ref(10);
// 模拟数据
const mockPlanData = [
{
id: 1,
publishTime: '2026-01-27 16:36:57',
title: '深圳市司法局2026年一季度行政执法计划',
enforcementUnit: '深圳市司法局',
area: '0127测试区域',
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf'
},
{
id: 2,
publishTime: '2026-01-20 09:49:26',
title: '深圳市司法局2027年一季度行政执法计划',
enforcementUnit: '深圳市司法局',
area: '测试区域0120',
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf'
},
{
id: 2,
publishTime: '2026-01-20 09:49:26',
title: '深圳市司法局2027年一季度行政执法计划',
enforcementUnit: '深圳市司法局',
area: '测试区域0120',
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf'
},
{
id: 2,
publishTime: '2026-01-20 09:49:26',
title: '深圳市司法局2027年一季度行政执法计划',
enforcementUnit: '深圳市司法局',
area: '测试区域0120',
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf'
},
{
id: 2,
publishTime: '2026-01-20 09:49:26',
title: '深圳市司法局2027年一季度行政执法计划',
enforcementUnit: '深圳市司法局',
area: '测试区域0120',
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf'
},
{
id: 2,
publishTime: '2026-01-20 09:49:26',
title: '深圳市司法局2027年一季度行政执法计划',
enforcementUnit: '深圳市司法局',
area: '测试区域0120',
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf'
},
{
id: 2,
publishTime: '2026-01-20 09:49:26',
title: '深圳市司法局2027年一季度行政执法计划',
enforcementUnit: '深圳市司法局',
area: '测试区域0120',
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf'
},
];
// 加载数据
const fetchQueryList = () => {
queryLoading.value = true;
setTimeout(() => {
const startIndex = (queryPageNum.value - 1) * queryPageSize.value;
const endIndex = startIndex + queryPageSize.value;
let filteredData = [...mockPlanData];
// 关键字搜索过滤
if (querySearchText.value) {
filteredData = filteredData.filter(item =>
item.title.includes(querySearchText.value) ||
item.enforcementUnit.includes(querySearchText.value) ||
item.area.includes(querySearchText.value)
);
}
// 分页切片
const pageData = filteredData.slice(startIndex, endIndex);
if (queryRefreshing.value) {
queryList.value = pageData;
queryRefreshing.value = false;
} else {
queryList.value = [...queryList.value, ...pageData];
}
// 判断是否加载完毕
if (pageData.length === 0 || queryList.value.length >= filteredData.length) {
queryFinished.value = true;
} else {
queryPageNum.value++;
}
queryLoading.value = false;
}, 500);
};
// 触底加载更多
const onQueryLoad = () => {
if (queryRefreshing.value) return;
fetchQueryList();
};
// 下拉刷新
const onQueryRefresh = () => {
queryPageNum.value = 1;
queryList.value = [];
queryFinished.value = false;
queryLoading.value = true;
fetchQueryList();
};
// 搜索
const onQuerySearch = () => {
queryPageNum.value = 1;
queryList.value = [];
queryFinished.value = false;
queryLoading.value = true;
fetchQueryList();
};
// 清空搜索
const onQueryClear = () => {
querySearchText.value = '';
onQuerySearch();
};
// 日期类型切换
const querySearchDateType = (val) => {
queryDateType.value = val;
if (val !== null && val !== 9) {
onQuerySearch();
} else if (val === null) {
onQuerySearch();
} else {
showQueryCalendar.value = true;
}
};
// 打开日期选择器
const openQueryDatePicker = () => {
queryDateType.value = 9;
showQueryCalendar.value = true;
};
// 日历确认
const onQueryDateConfirm = (dates) => {
const [start, end] = dates;
const format = (date) => {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
};
showToast(`已选择:${format(start)} 至 ${format(end)}`);
showQueryCalendar.value = false;
onQuerySearch();
};
// 下载文件
const downloadFile = (item) => {
showToast(`正在下载:${item.fileName}`);
};
// 取消公开
const cancelPublish = (item) => {
showToast('取消公开功能开发中');
};
// 初始化加载
onMounted(() => {
fetchQueryList();
});
</script>
<style lang="less" scoped>
.program-page {
display: flex;
flex-direction: column;
min-height: 100vh;
background: #f5f7fa;
}
.tab-header {
display: flex;
background: #fff;
border-bottom: 1px solid #ebedf0;
flex-shrink: 0;
.tab-item {
flex: 1;
text-align: center;
padding: 14px 0;
font-size: 15px;
color: #666;
position: relative;
cursor: pointer;
transition: all 0.3s;
&.active {
color: #000;
font-weight: 500;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 160px;
height: 1px;
background: #1989fa;
border-radius: 2px;
}
}
}
}
.content-wrapper {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
max-height: calc(100vh - 44px - 50px);
}
.form-container {
background: #fff;
margin-top: 10px;
padding: 4px;
flex: 1;
overflow-y: auto;
}
.period-field {
:deep(.van-field__label) {
display: none;
}
:deep(.van-cell__value) {
padding-left: 0;
}
}
.notice-bar {
display: flex;
align-items: center;
gap: 6px;
padding: 10px 16px;
margin: 0 6px;
background: #fffbe8;
color: #ed6a0c;
font-size: 13px;
border-radius: 8px;
.van-icon {
font-size: 16px;
}
&.upload-notice {
margin: 6px;
}
}
.upload-section {
padding: 16px 0;
.upload-label {
display: flex;
align-items: center;
margin-bottom: 12px;
font-size: 14px;
color: #323233;
.required {
color: #ee0a24;
margin-right: 4px;
}
.upload-hint {
color: #969799;
font-size: 12px;
margin-left: 4px;
}
.template-download {
margin-left: auto;
color: #ed6a0c;
font-size: 13px;
display: flex;
align-items: center;
gap: 4px;
&:active {
opacity: 0.7;
}
}
}
.upload-area {
width: 120px;
height: 120px;
border: 1px dashed #dcdee0;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
&:active {
background: #f7f8fa;
border-color: #1989fa;
}
p {
margin-top: 8px;
font-size: 12px;
color: #969799;
}
}
:deep(.van-uploader) {
width: auto;
}
:deep(.van-uploader__preview) {
margin: 0;
}
:deep(.van-uploader__preview-image) {
width: 120px;
height: 120px;
}
}
.submit-section {
padding: 30px 8px 20px;
.submit-btn {
height: 44px;
font-size: 16px;
letter-spacing: 4px;
}
}
.query-container {
padding: 0;
background: #f5f7fa;
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
:deep(.van-search) {
flex-shrink: 0;
background: #fff;
}
.search-button-list {
display: flex;
gap: 8px;
padding: 12px 16px 4px 16px;
flex-wrap: wrap;
flex-shrink: 0;
.search-btn {
padding: 0 12px;
height: 28px;
font-size: 13px;
color: #666;
border-color: #ebebeb;
background: #ebebeb;
&.active {
color: #1989fa;
border-color: #1989fa;
background: #e8f4ff;
}
}
}
.plan-list {
padding: 10px;
flex: 1;
overflow-y: auto;
}
.plan-card {
background: #fff;
border-radius: 8px;
padding: 16px;
margin-bottom: 10px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
.plan-time {
font-size: 13px;
color: #969799;
margin-bottom: 10px;
}
.plan-title {
font-size: 16px;
font-weight: 560;
color: #000;
line-height: 1.5;
padding: 20px 0px;
border-top: 1px solid #ebedf0;
}
.plan-info {
display: flex;
align-items: center;
font-size: 14px;
color: #646566;
margin-bottom: 8px;
line-height: 1.6;
.info-label {
flex-shrink: 0;
color: #333;
padding: 4px 0px;
}
.file-link {
color: #1989fa;
text-decoration: none;
word-break: break-all;
&:active {
opacity: 0.7;
}
}
}
.plan-actions {
text-align: right;
margin-top: 12px;
border-top: 1px solid #ebedf0;
.cancel-link {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 13px;
color: #969799;
text-decoration: none;
&:active {
opacity: 0.7;
}
}
}
}
}
/* ===== Vant 搜索框样式覆盖 ===== */
:deep(.van-search__content) {
height: 0.9rem;
border: 1px solid #ececec;
border-radius: 0.15rem 0 0 0.15rem;
}
:deep(.van-search__action) {
margin: 0 3% 0 0;
width: 10%;
height: 0.9rem;
background: #1367fe;
border: 0.01rem solid #1367fe;
border-radius: 0 0.15rem 0.15rem 0;
color: #fff;
text-align: center;
line-height: 0.9rem;
}
</style>

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

@ -1,43 +1,561 @@
<template>
<div class="query-page app_content">
<div class="page-header">
<div class="page-title">查询</div>
<div class="registration-list-page">
<!-- 搜索栏 -->
<van-search v-model="searchText" show-action placeholder="企业名称" clearable clear-trigger="always"
@clear="onClear" @search="onSearch">
<template #action>
<div @click="onSearch">搜索</div>
</template>
</van-search>
<!-- 日期快捷筛选按钮 -->
<div class="search-button-list">
<van-button plain hairline :class="dateType === 3 ? 'search-btn active' : 'search-btn'"
@click="searchDateType(3)" round type="primary" size="mini">自定义</van-button>
<van-button plain hairline :class="dateType === 9 ? 'search-btn active' : 'search-btn'"
@click="openDatePicker" round type="primary" size="mini">次数</van-button>
</div>
<div class="page-body">
<van-empty description="查询功能开发中" />
<div class="date-picker-container">
<van-notice-bar left-icon="info-o">
{{ noticeText }}
</van-notice-bar>
</div>
<!-- 可滚动列表区域 -->
<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>
</div>
</div> -->
</div>
</van-list>
</van-pull-refresh>
</div>
<van-calendar v-model:show="dateShow" type="range" @confirm="onConfirm" :min-date="minDate"
:max-date="maxDate" />
<van-popup v-model:show="showDatePicker" position="bottom" :style="{ height: '30%' }">
<div class="filter-popup-content">
<div class="filter-row">
<span class="filter-label">次数:</span>
<input type="text" v-model="countMin" class="filter-input" placeholder="最小值">
<span class="filter-separator">-</span>
<input type="text" v-model="countMax" class="filter-input" placeholder="最大值">
</div>
<div class="filter-btn-row">
<van-button type="primary" size="small" @click="onFilterSearch">查询</van-button>
</div>
</div>
</van-popup>
<!-- 底部导航栏 -->
<FooterBar active="query" />
</div>
</template>
<script setup name="enforcementQuery">
import { ref, computed, onMounted } from 'vue';
import FooterBar from '@/components/FooterBar.vue';
// ========== 列表状态 ==========
const list = ref([]);
const loading = ref(false);
const finished = ref(false);
const refreshing = ref(false);
const searchText = ref('');
// ========== 分页参数 ==========
const pageNum = ref(1);
const pageSize = ref(10);
// ========== 日期筛选 ==========
const dateType = ref(null);
const dateSearch = ref(null);
const dateSearchDisplay = ref('');
const dateShow = ref(false);
const showDatePicker = ref(false);
const minDate = new Date(2020, 0, 1);
const maxDate = new Date(2030, 11, 31);
// ========== 次数筛选 ==========
const countMin = ref('');
const countMax = ref('');
// ========== 展开状态 ==========
const expandedIds = ref([]);
// ========== 切换展开/收起 ==========
const toggleExpand = (id) => {
const index = expandedIds.value.indexOf(id);
if (index > -1) {
expandedIds.value.splice(index, 1);
} else {
expandedIds.value.push(id);
}
};
// ========== 提示信息文本 ==========
const noticeText = computed(() => {
if (dateSearchDisplay.value) {
return dateSearchDisplay.value;
}
if (countMin.value || countMax.value) {
const min = countMin.value || '0';
const max = countMax.value || '不限';
return `次数:${min} - ${max}`;
}
return '请输入关键字查询或选择筛选条件';
});
// ========== 格式化日期 ==========
const formatDate = (dateStr) => {
if (!dateStr) return '-';
const d = new Date(dateStr.replace ? dateStr.replace(/-/g, '/') : dateStr);
if (isNaN(d.getTime())) return dateStr;
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
};
// ========== 执法人员展示 ==========
const getEnforcerDisplay = (item) => {
if (item.lawEnforcerDisplay) return item.lawEnforcerDisplay;
const names = [];
if (item.lawEnforcer) names.push(item.lawEnforcer);
if (item.lawEnforcerTwo) names.push(item.lawEnforcerTwo);
if (Array.isArray(item.lawEnforcer2List)) names.push(...item.lawEnforcer2List);
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;
} else {
list.value = [...list.value, ...pageData];
}
// 判断是否加载完毕
if (pageData.length === 0 || list.value.length >= filteredData.length) {
finished.value = true;
} else {
pageNum.value++;
}
loading.value = false;
}, 500);
};
// ========== 日历确认(格式化 yyyy-MM-DD)==========
const onConfirm = (dates) => {
const [start, end] = dates;
const format = (date) => {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
};
dateSearchDisplay.value = `${format(start)} 至 ${format(end)}`;
dateSearch.value = `${format(start)}_${format(end)}`;
dateType.value = 3;
dateShow.value = false;
onSearch();
};
// ========== 触底加载更多 ==========
const onLoad = () => {
if (refreshing.value) return;
fetchList();
};
// ========== 下拉刷新 ==========
const onRefresh = () => {
pageNum.value = 1;
list.value = [];
finished.value = false;
loading.value = true;
fetchList();
};
// ========== 搜索 ==========
const onSearch = () => {
pageNum.value = 1;
list.value = [];
finished.value = false;
loading.value = true;
fetchList();
};
// ========== 次数筛选查询 ==========
const onFilterSearch = () => {
showDatePicker.value = false;
pageNum.value = 1;
list.value = [];
finished.value = false;
loading.value = true;
fetchList();
};
// ========== 清空搜索 ==========
const onClear = () => {
searchText.value = '';
onSearch();
};
// ========== 日期类型切换 ==========
const searchDateType = (val) => {
dateType.value = val;
dateShow.value = true;
onSearch();
};
// ========== 打开月份选择器 ==========
const openDatePicker = () => {
dateType.value = 9;
showDatePicker.value = true;
};
onMounted(() => {
fetchList();
});
</script>
<style lang="less" scoped>
.query-page {
.registration-list-page {
display: flex;
flex-direction: column;
height: 100%;
background: #f0f6ff;
background-color: #ebf3fb;
padding-bottom: calc(56px + env(safe-area-inset-bottom));
.list-container {
flex: 1;
overflow-y: auto;
padding: 0 0.27rem;
margin-top: 0.27rem;
}
}
.date-picker-container {
margin: 0px 10px;
:deep(.van-notice-bar) {
border-radius: 8px;
height: 28px;
font-size: 12px;
padding: 0px 10px;
}
}
.page-header {
height: 44px;
line-height: 44px;
/* ===== 搜索按钮行 ===== */
.search-button-list {
display: flex;
flex-direction: row;
padding: 0.2rem 0.27rem 0;
gap: 0.15rem;
margin-bottom: 8px;
.search-btn {
width: 60px;
height: 20px;
background: #ebebeb;
border-color: #ebebeb;
color: #999;
font-size: 0.3rem;
margin: 4px 0px;
}
.search-btn.active {
background-color: #d9e6fd;
border-color: #75a5fb;
color: #1367fe;
}
}
/* ===== Vant 搜索框样式覆盖 ===== */
:deep(.van-search__content) {
height: 0.9rem;
border: 1px solid #ececec;
border-radius: 0.15rem 0 0 0.15rem;
}
:deep(.van-search__action) {
margin: 0 3% 0 0;
width: 10%;
height: 0.9rem;
background: #1367fe;
border: 0.01rem solid #1367fe;
border-radius: 0 0.15rem 0.15rem 0;
color: #fff;
text-align: center;
background: linear-gradient(180deg, #2b7de9 0%, #5ba3f5 100%);
line-height: 0.9rem;
}
/* ===== 企业卡片样式 ===== */
.enterprise-card {
background: #fff;
border-radius: 8px;
margin-bottom: 12px;
padding: 16px;
position: relative;
cursor: pointer;
transition: all 0.2s;
&:active {
background: #f8f9fa;
}
}
.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;
}
.enterprise-detail {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #f0f0f0;
.detail-item {
display: flex;
font-size: 13px;
color: #666;
line-height: 1.8;
.detail-label {
color: #999;
white-space: nowrap;
margin-right: 8px;
min-width: 70px;
}
}
}
/* ===== Vant 搜索框样式覆盖 ===== */
:deep(.van-search__content) {
height: 0.9rem;
border: 1px solid #ececec;
border-radius: 0.15rem 0 0 0.15rem;
}
:deep(.van-search__action) {
margin: 0 3% 0 0;
width: 10%;
height: 0.9rem;
background: #1367fe;
border: 0.01rem solid #1367fe;
border-radius: 0 0.15rem 0.15rem 0;
color: #fff;
font-size: 17px;
font-weight: 500;
text-align: center;
line-height: 0.9rem;
}
/* ===== 筛选弹窗样式 ===== */
.filter-popup-content {
padding: 20px 16px;
}
.page-body {
flex: 1;
overflow-y: auto;
.filter-row {
display: flex;
align-items: center;
margin-bottom: 16px;
.filter-label {
font-size: 14px;
color: #333;
white-space: nowrap;
margin-right: 8px;
}
.filter-input {
width: 80px;
height: 36px;
border: 1px solid #e5e5e5;
border-radius: 6px;
padding: 0 12px;
font-size: 14px;
outline: none;
&:focus {
border-color: #1989fa;
}
}
.filter-separator {
margin: 0 10px;
color: #999;
font-size: 14px;
}
}
.filter-btn-row {
display: flex;
justify-content: center;
padding-top: 8px;
:deep(.van-button) {
width: 120px;
height: 40px;
}
}
</style>
</style>

162
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/statistics/statisticsAnalysis.vue

@ -1,8 +1,5 @@
<template>
<div class="statistics-page">
<!-- 顶部导航栏
<van-nav-bar title="统计分析" left-text="返回" left-arrow @click-left="goBack" /> -->
<!-- 内容区域 -->
<div class="content-area">
<!-- 顶部切换卡 -->
@ -67,27 +64,11 @@
<div class="section-value">-</div>
</div>
<div class="joint-stats">
<div class="joint-item">
<div class="joint-label">执法预告</div>
<div class="joint-value">{{ animatedNotice }}</div>
</div>
<div class="joint-item">
<div class="joint-label">委托执法</div>
<div class="joint-value">{{ animatedEntrusted > 0 ? animatedEntrusted : '-' }}</div>
</div>
<div class="joint-item">
<div class="joint-label">评价</div>
<div class="joint-value">{{ animatedEvaluate }}</div>
</div>
<div class="joint-item">
<div class="joint-label">投诉</div>
<div class="joint-value" :class="{ 'text-red': animatedComplaint > 0 }">
{{ animatedComplaint > 0 ? animatedComplaint : '-' }}
<div class="joint-item" v-for="(item, index) in jointStatsList" :key="index">
<div class="joint-label">{{ item.label }}</div>
<div class="joint-value" :class="{ 'text-red': item.isRed }">
{{ item.value }}
</div>
</div>
</div>
</div>
@ -97,34 +78,10 @@
<!-- Tab 切换 -->
<div class="tabs-section">
<van-tabs v-model:active="activeTab" animated>
<van-tab title="执法类别">
<div class="tab-content">
<div class="category-grid" v-if="lawCategoryList.length > 0">
<div class="category-item" v-for="(item, index) in lawCategoryList" :key="index">
<div class="category-name">{{ item.name }}</div>
<div class="category-value">{{ item.value }}</div>
<div class="category-sub" v-if="item.subValue">{{ item.subValue }}</div>
</div>
</div>
<van-empty v-else description="暂无数据" image="search" />
</div>
</van-tab>
<van-tab title="企业类别">
<van-tab v-for="(tab, index) in tabConfig" :key="index" :title="tab.title">
<div class="tab-content">
<div class="category-grid" v-if="enterpriseCategoryList.length > 0">
<div class="category-item" v-for="(item, index) in enterpriseCategoryList" :key="index">
<div class="category-name">{{ item.name }}</div>
<div class="category-value">{{ item.value }}</div>
<div class="category-sub" v-if="item.subValue">{{ item.subValue }}</div>
</div>
</div>
<van-empty v-else description="暂无数据" image="search" />
</div>
</van-tab>
<van-tab title="投诉类别">
<div class="tab-content">
<div class="category-grid" v-if="complaintCategoryList.length > 0">
<div class="category-item" v-for="(item, index) in complaintCategoryList" :key="index">
<div class="category-grid" v-if="currentTabData.length > 0">
<div class="category-item" v-for="(item, idx) in currentTabData" :key="idx">
<div class="category-name">{{ item.name }}</div>
<div class="category-value">{{ item.value }}</div>
<div class="category-sub" v-if="item.subValue">{{ item.subValue }}</div>
@ -150,50 +107,10 @@
<!-- 年度选择器 -->
<van-popup v-model:show="showYearSelector" position="bottom" round>
<!-- <div class="picker-header">
<div class="picker-cancel" @click="showYearSelector = false">取消</div>
<div class="picker-title">选择年份</div>
<div class="picker-confirm" @click="confirmYear">确定</div>
</div> -->
<van-picker :columns="yearColumns" title="选择年份" v-model="selectedYearPeriod" @confirm="onYearConfirm" />
</van-popup>
<!-- 筛选弹窗 -->
<!-- <van-popup v-model:show="showFilterSelector" position="bottom" round>
<div class="picker-header">
<div class="picker-cancel" @click="showFilterSelector = false">取消</div>
<div class="picker-title">筛选</div>
<div class="picker-confirm" @click="confirmFilter">确定</div>
</div>
<div class="filter-content">
<div class="filter-section">
<div class="filter-section-title">执法类型</div>
<div class="filter-options">
<div class="filter-option" :class="{ active: selectedFilterType.includes('all') }"
@click="toggleFilterType('all')">全部</div>
<div class="filter-option" :class="{ active: selectedFilterType.includes('check') }"
@click="toggleFilterType('check')">行政检查</div>
<div class="filter-option" :class="{ active: selectedFilterType.includes('punish') }"
@click="toggleFilterType('punish')">行政处罚</div>
<div class="filter-option" :class="{ active: selectedFilterType.includes('license') }"
@click="toggleFilterType('license')">行政许可</div>
</div>
</div>
<div class="filter-section">
<div class="filter-section-title">企业规模</div>
<div class="filter-options">
<div class="filter-option" :class="{ active: selectedFilterScale.includes('all') }"
@click="toggleFilterScale('all')">全部</div>
<div class="filter-option" :class="{ active: selectedFilterScale.includes('large') }"
@click="toggleFilterScale('large')">大型企业</div>
<div class="filter-option" :class="{ active: selectedFilterScale.includes('medium') }"
@click="toggleFilterScale('medium')">中型企业</div>
<div class="filter-option" :class="{ active: selectedFilterScale.includes('small') }"
@click="toggleFilterScale('small')">小微企业</div>
</div>
</div>
</div>
</van-popup> -->
<van-calendar v-model:show="showFilterSelector" type="range" allow-same-day
:min-date="new Date(new Date().getFullYear() - 2, 0, 1)" :max-date="new Date()" @confirm="onConfirm" />
</div>
@ -256,19 +173,6 @@ const toggleFilter = (selectedList, value, allKey = 'all') => {
}
};
// 数字动画工具
// const animateNumber = (target, duration = 800) => {
// let start = 0;
// const startTime = performance.now();
// const step = (currentTime) => {
// const elapsed = currentTime - startTime;
// const progress = Math.min(elapsed / duration, 1);
// const easeOut = 1 - Math.pow(1 - progress, 3);
// return Math.round(target * easeOut);
// };
// return step;
// };
// ========== 响应式状态 ==========
const userType = ref('personal');
const period = ref('year');
@ -298,18 +202,42 @@ const lawCategoryList = shallowRef([
]);
const enterpriseCategoryList = shallowRef([
{ name: '工业企业', value: 5, subValue: '45.45%' },
{ name: '商业企业', value: 3, subValue: '27.27%' },
{ name: '服务业', value: 2, subValue: '18.18%' },
{ name: '其他', value: 1, subValue: '9.1%' },
{ name: '有限责任公司', value: 5, subValue: '45.45%' },
{ name: '股份有限公司', value: 3, subValue: '27.27%' },
{ name: '个人独资企业', value: 2, subValue: '18.18%' },
{ name: '合伙企业', value: 1, subValue: '9.1%' },
{ name: '个体工商户', value: 4, subValue: '-' },
{ name: '其他', value: 0, subValue: '-' },
]);
const complaintCategoryList = shallowRef([
{ name: '执法态度', value: 2, subValue: '40%' },
{ name: '执法程序', value: 1, subValue: '20%' },
{ name: '执法结果', value: 2, subValue: '40%' },
{ name: '违规执法', value: 2, subValue: '40%' },
{ name: '入企不扫码', value: 1, subValue: '20%' },
{ name: '违法执法', value: 3, subValue: '40%' },
{ name: '其他', value: 0, subValue: '-' },
]);
// Tab 配置
const tabConfig = [
{ title: '执法类别', key: 'law' },
{ title: '企业类别', key: 'enterprise' },
{ title: '投诉类别', key: 'complaint' },
];
// Tab 数据映射
const tabDataMap = {
law: lawCategoryList,
enterprise: enterpriseCategoryList,
complaint: complaintCategoryList,
};
// 当前Tab数据
const currentTabData = computed(() => {
const currentKey = tabConfig[activeTab.value]?.key;
return currentKey ? tabDataMap[currentKey].value : [];
});
// 弹窗控制
const showMonthSelector = ref(false);
const showQuarterSelector = ref(false);
@ -380,6 +308,14 @@ const periodDisplayText = computed(() => {
}
});
// 联合执法统计数据
const jointStatsList = computed(() => [
{ label: '执法预告', value: animatedNotice.value, isRed: false },
{ label: '委托执法', value: animatedEntrusted.value > 0 ? animatedEntrusted.value : '-', isRed: false },
{ label: '评价', value: animatedEvaluate.value, isRed: false },
{ label: '投诉', value: animatedComplaint.value > 0 ? animatedComplaint.value : '-', isRed: animatedComplaint.value > 0 },
]);
// 年份列(computed 保持响应式)
const yearColumns = computed(() => {
console.log(new Date().getFullYear());
@ -481,7 +417,6 @@ const showPeriodPicker = () => {
// 确认选择
const confirmMonth = (value) => {
// showToast(`已选择${periodDisplayText.value}`);
showMonthSelector.value = false;
};
@ -519,7 +454,7 @@ const onQuarterConfirm = ({ selectedValues }) => {
const onYearConfirm = (value) => {
// van-picker 单列模式下confirm回调参数是对象,包含selectedValues数组
const selectedValues = value.selectedValues || value;
if (typeof selectedValues === 'string') {
selectedYearPeriod.value = [selectedValues];
currentYear.value = Number(selectedValues);
@ -667,12 +602,12 @@ onMounted(() => {
/* ===== 筛选栏 ===== */
.filter-bar {
display: flex;
// align-items: center;
flex-wrap: wrap;
margin-bottom: 0.3rem;
padding: 0 8px;
background: #fff;
border-radius: 0.2rem;
.year-display {
font-size: 16px;
color: var(--primary-dark);
@ -681,6 +616,7 @@ onMounted(() => {
align-items: center;
gap: 4px;
flex: 1;
&:active {
opacity: 0.7;
}

Loading…
Cancel
Save