统一排行榜对齐星雀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

@@ -310,6 +310,7 @@ App({
enterpriseId: config.kefu.enterpriseId || ''
};
}
this.emitEvent('configReady', config);
},
initGoEasyWithConfig() {

107
miniprogram/app.json Normal file
View File

@@ -0,0 +1,107 @@
{
"pages": [
"pages/index/index",
"pages/fenlei/fenlei",
"pages/xuanren/xuanren",
"pages/xiaoxi/xiaoxi",
"pages/wode/wode",
"pages/shangpinxiangqing/shangpinxiangqing",
"pages/tijiao/tijiao",
"pages/xiugai/xiugai",
"pages/dashouduan/dashouduan",
"pages/shangjiaduan/shangjiaduan",
"pages/guanshiduan/guanshiduan",
"pages/dingdan/dingdan",
"pages/dingdanxiangqing/dingdanxiangqing",
"pages/dashouguize/dashouguize",
"pages/dashouxiugai/dashouxiugai",
"pages/dashoupaihang/dashoupaihang",
"pages/dashouchongzhi/dashouchongzhi",
"pages/dashouxiaoxi/dashouxiaoxi",
"pages/dashoudingdan/dashoudingdan",
"pages/jiedan/jiedan",
"pages/tixian/tixian",
"pages/yaoqingdashou/yaoqingdashou",
"pages/wodedashou/wodedashou",
"pages/czjilu/czjilu",
"pages/guanshipaihang/guanshipaihang",
"pages/sjpaidan/sjpaidan",
"pages/sjdingdan/sjdingdan",
"pages/sjxiaoxi/sjxiaoxi",
"pages/sjpaihang/sjpaihang",
"pages/sjchongzhi/sjchongzhi",
"pages/sjddxq/sjddxq",
"pages/dsddxq/dsddxq",
"pages/liaotian/liaotian",
"pages/jisufd/jisufd",
"pages/jiedanchi/jiedanchi",
"pages/haibao/haibao",
"pages/jinpaids/jinpaids",
"pages/zuzhangduan/zuzhangduan",
"pages/guanzhual/guanzhual",
"pages/zzfhjilu/zzfhjilu",
"pages/yqguanshi/yqguanshi",
"components/global-notification/global-notification",
"components/popup-notice/popup-notice",
"pages/tixian/components/mode1/mode1",
"pages/tixian/components/mode2/mode2",
"pages/jiedanchi2/jiedanchi2",
"pages/qunliaotian/qunliaotian",
"pages/kefuliaotian/kefuliaotian",
"pages/renzheng/renzheng",
"components/order-card/order-card",
"components/order-sender/order-sender",
"pages/cfss/cfss/cfss",
"pages/cfss/components/fakuan-list/fakuan-list",
"pages/cfss/components/jifen-list/jifen-list",
"pages/cfss/components/fakuan-pay/fakuan-pay",
"pages/phone-auth/phone-auth",
"components/chenghao-tag/chenghao-tag",
"pages/kaoheguan/kaoheguan",
"pages/kaohe_dafen/kaohe_dafen",
"pages/kaohe_jilu/kaohe_jilu",
"pages/kaohe_zhongxin/kaohe_zhongxin",
"pages/kaohe_jinpai/kaohe_jinpai"
],
"usingComponents": {
"popup-notice": "/components/popup-notice/popup-notice",
"custom-tab-bar": "/custom-tab-bar/index"
},
"window": {
"navigationBarTextStyle": "black",
"navigationStyle": "default",
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "二H电竞"
},
"tabBar": {
"custom": true,
"list": [
{
"pagePath": "pages/jiedanchi/jiedanchi",
"text": "接单池",
"iconPath": "/images/jiedanchi.png",
"selectedIconPath": "/images/jiedanchi.png"
},
{
"pagePath": "pages/jiedanchi2/jiedanchi2",
"text": "接单池",
"iconPath": "/images/jiedanchi.png",
"selectedIconPath": "/images/jiedanchi.png"
}
]
},
"style": "v2",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents"
}

10
miniprogram/app.wxss Normal file
View File

@@ -0,0 +1,10 @@
/**app.wxss**/
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200rpx 0;
box-sizing: border-box;
}

View 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);' });
}
}
}
});

View File

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

View 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>

View 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); }
}

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;
}

View 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 }, {})
}
},
})

View File

@@ -0,0 +1,5 @@
{
"component": true,
"styleIsolation": "apply-shared",
"usingComponents": {}
}

View 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>

View 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);
}
}

View 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);
}
});
});
}
}
});

View File

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

View 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>

View 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; }

View File

@@ -0,0 +1,16 @@
const app = getApp();
Component({
properties: {
visible: Boolean,
showDetail: { type: Boolean, value: false }
},
data: {},
methods: {
close() {
this.triggerEvent('close');
}
}
});

View File

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

View 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>

View 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;
}

View 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() {}
}
});

View File

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

View 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>

View 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%;
}

144
miniprogram/config/roles.js Normal file
View File

@@ -0,0 +1,144 @@
/*
* 角色配置表 —— 用于动态切换底部 TabBar 展示内容
* 每个角色的 tabList 数组元素顺序决定 TabBar 显示顺序
* 字段说明:
* pagePath : 页面路径(以 pages/ 开头)
* text : TabBar 文字
* iconPath : 未选中图标(相对路径)
* selectedIconPath: 选中图标
*/
const roles = {
// 普通老板(默认)
normal: {
name: '普通老板',
defaultPage: 'pages/index/index',
tabList: [
{
pagePath: 'pages/index/index',
text: '商城',
iconPath: '/images/zhuye.png',
selectedIconPath: '/images/zhuye.png'
},
{
pagePath: 'pages/fenlei/fenlei',
text: '分类',
iconPath: '/images/fenlei.png',
selectedIconPath: '/images/fenlei.png'
},
{
pagePath: 'pages/xiaoxi/xiaoxi',
text: '消息',
iconPath: '/images/xiaoxi.png',
selectedIconPath: '/images/xiaoxi.png'
},
{
pagePath: 'pages/wode/wode',
text: '我的',
iconPath: '/images/wode.png',
selectedIconPath: '/images/wode.png'
}
]
},
// 打手
dashou: {
name: '打手',
defaultPage: 'pages/jiedanchi/jiedanchi',
tabList: [
{
pagePath: 'pages/jiedanchi/jiedanchi',
text: '接单池',
iconPath: '/images/jiedanchi.png',
selectedIconPath: '/images/jiedanchi.png'
},
{
pagePath: 'pages/xiaoxi/xiaoxi',
text: '消息',
iconPath: '/images/xiaoxi.png',
selectedIconPath: '/images/xiaoxi.png'
},
{
pagePath: 'pages/dashoudingdan/dashoudingdan',
text: '订单',
iconPath: '/images/dashoudingdan_icon.png', // 请替换为实际图标
selectedIconPath: '/images/dashoudingdan_icon.png'
},
{
pagePath: 'pages/dashouduan/dashouduan',
text: '我的',
iconPath: '/images/dashouduan_icon.png',
selectedIconPath: '/images/dashouduan_icon.png'
}
]
},
// 商家
shangjia: {
name: '商家',
defaultPage: 'pages/index/index',
tabList: [
{
pagePath: 'pages/index/index',
text: '首页',
iconPath: '/images/zhuye.png',
selectedIconPath: '/images/zhuye.png'
},
{
pagePath: 'pages/sjdingdan/sjdingdan',
text: '订单',
iconPath: '/images/sjdingdan_icon.png', // 请替换为实际图标
selectedIconPath: '/images/sjdingdan_icon.png'
},
{
pagePath: 'pages/xiaoxi/xiaoxi',
text: '消息',
iconPath: '/images/xiaoxi.png',
selectedIconPath: '/images/xiaoxi.png'
},
{
pagePath: 'pages/shangjiaduan/shangjiaduan',
text: '我的',
iconPath: '/images/shangjiaduan_icon.png',
selectedIconPath: '/images/shangjiaduan_icon.png'
}
]
},
// 管事
guanshi: {
name: '管事',
defaultPage: 'pages/guanshiduan/guanshiduan',
tabList: [
{
pagePath: 'pages/xiaoxi/xiaoxi',
text: '消息',
iconPath: '/images/xiaoxi.png',
selectedIconPath: '/images/xiaoxi.png'
},
{
pagePath: 'pages/guanshiduan/guanshiduan',
text: '我的',
iconPath: '/images/guanshiduan_icon.png',
selectedIconPath: '/images/guanshiduan_icon.png'
}
]
},
// 组长
zuzhang: {
name: '组长',
defaultPage: 'pages/zuzhangduan/zuzhangduan',
tabList: [
{
pagePath: 'pages/xiaoxi/xiaoxi',
text: '消息',
iconPath: '/images/xiaoxi.png',
selectedIconPath: '/images/xiaoxi.png'
},
{
pagePath: 'pages/zuzhangduan/zuzhangduan',
text: '我的',
iconPath: '/images/zuzhangduan_icon.png',
selectedIconPath: '/images/zuzhangduan_icon.png'
}
]
}
};
module.exports = { roles };

View File

@@ -0,0 +1,194 @@
// custom-tab-bar/index.js
const app = getApp();
const roleCfg = {
normal: {
name: '点单老板',
defaultPage: '/pages/index/index',
tabList: [
{ pagePath: 'pages/index/index', text: '商城', iconPath: '/images/zhuye.png', selectedIconPath: '/images/zhuye.png' },
{ pagePath: 'pages/fenlei/fenlei', text: '分类', iconPath: '/images/fenlei.png', selectedIconPath: '/images/fenlei.png' },
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
{ pagePath: 'pages/wode/wode', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
]
},
dashou: {
name: '打手',
defaultPage: '/pages/jiedan/jiedan',
tabList: [
{ pagePath: 'pages/jiedan/jiedan', text: '接单池', iconPath: '/images/jiedanchi.png', selectedIconPath: '/images/jiedanchi.png' },
{ pagePath: 'pages/dashoudingdan/dashoudingdan', text: '订单', iconPath: '/images/dingdan.png', selectedIconPath: '/images/dingdan.png' },
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
{ pagePath: 'pages/dashouduan/dashouduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
]
},
shangjia: {
name: '商家',
defaultPage: 'pages/sjdingdan/sjdingdan',
tabList: [
{ pagePath: 'pages/sjdingdan/sjdingdan', text: '订单', iconPath: '/images/dingdan.png', selectedIconPath: '/images/dingdan.png' },
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
{ pagePath: 'pages/shangjiaduan/shangjiaduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
]
},
guanshi: {
name: '管事',
defaultPage: '/pages/guanshiduan/guanshiduan',
tabList: [
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
{ pagePath: 'pages/guanshiduan/guanshiduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
]
},
zuzhang: {
name: '组长',
defaultPage: '/pages/zuzhangduan/zuzhangduan',
tabList: [
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
{ pagePath: 'pages/zuzhangduan/zuzhangduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
]
},
kaoheguan: {
name: '考核官',
defaultPage: '/pages/kaoheguan/kaoheguan',
tabList: [
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
{ pagePath: 'pages/kaoheguan/kaoheguan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
]
}
};
Component({
data: {
selectedIndex: 0,
tabList: [],
currentRole: 'normal',
showRolePicker: false,
availableRoles: [],
badgeText: ''
},
lifetimes: {
attached() {
this.refresh();
this._badgeListener = (data) => {
if (data.badgeText !== undefined) {
this.setData({ badgeText: data.badgeText });
}
};
app.on('tabBarBadgeChanged', this._badgeListener);
},
detached() {
if (this._badgeListener) {
app.off('tabBarBadgeChanged', this._badgeListener);
}
}
},
pageLifetimes: {
show() {
this.refresh();
const globalBadge = app.globalData.messageManager?.tabBarBadgeText || '';
if (this.data.badgeText !== globalBadge) {
this.setData({ badgeText: globalBadge });
}
this._syncUnreadWithDebounce();
}
},
methods: {
_syncUnreadWithDebounce() {
if (this._syncTimer) clearTimeout(this._syncTimer);
this._syncTimer = setTimeout(() => {
if (app && app.loadConversations) {
app.loadConversations();
}
}, 300);
},
refresh() {
const role = app.globalData.currentRole || 'normal';
let cfg = roleCfg[role];
if (!cfg) return;
// 打手身份下,如果 isJinpai=1悄悄把“我的”页面换成金牌页面
if (role === 'dashou') {
const isJinpai = wx.getStorageSync('isJinpai') === 1;
if (isJinpai) {
cfg = {
...cfg,
tabList: cfg.tabList.map(item => {
if (item.pagePath === 'pages/dashouduan/dashouduan') {
return { ...item, pagePath: 'pages/jinpaids/jinpaids' };
}
return { ...item };
})
};
}
}
const list = cfg.tabList.map((v, i) => ({ ...v, index: i }));
const pages = getCurrentPages();
const curRoute = pages.length ? pages[pages.length - 1].route : '';
let idx = list.findIndex(t => t.pagePath === curRoute);
if (idx === -1) idx = 0;
const avail = this._getAvail();
this.setData({ currentRole: role, tabList: list, selectedIndex: idx, availableRoles: avail });
},
_getAvail() {
const arr = [{ key: 'normal', name: '点单老板' }];
if (wx.getStorageSync('dashoustatus') === 1) arr.push({ key: 'dashou', name: '打手' });
if (wx.getStorageSync('shangjiastatus') === 1) arr.push({ key: 'shangjia', name: '商家' });
if (wx.getStorageSync('guanshistatus') === 1) arr.push({ key: 'guanshi', name: '管事' });
if (wx.getStorageSync('zuzhangstatus') === 1) arr.push({ key: 'zuzhang', name: '组长' });
if (wx.getStorageSync('kaoheguanstatus') === 1) arr.push({ key: 'kaoheguan', name: '考核官' });
return arr;
},
toggleRolePicker() {
this.setData({ showRolePicker: !this.data.showRolePicker });
},
async selectRole(e) {
const newRole = e.currentTarget.dataset.role;
if (newRole === this.data.currentRole) {
this.setData({ showRolePicker: false });
return;
}
this.setData({ showRolePicker: false });
// 1. 立即更新角色并保存
app.globalData.currentRole = newRole;
wx.setStorageSync('currentRole', newRole);
// 2. 🔥 确定跳转页面:所有角色都去“我的”页面
let targetPage = '';
if (newRole === 'dashou') {
// 打手需要根据金牌状态决定跳转
const isJinpai = wx.getStorageSync('isJinpai') === 1;
targetPage = isJinpai ? '/pages/jinpaids/jinpaids' : '/pages/dashouduan/dashouduan';
} else {
// 其他角色直接取 tabList 最后一个页面
const cfg = roleCfg[newRole];
if (cfg && cfg.tabList.length > 0) {
targetPage = '/' + cfg.tabList[cfg.tabList.length - 1].pagePath;
}
}
// 3. 跳转
if (targetPage) {
wx.reLaunch({ url: targetPage });
}
// 4. 后台异步切换连接
app.switchRoleAndReconnect(newRole).catch(err => {
console.error('后台切换连接失败:', err);
});
},
switchTab(e) {
const path = e.currentTarget.dataset.path;
const idx = e.currentTarget.dataset.index;
if (idx === this.data.selectedIndex) return;
wx.redirectTo({ url: '/' + path });
}
}
});

View File

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

View File

@@ -0,0 +1,42 @@
<view class="custom-tab-bar">
<view class="bar-row">
<!-- 左侧椭圆身份按钮 -->
<view class="role-switch-btn" catchtap="toggleRolePicker">
<image class="role-switch-icon" src="/images/role-switch.png" mode="aspectFit" />
<text class="role-switch-text">切换</text>
</view>
<!-- 右侧纯黑闪光胶囊 -->
<view class="tab-capsule">
<block wx:for="{{tabList}}" wx:key="index">
<view class="tab-item {{selectedIndex === index ? 'active' : ''}}"
data-path="{{item.pagePath}}"
data-index="{{index}}"
catchtap="switchTab">
<!-- 🔥 根据页面路径判断是否显示角标,不再写死 index -->
<view class="badge-container" wx:if="{{item.pagePath === 'pages/xiaoxi/xiaoxi' && badgeText}}">
<view class="tab-badge">{{badgeText}}</view>
</view>
<image class="tab-icon" src="{{selectedIndex === index ? item.selectedIconPath : item.iconPath}}" mode="aspectFit" />
<text class="tab-text">{{item.text}}</text>
</view>
</block>
</view>
</view>
<!-- 身份选择弹窗 -->
<view wx:if="{{showRolePicker}}" class="picker-mask" catchtap="toggleRolePicker"></view>
<view wx:if="{{showRolePicker}}" class="picker-panel">
<view class="picker-header">
<text class="picker-title">切换身份</text>
<text class="picker-close" catchtap="toggleRolePicker">✕</text>
</view>
<block wx:for="{{availableRoles}}" wx:key="key">
<view class="role-option {{currentRole === item.key ? 'active' : ''}}"
data-role="{{item.key}}" catchtap="selectRole">
<text>{{item.name}}</text>
<text wx:if="{{currentRole === item.key}}" class="check-mark">✓</text>
</view>
</block>
</view>
</view>

View File

@@ -0,0 +1,165 @@
/* custom-tab-bar/index.wxss - 角标浮于右上角,可超出胶囊 */
.custom-tab-bar {
position: fixed;
bottom: 0; left: 0; right: 0;
z-index: 9999;
display: flex;
justify-content: center;
align-items: flex-end;
padding-bottom: env(safe-area-inset-bottom);
background: transparent;
}
.bar-row {
display: flex;
align-items: center;
margin-bottom: 15rpx;
padding: 0 20rpx;
width: 100%;
box-sizing: border-box;
}
/* 左侧椭圆身份按钮 */
.role-switch-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 110rpx;
height: 100rpx;
border-radius: 50rpx;
background: #000000;
border: 1px solid rgba(255,255,255,0.15);
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.8);
margin-right: 20rpx;
flex-shrink: 0;
}
.role-switch-icon {
width: 40rpx;
height: 40rpx;
margin-bottom: 2rpx;
}
.role-switch-text {
font-size: 22rpx;
font-weight: 600;
color: #00f7ff;
letter-spacing: 1rpx;
}
/* 纯黑闪光胶囊移除overflow保证角标可见 */
.tab-capsule {
flex: 1;
display: flex;
align-items: center;
justify-content: space-around;
height: 100rpx;
background: #000000;
border-radius: 50rpx;
border: 1px solid rgba(255,255,255,0.15);
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.8);
padding: 0 20rpx;
position: relative;
}
.tab-capsule::before {
content: '';
position: absolute;
top: 0; left: -100%;
width: 60%; height: 100%;
background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.25) 20%, rgba(255,255,255,0.35) 50%, rgba(255,255,255,0.25) 80%, transparent 100%);
animation: shine 3s infinite;
z-index: 1;
}
@keyframes shine {
0% { left: -100%; }
25% { left: 150%; }
100% { left: 150%; }
}
.tab-item {
position: relative; /* 角标的定位参考 */
z-index: 2;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 70rpx;
}
.tab-icon {
width: 44rpx;
height: 44rpx;
margin-bottom: 4rpx;
filter: brightness(0) invert(1);
opacity: 0.9;
}
.tab-text {
font-size: 20rpx;
color: #aaa;
z-index: 2;
}
.active .tab-icon {
opacity: 1;
filter: brightness(0) invert(1) drop-shadow(0 0 8rpx #00aaff);
}
.active .tab-text {
color: #00aaff;
font-weight: 500;
}
/* 🔴 角标容器:绝对定位到图标右上角,允许超出 */
.badge-container {
position: absolute;
top: -10rpx; /* 往上提,更接近图标右上角 */
right: -15rpx; /* 往右伸超出tab-item */
z-index: 100;
pointer-events: none; /* 不阻挡点击 */
}
.tab-badge {
min-width: 36rpx;
height: 36rpx;
line-height: 36rpx;
padding: 0 10rpx;
background: #ff3b30;
color: #fff;
font-size: 22rpx;
font-weight: bold;
border-radius: 36rpx;
text-align: center;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.3);
border: 1rpx solid rgba(255,255,255,0.4);
}
/* 弹窗部分保持不变 */
.picker-mask {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.6);
z-index: 10000;
}
.picker-panel {
position: fixed; bottom: 0; left: 0; right: 0;
background: #000000;
border-radius: 30rpx 30rpx 0 0;
z-index: 10001;
max-height: 70vh;
overflow-y: auto;
}
.picker-header {
display: flex; justify-content: space-between; align-items: center;
padding: 30rpx 40rpx 20rpx;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.picker-title { font-size: 32rpx; color: #00f7ff; }
.picker-close { font-size: 36rpx; color: #aaa; padding: 0 10rpx; }
.role-option {
display: flex; justify-content: space-between; align-items: center;
padding: 28rpx 40rpx;
border-bottom: 1px solid rgba(255,255,255,0.05);
color: #ccc; font-size: 28rpx;
}
.role-option.active { color: #00f7ff; }
.check-mark { font-size: 32rpx; color: #00f7ff; }
page {
padding-bottom: 100rpx;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
miniprogram/images/wode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -0,0 +1,100 @@
// pages/cfss/cfss.js
import request from '../../../utils/request.js'; // 向上4级就是根目录
// 如果有 COS
const app = getApp();
Page({
data: {
// 统计数字(由接口 /yonghu/cffktjhq 返回)
stats: {
fakuan: { total: 0, daijiaona: 0, shensuzhong: 0, yijiaona: 0, yibohui: 0 },
jifen: { total: 0, daichuli: 0, yichuli: 0, daichuli_0: 0, shensuzhong_3: 0, yichufa_1: 0, yibohui_2: 0 }
},
// 当前选中的主标签:'fakuan' 或 'jifen'
activeTab: 'fakuan',
// 子标签状态值
activeFakuanStatus: 1, // 罚款默认展示“待缴纳”
activeJifenStatus: 0, // 积分默认展示“待处理”
// 搜索关键词(订单号)
searchDingdan: '',
// 刷新触发器,传给子组件
trigger: {
fakuan: 0,
jifen: 0
}
},
onLoad() {
this.fetchTongji();
},
onShow() {
// 每次显示页面都重新拉取统计,确保数字最新
this.fetchTongji();
},
// 获取综合统计
async fetchTongji() {
try {
const res = await request({
url: '/yonghu/cffktjhq',
method: 'POST'
});
if (res.statusCode === 200 && res.data.code === 0) {
this.setData({ stats: res.data.data });
}
} catch (error) {
console.error('获取处罚统计失败', error);
}
},
// 切换主Tab罚款 / 积分)
switchMainTab(e) {
const tab = e.currentTarget.dataset.tab;
if (tab === this.data.activeTab) return;
this.setData({ activeTab: tab });
},
// 切换罚款状态子标签
switchFakuanStatus(e) {
const status = Number(e.currentTarget.dataset.status);
if (status === this.data.activeFakuanStatus) return;
this.setData({ activeFakuanStatus: status });
this.bumpTrigger('fakuan'); // 通知罚款列表组件刷新
},
// 切换积分状态子标签
switchJifenStatus(e) {
const status = Number(e.currentTarget.dataset.status);
if (status === this.data.activeJifenStatus) return;
this.setData({ activeJifenStatus: status });
this.bumpTrigger('jifen'); // 通知积分列表组件刷新
},
// 搜索输入
onSearchInput(e) {
this.setData({ searchDingdan: e.detail.value });
},
// 执行搜索(按键盘搜索按钮或点击搜索图标)
onSearchConfirm() {
this.bumpTrigger(this.data.activeTab);
},
// 清空搜索关键字
clearSearch() {
this.setData({ searchDingdan: '' });
this.bumpTrigger(this.data.activeTab);
},
// 递增触发器,子组件通过 observer 响应
bumpTrigger(tab) {
const key = `trigger.${tab}`;
this.setData({ [key]: this.data.trigger[tab] + 1 });
}
});

View File

@@ -0,0 +1,10 @@
{
"usingComponents": {
"fakuan-list": "/pages/cfss/components/fakuan-list/fakuan-list",
"jifen-list": "/pages/cfss/components/jifen-list/jifen-list"
},
"navigationBarTitleText": "处罚中心",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#f7f8fa"
}

View File

@@ -0,0 +1,68 @@
<!-- pages/cfss/cfss.wxml -->
<view class="page-container">
<!-- 统计卡片 -->
<view class="stats-card">
<view class="stats-number-row">
<view class="number-block">
<text class="big-num green">{{ stats.fakuan.daijiaona }}</text>
<text class="num-label">待缴罚款</text>
</view>
<view class="number-block">
<text class="big-num orange">{{ stats.jifen.daichuli }}</text>
<text class="num-label">待处理积分</text>
</view>
</view>
<view class="stats-detail-row">
<text>罚款:已缴{{ stats.fakuan.yijiaona }} · 申诉中{{ stats.fakuan.shensuzhong }} · 成功{{ stats.fakuan.yibohui }}</text>
<text>积分:已处理{{ stats.jifen.yichuli }}</text>
</view>
</view>
<!-- 主Tab -->
<view class="tab-row">
<view class="tab-item {{ activeTab === 'fakuan' ? 'active' : '' }}" bindtap="switchMainTab" data-tab="fakuan">
罚款
</view>
<view class="tab-item {{ activeTab === 'jifen' ? 'active' : '' }}" bindtap="switchMainTab" data-tab="jifen">
积分处罚
</view>
</view>
<!-- 子状态标签(横向滑动) -->
<block wx:if="{{ activeTab === 'fakuan' }}">
<scroll-view class="sub-tabs" scroll-x enable-flex>
<view class="sub-tag {{ activeFakuanStatus === 1 ? 'sub-active' : '' }}" bindtap="switchFakuanStatus" data-status="1">待缴纳 {{ stats.fakuan.daijiaona }}</view>
<view class="sub-tag {{ activeFakuanStatus === 3 ? 'sub-active' : '' }}" bindtap="switchFakuanStatus" data-status="3">申诉中 {{ stats.fakuan.shensuzhong }}</view>
<view class="sub-tag {{ activeFakuanStatus === 2 ? 'sub-active' : '' }}" bindtap="switchFakuanStatus" data-status="2">已缴纳 {{ stats.fakuan.yijiaona }}</view>
<view class="sub-tag {{ activeFakuanStatus === 4 ? 'sub-active' : '' }}" bindtap="switchFakuanStatus" data-status="4">申诉成功 {{ stats.fakuan.yibohui }}</view>
</scroll-view>
</block>
<block wx:else>
<scroll-view class="sub-tabs" scroll-x enable-flex>
<view class="sub-tag {{ activeJifenStatus === 0 ? 'sub-active' : '' }}" bindtap="switchJifenStatus" data-status="0">待处理 {{ stats.jifen.daichuli }}</view>
<view class="sub-tag {{ activeJifenStatus === 1 ? 'sub-active' : '' }}" bindtap="switchJifenStatus" data-status="1">已处理 {{ stats.jifen.yichuli }}</view>
</scroll-view>
</block>
<!-- 搜索栏 -->
<view class="search-bar">
<input class="search-input" placeholder="输入订单号搜索" value="{{ searchDingdan }}" bindinput="onSearchInput" bindconfirm="onSearchConfirm" confirm-type="search"/>
<view class="search-btn" bindtap="onSearchConfirm">搜索</view>
<view wx:if="{{ searchDingdan }}" class="clear-btn" bindtap="clearSearch">✕</view>
</view>
<!-- 列表区域(动态显示对应组件) -->
<view wx:if="{{ activeTab === 'fakuan' }}" class="list-area">
<fakuan-list
status="{{ activeFakuanStatus }}"
searchDingdan="{{ searchDingdan }}"
trigger="{{ trigger.fakuan }}"
/>
</view>
<view wx:else class="list-area">
<jifen-list
status="{{ activeJifenStatus }}"
trigger="{{ trigger.jifen }}"
/>
</view>
</view>

View File

@@ -0,0 +1,121 @@
/* pages/cfss/cfss.wxss */
page { background: #f7f8fa; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
.page-container { padding-bottom: 30rpx; }
/* 统计卡片 */
.stats-card {
margin: 24rpx 28rpx;
padding: 30rpx 24rpx;
background: #fff;
border-radius: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
}
.stats-number-row {
display: flex;
justify-content: space-around;
margin-bottom: 20rpx;
}
.number-block {
display: flex;
flex-direction: column;
align-items: center;
}
.big-num {
font-size: 56rpx;
font-weight: 700;
}
.green { color: #2E7D32; }
.orange { color: #E67E22; }
.num-label {
font-size: 24rpx;
color: #888;
margin-top: 8rpx;
}
.stats-detail-row {
border-top: 1rpx solid #eee;
padding-top: 18rpx;
}
.stats-detail-row text {
display: block;
font-size: 22rpx;
color: #aaa;
margin-bottom: 4rpx;
}
/* 主Tab */
.tab-row {
display: flex;
margin: 16rpx 28rpx;
background: #fff;
border-radius: 18rpx;
overflow: hidden;
box-shadow: 0 2rpx 6rpx rgba(0,0,0,0.02);
}
.tab-item {
flex: 1;
text-align: center;
padding: 24rpx 0;
font-size: 28rpx;
color: #666;
transition: 0.2s;
}
.tab-item.active {
color: #2E7D32;
font-weight: 600;
background: #F0F9F0;
}
/* 子状态标签 */
.sub-tabs {
white-space: nowrap;
margin: 8rpx 28rpx 16rpx;
}
.sub-tag {
display: inline-block;
padding: 10rpx 26rpx;
margin-right: 14rpx;
background: #fff;
border-radius: 30rpx;
font-size: 24rpx;
color: #888;
box-shadow: 0 1rpx 4rpx rgba(0,0,0,0.02);
}
.sub-tag.sub-active {
background: #E8F5E9;
color: #2E7D32;
font-weight: 600;
}
/* 搜索栏 */
.search-bar {
display: flex;
align-items: center;
margin: 16rpx 28rpx;
background: #fff;
border-radius: 20rpx;
padding: 12rpx 22rpx;
box-shadow: 0 1rpx 6rpx rgba(0,0,0,0.02);
}
.search-input {
flex: 1;
font-size: 26rpx;
color: #333;
}
.search-btn {
margin-left: 18rpx;
padding: 12rpx 30rpx;
background: #2E7D32;
color: #fff;
border-radius: 16rpx;
font-size: 24rpx;
}
.clear-btn {
margin-left: 16rpx;
font-size: 30rpx;
color: #aaa;
}
.list-area {
margin-top: 10rpx;
}

View File

@@ -0,0 +1,317 @@
// components/fakuan-list/fakuan-list.js
import request from '../../../../utils/request.js';
const app = getApp();
const MEIYE_TIAOSHU = 5;
Component({
properties: {
status: { type: Number, value: 1, observer: 'reload' },
searchDingdan: { type: String, value: '', observer: 'reload' },
trigger: { type: Number, value: 0, observer: 'reload' }
},
data: {
list: [],
page: 1,
hasMore: true,
loading: false,
loadingMore: false,
ossImageUrl: app.globalData.ossImageUrl || '',
// 详情弹窗
showDetail: false,
detailItem: null,
// 申诉弹窗
showShensu: false,
shensuLiyou: '',
shensuTupian: [],
uploading: false,
uploadProgress: { current: 0, total: 0 },
uploadProgressWidth: '0%',
// 支付弹窗
showPay: false,
payFadan: null
},
lifetimes: {
attached() { this.reload(); }
},
methods: {
// ==================== 数据加载 ====================
reload() {
this.setData({ page: 1, list: [], hasMore: true });
this.loadData();
},
async loadData(isLoadMore = false) {
// 防止重复请求
if (this.data.loading || this.data.loadingMore) return;
if (!isLoadMore) {
this.setData({ loading: true });
} else {
// 加载更多时,如果后端已经告知没有更多数据,不发送请求
if (!this.data.hasMore) {
wx.showToast({ title: '没有更多了', icon: 'none', duration: 1000 });
return;
}
this.setData({ loadingMore: true });
}
try {
const res = await request({
url: '/yonghu/dsfklbhq',
method: 'POST',
data: {
page: this.data.page,
page_size: MEIYE_TIAOSHU,
zhuangtai: this.data.status,
sousuo_dingdan_id: this.data.searchDingdan
}
});
if (res.statusCode === 200 && res.data.code === 0) {
const data = res.data.data;
const newList = isLoadMore ? this.data.list.concat(data.list || []) : (data.list || []);
this.setData({
list: newList,
hasMore: data.has_more || false, // 以后端返回为准
page: isLoadMore ? this.data.page + 1 : 2, // 首次加载后页码为2
loading: false,
loadingMore: false
});
} else {
wx.showToast({ title: res.data?.msg || '加载失败', icon: 'none' });
this.setData({ loading: false, loadingMore: false });
}
} catch (e) {
wx.showToast({ title: '网络请求失败', icon: 'none' });
this.setData({ loading: false, loadingMore: false });
}
},
loadMore() {
if (this.data.list.length === 0) return;
this.loadData(true);
},
// ==================== 详情弹窗 ====================
openDetail(e) {
const item = e.currentTarget.dataset.item;
this.setData({ showDetail: true, detailItem: item });
},
closeDetail() {
this.setData({ showDetail: false, detailItem: null });
},
// ==================== 申诉逻辑 ====================
openShensu() {
const item = this.data.detailItem;
if (item.bohuiliyou) {
wx.showToast({ title: '该罚单申诉已被驳回', icon: 'none' });
return;
}
const existingImgs = (item.shensu_tupian || []).map(url => this.getFullUrl(url));
this.setData({
showShensu: true,
shensuLiyou: item.shensuliyou || '',
shensuTupian: existingImgs,
uploading: false,
uploadProgress: { current: 0, total: 0 },
uploadProgressWidth: '0%'
});
},
closeShensu() {
this.setData({ showShensu: false });
},
onShensuInput(e) {
this.setData({ shensuLiyou: e.detail.value.slice(0, 500) });
},
chooseImage() {
const remain = 9 - this.data.shensuTupian.length;
if (remain <= 0) return;
wx.chooseMedia({
count: remain,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: (res) => {
const newImgs = [...this.data.shensuTupian, ...res.tempFiles.map(f => f.tempFilePath)];
this.setData({ shensuTupian: newImgs.slice(0, 9) });
}
});
},
deleteShensuImg(e) {
const arr = this.data.shensuTupian;
arr.splice(e.currentTarget.dataset.index, 1);
this.setData({ shensuTupian: arr });
},
previewImage(e) {
wx.previewImage({ current: e.currentTarget.dataset.url, urls: [e.currentTarget.dataset.url] });
},
previewLocalImg(e) {
const idx = e.currentTarget.dataset.index;
wx.previewImage({ current: this.data.shensuTupian[idx], urls: this.data.shensuTupian });
},
// ==================== 提交流程 ====================
async submitShensu() {
if (!this.data.shensuLiyou.trim()) {
wx.showToast({ title: '请输入申诉理由', icon: 'none' });
return;
}
wx.showLoading({ title: '提交中...' });
try {
const newLocalPaths = this.data.shensuTupian.filter(path => !path.startsWith('http'));
let finalUrls = [];
if (newLocalPaths.length > 0) {
finalUrls = await this.piliangShangchuanShensuTupian(newLocalPaths);
if (!finalUrls) {
wx.hideLoading();
return;
}
}
const oldRemoteRelative = this.data.shensuTupian
.filter(path => path.startsWith('http'))
.map(url => url.replace(this.data.ossImageUrl, '').replace(/^\//, ''));
const allUrls = oldRemoteRelative.concat(finalUrls);
await this.doSubmitShensu(allUrls);
} catch (err) {
console.error('提交申诉失败', err);
wx.showToast({ title: err.message || '提交失败', icon: 'none' });
} finally {
wx.hideLoading();
}
},
async doSubmitShensu(tupianUrls) {
const res = await request({
url: '/yonghu/fkss',
method: 'POST',
data: {
fadan_id: this.data.detailItem.id,
shensu_liyou: this.data.shensuLiyou,
shensu_tupian_urls: tupianUrls
}
});
if (res.statusCode === 200 && res.data.code === 0) {
wx.showToast({ title: '申诉已提交', icon: 'success' });
this.setData({ showShensu: false, showDetail: false });
this.reload();
} else {
throw new Error(res.data?.msg || '提交失败');
}
},
// ==================== 图片上传到COS ====================
async piliangShangchuanShensuTupian(localPaths) {
const total = localPaths.length;
if (!total) return [];
this.setData({ uploading: true, uploadProgress: { current: 0, total: total }, uploadProgressWidth: '0%' });
let tokenData;
try {
const credRes = await request({
url: '/dingdan/dsscpz',
method: 'POST',
data: {
dingdan_id: this.data.detailItem.guanliandingdan_id || '',
fadan_id: this.data.detailItem.id,
yongtu: 'fakuan'
}
});
if (credRes.data.code !== 0) throw new Error(credRes.data.msg || '凭证获取失败');
tokenData = credRes.data.data;
} catch (e) {
wx.showToast({ title: '获取上传凭证失败', icon: 'none' });
this.setData({ uploading: false });
return null;
}
const COS = require('../../../../utils/cos-wx-sdk-v5.js');
const credentials = tokenData.credentials || tokenData;
const cos = new COS({
SimpleUploadMethod: 'putObject',
getAuthorization: (options, callback) => {
callback({
TmpSecretId: credentials.tmpSecretId,
TmpSecretKey: credentials.tmpSecretKey,
SecurityToken: credentials.sessionToken || '',
StartTime: tokenData.startTime,
ExpiredTime: tokenData.expiredTime
});
}
});
const bucket = tokenData.bucket || 'julebu-1361527063';
const region = tokenData.region || 'ap-shanghai';
const yonghuid = app.globalData.userInfo?.yonghuid || wx.getStorageSync('uid') || '0000000';
const fadanId = this.data.detailItem.id;
const keys = [];
for (let i = 0; i < total; i++) {
keys.push(`fakuan/dashoufakuan/dashoufakuanshensu/${yonghuid}_${fadanId}_${Date.now() + i}.jpg`);
}
for (let i = 0; i < total; i++) {
try {
await new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => resolve(), 2000);
cos.uploadFile({
Bucket: bucket,
Region: region,
Key: keys[i],
FilePath: localPaths[i],
onProgress: (info) => {
if (Math.round(info.percent * 100) === 100) {
clearTimeout(timeoutId);
resolve();
}
}
}, (err) => {
clearTimeout(timeoutId);
if (err) reject(err);
else resolve();
});
});
} catch (err) {
wx.showToast({ title: `${i + 1} 张上传失败`, icon: 'none' });
this.setData({ uploading: false });
return null;
}
const done = i + 1;
this.setData({
uploadProgress: { current: done, total: total },
uploadProgressWidth: `${((done / total) * 100).toFixed(0)}%`
});
}
this.setData({ uploading: false });
return keys;
},
// ==================== 缴纳支付 ====================
startPay() {
this.setData({
showDetail: false,
showPay: true,
payFadan: this.data.detailItem
});
},
onPayClose() {
this.setData({ showPay: false, payFadan: null });
},
onPaySuccess() {
this.setData({ showPay: false, payFadan: null });
this.reload();
},
// ==================== 工具方法 ====================
getFullUrl(url) {
if (!url) return '';
if (url.startsWith('http')) return url;
return this.data.ossImageUrl + url;
}
}
});

View File

@@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"fakuan-pay": "/pages/cfss/components/fakuan-pay/fakuan-pay"
}
}

View File

@@ -0,0 +1,116 @@
<!-- components/fakuan-list/fakuan-list.wxml -->
<view class="container">
<scroll-view scroll-y class="scroll" bindscrolltolower="loadMore">
<view wx:if="{{ loading && list.length === 0 }}" class="tip">加载中...</view>
<block wx:for="{{ list }}" wx:key="id">
<view class="card" bindtap="openDetail" data-item="{{ item }}">
<view class="card-head">
<text class="time">{{ item.create_time }}</text>
<text class="tag {{ item.status_class }}">{{ item.display_status }}</text>
</view>
<text class="info">金额:¥{{ item.fakuanjine }}</text>
<text class="info">理由:{{ item.chufaliyou }}</text>
<text class="info" wx:if="{{ item.guanliandingdan_id }}">订单:{{ item.guanliandingdan_id }}</text>
</view>
</block>
<!-- ✅ 按钮始终显示,只要列表不为空且没有正在加载 -->
<view wx:if="{{ list.length > 0 && !loadingMore }}" class="load-more-btn" bindtap="loadMore">
<text>点击获取更多</text>
</view>
<view wx:if="{{ loadingMore }}" class="tip">加载中...</view>
<view wx:if="{{ !hasMore && list.length > 0 }}" class="tip">—— 没有更多了 ——</view>
<view wx:if="{{ !loading && list.length === 0 }}" class="empty">暂无罚款记录</view>
</scroll-view>
<!-- 详情弹窗 -->
<view wx:if="{{ showDetail }}" class="modal-mask">
<view class="modal-panel">
<view class="modal-head">
<text class="modal-title">罚款详情</text>
<view class="modal-close" bindtap="closeDetail">✕</view>
</view>
<scroll-view scroll-y class="modal-body">
<view class="row"><text class="lbl">罚款金额</text><text class="val strong">¥{{ detailItem.fakuanjine }}</text></view>
<view class="row"><text class="lbl">当前状态</text><text class="tag {{ detailItem.status_class }}">{{ detailItem.display_status }}</text></view>
<view class="row"><text class="lbl">处罚理由</text><text class="val">{{ detailItem.chufaliyou }}</text></view>
<view wx:if="{{ detailItem.guanliandingdan_id }}" class="row"><text class="lbl">关联订单</text><text class="val">{{ detailItem.guanliandingdan_id }}</text></view>
<view wx:if="{{ detailItem.bohuiliyou }}" class="row"><text class="lbl">驳回理由</text><text class="val">{{ detailItem.bohuiliyou }}</text></view>
<view wx:if="{{ detailItem.shensuliyou }}" class="row"><text class="lbl">申诉理由</text><text class="val">{{ detailItem.shensuliyou }}</text></view>
<!-- 证据图片 -->
<view wx:if="{{ detailItem.zhengju_tupian && detailItem.zhengju_tupian.length }}" class="section">
<text class="sec-title">证据图片</text>
<view class="img-grid">
<block wx:for="{{ detailItem.zhengju_tupian }}" wx:key="index">
<view class="img-box" bindtap="previewImage" data-url="{{ ossImageUrl + item }}">
<image class="img" src="{{ ossImageUrl + item }}" mode="aspectFill" />
</view>
</block>
</view>
</view>
<!-- 申诉图片 -->
<view wx:if="{{ detailItem.shensu_tupian && detailItem.shensu_tupian.length }}" class="section">
<text class="sec-title">申诉图片</text>
<view class="img-grid">
<block wx:for="{{ detailItem.shensu_tupian }}" wx:key="index">
<view class="img-box" bindtap="previewImage" data-url="{{ ossImageUrl + item }}">
<image class="img" src="{{ ossImageUrl + item }}" mode="aspectFill" />
</view>
</block>
</view>
</view>
</scroll-view>
<view class="modal-foot">
<view wx:if="{{ detailItem.zhuangtai === 1 && !detailItem.bohuiliyou }}" class="btn primary" bindtap="openShensu">申诉</view>
<view wx:if="{{ detailItem.zhuangtai === 1 || detailItem.zhuangtai === 3 }}" class="btn primary" bindtap="startPay">立即缴纳</view>
<view class="btn default" bindtap="closeDetail">关闭</view>
</view>
</view>
</view>
<!-- 申诉弹窗 -->
<view wx:if="{{ showShensu }}" class="modal-mask">
<view class="modal-panel">
<view class="modal-head">
<text class="modal-title">提交申诉</text>
<view class="modal-close" bindtap="closeShensu">✕</view>
</view>
<scroll-view scroll-y class="modal-body">
<view class="form-group">
<text class="sec-title">申诉理由</text>
<textarea class="textarea" placeholder="请输入申诉理由" value="{{ shensuLiyou }}" bindinput="onShensuInput" maxlength="500" />
<text class="word-count">{{ shensuLiyou.length }}/500</text>
</view>
<view class="form-group">
<text class="sec-title">上传凭证最多9张</text>
<view class="img-grid">
<block wx:for="{{ shensuTupian }}" wx:key="index">
<view class="img-box">
<image class="img" src="{{ item }}" mode="aspectFill" bindtap="previewLocalImg" data-index="{{ index }}" />
<view class="img-del" catchtap="deleteShensuImg" data-index="{{ index }}">✕</view>
</view>
</block>
<view wx:if="{{ shensuTupian.length < 9 }}" class="img-add" bindtap="chooseImage">+</view>
</view>
<view wx:if="{{ uploading }}" class="progress-box">
<view class="progress-bar"><view class="progress-inner" style="width: {{ uploadProgressWidth }}"></view></view>
<text class="progress-text">{{ uploadProgress.current }}/{{ uploadProgress.total }}</text>
</view>
</view>
</scroll-view>
<view class="modal-foot">
<view class="btn default" bindtap="closeShensu">取消</view>
<view class="btn primary" bindtap="submitShensu">提交申诉</view>
</view>
</view>
</view>
<!-- 支付组件(缴纳罚款) -->
<fakuan-pay
visible="{{ showPay }}"
fadan="{{ payFadan }}"
bind:close="onPayClose"
bind:success="onPaySuccess"
/>
</view>

View File

@@ -0,0 +1,77 @@
/* 完全复用积分组件的样式 */
page { background: #f5f5f5; }
.container { width: 100%; box-sizing: border-box; display: flex; flex-direction: column; }
.scroll { width: 100%; box-sizing: border-box; padding: 20rpx 24rpx; }
.tip { text-align: center; color: #999; font-size: 24rpx; padding: 30rpx 0; }
.empty { display: flex; flex-direction: column; align-items: center; padding-top: 200rpx; color: #999; font-size: 28rpx; }
.card { width: 100%; box-sizing: border-box; background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; }
.card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12rpx; }
.time { font-size: 24rpx; color: #999; }
.tag { font-size: 20rpx; padding: 2rpx 14rpx; border-radius: 16rpx; display: inline-block; }
/* 状态标签颜色(与积分组件完全相同) */
.zhuangtai-daijiaona { background: #FFF3E0; color: #E65100; } /* 待缴纳 */
.zhuangtai-yijiaona { background: #E8F5E9; color: #2E7D32; } /* 已缴纳 */
.zhuangtai-shensuzhong { background: #E3F2FD; color: #1565C0; } /* 申诉中 */
.zhuangtai-yibohui { background: #F3E5F5; color: #7B1FA2; } /* 申诉成功 */
.info { display: block; font-size: 26rpx; color: #333; margin-top: 8rpx; }
.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 999; display: flex; align-items: flex-end; justify-content: center; }
.modal-panel { width: 100%; box-sizing: border-box; max-height: 85vh; background: #fff; border-radius: 24rpx 24rpx 0 0; display: flex; flex-direction: column; overflow: hidden; }
.modal-head { display: flex; justify-content: space-between; align-items: center; padding: 28rpx 30rpx 16rpx; border-bottom: 1px solid #eee; }
.modal-title { font-size: 32rpx; font-weight: 600; }
.modal-close { width: 44rpx; height: 44rpx; border-radius: 50%; background: #f5f5f5; display: flex; align-items: center; justify-content: center; font-size: 26rpx; color: #888; }
.modal-body { width: 100%; box-sizing: border-box; padding: 20rpx 30rpx; overflow-y: auto; }
.row { display: flex; margin-bottom: 16rpx; }
.lbl { width: 150rpx; font-size: 26rpx; color: #888; flex-shrink: 0; }
.val { flex: 1; font-size: 26rpx; color: #333; word-break: break-all; }
.strong { font-weight: 600; color: #E53935; }
.section { margin: 20rpx 0; }
.sec-title { font-size: 26rpx; color: #888; margin-bottom: 12rpx; display: block; }
.img-grid { width: 100%; box-sizing: border-box; display: flex; flex-wrap: wrap; gap: 12rpx; }
.img-box { position: relative; width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); }
.img-box .img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 10rpx; background: #f0f0f0; }
.img-del { position: absolute; top: -6rpx; right: -6rpx; width: 34rpx; height: 34rpx; background: #e53935; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 18rpx; z-index: 2; }
.img-add { width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); border: 2rpx dashed #ccc; border-radius: 10rpx; display: flex; align-items: center; justify-content: center; font-size: 48rpx; color: #aaa; position: relative; box-sizing: border-box; }
.form-group { margin-bottom: 24rpx; }
.textarea { width: 100%; box-sizing: border-box; height: 180rpx; background: #f9f9f9; border-radius: 10rpx; padding: 16rpx; font-size: 26rpx; border: 1px solid #eee; }
.word-count { text-align: right; font-size: 22rpx; color: #bbb; margin-top: 6rpx; }
.progress-box { margin-top: 12rpx; }
.progress-bar { height: 10rpx; background: #eee; border-radius: 5rpx; }
.progress-inner { height: 100%; background: #2e7d32; border-radius: 5rpx; }
.progress-text { font-size: 22rpx; color: #2e7d32; text-align: right; margin-top: 4rpx; }
.modal-foot { width: 100%; box-sizing: border-box; display: flex; padding: 16rpx 30rpx 30rpx; gap: 20rpx; border-top: 1px solid #eee; }
.btn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; }
.primary { background: #2e7d32; color: #fff; }
.default { background: #f0f0f0; color: #555; }
.btn:active { opacity: 0.8; }
/* ===== 获取更多按钮 ===== */
.load-more-btn {
width: 80%;
margin: 20rpx auto 30rpx;
padding: 22rpx 0;
text-align: center;
background: #ffffff;
border: 2rpx solid #e0e0e0;
border-radius: 20rpx;
color: #555;
font-size: 26rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.02);
transition: all 0.15s;
}
.load-more-btn:active {
background: #f5f5f5;
border-color: #2e7d32;
color: #2e7d32;
}

View File

@@ -0,0 +1,178 @@
// components/fakuan-list/fakuan-pay/fakuan-pay.js
import request from '../../../../utils/request.js'; // 路径保持您原来的样子
Component({
properties: {
visible: { type: Boolean, value: false, observer: 'onVisibleChange' },
fadan: { type: Object, value: null } // { id, fakuanjine }
},
data: {
step: 'method', // method / balance / confirm / loading
balanceOptions: [],
selectedBalance: null,
loadingText: '',
payLoading: false,
dingdanid: '' // 微信支付内部订单ID
},
methods: {
onVisibleChange(val) {
if (val) {
// 每次打开重置为支付方式选择
this.setData({ step: 'method', balanceOptions: [], selectedBalance: null });
}
},
close() {
this.setData({ visible: false });
this.triggerEvent('close');
},
// 选择支付方式
async chooseMethod(e) {
const method = e.currentTarget.dataset.method;
if (method === 'wx') {
this.wxPay();
} else if (method === 'balance') {
this.fetchBalanceOptions();
}
},
// ========== 微信支付流程 ==========
async wxPay() {
this.setData({ step: 'loading', loadingText: '发起支付中...' });
try {
const res = await request({
url: '/yonghu/fkjn',
method: 'POST',
data: { fadan_id: this.properties.fadan.id }
});
if (res.data.code !== 200) throw new Error(res.data.msg || '下单失败');
// 🔥 唯一修改点:后端返回格式 { code:200, payParams:{...}, dingdanid:'...' }
const payParams = res.data.payParams;
const dingdanid = res.data.dingdanid;
this.setData({ dingdanid });
await this.requestWxPayment(payParams);
} catch (e) {
this.setData({ step: 'method' });
wx.showToast({ title: e.message, icon: 'none' });
}
},
async requestWxPayment(payParams) {
return new Promise((resolve, reject) => {
if (!payParams || !payParams.timeStamp || !payParams.paySign) {
wx.showToast({ title: '支付参数错误', icon: 'none' });
reject(new Error('支付参数错误'));
return;
}
wx.requestPayment({
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType || 'MD5',
paySign: payParams.paySign,
success: () => {
this.pollPayStatus();
resolve();
},
fail: (err) => {
this.setData({ step: 'method' });
this.notifyPayFail();
reject(err);
}
});
});
},
async pollPayStatus() {
this.setData({ step: 'loading', loadingText: '确认支付结果...' });
for (let i = 0; i < 10; i++) {
try {
const res = await request({
url: '/yonghu/fkzffc',
method: 'POST',
data: { dingdanid: this.data.dingdanid }
});
if (res.data.code === 200) {
this.triggerEvent('success');
this.close();
wx.showToast({ title: '缴纳成功', icon: 'success' });
return;
}
} catch (e) {}
await new Promise(r => setTimeout(r, 2000));
}
this.setData({ step: 'method' });
wx.showToast({ title: '支付确认超时', icon: 'none' });
},
async notifyPayFail() {
try {
await request({
url: '/yonghu/fkzfsb',
method: 'POST',
data: { dingdanid: this.data.dingdanid }
});
} catch (e) {}
},
// ========== 佣金抵扣流程 ==========
async fetchBalanceOptions() {
this.setData({ step: 'loading', loadingText: '获取可抵扣身份...' });
try {
const res = await request({
url: '/shangpin/czhqdy',
method: 'POST',
data: {
leixing: 4,
fadan_id: this.properties.fadan.id,
jine: this.properties.fadan.fakuanjine
}
});
if (res.data.code !== 200) throw new Error(res.data.msg || '获取失败');
this.setData({ balanceOptions: res.data.data, step: 'balance' });
} catch (e) {
this.setData({ step: 'method' });
wx.showToast({ title: e.message, icon: 'none' });
}
},
selectBalance(e) {
const id = e.currentTarget.dataset.id;
const info = this.data.balanceOptions.find(opt => opt.id === id);
if (!info) return;
this.setData({ selectedBalance: info, step: 'confirm' });
},
backToMethod() {
this.setData({ step: 'method' });
},
async confirmBalance() {
if (!this.data.selectedBalance) return;
this.setData({ step: 'loading', loadingText: '抵扣扣款中...' });
try {
const res = await request({
url: '/shangpin/dsqrgmdh',
method: 'POST',
data: {
leixing: 4,
shenfen_id: this.data.selectedBalance.id,
fadan_id: this.properties.fadan.id
}
});
if (res.data.code !== 200) throw new Error(res.data.msg || '抵扣失败');
this.triggerEvent('success');
this.close();
wx.showToast({ title: '缴纳成功', icon: 'success' });
} catch (e) {
this.setData({ step: 'method' });
wx.showToast({ title: e.message, icon: 'none' });
}
}
}
});

View File

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

View File

@@ -0,0 +1,61 @@
<!-- 支付组件模板 -->
<view wx:if="{{ visible }}" class="pay-mask" catchtouchmove="prevent">
<!-- 支付方式选择 -->
<view wx:if="{{ step === 'method' }}" class="pay-panel">
<view class="pay-head">
<text class="pay-title">选择支付方式</text>
<view class="pay-close" bindtap="close">✕</view>
</view>
<view class="pay-body">
<view class="method-item" bindtap="chooseMethod" data-method="wx">
<text class="method-icon">💳</text>
<text class="method-name">微信支付</text>
</view>
<view class="method-item" bindtap="chooseMethod" data-method="balance">
<text class="method-icon">💰</text>
<text class="method-name">佣金抵扣</text>
</view>
</view>
</view>
<!-- 余额抵扣身份选择 -->
<view wx:if="{{ step === 'balance' }}" class="pay-panel">
<view class="pay-head">
<text class="pay-title">选择抵扣身份</text>
<view class="pay-close" bindtap="close">✕</view>
</view>
<scroll-view scroll-y class="pay-body">
<view wx:for="{{ balanceOptions }}" wx:key="id" class="balance-item" bindtap="selectBalance" data-id="{{ item.id }}">
<text class="balance-name">{{ item.jieshao }}</text>
<text class="balance-amount">需¥{{ item.xuyao }}</text>
</view>
</scroll-view>
</view>
<!-- 余额抵扣确认 -->
<view wx:if="{{ step === 'confirm' }}" class="pay-panel">
<view class="pay-head">
<text class="pay-title">确认抵扣</text>
<view class="pay-close" bindtap="close">✕</view>
</view>
<view class="pay-body">
<view class="confirm-row">
<text>抵扣身份:{{ selectedBalance.jieshao }}</text>
</view>
<view class="confirm-row">
<text>需扣除:¥{{ selectedBalance.xuyao }}</text>
</view>
<text class="confirm-tip">确认后将从您的佣金/分红中扣除</text>
</view>
<view class="pay-foot">
<view class="btn default" bindtap="backToMethod">取消</view>
<view class="btn primary" bindtap="confirmBalance">确认</view>
</view>
</view>
<!-- 支付加载动画 -->
<view wx:if="{{ step === 'loading' }}" class="pay-mask loading-box">
<view class="spinner"></view>
<text class="loading-text">{{ loadingText }}</text>
</view>
</view>

View File

@@ -0,0 +1,59 @@
/* 支付组件样式,与罚款列表统一 */
.pay-mask {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5); z-index: 2000;
display: flex; align-items: flex-end; justify-content: center;
}
.pay-panel {
width: 100%; max-height: 85vh; background: #fff;
border-radius: 24rpx 24rpx 0 0; display: flex; flex-direction: column; overflow: hidden;
}
.pay-head {
display: flex; justify-content: space-between; align-items: center;
padding: 28rpx 30rpx 16rpx; border-bottom: 1px solid #eee;
}
.pay-title { font-size: 32rpx; font-weight: 600; }
.pay-close {
width: 44rpx; height: 44rpx; background: #f5f5f5;
border-radius: 50%; display: flex; align-items: center; justify-content: center;
font-size: 26rpx; color: #888;
}
.pay-body { padding: 20rpx 30rpx; flex: 1; overflow-y: auto; }
.pay-foot {
display: flex; padding: 16rpx 30rpx 30rpx; gap: 20rpx; border-top: 1px solid #eee;
}
.method-item {
display: flex; flex-direction: column; align-items: center;
padding: 40rpx 30rpx; background: #f9f9f9; border-radius: 20rpx; margin-bottom: 20rpx;
}
.method-icon { font-size: 48rpx; margin-bottom: 12rpx; }
.method-name { font-size: 28rpx; color: #333; }
.balance-item {
display: flex; justify-content: space-between; align-items: center;
padding: 24rpx 0; border-bottom: 1rpx solid #f0f0f0;
}
.balance-name { font-size: 28rpx; color: #333; }
.balance-amount { font-size: 28rpx; color: #E53935; }
.confirm-row { font-size: 28rpx; margin-bottom: 16rpx; }
.confirm-tip { font-size: 24rpx; color: #888; display: block; margin-top: 20rpx; }
.loading-box {
display: flex; flex-direction: column; align-items: center; justify-content: center;
background: rgba(0,0,0,0.6);
}
.spinner {
width: 60rpx; height: 60rpx;
border: 4rpx solid rgba(255,255,255,0.3);
border-top-color: #fff; border-radius: 50%;
animation: spin 1s linear infinite; margin-bottom: 20rpx;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loading-text { color: #fff; font-size: 26rpx; }
.btn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; }
.primary { background: #2e7d32; color: #fff; }
.default { background: #f0f0f0; color: #555; }
.btn:active { opacity: 0.8; }

View File

@@ -0,0 +1,220 @@
// components/jifen-list/jifen-list.js
import request from '../../../../utils/request.js';
const app = getApp();
const MEIYE_TIAOSHU = 5;
Component({
properties: {
status: { type: Number, value: 0, observer: 'reload' },
trigger: { type: Number, value: 0, observer: 'reload' }
},
data: {
chufaList: [],
dangqianye: 1,
haiyougengduo: true,
jiazhaozhong: false,
jiazhaigengduo: false,
ossImageUrl: app.globalData.ossImageUrl || '',
showXiangqing: false,
xuanzhongChufa: {},
showShensuModal: false,
shensuLiyou: '',
shensuTupian: [],
shangchuanJindu: 0,
shangchuanZongshu: 0,
jinduWidth: '0%',
isShangchuanzhong: false,
fangdouTimer: null
},
lifetimes: {
attached() { this.reload(); },
detached() { if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer); }
},
methods: {
reload() {
this.setData({ dangqianye: 1, chufaList: [], haiyougengduo: true });
this.loadJifenList();
},
async loadJifenList(isLoadMore = false) {
if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return;
if (isLoadMore && !this.data.haiyougengduo) return;
if (isLoadMore) this.setData({ jiazhaigengduo: true });
else this.setData({ jiazhaozhong: true });
try {
const params = { page: this.data.dangqianye, page_size: MEIYE_TIAOSHU, zhuangtai: this.data.status };
const res = await request({ url: '/yonghu/dshqcfjl', method: 'POST', data: params });
if (res.statusCode === 200 && res.data.code === 0) {
const data = res.data.data;
if (data.list && Array.isArray(data.list)) {
const processedList = data.list.map(item => {
let displayTime = '--';
if (item.create_time) displayTime = item.create_time;
let displayStatus = '未知状态', statusClass = 'zhuangtai-weizhi';
const n = Number(item.sqzhuangtai);
if (!isNaN(n)) {
if (n === 0) { displayStatus = '待处理'; statusClass = 'zhuangtai-daichuli'; }
else if (n === 1) { displayStatus = '已处罚'; statusClass = 'zhuangtai-yichufa'; }
else if (n === 2) { displayStatus = '处罚已撤销'; statusClass = 'zhuangtai-yibohui'; }
else if (n === 3) { displayStatus = '申诉中'; statusClass = 'zhuangtai-shensuzhong'; }
}
return {
...item,
display_time: displayTime, display_status: displayStatus, status_class: statusClass,
full_zhengju_tupian: (item.zhengju_tupian || []).map(url => this.getFullImageUrl(url)),
full_shensu_tupian: (item.shensu_tupian || []).map(url => this.getFullImageUrl(url))
};
});
const newList = isLoadMore ? [...this.data.chufaList, ...processedList] : processedList;
this.setData({ chufaList: newList, haiyougengduo: data.has_more === true, dangqianye: this.data.dangqianye + 1 });
} else this.setData({ haiyougengduo: false });
}
} catch (e) { wx.showToast({ title: '加载失败', icon: 'none' }); this.setData({ haiyougengduo: false }); }
finally { if (isLoadMore) this.setData({ jiazhaigengduo: false }); else this.setData({ jiazhaozhong: false }); }
},
onReachBottom() {
if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer);
const timer = setTimeout(() => {
if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return;
if (!this.data.haiyougengduo) return;
this.loadJifenList(true);
}, 300);
this.setData({ fangdouTimer: timer });
},
chakanXiangqing(e) { this.setData({ showXiangqing: true, xuanzhongChufa: e.currentTarget.dataset.item }); },
guanbiXiangqing() { this.setData({ showXiangqing: false, xuanzhongChufa: {} }); },
openShensuModal() {
const { xuanzhongChufa } = this.data;
if (xuanzhongChufa.ssliyou || (xuanzhongChufa.shensu_tupian && xuanzhongChufa.shensu_tupian.length > 0)) {
wx.showToast({ title: '您已经提交过申诉,请等待处理结果', icon: 'none' }); return;
}
this.setData({ showShensuModal: true, shensuLiyou: '', shensuTupian: [], shangchuanJindu: 0, shangchuanZongshu: 0, jinduWidth: '0%', isShangchuanzhong: false });
},
closeShensuModal() { this.setData({ showShensuModal: false }); },
onShensuLiyouInput(e) { this.setData({ shensuLiyou: e.detail.value.slice(0, 500) }); },
chooseShensuTupian() {
if (this.data.isShangchuanzhong) return;
const shengyu = 9 - this.data.shensuTupian.length;
if (shengyu <= 0) { wx.showToast({ title: '最多只能上传9张图片', icon: 'none' }); return; }
wx.chooseMedia({ count: shengyu, mediaType: ['image'], sourceType: ['album', 'camera'], success: (res) => {
const newTupian = [...this.data.shensuTupian, ...res.tempFiles.map(f => f.tempFilePath)];
this.setData({ shensuTupian: newTupian.slice(0, 9) });
}});
},
deleteShensuTupian(e) { const arr = [...this.data.shensuTupian]; arr.splice(e.currentTarget.dataset.index, 1); this.setData({ shensuTupian: arr }); },
yulanShensuTupian(e) {
const idx = e.currentTarget.dataset.index;
const urls = this.data.shensuTupian;
if (!urls[idx]) return;
wx.previewImage({ current: urls[idx], urls });
},
yulanTupian(e) {
const current = e.currentTarget.dataset.url;
const urls = e.currentTarget.dataset.urls || [];
const fullCurrent = current.startsWith('http') ? current : this.getFullImageUrl(current);
const fullUrls = urls.map(url => url.startsWith('http') ? url : this.getFullImageUrl(url));
wx.previewImage({ current: fullCurrent, urls: fullUrls.length ? fullUrls : [fullCurrent] });
},
/* ========== 图片上传(完全照抄原 cfss.js ========== */
// 【仅替换此方法,其余代码一个字别动】
getFullImageUrl(relativeUrl) {
if (!relativeUrl) return '';
if (relativeUrl.startsWith('http')) return relativeUrl;
const ossUrl = this.data.ossImageUrl;
if (!ossUrl) return relativeUrl;
// 确保拼接时只有一个斜杠
const base = ossUrl.replace(/\/+$/, '');
const rel = relativeUrl.replace(/^\/+/, '');
return base + '/' + rel;
},
getFileExtension(filePath) {
const lastDot = filePath.lastIndexOf('.'); if (lastDot === -1) return '.jpg';
const ext = filePath.substring(lastDot).toLowerCase();
return ['.jpg','.jpeg','.png','.gif','.bmp','.webp'].includes(ext) ? ext : '.jpg';
},
async getCOSZhengshu() {
const res = await request({ url: '/dingdan/dsscpz', method: 'POST', data: { dingdan_id: this.data.xuanzhongChufa.dingdan_id || '', yongtu: 'chufa' } });
if (res.statusCode === 200 && res.data.code === 0 && res.data.data) return res.data.data;
throw new Error(res?.data?.msg || '获取上传凭证失败');
},
initCOSClient(tokenData) {
const COS = require('../../../../utils/cos-wx-sdk-v5.js');
const credentials = tokenData.credentials || tokenData;
const bucket = tokenData.bucket || 'julebu-1361527063';
const region = tokenData.region || 'ap-shanghai';
const cos = new COS({ SimpleUploadMethod: 'putObject', getAuthorization: (options, callback) => { callback({ TmpSecretId: credentials.tmpSecretId, TmpSecretKey: credentials.tmpSecretKey, SecurityToken: credentials.sessionToken || '', StartTime: tokenData.startTime || Math.floor(Date.now() / 1000), ExpiredTime: tokenData.expiredTime || (Math.floor(Date.now() / 1000) + 7200) }); } });
return { cos, bucket, region };
},
shangchuanDanZhangTupian(filePath, cosKey, cosClient, bucket, region) {
return new Promise(resolve => {
const timeoutId = setTimeout(() => resolve({ status: 'optimistic_success', key: cosKey }), 2000);
cosClient.uploadFile({ Bucket: bucket, Region: region, Key: cosKey, FilePath: filePath, onProgress: (info) => { if (Math.round(info.percent * 100) === 100) { clearTimeout(timeoutId); resolve({ status: 'optimistic_success', key: cosKey }); } } });
});
},
async piliangShangchuanShensuTupian() {
const total = this.data.shensuTupian.length;
if (total === 0) return [];
this.setData({ shangchuanZongshu: total, shangchuanJindu: 0, jinduWidth: '0%', isShangchuanzhong: true });
let yonghuid = '';
const uid = wx.getStorageSync('uid'); if (uid) yonghuid = String(uid).padStart(7, '0');
else if (app.globalData.userInfo?.yonghuid) yonghuid = String(app.globalData.userInfo.yonghuid).padStart(7, '0');
if (!yonghuid) { wx.showToast({ title: '请先登录', icon: 'none' }); this.setData({ isShangchuanzhong: false }); return []; }
const preGeneratedUrls = [];
for (let i = 0; i < total; i++) {
const timestamp = Date.now() + i, random = Math.floor(Math.random() * 10000);
preGeneratedUrls.push(`a_long/chfajltp/dssstp/${yonghuid}_${timestamp}_${random}${this.getFileExtension(this.data.shensuTupian[i])}`);
}
let cosClient, bucket, region;
try { const tokenData = await this.getCOSZhengshu(); const c = this.initCOSClient(tokenData); cosClient = c.cos; bucket = c.bucket; region = c.region; }
catch (e) { wx.showToast({ title: '上传初始化失败', icon: 'none' }); this.setData({ isShangchuanzhong: false }); return []; }
for (let i = 0; i < total; i++) {
await this.shangchuanDanZhangTupian(this.data.shensuTupian[i], preGeneratedUrls[i], cosClient, bucket, region);
const done = i + 1;
this.setData({ shangchuanJindu: done, jinduWidth: `${((done / total) * 100).toFixed(0)}%` });
}
this.setData({ isShangchuanzhong: false });
return preGeneratedUrls;
},
async submitShensu() {
if (!this.data.shensuLiyou.trim()) { wx.showToast({ title: '请输入申诉理由', icon: 'none' }); return; }
if (this.data.shensuTupian.length === 0) { wx.showModal({ title: '提示', content: '未上传任何图片,确定要提交申诉吗?', success: async (res) => { if (res.confirm) await this.tijiaoShensuData([]); } }); return; }
wx.showToast({ title: '开始上传图片...', icon: 'none' });
try {
const urls = await this.piliangShangchuanShensuTupian();
if (!urls || urls.length === 0) throw new Error('图片上传失败');
await this.tijiaoShensuData(urls);
} catch (e) { wx.showToast({ title: e.message || '提交失败', icon: 'none' }); }
},
async tijiaoShensuData(tupianUrls) {
wx.showToast({ title: '提交申诉中...', icon: 'none' });
try {
const res = await request({ url: '/yonghu/dscfss', method: 'POST', data: { chufa_id: this.data.xuanzhongChufa.id, shensu_liyou: this.data.shensuLiyou, shensu_tupian_urls: tupianUrls } });
if (res.statusCode === 200 && res.data.code === 0) {
const newList = this.data.chufaList.map(item => {
if (item.id === this.data.xuanzhongChufa.id) { return { ...item, sqzhuangtai: 3, ssliyou: this.data.shensuLiyou, display_status: '申诉中', status_class: 'zhuangtai-shensuzhong', full_shensu_tupian: tupianUrls.map(url => this.getFullImageUrl(url)) }; }
return item;
});
this.setData({ chufaList: newList, showShensuModal: false, showXiangqing: false });
wx.showToast({ title: '申诉提交成功,请等待处理', icon: 'success' });
} else throw new Error(res.data?.msg || '提交申诉失败');
} catch (e) { wx.showToast({ title: e.message || '提交失败', icon: 'none' }); }
}
}
});

View File

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

View File

@@ -0,0 +1,105 @@
<view class="container">
<scroll-view scroll-y class="scroll" bindscrolltolower="onReachBottom">
<view wx:if="{{ jiazhaozhong && chufaList.length === 0 }}" class="tip">加载中...</view>
<block wx:for="{{ chufaList }}" wx:key="id">
<view class="card" bindtap="chakanXiangqing" data-item="{{ item }}">
<view class="card-head">
<text class="time">{{ item.display_time }}</text>
<text class="tag {{ item.status_class }}">{{ item.display_status }}</text>
</view>
<text class="info">申请人:{{ item.qingqiuid || '--' }}</text>
<text class="info">理由:{{ item.cfliyou || '无' }}</text>
<text class="info" wx:if="{{ item.dingdan_id }}">订单:{{ item.dingdan_id }}</text>
</view>
</block>
<!-- ========== 底部:按钮 / 加载中 / 没有更多 ========== -->
<view wx:if="{{ haiyougengduo && !jiazhaigengduo }}" class="load-more-btn" bindtap="onReachBottom">
<text>点击获取更多</text>
</view>
<view wx:if="{{ jiazhaigengduo }}" class="tip">加载中...</view>
<view wx:if="{{ !haiyougengduo && chufaList.length > 0 }}" class="tip">—— 没有更多了 ——</view>
<view wx:if="{{ !jiazhaozhong && chufaList.length === 0 }}" class="empty">暂无积分处罚记录</view>
</scroll-view>
<!-- 详情弹窗(不动) -->
<view wx:if="{{ showXiangqing }}" class="modal-mask">
<view class="modal-panel">
<view class="modal-head">
<text class="modal-title">处罚详情</text>
<view class="modal-close" bindtap="guanbiXiangqing">✕</view>
</view>
<scroll-view scroll-y class="modal-body">
<view class="row"><text class="lbl">订单编号</text><text class="val">{{ xuanzhongChufa.dingdan_id || '--' }}</text></view>
<view class="row"><text class="lbl">申请人ID</text><text class="val">{{ xuanzhongChufa.qingqiuid || '--' }}</text></view>
<view class="row"><text class="lbl">时间</text><text class="val">{{ xuanzhongChufa.display_time }}</text></view>
<view class="row"><text class="lbl">状态</text><text class="val"><text class="tag {{ xuanzhongChufa.status_class }}">{{ xuanzhongChufa.display_status }}</text></text></view>
<view class="row"><text class="lbl">处罚理由</text><text class="val">{{ xuanzhongChufa.cfliyou || '无' }}</text></view>
<view wx:if="{{ xuanzhongChufa.full_zhengju_tupian.length }}" class="section">
<text class="sec-title">证据图片</text>
<view class="img-grid">
<block wx:for="{{ xuanzhongChufa.full_zhengju_tupian }}" wx:key="index">
<view class="img-box" bindtap="yulanTupian" data-url="{{ item }}" data-urls="{{ xuanzhongChufa.full_zhengju_tupian }}">
<image class="img" src="{{ item }}" mode="aspectFill" />
</view>
</block>
</view>
</view>
<view wx:if="{{ xuanzhongChufa.ssliyou || xuanzhongChufa.full_shensu_tupian.length }}" class="section">
<text class="sec-title">我的申诉</text>
<view wx:if="{{ xuanzhongChufa.ssliyou }}" class="row"><text class="lbl">理由</text><text class="val">{{ xuanzhongChufa.ssliyou }}</text></view>
<view wx:if="{{ xuanzhongChufa.full_shensu_tupian.length }}" class="img-grid">
<block wx:for="{{ xuanzhongChufa.full_shensu_tupian }}" wx:key="index">
<view class="img-box" bindtap="yulanTupian" data-url="{{ item }}" data-urls="{{ xuanzhongChufa.full_shensu_tupian }}">
<image class="img" src="{{ item }}" mode="aspectFill" />
</view>
</block>
</view>
</view>
</scroll-view>
<view class="modal-foot">
<view wx:if="{{ xuanzhongChufa.sqzhuangtai === 0 }}" class="btn primary" bindtap="openShensuModal">申诉</view>
<view class="btn default" bindtap="guanbiXiangqing">关闭</view>
</view>
</view>
</view>
<!-- 申诉弹窗(不动) -->
<view wx:if="{{ showShensuModal }}" class="modal-mask">
<view class="modal-panel">
<view class="modal-head">
<text class="modal-title">提交申诉</text>
<view class="modal-close" bindtap="closeShensuModal">✕</view>
</view>
<scroll-view scroll-y class="modal-body">
<view class="form-group">
<text class="sec-title">申诉理由</text>
<textarea class="textarea" placeholder="请输入申诉理由最多500字" value="{{ shensuLiyou }}" bindinput="onShensuLiyouInput" maxlength="500" />
<text class="word-count">{{ shensuLiyou.length }}/500</text>
</view>
<view class="form-group">
<text class="sec-title">上传凭证最多9张</text>
<view class="img-grid">
<block wx:for="{{ shensuTupian }}" wx:key="index">
<view class="img-box">
<image class="img" src="{{ item }}" mode="aspectFill" bindtap="yulanShensuTupian" data-index="{{ index }}" />
<view class="img-del" catchtap="deleteShensuTupian" data-index="{{ index }}">✕</view>
</view>
</block>
<view wx:if="{{ shensuTupian.length < 9 }}" class="img-add" bindtap="chooseShensuTupian">+</view>
</view>
<view wx:if="{{ shangchuanZongshu > 0 }}" class="progress-box">
<view class="progress-bar"><view class="progress-inner" style="width: {{ jinduWidth }}"></view></view>
<text class="progress-text">{{ shangchuanJindu }}/{{ shangchuanZongshu }}</text>
</view>
</view>
</scroll-view>
<view class="modal-foot">
<view class="btn default" bindtap="closeShensuModal">取消</view>
<view class="btn primary" bindtap="submitShensu">提交申诉</view>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,86 @@
/* 页面与容器 */
page { background: #f5f5f5; }
/* 🔥 组件自身必须占满宽度,且 padding 左右严格相等 */
.container { width: 100%; box-sizing: border-box; display: flex; flex-direction: column; }
/* 🔥 滚动区 padding 左右必须相等,且容器用 box-sizing */
.scroll { width: 100%; box-sizing: border-box; padding: 20rpx 24rpx; }
/* 提示文字 */
.tip { text-align: center; color: #999; font-size: 24rpx; padding: 30rpx 0; }
.empty { display: flex; flex-direction: column; align-items: center; padding-top: 200rpx; color: #999; font-size: 28rpx; }
/* 🔥 卡片 width:100% + box-sizing 使左右边距完全由父容器决定 */
.card { width: 100%; box-sizing: border-box; background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; }
.card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12rpx; }
.time { font-size: 24rpx; color: #999; }
.tag { font-size: 20rpx; padding: 2rpx 14rpx; border-radius: 16rpx; display: inline-block; }
.zhuangtai-daichuli { background: #fff3e0; color: #e65100; }
.zhuangtai-yichufa { background: #e8f5e9; color: #2e7d32; }
.zhuangtai-yibohui { background: #f3e5f5; color: #7b1fa2; }
.zhuangtai-shensuzhong { background: #e3f2fd; color: #1565c0; }
.zhuangtai-weizhi { background: #f0f0f0; color: #888; }
.info { display: block; font-size: 26rpx; color: #333; margin-top: 8rpx; }
/* 弹窗遮罩 */
.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 999; display: flex; align-items: flex-end; justify-content: center; }
/* 🔥 弹窗面板 width:100% + box-sizing */
.modal-panel { width: 100%; box-sizing: border-box; max-height: 85vh; background: #fff; border-radius: 24rpx 24rpx 0 0; display: flex; flex-direction: column; overflow: hidden; }
.modal-head { display: flex; justify-content: space-between; align-items: center; padding: 28rpx 30rpx 16rpx; border-bottom: 1px solid #eee; }
.modal-title { font-size: 32rpx; font-weight: 600; }
.modal-close { width: 44rpx; height: 44rpx; border-radius: 50%; background: #f5f5f5; display: flex; align-items: center; justify-content: center; font-size: 26rpx; color: #888; }
/* 🔥 内容区左右 padding 严格相等 */
.modal-body { width: 100%; box-sizing: border-box; padding: 20rpx 30rpx; overflow-y: auto; }
.row { display: flex; margin-bottom: 16rpx; }
.lbl { width: 150rpx; font-size: 26rpx; color: #888; flex-shrink: 0; }
.val { flex: 1; font-size: 26rpx; color: #333; word-break: break-all; }
.section { margin: 20rpx 0; }
.sec-title { font-size: 26rpx; color: #888; margin-bottom: 12rpx; display: block; }
/* 🔥 图片网格gap 均等,无任何额外水平偏移 */
.img-grid { width: 100%; box-sizing: border-box; display: flex; flex-wrap: wrap; gap: 12rpx; }
.img { width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); border-radius: 10rpx; background: #f0f0f0; display: block; position: relative; }
.img-box { position: relative; width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); }
.img-box .img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; padding-bottom: 0; }
.img-del { position: absolute; top: -6rpx; right: -6rpx; width: 34rpx; height: 34rpx; background: #e53935; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 18rpx; z-index: 2; }
.img-add { width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); border: 2rpx dashed #ccc; border-radius: 10rpx; display: flex; align-items: center; justify-content: center; font-size: 52rpx; color: #aaa; box-sizing: border-box; position: relative; }
/* 表单 */
.form-group { margin-bottom: 24rpx; }
.textarea { width: 100%; box-sizing: border-box; height: 180rpx; background: #f9f9f9; border-radius: 10rpx; padding: 16rpx; font-size: 26rpx; border: 1px solid #eee; }
.word-count { text-align: right; font-size: 22rpx; color: #bbb; margin-top: 6rpx; }
.progress-box { margin-top: 12rpx; }
.progress-bar { height: 10rpx; background: #eee; border-radius: 5rpx; }
.progress-inner { height: 100%; background: #2e7d32; border-radius: 5rpx; }
.progress-text { font-size: 22rpx; color: #2e7d32; text-align: right; margin-top: 4rpx; }
/* 🔥 底部按钮区左右 padding 严格均等 */
.modal-foot { width: 100%; box-sizing: border-box; display: flex; padding: 16rpx 30rpx 30rpx; gap: 20rpx; border-top: 1px solid #eee; }
.btn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; }
.primary { background: #2e7d32; color: #fff; }
.default { background: #f0f0f0; color: #555; }
.btn:active { opacity: 0.8; }
/* ===== 获取更多按钮 ===== */
.load-more-btn {
width: 80%;
margin: 20rpx auto 30rpx;
padding: 22rpx 0;
text-align: center;
background: #ffffff;
border: 2rpx solid #e0e0e0;
border-radius: 20rpx;
color: #555;
font-size: 26rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.02);
transition: all 0.15s;
}
.load-more-btn:active {
background: #f5f5f5;
border-color: #2e7d32;
color: #2e7d32;
}

View File

@@ -0,0 +1,288 @@
// 管事会员记录页面逻辑
import request from '../../utils/request.js';
// 每页数据条数常量 - 统一管理,方便修改
const MEIYE_TIAOSHU = 50;
Page({
data: {
// 从全局变量获取的数据
yaoqingzongshu: 0, // 邀请打手总数
fenyongzonge: '0.00', // 分成总额
// 从接口获取的数据
chongzhiDashouShuliang: 0, // 充值打手数量
dashouList: [], // 打手列表数据
zongTiaoshu: 0, // 总条数
// 分页相关
dangqianYe: 1, // 当前页
meiyouGengduo: false, // 没有更多数据
jiazaiZhong: false, // 加载中状态
jiazaiGengduo: false, // 加载更多状态
// UI状态
cuowuTishi: '', // 错误提示
shuaxinKezhi: false, // 刷新按钮是否可点击
shuaxinCd: 2000, // 刷新冷却时间(毫秒)
// 常量(用于模板)
meiyeTiaoshu: MEIYE_TIAOSHU // 每页条数
},
onLoad(options) {
// 从全局变量获取管事数据
this.huoquQuanjubianliang();
// 加载第一页数据
this.jiazaiShuju(true);
},
onShow() {
// 页面显示时检查全局变量是否有更新
this.registerNotificationComponent();
this.huoquQuanjubianliang();
},
// 🆕 新增:注册通知组件
registerNotificationComponent() {
const app = getApp();
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
console.log('🏪 商城页面注册通知组件');
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
// 获取全局变量数据
huoquQuanjubianliang() {
const app = getApp();
const guanshiData = app.globalData.guanshi;
if (guanshiData) {
this.setData({
yaoqingzongshu: guanshiData.yaoqingzongshu || 0,
fenyongzonge: guanshiData.fenyongzonge || '0.00'
});
}
},
// 加载数据核心方法
async jiazaiShuju(chushihua = false) {
// 防止重复请求
if (this.data.jiazaiZhong) return;
// 如果是初始化加载,重置分页
if (chushihua) {
this.setData({
dangqianYe: 1,
meiyouGengduo: false,
dashouList: [],
zongTiaoshu: 0,
chongzhiDashouShuliang: 0
});
} else {
// 上拉加载更多时,检查是否还有更多数据
if (this.data.meiyouGengduo) return;
// 如果有数据但不足一页,说明后端已经没数据了
if (this.data.dashouList.length > 0 && this.data.dashouList.length < MEIYE_TIAOSHU) {
this.setData({ meiyouGengduo: true });
return;
}
// 如果数据量已经达到总条数,则不请求
if (this.data.dashouList.length >= this.data.zongTiaoshu && this.data.zongTiaoshu > 0) {
this.setData({ meiyouGengduo: true });
return;
}
}
// 设置加载状态
this.setData({
jiazaiZhong: chushihua ? true : false,
jiazaiGengduo: !chushihua ? true : false,
cuowuTishi: ''
});
try {
const { dangqianYe } = this.data;
// 严格按照您的request规范调用
const res = await request({
url: '/shangpin/fenhonghq',
method: 'POST',
data: {
page: dangqianYe,
page_size: MEIYE_TIAOSHU // 使用统一常量
}
});
// 处理返回数据
if (res.data.code === 200) {
const data = res.data.data || {};
const newList = data.list || [];
const total = data.total || 0;
// 处理头像URL拼接
const processedList = this.chuliTouxiangURL(newList);
// 计算是否有更多数据
const hasMoreData = processedList.length === MEIYE_TIAOSHU;
// 更新数据
this.setData({
dashouList: chushihua ? processedList : [...this.data.dashouList, ...processedList],
zongTiaoshu: total,
chongzhiDashouShuliang: data.chongzhi_dashou_shuliang || 0,
meiyouGengduo: !hasMoreData,
dangqianYe: chushihua ? 2 : this.data.dangqianYe + 1
});
// 如果数据为空,显示空状态
if (chushihua && processedList.length === 0) {
wx.showToast({
title: '暂无数据',
icon: 'none',
duration: 1500
});
}
// 如果加载了数据,给个提示
if (processedList.length > 0) {
if (chushihua) {
wx.showToast({
title: `已加载${processedList.length}条记录`,
icon: 'success',
duration: 1000
});
}
}
} else {
this.setData({
cuowuTishi: res.data.msg || '数据加载失败'
});
wx.showToast({
title: res.data.msg || '加载失败',
icon: 'none',
duration: 2000
});
}
} catch (error) {
console.error('加载数据失败:', error);
this.setData({
cuowuTishi: '网络错误,请检查网络连接'
});
wx.showToast({
title: '网络错误',
icon: 'none',
duration: 2000
});
} finally {
this.setData({
jiazaiZhong: false,
jiazaiGengduo: false
});
}
},
// 处理头像URL拼接
chuliTouxiangURL(list) {
const app = getApp();
const ossUrl = app.globalData.ossImageUrl || '';
return list.map(item => {
// 确保有头像URL如果没有则使用默认头像
let touxiangUrl = item.avatar || '';
if (touxiangUrl && !touxiangUrl.startsWith('http')) {
touxiangUrl = ossUrl + touxiangUrl;
}
// 格式化时间
let shijian = item.create_time || '';
if (shijian) {
// 如果是时间戳,转换为日期格式
if (/^\d+$/.test(shijian)) {
const date = new Date(parseInt(shijian));
shijian = `${date.getMonth() + 1}${date.getDate()}${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
} else {
// 如果是字符串,提取日期和时间
try {
const dateObj = new Date(shijian);
shijian = `${dateObj.getMonth() + 1}${dateObj.getDate()}${dateObj.getHours().toString().padStart(2, '0')}:${dateObj.getMinutes().toString().padStart(2, '0')}`;
} catch (e) {
shijian = shijian.substring(0, 10);
}
}
}
return {
...item,
touxiangUrl: touxiangUrl || '/images/default-avatar.png',
shijian: shijian,
fenhong: parseFloat(item.fenhong || 0).toFixed(2)
};
});
},
// 刷新数据(带防重复点击)
shuaxinShuju() {
// 检查是否在冷却中
if (this.data.shuaxinKezhi) {
wx.showToast({
title: `请等待${this.data.shuaxinCd/1000}`,
icon: 'none',
duration: 1000
});
return;
}
// 设置冷却状态
this.setData({ shuaxinKezhi: true });
// 显示刷新动画
this.setData({
jiazaiZhong: true
});
// 刷新数据
this.jiazaiShuju(true).finally(() => {
// 2秒后恢复按钮可点击
setTimeout(() => {
this.setData({ shuaxinKezhi: false });
}, this.data.shuaxinCd);
});
},
// 上拉加载更多
shanglaJiazai() {
// 没有更多数据时不再请求
if (this.data.meiyouGengduo) {
wx.showToast({
title: '已经到底了',
icon: 'none',
duration: 1000
});
return;
}
// 如果正在加载,不再请求
if (this.data.jiazaiGengduo || this.data.jiazaiZhong) {
return;
}
this.jiazaiShuju(false);
},
// 重试加载
chongshiJiazai() {
this.setData({
cuowuTishi: '',
meiyouGengduo: false
});
this.jiazaiShuju(true);
}
});

View File

@@ -0,0 +1,11 @@
{
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
},
"navigationBarTitleText": "会员记录",
"navigationBarBackgroundColor": "#0a0a0f",
"navigationBarTextStyle": "white",
"backgroundColor": "#0a0a0f",
"backgroundTextStyle": "light",
"enablePullDownRefresh": false
}

View File

@@ -0,0 +1,119 @@
<!-- 管事会员记录页面 -->
<view class="page-container">
<!-- 固定顶部区域 - 不动 -->
<view class="fixed-top">
<!-- 顶部统计区域 -->
<view class="tongji-quyu">
<!-- 分红总额 -->
<view class="fenyong-zonge">
<view class="zonge-label">分红总额</view>
<view class="zonge-value">¥{{fenyongzonge}}</view>
<view class="zonge-tip">累计收益</view>
</view>
<!-- 底部统计信息 -->
<view class="tongji-bottom">
<view class="tongji-item">
<view class="tongji-number">{{yaoqingzongshu}}</view>
<view class="tongji-label">邀请打手</view>
</view>
<view class="tongji-divider"></view>
<view class="tongji-item">
<view class="tongji-number">{{chongzhiDashouShuliang}}</view>
<view class="tongji-label">充值打手</view>
</view>
</view>
</view>
<!-- 刷新按钮 -->
<view class="shuaxin-anniu" bindtap="shuaxinShuju">
<text class="shuaxin-icon {{shuaxinKezhi ? 'shuaxin-disabled' : ''}}">↻</text>
</view>
</view>
<!-- 滚动区域 - 只包裹列表部分 -->
<scroll-view
class="scroll-area"
scroll-y
enable-back-to-top
bindscrolltolower="shanglaJiazai">
<!-- 全局加载状态 -->
<view wx:if="{{jiazaiZhong && dashouList.length === 0}}" class="jiazai-quanju">
<view class="jiazai-spinner"></view>
<view class="jiazai-text">加载中...</view>
</view>
<!-- 空数据提示 -->
<view wx:if="{{!jiazaiZhong && dashouList.length === 0}}" class="kong-shuju">
<view class="kong-icon">🚀</view>
<view class="kong-text">暂无会员记录</view>
<view class="kong-tip">快去邀请打手吧</view>
<view class="kong-btn" bindtap="shuaxinShuju">点击刷新</view>
</view>
<!-- 会员记录卡片列表 -->
<block wx:if="{{dashouList.length > 0}}">
<!-- 列表标题 -->
<view class="list-title">
<text class="title-text">充值记录</text>
<text class="title-count">({{dashouList.length}}/{{zongTiaoshu}})</text>
</view>
<!-- 卡片列表 -->
<view class="card-list">
<block wx:for="{{dashouList}}" wx:key="index">
<view class="dashou-card">
<!-- 圆形头像区域 -->
<view class="touxiang-quyu">
<image
class="dashou-touxiang"
src="{{item.touxiangUrl}}"
mode="aspectFill">
</image>
<!-- 头像边框效果 -->
<view class="touxiang-border"></view>
</view>
<!-- 信息区域 -->
<view class="xinxi-quyu">
<view class="nicheng">{{item.nicheng}}</view>
<view class="dashou-id">ID: {{item.yonghuid}}</view>
</view>
<!-- 分红金额区域 -->
<view class="fenhong-quyu">
<view class="fenhong-value">
<text class="fenhong-jiahao">+</text>
<text class="fenhong-shuzi">¥{{item.fenhong}}</text>
</view>
<view class="shijian">{{item.shijian}}</view>
</view>
</view>
</block>
</view>
<!-- 加载更多提示 -->
<view wx:if="{{jiazaiGengduo}}" class="jiazai-gengduo">
<view class="gengduo-spinner"></view>
<text class="gengduo-text">加载更多中...</text>
</view>
<!-- 没有更多数据 -->
<view wx:if="{{meiyouGengduo && dashouList.length > 0}}" class="meiyou-gengduo">
<view class="meiyou-icon">✓</view>
<text class="meiyou-text">已经到底了,共{{dashouList.length}}条记录</text>
</view>
</block>
</scroll-view>
<!-- 错误提示 -->
<view wx:if="{{cuowuTishi}}" class="cuowu-quyu">
<view class="cuowu-icon">⚠️</view>
<view class="cuowu-text">{{cuowuTishi}}</view>
<view class="cuowu-btn" bindtap="chongshiJiazai">重试加载</view>
</view>
</view>
<global-notification id="global-notification" />

View File

@@ -0,0 +1,506 @@
/* 管事会员记录页面样式 - 赛博风格 */
/* 页面容器 */
page {
height: 100%;
background: #0a0a0f;
}
.page-container {
height: 100%;
background: linear-gradient(135deg, #0a0a0f 0%, #1a1a2e 50%, #16213e 100%);
position: relative;
}
/* 固定顶部区域 */
.fixed-top {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: linear-gradient(135deg, #0a0a0f 0%, #1a1a2e 100%);
padding-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3);
}
/* 顶部统计区域 - 赛博卡片设计 */
.tongji-quyu {
position: relative;
margin: 20rpx 30rpx 0;
padding: 40rpx 30rpx;
background: rgba(20, 20, 35, 0.9);
border-radius: 24rpx;
border: 1px solid rgba(100, 255, 255, 0.1);
box-shadow:
0 10rpx 30rpx rgba(0, 0, 0, 0.4),
0 0 20rpx rgba(100, 255, 255, 0.05) inset,
0 0 0 1px rgba(100, 255, 255, 0.1);
backdrop-filter: blur(10px);
overflow: hidden;
}
.tongji-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
linear-gradient(45deg,
rgba(100, 255, 255, 0.03) 0%,
rgba(255, 100, 255, 0.03) 100%);
z-index: 0;
}
/* 分红总额样式 - 赛博霓虹效果 */
.fenyong-zonge {
position: relative;
text-align: center;
padding: 20rpx 0 30rpx;
margin-bottom: 25rpx;
border-bottom: 1px solid rgba(100, 255, 255, 0.1);
z-index: 1;
}
.zonge-label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 15rpx;
text-transform: uppercase;
letter-spacing: 2rpx;
}
.zonge-value {
font-size: 60rpx;
font-weight: 800;
color: #00ffea;
margin-bottom: 8rpx;
text-shadow:
0 0 10rpx rgba(0, 255, 234, 0.5),
0 0 20rpx rgba(0, 255, 234, 0.3);
font-family: 'Arial', sans-serif;
}
.zonge-tip {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.5);
}
/* 底部统计信息 */
.tongji-bottom {
display: flex;
justify-content: space-around;
align-items: center;
z-index: 1;
position: relative;
}
.tongji-item {
text-align: center;
flex: 1;
}
.tongji-number {
font-size: 40rpx;
font-weight: 700;
color: #ffffff;
margin-bottom: 6rpx;
text-shadow: 0 0 10rpx rgba(255, 255, 255, 0.3);
}
.tongji-label {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
}
.tongji-divider {
width: 1px;
height: 45rpx;
background: linear-gradient(to bottom,
transparent 0%,
rgba(100, 255, 255, 0.3) 50%,
transparent 100%);
}
/* 刷新按钮 - 固定在右上角 */
.shuaxin-anniu {
position: absolute;
top: 60rpx;
right: 60rpx;
z-index: 200;
display: flex;
align-items: center;
justify-content: center;
width: 70rpx;
height: 70rpx;
background: rgba(20, 20, 35, 0.95);
border-radius: 50%;
border: 1px solid rgba(100, 255, 255, 0.2);
box-shadow:
0 8rpx 20rpx rgba(0, 0, 0, 0.4),
0 0 20rpx rgba(100, 255, 255, 0.1);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.shuaxin-anniu:active {
transform: scale(0.95);
background: rgba(100, 255, 255, 0.1);
box-shadow:
0 4rpx 10rpx rgba(0, 0, 0, 0.3),
0 0 30rpx rgba(100, 255, 255, 0.2);
}
.shuaxin-icon {
font-size: 32rpx;
color: #00ffea;
transition: all 0.3s ease;
}
.shuaxin-disabled {
color: rgba(255, 255, 255, 0.3);
animation: rotate 1s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* 滚动区域 - 只包裹列表部分 */
.scroll-area {
position: fixed;
top: 340rpx; /* 根据固定区域高度调整 */
left: 0;
right: 0;
bottom: 0;
z-index: 50;
}
/* 全局加载状态 */
.jiazai-quanju {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
}
.jiazai-spinner {
width: 80rpx;
height: 80rpx;
border: 4rpx solid rgba(100, 255, 255, 0.1);
border-top: 4rpx solid #00ffea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 30rpx;
box-shadow: 0 0 20rpx rgba(0, 255, 234, 0.3);
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.jiazai-text {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
}
/* 空数据提示 */
.kong-shuju {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 30rpx;
background: rgba(20, 20, 35, 0.5);
border-radius: 24rpx;
border: 1px solid rgba(100, 255, 255, 0.1);
margin: 40rpx 30rpx 0;
}
.kong-icon {
font-size: 100rpx;
margin-bottom: 30rpx;
color: rgba(255, 255, 255, 0.2);
}
.kong-text {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 10rpx;
font-weight: 500;
}
.kong-tip {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.5);
margin-bottom: 50rpx;
}
.kong-btn {
padding: 20rpx 50rpx;
background: linear-gradient(90deg, #00ffea 0%, #0088ff 100%);
color: #000;
border-radius: 50rpx;
font-size: 28rpx;
font-weight: 600;
box-shadow: 0 8rpx 20rpx rgba(0, 255, 234, 0.3);
}
.kong-btn:active {
transform: scale(0.95);
box-shadow: 0 4rpx 10rpx rgba(0, 255, 234, 0.2);
}
/* 列表标题 */
.list-title {
margin: 0 30rpx 20rpx;
display: flex;
align-items: baseline;
}
.title-text {
font-size: 32rpx;
color: #ffffff;
font-weight: 600;
margin-right: 10rpx;
text-shadow: 0 0 10rpx rgba(255, 255, 255, 0.2);
}
.title-count {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.5);
}
/* 卡片列表 */
.card-list {
margin: 0 30rpx;
}
/* 打手卡片 - 赛博风格设计 */
.dashou-card {
position: relative;
display: flex;
align-items: center;
padding: 30rpx;
margin-bottom: 20rpx;
background: rgba(20, 20, 35, 0.7);
border-radius: 20rpx;
border: 1px solid rgba(100, 255, 255, 0.1);
box-shadow:
0 8rpx 24rpx rgba(0, 0, 0, 0.3),
0 0 0 1px rgba(100, 255, 255, 0.05);
transition: all 0.3s ease;
}
.dashou-card:active {
transform: translateY(-2rpx);
background: rgba(30, 30, 50, 0.8);
box-shadow:
0 12rpx 30rpx rgba(0, 0, 0, 0.4),
0 0 20rpx rgba(100, 255, 255, 0.1);
}
/* 头像区域 - 纯圆形 */
.touxiang-quyu {
position: relative;
margin-right: 30rpx;
flex-shrink: 0;
}
.dashou-touxiang {
width: 100rpx;
height: 100rpx;
border-radius: 50%; /* 确保是纯圆形 */
z-index: 2;
position: relative;
}
.touxiang-border {
position: absolute;
top: -4rpx;
left: -4rpx;
width: 108rpx;
height: 108rpx;
border-radius: 50%;
background: linear-gradient(45deg, #00ffea, #ff00ff);
z-index: 1;
opacity: 0.3;
animation: borderGlow 2s linear infinite;
}
@keyframes borderGlow {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.6; }
}
/* 信息区域 */
.xinxi-quyu {
flex: 1;
min-width: 0; /* 防止内容撑开 */
}
.nicheng {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-shadow: 0 0 10rpx rgba(255, 255, 255, 0.2);
}
.dashou-id {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.5);
letter-spacing: 1rpx;
}
/* 分红金额区域 */
.fenhong-quyu {
text-align: right;
flex-shrink: 0;
min-width: 180rpx;
}
.fenhong-value {
margin-bottom: 8rpx;
}
.fenhong-jiahao {
font-size: 26rpx;
color: #00ff00;
margin-right: 4rpx;
}
.fenhong-shuzi {
font-size: 32rpx;
font-weight: 700;
color: #00ff00;
text-shadow: 0 0 10rpx rgba(0, 255, 0, 0.5);
}
.shijian {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.4);
}
/* 加载更多提示 */
.jiazai-gengduo {
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 0 60rpx;
margin: 0 30rpx;
}
.gengduo-spinner {
width: 40rpx;
height: 40rpx;
border: 3rpx solid rgba(100, 255, 255, 0.1);
border-top: 3rpx solid #00ffea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20rpx;
}
.gengduo-text {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.6);
}
/* 没有更多数据 */
.meiyou-gengduo {
display: flex;
flex-direction: column;
align-items: center;
padding: 50rpx 0 80rpx;
margin: 0 30rpx;
color: rgba(255, 255, 255, 0.4);
}
.meiyou-icon {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background: rgba(0, 255, 234, 0.1);
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #00ffea;
margin-bottom: 20rpx;
border: 1px solid rgba(0, 255, 234, 0.2);
}
.meiyou-text {
font-size: 26rpx;
text-align: center;
}
/* 错误提示 */
.cuowu-quyu {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 30rpx;
background: rgba(40, 20, 20, 0.8);
border-radius: 24rpx;
border: 1px solid rgba(255, 100, 100, 0.2);
margin: 40rpx 30rpx 0;
}
.cuowu-icon {
font-size: 80rpx;
color: #ff5555;
margin-bottom: 30rpx;
text-shadow: 0 0 20rpx rgba(255, 85, 85, 0.5);
}
.cuowu-text {
font-size: 30rpx;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 40rpx;
text-align: center;
line-height: 1.5;
}
.cuowu-btn {
padding: 20rpx 50rpx;
background: linear-gradient(90deg, #ff5555 0%, #ff8800 100%);
color: #fff;
border-radius: 50rpx;
font-size: 28rpx;
font-weight: 600;
box-shadow: 0 8rpx 20rpx rgba(255, 85, 85, 0.3);
}
.cuowu-btn:active {
transform: scale(0.95);
box-shadow: 0 4rpx 10rpx rgba(255, 85, 85, 0.2);
}
/* 调整滚动区域的顶部距离,增加间隔 */
.scroll-area {
position: fixed;
top: 400rpx; /* 从 340rpx 增加到 360rpx增加20rpx的间隔 */
left: 0;
right: 0;
bottom: 0;
z-index: 50;
}
/* 如果觉得还不够,可以再增加列表标题的上边距 */
.list-title {
margin: 10rpx 30rpx 20rpx; /* 从 0 30rpx 20rpx 改为 10rpx 30rpx 20rpx */
display: flex;
align-items: baseline;
}

View File

@@ -0,0 +1,975 @@
// pages/dashou-chongzhi/index.js
import request from '../../utils/request';
// 引入弹窗服务(路径根据实际调整)
import PopupService from '../../services/popupService.js';
Page({
data: {
// ========== 全局数据 ==========
clubmber: [], // 会员列表(注意:全局变量中是 clumber这里保持原样
yajin: 0, // 押金
jifen: 0, // 积分(注意:全局变量中是 jinfen
jifenProgress: 0, // 积分进度条角度
// ========== 会员商品列表 ==========
huiyuanList: [], // 从后端获取的会员列表
currentHuiyuanIndex: 0, // 当前选中的会员索引
currentHuiyuan: {}, // 当前选中的会员详情
// ========== 押金充值相关 ==========
showYajinModal: false, // 显示押金弹窗
yajinAmount: '100', // 押金金额
// ========== 支付相关 ==========
payLoading: false, // 支付加载状态
loadingText: '支付中...',
// ========== 订单ID记录 ==========
currentDingdanid: '', // 当前操作的订单ID
// ========== 计算高度 ==========
huiyuanListHeight: 120,
// ========== 会员详情规则 ==========
detailRules: [
'会员有效期为30天从购买当天开始计算',
'会员期间享受优先接单特权',
'可享受专属客服服务',
'会员到期前3天会有提醒',
'支持随时续费,续费天数叠加'
],
// ========== 新增:跳转参数处理 ==========
// 用于接收从其他页面跳转过来的参数,决定是否自动滚动
jumpParams: {},
// ========== 新增:支付方式选择 ==========
showPayMethodModal: false, // 支付方式选择弹窗
currentBuyType: null, // 当前购买类型1会员 2押金 3积分
currentHuiyuanId: null, // 当前选中的会员ID用于会员购买
currentYajinAmount: null, // 当前押金金额(用于押金购买)
// ========== 新增:余额抵扣身份选择 ==========
showBalanceModal: false, // 余额抵扣身份选择弹窗
balanceOptions: [], // 后端返回的身份列表
selectedBalanceId: null, // 选中的身份ID
// ========== 新增:余额抵扣确认弹窗 ==========
showConfirmModal: false, // 余额抵扣确认弹窗
selectedBalanceInfo: null, // 选中的身份详情(用于确认弹窗)
// ========== 新增:会员详情弹窗 ==========
showMemberModal: false,
currentMemberDetail: null,
},
// ========== 生命周期函数 ==========
onLoad(options) {
//console.log('页面加载,跳转参数:', options);
// ✅ 新增:保存跳转参数
this.setData({
jumpParams: options || {}
});
this.initPage();
},
// pages/tijiao/tijiao.js 中添加 onHide 方法(如果已有则合并内容)
onHide() {
const popupComp = this.selectComponent('#popupNotice');
if (popupComp && popupComp.cleanup) {
popupComp.cleanup();
}
},
onShow() {
// 每次显示页面都刷新数据
this.registerNotificationComponent();
this.loadFromGlobalData();
this.checkAndRefreshData();
// 调用统一弹窗组件检查并展示公告
PopupService.checkAndShow(this, 'dashouchongzhi');
},
// 注册通知组件(原有,保持不变)
registerNotificationComponent() {
const app = getApp();
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
onReady() {
// ✅ 新增:页面渲染完成后处理自动滚动
this.handleAutoScroll();
},
// ========== 初始化页面 ==========
async initPage() {
// 从全局数据加载
this.loadFromGlobalData();
// 获取会员商品列表
await this.fetchHuiyuanList();
// 计算会员列表高度
this.calculateHuiyuanHeight();
},
// ========== 从全局变量加载数据 ==========
loadFromGlobalData() {
const app = getApp();
const globalData = app.globalData || {};
// ✅ 重要:保持字段对应关系
// 全局变量中是 clumber页面中是 clubmber
// 全局变量中是 jinfen页面中是 jifen
this.setData({
clubmber: globalData.clumber || [],
yajin: globalData.yajin || 0,
jifen: globalData.jinfen || 0 // ✅ 注意:这里从 jinfen 读取
});
// 计算积分进度条
this.calculateJifenProgress();
},
// ========== 计算积分进度条角度 ==========
calculateJifenProgress() {
const jifen = this.data.jifen || 0;
const progress = (jifen / 10) * 360; // 满分10分
this.setData({
jifenProgress: progress
});
},
// ========== 计算会员列表高度 ==========
calculateHuiyuanHeight() {
const clubmber = this.data.clubmber || [];
const itemHeight = 100; // 每个会员标签高度
const maxHeight = 300; // 最大高度3个会员
let height = clubmber.length * itemHeight;
height = Math.min(height, maxHeight);
this.setData({
huiyuanListHeight: height
});
},
// ========== 获取会员商品列表 ==========
async fetchHuiyuanList() {
try {
const res = await request({
url: '/shangpin/dshyhq',
method: 'POST',
data: {}
});
if (res.data.code === 200) {
const huiyuanList = res.data.data || [];
// 处理数据,标记已购买的会员
const processedList = this.processHuiyuanList(huiyuanList);
this.setData({
huiyuanList: processedList,
currentHuiyuan: processedList[0] || {}
});
} else {
wx.showToast({
title: res.data.message || '获取会员列表失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取会员列表失败:', error);
wx.showToast({
title: '网络错误,请重试',
icon: 'none'
});
}
},
// ========== 处理会员列表,标记已购买的会员 ==========
processHuiyuanList(huiyuanList) {
const clubmber = this.data.clubmber || [];
return huiyuanList.map(item => {
// 检查是否已购买注意会员ID字段是 id
const boughtItem = clubmber.find(c => c.huiyuanid === item.id);
return {
...item,
isBought: !!boughtItem,
buyInfo: boughtItem || null
};
});
},
// ========== Swiper切换事件 ==========
onSwiperChange(e) {
const current = e.detail.current;
const huiyuanList = this.data.huiyuanList || [];
this.setData({
currentHuiyuanIndex: current,
currentHuiyuan: huiyuanList[current] || {}
});
},
// ========== 手动选择会员 ==========
selectHuiyuan(e) {
const index = e.currentTarget.dataset.index;
const huiyuanList = this.data.huiyuanList || [];
this.setData({
currentHuiyuanIndex: index,
currentHuiyuan: huiyuanList[index] || {}
});
},
// ========== 格式化到期时间 ==========
formatDaoqiTime(daoqi) {
if (!daoqi) return '';
try {
// 如果是标准日期格式,格式化为 YYYY-MM-DD
if (daoqi.includes(' ')) {
const dateStr = daoqi.split(' ')[0];
const dateParts = dateStr.split('-');
if (dateParts.length === 3) {
return `${dateParts[0]}-${dateParts[1]}-${dateParts[2]}`;
}
}
return daoqi;
} catch (error) {
console.error('格式化到期时间错误:', error);
return daoqi;
}
},
// ========== 计算剩余时间 ==========
formatRemainTime(daoqi) {
if (!daoqi) return '';
try {
const now = new Date();
const end = new Date(daoqi);
const diff = end - now;
if (diff <= 0) return '已过期';
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (days > 0) {
return `${days}`;
}
const hours = Math.floor(diff / (1000 * 60 * 60));
if (hours > 0) {
return `${hours}小时`;
}
return '即将到期';
} catch (error) {
console.error('计算剩余时间错误:', error);
return '时间错误';
}
},
// ========== 处理自动滚动 ==========
handleAutoScroll() {
const { jumpParams } = this.data;
if (!jumpParams || !jumpParams.needScroll) return;
setTimeout(() => {
let scrollTop = 0;
if (jumpParams.scrollTo === 'bottom') {
scrollTop = 2000;
} else if (jumpParams.scrollTo === 'member') {
return;
}
if (scrollTop > 0) {
wx.pageScrollTo({
scrollTop: scrollTop,
duration: 300
});
}
}, 800);
},
// ========== 押金充值相关 ==========
showYajinModal() {
this.setData({
showYajinModal: true,
yajinAmount: '100'
});
},
hideYajinModal() {
this.setData({
showYajinModal: false
});
},
onYajinInput(e) {
let value = e.detail.value;
value = value.replace(/[^\d]/g, '');
if (value) {
const num = parseInt(value);
if (num < 1) value = '1';
if (num > 10000) value = '10000';
}
this.setData({
yajinAmount: value
});
},
setYajinAmount(e) {
const amount = e.currentTarget.dataset.amount;
this.setData({
yajinAmount: amount
});
},
async handleYajinPay() {
const amount = this.data.yajinAmount;
if (!amount || amount < 1 || amount > 10000) {
wx.showToast({
title: '请输入1-10000元的金额',
icon: 'none'
});
return;
}
this.showLoading('发起支付中...');
try {
const res = await request({
url: '/shangpin/yajingoumai',
method: 'POST',
data: {
jine: parseInt(amount)
}
});
if (res.data.code === 200) {
const payParams = res.data.payParams;
const dingdanid = res.data.dingdanid;
this.setData({
currentDingdanid: dingdanid
});
await this.wechatPay(payParams, 'yajin');
} else {
wx.showToast({
title: res.data.message || '支付发起失败',
icon: 'none'
});
}
} catch (error) {
console.error('押金支付失败:', error);
wx.showToast({
title: '支付失败,请重试',
icon: 'none'
});
} finally {
this.hideLoading();
}
},
// ========== 积分充值相关 ==========
// 🔥 修改点1增加积分已满的硬拦截不弹任何提示直接返回
onJifenClick(e) {
// 优先判断积分是否已满10分为满分
if (this.data.jifen >= 10) {
// 积分已满,不弹窗,不提示,静默返回
return;
}
// 检查是否有会员
const clubmber = this.data.clubmber || [];
if (clubmber.length === 0) {
wx.showToast({
title: '请先购买会员才能充值积分',
icon: 'none',
duration: 3000
});
return;
}
// 积分未满,正常打开支付方式选择
this.setData({
currentBuyType: 3,
showPayMethodModal: true
});
},
// ========== 会员购买相关 ==========
async handleHuiyuanBuy(e) {
const huiyuanId = e.currentTarget.dataset.id;
if (!huiyuanId) {
wx.showToast({
title: '会员信息错误',
icon: 'none'
});
return;
}
this.showLoading('发起会员支付中...');
try {
const res = await request({
url: '/shangpin/huiyuangm',
method: 'POST',
data: {
huiyuanid: huiyuanId
}
});
if (res.data.code === 200) {
const payParams = res.data.payParams;
const dingdanid = res.data.dingdanid;
this.setData({
currentDingdanid: dingdanid
});
await this.wechatPay(payParams, 'huiyuan', huiyuanId);
} else {
wx.showToast({
title: res.data.message || '会员支付发起失败',
icon: 'none'
});
}
} catch (error) {
console.error('会员支付失败:', error);
wx.showToast({
title: '支付失败,请重试',
icon: 'none'
});
} finally {
this.hideLoading();
}
},
// ========== 积分购买(微信支付)—— 已替换为正确的后端接口 ==========
async handleJifenBuy(e) {
const disabled = e.currentTarget.dataset.disabled;
// 检查积分是否已满
if (disabled === 'true') {
wx.showToast({ title: '积分已满,无需补充', icon: 'none' });
return;
}
// 检查是否有会员
const clubmber = this.data.clubmber || [];
if (clubmber.length === 0) {
wx.showToast({ title: '请先购买会员才能充值积分', icon: 'none', duration: 3000 });
return;
}
this.showLoading('发起积分支付中...');
try {
const res = await request({
url: '/shangpin/jifenbc', // 正确的积分下单接口
method: 'POST',
data: {}
});
if (res.data.code === 200) {
const payParams = res.data.payParams;
const dingdanid = res.data.dingdanid;
this.setData({ currentDingdanid: dingdanid });
await this.wechatPay(payParams, 'jifen');
} else {
wx.showToast({ title: res.data.message || '积分支付发起失败', icon: 'none' });
}
} catch (error) {
wx.showToast({ title: '支付失败,请重试', icon: 'none' });
} finally {
this.hideLoading();
}
},
// ========== 微信支付封装 ==========
async wechatPay(payParams, payType, extraData = null) {
return new Promise((resolve, reject) => {
if (!payParams || !payParams.timeStamp || !payParams.paySign) {
wx.showToast({
title: '支付参数错误',
icon: 'none'
});
reject(new Error('支付参数错误'));
return;
}
this.showLoading('调起支付中...');
wx.requestPayment({
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType || 'MD5',
paySign: payParams.paySign,
success: async () => {
this.hideLoading();
wx.showToast({
title: '支付成功!确认中...',
icon: 'none',
duration: 2000
});
try {
await this.startPolling(payType, extraData);
resolve();
} catch (error) {
reject(error);
}
},
fail: async (res) => {
this.hideLoading();
let errorMsg = '支付失败';
if (res.errMsg.includes('cancel')) {
errorMsg = '您取消了支付';
} else if (res.errMsg.includes('fail')) {
errorMsg = '支付失败,请重试';
}
wx.showToast({
title: errorMsg,
icon: 'none'
});
try {
await this.handlePayFailure(payType, extraData);
} catch (error) {
console.error('支付失败通知失败:', error);
}
reject(new Error(res.errMsg));
}
});
});
},
// ========== 开始轮询支付结果 ==========
async startPolling(payType, extraData = null) {
const maxRetries = 10;
const interval = 2000;
const dingdanid = this.data.currentDingdanid;
if (!dingdanid) {
wx.showToast({
title: '订单号缺失',
icon: 'none'
});
return;
}
this.showLoading('确认支付结果中...');
for (let i = 0; i < maxRetries; i++) {
try {
const pollResult = await this.checkPayStatus(payType, dingdanid, extraData);
if (pollResult.success) {
this.hideLoading();
await this.updateAfterPayment(payType, extraData, pollResult.data);
wx.showToast({
title: this.getSuccessMessage(payType),
icon: 'success',
duration: 2000
});
return;
}
await this.sleep(interval);
} catch (error) {
console.error(`轮询失败第${i + 1}次:`, error);
}
}
this.hideLoading();
wx.showToast({
title: '支付确认超时,请联系客服',
icon: 'none'
});
},
// ========== 检查支付状态 ==========
async checkPayStatus(payType, dingdanid, extraData = null) {
let url = '';
let data = { dingdanid: dingdanid };
switch (payType) {
case 'yajin':
url = '/shangpin/yajinlunxun';
break;
case 'jifen':
url = '/shangpin/jifenlunxun';
break;
case 'huiyuan':
url = '/shangpin/huiyuanlx';
data.huiyuanid = extraData;
break;
default:
throw new Error('未知的支付类型');
}
try {
const res = await request({
url,
method: 'POST',
data
});
return {
success: res.data.code === 200,
data: res.data
};
} catch (error) {
console.error('检查支付状态失败:', error);
return {
success: false,
data: null
};
}
},
// ========== 支付失败处理 ==========
async handlePayFailure(payType, extraData = null) {
let url = '';
let data = { dingdanid: this.data.currentDingdanid };
switch (payType) {
case 'yajin':
url = '/shangpin/yajinshibai';
break;
case 'jifen':
url = '/shangpin/jifenshibai';
break;
case 'huiyuan':
url = '/shangpin/huiyuanshibai';
data.huiyuanid = extraData;
break;
default:
return;
}
try {
await request({
url,
method: 'POST',
data
});
} catch (error) {
console.error('支付失败通知失败:', error);
}
},
// ========== 支付成功后更新数据 ==========
async updateAfterPayment(payType, extraData = null, responseData = null) {
const app = getApp();
switch (payType) {
case 'yajin':
if (responseData && responseData.yajin !== undefined) {
app.globalData.yajin = responseData.yajin;
}
await this.refreshYajinData();
break;
case 'jifen':
if (responseData && responseData.jifen !== undefined) {
app.globalData.jinfen = responseData.jifen;
}
await this.refreshJifenData();
break;
case 'huiyuan':
if (responseData && responseData.huiyuan) {
app.globalData.jinfen = responseData.jifen;
await this.updateClubmberData(responseData.huiyuan);
}
await this.refreshHuiyuanData(extraData);
break;
}
this.loadFromGlobalData();
},
// ========== 刷新押金数据 ==========
async refreshYajinData() {
this.loadFromGlobalData();
},
// ========== 刷新积分数据 ==========
async refreshJifenData() {
this.loadFromGlobalData();
},
// ========== 刷新会员数据 ==========
async refreshHuiyuanData(huiyuanId) {
this.loadFromGlobalData();
await this.fetchHuiyuanList();
},
// ========== 更新会员数据到全局变量 ==========
async updateClubmberData(huiyuanData) {
const app = getApp();
const globalClubmber = app.globalData.clumber || [];
const { huiyuanid, huiyuanming, daoqi } = huiyuanData;
const index = globalClubmber.findIndex(item => item.huiyuanid === huiyuanid);
if (index >= 0) {
globalClubmber[index].daoqi = daoqi;
} else {
globalClubmber.push({
huiyuanid: huiyuanid,
huiyuanming: huiyuanming,
daoqi: daoqi
});
}
app.globalData.clumber = globalClubmber;
this.setData({
clubmber: [...globalClubmber]
});
},
// ========== 检查并刷新数据 ==========
async checkAndRefreshData() {
this.loadFromGlobalData();
},
// ========== 工具方法 ==========
getSuccessMessage(payType) {
switch (payType) {
case 'yajin': return '押金充值成功';
case 'jifen': return '积分补充成功';
case 'huiyuan': return '会员购买成功';
default: return '支付成功';
}
},
showLoading(text = '加载中...') {
this.setData({
payLoading: true,
loadingText: text
});
},
hideLoading() {
this.setData({
payLoading: false
});
},
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
},
// ========== 新增功能(余额抵扣等) ==========
onBuyHuiyuanClick(e) {
const huiyuanId = e.currentTarget.dataset.id;
this.setData({
currentBuyType: 1,
currentHuiyuanId: huiyuanId,
showPayMethodModal: true
});
},
onYajinClick() {
this.showYajinModal();
},
onYajinConfirm() {
const amount = this.data.yajinAmount;
if (!amount || amount < 1 || amount > 10000) {
wx.showToast({ title: '请输入1-10000元的金额', icon: 'none' });
return;
}
this.hideYajinModal();
this.setData({
currentBuyType: 2,
currentYajinAmount: amount,
showPayMethodModal: true
});
},
hidePayMethodModal() {
this.setData({ showPayMethodModal: false });
},
// 🔥 修改点2选择支付方式时再次校验积分状态防止异步过程中积分变化
onPayMethodSelect(e) {
const method = e.currentTarget.dataset.method;
const { currentBuyType, currentHuiyuanId, currentYajinAmount, jifen } = this.data;
this.hidePayMethodModal();
// 如果是积分购买,且积分已满,直接拒绝
if (currentBuyType === 3 && jifen >= 10) {
wx.showToast({
title: '积分已满,无需补充',
icon: 'none'
});
return;
}
if (method === 'wx') {
if (currentBuyType === 1) {
this.handleHuiyuanBuy({ currentTarget: { dataset: { id: currentHuiyuanId } } });
} else if (currentBuyType === 2) {
this.setData({ yajinAmount: currentYajinAmount }, () => {
this.handleYajinPay();
});
} else if (currentBuyType === 3) {
// ✅ 调用已修正的积分支付函数
this.handleJifenBuy({ currentTarget: { dataset: { disabled: 'false' } } });
}
} else if (method === 'balance') {
this.fetchBalanceOptions();
}
},
// 🔥 修改点3请求余额抵扣选项前校验积分状态
async fetchBalanceOptions() {
const { currentBuyType, currentHuiyuanId, currentYajinAmount, jifen } = this.data;
// 如果是积分购买,且积分已满,拒绝发起余额抵扣
if (currentBuyType === 3 && jifen >= 10) {
wx.showToast({
title: '积分已满,无需补充',
icon: 'none'
});
return;
}
this.showLoading('获取抵扣信息...');
try {
const res = await request({
url: '/shangpin/czhqdy',
method: 'POST',
data: {
leixing: currentBuyType,
huiyuan_id: currentBuyType === 1 ? currentHuiyuanId : undefined,
yajin_jine: currentBuyType === 2 ? parseInt(currentYajinAmount) : undefined,
}
});
if (res.data.code === 200) {
const options = res.data.data || [];
this.setData({
balanceOptions: options,
showBalanceModal: true
});
} else {
wx.showToast({ title: res.data.message || '获取失败', icon: 'none' });
}
} catch (error) {
console.error('获取抵扣身份失败:', error);
wx.showToast({ title: '网络错误', icon: 'none' });
} finally {
this.hideLoading();
}
},
hideBalanceModal() {
this.setData({ showBalanceModal: false });
},
onBalanceSelect(e) {
const identityId = e.currentTarget.dataset.id;
const selected = this.data.balanceOptions.find(opt => opt.id === identityId);
if (!selected) return;
this.setData({
selectedBalanceId: identityId,
selectedBalanceInfo: selected,
showConfirmModal: true,
showBalanceModal: false
});
},
hideConfirmModal() {
this.setData({ showConfirmModal: false });
},
async confirmBalancePay() {
const { selectedBalanceId, currentBuyType, currentHuiyuanId, currentYajinAmount } = this.data;
if (!selectedBalanceId) return;
this.hideConfirmModal();
this.showLoading('抵扣支付中...');
try {
const res = await request({
url: '/shangpin/dsqrgmdh',
method: 'POST',
data: {
leixing: currentBuyType,
shenfen_id: selectedBalanceId,
huiyuan_id: currentBuyType === 1 ? currentHuiyuanId : undefined,
yajin_jine: currentBuyType === 2 ? parseInt(currentYajinAmount) : undefined,
}
});
if (res.data.code === 200) {
const responseData = res.data.data || {};
await this.updateAfterPayment(
currentBuyType === 1 ? 'huiyuan' : (currentBuyType === 2 ? 'yajin' : 'jifen'),
currentBuyType === 1 ? currentHuiyuanId : null,
responseData
);
wx.showToast({ title: '购买成功', icon: 'success' });
} else {
wx.showToast({ title: res.data.message || '购买失败', icon: 'none' });
}
} catch (error) {
console.error('抵扣支付失败:', error);
wx.showToast({ title: '网络错误', icon: 'none' });
} finally {
this.hideLoading();
}
},
showMemberDetail(e) {
const item = e.currentTarget.dataset.item;
this.setData({
currentMemberDetail: item,
showMemberModal: true
});
},
hideMemberModal() {
this.setData({ showMemberModal: false });
},
});

View File

@@ -0,0 +1,12 @@
{
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
},
"navigationBarTitleText": "充值中心",
"navigationBarBackgroundColor": "#0a0e2a",
"navigationBarTextStyle": "white",
"backgroundColor": "#0a0e2a",
"backgroundTextStyle": "light",
"enablePullDownRefresh": false,
"onReachBottomDistance": 50
}

View File

@@ -0,0 +1,514 @@
<!--pages/dashou-chongzhi/index.wxml-->
<view class="page-container">
<!-- ❌ 删除自定义导航栏区块 -->
<!-- ======================= -->
<!-- VIP会员区域 - 放在上面 -->
<!-- ======================= -->
<view class="vip-section">
<view class="section-header">
<view class="header-decoration">
<view class="dec-line"></view>
<view class="dec-diamond"></view>
<text class="dec-text">VIP会员充值</text>
<view class="dec-diamond"></view>
<view class="dec-line"></view>
</view>
<text class="section-subtitle">选择你的会员</text>
</view>
<!-- 会员卡片滑动区域 -->
<swiper
class="vip-swiper"
current="{{currentHuiyuanIndex}}"
circular="{{true}}"
previous-margin="120rpx"
next-margin="120rpx"
duration="500"
easing-function="easeOutCubic"
bindchange="onSwiperChange">
<swiper-item wx:for="{{huiyuanList}}" wx:key="id">
<view class="vip-card {{index === currentHuiyuanIndex ? 'active' : ''}}"
data-index="{{index}}" bindtap="selectHuiyuan">
<!-- 机甲风格边框 -->
<view class="card-frame">
<view class="frame-corner tl"></view>
<view class="frame-corner tr"></view>
<view class="frame-corner bl"></view>
<view class="frame-corner br"></view>
<view class="frame-line top"></view>
<view class="frame-line right"></view>
<view class="frame-line bottom"></view>
<view class="frame-line left"></view>
</view>
<!-- 卡片内容 -->
<view class="card-content">
<view class="vip-badge" wx:if="{{item.isBought}}">
<text class="badge-text">已拥有</text>
</view>
<view class="vip-title">
<text class="title-text">{{item.mingzi}}</text>
<view class="title-underline"></view>
</view>
<view class="vip-price">
<text class="price-symbol">¥</text>
<text class="price-number">{{item.jiage}}</text>
<text class="price-unit">/30天</text>
</view>
<view class="vip-features">
<view class="feature-item" wx:for="{{item.features || ['特权功能', '专属标识', '优先接单']}}"
wx:key="index">
<image class="feature-icon" src="/static/images/check-tech.png"></image>
<text class="feature-text">{{item}}</text>
</view>
</view>
</view>
<!-- 选中状态指示器 -->
<view class="card-indicator" wx:if="{{index === currentHuiyuanIndex}}">
<view class="indicator-triangle"></view>
<view class="indicator-glow"></view>
</view>
</view>
</swiper-item>
</swiper>
<!-- 会员详细介绍 -->
<view class="vip-detail">
<view class="detail-header">
<view class="detail-title">
<image class="detail-icon" src="/static/images/info-tech.png"></image>
<text class="title-text">{{currentHuiyuan.mingzi}} - 会员详情</text>
</view>
<view class="detail-tech"></view>
</view>
<view class="detail-content">
<scroll-view class="detail-scroll" scroll-y>
<text class="detail-text">{{currentHuiyuan.jieshao || '加载中...'}}</text>
<view class="detail-rules">
<view class="rule-item" wx:for="{{detailRules}}" wx:key="index">
<view class="rule-number">{{index + 1}}</view>
<text class="rule-text">{{item}}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 购买区域 - 点击按钮先弹出支付方式选择 -->
<view class="buy-section">
<view class="buy-info">
<view class="price-display">
<!-- 🔥 将“机甲能源”改为“价格” -->
<text class="price-label">价格:</text>
<text class="price-value">¥{{currentHuiyuan.jiage}}</text>
</view>
<text class="buy-desc" wx:if="{{currentHuiyuan.isBought && currentHuiyuan.buyInfo}}">
已激活,<text class="expire-date">{{currentHuiyuan.buyInfo.daoqi}}</text>后到期
</text>
<text class="buy-desc" wx:else>激活会员特权</text>
</view>
<view class="buy-action">
<view class="tech-buy-btn {{currentHuiyuan.isBought ? 'renew' : ''}}"
bindtap="onBuyHuiyuanClick" data-id="{{currentHuiyuan.id}}">
<view class="buy-btn-bg"></view>
<view class="buy-btn-glow"></view>
<text class="buy-btn-text">
{{currentHuiyuan.isBought ? '立即续费' : '立即激活'}}
</text>
<view class="buy-btn-energy">
<view class="energy-dot"></view>
<view class="energy-dot"></view>
<view class="energy-dot"></view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- ======================= -->
<!-- 三卡片区域 - 放在下面 -->
<!-- ======================= -->
<view class="tech-grid">
<!-- 会员区域 - 高度限制最多3个超出可滚动 -->
<view class="tech-card huiyuan-card">
<view class="card-header">
<view class="card-title">
<text class="title-text">我的会员</text>
<view class="title-text">👑</view>
</view>
<view class="card-badge">{{clubmber.length}}</view>
</view>
<scroll-view
class="huiyuan-scroll"
scroll-y
enable-flex
style="height: {{huiyuanListHeight}}rpx">
<view wx:for="{{clubmber}}" wx:key="huiyuanid" class="huiyuan-item" data-item="{{item}}" bindtap="showMemberDetail">
<view class="huiyuan-tag tech-tag">
<view class="tag-triangle"></view>
<text class="tag-name">{{item.huiyuanming}}</text>
<text class="tag-time">到期: {{formatDaoqiTime(item.daoqi)}}</text>
<view class="tag-glow"></view>
</view>
</view>
<view wx:if="{{clubmber.length === 0}}" class="empty-huiyuan">
<image class="empty-icon" src="/static/images/empty-member.png"></image>
<text class="empty-text">暂无会员</text>
</view>
</scroll-view>
<view class="card-footer">
<text class="footer-text">共{{clubmber.length}}个会员</text>
<view class="footer-line"></view>
</view>
</view>
<!-- 押金区域 - 点击充值按钮弹出押金输入弹窗 -->
<view class="tech-card yajin-card">
<view class="card-header">
<view class="card-title">
<text class="title-text">我的押金</text>
<view class="title-text">💰</view>
</view>
<view class="card-corner"></view>
</view>
<view class="yajin-display">
<view class="amount-container">
<text class="amount-symbol">¥</text>
<text class="amount-number">{{yajin}}</text>
</view>
<text class="amount-unit">可用余额</text>
</view>
<view class="tech-line"></view>
<view class="action-container">
<view class="tech-btn yajin-btn" bindtap="onYajinClick">
<view class="btn-bg"></view>
<text class="btn-text">立即充值</text>
<view class="btn-arrow" style="display:none;">➤</view>
<view class="btn-glow"></view>
</view>
</view>
<view class="card-corner corner-bottom"></view>
</view>
<!-- 积分区域 - 点击补充按钮弹出支付方式选择 -->
<view class="tech-card jifen-card">
<view class="card-header">
<view class="card-title">
<text class="title-text">我的积分</text>
<view class="title-text">⭐</view>
</view>
</view>
<view class="jifen-display">
<view class="circle-progress">
<view class="circle-bg"></view>
<view class="circle-mask" style="transform: rotate({{jifenProgress}}deg);"></view>
<view class="circle-fill" style="transform: rotate({{jifenProgress}}deg);"></view>
<view class="circle-center">
<text class="jifen-value">{{jifen}}</text>
<text class="jifen-label">积分</text>
</view>
<view class="circle-dots">
<view class="circle-dot" wx:for="{{10}}" wx:key="index"
style="transform: rotate({{index * 36}}deg);"></view>
</view>
</view>
<view class="jifen-status">
<text class="status-text" wx:if="{{jifen < 10}}">积分不足,影响接单,提现权限</text>
<text class="status-text full" wx:else>积分充足,无需补充</text>
</view>
</view>
<view class="action-container">
<view class="tech-btn jifen-btn" bindtap="onJifenClick" data-disabled="{{jifen >= 10}}">
<view class="btn-bg"></view>
<text class="btn-text">{{jifen >= 10 ? '积分已满' : '立即补充'}}</text>
<view class="btn-energy" wx:if="{{jifen < 10}}"></view>
<view class="btn-glow" wx:if="{{jifen < 10}}"></view>
</view>
</view>
</view>
</view>
<!-- ======================= -->
<!-- 押金充值弹窗(原有,确认事件改为 onYajinConfirm -->
<!-- ======================= -->
<view class="tech-modal" wx:if="{{showYajinModal}}">
<view class="modal-backdrop" bindtap="hideYajinModal"></view>
<view class="modal-container">
<view class="modal-corner tl"></view>
<view class="modal-corner tr"></view>
<view class="modal-corner bl"></view>
<view class="modal-corner br"></view>
<view class="modal-header">
<text class="modal-title">押金充值</text>
<text class="modal-subtitle">充值保证金</text>
<view class="modal-close" bindtap="hideYajinModal">
<text class="close-text">✕</text>
</view>
</view>
<view class="modal-body">
<view class="input-section">
<view class="input-label">
<image class="label-icon" src="/static/images/money-tech.png"></image>
<text class="label-text">充值金额 (1-10000元)</text>
</view>
<view class="input-container">
<view class="input-prefix">¥</view>
<input
class="yajin-input"
type="digit"
placeholder="输入充值金额"
placeholder-class="input-placeholder"
value="{{yajinAmount}}"
bindinput="onYajinInput"
maxlength="5"
focus="{{showYajinModal}}"/>
<view class="input-underline"></view>
</view>
<text class="input-tip">支持1-10000元整数充值</text>
</view>
<view class="quick-section">
<text class="quick-title">快速选择</text>
<view class="quick-buttons">
<view
class="quick-btn {{yajinAmount == '100' ? 'active' : ''}}"
bindtap="setYajinAmount"
data-amount="100">
<text class="quick-text">100元</text>
</view>
<view
class="quick-btn {{yajinAmount == '500' ? 'active' : ''}}"
bindtap="setYajinAmount"
data-amount="500">
<text class="quick-text">500元</text>
</view>
<view
class="quick-btn {{yajinAmount == '1000' ? 'active' : ''}}"
bindtap="setYajinAmount"
data-amount="1000">
<text class="quick-text">1000元</text>
</view>
<view
class="quick-btn {{yajinAmount == '5000' ? 'active' : ''}}"
bindtap="setYajinAmount"
data-amount="5000">
<text class="quick-text">5000元</text>
</view>
</view>
</view>
</view>
<view class="modal-footer">
<view class="modal-btn cancel-btn" bindtap="hideYajinModal">
<text class="btn-text">取消</text>
</view>
<view class="modal-divider"></view>
<view class="modal-btn confirm-btn" bindtap="onYajinConfirm">
<text class="btn-text">确认支付</text>
<view class="btn-sparkle"></view>
</view>
</view>
</view>
</view>
<!-- ====== 支付方式选择弹窗 ====== -->
<view class="tech-modal" wx:if="{{showPayMethodModal}}">
<view class="modal-backdrop" bindtap="hidePayMethodModal"></view>
<view class="modal-container small">
<view class="modal-corner tl"></view>
<view class="modal-corner tr"></view>
<view class="modal-corner bl"></view>
<view class="modal-corner br"></view>
<view class="modal-header">
<text class="modal-title">选择支付方式</text>
<view class="modal-close" bindtap="hidePayMethodModal">
<text class="close-text">✕</text>
</view>
</view>
<view class="modal-body">
<view class="pay-method-list">
<view class="pay-method-item" bindtap="onPayMethodSelect" data-method="wx">
<image class="method-icon" src="/static/images/wechat-pay.png"></image>
<text class="method-name">微信支付</text>
<text class="method-desc">安全快捷</text>
</view>
<view class="pay-method-item" bindtap="onPayMethodSelect" data-method="balance">
<image class="method-icon" src="/static/images/balance-pay.png"></image>
<text class="method-name">余额抵扣</text>
<text class="method-desc">使用佣金或分红</text>
</view>
</view>
</view>
</view>
</view>
<!-- ====== 余额抵扣身份选择弹窗 ====== -->
<view class="tech-modal" wx:if="{{showBalanceModal}}">
<view class="modal-backdrop" bindtap="hideBalanceModal"></view>
<view class="modal-container">
<view class="modal-corner tl"></view>
<view class="modal-corner tr"></view>
<view class="modal-corner bl"></view>
<view class="modal-corner br"></view>
<view class="modal-header">
<text class="modal-title">选择抵扣身份</text>
<view class="modal-close" bindtap="hideBalanceModal">
<text class="close-text">✕</text>
</view>
</view>
<view class="modal-body">
<view class="balance-list">
<view wx:for="{{balanceOptions}}" wx:key="id" class="balance-item"
data-id="{{item.id}}" bindtap="onBalanceSelect">
<view class="balance-info">
<text class="balance-name">{{item.jieshao}}</text>
<text class="balance-amount">需¥{{item.xuyao}}</text>
</view>
<view class="balance-arrow">➤</view>
</view>
</view>
</view>
</view>
</view>
<!-- ====== 余额抵扣确认弹窗 ====== -->
<view class="tech-modal" wx:if="{{showConfirmModal}}">
<view class="modal-backdrop" bindtap="hideConfirmModal"></view>
<view class="modal-container">
<view class="modal-corner tl"></view>
<view class="modal-corner tr"></view>
<view class="modal-corner bl"></view>
<view class="modal-corner br"></view>
<view class="modal-header">
<text class="modal-title">确认抵扣</text>
<view class="modal-close" bindtap="hideConfirmModal">
<text class="close-text">✕</text>
</view>
</view>
<view class="modal-body">
<view class="confirm-detail">
<view class="detail-row">
<text class="detail-label">抵扣身份:</text>
<text class="detail-value">{{selectedBalanceInfo && selectedBalanceInfo.jieshao}}</text>
</view>
<view class="detail-row">
<text class="detail-label">所需金额:</text>
<text class="detail-value">¥{{selectedBalanceInfo && selectedBalanceInfo.xuyao}}</text>
</view>
<view class="detail-row">
<text class="detail-label">实际扣除:</text>
<text class="detail-value">¥{{selectedBalanceInfo && selectedBalanceInfo.xuyao}}</text>
</view>
<view class="detail-tip">确认后将从您的余额中扣除</view>
</view>
</view>
<view class="modal-footer">
<view class="modal-btn cancel-btn" bindtap="hideConfirmModal">
<text class="btn-text">取消</text>
</view>
<view class="modal-divider"></view>
<view class="modal-btn confirm-btn" bindtap="confirmBalancePay">
<text class="btn-text">确认支付</text>
<view class="btn-sparkle"></view>
</view>
</view>
</view>
</view>
<!-- ====== 会员详情弹窗 ====== -->
<view class="tech-modal" wx:if="{{showMemberModal}}">
<view class="modal-backdrop" bindtap="hideMemberModal"></view>
<view class="modal-container">
<view class="modal-corner tl"></view>
<view class="modal-corner tr"></view>
<view class="modal-corner bl"></view>
<view class="modal-corner br"></view>
<view class="modal-header">
<text class="modal-title">会员详情</text>
<view class="modal-close" bindtap="hideMemberModal">
<text class="close-text">✕</text>
</view>
</view>
<view class="modal-body">
<view class="member-detail">
<view class="detail-row">
<text class="detail-label">会员名称:</text>
<text class="detail-value">{{currentMemberDetail && currentMemberDetail.huiyuanming}}</text>
</view>
<view class="detail-row">
<text class="detail-label">到期时间:</text>
<text class="detail-value">{{currentMemberDetail && currentMemberDetail.daoqi}}</text>
</view>
<view class="detail-row">
<text class="detail-label">剩余天数:</text>
<text class="detail-value">{{formatRemainTime(currentMemberDetail && currentMemberDetail.daoqi)}}</text>
</view>
</view>
</view>
</view>
</view>
<!-- ❌ 删除“进入提示弹窗”整个区块 -->
<!-- ======================= -->
<!-- 支付加载动画(原有) -->
<!-- ======================= -->
<view class="tech-loading" wx:if="{{payLoading}}">
<view class="loading-backdrop"></view>
<view class="loading-content">
<view class="loading-spinner">
<view class="spinner-ring ring-1"></view>
<view class="spinner-ring ring-2"></view>
<view class="spinner-ring ring-3"></view>
<view class="spinner-core"></view>
<view class="spinner-energy"></view>
</view>
<text class="loading-text">{{loadingText}}</text>
<text class="loading-subtext">正在连接支付系统...</text>
</view>
</view>
<!-- ======================= -->
<!-- 底部机甲装饰 -->
<!-- ======================= -->
<view class="tech-footer">
<view class="footer-line"></view>
<view class="footer-text">星阙网络 © 7891 科技</view>
</view>
</view>
<!-- 通知组件 -->
<global-notification id="global-notification" />
<!-- 🆕 统一弹窗组件 -->
<popup-notice id="popupNotice" />

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,344 @@
const app = getApp()
import request from '../../utils/request.js'
Page({
onShow() {
// 原有代码...
// 🆕 注册通知组件
this.registerNotificationComponent();
},
// 🆕 新增:注册通知组件
registerNotificationComponent() {
const app = getApp();
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
//console.log('🏪 商城页面注册通知组件');
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
data: {
// 打手订单状态tab配置去掉已下单其他保留
dsDingdanZhuangtaiTabs: [
{ name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2] },
{ name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4] },
{ name: '已退款', key: 'yituikuan', zhuangtaiList: [5] },
{ name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6] },
{ name: '已完成', key: 'yiwancheng', zhuangtaiList: [3] },
{ name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8] }
],
// 当前选中的tab索引默认0=进行中)
xuanzhongTabIndex: 0,
// 当前选中的tab的key
currentTabKey: 'jinxingzhong',
// 防抖标志是否允许切换tab
allowSwitchTab: true,
// 防抖时间(毫秒)
switchDebounceTime: 2000,
// 每个tab对应的订单数据
dsDingdanShuju: {
jinxingzhong: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanzhong: { list: [], page: 1, hasMore: true, isLoading: false },
yituikuan: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanshibai: { list: [], page: 1, hasMore: true, isLoading: false },
yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false },
jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }
},
// 打手订单图片URL从缓存或全局变量获取后拼接
dsDingdanTupianUrl: '',
// 页面加载状态
isLoading: true,
// 是否显示加载更多
xianshiJiazaigengduo: false
},
onLoad(options) {
// 设置页面标题
wx.setNavigationBarTitle({
title: '我的接单'
})
// 初始化获取图片URL并拼接
this.getAndSetTupianUrl()
// 加载初始数据(默认进行中)
this.setData({
isLoading: false
})
this.jiazaiDsDingdanShuju(0, false)
},
// 获取并设置图片URL
getAndSetTupianUrl() {
const app = getApp()
// 1. 先从缓存获取touxiang
const cacheTouxiang = wx.getStorageSync('touxiang') || ''
// 2. 如果缓存没有从全局变量获取morentouxiang
let tupianPath = cacheTouxiang || app.globalData.morentouxiang || ''
// 3. 拼接完整的图片URL
const ossImageUrl = app.globalData.ossImageUrl || ''
let fullTupianUrl = ''
if (tupianPath && ossImageUrl) {
// 确保路径不以斜杠开头(防止双斜杠)
if (tupianPath.startsWith('/')) {
tupianPath = tupianPath.substring(1)
}
fullTupianUrl = ossImageUrl + tupianPath
}
this.setData({
dsDingdanTupianUrl: fullTupianUrl
})
},
// 切换订单tab增加防抖
switchDsDingdanTab(e) {
const tabIndex = e.currentTarget.dataset.index
// 如果当前正在加载,不允许切换
const currentTabKey = this.data.currentTabKey
const currentTabData = this.data.dsDingdanShuju[currentTabKey]
if (currentTabData.isLoading) {
console.log('当前正在加载,禁止切换')
return
}
// 防抖:检查是否允许切换
if (!this.data.allowSwitchTab) {
wx.showToast({
title: '切换太频繁,请稍后',
icon: 'none',
duration: 1000
})
return
}
// 如果点击的是当前选中的tab不处理
if (tabIndex === this.data.xuanzhongTabIndex) {
return
}
const tabKey = this.data.dsDingdanZhuangtaiTabs[tabIndex].key
// 设置防抖标志2秒内不允许再次切换
this.setData({
allowSwitchTab: false
})
// 设置定时器2秒后恢复允许切换
setTimeout(() => {
this.setData({
allowSwitchTab: true
})
}, this.data.switchDebounceTime)
// 更新选中的tab
this.setData({
xuanzhongTabIndex: tabIndex,
currentTabKey: tabKey
})
// 加载对应tab的数据
this.jiazaiDsDingdanShuju(tabIndex, false)
},
// 加载打手订单数据
async jiazaiDsDingdanShuju(tabIndex, isLoadMore = false) {
const tab = this.data.dsDingdanZhuangtaiTabs[tabIndex]
const tabKey = tab.key
const tabData = this.data.dsDingdanShuju[tabKey]
const page = isLoadMore ? tabData.page + 1 : 1
// 检查是否正在加载
if (tabData.isLoading) {
return
}
// 如果不是加载更多,清空已有数据
if (!isLoadMore) {
this.setData({
[`dsDingdanShuju.${tabKey}.list`]: [],
[`dsDingdanShuju.${tabKey}.page`]: 1
})
}
// 设置加载状态
this.setData({
[`dsDingdanShuju.${tabKey}.isLoading`]: true
})
// 显示加载提示(非加载更多时显示)
if (!isLoadMore) {
wx.showLoading({
title: '加载订单...',
mask: true
})
}
try {
// 准备请求数据
const requestData = {
zhuangtai_list: tab.zhuangtaiList,
page: page,
page_size: 10
}
const res = await request({
url: '/dingdan/dshqdingdan',
method: 'POST',
data: requestData,
header: {
'content-type': 'application/json'
}
})
// 隐藏加载提示
if (!isLoadMore) {
wx.hideLoading()
}
// 处理返回数据
if (res && res.data.code === 0) {
const newDingdanList = res.data.data.list || []
const hasMore = res.data.data.has_more || false
// 为每个订单添加状态中文描述和统一的图片URL
const processedList = newDingdanList.map(item => {
const zhuangtaiZh = this.getZhuangtaiZhongwen(item.zhuangtai)
return {
...item,
tupian: this.data.dsDingdanTupianUrl, // 使用统一的图片URL
zhuangtaiZh: zhuangtaiZh
}
})
const currentList = this.data.dsDingdanShuju[tabKey].list
const updatedList = isLoadMore
? [...currentList, ...processedList]
: processedList
// 更新数据
this.setData({
[`dsDingdanShuju.${tabKey}.list`]: updatedList,
[`dsDingdanShuju.${tabKey}.page`]: page,
[`dsDingdanShuju.${tabKey}.hasMore`]: hasMore,
[`dsDingdanShuju.${tabKey}.isLoading`]: false,
xianshiJiazaigengduo: hasMore
})
} else {
// 请求失败处理
const errorMsg = res ? (res.data.msg || '加载失败') : '请求失败'
if (!isLoadMore) {
wx.showToast({
title: errorMsg,
icon: 'none',
duration: 2000
})
}
this.setData({
[`dsDingdanShuju.${tabKey}.isLoading`]: false
})
}
} catch (error) {
if (!isLoadMore) {
wx.hideLoading()
wx.showToast({
title: '网络请求失败,请重试',
icon: 'none',
duration: 2000
})
}
this.setData({
[`dsDingdanShuju.${tabKey}.isLoading`]: false
})
}
},
// 获取状态中文描述
getZhuangtaiZhongwen(zhuangtai) {
const zhuangtaiMap = {
1: '已下单', // 打手看不到这个状态
2: '进行中',
3: '已完成',
4: '退款中',
5: '已退款',
6: '退款失败',
7: '指定中', // 打手看不到这个状态
8: '结算中'
}
return zhuangtaiMap[zhuangtai] || '未知状态'
},
// 上拉加载更多
onReachBottom() {
const tabKey = this.data.currentTabKey
const tabData = this.data.dsDingdanShuju[tabKey]
// 检查是否还有更多数据并且当前没有正在加载
if (tabData.hasMore && !tabData.isLoading) {
const tabIndex = this.data.xuanzhongTabIndex
this.jiazaiDsDingdanShuju(tabIndex, true)
}
},
// 跳转到订单详情页面
goToDsDingdanXiangqing(e) {
const dingdanItem = e.currentTarget.dataset.item
if (!dingdanItem) return
// 编码订单数据
const dingdanDataStr = encodeURIComponent(JSON.stringify(dingdanItem))
// 跳转到打手订单详情页
wx.navigateTo({
url: `/pages/dsddxq/dsddxq?dingdanData=${dingdanDataStr}`
})
},
// 图片加载失败处理
onImageError(e) {
// 如果统一图片加载失败,使用默认图片
const defaultImage = app.globalData.lunbozhanwei || '/images/default.jpg'
this.setData({
dsDingdanTupianUrl: defaultImage
})
}
})

View File

@@ -0,0 +1,8 @@
{
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
},
"navigationBarTitleText": "我的接单",
"enablePullDownRefresh": false,
"onReachBottomDistance": 50
}

View File

@@ -0,0 +1,111 @@
<!-- pages/dashouDingdan/dashouDingdan.wxml -->
<view class="ds-dingdan-page">
<!-- 打手订单状态tab区域 -->
<scroll-view
class="ds-dingdan-tabs-scroll"
scroll-x="true"
scroll-with-animation="true"
>
<view class="ds-dingdan-tabs-container">
<block wx:for="{{dsDingdanZhuangtaiTabs}}" wx:key="key">
<view
class="ds-dingdan-tab-item {{index === xuanzhongTabIndex ? 'ds-tab-active' : ''}}"
bindtap="switchDsDingdanTab"
data-index="{{index}}"
>
<text class="ds-tab-text">{{item.name}}</text>
<view class="ds-tab-active-line" wx:if="{{index === xuanzhongTabIndex}}"></view>
</view>
</block>
</view>
</scroll-view>
<!-- 打手订单列表区域 -->
<view class="ds-dingdan-list-container">
<!-- 页面加载中 -->
<view class="ds-loading-container" wx:if="{{isLoading}}">
<view class="ds-loading-spinner"></view>
<text class="ds-loading-text">加载中...</text>
</view>
<!-- 空状态 -->
<view
class="ds-empty-container"
wx:if="{{!isLoading && dsDingdanShuju[currentTabKey].list.length === 0}}"
>
<image
class="ds-empty-image"
src="/images/empty-order.png"
mode="aspectFit"
></image>
<text class="ds-empty-text">暂无订单</text>
<text class="ds-empty-subtext">快去接单吧</text>
</view>
<!-- 打手订单列表 -->
<block wx:if="{{!isLoading && dsDingdanShuju[currentTabKey].list.length > 0}}">
<block wx:for="{{dsDingdanShuju[currentTabKey].list}}" wx:key="dingdan_id">
<view
class="ds-dingdan-card"
bindtap="goToDsDingdanXiangqing"
data-item="{{item}}"
>
<!-- 商品图片(统一图片) -->
<view class="ds-dingdan-card-left">
<image
class="ds-dingdan-image"
src="{{item.tupian}}"
mode="aspectFill"
binderror="onImageError"
></image>
</view>
<!-- 订单信息(订单介绍显示三行) -->
<view class="ds-dingdan-card-center">
<view class="ds-dingdan-jieshao ds-jieshao-three-line">
{{item.jieshao || '暂无订单描述'}}
</view>
</view>
<!-- 订单状态和价格 -->
<view class="ds-dingdan-card-right">
<!-- 订单状态 -->
<view class="ds-dingdan-zhuangtai">
{{item.zhuangtaiZh || '未知状态'}}
</view>
<!-- 订单价格 -->
<view class="ds-dingdan-jine">
<text class="ds-jine-symbol">¥</text>
<text class="ds-jine-number">{{item.jine || '0.00'}}</text>
</view>
</view>
</view>
</block>
</block>
<!-- 加载更多提示 -->
<view
class="ds-load-more-container"
wx:if="{{xianshiJiazaigengduo && !isLoading && dsDingdanShuju[currentTabKey].list.length > 0}}"
>
<view class="ds-load-more-line"></view>
<text class="ds-load-more-text">上拉加载更多</text>
<view class="ds-load-more-line"></view>
</view>
<!-- 没有更多提示 -->
<view
class="ds-no-more-container"
wx:if="{{!xianshiJiazaigengduo && !isLoading && dsDingdanShuju[currentTabKey].list.length > 0}}"
>
<text class="ds-no-more-text">没有更多订单了</text>
</view>
</view>
<custom-tab-bar />
</view>
<global-notification id="global-notification" />

View File

@@ -0,0 +1,273 @@
/* pages/dashouDingdan/dashouDingdan.wxss */
.ds-dingdan-page {
min-height: 100vh;
background-color: #f8f9fa;
padding-bottom: 20rpx;
}
.ds-dingdan-tabs-scroll {
width: 100%;
height: 90rpx;
white-space: nowrap;
background-color: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.03);
}
.ds-dingdan-tabs-container {
display: flex;
align-items: center;
height: 100%;
padding: 0 20rpx;
}
.ds-dingdan-tab-item {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60rpx;
padding: 0 30rpx;
margin-right: 20rpx;
border-radius: 30rpx;
background-color: #f5f5f5;
color: #666666;
font-size: 28rpx;
transition: all 0.3s ease;
position: relative;
flex-shrink: 0;
}
.ds-dingdan-tab-item:last-child {
margin-right: 0;
}
.ds-dingdan-tab-item.ds-tab-active {
background-color: #e3f2fd;
color: #1565c0;
font-weight: 500;
box-shadow: 0 4rpx 12rpx rgba(21, 101, 192, 0.1);
}
.ds-tab-active-line {
width: 40rpx;
height: 4rpx;
background-color: #2196f3;
border-radius: 2rpx;
margin-top: 6rpx;
animation: ds-fadeIn 0.3s ease;
}
@keyframes ds-fadeIn {
from { opacity: 0; transform: scale(0.8); }
to { opacity: 1; transform: scale(1); }
}
.ds-tab-text {
font-weight: 400;
}
.ds-dingdan-list-container {
padding: 20rpx;
}
.ds-loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 400rpx;
}
.ds-loading-spinner {
width: 60rpx;
height: 60rpx;
border: 6rpx solid #e0e0e0;
border-top-color: #2196f3;
border-radius: 50%;
animation: ds-spin 1s linear infinite;
margin-bottom: 20rpx;
}
@keyframes ds-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.ds-loading-text {
font-size: 28rpx;
color: #999999;
}
.ds-empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 500rpx;
padding-top: 80rpx;
}
.ds-empty-image {
width: 200rpx;
height: 200rpx;
margin-bottom: 30rpx;
opacity: 0.6;
}
.ds-empty-text {
font-size: 32rpx;
color: #999999;
margin-bottom: 10rpx;
}
.ds-empty-subtext {
font-size: 26rpx;
color: #cccccc;
}
.ds-dingdan-card {
display: flex;
align-items: center;
background-color: #ffffff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.ds-dingdan-card:active {
transform: translateY(-2rpx);
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.08);
}
.ds-dingdan-card-left {
flex-shrink: 0;
margin-right: 24rpx;
}
.ds-dingdan-image {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
background-color: #f5f5f5;
}
.ds-dingdan-card-center {
flex: 1;
height: 160rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
/* 关键不同点:订单介绍显示三行 */
.ds-dingdan-jieshao.ds-jieshao-three-line {
font-size: 28rpx;
line-height: 36rpx;
color: #333333;
font-weight: 400;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3; /* 改为3行 */
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 8rpx;
min-height: 108rpx; /* 3 * 36rpx */
}
.ds-dingdan-card-right {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
height: 160rpx;
margin-left: 20rpx;
}
.ds-dingdan-zhuangtai {
font-size: 24rpx;
color: #ff6b6b;
font-weight: 500;
padding: 6rpx 16rpx;
border-radius: 20rpx;
background-color: #fff5f5;
white-space: nowrap;
text-align: center;
min-width: 100rpx;
}
.ds-dingdan-jine {
display: flex;
align-items: center;
}
.ds-jine-symbol {
font-size: 28rpx;
color: #333333;
font-weight: 500;
margin-right: 4rpx;
}
.ds-jine-number {
font-size: 32rpx;
color: #333333;
font-weight: 600;
}
.ds-load-more-container {
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx 0;
}
.ds-load-more-line {
width: 80rpx;
height: 1rpx;
background-color: #e0e0e0;
margin: 0 20rpx;
}
.ds-load-more-text {
font-size: 26rpx;
color: #999999;
white-space: nowrap;
}
.ds-no-more-container {
text-align: center;
padding: 40rpx 0;
}
.ds-no-more-text {
font-size: 26rpx;
color: #cccccc;
}
@media (max-width: 375px) {
.ds-dingdan-image {
width: 140rpx;
height: 140rpx;
}
.ds-dingdan-card-center {
height: 140rpx;
}
.ds-dingdan-card-right {
height: 140rpx;
}
.ds-jine-number {
font-size: 30rpx;
}
.ds-dingdan-jieshao.ds-jieshao-three-line {
min-height: 96rpx; /* 调整小屏幕下的最小高度 */
font-size: 26rpx;
line-height: 32rpx;
}
}

View File

@@ -0,0 +1,543 @@
// pages/dashouzhongxin/dashouzhongxin.js
import request from '../../utils/request.js'
import popupService from '../../services/popupService.js'
const PLATFORM_INVITE_CODE = '0000008RffVgKHMj7kQC'
Page({
data: {
isDashou: false,
isLoading: false,
inviteCode: '',
uid: '',
touxiang: '',
avatarUrl: '',
dashouNicheng: '',
zhanghaoStatus: '',
yongjin: '',
zonge: '',
yajin: '',
chenghao: '',
jinfen: '',
clumber: [],
chengjiaoliang: '',
zaixianZhuangtai: 0,
lastRefreshTime: 0,
canRefresh: true,
isAutoRegistering: false,
inviterCache: null,
// 🆕 称号标签列表(从独立接口获取)
chenghaoList: []
},
onLoad(options) {
if (options.scene) {
try {
const scene = decodeURIComponent(options.scene);
options.inviteCode = scene;
} catch (e) {
console.error('scene解码失败', e);
}
}
this.checkDashouStatus();
const { inviteCode } = options || {};
if (inviteCode) {
this.setData({ inviteCode: inviteCode });
setTimeout(() => {
this.handleInviteCodeWithLoginCheck(inviteCode);
}, 500);
}
const app = getApp();
if (!app.globalData.hasShownPopupOnColdStart) {
app.globalData.hasShownPopupOnColdStart = true;
setTimeout(() => {
popupService.checkAndShow(this, 'dashouduan');
}, 300);
}
},
onHide() {
const popupComp = this.selectComponent('#popupNotice');
if (popupComp && popupComp.cleanup) {
popupComp.cleanup();
}
},
onShow() {
// 从缓存读取邀请人信息
const inviterCache = wx.getStorageSync('inviterCache') || null;
this.setData({ inviterCache });
this.registerNotificationComponent();
this.checkAndRedirectToJinpai();
if (this.data.isDashou) {
setTimeout(() => {
this.refreshDashouInfo();
this.fetchChenghaoList(); // 🆕 获取标签
}, 300);
}
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.options || {};
if (options.inviteCode && this.data.isDashou === false) {
const { inviteCode } = options;
setTimeout(() => {
this.handleInviteCodeWithLoginCheck(inviteCode);
}, 300);
}
},
// ==================== 🆕 获取称号标签列表 ====================
async fetchChenghaoList() {
try {
const res = await request({
url: '/dengji/dsbqhq',
method: 'POST'
});
if (res && res.data.code === 0 && res.data.data) {
const list = res.data.data.chenghao_list || [];
this.setData({ chenghaoList: list });
// 可选:也存入全局,方便其他页面使用
const app = getApp();
app.globalData.chenghaoList = list;
} else {
this.setData({ chenghaoList: [] });
}
} catch (e) {
console.error('获取称号标签失败', e);
// 失败不影响主流程
}
},
// ========================================================
// 联系邀请人(保持不变)
async contactInviter() {
wx.showLoading({ title: '获取邀请人...', mask: true });
try {
const res = await request({ url: '/yonghu/hqwdyqr', method: 'POST' });
if (res && res.data.code === 200) {
const inviter = res.data.data;
const ossImageUrl = getApp().globalData.ossImageUrl || '';
const fullAvatar = inviter.touxiang
? (inviter.touxiang.startsWith('http') ? inviter.touxiang : ossImageUrl + inviter.touxiang)
: '';
const cacheData = {
uid: inviter.uid,
nicheng: inviter.nicheng || '',
avatar: fullAvatar
};
wx.setStorageSync('inviterCache', cacheData);
this.setData({ inviterCache: cacheData });
const app = getApp();
const myUid = wx.getStorageSync('uid');
if (app.globalData.currentRole !== 'dashou') {
app.globalData.currentRole = 'dashou';
wx.setStorageSync('currentRole', 'dashou');
if (app.switchRoleAndReconnect) {
await app.switchRoleAndReconnect('dashou');
}
} else {
if (app.connectForCurrentRole) app.connectForCurrentRole();
}
const param = {
toUserId: 'Gs' + inviter.uid,
toName: inviter.nicheng || '邀请人',
toAvatar: fullAvatar
};
wx.navigateTo({
url: '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param))
});
} else {
wx.showToast({ title: res?.data?.msg || '获取失败', icon: 'none' });
}
} catch (e) {
wx.showToast({ title: '网络错误', icon: 'none' });
} finally {
wx.hideLoading();
}
},
handleInviteCodeWithLoginCheck(inviteCode) {
const that = this;
const token = wx.getStorageSync('token');
const uid = wx.getStorageSync('uid');
if (!token || !uid) {
this.loginAndRegisterWithInviteCode(inviteCode);
} else {
if (!that.data.isDashou) {
wx.showLoading({ title: '自动注册中...', mask: true });
that.setData({ isAutoRegistering: true });
that.onRegister();
} else {
wx.showToast({ title: '您已是打手', icon: 'success', duration: 2000 });
}
}
},
loginAndRegisterWithInviteCode(inviteCode) {
const that = this;
wx.showLoading({ title: '登录注册中...', mask: true });
wx.login({
success: (loginRes) => {
if (loginRes.code) {
that.callLoginRegisterApi(loginRes.code, inviteCode);
} else {
wx.hideLoading();
wx.showToast({ title: '微信登录失败', icon: 'none' });
}
},
fail: () => {
wx.hideLoading();
console.error('微信登录失败');
wx.reLaunch({ url: '/pages/gerenzhongxin/gerenzhongxin' });
}
});
},
callLoginRegisterApi(code, inviteCode) {
const that = this;
const app = getApp();
const apiBaseUrl = app.globalData.apiBaseUrl;
if (!apiBaseUrl) {
wx.hideLoading();
wx.showToast({ title: '服务器配置错误', icon: 'none' });
return;
}
wx.request({
url: apiBaseUrl + '/yonghu/wdlyhdl',
method: 'POST',
data: { code: code, inviteCode: inviteCode },
header: { 'content-type': 'application/json' },
success: (res) => {
if (res.statusCode === 200 && res.data && res.data.code === 0 && res.data.data) {
const userData = res.data.data;
if (userData.token) wx.setStorageSync('token', userData.token);
const identityFields = ['nicheng', 'uid', 'touxiang', 'dashoustatus', 'guanshistatus', 'shangjiastatus', 'zuzhangstatus'];
identityFields.forEach(field => {
if (userData[field] !== undefined) {
wx.setStorageSync(field, userData[field]);
app.globalData[field] = userData[field];
}
});
const groupFields = ['dashouqun', 'dashouqunid', 'guanshiqun', 'guanshiqunid'];
groupFields.forEach(field => {
if (userData[field] !== undefined) {
wx.setStorageSync(field, userData[field]);
app.globalData[field] = userData[field];
}
});
if (userData.dashounicheng || userData.yongjin) {
const updateData = {
dashouNicheng: userData.dashounicheng || '',
zhanghaoStatus: userData.zhanghaostatus || '',
yongjin: userData.yongjin || '0.00',
zonge: userData.zonge || '0.00',
yajin: userData.yajin || '0.00',
chenghao: userData.chenghao || '',
jinfen: userData.jifen || userData.jinfen || '0',
clumber: userData.clumber || [],
chengjiaoliang: userData.chengjiaoliang || '0',
zaixianZhuangtai: userData.zaixianzhuangtai || 0,
dashouzhuangtai: userData.dashouzhuangtai || '',
// 🔥 不再从这里取 chenghaoList单独调用 fetchChenghaoList
};
Object.assign(app.globalData, updateData);
}
that.setData({
isDashou: true,
uid: userData.uid || '',
touxiang: userData.touxiang || '',
isAutoRegistering: false
// 不设置 chenghaoList稍后在 onShow 中自动获取
});
that.loadAvatar();
that.getDashouInfo();
that.switchToDashouRole();
wx.hideLoading();
wx.showToast({ title: '登录注册成功!', icon: 'success', duration: 2000 });
} else {
wx.hideLoading();
wx.showToast({ title: (res.data && res.data.msg) || '登录注册失败', icon: 'none' });
}
},
fail: () => {
wx.hideLoading();
wx.showToast({ title: '网络请求失败', icon: 'none' });
}
});
},
registerNotificationComponent() {
const app = getApp();
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
checkDashouStatus() {
try {
const dashoustatus = wx.getStorageSync('dashoustatus');
const uid = wx.getStorageSync('uid');
const touxiang = wx.getStorageSync('touxiang');
if (dashoustatus === 1) {
this.setData({ isDashou: true, uid: uid || '', touxiang: touxiang || '' });
this.loadAvatar();
this.checkGlobalData();
} else {
this.setData({ isDashou: false });
}
} catch (error) {
console.error('读取缓存失败:', error);
this.setData({ isDashou: false });
}
},
loadAvatar() {
const app = getApp();
const { ossImageUrl = '', morentouxiang = 'avatar/default.jpg' } = app.globalData || {};
const { touxiang } = this.data;
let avatarUrl = '';
if (touxiang) {
avatarUrl = ossImageUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang);
} else {
avatarUrl = ossImageUrl + (morentouxiang.startsWith('/') ? morentouxiang : '/' + morentouxiang);
}
this.setData({ avatarUrl });
},
checkGlobalData() {
const app = getApp();
const globalData = app.globalData || {};
const needRefresh = !globalData.dashouNicheng || globalData.dashouNicheng === '' || !globalData.yongjin;
if (needRefresh) {
this.getDashouInfo();
} else {
this.setDataFromGlobalData();
}
},
setDataFromGlobalData() {
const app = getApp();
const {
dashouNicheng = '', zhanghaoStatus = '', yongjin = '0.00', zonge = '0.00',
yajin = '0.00', chenghao = '', jinfen = '0', clumber = [],
chengjiaoliang = '0', zaixianZhuangtai = 0
// 🔥 chenghaoList 不再从全局取,避免旧数据干扰
} = app.globalData || {};
this.setData({
dashouNicheng, zhanghaoStatus, yongjin, zonge, yajin,
chenghao, jinfen,
clumber: Array.isArray(clumber) ? clumber : [],
chengjiaoliang,
zaixianZhuangtai: zaixianZhuangtai === 1 ? 1 : 0
});
},
async getDashouInfo() {
this.setData({ isLoading: true });
try {
const res = await request({ url: '/yonghu/dashouxinxi', method: 'POST' });
if (res && res.data.code == 200) {
const data = res.data.data;
const app = getApp();
const updateData = {
dashouNicheng: data.dashounicheng || '',
zhanghaoStatus: data.zhanghaostatus || '',
yongjin: data.yongjin || '0.00',
zonge: data.zonge || '0.00',
yajin: data.yajin || '0.00',
chenghao: data.chenghao || '',
jinfen: data.jifen || '0',
clumber: data.clumber || [],
chengjiaoliang: data.chengjiaoliang || '0',
zaixianZhuangtai: data.zaixianzhuangtai || 0,
dashouzhuangtai: data.dashouzhuangtai || '',
// 🔥 不再包含 chenghaoList
};
Object.assign(app.globalData, updateData);
if (data.dashoustatus !== undefined) {
wx.setStorageSync('dashoustatus', data.dashoustatus);
app.globalData.dashoustatus = data.dashoustatus;
}
if (data.guanshistatus !== undefined) {
wx.setStorageSync('guanshistatus', data.guanshistatus);
app.globalData.guanshistatus = data.guanshistatus;
}
this.setData({ ...updateData, isLoading: false });
if (this.data.isAutoRegistering) {
this.setData({ isAutoRegistering: false });
wx.showToast({ title: '注册成功!', icon: 'success', duration: 2000 });
} else {
wx.showToast({ title: '信息已更新', icon: 'success', duration: 1500 });
}
this.handleJinpaiStatus(data.chenghao);
}
} catch (error) {
console.error('获取打手信息失败:', error);
this.setData({ isLoading: false, isAutoRegistering: false });
wx.showToast({ title: '获取信息失败', icon: 'error', duration: 2000 });
}
},
refreshDashouInfo() {
const now = Date.now();
if (!this.data.canRefresh || (now - this.data.lastRefreshTime < 3000)) return;
this.setData({ lastRefreshTime: now, canRefresh: false });
this.getDashouInfo();
this.fetchChenghaoList(); // 🆕 同时刷新标签
setTimeout(() => this.setData({ canRefresh: true }), 3000);
},
onInviteCodeInput(e) {
this.setData({ inviteCode: e.detail.value.trim() });
},
async onRegister() {
const { inviteCode } = this.data;
if (!inviteCode) {
wx.showToast({ title: '请输入邀请码', icon: 'none', duration: 2000 });
return;
}
if (inviteCode.length > 100) {
wx.showToast({ title: '邀请码不能超过100字符', icon: 'none', duration: 2000 });
return;
}
this.setData({ isLoading: true });
try {
const res = await request({
url: '/yonghu/dashouzhuce',
method: 'POST',
data: { inviteCode: inviteCode }
});
if (res && res.data.code == 200) {
const data = res.data.data;
const app = getApp();
const identityFields = ['dashoustatus', 'guanshistatus', 'shangjiastatus', 'zuzhangstatus'];
identityFields.forEach(field => {
if (data[field] !== undefined) {
wx.setStorageSync(field, data[field]);
app.globalData[field] = data[field];
}
});
if (data.dashoustatus === undefined) {
wx.setStorageSync('dashoustatus', 1);
app.globalData.dashoustatus = 1;
}
const updateData = {
dashouNicheng: data.dashounicheng || '',
zhanghaoStatus: data.zhanghaostatus || '',
yongjin: data.yongjin || '0.00',
zonge: data.zonge || '0.00',
yajin: data.yajin || '0.00',
chenghao: data.chenghao || '',
jinfen: data.jinfen || '0',
clumber: data.clumber || [],
chengjiaoliang: data.chengjiaoliang || '0',
zaixianZhuangtai: data.zaixianzhuangtai || 0,
dashouzhuangtai: data.dashouzhuangtai || '',
// chenghaoList 不从这取
};
Object.assign(app.globalData, updateData);
this.setData({
isDashou: true,
...updateData,
isLoading: false,
isAutoRegistering: false
});
this.loadAvatar();
if (this.data.isAutoRegistering || !wx.getStorageSync('token')) {
this.switchToDashouRole();
}
wx.showToast({ title: '注册成功!', icon: 'success', duration: 2000 });
this.handleJinpaiStatus(data.chenghao);
} else {
this.setData({ isLoading: false, isAutoRegistering: false });
wx.showToast({ title: (res.data && res.data.message) || '注册失败', icon: 'error', duration: 2000 });
}
} catch (error) {
console.error('注册失败:', error);
this.setData({ isLoading: false, isAutoRegistering: false });
wx.showToast({ title: error.message || '注册失败', icon: 'error', duration: 2000 });
}
},
switchToDashouRole() {
const app = getApp();
app.globalData.currentRole = 'dashou';
wx.setStorageSync('currentRole', 'dashou');
const tabBar = this.getTabBar();
if (tabBar && tabBar.refresh) tabBar.refresh();
if (app.connectForCurrentRole) {
app.connectForCurrentRole();
} else if (app.ensureConnection) {
app.ensureConnection();
}
},
copyUid() {
const { uid } = this.data;
if (!uid) {
wx.showToast({ title: 'UID不存在', icon: 'none', duration: 1500 });
return;
}
wx.setClipboardData({
data: uid,
success: () => wx.showToast({ title: '已复制UID', icon: 'success', duration: 1500 }),
fail: () => wx.showToast({ title: '复制失败', icon: 'error', duration: 1500 })
});
},
previewAvatar() {
const { avatarUrl } = this.data;
if (avatarUrl) wx.previewImage({ urls: [avatarUrl] });
},
goToWithdraw() { wx.navigateTo({ url: '/pages/tixian/tixian' }) },
goToReceiveOrder() { wx.navigateTo({ url: '/pages/jiedan/jiedan' }) },
goToMyOrders() { wx.navigateTo({ url: '/pages/dashoudingdan/dashoudingdan' }) },
goToMyPunishment() { wx.navigateTo({ url: '/pages/cfss/cfss/cfss' }) },
goToRecharge() { wx.navigateTo({ url: '/pages/dashouchongzhi/dashouchongzhi' }) },
goToRanking() { wx.navigateTo({ url: '/pages/dashoupaihang/dashoupaihang' }) },
goToModifyInfo() { wx.navigateTo({ url: '/pages/dashouxiugai/dashouxiugai' }) },
goToRules() { wx.navigateTo({ url: '/pages/dashouguize/dashouguize' }) },
// 跳转到考核金牌
goToKaohe() {
wx.navigateTo({ url: '/pages/kaohe_jinpai/kaohe_jinpai' });
},
quickRegisterAsPlatform() {
const that = this;
wx.showModal({
title: '提示',
content: '使用此功能后,您将不属于任何管事,确认注册吗?',
success: (res) => {
if (res.confirm) {
that.setData({ inviteCode: PLATFORM_INVITE_CODE });
that.onRegister();
}
}
});
},
checkAndRedirectToJinpai() {
const isJinpai = wx.getStorageSync('isJinpai');
if (isJinpai === 1) {
wx.redirectTo({ url: '/pages/jinpaids/jinpaids' });
}
},
handleJinpaiStatus(chenghao) {
const isJinpai = (chenghao === '金牌选手') ? 1 : 0;
wx.setStorageSync('isJinpai', isJinpai);
if (isJinpai) {
wx.redirectTo({ url: '/pages/jinpaids/jinpaids' });
}
}
})

View File

@@ -0,0 +1,14 @@
{
"usingComponents": {
"global-notification": "/components/global-notification/global-notification",
"chenghao-tag": "/components/chenghao-tag/chenghao-tag"
},
"navigationBarBackgroundColor": "#0F0A1F",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "服务中心",
"backgroundColor": "#0F0A1F",
"backgroundTextStyle": "light",
"enablePullDownRefresh": false,
"onReachBottomDistance": 50
}

View File

@@ -0,0 +1,212 @@
<!-- pages/dashouzhongxin/dashouzhongxin.wxml -->
<view class="page-container unregistered-page" wx:if="{{!isDashou}}">
<!-- 未注册部分完全不变 -->
<view class="status-tip">您还未解锁此功能,请输入邀请码解锁</view>
<view class="input-container">
<input
class="invite-input"
type="text"
placeholder="请输入100位以内邀请码"
maxlength="100"
value="{{inviteCode}}"
bindinput="onInviteCodeInput"
placeholder-class="placeholder"
/>
</view>
<view class="register-view-btn" bindtap="onRegister">
<text class="register-text">输入邀请码注册</text>
<view class="register-glow"></view>
</view>
<view class="quick-register-btn" bindtap="quickRegisterAsPlatform">
<view class="quick-register-glow"></view>
</view>
</view>
<!-- 已注册状态 -->
<view class="page-container registered-page" wx:else>
<!-- 刷新按钮 -->
<view class="refresh-btn" bindtap="refreshDashouInfo">
<text class="refresh-icon">↻</text>
</view>
<!-- 头像区域 -->
<view class="avatar-section">
<view class="avatar-info">
<image
class="avatar-img"
src="{{avatarUrl}}"
mode="aspectFill"
bindtap="previewAvatar"
/>
<view class="user-info">
<!-- 昵称行:去掉原称号标签 -->
<view class="nickname-row">
<text class="dashou-nicheng">{{dashouNicheng || '打手昵称'}}</text>
<!-- 原称号标签已移除 -->
</view>
<view class="uid-row">
<text class="uid-label">UID:</text>
<text class="uid-value">{{uid || '未获取'}}</text>
<view class="copy-btn" bindtap="copyUid">
<text class="copy-icon">⎘</text>
</view>
</view>
</view>
</view>
</view>
<!-- 🆕 称号标签展示区(自适应两行,超出可滑动) -->
<view class="badge-area" wx:if="{{chenghaoList.length > 0}}">
<scroll-view class="badge-scroll" scroll-y="true" enable-flex>
<view class="badge-grid">
<view class="badge-item" wx:for="{{chenghaoList}}" wx:key="index">
<chenghao-tag
mingcheng="{{item.mingcheng}}"
texiaoJson="{{item.texiao_json}}"
/>
</view>
</view>
</scroll-view>
</view>
<!-- 其余部分完全不变 -->
<!-- 资产区域 -->
<view class="assets-section">
<view class="black-card">
<view class="card-header">
<text class="card-title">资产总览</text>
</view>
<view class="yongjin-row">
<view class="yongjin-info">
<text class="asset-label">我的佣金</text>
<view class="asset-value-container">
<text class="asset-value">{{yongjin || '0.00'}}</text>
<text class="asset-unit">元</text>
</view>
</view>
<view class="withdraw-view-btn" bindtap="goToWithdraw">
<text class="withdraw-text">提现</text>
<view class="withdraw-glow"></view>
</view>
</view>
<view class="assets-bottom">
<view class="bottom-item">
<text class="bottom-label">积分</text>
<text class="bottom-value">{{jinfen || '0'}}</text>
</view>
<view class="vertical-divider"></view>
<view class="bottom-item">
<text class="bottom-label">保证金</text>
<text class="bottom-value">{{yajin || '0.00'}}元</text>
</view>
</view>
</view>
</view>
<!-- 功能区域 -->
<view class="function-section">
<view class="main-function" bindtap="goToReceiveOrder">
<text class="main-function-icon">🎮</text>
<text class="main-function-text">抢单大厅</text>
<view class="main-function-glow"></view>
</view>
<view class="function-grid-second">
<view class="function-item-second" bindtap="goToRecharge">
<text class="function-icon-second">💳</text>
<text class="function-text-second">充值会员保证金</text>
</view>
<view class="function-item-second" bindtap="goToMyOrders">
<text class="function-icon-second">📦</text>
<text class="function-text-second">我的订单</text>
</view>
</view>
<view class="function-grid-third">
<view class="function-item-third" bindtap="goToMyPunishment">
<text class="function-icon-third">⚖️</text>
<text class="function-text-third">我的处罚</text>
</view>
<view class="function-item-third" bindtap="goToRanking">
<text class="function-icon-third">🏆</text>
<text class="function-text-third">排行榜</text>
</view>
</view>
</view>
<!-- 其他功能区域 (邀请人等) 保持原样 -->
<view class="other-section">
<view class="other-list">
<view class="other-item" wx:if="{{inviterCache && inviterCache.uid}}">
<view class="other-item-left" bindtap="contactInviter">
<image class="inviter-avatar" src="{{inviterCache.avatar}}" mode="aspectFill" />
<view class="inviter-text-info">
<text class="other-text">邀请人:{{inviterCache.nicheng || '未知'}}</text>
<text class="inviter-uid-text">ID: {{inviterCache.uid}}</text>
</view>
</view>
<view class="contact-badge" bindtap="contactInviter">联系</view>
</view>
<view class="other-item" wx:else bindtap="contactInviter">
<view class="other-item-left">
<text class="other-icon">🤝</text>
<text class="other-text">联系邀请人</text>
</view>
<text class="arrow-icon"></text>
</view>
<!-- 考核金牌(新增,放在最上面) -->
<view class="other-item" bindtap="goToKaohe">
<view class="other-item-left">
<text class="other-icon">🏅</text>
<text class="other-text">考核金牌</text>
</view>
<text class="arrow-icon"></text>
</view>
<view class="other-item" bindtap="goToModifyInfo">
<view class="other-item-left">
<text class="other-icon">✏️</text>
<text class="other-text">修改个人信息</text>
</view>
<text class="arrow-icon"></text>
</view>
<view class="other-item" bindtap="goToRules">
<view class="other-item-left">
<text class="other-icon">📖</text>
<text class="other-text">用户规则</text>
</view>
<text class="arrow-icon"></text>
</view>
</view>
</view>
</view>
<!-- 加载提示保持不变 -->
<view class="loading-mask" wx:if="{{isLoading}}">
<view class="loading-content">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
</view>
<global-notification id="global-notification" />
<custom-tab-bar />
<popup-notice id="popupNotice" />

View File

@@ -0,0 +1,860 @@
/* pages/dashouzhongxin/dashouzhongxin.wxss */
/* =========== 全局样式 =========== */
page {
background: linear-gradient(135deg, #121212 0%, #1a1a2e 30%, #16213e 70%, #0f3460 100%);
min-height: 100vh;
color: #FFFFFF;
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
background-attachment: fixed;
background-size: cover;
}
.page-container {
min-height: 100vh;
position: relative;
box-sizing: border-box;
}
/* =========== 未注册状态页面 =========== */
.unregistered-page {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 200rpx;
min-height: 100vh;
box-sizing: border-box;
}
.status-tip {
font-size: 32rpx;
color: #FFFFFF;
margin-bottom: 80rpx;
font-weight: 500;
text-align: center;
opacity: 0;
animation: fadeIn 1s ease-out forwards;
line-height: 1.5;
padding: 0 40rpx;
}
.input-container {
width: 85%;
margin-bottom: 60rpx;
box-sizing: border-box;
}
.invite-input {
width: 100%;
height: 90rpx;
background: rgba(255, 255, 255, 0.1);
border-radius: 20rpx;
padding: 0 30rpx;
font-size: 30rpx;
color: #FFFFFF;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.3);
border: 1rpx solid rgba(255, 215, 0, 0.3);
transition: all 0.3s;
box-sizing: border-box;
}
.invite-input:focus {
border-color: #FFD700;
box-shadow: 0 15rpx 40rpx rgba(255, 215, 0, 0.25);
transform: translateY(-2rpx);
}
.placeholder {
color: rgba(255, 255, 255, 0.5);
font-size: 28rpx;
}
.register-view-btn {
width: 85%;
height: 80rpx;
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
border-radius: 40rpx;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 10rpx 30rpx rgba(255, 215, 0, 0.3);
transition: all 0.3s ease;
margin-top: 20rpx;
}
.register-view-btn:active {
transform: scale(0.98);
}
.register-text {
font-size: 34rpx;
font-weight: bold;
color: #333;
letter-spacing: 4rpx;
position: relative;
z-index: 2;
text-shadow: 0 2rpx 4rpx rgba(255, 255, 255, 0.2);
line-height: 80rpx;
}
.register-glow {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg,
transparent,
rgba(255, 255, 255, 0.4),
transparent);
animation: shine 2s infinite;
}
/* 一键注册按钮样式 */
.quick-register-btn {
width: 85%;
height: 80rpx;
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
border-radius: 40rpx;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 10rpx 30rpx rgba(76, 175, 80, 0.4);
transition: all 0.3s ease;
margin-top: 20rpx;
}
.quick-register-btn:active {
transform: scale(0.98);
}
.quick-register-text {
font-size: 34rpx;
font-weight: bold;
color: white;
letter-spacing: 4rpx;
position: relative;
z-index: 2;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
line-height: 80rpx;
}
.quick-register-glow {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg,
transparent,
rgba(255, 255, 255, 0.3),
transparent);
animation: shine 2s infinite;
}
/* =========== 已注册状态页面 =========== */
.registered-page {
padding: 30rpx 25rpx;
box-sizing: border-box;
}
.refresh-btn {
position: absolute;
top: 30rpx;
right: 25rpx;
width: 60rpx;
height: 60rpx;
background: rgba(255, 255, 255, 0.08);
border-radius: 50%;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.25);
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
border: 1rpx solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.refresh-btn:active {
transform: rotate(180deg);
background: rgba(255, 255, 255, 0.12);
}
.refresh-icon {
font-size: 32rpx;
color: #FFD700;
font-weight: bold;
}
/* 头像区域 */
.avatar-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
margin-top: 40rpx;
position: relative;
}
.avatar-info {
display: flex;
align-items: center;
flex: 1;
}
.avatar-img {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
box-shadow: 0 0 0 3rpx rgba(255, 215, 0, 0.5),
0 8rpx 25rpx rgba(0, 0, 0, 0.4);
margin-right: 25rpx;
transition: transform 0.3s ease;
background: linear-gradient(45deg, #2a2a2a, #1a1a1a);
}
.avatar-img:active {
transform: scale(0.95);
}
.user-info {
margin-left: 25rpx;
flex: 1;
}
.nickname-row {
display: flex;
align-items: center;
margin-bottom: 15rpx;
flex-wrap: wrap;
}
.dashou-nicheng {
font-size: 36rpx;
font-weight: bold;
color: #FFFFFF;
margin-right: 15rpx;
max-width: 200rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
}
.chenghao-tag {
background: linear-gradient(135deg,
rgba(255, 215, 0, 0.9) 0%,
rgba(255, 165, 0, 0.9) 100%);
padding: 4rpx 16rpx;
border-radius: 20rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
border: 1rpx solid rgba(255, 215, 0, 0.5);
box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.3);
}
.chenghao-text {
font-size: 22rpx;
font-weight: bold;
color: #333;
text-shadow: 0 1rpx 1rpx rgba(255, 255, 255, 0.8);
letter-spacing: 1rpx;
}
.uid-row {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.uid-label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
margin-right: 10rpx;
font-weight: 500;
}
.uid-value {
font-size: 26rpx;
color: #FFFFFF;
font-family: 'Courier New', monospace;
margin-right: 15rpx;
background: rgba(255, 255, 255, 0.08);
padding: 6rpx 12rpx;
border-radius: 8rpx;
border: 1rpx solid rgba(255, 255, 255, 0.1);
}
.copy-btn {
display: inline-flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
width: 50rpx;
height: 50rpx;
border-radius: 50%;
background: rgba(255, 215, 0, 0.1);
border: 1rpx solid rgba(255, 215, 0, 0.3);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
}
.copy-btn:active {
transform: scale(0.95);
background: rgba(255, 215, 0, 0.2);
}
.copy-icon {
font-size: 28rpx;
color: #FFD700;
font-weight: 500;
}
/* 资产区域 */
.assets-section {
position: relative;
margin-bottom: 30rpx;
}
.black-card {
background: linear-gradient(135deg,
rgba(30, 30, 46, 0.9) 0%,
rgba(22, 33, 62, 0.9) 100%);
border-radius: 20rpx;
padding: 30rpx;
position: relative;
box-shadow:
0 15rpx 40rpx rgba(0, 0, 0, 0.4);
overflow: hidden;
border: 1rpx solid rgba(255, 215, 0, 0.15);
min-height: 180rpx;
}
.card-header {
margin-bottom: 25rpx;
}
.card-title {
font-size: 28rpx;
color: #FFD700;
letter-spacing: 2rpx;
font-weight: bold;
}
.yongjin-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25rpx;
}
.yongjin-info {
flex: 1;
}
.asset-label {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.7);
font-weight: 400;
margin-bottom: 8rpx;
display: block;
}
.asset-value-container {
display: flex;
align-items: baseline;
}
.asset-value {
font-size: 48rpx;
font-weight: bold;
color: #FFD700;
font-family: 'Arial', sans-serif;
letter-spacing: 1rpx;
}
.asset-unit {
font-size: 28rpx;
color: #FFD700;
margin-left: 8rpx;
font-weight: 500;
}
.withdraw-view-btn {
width: 120rpx;
height: 55rpx;
background: linear-gradient(135deg,
rgba(255, 215, 0, 0.9) 0%,
rgba(255, 165, 0, 0.9) 100%);
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow:
0 6rpx 20rpx rgba(255, 215, 0, 0.4);
transition: all 0.3s ease;
border: 1rpx solid rgba(255, 215, 0, 0.3);
position: relative;
overflow: hidden;
}
.withdraw-view-btn:active {
transform: scale(0.95);
}
.withdraw-text {
font-size: 26rpx;
font-weight: bold;
color: #333;
letter-spacing: 2rpx;
position: relative;
z-index: 2;
text-shadow: 0 1rpx 2rpx rgba(255, 255, 255, 0.5);
}
.assets-bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 25rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.1);
}
.bottom-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.bottom-label {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 8rpx;
font-weight: 400;
}
.bottom-value {
font-size: 30rpx;
font-weight: bold;
color: #FFD700;
}
.vertical-divider {
width: 1rpx;
height: 40rpx;
background: linear-gradient(to bottom,
transparent,
rgba(255, 215, 0, 0.5) 50%,
transparent);
}
/* 功能区域 */
.function-section {
background: rgba(255, 255, 255, 0.05);
border-radius: 20rpx;
padding: 30rpx 25rpx;
margin-bottom: 30rpx;
box-shadow:
0 10rpx 25rpx rgba(0, 0, 0, 0.2);
border: 1rpx solid rgba(255, 215, 0, 0.1);
}
/* 抢单大厅 */
.main-function {
display: flex;
flex-direction: column;
align-items: center;
padding: 35rpx 0;
border-radius: 20rpx;
background: linear-gradient(135deg,
rgba(0, 150, 255, 0.2) 0%,
rgba(0, 100, 200, 0.2) 100%);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
margin-bottom: 25rpx;
border: 1rpx solid rgba(0, 150, 255, 0.3);
}
.main-function:active {
transform: scale(0.98);
background: linear-gradient(135deg,
rgba(0, 150, 255, 0.3) 0%,
rgba(0, 100, 200, 0.3) 100%);
}
.main-function-icon {
font-size: 60rpx;
margin-bottom: 20rpx;
}
.main-function-text {
font-size: 32rpx;
color: #FFFFFF;
font-weight: bold;
text-align: center;
position: relative;
z-index: 2;
letter-spacing: 2rpx;
}
.main-function-glow {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg,
transparent,
rgba(255, 255, 255, 0.2),
transparent);
animation: shine 3s infinite;
}
/* 第二行:充值和订单 */
.function-grid-second {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
.function-grid-second .function-item-second:nth-child(1) {
position: relative;
overflow: hidden;
background: linear-gradient(135deg,
rgba(255, 215, 0, 0.2) 0%,
rgba(255, 165, 0, 0.2) 100%);
border: 1rpx solid rgba(255, 215, 0, 0.4);
box-shadow:
0 8rpx 25rpx rgba(255, 215, 0, 0.3),
0 0 15rpx rgba(255, 215, 0, 0.2);
}
.function-grid-second .function-item-second:nth-child(1):active {
transform: scale(0.96);
background: linear-gradient(135deg,
rgba(255, 215, 0, 0.3) 0%,
rgba(255, 165, 0, 0.3) 100%);
}
.function-grid-second .function-item-second:nth-child(1) .function-icon-second {
font-size: 48rpx;
color: #FFD700;
text-shadow:
0 2rpx 8rpx rgba(255, 215, 0, 0.5),
0 0 15rpx rgba(255, 215, 0, 0.3);
animation: goldPulse 2s infinite ease-in-out;
}
.function-grid-second .function-item-second:nth-child(1) .function-text-second {
font-size: 26rpx;
font-weight: bold;
color: #FFD700;
text-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.5);
letter-spacing: 1rpx;
}
.function-grid-second .function-item-second:nth-child(1)::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg,
transparent,
rgba(255, 255, 255, 0.3),
transparent);
animation: shine 2.5s infinite;
}
.function-item-second {
display: flex;
flex-direction: column;
align-items: center;
padding: 25rpx 0;
border-radius: 16rpx;
background: rgba(255, 255, 255, 0.08);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
box-shadow:
0 5rpx 15rpx rgba(0, 0, 0, 0.2);
width: 48%;
}
.function-item-second:active {
transform: scale(0.96);
background: rgba(255, 255, 255, 0.12);
}
.function-icon-second {
font-size: 44rpx;
margin-bottom: 15rpx;
}
.function-text-second {
font-size: 26rpx;
color: #FFFFFF;
font-weight: 500;
text-align: center;
position: relative;
z-index: 2;
}
/* 第三行:我的处罚和排行 */
.function-grid-third {
display: flex;
justify-content: space-between;
}
.function-item-third {
display: flex;
flex-direction: column;
align-items: center;
padding: 22rpx 0;
border-radius: 16rpx;
background: rgba(255, 255, 255, 0.06);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
box-shadow:
0 4rpx 12rpx rgba(0, 0, 0, 0.2);
width: 48%;
}
.function-item-third:active {
transform: scale(0.96);
background: rgba(255, 255, 255, 0.09);
}
.function-icon-third {
font-size: 40rpx;
margin-bottom: 12rpx;
}
.function-text-third {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.9);
font-weight: 500;
text-align: center;
position: relative;
z-index: 2;
}
/* 其他功能区域 */
.other-section {
background: rgba(255, 255, 255, 0.05);
border-radius: 20rpx;
padding: 0 25rpx;
box-shadow:
0 10rpx 25rpx rgba(0, 0, 0, 0.2);
border: 1rpx solid rgba(255, 215, 0, 0.1);
}
.other-list {
padding: 20rpx 0;
}
.other-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 28rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
transition: all 0.3s ease;
}
.other-item:active {
background: rgba(255, 215, 0, 0.05);
transform: translateX(5rpx);
}
.other-item:last-child {
border-bottom: none;
}
.other-item-left {
display: flex;
align-items: center;
}
.other-icon {
font-size: 36rpx;
margin-right: 20rpx;
}
.other-text {
font-size: 28rpx;
color: #FFFFFF;
font-weight: 500;
}
.arrow-icon {
font-size: 36rpx;
color: rgba(255, 255, 255, 0.5);
font-weight: bold;
transition: all 0.3s ease;
}
.other-item:active .arrow-icon {
color: #FFD700;
transform: translateX(5rpx);
}
/* 邀请人专属样式(新增) */
.inviter-avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
margin-right: 20rpx;
border: 2rpx solid rgba(255, 215, 0, 0.5);
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.4);
}
.inviter-text-info {
display: flex;
flex-direction: column;
}
.inviter-uid-text {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.5);
margin-top: 6rpx;
}
.contact-badge {
background: rgba(255, 215, 0, 0.2);
border: 1rpx solid rgba(255, 215, 0, 0.5);
color: #FFD700;
font-size: 24rpx;
padding: 8rpx 20rpx;
border-radius: 20rpx;
font-weight: 500;
transition: all 0.3s;
}
.contact-badge:active {
background: rgba(255, 215, 0, 0.4);
}
/* 加载遮罩 */
.loading-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(18, 18, 18, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-content {
background: rgba(30, 30, 46, 0.95);
padding: 60rpx 80rpx;
border-radius: 30rpx;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.5);
border: 1rpx solid rgba(255, 215, 0, 0.2);
}
.loading-spinner {
width: 80rpx;
height: 80rpx;
border: 6rpx solid rgba(255, 215, 0, 0.2);
border-top: 6rpx solid #FFD700;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 30rpx;
}
.loading-text {
font-size: 28rpx;
color: #FFFFFF;
font-weight: 500;
}
/* =========== 动画定义 =========== */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(30rpx); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes shine {
0% { left: -100%; }
100% { left: 100%; }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes goldPulse {
0%, 100% { transform: scale(1); opacity: 0.9; }
50% { transform: scale(1.05); opacity: 1; }
}
/* ========== 🆕 称号标签展示区 ========== */
/* ========== 🆕 称号标签展示区 ========== */
.badge-area {
width: 100%;
margin: 20rpx 0 30rpx 0;
padding: 0 20rpx;
box-sizing: border-box;
}
.badge-scroll {
width: 100%;
max-height: 140rpx; /* 最多两行高度52*2=104 + 间距36rpx */
overflow-y: auto; /* 超出自动滚动 */
background: transparent;
}
.badge-grid {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
padding: 6rpx 0;
align-items: center;
justify-content: flex-start;
}
.badge-item {
flex-shrink: 0;
/* 不设宽度限制,由组件自身宽度决定 */
}
.other-icon-img {
width: 36rpx;
height: 36rpx;
margin-right: 20rpx;
}

View File

@@ -0,0 +1,103 @@
Page({
onShow() {
// 原有代码...
// 🆕 注册通知组件
this.registerNotificationComponent();
},
// 🆕 新增:注册通知组件
registerNotificationComponent() {
const app = getApp();
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
console.log('🏪 商城页面注册通知组件');
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
data: {
// 图片URL带时间戳避免缓存
imageUrl: '',
// 容器高度初始为100vh加载后调整为图片高度
containerHeight: 0
},
onLoad() {
// 初始设置容器高度为屏幕高度
this.getSystemInfo()
// 构建图片URL带时间戳
this.buildImageUrl()
},
// 获取系统信息
getSystemInfo() {
const res = wx.getSystemInfoSync()
this.setData({
containerHeight: res.windowHeight
})
},
// 构建图片URL关键加时间戳避免缓存
buildImageUrl() {
const app = getApp()
const ossImageUrl = app.globalData.ossImageUrl || ''
const dashouguize = app.globalData.dashouguize || ''
// 加上时间戳参数,强制每次重新加载
const timestamp = new Date().getTime()
const fullImageUrl = ossImageUrl + dashouguize + '?t=' + timestamp
//console.log('规则图片URL带时间戳:', fullImageUrl)
this.setData({
imageUrl: fullImageUrl
})
},
// 图片加载成功
onImageLoad(e) {
//console.log('图片加载成功,原始尺寸:', e.detail.width, 'x', e.detail.height)
// 根据图片实际高度设置容器高度
// 图片宽度为屏幕宽度,高度按比例计算
const systemInfo = wx.getSystemInfoSync()
const screenWidth = systemInfo.screenWidth // 单位px
// 图片的实际像素尺寸
const imageWidth = e.detail.width
const imageHeight = e.detail.height
// 计算在小程序中显示的高度(保持比例)
// 图片在小程序中宽度为100%屏幕宽度,高度按比例缩放
const displayHeight = (imageHeight / imageWidth) * screenWidth
//console.log('计算后显示高度:', displayHeight, 'px')
// 设置容器高度为图片高度
this.setData({
containerHeight: displayHeight
})
},
// 图片加载失败
onImageError() {
console.error('图片加载失败')
wx.showToast({
title: '图片加载失败',
icon: 'none',
duration: 2000
})
},
// 下拉刷新
onPullDownRefresh() {
// 重新构建图片URL带新时间戳
this.buildImageUrl()
// 停止下拉刷新
wx.stopPullDownRefresh()
}
})

View File

@@ -0,0 +1,11 @@
{
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
},
"navigationBarTitleText": "用户规则",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#ffffff",
"enablePullDownRefresh": false,
"backgroundTextStyle": "dark"
}

View File

@@ -0,0 +1,12 @@
<!-- 打手规则页面 - 图片完整覆盖 -->
<view class="container" style="height:{{containerHeight}}px">
<image
src="{{ imageUrl }}"
mode="widthFix"
class="rule-image"
bindload="onImageLoad"
binderror="onImageError"
style="width:100%;height:auto"
></image>
</view>
<global-notification id="global-notification" />

View File

@@ -0,0 +1,15 @@
/* 页面容器 */
.container {
width: 100%;
position: relative;
overflow-y: auto;
background-color: #ffffff;
}
/* 规则图片样式 - 绝对定位,从顶部开始 */
.rule-image {
position: absolute;
top: 0;
left: 0;
display: block;
}

View File

@@ -1,9 +1,10 @@
/**
* 统一排行榜(打手/管事/组长/商家)
* 统一排行榜(打手/管事/组长/商家)— 对齐星雀 fighter-rank排序按收益/流水
* POST /yonghu/phbhqsj
*/
import request from '../../utils/request.js'
import { resolveAvatarUrl, getDefaultAvatarUrl } from '../../utils/avatar.js'
import { getPaihangImgUrls } from '../../utils/page-assets.js'
const RANK_API = '/yonghu/phbhqsj'
@@ -32,8 +33,8 @@ const ROLE_META = {
mainType: 'money',
metrics: [
{ field: 'jiedan_zongliang', label: '接单量', type: 'int' },
{ field: 'chengjiao_zongliang', label: '成交量', type: 'int' },
{ field: 'jiedan_zonge', label: '接单额', type: 'money' },
{ field: 'chengjiao_zongliang', label: '成交量', type: 'int' },
],
},
guanshi: {
@@ -71,7 +72,7 @@ const ROLE_META = {
function buildAvatar(path, app) {
const def = getDefaultAvatarUrl(app)
if (!path) return def
if (!path || path === 'null' || path === 'undefined') return def
return resolveAvatarUrl(path, app) || def
}
@@ -83,7 +84,7 @@ Page({
currentRole: 'dashou',
currentDate: '今日',
roleMeta: ROLE_META.dashou,
sortLabel: '成交总额',
sortLabel: '收益',
isLoading: true,
rankList: [],
topThree: [],
@@ -95,34 +96,34 @@ Page({
onLoad(options) {
const type = options.type || options.rankType || 'dashou'
const validRole = ROLE_META[type] ? type : 'dashou'
this._onConfigReady = () => this.initPageAssets()
getApp().on('configReady', this._onConfigReady)
this.initPageAssets()
this.applyRole(validRole, false)
this.fetchRankList()
},
onUnload() {
if (this._onConfigReady) {
getApp().off('configReady', this._onConfigReady)
}
},
onPullDownRefresh() {
this.initPageAssets()
this.fetchRankList().finally(() => wx.stopPullDownRefresh())
},
initPageAssets() {
this.syncDefaultAvatar()
const g = getApp().globalData
const oss = (g.ossImageUrl || '').replace(/\/$/, '')
const assets = g.paihangAssets || {}
const url = (p) => (p ? `${oss}/${String(p).replace(/^\//, '')}` : '')
this.setData({
imgUrls: {
pageBg: url(assets.pageBg) || `${oss}/beijing/paihangbang/page-bg.jpg`,
cardBg: url(assets.cardBg) || `${oss}/beijing/paihangbang/list-card-bg.png`,
emptyIcon: url(assets.emptyIcon) || `${oss}/beijing/paihangbang/empty.png`,
refreshIcon: url(assets.refreshIcon) || `${oss}/beijing/paihangbang/icon-refresh.png`,
},
})
this.setData({ imgUrls: getPaihangImgUrls() })
},
syncDefaultAvatar() {
const def = getDefaultAvatarUrl()
if (def) this.setData({ defaultAvatar: def })
if (def && def !== this.data.defaultAvatar) {
this.setData({ defaultAvatar: def })
}
},
ensureAvatars() {
@@ -134,7 +135,8 @@ Page({
this.setData({ defaultAvatar: def })
return
}
const isValid = (url) => url && typeof url === 'string' && (url.startsWith('http') || (oss && url.indexOf(oss) === 0))
const isValid = (url) =>
url && typeof url === 'string' && (url.startsWith('http') || (oss && url.indexOf(oss) === 0))
const fix = (item) => ({ ...item, avatar: isValid(item.avatar) ? item.avatar : def })
this.setData({
defaultAvatar: def,
@@ -165,6 +167,7 @@ Page({
},
onRefreshTap() {
this.initPageAssets()
this.fetchRankList()
},
@@ -256,6 +259,7 @@ Page({
},
onShow() {
this.initPageAssets()
this.syncDefaultAvatar()
this.ensureAvatars()
this.registerNotificationComponent()

View File

@@ -75,6 +75,7 @@ page {
font-weight: 600;
background: rgba(107, 163, 247, 0.45);
border-color: rgba(107, 163, 247, 0.65);
box-shadow: 0 0 20rpx rgba(107, 163, 247, 0.2);
}
.date-grid {
@@ -183,16 +184,19 @@ page {
.badge-first {
color: #fff;
background: linear-gradient(145deg, #6BA3F7, #4A7FD4);
box-shadow: 0 4rpx 16rpx rgba(107, 163, 247, 0.4);
}
.badge-silver {
color: #fff;
background: linear-gradient(145deg, #A8B4C4, #7A8899);
box-shadow: 0 4rpx 12rpx rgba(168, 180, 196, 0.25);
}
.badge-bronze {
color: #fff;
background: linear-gradient(145deg, #C4A882, #9A7858);
box-shadow: 0 4rpx 12rpx rgba(196, 168, 130, 0.25);
}
.p-avatar-wrap {
@@ -201,7 +205,12 @@ page {
margin-bottom: 12rpx;
}
.wrap-first { padding: 5rpx; background: linear-gradient(145deg, #8EB8FF, #5B8FD8); }
.wrap-first {
padding: 5rpx;
background: linear-gradient(145deg, #8EB8FF, #5B8FD8);
box-shadow: 0 6rpx 24rpx rgba(107, 163, 247, 0.3);
}
.wrap-silver { background: linear-gradient(145deg, #C8D0DA, #909AA8); }
.wrap-bronze { background: linear-gradient(145deg, #D4B896, #A88860); }
@@ -222,6 +231,7 @@ page {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.p-name-first { font-size: 28rpx; color: #fff; }
@@ -270,6 +280,16 @@ page {
.p-pedestal {
width: 88%;
border-radius: 20rpx 20rpx 0 0;
position: relative;
}
.p-pedestal::before {
content: '';
position: absolute;
top: 0; left: 10%; right: 10%;
height: 2rpx;
background: rgba(255,255,255,0.15);
border-radius: 2rpx;
}
.pedestal-first {
@@ -335,6 +355,7 @@ page {
align-items: center;
padding: 20rpx 22rpx;
background: rgba(12, 16, 28, 0.92);
backdrop-filter: blur(8px);
}
.lc-rank {

View File

@@ -0,0 +1,684 @@
// pages/cfss/cfss.js
import request from '../../utils/request.js'
const app = getApp()
const MEIYE_TIAOSHU = 5
Page({
data: {
zongshu: 0,
daichuli: 0,
yichuli: 0,
shenfen: 0,
chufaList: [],
dangqianye: 1,
haiyougengduo: true,
jiazhaozhong: false,
jiazhaigengduo: false,
scrollHeight: 0,
showXiangqing: false,
xuanzhongChufa: {},
showShensuModal: false,
shensuLiyou: '',
shensuTupian: [],
shensuTupianUrls: [],
shangchuanJindu: 0,
shangchuanZongshu: 0,
jinduWidth: '0%',
isShangchuanzhong: false,
ossImageUrl: '',
fangdouTimer: null
},
onLoad(options) {
this.registerNotificationComponent()
this.initOSSUrl()
this.jisuanGaodu()
this.chushihuaShuju()
},
onShow() {
this.registerNotificationComponent()
},
onUnload() {
if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer)
},
onReachBottom() {
this.shanglaShuaxin()
},
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification')
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
}
}
},
initOSSUrl() {
const ossImageUrl = app.globalData.ossImageUrl || ''
this.setData({ ossImageUrl })
},
jisuanGaodu() {
const systemInfo = wx.getSystemInfoSync()
const windowHeight = systemInfo.windowHeight
const height = windowHeight - 200 - 100 - 40
this.setData({
scrollHeight: height > 0 ? height : 400
})
},
chushihuaShuju() {
this.setData({
dangqianye: 1,
chufaList: [],
haiyougengduo: true
})
this.jiazhuoquTongji()
this.jiazhuoquChufaList()
},
async jiazhuoquTongji() {
try {
const res = await request({
url: '/yonghu/dshqcfjl',
method: 'POST',
data: { qingqiu_tongji: true }
})
if (res.statusCode === 200 && res.data.code === 0) {
const data = res.data.data
this.setData({
zongshu: data.zongshu || 0,
daichuli: data.daichuli || 0,
yichuli: data.yichuli || 0
})
}
} catch (error) {
console.error('获取统计信息失败:', error)
}
},
async jiazhuoquChufaList(isLoadMore = false) {
if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return
if (isLoadMore) {
if (!this.data.haiyougengduo) return
this.setData({ jiazhaigengduo: true })
} else {
this.setData({ jiazhaozhong: true })
}
try {
const params = {
page: this.data.dangqianye,
page_size: MEIYE_TIAOSHU
}
if (this.data.shenfen === 0) {
params.zhuangtai = 0
} else {
params.zhuangtai = 1
}
const res = await request({
url: '/yonghu/dshqcfjl',
method: 'POST',
data: params
})
//console.log('🔴【后端返回的数据】:', res.data)
if (res.statusCode === 200 && res.data.code === 0) {
const data = res.data.data
if (data.list && Array.isArray(data.list)) {
// 🔴【关键修复】在数据加载时就处理好显示字段
const processedList = data.list.map(item => {
// 处理显示时间
let displayTime = '--'
if (item.create_time) {
if (typeof item.create_time === 'number') {
displayTime = String(item.create_time)
} else if (typeof item.create_time === 'string') {
displayTime = item.create_time
} else {
displayTime = String(item.create_time)
}
}
// 处理显示状态
let displayStatus = '未知状态'
let statusClass = 'zhuangtai-weizhi'
const statusNum = Number(item.sqzhuangtai)
if (!isNaN(statusNum)) {
if (statusNum === 0) {
displayStatus = '待处罚'
statusClass = 'zhuangtai-daichuli'
} else if (statusNum === 1) {
displayStatus = '已处罚'
statusClass = 'zhuangtai-yichufa'
} else if (statusNum === 2) {
displayStatus = '已驳回'
statusClass = 'zhuangtai-yibohui'
} else if (statusNum === 3) {
displayStatus = '申诉中'
statusClass = 'zhuangtai-shensuzhong'
}
}
// 处理图片URL在数据加载时就拼接完整URL
const fullZhengjuTupian = (item.zhengju_tupian || []).map(url => this.getFullImageUrl(url))
const fullShensuTupian = (item.shensu_tupian || []).map(url => this.getFullImageUrl(url))
return {
...item,
display_time: displayTime, // 🔴 处理好的显示时间
display_status: displayStatus, // 🔴 处理好的显示状态
status_class: statusClass, // 🔴 处理好的状态样式类
zhengju_tupian: item.zhengju_tupian || [],
shensu_tupian: item.shensu_tupian || [],
full_zhengju_tupian: fullZhengjuTupian, // 🔴 完整图片URL
full_shensu_tupian: fullShensuTupian // 🔴 完整图片URL
}
})
//console.log('🔴【处理后的数据】:', processedList)
let newList
if (isLoadMore) {
newList = [...this.data.chufaList, ...processedList]
} else {
newList = processedList
}
const hasMore = data.has_more === true
this.setData({
chufaList: newList,
haiyougengduo: hasMore,
dangqianye: this.data.dangqianye + 1
})
} else {
this.setData({ haiyougengduo: false })
}
} else {
throw new Error(res.data?.msg || '加载处罚记录失败')
}
} catch (error) {
console.error('加载处罚记录失败:', error)
wx.showToast({
title: error.message || '加载失败',
icon: 'none'
})
this.setData({ haiyougengduo: false })
} finally {
if (isLoadMore) {
this.setData({ jiazhaigengduo: false })
} else {
this.setData({ jiazhaozhong: false })
}
}
},
qiehuanShenfen(e) {
const type = parseInt(e.currentTarget.dataset.type)
if (this.data.shenfen === type) return
this.setData({
shenfen: type,
dangqianye: 1,
chufaList: [],
haiyougengduo: true
})
this.jiazhuoquChufaList()
},
shanglaShuaxin() {
if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer)
const timer = setTimeout(() => {
if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return
if (!this.data.haiyougengduo) return
this.jiazhuoquChufaList(true)
}, 300)
this.setData({ fangdouTimer: timer })
},
chakanXiangqing(e) {
const item = e.currentTarget.dataset.item
this.setData({
xuanzhongChufa: item,
showXiangqing: true
})
},
guanbiXiangqing() {
this.setData({
showXiangqing: false,
xuanzhongChufa: {}
})
},
openShensuModal() {
if (this.data.xuanzhongChufa.ssliyou ||
(this.data.xuanzhongChufa.shensu_tupian && this.data.xuanzhongChufa.shensu_tupian.length > 0)) {
wx.showToast({
title: '您已经提交过申诉,请等待处理结果',
icon: 'none'
})
return
}
this.setData({
showShensuModal: true,
shensuLiyou: '',
shensuTupian: [],
shensuTupianUrls: [],
shangchuanJindu: 0,
shangchuanZongshu: 0,
jinduWidth: '0%'
})
},
closeShensuModal() {
this.setData({
showShensuModal: false,
shensuLiyou: '',
shensuTupian: [],
shensuTupianUrls: []
})
},
onShensuLiyouInput(e) {
const value = e.detail.value.slice(0, 500)
this.setData({ shensuLiyou: value })
},
chooseShensuTupian() {
if (this.data.isShangchuanzhong) return
const shengyu = 9 - this.data.shensuTupian.length
if (shengyu <= 0) {
wx.showToast({
title: '最多只能上传9张图片',
icon: 'none'
})
return
}
wx.chooseMedia({
count: shengyu,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: (res) => {
const newTupian = [...this.data.shensuTupian, ...res.tempFiles.map(file => file.tempFilePath)]
this.setData({ shensuTupian: newTupian.slice(0, 9) })
}
})
},
deleteShensuTupian(e) {
const index = e.currentTarget.dataset.index
const newTupian = [...this.data.shensuTupian]
newTupian.splice(index, 1)
this.setData({ shensuTupian: newTupian })
},
yulanShensuTupian(e) {
const index = e.currentTarget.dataset.index
const urls = this.data.shensuTupian
if (!urls[index]) return
wx.previewImage({
current: urls[index],
urls: urls
})
},
lianxiKefu() {
const kefuLink = app.globalData.kefuConfig?.link || '';
const corpId = app.globalData.kefuConfig?.enterpriseId || '';
if (!kefuLink || !corpId) {
wx.showToast({
title: '客服配置未加载',
icon: 'none'
});
return;
}
wx.openCustomerServiceChat({
extInfo: { url: kefuLink },
corpId: corpId,
success: (res) => {
console.log('跳转客服成功', res);
},
fail: (err) => {
console.error('跳转客服失败', err);
wx.showToast({
title: '暂时无法连接客服',
icon: 'none'
});
}
});
},
async getCOSZhengshu() {
try {
const res = await request({
url: '/dingdan/dsscpz',
method: 'POST',
data: {
dingdan_id: this.data.xuanzhongChufa.dingdan_id,
yongtu: 'chufa'
}
})
if (res.statusCode === 200 && res.data.code === 0 && res.data.data) {
return res.data.data
} else {
throw new Error(res?.data?.msg || '获取上传凭证失败')
}
} catch (error) {
console.error('获取COS凭证失败:', error)
throw error
}
},
initCOSClient(tokenData) {
const COS = require('../../utils/cos-wx-sdk-v5.js')
const credentials = tokenData.credentials || tokenData
const bucket = tokenData.bucket || 'julebu-1361527063'
const region = tokenData.region || 'ap-shanghai'
const cos = new COS({
SimpleUploadMethod: 'putObject',
getAuthorization: async (options, callback) => {
const authParams = {
TmpSecretId: credentials.tmpSecretId,
TmpSecretKey: credentials.tmpSecretKey,
SecurityToken: credentials.sessionToken || '',
StartTime: tokenData.startTime || Math.floor(Date.now() / 1000),
ExpiredTime: tokenData.expiredTime || (Math.floor(Date.now() / 1000) + 7200)
}
callback(authParams)
}
})
return { cos, bucket, region }
},
async shangchuanDanZhangTupian(filePath, cosKey, index, cosInstance, bucket, region) {
return new Promise((resolve) => {
const timeoutId = setTimeout(() => {
resolve({ status: 'optimistic_success', key: cosKey })
}, 2000)
cosInstance.uploadFile({
Bucket: bucket,
Region: region,
Key: cosKey,
FilePath: filePath,
onProgress: (progressInfo) => {
const percent = Math.round(progressInfo.percent * 100)
if (percent === 100) {
clearTimeout(timeoutId)
resolve({ status: 'optimistic_success', key: cosKey })
}
}
})
})
},
async piliangShangchuanShensuTupian() {
const total = this.data.shensuTupian.length
if (total === 0) return []
this.setData({
shangchuanZongshu: total,
shangchuanJindu: 0,
jinduWidth: '0%',
isShangchuanzhong: true
})
let yonghuid = ''
// 方案1直接从全局缓存获取
const uid = wx.getStorageSync('uid')
if (uid) {
yonghuid = String(uid).padStart(7, '0')
//console.log('🔴【从uid获取yonghuid】', yonghuid)
}
// 方案2如果还没有从用户信息获取
if (!yonghuid && app.globalData.userInfo && app.globalData.userInfo.yonghuid) {
yonghuid = String(app.globalData.userInfo.yonghuid).padStart(7, '0')
//console.log('🔴【从userInfo获取yonghuid】', yonghuid)
}
// 方案3如果还没有直接提示并返回
if (!yonghuid) {
wx.showToast({
title: '请先登录',
icon: 'none'
})
this.setData({ isShangchuanzhong: false })
return []
}
const preGeneratedUrls = []
for (let i = 0; i < total; i++) {
const timestamp = Date.now() + i
const random = Math.floor(Math.random() * 10000)
const fileExt = this.getFileExtension(this.data.shensuTupian[i])
const fileName = `${yonghuid}_${timestamp}_${random}${fileExt}`
const cosKey = `a_long/chfajltp/dssstp/${fileName}`
preGeneratedUrls.push(cosKey)
}
let cosClient, bucket, region
try {
const tokenData = await this.getCOSZhengshu()
const { cos, bucket: cosBucket, region: cosRegion } = this.initCOSClient(tokenData)
cosClient = cos
bucket = cosBucket
region = cosRegion
} catch (error) {
wx.showToast({ title: '上传初始化失败', icon: 'none' })
this.setData({ isShangchuanzhong: false })
return []
}
const uploadTasks = []
for (let i = 0; i < total; i++) {
const task = this.shangchuanDanZhangTupian(
this.data.shensuTupian[i],
preGeneratedUrls[i],
i,
cosClient,
bucket,
region
).then((result) => {
const currentDone = i + 1
const jinduPercent = ((currentDone / total) * 100).toFixed(0)
this.setData({
shangchuanJindu: currentDone,
jinduWidth: `${jinduPercent}%`
})
return result
})
uploadTasks.push(task)
}
await Promise.all(uploadTasks)
this.setData({ isShangchuanzhong: false })
return preGeneratedUrls
},
getFileExtension(filePath) {
const lastDotIndex = filePath.lastIndexOf('.')
if (lastDotIndex === -1) return '.jpg'
const ext = filePath.substring(lastDotIndex).toLowerCase()
const allowedExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
return allowedExts.includes(ext) ? ext : '.jpg'
},
async submitShensu() {
if (!this.data.shensuLiyou.trim()) {
wx.showToast({ title: '请输入申诉理由', icon: 'none' }); return
}
if (this.data.shensuTupian.length === 0) {
wx.showModal({
title: '提示',
content: '未上传任何图片,确定要提交申诉吗?',
success: async (res) => {
if (res.confirm) await this.tijiaoShensuData([])
}
})
return
}
wx.showToast({ title: '开始上传图片...', icon: 'none' })
try {
const tupianUrls = await this.piliangShangchuanShensuTupian()
if (!tupianUrls || tupianUrls.length === 0) throw new Error('图片上传失败')
await this.tijiaoShensuData(tupianUrls)
} catch (error) {
console.error('提交申诉失败:', error)
wx.showToast({ title: error.message || '提交失败', icon: 'none' })
}
},
async tijiaoShensuData(tupianUrls) {
wx.showToast({ title: '提交申诉中...', icon: 'none' })
try {
const res = await request({
url: '/yonghu/dscfss',
method: 'POST',
data: {
chufa_id: this.data.xuanzhongChufa.id,
shensu_liyou: this.data.shensuLiyou,
shensu_tupian_urls: tupianUrls
}
})
if (res.statusCode === 200 && res.data.code === 0) {
this.updateChufaRecord({
sqzhuangtai: 3,
ssliyou: this.data.shensuLiyou,
shensu_tupian: tupianUrls
})
this.setData({
showShensuModal: false,
showXiangqing: false,
shensuLiyou: '',
shensuTupian: [],
xuanzhongChufa: {}
})
wx.showToast({ title: '申诉提交成功,请等待处理', icon: 'success' })
setTimeout(() => { this.chushihuaShuju() }, 500)
} else {
throw new Error(res.data?.msg || '提交申诉失败')
}
} catch (error) {
console.error('提交申诉数据失败:', error)
wx.showToast({ title: error.message || '提交失败', icon: 'none' })
}
},
updateChufaRecord(newData) {
const newList = this.data.chufaList.map(item => {
if (item.id === this.data.xuanzhongChufa.id) {
const updatedItem = { ...item, ...newData }
// 🔴 更新显示字段
if (newData.sqzhuangtai === 3) {
updatedItem.display_status = '申诉中'
updatedItem.status_class = 'zhuangtai-shensuzhong'
}
if (newData.shensu_tupian) {
updatedItem.full_shensu_tupian = newData.shensu_tupian.map(url => this.getFullImageUrl(url))
}
return updatedItem
}
return item
})
this.setData({ chufaList: newList })
},
// 🔴【图片URL拼接 - 确保返回完整URL】
getFullImageUrl(relativeUrl) {
//console.log('🔴【图片拼接】输入:', relativeUrl)
if (!relativeUrl) return ''
if (relativeUrl.startsWith('http')) {
//console.log('🔴【已经是完整URL】返回:', relativeUrl)
return relativeUrl
}
const ossUrl = this.data.ossImageUrl
if (!ossUrl) {
//console.log('🔴【OSS地址为空】返回原始URL:', relativeUrl)
return relativeUrl
}
let fullUrl
if (ossUrl.endsWith('/') && relativeUrl.startsWith('/')) {
fullUrl = ossUrl + relativeUrl.substring(1)
} else if (!ossUrl.endsWith('/') && !relativeUrl.startsWith('/')) {
fullUrl = ossUrl + '/' + relativeUrl
} else {
fullUrl = ossUrl + relativeUrl
}
// console.log('🔴【拼接后URL】:', fullUrl)
return fullUrl
},
yulanTupian(e) {
const currentUrl = e.currentTarget.dataset.url
const urls = e.currentTarget.dataset.urls || []
if (!currentUrl) return
const fullCurrentUrl = currentUrl.startsWith('http') ? currentUrl : this.getFullImageUrl(currentUrl)
const fullUrls = urls.map(url => url.startsWith('http') ? url : this.getFullImageUrl(url))
wx.previewImage({
current: fullCurrentUrl,
urls: fullUrls.length > 0 ? fullUrls : [fullCurrentUrl]
})
},
fuzhiWenben(e) {
const text = e.currentTarget.dataset.text
if (!text) return
wx.setClipboardData({
data: text,
success: () => {
wx.showToast({ title: '复制成功', icon: 'success' })
},
fail: () => {
wx.showToast({ title: '复制失败', icon: 'none' })
}
})
},
// 🔴【保留原函数但已不再使用(用于向后兼容)】
formatShijian(shijian) {
if (!shijian) return '--'
return String(shijian)
},
getZhuangtaiText(zhuangtai) {
const statusNum = Number(zhuangtai)
if (statusNum === 0) return '待处罚'
if (statusNum === 1) return '已处罚'
if (statusNum === 2) return '已驳回'
if (statusNum === 3) return '申诉中'
return '未知状态'
},
getZhuangtaiClass(zhuangtai) {
const statusNum = Number(zhuangtai)
if (statusNum === 0) return 'zhuangtai-daichuli'
if (statusNum === 1) return 'zhuangtai-yichufa'
if (statusNum === 2) return 'zhuangtai-yibohui'
if (statusNum === 3) return 'zhuangtai-shensuzhong'
return 'zhuangtai-weizhi'
}
})

View File

@@ -0,0 +1,10 @@
{
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
},
"navigationBarTitleText": "处罚记录",
"navigationBarBackgroundColor": "#0a0a12",
"navigationBarTextStyle": "white",
"backgroundColor": "#0a0a12",
"enablePullDownRefresh": false
}

View File

@@ -0,0 +1,316 @@
<!-- pages/cfss/cfss.wxml -->
<view class="cfss-container">
<!-- 顶部统计卡片 -->
<view class="tongji-card">
<view class="tongji-title">处罚统计</view>
<view class="tongji-content">
<view class="tongji-item">
<text class="tongji-label">总记录</text>
<text class="tongji-value">{{ zongshu }}</text>
</view>
<view class="tongji-item">
<text class="tongji-label">待处理</text>
<text class="tongji-value daichuli-value">{{ daichuli }}</text>
</view>
<view class="tongji-item">
<text class="tongji-label">已处理</text>
<text class="tongji-value yichuli-value">{{ yichuli }}</text>
</view>
</view>
</view>
<!-- 类型切换 -->
<view class="leixing-qiehuan">
<view
class="leixing-item {{ shenfen === 0 ? 'leixing-active' : '' }}"
bindtap="qiehuanShenfen"
data-type="0"
>
<text class="leixing-text">待处理</text>
<text class="leixing-count">{{ daichuli }}</text>
</view>
<view
class="leixing-item {{ shenfen === 1 ? 'leixing-active' : '' }}"
bindtap="qiehuanShenfen"
data-type="1"
>
<text class="leixing-text">已处理</text>
<text class="leixing-count">{{ yichuli }}</text>
</view>
</view>
<!-- 处罚记录列表 -->
<scroll-view
class="chufa-list"
scroll-y
enable-back-to-top
scroll-with-animation
style="height: {{ scrollHeight }}px"
bindscrolltolower="shanglaShuaxin"
>
<!-- 加载中 -->
<view wx:if="{{ jiazhaozhong && chufaList.length === 0 }}" class="jiazai-zhong">
<view class="loading-animation">
<view class="loading-dot"></view>
<view class="loading-dot"></view>
<view class="loading-dot"></view>
</view>
<text>加载中...</text>
</view>
<!-- 🔴【关键修改】处罚记录卡片列表 - 直接使用处理好的字段 -->
<block wx:for="{{ chufaList }}" wx:key="index">
<view
class="chufa-card"
bindtap="chakanXiangqing"
data-item="{{ item }}"
>
<!-- 卡片顶部:时间和状态 -->
<view class="card-top">
<!-- 🔴 直接使用display_time -->
<text class="shijian">{{ item.display_time }}</text>
<!-- 🔴 直接使用display_status和status_class -->
<view class="zhuangtai-badge {{ item.status_class }}">
{{ item.display_status }}
</view>
</view>
<!-- 申请人信息 -->
<view class="qingqiu-row">
<text class="qingqiu-label">申请人:</text>
<text class="qingqiu-value">{{ item.qingqiuid || '--' }}</text>
</view>
<!-- 处罚理由 -->
<view class="liyou-row">
<text class="liyou-label">处罚理由:</text>
<text class="liyou-value">{{ item.cfliyou || '无' }}</text>
</view>
<!-- 订单编号 -->
<view class="dingdan-row">
<text class="dingdan-label">订单编号:</text>
<text class="dingdan-value">{{ item.dingdan_id || '--' }}</text>
</view>
<!-- 卡片边框效果 -->
<view class="card-border"></view>
<view class="card-glow"></view>
</view>
</block>
<!-- 加载更多 -->
<view wx:if="{{ jiazhaigengduo }}" class="jiazai-gengduo">
<view class="scan-line"></view>
<text>加载更多...</text>
</view>
<!-- 没有更多数据 -->
<view wx:if="{{ !haiyougengduo && chufaList.length > 0 }}" class="meiyou-gengduo">
<text>—— 没有更多记录了 ——</text>
</view>
<!-- 空状态 -->
<view wx:if="{{ !jiazhaozhong && chufaList.length === 0 }}" class="kong-zhuangtai">
<view class="empty-icon">⚡</view>
<text class="empty-text">暂无处罚记录</text>
<text class="empty-subtext">所有事务已处理完毕</text>
</view>
</scroll-view>
<!-- 处罚详情弹窗 -->
<view wx:if="{{ showXiangqing }}" class="xiangqing-modal">
<view class="modal-mask" bindtap="guanbiXiangqing"></view>
<view class="modal-content">
<!-- 弹窗头部 -->
<view class="modal-header">
<text class="modal-title">处罚详情</text>
<view class="modal-close" bindtap="guanbiXiangqing">
<text class="close-icon">×</text>
</view>
</view>
<!-- 🔴【弹窗主体 - 确保可以滚动】 -->
<scroll-view class="modal-body" scroll-y style="max-height: 1000rpx;">
<!-- 基本信息 -->
<view class="detail-section">
<view class="section-title">基本信息</view>
<view class="detail-row">
<text class="detail-label">订单编号:</text>
<view class="detail-value-container">
<text class="detail-value">{{ xuanzhongChufa.dingdan_id || '--' }}</text>
<text class="fuzhi-btn" bindtap="fuzhiWenben" data-text="{{ xuanzhongChufa.dingdan_id }}">复制</text>
</view>
</view>
<view class="detail-row">
<text class="detail-label">申请人ID</text>
<view class="detail-value-container">
<text class="detail-value">{{ xuanzhongChufa.qingqiuid || '--' }}</text>
<text class="fuzhi-btn" bindtap="fuzhiWenben" data-text="{{ xuanzhongChufa.qingqiuid }}">复制</text>
</view>
</view>
<view class="detail-row">
<!-- 🔴 直接使用display_time -->
<text class="detail-label">创建时间:</text>
<text class="detail-value">{{ xuanzhongChufa.display_time || '--' }}</text>
</view>
</view>
<!-- 处罚信息 -->
<view class="detail-section">
<view class="section-title">处罚信息</view>
<view class="detail-row">
<text class="detail-label">处罚状态:</text>
<!-- 🔴 直接使用display_status和status_class -->
<view class="zhuangtai-badge-inline {{ xuanzhongChufa.status_class }}">
{{ xuanzhongChufa.display_status }}
</view>
</view>
<view class="detail-row full-row">
<text class="detail-label">处罚理由:</text>
<text class="detail-value liyou-full">{{ xuanzhongChufa.cfliyou || '无' }}</text>
</view>
<!-- 🔴 证据图片展示 - 直接使用full_zhengju_tupian -->
<view wx:if="{{ xuanzhongChufa.full_zhengju_tupian && xuanzhongChufa.full_zhengju_tupian.length > 0 }}" class="detail-row full-row">
<text class="detail-label">证据图片:</text>
<view class="tupian-grid">
<block wx:for="{{ xuanzhongChufa.full_zhengju_tupian }}" wx:key="index">
<view class="tupian-item" bindtap="yulanTupian" data-url="{{ item }}" data-urls="{{ xuanzhongChufa.full_zhengju_tupian }}">
<image
src="{{ item }}"
class="tupian-image"
mode="aspectFill"
/>
</view>
</block>
</view>
</view>
</view>
<!-- 申诉信息 -->
<view wx:if="{{ xuanzhongChufa.ssliyou || (xuanzhongChufa.full_shensu_tupian && xuanzhongChufa.full_shensu_tupian.length > 0) }}" class="detail-section">
<view class="section-title">申诉信息</view>
<view wx:if="{{ xuanzhongChufa.ssliyou }}" class="detail-row full-row">
<text class="detail-label">申诉理由:</text>
<text class="detail-value liyou-full">{{ xuanzhongChufa.ssliyou }}</text>
</view>
<!-- 🔴 申诉图片展示 - 直接使用full_shensu_tupian -->
<view wx:if="{{ xuanzhongChufa.full_shensu_tupian && xuanzhongChufa.full_shensu_tupian.length > 0 }}" class="detail-row full-row">
<text class="detail-label">申诉图片:</text>
<view class="tupian-grid">
<block wx:for="{{ xuanzhongChufa.full_shensu_tupian }}" wx:key="index">
<view class="tupian-item" bindtap="yulanTupian" data-url="{{ item }}" data-urls="{{ xuanzhongChufa.full_shensu_tupian }}">
<image
src="{{ item }}"
class="tupian-image"
mode="aspectFill"
/>
</view>
</block>
</view>
</view>
</view>
</scroll-view>
<!-- 弹窗底部按钮 -->
<view class="modal-footer">
<view class="btn-group">
<!-- 联系客服按钮(始终显示) -->
<view class="btn btn-kefu" bindtap="lianxiKefu">
<text>联系客服</text>
</view>
<!-- 申诉按钮(只有待处罚状态可点击) -->
<view
class="btn {{ xuanzhongChufa.sqzhuangtai === 0 ? 'btn-shensu' : 'btn-shensu-disabled' }}"
bindtap="{{ xuanzhongChufa.sqzhuangtai === 0 ? 'openShensuModal' : '' }}"
>
<text>申诉</text>
</view>
</view>
</view>
</view>
</view>
<!-- 申诉弹窗 -->
<view wx:if="{{ showShensuModal }}" class="shensu-modal">
<view class="modal-mask" bindtap="closeShensuModal"></view>
<view class="modal-content small-modal">
<view class="modal-header">
<text class="modal-title">提交申诉</text>
</view>
<view class="modal-body">
<!-- 申诉理由输入 -->
<view class="input-group">
<view class="input-label">申诉理由</view>
<textarea
class="shensu-textarea"
value="{{ shensuLiyou }}"
bindinput="onShensuLiyouInput"
placeholder="请输入申诉理由最多500字..."
maxlength="500"
auto-height
/>
<view class="word-count">{{ shensuLiyou.length }}/500</view>
</view>
<!-- 申诉图片上传 -->
<view class="input-group">
<view class="input-label">申诉图片</view>
<view class="upload-tip">最多可上传9张图片</view>
<!-- 图片网格 -->
<view class="tupian-grid-upload">
<!-- 已上传图片 -->
<block wx:for="{{ shensuTupian }}" wx:key="index">
<view class="tupian-item-upload">
<image
src="{{ item }}"
class="tupian-image-upload"
mode="aspectFill"
bindtap="yulanShensuTupian"
data-index="{{ index }}"
/>
<view class="tupian-delete" bindtap="deleteShensuTupian" data-index="{{ index }}">
<text>×</text>
</view>
</view>
</block>
<!-- 添加按钮 -->
<view
wx:if="{{ shensuTupian.length < 9 }}"
class="tupian-add-btn"
bindtap="chooseShensuTupian"
>
<view class="add-icon">+</view>
<text class="add-text">添加图片</text>
</view>
</view>
</view>
<!-- 上传进度 -->
<view wx:if="{{ shangchuanZongshu > 0 }}" class="upload-progress">
<view class="progress-title">上传进度</view>
<view class="progress-bar">
<view class="progress-inner" style="width: {{ jinduWidth }}"></view>
</view>
<view class="progress-text">{{ shangchuanJindu }}/{{ shangchuanZongshu }}</view>
</view>
</view>
<view class="modal-footer">
<view class="btn-group">
<view class="btn btn-quxiao" bindtap="closeShensuModal">
<text>取消</text>
</view>
<view class="btn btn-queren" bindtap="submitShensu">
<text>提交申诉</text>
</view>
</view>
</view>
</view>
</view>
<!-- 全局通知组件 -->
<global-notification id="global-notification" />
</view>

View File

@@ -0,0 +1,963 @@
/* pages/cfss/cfss.wxss */
/* 🔴【恢复您原来的深色背景,但调淡一点】 */
:root {
/* 🔴【颜色调淡方案】 */
--cyber-blue: rgba(0, 243, 255, 0.7); /* 蓝色调淡70% */
--cyber-pink: rgba(255, 0, 255, 0.7); /* 粉色调淡70% */
--cyber-purple: rgba(157, 0, 255, 0.7); /* 紫色调淡70% */
--cyber-green: rgba(0, 255, 157, 0.7); /* 绿色调淡70% */
--cyber-red: rgba(255, 0, 102, 0.7); /* 红色调淡70% */
/* 🔴【背景改为淡灰色,不是深色,也不是白色】 */
--cyber-bg: #2a2a3a; /* 深灰色背景,比原来的淡 */
--cyber-card: #333344; /* 卡片:比背景稍深的灰色 */
--cyber-border: rgba(255, 255, 255, 0.15); /* 边框:半透明白色 */
--cyber-glow: rgba(0, 243, 255, 0.2); /* 光效:调淡 */
/* 文字颜色保持不变 */
--cyber-text: #ffffff;
--cyber-text-light: #e6e6e6;
--cyber-text-lighter: #cccccc;
}
.cfss-container {
padding: 30rpx;
background: var(--cyber-bg);
min-height: 100vh;
color: var(--cyber-text) !important;
position: relative;
overflow: hidden;
box-sizing: border-box;
}
/* 恢复网格背景 */
.cfss-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
linear-gradient(rgba(0, 243, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 243, 255, 0.05) 1px, transparent 1px);
background-size: 40rpx 40rpx;
pointer-events: none;
opacity: 0.15;
z-index: 0;
}
/* ====== 统计卡片 ====== */
.tongji-card {
background: rgba(255, 0, 102, 0.3); /* 🔴 调淡为30%透明度 */
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 30rpx;
border: 1rpx solid var(--cyber-border);
box-shadow:
0 0 20rpx rgba(255, 0, 102, 0.2),
inset 0 0 10rpx rgba(0, 243, 255, 0.05);
position: relative;
overflow: hidden;
z-index: 1;
}
.tongji-title {
color: var(--cyber-blue);
font-size: 34rpx;
font-weight: bold;
margin-bottom: 30rpx;
text-shadow: 0 0 8rpx var(--cyber-blue);
letter-spacing: 2rpx;
}
.tongji-content {
display: flex;
justify-content: space-between;
}
.tongji-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.tongji-label {
color: var(--cyber-text-light) !important;
font-size: 26rpx;
margin-bottom: 15rpx;
opacity: 0.9;
}
.tongji-value {
color: var(--cyber-text) !important;
font-size: 44rpx;
font-weight: bold;
text-shadow: 0 0 8rpx currentColor;
}
.daichuli-value {
color: var(--cyber-red) !important;
text-shadow: 0 0 8rpx rgba(255, 0, 102, 0.3);
}
.yichuli-value {
color: var(--cyber-green) !important;
text-shadow: 0 0 8rpx rgba(0, 255, 157, 0.3);
}
/* ====== 类型切换 ====== */
.leixing-qiehuan {
display: flex;
background: rgba(255, 0, 102, 0.3); /* 🔴 调淡为30%透明度 */
border-radius: 16rpx;
padding: 10rpx;
margin-bottom: 30rpx;
border: 1rpx solid var(--cyber-border);
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.2);
z-index: 1;
}
.leixing-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 25rpx 0;
border-radius: 12rpx;
transition: all 0.3s;
}
.leixing-item:active {
transform: scale(0.98);
}
.leixing-active {
background: linear-gradient(135deg, rgba(0, 243, 255, 0.15), rgba(157, 0, 255, 0.15));
box-shadow: 0 0 15rpx rgba(0, 243, 255, 0.2);
}
.leixing-text {
font-size: 28rpx;
font-weight: bold;
margin-bottom: 8rpx;
color: var(--cyber-text-light) !important;
}
.leixing-active .leixing-text {
color: var(--cyber-text) !important;
text-shadow: 0 0 8rpx var(--cyber-blue);
}
.leixing-count {
font-size: 24rpx;
color: var(--cyber-text-lighter) !important;
opacity: 0.9;
}
.leixing-active .leixing-count {
color: rgba(255, 255, 255, 0.9) !important;
}
/* ====== 处罚列表 ====== */
.chufa-list {
margin-bottom: 0;
padding-bottom: 30rpx;
z-index: 1;
}
/* 加载动画 */
.jiazai-zhong {
display: flex;
flex-direction: column;
align-items: center;
padding: 60rpx 0;
color: var(--cyber-blue);
}
.loading-animation {
display: flex;
justify-content: center;
margin-bottom: 20rpx;
}
.loading-dot {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background-color: var(--cyber-blue);
margin: 0 10rpx;
animation: pulse 1.5s infinite ease-in-out;
}
.loading-dot:nth-child(2) {
animation-delay: 0.2s;
}
.loading-dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes pulse {
0%, 100% {
transform: scale(0.8);
opacity: 0.5;
}
50% {
transform: scale(1.2);
opacity: 1;
}
}
/* 🔴【关键:处罚卡片背景改为淡灰色】 */
.chufa-card {
background: rgba(50, 50, 65, 0.8); /* 🔴 淡灰色卡片,半透明 */
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 30rpx;
border: 1rpx solid var(--cyber-border);
position: relative;
overflow: hidden;
transition: all 0.3s;
z-index: 1;
}
.chufa-card:active {
transform: translateY(-4rpx);
box-shadow:
0 10rpx 30rpx rgba(0, 0, 0, 0.3),
0 0 25rpx var(--cyber-glow);
}
.card-border {
position: absolute;
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
border-radius: 21rpx;
background: linear-gradient(45deg, rgba(255, 0, 255, 0.3), rgba(0, 243, 255, 0.3), rgba(255, 0, 255, 0.3), rgba(0, 243, 255, 0.3));
z-index: -1;
opacity: 0;
transition: opacity 0.3s ease;
}
.chufa-card:active .card-border {
opacity: 0.3;
}
.card-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25rpx;
}
/* 🔴【时间必须显示】 */
.shijian {
color: var(--cyber-text-light) !important;
font-size: 26rpx;
font-family: 'Courier New', monospace;
letter-spacing: 1rpx;
opacity: 0.9;
}
/* 🔴【状态标签 - 必须显示出来】 */
.zhuangtai-badge {
padding: 8rpx 25rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 2rpx;
border: 1rpx solid;
box-shadow: 0 0 8rpx;
color: #ffffff !important;
}
.zhuangtai-daichuli {
background: rgba(255, 0, 102, 0.2);
border-color: var(--cyber-red);
box-shadow: 0 0 8rpx rgba(255, 0, 102, 0.2);
}
.zhuangtai-yichufa {
background: rgba(0, 255, 157, 0.2);
border-color: var(--cyber-green);
box-shadow: 0 0 8rpx rgba(0, 255, 157, 0.2);
}
.zhuangtai-yibohui {
background: rgba(102, 102, 102, 0.2);
border-color: #666;
box-shadow: 0 0 8rpx rgba(102, 102, 102, 0.2);
}
.zhuangtai-shensuzhong {
background: rgba(255, 165, 0, 0.2);
border-color: #FFA500;
box-shadow: 0 0 8rpx rgba(255, 165, 0, 0.2);
}
.zhuangtai-weizhi {
background: rgba(128, 0, 255, 0.2); /* 未知状态:紫色 */
border-color: rgba(128, 0, 255, 0.5);
box-shadow: 0 0 8rpx rgba(128, 0, 255, 0.2);
}
/* 其他文字内容 */
.qingqiu-row, .liyou-row, .dingdan-row {
display: flex;
align-items: flex-start;
margin-bottom: 20rpx;
}
.qingqiu-label, .liyou-label, .dingdan-label {
color: var(--cyber-text-light) !important;
font-size: 26rpx;
font-weight: bold;
margin-right: 15rpx;
opacity: 0.9;
flex-shrink: 0;
}
.qingqiu-value, .liyou-value, .dingdan-value {
color: var(--cyber-text) !important;
font-size: 26rpx;
flex: 1;
word-break: break-all;
}
.liyou-value {
line-height: 1.5;
}
.dingdan-value {
font-family: 'Courier New', monospace;
letter-spacing: 1rpx;
}
/* 加载更多 */
.jiazai-gengduo {
text-align: center;
padding: 40rpx 0;
color: var(--cyber-blue);
position: relative;
overflow: hidden;
}
.scan-line {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 2rpx;
background: linear-gradient(90deg, transparent, var(--cyber-blue), transparent);
animation: scan 2s linear infinite;
}
@keyframes scan {
0% { left: -100%; }
100% { left: 100%; }
}
/* 没有更多数据 */
.meiyou-gengduo {
text-align: center;
padding: 40rpx 0;
color: var(--cyber-green);
font-size: 28rpx;
opacity: 0.9;
}
/* 空状态 */
.kong-zhuangtai {
display: flex;
flex-direction: column;
align-items: center;
padding: 100rpx 40rpx;
text-align: center;
z-index: 1;
}
.empty-icon {
font-size: 120rpx;
color: var(--cyber-blue);
margin-bottom: 30rpx;
text-shadow: 0 0 15rpx var(--cyber-blue);
animation: pulse 2s infinite;
}
.empty-text {
font-size: 34rpx;
color: var(--cyber-text) !important;
margin-bottom: 15rpx;
font-weight: bold;
}
.empty-subtext {
font-size: 28rpx;
color: var(--cyber-text-light) !important;
opacity: 0.8;
}
/* ====== 弹窗样式 ====== */
.xiangqing-modal,
.shensu-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.modal-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
}
/* 🔴【关键:弹窗背景改为不透明的淡灰色】 */
.modal-content {
position: relative;
background: #3a3a4a; /* 🔴 淡灰色,不透明 */
border-radius: 30rpx;
width: 90%;
max-height: 80vh;
display: flex;
flex-direction: column;
border: 1rpx solid var(--cyber-border);
box-shadow: 0 0 35rpx rgba(0, 243, 255, 0.3);
z-index: 1001;
backdrop-filter: blur(0px); /* 去掉模糊效果 */
}
.small-modal {
max-height: 85vh;
width: 85%;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 40rpx 30rpx 30rpx 30rpx;
border-bottom: 1rpx solid rgba(0, 243, 255, 0.2);
flex-shrink: 0;
}
.modal-title {
font-size: 36rpx;
font-weight: bold;
color: var(--cyber-blue);
text-shadow: 0 0 8rpx var(--cyber-blue);
}
.modal-close {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: rgba(0, 243, 255, 0.1);
border: 1rpx solid rgba(0, 243, 255, 0.3);
}
.modal-close:active {
background: rgba(0, 243, 255, 0.2);
transform: scale(0.95);
}
.close-icon {
font-size: 40rpx;
color: var(--cyber-blue);
line-height: 1;
}
/* 弹窗主体 */
.modal-body {
flex: 1;
padding: 30rpx;
overflow-y: auto;
min-height: 200rpx;
box-sizing: border-box;
}
/* 详情区域 */
.detail-section {
margin-bottom: 40rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: var(--cyber-blue);
margin-bottom: 25rpx;
padding-bottom: 15rpx;
border-bottom: 1rpx solid rgba(0, 243, 255, 0.2);
text-shadow: 0 0 8rpx var(--cyber-blue);
}
.detail-row {
display: flex;
margin-bottom: 25rpx;
align-items: center;
}
.detail-label {
color: var(--cyber-text-light) !important;
font-size: 30rpx;
min-width: 180rpx;
margin-right: 20rpx;
line-height: 1.5;
opacity: 0.9;
flex-shrink: 0;
}
.detail-value-container {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
}
.detail-value {
color: var(--cyber-text) !important;
font-size: 30rpx;
line-height: 1.5;
word-break: break-all;
opacity: 0.95;
flex: 1;
margin-right: 20rpx;
}
.fuzhi-btn {
color: var(--cyber-blue);
font-size: 24rpx;
padding: 8rpx 20rpx;
border-radius: 8rpx;
background: rgba(0, 243, 255, 0.1);
border: 1rpx solid rgba(0, 243, 255, 0.3);
flex-shrink: 0;
}
.fuzhi-btn:active {
background: rgba(0, 243, 255, 0.2);
}
.full-row {
flex-direction: column;
align-items: flex-start;
}
.full-row .detail-label {
margin-bottom: 10rpx;
min-width: auto;
}
/* 弹窗内的状态标签 */
.zhuangtai-badge-inline {
padding: 8rpx 24rpx;
border-radius: 20rpx;
font-size: 26rpx;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1rpx;
border: 1rpx solid;
min-width: 120rpx;
text-align: center;
color: #ffffff !important;
}
/* 理由方框 */
.liyou-full {
background: rgba(255, 255, 255, 0.05);
padding: 25rpx 30rpx;
border-radius: 15rpx;
line-height: 1.6;
border: 1rpx solid rgba(255, 255, 255, 0.1);
color: var(--cyber-text) !important;
width: 100%;
box-sizing: border-box;
margin: 0;
display: block;
opacity: 0.95;
}
/* 图片网格样式 */
.tupian-grid {
display: flex;
flex-wrap: wrap;
margin-top: 10rpx;
margin-left: -5rpx;
margin-right: -5rpx;
}
.tupian-item {
width: calc(33.333% - 10rpx);
margin: 5rpx;
aspect-ratio: 1;
border-radius: 10rpx;
overflow: hidden;
position: relative;
border: 1rpx solid rgba(0, 243, 255, 0.2);
}
.tupian-image {
width: 100%;
height: 100%;
}
/* 弹窗底部按钮 */
.modal-footer {
padding: 30rpx;
border-top: 1rpx solid rgba(0, 243, 255, 0.2);
flex-shrink: 0;
}
.btn-group {
display: flex;
gap: 20rpx;
}
.btn {
flex: 1;
height: 90rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 15rpx;
font-size: 32rpx;
font-weight: bold;
transition: all 0.3s;
border: 1rpx solid;
box-shadow: 0 0 12rpx;
color: var(--cyber-text) !important;
}
.btn:active {
transform: scale(0.98);
}
/* 按钮样式 */
.btn-kefu {
background: rgba(0, 243, 255, 0.1);
border-color: var(--cyber-blue);
box-shadow: 0 0 12rpx rgba(0, 243, 255, 0.2);
}
.btn-shensu {
background: rgba(255, 165, 0, 0.1);
border-color: #FFA500;
box-shadow: 0 0 12rpx rgba(255, 165, 0, 0.2);
}
.btn-shensu-disabled {
background: rgba(102, 102, 102, 0.1);
border-color: #666;
box-shadow: 0 0 12rpx rgba(102, 102, 102, 0.2);
opacity: 0.5;
}
.btn-quxiao {
background: rgba(255, 255, 255, 0.07);
border-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 12rpx rgba(255, 255, 255, 0.1);
}
.btn-queren {
background: rgba(0, 255, 157, 0.1);
border-color: var(--cyber-green);
box-shadow: 0 0 12rpx rgba(0, 255, 157, 0.2);
}
/* 申诉弹窗输入样式 */
.input-group {
margin-bottom: 40rpx;
}
.input-label {
color: var(--cyber-text-light) !important;
font-size: 30rpx;
margin-bottom: 15rpx;
font-weight: bold;
}
.shensu-textarea {
width: 100%;
min-height: 200rpx;
padding: 25rpx 30rpx;
background: rgba(255, 255, 255, 0.05);
border-radius: 15rpx;
font-size: 30rpx;
line-height: 1.5;
color: var(--cyber-text) !important;
border: 1rpx solid rgba(0, 243, 255, 0.2);
box-sizing: border-box;
}
.shensu-textarea::placeholder {
color: var(--cyber-text-lighter) !important;
opacity: 0.6;
}
.word-count {
text-align: right;
color: var(--cyber-text-lighter) !important;
font-size: 24rpx;
margin-top: 10rpx;
opacity: 0.7;
}
.upload-tip {
color: var(--cyber-text-lighter) !important;
font-size: 26rpx;
margin-bottom: 20rpx;
opacity: 0.8;
}
/* 上传图片网格 */
.tupian-grid-upload {
display: flex;
flex-wrap: wrap;
margin-left: -5rpx;
margin-right: -5rpx;
}
.tupian-item-upload {
width: calc(33.333% - 10rpx);
margin: 5rpx;
aspect-ratio: 1;
border-radius: 10rpx;
overflow: hidden;
position: relative;
border: 1rpx solid rgba(0, 243, 255, 0.2);
}
.tupian-image-upload {
width: 100%;
height: 100%;
}
.tupian-delete {
position: absolute;
top: 5rpx;
right: 5rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: rgba(255, 0, 102, 0.7);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 30rpx;
font-weight: bold;
}
.tupian-add-btn {
width: calc(33.333% - 10rpx);
margin: 5rpx;
aspect-ratio: 1;
border-radius: 10rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 2rpx dashed rgba(0, 243, 255, 0.4);
color: var(--cyber-blue);
}
.tupian-add-btn:active {
border-color: var(--cyber-blue);
background: rgba(0, 243, 255, 0.07);
}
.add-icon {
font-size: 60rpx;
margin-bottom: 10rpx;
}
.add-text {
font-size: 24rpx;
}
/* 上传进度 */
.upload-progress {
margin-top: 30rpx;
padding: 25rpx 30rpx;
background: rgba(255, 255, 255, 0.05);
border-radius: 15rpx;
border: 1rpx solid rgba(0, 243, 255, 0.2);
}
.progress-title {
color: var(--cyber-blue);
font-size: 28rpx;
margin-bottom: 15rpx;
font-weight: bold;
}
.progress-bar {
height: 20rpx;
background: rgba(255, 255, 255, 0.07);
border-radius: 10rpx;
overflow: hidden;
margin-bottom: 10rpx;
}
.progress-inner {
height: 100%;
background: linear-gradient(90deg, var(--cyber-blue), var(--cyber-purple));
border-radius: 10rpx;
transition: width 0.3s ease;
}
.progress-text {
text-align: center;
color: var(--cyber-text-light) !important;
font-size: 26rpx;
opacity: 0.9;
}
/* 响应式调整 */
@media (max-width: 375px) {
.tongji-value {
font-size: 36rpx;
}
.shijian,
.qingqiu-label,
.liyou-label,
.dingdan-label {
font-size: 24rpx;
}
.qingqiu-value,
.liyou-value,
.dingdan-value {
font-size: 24rpx;
}
.modal-content {
width: 95%;
}
}
/* 🔴【颜色调整】卡片和弹窗背景色稍微调淡一点 */
:root {
--cyber-bg: #2c2c3c; /* 从#2a2a3a调亮一点 */
--cyber-card: #363648; /* 从#333344调亮一点 */
}
/* 🔴【卡片背景色调整 - 更淡一点】 */
.chufa-card {
background: rgba(130, 141, 146, 0.8); /* 从rgba(50,50,65,0.8)调亮 */
}
/* 🔴【弹窗背景色调整 - 更淡一点】 */
.modal-content {
background: #acacda; /* 从#3a3a4a调亮 */
}
/* 🔴【增加弹窗高度】 */
.modal-content {
max-height: 88vh; /* 从80vh增加 */
}
.small-modal {
max-height: 88vh; /* 从85vh调整 */
}
/* 🔴【增加弹窗内部滚动区域高度】 */
/* 修改wxml中对应的style */
/* 需要将:<scroll-view class="modal-body" scroll-y style="max-height: 500rpx;"> */
/* 改为:<scroll-view class="modal-body" scroll-y style="max-height: 600rpx;"> */
/* pages/cfss/cfss.wxss 中新增样式 */
/* 🔴【关键修改】图片网格和图片样式 */
.tupian-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12rpx;
margin-top: 10rpx;
width: 100%;
}
.tupian-item {
position: relative;
width: 100%;
aspect-ratio: 1;
border-radius: 12rpx;
overflow: hidden;
border: 1rpx solid rgba(0, 243, 255, 0.3);
background-color: rgba(0, 0, 0, 0.2);
}
.tupian-image {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
/* 图片预览遮罩 */
.tupian-preview {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.tupian-item:hover .tupian-preview {
opacity: 1;
}
.preview-text {
color: white;
font-size: 24rpx;
background: rgba(0, 243, 255, 0.7);
padding: 8rpx 16rpx;
border-radius: 8rpx;
}

View File

@@ -0,0 +1,422 @@
import request from '../../utils/request.js'
Page({
data: {
// 头像相关
touxiangUrl: '', // 完整头像URL
touxiangRelative: '', // 头像相对URL用于上传后更新
// 打手信息字段
dashouUid: '', // UID不可修改
dashouNicheng: '', // 昵称
dashouJieshao: '', // 个人介绍
dashouDianhua: '', // 电话
dashouWeixin: '', // 微信
// 页面状态
isLoading: false, // 是否正在加载
isDataFromApi: false, // 数据是否来自API用于显示提示
isSubmitting: false, // 是否正在提交
hasLoadedFromCache: false, // 是否已从缓存加载
// 验证状态
nichengValid: true,
dianhuaValid: true,
weixinValid: true,
jieshaoValid: true
},
onLoad() {
// 页面加载时初始化数据
this.initPageData()
},
onShow() {
// 每次页面显示时检查头像是否需要更新
this.registerNotificationComponent();
this.checkAvatarUpdate()
},
// 🆕 新增:注册通知组件
registerNotificationComponent() {
const app = getApp();
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
console.log('🏪 商城页面注册通知组件');
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
// 初始化页面数据
async initPageData() {
this.setData({ isLoading: true })
try {
// 1. 从缓存加载基本信息
const cachedData = this.loadFromCache()
// 2. 检查是否需要从API加载
const needApiLoad = this.checkNeedApiLoad(cachedData)
if (needApiLoad) {
// 从API加载数据
await this.loadFromApi()
this.setData({ isDataFromApi: true })
} else {
// 使用缓存数据
this.setDataFromCache(cachedData)
this.setData({ isDataFromApi: false })
}
// 3. 构建完整头像URL
this.buildAvatarUrl()
} catch (error) {
console.error('初始化页面数据失败:', error)
wx.showToast({
title: '数据加载失败',
icon: 'error',
duration: 2000
})
} finally {
this.setData({ isLoading: false, hasLoadedFromCache: true })
}
},
// 从缓存加载数据
loadFromCache() {
const app = getApp()
return {
// 从缓存获取头像相对URL
touxiang: wx.getStorageSync('touxiang') || '',
// 从缓存获取用户ID
uid: wx.getStorageSync('uid') || '',
// 从缓存获取打手信息
dsnc: wx.getStorageSync('dsnc') || '',
dsjieshao: wx.getStorageSync('dsjieshao') || '',
dsdianhua: wx.getStorageSync('dsdianhua') || '',
dsweixin: wx.getStorageSync('dsweixin') || ''
}
},
// 检查是否需要从API加载数据
checkNeedApiLoad(cachedData) {
// 如果昵称或介绍有一个为空则需要从API加载
return !cachedData.dsnc || !cachedData.dsjieshao
},
// 设置缓存数据到页面
setDataFromCache(cachedData) {
const app = getApp()
// 处理空值显示
const formatEmptyValue = (value) => {
return value && value.trim() !== '' ? value : '暂未填写'
}
this.setData({
dashouUid: cachedData.uid || '',
dashouNicheng: cachedData.dsnc || '',
dashouJieshao: cachedData.dsjieshao || '',
dashouDianhua: formatEmptyValue(cachedData.dsdianhua),
dashouWeixin: formatEmptyValue(cachedData.dsweixin),
touxiangRelative: cachedData.touxiang || ''
})
},
// 从API加载数据
async loadFromApi() {
try {
const res = await request({
url: '/yonghu/dszjxxhq',
method: 'POST'
})
if (res && res.data.code === 200) {
const data = res.data.data || {}
// 格式化空值
const formatValue = (value) => {
if (value === null || value === undefined || value.trim() === '') {
return '暂未填写'
}
return value
}
// 更新页面数据
this.setData({
dashouNicheng: formatValue(data.nicheng),
dashouJieshao: formatValue(data.jieshao),
dashouDianhua: formatValue(data.dianhua),
dashouWeixin: formatValue(data.wechat)
})
// 更新缓存(即使为空也存储,避免重复请求)
this.updateCache(data)
} else {
throw new Error(res?.data?.msg || '获取信息失败')
}
} catch (error) {
console.error('API加载失败:', error)
throw error
}
},
// 更新缓存
updateCache(data) {
// 存储到缓存,确保字段存在(即使为空)
wx.setStorageSync('dsnc', data.nicheng || '')
wx.setStorageSync('dsjieshao', data.jieshao || '')
wx.setStorageSync('dsdianhua', data.dianhua || '')
wx.setStorageSync('dsweixin', data.wechat || '')
},
// 构建完整头像URL
buildAvatarUrl() {
const app = getApp()
// 获取头像相对路径
const touxiangRelative = this.data.touxiangRelative || ''
// 获取全局配置
const ossImageUrl = app.globalData.ossImageUrl || ''
const morentouxiang = app.globalData.morentouxiang || ''
// 构建完整URL
let fullUrl = ''
if (touxiangRelative && touxiangRelative.trim() !== '') {
fullUrl = ossImageUrl + touxiangRelative
} else {
fullUrl = ossImageUrl + morentouxiang
}
this.setData({ touxiangUrl: fullUrl })
},
// 检查头像是否需要更新
checkAvatarUpdate() {
const app = getApp()
const currentAvatar = wx.getStorageSync('touxiang') || ''
// 如果缓存中的头像与当前显示的不同重新构建URL
if (currentAvatar !== this.data.touxiangRelative) {
this.setData({ touxiangRelative: currentAvatar })
this.buildAvatarUrl()
}
},
// 事件处理函数
onAvatarTap() {
// 头像点击事件(后续可扩展为更换头像)
wx.showToast({
title: '头像上传功能待实现',
icon: 'none',
duration: 2000
})
},
onNichengInput(e) {
const value = e.detail.value
this.setData({
dashouNicheng: value,
nichengValid: value.length <= 20
})
},
onDianhuaInput(e) {
const value = e.detail.value
// 简单的手机号验证
const isValid = /^1[3-9]\d{9}$/.test(value) || value === '' || value === '暂未填写'
this.setData({
dashouDianhua: value,
dianhuaValid: isValid
})
},
onWeixinInput(e) {
const value = e.detail.value
this.setData({
dashouWeixin: value,
weixinValid: value.length <= 30
})
},
onJieshaoInput(e) {
const value = e.detail.value
this.setData({
dashouJieshao: value,
jieshaoValid: value.length <= 200
})
},
// 表单验证
validateForm() {
const {
dashouNicheng,
dashouDianhua,
dashouWeixin,
dashouJieshao,
nichengValid,
dianhuaValid,
weixinValid,
jieshaoValid
} = this.data
// 1. 验证昵称
if (!dashouNicheng || dashouNicheng.trim() === '') {
wx.showToast({
title: '昵称不能为空',
icon: 'none',
duration: 2000
})
return false
}
if (dashouNicheng.length > 20) {
wx.showToast({
title: '昵称不能超过20字',
icon: 'none',
duration: 2000
})
return false
}
// 2. 验证手机号(非必填,但填写时必须正确)
if (dashouDianhua && dashouDianhua !== '暂未填写') {
if (!/^1[3-9]\d{9}$/.test(dashouDianhua)) {
wx.showToast({
title: '手机号格式不正确',
icon: 'none',
duration: 2000
})
return false
}
}
// 3. 验证微信号(非必填)
if (dashouWeixin && dashouWeixin.length > 30) {
wx.showToast({
title: '微信号不能超过30字符',
icon: 'none',
duration: 2000
})
return false
}
// 4. 验证个人介绍
if (dashouJieshao && dashouJieshao.length > 200) {
wx.showToast({
title: '介绍不能超过200字',
icon: 'none',
duration: 2000
})
return false
}
return true
},
// 格式化提交数据
formatSubmitData() {
const {
dashouNicheng,
dashouDianhua,
dashouWeixin,
dashouJieshao
} = this.data
// 将"暂未填写"转换为空字符串
const formatValue = (value) => {
return value === '暂未填写' ? '' : value.trim()
}
return {
nicheng: formatValue(dashouNicheng),
dianhua: formatValue(dashouDianhua),
wechat: formatValue(dashouWeixin),
jieshao: formatValue(dashouJieshao)
}
},
// 提交修改
async onSubmit() {
// 防止重复提交
if (this.data.isSubmitting) return
// 表单验证
if (!this.validateForm()) return
this.setData({ isSubmitting: true })
// 准备提交数据
const postData = this.formatSubmitData()
try {
const res = await request({
url: '/yonghu/dsgxxx',
method: 'POST',
data: postData
})
if (res && res.data.code === 200) {
// 更新成功
wx.showToast({
title: '修改成功',
icon: 'success',
duration: 2000
})
// 更新本地缓存
this.updateLocalCache(postData)
// 延迟返回上一页
setTimeout(() => {
wx.navigateBack()
}, 1500)
} else {
// 更新失败
const errorMsg = res?.data?.msg || '修改失败,请重试'
wx.showModal({
title: '修改失败',
content: errorMsg,
showCancel: false
})
this.setData({ isSubmitting: false })
}
} catch (error) {
console.error('提交修改失败:', error)
wx.showModal({
title: '请求失败',
content: error.message || '网络错误,请检查连接',
showCancel: false
})
this.setData({ isSubmitting: false })
}
},
// 更新本地缓存
updateLocalCache(data) {
if (data.nicheng !== undefined) {
wx.setStorageSync('dsnc', data.nicheng)
}
if (data.jieshao !== undefined) {
wx.setStorageSync('dsjieshao', data.jieshao)
}
if (data.dianhua !== undefined) {
wx.setStorageSync('dsdianhua', data.dianhua)
}
if (data.wechat !== undefined) {
wx.setStorageSync('dsweixin', data.wechat)
}
}
})

View File

@@ -0,0 +1,11 @@
{
"navigationBarTitleText": "服务者信息修改",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#f8f9fa",
"enablePullDownRefresh": false,
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
}
}

View File

@@ -0,0 +1,103 @@
<!-- 打手信息修改页面 -->
<view class="container">
<!-- 头像区域 -->
<view class="avatar-section">
<view class="avatar-wrapper">
<image
src="{{ touxiangUrl }}"
mode="aspectFill"
class="avatar"
bindtap="onAvatarTap"
></image>
<view class="avatar-halo"></view>
<text class="avatar-hint">点击更换头像</text>
</view>
</view>
<!-- 信息展示与修改区域 -->
<view class="info-section">
<!-- UID不可修改 -->
<view class="info-item">
<text class="info-label">服务者UID</text>
<text class="info-value info-value-disabled">{{ dashouUid }}</text>
</view>
<!-- 昵称 -->
<view class="info-item">
<text class="info-label">服务昵称</text>
<input
value="{{ dashouNicheng }}"
placeholder="请输入昵称"
maxlength="20"
bindinput="onNichengInput"
class="info-input"
/>
<text class="char-count">{{ dashouNicheng.length || 0 }}/20</text>
</view>
<!-- 电话 -->
<view class="info-item">
<text class="info-label">联系电话</text>
<input
value="{{ dashouDianhua }}"
placeholder="请输入手机号"
type="number"
maxlength="11"
bindinput="onDianhuaInput"
class="info-input"
/>
</view>
<!-- 微信 -->
<view class="info-item">
<text class="info-label">微信号</text>
<input
value="{{ dashouWeixin }}"
placeholder="请输入微信号"
maxlength="30"
bindinput="onWeixinInput"
class="info-input"
/>
<text class="char-count">{{ dashouWeixin.length || 0 }}/30</text>
</view>
<!-- 个人介绍 -->
<view class="info-item info-item-textarea">
<text class="info-label">个人介绍</text>
<textarea
value="{{ dashouJieshao }}"
placeholder="请简单介绍一下自己..."
maxlength="200"
bindinput="onJieshaoInput"
class="info-textarea"
></textarea>
<text class="char-count">{{ dashouJieshao.length || 0 }}/200</text>
</view>
</view>
<!-- 状态提示 -->
<view class="status-section" wx:if="{{ isDataFromApi }}">
<text class="status-text">提示:已从服务器加载最新信息</text>
</view>
<!-- 提交按钮 -->
<view class="button-section">
<button
class="submit-btn"
bindtap="onSubmit"
disabled="{{ isSubmitting }}"
loading="{{ isSubmitting }}"
>
{{ isSubmitting ? '提交中...' : '立即修改' }}
</button>
</view>
<!-- 加载中提示 -->
<view class="loading-wrapper" wx:if="{{ isLoading }}">
<view class="loading-content">
<image src="/images/loading.gif" class="loading-icon"></image>
<text class="loading-text">加载中...</text>
</view>
</view>
</view>
<global-notification id="global-notification" />

View File

@@ -0,0 +1,197 @@
/* 页面容器 */
.container {
min-height: 100vh;
background: linear-gradient(135deg, #fffacd 0%, #e6f7ff 100%);
padding: 20rpx;
box-sizing: border-box;
}
/* 头像区域 */
.avatar-section {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx 0;
position: relative;
}
.avatar-wrapper {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.avatar {
width: 180rpx;
height: 180rpx;
border-radius: 50%;
border: 6rpx solid #ffffff;
box-shadow: 0 0 30rpx rgba(255, 215, 0, 0.4);
z-index: 2;
position: relative;
background-color: #f5f5f5;
}
.avatar-halo {
position: absolute;
width: 220rpx;
height: 220rpx;
border-radius: 50%;
background: radial-gradient(circle, rgba(255, 215, 0, 0.2) 0%, transparent 70%);
z-index: 1;
animation: halo-pulse 2s infinite ease-in-out;
}
@keyframes halo-pulse {
0%, 100% { opacity: 0.6; transform: scale(1); }
50% { opacity: 0.8; transform: scale(1.05); }
}
.avatar-hint {
margin-top: 20rpx;
font-size: 26rpx;
color: #666;
text-align: center;
}
/* 信息区域 */
.info-section {
background: rgba(255, 255, 255, 0.9);
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 40rpx;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05);
}
.info-item {
display: flex;
flex-direction: column;
margin-bottom: 40rpx;
position: relative;
padding-bottom: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.info-item:last-child {
border-bottom: none;
}
.info-label {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 15rpx;
}
.info-value-disabled {
font-size: 32rpx;
color: #999;
font-weight: bold;
background: #f9f9f9;
padding: 20rpx;
border-radius: 10rpx;
}
.info-input {
font-size: 32rpx;
color: #333;
padding: 20rpx;
background: #f9f9f9;
border-radius: 10rpx;
border: 1rpx solid #e0e0e0;
}
.info-textarea {
font-size: 30rpx;
color: #333;
padding: 20rpx;
background: #f9f9f9;
border-radius: 10rpx;
border: 1rpx solid #e0e0e0;
min-height: 200rpx;
line-height: 1.5;
}
.info-item-textarea {
padding-bottom: 20rpx;
}
.char-count {
position: absolute;
right: 0;
bottom: 5rpx;
font-size: 24rpx;
color: #999;
}
/* 状态提示 */
.status-section {
background: #e8f4fd;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 30rpx;
border-left: 6rpx solid #1890ff;
}
.status-text {
font-size: 26rpx;
color: #1890ff;
}
/* 按钮区域 */
.button-section {
padding: 20rpx 0 40rpx;
}
.submit-btn {
background: linear-gradient(90deg, #52c41a, #73d13d);
color: white;
font-size: 32rpx;
font-weight: 600;
height: 90rpx;
line-height: 70rpx;
border-radius: 45rpx;
border: none;
box-shadow: 0 10rpx 20rpx rgba(82, 196, 26, 0.2);
}
.submit-btn:disabled {
background: #cccccc;
box-shadow: none;
}
.submit-btn::after {
border: none;
}
/* 加载中 */
.loading-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
}
.loading-icon {
width: 120rpx;
height: 120rpx;
margin-bottom: 30rpx;
}
.loading-text {
font-size: 28rpx;
color: #666;
}

View File

@@ -0,0 +1,287 @@
// pages/dingdan/dingdan.js
const app = getApp()
import request from '../../utils/request.js'
Page({
data: {
// 订单状态tab配置
dingdanZhuangtaiTabs: [
{ name: '全部', key: 'all', zhuangtaiList: [1, 2, 3, 4, 5, 6, 7, 8] },
{ name: '已下单', key: 'yixiadan', zhuangtaiList: [1, 7] },
{ name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2] },
{ name: '争议订单', key: 'zhengyi', zhuangtaiList: [4, 5, 6] },
{ name: '已完成', key: 'yiwancheng', zhuangtaiList: [3] },
{ name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8] }
],
// 当前选中的tab索引
xuanzhongTabIndex: 0,
// 当前选中的tab的key
currentTabKey: 'all',
// 每个tab对应的订单数据
dingdanShuju: {
all: { list: [], page: 1, hasMore: true, isLoading: false },
yixiadan: { list: [], page: 1, hasMore: true, isLoading: false },
jinxingzhong: { list: [], page: 1, hasMore: true, isLoading: false },
zhengyi: { list: [], page: 1, hasMore: true, isLoading: false },
yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false },
jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }
},
// 个人中心传递过来的type参数
gerenzhongxinType: '',
// 是否显示加载更多
xianshiJiazaigengduo: false,
// 页面加载状态
isLoading: true
},
onLoad(options) {
const type = options.type || ''
wx.setNavigationBarTitle({
title: '我的订单'
})
this.qingkongGerenzhongxinJishu(type)
const tabIndex = this.yingsheTypeToTabIndex(type)
const tabKey = this.data.dingdanZhuangtaiTabs[tabIndex].key
this.setData({
gerenzhongxinType: type,
xuanzhongTabIndex: tabIndex,
currentTabKey: tabKey,
isLoading: false
})
this.jiazaiDingdanShuju(tabIndex)
},
onShow() {
// 原有代码...
// 🆕 注册通知组件
this.registerNotificationComponent();
},
// 🆕 新增:注册通知组件
registerNotificationComponent() {
const app = getApp();
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
console.log('🏪 商城页面注册通知组件');
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
qingkongGerenzhongxinJishu(type) {
const jishuMap = {
'daifuwu': 'daifuwu',
'fuwuzhong': 'fuwuzhong',
'yiwancheng': 'yiwancheng',
'yituikuan': 'yituikuan'
}
const jishuKey = jishuMap[type]
if (jishuKey && app.globalData.dingdanTiaoshu) {
app.globalData.dingdanTiaoshu[jishuKey] = 0
}
},
yingsheTypeToTabIndex(type) {
const yingsheMap = {
'daifuwu': 1,
'fuwuzhong': 2,
'yiwancheng': 4,
'yituikuan': 3
}
return yingsheMap[type] || 0
},
switchDingdanTab(e) {
const tabIndex = e.currentTarget.dataset.index
if (tabIndex === this.data.xuanzhongTabIndex) return
const tabKey = this.data.dingdanZhuangtaiTabs[tabIndex].key
this.setData({
xuanzhongTabIndex: tabIndex,
currentTabKey: tabKey
})
this.jiazaiDingdanShuju(tabIndex)
},
async jiazaiDingdanShuju(tabIndex, isLoadMore = false) {
const tab = this.data.dingdanZhuangtaiTabs[tabIndex]
const tabKey = tab.key
const tabData = this.data.dingdanShuju[tabKey]
const page = isLoadMore ? tabData.page + 1 : 1
if (tabData.isLoading) return
// 重置逻辑:如果不是加载更多,清空已有数据
if (!isLoadMore) {
this.setData({
[`dingdanShuju.${tabKey}.list`]: [],
[`dingdanShuju.${tabKey}.page`]: 1
})
}
this.setData({
[`dingdanShuju.${tabKey}.isLoading`]: true
})
wx.showLoading({
title: isLoadMore ? '加载更多...' : '加载订单...',
mask: true
})
try {
const requestData = {
zhuangtai_list: tab.zhuangtaiList,
page: page,
page_size: 10
}
const res = await request({
url: '/dingdan/dingdanhuoqu',
method: 'POST',
data: requestData,
header: {
'content-type': 'application/json'
}
})
wx.hideLoading()
// ✅ 关键修正res是直接返回的数据对象不是res.data
if (res && res.data.code === 0) {
// ✅ 关键修正直接使用res.data.list
const newDingdanList = res.data.data.list || []
const hasMore = res.data.data.has_more || false
// 处理图片URL拼接完整路径
const processedList = newDingdanList.map(item => {
let tupianUrl = item.tupian || ''
// 如果图片URL不是完整路径拼接ossImageUrl
if (tupianUrl && !tupianUrl.startsWith('http')) {
const ossImageUrl = app.globalData.ossImageUrl || ''
tupianUrl = ossImageUrl + tupianUrl
}
const zhuangtaiZh = this.getZhuangtaiZhongwen(item.zhuangtai)
return {
...item,
tupian: tupianUrl,
zhuangtaiZh: zhuangtaiZh
}
})
const currentList = this.data.dingdanShuju[tabKey].list
const updatedList = isLoadMore
? [...currentList, ...processedList]
: processedList
this.setData({
[`dingdanShuju.${tabKey}.list`]: updatedList,
[`dingdanShuju.${tabKey}.page`]: page,
[`dingdanShuju.${tabKey}.hasMore`]: hasMore,
[`dingdanShuju.${tabKey}.isLoading`]: false,
xianshiJiazaigengduo: hasMore
})
} else {
wx.showToast({
title: res ? (res.msg || '加载失败') : '请求失败',
icon: 'none',
duration: 2000
})
this.setData({
[`dingdanShuju.${tabKey}.isLoading`]: false
})
}
} catch (error) {
console.error('加载订单数据失败:', error)
wx.hideLoading()
wx.showToast({
title: '网络请求失败,请重试',
icon: 'none',
duration: 2000
})
this.setData({
[`dingdanShuju.${tabKey}.isLoading`]: false
})
}
},
getZhuangtaiZhongwen(zhuangtai) {
const zhuangtaiMap = {
1: '已下单',
2: '进行中',
3: '已完成',
4: '退款中',
5: '已退款',
6: '退款失败',
7: '指定中',
8: '结算中'
}
return zhuangtaiMap[zhuangtai] || '未知状态'
},
onReachBottom() {
const tabKey = this.data.currentTabKey
const tabData = this.data.dingdanShuju[tabKey]
if (tabData.hasMore && !tabData.isLoading) {
const tabIndex = this.data.xuanzhongTabIndex
this.jiazaiDingdanShuju(tabIndex, true)
}
},
goToDingdanXiangqing(e) {
const dingdanItem = e.currentTarget.dataset.item
if (!dingdanItem) return
const dingdanDataStr = encodeURIComponent(JSON.stringify(dingdanItem))
wx.navigateTo({
url: `/pages/dingdanxiangqing/dingdanxiangqing?dingdanData=${dingdanDataStr}`
})
},
onImageError(e) {
const index = e.currentTarget.dataset.index
const tabKey = this.data.currentTabKey
const defaultImage = app.globalData.lunbozhanwei || '/images/default.jpg'
this.setData({
[`dingdanShuju.${tabKey}.list[${index}].tupian`]: defaultImage
})
}
})

View File

@@ -0,0 +1,5 @@
{
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
}
}

View File

@@ -0,0 +1,111 @@
<!--pages/dingdan/dingdan.wxml-->
<view class="dingdan-page">
<!-- 订单状态tab区域 -->
<scroll-view
class="dingdan-tabs-scroll"
scroll-x="true"
scroll-with-animation="true"
>
<view class="dingdan-tabs-container">
<block wx:for="{{dingdanZhuangtaiTabs}}" wx:key="key">
<view
class="dingdan-tab-item {{index === xuanzhongTabIndex ? 'tab-active' : ''}}"
bindtap="switchDingdanTab"
data-index="{{index}}"
>
<text class="tab-text">{{item.name}}</text>
<view class="tab-active-line" wx:if="{{index === xuanzhongTabIndex}}"></view>
</view>
</block>
</view>
</scroll-view>
<!-- 订单列表区域 -->
<view class="dingdan-list-container">
<!-- 页面加载中 -->
<view class="loading-container" wx:if="{{isLoading}}">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 空状态 -->
<view
class="empty-container"
wx:if="{{!isLoading && dingdanShuju[currentTabKey].list.length === 0}}"
>
<image
class="empty-image"
src="/images/empty-order.png"
mode="aspectFit"
></image>
<text class="empty-text">暂无订单</text>
<text class="empty-subtext">快去下单体验服务吧</text>
</view>
<!-- 订单列表 -->
<block wx:if="{{!isLoading && dingdanShuju[currentTabKey].list.length > 0}}">
<block wx:for="{{dingdanShuju[currentTabKey].list}}" wx:key="dingdanId">
<view
class="dingdan-card"
bindtap="goToDingdanXiangqing"
data-item="{{item}}"
>
<!-- 商品图片 -->
<view class="dingdan-card-left">
<image
class="dingdan-image"
src="{{item.tupian}}"
mode="aspectFill"
binderror="onImageError"
data-index="{{index}}"
></image>
</view>
<!-- 订单信息 -->
<view class="dingdan-card-center">
<view class="dingdan-jieshao">
{{item.jieshao || '暂无描述'}}
</view>
</view>
<!-- 订单状态和价格 -->
<view class="dingdan-card-right">
<!-- 订单状态 -->
<view class="dingdan-zhuangtai">
{{item.zhuangtaiZh || '未知状态'}}
</view>
<!-- 订单价格 -->
<view class="dingdan-jine">
<text class="jine-symbol">¥</text>
<text class="jine-number">{{item.jine || '0.00'}}</text>
</view>
</view>
</view>
</block>
</block>
<!-- 加载更多提示 -->
<view
class="load-more-container"
wx:if="{{xianshiJiazaigengduo && !isLoading && dingdanShuju[currentTabKey].list.length > 0}}"
>
<view class="load-more-line"></view>
<text class="load-more-text">上拉加载更多</text>
<view class="load-more-line"></view>
</view>
<!-- 没有更多提示 -->
<view
class="no-more-container"
wx:if="{{!xianshiJiazaigengduo && !isLoading && dingdanShuju[currentTabKey].list.length > 0}}"
>
<text class="no-more-text">没有更多订单了</text>
</view>
</view>
</view>
<global-notification id="global-notification" />

Some files were not shown because too many files have changed in this diff Show More