修改 通知详情样式 和 即时通讯

This commit is contained in:
浅念
2025-08-26 09:55:42 +08:00
parent 1bd9d163ec
commit aefd43f79d
7 changed files with 222 additions and 104 deletions
+23 -35
View File
@@ -16,7 +16,7 @@
import appLogo from '@/assets/img/common/logox.png'
const userStore = useUserStore()
const { language } = storeToRefs(userStore)
const { language, accessToken } = storeToRefs(userStore)
function setCompanyFromUrl() {
const userStore = useUserStore()
const params = new URLSearchParams(window.location.search)
@@ -99,37 +99,6 @@
}
} catch (error) {}
}
const showEventNotification = () => {
if (Notification.permission !== 'granted') {
ElMessage({
message: '请先授予通知权限',
type: 'warning'
})
return
}
try {
const notification = new Notification('带事件监听的通知', {
body: '这个通知演示各种事件监听',
icon: 'https://element-plus.org/images/element-plus-logo.svg'
})
notification.onshow = () => {
console.log('通知显示事件触发')
}
notification.onclick = () => {
console.log('通知点击事件触发')
notification.close()
}
notification.onclose = () => {
console.log('通知关闭事件触发')
}
notification.onerror = (error) => {
// console.log(`通知错误事件: ${error.message}`)
}
console.log('已发送带事件监听的通知')
} catch (error) {
// console.log(`发送通知失败: ${error.message}`)
}
}
onBeforeMount(() => {
setThemeTransitionClass(true)
setCompanyFromUrl()
@@ -165,14 +134,13 @@
// 获取用户信息
getUserInfo()
// 初始化 WebSocket
const { accessToken } = useUserStore()
// showEventNotification()
if (accessToken) {
if (accessToken.value) {
import('@/utils/sys/wsClient').then(
({ initWebSocket, resetWebSocketReconnect, getWebSocketStatus }) => {
// 页面刷新时重置重连次数
resetWebSocketReconnect()
initWebSocket(accessToken)
initWebSocket(accessToken.value)
// 添加调试功能,可以在控制台查看WebSocket状态
// @ts-ignore
window.getWebSocketStatus = getWebSocketStatus
@@ -185,6 +153,26 @@
mittBus.on('ws:message', handleWsMessageNotification)
})
// 登录后,accessToken 变更时立即初始化 WebSocket(无需刷新)
watch(accessToken, async (token) => {
if (token) {
const { initWebSocket, getWebSocketStatus } = await import('@/utils/sys/wsClient')
const status = getWebSocketStatus?.()
// 避免重复初始化:已连接或正在连接时不再初始化
if (
!status?.readyState ||
(status.readyState !== WebSocket.OPEN && status.readyState !== WebSocket.CONNECTING)
) {
initWebSocket(token)
}
} else {
// 退出登录时,关闭并重置
const { closeWebSocket, resetWebSocketReconnect } = await import('@/utils/sys/wsClient')
closeWebSocket()
resetWebSocketReconnect()
}
})
// 获取用户信息
const getUserInfo = async () => {
if (userStore.isLogin) {
+1 -1
View File
@@ -23,7 +23,7 @@ export const menuService = {
ElMessage.error('暂无系统菜单权限')
return { menuList: [] }
}
console.log('menuData', menuData)
// console.log('menuData', menuData)
// 处理菜单数据
const menuList = menuData.map((route: any) => menuDataToRouter(route))
// 验证处理后的菜单列表
@@ -91,17 +91,21 @@
</div>
<!-- 全局通知详情弹窗 -->
<Teleport to="body">
<ElDialog v-model="dialogVisible" title="通知详情" align-center :append-to-body="true">
<div class="detail-info-table">
<div class="row">
<div>发送人:{{ noticeDetail.sender_name }}</div>
<ElDialog v-model="dialogVisible" title="通知详情" align-center :append-to-body="true" width="640px">
<div class="detail-dialog">
<div class="section">
<div class="info-grid">
<div class="label">发送人</div>
<div class="value">{{ noticeDetail.sender_name || '-' }}</div>
</div>
<div class="row">
<div>通知标题:{{ noticeDetail.title }}</div>
<div class="info-grid">
<div class="label">通知标题</div>
<div class="value">{{ noticeDetail.title || '-' }}</div>
</div>
<div class="row">
<div>
通知内容:
</div>
<div class="section">
<div class="label" style="margin-bottom: 8px">通知内容</div>
<div class="content-box">
<div class="message-text" v-html="noticeDetail.content"></div>
</div>
</div>
@@ -282,7 +286,42 @@
}
.message-text {
padding: 10px 14px;
line-height: 1.4;
line-height: 1.7;
white-space: pre-line;
word-break: break-word;
}
.detail-dialog {
display: flex;
flex-direction: column;
gap: 16px;
}
.section {
background: var(--el-fill-color-blank);
border: 1px solid var(--el-border-color-light);
border-radius: 8px;
padding: 12px 14px;
}
.info-grid {
display: grid;
grid-template-columns: 96px 1fr;
gap: 6px 12px;
align-items: center;
padding: 6px 0;
}
.label {
color: var(--el-text-color-secondary);
font-size: 13px;
}
.value {
color: var(--el-text-color-primary);
font-size: 14px;
}
.content-box {
background: var(--el-fill-color-lighter);
border: 1px dashed var(--el-border-color);
border-radius: 8px;
padding: 10px 12px;
max-height: 320px;
overflow: auto;
}
</style>
+43 -13
View File
@@ -1,5 +1,6 @@
import mittBus from './mittBus'
import { useUserStore } from '@/store/modules/user'
import { ElNotification } from 'element-plus'
type JsonValue = Record<string, any> | any[]
@@ -13,6 +14,10 @@ let reconnectTimer: any = null
let heartbeatTimer: any = null
let reconnectAttempts = 0
let isInitialConnection = true
let isConnecting = false
let lastInitToken: string | null = null
let lastInitAt = 0
const INIT_DEBOUNCE_MS = 1200
// 生成唯一ID
const generateTraceId = (): string => {
@@ -108,6 +113,7 @@ const handleMessage = (event: MessageEvent) => {
const setupWebSocketHandlers = (ws: WebSocket) => {
ws.onopen = () => {
isInitialConnection = false
isConnecting = false
startHeartbeat()
mittBus.emit('ws:open')
sendPing()
@@ -117,10 +123,19 @@ const setupWebSocketHandlers = (ws: WebSocket) => {
ws.onerror = (err) => {
mittBus.emit('ws:error', err)
isConnecting = false
try {
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
ws.close()
}
} catch {
// 忽略关闭错误
}
}
ws.onclose = () => {
mittBus.emit('ws:close')
isConnecting = false
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
scheduleReconnect()
}
@@ -129,11 +144,10 @@ const setupWebSocketHandlers = (ws: WebSocket) => {
// 安排重连
const scheduleReconnect = () => {
if (!isInitialConnection) {
// 每次安排重连都递增(即使从未成功连接过)
reconnectAttempts += 1
}
if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) {
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
console.warn(`WebSocket重连失败,已达到最大重连次数(${MAX_RECONNECT_ATTEMPTS}),停止重连`)
mittBus.emit('ws:max-reconnect-reached')
return
@@ -144,7 +158,7 @@ const scheduleReconnect = () => {
console.log(`WebSocket重连尝试 ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS},延迟: ${delay}ms`)
reconnectTimer = setTimeout(() => {
const { accessToken } = useUserStore()
if (accessToken) {
if (accessToken && !isConnecting) {
try {
if (socket) {
try {
@@ -156,11 +170,13 @@ const scheduleReconnect = () => {
}
const url = `${WS_BASE}${accessToken}`
isConnecting = true
socket = new WebSocket(url)
setupWebSocketHandlers(socket)
} catch (e) {
mittBus.emit('ws:error', e)
if (reconnectAttempts <= MAX_RECONNECT_ATTEMPTS) {
isConnecting = false
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
scheduleReconnect()
}
}
@@ -172,23 +188,33 @@ const scheduleReconnect = () => {
export const initWebSocket = (token: string) => {
try {
if (!token) return
if (socket) {
try {
socket.close()
} catch {
// 忽略关闭错误
const now = Date.now()
// 简单防抖:在短时间内重复触发则跳过
if (now - lastInitAt < INIT_DEBOUNCE_MS) {
return
}
// 避免并发初始化:已在连接中或已连接且 token 未变化则跳过
if (isConnecting) return
if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
if (lastInitToken === token) {
return
}
// token 变更:需要切换连接
try { socket.close() } catch {}
socket = null
}
cleanup()
reconnectAttempts = 0
isInitialConnection = true
const url = `${WS_BASE}${token}`
isConnecting = true
lastInitToken = token
lastInitAt = now
socket = new WebSocket(url)
setupWebSocketHandlers(socket)
} catch (e) {
mittBus.emit('ws:error', e)
isConnecting = false
scheduleReconnect()
}
}
@@ -204,6 +230,8 @@ export const closeWebSocket = () => {
socket = null
reconnectAttempts = 0
isInitialConnection = true
isConnecting = false
lastInitToken = null
}
// 发送WebSocket消息
@@ -239,5 +267,7 @@ export const getWebSocketStatus = () => ({
maxReconnectAttempts: MAX_RECONNECT_ATTEMPTS,
isReconnecting: reconnectTimer !== null,
isHeartbeating: heartbeatTimer !== null,
isInitialConnection
isInitialConnection,
isConnecting,
lastInitToken
})
+49 -10
View File
@@ -89,17 +89,21 @@
</div>
</template>
</ElDialog>
<ElDialog v-model="dialogVisiblexq" title="通知详情" align-center>
<div class="detail-info-table">
<div class="row">
<div>发送人:{{ noticeDetail.sender_name }}</div>
<ElDialog v-model="dialogVisiblexq" title="通知详情" align-center width="640px">
<div class="detail-dialog">
<div class="section">
<div class="info-grid">
<div class="label">发送人</div>
<div class="value">{{ noticeDetail.sender_name || '-' }}</div>
</div>
<div class="row">
<div>通知标题:{{ noticeDetail.title }}</div>
<div class="info-grid">
<div class="label">通知标题</div>
<div class="value">{{ noticeDetail.title || '-' }}</div>
</div>
<div class="row">
<div>
通知内容:
</div>
<div class="section">
<div class="label" style="margin-bottom: 8px">通知内容</div>
<div class="content-box">
<div class="message-text" v-html="noticeDetail.content"></div>
</div>
</div>
@@ -264,7 +268,42 @@
}
.message-text {
padding: 10px 14px;
line-height: 1.4;
line-height: 1.7;
white-space: pre-line;
word-break: break-word;
}
.detail-dialog {
display: flex;
flex-direction: column;
gap: 16px;
}
.section {
background: var(--el-fill-color-blank);
border: 1px solid var(--el-border-color-light);
border-radius: 8px;
padding: 12px 14px;
}
.info-grid {
display: grid;
grid-template-columns: 96px 1fr;
gap: 6px 12px;
align-items: center;
padding: 6px 0;
}
.label {
color: var(--el-text-color-secondary);
font-size: 13px;
}
.value {
color: var(--el-text-color-primary);
font-size: 14px;
}
.content-box {
background: var(--el-fill-color-lighter);
border: 1px dashed var(--el-border-color);
border-radius: 8px;
padding: 10px 12px;
max-height: 320px;
overflow: auto;
}
</style>
+49 -28
View File
@@ -77,17 +77,21 @@
</template>
</ArtTable>
</div>
<ElDialog v-model="dialogVisible" title="通知详情" align-center>
<div class="detail-info-table">
<div class="row">
<div>发送人:{{ noticeDetail.sender_name }}</div>
<ElDialog v-model="dialogVisible" title="通知详情" align-center width="640px">
<div class="detail-dialog">
<div class="section">
<div class="info-grid">
<div class="label">发送人</div>
<div class="value">{{ noticeDetail.sender_name || '-' }}</div>
</div>
<div class="row">
<div>通知标题:{{ noticeDetail.title }}</div>
<div class="info-grid">
<div class="label">通知标题</div>
<div class="value">{{ noticeDetail.title || '-' }}</div>
</div>
<div class="row">
<div>
通知内容:
</div>
<div class="section">
<div class="label" style="margin-bottom: 8px">通知内容</div>
<div class="content-box">
<div class="message-text" v-html="noticeDetail.content"></div>
</div>
</div>
@@ -203,23 +207,45 @@
align-items: center;
justify-content: center;
}
.detail-info-table {
padding-left: 15px;
.row {
.message-text {
padding: 10px 14px;
line-height: 1.7;
white-space: pre-line;
word-break: break-word;
}
.detail-dialog {
display: flex;
justify-content: flex-start;
margin-bottom: 6px;
> div {
flex: 1;
min-width: 0;
margin-right: 16px;
overflow: hidden;
flex-direction: column;
gap: 16px;
}
.section {
background: var(--el-fill-color-blank);
border: 1px solid var(--el-border-color-light);
border-radius: 8px;
padding: 12px 14px;
}
.info-grid {
display: grid;
grid-template-columns: 96px 1fr;
gap: 6px 12px;
align-items: center;
padding: 6px 0;
}
.label {
color: var(--el-text-color-secondary);
font-size: 13px;
}
.value {
color: var(--el-text-color-primary);
font-size: 14px;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.content-box {
background: var(--el-fill-color-lighter);
border: 1px dashed var(--el-border-color);
border-radius: 8px;
padding: 10px 12px;
max-height: 320px;
overflow: auto;
}
.customer-page {
background: var(--art-main-bg-color);
@@ -247,9 +273,4 @@
display: flex;
gap: 12px;
}
.message-text {
padding: 10px 14px;
line-height: 1.4;
white-space: pre-line;
}
</style>
+1
View File
@@ -173,6 +173,7 @@
if (valid) {
try {
const res = await CrmService.customerAdd(form.value)
console.log(res)
ElMessage.success(res?.message || '提交成功!')
router.push('/crmpc/customer')
} catch (e: any) {