统一排行榜对齐星雀UI,页面资源后台配置实时刷新
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
73
miniprogram/components/chenghao-tag/chenghao-tag.js
Normal file
73
miniprogram/components/chenghao-tag/chenghao-tag.js
Normal file
@@ -0,0 +1,73 @@
|
||||
// components/chenghao-tag/chenghao-tag.js
|
||||
const app = getApp();
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
mingcheng: { type: String, value: '' },
|
||||
texiaoJson: { type: Object, value: {} }
|
||||
},
|
||||
data: {
|
||||
// 默认值:宽152,高52,六边形,无背景图,无动画,白色字
|
||||
width: 152,
|
||||
height: 52,
|
||||
shapeClass: 'liubianxing', // 保证至少不是矩形
|
||||
animationClass: '',
|
||||
bgStyle: '',
|
||||
textColor: '#FFFFFF',
|
||||
textSize: 22,
|
||||
imageUrl: ''
|
||||
},
|
||||
lifetimes: {
|
||||
attached() {
|
||||
const cfg = this.properties.texiaoJson || {};
|
||||
|
||||
// 1. 尺寸(稍大一些,一行能放3~4个)
|
||||
const width = cfg.width || 152;
|
||||
const height = cfg.height || 52;
|
||||
|
||||
// 2. 背景处理:优先背景图,否则用渐变/纯色
|
||||
let bgStyle = '';
|
||||
let imageUrl = '';
|
||||
if (cfg.image_url) {
|
||||
const ossImageUrl = app.globalData.ossImageUrl || '';
|
||||
imageUrl = cfg.image_url.startsWith('http')
|
||||
? cfg.image_url
|
||||
: ossImageUrl + cfg.image_url;
|
||||
} else if (cfg.bg_gradient) {
|
||||
bgStyle = `background: ${cfg.bg_gradient};`;
|
||||
} else if (cfg.bg_color) {
|
||||
bgStyle = `background: ${cfg.bg_color};`;
|
||||
} else {
|
||||
// 默认金橙渐变
|
||||
bgStyle = `background: linear-gradient(135deg, #FFD700, #FF8C00);`;
|
||||
}
|
||||
|
||||
// 3. 形状(后端传入 shape 字段,默认 liubianxing)
|
||||
const shapeClass = cfg.shape || 'liubianxing';
|
||||
|
||||
// 4. 动画
|
||||
const animationClass = cfg.animation || '';
|
||||
|
||||
// 5. 文字
|
||||
const textColor = cfg.text_color || '#FFFFFF';
|
||||
const textSize = cfg.text_size || 22;
|
||||
|
||||
this.setData({
|
||||
width, height,
|
||||
bgStyle, imageUrl,
|
||||
shapeClass, animationClass,
|
||||
textColor, textSize
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 背景图加载失败时回退为纯色背景
|
||||
onImageError() {
|
||||
this.setData({ imageUrl: '' });
|
||||
// 如果 bgStyle 为空,给个默认背景
|
||||
if (!this.data.bgStyle) {
|
||||
this.setData({ bgStyle: 'background: linear-gradient(135deg, #FFD700, #FF8C00);' });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
4
miniprogram/components/chenghao-tag/chenghao-tag.json
Normal file
4
miniprogram/components/chenghao-tag/chenghao-tag.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
20
miniprogram/components/chenghao-tag/chenghao-tag.wxml
Normal file
20
miniprogram/components/chenghao-tag/chenghao-tag.wxml
Normal file
@@ -0,0 +1,20 @@
|
||||
<!-- components/chenghao-tag/chenghao-tag.wxml -->
|
||||
<view
|
||||
class="tag-root {{shapeClass}} {{animationClass}}"
|
||||
style="width: {{width}}rpx; height: {{height}}rpx; {{bgStyle}}"
|
||||
>
|
||||
<!-- 背景图(如果有) -->
|
||||
<image
|
||||
wx:if="{{imageUrl}}"
|
||||
class="tag-bg-image"
|
||||
src="{{imageUrl}}"
|
||||
mode="aspectFill"
|
||||
binderror="onImageError"
|
||||
></image>
|
||||
|
||||
<!-- 文字层 -->
|
||||
<text
|
||||
class="tag-text"
|
||||
style="color: {{textColor}}; font-size: {{textSize}}rpx;"
|
||||
>{{mingcheng}}</text>
|
||||
</view>
|
||||
90
miniprogram/components/chenghao-tag/chenghao-tag.wxss
Normal file
90
miniprogram/components/chenghao-tag/chenghao-tag.wxss
Normal file
@@ -0,0 +1,90 @@
|
||||
/* components/chenghao-tag/chenghao-tag.wxss */
|
||||
|
||||
/* ========== 根容器 ========== */
|
||||
.tag-root {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
border-radius: 4rpx;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
margin: 4rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ========== 背景图 ========== */
|
||||
.tag-bg-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
/* ========== 文字层 ========== */
|
||||
.tag-text {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
/* ========== 多边形形状 ========== */
|
||||
.tag-root.liubianxing {
|
||||
clip-path: polygon(12% 0%, 88% 0%, 100% 50%, 88% 100%, 12% 100%, 0% 50%);
|
||||
}
|
||||
.tag-root.lingxing {
|
||||
clip-path: polygon(50% 0%, 85% 12%, 100% 50%, 85% 88%, 50% 100%, 15% 88%, 0% 50%, 15% 12%);
|
||||
}
|
||||
.tag-root.wujiaoxing {
|
||||
clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
|
||||
}
|
||||
.tag-root.dunpai {
|
||||
clip-path: polygon(18% 0%, 82% 0%, 100% 18%, 95% 50%, 100% 82%, 82% 100%, 18% 100%, 0% 82%, 5% 50%, 0% 18%);
|
||||
}
|
||||
.tag-root.jiantou {
|
||||
clip-path: polygon(0% 0%, 85% 0%, 100% 50%, 85% 100%, 0% 100%, 12% 50%);
|
||||
}
|
||||
|
||||
/* ========== 动画 ========== */
|
||||
/* 流光扫描 */
|
||||
.tag-root.shine::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent);
|
||||
transform: skewX(-20deg);
|
||||
animation: shine-anim 2s infinite;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
@keyframes shine-anim {
|
||||
0% { left: -100%; }
|
||||
100% { left: 200%; }
|
||||
}
|
||||
|
||||
/* 呼吸光晕 */
|
||||
.tag-root.pulse {
|
||||
animation: pulse-anim 1.5s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse-anim {
|
||||
0%, 100% { filter: brightness(1); transform: scale(1); }
|
||||
50% { filter: brightness(1.2); transform: scale(1.04); }
|
||||
}
|
||||
|
||||
/* 外发光闪烁 */
|
||||
.tag-root.glow {
|
||||
animation: glow-anim 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes glow-anim {
|
||||
0%, 100% { box-shadow: 0 0 8rpx rgba(255, 215, 0, 0.4); }
|
||||
50% { box-shadow: 0 0 20rpx rgba(255, 215, 0, 0.8), 0 0 40rpx rgba(255, 215, 0, 0.4); }
|
||||
}
|
||||
@@ -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' });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
102
miniprogram/components/navigation-bar/navigation-bar.js
Normal file
102
miniprogram/components/navigation-bar/navigation-bar.js
Normal file
@@ -0,0 +1,102 @@
|
||||
Component({
|
||||
options: {
|
||||
multipleSlots: true // 在组件定义时的选项中启用多slot支持
|
||||
},
|
||||
/**
|
||||
* 组件的属性列表
|
||||
*/
|
||||
properties: {
|
||||
extClass: {
|
||||
type: String,
|
||||
value: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
value: ''
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
value: ''
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
value: ''
|
||||
},
|
||||
back: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
homeButton: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
animated: {
|
||||
// 显示隐藏的时候opacity动画效果
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
show: {
|
||||
// 显示隐藏导航,隐藏的时候navigation-bar的高度占位还在
|
||||
type: Boolean,
|
||||
value: true,
|
||||
observer: '_showChange'
|
||||
},
|
||||
// back为true的时候,返回的页面深度
|
||||
delta: {
|
||||
type: Number,
|
||||
value: 1
|
||||
},
|
||||
},
|
||||
/**
|
||||
* 组件的初始数据
|
||||
*/
|
||||
data: {
|
||||
displayStyle: ''
|
||||
},
|
||||
lifetimes: {
|
||||
attached() {
|
||||
const rect = wx.getMenuButtonBoundingClientRect()
|
||||
const platform = (wx.getDeviceInfo() || wx.getSystemInfoSync()).platform
|
||||
const isAndroid = platform === 'android'
|
||||
const isDevtools = platform === 'devtools'
|
||||
const { windowWidth, safeArea: { top = 0, bottom = 0 } = {} } = wx.getWindowInfo() || wx.getSystemInfoSync()
|
||||
this.setData({
|
||||
ios: !isAndroid,
|
||||
innerPaddingRight: `padding-right: ${windowWidth - rect.left}px`,
|
||||
leftWidth: `width: ${windowWidth - rect.left}px`,
|
||||
safeAreaTop: isDevtools || isAndroid ? `height: calc(var(--height) + ${top}px); padding-top: ${top}px` : ``
|
||||
})
|
||||
},
|
||||
},
|
||||
/**
|
||||
* 组件的方法列表
|
||||
*/
|
||||
methods: {
|
||||
_showChange(show) {
|
||||
const animated = this.data.animated
|
||||
let displayStyle = ''
|
||||
if (animated) {
|
||||
displayStyle = `opacity: ${show ? '1' : '0'
|
||||
};transition:opacity 0.5s;`
|
||||
} else {
|
||||
displayStyle = `display: ${show ? '' : 'none'}`
|
||||
}
|
||||
this.setData({
|
||||
displayStyle
|
||||
})
|
||||
},
|
||||
back() {
|
||||
const data = this.data
|
||||
if (data.delta) {
|
||||
wx.navigateBack({
|
||||
delta: data.delta
|
||||
})
|
||||
}
|
||||
this.triggerEvent('back', { delta: data.delta }, {})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"component": true,
|
||||
"styleIsolation": "apply-shared",
|
||||
"usingComponents": {}
|
||||
}
|
||||
64
miniprogram/components/navigation-bar/navigation-bar.wxml
Normal file
64
miniprogram/components/navigation-bar/navigation-bar.wxml
Normal file
@@ -0,0 +1,64 @@
|
||||
<view class="weui-navigation-bar {{extClass}}">
|
||||
<view class="weui-navigation-bar__inner {{ios ? 'ios' : 'android'}}" style="color: {{color}}; background: {{background}}; {{displayStyle}}; {{innerPaddingRight}}; {{safeAreaTop}}">
|
||||
|
||||
<!-- 左侧按钮 -->
|
||||
<view class='weui-navigation-bar__left' style="{{leftWidth}}">
|
||||
<block wx:if="{{back || homeButton}}">
|
||||
<!-- 返回上一页 -->
|
||||
<block wx:if="{{back}}">
|
||||
<view class="weui-navigation-bar__buttons weui-navigation-bar__buttons_goback">
|
||||
<view
|
||||
bindtap="back"
|
||||
class="weui-navigation-bar__btn_goback_wrapper"
|
||||
hover-class="weui-active"
|
||||
hover-stay-time="100"
|
||||
aria-role="button"
|
||||
aria-label="返回"
|
||||
>
|
||||
<view class="weui-navigation-bar__button weui-navigation-bar__btn_goback"></view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<!-- 返回首页 -->
|
||||
<block wx:if="{{homeButton}}">
|
||||
<view class="weui-navigation-bar__buttons weui-navigation-bar__buttons_home">
|
||||
<view
|
||||
bindtap="home"
|
||||
class="weui-navigation-bar__btn_home_wrapper"
|
||||
hover-class="weui-active"
|
||||
aria-role="button"
|
||||
aria-label="首页"
|
||||
>
|
||||
<view class="weui-navigation-bar__button weui-navigation-bar__btn_home"></view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<slot name="left"></slot>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 标题 -->
|
||||
<view class='weui-navigation-bar__center'>
|
||||
<view wx:if="{{loading}}" class="weui-navigation-bar__loading" aria-role="alert">
|
||||
<view
|
||||
class="weui-loading"
|
||||
aria-role="img"
|
||||
aria-label="加载中"
|
||||
></view>
|
||||
</view>
|
||||
<block wx:if="{{title}}">
|
||||
<text>{{title}}</text>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<slot name="center"></slot>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 右侧留空 -->
|
||||
<view class='weui-navigation-bar__right'>
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
96
miniprogram/components/navigation-bar/navigation-bar.wxss
Normal file
96
miniprogram/components/navigation-bar/navigation-bar.wxss
Normal file
@@ -0,0 +1,96 @@
|
||||
.weui-navigation-bar {
|
||||
--weui-FG-0:rgba(0,0,0,.9);
|
||||
--height: 44px;
|
||||
--left: 16px;
|
||||
}
|
||||
.weui-navigation-bar .android {
|
||||
--height: 48px;
|
||||
}
|
||||
|
||||
.weui-navigation-bar {
|
||||
overflow: hidden;
|
||||
color: var(--weui-FG-0);
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.weui-navigation-bar__inner {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: calc(var(--height) + env(safe-area-inset-top));
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: env(safe-area-inset-top);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.weui-navigation-bar__left {
|
||||
position: relative;
|
||||
padding-left: var(--left);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.weui-navigation-bar__btn_goback_wrapper {
|
||||
padding: 11px 18px 11px 16px;
|
||||
margin: -11px -18px -11px -16px;
|
||||
}
|
||||
|
||||
.weui-navigation-bar__btn_goback_wrapper.weui-active {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.weui-navigation-bar__btn_goback {
|
||||
font-size: 12px;
|
||||
width: 12px;
|
||||
height: 24px;
|
||||
-webkit-mask: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%;
|
||||
mask: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%;
|
||||
-webkit-mask-size: cover;
|
||||
mask-size: cover;
|
||||
background-color: var(--weui-FG-0);
|
||||
}
|
||||
|
||||
.weui-navigation-bar__center {
|
||||
font-size: 17px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.weui-navigation-bar__loading {
|
||||
margin-right: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.weui-loading {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
background: transparent url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='80px' height='80px' viewBox='0 0 80 80' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3Eloading%3C/title%3E%3Cdefs%3E%3ClinearGradient x1='94.0869141%25' y1='0%25' x2='94.0869141%25' y2='90.559082%25' id='linearGradient-1'%3E%3Cstop stop-color='%23606060' stop-opacity='0' offset='0%25'%3E%3C/stop%3E%3Cstop stop-color='%23606060' stop-opacity='0.3' offset='100%25'%3E%3C/stop%3E%3C/linearGradient%3E%3ClinearGradient x1='100%25' y1='8.67370605%25' x2='100%25' y2='90.6286621%25' id='linearGradient-2'%3E%3Cstop stop-color='%23606060' offset='0%25'%3E%3C/stop%3E%3Cstop stop-color='%23606060' stop-opacity='0.3' offset='100%25'%3E%3C/stop%3E%3C/linearGradient%3E%3C/defs%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' opacity='0.9'%3E%3Cg%3E%3Cpath d='M40,0 C62.09139,0 80,17.90861 80,40 C80,62.09139 62.09139,80 40,80 L40,73 C58.2253967,73 73,58.2253967 73,40 C73,21.7746033 58.2253967,7 40,7 L40,0 Z' fill='url(%23linearGradient-1)'%3E%3C/path%3E%3Cpath d='M40,0 L40,7 C21.7746033,7 7,21.7746033 7,40 C7,58.2253967 21.7746033,73 40,73 L40,80 C17.90861,80 0,62.09139 0,40 C0,17.90861 17.90861,0 40,0 Z' fill='url(%23linearGradient-2)'%3E%3C/path%3E%3Ccircle id='Oval' fill='%23606060' cx='40.5' cy='3.5' r='3.5'%3E%3C/circle%3E%3C/g%3E%3C/g%3E%3C/svg%3E%0A") no-repeat;
|
||||
background-size: 100%;
|
||||
margin-left: 0;
|
||||
animation: loading linear infinite 1s;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
from {
|
||||
transform: rotate(0);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
102
miniprogram/components/order-card/order-card.js
Normal file
102
miniprogram/components/order-card/order-card.js
Normal file
@@ -0,0 +1,102 @@
|
||||
const app = getApp();
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
orderId: { type: String, value: '' }
|
||||
},
|
||||
|
||||
data: {
|
||||
order: null,
|
||||
loading: false,
|
||||
errorMsg: '',
|
||||
// 跨平台字段
|
||||
isCross: false, // 是否跨平台订单
|
||||
dispatchType: 0, // 1-我方派单,2-对方派单
|
||||
partnerClubId: '', // 对方俱乐部ID
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
attached() {
|
||||
if (this.properties.orderId) {
|
||||
this.fetchOrderDetail();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 获取订单详情(含跨平台字段)
|
||||
fetchOrderDetail() {
|
||||
if (!this.properties.orderId) return;
|
||||
this.setData({ loading: true, errorMsg: '' });
|
||||
const baseUrl = app.globalData.apiBaseUrl;
|
||||
wx.request({
|
||||
url: `${baseUrl}/dingdan/order_detail/`, // 需后端返回 is_cross, dispatch_type, partner_club_id
|
||||
method: 'POST',
|
||||
data: { order_id: this.properties.orderId },
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 0) {
|
||||
const order = res.data.data;
|
||||
// 处理图片URL
|
||||
if (order.tupian && !order.tupian.startsWith('http')) {
|
||||
order.tupian = app.globalData.ossImageUrl + order.tupian;
|
||||
}
|
||||
this.setData({
|
||||
order,
|
||||
loading: false,
|
||||
isCross: order.is_cross === 1,
|
||||
dispatchType: order.dispatch_type || 0,
|
||||
partnerClubId: order.partner_club_id || '',
|
||||
});
|
||||
// 通知父页面状态
|
||||
this.triggerEvent('statusready', {
|
||||
isCross: this.data.isCross,
|
||||
dispatchType: this.data.dispatchType,
|
||||
partnerClubId: this.data.partnerClubId
|
||||
});
|
||||
} else {
|
||||
this.setData({ errorMsg: res.data?.msg || '获取订单失败', loading: false });
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
this.setData({ errorMsg: '网络请求失败', loading: false });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 发送订单卡片消息(原有)
|
||||
sendOrderMessage() {
|
||||
if (!this.data.order) return;
|
||||
this.triggerEvent('sendorder', { order: this.data.order });
|
||||
},
|
||||
|
||||
// 跨平台消息转发(供父页面调用)
|
||||
forwardMessage(payload) {
|
||||
const that = this;
|
||||
const baseUrl = app.globalData.apiBaseUrl;
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: `${baseUrl}/dingdan/kptxxts`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
order_id: that.properties.orderId,
|
||||
message_type: payload.type || 'text',
|
||||
content: payload.content || '',
|
||||
image_url: payload.imageUrl || '', // 图片等
|
||||
},
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 0) {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
reject(res.data);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
4
miniprogram/components/order-card/order-card.json
Normal file
4
miniprogram/components/order-card/order-card.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
20
miniprogram/components/order-card/order-card.wxml
Normal file
20
miniprogram/components/order-card/order-card.wxml
Normal file
@@ -0,0 +1,20 @@
|
||||
<view class="order-card" wx:if="{{order}}">
|
||||
<view class="card-header">
|
||||
<text class="card-title">关联订单</text>
|
||||
<text class="card-id">{{order.dingdan_id}}</text>
|
||||
<!-- 跨平台标记 -->
|
||||
<text class="cross-tag" wx:if="{{isCross}}">跨平台</text>
|
||||
</view>
|
||||
<view class="card-body" bindtap="sendOrderMessage">
|
||||
<image class="card-img" src="{{order.tupian || '/images/default-goods.png'}}" mode="aspectFill" />
|
||||
<view class="card-info">
|
||||
<text class="card-jieshao">{{order.jieshao || '暂无介绍'}}</text>
|
||||
<text class="card-jine">¥{{order.jine}}</text>
|
||||
</view>
|
||||
<view class="card-send-btn">发送</view>
|
||||
</view>
|
||||
<view wx:if="{{errorMsg}}" class="card-error">{{errorMsg}}</view>
|
||||
</view>
|
||||
<view class="order-card" wx:elif="{{loading}}">
|
||||
<text class="loading-text">加载订单信息...</text>
|
||||
</view>
|
||||
39
miniprogram/components/order-card/order-card.wxss
Normal file
39
miniprogram/components/order-card/order-card.wxss
Normal file
@@ -0,0 +1,39 @@
|
||||
.order-card {
|
||||
background: #fff;
|
||||
margin: 15rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 20rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.card-title { font-size: 26rpx; color: #666; }
|
||||
.card-id { font-size: 24rpx; color: #999; margin-left: 15rpx; }
|
||||
.cross-tag {
|
||||
margin-left: auto;
|
||||
background: #ff9900;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.card-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
}
|
||||
.card-img { width: 100rpx; height: 100rpx; border-radius: 8rpx; margin-right: 15rpx; }
|
||||
.card-info { flex: 1; }
|
||||
.card-jieshao { font-size: 28rpx; color: #333; }
|
||||
.card-jine { font-size: 26rpx; color: #e74c3c; }
|
||||
.card-send-btn {
|
||||
width: 80rpx; height: 50rpx; line-height: 50rpx;
|
||||
text-align: center; background: #07c160; color: #fff;
|
||||
border-radius: 8rpx; font-size: 24rpx;
|
||||
}
|
||||
.card-error { padding: 20rpx; color: #e74c3c; text-align: center; }
|
||||
.loading-text { display: block; padding: 20rpx; text-align: center; color: #999; }
|
||||
16
miniprogram/components/order-sender/order-sender.js
Normal file
16
miniprogram/components/order-sender/order-sender.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const app = getApp();
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
visible: Boolean,
|
||||
showDetail: { type: Boolean, value: false }
|
||||
},
|
||||
|
||||
data: {},
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.triggerEvent('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
4
miniprogram/components/order-sender/order-sender.json
Normal file
4
miniprogram/components/order-sender/order-sender.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
12
miniprogram/components/order-sender/order-sender.wxml
Normal file
12
miniprogram/components/order-sender/order-sender.wxml
Normal file
@@ -0,0 +1,12 @@
|
||||
<view class="order-sender-mask" wx:if="{{visible}}" catchtap="close"></view>
|
||||
<view class="order-sender {{visible ? 'show' : ''}}">
|
||||
<view class="header">
|
||||
<text class="title">提示</text>
|
||||
<text class="close" bindtap="close">✕</text>
|
||||
</view>
|
||||
<view class="content">
|
||||
<view class="icon">📋</view>
|
||||
<text class="notice">此功能暂未开放</text>
|
||||
<text class="sub">敬请期待</text>
|
||||
</view>
|
||||
</view>
|
||||
45
miniprogram/components/order-sender/order-sender.wxss
Normal file
45
miniprogram/components/order-sender/order-sender.wxss
Normal file
@@ -0,0 +1,45 @@
|
||||
.order-sender-mask {
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 2000;
|
||||
}
|
||||
.order-sender {
|
||||
position: fixed; bottom: 0; left: 0; right: 0;
|
||||
height: 360rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
z-index: 2001;
|
||||
transform: translateY(100%);
|
||||
transition: 0.25s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.order-sender.show { transform: translateY(0); }
|
||||
|
||||
.header {
|
||||
display: flex; justify-content: space-between;
|
||||
padding: 24rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.title { font-size: 32rpx; font-weight: 600; }
|
||||
.close { font-size: 44rpx; color: #999; padding: 0 12rpx; }
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
.icon { font-size: 80rpx; margin-bottom: 20rpx; }
|
||||
.notice {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
.sub {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
324
miniprogram/components/popup-notice/popup-notice.js
Normal file
324
miniprogram/components/popup-notice/popup-notice.js
Normal file
@@ -0,0 +1,324 @@
|
||||
// components/popup-notice/popup-notice.js
|
||||
import PopupService from '../../services/popupService.js';
|
||||
|
||||
Component({
|
||||
data: {
|
||||
// 原有全屏弹窗数据
|
||||
visible: false,
|
||||
title: '',
|
||||
images: [],
|
||||
processedImages: [],
|
||||
hasImages: false,
|
||||
hasTextOnly: false,
|
||||
textContent: '',
|
||||
duration: 0,
|
||||
remainingSeconds: 0,
|
||||
muteChecked: false,
|
||||
showMuteCheckbox: false,
|
||||
isFullscreen: false,
|
||||
timer: null,
|
||||
|
||||
// 悬浮窗相关数据
|
||||
isMinimized: false,
|
||||
floatX: 20,
|
||||
floatY: 200,
|
||||
savedRemainingSeconds: 0,
|
||||
floatAvatarUrl: '',
|
||||
|
||||
// 🆕 自定义预览数据
|
||||
showCustomPreview: false, // 是否显示自定义预览遮罩
|
||||
previewUrls: [], // 当前预览的图片URL列表
|
||||
currentPreviewIndex: 0, // 当前预览图片索引
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
detached() {
|
||||
this.clearTimer();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// ========== 原有所有方法(完全不变,一字不漏) ==========
|
||||
async show(config, onCloseCallback) {
|
||||
const { title, images, duration, showMuteCheckbox, serverTime } = config;
|
||||
this.onCloseCallback = onCloseCallback;
|
||||
|
||||
let safeImages = JSON.parse(JSON.stringify(images || []));
|
||||
const hasImages = safeImages.length > 0;
|
||||
let hasTextOnly = false, textContent = '';
|
||||
if (!hasImages) {
|
||||
hasTextOnly = true;
|
||||
textContent = config.text || '暂无内容';
|
||||
}
|
||||
|
||||
this.setData({
|
||||
visible: true,
|
||||
isMinimized: false,
|
||||
title: title || '',
|
||||
images: safeImages,
|
||||
hasImages,
|
||||
hasTextOnly,
|
||||
textContent,
|
||||
duration: duration || 0,
|
||||
remainingSeconds: duration || 0,
|
||||
muteChecked: false,
|
||||
showMuteCheckbox: showMuteCheckbox || false,
|
||||
isFullscreen: false,
|
||||
processedImages: [],
|
||||
});
|
||||
|
||||
if (duration > 0) {
|
||||
this.startCountdown();
|
||||
}
|
||||
|
||||
if (hasImages) {
|
||||
const processed = [];
|
||||
const screenWidth = wx.getSystemInfoSync().windowWidth;
|
||||
const containerWidth = screenWidth * 0.9;
|
||||
const imageWidth = containerWidth - 60;
|
||||
for (let i = 0; i < safeImages.length; i++) {
|
||||
const img = safeImages[i];
|
||||
const fullUrl = this.getFullImageUrl(img.url);
|
||||
let imageHeight = 200;
|
||||
try {
|
||||
const res = await this.getImageInfoPromise(fullUrl);
|
||||
const realHeight = (imageWidth * res.height) / res.width;
|
||||
imageHeight = realHeight;
|
||||
} catch (e) {
|
||||
console.error('获取图片信息失败', fullUrl, e);
|
||||
}
|
||||
processed.push({
|
||||
url: fullUrl,
|
||||
text: img.text,
|
||||
height: imageHeight,
|
||||
});
|
||||
}
|
||||
this.setData({ processedImages: processed });
|
||||
}
|
||||
},
|
||||
|
||||
getImageInfoPromise(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.getImageInfo({
|
||||
src: url,
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
hide(muteToday = false) {
|
||||
this.clearTimer();
|
||||
this.setData({
|
||||
visible: false,
|
||||
isMinimized: false
|
||||
});
|
||||
if (this.onCloseCallback) {
|
||||
this.onCloseCallback({ muteToday });
|
||||
}
|
||||
},
|
||||
|
||||
startCountdown() {
|
||||
this.clearTimer();
|
||||
this.timer = setInterval(() => {
|
||||
let sec = this.data.remainingSeconds;
|
||||
if (sec <= 1) {
|
||||
this.clearTimer();
|
||||
this.setData({ remainingSeconds: 0 });
|
||||
} else {
|
||||
this.setData({ remainingSeconds: sec - 1 });
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
clearTimer() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
},
|
||||
|
||||
onCloseTap() {
|
||||
if (this.data.remainingSeconds > 0) {
|
||||
wx.showToast({
|
||||
title: `请阅读${this.data.remainingSeconds}秒后再关闭`,
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.data.showMuteCheckbox && this.data.muteChecked) {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '今天将不再收到本页面的任何公告,确认吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) this.hide(true);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.hide(false);
|
||||
}
|
||||
},
|
||||
|
||||
onMuteChange(e) {
|
||||
this.setData({ muteChecked: e.detail.value.length > 0 });
|
||||
},
|
||||
|
||||
// 🆕 替换原有的 onPreviewImage,改用自定义预览(不触发 onShow)
|
||||
onCustomPreview(e) {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
const urls = this.data.processedImages.map(img => img.url);
|
||||
this.setData({
|
||||
showCustomPreview: true,
|
||||
previewUrls: urls,
|
||||
currentPreviewIndex: index !== undefined ? index : 0
|
||||
});
|
||||
},
|
||||
|
||||
// 🆕 关闭自定义预览
|
||||
onCloseCustomPreview() {
|
||||
this.setData({
|
||||
showCustomPreview: false,
|
||||
previewUrls: [],
|
||||
currentPreviewIndex: 0
|
||||
});
|
||||
},
|
||||
|
||||
// 🆕 swiper 切换事件
|
||||
onPreviewSwiperChange(e) {
|
||||
this.setData({
|
||||
currentPreviewIndex: e.detail.current
|
||||
});
|
||||
},
|
||||
|
||||
// 保留原 onPreviewImage 方法以防外部调用,但不再使用
|
||||
onPreviewImage(e) {
|
||||
// 重定向到自定义预览
|
||||
this.onCustomPreview(e);
|
||||
},
|
||||
|
||||
onImageError(e) {
|
||||
console.error('图片加载失败:', e.currentTarget.dataset.url);
|
||||
},
|
||||
|
||||
toggleFullscreen() {
|
||||
this.setData({ isFullscreen: !this.data.isFullscreen });
|
||||
},
|
||||
|
||||
getFullImageUrl(relativeUrl) {
|
||||
if (!relativeUrl) return '';
|
||||
if (relativeUrl.startsWith('http')) return relativeUrl;
|
||||
const app = getApp();
|
||||
let base = app.globalData.ossImageUrl || '';
|
||||
base = base.replace(/\/+$/, '');
|
||||
const clean = relativeUrl.replace(/^\/+/, '');
|
||||
return base ? base + '/' + clean : clean;
|
||||
},
|
||||
|
||||
preventMove() {},
|
||||
|
||||
onMinimize() {
|
||||
const remaining = this.data.remainingSeconds;
|
||||
this.clearTimer();
|
||||
|
||||
const app = getApp();
|
||||
let avatarUrl = '';
|
||||
if (app.globalData.morentouxiang) {
|
||||
avatarUrl = app.globalData.morentouxiang;
|
||||
} else {
|
||||
avatarUrl = '/images/default-avatar.png';
|
||||
}
|
||||
|
||||
if (avatarUrl && !avatarUrl.startsWith('http')) {
|
||||
const ossBase = app.globalData.ossImageUrl || '';
|
||||
avatarUrl = ossBase.replace(/\/+$/, '') + '/' + avatarUrl.replace(/^\/+/, '');
|
||||
}
|
||||
|
||||
this.setData({
|
||||
visible: false,
|
||||
isMinimized: true,
|
||||
savedRemainingSeconds: remaining,
|
||||
floatX: 20,
|
||||
floatY: 200,
|
||||
floatAvatarUrl: avatarUrl
|
||||
});
|
||||
},
|
||||
|
||||
onFloatChange(e) {
|
||||
this.setData({
|
||||
floatX: e.detail.x,
|
||||
floatY: e.detail.y
|
||||
});
|
||||
},
|
||||
|
||||
onFloatTap() {
|
||||
this.setData({
|
||||
visible: true,
|
||||
isMinimized: false,
|
||||
remainingSeconds: this.data.savedRemainingSeconds
|
||||
});
|
||||
|
||||
if (this.data.duration > 0 && this.data.savedRemainingSeconds > 0) {
|
||||
this.startCountdown();
|
||||
}
|
||||
},
|
||||
|
||||
onFloatAvatarError() {
|
||||
this.setData({
|
||||
floatAvatarUrl: '/images/avatar-placeholder.png'
|
||||
});
|
||||
},
|
||||
|
||||
cleanup() {
|
||||
this.clearTimer();
|
||||
this.setData({
|
||||
visible: false,
|
||||
isMinimized: false
|
||||
});
|
||||
PopupService.reset();
|
||||
},
|
||||
|
||||
onFloatTouchStart(e) {
|
||||
const touch = e.touches[0];
|
||||
this.touchStartX = touch.clientX;
|
||||
this.touchStartY = touch.clientY;
|
||||
this.startFloatX = this.data.floatX;
|
||||
this.startFloatY = this.data.floatY;
|
||||
this.hasMoved = false;
|
||||
return false;
|
||||
},
|
||||
|
||||
onFloatTouchMove(e) {
|
||||
const touch = e.touches[0];
|
||||
const deltaX = touch.clientX - this.touchStartX;
|
||||
const deltaY = touch.clientY - this.touchStartY;
|
||||
|
||||
if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
|
||||
this.hasMoved = true;
|
||||
}
|
||||
|
||||
let newX = this.startFloatX + deltaX;
|
||||
let newY = this.startFloatY + deltaY;
|
||||
|
||||
const systemInfo = wx.getSystemInfoSync();
|
||||
const screenWidth = systemInfo.windowWidth;
|
||||
const screenHeight = systemInfo.windowHeight;
|
||||
const estimatedSize = 60;
|
||||
|
||||
newX = Math.max(0, Math.min(newX, screenWidth - estimatedSize));
|
||||
newY = Math.max(0, Math.min(newY, screenHeight - estimatedSize));
|
||||
this.setData({ floatX: newX, floatY: newY });
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
onFloatTouchEnd(e) {
|
||||
if (!this.hasMoved) {
|
||||
this.onFloatTap();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// 🆕 阻止事件冒泡(用于预览遮罩内部点击不关闭)
|
||||
stopPropagation() {}
|
||||
}
|
||||
});
|
||||
4
miniprogram/components/popup-notice/popup-notice.json
Normal file
4
miniprogram/components/popup-notice/popup-notice.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
110
miniprogram/components/popup-notice/popup-notice.wxml
Normal file
110
miniprogram/components/popup-notice/popup-notice.wxml
Normal file
@@ -0,0 +1,110 @@
|
||||
<!-- components/popup-notice/popup-notice.wxml -->
|
||||
|
||||
<!-- 全屏弹窗遮罩层(完全不变) -->
|
||||
<view class="popup-mask" wx:if="{{visible}}" catchtouchmove="preventMove">
|
||||
<view class="popup-container {{isFullscreen ? 'fullscreen' : ''}}">
|
||||
<!-- 右上角操作按钮组 -->
|
||||
<view class="action-buttons">
|
||||
<!-- 最小化按钮 -->
|
||||
<view class="minimize-btn" bindtap="onMinimize">
|
||||
<text class="btn-icon">−</text>
|
||||
</view>
|
||||
<!-- 全屏切换按钮 -->
|
||||
<view class="fullscreen-btn" bindtap="toggleFullscreen">
|
||||
<text class="btn-icon">{{isFullscreen ? '✕' : '⛶'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 标题 -->
|
||||
<view wx:if="{{title}}" class="popup-title">{{title}}</view>
|
||||
|
||||
<!-- 可滚动内容区 -->
|
||||
<view class="scroll-wrapper">
|
||||
<scroll-view scroll-y class="scroll-view" enhanced show-scrollbar="{{true}}">
|
||||
<view wx:if="{{hasTextOnly}}" class="text-content">{{textContent}}</view>
|
||||
<view wx:else>
|
||||
<view wx:for="{{processedImages}}" wx:key="index" class="image-block">
|
||||
<view wx:if="{{item.text}}" class="block-text">{{item.text}}</view>
|
||||
<view class="image-padding">
|
||||
<image
|
||||
class="block-image"
|
||||
src="{{item.url}}"
|
||||
style="width:100%; height:{{item.height}}px; display:block;"
|
||||
data-index="{{index}}"
|
||||
data-url="{{item.url}}"
|
||||
bindtap="onCustomPreview"
|
||||
binderror="onImageError"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 底部固定区 -->
|
||||
<view class="bottom-fixed">
|
||||
<view wx:if="{{remainingSeconds > 0}}" class="countdown-tip">
|
||||
请阅读 {{remainingSeconds}} 秒后关闭
|
||||
</view>
|
||||
<view wx:if="{{remainingSeconds === 0 && showMuteCheckbox}}" class="mute-row">
|
||||
<checkbox-group bindchange="onMuteChange">
|
||||
<checkbox value="mute" color="#000" />
|
||||
<text class="mute-label">今日不再提示</text>
|
||||
</checkbox-group>
|
||||
</view>
|
||||
<!-- 关闭按钮 -->
|
||||
<view
|
||||
class="close-btn {{remainingSeconds > 0 ? 'close-btn-disabled' : ''}}"
|
||||
bindtap="{{remainingSeconds > 0 ? '' : 'onCloseTap'}}"
|
||||
>
|
||||
<text class="close-btn-text">{{remainingSeconds > 0 ? '阅读中...' : '关闭'}}</text>
|
||||
<view class="close-btn-sweep" wx:if="{{remainingSeconds === 0}}"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 悬浮窗(自定义触摸拖动) -->
|
||||
<view
|
||||
class="floating-avatar-wrapper"
|
||||
wx:if="{{isMinimized}}"
|
||||
style="left: {{floatX}}px; top: {{floatY}}px;"
|
||||
catchtouchstart="onFloatTouchStart"
|
||||
catchtouchmove="onFloatTouchMove"
|
||||
catchtouchend="onFloatTouchEnd"
|
||||
>
|
||||
<image
|
||||
class="floating-avatar"
|
||||
src="{{floatAvatarUrl}}"
|
||||
mode="aspectFill"
|
||||
binderror="onFloatAvatarError"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 🆕 自定义图片预览遮罩(全屏,不触发页面生命周期) -->
|
||||
<view
|
||||
class="custom-preview-mask"
|
||||
wx:if="{{showCustomPreview}}"
|
||||
catchtouchmove="preventMove"
|
||||
bindtap="onCloseCustomPreview"
|
||||
>
|
||||
<view class="preview-header">
|
||||
<text class="preview-index">{{currentPreviewIndex + 1}} / {{previewUrls.length}}</text>
|
||||
<view class="preview-close" bindtap="onCloseCustomPreview">×</view>
|
||||
</view>
|
||||
<swiper
|
||||
class="preview-swiper"
|
||||
current="{{currentPreviewIndex}}"
|
||||
bindchange="onPreviewSwiperChange"
|
||||
indicator-dots="{{false}}"
|
||||
>
|
||||
<swiper-item wx:for="{{previewUrls}}" wx:key="index">
|
||||
<image
|
||||
src="{{item}}"
|
||||
mode="aspectFit"
|
||||
class="preview-image"
|
||||
catchtap="stopPropagation"
|
||||
/>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
295
miniprogram/components/popup-notice/popup-notice.wxss
Normal file
295
miniprogram/components/popup-notice/popup-notice.wxss
Normal file
@@ -0,0 +1,295 @@
|
||||
/* components/popup-notice/popup-notice.wxss */
|
||||
|
||||
/* ========== 全屏弹窗样式(原有,完全不变) ========== */
|
||||
.popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
width: 90%;
|
||||
height: 85vh;
|
||||
background: white;
|
||||
border-radius: 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.popup-container.fullscreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.minimize-btn,
|
||||
.fullscreen-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(4rpx);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.minimize-btn:active,
|
||||
.fullscreen-btn:active {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 36rpx;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
padding: 30rpx 30rpx 20rpx;
|
||||
border-bottom: 2rpx solid #eee;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.scroll-wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
line-height: 1.6;
|
||||
padding: 30rpx;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.image-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.block-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
padding: 16rpx 30rpx 4rpx 30rpx;
|
||||
text-align: left;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.image-padding {
|
||||
padding: 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.block-image {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.bottom-fixed {
|
||||
background: white;
|
||||
border-top: 1rpx solid #eee;
|
||||
padding: 20rpx 0 30rpx 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.countdown-tip {
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #ff6600;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.mute-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.mute-label {
|
||||
font-size: 28rpx;
|
||||
color: #555;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
/* 🔥 关闭按钮(替换原有 button,增加扫光) */
|
||||
.close-btn {
|
||||
width: 600rpx;
|
||||
height: 100rpx;
|
||||
background: #1a1a1a;
|
||||
border-radius: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.close-btn:active {
|
||||
opacity: 0.85;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.close-btn-disabled {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.close-btn-text {
|
||||
color: white;
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.close-btn-sweep {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5), transparent);
|
||||
transform: skewX(-20deg);
|
||||
animation: sweep 2.5s infinite ease-in-out;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes sweep {
|
||||
0% { left: -100%; }
|
||||
40% { left: 100%; }
|
||||
100% { left: 100%; }
|
||||
}
|
||||
|
||||
/* ========== 悬浮窗样式(纯圆形,适配自定义触摸拖动) ========== */
|
||||
.floating-avatar-wrapper {
|
||||
position: fixed;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
z-index: 10001;
|
||||
transform: translateZ(0);
|
||||
will-change: left, top;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.floating-avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.3);
|
||||
border: 2rpx solid rgba(255, 215, 0, 0.6);
|
||||
background: #f0f0f0;
|
||||
display: block;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
/* 以下原有 movable-view 相关样式已弃用,但保留以维持代码完整(用户要求不删代码) */
|
||||
.floating-area {
|
||||
display: none;
|
||||
}
|
||||
.floating-view {
|
||||
display: none;
|
||||
}
|
||||
.float-avatar-wrapper {
|
||||
display: none;
|
||||
}
|
||||
.float-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ========== 自定义图片预览遮罩样式(全屏,不触发 onShow) ========== */
|
||||
.custom-preview-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
z-index: 100000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 30rpx;
|
||||
color: white;
|
||||
font-size: 32rpx;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.preview-index {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.preview-close {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 48rpx;
|
||||
color: white;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.preview-swiper {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
Reference in New Issue
Block a user