优化了样式,抽象出了 WXSS/JS 库

This commit is contained in:
TermiNexus committed 2026-06-13 18:19:46 +08:00
1 parent 6fbae9b32c
commit 3dc03f6fe5
51 files changed
+3736 -5617

No files matched your search

+1179
View File
File diff suppressed because it is too large. Load diff
+38 -93
View File
@@ -1,18 +1,9 @@
// pages/assessor/assessor.js
// 只新增了两个字段的接收,其余所有代码未变
import request from '../../utils/request.js';
import {
isRoleStatusActive,
isCenterPageActive,
syncRoleStatuses,
ensureRoleOnCenterPage,
} from '../../utils/role-tab-bar.js';
const app = getApp();
import { createPage, request, isCenterPageActive, ensureRoleOnCenterPage } from '../../utils/base-page.js';
Page({
Page(createPage({
data: {
isKaoheguan: false,
isLoading: false,
inviteCode: '',
statusText: '用户尚未注册或账号已被封禁',
uid: '',
@@ -32,11 +23,9 @@ Page({
zonge: '0.00',
shenheZhuangtai: 0,
bankuaiList: [],
isRenzheng: false, // 🆕 是否认证
pendingShenheCount: 0 // 🆕 审核中数量
isRenzheng: false,
pendingShenheCount: 0
},
lastRefreshTime: 0,
canRefresh: true,
planetPaused: {
top: false,
bottom: false,
@@ -50,54 +39,31 @@ Page({
onLoad() {
wx.redirectTo({ url: '/pages/fighter/fighter' });
return;
const ossUrl = app.globalData.ossImageUrl || '';
const iconBase = 'beijing/kaohe/';
this.setData({
'icons.refresh': ossUrl + iconBase + 'shuaxin.png',
'icons.tixian': ossUrl + iconBase + 'tixian.png',
'icons.daofen': ossUrl + iconBase + 'daofen.png',
'icons.jilu': ossUrl + iconBase + 'jilu.png',
'icons.zhongxin': ossUrl + iconBase + 'zhongxin.png',
'icons.bgImage': ossUrl + iconBase + 'top_bg.jpg'
const icons = this.initImageUrls({
'icons.refresh': 'beijing/kaohe/shuaxin.png',
'icons.tixian': 'beijing/kaohe/tixian.png',
'icons.daofen': 'beijing/kaohe/daofen.png',
'icons.jilu': 'beijing/kaohe/jilu.png',
'icons.zhongxin': 'beijing/kaohe/zhongxin.png',
'icons.bgImage': 'beijing/kaohe/top_bg.jpg'
});
this.setData(icons);
const kaoheguanstatus = wx.getStorageSync('kaoheguanstatus');
if (isRoleStatusActive(kaoheguanstatus)) {
this.loadUserInfo();
this.setData({ isKaoheguan: true });
this.checkRoleStatus();
if (this.data.isKaoheguan) {
this.getKaoheguanInfo();
ensureRoleOnCenterPage(this, 'kaoheguan');
} else {
this.setData({ isKaoheguan: false });
}
},
onReady() {
if (isCenterPageActive(this, 'isKaoheguan', 'kaoheguanstatus')) {
ensureRoleOnCenterPage(this, 'kaoheguan');
}
this.checkColdStartPopup('kaoheguanduan');
},
onShow() {
if (isCenterPageActive(this, 'isKaoheguan', 'kaoheguanstatus')) {
if (!this.data.isKaoheguan) this.setData({ isKaoheguan: true });
ensureRoleOnCenterPage(this, 'kaoheguan');
ensureRoleOnCenterPage(this, 'dashou');
this.refreshKaoheInfo();
}
},
loadUserInfo() {
const uid = wx.getStorageSync('uid') || '';
const touxiang = wx.getStorageSync('touxiang') || '';
const ossUrl = app.globalData.ossImageUrl || '';
const defaultAvatar = ossUrl + (app.globalData.morentouxiang || 'avatar/default.jpg');
let avatarUrl = defaultAvatar;
if (touxiang) {
avatarUrl = ossUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang);
}
this.setData({ uid, avatarUrl });
},
async getKaoheguanInfo() {
this.setData({ isLoading: true });
try {
@@ -120,11 +86,11 @@ Page({
zonge: data.zonge || '0.00',
shenheZhuangtai: data.shenhe_zhuangtai || 0,
bankuaiList: data.bankuai_list || [],
isRenzheng: data.is_renzheng || false, // 🆕
pendingShenheCount: data.pending_shenhe_count || 0 // 🆕
isRenzheng: data.is_renzheng || false,
pendingShenheCount: data.pending_shenhe_count || 0
}
});
ensureRoleOnCenterPage(this, 'kaoheguan');
ensureRoleOnCenterPage(this, 'dashou');
} else {
wx.showToast({ title: res?.data?.msg || '获取信息失败', icon: 'none' });
this.setData({ isLoading: false });
@@ -141,51 +107,22 @@ Page({
},
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: '/dengji/khgzc',
method: 'POST',
data: { inviteCode: inviteCode }
});
if (res && res.data.code === 200) {
const data = res.data.data || {};
syncRoleStatuses(data);
if (!Object.prototype.hasOwnProperty.call(data, 'kaoheguanstatus')) {
wx.setStorageSync('kaoheguanstatus', 1);
}
wx.showToast({ title: '注册成功', icon: 'success', duration: 2000 });
this.registerWithInviteCode({
inviteCode: this.data.inviteCode,
apiUrl: '/dengji/khgzc',
role: 'dashou',
statusKey: 'kaoheguanstatus',
successMsg: '注册成功',
onSuccess: async () => {
this.loadUserInfo();
await this.getKaoheguanInfo();
ensureRoleOnCenterPage(this, 'kaoheguan');
} else {
wx.showToast({ title: res?.data?.msg || '注册失败', icon: 'none', duration: 2000 });
this.setData({ isLoading: false });
}
} catch (error) {
console.error('注册失败:', error);
wx.showToast({ title: '网络错误', icon: 'none' });
this.setData({ isLoading: false });
ensureRoleOnCenterPage(this, 'dashou');
}
});
},
refreshKaoheInfo() {
const now = Date.now();
const lastTime = this.data.lastRefreshTime;
const canRefresh = this.data.canRefresh;
if (!canRefresh || (now - lastTime < 3000)) return;
this.setData({ lastRefreshTime: now, canRefresh: false });
this.getKaoheguanInfo();
setTimeout(() => this.setData({ canRefresh: true }), 3000);
this.throttledRefresh(this.getKaoheguanInfo);
},
goToWithdraw() {
@@ -215,4 +152,12 @@ Page({
this.setData({ [paused]: false });
}, 5000);
}
});
}, {
roleConfig: {
role: 'dashou',
statusKey: 'kaoheguanstatus',
dataFlag: 'isKaoheguan',
pageId: 'kaoheguanduan',
apiUrl: '/dengji/khgsx'
}
}));
+3 -13
View File
@@ -1,7 +1,8 @@
// pages/category/category.js
const app = getApp()
import { createPage } from '../../utils/base-page.js';
Page({
Page(createPage({
data: {
ossImageUrl: '',
searchText: '',
@@ -33,17 +34,6 @@ Page({
this.registerNotificationComponent();
},
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()
};
}
},
initPageData() {
const that = this
this.setData({ isLoading: true })
@@ -328,4 +318,4 @@ Page({
path: '/pages/category/category'
}
}
})
}))
+43 -43
View File
@@ -1,31 +1,31 @@
<view class="chat-page">
<scroll-view class="msg-list" scroll-y="true"
<scroll-view class="chat-msg-list" scroll-y="true"
scroll-into-view="{{scrollToView}}"
refresher-enabled="{{true}}"
refresher-triggered="{{isRefreshing}}"
bindrefresherrefresh="onPullDownRefresh"
style="top: 20rpx; bottom: calc({{bottomSafeHeight}}rpx + {{keyboardHeight}}px + env(safe-area-inset-bottom) + 20rpx);">
<view class="loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
<view class="chat-loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
<block wx:for="{{messages}}" wx:key="messageId">
<view class="msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="msg-row {{item.senderId === currentUser.id ? 'right' : 'left'}}">
<image wx:if="{{item.senderId !== currentUser.id}}" class="avatar" src="{{item.senderData.avatar || toAvatar}}" mode="aspectFill" binderror="onPeerAvatarError" />
<view class="bubble {{item.senderId === currentUser.id ? 'bubble-right' : 'bubble-left'}}"
<view class="chat-msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="chat-msg-row {{item.senderId === currentUser.id ? 'chat-msg-row--right' : 'chat-msg-row--left'}}">
<image wx:if="{{item.senderId !== currentUser.id}}" class="chat-avatar" src="{{item.senderData.avatar || toAvatar}}" mode="aspectFill" binderror="onPeerAvatarError" />
<view class="chat-bubble {{item.senderId === currentUser.id ? 'chat-bubble--right' : 'chat-bubble--left'}}"
data-messageid="{{item.messageId}}"
data-text="{{item.type==='text' ? item.payload.text : ''}}"
bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}"
data-url="{{item.type==='image' ? item.payload.url : ''}}">
<text wx:if="{{item.type === 'text'}}" class="msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="msg-image" src="{{item.payload.url}}" mode="widthFix" />
<view wx:elif="{{item.type === 'order'}}" class="order-bubble">
<text class="order-label">[订单]</text>
<text class="order-info">ID: {{item.payload.orderId}}</text>
<text wx:if="{{item.type === 'text'}}" class="chat-msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="chat-msg-image" src="{{item.payload.url}}" mode="widthFix" />
<view wx:elif="{{item.type === 'order'}}" class="chat-order-bubble">
<text class="chat-order-label">[订单]</text>
<text class="chat-order-info">ID: {{item.payload.orderId}}</text>
</view>
</view>
<image wx:if="{{item.senderId === currentUser.id}}" class="avatar" src="{{item.senderData.avatar || currentUser.avatar}}" mode="aspectFill" binderror="onSelfAvatarError" data-index="{{index}}" />
<image wx:if="{{item.senderId === currentUser.id}}" class="chat-avatar" src="{{item.senderData.avatar || currentUser.avatar}}" mode="aspectFill" binderror="onSelfAvatarError" data-index="{{index}}" />
</view>
<view wx:if="{{item.senderId === currentUser.id && item.status === 'success'}}" class="read-tag">
<view wx:if="{{item.senderId === currentUser.id && item.status === 'success'}}" class="chat-read-tag">
{{item.read ? '已读' : '未读'}}
</view>
</view>
@@ -33,53 +33,53 @@
<view id="msg-bottom" style="height:20rpx;"></view>
</scroll-view>
<view class="input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
<view class="plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
<view class="plus-grid">
<view class="plus-item" catchtap="openEmojiFromPlus">
<text class="plus-icon">😀</text>
<text class="plus-label">表情</text>
<view class="chat-input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
<view class="chat-plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
<view class="chat-plus-grid">
<view class="chat-plus-item" catchtap="openEmojiFromPlus">
<text class="chat-plus-icon">😀</text>
<text class="chat-plus-label">表情</text>
</view>
<view class="plus-item" catchtap="chooseImage">
<text class="plus-icon">📷</text>
<text class="plus-label">图片</text>
<view class="chat-plus-item" catchtap="chooseImage">
<text class="chat-plus-icon">📷</text>
<text class="chat-plus-label">图片</text>
</view>
<view class="plus-item" catchtap="openOrderSender">
<text class="plus-icon">📋</text>
<text class="plus-label">订单</text>
<view class="chat-plus-item" catchtap="openOrderSender">
<text class="chat-plus-icon">📋</text>
<text class="chat-plus-label">订单</text>
</view>
</view>
</view>
<view class="emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="emoji-scroll">
<view class="emoji-grid">
<view class="chat-emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="chat-emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="chat-emoji-scroll">
<view class="chat-emoji-grid">
<block wx:for="{{emojiList}}" wx:key="*this">
<view class="emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view>
<view class="chat-emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view>
</block>
</view>
</scroll-view>
</view>
<view class="pending-image" wx:if="{{pendingImage}}">
<image class="pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="pending-remove" catchtap="clearPendingImage">✕</view>
<view class="chat-pending-image" wx:if="{{pendingImage}}">
<image class="chat-pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="chat-pending-remove" catchtap="clearPendingImage">✕</view>
</view>
<view class="write-row">
<input class="text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="send-btn" bindtap="sendMessage">发送</view>
<view class="plus-btn" bindtap="togglePlusPanel">
<text class="plus-icon-single">➕</text>
<view class="chat-write-row">
<input class="chat-text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="chat-send-btn" bindtap="sendMessage">发送</view>
<view class="chat-plus-btn" bindtap="togglePlusPanel">
<text class="chat-plus-icon-single">➕</text>
</view>
</view>
</view>
<view class="detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="detail-content" catchtap="noop">
<text class="detail-text" user-select="true">{{detailText}}</text>
<button class="copy-btn" bindtap="copyDetail">复制</button>
<view class="chat-detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="chat-detail-content" catchtap="noop">
<text class="chat-detail-text" user-select="true">{{detailText}}</text>
<button class="chat-copy-btn" bindtap="copyDetail">复制</button>
</view>
</view>
+1 -98
View File
@@ -1,98 +1 @@
.chat-page { width:100%; height:100vh; background:#f5f5f5; }
.msg-list {
position: absolute;
left:0; right:0;
padding: 0 20rpx;
box-sizing: border-box;
overflow-y: auto;
}
.loading-more { text-align:center; font-size:24rpx; color:#999; padding:20rpx 0; }
.time-tag { text-align:center; margin:28rpx 0; font-size:24rpx; color:#b0b0b0; }
.msg-wrapper { margin-bottom:12rpx; }
.msg-row { display:flex; align-items:flex-start; margin-bottom:4rpx; }
.right { justify-content:flex-end; }
.left { justify-content:flex-start; }
.avatar { width:76rpx; height:76rpx; border-radius:50%; background:#e0e0e0; margin:0 12rpx; flex-shrink:0; }
.bubble { max-width:70%; padding:16rpx 20rpx; border-radius:16rpx; position:relative; word-break:break-all; font-size:30rpx; line-height:1.5; }
.bubble-left { background:#fff; border-top-left-radius:4rpx; }
.bubble-right { background:#00aaff; color:#fff; border-top-right-radius:4rpx; }
.msg-text { color:inherit; }
.msg-image { max-width:240rpx; border-radius:10rpx; }
.order-bubble { background:#fff7e0; padding:12rpx; border-radius:8rpx; }
.order-label { font-size:24rpx; color:#e6a23c; font-weight:600; }
.order-info { font-size:24rpx; color:#333; margin:6rpx 0; }
.read-tag {
font-size:20rpx;
color:#888;
text-align:right;
padding:4rpx 12rpx 0 0;
margin-top: -4rpx;
}
.input-area {
position: fixed;
left:0; right:0;
background:#f7f7f7;
border-top:1rpx solid #ddd;
z-index:10;
}
.plus-panel {
position: absolute;
bottom:100%; left:0; right:0;
background:#fff;
border-top:1rpx solid #e0e0e0;
padding: 20rpx;
transform: translateY(100%);
opacity:0;
transition: transform 0.3s, opacity 0.3s;
pointer-events:none;
z-index:9;
}
.plus-panel.show { transform: translateY(0); opacity:1; pointer-events:auto; }
.plus-grid { display: flex; justify-content: space-around; }
.plus-item { display: flex; flex-direction: column; align-items: center; width: 120rpx; }
.plus-icon { font-size: 60rpx; }
.plus-label { font-size: 24rpx; color: #666; margin-top: 8rpx; }
.emoji-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:transparent; z-index:8; }
.emoji-panel {
position: absolute; bottom:100%; left:0; right:0;
background:#fff; border-top:1rpx solid #e0e0e0;
height:380rpx;
transform: translateY(100%); opacity:0; transition:0.3s;
pointer-events:none; z-index:9;
}
.emoji-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
.emoji-scroll { height:100%; }
.emoji-grid { display:flex; flex-wrap:wrap; padding:20rpx 10rpx; }
.emoji-item { width:14.28%; text-align:center; padding:16rpx 0; font-size:44rpx; }
.pending-image { display:flex; align-items:center; padding:10rpx 20rpx; background:#fff; }
.pending-thumb { width:80rpx; height:80rpx; border-radius:8rpx; }
.pending-remove { font-size:30rpx; color:#999; margin-left:10rpx; }
.write-row { display:flex; padding:0 20rpx 10rpx; align-items:center; }
.text-input {
flex:1; height:76rpx; background:#fff; border-radius:12rpx;
padding:0 20rpx; font-size:30rpx; margin-right:12rpx;
}
.send-btn {
width:120rpx; height:76rpx; background:#00aaff; color:#fff;
font-size:28rpx; font-weight:500; border-radius:12rpx;
display:flex; align-items:center; justify-content:center;
}
.plus-btn {
width:56rpx; height:56rpx;
display:flex; align-items:center; justify-content:center;
margin-left: 10rpx;
}
.plus-icon-single { font-size:46rpx; color:#00aaff; }
.detail-modal { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:999; display:flex; align-items:center; justify-content:center; }
.detail-content { width:80%; max-height:80%; background:#fff; border-radius:16rpx; padding:40rpx; }
.detail-text { font-size:32rpx; line-height:1.6; word-break:break-all; overflow-y:auto; }
.copy-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:20rpx; font-size:30rpx; }
/* 使用全局 chat-* 样式 */
+54 -54
View File
@@ -1,37 +1,37 @@
<view class="chat-page">
<scroll-view class="msg-list" scroll-y="true"
<scroll-view class="chat-msg-list" scroll-y="true"
scroll-into-view="{{scrollToView}}"
refresher-enabled="{{true}}"
refresher-triggered="{{isRefreshing}}"
bindrefresherrefresh="onPullDownRefresh"
style="top: 20rpx; bottom: calc({{bottomSafeHeight}}rpx + {{keyboardHeight}}px + env(safe-area-inset-bottom) + 20rpx);">
<view class="loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
<view class="chat-loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
<block wx:for="{{messages}}" wx:key="messageId">
<view class="msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="msg-row {{item.senderId === currentUser.id ? 'right' : 'left'}}">
<view class="chat-msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="chat-msg-row {{item.senderId === currentUser.id ? 'chat-msg-row--right' : 'chat-msg-row--left'}}">
<!-- 对方头像(客服) -->
<image wx:if="{{item.senderId !== currentUser.id}}" class="avatar" src="{{teamAvatar}}" mode="aspectFill" />
<view class="bubble {{item.senderId === currentUser.id ? 'bubble-right' : 'bubble-left'}}"
<image wx:if="{{item.senderId !== currentUser.id}}" class="chat-avatar" src="{{teamAvatar}}" mode="aspectFill" />
<view class="chat-bubble {{item.senderId === currentUser.id ? 'chat-bubble--right' : 'chat-bubble--left'}}"
data-messageid="{{item.messageId}}"
data-text="{{item.type==='text' ? item.payload.text : ''}}"
bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}"
data-url="{{item.type==='image' ? item.payload.url : ''}}">
<text wx:if="{{item.type === 'text'}}" class="msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="msg-image" src="{{item.payload.url}}" mode="widthFix" data-url="{{item.payload.url}}" bindtap="previewImage"/>
<view wx:elif="{{item.type === 'order'}}" class="order-bubble">
<text class="order-label">📋 订单消息</text>
<view class="order-brief">
<text wx:if="{{item.type === 'text'}}" class="chat-msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="chat-msg-image" src="{{item.payload.url}}" mode="widthFix" data-url="{{item.payload.url}}" bindtap="previewImage"/>
<view wx:elif="{{item.type === 'order'}}" class="chat-order-bubble">
<text class="chat-order-label">📋 订单消息</text>
<view class="chat-order-info">
<text>订单号:{{item.payload.dingdan_id || '无'}}</text>
<text>服务:{{item.payload.jieshao || ''}}</text>
<text class="order-price">¥{{item.payload.jine}}</text>
<text style="color:#e6a23c;font-weight:bold">¥{{item.payload.jine}}</text>
</view>
<button class="order-detail-btn" catchtap="showOrderDetailPopup" data-order="{{item.payload}}" size="mini">查看详情</button>
<button class="chat-copy-btn" catchtap="showOrderDetailPopup" data-order="{{item.payload}}" size="mini">查看详情</button>
</view>
<text wx:else class="msg-text">{{item.payload.text || '[未知消息]'}}</text>
<text wx:else class="chat-msg-text">{{item.payload.text || '[未知消息]'}}</text>
</view>
<!-- 自己头像 -->
<image wx:if="{{item.senderId === currentUser.id}}" class="avatar" src="{{currentUser.avatar}}" mode="aspectFill" />
<image wx:if="{{item.senderId === currentUser.id}}" class="chat-avatar" src="{{currentUser.avatar}}" mode="aspectFill" />
</view>
<!-- 🔥 已读/未读标签已彻底移除 -->
</view>
@@ -40,67 +40,67 @@
</scroll-view>
<!-- 底部输入区域(保持不变) -->
<view class="input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
<view class="plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
<view class="plus-grid">
<view class="plus-item" catchtap="openEmojiFromPlus">
<text class="plus-icon">😀</text>
<text class="plus-label">表情</text>
<view class="chat-input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
<view class="chat-plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
<view class="chat-plus-grid">
<view class="chat-plus-item" catchtap="openEmojiFromPlus">
<text class="chat-plus-icon">😀</text>
<text class="chat-plus-label">表情</text>
</view>
<view class="plus-item" catchtap="chooseImage">
<text class="plus-icon">📷</text>
<text class="plus-label">图片</text>
<view class="chat-plus-item" catchtap="chooseImage">
<text class="chat-plus-icon">📷</text>
<text class="chat-plus-label">图片</text>
</view>
<view class="plus-item" catchtap="openOrderSender">
<text class="plus-icon">📋</text>
<text class="plus-label">订单</text>
<view class="chat-plus-item" catchtap="openOrderSender">
<text class="chat-plus-icon">📋</text>
<text class="chat-plus-label">订单</text>
</view>
</view>
</view>
<view class="emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="emoji-scroll">
<view class="emoji-grid">
<view class="chat-emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="chat-emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="chat-emoji-scroll">
<view class="chat-emoji-grid">
<block wx:for="{{emojiList}}" wx:key="*this">
<view class="emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view>
<view class="chat-emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view>
</block>
</view>
</scroll-view>
</view>
<view class="pending-image" wx:if="{{pendingImage}}">
<image class="pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="pending-remove" catchtap="clearPendingImage">✕</view>
<view class="chat-pending-image" wx:if="{{pendingImage}}">
<image class="chat-pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="chat-pending-remove" catchtap="clearPendingImage">✕</view>
</view>
<view class="write-row">
<input class="text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="send-btn" bindtap="sendMessage">发送</view>
<view class="plus-btn" bindtap="togglePlusPanel">
<text class="plus-icon-single">➕</text>
<view class="chat-write-row">
<input class="chat-text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="chat-send-btn" bindtap="sendMessage">发送</view>
<view class="chat-plus-btn" bindtap="togglePlusPanel">
<text class="chat-plus-icon-single">➕</text>
</view>
</view>
</view>
<!-- 文本双击放大弹窗 -->
<view class="detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="detail-content" catchtap="noop">
<text class="detail-text" user-select="true">{{detailText}}</text>
<button class="copy-btn" bindtap="copyDetail">复制</button>
<view class="chat-detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="chat-detail-content" catchtap="noop">
<text class="chat-detail-text" user-select="true">{{detailText}}</text>
<button class="chat-copy-btn" bindtap="copyDetail">复制</button>
</view>
</view>
<!-- 订单详情弹窗 -->
<view class="order-detail-mask" wx:if="{{showOrderDetail}}" catchtap="hideOrderDetail">
<view class="order-detail-panel" catchtap="noop">
<view class="order-detail-title">订单详情</view>
<view class="order-detail-item">订单号:{{detailOrder.dingdan_id}}</view>
<view class="order-detail-item">服务描述:{{detailOrder.jieshao}}</view>
<view class="order-detail-item">金额:¥{{detailOrder.jine}}</view>
<view class="order-detail-item">类型:{{detailOrder.leixing || '无'}}</view>
<view class="order-detail-item">时间:{{detailOrder.shijian || '无'}}</view>
<button class="order-detail-close-btn" catchtap="hideOrderDetail">关闭</button>
<view class="chat-detail-modal" wx:if="{{showOrderDetail}}" catchtap="hideOrderDetail">
<view class="chat-detail-content" catchtap="noop">
<view class="chat-detail-text">订单详情</view>
<view class="chat-detail-text">订单号:{{detailOrder.dingdan_id}}</view>
<view class="chat-detail-text">服务描述:{{detailOrder.jieshao}}</view>
<view class="chat-detail-text">金额:¥{{detailOrder.jine}}</view>
<view class="chat-detail-text">类型:{{detailOrder.leixing || '无'}}</view>
<view class="chat-detail-text">时间:{{detailOrder.shijian || '无'}}</view>
<button class="chat-copy-btn" catchtap="hideOrderDetail">关闭</button>
</view>
</view>
+1 -67
View File
@@ -1,67 +1 @@
.chat-page { width:100%; height:100vh; background:#f5f5f5; }
.msg-list { position:absolute; left:0; right:0; padding:0 20rpx; box-sizing:border-box; overflow-y:auto; }
.loading-more { text-align:center; font-size:24rpx; color:#999; padding:20rpx 0; }
.time-tag { text-align:center; margin:28rpx 0; font-size:24rpx; color:#b0b0b0; }
.msg-wrapper { margin-bottom:12rpx; }
.msg-row { display:flex; align-items:flex-start; margin-bottom:4rpx; }
.right { justify-content:flex-end; }
.left { justify-content:flex-start; }
.avatar { width:76rpx; height:76rpx; border-radius:50%; background:#e0e0e0; margin:0 12rpx; flex-shrink:0; }
.bubble { max-width:70%; padding:16rpx 20rpx; border-radius:16rpx; position:relative; word-break:break-all; font-size:30rpx; line-height:1.5; }
.bubble-left { background:#fff; border-top-left-radius:4rpx; }
.bubble-right { background:#00aaff; color:#fff; border-top-right-radius:4rpx; }
.msg-text { color:inherit; }
.msg-image { max-width:240rpx; border-radius:10rpx; }
/* 已读/未读标签:靠右显示 */
.read-tag {
display: flex;
justify-content: flex-end;
font-size: 20rpx;
color: #888;
padding: 4rpx 12rpx 0 0;
margin-top: 2rpx;
}
/* 订单气泡 */
.order-bubble { background:#fff7e0; padding:12rpx; border-radius:8rpx; }
.order-label { font-size:24rpx; color:#e6a23c; font-weight:600; margin-bottom:8rpx; }
.order-brief { font-size:24rpx; color:#333; line-height:1.6; }
.order-price { color:#e6a23c; font-weight:bold; }
.order-detail-btn { margin-top:10rpx; background:#e6a23c; color:#fff; border-radius:8rpx; }
/* 订单详情弹窗 */
.order-detail-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.6); z-index:999; display:flex; justify-content:center; align-items:center; }
.order-detail-panel { width:80%; background:#fff; border-radius:20rpx; padding:30rpx; }
.order-detail-title { font-size:34rpx; font-weight:bold; margin-bottom:20rpx; color:#333; }
.order-detail-item { font-size:28rpx; margin:16rpx 0; color:#555; }
.order-detail-close-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:30rpx; font-size:30rpx; }
/* 底部输入区域 */
.input-area { position:fixed; left:0; right:0; background:#f7f7f7; border-top:1rpx solid #ddd; z-index:10; }
.plus-panel { position:absolute; bottom:100%; left:0; right:0; background:#fff; border-top:1rpx solid #e0e0e0; padding:20rpx; transform:translateY(100%); opacity:0; transition:0.3s; pointer-events:none; z-index:9; }
.plus-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
.plus-grid { display:flex; justify-content:space-around; }
.plus-item { display:flex; flex-direction:column; align-items:center; width:120rpx; }
.plus-icon { font-size:60rpx; }
.plus-label { font-size:24rpx; color:#666; margin-top:8rpx; }
.emoji-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:transparent; z-index:8; }
.emoji-panel { position:absolute; bottom:100%; left:0; right:0; background:#fff; border-top:1rpx solid #e0e0e0; height:380rpx; transform:translateY(100%); opacity:0; transition:0.3s; pointer-events:none; z-index:9; }
.emoji-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
.emoji-scroll { height:100%; }
.emoji-grid { display:flex; flex-wrap:wrap; padding:20rpx 10rpx; }
.emoji-item { width:14.28%; text-align:center; padding:16rpx 0; font-size:44rpx; }
.pending-image { display:flex; align-items:center; padding:10rpx 20rpx; background:#fff; }
.pending-thumb { width:80rpx; height:80rpx; border-radius:8rpx; }
.pending-remove { font-size:30rpx; color:#999; margin-left:10rpx; }
.write-row { display:flex; padding:0 20rpx 10rpx; align-items:center; }
.text-input { flex:1; height:76rpx; background:#fff; border-radius:12rpx; padding:0 20rpx; font-size:30rpx; margin-right:12rpx; }
.send-btn { width:120rpx; height:76rpx; background:#00aaff; color:#fff; font-size:28rpx; font-weight:500; border-radius:12rpx; display:flex; align-items:center; justify-content:center; }
.plus-btn { width:56rpx; height:56rpx; display:flex; align-items:center; justify-content:center; margin-left:10rpx; }
.plus-icon-single { font-size:46rpx; color:#00aaff; }
/* 双击放大文本弹窗 */
.detail-modal { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:999; display:flex; align-items:center; justify-content:center; }
.detail-content { width:80%; max-height:80%; background:#fff; border-radius:16rpx; padding:40rpx; }
.detail-text { font-size:32rpx; line-height:1.6; word-break:break-all; overflow-y:auto; }
.copy-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:20rpx; font-size:30rpx; }
/* 使用全局 chat-* 样式 */
+6 -6
View File
@@ -215,14 +215,14 @@
<!-- 弹窗底部按钮 -->
<view class="modal-footer">
<view class="btn-group">
<view class="cbtn-group">
<!-- 联系客服按钮(始终显示) -->
<view class="btn btn-kefu" bindtap="lianxiKefu">
<view class="cbtn cbtn-kefu" bindtap="lianxiKefu">
<text>联系客服</text>
</view>
<!-- 申诉按钮(只有待处罚状态可点击) -->
<view
class="btn {{ xuanzhongChufa.sqzhuangtai === 0 ? 'btn-shensu' : 'btn-shensu-disabled' }}"
class="cbtn {{ xuanzhongChufa.sqzhuangtai === 0 ? 'cbtn-shensu' : 'cbtn-shensu-disabled' }}"
bindtap="{{ xuanzhongChufa.sqzhuangtai === 0 ? 'openShensuModal' : '' }}"
>
<text>申诉</text>
@@ -299,11 +299,11 @@
</view>
</view>
<view class="modal-footer">
<view class="btn-group">
<view class="btn btn-quxiao" bindtap="closeShensuModal">
<view class="cbtn-group">
<view class="cbtn cbtn-quxiao" bindtap="closeShensuModal">
<text>取消</text>
</view>
<view class="btn btn-queren" bindtap="submitShensu">
<view class="cbtn cbtn-queren" bindtap="submitShensu">
<text>提交申诉</text>
</view>
</view>
+8 -8
View File
@@ -625,12 +625,12 @@
flex-shrink: 0;
}
.btn-group {
.cbtn-group {
display: flex;
gap: 20rpx;
}
.btn {
.cbtn {
flex: 1;
height: 90rpx;
display: flex;
@@ -645,37 +645,37 @@
color: var(--cyber-text) !important;
}
.btn:active {
.cbtn:active {
transform: scale(0.98);
}
/* 按钮样式 */
.btn-kefu {
.cbtn-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 {
.cbtn-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 {
.cbtn-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 {
.cbtn-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 {
.cbtn-queren {
background: rgba(0, 255, 157, 0.1);
border-color: var(--cyber-green);
box-shadow: 0 0 12rpx rgba(0, 255, 157, 0.2);
+6 -24
View File
@@ -1,8 +1,9 @@
const app = getApp();
import request from '../../utils/request.js';
import { createPage, request } from '../../utils/base-page.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getOrderStatusText } from '../../utils/api-helper.js';
Page({
Page(createPage({
data: {
// 商品类型
shangpinleixing: [],
@@ -63,16 +64,6 @@ Page({
}
},
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
// 加载商品类型(图片拼接)
async loadShangpinLeixing() {
try {
@@ -192,7 +183,7 @@ Page({
// ✅ 图片URL拼接(核心)
const processed = newList.map(item => ({
...item,
zhuangtaiZh: this.getZhuangtaiZh(item.zhuangtai),
zhuangtaiZh: getOrderStatusText(item.zhuangtai),
zhuangtaiColor: statusItem.color,
tupian: item.tupian
? (item.tupian.startsWith('http') ? item.tupian : this.data.ossImageUrl + item.tupian)
@@ -269,7 +260,7 @@ Page({
this.loadCurrentStatusOrders(false);
},
// ✅ 手动点击“加载更多”按钮
// ✅ 手动点击"加载更多"按钮
onLoadMoreTap() {
const key = this.data.currentStatusKey;
const tabData = this.data.dsDingdanShuju[key];
@@ -278,15 +269,6 @@ Page({
this.loadCurrentStatusOrders(false);
},
getZhuangtaiZh(zhuangtai) {
const map = {
1: '已下单', 2: '进行中', 3: '已完成',
4: '退款中', 5: '已退款', 6: '退款失败',
7: '指定中', 8: '结算中'
};
return map[zhuangtai] || '未知状态';
},
goToDsDingdanXiangqing(e) {
const item = e.currentTarget.dataset.item;
if (!item) return;
@@ -304,4 +286,4 @@ Page({
},
onImageError() {}
});
}))
+1 -1
View File
@@ -503,7 +503,7 @@
<!-- ======================= -->
<view class="tech-footer">
<view class="footer-line"></view>
<view class="footer-text">星阙网络 © 7891 科技</view>
<view class="footer-text">星阙网络 © 科技</view>
</view>
</view>
+11 -133
View File
@@ -1,22 +1,11 @@
// pages/dashouzhongxin/dashouzhongxin.js
import request from '../../utils/request.js'
import popupService from '../../services/popupService.js'
import {
isRoleStatusActive,
isCenterPageActive,
syncRoleStatuses,
syncProfileFields,
syncGroupFields,
ensureRoleOnCenterPage,
clearCacheAndEnterNormal,
} from '../../utils/role-tab-bar.js'
import { lockPrimaryRole, migrateLegacyCenterRole, enterLockedRole } from '../../utils/primary-role.js'
import { resolveAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js'
import { createPage, request, isRoleStatusActive, isCenterPageActive, syncRoleStatuses, syncProfileFields, syncGroupFields, ensureRoleOnCenterPage, clearCacheAndEnterNormal, lockPrimaryRole, migrateLegacyCenterRole, enterLockedRole, parseSceneOptions } from '../../utils/base-page.js'
import { resolveAvatarUrl } from '../../utils/avatar.js'
import { openPrivateChat, buildInviterPeerId } from '../../utils/im-user.js'
const PLATFORM_INVITE_CODE = '0000008RffVgKHMj7kQC'
Page({
Page(createPage({
data: {
// 图片路径对象
imgUrls: {},
@@ -31,7 +20,6 @@ Page({
assetBreakdown: [],
pendingAuthList: [],
showAuthSection: false,
isLoading: false,
inviteCode: '',
guanshiInviteCode: '',
zuzhangInviteCode: '',
@@ -48,8 +36,6 @@ Page({
clumber: [],
chengjiaoliang: '',
zaixianZhuangtai: 0,
lastRefreshTime: 0,
canRefresh: true,
isAutoRegistering: false,
inviterCache: null,
zuzhangInviterCache: null,
@@ -165,17 +151,10 @@ Page({
});
this.checkRoleStatuses();
const registerType = options.registerType || 'dashou';
if (options.scene) {
try {
const scene = decodeURIComponent(options.scene);
options.inviteCode = scene;
} catch (e) {
console.error('scene解码失败', e);
}
}
const parsed = parseSceneOptions(options);
const registerType = parsed.registerType || 'dashou';
const { inviteCode } = parsed;
this.checkDashouStatus();
const { inviteCode } = options || {};
if (inviteCode) {
if (registerType === 'guanshi') {
this.setData({ guanshiInviteCode: inviteCode });
@@ -189,26 +168,7 @@ Page({
}, 500);
}
}
// 注意:不再重复声明 app
if (!app.globalData.hasShownPopupOnColdStart) {
app.globalData.hasShownPopupOnColdStart = true;
setTimeout(() => {
popupService.checkAndShow(this, 'dashouduan');
}, 300);
}
},
onReady() {
if (isCenterPageActive(this, 'isDashou', 'dashoustatus')) {
ensureRoleOnCenterPage(this, 'dashou');
}
},
onHide() {
const popupComp = this.selectComponent('#popupNotice');
if (popupComp && popupComp.cleanup) {
popupComp.cleanup();
}
this.checkColdStartPopup('dashouduan');
},
onShow() {
@@ -324,33 +284,9 @@ Page({
});
},
_isApiSuccess(body) {
if (!body) return false;
const code = Number(body.code);
return code === 200 || code === 0;
},
_getApiMsg(body) {
if (!body) return '';
return body.msg || body.message || '';
},
_extractUserData(body) {
if (!body) return {};
if (body.data && typeof body.data === 'object' && !Array.isArray(body.data)) {
return body.data;
}
const userData = { ...body };
delete userData.code;
delete userData.msg;
delete userData.message;
return userData;
},
async refreshAllInfo(showToast = true) {
const now = Date.now();
if (!this.data.canRefresh || (now - this.data.lastRefreshTime < 3000)) return;
this.setData({ lastRefreshTime: now, canRefresh: false, isLoading: true });
this.throttledRefresh(async () => {
this.setData({ isLoading: true });
try {
this.checkRoleStatuses();
const results = await Promise.allSettled([
@@ -376,8 +312,8 @@ Page({
}
} finally {
this.setData({ isLoading: false });
setTimeout(() => this.setData({ canRefresh: true }), 3000);
}
}, 3000);
},
refreshDashouInfo() {
@@ -875,17 +811,6 @@ Page({
});
},
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');
@@ -905,11 +830,6 @@ Page({
}
},
loadAvatar() {
const avatarUrl = resolveAvatarFromStorage(getApp());
this.setData({ avatarUrl });
},
checkGlobalData() {
const app = getApp();
const globalData = app.globalData || {};
@@ -1058,19 +978,6 @@ Page({
ensureRoleOnCenterPage(this, 'dashou');
},
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] });
@@ -1079,19 +986,6 @@ Page({
goToWithdraw() { wx.navigateTo({ url: '/pages/withdraw/withdraw' }) },
goToTotalWithdraw() { this.goToWithdraw(); },
goToKefu() {
const app = getApp();
const { link, enterpriseId } = app.globalData.kefuConfig || {};
if (!link || !enterpriseId) {
wx.showToast({ title: '客服配置未加载', icon: 'none' });
return;
}
wx.openCustomerServiceChat({
extInfo: { url: link },
corpId: enterpriseId,
});
},
goToAuth(e) {
const type = e.currentTarget.dataset.type;
if (!type || !['dashou', 'shangjia', 'guanshi', 'zuzhang'].includes(type)) return;
@@ -1171,20 +1065,4 @@ Page({
});
},
clearCache() {
wx.showModal({
title: '清除缓存',
content: '将清除所有登录与身份数据,并回到点单端,确定继续?',
confirmText: '确定',
cancelText: '取消',
success: (r) => {
if (r.confirm) clearCacheAndEnterNormal(this);
},
});
},
switchToNormal() {
lockPrimaryRole('normal');
wx.reLaunch({ url: '/pages/mine/mine' });
},
})
}, { roleConfig: { role: 'dashou', statusKey: 'dashoustatus', dataFlag: 'isDashou', pageId: 'dashouduan', apiUrl: '/yonghu/dashouxinxi' } }))
+1 -1
View File
@@ -193,7 +193,7 @@
>
<view class="rank-cell-inner">
<image class="rank-bg-icon" src="{{item.icon}}" mode="aspectFit" />
<view class="rank-content">
<view class="rank-cell-content">
<image class="rank-main-icon" src="{{item.icon}}" mode="aspectFit" />
<text class="rank-cell-name">{{item.name}}</text>
<text class="rank-cell-sub">{{item.sub}}</text>
+12 -10
View File
@@ -469,7 +469,7 @@ page {
position: relative;
display: flex;
align-items: center;
padding: 0 20rpx;
padding: 0 24rpx;
box-sizing: border-box;
border-radius: 16rpx;
overflow: hidden;
@@ -477,25 +477,27 @@ page {
.rank-bg-icon {
position: absolute;
width: 120rpx;
height: 120rpx;
right: -12rpx;
width: 100rpx;
height: 100rpx;
right: 8rpx;
top: 50%;
transform: translateY(-50%) rotate(-10deg);
opacity: 0.15;
opacity: 0.12;
z-index: 0;
pointer-events: none;
}
.rank-content {
.rank-cell-content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
}
.rank-main-icon {
width: 56rpx;
height: 56rpx;
margin-bottom: 6rpx;
width: 44rpx;
height: 44rpx;
margin-bottom: 8rpx;
}
.rank-shape-right .rank-cell-inner {
@@ -509,7 +511,7 @@ page {
transform: translateY(-50%) rotate(10deg);
}
.rank-shape-right .rank-content {
.rank-shape-right .rank-cell-content {
text-align: right;
}
+45 -45
View File
@@ -1,90 +1,90 @@
<view class="chat-page">
<scroll-view class="msg-list" scroll-y="true"
<scroll-view class="chat-msg-list" scroll-y="true"
scroll-into-view="{{scrollToView}}"
refresher-enabled="{{true}}"
refresher-triggered="{{isRefreshing}}"
bindrefresherrefresh="onPullDownRefresh"
style="top: 20rpx; bottom: calc({{bottomSafeHeight}}rpx + {{keyboardHeight}}px + env(safe-area-inset-bottom) + 20rpx);">
<view class="loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
<view class="chat-loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
<block wx:for="{{messages}}" wx:key="messageId">
<view class="msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="msg-row {{item.senderId === currentUser.id ? 'right' : 'left'}}">
<view class="chat-msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="chat-msg-row {{item.senderId === currentUser.id ? 'chat-msg-row--right' : 'chat-msg-row--left'}}">
<!-- 对方头像及昵称 -->
<view wx:if="{{item.senderId !== currentUser.id}}" class="sender-info">
<image class="avatar" src="{{item.senderData.avatar || '/images/default-avatar.png'}}" mode="aspectFill" />
<image class="chat-avatar" src="{{item.senderData.avatar || '/images/default-avatar.png'}}" mode="aspectFill" />
<text class="sender-name">{{item.senderData.name || item.senderId}}</text>
</view>
<view class="bubble {{item.senderId === currentUser.id ? 'bubble-right' : 'bubble-left'}}"
<view class="chat-bubble {{item.senderId === currentUser.id ? 'chat-bubble--right' : 'chat-bubble--left'}}"
data-messageid="{{item.messageId}}"
data-text="{{item.type==='text' ? item.payload.text : ''}}"
bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}"
data-url="{{item.type==='image' ? item.payload.url : ''}}">
<text wx:if="{{item.type === 'text'}}" class="msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="msg-image" src="{{item.payload.url}}" mode="widthFix" />
<view wx:elif="{{item.type === 'order'}}" class="order-bubble" data-payload="{{item.payload}}" bindtap="viewOrderDetail">
<text class="order-label">[订单]</text>
<text class="order-info">ID: {{item.payload.orderId}}</text>
<text class="order-info">内容: {{item.payload.jieshao}}</text>
<text class="order-info">金额: ¥{{item.payload.jine}}</text>
<view class="order-detail-link">点击查看详情</view>
<text wx:if="{{item.type === 'text'}}" class="chat-msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="chat-msg-image" src="{{item.payload.url}}" mode="widthFix" />
<view wx:elif="{{item.type === 'order'}}" class="chat-order-bubble" data-payload="{{item.payload}}" bindtap="viewOrderDetail">
<text class="chat-order-label">[订单]</text>
<text class="chat-order-info">ID: {{item.payload.orderId}}</text>
<text class="chat-order-info">内容: {{item.payload.jieshao}}</text>
<text class="chat-order-info">金额: ¥{{item.payload.jine}}</text>
<view class="chat-order-info" style="color:#007aff;margin-top:8rpx">点击查看详情</view>
</view>
</view>
<!-- 自己的头像 -->
<image wx:if="{{item.senderId === currentUser.id}}" class="avatar" src="{{currentUser.avatar}}" mode="aspectFill" />
<image wx:if="{{item.senderId === currentUser.id}}" class="chat-avatar" src="{{currentUser.avatar}}" mode="aspectFill" />
</view>
</view>
</block>
<view id="msg-bottom" style="height:20rpx;"></view>
</scroll-view>
<view class="input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
<view class="plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
<view class="plus-grid">
<view class="plus-item" catchtap="openEmojiFromPlus">
<text class="plus-icon">😀</text>
<text class="plus-label">表情</text>
<view class="chat-input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
<view class="chat-plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
<view class="chat-plus-grid">
<view class="chat-plus-item" catchtap="openEmojiFromPlus">
<text class="chat-plus-icon">😀</text>
<text class="chat-plus-label">表情</text>
</view>
<view class="plus-item" catchtap="chooseImage">
<text class="plus-icon">📷</text>
<text class="plus-label">图片</text>
<view class="chat-plus-item" catchtap="chooseImage">
<text class="chat-plus-icon">📷</text>
<text class="chat-plus-label">图片</text>
</view>
<view class="plus-item" catchtap="openOrderSender">
<text class="plus-icon">📋</text>
<text class="plus-label">订单</text>
<view class="chat-plus-item" catchtap="openOrderSender">
<text class="chat-plus-icon">📋</text>
<text class="chat-plus-label">订单</text>
</view>
</view>
</view>
<view class="emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="emoji-scroll">
<view class="emoji-grid">
<view class="chat-emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="chat-emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="chat-emoji-scroll">
<view class="chat-emoji-grid">
<block wx:for="{{emojiList}}" wx:key="*this">
<view class="emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view>
<view class="chat-emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view>
</block>
</view>
</scroll-view>
</view>
<view class="pending-image" wx:if="{{pendingImage}}">
<image class="pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="pending-remove" catchtap="clearPendingImage">✕</view>
<view class="chat-pending-image" wx:if="{{pendingImage}}">
<image class="chat-pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="chat-pending-remove" catchtap="clearPendingImage">✕</view>
</view>
<view class="write-row">
<input class="text-input" placeholder="发送消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="send-btn" bindtap="sendMessage">发送</view>
<view class="plus-btn" bindtap="togglePlusPanel">
<text class="plus-icon-single">➕</text>
<view class="chat-write-row">
<input class="chat-text-input" placeholder="发送消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="chat-send-btn" bindtap="sendMessage">发送</view>
<view class="chat-plus-btn" bindtap="togglePlusPanel">
<text class="chat-plus-icon-single">➕</text>
</view>
</view>
</view>
<view class="detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="detail-content" catchtap="noop">
<text class="detail-text" user-select="true">{{detailText}}</text>
<button class="copy-btn" bindtap="copyDetail">复制</button>
<view class="chat-detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="chat-detail-content" catchtap="noop">
<text class="chat-detail-text" user-select="true">{{detailText}}</text>
<button class="chat-copy-btn" bindtap="copyDetail">复制</button>
</view>
</view>
+2 -93
View File
@@ -1,97 +1,6 @@
.chat-page { width:100%; height:100vh; background:#f5f5f5; }
/* 使用全局 chat-* 样式 */
.msg-list {
position: absolute;
left:0; right:0;
padding: 0 20rpx;
box-sizing: border-box;
overflow-y: auto;
}
.loading-more { text-align:center; font-size:24rpx; color:#999; padding:20rpx 0; }
.time-tag { text-align:center; margin:28rpx 0; font-size:24rpx; color:#b0b0b0; }
.msg-wrapper { margin-bottom:12rpx; }
.msg-row { display:flex; align-items:flex-start; margin-bottom:4rpx; }
.right { justify-content:flex-end; }
.left { justify-content:flex-start; }
.avatar { width:76rpx; height:76rpx; border-radius:50%; background:#e0e0e0; margin:0 12rpx; flex-shrink:0; }
.bubble { max-width:70%; padding:16rpx 20rpx; border-radius:16rpx; position:relative; word-break:break-all; font-size:30rpx; line-height:1.5; }
.bubble-left { background:#fff; border-top-left-radius:4rpx; }
.bubble-right { background:#00aaff; color:#fff; border-top-right-radius:4rpx; }
.msg-text { color:inherit; }
.msg-image { max-width:240rpx; border-radius:10rpx; }
.order-bubble { background:#fff7e0; padding:12rpx; border-radius:8rpx; }
.order-label { font-size:24rpx; color:#e6a23c; font-weight:600; }
.order-info { font-size:24rpx; color:#333; margin:6rpx 0; }
.order-detail-link { color:#007aff; font-size:24rpx; margin-top:8rpx; }
.input-area {
position: fixed;
left:0; right:0;
background:#f7f7f7;
border-top:1rpx solid #ddd;
z-index:10;
}
.plus-panel {
position: absolute;
bottom:100%; left:0; right:0;
background:#fff;
border-top:1rpx solid #e0e0e0;
padding: 20rpx;
transform: translateY(100%);
opacity:0;
transition: transform 0.3s, opacity 0.3s;
pointer-events:none;
z-index:9;
}
.plus-panel.show { transform: translateY(0); opacity:1; pointer-events:auto; }
.plus-grid { display: flex; justify-content: space-around; }
.plus-item { display: flex; flex-direction: column; align-items: center; width: 120rpx; }
.plus-icon { font-size: 60rpx; }
.plus-label { font-size: 24rpx; color: #666; margin-top: 8rpx; }
.emoji-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:transparent; z-index:8; }
.emoji-panel {
position: absolute; bottom:100%; left:0; right:0;
background:#fff; border-top:1rpx solid #e0e0e0;
height:380rpx;
transform: translateY(100%); opacity:0; transition:0.3s;
pointer-events:none; z-index:9;
}
.emoji-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
.emoji-scroll { height:100%; }
.emoji-grid { display:flex; flex-wrap:wrap; padding:20rpx 10rpx; }
.emoji-item { width:14.28%; text-align:center; padding:16rpx 0; font-size:44rpx; }
.pending-image { display:flex; align-items:center; padding:10rpx 20rpx; background:#fff; }
.pending-thumb { width:80rpx; height:80rpx; border-radius:8rpx; }
.pending-remove { font-size:30rpx; color:#999; margin-left:10rpx; }
.write-row { display:flex; padding:0 20rpx 10rpx; align-items:center; }
.text-input {
flex:1; height:76rpx; background:#fff; border-radius:12rpx;
padding:0 20rpx; font-size:30rpx; margin-right:12rpx;
}
.send-btn {
width:120rpx; height:76rpx; background:#00aaff; color:#fff;
font-size:28rpx; font-weight:500; border-radius:12rpx;
display:flex; align-items:center; justify-content:center;
}
.plus-btn {
width:56rpx; height:56rpx;
display:flex; align-items:center; justify-content:center;
margin-left: 10rpx;
}
.plus-icon-single { font-size:46rpx; color:#00aaff; }
.detail-modal { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:999; display:flex; align-items:center; justify-content:center; }
.detail-content { width:80%; max-height:80%; background:#fff; border-radius:16rpx; padding:40rpx; }
.detail-text { font-size:32rpx; line-height:1.6; word-break:break-all; overflow-y:auto; }
.copy-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:20rpx; font-size:30rpx; }
/* 发送者昵称样式 */
/* 发送者昵称样式(群聊特有) */
.sender-info {
display: flex;
flex-direction: column;
+6 -12
View File
@@ -4,11 +4,12 @@ import { ensureLogin } from '../../utils/login';
import { fetchShopGoods, bindShop } from '../../utils/shop';
import { backfillUserProfileCache, getSessionToken } from '../../utils/role-tab-bar';
import PopupService from '../../services/popupService.js';
import { createPage } from '../../utils/base-page.js';
// 扫码场景参数名
const SCENE_PARAM = 'scene';
Page({
Page(createPage({
data: {
ossImageUrl: '',
lunbozhanwei: '',
@@ -107,16 +108,6 @@ Page({
}
},
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
getCurrentTime() {
const now = new Date();
const year = now.getFullYear();
@@ -317,6 +308,7 @@ Page({
showGonggaoDetail() {
this.setData({
showGonggaoModal: true,
_modalLeaving: false,
gonggaoAnim: false,
currentTime: this.getCurrentTime()
});
@@ -325,8 +317,10 @@ Page({
hideGonggaoDetail() {
this.setData({
showGonggaoModal: false,
_modalLeaving: true,
gonggaoAnim: true
});
setTimeout(() => this.setData({ _modalLeaving: false }), 300);
},
/**
@@ -388,4 +382,4 @@ Page({
onUnload() {
this.setData({ gonggaoAnim: false });
}
});
}))
+3 -3
View File
@@ -146,9 +146,9 @@
</view>
<!-- 公告详情弹窗 -->
<view class="gonggao-modal" wx:if="{{showGonggaoModal}}">
<view class="modal-mask" bindtap="hideGonggaoDetail"></view>
<view class="modal-content">
<view class="gonggao-modal {{showGonggaoModal ? 'gonggao-modal--show' : 'gonggao-modal--hide'}}" wx:if="{{showGonggaoModal || _modalLeaving}}">
<view class="modal-mask {{showGonggaoModal ? 'modal-mask--in' : 'modal-mask--out'}}" bindtap="hideGonggaoDetail"></view>
<view class="modal-content {{showGonggaoModal ? 'modal-content--in' : 'modal-content--out'}}">
<view class="modal-header">
<text class="modal-title">公告详情</text>
<view class="modal-close" bindtap="hideGonggaoDetail">×</view>
+79
View File
@@ -411,3 +411,82 @@
align-items: center;
}
.gonggao-modal .modal-mask {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 0;
transition: opacity 0.25s ease;
}
.gonggao-modal .modal-mask--in {
opacity: 1;
}
.gonggao-modal .modal-mask--out {
opacity: 0;
}
.gonggao-modal .modal-content {
position: relative;
z-index: 1;
width: 620rpx;
max-height: 70vh;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
display: flex;
flex-direction: column;
transition: transform 0.25s ease, opacity 0.25s ease;
}
.gonggao-modal .modal-content--in {
transform: scale(1);
opacity: 1;
}
.gonggao-modal .modal-content--out {
transform: scale(0.9);
opacity: 0;
}
.gonggao-modal .modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 32rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.gonggao-modal .modal-title {
font-size: 30rpx;
font-weight: 700;
color: #1a1a1a;
}
.gonggao-modal .modal-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
color: #999;
border-radius: 50%;
background: #f5f5f5;
line-height: 1;
}
.gonggao-modal .modal-body {
flex: 1;
padding: 28rpx 32rpx 36rpx;
max-height: 55vh;
}
.gonggao-modal .modal-text {
font-size: 28rpx;
color: #444;
line-height: 1.8;
word-break: break-all;
}
+32 -122
View File
@@ -1,18 +1,9 @@
// pages/leader/leader.js
import request from '../../utils/request.js'
import {
isRoleStatusActive,
isCenterPageActive,
syncRoleStatuses,
syncProfileFields,
ensureRoleOnCenterPage,
} from '../../utils/role-tab-bar.js'
import { createPage, request, syncRoleStatuses, syncProfileFields, ensureRoleOnCenterPage, isCenterPageActive, parseSceneOptions } from '../../utils/base-page.js'
Page({
Page(createPage({
data: {
// 页面状态
isZuzhang: false,
isLoading: false,
// 邀请码
inviteCode: '',
@@ -28,21 +19,20 @@ Page({
fenhongZonge: '0.00',
},
onLoad() {
wx.redirectTo({ url: '/pages/fighter/fighter' });
return;
onLoad(options) {
const parsed = parseSceneOptions(options)
if (parsed.inviteCode) {
this.setData({ inviteCode: parsed.inviteCode })
}
wx.redirectTo({ url: '/pages/fighter/fighter' })
return
// 以下保留供恢复参考
this.checkLoginStatus()
},
onReady() {
if (isCenterPageActive(this, 'isZuzhang', 'zuzhangstatus')) {
ensureRoleOnCenterPage(this, 'zuzhang')
}
},
onShow() {
this.registerNotificationComponent()
this.checkColdStartPopup('zuzhangduan')
if (isCenterPageActive(this, 'isZuzhang', 'zuzhangstatus')) {
if (!this.data.isZuzhang) this.setData({ isZuzhang: true })
ensureRoleOnCenterPage(this, 'zuzhang')
@@ -66,92 +56,36 @@ Page({
})
return false
}
this.checkZuzhangStatus()
this.checkRoleStatus()
return true
},
// 检查组长状态
checkZuzhangStatus() {
try {
const zuzhangstatus = wx.getStorageSync('zuzhangstatus')
if (isRoleStatusActive(zuzhangstatus)) {
this.setData({ isZuzhang: true })
// 角色状态检查后的额外数据加载(由 mixin 的 checkRoleStatus 调用)
checkGlobalData() {
this.loadUserInfo()
this.getZuzhangInfo()
ensureRoleOnCenterPage(this, 'zuzhang')
} else {
this.setData({ isZuzhang: false })
}
} catch (error) {
console.error('读取缓存失败:', error)
this.setData({ isZuzhang: false })
}
},
// 加载用户信息
loadUserInfo() {
const app = getApp()
const globalData = app.globalData || {}
const touxiang = wx.getStorageSync('touxiang')
const ossImageUrl = globalData.ossImageUrl || ''
const morentouxiang = globalData.morentouxiang || 'avatar/default.jpg'
let avatarUrl = ''
if (touxiang) {
avatarUrl = ossImageUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang)
} else {
avatarUrl = ossImageUrl + (morentouxiang.startsWith('/') ? morentouxiang : '/' + morentouxiang)
}
const uid = wx.getStorageSync('uid') || ''
const nicheng = wx.getStorageSync('nicheng') || globalData.nicheng || ''
this.setData({ avatarUrl, uid, nicheng })
},
// 注册通知组件
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()
}
}
},
// 获取组长信息(刷新)
async getZuzhangInfo() {
this.setData({ isLoading: true })
try {
const res = await request({
this.loadData({
url: '/yonghu/zuzhangxinxi',
method: 'POST'
})
if (res && res.data.code === 200) {
const data = res.data.data
method: 'POST',
errorMsg: '获取信息失败',
onSuccess: (data) => {
this.setData({
ketixian: data.ketixian || '0.00',
guanshiCount: data.guanshiCount || 0,
fenhongZonge: data.fenhongZonge || '0.00',
isLoading: false
})
wx.showToast({ title: '刷新成功', icon: 'success', duration: 1500 })
} else {
throw new Error(res?.data?.msg || '获取信息失败')
}
} catch (error) {
console.error('获取组长信息失败:', error)
this.setData({ isLoading: false })
wx.showToast({ title: error.message || '刷新失败', icon: 'none' })
}
})
},
// 刷新按钮点击
refreshZuzhangInfo() {
this.getZuzhangInfo()
this.throttledRefresh(this.getZuzhangInfo)
},
// 邀请码输入
@@ -160,49 +94,25 @@ Page({
},
// 注册组长
async onRegister() {
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/zuzhangzhuce',
method: 'POST',
data: { inviteCode }
})
if (res && res.data.code === 200) {
const data = res.data.data || {}
syncRoleStatuses(data)
syncProfileFields(data)
if (!Object.prototype.hasOwnProperty.call(data, 'zuzhangstatus')) {
wx.setStorageSync('zuzhangstatus', 1)
}
this.registerWithInviteCode({
inviteCode,
apiUrl: '/yonghu/zuzhangzhuce',
role: 'zuzhang',
statusKey: 'zuzhangstatus',
successMsg: '注册成功!',
onSuccess: (userData) => {
this.setData({
isZuzhang: true,
ketixian: data.ketixian || '0.00',
guanshiCount: data.guanshiCount || 0,
fenhongZonge: data.fenhongZonge || '0.00',
isLoading: false
ketixian: userData.ketixian || '0.00',
guanshiCount: userData.guanshiCount || 0,
fenhongZonge: userData.fenhongZonge || '0.00',
})
this.loadUserInfo()
ensureRoleOnCenterPage(this, 'zuzhang')
wx.showToast({ title: '注册成功!', icon: 'success', duration: 2000 })
} else {
throw new Error(res?.data?.msg || '注册失败')
}
} catch (error) {
console.error('注册失败:', error)
this.setData({ isLoading: false })
wx.showToast({ title: error.message || '注册失败', icon: 'error' })
}
})
},
// 跳转到提现页面
@@ -222,4 +132,4 @@ Page({
goToYaoqingGuanshi() {
wx.navigateTo({ url: '/pages/invite-manager/invite-manager' })
}
})
}, { roleConfig: { role: 'dashou', statusKey: 'zuzhangstatus', dataFlag: 'isZuzhang', pageId: 'zuzhangduan', apiUrl: '/yonghu/zuzhangxinxi' } }))
+129 -163
View File
@@ -1,124 +1,114 @@
<!-- pages/guanshi-paihang/guanshi-paihang.wxml -->
<view class="guanshi-paihang-page">
<!-- pages/manager-rank/manager-rank.wxml -->
<view class="rank-page">
<!-- 赛博朋克背景元素 -->
<view class="cyber-bg">
<view class="rank-cyber-bg">
<!-- 网格线 -->
<view class="cyber-grid"></view>
<view class="rank-cyber-grid"></view>
<!-- 扫描线 -->
<view class="cyber-scanline"></view>
<view class="rank-cyber-scanline"></view>
<!-- 流动数据流 -->
<view class="data-streams">
<view class="stream stream-1"></view>
<view class="stream stream-2"></view>
<view class="stream stream-3"></view>
<view class="rank-data-streams">
<view class="rank-stream rank-stream--1"></view>
<view class="rank-stream rank-stream--2"></view>
<view class="rank-stream rank-stream--3"></view>
</view>
<!-- 浮动粒子 -->
<view class="floating-particles">
<view class="particle" wx:for="{{12}}" wx:key="index"></view>
</view>
<view class="rank-particle" wx:for="{{12}}" wx:key="index"></view>
</view>
<!-- 页面头部 -->
<view class="page-header">
<view class="rank-page-header">
<!-- 标题 -->
<view class="title-container">
<text class="main-title">管事排行榜</text>
<view class="title-line"></view>
<text class="sub-title">GUANSHI RANKING</text>
<view class="rank-title-container">
<text class="rank-main-title">管事排行榜</text>
<view class="rank-title-line"></view>
<text class="rank-sub-title">GUANSHI RANKING</text>
</view>
<!-- 切换按钮 -->
<view class="toggle-container">
<view class="toggle-bg" style="transform: translateX({{paihangLeixing == 1 ? '0' : '100%'}});"></view>
<view class="rank-toggle-container">
<view class="rank-toggle-bg" style="transform: translateX({{paihangLeixing == 1 ? '0' : '100%'}});"></view>
<view
class="toggle-btn {{paihangLeixing == 1 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
class="rank-toggle-btn {{paihangLeixing == 1 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
data-leixing="1"
bindtap="qiehuangPaihangLeixing"
>
<text class="toggle-text">今日充值人数</text>
<view class="toggle-line {{paihangLeixing == 1 ? 'active' : ''}}"></view>
<text class="rank-toggle-text">今日充值人数</text>
<view class="rank-toggle-line {{paihangLeixing == 1 ? 'active' : ''}}"></view>
</view>
<view
class="toggle-btn {{paihangLeixing == 2 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
class="rank-toggle-btn {{paihangLeixing == 2 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
data-leixing="2"
bindtap="qiehuangPaihangLeixing"
>
<text class="toggle-text">今月充值人数</text>
<view class="toggle-line {{paihangLeixing == 2 ? 'active' : ''}}"></view>
<text class="rank-toggle-text">今月充值人数</text>
<view class="rank-toggle-line {{paihangLeixing == 2 ? 'active' : ''}}"></view>
</view>
</view>
</view>
<!-- 加载状态 -->
<view wx:if="{{jiazaiZhuangtai}}" class="loading-state">
<view class="loading-content">
<view class="loading-spinner">
<view class="spinner-outer"></view>
<view class="spinner-inner"></view>
<view class="spinner-core"></view>
</view>
<text class="loading-text">数据加载中</text>
<text class="loading-sub">Connecting to server...</text>
<view wx:if="{{jiazaiZhuangtai}}" class="rank-loading-state">
<view class="rank-loading-spinner">
<view class="rank-spinner-outer"></view>
<view class="rank-spinner-inner"></view>
<view class="rank-spinner-core"></view>
</view>
<text class="rank-loading-text">数据加载中</text>
<text class="rank-loading-sub">Connecting to server...</text>
</view>
<!-- 排行榜内容 -->
<view wx:else class="ranking-content">
<view wx:else class="rank-content">
<!-- 虚拟数据提示 -->
<view wx:if="{{!xianshiZhenshiShuju}}" class="demo-notice">
<view class="notice-icon">📡</view>
<view class="notice-content">
<text class="notice-title">排行榜数据准备中</text>
<text class="notice-desc">当前显示为模拟数据,实时排名即将开放</text>
<view wx:if="{{!xianshiZhenshiShuju}}" class="rank-demo-notice">
<view class="rank-notice-icon">📡</view>
<view class="rank-notice-content">
<text class="rank-notice-title">排行榜数据准备中</text>
<text class="rank-notice-desc">当前显示为模拟数据,实时排名即将开放</text>
</view>
<view class="notice-glow"></view>
</view>
<!-- ========== 前三名区域 ========== -->
<view class="top-three-section" wx:if="{{qiansanMing.length > 0}}">
<view class="rank-top-three" wx:if="{{qiansanMing.length > 0}}">
<!-- 第二名 -->
<view class="rank-card rank-2 {{flashCardIndex == 1 && flashCardType == 'top3' ? 'flash' : ''}}"
<view class="rank-card rank-card--2 {{flashCardIndex == 1 && flashCardType == 'top3' ? 'flash' : ''}}"
animation="{{flashCardIndex == 1 && flashCardType == 'top3' ? flashAnimation : ''}}"
data-index="1" data-type="top3" bindtap="handleCardTap">
<view class="podium-base podium-silver">
<view class="podium-glow"></view>
<text class="podium-number">2</text>
<view class="rank-podium-base rank-podium-silver">
<text class="rank-podium-number">2</text>
</view>
<view class="rank-content">
<!-- 头像容器 -->
<view class="avatar-frame frame-silver">
<view class="avatar-container">
<image class="avatar-image" src="{{qiansanMing[1] ? qiansanMing[1].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-silver"></view>
<view class="avatar-sparkles">
<view class="sparkle s1"></view>
<view class="sparkle s2"></view>
<view class="sparkle s3"></view>
<view class="rank-avatar-frame rank-frame-silver">
<view class="rank-avatar-container">
<image class="rank-avatar-image" src="{{qiansanMing[1] ? qiansanMing[1].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo rank-halo-silver"></view>
<view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
</view>
</view>
<view class="rank-badge badge-silver">2</view>
<view class="rank-badge rank-badge-silver">2</view>
</view>
<!-- 用户信息 -->
<view class="user-info-card">
<view class="info-header">
<text class="user-name">{{qiansanMing[1] ? qiansanMing[1].nicheng : '等待上榜'}}</text>
<view class="rank-user-info-card">
<text class="rank-user-name">{{qiansanMing[1] ? qiansanMing[1].nicheng : '等待上榜'}}</text>
<view class="rank-info-body">
<view class="rank-uid-row">
<text class="rank-uid-label">ID:</text>
<text class="rank-uid-value">{{qiansanMing[1] ? qiansanMing[1].uid : '----'}}</text>
</view>
<view class="info-body">
<view class="uid-row">
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[1] ? qiansanMing[1].uid : '----'}}</text>
</view>
<view class="income-row">
<text class="income-label">充值大手:</text>
<view class="income-value">
<text class="amount {{qiansanMing[1] ? qiansanMing[1].jineClass : ''}}">{{qiansanMing[1] ? qiansanMing[1].jine : '0'}}</text>
<text class="unit">人</text>
<view class="rank-income-row">
<text class="rank-income-label">充值大手:</text>
<view class="rank-income-value">
<text class="rank-amount {{qiansanMing[1] ? qiansanMing[1].jineClass : ''}}">{{qiansanMing[1] ? qiansanMing[1].jine : '0'}}</text>
<text class="rank-unit">人</text>
</view>
</view>
</view>
@@ -127,62 +117,47 @@
</view>
<!-- 第一名 -->
<view class="rank-card rank-1 {{flashCardIndex == 0 && flashCardType == 'top3' ? 'flash' : ''}}"
<view class="rank-card rank-card--1 {{flashCardIndex == 0 && flashCardType == 'top3' ? 'flash' : ''}}"
animation="{{flashCardIndex == 0 && flashCardType == 'top3' ? flashAnimation : ''}}"
data-index="0" data-type="top3" bindtap="handleCardTap">
<!-- 皇冠 -->
<view class="crown-container" animation="{{crownAnimation}}">
<view class="crown">
<text class="crown-icon">👑</text>
<view class="crown-rays">
<view class="ray ray-1"></view>
<view class="ray ray-2"></view>
<view class="ray ray-3"></view>
<view class="ray ray-4"></view>
<view class="ray ray-5"></view>
<view class="ray ray-6"></view>
</view>
<view class="crown-glow"></view>
</view>
<view class="rank-crown-container" animation="{{crownAnimation}}">
<text class="rank-crown-icon">👑</text>
<view class="rank-crown-glow"></view>
</view>
<view class="podium-base podium-gold">
<view class="podium-glow"></view>
<text class="podium-number">1</text>
<view class="rank-podium-base rank-podium-gold">
<text class="rank-podium-number">1</text>
</view>
<view class="rank-content">
<!-- 头像容器 -->
<view class="avatar-frame frame-gold">
<view class="avatar-container">
<image class="avatar-image" src="{{qiansanMing[0] ? qiansanMing[0].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-gold"></view>
<view class="avatar-sparkles">
<view class="sparkle s1"></view>
<view class="sparkle s2"></view>
<view class="sparkle s3"></view>
<view class="sparkle s4"></view>
<view class="rank-avatar-frame rank-frame-gold">
<view class="rank-avatar-container">
<image class="rank-avatar-image" src="{{qiansanMing[0] ? qiansanMing[0].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo rank-halo-gold"></view>
<view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
<view class="rank-sparkle" style="bottom:15%;right:15%;animation-delay:1.5s"></view>
</view>
</view>
<view class="rank-badge badge-gold">1</view>
<view class="rank-badge rank-badge-gold">1</view>
</view>
<!-- 用户信息 -->
<view class="user-info-card">
<view class="info-header">
<text class="user-name">{{qiansanMing[0] ? qiansanMing[0].nicheng : '等待上榜'}}</text>
<view class="rank-user-info-card">
<text class="rank-user-name">{{qiansanMing[0] ? qiansanMing[0].nicheng : '等待上榜'}}</text>
<view class="rank-info-body">
<view class="rank-uid-row">
<text class="rank-uid-label">ID:</text>
<text class="rank-uid-value">{{qiansanMing[0] ? qiansanMing[0].uid : '----'}}</text>
</view>
<view class="info-body">
<view class="uid-row">
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[0] ? qiansanMing[0].uid : '----'}}</text>
</view>
<view class="income-row">
<text class="income-label">充值大手:</text>
<view class="income-value">
<text class="amount {{qiansanMing[0] ? qiansanMing[0].jineClass : ''}}">{{qiansanMing[0] ? qiansanMing[0].jine : '0'}}</text>
<text class="unit">人</text>
<view class="rank-income-row">
<text class="rank-income-label">充值大手:</text>
<view class="rank-income-value">
<text class="rank-amount {{qiansanMing[0] ? qiansanMing[0].jineClass : ''}}">{{qiansanMing[0] ? qiansanMing[0].jine : '0'}}</text>
<text class="rank-unit">人</text>
</view>
</view>
</view>
@@ -191,45 +166,40 @@
</view>
<!-- 第三名 -->
<view class="rank-card rank-3 {{flashCardIndex == 2 && flashCardType == 'top3' ? 'flash' : ''}}"
<view class="rank-card rank-card--3 {{flashCardIndex == 2 && flashCardType == 'top3' ? 'flash' : ''}}"
animation="{{flashCardIndex == 2 && flashCardType == 'top3' ? flashAnimation : ''}}"
data-index="2" data-type="top3" bindtap="handleCardTap">
<view class="podium-base podium-bronze">
<view class="podium-glow"></view>
<text class="podium-number">3</text>
<view class="rank-podium-base rank-podium-bronze">
<text class="rank-podium-number">3</text>
</view>
<view class="rank-content">
<!-- 头像容器 -->
<view class="avatar-frame frame-bronze">
<view class="avatar-container">
<image class="avatar-image" src="{{qiansanMing[2] ? qiansanMing[2].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-bronze"></view>
<view class="avatar-sparkles">
<view class="sparkle s1"></view>
<view class="sparkle s2"></view>
<view class="sparkle s3"></view>
<view class="rank-avatar-frame rank-frame-bronze">
<view class="rank-avatar-container">
<image class="rank-avatar-image" src="{{qiansanMing[2] ? qiansanMing[2].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo rank-halo-bronze"></view>
<view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
</view>
</view>
<view class="rank-badge badge-bronze">3</view>
<view class="rank-badge rank-badge-bronze">3</view>
</view>
<!-- 用户信息 -->
<view class="user-info-card">
<view class="info-header">
<text class="user-name">{{qiansanMing[2] ? qiansanMing[2].nicheng : '等待上榜'}}</text>
<view class="rank-user-info-card">
<text class="rank-user-name">{{qiansanMing[2] ? qiansanMing[2].nicheng : '等待上榜'}}</text>
<view class="rank-info-body">
<view class="rank-uid-row">
<text class="rank-uid-label">ID:</text>
<text class="rank-uid-value">{{qiansanMing[2] ? qiansanMing[2].uid : '----'}}</text>
</view>
<view class="info-body">
<view class="uid-row">
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[2] ? qiansanMing[2].uid : '----'}}</text>
</view>
<view class="income-row">
<text class="income-label">充值大手:</text>
<view class="income-value">
<text class="amount {{qiansanMing[2] ? qiansanMing[2].jineClass : ''}}">{{qiansanMing[2] ? qiansanMing[2].jine : '0'}}</text>
<text class="unit">人</text>
<view class="rank-income-row">
<text class="rank-income-label">充值大手:</text>
<view class="rank-income-value">
<text class="rank-amount {{qiansanMing[2] ? qiansanMing[2].jineClass : ''}}">{{qiansanMing[2] ? qiansanMing[2].jine : '0'}}</text>
<text class="rank-unit">人</text>
</view>
</view>
</view>
@@ -239,50 +209,46 @@
</view>
<!-- ========== 其他排名列表 ========== -->
<view class="other-ranks-section" wx:if="{{paihangShuju.length > 0}}">
<view class="section-title">
<view class="title-line"></view>
<text class="title-text">其他排名</text>
<view class="title-line"></view>
<view wx:if="{{paihangShuju.length > 0}}">
<view class="rank-section-title">
<view class="rank-section-line"></view>
<text class="rank-section-text">其他排名</text>
<view class="rank-section-line"></view>
</view>
<view class="rank-list">
<view class="rank-item {{flashCardIndex == index && flashCardType == 'normal' ? 'flash' : ''}}"
<view class="rank-list-item {{flashCardIndex == index && flashCardType == 'normal' ? 'flash' : ''}}"
wx:for="{{paihangShuju}}" wx:key="index"
animation="{{flashCardIndex == index && flashCardType == 'normal' ? flashAnimation : ''}}"
data-index="{{index}}" data-type="normal" bindtap="handleCardTap">
<view class="rank-number">
<text class="number-text">{{xianshiZhenshiShuju ? index + 4 : index + 1}}</text>
<view class="number-glow"></view>
<text class="rank-number-text">{{xianshiZhenshiShuju ? index + 4 : index + 1}}</text>
<view class="rank-number-glow"></view>
</view>
<view class="rank-avatar">
<image class="rank-avatar-img" src="{{item.full_touxiang}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo"></view>
<view class="rank-list-avatar">
<image class="rank-list-avatar-img" src="{{item.full_touxiang}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-list-avatar-halo"></view>
</view>
<view class="rank-info">
<text class="rank-name">{{item.nicheng}}</text>
<text class="rank-uid">UID: {{item.uid}}</text>
<view class="rank-list-info">
<text class="rank-list-name">{{item.nicheng}}</text>
<text class="rank-list-uid">UID: {{item.uid}}</text>
</view>
<view class="rank-income">
<text class="income-amount {{item.jineClass}}">{{item.jine}}</text>
<text class="income-unit">人</text>
<view class="income-glow"></view>
</view>
<view class="rank-hover"></view>
<view class="rank-list-income">
<text class="rank-income-amount {{item.jineClass}}">{{item.jine}}</text>
<text class="rank-income-unit">人</text>
<view class="rank-income-glow"></view>
</view>
</view>
</view>
<!-- 空状态 -->
<view wx:if="{{xianshiZhenshiShuju && paihangShuju.length === 0 && qiansanMing.length === 0}}" class="empty-state">
<view class="empty-icon">🏆</view>
<text class="empty-title">暂无排名数据</text>
<text class="empty-desc">成为第一个登榜的管事吧</text>
<view wx:if="{{xianshiZhenshiShuju && paihangShuju.length === 0 && qiansanMing.length === 0}}" class="rank-empty-state">
<view class="rank-empty-icon">🏆</view>
<text class="rank-empty-title">暂无排名数据</text>
<text class="rank-empty-desc">成为第一个登榜的管事吧</text>
</view>
</view>
</view>
File diff suppressed because it is too large. Load diff
+81 -262
View File
@@ -1,16 +1,17 @@
// pages/guanshizhongxin/guanshizhongxin.js
import request from '../../utils/request.js';
import popupService from '../../services/popupService.js';
import {
createPage, request, isRoleStatusActive, syncRoleStatuses, syncProfileFields,
syncGroupFields, ensureRoleOnCenterPage, refreshTabBar, parseSceneOptions,
isApiSuccess, getApiMsg, extractUserData
} from '../../utils/base-page.js';
import { buildInviterPeerId } from '../../utils/im-user.js';
import { resolveAvatarUrl } from '../../utils/avatar.js';
Page({
Page(createPage({
data: {
// 图片路径对象
imgUrls: {},
isGuanshi: false,
isLoading: false,
inviteCode: '',
uid: '',
nicheng: '',
@@ -33,14 +34,9 @@ Page({
onLoad(options) {
let url = '/pages/fighter/fighter?registerType=guanshi';
if (options.scene) {
try {
url += '&inviteCode=' + encodeURIComponent(decodeURIComponent(options.scene));
} catch (e) {
console.error('scene解码失败', e);
}
} else if (options.inviteCode) {
url += '&inviteCode=' + encodeURIComponent(options.inviteCode);
const parsed = parseSceneOptions(options);
if (parsed.inviteCode) {
url += '&inviteCode=' + encodeURIComponent(parsed.inviteCode);
}
wx.redirectTo({ url });
return;
@@ -50,7 +46,7 @@ Page({
const ossImageUrl = app.globalData.ossImageUrl || '';
const imgBase = ossImageUrl + 'beijing/guanshiduan/';
this.setData({
imgUrls: {
imgUrls: this.initImageUrls({
pageBg: imgBase + 'page-bg.jpg',
topDecor: imgBase + 'top-decor.png',
cardBg1: imgBase + 'card-bg1.png',
@@ -66,48 +62,30 @@ Page({
iconWithdraw: imgBase + 'icon-withdraw.png',
iconContact: imgBase + 'icon-contact.png',
iconArrow: imgBase + 'icon-arrow.png',
}
})
});
// 处理扫码 scene
if (options.scene) {
try {
const scene = decodeURIComponent(options.scene);
options.inviteCode = scene;
} catch (e) {
console.error('scene解码失败', e);
}
}
this.checkRoleStatus();
this.checkGuanshiStatus();
const { inviteCode } = options || {};
const inviteCode = parsed.inviteCode;
if (inviteCode) {
this.setData({ inviteCode });
setTimeout(() => {
this.handleInviteCodeWithLoginCheck(inviteCode);
this.handleInviteCodeWithLoginCheck({
inviteCode,
role: 'dashou',
statusKey: 'guanshistatus',
alreadyMsg: '已是管事',
onNeedLogin: (code) => this.loginAndRegisterWithInviteCode({
inviteCode: code,
onLoginSuccess: (wxCode, invCode) => this.callLoginRegisterApi(wxCode, invCode),
}),
onAlreadyRegistered: () => this._enterRegisteredGuanshiState('您已是管事'),
});
}, 500);
}
if (!app.globalData.hasShownPopupOnColdStart) {
app.globalData.hasShownPopupOnColdStart = true;
setTimeout(() => {
popupService.checkAndShow(this, 'guanshiduan');
}, 300);
}
},
onReady() {
if (this._isGuanshiPageActive()) {
this.ensureGuanshiRoleAndTabBar();
}
},
onHide() {
const popupComp = this.selectComponent('#popupNotice');
if (popupComp && popupComp.cleanup) {
popupComp.cleanup();
}
this.checkColdStartPopup('guanshiduan');
},
onShow() {
@@ -120,7 +98,7 @@ Page({
if (!this.data.isGuanshi) {
this.setData({ isGuanshi: true });
}
this.ensureGuanshiRoleAndTabBar();
ensureRoleOnCenterPage(this, 'guanshi');
setTimeout(() => {
this.getGuanshiInfo();
this.fetchChenghaoList();
@@ -132,23 +110,21 @@ Page({
const options = currentPage.options || {};
if (options.inviteCode && !this._isGuanshiPageActive()) {
setTimeout(() => {
this.handleInviteCodeWithLoginCheck(options.inviteCode);
this.handleInviteCodeWithLoginCheck({
inviteCode: options.inviteCode,
role: 'dashou',
statusKey: 'guanshistatus',
alreadyMsg: '已是管事',
onNeedLogin: (code) => this.loginAndRegisterWithInviteCode({
inviteCode: code,
onLoginSuccess: (wxCode, invCode) => this.callLoginRegisterApi(wxCode, invCode),
}),
onAlreadyRegistered: () => this._enterRegisteredGuanshiState('您已是管事'),
});
}, 300);
}
},
// 全局通知注册
registerNotificationComponent() {
const app = getApp();
const comp = this.selectComponent('#global-notification');
if (comp && comp.showNotification) {
app.globalData.globalNotification = {
show: (data) => comp.showNotification(data),
hide: () => comp.hideNotification()
};
}
},
// 称号列表
async fetchChenghaoList() {
try {
@@ -166,7 +142,7 @@ Page({
},
// 联系组长
async contactInviter() {
async contactInviter() {
wx.showLoading({ title: '获取组长信息...', mask: true });
try {
const res = await request({
@@ -207,35 +183,9 @@ async contactInviter() {
} finally {
wx.hideLoading();
}
},
},
// 管事状态辅助
_isApiSuccess(body) {
if (!body) return false;
const code = Number(body.code);
return code === 200 || code === 0;
},
_getApiMsg(body) {
if (!body) return '';
return body.msg || body.message || '';
},
_extractUserData(body) {
if (!body) return {};
if (body.data && typeof body.data === 'object' && !Array.isArray(body.data)) {
return body.data;
}
const userData = { ...body };
delete userData.code;
delete userData.msg;
delete userData.message;
return userData;
},
_isGuanshiStatusActive(status) {
return Number(status) === 1;
},
_isAlreadyGuanshiMsg(msg) {
if (!msg || typeof msg !== 'string') return false;
return msg.includes('已是管事') || msg.includes('已经是管事');
@@ -247,21 +197,6 @@ async contactInviter() {
app.globalData.guanshistatus = 1;
},
// 仅同步后端返回的身份 status 字段
_syncRoleStatuses(data) {
if (!data || typeof data !== 'object') return;
const app = getApp();
const roleFields = ['dashoustatus', 'guanshistatus', 'shangjiastatus', 'zuzhangstatus', 'kaoheguanstatus'];
roleFields.forEach(field => {
if (Object.prototype.hasOwnProperty.call(data, field)) {
const val = Number(data[field]);
const normalized = Number.isNaN(val) ? 0 : val;
wx.setStorageSync(field, normalized);
app.globalData[field] = normalized;
}
});
},
// 后端若返回打手详情字段则写入 globalData
_syncDashouGlobalData(data) {
const app = getApp();
@@ -286,31 +221,7 @@ async contactInviter() {
},
_isGuanshiPageActive() {
return this.data.isGuanshi || this._isGuanshiStatusActive(wx.getStorageSync('guanshistatus'));
},
// 管事页必须切到 guanshi 角色
ensureGuanshiRoleAndTabBar() {
const app = getApp();
if (app.setCurrentRole) {
app.setCurrentRole('guanshi');
} else {
app.globalData.currentRole = 'guanshi';
wx.setStorageSync('currentRole', 'guanshi');
app.emitEvent('currentRoleChanged', { role: 'guanshi' });
}
this._refreshTabBar();
},
_refreshTabBar() {
const tryRefresh = () => {
const tabBar = this.getTabBar && this.getTabBar();
if (tabBar && tabBar.refresh) {
tabBar.refresh();
}
};
tryRefresh();
[50, 150, 300, 500, 800].forEach((delay) => setTimeout(tryRefresh, delay));
return this.data.isGuanshi || isRoleStatusActive(wx.getStorageSync('guanshistatus'));
},
_applyRegisterSuccess(userData, toastMsg) {
@@ -319,31 +230,16 @@ async contactInviter() {
if (data.token) wx.setStorageSync('token', data.token);
if (data.token) app.globalData.token = data.token;
const profileFields = ['nicheng', 'uid', 'touxiang'];
profileFields.forEach(field => {
if (data[field] !== undefined && data[field] !== null) {
wx.setStorageSync(field, data[field]);
if (field === 'nicheng') {
app.globalData.nicheng = data[field];
}
}
});
syncProfileFields(data);
syncRoleStatuses(data);
// 身份列表以后端返回的 *status 字段为准
this._syncRoleStatuses(data);
if (!Object.prototype.hasOwnProperty.call(data, 'guanshistatus') &&
!this._isGuanshiStatusActive(wx.getStorageSync('guanshistatus'))) {
!isRoleStatusActive(wx.getStorageSync('guanshistatus'))) {
this._syncGuanshiCache();
}
this._syncDashouGlobalData(data);
const groupFields = ['dashouqun', 'dashouqunid', 'guanshiqun', 'guanshiqunid'];
groupFields.forEach(field => {
if (data[field] !== undefined) {
wx.setStorageSync(field, data[field]);
app.globalData[field] = data[field];
}
});
syncGroupFields(data);
const guanshiData = {
gszhstatus: data.gszhstatus ?? '',
@@ -392,41 +288,6 @@ async contactInviter() {
}
},
// 邀请码处理
handleInviteCodeWithLoginCheck(inviteCode) {
const token = wx.getStorageSync('token');
const uid = wx.getStorageSync('uid');
if (!token || !uid) {
this.loginAndRegisterWithInviteCode(inviteCode);
} else {
if (!this.data.isGuanshi) {
wx.showLoading({ title: '自动注册中...', mask: true });
this.callRegisterApi(inviteCode);
} else {
this._enterRegisteredGuanshiState('您已是管事');
}
}
},
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();
wx.reLaunch({ url: '/pages/gerenzhongxin/gerenzhongxin' });
}
});
},
callLoginRegisterApi(code, inviteCode) {
const that = this;
const app = getApp();
@@ -443,18 +304,18 @@ async contactInviter() {
header: { 'content-type': 'application/json' },
success: (res) => {
const body = res.data || {};
if (res.statusCode === 200 && that._isApiSuccess(body)) {
const userData = that._extractUserData(body);
const toastMsg = that._isAlreadyGuanshiMsg(that._getApiMsg(body))
? that._getApiMsg(body)
if (res.statusCode === 200 && isApiSuccess(body)) {
const userData = extractUserData(body);
const toastMsg = that._isAlreadyGuanshiMsg(getApiMsg(body))
? getApiMsg(body)
: '注册成功!';
that._applyRegisterSuccess(userData, toastMsg);
} else if (res.statusCode === 200 && that._isAlreadyGuanshiMsg(that._getApiMsg(body))) {
that._enterRegisteredGuanshiState(that._getApiMsg(body) || '您已是管事', that._extractUserData(body));
} else if (res.statusCode === 200 && that._isAlreadyGuanshiMsg(getApiMsg(body))) {
that._enterRegisteredGuanshiState(getApiMsg(body) || '您已是管事', extractUserData(body));
} else {
wx.hideLoading();
that.setData({ isLoading: false });
wx.showToast({ title: that._getApiMsg(body) || '注册失败', icon: 'none' });
wx.showToast({ title: getApiMsg(body) || '注册失败', icon: 'none' });
}
},
fail: () => {
@@ -472,18 +333,18 @@ async contactInviter() {
data: { inviteCode }
});
const body = res?.data || {};
if (res && this._isApiSuccess(body)) {
const userData = this._extractUserData(body);
const toastMsg = this._isAlreadyGuanshiMsg(this._getApiMsg(body))
? this._getApiMsg(body)
if (res && isApiSuccess(body)) {
const userData = extractUserData(body);
const toastMsg = this._isAlreadyGuanshiMsg(getApiMsg(body))
? getApiMsg(body)
: '注册成功!';
this._applyRegisterSuccess(userData, toastMsg);
} else if (this._isAlreadyGuanshiMsg(this._getApiMsg(body))) {
this._enterRegisteredGuanshiState(this._getApiMsg(body) || '您已是管事', this._extractUserData(body));
} else if (this._isAlreadyGuanshiMsg(getApiMsg(body))) {
this._enterRegisteredGuanshiState(getApiMsg(body) || '您已是管事', extractUserData(body));
} else {
wx.hideLoading();
this.setData({ isLoading: false });
wx.showToast({ title: this._getApiMsg(body) || '注册失败', icon: 'none' });
wx.showToast({ title: getApiMsg(body) || '注册失败', icon: 'none' });
}
} catch (e) {
console.error('注册失败:', e);
@@ -494,50 +355,7 @@ async contactInviter() {
},
switchToGuanshiRole() {
const app = getApp();
this.ensureGuanshiRoleAndTabBar();
if (app.switchRoleAndReconnect) {
app.switchRoleAndReconnect('dashou').catch(() => {});
} else if (app.connectForCurrentRole) {
app.connectForCurrentRole();
} else if (app.ensureConnection) {
app.ensureConnection();
}
},
checkGuanshiStatus() {
try {
const status = wx.getStorageSync('guanshistatus');
if (this._isGuanshiStatusActive(status)) {
this.setData({ isGuanshi: true });
this.loadUserInfo();
this.checkGlobalData();
this.ensureGuanshiRoleAndTabBar();
} else {
this.setData({ isGuanshi: false });
}
} catch (e) {
this.setData({ isGuanshi: false });
}
},
loadUserInfo() {
const app = getApp();
const globalData = app.globalData || {};
const touxiang = wx.getStorageSync('touxiang');
const ossImageUrl = globalData.ossImageUrl || '';
const morentouxiang = 'avatar/default.jpg';
let avatarUrl = '';
if (touxiang) {
avatarUrl = ossImageUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang);
} else {
avatarUrl = ossImageUrl + (morentouxiang.startsWith('/') ? morentouxiang : '/' + morentouxiang);
}
this.setData({
avatarUrl,
uid: wx.getStorageSync('uid') || '',
nicheng: globalData.nicheng || wx.getStorageSync('nicheng') || ''
});
ensureRoleOnCenterPage(this, 'guanshi');
},
checkGlobalData() {
@@ -574,8 +392,8 @@ async contactInviter() {
url: '/yonghu/guanshixinxi',
method: 'POST'
});
if (res && this._isApiSuccess(res.data)) {
const data = this._extractUserData(res.data);
if (res && isApiSuccess(res.data)) {
const data = extractUserData(res.data);
const app = getApp();
this._syncGuanshiCache();
if (!app.globalData.guanshi) app.globalData.guanshi = {};
@@ -602,7 +420,7 @@ async contactInviter() {
} else {
this.setData({ isLoading: false });
if (showToast) {
wx.showToast({ title: this._getApiMsg(res?.data) || '获取失败', icon: 'none' });
wx.showToast({ title: getApiMsg(res?.data) || '获取失败', icon: 'none' });
}
}
} catch (e) {
@@ -614,8 +432,10 @@ async contactInviter() {
},
refreshGuanshiInfo() {
this.throttledRefresh(() => {
this.getGuanshiInfo();
this.fetchChenghaoList();
});
},
onInviteCodeInput(e) {
@@ -640,17 +460,17 @@ async contactInviter() {
data: { inviteCode }
});
const body = res?.data || {};
if (res && this._isApiSuccess(body)) {
const userData = this._extractUserData(body);
const toastMsg = this._isAlreadyGuanshiMsg(this._getApiMsg(body))
? this._getApiMsg(body)
if (res && isApiSuccess(body)) {
const userData = extractUserData(body);
const toastMsg = this._isAlreadyGuanshiMsg(getApiMsg(body))
? getApiMsg(body)
: '注册成功!';
this._applyRegisterSuccess(userData, toastMsg);
} else if (this._isAlreadyGuanshiMsg(this._getApiMsg(body))) {
this._enterRegisteredGuanshiState(this._getApiMsg(body) || '您已是管事', this._extractUserData(body));
} else if (this._isAlreadyGuanshiMsg(getApiMsg(body))) {
this._enterRegisteredGuanshiState(getApiMsg(body) || '您已是管事', extractUserData(body));
} else {
this.setData({ isLoading: false });
wx.showToast({ title: this._getApiMsg(body) || '注册失败', icon: 'none' });
wx.showToast({ title: getApiMsg(body) || '注册失败', icon: 'none' });
}
} catch (e) {
this.setData({ isLoading: false });
@@ -658,15 +478,6 @@ async contactInviter() {
}
},
copyUid() {
const { uid } = this.data;
if (!uid) return;
wx.setClipboardData({
data: uid,
success: () => wx.showToast({ title: '已复制', icon: 'success' })
});
},
// 页面跳转
goToWithdraw() { wx.navigateTo({ url: '/pages/withdraw/withdraw' }); },
goToInviteUser() { wx.navigateTo({ url: '/pages/invite-fighter/invite-fighter' }); },
@@ -674,4 +485,12 @@ async contactInviter() {
goToRechargeRecord() { wx.navigateTo({ url: '/pages/recharge-log/recharge-log' }); },
goToGuanshiRanking() { wx.navigateTo({ url: '/pages/manager-rank/manager-rank' }); },
goToPromoPoster() { wx.navigateTo({ url: '/pages/poster/poster' }); }
});
}, {
roleConfig: {
role: 'dashou',
statusKey: 'guanshistatus',
dataFlag: 'isGuanshi',
pageId: 'guanshiduan',
apiUrl: '/yonghu/guanshixinxi'
}
}));
+3 -15
View File
@@ -1,8 +1,8 @@
// pages/merchant-dispatch/merchant-dispatch.js
import request from '../../utils/request.js'
import { createPage, request } from '../../utils/base-page.js'
import PopupService from '../../services/popupService.js' // 引入弹窗服务
Page({
Page(createPage({
data: {
// 商家余额 (从全局变量获取)
sjyue: '0.00',
@@ -143,18 +143,6 @@ Page({
})
},
// 注册全局通知组件(用于顶部提示)
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()
}
}
},
// ==================== 教程按钮控制 ====================
// 启动定时器:5秒后让按钮缩进
startTutorialBtnTimer() {
@@ -415,4 +403,4 @@ Page({
const { id, item } = e.currentTarget.dataset
this.setSelectedType(item)
}
})
}))
+5 -25
View File
@@ -1,9 +1,10 @@
// pages/merchant-orders/merchant-orders.js
const app = getApp();
import request from '../../utils/request.js';
import { createPage, request } from '../../utils/base-page.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getOrderStatusText } from '../../utils/api-helper.js';
Page({
Page(createPage({
data: {
// 商品类型(和打手端用同一个接口)
shangpinleixing: [],
@@ -65,17 +66,6 @@ Page({
}
},
// 注册全局通知组件
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
// 🆕 加载商品类型(和打手端用同一个接口 /dingdan/dsqdhqddlx)
async loadShangpinLeixing() {
try {
@@ -193,7 +183,7 @@ Page({
// 处理数据:拼接图片、转换状态中文
const processed = newList.map(item => ({
...item,
zhuangtaiZh: this.getZhuangtaiZh(item.zhuangtai),
zhuangtaiZh: getOrderStatusText(item.zhuangtai),
zhuangtaiColor: statusItem.color,
// 如果有商品图片则用商品图片,否则用默认图
tupian: item.tupian
@@ -302,16 +292,6 @@ Page({
this.loadCurrentStatusOrders(false);
},
// 状态码转中文
getZhuangtaiZh(zhuangtai) {
const map = {
1: '已下单', 2: '进行中', 3: '已完成',
4: '退款中', 5: '已退款', 6: '退款失败',
7: '指定中', 8: '结算中'
};
return map[zhuangtai] || '未知状态';
},
// 跳转订单详情
goToSjDingdanXiangqing(e) {
const item = e.currentTarget.dataset.item;
@@ -324,4 +304,4 @@ Page({
// 图片加载失败
onImageError() {}
});
}))
+129 -163
View File
@@ -1,124 +1,114 @@
<!-- pages/shangjia-paihang/shangjia-paihang.wxml -->
<view class="shangjia-paihang-page">
<!-- pages/merchant-rank/merchant-rank.wxml -->
<view class="rank-page">
<!-- 赛博朋克背景元素 -->
<view class="cyber-bg">
<view class="rank-cyber-bg">
<!-- 网格线 -->
<view class="cyber-grid"></view>
<view class="rank-cyber-grid"></view>
<!-- 扫描线 -->
<view class="cyber-scanline"></view>
<view class="rank-cyber-scanline"></view>
<!-- 流动数据流 -->
<view class="data-streams">
<view class="stream stream-1"></view>
<view class="stream stream-2"></view>
<view class="stream stream-3"></view>
<view class="rank-data-streams">
<view class="rank-stream rank-stream--1"></view>
<view class="rank-stream rank-stream--2"></view>
<view class="rank-stream rank-stream--3"></view>
</view>
<!-- 浮动粒子 -->
<view class="floating-particles">
<view class="particle" wx:for="{{12}}" wx:key="index"></view>
</view>
<view class="rank-particle" wx:for="{{12}}" wx:key="index"></view>
</view>
<!-- 页面头部 -->
<view class="page-header">
<view class="rank-page-header">
<!-- 标题 -->
<view class="title-container">
<text class="main-title">商家排行榜</text>
<view class="title-line"></view>
<text class="sub-title">SHANGJIA RANKING</text>
<view class="rank-title-container">
<text class="rank-main-title">商家排行榜</text>
<view class="rank-title-line"></view>
<text class="rank-sub-title">SHANGJIA RANKING</text>
</view>
<!-- 切换按钮 -->
<view class="toggle-container">
<view class="toggle-bg" style="transform: translateX({{paihangLeixing == 1 ? '0' : '100%'}});"></view>
<view class="rank-toggle-container">
<view class="rank-toggle-bg" style="transform: translateX({{paihangLeixing == 1 ? '0' : '100%'}});"></view>
<view
class="toggle-btn {{paihangLeixing == 1 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
class="rank-toggle-btn {{paihangLeixing == 1 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
data-leixing="1"
bindtap="qiehuangPaihangLeixing"
>
<text class="toggle-text">今日流水</text>
<view class="toggle-line {{paihangLeixing == 1 ? 'active' : ''}}"></view>
<text class="rank-toggle-text">今日流水</text>
<view class="rank-toggle-line {{paihangLeixing == 1 ? 'active' : ''}}"></view>
</view>
<view
class="toggle-btn {{paihangLeixing == 2 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
class="rank-toggle-btn {{paihangLeixing == 2 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
data-leixing="2"
bindtap="qiehuangPaihangLeixing"
>
<text class="toggle-text">今月流水</text>
<view class="toggle-line {{paihangLeixing == 2 ? 'active' : ''}}"></view>
<text class="rank-toggle-text">今月流水</text>
<view class="rank-toggle-line {{paihangLeixing == 2 ? 'active' : ''}}"></view>
</view>
</view>
</view>
<!-- 加载状态 -->
<view wx:if="{{jiazaiZhuangtai}}" class="loading-state">
<view class="loading-content">
<view class="loading-spinner">
<view class="spinner-outer"></view>
<view class="spinner-inner"></view>
<view class="spinner-core"></view>
</view>
<text class="loading-text">数据加载中</text>
<text class="loading-sub">Connecting to server...</text>
<view wx:if="{{jiazaiZhuangtai}}" class="rank-loading-state">
<view class="rank-loading-spinner">
<view class="rank-spinner-outer"></view>
<view class="rank-spinner-inner"></view>
<view class="rank-spinner-core"></view>
</view>
<text class="rank-loading-text">数据加载中</text>
<text class="rank-loading-sub">Connecting to server...</text>
</view>
<!-- 排行榜内容 -->
<view wx:else class="ranking-content">
<view wx:else class="rank-content">
<!-- 虚拟数据提示 -->
<view wx:if="{{!xianshiZhenshiShuju}}" class="demo-notice">
<view class="notice-icon">📡</view>
<view class="notice-content">
<text class="notice-title">排行榜数据准备中</text>
<text class="notice-desc">当前显示为模拟数据,实时排名即将开放</text>
<view wx:if="{{!xianshiZhenshiShuju}}" class="rank-demo-notice">
<view class="rank-notice-icon">📡</view>
<view class="rank-notice-content">
<text class="rank-notice-title">排行榜数据准备中</text>
<text class="rank-notice-desc">当前显示为模拟数据,实时排名即将开放</text>
</view>
<view class="notice-glow"></view>
</view>
<!-- ========== 前三名区域 ========== -->
<view class="top-three-section" wx:if="{{qiansanMing.length > 0}}">
<view class="rank-top-three" wx:if="{{qiansanMing.length > 0}}">
<!-- 第二名 -->
<view class="rank-card rank-2 {{flashCardIndex == 1 && flashCardType == 'top3' ? 'flash' : ''}}"
<view class="rank-card rank-card--2 {{flashCardIndex == 1 && flashCardType == 'top3' ? 'flash' : ''}}"
animation="{{flashCardIndex == 1 && flashCardType == 'top3' ? flashAnimation : ''}}"
data-index="1" data-type="top3" bindtap="handleCardTap">
<view class="podium-base podium-silver">
<view class="podium-glow"></view>
<text class="podium-number">2</text>
<view class="rank-podium-base rank-podium-silver">
<text class="rank-podium-number">2</text>
</view>
<view class="rank-content">
<!-- 头像容器 -->
<view class="avatar-frame frame-silver">
<view class="avatar-container">
<image class="avatar-image" src="{{qiansanMing[1] ? qiansanMing[1].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-silver"></view>
<view class="avatar-sparkles">
<view class="sparkle s1"></view>
<view class="sparkle s2"></view>
<view class="sparkle s3"></view>
<view class="rank-avatar-frame rank-frame-silver">
<view class="rank-avatar-container">
<image class="rank-avatar-image" src="{{qiansanMing[1] ? qiansanMing[1].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo rank-halo-silver"></view>
<view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
</view>
</view>
<view class="rank-badge badge-silver">2</view>
<view class="rank-badge rank-badge-silver">2</view>
</view>
<!-- 用户信息 -->
<view class="user-info-card">
<view class="info-header">
<text class="user-name">{{qiansanMing[1] ? qiansanMing[1].nicheng : '等待上榜'}}</text>
<view class="rank-user-info-card">
<text class="rank-user-name">{{qiansanMing[1] ? qiansanMing[1].nicheng : '等待上榜'}}</text>
<view class="rank-info-body">
<view class="rank-uid-row">
<text class="rank-uid-label">ID:</text>
<text class="rank-uid-value">{{qiansanMing[1] ? qiansanMing[1].uid : '----'}}</text>
</view>
<view class="info-body">
<view class="uid-row">
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[1] ? qiansanMing[1].uid : '----'}}</text>
</view>
<view class="income-row">
<text class="income-label">流水:</text>
<view class="income-value">
<text class="currency">¥</text>
<text class="amount {{qiansanMing[1] ? qiansanMing[1].jineClass : ''}}">{{qiansanMing[1] ? qiansanMing[1].jine : '0.00'}}</text>
<view class="rank-income-row">
<text class="rank-income-label">流水:</text>
<view class="rank-income-value">
<text class="rank-currency">¥</text>
<text class="rank-amount {{qiansanMing[1] ? qiansanMing[1].jineClass : ''}}">{{qiansanMing[1] ? qiansanMing[1].jine : '0.00'}}</text>
</view>
</view>
</view>
@@ -127,62 +117,47 @@
</view>
<!-- 第一名 -->
<view class="rank-card rank-1 {{flashCardIndex == 0 && flashCardType == 'top3' ? 'flash' : ''}}"
<view class="rank-card rank-card--1 {{flashCardIndex == 0 && flashCardType == 'top3' ? 'flash' : ''}}"
animation="{{flashCardIndex == 0 && flashCardType == 'top3' ? flashAnimation : ''}}"
data-index="0" data-type="top3" bindtap="handleCardTap">
<!-- 皇冠 -->
<view class="crown-container" animation="{{crownAnimation}}">
<view class="crown">
<text class="crown-icon">👑</text>
<view class="crown-rays">
<view class="ray ray-1"></view>
<view class="ray ray-2"></view>
<view class="ray ray-3"></view>
<view class="ray ray-4"></view>
<view class="ray ray-5"></view>
<view class="ray ray-6"></view>
</view>
<view class="crown-glow"></view>
</view>
<view class="rank-crown-container" animation="{{crownAnimation}}">
<text class="rank-crown-icon">👑</text>
<view class="rank-crown-glow"></view>
</view>
<view class="podium-base podium-gold">
<view class="podium-glow"></view>
<text class="podium-number">1</text>
<view class="rank-podium-base rank-podium-gold">
<text class="rank-podium-number">1</text>
</view>
<view class="rank-content">
<!-- 头像容器 -->
<view class="avatar-frame frame-gold">
<view class="avatar-container">
<image class="avatar-image" src="{{qiansanMing[0] ? qiansanMing[0].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-gold"></view>
<view class="avatar-sparkles">
<view class="sparkle s1"></view>
<view class="sparkle s2"></view>
<view class="sparkle s3"></view>
<view class="sparkle s4"></view>
<view class="rank-avatar-frame rank-frame-gold">
<view class="rank-avatar-container">
<image class="rank-avatar-image" src="{{qiansanMing[0] ? qiansanMing[0].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo rank-halo-gold"></view>
<view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
<view class="rank-sparkle" style="bottom:15%;right:15%;animation-delay:1.5s"></view>
</view>
</view>
<view class="rank-badge badge-gold">1</view>
<view class="rank-badge rank-badge-gold">1</view>
</view>
<!-- 用户信息 -->
<view class="user-info-card">
<view class="info-header">
<text class="user-name">{{qiansanMing[0] ? qiansanMing[0].nicheng : '等待上榜'}}</text>
<view class="rank-user-info-card">
<text class="rank-user-name">{{qiansanMing[0] ? qiansanMing[0].nicheng : '等待上榜'}}</text>
<view class="rank-info-body">
<view class="rank-uid-row">
<text class="rank-uid-label">ID:</text>
<text class="rank-uid-value">{{qiansanMing[0] ? qiansanMing[0].uid : '----'}}</text>
</view>
<view class="info-body">
<view class="uid-row">
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[0] ? qiansanMing[0].uid : '----'}}</text>
</view>
<view class="income-row">
<text class="income-label">流水:</text>
<view class="income-value">
<text class="currency">¥</text>
<text class="amount {{qiansanMing[0] ? qiansanMing[0].jineClass : ''}}">{{qiansanMing[0] ? qiansanMing[0].jine : '0.00'}}</text>
<view class="rank-income-row">
<text class="rank-income-label">流水:</text>
<view class="rank-income-value">
<text class="rank-currency">¥</text>
<text class="rank-amount {{qiansanMing[0] ? qiansanMing[0].jineClass : ''}}">{{qiansanMing[0] ? qiansanMing[0].jine : '0.00'}}</text>
</view>
</view>
</view>
@@ -191,45 +166,40 @@
</view>
<!-- 第三名 -->
<view class="rank-card rank-3 {{flashCardIndex == 2 && flashCardType == 'top3' ? 'flash' : ''}}"
<view class="rank-card rank-card--3 {{flashCardIndex == 2 && flashCardType == 'top3' ? 'flash' : ''}}"
animation="{{flashCardIndex == 2 && flashCardType == 'top3' ? flashAnimation : ''}}"
data-index="2" data-type="top3" bindtap="handleCardTap">
<view class="podium-base podium-bronze">
<view class="podium-glow"></view>
<text class="podium-number">3</text>
<view class="rank-podium-base rank-podium-bronze">
<text class="rank-podium-number">3</text>
</view>
<view class="rank-content">
<!-- 头像容器 -->
<view class="avatar-frame frame-bronze">
<view class="avatar-container">
<image class="avatar-image" src="{{qiansanMing[2] ? qiansanMing[2].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-bronze"></view>
<view class="avatar-sparkles">
<view class="sparkle s1"></view>
<view class="sparkle s2"></view>
<view class="sparkle s3"></view>
<view class="rank-avatar-frame rank-frame-bronze">
<view class="rank-avatar-container">
<image class="rank-avatar-image" src="{{qiansanMing[2] ? qiansanMing[2].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo rank-halo-bronze"></view>
<view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
</view>
</view>
<view class="rank-badge badge-bronze">3</view>
<view class="rank-badge rank-badge-bronze">3</view>
</view>
<!-- 用户信息 -->
<view class="user-info-card">
<view class="info-header">
<text class="user-name">{{qiansanMing[2] ? qiansanMing[2].nicheng : '等待上榜'}}</text>
<view class="rank-user-info-card">
<text class="rank-user-name">{{qiansanMing[2] ? qiansanMing[2].nicheng : '等待上榜'}}</text>
<view class="rank-info-body">
<view class="rank-uid-row">
<text class="rank-uid-label">ID:</text>
<text class="rank-uid-value">{{qiansanMing[2] ? qiansanMing[2].uid : '----'}}</text>
</view>
<view class="info-body">
<view class="uid-row">
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[2] ? qiansanMing[2].uid : '----'}}</text>
</view>
<view class="income-row">
<text class="income-label">流水:</text>
<view class="income-value">
<text class="currency">¥</text>
<text class="amount {{qiansanMing[2] ? qiansanMing[2].jineClass : ''}}">{{qiansanMing[2] ? qiansanMing[2].jine : '0.00'}}</text>
<view class="rank-income-row">
<text class="rank-income-label">流水:</text>
<view class="rank-income-value">
<text class="rank-currency">¥</text>
<text class="rank-amount {{qiansanMing[2] ? qiansanMing[2].jineClass : ''}}">{{qiansanMing[2] ? qiansanMing[2].jine : '0.00'}}</text>
</view>
</view>
</view>
@@ -239,50 +209,46 @@
</view>
<!-- ========== 其他排名列表 ========== -->
<view class="other-ranks-section" wx:if="{{paihangShuju.length > 0}}">
<view class="section-title">
<view class="title-line"></view>
<text class="title-text">其他排名</text>
<view class="title-line"></view>
<view wx:if="{{paihangShuju.length > 0}}">
<view class="rank-section-title">
<view class="rank-section-line"></view>
<text class="rank-section-text">其他排名</text>
<view class="rank-section-line"></view>
</view>
<view class="rank-list">
<view class="rank-item {{flashCardIndex == index && flashCardType == 'normal' ? 'flash' : ''}}"
<view class="rank-list-item {{flashCardIndex == index && flashCardType == 'normal' ? 'flash' : ''}}"
wx:for="{{paihangShuju}}" wx:key="index"
animation="{{flashCardIndex == index && flashCardType == 'normal' ? flashAnimation : ''}}"
data-index="{{index}}" data-type="normal" bindtap="handleCardTap">
<view class="rank-number">
<text class="number-text">{{xianshiZhenshiShuju ? index + 4 : index + 1}}</text>
<view class="number-glow"></view>
<text class="rank-number-text">{{xianshiZhenshiShuju ? index + 4 : index + 1}}</text>
<view class="rank-number-glow"></view>
</view>
<view class="rank-avatar">
<image class="rank-avatar-img" src="{{item.full_touxiang}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo"></view>
<view class="rank-list-avatar">
<image class="rank-list-avatar-img" src="{{item.full_touxiang}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-list-avatar-halo"></view>
</view>
<view class="rank-info">
<text class="rank-name">{{item.nicheng}}</text>
<text class="rank-uid">UID: {{item.uid}}</text>
<view class="rank-list-info">
<text class="rank-list-name">{{item.nicheng}}</text>
<text class="rank-list-uid">UID: {{item.uid}}</text>
</view>
<view class="rank-income">
<text class="income-currency">¥</text>
<text class="income-amount {{item.jineClass}}">{{item.jine}}</text>
<view class="income-glow"></view>
</view>
<view class="rank-hover"></view>
<view class="rank-list-income">
<text class="rank-income-currency">¥</text>
<text class="rank-income-amount {{item.jineClass}}">{{item.jine}}</text>
<view class="rank-income-glow"></view>
</view>
</view>
</view>
<!-- 空状态 -->
<view wx:if="{{xianshiZhenshiShuju && paihangShuju.length === 0 && qiansanMing.length === 0}}" class="empty-state">
<view class="empty-icon">🏆</view>
<text class="empty-title">暂无排名数据</text>
<text class="empty-desc">成为第一个登榜的商家吧</text>
<view wx:if="{{xianshiZhenshiShuju && paihangShuju.length === 0 && qiansanMing.length === 0}}" class="rank-empty-state">
<view class="rank-empty-icon">🏆</view>
<text class="rank-empty-title">暂无排名数据</text>
<text class="rank-empty-desc">成为第一个登榜的商家吧</text>
</view>
</view>
<global-notification id="global-notification" />
File diff suppressed because it is too large. Load diff
+68 -194
View File
@@ -1,21 +1,11 @@
// pages/shangjiazhongxin/shangjiazhongxin.js
// pages/shangjiazhongxin/shangjiiazhongxin.js
import request from '../../utils/request.js';
import popupService from '../../services/popupService.js';
import {
isRoleStatusActive,
isCenterPageActive,
syncRoleStatuses,
syncProfileFields,
ensureRoleOnCenterPage,
clearCacheAndEnterNormal,
} from '../../utils/role-tab-bar.js';
import { lockPrimaryRole } from '../../utils/primary-role.js';
import { createPage, ensureRoleOnCenterPage, lockPrimaryRole, parseSceneOptions } from '../../utils/base-page.js';
import { ensureLogin } from '../../utils/login.js';
const app = getApp();
Page({
Page(createPage({
data: {
// 图片资源
imgUrls: {
@@ -42,7 +32,6 @@ Page({
// 商家状态
isShangjia: false,
isLoading: false,
isAutoRegistering: false,
// 用户基础信息
@@ -62,52 +51,57 @@ Page({
// 交互控制
inviteCode: '',
lastRefreshTime: 0,
canRefresh: true,
},
onLoad(options) {
this.initImageUrls();
this.checkShangjiaStatus();
const parsed = parseSceneOptions(options);
this.setupImageUrls();
this.checkRoleStatus();
const { inviteCode } = options || {};
const inviteCode = parsed.inviteCode;
if (inviteCode) {
this.setData({ inviteCode });
setTimeout(() => this.handleInviteCodeWithLoginCheck(inviteCode), 500);
}
if (!app.globalData.hasShownPopupOnColdStart) {
app.globalData.hasShownPopupOnColdStart = true;
setTimeout(() => popupService.checkAndShow(this, 'shangjiaduan'), 300);
setTimeout(() => this.handleInviteCodeWithLoginCheck({
inviteCode,
role: 'shangjia',
statusKey: 'shangjiastatus',
alreadyMsg: '您已是商家',
onNeedLogin: (code) => {
this.setData({ inviteCode: code });
ensureLogin({ silent: false, page: this })
.then(() => {
if (!this.data.isShangjia) {
wx.showLoading({ title: '商家注册中...', mask: true });
this.setData({ isAutoRegistering: true });
this.onRegister();
}
})
.catch(() => {});
},
onReady() {
if (isCenterPageActive(this, 'isShangjia', 'shangjiastatus')) {
onAlreadyRegistered: () => {
wx.showToast({ title: '您已是商家', icon: 'success' });
ensureRoleOnCenterPage(this, 'shangjia');
},
}), 500);
}
this.checkColdStartPopup('shangjiaduan');
},
onShow() {
this.registerNotificationComponent();
if (isCenterPageActive(this, 'isShangjia', 'shangjiastatus')) {
if (!this.data.isShangjia) this.setData({ isShangjia: true });
if (this.data.isShangjia) {
ensureRoleOnCenterPage(this, 'shangjia');
this.refreshShangjiaInfo();
}
},
onHide() {
const popupComp = this.selectComponent('#popupNotice');
popupComp?.cleanup?.();
},
// 图片路径初始化
initImageUrls() {
setupImageUrls() {
const ossBase = app.globalData.ossImageUrl || '';
const imgDir = `${ossBase}beijing/shangjiaduan/`;
this.setData({
imgUrls: {
imgUrls: this.initImageUrls({
pageBg: `${imgDir}page_bg.png`,
avatarCardBg: `${imgDir}avatar_card_bg.png`,
assetCardBg: `${imgDir}asset_card_bg.png`,
@@ -128,16 +122,16 @@ Page({
avatarFrame: `${imgDir}avatar_frame.png`,
iconClear: `${ossBase}beijing/tubiao/grzx_qingchu.jpg`,
iconSwitch: '/images/_exit.png',
}
})
});
},
// 商家状态检查
checkShangjiaStatus() {
// 商家状态检查(覆盖基类 checkRoleStatus,增加 getShangjiaInfo 调用)
checkRoleStatus() {
try {
const shangjiastatus = wx.getStorageSync('shangjiastatus');
const uid = wx.getStorageSync('uid');
if (isRoleStatusActive(shangjiastatus)) {
if (shangjiastatus) {
this.setData({ isShangjia: true, uid: uid || '' });
this.loadAvatar();
this.getShangjiaInfo();
@@ -151,32 +145,20 @@ Page({
}
},
loadAvatar() {
const { ossImageUrl = '', morentouxiang = 'avatar/default.jpg' } = app.globalData || {};
const touxiang = wx.getStorageSync('touxiang') || '';
let avatarUrl = '';
if (touxiang) {
avatarUrl = ossImageUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang);
} else {
avatarUrl = ossImageUrl + (morentouxiang.startsWith('/') ? morentouxiang : '/' + morentouxiang);
}
this.setData({ avatarUrl });
},
// 获取商家信息
async getShangjiaInfo() {
this.setData({ isLoading: true });
try {
const res = await request({ url: '/yonghu/shangjiaxinxi', method: 'POST' });
if (res?.data?.code === 200) {
const data = res.data.data;
await this.loadData({
url: '/yonghu/shangjiaxinxi',
method: 'POST',
successCode: 200,
errorMsg: '获取商家信息失败',
onSuccess: (data) => {
// 更新全局缓存
app.globalData.shangjia = data;
wx.setStorageSync('shangjiastatus', 1);
const chenghaoList = data.chenghao_list || [];
// 字段映射
this.setData({
nicheng: data.nicheng || '',
sjyue: data.sjyue || '0.00',
@@ -194,128 +176,41 @@ Page({
this.setData({ isAutoRegistering: false });
wx.showToast({ title: '商家注册成功', icon: 'success' });
}
} else {
throw new Error(res?.data?.msg || '获取商家信息失败');
}
} catch (error) {
console.error('获取商家信息失败', error);
this.setData({ isLoading: false });
wx.showToast({ title: error.message || '加载失败', icon: 'none' });
}
},
});
},
refreshShangjiaInfo() {
const now = Date.now();
if (!this.data.canRefresh || now - this.data.lastRefreshTime < 3000) {
wx.showToast({ title: '操作过快', icon: 'none' });
return;
}
this.setData({ lastRefreshTime: now, canRefresh: false });
this.getShangjiaInfo();
setTimeout(() => this.setData({ canRefresh: true }), 3000);
},
// 邀请码注册
handleInviteCodeWithLoginCheck(inviteCode) {
const token = wx.getStorageSync('token');
const uid = wx.getStorageSync('uid');
if (!token || !uid) {
this.setData({ inviteCode });
ensureLogin({ silent: false, page: this })
.then(() => {
if (!this.data.isShangjia) {
wx.showLoading({ title: '商家注册中...', mask: true });
this.setData({ isAutoRegistering: true });
this.onRegister();
}
})
.catch(() => {});
} else {
if (!this.data.isShangjia) {
wx.showLoading({ title: '商家注册中...', mask: true });
this.setData({ isAutoRegistering: true });
this.onRegister();
} else {
wx.showToast({ title: '您已是商家', icon: 'success' });
ensureRoleOnCenterPage(this, 'shangjia');
}
}
this.throttledRefresh(() => this.getShangjiaInfo());
},
async onRegister() {
const { inviteCode } = this.data;
if (!inviteCode) {
wx.showToast({ title: '请输入邀请码', icon: 'none' });
return;
}
if (inviteCode.length > 100) {
wx.showToast({ title: '邀请码不能超过100字符', icon: 'none' });
return;
}
this.setData({ isLoading: true });
try {
const res = await request({
url: '/yonghu/shangjiahuce',
method: 'POST',
data: { inviteCode }
});
if (res?.data?.code === 200) {
const data = res.data.data || {};
syncRoleStatuses(data);
syncProfileFields(data);
if (!Object.prototype.hasOwnProperty.call(data, 'shangjiastatus')) {
wx.setStorageSync('shangjiastatus', 1);
app.globalData.shangjiastatus = 1;
}
app.globalData.shangjia = data;
const chenghaoList = data.chenghao_list || [];
await this.registerWithInviteCode({
inviteCode,
apiUrl: '/yonghu/shangjiahuce',
role: 'shangjia',
statusKey: 'shangjiastatus',
successMsg: '商家注册成功',
onSuccess: (userData) => {
app.globalData.shangjia = userData;
const chenghaoList = userData.chenghao_list || [];
this.setData({
isShangjia: true,
nicheng: data.nicheng || '',
sjyue: data.sjyue || '0.00',
fabu: data.fabu || 0,
tuikuan: data.tuikuan || 0,
jinriliushui: data.jinriliushui || '0.00',
jinyueliushui: data.jinyueliushui || '0.00',
jinridingdan: data.jinridingdan || 0,
nicheng: userData.nicheng || '',
sjyue: userData.sjyue || '0.00',
fabu: userData.fabu || 0,
tuikuan: userData.tuikuan || 0,
jinriliushui: userData.jinriliushui || '0.00',
jinyueliushui: userData.jinyueliushui || '0.00',
jinridingdan: userData.jinridingdan || 0,
chenghaoList: chenghaoList,
isLoading: false,
isAutoRegistering: false,
});
this.loadAvatar();
ensureRoleOnCenterPage(this, 'shangjia');
lockPrimaryRole('shangjia');
wx.showToast({ title: '商家注册成功', icon: 'success' });
} else {
throw new Error(res?.data?.msg || '注册失败');
}
} catch (error) {
console.error('商家注册失败', error);
this.setData({ isLoading: false, isAutoRegistering: false });
wx.showToast({ title: error.message, icon: 'error' });
}
},
// 辅助功能
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp?.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
copyUid() {
if (!this.data.uid) {
wx.showToast({ title: 'UID不存在', icon: 'none' });
return;
}
wx.setClipboardData({
data: this.data.uid,
success: () => wx.showToast({ title: 'UID已复制', icon: 'success' }),
fail: () => wx.showToast({ title: '复制失败', icon: 'error' })
});
},
@@ -336,33 +231,12 @@ Page({
goToWithdraw() { wx.navigateTo({ url: '/pages/withdraw/withdraw' }); },
goToRanking() { wx.navigateTo({ url: '/pages/fighter-rank/fighter-rank?type=shangjia' }); },
goToMessages() { wx.navigateTo({ url: '/pages/merchant-msg/merchant-msg' }); },
goToKefu() {
const { kefuConfig } = app.globalData;
if (kefuConfig?.link && kefuConfig?.enterpriseId) {
wx.openCustomerServiceChat({
extInfo: { url: kefuConfig.link },
corpId: kefuConfig.enterpriseId,
fail: () => wx.showToast({ title: '客服暂不可用', icon: 'none' })
});
} else {
wx.showToast({ title: '客服配置未加载', icon: 'none' });
}
}, {
roleConfig: {
role: 'shangjia',
statusKey: 'shangjiastatus',
dataFlag: 'isShangjia',
pageId: 'shangjiaduan',
apiUrl: '/yonghu/shangjiaxinxi',
},
clearCache() {
wx.showModal({
title: '清除缓存',
content: '将清除所有登录与身份数据,并回到点单端,确定继续?',
confirmText: '确定',
cancelText: '取消',
success: (r) => {
if (r.confirm) clearCacheAndEnterNormal(this);
},
});
},
switchToNormal() {
lockPrimaryRole('normal');
wx.reLaunch({ url: '/pages/mine/mine' });
},
});
}));
+3 -12
View File
@@ -6,9 +6,10 @@ import { getLocalImUserId } from '../../utils/im-user.js';
import { normalizeConversationsList } from '../../utils/conversation-display.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getPrimaryRole } from '../../utils/primary-role.js';
import { createPage } from '../../utils/base-page.js';
const { jianquanxian } = require('../../utils/imAuth/jianquanxian');
Page({
Page(createPage({
data: {
allConversations: [],
filteredConversations: [],
@@ -88,16 +89,6 @@ Page({
return true;
},
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification(),
};
}
},
autoConnect() {
const targetUserId = getLocalImUserId(app);
if (!targetUserId) return;
@@ -400,4 +391,4 @@ Page({
const key = `filteredConversations[${index}].data.avatar`;
this.setData({ [key]: def });
}
});
}))
+247 -31
View File
@@ -1,38 +1,254 @@
.msg-page { background: #f5f5f5; }
/* pages/messages/messages.wxss */
.header { display: flex; align-items: center; padding: 20rpx 24rpx; background: #fff; border-bottom: 1rpx solid #e8e8e8; }
.search-bar { flex: 1; }
.search-input { height: 68rpx; }
.search-icon { font-size: 32rpx; margin-left: 10rpx; color: #999; }
.notify-switch { margin-left: 20rpx; }
.notify-text { font-size: 26rpx; color: #666; padding: 10rpx 16rpx; background: #f0f0f0; border-radius: 30rpx; }
.msg-page {
background: #f5f5f5;
min-height: 100vh;
}
.tab-row { border-bottom: 1rpx solid #e8e8e8; }
/* .tab-item 已由全局 .tab-item 覆盖 */
.tab-item.active { color: #07c160; font-weight: 600; }
.tab-item.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 60rpx; height: 6rpx; background: #07c160; border-radius: 3rpx; }
.tab-badge { border-radius: 20rpx; padding: 2rpx 12rpx; margin-left: 8rpx; }
/* 顶部搜索 + 通知 */
.header {
display: flex;
align-items: center;
padding: 20rpx 24rpx;
background: #fff;
}
.conversation-list { background: #fff; }
.conversation-item { display: flex; align-items: center; padding: 20rpx 24rpx; border-bottom: 1rpx solid #f0f0f0; }
.has-unread { background: #fafafa; }
.avatar { width: 96rpx; height: 96rpx; border-radius: 12rpx; margin-right: 20rpx; background: #eee; }
.info { flex: 1; }
.top-line { display: flex; justify-content: space-between; margin-bottom: 8rpx; }
.name { font-size: 30rpx; color: #1a1a1a; font-weight: 500; }
.time { font-size: 22rpx; color: #999; }
.bottom-line { display: flex; justify-content: space-between; align-items: center; }
.last-msg { font-size: 26rpx; color: #888; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.unread-dot { background: #fa5151; color: #fff; font-size: 20rpx; border-radius: 50%; min-width: 36rpx; height: 36rpx; line-height: 36rpx; text-align: center; margin-left: 12rpx; }
.load-tip { text-align: center; padding: 24rpx; color: #999; font-size: 24rpx; }
.empty { text-align: center; padding: 200rpx 0; color: #bbb; font-size: 28rpx; }
.search-bar {
flex: 1;
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 36rpx;
padding: 0 24rpx;
height: 68rpx;
}
.search-input {
flex: 1;
height: 68rpx;
font-size: 28rpx;
color: #333;
}
.search-icon {
font-size: 28rpx;
margin-left: 10rpx;
color: #999;
}
.notify-switch {
margin-left: 20rpx;
flex-shrink: 0;
}
.notify-text {
font-size: 24rpx;
color: #666;
padding: 10rpx 20rpx;
background: #f0f0f0;
border-radius: 30rpx;
}
/* TAB 栏 */
.tab-row {
display: flex;
background: #fff;
border-bottom: 1rpx solid #e8e8e8;
}
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 88rpx;
position: relative;
font-size: 28rpx;
color: #666;
}
.tab-item.active {
color: #07c160;
font-weight: 600;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 6rpx;
background: #07c160;
border-radius: 3rpx;
}
.tab-label {
line-height: 1;
}
.tab-badge {
background: #fa5151;
color: #fff;
font-size: 20rpx;
border-radius: 20rpx;
padding: 2rpx 10rpx;
margin-left: 8rpx;
line-height: 1.2;
}
/* 会话列表 */
.conversation-list {
background: #fff;
}
.conversation-item {
display: flex;
align-items: center;
padding: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background 0.15s;
}
.conversation-item:active {
background: #f5f5f5;
}
.has-unread {
background: #fafafa;
}
.avatar {
width: 96rpx;
height: 96rpx;
border-radius: 12rpx;
margin-right: 20rpx;
background: #eee;
flex-shrink: 0;
}
.info {
flex: 1;
min-width: 0;
}
.top-line {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
}
.name {
font-size: 30rpx;
color: #1a1a1a;
font-weight: 500;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.time {
font-size: 22rpx;
color: #999;
flex-shrink: 0;
margin-left: 16rpx;
}
.bottom-line {
display: flex;
justify-content: space-between;
align-items: center;
}
.last-msg {
font-size: 26rpx;
color: #888;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.unread-dot {
background: #fa5151;
color: #fff;
font-size: 20rpx;
border-radius: 50%;
min-width: 36rpx;
height: 36rpx;
line-height: 36rpx;
text-align: center;
margin-left: 12rpx;
flex-shrink: 0;
}
.load-tip {
text-align: center;
padding: 24rpx;
color: #999;
font-size: 24rpx;
}
.empty {
text-align: center;
padding: 200rpx 0;
color: #bbb;
font-size: 28rpx;
}
/* 客服入口 */
.kefu-entry { text-align: center; padding: 100rpx 0; }
.kefu-btn { width: 300rpx; background: #07c160; color: #fff; border-radius: 40rpx; font-size: 28rpx; }
.kefu-entry {
text-align: center;
padding: 100rpx 0;
}
.kefu-btn {
width: 300rpx;
background: #07c160;
color: #fff;
border-radius: 40rpx;
font-size: 28rpx;
}
/* 操作弹窗 */
.action-mask { background: rgba(0,0,0,0.4); z-index: 10000; }
.action-sheet { position: absolute; bottom: 0; left: 0; right: 0; background: #fff; border-radius: 24rpx 24rpx 0 0; padding: 20rpx 20rpx 150rpx 20rpx; }
.action-btn { text-align: center; padding: 28rpx 0; font-size: 32rpx; border-bottom: 1rpx solid #eee; }
.cancel { color: #999; margin-top: 12rpx; border-bottom: none; }
.action-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 10000;
display: flex;
align-items: flex-end;
animation: fadeIn 0.2s ease-out;
}
.action-sheet {
width: 100%;
background: #fff;
border-radius: 24rpx 24rpx 0 0;
padding: 20rpx 20rpx 60rpx;
animation: slideInUp 0.25s ease-out;
}
.action-btn {
text-align: center;
padding: 28rpx 0;
font-size: 32rpx;
color: #333;
border-bottom: 1rpx solid #eee;
}
.action-btn:active {
background: #f5f5f5;
}
.cancel {
color: #999;
margin-top: 12rpx;
border-bottom: none;
}
+4 -33
View File
@@ -4,13 +4,13 @@ import { ensureLogin } from '../../utils/login';
import {
backfillUserProfileCache,
getUserProfileForDisplay,
clearCacheAndEnterNormal,
isRoleStatusActive,
ensureRoleOnCenterPage,
} from '../../utils/role-tab-bar';
import { enterLockedRole } from '../../utils/primary-role.js';
import { createPage } from '../../utils/base-page.js';
Page({
Page(createPage({
data: {
avatarUrl: '',
userNicheng: '',
@@ -34,7 +34,7 @@ Page({
onShow() {
this.initIconUrls();
this.registerNotification();
this.registerNotificationComponent();
this.checkAvatarPrompt();
backfillUserProfileCache(app);
this.loadUserData();
@@ -50,16 +50,6 @@ Page({
})
},
registerNotification() {
const comp = this.selectComponent('#global-notification')
if (comp?.showNotification) {
app.globalData.globalNotification = {
show: d => comp.showNotification(d),
hide: () => comp.hideNotification()
}
}
},
checkAvatarPrompt() {
const ds = wx.getStorageSync('dashoustatus')
const tx = wx.getStorageSync('touxiang') || ''
@@ -145,25 +135,6 @@ Page({
.catch(() => {});
},
goToKefu() {
const { link, enterpriseId } = app.globalData.kefuConfig || {}
if (!link || !enterpriseId) { wx.showToast({ title: '客服配置未加载', icon: 'none' }); return }
wx.openCustomerServiceChat({ extInfo: { url: link }, corpId: enterpriseId })
},
goToCustomService() { this.goToKefu() },
goToGuanzhuA() { wx.navigateTo({ url: '/pages/manager-assign/manager-assign' }) },
clearCache() {
wx.showModal({
title: '清除缓存',
content: '将清除所有登录与身份数据,并回到点单端,确定继续?',
confirmText: '确定',
cancelText: '取消',
success: r => {
if (r.confirm) {
clearCacheAndEnterNormal(this);
}
},
});
},
})
}))
+149 -126
View File
@@ -1,190 +1,213 @@
/* pages/orders/orders.wxss */
.dingdan-page {
background-color: #f8f9fa;
padding-bottom: 20rpx;
}
background: #f2f2f2;
min-height: 100vh;
}
.dingdan-tabs-scroll {
/* TAB栏 */
.dingdan-tabs-scroll {
width: 100%;
height: 90rpx;
background: #fff;
white-space: nowrap;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.03);
}
border-bottom: 1rpx solid #f0f0f0;
}
.dingdan-tabs-container {
.dingdan-tabs-container {
display: flex;
align-items: center;
height: 100%;
padding: 0 20rpx;
}
height: 88rpx;
padding: 0 8rpx;
}
.dingdan-tab-item {
.dingdan-tab-item {
position: relative;
}
padding: 0 28rpx;
height: 88rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.dingdan-tab-item:last-child {
margin-right: 0;
}
/* .dingdan-tab-item.tab-active 已由全局 .tab-capsule--active 覆盖 */
.tab-active-line {
width: 40rpx;
height: 4rpx;
background-color: #4caf50;
border-radius: 2rpx;
margin-top: 6rpx;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.8); }
to { opacity: 1; transform: scale(1); }
}
.tab-text {
.tab-text {
font-size: 28rpx;
color: #666;
font-weight: 400;
}
line-height: 1;
}
.dingdan-list-container {
padding: 20rpx;
}
.dingdan-tab-item.tab-active .tab-text {
color: #e02e24;
font-weight: 700;
}
.loading-container {
.tab-active-line {
width: 40rpx;
height: 6rpx;
background: #e02e24;
border-radius: 3rpx;
margin-top: 10rpx;
}
/* 列表区 */
.dingdan-list-container {
padding: 20rpx 24rpx;
}
/* 加载 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 400rpx;
}
}
.loading-spinner {
border: 6rpx solid #e0e0e0;
border-top-color: #4caf50;
.loading-spinner {
width: 56rpx;
height: 56rpx;
border: 4rpx solid #f0f0f0;
border-top-color: #e02e24;
border-radius: 50%;
animation: spin 1s linear infinite;
animation: spin 0.7s linear infinite;
margin-bottom: 20rpx;
}
}
.loading-text {
font-size: 28rpx;
color: #999999;
}
.loading-text {
font-size: 26rpx;
color: #999;
}
.empty-container {
/* 空状态 */
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 500rpx;
padding-top: 80rpx;
}
}
.empty-image {
opacity: 0.6;
}
.empty-image {
width: 200rpx;
height: 200rpx;
opacity: 0.5;
margin-bottom: 24rpx;
}
.empty-text {
font-size: 32rpx;
color: #999999;
}
.empty-text {
font-size: 30rpx;
color: #999;
margin-bottom: 8rpx;
}
.dingdan-card {
.empty-subtext {
font-size: 24rpx;
color: #bbb;
}
/* 订单卡片 */
.dingdan-card {
display: flex;
align-items: center;
border-radius: 16rpx;
background: #fff;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 20rpx;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
margin-bottom: 16rpx;
}
.dingdan-card:active {
transform: translateY(-2rpx);
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.08);
}
.dingdan-card-left {
.dingdan-card-left {
flex-shrink: 0;
margin-right: 24rpx;
}
margin-right: 20rpx;
}
.dingdan-image {
.dingdan-image {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
background-color: #f5f5f5;
}
border-radius: 8rpx;
background: #f5f5f5;
}
.dingdan-card-center {
.dingdan-card-center {
flex: 1;
height: 160rpx;
display: flex;
align-items: flex-start;
}
}
.dingdan-jieshao {
.dingdan-jieshao {
font-size: 28rpx;
line-height: 40rpx;
color: #333333;
font-weight: 400;
color: #333;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
}
}
.dingdan-card-right {
.dingdan-card-right {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
height: 160rpx;
margin-left: 20rpx;
}
margin-left: 16rpx;
flex-shrink: 0;
}
/* 价格样式 */
.dingdan-jine {
display: flex;
align-items: center; /* 垂直居中 */
}
.jine-symbol {
font-size: 28rpx;
color: #333333;
font-weight: 500;
margin-right: 4rpx; /* 添加右边距,让符号和数字有间隔 */
}
.jine-number {
font-size: 32rpx; /* 整数和小数一样大 */
color: #333333;
.dingdan-zhuangtai {
font-size: 24rpx;
color: #e02e24;
font-weight: 600;
}
}
.load-more-line {
.dingdan-jine {
display: flex;
align-items: baseline;
}
.jine-symbol {
font-size: 24rpx;
color: #333;
font-weight: 500;
}
.jine-number {
font-size: 32rpx;
color: #333;
font-weight: 700;
}
/* 加载更多 */
.load-more-container {
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx 0;
}
.load-more-line {
width: 80rpx;
height: 1rpx;
background-color: #e0e0e0;
margin: 0 20rpx;
}
background: #e0e0e0;
}
.load-more-text {
.load-more-text {
font-size: 24rpx;
color: #999;
white-space: nowrap;
}
margin: 0 20rpx;
}
@media (max-width: 375px) {
.dingdan-image {
width: 140rpx;
height: 140rpx;
}
.no-more-container {
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx 0;
}
.dingdan-card-center {
height: 140rpx;
}
.dingdan-card-right {
height: 140rpx;
}
.jine-number {
font-size: 30rpx;
}
}
.no-more-text {
font-size: 24rpx;
color: #ccc;
}
@@ -62,9 +62,9 @@
</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 wx:if="{{ detailItem.zhuangtai === 1 && !detailItem.bohuiliyou }}" class="pbtn primary" bindtap="openShensu">申诉</view>
<view wx:if="{{ detailItem.zhuangtai === 1 || detailItem.zhuangtai === 3 }}" class="pbtn primary" bindtap="startPay">立即缴纳</view>
<view class="pbtn default" bindtap="closeDetail">关闭</view>
</view>
</view>
</view>
@@ -100,8 +100,8 @@
</view>
</scroll-view>
<view class="modal-foot">
<view class="btn default" bindtap="closeShensu">取消</view>
<view class="btn primary" bindtap="submitShensu">提交申诉</view>
<view class="pbtn default" bindtap="closeShensu">取消</view>
<view class="pbtn primary" bindtap="submitShensu">提交申诉</view>
</view>
</view>
</view>
@@ -50,10 +50,10 @@ page { background: #f5f5f5; }
.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; }
.pbtn { 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; }
.pbtn:active { opacity: 0.8; }
/* ===== 获取更多按钮 ===== */
@@ -48,8 +48,8 @@
<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 class="pbtn default" bindtap="backToMethod">取消</view>
<view class="pbtn primary" bindtap="confirmBalance">确认</view>
</view>
</view>
@@ -53,7 +53,7 @@
@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; }
.pbtn { 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; }
.pbtn:active { opacity: 0.8; }
@@ -60,8 +60,8 @@
</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 wx:if="{{ xuanzhongChufa.sqzhuangtai === 0 }}" class="pbtn primary" bindtap="openShensuModal">申诉</view>
<view class="pbtn default" bindtap="guanbiXiangqing">关闭</view>
</view>
</view>
</view>
@@ -97,8 +97,8 @@
</view>
</scroll-view>
<view class="modal-foot">
<view class="btn default" bindtap="closeShensuModal">取消</view>
<view class="btn primary" bindtap="submitShensu">提交申诉</view>
<view class="pbtn default" bindtap="closeShensuModal">取消</view>
<view class="pbtn primary" bindtap="submitShensu">提交申诉</view>
</view>
</view>
</view>
@@ -59,10 +59,10 @@ page { background: #f5f5f5; }
/* 🔥 底部按钮区左右 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; }
.pbtn { 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; }
.pbtn:active { opacity: 0.8; }
/* ===== 获取更多按钮 ===== */
+20 -20
View File
@@ -25,38 +25,38 @@
<!-- 按钮区域(第一行:保存/刷新/生成海报) -->
<view class="button-section">
<view class="btn-group">
<view class="btn save-btn" bindtap="saveToAlbum" wx:if="{{qrcodeUrl}}">
<text class="btn-icon">📥</text>
<text class="btn-text">保存二维码</text>
<view class="pbtn-group">
<view class="pbtn pbtn-save" bindtap="saveToAlbum" wx:if="{{qrcodeUrl}}">
<text class="pbtn-icon">📥</text>
<text class="pbtn-text">保存二维码</text>
</view>
<view class="btn refresh-btn" bindtap="refreshQRCode" wx:if="{{!isLoading}}">
<text class="btn-icon">⟳</text>
<text class="btn-text">{{qrcodeUrl ? '重新获取' : '获取二维码'}}</text>
<view class="pbtn pbtn-refresh" bindtap="refreshQRCode" wx:if="{{!isLoading}}">
<text class="pbtn-icon">⟳</text>
<text class="pbtn-text">{{qrcodeUrl ? '重新获取' : '获取二维码'}}</text>
</view>
<view class="btn loading-btn" wx:else>
<view class="pbtn pbtn-loading" wx:else>
<view class="loading-spinner-small"></view>
<text class="btn-text">生成中...</text>
<text class="pbtn-text">生成中...</text>
</view>
<!-- 🔥【新增】生成我的专属海报按钮 -->
<view class="btn poster-btn" bindtap="generateMyPoster" wx:if="{{qrcodeUrl && !showPosterCanvas}}">
<text class="btn-icon">🎨</text>
<text class="btn-text">生成我的专属海报</text>
<!-- 生成我的专属海报按钮 -->
<view class="pbtn pbtn-poster" bindtap="generateMyPoster" wx:if="{{qrcodeUrl && !showPosterCanvas}}">
<text class="pbtn-icon">🎨</text>
<text class="pbtn-text">生成我的专属海报</text>
</view>
</view>
</view>
<!-- 🔥【新增】专属海报展示区(当 showPosterCanvas 为 true 时显示) -->
<!-- 专属海报展示区(当 showPosterCanvas 为 true 时显示) -->
<view class="poster-section" wx:if="{{showPosterCanvas}}">
<canvas canvas-id="posterCanvas" class="poster-canvas"></canvas>
<view class="poster-actions">
<view class="btn save-poster-btn" bindtap="savePoster">
<text class="btn-icon">📥</text>
<text class="btn-text">保存海报</text>
<view class="pbtn pbtn-save-poster" bindtap="savePoster">
<text class="pbtn-icon">📥</text>
<text class="pbtn-text">保存海报</text>
</view>
<view class="btn back-btn" bindtap="hidePoster">
<text class="btn-icon">↩️</text>
<text class="btn-text">返回</text>
<view class="pbtn pbtn-back" bindtap="hidePoster">
<text class="pbtn-icon">↩️</text>
<text class="pbtn-text">返回</text>
</view>
</view>
</view>
+93 -125
View File
@@ -1,57 +1,51 @@
/* 机甲风格推广海报页面(含新增专属海报样式) */
/* 推广海报页面 */
page {
background: #0a0e17;
min-height: 100vh;
font-family: 'Avenir', 'PingFang SC', 'Helvetica Neue', sans-serif;
color: #fff;
}
}
/* 背景网格 */
.grid-bg {
/* 背景网格 */
.grid-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
top: 0; left: 0;
width: 100%; height: 100%;
background-image:
linear-gradient(rgba(0, 160, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 160, 255, 0.05) 1px, transparent 1px);
background-size: 50rpx 50rpx;
pointer-events: none;
z-index: 0;
}
}
.page-container {
.page-container {
position: relative;
z-index: 2;
min-height: 100vh;
padding: 30rpx 25rpx;
box-sizing: border-box;
}
}
/* 机械装饰线条 */
.mech-line {
/* 机械装饰线条 */
.mech-line {
position: absolute;
pointer-events: none;
z-index: 1;
}
.line-h {
top: 200rpx;
left: 0;
width: 100%;
height: 2rpx;
}
.line-h {
top: 200rpx; left: 0;
width: 100%; height: 2rpx;
background: linear-gradient(90deg, transparent, #00a6ff, transparent);
}
.line-v {
right: 50rpx;
top: 100rpx;
width: 2rpx;
height: 300rpx;
}
.line-v {
right: 50rpx; top: 100rpx;
width: 2rpx; height: 300rpx;
background: linear-gradient(180deg, transparent, #00a6ff, transparent);
}
}
.content {
.content {
position: relative;
z-index: 3;
display: flex;
@@ -60,14 +54,14 @@ page {
justify-content: center;
min-height: 100vh;
padding: 40rpx 0;
}
}
/* 标题区域 */
.title-section {
/* 标题区域 */
.title-section {
text-align: center;
margin-bottom: 60rpx;
}
.title {
}
.title {
font-size: 44rpx;
font-weight: bold;
color: #fff;
@@ -75,87 +69,80 @@ page {
letter-spacing: 2rpx;
display: block;
margin-bottom: 20rpx;
}
.subtitle {
}
.subtitle {
font-size: 28rpx;
color: #9cf;
opacity: 0.9;
letter-spacing: 1rpx;
}
}
/* 二维码区域(方形,无斜切) */
.qrcode-section {
/* 二维码区域 */
.qrcode-section {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 60rpx;
}
.qrcode-wrapper {
}
.qrcode-wrapper {
position: relative;
width: 400rpx;
height: 400rpx;
width: 400rpx; height: 400rpx;
background: rgba(0, 20, 40, 0.8);
border: 4rpx solid #00a6ff;
/* 移除 clip-path,保持方形 */
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 80rpx rgba(0, 166, 255, 0.4);
animation: qrcodePulse 2s infinite alternate;
}
.qrcode-img {
width: 90%;
height: 90%;
}
.qrcode-img {
width: 90%; height: 90%;
object-fit: contain;
}
.qrcode-glow {
}
.qrcode-glow {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
top: 0; left: 0;
width: 100%; height: 100%;
background: radial-gradient(circle at 30% 30%, rgba(0, 166, 255, 0.2), transparent 70%);
pointer-events: none;
}
.empty-qrcode {
width: 400rpx;
height: 400rpx;
}
.empty-qrcode {
width: 400rpx; height: 400rpx;
background: rgba(0, 20, 40, 0.8);
border: 4rpx dashed #00a6ff;
/* 方形 */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-icon {
}
.empty-icon {
font-size: 80rpx;
color: #00a6ff;
margin-bottom: 20rpx;
opacity: 0.5;
}
.empty-text {
}
.empty-text {
font-size: 26rpx;
color: #9cf;
text-align: center;
padding: 0 40rpx;
}
}
/* 按钮区域(机甲斜切风格) */
.button-section {
/* 按钮区域 */
.button-section {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 40rpx;
}
.btn-group {
}
.pbtn-group {
display: flex;
gap: 20rpx;
flex-wrap: wrap;
justify-content: center;
}
.btn {
}
.pbtn {
width: 220rpx;
height: 80rpx;
background: rgba(0, 30, 60, 0.8);
@@ -168,102 +155,83 @@ page {
justify-content: center;
position: relative;
transition: 0.2s;
cursor: pointer;
}
.btn:active {
}
.pbtn:active {
transform: scale(0.96);
box-shadow: 0 0 40rpx #00a6ff;
}
.save-btn {
border-color: #00a6ff;
}
.refresh-btn {
border-color: #ffaa00;
}
.refresh-btn .btn-icon {
color: #ffaa00;
}
/* 🔥【新增】生成海报按钮样式 */
.poster-btn {
border-color: #ffaa00;
}
.poster-btn .btn-icon {
color: #ffaa00;
}
.loading-btn {
}
.pbtn-save { border-color: #00a6ff; }
.pbtn-refresh { border-color: #ffaa00; }
.pbtn-refresh .pbtn-icon { color: #ffaa00; }
.pbtn-poster { border-color: #ffaa00; }
.pbtn-poster .pbtn-icon { color: #ffaa00; }
.pbtn-loading {
opacity: 0.7;
pointer-events: none;
}
.btn-icon {
}
.pbtn-icon {
font-size: 36rpx;
margin-right: 8rpx;
}
.btn-text {
}
.pbtn-text {
font-size: 26rpx;
font-weight: 500;
color: #fff;
}
.loading-spinner-small {
width: 30rpx;
height: 30rpx;
}
.loading-spinner-small {
width: 30rpx; height: 30rpx;
border: 4rpx solid rgba(255,255,255,0.2);
border-top: 4rpx solid #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 10rpx;
}
}
/* 🔥【新增】专属海报展示区 */
.poster-section {
/* 专属海报展示区 */
.poster-section {
margin-top: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.poster-canvas {
width: 600rpx;
height: 900rpx;
background: #1a1f2e; /* 占位色 */
}
.poster-canvas {
width: 600rpx; height: 900rpx;
background: #1a1f2e;
border: 4rpx solid #00a6ff;
box-shadow: 0 0 60rpx #00a6ff;
/* 保持方形,无斜切 */
}
.poster-actions {
}
.poster-actions {
display: flex;
gap: 30rpx;
margin-top: 30rpx;
justify-content: center;
}
.save-poster-btn {
}
.pbtn-save-poster {
background: linear-gradient(45deg, #0066cc, #00a6ff);
border-color: #00a6ff;
}
.back-btn {
}
.pbtn-back {
background: rgba(0, 30, 60, 0.8);
border-color: #00a6ff;
}
}
/* 使用说明(保留) */
.instruction {
/* 使用说明 */
.instruction {
margin-top: 30rpx;
padding: 20rpx 40rpx;
background: rgba(0, 30, 50, 0.5);
border-left: 4rpx solid #00a6ff;
clip-path: polygon(0 0, 100% 0, 98% 100%, 0 100%);
}
.instruction-text {
}
.instruction-text {
font-size: 24rpx;
color: #9cf;
line-height: 1.6;
}
}
/* 动画 */
@keyframes qrcodePulse {
/* 动画 */
@keyframes qrcodePulse {
0% { box-shadow: 0 0 40rpx rgba(0, 166, 255, 0.3); }
100% { box-shadow: 0 0 100rpx rgba(0, 166, 255, 0.6); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
}
+5 -2
View File
@@ -140,8 +140,7 @@ Page({
that.handleLoadError(res.data?.msg || '加载失败')
}
},
fail(error) {
console.error('商品详情加载失败:', error)
fail() {
that.handleLoadError('网络错误,请重试')
}
})
@@ -234,6 +233,10 @@ Page({
this.loadShangpinDetail()
},
goToKefu() {
wx.navigateTo({ url: '/pages/cs-chat/cs-chat' })
},
onShareAppMessage() {
const { shangpinData, shangpinId } = this.data
+110 -99
View File
@@ -1,112 +1,123 @@
<!--pages/product-detail/product-detail.wxml-->
<view class="detail-page page-container">
<!-- 加载状态 -->
<view class="loading-container loading-mask loading-mask--light" wx:if="{{isLoading}}">
<view class="loading-content">
<view class="loading-spinner">
<view class="spinner-ring"></view>
<view class="spinner-dot"></view>
</view>
<view class="page">
<!-- 加载 -->
<view class="loading-box" wx:if="{{isLoading}}">
<view class="spinner"></view>
<text class="loading-text">加载中...</text>
</view>
</view>
<!-- 错误状态 -->
<view class="error-container empty-state" wx:if="{{!isLoading && loadError}}">
<image src="/images/error.png" class="error-icon" />
<!-- 错误 -->
<view class="error-box" wx:if="{{!isLoading && loadError}}">
<text class="error-text">加载失败</text>
<button class="error-btn btn btn-sm btn-sm--green" bindtap="onRefresh">重新加载</button>
<view class="error-btn" bindtap="onRefresh">重新加载</view>
</view>
<!-- 商品详情内容 -->
<view class="detail-content" wx:if="{{!isLoading && !loadError && shangpinData}}">
<!-- 商品图片区域(占满屏幕) -->
<view class="image-section">
<view class="image-container" bindtap="previewImage">
<image
src="{{shangpinData.tupian}}"
mode="aspectFill"
class="product-image"
/>
<!-- 库存标签 -->
<view class="kucun-tag {{shangpinData.kucunClass || 'kucun-normal'}}">
<text class="kucun-text">{{shangpinData.kucunText || '库存充足'}}</text>
<!-- 商品详情 -->
<block wx:if="{{!isLoading && !loadError && shangpinData}}">
<!-- 商品大图 -->
<view class="hero" bindtap="previewImage">
<image src="{{shangpinData.tupian}}" mode="aspectFill" class="hero-img" />
<!-- 图片角标 -->
<view class="hero-badge">
<text class="hero-badge-text">{{shangpinData.kucun <= 0 ? '已售罄' : '现货'}}</text>
</view>
</view>
<!-- 价格区 - 红底渐变 -->
<view class="price-block">
<view class="price-left">
<text class="price-sym">¥</text>
<text class="price-int">{{shangpinData.priceInteger || '0'}}</text>
<text class="price-dec">.{{shangpinData.priceDecimal || '00'}}</text>
</view>
<view class="price-right">
<text class="sold-text">已拼{{shangpinData.xiaoliangText || '0'}}件</text>
</view>
</view>
<!-- 标题区 -->
<view class="title-block">
<view class="title-tags">
<text class="tag-hot">热卖</text>
</view>
<text class="title-text">{{shangpinData.biaoti || ''}}</text>
</view>
<!-- 服务保障条 -->
<view class="service-strip">
<view class="service-item">
<text class="service-check">✓</text>
<text class="service-label">极速接单</text>
</view>
<view class="service-item">
<text class="service-check">✓</text>
<text class="service-label">资金担保</text>
</view>
<view class="service-item">
<text class="service-check">✓</text>
<text class="service-label">售后保障</text>
</view>
<view class="service-item">
<text class="service-check">✓</text>
<text class="service-label">隐私保护</text>
</view>
</view>
<!-- 选择SKU入口 -->
<view class="sku-trigger" bindtap="onOrderClick">
<view class="sku-trigger-left">
<text class="sku-label">选择</text>
<text class="sku-value">服务规格、购买数量</text>
</view>
<text class="sku-arrow">></text>
</view>
<!-- 商品介绍 -->
<view class="detail-card" wx:if="{{shangpinData.xiangqing}}">
<view class="detail-card-head">
<text class="detail-card-title">商品介绍</text>
</view>
<text class="detail-card-body">{{shangpinData.xiangqing}}</text>
</view>
<!-- 购买须知 -->
<view class="detail-card" wx:if="{{shangpinData.xiadanxuzhi}}">
<view class="detail-card-head">
<text class="detail-card-title">购买须知</text>
</view>
<text class="detail-card-body">{{shangpinData.xiadanxuzhi}}</text>
</view>
<!-- 规则图片 -->
<view class="detail-card" wx:if="{{shangpinData.guize}}">
<view class="detail-card-head">
<text class="detail-card-title">服务规则</text>
</view>
<image src="{{shangpinData.guize}}" mode="widthFix" class="rule-img" binderror="onImageError" />
</view>
<!-- 底部留白 -->
<view class="bottom-space"></view>
</block>
<!-- 底部操作栏 -->
<view class="bottom-bar" wx:if="{{!isLoading && !loadError && shangpinData}}">
<view class="bottom-bar-inner">
<!-- 客服 -->
<view class="bar-icon-btn" bindtap="goToKefu">
<text class="bar-icon-text">客服</text>
</view>
<!-- 单独购买 -->
<view
class="bar-buy-btn {{shangpinData.kucun <= 0 ? 'bar-buy-btn--disabled' : ''}}"
bindtap="onOrderClick"
hover-class="bar-buy-btn--hover"
>
<text class="bar-buy-text">{{shangpinData.kucun <= 0 ? '已售罄' : '立即下单'}}</text>
<text class="bar-buy-price">¥{{shangpinData.priceInteger || '0'}}.{{shangpinData.priceDecimal || '00'}}</text>
</view>
</view>
</view>
<!-- 下方内容区域(顶部有圆角) -->
<view class="content-area">
<!-- 商品标题和价格区域 -->
<view class="title-price-section">
<view class="title-row">
<text class="product-title">{{shangpinData.biaoti || ''}}</text>
</view>
<view class="price-row">
<!-- 价格 -->
<view class="price-section">
<text class="price-icon">¥</text>
<text class="price-integer">{{shangpinData.priceInteger || '0'}}</text>
<text class="price-decimal">.{{shangpinData.priceDecimal || '00'}}</text>
</view>
<!-- 销量 -->
<view class="sales-section">
<image src="/images/xiaoliang.png" class="sales-icon" />
<text class="sales-text">{{shangpinData.xiaoliangText || '0'}}次购买</text>
</view>
</view>
</view>
<!-- 商品介绍区域 -->
<view class="info-section" wx:if="{{shangpinData.xiangqing}}">
<view class="section-header">
<image src="/images/jieshao.png" class="section-icon" />
<text class="section-title">商品介绍:</text>
</view>
<view class="section-content">
<text class="content-text">{{shangpinData.xiangqing}}</text>
</view>
</view>
<!-- 购买须知区域 -->
<view class="info-section" wx:if="{{shangpinData.xiadanxuzhi}}">
<view class="section-header">
<image src="/images/jieshao.png" class="section-icon" />
<text class="section-title">购买须知:</text>
</view>
<view class="section-content">
<text class="content-text">{{shangpinData.xiadanxuzhi}}</text>
</view>
</view>
<!-- 规则图片区域(只有有图片时才显示) -->
<view class="rule-section" wx:if="{{shangpinData.guize}}">
<image
src="{{shangpinData.guize}}"
mode="widthFix"
class="rule-image"
binderror="onImageError"
/>
</view>
<!-- 占位区域(给底部按钮留空间) -->
<view class="placeholder"></view>
</view>
</view>
<!-- 立即下单按钮(固定在底部,无价格标签) -->
<view class="order-btn-container fixed-bottom" wx:if="{{!isLoading && !loadError && shangpinData}}">
<view class="order-btn btn btn-primary btn-primary--green" bindtap="onOrderClick">
<!-- 按钮发光特效 -->
<view class="btn-glow"></view>
<!-- 按钮流光特效 -->
<view class="btn-streamer"></view>
<!-- 按钮文字 -->
<text class="btn-text">立即预约</text>
</view>
</view>
<global-notification id="global-notification" />
</view>
+293 -295
View File
@@ -1,349 +1,347 @@
/* pages/product-detail/product-detail.wxss */
.detail-page {
width: 100%;
background: #f5f5f5;
}
/* 加载状态 */
.loading-container {
position: absolute;
background: #f5f5f5;
z-index: 100;
}
page {
background: #f2f2f2;
}
.loading-content {
.page {
min-height: 100vh;
padding-bottom: 140rpx;
box-sizing: border-box;
}
/* 加载 */
.loading-box {
display: flex;
flex-direction: column;
align-items: center;
}
justify-content: center;
height: 60vh;
}
.loading-spinner {
width: 80rpx;
height: 80rpx;
position: relative;
margin-bottom: 30rpx;
}
.spinner-ring {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 4rpx solid rgba(82, 196, 26, 0.2);
.spinner {
width: 56rpx;
height: 56rpx;
border: 4rpx solid #f0f0f0;
border-top-color: #e02e24;
border-radius: 50%;
}
animation: spin 0.7s linear infinite;
margin-bottom: 20rpx;
}
.spinner-dot {
position: absolute;
top: 0;
left: 50%;
width: 12rpx;
height: 12rpx;
background: #52c41a;
border-radius: 50%;
transform: translateX(-50%);
animation: spin 1s linear infinite;
}
.loading-text {
font-size: 26rpx;
color: #999;
}
.loading-text {
font-size: 28rpx;
color: #666;
font-weight: 500;
}
/* 错误 */
.error-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60vh;
}
/* 错误状态 */
.error-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #f5f5f5;
z-index: 100;
padding: 0;
}
.error-icon {
width: 180rpx;
height: 180rpx;
opacity: 0.3;
margin-bottom: 30rpx;
}
.error-text {
.error-text {
font-size: 30rpx;
color: #999;
margin-bottom: 40rpx;
}
margin-bottom: 32rpx;
}
.error-btn {
font-weight: 500;
padding: 20rpx 50rpx;
border-radius: 50rpx;
box-shadow: 0 8rpx 24rpx rgba(82, 196, 26, 0.3);
}
.error-btn {
padding: 16rpx 48rpx;
background: #e02e24;
color: #fff;
font-size: 28rpx;
font-weight: 600;
border-radius: 40rpx;
}
/* 商品详情内容 */
.detail-content {
padding-bottom: 140rpx; /* 给底部按钮留出空间 */
}
/* 商品图片区域(占满屏幕) */
.image-section {
width: 100%;
background: white;
}
.image-container {
width: 100%;
height: 750rpx; /* 正方形,占满屏幕宽度 */
/* 商品大图 */
.hero {
position: relative;
overflow: hidden;
background: #f5f5f5;
}
width: 100%;
height: 750rpx;
background: #fff;
}
.product-image {
.hero-img {
width: 100%;
height: 100%;
}
}
/* 库存标签 */
.kucun-tag {
.hero-badge {
position: absolute;
bottom: 30rpx;
right: 30rpx;
padding: 12rpx 24rpx;
border-radius: 30rpx;
backdrop-filter: blur(10rpx);
z-index: 10;
}
.kucun-normal {
background: rgba(0, 0, 0, 0.6);
color: white;
}
.kucun-none {
background: rgba(255, 77, 79, 0.8);
color: white;
}
.kucun-text {
font-size: 24rpx;
font-weight: 500;
}
/* 下方内容区域(顶部有圆角,与图片重叠一部分) */
.content-area {
position: relative;
background: white;
border-radius: 40rpx 40rpx 0 0;
margin-top: -40rpx; /* 与图片重叠一部分,实现平滑过渡 */
padding: 40rpx 30rpx 0;
min-height: 400rpx;
}
/* 标题和价格区域 */
.title-price-section {
margin-bottom: 40rpx;
}
.title-row {
margin-bottom: 30rpx;
}
.product-title {
font-size: 38rpx;
font-weight: 700;
color: #222;
line-height: 1.5;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
/* 价格和销量行 */
.price-row {
top: 24rpx;
left: 24rpx;
padding: 8rpx 16rpx;
border-radius: 6rpx;
background: rgba(224, 46, 36, 0.85);
display: flex;
justify-content: space-between;
align-items: center;
}
justify-content: center;
line-height: 1;
}
/* 价格部分 */
.price-section {
.hero-badge-text {
font-size: 20rpx;
color: #fff;
font-weight: 600;
line-height: 1;
}
/* 价格区 - 拼多多红底 */
.price-block {
display: flex;
align-items: baseline;
}
justify-content: space-between;
padding: 24rpx 28rpx;
background: linear-gradient(135deg, #e02e24, #f05545);
}
.price-icon {
font-size: 34rpx;
color: #ff4d4f;
font-weight: 600;
}
.price-integer {
font-size: 58rpx;
color: #ff4d4f;
font-weight: 700;
margin-left: 4rpx;
}
.price-decimal {
font-size: 34rpx;
color: #ff4d4f;
font-weight: 600;
}
/* 销量部分 */
.sales-section {
.price-left {
display: flex;
align-items: center;
}
align-items: baseline;
}
.sales-icon {
width: 30rpx;
height: 30rpx;
margin-right: 12rpx;
opacity: 0.6;
}
.sales-text {
.price-sym {
font-size: 28rpx;
color: #999;
font-weight: 400;
}
color: #fff;
font-weight: 700;
}
/* 信息区域(商品介绍、购买须知) */
.info-section {
margin-bottom: 40rpx;
padding-top: 30rpx;
border-top: 1rpx solid #f0f0f0;
}
.price-int {
font-size: 60rpx;
color: #fff;
font-weight: 800;
line-height: 1;
margin-left: 2rpx;
}
.section-header {
.price-dec {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.9);
font-weight: 700;
}
.price-right {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
}
.section-icon {
width: 36rpx;
height: 36rpx;
margin-right: 16rpx;
}
.sold-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.85);
background: rgba(0, 0, 0, 0.15);
padding: 6rpx 16rpx;
border-radius: 20rpx;
}
.section-title {
/* 标题区 */
.title-block {
background: #fff;
padding: 24rpx 28rpx 20rpx;
}
.title-tags {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.tag-hot {
font-size: 20rpx;
color: #fff;
background: #e02e24;
padding: 4rpx 12rpx;
border-radius: 4rpx;
font-weight: 600;
margin-right: 12rpx;
}
.title-text {
font-size: 32rpx;
font-weight: 600;
color: #52c41a; /* 绿色 */
}
color: #222;
line-height: 1.6;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
.section-content {
line-height: 1.8;
padding-left: 52rpx; /* 与图标对齐 */
}
/* 服务保障条 */
.service-strip {
display: flex;
flex-wrap: wrap;
gap: 0;
padding: 16rpx 28rpx;
background: #fff;
margin-bottom: 16rpx;
}
.content-text {
font-size: 28rpx;
.service-item {
display: flex;
align-items: center;
margin-right: 24rpx;
margin-bottom: 8rpx;
}
.service-check {
font-size: 20rpx;
color: #e02e24;
font-weight: 700;
margin-right: 4rpx;
}
.service-label {
font-size: 22rpx;
color: #666;
}
/* 选择SKU入口 */
.sku-trigger {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
padding: 24rpx 28rpx;
margin-bottom: 16rpx;
}
.sku-trigger-left {
display: flex;
align-items: center;
}
.sku-label {
font-size: 26rpx;
color: #999;
margin-right: 16rpx;
}
.sku-value {
font-size: 26rpx;
color: #333;
font-weight: 400;
white-space: pre-line;
line-height: 1.8;
}
}
/* 规则图片区域 */
.rule-section {
margin: 40rpx 0;
padding-top: 30rpx;
border-top: 1rpx solid #f0f0f0;
}
.sku-arrow {
font-size: 28rpx;
color: #ccc;
margin-left: 12rpx;
}
.rule-image {
width: 100%;
border-radius: 16rpx;
display: block;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
}
/* 详情卡片 */
.detail-card {
background: #fff;
margin-bottom: 16rpx;
padding: 28rpx;
}
/* 占位区域 */
.placeholder {
height: 140rpx;
}
.detail-card-head {
margin-bottom: 20rpx;
}
/* 立即下单按钮容器(固定底部) */
.order-btn-container {
background: linear-gradient(transparent, rgba(255, 255, 255, 0.9) 70%);
}
.detail-card-title {
font-size: 30rpx;
font-weight: 700;
color: #222;
position: relative;
padding-left: 16rpx;
}
/* 立即下单按钮 */
.order-btn {
border-radius: 50rpx;
height: 100rpx;
box-shadow:
0 12rpx 36rpx rgba(82, 196, 26, 0.4),
0 4rpx 12rpx rgba(82, 196, 26, 0.3);
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.order-btn:active {
box-shadow:
0 6rpx 24rpx rgba(82, 196, 26, 0.5),
0 2rpx 8rpx rgba(82, 196, 26, 0.4);
}
/* 按钮发光特效 */
.btn-glow {
.detail-card-title::before {
content: '';
position: absolute;
top: 0;
left: 0;
top: 4rpx;
width: 6rpx;
height: 28rpx;
border-radius: 3rpx;
background: #e02e24;
}
.detail-card-body {
font-size: 26rpx;
color: #555;
line-height: 1.8;
white-space: pre-line;
}
.rule-img {
width: 100%;
border-radius: 8rpx;
display: block;
}
/* 底部留白 */
.bottom-space {
height: 40rpx;
}
/* 底部操作栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.1);
animation: glowPulse 2s infinite;
}
z-index: 9999;
background: #fff;
padding-bottom: env(safe-area-inset-bottom);
border-top: 1rpx solid #f0f0f0;
}
/* 按钮流光特效 */
.btn-streamer {
position: absolute;
top: 0;
left: 0;
width: 100rpx;
.bottom-bar-inner {
display: flex;
align-items: stretch;
height: 100rpx;
}
.bar-icon-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 28rpx;
height: 96rpx;
}
.bar-icon-text {
font-size: 22rpx;
color: #666;
margin-top: 4rpx;
}
.bar-buy-btn {
width: 50%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transform: translateX(-100%);
animation: streamerFlow 3s infinite;
}
border-radius: 0;
display: flex;
align-items: center;
justify-content: center;
background: #e02e24;
margin-left: auto;
}
/* 按钮文字 */
.btn-text {
font-size: 36rpx;
color: white;
font-weight: 600;
letter-spacing: 2rpx;
position: relative;
z-index: 2;
}
.bar-buy-btn--disabled {
background: #ccc;
}
/* 动画定义 */
@keyframes glowPulse {
0%, 100% {
opacity: 0.5;
}
50% {
opacity: 0.8;
}
}
.bar-buy-btn--hover {
opacity: 0.9;
transform: scale(0.98);
}
@keyframes streamerFlow {
0% {
transform: translateX(-100%);
}
50% {
transform: translateX(100%);
}
100% {
transform: translateX(100%);
}
}
.bar-buy-text {
font-size: 30rpx;
font-weight: 700;
color: #fff;
margin-right: 8rpx;
}
.bar-buy-price {
font-size: 26rpx;
font-weight: 700;
color: rgba(255, 255, 255, 0.9);
}
+95 -310
View File
@@ -1,31 +1,29 @@
// pages/submit/submit.js
const app = getApp()
import request from '../../utils/request.js'
// 引入弹窗服务(路径请根据项目实际调整)
import PopupService from '../../services/popupService.js'
Page({
data: {
// 全局变量
ossImageUrl: '',
apiBaseUrl: '',
// 从商品详情页传入的数据
shangpinData: null,
shangpinId: '',
// 表单数据
nicheng: '', // 游戏昵称/ID
beizhu: '', // 备注信息
zhiding: '', // 指定打手ID(新增)
jine: 0, // 总金额
nicheng: '',
beizhu: '',
zhiding: '',
quantity: 1,
jine: 0,
totalPriceInteger: '0',
totalPriceDecimal: '00',
// 页面状态
isSubmitting: false,
canSubmit: false,
isPaying: false,
// 支付相关
orderId: '',
payParams: null,
checkCount: 0,
@@ -33,48 +31,38 @@ Page({
},
onLoad(options) {
// 获取全局变量
this.setData({
ossImageUrl: app.globalData.ossImageUrl || '',
apiBaseUrl: app.globalData.apiBaseUrl || ''
})
// 设置页面标题
wx.setNavigationBarTitle({
title: '提交订单'
})
wx.setNavigationBarTitle({ title: '提交订单' })
// 解析传递的商品数据
try {
if (options.data) {
const shangpinData = JSON.parse(decodeURIComponent(options.data))
// 处理图片URL(拼接完整路径)
if (shangpinData.image && !shangpinData.image.startsWith('http')) {
shangpinData.image = this.data.ossImageUrl + shangpinData.image
}
// 处理价格显示
if (shangpinData.price) {
const price = parseFloat(shangpinData.price) || 0
const priceInteger = Math.floor(price)
let priceDecimal = '00'
const decimalPart = (price - priceInteger).toFixed(2)
if (decimalPart > 0) {
priceDecimal = decimalPart.toString().split('.')[1]
}
shangpinData.priceInteger = priceInteger
shangpinData.priceDecimal = priceDecimal
}
// 设置商品数据
this.setData({
shangpinData: shangpinData,
shangpinId: shangpinData.id || '',
jine: parseFloat(shangpinData.price) || 0,
})
}, () => this.updateTotalPrice())
} else {
this.handleDataError('商品数据不完整')
}
@@ -84,147 +72,101 @@ Page({
},
onShow() {
// 原有代码:注册通知组件
this.registerNotificationComponent();
// 🆕 进入页面时触发弹窗检查(PopupService 会自动判断是否展示)
PopupService.checkAndShow(this, 'tijiao');
this.registerNotificationComponent()
PopupService.checkAndShow(this, 'tijiao')
},
// pages/submit/submit.js 中添加 onHide 方法(如果已有则合并内容)
onHide() {
const popupComp = this.selectComponent('#popupNotice');
if (popupComp && popupComp.cleanup) {
popupComp.cleanup();
}
onHide() {
const popupComp = this.selectComponent('#popupNotice')
if (popupComp && popupComp.cleanup) popupComp.cleanup()
},
// 新增:注册通知组件
registerNotificationComponent() {
const app = getApp();
const notificationComp = this.selectComponent('#global-notification');
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()
};
}
}
},
/**
* 处理数据错误
*/
updateTotalPrice() {
const total = this.data.jine * this.data.quantity
const intPart = Math.floor(total)
const decPart = (total - intPart).toFixed(2).split('.')[1]
this.setData({
totalPriceInteger: intPart,
totalPriceDecimal: decPart
})
},
onQtyMinus() {
if (this.data.quantity <= 1) return
this.setData({ quantity: this.data.quantity - 1 }, () => this.updateTotalPrice())
},
onQtyPlus() {
if (this.data.quantity >= 99) return
this.setData({ quantity: this.data.quantity + 1 }, () => this.updateTotalPrice())
},
handleDataError(errorMsg) {
wx.showToast({
title: errorMsg || '数据错误',
icon: 'none'
})
wx.showToast({ title: errorMsg || '数据错误', icon: 'none' })
setTimeout(() => wx.navigateBack(), 1500)
},
setTimeout(() => {
goBack() {
wx.navigateBack()
}, 1500)
},
/**
* 游戏昵称输入事件
*/
onNicknameInput(e) {
const value = e.detail.value.trim()
this.setData({
nicheng: value
}, () => {
this.checkFormValid()
})
this.setData({ nicheng: e.detail.value.trim() }, () => this.checkFormValid())
},
/**
* 备注信息输入事件
*/
onRemarkInput(e) {
const value = e.detail.value.trim()
this.setData({
beizhu: value
})
this.setData({ beizhu: e.detail.value.trim() })
},
/**
* 指定打手ID输入事件
*/
onZhidingInput(e) {
const value = e.detail.value.trim()
this.setData({
zhiding: value
})
this.setData({ zhiding: e.detail.value.trim() })
},
/**
* 检查表单是否有效
*/
checkFormValid() {
const { nicheng } = this.data
const isValid = nicheng && nicheng.length > 0 && nicheng.length <= 15
this.setData({
canSubmit: isValid
})
this.setData({ canSubmit: nicheng && nicheng.length > 0 && nicheng.length <= 15 })
},
/**
* 提交订单
*/
onSubmitOrder() {
const { nicheng, beizhu, zhiding, jine, shangpinId, canSubmit, isSubmitting } = this.data
const { nicheng, beizhu, zhiding, jine, quantity, shangpinId, canSubmit, isSubmitting } = this.data
// 验证表单
if (!canSubmit) {
wx.showToast({
title: '请填写游戏昵称',
icon: 'none'
})
wx.showToast({ title: '请填写游戏昵称', icon: 'none' })
return
}
// 防止重复提交
if (isSubmitting) {
return
}
if (isSubmitting) return
this.setData({
isSubmitting: true,
isPaying: false
})
this.setData({ isSubmitting: true, isPaying: false })
// 显示loading(超时设为10秒)
wx.showLoading({
title: '创建订单中...',
mask: true
})
wx.showLoading({ title: '创建订单中...', mask: true })
const timeoutTimer = setTimeout(() => {
wx.hideLoading()
wx.showToast({
title: '网络超时,请检查您是否已登录',
icon: 'none'
})
this.setData({
isSubmitting: false,
isPaying: false
})
wx.showToast({ title: '网络超时,请检查您是否已登录', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
}, 10000)
// 构建请求数据(添加zhiding字段)
const orderData = {
shangpin_id: shangpinId, // 商品ID
nicheng: nicheng, // 游戏昵称
beizhu: beizhu || '', // 备注(可为空)
zhiding: zhiding || '', // 指定打手ID(可为空)
jine: jine // 金额
shangpin_id: shangpinId,
nicheng: nicheng,
beizhu: beizhu || '',
zhiding: zhiding || '',
jine: jine * quantity,
shuliang: quantity
}
// 调用后端创建订单接口
request({
url: '/dingdan/xiadan',
method: 'POST',
@@ -236,12 +178,9 @@ onHide() {
if (res.statusCode === 200 && res.data) {
const response = res.data
if (response.code === 0 && response.data) {
// 保存订单ID和支付参数
const orderId = response.data.dingdanid
const payParams = response.data.payParams || {}
this.setData({
orderId: orderId,
payParams: payParams,
@@ -249,59 +188,29 @@ onHide() {
isPaying: true,
checkCount: 0
})
// 调起微信支付
this.triggerWxPayment(payParams)
} else {
wx.showToast({
title: response.msg || '创建订单失败',
icon: 'none'
})
this.setData({
isSubmitting: false,
isPaying: false
})
wx.showToast({ title: response.msg || '创建订单失败', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
}
} else {
wx.showToast({
title: '网络错误,请重试',
icon: 'none'
})
this.setData({
isSubmitting: false,
isPaying: false
})
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
}
})
.catch(error => {
.catch(() => {
clearTimeout(timeoutTimer)
wx.hideLoading()
wx.showToast({
title: '创建订单失败',
icon: 'none'
})
this.setData({
isSubmitting: false,
isPaying: false
})
wx.showToast({ title: '创建订单失败', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
})
},
/**
* 调起微信支付
*/
triggerWxPayment(payParams) {
// 验证支付参数是否完整
if (!payParams || !payParams.timeStamp || !payParams.nonceStr ||
!payParams.package || !payParams.signType || !payParams.paySign) {
wx.showToast({
title: '支付参数不完整',
icon: 'none'
})
this.setData({
isSubmitting: false,
isPaying: false
})
wx.showToast({ title: '支付参数不完整', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
return
}
@@ -311,237 +220,113 @@ onHide() {
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign,
success: (res) => {
// 支付成功,开始双重确认
this.onPaymentSuccess()
},
success: () => this.onPaymentSuccess(),
fail: (err) => {
// 根据错误类型处理
if (err.errMsg === 'requestPayment:fail cancel') {
// 用户取消支付
this.handleUserCancel()
} else {
// 其他支付错误
this.handlePaymentFail(err)
}
}
})
},
/**
* 处理用户取消支付
*/
handleUserCancel() {
const { orderId } = this.data
wx.showToast({
title: '已取消支付',
icon: 'none',
duration: 1500
})
// 通知后端用户取消(静默请求,不阻塞用户)
wx.showToast({ title: '已取消支付', icon: 'none', duration: 1500 })
if (orderId) {
request({
url: '/dingdan/shibai',
method: 'POST',
data: {
dingdanid: orderId,
yuanyin: 'user_cancel'
data: { dingdanid: orderId, yuanyin: 'user_cancel' }
}).catch(() => {})
}
}).catch(() => {
// 静默失败
})
}
this.setData({
isSubmitting: false,
isPaying: false
})
this.setData({ isSubmitting: false, isPaying: false })
},
/**
* 处理支付失败
*/
handlePaymentFail(err) {
const { orderId } = this.data
wx.showToast({
title: '支付失败',
icon: 'none',
duration: 1500
})
// 通知后端支付失败(简化流程,直接请求,不显示loading)
wx.showToast({ title: '支付失败', icon: 'none', duration: 1500 })
if (orderId) {
request({
url: '/dingdan/shibai',
method: 'POST',
data: {
dingdanid: orderId,
yuanyin: 'payment_fail',
cuowu: err.errMsg || ''
}
}).catch(() => {
// 静默失败
}).finally(() => {
this.setData({
isSubmitting: false,
isPaying: false
})
data: { dingdanid: orderId, yuanyin: 'payment_fail', cuowu: err.errMsg || '' }
}).catch(() => {}).finally(() => {
this.setData({ isSubmitting: false, isPaying: false })
})
} else {
this.setData({
isSubmitting: false,
isPaying: false
})
this.setData({ isSubmitting: false, isPaying: false })
}
},
/**
* 支付成功处理(双重确认)
*/
onPaymentSuccess() {
const { orderId } = this.data
if (!orderId) {
wx.showToast({
title: '订单ID不存在',
icon: 'none'
})
this.setData({
isSubmitting: false,
isPaying: false
})
wx.showToast({ title: '订单ID不存在', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
return
}
// 显示支付成功提示(时间缩短)
wx.showToast({
title: '支付成功,确认中...',
icon: 'none',
duration: 1500
})
// 开始双重确认流程
wx.showToast({ title: '支付成功,确认中...', icon: 'none', duration: 1500 })
this.checkOrderStatus()
},
/**
* 检查订单状态(双重确认)
*/
checkOrderStatus() {
const { orderId, checkCount, maxCheckCount } = this.data
wx.showLoading({ title: `确认支付状态${checkCount > 0 ? `(${checkCount + 1})` : ''}`, mask: true })
// 显示加载(时间缩短)
wx.showLoading({
title: `确认支付状态${checkCount > 0 ? `(${checkCount + 1})` : ''}`,
mask: true
})
// 超时设为5秒
const timeoutTimer = setTimeout(() => {
wx.hideLoading()
this.handleConfirmError('网络超时')
}, 5000)
// 向后端发起支付复查请求
request({
url: '/dingdan/fucha',
method: 'POST',
data: {
dingdanid: orderId
}
data: { dingdanid: orderId }
})
.then(res => {
clearTimeout(timeoutTimer)
wx.hideLoading()
if (res.statusCode === 200 && res.data) {
const response = res.data
if (response.code === 0) {
// 确认成功,订单状态已更新
if (res.data.code === 0) {
this.handleConfirmSuccess()
} else {
// 后端返回错误
this.handleConfirmError(response.msg || '订单状态异常')
this.handleConfirmError(res.data.msg || '订单状态异常')
}
} else {
// 网络错误
this.handleConfirmError('网络错误')
}
})
.catch(error => {
.catch(() => {
clearTimeout(timeoutTimer)
wx.hideLoading()
this.handleConfirmError('检查失败')
})
},
/**
* 处理确认成功
*/
handleConfirmSuccess() {
// 显示最终成功提示
wx.showToast({
title: '购买成功!',
icon: 'success',
duration: 1500
})
// 1.5秒后跳转到首页
setTimeout(() => {
wx.switchTab({
url: '/pages/index/index'
})
}, 1500)
this.setData({
isSubmitting: false,
isPaying: false
})
wx.showToast({ title: '购买成功!', icon: 'success', duration: 1500 })
setTimeout(() => wx.switchTab({ url: '/pages/index/index' }), 1500)
this.setData({ isSubmitting: false, isPaying: false })
},
/**
* 处理确认错误
*/
handleConfirmError(errorMsg) {
const { checkCount, maxCheckCount } = this.data
const newCheckCount = checkCount + 1
if (newCheckCount < maxCheckCount) {
// 还可以重试
this.setData({
checkCount: newCheckCount
})
// 1秒后重试
setTimeout(() => {
this.checkOrderStatus()
}, 1000)
const newCheckCount = this.data.checkCount + 1
if (newCheckCount < this.data.maxCheckCount) {
this.setData({ checkCount: newCheckCount })
setTimeout(() => this.checkOrderStatus(), 1000)
} else {
// 达到最大重试次数
wx.showModal({
title: '支付确认中',
content: '支付已成功,但订单状态确认异常。请稍后在订单列表中查看状态,如有问题请联系客服。',
showCancel: false,
confirmText: '我知道了',
success: (res) => {
if (res.confirm) {
wx.switchTab({
url: '/pages/index/index'
})
}
if (res.confirm) wx.switchTab({ url: '/pages/index/index' })
}
})
this.setData({
isSubmitting: false,
isPaying: false
})
this.setData({ isSubmitting: false, isPaying: false })
}
}
})
+81 -73
View File
@@ -1,124 +1,132 @@
<!--pages/submit/submit.wxml-->
<view class="submit-page">
<!-- 商品信息区域 -->
<view class="product-section">
<view class="product-card">
<!-- 商品图片 -->
<view class="product-image-box">
<image
src="{{shangpinData && shangpinData.image ? shangpinData.image : ''}}"
mode="aspectFill"
class="product-image"
/>
<view class="page">
<!-- 商品摘要 - 拼多多SKU弹窗顶部风格 -->
<view class="sku-header">
<image class="sku-thumb" src="{{shangpinData.image || ''}}" mode="aspectFill" />
<view class="sku-header-info">
<view class="sku-price-row">
<text class="sku-price-sym">¥</text>
<text class="sku-price-int">{{totalPriceInteger}}</text>
<text class="sku-price-dec">.{{totalPriceDecimal}}</text>
</view>
<text class="sku-stock">库存{{shangpinData.kucun || 0}}件</text>
<text class="sku-selected">已选:{{quantity}}份</text>
</view>
<view class="sku-close" bindtap="goBack">×</view>
</view>
<!-- 商品信息 -->
<view class="product-info">
<view class="product-title">
<text class="title-text">{{shangpinData && shangpinData.title ? shangpinData.title : '商品标题'}}</text>
</view>
<!-- 价格信息 -->
<view class="price-info">
<text class="price-label">价格:</text>
<view class="price-value">
<text class="price-icon">¥</text>
<text class="price-integer">{{shangpinData && shangpinData.priceInteger ? shangpinData.priceInteger : '0'}}</text>
<text class="price-decimal">.{{shangpinData && shangpinData.priceDecimal ? shangpinData.priceDecimal : '00'}}</text>
</view>
</view>
</view>
<!-- 购买数量 -->
<view class="sku-section">
<text class="sku-section-title">购买数量</text>
<view class="qty-row">
<view class="qty-btn {{quantity <= 1 ? 'qty-btn--disabled' : ''}}" bindtap="onQtyMinus">-</view>
<text class="qty-val">{{quantity}}</text>
<view class="qty-btn {{quantity >= 99 ? 'qty-btn--disabled' : ''}}" bindtap="onQtyPlus">+</view>
</view>
</view>
<!-- 表单区域(左右布局) -->
<view class="form-section">
<view class="form-card">
<!-- 游戏昵称 - 左右布局 -->
<view class="form-item-row">
<view class="item-label-row">
<text class="label-text-bold">昵称</text>
<text class="label-required">*</text>
<!-- 服务信息 -->
<view class="sku-section">
<text class="sku-section-title">服务信息</text>
<view class="form-field">
<view class="field-top">
<text class="field-name">游戏昵称</text>
<text class="field-required">必填</text>
</view>
<input
class="item-input-row"
class="field-input"
type="text"
placeholder="请输入您的昵称"
placeholder-class="input-placeholder-gray"
placeholder="请输入您的游戏昵称"
placeholder-class="ph"
maxlength="15"
value="{{nicheng}}"
bindinput="onNicknameInput"
/>
</view>
<!-- 备注信息 - 左右布局 -->
<view class="form-item-row">
<view class="item-label-row">
<text class="label-text-bold">备注</text>
<view class="form-field">
<view class="field-top">
<text class="field-name">备注</text>
<text class="field-optional">选填</text>
</view>
<input
class="item-input-row"
class="field-input"
type="text"
placeholder="请补充您要补充的其他联系信息"
placeholder-class="input-placeholder-gray"
placeholder="补充其他联系信息"
placeholder-class="ph"
maxlength="30"
value="{{beizhu}}"
bindinput="onRemarkInput"
/>
</view>
<!-- 指定打手ID - 左右布局 -->
<view class="form-item-row">
<view class="item-label-row">
<text class="label-text-bold">指定服务者ID(选填)</text>
<view class="form-field">
<view class="field-top">
<text class="field-name">指定服务者</text>
<text class="field-optional">选填</text>
</view>
<input
class="item-input-row"
class="field-input"
type="text"
placeholder="填写您需要指定的服务者"
placeholder-class="input-placeholder-gray"
placeholder="填写指定服务者ID"
placeholder-class="ph"
maxlength="20"
value="{{zhiding}}"
bindinput="onZhidingInput"
/>
</view>
</view>
<!-- 支付方式 -->
<view class="payment-item">
<view class="payment-method">
<image src="/images/wechat-pay.png" class="payment-icon" />
<text class="payment-text">微信支付</text>
<view class="payment-checkbox">
<text class="checkbox-text">✓</text>
<!-- 服务保障 -->
<view class="sku-section">
<text class="sku-section-title">服务保障</text>
<view class="guarantee-row">
<view class="guarantee-item">
<text class="g-check">✓</text>
<text class="g-text">极速接单</text>
</view>
<view class="guarantee-item">
<text class="g-check">✓</text>
<text class="g-text">资金担保</text>
</view>
<view class="guarantee-item">
<text class="g-check">✓</text>
<text class="g-text">售后保障</text>
</view>
<view class="guarantee-item">
<text class="g-check">✓</text>
<text class="g-text">隐私保护</text>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="footer-section">
<view class="total-price">
<text class="total-label">总价:</text>
<view class="total-value">
<text class="price-icon">¥</text>
<text class="price-integer">{{shangpinData && shangpinData.priceInteger ? shangpinData.priceInteger : '0'}}</text>
<text class="price-decimal">.{{shangpinData && shangpinData.priceDecimal ? shangpinData.priceDecimal : '00'}}</text>
<!-- 支付方式 -->
<view class="sku-section">
<text class="sku-section-title">支付方式</text>
<view class="pay-row">
<image src="/images/wechat-pay.png" class="pay-icon" />
<text class="pay-label">微信支付</text>
<view class="pay-check">
<text class="pay-check-icon">✓</text>
</view>
</view>
</view>
<!-- 底部留白 -->
<view class="bottom-space"></view>
<!-- 底部确认按钮 -->
<view class="confirm-bar">
<view
class="submit-btn {{canSubmit && !isSubmitting ? 'submit-btn-active' : 'submit-btn-disabled'}}"
class="confirm-btn {{canSubmit && !isSubmitting ? 'confirm-btn--active' : 'confirm-btn--disabled'}}"
bindtap="onSubmitOrder"
hover-class="confirm-btn--hover"
>
<text class="btn-text">{{isSubmitting ? '提交中...' : '提交订单'}}</text>
<view class="btn-glow"></view>
{{isSubmitting ? '提交中...' : '确定'}}
</view>
</view>
<!-- 全局通知组件 -->
<global-notification id="global-notification" />
<!-- 弹窗公告组件(供 PopupService 调用) -->
<popup-notice id="popupNotice" />
</view>
+249 -233
View File
@@ -1,280 +1,296 @@
/* pages/submit/submit.wxss */
.submit-page {
display: flex;
flex-direction: column;
page {
background: #f2f2f2;
}
.page {
min-height: 100vh;
background: linear-gradient(135deg, #f8f9ff 0%, #f0f3ff 100%);
padding-bottom: 140rpx;
box-sizing: border-box;
}
}
/* 商品信息区域 */
.product-section {
padding: 30rpx 30rpx 20rpx 30rpx;
flex-shrink: 0;
}
.product-card {
background: white;
border-radius: 20rpx;
padding: 30rpx;
/* SKU头部 - 拼多多弹窗风格 */
.sku-header {
display: flex;
align-items: center;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06);
}
background: #fff;
padding: 24rpx;
position: relative;
margin-bottom: 16rpx;
}
.product-image-box {
width: 160rpx;
height: 160rpx;
border-radius: 16rpx;
overflow: hidden;
background: #f5f5f5;
.sku-thumb {
width: 180rpx;
height: 180rpx;
border-radius: 12rpx;
flex-shrink: 0;
margin-right: 24rpx;
}
margin-right: 20rpx;
background: #f5f5f5;
}
.product-image {
width: 100%;
height: 100%;
}
.product-info {
.sku-header-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 160rpx;
}
padding: 4rpx 0;
}
.product-title {
margin-bottom: 20rpx;
}
.title-text {
font-size: 30rpx;
font-weight: 600;
color: #222;
line-height: 1.4;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.price-info {
.sku-price-row {
display: flex;
align-items: baseline;
}
}
.price-label {
font-size: 26rpx;
color: #666;
font-weight: 500;
}
.price-value {
display: flex;
align-items: baseline;
}
.price-icon {
.sku-price-sym {
font-size: 28rpx;
color: #ff4d4f;
font-weight: 600;
}
.price-integer {
font-size: 38rpx;
color: #ff4d4f;
color: #e02e24;
font-weight: 700;
margin-left: 4rpx;
}
}
.price-decimal {
.sku-price-int {
font-size: 48rpx;
color: #e02e24;
font-weight: 800;
line-height: 1;
margin-left: 2rpx;
}
.sku-price-dec {
font-size: 28rpx;
color: #ff4d4f;
font-weight: 600;
}
color: #e02e24;
font-weight: 700;
}
/* 表单区域 */
.form-section {
padding: 0 30rpx 20rpx 30rpx;
flex-shrink: 0;
}
.form-card {
background: white;
border-radius: 20rpx;
padding: 30rpx;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.05);
}
.form-item-row {
display: flex;
align-items: center;
padding: 25rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.form-item-row:last-child {
border-bottom: none;
}
.item-label-row {
width: 220rpx;
flex-shrink: 0;
display: flex;
align-items: center;
}
.label-text-bold {
font-size: 30rpx;
font-weight: 600;
color: #333;
}
.label-required {
font-size: 30rpx;
color: #ff4d4f;
margin-left: 8rpx;
}
.item-input-row {
flex: 1;
font-size: 28rpx;
color: #222;
height: 60rpx;
line-height: 60rpx;
padding: 0 20rpx;
text-align: right;
background: #fafafa;
border-radius: 10rpx;
}
.input-placeholder-gray {
.sku-stock {
font-size: 24rpx;
color: #999;
font-size: 28rpx;
}
}
/* 支付方式 */
.payment-item {
margin-top: 30rpx;
padding-top: 30rpx;
border-top: 1rpx solid #f0f0f0;
}
.sku-selected {
font-size: 24rpx;
color: #666;
}
.payment-method {
display: flex;
align-items: center;
padding: 20rpx 0;
}
.payment-icon {
.sku-close {
position: absolute;
top: 16rpx;
right: 16rpx;
width: 48rpx;
height: 48rpx;
margin-right: 20rpx;
}
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
color: #999;
border-radius: 50%;
background: #f5f5f5;
line-height: 1;
}
.payment-text {
/* SKU区块 */
.sku-section {
background: #fff;
padding: 24rpx 28rpx;
margin-bottom: 16rpx;
}
.sku-section-title {
font-size: 28rpx;
font-weight: 700;
color: #222;
margin-bottom: 20rpx;
display: block;
}
/* 数量选择器 */
.qty-row {
display: flex;
align-items: center;
}
.qty-btn {
width: 64rpx;
height: 64rpx;
border-radius: 8rpx;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 700;
color: #333;
border: 1rpx solid #e8e8e8;
}
.qty-btn--disabled {
color: #ccc;
border-color: #f0f0f0;
background: #fafafa;
}
.qty-val {
min-width: 80rpx;
text-align: center;
font-size: 32rpx;
font-weight: 700;
color: #222;
margin: 0 20rpx;
}
/* 表单字段 */
.form-field {
padding: 16rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.form-field:last-child {
border-bottom: none;
padding-bottom: 0;
}
.field-top {
display: flex;
align-items: center;
margin-bottom: 10rpx;
}
.field-name {
font-size: 26rpx;
font-weight: 600;
color: #333;
}
.field-required {
font-size: 20rpx;
color: #e02e24;
background: #fff1f0;
padding: 2rpx 10rpx;
border-radius: 4rpx;
margin-left: 10rpx;
}
.field-optional {
font-size: 20rpx;
color: #bbb;
margin-left: 10rpx;
}
.field-input {
width: 100%;
height: 72rpx;
background: #f7f7f7;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 26rpx;
color: #333;
border: 1rpx solid #eee;
box-sizing: border-box;
}
.ph {
color: #bbb;
font-size: 26rpx;
}
/* 服务保障 */
.guarantee-row {
display: flex;
flex-wrap: wrap;
gap: 12rpx 20rpx;
}
.guarantee-item {
display: flex;
align-items: center;
}
.g-check {
font-size: 20rpx;
color: #e02e24;
font-weight: 700;
margin-right: 4rpx;
}
.g-text {
font-size: 24rpx;
color: #666;
}
/* 支付方式 */
.pay-row {
display: flex;
align-items: center;
}
.pay-icon {
width: 44rpx;
height: 44rpx;
margin-right: 16rpx;
}
.pay-label {
flex: 1;
font-size: 28rpx;
color: #333;
font-weight: 500;
flex: 1;
}
}
.payment-checkbox {
width: 40rpx;
height: 40rpx;
.pay-check {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
background: #52c41a;
display: flex;
align-items: center;
justify-content: center;
}
}
.checkbox-text {
color: white;
font-size: 28rpx;
font-weight: 600;
}
.pay-check-icon {
color: #fff;
font-size: 22rpx;
font-weight: 700;
}
/* 底部操作栏 */
.footer-section {
/* 底部留白 */
.bottom-space {
height: 20rpx;
}
/* 底部确认按钮 */
.confirm-bar {
position: fixed;
bottom: 0;
right: 0;
width: 50%;
height: 100rpx;
background: #fff;
padding-bottom: env(safe-area-inset-bottom);
z-index: 9999;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background: white;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
margin-top: auto;
flex-shrink: 0;
}
}
.total-price {
display: flex;
align-items: baseline;
flex: 1;
}
.total-label {
font-size: 30rpx;
color: #333;
font-weight: 600;
}
.total-value {
display: flex;
align-items: baseline;
}
.submit-btn {
width: 240rpx;
height: 88rpx;
border-radius: 44rpx;
.confirm-btn {
width: 100%;
height: 100%;
border-radius: 0;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
transition: all 0.3s;
}
.submit-btn-active {
background: linear-gradient(135deg, #52c41a, #389e0d);
box-shadow: 0 8rpx 32rpx rgba(255, 77, 79, 0.4);
}
.submit-btn-disabled {
background: #ccc;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
}
.submit-btn-active:active {
transform: scale(0.98);
box-shadow: 0 4rpx 20rpx rgba(255, 77, 79, 0.5);
}
.btn-text {
font-size: 32rpx;
color: white;
font-weight: 600;
position: relative;
z-index: 2;
}
font-weight: 700;
color: #fff;
letter-spacing: 4rpx;
}
.btn-glow {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transform: translateX(-100%);
animation: btnGlow 3s infinite;
}
.confirm-btn--active {
background: #e02e24;
}
@keyframes btnGlow {
0% { transform: translateX(-100%); }
50% { transform: translateX(100%); }
100% { transform: translateX(100%); }
}
.confirm-btn--disabled {
background: #ccc;
}
.confirm-btn--hover {
opacity: 0.9;
transform: scale(0.98);
}
+3 -13
View File
@@ -1,5 +1,5 @@
// pages/verify/verify.js
import request from '../../utils/request.js'
import { createPage, request } from '../../utils/base-page.js'
import { enterLockedRole } from '../../utils/primary-role.js'
const API_MAP = {
@@ -26,7 +26,7 @@ const NAME_MAP = {
kaoheguan: '考核官'
}
Page({
Page(createPage({
data: {
authType: '',
authName: '',
@@ -156,14 +156,4 @@ Page({
wx.showToast({ title: err.message || '注册失败', icon: 'none' })
}
},
goToKefu() {
const app = getApp()
const { link, enterpriseId } = app.globalData.kefuConfig || {}
if (!link || !enterpriseId) {
wx.showToast({ title: '客服配置未加载', icon: 'none' })
return
}
wx.openCustomerServiceChat({ extInfo: { url: link }, corpId: enterpriseId })
}
})
}))
+3 -21
View File
@@ -1,10 +1,10 @@
// pages/withdraw/withdraw.js
import request from '../../utils/request.js';
import { createPage, request } from '../../utils/base-page.js';
import PopupService from '../../services/popupService.js';
const app = getApp();
Page({
Page(createPage({
data: {
pageMode: 0,
pageOptions: {},
@@ -23,24 +23,6 @@ Page({
PopupService.checkAndShow(this, 'tixian');
},
onHide() {
const popupComp = this.selectComponent('#popupNotice');
if (popupComp && popupComp.cleanup) {
popupComp.cleanup();
}
},
// 注册通知组件
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
async fetchPageMode() {
try {
const res = await request({
@@ -69,4 +51,4 @@ Page({
},
stopPropagation() {}
});
}))
+8 -7
View File
@@ -34,6 +34,7 @@
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.8);
padding: 0 20rpx;
position: relative;
overflow: hidden;
}
.tab-capsule::before {
@@ -46,19 +47,19 @@
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%,
rgba(255, 255, 255, 0.15) 20%,
rgba(255, 255, 255, 0.22) 50%,
rgba(255, 255, 255, 0.15) 80%,
transparent 100%
);
animation: shine 3s infinite;
animation: shine 8s infinite;
z-index: 1;
}
@keyframes shine {
0% { left: -100%; }
25% { left: 150%; }
100% { left: 150%; }
0% { left: -60%; }
15% { left: 100%; }
100% { left: 100%; }
}
.tab-item {
+46
View File
@@ -0,0 +1,46 @@
// utils/api-helper.js - API 响应解析
export function isApiSuccess(body) {
if (!body) return false;
const code = Number(body.code);
return code === 200 || code === 0;
}
export function getApiMsg(body) {
if (!body) return '';
return body.msg || body.message || '';
}
export function extractUserData(body) {
if (!body) return {};
if (body.data && typeof body.data === 'object' && !Array.isArray(body.data)) {
return body.data;
}
const userData = { ...body };
delete userData.code;
delete userData.msg;
delete userData.message;
return userData;
}
/** 解析扫码场景参数 */
export function parseSceneOptions(options) {
if (!options || !options.scene) return options || {};
try {
const scene = decodeURIComponent(options.scene);
return { ...options, inviteCode: scene };
} catch (e) {
return options;
}
}
/** 订单状态中文映射 */
const ORDER_STATUS_MAP = {
1: '已下单', 2: '进行中', 3: '已完成',
4: '退款中', 5: '已退款', 6: '退款失败',
7: '指定中', 8: '结算中',
};
export function getOrderStatusText(status) {
return ORDER_STATUS_MAP[status] || '未知状态';
}
+362
View File
@@ -0,0 +1,362 @@
// utils/base-page.js - 页面公共框架
import request from './request.js';
import popupService from '../services/popupService.js';
import {
isRoleStatusActive,
isCenterPageActive,
syncRoleStatuses,
syncProfileFields,
syncGroupFields,
ensureRoleOnCenterPage,
clearCacheAndEnterNormal,
refreshTabBar,
} from './role-tab-bar.js';
import { lockPrimaryRole, migrateLegacyCenterRole, enterLockedRole } from './primary-role.js';
import { resolveAvatarFromStorage } from './avatar.js';
import { isApiSuccess, getApiMsg, extractUserData, parseSceneOptions } from './api-helper.js';
// ===== 基类数据 =====
const BASE_DATA = {
isLoading: false,
lastRefreshTime: 0,
canRefresh: true,
};
// ===== 基类方法 =====
const BASE_METHODS = {
// ---------- 通知组件 ----------
registerNotificationComponent() {
const app = getApp();
const comp = this.selectComponent('#global-notification');
if (comp && comp.showNotification) {
app.globalData.globalNotification = {
show: (data) => comp.showNotification(data),
hide: () => comp.hideNotification(),
};
}
},
// ---------- 弹窗清理 ----------
cleanupPopup() {
const comp = this.selectComponent('#popupNotice');
if (comp && comp.cleanup) comp.cleanup();
},
// ---------- 冷启动弹窗 ----------
checkColdStartPopup(pageId) {
const app = getApp();
if (!app.globalData.hasShownPopupOnColdStart) {
app.globalData.hasShownPopupOnColdStart = true;
setTimeout(() => popupService.checkAndShow(this, pageId), 300);
}
},
// ---------- 头像加载 ----------
loadAvatar() {
this.setData({ avatarUrl: resolveAvatarFromStorage(getApp()) });
},
// ---------- 通用用户信息加载 ----------
loadUserInfo() {
const app = getApp();
const { nick, uid, avatarUrl } = (app.getUserProfileForDisplay && app.getUserProfileForDisplay()) || {};
this.setData({
avatarUrl: avatarUrl || resolveAvatarFromStorage(app),
uid: uid || wx.getStorageSync('uid') || '',
nicheng: nick || wx.getStorageSync('nicheng') || '',
});
},
// ---------- 通用数据加载 ----------
async loadData(opts) {
const {
url, method = 'POST', data: reqData,
fields, loadingKey = 'isLoading',
successCode = 200, errorMsg = '加载失败',
onSuccess, onError,
} = opts;
this.setData({ [loadingKey]: true });
try {
const res = await request({ url, method, data: reqData });
if (res && (res.data.code === successCode || (successCode === 0 && res.data.code === 0))) {
const resData = res.data.data || {};
if (fields) {
const updateData = {};
Object.keys(fields).forEach((pageKey) => {
const apiField = fields[pageKey];
updateData[pageKey] = apiField in resData ? resData[apiField] : (typeof apiField === 'object' ? apiField.default : '');
});
this.setData({ ...updateData, [loadingKey]: false });
}
if (onSuccess) onSuccess(resData, res);
return resData;
}
throw new Error(res?.data?.msg || res?.data?.message || errorMsg);
} catch (error) {
this.setData({ [loadingKey]: false });
if (onError) {
onError(error);
} else {
wx.showToast({ title: error.message || errorMsg, icon: 'none' });
}
return null;
}
},
// ---------- 节流刷新 ----------
throttledRefresh(fn, interval = 3000) {
const now = Date.now();
if (!this.data.canRefresh || (now - this.data.lastRefreshTime < interval)) {
wx.showToast({ title: '操作过快', icon: 'none' });
return;
}
this.setData({ lastRefreshTime: now, canRefresh: false });
fn.call(this);
setTimeout(() => this.setData({ canRefresh: true }), interval);
},
// ---------- 复制 UID ----------
copyUid() {
const uid = this.data.uid;
if (!uid) {
wx.showToast({ title: 'UID不存在', icon: 'none' });
return;
}
wx.setClipboardData({
data: uid,
success: () => wx.showToast({ title: '已复制UID', icon: 'success' }),
fail: () => wx.showToast({ title: '复制失败', icon: 'none' }),
});
},
// ---------- 清除缓存 ----------
clearCache() {
wx.showModal({
title: '清除缓存',
content: '将清除所有登录与身份数据,并回到点单端,确定继续?',
confirmText: '确定',
cancelText: '取消',
success: (r) => { if (r.confirm) clearCacheAndEnterNormal(this); },
});
},
// ---------- 返回点单端 ----------
switchToNormal() {
lockPrimaryRole('normal');
wx.reLaunch({ url: '/pages/mine/mine' });
},
// ---------- 客服 ----------
goToKefu() {
const app = getApp();
const { link, enterpriseId } = app.globalData.kefuConfig || {};
if (!link || !enterpriseId) {
wx.showToast({ title: '客服配置未加载', icon: 'none' });
return;
}
wx.openCustomerServiceChat({ extInfo: { url: link }, corpId: enterpriseId });
},
// ---------- API 辅助(快捷访问) ----------
_isApiSuccess: isApiSuccess,
_getApiMsg: getApiMsg,
_extractUserData: extractUserData,
// ---------- 邀请码注册通用流程 ----------
async registerWithInviteCode(opts) {
const {
inviteCode, apiUrl, role, statusKey,
alreadyMsg, successMsg = '注册成功',
onSuccess,
} = opts;
if (!inviteCode) {
wx.showToast({ title: '请输入邀请码', icon: 'none' });
return;
}
if (inviteCode.length > 100) {
wx.showToast({ title: '邀请码不能超过100字符', icon: 'none' });
return;
}
this.setData({ isLoading: true });
try {
const res = await request({ url: apiUrl, method: 'POST', data: { inviteCode } });
const body = res?.data || {};
if (isApiSuccess(body)) {
const userData = extractUserData(body);
syncRoleStatuses(userData);
syncProfileFields(userData);
syncGroupFields(userData);
if (userData.token) {
wx.setStorageSync('token', userData.token);
getApp().globalData.token = userData.token;
}
if (!Object.prototype.hasOwnProperty.call(userData, statusKey) &&
!isRoleStatusActive(wx.getStorageSync(statusKey))) {
wx.setStorageSync(statusKey, 1);
getApp().globalData[statusKey] = 1;
}
if (onSuccess) {
await onSuccess(userData, body);
}
this.setData({ isLoading: false });
wx.showToast({ title: successMsg, icon: 'success' });
} else {
const msg = getApiMsg(body);
if (alreadyMsg && msg && msg.includes(alreadyMsg)) {
if (onSuccess) {
await onSuccess(extractUserData(body), body);
}
this.setData({ isLoading: false });
wx.showToast({ title: msg, icon: 'success' });
} else {
this.setData({ isLoading: false });
wx.showToast({ title: msg || '注册失败', icon: 'none' });
}
}
} catch (error) {
this.setData({ isLoading: false });
wx.showToast({ title: error.message || '注册失败', icon: 'none' });
}
},
// ---------- 邀请码 + 登录检查 ----------
handleInviteCodeWithLoginCheck(opts) {
const { inviteCode, role, statusKey, alreadyMsg, onNeedLogin, onAlreadyRegistered } = opts;
const token = wx.getStorageSync('token');
const uid = wx.getStorageSync('uid');
if (!token || !uid) {
if (onNeedLogin) onNeedLogin(inviteCode);
return;
}
const isActive = isRoleStatusActive(wx.getStorageSync(statusKey));
if (!isActive) {
wx.showLoading({ title: '注册中...', mask: true });
this.registerWithInviteCode(opts);
} else {
if (onAlreadyRegistered) onAlreadyRegistered();
else wx.showToast({ title: alreadyMsg || '您已注册', icon: 'success' });
}
},
// ---------- 微信登录 + 注册 ----------
loginAndRegisterWithInviteCode(opts) {
const { inviteCode, onLoginSuccess } = opts;
wx.showLoading({ title: '登录注册中...', mask: true });
wx.login({
success: (loginRes) => {
if (loginRes.code) {
if (onLoginSuccess) onLoginSuccess(loginRes.code, inviteCode);
} else {
wx.hideLoading();
wx.showToast({ title: '微信登录失败', icon: 'none' });
}
},
fail: () => {
wx.hideLoading();
wx.showToast({ title: '微信登录失败', icon: 'none' });
},
});
},
// ---------- 图片 URL 初始化 ----------
initImageUrls(urlMap) {
const ossImageUrl = getApp().globalData.ossImageUrl || '';
const result = {};
Object.keys(urlMap).forEach((key) => {
const path = urlMap[key];
result[key] = path.startsWith('http') || path.startsWith('/') ? path : ossImageUrl + path;
});
return result;
},
};
// ===== 角色中心页增强 =====
function createRoleCenterMixin(roleConfig) {
const { role, statusKey, dataFlag, pageId, apiUrl } = roleConfig;
return {
onReady() {
if (isCenterPageActive(this, dataFlag, statusKey)) {
ensureRoleOnCenterPage(this, role);
}
},
onHide() {
this.cleanupPopup();
},
checkRoleStatus() {
try {
const status = wx.getStorageSync(statusKey);
const uid = wx.getStorageSync('uid');
if (isRoleStatusActive(status)) {
this.setData({ [dataFlag]: true, uid: uid || '' });
this.loadAvatar();
if (this.checkGlobalData) this.checkGlobalData();
ensureRoleOnCenterPage(this, role);
} else {
this.setData({ [dataFlag]: false });
}
} catch (error) {
this.setData({ [dataFlag]: false });
}
},
};
}
/**
* 创建页面配置(合并基类方法)
* @param {Object} pageConfig - 页面特有配置
* @param {Object} [options] - 可选配置
* @param {Object} [options.roleConfig] - 角色中心页配置 { role, statusKey, dataFlag, pageId, apiUrl }
* @returns {Object} 完整的 Page 配置
*/
export function createPage(pageConfig, options = {}) {
const { roleConfig } = options;
// 合并 data
const data = { ...BASE_DATA, ...(pageConfig.data || {}) };
// 合并方法
const methods = { ...BASE_METHODS };
// 如果是角色中心页,混入角色相关方法
if (roleConfig) {
Object.assign(methods, createRoleCenterMixin(roleConfig));
}
// 页面特有方法覆盖基类方法
const allMethods = { ...methods, ...pageConfig };
// 确保 data 正确
allMethods.data = data;
return allMethods;
}
// 导出工具函数供页面直接使用
export {
request,
isRoleStatusActive,
isCenterPageActive,
syncRoleStatuses,
syncProfileFields,
syncGroupFields,
ensureRoleOnCenterPage,
clearCacheAndEnterNormal,
refreshTabBar,
lockPrimaryRole,
migrateLegacyCenterRole,
enterLockedRole,
isApiSuccess,
getApiMsg,
extractUserData,
parseSceneOptions,
};