Component({ properties: { position: { type: String, value: 'top' }, duration: { type: Number, value: 3000 }, showProgress: { type: Boolean, value: true }, showMuteButton: { type: Boolean, value: true }, showTime: { type: Boolean, value: true }, swipeToClose: { type: Boolean, value: true }, swipeThreshold: { type: Number, value: 50 } }, data: { show: false, title: '', message: '', avatar: '', notificationData: null, progress: 100, progressInterval: null, positionClass: 'top', // 滑动相关 touchStartY: 0, touchStartX: 0, currentY: 0, currentX: 0, isSwiping: false, swipingClass: '', swipeStyle: '', swipeDistance: 0, // 静音状态 isMuted: false, // 自动隐藏计时器 autoHideTimeout: null }, lifetimes: { attached() { this.loadMuteSetting(); this.setData({ positionClass: this.properties.position }); this.setupDirectEventListeners(); }, detached() { this.clearTimers(); } }, methods: { setupDirectEventListeners() { const app = getApp(); if (app) { const self = this; const globalNotification = { show: (data) => { if (self && self.showNotification) { self.showNotification(data); } }, hide: () => { if (self && self.hideNotification) { self.hideNotification(); } }, isMuted: () => { return self.data.isMuted; } }; app.globalData.globalNotification = globalNotification; } }, loadMuteSetting() { try { const isMuted = wx.getStorageSync('notificationMuted') || false; this.setData({ isMuted }); } catch (error) { console.warn('加载静音设置失败:', error); } }, saveMuteSetting(isMuted) { try { wx.setStorageSync('notificationMuted', isMuted); } catch (error) { console.warn('保存静音设置失败:', error); } }, showNotification(data) { if (!data) return; if (this.data.isMuted) return; this.clearTimers(); const app = getApp(); let avatar = data.avatar; if (!avatar && app && app.globalData) { avatar = app.globalData.ossImageUrl + app.globalData.morentouxiang; } this.setData({ show: true, title: data.senderName || '新消息', message: data.content || '[消息内容]', avatar: avatar, notificationData: data, progress: 100, swipingClass: '', swipeStyle: '' }); if (this.properties.showProgress) { this.startProgressBar(this.properties.duration); } this.data.autoHideTimeout = setTimeout(() => { this.hideNotification(); }, this.properties.duration); }, hideNotification() { this.clearTimers(); this.setData({ show: false, progress: 100, swipingClass: '', swipeStyle: '' }); this.triggerEvent('hide'); }, startProgressBar(duration) { const interval = 100; const steps = duration / interval; const stepValue = 100 / steps; let currentProgress = 100; this.data.progressInterval = setInterval(() => { currentProgress -= stepValue; if (currentProgress <= 0) { currentProgress = 0; this.setData({ progress: currentProgress }); clearInterval(this.data.progressInterval); } else { this.setData({ progress: currentProgress }); } }, interval); }, clearTimers() { if (this.data.autoHideTimeout) { clearTimeout(this.data.autoHideTimeout); this.data.autoHideTimeout = null; } if (this.data.progressInterval) { clearInterval(this.data.progressInterval); this.data.progressInterval = null; } }, onTouchStart(e) { if (!this.properties.swipeToClose) return; const touch = e.touches[0]; this.setData({ touchStartY: touch.clientY, touchStartX: touch.clientX, currentY: touch.clientY, currentX: touch.clientX, isSwiping: false }); this.clearTimers(); }, onTouchMove(e) { if (!this.properties.swipeToClose) return; const touch = e.touches[0]; const deltaY = touch.clientY - this.data.touchStartY; const deltaX = touch.clientX - this.data.touchStartX; const absDeltaY = Math.abs(deltaY); const absDeltaX = Math.abs(deltaX); if (absDeltaY > 10 && absDeltaY > absDeltaX) { this.setData({ isSwiping: true, currentY: touch.clientY, swipingClass: deltaY < 0 ? 'swiping-up' : 'swiping', swipeDistance: Math.abs(deltaY), swipeStyle: `--swipe-distance: ${Math.abs(deltaY)}rpx;` }); e.stopPropagation(); } }, onTouchEnd(e) { if (!this.properties.swipeToClose || !this.data.isSwiping) { this.restartAutoHide(); return; } const deltaY = this.data.currentY - this.data.touchStartY; const absDeltaY = Math.abs(deltaY); if (absDeltaY > this.properties.swipeThreshold) { this.hideNotification(); } else { this.setData({ swipingClass: '', swipeStyle: 'transition: transform 0.3s ease; transform: translateY(0);' }); setTimeout(() => { this.setData({ swipingClass: '', swipeStyle: '' }); }, 300); this.restartAutoHide(); } this.setData({ isSwiping: false }); }, restartAutoHide() { const remainingTime = (this.data.progress / 100) * this.properties.duration; if (remainingTime > 0) { this.data.autoHideTimeout = setTimeout(() => { this.hideNotification(); }, remainingTime); } }, onTap() { const { notificationData } = this.data; if (!notificationData) return; this.triggerEvent('tap', notificationData); this.hideNotification(); this.navigateToChatPage(notificationData); }, onClose(e) { e.stopPropagation(); this.triggerEvent('close'); this.hideNotification(); }, onMute(e) { e.stopPropagation(); const newMutedState = !this.data.isMuted; this.setData({ isMuted: newMutedState }); this.saveMuteSetting(newMutedState); const app = getApp(); if (app && app.toggleDoNotDisturb) { app.toggleDoNotDisturb(newMutedState); } this.triggerEvent('mute', { muted: newMutedState }); wx.showToast({ title: newMutedState ? '已开启免打扰' : '已关闭免打扰', icon: 'success', duration: 1500 }); if (newMutedState) { this.hideNotification(); } }, formatTime(timestamp) { if (!timestamp) return ''; const date = new Date(timestamp); const now = new Date(); const diff = now - date; if (diff < 60 * 1000) return '刚刚'; if (diff < 60 * 60 * 1000) return Math.floor(diff / (60 * 1000)) + '分钟前'; if (date.toDateString() === now.toDateString()) { return date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0'); } const yesterday = new Date(now); yesterday.setDate(yesterday.getDate() - 1); if (date.toDateString() === yesterday.toDateString()) { return '昨天 ' + date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0'); } return (date.getMonth() + 1) + '月' + date.getDate() + '日'; }, navigateToChatPage(data) { if (!data || !data.message) { console.warn('通知数据无效,无法跳转'); return; } const msg = data.message; let path = ''; let queryParam = null; // 根据 notificationType 或消息字段判断 if (data.notificationType === 'cs' || msg.teamId) { // 客服消息 const teamId = data.teamId || msg.teamId; path = '/pages/kefuliaotian/kefuliaotian'; queryParam = { teamId: teamId }; } else if (data.notificationType === 'group' || msg.groupId) { // 群聊消息 const groupId = data.groupId || msg.groupId; const orderId = data.orderId || msg.orderId || ''; const groupName = data.groupName || '订单群聊'; const groupAvatar = data.groupAvatar || ''; const isCross = data.isCross || msg.isCross || 0; path = '/pages/qunliaotian/qunliaotian'; queryParam = { groupId, orderId, groupName, groupAvatar, isCross }; } else { // 默认私聊 const toUserId = data.senderId || msg.senderId; const toName = data.senderName || '用户'; const toAvatar = data.avatar || ''; path = '/pages/liaotian/liaotian'; queryParam = { toUserId, toName, toAvatar }; } if (path && queryParam) { const encoded = encodeURIComponent(JSON.stringify(queryParam)); wx.navigateTo({ url: `${path}?data=${encoded}`, fail: (err) => { console.error('通知跳转失败:', err); wx.switchTab({ url: '/pages/xiaoxi/xiaoxi' }); } }); } else { wx.switchTab({ url: '/pages/xiaoxi/xiaoxi' }); } } } });