diff --git a/app.js b/app.js index 811ef86..e8e1994 100644 --- a/app.js +++ b/app.js @@ -1,414 +1,451 @@ -// app.js -import GoEasy from './static/lib/goeasy-2.13.24.esm.min'; -const ChatCore = require('./utils/chat-core'); -import { check } from './utils/phone-auth'; -import { getPrimaryRole, lockPrimaryRole, migrateLegacyCenterRole, PRIMARY_DEFAULT_PAGES } from './utils/primary-role'; - -// 三端固定首页 -const roleDefaultPage = PRIMARY_DEFAULT_PAGES; - -App({ - globalData: { - - hasShownPopupOnColdStart: false, - - apiBaseUrl: 'https://www.abas.asia/hqhd', - ossImageUrl: '', - morentouxiang: '', - dashouguize: '', - - shangpinliebiao: [], - shangpinzhuanqu: [], - shangpinleixing: [], - shangpinlunbo: [], - shangpingonggao: '', - lunbozhanwei: '/images/lunbozhanwei.jpg', - - dingdanTiaoshu: { - daifuwu: 0, - fuwuzhong: 0, - yiwancheng: 0, - yituikuan: 0 - }, - - dashouqun: '', - dashouqunid: '', - guanshiqun: '', - guanshiqunid: '', - - appId:'wx0e4be86faac4a8d1', - - shangjiastatus: 0, - dashoustatus: 0, - guanshistatus: 0, - dashouNicheng: '', - zhanghaoStatus: null, - dashouzhuangtai: null, - yongjin: null, - zonge: null, - yajin: null, - chenghao: '', - jinfen: null, - clumber: [{ - huiyuanid: '', - huiyuanming: '', - daoqi: '' - }], - chengjiaoliang: null, - zaixianZhuangtai: null, - - guanshi: { - nicheng: '', - uid: '', - touxiang: '', - gszhstatus: '', - yaoqingzongshu: 0, - fenyongzonge: '0.00', - fenyongtixian: '0.00' - }, - - shangjia: { - sjzhzhuangtai: '', - sjyue: '', - fadanzong: null, - tuikuanzong: null, - riliushui: '', - yueliushui: '' - }, - - liaotian_liebiao: [], - xshenfen: 1, - - goEasyConfig: null, - kefuConfig: { - link: '', - enterpriseId: '' - }, - - cosConfig: { - bucket: '', - region: 'ap-shanghai', - uploadPathPrefix: 'order/' - }, - - goEasyConnection: { - status: 'disconnected', - userId: '', - identityType: '', - lastConnectTime: 0, - autoReconnect: true, - reconnectAttempts: 0, - maxReconnectAttempts: 5, - heartbeatInterval: null, - cacheKeys: { - savedConnection: 'savedGoEasyConnection', - userId: 'goEasyUserId', - identityType: 'currentGoEasyIdentity', - connectTime: 'goEasyConnectTime' - }, - config: { - heartbeatInterval: 20000, - reconnectDelay: 3000, - offlineTimeout: 30000, - cacheValidityHours: 12 - } - }, - - messageManager: { - unreadTotal: 0, - unreadMap: {}, - latestMessages: [], - notificationQueue: [], - notificationVisible: false, - lastNotificationTime: 0, - notificationCooldown: 3000, - tabBarIndex: 2, - showTabBarBadge: true, - customTabBar: true, - tabBarBadgeText: 0, - soundEnabled: true, - vibrationEnabled: true, - doNotDisturb: false, - doNotDisturbStart: '22:00', - doNotDisturbEnd: '08:00', - notificationStyle: { - position: 'top', - duration: 3000, - backgroundColor: '#00f7ff', - textColor: '#ffffff', - borderRadius: '16rpx', - boxShadow: '0 10rpx 30rpx rgba(0, 247, 255, 0.3)' - }, - currentNotification: null, - cacheKeys: { - messageSettings: 'messageSettings', - unreadTotal: 'messageUnreadTotal', - latestMessages: 'latestMessages', - notificationMuted: 'notificationMuted' - } - }, - - pageState: { - currentPage: '', - isInChatPage: false, - currentChatId: '', - lastPageUpdate: 0 - }, - - eventListeners: {}, - debugMode: false, - currentUser: null, - currentRole: 'normal', - primaryRole: 'normal' - }, - - // 核心启动流程 - async onLaunch() { - // ① 隐藏返回首页按钮 - wx.hideHomeButton(); - wx.onAppRoute((res) => { - wx.hideHomeButton(); - const path = (res && res.path) || ''; - if (path === 'pages/manager/manager' || path === 'pages/leader/leader') { - migrateLegacyCenterRole(this); - lockPrimaryRole('dashou', this); - } - }); - - // ② 恢复主端并跳转 - migrateLegacyCenterRole(this); - const savedRole = getPrimaryRole(this); - lockPrimaryRole(savedRole, this); - - const targetPage = roleDefaultPage[savedRole] || '/pages/index/index'; - if (savedRole !== 'normal') { - setTimeout(() => { - wx.reLaunch({ url: targetPage }); - }, 0); - } - - // ③ 应用本地缓存配置 - const cachedConfig = this.readConfigFromStorage(); - if (cachedConfig) { - this.applyDynamicConfig(cachedConfig); - } - - // ④ 初始化 - this.initGoEasyWithConfig(); // 只有appkey有效时才真正初始化 - ChatCore.initGlobalMessageSystem(this); - this.initCurrentUser(); - - // ⑤ 建立连接 - const saved = this.getSavedConnection(); - console.log('【启动】缓存身份:', saved ? saved.identityType : '无', - 'userId:', saved ? saved.userId : '无'); - setTimeout(() => { - if (this.startImWhenReady) this.startImWhenReady(); - else if (this.ensureConnection) this.ensureConnection(); - }, 300); - - // ⑥ 获取远程配置 - this.fetchConfigSafely() - .then(latestConfig => { - this.saveConfigToStorage(latestConfig); - this.applyDynamicConfig(latestConfig); - this.initGoEasyWithConfig(); - this.initCurrentUser(); - if (this.startImWhenReady) this.startImWhenReady(); - else if (this.connectForCurrentRole) this.connectForCurrentRole(); - console.log('远程配置更新完成'); - }) - .catch(err => { - console.warn('远程配置获取失败,继续使用本地缓存', err); - }); - - // ⑦ 手机号认证检查 - try { - const needAuth = await check(); - if (needAuth) { - wx.reLaunch({ url: '/pages/phone-auth/phone-auth' }); - return; - } - } catch (e) {} - }, - - // 连接管理方法 - getSavedConnection() { - try { - const saved = wx.getStorageSync( - this.globalData.goEasyConnection.cacheKeys.savedConnection - ); - return saved || null; - } catch (e) { - return null; - } - }, - - // 配置缓存读写 - CONFIG_CACHE_KEY: 'app_dynamic_config', - - readConfigFromStorage() { - try { - const data = wx.getStorageSync(this.CONFIG_CACHE_KEY); - if (data && typeof data === 'object') { - return data; - } - } catch (e) {} - return null; - }, - - saveConfigToStorage(config) { - try { - if (config && typeof config === 'object') { - wx.setStorageSync(this.CONFIG_CACHE_KEY, config); - } - } catch (e) {} - }, - - // 获取远程配置 - fetchConfigSafely() { - return new Promise((resolve) => { - wx.request({ - url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq', - method: 'POST', - header: { 'content-type': 'application/json' }, - success: (res) => { - if (res.statusCode === 200 && res.data && res.data.code === 0 && res.data.data) { - resolve(res.data.data); - } else { - console.warn('远程配置接口返回异常,使用已有配置'); - resolve(this.readConfigFromStorage() || {}); - } - }, - fail: (err) => { - console.warn('远程配置网络请求失败,使用已有配置', err); - resolve(this.readConfigFromStorage() || {}); - } - }); - }); - }, - - // 获取远程配置 - fetchDynamicConfig() { - return new Promise((resolve, reject) => { - wx.request({ - url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq', - method: 'POST', - header: { 'content-type': 'application/json' }, - success: (res) => { - if (res.statusCode === 200 && res.data && res.data.code === 0) { - resolve(res.data.data); - } else { - reject(new Error(res.data?.msg || '接口返回错误')); - } - }, - fail: reject - }); - }); - }, - - applyDynamicConfig(config) { - if (!config || typeof config !== 'object') return; - - if (config.cos) { - this.globalData.ossImageUrl = config.cos.ossImageUrl || ''; - this.globalData.cosConfig = { - bucket: config.cos.bucket || '', - region: config.cos.region || 'ap-shanghai', - uploadPathPrefix: config.cos.uploadPathPrefix || 'order/' - }; - } - - if (config.goEasy) { - this.globalData.goEasyConfig = { - host: config.goEasy.host || 'hangzhou.goeasy.io', - appkey: config.goEasy.appkey || '' - }; - } - - if (config.otherConfig) { - this.globalData.morentouxiang = config.otherConfig.morentouxiang || ''; - this.globalData.dashouguize = config.otherConfig.dashouguize || ''; - } - - if (config.kefu) { - this.globalData.kefuConfig = { - link: config.kefu.link || '', - enterpriseId: config.kefu.enterpriseId || '' - }; - } - }, - - // 仅在有效 appkey 时初始化 - initGoEasyWithConfig() { - const cfg = this.globalData.goEasyConfig; - if (!cfg || !cfg.appkey) { - console.warn('GoEasy appkey 未配置,聊天功能将在配置就绪后自动恢复'); - return; - } - - // 避免重复初始化 - if (wx.goEasy && wx.GoEasy) { - console.log('GoEasy 已初始化,跳过'); - return; - } - - try { - wx.goEasy = GoEasy.getInstance({ - host: cfg.host || 'hangzhou.goeasy.io', - appkey: cfg.appkey, - modules: ['im', 'pubsub'] - }); - wx.GoEasy = GoEasy; - console.log('GoEasy 初始化成功'); - if (this.startImWhenReady && wx.getStorageSync('uid')) { - setTimeout(() => this.startImWhenReady(), 100); - } - } catch (error) { - console.error('GoEasy 初始化失败:', error); - } - }, - - initCurrentUser() { - const uid = wx.getStorageSync('uid'); - if (uid) { - this.globalData.currentUser = { - id: uid, - name: '用户' + uid.substring(0, 6), - avatar: this.globalData.ossImageUrl + this.globalData.morentouxiang - }; - } - }, - - emitEvent(eventName, data) { - if (this.globalData.eventListeners[eventName]) { - this.globalData.eventListeners[eventName].forEach(callback => { - try { callback(data); } catch (error) { console.error(error); } - }); - } - }, - - /** 切换角色并通知刷新 */ - setCurrentRole(role) { - if (!role) return; - this.globalData.currentRole = role; - wx.setStorageSync('currentRole', role); - this.emitEvent('currentRoleChanged', { role }); - }, - - on(eventName, callback) { - if (!this.globalData.eventListeners[eventName]) { - this.globalData.eventListeners[eventName] = []; - } - this.globalData.eventListeners[eventName].push(callback); - }, - - off(eventName, callback) { - if (this.globalData.eventListeners[eventName]) { - const index = this.globalData.eventListeners[eventName].indexOf(callback); - if (index > -1) { - this.globalData.eventListeners[eventName].splice(index, 1); - } - } - } +// app.js +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 roleDefaultPage = PRIMARY_DEFAULT_PAGES; + +App({ + globalData: { + + hasShownPopupOnColdStart: false, + + apiBaseUrl: 'https://www.abas.asia/hqhd', + ossImageUrl: '', + morentouxiang: '', + dashouguize: '', + + shangpinliebiao: [], + shangpinzhuanqu: [], + shangpinleixing: [], + shangpinlunbo: [], + shangpingonggao: '', + lunbozhanwei: '/images/lunbozhanwei.jpg', + + dingdanTiaoshu: { + daifuwu: 0, + fuwuzhong: 0, + yiwancheng: 0, + yituikuan: 0 + }, + + dashouqun: '', + dashouqunid: '', + guanshiqun: '', + guanshiqunid: '', + + appId: WX_APP_ID, + clubId: CLUB_ID, + + shangjiastatus: 0, + dashoustatus: 0, + guanshistatus: 0, + dashouNicheng: '', + zhanghaoStatus: null, + dashouzhuangtai: null, + yongjin: null, + zonge: null, + yajin: null, + chenghao: '', + jinfen: null, + clumber: [], + chengjiaoliang: null, + zaixianZhuangtai: null, + + guanshi: { + nicheng: '', + uid: '', + touxiang: '', + gszhstatus: '', + yaoqingzongshu: 0, + fenyongzonge: '0.00', + fenyongtixian: '0.00' + }, + + shangjia: { + sjzhzhuangtai: '', + sjyue: '', + fadanzong: null, + tuikuanzong: null, + riliushui: '', + yueliushui: '' + }, + + liaotian_liebiao: [], + xshenfen: 1, + + goEasyConfig: null, + kefuConfig: { + link: '', + enterpriseId: '' + }, + + cosConfig: { + bucket: '', + region: 'ap-shanghai', + uploadPathPrefix: 'order/' + }, + + goEasyConnection: { + status: 'disconnected', + userId: '', + identityType: '', + lastConnectTime: 0, + autoReconnect: true, + reconnectAttempts: 0, + maxReconnectAttempts: 5, + heartbeatInterval: null, + cacheKeys: { + savedConnection: 'savedGoEasyConnection', + userId: 'goEasyUserId', + identityType: 'currentGoEasyIdentity', + connectTime: 'goEasyConnectTime' + }, + config: { + heartbeatInterval: 20000, + reconnectDelay: 3000, + offlineTimeout: 30000, + cacheValidityHours: 12 + } + }, + + messageManager: { + unreadTotal: 0, + unreadMap: {}, + latestMessages: [], + notificationQueue: [], + notificationVisible: false, + lastNotificationTime: 0, + notificationCooldown: 3000, + tabBarIndex: 2, + showTabBarBadge: true, + customTabBar: true, + tabBarBadgeText: 0, + soundEnabled: true, + vibrationEnabled: true, + doNotDisturb: false, + doNotDisturbStart: '22:00', + doNotDisturbEnd: '08:00', + notificationStyle: { + position: 'top', + duration: 3000, + backgroundColor: '#00f7ff', + textColor: '#ffffff', + borderRadius: '16rpx', + boxShadow: '0 10rpx 30rpx rgba(0, 247, 255, 0.3)' + }, + currentNotification: null, + cacheKeys: { + messageSettings: 'messageSettings', + unreadTotal: 'messageUnreadTotal', + latestMessages: 'latestMessages', + notificationMuted: 'notificationMuted' + } + }, + + pageState: { + currentPage: '', + isInChatPage: false, + currentChatId: '', + lastPageUpdate: 0 + }, + + eventListeners: {}, + debugMode: false, + currentUser: null, + currentRole: 'normal', + primaryRole: 'normal' + }, + + // 核心启动流程 + async onLaunch() { + setClubId(getConfiguredClubId(), this); + this.globalData.clubId = CLUB_ID; + // ① 隐藏返回首页按钮 + wx.hideHomeButton(); + wx.onAppRoute((res) => { + wx.hideHomeButton(); + const path = (res && res.path) || ''; + if (path === 'pages/manager/manager' || path === 'pages/leader/leader') { + migrateLegacyCenterRole(this); + lockPrimaryRole('dashou', this); + } + }); + + // ② 已登录时先恢复子客服身份,再决定主端 Tab(避免客服被当成未入驻) + if (wx.getStorageSync('token')) { + try { + const { restoreStaffContextAfterAuth } = require('./utils/staff-api.js'); + await restoreStaffContextAfterAuth(); + } catch (e) {} + } + + migrateLegacyCenterRole(this); + const savedRole = getPrimaryRole(this); + lockPrimaryRole(savedRole, this); + + // ②b 冷启动:有 token 时先问后端是否需要手机号认证 + if (wx.getStorageSync('token')) { + const phoneOk = await ensurePhoneAuth({ redirect: true }); + 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(() => { + wx.reLaunch({ url: targetPage }); + }, 0); + } + + // ③ 应用本地缓存配置 + const cachedConfig = this.readConfigFromStorage(); + if (cachedConfig) { + this.applyDynamicConfig(cachedConfig); + } + + // ④ 初始化 + this.initGoEasyWithConfig(); // 只有appkey有效时才真正初始化 + ChatCore.initGlobalMessageSystem(this); + this.initCurrentUser(); + if (wx.getStorageSync('token') && this.emitEvent) { + this.emitEvent('staffContextChanged', {}); + } + + // ⑤ 建立连接 + const saved = this.getSavedConnection(); + console.log('【启动】缓存身份:', saved ? saved.identityType : '无', + 'userId:', saved ? saved.userId : '无'); + setTimeout(() => { + if (this.startImWhenReady) this.startImWhenReady(); + else if (this.ensureConnection) this.ensureConnection(); + }, 300); + + // ⑥ 获取远程配置 + this.fetchConfigSafely() + .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(); + console.log('远程配置更新完成'); + }) + .catch(err => { + console.warn('远程配置获取失败,继续使用本地缓存', err); + }); + }, + + /** 从后台切回前台时再次向后端校验 */ + async onShow() { + if (!wx.getStorageSync('token')) return; + await ensurePhoneAuth({ redirect: true }); + }, + + // 连接管理方法 + getSavedConnection() { + try { + const saved = wx.getStorageSync( + this.globalData.goEasyConnection.cacheKeys.savedConnection + ); + return saved || null; + } catch (e) { + return null; + } + }, + + // 配置缓存读写 + 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.getConfigCacheKey()); + if (data && typeof data === 'object') { + return data; + } + } catch (e) {} + return null; + }, + + saveConfigToStorage(config) { + try { + if (config && typeof config === 'object') { + wx.setStorageSync(this.getConfigCacheKey(), config); + } + } catch (e) {} + }, + + // 获取远程配置 + fetchConfigSafely() { + return new Promise((resolve) => { + wx.request({ + url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq', + method: 'POST', + 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); + } else { + console.warn('远程配置接口返回异常,使用已有配置'); + resolve(this.readConfigFromStorage() || {}); + } + }, + fail: (err) => { + console.warn('远程配置网络请求失败,使用已有配置', err); + resolve(this.readConfigFromStorage() || {}); + } + }); + }); + }, + + // 获取远程配置 + fetchDynamicConfig() { + return new Promise((resolve, reject) => { + wx.request({ + url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq', + method: 'POST', + header: buildClubHeaders({ 'content-type': 'application/json' }), + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.code === 0) { + resolve(res.data.data); + } else { + reject(new Error(res.data?.msg || '接口返回错误')); + } + }, + fail: reject + }); + }); + }, + + applyDynamicConfig(config) { + if (!config || typeof config !== 'object') return; + + if (config.cos) { + this.globalData.ossImageUrl = config.cos.ossImageUrl || ''; + this.globalData.cosConfig = { + bucket: config.cos.bucket || '', + region: config.cos.region || 'ap-shanghai', + uploadPathPrefix: config.cos.uploadPathPrefix || 'order/' + }; + } + + if (config.goEasy) { + this.globalData.goEasyConfig = { + host: config.goEasy.host || 'hangzhou.goeasy.io', + appkey: config.goEasy.appkey || '' + }; + } + + if (config.otherConfig) { + this.globalData.morentouxiang = config.otherConfig.morentouxiang || ''; + this.globalData.dashouguize = config.otherConfig.dashouguize || ''; + } + + if (config.kefu) { + this.globalData.kefuConfig = { + link: config.kefu.link || '', + enterpriseId: config.kefu.enterpriseId || '' + }; + } + + try { + applyMiniappAssetsFromConfig(this, config); + } catch (e) { + console.warn('miniapp assets apply failed', e); + } + }, + + // 仅在有效 appkey 时初始化 + initGoEasyWithConfig() { + const cfg = this.globalData.goEasyConfig; + if (!cfg || !cfg.appkey) { + console.warn('GoEasy appkey 未配置,聊天功能将在配置就绪后自动恢复'); + return; + } + + // 避免重复初始化 + if (wx.goEasy && wx.GoEasy) { + console.log('GoEasy 已初始化,跳过'); + return; + } + + try { + wx.goEasy = GoEasy.getInstance({ + host: cfg.host || 'hangzhou.goeasy.io', + appkey: cfg.appkey, + modules: ['im', 'pubsub'] + }); + wx.GoEasy = GoEasy; + console.log('GoEasy 初始化成功'); + if (this.startImWhenReady && wx.getStorageSync('uid')) { + setTimeout(() => this.startImWhenReady(), 100); + } + } catch (error) { + console.error('GoEasy 初始化失败:', error); + } + }, + + initCurrentUser() { + const uid = wx.getStorageSync('uid'); + if (uid) { + this.globalData.currentUser = { + id: uid, + name: '用户' + uid.substring(0, 6), + avatar: this.globalData.ossImageUrl + this.globalData.morentouxiang + }; + } + }, + + emitEvent(eventName, data) { + if (this.globalData.eventListeners[eventName]) { + this.globalData.eventListeners[eventName].forEach(callback => { + try { callback(data); } catch (error) { console.error(error); } + }); + } + }, + + /** 切换角色并通知刷新 */ + setCurrentRole(role) { + if (!role) return; + this.globalData.currentRole = role; + wx.setStorageSync('currentRole', role); + this.emitEvent('currentRoleChanged', { role }); + }, + + on(eventName, callback) { + if (!this.globalData.eventListeners[eventName]) { + this.globalData.eventListeners[eventName] = []; + } + this.globalData.eventListeners[eventName].push(callback); + }, + + off(eventName, callback) { + if (this.globalData.eventListeners[eventName]) { + const index = this.globalData.eventListeners[eventName].indexOf(callback); + if (index > -1) { + this.globalData.eventListeners[eventName].splice(index, 1); + } + } + } }); \ No newline at end of file diff --git a/app.json b/app.json index 1ed971e..bd83c9b 100644 --- a/app.json +++ b/app.json @@ -13,6 +13,9 @@ "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", @@ -23,14 +26,17 @@ "pages/fighter-msg/fighter-msg", "pages/fighter-orders/fighter-orders", "pages/accept-order/accept-order", + "pages/dashou-exam/dashou-exam", "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/merchant-regular-dispatch/merchant-regular-dispatch", "pages/merchant-orders/merchant-orders", "pages/merchant-msg/merchant-msg", + "pages/merchant-penalty/merchant-penalty", "pages/merchant-rank/merchant-rank", "pages/merchant-recharge/merchant-recharge", "pages/merchant-order-detail/merchant-order-detail", @@ -44,7 +50,6 @@ "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", @@ -59,22 +64,33 @@ "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" - - ], - + "subPackages": [ + { + "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", @@ -85,26 +101,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/components/chenghao-tag/chenghao-tag.js b/components/chenghao-tag/chenghao-tag.js index ae79133..2edff62 100644 --- a/components/chenghao-tag/chenghao-tag.js +++ b/components/chenghao-tag/chenghao-tag.js @@ -1,73 +1,106 @@ // 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: Object, value: {} }, }, 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) { + const cfg = raw || {}; + 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, #FFD700, #FF8C00);`; + 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, #FFD700, #FF8C00);' }); + 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 d142188..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 { @@ -87,4 +99,19 @@ @keyframes glow-anim { 0%, 100% { box-shadow: 0 0 8rpx rgba(255, 215, 0, 0.4); } 50% { box-shadow: 0 0 20rpx rgba(255, 215, 0, 0.8), 0 0 40rpx rgba(255, 215, 0, 0.4); } + } + + /* 身份标签闪光 */ + .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/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..628c411 --- /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/config/club-config.js b/config/club-config.js new file mode 100644 index 0000000..7f52960 --- /dev/null +++ b/config/club-config.js @@ -0,0 +1,11 @@ +/** + * 小程序所属俱乐部配置 — 每个小程序工程编译前改这里即可。 + * 星阙 = xq;星之界 = xzj;与 club 表 club_id 一致。 + * 不要依赖后端猜测:前端必须明确所属 club。 + */ +export const CLUB_ID = 'xq'; + +/** 与 club 表 wx_appid 一致,登录时可传给后端反查 club */ +export const WX_APP_ID = 'wx0e4be86faac4a8d1'; + +export const CLUB_NAME = '星阙电竞'; diff --git a/pages/accept-order/accept-order.js b/pages/accept-order/accept-order.js index 749dfe7..c23fe70 100644 --- a/pages/accept-order/accept-order.js +++ b/pages/accept-order/accept-order.js @@ -1,8 +1,14 @@ // 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'; Page({ data: { @@ -29,7 +35,7 @@ Page({ // 4. 刷新控制字段 scrollViewRefreshing: false, lastRefreshTime: 0, - refreshCooldown: 2000, + refreshCooldown: 0, isLoadingMore: false, // 5. 切换类型冷却 @@ -46,13 +52,59 @@ Page({ // 商品轮播展示(只读 globalData,无额外接口) lunboList: [], + gonggao: '', + + examRequired: false, + examPassed: false, + _examChecked: false, }, - async onLoad(options) { - this.syncShopBannerFromGlobal(); + async onLoad() { + if (app.globalData._acceptOrderSessionReady && app.globalData._acceptOrderPageCache) { + const cache = app.globalData._acceptOrderPageCache; + this.setData({ ...cache }); + 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(); + this._skipShowRefresh = 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); + 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 () { @@ -62,63 +114,84 @@ Page({ } }, - onShow() { - this.syncShopBannerFromGlobal(); + async onShow() { + if (!app.globalData._dashouPhoneChecked) { + const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' }); + if (!phoneOk) return; + app.globalData._dashouPhoneChecked = true; + } + this.registerNotificationComponent(); - this.loadGlobalStatus(); + await this.syncDashouProfileFromServer(); + warmupPindaoConfig(app); + if (wx.getStorageSync('uid')) { reconnectForRole('dashou'); if (app.startImWhenReady) app.startImWhenReady(); - this.refreshDashouProfileSilent(); } - if (this.data.xuanzhongLeixingId) { - this.loadDingdanList(true); + + await this.checkExamStatus(false); + + 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(); + } + 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() { @@ -144,9 +217,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', @@ -163,14 +249,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' }); @@ -178,7 +266,7 @@ Page({ } catch (error) { wx.showToast({ title: '网络错误,加载失败', icon: 'none' }); } finally { - wx.hideLoading(); + if (showLoading) wx.hideLoading(); } }, @@ -245,8 +333,8 @@ Page({ xuanzhongBiaoqianId: 0 // 重置标签筛选 }); await this.loadDingdanList(true); - // 🆕 切换类型后加载新标签 await this.loadBankuaiBiaoqian(); + this.persistPageCache(); } catch (error) { console.error('切换类型失败:', error); } finally { @@ -266,19 +354,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({ @@ -298,6 +391,7 @@ Page({ isLoadingMore: false, scrollViewRefreshing: false }); + this._listRequesting = false; if (res.data.code === 200 || res.data.code === 0) { const newList = res.data.data.list || []; @@ -313,6 +407,7 @@ Page({ page: loadPage, hasMore: hasMore }); + this.persistPageCache(); if (isRefresh) { this.setData({ lastRefreshTime: Date.now() }); @@ -327,20 +422,28 @@ 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; @@ -353,25 +456,32 @@ Page({ } const zhidingNicheng = item.zhiding_nicheng || ''; - // 🆕 判断用户是否有订单所需的会员(仅当订单要求会员类型且存在huiyuan_id) + // 订单要求指定会员时,核对用户是否持有该会员类型 let hasRequiredMember = true; if (item.yaoqiuleixing == 1 && item.huiyuan_id) { - const userHasIt = this.data.huiyuanList.some(h => h.huiyuanid == 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, 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, }; @@ -409,11 +519,50 @@ 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; + 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' }); @@ -438,7 +587,7 @@ Page({ } // 校验5: 会员要求 if (dingdanItem.yaoqiuleixing == 1) { - const hasHuiyuan = this.data.huiyuanList.some(h => h.huiyuanid == dingdanItem.huiyuan_id); + const hasHuiyuan = userHasHuiyuan(this.data.huiyuanList, dingdanItem.huiyuan_id); if (!hasHuiyuan) { wx.showToast({ title: '未开通对应会员,无法抢单', @@ -504,6 +653,7 @@ Page({ item => item.dingdan_id !== dingdanItem.dingdan_id ); that.setData({ dingdanList: newList }); + that.persistPageCache(); } else { wx.showToast({ title: qiangdanRes.data.msg || '抢单失败', @@ -535,42 +685,14 @@ Page({ }, onReachBottom() { - const now = Date.now(); - const lastTime = this.data.lastRefreshTime; - const cooldown = this.data.refreshCooldown; - - if (now - lastTime < cooldown) { - wx.showToast({ - title: `操作太快,请${Math.ceil((cooldown - (now - lastTime)) / 1000)}秒后再试`, - icon: 'none', - duration: 1500 - }); - return; - } - 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; @@ -578,14 +700,25 @@ Page({ this.setData({ scrollViewRefreshing: true, - lastRefreshTime: now, + lastRefreshTime: Date.now(), page: 1, - hasMore: true + hasMore: true, }); - if (this.data.xuanzhongLeixingId) { - 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); + } + 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 e7e7be0..d496762 100644 --- a/pages/accept-order/accept-order.json +++ b/pages/accept-order/accept-order.json @@ -1,6 +1,8 @@ { "navigationBarTitleText": "抢单大厅", "navigationBarBackgroundColor": "#f7dc51", + "backgroundColor": "#f7dc51", + "backgroundColorTop": "#f7dc51", "navigationBarTextStyle": "black", "enablePullDownRefresh": false, "backgroundTextStyle": "dark", diff --git a/pages/accept-order/accept-order.wxml b/pages/accept-order/accept-order.wxml index 389b531..4088337 100644 --- a/pages/accept-order/accept-order.wxml +++ b/pages/accept-order/accept-order.wxml @@ -1,4 +1,4 @@ - + @@ -38,6 +38,12 @@ + + + + {{gonggao}} + + @@ -74,7 +80,7 @@ - + @@ -190,16 +196,6 @@ - - - 指定 - - - - {{item.zhiding_nicheng || '指定接单员'}} - - - @@ -212,6 +208,20 @@ + + + + 指定 + + {{item.zhiding_nicheng || '指定接单员'}} + + + + + + + + @@ -226,9 +236,9 @@ - + - 备注:{{item.beizhu}} + 备注:{{item.beizhu || '暂无'}} @@ -236,10 +246,10 @@ 需求标签 - - - - + + + + @@ -260,6 +270,81 @@ + + + + + + + + + + + + + {{item.jieshao || '暂无介绍'}} + + 优质商家 + + + + + + + 指定 + + {{item.zhiding_nicheng || '指定接单员'}} + + + + + + + + + + {{item.dashou_fencheng || 0}} + + + 未开通会员 + + + 商家备注:{{item.beizhu || '暂无'}} + + + + + + + + + + + + + + + + + + + + + + + {{item.sjnicheng || '未知商家'}} + 发布于 {{item.creat_time}} + + + 抢单 + + + 商家派单 + + + + + @@ -276,19 +361,23 @@ - - - 指定 - - - - {{item.zhiding_nicheng || '指定接单员'}} - - + + + + 指定 + + {{item.zhiding_nicheng || '指定接单员'}} + + + + + + + - + {{item.sjnicheng || '未知商家'}} @@ -318,24 +407,33 @@ - + - 商家备注:{{item.beizhu}} + 商家备注:{{item.beizhu || '暂无'}} - + - - - + + + + - + - + + + + + + + + + diff --git a/pages/accept-order/accept-order.wxss b/pages/accept-order/accept-order.wxss index cecbb2b..394b5ff 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 { @@ -763,5 +770,3 @@ margin-left: auto; max-width: 50%; } - -@import '../../styles/dashou-xym-order-card.wxss'; 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/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..34f1557 --- /dev/null +++ b/pages/dashou-exam/dashou-exam.json @@ -0,0 +1,6 @@ +{ + "usingComponents": {}, + "navigationBarTitleText": "接单考试", + "navigationBarBackgroundColor": "#f7dc51", + "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..a7f56b3 --- /dev/null +++ b/pages/dashou-exam/dashou-exam.wxss @@ -0,0 +1,228 @@ +.exam-page { + min-height: 100vh; + background: linear-gradient(180deg, #fff8e1 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, #f7dc51, #ffc0a3); + padding: 22rpx 48rpx; + border-radius: 48rpx; + font-weight: 600; + font-size: 30rpx; + color: #492f00; + 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: #492f00; + 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: #fff8e1; + border: 1rpx solid #f7dc51; + 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: #f7dc51; + 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: #f7dc51; + background: #fff8e1; +} + +.opt-radio-dot { + width: 18rpx; + height: 18rpx; + border-radius: 50%; + background: #f7dc51; +} + +.opt-body { + flex: 1; + display: flex; + gap: 12rpx; + align-items: flex-start; +} + +.opt-label { + font-weight: 700; + color: #492f00; + 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/express-order/express-order.js b/pages/express-order/express-order.js index 5c14b45..c682217 100644 --- a/pages/express-order/express-order.js +++ b/pages/express-order/express-order.js @@ -1,618 +1,666 @@ -// pages/express-order/express-order.js -import request from '../../utils/request.js' - -const PAGE_SIZE = 50 -const LINK_PAGE_SIZE = 5 - -Page({ - data: { - sjyue: '0.00', - shangpinList: [], // 最终展示的商品类型列表(已倒序) - selectedTypeId: null, - selectedHuiyuanId: null, - selectedYaoqiuleixing: null, - currentTypeChenghaoList: [], - - searchParams: { keyword: '', minPrice: '', labelId: '' }, - searchLabelName: '', - isSearchMode: false, - - templateList: [], - currentPage: 1, - hasMoreData: true, - isLoadingMore: false, - - linkMap: {}, - - showDetailModal: false, - showAddModal: false, - showDeleteConfirm: false, - showNewLinkConfirm: false, - - currentTemplate: null, - currentTemplateIndex: -1, - - isEditing: false, - editJieshao: '', - editJiage: '', - editLabelId: '', - editLabelName: '', - editCommissionEnabled: false, - editCommissionValue: '', - - addJieshao: '', - addJiage: '', - addLabelId: '', - addLabelName: '', - addCommissionEnabled: false, - addCommissionValue: '', - - detailLinkFilter: 0, - detailLinkList: [], - detailLinkPage: 1, - detailLinkHasMore: true, - isLoadingLinks: false, - - isLoading: false - }, - - onLoad() { - this.loadShangjiaYue() - this.loadShangpinTypes() - }, - - onShow() { - this.registerNotificationComponent() - this.loadShangjiaYue() - }, - - registerNotificationComponent() { - const app = getApp() - const comp = this.selectComponent('#global-notification') - if (comp && comp.showNotification) { - app.globalData.globalNotification = { - show: data => comp.showNotification(data), - hide: () => comp.hideNotification() - } - } - }, - - loadShangjiaYue() { - const app = getApp() - const data = app.globalData.shangjia || {} - this.setData({ sjyue: data.sjyue || '0.00' }) - }, - - async loadShangpinTypes() { - this.setData({ isLoading: true }) - try { - const res = await request({ url: '/dingdan/sjspleixing', method: 'POST' }) - if (res && res.data.code === 200) { - let list = res.data.data || [] - // 强制倒序排列 - list.reverse() - this.setData({ shangpinList: list, isLoading: false }) - if (list.length > 0) { - this.setSelectedType(list[0]) - } - } - } catch (e) { - console.error(e) - this.setData({ isLoading: false }) - wx.showToast({ title: '加载类型失败', icon: 'error' }) - } - }, - - setSelectedType(item) { - this.setData({ - selectedTypeId: item.id, - selectedHuiyuanId: item.huiyuan_id, - selectedYaoqiuleixing: item.yaoqiuleixing, - currentTypeChenghaoList: item.chenghaoList || [] - }, () => { - this.resetAndLoadTemplates() - }) - }, - - resetAndLoadTemplates() { - this.setData({ - templateList: [], - currentPage: 1, - hasMoreData: true, - isSearchMode: false, - searchParams: { keyword: '', minPrice: '', labelId: '' }, - searchLabelName: '' - }) - this.loadTemplates() - }, - - async loadTemplates() { - const { selectedTypeId, currentPage, isSearchMode, searchParams, linkMap } = this.data - if (!selectedTypeId) { - wx.showToast({ title: '请先选择订单类型', icon: 'none' }) - return - } - if (currentPage === 1) this.setData({ isLoading: true }) - else this.setData({ isLoadingMore: true }) - - try { - const postData = { - shangpinTypeId: selectedTypeId, - page: currentPage, - pageSize: PAGE_SIZE, - getType: isSearchMode ? 1 : 2 - } - if (isSearchMode) { - if (searchParams.keyword) postData.keyword = searchParams.keyword.trim() - if (searchParams.minPrice) postData.minPrice = searchParams.minPrice - if (searchParams.labelId) postData.labelId = searchParams.labelId - } - - const res = await request({ url: '/peizhi/sjddmblb', method: 'POST', data: postData }) - if (res && res.data.code === 200) { - const data = res.data.data || {} - const newTemplates = data.list || [] - const existingIds = new Set(this.data.templateList.map(item => item.mobanId)) - const uniqueNew = newTemplates.filter(item => !existingIds.has(item.mobanId)) - - const processed = uniqueNew.map(item => { - const mobanId = item.mobanId || item.id - const cached = linkMap[mobanId] || {} - return { - ...item, - hasGenerated: cached.hasGenerated || !!item.linkUrl, - isCopied: cached.isCopied || false, - linkUrl: cached.linkUrl || item.linkUrl || '', - mobanId: mobanId, - jieshao: item.jieshao || item.moban_jieshao, - jiage: item.jiage || item.price, - fabushuliang: item.fabushuliang || item.fabu_shuliang || 0, - labelId: item.labelId, - labelName: item.labelName || '', - commissionEnabled: item.commissionEnabled, - commissionValue: item.commissionValue - } - }) - - this.setData({ - templateList: currentPage === 1 ? processed : [...this.data.templateList, ...processed], - hasMoreData: data.hasMore !== false && processed.length === PAGE_SIZE, - isLoading: false, - isLoadingMore: false - }) - } else { - this.setData({ isLoading: false, isLoadingMore: false, hasMoreData: false }) - } - } catch (e) { - console.error(e) - this.setData({ isLoading: false, isLoadingMore: false }) - wx.showToast({ title: '加载模板失败', icon: 'error' }) - } - }, - - // ---------- 搜索相关 ---------- - onSearchKeywordInput(e) { this.setData({ 'searchParams.keyword': e.detail.value }) }, - onSearchMinPriceInput(e) { this.setData({ 'searchParams.minPrice': e.detail.value }) }, - onSearchLabelChange(e) { - const idx = e.detail.value - const label = this.data.currentTypeChenghaoList[idx] - this.setData({ - 'searchParams.labelId': label ? label.id : '', - searchLabelName: label ? label.mingcheng : '' - }) - }, - onSearchConfirm() { - const { searchParams } = this.data - if (!searchParams.keyword && !searchParams.minPrice && !searchParams.labelId) { - this.resetAndLoadTemplates() - return - } - this.setData({ - isSearchMode: true, - currentPage: 1, - templateList: [], - hasMoreData: true - }, () => this.loadTemplates()) - }, - onClearSearch() { this.resetAndLoadTemplates() }, - - // ---------- 卡片生成链接 ---------- - async onGenerateLink(e) { - const { id } = e.currentTarget.dataset - const idx = this.data.templateList.findIndex(item => item.mobanId === id) - if (idx === -1) return - await this.generateLinkForTemplate(this.data.templateList[idx], idx) - }, - - onCopyLink(e) { - const { id } = e.currentTarget.dataset - const idx = this.data.templateList.findIndex(item => item.mobanId === id) - if (idx === -1) return - const template = this.data.templateList[idx] - if (!template.linkUrl) { - wx.showToast({ title: '请先生成链接', icon: 'none' }) - return - } - wx.setClipboardData({ - data: template.linkUrl, - success: () => { - const { linkMap } = this.data - const mobanId = template.mobanId - const updatedLinkMap = { ...linkMap } - if (updatedLinkMap[mobanId]) updatedLinkMap[mobanId].isCopied = true - const updatedList = [...this.data.templateList] - updatedList[idx].isCopied = true - this.setData({ linkMap: updatedLinkMap, templateList: updatedList }) - wx.showToast({ title: '链接已复制', icon: 'success' }) - } - }) - }, - - async generateLinkForTemplate(template, templateIndex) { - const { selectedTypeId, linkMap } = this.data - if (!selectedTypeId) { - wx.showToast({ title: '请选择订单类型', icon: 'none' }) - return - } - wx.showLoading({ title: '生成中...', mask: true }) - try { - const res = await request({ - url: '/peizhi/sjljhq', - method: 'POST', - data: { mobanId: template.mobanId, shangpinTypeId: selectedTypeId } - }) - if (res && res.data.code === 200) { - const data = res.data.data || {} - const mobanId = template.mobanId - const updatedLinkMap = { ...linkMap } - updatedLinkMap[mobanId] = { - hasGenerated: true, - linkUrl: data.linkUrl || '', - isCopied: false, - newBalance: data.newBalance - } - const updatedList = [...this.data.templateList] - updatedList[templateIndex] = { - ...updatedList[templateIndex], - hasGenerated: true, - linkUrl: data.linkUrl || '', - isCopied: false, - newBalance: data.newBalance - } - if (data.newBalance) { - const app = getApp() - if (app.globalData.shangjia) app.globalData.shangjia.sjyue = data.newBalance - this.setData({ sjyue: data.newBalance }) - } - this.setData({ linkMap: updatedLinkMap, templateList: updatedList }) - wx.showToast({ title: '链接生成成功', icon: 'success' }) - } else { - wx.showToast({ title: res.data.msg || '生成失败', icon: 'error' }) - } - } catch (e) { - wx.showToast({ title: '生成失败', icon: 'error' }) - } finally { - wx.hideLoading() - } - }, - - onCardTap(e) { - const { target } = e - if (target.className && (target.className.includes('btn') || target.className.includes('action'))) return - const { item } = e.currentTarget.dataset - if (!item) return - const idx = this.data.templateList.findIndex(t => t.mobanId === item.mobanId) - this.setData({ - currentTemplate: item, - currentTemplateIndex: idx, - showDetailModal: true, - isEditing: false, - editJieshao: item.jieshao || '', - editJiage: item.jiage || '', - editLabelId: item.labelId || '', - editLabelName: item.labelName || '', - editCommissionEnabled: item.commissionEnabled || false, - editCommissionValue: item.commissionValue || '', - detailLinkFilter: 0, - detailLinkList: [], - detailLinkPage: 1, - detailLinkHasMore: true - }) - this.loadDetailLinks() - }, - - hideDetailModal() { this.setData({ showDetailModal: false, isEditing: false }) }, - showAddModal() { - this.setData({ - showAddModal: true, - addJieshao: '', addJiage: '', addLabelId: '', addLabelName: '', - addCommissionEnabled: false, addCommissionValue: '' - }) - }, - hideAddModal() { this.setData({ showAddModal: false }) }, - hideDeleteConfirm() { this.setData({ showDeleteConfirm: false }) }, - hideNewLinkConfirm() { this.setData({ showNewLinkConfirm: false }) }, - - // ---------- 链接筛选与加载 ---------- - onToggleLinkFilter() { - const newFilter = this.data.detailLinkFilter === 0 ? 1 : 0 - this.setData({ - detailLinkFilter: newFilter, - detailLinkList: [], - detailLinkPage: 1, - detailLinkHasMore: true - }, () => this.loadDetailLinks()) - }, - - async loadDetailLinks() { - const { currentTemplate, detailLinkFilter, detailLinkPage, isLoadingLinks } = this.data - if (!currentTemplate || isLoadingLinks) return - this.setData({ isLoadingLinks: true }) - try { - const res = await request({ - url: '/peizhi/hqsjlslj', - method: 'POST', - data: { - mobanId: currentTemplate.mobanId, - used: detailLinkFilter, - page: detailLinkPage, - pageSize: LINK_PAGE_SIZE - } - }) - if (res && res.data.code === 200) { - const data = res.data.data || {} - const newLinks = data.list || [] - const merged = detailLinkPage === 1 ? newLinks : [...this.data.detailLinkList, ...newLinks] - this.setData({ - detailLinkList: merged, - detailLinkHasMore: newLinks.length === LINK_PAGE_SIZE, - isLoadingLinks: false - }) - } else { - this.setData({ isLoadingLinks: false }) - } - } catch (e) { - this.setData({ isLoadingLinks: false }) - wx.showToast({ title: '加载链接失败', icon: 'error' }) - } - }, - - onLoadMoreLinks() { - if (!this.data.detailLinkHasMore || this.data.isLoadingLinks) return - this.setData({ detailLinkPage: this.data.detailLinkPage + 1 }, () => this.loadDetailLinks()) - }, - - onCopyLinkItem(e) { - const url = e.currentTarget.dataset.url - if (!url) return - wx.setClipboardData({ data: url, success: () => wx.showToast({ title: '已复制', icon: 'success' }) }) - }, - - // ---------- 弹窗内操作 ---------- - onGenerateLinkModal() { - const { currentTemplate, currentTemplateIndex } = this.data - if (!currentTemplate) return - if (currentTemplate.hasGenerated && !currentTemplate.isCopied) { - this.setData({ showNewLinkConfirm: true }) - return - } - this.generateLinkForTemplate(currentTemplate, currentTemplateIndex) - }, - - onCopyLinkModal() { - const { currentTemplate, linkMap } = this.data - if (!currentTemplate || !currentTemplate.linkUrl) { - wx.showToast({ title: '请先生成链接', icon: 'none' }) - return - } - wx.setClipboardData({ - data: currentTemplate.linkUrl, - success: () => { - const mobanId = currentTemplate.mobanId - const updatedLinkMap = { ...linkMap } - if (updatedLinkMap[mobanId]) updatedLinkMap[mobanId].isCopied = true - const updatedList = [...this.data.templateList] - if (this.data.currentTemplateIndex !== -1) { - updatedList[this.data.currentTemplateIndex].isCopied = true - } - const updatedCurrent = { ...currentTemplate, isCopied: true } - this.setData({ linkMap: updatedLinkMap, templateList: updatedList, currentTemplate: updatedCurrent }) - wx.showToast({ title: '链接已复制', icon: 'success' }) - } - }) - }, - - // ---------- 编辑 ---------- - onEditTemplate() { - if (!this.data.isEditing) this.setData({ isEditing: true }) - else this.saveTemplateEdit() - }, - onEditJieshaoInput(e) { this.setData({ editJieshao: e.detail.value }) }, - onEditJiageInput(e) { this.setData({ editJiage: e.detail.value }) }, - onEditLabelChange(e) { - const idx = e.detail.value - const label = this.data.currentTypeChenghaoList[idx] - this.setData({ editLabelId: label ? label.id : '', editLabelName: label ? label.mingcheng : '' }) - }, - onEditCommissionToggle() { this.setData({ editCommissionEnabled: !this.data.editCommissionEnabled }) }, - onEditCommissionValueInput(e) { this.setData({ editCommissionValue: e.detail.value }) }, - - async saveTemplateEdit() { - const { currentTemplate, selectedTypeId, editJieshao, editJiage, editLabelId, editCommissionEnabled, editCommissionValue } = this.data - if (!currentTemplate || !selectedTypeId) return - if (editCommissionEnabled && (!editCommissionValue || isNaN(parseFloat(editCommissionValue)))) { - wx.showToast({ title: '请输入有效佣金金额', icon: 'none' }) - return - } - wx.showLoading({ title: '保存中...', mask: true }) - try { - const res = await request({ - url: '/peizhi/sjddmbgx', - method: 'POST', - data: { - mobanId: currentTemplate.mobanId, - shangpinTypeId: selectedTypeId, - jieshao: editJieshao, - jiage: editJiage, - labelId: editLabelId || '', - commissionEnabled: editCommissionEnabled, - commissionValue: editCommissionValue - } - }) - if (res && res.data.code === 200) { - const updatedList = [...this.data.templateList] - const idx = this.data.currentTemplateIndex - if (idx !== -1) { - updatedList[idx] = { - ...updatedList[idx], - jieshao: editJieshao, - jiage: editJiage, - labelId: editLabelId, - labelName: this.getLabelNameById(editLabelId), - commissionEnabled: editCommissionEnabled, - commissionValue: editCommissionValue - } - this.setData({ templateList: updatedList, currentTemplate: updatedList[idx], isEditing: false }) - } - wx.showToast({ title: '修改成功', icon: 'success' }) - } else { - wx.showToast({ title: res.data.msg || '修改失败', icon: 'error' }) - } - } catch (e) { - wx.showToast({ title: '修改失败', icon: 'error' }) - } finally { wx.hideLoading() } - }, - - onDeleteTemplate() { this.setData({ showDeleteConfirm: true }) }, - - async confirmDeleteTemplate() { - const { currentTemplate, selectedTypeId, linkMap } = this.data - if (!currentTemplate || !selectedTypeId) return - wx.showLoading({ title: '删除中...', mask: true }) - try { - const res = await request({ - url: '/peizhi/sjddmbsc', - method: 'POST', - data: { mobanId: currentTemplate.mobanId, shangpinTypeId: selectedTypeId } - }) - if (res && res.data.code === 200) { - const updatedLinkMap = { ...linkMap } - delete updatedLinkMap[currentTemplate.mobanId] - const updatedList = [...this.data.templateList] - updatedList.splice(this.data.currentTemplateIndex, 1) - this.setData({ - linkMap: updatedLinkMap, - templateList: updatedList, - showDetailModal: false, - showDeleteConfirm: false, - currentTemplate: null, - currentTemplateIndex: -1 - }) - wx.showToast({ title: '删除成功', icon: 'success' }) - } else { - wx.showToast({ title: res.data.msg || '删除失败', icon: 'error' }) - } - } catch (e) { - wx.showToast({ title: '删除失败', icon: 'error' }) - } finally { wx.hideLoading() } - }, - - async confirmNewLink() { - const { currentTemplate, currentTemplateIndex } = this.data - if (!currentTemplate) return - this.setData({ showNewLinkConfirm: false }) - await this.generateLinkForTemplate(currentTemplate, currentTemplateIndex) - }, - - // ---------- 添加模板 ---------- - onAddJieshaoInput(e) { this.setData({ addJieshao: e.detail.value }) }, - onAddJiageInput(e) { this.setData({ addJiage: e.detail.value }) }, - onAddLabelChange(e) { - const idx = e.detail.value - const label = this.data.currentTypeChenghaoList[idx] - this.setData({ addLabelId: label ? label.id : '', addLabelName: label ? label.mingcheng : '' }) - }, - onAddCommissionToggle() { this.setData({ addCommissionEnabled: !this.data.addCommissionEnabled }) }, - onAddCommissionValueInput(e) { this.setData({ addCommissionValue: e.detail.value }) }, - - async onAddTemplate() { - const { selectedTypeId, addJieshao, addJiage, addLabelId, addCommissionEnabled, addCommissionValue } = this.data - if (!selectedTypeId) { - wx.showToast({ title: '请选择订单类型', icon: 'none' }) - return - } - if (!addJieshao.trim()) { - wx.showToast({ title: '请输入订单介绍', icon: 'none' }) - return - } - if (!addJiage || isNaN(parseFloat(addJiage)) || parseFloat(addJiage) <= 0) { - wx.showToast({ title: '请输入有效的价格', icon: 'none' }) - return - } - if (addCommissionEnabled && (!addCommissionValue || isNaN(parseFloat(addCommissionValue)))) { - wx.showToast({ title: '请输入有效的佣金金额', icon: 'none' }) - return - } - - wx.showLoading({ title: '添加中...', mask: true }) - try { - const res = await request({ - url: '/peizhi/sjtjddmb', - method: 'POST', - data: { - shangpinTypeId: selectedTypeId, - jieshao: addJieshao.trim(), - jiage: addJiage, - labelId: addLabelId || '', - commissionEnabled: addCommissionEnabled, - commissionValue: addCommissionValue - } - }) - if (res && res.data.code === 200) { - const data = res.data.data || {} - const newTemplate = { - mobanId: data.mobanId, - jieshao: addJieshao, - jiage: addJiage, - fabushuliang: 0, - hasGenerated: false, - isCopied: false, - linkUrl: '', - labelId: addLabelId, - labelName: this.getLabelNameById(addLabelId), - commissionEnabled: addCommissionEnabled, - commissionValue: addCommissionValue - } - this.setData({ - templateList: [newTemplate, ...this.data.templateList], - showAddModal: false, - addJieshao: '', addJiage: '', addLabelId: '', addLabelName: '', - addCommissionEnabled: false, addCommissionValue: '' - }) - wx.showToast({ title: '添加成功', icon: 'success' }) - } else { - wx.showToast({ title: res.data.msg || '添加失败', icon: 'error' }) - } - } catch (e) { - wx.showToast({ title: '添加失败', icon: 'error' }) - } finally { wx.hideLoading() } - }, - - getLabelNameById(labelId) { - if (!labelId) return '' - const list = this.data.currentTypeChenghaoList - const found = list.find(item => item.id == labelId) - return found ? found.mingcheng : '' - }, - - onScrollToLower() { - const { hasMoreData, isLoadingMore, isSearchMode } = this.data - if (!hasMoreData || isLoadingMore || isSearchMode) return - this.setData({ currentPage: this.data.currentPage + 1 }, () => this.loadTemplates()) - }, - - onSelectType(e) { - const { id, item } = e.currentTarget.dataset - this.setSelectedType(item) - } +// pages/express-order/express-order.js +import request from '../../utils/request.js' +import { STAFF_API, isStaffMode, getStaffContext, setStaffContext, syncStaffUi, staffOrOwner, refreshStaffContext, restoreStaffContextAfterAuth, isMerchantPortalUser } from '../../utils/staff-api.js' + +const PAGE_SIZE = 50 +const LINK_PAGE_SIZE = 5 + +function apiOk(res) { + const code = res && res.data && res.data.code + return code === 200 || code === 0 +} + +Page({ + data: { + sjyue: '0.00', + shangpinList: [], // 最终展示的商品类型列表(已倒序) + selectedTypeId: null, + selectedHuiyuanId: null, + selectedYaoqiuleixing: null, + currentTypeChenghaoList: [], + + searchParams: { keyword: '', minPrice: '', labelId: '' }, + searchLabelName: '', + isSearchMode: false, + + templateList: [], + currentPage: 1, + hasMoreData: true, + isLoadingMore: false, + + linkMap: {}, + + showDetailModal: false, + showAddModal: false, + showDeleteConfirm: false, + showNewLinkConfirm: false, + + currentTemplate: null, + currentTemplateIndex: -1, + + isEditing: false, + editJieshao: '', + editJiage: '', + editLabelId: '', + editLabelName: '', + editCommissionEnabled: false, + editCommissionValue: '', + + addJieshao: '', + addJiage: '', + addLabelId: '', + addLabelName: '', + addCommissionEnabled: false, + addCommissionValue: '', + + detailLinkFilter: 0, + detailLinkList: [], + detailLinkPage: 1, + detailLinkHasMore: true, + isLoadingLinks: false, + + isLoading: false, + canTemplate: true, + isStaffMode: false, + staffRoleName: '', + }, + + async onLoad() { + if (wx.getStorageSync('token')) { + await restoreStaffContextAfterAuth() + } + if (!isMerchantPortalUser()) { + wx.showToast({ title: '请先成为商家或绑定客服', icon: 'none' }) + setTimeout(() => wx.navigateBack(), 1500) + return + } + syncStaffUi(this) + this.loadShangjiaYue() + this.loadShangpinTypes() + }, + + onShow() { + this.registerNotificationComponent() + syncStaffUi(this) + this.loadShangjiaYue() + }, + + registerNotificationComponent() { + const app = getApp() + const comp = this.selectComponent('#global-notification') + if (comp && comp.showNotification) { + app.globalData.globalNotification = { + show: data => comp.showNotification(data), + hide: () => comp.hideNotification() + } + } + }, + + loadShangjiaYue() { + if (isStaffMode()) { + refreshStaffContext(request).then((ctx) => { + const wallet = (ctx && ctx.wallet) || (getStaffContext() || {}).wallet || {}; + this.setData({ sjyue: wallet.quota_available || '0.00' }); + }).catch(() => { + const ctx = getStaffContext() || {}; + const wallet = ctx.wallet || {}; + this.setData({ sjyue: wallet.quota_available || '0.00' }); + }); + return; + } + const app = getApp() + const data = app.globalData.shangjia || {} + this.setData({ sjyue: data.sjyue || '0.00' }) + }, + + async loadShangpinTypes() { + this.setData({ isLoading: true }) + const staff = isStaffMode() + try { + const res = await request({ + url: staff ? STAFF_API.productTypes : '/dingdan/sjspleixing', + method: 'POST', + }) + const code = res && res.data && res.data.code + if (code === 200 || code === 0) { + let list = res.data.data || [] + if (!Array.isArray(list) && list.list) list = list.list + // 强制倒序排列 + list.reverse() + this.setData({ shangpinList: list, isLoading: false }) + if (list.length > 0) { + this.setSelectedType(list[0]) + } + } else { + throw new Error((res.data && res.data.msg) || '加载失败') + } + } catch (e) { + console.error(e) + this.setData({ isLoading: false }) + wx.showToast({ title: e.message || '加载类型失败', icon: 'none' }) + } + }, + + setSelectedType(item) { + this.setData({ + selectedTypeId: item.id, + selectedHuiyuanId: item.huiyuan_id, + selectedYaoqiuleixing: item.yaoqiuleixing, + currentTypeChenghaoList: item.chenghaoList || [] + }, () => { + this.resetAndLoadTemplates() + }) + }, + + resetAndLoadTemplates() { + this.setData({ + templateList: [], + currentPage: 1, + hasMoreData: true, + isSearchMode: false, + searchParams: { keyword: '', minPrice: '', labelId: '' }, + searchLabelName: '' + }) + this.loadTemplates() + }, + + async loadTemplates() { + const { selectedTypeId, currentPage, isSearchMode, searchParams, linkMap } = this.data + if (!selectedTypeId) { + wx.showToast({ title: '请先选择订单类型', icon: 'none' }) + return + } + if (currentPage === 1) this.setData({ isLoading: true }) + else this.setData({ isLoadingMore: true }) + + try { + const postData = { + shangpinTypeId: selectedTypeId, + page: currentPage, + pageSize: PAGE_SIZE, + getType: isSearchMode ? 1 : 2 + } + if (isSearchMode) { + if (searchParams.keyword) postData.keyword = searchParams.keyword.trim() + if (searchParams.minPrice) postData.minPrice = searchParams.minPrice + if (searchParams.labelId) postData.labelId = searchParams.labelId + } + + const res = await request({ url: staffOrOwner(STAFF_API.templateList, '/peizhi/sjddmblb'), method: 'POST', data: postData }) + if (res && apiOk(res)) { + const data = res.data.data || {} + const newTemplates = data.list || [] + const existingIds = new Set(this.data.templateList.map(item => item.mobanId)) + const uniqueNew = newTemplates.filter(item => !existingIds.has(item.mobanId)) + + const processed = uniqueNew.map(item => { + const mobanId = item.mobanId || item.id + const cached = linkMap[mobanId] || {} + return { + ...item, + hasGenerated: cached.hasGenerated || !!item.linkUrl, + isCopied: cached.isCopied || false, + linkUrl: cached.linkUrl || item.linkUrl || '', + mobanId: mobanId, + jieshao: item.jieshao || item.moban_jieshao, + jiage: item.jiage || item.price, + fabushuliang: item.fabushuliang || item.fabu_shuliang || 0, + labelId: item.labelId, + labelName: item.labelName || '', + commissionEnabled: item.commissionEnabled, + commissionValue: item.commissionValue + } + }) + + this.setData({ + templateList: currentPage === 1 ? processed : [...this.data.templateList, ...processed], + hasMoreData: data.hasMore !== false && processed.length === PAGE_SIZE, + isLoading: false, + isLoadingMore: false + }) + } else { + this.setData({ isLoading: false, isLoadingMore: false, hasMoreData: false }) + } + } catch (e) { + console.error(e) + this.setData({ isLoading: false, isLoadingMore: false }) + wx.showToast({ title: '加载模板失败', icon: 'error' }) + } + }, + + // ---------- 搜索相关 ---------- + onSearchKeywordInput(e) { this.setData({ 'searchParams.keyword': e.detail.value }) }, + onSearchMinPriceInput(e) { this.setData({ 'searchParams.minPrice': e.detail.value }) }, + onSearchLabelChange(e) { + const idx = e.detail.value + const label = this.data.currentTypeChenghaoList[idx] + this.setData({ + 'searchParams.labelId': label ? label.id : '', + searchLabelName: label ? label.mingcheng : '' + }) + }, + onSearchConfirm() { + const { searchParams } = this.data + if (!searchParams.keyword && !searchParams.minPrice && !searchParams.labelId) { + this.resetAndLoadTemplates() + return + } + this.setData({ + isSearchMode: true, + currentPage: 1, + templateList: [], + hasMoreData: true + }, () => this.loadTemplates()) + }, + onClearSearch() { this.resetAndLoadTemplates() }, + + // ---------- 卡片生成链接 ---------- + async onGenerateLink(e) { + const { id } = e.currentTarget.dataset + const idx = this.data.templateList.findIndex(item => item.mobanId === id) + if (idx === -1) return + await this.generateLinkForTemplate(this.data.templateList[idx], idx) + }, + + onCopyLink(e) { + const { id } = e.currentTarget.dataset + const idx = this.data.templateList.findIndex(item => item.mobanId === id) + if (idx === -1) return + const template = this.data.templateList[idx] + if (!template.linkUrl) { + wx.showToast({ title: '请先生成链接', icon: 'none' }) + return + } + wx.setClipboardData({ + data: template.linkUrl, + success: () => { + const { linkMap } = this.data + const mobanId = template.mobanId + const updatedLinkMap = { ...linkMap } + if (updatedLinkMap[mobanId]) updatedLinkMap[mobanId].isCopied = true + const updatedList = [...this.data.templateList] + updatedList[idx].isCopied = true + this.setData({ linkMap: updatedLinkMap, templateList: updatedList }) + wx.showToast({ title: '链接已复制', icon: 'success' }) + } + }) + }, + + async generateLinkForTemplate(template, templateIndex) { + const { selectedTypeId, linkMap } = this.data + if (!selectedTypeId) { + wx.showToast({ title: '请选择订单类型', icon: 'none' }) + return + } + wx.showLoading({ title: '生成中...', mask: true }) + try { + const res = await request({ + url: staffOrOwner(STAFF_API.linkGenerate, '/peizhi/sjljhq'), + method: 'POST', + data: { mobanId: template.mobanId, shangpinTypeId: selectedTypeId } + }) + if (res && apiOk(res)) { + const data = res.data.data || {} + const mobanId = template.mobanId + const updatedLinkMap = { ...linkMap } + updatedLinkMap[mobanId] = { + hasGenerated: true, + linkUrl: data.linkUrl || '', + isCopied: false, + newBalance: data.newBalance + } + const updatedList = [...this.data.templateList] + updatedList[templateIndex] = { + ...updatedList[templateIndex], + hasGenerated: true, + linkUrl: data.linkUrl || '', + isCopied: false, + newBalance: data.newBalance + } + if (data.newBalance || data.quotaAvailable) { + const balance = data.quotaAvailable || data.newBalance + if (isStaffMode()) { + const ctx = getStaffContext() || {} + if (ctx.wallet) { + ctx.wallet.quota_available = balance + setStaffContext(ctx) + } + this.setData({ sjyue: balance }) + } else { + const app = getApp() + if (app.globalData.shangjia) app.globalData.shangjia.sjyue = balance + this.setData({ sjyue: balance }) + } + } + this.setData({ linkMap: updatedLinkMap, templateList: updatedList }) + wx.showToast({ title: '链接生成成功', icon: 'success' }) + } else { + wx.showToast({ title: res.data.msg || '生成失败', icon: 'error' }) + } + } catch (e) { + wx.showToast({ title: '生成失败', icon: 'error' }) + } finally { + wx.hideLoading() + } + }, + + onCardTap(e) { + const { target } = e + if (target.className && (target.className.includes('btn') || target.className.includes('action'))) return + const { item } = e.currentTarget.dataset + if (!item) return + const idx = this.data.templateList.findIndex(t => t.mobanId === item.mobanId) + this.setData({ + currentTemplate: item, + currentTemplateIndex: idx, + showDetailModal: true, + isEditing: false, + editJieshao: item.jieshao || '', + editJiage: item.jiage || '', + editLabelId: item.labelId || '', + editLabelName: item.labelName || '', + editCommissionEnabled: item.commissionEnabled || false, + editCommissionValue: item.commissionValue || '', + detailLinkFilter: 0, + detailLinkList: [], + detailLinkPage: 1, + detailLinkHasMore: true + }) + this.loadDetailLinks() + }, + + hideDetailModal() { this.setData({ showDetailModal: false, isEditing: false }) }, + showAddModal() { + this.setData({ + showAddModal: true, + addJieshao: '', addJiage: '', addLabelId: '', addLabelName: '', + addCommissionEnabled: false, addCommissionValue: '' + }) + }, + hideAddModal() { this.setData({ showAddModal: false }) }, + hideDeleteConfirm() { this.setData({ showDeleteConfirm: false }) }, + hideNewLinkConfirm() { this.setData({ showNewLinkConfirm: false }) }, + + // ---------- 链接筛选与加载 ---------- + onToggleLinkFilter() { + const newFilter = this.data.detailLinkFilter === 0 ? 1 : 0 + this.setData({ + detailLinkFilter: newFilter, + detailLinkList: [], + detailLinkPage: 1, + detailLinkHasMore: true + }, () => this.loadDetailLinks()) + }, + + async loadDetailLinks() { + const { currentTemplate, detailLinkFilter, detailLinkPage, isLoadingLinks } = this.data + if (!currentTemplate || isLoadingLinks) return + this.setData({ isLoadingLinks: true }) + try { + const res = await request({ + url: staffOrOwner(STAFF_API.linkList, '/peizhi/hqsjlslj'), + method: 'POST', + data: { + mobanId: currentTemplate.mobanId, + used: detailLinkFilter, + page: detailLinkPage, + pageSize: LINK_PAGE_SIZE + } + }) + if (res && apiOk(res)) { + const data = res.data.data || {} + const newLinks = data.list || [] + const merged = detailLinkPage === 1 ? newLinks : [...this.data.detailLinkList, ...newLinks] + this.setData({ + detailLinkList: merged, + detailLinkHasMore: newLinks.length === LINK_PAGE_SIZE, + isLoadingLinks: false + }) + } else { + this.setData({ isLoadingLinks: false }) + } + } catch (e) { + this.setData({ isLoadingLinks: false }) + wx.showToast({ title: '加载链接失败', icon: 'error' }) + } + }, + + onLoadMoreLinks() { + if (!this.data.detailLinkHasMore || this.data.isLoadingLinks) return + this.setData({ detailLinkPage: this.data.detailLinkPage + 1 }, () => this.loadDetailLinks()) + }, + + onCopyLinkItem(e) { + const url = e.currentTarget.dataset.url + if (!url) return + wx.setClipboardData({ data: url, success: () => wx.showToast({ title: '已复制', icon: 'success' }) }) + }, + + // ---------- 弹窗内操作 ---------- + onGenerateLinkModal() { + const { currentTemplate, currentTemplateIndex } = this.data + if (!currentTemplate) return + if (currentTemplate.hasGenerated && !currentTemplate.isCopied) { + this.setData({ showNewLinkConfirm: true }) + return + } + this.generateLinkForTemplate(currentTemplate, currentTemplateIndex) + }, + + onCopyLinkModal() { + const { currentTemplate, linkMap } = this.data + if (!currentTemplate || !currentTemplate.linkUrl) { + wx.showToast({ title: '请先生成链接', icon: 'none' }) + return + } + wx.setClipboardData({ + data: currentTemplate.linkUrl, + success: () => { + const mobanId = currentTemplate.mobanId + const updatedLinkMap = { ...linkMap } + if (updatedLinkMap[mobanId]) updatedLinkMap[mobanId].isCopied = true + const updatedList = [...this.data.templateList] + if (this.data.currentTemplateIndex !== -1) { + updatedList[this.data.currentTemplateIndex].isCopied = true + } + const updatedCurrent = { ...currentTemplate, isCopied: true } + this.setData({ linkMap: updatedLinkMap, templateList: updatedList, currentTemplate: updatedCurrent }) + wx.showToast({ title: '链接已复制', icon: 'success' }) + } + }) + }, + + // ---------- 编辑 ---------- + onEditTemplate() { + if (!this.data.isEditing) this.setData({ isEditing: true }) + else this.saveTemplateEdit() + }, + onEditJieshaoInput(e) { this.setData({ editJieshao: e.detail.value }) }, + onEditJiageInput(e) { this.setData({ editJiage: e.detail.value }) }, + onEditLabelChange(e) { + const idx = e.detail.value + const label = this.data.currentTypeChenghaoList[idx] + this.setData({ editLabelId: label ? label.id : '', editLabelName: label ? label.mingcheng : '' }) + }, + onEditCommissionToggle() { this.setData({ editCommissionEnabled: !this.data.editCommissionEnabled }) }, + onEditCommissionValueInput(e) { this.setData({ editCommissionValue: e.detail.value }) }, + + async saveTemplateEdit() { + const { currentTemplate, selectedTypeId, editJieshao, editJiage, editLabelId, editCommissionEnabled, editCommissionValue } = this.data + if (!currentTemplate || !selectedTypeId) return + if (editCommissionEnabled && (!editCommissionValue || isNaN(parseFloat(editCommissionValue)))) { + wx.showToast({ title: '请输入有效佣金金额', icon: 'none' }) + return + } + wx.showLoading({ title: '保存中...', mask: true }) + try { + const res = await request({ + url: staffOrOwner(STAFF_API.templateUpdate, '/peizhi/sjddmbgx'), + method: 'POST', + data: { + mobanId: currentTemplate.mobanId, + shangpinTypeId: selectedTypeId, + jieshao: editJieshao, + jiage: editJiage, + labelId: editLabelId || '', + commissionEnabled: editCommissionEnabled, + commissionValue: editCommissionValue + } + }) + if (res && apiOk(res)) { + const updatedList = [...this.data.templateList] + const idx = this.data.currentTemplateIndex + if (idx !== -1) { + updatedList[idx] = { + ...updatedList[idx], + jieshao: editJieshao, + jiage: editJiage, + labelId: editLabelId, + labelName: this.getLabelNameById(editLabelId), + commissionEnabled: editCommissionEnabled, + commissionValue: editCommissionValue + } + this.setData({ templateList: updatedList, currentTemplate: updatedList[idx], isEditing: false }) + } + wx.showToast({ title: '修改成功', icon: 'success' }) + } else { + wx.showToast({ title: res.data.msg || '修改失败', icon: 'error' }) + } + } catch (e) { + wx.showToast({ title: '修改失败', icon: 'error' }) + } finally { wx.hideLoading() } + }, + + onDeleteTemplate() { this.setData({ showDeleteConfirm: true }) }, + + async confirmDeleteTemplate() { + const { currentTemplate, selectedTypeId, linkMap } = this.data + if (!currentTemplate || !selectedTypeId) return + wx.showLoading({ title: '删除中...', mask: true }) + try { + const res = await request({ + url: staffOrOwner(STAFF_API.templateDelete, '/peizhi/sjddmbsc'), + method: 'POST', + data: { mobanId: currentTemplate.mobanId, shangpinTypeId: selectedTypeId } + }) + if (res && apiOk(res)) { + const updatedLinkMap = { ...linkMap } + delete updatedLinkMap[currentTemplate.mobanId] + const updatedList = [...this.data.templateList] + updatedList.splice(this.data.currentTemplateIndex, 1) + this.setData({ + linkMap: updatedLinkMap, + templateList: updatedList, + showDetailModal: false, + showDeleteConfirm: false, + currentTemplate: null, + currentTemplateIndex: -1 + }) + wx.showToast({ title: '删除成功', icon: 'success' }) + } else { + wx.showToast({ title: res.data.msg || '删除失败', icon: 'error' }) + } + } catch (e) { + wx.showToast({ title: '删除失败', icon: 'error' }) + } finally { wx.hideLoading() } + }, + + async confirmNewLink() { + const { currentTemplate, currentTemplateIndex } = this.data + if (!currentTemplate) return + this.setData({ showNewLinkConfirm: false }) + await this.generateLinkForTemplate(currentTemplate, currentTemplateIndex) + }, + + // ---------- 添加模板 ---------- + onAddJieshaoInput(e) { this.setData({ addJieshao: e.detail.value }) }, + onAddJiageInput(e) { this.setData({ addJiage: e.detail.value }) }, + onAddLabelChange(e) { + const idx = e.detail.value + const label = this.data.currentTypeChenghaoList[idx] + this.setData({ addLabelId: label ? label.id : '', addLabelName: label ? label.mingcheng : '' }) + }, + onAddCommissionToggle() { this.setData({ addCommissionEnabled: !this.data.addCommissionEnabled }) }, + onAddCommissionValueInput(e) { this.setData({ addCommissionValue: e.detail.value }) }, + + async onAddTemplate() { + const { selectedTypeId, addJieshao, addJiage, addLabelId, addCommissionEnabled, addCommissionValue } = this.data + if (!selectedTypeId) { + wx.showToast({ title: '请选择订单类型', icon: 'none' }) + return + } + if (!addJieshao.trim()) { + wx.showToast({ title: '请输入订单介绍', icon: 'none' }) + return + } + if (!addJiage || isNaN(parseFloat(addJiage)) || parseFloat(addJiage) <= 0) { + wx.showToast({ title: '请输入有效的价格', icon: 'none' }) + return + } + if (addCommissionEnabled && (!addCommissionValue || isNaN(parseFloat(addCommissionValue)))) { + wx.showToast({ title: '请输入有效的佣金金额', icon: 'none' }) + return + } + + wx.showLoading({ title: '添加中...', mask: true }) + try { + const res = await request({ + url: staffOrOwner(STAFF_API.templateCreate, '/peizhi/sjtjddmb'), + method: 'POST', + data: { + shangpinTypeId: selectedTypeId, + jieshao: addJieshao.trim(), + jiage: addJiage, + labelId: addLabelId || '', + commissionEnabled: addCommissionEnabled, + commissionValue: addCommissionValue + } + }) + if (res && apiOk(res)) { + const data = res.data.data || {} + const newTemplate = { + mobanId: data.mobanId, + jieshao: addJieshao, + jiage: addJiage, + fabushuliang: 0, + hasGenerated: false, + isCopied: false, + linkUrl: '', + labelId: addLabelId, + labelName: this.getLabelNameById(addLabelId), + commissionEnabled: addCommissionEnabled, + commissionValue: addCommissionValue + } + this.setData({ + templateList: [newTemplate, ...this.data.templateList], + showAddModal: false, + addJieshao: '', addJiage: '', addLabelId: '', addLabelName: '', + addCommissionEnabled: false, addCommissionValue: '' + }) + wx.showToast({ title: '添加成功', icon: 'success' }) + } else { + wx.showToast({ title: res.data.msg || '添加失败', icon: 'error' }) + } + } catch (e) { + wx.showToast({ title: '添加失败', icon: 'error' }) + } finally { wx.hideLoading() } + }, + + getLabelNameById(labelId) { + if (!labelId) return '' + const list = this.data.currentTypeChenghaoList + const found = list.find(item => item.id == labelId) + return found ? found.mingcheng : '' + }, + + onScrollToLower() { + const { hasMoreData, isLoadingMore, isSearchMode } = this.data + if (!hasMoreData || isLoadingMore || isSearchMode) return + this.setData({ currentPage: this.data.currentPage + 1 }, () => this.loadTemplates()) + }, + + onSelectType(e) { + const { id, item } = e.currentTarget.dataset + this.setSelectedType(item) + } }) \ No newline at end of file diff --git a/pages/express-order/express-order.wxml b/pages/express-order/express-order.wxml index 64cfa0a..7a941d7 100644 --- a/pages/express-order/express-order.wxml +++ b/pages/express-order/express-order.wxml @@ -2,12 +2,13 @@ - 可用余额 + {{isStaffMode ? '可用额度' : '可用余额'}} {{sjyue}} - 生成链接将从此余额扣除 + 当前身份:{{staffRoleName || '客服'}} · 生成链接从此额度扣除 + 生成链接将从此余额扣除 @@ -126,6 +127,7 @@ {{item.lianjie}} + {{item.roleName || item.displayName}} {{item.is_used ? '已使用' : '未使用'}} 复制 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-order-detail/fighter-order-detail.js b/pages/fighter-order-detail/fighter-order-detail.js index 1703829..0051b27 100644 --- a/pages/fighter-order-detail/fighter-order-detail.js +++ b/pages/fighter-order-detail/fighter-order-detail.js @@ -1,741 +1,741 @@ -// 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: '#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' - }); - } - }); - }, - - // ===== 联系老板(订单群 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' +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' + }); + } + }); + }, + + // ===== 联系老板(订单群 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' }) + } + } }) \ No newline at end of file diff --git a/pages/fighter-orders/fighter-orders.js b/pages/fighter-orders/fighter-orders.js index 8323223..5d778af 100644 --- a/pages/fighter-orders/fighter-orders.js +++ b/pages/fighter-orders/fighter-orders.js @@ -49,7 +49,14 @@ 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(); }, @@ -59,8 +66,15 @@ Page(createPage({ 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 +155,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 +226,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-rank/fighter-rank.js b/pages/fighter-rank/fighter-rank.js index 7d72942..452b687 100644 --- a/pages/fighter-rank/fighter-rank.js +++ b/pages/fighter-rank/fighter-rank.js @@ -1,9 +1,14 @@ /** - * 排行榜 POST /yonghu/phbhqsj - * 参数: shenfen(dashou|guanshi|zuzhang|shangjia), riqi(今日|本周|本月|总榜|昨日|上周|上月) + * 排行榜 + * 默认 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 ROLE_LIST = [ { key: 'dashou', label: '接单员' }, @@ -27,6 +32,7 @@ const ROLE_META = { title: '接单员排行榜', sortField: 'chengjiao_zonge', sortLabel: '分红总额', + mainType: 'money', metrics: [ { field: 'jiedan_zongliang', label: '接单量', type: 'int' }, { field: 'jiedan_zonge', label: '接单额', type: 'money' }, @@ -37,6 +43,7 @@ const ROLE_META = { title: '管事排行榜', sortField: 'shouru_zonge', sortLabel: '收入总额', + mainType: 'money', metrics: [ { field: 'yaoqing_dashou_shu', label: '邀请', type: 'int' }, { field: 'chongzhi_dashou_shu', label: '充值', type: 'int' }, @@ -46,6 +53,7 @@ const ROLE_META = { title: '组长排行榜', sortField: 'shouru_zonge', sortLabel: '收入总额', + mainType: 'money', metrics: [ { field: 'yaoqing_guanshi_shu', label: '邀请', type: 'int' }, { field: 'fenyong_jine', label: '分佣', type: 'money' }, @@ -55,6 +63,7 @@ const ROLE_META = { title: '商家排行榜', sortField: 'jiesuan_jine', sortLabel: '成交总额', + mainType: 'money', metrics: [ { field: 'jiesuan_dingdan_shu', label: '成交量', type: 'int' }, { field: 'paifa_dingdan_shu', label: '派单量', type: 'int' }, @@ -63,6 +72,55 @@ const ROLE_META = { }, } +/** 星之界专用展示与排序字段 */ +const XZJ_ROLE_META = { + dashou: { + title: '接单员排行榜', + sortField: 'chengjiao_zonge', + sortLabel: '成交金额', + mainType: 'money', + metrics: [ + { field: 'jiedan_zongliang', label: '接单量', type: 'int' }, + { field: 'jiedan_zonge', label: '接单额', type: 'money' }, + { field: 'chengjiao_zongliang', label: '成交量', type: 'int' }, + ], + }, + guanshi: { + title: '管事排行榜', + sortField: 'yaoqing_dashou_shu', + sortLabel: '邀请人数', + mainType: 'int', + metrics: [ + { field: 'wuxiao_yaoqing_dashou_shu', label: '无效邀请', type: 'int' }, + { field: 'chongzhi_dashou_shu', label: '有效人数', type: 'int' }, + { field: 'shouru_zonge', label: '收入金额', type: 'money' }, + ], + }, + zuzhang: { + title: '组长排行榜', + sortField: 'yaoqing_guanshi_shu', + sortLabel: '邀请人数', + mainType: 'int', + metrics: [ + { field: 'wuxiao_yaoqing_guanshi_shu', label: '无效邀请', type: 'int' }, + { field: 'youxiao_guanshi_shu', label: '有效人数', type: 'int' }, + { field: 'shouru_zonge', label: '收入金额', type: 'money' }, + ], + }, + shangjia: { + title: '商家排行榜', + sortField: 'paifa_jine', + sortLabel: '派单流水', + mainType: 'money', + metrics: [ + { field: 'paifa_dingdan_shu', label: '派单量', type: 'int' }, + { field: 'paifa_jine', label: '派单流水', type: 'money' }, + ], + }, +} + +const ACTIVE_ROLE_META = IS_XZJ ? XZJ_ROLE_META : ROLE_META + function isInvalidAvatarPath(path) { if (path === null || path === undefined) return true if (typeof path !== 'string') return true @@ -99,7 +157,7 @@ Page({ onLoad(options) { const type = options.type || options.rankType || 'dashou' - const validRole = ROLE_META[type] ? type : 'dashou' + const validRole = ACTIVE_ROLE_META[type] ? type : 'dashou' this.initPage() this.applyRole(validRole, false) this.fetchRankList() @@ -162,7 +220,7 @@ Page({ }, applyRole(role, reload = true) { - const meta = ROLE_META[role] + const meta = ACTIVE_ROLE_META[role] wx.setNavigationBarTitle({ title: meta.title }) this.setData({ currentRole: role, roleMeta: meta, sortLabel: meta.sortLabel }) if (reload) this.fetchRankList() @@ -196,7 +254,7 @@ Page({ }, normalizeItem(raw, index, role) { - const meta = ROLE_META[role] + const meta = ACTIVE_ROLE_META[role] const app = getApp() const uid = raw.yonghuid || raw.uid || '' const nicheng = raw.nicheng || raw.nick || '用户' @@ -209,12 +267,19 @@ Page({ value: m.type === 'money' ? this.formatMoney(v) : this.formatInt(v), } }) + const sortRaw = raw[meta.sortField] + const mainType = meta.mainType || 'money' + const mainValue = mainType === 'money' + ? this.formatMoney(sortRaw) + : this.formatInt(sortRaw) return { mingci: raw.mingci || index + 1, uid, nicheng, avatar, - mainValue: this.formatMoney(raw[meta.sortField]), + mainType, + mainPrefix: mainType === 'money' ? '¥' : '', + mainValue, mainLabel: meta.sortLabel, metrics, } @@ -224,7 +289,7 @@ Page({ this.setData({ isLoading: true, isEmpty: false }) try { const res = await request({ - url: '/yonghu/phbhqsj', + url: RANK_API, method: 'POST', data: { shenfen: this.data.currentRole, riqi: this.data.currentDate }, }) diff --git a/pages/fighter-rank/fighter-rank.wxml b/pages/fighter-rank/fighter-rank.wxml index 8c4db95..e486bee 100644 --- a/pages/fighter-rank/fighter-rank.wxml +++ b/pages/fighter-rank/fighter-rank.wxml @@ -52,7 +52,7 @@ {{topThree[1].nicheng}} ID {{topThree[1].uid}} - ¥{{topThree[1].mainValue}} + {{topThree[1].mainPrefix}}{{topThree[1].mainValue}} {{topThree[1].mainLabel}} @@ -70,7 +70,7 @@ {{topThree[0].nicheng}} ID {{topThree[0].uid}} - ¥{{topThree[0].mainValue}} + {{topThree[0].mainPrefix}}{{topThree[0].mainValue}} {{topThree[0].mainLabel}} @@ -88,7 +88,7 @@ {{topThree[2].nicheng}} ID {{topThree[2].uid}} - ¥{{topThree[2].mainValue}} + {{topThree[2].mainPrefix}}{{topThree[2].mainValue}} {{topThree[2].mainLabel}} @@ -120,7 +120,7 @@ - ¥{{item.mainValue}} + {{item.mainPrefix}}{{item.mainValue}} {{item.mainLabel}} diff --git a/pages/fighter-recharge/fighter-recharge.js b/pages/fighter-recharge/fighter-recharge.js index ebb1da2..9e7b957 100644 --- a/pages/fighter-recharge/fighter-recharge.js +++ b/pages/fighter-recharge/fighter-recharge.js @@ -1,975 +1,1018 @@ -// pages/dashou-chongzhi/index.js -import request from '../../utils/request'; -// 引入弹窗服务(路径根据实际调整) -import PopupService from '../../services/popupService.js'; - -Page({ - data: { - // ========== 全局数据 ========== - clubmber: [], // 会员列表(注意:全局变量中是 clumber,这里保持原样) - yajin: 0, // 押金 - jifen: 0, // 积分(注意:全局变量中是 jinfen) - jifenProgress: 0, // 积分进度条角度 - - // ========== 会员商品列表 ========== - huiyuanList: [], // 从后端获取的会员列表 - currentHuiyuanIndex: 0, // 当前选中的会员索引 - currentHuiyuan: {}, // 当前选中的会员详情 - - // ========== 押金充值相关 ========== - showYajinModal: false, // 显示押金弹窗 - yajinAmount: '100', // 押金金额 - - // ========== 支付相关 ========== - payLoading: false, // 支付加载状态 - loadingText: '支付中...', - - // ========== 订单ID记录 ========== - currentDingdanid: '', // 当前操作的订单ID - - // ========== 计算高度 ========== - huiyuanListHeight: 120, - - // ========== 会员详情规则 ========== - detailRules: [ - '会员有效期为30天,从购买当天开始计算', - '会员期间享受优先接单特权', - '可享受专属客服服务', - '会员到期前3天会有提醒', - '支持随时续费,续费天数叠加' - ], - - // ========== 新增:跳转参数处理 ========== - // 用于接收从其他页面跳转过来的参数,决定是否自动滚动 - jumpParams: {}, - - // ========== 新增:支付方式选择 ========== - showPayMethodModal: false, // 支付方式选择弹窗 - currentBuyType: null, // 当前购买类型:1会员 2押金 3积分 - currentHuiyuanId: null, // 当前选中的会员ID(用于会员购买) - currentYajinAmount: null, // 当前押金金额(用于押金购买) - - // ========== 新增:余额抵扣身份选择 ========== - showBalanceModal: false, // 余额抵扣身份选择弹窗 - balanceOptions: [], // 后端返回的身份列表 - selectedBalanceId: null, // 选中的身份ID - - // ========== 新增:余额抵扣确认弹窗 ========== - showConfirmModal: false, // 余额抵扣确认弹窗 - selectedBalanceInfo: null, // 选中的身份详情(用于确认弹窗) - - // ========== 新增:会员详情弹窗 ========== - showMemberModal: false, - currentMemberDetail: null, - }, - - // ========== 生命周期函数 ========== - onLoad(options) { - //console.log('页面加载,跳转参数:', options); - - // ✅ 新增:保存跳转参数 - this.setData({ - jumpParams: options || {} - }); - - this.initPage(); - }, - - // pages/submit/submit.js 中添加 onHide 方法(如果已有则合并内容) -onHide() { - const popupComp = this.selectComponent('#popupNotice'); - if (popupComp && popupComp.cleanup) { - popupComp.cleanup(); - } - }, - - onShow() { - // 每次显示页面都刷新数据 - this.registerNotificationComponent(); - this.loadFromGlobalData(); - this.checkAndRefreshData(); - - // 调用统一弹窗组件检查并展示公告 - PopupService.checkAndShow(this, 'dashouchongzhi'); - }, - - // 注册通知组件(原有,保持不变) - registerNotificationComponent() { - const app = getApp(); - const notificationComp = this.selectComponent('#global-notification'); - - if (notificationComp && notificationComp.showNotification) { - - app.globalData.globalNotification = { - show: (data) => notificationComp.showNotification(data), - hide: () => notificationComp.hideNotification() - }; - } - }, - - onReady() { - // ✅ 新增:页面渲染完成后处理自动滚动 - this.handleAutoScroll(); - }, - - // ========== 初始化页面 ========== - async initPage() { - // 从全局数据加载 - this.loadFromGlobalData(); - - // 获取会员商品列表 - await this.fetchHuiyuanList(); - - // 计算会员列表高度 - this.calculateHuiyuanHeight(); - }, - - // ========== 从全局变量加载数据 ========== - loadFromGlobalData() { - const app = getApp(); - const globalData = app.globalData || {}; - - // ✅ 重要:保持字段对应关系 - // 全局变量中是 clumber,页面中是 clubmber - // 全局变量中是 jinfen,页面中是 jifen - this.setData({ - clubmber: globalData.clumber || [], - yajin: globalData.yajin || 0, - jifen: globalData.jinfen || 0 // ✅ 注意:这里从 jinfen 读取 - }); - - // 计算积分进度条 - this.calculateJifenProgress(); - }, - - // ========== 计算积分进度条角度 ========== - calculateJifenProgress() { - const jifen = this.data.jifen || 0; - const progress = (jifen / 10) * 360; // 满分10分 - this.setData({ - jifenProgress: progress - }); - }, - - // ========== 计算会员列表高度 ========== - calculateHuiyuanHeight() { - const clubmber = this.data.clubmber || []; - - const itemHeight = 100; // 每个会员标签高度 - const maxHeight = 300; // 最大高度(3个会员) - - let height = clubmber.length * itemHeight; - height = Math.min(height, maxHeight); - - this.setData({ - huiyuanListHeight: height - }); - }, - - // ========== 获取会员商品列表 ========== - async fetchHuiyuanList() { - try { - const res = await request({ - url: '/shangpin/dshyhq', - method: 'POST', - data: {} - }); - - if (res.data.code === 200) { - const huiyuanList = res.data.data || []; - - // 处理数据,标记已购买的会员 - const processedList = this.processHuiyuanList(huiyuanList); - - this.setData({ - huiyuanList: processedList, - currentHuiyuan: processedList[0] || {} - }); - } else { - wx.showToast({ - title: res.data.message || '获取会员列表失败', - icon: 'none' - }); - } - } catch (error) { - console.error('获取会员列表失败:', error); - wx.showToast({ - title: '网络错误,请重试', - icon: 'none' - }); - } - }, - - // ========== 处理会员列表,标记已购买的会员 ========== - processHuiyuanList(huiyuanList) { - const clubmber = this.data.clubmber || []; - - return huiyuanList.map(item => { - // 检查是否已购买(注意:会员ID字段是 id) - const boughtItem = clubmber.find(c => c.huiyuanid === item.id); - - return { - ...item, - isBought: !!boughtItem, - buyInfo: boughtItem || null - }; - }); - }, - - // ========== Swiper切换事件 ========== - onSwiperChange(e) { - const current = e.detail.current; - const huiyuanList = this.data.huiyuanList || []; - - this.setData({ - currentHuiyuanIndex: current, - currentHuiyuan: huiyuanList[current] || {} - }); - }, - - // ========== 手动选择会员 ========== - selectHuiyuan(e) { - const index = e.currentTarget.dataset.index; - const huiyuanList = this.data.huiyuanList || []; - - this.setData({ - currentHuiyuanIndex: index, - currentHuiyuan: huiyuanList[index] || {} - }); - }, - - // ========== 格式化到期时间 ========== - formatDaoqiTime(daoqi) { - if (!daoqi) return ''; - - try { - // 如果是标准日期格式,格式化为 YYYY-MM-DD - if (daoqi.includes(' ')) { - const dateStr = daoqi.split(' ')[0]; - const dateParts = dateStr.split('-'); - if (dateParts.length === 3) { - return `${dateParts[0]}-${dateParts[1]}-${dateParts[2]}`; - } - } - return daoqi; - } catch (error) { - console.error('格式化到期时间错误:', error); - return daoqi; - } - }, - - // ========== 计算剩余时间 ========== - formatRemainTime(daoqi) { - if (!daoqi) return ''; - - try { - const now = new Date(); - const end = new Date(daoqi); - const diff = end - now; - - if (diff <= 0) return '已过期'; - - const days = Math.floor(diff / (1000 * 60 * 60 * 24)); - if (days > 0) { - return `${days}天`; - } - - const hours = Math.floor(diff / (1000 * 60 * 60)); - if (hours > 0) { - return `${hours}小时`; - } - - return '即将到期'; - } catch (error) { - console.error('计算剩余时间错误:', error); - return '时间错误'; - } - }, - - // ========== 处理自动滚动 ========== - handleAutoScroll() { - const { jumpParams } = this.data; - - if (!jumpParams || !jumpParams.needScroll) return; - - setTimeout(() => { - let scrollTop = 0; - - if (jumpParams.scrollTo === 'bottom') { - scrollTop = 2000; - } else if (jumpParams.scrollTo === 'member') { - return; - } - - if (scrollTop > 0) { - wx.pageScrollTo({ - scrollTop: scrollTop, - duration: 300 - }); - } - }, 800); - }, - - // ========== 押金充值相关 ========== - showYajinModal() { - this.setData({ - showYajinModal: true, - yajinAmount: '100' - }); - }, - - hideYajinModal() { - this.setData({ - showYajinModal: false - }); - }, - - onYajinInput(e) { - let value = e.detail.value; - value = value.replace(/[^\d]/g, ''); - - if (value) { - const num = parseInt(value); - if (num < 1) value = '1'; - if (num > 10000) value = '10000'; - } - - this.setData({ - yajinAmount: value - }); - }, - - setYajinAmount(e) { - const amount = e.currentTarget.dataset.amount; - this.setData({ - yajinAmount: amount - }); - }, - - async handleYajinPay() { - const amount = this.data.yajinAmount; - - if (!amount || amount < 1 || amount > 10000) { - wx.showToast({ - title: '请输入1-10000元的金额', - icon: 'none' - }); - return; - } - - this.showLoading('发起支付中...'); - - try { - const res = await request({ - url: '/shangpin/yajingoumai', - method: 'POST', - data: { - jine: parseInt(amount) - } - }); - - if (res.data.code === 200) { - const payParams = res.data.payParams; - const dingdanid = res.data.dingdanid; - - this.setData({ - currentDingdanid: dingdanid - }); - - await this.wechatPay(payParams, 'yajin'); - } else { - wx.showToast({ - title: res.data.message || '支付发起失败', - icon: 'none' - }); - } - } catch (error) { - console.error('押金支付失败:', error); - wx.showToast({ - title: '支付失败,请重试', - icon: 'none' - }); - } finally { - this.hideLoading(); - } - }, - - // ========== 积分充值相关 ========== - // 🔥 修改点1:增加积分已满的硬拦截,不弹任何提示,直接返回 - onJifenClick(e) { - // 优先判断积分是否已满(10分为满分) - if (this.data.jifen >= 10) { - // 积分已满,不弹窗,不提示,静默返回 - return; - } - - // 检查是否有会员 - const clubmber = this.data.clubmber || []; - if (clubmber.length === 0) { - wx.showToast({ - title: '请先购买会员才能充值积分', - icon: 'none', - duration: 3000 - }); - return; - } - - // 积分未满,正常打开支付方式选择 - this.setData({ - currentBuyType: 3, - showPayMethodModal: true - }); - }, - - // ========== 会员购买相关 ========== - async handleHuiyuanBuy(e) { - const huiyuanId = e.currentTarget.dataset.id; - - if (!huiyuanId) { - wx.showToast({ - title: '会员信息错误', - icon: 'none' - }); - return; - } - - this.showLoading('发起会员支付中...'); - - try { - const res = await request({ - url: '/shangpin/huiyuangm', - method: 'POST', - data: { - huiyuanid: huiyuanId - } - }); - - if (res.data.code === 200) { - const payParams = res.data.payParams; - const dingdanid = res.data.dingdanid; - - this.setData({ - currentDingdanid: dingdanid - }); - - await this.wechatPay(payParams, 'huiyuan', huiyuanId); - } else { - wx.showToast({ - title: res.data.message || '会员支付发起失败', - icon: 'none' - }); - } - } catch (error) { - console.error('会员支付失败:', error); - wx.showToast({ - title: '支付失败,请重试', - icon: 'none' - }); - } finally { - this.hideLoading(); - } - }, - - // ========== 积分购买(微信支付)—— 已替换为正确的后端接口 ========== - async handleJifenBuy(e) { - const disabled = e.currentTarget.dataset.disabled; - - // 检查积分是否已满 - if (disabled === 'true') { - wx.showToast({ title: '积分已满,无需补充', icon: 'none' }); - return; - } - - // 检查是否有会员 - const clubmber = this.data.clubmber || []; - if (clubmber.length === 0) { - wx.showToast({ title: '请先购买会员才能充值积分', icon: 'none', duration: 3000 }); - return; - } - - this.showLoading('发起积分支付中...'); - - try { - const res = await request({ - url: '/shangpin/jifenbc', // 正确的积分下单接口 - method: 'POST', - data: {} - }); - - if (res.data.code === 200) { - const payParams = res.data.payParams; - const dingdanid = res.data.dingdanid; - - this.setData({ currentDingdanid: dingdanid }); - await this.wechatPay(payParams, 'jifen'); - } else { - wx.showToast({ title: res.data.message || '积分支付发起失败', icon: 'none' }); - } - } catch (error) { - wx.showToast({ title: '支付失败,请重试', icon: 'none' }); - } finally { - this.hideLoading(); - } - }, - - // ========== 微信支付封装 ========== - async wechatPay(payParams, payType, extraData = null) { - return new Promise((resolve, reject) => { - if (!payParams || !payParams.timeStamp || !payParams.paySign) { - wx.showToast({ - title: '支付参数错误', - icon: 'none' - }); - reject(new Error('支付参数错误')); - return; - } - - this.showLoading('调起支付中...'); - - wx.requestPayment({ - timeStamp: payParams.timeStamp, - nonceStr: payParams.nonceStr, - package: payParams.package, - signType: payParams.signType || 'MD5', - paySign: payParams.paySign, - success: async () => { - this.hideLoading(); - - wx.showToast({ - title: '支付成功!确认中...', - icon: 'none', - duration: 2000 - }); - - try { - await this.startPolling(payType, extraData); - resolve(); - } catch (error) { - reject(error); - } - }, - fail: async (res) => { - this.hideLoading(); - - let errorMsg = '支付失败'; - if (res.errMsg.includes('cancel')) { - errorMsg = '您取消了支付'; - } else if (res.errMsg.includes('fail')) { - errorMsg = '支付失败,请重试'; - } - - wx.showToast({ - title: errorMsg, - icon: 'none' - }); - - try { - await this.handlePayFailure(payType, extraData); - } catch (error) { - console.error('支付失败通知失败:', error); - } - - reject(new Error(res.errMsg)); - } - }); - }); - }, - - // ========== 开始轮询支付结果 ========== - async startPolling(payType, extraData = null) { - const maxRetries = 10; - const interval = 2000; - const dingdanid = this.data.currentDingdanid; - - if (!dingdanid) { - wx.showToast({ - title: '订单号缺失', - icon: 'none' - }); - return; - } - - this.showLoading('确认支付结果中...'); - - for (let i = 0; i < maxRetries; i++) { - try { - const pollResult = await this.checkPayStatus(payType, dingdanid, extraData); - - if (pollResult.success) { - this.hideLoading(); - await this.updateAfterPayment(payType, extraData, pollResult.data); - - wx.showToast({ - title: this.getSuccessMessage(payType), - icon: 'success', - duration: 2000 - }); - - return; - } - - await this.sleep(interval); - } catch (error) { - console.error(`轮询失败第${i + 1}次:`, error); - } - } - - this.hideLoading(); - wx.showToast({ - title: '支付确认超时,请联系客服', - icon: 'none' - }); - }, - - // ========== 检查支付状态 ========== - async checkPayStatus(payType, dingdanid, extraData = null) { - let url = ''; - let data = { dingdanid: dingdanid }; - - switch (payType) { - case 'yajin': - url = '/shangpin/yajinlunxun'; - break; - case 'jifen': - url = '/shangpin/jifenlunxun'; - break; - case 'huiyuan': - url = '/shangpin/huiyuanlx'; - data.huiyuanid = extraData; - break; - default: - throw new Error('未知的支付类型'); - } - - try { - const res = await request({ - url, - method: 'POST', - data - }); - - return { - success: res.data.code === 200, - data: res.data - }; - } catch (error) { - console.error('检查支付状态失败:', error); - return { - success: false, - data: null - }; - } - }, - - // ========== 支付失败处理 ========== - async handlePayFailure(payType, extraData = null) { - let url = ''; - let data = { dingdanid: this.data.currentDingdanid }; - - switch (payType) { - case 'yajin': - url = '/shangpin/yajinshibai'; - break; - case 'jifen': - url = '/shangpin/jifenshibai'; - break; - case 'huiyuan': - url = '/shangpin/huiyuanshibai'; - data.huiyuanid = extraData; - break; - default: - return; - } - - try { - await request({ - url, - method: 'POST', - data - }); - } catch (error) { - console.error('支付失败通知失败:', error); - } - }, - - // ========== 支付成功后更新数据 ========== - async updateAfterPayment(payType, extraData = null, responseData = null) { - const app = getApp(); - - switch (payType) { - case 'yajin': - if (responseData && responseData.yajin !== undefined) { - app.globalData.yajin = responseData.yajin; - } - await this.refreshYajinData(); - break; - - case 'jifen': - if (responseData && responseData.jifen !== undefined) { - app.globalData.jinfen = responseData.jifen; - } - await this.refreshJifenData(); - break; - - case 'huiyuan': - if (responseData && responseData.huiyuan) { - app.globalData.jinfen = responseData.jifen; - await this.updateClubmberData(responseData.huiyuan); - } - await this.refreshHuiyuanData(extraData); - break; - } - - this.loadFromGlobalData(); - }, - - // ========== 刷新押金数据 ========== - async refreshYajinData() { - this.loadFromGlobalData(); - }, - - // ========== 刷新积分数据 ========== - async refreshJifenData() { - this.loadFromGlobalData(); - }, - - // ========== 刷新会员数据 ========== - async refreshHuiyuanData(huiyuanId) { - this.loadFromGlobalData(); - await this.fetchHuiyuanList(); - }, - - // ========== 更新会员数据到全局变量 ========== - async updateClubmberData(huiyuanData) { - const app = getApp(); - const globalClubmber = app.globalData.clumber || []; - - const { huiyuanid, huiyuanming, daoqi } = huiyuanData; - - const index = globalClubmber.findIndex(item => item.huiyuanid === huiyuanid); - - if (index >= 0) { - globalClubmber[index].daoqi = daoqi; - } else { - globalClubmber.push({ - huiyuanid: huiyuanid, - huiyuanming: huiyuanming, - daoqi: daoqi - }); - } - - app.globalData.clumber = globalClubmber; - - this.setData({ - clubmber: [...globalClubmber] - }); - }, - - // ========== 检查并刷新数据 ========== - async checkAndRefreshData() { - this.loadFromGlobalData(); - }, - - // ========== 工具方法 ========== - getSuccessMessage(payType) { - switch (payType) { - case 'yajin': return '押金充值成功'; - case 'jifen': return '积分补充成功'; - case 'huiyuan': return '会员购买成功'; - default: return '支付成功'; - } - }, - - showLoading(text = '加载中...') { - this.setData({ - payLoading: true, - loadingText: text - }); - }, - - hideLoading() { - this.setData({ - payLoading: false - }); - }, - - sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - }, - - // ========== 新增功能(余额抵扣等) ========== - onBuyHuiyuanClick(e) { - const huiyuanId = e.currentTarget.dataset.id; - this.setData({ - currentBuyType: 1, - currentHuiyuanId: huiyuanId, - showPayMethodModal: true - }); - }, - - onYajinClick() { - this.showYajinModal(); - }, - - onYajinConfirm() { - const amount = this.data.yajinAmount; - if (!amount || amount < 1 || amount > 10000) { - wx.showToast({ title: '请输入1-10000元的金额', icon: 'none' }); - return; - } - this.hideYajinModal(); - this.setData({ - currentBuyType: 2, - currentYajinAmount: amount, - showPayMethodModal: true - }); - }, - - hidePayMethodModal() { - this.setData({ showPayMethodModal: false }); - }, - - // 🔥 修改点2:选择支付方式时再次校验积分状态(防止异步过程中积分变化) - onPayMethodSelect(e) { - const method = e.currentTarget.dataset.method; - const { currentBuyType, currentHuiyuanId, currentYajinAmount, jifen } = this.data; - this.hidePayMethodModal(); - - // 如果是积分购买,且积分已满,直接拒绝 - if (currentBuyType === 3 && jifen >= 10) { - wx.showToast({ - title: '积分已满,无需补充', - icon: 'none' - }); - return; - } - - if (method === 'wx') { - if (currentBuyType === 1) { - this.handleHuiyuanBuy({ currentTarget: { dataset: { id: currentHuiyuanId } } }); - } else if (currentBuyType === 2) { - this.setData({ yajinAmount: currentYajinAmount }, () => { - this.handleYajinPay(); - }); - } else if (currentBuyType === 3) { - // ✅ 调用已修正的积分支付函数 - this.handleJifenBuy({ currentTarget: { dataset: { disabled: 'false' } } }); - } - } else if (method === 'balance') { - this.fetchBalanceOptions(); - } - }, - - // 🔥 修改点3:请求余额抵扣选项前校验积分状态 - async fetchBalanceOptions() { - const { currentBuyType, currentHuiyuanId, currentYajinAmount, jifen } = this.data; - - // 如果是积分购买,且积分已满,拒绝发起余额抵扣 - if (currentBuyType === 3 && jifen >= 10) { - wx.showToast({ - title: '积分已满,无需补充', - icon: 'none' - }); - return; - } - - this.showLoading('获取抵扣信息...'); - - try { - const res = await request({ - url: '/shangpin/czhqdy', - method: 'POST', - data: { - leixing: currentBuyType, - huiyuan_id: currentBuyType === 1 ? currentHuiyuanId : undefined, - yajin_jine: currentBuyType === 2 ? parseInt(currentYajinAmount) : undefined, - } - }); - if (res.data.code === 200) { - const options = res.data.data || []; - this.setData({ - balanceOptions: options, - showBalanceModal: true - }); - } else { - wx.showToast({ title: res.data.message || '获取失败', icon: 'none' }); - } - } catch (error) { - console.error('获取抵扣身份失败:', error); - wx.showToast({ title: '网络错误', icon: 'none' }); - } finally { - this.hideLoading(); - } - }, - - hideBalanceModal() { - this.setData({ showBalanceModal: false }); - }, - - onBalanceSelect(e) { - const identityId = e.currentTarget.dataset.id; - const selected = this.data.balanceOptions.find(opt => opt.id === identityId); - if (!selected) return; - - this.setData({ - selectedBalanceId: identityId, - selectedBalanceInfo: selected, - showConfirmModal: true, - showBalanceModal: false - }); - }, - - hideConfirmModal() { - this.setData({ showConfirmModal: false }); - }, - - async confirmBalancePay() { - const { selectedBalanceId, currentBuyType, currentHuiyuanId, currentYajinAmount } = this.data; - if (!selectedBalanceId) return; - - this.hideConfirmModal(); - this.showLoading('抵扣支付中...'); - - try { - const res = await request({ - url: '/shangpin/dsqrgmdh', - method: 'POST', - data: { - leixing: currentBuyType, - shenfen_id: selectedBalanceId, - huiyuan_id: currentBuyType === 1 ? currentHuiyuanId : undefined, - yajin_jine: currentBuyType === 2 ? parseInt(currentYajinAmount) : undefined, - } - }); - - if (res.data.code === 200) { - const responseData = res.data.data || {}; - await this.updateAfterPayment( - currentBuyType === 1 ? 'huiyuan' : (currentBuyType === 2 ? 'yajin' : 'jifen'), - currentBuyType === 1 ? currentHuiyuanId : null, - responseData - ); - wx.showToast({ title: '购买成功', icon: 'success' }); - } else { - wx.showToast({ title: res.data.message || '购买失败', icon: 'none' }); - } - } catch (error) { - console.error('抵扣支付失败:', error); - wx.showToast({ title: '网络错误', icon: 'none' }); - } finally { - this.hideLoading(); - } - }, - - showMemberDetail(e) { - const item = e.currentTarget.dataset.item; - this.setData({ - currentMemberDetail: item, - showMemberModal: true - }); - }, - - hideMemberModal() { - this.setData({ showMemberModal: false }); - }, +// pages/dashou-chongzhi/index.js +import request from '../../utils/request'; +import PopupService from '../../services/popupService.js'; +import { ensurePhoneAuth } from '../../utils/phone-auth.js'; + +Page({ + data: { + // ========== 全局数据 ========== + clubmber: [], // 会员列表(注意:全局变量中是 clumber,这里保持原样) + yajin: 0, // 押金 + jifen: 0, // 积分(注意:全局变量中是 jinfen) + jifenProgress: 0, // 积分进度条角度 + + // ========== 会员商品列表 ========== + huiyuanList: [], // 从后端获取的会员列表 + currentHuiyuanIndex: 0, // 当前选中的会员索引 + currentHuiyuan: {}, // 当前选中的会员详情 + + // ========== 押金充值相关 ========== + showYajinModal: false, // 显示押金弹窗 + yajinAmount: '100', // 押金金额 + + // ========== 支付相关 ========== + payLoading: false, // 支付加载状态 + loadingText: '支付中...', + + // ========== 订单ID记录 ========== + currentDingdanid: '', // 当前操作的订单ID + + // ========== 计算高度 ========== + huiyuanListHeight: 120, + + // ========== 会员详情规则 ========== + detailRules: [], + + // ========== 新增:跳转参数处理 ========== + // 用于接收从其他页面跳转过来的参数,决定是否自动滚动 + 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, // 选中的身份详情(用于确认弹窗) + + // ========== 新增:会员详情弹窗 ========== + showMemberModal: false, + currentMemberDetail: null, + }, + + // ========== 生命周期函数 ========== + onLoad(options) { + //console.log('页面加载,跳转参数:', options); + + // ✅ 新增:保存跳转参数 + this.setData({ + jumpParams: options || {} + }); + + this.initPage(); + }, + + // pages/submit/submit.js 中添加 onHide 方法(如果已有则合并内容) +onHide() { + const popupComp = this.selectComponent('#popupNotice'); + if (popupComp && popupComp.cleanup) { + popupComp.cleanup(); + } + }, + + async onShow() { + const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' }); + if (!phoneOk) return; + + // 每次显示页面都刷新数据 + this.registerNotificationComponent(); + this.loadFromGlobalData(); + this.checkAndRefreshData(); + + // 调用统一弹窗组件检查并展示公告 + PopupService.checkAndShow(this, 'dashouchongzhi'); + }, + + // 注册通知组件(原有,保持不变) + registerNotificationComponent() { + const app = getApp(); + const notificationComp = this.selectComponent('#global-notification'); + + if (notificationComp && notificationComp.showNotification) { + + app.globalData.globalNotification = { + show: (data) => notificationComp.showNotification(data), + hide: () => notificationComp.hideNotification() + }; + } + }, + + onReady() { + // ✅ 新增:页面渲染完成后处理自动滚动 + this.handleAutoScroll(); + }, + + // ========== 初始化页面 ========== + async initPage() { + // 从全局数据加载 + this.loadFromGlobalData(); + + // 获取会员商品列表 + await this.fetchHuiyuanList(); + + // 计算会员列表高度 + this.calculateHuiyuanHeight(); + }, + + // ========== 从全局变量加载数据 ========== + loadFromGlobalData() { + const app = getApp(); + const globalData = app.globalData || {}; + + // ✅ 重要:保持字段对应关系 + // 全局变量中是 clumber,页面中是 clubmber + // 全局变量中是 jinfen,页面中是 jifen + const rawClubmber = globalData.clumber || []; + const clubmber = rawClubmber.filter((c) => c && c.huiyuanid); + + this.setData({ + clubmber: clubmber, + yajin: globalData.yajin || 0, + jifen: globalData.jinfen || 0 // ✅ 注意:这里从 jinfen 读取 + }); + + // 计算积分进度条 + this.calculateJifenProgress(); + }, + + // ========== 计算积分进度条角度 ========== + calculateJifenProgress() { + const jifen = this.data.jifen || 0; + const progress = (jifen / 10) * 360; // 满分10分 + this.setData({ + jifenProgress: progress + }); + }, + + // ========== 计算会员列表高度 ========== + calculateHuiyuanHeight() { + const clubmber = this.data.clubmber || []; + + const itemHeight = 100; // 每个会员标签高度 + const maxHeight = 300; // 最大高度(3个会员) + + let height = clubmber.length * itemHeight; + height = Math.min(height, maxHeight); + + this.setData({ + huiyuanListHeight: height + }); + }, + + // ========== 获取会员商品列表 ========== + async fetchHuiyuanList() { + try { + const res = await request({ + url: '/shangpin/dshyhq', + method: 'POST', + data: {} + }); + + if (res.data.code === 200) { + const huiyuanList = res.data.data || []; + + // 处理数据,标记已购买的会员 + const processedList = this.processHuiyuanList(huiyuanList); + + const currentHuiyuan = processedList[0] || {}; + this.setData({ + huiyuanList: processedList, + currentHuiyuan, + detailRules: this.buildDetailRules(currentHuiyuan) + }); + } else { + wx.showToast({ + title: res.data.message || '获取会员列表失败', + icon: 'none' + }); + } + } catch (error) { + console.error('获取会员列表失败:', error); + wx.showToast({ + title: '网络错误,请重试', + icon: 'none' + }); + } + }, + + // ========== 处理会员列表,标记已购买的会员 ========== + processHuiyuanList(huiyuanList) { + const clubmber = this.data.clubmber || []; + + return huiyuanList.map(item => { + // 检查是否已购买(注意:会员ID字段是 id) + const boughtItem = clubmber.find(c => c.huiyuanid === item.id); + + return { + ...item, + isBought: !!boughtItem, + buyInfo: boughtItem || null + }; + }); + }, + + // ========== Swiper切换事件 ========== + onSwiperChange(e) { + const current = e.detail.current; + const huiyuanList = this.data.huiyuanList || []; + const currentHuiyuan = huiyuanList[current] || {}; + + this.setData({ + currentHuiyuanIndex: current, + currentHuiyuan, + detailRules: this.buildDetailRules(currentHuiyuan) + }); + }, + + // ========== 手动选择会员 ========== + 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) + }); + }, + + buildDetailRules(huiyuan = {}) { + const formalDays = huiyuan.formal_days || 30; + const rules = [ + `正式会员有效期为${formalDays}天,从购买当天开始计算`, + '会员期间享受优先接单特权', + '支持随时续费,未过期续费天数叠加,已过期从今天重新计算' + ]; + if (huiyuan.can_buy_trial) { + rules.push(`体验会员终身仅可购买1次(${huiyuan.trial_days || 0}天),体验期仅限制佣金提现`); + } + if (huiyuan.trial_enabled && !huiyuan.can_buy_trial) { + rules.push('您已使用过该会员的体验资格'); + } + rules.push('体验会员仅支持微信支付;正式会员可使用佣金、分红或押金抵扣'); + return rules; + }, + + // ========== 格式化到期时间 ========== + formatDaoqiTime(daoqi) { + if (!daoqi) return ''; + + try { + // 如果是标准日期格式,格式化为 YYYY-MM-DD + if (daoqi.includes(' ')) { + const dateStr = daoqi.split(' ')[0]; + const dateParts = dateStr.split('-'); + if (dateParts.length === 3) { + return `${dateParts[0]}-${dateParts[1]}-${dateParts[2]}`; + } + } + return daoqi; + } catch (error) { + console.error('格式化到期时间错误:', error); + return daoqi; + } + }, + + // ========== 计算剩余时间 ========== + formatRemainTime(daoqi) { + if (!daoqi) return ''; + + try { + const now = new Date(); + const end = new Date(daoqi); + const diff = end - now; + + if (diff <= 0) return '已过期'; + + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + if (days > 0) { + return `${days}天`; + } + + const hours = Math.floor(diff / (1000 * 60 * 60)); + if (hours > 0) { + return `${hours}小时`; + } + + return '即将到期'; + } catch (error) { + console.error('计算剩余时间错误:', error); + return '时间错误'; + } + }, + + // ========== 处理自动滚动 ========== + handleAutoScroll() { + const { jumpParams } = this.data; + + if (!jumpParams || !jumpParams.needScroll) return; + + setTimeout(() => { + let scrollTop = 0; + + if (jumpParams.scrollTo === 'bottom') { + scrollTop = 2000; + } else if (jumpParams.scrollTo === 'member') { + return; + } + + if (scrollTop > 0) { + wx.pageScrollTo({ + scrollTop: scrollTop, + duration: 300 + }); + } + }, 800); + }, + + // ========== 押金充值相关 ========== + showYajinModal() { + this.setData({ + showYajinModal: true, + yajinAmount: '100' + }); + }, + + hideYajinModal() { + this.setData({ + showYajinModal: false + }); + }, + + onYajinInput(e) { + let value = e.detail.value; + value = value.replace(/[^\d]/g, ''); + + if (value) { + const num = parseInt(value); + if (num < 1) value = '1'; + if (num > 10000) value = '10000'; + } + + this.setData({ + yajinAmount: value + }); + }, + + setYajinAmount(e) { + const amount = e.currentTarget.dataset.amount; + this.setData({ + yajinAmount: amount + }); + }, + + async handleYajinPay() { + const amount = this.data.yajinAmount; + + if (!amount || amount < 1 || amount > 10000) { + wx.showToast({ + title: '请输入1-10000元的金额', + icon: 'none' + }); + return; + } + + this.showLoading('发起支付中...'); + + try { + const res = await request({ + url: '/shangpin/yajingoumai', + method: 'POST', + data: { + jine: parseInt(amount) + } + }); + + if (res.data.code === 200) { + const payParams = res.data.payParams; + const dingdanid = res.data.dingdanid; + + this.setData({ + currentDingdanid: dingdanid + }); + + await this.wechatPay(payParams, 'yajin'); + } else { + wx.showToast({ + title: res.data.message || '支付发起失败', + icon: 'none' + }); + } + } catch (error) { + console.error('押金支付失败:', error); + wx.showToast({ + title: '支付失败,请重试', + icon: 'none' + }); + } finally { + this.hideLoading(); + } + }, + + // ========== 积分充值相关 ========== + // 🔥 修改点1:增加积分已满的硬拦截,不弹任何提示,直接返回 + onJifenClick(e) { + // 优先判断积分是否已满(10分为满分) + if (this.data.jifen >= 10) { + // 积分已满,不弹窗,不提示,静默返回 + return; + } + + // 检查是否有会员 + const clubmber = this.data.clubmber || []; + if (clubmber.length === 0) { + wx.showToast({ + title: '请先购买会员才能充值积分', + icon: 'none', + duration: 3000 + }); + return; + } + + // 积分未满,正常打开支付方式选择 + this.setData({ + currentBuyType: 3, + showPayMethodModal: true + }); + }, + + // ========== 会员购买相关 ========== + async handleHuiyuanBuy(e) { + const huiyuanId = e.currentTarget.dataset.id; + const isTrial = e.currentTarget.dataset.trial === true || e.currentTarget.dataset.trial === 'true'; + + if (!huiyuanId) { + wx.showToast({ + title: '会员信息错误', + icon: 'none' + }); + return; + } + + this.showLoading('发起会员支付中...'); + + try { + const res = await request({ + url: '/shangpin/huiyuangm', + method: 'POST', + data: { + huiyuanid: huiyuanId, + is_trial: isTrial + } + }); + + if (res.data.code === 200) { + const payParams = res.data.payParams; + const dingdanid = res.data.dingdanid; + + this.setData({ + currentDingdanid: dingdanid + }); + + await this.wechatPay(payParams, 'huiyuan', huiyuanId); + } else { + wx.showToast({ + title: res.data.message || '会员支付发起失败', + icon: 'none' + }); + } + } catch (error) { + console.error('会员支付失败:', error); + wx.showToast({ + title: '支付失败,请重试', + icon: 'none' + }); + } finally { + this.hideLoading(); + } + }, + + // ========== 积分购买(微信支付)—— 已替换为正确的后端接口 ========== + async handleJifenBuy(e) { + const disabled = e.currentTarget.dataset.disabled; + + // 检查积分是否已满 + if (disabled === 'true') { + wx.showToast({ title: '积分已满,无需补充', icon: 'none' }); + return; + } + + // 检查是否有会员 + const clubmber = this.data.clubmber || []; + if (clubmber.length === 0) { + wx.showToast({ title: '请先购买会员才能充值积分', icon: 'none', duration: 3000 }); + return; + } + + this.showLoading('发起积分支付中...'); + + try { + const res = await request({ + url: '/shangpin/jifenbc', // 正确的积分下单接口 + method: 'POST', + data: {} + }); + + if (res.data.code === 200) { + const payParams = res.data.payParams; + const dingdanid = res.data.dingdanid; + + this.setData({ currentDingdanid: dingdanid }); + await this.wechatPay(payParams, 'jifen'); + } else { + wx.showToast({ title: res.data.message || '积分支付发起失败', icon: 'none' }); + } + } catch (error) { + wx.showToast({ title: '支付失败,请重试', icon: 'none' }); + } finally { + this.hideLoading(); + } + }, + + // ========== 微信支付封装 ========== + async wechatPay(payParams, payType, extraData = null) { + return new Promise((resolve, reject) => { + if (!payParams || !payParams.timeStamp || !payParams.paySign) { + wx.showToast({ + title: '支付参数错误', + icon: 'none' + }); + reject(new Error('支付参数错误')); + return; + } + + this.showLoading('调起支付中...'); + + wx.requestPayment({ + timeStamp: payParams.timeStamp, + nonceStr: payParams.nonceStr, + package: payParams.package, + signType: payParams.signType || 'MD5', + paySign: payParams.paySign, + success: async () => { + this.hideLoading(); + + wx.showToast({ + title: '支付成功!确认中...', + icon: 'none', + duration: 2000 + }); + + try { + await this.startPolling(payType, extraData); + resolve(); + } catch (error) { + reject(error); + } + }, + fail: async (res) => { + this.hideLoading(); + + let errorMsg = '支付失败'; + if (res.errMsg.includes('cancel')) { + errorMsg = '您取消了支付'; + } else if (res.errMsg.includes('fail')) { + errorMsg = '支付失败,请重试'; + } + + wx.showToast({ + title: errorMsg, + icon: 'none' + }); + + try { + await this.handlePayFailure(payType, extraData); + } catch (error) { + console.error('支付失败通知失败:', error); + } + + reject(new Error(res.errMsg)); + } + }); + }); + }, + + // ========== 开始轮询支付结果 ========== + async startPolling(payType, extraData = null) { + const maxRetries = 10; + const interval = 2000; + const dingdanid = this.data.currentDingdanid; + + if (!dingdanid) { + wx.showToast({ + title: '订单号缺失', + icon: 'none' + }); + return; + } + + this.showLoading('确认支付结果中...'); + + for (let i = 0; i < maxRetries; i++) { + try { + const pollResult = await this.checkPayStatus(payType, dingdanid, extraData); + + if (pollResult.success) { + this.hideLoading(); + await this.updateAfterPayment(payType, extraData, pollResult.data); + + wx.showToast({ + title: this.getSuccessMessage(payType), + icon: 'success', + duration: 2000 + }); + + return; + } + + await this.sleep(interval); + } catch (error) { + console.error(`轮询失败第${i + 1}次:`, error); + } + } + + this.hideLoading(); + wx.showToast({ + title: '支付确认超时,请联系客服', + icon: 'none' + }); + }, + + // ========== 检查支付状态 ========== + async checkPayStatus(payType, dingdanid, extraData = null) { + let url = ''; + let data = { dingdanid: dingdanid }; + + switch (payType) { + case 'yajin': + url = '/shangpin/yajinlunxun'; + break; + case 'jifen': + url = '/shangpin/jifenlunxun'; + break; + case 'huiyuan': + url = '/shangpin/huiyuanlx'; + data.huiyuanid = extraData; + break; + default: + throw new Error('未知的支付类型'); + } + + try { + const res = await request({ + url, + method: 'POST', + data + }); + + return { + success: res.data.code === 200, + data: res.data + }; + } catch (error) { + console.error('检查支付状态失败:', error); + return { + success: false, + data: null + }; + } + }, + + // ========== 支付失败处理 ========== + async handlePayFailure(payType, extraData = null) { + let url = ''; + let data = { dingdanid: this.data.currentDingdanid }; + + switch (payType) { + case 'yajin': + url = '/shangpin/yajinshibai'; + break; + case 'jifen': + url = '/shangpin/jifenshibai'; + break; + case 'huiyuan': + url = '/shangpin/huiyuanshibai'; + data.huiyuanid = extraData; + break; + default: + return; + } + + try { + await request({ + url, + method: 'POST', + data + }); + } catch (error) { + console.error('支付失败通知失败:', error); + } + }, + + // ========== 支付成功后更新数据 ========== + async updateAfterPayment(payType, extraData = null, responseData = null) { + const app = getApp(); + + switch (payType) { + case 'yajin': + if (responseData && responseData.yajin !== undefined) { + app.globalData.yajin = responseData.yajin; + } + await this.refreshYajinData(); + break; + + case 'jifen': + if (responseData && responseData.jifen !== undefined) { + app.globalData.jinfen = responseData.jifen; + } + await this.refreshJifenData(); + break; + + case 'huiyuan': + if (responseData && responseData.huiyuan) { + app.globalData.jinfen = responseData.jifen; + await this.updateClubmberData(responseData.huiyuan); + } + await this.refreshHuiyuanData(extraData); + break; + } + + this.loadFromGlobalData(); + }, + + // ========== 刷新押金数据 ========== + async refreshYajinData() { + this.loadFromGlobalData(); + }, + + // ========== 刷新积分数据 ========== + async refreshJifenData() { + this.loadFromGlobalData(); + }, + + // ========== 刷新会员数据 ========== + async refreshHuiyuanData(huiyuanId) { + this.loadFromGlobalData(); + await this.fetchHuiyuanList(); + }, + + // ========== 更新会员数据到全局变量 ========== + async updateClubmberData(huiyuanData) { + const app = getApp(); + const globalClubmber = app.globalData.clumber || []; + + const { huiyuanid, huiyuanming, daoqi } = huiyuanData; + + const index = globalClubmber.findIndex(item => item.huiyuanid === huiyuanid); + + if (index >= 0) { + globalClubmber[index].daoqi = daoqi; + } else { + globalClubmber.push({ + huiyuanid: huiyuanid, + huiyuanming: huiyuanming, + daoqi: daoqi + }); + } + + app.globalData.clumber = globalClubmber; + + this.setData({ + clubmber: [...globalClubmber] + }); + }, + + // ========== 检查并刷新数据 ========== + async checkAndRefreshData() { + this.loadFromGlobalData(); + }, + + // ========== 工具方法 ========== + getSuccessMessage(payType) { + switch (payType) { + case 'yajin': return '押金充值成功'; + case 'jifen': return '积分补充成功'; + case 'huiyuan': return '会员购买成功'; + default: return '支付成功'; + } + }, + + showLoading(text = '加载中...') { + this.setData({ + payLoading: true, + loadingText: text + }); + }, + + hideLoading() { + this.setData({ + payLoading: false + }); + }, + + sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + }, + + // ========== 新增功能(余额抵扣等) ========== + onBuyHuiyuanClick(e) { + const huiyuanId = e.currentTarget.dataset.id; + const isTrial = e.currentTarget.dataset.trial === true || e.currentTarget.dataset.trial === 'true'; + + if (isTrial) { + this.handleHuiyuanBuy(e); + return; + } + + this.setData({ + 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 + }); + }, + + hidePayMethodModal() { + this.setData({ showPayMethodModal: false }); + }, + + // 🔥 修改点2:选择支付方式时再次校验积分状态(防止异步过程中积分变化) + onPayMethodSelect(e) { + const method = e.currentTarget.dataset.method; + const { currentBuyType, currentHuiyuanId, currentYajinAmount, jifen } = this.data; + this.hidePayMethodModal(); + + // 如果是积分购买,且积分已满,直接拒绝 + if (currentBuyType === 3 && jifen >= 10) { + wx.showToast({ + title: '积分已满,无需补充', + icon: 'none' + }); + return; + } + + if (method === 'wx') { + if (currentBuyType === 1) { + this.handleHuiyuanBuy({ + currentTarget: { dataset: { id: currentHuiyuanId, trial: 'false' } } + }); + } else if (currentBuyType === 2) { + this.setData({ yajinAmount: currentYajinAmount }, () => { + this.handleYajinPay(); + }); + } else if (currentBuyType === 3) { + // ✅ 调用已修正的积分支付函数 + this.handleJifenBuy({ currentTarget: { dataset: { disabled: 'false' } } }); + } + } else if (method === 'balance') { + this.fetchBalanceOptions(); + } + }, + + // 🔥 修改点3:请求余额抵扣选项前校验积分状态 + async fetchBalanceOptions() { + const { currentBuyType, currentHuiyuanId, currentYajinAmount, currentBuyIsTrial, jifen } = this.data; + + if (currentBuyType === 1 && currentBuyIsTrial) { + wx.showToast({ title: '体验会员仅支持微信支付', icon: 'none' }); + return; + } + + // 如果是积分购买,且积分已满,拒绝发起余额抵扣 + if (currentBuyType === 3 && jifen >= 10) { + wx.showToast({ + title: '积分已满,无需补充', + icon: 'none' + }); + return; + } + + this.showLoading('获取抵扣信息...'); + + try { + const res = await request({ + url: '/shangpin/czhqdy', + method: 'POST', + data: { + leixing: currentBuyType, + huiyuan_id: currentBuyType === 1 ? currentHuiyuanId : undefined, + is_trial: currentBuyType === 1 ? false : undefined, + yajin_jine: currentBuyType === 2 ? parseInt(currentYajinAmount) : undefined, + } + }); + if (res.data.code === 200) { + const options = res.data.data || []; + this.setData({ + balanceOptions: options, + showBalanceModal: true + }); + } else { + wx.showToast({ title: res.data.message || '获取失败', icon: 'none' }); + } + } catch (error) { + console.error('获取抵扣身份失败:', error); + wx.showToast({ title: '网络错误', icon: 'none' }); + } finally { + this.hideLoading(); + } + }, + + hideBalanceModal() { + this.setData({ showBalanceModal: false }); + }, + + onBalanceSelect(e) { + const identityId = e.currentTarget.dataset.id; + const selected = this.data.balanceOptions.find(opt => opt.id === identityId); + if (!selected) return; + + this.setData({ + selectedBalanceId: identityId, + selectedBalanceInfo: selected, + showConfirmModal: true, + showBalanceModal: false + }); + }, + + hideConfirmModal() { + this.setData({ showConfirmModal: false }); + }, + + async confirmBalancePay() { + const { selectedBalanceId, currentBuyType, currentHuiyuanId, currentYajinAmount, currentBuyIsTrial } = this.data; + if (!selectedBalanceId) return; + + this.hideConfirmModal(); + this.showLoading('抵扣支付中...'); + + try { + const res = await request({ + url: '/shangpin/dsqrgmdh', + method: 'POST', + data: { + leixing: currentBuyType, + shenfen_id: selectedBalanceId, + huiyuan_id: currentBuyType === 1 ? currentHuiyuanId : undefined, + 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 ? currentHuiyuanId : null, + responseData + ); + wx.showToast({ title: '购买成功', icon: 'success' }); + } else { + wx.showToast({ title: res.data.message || '购买失败', icon: 'none' }); + } + } catch (error) { + console.error('抵扣支付失败:', error); + wx.showToast({ title: '网络错误', icon: 'none' }); + } finally { + this.hideLoading(); + } + }, + + showMemberDetail(e) { + const item = e.currentTarget.dataset.item; + this.setData({ + currentMemberDetail: item, + showMemberModal: true + }); + }, + + hideMemberModal() { + this.setData({ showMemberModal: false }); + }, }); \ No newline at end of file diff --git a/pages/fighter-recharge/fighter-recharge.wxml b/pages/fighter-recharge/fighter-recharge.wxml index a5187df..42fa21e 100644 --- a/pages/fighter-recharge/fighter-recharge.wxml +++ b/pages/fighter-recharge/fighter-recharge.wxml @@ -47,6 +47,12 @@ 已拥有 + + 可购体验 + + + 体验已用/不可购 + {{item.mingzi}} @@ -56,7 +62,11 @@ ¥ {{item.jiage}} - /30天 + /{{item.formal_days || 30}}天 + + + 体验 + ¥{{item.trial_price}}/{{item.trial_days}}天 @@ -107,20 +117,27 @@ 价格: ¥{{currentHuiyuan.jiage}} + /{{currentHuiyuan.formal_days || 30}}天 已激活,{{currentHuiyuan.buyInfo.daoqi}}后到期 - 激活会员特权 + 正式会员支持微信或余额抵扣 + + 体验版仅微信支付 ¥{{currentHuiyuan.trial_price}}/{{currentHuiyuan.trial_days}}天,终身1次 + + + 本俱乐部尚未为该会员开启体验版,请联系管理员在后台配置 + + bindtap="onBuyHuiyuanClick" data-id="{{currentHuiyuan.id}}" data-trial="false"> - {{currentHuiyuan.isBought ? '立即续费' : '立即激活'}} + {{currentHuiyuan.isBought ? '正式续费' : '购买正式版'}} @@ -128,6 +145,16 @@ + + + 购买体验版 + diff --git a/pages/fighter-recharge/fighter-recharge.wxss b/pages/fighter-recharge/fighter-recharge.wxss index 2f808eb..08f6d12 100644 --- a/pages/fighter-recharge/fighter-recharge.wxss +++ b/pages/fighter-recharge/fighter-recharge.wxss @@ -1,4 +1,5 @@ -/* pages/dashou-chongzhi/index.wxss - 完整保留原有样式,仅删除导航栏相关,调整间距和字体 */ +/* pages/dashou-chongzhi/index.wxss - 逍遥梦主题覆盖 + 机甲基础样式 */ +@import '../../styles/dashou-xym-recharge-theme.wxss'; /* ==================== 基础样式 ==================== */ .page-container { @@ -676,6 +677,32 @@ box-shadow: 0 0 15rpx rgba(255, 107, 157, 0.4); z-index: 20; } + + .vip-badge.trial-badge { + top: 60rpx; + background: linear-gradient(135deg, #ffb347, #ff7043); + } + + .vip-badge.trial-off-badge { + top: 60rpx; + background: rgba(120, 120, 120, 0.85); + box-shadow: none; + } + + .vip-trial-price { + margin-top: 8rpx; + font-size: 22rpx; + color: #ffb347; + } + + .trial-price-label { + margin-right: 8rpx; + color: #a0c8ff; + } + + .trial-price-value { + font-weight: 600; + } .vip-title { margin-bottom: 25rpx; @@ -855,11 +882,35 @@ /* 购买区域 */ .buy-section { display: flex; - align-items: center; - justify-content: space-between; + flex-direction: column; + align-items: stretch; + gap: 20rpx; padding-top: 25rpx; border-top: 1px solid rgba(64, 156, 255, 0.2); } + + .buy-action { + display: flex; + flex-direction: column; + gap: 16rpx; + align-items: flex-end; + } + + .price-days { + font-size: 22rpx; + color: #a0c8ff; + } + + .trial-hint { + margin-top: 6rpx; + color: #ffb347; + } + + .trial-warn { + margin-top: 6rpx; + color: #ff8a8a; + font-size: 22rpx; + } .buy-info { display: flex; @@ -928,6 +979,11 @@ background: linear-gradient(135deg, #1e4b8f, #409cff); } + .tech-buy-btn.trial-buy-btn .buy-btn-bg { + background: linear-gradient(135deg, rgba(255, 179, 71, 0.35), rgba(255, 120, 80, 0.45)); + border: 1px solid rgba(255, 179, 71, 0.6); + } + .tech-buy-btn.renew .buy-btn-bg { background: linear-gradient(135deg, #0f2c5c, #36cfc9); } @@ -1702,6 +1758,4 @@ display: flex !important; align-items: center !important; justify-content: center !important; - } - -@import '../../styles/dashou-xym-recharge-theme.wxss'; \ No newline at end of file + } \ No newline at end of file diff --git a/pages/fighter/fighter.js b/pages/fighter/fighter.js index 99a60b9..1a5954d 100644 --- a/pages/fighter/fighter.js +++ b/pages/fighter/fighter.js @@ -2,6 +2,18 @@ import { createPage, request, isRoleStatusActive, isCenterPageActive, syncRoleStatuses, syncProfileFields, syncGroupFields, ensureRoleOnCenterPage, clearCacheAndEnterNormal, lockPrimaryRole, migrateLegacyCenterRole, enterLockedRole, parseSceneOptions } from '../../utils/base-page.js' import { resolveAvatarUrl } from '../../utils/avatar.js' import { openPrivateChat, buildInviterPeerId } from '../../utils/im-user.js' +import { isStaffMode, getStaffContext } from '../../utils/staff-api.js' +import { ensurePhoneAuth } from '../../utils/phone-auth.js' +import { + ICON_KEYS, + resolveMiniappIcon, + resolveConfiguredIcon, + refreshPindaoConfig, + getPindaoConfig, + resolvePindaoImages, + warmupPindaoConfig, + PINDAO_ICON_FALLBACK, +} from '../../utils/miniapp-icons.js' const PLATFORM_INVITE_CODE = '0000008RffVgKHMj7kQC' @@ -41,6 +53,7 @@ Page(createPage({ zuzhangInviterCache: null, chenghaoList: [], guanshiChenghaoList: [], + identityTagList: [], gszhstatus: '', yaoqingzongshu: 0, fenyongzonge: '0.00', @@ -65,13 +78,25 @@ Page(createPage({ statusBar: 20, navBar: 44, + pindaoVisible: false, + pindaoTitle: '频道', + pindaoChannelNo: '', + examEnabled: false, + examPassed: false, + pindaoImages: [], + + scrollViewRefreshing: false, + showRechargeBanners: false, + showPindaoEntry: true, }, _buildImgUrls(ossImageUrl) { + const app = getApp(); const dsBase = ossImageUrl + 'beijing/dashouduan/'; const iconBase = ossImageUrl + 'beijing/dashouduan/dashoutubiaobeijing/'; const gsBase = ossImageUrl + 'beijing/guanshiduan/'; const khBase = ossImageUrl + 'beijing/kaohe/'; + const icon = (key, fallback) => resolveMiniappIcon(app, key, fallback); return { pageBg: dsBase + 'page-bg.jpg', userCardBg: dsBase + 'user-card-bg.png', @@ -83,7 +108,7 @@ Page(createPage({ listBg: dsBase + 'list-card-bg.png', topBg: iconBase + 'top-bg.jpg', cardBg: iconBase + 'card-bg.jpg', - iconRefresh: gsBase + 'icon-refresh.png', + iconRefresh: icon(ICON_KEYS.ICON_REFRESH, gsBase + 'icon-refresh.png'), iconCopy: gsBase + 'icon-copy.png', iconVip: dsBase + 'icon-vip.png', iconArrowLight: dsBase + 'icon-arrow-light.png', @@ -118,6 +143,7 @@ Page(createPage({ iconSwitch: '/images/_exit.png', iconKefu: ossImageUrl + 'beijing/tubiao/grzx_kefu.jpg', iconKuaishou: ossImageUrl + 'beijing/tubiao/grzx_guanzhualong.jpg', + iconPindao: icon(ICON_KEYS.MINE_PINDAO, ossImageUrl + PINDAO_ICON_FALLBACK), iconKaoheDafen: khBase + 'daofen.png', iconKaoheJilu: khBase + 'jilu.png', iconKaoheZhongxin: khBase + 'zhongxin.png', @@ -128,8 +154,8 @@ Page(createPage({ 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', + bannerVip: resolveConfiguredIcon(app, ICON_KEYS.FIGHTER_RECHARGE_MEMBER), + bannerDeposit: resolveConfiguredIcon(app, ICON_KEYS.FIGHTER_RECHARGE_DEPOSIT), }; }, @@ -177,9 +203,28 @@ Page(createPage({ } } this.checkColdStartPopup('dashouduan'); + this.syncConfiguredAssets(); }, - onShow() { + syncConfiguredAssets() { + const app = getApp(); + const bannerVip = resolveConfiguredIcon(app, ICON_KEYS.FIGHTER_RECHARGE_MEMBER); + const bannerDeposit = resolveConfiguredIcon(app, ICON_KEYS.FIGHTER_RECHARGE_DEPOSIT); + const imgUrls = { ...this.data.imgUrls, bannerVip, bannerDeposit }; + this.setData({ + imgUrls, + showRechargeBanners: !!(bannerVip || bannerDeposit), + showPindaoEntry: this.data.isDashou || this.data.isGuanshi || this.data.isZuzhang || this.data.isKaoheguan, + }); + }, + + async onShow() { + if (!app.globalData._dashouPhoneChecked) { + const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' }); + if (!phoneOk) return; + app.globalData._dashouPhoneChecked = true; + } + migrateLegacyCenterRole(getApp()); lockPrimaryRole('dashou', getApp()); wx.setStorageSync('isJinpai', 0); @@ -190,16 +235,17 @@ Page(createPage({ this.setData({ inviterCache, zuzhangInviterCache }); this.registerNotificationComponent(); + warmupPindaoConfig(app); this.checkRoleStatuses(); + this.syncConfiguredAssets(); if (isCenterPageActive(this, 'isDashou', 'dashoustatus')) { if (!this.data.isDashou) this.setData({ isDashou: true }); ensureRoleOnCenterPage(this, 'dashou'); - setTimeout(() => this.refreshAllInfo(false), 300); + this.loadExamStatus(); } else if (isRoleStatusActive(wx.getStorageSync('guanshistatus')) || isRoleStatusActive(wx.getStorageSync('zuzhangstatus')) || isRoleStatusActive(wx.getStorageSync('kaoheguanstatus'))) { ensureRoleOnCenterPage(this, 'dashou'); - setTimeout(() => this.refreshAllInfo(false), 300); } const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; @@ -277,12 +323,17 @@ Page(createPage({ 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: '去认证' }); } @@ -293,35 +344,37 @@ Page(createPage({ }, 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 }); + if (this._profileRefreshing) return; + this._profileRefreshing = true; + 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.loadIdentityTags(), + ]); + this.checkRoleStatuses(); + 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() { @@ -332,6 +385,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) { @@ -1015,6 +1096,14 @@ Page(createPage({ onTapAuthItem(e) { const type = e.currentTarget.dataset.type; + if (type === 'staff') { + if (isStaffMode()) { + enterLockedRole('shangjia', getApp()); + } else { + wx.navigateTo({ url: '/pages/staff-join/staff-join' }); + } + return; + } if (type === 'shangjia') { this.onTapShangjiaAuth(); return; @@ -1038,6 +1127,21 @@ Page(createPage({ wx.navigateTo({ url: '/pages/verify/verify?type=shangjia' }); }, goToReceiveOrder() { wx.navigateTo({ url: '/pages/accept-order/accept-order' }) }, + goToDashouExam() { wx.navigateTo({ url: '/pages/dashou-exam/dashou-exam' }) }, + + 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) { /* 静默 */ } + }, goToMyOrders() { wx.navigateTo({ url: '/pages/fighter-orders/fighter-orders' }) }, goToMyPunishment() { wx.navigateTo({ url: '/pages/penalty/penalty/penalty' }) }, goToRecharge() { wx.navigateTo({ url: '/pages/fighter-recharge/fighter-recharge' }) }, @@ -1045,7 +1149,33 @@ Page(createPage({ 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 8458bf8..a31615c 100644 --- a/pages/fighter/fighter.json +++ b/pages/fighter/fighter.json @@ -2,7 +2,8 @@ "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": "#fff8e1", diff --git a/pages/fighter/fighter.wxml b/pages/fighter/fighter.wxml index 8f9bf1a..b9f6e40 100644 --- a/pages/fighter/fighter.wxml +++ b/pages/fighter/fighter.wxml @@ -26,14 +26,23 @@ - 我的 + + 我的 + - - - - - + - + + + + - + @@ -70,6 +82,11 @@ + + + + + @@ -85,10 +102,14 @@ - - @@ -217,6 +242,7 @@ 在线客服 关注快手 + 频道 联系邀请人 用户规则 返回点单端 @@ -236,3 +262,10 @@ + diff --git a/pages/fighter/fighter.wxss b/pages/fighter/fighter.wxss index d10bfb2..120d290 100644 --- a/pages/fighter/fighter.wxss +++ b/pages/fighter/fighter.wxss @@ -40,6 +40,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; @@ -134,6 +147,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; @@ -233,6 +260,23 @@ 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; @@ -356,11 +400,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 { 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/invite-manager/invite-manager.js b/pages/invite-manager/invite-manager.js index 7a0fff4..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('#ffaa00'); // 金黄色,呼应组长主题 - 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/merchant-dispatch/merchant-dispatch.js b/pages/merchant-dispatch/merchant-dispatch.js index e28da8e..28e1381 100644 --- a/pages/merchant-dispatch/merchant-dispatch.js +++ b/pages/merchant-dispatch/merchant-dispatch.js @@ -1,11 +1,15 @@ // pages/merchant-dispatch/merchant-dispatch.js import { createPage, request } from '../../utils/base-page.js' import PopupService from '../../services/popupService.js' // 引入弹窗服务 +import { STAFF_API, isStaffMode, refreshStaffContext, getStaffContext, restoreStaffContextAfterAuth, isMerchantPortalUser } from '../../utils/staff-api.js' Page(createPage({ data: { - // 商家余额 (从全局变量获取) + // 可用余额 / 客服额度 sjyue: '0.00', + isStaffMode: false, + balanceLabel: '可用余额', + balanceTip: '派单将从此余额扣除', // 商品类型列表 shangpinList: [], @@ -20,6 +24,7 @@ Page(createPage({ dingdanJieshao: '', // 订单介绍 dingdanBeizhu: '', // 订单备注 laobanName: '', // 老板游戏昵称/UID + waibuDingdanId: '', // 外部平台订单号 zhidingUid: '', // 指定打手UID jiage: '', // 订单价格 @@ -42,7 +47,15 @@ Page(createPage({ bgImageUrl: '' }, - onLoad() { + async onLoad() { + if (wx.getStorageSync('token')) { + await restoreStaffContextAfterAuth() + } + if (!isMerchantPortalUser()) { + wx.showToast({ title: '请先成为商家或绑定客服', icon: 'none' }) + setTimeout(() => wx.navigateBack(), 1500) + return + } this.loadBgImage() // 拼接背景图 this.loadShangjiaYue() // 从全局变量获取商家余额 this.loadShangpinTypes() // 加载商品类型 @@ -50,15 +63,9 @@ Page(createPage({ }, onShow() { - // 每次显示页面时刷新余额(可能从其他页面充值后返回) this.loadShangjiaYue() - // 重新拼接背景图(确保最新) this.loadBgImage() - - // 注册通知组件(用于全局消息提示) this.registerNotificationComponent() - - // 重置按钮缩进定时器(重新开始5秒倒计时) this.startTutorialBtnTimer() }, @@ -87,6 +94,22 @@ Page(createPage({ // ==================== 基础数据加载 ==================== loadShangjiaYue() { + const staff = isStaffMode() + this.setData({ + isStaffMode: staff, + balanceLabel: staff ? '可用额度' : '可用余额', + balanceTip: staff ? '派单将从您的客服额度扣除(需老板先划额度)' : '派单将从此余额扣除', + }) + if (staff) { + const ctx = getStaffContext() || {} + const wallet = ctx.wallet || {} + this.setData({ sjyue: wallet.quota_available || '0.00' }) + refreshStaffContext(request).then((fresh) => { + const w = (fresh && fresh.wallet) || wallet + this.setData({ sjyue: w.quota_available || '0.00' }) + }).catch(() => {}) + return + } const app = getApp() const shangjiaData = app.globalData.shangjia || {} this.setData({ @@ -97,13 +120,16 @@ Page(createPage({ // 加载商品类型(与极速派单完全一致) async loadShangpinTypes() { this.setData({ isLoading: true }) + const staff = isStaffMode() try { const res = await request({ - url: '/dingdan/sjspleixing', + url: staff ? STAFF_API.productTypes : '/dingdan/sjspleixing', method: 'POST' }) - if (res && res.data.code === 200) { - let list = res.data.data || [] + const code = res && res.data && res.data.code + if (code === 200 || code === 0) { + let list = (res.data.data || []) + if (!Array.isArray(list) && list.list) list = list.list // 强制倒序排列(与极速派单保持一致) list.reverse() this.setData({ @@ -115,14 +141,14 @@ Page(createPage({ this.setSelectedType(list[0]) } } else { - throw new Error(res.data.msg || '加载失败') + throw new Error((res.data && res.data.msg) || '加载失败') } } catch (error) { console.error('加载商品类型失败:', error) this.setData({ isLoading: false }) wx.showToast({ - title: '加载类型失败', - icon: 'error', + title: error.message || '加载类型失败', + icon: 'none', duration: 2000 }) } @@ -189,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 }) }, @@ -282,21 +311,29 @@ Page(createPage({ } } - // 4. 验证余额是否足够 + // 4. 验证余额/额度是否足够 const balance = parseFloat(sjyue) if (price > balance) { - wx.showModal({ - title: '余额不足', - content: `当前余额${sjyue}元,需要${jiage}元。请先充值。`, - confirmText: '去充值', - success: (res) => { - if (res.confirm) { - wx.navigateTo({ - url: '/pages/merchant-recharge/merchant-recharge' - }) + if (this.data.isStaffMode) { + wx.showModal({ + title: '额度不足', + content: `当前可用额度${sjyue}元,需要${jiage}元。请联系商家老板划额度。`, + showCancel: false, + }) + } else { + wx.showModal({ + title: '余额不足', + content: `当前余额${sjyue}元,需要${jiage}元。请先充值。`, + confirmText: '去充值', + success: (res) => { + if (res.confirm) { + wx.navigateTo({ + url: '/pages/merchant-recharge/merchant-recharge' + }) + } } - } - }) + }) + } return false } @@ -309,6 +346,7 @@ Page(createPage({ dingdanJieshao: '', dingdanBeizhu: '', laobanName: '', + waibuDingdanId: '', zhidingUid: '', jiage: '', selectedLabelId: '', @@ -338,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, // 新增字段 @@ -349,29 +388,31 @@ Page(createPage({ try { const res = await request({ - url: '/dingdan/sjpaifa', + url: isStaffMode() ? STAFF_API.orderDispatch : '/dingdan/sjpaifa', method: 'POST', data: postData }) if (res && res.data.code === 200) { - // 派单成功 wx.showToast({ title: '派单成功', icon: 'success', duration: 2000 }) - // 更新全局变量中的商家信息(余额、发单量、流水) const orderAmount = parseFloat(this.data.jiage) - const app = getApp() - if (app.globalData.shangjia) { - const newBalance = parseFloat(app.globalData.shangjia.sjyue || '0') - orderAmount - app.globalData.shangjia.sjyue = newBalance.toFixed(2) - app.globalData.shangjia.fadanzong = (parseInt(app.globalData.shangjia.fadanzong || '0') + 1).toString() - app.globalData.shangjia.riliushui = (parseFloat(app.globalData.shangjia.riliushui || '0') + orderAmount).toFixed(2) - app.globalData.shangjia.yueliushui = (parseFloat(app.globalData.shangjia.yueliushui || '0') + orderAmount).toFixed(2) - this.setData({ sjyue: app.globalData.shangjia.sjyue }) + if (this.data.isStaffMode) { + this.loadShangjiaYue() + } else { + const app = getApp() + if (app.globalData.shangjia) { + const newBalance = parseFloat(app.globalData.shangjia.sjyue || '0') - orderAmount + app.globalData.shangjia.sjyue = newBalance.toFixed(2) + app.globalData.shangjia.fadanzong = (parseInt(app.globalData.shangjia.fadanzong || '0') + 1).toString() + app.globalData.shangjia.riliushui = (parseFloat(app.globalData.shangjia.riliushui || '0') + orderAmount).toFixed(2) + app.globalData.shangjia.yueliushui = (parseFloat(app.globalData.shangjia.yueliushui || '0') + orderAmount).toFixed(2) + this.setData({ sjyue: app.globalData.shangjia.sjyue }) + } } // 延迟重置表单,避免用户连续提交 diff --git a/pages/merchant-dispatch/merchant-dispatch.wxml b/pages/merchant-dispatch/merchant-dispatch.wxml index 947aba1..b535bc2 100644 --- a/pages/merchant-dispatch/merchant-dispatch.wxml +++ b/pages/merchant-dispatch/merchant-dispatch.wxml @@ -2,12 +2,12 @@ - 可用余额 + {{balanceLabel}} {{sjyue}} - 派单将从此余额扣除 + {{balanceTip}} @@ -84,6 +84,21 @@ /> + + + + 拼多多订单号 + 选填 + + + + diff --git a/pages/merchant-home/merchant-home.js b/pages/merchant-home/merchant-home.js index f82958d..ec6ca8c 100644 --- a/pages/merchant-home/merchant-home.js +++ b/pages/merchant-home/merchant-home.js @@ -1,5 +1,13 @@ import { createPage, request, ensureRoleOnCenterPage } from '../../utils/base-page.js'; import { getOrderStatusText } from '../../utils/api-helper.js'; +import { + STAFF_API, isStaffMode, isMerchantPortalUser, refreshStaffContext, syncStaffUi, + restoreStaffContextAfterAuth, applyStaffStatsToPage, getStaffContext, +} from '../../utils/staff-api.js'; +import { ensurePhoneAuth } from '../../utils/phone-auth.js'; +import { fetchGonggaoLunbo, isGonggaoCacheValid } from '../../utils/display-config'; +import { ICON_KEYS, resolveMiniappIcon } from '../../utils/miniapp-icons.js'; +import { fetchMerchantOrderStats, applyOrderStatsToHome } from '../../utils/merchant-order-stats.js'; const app = getApp(); @@ -12,46 +20,106 @@ Page(createPage({ statusBar: 20, navBar: 44, isShangjia: false, + isStaffMode: false, + staffRoleName: '', + quotaAvailable: '0.00', + canDispatch: true, + canStaffManage: true, lunboList: [], lunbozhanwei: '/images/lunbozhanwei.jpg', gonggao: '', swiperCurrent: 0, imgUrls: { - noticeIco: 'https://bintao.xmxym88.com/xcx/bintao/38.png', - statCardBg: 'https://bintao.xmxym88.com/xcx/bintao/3.png', - kefuKeyBtn: 'https://bintao.xmxym88.com/xcx/bintao/13.png', - kefuListBtn: 'https://bintao.xmxym88.com/xcx/bintao/21.png', + noticeIco: '', + statCardBg: '', + kefuKeyBtn: '', + kefuListBtn: '', + iconRegular: '', + iconCustom: '', }, stats: { pendingCount: 0, pendingAmount: '0.00', todayDispatch: 0, todayRefund: 0, + completedCount: 0, + completedAmount: '0.00', + refundCount: 0, + refundAmount: '0.00', + dispatchCount: 0, + dispatchAmount: '0.00', }, recentOrders: [], recentLoading: false, }, - onLoad() { + async onLoad() { const sys = wx.getSystemInfoSync(); this.setData({ statusBar: sys.statusBarHeight || 20, navBar: (sys.statusBarHeight || 20) + 44, lunbozhanwei: app.globalData.lunbozhanwei || '/images/lunbozhanwei.jpg', }); + if (wx.getStorageSync('token')) { + await restoreStaffContextAfterAuth(); + } this.checkRole(); - this.waitForConfigAndLoadBanner(); + syncStaffUi(this); + this.setupImageUrls(); + await this.waitForConfigAndLoadBanner(); + if (this.data.isShangjia) { + await this.loadDashboard(false); + } + this._merchantHomeSessionReady = true; + this._skipShowRefresh = true; this.checkColdStartPopup('shangjiaduan'); }, - onShow() { + async onShow() { + if (!app.globalData._shangjiaPhoneChecked) { + const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'shangjia' }); + if (!phoneOk) return; + app.globalData._shangjiaPhoneChecked = true; + } + this.registerNotificationComponent(); + // 先用本地缓存渲染,再向服务端同步子客服身份 this.checkRole(); + syncStaffUi(this); + if (app.globalData.ossImageUrl || app.globalData.miniappIcons) { + this.setupImageUrls(); + } + const token = wx.getStorageSync('token'); + const isOwner = Number(wx.getStorageSync('shangjiastatus')) === 1; + if (token && !isOwner) { + await restoreStaffContextAfterAuth(); + this.checkRole(); + syncStaffUi(this); + } else if (isStaffMode()) { + await refreshStaffContext(request); + this.checkRole(); + syncStaffUi(this); + applyStaffStatsToPage(this, getStaffContext()); + } + if (this.data.isShangjia) { ensureRoleOnCenterPage(this, 'shangjia'); - this.loadDashboard(); } - this.loadBannerData(); + + if (this._skipShowRefresh) { + this._skipShowRefresh = false; + return; + } + + const silent = this._merchantHomeSessionReady; + if (this.data.isShangjia) { + this.loadDashboard(silent); + } + if (silent) { + this.loadBannerData(true, true); + } else { + this.loadBannerData(false); + } }, onPullDownRefresh() { @@ -65,10 +133,36 @@ Page(createPage({ waitForConfigAndLoadBanner() { if (app.globalData.ossImageUrl || app.globalData.apiBaseUrl) { - this.loadBannerData(true); - return; + this.setupImageUrls(); + return this.loadBannerData(true); } - setTimeout(() => 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, `${homeDir}notice.png`), + statCardBg: icon(ICON_KEYS.MERCHANT_HOME_STAT_BG, `${ossBase}beijing/shangjiaduan/stat_card_bg.png`), + kefuKeyBtn: icon(ICON_KEYS.MERCHANT_HOME_KEFU_KEY, `${homeDir}kefu_key.png`), + kefuListBtn: icon(ICON_KEYS.MERCHANT_HOME_KEFU_LIST, `${homeDir}kefu_list.png`), + iconRegular: icon(ICON_KEYS.MERCHANT_HOME_REGULAR, `${homeDir}regular_dispatch.png`), + iconCustom: icon(ICON_KEYS.MERCHANT_HOME_CUSTOM, `${homeDir}custom_dispatch.png`), + }, + }); }, processImageUrls(urlList) { @@ -78,14 +172,13 @@ Page(createPage({ }, checkRole() { - const ok = Number(wx.getStorageSync('shangjiastatus')) === 1; - this.setData({ isShangjia: ok }); + 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 || '', @@ -93,45 +186,30 @@ 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), ]); }, async loadShangjiaBrief() { + if (isStaffMode()) { + const ctx = await refreshStaffContext(request); + syncStaffUi(this); + applyStaffStatsToPage(this, ctx); + return; + } try { const res = await request({ url: '/yonghu/shangjiaxinxi', @@ -155,39 +233,24 @@ Page(createPage({ async loadPendingStats() { try { - const res = await request({ - url: '/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: '/dingdan/sjdingdanhq', + url: isStaffMode() ? STAFF_API.orderList : '/dingdan/sjdingdanhq', method: 'POST', data: { zhuangtai_list: ALL_ORDER_ZHUANGTAI, @@ -206,7 +269,7 @@ Page(createPage({ return { dingdan_id: item.dingdan_id, title: item.jieshao || '暂无描述', - time: item.create_time || '', + time: item.create_time || item.CreateTime || '', status: getOrderStatusText(item.zhuangtai), money: item.jine || '0.00', raw, @@ -217,7 +280,10 @@ Page(createPage({ } catch (e) { console.error('最近订单加载失败', e); } finally { - this.setData({ recentLoading: false }); + this._recentOrdersRequesting = false; + if (!silent) { + this.setData({ recentLoading: false }); + } } }, @@ -225,12 +291,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 354e576..49adef1 100644 --- a/pages/merchant-home/merchant-home.wxml +++ b/pages/merchant-home/merchant-home.wxml @@ -7,7 +7,7 @@ - + 商家入驻 完成入驻后可发单、管理订单 @@ -18,6 +18,11 @@ + + + {{gonggao}} + + - - - {{gonggao}} + + 商家客服 · {{staffRoleName}} · 可用额度 ¥{{quotaAvailable}} - - - + + + + + + + + 链接派单 + 模板管理 · 生成链接 + + @@ -62,6 +74,28 @@ {{stats.pendingAmount}} + + + 成交总量 + {{stats.completedCount}} + + + + 成交总额(元) + {{stats.completedAmount}} + + + + + 退款总量 + {{stats.refundCount}} + + + + 退款总额(元) + {{stats.refundAmount}} + + 今日派单(数) @@ -73,7 +107,7 @@ {{stats.todayRefund}} - + diff --git a/pages/merchant-home/merchant-home.wxss b/pages/merchant-home/merchant-home.wxss index 1945741..4a6d535 100644 --- a/pages/merchant-home/merchant-home.wxss +++ b/pages/merchant-home/merchant-home.wxss @@ -92,7 +92,7 @@ page { display: flex; align-items: center; background: #fdf9db; - margin: 0 24rpx; + margin: 12rpx 24rpx 0; border-radius: 10rpx; padding: 10rpx 16rpx; } @@ -111,11 +111,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 { @@ -125,18 +136,35 @@ page { background-repeat: no-repeat; } -.sj-fadan-img-btn--left { - background-image: url('https://bintao.xmxym88.com/xcx/bintao/74.png'); - margin-right: 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-img-btn--right { - background-image: url('https://bintao.xmxym88.com/xcx/bintao/76.png'); - margin-left: 8rpx; +.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; @@ -307,3 +335,16 @@ page { -webkit-line-clamp: 2; -webkit-box-orient: vertical; } + +.staff-banner { + margin-top: 16rpx; + padding: 16rpx 24rpx; + background: #fff8e8; + border-radius: 12rpx; + border: 1rpx solid #f0d89a; +} + +.staff-banner-txt { + font-size: 24rpx; + color: #8a6d2b; +} diff --git a/pages/merchant-kefu-key/merchant-kefu-key.js b/pages/merchant-kefu-key/merchant-kefu-key.js index 07716c2..778c87e 100644 --- a/pages/merchant-kefu-key/merchant-kefu-key.js +++ b/pages/merchant-kefu-key/merchant-kefu-key.js @@ -1,60 +1,128 @@ -/** 客服邀请码/秘钥 - UI 预览,后端接入前使用 mock 数据 */ +/** 客服邀请码管理 — 对接 merchant-staff API */ +import request from '../../utils/request.js'; +import { STAFF_API } from '../../utils/staff-api.js'; + Page({ data: { statusBar: 20, gudingCode: 'XQKF2026', - kefuTips: '客服秘钥用于绑定子客服账号,后端接口就绪后将自动替换为真实数据。', - sublist: [{ name: '全部' }, { name: '未使用' }, { name: '已使用' }], + kefuTips: '一次性邀请码绑定子客服,固定码仅展示不可绑定。', + sublist: [{ name: '全部', status: null }, { name: '未使用', status: 0 }, { name: '已使用', status: 1 }], subCurrent: 0, - list: [ - { id: 1, typeName: '财务', code: 'CW-8X2K9P', status: 0, adddate: '2026-06-10' }, - { id: 2, typeName: '总管', code: 'ZG-3M7N1Q', status: 1, adddate: '2026-06-08', usedate: '2026-06-12', user: { nickname: '客服小张', avatar: '/images/default-avatar.png' } }, - ], + list: [], + roles: [], showQrcodeTips: false, + loading: false, }, onLoad() { const sys = wx.getSystemInfoSync(); this.setData({ statusBar: sys.statusBarHeight || 20 }); + this.loadRoles(); + this.loadList(); }, - goBack() { - wx.navigateBack(); + onShow() { + this.loadList(); }, + async loadRoles() { + try { + const res = await request({ url: STAFF_API.roleList, method: 'POST' }); + if (res.data && res.data.code === 0) { + this.setData({ roles: res.data.data.roles || [] }); + } + } catch (e) {} + }, + + async loadList() { + const tab = this.data.sublist[this.data.subCurrent]; + try { + const res = await request({ + url: STAFF_API.inviteList, + method: 'POST', + data: tab.status === null ? {} : { status: tab.status }, + }); + if (res.data && res.data.code === 0) { + const d = res.data.data || {}; + this.setData({ + list: d.list || [], + gudingCode: d.gudingCode || 'XQKF2026', + }); + } + } catch (e) { + wx.showToast({ title: '加载失败', icon: 'none' }); + } + }, + + goBack() { wx.navigateBack(); }, + copyFixed() { wx.setClipboardData({ data: this.data.gudingCode }); }, switchSub(e) { - this.setData({ subCurrent: Number(e.currentTarget.dataset.idx) }); + this.setData({ subCurrent: Number(e.currentTarget.dataset.idx) }, () => this.loadList()); }, - addKey() { - wx.showToast({ title: '后端接入后可添加', icon: 'none' }); + async addKey() { + const roles = this.data.roles; + if (!roles.length) { + wx.showToast({ title: '暂无角色,请稍后重试', icon: 'none' }); + return; + } + const names = roles.map(r => r.role_name); + wx.showActionSheet({ + itemList: names, + success: async (r) => { + const role = roles[r.tapIndex]; + wx.showLoading({ title: '生成中' }); + try { + const res = await request({ + url: STAFF_API.inviteCreate, + method: 'POST', + data: { role_id: role.id }, + }); + if (res.data && res.data.code === 0) { + wx.showToast({ title: '已生成', icon: 'success' }); + this.loadList(); + } else { + wx.showToast({ title: res.data.msg || '失败', icon: 'none' }); + } + } finally { + wx.hideLoading(); + } + }, + }); }, copyCode(e) { wx.setClipboardData({ data: e.currentTarget.dataset.code || '' }); }, - delKey() { - wx.showToast({ title: '预览数据不可删除', icon: 'none' }); - }, - - showTips() { - this.setData({ showQrcodeTips: true }); - }, - - closeTips() { - this.setData({ showQrcodeTips: false }); - }, - - previewQr() { - wx.previewImage({ - urls: ['https://xy-1314784552.cos.ap-guangzhou.myqcloud.com/uploads/images/ewm.png'], + async delKey(e) { + const id = e.currentTarget.dataset.id; + if (!id) return; + const res = await request({ + url: STAFF_API.inviteRevoke, + method: 'POST', + data: { id }, }); + if (res.data && res.data.code === 0) { + wx.showToast({ title: '已作废' }); + this.loadList(); + } else { + wx.showToast({ title: res.data.msg || '失败', icon: 'none' }); + } }, + showTips() { this.setData({ showQrcodeTips: true }); }, + closeTips() { this.setData({ showQrcodeTips: false }); }, + goAudit() { + wx.navigateTo({ url: '/pages/merchant-staff-audit/merchant-staff-audit' }); + }, + goToRoleManage() { + wx.navigateTo({ url: '/pages/merchant-staff-role/merchant-staff-role' }); + }, noop() {}, }); diff --git a/pages/merchant-kefu-key/merchant-kefu-key.wxml b/pages/merchant-kefu-key/merchant-kefu-key.wxml index 76ea3a5..1696a56 100644 --- a/pages/merchant-kefu-key/merchant-kefu-key.wxml +++ b/pages/merchant-kefu-key/merchant-kefu-key.wxml @@ -26,6 +26,16 @@ 添加 + + 角色与权限管理(资金池/划额度在此配置角色) + + + + + 操作日志与客服排行 + + + 秘钥使用方法 @@ -50,7 +60,6 @@ 暂无卡密 - 预览 · 接口就绪后对接真实数据 diff --git a/pages/merchant-kefu-list/merchant-kefu-list.js b/pages/merchant-kefu-list/merchant-kefu-list.js index 20e28fe..8364979 100644 --- a/pages/merchant-kefu-list/merchant-kefu-list.js +++ b/pages/merchant-kefu-list/merchant-kefu-list.js @@ -1,47 +1,433 @@ -/** 客服列表 - UI 预览,mock 数据 */ +/** 客服列表 — 派单量 / 剩余额度 / 封禁解封 */ + +import request from '../../utils/request.js'; + +import { STAFF_API, isStaffMode } from '../../utils/staff-api.js'; + + + +const app = getApp(); + + + +function avatarUrl(path) { + + if (!path) return '/images/default-avatar.png'; + + if (path.startsWith('http')) return path; + + const oss = app.globalData.ossImageUrl || ''; + + const p = path.startsWith('/') ? path.slice(1) : path; + + return oss ? oss + p : '/images/default-avatar.png'; + +} + + + Page({ + data: { + statusBar: 20, - list: [ - { - id: 1, - nickname: '客服总管-阿星', - avatar: '/images/default-avatar.png', - uid: '1008601', - isChief: true, - isFinance: false, - orderCount: 128, - settleCount: 96, - settleAmount: '12800.00', - }, - { - id: 2, - nickname: '财务-小李', - avatar: '/images/default-avatar.png', - uid: '1008602', - isChief: false, - isFinance: true, - orderCount: 56, - settleCount: 52, - settleAmount: '6800.00', - }, - ], + + list: [], + + isOwner: false, + }, + + onLoad() { + const sys = wx.getSystemInfoSync(); - this.setData({ statusBar: sys.statusBarHeight || 20 }); + + this.setData({ statusBar: sys.statusBarHeight || 20, isOwner: !isStaffMode() }); + + this.loadList(); + }, - goBack() { - wx.navigateBack(); + + + onShow() { + + this.loadList(); + + }, + + + + formatList(raw) { + + return (raw || []).map((item) => ({ + + ...item, + + avatar: avatarUrl(item.avatar), + + todayDispatchCount: item.todayDispatchCount != null ? item.todayDispatchCount : 0, + + todayDispatchAmount: item.todayDispatchAmount || '0.00', + + dispatchCount: item.dispatchCount != null ? item.dispatchCount : (item.orderCount || 0), + + dispatchAmount: item.dispatchAmount || '0.00', + + settleCount: item.settleCount != null ? item.settleCount : 0, + + settleAmount: item.settleAmount || '0.00', + + refundCount: item.refundCount != null ? item.refundCount : 0, + + refundAmount: item.refundAmount || '0.00', + + quotaAvailable: item.quota_available || '0.00', + + quotaRevokable: item.quota_available || '0.00', + + isSelf: !!item.isSelf, + + })); + + }, + + + + rejectSelf(id) { + + const row = (this.data.list || []).find((x) => String(x.id) === String(id)); + + if (row && row.isSelf) { + + wx.showToast({ title: '不能对本人操作', icon: 'none' }); + + return true; + + } + + return false; + + }, + + + + async loadList() { + + try { + + const res = await request({ url: STAFF_API.memberList, method: 'POST' }); + + if (res.data && res.data.code === 0) { + + const raw = (res.data.data && res.data.data.list) || []; + + this.setData({ list: this.formatList(raw) }); + + } + + } catch (e) { + + wx.showToast({ title: '加载失败', icon: 'none' }); + + } + + }, + + + + goBack() { wx.navigateBack(); }, + + + + goBack() { wx.navigateBack(); }, + + 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) { + wx.setClipboardData({ data: e.currentTarget.dataset.uid || '' }); + }, - onAction(e) { - wx.showToast({ title: '后端接入后生效', icon: 'none' }); + + + async onAllocate(e) { + + const id = e.currentTarget.dataset.id; + + if (this.rejectSelf(id)) return; + + wx.showModal({ + + title: '划额度', + + editable: true, + + placeholderText: '输入金额', + + success: async (r) => { + + if (!r.confirm || !r.content) return; + + const res = await request({ + + url: STAFF_API.walletAllocate, + + method: 'POST', + + data: { member_id: id, amount: r.content }, + + }); + + wx.showToast({ + + title: (res.data && res.data.code === 0) ? '成功' : (res.data.msg || '失败'), + + icon: 'none', + + }); + + this.loadList(); + + }, + + }); + }, + + + + async onRevoke(e) { + + const id = e.currentTarget.dataset.id; + + if (this.rejectSelf(id)) return; + + const available = e.currentTarget.dataset.available || '0'; + + wx.showModal({ + + title: '收回额度', + + editable: true, + + placeholderText: `最多可收回 ${available} 元`, + + success: async (r) => { + + if (!r.confirm || !r.content) return; + + const res = await request({ + + url: STAFF_API.walletRevoke, + + method: 'POST', + + data: { member_id: id, amount: r.content }, + + }); + + wx.showToast({ + + title: (res.data && res.data.code === 0) ? '已收回' : (res.data.msg || '失败'), + + icon: 'none', + + }); + + if (res.data && res.data.code === 0) this.loadList(); + + }, + + }); + + }, + + + + async onToggleDisable(e) { + + const id = e.currentTarget.dataset.id; + + if (this.rejectSelf(id)) return; + + const disabled = e.currentTarget.dataset.disabled; + + const action = disabled ? '解封' : '封禁'; + + wx.showModal({ + + title: `${action}客服`, + + content: disabled ? '解封后该客服可继续登录操作' : '封禁后该客服无法使用商家端功能', + + success: async (r) => { + + if (!r.confirm) return; + + const res = await request({ + + url: STAFF_API.memberDisable, + + method: 'POST', + + data: { member_id: id, disable: !disabled }, + + }); + + wx.showToast({ title: (res.data && res.data.msg) || action, icon: 'none' }); + + this.loadList(); + + }, + + }); + + }, + + + + async onRemove(e) { + + const id = e.currentTarget.dataset.id; + + wx.showModal({ + + title: '移除客服', + + content: '移除后该用户可认证商家或绑定其他商家', + + success: async (r) => { + + if (!r.confirm) return; + + const res = await request({ + + url: STAFF_API.memberRemove, + + method: 'POST', + + data: { member_id: id }, + + }); + + wx.showToast({ title: res.data.msg || '完成', icon: 'none' }); + + this.loadList(); + + }, + + }); + + }, + + + + async onAssignRole(e) { + + const memberId = e.currentTarget.dataset.id; + + if (this.rejectSelf(memberId)) return; + + const rolesRes = await request({ url: STAFF_API.roleList, method: 'POST' }); + + const roles = (rolesRes.data && rolesRes.data.data && rolesRes.data.data.roles) || []; + + if (!roles.length) { + + wx.showToast({ title: '请先创建角色', icon: 'none' }); + + return; + + } + + wx.showActionSheet({ + + itemList: roles.map((r) => r.role_name), + + success: async (r) => { + + const role = roles[r.tapIndex]; + + const res = await request({ + + url: STAFF_API.memberUpdateRole, + + method: 'POST', + + data: { member_id: memberId, role_id: role.id }, + + }); + + const ok = res.data && res.data.code === 0; + + const roleName = (res.data && res.data.data && res.data.data.role_name) || role.role_name; + + wx.showToast({ + + title: ok ? `已设为${roleName}` : (res.data.msg || '失败'), + + icon: 'none', + + }); + + if (ok) this.loadList(); + + }, + + }); + + }, + + + + goRank() { + + wx.navigateTo({ url: '/pages/merchant-staff-audit/merchant-staff-audit?tab=rank' }); + + }, + + + + goAudit() { + + wx.navigateTo({ url: '/pages/merchant-staff-audit/merchant-staff-audit' }); + + }, + }); + diff --git a/pages/merchant-kefu-list/merchant-kefu-list.wxml b/pages/merchant-kefu-list/merchant-kefu-list.wxml index db7b934..a4684eb 100644 --- a/pages/merchant-kefu-list/merchant-kefu-list.wxml +++ b/pages/merchant-kefu-list/merchant-kefu-list.wxml @@ -1,47 +1,165 @@ + + + + 客服列表 + + + + - + + + + + + + {{item.nickname}} + + {{item.role_name || '客服'}} + 总管 + + 本人 + 财务 + + 已封禁 + + ID: {{item.uid}} 复制 + 备注:{{item.merchant_remark}} + + + + + + 剩余额度 + + ¥{{item.quotaAvailable}} + + + + - {{item.orderCount}} - 接单数 + + {{item.todayDispatchCount}} + + 今日派单 + + + + {{item.dispatchCount}} + + 累计派单 + + + + + {{item.settleCount}} - 结算数 + + 结单数 + + - ¥{{item.settleAmount}} - 结算金额 + + {{item.refundCount}} + + 退款数 + + + + + + + + ¥{{item.todayDispatchAmount}} + + 今日派单额 + + + + + + ¥{{item.dispatchAmount}} + + 派单金额 + + + + + + ¥{{item.settleAmount}} + + 结算金额 + + + + + + ¥{{item.refundAmount}} + + 退款金额 + + + + + - 查看订单 - 权限管理 - 删除 + 备注 + 查看订单 + + + + 划额度 + + 收回额度 + + 换角色 + + {{item.isDisabled ? '解封' : '封禁'}} + + 移除 + + + + 本人账号不可在此操作,如需调整请联系商家老板 + - 预览 · 接口就绪后对接真实数据 + + 暂无客服成员 + + 操作日志 › + + 客服排行 › + + + diff --git a/pages/merchant-kefu-list/merchant-kefu-list.wxss b/pages/merchant-kefu-list/merchant-kefu-list.wxss index effefea..12a8fce 100644 --- a/pages/merchant-kefu-list/merchant-kefu-list.wxss +++ b/pages/merchant-kefu-list/merchant-kefu-list.wxss @@ -31,6 +31,34 @@ box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06); } +.kefu-card.disabled-card { opacity: 0.72; } + +.badge-role { + font-size: 20rpx; + padding: 2rpx 10rpx; + background: #f0f0f0; + color: #666; + border-radius: 6rpx; + margin-left: 8rpx; +} + +.badge-off { + background: #ffebee; + color: #c62828; +} + +.quota-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12rpx 0 8rpx; + border-bottom: 1rpx solid #f0f0f0; + margin-bottom: 12rpx; +} + +.quota-label { font-size: 24rpx; color: #888; } +.quota-val { font-size: 30rpx; color: #C9A962; font-weight: 600; } + .kefu-av { width: 88rpx; height: 88rpx; @@ -55,6 +83,15 @@ .badge-chief { background: #ffb508; } .badge-finance { background: #1b6ef3; } +.badge-self { background: #9e9e9e; } + +.self-tip { + margin-top: 16rpx; + padding: 16rpx 0 4rpx; + font-size: 24rpx; + color: #999; + text-align: center; +} .kefu-uid { font-size: 24rpx; @@ -75,12 +112,23 @@ border-bottom: 1rpx solid #f5f5f5; } +.amount-stats { + margin-top: 0; + border-top: none; + padding-top: 0; +} + .stat-n { font-size: 28rpx; font-weight: 700; color: #e67e22; } +.stat-amt { + font-size: 24rpx; + color: #c0392b; +} + .stat-l { font-size: 22rpx; color: #999; diff --git a/pages/merchant-order-detail/merchant-order-detail.js b/pages/merchant-order-detail/merchant-order-detail.js index 25b5660..8c0afe0 100644 --- a/pages/merchant-order-detail/merchant-order-detail.js +++ b/pages/merchant-order-detail/merchant-order-detail.js @@ -1,835 +1,901 @@ -// pages/merchant-order-detail/merchant-order-detail.js - 商家订单详情(完整版,已添加罚款/退款操作及教程按钮) -const app = getApp() -import request from '../../utils/request.js' -import PopupService from '../../services/popupService.js' // 新增:导入弹窗服务 - -Page({ - data: { - jibenShuju: { - dingdan_id: '', tupian: '', jieshao: '', create_time: '', - jine: '', nicheng: '', beizhu: '', zhuangtai: 0, fadanpingtai: 0 - }, - dashouInfo: { yonghuid: '', nicheng: '', avatar: '' }, - xiangxiShuju: { - dashou_jiaofu: [], dashou_liuyan: '', tuikuan_liyou: '', shangjia_liuyan: '' - }, - fadanData: null, - fenhongLilv: 0, - xinDingdanId: '', - 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, - showChexiaoModal: false, chexiaoLiyou: '', isChexiaoLoading: false, - showTuikuanModal: false, tuikuanLiyou: '', isTuikuanLoading: false, - showFakuanModal: false, fakuanLiyou: '', fakuanJine: '', yingxiangQiangdan: true, isFakuanLoading: false, - showGhDashouModal: false, isGhDashouLoading: false, - showJiesuanConfirm: false, isJiesuanLoading: false, - jiesuanPingfen: 0, jiesuanLiuyan: '', - xingxingData: [ - { id: 1, active: false }, { id: 2, active: false }, { id: 3, active: false }, - { id: 4, active: false }, { id: 5, active: false } - ], - currentIMUserId: '', - - // ========== 新增:罚单/订单操作弹窗控制 ========== - showModifyFakuanModal: false, // 修改罚款弹窗 - modifyFakuanLiyou: '', // 修改后的罚款理由 - modifyFakuanJine: '', // 修改后的罚款金额 - showCancelFakuanModal: false, // 撤销罚款弹窗 - cancelFakuanLiyou: '', // 撤销驳回理由 - showResubmitFakuanModal: false, // 再次申请罚款弹窗 - resubmitFakuanLiyou: '', // 再次申请的理由 - resubmitFakuanJine: '', // 再次申请的金额 - showCancelTuikuanModal: false, // 撤销退款申请弹窗 - cancelTuikuanLiyou: '', // 撤销退款理由 - showModifyTuikuanModal: false, // 修改退款理由弹窗 - modifyTuikuanLiyou: '', // 修改后的退款理由 - // 教程按钮状态 - tutorialBtnHidden: false - }, - - onLoad(options) { - wx.setNavigationBarTitle({ title: '订单详情' }) - this.ensureIMConnection() - this.jiexiTiaozhuanCanshu(options) - // 新增:启动教程按钮定时器 - this.startTutorialBtnTimer() - }, - - onShow() { - this.registerNotificationComponent() - this.ensureIMConnection() - }, - - onHide() { - // 页面隐藏时重置弹窗服务状态,避免影响其他页面 - PopupService.reset() - }, - - onUnload() { - // 清除缩进定时器,避免内存泄漏 - if (this.tutorialTimer) { - clearTimeout(this.tutorialTimer) - } - // 页面卸载时重置弹窗服务 - PopupService.reset() - }, - - registerNotificationComponent() { - const notificationComp = this.selectComponent('#global-notification') - if (notificationComp && notificationComp.showNotification) { - app.globalData.globalNotification = { - show: (data) => notificationComp.showNotification(data), - hide: () => notificationComp.hideNotification() - } - } - }, - - ensureIMConnection() { - const uid = wx.getStorageSync('uid') - if (!uid) return - const targetUserId = 'Sj' + uid - if (wx.goEasy && wx.goEasy.getConnectionStatus) { - const status = wx.goEasy.getConnectionStatus() - if (status === 'connected' || status === 'reconnected') { - const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null - if (currentUserId === targetUserId) { - this.setData({ currentIMUserId: targetUserId }) - return - } else { - wx.goEasy.disconnect() - } - } - } - this.connectGoEasy(targetUserId) - }, - - connectGoEasy(userId) { - let currentUser = app.globalData.currentUser - if (!currentUser) { - const uid = wx.getStorageSync('uid') - currentUser = { - id: uid, - name: '用户' + (uid ? uid.substring(0, 6) : ''), - avatar: app.globalData.ossImageUrl + app.globalData.morentouxiang - } - } - wx.goEasy.connect({ - id: userId, - data: { - name: currentUser.name, - avatar: currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang) - }, - onSuccess: () => { - this.setData({ currentIMUserId: userId }) - try { - wx.setStorageSync('savedGoEasyConnection', JSON.stringify({ - userId, identityType: 'shangjia', lastConnectTime: Date.now() - })) - wx.setStorageSync('goEasyUserId', userId) - } catch (e) {} - }, - onFailed: (error) => { - if (error.code === 408) { - this.setData({ currentIMUserId: userId }) - } else { - console.error('IM连接失败', error) - } - } - }) - }, - - 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 - this.setData({ - 'jibenShuju.dingdan_id': dingdanData.dingdan_id || '', - 'jibenShuju.tupian': tupianUrl, - 'jibenShuju.jieshao': dingdanData.jieshao || '', - 'jibenShuju.create_time': dingdanData.create_time || '', - 'jibenShuju.jine': jine.toFixed(2), - '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 - try { - const res = await request({ - url: '/dingdan/sjddxq', - method: 'POST', - data: { dingdan_id: dingdanId }, - header: { 'content-type': 'application/json' } - }) - if (res && res.data.code === 0 && res.data.data) { - const d = res.data.data - let jiaofuList = d.dashou_jiaofu || [] - jiaofuList = jiaofuList.map(url => { - if (url && !url.startsWith('http')) { - const oss = app.globalData.ossImageUrl || '' - return oss + url - } - return url - }) - let dashouAvatar = d.dashou_avatar || '' - if (dashouAvatar && !dashouAvatar.startsWith('http')) { - dashouAvatar = (app.globalData.ossImageUrl || '') + dashouAvatar - } - this.setData({ - 'dashouInfo.yonghuid': d.dashou_yonghuid || '', - 'dashouInfo.nicheng': d.dashou_nicheng || '', - 'dashouInfo.avatar': dashouAvatar, - 'xiangxiShuju.dashou_jiaofu': jiaofuList, - 'xiangxiShuju.dashou_liuyan': d.dashou_liuyan || '', - 'xiangxiShuju.tuikuan_liyou': d.tuikuan_liyou || '', - 'xiangxiShuju.shangjia_liuyan': d.shangjia_liuyan || '', - fadanData: d.fadan || null, - fenhongLilv: parseFloat(d.fenhong_lilv) || 0, - xinDingdanId: d.xin_dingdan_id || '', - 'jibenShuju.zhuangtai': d.zhuangtai || this.data.jibenShuju.zhuangtai - }) - } else { - wx.showToast({ title: res?.data?.msg || '获取详情失败', icon: 'none' }) - } - } catch (err) { - console.error('加载详情失败', err) - wx.showToast({ title: '网络错误', icon: 'none' }) - } - }, - - fuzhiWenben(e) { - const text = e.currentTarget.dataset.text - if (!text) return - wx.setClipboardData({ data: text }) - wx.showToast({ title: '已复制', icon: 'success' }) - }, - - yulanTupian(e) { - const current = e.currentTarget.dataset.url - const urls = e.currentTarget.dataset.urls || [current] - wx.previewImage({ current, urls }) - }, - - 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' }) - } - }) - }, - - // ===== 联系打手(替打手订阅 + 发机器人消息 + 跳转) ===== - goToChatWithDashou() { - const uid = wx.getStorageSync('uid') - if (!uid) return - - const orderId = this.data.jibenShuju.dingdan_id - if (!orderId) { - wx.showToast({ title: '订单ID缺失', icon: 'none' }) - 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 - - // 第一步:商家自己订阅群组 - 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' }) - } - }) - }, - - // 帮打手订阅群组 + 发送机器人消息 - bangDashouDingyue(orderId, dashouUid) { - return new Promise((resolve, reject) => { - const dashouImId = 'Ds' + dashouUid - const appkey = 'BC-32e2991c69bc47e1b538e73aaed37fc1' // 你的GoEasy AppKey - const restHost = 'https://rest-hz.goeasy.io' - - // 1. 通过 REST API 为打手订阅群组 - wx.request({ - url: restHost + '/v2/im/subscribe-groups', - method: 'POST', - header: { 'content-type': 'application/json' }, - data: { - appkey: appkey, - userIds: [dashouImId], - groupIds: [orderId] - }, - success: (res) => { - if (res.data.code === 200) { - // 2. 订阅成功后,以打手身份发送一条系统消息 - wx.request({ - url: restHost + '/v2/im/message', - method: 'POST', - header: { 'content-type': 'application/json' }, - data: { - appkey: appkey, - senderId: dashouImId, - senderData: { name: '系统通知', avatar: '' }, - to: { - type: 'group', - id: orderId, - data: { name: '订单群', avatar: '' } - }, - type: 'text', - payload: '此消息为系统自动发送,接单员看到后会尽快回复您。' - }, - success: (msgRes) => { - if (msgRes.data.code === 200) { - resolve() - } else { - reject(new Error('发送消息失败')) - } - }, - fail: reject - }) - } else { - reject(new Error('订阅打手失败')) - } - }, - fail: reject - }) - }) - }, - - // 跳转群聊页面 - 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 }) }, - async tijiaoGhDashou() { - if (this.data.isGhDashouLoading) return - this.setData({ isGhDashouLoading: true }) - wx.showLoading({ title: '更换中...', mask: true }) - try { - const res = await request({ - url: '/dingdan/zxsjghds', - method: 'POST', - data: { dingdan_id: this.data.jibenShuju.dingdan_id }, - header: { 'content-type': 'application/json' } - }) - wx.hideLoading() - this.setData({ isGhDashouLoading: false, showGhDashouModal: false }) - if (res && res.data.code === 0) { - this.setData({ xinDingdanId: res.data.data?.xin_dingdan_id || '' }) - wx.showToast({ title: '更换成功', icon: 'success' }) - this.jiazaiXiangxiShuju(this.data.jibenShuju.dingdan_id) - } else { - wx.showToast({ title: res?.data?.msg || '更换失败', icon: 'none' }) - } - } catch (err) { - wx.hideLoading() - this.setData({ isGhDashouLoading: false, showGhDashouModal: false }) - wx.showToast({ title: '网络错误', icon: 'none' }) - } - }, - - showFakuanModal() { 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 }) }, - toggleQiangdan() { this.setData({ yingxiangQiangdan: !this.data.yingxiangQiangdan }) }, - jisuanFenhong() { - const jine = parseFloat(this.data.fakuanJine) - if (isNaN(jine) || jine <= 0) return '0.00' - return (jine * this.data.fenhongLilv).toFixed(2) - }, - async tijiaoFakuan() { - const { fakuanLiyou, fakuanJine, jibenShuju, dashouInfo } = this.data - if (!fakuanLiyou.trim()) { wx.showToast({ title: '请输入罚款原因', icon: 'none' }); return } - const jineNum = parseFloat(fakuanJine) - if (isNaN(jineNum) || jineNum <= 0) { wx.showToast({ title: '请输入正确的罚款金额', icon: 'none' }); return } - if (this.data.isFakuanLoading) return - this.setData({ isFakuanLoading: true }) - wx.showLoading({ title: '提交中...', mask: true }) - try { - const res = await request({ - url: '/dingdan/sjfksq', - method: 'POST', - data: { - dingdan_id: jibenShuju.dingdan_id, - chufa_liyou: fakuanLiyou, - fakuanjine: jineNum.toFixed(2), - yingxiang_qiangdan: this.data.yingxiangQiangdan ? 1 : 0, - dashou_id: dashouInfo.yonghuid - }, - header: { 'content-type': 'application/json' } - }) - wx.hideLoading() - this.setData({ isFakuanLoading: false }) - if (res && res.data.code === 0) { - wx.showToast({ title: '罚款申请已提交', icon: 'success' }) - this.setData({ showFakuanModal: false }) - this.jiazaiXiangxiShuju(jibenShuju.dingdan_id) - } else { - wx.showToast({ title: res?.data?.msg || '提交失败', icon: 'none' }) - } - } catch (err) { - wx.hideLoading() - this.setData({ isFakuanLoading: false }) - wx.showToast({ title: '网络错误', icon: 'none' }) - } - }, - - showChexiaoBtn() { this.setData({ showChexiaoModal: true, chexiaoLiyou: '' }) }, - closeChexiaoModal() { this.setData({ showChexiaoModal: false }) }, - shuruChexiaoLiyou(e) { this.setData({ chexiaoLiyou: e.detail.value.slice(0, 50) }) }, - async tijiaoChexiao() { - if (this.data.isChexiaoLoading) return - const { chexiaoLiyou, jibenShuju } = this.data - if (!chexiaoLiyou.trim()) { wx.showToast({ title: '请输入撤销原因', icon: 'none' }); return } - this.setData({ isChexiaoLoading: true }) - wx.showLoading({ title: '撤销中...', mask: true }) - try { - const res = await request({ - url: '/dingdan/sjchexiao', - method: 'POST', - data: { dingdan_id: jibenShuju.dingdan_id, chexiao_liyou: chexiaoLiyou }, - header: { 'content-type': 'application/json' } - }) - wx.hideLoading() - this.setData({ isChexiaoLoading: false }) - if (res && res.data.code === 0) { - this.setData({ 'jibenShuju.zhuangtai': 5, showChexiaoModal: false }) - wx.showToast({ title: '撤销成功', icon: 'success' }) - } else { - wx.showToast({ title: res?.data?.msg || '撤销失败', icon: 'none' }) - } - } catch (err) { - wx.hideLoading() - this.setData({ isChexiaoLoading: false }) - wx.showToast({ title: '网络错误', icon: 'none' }) - } - }, - - showTuikuanBtn() { this.setData({ showTuikuanModal: true, tuikuanLiyou: '' }) }, - closeTuikuanModal() { this.setData({ showTuikuanModal: false }) }, - shuruTuikuanLiyou(e) { this.setData({ tuikuanLiyou: e.detail.value.slice(0, 50) }) }, - async tijiaoTuikuan() { - if (this.data.isTuikuanLoading) return - const { tuikuanLiyou, jibenShuju } = this.data - if (!tuikuanLiyou.trim()) { wx.showToast({ title: '请输入退款原因', icon: 'none' }); return } - this.setData({ isTuikuanLoading: true }) - wx.showLoading({ title: '提交中...', mask: true }) - try { - const res = await request({ - url: '/dingdan/sjtkshenqing', - method: 'POST', - data: { dingdan_id: jibenShuju.dingdan_id, tuikuan_liyou: tuikuanLiyou }, - header: { 'content-type': 'application/json' } - }) - wx.hideLoading() - this.setData({ isTuikuanLoading: false }) - if (res && res.data.code === 0) { - this.setData({ 'jibenShuju.zhuangtai': 4, showTuikuanModal: false }) - wx.showToast({ title: '退款申请已提交', icon: 'success' }) - } else { - wx.showToast({ title: res?.data?.msg || '失败', icon: 'none' }) - } - } catch (err) { - wx.hideLoading() - this.setData({ isTuikuanLoading: false }) - wx.showToast({ title: '网络错误', icon: 'none' }) - } - }, - - dianjiXingxing(e) { - const pf = e.currentTarget.dataset.index - const newStars = this.data.xingxingData.map((item, idx) => ({ ...item, active: idx < pf })) - this.setData({ xingxingData: newStars, jiesuanPingfen: pf }) - }, - shuruLiuyan(e) { this.setData({ jiesuanLiuyan: e.detail.value.slice(0, 50) }) }, - showJiesuanConfirm() { this.setData({ showJiesuanConfirm: true }) }, - closeJiesuanConfirm() { this.setData({ showJiesuanConfirm: false }) }, - async tijiaoJiesuan() { - if (this.data.isJiesuanLoading) return - this.setData({ isJiesuanLoading: true }) - wx.showLoading({ title: '结算中...', mask: true }) - try { - const res = await request({ - url: '/dingdan/sjjiesuan', - method: 'POST', - data: { - dingdan_id: this.data.jibenShuju.dingdan_id, - pingfen: this.data.jiesuanPingfen, - liuyan: this.data.jiesuanLiuyan - }, - header: { 'content-type': 'application/json' } - }) - wx.hideLoading() - this.setData({ isJiesuanLoading: false, showJiesuanConfirm: false }) - if (res && res.data.code === 0) { - this.setData({ 'jibenShuju.zhuangtai': 3 }) - wx.showToast({ title: '结算成功', icon: 'success' }) - } else { - wx.showToast({ title: res?.data?.msg || '结算失败', icon: 'none' }) - } - } catch (err) { - wx.hideLoading() - this.setData({ isJiesuanLoading: false }) - wx.showToast({ title: '网络错误', icon: 'none' }) - } - }, - - // ========== 新增:罚款操作(修改/撤销/再次申请) ========== - // 显示修改罚款弹窗(仅当状态为待缴纳 或 申诉中 时可用) - showModifyFakuan() { - const status = this.data.fadanData?.zhuangtai; - if (status !== 1 && status !== 3) { - wx.showToast({ title: '当前状态不可修改罚款', icon: 'none' }); - return; - } - this.setData({ - showModifyFakuanModal: true, - modifyFakuanLiyou: this.data.fadanData?.chufaliyou || '', - modifyFakuanJine: this.data.fadanData?.fakuanjine || '' - }); - }, - closeModifyFakuanModal() { - this.setData({ showModifyFakuanModal: false }); - }, - inputModifyFakuanLiyou(e) { - this.setData({ modifyFakuanLiyou: e.detail.value.slice(0, 100) }); - }, - inputModifyFakuanJine(e) { - this.setData({ modifyFakuanJine: e.detail.value }); - }, - async submitModifyFakuan() { - const { modifyFakuanLiyou, modifyFakuanJine, fadanData, jibenShuju } = this.data; - if (!modifyFakuanLiyou.trim()) { - wx.showToast({ title: '请输入罚款理由', icon: 'none' }); return; - } - const jine = parseFloat(modifyFakuanJine); - if (isNaN(jine) || jine <= 0) { - wx.showToast({ title: '请输入正确的罚款金额', icon: 'none' }); return; - } - wx.showLoading({ title: '提交中...', mask: true }); - try { - const res = await request({ - url: '/dingdan/sjfkxgycx', - method: 'POST', - data: { - operation: 'modify_penalty', - fadan_id: fadanData.id, - dingdan_id: jibenShuju.dingdan_id, - liyou: modifyFakuanLiyou, - jine: jine.toFixed(2) - } - }); - wx.hideLoading(); - if (res && res.data.code === 0) { - wx.showToast({ title: '修改成功', icon: 'success' }); - this.setData({ showModifyFakuanModal: false }); - this.jiazaiXiangxiShuju(jibenShuju.dingdan_id); // 刷新数据 - } else { - wx.showToast({ title: res?.data?.msg || '修改失败', icon: 'none' }); - } - } catch (err) { - wx.hideLoading(); - wx.showToast({ title: '网络错误', icon: 'none' }); - } - }, - - // 显示撤销罚款弹窗(待缴纳/申诉中 可撤销,变为已驳回) - showCancelFakuan() { - const status = this.data.fadanData?.zhuangtai; - if (status !== 1 && status !== 3) { - wx.showToast({ title: '当前状态不可撤销罚款', icon: 'none' }); - return; - } - this.setData({ showCancelFakuanModal: true, cancelFakuanLiyou: '' }); - }, - closeCancelFakuanModal() { - this.setData({ showCancelFakuanModal: false }); - }, - inputCancelFakuanLiyou(e) { - this.setData({ cancelFakuanLiyou: e.detail.value.slice(0, 100) }); - }, - async submitCancelFakuan() { - const { cancelFakuanLiyou, fadanData, jibenShuju } = this.data; - if (!cancelFakuanLiyou.trim()) { - wx.showToast({ title: '请输入驳回理由', icon: 'none' }); return; - } - wx.showLoading({ title: '提交中...', mask: true }); - try { - const res = await request({ - url: '/dingdan/sjfkxgycx', - method: 'POST', - data: { - operation: 'cancel_penalty', - fadan_id: fadanData.id, - dingdan_id: jibenShuju.dingdan_id, - liyou: cancelFakuanLiyou - } - }); - wx.hideLoading(); - if (res && res.data.code === 0) { - wx.showToast({ title: '已撤销', icon: 'success' }); - this.setData({ showCancelFakuanModal: false }); - this.jiazaiXiangxiShuju(jibenShuju.dingdan_id); - } else { - wx.showToast({ title: res?.data?.msg || '撤销失败', icon: 'none' }); - } - } catch (err) { - wx.hideLoading(); - wx.showToast({ title: '网络错误', icon: 'none' }); - } - }, - - // 显示再次申请罚款弹窗(仅当状态为已驳回时可用) - showResubmitFakuan() { - if (this.data.fadanData?.zhuangtai !== 4) { - wx.showToast({ title: '当前状态不可再次申请', icon: 'none' }); - return; - } - this.setData({ - showResubmitFakuanModal: true, - resubmitFakuanLiyou: '', - resubmitFakuanJine: '' - }); - }, - closeResubmitFakuanModal() { - this.setData({ showResubmitFakuanModal: false }); - }, - inputResubmitFakuanLiyou(e) { - this.setData({ resubmitFakuanLiyou: e.detail.value.slice(0, 100) }); - }, - inputResubmitFakuanJine(e) { - this.setData({ resubmitFakuanJine: e.detail.value }); - }, - async submitResubmitFakuan() { - const { resubmitFakuanLiyou, resubmitFakuanJine, fadanData, jibenShuju } = this.data; - if (!resubmitFakuanLiyou.trim()) { - wx.showToast({ title: '请输入罚款理由', icon: 'none' }); return; - } - const jine = parseFloat(resubmitFakuanJine); - if (isNaN(jine) || jine <= 0) { - wx.showToast({ title: '请输入正确的罚款金额', icon: 'none' }); return; - } - wx.showLoading({ title: '提交中...', mask: true }); - try { - const res = await request({ - url: '/dingdan/sjfkxgycx', - method: 'POST', - data: { - operation: 'resubmit_penalty', - fadan_id: fadanData.id, - dingdan_id: jibenShuju.dingdan_id, - liyou: resubmitFakuanLiyou, - jine: jine.toFixed(2) - } - }); - wx.hideLoading(); - if (res && res.data.code === 0) { - wx.showToast({ title: '申请成功', icon: 'success' }); - this.setData({ showResubmitFakuanModal: false }); - this.jiazaiXiangxiShuju(jibenShuju.dingdan_id); - } else { - wx.showToast({ title: res?.data?.msg || '申请失败', icon: 'none' }); - } - } catch (err) { - wx.hideLoading(); - wx.showToast({ title: '网络错误', icon: 'none' }); - } - }, - - // ========== 新增:订单退款操作(撤销/修改理由) ========== - showCancelTuikuan() { - if (this.data.jibenShuju.zhuangtai !== 4) { - wx.showToast({ title: '只有退款中的订单可撤销', icon: 'none' }); - return; - } - this.setData({ showCancelTuikuanModal: true, cancelTuikuanLiyou: '' }); - }, - closeCancelTuikuanModal() { - this.setData({ showCancelTuikuanModal: false }); - }, - inputCancelTuikuanLiyou(e) { - this.setData({ cancelTuikuanLiyou: e.detail.value.slice(0, 100) }); - }, - async submitCancelTuikuan() { - const { cancelTuikuanLiyou, jibenShuju } = this.data; - if (!cancelTuikuanLiyou.trim()) { - wx.showToast({ title: '请输入撤销理由', icon: 'none' }); return; - } - wx.showLoading({ title: '撤销中...', mask: true }); - try { - const res = await request({ - url: '/dingdan/sjfkxgycx', - method: 'POST', - data: { - operation: 'cancel_refund', - dingdan_id: jibenShuju.dingdan_id, - liyou: cancelTuikuanLiyou - } - }); - wx.hideLoading(); - if (res && res.data.code === 0) { - wx.showToast({ title: '已撤销退款申请', icon: 'success' }); - this.setData({ showCancelTuikuanModal: false }); - this.jiazaiXiangxiShuju(jibenShuju.dingdan_id); - } else { - wx.showToast({ title: res?.data?.msg || '撤销失败', icon: 'none' }); - } - } catch (err) { - wx.hideLoading(); - wx.showToast({ title: '网络错误', icon: 'none' }); - } - }, - - showModifyTuikuan() { - if (this.data.jibenShuju.zhuangtai !== 4) { - wx.showToast({ title: '只有退款中的订单可修改理由', icon: 'none' }); - return; - } - this.setData({ - showModifyTuikuanModal: true, - modifyTuikuanLiyou: this.data.xiangxiShuju.tuikuan_liyou || '' - }); - }, - closeModifyTuikuanModal() { - this.setData({ showModifyTuikuanModal: false }); - }, - inputModifyTuikuanLiyou(e) { - this.setData({ modifyTuikuanLiyou: e.detail.value.slice(0, 100) }); - }, - async submitModifyTuikuan() { - const { modifyTuikuanLiyou, jibenShuju } = this.data; - if (!modifyTuikuanLiyou.trim()) { - wx.showToast({ title: '请输入退款理由', icon: 'none' }); return; - } - wx.showLoading({ title: '修改中...', mask: true }); - try { - const res = await request({ - url: '/dingdan/sjfkxgycx', - method: 'POST', - data: { - operation: 'modify_refund_reason', - dingdan_id: jibenShuju.dingdan_id, - liyou: modifyTuikuanLiyou - } - }); - wx.hideLoading(); - if (res && res.data.code === 0) { - wx.showToast({ title: '修改成功', icon: 'success' }); - this.setData({ showModifyTuikuanModal: false }); - this.jiazaiXiangxiShuju(jibenShuju.dingdan_id); - } else { - wx.showToast({ title: res?.data?.msg || '修改失败', icon: 'none' }); - } - } catch (err) { - wx.hideLoading(); - wx.showToast({ title: '网络错误', icon: 'none' }); - } - }, - - // ========== 新增:教程按钮 ========== - startTutorialBtnTimer() { - if (this.tutorialTimer) clearTimeout(this.tutorialTimer); - this.tutorialTimer = setTimeout(() => { - this.setData({ tutorialBtnHidden: true }); - }, 5000); - }, - resetTutorialBtn() { - if (this.data.tutorialBtnHidden) { - this.setData({ tutorialBtnHidden: false }); - } - if (this.tutorialTimer) clearTimeout(this.tutorialTimer); - this.tutorialTimer = setTimeout(() => { - this.setData({ tutorialBtnHidden: true }); - }, 5000); - }, - onTutorialBtnTap() { - this.resetTutorialBtn(); - PopupService.checkAndShow(this, 'sjddxq1'); - } +// pages/merchant-order-detail/merchant-order-detail.js - 商家订单详情(完整版,已添加罚款/退款操作及教程按钮) +const app = getApp() +import request from '../../utils/request.js' +import PopupService from '../../services/popupService.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: '', waibu_dingdan_id: '', zhuangtai: 0, fadanpingtai: 0 + }, + dashouInfo: { yonghuid: '', nicheng: '', avatar: '' }, + dispatchStaff: null, + dispatchStaffAvatar: '', + xiangxiShuju: { + dashou_jiaofu: [], dashou_liuyan: '', tuikuan_liyou: '', shangjia_liuyan: '' + }, + fadanData: null, + fenhongLilv: 0, + xinDingdanId: '', + 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, + showChexiaoModal: false, chexiaoLiyou: '', isChexiaoLoading: false, + showTuikuanModal: false, tuikuanLiyou: '', isTuikuanLoading: false, + showFakuanModal: false, fakuanLiyou: '', fakuanJine: '', yingxiangQiangdan: true, isFakuanLoading: false, + showGhDashouModal: false, isGhDashouLoading: false, + showJiesuanConfirm: false, isJiesuanLoading: false, + jiesuanPingfen: 0, jiesuanLiuyan: '', + xingxingData: [ + { id: 1, active: false }, { id: 2, active: false }, { id: 3, active: false }, + { id: 4, active: false }, { id: 5, active: false } + ], + currentIMUserId: '', + + // ========== 新增:罚单/订单操作弹窗控制 ========== + showModifyFakuanModal: false, // 修改罚款弹窗 + modifyFakuanLiyou: '', // 修改后的罚款理由 + modifyFakuanJine: '', // 修改后的罚款金额 + showCancelFakuanModal: false, // 撤销罚款弹窗 + cancelFakuanLiyou: '', // 撤销驳回理由 + showResubmitFakuanModal: false, // 再次申请罚款弹窗 + resubmitFakuanLiyou: '', // 再次申请的理由 + resubmitFakuanJine: '', // 再次申请的金额 + showCancelTuikuanModal: false, // 撤销退款申请弹窗 + cancelTuikuanLiyou: '', // 撤销退款理由 + showModifyTuikuanModal: false, // 修改退款理由弹窗 + modifyTuikuanLiyou: '', // 修改后的退款理由 + // 教程按钮状态 + tutorialBtnHidden: false, + canCancel: true, + canRefund: true, + 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) { + wx.setNavigationBarTitle({ title: '订单详情' }) + syncStaffUi(this) + this.ensureIMConnection() + this.jiexiTiaozhuanCanshu(options) + // 新增:启动教程按钮定时器 + this.startTutorialBtnTimer() + }, + + onShow() { + this.registerNotificationComponent() + syncStaffUi(this) + this.refreshPenaltyApplyBtn() + this.ensureIMConnection() + }, + + onHide() { + // 页面隐藏时重置弹窗服务状态,避免影响其他页面 + PopupService.reset() + }, + + onUnload() { + // 清除缩进定时器,避免内存泄漏 + if (this.tutorialTimer) { + clearTimeout(this.tutorialTimer) + } + // 页面卸载时重置弹窗服务 + PopupService.reset() + }, + + registerNotificationComponent() { + const notificationComp = this.selectComponent('#global-notification') + if (notificationComp && notificationComp.showNotification) { + app.globalData.globalNotification = { + show: (data) => notificationComp.showNotification(data), + hide: () => notificationComp.hideNotification() + } + } + }, + + ensureIMConnection() { + const uid = wx.getStorageSync('uid') + if (!uid) return + const targetUserId = 'Sj' + uid + if (wx.goEasy && wx.goEasy.getConnectionStatus) { + const status = wx.goEasy.getConnectionStatus() + if (status === 'connected' || status === 'reconnected') { + const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null + if (currentUserId === targetUserId) { + this.setData({ currentIMUserId: targetUserId }) + return + } else { + wx.goEasy.disconnect() + } + } + } + this.connectGoEasy(targetUserId) + }, + + connectGoEasy(userId) { + let currentUser = app.globalData.currentUser + if (!currentUser) { + const uid = wx.getStorageSync('uid') + currentUser = { + id: uid, + name: '用户' + (uid ? uid.substring(0, 6) : ''), + avatar: app.globalData.ossImageUrl + app.globalData.morentouxiang + } + } + const staffCtx = isStaffMode() ? getStaffContext() : null + let imName = currentUser.name || wx.getStorageSync('nicheng') || '商家' + if (staffCtx) { + const roleLabel = staffCtx.role_name || '客服' + const nick = staffCtx.display_name || wx.getStorageSync('nicheng') || '' + imName = nick ? `${nick}(${roleLabel})` : roleLabel + } + wx.goEasy.connect({ + id: userId, + data: { + name: imName, + avatar: currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang) + }, + onSuccess: () => { + this.setData({ currentIMUserId: userId }) + try { + wx.setStorageSync('savedGoEasyConnection', JSON.stringify({ + userId, identityType: 'shangjia', lastConnectTime: Date.now() + })) + wx.setStorageSync('goEasyUserId', userId) + } catch (e) {} + }, + onFailed: (error) => { + if (error.code === 408) { + this.setData({ currentIMUserId: userId }) + } else { + console.error('IM连接失败', error) + } + } + }) + }, + + jiexiTiaozhuanCanshu(options) { + const directId = options.dingdan_id || options.dingdanId || '' + if (directId) { + this.setData({ + 'jibenShuju.dingdan_id': directId, + isLoading: false, + }) + this.jiazaiXiangxiShuju(directId) + return + } + 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 + this.setData({ + 'jibenShuju.dingdan_id': dingdanData.dingdan_id || '', + 'jibenShuju.tupian': tupianUrl, + 'jibenShuju.jieshao': dingdanData.jieshao || '', + 'jibenShuju.create_time': dingdanData.create_time || '', + 'jibenShuju.jine': jine.toFixed(2), + '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 + try { + const res = await request({ + url: isStaffMode() ? STAFF_API.orderDetail : '/dingdan/sjddxq', + method: 'POST', + data: { dingdan_id: dingdanId }, + header: { 'content-type': 'application/json' } + }) + if (res && res.data.code === 0 && res.data.data) { + const d = res.data.data + let jiaofuList = d.dashou_jiaofu || [] + jiaofuList = jiaofuList.map(url => { + if (url && !url.startsWith('http')) { + const oss = app.globalData.ossImageUrl || '' + return oss + url + } + return url + }) + let dashouAvatar = d.dashou_avatar || '' + if (dashouAvatar && !dashouAvatar.startsWith('http')) { + dashouAvatar = (app.globalData.ossImageUrl || '') + dashouAvatar + } + let dispatchStaffAvatar = (d.dispatch_staff && d.dispatch_staff.dispatch_staff_avatar) || '' + if (dispatchStaffAvatar && !dispatchStaffAvatar.startsWith('http')) { + const oss = app.globalData.ossImageUrl || '' + dispatchStaffAvatar = oss + (dispatchStaffAvatar.startsWith('/') ? dispatchStaffAvatar.slice(1) : dispatchStaffAvatar) + } + this.setData({ + 'dashouInfo.yonghuid': d.dashou_yonghuid || '', + 'dashouInfo.nicheng': d.dashou_nicheng || '', + 'dashouInfo.avatar': dashouAvatar, + dispatchStaff: d.dispatch_staff || null, + dispatchStaffAvatar: dispatchStaffAvatar || '/images/default-avatar.png', + 'xiangxiShuju.dashou_jiaofu': jiaofuList, + 'xiangxiShuju.dashou_liuyan': d.dashou_liuyan || '', + 'xiangxiShuju.tuikuan_liyou': d.tuikuan_liyou || '', + 'xiangxiShuju.shangjia_liuyan': d.shangjia_liuyan || '', + fadanData: d.fadan || null, + fenhongLilv: parseFloat(d.fenhong_lilv) || 0, + xinDingdanId: d.xin_dingdan_id || '', + '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' }) + } + } catch (err) { + console.error('加载详情失败', err) + wx.showToast({ title: '网络错误', icon: 'none' }) + } + }, + + fuzhiWenben(e) { + const text = e.currentTarget.dataset.text + if (!text) return + wx.setClipboardData({ data: text }) + wx.showToast({ title: '已复制', icon: 'success' }) + }, + + yulanTupian(e) { + const current = e.currentTarget.dataset.url + const urls = e.currentTarget.dataset.urls || [current] + wx.previewImage({ current, urls }) + }, + + 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' }) + } + }) + }, + + // ===== 联系打手(替打手订阅 + 发机器人消息 + 跳转) ===== + goToChatWithDashou() { + if (isStaffMode() && !staffCan('imChat')) { + wx.showToast({ title: '当前角色无订单聊天权限', icon: 'none' }) + return + } + const uid = wx.getStorageSync('uid') + if (!uid) return + + const orderId = this.data.jibenShuju.dingdan_id + if (!orderId) { + wx.showToast({ title: '订单ID缺失', icon: 'none' }) + 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 + + // 第一步:商家自己订阅群组 + 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' }) + } + }) + }, + + // 帮打手订阅群组 + 发送机器人消息 + bangDashouDingyue(orderId, dashouUid) { + return new Promise((resolve, reject) => { + const dashouImId = 'Ds' + dashouUid + const appkey = 'BC-32e2991c69bc47e1b538e73aaed37fc1' // 你的GoEasy AppKey + const restHost = 'https://rest-hz.goeasy.io' + + // 1. 通过 REST API 为打手订阅群组 + wx.request({ + url: restHost + '/v2/im/subscribe-groups', + method: 'POST', + header: { 'content-type': 'application/json' }, + data: { + appkey: appkey, + userIds: [dashouImId], + groupIds: [orderId] + }, + success: (res) => { + if (res.data.code === 200) { + // 2. 订阅成功后,以打手身份发送一条系统消息 + wx.request({ + url: restHost + '/v2/im/message', + method: 'POST', + header: { 'content-type': 'application/json' }, + data: { + appkey: appkey, + senderId: dashouImId, + senderData: { name: '系统通知', avatar: '' }, + to: { + type: 'group', + id: orderId, + data: { name: '订单群', avatar: '' } + }, + type: 'text', + payload: '此消息为系统自动发送,接单员看到后会尽快回复您。' + }, + success: (msgRes) => { + if (msgRes.data.code === 200) { + resolve() + } else { + reject(new Error('发送消息失败')) + } + }, + fail: reject + }) + } else { + reject(new Error('订阅打手失败')) + } + }, + fail: reject + }) + }) + }, + + // 跳转群聊页面 + 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 }) }, + async tijiaoGhDashou() { + if (this.data.isGhDashouLoading) return + this.setData({ isGhDashouLoading: true }) + wx.showLoading({ title: '更换中...', mask: true }) + try { + const res = await request({ + url: isStaffMode() ? STAFF_API.changePlayer : '/dingdan/zxsjghds', + method: 'POST', + data: { dingdan_id: this.data.jibenShuju.dingdan_id }, + header: { 'content-type': 'application/json' } + }) + wx.hideLoading() + this.setData({ isGhDashouLoading: false, showGhDashouModal: false }) + if (res && res.data.code === 0) { + this.setData({ xinDingdanId: res.data.data?.xin_dingdan_id || '' }) + wx.showToast({ title: '更换成功', icon: 'success' }) + this.jiazaiXiangxiShuju(this.data.jibenShuju.dingdan_id) + } else { + wx.showToast({ title: res?.data?.msg || '更换失败', icon: 'none' }) + } + } catch (err) { + wx.hideLoading() + this.setData({ isGhDashouLoading: false, showGhDashouModal: false }) + wx.showToast({ title: '网络错误', icon: 'none' }) + } + }, + + showFakuanModal() { 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 }) }, + toggleQiangdan() { this.setData({ yingxiangQiangdan: !this.data.yingxiangQiangdan }) }, + jisuanFenhong() { + const jine = parseFloat(this.data.fakuanJine) + if (isNaN(jine) || jine <= 0) return '0.00' + return (jine * this.data.fenhongLilv).toFixed(2) + }, + async tijiaoFakuan() { + const { fakuanLiyou, fakuanJine, jibenShuju, dashouInfo } = this.data + if (!fakuanLiyou.trim()) { wx.showToast({ title: '请输入罚款原因', icon: 'none' }); return } + const jineNum = parseFloat(fakuanJine) + if (isNaN(jineNum) || jineNum <= 0) { wx.showToast({ title: '请输入正确的罚款金额', icon: 'none' }); return } + if (this.data.isFakuanLoading) return + this.setData({ isFakuanLoading: true }) + wx.showLoading({ title: '提交中...', mask: true }) + try { + const res = await request({ + url: isStaffMode() ? STAFF_API.penaltyApply : '/dingdan/sjfksq', + method: 'POST', + data: { + dingdan_id: jibenShuju.dingdan_id, + chufa_liyou: fakuanLiyou, + fakuanjine: jineNum.toFixed(2), + yingxiang_qiangdan: this.data.yingxiangQiangdan ? 1 : 0, + dashou_id: dashouInfo.yonghuid + }, + header: { 'content-type': 'application/json' } + }) + wx.hideLoading() + this.setData({ isFakuanLoading: false }) + if (res && res.data.code === 0) { + wx.showToast({ title: '罚款申请已提交', icon: 'success' }) + this.setData({ showFakuanModal: false }) + this.jiazaiXiangxiShuju(jibenShuju.dingdan_id) + } else { + wx.showToast({ title: res?.data?.msg || '提交失败', icon: 'none' }) + } + } catch (err) { + wx.hideLoading() + this.setData({ isFakuanLoading: false }) + wx.showToast({ title: '网络错误', icon: 'none' }) + } + }, + + showChexiaoBtn() { this.setData({ showChexiaoModal: true, chexiaoLiyou: '' }) }, + closeChexiaoModal() { this.setData({ showChexiaoModal: false }) }, + shuruChexiaoLiyou(e) { this.setData({ chexiaoLiyou: e.detail.value.slice(0, 50) }) }, + async tijiaoChexiao() { + if (this.data.isChexiaoLoading) return + const { chexiaoLiyou, jibenShuju } = this.data + if (!chexiaoLiyou.trim()) { wx.showToast({ title: '请输入撤销原因', icon: 'none' }); return } + this.setData({ isChexiaoLoading: true }) + wx.showLoading({ title: '撤销中...', mask: true }) + try { + const res = await request({ + url: isStaffMode() ? STAFF_API.orderCancel : '/dingdan/sjchexiao', + method: 'POST', + data: { dingdan_id: jibenShuju.dingdan_id, chexiao_liyou: chexiaoLiyou }, + header: { 'content-type': 'application/json' } + }) + wx.hideLoading() + 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' }) + } + } catch (err) { + wx.hideLoading() + this.setData({ isChexiaoLoading: false }) + wx.showToast({ title: '网络错误', icon: 'none' }) + } + }, + + showTuikuanBtn() { this.setData({ showTuikuanModal: true, tuikuanLiyou: '' }) }, + closeTuikuanModal() { this.setData({ showTuikuanModal: false }) }, + shuruTuikuanLiyou(e) { this.setData({ tuikuanLiyou: e.detail.value.slice(0, 50) }) }, + async tijiaoTuikuan() { + if (this.data.isTuikuanLoading) return + const { tuikuanLiyou, jibenShuju } = this.data + if (!tuikuanLiyou.trim()) { wx.showToast({ title: '请输入退款原因', icon: 'none' }); return } + this.setData({ isTuikuanLoading: true }) + wx.showLoading({ title: '提交中...', mask: true }) + try { + const res = await request({ + url: isStaffMode() ? STAFF_API.orderRefund : '/dingdan/sjtkshenqing', + method: 'POST', + data: { dingdan_id: jibenShuju.dingdan_id, tuikuan_liyou: tuikuanLiyou }, + header: { 'content-type': 'application/json' } + }) + wx.hideLoading() + 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' }) + } + } catch (err) { + wx.hideLoading() + this.setData({ isTuikuanLoading: false }) + wx.showToast({ title: '网络错误', icon: 'none' }) + } + }, + + dianjiXingxing(e) { + const pf = e.currentTarget.dataset.index + const newStars = this.data.xingxingData.map((item, idx) => ({ ...item, active: idx < pf })) + this.setData({ xingxingData: newStars, jiesuanPingfen: pf }) + }, + shuruLiuyan(e) { this.setData({ jiesuanLiuyan: e.detail.value.slice(0, 50) }) }, + showJiesuanConfirm() { this.setData({ showJiesuanConfirm: true }) }, + closeJiesuanConfirm() { this.setData({ showJiesuanConfirm: false }) }, + async tijiaoJiesuan() { + if (this.data.isJiesuanLoading) return + this.setData({ isJiesuanLoading: true }) + wx.showLoading({ title: '结算中...', mask: true }) + try { + const res = await request({ + url: isStaffMode() ? STAFF_API.orderSettle : '/dingdan/sjjiesuan', + method: 'POST', + data: { + dingdan_id: this.data.jibenShuju.dingdan_id, + pingfen: this.data.jiesuanPingfen, + liuyan: this.data.jiesuanLiuyan + }, + header: { 'content-type': 'application/json' } + }) + wx.hideLoading() + 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' }) + } + } catch (err) { + wx.hideLoading() + this.setData({ isJiesuanLoading: false }) + wx.showToast({ title: '网络错误', icon: 'none' }) + } + }, + + // ========== 新增:罚款操作(修改/撤销/再次申请) ========== + // 显示修改罚款弹窗(仅当状态为待缴纳 或 申诉中 时可用) + showModifyFakuan() { + const status = this.data.fadanData?.zhuangtai; + if (status !== 1 && status !== 3) { + wx.showToast({ title: '当前状态不可修改罚款', icon: 'none' }); + return; + } + this.setData({ + showModifyFakuanModal: true, + modifyFakuanLiyou: this.data.fadanData?.chufaliyou || '', + modifyFakuanJine: this.data.fadanData?.fakuanjine || '' + }); + }, + closeModifyFakuanModal() { + this.setData({ showModifyFakuanModal: false }); + }, + inputModifyFakuanLiyou(e) { + this.setData({ modifyFakuanLiyou: e.detail.value.slice(0, 100) }); + }, + inputModifyFakuanJine(e) { + this.setData({ modifyFakuanJine: e.detail.value }); + }, + async submitModifyFakuan() { + const { modifyFakuanLiyou, modifyFakuanJine, fadanData, jibenShuju } = this.data; + if (!modifyFakuanLiyou.trim()) { + wx.showToast({ title: '请输入罚款理由', icon: 'none' }); return; + } + const jine = parseFloat(modifyFakuanJine); + if (isNaN(jine) || jine <= 0) { + wx.showToast({ title: '请输入正确的罚款金额', icon: 'none' }); return; + } + wx.showLoading({ title: '提交中...', mask: true }); + try { + const res = await request({ + url: isStaffMode() ? STAFF_API.penaltyManage : '/dingdan/sjfkxgycx', + method: 'POST', + data: { + operation: 'modify_penalty', + fadan_id: fadanData.id, + dingdan_id: jibenShuju.dingdan_id, + liyou: modifyFakuanLiyou, + jine: jine.toFixed(2) + } + }); + wx.hideLoading(); + if (res && res.data.code === 0) { + wx.showToast({ title: '修改成功', icon: 'success' }); + this.setData({ showModifyFakuanModal: false }); + this.jiazaiXiangxiShuju(jibenShuju.dingdan_id); // 刷新数据 + } else { + wx.showToast({ title: res?.data?.msg || '修改失败', icon: 'none' }); + } + } catch (err) { + wx.hideLoading(); + wx.showToast({ title: '网络错误', icon: 'none' }); + } + }, + + // 显示撤销罚款弹窗(待缴纳/申诉中 可撤销,变为已驳回) + showCancelFakuan() { + const status = this.data.fadanData?.zhuangtai; + if (status !== 1 && status !== 3) { + wx.showToast({ title: '当前状态不可撤销罚款', icon: 'none' }); + return; + } + this.setData({ showCancelFakuanModal: true, cancelFakuanLiyou: '' }); + }, + closeCancelFakuanModal() { + this.setData({ showCancelFakuanModal: false }); + }, + inputCancelFakuanLiyou(e) { + this.setData({ cancelFakuanLiyou: e.detail.value.slice(0, 100) }); + }, + async submitCancelFakuan() { + const { cancelFakuanLiyou, fadanData, jibenShuju } = this.data; + if (!cancelFakuanLiyou.trim()) { + wx.showToast({ title: '请输入驳回理由', icon: 'none' }); return; + } + wx.showLoading({ title: '提交中...', mask: true }); + try { + const res = await request({ + url: isStaffMode() ? STAFF_API.penaltyManage : '/dingdan/sjfkxgycx', + method: 'POST', + data: { + operation: 'cancel_penalty', + fadan_id: fadanData.id, + dingdan_id: jibenShuju.dingdan_id, + liyou: cancelFakuanLiyou + } + }); + wx.hideLoading(); + if (res && res.data.code === 0) { + wx.showToast({ title: '已撤销', icon: 'success' }); + this.setData({ showCancelFakuanModal: false }); + this.jiazaiXiangxiShuju(jibenShuju.dingdan_id); + } else { + wx.showToast({ title: res?.data?.msg || '撤销失败', icon: 'none' }); + } + } catch (err) { + wx.hideLoading(); + wx.showToast({ title: '网络错误', icon: 'none' }); + } + }, + + // 显示再次申请罚款弹窗(仅当状态为已驳回时可用) + showResubmitFakuan() { + if (this.data.fadanData?.zhuangtai !== 4) { + wx.showToast({ title: '当前状态不可再次申请', icon: 'none' }); + return; + } + this.setData({ + showResubmitFakuanModal: true, + resubmitFakuanLiyou: '', + resubmitFakuanJine: '' + }); + }, + closeResubmitFakuanModal() { + this.setData({ showResubmitFakuanModal: false }); + }, + inputResubmitFakuanLiyou(e) { + this.setData({ resubmitFakuanLiyou: e.detail.value.slice(0, 100) }); + }, + inputResubmitFakuanJine(e) { + this.setData({ resubmitFakuanJine: e.detail.value }); + }, + async submitResubmitFakuan() { + const { resubmitFakuanLiyou, resubmitFakuanJine, fadanData, jibenShuju } = this.data; + if (!resubmitFakuanLiyou.trim()) { + wx.showToast({ title: '请输入罚款理由', icon: 'none' }); return; + } + const jine = parseFloat(resubmitFakuanJine); + if (isNaN(jine) || jine <= 0) { + wx.showToast({ title: '请输入正确的罚款金额', icon: 'none' }); return; + } + wx.showLoading({ title: '提交中...', mask: true }); + try { + const res = await request({ + url: isStaffMode() ? STAFF_API.penaltyManage : '/dingdan/sjfkxgycx', + method: 'POST', + data: { + operation: 'resubmit_penalty', + fadan_id: fadanData.id, + dingdan_id: jibenShuju.dingdan_id, + liyou: resubmitFakuanLiyou, + jine: jine.toFixed(2) + } + }); + wx.hideLoading(); + if (res && res.data.code === 0) { + wx.showToast({ title: '申请成功', icon: 'success' }); + this.setData({ showResubmitFakuanModal: false }); + this.jiazaiXiangxiShuju(jibenShuju.dingdan_id); + } else { + wx.showToast({ title: res?.data?.msg || '申请失败', icon: 'none' }); + } + } catch (err) { + wx.hideLoading(); + wx.showToast({ title: '网络错误', icon: 'none' }); + } + }, + + // ========== 新增:订单退款操作(撤销/修改理由) ========== + showCancelTuikuan() { + if (this.data.jibenShuju.zhuangtai !== 4) { + wx.showToast({ title: '只有退款中的订单可撤销', icon: 'none' }); + return; + } + this.setData({ showCancelTuikuanModal: true, cancelTuikuanLiyou: '' }); + }, + closeCancelTuikuanModal() { + this.setData({ showCancelTuikuanModal: false }); + }, + inputCancelTuikuanLiyou(e) { + this.setData({ cancelTuikuanLiyou: e.detail.value.slice(0, 100) }); + }, + async submitCancelTuikuan() { + const { cancelTuikuanLiyou, jibenShuju } = this.data; + if (!cancelTuikuanLiyou.trim()) { + wx.showToast({ title: '请输入撤销理由', icon: 'none' }); return; + } + wx.showLoading({ title: '撤销中...', mask: true }); + try { + const res = await request({ + url: isStaffMode() ? STAFF_API.penaltyManage : '/dingdan/sjfkxgycx', + method: 'POST', + data: { + operation: 'cancel_refund', + dingdan_id: jibenShuju.dingdan_id, + liyou: cancelTuikuanLiyou + } + }); + wx.hideLoading(); + if (res && res.data.code === 0) { + wx.showToast({ title: '已撤销退款申请', icon: 'success' }); + this.setData({ showCancelTuikuanModal: false }); + this.jiazaiXiangxiShuju(jibenShuju.dingdan_id); + } else { + wx.showToast({ title: res?.data?.msg || '撤销失败', icon: 'none' }); + } + } catch (err) { + wx.hideLoading(); + wx.showToast({ title: '网络错误', icon: 'none' }); + } + }, + + showModifyTuikuan() { + if (this.data.jibenShuju.zhuangtai !== 4) { + wx.showToast({ title: '只有退款中的订单可修改理由', icon: 'none' }); + return; + } + this.setData({ + showModifyTuikuanModal: true, + modifyTuikuanLiyou: this.data.xiangxiShuju.tuikuan_liyou || '' + }); + }, + closeModifyTuikuanModal() { + this.setData({ showModifyTuikuanModal: false }); + }, + inputModifyTuikuanLiyou(e) { + this.setData({ modifyTuikuanLiyou: e.detail.value.slice(0, 100) }); + }, + async submitModifyTuikuan() { + const { modifyTuikuanLiyou, jibenShuju } = this.data; + if (!modifyTuikuanLiyou.trim()) { + wx.showToast({ title: '请输入退款理由', icon: 'none' }); return; + } + wx.showLoading({ title: '修改中...', mask: true }); + try { + const res = await request({ + url: isStaffMode() ? STAFF_API.penaltyManage : '/dingdan/sjfkxgycx', + method: 'POST', + data: { + operation: 'modify_refund_reason', + dingdan_id: jibenShuju.dingdan_id, + liyou: modifyTuikuanLiyou + } + }); + wx.hideLoading(); + if (res && res.data.code === 0) { + wx.showToast({ title: '修改成功', icon: 'success' }); + this.setData({ showModifyTuikuanModal: false }); + this.jiazaiXiangxiShuju(jibenShuju.dingdan_id); + } else { + wx.showToast({ title: res?.data?.msg || '修改失败', icon: 'none' }); + } + } catch (err) { + wx.hideLoading(); + wx.showToast({ title: '网络错误', icon: 'none' }); + } + }, + + // ========== 新增:教程按钮 ========== + startTutorialBtnTimer() { + if (this.tutorialTimer) clearTimeout(this.tutorialTimer); + this.tutorialTimer = setTimeout(() => { + this.setData({ tutorialBtnHidden: true }); + }, 5000); + }, + resetTutorialBtn() { + if (this.data.tutorialBtnHidden) { + this.setData({ tutorialBtnHidden: false }); + } + if (this.tutorialTimer) clearTimeout(this.tutorialTimer); + this.tutorialTimer = setTimeout(() => { + this.setData({ tutorialBtnHidden: true }); + }, 5000); + }, + onTutorialBtnTap() { + this.resetTutorialBtn(); + PopupService.checkAndShow(this, 'sjddxq1'); + } }) \ No newline at end of file diff --git a/pages/merchant-order-detail/merchant-order-detail.wxml b/pages/merchant-order-detail/merchant-order-detail.wxml index 5301cd6..0f6441b 100644 --- a/pages/merchant-order-detail/merchant-order-detail.wxml +++ b/pages/merchant-order-detail/merchant-order-detail.wxml @@ -1,354 +1,369 @@ - - - - - - - - - 加载中... - - - - - - - - - - - - {{jibenShuju.jieshao || '无描述'}} - - - - - - 订单信息 - - 游戏昵称{{jibenShuju.nicheng}} - - 备注{{jibenShuju.beizhu}} - - 订单ID{{jibenShuju.dingdan_id}}📋 - - 创建时间{{jibenShuju.create_time}} - - 订单金额¥{{jibenShuju.jine}} - - 状态{{zhuangtaiWenziMap[jibenShuju.zhuangtai]}} - - - 撤销退款申请 - 修改退款理由 - - - - - - 服务接单员 - - - - - - {{dashouInfo.nicheng || '未知昵称'}} - - 用户ID{{dashouInfo.yonghuid}}📋 - - - - - - - 服务详情 - - - 交付图片 - - - - - - - - - - - - - - 接单员留言 - {{xiangxiShuju.dashou_liuyan}} - - - - 退款理由 - {{xiangxiShuju.tuikuan_liyou}} - - - - 商家留言 - {{xiangxiShuju.shangjia_liuyan}} - - - - - - 罚款信息 - - 罚款金额¥{{fadanData.fakuanjine || '0.00'}} - - 罚款理由{{fadanData.chufaliyou || '-'}} - - 是否影响抢单{{fadanData.yingxiang_qiangdan == 1 ? '是' : '否'}} - - 罚单状态{{fadanData.zhuangtai == 1 ? '待缴纳' : fadanData.zhuangtai == 2 ? '已缴纳' : fadanData.zhuangtai == 3 ? '申诉中' : '已驳回'}} - - 申诉理由{{fadanData.shensuliyou}} - - 同意处罚理由{{fadanData.tongyi_chufaliyou}} - - 驳回理由{{fadanData.bohuiliyou}} - - 创建时间{{fadanData.create_time}} - - 更新时间{{fadanData.update_time}} - - - - - 修改罚款 - 撤销罚款 - - - - 再次申请罚款 - - - - - - - 更换接单员成功 - 新订单ID{{xinDingdanId}}📋 - - - - - 订单结算 - - 打星评分 - - - - - {{item.active ? '★' : '☆'}} - - - - -