Browse Source

数据分析,消息接口提交

dev-V1.0.1
wangmingyong 5 months ago
parent
commit
0b2119454b
  1. 14
      zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/index.js
  2. BIN
      zdxt-web-client/zdxt-efcode-enforcement-app/src/assets/images/bg_msg.jpg
  3. BIN
      zdxt-web-client/zdxt-efcode-enforcement-app/src/assets/images/icon_pffw.jpg
  4. BIN
      zdxt-web-client/zdxt-efcode-enforcement-app/src/assets/images/label_msg.png
  5. 23
      zdxt-web-client/zdxt-efcode-enforcement-app/src/components/FooterBar.vue
  6. 1
      zdxt-web-client/zdxt-efcode-enforcement-app/src/http/index.js
  7. 18
      zdxt-web-client/zdxt-efcode-enforcement-app/src/router/enforcement-app/enf-main-router.js
  8. 582
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/enforce/enforcementChat.vue
  9. 259
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/enforce/enforcementNoticeList.vue
  10. 13
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/law/lawServiceList.vue
  11. 60
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/message/messageCenter.vue
  12. 276
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/statistics/statisticsAnalysis.vue

14
zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/index.js

@ -33,9 +33,9 @@ export const queryEnforcementRegistrationPageList = (params, success, error) =>
export const getEnforcementRegistrationDetailById = (params, success, error) => _get(baseUrl + "/enfRegistration/queryUniteById", params, success, error);
export const getEducationServicesList = (params, success, error) => _get(baseUrl + "/enforcementcode/educationServices/list", params, success, error);
export const getEducationServicesList = (params, success, error) => _get(baseUrl + "/educationServices/list", params, success, error);
export const getEducationServicesDetail = (params, success, error) => _get(baseUrl + "/enforcementcode/educationServices/details", params, success, error);
export const getEducationServicesDetail = (params, success, error) => _get(baseUrl + "/educationServices/details", params, success, error);
// endregion
@ -69,6 +69,9 @@ export const addNonRegistration = (params, success, error) => _post(baseUrl + "/
export const queryEnforcementNoticeList = (params, success, error) => _post(baseUrl + "/enforcement/queryList", params, success, error);
// 执法预告列表
export const getLawNoticeList = (params, success, error) => _post(baseUrl + "/enforcement/queryList", params, success, error);
export const queryUsersByPage = (params, success, error) => _get(baseUrl + "/system/queryUsersByPage", params, success, error);
// 获取部门列表
@ -110,3 +113,10 @@ export const getComprehensiveAnalysis = (params, success, error) => _get(baseUrl
export const queryDict = (params, success, error) => _get("/system/dict/data/dictType", params, success, error);
// endregion
// region 消息模块
// 消息数据统计接口(未读数量)
export const getMessageUnreadCount = (params, success, error) => _get(baseUrl + "/enforcement/todoitem/statisticsUnReadCount", params, success, error);
// endregion

BIN
zdxt-web-client/zdxt-efcode-enforcement-app/src/assets/images/bg_msg.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
zdxt-web-client/zdxt-efcode-enforcement-app/src/assets/images/icon_pffw.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
zdxt-web-client/zdxt-efcode-enforcement-app/src/assets/images/label_msg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

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

@ -14,7 +14,8 @@
<script setup name="FooterBar">
import { useRouter } from 'vue-router';
import { messageUnread } from '@/store/messageState';
import { onMounted } from 'vue';
import { getMessageUnreadCount } from '@/api/enforcement/index';
const props = defineProps({
active: {
@ -25,6 +26,8 @@ const props = defineProps({
const router = useRouter();
const messageUnread = ref(0);
const tabs = [
{
key: 'home', label: '首页', route: '/enforcement/enforcementHome',
@ -51,7 +54,25 @@ const onTabClick = (item) => {
if (router.currentRoute.value.path !== item.route) {
router.replace(item.route);
}
fetchMessageUnreadCount();
};
// ========== 获取消息未读数量 ==========
const fetchMessageUnreadCount = () => {
getMessageUnreadCount({}, (res) => {
if (res && res.code === 200 && res.data) {
const totalCount = Number(res.data.totalCount) || 0;
messageUnread.value = totalCount;
}
}, () => {
// 接口调用失败不处理
});
};
// ========== 组件挂载时获取未读数量 ==========
onMounted(() => {
fetchMessageUnreadCount();
});
</script>
<style lang="less" scoped>

1
zdxt-web-client/zdxt-efcode-enforcement-app/src/http/index.js

@ -40,7 +40,6 @@ function request(type, url, params, success, error) {
data: params,
};
if (type == "GET") {
console.log(params);
config.params = params;
} else {
config.data = params;

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

@ -71,6 +71,24 @@ const routes = [
isKeepAlive: true,
},
},
// 执法端-通知列表(tabbar)
{
path: "/enforcement/enforcementNoticeList",
name: "enforcementNoticeList",
component: () => import("@/views/enforcement/enforce/enforcementNoticeList.vue"),
meta: {
isKeepAlive: true,
},
},
// 执法端-执法预告反馈聊天
{
path: "/enforcement/enforcementChat",
name: "enforcementChat",
component: () => import("@/views/enforcement/enforce/enforcementChat.vue"),
meta: {
isKeepAlive: false,
},
},
// 执法端-我的(tabbar)
{
path: "/enforcement/my",

582
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/enforce/enforcementChat.vue

@ -0,0 +1,582 @@
<template>
<div class="chat-page">
<!-- 顶部导航栏 -->
<van-nav-bar
title="在线联系"
left-text="关闭"
left-arrow
@click-left="onClickLeft"
>
<template #right>
<van-icon name="ellipsis" size="20" @click="onMoreClick" />
</template>
</van-nav-bar>
<!-- 聊天对象信息 -->
<!-- <div class="chat-header">
<div class="chat-target">{{ bureauDeptName }} · {{ contactPerson }}</div>
</div> -->
<!-- 消息列表 -->
<div class="message-list" ref="messageListRef">
<div
v-for="(msg, index) in messageList"
:key="msg.id"
class="message-group"
>
<!-- 时间分割线 -->
<div
v-if="shouldShowTime(index)"
class="time-divider"
>
{{ msg.time }}
</div>
<!-- 消息行 -->
<div
class="message-row"
:class="{ 'self': msg.sender === 'self' }"
>
<!-- 对方头像在左 -->
<img
v-if="msg.sender === 'other'"
:src="msg.avatar"
class="avatar"
alt="avatar"
/>
<div class="message-content">
<div
v-if="msg.isRecalled"
class="message-bubble recalled"
>
{{ msg.sender === 'self' ? '你撤回了一条消息' : '对方撤回了一条消息' }}
</div>
<div
v-else
class="message-bubble"
:class="msg.sender"
@contextmenu.prevent="onBubbleContextMenu($event, msg, index)"
@touchstart="onTouchStart($event, msg, index)"
@touchend="onTouchEnd"
@touchmove="onTouchMove"
>
{{ msg.content }}
</div>
</div>
<!-- 自己头像在右 -->
<img
v-if="msg.sender === 'self'"
:src="msg.avatar"
class="avatar"
alt="avatar"
/>
</div>
</div>
</div>
<!-- 长按/右击菜单 -->
<div
v-if="menuVisible"
class="menu-overlay"
@click="closeMenu"
@contextmenu.prevent
>
<div
class="context-menu"
:style="{ top: menuPosition.y + 'px', left: menuPosition.x + 'px' }"
>
<div class="menu-item" @click.stop="onEdit">
<van-icon name="edit" size="16" />
<span>编辑</span>
</div>
<div class="menu-divider"></div>
<div class="menu-item" @click.stop="onRecall">
<van-icon name="revoke" size="16" />
<span>撤回</span>
</div>
</div>
</div>
<!-- 底部输入区域 -->
<div class="chat-input-area">
<div v-if="editMode" class="edit-tip">
正在编辑消息
<span class="edit-cancel" @click="cancelEdit">取消</span>
</div>
<div class="input-wrapper">
<input
v-model="inputMessage"
type="text"
class="chat-input"
:placeholder="editMode ? '编辑消息...' : '请输入消息...'"
@keyup.enter="sendMessage"
/>
<div class="send-btn" @click="sendMessage">
<van-icon :name="editMode ? 'success' : 'arrow-up'" size="18" color="#fff" />
</div>
</div>
</div>
</div>
</template>
<script setup name="enforcementChat">
import { ref, nextTick } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { showToast } from 'vant';
import 'vant/es/toast/style';
const router = useRouter();
const route = useRoute();
// ========== 从路由参数获取信息 ==========
const bureauDeptName = ref(route.query.bureauDeptName || '深圳司法局');
const contactPerson = ref(route.query.contactPerson || '付明明 周昂');
const noticeTitle = ref(route.query.title || '安全生产检查');
// ========== 头像占位 ==========
const selfAvatar = 'https://placehold.co/40x40/447bf5/fff?text=我';
const otherAvatar = 'https://placehold.co/40x40/999/fff?text=企';
// ========== 消息列表 ==========
const messageList = ref([
{
id: 1,
sender: 'other',
content: `您好,我司将于${route.query.enforcementTime || '2026-04-07'}进行执法检查,特此告知。`,
time: '2026-03-31 10:20',
avatar: otherAvatar,
isRecalled: false,
},
{
id: 2,
sender: 'self',
content: '收到,我们会做好准备工作。',
time: '2026-03-31 18:32',
avatar: selfAvatar,
isRecalled: false,
},
]);
const inputMessage = ref('');
const messageListRef = ref(null);
// ========== 菜单相关 ==========
const menuVisible = ref(false);
const menuPosition = ref({ x: 0, y: 0 });
const selectedMsg = ref(null);
const selectedIndex = ref(-1);
// ========== 编辑模式 ==========
const editMode = ref(false);
const editingMsgId = ref(null);
// ========== 长按计时器 ==========
let touchTimer = null;
let touchMoved = false;
// ========== 是否显示时间分割线 ==========
const shouldShowTime = (index) => {
if (index === 0) return true;
const prev = messageList.value[index - 1];
const curr = messageList.value[index];
return prev.time !== curr.time;
};
// ========== 判断消息是否可编辑/撤回 ==========
const canEditOrRecall = (msg, index) => {
// 1. 必须是自己发的消息
if (msg.sender !== 'self') return false;
// 2. 消息已撤回则不可再次操作
if (msg.isRecalled) return false;
// 3. 时间不超过3分钟
const msgTime = new Date(msg.time.replace(/-/g, '/'));
const now = new Date();
const diffMinutes = (now - msgTime) / (1000 * 60);
if (diffMinutes > 3) return false;
// 4. 之后没有对方的消息
const hasOtherReply = messageList.value.slice(index + 1).some(m => m.sender === 'other');
if (hasOtherReply) return false;
return true;
};
// ========== PC端右击 ==========
const onBubbleContextMenu = (event, msg, index) => {
if (!canEditOrRecall(msg, index)) {
showToast('该消息无法编辑或撤回');
return;
}
selectedMsg.value = msg;
selectedIndex.value = index;
const rect = event.currentTarget.getBoundingClientRect();
menuPosition.value = {
x: rect.left + rect.width / 2,
y: rect.top - 10,
};
menuVisible.value = true;
};
// ========== 移动端长按 ==========
const onTouchStart = (event, msg, index) => {
touchMoved = false;
const target = event.currentTarget;
touchTimer = setTimeout(() => {
if (!touchMoved) {
if (!canEditOrRecall(msg, index)) {
showToast('该消息无法编辑或撤回');
return;
}
selectedMsg.value = msg;
selectedIndex.value = index;
const rect = target.getBoundingClientRect();
menuPosition.value = {
x: rect.left + rect.width / 2,
y: rect.top - 10,
};
menuVisible.value = true;
}
}, 600);
};
const onTouchEnd = () => {
if (touchTimer) {
clearTimeout(touchTimer);
touchTimer = null;
}
};
const onTouchMove = () => {
touchMoved = true;
if (touchTimer) {
clearTimeout(touchTimer);
touchTimer = null;
}
};
// ========== 关闭菜单 ==========
const closeMenu = () => {
menuVisible.value = false;
selectedMsg.value = null;
selectedIndex.value = -1;
};
// ========== 编辑消息 ==========
const onEdit = () => {
if (!selectedMsg.value) return;
editMode.value = true;
editingMsgId.value = selectedMsg.value.id;
inputMessage.value = selectedMsg.value.content;
closeMenu();
nextTick(() => {
const input = document.querySelector('.chat-input');
if (input) input.focus();
});
};
// ========== 撤回消息 ==========
const onRecall = () => {
if (selectedIndex.value === -1) return;
messageList.value[selectedIndex.value].isRecalled = true;
showToast('消息已撤回');
closeMenu();
};
// ========== 取消编辑 ==========
const cancelEdit = () => {
editMode.value = false;
editingMsgId.value = null;
inputMessage.value = '';
};
// ========== 发送/更新消息 ==========
const sendMessage = () => {
const text = inputMessage.value.trim();
if (!text) {
showToast('请输入消息内容');
return;
}
// 编辑模式:更新已有消息
if (editMode.value && editingMsgId.value !== null) {
const msg = messageList.value.find(m => m.id === editingMsgId.value);
if (msg) {
msg.content = text;
showToast('消息已修改');
}
cancelEdit();
return;
}
const now = new Date();
const timeStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
messageList.value.push({
id: Date.now(),
sender: 'self',
content: text,
time: timeStr,
avatar: selfAvatar,
isRecalled: false,
});
inputMessage.value = '';
// 滚动到底部
nextTick(() => {
scrollToBottom();
});
};
// ========== 滚动到底部 ==========
const scrollToBottom = () => {
const el = messageListRef.value;
if (el) {
el.scrollTop = el.scrollHeight;
}
};
// ========== 返回上一页 ==========
const onClickLeft = () => {
router.back();
};
// ========== 更多点击 ==========
const onMoreClick = () => {
showToast('更多功能开发中');
};
</script>
<style lang="less" scoped>
.chat-page {
display: flex;
flex-direction: column;
height: 100%;
background-color: #e8f0f8;
position: relative;
}
/* ===== 导航栏样式覆盖 ===== */
:deep(.van-nav-bar) {
background-color: #fff;
}
:deep(.van-nav-bar__title) {
font-size: 0.45rem;
font-weight: 600;
color: #333;
}
:deep(.van-nav-bar__text) {
font-size: 0.35rem;
color: #333;
}
:deep(.van-nav-bar .van-icon) {
color: #333;
}
/* ===== 聊天对象信息 ===== */
.chat-header {
padding: 0.2rem 0;
text-align: center;
background-color: #e8f0f8;
.chat-target {
font-size: 0.32rem;
color: #2d6fd5;
}
}
/* ===== 消息列表 ===== */
.message-list {
flex: 1;
overflow-y: auto;
padding: 0 0.3rem 0.3rem;
}
.message-group {
margin-bottom: 0.1rem;
}
.time-divider {
text-align: center;
font-size: 0.24rem;
color: #999;
padding: 0.2rem 0;
}
.message-row {
display: flex;
align-items: flex-start;
margin-bottom: 0.2rem;
&.self {
justify-content: flex-end;
}
}
.avatar {
width: 0.8rem;
height: 0.8rem;
border-radius: 50%;
flex-shrink: 0;
object-fit: cover;
}
.message-content {
max-width: 70%;
margin: 0 0.2rem;
}
.message-bubble {
padding: 0.22rem 0.28rem;
border-radius: 0.16rem;
font-size: 0.32rem;
line-height: 1.5;
word-break: break-word;
user-select: none;
-webkit-user-select: none;
&.self {
background-color: #4485f5;
color: #fff;
border-bottom-right-radius: 0.04rem;
}
&.other {
background-color: #fff;
color: #333;
border-bottom-left-radius: 0.04rem;
}
&.recalled {
background-color: rgba(0, 0, 0, 0.05);
color: #999;
font-size: 0.28rem;
text-align: center;
border-radius: 0.08rem;
}
}
/* ===== 长按/右击菜单 ===== */
.menu-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
}
.context-menu {
position: absolute;
background: rgba(50, 50, 50, 0.9);
border-radius: 0.12rem;
padding: 0.1rem 0;
min-width: 2rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
transform: translate(-50%, -100%);
/* 底部三角形箭头 */
&::after {
content: '';
position: absolute;
bottom: -0.12rem;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 0.12rem solid transparent;
border-right: 0.12rem solid transparent;
border-top: 0.12rem solid rgba(50, 50, 50, 0.9);
}
}
.menu-item {
display: flex;
align-items: center;
justify-content: center;
gap: 0.1rem;
padding: 0.2rem 0.3rem;
color: #fff;
font-size: 0.3rem;
cursor: pointer;
&:active {
background: rgba(255, 255, 255, 0.1);
}
:deep(.van-icon) {
color: #fff;
}
}
.menu-divider {
height: 1px;
background: rgba(255, 255, 255, 0.2);
margin: 0 0.2rem;
}
/* ===== 底部输入区域 ===== */
.chat-input-area {
background-color: #fff;
padding: 0.2rem 0.3rem;
border-top: 1px solid #e5e5e5;
}
.edit-tip {
font-size: 0.26rem;
color: #4485f5;
margin-bottom: 0.1rem;
display: flex;
align-items: center;
justify-content: space-between;
.edit-cancel {
color: #999;
cursor: pointer;
}
}
.input-wrapper {
display: flex;
align-items: center;
gap: 0.2rem;
}
.chat-input {
flex: 1;
height: 0.8rem;
border: 1px solid #d9d9d9;
border-radius: 0.4rem;
padding: 0 0.3rem;
font-size: 0.32rem;
outline: none;
background-color: #fff;
&:focus {
border-color: #4485f5;
}
}
.send-btn {
width: 0.8rem;
height: 0.8rem;
border-radius: 50%;
background-color: #4485f5;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
cursor: pointer;
&:active {
opacity: 0.8;
}
}
</style>

259
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/enforce/enforcementNoticeList.vue

@ -0,0 +1,259 @@
<template>
<div class="notice-list-page">
<!-- 顶部导航栏 -->
<van-nav-bar
title="执法预告"
left-text="关闭"
left-arrow
@click-left="onClickLeft"
>
<!-- <template #right>
<van-icon name="filter-o" size="20" @click="onFilterClick" />
<van-icon name="search" size="20" style="margin-left: 12px;" @click="onSearchClick" />
</template> -->
</van-nav-bar>
<!-- 列表区域 -->
<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="notice-card"
v-for="item in list"
:key="item.id"
>
<div class="card-row time-row">
<span class="label">执法时间:</span>
<span class="value">{{ item.checkTime }}</span>
</div>
<div class="card-title">{{ item.checkItemText }}</div>
<div class="card-row">
<span class="label">执法单位:</span>
<span class="value">{{ item.bureauDeptName }}</span>
</div>
<div class="card-action">
<span class="feedback-btn" :class="{ 'has-dot': item.hasFeedback }" @click="onFeedback(item)">
<van-icon name="chat-o" size="14" />
反馈
</span>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
</div>
</template>
<script setup name="enforcementNoticeList">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { showToast } from 'vant';
import 'vant/es/toast/style';
import { getLawNoticeList } from '@/api/enforcement';
const router = useRouter();
// ========== 列表状态 ==========
const list = ref([]);
const loading = ref(false);
const finished = ref(false);
const refreshing = ref(false);
const pageNum = ref(1);
const pageSize = ref(10);
// ========== 加载数据 ==========
const fetchList = () => {
const params = {
pageNum: pageNum.value,
pageSize: pageSize.value,
};
getLawNoticeList(params, (res) => {
if (res.code === 200) {
const rows = res.rows || res.data?.rows || res.data?.list || res.data || [];
const total = res.total ?? res.data?.total ?? 0;
if (refreshing.value) {
list.value = Array.isArray(rows) ? rows : [];
refreshing.value = false;
} else {
list.value = [...list.value, ...(Array.isArray(rows) ? rows : [])];
}
if (!Array.isArray(rows) || rows.length === 0 || list.value.length >= total) {
finished.value = true;
} else {
pageNum.value++;
}
loading.value = false;
} else {
finished.value = true;
loading.value = false;
refreshing.value = false;
showToast(res.msg || '加载失败');
}
}, () => {
finished.value = true;
loading.value = false;
refreshing.value = false;
showToast('网络请求失败');
});
};
// ========== 触底加载 ==========
const onLoad = () => {
if (refreshing.value) return;
fetchList();
};
// ========== 下拉刷新 ==========
const onRefresh = () => {
pageNum.value = 1;
list.value = [];
finished.value = false;
loading.value = true;
fetchList();
};
// ========== 返回上一页 ==========
const onClickLeft = () => {
router.back();
};
// ========== 筛选点击 ==========
const onFilterClick = () => {
showToast('筛选功能开发中');
};
// ========== 搜索点击 ==========
const onSearchClick = () => {
showToast('搜索功能开发中');
};
// ========== 反馈点击 ==========
const onFeedback = (item) => {
router.push({
path: '/enforcement/enforcementChat',
query: {
title: item.title,
bureauDeptName: item.bureauDeptName,
enforcementTime: item.enforcementTime,
}
});
};
</script>
<style lang="less" scoped>
.notice-list-page {
display: flex;
flex-direction: column;
height: 100%;
background-color: #ebf3fb;
.list-container {
flex: 1;
overflow-y: auto;
padding: 0.27rem;
}
}
/* ===== 导航栏样式覆盖 ===== */
:deep(.van-nav-bar) {
background-color: #fff;
}
:deep(.van-nav-bar__title) {
font-size: 0.45rem;
font-weight: 600;
color: #333;
}
:deep(.van-nav-bar__text) {
font-size: 0.35rem;
color: #333;
}
:deep(.van-nav-bar .van-icon) {
color: #333;
}
/* ===== 卡片样式 ===== */
.notice-card {
background: #fff;
border-radius: 0.2rem;
padding: 0.3rem 0.35rem;
margin-bottom: 0.27rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
.card-row {
font-size: 0.32rem;
color: #666;
line-height: 1.6;
.label {
color: #999;
}
.value {
color: #666;
}
}
.time-row {
margin-bottom: 0.15rem;
}
.card-title {
font-size: 0.42rem;
font-weight: bold;
color: #1a1a1a;
margin: 0.15rem 0;
line-height: 1.4;
}
.card-action {
display: flex;
justify-content: flex-end;
margin-top: 0.2rem;
}
.feedback-btn {
display: inline-flex;
align-items: center;
font-size: 0.32rem;
color: #2d94fa;
cursor: pointer;
:deep(.van-icon) {
margin-right: 0.08rem;
color: #2d94fa;
}
&.has-dot {
position: relative;
:deep(.van-icon) {
position: relative;
&::after {
content: '';
position: absolute;
top: -2px;
right: -2px;
width: 6px;
height: 6px;
background: #f44336;
border-radius: 50%;
}
}
}
}
</style>

13
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/law/lawServiceList.vue

@ -19,7 +19,7 @@
<van-list
v-model:loading="loading"
:finished="finished"
finished-text=""
finished-text="已全部加载"
@load="onLoad"
:offset="50"
>
@ -76,6 +76,7 @@ const finished = ref(false);
const refreshing = ref(false);
const pageNum = ref(1);
const pageSize = ref(10);
const isFetching = ref(false); // 防重复请求标志位
// ========== 格式化时间 ==========
const formatTime = (val) => {
@ -100,6 +101,10 @@ const stripHtml = (html) => {
// ========== 加载数据 ==========
const fetchList = () => {
// 防止重复请求(van-list会自动设置loading为true,所以不检查loading)
if (isFetching.value) return;
isFetching.value = true;
const params = {
pageNum: pageNum.value,
pageSize: pageSize.value,
@ -107,7 +112,7 @@ const fetchList = () => {
getEducationServicesList(params, (res) => {
if (res.code === 200) {
const rows = res.rows || res.data?.rows || [];
const total = res.total ?? res.data?.total ?? 0;
const total = res.total || 0;
if (refreshing.value) {
list.value = rows;
@ -122,15 +127,18 @@ const fetchList = () => {
pageNum.value++;
}
loading.value = false;
isFetching.value = false;
} else {
finished.value = true;
loading.value = false;
isFetching.value = false;
showToast(res.msg || '加载失败');
}
}, () => {
finished.value = true;
loading.value = false;
refreshing.value = false;
isFetching.value = false;
showToast('网络请求失败');
});
};
@ -174,6 +182,7 @@ const toDetail = (item) => {
};
onMounted(() => {
// 手动触发初始加载,van-list可能不会自动触发onLoad事件
fetchList();
});
</script>

60
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/message/messageCenter.vue

@ -74,6 +74,7 @@ import { useRouter, useRoute } from 'vue-router';
import { showToast } from 'vant';
import 'vant/es/toast/style';
import FooterBar from '@/components/FooterBar.vue';
import { getMessageUnreadCount } from '@/api/enforcement/index';
const router = useRouter();
const route = useRoute();
@ -114,17 +115,20 @@ const totalUnread = computed(() => {
// ========== 获取消息未读数量 ==========
const fetchMessageStats = () => {
// 模拟接口请求
setTimeout(() => {
messageStats.value = {
pffwCount: 2,
zfpjcount: 3,
zfygCount: 1,
totalCount: 6,
};
initMessageList();
initTodoList();
}, 500);
getMessageUnreadCount({}, (res) => {
if (res && res.code === 200 && res.data) {
messageStats.value = {
pffwCount: Number(res.data.pffwCount) || 0, // 普法服务未读数量
zfpjcount: Number(res.data.zfpjcount) || 0, // 执法评价未读数量
zfygCount: Number(res.data.zfygCount) || 0, // 执法预告未读数量
totalCount: Number(res.data.totalCount) || 0, // 总未读数量
};
initMessageList();
initTodoList();
}
}, () => {
// 接口调用失败不处理
});
};
// ========== 初始化默认消息列表 ==========
@ -156,12 +160,21 @@ const initTodoList = () => {
},
{
id: 102,
title: '待评价执法',
desc: messageStats.value.zfpjcount > 0 ? `您有${messageStats.value.zfpjcount}条执法记录待评价` : '暂无待评价执法',
time: '2026-04-14 15:00',
title: '执法任务',
desc: messageStats.value.zfygCount > 0 ? `您有${messageStats.value.zfygCount}条执法任务,请及时查看` : '暂无执法预告',
time: '2026-04-15 09:30',
icon: 'check',
iconBg: 'icon-orange',
unreadCount: messageStats.value.zfpjcount,
iconBg: 'icon-red',
unreadCount: messageStats.value.zfygCount,
},
{
id: 103,
title: '执法预约',
desc: messageStats.value.zfygCount > 0 ? `您有${messageStats.value.zfygCount}条执法预约,请及时查看` : '暂无执法预告',
time: '2026-04-15 09:30',
icon: 'check',
iconBg: 'icon-red',
unreadCount: messageStats.value.zfygCount,
},
];
};
@ -202,15 +215,6 @@ const fetchTodoList = () => {
iconBg: 'icon-red',
unreadCount: messageStats.value.zfygCount,
},
{
id: 102,
title: '待评价执法',
desc: messageStats.value.zfpjcount > 0 ? `您有${messageStats.value.zfpjcount}条执法记录待评价` : '暂无待评价执法',
time: '2026-04-14 15:00',
icon: 'check',
iconBg: 'icon-orange',
unreadCount: messageStats.value.zfpjcount,
},
];
todoRefreshing.value = false;
}
@ -402,17 +406,17 @@ onMounted(() => {
/* 图标背景色 */
.icon-blue {
background: url('../../../assets/images/icon_pffw.jpg') center center no-repeat;
background: url('@/assets/images/icon_pffw.jpg') center center no-repeat;
background-size: 100% 100%;
}
.icon-orange {
background: url('../../../assets/images/icon_zfyg.jpg') center center no-repeat;
background: url('@/assets/images/icon_zfyg.jpg') center center no-repeat;
background-size: 100% 100%;
}
.icon-red {
background: url('../../../assets/images/icon_zfpj.jpg') center center no-repeat;
background: url('@/assets/images/icon_zfpj.jpg') center center no-repeat;
background-size: 100% 100%;
}

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

@ -4,12 +4,12 @@
<div class="content-area">
<!-- 顶部切换卡 -->
<div class="type-switcher">
<div class="switcher-item personal" :class="{ active: userType === 'personal' }"
@click="userType = 'personal'">
<div class="switcher-item personal" :class="{ active: searchType === 'user' }"
@click="searchType = 'user'">
个人
</div>
<div class="switcher-item company" :class="{ active: userType === 'company' }"
@click="userType = 'company'">
<div class="switcher-item company" :class="{ active: searchType === 'bureauDept' }"
@click="searchType = 'bureauDept'">
单位
</div>
</div>
@ -21,13 +21,13 @@
<van-icon name="arrow-down" class="year-arrow" />
</div>
<div class="period-selector">
<div class="period-btn" :class="period === 'month' ? 'active' : ''" @click="showMonthPicker">月度
<div class="period-btn" :class="dateType === 'month' ? 'active' : ''" @click="showMonthPicker">月度
</div>
<div class="period-btn" :class="period === 'quarter' ? 'active' : ''" @click="showQuarterPicker">季度
<div class="period-btn" :class="dateType === 'quarter' ? 'active' : ''" @click="showQuarterPicker">季度
</div>
<div class="period-btn" :class="period === 'year' ? 'active' : ''" @click="showYearPeriodPicker">年度
<div class="period-btn" :class="dateType === 'year' ? 'active' : ''" @click="showYearPeriodPicker">年度
</div>
<div class="period-btn" :class="period === 'other' ? 'active' : ''" @click="showFilterPopup">筛选
<div class="period-btn" :class="dateType === 'day' ? 'active' : ''" @click="showFilterPopup">筛选
</div>
</div>
</div>
@ -38,7 +38,7 @@
<van-skeleton title :row="3" />
</div>
<div v-else class="card-body">
<div class="card-title">涉企执法数量</div>
<div class="card-title">涉企检查数量</div>
<div class="total-section">
<div class="left-part">
<div class="total-number">{{ animatedTotal }}</div>
@ -47,21 +47,21 @@
<div class="comparison-item">
<span class="comparison-label">环比:</span>
<span class="up-arrow">↑</span>
<span class="comparison-value up">{{ statistics.monthOverMonth }}%</span>
<span class="comparison-value up">{{ statistics.huanPercentage }}%</span>
</div>
<div class="comparison-item">
<span class="comparison-label">同比:</span>
<span class="up-arrow">↑</span>
<span class="comparison-value up">{{ statistics.yearOverYear }}%</span>
<span class="comparison-value up">{{ statistics.tongPercentage }}%</span>
</div>
</div>
</div>
<!-- 联合执法 -->
<!-- 统计数据 -->
<div class="joint-law-section">
<div class="section-header">
<div class="section-title">联合执法</div>
<div class="section-value">-</div>
<div class="section-title">联合检查</div>
<div class="section-value">{{ statistics.uniteTotal }}</div>
</div>
<div class="joint-stats">
<div class="joint-item" v-for="(item, index) in jointStatsList" :key="index">
@ -81,10 +81,12 @@
<van-tab v-for="(tab, index) in tabConfig" :key="index" :title="tab.title">
<div class="tab-content">
<div class="category-grid" v-if="currentTabData.length > 0">
<div class="category-item" v-for="(item, idx) in currentTabData" :key="idx">
<div class="category-item" :class="{ 'complaint-item': activeTab === 2 }"
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>
<div class="category-value" :class="{ 'complaint-value': activeTab === 2 }">{{
item.total }}</div>
<div class="category-sub" v-if="item.percentage">{{ item.percentage }}</div>
</div>
</div>
<van-empty v-else description="暂无数据" image="search" />
@ -136,10 +138,10 @@ const currentYearValue = new Date().getFullYear();
// 根据年份获取季度列表
const getQuarterColumns = (year) => {
const quarters = [
{ text: '1', value: 'Q1' },
{ text: '2', value: 'Q2' },
{ text: '3', value: 'Q3' },
{ text: '4', value: 'Q4' },
{ text: '1', value: '1' },
{ text: '2', value: '2' },
{ text: '3', value: '3' },
{ text: '4', value: '4' },
];
// 如果是当前年份,只显示到当前季度
if (Number(year) === currentYearValue) {
@ -175,48 +177,35 @@ const toggleFilter = (selectedList, value, allKey = 'all') => {
};
// ========== 响应式状态 ==========
const userType = ref('personal');
const period = ref('year');
const searchType = ref('user');
const dateType = ref('year');
const currentYear = ref(new Date().getFullYear());
const activeTab = ref(0);
// 统计数据
const statistics = ref({
totalCount: 0,
monthOverMonth: 0,
yearOverYear: 0,
jointLaw: { noticeCount: 0, entrustedCount: 0, evaluateCount: 0, complaintCount: 0 },
registrationTotal: 0, // 涉企检查数量
uniteTotal: 0, // 联合检查数量
authTotal: 0, // 委托检查数量
noticeTotal: 0, // 执法检查预告数量
warningTotal: 0, // 执法预警数量
enterpriseTotal: 0, // 企业数量
complaintTotal: 0, // 投诉数量
evaluateTotal: 0, // 评论数量
plantTotal: 0, // 计划数量
huanPercentage: 0, // 环比
tongPercentage: 0, // 同比
});
// 类别数据(使用 shallowRef 优化性能)
const lawCategoryList = shallowRef([
{ name: '行政检查', value: 11, subValue: '100%' },
{ name: '行政处罚', value: 0, subValue: '-' },
{ name: '行政许可', value: 0, subValue: '-' },
{ name: '行政强制', value: 0, subValue: '-' },
{ name: '行政征收', value: 0, subValue: '-' },
{ name: '行政给付', value: 0, subValue: '-' },
{ name: '行政确认', value: 0, subValue: '-' },
{ name: '行政登记', value: 0, subValue: '-' },
{ name: '行政裁决', value: 0, subValue: '-' },
{ name: '文书送达', value: 0, subValue: '-' },
const lawEnforceCategoryList = 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: 4, subValue: '-' },
{ name: '其他', value: 0, subValue: '-' },
const companyOrgTypeList = shallowRef([
]);
const complaintCategoryList = shallowRef([
{ name: '违规执法', value: 2, subValue: '40%' },
{ name: '入企不扫码', value: 1, subValue: '20%' },
{ name: '违法执法', value: 3, subValue: '40%' },
{ name: '其他', value: 0, subValue: '-' },
const complaintTypeList = shallowRef([
]);
// Tab 配置
@ -228,9 +217,9 @@ const tabConfig = [
// Tab 数据映射
const tabDataMap = {
law: lawCategoryList,
enterprise: enterpriseCategoryList,
complaint: complaintCategoryList,
law: lawEnforceCategoryList,
enterprise: companyOrgTypeList,
complaint: complaintTypeList,
};
// 当前Tab数据
@ -270,14 +259,14 @@ watch(() => selectedQuarter.value[0], (newYear) => {
// 动画数字
const animatedTotal = ref(0);
const animatedNotice = ref(0);
const animatedEntrusted = ref(0);
const animatedAuth = ref(0);
const animatedEvaluate = ref(0);
const animatedComplaint = ref(0);
// 根据当前周期显示的时间文本
const periodDisplayText = computed(() => {
// 如果是筛选模式,显示日期范围
if (period.value === 'other') {
if (dateType.value === 'day') {
const [startDate, endDate] = selectedDateRange.value;
const formatDate = (date) => {
const d = new Date(date);
@ -286,20 +275,20 @@ const periodDisplayText = computed(() => {
return `${formatDate(startDate)} - ${formatDate(endDate)}`;
}
if (period.value === 'month') {
if (dateType.value === 'month') {
if (selectedMonth.value && selectedMonth.value.length === 2) {
return `${selectedMonth.value[0]}年${selectedMonth.value[1]}月`;
}
return `${currentYear.value}年`;
} else if (period.value === 'quarter') {
} else if (dateType.value === 'quarter') {
if (selectedQuarter.value && selectedQuarter.value.length === 2) {
const year = selectedQuarter.value[0];
const quarter = selectedQuarter.value[1];
const quarterMap = {
'Q1': '1季度',
'Q2': '2季度',
'Q3': '3季度',
'Q4': '4季度'
'1': '1季度',
'2': '2季度',
'3': '3季度',
'4': '4季度'
};
return `${year}年${quarterMap[quarter] || quarter}`;
}
@ -309,10 +298,10 @@ const periodDisplayText = computed(() => {
}
});
// 联合执法统计数据
// 统计数据列表
const jointStatsList = computed(() => [
{ label: '执法预告', value: animatedNotice.value, isRed: false },
{ label: '委托执法', value: animatedEntrusted.value > 0 ? animatedEntrusted.value : '-', isRed: false },
{ label: '委托执法', value: animatedAuth.value > 0 ? animatedAuth.value : '-', isRed: false },
{ label: '评价', value: animatedEvaluate.value, isRed: false },
{ label: '投诉', value: animatedComplaint.value > 0 ? animatedComplaint.value : '-', isRed: animatedComplaint.value > 0 },
]);
@ -331,62 +320,76 @@ const yearColumns = computed(() => {
// ========== 数据加载与动画 ==========
const loadStatistics = () => {
isLoading.value = true;
// 构建请求参数
const params = {
userType: userType.value, // 'personal' 或 'company'
period: period.value, // 'month', 'quarter', 'year', 'other'
searchType: searchType.value, // 'user' 或 'bureauDept'
dateType: dateType.value, // 'month', 'quarter', 'year', 'other'
};
// 根据周期类型添加具体参数
if (period.value === 'month') {
if (dateType.value === 'month') {
params.year = selectedMonth.value[0];
params.month = selectedMonth.value[1];
} else if (period.value === 'quarter') {
} else if (dateType.value === 'quarter') {
params.year = selectedQuarter.value[0];
params.quarter = selectedQuarter.value[1];
} else if (period.value === 'year') {
} else if (dateType.value === 'year') {
params.year = currentYear.value;
} else if (period.value === 'other' && selectedDateRange.value) {
} else if (dateType.value === 'day' && selectedDateRange.value) {
const formatDate = (date) => {
const d = new Date(date);
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
};
params.startDate = formatDate(selectedDateRange.value[0]);
params.endDate = formatDate(selectedDateRange.value[1]);
params.beginTime = formatDate(selectedDateRange.value[0]);
params.endTime = formatDate(selectedDateRange.value[1]);
}
getComprehensiveAnalysis(params, (res) => {
if (res.code === 200 || res.code === 0) {
const data = res.data || {};
statistics.value = {
totalCount: data.totalCount || 0,
monthOverMonth: data.monthOverMonth || 0,
yearOverYear: data.yearOverYear || 0,
jointLaw: {
noticeCount: data.jointLaw?.noticeCount || 0,
entrustedCount: data.jointLaw?.entrustedCount || 0,
evaluateCount: data.jointLaw?.evaluateCount || 0,
complaintCount: data.jointLaw?.complaintCount || 0,
},
registrationTotal: data.registrationTotal || 0, // 涉企检查数量
uniteTotal: data.uniteTotal || 0, // 联合检查数量
authTotal: data.authTotal || 0, // 委托检查数量
noticeTotal: data.noticeTotal || 0, // 执法检查预告数量
warningTotal: data.warningTotal || 0, // 执法预警数量
enterpriseTotal: data.enterpriseTotal || 0, // 企业数量
complaintTotal: data.complaintTotal || 0, // 投诉数量
evaluateTotal: data.evaluateTotal || 0, // 评论数量
plantTotal: data.plantTotal || 0, // 计划数量
huanPercentage: data.huanPercentage || 0, // 环比
tongPercentage: data.tongPercentage || 0, // 同比
};
// 更新执法类别数据
if (data.lawCategoryList && Array.isArray(data.lawCategoryList)) {
lawCategoryList.value = data.lawCategoryList;
// 更新执法类别数据(使用后台返回的数据)
if (data.lawEnforceCategoryList && Array.isArray(data.lawEnforceCategoryList)) {
lawEnforceCategoryList.value = data.lawEnforceCategoryList.map(item => ({
name: item.lawEnforceCategory || '',
total: item.lawEnforceCategoryNumber || 0,
percentage: item.percentage ? `${item.percentage}%` : '-'
}));
}
// 更新企业类别数据
if (data.enterpriseCategoryList && Array.isArray(data.enterpriseCategoryList)) {
enterpriseCategoryList.value = data.enterpriseCategoryList;
// 更新企业类别数据(使用后台返回的数据)
if (data.companyOrgTypeList && Array.isArray(data.companyOrgTypeList)) {
companyOrgTypeList.value = data.companyOrgTypeList.map(item => ({
name: item.companyOrgType || '',
total: item.companyOrgTypeNumber || 0,
percentage: item.percentage ? `${item.percentage}%` : '-'
}));
}
// 更新投诉类别数据
if (data.complaintCategoryList && Array.isArray(data.complaintCategoryList)) {
complaintCategoryList.value = data.complaintCategoryList;
// 更新投诉类别数据(使用后台返回的数据)
if (data.complaintTypeList && Array.isArray(data.complaintTypeList)) {
complaintTypeList.value = data.complaintTypeList.map(item => ({
name: item.complaintType || '',
total: item.complaintTypeNumber || 0,
percentage: item.percentage ? `${item.percentage}%` : '-'
}));
}
startNumberAnimation();
} else {
showToast(res.msg || '加载失败');
@ -402,11 +405,11 @@ const loadStatistics = () => {
const startNumberAnimation = () => {
const duration = 1000;
const startTime = performance.now();
const targetTotal = statistics.value.totalCount;
const targetNotice = statistics.value.jointLaw.noticeCount;
const targetEntrusted = statistics.value.jointLaw.entrustedCount;
const targetEvaluate = statistics.value.jointLaw.evaluateCount;
const targetComplaint = statistics.value.jointLaw.complaintCount;
const targetTotal = statistics.value.registrationTotal; // 涉企检查数量
const targetNotice = statistics.value.noticeTotal; // 执法检查预告数量
const targetAuth = statistics.value.authTotal; // 委托检查数量
const targetEvaluate = statistics.value.evaluateTotal; // 评论数量
const targetComplaint = statistics.value.complaintTotal; // 投诉数量
const animate = (currentTime) => {
const elapsed = currentTime - startTime;
@ -415,7 +418,7 @@ const startNumberAnimation = () => {
animatedTotal.value = Math.round(targetTotal * easeOut);
animatedNotice.value = Math.round(targetNotice * easeOut);
animatedEntrusted.value = Math.round(targetEntrusted * easeOut);
animatedAuth.value = Math.round(targetAuth * easeOut);
animatedEvaluate.value = Math.round(targetEvaluate * easeOut);
animatedComplaint.value = Math.round(targetComplaint * easeOut);
@ -428,21 +431,16 @@ const startNumberAnimation = () => {
// 显示弹窗
const showMonthPicker = () => {
period.value = 'month';
selectedMonth.value = [now.getFullYear(), now.getMonth() + 1];
showMonthSelector.value = true;
isFilterMode.value = false; // 退出筛选模式
};
const showQuarterPicker = () => {
period.value = 'quarter';
selectedQuarter.value = [String(now.getFullYear()), `Q${Math.ceil((now.getMonth() + 1) / 3)}`];
showQuarterSelector.value = true;
isFilterMode.value = false; // 退出筛选模式
};
const showYearPeriodPicker = () => {
period.value = 'year';
selectedYearPeriod.value = ['2026'];
showYearSelector.value = true;
};
@ -453,16 +451,15 @@ const showYearPicker = () => {
};
const showFilterPopup = () => {
period.value = 'other';
showFilterSelector.value = true;
};
// 根据当前周期显示对应的选择器
const showPeriodPicker = () => {
if (period.value === 'month') {
if (dateType.value === 'month') {
selectedMonth.value = selectedMonth.value || [now.getFullYear(), now.getMonth() + 1];
showMonthSelector.value = true;
} else if (period.value === 'quarter') {
} else if (dateType.value === 'quarter') {
const currentQuarter = Math.ceil((new Date().getMonth() + 1) / 3);
selectedQuarter.value = [String(now.getFullYear()), `Q${currentQuarter}`];
showQuarterSelector.value = true;
@ -474,42 +471,44 @@ const showPeriodPicker = () => {
// 确认选择
const confirmMonth = (value) => {
showMonthSelector.value = false;
console.log(value);
onMonthConfirm(value);
};
const confirmQuarter = () => {
showToast(`已选择${periodDisplayText.value}`);
showQuarterSelector.value = false;
const confirmQuarter = (value) => {
onQuarterConfirm(value);
};
const confirmYear = () => {
currentYear.value = Number(selectedYearPeriod.value[0]);
showToast(`已选择${periodDisplayText.value}`);
showYearSelector.value = false;
const confirmYear = (value) => {
onYearConfirm(value);
};
const confirmFilter = () => {
showToast('筛选已更新');
showFilterSelector.value = false;
const confirmFilter = (dates) => {
onConfirm(dates);
};
const onMonthConfirm = (value) => {
selectedMonth.value = value;
showToast(`已选择${periodDisplayText.value}`);
dateType.value = 'month';
// van-date-picker 返回的是对象,包含 selectedValues 属性
const selectedValues = value.selectedValues || value;
if (Array.isArray(selectedValues)) {
selectedMonth.value = selectedValues;
}
showMonthSelector.value = false;
loadStatistics();
};
const onQuarterConfirm = ({ selectedValues }) => {
dateType.value = 'quarter';
if (Array.isArray(selectedValues)) {
selectedQuarter.value = selectedValues;
}
showToast(`已选择${periodDisplayText.value}`);
showQuarterSelector.value = false;
loadStatistics();
};
const onYearConfirm = (value) => {
dateType.value = 'year';
const selectedValues = value.selectedValues || value;
if (typeof selectedValues === 'string') {
@ -519,11 +518,11 @@ const onYearConfirm = (value) => {
selectedYearPeriod.value = selectedValues;
currentYear.value = Number(selectedValues[0]);
}
showToast(`已选择${periodDisplayText.value}`);
showYearSelector.value = false;
loadStatistics();
};
const onConfirm = (dates) => {
dateType.value = 'day';
if (dates && dates.length === 2) {
selectedDateRange.value = dates;
isFilterMode.value = true;
@ -546,8 +545,12 @@ onMounted(() => {
loadStatistics();
});
// 监听用户类型和周期变化,重新加载数据
watch([userType, period], () => {
// 只监听用户类型变化,周期变化不在此处触发接口调用
watch(searchType, () => {
// 清空类别数据,确保切换时页面能正确显示加载状态
lawEnforceCategoryList.value = [];
companyOrgTypeList.value = [];
complaintTypeList.value = [];
loadStatistics();
});
</script>
@ -945,7 +948,8 @@ watch([userType, period], () => {
text-align: center;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
transition: all 0.15s ease;
border: 1px solid transparent;
&:active {
transform: scale(0.97);
@ -974,6 +978,18 @@ watch([userType, period], () => {
}
}
/* ===== 投诉类别样式 ===== */
.category-grid {
.complaint-item {
background: #fff5f5 !important;
border-color: #ffccc7 !important;
.complaint-value {
color: #ff4d4f !important;
}
}
}
/* ===== 弹窗样式 ===== */
.picker-header {
display: flex;

Loading…
Cancel
Save