diff --git a/app.js b/app.js index f9d7d0a..be37b63 100644 --- a/app.js +++ b/app.js @@ -3,6 +3,11 @@ import GoEasy from './static/lib/goeasy-2.13.24.esm.min'; const ChatCore = require('./utils/chat-core'); import { ensurePhoneAuth } from './utils/phone-auth'; import { getPrimaryRole, lockPrimaryRole, migrateLegacyCenterRole, PRIMARY_DEFAULT_PAGES } from './utils/primary-role'; +import { setClubId, getConfiguredClubId, buildClubHeaders, getClubId } from './utils/club-context'; +import { CLUB_ID, WX_APP_ID } from './config/club-config'; +import { applyMiniappAssetsFromConfig, warmupPindaoConfig } from './utils/miniapp-icons.js'; +import { refreshDashouMembership } from './utils/dashou-profile.js'; +const { getDefaultAvatarUrl, resolveAvatarFromStorage } = require('./utils/avatar.js'); // 三端固定首页 const roleDefaultPage = PRIMARY_DEFAULT_PAGES; @@ -36,7 +41,8 @@ App({ guanshiqun: '', guanshiqunid: '', - appId:'wx0e4be86faac4a8d1', + appId: WX_APP_ID, + clubId: CLUB_ID, shangjiastatus: 0, dashoustatus: 0, @@ -49,11 +55,7 @@ App({ yajin: null, chenghao: '', jinfen: null, - clumber: [{ - huiyuanid: '', - huiyuanming: '', - daoqi: '' - }], + clumber: [], chengjiaoliang: null, zaixianZhuangtai: null, @@ -80,6 +82,8 @@ App({ xshenfen: 1, goEasyConfig: null, + chatEnabled: false, + groupInfoMap: {}, kefuConfig: { link: '', enterpriseId: '' @@ -151,6 +155,7 @@ App({ pageState: { currentPage: '', isInChatPage: false, + isInGroupChat: false, currentChatId: '', lastPageUpdate: 0 }, @@ -159,11 +164,23 @@ App({ debugMode: false, currentUser: null, currentRole: 'normal', - primaryRole: 'normal' - }, + primaryRole: 'normal', + /** 客服浮钮:full | mini,onLaunch 重置为 full */ + kefuFloatMode: 'full', + /** 本次启动内隐藏客服浮钮(不持久化,杀进程后恢复) */ + kefuFloatSessionHidden: false, + kefuFloatY: null, + /** 从「我的」跳抢单页时高亮指定单 */ + _acceptOrderHighlight: '', + }, // 核心启动流程 async onLaunch() { + this.globalData.kefuFloatMode = 'full'; + this.globalData.kefuFloatSessionHidden = false; + try { wx.removeStorageSync('kefuFloatPermanentHidden'); } catch (e) {} + setClubId(getConfiguredClubId(), this); + this.globalData.clubId = CLUB_ID; // ① 隐藏返回首页按钮 wx.hideHomeButton(); wx.onAppRoute((res) => { @@ -193,6 +210,11 @@ App({ if (!phoneOk) return; } + // ②c 打手端冷启动:同步会员/押金等到 globalData(抢单页依赖 clumber) + if (wx.getStorageSync('token') && savedRole === 'dashou') { + refreshDashouMembership(this).catch(() => {}); + } + const targetPage = roleDefaultPage[savedRole] || '/pages/index/index'; if (savedRole !== 'normal') { setTimeout(() => { @@ -214,13 +236,11 @@ App({ this.emitEvent('staffContextChanged', {}); } - // ⑤ 建立连接 - const saved = this.getSavedConnection(); - console.log('【启动】缓存身份:', saved ? saved.identityType : '无', - 'userId:', saved ? saved.userId : '无'); + // ⑤ 建立连接(文赫式:全局监听,不反复 disconnect) setTimeout(() => { - if (this.startImWhenReady) this.startImWhenReady(); - else if (this.ensureConnection) this.ensureConnection(); + if (this.globalData.chatEnabled && this.ensureConnection) { + this.ensureConnection(); + } }, 300); // ⑥ 获取远程配置 @@ -228,10 +248,16 @@ App({ .then(latestConfig => { this.saveConfigToStorage(latestConfig); this.applyDynamicConfig(latestConfig); + warmupPindaoConfig(this); + try { + const { fetchGonggaoLunbo } = require('./utils/display-config'); + fetchGonggaoLunbo(this, 'accept_order').catch(() => {}); + } catch (e) {} this.initGoEasyWithConfig(); this.initCurrentUser(); - if (this.startImWhenReady) this.startImWhenReady(); - else if (this.connectForCurrentRole) this.connectForCurrentRole(); + if (this.globalData.chatEnabled && this.ensureConnection) { + this.ensureConnection(); + } console.log('远程配置更新完成'); }) .catch(err => { @@ -239,10 +265,25 @@ App({ }); }, - /** 从后台切回前台时再次向后端校验 */ + /** 从后台切回前台时再次向后端校验,并维持 IM 连接 */ async onShow() { if (!wx.getStorageSync('token')) return; await ensurePhoneAuth({ redirect: true }); + if (this.globalData.chatEnabled && typeof this.startImWhenReady === 'function') { + this.startImWhenReady(); + } else if (this.globalData.chatEnabled && typeof this.ensureConnection === 'function') { + this.globalData.goEasyConnection.autoReconnect = true; + if (typeof this.restoreTabBarBadge === 'function') { + this.restoreTabBarBadge(); + } + this.ensureConnection(); + if (typeof this.syncConnectionStatus === 'function') { + setTimeout(() => this.syncConnectionStatus(), 800); + } + if (typeof this.loadConversations === 'function') { + setTimeout(() => this.loadConversations(), 300); + } + } }, // 连接管理方法 @@ -258,11 +299,16 @@ App({ }, // 配置缓存读写 - CONFIG_CACHE_KEY: 'app_dynamic_config', + CONFIG_CACHE_KEY_PREFIX: 'app_dynamic_config_', + + getConfigCacheKey() { + const cid = getClubId(this) || CLUB_ID; + return this.CONFIG_CACHE_KEY_PREFIX + cid; + }, readConfigFromStorage() { try { - const data = wx.getStorageSync(this.CONFIG_CACHE_KEY); + const data = wx.getStorageSync(this.getConfigCacheKey()); if (data && typeof data === 'object') { return data; } @@ -273,7 +319,7 @@ App({ saveConfigToStorage(config) { try { if (config && typeof config === 'object') { - wx.setStorageSync(this.CONFIG_CACHE_KEY, config); + wx.setStorageSync(this.getConfigCacheKey(), config); } } catch (e) {} }, @@ -284,7 +330,7 @@ App({ wx.request({ url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq', method: 'POST', - header: { 'content-type': 'application/json' }, + header: buildClubHeaders({ 'content-type': 'application/json' }), success: (res) => { if (res.statusCode === 200 && res.data && res.data.code === 0 && res.data.data) { resolve(res.data.data); @@ -307,7 +353,7 @@ App({ wx.request({ url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq', method: 'POST', - header: { 'content-type': 'application/json' }, + header: buildClubHeaders({ 'content-type': 'application/json' }), success: (res) => { if (res.statusCode === 200 && res.data && res.data.code === 0) { resolve(res.data.data); @@ -350,6 +396,19 @@ App({ enterpriseId: config.kefu.enterpriseId || '' }; } + + try { + applyMiniappAssetsFromConfig(this, config); + } catch (e) { + console.warn('miniapp assets apply failed', e); + } + + if (this.emitEvent) { + this.emitEvent('configApplied', { + ossImageUrl: this.globalData.ossImageUrl, + morentouxiang: this.globalData.morentouxiang, + }); + } }, // 仅在有效 appkey 时初始化 @@ -373,9 +432,10 @@ App({ modules: ['im', 'pubsub'] }); wx.GoEasy = GoEasy; + this.globalData.chatEnabled = true; console.log('GoEasy 初始化成功'); - if (this.startImWhenReady && wx.getStorageSync('uid')) { - setTimeout(() => this.startImWhenReady(), 100); + if (this.ensureConnection && wx.getStorageSync('uid')) { + setTimeout(() => this.ensureConnection(), 100); } } catch (error) { console.error('GoEasy 初始化失败:', error); @@ -385,10 +445,12 @@ App({ initCurrentUser() { const uid = wx.getStorageSync('uid'); if (uid) { + const def = getDefaultAvatarUrl(this); + const avatar = resolveAvatarFromStorage(this) || def; this.globalData.currentUser = { id: uid, - name: '用户' + uid.substring(0, 6), - avatar: this.globalData.ossImageUrl + this.globalData.morentouxiang + name: wx.getStorageSync('nicheng') || ('用户' + uid.substring(0, 6)), + avatar: avatar || def, }; } }, diff --git a/app.json b/app.json index 7dba254..6f42352 100644 --- a/app.json +++ b/app.json @@ -2,82 +2,252 @@ "pages": [ "pages/index/index", "pages/category/category", - "pages/pick-user/pick-user", "pages/messages/messages", "pages/mine/mine", - "pages/product-detail/product-detail", - "pages/submit/submit", - "pages/edit/edit", - "pages/fighter/fighter", - "pages/merchant/merchant", - "pages/merchant-home/merchant-home", - "pages/merchant-kefu-key/merchant-kefu-key", - "pages/merchant-kefu-list/merchant-kefu-list", - "pages/staff-join/staff-join", - "pages/merchant-staff-audit/merchant-staff-audit", - "pages/merchant-staff-role/merchant-staff-role", - "pages/manager/manager", - "pages/orders/orders", - "pages/order-detail/order-detail", - "pages/fighter-rules/fighter-rules", - "pages/fighter-edit/fighter-edit", - "pages/fighter-rank/fighter-rank", - "pages/fighter-recharge/fighter-recharge", - "pages/fighter-msg/fighter-msg", - "pages/fighter-orders/fighter-orders", "pages/accept-order/accept-order", - "pages/withdraw/withdraw", - "pages/invite-fighter/invite-fighter", - "pages/my-fighter/my-fighter", - "pages/recharge-log/recharge-log", - "pages/manager-rank/manager-rank", - "pages/merchant-dispatch/merchant-dispatch", + "pages/fighter-orders/fighter-orders", + "pages/fighter/fighter", + "pages/merchant-home/merchant-home", "pages/merchant-orders/merchant-orders", - "pages/merchant-msg/merchant-msg", - "pages/merchant-rank/merchant-rank", - "pages/merchant-recharge/merchant-recharge", - "pages/merchant-order-detail/merchant-order-detail", - "pages/fighter-order-detail/fighter-order-detail", - "pages/chat/chat", - "pages/express-order/express-order", - "pages/order-pool/order-pool", - "pages/poster/poster", - "pages/gold-fighter/gold-fighter", - "pages/leader/leader", - "pages/manager-assign/manager-assign", - "pages/leader-bonus-log/leader-bonus-log", - "pages/invite-manager/invite-manager", - "pages/order-pool2/order-pool2", - "pages/group-chat/group-chat", - "pages/cs-chat/cs-chat", - "pages/verify/verify", + "pages/merchant/merchant", "components/global-notification/global-notification", "components/popup-notice/popup-notice", "components/order-card/order-card", "components/order-sender/order-sender", - "pages/penalty/penalty/penalty", - "pages/penalty/components/fakuan-list/fakuan-list", - "pages/penalty/components/jifen-list/jifen-list", - "pages/penalty/components/fakuan-pay/fakuan-pay", - "pages/phone-auth/phone-auth", - "components/chenghao-tag/chenghao-tag", - "pages/assessor/assessor", - "pages/assess-score/assess-score", - "pages/assess-log/assess-log", - "pages/assess-center/assess-center", - "pages/assess-gold/assess-gold", - "pages/escort-orders/escort-orders", - "pages/withdraw/components/mode1/mode1", - "pages/withdraw/components/mode2/mode2" - - + "components/chenghao-tag/chenghao-tag" + ], + "subPackages": [ + { + "root": "pages/dashouduan", + "pages": ["dashouduan"] + }, + { + "root": "pages/guanshiduan", + "pages": ["guanshiduan"] + }, + { + "root": "pages/pick-user", + "pages": ["pick-user"] + }, + { + "root": "pages/product-detail", + "pages": ["product-detail"] + }, + { + "root": "pages/submit", + "pages": ["submit"] + }, + { + "root": "pages/edit", + "pages": ["edit"] + }, + { + "root": "pages/merchant-kefu-key", + "pages": ["merchant-kefu-key"] + }, + { + "root": "pages/merchant-kefu-list", + "pages": ["merchant-kefu-list"] + }, + { + "root": "pages/staff-join", + "pages": ["staff-join"] + }, + { + "root": "pages/merchant-staff-audit", + "pages": ["merchant-staff-audit"] + }, + { + "root": "pages/merchant-staff-role", + "pages": ["merchant-staff-role"] + }, + { + "root": "pages/manager", + "pages": ["manager"] + }, + { + "root": "pages/orders", + "pages": ["orders"] + }, + { + "root": "pages/order-detail", + "pages": ["order-detail"] + }, + { + "root": "pages/fighter-rules", + "pages": ["fighter-rules"] + }, + { + "root": "pages/fighter-edit", + "pages": ["fighter-edit"] + }, + { + "root": "pages/fighter-rank", + "pages": ["fighter-rank"] + }, + { + "root": "pages/fighter-recharge", + "pages": ["fighter-recharge", "fighter-deposit"] + }, + { + "root": "pages/fighter-ledger", + "pages": ["fighter-ledger"] + }, + { + "root": "pages/fighter-msg", + "pages": ["fighter-msg"] + }, + { + "root": "pages/dashou-exam", + "pages": ["dashou-exam"] + }, + { + "root": "pages/withdraw", + "pages": ["withdraw", "withdraw-records", "components/mode1/mode1", "components/mode2/mode2"] + }, + { + "root": "pages/invite-fighter", + "pages": ["invite-fighter"] + }, + { + "root": "pages/my-fighter", + "pages": ["my-fighter"] + }, + { + "root": "pages/recharge-log", + "pages": ["recharge-log"] + }, + { + "root": "pages/manager-rank", + "pages": ["manager-rank"] + }, + { + "root": "pages/merchant-data-stats", + "pages": ["merchant-data-stats"] + }, + { + "root": "pages/merchant-dispatch", + "pages": ["merchant-dispatch"] + }, + { + "root": "pages/merchant-regular-dispatch", + "pages": ["merchant-regular-dispatch"] + }, + { + "root": "pages/merchant-msg", + "pages": ["merchant-msg"] + }, + { + "root": "pages/merchant-penalty", + "pages": ["merchant-penalty"] + }, + { + "root": "pages/merchant-rank", + "pages": ["merchant-rank"] + }, + { + "root": "pages/merchant-recharge", + "pages": ["merchant-recharge"] + }, + { + "root": "pages/merchant-order-detail", + "pages": ["merchant-order-detail"] + }, + { + "root": "pages/fighter-order-detail", + "pages": ["fighter-order-detail"] + }, + { + "root": "pages/chat", + "pages": ["chat"] + }, + { + "root": "pages/express-order", + "pages": ["express-order"] + }, + { + "root": "pages/order-pool", + "pages": ["order-pool"] + }, + { + "root": "pages/poster", + "pages": ["poster"] + }, + { + "root": "pages/gold-fighter", + "pages": ["gold-fighter"] + }, + { + "root": "pages/leader", + "pages": ["leader"] + }, + { + "root": "pages/manager-assign", + "pages": ["manager-assign"] + }, + { + "root": "pages/leader-bonus-log", + "pages": ["leader-bonus-log"] + }, + { + "root": "pages/invite-manager", + "pages": ["invite-manager"] + }, + { + "root": "pages/group-chat", + "pages": ["group-chat"] + }, + { + "root": "pages/cs-chat", + "pages": ["cs-chat"] + }, + { + "root": "pages/verify", + "pages": ["verify"] + }, + { + "root": "pages/penalty", + "pages": [ + "penalty/penalty", + "components/fakuan-list/fakuan-list", + "components/jifen-list/jifen-list", + "components/fakuan-pay/fakuan-pay" + ] + }, + { + "root": "pages/phone-auth", + "pages": ["phone-auth"] + }, + { + "root": "pages/assessor", + "pages": ["assessor"] + }, + { + "root": "pages/escort-orders", + "pages": ["escort-orders"] + }, + { + "root": "pages/assess-center", + "name": "assess", + "pages": ["assess-center"] + }, + { + "root": "pages/assess-score", + "pages": ["assess-score"] + }, + { + "root": "pages/assess-log", + "pages": ["assess-log"] + }, + { + "root": "pages/assess-gold", + "pages": ["assess-gold"] + } ], - "usingComponents": { "popup-notice": "/components/popup-notice/popup-notice", "tab-bar": "/tab-bar/index" }, - "window": { "navigationBarTextStyle": "black", "navigationStyle": "default", @@ -88,26 +258,33 @@ "tabBar": { "custom": true, "list": [ - - { - "pagePath": "pages/order-pool/order-pool", + "pagePath": "pages/accept-order/accept-order", "text": "接单池", "iconPath": "/images/order-pool.png", "selectedIconPath": "/images/order-pool.png" }, { - "pagePath": "pages/order-pool2/order-pool2", - "text": "接单池", - "iconPath": "/images/order-pool.png", - "selectedIconPath": "/images/order-pool.png" + "pagePath": "pages/fighter-orders/fighter-orders", + "text": "订单", + "iconPath": "/images/orders.png", + "selectedIconPath": "/images/orders.png" + }, + { + "pagePath": "pages/messages/messages", + "text": "消息", + "iconPath": "/images/messages.png", + "selectedIconPath": "/images/messages.png" + }, + { + "pagePath": "pages/fighter/fighter", + "text": "我的", + "iconPath": "/images/mine.png", + "selectedIconPath": "/images/mine.png" } - ] }, - - "style": "v2", "sitemapLocation": "sitemap.json", "lazyCodeLoading": "requiredComponents" -} \ No newline at end of file +} diff --git a/app.wxss b/app.wxss index b58b0e0..58b2d37 100644 --- a/app.wxss +++ b/app.wxss @@ -2,15 +2,6 @@ /* ========== CSS 变量 ========== */ page { - /* 星阙紫色主题 */ - --theme-primary: #9333ea; - --theme-primary-light: #a855f7; - --theme-primary-lighter: #c4b5fd; - --theme-primary-lightest: #ede9fe; - --theme-bg-tint: #f5f3ff; - --theme-gradient: linear-gradient(180deg, #c4b5fd, #a78bfa); - --theme-gradient-bg: linear-gradient(180deg, #c4b5fd 0%, #fff 28%, #f5f3ff 100%); - --color-price: #ff4444; --color-text-main: #333; --color-text-gray: #999; @@ -467,7 +458,7 @@ page { .tag-warning { background: rgba(250, 173, 20, 0.1); - color: #a855f7; + color: #faad14; } .tag-danger { @@ -477,7 +468,7 @@ page { .tag-gold { background: rgba(201, 169, 98, 0.1); - color: #9333ea; + color: #C9A962; } /* ========== 页面容器 ========== */ @@ -524,8 +515,8 @@ page { } .btn-primary--gold { - background: linear-gradient(135deg, #9333ea, #a855f7); - box-shadow: 0 8rpx 20rpx rgba(147, 51, 234, 0.3); + background: linear-gradient(135deg, #C9A962, #D4B56A); + box-shadow: 0 8rpx 20rpx rgba(201, 169, 98, 0.3); } .btn-primary--green { @@ -539,8 +530,8 @@ page { } .btn-primary--orange { - background: linear-gradient(135deg, #a855f7, #7c3aed); - box-shadow: 0 8rpx 20rpx rgba(124, 58, 237, 0.3); + background: linear-gradient(135deg, #ffb347, #ff8c1a); + box-shadow: 0 8rpx 20rpx rgba(255, 140, 26, 0.3); } .btn-primary--purple { @@ -574,8 +565,8 @@ page { } .btn-sm--gold { - background: linear-gradient(135deg, #c4b5fd, #9333ea); - box-shadow: 0 6rpx 16rpx rgba(147, 51, 234, 0.3); + background: linear-gradient(135deg, #ffcc00, #ffaa00); + box-shadow: 0 6rpx 16rpx rgba(255, 170, 0, 0.3); } .btn-sm--green { @@ -613,8 +604,8 @@ page { } .btn-ghost--gold { - color: #9333ea; - border-color: #9333ea; + color: #C9A962; + border-color: #C9A962; } /* 禁用状态 */ @@ -863,8 +854,8 @@ page { } .status-tag--warning { - background: #faf5ff; - color: #a855f7; + background: #fffbe6; + color: #faad14; } .status-tag--info { @@ -971,8 +962,8 @@ page { } @keyframes crownFloat { - 0%, 100% { transform: translateY(0) rotate(5deg) scale(1); filter: drop-shadow(0 0 10rpx rgba(196, 181, 253, 0.8)); } - 50% { transform: translateY(-15rpx) rotate(10deg) scale(1.1); filter: drop-shadow(0 0 25rpx rgba(196, 181, 253, 1)); } + 0%, 100% { transform: translateY(0) rotate(5deg) scale(1); filter: drop-shadow(0 0 10rpx rgba(255, 215, 0, 0.8)); } + 50% { transform: translateY(-15rpx) rotate(10deg) scale(1.1); filter: drop-shadow(0 0 25rpx rgba(255, 215, 0, 1)); } } @keyframes haloRotate { @@ -1112,14 +1103,14 @@ page { } .chat-order-bubble { - background: #f5f3ff; + background: #fff7e0; padding: 12rpx; border-radius: 8rpx; } .chat-order-label { font-size: 24rpx; - color: #a855f7; + color: #e6a23c; font-weight: 600; } @@ -1614,7 +1605,7 @@ page { .rank-card--1 .rank-podium-base { height: 80rpx; } -.rank-podium-gold { background: linear-gradient(180deg, rgba(196, 181, 253, 0.3) 0%, rgba(196, 181, 253, 0.1) 100%); border: 1rpx solid rgba(196, 181, 253, 0.4); } +.rank-podium-gold { background: linear-gradient(180deg, rgba(255, 215, 0, 0.3) 0%, rgba(255, 215, 0, 0.1) 100%); border: 1rpx solid rgba(255, 215, 0, 0.4); } .rank-podium-silver { background: linear-gradient(180deg, rgba(192, 192, 192, 0.3) 0%, rgba(192, 192, 192, 0.1) 100%); border: 1rpx solid rgba(192, 192, 192, 0.4); } .rank-podium-bronze { background: linear-gradient(180deg, rgba(205, 127, 50, 0.3) 0%, rgba(205, 127, 50, 0.1) 100%); border: 1rpx solid rgba(205, 127, 50, 0.4); } @@ -1642,7 +1633,7 @@ page { font-size: 85rpx; position: relative; z-index: 2; - filter: drop-shadow(0 0 10rpx rgba(196, 181, 253, 0.8)); + filter: drop-shadow(0 0 10rpx rgba(255, 215, 0, 0.8)); animation: crownFloat 3s ease-in-out infinite; transform: rotate(5deg); } @@ -1654,7 +1645,7 @@ page { transform: translate(-50%, -50%); width: 200rpx; height: 200rpx; - background: radial-gradient(circle, rgba(196, 181, 253, 0.3) 0%, rgba(196, 181, 253, 0.1) 40%, transparent 70%); + background: radial-gradient(circle, rgba(255, 215, 0, 0.3) 0%, rgba(255, 215, 0, 0.1) 40%, transparent 70%); z-index: 0; animation: glowPulse 2s ease-in-out infinite; } @@ -1686,7 +1677,7 @@ page { .rank-card--1 .rank-avatar-container { border-width: 6rpx; } -.rank-frame-gold .rank-avatar-container { border-color: rgba(196, 181, 253, 0.3); filter: drop-shadow(0 0 30rpx rgba(196, 181, 253, 0.5)); } +.rank-frame-gold .rank-avatar-container { border-color: rgba(255, 215, 0, 0.3); filter: drop-shadow(0 0 30rpx rgba(255, 215, 0, 0.5)); } .rank-frame-silver .rank-avatar-container { border-color: rgba(192, 192, 192, 0.3); filter: drop-shadow(0 0 25rpx rgba(192, 192, 192, 0.5)); } .rank-frame-bronze .rank-avatar-container { border-color: rgba(205, 127, 50, 0.3); filter: drop-shadow(0 0 25rpx rgba(205, 127, 50, 0.5)); } @@ -1710,7 +1701,7 @@ page { animation: haloRotate 10s linear infinite; } -.rank-halo-gold { width: 220rpx; height: 220rpx; background: radial-gradient(circle, rgba(196, 181, 253, 0.4) 0%, rgba(196, 181, 253, 0.2) 30%, transparent 70%); } +.rank-halo-gold { width: 220rpx; height: 220rpx; background: radial-gradient(circle, rgba(255, 215, 0, 0.4) 0%, rgba(255, 215, 0, 0.2) 30%, transparent 70%); } .rank-card--1 .rank-halo-gold { width: 260rpx; height: 260rpx; } .rank-halo-silver { width: 200rpx; height: 200rpx; background: radial-gradient(circle, rgba(192, 192, 192, 0.4) 0%, rgba(192, 192, 192, 0.2) 30%, transparent 70%); } .rank-halo-bronze { width: 200rpx; height: 200rpx; background: radial-gradient(circle, rgba(205, 127, 50, 0.4) 0%, rgba(205, 127, 50, 0.2) 30%, transparent 70%); } @@ -1748,7 +1739,7 @@ page { .rank-card--1 .rank-badge { width: 70rpx; height: 70rpx; font-size: 32rpx; bottom: -15rpx; right: -15rpx; } -.rank-badge-gold { background: linear-gradient(135deg, #c4b5fd, #9333ea); } +.rank-badge-gold { background: linear-gradient(135deg, #ffd700, #ff9800); } .rank-badge-silver { background: linear-gradient(135deg, #c0c0c0, #808080); } .rank-badge-bronze { background: linear-gradient(135deg, #cd7f32, #a0522d); } @@ -1763,7 +1754,7 @@ page { overflow: hidden; } -.rank-card--1 .rank-user-info-card { border-color: rgba(196, 181, 253, 0.3); background: rgba(20, 15, 5, 0.95); } +.rank-card--1 .rank-user-info-card { border-color: rgba(255, 215, 0, 0.3); background: rgba(20, 15, 5, 0.95); } .rank-card--2 .rank-user-info-card { border-color: rgba(192, 192, 192, 0.3); background: rgba(25, 25, 35, 0.95); } .rank-card--3 .rank-user-info-card { border-color: rgba(205, 127, 50, 0.3); background: rgba(30, 20, 10, 0.95); } @@ -1778,7 +1769,7 @@ page { text-align: center; } -.rank-card--1 .rank-user-name { color: #c4b5fd; text-shadow: 0 0 15rpx rgba(196, 181, 253, 0.7); font-size: 34rpx; } +.rank-card--1 .rank-user-name { color: #ffd700; text-shadow: 0 0 15rpx rgba(255, 215, 0, 0.7); font-size: 34rpx; } .rank-card--2 .rank-user-name { color: #c0c0c0; text-shadow: 0 0 10rpx rgba(192, 192, 192, 0.7); } .rank-card--3 .rank-user-name { color: #cd7f32; text-shadow: 0 0 10rpx rgba(205, 127, 50, 0.7); } @@ -1816,7 +1807,7 @@ page { margin-right: 4rpx; } -.rank-card--1 .rank-currency { color: #c4b5fd; } +.rank-card--1 .rank-currency { color: #ffd700; } .rank-card--2 .rank-currency { color: #c0c0c0; } .rank-card--3 .rank-currency { color: #cd7f32; } @@ -1827,7 +1818,7 @@ page { text-shadow: 0 0 10rpx rgba(74, 222, 128, 0.5); } -.rank-card--1 .rank-amount { color: #c4b5fd; text-shadow: 0 0 15rpx rgba(196, 181, 253, 0.7); } +.rank-card--1 .rank-amount { color: #ffd700; text-shadow: 0 0 15rpx rgba(255, 215, 0, 0.7); } .rank-card--2 .rank-amount { color: #c0c0c0; text-shadow: 0 0 10rpx rgba(192, 192, 192, 0.7); } .rank-card--3 .rank-amount { color: #cd7f32; text-shadow: 0 0 10rpx rgba(205, 127, 50, 0.7); } diff --git a/components/chenghao-tag/chenghao-tag.js b/components/chenghao-tag/chenghao-tag.js index b52ed96..37fcf1d 100644 --- a/components/chenghao-tag/chenghao-tag.js +++ b/components/chenghao-tag/chenghao-tag.js @@ -1,73 +1,119 @@ // components/chenghao-tag/chenghao-tag.js const app = getApp(); +const PILL_SHAPES = ['pill', 'rectangle', 'rounded']; + Component({ properties: { mingcheng: { type: String, value: '' }, - texiaoJson: { type: Object, value: {} } + texiaoJson: { type: null, value: null }, }, data: { - // 默认值:宽152,高52,六边形,无背景图,无动画,白色字 - width: 152, - height: 52, - shapeClass: 'liubianxing', // 保证至少不是矩形 + inlineStyle: '', + shapeClass: 'tag-pill', animationClass: '', - bgStyle: '', textColor: '#FFFFFF', textSize: 22, - imageUrl: '' + imageUrl: '', + isPill: true, + }, + observers: { + 'texiaoJson, mingcheng': function () { + this._applyConfig(this.properties.texiaoJson); + }, }, lifetimes: { attached() { - const cfg = this.properties.texiaoJson || {}; - - // 1. 尺寸(稍大一些,一行能放3~4个) - const width = cfg.width || 152; - const height = cfg.height || 52; + this._applyConfig(this.properties.texiaoJson); + }, + }, + methods: { + _applyConfig(raw) { + let cfg = raw || {}; + if (typeof cfg === 'string') { + try { + cfg = JSON.parse(cfg); + } catch (e) { + cfg = {}; + } + } + if (Array.isArray(cfg)) { + cfg = cfg[0] || {}; + } + if (!cfg || typeof cfg !== 'object') { + cfg = {}; + } + const ossImageUrl = app.globalData.ossImageUrl || ''; - // 2. 背景处理:优先背景图,否则用渐变/纯色 + // 无 shape:身份装饰标签 / 自定义色圆角标;有 shape:考核称号等特殊形状 + const isPill = !cfg.shape || PILL_SHAPES.includes(cfg.shape); + + if (isPill) { + const height = Number(cfg.height) || 44; + const minWidth = Number(cfg.width) || 72; + const solidColor = cfg.bg_color || cfg.background || cfg.color; + let bgStyle = ''; + if (cfg.bg_gradient) { + bgStyle = `background: ${cfg.bg_gradient};`; + } else if (solidColor) { + bgStyle = `background: ${solidColor};`; + } else { + bgStyle = 'background: linear-gradient(135deg, #FFD700, #FF8C00);'; + } + const flash = cfg.flash || cfg.animation === 'shine' || cfg.animation === 'glow'; + let animationClass = cfg.animation || ''; + if (flash && !animationClass) { + animationClass = 'pill-flash'; + } + const borderColor = cfg.borderColor || cfg.border_color; + const borderStyle = borderColor ? `border: 2rpx solid ${borderColor};` : ''; + const textColor = cfg.text_color || cfg.textColor || '#FFFFFF'; + this.setData({ + isPill: true, + shapeClass: 'tag-pill', + animationClass, + inlineStyle: `min-width: ${minWidth}rpx; height: ${height}rpx; padding: 0 18rpx; ${bgStyle} ${borderStyle}`, + textColor, + textSize: cfg.text_size || 22, + imageUrl: '', + }); + return; + } + + const width = Number(cfg.width) || 152; + const height = Number(cfg.height) || 52; let bgStyle = ''; let imageUrl = ''; if (cfg.image_url) { - const ossImageUrl = app.globalData.ossImageUrl || ''; - imageUrl = cfg.image_url.startsWith('http') - ? cfg.image_url + imageUrl = cfg.image_url.startsWith('http') + ? cfg.image_url : ossImageUrl + cfg.image_url; } else if (cfg.bg_gradient) { bgStyle = `background: ${cfg.bg_gradient};`; } else if (cfg.bg_color) { bgStyle = `background: ${cfg.bg_color};`; } else { - // 默认紫色渐变 - bgStyle = `background: linear-gradient(135deg, #c4b5fd, #9333ea);`; + bgStyle = 'background: linear-gradient(135deg, #FFD700, #FF8C00);'; } - // 3. 形状(后端传入 shape 字段,默认 liubianxing) - const shapeClass = cfg.shape || 'liubianxing'; - - // 4. 动画 - const animationClass = cfg.animation || ''; - - // 5. 文字 - const textColor = cfg.text_color || '#FFFFFF'; - const textSize = cfg.text_size || 22; - this.setData({ - width, height, - bgStyle, imageUrl, - shapeClass, animationClass, - textColor, textSize + isPill: false, + inlineStyle: `width: ${width}rpx; height: ${height}rpx; ${bgStyle}`, + shapeClass: cfg.shape || 'liubianxing', + animationClass: cfg.animation || '', + textColor: cfg.text_color || '#FFFFFF', + textSize: cfg.text_size || 22, + imageUrl, }); - } - }, - methods: { - // 背景图加载失败时回退为纯色背景 + }, + onImageError() { this.setData({ imageUrl: '' }); - // 如果 bgStyle 为空,给个默认背景 - if (!this.data.bgStyle) { - this.setData({ bgStyle: 'background: linear-gradient(135deg, #c4b5fd, #9333ea);' }); + if (!this.data.inlineStyle.includes('background')) { + this.setData({ + inlineStyle: this.data.inlineStyle + ' background: linear-gradient(135deg, #FFD700, #FF8C00);', + }); } - } - } -}); \ No newline at end of file + }, + }, +}); diff --git a/components/chenghao-tag/chenghao-tag.wxml b/components/chenghao-tag/chenghao-tag.wxml index e9c44f3..ada7d77 100644 --- a/components/chenghao-tag/chenghao-tag.wxml +++ b/components/chenghao-tag/chenghao-tag.wxml @@ -1,20 +1,17 @@ - - - - - - + {{mingcheng}} - \ No newline at end of file + diff --git a/components/chenghao-tag/chenghao-tag.wxss b/components/chenghao-tag/chenghao-tag.wxss index 27bdd1d..6fae7d6 100644 --- a/components/chenghao-tag/chenghao-tag.wxss +++ b/components/chenghao-tag/chenghao-tag.wxss @@ -12,6 +12,18 @@ margin: 4rpx; box-sizing: border-box; } + + /* 身份装饰 / 自定义色:圆角标 */ + .tag-root.tag-pill { + border-radius: 20rpx; + clip-path: none; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.12); + } + + .tag-pill .tag-text { + padding: 0; + text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.25); + } /* ========== 背景图 ========== */ .tag-bg-image { @@ -85,6 +97,21 @@ animation: glow-anim 2s ease-in-out infinite; } @keyframes glow-anim { - 0%, 100% { box-shadow: 0 0 8rpx rgba(196, 181, 253, 0.4); } - 50% { box-shadow: 0 0 20rpx rgba(196, 181, 253, 0.8), 0 0 40rpx rgba(196, 181, 253, 0.4); } + 0%, 100% { box-shadow: 0 0 8rpx rgba(255, 215, 0, 0.4); } + 50% { box-shadow: 0 0 20rpx rgba(255, 215, 0, 0.8), 0 0 40rpx rgba(255, 215, 0, 0.4); } + } + + /* 身份标签闪光 */ + .tag-root.pill-flash::after { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 60%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.55), transparent); + transform: skewX(-20deg); + animation: shine-anim 2.2s infinite; + z-index: 1; + pointer-events: none; } \ No newline at end of file diff --git a/components/global-notification/global-notification.js b/components/global-notification/global-notification.js index 29820d6..139fce1 100644 --- a/components/global-notification/global-notification.js +++ b/components/global-notification/global-notification.js @@ -1,4 +1,4 @@ -const { resolveAvatarUrl } = require('../../utils/avatar.js'); +const { resolveAvatarUrl, getDefaultAvatarUrl } = require('../../utils/avatar.js'); Component({ properties: { @@ -53,7 +53,8 @@ Component({ // 静音状态 isMuted: false, - + formattedTime: '', + // 自动隐藏计时器 autoHideTimeout: null }, @@ -123,7 +124,7 @@ Component({ let avatar = resolveAvatarUrl( data.avatar || data.message?.senderData?.avatar, app - ); + ) || getDefaultAvatarUrl(app); this.setData({ show: true, @@ -131,6 +132,7 @@ Component({ message: data.content || '[消息内容]', avatar: avatar, notificationData: data, + formattedTime: this.formatTime(data.timestamp), progress: 100, swipingClass: '', swipeStyle: '' @@ -149,6 +151,7 @@ Component({ this.clearTimers(); this.setData({ show: false, + formattedTime: '', progress: 100, swipingClass: '', swipeStyle: '' @@ -265,6 +268,13 @@ Component({ this.triggerEvent('close'); this.hideNotification(); }, + + onAvatarError() { + const def = getDefaultAvatarUrl(getApp()); + if (def && this.data.avatar !== def) { + this.setData({ avatar: def }); + } + }, onMute(e) { e.stopPropagation(); diff --git a/components/global-notification/global-notification.wxml b/components/global-notification/global-notification.wxml index d76b58c..3f7cea1 100644 --- a/components/global-notification/global-notification.wxml +++ b/components/global-notification/global-notification.wxml @@ -15,14 +15,15 @@ - + + 新消息 {{title}} {{message}} - - {{formatTime(notificationData ? notificationData.timestamp : '')}} + + {{formattedTime}} diff --git a/components/global-notification/global-notification.wxss b/components/global-notification/global-notification.wxss index fc67be6..80e8f3a 100644 --- a/components/global-notification/global-notification.wxss +++ b/components/global-notification/global-notification.wxss @@ -1,29 +1,40 @@ .global-notification { position: fixed; - left: 20rpx; - right: 20rpx; - top: 0rpx; + left: 24rpx; + right: 24rpx; + top: calc(env(safe-area-inset-top) + 108rpx); z-index: 99999; opacity: 0; - transform: translateY(-120%); - transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55); + transform: translateY(-160%); + transition: all 0.42s cubic-bezier(0.34, 1.2, 0.64, 1); pointer-events: none; overflow: hidden; - border-radius: 24rpx; - background: linear-gradient(135deg, - rgba(41, 47, 61, 0.98) 0%, - rgba(31, 36, 48, 0.98) 100%); - backdrop-filter: blur(30rpx) saturate(180%); - -webkit-backdrop-filter: blur(30rpx) saturate(180%); - border: 1rpx solid rgba(255, 255, 255, 0.08); - box-shadow: - 0 20rpx 60rpx rgba(0, 0, 0, 0.4), - 0 0 0 1rpx rgba(255, 255, 255, 0.03), - inset 0 1rpx 0 rgba(255, 255, 255, 0.1); - min-height: 140rpx; + border-radius: 28rpx; + background: linear-gradient(145deg, + rgba(28, 32, 42, 0.97) 0%, + rgba(22, 26, 36, 0.98) 100%); + backdrop-filter: blur(40rpx) saturate(180%); + -webkit-backdrop-filter: blur(40rpx) saturate(180%); + border: 1rpx solid rgba(255, 255, 255, 0.1); + box-shadow: + 0 24rpx 64rpx rgba(0, 0, 0, 0.45), + 0 0 0 1rpx rgba(255, 255, 255, 0.04), + inset 0 1rpx 0 rgba(255, 255, 255, 0.12); + min-height: 148rpx; touch-action: pan-y; } +.global-notification::before { + content: ''; + position: absolute; + left: 0; + top: 24rpx; + bottom: 24rpx; + width: 6rpx; + border-radius: 0 6rpx 6rpx 0; + background: linear-gradient(180deg, #07c160 0%, #D4AF37 100%); +} + .global-notification.show { opacity: 1; transform: translateY(0); @@ -58,8 +69,8 @@ .notification-content { display: flex; align-items: center; - padding: 32rpx; - min-height: 140rpx; + padding: 28rpx 28rpx 28rpx 36rpx; + min-height: 148rpx; position: relative; } @@ -84,6 +95,18 @@ justify-content: center; } +.notification-tag { + display: inline-block; + align-self: flex-start; + font-size: 20rpx; + color: #07c160; + background: rgba(7, 193, 96, 0.15); + padding: 4rpx 14rpx; + border-radius: 20rpx; + margin-bottom: 8rpx; + font-weight: 500; +} + .notification-title { font-size: 34rpx; font-weight: 600; diff --git a/components/kefu-float/kefu-float.js b/components/kefu-float/kefu-float.js new file mode 100644 index 0000000..3fda074 --- /dev/null +++ b/components/kefu-float/kefu-float.js @@ -0,0 +1,170 @@ +import { openCustomerServiceChat, resolveKefuIconUrl } from '../../utils/kefu-nav.js'; + +const CS_HOUR_START = 8; +const CS_HOUR_END = 23; + +function getCsOnlineStatus() { + const hour = new Date().getHours(); + const online = hour >= CS_HOUR_START && hour < CS_HOUR_END; + return { + csOnline: online, + csHoursText: '8:00-23:00', + csStatusText: online ? '在线' : '在线时间', + }; +} + +Component({ + properties: { + bottom: { type: String, value: '200rpx' }, + }, + + data: { + mode: 'full', + sessionHidden: false, + csUnread: 0, + floatY: 520, + iconUrl: '', + csOnline: false, + csHoursText: '8:00-23:00', + csStatusText: '在线时间', + }, + + lifetimes: { + attached() { + const app = getApp(); + this._syncIcon(); + this._syncFromGlobal(); + this._initFloatY(); + this._convHandler = () => this.syncCsUnread(); + this._configHandler = () => this._syncIcon(); + if (app.on) { + app.on('conversationsUpdated', this._convHandler); + app.on('unreadCountChanged', this._convHandler); + app.on('configApplied', this._configHandler); + } + this.syncCsUnread(); + this._syncOnlineStatus(); + }, + detached() { + const app = getApp(); + if (app.off) { + if (this._convHandler) { + app.off('conversationsUpdated', this._convHandler); + app.off('unreadCountChanged', this._convHandler); + } + if (this._configHandler) { + app.off('configApplied', this._configHandler); + } + } + }, + }, + + pageLifetimes: { + show() { + this._syncIcon(); + this._syncFromGlobal(); + this.syncCsUnread(); + this._syncOnlineStatus(); + }, + }, + + methods: { + _syncOnlineStatus() { + this.setData(getCsOnlineStatus()); + }, + _syncIcon() { + const url = resolveKefuIconUrl(getApp()); + if (url && url !== this.data.iconUrl) { + this.setData({ iconUrl: url }); + } + }, + + _initFloatY() { + const app = getApp(); + if (app.globalData.kefuFloatY != null) { + this.setData({ floatY: app.globalData.kefuFloatY }); + return; + } + try { + const sys = wx.getSystemInfoSync(); + const defaultY = Math.floor((sys.windowHeight || 600) * 0.55); + this.setData({ floatY: defaultY }); + } catch (e) { + this.setData({ floatY: 520 }); + } + }, + + _syncFromGlobal() { + const app = getApp(); + const sessionHidden = !!app.globalData.kefuFloatSessionHidden; + const mode = app.globalData.kefuFloatMode || 'full'; + this.setData({ sessionHidden, mode }); + }, + + onFloatMove(e) { + const y = e.detail && e.detail.y; + if (y == null) return; + const app = getApp(); + app.globalData.kefuFloatY = y; + }, + + syncCsUnread() { + if (this.data.sessionHidden) return; + if (!wx.goEasy?.im?.latestConversations) return; + wx.goEasy.im.latestConversations({ + onSuccess: (res) => { + const list = res.content?.conversations || res.conversations || []; + let unread = 0; + list.forEach((c) => { + if (c.type === 'cs' || c.teamId === 'support_team') { + unread += c.unread || 0; + } + }); + if (unread !== this.data.csUnread) { + this.setData({ csUnread: unread }); + } + }, + onFailed: () => {}, + }); + }, + + onIconError() { + this._syncIcon(); + }, + + onContact() { + openCustomerServiceChat(); + }, + + onHideTap(e) { + if (e && e.stopPropagation) e.stopPropagation(); + const app = getApp(); + app.globalData.kefuFloatMode = 'mini'; + this.setData({ mode: 'mini' }); + }, + + onExpand(e) { + if (e && e.stopPropagation) e.stopPropagation(); + const app = getApp(); + app.globalData.kefuFloatMode = 'full'; + this.setData({ mode: 'full' }); + }, + + onSessionDismiss(e) { + if (e && e.stopPropagation) e.stopPropagation(); + wx.showModal({ + title: '隐藏联系客服', + content: '本次使用期间不再显示此按钮。清空后台重新进入小程序后会再次出现。', + confirmText: '确定隐藏', + cancelText: '取消', + success: (res) => { + if (res.confirm) { + const app = getApp(); + app.globalData.kefuFloatSessionHidden = true; + this.setData({ sessionHidden: true }); + } + }, + }); + }, + }, +}); diff --git a/components/kefu-float/kefu-float.json b/components/kefu-float/kefu-float.json new file mode 100644 index 0000000..a89ef4d --- /dev/null +++ b/components/kefu-float/kefu-float.json @@ -0,0 +1,4 @@ +{ + "component": true, + "usingComponents": {} +} diff --git a/components/kefu-float/kefu-float.wxml b/components/kefu-float/kefu-float.wxml new file mode 100644 index 0000000..bdc6b2d --- /dev/null +++ b/components/kefu-float/kefu-float.wxml @@ -0,0 +1,44 @@ + + + + + + {{csUnread > 99 ? '99+' : csUnread}} + + + + 联系客服 + 有新消息 + {{csStatusText}} {{csHoursText}} + + + + × + + + + 本次不再显示 + + + + + + + + {{csUnread > 99 ? '99+' : csUnread}} + + + + 隐藏 + + + + + diff --git a/components/kefu-float/kefu-float.wxss b/components/kefu-float/kefu-float.wxss new file mode 100644 index 0000000..6cd2c59 --- /dev/null +++ b/components/kefu-float/kefu-float.wxss @@ -0,0 +1,186 @@ +.kefu-movable-area { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 900; +} + +.kefu-movable-view { + width: auto; + height: auto; + pointer-events: auto; + position: absolute; + right: 20rpx; + top: 0; +} + +.kefu-wrap { + display: flex; + flex-direction: column; + align-items: flex-end; + pointer-events: auto; +} + +.kefu-float { + position: relative; +} + +.kefu-float--full { + display: flex; + align-items: stretch; + background: linear-gradient(135deg, #07c160 0%, #06ad56 100%); + border-radius: 999rpx; + box-shadow: 0 8rpx 28rpx rgba(7, 193, 96, 0.45); + border: 2rpx solid rgba(255, 255, 255, 0.85); + overflow: visible; + animation: kefu-pulse 2.4s ease-in-out infinite; +} + +@keyframes kefu-pulse { + 0%, 100% { box-shadow: 0 8rpx 28rpx rgba(7, 193, 96, 0.45); } + 50% { box-shadow: 0 8rpx 36rpx rgba(7, 193, 96, 0.65), 0 0 0 8rpx rgba(7, 193, 96, 0.12); } +} + +.kefu-unread-badge { + position: absolute; + top: -10rpx; + right: -6rpx; + min-width: 36rpx; + height: 36rpx; + line-height: 36rpx; + padding: 0 8rpx; + background: #fa5151; + color: #fff; + font-size: 20rpx; + font-weight: 700; + text-align: center; + border-radius: 18rpx; + border: 3rpx solid #fff; + z-index: 2; +} + +.kefu-unread-badge--mini { + top: -6rpx; + right: -4rpx; +} + +.kefu-float-main { + display: flex; + align-items: center; + padding: 18rpx 24rpx; + flex: 1; +} + +.kefu-float-main:active { + opacity: 0.88; +} + +.kefu-float-icon-img { + width: 44rpx; + height: 44rpx; + margin-right: 12rpx; + flex-shrink: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.95); + padding: 4rpx; +} + +.kefu-text-col { + display: flex; + flex-direction: column; + min-width: 0; +} + +.kefu-float-text { + font-size: 28rpx; + color: #fff; + font-weight: 700; + white-space: nowrap; +} + +.kefu-float-sub { + font-size: 20rpx; + color: rgba(255, 255, 255, 0.92); + margin-top: 2rpx; + font-weight: 600; +} + +.kefu-float-sub--online { + color: #e8ffe8; +} + +.kefu-float-sub--warn { + color: #fff3e0; +} + +.kefu-float-close { + width: 52rpx; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.12); + color: rgba(255, 255, 255, 0.9); + font-size: 34rpx; + line-height: 1; + border-left: 1rpx solid rgba(255, 255, 255, 0.25); + border-radius: 0 999rpx 999rpx 0; +} + +.kefu-float-close:active { + background: rgba(0, 0, 0, 0.2); +} + +.kefu-permanent-row { + display: flex; + align-items: center; + justify-content: flex-end; + margin-top: 6rpx; + padding: 4rpx 10rpx; +} + +.kefu-permanent-row:active { + opacity: 0.75; +} + +.kefu-permanent-row--mini { + margin-top: 4rpx; + padding: 2rpx 8rpx; +} + +.kefu-permanent-label { + font-size: 20rpx; + color: #888; +} + +.kefu-wrap--mini { + align-items: flex-end; +} + +.kefu-float--mini { + width: 96rpx; + height: 96rpx; + padding: 0; + border-radius: 50%; + background: linear-gradient(135deg, #07c160 0%, #06ad56 100%); + box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.45); + border: 3rpx solid #fff; + display: flex; + align-items: center; + justify-content: center; + animation: kefu-pulse 2.4s ease-in-out infinite; +} + +.kefu-float--mini:active { + transform: scale(0.96); +} + +.kefu-float-mini-icon-img { + width: 48rpx; + height: 48rpx; + border-radius: 50%; + background: rgba(255, 255, 255, 0.95); + padding: 4rpx; +} diff --git a/components/order-card/order-card.wxss b/components/order-card/order-card.wxss index 51c76d2..62c2737 100644 --- a/components/order-card/order-card.wxss +++ b/components/order-card/order-card.wxss @@ -15,7 +15,7 @@ .card-id { font-size: 24rpx; color: #999; margin-left: 15rpx; } .cross-tag { margin-left: auto; - background: #8b5cf6; + background: #ff9900; color: #fff; font-size: 20rpx; padding: 2rpx 12rpx; diff --git a/components/order-sender/order-sender.js b/components/order-sender/order-sender.js index f4c9444..5e0ce70 100644 --- a/components/order-sender/order-sender.js +++ b/components/order-sender/order-sender.js @@ -1,16 +1,87 @@ +import request from '../../utils/request'; + const app = getApp(); +function mapIdentityForApi() { + const role = app.globalData.currentRole || wx.getStorageSync('currentRole') || 'normal'; + if (role === 'dashou') return 'dashou'; + if (role === 'shangjia') return 'shangjia'; + return 'normal'; +} + Component({ properties: { - visible: Boolean, - showDetail: { type: Boolean, value: false } + visible: { type: Boolean, value: false }, + groupId: { type: String, value: '' }, + orderId: { type: String, value: '' }, }, - data: {}, + data: { + keyword: '', + loading: false, + orders: [], + filteredOrders: [], + }, + + observers: { + visible(v) { + if (v) { + this.setData({ keyword: '' }); + this.loadOrders(''); + } + }, + }, methods: { close() { this.triggerEvent('close'); - } - } -}); \ No newline at end of file + }, + + onSearchInput(e) { + const keyword = (e.detail.value || '').trim(); + this.setData({ keyword }); + if (this._searchTimer) clearTimeout(this._searchTimer); + this._searchTimer = setTimeout(() => { + this.loadOrders(keyword); + }, 350); + }, + + async loadOrders(keyword) { + const { groupId, orderId } = this.properties; + if (!groupId && !orderId) return; + + this.setData({ loading: true }); + try { + const res = await request({ + url: '/dingdan/ltpddd', + method: 'POST', + data: { + groupId, + dingdan_id: orderId, + keyword: keyword || '', + identityType: mapIdentityForApi(), + }, + }); + const body = res?.data || {}; + const list = body.data?.list || []; + this.setData({ + orders: list, + filteredOrders: list, + loading: false, + }); + } catch (e) { + console.warn('加载历史订单失败', e); + this.setData({ loading: false, orders: [], filteredOrders: [] }); + wx.showToast({ title: '加载订单失败', icon: 'none' }); + } + }, + + onSelectOrder(e) { + const index = e.currentTarget.dataset.index; + const order = this.data.filteredOrders[index]; + if (!order) return; + this.triggerEvent('send', { order }); + this.close(); + }, + }, +}); diff --git a/components/order-sender/order-sender.wxml b/components/order-sender/order-sender.wxml index ae53ed5..3e5dd08 100644 --- a/components/order-sender/order-sender.wxml +++ b/components/order-sender/order-sender.wxml @@ -1,12 +1,28 @@ - 提示 + 选择订单发送 - - 📋 - 此功能暂未开放 - 敬请期待 + + - \ No newline at end of file + + 加载中... + + + + {{item.dingdan_id}} + {{item.zhuangtaiText}} + + {{item.jieshao || '暂无介绍'}} + + ¥{{item.jine || '0'}} + {{item.create_time}} + + 点击发送订单卡片 + + + 暂无相关订单 + + diff --git a/components/order-sender/order-sender.wxss b/components/order-sender/order-sender.wxss index c0ffc48..304f2cd 100644 --- a/components/order-sender/order-sender.wxss +++ b/components/order-sender/order-sender.wxss @@ -1,45 +1,140 @@ .order-sender-mask { - position: fixed; top: 0; left: 0; right: 0; bottom: 0; - background: rgba(0,0,0,0.5); - z-index: 2000; - } - .order-sender { - position: fixed; bottom: 0; left: 0; right: 0; - height: 360rpx; - background: #fff; - border-radius: 24rpx 24rpx 0 0; - z-index: 2001; - transform: translateY(100%); - transition: 0.25s; - display: flex; - flex-direction: column; - } - .order-sender.show { transform: translateY(0); } - - .header { - display: flex; justify-content: space-between; - padding: 24rpx 30rpx; - border-bottom: 1rpx solid #f0f0f0; - } - .title { font-size: 32rpx; font-weight: 600; } - .close { font-size: 44rpx; color: #999; padding: 0 12rpx; } - - .content { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding-bottom: 40rpx; - } - .icon { font-size: 80rpx; margin-bottom: 20rpx; } - .notice { - font-size: 34rpx; - font-weight: 600; - color: #333; - margin-bottom: 10rpx; - } - .sub { - font-size: 24rpx; - color: #999; - } \ No newline at end of file + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 2000; +} + +.order-sender { + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 72vh; + max-height: 900rpx; + background: #fff; + border-radius: 24rpx 24rpx 0 0; + z-index: 2001; + transform: translateY(100%); + transition: transform 0.25s; + display: flex; + flex-direction: column; +} + +.order-sender.show { + transform: translateY(0); +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 24rpx 30rpx; + border-bottom: 1rpx solid #f0f0f0; + flex-shrink: 0; +} + +.title { + font-size: 32rpx; + font-weight: 600; +} + +.close { + font-size: 44rpx; + color: #999; + padding: 0 12rpx; +} + +.search-row { + padding: 16rpx 24rpx; + flex-shrink: 0; +} + +.search-input { + background: #f5f5f5; + border-radius: 32rpx; + padding: 16rpx 28rpx; + font-size: 28rpx; +} + +.order-list { + flex: 1; + height: 0; + padding: 0 24rpx 24rpx; + box-sizing: border-box; +} + +.loading-tip, +.empty-tip { + text-align: center; + color: #999; + font-size: 28rpx; + padding: 60rpx 0; +} + +.order-item { + background: #fafafa; + border-radius: 16rpx; + padding: 20rpx 24rpx; + margin-bottom: 16rpx; + border: 1rpx solid #eee; +} + +.order-item:active { + background: #f0f7ff; +} + +.order-item-top { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8rpx; +} + +.order-id { + font-size: 26rpx; + font-weight: 600; + color: #333; +} + +.order-status { + font-size: 22rpx; + color: #07c160; + background: #e8f8ee; + padding: 4rpx 14rpx; + border-radius: 20rpx; +} + +.order-desc { + font-size: 26rpx; + color: #666; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-bottom: 8rpx; +} + +.order-item-bottom { + display: flex; + justify-content: space-between; + align-items: center; +} + +.order-price { + font-size: 28rpx; + color: #ff6b00; + font-weight: 600; +} + +.order-time { + font-size: 22rpx; + color: #aaa; +} + +.order-send-hint { + display: block; + font-size: 22rpx; + color: #007aff; + margin-top: 10rpx; +} diff --git a/components/pindao-modal/pindao-modal.js b/components/pindao-modal/pindao-modal.js new file mode 100644 index 0000000..c1bbf7a --- /dev/null +++ b/components/pindao-modal/pindao-modal.js @@ -0,0 +1,15 @@ +Component({ + properties: { + visible: { type: Boolean, value: false }, + title: { type: String, value: '频道' }, + channelNo: { type: String, value: '' }, + images: { type: Array, value: [] }, + }, + + methods: { + preventMove() {}, + onClose() { + this.triggerEvent('close'); + }, + }, +}); diff --git a/components/pindao-modal/pindao-modal.json b/components/pindao-modal/pindao-modal.json new file mode 100644 index 0000000..a89ef4d --- /dev/null +++ b/components/pindao-modal/pindao-modal.json @@ -0,0 +1,4 @@ +{ + "component": true, + "usingComponents": {} +} diff --git a/components/pindao-modal/pindao-modal.wxml b/components/pindao-modal/pindao-modal.wxml new file mode 100644 index 0000000..9e7d8a3 --- /dev/null +++ b/components/pindao-modal/pindao-modal.wxml @@ -0,0 +1,24 @@ + + + + {{title}} + × + + + 频道号 + {{channelNo}} + + + 暂无频道内容 + + + + diff --git a/components/pindao-modal/pindao-modal.wxss b/components/pindao-modal/pindao-modal.wxss new file mode 100644 index 0000000..275d9be --- /dev/null +++ b/components/pindao-modal/pindao-modal.wxss @@ -0,0 +1,88 @@ +.pindao-mask { + position: fixed; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.55); + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + padding: 40rpx; + box-sizing: border-box; +} + +.pindao-panel { + width: 100%; + max-height: 80vh; + background: #fff; + border-radius: 24rpx; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.pindao-head { + padding: 28rpx 32rpx 16rpx; + align-items: center; +} + +.pindao-title { + font-size: 32rpx; + font-weight: 600; + color: #222; +} + +.pindao-close { + width: 56rpx; + height: 56rpx; + line-height: 52rpx; + text-align: center; + font-size: 40rpx; + color: #999; +} + +.pindao-channel { + padding: 0 32rpx 20rpx; + text-align: center; +} + +.pindao-channel-label { + display: block; + font-size: 24rpx; + color: #999; + margin-bottom: 8rpx; +} + +.pindao-channel-no { + font-size: 36rpx; + font-weight: 700; + color: #c9a962; +} + +.pindao-scroll { + flex: 1; + max-height: 60vh; + padding: 0 24rpx 24rpx; + box-sizing: border-box; +} + +.pindao-img { + width: 100%; + display: block; + margin-bottom: 16rpx; + border-radius: 12rpx; +} + +.pindao-empty { + text-align: center; + color: #999; + padding: 48rpx 0; + font-size: 28rpx; +} + +.flexb { + display: flex; + justify-content: space-between; +} diff --git a/components/popup-notice/popup-notice.wxss b/components/popup-notice/popup-notice.wxss index 76ac9c5..e19960f 100644 --- a/components/popup-notice/popup-notice.wxss +++ b/components/popup-notice/popup-notice.wxss @@ -138,7 +138,7 @@ .countdown-tip { text-align: center; font-size: 28rpx; - color: #9333ea; + color: #ff6600; margin-bottom: 20rpx; } @@ -223,7 +223,7 @@ height: 120rpx; border-radius: 50%; box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.3); - border: 2rpx solid rgba(196, 181, 253, 0.6); + border: 2rpx solid rgba(255, 215, 0, 0.6); background: #f0f0f0; display: block; transform: translateZ(0); diff --git a/config/club-config.js b/config/club-config.js new file mode 100644 index 0000000..3343581 --- /dev/null +++ b/config/club-config.js @@ -0,0 +1,11 @@ +/** + * 小程序所属俱乐部配置 — 每个小程序工程编译前改这里即可。 + * 星阙 = xq;星之界 = xzj;与 club 表 club_id 一致。 + * 不要依赖后端猜测:前端必须明确所属 club。 + */ +export const CLUB_ID = 'xzj'; + +/** 与 club 表 wx_appid 一致,登录时可传给后端反查 club */ +export const WX_APP_ID = 'wxdefa454152e78a03'; + +export const CLUB_NAME = '星之界电竞'; diff --git a/pages/accept-order/accept-order.js b/pages/accept-order/accept-order.js index 7dea5c6..ca6d6fd 100644 --- a/pages/accept-order/accept-order.js +++ b/pages/accept-order/accept-order.js @@ -1,9 +1,16 @@ // pages/qiangdan/qiangdan.js const app = getApp(); import request from '../../utils/request.js'; +import { normalizeOrderTags } from '../../utils/order-tags.js'; +import { refreshDashouMembership, userHasHuiyuan } from '../../utils/dashou-profile.js'; +import { getDefaultAvatarUrl } from '../../utils/avatar.js'; +import { warmupPindaoConfig } from '../../utils/miniapp-icons.js'; import PopupService from '../../services/popupService.js'; import { reconnectForRole } from '../../utils/role-tab-bar.js'; import { ensurePhoneAuth } from '../../utils/phone-auth.js'; +import { fetchGonggaoLunbo, isGonggaoCacheValid } from '../../utils/display-config.js'; +import { checkPenaltyForGrab } from '../../utils/grab-order-gate.js'; +import { fetchMyZhidingOrders, submitZhidingResponse, processZhidingList, filterZhidingBannerList, ZHIDING_HF_MAP } from '../../utils/zhiding-order.js'; Page({ data: { @@ -30,7 +37,7 @@ Page({ // 4. 刷新控制字段 scrollViewRefreshing: false, lastRefreshTime: 0, - refreshCooldown: 2000, + refreshCooldown: 0, isLoadingMore: false, // 5. 切换类型冷却 @@ -47,13 +54,93 @@ Page({ // 商品轮播展示(只读 globalData,无额外接口) lunboList: [], + gonggao: '', + + examRequired: false, + examPassed: false, + _examChecked: false, + + myZhidingList: [], + highlightOrderId: '', + myZhidingCount: 0, + defaultAvatarUrl: '', + showZhidingDetail: false, + zhidingDetailOrder: null, + showLaterModal: false, + laterOrderId: '', + laterNote: '', }, async onLoad(options) { - this.syncShopBannerFromGlobal(); + const defAvatar = getDefaultAvatarUrl(app); + const highlight = (options && options.highlight) ? String(options.highlight) : ''; + this.setData({ defaultAvatarUrl: defAvatar, highlightOrderId: highlight }); + if (options && options.highlight) { + this._pendingHighlight = String(options.highlight); + } + if (app.globalData._acceptOrderSessionReady && app.globalData._acceptOrderPageCache) { + const cache = { ...app.globalData._acceptOrderPageCache }; + this.data.shangpinleixing = cache.shangpinleixing || []; + const dingdanList = Array.isArray(cache.dingdanList) + ? cache.dingdanList.map((item) => this.processDingdanItem(item)) + : []; + this.setData({ + ...cache, + shangpinleixing: cache.shangpinleixing || [], + dingdanList, + bankuaiBiaoqian: cache.bankuaiBiaoqian || [], + lunboList: cache.lunboList || [], + }); + if ((!cache.lunboList || cache.lunboList.length === 0) && !cache.gonggao && isGonggaoCacheValid(app)) { + const lunboList = this.processLunboUrls(app.globalData.shangpinlunbo); + const gonggao = app.globalData.shangpingonggao || ''; + this.setData({ lunboList, gonggao }); + this.persistPageCache({ lunboList, gonggao }); + } + this.loadGlobalStatus(); + this.registerNotificationComponent(); + await this.syncDashouProfileFromServer(); + if (!this.data.shangpinleixing || this.data.shangpinleixing.length === 0) { + await this.loadShangpinLeixing(true, false, false); + } else if (this.data.xuanzhongLeixingId) { + await this.loadDingdanList(true, true); + } + this.persistPageCache(); + this._skipShowRefresh = true; + await this.loadMyZhidingOrders(true); + return; + } + this.loadGlobalStatus(); - await this.loadShangpinLeixing(); - PopupService.checkAndShow(this, 'jiedan'); + const banner = await this.loadGonggaoAndLunbo(false); + await this.syncDashouProfileFromServer(); + await this.loadShangpinLeixing(true, false, false); + await this.loadMyZhidingOrders(true); + this.persistPageCache(banner); + app.globalData._acceptOrderSessionReady = true; + + if (!app.globalData._acceptOrderPopupDone) { + PopupService.checkAndShow(this, 'jiedan'); + app.globalData._acceptOrderPopupDone = true; + } + this._skipShowRefresh = true; + }, + + persistPageCache(extra = {}) { + const d = this.data; + app.globalData._acceptOrderPageCache = { + shangpinleixing: d.shangpinleixing, + xuanzhongLeixingId: d.xuanzhongLeixingId, + dingdanList: d.dingdanList, + page: d.page, + hasMore: d.hasMore, + bankuaiBiaoqian: d.bankuaiBiaoqian, + xuanzhongBiaoqianId: d.xuanzhongBiaoqianId, + lunboList: extra.lunboList ?? d.lunboList, + gonggao: extra.gonggao ?? d.gonggao, + lastRefreshTime: d.lastRefreshTime, + ossImageUrl: d.ossImageUrl, + }; }, onHide: function () { @@ -64,65 +151,94 @@ Page({ }, async onShow() { - const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' }); - if (!phoneOk) return; + if (!app.globalData._dashouPhoneChecked) { + const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' }); + if (!phoneOk) return; + app.globalData._dashouPhoneChecked = true; + } - this.syncShopBannerFromGlobal(); this.registerNotificationComponent(); - this.loadGlobalStatus(); + await this.syncDashouProfileFromServer(); + warmupPindaoConfig(app); if (wx.getStorageSync('uid')) { - // IM 延后连接,避免切 Tab 时卡顿;订单列表不自动刷新,由用户下拉刷新 - setTimeout(() => { - reconnectForRole('dashou'); - if (app.startImWhenReady) app.startImWhenReady(); - }, 600); + reconnectForRole('dashou'); + if (app.startImWhenReady) app.startImWhenReady(); + } + + await this.checkExamStatus(false); + + this.setData({ defaultAvatarUrl: getDefaultAvatarUrl(app) }); + await this.loadMyZhidingOrders(true); + + const hl = app.globalData._acceptOrderHighlight; + if (hl) { + app.globalData._acceptOrderHighlight = ''; + this.setData({ highlightOrderId: hl }); + await this.loadMyZhidingOrders(true); + } + + if (this._skipShowRefresh) { + this._skipShowRefresh = false; + return; + } + + if (app.globalData._acceptOrderSessionReady && !this._silentRefreshRunning) { + this._silentRefreshRunning = true; + this.silentRefreshOnShow().finally(() => { + this._silentRefreshRunning = false; + }); } }, - /** 同步点单端已加载的商品轮播图,仅展示用 */ - syncShopBannerFromGlobal() { - const oss = app.globalData.ossImageUrl || ''; - const lunboList = (app.globalData.shangpinlunbo || []).map((url) => { + /** 进入页面静默刷新(无 loading 遮罩、不清空列表) */ + async silentRefreshOnShow() { + try { + const banner = await this.loadGonggaoAndLunbo(true); + await this.syncDashouProfileFromServer(); + const leixingId = this.data.xuanzhongLeixingId; + if (leixingId) { + await this.loadDingdanList(true, true); + await this.loadBankuaiBiaoqian(); + } + await this.loadMyZhidingOrders(true); + this.loadGlobalStatus(); + this.persistPageCache(banner); + } catch (e) { + console.error('静默刷新失败:', e); + } + }, + + /** 公告 + 轮播(首次/下拉强制拉接口,其余读 globalData 或页面缓存) */ + processLunboUrls(urlList) { + const oss = app.globalData.ossImageUrl || this.data.ossImageUrl || ''; + return (urlList || []).map((url) => { if (!url) return ''; return url.startsWith('http') ? url : oss + url; }).filter(Boolean); - this.setData({ lunboList }); }, - async refreshDashouProfileSilent() { - try { - const res = await request({ url: '/yonghu/dashouxinxi', method: 'POST' }); - if (!res || res.data.code != 200) return; - const data = res.data.data || {}; - const patch = { - dashouNicheng: data.dashounicheng || '', - zhanghaoStatus: data.zhanghaostatus || '', - dashouzhuangtai: data.dashouzhuangtai || '', - yajin: data.yajin || 0, - jifen: data.jifen || 0, - clumber: data.clumber || [], - }; - Object.assign(app.globalData, { - dashouNicheng: patch.dashouNicheng, - zhanghaoStatus: patch.zhanghaoStatus, - dashouzhuangtai: patch.dashouzhuangtai, - yajin: patch.yajin, - jinfen: patch.jifen, - clumber: patch.clumber, - }); - if (data.dashoustatus !== undefined) { - wx.setStorageSync('dashoustatus', data.dashoustatus); - app.globalData.dashoustatus = data.dashoustatus; + async loadGonggaoAndLunbo(forceFetch = false) { + let lunboRaw = app.globalData.shangpinlunbo || []; + let gonggaoText = app.globalData.shangpingonggao || ''; + + if (forceFetch || !isGonggaoCacheValid(app)) { + try { + const data = await fetchGonggaoLunbo(app, 'accept_order'); + if (data) { + lunboRaw = data.shangpinlunbo || app.globalData.shangpinlunbo || []; + gonggaoText = data.shangpingonggao || app.globalData.shangpingonggao || ''; + } + } catch (e) { + lunboRaw = app.globalData.shangpinlunbo || []; + gonggaoText = app.globalData.shangpingonggao || ''; } - this.setData({ - zhuanghaoStatus: patch.zhanghaoStatus, - dashouzhuangtai: patch.dashouzhuangtai, - huiyuanList: patch.clumber, - yajin: patch.yajin, - jifen: patch.jifen, - }); - } catch (e) {} + } + + const lunboList = this.processLunboUrls(lunboRaw); + const gonggao = gonggaoText; + this.setData({ lunboList, gonggao }); + return { lunboList, gonggao }; }, registerNotificationComponent() { @@ -148,9 +264,22 @@ Page({ }); }, + /** 拉取最新打手信息(会员/押金/积分),并刷新订单卡片上的会员展示 */ + async syncDashouProfileFromServer() { + if (!wx.getStorageSync('token')) return; + await refreshDashouMembership(app); + this.loadGlobalStatus(); + if (this.data.dingdanList && this.data.dingdanList.length) { + this.setData({ + dingdanList: this.data.dingdanList.map((item) => this.processDingdanItem(item)), + }); + } + }, + // 加载商品类型 - async loadShangpinLeixing() { - wx.showLoading({ title: '加载商品类型...' }); + async loadShangpinLeixing(autoLoadOrders = false, preserveSelection = false, showLoading = true) { + if (showLoading) wx.showLoading({ title: '加载商品类型...' }); + const prevId = this.data.xuanzhongLeixingId; try { const res = await request({ url: '/dingdan/dsqdhqddlx', @@ -167,14 +296,16 @@ Page({ else if (Array.isArray(data)) list = data; } const processedList = this.processTupianUrl(list); + const firstId = processedList[0]?.id || null; + const keepPrev = preserveSelection && prevId && processedList.some((i) => i.id === prevId); + const selectedId = keepPrev ? prevId : firstId; this.setData({ shangpinleixing: processedList, - xuanzhongLeixingId: processedList[0]?.id || null + xuanzhongLeixingId: selectedId, }); - if (this.data.xuanzhongLeixingId) { - this.loadDingdanList(true); - // 🆕 加载板块标签 - this.loadBankuaiBiaoqian(); + if (autoLoadOrders && selectedId) { + await this.loadDingdanList(true); + await this.loadBankuaiBiaoqian(); } } else { wx.showToast({ title: res.data?.msg || '加载商品类型失败', icon: 'none' }); @@ -182,7 +313,7 @@ Page({ } catch (error) { wx.showToast({ title: '网络错误,加载失败', icon: 'none' }); } finally { - wx.hideLoading(); + if (showLoading) wx.hideLoading(); } }, @@ -249,8 +380,8 @@ Page({ xuanzhongBiaoqianId: 0 // 重置标签筛选 }); await this.loadDingdanList(true); - // 🆕 切换类型后加载新标签 await this.loadBankuaiBiaoqian(); + this.persistPageCache(); } catch (error) { console.error('切换类型失败:', error); } finally { @@ -270,19 +401,24 @@ Page({ hasMore: true }); await this.loadDingdanList(true); + this.persistPageCache(); }, // 加载订单列表 - async loadDingdanList(isRefresh = false) { - if (this.data.isLoading || !this.data.xuanzhongLeixingId) return; + async loadDingdanList(isRefresh = false, silent = false) { + if ((!silent && this.data.isLoading) || !this.data.xuanzhongLeixingId) return; + if (silent && this._listRequesting) return; const loadPage = isRefresh ? 1 : this.data.page; if (!isRefresh && !this.data.hasMore) return; - this.setData({ - isLoading: true, - isLoadingMore: !isRefresh - }); + if (!silent) { + this.setData({ + isLoading: true, + isLoadingMore: !isRefresh, + }); + } + this._listRequesting = true; try { const res = await request({ @@ -302,6 +438,7 @@ Page({ isLoadingMore: false, scrollViewRefreshing: false }); + this._listRequesting = false; if (res.data.code === 200 || res.data.code === 0) { const newList = res.data.data.list || []; @@ -322,6 +459,7 @@ Page({ page: loadPage, hasMore: hasMore }); + this.persistPageCache(); if (isRefresh) { this.setData({ lastRefreshTime: Date.now() }); @@ -336,25 +474,36 @@ Page({ isLoadingMore: false, scrollViewRefreshing: false }); - wx.showToast({ title: '网络请求失败', icon: 'none' }); + this._listRequesting = false; + if (!silent) { + wx.showToast({ title: '网络请求失败', icon: 'none' }); + } } }, // 处理单条订单数据(包含标签、会员判断) processDingdanItem(item) { const ossUrl = app.globalData.ossImageUrl || ''; + const leixing = this.data.shangpinleixing.find((l) => l.id == item.leixing_id); let fullTupianUrl = ''; - if (item.tupian) { - fullTupianUrl = !item.tupian.startsWith('http') ? ossUrl + item.tupian : item.tupian; + // 卡片左上角优先用订单类型图,没有再退回默认图(不用商品图/头像顶替类型) + if (leixing && leixing.full_tupian_url) { + fullTupianUrl = leixing.full_tupian_url; + } else if (leixing && leixing.tupian_url) { + fullTupianUrl = leixing.tupian_url.startsWith('http') + ? leixing.tupian_url + : ossUrl + leixing.tupian_url; } else { - const leixing = this.data.shangpinleixing.find(l => l.id === item.leixing_id); - fullTupianUrl = leixing ? leixing.full_tupian_url : '/images/default-order.png'; + fullTupianUrl = '/images/default-order.png'; } const isZhiding = item.zhuangtai === 7; + const uid = this.data.uid; + const isMyZhiding = isZhiding && uid && String(item.zhiding_uid) === String(uid); - let zhidingAvatar = ''; + const defAvatar = getDefaultAvatarUrl(app); + let zhidingAvatar = defAvatar; if (item.zhiding_avatar) { zhidingAvatar = !item.zhiding_avatar.startsWith('http') ? ossUrl + item.zhiding_avatar @@ -362,31 +511,41 @@ Page({ } const zhidingNicheng = item.zhiding_nicheng || ''; - // 🆕 判断用户是否有订单所需的会员(优先后端 hasRequiredMember) + // 订单要求指定会员时,核对用户是否持有该会员类型(优先后端字段) let hasRequiredMember = true; if (item.hasRequiredMember !== undefined) { hasRequiredMember = !!item.hasRequiredMember; } else if (item.has_required_member !== undefined) { hasRequiredMember = !!item.has_required_member; } else if (item.yaoqiuleixing == 1 && item.huiyuan_id) { - const userHasIt = this.data.huiyuanList.some(h => - (h.huiyuanid == item.huiyuan_id) || (h.huiyuan_id == item.huiyuan_id) - ); - hasRequiredMember = userHasIt; + hasRequiredMember = userHasHuiyuan(this.data.huiyuanList, item.huiyuan_id); } + let shangjiaAvatar = getDefaultAvatarUrl(app); + const sjAvatar = (item.sj_avatar || '').trim(); + if (sjAvatar) { + shangjiaAvatar = sjAvatar.startsWith('http') ? sjAvatar : ossUrl + sjAvatar; + } + + const tags = normalizeOrderTags(item) + return { ...item, full_tupian_url: fullTupianUrl, + leixing_icon_url: fullTupianUrl, + shangjia_avatar_full: shangjiaAvatar, isZhiding: isZhiding, + isMyZhiding, + isHighlighted: this.data.highlightOrderId && item.dingdan_id === this.data.highlightOrderId, + zhiding_hf: item.zhiding_hf, + zhiding_hf_text: item.zhiding_hf_text || ZHIDING_HF_MAP[item.zhiding_hf] || '', + zhiding_dhj_sm: item.zhiding_dhj_sm || '', isPingtai: item.pingtai == 1, isShangjia: item.pingtai == 2, zhiding_avatar_full: zhidingAvatar, zhiding_nicheng: zhidingNicheng, - // 🆕 三层标签(后端返回,直接保留) - xuqiu_biaoqian: item.xuqiu_biaoqian || [], - dashou_biaoqian: item.dashou_biaoqian || [], - shangjia_biaoqian: item.shangjia_biaoqian || [], + ...tags, + shangjia_youzhi: !!item.shangjia_youzhi, // 🆕 是否有查看价格的权限 hasRequiredMember: hasRequiredMember, }; @@ -399,13 +558,16 @@ Page({ switch (failureType) { case 'huiyuan': - params = { needScroll: '0', scrollTo: 'member' }; + url = '/pages/fighter-recharge/fighter-recharge'; + params = { scrollTo: 'member' }; break; case 'yajin': - params = { needScroll: '1', scrollTo: 'bottom', requiredYajin: requiredYajin }; + url = '/pages/fighter-recharge/fighter-deposit'; + params = { requiredYajin: requiredYajin }; break; case 'jifen': - params = { needScroll: '1', scrollTo: 'bottom' }; + url = '/pages/fighter-recharge/fighter-recharge'; + params = { scrollTo: 'jifen' }; break; } @@ -424,11 +586,63 @@ Page({ }); }, + /** 抢单前考试状态(仅更新状态,不自动跳转考试页) */ + async checkExamStatus() { + try { + const res = await request({ + url: '/jituan/dashou-exam/status', + method: 'POST', + header: { 'content-type': 'application/json' }, + }); + const body = res?.data; + if (body && (body.code === 200 || body.code === 0) && body.data) { + const d = body.data; + this.setData({ + examRequired: !!d.exam_required, + examPassed: !!d.exam_passed, + }); + } + } catch (e) { + console.error('考试状态检查失败', e); + } + return true; + }, + // 抢单按钮(原封不动) async onQiangdanTap(e) { const dingdanItem = e.currentTarget.dataset.item; if (!dingdanItem) return; + const penalty = await checkPenaltyForGrab(); + if (penalty.blocked) { + wx.showToast({ + title: penalty.msg, + icon: 'none', + duration: 2000, + }); + setTimeout(() => { + wx.navigateTo({ url: penalty.url }); + }, 500); + return; + } + + await this.checkExamStatus(); + + if (this.data.examRequired && !this.data.examPassed) { + wx.showModal({ + title: '须通过接单考试', + content: '抢单前需先通过接单考试,是否现在去考试?', + confirmText: '去考试', + cancelText: '暂不', + success: (r) => { + if (r.confirm) { + wx.navigateTo({ url: '/pages/dashou-exam/dashou-exam' }); + } + }, + }); + return; + } + // 校验1: 打手身份 if (!this.data.dashoustatus || this.data.dashoustatus != 1) { wx.showToast({ title: '请先开启接单员身份', icon: 'none' }); @@ -453,9 +667,7 @@ Page({ } // 校验5: 会员要求 if (dingdanItem.yaoqiuleixing == 1) { - const hasHuiyuan = this.data.huiyuanList.some(h => - (h.huiyuanid == dingdanItem.huiyuan_id) || (h.huiyuan_id == dingdanItem.huiyuan_id) - ); + const hasHuiyuan = userHasHuiyuan(this.data.huiyuanList, dingdanItem.huiyuan_id); if (!hasHuiyuan) { wx.showToast({ title: '未开通对应会员,无法抢单', @@ -521,6 +733,8 @@ Page({ item => item.dingdan_id !== dingdanItem.dingdan_id ); that.setData({ dingdanList: newList }); + that.persistPageCache(); + that.loadMyZhidingOrders(true); } else { wx.showToast({ title: qiangdanRes.data.msg || '抢单失败', @@ -551,43 +765,134 @@ Page({ }); }, - onReachBottom() { - const now = Date.now(); - const lastTime = this.data.lastRefreshTime; - const cooldown = this.data.refreshCooldown; + async loadMyZhidingOrders(silent = false) { + if (!this.data.uid && !wx.getStorageSync('uid')) return; + try { + const raw = await fetchMyZhidingOrders(); + const uid = this.data.uid || wx.getStorageSync('uid'); + const highlight = this.data.highlightOrderId || this._pendingHighlight || ''; + const list = filterZhidingBannerList(processZhidingList(raw, app, uid, highlight)); + this.setData({ myZhidingList: list, myZhidingCount: list.length }); + if (highlight && list.some((o) => o.dingdan_id === highlight)) { + this.setData({ highlightOrderId: highlight }); + this._pendingHighlight = ''; + } + } catch (e) { + if (!silent) console.warn('loadMyZhidingOrders', e); + } + }, - if (now - lastTime < cooldown) { - wx.showToast({ - title: `操作太快,请${Math.ceil((cooldown - (now - lastTime)) / 1000)}秒后再试`, - icon: 'none', - duration: 1500 - }); + openZhidingDetail(e) { + const item = e.currentTarget.dataset.item; + if (!item) return; + this.setData({ showZhidingDetail: true, zhidingDetailOrder: item }); + }, + + closeZhidingDetail() { + this.setData({ showZhidingDetail: false, zhidingDetailOrder: null }); + }, + + async onZhidingReject(e) { + const item = e.currentTarget.dataset.item; + if (!item) return; + wx.showModal({ + title: '确认婉拒', + content: '婉拒后订单将退回抢单大厅,确定不想接此单吗?', + confirmText: '不想接', + confirmColor: '#e64340', + success: async (res) => { + if (!res.confirm) return; + wx.showLoading({ title: '提交中...', mask: true }); + try { + const body = await submitZhidingResponse(item.dingdan_id, 2); + wx.hideLoading(); + if (body && (body.code === 200 || body.code === 0)) { + wx.showToast({ title: body.msg || '已婉拒', icon: 'none' }); + this.closeZhidingDetail(); + await this.loadMyZhidingOrders(true); + await this.loadDingdanList(true, true); + } else { + wx.showToast({ title: body?.msg || '操作失败', icon: 'none' }); + } + } catch (err) { + wx.hideLoading(); + wx.showToast({ title: '网络错误', icon: 'none' }); + } + }, + }); + }, + + onZhidingLater(e) { + const item = e.currentTarget.dataset.item; + if (!item) return; + this.setData({ + showZhidingDetail: false, + zhidingDetailOrder: null, + showLaterModal: true, + laterOrderId: item.dingdan_id, + laterNote: item.zhiding_dhj_sm || '', + }); + }, + + closeLaterModal() { + this.setData({ showLaterModal: false, laterOrderId: '', laterNote: '' }); + }, + + onLaterNoteInput(e) { + this.setData({ laterNote: e.detail.value }); + }, + + async confirmZhidingLater() { + const { laterOrderId, laterNote } = this.data; + if (!laterNote.trim()) { + wx.showToast({ title: '请填写预计接待时间', icon: 'none' }); return; } + wx.showLoading({ title: '提交中...', mask: true }); + try { + const body = await submitZhidingResponse(laterOrderId, 3, laterNote.trim()); + wx.hideLoading(); + if (body && (body.code === 200 || body.code === 0)) { + const note = laterNote.trim(); + wx.showToast({ title: `已记录:${note}`, icon: 'none', duration: 3500 }); + this.closeLaterModal(); + this.closeZhidingDetail(); + await this.loadMyZhidingOrders(true); + await this.loadDingdanList(true, true); + } else { + wx.showToast({ title: body?.msg || '操作失败', icon: 'none' }); + } + } catch (e) { + wx.hideLoading(); + wx.showToast({ title: '网络错误', icon: 'none' }); + } + }, + async onZhidingAccept(e) { + const item = e.currentTarget.dataset.item; + if (!item) return; + wx.showLoading({ title: '处理中...', mask: true }); + try { + await submitZhidingResponse(item.dingdan_id, 1); + wx.hideLoading(); + } catch (e) { + wx.hideLoading(); + } + const processed = this.processDingdanItem(item); + this.onQiangdanTap({ currentTarget: { dataset: { item: processed } } }); + }, + + noop() {}, + + onReachBottom() { if (this.data.isLoading || this.data.isLoadingMore) return; - if (this.data.hasMore && !this.data.isLoading && this.data.xuanzhongLeixingId) { this.setData({ page: this.data.page + 1 }); this.loadDingdanList(false); } }, - onPullDownRefresh() { - const now = Date.now(); - const lastTime = this.data.lastRefreshTime; - const cooldown = this.data.refreshCooldown; - - if (now - lastTime < cooldown) { - this.setData({ scrollViewRefreshing: false }); - wx.showToast({ - title: `操作太快,请${Math.ceil((cooldown - (now - lastTime)) / 1000)}秒后再试`, - icon: 'none', - duration: 1500 - }); - return; - } - + async onPullDownRefresh() { if (this.data.isLoading) { this.setData({ scrollViewRefreshing: false }); return; @@ -595,15 +900,26 @@ Page({ this.setData({ scrollViewRefreshing: true, - lastRefreshTime: now, + lastRefreshTime: Date.now(), page: 1, - hasMore: true + hasMore: true, }); - if (this.data.xuanzhongLeixingId) { - this.refreshDashouProfileSilent(); - this.loadDingdanList(true); - } else { + try { + const banner = await this.loadGonggaoAndLunbo(true); + await this.syncDashouProfileFromServer(); + await this.loadShangpinLeixing(false, true, false); + const leixingId = this.data.xuanzhongLeixingId; + if (leixingId) { + await this.loadBankuaiBiaoqian(); + await this.loadDingdanList(true); + } + await this.loadMyZhidingOrders(true); + this.loadGlobalStatus(); + this.persistPageCache(banner); + } catch (e) { + console.error('下拉刷新失败:', e); + } finally { this.setData({ scrollViewRefreshing: false }); } } diff --git a/pages/accept-order/accept-order.json b/pages/accept-order/accept-order.json index d8aa64a..8be2c01 100644 --- a/pages/accept-order/accept-order.json +++ b/pages/accept-order/accept-order.json @@ -1,12 +1,14 @@ { - "navigationBarTitleText": "抢单大厅", - "navigationBarBackgroundColor": "#c4b5fd", + "navigationBarTitleText": "抢单列表", + "navigationBarBackgroundColor": "#f7dc51", + "backgroundColorTop": "#f7dc51", "navigationBarTextStyle": "black", "enablePullDownRefresh": false, "backgroundTextStyle": "dark", - "backgroundColor": "#f5f3ff", + "backgroundColor": "#fff8e1", "usingComponents": { "chenghao-tag": "/components/chenghao-tag/chenghao-tag", - "global-notification": "/components/global-notification/global-notification" + "global-notification": "/components/global-notification/global-notification", + "kefu-float": "/components/kefu-float/kefu-float" } } diff --git a/pages/accept-order/accept-order.wxml b/pages/accept-order/accept-order.wxml index cbad5f6..5dfa720 100644 --- a/pages/accept-order/accept-order.wxml +++ b/pages/accept-order/accept-order.wxml @@ -18,7 +18,7 @@ refresher-default-style="black" - refresher-background="#f5f3ff" + refresher-background="#fff8e1" refresher-triggered="{{scrollViewRefreshing}}" @@ -38,6 +38,38 @@ + + + + {{gonggao}} + + + + + + + 指定给您 + {{myZhidingCount || myZhidingList.length}}单 + + 请确认是否接待 + + + + + 单号 {{item.dingdan_id}} + ¥{{item.dashou_fencheng || 0}} + + {{item.jieshao || '暂无介绍'}} + 您的回复:{{item.zhiding_hf_text}}{{item.zhiding_dhj_sm ? ' · ' + item.zhiding_dhj_sm : ''}} + + + 不想接 + 等会接 + 立即接待 + + + + @@ -50,7 +82,7 @@ indicator-color="rgba(255,255,255,0.6)" - indicator-active-color="#9333ea" + indicator-active-color="#ffd061" autoplay="{{lunboList.length > 1}}" @@ -74,11 +106,11 @@ - + - + @@ -176,7 +208,7 @@ - + @@ -190,16 +222,6 @@ - - - 指定 - - - - {{item.zhiding_nicheng || '指定接单员'}} - - - @@ -212,6 +234,20 @@ + + + + 指定 + + {{item.zhiding_nicheng || '指定接单员'}} + + + + + + + + @@ -226,9 +262,9 @@ - + - 备注:{{item.beizhu}} + 备注:{{item.beizhu || '暂无'}} @@ -236,10 +272,10 @@ 需求标签 - - - - + + + + @@ -248,7 +284,7 @@ 平台派单 - 抢单 + {{item.isMyZhiding ? '立即接待' : '抢单'}} @@ -260,9 +296,84 @@ + + + + + + + + + + + + + {{item.jieshao || '暂无介绍'}} + + 优质商家 + + + + + + + 指定 + + {{item.zhiding_nicheng || '指定接单员'}} + + + + + + + + + + {{item.dashou_fencheng || 0}} + + + 未开通会员 + + + 商家备注:{{item.beizhu || '暂无'}} + + + + + + + + + + + + + + + + + + + + + + + {{item.sjnicheng || '未知商家'}} + 发布于 {{item.creat_time}} + + + {{item.isMyZhiding ? '立即接待' : '抢单'}} + + + 商家派单 + + + + + - + @@ -276,19 +387,23 @@ - - - 指定 - - - - {{item.zhiding_nicheng || '指定接单员'}} - - + + + + 指定 + + {{item.zhiding_nicheng || '指定接单员'}} + + + + + + + - + {{item.sjnicheng || '未知商家'}} @@ -318,24 +433,33 @@ - + - 商家备注:{{item.beizhu}} + 商家备注:{{item.beizhu || '暂无'}} - + - - - + + + + - + - + + + + + + + + + @@ -344,7 +468,7 @@ 商家派单 - 抢单 + {{item.isMyZhiding ? '立即接待' : '抢单'}} @@ -384,8 +508,58 @@ + + + + + + + 指定订单详情 + × + + + 单号{{zhidingDetailOrder.dingdan_id}} + 分成¥{{zhidingDetailOrder.dashou_fencheng || 0}} + 介绍{{zhidingDetailOrder.jieshao || '暂无'}} + 备注{{zhidingDetailOrder.beizhu}} + 您的回复{{zhidingDetailOrder.zhiding_hf_text}} + + + 不想接 + 等会接 + 立即接待 + + + + + + + + + 等会接 + × + + + 请填写预计接待时间,例如「30分钟后」「今晚8点」 + + + 确认 + + + diff --git a/pages/accept-order/accept-order.wxss b/pages/accept-order/accept-order.wxss index 45c1dbe..8ebdb0a 100644 --- a/pages/accept-order/accept-order.wxss +++ b/pages/accept-order/accept-order.wxss @@ -1,5 +1,11 @@ -/* pages/qiangdan/qiangdan.wxss - 高级机甲风格重构版(对称优化) */ +/* pages/qiangdan/qiangdan.wxss - 逍遥梦抢单页 */ @import '../../styles/dashou-xym-theme.wxss'; +@import '../../styles/dashou-xym-order-card.wxss'; + +page { + background-color: #f7dc51; +} + .qiangdan-page { height: 100vh; display: flex; @@ -126,6 +132,7 @@ flex: 1; height: 0; -webkit-overflow-scrolling: touch; + background: transparent; } .scroll-bottom-spacer { @@ -486,7 +493,7 @@ background: rgba(0, 0, 0, 0.3); padding: 8rpx 20rpx; border-radius: 60rpx; - border: 1rpx solid rgba(196, 181, 253, 0.3); + border: 1rpx solid rgba(255, 215, 0, 0.3); } /* 分佣图标占位符,用户可替换为自己图标 */ @@ -504,10 +511,10 @@ .fenyong-price { font-size: 44rpx; - color: #c4b5fd; + color: #ffd700; font-weight: 800; margin-right: 4rpx; - text-shadow: 0 0 20px rgba(196, 181, 253, 0.8); + text-shadow: 0 0 20px rgba(255, 215, 0, 0.8); } .fenyong-unit { @@ -764,4 +771,179 @@ max-width: 50%; } -@import '../../styles/dashou-xym-order-card.wxss'; + /* 被指定订单(星阙功能保留,不影响星之界卡片 UI) */ + .my-zhiding-banner { + margin: 16rpx 24rpx 0; + padding: 22rpx; + border-radius: 20rpx; + background: linear-gradient(135deg, #ff9500 0%, #ff6b00 100%); + border: 3rpx solid #ff4500; + box-shadow: 0 12rpx 32rpx rgba(255, 69, 0, 0.45); + position: relative; + z-index: 20; + } + .my-zhiding-banner-head { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16rpx; + } + .my-zhiding-head-left { + display: flex; + align-items: center; + gap: 12rpx; + } + .my-zhiding-badge { + font-size: 26rpx; + font-weight: 800; + color: #ff4500; + background: #fff; + padding: 8rpx 18rpx; + border-radius: 999rpx; + } + .my-zhiding-count-num { + font-size: 32rpx; + font-weight: 800; + color: #fff; + } + .my-zhiding-sub { + font-size: 26rpx; + color: #fff; + font-weight: 700; + } + .my-zhiding-card { + background: #fff; + border-radius: 16rpx; + padding: 18rpx; + margin-bottom: 12rpx; + border: 2rpx solid #ffe0b2; + } + .my-zhiding-card--highlight { + border-color: #ff8c00; + box-shadow: 0 0 0 4rpx rgba(255, 140, 0, 0.15); + } + .my-zhiding-card-top { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8rpx; + } + .my-zhiding-order-id { + font-size: 24rpx; + color: #666; + } + .my-zhiding-reward { + font-size: 30rpx; + font-weight: 700; + color: #e64340; + } + .my-zhiding-desc { + font-size: 28rpx; + color: #333; + line-height: 1.5; + } + .my-zhiding-status { + display: block; + margin-top: 8rpx; + font-size: 24rpx; + color: #059669; + } + .my-zhiding-actions { + display: flex; + gap: 12rpx; + margin-top: 16rpx; + } + .zhiding-act { + flex: 1; + text-align: center; + padding: 14rpx 0; + border-radius: 999rpx; + font-size: 26rpx; + font-weight: 600; + } + .zhiding-act--ghost { + background: #f3f4f6; + color: #666; + } + .zhiding-act--primary { + background: linear-gradient(90deg, #ff8c00, #ff6b00); + color: #fff; + } + .order-my-zhiding { + border: 3rpx solid #ff4500 !important; + box-shadow: 0 0 0 6rpx rgba(255, 69, 0, 0.25), 0 12rpx 36rpx rgba(255, 69, 0, 0.35) !important; + } + .order-zhiding-highlight { + animation: zhidingPulse 1.5s ease-in-out 2; + } + @keyframes zhidingPulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(255, 140, 0, 0.3); } + 50% { box-shadow: 0 0 0 12rpx rgba(255, 140, 0, 0); } + } + .zhiding-modal-mask { + position: fixed; + left: 0; right: 0; top: 0; bottom: 0; + background: rgba(0, 0, 0, 0.62); + z-index: 10050; + display: flex; + align-items: center; + justify-content: center; + padding: 40rpx; + } + .zhiding-modal-mask--top { + z-index: 10060; + } + .zhiding-modal { + width: 100%; + max-width: 640rpx; + background: #ffffff; + border-radius: 24rpx; + overflow: hidden; + box-shadow: 0 24rpx 64rpx rgba(0, 0, 0, 0.35); + } + .zhiding-modal--sm { max-width: 580rpx; } + .zhiding-modal-head { + display: flex; + justify-content: space-between; + align-items: center; + padding: 24rpx 28rpx; + border-bottom: 1rpx solid #eee; + background: #fff; + } + .zhiding-modal-title { font-size: 32rpx; font-weight: 700; color: #222; } + .zhiding-modal-close { font-size: 44rpx; color: #666; line-height: 1; padding: 0 8rpx; } + .zhiding-modal-body { padding: 24rpx 28rpx; background: #fff; } + .zhiding-modal-row { + display: flex; + justify-content: space-between; + margin-bottom: 16rpx; + font-size: 28rpx; + color: #333; + } + .zhiding-modal-row.col { flex-direction: column; gap: 8rpx; } + .zhiding-modal-row .lbl { color: #999; font-size: 24rpx; } + .zhiding-modal-row .reward { color: #e64340; font-weight: 700; } + .zhiding-modal-actions { padding: 0 28rpx 28rpx; margin-top: 0; background: #fff; } + .later-hint { font-size: 26rpx; color: #444; display: block; margin-bottom: 16rpx; } + .later-input { + width: 100%; + box-sizing: border-box; + border: 2rpx solid #ddd; + border-radius: 12rpx; + padding: 22rpx 20rpx; + font-size: 28rpx; + color: #222; + background: #f9fafb; + min-height: 88rpx; + } + .later-input-ph { color: #aaa; } + .later-confirm { + margin: 0 28rpx 28rpx; + text-align: center; + padding: 24rpx; + background: linear-gradient(90deg, #ff8c00, #ff4500); + color: #fff; + border-radius: 999rpx; + font-size: 30rpx; + font-weight: 700; + } diff --git a/pages/assess-log/assess-log.wxss b/pages/assess-log/assess-log.wxss index 679e76a..1b986f6 100644 --- a/pages/assess-log/assess-log.wxss +++ b/pages/assess-log/assess-log.wxss @@ -132,7 +132,7 @@ page { border-radius: 20rpx; font-weight: 500; } - .status-0 { background: #F3E8FF; color: #9333ea; } + .status-0 { background: #FFF3E0; color: #EF6C00; } .status-1 { background: #E8F5E9; color: #2E7D32; } .status-2 { background: #FFEBEE; color: #C62828; } .status-3 { background: #E3F2FD; color: #1565C0; } diff --git a/pages/category/category.js b/pages/category/category.js index abd8f65..5dffc1a 100644 --- a/pages/category/category.js +++ b/pages/category/category.js @@ -1,6 +1,7 @@ // pages/category/category.js const app = getApp() import { createPage } from '../../utils/base-page.js'; +import { buildClubHeaders } from '../../utils/club-context.js'; Page(createPage({ data: { @@ -114,9 +115,7 @@ Page(createPage({ wx.request({ url: app.globalData.apiBaseUrl + '/shangpin/shangpinhuoqu/', method: 'POST', - header: { - 'content-type': 'application/json' - }, + header: buildClubHeaders({ 'content-type': 'application/json' }), success(res) { if (res.statusCode === 200 && res.data) { const data = res.data diff --git a/pages/category/category.wxss b/pages/category/category.wxss index d5791ab..9ce1023 100644 --- a/pages/category/category.wxss +++ b/pages/category/category.wxss @@ -60,13 +60,13 @@ .search-btn { margin-left: 20rpx; - box-shadow: 0 6rpx 20rpx rgba(147, 51, 234, 0.3); + box-shadow: 0 6rpx 20rpx rgba(255, 170, 0, 0.3); transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); } .search-btn:active { transform: scale(0.95); - box-shadow: 0 4rpx 15rpx rgba(147, 51, 234, 0.4); + box-shadow: 0 4rpx 15rpx rgba(255, 170, 0, 0.4); } .search-btn-text { @@ -127,7 +127,7 @@ transform: translate(-50%, -50%); width: 300rpx; height: 300rpx; - background: radial-gradient(circle, rgba(168, 85, 247, 0.1) 0%, transparent 70%); + background: radial-gradient(circle, rgba(255, 204, 0, 0.1) 0%, transparent 70%); z-index: -1; } @@ -167,8 +167,8 @@ .leixing-active .leixing-image-box { transform: scale(1.12); box-shadow: - 0 18rpx 40rpx rgba(168, 85, 247, 0.3), - 0 8rpx 16rpx rgba(147, 51, 234, 0.2); + 0 18rpx 40rpx rgba(255, 204, 0, 0.3), + 0 8rpx 16rpx rgba(255, 170, 0, 0.2); } .leixing-image { @@ -185,7 +185,7 @@ right: -3rpx; bottom: -3rpx; border-radius: 50%; - background: linear-gradient(135deg, #c4b5fd, #9333ea, #7c3aed); + background: linear-gradient(135deg, #ffcc00, #ffaa00, #ff8800); opacity: 0; transition: opacity 0.4s; z-index: -1; @@ -223,10 +223,10 @@ } .leixing-active .leixing-text { - color: #8b5cf6; + color: #ff9900; font-weight: 600; transform: translateY(-2rpx); - text-shadow: 0 2rpx 4rpx rgba(168, 85, 247, 0.2); + text-shadow: 0 2rpx 4rpx rgba(255, 153, 0, 0.2); } .leixing-active-bar { @@ -234,12 +234,12 @@ bottom: -4rpx; width: 44rpx; height: 4rpx; - background: linear-gradient(90deg, #c4b5fd, #9333ea); + background: linear-gradient(90deg, #ffcc00, #ffaa00); border-radius: 2rpx; opacity: 0; transform: scaleX(0); transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); - box-shadow: 0 2rpx 8rpx rgba(147, 51, 234, 0.4); + box-shadow: 0 2rpx 8rpx rgba(255, 170, 0, 0.4); } .leixing-active-bar-show { @@ -261,7 +261,7 @@ .border-effect-active { opacity: 1; - border-color: rgba(168, 85, 247, 0.5); + border-color: rgba(255, 204, 0, 0.5); animation: borderGlow 2s infinite; } @@ -295,7 +295,7 @@ } .zhuanqu-item:active { - background: rgba(168, 85, 247, 0.05); + background: rgba(255, 204, 0, 0.05); } .zhuanqu-item-content { @@ -320,9 +320,9 @@ } .zhuanqu-active .zhuanqu-name { - color: #8b5cf6; + color: #ff9900; font-weight: 600; - text-shadow: 0 2rpx 4rpx rgba(168, 85, 247, 0.2); + text-shadow: 0 2rpx 4rpx rgba(255, 153, 0, 0.2); } .zhuanqu-count { @@ -332,7 +332,7 @@ } .zhuanqu-active .zhuanqu-count { - color: #9333ea; + color: #ffaa00; font-weight: 500; } @@ -343,7 +343,7 @@ transform: translateY(-50%); width: 6rpx; height: 44rpx; - background: linear-gradient(180deg, #a855f7, #9333ea); + background: linear-gradient(180deg, #ffcc00, #ffaa00); border-radius: 3rpx 0 0 3rpx; opacity: 0; transition: all 0.3s; @@ -353,7 +353,7 @@ .zhuanqu-indicator-glow { opacity: 1; animation: indicatorGlow 2s infinite; - box-shadow: 0 0 20rpx rgba(168, 85, 247, 0.5); + box-shadow: 0 0 20rpx rgba(255, 204, 0, 0.5); } .indicator-sparkle { @@ -372,7 +372,7 @@ left: 0; right: 0; bottom: 0; - background: linear-gradient(90deg, transparent, rgba(168, 85, 247, 0.05), transparent); + background: linear-gradient(90deg, transparent, rgba(255, 204, 0, 0.05), transparent); opacity: 0; transition: opacity 0.3s; pointer-events: none; @@ -389,7 +389,7 @@ width: 0; height: 0; border-radius: 50%; - background: rgba(168, 85, 247, 0.1); + background: rgba(255, 204, 0, 0.1); transform: translate(-50%, -50%); opacity: 0; pointer-events: none; @@ -463,7 +463,7 @@ right: -4rpx; bottom: -4rpx; border-radius: 18rpx; - background: linear-gradient(135deg, rgba(168, 85, 247, 0.2), transparent); + background: linear-gradient(135deg, rgba(255, 204, 0, 0.2), transparent); opacity: 0; transition: opacity 0.3s; pointer-events: none; @@ -479,7 +479,7 @@ right: 0; width: 20rpx; height: 20rpx; - background: linear-gradient(135deg, #a855f7, transparent 70%); + background: linear-gradient(135deg, #ffcc00, transparent 70%); border-radius: 0 14rpx 0 0; } @@ -514,7 +514,7 @@ left: 0; width: 0; height: 2rpx; - background: linear-gradient(90deg, #c4b5fd, #9333ea); + background: linear-gradient(90deg, #ffcc00, #ffaa00); transition: width 0.3s; } @@ -537,20 +537,20 @@ .price-icon { font-size: 24rpx; - color: #9333ea; + color: #ff6600; font-weight: 500; } .price-integer { font-size: 36rpx; /* 适当调整大小 */ - color: #9333ea; + color: #ff6600; font-weight: 600; margin-left: 2rpx; } .price-decimal { font-size: 24rpx; - color: #9333ea; + color: #ff6600; font-weight: 500; } @@ -573,11 +573,11 @@ .detail-btn { position: relative; padding: 12rpx 28rpx; - background: linear-gradient(135deg, #c4b5fd, #9333ea); + background: linear-gradient(135deg, #ffcc00, #ffaa00); border-radius: 40rpx; transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); overflow: hidden; - box-shadow: 0 6rpx 20rpx rgba(147, 51, 234, 0.2); + box-shadow: 0 6rpx 20rpx rgba(255, 170, 0, 0.2); display: flex; align-items: center; justify-content: center; @@ -586,7 +586,7 @@ .detail-btn:active { transform: scale(0.95); - box-shadow: 0 4rpx 15rpx rgba(147, 51, 234, 0.4); + box-shadow: 0 4rpx 15rpx rgba(255, 170, 0, 0.4); } .detail-btn-text { @@ -659,7 +659,7 @@ left: 0; right: 0; bottom: 0; - background: linear-gradient(135deg, transparent, rgba(168, 85, 247, 0.03), transparent); + background: linear-gradient(135deg, transparent, rgba(255, 204, 0, 0.03), transparent); opacity: 0; transition: opacity 0.3s; pointer-events: none; @@ -676,7 +676,7 @@ width: 0; height: 0; border-radius: 50%; - background: rgba(168, 85, 247, 0.1); + background: rgba(255, 204, 0, 0.1); transform: translate(-50%, -50%); opacity: 0; pointer-events: none; @@ -707,19 +707,19 @@ } .shangpin-empty-btn { - background: linear-gradient(135deg, #c4b5fd, #9333ea); + background: linear-gradient(135deg, #ffcc00, #ffaa00); color: white; font-size: 26rpx; font-weight: 500; padding: 16rpx 44rpx; border-radius: 40rpx; - box-shadow: 0 8rpx 24rpx rgba(147, 51, 234, 0.3); + box-shadow: 0 8rpx 24rpx rgba(255, 170, 0, 0.3); transition: all 0.3s; } .shangpin-empty-btn:active { transform: scale(0.95); - box-shadow: 0 4rpx 16rpx rgba(147, 51, 234, 0.4); + box-shadow: 0 4rpx 16rpx rgba(255, 170, 0, 0.4); } .load-more-line { @@ -859,7 +859,7 @@ right: -4rpx; bottom: -4rpx; border-radius: 20rpx; - background: linear-gradient(135deg, rgba(168, 85, 247, 0.2), transparent); + background: linear-gradient(135deg, rgba(255, 204, 0, 0.2), transparent); opacity: 0; transition: opacity 0.3s; pointer-events: none; @@ -896,7 +896,7 @@ .info-tag { padding: 6rpx 12rpx; - background: rgba(168, 85, 247, 0.1); + background: rgba(255, 204, 0, 0.1); border-radius: 6rpx; } @@ -921,7 +921,7 @@ left: 0; right: 0; bottom: 0; - background: linear-gradient(135deg, transparent, rgba(168, 85, 247, 0.05), transparent); + background: linear-gradient(135deg, transparent, rgba(255, 204, 0, 0.05), transparent); opacity: 0; transition: opacity 0.3s; pointer-events: none; @@ -977,7 +977,7 @@ width: 100%; height: 100%; border: 4rpx solid transparent; - border-top-color: #a855f7; + border-top-color: #ffcc00; border-radius: 50%; animation: spin 1s linear infinite; } @@ -1028,7 +1028,7 @@ left: 50%; width: 12rpx; height: 12rpx; - background: #a855f7; + background: #ffcc00; border-radius: 50%; transform: translateX(-50%); animation: spin 1s linear infinite; @@ -1045,7 +1045,7 @@ left: 50%; width: 200rpx; height: 200rpx; - background: radial-gradient(circle, rgba(168, 85, 247, 0.2) 0%, transparent 70%); + background: radial-gradient(circle, rgba(255, 204, 0, 0.2) 0%, transparent 70%); transform: translate(-50%, -50%); z-index: -1; } @@ -1076,19 +1076,19 @@ @keyframes borderGlow { 0%, 100% { - border-color: rgba(168, 85, 247, 0.3); + border-color: rgba(255, 204, 0, 0.3); } 50% { - border-color: rgba(168, 85, 247, 0.8); + border-color: rgba(255, 204, 0, 0.8); } } @keyframes indicatorGlow { 0%, 100% { - box-shadow: 0 0 20rpx rgba(168, 85, 247, 0.3); + box-shadow: 0 0 20rpx rgba(255, 204, 0, 0.3); } 50% { - box-shadow: 0 0 30rpx rgba(168, 85, 247, 0.8); + box-shadow: 0 0 30rpx rgba(255, 204, 0, 0.8); } } diff --git a/pages/chat/chat.js b/pages/chat/chat.js index 9178c6c..3f3ec92 100644 --- a/pages/chat/chat.js +++ b/pages/chat/chat.js @@ -1,7 +1,14 @@ const app = getApp(); import { formatDate } from '../../static/lib/utils'; -import { resolveAvatarUrl, getDefaultAvatarUrl } from '../../utils/avatar.js'; +import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage, subscribeConfigAvatarRefresh } from '../../utils/avatar.js'; import { getLocalImUserId } from '../../utils/im-user.js'; +import { recordConversationOpen } from '../../utils/group-chat.js'; +import { + fetchHistoryMessages, + getOldestHistoryTimestamp, + mergeHistoryMessages, + waitChatImReady, +} from '../../utils/chat-history.js'; Page({ data: { @@ -25,29 +32,58 @@ Page({ pendingImage: '', pendingImageFile: null, keyboardHeight: 0, - bottomSafeHeight: 10 + bottomSafeHeight: 140, + defaultAvatarUrl: '', }, onLoad(options) { + const defAvatar = getDefaultAvatarUrl(app); if (options.data) { try { const p = JSON.parse(decodeURIComponent(options.data)); this.setData({ toUserId: p.toUserId || '', toName: p.toName || '聊天', - toAvatar: resolveAvatarUrl(p.toAvatar, app), + toAvatar: resolveAvatarUrl(p.toAvatar, app) || defAvatar, + defaultAvatarUrl: defAvatar, }); - } catch (e) {} + } catch (e) { + this.setData({ defaultAvatarUrl: defAvatar }); + } + } else { + this.setData({ defaultAvatarUrl: defAvatar }); } wx.setNavigationBarTitle({ title: this.data.toName }); this.initCurrentUser(); + this._unsubConfigAvatar = subscribeConfigAvatarRefresh(this, () => { + const def = getDefaultAvatarUrl(app); + this.setData({ + defaultAvatarUrl: def, + toAvatar: resolveAvatarUrl(this.data.toAvatar, app) || def, + }); + this.refreshCurrentUserAvatar(); + }); }, onShow() { + const defAvatar = getDefaultAvatarUrl(app); + this.setData({ + defaultAvatarUrl: defAvatar, + toAvatar: resolveAvatarUrl(this.data.toAvatar, app) || defAvatar, + }); + this.refreshCurrentUserAvatar(); + if (this.data.toUserId) { + if (!app.globalData.pageState) app.globalData.pageState = {}; + app.globalData.pageState.lastOpenedConvId = this.data.toUserId; + recordConversationOpen({ userId: this.data.toUserId }); + } this.ensureChatReady(); }, onHide() { + if (this.data.toUserId) { + recordConversationOpen({ userId: this.data.toUserId }); + } this.clearPageListeners(); this.markPrivateMessageAsRead(); setTimeout(() => { @@ -55,27 +91,34 @@ Page({ }, 250); }, - ensureChatReady() { + onUnload() { + if (this._unsubConfigAvatar) { + this._unsubConfigAvatar(); + this._unsubConfigAvatar = null; + } + }, + + async ensureChatReady() { const targetUserId = getLocalImUserId(app); if (!targetUserId) return; - const ready = () => { + const ready = async () => { this.setupAllListeners(); - this.loadHistory(true); + await this.loadHistory(true); }; - if (app.ensureConnection) { - const ret = app.ensureConnection(); - if (ret && typeof ret.then === 'function') { - ret.then(ready).catch(ready); - } else { - setTimeout(ready, 300); + try { + const ok = await waitChatImReady(app, 12000); + if (ok) { + await ready(); + return; } - return; + } catch (e) { + console.warn('[私聊] IM 连接失败', e); } if (this.isConnected() && wx.goEasy.im?.userId === targetUserId) { - ready(); + await ready(); return; } this.connectGoEasy(targetUserId); @@ -133,14 +176,26 @@ Page({ const imId = getLocalImUserId(app); const uid = wx.getStorageSync('uid'); const nick = app.globalData.nicheng || app.globalData.dashouNicheng || wx.getStorageSync('nicheng') || ''; + const defAvatar = getDefaultAvatarUrl(app); + const avatar = resolveAvatarFromStorage(app) || defAvatar; this.setData({ currentUser: { id: imId || ('Ds' + uid), name: nick || `用户${uid}`, - avatar: resolveAvatarUrl(wx.getStorageSync('touxiang'), app), + avatar, }, }); }, + + refreshCurrentUserAvatar() { + const defAvatar = getDefaultAvatarUrl(app); + const cu = this.data.currentUser; + if (!cu) return; + const avatar = resolveAvatarUrl(cu.avatar || wx.getStorageSync('touxiang'), app) || defAvatar; + if (avatar !== cu.avatar) { + this.setData({ currentUser: { ...cu, avatar } }); + } + }, fixAvatar(url) { return resolveAvatarUrl(url, app); }, @@ -156,6 +211,19 @@ Page({ normalizeMessage(msg) { if (!msg) return msg; if (!msg.payload) msg.payload = {}; + const defAvatar = this.data.defaultAvatarUrl || getDefaultAvatarUrl(app); + const myId = this.data.currentUser?.id; + if (myId && msg.senderId === myId) { + msg.senderData = { + name: this.data.currentUser.name, + avatar: resolveAvatarUrl(this.data.currentUser.avatar, app) || defAvatar, + }; + } else { + msg.senderData = { + name: this.data.toName, + avatar: resolveAvatarUrl(this.data.toAvatar, app) || defAvatar, + }; + } if (msg.type === 'image') { const p = msg.payload; if (!p.url) p.url = p.thumbnail || p.thumb || p.imageUrl || ''; @@ -188,63 +256,91 @@ Page({ onPullDownRefresh() { if (this.data.loadingHistory) return; + if (!this.data.hasMore) { + wx.showToast({ title: '没有更早的消息了', icon: 'none' }); + this.setData({ isRefreshing: false }); + wx.stopPullDownRefresh(); + return; + } this.setData({ isRefreshing: true }); - this.loadHistory(false).finally(() => { this.setData({ isRefreshing: false }); wx.stopPullDownRefresh(); }); + this.loadHistory(false).finally(() => { + this.setData({ isRefreshing: false }); + wx.stopPullDownRefresh(); + }); }, - loadHistory(refresh) { - if (!refresh && !this.data.hasMore) return Promise.resolve(); - return new Promise((resolve) => { - this.setData({ loadingHistory: true }); - const { toUserId, lastTimestamp } = this.data; - const ts = refresh ? null : lastTimestamp; - wx.goEasy.im.history({ - type: wx.GoEasy.IM_SCENE.PRIVATE, id: toUserId, lastTimestamp: ts, limit: 20, - onSuccess: (res) => { - let list = res.content || []; - list.sort((a, b) => a.timestamp - b.timestamp); - list.forEach((m, idx) => { - m = this.normalizeMessage(m); - list[idx] = m; - m.formattedTime = formatDate(m.timestamp); - if (idx === 0) m.showTime = true; - else m.showTime = (m.timestamp - list[idx-1].timestamp) / 60000 > 5; - if (m.senderId === this.data.currentUser.id) { - m.senderData = { - name: this.data.currentUser.name, - avatar: resolveAvatarUrl(this.data.currentUser.avatar, app), - }; - } else { - m.senderData = { - name: this.data.toName, - avatar: resolveAvatarUrl(this.data.toAvatar, app), - }; - } - }); - let final = refresh ? list : [...list, ...this.data.messages]; - if (!refresh) { - for (let i=0; i 5; - } - } - const ids = new Set(); - const unique = []; - for (const m of final) { if (!ids.has(m.messageId)) { ids.add(m.messageId); unique.push(m); } } - const update = { - messages: unique, - hasMore: list.length >= 20, - lastTimestamp: unique.length ? unique[0].timestamp : lastTimestamp, - loadingHistory: false - }; - if (refresh) update.scrollToView = 'msg-bottom'; - this.setData(update); - if (refresh) this.markPrivateMessageAsRead(); - resolve(); - }, - onFailed: () => { this.setData({ loadingHistory: false }); resolve(); } + mapHistoryMessage(m, idx, list) { + m = this.normalizeMessage(m); + m.formattedTime = formatDate(m.timestamp); + if (idx === 0) m.showTime = true; + else m.showTime = (m.timestamp - list[idx - 1].timestamp) / 60000 > 5; + if (m.senderId === this.data.currentUser?.id) { + m.senderData = { + name: this.data.currentUser.name, + avatar: resolveAvatarUrl(this.data.currentUser.avatar, app), + }; + } else { + m.senderData = { + name: this.data.toName, + avatar: resolveAvatarUrl(this.data.toAvatar, app), + }; + } + return m; + }, + + async loadHistory(refresh) { + if (!refresh && !this.data.hasMore) return; + if (this.data.loadingHistory) return; + if (!this.data.toUserId) return; + + this.setData({ loadingHistory: true }); + try { + const ready = await waitChatImReady(app, 12000); + if (!ready) { + wx.showToast({ title: '消息服务未连接,请稍后重试', icon: 'none' }); + return; + } + + const ts = refresh + ? null + : getOldestHistoryTimestamp(this.data.messages, this.data.lastTimestamp); + if (!refresh && ts == null) { + this.setData({ hasMore: false }); + wx.showToast({ title: '没有更早的消息了', icon: 'none' }); + return; + } + + const raw = await fetchHistoryMessages({ + scene: wx.GoEasy.IM_SCENE.PRIVATE, + id: this.data.toUserId, + lastTimestamp: ts, + limit: 20, }); - }); + + const merged = mergeHistoryMessages({ + incoming: raw, + existing: this.data.messages, + refresh, + limit: 20, + mapMessage: (m, idx, list) => this.mapHistoryMessage(m, idx, list), + }); + + const update = { + messages: merged.messages, + hasMore: merged.hasMore, + lastTimestamp: merged.lastTimestamp, + }; + if (refresh) update.scrollToView = 'msg-bottom'; + this.setData(update); + if (refresh) this.markPrivateMessageAsRead(); + } catch (e) { + console.warn('[私聊] 历史消息加载失败', e); + if (!refresh) { + wx.showToast({ title: '加载历史消息失败', icon: 'none' }); + } + } finally { + this.setData({ loadingHistory: false }); + } }, listenNewMsg() { diff --git a/pages/chat/chat.wxml b/pages/chat/chat.wxml index 5dd1fb6..c64b50a 100644 --- a/pages/chat/chat.wxml +++ b/pages/chat/chat.wxml @@ -4,13 +4,13 @@ refresher-enabled="{{true}}" refresher-triggered="{{isRefreshing}}" bindrefresherrefresh="onPullDownRefresh" - style="top: 20rpx; bottom: calc({{bottomSafeHeight}}rpx + {{keyboardHeight}}px + env(safe-area-inset-bottom) + 20rpx);"> + style="top: 20rpx; bottom: calc({{bottomSafeHeight}}rpx + {{keyboardHeight}}px + env(safe-area-inset-bottom) + 24rpx);"> 加载更早消息... {{item.formattedTime}} - + ID: {{item.payload.orderId}} - + {{item.read ? '已读' : '未读'}} - + diff --git a/pages/cs-chat/cs-chat.js b/pages/cs-chat/cs-chat.js index 645fcd6..1e1a4c8 100644 --- a/pages/cs-chat/cs-chat.js +++ b/pages/cs-chat/cs-chat.js @@ -1,7 +1,14 @@ const app = getApp(); import { formatDate } from '../../static/lib/utils'; import { getLocalImUserId } from '../../utils/im-user.js'; -import { resolveAvatarUrl } from '../../utils/avatar.js'; +import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage, subscribeConfigAvatarRefresh, ensureAvatarUrl } from '../../utils/avatar.js'; +import { + fetchHistoryMessages, + getOldestHistoryTimestamp, + mergeHistoryMessages, + waitChatImReady, +} from '../../utils/chat-history.js'; +import { matchAutoReply, getWelcomeText } from '../../utils/scriptService.js'; Page({ data: { @@ -29,11 +36,14 @@ Page({ pendingImage: '', pendingImageFile: null, keyboardHeight: 0, - bottomSafeHeight: 10 + bottomSafeHeight: 140, + defaultAvatarUrl: '', + welcomeText: '你好,请问有什么可以帮到您的?', }, onLoad(options) { let teamId = '', teamName = '客服', teamAvatar = ''; + const defAvatar = getDefaultAvatarUrl(app); if (options.data) { try { const p = JSON.parse(decodeURIComponent(options.data)); @@ -43,38 +53,102 @@ Page({ } catch (e) { } } - if (!teamAvatar) { - teamAvatar = app.globalData.ossImageUrl + app.globalData.morentouxiang; + let teamAvatarResolved = teamAvatar ? ensureAvatarUrl(teamAvatar, app) : defAvatar; + if (!teamAvatarResolved || !teamAvatarResolved.startsWith('http')) { + teamAvatarResolved = defAvatar; } - this.setData({ teamId, teamName, teamAvatar }); + this.setData({ teamId, teamName, teamAvatar: teamAvatarResolved, defaultAvatarUrl: defAvatar }); wx.setNavigationBarTitle({ title: teamName }); this.initCurrentUser(); + this.loadWelcomeScript(); + this._unsubConfigAvatar = subscribeConfigAvatarRefresh(this, () => { + const def = getDefaultAvatarUrl(app); + this.setData({ + defaultAvatarUrl: def, + teamAvatar: def, + }); + this.refreshCurrentUserAvatar(); + }); + }, + + onUnload() { + if (this._unsubConfigAvatar) { + this._unsubConfigAvatar(); + this._unsubConfigAvatar = null; + } }, onShow() { + const defAvatar = getDefaultAvatarUrl(app); + const patch = { + defaultAvatarUrl: defAvatar, + teamAvatar: defAvatar, + }; + this.setData(patch); + this.refreshCurrentUserAvatar(); this.autoConnect(); }, + refreshCurrentUserAvatar() { + const defAvatar = getDefaultAvatarUrl(app); + const cu = this.data.currentUser; + if (!cu) return; + const avatar = resolveAvatarUrl(cu.avatar || wx.getStorageSync('touxiang'), app) || defAvatar; + if (avatar !== cu.avatar) { + this.setData({ currentUser: { ...cu, avatar } }); + } + }, + onHide() { this.clearAllListeners(); }, - autoConnect() { + async loadWelcomeScript() { + try { + const text = await getWelcomeText(); + if (!text) return; + this.setData({ welcomeText: text }); + const msgs = this.data.messages; + const idx = msgs.findIndex((m) => m.messageId === 'welcome-cs'); + if (idx >= 0) { + const updated = [...msgs]; + updated[idx] = { ...updated[idx], payload: { ...updated[idx].payload, text } }; + this.setData({ messages: updated }); + } + } catch (e) { + console.warn('loadWelcomeScript failed', e); + } + }, + + async autoConnect() { const targetUserId = getLocalImUserId(app); if (!targetUserId) return; - const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected'; - if (status === 'connected' || status === 'reconnected') { - const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null; - if (currentUserId === targetUserId) { - this.setupAllListeners(); - this.loadHistory(true); + const ready = async () => { + this.setupAllListeners(); + await this.loadHistory(true); + }; + + try { + const ok = await waitChatImReady(app, 12000); + if (ok) { + await ready(); return; - } else { - wx.goEasy.disconnect(); } + } catch (e) { + console.warn('[客服] IM 连接失败', e); } - this.connectGoEasy(targetUserId); + + if (this.isConnected()) { + await ready(); + } else { + this.connectGoEasy(targetUserId); + } + }, + + isConnected() { + const s = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected'; + return s === 'connected' || s === 'reconnected'; }, connectGoEasy(userId) { @@ -84,7 +158,7 @@ Page({ id: userId, data: { name: currentUser.name, - avatar: resolveAvatarUrl(currentUser.avatar, app) + avatar: resolveAvatarUrl(currentUser.avatar, app) || getDefaultAvatarUrl(app) }, onSuccess: () => { this.setupAllListeners(); @@ -119,11 +193,13 @@ Page({ initCurrentUser() { const imId = getLocalImUserId(app); const uid = wx.getStorageSync('uid'); + const defAvatar = getDefaultAvatarUrl(app); + const avatar = resolveAvatarFromStorage(app) || defAvatar; this.setData({ currentUser: { id: imId || ('Boss' + uid), - name: app.globalData.currentUser?.name || `用户${uid}`, - avatar: resolveAvatarUrl(wx.getStorageSync('touxiang'), app), + name: app.globalData.currentUser?.name || wx.getStorageSync('nicheng') || `用户${uid}`, + avatar, }, }); }, @@ -169,66 +245,89 @@ Page({ this.setData({ messages: msgs }); }, + mapHistoryMessage(m, idx, list) { + m = this.normalizeMessage(m); + m.formattedTime = formatDate(m.timestamp); + if (idx === 0) m.showTime = true; + else m.showTime = (m.timestamp - list[idx - 1].timestamp) / 60000 > 5; + if (m.senderId === this.data.currentUser?.id) { + const av = resolveAvatarUrl(this.data.currentUser.avatar, app) || this.data.defaultAvatarUrl; + m.senderData = { name: this.data.currentUser.name, avatar: av }; + } else { + const av = resolveAvatarUrl(this.data.teamAvatar, app) || this.data.defaultAvatarUrl; + m.senderData = { name: this.data.teamName, avatar: av }; + } + return m; + }, + + injectWelcomeMessage(messages) { + const list = Array.isArray(messages) ? [...messages] : []; + const welcomeId = 'welcome-cs'; + if (!list.some((m) => m.messageId === welcomeId)) { + const welcome = this.createWelcomeMessage(); + welcome.messageId = welcomeId; + welcome.showTime = true; + list.unshift(welcome); + } + return list; + }, + // 历史消息 - loadHistory(refresh) { - if (!refresh && !this.data.hasMore) return Promise.resolve(); - return new Promise((resolve) => { - this.setData({ loadingHistory: true }); - const { teamId, lastTimestamp } = this.data; - const ts = refresh ? null : lastTimestamp; - wx.goEasy.im.history({ - type: wx.GoEasy.IM_SCENE.CS, - id: teamId, + async loadHistory(refresh) { + if (!refresh && !this.data.hasMore) return; + if (this.data.loadingHistory) return; + if (!this.data.teamId) return; + + this.setData({ loadingHistory: true }); + try { + const ready = await waitChatImReady(app, 12000); + if (!ready) { + wx.showToast({ title: '消息服务未连接,请稍后重试', icon: 'none' }); + return; + } + + const ts = refresh + ? null + : getOldestHistoryTimestamp(this.data.messages, this.data.lastTimestamp); + if (!refresh && ts == null) { + this.setData({ hasMore: false }); + wx.showToast({ title: '没有更早的消息了', icon: 'none' }); + return; + } + + const raw = await fetchHistoryMessages({ + scene: wx.GoEasy.IM_SCENE.CS, + id: this.data.teamId, lastTimestamp: ts, limit: 20, - onSuccess: (res) => { - let list = res.content || []; - list.sort((a, b) => a.timestamp - b.timestamp); - list.forEach((m, idx) => { - m = this.normalizeMessage(m); - list[idx] = m; - m.formattedTime = formatDate(m.timestamp); - if (idx === 0) m.showTime = true; - else m.showTime = (m.timestamp - list[idx-1].timestamp) / 60000 > 5; - if (m.senderId === this.data.currentUser.id) { - m.senderData = { name: this.data.currentUser.name, avatar: this.data.currentUser.avatar }; - } else { - m.senderData = { name: this.data.teamName, avatar: this.data.teamAvatar }; - } - }); - let final = refresh ? list : [...list, ...this.data.messages]; - if (!refresh) { - for (let i=0; i 5; - } - } - const ids = new Set(); - const unique = []; - for (const m of final) { if (!ids.has(m.messageId)) { ids.add(m.messageId); unique.push(m); } } - - // 插入欢迎语 - const welcomeId = 'welcome-cs'; - if (!unique.some(m => m.messageId === welcomeId)) { - const welcome = this.createWelcomeMessage(); - welcome.messageId = welcomeId; - welcome.showTime = true; - unique.unshift(welcome); - } - - const update = { - messages: unique, - hasMore: list.length >= 20, - lastTimestamp: unique.length ? unique[0].timestamp : lastTimestamp, - loadingHistory: false - }; - if (refresh) update.scrollToView = 'msg-bottom'; - this.setData(update); - resolve(); - }, - onFailed: () => { this.setData({ loadingHistory: false }); resolve(); } }); - }); + + let merged = mergeHistoryMessages({ + incoming: raw, + existing: this.data.messages, + refresh, + limit: 20, + mapMessage: (m, idx, list) => this.mapHistoryMessage(m, idx, list), + }); + + merged.messages = this.injectWelcomeMessage(merged.messages); + merged.lastTimestamp = getOldestHistoryTimestamp(merged.messages, merged.lastTimestamp); + + const update = { + messages: merged.messages, + hasMore: merged.hasMore, + lastTimestamp: merged.lastTimestamp, + }; + if (refresh) update.scrollToView = 'msg-bottom'; + this.setData(update); + } catch (e) { + console.warn('[客服] 历史消息加载失败', e); + if (!refresh) { + wx.showToast({ title: '加载历史消息失败', icon: 'none' }); + } + } finally { + this.setData({ loadingHistory: false }); + } }, createWelcomeMessage() { @@ -238,8 +337,11 @@ Page({ timestamp: Date.now(), senderId: 'system', receiverId: this.data.currentUser?.id, - senderData: { name: '系统消息', avatar: '' }, - payload: { text: '你好,请问有什么可以帮到您的?' }, + senderData: { + name: this.data.teamName || '客服', + avatar: resolveAvatarUrl(this.data.teamAvatar, app) || this.data.defaultAvatarUrl, + }, + payload: { text: this.data.welcomeText || '你好,请问有什么可以帮到您的?' }, status: 'success', read: false, formattedTime: formatDate(Date.now()), @@ -255,7 +357,10 @@ Page({ if (msg.senderId === this.data.currentUser.id) return; msg = this.normalizeMessage(msg); msg.formattedTime = formatDate(msg.timestamp); - msg.senderData = { name: this.data.teamName, avatar: this.data.teamAvatar }; + msg.senderData = { + name: this.data.teamName, + avatar: resolveAvatarUrl(this.data.teamAvatar, app) || this.data.defaultAvatarUrl, + }; const msgs = this.data.messages; const last = msgs.length ? msgs[msgs.length-1] : null; msg.showTime = last ? (msg.timestamp - last.timestamp) / 60000 > 5 : true; @@ -370,11 +475,53 @@ Page({ }); wx.goEasy.im.sendMessage({ message:msg, - onSuccess:() => this.updateMsg(id, 'success'), + onSuccess:() => { + this.updateMsg(id, 'success'); + this.tryAutoReply(text); + }, onFailed:() => this.updateMsg(id, 'failed') }); }, + async tryAutoReply(userText) { + try { + const result = await matchAutoReply(userText); + if (result && result.matched && result.content) { + this.insertCsAutoReply(result); + } + } catch (e) { + console.warn('tryAutoReply failed', e); + } + }, + + insertCsAutoReply(result) { + const replyType = (result && result.reply_type) || 'text'; + const content = (result && result.content) || ''; + if (!content) return; + const isImage = replyType === 'image'; + const msg = { + messageId: 'auto-reply-' + Date.now(), + type: isImage ? 'image' : 'text', + timestamp: Date.now(), + senderId: this.data.teamId, + receiverId: this.data.currentUser?.id, + senderData: { + name: this.data.teamName, + avatar: resolveAvatarUrl(this.data.teamAvatar, app) || this.data.defaultAvatarUrl, + }, + payload: isImage ? { url: content } : { text: content }, + status: 'success', + read: false, + formattedTime: formatDate(Date.now()), + showTime: this.needShow(Date.now()), + }; + if (isImage) msg.imageUrl = content; + this.setData({ + messages: [...this.data.messages, msg], + scrollToView: 'msg-bottom', + }); + }, + sendImageMsg(filePath, fileObj) { const { currentUser, teamId, teamName, teamAvatar } = this.data; if (!teamId || !currentUser || !filePath) return; @@ -521,9 +668,30 @@ Page({ onKeyboardHeightChange(e) { this.setData({ keyboardHeight: e.detail.height }); }, + onTeamAvatarError() { + this.setData({ teamAvatar: getDefaultAvatarUrl(app) }); + }, + + onSelfAvatarError() { + const def = getDefaultAvatarUrl(app); + const cu = this.data.currentUser; + if (cu) { + this.setData({ currentUser: { ...cu, avatar: def } }); + } + }, + onPullDownRefresh() { if (this.data.loadingHistory) return; + if (!this.data.hasMore) { + wx.showToast({ title: '没有更早的消息了', icon: 'none' }); + this.setData({ isRefreshing: false }); + wx.stopPullDownRefresh(); + return; + } this.setData({ isRefreshing: true }); - this.loadHistory(false).finally(() => { this.setData({ isRefreshing: false }); wx.stopPullDownRefresh(); }); + this.loadHistory(false).finally(() => { + this.setData({ isRefreshing: false }); + wx.stopPullDownRefresh(); + }); } }); \ No newline at end of file diff --git a/pages/cs-chat/cs-chat.wxml b/pages/cs-chat/cs-chat.wxml index 723917b..bc8bc09 100644 --- a/pages/cs-chat/cs-chat.wxml +++ b/pages/cs-chat/cs-chat.wxml @@ -11,7 +11,7 @@ {{item.formattedTime}} - + 订单号:{{item.payload.dingdan_id || '无'}} 服务:{{item.payload.jieshao || ''}} - ¥{{item.payload.jine}} + ¥{{item.payload.jine}} {{item.payload.text || '[未知消息]'}} - + - + diff --git a/pages/dashou-exam/dashou-exam.js b/pages/dashou-exam/dashou-exam.js new file mode 100644 index 0000000..4332310 --- /dev/null +++ b/pages/dashou-exam/dashou-exam.js @@ -0,0 +1,283 @@ +import { createPage, request } from '../../utils/base-page.js'; +import { refreshDashouMembership } from '../../utils/dashou-profile.js'; +import { isApiSuccess, getApiMsg } from '../../utils/api-helper.js'; + +const LABELS = ['A', 'B', 'C', 'D', 'E', 'F']; + +function toSlotNum(slot) { + const n = Number(slot); + return Number.isFinite(n) ? n : slot; +} + +function normalizeSlots(slots) { + return (slots || []).map(toSlotNum); +} + +function slotsEqual(a, b) { + const sa = normalizeSlots(a).sort((x, y) => x - y); + const sb = normalizeSlots(b).sort((x, y) => x - y); + return sa.length === sb.length && sa.every((v, i) => v == sb[i]); +} + +function shuffle(arr) { + const a = [...arr]; + for (let i = a.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [a[i], a[j]] = [a[j], a[i]]; + } + return a; +} + +Page(createPage({ + data: { + loading: true, + alreadyPassed: false, + finished: false, + blocked: false, + blockTitle: '', + blockReason: '', + needRecharge: false, + questions: [], + currentIndex: 0, + currentQuestion: null, + questionType: 1, + total: 0, + wrongCount: 0, + maxWrong: 0, + correctCount: 0, + selectedSlots: [], + showExplanation: false, + }, + + onLoad() { + this.initExam(); + }, + + showBlocked(title, reason, needRecharge = false) { + this.setData({ + loading: false, + blocked: true, + blockTitle: title, + blockReason: reason, + needRecharge: !!needRecharge, + }); + }, + + /** 进入页先查 status:不满足条件留在本页提示,不自动退出 */ + async initExam() { + this.setData({ loading: true, blocked: false, needRecharge: false }); + try { + await refreshDashouMembership(getApp()); + const res = await request({ url: '/jituan/dashou-exam/status', method: 'POST' }); + const body = res?.data; + if (!isApiSuccess(body)) { + this.showBlocked('无法开始考试', getApiMsg(body) || '无法获取考试状态,请稍后重试'); + return; + } + const d = body.data || {}; + if (!d.exam_enabled) { + this.showBlocked( + '未开启考试', + '本俱乐部尚未开启接单考试。若后台已开启,请确认配置的是当前小程序对应俱乐部。' + ); + return; + } + if (d.exam_passed) { + this.setData({ loading: false, alreadyPassed: true }); + return; + } + const cfg = d.config || {}; + if (cfg.require_member && !d.has_member) { + this.showBlocked('须先开通会员', '本俱乐部要求开通会员后才能参加考试', true); + return; + } + if ((d.pool_count || 0) < 1) { + this.showBlocked( + '暂无可用考题', + '考试已开启,但题库没有「已上架」的题目。请在后台确认题目已上架,且每题至少两个选项并标记正确答案。' + ); + return; + } + await this.loadBundle(); + } catch (e) { + this.showBlocked('网络错误', '请检查网络后下拉重新进入本页'); + } + }, + + async loadBundle() { + this.setData({ loading: true, finished: false, showExplanation: false, blocked: false }); + try { + const res = await request({ url: '/jituan/dashou-exam/bundle', method: 'POST' }); + const body = res?.data; + if (!isApiSuccess(body)) { + this.showBlocked('加载考题失败', getApiMsg(body) || '请稍后重试或联系管理员'); + return; + } + const d = body.data || {}; + if (d.already_passed) { + this.setData({ alreadyPassed: true, loading: false }); + return; + } + const cfg = d.config || {}; + const prepared = (d.questions || []).map((q) => this.prepareQuestion(q)).filter((q) => (q.displayOptions || []).length > 0); + if (!prepared.length) { + this.showBlocked( + '题目配置不完整', + '已抽到的题目缺少有效选项,请在后台检查题目是否至少两个选项并标记正确答案。' + ); + return; + } + this.setData({ + loading: false, + questions: prepared, + total: prepared.length, + currentIndex: 0, + wrongCount: 0, + correctCount: 0, + maxWrong: cfg.max_wrong || 0, + currentQuestion: prepared[0], + questionType: prepared[0].question_type || 1, + selectedSlots: [], + }); + } catch (e) { + this.showBlocked('网络错误', '拉取考题失败,请稍后重试'); + } + }, + + prepareQuestion(q) { + const oss = getApp().globalData.ossImageUrl || ''; + const displayImages = (q.images || []).map((u) => (u.startsWith('http') ? u : oss + u)); + const shuffled = shuffle(q.options || []).map((opt, idx) => ({ + slot: toSlotNum(opt.slot), + text: opt.text, + label: LABELS[idx] || String(idx + 1), + selected: false, + })); + return { + ...q, + question_type: Number(q.question_type) || 1, + displayImages, + displayOptions: shuffled, + correctSlots: normalizeSlots(q.correct_slots || []), + }; + }, + + applySelectedToOptions(selectedSlots) { + const q = this.data.currentQuestion; + if (!q) return; + const set = normalizeSlots(selectedSlots); + const displayOptions = (q.displayOptions || []).map((opt) => ({ + ...opt, + selected: set.some((s) => s == opt.slot), + })); + this.setData({ + selectedSlots: set, + currentQuestion: { ...q, displayOptions }, + }); + }, + + onSelectOption(e) { + const slot = toSlotNum(e.currentTarget.dataset.slot); + const q = this.data.currentQuestion; + if (!q || slot === undefined || slot === null || slot === '') return; + if (q.question_type === 2) { + const set = [...this.data.selectedSlots]; + const i = set.findIndex((s) => s == slot); + if (i >= 0) set.splice(i, 1); + else set.push(slot); + if (this.data.showExplanation) { + this.setData({ showExplanation: false }); + } + this.applySelectedToOptions(set); + return; + } + this.applySelectedToOptions([slot]); + if (this.data.showExplanation) { + this.setData({ showExplanation: false }); + } + setTimeout(() => this.gradeAnswer([slot]), 120); + }, + + confirmMulti() { + if (this.data.showExplanation) { + this.setData({ showExplanation: false }); + this.applySelectedToOptions([]); + return; + } + if (!this.data.selectedSlots.length) { + wx.showToast({ title: '请选择答案', icon: 'none' }); + return; + } + this.gradeAnswer([...this.data.selectedSlots]); + }, + + /** 前端本地判分(bundle 已含正确答案与解析) */ + gradeAnswer(selected) { + const q = this.data.currentQuestion; + const correct = q.correctSlots || []; + const isRight = slotsEqual(selected, correct); + + if (!isRight) { + const wrongCount = this.data.wrongCount + 1; + this.setData({ wrongCount, showExplanation: true }); + if (wrongCount > this.data.maxWrong) { + wx.showModal({ + title: '答题失败', + content: `${q.explanation || '本题答错了'}。错题过多,将重新开始本轮考试。`, + showCancel: false, + success: () => this.loadBundle(), + }); + } else { + wx.showToast({ title: '答错了,请查看解析', icon: 'none' }); + } + return; + } + + const correctCount = this.data.correctCount + 1; + const next = this.data.currentIndex + 1; + if (next >= this.data.total) { + this.submitPassed(correctCount); + return; + } + const nextQ = this.data.questions[next]; + this.setData({ + correctCount, + currentIndex: next, + currentQuestion: nextQ, + questionType: nextQ.question_type, + selectedSlots: [], + showExplanation: false, + }); + }, + + async submitPassed(correctCount) { + try { + const res = await request({ url: '/jituan/dashou-exam/mark-passed', method: 'POST' }); + const body = res?.data; + if (isApiSuccess(body)) { + this.setData({ finished: true, correctCount }); + } else { + wx.showToast({ title: getApiMsg(body) || '提交失败', icon: 'none' }); + } + } catch (e) { + wx.showToast({ title: '提交失败', icon: 'none' }); + } + }, + + goRecharge() { + wx.navigateTo({ url: '/pages/fighter-recharge/fighter-recharge' }); + }, + + retryInit() { + this.initExam(); + }, + + goBack() { + const pages = getCurrentPages(); + if (pages.length > 1) { + wx.navigateBack(); + } else { + wx.switchTab({ url: '/pages/accept-order/accept-order' }); + } + }, +})); diff --git a/pages/dashou-exam/dashou-exam.json b/pages/dashou-exam/dashou-exam.json new file mode 100644 index 0000000..26876a8 --- /dev/null +++ b/pages/dashou-exam/dashou-exam.json @@ -0,0 +1,6 @@ +{ + "usingComponents": {}, + "navigationBarTitleText": "接单考试", + "navigationBarBackgroundColor": "#c4b5fd", + "navigationBarTextStyle": "black" +} diff --git a/pages/dashou-exam/dashou-exam.wxml b/pages/dashou-exam/dashou-exam.wxml new file mode 100644 index 0000000..6a36f55 --- /dev/null +++ b/pages/dashou-exam/dashou-exam.wxml @@ -0,0 +1,66 @@ + + 正在检查考试资格... + + + {{blockTitle}} + {{blockReason}} + + 去开通会员 + 重试 + 返回 + + + + + 已通过考试 + 无需重复考试,可直接抢单 + 返回 + + + + 考试通过 + 恭喜通过,现在可以去抢单了 + 去抢单 + + + + + 第 {{currentIndex + 1}} / {{total}} 题 + 已错 {{wrongCount}} 题 · 最多可错 {{maxWrong}} 题 + + {{questionType === 2 ? '多选题' : '单选题'}} + {{currentQuestion.stem}} + + + + + + + + + + + + {{item.label}} + {{item.text}} + + + + + + 解析 + {{currentQuestion.explanation || '暂无解析'}} + + + + + {{showExplanation ? '重新作答' : '确认本题'}} + + + diff --git a/pages/dashou-exam/dashou-exam.wxss b/pages/dashou-exam/dashou-exam.wxss new file mode 100644 index 0000000..8227bc8 --- /dev/null +++ b/pages/dashou-exam/dashou-exam.wxss @@ -0,0 +1,228 @@ +.exam-page { + min-height: 100vh; + background: linear-gradient(180deg, #f5f3ff 0%, #fff3d6 100%); + padding: 24rpx; + padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); + box-sizing: border-box; +} + +.center-tip, +.center-box { + padding: 80rpx 40rpx; + text-align: center; +} + +.tip-title { + font-size: 36rpx; + font-weight: 600; + display: block; + margin-bottom: 16rpx; + color: #333; +} + +.tip-desc { + font-size: 28rpx; + color: #666; + display: block; + margin-bottom: 32rpx; + line-height: 1.6; +} + +.btn-primary { + background: linear-gradient(90deg, #c4b5fd, #a78bfa); + padding: 22rpx 48rpx; + border-radius: 48rpx; + font-weight: 600; + font-size: 30rpx; + color: #6d28d9; + text-align: center; +} + +.btn-primary.block { + display: block; + width: 100%; + box-sizing: border-box; +} + +.btn-row { + display: flex; + flex-direction: column; + gap: 20rpx; + margin-top: 24rpx; +} + +.btn-outline { + padding: 20rpx 48rpx; + border-radius: 48rpx; + border: 2rpx solid #ddd; + color: #666; + font-size: 28rpx; + text-align: center; + background: #fff; +} + +.btn-outline.block { + display: block; + width: 100%; + box-sizing: border-box; +} + +.exam-body { + display: flex; + flex-direction: column; + min-height: calc(100vh - 48rpx); +} + +.exam-card { + flex: 1; + background: #fff; + border-radius: 24rpx; + padding: 28rpx; + box-shadow: 0 8rpx 32rpx rgba(73, 47, 0, 0.06); +} + +.progress { + font-size: 28rpx; + color: #6d28d9; + font-weight: 600; +} + +.progress-sub { + font-size: 24rpx; + color: #999; + margin-top: 8rpx; + margin-bottom: 24rpx; +} + +.q-type-tag { + display: inline-block; + font-size: 22rpx; + color: #b8860b; + background: #f5f3ff; + border: 1rpx solid #c4b5fd; + border-radius: 8rpx; + padding: 4rpx 16rpx; + margin-bottom: 16rpx; +} + +.q-stem { + font-size: 32rpx; + font-weight: 600; + line-height: 1.6; + margin-bottom: 24rpx; + color: #222; +} + +.q-images { + margin-bottom: 24rpx; +} + +.q-img { + width: 100%; + border-radius: 12rpx; + margin-bottom: 12rpx; +} + +.options { + margin-top: 8rpx; +} + +.opt-item { + background: #fafafa; + border: 2rpx solid #eee; + border-radius: 16rpx; + padding: 24rpx; + margin-bottom: 16rpx; + display: flex; + align-items: flex-start; + gap: 20rpx; +} + +.opt-active { + border-color: #c4b5fd; + background: #fffdf0; +} + +.opt-radio { + width: 36rpx; + height: 36rpx; + border-radius: 50%; + border: 2rpx solid #ccc; + flex-shrink: 0; + margin-top: 4rpx; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; +} + +.opt-radio-on { + border-color: #c4b5fd; + background: #f5f3ff; +} + +.opt-radio-dot { + width: 18rpx; + height: 18rpx; + border-radius: 50%; + background: #c4b5fd; +} + +.opt-body { + flex: 1; + display: flex; + gap: 12rpx; + align-items: flex-start; +} + +.opt-label { + font-weight: 700; + color: #6d28d9; + min-width: 40rpx; + font-size: 28rpx; +} + +.opt-text { + flex: 1; + font-size: 28rpx; + line-height: 1.5; + color: #333; +} + +.explain-box { + background: #fffbf0; + border-left: 6rpx solid #e6a23c; + padding: 20rpx 24rpx; + margin-top: 24rpx; + border-radius: 0 12rpx 12rpx 0; +} + +.explain-title { + font-weight: 600; + color: #333; + display: block; + margin-bottom: 8rpx; + font-size: 28rpx; +} + +.explain-text { + font-size: 26rpx; + color: #666; + line-height: 1.6; +} + +.foot-actions { + margin-top: 24rpx; + padding: 0 8rpx; +} + +.foot-btn { + width: 100%; + box-sizing: border-box; + min-height: 88rpx; + display: flex; + align-items: center; + justify-content: center; + line-height: 1.2; + padding: 0 32rpx; +} diff --git a/pages/dashouduan/dashouduan.js b/pages/dashouduan/dashouduan.js new file mode 100644 index 0000000..395bbe5 --- /dev/null +++ b/pages/dashouduan/dashouduan.js @@ -0,0 +1,14 @@ +/** 旧二维码兼容:dashouduan → 打手中心,inviteCode 自动注册逻辑不变 */ +import { parseSceneOptions } from '../../utils/base-page.js'; + +Page({ + onLoad(options) { + const parsed = parseSceneOptions(options || {}); + const inviteCode = parsed.inviteCode || options.inviteCode || ''; + let url = '/pages/fighter/fighter'; + if (inviteCode) { + url += `?inviteCode=${encodeURIComponent(inviteCode)}`; + } + wx.reLaunch({ url }); + }, +}); diff --git a/pages/dashouduan/dashouduan.json b/pages/dashouduan/dashouduan.json new file mode 100644 index 0000000..52bc9cd --- /dev/null +++ b/pages/dashouduan/dashouduan.json @@ -0,0 +1 @@ +{ "navigationBarTitleText": "加载中", "usingComponents": {} } diff --git a/pages/dashouduan/dashouduan.wxml b/pages/dashouduan/dashouduan.wxml new file mode 100644 index 0000000..3dee721 --- /dev/null +++ b/pages/dashouduan/dashouduan.wxml @@ -0,0 +1 @@ +正在进入打手端… diff --git a/pages/dashouduan/dashouduan.wxss b/pages/dashouduan/dashouduan.wxss new file mode 100644 index 0000000..8082289 --- /dev/null +++ b/pages/dashouduan/dashouduan.wxss @@ -0,0 +1 @@ +.tip { display:flex; align-items:center; justify-content:center; min-height:100vh; color:#999; font-size:28rpx; } diff --git a/pages/edit/edit.wxss b/pages/edit/edit.wxss index 8ce2df8..f98b51a 100644 --- a/pages/edit/edit.wxss +++ b/pages/edit/edit.wxss @@ -1,6 +1,6 @@ /* pages/edit/edit.wxss */ page { - background: linear-gradient(135deg, #faf5ff 0%, #f0f8ff 100%); + background: linear-gradient(135deg, #fffaf0 0%, #f0f8ff 100%); min-height: 100vh; } diff --git a/pages/express-order/express-order.json b/pages/express-order/express-order.json index 7682a2b..87bc997 100644 --- a/pages/express-order/express-order.json +++ b/pages/express-order/express-order.json @@ -4,7 +4,7 @@ "chenghao-tag": "/components/chenghao-tag/chenghao-tag" }, "backgroundTextStyle": "dark", - "navigationBarBackgroundColor": "#fff8e1", + "navigationBarBackgroundColor": "#f7dc51", "navigationBarTitleText": "常规发单", "navigationBarTextStyle": "black", "backgroundColor": "#f5f5f5", diff --git a/pages/express-order/express-order.wxss b/pages/express-order/express-order.wxss index 8081935..4584d49 100644 --- a/pages/express-order/express-order.wxss +++ b/pages/express-order/express-order.wxss @@ -289,7 +289,7 @@ page { } .filter-switch.active { - background: linear-gradient(180deg, #c4b5fd, #a78bfa); + background: linear-gradient(180deg, #fae04d, #ffc0a3); color: #492f00; } @@ -323,7 +323,7 @@ page { .copy-link-btn { padding: 8rpx 20rpx; - background: linear-gradient(180deg, #c4b5fd, #a78bfa); + background: linear-gradient(180deg, #fae04d, #ffc0a3); color: #492f00; border-radius: 15rpx; font-size: 22rpx; @@ -337,7 +337,7 @@ page { .load-more-btn { display: inline-block; padding: 15rpx 40rpx; - background: linear-gradient(180deg, #c4b5fd, #a78bfa); + background: linear-gradient(180deg, #fae04d, #ffc0a3); border-radius: 30rpx; color: #492f00; font-size: 26rpx; @@ -373,7 +373,7 @@ page { .generate-modal-grid-btn, .copy-modal-grid-btn, .edit-modal-grid-btn { - background: linear-gradient(180deg, #c4b5fd, #a78bfa); + background: linear-gradient(180deg, #fae04d, #ffc0a3); color: #492f00; } @@ -484,7 +484,7 @@ page { } .confirm-add-grid-btn { - background: linear-gradient(180deg, #c4b5fd, #a78bfa); + background: linear-gradient(180deg, #fae04d, #ffc0a3); color: #492f00; } @@ -538,7 +538,7 @@ page { } .confirm-new-link-grid-btn { - background: linear-gradient(180deg, #c4b5fd, #a78bfa); + background: linear-gradient(180deg, #fae04d, #ffc0a3); color: #492f00; } @@ -569,7 +569,7 @@ page { width: 90rpx; height: 90rpx; border: 8rpx solid #eee; - border-top: 8rpx solid #9333ea; + border-top: 8rpx solid #ffd061; border-radius: 50%; animation: spin 1.2s linear infinite; margin-bottom: 35rpx; diff --git a/pages/fighter-edit/fighter-edit.wxss b/pages/fighter-edit/fighter-edit.wxss index cccfd60..653e8db 100644 --- a/pages/fighter-edit/fighter-edit.wxss +++ b/pages/fighter-edit/fighter-edit.wxss @@ -1,7 +1,7 @@ /* 页面容器 */ .container { min-height: 100vh; - background: linear-gradient(135deg, #f5f3ff 0%, #e6f7ff 100%); + background: linear-gradient(135deg, #fffacd 0%, #e6f7ff 100%); padding: 20rpx; box-sizing: border-box; } @@ -27,7 +27,7 @@ height: 180rpx; border-radius: 50%; border: 6rpx solid #ffffff; - box-shadow: 0 0 30rpx rgba(196, 181, 253, 0.4); + box-shadow: 0 0 30rpx rgba(255, 215, 0, 0.4); z-index: 2; position: relative; background-color: #f5f5f5; @@ -38,7 +38,7 @@ width: 220rpx; height: 220rpx; border-radius: 50%; - background: radial-gradient(circle, rgba(196, 181, 253, 0.2) 0%, transparent 70%); + background: radial-gradient(circle, rgba(255, 215, 0, 0.2) 0%, transparent 70%); z-index: 1; animation: halo-pulse 2s infinite ease-in-out; } diff --git a/pages/fighter-ledger/fighter-ledger.js b/pages/fighter-ledger/fighter-ledger.js new file mode 100644 index 0000000..a714e82 --- /dev/null +++ b/pages/fighter-ledger/fighter-ledger.js @@ -0,0 +1,139 @@ +import request from '../../utils/request.js'; +import { getOrderStatusText } from '../../utils/api-helper.js'; + +const PAGE_SIZE = 15; + +const TYPE_CONFIG = { + commission: { title: '佣金记录', statuses: [3, 8] }, + settle: { title: '结算记录', statuses: [3] }, + withdraw: { title: '提现记录', api: 'withdraw' }, + deduct: { title: '扣款记录', api: 'deduct' }, +}; + +Page({ + data: { + pageTitle: '交易明细', + type: 'commission', + statusBar: 20, + list: [], + page: 1, + hasMore: true, + loading: false, + refreshing: false, + }, + + onLoad(options) { + const sys = wx.getSystemInfoSync(); + const type = (options.type || 'commission').trim(); + const cfg = TYPE_CONFIG[type] || TYPE_CONFIG.commission; + this.setData({ + type, + pageTitle: cfg.title, + statusBar: sys.statusBarHeight || 20, + }); + this.loadList(true); + }, + + goBack() { + wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/fighter/fighter' }) }); + }, + + onRefresh() { + this.setData({ refreshing: true }); + this.loadList(true).finally(() => this.setData({ refreshing: false })); + }, + + onLoadMore() { + if (this.data.hasMore && !this.data.loading) { + this.loadList(false); + } + }, + + async loadList(refresh) { + const { type, loading } = this.data; + if (loading) return; + const page = refresh ? 1 : this.data.page; + this.setData({ loading: true }); + try { + let rows = []; + let hasMore = false; + if (type === 'withdraw') { + const res = await request({ + url: '/yonghu/tixianjiluhq', + method: 'POST', + data: { page, limit: PAGE_SIZE }, + }); + const body = res?.data; + if (body && (body.code === 200 || body.code === 0)) { + const raw = body.data?.list || []; + rows = raw.map((item, idx) => ({ + id: item.id || item.jilu_id || `${page}-${idx}`, + title: item.beizhu || item.status_text || item.zhuangtai_text || '提现', + time: item.creat_time || item.create_time || item.tijiao_shijian || '', + amount: `-¥${item.jine || item.amount || item.tixian_jine || '0.00'}`, + minus: true, + })); + hasMore = raw.length >= PAGE_SIZE; + } + } else if (type === 'deduct') { + const res = await request({ + url: '/yonghu/dsfklbhq', + method: 'POST', + data: { page, page_size: PAGE_SIZE, zhuangtai: 0 }, + }); + const body = res?.data; + if (body && (body.code === 200 || body.code === 0)) { + const raw = body.data?.list || []; + rows = raw.map((item, idx) => ({ + id: item.fadan_id || item.id || `${page}-${idx}`, + title: item.yuanyin || item.beizhu || item.fakuan_yuanyin || '扣款', + time: item.creat_time || item.create_time || '', + amount: `-¥${item.jine || item.fakuan_jine || item.fakuanjine || '0.00'}`, + minus: true, + })); + hasMore = raw.length >= PAGE_SIZE; + } + } else { + const cfg = TYPE_CONFIG[type] || TYPE_CONFIG.commission; + const res = await request({ + url: '/dingdan/dshqdingdan', + method: 'POST', + data: { + zhuangtai_list: cfg.statuses, + page, + page_size: PAGE_SIZE, + }, + }); + const body = res?.data; + if (body && (body.code === 200 || body.code === 0)) { + const raw = body.data?.list || []; + rows = raw.map((item) => { + const amt = item.jine || item.dashou_fencheng || '0.00'; + const brief = (item.jieshao || '').trim(); + const title = brief + ? (brief.length > 18 ? `${brief.slice(0, 18)}…` : brief) + : `订单 ${item.dingdan_id || ''}`; + return { + id: item.dingdan_id, + title, + time: item.wancheng_shijian || item.jiesuan_shijian || item.creat_time || getOrderStatusText(item.zhuangtai), + amount: `+¥${amt}`, + minus: false, + }; + }); + hasMore = body.data?.has_more ?? raw.length >= PAGE_SIZE; + } + } + const list = refresh ? rows : [...this.data.list, ...rows]; + this.setData({ + list, + page: refresh ? 2 : page + 1, + hasMore, + }); + } catch (e) { + console.error('ledger load fail', e); + } finally { + this.setData({ loading: false }); + } + }, +}); diff --git a/pages/fighter-ledger/fighter-ledger.json b/pages/fighter-ledger/fighter-ledger.json new file mode 100644 index 0000000..b16ef3e --- /dev/null +++ b/pages/fighter-ledger/fighter-ledger.json @@ -0,0 +1,4 @@ +{ + "navigationStyle": "custom", + "usingComponents": {} +} diff --git a/pages/fighter-ledger/fighter-ledger.wxml b/pages/fighter-ledger/fighter-ledger.wxml new file mode 100644 index 0000000..b7513d1 --- /dev/null +++ b/pages/fighter-ledger/fighter-ledger.wxml @@ -0,0 +1,20 @@ + + + + + {{pageTitle}} + + + + 加载中... + 暂无记录 + + + {{item.title}} + {{item.time}} + + {{item.amount}} + + — 已加载全部 — + + diff --git a/pages/fighter-ledger/fighter-ledger.wxss b/pages/fighter-ledger/fighter-ledger.wxss new file mode 100644 index 0000000..ece738c --- /dev/null +++ b/pages/fighter-ledger/fighter-ledger.wxss @@ -0,0 +1,75 @@ +@import '../../styles/xym-layout.wxss'; + +.ledger-page { + min-height: 100vh; + background: #f5f5f5; + display: flex; + flex-direction: column; +} + +.nav-bar { + padding: 0 24rpx 16rpx; +} + +.back { + width: 60rpx; + font-size: 48rpx; + color: #333; + line-height: 1; +} + +.back.placeholder { + visibility: hidden; +} + +.nav-title { + font-size: 34rpx; + font-weight: 700; + color: #222; +} + +.ledger-scroll { + flex: 1; + height: 0; + padding: 0 24rpx; + box-sizing: border-box; +} + +.list-item { + background: #fff; + border-radius: 16rpx; + padding: 24rpx; + margin-bottom: 16rpx; +} + +.row-t { + font-size: 28rpx; + color: #222; + font-weight: 600; +} + +.row-d { + font-size: 24rpx; + color: #999; + margin-top: 8rpx; +} + +.row-r { + font-size: 30rpx; + font-weight: 700; + color: #492f00; + flex-shrink: 0; + margin-left: 16rpx; +} + +.row-r.minus { + color: #ff4c45; +} + +.empty-box, +.load-tip { + text-align: center; + color: #999; + font-size: 26rpx; + padding: 80rpx 0; +} diff --git a/pages/fighter-msg/fighter-msg.js b/pages/fighter-msg/fighter-msg.js index 8088f2f..22ab72f 100644 --- a/pages/fighter-msg/fighter-msg.js +++ b/pages/fighter-msg/fighter-msg.js @@ -1,684 +1,684 @@ -// pages/penalty/penalty.js -import request from '../../utils/request.js' -const app = getApp() -const MEIYE_TIAOSHU = 5 - -Page({ - data: { - zongshu: 0, - daichuli: 0, - yichuli: 0, - shenfen: 0, - chufaList: [], - dangqianye: 1, - haiyougengduo: true, - jiazhaozhong: false, - jiazhaigengduo: false, - scrollHeight: 0, - showXiangqing: false, - xuanzhongChufa: {}, - showShensuModal: false, - shensuLiyou: '', - shensuTupian: [], - shensuTupianUrls: [], - shangchuanJindu: 0, - shangchuanZongshu: 0, - jinduWidth: '0%', - isShangchuanzhong: false, - ossImageUrl: '', - fangdouTimer: null - }, - - onLoad(options) { - this.registerNotificationComponent() - this.initOSSUrl() - this.jisuanGaodu() - this.chushihuaShuju() - }, - - - - - - - - onShow() { - this.registerNotificationComponent() - }, - - onUnload() { - if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer) - }, - - onReachBottom() { - this.shanglaShuaxin() - }, - - registerNotificationComponent() { - const notificationComp = this.selectComponent('#global-notification') - if (notificationComp && notificationComp.showNotification) { - app.globalData.globalNotification = { - show: (data) => notificationComp.showNotification(data), - hide: () => notificationComp.hideNotification() - } - } - }, - - initOSSUrl() { - const ossImageUrl = app.globalData.ossImageUrl || '' - this.setData({ ossImageUrl }) - }, - - jisuanGaodu() { - const systemInfo = wx.getSystemInfoSync() - const windowHeight = systemInfo.windowHeight - const height = windowHeight - 200 - 100 - 40 - this.setData({ - scrollHeight: height > 0 ? height : 400 - }) - }, - - chushihuaShuju() { - this.setData({ - dangqianye: 1, - chufaList: [], - haiyougengduo: true - }) - this.jiazhuoquTongji() - this.jiazhuoquChufaList() - }, - - async jiazhuoquTongji() { - try { - const res = await request({ - url: '/yonghu/dshqcfjl', - method: 'POST', - data: { qingqiu_tongji: true } - }) - if (res.statusCode === 200 && res.data.code === 0) { - const data = res.data.data - this.setData({ - zongshu: data.zongshu || 0, - daichuli: data.daichuli || 0, - yichuli: data.yichuli || 0 - }) - } - } catch (error) { - console.error('获取统计信息失败:', error) - } - }, - - async jiazhuoquChufaList(isLoadMore = false) { - if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return - if (isLoadMore) { - if (!this.data.haiyougengduo) return - this.setData({ jiazhaigengduo: true }) - } else { - this.setData({ jiazhaozhong: true }) - } - - try { - const params = { - page: this.data.dangqianye, - page_size: MEIYE_TIAOSHU - } - if (this.data.shenfen === 0) { - params.zhuangtai = 0 - } else { - params.zhuangtai = 1 - } - - const res = await request({ - url: '/yonghu/dshqcfjl', - method: 'POST', - data: params - }) - - //console.log('🔴【后端返回的数据】:', res.data) - - if (res.statusCode === 200 && res.data.code === 0) { - const data = res.data.data - - if (data.list && Array.isArray(data.list)) { - // 🔴【关键修复】在数据加载时就处理好显示字段 - const processedList = data.list.map(item => { - // 处理显示时间 - let displayTime = '--' - if (item.create_time) { - if (typeof item.create_time === 'number') { - displayTime = String(item.create_time) - } else if (typeof item.create_time === 'string') { - displayTime = item.create_time - } else { - displayTime = String(item.create_time) - } - } - - // 处理显示状态 - let displayStatus = '未知状态' - let statusClass = 'zhuangtai-weizhi' - const statusNum = Number(item.sqzhuangtai) - - if (!isNaN(statusNum)) { - if (statusNum === 0) { - displayStatus = '待处罚' - statusClass = 'zhuangtai-daichuli' - } else if (statusNum === 1) { - displayStatus = '已处罚' - statusClass = 'zhuangtai-yichufa' - } else if (statusNum === 2) { - displayStatus = '已驳回' - statusClass = 'zhuangtai-yibohui' - } else if (statusNum === 3) { - displayStatus = '申诉中' - statusClass = 'zhuangtai-shensuzhong' - } - } - - // 处理图片URL(在数据加载时就拼接完整URL) - const fullZhengjuTupian = (item.zhengju_tupian || []).map(url => this.getFullImageUrl(url)) - const fullShensuTupian = (item.shensu_tupian || []).map(url => this.getFullImageUrl(url)) - - return { - ...item, - display_time: displayTime, // 🔴 处理好的显示时间 - display_status: displayStatus, // 🔴 处理好的显示状态 - status_class: statusClass, // 🔴 处理好的状态样式类 - zhengju_tupian: item.zhengju_tupian || [], - shensu_tupian: item.shensu_tupian || [], - full_zhengju_tupian: fullZhengjuTupian, // 🔴 完整图片URL - full_shensu_tupian: fullShensuTupian // 🔴 完整图片URL - } - }) - - //console.log('🔴【处理后的数据】:', processedList) - - let newList - if (isLoadMore) { - newList = [...this.data.chufaList, ...processedList] - } else { - newList = processedList - } - - const hasMore = data.has_more === true - this.setData({ - chufaList: newList, - haiyougengduo: hasMore, - dangqianye: this.data.dangqianye + 1 - }) - } else { - this.setData({ haiyougengduo: false }) - } - } else { - throw new Error(res.data?.msg || '加载处罚记录失败') - } - } catch (error) { - console.error('加载处罚记录失败:', error) - wx.showToast({ - title: error.message || '加载失败', - icon: 'none' - }) - this.setData({ haiyougengduo: false }) - } finally { - if (isLoadMore) { - this.setData({ jiazhaigengduo: false }) - } else { - this.setData({ jiazhaozhong: false }) - } - } - }, - - qiehuanShenfen(e) { - const type = parseInt(e.currentTarget.dataset.type) - if (this.data.shenfen === type) return - this.setData({ - shenfen: type, - dangqianye: 1, - chufaList: [], - haiyougengduo: true - }) - this.jiazhuoquChufaList() - }, - - shanglaShuaxin() { - if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer) - const timer = setTimeout(() => { - if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return - if (!this.data.haiyougengduo) return - this.jiazhuoquChufaList(true) - }, 300) - this.setData({ fangdouTimer: timer }) - }, - - chakanXiangqing(e) { - const item = e.currentTarget.dataset.item - this.setData({ - xuanzhongChufa: item, - showXiangqing: true - }) - }, - - guanbiXiangqing() { - this.setData({ - showXiangqing: false, - xuanzhongChufa: {} - }) - }, - - openShensuModal() { - if (this.data.xuanzhongChufa.ssliyou || - (this.data.xuanzhongChufa.shensu_tupian && this.data.xuanzhongChufa.shensu_tupian.length > 0)) { - wx.showToast({ - title: '您已经提交过申诉,请等待处理结果', - icon: 'none' - }) - return - } - this.setData({ - showShensuModal: true, - shensuLiyou: '', - shensuTupian: [], - shensuTupianUrls: [], - shangchuanJindu: 0, - shangchuanZongshu: 0, - jinduWidth: '0%' - }) - }, - - closeShensuModal() { - this.setData({ - showShensuModal: false, - shensuLiyou: '', - shensuTupian: [], - shensuTupianUrls: [] - }) - }, - - onShensuLiyouInput(e) { - const value = e.detail.value.slice(0, 500) - this.setData({ shensuLiyou: value }) - }, - - chooseShensuTupian() { - if (this.data.isShangchuanzhong) return - const shengyu = 9 - this.data.shensuTupian.length - if (shengyu <= 0) { - wx.showToast({ - title: '最多只能上传9张图片', - icon: 'none' - }) - return - } - wx.chooseMedia({ - count: shengyu, - mediaType: ['image'], - sourceType: ['album', 'camera'], - success: (res) => { - const newTupian = [...this.data.shensuTupian, ...res.tempFiles.map(file => file.tempFilePath)] - this.setData({ shensuTupian: newTupian.slice(0, 9) }) - } - }) - }, - - deleteShensuTupian(e) { - const index = e.currentTarget.dataset.index - const newTupian = [...this.data.shensuTupian] - newTupian.splice(index, 1) - this.setData({ shensuTupian: newTupian }) - }, - - yulanShensuTupian(e) { - const index = e.currentTarget.dataset.index - const urls = this.data.shensuTupian - if (!urls[index]) return - wx.previewImage({ - current: urls[index], - urls: urls - }) - }, - - lianxiKefu() { - const kefuLink = app.globalData.kefuConfig?.link || ''; - const corpId = app.globalData.kefuConfig?.enterpriseId || ''; - - if (!kefuLink || !corpId) { - wx.showToast({ - title: '客服配置未加载', - icon: 'none' - }); - return; - } - - wx.openCustomerServiceChat({ - extInfo: { url: kefuLink }, - corpId: corpId, - success: (res) => { - console.log('跳转客服成功', res); - }, - fail: (err) => { - console.error('跳转客服失败', err); - wx.showToast({ - title: '暂时无法连接客服', - icon: 'none' - }); - } - }); - }, - - async getCOSZhengshu() { - try { - const res = await request({ - url: '/dingdan/dsscpz', - method: 'POST', - data: { - dingdan_id: this.data.xuanzhongChufa.dingdan_id, - yongtu: 'chufa' - } - }) - if (res.statusCode === 200 && res.data.code === 0 && res.data.data) { - return res.data.data - } else { - throw new Error(res?.data?.msg || '获取上传凭证失败') - } - } catch (error) { - console.error('获取COS凭证失败:', error) - throw error - } - }, - - initCOSClient(tokenData) { - const COS = require('../../utils/cos-wx-sdk-v5.js') - const credentials = tokenData.credentials || tokenData - const bucket = tokenData.bucket || 'julebu-1361527063' - const region = tokenData.region || 'ap-shanghai' - const cos = new COS({ - SimpleUploadMethod: 'putObject', - getAuthorization: async (options, callback) => { - const authParams = { - TmpSecretId: credentials.tmpSecretId, - TmpSecretKey: credentials.tmpSecretKey, - SecurityToken: credentials.sessionToken || '', - StartTime: tokenData.startTime || Math.floor(Date.now() / 1000), - ExpiredTime: tokenData.expiredTime || (Math.floor(Date.now() / 1000) + 7200) - } - callback(authParams) - } - }) - return { cos, bucket, region } - }, - - async shangchuanDanZhangTupian(filePath, cosKey, index, cosInstance, bucket, region) { - return new Promise((resolve) => { - const timeoutId = setTimeout(() => { - resolve({ status: 'optimistic_success', key: cosKey }) - }, 2000) - cosInstance.uploadFile({ - Bucket: bucket, - Region: region, - Key: cosKey, - FilePath: filePath, - onProgress: (progressInfo) => { - const percent = Math.round(progressInfo.percent * 100) - if (percent === 100) { - clearTimeout(timeoutId) - resolve({ status: 'optimistic_success', key: cosKey }) - } - } - }) - }) - }, - - async piliangShangchuanShensuTupian() { - const total = this.data.shensuTupian.length - if (total === 0) return [] - this.setData({ - shangchuanZongshu: total, - shangchuanJindu: 0, - jinduWidth: '0%', - isShangchuanzhong: true - }) - let yonghuid = '' - - // 方案1:直接从全局缓存获取 - const uid = wx.getStorageSync('uid') - if (uid) { - yonghuid = String(uid).padStart(7, '0') - //console.log('🔴【从uid获取yonghuid】', yonghuid) - } - - // 方案2:如果还没有,从用户信息获取 - if (!yonghuid && app.globalData.userInfo && app.globalData.userInfo.yonghuid) { - yonghuid = String(app.globalData.userInfo.yonghuid).padStart(7, '0') - //console.log('🔴【从userInfo获取yonghuid】', yonghuid) - } - - // 方案3:如果还没有,直接提示并返回 - if (!yonghuid) { - wx.showToast({ - title: '请先登录', - icon: 'none' - }) - this.setData({ isShangchuanzhong: false }) - return [] - } - - - - const preGeneratedUrls = [] - for (let i = 0; i < total; i++) { - const timestamp = Date.now() + i - const random = Math.floor(Math.random() * 10000) - const fileExt = this.getFileExtension(this.data.shensuTupian[i]) - const fileName = `${yonghuid}_${timestamp}_${random}${fileExt}` - const cosKey = `a_long/chfajltp/dssstp/${fileName}` - preGeneratedUrls.push(cosKey) - } - - let cosClient, bucket, region - try { - const tokenData = await this.getCOSZhengshu() - const { cos, bucket: cosBucket, region: cosRegion } = this.initCOSClient(tokenData) - cosClient = cos - bucket = cosBucket - region = cosRegion - } catch (error) { - wx.showToast({ title: '上传初始化失败', icon: 'none' }) - this.setData({ isShangchuanzhong: false }) - return [] - } - - const uploadTasks = [] - for (let i = 0; i < total; i++) { - const task = this.shangchuanDanZhangTupian( - this.data.shensuTupian[i], - preGeneratedUrls[i], - i, - cosClient, - bucket, - region - ).then((result) => { - const currentDone = i + 1 - const jinduPercent = ((currentDone / total) * 100).toFixed(0) - this.setData({ - shangchuanJindu: currentDone, - jinduWidth: `${jinduPercent}%` - }) - return result - }) - uploadTasks.push(task) - } - await Promise.all(uploadTasks) - this.setData({ isShangchuanzhong: false }) - return preGeneratedUrls - }, - - getFileExtension(filePath) { - const lastDotIndex = filePath.lastIndexOf('.') - if (lastDotIndex === -1) return '.jpg' - const ext = filePath.substring(lastDotIndex).toLowerCase() - const allowedExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'] - return allowedExts.includes(ext) ? ext : '.jpg' - }, - - async submitShensu() { - if (!this.data.shensuLiyou.trim()) { - wx.showToast({ title: '请输入申诉理由', icon: 'none' }); return - } - if (this.data.shensuTupian.length === 0) { - wx.showModal({ - title: '提示', - content: '未上传任何图片,确定要提交申诉吗?', - success: async (res) => { - if (res.confirm) await this.tijiaoShensuData([]) - } - }) - return - } - wx.showToast({ title: '开始上传图片...', icon: 'none' }) - try { - const tupianUrls = await this.piliangShangchuanShensuTupian() - if (!tupianUrls || tupianUrls.length === 0) throw new Error('图片上传失败') - await this.tijiaoShensuData(tupianUrls) - } catch (error) { - console.error('提交申诉失败:', error) - wx.showToast({ title: error.message || '提交失败', icon: 'none' }) - } - }, - - async tijiaoShensuData(tupianUrls) { - wx.showToast({ title: '提交申诉中...', icon: 'none' }) - try { - const res = await request({ - url: '/yonghu/dscfss', - method: 'POST', - data: { - chufa_id: this.data.xuanzhongChufa.id, - shensu_liyou: this.data.shensuLiyou, - shensu_tupian_urls: tupianUrls - } - }) - if (res.statusCode === 200 && res.data.code === 0) { - this.updateChufaRecord({ - sqzhuangtai: 3, - ssliyou: this.data.shensuLiyou, - shensu_tupian: tupianUrls - }) - this.setData({ - showShensuModal: false, - showXiangqing: false, - shensuLiyou: '', - shensuTupian: [], - xuanzhongChufa: {} - }) - wx.showToast({ title: '申诉提交成功,请等待处理', icon: 'success' }) - setTimeout(() => { this.chushihuaShuju() }, 500) - } else { - throw new Error(res.data?.msg || '提交申诉失败') - } - } catch (error) { - console.error('提交申诉数据失败:', error) - wx.showToast({ title: error.message || '提交失败', icon: 'none' }) - } - }, - - updateChufaRecord(newData) { - const newList = this.data.chufaList.map(item => { - if (item.id === this.data.xuanzhongChufa.id) { - const updatedItem = { ...item, ...newData } - // 🔴 更新显示字段 - if (newData.sqzhuangtai === 3) { - updatedItem.display_status = '申诉中' - updatedItem.status_class = 'zhuangtai-shensuzhong' - } - if (newData.shensu_tupian) { - updatedItem.full_shensu_tupian = newData.shensu_tupian.map(url => this.getFullImageUrl(url)) - } - return updatedItem - } - return item - }) - this.setData({ chufaList: newList }) - }, - - // 🔴【图片URL拼接 - 确保返回完整URL】 - getFullImageUrl(relativeUrl) { - //console.log('🔴【图片拼接】输入:', relativeUrl) - - if (!relativeUrl) return '' - if (relativeUrl.startsWith('http')) { - //console.log('🔴【已经是完整URL】返回:', relativeUrl) - return relativeUrl - } - - const ossUrl = this.data.ossImageUrl - if (!ossUrl) { - //console.log('🔴【OSS地址为空】返回原始URL:', relativeUrl) - return relativeUrl - } - - let fullUrl - if (ossUrl.endsWith('/') && relativeUrl.startsWith('/')) { - fullUrl = ossUrl + relativeUrl.substring(1) - } else if (!ossUrl.endsWith('/') && !relativeUrl.startsWith('/')) { - fullUrl = ossUrl + '/' + relativeUrl - } else { - fullUrl = ossUrl + relativeUrl - } - - // console.log('🔴【拼接后URL】:', fullUrl) - return fullUrl - }, - - yulanTupian(e) { - const currentUrl = e.currentTarget.dataset.url - const urls = e.currentTarget.dataset.urls || [] - if (!currentUrl) return - - const fullCurrentUrl = currentUrl.startsWith('http') ? currentUrl : this.getFullImageUrl(currentUrl) - const fullUrls = urls.map(url => url.startsWith('http') ? url : this.getFullImageUrl(url)) - - wx.previewImage({ - current: fullCurrentUrl, - urls: fullUrls.length > 0 ? fullUrls : [fullCurrentUrl] - }) - }, - - fuzhiWenben(e) { - const text = e.currentTarget.dataset.text - if (!text) return - - wx.setClipboardData({ - data: text, - success: () => { - wx.showToast({ title: '复制成功', icon: 'success' }) - }, - fail: () => { - wx.showToast({ title: '复制失败', icon: 'none' }) - } - }) - }, - - // 🔴【保留原函数但已不再使用(用于向后兼容)】 - formatShijian(shijian) { - if (!shijian) return '--' - return String(shijian) - }, - - getZhuangtaiText(zhuangtai) { - const statusNum = Number(zhuangtai) - if (statusNum === 0) return '待处罚' - if (statusNum === 1) return '已处罚' - if (statusNum === 2) return '已驳回' - if (statusNum === 3) return '申诉中' - return '未知状态' - }, - - getZhuangtaiClass(zhuangtai) { - const statusNum = Number(zhuangtai) - if (statusNum === 0) return 'zhuangtai-daichuli' - if (statusNum === 1) return 'zhuangtai-yichufa' - if (statusNum === 2) return 'zhuangtai-yibohui' - if (statusNum === 3) return 'zhuangtai-shensuzhong' - return 'zhuangtai-weizhi' - } +// pages/penalty/penalty.js +import request from '../../utils/request.js' +const app = getApp() +const MEIYE_TIAOSHU = 5 + +Page({ + data: { + zongshu: 0, + daichuli: 0, + yichuli: 0, + shenfen: 0, + chufaList: [], + dangqianye: 1, + haiyougengduo: true, + jiazhaozhong: false, + jiazhaigengduo: false, + scrollHeight: 0, + showXiangqing: false, + xuanzhongChufa: {}, + showShensuModal: false, + shensuLiyou: '', + shensuTupian: [], + shensuTupianUrls: [], + shangchuanJindu: 0, + shangchuanZongshu: 0, + jinduWidth: '0%', + isShangchuanzhong: false, + ossImageUrl: '', + fangdouTimer: null + }, + + onLoad(options) { + this.registerNotificationComponent() + this.initOSSUrl() + this.jisuanGaodu() + this.chushihuaShuju() + }, + + + + + + + + onShow() { + this.registerNotificationComponent() + }, + + onUnload() { + if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer) + }, + + onReachBottom() { + this.shanglaShuaxin() + }, + + registerNotificationComponent() { + const notificationComp = this.selectComponent('#global-notification') + if (notificationComp && notificationComp.showNotification) { + app.globalData.globalNotification = { + show: (data) => notificationComp.showNotification(data), + hide: () => notificationComp.hideNotification() + } + } + }, + + initOSSUrl() { + const ossImageUrl = app.globalData.ossImageUrl || '' + this.setData({ ossImageUrl }) + }, + + jisuanGaodu() { + const systemInfo = wx.getSystemInfoSync() + const windowHeight = systemInfo.windowHeight + const height = windowHeight - 200 - 100 - 40 + this.setData({ + scrollHeight: height > 0 ? height : 400 + }) + }, + + chushihuaShuju() { + this.setData({ + dangqianye: 1, + chufaList: [], + haiyougengduo: true + }) + this.jiazhuoquTongji() + this.jiazhuoquChufaList() + }, + + async jiazhuoquTongji() { + try { + const res = await request({ + url: '/yonghu/dshqcfjl', + method: 'POST', + data: { qingqiu_tongji: true } + }) + if (res.statusCode === 200 && res.data.code === 0) { + const data = res.data.data + this.setData({ + zongshu: data.zongshu || 0, + daichuli: data.daichuli || 0, + yichuli: data.yichuli || 0 + }) + } + } catch (error) { + console.error('获取统计信息失败:', error) + } + }, + + async jiazhuoquChufaList(isLoadMore = false) { + if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return + if (isLoadMore) { + if (!this.data.haiyougengduo) return + this.setData({ jiazhaigengduo: true }) + } else { + this.setData({ jiazhaozhong: true }) + } + + try { + const params = { + page: this.data.dangqianye, + page_size: MEIYE_TIAOSHU + } + if (this.data.shenfen === 0) { + params.zhuangtai = 0 + } else { + params.zhuangtai = 1 + } + + const res = await request({ + url: '/yonghu/dshqcfjl', + method: 'POST', + data: params + }) + + //console.log('🔴【后端返回的数据】:', res.data) + + if (res.statusCode === 200 && res.data.code === 0) { + const data = res.data.data + + if (data.list && Array.isArray(data.list)) { + // 🔴【关键修复】在数据加载时就处理好显示字段 + const processedList = data.list.map(item => { + // 处理显示时间 + let displayTime = '--' + if (item.create_time) { + if (typeof item.create_time === 'number') { + displayTime = String(item.create_time) + } else if (typeof item.create_time === 'string') { + displayTime = item.create_time + } else { + displayTime = String(item.create_time) + } + } + + // 处理显示状态 + let displayStatus = '未知状态' + let statusClass = 'zhuangtai-weizhi' + const statusNum = Number(item.sqzhuangtai) + + if (!isNaN(statusNum)) { + if (statusNum === 0) { + displayStatus = '待处罚' + statusClass = 'zhuangtai-daichuli' + } else if (statusNum === 1) { + displayStatus = '已处罚' + statusClass = 'zhuangtai-yichufa' + } else if (statusNum === 2) { + displayStatus = '已驳回' + statusClass = 'zhuangtai-yibohui' + } else if (statusNum === 3) { + displayStatus = '申诉中' + statusClass = 'zhuangtai-shensuzhong' + } + } + + // 处理图片URL(在数据加载时就拼接完整URL) + const fullZhengjuTupian = (item.zhengju_tupian || []).map(url => this.getFullImageUrl(url)) + const fullShensuTupian = (item.shensu_tupian || []).map(url => this.getFullImageUrl(url)) + + return { + ...item, + display_time: displayTime, // 🔴 处理好的显示时间 + display_status: displayStatus, // 🔴 处理好的显示状态 + status_class: statusClass, // 🔴 处理好的状态样式类 + zhengju_tupian: item.zhengju_tupian || [], + shensu_tupian: item.shensu_tupian || [], + full_zhengju_tupian: fullZhengjuTupian, // 🔴 完整图片URL + full_shensu_tupian: fullShensuTupian // 🔴 完整图片URL + } + }) + + //console.log('🔴【处理后的数据】:', processedList) + + let newList + if (isLoadMore) { + newList = [...this.data.chufaList, ...processedList] + } else { + newList = processedList + } + + const hasMore = data.has_more === true + this.setData({ + chufaList: newList, + haiyougengduo: hasMore, + dangqianye: this.data.dangqianye + 1 + }) + } else { + this.setData({ haiyougengduo: false }) + } + } else { + throw new Error(res.data?.msg || '加载处罚记录失败') + } + } catch (error) { + console.error('加载处罚记录失败:', error) + wx.showToast({ + title: error.message || '加载失败', + icon: 'none' + }) + this.setData({ haiyougengduo: false }) + } finally { + if (isLoadMore) { + this.setData({ jiazhaigengduo: false }) + } else { + this.setData({ jiazhaozhong: false }) + } + } + }, + + qiehuanShenfen(e) { + const type = parseInt(e.currentTarget.dataset.type) + if (this.data.shenfen === type) return + this.setData({ + shenfen: type, + dangqianye: 1, + chufaList: [], + haiyougengduo: true + }) + this.jiazhuoquChufaList() + }, + + shanglaShuaxin() { + if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer) + const timer = setTimeout(() => { + if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return + if (!this.data.haiyougengduo) return + this.jiazhuoquChufaList(true) + }, 300) + this.setData({ fangdouTimer: timer }) + }, + + chakanXiangqing(e) { + const item = e.currentTarget.dataset.item + this.setData({ + xuanzhongChufa: item, + showXiangqing: true + }) + }, + + guanbiXiangqing() { + this.setData({ + showXiangqing: false, + xuanzhongChufa: {} + }) + }, + + openShensuModal() { + if (this.data.xuanzhongChufa.ssliyou || + (this.data.xuanzhongChufa.shensu_tupian && this.data.xuanzhongChufa.shensu_tupian.length > 0)) { + wx.showToast({ + title: '您已经提交过申诉,请等待处理结果', + icon: 'none' + }) + return + } + this.setData({ + showShensuModal: true, + shensuLiyou: '', + shensuTupian: [], + shensuTupianUrls: [], + shangchuanJindu: 0, + shangchuanZongshu: 0, + jinduWidth: '0%' + }) + }, + + closeShensuModal() { + this.setData({ + showShensuModal: false, + shensuLiyou: '', + shensuTupian: [], + shensuTupianUrls: [] + }) + }, + + onShensuLiyouInput(e) { + const value = e.detail.value.slice(0, 500) + this.setData({ shensuLiyou: value }) + }, + + chooseShensuTupian() { + if (this.data.isShangchuanzhong) return + const shengyu = 9 - this.data.shensuTupian.length + if (shengyu <= 0) { + wx.showToast({ + title: '最多只能上传9张图片', + icon: 'none' + }) + return + } + wx.chooseMedia({ + count: shengyu, + mediaType: ['image'], + sourceType: ['album', 'camera'], + success: (res) => { + const newTupian = [...this.data.shensuTupian, ...res.tempFiles.map(file => file.tempFilePath)] + this.setData({ shensuTupian: newTupian.slice(0, 9) }) + } + }) + }, + + deleteShensuTupian(e) { + const index = e.currentTarget.dataset.index + const newTupian = [...this.data.shensuTupian] + newTupian.splice(index, 1) + this.setData({ shensuTupian: newTupian }) + }, + + yulanShensuTupian(e) { + const index = e.currentTarget.dataset.index + const urls = this.data.shensuTupian + if (!urls[index]) return + wx.previewImage({ + current: urls[index], + urls: urls + }) + }, + + lianxiKefu() { + const kefuLink = app.globalData.kefuConfig?.link || ''; + const corpId = app.globalData.kefuConfig?.enterpriseId || ''; + + if (!kefuLink || !corpId) { + wx.showToast({ + title: '客服配置未加载', + icon: 'none' + }); + return; + } + + wx.openCustomerServiceChat({ + extInfo: { url: kefuLink }, + corpId: corpId, + success: (res) => { + console.log('跳转客服成功', res); + }, + fail: (err) => { + console.error('跳转客服失败', err); + wx.showToast({ + title: '暂时无法连接客服', + icon: 'none' + }); + } + }); + }, + + async getCOSZhengshu() { + try { + const res = await request({ + url: '/dingdan/dsscpz', + method: 'POST', + data: { + dingdan_id: this.data.xuanzhongChufa.dingdan_id, + yongtu: 'chufa' + } + }) + if (res.statusCode === 200 && res.data.code === 0 && res.data.data) { + return res.data.data + } else { + throw new Error(res?.data?.msg || '获取上传凭证失败') + } + } catch (error) { + console.error('获取COS凭证失败:', error) + throw error + } + }, + + initCOSClient(tokenData) { + const COS = require('../../utils/cos-wx-sdk-v5.min.js') + const credentials = tokenData.credentials || tokenData + const bucket = tokenData.bucket || 'julebu-1361527063' + const region = tokenData.region || 'ap-shanghai' + const cos = new COS({ + SimpleUploadMethod: 'putObject', + getAuthorization: async (options, callback) => { + const authParams = { + TmpSecretId: credentials.tmpSecretId, + TmpSecretKey: credentials.tmpSecretKey, + SecurityToken: credentials.sessionToken || '', + StartTime: tokenData.startTime || Math.floor(Date.now() / 1000), + ExpiredTime: tokenData.expiredTime || (Math.floor(Date.now() / 1000) + 7200) + } + callback(authParams) + } + }) + return { cos, bucket, region } + }, + + async shangchuanDanZhangTupian(filePath, cosKey, index, cosInstance, bucket, region) { + return new Promise((resolve) => { + const timeoutId = setTimeout(() => { + resolve({ status: 'optimistic_success', key: cosKey }) + }, 2000) + cosInstance.uploadFile({ + Bucket: bucket, + Region: region, + Key: cosKey, + FilePath: filePath, + onProgress: (progressInfo) => { + const percent = Math.round(progressInfo.percent * 100) + if (percent === 100) { + clearTimeout(timeoutId) + resolve({ status: 'optimistic_success', key: cosKey }) + } + } + }) + }) + }, + + async piliangShangchuanShensuTupian() { + const total = this.data.shensuTupian.length + if (total === 0) return [] + this.setData({ + shangchuanZongshu: total, + shangchuanJindu: 0, + jinduWidth: '0%', + isShangchuanzhong: true + }) + let yonghuid = '' + + // 方案1:直接从全局缓存获取 + const uid = wx.getStorageSync('uid') + if (uid) { + yonghuid = String(uid).padStart(7, '0') + //console.log('🔴【从uid获取yonghuid】', yonghuid) + } + + // 方案2:如果还没有,从用户信息获取 + if (!yonghuid && app.globalData.userInfo && app.globalData.userInfo.yonghuid) { + yonghuid = String(app.globalData.userInfo.yonghuid).padStart(7, '0') + //console.log('🔴【从userInfo获取yonghuid】', yonghuid) + } + + // 方案3:如果还没有,直接提示并返回 + if (!yonghuid) { + wx.showToast({ + title: '请先登录', + icon: 'none' + }) + this.setData({ isShangchuanzhong: false }) + return [] + } + + + + const preGeneratedUrls = [] + for (let i = 0; i < total; i++) { + const timestamp = Date.now() + i + const random = Math.floor(Math.random() * 10000) + const fileExt = this.getFileExtension(this.data.shensuTupian[i]) + const fileName = `${yonghuid}_${timestamp}_${random}${fileExt}` + const cosKey = `a_long/chfajltp/dssstp/${fileName}` + preGeneratedUrls.push(cosKey) + } + + let cosClient, bucket, region + try { + const tokenData = await this.getCOSZhengshu() + const { cos, bucket: cosBucket, region: cosRegion } = this.initCOSClient(tokenData) + cosClient = cos + bucket = cosBucket + region = cosRegion + } catch (error) { + wx.showToast({ title: '上传初始化失败', icon: 'none' }) + this.setData({ isShangchuanzhong: false }) + return [] + } + + const uploadTasks = [] + for (let i = 0; i < total; i++) { + const task = this.shangchuanDanZhangTupian( + this.data.shensuTupian[i], + preGeneratedUrls[i], + i, + cosClient, + bucket, + region + ).then((result) => { + const currentDone = i + 1 + const jinduPercent = ((currentDone / total) * 100).toFixed(0) + this.setData({ + shangchuanJindu: currentDone, + jinduWidth: `${jinduPercent}%` + }) + return result + }) + uploadTasks.push(task) + } + await Promise.all(uploadTasks) + this.setData({ isShangchuanzhong: false }) + return preGeneratedUrls + }, + + getFileExtension(filePath) { + const lastDotIndex = filePath.lastIndexOf('.') + if (lastDotIndex === -1) return '.jpg' + const ext = filePath.substring(lastDotIndex).toLowerCase() + const allowedExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'] + return allowedExts.includes(ext) ? ext : '.jpg' + }, + + async submitShensu() { + if (!this.data.shensuLiyou.trim()) { + wx.showToast({ title: '请输入申诉理由', icon: 'none' }); return + } + if (this.data.shensuTupian.length === 0) { + wx.showModal({ + title: '提示', + content: '未上传任何图片,确定要提交申诉吗?', + success: async (res) => { + if (res.confirm) await this.tijiaoShensuData([]) + } + }) + return + } + wx.showToast({ title: '开始上传图片...', icon: 'none' }) + try { + const tupianUrls = await this.piliangShangchuanShensuTupian() + if (!tupianUrls || tupianUrls.length === 0) throw new Error('图片上传失败') + await this.tijiaoShensuData(tupianUrls) + } catch (error) { + console.error('提交申诉失败:', error) + wx.showToast({ title: error.message || '提交失败', icon: 'none' }) + } + }, + + async tijiaoShensuData(tupianUrls) { + wx.showToast({ title: '提交申诉中...', icon: 'none' }) + try { + const res = await request({ + url: '/yonghu/dscfss', + method: 'POST', + data: { + chufa_id: this.data.xuanzhongChufa.id, + shensu_liyou: this.data.shensuLiyou, + shensu_tupian_urls: tupianUrls + } + }) + if (res.statusCode === 200 && res.data.code === 0) { + this.updateChufaRecord({ + sqzhuangtai: 3, + ssliyou: this.data.shensuLiyou, + shensu_tupian: tupianUrls + }) + this.setData({ + showShensuModal: false, + showXiangqing: false, + shensuLiyou: '', + shensuTupian: [], + xuanzhongChufa: {} + }) + wx.showToast({ title: '申诉提交成功,请等待处理', icon: 'success' }) + setTimeout(() => { this.chushihuaShuju() }, 500) + } else { + throw new Error(res.data?.msg || '提交申诉失败') + } + } catch (error) { + console.error('提交申诉数据失败:', error) + wx.showToast({ title: error.message || '提交失败', icon: 'none' }) + } + }, + + updateChufaRecord(newData) { + const newList = this.data.chufaList.map(item => { + if (item.id === this.data.xuanzhongChufa.id) { + const updatedItem = { ...item, ...newData } + // 🔴 更新显示字段 + if (newData.sqzhuangtai === 3) { + updatedItem.display_status = '申诉中' + updatedItem.status_class = 'zhuangtai-shensuzhong' + } + if (newData.shensu_tupian) { + updatedItem.full_shensu_tupian = newData.shensu_tupian.map(url => this.getFullImageUrl(url)) + } + return updatedItem + } + return item + }) + this.setData({ chufaList: newList }) + }, + + // 🔴【图片URL拼接 - 确保返回完整URL】 + getFullImageUrl(relativeUrl) { + //console.log('🔴【图片拼接】输入:', relativeUrl) + + if (!relativeUrl) return '' + if (relativeUrl.startsWith('http')) { + //console.log('🔴【已经是完整URL】返回:', relativeUrl) + return relativeUrl + } + + const ossUrl = this.data.ossImageUrl + if (!ossUrl) { + //console.log('🔴【OSS地址为空】返回原始URL:', relativeUrl) + return relativeUrl + } + + let fullUrl + if (ossUrl.endsWith('/') && relativeUrl.startsWith('/')) { + fullUrl = ossUrl + relativeUrl.substring(1) + } else if (!ossUrl.endsWith('/') && !relativeUrl.startsWith('/')) { + fullUrl = ossUrl + '/' + relativeUrl + } else { + fullUrl = ossUrl + relativeUrl + } + + // console.log('🔴【拼接后URL】:', fullUrl) + return fullUrl + }, + + yulanTupian(e) { + const currentUrl = e.currentTarget.dataset.url + const urls = e.currentTarget.dataset.urls || [] + if (!currentUrl) return + + const fullCurrentUrl = currentUrl.startsWith('http') ? currentUrl : this.getFullImageUrl(currentUrl) + const fullUrls = urls.map(url => url.startsWith('http') ? url : this.getFullImageUrl(url)) + + wx.previewImage({ + current: fullCurrentUrl, + urls: fullUrls.length > 0 ? fullUrls : [fullCurrentUrl] + }) + }, + + fuzhiWenben(e) { + const text = e.currentTarget.dataset.text + if (!text) return + + wx.setClipboardData({ + data: text, + success: () => { + wx.showToast({ title: '复制成功', icon: 'success' }) + }, + fail: () => { + wx.showToast({ title: '复制失败', icon: 'none' }) + } + }) + }, + + // 🔴【保留原函数但已不再使用(用于向后兼容)】 + formatShijian(shijian) { + if (!shijian) return '--' + return String(shijian) + }, + + getZhuangtaiText(zhuangtai) { + const statusNum = Number(zhuangtai) + if (statusNum === 0) return '待处罚' + if (statusNum === 1) return '已处罚' + if (statusNum === 2) return '已驳回' + if (statusNum === 3) return '申诉中' + return '未知状态' + }, + + getZhuangtaiClass(zhuangtai) { + const statusNum = Number(zhuangtai) + if (statusNum === 0) return 'zhuangtai-daichuli' + if (statusNum === 1) return 'zhuangtai-yichufa' + if (statusNum === 2) return 'zhuangtai-yibohui' + if (statusNum === 3) return 'zhuangtai-shensuzhong' + return 'zhuangtai-weizhi' + } }) \ No newline at end of file diff --git a/pages/fighter-msg/fighter-msg.wxss b/pages/fighter-msg/fighter-msg.wxss index 21bbf83..2c19a0c 100644 --- a/pages/fighter-msg/fighter-msg.wxss +++ b/pages/fighter-msg/fighter-msg.wxss @@ -296,9 +296,9 @@ } .zhuangtai-shensuzhong { - background: rgba(147, 51, 234, 0.2); - border-color: #9333ea; - box-shadow: 0 0 8rpx rgba(147, 51, 234, 0.2); + background: rgba(255, 165, 0, 0.2); + border-color: #FFA500; + box-shadow: 0 0 8rpx rgba(255, 165, 0, 0.2); } .zhuangtai-weizhi { @@ -657,9 +657,9 @@ } .cbtn-shensu { - background: rgba(147, 51, 234, 0.1); - border-color: #9333ea; - box-shadow: 0 0 12rpx rgba(147, 51, 234, 0.2); + background: rgba(255, 165, 0, 0.1); + border-color: #FFA500; + box-shadow: 0 0 12rpx rgba(255, 165, 0, 0.2); } .cbtn-shensu-disabled { diff --git a/pages/fighter-order-detail/fighter-order-detail.js b/pages/fighter-order-detail/fighter-order-detail.js index 582e5f1..7e8951a 100644 --- a/pages/fighter-order-detail/fighter-order-detail.js +++ b/pages/fighter-order-detail/fighter-order-detail.js @@ -1,741 +1,727 @@ -// pages/fighter-order-detail/fighter-order-detail.js - 【最终版:提交前公告弹窗 + 修改图片至少保留一张】 -const app = getApp() -import request from '../../utils/request.js' -const COS = require('../../utils/cos-wx-sdk-v5.js') - -Page({ - data: { - jibenShuju: { - dingdan_id: '', - tupian: '', - jieshao: '', - create_time: '', - jine: '', - nicheng: '', - beizhu: '', - zhuangtai: 0, - fadanpingtai: 0 - }, - xiangxiShuju: { - shangjia_id: '', - shangjia_nicheng: '', - shangjia_liuyan: '', - tuikuan_liyou: '', - chufa_liyou: '', - chufa_zhuangtai: 0, - chufa_jieguo: '', - bohui_liyou: '', - dashou_jiaofu: [], - dashou_liuyan: '', - laoban_id: '' - }, - zhuangtaiYanseMap: { - 1: '#E8F5E9', 2: '#E3F2FD', 3: '#F3E5F5', 4: '#F3E8FF', - 5: '#FFEBEE', 6: '#EFEBE9', 7: '#E0F7FA', 8: '#F5F3FF' - }, - zhuangtaiWenziMap: { - 1: '已下单', 2: '进行中', 3: '已完成', 4: '退款中', - 5: '已退款', 6: '退款失败', 7: '指定中', 8: '结算中' - }, - isLoading: true, - isSubmitting: false, - liuyan: '', - xuanzhongTupian: [], - shangchuanJindu: 0, - shangchuanZongshu: 0, - jinduWidth: '0%', - showWanzhengJieshao: false, - wanzhengJieshao: '', - showWanzhengBeizhu: false, - wanzhengBeizhu: '', - ossImageUrl: '', - morentouxiang: '', - showRefundInfo: false, - - // 修改功能所需字段 - isModifying: false, - originalDashouJiaofu: [], - originalDashouLiuyan: '', - remainingOriginals: [], - modifiedLiuyan: '', - deletedImages: [], - newImages: [], - isConfirming: false, - modifyUploadProgress: { total: 0, current: 0, width: '0%' } - }, - - onLoad(options) { - wx.setNavigationBarTitle({ title: '订单详情' }) - this.initGlobalData() - this.jiexiTiaozhuanCanshu(options) - }, - onHide() { - const popupComp = this.selectComponent('#popupNotice'); - if (popupComp && popupComp.cleanup) { - popupComp.cleanup(); - } - }, - - onShow() { - 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() - }; - } - }, - - initGlobalData() { - const app = getApp() - const ossImageUrl = app.globalData.ossImageUrl || 'https://your-oss-domain.com/' - const morentouxiang = app.globalData.morentouxiang || '/images/default-avatar.png' - this.setData({ ossImageUrl, morentouxiang }) - }, - - jiexiTiaozhuanCanshu(options) { - try { - const dingdanDataStr = options.dingdanData || '' - if (!dingdanDataStr) { - wx.showToast({ title: '订单数据错误', icon: 'none' }) - setTimeout(() => wx.navigateBack(), 1500) - return - } - const dingdanData = JSON.parse(decodeURIComponent(dingdanDataStr)) - let tupianUrl = dingdanData.tupian || '' - if (!tupianUrl) tupianUrl = this.getTupianUrl() - let jine = parseFloat(dingdanData.jine || 0) - if (isNaN(jine)) jine = 0 - const formattedJine = jine.toFixed(2) - this.setData({ - 'jibenShuju.dingdan_id': dingdanData.dingdan_id || '', - 'jibenShuju.tupian': tupianUrl, - 'jibenShuju.jieshao': dingdanData.jieshao || '', - 'jibenShuju.create_time': dingdanData.create_time || '', - 'jibenShuju.jine': formattedJine, - 'jibenShuju.nicheng': dingdanData.nicheng || '', - 'jibenShuju.beizhu': dingdanData.beizhu || '', - 'jibenShuju.zhuangtai': dingdanData.zhuangtai || 0, - 'jibenShuju.fadanpingtai': dingdanData.fadanpingtai || 0, - isLoading: false - }) - this.jiazaiXiangxiShuju(dingdanData.dingdan_id) - } catch (error) { - console.error('解析订单数据失败:', error) - wx.showToast({ title: '数据解析失败', icon: 'none' }) - setTimeout(() => wx.navigateBack(), 1500) - } - }, - - getTupianUrl() { - const cacheTouxiang = wx.getStorageSync('touxiang') || '' - let tupianPath = cacheTouxiang || app.globalData.morentouxiang || '' - const ossImageUrl = app.globalData.ossImageUrl || '' - if (tupianPath && ossImageUrl) { - if (tupianPath.startsWith('/')) tupianPath = tupianPath.substring(1) - return ossImageUrl + tupianPath - } - return '' - }, - - async jiazaiXiangxiShuju(dingdanId) { - if (!dingdanId) return - wx.showLoading({ title: '加载中...', mask: true }) - try { - const res = await request({ - url: '/dingdan/dsddxq', - method: 'POST', - data: { dingdan_id: dingdanId }, - header: { 'content-type': 'application/json' } - }) - wx.hideLoading() - if (res && res.data.code === 0 && res.data.data) { - const data = res.data.data - let dashouJiaofuList = data.dashou_jiaofu || [] - dashouJiaofuList = dashouJiaofuList.map(imgUrl => { - if (imgUrl && !imgUrl.startsWith('http')) { - const ossImageUrl = app.globalData.ossImageUrl || '' - if (imgUrl.startsWith('/')) imgUrl = imgUrl.substring(1) - return ossImageUrl + imgUrl - } - return imgUrl - }) - this.setData({ - 'xiangxiShuju.shangjia_id': data.shangjia_id || '', - 'xiangxiShuju.shangjia_nicheng': data.shangjia_nicheng || '', - 'xiangxiShuju.shangjia_liuyan': data.shangjia_liuyan || '', - 'xiangxiShuju.tuikuan_liyou': data.tuikuan_liyou || '', - 'xiangxiShuju.chufa_liyou': data.chufa_liyou || '', - 'xiangxiShuju.chufa_zhuangtai': data.chufa_zhuangtai || 0, - 'xiangxiShuju.chufa_jieguo': data.chufa_jieguo || '', - 'xiangxiShuju.bohui_liyou': data.bohui_liyou || '', - 'xiangxiShuju.dashou_jiaofu': dashouJiaofuList, - 'xiangxiShuju.dashou_liuyan': data.dashou_liuyan || '', - 'xiangxiShuju.laoban_id': data.laoban_id || '', - 'jibenShuju.zhuangtai': data.zhuangtai || 0, - 'jibenShuju.beizhu': data.beizhu || '', - 'liuyan': data.dashou_liuyan || '', - 'jibenShuju.fadanpingtai': data.fadanpingtai || 0 - }) - const currentStatus = data.zhuangtai || 0 - const showRefund = [4,5,6].includes(currentStatus) - this.setData({ showRefundInfo: showRefund }) - } else { - const errorMsg = res?.data?.msg || '获取订单详情失败' - wx.showToast({ title: errorMsg, icon: 'none', duration: 2000 }) - } - } catch (error) { - console.error('加载详细数据失败:', error) - wx.hideLoading() - wx.showToast({ title: '加载数据失败,请重试', icon: 'none', duration: 2000 }) - } - }, - - // ========== 原有提交功能(部分修改) ========== - xuanzeTupian() { - if (this.data.isSubmitting) return - const shengyu = 9 - this.data.xuanzhongTupian.length - if (shengyu <= 0) { - wx.showToast({ title: '最多只能选择9张图片', icon: 'none' }) - return - } - wx.chooseImage({ - count: shengyu, - sizeType: ['compressed'], - sourceType: ['album', 'camera'], - success: (res) => { - const newTupian = [...this.data.xuanzhongTupian, ...res.tempFilePaths] - this.setData({ xuanzhongTupian: newTupian.slice(0, 9) }) - } - }) - }, - shanchuTupian(e) { - const index = e.currentTarget.dataset.index - const newTupian = [...this.data.xuanzhongTupian] - newTupian.splice(index, 1) - this.setData({ xuanzhongTupian: newTupian }) - }, - yulanTupian(e) { - const currentUrl = e.currentTarget.dataset.url - const urls = e.currentTarget.dataset.urls || [] - if (!currentUrl) return - wx.previewImage({ current: currentUrl, urls: urls.length > 0 ? urls : [currentUrl] }) - }, - getFileExtension(filePath) { - const lastDotIndex = filePath.lastIndexOf('.') - if (lastDotIndex === -1) return '.jpg' - const ext = filePath.substring(lastDotIndex).toLowerCase() - const allowedExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'] - return allowedExts.includes(ext) ? ext : '.jpg' - }, - shuruLiuyan(e) { - const liuyan = e.detail.value.slice(0, 50) - this.setData({ liuyan }) - }, - chakanWanzhengJieshao() { - this.setData({ showWanzhengJieshao: true, wanzhengJieshao: this.data.jibenShuju.jieshao || '无' }) - }, - guanbiWanzhengJieshao() { this.setData({ showWanzhengJieshao: false }) }, - chakanWanzhengBeizhu() { - this.setData({ showWanzhengBeizhu: true, wanzhengBeizhu: this.data.jibenShuju.beizhu || '无' }) - }, - guanbiWanzhengBeizhu() { this.setData({ showWanzhengBeizhu: false }) }, - async fuzhiWenben(e) { - const text = e.currentTarget.dataset.text - if (!text) return - try { - await wx.setClipboardData({ data: text }) - wx.showToast({ title: '复制成功', icon: 'success', duration: 1500 }) - } catch (error) { - console.error('复制失败:', error) - wx.showToast({ title: '复制失败', icon: 'none' }) - } - }, - goToKefu() { - const kefuLink = app.globalData.kefuConfig?.link || ''; - const corpId = app.globalData.kefuConfig?.enterpriseId || ''; - - if (!kefuLink || !corpId) { - wx.showToast({ - title: '客服配置未加载', - icon: 'none' - }); - return; - } - - wx.openCustomerServiceChat({ - extInfo: { url: kefuLink }, - corpId: corpId, - success: (res) => { - console.log('跳转客服成功', res); - }, - fail: (err) => { - console.error('跳转客服失败', err); - wx.showToast({ - title: '暂时无法连接客服', - icon: 'none' - }); - } - }); - }, - - // ===== 联系老板(订单群 groupId 即订单号,与商家端 sjddxq 一致) ===== - goToChatWithBoss() { - const uid = wx.getStorageSync('uid') - if (!uid) { - wx.showToast({ title: '用户信息缺失', icon: 'none' }) - return - } - - const orderId = this.data.jibenShuju.dingdan_id - if (!orderId) { - wx.showToast({ title: '订单ID缺失', icon: 'none' }) - return - } - - const targetUserId = 'Ds' + uid - const connected = wx.goEasy && wx.goEasy.getConnectionStatus && wx.goEasy.getConnectionStatus() === 'connected' - const currentUserId = wx.goEasy?.im?.userId - if (!connected || currentUserId !== targetUserId) { - if (app.ensureConnection) app.ensureConnection() - wx.showToast({ title: '连接中,请稍后再试', icon: 'none' }) - return - } - - const orderIdStr = String(orderId) - const groupName = (this.data.jibenShuju.nicheng || '订单') + '的订单群' - const isCross = this.data.jibenShuju.fadanpingtai || 0 - - wx.goEasy.im.subscribeGroup({ - groupIds: [orderIdStr], - onSuccess: () => { - const param = { - groupId: orderIdStr, - orderId: orderIdStr, - groupName, - groupAvatar: '', - isCross, - } - wx.navigateTo({ - url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)), - }) - }, - onFailed: (err) => { - console.error('订阅群组失败', err) - wx.showToast({ title: '连接群聊失败', icon: 'none' }) - }, - }) - }, - - async getCOSZhengshu() { - try { - const res = await request({ - url: '/dingdan/dsscpz', - method: 'POST', - data: { dingdan_id: this.data.jibenShuju.dingdan_id } - }) - if (res && res.data.code === 0 && res.data.data) return res.data.data - else throw new Error(res?.data?.msg || '获取上传凭证失败') - } catch (error) { - console.error('获取COS凭证失败:', error) - throw error - } - }, - - initCOSClient(tokenData) { - const credentials = tokenData.credentials || tokenData - const bucket = tokenData.bucket || 'julebu-1361527063' - const region = tokenData.region || 'ap-shanghai' - const cos = new COS({ - SimpleUploadMethod: 'putObject', - getAuthorization: async (options, callback) => { - const authParams = { - TmpSecretId: credentials.tmpSecretId, - TmpSecretKey: credentials.tmpSecretKey, - SecurityToken: credentials.sessionToken || '', - StartTime: tokenData.startTime || Math.floor(Date.now() / 1000), - ExpiredTime: tokenData.expiredTime || (Math.floor(Date.now() / 1000) + 7200) - } - callback(authParams) - } - }) - return { cos, bucket, region } - }, - - async shangchuanDanZhangTupian(filePath, cosKey, index, cosInstance, bucket, region) { - return new Promise((resolve) => { - const timeoutId = setTimeout(() => resolve({ status: 'optimistic_success', key: cosKey }), 2000) - cosInstance.uploadFile({ - Bucket: bucket, - Region: region, - Key: cosKey, - FilePath: filePath, - onProgress: (progressInfo) => { - const percent = Math.round(progressInfo.percent * 100) - if (percent === 100) { - clearTimeout(timeoutId) - resolve({ status: 'optimistic_success', key: cosKey }) - } - } - }) - }) - }, - - async piliangShangchuanTupian() { - const total = this.data.xuanzhongTupian.length - this.setData({ shangchuanZongshu: total, shangchuanJindu: 0, jinduWidth: '0%' }) - wx.showLoading({ title: '正在准备...', mask: true }) - const preGeneratedUrls = [] - for (let i = 0; i < total; i++) { - const timestamp = Date.now() + i - const random = Math.floor(Math.random() * 10000) - const fileExt = this.getFileExtension(this.data.xuanzhongTupian[i]) - const fileName = `${timestamp}_${random}${fileExt}` - const cosKey = `order/${this.data.jibenShuju.dingdan_id}/${fileName}` - preGeneratedUrls.push(cosKey) - } - let cosClient, bucket, region - try { - const tokenData = await this.getCOSZhengshu() - const { cos, bucket: cosBucket, region: cosRegion } = this.initCOSClient(tokenData) - cosClient = cos - bucket = cosBucket - region = cosRegion - } catch (error) { - wx.hideLoading() - wx.showToast({ title: '上传初始化失败', icon: 'none' }) - return [] - } - const uploadTasks = [] - for (let i = 0; i < total; i++) { - const task = this.shangchuanDanZhangTupian( - this.data.xuanzhongTupian[i], - preGeneratedUrls[i], - i, - cosClient, - bucket, - region - ).then((result) => { - const currentDone = i + 1 - const jinduPercent = ((currentDone / total) * 100).toFixed(0) - this.setData({ shangchuanJindu: currentDone, jinduWidth: `${jinduPercent}%` }) - return result - }) - uploadTasks.push(task) - } - wx.showLoading({ title: `上传中...`, mask: true }) - await Promise.all(uploadTasks) - wx.hideLoading() - return preGeneratedUrls - }, - - tijiaoDingdan() { - this.showAnnouncementAndConfirm() - }, - - async showAnnouncementAndConfirm() { - try { - const res = await request({ - url: '/peizhi/tanchuang', - method: 'POST', - data: { pageKey: 'dsddxq' } - }) - if (res.statusCode !== 200 || res.data.code !== 0) { - this.showOriginalConfirmAndSubmit() - return - } - const { popups = [], serverTime } = res.data.data - if (!popups.length) { - this.showOriginalConfirmAndSubmit() - return - } - const popup = popups[0] - const popupComp = this.selectComponent('#popupNotice') - if (!popupComp) { - console.error('未找到弹窗组件 #popupNotice') - this.showOriginalConfirmAndSubmit() - return - } - const showData = { - popupId: popup.popup_id, - title: popup.title || '', - duration: popup.duration || 0, - images: popup.images || [], - showMuteCheckbox: false, - serverTime: serverTime - } - popupComp.show(showData, (result) => { - this.showOriginalConfirmAndSubmit() - }) - } catch (error) { - console.error('获取公告配置失败', error) - this.showOriginalConfirmAndSubmit() - } - }, - - showOriginalConfirmAndSubmit() { - if (this.data.isSubmitting) { - wx.showToast({ title: '正在提交中', icon: 'none' }) - return - } - if (this.data.jibenShuju.zhuangtai !== 2) { - wx.showToast({ title: '订单状态不可提交', icon: 'none' }) - return - } - if (this.data.xuanzhongTupian.length === 0) { - wx.showToast({ title: '请至少上传一张图片', icon: 'none' }) - return - } - wx.showModal({ - title: '⚠️ 提交确认', - content: '在提交订单之前,请确保您已在接单员端页面认真阅读接单员规则。如有漏交图片,订单被退款,概不申诉。', - confirmText: '我已阅读', - cancelText: '我再想想', - success: (res) => { - if (res.confirm) this._doSubmit() - }, - fail: (err) => { - console.error('弹窗失败', err) - wx.showToast({ title: '系统异常,请重试', icon: 'none' }) - } - }) - }, - - async _doSubmit() { - this.setData({ isSubmitting: true }) - wx.showLoading({ title: '开始提交...', mask: true }) - try { - const tupianUrls = await this.piliangShangchuanTupian() - if (!tupianUrls || tupianUrls.length === 0) throw new Error('无法生成上传路径') - wx.showLoading({ title: '提交订单数据...', mask: true }) - const res = await request({ - url: '/dingdan/dstijiao', - method: 'POST', - data: { - dingdan_id: this.data.jibenShuju.dingdan_id, - liuyan: this.data.liuyan || '', - jiaofu_tupian_urls: tupianUrls - }, - header: { 'content-type': 'application/json' } - }) - wx.hideLoading() - this.setData({ isSubmitting: false }) - if (res && res.data.code === 0) { - const ossImageUrl = app.globalData.ossImageUrl || '' - const fullUrls = tupianUrls.map(url => { - if (url && !url.startsWith('http')) { - if (url.startsWith('/')) url = url.substring(1) - return ossImageUrl + url - } - return url - }) - this.setData({ - 'jibenShuju.zhuangtai': 8, - 'xiangxiShuju.dashou_liuyan': this.data.liuyan || '', - 'xiangxiShuju.dashou_jiaofu': fullUrls, - xuanzhongTupian: [], - liuyan: '', - shangchuanJindu: 0, - shangchuanZongshu: 0, - jinduWidth: '0%', - }) - if (app.globalData.dashouzhuangtai != 1) app.globalData.dashouzhuangtai = 1 - wx.showToast({ title: '提交成功!', icon: 'success', duration: 2000 }) - setTimeout(() => wx.navigateBack(), 1500) - } else { - const errorMsg = res?.data?.msg || '提交失败' - wx.showToast({ title: errorMsg, icon: 'none' }) - } - } catch (error) { - console.error('提交失败:', error) - wx.hideLoading() - this.setData({ isSubmitting: false }) - wx.showToast({ title: error.message || '提交失败', icon: 'none' }) - } - }, - - // ========== 修改功能(增加最少一张图片校验) ========== - extractRelativePath(fullUrl) { - const ossImageUrl = this.data.ossImageUrl - if (fullUrl.startsWith(ossImageUrl)) return fullUrl.substring(ossImageUrl.length) - return fullUrl - }, - enterModify() { - const { dashou_jiaofu, dashou_liuyan } = this.data.xiangxiShuju - this.setData({ - isModifying: true, - originalDashouJiaofu: dashou_jiaofu.slice(), - remainingOriginals: dashou_jiaofu.slice(), - originalDashouLiuyan: dashou_liuyan || '', - modifiedLiuyan: dashou_liuyan || '', - deletedImages: [], - newImages: [] - }) - }, - cancelModify() { - this.setData({ - isModifying: false, - originalDashouJiaofu: [], - remainingOriginals: [], - originalDashouLiuyan: '', - modifiedLiuyan: '', - deletedImages: [], - newImages: [] - }) - }, - handleDeleteImage(e) { - const fullUrl = e.currentTarget.dataset.url - const { originalDashouJiaofu, remainingOriginals, deletedImages, newImages } = this.data - if (originalDashouJiaofu.includes(fullUrl)) { - const newRemaining = remainingOriginals.filter(url => url !== fullUrl) - this.setData({ - remainingOriginals: newRemaining, - deletedImages: [...deletedImages, fullUrl] - }) - } else { - const index = newImages.indexOf(fullUrl) - if (index > -1) { - const newImagesCopy = [...newImages] - newImagesCopy.splice(index, 1) - this.setData({ newImages: newImagesCopy }) - } - } - }, - handleAddImage() { - if (this.data.isConfirming) return - const currentCount = this.getCurrentImageCount() - const maxCount = 9 - currentCount - if (maxCount <= 0) { - wx.showToast({ title: '最多9张图片', icon: 'none' }) - return - } - wx.chooseImage({ - count: maxCount, - sizeType: ['compressed'], - sourceType: ['album', 'camera'], - success: (res) => { - const newImages = [...this.data.newImages, ...res.tempFilePaths] - this.setData({ newImages: newImages.slice(0, 9) }) - } - }) - }, - getCurrentImageCount() { - const { remainingOriginals, newImages } = this.data - return remainingOriginals.length + newImages.length - }, - onModifyLiuyanInput(e) { - this.setData({ modifiedLiuyan: e.detail.value.slice(0, 50) }) - }, - - async confirmModify() { - if (this.data.isConfirming) return - const { dingdan_id } = this.data.jibenShuju - const { remainingOriginals, deletedImages, newImages, modifiedLiuyan, originalDashouLiuyan } = this.data - const hasLiuyanChange = modifiedLiuyan !== originalDashouLiuyan - const hasDelete = deletedImages.length > 0 - const hasAdd = newImages.length > 0 - - if (!hasLiuyanChange && !hasDelete && !hasAdd) { - wx.showToast({ title: '没有修改内容', icon: 'none' }) - return - } - - const finalImageCount = remainingOriginals.length + newImages.length - if (finalImageCount === 0) { - wx.showToast({ title: '至少保留一张图片', icon: 'none' }) - return - } - - this.setData({ isConfirming: true }) - wx.showLoading({ title: '提交修改...', mask: true }) - try { - const tokenData = await this.getCOSZhengshu() - const { cos, bucket, region } = this.initCOSClient(tokenData) - - let newImageRelativePaths = [] - if (hasAdd) { - const total = newImages.length - this.setData({ 'modifyUploadProgress.total': total, 'modifyUploadProgress.current': 0, 'modifyUploadProgress.width': '0%' }) - const preGeneratedKeys = [] - for (let i = 0; i < total; i++) { - const timestamp = Date.now() + i - const random = Math.floor(Math.random() * 10000) - const fileExt = this.getFileExtension(newImages[i]) - const fileName = `${timestamp}_${random}${fileExt}` - const cosKey = `order/${this.data.jibenShuju.dingdan_id}/${fileName}` - preGeneratedKeys.push(cosKey) - } - const uploadTasks = [] - for (let i = 0; i < total; i++) { - const task = this.shangchuanDanZhangTupian( - newImages[i], - preGeneratedKeys[i], - i, - cos, - bucket, - region - ).then((result) => { - const currentDone = i + 1 - const jinduPercent = ((currentDone / total) * 100).toFixed(0) - this.setData({ - 'modifyUploadProgress.current': currentDone, - 'modifyUploadProgress.width': `${jinduPercent}%` - }) - return result.key - }) - uploadTasks.push(task) - } - wx.showLoading({ title: `上传中...`, mask: true }) - newImageRelativePaths = await Promise.all(uploadTasks) - wx.hideLoading() - this.setData({ 'modifyUploadProgress.total': 0, 'modifyUploadProgress.current': 0, 'modifyUploadProgress.width': '0%' }) - } - - const deletedRelativePaths = deletedImages.map(fullUrl => this.extractRelativePath(fullUrl)) - - const res = await request({ - url: '/dingdan/dsxiugaidd', - method: 'POST', - data: { - dingdan_id, - liuyan: modifiedLiuyan, - deleted_images: deletedRelativePaths, - new_images: newImageRelativePaths - }, - header: { 'content-type': 'application/json' } - }) - wx.hideLoading() - this.setData({ isConfirming: false }) - if (res && res.data.code === 0) { - const ossImageUrl = this.data.ossImageUrl - const newFullUrls = newImageRelativePaths.map(rel => ossImageUrl + rel) - const newDashouJiaofu = [...remainingOriginals, ...newFullUrls] - this.setData({ - 'xiangxiShuju.dashou_jiaofu': newDashouJiaofu, - 'xiangxiShuju.dashou_liuyan': modifiedLiuyan, - isModifying: false, - originalDashouJiaofu: [], - remainingOriginals: [], - originalDashouLiuyan: '', - deletedImages: [], - newImages: [], - modifiedLiuyan: '' - }) - wx.showToast({ title: '修改成功', icon: 'success' }) - } else { - const errorMsg = res?.data?.msg || '修改失败' - wx.showToast({ title: errorMsg, icon: 'none' }) - } - } catch (error) { - console.error('修改失败:', error) - wx.hideLoading() - this.setData({ isConfirming: false }) - wx.showToast({ title: error.message || '修改失败', icon: 'none' }) - } - } +// pages/fighter-order-detail/fighter-order-detail.js - 【最终版:提交前公告弹窗 + 修改图片至少保留一张】 +const app = getApp() +import request from '../../utils/request.js' +import connectionManager from '../../utils/xiaoxilj.js' +const COS = require('../../utils/cos-wx-sdk-v5.min.js') + +Page({ + data: { + jibenShuju: { + dingdan_id: '', + tupian: '', + jieshao: '', + create_time: '', + jine: '', + nicheng: '', + beizhu: '', + zhuangtai: 0, + fadanpingtai: 0 + }, + xiangxiShuju: { + shangjia_id: '', + shangjia_nicheng: '', + shangjia_liuyan: '', + tuikuan_liyou: '', + chufa_liyou: '', + chufa_zhuangtai: 0, + chufa_jieguo: '', + bohui_liyou: '', + dashou_jiaofu: [], + dashou_liuyan: '', + laoban_id: '' + }, + zhuangtaiYanseMap: { + 1: '#E8F5E9', 2: '#E3F2FD', 3: '#F3E5F5', 4: '#FFF3E0', + 5: '#FFEBEE', 6: '#EFEBE9', 7: '#E0F7FA', 8: '#FFF8E1' + }, + zhuangtaiWenziMap: { + 1: '已下单', 2: '进行中', 3: '已完成', 4: '退款中', + 5: '已退款', 6: '退款失败', 7: '指定中', 8: '结算中' + }, + isLoading: true, + isSubmitting: false, + liuyan: '', + xuanzhongTupian: [], + shangchuanJindu: 0, + shangchuanZongshu: 0, + jinduWidth: '0%', + showWanzhengJieshao: false, + wanzhengJieshao: '', + showWanzhengBeizhu: false, + wanzhengBeizhu: '', + ossImageUrl: '', + morentouxiang: '', + showRefundInfo: false, + + // 修改功能所需字段 + isModifying: false, + originalDashouJiaofu: [], + originalDashouLiuyan: '', + remainingOriginals: [], + modifiedLiuyan: '', + deletedImages: [], + newImages: [], + isConfirming: false, + modifyUploadProgress: { total: 0, current: 0, width: '0%' } + }, + + onLoad(options) { + wx.setNavigationBarTitle({ title: '订单详情' }) + this.initGlobalData() + this.jiexiTiaozhuanCanshu(options) + }, + onHide() { + const popupComp = this.selectComponent('#popupNotice'); + if (popupComp && popupComp.cleanup) { + popupComp.cleanup(); + } + }, + + onShow() { + 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() + }; + } + }, + + initGlobalData() { + const app = getApp() + const ossImageUrl = app.globalData.ossImageUrl || 'https://your-oss-domain.com/' + const morentouxiang = app.globalData.morentouxiang || '/images/default-avatar.png' + this.setData({ ossImageUrl, morentouxiang }) + }, + + jiexiTiaozhuanCanshu(options) { + try { + const dingdanDataStr = options.dingdanData || '' + if (!dingdanDataStr) { + wx.showToast({ title: '订单数据错误', icon: 'none' }) + setTimeout(() => wx.navigateBack(), 1500) + return + } + const dingdanData = JSON.parse(decodeURIComponent(dingdanDataStr)) + let tupianUrl = dingdanData.tupian || '' + if (!tupianUrl) tupianUrl = this.getTupianUrl() + let jine = parseFloat(dingdanData.jine || 0) + if (isNaN(jine)) jine = 0 + const formattedJine = jine.toFixed(2) + this.setData({ + 'jibenShuju.dingdan_id': dingdanData.dingdan_id || '', + 'jibenShuju.tupian': tupianUrl, + 'jibenShuju.jieshao': dingdanData.jieshao || '', + 'jibenShuju.create_time': dingdanData.create_time || '', + 'jibenShuju.jine': formattedJine, + 'jibenShuju.nicheng': dingdanData.nicheng || '', + 'jibenShuju.beizhu': dingdanData.beizhu || '', + 'jibenShuju.zhuangtai': dingdanData.zhuangtai || 0, + 'jibenShuju.fadanpingtai': dingdanData.fadanpingtai || 0, + isLoading: false + }) + this.jiazaiXiangxiShuju(dingdanData.dingdan_id) + } catch (error) { + console.error('解析订单数据失败:', error) + wx.showToast({ title: '数据解析失败', icon: 'none' }) + setTimeout(() => wx.navigateBack(), 1500) + } + }, + + getTupianUrl() { + const cacheTouxiang = wx.getStorageSync('touxiang') || '' + let tupianPath = cacheTouxiang || app.globalData.morentouxiang || '' + const ossImageUrl = app.globalData.ossImageUrl || '' + if (tupianPath && ossImageUrl) { + if (tupianPath.startsWith('/')) tupianPath = tupianPath.substring(1) + return ossImageUrl + tupianPath + } + return '' + }, + + async jiazaiXiangxiShuju(dingdanId) { + if (!dingdanId) return + wx.showLoading({ title: '加载中...', mask: true }) + try { + const res = await request({ + url: '/dingdan/dsddxq', + method: 'POST', + data: { dingdan_id: dingdanId }, + header: { 'content-type': 'application/json' } + }) + wx.hideLoading() + if (res && res.data.code === 0 && res.data.data) { + const data = res.data.data + let dashouJiaofuList = data.dashou_jiaofu || [] + dashouJiaofuList = dashouJiaofuList.map(imgUrl => { + if (imgUrl && !imgUrl.startsWith('http')) { + const ossImageUrl = app.globalData.ossImageUrl || '' + if (imgUrl.startsWith('/')) imgUrl = imgUrl.substring(1) + return ossImageUrl + imgUrl + } + return imgUrl + }) + this.setData({ + 'xiangxiShuju.shangjia_id': data.shangjia_id || '', + 'xiangxiShuju.shangjia_nicheng': data.shangjia_nicheng || '', + 'xiangxiShuju.shangjia_liuyan': data.shangjia_liuyan || '', + 'xiangxiShuju.tuikuan_liyou': data.tuikuan_liyou || '', + 'xiangxiShuju.chufa_liyou': data.chufa_liyou || '', + 'xiangxiShuju.chufa_zhuangtai': data.chufa_zhuangtai || 0, + 'xiangxiShuju.chufa_jieguo': data.chufa_jieguo || '', + 'xiangxiShuju.bohui_liyou': data.bohui_liyou || '', + 'xiangxiShuju.dashou_jiaofu': dashouJiaofuList, + 'xiangxiShuju.dashou_liuyan': data.dashou_liuyan || '', + 'xiangxiShuju.laoban_id': data.laoban_id || '', + 'jibenShuju.zhuangtai': data.zhuangtai || 0, + 'jibenShuju.beizhu': data.beizhu || '', + 'liuyan': data.dashou_liuyan || '', + 'jibenShuju.fadanpingtai': data.fadanpingtai || 0 + }) + const currentStatus = data.zhuangtai || 0 + const showRefund = [4,5,6].includes(currentStatus) + this.setData({ showRefundInfo: showRefund }) + } else { + const errorMsg = res?.data?.msg || '获取订单详情失败' + wx.showToast({ title: errorMsg, icon: 'none', duration: 2000 }) + } + } catch (error) { + console.error('加载详细数据失败:', error) + wx.hideLoading() + wx.showToast({ title: '加载数据失败,请重试', icon: 'none', duration: 2000 }) + } + }, + + // ========== 原有提交功能(部分修改) ========== + xuanzeTupian() { + if (this.data.isSubmitting) return + const shengyu = 9 - this.data.xuanzhongTupian.length + if (shengyu <= 0) { + wx.showToast({ title: '最多只能选择9张图片', icon: 'none' }) + return + } + wx.chooseImage({ + count: shengyu, + sizeType: ['compressed'], + sourceType: ['album', 'camera'], + success: (res) => { + const newTupian = [...this.data.xuanzhongTupian, ...res.tempFilePaths] + this.setData({ xuanzhongTupian: newTupian.slice(0, 9) }) + } + }) + }, + shanchuTupian(e) { + const index = e.currentTarget.dataset.index + const newTupian = [...this.data.xuanzhongTupian] + newTupian.splice(index, 1) + this.setData({ xuanzhongTupian: newTupian }) + }, + yulanTupian(e) { + const currentUrl = e.currentTarget.dataset.url + const urls = e.currentTarget.dataset.urls || [] + if (!currentUrl) return + wx.previewImage({ current: currentUrl, urls: urls.length > 0 ? urls : [currentUrl] }) + }, + getFileExtension(filePath) { + const lastDotIndex = filePath.lastIndexOf('.') + if (lastDotIndex === -1) return '.jpg' + const ext = filePath.substring(lastDotIndex).toLowerCase() + const allowedExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'] + return allowedExts.includes(ext) ? ext : '.jpg' + }, + shuruLiuyan(e) { + const liuyan = e.detail.value.slice(0, 50) + this.setData({ liuyan }) + }, + chakanWanzhengJieshao() { + this.setData({ showWanzhengJieshao: true, wanzhengJieshao: this.data.jibenShuju.jieshao || '无' }) + }, + guanbiWanzhengJieshao() { this.setData({ showWanzhengJieshao: false }) }, + chakanWanzhengBeizhu() { + this.setData({ showWanzhengBeizhu: true, wanzhengBeizhu: this.data.jibenShuju.beizhu || '无' }) + }, + guanbiWanzhengBeizhu() { this.setData({ showWanzhengBeizhu: false }) }, + async fuzhiWenben(e) { + const text = e.currentTarget.dataset.text + if (!text) return + try { + await wx.setClipboardData({ data: text }) + wx.showToast({ title: '复制成功', icon: 'success', duration: 1500 }) + } catch (error) { + console.error('复制失败:', error) + wx.showToast({ title: '复制失败', icon: 'none' }) + } + }, + goToKefu() { + const kefuLink = app.globalData.kefuConfig?.link || ''; + const corpId = app.globalData.kefuConfig?.enterpriseId || ''; + + if (!kefuLink || !corpId) { + wx.showToast({ + title: '客服配置未加载', + icon: 'none' + }); + return; + } + + wx.openCustomerServiceChat({ + extInfo: { url: kefuLink }, + corpId: corpId, + success: (res) => { + console.log('跳转客服成功', res); + }, + fail: (err) => { + console.error('跳转客服失败', err); + wx.showToast({ + title: '暂时无法连接客服', + icon: 'none' + }); + } + }); + }, + + // ===== 联系老板/商家(配对群 group_Ds{打手}_Sj/Boss{对方}) ===== + goToChatWithBoss() { + const uid = wx.getStorageSync('uid') + if (!uid) { + wx.showToast({ title: '用户信息缺失', icon: 'none' }) + return + } + + const orderId = this.data.jibenShuju.dingdan_id + if (!orderId) { + wx.showToast({ title: '订单ID缺失', icon: 'none' }) + return + } + + const fadanPingtai = this.data.jibenShuju.fadanpingtai || 0 + const partnerUid = fadanPingtai === 2 + ? (this.data.xiangxiShuju.shangjia_id || '') + : (this.data.xiangxiShuju.laoban_id || '') + + connectionManager.connectToGroupChat({ + identityType: 'dashou', + userId: 'Ds' + uid, + orderId: orderId, + partnerUid, + fadanPingtai, + groupName: (this.data.jibenShuju.nicheng || '订单') + '的订单群', + isCross: this.data.jibenShuju.fadanpingtai || 0, + }).catch((err) => { + console.error('跳转群聊失败', err) + wx.showToast({ title: '跳转聊天失败', icon: 'none' }) + }) + }, + + async getCOSZhengshu() { + try { + const res = await request({ + url: '/dingdan/dsscpz', + method: 'POST', + data: { dingdan_id: this.data.jibenShuju.dingdan_id } + }) + if (res && res.data.code === 0 && res.data.data) return res.data.data + else throw new Error(res?.data?.msg || '获取上传凭证失败') + } catch (error) { + console.error('获取COS凭证失败:', error) + throw error + } + }, + + initCOSClient(tokenData) { + const credentials = tokenData.credentials || tokenData + const bucket = tokenData.bucket || 'julebu-1361527063' + const region = tokenData.region || 'ap-shanghai' + const cos = new COS({ + SimpleUploadMethod: 'putObject', + getAuthorization: async (options, callback) => { + const authParams = { + TmpSecretId: credentials.tmpSecretId, + TmpSecretKey: credentials.tmpSecretKey, + SecurityToken: credentials.sessionToken || '', + StartTime: tokenData.startTime || Math.floor(Date.now() / 1000), + ExpiredTime: tokenData.expiredTime || (Math.floor(Date.now() / 1000) + 7200) + } + callback(authParams) + } + }) + return { cos, bucket, region } + }, + + async shangchuanDanZhangTupian(filePath, cosKey, index, cosInstance, bucket, region) { + return new Promise((resolve) => { + const timeoutId = setTimeout(() => resolve({ status: 'optimistic_success', key: cosKey }), 2000) + cosInstance.uploadFile({ + Bucket: bucket, + Region: region, + Key: cosKey, + FilePath: filePath, + onProgress: (progressInfo) => { + const percent = Math.round(progressInfo.percent * 100) + if (percent === 100) { + clearTimeout(timeoutId) + resolve({ status: 'optimistic_success', key: cosKey }) + } + } + }) + }) + }, + + async piliangShangchuanTupian() { + const total = this.data.xuanzhongTupian.length + this.setData({ shangchuanZongshu: total, shangchuanJindu: 0, jinduWidth: '0%' }) + wx.showLoading({ title: '正在准备...', mask: true }) + const preGeneratedUrls = [] + for (let i = 0; i < total; i++) { + const timestamp = Date.now() + i + const random = Math.floor(Math.random() * 10000) + const fileExt = this.getFileExtension(this.data.xuanzhongTupian[i]) + const fileName = `${timestamp}_${random}${fileExt}` + const cosKey = `order/${this.data.jibenShuju.dingdan_id}/${fileName}` + preGeneratedUrls.push(cosKey) + } + let cosClient, bucket, region + try { + const tokenData = await this.getCOSZhengshu() + const { cos, bucket: cosBucket, region: cosRegion } = this.initCOSClient(tokenData) + cosClient = cos + bucket = cosBucket + region = cosRegion + } catch (error) { + wx.hideLoading() + wx.showToast({ title: '上传初始化失败', icon: 'none' }) + return [] + } + const uploadTasks = [] + for (let i = 0; i < total; i++) { + const task = this.shangchuanDanZhangTupian( + this.data.xuanzhongTupian[i], + preGeneratedUrls[i], + i, + cosClient, + bucket, + region + ).then((result) => { + const currentDone = i + 1 + const jinduPercent = ((currentDone / total) * 100).toFixed(0) + this.setData({ shangchuanJindu: currentDone, jinduWidth: `${jinduPercent}%` }) + return result + }) + uploadTasks.push(task) + } + wx.showLoading({ title: `上传中...`, mask: true }) + await Promise.all(uploadTasks) + wx.hideLoading() + return preGeneratedUrls + }, + + tijiaoDingdan() { + this.showAnnouncementAndConfirm() + }, + + async showAnnouncementAndConfirm() { + try { + const res = await request({ + url: '/peizhi/tanchuang', + method: 'POST', + data: { pageKey: 'dsddxq' } + }) + if (res.statusCode !== 200 || res.data.code !== 0) { + this.showOriginalConfirmAndSubmit() + return + } + const { popups = [], serverTime } = res.data.data + if (!popups.length) { + this.showOriginalConfirmAndSubmit() + return + } + const popup = popups[0] + const popupComp = this.selectComponent('#popupNotice') + if (!popupComp) { + console.error('未找到弹窗组件 #popupNotice') + this.showOriginalConfirmAndSubmit() + return + } + const showData = { + popupId: popup.popup_id, + title: popup.title || '', + duration: popup.duration || 0, + images: popup.images || [], + showMuteCheckbox: false, + serverTime: serverTime + } + popupComp.show(showData, (result) => { + this.showOriginalConfirmAndSubmit() + }) + } catch (error) { + console.error('获取公告配置失败', error) + this.showOriginalConfirmAndSubmit() + } + }, + + showOriginalConfirmAndSubmit() { + if (this.data.isSubmitting) { + wx.showToast({ title: '正在提交中', icon: 'none' }) + return + } + if (this.data.jibenShuju.zhuangtai !== 2) { + wx.showToast({ title: '订单状态不可提交', icon: 'none' }) + return + } + if (this.data.xuanzhongTupian.length === 0) { + wx.showToast({ title: '请至少上传一张图片', icon: 'none' }) + return + } + wx.showModal({ + title: '⚠️ 提交确认', + content: '在提交订单之前,请确保您已在接单员端页面认真阅读接单员规则。如有漏交图片,订单被退款,概不申诉。', + confirmText: '我已阅读', + cancelText: '我再想想', + success: (res) => { + if (res.confirm) this._doSubmit() + }, + fail: (err) => { + console.error('弹窗失败', err) + wx.showToast({ title: '系统异常,请重试', icon: 'none' }) + } + }) + }, + + async _doSubmit() { + this.setData({ isSubmitting: true }) + wx.showLoading({ title: '开始提交...', mask: true }) + try { + const tupianUrls = await this.piliangShangchuanTupian() + if (!tupianUrls || tupianUrls.length === 0) throw new Error('无法生成上传路径') + wx.showLoading({ title: '提交订单数据...', mask: true }) + const res = await request({ + url: '/dingdan/dstijiao', + method: 'POST', + data: { + dingdan_id: this.data.jibenShuju.dingdan_id, + liuyan: this.data.liuyan || '', + jiaofu_tupian_urls: tupianUrls + }, + header: { 'content-type': 'application/json' } + }) + wx.hideLoading() + this.setData({ isSubmitting: false }) + if (res && res.data.code === 0) { + const ossImageUrl = app.globalData.ossImageUrl || '' + const fullUrls = tupianUrls.map(url => { + if (url && !url.startsWith('http')) { + if (url.startsWith('/')) url = url.substring(1) + return ossImageUrl + url + } + return url + }) + this.setData({ + 'jibenShuju.zhuangtai': 8, + 'xiangxiShuju.dashou_liuyan': this.data.liuyan || '', + 'xiangxiShuju.dashou_jiaofu': fullUrls, + xuanzhongTupian: [], + liuyan: '', + shangchuanJindu: 0, + shangchuanZongshu: 0, + jinduWidth: '0%', + }) + if (app.globalData.dashouzhuangtai != 1) app.globalData.dashouzhuangtai = 1 + wx.showToast({ title: '提交成功!', icon: 'success', duration: 2000 }) + setTimeout(() => wx.navigateBack(), 1500) + } else { + const errorMsg = res?.data?.msg || '提交失败' + wx.showToast({ title: errorMsg, icon: 'none' }) + } + } catch (error) { + console.error('提交失败:', error) + wx.hideLoading() + this.setData({ isSubmitting: false }) + wx.showToast({ title: error.message || '提交失败', icon: 'none' }) + } + }, + + // ========== 修改功能(增加最少一张图片校验) ========== + extractRelativePath(fullUrl) { + const ossImageUrl = this.data.ossImageUrl + if (fullUrl.startsWith(ossImageUrl)) return fullUrl.substring(ossImageUrl.length) + return fullUrl + }, + enterModify() { + const { dashou_jiaofu, dashou_liuyan } = this.data.xiangxiShuju + this.setData({ + isModifying: true, + originalDashouJiaofu: dashou_jiaofu.slice(), + remainingOriginals: dashou_jiaofu.slice(), + originalDashouLiuyan: dashou_liuyan || '', + modifiedLiuyan: dashou_liuyan || '', + deletedImages: [], + newImages: [] + }) + }, + cancelModify() { + this.setData({ + isModifying: false, + originalDashouJiaofu: [], + remainingOriginals: [], + originalDashouLiuyan: '', + modifiedLiuyan: '', + deletedImages: [], + newImages: [] + }) + }, + handleDeleteImage(e) { + const fullUrl = e.currentTarget.dataset.url + const { originalDashouJiaofu, remainingOriginals, deletedImages, newImages } = this.data + if (originalDashouJiaofu.includes(fullUrl)) { + const newRemaining = remainingOriginals.filter(url => url !== fullUrl) + this.setData({ + remainingOriginals: newRemaining, + deletedImages: [...deletedImages, fullUrl] + }) + } else { + const index = newImages.indexOf(fullUrl) + if (index > -1) { + const newImagesCopy = [...newImages] + newImagesCopy.splice(index, 1) + this.setData({ newImages: newImagesCopy }) + } + } + }, + handleAddImage() { + if (this.data.isConfirming) return + const currentCount = this.getCurrentImageCount() + const maxCount = 9 - currentCount + if (maxCount <= 0) { + wx.showToast({ title: '最多9张图片', icon: 'none' }) + return + } + wx.chooseImage({ + count: maxCount, + sizeType: ['compressed'], + sourceType: ['album', 'camera'], + success: (res) => { + const newImages = [...this.data.newImages, ...res.tempFilePaths] + this.setData({ newImages: newImages.slice(0, 9) }) + } + }) + }, + getCurrentImageCount() { + const { remainingOriginals, newImages } = this.data + return remainingOriginals.length + newImages.length + }, + onModifyLiuyanInput(e) { + this.setData({ modifiedLiuyan: e.detail.value.slice(0, 50) }) + }, + + async confirmModify() { + if (this.data.isConfirming) return + const { dingdan_id } = this.data.jibenShuju + const { remainingOriginals, deletedImages, newImages, modifiedLiuyan, originalDashouLiuyan } = this.data + const hasLiuyanChange = modifiedLiuyan !== originalDashouLiuyan + const hasDelete = deletedImages.length > 0 + const hasAdd = newImages.length > 0 + + if (!hasLiuyanChange && !hasDelete && !hasAdd) { + wx.showToast({ title: '没有修改内容', icon: 'none' }) + return + } + + const finalImageCount = remainingOriginals.length + newImages.length + if (finalImageCount === 0) { + wx.showToast({ title: '至少保留一张图片', icon: 'none' }) + return + } + + this.setData({ isConfirming: true }) + wx.showLoading({ title: '提交修改...', mask: true }) + try { + const tokenData = await this.getCOSZhengshu() + const { cos, bucket, region } = this.initCOSClient(tokenData) + + let newImageRelativePaths = [] + if (hasAdd) { + const total = newImages.length + this.setData({ 'modifyUploadProgress.total': total, 'modifyUploadProgress.current': 0, 'modifyUploadProgress.width': '0%' }) + const preGeneratedKeys = [] + for (let i = 0; i < total; i++) { + const timestamp = Date.now() + i + const random = Math.floor(Math.random() * 10000) + const fileExt = this.getFileExtension(newImages[i]) + const fileName = `${timestamp}_${random}${fileExt}` + const cosKey = `order/${this.data.jibenShuju.dingdan_id}/${fileName}` + preGeneratedKeys.push(cosKey) + } + const uploadTasks = [] + for (let i = 0; i < total; i++) { + const task = this.shangchuanDanZhangTupian( + newImages[i], + preGeneratedKeys[i], + i, + cos, + bucket, + region + ).then((result) => { + const currentDone = i + 1 + const jinduPercent = ((currentDone / total) * 100).toFixed(0) + this.setData({ + 'modifyUploadProgress.current': currentDone, + 'modifyUploadProgress.width': `${jinduPercent}%` + }) + return result.key + }) + uploadTasks.push(task) + } + wx.showLoading({ title: `上传中...`, mask: true }) + newImageRelativePaths = await Promise.all(uploadTasks) + wx.hideLoading() + this.setData({ 'modifyUploadProgress.total': 0, 'modifyUploadProgress.current': 0, 'modifyUploadProgress.width': '0%' }) + } + + const deletedRelativePaths = deletedImages.map(fullUrl => this.extractRelativePath(fullUrl)) + + const res = await request({ + url: '/dingdan/dsxiugaidd', + method: 'POST', + data: { + dingdan_id, + liuyan: modifiedLiuyan, + deleted_images: deletedRelativePaths, + new_images: newImageRelativePaths + }, + header: { 'content-type': 'application/json' } + }) + wx.hideLoading() + this.setData({ isConfirming: false }) + if (res && res.data.code === 0) { + const ossImageUrl = this.data.ossImageUrl + const newFullUrls = newImageRelativePaths.map(rel => ossImageUrl + rel) + const newDashouJiaofu = [...remainingOriginals, ...newFullUrls] + this.setData({ + 'xiangxiShuju.dashou_jiaofu': newDashouJiaofu, + 'xiangxiShuju.dashou_liuyan': modifiedLiuyan, + isModifying: false, + originalDashouJiaofu: [], + remainingOriginals: [], + originalDashouLiuyan: '', + deletedImages: [], + newImages: [], + modifiedLiuyan: '' + }) + wx.showToast({ title: '修改成功', icon: 'success' }) + } else { + const errorMsg = res?.data?.msg || '修改失败' + wx.showToast({ title: errorMsg, icon: 'none' }) + } + } catch (error) { + console.error('修改失败:', error) + wx.hideLoading() + this.setData({ isConfirming: false }) + wx.showToast({ title: error.message || '修改失败', icon: 'none' }) + } + } }) \ No newline at end of file diff --git a/pages/fighter-order-detail/fighter-order-detail.wxml b/pages/fighter-order-detail/fighter-order-detail.wxml index 40790b3..47ff950 100644 --- a/pages/fighter-order-detail/fighter-order-detail.wxml +++ b/pages/fighter-order-detail/fighter-order-detail.wxml @@ -179,7 +179,7 @@ 申请状态: {{xiangxiShuju.chufa_zhuangtai == 0 ? '审核中' : (xiangxiShuju.chufa_zhuangtai == 1 ? '处罚成功' : '处罚驳回')}} diff --git a/pages/fighter-order-detail/fighter-order-detail.wxss b/pages/fighter-order-detail/fighter-order-detail.wxss index 2d7a8a8..a990b50 100644 --- a/pages/fighter-order-detail/fighter-order-detail.wxss +++ b/pages/fighter-order-detail/fighter-order-detail.wxss @@ -432,9 +432,9 @@ /* 🆕 新增:联系老板按钮 */ .boss-btn { - background-color: #9333ea; + background-color: #ff9800; color: #ffffff; - box-shadow: 0 4rpx 15rpx rgba(147, 51, 234, 0.3); + box-shadow: 0 4rpx 15rpx rgba(255, 152, 0, 0.3); } /* 🆕 新增:修改按钮(黑色) */ diff --git a/pages/fighter-orders/fighter-orders.js b/pages/fighter-orders/fighter-orders.js index 189638b..9a6ca25 100644 --- a/pages/fighter-orders/fighter-orders.js +++ b/pages/fighter-orders/fighter-orders.js @@ -19,7 +19,7 @@ Page(createPage({ statusList: [ { name: '全部', key: 'all', zhuangtaiList: [], color: '#333333' }, { name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2], color: '#2196F3' }, - { name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#9333ea' }, + { name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#FF9800' }, { name: '已退款', key: 'yituikuan', zhuangtaiList: [5], color: '#9E9E9E' }, { name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6], color: '#F44336' }, { name: '已完成', key: 'yiwancheng', zhuangtaiList: [3], color: '#4CAF50' }, @@ -49,18 +49,43 @@ Page(createPage({ onLoad() { wx.setNavigationBarTitle({ title: '我的接单' }); - this.loadShangpinLeixing(); + this.loadShangpinLeixing().then(() => { + if (this.data.xuanzhongLeixingId) { + return this.loadCurrentStatusOrders(true); + } + }).finally(() => { + this._ordersSessionReady = true; + this._skipShowRefresh = true; + }); this.registerNotificationComponent(); }, onShow() { this.registerNotificationComponent(); + const app = getApp(); + const filterKey = app.globalData._fighterOrdersStatusKey; + if (filterKey) { + app.globalData._fighterOrdersStatusKey = null; + if (this.data.statusList.some((s) => s.key === filterKey)) { + this.setData({ currentStatusKey: filterKey }); + } + if (this.data.xuanzhongLeixingId) { + this.loadCurrentStatusOrders(true); + } + } if (wx.getStorageSync('uid')) { reconnectForRole('dashou'); if (app.startImWhenReady) app.startImWhenReady(); } - if (this.data.currentStatusKey && this.data.xuanzhongLeixingId) { - this.loadCurrentStatusOrders(true); + if (this._skipShowRefresh) { + this._skipShowRefresh = false; + return; + } + if (this._ordersSessionReady && this.data.xuanzhongLeixingId && !this._silentRefreshRunning) { + this._silentRefreshRunning = true; + this.loadCurrentStatusOrders(true, true).finally(() => { + this._silentRefreshRunning = false; + }); } }, @@ -141,17 +166,21 @@ Page(createPage({ }, // 加载订单(核心) - async loadCurrentStatusOrders(isRefresh = false) { + async loadCurrentStatusOrders(isRefresh = false, silent = false) { const key = this.data.currentStatusKey; const tabData = this.data.dsDingdanShuju[key]; - if (tabData.isLoading) return; + if (tabData.isLoading && !silent) return; + if (silent && this._listRequesting) return; const page = isRefresh ? 1 : tabData.page; - this.setData({ - [`dsDingdanShuju.${key}.isLoading`]: true, - isLoading: true, - isLoadingMore: !isRefresh - }); + if (!silent) { + this.setData({ + [`dsDingdanShuju.${key}.isLoading`]: true, + isLoading: true, + isLoadingMore: !isRefresh, + }); + } + this._listRequesting = true; try { const apiUrl = this.data.orderType === 'peihu' @@ -208,14 +237,20 @@ Page(createPage({ } } catch (err) { console.error('加载订单失败', err); - wx.showToast({ title: err.message || '加载失败', icon: 'none' }); - this.setData({ - [`dsDingdanShuju.${key}.isLoading`]: false, - isLoading: false, - isLoadingMore: false, - scrollViewRefreshing: false - }); - this.refreshCurrentListView(); + if (!silent) { + wx.showToast({ title: err.message || '加载失败', icon: 'none' }); + } + if (!silent) { + this.setData({ + [`dsDingdanShuju.${key}.isLoading`]: false, + isLoading: false, + isLoadingMore: false, + scrollViewRefreshing: false + }); + this.refreshCurrentListView(); + } + } finally { + this._listRequesting = false; } }, diff --git a/pages/fighter-orders/fighter-orders.json b/pages/fighter-orders/fighter-orders.json index 60b86d2..44bcb57 100644 --- a/pages/fighter-orders/fighter-orders.json +++ b/pages/fighter-orders/fighter-orders.json @@ -1,6 +1,7 @@ { "usingComponents": { - "global-notification": "/components/global-notification/global-notification" + "global-notification": "/components/global-notification/global-notification", + "tab-bar": "/tab-bar/index" }, "navigationBarTitleText": "我的接单", "enablePullDownRefresh": false, diff --git a/pages/fighter-orders/fighter-orders.wxss b/pages/fighter-orders/fighter-orders.wxss index a523181..c8bb5ed 100644 --- a/pages/fighter-orders/fighter-orders.wxss +++ b/pages/fighter-orders/fighter-orders.wxss @@ -346,4 +346,5 @@ page { color: #999; } -@import '../../styles/dashou-xym-orders-theme.wxss'; \ No newline at end of file +@import '../../styles/dashou-xym-orders-theme.wxss'; +@import '../../styles/dashou-xym-orders-page.wxss'; \ No newline at end of file diff --git a/pages/fighter-rank/fighter-rank.js b/pages/fighter-rank/fighter-rank.js index baac5bf..111cd33 100644 --- a/pages/fighter-rank/fighter-rank.js +++ b/pages/fighter-rank/fighter-rank.js @@ -1,119 +1,121 @@ -/** - * 鎺掕姒? * 榛樿 POST /yonghu/phbhqsj - * 鏄熶箣鐣?xzj) POST /yonghu/xzjphbhqsj锛堥個璇蜂汉鏁版帓搴忕瓑涓撶敤瑙勫垯锛? */ +/** + * 排行榜 + * 默认 POST /yonghu/phbhqsj + * 星之界(xzj) POST /yonghu/xzjphbhqsj(邀请人数排序等专用规则) + */ import request from '../../utils/request.js' import { resolveAvatarUrl, getDefaultAvatarUrl } from '../../utils/avatar.js' import { CLUB_ID } from '../../config/club-config.js' const IS_XZJ = CLUB_ID === 'xzj' const RANK_API = IS_XZJ ? '/yonghu/xzjphbhqsj' : '/yonghu/phbhqsj' -const CLOSED_DATES = ['鏄ㄦ棩', '涓婂懆', '涓婃湀'] +const CLOSED_DATES = ['昨日', '上周', '上月'] const ROLE_LIST = [ - { key: 'dashou', label: '鎺ュ崟鍛? }, - { key: 'guanshi', label: '绠′簨' }, - { key: 'zuzhang', label: '缁勯暱' }, - { key: 'shangjia', label: '鍟嗗' }, + { key: 'dashou', label: '接单员' }, + { key: 'guanshi', label: '管事' }, + { key: 'zuzhang', label: '组长' }, + { key: 'shangjia', label: '商家' }, ] const DATE_LIST = [ - { key: '浠婃棩', label: '浠婃棩' }, - { key: '鏄ㄦ棩', label: '鏄ㄦ棩' }, - { key: '鏈懆', label: '鏈懆' }, - { key: '涓婂懆', label: '涓婂懆' }, - { key: '鏈湀', label: '鏈湀' }, - { key: '涓婃湀', label: '涓婃湀' }, - { key: '鎬绘', label: '鎬绘' }, + { key: '今日', label: '今日' }, + { key: '昨日', label: '昨日' }, + { key: '本周', label: '本周' }, + { key: '上周', label: '上周' }, + { key: '本月', label: '本月' }, + { key: '上月', label: '上月' }, + { key: '总榜', label: '总榜' }, ] const ROLE_META = { dashou: { - title: '鎺ュ崟鍛樻帓琛屾', + title: '接单员排行榜', sortField: 'chengjiao_zonge', - sortLabel: '鍒嗙孩鎬婚', + sortLabel: '分红总额', mainType: 'money', metrics: [ - { field: 'jiedan_zongliang', label: '鎺ュ崟閲?, type: 'int' }, - { field: 'jiedan_zonge', label: '鎺ュ崟棰?, type: 'money' }, - { field: 'chengjiao_zongliang', label: '鎴愪氦閲?, type: 'int' }, + { field: 'jiedan_zongliang', label: '接单量', type: 'int' }, + { field: 'jiedan_zonge', label: '接单额', type: 'money' }, + { field: 'chengjiao_zongliang', label: '成交量', type: 'int' }, ], }, guanshi: { - title: '绠′簨鎺掕姒?, + title: '管事排行榜', sortField: 'shouru_zonge', - sortLabel: '鏀跺叆鎬婚', + sortLabel: '收入总额', mainType: 'money', metrics: [ - { field: 'yaoqing_dashou_shu', label: '閭€璇?, type: 'int' }, - { field: 'chongzhi_dashou_shu', label: '鍏呭€?, type: 'int' }, + { field: 'yaoqing_dashou_shu', label: '邀请', type: 'int' }, + { field: 'chongzhi_dashou_shu', label: '充值', type: 'int' }, ], }, zuzhang: { - title: '缁勯暱鎺掕姒?, + title: '组长排行榜', sortField: 'shouru_zonge', - sortLabel: '鏀跺叆鎬婚', + sortLabel: '收入总额', mainType: 'money', metrics: [ - { field: 'yaoqing_guanshi_shu', label: '閭€璇?, type: 'int' }, - { field: 'fenyong_jine', label: '鍒嗕剑', type: 'money' }, + { field: 'yaoqing_guanshi_shu', label: '邀请', type: 'int' }, + { field: 'fenyong_jine', label: '分佣', type: 'money' }, ], }, shangjia: { - title: '鍟嗗鎺掕姒?, + title: '商家排行榜', sortField: 'jiesuan_jine', - sortLabel: '鎴愪氦鎬婚', + sortLabel: '成交总额', mainType: 'money', metrics: [ - { field: 'jiesuan_dingdan_shu', label: '鎴愪氦閲?, type: 'int' }, - { field: 'paifa_dingdan_shu', label: '娲惧崟閲?, type: 'int' }, - { field: 'paifa_jine', label: '娲惧崟棰?, type: 'money' }, + { field: 'jiesuan_dingdan_shu', label: '成交量', type: 'int' }, + { field: 'paifa_dingdan_shu', label: '派单量', type: 'int' }, + { field: 'paifa_jine', label: '派单额', type: 'money' }, ], }, } -/** 鏄熶箣鐣屼笓鐢ㄥ睍绀轰笌鎺掑簭瀛楁 */ +/** 星之界专用展示与排序字段 */ const XZJ_ROLE_META = { dashou: { - title: '鎺ュ崟鍛樻帓琛屾', + title: '接单员排行榜', sortField: 'chengjiao_zonge', - sortLabel: '鎴愪氦閲戦', + sortLabel: '成交金额', mainType: 'money', metrics: [ - { field: 'jiedan_zongliang', label: '鎺ュ崟閲?, type: 'int' }, - { field: 'jiedan_zonge', label: '鎺ュ崟棰?, type: 'money' }, - { field: 'chengjiao_zongliang', label: '鎴愪氦閲?, type: 'int' }, + { field: 'jiedan_zongliang', label: '接单量', type: 'int' }, + { field: 'jiedan_zonge', label: '接单额', type: 'money' }, + { field: 'chengjiao_zongliang', label: '成交量', type: 'int' }, ], }, guanshi: { - title: '绠′簨鎺掕姒?, + title: '管事排行榜', sortField: 'chongzhi_dashou_shu', - sortLabel: '鏈夋晥浜烘暟', + sortLabel: '有效人数', mainType: 'int', metrics: [ - { field: 'wuxiao_yaoqing_dashou_shu', label: '鏃犳晥閭€璇?, type: 'int' }, - { field: 'yaoqing_dashou_shu', label: '閭€璇蜂汉鏁?, type: 'int' }, - { field: 'shouru_zonge', label: '鏀剁泭鎬婚', type: 'money' }, + { field: 'wuxiao_yaoqing_dashou_shu', label: '无效邀请', type: 'int' }, + { field: 'yaoqing_dashou_shu', label: '邀请人数', type: 'int' }, + { field: 'shouru_zonge', label: '收益总额', type: 'money' }, ], }, zuzhang: { - title: '缁勯暱鎺掕姒?, + title: '组长排行榜', sortField: 'shouru_zonge', - sortLabel: '鏀剁泭鎬婚', + sortLabel: '收益总额', mainType: 'money', metrics: [ - { field: 'wuxiao_yaoqing_guanshi_shu', label: '鏃犳晥閭€璇?, type: 'int' }, - { field: 'youxiao_guanshi_shu', label: '鏈夋晥浜烘暟', type: 'int' }, - { field: 'yaoqing_guanshi_shu', label: '閭€璇蜂汉鏁?, type: 'int' }, + { field: 'wuxiao_yaoqing_guanshi_shu', label: '无效邀请', type: 'int' }, + { field: 'youxiao_guanshi_shu', label: '有效人数', type: 'int' }, + { field: 'yaoqing_guanshi_shu', label: '邀请人数', type: 'int' }, ], }, shangjia: { - title: '鍟嗗鎺掕姒?, + title: '商家排行榜', sortField: 'paifa_jine', - sortLabel: '娲惧崟娴佹按', + sortLabel: '派单流水', mainType: 'money', metrics: [ - { field: 'paifa_dingdan_shu', label: '娲惧崟閲?, type: 'int' }, - { field: 'paifa_jine', label: '娲惧崟娴佹按', type: 'money' }, + { field: 'paifa_dingdan_shu', label: '派单量', type: 'int' }, + { field: 'paifa_jine', label: '派单流水', type: 'money' }, ], }, } @@ -143,11 +145,12 @@ Page({ roleList: ROLE_LIST, dateList: DATE_LIST, currentRole: 'dashou', - currentDate: '浠婃棩', + currentDate: '今日', fanwei: 'club', - showJituanScope: false, // 涓存椂闅愯棌闆嗗洟鎬绘锛屾仮澶嶆椂鏀?true 骞惰皟鏁?fanwei 榛樿鍊? myClubId: CLUB_ID || 'xq', + showJituanScope: false, // 临时隐藏集团总榜,恢复时改 true 并调整 fanwei 默认值 + myClubId: CLUB_ID || 'xq', roleMeta: ROLE_META.dashou, - sortLabel: '鍒嗙孩鎬婚', + sortLabel: '分红总额', isLoading: true, rankList: [], topThree: [], @@ -183,7 +186,7 @@ Page({ }) }, - /** 涓?app.js 涓€鑷达細ossImageUrl + morentouxiang锛岄厤缃紓姝ュ姞杞藉悗 onShow 鍐嶅悓姝?*/ + /** 与 app.js 一致:ossImageUrl + morentouxiang,配置异步加载后 onShow 再同步 */ syncDefaultAvatar() { const def = getDefaultAvatar() if (def && def !== this.data.defaultAvatar) { @@ -193,7 +196,7 @@ Page({ } }, - /** 閰嶇疆鏅氫簬椤甸潰鍔犺浇鏃讹紝琛ュ叏绌哄ご鍍?*/ + /** 配置晚于页面加载时,补全空头像 */ ensureAvatars() { const def = getDefaultAvatar() if (!def) return @@ -223,7 +226,7 @@ Page({ applyRole(role, reload = true) { const meta = ACTIVE_ROLE_META[role] - wx.setNavigationBarTitle({ title: '鎬绘帓琛屾' }) + wx.setNavigationBarTitle({ title: '总排行榜' }) this.setData({ currentRole: role, roleMeta: meta, sortLabel: meta.sortLabel }) if (reload) this.fetchRankList() }, @@ -252,19 +255,19 @@ Page({ onShowRules() { const r = this.data.rewardInfo if (!r || !r.tiers || !r.tiers.length) { - wx.showToast({ title: '鏆傛棤濂栧姳瑙勫垯', icon: 'none' }) + wx.showToast({ title: '暂无奖励规则', icon: 'none' }) return } const lines = r.tiers.map((t) => { - const label = t.label || `绗?{t.rank_from}${t.rank_to > t.rank_from ? '-' + t.rank_to : ''}鍚峘 - return `${label}锛毬?{t.amount}` + const label = t.label || `第${t.rank_from}${t.rank_to > t.rank_from ? '-' + t.rank_to : ''}名` + return `${label}:¥${t.amount}` }) - const tip = r.period_status === 'open' ? '锛堟湰鏈熻繘琛屼腑锛岀粨鏉熷悗鍙鍙栵級' : '' + const tip = r.period_status === 'open' ? '(本期进行中,结束后可领取)' : '' wx.showModal({ - title: `${r.title || '鎺掕姒滃鍔?}${tip}`, + title: `${r.title || '排行榜奖励'}${tip}`, content: [ - `淇变箰閮細${r.club_id || this.data.myClubId}`, - `鎺掑簭锛?{r.sort_label || ''}`, + `俱乐部:${r.club_id || this.data.myClubId}`, + `排序:${r.sort_label || ''}`, ...lines, r.description || '', ].filter(Boolean).join('\n'), @@ -284,13 +287,13 @@ Page({ }) const body = res?.data || {} if (body.code === 200) { - wx.showToast({ title: `宸查鍙?楼${claim.amount}`, icon: 'success' }) + wx.showToast({ title: `已领取 ¥${claim.amount}`, icon: 'success' }) await this.fetchRewardInfo() } else { - wx.showToast({ title: body.msg || '棰嗗彇澶辫触', icon: 'none' }) + wx.showToast({ title: body.msg || '领取失败', icon: 'none' }) } } catch (e) { - wx.showToast({ title: '棰嗗彇澶辫触', icon: 'none' }) + wx.showToast({ title: '领取失败', icon: 'none' }) } finally { this.setData({ claimLoading: false }) } @@ -314,7 +317,7 @@ Page({ const meta = ACTIVE_ROLE_META[role] const app = getApp() const uid = raw.yonghuid || raw.uid || '' - const nicheng = raw.nicheng || raw.nick || (role === 'dashou' ? '鎵撴墜' : role === 'guanshi' ? '绠′簨' : role === 'zuzhang' ? '缁勯暱' : role === 'shangjia' ? '鍟嗗' : '鐢ㄦ埛') + const nicheng = raw.nicheng || raw.nick || (role === 'dashou' ? '打手' : role === 'guanshi' ? '管事' : role === 'zuzhang' ? '组长' : role === 'shangjia' ? '商家' : '用户') const def = getDefaultAvatar(app) const avatar = buildAvatar(raw.touxiang || raw.avatar, app) || def const metrics = meta.metrics.map((m) => { @@ -345,7 +348,7 @@ Page({ clubName: raw.club_name || raw.club_id || '', rewardAmount: raw.reward_amount || 0, mainType, - mainPrefix: mainType === 'money' ? '楼' : '', + mainPrefix: mainType === 'money' ? '¥' : '', mainValue, mainLabel: (IS_XZJ ? meta.sortLabel : (raw.metric_label || meta.sortLabel)), metrics, @@ -387,7 +390,7 @@ Page({ restList: merged.slice(3), }) } catch (e) { - console.warn('濂栧姳淇℃伅鍔犺浇澶辫触', e) + console.warn('奖励信息加载失败', e) } }, @@ -432,7 +435,7 @@ Page({ if (!Array.isArray(list)) list = [] const role = currentRole - // 鍚嶆椤哄簭瀹屽叏娌跨敤鍚庣杩斿洖锛屽墠绔笉鍋?sort / 閲嶆帓 + // 名次顺序完全沿用后端返回,前端不做 sort / 重排 let normalized = list.slice(0, 50).map((item, i) => this.normalizeItem(item, i, role)) if (rewardFromRank) { normalized = this.applyRewardToList(normalized, rewardFromRank, 'club') @@ -450,8 +453,8 @@ Page({ await this.fetchRewardInfo() } } catch (e) { - console.error('鎺掕姒滃姞杞藉け璐?, e) - wx.showToast({ title: '鍔犺浇澶辫触', icon: 'none' }) + console.error('排行榜加载失败', e) + wx.showToast({ title: '加载失败', icon: 'none' }) this.setData({ rankList: [], topThree: [], restList: [], isEmpty: true, isLoading: false }) } }, diff --git a/pages/fighter-rank/fighter-rank.json b/pages/fighter-rank/fighter-rank.json index 2d19320..a432e9d 100644 --- a/pages/fighter-rank/fighter-rank.json +++ b/pages/fighter-rank/fighter-rank.json @@ -2,10 +2,10 @@ "usingComponents": { "global-notification": "/components/global-notification/global-notification" }, - "navigationBarTitleText": "排行榜", - "navigationBarBackgroundColor": "#0d1117", - "navigationBarTextStyle": "white", + "navigationBarTitleText": "总排行榜", + "navigationBarBackgroundColor": "#FFE566", + "navigationBarTextStyle": "black", "enablePullDownRefresh": true, - "backgroundColor": "#0d1117", - "backgroundTextStyle": "light" + "backgroundColor": "#FFE566", + "backgroundTextStyle": "dark" } diff --git a/pages/fighter-rank/fighter-rank.wxml b/pages/fighter-rank/fighter-rank.wxml index 8c4db95..ea58de9 100644 --- a/pages/fighter-rank/fighter-rank.wxml +++ b/pages/fighter-rank/fighter-rank.wxml @@ -1,30 +1,47 @@ - - - - - {{roleMeta.title}} - - + + + + + {{item.label}} + - - + + + - - {{item.label}} + + + + 集团总榜 + 本俱乐部榜 + + + {{currentDate}} · 按{{sortLabel}} · TOP50 + + + + 🏆 {{rewardInfo.title || '排行榜奖励'}} + 俱乐部 {{rewardInfo.club_id || myClubId}} + 进行中 · 结束后可领 + 待领取 ¥{{rewardInfo.my_claim.amount}} + 已领取 ¥{{rewardInfo.my_claimed.amount}} + + 规则 › + @@ -38,97 +55,85 @@ + + + - - - - - - - 2 - + + 👑 + {{topThree[1].nicheng}} - ID {{topThree[1].uid}} - - ¥{{topThree[1].mainValue}} - {{topThree[1].mainLabel}} - + {{topThree[1].mainLabel}} + {{topThree[1].mainPrefix}}{{topThree[1].mainValue}} {{m.label}} {{m.value}} - + 奖 ¥{{topThree[1].rewardAmount}} + - - - 1 - + + 👑 + - {{topThree[0].nicheng}} - ID {{topThree[0].uid}} - - ¥{{topThree[0].mainValue}} - {{topThree[0].mainLabel}} - + {{topThree[0].nicheng}} + {{topThree[0].mainLabel}} + {{topThree[0].mainPrefix}}{{topThree[0].mainValue}} {{m.label}} {{m.value}} - + 奖 ¥{{topThree[0].rewardAmount}} + - - - 3 - + + 👑 + {{topThree[2].nicheng}} - ID {{topThree[2].uid}} - - ¥{{topThree[2].mainValue}} - {{topThree[2].mainLabel}} - + {{topThree[2].mainLabel}} + {{topThree[2].mainPrefix}}{{topThree[2].mainValue}} {{m.label}} {{m.value}} - + 奖 ¥{{topThree[2].rewardAmount}} + - - - - 完整榜单 - 第 4 - {{rankList.length}} 名 - - - - - - {{item.mingci}} - - - {{item.nicheng}} - ID {{item.uid}} - - {{m.label}} {{m.value}} - - - - ¥{{item.mainValue}} - {{item.mainLabel}} + + + + {{item.mingci}} + + {{item.nicheng}} + + {{item.mainPrefix}}{{item.mainValue}} + {{item.mainLabel}} + + {{m.label}} {{m.value}} + 奖¥{{item.rewardAmount}} + + + + 🎉 第{{rewardInfo.my_claim.mingci}}名 · 待领取 + ¥{{rewardInfo.my_claim.amount}} + + + diff --git a/pages/fighter-rank/fighter-rank.wxss b/pages/fighter-rank/fighter-rank.wxss index 232f63d..38540ce 100644 --- a/pages/fighter-rank/fighter-rank.wxss +++ b/pages/fighter-rank/fighter-rank.wxss @@ -1,290 +1,434 @@ page { - background: #0d1117; - color: #fff; - font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif; + background: #ffe566; + color: #333; + font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; } -/* 主题色 */ -/* 选中/金额: #6BA3F7 浅蓝: #8EB8FF 银: #A8B4C4 铜: #C4A882 */ - .rank-page { min-height: 100vh; - position: relative; + background: linear-gradient(180deg, #ffe566 0%, #ffe566 420rpx, #fff8dc 520rpx, #ffffff 620rpx); } -.page-bg { - position: fixed; - top: 0; left: 0; - width: 100%; height: 100%; - z-index: 0; -} - -/* ========== 顶栏 ========== */ -.top-bar { +/* ========== 顶区 ========== */ +.header-zone { position: relative; z-index: 10; - padding: 16rpx 28rpx 20rpx; + padding-bottom: 8rpx; } -.top-row { +.role-tabs { + display: flex; + align-items: stretch; + background: #ffffff; + margin: 0 0 0; + padding: 0 8rpx; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04); +} + +.role-tab { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 24rpx 0 18rpx; + position: relative; +} + +.role-tab-text { + font-size: 28rpx; + color: #666666; + line-height: 1.2; +} + +.role-tab.active .role-tab-text { + color: #1a1a1a; + font-weight: 700; + font-size: 30rpx; +} + +.role-tab-line { + position: absolute; + bottom: 8rpx; + left: 50%; + transform: translateX(-50%); + width: 48rpx; + height: 6rpx; + border-radius: 6rpx; + background: #3d2914; +} + +.date-scroll { + width: 100%; + white-space: nowrap; + padding: 20rpx 0 8rpx; +} + +.date-line { + display: inline-flex; + padding: 0 20rpx; + gap: 8rpx; +} + +.date-tab { + display: inline-block; + padding: 10rpx 22rpx; + font-size: 26rpx; + color: rgba(0, 0, 0, 0.45); + border-radius: 8rpx; +} + +.date-tab.active { + color: #1a1a1a; + font-weight: 700; + position: relative; +} + +.date-tab.active::after { + content: ''; + position: absolute; + left: 50%; + bottom: 0; + transform: translateX(-50%); + width: 36rpx; + height: 4rpx; + border-radius: 4rpx; + background: #3d2914; +} + +.scope-row { display: flex; align-items: center; - justify-content: space-between; - margin-bottom: 20rpx; + gap: 12rpx; + padding: 8rpx 24rpx 12rpx; } -.page-title { - font-size: 38rpx; - font-weight: 700; - color: #fff; +.scope-tab { + padding: 8rpx 20rpx; + border-radius: 999rpx; + font-size: 22rpx; + color: rgba(0, 0, 0, 0.55); + background: rgba(255, 255, 255, 0.55); + border: 1rpx solid rgba(0, 0, 0, 0.06); } -.refresh-wrap { - width: 60rpx; height: 60rpx; +.scope-tab.active { + color: #1a1a1a; + font-weight: 600; + background: #ffffff; + border-color: rgba(0, 0, 0, 0.12); +} + +.refresh-btn { + margin-left: auto; + width: 56rpx; + height: 56rpx; border-radius: 50%; - background: rgba(255,255,255,0.14); + background: rgba(255, 255, 255, 0.7); display: flex; align-items: center; justify-content: center; } .refresh-ico { - width: 32rpx; height: 32rpx; - opacity: 0.8; + width: 28rpx; + height: 28rpx; + opacity: 0.65; } -.role-scroll { - width: 100%; - white-space: nowrap; - margin-bottom: 16rpx; +.reward-banner { + margin: 0 24rpx 12rpx; + padding: 18rpx 22rpx; + border-radius: 16rpx; + background: rgba(255, 255, 255, 0.85); + border: 1rpx solid rgba(255, 193, 7, 0.35); + display: flex; + align-items: center; + justify-content: space-between; } -.role-line { - display: inline-flex; - gap: 12rpx; +.reward-banner-main { + display: flex; + flex-direction: column; + gap: 4rpx; } -.role-chip { - display: inline-block; - padding: 12rpx 28rpx; - border-radius: 999rpx; - font-size: 26rpx; - color: rgba(255,255,255,0.65); - background: rgba(20, 26, 40, 0.75); - border: 1rpx solid rgba(255,255,255,0.12); -} - -.role-chip.on { - color: #fff; - font-weight: 600; - background: rgba(107, 163, 247, 0.45); - border-color: rgba(107, 163, 247, 0.65); - box-shadow: 0 0 20rpx rgba(107, 163, 247, 0.2); -} - -.date-grid { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 10rpx; - margin-bottom: 12rpx; -} - -.date-chip { - text-align: center; - padding: 14rpx 0; - border-radius: 14rpx; - font-size: 24rpx; - color: rgba(255,255,255,0.65); - background: rgba(20, 26, 40, 0.72); - border: 1rpx solid rgba(255,255,255,0.08); -} - -.date-chip.on { - color: #A8CCFF; - font-weight: 600; - background: rgba(107, 163, 247, 0.35); - border-color: rgba(107, 163, 247, 0.55); -} - -.sort-tip { - font-size: 22rpx; - color: rgba(255,255,255,0.3); -} +.reward-tag { font-size: 26rpx; font-weight: 600; color: #d48806; } +.reward-club { font-size: 22rpx; color: #666; } +.reward-status { font-size: 22rpx; color: #999; } +.reward-status.pending { color: #52c41a; font-weight: 600; } +.reward-status.claimed { color: #8c8c8c; } +.reward-link { font-size: 24rpx; color: #d48806; } /* ========== 状态 ========== */ .state-wrap { - position: relative; - z-index: 5; display: flex; flex-direction: column; align-items: center; - padding: 140rpx 0; - color: rgba(255,255,255,0.45); + padding: 120rpx 0; + color: rgba(0, 0, 0, 0.45); font-size: 28rpx; } .loader { - width: 48rpx; height: 48rpx; - border: 3rpx solid rgba(255,255,255,0.12); - border-top-color: #6BA3F7; + width: 48rpx; + height: 48rpx; + border: 3rpx solid rgba(0, 0, 0, 0.08); + border-top-color: #ffb800; border-radius: 50%; animation: spin 0.7s linear infinite; margin-bottom: 20rpx; } +@keyframes spin { + to { transform: rotate(360deg); } +} + .state-img { - width: 160rpx; height: 160rpx; + width: 160rpx; + height: 160rpx; margin-bottom: 20rpx; - opacity: 0.65; + opacity: 0.5; } -/* ========== 滚动 ========== */ +/* ========== 滚动区 ========== */ .body-scroll { - position: relative; - z-index: 5; - height: calc(100vh - 300rpx); + height: calc(100vh - 280rpx); } -/* ========== 领奖台(纯 CSS) ========== */ -.podium-panel { - margin: 8rpx 24rpx 24rpx; - padding: 28rpx 12rpx 0; - border-radius: 28rpx; - background: rgba(14, 18, 30, 0.88); - border: 1rpx solid rgba(255, 255, 255, 0.1); +/* ========== 领奖台 ========== */ +.podium-zone { + padding: 16rpx 16rpx 0; } -.podium-cols { +.podium-row { display: flex; align-items: flex-end; justify-content: center; - gap: 10rpx; + gap: 6rpx; } -.p-col { +.podium-col { flex: 1; - max-width: 220rpx; + max-width: 230rpx; display: flex; flex-direction: column; align-items: center; - position: relative; } -.p-first { - max-width: 250rpx; - margin-bottom: 16rpx; +.col-1 { + max-width: 260rpx; + margin-bottom: 10rpx; } -.p-badge { - width: 44rpx; height: 44rpx; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 14rpx; - font-size: 24rpx; - font-weight: 800; -} - -.p-first .p-badge { - width: 52rpx; height: 52rpx; +.crown { font-size: 28rpx; + line-height: 1; + margin-bottom: 6rpx; + filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.15)); } -.badge-first { - color: #fff; - background: linear-gradient(145deg, #6BA3F7, #4A7FD4); - box-shadow: 0 4rpx 16rpx rgba(107, 163, 247, 0.4); +.col-1 .crown { + font-size: 34rpx; } -.badge-silver { - color: #fff; - background: linear-gradient(145deg, #A8B4C4, #7A8899); - box-shadow: 0 4rpx 12rpx rgba(168, 180, 196, 0.25); -} - -.badge-bronze { - color: #fff; - background: linear-gradient(145deg, #C4A882, #9A7858); - box-shadow: 0 4rpx 12rpx rgba(196, 168, 130, 0.25); -} - -.p-avatar-wrap { +.avatar-ring { border-radius: 50%; - padding: 4rpx; - margin-bottom: 12rpx; + padding: 6rpx; + margin-bottom: 10rpx; + background: #ffffff; } -.wrap-first { - padding: 5rpx; - background: linear-gradient(145deg, #8EB8FF, #5B8FD8); - box-shadow: 0 6rpx 24rpx rgba(107, 163, 247, 0.3); +.ring-gold { + padding: 8rpx; + box-shadow: 0 0 0 4rpx #ffd700, 0 8rpx 20rpx rgba(255, 183, 0, 0.35); } -.wrap-silver { - background: linear-gradient(145deg, #C8D0DA, #909AA8); +.ring-silver { + box-shadow: 0 0 0 4rpx #b8c4d4, 0 6rpx 16rpx rgba(120, 140, 180, 0.25); } -.wrap-bronze { - background: linear-gradient(145deg, #D4B896, #A88860); +.ring-bronze { + box-shadow: 0 0 0 4rpx #cd9a5a, 0 6rpx 16rpx rgba(205, 127, 50, 0.25); } .p-avatar { - width: 100rpx; height: 100rpx; + width: 96rpx; + height: 96rpx; border-radius: 50%; display: block; - background: #1a2030; + background: #f0f0f0; } .p-avatar-lg { - width: 120rpx; height: 120rpx; + width: 116rpx; + height: 116rpx; } .p-name { font-size: 26rpx; font-weight: 600; - color: rgba(255,255,255,0.92); + color: #1a1a1a; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; + margin-bottom: 8rpx; } -.p-name-first { +.p-name-lg { font-size: 28rpx; - color: #fff; -} - -.p-uid { - margin-top: 4rpx; - font-size: 20rpx; - color: rgba(255,255,255,0.35); -} - -.p-amount { - margin: 12rpx 0 10rpx; - text-align: center; -} - -.p-money { - display: block; - font-size: 30rpx; - font-weight: 700; - color: #8EB8FF; - letter-spacing: -0.5rpx; -} - -.p-money-first { - font-size: 34rpx; - color: #A8CCFF; } .p-label { - display: block; + font-size: 22rpx; + color: #999999; + line-height: 1.2; +} + +.p-amount { + font-size: 34rpx; + font-weight: 800; + color: #ffb800; + line-height: 1.3; margin-top: 2rpx; +} + +.p-amount-lg { + font-size: 40rpx; +} + +.p-sub-label { + font-size: 22rpx; + color: #999999; + margin-top: 6rpx; +} + +.p-sub-value { + font-size: 28rpx; + font-weight: 700; + color: #ffb800; + line-height: 1.2; +} + +.p-sub-value-lg { + font-size: 32rpx; +} + +.p-reward { + margin-top: 4rpx; font-size: 20rpx; - color: rgba(255,255,255,0.35); + color: #d48806; + font-weight: 600; +} + +.pedestal { + width: 92%; + margin-top: 14rpx; + border-radius: 16rpx 16rpx 0 0; + background: linear-gradient(180deg, #ffe08a 0%, #ffd04d 100%); + box-shadow: inset 0 2rpx 0 rgba(255, 255, 255, 0.5); +} + +.pedestal-1 { + height: 88rpx; + background: linear-gradient(180deg, #ffe08a 0%, #ffc933 100%); +} + +.pedestal-2 { + height: 64rpx; + background: linear-gradient(180deg, #ffe08a 0%, #ffd04d 100%); +} + +.pedestal-3 { + height: 48rpx; + background: linear-gradient(180deg, #ffe08a 0%, #ffd04d 100%); +} + +/* ========== 列表区(白底) ========== */ +.list-panel { + margin-top: 8rpx; + background: #ffffff; + border-radius: 32rpx 32rpx 0 0; + padding: 24rpx 28rpx 0; + min-height: 400rpx; + box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.04); +} + +.list-row { + display: flex; + align-items: center; + padding: 24rpx 0; + border-bottom: 1rpx solid #f3f3f3; +} + +.list-row:last-child { + border-bottom: none; +} + +.li-rank { + width: 44rpx; + flex-shrink: 0; + font-size: 32rpx; + font-weight: 700; + color: #333333; + text-align: center; +} + +.li-avatar { + width: 72rpx; + height: 72rpx; + border-radius: 50%; + flex-shrink: 0; + margin: 0 16rpx 0 8rpx; + background: #f0f0f0; +} + +.li-name { + flex: 1; + min-width: 0; + font-size: 28rpx; + font-weight: 500; + color: #1a1a1a; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.li-right { + flex-shrink: 0; + text-align: right; + margin-left: 12rpx; +} + +.li-amount { + display: block; + font-size: 30rpx; + font-weight: 700; + color: #1a1a1a; + line-height: 1.3; +} + +.li-orders { + display: block; + margin-top: 4rpx; + font-size: 24rpx; + color: #999999; +} + +.li-reward { + display: block; + margin-top: 4rpx; + font-size: 20rpx; + color: #d48806; +} + +.sort-tip { + display: block; + padding: 0 24rpx 8rpx; + font-size: 22rpx; + color: rgba(0, 0, 0, 0.45); } .p-metrics { @@ -292,176 +436,86 @@ page { flex-wrap: wrap; gap: 6rpx; justify-content: center; - margin-bottom: 16rpx; - min-height: 36rpx; + margin: 8rpx 0 10rpx; + max-width: 100%; } .p-metric { - font-size: 18rpx; - color: rgba(255,255,255,0.55); - background: rgba(255,255,255,0.1); + font-size: 20rpx; + color: #888; + background: rgba(255, 255, 255, 0.65); padding: 4rpx 10rpx; border-radius: 8rpx; } -/* 底座 · 圆角柱体,非方块 */ -.p-pedestal { - width: 88%; - border-radius: 20rpx 20rpx 0 0; - position: relative; -} - -.p-pedestal::before { - content: ''; - position: absolute; - top: 0; left: 10%; right: 10%; - height: 2rpx; - background: rgba(255,255,255,0.15); - border-radius: 2rpx; -} - -.pedestal-first { - height: 100rpx; - background: linear-gradient(180deg, rgba(107,163,247,0.55) 0%, rgba(107,163,247,0.22) 100%); - border: 1rpx solid rgba(107,163,247,0.35); - border-bottom: none; -} - -.pedestal-silver { - height: 72rpx; - background: linear-gradient(180deg, rgba(168,180,196,0.45) 0%, rgba(168,180,196,0.18) 100%); - border: 1rpx solid rgba(168,180,196,0.28); - border-bottom: none; -} - -.pedestal-bronze { - height: 56rpx; - background: linear-gradient(180deg, rgba(196,168,130,0.45) 0%, rgba(196,168,130,0.18) 100%); - border: 1rpx solid rgba(196,168,130,0.28); - border-bottom: none; -} - -/* ========== 列表 ========== */ -.list-wrap { - padding: 0 24rpx; -} - -.list-title-row { - display: flex; - align-items: baseline; - justify-content: space-between; - padding: 8rpx 8rpx 20rpx; -} - -.list-title { - font-size: 30rpx; - font-weight: 700; - color: rgba(255,255,255,0.85); -} - -.list-range { - font-size: 22rpx; - color: rgba(255,255,255,0.3); -} - -.list-card { - position: relative; - margin-bottom: 14rpx; - border-radius: 18rpx; - overflow: hidden; - border: 1rpx solid rgba(255,255,255,0.05); -} - -.list-card-bg { - position: absolute; - top: 0; left: 0; - width: 100%; height: 100%; - z-index: 0; -} - -.list-card-body { - position: relative; - z-index: 1; - display: flex; - align-items: center; - padding: 20rpx 22rpx; - background: rgba(12, 16, 28, 0.92); - backdrop-filter: blur(8px); -} - -.lc-rank { - width: 48rpx; - flex-shrink: 0; - text-align: center; -} - -.lc-rank text { - font-size: 32rpx; - font-weight: 700; - color: rgba(255,255,255,0.18); -} - -.lc-avatar { - width: 80rpx; height: 80rpx; - border-radius: 50%; - flex-shrink: 0; - margin: 0 16rpx 0 6rpx; - border: 2rpx solid rgba(107, 163, 247, 0.2); - background: #1a2030; -} - -.lc-info { flex: 1; min-width: 0; } - -.lc-name { +.li-label { display: block; - font-size: 28rpx; - font-weight: 600; - color: rgba(255,255,255,0.92); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.lc-uid { - display: block; - margin-top: 4rpx; + margin-top: 2rpx; font-size: 22rpx; - color: rgba(255,255,255,0.32); + color: #999; } -.lc-tags { +.li-tags { display: flex; flex-wrap: wrap; - gap: 8rpx; - margin-top: 8rpx; + gap: 6rpx; + justify-content: flex-end; + margin-top: 6rpx; + max-width: 280rpx; } -.lc-tag { +.li-tag { font-size: 20rpx; - color: rgba(255,255,255,0.5); - background: rgba(255,255,255,0.1); - padding: 2rpx 10rpx; + color: #888; + background: #f5f5f5; + padding: 2rpx 8rpx; border-radius: 6rpx; } -.lc-score { - flex-shrink: 0; - text-align: right; - margin-left: 10rpx; +.bottom-gap { + height: 160rpx; } -.lc-money { - display: block; - font-size: 28rpx; +/* ========== 领取栏 ========== */ +.claim-bar { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 100; + padding: 20rpx 28rpx calc(20rpx + env(safe-area-inset-bottom)); + background: #ffffff; + display: flex; + align-items: center; + justify-content: space-between; + border-top: 1rpx solid #f0f0f0; + box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06); +} + +.claim-text { + display: flex; + flex-direction: column; +} + +.claim-title { + font-size: 26rpx; + color: #333; +} + +.claim-amount { + font-size: 36rpx; font-weight: 700; - color: #8EB8FF; + color: #ffb800; } -.lc-label { - display: block; - margin-top: 2rpx; - font-size: 20rpx; - color: rgba(255,255,255,0.32); +.claim-btn { + margin: 0; + padding: 0 40rpx; + height: 72rpx; + line-height: 72rpx; + font-size: 28rpx; + background: linear-gradient(135deg, #ffc107, #ffb300); + color: #1a1a1a; + border-radius: 999rpx; + border: none; } - -.bottom-gap { height: 48rpx; } diff --git a/pages/fighter-recharge/fighter-deposit.js b/pages/fighter-recharge/fighter-deposit.js new file mode 100644 index 0000000..932b0db --- /dev/null +++ b/pages/fighter-recharge/fighter-deposit.js @@ -0,0 +1,368 @@ +// pages/fighter-recharge/fighter-deposit.js — 平台履约金/保证金独立页 +import request from '../../utils/request'; +import PopupService from '../../services/popupService.js'; +import { ensurePhoneAuth } from '../../utils/phone-auth.js'; +import { ICON_KEYS, resolveConfiguredIcon, resolveMiniappIcon, ICON_FOLDER, refreshPindaoConfig } from '../../utils/miniapp-icons.js'; +import { resolveAvatarFromStorage, onAvatarImageError } from '../../utils/avatar.js'; + +const app = getApp(); + +const DEFAULT_RULES = [ + '履约金有效期为永久有效', + '超过15天未接单,可联系客服申请退还', + '缴纳后可优先接锁单/平台单等待遇', +]; + +const DEFAULT_BENEFITS = [ + { label: '平台订单优先', key: ICON_KEYS.FIGHTER_DEPOSIT_BENEFIT_1 }, + { label: '快人一步', key: ICON_KEYS.FIGHTER_DEPOSIT_BENEFIT_2 }, + { label: '优质接单', key: ICON_KEYS.FIGHTER_DEPOSIT_BENEFIT_3 }, + { label: '解锁平台单', key: ICON_KEYS.FIGHTER_DEPOSIT_BENEFIT_4 }, +]; + +Page({ + data: { + imgUrls: { pageBg: '', hero: '' }, + avatarUrl: '', + nickname: '', + uid: '', + yajin: 0, + yajinDisplay: '0.00', + yajinActive: false, + depositRules: DEFAULT_RULES, + depositBenefits: DEFAULT_BENEFITS, + agreed: false, + payAmountLabel: '', + yajinAmount: '100', + quickAmounts: ['100', '500', '1000', '5000'], + showAmountModal: false, + showPayMethodModal: false, + showBalanceModal: false, + showConfirmModal: false, + balanceOptions: [], + selectedBalanceId: null, + selectedBalanceInfo: null, + currentYajinAmount: null, + currentDingdanid: '', + payLoading: false, + loadingText: '处理中...', + }, + + onLoad(options) { + this._jumpParams = options || {}; + this.initImages(); + this.loadUserInfo(); + }, + + onHide() { + this.selectComponent('#popupNotice')?.cleanup?.(); + }, + + async onShow() { + const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' }); + if (!phoneOk) return; + this.registerNotification(); + this.loadYajin(); + this.refreshConfiguredIcons(); + PopupService.checkAndShow(this, 'dashouchongzhi'); + const preset = this._jumpParams?.presetAmount || this._jumpParams?.requiredYajin; + if (preset && parseFloat(preset) > 0) { + this.setData({ yajinAmount: String(parseInt(preset, 10)) }); + } + }, + + registerNotification() { + const comp = this.selectComponent('#global-notification'); + if (comp?.showNotification) { + app.globalData.globalNotification = { + show: (data) => comp.showNotification(data), + hide: () => comp.hideNotification(), + }; + } + }, + + initImages() { + const fb = (key) => `${ICON_FOLDER}/${key}.png`; + const depositBenefits = DEFAULT_BENEFITS.map((item) => ({ + label: item.label, + iconUrl: resolveMiniappIcon(app, item.key, fb(item.key)), + })); + this.setData({ + imgUrls: { + pageBg: resolveConfiguredIcon(app, ICON_KEYS.FIGHTER_DEPOSIT_BG), + hero: resolveConfiguredIcon(app, ICON_KEYS.FIGHTER_DEPOSIT_HERO), + }, + depositBenefits, + }); + }, + + async refreshConfiguredIcons() { + try { + await refreshPindaoConfig(app); + } catch (e) { + console.warn('refreshConfiguredIcons failed', e); + } + this.initImages(); + this.loadUserInfo(); + }, + + loadUserInfo() { + const gd = app.globalData || {}; + this.setData({ + avatarUrl: resolveAvatarFromStorage(app), + nickname: gd.dashouNicheng || gd.nicheng || '打手', + uid: wx.getStorageSync('uid') || gd.uid || '', + }); + }, + + onAvatarError() { + onAvatarImageError(this, 'avatarUrl', app); + }, + + loadYajin() { + const yajin = parseFloat(app.globalData.yajin || 0) || 0; + this.setData({ + yajin, + yajinDisplay: yajin.toFixed(2), + yajinActive: yajin > 0, + }); + }, + + toggleAgree() { + this.setData({ agreed: !this.data.agreed }); + }, + + onTapPay() { + if (!this.data.agreed) { + wx.showToast({ title: '请先阅读并同意缴纳规则', icon: 'none' }); + return; + } + this.setData({ showAmountModal: true }); + }, + + hideAmountModal() { + this.setData({ showAmountModal: false }); + }, + + onYajinInput(e) { + let v = (e.detail.value || '').replace(/[^\d]/g, ''); + if (v) { + const n = parseInt(v, 10); + if (n > 10000) v = '10000'; + if (n < 1) v = '1'; + } + this.setData({ yajinAmount: v }); + }, + + setQuickAmount(e) { + this.setData({ yajinAmount: e.currentTarget.dataset.amount }); + }, + + onConfirmAmount() { + const amount = this.data.yajinAmount; + if (!amount || amount < 1 || amount > 10000) { + wx.showToast({ title: '请输入1-10000元', icon: 'none' }); + return; + } + this.hideAmountModal(); + this.setData({ + currentYajinAmount: amount, + payAmountLabel: `(¥ ${amount})`, + showPayMethodModal: true, + }); + }, + + hidePayMethodModal() { + this.setData({ showPayMethodModal: false }); + }, + + onPayMethodSelect(e) { + const method = e.currentTarget.dataset.method; + this.hidePayMethodModal(); + if (method === 'wx') { + this.setData({ yajinAmount: this.data.currentYajinAmount }, () => this.handleYajinPay()); + } else { + this.fetchBalanceOptions(); + } + }, + + async fetchBalanceOptions() { + this.showLoading('获取抵扣信息...'); + try { + const res = await request({ + url: '/shangpin/czhqdy', + method: 'POST', + data: { + leixing: 2, + yajin_jine: parseInt(this.data.currentYajinAmount, 10), + }, + }); + if (res.data.code === 200) { + this.setData({ balanceOptions: res.data.data || [], showBalanceModal: true }); + } else { + wx.showToast({ title: res.data.message || '获取失败', icon: 'none' }); + } + } catch (err) { + wx.showToast({ title: '网络错误', icon: 'none' }); + } finally { + this.hideLoading(); + } + }, + + hideBalanceModal() { + this.setData({ showBalanceModal: false }); + }, + + onBalanceSelect(e) { + const id = e.currentTarget.dataset.id; + const selected = this.data.balanceOptions.find((o) => o.id === id); + if (!selected) return; + this.setData({ + selectedBalanceId: id, + selectedBalanceInfo: selected, + showConfirmModal: true, + showBalanceModal: false, + }); + }, + + hideConfirmModal() { + this.setData({ showConfirmModal: false }); + }, + + async confirmBalancePay() { + const { selectedBalanceId, currentYajinAmount } = this.data; + if (!selectedBalanceId) return; + this.hideConfirmModal(); + this.showLoading('抵扣支付中...'); + try { + const res = await request({ + url: '/shangpin/dsqrgmdh', + method: 'POST', + data: { + leixing: 2, + shenfen_id: selectedBalanceId, + yajin_jine: parseInt(currentYajinAmount, 10), + }, + }); + if (res.data.code === 200) { + const data = res.data.data || {}; + if (data.yajin !== undefined) app.globalData.yajin = data.yajin; + this.loadYajin(); + wx.showToast({ title: '缴纳成功', icon: 'success' }); + } else { + wx.showToast({ title: res.data.message || '购买失败', icon: 'none' }); + } + } catch (err) { + wx.showToast({ title: '网络错误', icon: 'none' }); + } finally { + this.hideLoading(); + } + }, + + confirmWechatPurchase() { + return new Promise((resolve) => { + wx.showModal({ + title: '确认微信支付', + content: '您即将通过微信支付缴纳履约金。购买成功后不可随意退款,请确认无误后再继续。', + confirmText: '确认购买', + cancelText: '再想想', + success: (res) => resolve(!!res.confirm), + fail: () => resolve(false), + }); + }); + }, + + async handleYajinPay() { + const amount = this.data.yajinAmount; + if (!amount || amount < 1 || amount > 10000) { + wx.showToast({ title: '请输入1-10000元', icon: 'none' }); + return; + } + if (!(await this.confirmWechatPurchase())) return; + this.showLoading('发起支付中...'); + try { + const res = await request({ + url: '/shangpin/yajingoumai', + method: 'POST', + data: { jine: parseInt(amount, 10) }, + }); + if (res.data.code === 200) { + this.setData({ currentDingdanid: res.data.dingdanid }); + await this.wechatPay(res.data.payParams); + } else { + wx.showToast({ title: res.data.message || '支付发起失败', icon: 'none' }); + } + } catch (err) { + wx.showToast({ title: '支付失败', icon: 'none' }); + } finally { + this.hideLoading(); + } + }, + + wechatPay(payParams) { + return new Promise((resolve, reject) => { + wx.requestPayment({ + timeStamp: payParams.timeStamp, + nonceStr: payParams.nonceStr, + package: payParams.package, + signType: payParams.signType || 'MD5', + paySign: payParams.paySign, + success: async () => { + wx.showToast({ title: '支付成功!确认中...', icon: 'none' }); + try { + await this.startPolling(); + resolve(); + } catch (e) { + reject(e); + } + }, + fail: async (res) => { + wx.showToast({ title: res.errMsg?.includes('cancel') ? '已取消支付' : '支付失败', icon: 'none' }); + try { + await request({ + url: '/shangpin/yajinshibai', + method: 'POST', + data: { dingdanid: this.data.currentDingdanid }, + }); + } catch (e) { /* ignore */ } + reject(new Error(res.errMsg)); + }, + }); + }); + }, + + async startPolling() { + const dingdanid = this.data.currentDingdanid; + if (!dingdanid) return; + this.showLoading('确认支付结果...'); + for (let i = 0; i < 10; i++) { + try { + const res = await request({ + url: '/shangpin/yajinlunxun', + method: 'POST', + data: { dingdanid }, + }); + if (res.data.code === 200) { + if (res.data.yajin !== undefined) app.globalData.yajin = res.data.yajin; + this.loadYajin(); + this.hideLoading(); + wx.showToast({ title: '履约金缴纳成功', icon: 'success' }); + return; + } + } catch (e) { /* retry */ } + await new Promise((r) => setTimeout(r, 2000)); + } + this.hideLoading(); + wx.showToast({ title: '确认超时,请稍后查看余额', icon: 'none' }); + }, + + showLoading(text) { + this.setData({ payLoading: true, loadingText: text || '处理中...' }); + }, + + hideLoading() { + this.setData({ payLoading: false }); + }, + + stopPropagation() {}, +}); diff --git a/pages/fighter-recharge/fighter-deposit.json b/pages/fighter-recharge/fighter-deposit.json new file mode 100644 index 0000000..e0966f2 --- /dev/null +++ b/pages/fighter-recharge/fighter-deposit.json @@ -0,0 +1,10 @@ +{ + "usingComponents": { + "global-notification": "/components/global-notification/global-notification", + "popup-notice": "/components/popup-notice/popup-notice" + }, + "navigationBarTitleText": "平台履约金", + "navigationBarBackgroundColor": "#FFF9F0", + "navigationBarTextStyle": "black", + "backgroundColor": "#FFF9F0" +} diff --git a/pages/fighter-recharge/fighter-deposit.wxml b/pages/fighter-recharge/fighter-deposit.wxml new file mode 100644 index 0000000..183de81 --- /dev/null +++ b/pages/fighter-recharge/fighter-deposit.wxml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + 平台接单履约金 + + ¥ + {{yajinDisplay}} + + 有效期:永久有效 + + + + + + {{yajinActive ? '已缴纳' : '未激活'}} + + + + + + 履约金规则 + + + {{index + 1}}. + {{item}} + + + + + + 平台履约金权益 + + + + + + {{item.label}} + + + + + + + + {{agreed ? '✓' : ''}} + 已阅读并同意 + 《平台履约金缴纳规则》 + + + 缴纳履约金{{payAmountLabel}} + + + + + + + + + 缴纳履约金 + 支持 1-10000 元整数 + + ¥ + + + + {{item}}元 + + + 取消 + 下一步 + + + + + + + + 选择支付方式 + + 微信支付 + 安全快捷 + + + 余额抵扣 + 使用佣金或分红 + + + + + + + 选择抵扣身份 + + {{item.jieshao}} + 需¥{{item.xuyao}} + + + + + + + 确认抵扣 + 身份{{selectedBalanceInfo.jieshao}} + 金额¥{{selectedBalanceInfo.xuyao}} + + 取消 + 确认支付 + + + + + + + + {{loadingText}} + + + + + diff --git a/pages/fighter-recharge/fighter-deposit.wxss b/pages/fighter-recharge/fighter-deposit.wxss new file mode 100644 index 0000000..3680266 --- /dev/null +++ b/pages/fighter-recharge/fighter-deposit.wxss @@ -0,0 +1,279 @@ +@import '../../styles/xym-recharge-shared.wxss'; + +.deposit-page { + min-height: 100vh; + background: #fff9f0; + padding-bottom: calc(220rpx + env(safe-area-inset-bottom)); + position: relative; +} + +.page-bg { + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: 0; +} + +.page-bg-fallback { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 360rpx; + background: linear-gradient(180deg, #ffe082 0%, #fff9f0 100%); + z-index: 0; +} + +.page-body { + position: relative; + z-index: 1; + padding: 24rpx 30rpx 0; +} + +.user-row { + display: flex; + align-items: center; + margin-bottom: 24rpx; +} + +.user-avatar { + width: 88rpx; + height: 88rpx; + border-radius: 50%; + margin-right: 20rpx; + background: #eee; +} + +.user-name { + display: block; + font-size: 32rpx; + font-weight: 700; + color: #222; +} + +.user-id { + display: block; + margin-top: 6rpx; + font-size: 24rpx; + color: #999; +} + +.deposit-card { + position: relative; + overflow: hidden; + border-radius: 24rpx; + padding: 32rpx 28rpx; + background: linear-gradient(135deg, #fff8e1 0%, #ffe0b2 100%); + display: flex; + justify-content: space-between; + align-items: stretch; + margin-bottom: 24rpx; + box-shadow: 0 8rpx 24rpx rgba(255, 183, 77, 0.25); +} + +.deposit-card-left { + flex: 1; + min-width: 0; +} + +.deposit-card-title { + font-size: 30rpx; + font-weight: 700; + color: #492f00; +} + +.deposit-price-row { + display: flex; + align-items: baseline; + margin: 16rpx 0 8rpx; +} + +.deposit-currency { + font-size: 36rpx; + font-weight: 700; + color: #222; + margin-right: 4rpx; +} + +.deposit-price { + font-size: 64rpx; + font-weight: 800; + color: #222; + line-height: 1; +} + +.deposit-validity { + font-size: 24rpx; + color: #947641; +} + +.deposit-card-right { + position: relative; + width: 200rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.icon-clip { + overflow: hidden; + flex-shrink: 0; + background: rgba(255, 255, 255, 0.5); +} + +.icon-clip-img { + width: 100%; + height: 100%; + display: block; +} + +.icon-clip-deposit { + width: 180rpx; + height: 180rpx; + border-radius: 40rpx; +} + +.icon-clip-deposit-benefit { + width: 36rpx; + height: 36rpx; + border-radius: 14rpx; +} + +.deposit-status { + position: absolute; + right: 0; + bottom: 0; + padding: 6rpx 16rpx; + border-radius: 8rpx; + background: rgba(0, 0, 0, 0.08); + font-size: 22rpx; + color: #666; +} + +.deposit-shimmer { + position: absolute; + inset: 0; + background: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.45) 50%, transparent 60%); + animation: card-shimmer 3s infinite; + pointer-events: none; +} + +.section-card { + background: #fff; + border-radius: 20rpx; + padding: 28rpx; + margin-bottom: 20rpx; + box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04); +} + +.section-title { + display: block; + text-align: center; + font-size: 30rpx; + font-weight: 700; + color: #333; + margin-bottom: 20rpx; +} + +.rule-item { + display: flex; + gap: 8rpx; + margin-bottom: 12rpx; + font-size: 26rpx; + color: #666; + line-height: 1.6; +} + +.benefit-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20rpx 16rpx; +} + +.benefit-item { + display: flex; + align-items: center; + gap: 10rpx; + font-size: 26rpx; + color: #555; +} + +.footer-bar { + position: fixed; + left: 0; + right: 0; + bottom: 0; + padding: 20rpx 30rpx calc(20rpx + env(safe-area-inset-bottom)); + background: rgba(255, 249, 240, 0.96); + box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06); + z-index: 10; +} + +.agree-row { + display: flex; + align-items: center; + flex-wrap: wrap; + margin-bottom: 16rpx; + font-size: 24rpx; + color: #666; +} + +.agree-check { + width: 32rpx; + height: 32rpx; + border-radius: 50%; + border: 2rpx solid #ccc; + margin-right: 10rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 20rpx; + color: #fff; +} + +.agree-check--on { + background: #f5a623; + border-color: #f5a623; +} + +.agree-link { + color: #e6a23c; +} + +.pay-btn { + position: relative; + overflow: hidden; + height: 96rpx; + border-radius: 48rpx; + background: linear-gradient(90deg, #fae04d, #ffc0a3); + display: flex; + align-items: center; + justify-content: center; + font-size: 32rpx; + font-weight: 700; + color: #492f00; + box-shadow: 0 8rpx 20rpx rgba(245, 166, 35, 0.35); +} + +.pay-btn--disabled { + opacity: 0.55; +} + +.pay-btn-shimmer { + position: absolute; + inset: 0; + background: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.5) 50%, transparent 60%); + animation: btn-shimmer 2.5s infinite; + pointer-events: none; +} + +@keyframes card-shimmer { + 0% { transform: translateX(-120%); } + 100% { transform: translateX(120%); } +} + +@keyframes btn-shimmer { + 0% { transform: translateX(-120%); } + 100% { transform: translateX(120%); } +} diff --git a/pages/fighter-recharge/fighter-recharge.js b/pages/fighter-recharge/fighter-recharge.js index 63292eb..6441ae6 100644 --- a/pages/fighter-recharge/fighter-recharge.js +++ b/pages/fighter-recharge/fighter-recharge.js @@ -1,72 +1,57 @@ -// pages/dashou-chongzhi/index.js +// pages/fighter-recharge/fighter-recharge.js import request from '../../utils/request'; import PopupService from '../../services/popupService.js'; import { ensurePhoneAuth } from '../../utils/phone-auth.js'; +import { ICON_KEYS, resolveConfiguredIcon, resolveMiniappIcon, ICON_FOLDER, refreshPindaoConfig } from '../../utils/miniapp-icons.js'; +import { resolveAvatarFromStorage, onAvatarImageError } from '../../utils/avatar.js'; + +const app = getApp(); + +const PLATFORM_ADVANTAGE_META = [ + { key: ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_1, title: '订单丰富', desc: '平台单、商家单多种类型' }, + { key: ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_2, title: '俱乐部专属', desc: '按俱乐部独立配置权益' }, + { key: ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_3, title: '客服支持', desc: '遇到问题随时联系客服' }, + { key: ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_4, title: '安全可靠', desc: '资金与订单平台保障' }, +]; Page({ data: { - // ========== 全局数据 ========== - clubmber: [], // 会员列表(注意:全局变量中是 clumber,这里保持原样) - yajin: 0, // 押金 - jifen: 0, // 积分(注意:全局变量中是 jinfen) - jifenProgress: 0, // 积分进度条角度 - - // ========== 会员商品列表 ========== - huiyuanList: [], // 从后端获取的会员列表 - currentHuiyuanIndex: 0, // 当前选中的会员索引 - currentHuiyuan: {}, // 当前选中的会员详情 - - // ========== 押金充值相关 ========== - showYajinModal: false, // 显示押金弹窗 - yajinAmount: '100', // 押金金额 - - // ========== 支付相关 ========== - payLoading: false, // 支付加载状态 + imgUrls: { pageBg: '' }, + avatarUrl: '', + nickname: '', + uid: '', + memberBadge: '会员', + agreed: false, + memberBenefits: [], + platformAdvantages: [], + clubmber: [], + jifen: 0, + huiyuanList: [], + currentHuiyuanIndex: 0, + currentHuiyuan: {}, + payLoading: false, loadingText: '支付中...', - - // ========== 订单ID记录 ========== - currentDingdanid: '', // 当前操作的订单ID - - // ========== 计算高度 ========== - huiyuanListHeight: 120, - - // ========== 会员详情规则 ========== + currentDingdanid: '', detailRules: [], - - // ========== 新增:跳转参数处理 ========== - // 用于接收从其他页面跳转过来的参数,决定是否自动滚动 jumpParams: {}, - - // ========== 新增:支付方式选择 ========== - showPayMethodModal: false, // 支付方式选择弹窗 - currentBuyType: null, // 当前购买类型:1会员 2押金 3积分 - currentHuiyuanId: null, // 当前选中的会员ID(用于会员购买) - currentBuyIsTrial: false, // 正式/体验:仅体验禁余额抵扣 - currentYajinAmount: null, // 当前押金金额(用于押金购买) - - // ========== 新增:余额抵扣身份选择 ========== - showBalanceModal: false, // 余额抵扣身份选择弹窗 - balanceOptions: [], // 后端返回的身份列表 - selectedBalanceId: null, // 选中的身份ID - - // ========== 新增:余额抵扣确认弹窗 ========== - showConfirmModal: false, // 余额抵扣确认弹窗 - selectedBalanceInfo: null, // 选中的身份详情(用于确认弹窗) - - // ========== 新增:会员详情弹窗 ========== + showPayMethodModal: false, + currentBuyType: null, + currentHuiyuanId: null, + currentBuyIsTrial: false, + showBalanceModal: false, + balanceOptions: [], + selectedBalanceId: null, + showConfirmModal: false, + selectedBalanceInfo: null, showMemberModal: false, currentMemberDetail: null, }, // ========== 生命周期函数 ========== onLoad(options) { - //console.log('页面加载,跳转参数:', options); - - // ✅ 新增:保存跳转参数 - this.setData({ - jumpParams: options || {} - }); - + this.setData({ jumpParams: options || {} }); + this.initImages(); + this.loadUserInfo(); this.initPage(); }, @@ -86,6 +71,7 @@ onHide() { this.registerNotificationComponent(); this.loadFromGlobalData(); this.checkAndRefreshData(); + this.refreshConfiguredIcons(); // 调用统一弹窗组件检查并展示公告 PopupService.checkAndShow(this, 'dashouchongzhi'); @@ -112,16 +98,139 @@ onHide() { // ========== 初始化页面 ========== async initPage() { - // 从全局数据加载 this.loadFromGlobalData(); - - // 获取会员商品列表 await this.fetchHuiyuanList(); - - // 计算会员列表高度 - this.calculateHuiyuanHeight(); }, + initImages() { + this.setData({ + imgUrls: { + pageBg: resolveConfiguredIcon(app, ICON_KEYS.FIGHTER_RECHARGE_VIP_BG), + }, + platformAdvantages: this.buildPlatformAdvantages(), + }); + }, + + async refreshConfiguredIcons() { + try { + await refreshPindaoConfig(app); + } catch (e) { + console.warn('refreshConfiguredIcons failed', e); + } + this.initImages(); + const list = this.data.huiyuanList || []; + const idx = this.data.currentHuiyuanIndex || 0; + if (list.length) { + this.applyCurrentHuiyuan(list[idx] || {}, idx); + } + this.loadUserInfo(); + }, + + _icon(key, fallbackRel) { + return resolveMiniappIcon(app, key, fallbackRel); + }, + + buildPlatformAdvantages() { + const fb = (name) => `${ICON_FOLDER}/${name}.png`; + const keys = [ + ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_1, + ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_2, + ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_3, + ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_4, + ]; + return PLATFORM_ADVANTAGE_META.map((item, i) => ({ + title: item.title, + desc: item.desc, + iconUrl: this._icon(keys[i], fb(keys[i])), + })); + }, + + loadUserInfo() { + const gd = app.globalData || {}; + this.setData({ + avatarUrl: resolveAvatarFromStorage(app), + nickname: gd.dashouNicheng || gd.nicheng || '打手', + uid: wx.getStorageSync('uid') || gd.uid || '', + }); + }, + + onAvatarError() { + onAvatarImageError(this, 'avatarUrl', app); + }, + + resolveOssUrl(path) { + if (!path) return ''; + if (path.startsWith('http')) return path; + return (app.globalData.ossImageUrl || '') + path; + }, + + buildMemberBenefits(huiyuan = {}) { + const name = huiyuan.mingzi || '会员'; + const formalPrice = huiyuan.jiage || '0'; + const trialPrice = huiyuan.trial_price || '0'; + const fb = (name) => `${ICON_FOLDER}/${name}.png`; + const meta = [ + { + key: ICON_KEYS.FIGHTER_RECHARGE_BENEFIT_1, + title: '充值立得', + desc: `开通${name}正式版 ¥${formalPrice},享优先接单`, + }, + { + key: ICON_KEYS.FIGHTER_RECHARGE_BENEFIT_2, + title: '体验尝鲜', + desc: huiyuan.can_buy_trial + ? `体验版 ¥${trialPrice}/${huiyuan.trial_days || 0}天,终身1次` + : '正式会员支持余额抵扣', + }, + { + key: ICON_KEYS.FIGHTER_RECHARGE_BENEFIT_3, + title: '专属特权', + desc: '解锁平台单、优质接单等待遇', + }, + ]; + return meta.map((item) => ({ + title: item.title, + desc: item.desc, + iconUrl: this._icon(item.key, fb(item.key)), + })); + }, + + applyCurrentHuiyuan(huiyuan, index) { + this.setData({ + currentHuiyuanIndex: index, + currentHuiyuan: huiyuan, + detailRules: this.buildDetailRules(huiyuan), + memberBenefits: this.buildMemberBenefits(huiyuan), + memberBadge: huiyuan.mingzi || '会员', + }); + }, + + toggleAgree() { + this.setData({ agreed: !this.data.agreed }); + }, + + goToDeposit() { + wx.navigateTo({ url: '/pages/fighter-recharge/fighter-deposit' }); + }, + + onTapTrial(e) { + if (!this.data.agreed) { + wx.showToast({ title: '请先阅读并同意会员协议', icon: 'none' }); + return; + } + this.onBuyHuiyuanClick(e); + }, + + onTapFormal(e) { + if (!this.data.agreed) { + wx.showToast({ title: '请先阅读并同意会员协议', icon: 'none' }); + return; + } + this.onBuyHuiyuanClick(e); + }, + + stopPropagation() {}, + // ========== 从全局变量加载数据 ========== loadFromGlobalData() { const app = getApp(); @@ -130,37 +239,12 @@ onHide() { // ✅ 重要:保持字段对应关系 // 全局变量中是 clumber,页面中是 clubmber // 全局变量中是 jinfen,页面中是 jifen - this.setData({ - clubmber: globalData.clumber || [], - yajin: globalData.yajin || 0, - jifen: globalData.jinfen || 0 // ✅ 注意:这里从 jinfen 读取 - }); - - // 计算积分进度条 - this.calculateJifenProgress(); - }, + const rawClubmber = globalData.clumber || []; + const clubmber = rawClubmber.filter((c) => c && c.huiyuanid); - // ========== 计算积分进度条角度 ========== - calculateJifenProgress() { - const jifen = this.data.jifen || 0; - const progress = (jifen / 10) * 360; // 满分10分 this.setData({ - jifenProgress: progress - }); - }, - - // ========== 计算会员列表高度 ========== - calculateHuiyuanHeight() { - const clubmber = this.data.clubmber || []; - - const itemHeight = 100; // 每个会员标签高度 - const maxHeight = 300; // 最大高度(3个会员) - - let height = clubmber.length * itemHeight; - height = Math.min(height, maxHeight); - - this.setData({ - huiyuanListHeight: height + clubmber: clubmber, + jifen: globalData.jinfen || 0, }); }, @@ -174,17 +258,15 @@ onHide() { }); if (res.data.code === 200) { - const huiyuanList = res.data.data || []; - - // 处理数据,标记已购买的会员 - const processedList = this.processHuiyuanList(huiyuanList); - - const currentHuiyuan = processedList[0] || {}; - this.setData({ - huiyuanList: processedList, - currentHuiyuan, - detailRules: this.buildDetailRules(currentHuiyuan) - }); + const huiyuanList = this.processHuiyuanList(res.data.data || []); + let index = 0; + const targetId = this.data.jumpParams.huiyuanId; + if (targetId) { + const found = huiyuanList.findIndex((h) => String(h.id) === String(targetId)); + if (found >= 0) index = found; + } + this.applyCurrentHuiyuan(huiyuanList[index] || {}, index); + this.setData({ huiyuanList }); } else { wx.showToast({ title: res.data.message || '获取会员列表失败', @@ -211,7 +293,9 @@ onHide() { return { ...item, isBought: !!boughtItem, - buyInfo: boughtItem || null + buyInfo: boughtItem || null, + cardImageUrl: this.resolveOssUrl(item.card_image), + cardBgUrl: this.resolveOssUrl(item.card_bg), }; }); }, @@ -220,43 +304,27 @@ onHide() { onSwiperChange(e) { const current = e.detail.current; const huiyuanList = this.data.huiyuanList || []; - const currentHuiyuan = huiyuanList[current] || {}; - - this.setData({ - currentHuiyuanIndex: current, - currentHuiyuan, - detailRules: this.buildDetailRules(currentHuiyuan) - }); + this.applyCurrentHuiyuan(huiyuanList[current] || {}, current); }, - // ========== 手动选择会员 ========== selectHuiyuan(e) { const index = e.currentTarget.dataset.index; const huiyuanList = this.data.huiyuanList || []; - - const currentHuiyuan = huiyuanList[index] || {}; - this.setData({ - currentHuiyuanIndex: index, - currentHuiyuan, - detailRules: this.buildDetailRules(currentHuiyuan) - }); + this.applyCurrentHuiyuan(huiyuanList[index] || {}, index); }, buildDetailRules(huiyuan = {}) { const formalDays = huiyuan.formal_days || 30; const rules = [ - `正式会员有效期为${formalDays}天,从购买当天开始计算`, - '会员期间享受优先接单特权', - '支持随时续费,未过期续费天数叠加,已过期从今天重新计算' + `有效期${formalDays}天`, + '可接多种订单类型', + '支持续费,天数叠加', + '正式会员支持余额抵扣', ]; if (huiyuan.can_buy_trial) { - rules.push(`体验会员终身仅可购买1次(${huiyuan.trial_days || 0}天),体验期仅限制佣金提现`); + rules[3] = `体验版¥${huiyuan.trial_price}/${huiyuan.trial_days}天`; } - if (huiyuan.trial_enabled && !huiyuan.can_buy_trial) { - rules.push('您已使用过该会员的体验资格'); - } - rules.push('体验会员仅支持微信支付;正式会员可使用佣金、分红或押金抵扣'); - return rules; + return rules.slice(0, 4); }, // ========== 格式化到期时间 ========== @@ -310,109 +378,10 @@ onHide() { // ========== 处理自动滚动 ========== handleAutoScroll() { const { jumpParams } = this.data; - - if (!jumpParams || !jumpParams.needScroll) return; - + if (!jumpParams || jumpParams.scrollTo !== 'jifen') return; setTimeout(() => { - let scrollTop = 0; - - if (jumpParams.scrollTo === 'bottom') { - scrollTop = 2000; - } else if (jumpParams.scrollTo === 'member') { - return; - } - - if (scrollTop > 0) { - wx.pageScrollTo({ - scrollTop: scrollTop, - duration: 300 - }); - } - }, 800); - }, - - // ========== 押金充值相关 ========== - showYajinModal() { - this.setData({ - showYajinModal: true, - yajinAmount: '100' - }); - }, - - hideYajinModal() { - this.setData({ - showYajinModal: false - }); - }, - - onYajinInput(e) { - let value = e.detail.value; - value = value.replace(/[^\d]/g, ''); - - if (value) { - const num = parseInt(value); - if (num < 1) value = '1'; - if (num > 10000) value = '10000'; - } - - this.setData({ - yajinAmount: value - }); - }, - - setYajinAmount(e) { - const amount = e.currentTarget.dataset.amount; - this.setData({ - yajinAmount: amount - }); - }, - - async handleYajinPay() { - const amount = this.data.yajinAmount; - - if (!amount || amount < 1 || amount > 10000) { - wx.showToast({ - title: '请输入1-10000元的金额', - icon: 'none' - }); - return; - } - - this.showLoading('发起支付中...'); - - try { - const res = await request({ - url: '/shangpin/yajingoumai', - method: 'POST', - data: { - jine: parseInt(amount) - } - }); - - if (res.data.code === 200) { - const payParams = res.data.payParams; - const dingdanid = res.data.dingdanid; - - this.setData({ - currentDingdanid: dingdanid - }); - - await this.wechatPay(payParams, 'yajin'); - } else { - wx.showToast({ - title: res.data.message || '支付发起失败', - icon: 'none' - }); - } - } catch (error) { - console.error('押金支付失败:', error); - wx.showToast({ - title: '支付失败,请重试', - icon: 'none' - }); - } finally { - this.hideLoading(); - } + wx.pageScrollTo({ selector: '#jifen-section', duration: 300 }); + }, 600); }, // ========== 积分充值相关 ========== @@ -455,19 +424,8 @@ onHide() { return; } - if (isTrial) { - const { currentHuiyuan, huiyuanList } = this.data; - const target = (currentHuiyuan && currentHuiyuan.id === huiyuanId) - ? currentHuiyuan - : (huiyuanList || []).find(item => item.id === huiyuanId); - if (target && !target.can_buy_trial) { - wx.showToast({ - title: '您已使用过该会员的体验资格', - icon: 'none' - }); - return; - } - } + const confirmed = await this.confirmWechatPurchase('huiyuan'); + if (!confirmed) return; this.showLoading('发起会员支付中...'); @@ -524,6 +482,9 @@ onHide() { return; } + const confirmed = await this.confirmWechatPurchase('jifen'); + if (!confirmed) return; + this.showLoading('发起积分支付中...'); try { @@ -834,12 +795,33 @@ onHide() { return new Promise(resolve => setTimeout(resolve, ms)); }, + /** 微信支付下单前确认(仅微信购买会员/押金/积分) */ + confirmWechatPurchase(kind) { + const labelMap = { + huiyuan: '会员', + yajin: '押金', + jifen: '积分', + }; + const label = labelMap[kind] || '服务'; + return new Promise((resolve) => { + wx.showModal({ + title: '确认微信支付', + content: `您即将通过微信支付购买${label}。请点击「确认购买」后才会发起支付;购买成功后不可随意退款,请确认无误后再继续。`, + confirmText: '确认购买', + cancelText: '再想想', + success: (res) => resolve(!!res.confirm), + fail: () => resolve(false), + }); + }); + }, + // ========== 新增功能(余额抵扣等) ========== onBuyHuiyuanClick(e) { const huiyuanId = e.currentTarget.dataset.id; const isTrial = e.currentTarget.dataset.trial === true || e.currentTarget.dataset.trial === 'true'; if (isTrial) { + this.setData({ currentBuyIsTrial: true }); this.handleHuiyuanBuy(e); return; } @@ -848,25 +830,7 @@ onHide() { currentBuyType: 1, currentHuiyuanId: huiyuanId, currentBuyIsTrial: false, - showPayMethodModal: true - }); - }, - - onYajinClick() { - this.showYajinModal(); - }, - - onYajinConfirm() { - const amount = this.data.yajinAmount; - if (!amount || amount < 1 || amount > 10000) { - wx.showToast({ title: '请输入1-10000元的金额', icon: 'none' }); - return; - } - this.hideYajinModal(); - this.setData({ - currentBuyType: 2, - currentYajinAmount: amount, - showPayMethodModal: true + showPayMethodModal: true, }); }, @@ -877,29 +841,20 @@ onHide() { // 🔥 修改点2:选择支付方式时再次校验积分状态(防止异步过程中积分变化) onPayMethodSelect(e) { const method = e.currentTarget.dataset.method; - const { currentBuyType, currentHuiyuanId, currentYajinAmount, jifen } = this.data; + const { currentBuyType, currentHuiyuanId, jifen } = this.data; this.hidePayMethodModal(); - // 如果是积分购买,且积分已满,直接拒绝 if (currentBuyType === 3 && jifen >= 10) { - wx.showToast({ - title: '积分已满,无需补充', - icon: 'none' - }); + wx.showToast({ title: '积分已满,无需补充', icon: 'none' }); return; } if (method === 'wx') { if (currentBuyType === 1) { this.handleHuiyuanBuy({ - currentTarget: { dataset: { id: currentHuiyuanId, trial: 'false' } } - }); - } else if (currentBuyType === 2) { - this.setData({ yajinAmount: currentYajinAmount }, () => { - this.handleYajinPay(); + currentTarget: { dataset: { id: currentHuiyuanId, trial: this.data.currentBuyIsTrial ? 'true' : 'false' } }, }); } else if (currentBuyType === 3) { - // ✅ 调用已修正的积分支付函数 this.handleJifenBuy({ currentTarget: { dataset: { disabled: 'false' } } }); } } else if (method === 'balance') { @@ -909,7 +864,7 @@ onHide() { // 🔥 修改点3:请求余额抵扣选项前校验积分状态 async fetchBalanceOptions() { - const { currentBuyType, currentHuiyuanId, currentYajinAmount, currentBuyIsTrial, jifen } = this.data; + const { currentBuyType, currentHuiyuanId, currentBuyIsTrial, jifen } = this.data; if (currentBuyType === 1 && currentBuyIsTrial) { wx.showToast({ title: '体验会员仅支持微信支付', icon: 'none' }); @@ -934,9 +889,8 @@ onHide() { data: { leixing: currentBuyType, huiyuan_id: currentBuyType === 1 ? currentHuiyuanId : undefined, - is_trial: currentBuyType === 1 ? false : undefined, - yajin_jine: currentBuyType === 2 ? parseInt(currentYajinAmount) : undefined, - } + is_trial: currentBuyType === 1 ? currentBuyIsTrial : undefined, + }, }); if (res.data.code === 200) { const options = res.data.data || []; @@ -977,7 +931,7 @@ onHide() { }, async confirmBalancePay() { - const { selectedBalanceId, currentBuyType, currentHuiyuanId, currentYajinAmount, currentBuyIsTrial } = this.data; + const { selectedBalanceId, currentBuyType, currentHuiyuanId, currentBuyIsTrial } = this.data; if (!selectedBalanceId) return; this.hideConfirmModal(); @@ -992,14 +946,13 @@ onHide() { shenfen_id: selectedBalanceId, huiyuan_id: currentBuyType === 1 ? currentHuiyuanId : undefined, is_trial: currentBuyType === 1 ? currentBuyIsTrial : undefined, - yajin_jine: currentBuyType === 2 ? parseInt(currentYajinAmount) : undefined, - } + }, }); if (res.data.code === 200) { const responseData = res.data.data || {}; await this.updateAfterPayment( - currentBuyType === 1 ? 'huiyuan' : (currentBuyType === 2 ? 'yajin' : 'jifen'), + currentBuyType === 1 ? 'huiyuan' : 'jifen', currentBuyType === 1 ? currentHuiyuanId : null, responseData ); diff --git a/pages/fighter-recharge/fighter-recharge.json b/pages/fighter-recharge/fighter-recharge.json index 90e50da..95411ea 100644 --- a/pages/fighter-recharge/fighter-recharge.json +++ b/pages/fighter-recharge/fighter-recharge.json @@ -1,12 +1,10 @@ { - "usingComponents": { - "global-notification": "/components/global-notification/global-notification" - }, - "navigationBarTitleText": "充值中心", - "navigationBarBackgroundColor": "#0a0e2a", - "navigationBarTextStyle": "white", - "backgroundColor": "#0a0e2a", - "backgroundTextStyle": "light", - "enablePullDownRefresh": false, - "onReachBottomDistance": 50 - } \ No newline at end of file + "usingComponents": { + "global-notification": "/components/global-notification/global-notification", + "popup-notice": "/components/popup-notice/popup-notice" + }, + "navigationBarTitleText": "VIP", + "navigationBarBackgroundColor": "#FFF9F0", + "navigationBarTextStyle": "black", + "backgroundColor": "#FFF9F0" +} diff --git a/pages/fighter-recharge/fighter-recharge.wxml b/pages/fighter-recharge/fighter-recharge.wxml index d3a4682..572cd58 100644 --- a/pages/fighter-recharge/fighter-recharge.wxml +++ b/pages/fighter-recharge/fighter-recharge.wxml @@ -1,528 +1,210 @@ - - - + + + + - - - - - - - - - VIP会员充值 - - + + + + - 选择你的会员 + {{memberBadge}} - - - - - - - - - - - - - - - - - - - 已拥有 + + + + + + + {{item.mingzi}} + {{item.isBought ? '已激活' : '未激活'}} + + + ¥ + {{item.jiage}} + + 有效期 {{item.formal_days || 30}} 天 - - - {{item.mingzi}} - - - - - ¥ - {{item.jiage}} - /{{item.formal_days || 30}}天 - - - - - - {{item}} + + + + + + VIP - - - - - - + - - - - - - {{currentHuiyuan.mingzi}} - 会员详情 + + + {{currentHuiyuan.mingzi || '会员'}}规则 + + + + + {{index + 1}} + {{item}} - - - - - {{currentHuiyuan.jieshao || '加载中...'}} - - - - {{index + 1}} - {{item}} - - - + + + + + {{currentHuiyuan.mingzi || '会员'}}专属权益 + + + + + + + {{item.title}} + {{item.desc}} - - - - - - - 价格: - ¥{{currentHuiyuan.jiage}} - /{{currentHuiyuan.formal_days || 30}}天 - - - 已激活,{{currentHuiyuan.buyInfo.daoqi}}后到期 - - 正式会员支持微信或余额抵扣 - - 体验版仅微信支付 ¥{{currentHuiyuan.trial_price}}/{{currentHuiyuan.trial_days}}天,终身1次 - + + + + + 平台优势 + + + + + - - - - - - - {{currentHuiyuan.isBought ? '正式续费' : '购买正式版'}} - - - - - - - - - - 购买体验版 - + {{item.title}} + {{item.desc}} + + + + + + + 积分充值 + + + + 当前积分 + {{jifen}} + 积分不足将影响接单与提现 + 积分充足 + + {{jifen >= 10 ? '已满' : '补充积分'}} + + + + + + + + 我的会员 + + + {{item.huiyuanming}} + 到期 {{formatDaoqiTime(item.daoqi)}} - - - - - - - - - 我的会员 - 👑 - - {{clubmber.length}} + + + 缴纳履约金,快人一步 + 去缴纳 > + + + {{agreed ? '✓' : ''}} + 已阅读并同意 + 《{{currentHuiyuan.mingzi || '会员'}}协议》 + + + + 体验会员 (¥{{currentHuiyuan.trial_price}}) - - - - - - {{item.huiyuanming}} - 到期: {{formatDaoqiTime(item.daoqi)}} - - - - - - 暂无会员 - - - - - 共{{clubmber.length}}个会员 - + + {{currentHuiyuan.isBought ? '续费正式会员' : '升级正式会员'}} (¥{{currentHuiyuan.jiage}}) + - - - - - - 我的押金 - 💰 - - - - - - - ¥ - {{yajin}} - - 可用余额 - - - - - - - - 立即充值 - - - - - - - - - - - - - 我的积分 - - - - - - - - - - - {{jifen}} - 积分 - - - - - - - - 积分不足,影响接单,提现权限 - 积分充足,无需补充 - - - - - - - {{jifen >= 10 ? '积分已满' : '立即补充'}} - - - - - - - - - - - - - - - - - - - - 押金充值 - 充值保证金 - - - - - - - - - - 充值金额 (1-10000元) - - - - ¥ - - - - - 支持1-10000元整数充值 - - - - 快速选择 - - - 100元 - - - 500元 - - - 1000元 - - - 5000元 - - - - - - - - 取消 - - - - 确认支付 - - - - - - - - - - - - - - - - - 选择支付方式 - - - - - - - - - - 微信支付 - 安全快捷 - - - - 余额抵扣 - 使用佣金或分红 - - - - - - - - - - - - - - - - - 选择抵扣身份 - - - - - - - - - - {{item.jieshao}} - 需¥{{item.xuyao}} - - - - - - - - - - - - - - - - - - - 确认抵扣 - - - - - - - - - 抵扣身份: - {{selectedBalanceInfo && selectedBalanceInfo.jieshao}} - - - 所需金额: - ¥{{selectedBalanceInfo && selectedBalanceInfo.xuyao}} - - - 实际扣除: - ¥{{selectedBalanceInfo && selectedBalanceInfo.xuyao}} - - 确认后将从您的余额中扣除 - - - - - - 取消 - - - - 确认支付 - - - - - - - - - - - - - - - - - 会员详情 - - - - - - - - - 会员名称: - {{currentMemberDetail && currentMemberDetail.huiyuanming}} - - - 到期时间: - {{currentMemberDetail && currentMemberDetail.daoqi}} - - - 剩余天数: - {{formatRemainTime(currentMemberDetail && currentMemberDetail.daoqi)}} - - - - - - - - - - - - - - - - - - - - - - {{loadingText}} - 正在连接支付系统... - - - - - - - - - 星阙网络 © 科技 - - + + + + 选择支付方式 + + 微信支付 + 安全快捷 + + + 余额抵扣 + 使用佣金或分红 + + + - - \ No newline at end of file + + + 选择抵扣身份 + + {{item.jieshao}} + 需¥{{item.xuyao}} + + + + + + + 确认抵扣 + 身份{{selectedBalanceInfo.jieshao}} + 金额¥{{selectedBalanceInfo.xuyao}} + + 取消 + 确认支付 + + + + + + + 会员详情 + 名称{{currentMemberDetail.huiyuanming}} + 到期{{currentMemberDetail.daoqi}} + 剩余{{formatRemainTime(currentMemberDetail.daoqi)}} + + + + + + + {{loadingText}} + + + + + diff --git a/pages/fighter-recharge/fighter-recharge.wxss b/pages/fighter-recharge/fighter-recharge.wxss index a576c31..3010c70 100644 --- a/pages/fighter-recharge/fighter-recharge.wxss +++ b/pages/fighter-recharge/fighter-recharge.wxss @@ -1,1730 +1,555 @@ -/* pages/dashou-chongzhi/index.wxss - 完整保留原有样式,仅删除导航栏相关,调整间距和字体 */ +@import '../../styles/xym-recharge-shared.wxss'; -/* ==================== 基础样式 ==================== */ -.page-container { - min-height: 100vh; - background: linear-gradient(135deg, #0a0e2a 0%, #1a1f4e 30%, #0a0e2a 100%); - position: relative; - overflow-x: hidden; - padding-bottom: 40rpx; - } - - .page-container::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: - radial-gradient(circle at 20% 30%, rgba(64, 156, 255, 0.1) 0%, transparent 50%), - radial-gradient(circle at 80% 70%, rgba(138, 43, 226, 0.08) 0%, transparent 50%), - linear-gradient(45deg, transparent 49%, rgba(64, 156, 255, 0.05) 50%, transparent 51%), - linear-gradient(-45deg, transparent 49%, rgba(64, 156, 255, 0.05) 50%, transparent 51%); - background-size: 100% 100%, 100% 100%, 60rpx 60rpx, 60rpx 60rpx; - z-index: 0; - pointer-events: none; - } - - /* ❌ 删除 .custom-navbar 及其所有子样式 */ - - /* 顶部三区域网格 */ - .tech-grid { - display: flex; - justify-content: space-between; - padding: 20rpx 30rpx; - gap: 20rpx; - position: relative; - z-index: 1; - } - - /* 科技卡片 */ - .tech-card { - flex: 1; - background: rgba(16, 20, 48, 0.8); - border-radius: 20rpx; - padding: 20rpx 25rpx; - position: relative; - overflow: hidden; - border: 1px solid rgba(64, 156, 255, 0.2); - box-shadow: - 0 10rpx 30rpx rgba(0, 10, 30, 0.5), - inset 0 1px 0 rgba(255, 255, 255, 0.1); - min-height: 260rpx; - display: flex; - flex-direction: column; - } - - .tech-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 20rpx; - padding: 2rpx; - background: linear-gradient(135deg, - rgba(64, 156, 255, 0.4) 0%, - rgba(64, 156, 255, 0.1) 50%, - rgba(64, 156, 255, 0.4) 100%); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - pointer-events: none; - } - - /* 卡片头部 */ - .card-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 20rpx; - padding: 0 5rpx; - } - - .card-title { - display: flex; - align-items: center; - gap: 12rpx; - flex: 1; - white-space: nowrap; - } - - .title-icon { - width: 32rpx; - height: 32rpx; - filter: drop-shadow(0 0 8rpx rgba(64, 156, 255, 0.6)); - } - - .title-text { - font-size: 26rpx; - font-weight: bold; - color: #e0f0ff; - text-shadow: 0 0 10rpx rgba(64, 156, 255, 0.5); - white-space: nowrap; - } - - /* ===== 会员区域(优化后) ===== */ - .huiyuan-scroll { - flex: 1; - margin-bottom: 15rpx; - overflow-y: scroll; - } - - .huiyuan-item { - margin-bottom: 12rpx; - } - - .huiyuan-tag { - background: linear-gradient(90deg, - rgba(64, 156, 255, 0.15), - rgba(64, 156, 255, 0.08)); - border: 1px solid rgba(64, 156, 255, 0.3); - border-radius: 16rpx; - padding: 15rpx 20rpx; - position: relative; - overflow: hidden; - min-height: 70rpx; - display: flex; - flex-direction: column; - justify-content: center; - } - - .tag-triangle { - position: absolute; - top: 10rpx; - left: 10rpx; - width: 0; - height: 0; - border-top: 10rpx solid #409cff; - border-right: 10rpx solid transparent; - } - - .tag-name { - display: block; - font-size: 24rpx; - color: #fff; - font-weight: bold; - margin-bottom: 5rpx; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .tag-time { - display: block; - font-size: 20rpx; - color: #c4b5fd; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .tag-glow { - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, - transparent, - rgba(255, 255, 255, 0.15), - transparent); - animation: techGlide 3s infinite; - } - - .empty-huiyuan { - height: 100rpx; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - } - - .empty-icon { - width: 50rpx; - height: 50rpx; - opacity: 0.5; - margin-bottom: 8rpx; - } - - .empty-text { - color: #6688cc; - font-size: 22rpx; - } - - .card-footer { - display: flex; - align-items: center; - justify-content: space-between; - margin-top: auto; - padding-top: 10rpx; - } - - .footer-text { - font-size: 20rpx; - color: #a0c8ff; - } - - .footer-line { - flex: 1; - height: 1px; - background: linear-gradient(90deg, - rgba(64, 156, 255, 0.3), - transparent); - margin-left: 15rpx; - } - - /* 押金区域 */ - .yajin-display { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - margin: 15rpx 0; - } - - .amount-container { - display: flex; - align-items: baseline; - margin-bottom: 8rpx; - } - - .amount-symbol { - font-size: 28rpx; - color: #c4b5fd; - margin-right: 6rpx; - } - - .amount-number { - font-size: 56rpx; - font-weight: bold; - background: linear-gradient(90deg, #40a9ff, #36cfc9); - -webkit-background-clip: text; - background-clip: text; - color: transparent; - text-shadow: 0 0 20rpx rgba(64, 169, 255, 0.4); - } - - .amount-unit { - font-size: 22rpx; - color: #a0c8ff; - } - - .tech-line { - height: 2rpx; - background: linear-gradient(90deg, - transparent, - rgba(64, 156, 255, 0.5), - transparent); - margin: 15rpx 0; - } - - /* 积分区域 */ - .jifen-card .card-title { - flex-direction: row; - } - - .score-badge { - background: rgba(64, 156, 255, 0.2); - border: 1px solid rgba(64, 156, 255, 0.5); - padding: 5rpx 14rpx; - border-radius: 20rpx; - font-size: 22rpx; - color: #409cff; - font-weight: bold; - } - - .jifen-display { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - margin: 10rpx 0; - } - - .circle-progress { - width: 140rpx; - height: 140rpx; - position: relative; - margin-bottom: 20rpx; - } - - .circle-bg { - width: 100%; - height: 100%; - border: 6rpx solid rgba(255, 255, 255, 0.05); - border-radius: 50%; - box-sizing: border-box; - } - - .circle-mask { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 6rpx solid transparent; - border-top-color: #409cff; - border-right-color: #409cff; - border-radius: 50%; - box-sizing: border-box; - transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); - } - - .circle-fill { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 6rpx solid transparent; - border-top-color: #36cfc9; - border-right-color: #36cfc9; - border-radius: 50%; - box-sizing: border-box; - transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); - filter: drop-shadow(0 0 15rpx rgba(54, 207, 201, 0.6)); - } - - .circle-center { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - text-align: center; - } - - .jifen-value { - display: block; - font-size: 42rpx; - font-weight: bold; - color: #fff; - text-shadow: 0 0 10rpx rgba(64, 156, 255, 0.8); - } - - .jifen-label { - display: block; - font-size: 20rpx; - color: #a0c8ff; - margin-top: 5rpx; - } - - .circle-dots { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - - .circle-dot { - position: absolute; - top: 0; - left: 50%; - width: 5rpx; - height: 5rpx; - background: rgba(64, 156, 255, 0.6); - border-radius: 50%; - transform-origin: 0 70rpx; - } - - .jifen-status { - text-align: center; - } - - .status-text { - font-size: 22rpx; - color: #a855f7; - } - - .status-text.full { - color: #36cfc9; - } - - /* 科技按钮 */ - .action-container { - margin-top: auto; - } - - .tech-btn { - height: 70rpx; - border-radius: 35rpx; - display: flex; - align-items: center; - justify-content: center; - position: relative; - overflow: hidden; - font-size: 26rpx; - font-weight: bold; - color: #fff; - transition: all 0.3s ease; - line-height: 1; - } - - .tech-btn:active { - transform: scale(0.98); - } - - .btn-bg { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 35rpx; - } - - .yajin-btn .btn-bg { - background: linear-gradient(135deg, #1e4b8f, #409cff); - } - - .jifen-btn .btn-bg { - background: linear-gradient(135deg, #0f2c5c, #36cfc9); - } - - .jifen-btn[data-disabled="true"] .btn-bg { - background: linear-gradient(135deg, #333, #666); - opacity: 0.7; - } - - .btn-text { - position: relative; - z-index: 1; - display: flex; - align-items: center; - gap: 8rpx; - text-align: center; - width: 100%; - justify-content: center; - height: 100%; - } - - .btn-arrow { - font-size: 22rpx; - margin-left: 6rpx; - color: #fff; - } - - .btn-energy { - position: absolute; - top: 50%; - right: 25rpx; - transform: translateY(-50%); - display: flex; - gap: 4rpx; - } - - .btn-energy .energy-dot { - width: 6rpx; - height: 6rpx; - background: #c4b5fd; - border-radius: 50%; - animation: energyPulse 1s infinite; - } - - .btn-energy .energy-dot:nth-child(2) { animation-delay: 0.2s; } - .btn-energy .energy-dot:nth-child(3) { animation-delay: 0.4s; } - - .btn-glow { - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, - transparent, - rgba(255, 255, 255, 0.3), - transparent); - animation: techShine 2s infinite; - } - - /* VIP会员区域 */ - .vip-section { - margin: 30rpx 30rpx; /* 🔥 移除导航栏后,无需额外 margin-top */ - background: rgba(16, 20, 48, 0.8); - border-radius: 30rpx; - padding: 30rpx; - border: 1px solid rgba(64, 156, 255, 0.2); - box-shadow: - 0 20rpx 40rpx rgba(0, 10, 30, 0.6), - inset 0 1px 0 rgba(255, 255, 255, 0.1); - position: relative; - z-index: 1; - } - - .section-header { - text-align: center; - margin-bottom: 40rpx; - } - - .header-decoration { - display: flex; - align-items: center; - justify-content: center; - gap: 15rpx; - margin-bottom: 12rpx; - } - - .dec-line { - width: 50rpx; - height: 2rpx; - background: linear-gradient(90deg, - rgba(64, 156, 255, 0.5), - rgba(64, 156, 255, 0.1)); - } - - .dec-diamond { - width: 14rpx; - height: 14rpx; - background: #409cff; - transform: rotate(45deg); - box-shadow: 0 0 10rpx rgba(64, 156, 255, 0.6); - } - - .dec-text { - font-size: 32rpx; - font-weight: bold; - background: linear-gradient(90deg, #40a9ff, #36cfc9); - -webkit-background-clip: text; - background-clip: text; - color: transparent; - text-shadow: 0 0 20rpx rgba(64, 169, 255, 0.4); - } - - .section-subtitle { - font-size: 22rpx; - color: #a0c8ff; - letter-spacing: 4rpx; - } - - /* 会员卡片滑动区域 */ - .vip-swiper { - height: 350rpx; - margin-bottom: 40rpx; - } - - .vip-card { - height: 100%; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); - position: relative; - } - - .vip-card.active { - transform: scale(1.08); - z-index: 10; - } - - .vip-card:not(.active) { - opacity: 0.6; - transform: scale(0.85); - } - - /* 机甲风格卡片边框 */ - .card-frame { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - } - - .frame-corner { - position: absolute; - width: 40rpx; - height: 40rpx; - } - - .frame-corner.tl { - top: 0; - left: 0; - border-top: 4rpx solid #409cff; - border-left: 4rpx solid #409cff; - border-top-left-radius: 20rpx; - } - - .frame-corner.tr { - top: 0; - right: 0; - border-top: 4rpx solid #409cff; - border-right: 4rpx solid #409cff; - border-top-right-radius: 20rpx; - } - - .frame-corner.bl { - bottom: 0; - left: 0; - border-bottom: 4rpx solid #409cff; - border-left: 4rpx solid #409cff; - border-bottom-left-radius: 20rpx; - } - - .frame-corner.br { - bottom: 0; - right: 0; - border-bottom: 4rpx solid #409cff; - border-right: 4rpx solid #409cff; - border-bottom-right-radius: 20rpx; - } - - .frame-line { - position: absolute; - background: rgba(64, 156, 255, 0.3); - } - - .frame-line.top { - top: 0; - left: 40rpx; - right: 40rpx; - height: 2rpx; - } - - .frame-line.right { - right: 0; - top: 40rpx; - bottom: 40rpx; - width: 2rpx; - } - - .frame-line.bottom { - bottom: 0; - left: 40rpx; - right: 40rpx; - height: 2rpx; - } - - .frame-line.left { - left: 0; - top: 40rpx; - bottom: 40rpx; - width: 2rpx; - } - - .vip-card.active .frame-corner { - border-color: #36cfc9; - } - - .vip-card.active .frame-line { - background: rgba(54, 207, 201, 0.5); - } - - .card-content { - position: relative; - z-index: 1; - text-align: center; - padding: 30rpx 25rpx; - width: 100%; - } - - .vip-badge { - position: absolute; - top: 25rpx; - right: 25rpx; - background: linear-gradient(135deg, #a855f7, #7c3aed); - padding: 5rpx 14rpx; - border-radius: 20rpx; - font-size: 20rpx; - color: #fff; - font-weight: bold; - box-shadow: 0 0 15rpx rgba(255, 107, 157, 0.4); - z-index: 20; - } - - .vip-title { - margin-bottom: 25rpx; - } - - .title-text { - display: block; - font-size: 32rpx; - font-weight: bold; - color: #fff; - margin-bottom: 8rpx; - text-shadow: 0 0 10rpx rgba(64, 156, 255, 0.8); - } - - .title-underline { - width: 50rpx; - height: 4rpx; - background: linear-gradient(90deg, #409cff, #36cfc9); - margin: 0 auto; - border-radius: 2rpx; - } - - .vip-price { - margin-bottom: 30rpx; - } - - .price-symbol { - font-size: 26rpx; - color: #c4b5fd; - margin-right: 5rpx; - } - - .price-number { - font-size: 50rpx; - font-weight: bold; - color: #c4b5fd; - text-shadow: 0 0 15rpx rgba(196, 181, 253, 0.6); - } - - .price-unit { - font-size: 22rpx; - color: #a0c8ff; - margin-left: 6rpx; - } - - .vip-features { - margin-top: 25rpx; - } - - .feature-item { - display: flex; - align-items: center; - justify-content: center; - gap: 8rpx; - margin-bottom: 12rpx; - } - - .feature-icon { - width: 20rpx; - height: 20rpx; - } - - .feature-text { - font-size: 22rpx; - color: #e0f0ff; - } - - .card-indicator { - position: absolute; - bottom: -35rpx; - left: 50%; - transform: translateX(-50%); - } - - .indicator-triangle { - width: 0; - height: 0; - border-left: 15rpx solid transparent; - border-right: 15rpx solid transparent; - border-top: 25rpx solid #409cff; - filter: drop-shadow(0 5rpx 5rpx rgba(0, 0, 0, 0.3)); - } - - .indicator-glow { - position: absolute; - top: -25rpx; - left: 50%; - transform: translateX(-50%); - width: 30rpx; - height: 30rpx; - background: radial-gradient(circle, rgba(64, 156, 255, 0.6), transparent 70%); - border-radius: 50%; - animation: pulseGlow 2s infinite; - } - - /* 会员详细介绍 */ - .vip-detail { - background: rgba(10, 14, 42, 0.9); - border-radius: 20rpx; - padding: 25rpx; - border: 1px solid rgba(64, 156, 255, 0.3); - } - - .detail-header { - display: flex; - align-items: center; - gap: 15rpx; - margin-bottom: 25rpx; - } - - .detail-icon { - width: 36rpx; - height: 36rpx; - filter: drop-shadow(0 0 8rpx rgba(64, 156, 255, 0.6)); - } - - .detail-title .title-text { - font-size: 28rpx; - color: #e0f0ff; - text-shadow: 0 0 10rpx rgba(64, 156, 255, 0.5); - } - - .detail-tech { - flex: 1; - height: 2rpx; - background: linear-gradient(90deg, rgba(64, 156, 255, 0.5), transparent); - } - - .detail-content { - margin-bottom: 30rpx; - } - - .detail-scroll { - max-height: 180rpx; - } - - .detail-text { - font-size: 24rpx; - color: #b8d4ff; - line-height: 1.5; - margin-bottom: 25rpx; - } - - .detail-rules { - margin-top: 25rpx; - } - - .rule-item { - display: flex; - align-items: flex-start; - gap: 15rpx; - margin-bottom: 15rpx; - } - - .rule-number { - width: 32rpx; - height: 32rpx; - background: rgba(64, 156, 255, 0.2); - border: 1px solid rgba(64, 156, 255, 0.5); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 20rpx; - color: #409cff; - flex-shrink: 0; - font-weight: bold; - } - - .rule-text { - font-size: 22rpx; - color: #a0c8ff; - line-height: 1.4; - flex: 1; - } - - /* 购买区域 */ - .buy-section { - display: flex; - flex-direction: column; - align-items: stretch; - gap: 20rpx; - padding-top: 25rpx; - border-top: 1px solid rgba(64, 156, 255, 0.2); - } +.vip-page { + min-height: 100vh; + background: #fff9f0; + padding-bottom: calc(320rpx + env(safe-area-inset-bottom)); + position: relative; +} - .buy-action { - display: flex; - flex-direction: column; - gap: 16rpx; - align-items: flex-end; - } - - .price-days { - font-size: 22rpx; - color: #a0c8ff; - } +.page-bg { + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: 0; +} - .trial-hint { - margin-top: 6rpx; - color: #a855f7; - } - - .buy-info { - display: flex; - flex-direction: column; - } - - .price-display { - display: flex; - align-items: baseline; - gap: 8rpx; - margin-bottom: 8rpx; - } - - .price-label { - font-size: 24rpx; - color: #a0c8ff; - } - - .price-value { - font-size: 32rpx; - font-weight: bold; - color: #c4b5fd; - text-shadow: 0 0 10rpx rgba(196, 181, 253, 0.4); - } - - .buy-desc { - font-size: 20rpx; - color: #6688cc; - } - - /* 🔥 新增:到期日期文字加大 */ - .expire-date { - font-size: 26rpx; - color: #c4b5fd; - font-weight: bold; - } - - /* 购买按钮 */ - .tech-buy-btn { - width: 220rpx; - height: 80rpx; - border-radius: 40rpx; - display: flex; - align-items: center; - justify-content: center; - position: relative; - overflow: hidden; - font-size: 28rpx; - font-weight: bold; - color: #fff; - transition: all 0.3s ease; - line-height: 1; - } - - .tech-buy-btn:active { - transform: scale(0.98); - } - - .buy-btn-bg { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 40rpx; - background: linear-gradient(135deg, #1e4b8f, #409cff); - } - - .tech-buy-btn.trial-buy-btn .buy-btn-bg { - background: linear-gradient(135deg, rgba(167, 139, 250, 0.35), rgba(124, 58, 237, 0.45)); - border: 1px solid rgba(167, 139, 250, 0.6); - } +.page-bg-fallback { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 360rpx; + background: linear-gradient(180deg, #ffe082 0%, #fff9f0 100%); + z-index: 0; +} - .tech-buy-btn.renew .buy-btn-bg { - background: linear-gradient(135deg, #0f2c5c, #36cfc9); - } - - .buy-btn-glow { - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, - transparent, - rgba(255, 255, 255, 0.3), - transparent); - animation: techShine 2s infinite; - } - - .buy-btn-text { - position: relative; - z-index: 1; - display: flex; - align-items: center; - gap: 10rpx; - text-align: center; - width: 100%; - justify-content: center; - height: 100%; - } - - .buy-btn-energy { - position: absolute; - top: 50%; - right: 25rpx; - transform: translateY(-50%); - display: flex; - flex-direction: column; - gap: 3rpx; - } - - .buy-btn-energy .energy-dot { - width: 5rpx; - height: 5rpx; - background: #c4b5fd; - border-radius: 50%; - animation: energyPulse 1s infinite; - } - - .buy-btn-energy .energy-dot:nth-child(2) { animation-delay: 0.2s; } - .buy-btn-energy .energy-dot:nth-child(3) { animation-delay: 0.4s; } - - /* ==================== 弹窗样式(保留原有,删除 tip-content 相关) ==================== */ - .tech-modal { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 9999; - display: flex; - align-items: center; - justify-content: center; - } - - .modal-backdrop { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 5, 20, 0.85); - backdrop-filter: blur(10rpx); - } - - .modal-container { - width: 650rpx; - background: rgba(16, 20, 48, 0.95); - border-radius: 30rpx; - border: 2px solid rgba(64, 156, 255, 0.4); - box-shadow: - 0 30rpx 80rpx rgba(0, 10, 30, 0.8), - 0 0 0 1px rgba(64, 156, 255, 0.2) inset; - position: relative; - z-index: 1; - overflow: hidden; - } - - .modal-header { - padding: 40rpx 40rpx 30rpx; - text-align: center; - position: relative; - } - - .modal-corner { - position: absolute; - width: 30rpx; - height: 30rpx; - } - - .modal-corner.tl { - top: 10rpx; - left: 10rpx; - border-top: 3rpx solid #409cff; - border-left: 3rpx solid #409cff; - border-top-left-radius: 10rpx; - } - - .modal-corner.tr { - top: 10rpx; - right: 10rpx; - border-top: 3rpx solid #409cff; - border-right: 3rpx solid #409cff; - border-top-right-radius: 10rpx; - } - - .modal-corner.bl { - bottom: 10rpx; - left: 10rpx; - border-bottom: 3rpx solid #409cff; - border-left: 3rpx solid #409cff; - border-bottom-left-radius: 10rpx; - } - - .modal-corner.br { - bottom: 10rpx; - right: 10rpx; - border-bottom: 3rpx solid #409cff; - border-right: 3rpx solid #409cff; - border-bottom-left-radius: 10rpx; - } - - .modal-title { - display: block; - font-size: 36rpx; - font-weight: bold; - background: linear-gradient(90deg, #40a9ff, #36cfc9); - -webkit-background-clip: text; - background-clip: text; - color: transparent; - margin-bottom: 10rpx; - text-shadow: 0 0 20rpx rgba(64, 169, 255, 0.4); - } - - .modal-subtitle { - display: block; - font-size: 22rpx; - color: #a0c8ff; - letter-spacing: 4rpx; - } - - .modal-close { - position: absolute; - top: 30rpx; - right: 30rpx; - width: 60rpx; - height: 60rpx; - display: flex; - align-items: center; - justify-content: center; - font-size: 32rpx; - color: #6688cc; - cursor: pointer; - transition: all 0.3s ease; - } - - .modal-close:active { - transform: scale(0.9); - } - - .modal-body { - padding: 0 40rpx 40rpx; - } - - /* 输入框区域(押金弹窗) */ - .input-section { - margin-bottom: 40rpx; - } - - .input-label { - display: flex; - align-items: center; - gap: 15rpx; - margin-bottom: 25rpx; - } - - .label-icon { - width: 32rpx; - height: 32rpx; - filter: drop-shadow(0 0 8rpx rgba(64, 156, 255, 0.6)); - } - - .label-text { - font-size: 28rpx; - color: #e0f0ff; - font-weight: bold; - } - - .input-container { - display: flex; - align-items: center; - background: rgba(10, 14, 42, 0.8); - border-radius: 20rpx; - border: 1px solid rgba(64, 156, 255, 0.5); - padding: 25rpx 30rpx; - margin-bottom: 20rpx; - position: relative; - } - - .input-prefix { - font-size: 40rpx; - color: #c4b5fd; - margin-right: 20rpx; - font-weight: bold; - } - - .yajin-input { - flex: 1; - font-size: 40rpx; - color: #fff; - min-height: 60rpx; - } - - .input-placeholder { - color: #6688cc; - font-size: 28rpx; - } - - .input-underline { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 2rpx; - background: linear-gradient(90deg, #409cff, #36cfc9); - transform: scaleX(0); - transition: transform 0.3s ease; - } - - .yajin-input:focus + .input-underline { - transform: scaleX(1); - } - - .input-tip { - display: block; - font-size: 22rpx; - color: #a0c8ff; - text-align: right; - } - - .quick-section { - margin-top: 40rpx; - } - - .quick-title { - display: block; - font-size: 26rpx; - color: #e0f0ff; - margin-bottom: 25rpx; - font-weight: bold; - } - - .quick-buttons { - display: flex; - gap: 20rpx; - } - - .quick-btn { - flex: 1; - height: 80rpx; - display: flex; - align-items: center; - justify-content: center; - background: rgba(10, 14, 42, 0.8); - border: 1px solid rgba(64, 156, 255, 0.3); - border-radius: 15rpx; - font-size: 26rpx; - color: #a0c8ff; - transition: all 0.3s ease; - } - - .quick-btn.active { - background: linear-gradient(135deg, #1e4b8f, #409cff); - color: #fff; - border-color: rgba(64, 156, 255, 0.8); - box-shadow: 0 0 20rpx rgba(64, 156, 255, 0.4); - transform: translateY(-2rpx); - } - - .quick-text { - font-weight: bold; - } - - .modal-footer { - display: flex; - border-top: 1px solid rgba(64, 156, 255, 0.3); - } - - .modal-btn { - flex: 1; - height: 100rpx; - display: flex; - align-items: center; - justify-content: center; - font-size: 30rpx; - font-weight: bold; - cursor: pointer; - transition: all 0.3s ease; - position: relative; - overflow: hidden; - } - - .cancel-btn { - color: #a0c8ff; - background: rgba(10, 14, 42, 0.5); - } - - .cancel-btn:active { - background: rgba(10, 14, 42, 0.8); - } - - .modal-divider { - width: 1px; - background: rgba(64, 156, 255, 0.3); - } - - .confirm-btn { - background: linear-gradient(135deg, #1e4b8f, #409cff); - color: #fff; - } - - .confirm-btn:active { - background: linear-gradient(135deg, #0f2c5c, #3080e0); - } - - .btn-sparkle { - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, - transparent, - rgba(255, 255, 255, 0.2), - transparent); - animation: techShine 2s infinite; - } - - /* 支付方式选择弹窗(小尺寸) */ - .modal-container.small { - width: 550rpx; - } - - .pay-method-list { - padding: 20rpx 0; - } - - .pay-method-item { - display: flex; - align-items: center; - padding: 30rpx 20rpx; - background: rgba(10, 14, 42, 0.5); - border: 1px solid rgba(64, 156, 255, 0.2); - border-radius: 20rpx; - margin-bottom: 20rpx; - transition: all 0.2s; - } - - .pay-method-item:active { - background: rgba(64, 156, 255, 0.1); - border-color: rgba(64, 156, 255, 0.5); - transform: scale(0.98); - } - - .method-icon { - width: 60rpx; - height: 60rpx; - margin-right: 20rpx; - } - - .method-name { - flex: 1; - font-size: 30rpx; - color: #e0f0ff; - font-weight: bold; - } - - .method-desc { - font-size: 24rpx; - color: #a0c8ff; - margin-right: 10rpx; - } - - /* 余额抵扣身份列表 */ - .balance-list { - max-height: 600rpx; - overflow-y: auto; - } - - .balance-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 30rpx 20rpx; - background: rgba(10, 14, 42, 0.5); - border: 1px solid rgba(64, 156, 255, 0.2); - border-radius: 20rpx; - margin-bottom: 20rpx; - transition: all 0.2s; - } - - .balance-item:active { - background: rgba(64, 156, 255, 0.1); - border-color: rgba(64, 156, 255, 0.5); - transform: scale(0.98); - } - - .balance-info { - display: flex; - flex-direction: column; - } - - .balance-name { - font-size: 28rpx; - color: #e0f0ff; - font-weight: bold; - margin-bottom: 8rpx; - } - - .balance-amount { - font-size: 24rpx; - color: #c4b5fd; - } - - .balance-arrow { - font-size: 32rpx; - color: #409cff; - transform: scaleX(1.2); - } - - /* 会员详情弹窗 */ - .member-detail { - padding: 30rpx 20rpx; - } - - .detail-row { - display: flex; - align-items: baseline; - margin-bottom: 25rpx; - padding-bottom: 15rpx; - border-bottom: 1px solid rgba(64, 156, 255, 0.2); - } - - .detail-row:last-child { - border-bottom: none; - margin-bottom: 0; - padding-bottom: 0; - } - - .detail-label { - width: 150rpx; - font-size: 28rpx; - color: #a0c8ff; - } - - .detail-value { - flex: 1; - font-size: 30rpx; - color: #c4b5fd; - font-weight: bold; - } - - /* ❌ 删除 .tip-content 等相关样式 */ - - /* ==================== 加载动画 ==================== */ - .tech-loading { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 10000; - display: flex; - align-items: center; - justify-content: center; - } - - .loading-backdrop { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 5, 20, 0.9); - backdrop-filter: blur(20rpx); - } - - .loading-content { - position: relative; - z-index: 1; - text-align: center; - } - - .loading-spinner { - width: 150rpx; - height: 150rpx; - position: relative; - margin: 0 auto 40rpx; - } - - .spinner-ring { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border: 8rpx solid transparent; - border-radius: 50%; - animation: spinRing 2s linear infinite; - } - - .spinner-ring.ring-1 { - border-top-color: #409cff; - animation-delay: 0s; - } - - .spinner-ring.ring-2 { - border-right-color: #36cfc9; - animation-delay: 0.5s; - } - - .spinner-ring.ring-3 { - border-bottom-color: #c4b5fd; - animation-delay: 1s; - } - - .spinner-core { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 40rpx; - height: 40rpx; - background: radial-gradient(circle, #409cff, #1e4b8f); - border-radius: 50%; - box-shadow: 0 0 30rpx rgba(64, 156, 255, 0.8); - } - - .spinner-energy { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 60rpx; - height: 60rpx; - border: 4rpx solid rgba(64, 156, 255, 0.3); - border-radius: 50%; - animation: pulseEnergy 1.5s ease-in-out infinite; - } - - .loading-text { - display: block; - font-size: 32rpx; - color: #fff; - margin-bottom: 15rpx; - font-weight: bold; - text-shadow: 0 0 10rpx rgba(64, 156, 255, 0.8); - } - - .loading-subtext { - display: block; - font-size: 22rpx; - color: #a0c8ff; - } - - /* ==================== 底部装饰 ==================== */ - .tech-footer { - text-align: center; - margin-top: 40rpx; - padding: 0 30rpx; - } - - .footer-line { - height: 1px; - background: linear-gradient(90deg, - transparent, - rgba(64, 156, 255, 0.3), - transparent); - margin-bottom: 20rpx; - } - - .footer-text { - font-size: 20rpx; - color: #6688cc; - letter-spacing: 4rpx; - } - - /* ==================== 动画定义 ==================== */ - @keyframes techGlide { - 0% { left: -100%; } - 50% { left: 100%; } - 100% { left: 100%; } - } - - @keyframes techShine { - 0% { left: -100%; } - 20% { left: 100%; } - 100% { left: 100%; } - } - - @keyframes spinRing { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } - } - - @keyframes pulseEnergy { - 0% { - width: 60rpx; - height: 60rpx; - opacity: 0.5; - } - 50% { - width: 80rpx; - height: 80rpx; - opacity: 0.8; - } - 100% { - width: 60rpx; - height: 60rpx; - opacity: 0.5; - } - } - - @keyframes dotPulse { - 0%, 100% { opacity: 0.3; } - 50% { opacity: 1; } - } - - @keyframes energyPulse { - 0%, 100% { opacity: 0.3; transform: scale(0.8); } - 50% { opacity: 1; transform: scale(1.2); } - } - - @keyframes pulseGlow { - 0%, 100% { opacity: 0.5; } - 50% { opacity: 1; } - } - - /* ==================== 响应式调整 ==================== */ - @media screen and (max-width: 750rpx) { - .tech-grid { - flex-direction: column; - gap: 20rpx; - } - - .tech-card { - width: 100%; - } - - .vip-swiper { - height: 320rpx; - } - - .modal-container { - width: 90%; - margin: 0 5%; - } - - .quick-buttons { - flex-wrap: wrap; - } - - .quick-btn { - flex: 0 0 calc(50% - 10rpx); - } - } - - /* ==================== 按钮垂直居中最终修复(覆盖) ==================== */ - .tech-btn { - height: 70rpx; - border-radius: 35rpx; - display: flex; - align-items: center; - justify-content: center; - position: relative; - overflow: hidden; - font-size: 26rpx; - font-weight: bold; - color: #fff; - transition: all 0.3s ease; - } - - .tech-btn .btn-text { - position: relative; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - padding: 0; - margin: 0; - } - - .yajin-btn .btn-arrow { - display: none !important; - } - - .tech-buy-btn { - width: 220rpx; - height: 80rpx; - border-radius: 40rpx; - display: flex; - align-items: center; - justify-content: center; - position: relative; - overflow: hidden; - font-size: 28rpx; - font-weight: bold; - color: #fff; - transition: all 0.3s ease; - } - - .tech-buy-btn .buy-btn-text { - position: relative; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - padding: 0; - margin: 0; - } - - .tech-btn .btn-bg, - .tech-btn .btn-glow, - .tech-buy-btn .buy-btn-bg, - .tech-buy-btn .buy-btn-glow { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; - pointer-events: none; - } - - .buy-btn-energy { - position: absolute; - right: 20rpx; - top: 50%; - transform: translateY(-50%); - z-index: 2; - } - - .btn-text, .buy-btn-text { - display: flex !important; - align-items: center !important; - justify-content: center !important; - height: 100% !important; - } - - .tech-buy-btn .buy-btn-text { - display: flex !important; - align-items: center !important; - justify-content: center !important; - width: 100% !important; - height: 100% !important; - line-height: 0.4 !important; - } - - .tech-buy-btn { - display: flex !important; - align-items: center !important; - justify-content: center !important; - } +.page-body { + position: relative; + z-index: 1; + padding: 24rpx 30rpx 0; +} -@import '../../styles/dashou-xym-recharge-theme.wxss'; \ No newline at end of file +.user-row { + display: flex; + align-items: center; + margin-bottom: 24rpx; +} + +.user-avatar { + width: 88rpx; + height: 88rpx; + border-radius: 50%; + margin-right: 20rpx; + background: #eee; +} + +.user-info { + flex: 1; + min-width: 0; +} + +.user-name { + display: block; + font-size: 32rpx; + font-weight: 700; + color: #222; +} + +.user-id { + display: block; + margin-top: 6rpx; + font-size: 24rpx; + color: #999; +} + +.member-badge { + padding: 8rpx 20rpx; + border-radius: 999rpx; + background: rgba(0, 0, 0, 0.06); + font-size: 22rpx; + color: #666; + flex-shrink: 0; +} + +.vip-swiper { + height: 280rpx; + margin-bottom: 28rpx; +} + +.vip-card { + position: relative; + overflow: hidden; + height: 260rpx; + margin: 0 8rpx; + border-radius: 24rpx; + transform: scale(0.96); + transition: transform 0.3s; +} + +.vip-card--active { + transform: scale(1); +} + +.vip-card-bg, +.vip-card-bg-img { + position: absolute; + inset: 0; + border-radius: 24rpx; +} + +.vip-card-bg { + background: linear-gradient(135deg, #fae04d 0%, #ffb74d 55%, #ff8a65 100%); +} + +.vip-card-bg-img { + width: 100%; + height: 100%; +} + +.vip-card-inner { + position: relative; + z-index: 1; + display: flex; + justify-content: space-between; + align-items: stretch; + padding: 28rpx; + height: 100%; + box-sizing: border-box; +} + +.vip-card-left { + flex: 1; + min-width: 0; +} + +.vip-title-row { + display: flex; + align-items: center; + gap: 12rpx; +} + +.vip-name { + font-size: 32rpx; + font-weight: 800; + color: #222; +} + +.vip-status { + padding: 4rpx 12rpx; + border-radius: 8rpx; + background: rgba(255, 255, 255, 0.55); + font-size: 20rpx; + color: #666; +} + +.vip-price-row { + display: flex; + align-items: baseline; + margin: 16rpx 0 8rpx; +} + +.vip-currency { + font-size: 32rpx; + font-weight: 700; + margin-right: 4rpx; +} + +.vip-price { + font-size: 56rpx; + font-weight: 800; + line-height: 1; +} + +.vip-validity { + font-size: 24rpx; + color: rgba(0, 0, 0, 0.55); +} + +.vip-card-right { + width: 160rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.icon-clip { + overflow: hidden; + flex-shrink: 0; + background: rgba(255, 255, 255, 0.6); +} + +.icon-clip-img { + width: 100%; + height: 100%; + display: block; +} + +.icon-clip-vip { + width: 140rpx; + height: 140rpx; + border-radius: 36rpx; +} + +.vip-logo-fallback { + width: 120rpx; + height: 120rpx; + border-radius: 32rpx; + background: rgba(255, 255, 255, 0.85); + display: flex; + align-items: center; + justify-content: center; + border: 2rpx solid rgba(255, 255, 255, 0.9); +} + +.vip-logo-text { + font-size: 36rpx; + font-weight: 800; + color: #e6a23c; +} + +.vip-shimmer { + position: absolute; + inset: 0; + background: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.4) 50%, transparent 60%); + animation: card-shimmer 3s infinite; + pointer-events: none; + z-index: 2; +} + +.section-head { + display: flex; + align-items: center; + margin: 24rpx 0 16rpx; +} + +.section-bar { + width: 8rpx; + height: 32rpx; + border-radius: 4rpx; + background: linear-gradient(180deg, #f5c242, #e6a23c); + margin-right: 12rpx; +} + +.section-title { + font-size: 30rpx; + font-weight: 700; + color: #333; +} + +.rules-scroll { + width: 100%; + white-space: nowrap; + margin-bottom: 8rpx; +} + +.rules-row { + display: inline-flex; + gap: 16rpx; + padding-bottom: 8rpx; +} + +.rule-card { + display: inline-flex; + flex-direction: column; + width: 200rpx; + min-height: 160rpx; + padding: 20rpx 16rpx; + border-radius: 16rpx; + background: #fff; + border: 2rpx solid #f5e6c8; + box-sizing: border-box; + white-space: normal; + vertical-align: top; +} + +.rule-num { + width: 40rpx; + height: 40rpx; + border-radius: 50%; + background: linear-gradient(135deg, #fae04d, #ffb74d); + display: flex; + align-items: center; + justify-content: center; + font-size: 22rpx; + font-weight: 700; + color: #492f00; + margin-bottom: 12rpx; +} + +.rule-text { + font-size: 22rpx; + color: #666; + line-height: 1.5; +} + +.benefits-row { + display: flex; + gap: 16rpx; + margin-bottom: 8rpx; +} + +.benefit-card { + flex: 1; + background: #fff; + border-radius: 16rpx; + padding: 20rpx 12rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04); + text-align: center; +} + +.benefit-icon-img, +.icon-clip-benefit { + width: 56rpx; + height: 56rpx; + margin: 0 auto 8rpx; + border-radius: 20rpx; +} + +.icon-clip-advantage, +.advantage-icon-img { + width: 48rpx; + height: 48rpx; + margin-bottom: 8rpx; + border-radius: 18rpx; +} + +.benefit-title { + display: block; + font-size: 26rpx; + font-weight: 700; + color: #e65100; + margin-bottom: 8rpx; +} + +.benefit-desc { + display: block; + font-size: 22rpx; + color: #888; + line-height: 1.45; +} + +.advantage-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20rpx 16rpx; + margin-bottom: 16rpx; +} + +.advantage-item { + background: #fff; + border-radius: 16rpx; + padding: 20rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04); +} + + +.advantage-title { + display: block; + font-size: 26rpx; + font-weight: 700; + color: #e6a23c; + margin-bottom: 6rpx; +} + +.advantage-desc { + display: block; + font-size: 22rpx; + color: #999; + line-height: 1.4; +} + +.jifen-section { + margin-top: 8rpx; +} + +.jifen-card { + background: #fff; + border-radius: 20rpx; + padding: 28rpx; + display: flex; + align-items: center; + justify-content: space-between; + box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04); +} + +.jifen-label { + display: block; + font-size: 24rpx; + color: #999; +} + +.jifen-value { + display: block; + font-size: 48rpx; + font-weight: 800; + color: #492f00; + margin: 8rpx 0; +} + +.jifen-tip { + font-size: 22rpx; + color: #e65100; +} + +.jifen-tip--ok { + color: #67c23a; +} + +.jifen-btn { + padding: 16rpx 32rpx; + border-radius: 999rpx; + background: linear-gradient(90deg, #fae04d, #ffc0a3); + font-size: 28rpx; + font-weight: 700; + color: #492f00; +} + +.jifen-btn--disabled { + opacity: 0.5; +} + +.owned-section { + margin-top: 24rpx; +} + +.owned-item { + background: #fff; + border-radius: 16rpx; + padding: 20rpx 24rpx; + margin-bottom: 12rpx; + display: flex; + justify-content: space-between; + align-items: center; +} + +.owned-name { + font-size: 28rpx; + font-weight: 600; + color: #333; +} + +.owned-expire { + font-size: 24rpx; + color: #999; +} + +.footer-bar { + position: fixed; + left: 0; + right: 0; + bottom: 0; + padding: 16rpx 30rpx calc(16rpx + env(safe-area-inset-bottom)); + background: rgba(255, 249, 240, 0.98); + box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06); + z-index: 10; +} + +.deposit-banner { + display: flex; + justify-content: space-between; + align-items: center; + padding: 14rpx 20rpx; + margin-bottom: 12rpx; + border-radius: 12rpx; + background: linear-gradient(90deg, #fff3e0, #ffe0b2); +} + +.deposit-banner-text { + font-size: 24rpx; + color: #666; +} + +.deposit-banner-link { + font-size: 24rpx; + color: #e6a23c; + font-weight: 600; +} + +.agree-row { + display: flex; + align-items: center; + flex-wrap: wrap; + margin-bottom: 12rpx; + font-size: 24rpx; + color: #666; +} + +.agree-check { + width: 32rpx; + height: 32rpx; + border-radius: 50%; + border: 2rpx solid #ccc; + margin-right: 10rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 20rpx; + color: #fff; +} + +.agree-check--on { + background: #f5a623; + border-color: #f5a623; +} + +.agree-link { + color: #e6a23c; +} + +.buy-row { + display: flex; + gap: 16rpx; +} + +.buy-btn { + position: relative; + overflow: hidden; + height: 88rpx; + border-radius: 44rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 26rpx; + font-weight: 700; + padding: 0 16rpx; + box-sizing: border-box; +} + +.buy-btn--outline { + flex: 0 0 38%; + background: #fff; + border: 2rpx solid #f5c242; + color: #333; +} + +.buy-btn--primary { + flex: 1; + background: linear-gradient(90deg, #fae04d, #ffc0a3); + color: #492f00; + box-shadow: 0 8rpx 20rpx rgba(245, 166, 35, 0.35); +} + +.buy-btn--full { + flex: 1; + width: 100%; +} + +.buy-btn-shimmer { + position: absolute; + inset: 0; + background: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.5) 50%, transparent 60%); + animation: btn-shimmer 2.5s infinite; + pointer-events: none; +} + +@keyframes card-shimmer { + 0% { transform: translateX(-120%); } + 100% { transform: translateX(120%); } +} + +@keyframes btn-shimmer { + 0% { transform: translateX(-120%); } + 100% { transform: translateX(120%); } +} diff --git a/pages/fighter/fighter.js b/pages/fighter/fighter.js index 6f3aa39..af464d7 100644 --- a/pages/fighter/fighter.js +++ b/pages/fighter/fighter.js @@ -1,11 +1,25 @@ // pages/dashouzhongxin/dashouzhongxin.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 { fetchMyZhidingOrders, processZhidingList, filterZhidingBannerList } from '../../utils/zhiding-order.js' import { openPrivateChat, buildInviterPeerId } from '../../utils/im-user.js' import { isStaffMode, getStaffContext } from '../../utils/staff-api.js' import { ensurePhoneAuth } from '../../utils/phone-auth.js' +import { + ICON_KEYS, + resolveMiniappIcon, + refreshPindaoConfig, + getPindaoConfig, + resolvePindaoImages, + warmupPindaoConfig, + PINDAO_ICON_FALLBACK, + defaultIconRel, + ICON_FOLDER, +} from '../../utils/miniapp-icons.js' +import { getDefaultAvatarUrl } from '../../utils/avatar.js' const PLATFORM_INVITE_CODE = '0000008RffVgKHMj7kQC' +const app = getApp() Page(createPage({ data: { @@ -20,8 +34,7 @@ Page(createPage({ shangjiaCertified: false, totalAssets: '0.00', assetBreakdown: [], - pendingAuthList: [], - showAuthSection: false, + moreFuncList: [], inviteCode: '', guanshiInviteCode: '', zuzhangInviteCode: '', @@ -43,6 +56,7 @@ Page(createPage({ zuzhangInviterCache: null, chenghaoList: [], guanshiChenghaoList: [], + identityTagList: [], gszhstatus: '', yaoqingzongshu: 0, fenyongzonge: '0.00', @@ -58,7 +72,6 @@ Page(createPage({ tongguoZongshu: 0, pendingShenheCount: 0, layoutSparse: false, - scrollRefreshing: false, rankBoardList: [ { type: 'dashou', name: '接单员榜', sub: '接单成交', icon: '', shapeClass: 'rank-shape-left', themeClass: 'rank-theme-blue' }, { type: 'guanshi', name: '管事榜', sub: '邀请收入', icon: '', shapeClass: 'rank-shape-right', themeClass: 'rank-theme-gold' }, @@ -68,95 +81,69 @@ Page(createPage({ statusBar: 20, navBar: 44, + pindaoVisible: false, + pindaoTitle: '频道', + pindaoChannelNo: '', + examEnabled: false, + examPassed: false, + pindaoImages: [], + + scrollViewRefreshing: false, + showRechargeBanners: false, + showPindaoEntry: true, + + userBg: '', + qiepianBanner: '', + ksIcon: '', + penaltyTipIcon: '', + followExpanded: false, + followClass: '', + pendingPenaltyCount: 0, + myZhidingList: [], + myZhidingCount: 0, + orderNavXym: [], + tradeNavXym: [], + inviteNavXym: [], }, _buildImgUrls(ossImageUrl) { - const dsBase = ossImageUrl + 'beijing/dashouduan/'; - const iconBase = ossImageUrl + 'beijing/dashouduan/dashoutubiaobeijing/'; - const gsBase = ossImageUrl + 'beijing/guanshiduan/'; - const khBase = ossImageUrl + 'beijing/kaohe/'; + const app = getApp(); + const icon = (key, fallback) => resolveMiniappIcon(app, key, fallback || defaultIconRel(key)); return { - pageBg: dsBase + 'page-bg.jpg', - userCardBg: dsBase + 'user-card-bg.png', - vipCardBg: dsBase + 'vip-card-bg.jpg', - assetPanelBg: dsBase + 'asset-panel-bg.png', - rankHubBg: dsBase + 'rank-hub-bg.png', - rankTileBg: dsBase + 'rank-tile-bg.png', - funcPanelBg: dsBase + 'func-panel-bg.png', - listBg: dsBase + 'list-card-bg.png', - topBg: iconBase + 'top-bg.jpg', - cardBg: iconBase + 'card-bg.jpg', - iconRefresh: gsBase + 'icon-refresh.png', - iconCopy: gsBase + 'icon-copy.png', - iconVip: dsBase + 'icon-vip.png', - iconArrowLight: dsBase + 'icon-arrow-light.png', - iconSectionAsset: dsBase + 'icon-section-asset.png', - iconSectionRank: dsBase + 'icon-section-rank.png', - iconSectionFunc: dsBase + 'icon-section-func.png', - iconSectionGuanshi: dsBase + 'icon-section-guanshi.png', - iconSectionZuzhang: dsBase + 'icon-section-zuzhang.png', - iconSectionKaohe: dsBase + 'icon-section-kaohe.png', - iconSectionMore: dsBase + 'icon-section-more.png', - iconRankDashou: dsBase + 'rank-dashou.png', - iconRankGuanshi: dsBase + 'rank-guanshi.png', - iconRankZuzhang: dsBase + 'rank-zuzhang.png', - iconRankShangjia: dsBase + 'rank-shangjia.png', - iconGame: iconBase + 'icon-game.jpg', - iconRecharge: iconBase + 'icon-recharge.jpg', - iconOrders: iconBase + 'icon-orders.jpg', - iconPunish: iconBase + 'icon-punish.jpg', - iconRank: iconBase + 'icon-rank.jpg', - iconInvite: gsBase + 'icon-invite.png', - iconMedal: iconBase + 'icon-medal.jpg', - iconEdit: iconBase + 'icon-edit.jpg', - iconBook: iconBase + 'icon-book.jpg', - iconArrow: gsBase + 'icon-arrow.png', - iconSub: gsBase + 'icon-sub.png', - iconRecord: gsBase + 'icon-record.png', - iconPoster: gsBase + 'icon-poster.png', - iconWithdraw: gsBase + 'icon-withdraw.png', - iconContact: gsBase + 'icon-contact.png', - avatarDefault: ossImageUrl + 'avatar/default.jpg', - iconClear: ossImageUrl + 'beijing/tubiao/grzx_qingchu.jpg', - iconSwitch: '/images/_exit.png', - iconKefu: ossImageUrl + 'beijing/tubiao/grzx_kefu.jpg', - iconKuaishou: ossImageUrl + 'beijing/tubiao/grzx_guanzhualong.jpg', - iconKaoheDafen: khBase + 'daofen.png', - iconKaoheJilu: khBase + 'jilu.png', - iconKaoheZhongxin: khBase + 'zhongxin.png', - iconShangjia: ossImageUrl + 'beijing/tubiao/grzx_shangjia.jpg', - iconDashouAuth: ossImageUrl + 'beijing/tubiao/grzx_dashou.jpg', - iconGuanshiAuth: ossImageUrl + 'beijing/tubiao/grzx_guanshi.jpg', - iconZuzhangAuth: ossImageUrl + 'beijing/tubiao/grzx_zuzhang.jpg', - kefuBannerBg: dsBase + 'kefu-banner-bg.png', - totalAssetBg: dsBase + 'total-asset-bg.png', - authPanelBg: dsBase + 'auth-panel-bg.png', - bannerVip: 'https://bintao-1308403501.cos.ap-guangzhou.myqcloud.com/uploads/images/20260512123427531240452.png', - bannerDeposit: 'https://bintao-1308403501.cos.ap-guangzhou.myqcloud.com/uploads/images/202605121234283091d3980.png', + iconRefresh: icon(ICON_KEYS.ICON_REFRESH, 'beijing/guanshiduan/icon-refresh.png'), + iconCopy: icon(ICON_KEYS.FIGHTER_MINE_COPY, 'beijing/guanshiduan/icon-copy.png'), + iconEdit: icon(ICON_KEYS.FIGHTER_MINE_EDIT, 'beijing/dashouduan/dashoutubiaobeijing/icon-edit.jpg'), + iconPunish: icon(ICON_KEYS.FIGHTER_MINE_PUNISH, 'beijing/dashouduan/dashoutubiaobeijing/icon-punish.jpg'), + iconMedal: icon(ICON_KEYS.FIGHTER_MINE_MEDAL, 'beijing/dashouduan/dashoutubiaobeijing/icon-medal.jpg'), + iconBook: icon(ICON_KEYS.FIGHTER_MINE_BOOK, 'beijing/dashouduan/dashoutubiaobeijing/icon-book.jpg'), + iconRank: icon(ICON_KEYS.FIGHTER_MINE_RANK, 'beijing/dashouduan/dashoutubiaobeijing/icon-rank.jpg'), + iconInvite: icon(ICON_KEYS.FIGHTER_MINE_INVITE, 'beijing/guanshiduan/icon-invite.png'), + iconContact: icon(ICON_KEYS.FIGHTER_MINE_CONTACT, 'beijing/guanshiduan/icon-contact.png'), + iconKefu: icon(ICON_KEYS.FIGHTER_MINE_KEFU, 'beijing/tubiao/grzx_kefu.jpg'), + iconPindao: icon(ICON_KEYS.MINE_PINDAO, PINDAO_ICON_FALLBACK), + iconKaoheDafen: icon(ICON_KEYS.FIGHTER_MINE_KAOHE_DAFEN, 'beijing/kaohe/daofen.png'), + iconKaoheJilu: icon(ICON_KEYS.FIGHTER_MINE_KAOHE_JILU, 'beijing/kaohe/jilu.png'), + iconKaoheZhongxin: icon(ICON_KEYS.FIGHTER_MINE_KAOHE_ZHONGXIN, 'beijing/kaohe/zhongxin.png'), + iconShangjia: icon(ICON_KEYS.FIGHTER_MINE_SHANGJIA, 'beijing/tubiao/grzx_shangjia.jpg'), + iconDashouAuth: icon(ICON_KEYS.FIGHTER_MINE_DASHOU_AUTH, 'beijing/tubiao/grzx_dashou.jpg'), + iconClear: icon(ICON_KEYS.FIGHTER_MINE_CLEAR, 'beijing/tubiao/grzx_qingchu.jpg'), + iconSwitch: icon(ICON_KEYS.FIGHTER_MINE_SWITCH, `${ICON_FOLDER}/fighter_mine_switch.png`), + iconRecord: icon(ICON_KEYS.FIGHTER_MINE_RECORD, 'beijing/guanshiduan/icon-record.png'), + bannerVip: icon(ICON_KEYS.FIGHTER_RECHARGE_MEMBER, `${ICON_FOLDER}/fighter_recharge_member.png`), + bannerDeposit: icon(ICON_KEYS.FIGHTER_RECHARGE_DEPOSIT, `${ICON_FOLDER}/fighter_recharge_deposit.png`), + avatarDefault: getDefaultAvatarUrl(app), }; }, - _applyRankBoardIcons(imgUrls) { - const iconMap = { - dashou: imgUrls.iconRankDashou, - guanshi: imgUrls.iconRankGuanshi, - zuzhang: imgUrls.iconRankZuzhang, - shangjia: imgUrls.iconRankShangjia, - }; - return (this.data.rankBoardList || []).map((item) => ({ - ...item, - icon: iconMap[item.type] || imgUrls.iconRank, - })); - }, - onLoad(options) { const app = getApp(); const sys = wx.getSystemInfoSync(); const ossImageUrl = app.globalData.ossImageUrl || ''; const imgUrls = this._buildImgUrls(ossImageUrl); + const nav = this._buildNavLists(app); this.setData({ imgUrls, - rankBoardList: this._applyRankBoardIcons(imgUrls), + ...nav, statusBar: sys.statusBarHeight || 20, navBar: (sys.statusBarHeight || 20) + 44, }); @@ -180,11 +167,60 @@ Page(createPage({ } } this.checkColdStartPopup('dashouduan'); + this.syncConfiguredAssets(); + }, + + _buildNavLists(app) { + const icon = (key) => resolveMiniappIcon(app, key, defaultIconRel(key)); + return { + orderNavXym: [ + { name: '待完成', statusKey: 'jinxingzhong', icon: icon(ICON_KEYS.FIGHTER_ORDER_PENDING), badge: 0, showBadge: false }, + { name: '待结算', statusKey: 'jiesuanzhong', icon: icon(ICON_KEYS.FIGHTER_ORDER_SETTLING), badge: 0, showBadge: false }, + { name: '已完成', statusKey: 'yiwancheng', icon: icon(ICON_KEYS.FIGHTER_ORDER_DONE), badge: 0, showBadge: false }, + { name: '已关闭', statusKey: 'yituikuan', icon: icon(ICON_KEYS.FIGHTER_ORDER_CLOSED), badge: 0, showBadge: false }, + ], + tradeNavXym: [ + { name: '佣金记录', type: 'commission', icon: icon(ICON_KEYS.FIGHTER_TRADE_COMMISSION) }, + { name: '提现记录', type: 'withdraw', icon: icon(ICON_KEYS.FIGHTER_TRADE_WITHDRAW) }, + { name: '结算记录', type: 'settle', icon: icon(ICON_KEYS.FIGHTER_TRADE_SETTLE) }, + { name: '扣款记录', type: 'deduct', icon: icon(ICON_KEYS.FIGHTER_TRADE_DEDUCT) }, + { name: '充值记录', type: 'recharge', icon: icon(ICON_KEYS.FIGHTER_TRADE_RECHARGE) }, + ], + inviteNavXym: [ + { name: '我的下级', action: 'subordinate', icon: icon(ICON_KEYS.FIGHTER_INVITE_SUBORDINATE) }, + { name: '推广海报', action: 'poster', icon: icon(ICON_KEYS.FIGHTER_INVITE_POSTER) }, + { name: '邀请码', action: 'inviteCode', icon: icon(ICON_KEYS.FIGHTER_INVITE_CODE) }, + ], + ksIcon: icon(ICON_KEYS.FIGHTER_KUAISHOU_CASH), + qiepianBanner: icon(ICON_KEYS.FIGHTER_MINE_QIEPIAN), + penaltyTipIcon: icon(ICON_KEYS.FIGHTER_MINE_PENALTY_TIP), + }; + }, + + syncConfiguredAssets() { + const app = getApp(); + const ossImageUrl = app.globalData.ossImageUrl || ''; + const imgUrls = this._buildImgUrls(ossImageUrl); + const nav = this._buildNavLists(app); + const bannerVip = imgUrls.bannerVip; + const bannerDeposit = imgUrls.bannerDeposit; + this.setData({ + imgUrls, + ...nav, + showRechargeBanners: !!(bannerVip || bannerDeposit), + // showPindaoEntry: ... || this.data.isKaoheguan 临时隐藏考核官 + showPindaoEntry: this.data.isDashou || this.data.isGuanshi || this.data.isZuzhang, + }, () => { + this._buildMoreFuncList(); + }); }, async onShow() { - const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' }); - if (!phoneOk) return; + if (!app.globalData._dashouPhoneChecked) { + const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' }); + if (!phoneOk) return; + app.globalData._dashouPhoneChecked = true; + } migrateLegacyCenterRole(getApp()); lockPrimaryRole('dashou', getApp()); @@ -196,11 +232,15 @@ Page(createPage({ this.setData({ inviterCache, zuzhangInviterCache }); this.registerNotificationComponent(); + warmupPindaoConfig(app); this.checkRoleStatuses(); - this.applyCachedProfileFromGlobal(); + this.syncConfiguredAssets(); + this.loadPenaltyCount(); if (isCenterPageActive(this, 'isDashou', 'dashoustatus')) { if (!this.data.isDashou) this.setData({ isDashou: true }); ensureRoleOnCenterPage(this, 'dashou'); + this.loadExamStatus(); + this.loadMyZhidingOrders(); } else if (isRoleStatusActive(wx.getStorageSync('guanshistatus')) || isRoleStatusActive(wx.getStorageSync('zuzhangstatus')) || isRoleStatusActive(wx.getStorageSync('kaoheguanstatus'))) { @@ -221,6 +261,7 @@ Page(createPage({ }, 300); } } + if (app.startImWhenReady) app.startImWhenReady(); }, checkRoleStatuses() { @@ -237,7 +278,7 @@ Page(createPage({ layoutSparse: subRoleCount === 0, }, () => { this._updateTotalAssets(); - this._updatePendingAuth(); + this._buildMoreFuncList(); }); if (isDashou) { const uid = wx.getStorageSync('uid') || ''; @@ -263,125 +304,117 @@ Page(createPage({ total += this._parseMoney(d.ketixian); breakdown.push({ label: '组长余额', amount: d.ketixian || '0.00' }); } - if (d.isKaoheguan) { - total += this._parseMoney(d.kaoheYue); - breakdown.push({ label: '考核余额', amount: d.kaoheYue || '0.00' }); - } + // 临时隐藏考核官余额展示,恢复时取消注释 + // if (d.isKaoheguan) { + // total += this._parseMoney(d.kaoheYue); + // breakdown.push({ label: '考核余额', amount: d.kaoheYue || '0.00' }); + // } this.setData({ totalAssets: total.toFixed(2), assetBreakdown: breakdown }); }, - /** 打手端:已认证不展示;商家始终第一位 */ - _updatePendingAuth() { + /** 更多功能:常用优先,按身份动态展示 */ + _buildMoreFuncList() { const d = this.data; const img = d.imgUrls || {}; const list = []; - list.push({ - type: 'shangjia', - name: '商家', - icon: img.iconShangjia, - tag: d.shangjiaCertified ? '进入商家端' : '去认证', - done: d.shangjiaCertified, - }); - list.push({ - type: 'staff', - name: '商家客服', - icon: img.iconKefu || img.iconShangjia, - tag: isStaffMode() ? '进入工作台' : '去入驻', - done: isStaffMode(), - }); - if (!d.isGuanshi) { - list.push({ type: 'guanshi', name: '管事', icon: img.iconGuanshiAuth, tag: '去认证' }); - } - if (!d.isZuzhang) { - list.push({ type: 'zuzhang', name: '组长', icon: img.iconZuzhangAuth, tag: '去认证' }); - } - if (!d.isDashou) { - list.push({ type: 'dashou', name: '接单员', icon: img.iconDashouAuth, tag: '去认证' }); - } - this.setData({ - pendingAuthList: list, - showAuthSection: list.length > 0, - }); - }, - - applyCachedProfileFromGlobal() { - const app = getApp(); - const g = app.globalData; - const guanshi = g.guanshi || {}; - const patch = { - dashouNicheng: g.dashouNicheng || this.data.dashouNicheng || '', - zhanghaoStatus: g.zhanghaoStatus ?? this.data.zhanghaoStatus, - yongjin: g.yongjin || this.data.yongjin || '0.00', - zonge: g.zonge || this.data.zonge || '0.00', - yajin: g.yajin || this.data.yajin || '0.00', - chenghao: g.chenghao || this.data.chenghao || '', - jinfen: g.jinfen || this.data.jinfen || '0', - clumber: g.clumber || this.data.clumber || [], - chengjiaoliang: g.chengjiaoliang || this.data.chengjiaoliang || '0', - zaixianZhuangtai: g.zaixianZhuangtai ?? this.data.zaixianZhuangtai ?? 0, - dashouzhuangtai: g.dashouzhuangtai ?? this.data.dashouzhuangtai, - gszhstatus: guanshi.gszhstatus || this.data.gszhstatus || '', - yaoqingzongshu: guanshi.yaoqingzongshu ?? this.data.yaoqingzongshu ?? 0, - fenyongzonge: guanshi.fenyongzonge || this.data.fenyongzonge || '0.00', - fenyongtixian: guanshi.fenyongtixian || this.data.fenyongtixian || '0.00', + const push = (action, name, icon, extra = {}) => { + list.push({ action, name, icon, ...extra }); }; - this.setData(patch, () => this._updateTotalAssets()); + + if (d.isZuzhang) { + push('goToYaoqingGuanshi', '邀请管事', img.iconInvite); + } + push('contactInviter', '联系管事', img.iconInvite); + if (d.isGuanshi) { + push('contactZuzhang', '联系组长', img.iconContact); + } + push('goToKefu', '在线客服', img.iconKefu); + push('onTapShangjiaAuth', d.shangjiaCertified ? '进入商家端' : '商家认证', img.iconShangjia); + push('onTapStaffAuth', isStaffMode() ? '商家客服工作台' : '商家客服入驻', img.iconKefu || img.iconShangjia); + if (d.showPindaoEntry) { + push('openPindaoModal', '频道', img.iconPindao); + } + + push('goToMyPunishment', '我的处罚', img.iconPunish); + // push('goToKaohe', '考核金牌', img.iconMedal); // 临时隐藏,恢复时取消注释 + push('goToDashouExam', '接单考试', img.iconBook); + push('goToRanking', '总排行榜', img.iconRank, { rankType: 'dashou' }); + + // 会员记录 / 分红记录已合并至「佣金记录」页 Tab 切换 + // 临时隐藏考核官入口,恢复时取消注释 + // if (d.isKaoheguan) { + // push('goToKaoheDafen', '考核打分', img.iconKaoheDafen); + // push('goToKaoheJilu', '考核记录', img.iconKaoheJilu); + // push('goToKaoheZhongxin', '考核中心', img.iconKaoheZhongxin); + // } + + if (!d.isDashou) { + push('goToAuth', '接单员认证', img.iconDashouAuth, { authType: 'dashou' }); + } + + push('goToRules', '用户规则', img.iconBook); + push('switchToNormal', '返回点单端', img.iconSwitch); + push('clearCache', '退出登录', img.iconClear); + + this.setData({ moreFuncList: list }); }, - async onPullDownRefresh() { - if (this._pullRefreshing) return; - this._pullRefreshing = true; - this.setData({ scrollRefreshing: true }); + onMoreFuncTap(e) { + const { action, authType, rankType } = e.currentTarget.dataset; + if (action === 'goToAuth' && authType) { + this.goToAuth({ currentTarget: { dataset: { type: authType } } }); + return; + } + if (action === 'goToRanking') { + this.goToRanking({ currentTarget: { dataset: { type: rankType || 'dashou' } } }); + return; + } + if (action === 'onTapStaffAuth') { + this.onTapAuthItem({ currentTarget: { dataset: { type: 'staff' } } }); + return; + } + if (action === 'onTapShangjiaAuth') { + this.onTapShangjiaAuth(); + return; + } + if (typeof this[action] === 'function') { + this[action](); + } + }, + + async refreshAllInfo(showToast = true) { + if (this._profileRefreshing) return; + this._profileRefreshing = true; + this.setData({ isLoading: true }); try { this.checkRoleStatuses(); - await Promise.allSettled([ + const results = await Promise.allSettled([ this._fetchDashouInfoSilent(), this.fetchChenghaoList(), this._fetchGuanshiInfoSilent(), this.fetchGuanshiChenghaoList(), this._fetchZuzhangInfoSilent(), this._fetchKaoheguanInfoSilent(), + this.loadIdentityTags(), + this.loadMyZhidingOrders(), ]); this.checkRoleStatuses(); - wx.showToast({ title: '已更新', icon: 'success', duration: 1200 }); - } catch (e) { - wx.showToast({ title: '刷新失败', icon: 'none' }); - } finally { - this._pullRefreshing = false; - this.setData({ scrollRefreshing: false }); - } - }, - - async refreshAllInfo(showToast = true) { - this.throttledRefresh(async () => { - this.setData({ isLoading: true }); - try { - this.checkRoleStatuses(); - const results = await Promise.allSettled([ - this._fetchDashouInfoSilent(), - this.fetchChenghaoList(), - this._fetchGuanshiInfoSilent(), - this.fetchGuanshiChenghaoList(), - this._fetchZuzhangInfoSilent(), - this._fetchKaoheguanInfoSilent(), - ]); - this.checkRoleStatuses(); - const dashouOk = results[0].status === 'fulfilled'; - if (showToast) { - wx.showToast({ - title: dashouOk ? '刷新成功' : '刷新失败', - icon: dashouOk ? 'success' : 'none', - duration: 1500, - }); - } - } catch (e) { - if (showToast) { - wx.showToast({ title: '刷新失败', icon: 'none' }); - } - } finally { - this.setData({ isLoading: false }); + const dashouOk = results[0].status === 'fulfilled'; + if (showToast) { + wx.showToast({ + title: dashouOk ? '刷新成功' : '刷新失败', + icon: dashouOk ? 'success' : 'none', + duration: 1500, + }); } - }, 3000); + } catch (e) { + if (showToast) { + wx.showToast({ title: '刷新失败', icon: 'none' }); + } + } finally { + this._profileRefreshing = false; + this.setData({ isLoading: false }); + } }, refreshDashouInfo() { @@ -392,6 +425,34 @@ Page(createPage({ this.refreshAllInfo(true); }, + onPullDownRefresh() { + this.setData({ scrollViewRefreshing: true }); + Promise.all([ + this.refreshAllInfo(false), + this.loadExamStatus(), + ]).finally(() => { + this.setData({ scrollViewRefreshing: false }); + wx.stopPullDownRefresh(); + }); + }, + + async loadIdentityTags() { + try { + const res = await request({ url: '/jituan/identity-tag/my-tags', method: 'POST' }); + if (res && res.data && (res.data.code === 0 || res.data.code === 200)) { + const d = res.data.data || {}; + const merged = [ + ...(d.dashou || []), + ...(d.zuzhang || []), + ...(d.guanshi || []), + ]; + this.setData({ identityTagList: merged }); + } + } catch (e) { + /* 静默 */ + } + }, + async _fetchDashouInfoSilent() { const res = await request({ url: '/yonghu/dashouxinxi', method: 'POST' }); if (res && res.data.code == 200) { @@ -685,12 +746,15 @@ Page(createPage({ goToInviteUser() { wx.navigateTo({ url: '/pages/invite-fighter/invite-fighter' }); }, goToMySubordinate() { wx.navigateTo({ url: '/pages/my-fighter/my-fighter' }); }, - goToRechargeRecord() { wx.navigateTo({ url: '/pages/recharge-log/recharge-log' }); }, + goToRechargeRecord() { wx.navigateTo({ url: '/pages/recharge-log/recharge-log?tab=guanshi' }); }, + goToCommissionLog(tab = 'dashou') { + wx.navigateTo({ url: `/pages/recharge-log/recharge-log?tab=${tab}` }); + }, goToGuanshiRanking() { wx.navigateTo({ url: '/pages/fighter-rank/fighter-rank?type=guanshi' }); }, goToPromoPoster() { wx.navigateTo({ url: '/pages/poster/poster' }); }, - goToFenhongJilu() { wx.navigateTo({ url: '/pages/leader-bonus-log/leader-bonus-log' }); }, + goToFenhongJilu() { wx.navigateTo({ url: '/pages/recharge-log/recharge-log?tab=zuzhang' }); }, goToYaoqingGuanshi() { wx.navigateTo({ url: '/pages/invite-manager/invite-manager' }); }, goToGuanshiAuthPage() { wx.navigateTo({ url: '/pages/verify/verify?type=guanshi' }); }, @@ -737,7 +801,7 @@ Page(createPage({ } }, - // 联系邀请人 + // 联系管事(邀请人) async contactInviter() { wx.showLoading({ title: '获取邀请人...', mask: true }); try { @@ -1105,15 +1169,174 @@ Page(createPage({ } wx.navigateTo({ url: '/pages/verify/verify?type=shangjia' }); }, - goToReceiveOrder() { wx.navigateTo({ url: '/pages/accept-order/accept-order' }) }, - goToMyOrders() { wx.navigateTo({ url: '/pages/fighter-orders/fighter-orders' }) }, + goToReceiveOrder() { wx.switchTab({ url: '/pages/accept-order/accept-order' }) }, + + async loadMyZhidingOrders() { + if (!isCenterPageActive(this, 'isDashou', 'dashoustatus')) return; + try { + const raw = await fetchMyZhidingOrders(); + const uid = this.data.uid || wx.getStorageSync('uid'); + const list = filterZhidingBannerList(processZhidingList(raw, getApp(), uid)); + this.setData({ myZhidingList: list, myZhidingCount: list.length }); + } catch (e) { + console.warn('fighter loadMyZhidingOrders', e); + } + }, + + goToZhidingFromMine() { + const app = getApp(); + const first = (this.data.myZhidingList || [])[0]; + if (first && first.dingdan_id) { + app.globalData._acceptOrderHighlight = first.dingdan_id; + } + wx.switchTab({ + url: '/pages/accept-order/accept-order', + fail: () => wx.showToast({ title: '跳转失败', icon: 'none' }), + }); + }, + + goToDashouExam() { wx.navigateTo({ url: '/pages/dashou-exam/dashou-exam' }) }, + + goToMyOrders() { wx.switchTab({ url: '/pages/fighter-orders/fighter-orders' }) }, + + goToOrdersWithStatus(e) { + const statusKey = e.currentTarget.dataset.status; + if (statusKey) { + getApp().globalData._fighterOrdersStatusKey = statusKey; + } + wx.switchTab({ url: '/pages/fighter-orders/fighter-orders' }); + }, + + goToAllOrders() { + getApp().globalData._fighterOrdersStatusKey = 'all'; + wx.switchTab({ url: '/pages/fighter-orders/fighter-orders' }); + }, + + goToTradeNav(e) { + const type = e.currentTarget.dataset.type; + if (type === 'commission') { + this.goToCommissionLog('dashou'); + return; + } + if (type === 'recharge') { + if (this.data.isGuanshi) { + this.goToCommissionLog('guanshi'); + } else { + this.onComingSoon(); + } + return; + } + if (type === 'withdraw') { + wx.navigateTo({ url: '/pages/withdraw/withdraw-records' }); + return; + } + if (type === 'deduct') { + wx.navigateTo({ url: '/pages/fighter-ledger/fighter-ledger?type=deduct' }); + return; + } + wx.navigateTo({ url: `/pages/fighter-ledger/fighter-ledger?type=${type}` }); + }, + + goToWalletDetail() { + this.goToCommissionLog('dashou'); + }, + + onInviteNavTap(e) { + if (!this.data.isGuanshi) { + this.goToGuanshiAuthPage(); + return; + } + const action = e.currentTarget.dataset.action; + if (action === 'subordinate') { + this.goToMySubordinate(); + return; + } + if (action === 'poster') { + this.goToPromoPoster(); + return; + } + if (action === 'inviteCode') { + this.goToInviteUser(); + } + }, + + onComingSoon() { + wx.showToast({ title: '该功能待开发', icon: 'none' }); + }, + + toggleFollowKuaishou() { + const next = !this.data.followExpanded; + this.setData({ followExpanded: next, followClass: next ? 'expanded' : '' }); + }, + + onQiepianTap() { + this.onComingSoon(); + }, + + async loadPenaltyCount() { + if (!this.data.isDashou && !this.data.isGuanshi) return; + try { + const res = await request({ url: '/yonghu/cffktjhq', method: 'POST' }); + const body = res?.data; + if (body && (body.code === 200 || body.code === 0) && body.data) { + const d = body.data; + const cnt = Number(d.daichuli_fakuan || d.fakuan_wait || d.pending || 0); + this.setData({ pendingPenaltyCount: cnt > 0 ? cnt : 0 }); + } + } catch (e) { + /* ignore */ + } + }, + goToMyPunishment() { wx.navigateTo({ url: '/pages/penalty/penalty/penalty' }) }, - goToRecharge() { wx.navigateTo({ url: '/pages/fighter-recharge/fighter-recharge' }) }, + + async loadExamStatus() { + try { + const res = await request({ url: '/jituan/dashou-exam/status', method: 'POST' }); + const body = res?.data; + if (body && (body.code === 200 || body.code === 0) && body.data) { + const d = body.data; + this.setData({ + examEnabled: !!d.exam_enabled, + examPassed: !!d.exam_passed, + }); + } + } catch (e) { /* 静默 */ } + }, + goToRecharge() { wx.navigateTo({ url: '/pages/fighter-recharge/fighter-recharge' }); }, + goToRechargeMember() { wx.navigateTo({ url: '/pages/fighter-recharge/fighter-recharge' }); }, + goToRechargeDeposit() { wx.navigateTo({ url: '/pages/fighter-recharge/fighter-deposit' }); }, goToRanking(e) { const type = e?.currentTarget?.dataset?.type || 'dashou'; wx.navigateTo({ url: `/pages/fighter-rank/fighter-rank?type=${type}` }); }, - goToGuanzhuKs() { wx.navigateTo({ url: '/pages/manager-assign/manager-assign' }) }, + goToGuanzhuKs() { + wx.navigateTo({ url: '/pages/manager-assign/manager-assign' }); + }, + + openPindaoModal() { + const app = getApp(); + const cfg = getPindaoConfig(app); + const images = resolvePindaoImages(app); + this.setData({ + pindaoVisible: true, + pindaoTitle: cfg.title || '频道', + pindaoChannelNo: cfg.channel_no || '', + pindaoImages: images, + }); + refreshPindaoConfig(app).then(({ cfg: latest, images: latestImages }) => { + if (!this.data.pindaoVisible) return; + this.setData({ + pindaoTitle: latest.title || '频道', + pindaoChannelNo: latest.channel_no || '', + pindaoImages: latestImages, + }); + }).catch(() => {}); + }, + + closePindaoModal() { + this.setData({ pindaoVisible: false }); + }, goToKaoheDafen() { wx.navigateTo({ url: '/pages/assess-score/assess-score' }) }, goToKaoheJilu() { wx.navigateTo({ url: '/pages/assess-log/assess-log' }) }, goToKaoheZhongxin() { wx.navigateTo({ url: '/pages/assess-center/assess-center' }) }, diff --git a/pages/fighter/fighter.json b/pages/fighter/fighter.json index 36119d5..a31615c 100644 --- a/pages/fighter/fighter.json +++ b/pages/fighter/fighter.json @@ -2,10 +2,11 @@ "usingComponents": { "global-notification": "/components/global-notification/global-notification", "chenghao-tag": "/components/chenghao-tag/chenghao-tag", - "tab-bar": "/tab-bar/index" + "tab-bar": "/tab-bar/index", + "pindao-modal": "/components/pindao-modal/pindao-modal" }, "navigationStyle": "custom", - "backgroundColor": "#f5f3ff", + "backgroundColor": "#fff8e1", "backgroundTextStyle": "dark", "enablePullDownRefresh": false, "onReachBottomDistance": 50 diff --git a/pages/fighter/fighter.wxml b/pages/fighter/fighter.wxml index 358139e..32e9115 100644 --- a/pages/fighter/fighter.wxml +++ b/pages/fighter/fighter.wxml @@ -14,7 +14,7 @@ 立即注册 平台直签注册 返回点单端 - 清除缓存,重新登录 + 退出登录 @@ -23,14 +23,12 @@ - + - 我的 - - - - + + 我的 + - - + ID: {{uid || '--'}} - + + + + - - + @@ -81,11 +81,15 @@ + + + + + - - + 我的佣金(元) ¥{{totalAssets}} 可提现 ¥{{yongjin || '0.00'}} · 查看 @@ -96,13 +100,19 @@ - - + + + @@ -247,3 +209,10 @@ + diff --git a/pages/fighter/fighter.wxss b/pages/fighter/fighter.wxss index 77158ab..94bd2ed 100644 --- a/pages/fighter/fighter.wxss +++ b/pages/fighter/fighter.wxss @@ -1,7 +1,8 @@ /* 打手端个人中心 - 逍遥梦 UI(逻辑不变) */ +@import '../../styles/dashou-xym-user.wxss'; page { - background: #f5f3ff; + background: #fff8e1; color: #333; } @@ -16,7 +17,7 @@ page { flex-direction: column; overflow: hidden; box-sizing: border-box; - background: linear-gradient(180deg, #c4b5fd 0%, #fff 28%, #f5f3ff 100%); + background: linear-gradient(180deg, #f7dc51 0%, #fff 28%, #fff8e1 100%); position: relative; } @@ -24,6 +25,7 @@ page { position: relative; z-index: 2; flex-shrink: 0; + background: linear-gradient(180deg, #f7dc51 0%, #ffd061 100%); } .status-bar, @@ -40,6 +42,19 @@ page { justify-content: center; } +.nav-bar-actions { + position: relative; + justify-content: center; +} + +.nav-refresh-ico { + position: absolute; + right: 24rpx; + width: 44rpx; + height: 44rpx; + padding: 8rpx; +} + .nav-title { font-size: 34rpx; font-weight: 700; @@ -52,10 +67,6 @@ page { padding-bottom: 0; } -.content-bottom { - height: 12rpx; -} - .refresh-float { position: fixed; right: 24rpx; @@ -112,7 +123,7 @@ page { font-size: 20rpx; padding: 2rpx 12rpx; border-radius: 8rpx; - background: linear-gradient(180deg, #c4b5fd, #a78bfa); + background: linear-gradient(180deg, #fae04d, #ffc0a3); color: #492f00; } @@ -134,6 +145,20 @@ page { opacity: 0.6; } +.user-header-actions { + display: flex; + align-items: center; + gap: 16rpx; + flex-shrink: 0; +} + +.header-action-ico { + width: 48rpx; + height: 48rpx; + padding: 6rpx; + flex-shrink: 0; +} + .setting-ico { width: 53rpx; height: 53rpx; @@ -169,7 +194,7 @@ page { margin: 15rpx 30rpx 0; padding: 24rpx 20rpx; border-radius: 26rpx; - background: #ede9fe; + background: #fef6d4; align-items: center; } @@ -210,7 +235,7 @@ page { font-weight: 700; border: 2rpx solid #fff; border-radius: 60rpx; - background: linear-gradient(180deg, #c4b5fd, #a78bfa); + background: linear-gradient(180deg, #fae04d, #ffc0a3); color: #492f00; white-space: nowrap; } @@ -233,11 +258,28 @@ page { margin-right: 2%; } +.recharge-text-row { + margin: 20rpx 24rpx 0; + gap: 16rpx; +} + +.recharge-text-btn { + flex: 1; + text-align: center; + padding: 22rpx 12rpx; + border-radius: 16rpx; + font-size: 28rpx; + font-weight: 600; + color: #492f00; + background: linear-gradient(180deg, #fff8e1, #ffe8b8); + border: 1rpx solid rgba(201, 169, 98, 0.45); +} + .freeze-row { margin: 16rpx 30rpx 0; padding: 20rpx 10rpx; border-radius: 15rpx; - background: #ede9fe; + background: #fef6d4; } .freeze-col { @@ -259,7 +301,7 @@ page { } .btn-bg { - background: linear-gradient(180deg, #c4b5fd, #a78bfa); + background: linear-gradient(180deg, #fae04d, #ffc0a3); border-radius: 30px; color: #492f00; } @@ -300,39 +342,6 @@ page { font-weight: 700; } -.stats-bar { - margin: 12rpx 30rpx 0; - padding: 16rpx 16rpx 8rpx; - background: #fdfcfa; - border-radius: 16rpx; - display: flex; - flex-wrap: wrap; - justify-content: flex-start; - align-content: flex-start; - box-shadow: 0 3px 10px rgba(0, 0, 0, 0.04); -} - -.stat-cell { - width: 25%; - padding: 8rpx 0 12rpx; - box-sizing: border-box; - text-align: center; -} - -.stat-val { - display: block; - font-size: 28rpx; - font-weight: 700; - color: #222; -} - -.stat-lbl { - display: block; - font-size: 22rpx; - color: #999; - margin-top: 4rpx; -} - .panel { margin: 20rpx 20rpx 12rpx; background: #fdfcfa; @@ -356,11 +365,35 @@ page { .order-nav { padding: 10rpx 0 20rpx; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; + align-items: flex-start; } .nav-item { - width: 25%; + width: 20%; + box-sizing: border-box; padding: 10rpx 0; + position: relative; +} + +.exam-tag { + font-size: 20rpx; + margin-top: 4rpx; + padding: 2rpx 10rpx; + border-radius: 8rpx; +} + +.exam-tag-pass { + color: #52c41a; + background: rgba(82, 196, 26, 0.12); +} + +.exam-tag-pending { + color: #fa8c16; + background: rgba(250, 140, 16, 0.12); } .nav-icon { @@ -405,7 +438,7 @@ page { } .func-tag-done { - color: #9333ea; + color: #c9a962; } .rank-hub-tip { @@ -484,7 +517,7 @@ page { } .rank-theme-gold { - background: linear-gradient(135deg, #a8842a 0%, #c4b5fd 100%); + background: linear-gradient(135deg, #a8842a 0%, #f5d563 100%); } .rank-theme-gold .rank-tile-name, @@ -498,7 +531,7 @@ page { } .rank-theme-orange { - background: linear-gradient(135deg, #7c3aed 0%, #c4b5fd 100%); + background: linear-gradient(135deg, #d4652a 0%, #f5a962 100%); } /* 未注册 */ @@ -576,7 +609,7 @@ page { width: 60rpx; height: 60rpx; border: 4rpx solid rgba(0, 0, 0, 0.08); - border-top-color: #9333ea; + border-top-color: #ffd061; border-radius: 50%; animation: fighterSpin 0.8s linear infinite; } diff --git a/pages/gold-fighter/gold-fighter.wxss b/pages/gold-fighter/gold-fighter.wxss index c8a3f34..c8abc80 100644 --- a/pages/gold-fighter/gold-fighter.wxss +++ b/pages/gold-fighter/gold-fighter.wxss @@ -69,20 +69,20 @@ page { /* 火苗图标燃烧动画 */ .fire-icon { animation: fireBurn 0.8s infinite alternate ease-in-out; - filter: drop-shadow(0 0 10rpx #9333ea); + filter: drop-shadow(0 0 10rpx #ffaa00); } @keyframes fireBurn { - 0% { transform: scale(1) rotate(-3deg); filter: drop-shadow(0 0 10rpx #9333ea); } - 25% { transform: scale(1.1) rotate(2deg); filter: drop-shadow(0 0 20rpx #9333ea); } - 50% { transform: scale(1.05) rotate(-2deg); filter: drop-shadow(0 0 30rpx #9333ea); } - 75% { transform: scale(1.15) rotate(3deg); filter: drop-shadow(0 0 40rpx #9333ea); } - 100% { transform: scale(1) rotate(-3deg); filter: drop-shadow(0 0 10rpx #9333ea); } + 0% { transform: scale(1) rotate(-3deg); filter: drop-shadow(0 0 10rpx #ffaa00); } + 25% { transform: scale(1.1) rotate(2deg); filter: drop-shadow(0 0 20rpx #ff5500); } + 50% { transform: scale(1.05) rotate(-2deg); filter: drop-shadow(0 0 30rpx #ffaa00); } + 75% { transform: scale(1.15) rotate(3deg); filter: drop-shadow(0 0 40rpx #ffaa00); } + 100% { transform: scale(1) rotate(-3deg); filter: drop-shadow(0 0 10rpx #ffaa00); } } .jinpai-title { font-size: 40rpx; font-weight: bold; - color: #9333ea; - text-shadow: 0 0 30rpx #9333ea, 0 0 60rpx #9333ea; + color: #ffaa00; + text-shadow: 0 0 30rpx #ffaa00, 0 0 60rpx #ffaa00; letter-spacing: 2rpx; } .refresh-btn { @@ -255,7 +255,7 @@ page { display: flex; align-items: center; background: rgba(255,255,255,0.1); - border: 2rpx solid #9333ea; + border: 2rpx solid #ffaa00; clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%); padding: 15rpx 30rpx; position: relative; @@ -269,7 +269,7 @@ page { .withdraw-text { font-size: 32rpx; font-weight: bold; - color: #9333ea; + color: #ffaa00; } .withdraw-glow { position: absolute; @@ -491,7 +491,7 @@ page { content: ''; width: 100rpx; height: 100rpx; - border: 4rpx solid #9333ea; + border: 4rpx solid #ffaa00; border-radius: 50%; border-bottom-color: transparent; animation: spin 1.5s reverse infinite; diff --git a/pages/group-chat/group-chat.js b/pages/group-chat/group-chat.js index dbbe03e..2a73f76 100644 --- a/pages/group-chat/group-chat.js +++ b/pages/group-chat/group-chat.js @@ -2,7 +2,15 @@ const app = getApp(); import { formatDate } from '../../static/lib/utils'; import { sendGroupMessage } from '../../utils/message-sender'; import { getLocalImUserId } from '../../utils/im-user.js'; -import { resolveAvatarUrl } from '../../utils/avatar.js'; +import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js'; +import { getZhuangtaiText, isPairGroupId, normalizeGroupMessage, recordConversationOpen } from '../../utils/group-chat.js'; +import request from '../../utils/request'; +import { + fetchHistoryMessages, + getOldestHistoryTimestamp, + mergeHistoryMessages, + waitChatImReady, +} from '../../utils/chat-history.js'; Page({ data: { @@ -11,6 +19,10 @@ Page({ groupAvatar: '', orderId: '', isCross: 0, + orderZhuangtai: null, + orderZhuangtaiText: '', + orderJieshao: '', + orderJine: '', currentUser: null, messages: [], inputText: '', @@ -30,7 +42,8 @@ Page({ pendingImage: '', pendingImageFile: null, keyboardHeight: 0, - bottomSafeHeight: 10 + bottomSafeHeight: 140, + defaultAvatarUrl: '', }, onLoad(options) { let p = { groupId: '', groupName: '订单群聊', groupAvatar: '', orderId: '', isCross: 0, @@ -41,37 +54,79 @@ Page({ p = { ...p, ...d }; } catch (e) {} } + const orderIdStr = String(p.orderId || '').replace(/^group_/, '') || + (p.groupId && !isPairGroupId(p.groupId) ? String(p.groupId).replace(/^group_/, '') : ''); + const groupId = p.groupId + ? String(p.groupId) + : (orderIdStr ? `group_${orderIdStr}` : ''); + const zt = p.orderZhuangtai; + const defAvatar = getDefaultAvatarUrl(app); this.setData({ - groupId: p.groupId, + groupId, groupName: p.groupName, - groupAvatar: p.groupAvatar, - orderId: p.orderId, - isCross: p.isCross + groupAvatar: resolveAvatarUrl(p.groupAvatar, app) || defAvatar, + orderId: orderIdStr, + isCross: p.isCross, + orderJieshao: p.orderDesc || p.orderJieshao || '', + orderJine: p.orderJine || '', + orderZhuangtai: zt, + orderZhuangtaiText: zt != null ? getZhuangtaiText(zt) : '', + defaultAvatarUrl: defAvatar, }); wx.setNavigationBarTitle({ title: this.data.groupName }); - if (p.currentUserId) { - this.setData({ - currentUser: { - id: p.currentUserId, - name: p.currentUserName || `用户${p.currentUserId.replace(/^[A-Za-z]+/,'')}`, - avatar: resolveAvatarUrl(p.currentUserAvatar, app) - } - }); - } else { + this.ensureCurrentUser(p); + }, + + ensureCurrentUser(p) { + p = p || {}; + const localImId = getLocalImUserId(app); + const uid = wx.getStorageSync('uid') || ''; + const userId = localImId || p.currentUserId || ''; + if (!userId) { this.initCurrentUser(); + return; } + const defAvatar = getDefaultAvatarUrl(app); + const rawAvatar = p.currentUserAvatar + || wx.getStorageSync('touxiang') + || app.globalData.currentUser?.avatar + || ''; + const name = p.currentUserName + || app.globalData.currentUser?.name + || wx.getStorageSync('nicheng') + || app.globalData.dashouNicheng + || app.globalData.nicheng + || `用户${uid}`; + const avatar = resolveAvatarUrl(rawAvatar, app) || defAvatar; + const currentUser = { id: userId, name, avatar: avatar || defAvatar }; + app.globalData.currentUser = { ...app.globalData.currentUser, ...currentUser }; + this.setData({ currentUser }); + }, + + getMyChatProfile() { + const cu = this.data.currentUser; + const defAvatar = getDefaultAvatarUrl(app); + if (cu && cu.id) { + return { + id: cu.id, + name: cu.name || `用户${wx.getStorageSync('uid') || ''}`, + avatar: resolveAvatarUrl(cu.avatar, app) || defAvatar, + }; + } + const imId = getLocalImUserId(app); + const uid = wx.getStorageSync('uid') || ''; + return { + id: imId || '', + name: wx.getStorageSync('nicheng') || app.globalData.nicheng || `用户${uid}`, + avatar: resolveAvatarFromStorage(app) || defAvatar, + }; }, initCurrentUser() { - const imId = getLocalImUserId(app); - const uid = wx.getStorageSync('uid'); - this.setData({ - currentUser: { - id: imId || ('Boss' + uid), - name: app.globalData.currentUser?.name || `用户${uid}`, - avatar: resolveAvatarUrl(wx.getStorageSync('touxiang'), app), - }, - }); + const profile = this.getMyChatProfile(); + if (!profile.id) return; + this.setData({ currentUser: profile }); + app.globalData.currentUser = { ...app.globalData.currentUser, ...profile }; }, buildImageFile(tempPath, size) { @@ -84,14 +139,99 @@ Page({ normalizeMessage(msg) { if (!msg) return msg; + msg = normalizeGroupMessage(msg); if (!msg.payload) msg.payload = {}; - if (msg.type === 'image') { - const p = msg.payload; - if (!p.url) p.url = p.thumbnail || p.thumb || p.imageUrl || ''; + const defAvatar = getDefaultAvatarUrl(app); + const me = this.getMyChatProfile(); + if (me.id && msg.senderId === me.id) { + msg.senderData = { + name: me.name, + avatar: me.avatar || defAvatar, + }; + } else if (msg.senderData?.avatar) { + msg.senderData.avatar = resolveAvatarUrl(msg.senderData.avatar, app) || defAvatar; + } else if (msg.senderData) { + msg.senderData.avatar = defAvatar; + } + if (msg.type === 'image' && msg.payload?.url && !String(msg.payload.url).startsWith('http')) { + msg.payload.url = resolveAvatarUrl(msg.payload.url, app) || msg.payload.url; } return msg; }, + mapIdentityForApi() { + const role = app.globalData.currentRole || wx.getStorageSync('currentRole') || 'normal'; + if (role === 'dashou') return 'dashou'; + if (role === 'shangjia') return 'shangjia'; + return 'normal'; + }, + + async refreshLatestOrderBar() { + const { groupId, orderId } = this.data; + if (!groupId && !orderId) return; + try { + const res = await request({ + url: '/dingdan/ltpddd', + method: 'POST', + data: { + groupId, + dingdan_id: orderId, + identityType: this.mapIdentityForApi(), + }, + }); + const body = res?.data || {}; + const latest = body.data?.latest; + if (!latest) return; + this.setData({ + orderId: latest.dingdan_id, + orderJieshao: latest.jieshao || '', + orderJine: latest.jine || '', + orderZhuangtai: latest.zhuangtai, + orderZhuangtaiText: getZhuangtaiText(latest.zhuangtai), + }); + } catch (e) { + console.warn('刷新最新订单条失败', e); + } + }, + + async refreshOrderStatus() { + const { orderId } = this.data; + if (!orderId) return; + try { + const res = await request({ + url: '/dingdan/ltddzt', + method: 'POST', + data: { + dingdan_id: orderId, + identityType: this.mapIdentityForApi(), + }, + }); + const body = res?.data || {}; + const st = body.data; + if (!st) return; + this.setData({ + orderJieshao: st.jieshao || this.data.orderJieshao, + orderJine: st.jine || this.data.orderJine, + orderZhuangtai: st.zhuangtai, + orderZhuangtaiText: getZhuangtaiText(st.zhuangtai), + }); + } catch (e) { + console.warn('刷新订单状态失败', e); + } + }, + + startOrderStatusPoll() { + this.stopOrderStatusPoll(); + this._orderPollTimer = setInterval(() => this.refreshOrderStatus(), 15000); + }, + + stopOrderStatusPoll() { + if (this._orderPollTimer) { + clearInterval(this._orderPollTimer); + this._orderPollTimer = null; + } + }, + mergeSentMessage(localId, sent) { if (!sent) { this.updateMsg(localId, 'success'); @@ -112,54 +252,90 @@ Page({ }, onShow() { + const defAvatar = getDefaultAvatarUrl(app); + this.setData({ + defaultAvatarUrl: defAvatar, + groupAvatar: resolveAvatarUrl(this.data.groupAvatar, app) || defAvatar, + }); + this.ensureCurrentUser({}); + if (this.data.groupId) { + if (!app.globalData.pageState) app.globalData.pageState = {}; + app.globalData.pageState.lastOpenedConvId = this.data.groupId; + recordConversationOpen({ groupId: this.data.groupId }); + } + this.refreshLatestOrderBar(); + this.startOrderStatusPoll(); this.autoConnect(); }, onHide() { + if (this.data.groupId) { + recordConversationOpen({ groupId: this.data.groupId }); + } + this.stopOrderStatusPoll(); this.clearAllListeners(); }, - autoConnect() { - const targetUserId = getLocalImUserId(app); + onUnload() { + this.stopOrderStatusPoll(); + }, + + async autoConnect() { + const targetUserId = this.data.currentUser?.id || getLocalImUserId(app); if (!targetUserId) return; + const afterReady = async () => { + await this.subscribeGroupIfNeeded(); + this.setupAllListeners(); + await this.loadHistory(true); + this.markGroupMessageAsRead(); + }; + + try { + const ok = await waitChatImReady(app, 12000); + if (ok) { + await afterReady(); + return; + } + } catch (e) { + console.warn('[群聊] IM 连接失败', e); + } + const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected'; if (status === 'connected' || status === 'reconnected') { const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null; if (currentUserId === targetUserId) { - this.subscribeGroupIfNeeded(); - this.setupAllListeners(); - this.loadHistory(true); + await afterReady(); return; - } else { - wx.goEasy.disconnect(); } + wx.goEasy.disconnect(); } this.connectGoEasy(targetUserId); }, connectGoEasy(userId) { - const { currentUser } = this.data; - if (!currentUser) return; + const me = this.getMyChatProfile(); + if (!me.id) return; + const defAvatar = getDefaultAvatarUrl(app); wx.goEasy.connect({ id: userId, data: { - name: currentUser.name, - avatar: resolveAvatarUrl(currentUser.avatar, app), + name: me.name, + avatar: me.avatar || defAvatar, }, onSuccess: () => { - this.subscribeGroupIfNeeded(); - this.setupAllListeners(); - this.loadHistory(true).then(()=>{ - this.markGroupMessageAsRead - + this.subscribeGroupIfNeeded().then(() => { + this.setupAllListeners(); + this.loadHistory(true); + this.markGroupMessageAsRead(); }); }, onFailed: (error) => { if (error.code === 408) { - this.subscribeGroupIfNeeded(); - this.setupAllListeners(); - this.loadHistory(true); + this.subscribeGroupIfNeeded().then(() => { + this.setupAllListeners(); + this.loadHistory(true); + }); } else { wx.showToast({ title: '连接失败,请重试', icon: 'none' }); } @@ -169,11 +345,16 @@ Page({ subscribeGroupIfNeeded() { const { groupId } = this.data; - if (!groupId) return; - wx.goEasy.im.subscribeGroup({ - groupIds: [groupId], - onSuccess: () => {}, - onFailed: () => {} + if (!groupId || !wx.goEasy?.im?.subscribeGroup) return Promise.resolve(); + return new Promise((resolve) => { + wx.goEasy.im.subscribeGroup({ + groupIds: [String(groupId)], + onSuccess: () => resolve(true), + onFailed: (error) => { + console.warn('subscribeGroup failed', error); + resolve(false); + }, + }); }); }, @@ -197,57 +378,95 @@ Page({ onPullDownRefresh() { if (this.data.loadingHistory) return; + if (!this.data.hasMore) { + wx.showToast({ title: '没有更早的消息了', icon: 'none' }); + this.setData({ isRefreshing: false }); + wx.stopPullDownRefresh(); + return; + } this.setData({ isRefreshing: true }); - this.loadHistory(false).finally(() => { this.setData({ isRefreshing: false }); wx.stopPullDownRefresh(); }); + this.loadHistory(false).finally(() => { + this.setData({ isRefreshing: false }); + wx.stopPullDownRefresh(); + }); }, - loadHistory(refresh) { - if (!refresh && !this.data.hasMore) return Promise.resolve(); - return new Promise((resolve) => { - this.setData({ loadingHistory: true }); - const { groupId, lastTimestamp } = this.data; - const ts = refresh ? null : lastTimestamp; - wx.goEasy.im.history({ - type: wx.GoEasy.IM_SCENE.GROUP, - id: groupId, - lastTimestamp: ts, - limit: 20, - onSuccess: (res) => { - let list = res.content || []; - list.sort((a, b) => a.timestamp - b.timestamp); - list.forEach((m, idx) => { - m = this.normalizeMessage(m); - list[idx] = m; - m.formattedTime = formatDate(m.timestamp); - if (idx === 0) m.showTime = true; - else m.showTime = (m.timestamp - list[idx-1].timestamp) / 60000 > 5; - if (!m.senderData) m.senderData = { name: '未知用户', avatar: '' }; - }); - let final = refresh ? list : [...list, ...this.data.messages]; - if (!refresh) { - for (let i = 0; i < final.length; i++) { - if (i === 0) final[i].showTime = true; - else final[i].showTime = (final[i].timestamp - final[i-1].timestamp) / 60000 > 5; - } - } - const ids = new Set(); - const unique = []; - for (const m of final) { if (!ids.has(m.messageId)) { ids.add(m.messageId); unique.push(m); } } - const update = { - messages: unique, - hasMore: list.length >= 20, - lastTimestamp: unique.length ? unique[0].timestamp : lastTimestamp, - loadingHistory: false - }; - if (refresh) update.scrollToView = 'msg-bottom'; - this.setData(update); - resolve(); - }, - onFailed: () => { this.setData({ loadingHistory: false }); resolve(); } - }); + mapHistoryMessage(m, idx, list) { + m = this.normalizeMessage(m); + m.formattedTime = formatDate(m.timestamp); + if (idx === 0) m.showTime = true; + else m.showTime = (m.timestamp - list[idx - 1].timestamp) / 60000 > 5; + if (!m.senderData) m.senderData = { name: '未知用户', avatar: '' }; + return m; + }, + + async fetchGroupHistoryPage(gid, lastTimestamp) { + return fetchHistoryMessages({ + scene: wx.GoEasy.IM_SCENE.GROUP, + id: gid, + lastTimestamp, + limit: 20, }); }, + async loadHistory(refresh) { + if (!refresh && !this.data.hasMore) return; + if (this.data.loadingHistory) return; + if (!this.data.groupId) return; + + this.setData({ loadingHistory: true }); + try { + const ready = await waitChatImReady(app, 12000); + if (!ready) { + wx.showToast({ title: '消息服务未连接,请稍后重试', icon: 'none' }); + return; + } + + await this.subscribeGroupIfNeeded(); + + const ts = refresh + ? null + : getOldestHistoryTimestamp(this.data.messages, this.data.lastTimestamp); + if (!refresh && ts == null) { + this.setData({ hasMore: false }); + wx.showToast({ title: '没有更早的消息了', icon: 'none' }); + return; + } + + let raw = await this.fetchGroupHistoryPage(this.data.groupId, ts); + + if (refresh && raw.length === 0 && isPairGroupId(this.data.groupId) && this.data.orderId) { + const legacyId = `group_${this.data.orderId}`; + if (legacyId !== this.data.groupId) { + raw = await this.fetchGroupHistoryPage(legacyId, ts); + } + } + + const merged = mergeHistoryMessages({ + incoming: raw, + existing: this.data.messages, + refresh, + limit: 20, + mapMessage: (m, idx, list) => this.mapHistoryMessage(m, idx, list), + }); + + const update = { + messages: merged.messages, + hasMore: merged.hasMore, + lastTimestamp: merged.lastTimestamp, + }; + if (refresh) update.scrollToView = 'msg-bottom'; + this.setData(update); + } catch (e) { + console.warn('[群聊] 历史消息加载失败', e); + if (!refresh) { + wx.showToast({ title: '加载历史消息失败', icon: 'none' }); + } + } finally { + this.setData({ loadingHistory: false }); + } + }, + listenNewMsg() { if (this._msgHandler) wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, this._msgHandler); this._msgHandler = (msg) => { @@ -531,7 +750,9 @@ Page({ orderId: order.dingdan_id, jieshao: order.jieshao, jine: order.jine, - beizhu: order.beizhu + beizhu: order.beizhu, + zhuangtai: order.zhuangtai, + nicheng: order.nicheng, }; sendGroupMessage({ msgData: { @@ -555,6 +776,10 @@ Page({ }, onSuccess: (messageId, status) => { that.updateMsg(messageId, status); + that.closeOrderSender(); + if (status === 'success') { + wx.showToast({ title: '订单已发送', icon: 'success' }); + } } }); }, @@ -566,5 +791,24 @@ Page({ this.setData({ detailText: text, showDetailModal: true }); }, - onKeyboardHeightChange(e) { this.setData({ keyboardHeight: e.detail.height }); } + onKeyboardHeightChange(e) { this.setData({ keyboardHeight: e.detail.height }); }, + + onAvatarError(e) { + const def = getDefaultAvatarUrl(app); + const idx = e.currentTarget.dataset.index; + if (idx != null && idx !== '') { + this.setData({ [`messages[${idx}].senderData.avatar`]: def }); + } + }, + + onSelfAvatarError() { + const def = getDefaultAvatarUrl(app); + if (this.data.currentUser) { + this.setData({ 'currentUser.avatar': def }); + } + }, + + tapOrderBar() { + this.openOrderSender(); + }, }); \ No newline at end of file diff --git a/pages/group-chat/group-chat.json b/pages/group-chat/group-chat.json index d5bfa0e..3e7741a 100644 --- a/pages/group-chat/group-chat.json +++ b/pages/group-chat/group-chat.json @@ -1,6 +1,7 @@ { "navigationBarTitleText": "群聊", "usingComponents": { - "order-card": "/components/order-card/order-card" + "order-card": "/components/order-card/order-card", + "order-sender": "/components/order-sender/order-sender" } } \ No newline at end of file diff --git a/pages/group-chat/group-chat.wxml b/pages/group-chat/group-chat.wxml index 2abee15..4e6555a 100644 --- a/pages/group-chat/group-chat.wxml +++ b/pages/group-chat/group-chat.wxml @@ -1,18 +1,28 @@ + + + + 订单 {{orderId}} + ¥{{orderJine}} + + {{orderZhuangtaiText}} + + {{orderJieshao}} + 点击查看历史订单并发送 + + style="top: {{orderId ? '120rpx' : '20rpx'}}; bottom: calc({{bottomSafeHeight}}rpx + {{keyboardHeight}}px + env(safe-area-inset-bottom) + 24rpx);"> 加载更早消息... - + {{item.formattedTime}} - - + {{item.senderData.name || item.senderId}} ID: {{item.payload.orderId}} 内容: {{item.payload.jieshao}} 金额: ¥{{item.payload.jine}} + 备注: {{item.payload.beizhu}} 点击查看详情 - - + - + @@ -88,5 +98,5 @@ - + diff --git a/pages/group-chat/group-chat.wxss b/pages/group-chat/group-chat.wxss index dab11fa..81654bb 100644 --- a/pages/group-chat/group-chat.wxss +++ b/pages/group-chat/group-chat.wxss @@ -1,5 +1,67 @@ /* 使用全局 chat-* 样式 */ +.order-status-bar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 11; + background: #fff; + padding: 16rpx 24rpx; + border-bottom: 1rpx solid #e8e8e8; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04); +} + +.order-status-main { + display: flex; + align-items: center; + justify-content: space-between; +} + +.order-status-id { + font-size: 26rpx; + color: #333; + font-weight: 600; +} + +.order-status-tag { + font-size: 22rpx; + color: #07c160; + background: #e8f8ee; + padding: 4rpx 16rpx; + border-radius: 20rpx; +} + +.order-status-left { + display: flex; + flex-direction: column; + flex: 1; + min-width: 0; +} + +.order-status-price { + font-size: 24rpx; + color: #ff6b00; + margin-top: 4rpx; +} + +.order-status-hint { + font-size: 22rpx; + color: #007aff; + margin-top: 6rpx; + display: block; +} + +.order-status-desc { + font-size: 24rpx; + color: #888; + margin-top: 8rpx; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + /* 发送者昵称样式(群聊特有) */ .sender-info { display: flex; diff --git a/pages/guanshiduan/guanshiduan.js b/pages/guanshiduan/guanshiduan.js new file mode 100644 index 0000000..9674848 --- /dev/null +++ b/pages/guanshiduan/guanshiduan.js @@ -0,0 +1,14 @@ +/** 旧二维码兼容:guanshiduan → 打手中心管事注册,逻辑不变 */ +import { parseSceneOptions } from '../../utils/base-page.js'; + +Page({ + onLoad(options) { + const parsed = parseSceneOptions(options || {}); + const inviteCode = parsed.inviteCode || options.inviteCode || ''; + let url = '/pages/fighter/fighter?registerType=guanshi'; + if (inviteCode) { + url += `&inviteCode=${encodeURIComponent(inviteCode)}`; + } + wx.reLaunch({ url }); + }, +}); diff --git a/pages/guanshiduan/guanshiduan.json b/pages/guanshiduan/guanshiduan.json new file mode 100644 index 0000000..52bc9cd --- /dev/null +++ b/pages/guanshiduan/guanshiduan.json @@ -0,0 +1 @@ +{ "navigationBarTitleText": "加载中", "usingComponents": {} } diff --git a/pages/guanshiduan/guanshiduan.wxml b/pages/guanshiduan/guanshiduan.wxml new file mode 100644 index 0000000..4a671a9 --- /dev/null +++ b/pages/guanshiduan/guanshiduan.wxml @@ -0,0 +1 @@ +正在进入管事注册… diff --git a/pages/guanshiduan/guanshiduan.wxss b/pages/guanshiduan/guanshiduan.wxss new file mode 100644 index 0000000..8082289 --- /dev/null +++ b/pages/guanshiduan/guanshiduan.wxss @@ -0,0 +1 @@ +.tip { display:flex; align-items:center; justify-content:center; min-height:100vh; color:#999; font-size:28rpx; } diff --git a/pages/index/index.js b/pages/index/index.js index 09651b3..79f575d 100644 --- a/pages/index/index.js +++ b/pages/index/index.js @@ -1,385 +1,374 @@ -// pages/shangpin/shangpin.js -const app = getApp(); -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(createPage({ - data: { - ossImageUrl: '', - lunbozhanwei: '', - - shangpingonggao: '', - lunboList: [], - shangpinleixing: [], - shangpinzhuanqu: [], - shangpinliebiao: [], - - selectedLeixingId: null, - filteredData: [], - showGonggaoModal: false, - gonggaoAnim: true, - isLoading: false, - isRefreshing: false, - hasInitialized: false, - - currentTime: '' - }, - - // 扫码解析的店铺ID - shopId: null, - - onLoad(options) { - // 解析二维码参数 - if (options[SCENE_PARAM]) { - try { - const scene = decodeURIComponent(options[SCENE_PARAM]); - this.shopId = scene; - } catch (e) { - console.error('scene解码失败', e); - } - } - - // 等待全局配置加载完成 - this.waitForConfigAndInit(); - // 已登录时才检查弹窗 - const token = getSessionToken(app); - if (token) { - PopupService.checkAndShow(this, 'index'); - } - - }, - - // 页面隐藏时清理弹窗视图 - onHide: function () { - const popupComp = this.selectComponent('#popupNotice'); - if (popupComp && popupComp.cleanup) { - popupComp.cleanup(); - } -}, - - waitForConfigAndInit() { - if (app.globalData.ossImageUrl) { - this.setData({ - ossImageUrl: app.globalData.ossImageUrl, - lunbozhanwei: app.globalData.lunbozhanwei, - currentTime: this.getCurrentTime() - }); - this.initPageData(); - } else { - setTimeout(() => this.waitForConfigAndInit(), 100); - } - }, - - onShow() { - backfillUserProfileCache(app); - if (this.data.ossImageUrl) { - this.setData({ - gonggaoAnim: true, - currentTime: this.getCurrentTime(), - }); - } - this.registerNotificationComponent(); - - if (!getSessionToken(app)) { - this.setData({ - hasInitialized: false, - filteredData: [], - shangpinleixing: [], - shangpinzhuanqu: [], - shangpinliebiao: [], - }); - return; - } - - // 已登录时加载/刷新商品;未登录不自动登录、不跳转 - if (!this.data.isLoading) { - const needLoad = !this.data.hasInitialized - || !this.data.filteredData - || this.data.filteredData.length === 0; - if (needLoad) { - this.initPageData(); - } - } - }, - - getCurrentTime() { - const now = new Date(); - const year = now.getFullYear(); - const month = (now.getMonth() + 1).toString().padStart(2, '0'); - const day = now.getDate().toString().padStart(2, '0'); - const hour = now.getHours().toString().padStart(2, '0'); - const minute = now.getMinutes().toString().padStart(2, '0'); - return `${year}-${month}-${day} ${hour}:${minute}`; - }, - - sortByPaixu(list) { - if (!list || !Array.isArray(list)) return []; - return [...list].sort((a, b) => { - const paixuA = a.paixu || 0; - const paixuB = b.paixu || 0; - if (paixuB !== paixuA) return paixuB - paixuA; - return a.id - b.id; - }); - }, - - /** - * 初始化页面数据(重构后) - * 流程:确保登录 → 如有扫码ID则绑定店铺 → 并行加载公告轮播图和商品数据 - */ - async initPageData() { - this.setData({ isLoading: true }); - - try { - // 1. 确保登录(静默,不跳转) - await ensureLogin({ page: this }); - - // 2. 扫码进入时绑定店铺 - if (this.shopId) { - await bindShop(this.shopId); - } - - // 3. 并行加载公告轮播图和商品数据 - const [gonggaoResult, shopData] = await Promise.all([ - this.loadGonggaoAndLunbo(), - fetchShopGoods() - ]); - - // 4. 处理商品数据 - const sortedLeixing = this.sortByPaixu(shopData.shangpinleixing || []); - const sortedZhuanqu = this.sortByPaixu(shopData.shangpinzhuanqu || []); - const sortedShangpin = this.sortByPaixu(shopData.shangpinliebiao || []); - - // 更新全局缓存 - app.globalData.shangpinleixing = sortedLeixing; - app.globalData.shangpinzhuanqu = sortedZhuanqu; - app.globalData.shangpinliebiao = sortedShangpin; - - this.setData({ - shangpinleixing: sortedLeixing, - shangpinzhuanqu: sortedZhuanqu, - shangpinliebiao: sortedShangpin, - selectedLeixingId: sortedLeixing[0]?.id || null, - isLoading: false, - hasInitialized: true, - }, () => { - this.filterShangpinData(); - }); - - } catch (error) { - console.error('初始化失败:', error); - this.setData({ isLoading: false }); - } - }, - - /** - * 加载公告和轮播图 - */ - loadGonggaoAndLunbo() { - return new Promise((resolve, reject) => { - // 如果全局已有数据,直接使用 - if (app.globalData.shangpingonggao && app.globalData.shangpinlunbo.length > 0) { - this.setData({ - shangpingonggao: app.globalData.shangpingonggao, - lunboList: this.processImageUrls(app.globalData.shangpinlunbo) - }); - resolve(); - return; - } - - wx.request({ - url: app.globalData.apiBaseUrl + '/peizhi/shangpingonggao/', - method: 'POST', - header: { 'content-type': 'application/json' }, - success: (res) => { - if (res.statusCode === 200 && res.data) { - const data = res.data; - app.globalData.shangpingonggao = data.shangpingonggao || ''; - app.globalData.shangpinlunbo = data.shangpinlunbo || []; - this.setData({ - shangpingonggao: app.globalData.shangpingonggao, - lunboList: this.processImageUrls(app.globalData.shangpinlunbo) - }); - resolve(); - } else { - reject(new Error('请求失败')); - } - }, - fail: reject - }); - }); - }, - - processImageUrls(urlList) { - if (!urlList || !Array.isArray(urlList)) return []; - return urlList.map(url => url.startsWith('http') ? url : this.data.ossImageUrl + url); - }, - - /** - * 筛选商品数据 - */ - filterShangpinData() { - const { selectedLeixingId, shangpinzhuanqu, shangpinliebiao } = this.data; - - if (!selectedLeixingId || !shangpinzhuanqu || !shangpinliebiao) { - this.setData({ filteredData: [] }); - return; - } - - const zhuanquByLeixing = new Map(); - shangpinzhuanqu.forEach(zhuanqu => { - if (zhuanqu.leixing_id === selectedLeixingId) { - if (!zhuanquByLeixing.has(selectedLeixingId)) { - zhuanquByLeixing.set(selectedLeixingId, []); - } - zhuanquByLeixing.get(selectedLeixingId).push(zhuanqu); - } - }); - - const shangpinByZhuanqu = new Map(); - shangpinliebiao.forEach(shangpin => { - if (shangpin.leixing_id === selectedLeixingId && shangpin.zhuanqu_id) { - const zhuanquId = shangpin.zhuanqu_id; - if (!shangpinByZhuanqu.has(zhuanquId)) { - shangpinByZhuanqu.set(zhuanquId, []); - } - - const jiage = parseFloat(shangpin.jiage) || 0; - const priceInteger = Math.floor(jiage); - let priceDecimal = '00'; - const decimalPart = (jiage - priceInteger).toFixed(2); - if (decimalPart > 0) { - priceDecimal = decimalPart.toString().split('.')[1]; - } - - shangpinByZhuanqu.get(zhuanquId).push({ - id: shangpin.id, - biaoqian: shangpin.biaoqian, - jiage: jiage, - priceInteger: priceInteger, - priceDecimal: priceDecimal, - tupian_url: shangpin.tupian_url, - duiwai_xiaoliang: shangpin.duiwai_xiaoliang || 0, - paixu: shangpin.paixu || 0 - }); - } - }); - - const filteredData = []; - const currentZhuanquList = zhuanquByLeixing.get(selectedLeixingId) || []; - - currentZhuanquList.forEach(zhuanqu => { - const shangpinList = shangpinByZhuanqu.get(zhuanqu.id) || []; - if (shangpinList.length > 0) { - filteredData.push({ - zhuanqu: { - id: zhuanqu.id, - mingzi: zhuanqu.mingzi, - paixu: zhuanqu.paixu || 0 - }, - shangpinList: shangpinList - }); - } - }); - - const sortedFilteredData = filteredData.sort((a, b) => { - const paixuA = a.zhuanqu.paixu || 0; - const paixuB = b.zhuanqu.paixu || 0; - if (paixuB !== paixuA) return paixuB - paixuA; - return a.zhuanqu.id - b.zhuanqu.id; - }); - - this.setData({ filteredData: sortedFilteredData }); - }, - - selectLeixing(e) { - const leixingId = e.currentTarget.dataset.id; - if (this.data.selectedLeixingId === leixingId) return; - this.setData({ selectedLeixingId: leixingId }, () => { - this.filterShangpinData(); - }); - }, - - showGonggaoDetail() { - this.setData({ - showGonggaoModal: true, - _modalLeaving: false, - gonggaoAnim: false, - currentTime: this.getCurrentTime() - }); - }, - - hideGonggaoDetail() { - this.setData({ - showGonggaoModal: false, - _modalLeaving: true, - gonggaoAnim: true - }); - setTimeout(() => this.setData({ _modalLeaving: false }), 300); - }, - - /** - * 刷新所有数据 - * 只刷新商品数据 - */ - async refreshAllData() { - if (this.data.isRefreshing) return; - this.setData({ isRefreshing: true }); - wx.showLoading({ title: '刷新中...', mask: true }); - - try { - // 重新获取商品数据 - const shopData = await fetchShopGoods(); - - const sortedLeixing = this.sortByPaixu(shopData.shangpinleixing || []); - const sortedZhuanqu = this.sortByPaixu(shopData.shangpinzhuanqu || []); - const sortedShangpin = this.sortByPaixu(shopData.shangpinliebiao || []); - - app.globalData.shangpinleixing = sortedLeixing; - app.globalData.shangpinzhuanqu = sortedZhuanqu; - app.globalData.shangpinliebiao = sortedShangpin; - - this.setData({ - shangpinleixing: sortedLeixing, - shangpinzhuanqu: sortedZhuanqu, - shangpinliebiao: sortedShangpin, - selectedLeixingId: sortedLeixing[0]?.id || null, - isRefreshing: false - }, () => { - this.filterShangpinData(); - }); - - wx.hideLoading(); - wx.showToast({ title: '刷新成功', icon: 'success', duration: 1500 }); - } catch (error) { - wx.hideLoading(); - this.setData({ isRefreshing: false }); - } - }, - - goToDetail(e) { - const shangpinId = e.currentTarget.dataset.id; - if (!shangpinId) return; - wx.navigateTo({ - url: `/pages/product-detail/product-detail?id=${shangpinId}` - }); - }, - - onReachBottom() {}, - - onShareAppMessage() { - return { - title: '阿龙电竞', - path: '/pages/shangpin/shangpin' - }; - }, - - onUnload() { - this.setData({ gonggaoAnim: false }); - } -})) +// pages/shangpin/shangpin.js +const app = getApp(); +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'; +import { fetchGonggaoLunbo, isGonggaoCacheValid } from '../../utils/display-config'; + +// 扫码场景参数名 +const SCENE_PARAM = 'scene'; + +Page(createPage({ + data: { + ossImageUrl: '', + lunbozhanwei: '', + + shangpingonggao: '', + lunboList: [], + shangpinleixing: [], + shangpinzhuanqu: [], + shangpinliebiao: [], + + selectedLeixingId: null, + filteredData: [], + showGonggaoModal: false, + gonggaoAnim: true, + isLoading: false, + isRefreshing: false, + hasInitialized: false, + + currentTime: '' + }, + + // 扫码解析的店铺ID + shopId: null, + + onLoad(options) { + // 解析二维码参数 + if (options[SCENE_PARAM]) { + try { + const scene = decodeURIComponent(options[SCENE_PARAM]); + this.shopId = scene; + } catch (e) { + console.error('scene解码失败', e); + } + } + + // 等待全局配置加载完成 + this.waitForConfigAndInit(); + // 已登录时才检查弹窗 + const token = getSessionToken(app); + if (token) { + PopupService.checkAndShow(this, 'index'); + } + + }, + + // 页面隐藏时清理弹窗视图 + onHide: function () { + const popupComp = this.selectComponent('#popupNotice'); + if (popupComp && popupComp.cleanup) { + popupComp.cleanup(); + } +}, + + waitForConfigAndInit() { + if (app.globalData.ossImageUrl) { + this.setData({ + ossImageUrl: app.globalData.ossImageUrl, + lunbozhanwei: app.globalData.lunbozhanwei, + currentTime: this.getCurrentTime() + }); + this.initPageData(); + } else { + setTimeout(() => this.waitForConfigAndInit(), 100); + } + }, + + onShow() { + backfillUserProfileCache(app); + if (this.data.ossImageUrl) { + this.setData({ + gonggaoAnim: true, + currentTime: this.getCurrentTime(), + }); + } + this.registerNotificationComponent(); + + if (!getSessionToken(app)) { + this.setData({ + hasInitialized: false, + filteredData: [], + shangpinleixing: [], + shangpinzhuanqu: [], + shangpinliebiao: [], + }); + return; + } + + // 已登录时加载/刷新商品;未登录不自动登录、不跳转 + if (!this.data.isLoading) { + const needLoad = !this.data.hasInitialized + || !this.data.filteredData + || this.data.filteredData.length === 0; + if (needLoad) { + this.initPageData(); + } + } + }, + + getCurrentTime() { + const now = new Date(); + const year = now.getFullYear(); + const month = (now.getMonth() + 1).toString().padStart(2, '0'); + const day = now.getDate().toString().padStart(2, '0'); + const hour = now.getHours().toString().padStart(2, '0'); + const minute = now.getMinutes().toString().padStart(2, '0'); + return `${year}-${month}-${day} ${hour}:${minute}`; + }, + + sortByPaixu(list) { + if (!list || !Array.isArray(list)) return []; + return [...list].sort((a, b) => { + const paixuA = a.paixu || 0; + const paixuB = b.paixu || 0; + if (paixuB !== paixuA) return paixuB - paixuA; + return a.id - b.id; + }); + }, + + /** + * 初始化页面数据(重构后) + * 流程:确保登录 → 如有扫码ID则绑定店铺 → 并行加载公告轮播图和商品数据 + */ + async initPageData() { + this.setData({ isLoading: true }); + + try { + // 1. 确保登录(静默,不跳转) + await ensureLogin({ page: this }); + + // 2. 扫码进入时绑定店铺 + if (this.shopId) { + await bindShop(this.shopId); + } + + // 3. 并行加载公告轮播图和商品数据 + const [gonggaoResult, shopData] = await Promise.all([ + this.loadGonggaoAndLunbo(), + fetchShopGoods() + ]); + + // 4. 处理商品数据 + const sortedLeixing = this.sortByPaixu(shopData.shangpinleixing || []); + const sortedZhuanqu = this.sortByPaixu(shopData.shangpinzhuanqu || []); + const sortedShangpin = this.sortByPaixu(shopData.shangpinliebiao || []); + + // 更新全局缓存 + app.globalData.shangpinleixing = sortedLeixing; + app.globalData.shangpinzhuanqu = sortedZhuanqu; + app.globalData.shangpinliebiao = sortedShangpin; + + this.setData({ + shangpinleixing: sortedLeixing, + shangpinzhuanqu: sortedZhuanqu, + shangpinliebiao: sortedShangpin, + selectedLeixingId: sortedLeixing[0]?.id || null, + isLoading: false, + hasInitialized: true, + }, () => { + this.filterShangpinData(); + }); + + } catch (error) { + console.error('初始化失败:', error); + this.setData({ isLoading: false }); + } + }, + + /** + * 加载公告和轮播图 + */ + loadGonggaoAndLunbo() { + return new Promise((resolve, reject) => { + if (isGonggaoCacheValid(app)) { + this.setData({ + shangpingonggao: app.globalData.shangpingonggao, + lunboList: this.processImageUrls(app.globalData.shangpinlunbo) + }); + resolve(); + return; + } + + fetchGonggaoLunbo(app, 'order_pool') + .then((data) => { + this.setData({ + shangpingonggao: data.shangpingonggao || '', + lunboList: this.processImageUrls(data.shangpinlunbo || []) + }); + resolve(); + }) + .catch(reject); + }); + }, + + processImageUrls(urlList) { + if (!urlList || !Array.isArray(urlList)) return []; + return urlList.map(url => url.startsWith('http') ? url : this.data.ossImageUrl + url); + }, + + /** + * 筛选商品数据 + */ + filterShangpinData() { + const { selectedLeixingId, shangpinzhuanqu, shangpinliebiao } = this.data; + + if (!selectedLeixingId || !shangpinzhuanqu || !shangpinliebiao) { + this.setData({ filteredData: [] }); + return; + } + + const zhuanquByLeixing = new Map(); + shangpinzhuanqu.forEach(zhuanqu => { + if (zhuanqu.leixing_id === selectedLeixingId) { + if (!zhuanquByLeixing.has(selectedLeixingId)) { + zhuanquByLeixing.set(selectedLeixingId, []); + } + zhuanquByLeixing.get(selectedLeixingId).push(zhuanqu); + } + }); + + const shangpinByZhuanqu = new Map(); + shangpinliebiao.forEach(shangpin => { + if (shangpin.leixing_id === selectedLeixingId && shangpin.zhuanqu_id) { + const zhuanquId = shangpin.zhuanqu_id; + if (!shangpinByZhuanqu.has(zhuanquId)) { + shangpinByZhuanqu.set(zhuanquId, []); + } + + const jiage = parseFloat(shangpin.jiage) || 0; + const priceInteger = Math.floor(jiage); + let priceDecimal = '00'; + const decimalPart = (jiage - priceInteger).toFixed(2); + if (decimalPart > 0) { + priceDecimal = decimalPart.toString().split('.')[1]; + } + + shangpinByZhuanqu.get(zhuanquId).push({ + id: shangpin.id, + biaoqian: shangpin.biaoqian, + jiage: jiage, + priceInteger: priceInteger, + priceDecimal: priceDecimal, + tupian_url: shangpin.tupian_url, + duiwai_xiaoliang: shangpin.duiwai_xiaoliang || 0, + paixu: shangpin.paixu || 0 + }); + } + }); + + const filteredData = []; + const currentZhuanquList = zhuanquByLeixing.get(selectedLeixingId) || []; + + currentZhuanquList.forEach(zhuanqu => { + const shangpinList = shangpinByZhuanqu.get(zhuanqu.id) || []; + if (shangpinList.length > 0) { + filteredData.push({ + zhuanqu: { + id: zhuanqu.id, + mingzi: zhuanqu.mingzi, + paixu: zhuanqu.paixu || 0 + }, + shangpinList: shangpinList + }); + } + }); + + const sortedFilteredData = filteredData.sort((a, b) => { + const paixuA = a.zhuanqu.paixu || 0; + const paixuB = b.zhuanqu.paixu || 0; + if (paixuB !== paixuA) return paixuB - paixuA; + return a.zhuanqu.id - b.zhuanqu.id; + }); + + this.setData({ filteredData: sortedFilteredData }); + }, + + selectLeixing(e) { + const leixingId = e.currentTarget.dataset.id; + if (this.data.selectedLeixingId === leixingId) return; + this.setData({ selectedLeixingId: leixingId }, () => { + this.filterShangpinData(); + }); + }, + + showGonggaoDetail() { + this.setData({ + showGonggaoModal: true, + _modalLeaving: false, + gonggaoAnim: false, + currentTime: this.getCurrentTime() + }); + }, + + hideGonggaoDetail() { + this.setData({ + showGonggaoModal: false, + _modalLeaving: true, + gonggaoAnim: true + }); + setTimeout(() => this.setData({ _modalLeaving: false }), 300); + }, + + /** + * 刷新所有数据 + * 只刷新商品数据 + */ + async refreshAllData() { + if (this.data.isRefreshing) return; + this.setData({ isRefreshing: true }); + wx.showLoading({ title: '刷新中...', mask: true }); + + try { + // 重新获取商品数据 + const shopData = await fetchShopGoods(); + + const sortedLeixing = this.sortByPaixu(shopData.shangpinleixing || []); + const sortedZhuanqu = this.sortByPaixu(shopData.shangpinzhuanqu || []); + const sortedShangpin = this.sortByPaixu(shopData.shangpinliebiao || []); + + app.globalData.shangpinleixing = sortedLeixing; + app.globalData.shangpinzhuanqu = sortedZhuanqu; + app.globalData.shangpinliebiao = sortedShangpin; + + this.setData({ + shangpinleixing: sortedLeixing, + shangpinzhuanqu: sortedZhuanqu, + shangpinliebiao: sortedShangpin, + selectedLeixingId: sortedLeixing[0]?.id || null, + isRefreshing: false + }, () => { + this.filterShangpinData(); + }); + + wx.hideLoading(); + wx.showToast({ title: '刷新成功', icon: 'success', duration: 1500 }); + } catch (error) { + wx.hideLoading(); + this.setData({ isRefreshing: false }); + } + }, + + goToDetail(e) { + const shangpinId = e.currentTarget.dataset.id; + if (!shangpinId) return; + wx.navigateTo({ + url: `/pages/product-detail/product-detail?id=${shangpinId}` + }); + }, + + onReachBottom() {}, + + onShareAppMessage() { + return { + title: '阿龙电竞', + path: '/pages/shangpin/shangpin' + }; + }, + + onUnload() { + this.setData({ gonggaoAnim: false }); + } +})) diff --git a/pages/index/index.wxml b/pages/index/index.wxml index f96faf4..9251efd 100644 --- a/pages/index/index.wxml +++ b/pages/index/index.wxml @@ -18,7 +18,7 @@ class="lunbo-swiper" indicator-dots="{{lunboList.length > 1}}" indicator-color="rgba(255,255,255,0.6)" - indicator-active-color="#a855f7" + indicator-active-color="#ffcc00" autoplay="{{lunboList.length > 1}}" interval="3000" circular diff --git a/pages/index/index.wxss b/pages/index/index.wxss index 2121cf9..fbcc9aa 100644 --- a/pages/index/index.wxss +++ b/pages/index/index.wxss @@ -12,7 +12,7 @@ border-radius: 40rpx; background: linear-gradient(135deg, rgba(220, 255, 220, 0.85) 0%, - rgba(237, 233, 254, 0.85) 100%); + rgba(255, 255, 200, 0.85) 100%); backdrop-filter: blur(10px); display: flex; align-items: center; @@ -120,7 +120,7 @@ position: absolute; width: 12rpx; height: 12rpx; - background: #a855f7; + background: #ffcc00; border-radius: 50%; top: 15rpx; right: 15rpx; @@ -195,8 +195,8 @@ } .leixing-active .leixing-glow { - border-color: #a855f7; - box-shadow: 0 0 20rpx rgba(168, 85, 247, 0.5); + border-color: #ffcc00; + box-shadow: 0 0 20rpx rgba(255, 204, 0, 0.5); } .leixing-active .leixing-image-box { @@ -239,7 +239,7 @@ .zhuanqu-icon-image { width: 100%; height: 100%; - filter: drop-shadow(0 2rpx 4rpx rgba(168, 85, 247, 0.3)); + filter: drop-shadow(0 2rpx 4rpx rgba(255, 204, 0, 0.3)); } .zhuanqu-sparkle { @@ -249,11 +249,11 @@ @keyframes sparkle { 0%, 100% { transform: scale(1) rotate(0deg); - filter: drop-shadow(0 2rpx 4rpx rgba(168, 85, 247, 0.3)); + filter: drop-shadow(0 2rpx 4rpx rgba(255, 204, 0, 0.3)); } 50% { transform: scale(1.1) rotate(10deg); - filter: drop-shadow(0 4rpx 8rpx rgba(168, 85, 247, 0.6)); + filter: drop-shadow(0 4rpx 8rpx rgba(255, 204, 0, 0.6)); } } @@ -271,7 +271,7 @@ bottom: -8rpx; width: 60rpx; height: 4rpx; - background: linear-gradient(90deg, #a855f7, transparent); + background: linear-gradient(90deg, #ffcc00, transparent); border-radius: 2rpx; } diff --git a/pages/invite-manager/invite-manager.js b/pages/invite-manager/invite-manager.js index 8fad7f7..b9ecaaf 100644 --- a/pages/invite-manager/invite-manager.js +++ b/pages/invite-manager/invite-manager.js @@ -1,21 +1,32 @@ -// pages/zuzhanghaibao/zuzhanghaibao.js +// pages/invite-manager/invite-manager.js — 组长推广海报 import request from '../../utils/request.js'; +import { + getFullImageUrl, + loadPosterPageConfig, + posterInviteCacheKey, + posterQrCacheKey, +} from '../../utils/poster-page.js'; const app = getApp(); Page({ data: { - qrcodeUrl: '', // 二维码完整URL - yaoqingma: '', // 组长邀请码字符串 + qrcodeUrl: '', + yaoqingma: '', isLoading: false, showPosterCanvas: false, - posterImageTemp: '', // 生成的海报临时路径 - avatarUrl: '', // 用户头像完整URL + posterImageTemp: '', + avatarUrl: '', uid: '', nickName: '', + posterBgUrl: '', }, - onLoad() { + async onLoad() { + const { bgUrl, qrCacheKey } = await loadPosterPageConfig('zuzhang', app); + this._qrCacheKey = qrCacheKey; + this._inviteCacheKey = posterInviteCacheKey(); + this.setData({ posterBgUrl: bgUrl }); this.loadFromCache(); this.loadUserInfo(); }, @@ -29,126 +40,88 @@ Page({ if (notificationComp && notificationComp.showNotification) { app.globalData.globalNotification = { show: (data) => notificationComp.showNotification(data), - hide: () => notificationComp.hideNotification() + hide: () => notificationComp.hideNotification(), }; } }, - /** - * 从缓存加载二维码URL和邀请码 - * 缓存键名使用独特拼音,避免与管事页面冲突 - */ loadFromCache() { try { - const relativeUrl = wx.getStorageSync('zuzhang_haibao_url'); + const key = this._qrCacheKey || posterQrCacheKey('zuzhang'); + const relativeUrl = wx.getStorageSync(key); if (relativeUrl) { - const fullUrl = this.getFullImageUrl(relativeUrl); - this.setData({ qrcodeUrl: fullUrl }); - } - const yaoqingma = wx.getStorageSync('zuzhang_yaoqingma'); - if (yaoqingma) { - this.setData({ yaoqingma }); + this.setData({ qrcodeUrl: getFullImageUrl(relativeUrl, app) }); } + const inviteKey = this._inviteCacheKey || posterInviteCacheKey(); + const yaoqingma = wx.getStorageSync(inviteKey); + if (yaoqingma) this.setData({ yaoqingma }); } catch (error) { console.error('读取缓存失败', error); } }, - /** - * 加载用户头像、昵称、UID - */ loadUserInfo() { const touxiang = wx.getStorageSync('touxiang'); const ossUrl = app.globalData.ossImageUrl || ''; const defaultAvatar = ossUrl + (app.globalData.morentouxiang || 'avatar/default.jpg'); let avatarUrl = defaultAvatar; if (touxiang && typeof touxiang === 'string' && touxiang.trim() !== '') { - avatarUrl = this.getFullImageUrl(touxiang); + avatarUrl = getFullImageUrl(touxiang, app); } const uid = wx.getStorageSync('uid') || ''; - // 组长昵称可能从全局变量或缓存获取 - let nickName = app.globalData.nicheng || wx.getStorageSync('nicheng') || '组长'; + const nickName = app.globalData.nicheng || wx.getStorageSync('nicheng') || '组长'; this.setData({ avatarUrl, uid, nickName }); }, - /** - * 拼接完整图片URL - */ - getFullImageUrl(url) { - if (!url) return ''; - const ossUrl = app.globalData.ossImageUrl || ''; - return url.startsWith('http') ? url : ossUrl + url; - }, - - /** - * 刷新/获取二维码和邀请码 - * 调用 /peizhi/zuzhanghb 接口 - */ refreshQRCode() { if (this.data.isLoading) return; this.setData({ isLoading: true }); - - request({ - url: '/peizhi/zuzhanghb', - method: 'POST', - }) - .then(res => { + request({ url: '/peizhi/zuzhanghb', method: 'POST' }) + .then((res) => { if (res.data && res.data.code === 0) { - const { url: relativeUrl, yaoqingma } = res.data.data; // 假设返回字段为 url 和 yaoqingma + const { url: relativeUrl, yaoqingma } = res.data.data || {}; if (relativeUrl) { - wx.setStorageSync('zuzhang_haibao_url', relativeUrl); - const fullUrl = this.getFullImageUrl(relativeUrl); - this.setData({ qrcodeUrl: fullUrl }); + const key = this._qrCacheKey || posterQrCacheKey('zuzhang'); + wx.setStorageSync(key, relativeUrl); + this.setData({ qrcodeUrl: getFullImageUrl(relativeUrl, app) }); } if (yaoqingma) { - wx.setStorageSync('zuzhang_yaoqingma', yaoqingma); + const inviteKey = this._inviteCacheKey || posterInviteCacheKey(); + wx.setStorageSync(inviteKey, yaoqingma); this.setData({ yaoqingma }); } wx.showToast({ title: '获取成功', icon: 'success' }); } else { - wx.showToast({ title: res.data.msg || '获取失败', icon: 'none' }); + wx.showToast({ title: res.data.msg || res.data.message || '获取失败', icon: 'none' }); } }) - .catch(err => { + .catch((err) => { console.error('获取组长海报失败', err); wx.showToast({ title: '网络错误', icon: 'none' }); }) - .finally(() => { - this.setData({ isLoading: false }); - }); + .finally(() => this.setData({ isLoading: false })); }, - /** - * 保存二维码到相册(先下载网络图片) - */ saveToAlbum() { if (!this.data.qrcodeUrl) { wx.showToast({ title: '暂无二维码', icon: 'none' }); return; } - wx.showLoading({ title: '保存中...', mask: true }); wx.downloadFile({ url: this.data.qrcodeUrl, success: (res) => { wx.hideLoading(); - if (res.statusCode === 200) { - this.downloadAndSave(res.tempFilePath); - } else { - wx.showToast({ title: '下载失败', icon: 'none' }); - } + if (res.statusCode === 200) this.downloadAndSave(res.tempFilePath); + else wx.showToast({ title: '下载失败', icon: 'none' }); }, - fail: (err) => { + fail: () => { wx.hideLoading(); - console.error('下载二维码失败', err); wx.showToast({ title: '下载失败', icon: 'none' }); - } + }, }); }, - /** - * 复制邀请码到剪贴板 - */ copyInviteCode() { if (!this.data.yaoqingma) { wx.showToast({ title: '暂无邀请码', icon: 'none' }); @@ -156,145 +129,78 @@ Page({ } wx.setClipboardData({ data: this.data.yaoqingma, - success: () => { - wx.showToast({ title: '复制成功', icon: 'success' }); - }, - fail: () => { - wx.showToast({ title: '复制失败', icon: 'none' }); - } + success: () => wx.showToast({ title: '复制成功', icon: 'success' }), }); }, - /** - * 生成专属海报(包含头像、二维码、昵称、标语) - */ generateMyPoster() { if (!this.data.qrcodeUrl) { wx.showToast({ title: '请先生成二维码', icon: 'none' }); return; } - wx.showLoading({ title: '生成海报中...', mask: true }); - - const ossUrl = app.globalData.ossImageUrl || ''; - // 🔥 组长海报背景图路径:beijing/zuzhangbeijing.jpg - const bgUrl = ossUrl + 'beijing/zuzhangbeijing.jpg'; - const urls = [bgUrl, this.data.qrcodeUrl, this.data.avatarUrl]; - - // 并行加载所有图片 - const promises = urls.map(url => this.loadImage(url)); - Promise.all(promises) + const bgUrl = this.data.posterBgUrl; + Promise.all([bgUrl, this.data.qrcodeUrl, this.data.avatarUrl].map((u) => this.loadImage(u))) .then((images) => { wx.hideLoading(); this.setData({ showPosterCanvas: true }, () => { - // 确保 canvas 已渲染 - setTimeout(() => { - this.drawPoster(images[0], images[1], images[2]); - }, 200); + setTimeout(() => this.drawPoster(images[0], images[1], images[2]), 200); }); }) - .catch((err) => { + .catch(() => { wx.hideLoading(); wx.showToast({ title: '图片加载失败', icon: 'none' }); - console.error('图片加载失败', err); }); }, - /** - * 加载图片为本地路径(用于 canvas 绘制) - */ loadImage(url) { return new Promise((resolve, reject) => { - wx.getImageInfo({ - src: url, - success: (res) => resolve(res.path), - fail: reject - }); + wx.getImageInfo({ src: url, success: (res) => resolve(res.path), fail: reject }); }); }, - /** - * 绘制海报 - * @param {string} bgPath 背景图本地路径 - * @param {string} qrPath 二维码本地路径 - * @param {string} avatarPath 头像本地路径 - */ drawPoster(bgPath, qrPath, avatarPath) { const ctx = wx.createCanvasContext('posterCanvas', this); - const query = wx.createSelectorQuery().in(this); - query.select('.poster-canvas').boundingClientRect(rect => { + wx.createSelectorQuery().in(this).select('.poster-canvas').boundingClientRect((rect) => { if (!rect) { wx.showToast({ title: '画布获取失败', icon: 'none' }); return; } const canvasWidth = rect.width; const canvasHeight = rect.height; - - // 1. 绘制背景 ctx.drawImage(bgPath, 0, 0, canvasWidth, canvasHeight); - - // 🔥【位置调整】二维码向右上角移动一丢丢 (增加 margin 从 5 到 15) const qrSize = 100; - const margin = 15; // 原来 5,现在 15,向右下各移一点,但看起来更协调 + const margin = 15; const qrX = canvasWidth - qrSize - margin; const qrY = canvasHeight - qrSize - margin; ctx.drawImage(qrPath, qrX, qrY, qrSize, qrSize); - - // 🔥【位置调整】头像也向右上移动一丢丢 (原来 margin 5,现在 margin 15) const avatarSize = 60; - const avatarMargin = 15; - const avatarX = avatarMargin + 10; // 原来 margin 5+20,现在 15+20,相当于右移10 - const avatarY = canvasHeight - avatarSize - avatarMargin; - - // 圆形头像裁剪 + const avatarX = margin + 10; + const avatarY = canvasHeight - avatarSize - margin; ctx.save(); ctx.beginPath(); - ctx.arc(avatarX + avatarSize/2, avatarY + avatarSize/2, avatarSize/2, 0, 2 * Math.PI); + ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, 2 * Math.PI); ctx.clip(); ctx.drawImage(avatarPath, avatarX, avatarY, avatarSize, avatarSize); ctx.restore(); - - // 绘制昵称(右侧) ctx.setFontSize(16); - ctx.setFillStyle('#333333'); // 根据背景图颜色可调整 + ctx.setFillStyle('#333333'); ctx.setTextAlign('left'); - let nick = this.data.nickName || '组长'; - if (nick.length > 3) { - nick = nick.substring(0, 3) + '...'; - } - const nameText = nick; - const textX = avatarX + avatarSize + 8; - const textY = avatarY + avatarSize/2 + 6; - ctx.fillText(nameText, textX, textY); - - // 绘制邀请语 + if (nick.length > 3) nick = nick.substring(0, 3) + '...'; + ctx.fillText(nick, avatarX + avatarSize + 8, avatarY + avatarSize / 2 + 6); ctx.setFontSize(18); - ctx.setFillStyle('#9333ea'); // 金黄色,呼应组长主题 - ctx.setTextAlign('left'); - const slogan = '加入我的团队 ✦'; - const sloganX = avatarX; - const sloganY = avatarY - 12; - ctx.fillText(slogan, sloganX, sloganY); - - // 异步绘制 + ctx.setFillStyle('#ffaa00'); + ctx.fillText('加入我的团队 ✦', avatarX, avatarY - 12); ctx.draw(false, () => { wx.canvasToTempFilePath({ canvasId: 'posterCanvas', - success: (res) => { - this.setData({ posterImageTemp: res.tempFilePath }); - }, - fail: (err) => { - console.error('生成海报临时文件失败', err); - } + success: (res) => this.setData({ posterImageTemp: res.tempFilePath }), }, this); }); }).exec(); }, - /** - * 保存生成的海报 - */ savePoster() { if (!this.data.posterImageTemp) { wx.showToast({ title: '海报尚未生成', icon: 'none' }); @@ -303,54 +209,37 @@ Page({ this.downloadAndSave(this.data.posterImageTemp); }, - /** - * 隐藏海报画布,返回主界面 - */ hidePoster() { this.setData({ showPosterCanvas: false, posterImageTemp: '' }); }, - /** - * 通用保存图片到相册(带权限检查) - */ downloadAndSave(filePath) { wx.getSetting({ success: (res) => { if (!res.authSetting['scope.writePhotosAlbum']) { wx.authorize({ scope: 'scope.writePhotosAlbum', - success: () => { - this._saveImage(filePath); - }, + success: () => this._saveImage(filePath), fail: () => { wx.showModal({ title: '提示', content: '需要您授权保存到相册', - success: (modalRes) => { - if (modalRes.confirm) { - wx.openSetting(); - } - } + success: (m) => { if (m.confirm) wx.openSetting(); }, }); - } + }, }); } else { this._saveImage(filePath); } - } + }, }); }, _saveImage(filePath) { wx.saveImageToPhotosAlbum({ - filePath: filePath, - success: () => { - wx.showToast({ title: '保存成功', icon: 'success' }); - }, - fail: (err) => { - console.error('保存失败', err); - wx.showToast({ title: '保存失败', icon: 'none' }); - } + filePath, + success: () => wx.showToast({ title: '保存成功', icon: 'success' }), + fail: () => wx.showToast({ title: '保存失败', icon: 'none' }), }); }, -}); \ No newline at end of file +}); diff --git a/pages/invite-manager/invite-manager.wxss b/pages/invite-manager/invite-manager.wxss index 28b59f7..b271fb5 100644 --- a/pages/invite-manager/invite-manager.wxss +++ b/pages/invite-manager/invite-manager.wxss @@ -15,8 +15,8 @@ page { width: 100%; height: 100%; background-image: - linear-gradient(rgba(168, 85, 247, 0.05) 1px, transparent 1px), - linear-gradient(90deg, rgba(168, 85, 247, 0.05) 1px, transparent 1px); + linear-gradient(rgba(255, 200, 0, 0.05) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 200, 0, 0.05) 1px, transparent 1px); background-size: 50rpx 50rpx; pointer-events: none; z-index: 0; @@ -41,14 +41,14 @@ page { left: 0; width: 100%; height: 2rpx; - background: linear-gradient(90deg, transparent, #9333ea, transparent); + background: linear-gradient(90deg, transparent, #ffaa00, transparent); } .line-v { right: 50rpx; top: 100rpx; width: 2rpx; height: 300rpx; - background: linear-gradient(180deg, transparent, #9333ea, transparent); + background: linear-gradient(180deg, transparent, #ffaa00, transparent); } .content { @@ -71,14 +71,14 @@ page { font-size: 44rpx; font-weight: bold; color: #fff; - text-shadow: 0 0 30rpx #9333ea; + text-shadow: 0 0 30rpx #ffaa00; letter-spacing: 2rpx; display: block; margin-bottom: 20rpx; } .subtitle { font-size: 28rpx; - color: #c4b5fd; + color: #ffd966; opacity: 0.9; letter-spacing: 1rpx; } @@ -96,11 +96,11 @@ page { width: 400rpx; height: 400rpx; background: rgba(20, 20, 20, 0.8); - border: 4rpx solid #9333ea; + border: 4rpx solid #ffaa00; display: flex; align-items: center; justify-content: center; - box-shadow: 0 0 80rpx rgba(147, 51, 234, 0.4); + box-shadow: 0 0 80rpx rgba(255, 170, 0, 0.4); animation: qrcodePulse 2s infinite alternate; } .qrcode-img { @@ -114,14 +114,14 @@ page { left: 0; width: 100%; height: 100%; - background: radial-gradient(circle at 30% 30%, rgba(147, 51, 234, 0.2), transparent 70%); + background: radial-gradient(circle at 30% 30%, rgba(255, 170, 0, 0.2), transparent 70%); pointer-events: none; } .empty-qrcode { width: 400rpx; height: 400rpx; background: rgba(20, 20, 20, 0.8); - border: 4rpx dashed #9333ea; + border: 4rpx dashed #ffaa00; display: flex; flex-direction: column; align-items: center; @@ -129,13 +129,13 @@ page { } .empty-icon { font-size: 80rpx; - color: #9333ea; + color: #ffaa00; margin-bottom: 20rpx; opacity: 0.5; } .empty-text { font-size: 26rpx; - color: #c4b5fd; + color: #ffd966; text-align: center; padding: 0 40rpx; } @@ -148,14 +148,14 @@ page { background: rgba(0, 0, 0, 0.5); padding: 20rpx 30rpx; border-radius: 60rpx; - border: 2rpx solid #9333ea; + border: 2rpx solid #ffaa00; margin-bottom: 30rpx; width: 90%; box-sizing: border-box; } .code-label { font-size: 28rpx; - color: #c4b5fd; + color: #ffd966; margin-right: 10rpx; } .code-value { @@ -169,23 +169,23 @@ page { .copy-btn { display: flex; align-items: center; - background: rgba(147, 51, 234, 0.2); + background: rgba(255, 170, 0, 0.2); padding: 10rpx 20rpx; border-radius: 40rpx; margin-left: 20rpx; - border: 2rpx solid #9333ea; + border: 2rpx solid #ffaa00; } .copy-icon { font-size: 28rpx; - color: #9333ea; + color: #ffaa00; margin-right: 6rpx; } .copy-text { font-size: 24rpx; - color: #9333ea; + color: #ffaa00; } .copy-btn:active { - background: rgba(147, 51, 234, 0.4); + background: rgba(255, 170, 0, 0.4); transform: scale(0.96); } @@ -206,7 +206,7 @@ page { width: 220rpx; height: 80rpx; background: rgba(20, 20, 20, 0.8); - border: 2rpx solid #9333ea; + border: 2rpx solid #ffaa00; border-right: none; border-bottom: none; clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%); @@ -219,22 +219,22 @@ page { } .btn:active { transform: scale(0.96); - box-shadow: 0 0 40rpx #9333ea; + box-shadow: 0 0 40rpx #ffaa00; } .save-btn { - border-color: #9333ea; + border-color: #ffaa00; } .refresh-btn { - border-color: #9333ea; + border-color: #ffaa00; } .refresh-btn .btn-icon { - color: #9333ea; + color: #ffaa00; } .poster-btn { - border-color: #9333ea; + border-color: #ffaa00; } .poster-btn .btn-icon { - color: #9333ea; + color: #ffaa00; } .loading-btn { opacity: 0.7; @@ -254,7 +254,7 @@ page { width: 30rpx; height: 30rpx; border: 4rpx solid rgba(255,255,255,0.2); - border-top: 4rpx solid #9333ea; + border-top: 4rpx solid #ffaa00; border-radius: 50%; animation: spin 1s linear infinite; margin-right: 10rpx; @@ -272,8 +272,8 @@ page { width: 600rpx; height: 900rpx; background: #1a1f2e; - border: 4rpx solid #9333ea; - box-shadow: 0 0 60rpx #9333ea; + border: 4rpx solid #ffaa00; + box-shadow: 0 0 60rpx #ffaa00; } .poster-actions { display: flex; @@ -282,12 +282,12 @@ page { justify-content: center; } .save-poster-btn { - background: linear-gradient(45deg, #6d28d9, #9333ea); - border-color: #9333ea; + background: linear-gradient(45deg, #cc8400, #ffaa00); + border-color: #ffaa00; } .back-btn { background: rgba(20, 20, 20, 0.8); - border-color: #9333ea; + border-color: #ffaa00; } /* 使用说明 */ @@ -295,19 +295,19 @@ page { margin-top: 30rpx; padding: 20rpx 40rpx; background: rgba(20, 20, 20, 0.5); - border-left: 4rpx solid #9333ea; + border-left: 4rpx solid #ffaa00; clip-path: polygon(0 0, 100% 0, 98% 100%, 0 100%); } .instruction-text { font-size: 24rpx; - color: #c4b5fd; + color: #ffd966; line-height: 1.6; } /* 动画 */ @keyframes qrcodePulse { - 0% { box-shadow: 0 0 40rpx rgba(147, 51, 234, 0.3); } - 100% { box-shadow: 0 0 100rpx rgba(147, 51, 234, 0.6); } + 0% { box-shadow: 0 0 40rpx rgba(255, 170, 0, 0.3); } + 100% { box-shadow: 0 0 100rpx rgba(255, 170, 0, 0.6); } } @keyframes spin { 0% { transform: rotate(0deg); } diff --git a/pages/manager-assign/manager-assign.json b/pages/manager-assign/manager-assign.json index 03bf4df..4a234f5 100644 --- a/pages/manager-assign/manager-assign.json +++ b/pages/manager-assign/manager-assign.json @@ -1,5 +1,5 @@ { - "navigationBarTitleText": "关注星阙", + "navigationBarTitleText": "星之界", "usingComponents": { "global-notification": "/components/global-notification/global-notification" }, diff --git a/pages/merchant-data-stats/merchant-data-stats.js b/pages/merchant-data-stats/merchant-data-stats.js new file mode 100644 index 0000000..2721567 --- /dev/null +++ b/pages/merchant-data-stats/merchant-data-stats.js @@ -0,0 +1,67 @@ +import { createPage, request } from '../../utils/base-page.js'; +import { fetchMerchantOrderStats, applyOrderStatsToPage } from '../../utils/merchant-order-stats.js'; + +Page(createPage({ + data: { + statusBar: 20, + loading: true, + canViewFinance: true, + orderStats: { + pending_count: 0, + pending_amount: '0.00', + completed_count: 0, + completed_amount: '0.00', + refund_count: 0, + refund_amount: '0.00', + dispatch_count: 0, + dispatch_amount: '0.00', + }, + penaltyStats: { + fakuan_pending: 0, + fakuan_pending_amount: '0.00', + jifen_pending: 0, + }, + jinriliushui: '0.00', + jinyueliushui: '0.00', + jinridingdan: 0, + jinrituikuan: 0, + }, + + onLoad() { + const sys = wx.getSystemInfoSync(); + this.setData({ statusBar: sys.statusBarHeight || 20 }); + this.loadData(); + }, + + onPullDownRefresh() { + this.loadData().finally(() => wx.stopPullDownRefresh()); + }, + + async loadData() { + this.setData({ loading: true }); + try { + const statsData = await fetchMerchantOrderStats(request, {}); + applyOrderStatsToPage(this, statsData); + try { + const res = await request({ url: '/yonghu/shangjiaxinxi', method: 'POST' }); + if (res.data && res.data.code === 200) { + const d = res.data.data || {}; + this.setData({ + jinriliushui: d.jinriliushui || '0.00', + jinyueliushui: d.jinyueliushui || '0.00', + jinridingdan: d.jinripaidan || 0, + jinrituikuan: d.jinrituikuan || 0, + }); + } + } catch (e) { + console.warn('商家流水加载失败', e); + } + } finally { + this.setData({ loading: false }); + } + }, + + goBack() { + wx.navigateBack(); + }, +})); diff --git a/pages/merchant-data-stats/merchant-data-stats.json b/pages/merchant-data-stats/merchant-data-stats.json new file mode 100644 index 0000000..2cfa46f --- /dev/null +++ b/pages/merchant-data-stats/merchant-data-stats.json @@ -0,0 +1,5 @@ +{ + "navigationStyle": "custom", + "enablePullDownRefresh": true, + "usingComponents": {} +} diff --git a/pages/merchant-data-stats/merchant-data-stats.wxml b/pages/merchant-data-stats/merchant-data-stats.wxml new file mode 100644 index 0000000..b815939 --- /dev/null +++ b/pages/merchant-data-stats/merchant-data-stats.wxml @@ -0,0 +1,56 @@ + + + + + 经营数据 + + + + + + + + {{orderStats.pending_count}} + 待结算(单) + ¥{{orderStats.pending_amount}} + + + {{orderStats.completed_count}} + 成交总量 + ¥{{orderStats.completed_amount}} + + + + + {{orderStats.refund_count}} + 退款总量 + ¥{{orderStats.refund_amount}} + + + {{orderStats.dispatch_count}} + 发单总量 + ¥{{orderStats.dispatch_amount}} + + + {{jinriliushui}} + 今日流水 + + + {{jinyueliushui}} + 今月流水 + + + {{jinridingdan}} + 今日派单 + + + {{jinrituikuan}} + 今日退款 + + + + 处罚待处理:罚单 {{penaltyStats.fakuan_pending}}(¥{{penaltyStats.fakuan_pending_amount}})· 积分 {{penaltyStats.jifen_pending}} + + + + diff --git a/pages/merchant-data-stats/merchant-data-stats.wxss b/pages/merchant-data-stats/merchant-data-stats.wxss new file mode 100644 index 0000000..f9d7bdd --- /dev/null +++ b/pages/merchant-data-stats/merchant-data-stats.wxss @@ -0,0 +1,84 @@ +@import '../../styles/shangjia-xym-common.wxss'; + +page { background: #fff8e1; } + +.sub-scroll { height: calc(100vh - 120rpx); } + +.data-panel { padding: 24rpx; margin-top: 16rpx; } + +.data-highlight { + margin-bottom: 24rpx; + gap: 16rpx; +} + +.data-hl-item { + flex: 1; + text-align: center; + background: #fff; + border-radius: 16rpx; + padding: 20rpx 12rpx; +} + +.data-hl-num { + display: block; + font-size: 40rpx; + font-weight: 700; + color: #333; +} + +.data-hl-num.accent { color: #e65100; } + +.data-hl-lbl { + display: block; + font-size: 24rpx; + color: #888; + margin-top: 8rpx; +} + +.data-hl-sub { + display: block; + font-size: 22rpx; + color: #999; + margin-top: 4rpx; +} + +.data-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16rpx; +} + +.data-cell { + background: #fff; + border-radius: 16rpx; + padding: 20rpx 16rpx; + text-align: center; +} + +.data-cell-num { + display: block; + font-size: 32rpx; + font-weight: 600; + color: #333; +} + +.data-cell-lbl { + display: block; + font-size: 22rpx; + color: #888; + margin-top: 6rpx; +} + +.data-cell-sub { + display: block; + font-size: 20rpx; + color: #aaa; + margin-top: 4rpx; +} + +.penalty-mini { + margin-top: 20rpx; + font-size: 24rpx; + color: #666; + line-height: 1.5; +} diff --git a/pages/merchant-dispatch/merchant-dispatch.js b/pages/merchant-dispatch/merchant-dispatch.js index d2f15d6..28e1381 100644 --- a/pages/merchant-dispatch/merchant-dispatch.js +++ b/pages/merchant-dispatch/merchant-dispatch.js @@ -24,6 +24,7 @@ Page(createPage({ dingdanJieshao: '', // 订单介绍 dingdanBeizhu: '', // 订单备注 laobanName: '', // 老板游戏昵称/UID + waibuDingdanId: '', // 外部平台订单号 zhidingUid: '', // 指定打手UID jiage: '', // 订单价格 @@ -214,6 +215,9 @@ Page(createPage({ onLaobanNameInput(e) { this.setData({ laobanName: e.detail.value }) }, + onWaibuDingdanIdInput(e) { + this.setData({ waibuDingdanId: e.detail.value }) + }, onZhidingUidInput(e) { this.setData({ zhidingUid: e.detail.value }) }, @@ -342,6 +346,7 @@ Page(createPage({ dingdanJieshao: '', dingdanBeizhu: '', laobanName: '', + waibuDingdanId: '', zhidingUid: '', jiage: '', selectedLabelId: '', @@ -371,6 +376,7 @@ Page(createPage({ dingdanJieshao: this.data.dingdanJieshao.trim(), dingdanBeizhu: this.data.dingdanBeizhu.trim() || '', laobanName: this.data.laobanName.trim(), + waibuDingdanId: this.data.waibuDingdanId.trim() || '', zhidingUid: this.data.zhidingUid.trim() || '', jiage: this.data.jiage, // 新增字段 diff --git a/pages/merchant-dispatch/merchant-dispatch.json b/pages/merchant-dispatch/merchant-dispatch.json index c01f2ef..a2b524c 100644 --- a/pages/merchant-dispatch/merchant-dispatch.json +++ b/pages/merchant-dispatch/merchant-dispatch.json @@ -4,9 +4,9 @@ "popup-notice": "/components/popup-notice/popup-notice" }, "backgroundTextStyle": "dark", - "navigationBarBackgroundColor": "#fff8e1", + "navigationBarBackgroundColor": "#f7dc51", "navigationBarTitleText": "自定义发单", "navigationBarTextStyle": "black", - "backgroundColor": "#f5f5f5", + "backgroundColor": "#fff8e1", "enablePullDownRefresh": false } \ No newline at end of file diff --git a/pages/merchant-dispatch/merchant-dispatch.wxml b/pages/merchant-dispatch/merchant-dispatch.wxml index d070ce7..b535bc2 100644 --- a/pages/merchant-dispatch/merchant-dispatch.wxml +++ b/pages/merchant-dispatch/merchant-dispatch.wxml @@ -84,6 +84,21 @@ /> + + + + 拼多多订单号 + 选填 + + + + @@ -144,7 +159,7 @@ 选填 - + {{commissionEnabled ? '已开启' : '未开启'}} this.waitForConfigAndLoadBanner(), 100); + return new Promise((resolve) => { + const tick = () => { + if (app.globalData.ossImageUrl || app.globalData.apiBaseUrl) { + this.setupImageUrls(); + this.loadBannerData(true).finally(resolve); + } else { + setTimeout(tick, 100); + } + }; + tick(); + }); + }, + + setupImageUrls() { + const ossBase = app.globalData.ossImageUrl || ''; + const homeDir = `${ossBase}beijing/shangjiaduan/home/`; + const icon = (key, fallback) => resolveMiniappIcon(app, key, fallback); + this.setData({ + imgUrls: { + noticeIco: icon(ICON_KEYS.MERCHANT_HOME_NOTICE, XYM_HOME_FALLBACK.noticeIco), + statCardBg: icon(ICON_KEYS.MERCHANT_HOME_STAT_BG, `${ossBase}beijing/shangjiaduan/stat_card_bg.png`) || XYM_HOME_FALLBACK.statCardBg, + kefuKeyBtn: icon(ICON_KEYS.MERCHANT_HOME_KEFU_KEY, XYM_HOME_FALLBACK.kefuKeyBtn), + kefuListBtn: icon(ICON_KEYS.MERCHANT_HOME_KEFU_LIST, XYM_HOME_FALLBACK.kefuListBtn), + iconRegular: icon(ICON_KEYS.MERCHANT_HOME_REGULAR, XYM_HOME_FALLBACK.iconRegular), + iconCustom: icon(ICON_KEYS.MERCHANT_HOME_CUSTOM, XYM_HOME_FALLBACK.iconCustom), + }, + }); }, processImageUrls(urlList) { @@ -109,13 +182,13 @@ Page(createPage({ }, checkRole() { - this.setData({ isShangjia: isMerchantPortalUser() }); + syncStaffUi(this); }, - loadBannerData(forceFetch) { + loadBannerData(forceFetch, silent) { const g = app.globalData || {}; const cachedList = g.shangpinlunbo || []; - if (!forceFetch && cachedList.length) { + if (!forceFetch && isGonggaoCacheValid(app) && cachedList.length) { this.setData({ lunboList: this.processImageUrls(cachedList), gonggao: g.shangpingonggao || '', @@ -123,41 +196,20 @@ Page(createPage({ }); return Promise.resolve(); } - if (!forceFetch && g.shangpingonggao) { + return fetchGonggaoLunbo(app, 'merchant_home').then((data) => { + if (!data) return; this.setData({ - lunboList: this.processImageUrls(cachedList), - gonggao: g.shangpingonggao, + lunboList: this.processImageUrls(app.globalData.shangpinlunbo), + gonggao: app.globalData.shangpingonggao || '', }); - return Promise.resolve(); - } - const apiBase = g.apiBaseUrl || ''; - if (!apiBase) return Promise.resolve(); - return new Promise((resolve) => { - wx.request({ - url: `${apiBase}/peizhi/shangpingonggao/`, - method: 'POST', - header: { 'content-type': 'application/json' }, - success: (res) => { - if (res.statusCode === 200 && res.data) { - const data = res.data; - app.globalData.shangpingonggao = data.shangpingonggao || ''; - app.globalData.shangpinlunbo = data.shangpinlunbo || []; - this.setData({ - lunboList: this.processImageUrls(app.globalData.shangpinlunbo), - gonggao: app.globalData.shangpingonggao, - }); - } - }, - complete: resolve, - }); - }); + }).catch(() => {}); }, - async loadDashboard() { + async loadDashboard(silent = false) { await Promise.all([ this.loadShangjiaBrief(), this.loadPendingStats(), - this.loadRecentOrders(), + this.loadRecentOrders(silent), ]); }, @@ -191,36 +243,21 @@ Page(createPage({ async loadPendingStats() { try { - const res = await request({ - url: isStaffMode() ? STAFF_API.orderList : '/dingdan/sjdingdanhq', - method: 'POST', - data: { zhuangtai_list: [8], page: 1, page_size: 20 }, - }); - if (res.data && (res.data.code === 0 || res.data.code === 200)) { - const data = res.data.data || {}; - const list = data.list || []; - let amount = 0; - list.forEach((item) => { - const v = parseFloat(item.jine || item.dingdan_jine || 0); - if (!Number.isNaN(v)) amount += v; - }); - this.setData({ - stats: { - ...this.data.stats, - pendingCount: data.pending_count != null ? data.pending_count : list.length, - pendingAmount: amount.toFixed(2), - }, - }); - } + const data = await fetchMerchantOrderStats(request, {}); + applyOrderStatsToHome(this, data); } catch (e) { - console.error('待结算统计失败', e); + console.warn('待结算统计失败', e); } }, /** 最近订单:同订单页「全部」筛选,取最新若干条 */ - async loadRecentOrders() { - if (this.data.recentLoading) return; - this.setData({ recentLoading: true }); + async loadRecentOrders(silent = false) { + if (this.data.recentLoading && !silent) return; + if (silent && this._recentOrdersRequesting) return; + if (!silent) { + this.setData({ recentLoading: true }); + } + this._recentOrdersRequesting = true; try { const res = await request({ url: isStaffMode() ? STAFF_API.orderList : '/dingdan/sjdingdanhq', @@ -253,7 +290,10 @@ Page(createPage({ } catch (e) { console.error('最近订单加载失败', e); } finally { - this.setData({ recentLoading: false }); + this._recentOrdersRequesting = false; + if (!silent) { + this.setData({ recentLoading: false }); + } } }, @@ -261,12 +301,24 @@ Page(createPage({ this.setData({ swiperCurrent: e.detail.current }); }, - goDispatch() { + goRegularDispatch() { + wx.navigateTo({ url: '/pages/merchant-regular-dispatch/merchant-regular-dispatch' }); + }, + + goLinkDispatch() { + wx.navigateTo({ url: '/pages/express-order/express-order' }); + }, + + goCustomDispatch() { wx.navigateTo({ url: '/pages/merchant-dispatch/merchant-dispatch' }); }, + goDispatch() { + this.goCustomDispatch(); + }, + goExpress() { - wx.navigateTo({ url: '/pages/express-order/express-order' }); + this.goLinkDispatch(); }, goOrdersTab() { diff --git a/pages/merchant-home/merchant-home.wxml b/pages/merchant-home/merchant-home.wxml index 86debc7..d8f66be 100644 --- a/pages/merchant-home/merchant-home.wxml +++ b/pages/merchant-home/merchant-home.wxml @@ -18,6 +18,11 @@ + + + {{gonggao}} + + - - - {{gonggao}} - - 商家客服 · {{staffRoleName}} · 可用额度 ¥{{quotaAvailable}} - - - + + + + + + + + 链接派单 + 模板管理 · 生成链接 + + diff --git a/pages/merchant-home/merchant-home.wxss b/pages/merchant-home/merchant-home.wxss index eb664c3..bfe38b0 100644 --- a/pages/merchant-home/merchant-home.wxss +++ b/pages/merchant-home/merchant-home.wxss @@ -1,7 +1,7 @@ @import '../../styles/shangjia-xym-common.wxss'; page { - background: #f5f3ff; + background: #fff8e1; } .sj-page--home { @@ -85,14 +85,14 @@ page { .dot.active { width: 16rpx; border-radius: 8rpx; - background: #9333ea; + background: #ffd061; } .sj-notice-bar { display: flex; align-items: center; background: #fdf9db; - margin: 0 24rpx; + margin: 12rpx 24rpx 0; border-radius: 10rpx; padding: 10rpx 16rpx; } @@ -102,6 +102,10 @@ page { height: 56rpx; flex-shrink: 0; margin-right: 12rpx; + background-image: url('https://bintao.xmxym88.com/xcx/bintao/38.png'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; } .sj-notice-txt { @@ -111,11 +115,22 @@ page { flex: 1; } +.sj-fadan-block { + margin: 16rpx 24rpx 0; + display: flex; + flex-direction: column; + gap: 10rpx; +} + .sj-fadan-row { display: flex; - margin: 24rpx 20rpx 0; - padding: 10rpx; - gap: 8rpx; + margin: 0; + padding: 0; + gap: 16rpx; +} + +.sj-fadan-row--link { + margin-top: 0; } .sj-fadan-img-btn { @@ -127,16 +142,41 @@ page { .sj-fadan-img-btn--left { background-image: url('https://bintao.xmxym88.com/xcx/bintao/74.png'); - margin-right: 8rpx; } .sj-fadan-img-btn--right { background-image: url('https://bintao.xmxym88.com/xcx/bintao/76.png'); - margin-left: 8rpx; +} + +.sj-fadan-big-btn { + flex: 1; + height: 128rpx; + border-radius: 16rpx; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08); +} + +.sj-fadan-big-btn--link { + background: linear-gradient(180deg, #fae04d, #ffc0a3); +} + +.sj-fadan-big-title { + font-size: 30rpx; + font-weight: 700; + color: #492f00; +} + +.sj-fadan-big-sub { + font-size: 22rpx; + color: #886633; + margin-top: 8rpx; } .merchant-stats { - margin: 32rpx 25rpx 0; + margin: 12rpx 25rpx 0; border-radius: 26rpx; padding-bottom: 24rpx; position: relative; @@ -151,8 +191,10 @@ page { left: 0; right: 0; bottom: 0; + background-image: url('https://bintao.xmxym88.com/xcx/bintao/3.png'); background-size: 100% auto; background-repeat: no-repeat; + background-position: center top; pointer-events: none; z-index: 0; } @@ -311,7 +353,7 @@ page { .staff-banner { margin-top: 16rpx; padding: 16rpx 24rpx; - background: #f5f3ff; + background: #fff8e8; border-radius: 12rpx; border: 1rpx solid #f0d89a; } diff --git a/pages/merchant-kefu-key/merchant-kefu-key.wxss b/pages/merchant-kefu-key/merchant-kefu-key.wxss index df03957..b51a295 100644 --- a/pages/merchant-kefu-key/merchant-kefu-key.wxss +++ b/pages/merchant-kefu-key/merchant-kefu-key.wxss @@ -95,7 +95,7 @@ padding: 16rpx 32rpx; min-height: 64rpx; border-radius: 10rpx; - background: linear-gradient(180deg, #c4b5fd, #a78bfa); + background: linear-gradient(180deg, #fae04d, #ffc0a3); color: #492f00; font-size: 28rpx; font-weight: 700; @@ -108,8 +108,8 @@ } .tips-bar { - background: #f5f3ff; - border: 1rpx solid #c4b5fd; + background: #fff7e6; + border: 1rpx solid #ffd591; border-radius: 12rpx; padding: 18rpx 20rpx; font-size: 26rpx; diff --git a/pages/merchant-kefu-list/merchant-kefu-list.js b/pages/merchant-kefu-list/merchant-kefu-list.js index 5ca8c31..8507c85 100644 --- a/pages/merchant-kefu-list/merchant-kefu-list.js +++ b/pages/merchant-kefu-list/merchant-kefu-list.js @@ -34,6 +34,10 @@ Page({ list: [], + displayList: [], + + searchKeyword: '', + isOwner: false, }, @@ -124,7 +128,9 @@ Page({ const raw = (res.data.data && res.data.data.list) || []; - this.setData({ list: this.formatList(raw) }); + this._allList = this.formatList(raw); + + this.applySearchFilter(); } @@ -140,7 +146,64 @@ Page({ goBack() { wx.navigateBack(); }, + applySearchFilter() { + const kw = (this.data.searchKeyword || '').trim().toLowerCase(); + const all = this._allList || []; + if (!kw) { + this.setData({ list: all, displayList: all }); + return; + } + const filtered = all.filter((item) => { + const uid = String(item.uid || '').toLowerCase(); + const remark = String(item.merchant_remark || '').toLowerCase(); + const nick = String(item.nickname || '').toLowerCase(); + return uid.includes(kw) || remark.includes(kw) || nick.includes(kw); + }); + this.setData({ list: all, displayList: filtered }); + }, + onSearchInput(e) { + this.setData({ searchKeyword: e.detail.value || '' }); + this.applySearchFilter(); + }, + + clearSearch() { + this.setData({ searchKeyword: '' }); + this.applySearchFilter(); + }, + + goStaffOrders(e) { + const id = e.currentTarget.dataset.id; + const label = e.currentTarget.dataset.label || ''; + wx.redirectTo({ + url: `/pages/merchant-orders/merchant-orders?staff_member_id=${id}&staff_label=${encodeURIComponent(label)}`, + }); + }, + + onEditRemark(e) { + const id = e.currentTarget.dataset.id; + const oldRemark = e.currentTarget.dataset.remark || ''; + wx.showModal({ + title: '客服备注', + editable: true, + placeholderText: '如:晚班小李、财务小王', + content: oldRemark, + success: async (r) => { + if (!r.confirm) return; + const remark = (r.content || '').trim(); + const res = await request({ + url: STAFF_API.memberUpdateRemark, + method: 'POST', + data: { member_id: id, merchant_remark: remark }, + }); + wx.showToast({ + title: (res.data && res.data.code === 0) ? '已保存' : (res.data.msg || '失败'), + icon: 'none', + }); + this.loadList(); + }, + }); + }, copyUid(e) { diff --git a/pages/merchant-kefu-list/merchant-kefu-list.wxml b/pages/merchant-kefu-list/merchant-kefu-list.wxml index 78166a7..ab260ae 100644 --- a/pages/merchant-kefu-list/merchant-kefu-list.wxml +++ b/pages/merchant-kefu-list/merchant-kefu-list.wxml @@ -12,11 +12,17 @@ - + + + + + + + - + @@ -41,12 +47,10 @@ - ID: {{item.uid}} - 复制 - + 备注:{{item.merchant_remark}} @@ -132,7 +136,12 @@ - + + 备注 + 查看订单 + + + 划额度 @@ -150,7 +159,7 @@ - 暂无客服成员 + 暂无客服成员 操作日志 › diff --git a/pages/merchant-kefu-list/merchant-kefu-list.wxss b/pages/merchant-kefu-list/merchant-kefu-list.wxss index 5ab7f2a..9d21e0f 100644 --- a/pages/merchant-kefu-list/merchant-kefu-list.wxss +++ b/pages/merchant-kefu-list/merchant-kefu-list.wxss @@ -57,7 +57,7 @@ } .quota-label { font-size: 24rpx; color: #888; } -.quota-val { font-size: 30rpx; color: #9333ea; font-weight: 600; } +.quota-val { font-size: 30rpx; color: #C9A962; font-weight: 600; } .kefu-av { width: 88rpx; @@ -81,7 +81,7 @@ color: #fff; } -.badge-chief { background: #9333ea; } +.badge-chief { background: #ffb508; } .badge-finance { background: #1b6ef3; } .badge-self { background: #9e9e9e; } @@ -155,3 +155,16 @@ font-size: 22rpx; color: #bbb; } + +.filter-row { margin-bottom: 8rpx; } +.search-box { + display: flex; + align-items: center; + background: #fff; + border-radius: 32rpx; + padding: 12rpx 20rpx; + box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06); +} +.search-icon { width: 32rpx; height: 32rpx; margin-right: 12rpx; } +.search-input { flex: 1; font-size: 26rpx; } +.search-clear { padding: 8rpx; color: #999; font-size: 24rpx; } diff --git a/pages/merchant-msg/merchant-msg.json b/pages/merchant-msg/merchant-msg.json index e3c4071..93de58c 100644 --- a/pages/merchant-msg/merchant-msg.json +++ b/pages/merchant-msg/merchant-msg.json @@ -3,8 +3,8 @@ "global-notification": "/components/global-notification/global-notification" }, "navigationBarTitleText": "处罚记录", - "navigationBarBackgroundColor": "#0a0a12", - "navigationBarTextStyle": "white", - "backgroundColor": "#0a0a12", + "navigationBarBackgroundColor": "#f7dc51", + "navigationBarTextStyle": "black", + "backgroundColor": "#fff8e1", "enablePullDownRefresh": false } \ No newline at end of file diff --git a/pages/merchant-msg/merchant-msg.wxss b/pages/merchant-msg/merchant-msg.wxss index 96c1fa0..7c1666a 100644 --- a/pages/merchant-msg/merchant-msg.wxss +++ b/pages/merchant-msg/merchant-msg.wxss @@ -2,20 +2,20 @@ /* 🔴【样式与cfss页面基本相同,只添加了独占按钮样式】 */ :root { - --cyber-blue: rgba(0, 243, 255, 0.7); - --cyber-pink: rgba(255, 0, 255, 0.7); - --cyber-purple: rgba(157, 0, 255, 0.7); - --cyber-green: rgba(0, 255, 157, 0.7); - --cyber-red: rgba(255, 0, 102, 0.7); + --cyber-blue: rgba(255, 208, 97, 0.85); + --cyber-pink: rgba(255, 192, 163, 0.85); + --cyber-purple: rgba(230, 126, 34, 0.85); + --cyber-green: rgba(76, 175, 80, 0.85); + --cyber-red: rgba(230, 74, 25, 0.85); - --cyber-bg: #2a2a3a; - --cyber-card: #333344; - --cyber-border: rgba(255, 255, 255, 0.15); - --cyber-glow: rgba(0, 243, 255, 0.2); + --cyber-bg: #fff8e1; + --cyber-card: #fef6d4; + --cyber-border: rgba(255, 208, 97, 0.35); + --cyber-glow: rgba(255, 208, 97, 0.2); - --cyber-text: #ffffff; - --cyber-text-light: #e6e6e6; - --cyber-text-lighter: #cccccc; + --cyber-text: #333333; + --cyber-text-light: #666666; + --cyber-text-lighter: #888888; } .cfss-container { @@ -38,8 +38,8 @@ bottom: 0; background-image: - linear-gradient(rgba(0, 243, 255, 0.05) 1px, transparent 1px), - linear-gradient(90deg, rgba(0, 243, 255, 0.05) 1px, transparent 1px); + linear-gradient(rgba(255, 208, 97, 0.08) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 208, 97, 0.08) 1px, transparent 1px); background-size: 40rpx 40rpx; pointer-events: none; opacity: 0.15; @@ -48,14 +48,14 @@ /* ====== 统计卡片 ====== */ .tongji-card { - background: rgba(255, 0, 102, 0.3); + background: #fef6d4; border-radius: 20rpx; padding: 30rpx; margin-bottom: 30rpx; border: 1rpx solid var(--cyber-border); box-shadow: 0 0 20rpx rgba(255, 0, 102, 0.2), - inset 0 0 10rpx rgba(0, 243, 255, 0.05); + inset 0 0 10rpx rgba(255, 208, 97, 0.05); position: relative; overflow: hidden; z-index: 1; @@ -109,7 +109,7 @@ /* ====== 类型切换 ====== */ .leixing-qiehuan { display: flex; - background: rgba(255, 0, 102, 0.3); + background: #fef6d4; border-radius: 16rpx; padding: 10rpx; margin-bottom: 30rpx; @@ -133,8 +133,8 @@ } .leixing-active { - background: linear-gradient(135deg, rgba(0, 243, 255, 0.15), rgba(157, 0, 255, 0.15)); - box-shadow: 0 0 15rpx rgba(0, 243, 255, 0.2); + background: linear-gradient(135deg, rgba(250, 224, 77, 0.25), rgba(255, 192, 163, 0.25)); + box-shadow: 0 0 15rpx rgba(255, 208, 97, 0.2); } .leixing-text { @@ -211,7 +211,7 @@ /* 处罚卡片 */ .chufa-card { - background: rgba(560, 50, 65, 0.8); + background: #fff; border-radius: 20rpx; padding: 30rpx; margin-bottom: 30rpx; @@ -236,7 +236,7 @@ right: -1px; bottom: -1px; border-radius: 21rpx; - background: linear-gradient(45deg, rgba(138, 60, 138, 0.3), rgba(131, 208, 212, 0.3), rgba(255, 0, 255, 0.3), rgba(0, 243, 255, 0.3)); + background: linear-gradient(45deg, rgba(250, 224, 77, 0.3), rgba(255, 192, 163, 0.3), rgba(255, 208, 97, 0.3), rgba(255, 224, 130, 0.3)); z-index: -1; opacity: 0; transition: opacity 0.3s ease; @@ -292,15 +292,15 @@ } .zhuangtai-shensuzhong { - background: rgba(147, 51, 234, 0.2); - border-color: #9333ea; - box-shadow: 0 0 8rpx rgba(147, 51, 234, 0.2); + background: rgba(255, 165, 0, 0.2); + border-color: #FFA500; + box-shadow: 0 0 8rpx rgba(255, 165, 0, 0.2); } .zhuangtai-weizhi { - background: rgba(128, 0, 255, 0.2); - border-color: rgba(128, 0, 255, 0.5); - box-shadow: 0 0 8rpx rgba(128, 0, 255, 0.2); + background: rgba(255, 152, 0, 0.15); + border-color: rgba(255, 152, 0, 0.5); + box-shadow: 0 0 8rpx rgba(255, 152, 0, 0.2); } /* 其他文字内容 */ @@ -423,7 +423,7 @@ .modal-content { position: relative; - background: #3a3a4a; + background: #fff; border-radius: 30rpx; width: 90%; max-height: 80vh; @@ -458,11 +458,11 @@ justify-content: center; border-radius: 50%; background: rgba(137, 210, 214, 0.1); - border: 1rpx solid rgba(0, 243, 255, 0.3); + border: 1rpx solid rgba(255, 208, 97, 0.3); } .modal-close:active { - background: rgba(0, 243, 255, 0.2); + background: rgba(255, 208, 97, 0.2); transform: scale(0.95); } @@ -492,7 +492,7 @@ color: var(--cyber-blue); margin-bottom: 25rpx; padding-bottom: 15rpx; - border-bottom: 1rpx solid rgba(0, 243, 255, 0.2); + border-bottom: 1rpx solid rgba(255, 208, 97, 0.2); text-shadow: 0 0 8rpx var(--cyber-blue); } @@ -534,13 +534,13 @@ font-size: 24rpx; padding: 8rpx 20rpx; border-radius: 8rpx; - background: rgba(0, 243, 255, 0.1); - border: 1rpx solid rgba(0, 243, 255, 0.3); + background: rgba(255, 208, 97, 0.1); + border: 1rpx solid rgba(255, 208, 97, 0.3); flex-shrink: 0; } .fuzhi-btn:active { - background: rgba(0, 243, 255, 0.2); + background: rgba(255, 208, 97, 0.2); } .full-row { @@ -598,7 +598,7 @@ border-radius: 10rpx; overflow: hidden; position: relative; - border: 1rpx solid rgba(0, 243, 255, 0.2); + border: 1rpx solid rgba(255, 208, 97, 0.2); } .tupian-image { @@ -628,7 +628,7 @@ .preview-text { color: white; font-size: 24rpx; - background: rgba(0, 243, 255, 0.7); + background: rgba(255, 208, 97, 0.7); padding: 8rpx 16rpx; border-radius: 8rpx; } @@ -636,7 +636,7 @@ /* 🔴【新增】弹窗底部按钮独占一行样式 */ .modal-footer { padding: 30rpx; - border-top: 1rpx solid rgba(0, 243, 255, 0.2); + border-top: 1rpx solid rgba(255, 208, 97, 0.2); flex-shrink: 0; } @@ -666,9 +666,9 @@ /* 按钮样式 */ .btn-kefu { - background: rgba(0, 243, 255, 0.1); + background: rgba(255, 208, 97, 0.1); border-color: var(--cyber-blue); - box-shadow: 0 0 12rpx rgba(0, 243, 255, 0.2); + box-shadow: 0 0 12rpx rgba(255, 208, 97, 0.2); } /* 响应式调整 */ @@ -712,7 +712,7 @@ aspect-ratio: 1; border-radius: 12rpx; overflow: hidden; - border: 1rpx solid rgba(0, 243, 255, 0.3); + border: 1rpx solid rgba(255, 208, 97, 0.3); background-color: rgba(0, 0, 0, 0.2); } @@ -745,7 +745,7 @@ .preview-text { color: white; font-size: 24rpx; - background: rgba(0, 243, 255, 0.7); + background: rgba(255, 208, 97, 0.7); padding: 8rpx 16rpx; border-radius: 8rpx; } \ No newline at end of file diff --git a/pages/merchant-order-detail/merchant-order-detail.js b/pages/merchant-order-detail/merchant-order-detail.js index d245e7b..f9b2d64 100644 --- a/pages/merchant-order-detail/merchant-order-detail.js +++ b/pages/merchant-order-detail/merchant-order-detail.js @@ -2,13 +2,24 @@ const app = getApp() import request from '../../utils/request.js' import PopupService from '../../services/popupService.js' // 新增:导入弹窗服务 +import connectionManager from '../../utils/xiaoxilj.js' import { STAFF_API, isStaffMode, syncStaffUi, getStaffContext, staffCan } from '../../utils/staff-api.js' +/** 可申请罚款的订单状态:进行中/已完成/退款中/已退款/退款失败/结算中 */ +const PENALTY_APPLY_ORDER_STATUSES = [2, 3, 4, 5, 6, 8] + +function canApplyOrderPenalty({ zhuangtai, dashouYonghuid, fadanData, canPenalty }) { + return canPenalty + && !fadanData + && dashouYonghuid + && PENALTY_APPLY_ORDER_STATUSES.includes(Number(zhuangtai)) +} + Page({ data: { jibenShuju: { dingdan_id: '', tupian: '', jieshao: '', create_time: '', - jine: '', nicheng: '', beizhu: '', zhuangtai: 0, fadanpingtai: 0 + jine: '', nicheng: '', beizhu: '', waibu_dingdan_id: '', zhuangtai: 0, fadanpingtai: 0 }, dashouInfo: { yonghuid: '', nicheng: '', avatar: '' }, dispatchStaff: null, @@ -20,8 +31,8 @@ Page({ fenhongLilv: 0, xinDingdanId: '', zhuangtaiYanseMap: { - 1: '#E8F5E9', 2: '#E3F2FD', 3: '#F3E5F5', 4: '#F3E8FF', - 5: '#FFEBEE', 6: '#EFEBE9', 7: '#E0F7FA', 8: '#F5F3FF' + 1: '#E8F5E9', 2: '#E3F2FD', 3: '#F3E5F5', 4: '#FFF3E0', + 5: '#FFEBEE', 6: '#EFEBE9', 7: '#E0F7FA', 8: '#FFF8E1' }, zhuangtaiWenziMap: { 1: '已下单', 2: '进行中', 3: '已完成', 4: '退款中', @@ -60,6 +71,19 @@ Page({ canSettle: true, canPenalty: true, canPenaltyManage: true, + canShowPenaltyApply: false, + }, + + refreshPenaltyApplyBtn() { + const { jibenShuju, dashouInfo, fadanData, canPenalty } = this.data + this.setData({ + canShowPenaltyApply: canApplyOrderPenalty({ + zhuangtai: jibenShuju.zhuangtai, + dashouYonghuid: dashouInfo.yonghuid, + fadanData, + canPenalty, + }), + }) }, onLoad(options) { @@ -74,6 +98,7 @@ Page({ onShow() { this.registerNotificationComponent() syncStaffUi(this) + this.refreshPenaltyApplyBtn() this.ensureIMConnection() }, @@ -256,8 +281,10 @@ Page({ fadanData: d.fadan || null, fenhongLilv: parseFloat(d.fenhong_lilv) || 0, xinDingdanId: d.xin_dingdan_id || '', - 'jibenShuju.zhuangtai': d.zhuangtai || this.data.jibenShuju.zhuangtai + 'jibenShuju.zhuangtai': d.zhuangtai || this.data.jibenShuju.zhuangtai, + 'jibenShuju.waibu_dingdan_id': d.waibu_dingdan_id || '', }) + this.refreshPenaltyApplyBtn() } else { wx.showToast({ title: res?.data?.msg || '获取详情失败', icon: 'none' }) } @@ -298,7 +325,7 @@ Page({ }) }, - // ===== 联系打手(替打手订阅 + 发机器人消息 + 跳转) ===== + // ===== 联系打手(订单群 groupId = 订单号) ===== goToChatWithDashou() { if (isStaffMode() && !staffCan('imChat')) { wx.showToast({ title: '当前角色无订单聊天权限', icon: 'none' }) @@ -313,43 +340,37 @@ Page({ return } - const targetUserId = 'Sj' + uid - if (this.data.currentIMUserId !== targetUserId) { - this.ensureIMConnection() - wx.showToast({ title: '连接中,请稍后再试', icon: 'none' }) - return - } - - const that = this const groupName = this.data.jibenShuju.nicheng + '的订单群' const isCross = this.data.jibenShuju.fadanpingtai || 0 const dashouUid = this.data.dashouInfo.yonghuid + const that = this - // 第一步:商家自己订阅群组 - wx.goEasy.im.subscribeGroup({ - groupIds: [orderId], - onSuccess: () => { - // 如果有打手,则需要帮打手订阅并发送消息 - if (dashouUid) { - that.bangDashouDingyue(orderId, dashouUid) - .then(() => { - that.tiaozhuanQunLiao(orderId, groupName, isCross) - }) - .catch((err) => { - console.error('替打手订阅失败,仍可跳转群聊', err) - wx.showToast({ title: '通知接单员可能失败', icon: 'none' }) - that.tiaozhuanQunLiao(orderId, groupName, isCross) - }) - } else { - // 无打手,直接跳转 - that.tiaozhuanQunLiao(orderId, groupName, isCross) - } - }, - onFailed: (error) => { - console.error('订阅群组失败', error) - wx.showToast({ title: '连接群聊失败', icon: 'none' }) - } - }) + const openGroupChat = () => { + connectionManager.connectToGroupChat({ + identityType: 'shangjia', + userId: 'Sj' + uid, + orderId: orderId, + partnerUid: dashouUid, + fadanPingtai: this.data.jibenShuju.fadanpingtai || 0, + groupName, + isCross, + }).catch((err) => { + console.error('跳转群聊失败', err) + wx.showToast({ title: '跳转聊天失败', icon: 'none' }) + }) + } + + if (dashouUid) { + that.bangDashouDingyue(orderId, dashouUid) + .then(openGroupChat) + .catch((err) => { + console.error('替打手订阅失败,仍可进入群聊', err) + wx.showToast({ title: '通知接单员可能失败', icon: 'none' }) + openGroupChat() + }) + } else { + openGroupChat() + } }, // 帮打手订阅群组 + 发送机器人消息 @@ -406,20 +427,6 @@ Page({ }) }, - // 跳转群聊页面 - tiaozhuanQunLiao(orderId, groupName, isCross) { - const param = { - groupId: orderId, - orderId: orderId, - groupName: groupName, - groupAvatar: '', - isCross: isCross - } - wx.navigateTo({ - url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)) - }) - }, - // ========== 以下所有业务功能完全保持不变 ========== showGhDashouConfirm() { this.setData({ showGhDashouModal: true }) }, closeGhDashouModal() { this.setData({ showGhDashouModal: false }) }, @@ -450,7 +457,13 @@ Page({ } }, - showFakuanModal() { this.setData({ showFakuanModal: true, fakuanLiyou: '', fakuanJine: '', yingxiangQiangdan: true }) }, + showFakuanModal() { + const dingdanId = this.data.jibenShuju.dingdan_id + if (dingdanId) { + this.jiazaiXiangxiShuju(dingdanId) + } + this.setData({ showFakuanModal: true, fakuanLiyou: '', fakuanJine: '', yingxiangQiangdan: true }) + }, closeFakuanModal() { this.setData({ showFakuanModal: false }) }, inputFakuanLiyou(e) { this.setData({ fakuanLiyou: e.detail.value.slice(0, 50) }) }, inputFakuanJine(e) { this.setData({ fakuanJine: e.detail.value }) }, @@ -517,6 +530,7 @@ Page({ this.setData({ isChexiaoLoading: false }) if (res && res.data.code === 0) { this.setData({ 'jibenShuju.zhuangtai': 5, showChexiaoModal: false }) + this.refreshPenaltyApplyBtn() wx.showToast({ title: '撤销成功', icon: 'success' }) } else { wx.showToast({ title: res?.data?.msg || '撤销失败', icon: 'none' }) @@ -548,6 +562,7 @@ Page({ this.setData({ isTuikuanLoading: false }) if (res && res.data.code === 0) { this.setData({ 'jibenShuju.zhuangtai': 4, showTuikuanModal: false }) + this.refreshPenaltyApplyBtn() wx.showToast({ title: '退款申请已提交', icon: 'success' }) } else { wx.showToast({ title: res?.data?.msg || '失败', icon: 'none' }) @@ -586,6 +601,7 @@ Page({ this.setData({ isJiesuanLoading: false, showJiesuanConfirm: false }) if (res && res.data.code === 0) { this.setData({ 'jibenShuju.zhuangtai': 3 }) + this.refreshPenaltyApplyBtn() wx.showToast({ title: '结算成功', icon: 'success' }) } else { wx.showToast({ title: res?.data?.msg || '结算失败', icon: 'none' }) diff --git a/pages/merchant-order-detail/merchant-order-detail.wxml b/pages/merchant-order-detail/merchant-order-detail.wxml index c9b5e5e..0f6441b 100644 --- a/pages/merchant-order-detail/merchant-order-detail.wxml +++ b/pages/merchant-order-detail/merchant-order-detail.wxml @@ -29,6 +29,8 @@ 游戏昵称{{jibenShuju.nicheng}} 备注{{jibenShuju.beizhu}} + + 拼多多订单号{{jibenShuju.waibu_dingdan_id}}📋 订单ID{{jibenShuju.dingdan_id}}📋 @@ -58,8 +60,8 @@ - 撤销退款申请 - 修改退款理由 + 撤销退款申请 + 修改退款理由 @@ -135,14 +137,12 @@ 更新时间{{fadanData.update_time}} - - 修改罚款 - 撤销罚款 + 修改罚款 + 撤销罚款 - - 再次申请罚款 + 再次申请罚款 @@ -169,25 +169,21 @@