Browse Source

Merge remote-tracking branch 'origin/dev' into dev

dev-v1.0.2
庄锐波 3 months ago
parent
commit
7eaf5ac8b7
  1. 9
      zdxt-web-client/zdxt-efcode-enforcement-app/src/router/enforcement-app/enf-main-router.js
  2. 946
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/enforce/enforcementRecord.vue

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

@ -290,6 +290,15 @@ const routes = [
isKeepAlive: false,
},
},
// 执法端 - 执法记录页面
{
path: "/enforcement/enforcementRecord",
name: "enforcementRecord",
component: () => import("@/views/enforcement/enforce/enforcementRecord.vue"),
meta: {
isKeepAlive: false,
},
},
];
export { routes };

946
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/enforce/enforcementRecord.vue

@ -0,0 +1,946 @@
<template>
<div class="enforcement-record app_content">
<!-- 步骤条导航 - 固定在顶部 -->
<div class="step-nav" :class="{ 'step-nav-fixed': isNavFixed }">
<div class="step-nav-inner">
<div v-for="(step, index) in steps" :key="step.id" class="step-item"
:class="{ active: activeStep === index }" @click="scrollToSection(step.id, index)">
<div class="step-dot"></div>
<span class="step-label">{{ step.label }}</span>
</div>
</div>
</div>
<!-- 内容区域 -->
<div class="content-area" ref="contentRef">
<!-- 通知栏 -->
<!-- 执法概览统计卡片 -->
<div class="overview-cards" id="section-overview">
<!-- 标题 -->
<div class="overview-title">执法概览</div>
<!-- 第一行三列统计 -->
<div class="stats-row three-col">
<div class="stat-item">
<div class="stat-value">89.5</div>
<div class="stat-label">企业总数</div>
</div>
<div class="stat-item">
<div class="stat-value">3.75万次</div>
<div class="stat-label">单独执法</div>
</div>
<div class="stat-item">
<div class="stat-value">6,563</div>
<div class="stat-label">联合执法</div>
</div>
</div>
<!-- 第二行两列统计 -->
<div class="stats-row two-col">
<div class="stat-item">
<div class="stat-value">89.5</div>
<div class="stat-label">被执法企业</div>
</div>
<div class="stat-item">
<div class="stat-value">89.5%</div>
<div class="stat-label">入企压减率</div>
</div>
</div>
<!-- Tab切换栏执法维度 -->
<van-tabs v-model:active="overviewTab" color="#2563eb" title-active-color="#2563eb"
title-inactive-color="#666" swipeable animated line-width="20px" line-height="2px">
<van-tab title="执法力量"></van-tab>
<van-tab title="执法力度"></van-tab>
<van-tab title="执法频次"></van-tab>
<van-tab title="执法效率"></van-tab>
<van-tab title="执法结果"></van-tab>
</van-tabs>
<!-- 执法力量 -->
<div v-if="overviewTab === 0" class="tab-content-cards">
<div class="large-card">
<div class="large-card-value">100</div>
<div class="large-card-label">执法单位数</div>
</div>
<div class="large-card">
<div class="large-card-value">5000</div>
<div class="large-card-label">执法人员数</div>
</div>
</div>
<!-- 执法力度图表 -->
<div v-if="overviewTab === 1" class="force-chart-wrapper">
<div class="force-chart" ref="forceChartRef"></div>
<div class="force-legend">
<div v-for="(item, index) in forceChartData" :key="index" class="fl-item">
<span class="fl-dot" :style="{ background: item.color }"></span>
<span class="fl-name">{{ item.name }}</span>
<span class="fl-value">{{ item.value }}</span>
<span class="fl-percent">{{ item.percent }}%</span>
</div>
</div>
</div>
<!-- 执法频次 -->
<div v-if="overviewTab === 2" class="tab-content-cards">
<div class="large-card">
<div class="large-card-value">3<span class="large-card-unit">/</span></div>
<div class="large-card-label">涉企检查频次</div>
</div>
<div class="large-card">
<div class="large-card-value">68<span class="large-card-unit">%</span></div>
<div class="large-card-label">重复检查率</div>
</div>
</div>
<!-- 执法效率 -->
<div v-if="overviewTab === 3" class="tab-content-cards">
<div class="large-card">
<div class="large-card-value">10<span class="large-card-unit">小时</span></div>
<div class="large-card-label">涉企行政处罚案件平均办理时长</div>
</div>
<div class="large-card">
<div class="large-card-value">68<span class="large-card-unit">%</span></div>
<div class="large-card-label">执法案件按时办结率</div>
</div>
</div>
<!-- 执法结果 -->
<div v-if="overviewTab === 4" class="tab-content-cards">
<div class="large-card">
<div class="large-card-value">10<span class="large-card-unit">小时</span></div>
<div class="large-card-label">行政处罚撤销率</div>
</div>
<div class="large-card">
<div class="large-card-value">68<span class="large-card-unit">%</span></div>
<div class="large-card-label">行政处罚变更率</div>
</div>
</div>
</div>
<!-- 执法监管 -->
<div class="supervision-section" id="section-supervision">
<div class="section-title">执法监管</div>
<div class="supervision-grid">
<div class="supervision-item">
<div class="sv-value">200<span class="sv-unit"></span></div>
<div class="sv-label">计划总数</div>
</div>
<div class="supervision-item">
<div class="sv-value">200<span class="sv-unit"></span></div>
<div class="sv-label">预告总数</div>
</div>
<div class="supervision-item">
<div class="sv-value">498<span class="sv-unit"></span></div>
<div class="sv-label">涉企执法总数</div>
</div>
<div class="supervision-item">
<div class="sv-value">89<span class="sv-unit"></span></div>
<div class="sv-label">检查评价总数</div>
</div>
<div class="supervision-item">
<div class="sv-value">4<span class="sv-unit"></span></div>
<div class="sv-label">检查投诉总数</div>
</div>
<div class="supervision-item">
<div class="sv-value">89<span class="sv-unit"></span></div>
<div class="sv-label">职权使用数</div>
</div>
</div>
</div>
<!-- 预警态势 -->
<div class="warning-section" id="section-warning">
<div class="section-title">预警态势</div>
<!-- 第一行两列统计 -->
<div class="warning-grid">
<div class="warning-item">
<div class="sv-value">89<span class="sv-unit"></span></div>
<div class="sv-label">风险信用预警</div>
</div>
<div class="warning-item">
<div class="sv-value">95<span class="sv-unit"></span></div>
<div class="sv-label">高额检查预警</div>
</div>
</div>
<!-- 第二行两列 + 中间竖线分隔 -->
<div class="warning-grid warning-grid-divider">
<div class="warning-item">
<div class="sv-value">87<span class="sv-unit"></span></div>
<div class="sv-label">异常人员</div>
</div>
<div class="warning-divider"></div>
<div class="warning-item">
<div class="sv-value">68<span class="sv-unit"></span></div>
<div class="sv-label">异常行为数</div>
</div>
</div>
<!-- Tab切换各区域/各领域 -->
<van-tabs v-model:active="warningTab" color="#2563eb" title-active-color="#2563eb"
title-inactive-color="#666" swipeable animated line-width="20px">
<van-tab title="各区风险态势感知"></van-tab>
<van-tab title="各领域风险态势感知"></van-tab>
</van-tabs>
<!-- 各区表格 -->
<div class="warning-table-wrapper">
<table class="warning-table">
<tbody>
<tr v-for="(row, index) in currentTableData" :key="index">
<td class="wt-name">{{ row.name }}</td>
<td v-for="(status, idx) in row.statuses" :key="idx" :class="'wt-cell wt-' + status">{{
status }}</td>
</tr>
<!-- 时间轴作为最后一行 -->
<tr class="wt-timeline-row">
<td class="wt-name"></td>
<td v-for="(item, idx) in warningTimeline" :key="idx" class="wt-time-cell">{{ item }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</template>
<script setup name="enforcementRecord">
import { ref, reactive, computed, watch, nextTick, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
const activeTab = ref(0);
const overviewTab = ref(0);
const warningTab = ref(0);
const activeStep = ref(0);
const isNavFixed = ref(false);
const contentRef = ref(null);
const forceChartRef = ref(null);
let forceChartInstance = null;
//
const forceChartData = [
{ name: '行政检查', value: 30, percent: 50, color: '#5b8ff9' },
{ name: '行政处罚', value: 9, percent: 15, color: '#5ad8a6' },
{ name: '行政强制', value: 9, percent: 15, color: '#5d7092' },
{ name: '其他', value: 6, percent: 10, color: '#f6bd16' },
];
//
const frequencyData = ref([
{ value: 1200, unit: '次', label: '年度执法总次数' },
{ value: 98.5, unit: '%', label: '计划完成率' },
{ value: 320, unit: '次', label: '月均执法次数' },
{ value: 45, unit: '家', label: '重点企业检查数' },
]);
//
const efficiencyData = ref([
{ value: 2.3, unit: '小时', label: '平均响应时间' },
{ value: 96.2, unit: '%', label: '按时办结率' },
{ value: 4.5, unit: '天', label: '平均结案周期' },
{ value: 99.1, unit: '%', label: '合规率' },
]);
//
const resultData = ref([
{ value: 156, unit: '件', label: '立案总数' },
{ value: 142, unit: '件', label: '已结案数' },
{ value: 91.0, unit: '%', label: '结案率' },
{ value: 28, unit: '万元', label: '罚没金额' },
]);
//
const renderForceChart = () => {
if (!forceChartRef.value) return;
if (forceChartInstance) {
forceChartInstance.dispose();
forceChartInstance = null;
}
forceChartInstance = echarts.init(forceChartRef.value);
const option = {
series: [{
type: 'pie',
radius: ['45%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
label: {
show: true,
position: 'center',
formatter: '{value|250次}\n{label|合计}',
rich: {
value: { fontSize: 20, fontWeight: 'bold', color: '#333', lineHeight: 28 },
label: { fontSize: 12, color: '#999', lineHeight: 18 },
},
},
data: forceChartData.map(item => ({ value: item.value, itemStyle: { color: item.color } })),
}],
};
forceChartInstance.setOption(option);
};
// Tab
watch(overviewTab, (val) => {
if (val === 1) {
nextTick(() => renderForceChart());
} else if (forceChartInstance) {
forceChartInstance.dispose();
forceChartInstance = null;
}
});
//
const steps = [
{ id: 'section-overview', label: '执法概览' },
{ id: 'section-supervision', label: '执法监管' },
{ id: 'section-warning', label: '预警态势' },
];
let observer = null;
//
const scrollToSection = (id, index) => {
//
activeStep.value = index;
const el = document.getElementById(id);
const container = contentRef.value;
if (el && container) {
const navHeight = 10;
const offsetTop = el.offsetTop - navHeight;
container.scrollTo({ top: offsetTop, behavior: 'smooth' });
}
};
//
const handleScroll = () => {
const container = contentRef.value;
if (!container) return;
const scrollTop = container.scrollTop;
isNavFixed.value = scrollTop > 0;
//
const navHeight = 50;
for (let i = 0; i < steps.length; i++) {
const section = document.getElementById(steps[i].id);
if (!section) continue;
const rect = section.getBoundingClientRect();
if (rect.top <= navHeight + 30) {
activeStep.value = i;
}
}
//
if (scrollTop <= 10) {
activeStep.value = 0;
}
};
onMounted(() => {
const container = contentRef.value;
if (container) {
container.addEventListener('scroll', handleScroll, { passive: true });
}
});
onUnmounted(() => {
const container = contentRef.value;
if (container) {
container.removeEventListener('scroll', handleScroll);
}
if (observer) observer.disconnect();
});
//
//
const warningTimeline = ref(['2025Q2', '2025Q3', '2025Q4', '2026Q1']);
//
const warningTableData = ref([
{ name: '福田区', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '罗湖区', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '盐田区', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '南山区', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '宝安区', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '龙岗区', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '龙华区', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '坪山区', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '光明区', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '大鹏新区', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '深汕特别合作区', statuses: ['一般', '良好', '优良', '优良'] },
]);
//
const fieldTableData = ref([
{ name: '应急管理局', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '消防救援', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '住建和建设局', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '生态环境局', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '卫生健康委员会', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '交通运输委员会', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '市场监督管理局', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '城市管理和综合执法局', statuses: ['一般', '良好', '优良', '优良'] },
{ name: '消防救援支队', statuses: ['一般', '良好', '优良', '优良'] },
]);
// Tab
const currentTableData = computed(() => {
return warningTab.value === 0 ? warningTableData.value : fieldTableData.value;
});
</script>
<style lang="less" scoped>
.enforcement-record {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
background: #f0f6ff;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
/* ===== 顶部蓝色背景区域 ===== */
.header-bg {
background: linear-gradient(135deg, #1a4f8e 0%, #2563eb 50%, #3b82f6 100%);
padding: 30px 20px 20px;
position: relative;
overflow: hidden;
&::after {
content: '';
position: absolute;
right: -60px;
top: -60px;
width: 200px;
height: 200px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.06);
}
&::before {
content: '';
position: absolute;
left: -40px;
bottom: -40px;
width: 150px;
height: 150px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.04);
}
}
.header-content {
position: relative;
z-index: 1;
}
.header-title {
font-size: 28px;
font-weight: 700;
color: #fff;
letter-spacing: 2px;
}
.header-subtitle {
font-size: 13px;
color: rgba(255, 255, 255, 0.75);
margin-top: 6px;
letter-spacing: 4px;
}
/* ===== 步骤条导航 ===== */
.step-nav {
position: sticky;
top: 0;
z-index: 100;
background: #fff;
transition: box-shadow 0.2s;
&.step-nav-fixed {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
}
.step-nav-inner {
display: flex;
align-items: center;
justify-content: center;
padding: 10px 16px;
position: relative;
/* 虚线连接线 */
&::before {
content: '';
position: absolute;
left: 20%;
right: 20%;
top: 50%;
height: 1px;
background-image: linear-gradient(to right, #d1d5db 50%, transparent 50%);
background-size: 8px 1px;
background-repeat: repeat-x;
z-index: 0;
}
}
.step-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
cursor: pointer;
padding: 0 20px;
position: relative;
z-index: 1;
flex: 1;
max-width: 120px;
transition: all 0.3s ease;
.step-dot {
width: 14px;
height: 14px;
border-radius: 50%;
border: 2px solid #d1d5db;
background: #fff;
transition: all 0.3s ease;
position: relative;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0);
width: 6px;
height: 6px;
border-radius: 50%;
background: #2563eb;
transition: transform 0.3s ease;
}
}
.step-label {
font-size: 13px;
color: #999;
font-weight: 400;
white-space: nowrap;
transition: color 0.3s ease;
}
&.active {
.step-dot {
border-color: #2563eb;
background: #fff;
&::after {
transform: translate(-50%, -50%) scale(1);
}
}
.step-label {
color: #2563eb;
font-weight: 600;
}
}
}
/* ===== 内容区域 ===== */
.content-area {
position: relative;
z-index: 2;
flex: 1;
overflow-y: auto;
}
/* 概览卡片 */
.overview-cards {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 14px;
background-color: #fff;
padding: 16px 16px;
}
/* 执法概览标题 */
.overview-title {
font-size: 16px;
font-weight: 700;
color: #1a1a1a;
padding-left: 12px;
border-left: 3px solid #2563eb;
line-height: 1;
margin-bottom: 12px;
}
/* 统计行 */
.stats-row {
display: flex;
gap: 10px;
&.three-col {
.stat-item {
flex: 1;
}
}
&.two-col {
.stat-item {
flex: 1;
}
}
.stat-item {
background: #f0f6ff;
border-radius: 8px;
padding: 14px 10px;
text-align: center;
.stat-value {
font-size: 20px;
font-weight: 700;
color: #2563eb;
line-height: 1.3;
}
.stat-label {
font-size: 12px;
color: #666;
margin-top: 4px;
}
}
}
/* 概览区Tab样式覆盖 */
.overview-cards {
:deep(.van-tabs__nav) {
background: transparent !important;
}
:deep(.van-tab) {
font-size: 12px;
padding: 0 8px;
}
:deep(.van-tabs__line) {
width: 20px !important;
height: 2px !important;
background-color: #2563eb;
border-radius: 0;
}
}
/* 执法力度图表 */
.force-chart-wrapper {
display: flex;
align-items: center;
padding: 16px 12px;
background: #fff;
.force-chart {
width: 150px;
height: 150px;
flex-shrink: 0;
}
.force-legend {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
padding-left: 8px;
}
.fl-item {
display: flex;
align-items: center;
font-size: 13px;
}
.fl-dot {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 6px;
flex-shrink: 0;
}
.fl-name {
color: #333;
width: 60px;
flex-shrink: 0;
}
.fl-value {
color: #333;
width: 40px;
text-align: right;
flex-shrink: 0;
}
.fl-percent {
color: #999;
width: 36px;
text-align: right;
flex-shrink: 0;
}
}
/* Tab内容区 */
.tab-content-cards {
display: flex;
flex-direction: column;
gap: 10px;
padding: 8px 0;
.large-card {
width: 100%;
margin: 0;
box-sizing: border-box;
}
}
.tab-content-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
padding: 8px 0;
.stat-card-sm {
background: #f5f9ff;
border-radius: 8px;
padding: 14px 16px;
text-align: center;
.sc-value {
font-size: 20px;
font-weight: 700;
color: #2563eb;
.sc-unit {
font-size: 13px;
font-weight: 400;
color: #666;
}
}
.sc-label {
font-size: 13px;
color: #666;
margin-top: 4px;
}
}
}
/* 大卡片 */
.large-card {
background: #f5f9ff;
border-radius: 8px;
padding: 18px 16px;
text-align: center;
.large-card-value {
font-size: 22px;
font-weight: 700;
color: #2563eb;
line-height: 1.3;
}
.large-card-label {
font-size: 13px;
color: #666;
margin-top: 4px;
}
}
/* ===== 通用区块标题 ===== */
.section-title {
font-size: 16px;
font-weight: 700;
color: #1a1a1a;
padding-left: 12px;
border-left: 3px solid #2563eb;
line-height: 1;
margin-bottom: 12px;
}
/* ===== 执法监管 ===== */
.supervision-section {
margin-bottom: 14px;
background-color: #fff;
padding: 16px 16px;
}
.supervision-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.supervision-item,
.warning-item {
background: #f0f6ff;
border-radius: 8px;
padding: 16px 10px;
text-align: center;
.sv-value {
font-size: 22px;
font-weight: 700;
color: #2563eb;
line-height: 1.3;
.sv-unit {
font-size: 13px;
font-weight: 400;
color: #2563eb;
margin-left: 2px;
}
}
.sv-label {
font-size: 12px;
color: #666;
margin-top: 4px;
}
}
/* ===== 预警态势 ===== */
.warning-section {
margin-bottom: 14px;
background-color: #fff;
padding: 16px 16px;
}
.warning-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
&.warning-grid-divider {
position: relative;
grid-template-columns: 1fr auto 1fr;
background: #f0f6ff;
border-radius: 8px;
margin-top: 10px;
.warning-item {
text-align: center;
.sv-value {
justify-content: center;
font-size: 22px;
}
.sv-label {
margin-top: 4px;
}
}
}
}
.warning-divider {
width: 1px;
align-self: stretch;
background: #c0d5ef;
height: 30px;
margin-top: 22px;
}
/* 预警态势内Tab */
.warning-section {
:deep(.van-tabs__nav) {
background: transparent !important;
}
:deep(.van-tab) {
font-size: 13px;
padding: 0 12px;
}
}
/* 预警态势表格 */
.warning-table-wrapper {
margin-top: 8px;
border-radius: 8px;
overflow: hidden;
background: #fff;
}
.warning-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
th,
td {
padding: 10px 6px;
text-align: center;
border-bottom: 1px solid #f0f0f0;
}
th {
background: #e8f4ff;
color: #666;
font-weight: 500;
font-size: 12px;
}
.wt-name {
color: #333;
font-weight: 500;
text-align: right;
padding-left: 8px;
width: 60px;
}
tbody tr:last-child td {
border-bottom: none;
}
.wt-cell {
text-align: center;
font-size: 13px;
font-weight: 500;
&.wt-一般 {
color: #b45309;
background: #fef3c7;
}
&.wt-良好 {
color: #047857;
background: #d1fae5;
}
&.wt-优良 {
color: #1d4ed8;
background: #dbeafe;
}
}
//
.wt-timeline-row {
border-top: 1px solid #e8e8e8;
td {
padding: 10px 4px;
}
}
.wt-time-cell {
text-align: center;
font-size: 12px;
color: #999;
&.wt-time-active {
color: #2563eb;
font-weight: 600;
}
}
}
</style>
Loading…
Cancel
Save