Browse Source

执法端页面修改

dev-V1.0.1
wangmingyong 6 months ago
parent
commit
c42cd1d1c1
  1. 114
      zdxt-web-client/zdxt-efcode-enforcement-app/src/components/FooterBar.vue
  2. 29
      zdxt-web-client/zdxt-efcode-enforcement-app/src/router/enforcement-app/enf-main-router.js
  3. 22
      zdxt-web-client/zdxt-efcode-enforcement-app/src/store/messageState.js
  4. 49
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/home/enforcementHome.vue
  5. 474
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/message/messageCenter.vue
  6. 348
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/enforcementMy.vue
  7. 43
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/query/enforcementQuery.vue

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

@ -0,0 +1,114 @@
<template>
<div class="footer-bar">
<div
v-for="item in tabs"
:key="item.key"
class="footer-item"
:class="{ 'footer-item--active': item.key === active }"
@click="onTabClick(item)"
>
<div v-if="item.key === 'message'" class="footer-icon-wrap">
<svg viewBox="0 0 24 24" fill="currentColor" v-html="item.path"></svg>
<span v-if="messageUnread" class="footer-badge">{{ messageUnread }}</span>
</div>
<svg v-else viewBox="0 0 24 24" fill="currentColor" v-html="item.path"></svg>
<span>{{ item.label }}</span>
</div>
</div>
</template>
<script setup name="FooterBar">
import { useRouter } from 'vue-router';
import { messageUnread } from '@/store/messageState';
const props = defineProps({
active: {
type: String,
default: '',
},
});
const router = useRouter();
const tabs = [
{ key: 'home', label: '首页', route: '/enforcement/enforcementHome',
path: '<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>' },
{ key: 'query', label: '查询', route: '/enforcement/query',
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: '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"/>' },
];
const onTabClick = (item) => {
if (item.key === props.active) return;
if (router.currentRoute.value.path !== item.route) {
router.replace(item.route);
}
};
</script>
<style lang="less" scoped>
.footer-bar {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-around;
background: #fff;
padding: 8px 0;
padding-bottom: calc(8px + env(safe-area-inset-bottom));
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.06);
}
.footer-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
color: #999;
font-size: 11px;
cursor: pointer;
user-select: none;
svg {
width: 24px;
height: 24px;
}
&--active {
color: #2b7de9;
}
&:active {
opacity: 0.7;
}
}
.footer-icon-wrap {
position: relative;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.footer-badge {
position: absolute;
top: -4px;
right: -10px;
min-width: 16px;
height: 16px;
line-height: 16px;
padding: 0 4px;
background: #f5222d;
color: #fff;
font-size: 10px;
text-align: center;
border-radius: 8px;
border: 2px solid #fff;
box-sizing: content-box;
}
</style>

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

@ -13,7 +13,7 @@ const routes = [
{
path: "/enforcement/enforcementHome",
name: "enforcementHome",
component: () => import("@/views/enforcement/enforcementHomeV2.vue"),
component: () => import("@/views/enforcement/home/enforcementHome.vue"),
meta: {
isKeepAlive: false,
},
@ -36,6 +36,33 @@ const routes = [
isKeepAlive: false,
},
},
// 执法端-查询(tabbar)
{
path: "/enforcement/query",
name: "enforcementQuery",
component: () => import("@/views/enforcement/query/enforcementQuery.vue"),
meta: {
isKeepAlive: true,
},
},
// 执法端-消息中心(tabbar)
{
path: "/enforcement/message",
name: "enforcementMessage",
component: () => import("@/views/enforcement/message/messageCenter.vue"),
meta: {
isKeepAlive: true,
},
},
// 执法端-我的(tabbar)
{
path: "/enforcement/my",
name: "enforcementMy",
component: () => import("@/views/enforcement/my/enforcementMy.vue"),
meta: {
isKeepAlive: true,
},
},
];
export {routes};

22
zdxt-web-client/zdxt-efcode-enforcement-app/src/store/messageState.js

@ -0,0 +1,22 @@
import { ref, computed } from 'vue';
/**
* 消息未读数(跨 tabbar 页面共享)
* - 模块级单例,四个 tabbar 页面(首页/查询/消息/我的)共享同一份
* - 消息中心页面负责写入;其他页面仅读取用于 footer-bar 角标展示
*/
export const messageUnread = ref(0);
/** 格式化为角标展示值(0 显示空) */
export const messageUnreadBadge = computed(() => (messageUnread.value > 0 ? messageUnread.value : ''));
/** 直接设置未读总数 */
export const setMessageUnread = (count) => {
messageUnread.value = Number(count) || 0;
};
// 占位 default 导出:src/store 目录会被 globalStore.js 通过 import.meta.glob
// 以 { import: 'default' } 的方式扫描,缺少 default 会触发
// "does not provide an export named 'default'" 错误。
// 此处导出一个不带 $id 的对象,globalStore 会自动跳过注册,不影响 Pinia。
export default {};

49
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/enforcementHomeV2.vue → zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/home/enforcementHome.vue

@ -191,24 +191,7 @@
</div>
<!-- 底部导航栏 -->
<div class="footer-bar">
<div class="footer-item footer-item--active">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
<span>首页</span>
</div>
<div class="footer-item">
<svg viewBox="0 0 24 24" fill="currentColor"><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"/></svg>
<span>查询</span>
</div>
<div class="footer-item">
<svg viewBox="0 0 24 24" fill="currentColor"><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"/></svg>
<span>消息</span>
</div>
<div class="footer-item">
<svg viewBox="0 0 24 24" fill="currentColor"><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"/></svg>
<span>我的</span>
</div>
</div>
<FooterBar active="home" />
</div>
</template>
@ -786,34 +769,4 @@ const toScanEntQRCode = (type) => {
.info-card-bg-decor--green {
color: #059669;
}
/* ===== 底部导航栏 ===== */
.footer-bar {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-around;
background: #fff;
padding: 8px 0;
padding-bottom: calc(8px + env(safe-area-inset-bottom));
box-shadow: 0 -2px 12px rgba(0,0,0,0.06);
.footer-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
color: #999;
font-size: 11px;
svg {
width: 24px;
height: 24px;
}
&--active {
color: #3b82f6;
}
}
}
</style>

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

@ -0,0 +1,474 @@
<template>
<div class="message-center app_content">
<!-- 顶部蓝色背景 -->
<div class="header-bg">
<!-- 城市天际线装饰 -->
<div class="cityscape"></div>
<!-- 标题区 -->
<div class="title-area">
<div class="bell-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
<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"/>
</svg>
</div>
<div class="title-group">
<div class="title-cn">消息中心</div>
<div class="title-en">Message Center</div>
</div>
</div>
</div>
<!-- Tab 切换 + 列表 -->
<div class="content-card">
<van-tabs
v-model:active="activeTab"
title-active-color="#1a1a2e"
title-inactive-color="#999"
color="#2b7de9"
line-width="40px"
line-height="3px"
:border="false"
>
<van-tab title="消息" name="message">
<div class="list-wrap">
<div
v-for="item in messageList"
:key="item.id"
class="msg-item"
@click="onItemClick(item)"
>
<van-badge
:content="item.unread"
:show-zero="false"
:max="99"
>
<div class="msg-icon" :class="item.iconClass">
<component :is="item.iconComp"/>
</div>
</van-badge>
<div class="msg-info">
<div class="msg-row">
<div class="msg-title">{{ item.title }}</div>
<div class="msg-time">{{ item.time }}</div>
</div>
<div class="msg-desc">{{ item.desc }}</div>
</div>
</div>
<div v-if="!messageList.length" class="empty-tip">暂无消息</div>
</div>
</van-tab>
<van-tab title="待办" name="todo">
<div class="list-wrap">
<div
v-for="item in todoList"
:key="item.id"
class="msg-item"
@click="onItemClick(item)"
>
<van-badge
:content="item.unread"
:show-zero="false"
:max="99"
>
<div class="msg-icon" :class="item.iconClass">
<component :is="item.iconComp"/>
</div>
</van-badge>
<div class="msg-info">
<div class="msg-row">
<div class="msg-title">{{ item.title }}</div>
<div class="msg-time">{{ item.time }}</div>
</div>
<div class="msg-desc">{{ item.desc }}</div>
</div>
</div>
<div v-if="!todoList.length" class="empty-tip">暂无待办</div>
</div>
</van-tab>
</van-tabs>
</div>
<!-- 底部导航栏 -->
<FooterBar active="message" />
</div>
</template>
<script setup name="messageCenter">
import { h, watch } from 'vue';
import { useRouter } from 'vue-router';
import { setMessageUnread } from '@/store/messageState';
const router = useRouter();
// ========== Tab 状态 ==========
const activeTab = ref('todo'); // 默认显示待办(与设计图一致)
// ========== 图标组件(SVG) ==========
const IconNotice = () => h('svg', { viewBox: '0 0 24 24', fill: 'currentColor' }, [
h('path', { d: 'M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM7 9h10v2H7V9zm6 5H7v-2h6v2zm4-6H7V6h10v2z' }),
]);
const IconTask = () => h('svg', { viewBox: '0 0 24 24', fill: 'currentColor' }, [
h('path', { d: 'M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 15l-4-4 1.41-1.41L14 15.17l4.59-4.59L20 12l-6 6z' }),
]);
const IconAppointment = () => h('svg', { viewBox: '0 0 24 24', fill: 'currentColor' }, [
h('path', { d: 'M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z' }),
]);
// ========== 列表数据 ==========
const messageList = ref([]);
const todoList = ref([
{
id: 1,
title: '执法预告',
desc: '最新一条预告信息标题',
time: '2026-04-15 10:07',
unread: 3,
iconComp: IconNotice,
iconClass: 'icon-blue',
type: 'notice',
},
{
id: 2,
title: '执法任务',
desc: '最新一条任务信息标题',
time: '2026-04-14 10:00',
unread: 2,
iconComp: IconTask,
iconClass: 'icon-cyan',
type: 'task',
},
{
id: 3,
title: '执法预约',
desc: '最新一条预约信息标题',
time: '',
unread: 0,
iconComp: IconAppointment,
iconClass: 'icon-green',
type: 'appointment',
},
]);
// 未读总数(tabbar 角标)
const totalUnread = computed(() => {
const total = [...messageList.value, ...todoList.value]
.reduce((sum, it) => sum + (Number(it.unread) || 0), 0);
return total > 0 ? total : '';
});
// 同步到全局共享 state,便于其他 tabbar 页面读取
watch(
[messageList, todoList],
() => {
const total = [...messageList.value, ...todoList.value]
.reduce((sum, it) => sum + (Number(it.unread) || 0), 0);
setMessageUnread(total);
},
{ immediate: true, deep: true }
);
// ========== 事件 ==========
const onBack = () => {
router.back();
};
const onItemClick = (item) => {
// TODO: 后续对接业务详情路由
// eslint-disable-next-line no-console
console.log('点击消息项:', item);
};
</script>
<style lang="less" scoped>
.message-center {
display: flex;
flex-direction: column;
height: 100%;
background: #f0f6ff;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
overflow: hidden;
box-sizing: border-box;
}
/* ===== 顶部蓝色背景 ===== */
.header-bg {
position: relative;
background: linear-gradient(180deg, #2b7de9 0%, #5ba3f5 60%, #a0cdf8 100%);
padding: 0 0 24px;
overflow: hidden;
flex-shrink: 0;
}
.nav-bar {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 12px;
position: relative;
z-index: 2;
.nav-back {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
svg {
width: 24px;
height: 24px;
}
}
.nav-title {
flex: 1;
text-align: center;
color: #fff;
font-size: 17px;
font-weight: 500;
}
.nav-right {
width: 32px;
}
}
.cityscape {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 100px;
opacity: 0.18;
pointer-events: none;
&::before {
content: '';
position: absolute;
bottom: 0;
left: 10%;
width: 8%;
height: 50px;
background: rgba(255, 255, 255, 0.5);
border-radius: 2px 2px 0 0;
box-shadow:
20px 15px 0 rgba(255, 255, 255, 0.4),
45px 30px 0 rgba(255, 255, 255, 0.35);
}
&::after {
content: '';
position: absolute;
bottom: 0;
right: 15%;
width: 6%;
height: 80px;
background: rgba(255, 255, 255, 0.4);
border-radius: 2px 2px 0 0;
box-shadow:
-30px 20px 0 rgba(255, 255, 255, 0.3),
-55px 35px 0 rgba(255, 255, 255, 0.25);
}
}
.title-area {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 20px 20px 10px;
gap: 14px;
.bell-icon {
width: 64px;
height: 64px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
svg {
width: 38px;
height: 38px;
color: #fff;
}
}
.title-group {
.title-cn {
font-size: 28px;
font-weight: 700;
color: #fff;
letter-spacing: 4px;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
.title-en {
margin-top: 4px;
font-size: 16px;
color: rgba(255, 255, 255, 0.75);
letter-spacing: 2px;
}
}
}
/* ===== 内容卡片 ===== */
.content-card {
flex: 1;
min-height: 0;
margin: -18px 0 0;
background: #fff;
border-radius: 18px 18px 0 0;
overflow: hidden;
display: flex;
flex-direction: column;
position: relative;
:deep(.van-tabs) {
display: flex;
flex-direction: column;
height: 100%;
}
:deep(.van-tabs__wrap) {
height: 52px;
border-bottom: 1px solid #f0f2f5;
flex-shrink: 0;
}
:deep(.van-tab) {
font-size: 16px;
}
:deep(.van-tab--active) {
font-weight: 700;
}
:deep(.van-tabs__content),
:deep(.van-tabs__track),
:deep(.van-tab__panel-wrapper),
:deep(.van-tab__pane) {
flex: 1;
min-height: 0;
height: 100%;
}
:deep(.van-tabs__content) {
overflow: hidden;
}
}
.list-wrap {
padding: 12px 14px 16px;
height: 100%;
box-sizing: border-box;
overflow-y: auto;
}
/* ===== 消息条目 ===== */
.msg-item {
display: flex;
align-items: center;
background: #fff;
padding: 14px 14px;
border-radius: 12px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
transition: background 0.2s;
&:active {
background: #f5f7fa;
}
}
.msg-icon {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
svg {
width: 26px;
height: 26px;
color: #fff;
}
&.icon-blue {
background: linear-gradient(135deg, #60a5fa, #3b82f6);
box-shadow: 0 4px 10px rgba(59, 130, 246, 0.25);
}
&.icon-cyan {
background: linear-gradient(135deg, #22d3ee, #06b6d4);
box-shadow: 0 4px 10px rgba(6, 182, 212, 0.25);
}
&.icon-green {
background: linear-gradient(135deg, #2dd4bf, #14b8a6);
box-shadow: 0 4px 10px rgba(20, 184, 166, 0.25);
}
}
.msg-info {
flex: 1;
margin-left: 14px;
min-width: 0;
}
.msg-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
.msg-title {
font-size: 16px;
font-weight: 600;
color: #1a1a2e;
}
.msg-time {
font-size: 12px;
color: #9ca3af;
flex-shrink: 0;
}
}
.msg-desc {
margin-top: 6px;
font-size: 13px;
color: #9ca3af;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.empty-tip {
text-align: center;
color: #bbb;
font-size: 14px;
padding: 60px 0;
}
/* 调整 Vant badge 样式 */
:deep(.van-badge) {
font-size: 11px;
min-width: 18px;
height: 18px;
line-height: 18px;
padding: 0 4px;
border: 2px solid #fff;
background: #f5222d;
}
</style>

348
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/enforcementMy.vue

@ -0,0 +1,348 @@
<template>
<div class="my-page app_content">
<!-- 顶部蓝色背景(含城市天际线 + 用户信息) -->
<div class="header-bg">
<div class="cityscape"></div>
<div class="user-wrap">
<div class="avatar">
<img
:src="userInfo.avatar"
alt="avatar"
@error="onAvatarError"
/>
</div>
<div class="user-name">{{ userInfo.name }}</div>
<div class="user-phone">电话:{{ userInfo.phone }}</div>
</div>
</div>
<!-- 菜单列表 -->
<div class="menu-wrap">
<div class="menu-card">
<div
v-for="(item, idx) in menuList"
:key="item.key"
class="menu-item"
:class="{ 'menu-item--last': idx === menuList.length - 1 }"
@click="onMenuClick(item)"
>
<div class="menu-icon" :class="item.iconClass">
<component :is="item.iconComp"/>
</div>
<div class="menu-label">{{ item.label }}</div>
<div class="menu-arrow">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6z"/>
</svg>
</div>
</div>
</div>
<!-- 设置单独一组卡片 -->
<div class="menu-card menu-card--split">
<div class="menu-item menu-item--last" @click="onMenuClick(settingItem)">
<div class="menu-icon" :class="settingItem.iconClass">
<component :is="settingItem.iconComp"/>
</div>
<div class="menu-label">{{ settingItem.label }}</div>
<div class="menu-arrow">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6z"/>
</svg>
</div>
</div>
</div>
</div>
<!-- 底部导航栏 -->
<FooterBar active="my" />
</div>
</template>
<script setup name="enforcementMy">
import { h } from 'vue';
import { useRouter } from 'vue-router';
import { showToast } from 'vant';
const router = useRouter();
// ========== 用户信息 ==========
const defaultAvatar = 'https://img.yzcdn.cn/vant/cat.jpeg';
const userInfo = ref({
name: '司小华',
phone: '13800138000',
avatar: defaultAvatar,
});
const onAvatarError = (e) => {
e.target.src = defaultAvatar;
};
// ========== 菜单图标(SVG) ==========
const IconNotice = () => h('svg', { viewBox: '0 0 24 24', fill: 'currentColor' }, [
h('path', { d: 'M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM7 9h10v2H7V9zm6 5H7v-2h6v2zm4-6H7V6h10v2z' }),
]);
const IconTask = () => h('svg', { viewBox: '0 0 24 24', fill: 'currentColor' }, [
h('path', { d: 'M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 15l-4-4 1.41-1.41L14 15.17l4.59-4.59L20 12l-6 6z' }),
]);
const IconAppointment = () => h('svg', { viewBox: '0 0 24 24', fill: 'currentColor' }, [
h('path', { d: 'M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z' }),
]);
const IconPlan = () => h('svg', { viewBox: '0 0 24 24', fill: 'currentColor' }, [
h('path', { d: 'M3.5 18.49l6-6.01 4 4L22 6.92l-1.41-1.41-7.09 7.97-4-4L2 16.99z' }),
]);
const IconSetting = () => h('svg', { viewBox: '0 0 24 24', fill: 'currentColor' }, [
h('path', { d: 'M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.488.488 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z' }),
]);
// ========== 菜单配置 ==========
const menuList = ref([
{ key: 'notice', label: '预告', iconComp: IconNotice, iconClass: 'icon-blue' },
{ key: 'task', label: '任务', iconComp: IconTask, iconClass: 'icon-cyan' },
{ key: 'appointment', label: '预约', iconComp: IconAppointment, iconClass: 'icon-teal' },
{ key: 'plan', label: '计划', iconComp: IconPlan, iconClass: 'icon-green' },
]);
const settingItem = {
key: 'setting',
label: '设置',
iconComp: IconSetting,
iconClass: 'icon-purple',
};
// ========== 菜单点击 ==========
const onMenuClick = (item) => {
switch (item.key) {
case 'notice':
case 'task':
case 'appointment':
// 跳至消息中心待办 tab,并带类型过滤参数
router.push({
path: '/enforcement/message',
query: { tab: 'todo', type: item.key },
});
break;
case 'plan':
// TODO: 对接执法计划列表页
showToast('执法计划功能开发中');
break;
case 'setting':
// TODO: 对接设置页
showToast('设置功能开发中');
break;
default:
showToast('功能开发中');
}
};
</script>
<style lang="less" scoped>
.my-page {
display: flex;
flex-direction: column;
height: 100%;
background: #f0f6ff;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
overflow: hidden;
}
/* ===== 顶部蓝色背景 ===== */
.header-bg {
position: relative;
background: linear-gradient(180deg, #2b7de9 0%, #5ba3f5 60%, #a0cdf8 100%);
padding: 24px 20px 70px;
overflow: hidden;
flex-shrink: 0;
}
.cityscape {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 110px;
opacity: 0.2;
pointer-events: none;
&::before {
content: '';
position: absolute;
bottom: 0;
left: 6%;
width: 8%;
height: 60px;
background: rgba(255, 255, 255, 0.5);
border-radius: 2px 2px 0 0;
box-shadow:
22px 18px 0 rgba(255, 255, 255, 0.4),
48px 30px 0 rgba(255, 255, 255, 0.35),
75px 12px 0 rgba(255, 255, 255, 0.3);
}
&::after {
content: '';
position: absolute;
bottom: 0;
right: 10%;
width: 6%;
height: 80px;
background: rgba(255, 255, 255, 0.4);
border-radius: 2px 2px 0 0;
box-shadow:
-30px 20px 0 rgba(255, 255, 255, 0.35),
-55px 35px 0 rgba(255, 255, 255, 0.28),
-80px 18px 0 rgba(255, 255, 255, 0.22);
}
}
.user-wrap {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
.avatar {
width: 84px;
height: 84px;
border-radius: 50%;
overflow: hidden;
border: 3px solid rgba(255, 255, 255, 0.9);
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
background: #fff;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.user-name {
font-size: 22px;
font-weight: 700;
color: #fff;
letter-spacing: 2px;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.user-phone {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
letter-spacing: 1px;
}
}
/* ===== 菜单列表 ===== */
.menu-wrap {
flex: 1;
overflow-y: auto;
padding: 0 16px 16px;
margin-top: -48px;
position: relative;
z-index: 2;
}
.menu-card {
background: #fff;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
margin-bottom: 14px;
}
.menu-card--split {
margin-top: 4px;
}
.menu-item {
display: flex;
align-items: center;
padding: 14px 16px;
position: relative;
transition: background 0.2s;
&::after {
content: '';
position: absolute;
left: 60px;
right: 16px;
bottom: 0;
height: 1px;
background: #f0f2f5;
transform: scaleY(0.5);
}
&--last::after {
display: none;
}
&:active {
background: #f5f7fa;
}
}
.menu-icon {
width: 34px;
height: 34px;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 14px;
svg {
width: 20px;
height: 20px;
color: #fff;
}
&.icon-blue {
background: linear-gradient(135deg, #60a5fa, #3b82f6);
}
&.icon-cyan {
background: linear-gradient(135deg, #22d3ee, #06b6d4);
}
&.icon-teal {
background: linear-gradient(135deg, #2dd4bf, #14b8a6);
}
&.icon-green {
background: linear-gradient(135deg, #34d399, #10b981);
}
&.icon-purple {
background: linear-gradient(135deg, #a78bfa, #8b5cf6);
}
}
.menu-label {
flex: 1;
font-size: 15px;
color: #1a1a2e;
font-weight: 500;
}
.menu-arrow {
width: 18px;
height: 18px;
color: #c0c4cc;
display: flex;
align-items: center;
justify-content: center;
svg {
width: 100%;
height: 100%;
}
}
</style>

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

@ -0,0 +1,43 @@
<template>
<div class="query-page app_content">
<div class="page-header">
<div class="page-title">查询</div>
</div>
<div class="page-body">
<van-empty description="查询功能开发中" />
</div>
<!-- 底部导航栏 -->
<FooterBar active="query" />
</div>
</template>
<script setup name="enforcementQuery">
</script>
<style lang="less" scoped>
.query-page {
display: flex;
flex-direction: column;
height: 100%;
background: #f0f6ff;
}
.page-header {
height: 44px;
line-height: 44px;
text-align: center;
background: linear-gradient(180deg, #2b7de9 0%, #5ba3f5 100%);
color: #fff;
font-size: 17px;
font-weight: 500;
}
.page-body {
flex: 1;
overflow-y: auto;
display: flex;
align-items: center;
justify-content: center;
}
</style>
Loading…
Cancel
Save