Browse Source

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

dev-V1.0.1
庄锐波 6 months ago
parent
commit
fef3843cef
  1. 1
      zdxt-web-client/zdxt-efcode-enterprise-app/.gitignore
  2. BIN
      zdxt-web-client/zdxt-efcode-enterprise-app/src/assets/images/bg_ajxx.jpg
  3. BIN
      zdxt-web-client/zdxt-efcode-enterprise-app/src/assets/images/bg_ajxx.png
  4. BIN
      zdxt-web-client/zdxt-efcode-enterprise-app/src/assets/images/bg_jdm.png
  5. BIN
      zdxt-web-client/zdxt-efcode-enterprise-app/src/assets/images/bg_zfxx.jpg
  6. BIN
      zdxt-web-client/zdxt-efcode-enterprise-app/src/assets/images/bg_zfxx.png
  7. 7
      zdxt-web-client/zdxt-efcode-enterprise-app/src/router/enterprise-app/ent-main-router.js
  8. 13
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementCaseInfo.vue
  9. 596
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementChat.vue
  10. 59
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementEvaluateDetail.vue
  11. 9
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementNoticeList.vue
  12. 18
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/home/enterpriseHomeV2.vue
  13. 238
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/message/messageCenter.vue
  14. 83
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/my/complaintSuggestion.vue
  15. 93
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/my/myPage.vue
  16. 59
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/my/settings.vue

1
zdxt-web-client/zdxt-efcode-enterprise-app/.gitignore

@ -17,6 +17,7 @@ package-lock.json
# Editor directories and files
.idea
.vscode
.qoder
*.suo
*.ntvs*
*.njsproj

BIN
zdxt-web-client/zdxt-efcode-enterprise-app/src/assets/images/bg_ajxx.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

BIN
zdxt-web-client/zdxt-efcode-enterprise-app/src/assets/images/bg_ajxx.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
zdxt-web-client/zdxt-efcode-enterprise-app/src/assets/images/bg_jdm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

BIN
zdxt-web-client/zdxt-efcode-enterprise-app/src/assets/images/bg_zfxx.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

BIN
zdxt-web-client/zdxt-efcode-enterprise-app/src/assets/images/bg_zfxx.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

7
zdxt-web-client/zdxt-efcode-enterprise-app/src/router/enterprise-app/ent-main-router.js

@ -124,6 +124,13 @@ const routes = [
name: "enforcementEvaluateDetail",
meta: { title: "评价详情" },
component: () => import("@/views/enterprise/enforcement/enforcementEvaluateDetail.vue"),
},
// 在线联系
{
path: "/enterprise/enforcementChat",
name: "enforcementChat",
meta: { title: "在线联系" },
component: () => import("@/views/enterprise/enforcement/enforcementChat.vue"),
}
];

13
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementCaseInfo.vue

@ -260,23 +260,28 @@ const toDetailView = (item) => {
top: 0;
left: 0;
padding: 0.1rem 0.3rem;
border-radius: 0.2rem 0 0.2rem 0;
// border-radius: 0.2rem 0 0.2rem 0;
font-size: 0.28rem;
color: #fff;
font-weight: 500;
z-index: 1;
height: 24px;
width: 52px;
}
.tag-blue {
background: linear-gradient(135deg, #4a9eff, #2d94fa);
background: url('../../../assets/images/bg_label_jc.png') no-repeat center center;
background-size: 100% 100%;
}
.tag-purple {
background: linear-gradient(135deg, #9b7cf5, #7c5cfb);
background: url('../../../assets/images/bg_label_cf.png') no-repeat center center;
background-size: 100% 100%;
}
.tag-orange {
background: linear-gradient(135deg, #ff9f5e, #f0853a);
background: url('../../../assets/images/bg_label_qz.png') no-repeat center center;
background-size: 100% 100%;
}
/* 卡片内容 */

596
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementChat.vue

@ -0,0 +1,596 @@
<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 { 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: 'self',
content: `你好,预告的执法时间${route.query.enforcementTime || '2026-04-07'}检查,无法正常迎检,可否调整到2026-04-10下午?`,
time: '2026-03-31 10:20',
avatar: selfAvatar,
isRecalled: false,
},
{
id: 2,
sender: 'other',
content: '好的',
time: '2026-03-31 18:32',
avatar: otherAvatar,
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();
});
// 模拟对方回复
// setTimeout(() => {
// messageList.value.push({
// id: Date.now() + 1,
// sender: 'other',
// content: '收到,我们会尽快安排并与您确认具体时间。',
// time: timeStr,
// avatar: otherAvatar,
// isRecalled: false,
// });
// nextTick(() => {
// scrollToBottom();
// });
// }, 1500);
};
// ========== 滚动到底部 ==========
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>

59
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementEvaluateDetail.vue

@ -98,9 +98,11 @@
:class="{ active: evaluateForm.satisfaction === '满意' }"
@click="evaluateForm.satisfaction = '满意'"
>
<div class="emoji-icon happy">
<van-icon name="smile-o" size="32" />
</div>
<img
class="emoji-img"
:src="evaluateForm.satisfaction === '满意' ? veryGoodImg : veryGoodDisImg"
alt="满意"
/>
<span class="emoji-label">满意</span>
</div>
<div
@ -108,9 +110,11 @@
:class="{ active: evaluateForm.satisfaction === '较满意' }"
@click="evaluateForm.satisfaction = '较满意'"
>
<div class="emoji-icon normal">
<van-icon name="smile-comment-o" size="32" />
</div>
<img
class="emoji-img"
:src="evaluateForm.satisfaction === '较满意' ? goodImg : goodDisImg"
alt="较满意"
/>
<span class="emoji-label">较满意</span>
</div>
<div
@ -118,9 +122,11 @@
:class="{ active: evaluateForm.satisfaction === '不满意' }"
@click="evaluateForm.satisfaction = '不满意'"
>
<div class="emoji-icon unhappy">
<van-icon name="warning-o" size="32" />
</div>
<img
class="emoji-img"
:src="evaluateForm.satisfaction === '不满意' ? badImg : badDisImg"
alt="不满意"
/>
<span class="emoji-label">不满意</span>
</div>
</div>
@ -192,6 +198,12 @@
<script setup name="enforcementEvaluateDetail">
import { showToast } from 'vant';
import 'vant/es/toast/style';
import veryGoodImg from '@/assets/images/eval-verygood.png';
import veryGoodDisImg from '@/assets/images/eval-verygood-dis.png';
import goodImg from '@/assets/images/eval-good.png';
import goodDisImg from '@/assets/images/eval-good-dis.png';
import badImg from '@/assets/images/eval-bad.png';
import badDisImg from '@/assets/images/eval-bad-dis.png';
const router = useRouter();
const route = useRoute();
@ -409,36 +421,17 @@ const onSubmit = () => {
&.active {
opacity: 1;
.emoji-icon {
.emoji-img {
transform: scale(1.1);
}
}
}
.emoji-icon {
width: 0.9rem;
height: 0.9rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.1rem;
.emoji-img {
width: 40px;
height: 40px;
margin-bottom: 4px;
transition: transform 0.2s;
&.happy {
background: #fff3e0;
color: #ff9800;
}
&.normal {
background: #f5f5f5;
color: #999;
}
&.unhappy {
background: #ffebee;
color: #f44336;
}
}
.emoji-label {

9
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementNoticeList.vue

@ -155,7 +155,14 @@ const onSearchClick = () => {
// ========== 反馈点击 ==========
const onFeedback = (item) => {
showToast(`反馈:${item.title}`);
router.push({
path: '/enterprise/enforcementChat',
query: {
title: item.title,
bureauDeptName: item.bureauDeptName,
enforcementTime: item.enforcementTime,
}
});
};
</script>

18
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/home/enterpriseHomeV2.vue

@ -46,7 +46,7 @@
</div>
<!-- 执法案件信息 -->
<div class="info-card" @click="toEnforcementCaseInfoView">
<div class="info-card info-enforce" @click="toEnforcementCaseInfoView">
<h4>执法案件信息</h4>
<div class="card-icon">
<img src="@/assets/images/icon_ajxx.jpg" class="card-icon-img" alt="" />
@ -436,7 +436,7 @@ onMounted(() => {
/* 执法监督码卡片 */
.supervision-card {
background: url('../../../assets/images/bg_jdm.jpg') center center no-repeat;
background: url('../../../assets/images/bg_jdm.jpg') center center no-repeat, linear-gradient(180deg, #7cc5fe 0%, #fff 100%);
background-size: 100% 100%;
border-radius: 16px;
padding: 18px 20px;
@ -444,7 +444,6 @@ onMounted(() => {
position: relative;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
h3 {
font-size: 14px;
color: #1a1a2e;
@ -461,9 +460,9 @@ onMounted(() => {
.qr-image {
width: 100px;
height: 100px;
// height: 100px;
object-fit: contain;
border-radius: 16px;
border-radius: 40px;
}
}
@ -476,14 +475,14 @@ onMounted(() => {
.info-card {
flex: 1;
background: linear-gradient(135deg, #eaf3ff 0%, #d6eaff 60%, #c8e0f8 100%);
border-radius: 16px;
padding: 18px 16px;
position: relative;
overflow: hidden;
min-height: 90px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
background: url('../../../assets/images/bg_zfxx.png') center center no-repeat, linear-gradient(180deg, #bbecfd 0%, #fff 100%);
background-size: 100% 100%;
h4 {
font-size: 14px;
color: #1a1a2e;
@ -511,7 +510,10 @@ onMounted(() => {
}
}
}
.info-enforce{
background: url('../../../assets/images/bg_ajxx.png') center center no-repeat, linear-gradient(180deg, #a6d1f9 0%, #fff 100%);
background-size: 100% 100%;
}
/* ===== 底部导航 ===== */
.footer_body {
flex-shrink: 0;

238
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/message/messageCenter.vue

@ -11,22 +11,7 @@
<!-- 顶部蓝色背景区域 -->
<div class="header-bg">
<div class="header-content">
<!-- 铃铛图标 -->
<div class="bell-wrapper">
<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.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2zm-2 1H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6z"/>
</svg>
</div>
</div>
<!-- 标题 -->
<div class="header-title">
<div class="title-cn">消息中心</div>
<div class="title-en">Message Center</div>
</div>
</div>
<!-- 城市剪影装饰 -->
<div class="cityscape-decoration"></div>
</div>
<!-- 内容区域 -->
@ -36,30 +21,16 @@
<van-tab title="消息">
<!-- 消息列表 -->
<van-pull-refresh v-model="messageRefreshing" @refresh="onMessageRefresh">
<van-list
v-model:loading="messageLoading"
:finished="messageFinished"
finished-text="没有更多了"
@load="onMessageLoad"
:offset="50"
>
<van-list v-model:loading="messageLoading" :finished="messageFinished" finished-text="没有更多了"
@load="onMessageLoad" :offset="50">
<van-empty v-if="messageFinished && messageList.length === 0" description="暂无消息" />
<div
class="message-item"
v-for="item in messageList"
:key="item.id"
@click="onMessageClick(item)"
>
<div class="message-item" v-for="item in messageList" :key="item.id"
@click="onMessageClick(item)">
<div class="item-icon" :class="item.iconBg">
<svg viewBox="0 0 24 24" fill="currentColor">
<path v-if="item.icon === 'law'" d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/>
<path v-else-if="item.icon === 'check'" d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
<path v-else-if="item.icon === 'notice'" d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 12h-2v-2h2v2zm0-4h-2V6h2v4z"/>
<path v-else d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/>
</svg>
<!-- 红点角标 -->
<span v-if="item.unreadCount > 0" class="icon-badge">{{ item.unreadCount > 99 ? '99+' : item.unreadCount }}</span>
<span v-if="item.unreadCount > 0" class="icon-badge">{{ item.unreadCount > 99 ?
'99+' : item.unreadCount }}</span>
</div>
<div class="item-info">
<div class="item-header">
@ -76,26 +47,15 @@
<van-tab title="待办">
<!-- 待办列表 -->
<van-pull-refresh v-model="todoRefreshing" @refresh="onTodoRefresh">
<van-list
v-model:loading="todoLoading"
:finished="todoFinished"
finished-text="没有更多了"
@load="onTodoLoad"
:offset="50"
>
<van-list v-model:loading="todoLoading" :finished="todoFinished" finished-text="没有更多了"
@load="onTodoLoad" :offset="50">
<van-empty v-if="todoFinished && todoList.length === 0" description="暂无待办" />
<div
class="message-item"
v-for="item in todoList"
:key="item.id"
@click="onTodoClick(item)"
>
<div class="message-item" v-for="item in todoList" :key="item.id"
@click="onTodoClick(item)">
<div class="item-icon" :class="item.iconBg">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
</svg>
<span v-if="item.unreadCount > 0" class="icon-badge">{{ item.unreadCount > 99 ? '99+' : item.unreadCount }}</span>
<span v-if="item.unreadCount > 0" class="icon-badge">{{ item.unreadCount > 99 ?
'99+' : item.unreadCount }}</span>
</div>
<div class="item-info">
<div class="item-header">
@ -157,42 +117,6 @@ const generateMessageMockData = () => {
iconBg: 'icon-blue',
unreadCount: 1,
},
{
id: 2,
title: '执法检查通知',
desc: '您有一条新的执法检查通知,请查收',
time: '2026-04-14 16:30',
icon: 'check',
iconBg: 'icon-orange',
unreadCount: 2,
},
{
id: 3,
title: '系统公告',
desc: '系统将于今晚22:00进行维护升级',
time: '2026-04-13 09:00',
icon: 'notice',
iconBg: 'icon-green',
unreadCount: 0,
},
{
id: 4,
title: '行政处罚提醒',
desc: '您的企业有一条行政处罚记录待处理',
time: '2026-04-12 14:20',
icon: 'law',
iconBg: 'icon-red',
unreadCount: 1,
},
{
id: 5,
title: '执法评价邀请',
desc: '请您对本次执法进行评价',
time: '2026-04-10 11:15',
icon: 'check',
iconBg: 'icon-purple',
unreadCount: 0,
},
];
};
@ -217,15 +141,6 @@ const generateTodoMockData = () => {
iconBg: 'icon-orange',
unreadCount: 2,
},
{
id: 103,
title: '待确认登记',
desc: '有一条执法登记信息待您确认',
time: '2026-04-13 10:45',
icon: 'check',
iconBg: 'icon-blue',
unreadCount: 1,
},
];
};
@ -379,107 +294,22 @@ onMounted(() => {
/* ===== 顶部蓝色背景区域 ===== */
.header-bg {
background: linear-gradient(180deg, #2b7de9 0%, #5ba3f5 40%, #a0cdf8 70%, #dceefb 90%, #eef5ff 100%);
padding: 20px 20px 30px;
position: relative;
overflow: hidden;
flex-shrink: 0;
z-index: 3;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background: linear-gradient(180deg, transparent, rgba(255,255,255,0.3));
}
background: url('@/assets/images/bg_msg.jpg') center center no-repeat;
background-size: 100% 100%;
height: 220px;
}
.header-content {
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 1;
gap: 12px;
}
/* 铃铛图标 */
.bell-wrapper {
width: 60px;
height: 60px;
background: rgba(255, 255, 255, 0.25);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(4px);
}
.bell-icon {
width: 36px;
height: 36px;
color: rgba(255, 255, 255, 0.9);
}
/* 标题 */
.header-title {
text-align: center;
.title-cn {
font-size: 24px;
font-weight: bold;
color: #fff;
letter-spacing: 3px;
text-shadow: 0 1px 4px rgba(0,0,0,0.15);
}
.title-en {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
margin-top: 4px;
letter-spacing: 1px;
}
}
/* 城市剪影装饰 */
.cityscape-decoration {
background: url('@/assets/images/label_msg.png') center center no-repeat;
background-size: 100% 100%;
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 80px;
opacity: 0.12;
&::before {
content: '';
position: absolute;
bottom: 0;
left: 8%;
width: 10%;
height: 50px;
background: rgba(255,255,255,0.5);
border-radius: 2px 2px 0 0;
}
&::after {
content: '';
position: absolute;
bottom: 0;
right: 12%;
width: 8%;
height: 70px;
background: rgba(255,255,255,0.4);
border-radius: 2px 2px 0 0;
box-shadow:
-25px 15px 0 rgba(255,255,255,0.3),
-50px 30px 0 rgba(255,255,255,0.2),
25px 20px 0 rgba(255,255,255,0.25);
}
top: 5%;
left: 5%;
width: 260px;
height: 90px;
}
/* ===== 内容区域 ===== */
.content-area {
flex: 1;
@ -487,16 +317,17 @@ onMounted(() => {
position: relative;
z-index: 2;
margin-top: -10px;
background: #f0f6ff;
// background: #105ccf;
border-radius: 12px 12px 0 0;
top: -65px;
}
/* ===== Tab 样式覆盖 ===== */
:deep(.message-tabs .van-tabs__wrap) {
background: #fff;
border-radius: 12px 12px 0 0;
border-top-left-radius: 0.68rem;
border-top-right-radius: 0.68rem;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
:deep(.message-tabs .van-tab) {
@ -553,23 +384,18 @@ onMounted(() => {
/* 图标背景色 */
.icon-blue {
background: linear-gradient(135deg, #4a9eff, #2d94fa);
background: url('../../../assets/images/icon_pffw.jpg') center center no-repeat;
background-size: 100% 100%;
}
.icon-orange {
background: linear-gradient(135deg, #ff9f5e, #f0853a);
}
.icon-green {
background: linear-gradient(135deg, #67c23a, #5daf34);
background: url('../../../assets/images/icon_zfyg.jpg') center center no-repeat;
background-size: 100% 100%;
}
.icon-red {
background: linear-gradient(135deg, #f56c6c, #e64340);
}
.icon-purple {
background: linear-gradient(135deg, #9b7cf5, #7c5cfb);
background: url('../../../assets/images/icon_zfpj.jpg') center center no-repeat;
background-size: 100% 100%;
}
/* 红点角标 */

83
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/my/complaintSuggestion.vue

@ -60,26 +60,41 @@
{ required: true, message: '请填写联系电话' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }
]" class="contact-field" required />
<!-- 是否授权 -->
<div class="authorize-bar">
<van-icon name="info" color="#2b7de9" size="18" />
<span class="authorize-text">是否授权</span>
<van-radio-group v-model="form.authorized" direction="horizontal">
<van-radio :name="true">
<template #icon="{ checked }">
<div class="custom-radio-icon" :class="{ checked }">
<van-icon v-if="checked" name="success" size="10" color="#fff" />
</div>
</template>
同意
</van-radio>
<van-radio :name="false">
<template #icon="{ checked }">
<div class="custom-radio-icon" :class="{ checked }">
<van-icon v-if="checked" name="success" size="10" color="#fff" />
</div>
</template>
不同意
</van-radio>
</van-radio-group>
</div>
<div class="remark-text">注:选择“同意”,处置部门将通过登记的联系人、联系电话与您联系,并对您的个人信息严格保密。</div>
</div>
<!-- 是否授权 -->
<div class="authorize-bar">
<van-icon name="info" color="#f5a623" size="18" />
<span class="authorize-text">是否授权</span>
<van-radio-group v-model="form.authorized" direction="horizontal">
<van-radio :name="true" checked-color="#2b7de9" icon-size="16">同意</van-radio>
<van-radio :name="false" icon-size="16">不同意</van-radio>
</van-radio-group>
</div>
<!-- 底部提交栏 -->
<div class="footer-bar">
<van-button type="primary" block round native-type="submit" :loading="submitting" loading-text="提交中...">提交</van-button>
<van-button type="primary" block round native-type="submit" :loading="submitting"
loading-text="提交中...">提交</van-button>
</div>
</van-form>
<!-- 投诉事项选择弹窗 -->
<ComplaintItemPicker v-model:show="showComplaintItemPicker" :list="complaintItemList" @select="onComplaintItemSelect" />
<ComplaintItemPicker v-model:show="showComplaintItemPicker" :list="complaintItemList"
@select="onComplaintItemSelect" />
<!-- 地址级联选择弹窗 -->
<van-popup v-model:show="showAddressPicker" position="bottom" :style="{ width: '100%', height: '60%' }">
@ -88,7 +103,8 @@
</van-popup>
<!-- 投诉类型选择弹窗 -->
<ComplaintTypePicker v-model:show="showComplaintTypePicker" :columns="complaintTypeList" @confirm="onComplaintTypeConfirm" />
<ComplaintTypePicker v-model:show="showComplaintTypePicker" :columns="complaintTypeList"
@confirm="onComplaintTypeConfirm" />
<!-- 执法单位查询弹窗 -->
<BureauPicker v-model:show="showBureauPicker" :list="bureauList" @confirm="onBureauConfirm" />
@ -419,10 +435,9 @@ const onFailed = (errorInfo) => {
.authorize-bar {
display: flex;
align-items: center;
background: #fff7e6;
background: #fff;
border-radius: 0.2rem;
padding: 0.25rem 0.3rem;
margin-bottom: 0.27rem;
padding: 0.25rem 0.48rem;
gap: 0.2rem;
.authorize-text {
@ -430,13 +445,41 @@ const onFailed = (errorInfo) => {
font-size: 0.32rem;
color: #333;
}
}
.remark-text {
font-size: 0.28rem;
color: #999;
padding:0px 20px 20px 20px;
box-sizing: border-box;
}
.auth-option {
.custom-radio-icon {
width: 16px;
height: 16px;
border: 1px solid #c8c9cc;
border-radius: 3px;
display: flex;
align-items: center;
gap: 0.1rem;
cursor: pointer;
justify-content: center;
box-sizing: border-box;
&.checked {
background-color: #2b7de9;
border-color: #2b7de9;
}
}
:deep(.van-radio) {
margin-right: 0.2rem;
&:last-child {
margin-right: 0;
}
}
:deep(.van-radio__label) {
font-size: 0.28rem;
color: #333;
}

93
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/my/myPage.vue

@ -4,17 +4,11 @@
<div class="header-bg">
<div class="user-info">
<div class="avatar-wrapper">
<img
class="avatar"
src="https://picsum.photos/120/120?random=avatar"
alt="avatar"
/>
<img class="avatar" src="https://picsum.photos/120/120?random=avatar" alt="avatar" />
</div>
<div class="user-name">{{ useCommon.enterpriseName || '胡小鹏' }}</div>
<div class="user-phone">电话:{{ useCommon.phonenumber || '13800138000' }}</div>
</div>
<!-- 城市剪影装饰 -->
<div class="cityscape-decoration"></div>
</div>
<!-- 内容区域 -->
@ -23,9 +17,6 @@
<div class="menu-list">
<div class="menu-item" @click="toComplaintSuggestion">
<div class="menu-icon icon-blue">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 12h-2v-2h2v2zm0-4h-2V6h2v4z"/>
</svg>
</div>
<div class="menu-text">投诉建议</div>
<van-icon name="arrow" class="menu-arrow" />
@ -33,9 +24,6 @@
<div class="menu-item" @click="toSettings">
<div class="menu-icon icon-purple">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.488.488 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.488.488 0 0 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 0 0-.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-.58 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"/>
</svg>
</div>
<div class="menu-text">设置</div>
<van-icon name="arrow" class="menu-arrow" />
@ -81,22 +69,10 @@ const toSettings = () => {
/* ===== 顶部蓝色背景区域 ===== */
.header-bg {
background: linear-gradient(180deg, #2b7de9 0%, #5ba3f5 40%, #a0cdf8 70%, #dceefb 90%, #eef5ff 100%);
padding: 40px 20px 50px;
position: relative;
overflow: hidden;
flex-shrink: 0;
z-index: 3;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background: linear-gradient(180deg, transparent, rgba(255,255,255,0.3));
}
padding-top: 26px;
background: url('@/assets/images/bg_msg.jpg') center center no-repeat;
background-size: 100% 100%;
height: 270px;
}
.user-info {
@ -123,52 +99,16 @@ const toSettings = () => {
}
.user-name {
font-size: 20px;
font-weight: bold;
color: #fff;
color: #000;
margin-bottom: 6px;
text-shadow: 0 1px 4px rgba(0,0,0,0.15);
// text-shadow: 0 1px 4px rgba(0,0,0,0.15);
font: 0.6em sans-serif;
}
.user-phone {
font-size: 14px;
color: rgba(255, 255, 255, 0.85);
}
/* 城市剪影装饰 */
.cityscape-decoration {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 80px;
opacity: 0.12;
&::before {
content: '';
position: absolute;
bottom: 0;
left: 8%;
width: 10%;
height: 50px;
background: rgba(255,255,255,0.5);
border-radius: 2px 2px 0 0;
}
&::after {
content: '';
position: absolute;
bottom: 0;
right: 12%;
width: 8%;
height: 70px;
background: rgba(255,255,255,0.4);
border-radius: 2px 2px 0 0;
box-shadow:
-25px 15px 0 rgba(255,255,255,0.3),
-50px 30px 0 rgba(255,255,255,0.2),
25px 20px 0 rgba(255,255,255,0.25);
}
color: #8d8d8d;
}
/* ===== 内容区域 ===== */
@ -177,10 +117,11 @@ const toSettings = () => {
overflow-y: auto;
position: relative;
z-index: 2;
// margin-top: -10px;
background: #fff;
border-radius: 28px 28px 0 0;
margin-top: -10px;
background: #f0f6ff;
border-radius: 12px 12px 0 0;
padding: 16px;
top: -92px;
}
/* ===== 菜单列表 ===== */
@ -195,7 +136,7 @@ const toSettings = () => {
align-items: center;
padding: 16px;
background: #fff;
border-radius: 12px;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
&:active {
@ -220,11 +161,13 @@ const toSettings = () => {
}
.icon-blue {
background: linear-gradient(135deg, #4a9eff, #2d94fa);
background: url('../../../assets/images/icon_tsjy.png') center center no-repeat;
background-size: 100% 100%;
}
.icon-purple {
background: linear-gradient(135deg, #9b7cf5, #7c5cfb);
background: url('../../../assets/images/icon_set.png') center center no-repeat;
background-size: 100% 100%;
}
.menu-text {

59
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/my/settings.vue

@ -21,24 +21,46 @@
<span class="item-text">公司名称</span>
<span class="item-value">深圳市新粤云技术有限公司</span>
</div>
<div class="settings-item" @click="onItemClick('联系电话')">
<div class="settings-item" @click="onEditPhone">
<span class="item-text">联系电话</span>
<div class="item-right">
<span class="item-value">18358047213</span>
<span class="item-value">{{ phone }}</span>
<van-icon name="arrow" class="item-arrow" />
</div>
</div>
</div>
</div>
<!-- 修改电话弹窗 -->
<van-dialog
v-model:show="showPhoneDialog"
title="修改联系电话"
show-cancel-button
:before-close="onBeforeClose"
>
<van-field
v-model="editPhone"
type="tel"
placeholder="请输入联系电话"
maxlength="11"
:rules="[{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }]"
/>
</van-dialog>
</div>
</template>
<script setup name="settings">
import { ref } from 'vue';
import { showToast } from 'vant';
import 'vant/es/toast/style';
const router = useRouter();
// ========== 联系电话 ==========
const phone = ref('18358047213');
const showPhoneDialog = ref(false);
const editPhone = ref('');
// ========== 返回上一页 ==========
const onClickLeft = () => {
router.back();
@ -46,7 +68,38 @@ const onClickLeft = () => {
// ========== 点击设置项 ==========
const onItemClick = (name) => {
showToast(`${name}功能开发中`);
// showToast(`${name}功能开发中`);
};
// ========== 编辑电话 ==========
const onEditPhone = () => {
editPhone.value = phone.value;
showPhoneDialog.value = true;
};
// ========== 弹窗关闭前校验 ==========
const onBeforeClose = (action) => {
return new Promise((resolve) => {
if (action === 'cancel') {
editPhone.value = '';
resolve(true);
return;
}
const pattern = /^1[3-9]\d{9}$/;
if (!editPhone.value) {
showToast('请输入联系电话');
resolve(false);
return;
}
if (!pattern.test(editPhone.value)) {
showToast('请输入正确的手机号');
resolve(false);
return;
}
phone.value = editPhone.value;
showToast('联系电话已更新');
resolve(true);
});
};
</script>

Loading…
Cancel
Save