统一排行榜对齐星雀UI,页面资源后台配置实时刷新

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-06-27 01:26:11 +08:00
parent 1ab2e2080a
commit 21112173f2
292 changed files with 64010 additions and 81 deletions

View File

@@ -0,0 +1,366 @@
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' });
}
}
}
});

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,49 @@
<view
class="global-notification {{show ? 'show' : ''}} {{positionClass}} {{swipingClass}}"
bindtap="onTap"
bindtouchstart="onTouchStart"
bindtouchmove="onTouchMove"
bindtouchend="onTouchEnd"
style="{{swipeStyle}}"
data-notification-id="{{notificationData && notificationData.id ? notificationData.id : ''}}">
<!-- 滑动关闭指示器 -->
<view class="notification-swipe-indicator {{isSwiping ? 'active' : ''}}">
<view class="swipe-line"></view>
</view>
<!-- 通知内容 -->
<view class="notification-content">
<!-- 左侧头像 -->
<image class="notification-avatar" src="{{avatar}}" mode="aspectFill"></image>
<!-- 中间内容区域 -->
<view class="notification-body">
<view class="notification-title">{{title}}</view>
<view class="notification-message">{{message}}</view>
<view class="notification-time" wx:if="{{showTime}}">
{{formatTime(notificationData ? notificationData.timestamp : '')}}
</view>
</view>
<!-- 右侧操作区域 -->
<view class="notification-actions">
<!-- 关闭按钮 -->
<view class="notification-close" catchtap="onClose">
<text class="close-icon">×</text>
</view>
<!-- 免打扰按钮 -->
<view class="notification-mute {{isMuted ? 'muted' : ''}}" catchtap="onMute" wx:if="{{showMuteButton}}">
<text class="mute-icon">
{{isMuted ? '🔕' : '🔔'}}
</text>
</view>
</view>
</view>
<!-- 进度条 -->
<view class="notification-progress" wx:if="{{showProgress}}">
<view class="progress-bar {{progress < 30 ? 'warning' : ''}}"></view>
</view>
</view>

View File

@@ -0,0 +1,215 @@
.global-notification {
position: fixed;
left: 20rpx;
right: 20rpx;
top: 0rpx;
z-index: 99999;
opacity: 0;
transform: translateY(-120%);
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
pointer-events: none;
overflow: hidden;
border-radius: 24rpx;
background: linear-gradient(135deg,
rgba(41, 47, 61, 0.98) 0%,
rgba(31, 36, 48, 0.98) 100%);
backdrop-filter: blur(30rpx) saturate(180%);
-webkit-backdrop-filter: blur(30rpx) saturate(180%);
border: 1rpx solid rgba(255, 255, 255, 0.08);
box-shadow:
0 20rpx 60rpx rgba(0, 0, 0, 0.4),
0 0 0 1rpx rgba(255, 255, 255, 0.03),
inset 0 1rpx 0 rgba(255, 255, 255, 0.1);
min-height: 140rpx;
touch-action: pan-y;
}
.global-notification.show {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.notification-swipe-indicator {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4rpx;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
.swipe-line {
width: 40rpx;
height: 4rpx;
background: rgba(255, 255, 255, 0.3);
border-radius: 2rpx;
transition: all 0.3s;
}
.notification-swipe-indicator.active .swipe-line {
background: rgba(255, 255, 255, 0.6);
transform: scaleX(1.2);
}
.notification-content {
display: flex;
align-items: center;
padding: 32rpx;
min-height: 140rpx;
position: relative;
}
.notification-avatar {
width: 88rpx;
height: 88rpx;
border-radius: 44rpx;
margin-right: 24rpx;
flex-shrink: 0;
border: 3rpx solid rgba(212, 175, 55, 0.6);
box-shadow:
0 8rpx 24rpx rgba(212, 175, 55, 0.2),
inset 0 1rpx 0 rgba(255, 255, 255, 0.3);
background-color: rgba(0, 0, 0, 0.2);
}
.notification-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.notification-title {
font-size: 34rpx;
font-weight: 600;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background: linear-gradient(135deg, #D4AF37 0%, #FFD700 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
.notification-message {
font-size: 28rpx;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
color: rgba(255, 255, 255, 0.95);
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.5);
}
.notification-time {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.5);
margin-top: 4rpx;
font-weight: 300;
}
.notification-actions {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-left: 20rpx;
gap: 16rpx;
}
.notification-close,
.notification-mute {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 28rpx;
background: rgba(255, 255, 255, 0.08);
border: 1rpx solid rgba(255, 255, 255, 0.1);
transition: all 0.3s;
flex-shrink: 0;
box-shadow:
0 4rpx 12rpx rgba(0, 0, 0, 0.2),
inset 0 1rpx 0 rgba(255, 255, 255, 0.05);
}
.notification-close:active,
.notification-mute:active {
background: rgba(255, 255, 255, 0.15);
transform: scale(0.92);
}
.close-icon {
font-size: 32rpx;
font-weight: 300;
color: rgba(255, 255, 255, 0.9);
line-height: 1;
}
.mute-icon {
font-size: 28rpx;
line-height: 0;
color: rgba(255, 255, 255, 0.9);
}
.notification-mute.muted .mute-icon {
color: #D4AF37;
}
.notification-progress {
height: 6rpx;
background: rgba(255, 255, 255, 0.05);
overflow: hidden;
border-radius: 0 0 24rpx 24rpx;
}
.progress-bar {
height: 100%;
width: 100%;
background: linear-gradient(90deg,
#D4AF37 0%,
#FFD700 50%,
#D4AF37 100%);
background-size: 200% 100%;
transition: width 0.1s linear;
box-shadow: 0 0 20rpx rgba(212, 175, 55, 0.4);
animation: progressShine 2s linear infinite;
}
@keyframes progressShine {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.progress-bar.warning {
background: linear-gradient(90deg,
#ff3b30 0%,
#ff6b6b 50%,
#ff3b30 100%);
animation: progressShine 1s linear infinite;
}
.global-notification.swiping {
opacity: 0.7;
transform: translateY(calc(var(--swipe-distance, 0) * -1));
}
.global-notification.swiping-up {
opacity: 0.3;
transform: translateY(calc(var(--swipe-distance, 0) * -1));
}
.global-notification:active:not(.swiping) {
transform: scale(0.995) translateY(0);
transition: transform 0.1s;
}