10 changed files with 719 additions and 200 deletions
@ -0,0 +1,312 @@ |
|||
<template> |
|||
<div class="notice-message-page app_content"> |
|||
<!-- 保留一份反馈记录 --> |
|||
<!-- 顶部导航 --> |
|||
<!-- <van-nav-bar |
|||
title="预告留言" |
|||
left-arrow |
|||
@click-left="onBack" |
|||
/> --> |
|||
|
|||
<!-- 消息列表区域 --> |
|||
<div class="message-area" ref="messageAreaRef"> |
|||
<div v-if="loading" class="loading-wrap"> |
|||
<van-loading size="18" /> 加载中... |
|||
</div> |
|||
<template v-else> |
|||
<van-empty v-if="!messageList.length" description="暂无留言" image-size="100" /> |
|||
<div |
|||
v-for="(msg, idx) in messageList" |
|||
:key="msg.id || idx" |
|||
class="message-item" |
|||
:class="{ 'message-right': msg.msgFrom === 0, 'message-left': msg.msgFrom === 1 }" |
|||
> |
|||
<!-- 左侧(企业消息) --> |
|||
<div v-if="msg.msgFrom === 1" class="msg-avatar left-avatar">企</div> |
|||
<div class="msg-bubble" :class="{ 'bubble-left': msg.msgFrom === 1, 'bubble-right': msg.msgFrom === 0 }"> |
|||
<div class="msg-sender">{{ msg.msgFrom === 0 ? (msg.bureauDeptName || useCommon.bureauUnitName || '执法单位') : msg.userName }}</div> |
|||
<div class="msg-content">{{ msg.message }}</div> |
|||
<div class="msg-time">{{ formatTime(msg.createTime) }}</div> |
|||
</div> |
|||
<!-- 右侧(执法单位消息) --> |
|||
</div> |
|||
</template> |
|||
</div> |
|||
|
|||
<!-- 底部输入区域(非只读时显示) --> |
|||
<div v-if="!isReadonly" class="input-area"> |
|||
<van-field |
|||
v-model="inputMessage" |
|||
type="textarea" |
|||
placeholder="输入留言内容..." |
|||
rows="1" |
|||
autosize |
|||
class="input-field" |
|||
/> |
|||
<van-button |
|||
type="primary" |
|||
size="small" |
|||
class="send-btn" |
|||
:disabled="!inputMessage.trim()" |
|||
:loading="sending" |
|||
@click="onSend" |
|||
> |
|||
留言 |
|||
</van-button> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup name="cpNoticeMessage"> |
|||
import { useRouter, useRoute } from 'vue-router'; |
|||
import { showToast } from 'vant'; |
|||
import { getNoticeMessages, addNoticeMessage } from '@/api/enforcement/notice'; |
|||
|
|||
const router = useRouter(); |
|||
const route = useRoute(); |
|||
const { useCommon } = $globalStore; |
|||
|
|||
const noticeId = computed(() => route.query.id || ''); |
|||
const isReadonly = computed(() => route.query.readonly === '1'); |
|||
|
|||
const messageList = ref([]); |
|||
const loading = ref(false); |
|||
const inputMessage = ref(''); |
|||
const sending = ref(false); |
|||
const messageAreaRef = ref(null); |
|||
let pollTimer = null; |
|||
|
|||
onMounted(() => { |
|||
loadMessages(); |
|||
startPolling(); |
|||
}); |
|||
|
|||
onUnmounted(() => { |
|||
stopPolling(); |
|||
}); |
|||
|
|||
const startPolling = () => { |
|||
stopPolling(); |
|||
pollTimer = setInterval(() => { |
|||
pollMessages(); |
|||
}, 16000); |
|||
}; |
|||
|
|||
const stopPolling = () => { |
|||
if (pollTimer) { |
|||
clearInterval(pollTimer); |
|||
pollTimer = null; |
|||
} |
|||
}; |
|||
|
|||
// 轮询加载新消息(静默,不显示loading) |
|||
const pollMessages = () => { |
|||
if (!noticeId.value) return; |
|||
getNoticeMessages(noticeId.value, (res) => { |
|||
if (res.code === 200 && Array.isArray(res.data)) { |
|||
const oldCount = messageList.value.length; |
|||
messageList.value = res.data; |
|||
if (res.data.length > oldCount) { |
|||
scrollToBottom(); |
|||
} |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const loadMessages = () => { |
|||
if (!noticeId.value) return; |
|||
loading.value = true; |
|||
getNoticeMessages(noticeId.value, (res) => { |
|||
loading.value = false; |
|||
if (res.code === 200 && Array.isArray(res.data)) { |
|||
messageList.value = res.data; |
|||
scrollToBottom(); |
|||
} |
|||
}, () => { |
|||
loading.value = false; |
|||
}); |
|||
}; |
|||
|
|||
const onSend = () => { |
|||
const text = inputMessage.value.trim(); |
|||
if (!text) return; |
|||
sending.value = true; |
|||
addNoticeMessage({ |
|||
noticeId: noticeId.value, |
|||
userId: String(useCommon.userId || ''), |
|||
userName: useCommon.nickName || useCommon.userName || '', |
|||
bureauDeptName: useCommon.bureauUnitName || '', |
|||
msgFrom: 0, // 执法单位=0 |
|||
message: text, |
|||
}, (res) => { |
|||
sending.value = false; |
|||
if (res.code === 200) { |
|||
inputMessage.value = ''; |
|||
loadMessages(); |
|||
} else { |
|||
showToast(res.msg || '发送失败'); |
|||
} |
|||
}, () => { |
|||
sending.value = false; |
|||
showToast('发送失败,请重试'); |
|||
}); |
|||
}; |
|||
|
|||
const scrollToBottom = () => { |
|||
nextTick(() => { |
|||
if (messageAreaRef.value) { |
|||
messageAreaRef.value.scrollTop = messageAreaRef.value.scrollHeight; |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const formatTime = (dateStr) => { |
|||
if (!dateStr) return ''; |
|||
return String(dateStr).substring(0, 16).replace('T', ' '); |
|||
}; |
|||
|
|||
const onBack = () => { |
|||
router.back(); |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.notice-message-page { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100%; |
|||
background: #fbfdff; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
:deep(.van-nav-bar) { |
|||
background: #347bc3; |
|||
flex-shrink: 0; |
|||
.van-nav-bar__title { color: #fff; font-size: 18px; font-weight: 600; } |
|||
.van-icon { color: #fff; } |
|||
} |
|||
|
|||
.message-area { |
|||
flex: 1; |
|||
overflow-y: auto; |
|||
padding: 16px 12px; |
|||
} |
|||
|
|||
.loading-wrap { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 30px 0; |
|||
color: #999; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.message-item { |
|||
display: flex; |
|||
align-items: flex-start; |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.message-left { |
|||
flex-direction: row; |
|||
} |
|||
|
|||
.message-right { |
|||
flex-direction: row-reverse; |
|||
} |
|||
|
|||
.msg-avatar { |
|||
width: 36px; |
|||
height: 36px; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-size: 14px; |
|||
font-weight: 600; |
|||
color: #fff; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.left-avatar { |
|||
background: linear-gradient(135deg, #f5a623, #f7c948); |
|||
margin-right: 10px; |
|||
} |
|||
|
|||
.right-avatar { |
|||
background: linear-gradient(135deg, #1890ff, #0b63d1); |
|||
margin-left: 10px; |
|||
} |
|||
|
|||
.msg-bubble { |
|||
max-width: 70%; |
|||
padding: 10px 14px; |
|||
border-radius: 12px; |
|||
position: relative; |
|||
} |
|||
|
|||
.bubble-left { |
|||
background: #fff; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); |
|||
border-top-left-radius: 4px; |
|||
} |
|||
|
|||
.bubble-right { |
|||
background: linear-gradient(135deg, #1890ff, #0b63d1); |
|||
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2); |
|||
border-top-right-radius: 4px; |
|||
.msg-sender { color: rgba(255, 255, 255, 0.8); } |
|||
.msg-content { color: #fff; } |
|||
.msg-time { color: rgba(255, 255, 255, 0.6); } |
|||
} |
|||
|
|||
.msg-sender { |
|||
font-size: 12px; |
|||
color: #999; |
|||
margin-bottom: 4px; |
|||
} |
|||
|
|||
.msg-content { |
|||
font-size: 15px; |
|||
color: #333; |
|||
line-height: 1.5; |
|||
word-break: break-all; |
|||
} |
|||
|
|||
.msg-time { |
|||
font-size: 11px; |
|||
color: #bbb; |
|||
margin-top: 4px; |
|||
text-align: right; |
|||
} |
|||
|
|||
.input-area { |
|||
display: flex; |
|||
align-items: flex-end; |
|||
gap: 10px; |
|||
padding: 10px 12px; |
|||
background: #fff; |
|||
border-top: 1px solid #e4edf7; |
|||
flex-shrink: 0; |
|||
|
|||
.input-field { |
|||
flex: 1; |
|||
background: #fbfdff; |
|||
border-radius: 20px; |
|||
padding: 8px 14px; |
|||
|
|||
:deep(.van-field__control) { |
|||
min-height: 24px; |
|||
} |
|||
} |
|||
|
|||
.send-btn { |
|||
height: 36px; |
|||
border-radius: 18px; |
|||
padding: 0 18px; |
|||
font-size: 14px; |
|||
background: linear-gradient(135deg, #1890ff, #0b63d1); |
|||
border: none; |
|||
} |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue