// var Parse = getApp().Parse; // var app = getApp() // const { wxLogin } = require('./utils/login') const CONFIG = require("config.js"); let config = { appid: CONFIG.default.appid, company: CONFIG.default.company, rootPage: CONFIG.default.rootPage, } const plugin = requirePlugin('fm-plugin') const { Parse, checkAuth } = plugin Page({ /** * 页面的初始数据 */ data: { splashUrl: wx.getStorageSync("enabledOptions")[0], loading:true }, /** * 生命周期函数--监听页面加载 */ onLoad: async function (options) { // 拦截插件的"登录信息已过期"提示 const originalShowModal = wx.showModal; wx.showModal = function(options) { // 如果是"登录信息已过期"的提示,改为友好的重新登录提示 if (options.content && options.content.includes('登录信息过期')) { console.log('⚠️ 拦截到"登录信息已过期"提示,改为友好提示'); return originalShowModal.call(this, { title: '提示', content: '登录状态异常,是否重新登录?', showCancel: true, cancelText: '取消', confirmText: '重新登录', success: (result) => { if (result.confirm) { // 清除登录状态 wx.removeStorageSync("sessionToken"); wx.removeStorageSync("userLogin"); // 重新加载首页 const rootPage = getApp().globalData.rootPage || getApp().globalData.defaultTabBar?.list?.[0]?.pagePath || '/pages/index/index'; wx.reLaunch({ url: rootPage }); } // 调用原始的 success 回调 if (options.success) { options.success(result); } } }); } // 其他提示正常显示 return originalShowModal.call(this, options); }; wx.login({ success: function (res) { if (res.code) { console.log(res); // wx.request({ // url: "https://server.fmode.cn/api/wxapp/auth_wxapp", // data: { // c: getApp().globalData.company, // code: res.code, // }, // async success(res) { // wx.setStorageSync("userInfo", res.data); // resolve(res) // }, // }); } }, fail: function (err) { wx.showToast({ title: '服务器繁忙,请稍后重试', }) } }); wx.setStorageSync("invite", null); // 处理扫码链接(options.q 包含完整的URL或activityId) if (options.q) { let str = decodeURIComponent(options.q); // 扫描二维码获得的跳转链接 // 兼容一些环境中把 & 编码成 & 的情况,防止参数解析错误(如 scanCount=0&storeId=...) if (str.indexOf('&') !== -1) { console.log('🔧 检测到 URL 中包含 &,自动还原为 &'); str = str.replace(/&/g, '&'); } try { wx.setStorageSync('scan_raw_url', str); } catch (e) {} // 检查是否是员工邀请链接(app.fmode.cn/dev/pobingfeng/manager/staff?invite=xxx) if (str.includes('app.fmode.cn/dev/pobingfeng/manager/staff')) { let obj = this.getParaName(str); if (obj && obj.invite) { wx.setStorageSync("invite", obj.invite); console.log('✅ 检测到员工邀请链接,invite:', obj.invite); } // 员工邀请链接不需要跳转到店铺,直接返回 this.setData({ options: options }); plugin.init(config, wx.getStorageSync('invite')); return; } // 检查是否是活动海报二维码(activityId作为qrCode参数值) // 如果str不包含http/https,且看起来像是一个ID,则可能是activityId if (!str.includes('http://') && !str.includes('https://') && !str.includes('?')) { // 可能是活动ID,尝试作为activityId处理 if (str && str.length > 0 && !isNaN(str)) { options.activityId = str; console.log('✅ 检测到活动海报二维码,activityId:', str); // 活动二维码需要特殊处理,先保存activityId wx.setStorageSync("activityId", str); } } // 处理店铺相关的二维码链接(pwa.fmode.cn/gomini/pmd/) // 从完整URL中提取参数 if (str.includes('pwa.fmode.cn/gomini/pmd') || str.includes('?')) { let obj = this.getParaName(str); if (obj && obj.invite) { wx.setStorageSync("invite", obj.invite); } // 将所有参数合并到 options 中 obj && Object.keys(obj).forEach(key=> options[key] = obj[key]); } } // 兼容从编译参数或页面直达传入的 storeId if (options && options.scene && !options.storeId) { try { const sceneStr = decodeURIComponent(options.scene) if (sceneStr) { const pairs = sceneStr.split('&') for (const p of pairs) { const [k, v] = p.split('=') if (k === 'storeId' && v) { options.storeId = v break } } } } catch (e) { console.warn('解析 scene 失败:', e) } } this.setData({ options: options }) let { time, dramaId, roomId, orderId, shopId, invite, activityId, company, inviteHost, storeId } = options time && wx.setStorageSync("time", time); dramaId && wx.setStorageSync("dramaId", dramaId); roomId && wx.setStorageSync("roomId", roomId); orderId && wx.setStorageSync("orderId", orderId); shopId && wx.setStorageSync("shopId", shopId); invite && wx.setStorageSync("invite", invite); activityId && wx.setStorageSync("activityId", activityId); inviteHost && wx.setStorageSync("inviteHost", true); if (storeId) { // 扫码进入时,强制设置店铺 ID,标记为扫码来源 wx.setStorageSync('storeId', storeId) getApp().globalData.storeId = storeId wx.setStorageSync('storeId_from_scan', true) console.log('✅ 入口页扫码进入,已设置店铺 ID(优先级最高):', storeId) } if (company) getApp().globalData.toCompany = true; // 检查是否是扫码进入(需要统计扫码次数) this.checkAndHandleScan(options); plugin.init(config, wx.getStorageSync('invite')) try { getApp().checkTrafficDeductPending = this.checkTrafficDeductPending.bind(this); console.log('🚦 [traffic] 已注册全局复判方法 checkTrafficDeductPending'); } catch (e) { console.warn('🚦 [traffic] 注册全局复判方法失败:', e?.message || e); } try { getApp().checkAndRecordPendingScan = this.checkAndRecordPendingScan.bind(this); } catch (e) {} }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: async function () { }, /** * 生命周期函数--监听页面显示 */ onShow: async function () { await this.review() try { if (typeof this.checkTrafficDeductPending === 'function') { console.log('🚦 [traffic] onShow 触发暂缓扣减复判'); await this.checkTrafficDeductPending(); } } catch (e) { console.warn('🚦 [traffic] onShow 复判触发失败:', e?.message || e); } }, async review(force){ try { let options = this.data.options let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath if (options) { let objArr = Object.keys(options) if (objArr && objArr.length > 0) { let parms = '?' objArr.forEach((o, index) => { if (index > 0) { parms += '&' + o + '=' + options[o] } else { parms += o + '=' + options[o] } }) url += parms } } let currentUser = Parse.User.current() console.log('==========================================='); console.log('======= index.js review 方法 ======='); console.log('当前用户:', currentUser ? currentUser.id : '无'); console.log('用户手机号:', currentUser?.get('mobile') || '无'); console.log('用户名:', currentUser?.get('username') || '无'); console.log('Session Token:', currentUser?.getSessionToken()?.substring(0, 20) || '无'); console.log('userLogin 存储:', wx.getStorageSync('userLogin') || '无'); console.log('force 参数:', force); console.log('==========================================='); // 查询 Company 的 isPublishing 字段 let isPublishing = false; try { const companyQuery = new Parse.Query('Company'); companyQuery.equalTo('objectId', getApp().globalData.company); companyQuery.select('isPublishing'); try { const companyObj = await companyQuery.first(); if (companyObj) { isPublishing = companyObj.get('isPublishing') === true; console.log('📋 Company isPublishing:', isPublishing); } else { console.log('⚠️ 未找到 Company 记录,默认 isPublishing = false'); isPublishing = false; } } catch (queryError) { console.error('❌ 查询 Company 失败(可能 token 无效):', queryError.message); // 查询失败时,默认为 false(强制登录) isPublishing = false; } // 保存到全局,供其他页面使用 getApp().globalData.isPublishing = isPublishing; wx.setStorageSync('isPublishing', isPublishing); } catch (error) { console.error('❌ 查询 Company isPublishing 失败:', error); // 查询失败时,默认为 false(强制登录) isPublishing = false; } // 根据 isPublishing 决定是否强制登录 if (!currentUser || force) { console.log('🔄 开始调用 checkAuth...'); // isPublishing == true 时不强制授权,否则强制授权 const forceAuth = !isPublishing; console.log('🔐 是否强制登录:', forceAuth); let r = await checkAuth(forceAuth); console.log('==========================================='); console.log('======= checkAuth 返回结果 ======='); console.log('返回值:', r); // 重新获取用户信息 currentUser = Parse.User.current(); console.log('checkAuth 后的用户:', currentUser ? currentUser.id : '无'); console.log('checkAuth 后的手机号:', currentUser?.get('mobile') || '无'); console.log('checkAuth 后的 Session Token:', currentUser?.getSessionToken()?.substring(0, 20) || '无'); console.log('==========================================='); // 如果强制登录但用户未登录,不允许继续访问 if (forceAuth && !r) { console.log('❌ 强制登录模式,用户未登录,停止访问'); return; } // 即使登录失败,也允许继续访问(仅在非强制登录模式下) if(!r) { console.log('⚠️ 用户未登录或拒绝授权,允许游客访问'); if (wx.getStorageSync('need_scan_redirect') === true) { await this.ensureTrackingUser(); await this.checkAndRecordUserSourceOnLogin(); } // 不要 return,继续执行后面的跳转逻辑 } else { // 登录成功,设置 userLogin if (currentUser && currentUser.get('mobile')) { wx.setStorageSync("userLogin", currentUser.id); console.log('✅ 授权登录成功,已设置 userLogin:', currentUser.id); console.log('✅ 用户手机号:', currentUser.get('mobile')); } else { console.warn('⚠️ checkAuth 返回成功,但用户没有手机号!'); console.warn(' 用户对象:', currentUser); } // 检查是否有待记录的扫码信息 await this.checkAndRecordPendingScan(); await this.checkTrafficDeductPending(); // 如果用户没有来源信息,且当前有扫码参数,记录来源 await this.checkAndRecordUserSourceOnLogin(); } } else { console.log('✅ 用户已登录,跳过 checkAuth'); // 用户已登录,确保 userLogin 已设置 if (currentUser.get('mobile')) { wx.setStorageSync("userLogin", currentUser.id); console.log('✅ 已确认 userLogin:', currentUser.id); } this.updateUser(currentUser.id); // 用户已登录,检查是否有待记录的扫码信息 await this.checkAndRecordPendingScan(); await this.checkTrafficDeductPending(); // 如果用户没有来源信息,且当前有扫码参数,记录来源 await this.checkAndRecordUserSourceOnLogin(); } getApp().Parse = Parse getApp().checkAuth = checkAuth getApp().checkTrafficDeductPending = this.checkTrafficDeductPending if (!await this.getCompanyServerExpire(url)) { return } // 检查是否需要跳转到活动页面 if (wx.getStorageSync('need_activity_redirect') === true) { await this.redirectToActivityPage(); return; } // 检查是否需要跳转到扫码统计页面 if (this.shouldRedirectToScanPage()) { await this.redirectToScanPage(); return; } console.log('✅ 准备跳转到:', url); wx.redirectTo({ url: url, success: () => { console.log('✅ redirectTo 跳转成功'); }, fail: (err) => { console.error('❌ redirectTo 失败:', err); console.log('⚠️ 尝试使用 reLaunch'); // 降级:尝试使用 reLaunch wx.reLaunch({ url: url, success: () => { console.log('✅ reLaunch 跳转成功'); }, fail: (err2) => { console.error('❌ reLaunch 也失败:', err2); // 显示错误提示 this.setData({ loading: false }); wx.showModal({ title: '温馨提示', content: '页面加载失败,请检查网络后重试。', showCancel: true, cancelText: '退出', confirmText: '重试', success: (result) => { if (result.confirm) { this.review(true); } else { wx.exitMiniProgram(); } } }); } }); } }); } catch (err) { console.log('❌ review 方法出错:', err); /* 登录身份信息到期,重新登陆 */ if((err?.message?.indexOf('Session token is expired') != -1 || err?.message?.indexOf('Invalid session token') != -1) && !force){ console.log('⚠️ Session Token 过期,准备重新登录'); // 保存需要保留的数据 const invite = wx.getStorageSync('invite'); const agreementAccepted = wx.getStorageSync('user_agreement_accepted'); const storeId = wx.getStorageSync('storeId'); const isPublishing = wx.getStorageSync('isPublishing'); // 先登出 Parse 用户 try { await Parse.User.logOut(); console.log('✅ Parse 用户已登出'); } catch (logoutErr) { console.warn('⚠️ Parse 登出失败:', logoutErr); } // 清除所有存储 wx.clearStorageSync(); // 恢复需要保留的数据 if (invite) wx.setStorageSync('invite', invite); if (agreementAccepted) wx.setStorageSync('user_agreement_accepted', agreementAccepted); if (storeId) wx.setStorageSync('storeId', storeId); if (isPublishing !== undefined) wx.setStorageSync('isPublishing', isPublishing); console.log('✅ 已清除过期登录信息,保留必要数据'); console.log(' 保留的 invite:', invite || '无'); console.log(' 保留的协议状态:', agreementAccepted || '无'); console.log(' 保留的店铺ID:', storeId || '无'); /* 强制重新登录 */ this.review(true); return; } // 如果出错,尝试继续跳转到主页(游客模式) console.log('⚠️ 登录出错,尝试游客模式访问'); let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath; if (this.data.options) { let objArr = Object.keys(this.data.options) if (objArr && objArr.length > 0) { let parms = '?' objArr.forEach((o, index) => { if (index > 0) { parms += '&' + o + '=' + this.data.options[o] } else { parms += o + '=' + this.data.options[o] } }) url += parms } } wx.redirectTo({ url: url, fail: () => { // 如果跳转失败,显示错误提示 this.setData({ loading:false }) wx.showModal({ title: '温馨提示', content: '页面加载失败,请检查网络后重试。', showCancel: true, cancelText: '退出', confirmText: '重试', success: (result) => { if (result.confirm) { // 用户选择重试 this.review(true) } else { // 用户选择退出 wx.exitMiniProgram() } }, }); } }); } }, /** * 检查并记录待处理的扫码统计 * 同时记录用户来源信息到 _User 表 */ async checkAndRecordPendingScan() { try { const pendingScan = wx.getStorageSync('pending_scan_record'); if (!pendingScan) { return; } console.log('==========================================='); console.log('======= 发现待记录的扫码信息 ======='); console.log('扫码信息:', pendingScan); console.log('==========================================='); // 检查是否超时(24小时) const now = Date.now(); const scanTime = pendingScan.timestamp || 0; const hoursPassed = (now - scanTime) / (1000 * 60 * 60); if (hoursPassed > 24) { console.log('⚠️ 扫码信息已超过24小时,不再记录'); wx.removeStorageSync('pending_scan_record'); return; } // 记录用户来源信息 await this.recordUserSource(pendingScan); // 记录扫码统计 await this.recordScanStatistics({ storeId: pendingScan.storeId, sourceType: pendingScan.sourceType, sourceId: pendingScan.sourceId, ownerId: pendingScan.ownerId, employeeId: pendingScan.employeeId, partnerId: pendingScan.partnerId, userId: pendingScan.userId, productId: pendingScan.productId, scanCount: pendingScan.scanCount, scanTimestamp: pendingScan.timestamp }); // 清除待记录的扫码信息 wx.removeStorageSync('pending_scan_record'); console.log('✅ 扫码统计已记录并清除'); } catch (error) { console.error('❌ 记录待处理扫码信息失败:', error); } }, /** * 检查并处理暂缓扣减任务(在获取到手机号后复判白名单并决定是否扣减) */ async checkTrafficDeductPending() { try { const pending = wx.getStorageSync('traffic_deduct_pending'); if (!pending) return; const currentUser = Parse.User.current(); if (!currentUser || !currentUser.id) return; const beforeNumbers = collectUserMobiles(currentUser); console.log('🚦 [traffic] 检测到暂缓扣减任务:', { storeId: pending && pending.storeId, userId: currentUser.id, userNumbers: beforeNumbers }); let userNumbers = beforeNumbers; try { const sessionToken = typeof currentUser.getSessionToken === 'function' ? currentUser.getSessionToken() : null; const q = new Parse.Query('_User'); q.select('mobile', 'phone'); const freshUser = await q.get(currentUser.id, sessionToken ? { sessionToken } : undefined); const afterNumbers = collectUserMobiles(freshUser); console.log('🚦 [traffic] 刷新用户后号码:', { before: beforeNumbers, after: afterNumbers }); if (Array.isArray(afterNumbers) && afterNumbers.length) { userNumbers = afterNumbers; } } catch (e) { console.warn('🚦 [traffic] 刷新用户手机号失败:', e?.message || e); } if (!userNumbers || !userNumbers.length) { const storageMobile = wx.getStorageSync('user_mobile') || ''; console.log('🚦 [traffic] 仍未获取到手机号,继续暂缓,并安排 800ms 后重试', { storageMobile }); try { const retryInfo = wx.getStorageSync('traffic_deduct_retry') || { count: 0 }; if ((retryInfo.count || 0) < 3) { wx.setStorageSync('traffic_deduct_retry', { count: (retryInfo.count || 0) + 1 }); setTimeout(() => { try { if (typeof getApp().checkTrafficDeductPending === 'function') { console.log('🚦 [traffic] 重试触发暂缓扣减复判'); getApp().checkTrafficDeductPending(); } } catch (e) {} }, 800); } } catch (e) {} return; } const storeId = pending && pending.storeId ? pending.storeId : null; if (!storeId) { console.warn('🚦 [traffic] 暂缓扣减任务缺少 storeId,清除任务'); wx.removeStorageSync('traffic_deduct_pending'); return; } const isExempt = await isTrafficExemptForStore(Parse, storeId, currentUser); if (isExempt) { console.log('🚦 [traffic] 暂缓扣减复判:命中白名单,取消扣减并清除暂缓任务'); try { currentUser.set('trafficExempt', true); currentUser.set('trafficExemptAt', new Date()); currentUser.set('trafficExemptStoreId', storeId); await currentUser.save(); } catch (e) { console.warn('🚦 [traffic] 写入 trafficExempt 失败(不影响继续访问):', e?.message || e); } wx.removeStorageSync('traffic_deduct_pending'); return; } console.log('🚦 [traffic] 暂缓扣减复判:未命中白名单,执行扣减'); try { await decrementStoreTrafficImpl(Parse, storeId); const deductedStores = Array.isArray(currentUser.get('trafficDeductedStores')) ? currentUser.get('trafficDeductedStores') : []; const updatedStores = Array.isArray(deductedStores) ? deductedStores.slice() : []; if (!updatedStores.includes(storeId)) updatedStores.push(storeId); currentUser.set('trafficDeductedStores', updatedStores); currentUser.set('trafficDeductedStoreId', storeId); const atMap = currentUser.get('trafficDeductedAtMap') || {}; atMap[storeId] = new Date(); currentUser.set('trafficDeductedAtMap', atMap); await currentUser.save(); console.log('🚦✅ [traffic] 暂缓扣减复判:已为店铺扣减 1 个流量,storeId:', storeId); } catch (decErr) { console.error('🚦❌ [traffic] 暂缓扣减复判:扣减失败:', decErr?.message || decErr); } wx.removeStorageSync('traffic_deduct_pending'); } catch (error) { console.error('🚦❌ [traffic] 处理暂缓扣减任务失败:', error); } }, async ensureTrackingUser() { try { let currentUser = Parse.User.current(); if (currentUser) { return currentUser; } if (Parse.AnonymousUtils && typeof Parse.AnonymousUtils.logIn === 'function') { await Parse.AnonymousUtils.logIn(); currentUser = Parse.User.current(); return currentUser || null; } return null; } catch (e) { console.warn('ensureTrackingUser 失败:', e?.message || e); return null; } }, /** * 记录用户来源信息到 _User 表 * 根据扫码参数判断来源类型并保存到用户的 source 字段 * * 来源类型: * 1. 渠道xxx→异业xxx(老板后台添加的异业) * 2. 渠道xxx→异业xxx→员工xxx(员工后台添加的异业) * 3. 员工xxx * 4. 老板 * 5. 自主进入(无任何推荐) */ async recordUserSource(scanInfo) { try { const currentUser = Parse.User.current(); if (!currentUser) { console.log('⚠️ 用户未登录,无法记录来源'); return; } const existingSource = currentUser.get('source'); console.log('📊 ==========================================='); console.log('📊 [记录来源] 开始记录用户来源信息'); console.log('📊 用户ID:', currentUser.id); console.log('📊 ==========================================='); const { partnerId, employeeId, ownerId, userId, storeId } = scanInfo; console.log('📌 [扣减前置] storeId:', storeId || '无'); console.log('📌 [扣减前置] existingSource:', existingSource ? '有' : '无'); console.log('📌 [扣减前置] trafficDeducted:', currentUser.get('trafficDeducted') === true ? 'true' : 'false'); console.log('📌 [扣减前置] trafficDeductedStoreId:', currentUser.get('trafficDeductedStoreId') || '无'); console.log('📌 [扣减前置] trafficDeductedAt:', currentUser.get('trafficDeductedAt') || '无'); try { const scanSnapshot = { storeId: wx.getStorageSync('scan_storeId') || null, sourceType: wx.getStorageSync('scan_sourceType') || null, sourceId: wx.getStorageSync('scan_sourceId') || null, ownerId: wx.getStorageSync('scan_ownerId') || null, employeeId: wx.getStorageSync('scan_employeeId') || null, partnerId: wx.getStorageSync('scan_partnerId') || null, userId: wx.getStorageSync('scan_userId') || null, productId: wx.getStorageSync('scan_productId') || null, caseId: wx.getStorageSync('scan_caseId') || null, planId: wx.getStorageSync('scan_planId') || null, shareUserId: wx.getStorageSync('scan_shareUserId') || null, scanCount: wx.getStorageSync('scan_scanCount') || null, rawUrl: wx.getStorageSync('scan_raw_url') || null }; console.log('🔗 [扫码参数快照] scanInfo:', scanInfo); console.log('🔗 [扫码参数快照] storage:', scanSnapshot); } catch (e) { console.warn('⚠️ 读取扫码参数快照失败:', e?.message || e); } // 员工推荐判定与日志 try { let staffJudge = { isStaff: false, reason: 'no_staff_params' }; if (employeeId) { staffJudge = { isStaff: true, reason: 'employeeId_param', staffId: employeeId }; } else if (ownerId) { staffJudge = { isStaff: true, reason: 'ownerId_param', staffId: ownerId }; } else if (userId) { try { const uq = new Parse.Query('_User'); const u = await uq.get(userId); const roles = Array.isArray(u.get('roles')) ? u.get('roles').map(r => String(r).toLowerCase()) : []; const isStaff = roles.includes('staff') || roles.includes('userstaff') || roles.includes('employee') || roles.includes('sales'); staffJudge = { isStaff, reason: isStaff ? 'user_role_staff' : 'user_role_non_staff', staffId: userId, roles }; } catch (e) { staffJudge = { isStaff: false, reason: 'user_lookup_failed', error: e?.message || e }; } } console.log('👥 [员工推荐判定] 结果:', staffJudge); } catch (e) { console.warn('⚠️ [员工推荐判定] 失败:', e?.message || e); } // 来源优先级判定与升级覆盖 const pickType = () => { if (partnerId && employeeId) return 'channel_partner_employee'; if (partnerId) return 'channel_partner'; if (employeeId) return 'employee'; if (ownerId) return 'owner'; if (userId) return 'promoter'; return 'self_entry'; }; const prio = (t) => { if (t === 'channel_partner_employee') return 4; if (t === 'owner' || t === 'employee') return 3; if (t === 'channel_partner') return 2; if (t === 'promoter') return 1; return 0; }; const newType = pickType(); const oldType = existingSource && existingSource.type ? String(existingSource.type) : 'self_entry'; const shouldOverride = prio(newType) > prio(oldType); console.log('🔀 [来源优先级] oldType:', oldType, 'newType:', newType, 'override:', shouldOverride ? 'YES' : 'NO'); if (!existingSource || shouldOverride) { if (existingSource && shouldOverride) { console.log('🔁 [来源升级] 触发覆盖: 从', oldType, '→', newType); } let sourceInfo = { type: 'self_entry', label: '自主进入', timestamp: new Date(), storeId: storeId }; if (partnerId && employeeId) { console.log('🔍 [来源类型] 渠道→异业→员工'); try { const partnerQuery = new Parse.Query('Partner'); const partner = await partnerQuery.get(partnerId); const partnerName = partner.get('name') || '未知异业'; const channelName = partner.get('channelName') || partner.get('category') || '未知渠道'; const employeeQuery = new Parse.Query('Employee'); const employee = await employeeQuery.get(employeeId); const employeeName = employee.get('name') || '未知员工'; sourceInfo = { type: 'channel_partner_employee', label: `${channelName},${partnerName},员工${employeeName}`, channelName: channelName, partnerId: partnerId, partnerName: partnerName, employeeId: employeeId, employeeName: employeeName, timestamp: new Date(), storeId: storeId }; console.log('✅ [来源信息] 渠道→异业→员工:', sourceInfo.label); } catch (error) { console.error('❌ 查询异业或员工信息失败:', error); } } else if (partnerId && !employeeId) { console.log('🔍 [来源类型] 渠道→异业'); try { const partnerQuery = new Parse.Query('Partner'); const partner = await partnerQuery.get(partnerId); const partnerName = partner.get('name') || '未知异业'; const channelName = partner.get('channelName') || partner.get('category') || '未知渠道'; sourceInfo = { type: 'channel_partner', label: `${channelName},${partnerName}`, channelName: channelName, partnerId: partnerId, partnerName: partnerName, timestamp: new Date(), storeId: storeId }; console.log('✅ [来源信息] 渠道→异业:', sourceInfo.label); } catch (error) { console.error('❌ 查询异业信息失败:', error); } } else if (employeeId && !partnerId) { console.log('🔍 [来源类型] 员工'); try { // 兼容管理员端员工表 UserStaff let employeeName = '未知员工'; try { const staffQuery = new Parse.Query('UserStaff'); const staff = await staffQuery.get(employeeId); employeeName = staff.get('name') || staff.get('nickname') || staff.get('mobile') || '管理员端员工'; } catch (e1) { try { const employeeQuery = new Parse.Query('Employee'); const employee = await employeeQuery.get(employeeId); employeeName = employee.get('name') || '未知员工'; } catch (e2) {} } sourceInfo = { type: 'employee', label: employeeName, employeeId: employeeId, employeeName: employeeName, timestamp: new Date(), storeId: storeId }; console.log('✅ [来源信息] 员工:', sourceInfo.label); } catch (error) { console.error('❌ 查询员工信息失败:', error); } } else if (ownerId) { console.log('🔍 [来源类型] 老板'); sourceInfo = { type: 'owner', label: '老板', ownerId: ownerId, timestamp: new Date(), storeId: storeId }; console.log('✅ [来源信息] 老板'); } else if (userId) { console.log('🔍 [来源类型] 推广员'); try { const userQuery = new Parse.Query('_User'); const promoter = await userQuery.get(userId); const promoterName = promoter.get('username') || promoter.get('mobile') || '未知推广员'; sourceInfo = { type: 'promoter', label: `推广员${promoterName}`, userId: userId, promoterName: promoterName, timestamp: new Date(), storeId: storeId }; console.log('✅ [来源信息] 推广员:', sourceInfo.label); } catch (error) { console.error('❌ 查询推广员信息失败:', error); } } else { console.log('🔍 [来源类型] 自主进入'); console.log('✅ [来源信息] 自主进入'); } currentUser.set('source', sourceInfo); await currentUser.save(); console.log('✅ [保存成功] 用户来源信息已保存'); console.log(' - 来源类型:', sourceInfo.type); console.log(' - 来源标签:', sourceInfo.label); console.log('📊 ==========================================='); } else { console.log('ℹ️ 用户已有来源信息,跳过覆盖:', existingSource); } const deductedStores = Array.isArray(currentUser.get('trafficDeductedStores')) ? currentUser.get('trafficDeductedStores') : []; const hasDeductedForThisStore = (Array.isArray(deductedStores) && deductedStores.includes(storeId)) || (currentUser.get('trafficDeductedStoreId') && currentUser.get('trafficDeductedStoreId') === storeId); if (storeId && !hasDeductedForThisStore) { const internal = await isInternalVisit(Parse, storeId, Parse.User.current(), { ownerId, employeeId, partnerId }); if (internal) { console.log('ℹ️ [扣减跳过] 内部角色访问(老板/异业/员工),不扣减流量'); } else { const nums = collectUserMobiles(currentUser); if (!nums || !nums.length) { console.log('ℹ️ [扣减暂停] 用户未提供手机号,暂缓扣减', { storeId, userId: currentUser.id }); try { wx.setStorageSync('traffic_deduct_pending', { storeId, userId: currentUser.id, time: Date.now() }); } catch (e) {} } else { const isExempt = await isTrafficExemptForStore(Parse, storeId, currentUser); if (isExempt) { console.log('ℹ️ [扣减跳过] 命中白名单手机号,跳过扣减流量'); try { currentUser.set('trafficExempt', true); currentUser.set('trafficExemptAt', new Date()); currentUser.set('trafficExemptStoreId', storeId); await currentUser.save(); } catch (e) { console.warn('⚠️ [扣减跳过] 写入 trafficExempt 失败(不影响继续访问):', e?.message || e); } } else { console.log('🧮 [扣减流量] 检测到新用户首次扣减,准备扣减门店流量'); try { await decrementStoreTrafficImpl(Parse, storeId); const updatedStores = Array.isArray(deductedStores) ? deductedStores.slice() : []; if (!updatedStores.includes(storeId)) updatedStores.push(storeId); currentUser.set('trafficDeductedStores', updatedStores); currentUser.set('trafficDeductedStoreId', storeId); const atMap = currentUser.get('trafficDeductedAtMap') || {}; atMap[storeId] = new Date(); currentUser.set('trafficDeductedAtMap', atMap); await currentUser.save(); console.log('✅ [扣减成功] 已为店铺扣减 1 个流量,storeId:', storeId); } catch (decErr) { console.error('❌ [扣减失败] 扣减门店流量失败:', decErr?.message || decErr); } } } } } else if (!storeId) { console.warn('⚠️ [扣减跳过] storeId 为空,无法扣减 ShopStore.traffic'); } else if (hasDeductedForThisStore) { console.log('ℹ️ [扣减跳过] 该用户已在该门店扣减过流量,不重复扣减'); } } catch (error) { console.error('❌ 记录用户来源失败:', error); } }, /** * 用户登录后检查并记录来源信息 * 如果用户是首次登录且没有来源信息,根据当前的扫码参数记录来源 */ async checkAndRecordUserSourceOnLogin() { try { const currentUser = Parse.User.current(); if (!currentUser) { return; } // 检查是否有扫码参数 const scanStoreId = wx.getStorageSync('scan_storeId'); const scanPartnerId = wx.getStorageSync('scan_partnerId'); const scanEmployeeId = wx.getStorageSync('scan_employeeId'); const scanOwnerId = wx.getStorageSync('scan_ownerId'); const scanUserId = wx.getStorageSync('scan_userId'); // 如果有任何扫码参数,记录来源 if (scanStoreId || scanPartnerId || scanEmployeeId || scanOwnerId || scanUserId) { console.log('📊 检测到扫码参数,记录用户来源'); await this.recordUserSource({ storeId: scanStoreId, partnerId: scanPartnerId, employeeId: scanEmployeeId, ownerId: scanOwnerId, userId: scanUserId }); } else { // 没有任何扫码参数,标记为自主进入 console.log('📊 无扫码参数,标记为自主进入'); await this.recordUserSource({ storeId: wx.getStorageSync('storeId') || null }); } } catch (error) { console.error('❌ 检查并记录用户来源失败:', error); } }, async updateUser(id) { try { let User = new Parse.Query('_User') let user = await User.get(id) let invite = wx.getStorageSync('invite') //查询邀请人user let query = new Parse.Query("_User") query.equalTo('objectId', invite) let result = await query.first() if (result && result.id && result.get("invite")?.id == user.id) { console.error('邀请人不能是自己的下级') return } if (invite && !user.get('invite') && user.id != invite && !user.get('agentLevel')) { console.log('上下级绑定成功'); user.set('invite', { __type: "Pointer", className: "_User", objectId: invite }) user.set('agent', { __type: "Pointer", className: "_User", objectId: invite }) await Parse.Cloud.run('user_save', { userJson: user.toJSON() }) } } catch (error) { console.error('❌ updateUser 失败:', error.message); // 不阻断流程,继续执行 } }, async getCompanyServerExpire(url) { try { let query = new Parse.Query('Company') query.equalTo('objectId', getApp().globalData.company) query.select('expireDate', 'expireMap') let com = await query.first() if (com?.id && com?.get('expireDate')) { let now = + new Date() let expireTime = + new Date(com?.get('expireDate')) if (com?.get('expireMap') && com.get('expireMap')[getApp().globalData.appid]) { expireTime = + new Date(com.get('expireMap')[getApp().globalData.appid]) } if (now >= expireTime) { console.log('服务器到期'); wx.reLaunch({ url: `common-page/pages/loading/index?url=${url}`, }); return } } return true } catch (error) { console.error('❌ getCompanyServerExpire 失败:', error.message); // 查询失败时,允许继续访问 return true } }, onUnload: function () { wx.setStorageSync("active", 0); }, getParaName(url) { if (!url || url.indexOf('?') == -1) { return } // 兼容 URL 中 & 的情况,先统一还原为 & if (url.indexOf('&') !== -1) { url = url.replace(/&/g, '&'); } // 提取查询参数部分(去除可能的 hash 部分) let queryString = url.split('?')[1]; // 如果包含 #,只取 # 之前的部分 if (queryString.indexOf('#') !== -1) { queryString = queryString.split('#')[0]; } return this.setObject(queryString) //封装成对象 }, setObject(paraArr) { let obj = {} let arr1 = paraArr.split('&') arr1.forEach(item => { let str = item.split('=') let key = str[0] let val = str[1] obj[key] = val }) return obj }, /** * 检查并处理扫码参数 * 支持所有类型的二维码: * 1. 推广员二维码: ?scanCount=0&storeId=xxx&userId=xxx * 2. 产品二维码: ?scanCount=0&storeId=xxx&productId=xxx * 3. 案例二维码: ?scanCount=0&storeId=xxx&caseId=xxx * 4. 活动海报二维码: activityId作为qrCode参数值 * 5. 异业合作伙伴二维码(移动端 / PC端统一): ?scanCount=xxx&storeId=xxx&partnerId=xxx * 6. 员工邀请二维码(移动端): ?scanCount=0&storeId=xxx&employeeId=xxx * 7. 我的二维码(老板): ?scanCount=0&storeId=xxx&ownerId=xxx * 8. 我的二维码(员工): ?scanCount=0&storeId=xxx&employeeId=xxx * 9. 方案分享二维码: ?scanCount=0&storeId=xxx&planId=xxx&shareUserId=xxx */ checkAndHandleScan(options) { console.log('🔍🔍🔍 ========================================'); console.log('🔍 [扫码检测] 开始检查扫码参数'); console.log('🔍 完整 options 对象:', JSON.stringify(options, null, 2)); console.log('🔍🔍🔍 ========================================'); const { scanCount, ownerId, employeeId, partnerId, storeId, productId, caseId, userId, // 推广员二维码使用userId activityId, schemeId, // 方案ID(旧参数名) planId, // 方案ID(新参数名,优先使用) shareUserId, // 分享方案的用户ID q // 扫码链接 } = options; // planId 和 schemeId 兼容处理,优先使用 planId const finalPlanId = planId || schemeId; console.log('📋 [扫码参数] 提取的参数:'); console.log(' - scanCount:', scanCount || '无'); console.log(' - storeId:', storeId || '无'); console.log(' - ownerId:', ownerId || '无'); console.log(' - employeeId:', employeeId || '无'); console.log(' - partnerId:', partnerId || '无'); console.log(' - userId:', userId || '无'); console.log(' - productId:', productId || '无'); console.log(' - caseId:', caseId || '无'); console.log(' - activityId:', activityId || '无'); console.log(' - planId:', planId || '无'); console.log(' - schemeId:', schemeId || '无'); console.log(' - finalPlanId (最终使用):', finalPlanId || '无'); console.log(' - shareUserId:', shareUserId || '无'); console.log(' - q (扫码链接):', q || '无'); // 处理活动海报二维码(activityId作为qrCode参数值) if (activityId && !storeId) { console.log('🎯 ==========================================='); console.log('🎯 [活动二维码] 检测到活动海报二维码'); console.log('🎯 活动ID (activityId):', activityId); console.log('🎯 来源类型: 活动海报'); console.log('🎯 ==========================================='); // 活动二维码需要跳转到活动页面,不需要跳转到店铺 wx.setStorageSync('scan_activityId', activityId); wx.setStorageSync('need_activity_redirect', true); return; } // 如果存在 storeId,说明是扫码进入具体门店(包括异业分享) if (storeId) { console.log('🏪 ==========================================='); console.log('🏪 [店铺扫码] 检测到扫码参数'); console.log('🏪 店铺ID (storeId):', storeId); console.log('🏪 扫码次数 (scanCount):', scanCount || '0'); // 确定来源类型 // 规则: // 1. 扫描异业码(partnerId)→ sourceType = "partner" // 2. 扫描员工码(employeeId、老板/员工)→ sourceType = "sales" 或 "promoter" // 3. 扫描其他码(产品码、案例码等)→ sourceType = "scan" let sourceType = 'scan'; // 默认为通用扫码 let sourceId = null; if (partnerId) { // 异业合作伙伴二维码 sourceType = 'partner'; sourceId = partnerId; console.log('🤝 [来源识别] 来源类型: 异业合作伙伴 (partner)'); console.log('🤝 合作伙伴ID (partnerId):', partnerId); } else if (employeeId) { // 员工二维码(包括老板和员工) sourceType = 'sales'; sourceId = employeeId; console.log('👤 [来源识别] 来源类型: 员工展业 (sales)'); console.log('👤 员工ID (employeeId):', employeeId); } else if (ownerId) { // 老板二维码 sourceType = 'sales'; sourceId = ownerId; console.log('👔 [来源识别] 来源类型: 老板展业 (sales)'); console.log('👔 老板ID (ownerId):', ownerId); } else if (userId) { // 推广员二维码 sourceType = 'promoter'; sourceId = userId; console.log('📢 [来源识别] 来源类型: 推广员展业 (promoter)'); console.log('📢 推广员ID (userId):', userId); } else { // 其他情况(产品码、案例码、店铺分享等) sourceType = 'scan'; console.log('🏬 [来源识别] 来源类型: 通用扫码 (scan)'); } if (productId) { console.log('📦 [目标内容] 产品ID (productId):', productId); } if (caseId) { console.log('📸 [目标内容] 案例ID (caseId):', caseId); } if (finalPlanId) { console.log('📋 [目标内容] 方案ID (planId/schemeId):', finalPlanId); console.log('👤 [分享人] 分享用户ID (shareUserId):', shareUserId || '无'); } console.log('🏪 ==========================================='); // 保存到临时存储,用于后续跳转和统计 console.log('💾 [保存扫码信息] 开始保存到本地存储...'); wx.setStorageSync('scan_storeId', storeId); wx.setStorageSync('scan_scanCount', scanCount || '0'); wx.setStorageSync('scan_sourceType', sourceType); wx.setStorageSync('scan_sourceId', sourceId || ''); wx.setStorageSync('scan_ownerId', ownerId || ''); wx.setStorageSync('scan_employeeId', employeeId || ''); wx.setStorageSync('scan_partnerId', partnerId || ''); wx.setStorageSync('scan_userId', userId || ''); if (productId) { wx.setStorageSync('scan_productId', productId); } if (caseId) { wx.setStorageSync('scan_caseId', caseId); } if (finalPlanId) { wx.setStorageSync('scan_planId', finalPlanId); if (shareUserId) { wx.setStorageSync('scan_shareUserId', shareUserId); } } wx.setStorageSync('need_scan_redirect', true); console.log('✅ [保存完成] 扫码信息已保存'); console.log('📋 [保存内容摘要]:'); console.log(' - 来源类型:', sourceType); console.log(' - 来源ID:', sourceId || '无'); console.log(' - 店铺ID:', storeId); console.log(' - 需要跳转:', true); } else if (partnerId) { // 理论上异业二维码现在也必须带 storeId,这里仅作为兜底保护 console.warn('⚠️ 检测到异业合作伙伴二维码缺少 storeId,无法确定门店,请检查二维码生成逻辑'); } }, /** * 判断是否需要跳转到扫码统计页面 */ shouldRedirectToScanPage() { return wx.getStorageSync('need_scan_redirect') === true; }, /** * 跳转到扫码对应的店铺页面 * 根据 storeId 跳转到对应店铺,同时记录所有来源信息用于统计 */ async redirectToScanPage() { try { console.log('🚀 ==========================================='); console.log('🚀 [跳转处理] 开始处理扫码跳转'); console.log('🚀 ==========================================='); // 获取所有扫码相关参数 const storeId = wx.getStorageSync('scan_storeId'); const scanCount = wx.getStorageSync('scan_scanCount'); const sourceType = wx.getStorageSync('scan_sourceType'); const sourceId = wx.getStorageSync('scan_sourceId'); const ownerId = wx.getStorageSync('scan_ownerId'); const employeeId = wx.getStorageSync('scan_employeeId'); const partnerId = wx.getStorageSync('scan_partnerId'); const userId = wx.getStorageSync('scan_userId'); const productId = wx.getStorageSync('scan_productId'); const caseId = wx.getStorageSync('scan_caseId'); const planId = wx.getStorageSync('scan_planId'); const shareUserId = wx.getStorageSync('scan_shareUserId'); console.log('📖 [读取存储] 从本地存储读取扫码信息:'); console.log(' - storeId:', storeId || '无'); console.log(' - sourceType:', sourceType || '无'); console.log(' - sourceId:', sourceId || '无'); console.log(' - scanCount:', scanCount || '无'); console.log(' - ownerId:', ownerId || '无'); console.log(' - employeeId:', employeeId || '无'); console.log(' - partnerId:', partnerId || '无'); console.log(' - userId:', userId || '无'); console.log(' - productId:', productId || '无'); console.log(' - caseId:', caseId || '无'); console.log(' - planId:', planId || '无'); console.log(' - shareUserId:', shareUserId || '无'); // 清除临时存储 console.log('🧹 [清理存储] 清除临时扫码信息...'); wx.removeStorageSync('scan_storeId'); wx.removeStorageSync('scan_scanCount'); wx.removeStorageSync('scan_sourceType'); wx.removeStorageSync('scan_sourceId'); wx.removeStorageSync('scan_ownerId'); wx.removeStorageSync('scan_employeeId'); wx.removeStorageSync('scan_partnerId'); wx.removeStorageSync('scan_userId'); wx.removeStorageSync('scan_productId'); wx.removeStorageSync('scan_caseId'); wx.removeStorageSync('scan_planId'); wx.removeStorageSync('scan_shareUserId'); wx.removeStorageSync('need_scan_redirect'); console.log('✅ [清理完成] 临时存储已清除'); if (!storeId) { console.error('❌ [错误] 缺少 storeId 参数,无法跳转'); return; } console.log('📊 ==========================================='); console.log('📊 [扫码来源汇总] 扫码进入店铺'); console.log('📊 店铺 ID:', storeId); console.log('📊 来源类型:', sourceType); console.log('📊 来源 ID:', sourceId || '无'); console.log('📊 扫码次数:', scanCount); if (ownerId) console.log('📊 老板 ID:', ownerId); if (employeeId) console.log('📊 员工 ID:', employeeId); if (partnerId) console.log('📊 合作伙伴 ID:', partnerId); if (userId) console.log('📊 推广员 ID:', userId); if (productId) console.log('📊 产品 ID:', productId); if (caseId) console.log('📊 案例 ID:', caseId); if (planId) { console.log('📊 方案 ID:', planId); console.log('📊 分享用户 ID:', shareUserId || '无'); } console.log('📊 ==========================================='); // 设置店铺 ID 到全局和本地存储 wx.setStorageSync('storeId', storeId); getApp().globalData.storeId = storeId; // 保存来源信息到本地存储(用于后续统计或绑定关系) if (sourceId) { wx.setStorageSync('scan_from_sourceType', sourceType); wx.setStorageSync('scan_from_sourceId', sourceId); console.log('✅ 已记录来源信息:', sourceType, sourceId); } // 检查用户是否已登录 const currentUser = Parse.User.current(); console.log('👤 [登录检查] 检查用户登录状态...'); console.log(' - 当前用户:', currentUser ? currentUser.id : '未登录'); console.log(' - 手机号:', currentUser?.get('mobile') || '无'); if (currentUser) { // 用户已登录,立即记录扫码统计 console.log('✅ [已登录] 用户已登录,立即记录扫码统计'); await this.recordScanStatistics({ storeId, sourceType, sourceId, ownerId, employeeId, partnerId, userId, productId, scanCount, scanTimestamp: Date.now() }); } else { // 用户未登录,保存扫码信息,等登录后再记录 console.log('⚠️ [未登录] 用户未登录,保存扫码信息待登录后记录'); const pendingData = { storeId, sourceType, sourceId, ownerId, employeeId, partnerId, userId, productId, scanCount, timestamp: Date.now() }; console.log('💾 [待处理记录] 保存内容:', JSON.stringify(pendingData, null, 2)); wx.setStorageSync('pending_scan_record', pendingData); console.log('✅ [保存成功] 待处理扫码记录已保存'); } // 如果有产品ID,直接跳转到产品详情的 H5 页面 if (productId) { console.log('🎯 检测到产品ID,跳转到产品详情页'); await this.redirectToProductDetail(storeId, productId, partnerId); return; } // 如果有案例ID,直接跳转到案例详情的 H5 页面 if (caseId) { console.log('🎯 检测到案例ID,跳转到案例详情页'); await this.redirectToCaseDetail(storeId, caseId, partnerId); return; } // 如果有方案ID,跳转到门店首页并传递方案ID if (planId) { console.log('🎯 检测到方案ID,跳转到门店首页展示方案'); await this.redirectToStoreWithPlan(storeId, planId, shareUserId, partnerId); return; } // 获取默认首页路径并跳转 let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath; url += `?storeId=${storeId}`; // 如果有产品ID,添加到URL参数中 if (productId) { url += `&productId=${productId}`; } // 如果有异业合作伙伴ID,添加到URL参数中传回web-view if (partnerId) { url += `&partnerId=${partnerId}`; } console.log('✅ 跳转到店铺页面:', url); wx.redirectTo({ url: url, fail: (err) => { console.error('❌ 跳转失败:', err); // 如果 redirectTo 失败,尝试 reLaunch wx.reLaunch({ url: url, fail: (err2) => { console.error('❌ reLaunch 也失败:', err2); } }); } }); } catch (error) { console.error('❌ 跳转到店铺页面失败:', error); } }, /** * 跳转到活动页面 */ async redirectToActivityPage() { try { const activityId = wx.getStorageSync('scan_activityId'); // 清除临时存储 wx.removeStorageSync('scan_activityId'); wx.removeStorageSync('need_activity_redirect'); if (!activityId) { console.error('❌ 缺少 activityId 参数,无法跳转'); return; } console.log('==========================================='); console.log('======= 扫码进入活动页面 ======='); console.log('活动 ID (activityId):', activityId); console.log('==========================================='); // 保存活动ID wx.setStorageSync('activityId', activityId); // 获取默认首页路径并跳转(活动页面可能需要根据实际路由调整) let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath; url += `?activityId=${activityId}`; console.log('✅ 跳转到活动页面:', url); wx.redirectTo({ url: url, fail: (err) => { console.error('❌ 跳转失败:', err); wx.reLaunch({ url: url, fail: (err2) => { console.error('❌ reLaunch 也失败:', err2); } }); } }); } catch (error) { console.error('❌ 跳转到活动页面失败:', error); } }, /** * 跳转到产品详情的 H5 页面 * @param {string} storeId - 店铺 ID * @param {string} productId - 产品 ID * @param {string} partnerId - 可选的合作伙伴 ID */ async redirectToProductDetail(storeId, productId, partnerId = null) { try { console.log('==========================================='); console.log('======= 跳转到产品详情页 ======='); console.log('店铺 ID:', storeId); console.log('产品 ID:', productId); if (partnerId) console.log('合作伙伴 ID:', partnerId); console.log('==========================================='); const currentUser = Parse.User.current(); const token = currentUser ? currentUser.getSessionToken() : null; // 构建产品详情的 H5 URL(不要在这里编码,后面统一编码) let h5Url = `https://app.fmode.cn/dev/pobingfeng/owner/nav/products?storeId=${storeId}`; // 如果有 token,添加到 URL if (token) { h5Url += `&token=${token}`; } else { // 如果没有 token,使用游客模式 h5Url += `&guestMode=true`; } // 添加产品ID(不要在这里编码,避免双重编码) h5Url += `&productId=${productId}`; // 如果有合作伙伴ID,也添加到URL中 if (partnerId) { h5Url += `&partnerId=${partnerId}`; } console.log('🌐 H5 URL:', h5Url); // 编码 URL(统一在这里编码一次) const encodedUrl = encodeURIComponent(h5Url); // 构建 web-view 页面路径 let webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&storeId=${storeId}`; console.log('📄 web-view 页面路径:', webViewPath.substring(0, 100) + '...'); console.log('==========================================='); wx.redirectTo({ url: webViewPath, success: () => { console.log('✅ 跳转到产品详情页成功'); }, fail: (err) => { console.error('❌ 跳转失败:', err); // 如果 redirectTo 失败,尝试 reLaunch wx.reLaunch({ url: webViewPath, fail: (err2) => { console.error('❌ reLaunch 也失败:', err2); } }); } }); } catch (error) { console.error('❌ 跳转到产品详情页失败:', error); } }, /** * 跳转到案例详情的 H5 页面 * @param {string} storeId - 店铺 ID * @param {string} caseId - 案例 ID * @param {string} partnerId - 可选的合作伙伴 ID */ async redirectToCaseDetail(storeId, caseId, partnerId = null) { try { console.log('==========================================='); console.log('======= 跳转到案例详情页 ======='); console.log('店铺 ID:', storeId); console.log('案例 ID:', caseId); if (partnerId) console.log('合作伙伴 ID:', partnerId); console.log('==========================================='); const currentUser = Parse.User.current(); const token = currentUser ? currentUser.getSessionToken() : null; // 构建案例详情的 H5 URL(不要在这里编码,后面统一编码) let h5Url = `https://app.fmode.cn/dev/pobingfeng/owner/nav/cases?storeId=${storeId}`; // 如果有 token,添加到 URL if (token) { h5Url += `&token=${token}`; } else { // 如果没有 token,使用游客模式 h5Url += `&guestMode=true`; } // 添加案例ID(不要在这里编码,避免双重编码) h5Url += `&caseId=${caseId}`; // 如果有合作伙伴ID,也添加到URL中 if (partnerId) { h5Url += `&partnerId=${partnerId}`; } console.log('🌐 H5 URL:', h5Url); // 编码 URL(统一在这里编码一次) const encodedUrl = encodeURIComponent(h5Url); // 构建 web-view 页面路径 let webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&storeId=${storeId}`; console.log('📄 web-view 页面路径:', webViewPath.substring(0, 100) + '...'); console.log('==========================================='); wx.redirectTo({ url: webViewPath, success: () => { console.log('✅ 跳转到案例详情页成功'); }, fail: (err) => { console.error('❌ 跳转失败:', err); // 如果 redirectTo 失败,尝试 reLaunch wx.reLaunch({ url: webViewPath, fail: (err2) => { console.error('❌ reLaunch 也失败:', err2); } }); } }); } catch (error) { console.error('❌ 跳转到案例详情页失败:', error); } }, /** * 跳转到门店首页并展示方案 * @param {string} storeId - 店铺 ID * @param {string} planId - 方案 ID * @param {string} shareUserId - 分享方案的用户 ID * @param {string} partnerId - 可选的合作伙伴 ID */ async redirectToStoreWithPlan(storeId, planId, shareUserId = null, partnerId = null) { try { console.log('==========================================='); console.log('======= 跳转到门店首页展示方案 ======='); console.log('店铺 ID:', storeId); console.log('方案 ID:', planId); if (shareUserId) console.log('分享用户 ID:', shareUserId); if (partnerId) console.log('合作伙伴 ID:', partnerId); console.log('==========================================='); const currentUser = Parse.User.current(); const token = currentUser ? currentUser.getSessionToken() : null; // 构建门店首页的 H5 URL,带上方案ID let h5Url = `https://app.fmode.cn/dev/pobingfeng/owner/nav/home?storeId=${storeId}`; // 如果有 token,添加到 URL if (token) { h5Url += `&token=${token}`; } else { // 如果没有 token,使用游客模式 h5Url += `&guestMode=true`; } // 添加方案ID(使用planId参数名) h5Url += `&planId=${planId}`; // 如果有分享用户ID,也添加到URL中 if (shareUserId) { h5Url += `&shareUserId=${shareUserId}`; } // 如果有合作伙伴ID,也添加到URL中 if (partnerId) { h5Url += `&partnerId=${partnerId}`; } console.log('🌐 H5 URL:', h5Url); // 编码 URL const encodedUrl = encodeURIComponent(h5Url); // 构建 web-view 页面路径 let webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&storeId=${storeId}`; console.log('📄 web-view 页面路径:', webViewPath.substring(0, 100) + '...'); console.log('==========================================='); wx.redirectTo({ url: webViewPath, success: () => { console.log('✅ 跳转到门店首页展示方案成功'); }, fail: (err) => { console.error('❌ 跳转失败:', err); // 如果 redirectTo 失败,尝试 reLaunch wx.reLaunch({ url: webViewPath, fail: (err2) => { console.error('❌ reLaunch 也失败:', err2); } }); } }); } catch (error) { console.error('❌ 跳转到门店首页展示方案失败:', error); } }, /** * 记录扫码统计信息 * @param {Object} params - 扫码参数对象 * @param {string} params.storeId - 店铺 ID * @param {string} params.sourceType - 来源类型 (employee/owner/partner/promoter/store) * @param {string} params.sourceId - 来源 ID * @param {string} params.ownerId - 老板 ID * @param {string} params.employeeId - 员工 ID * @param {string} params.partnerId - 合作伙伴 ID * @param {string} params.userId - 推广员 ID * @param {string} params.productId - 产品 ID * @param {string} params.scanCount - 扫码次数 */ async recordScanStatistics(params) { try { console.log('📝 ==========================================='); console.log('📝 [扫码统计] 开始记录扫码统计'); console.log('📝 ==========================================='); const { storeId, sourceType, sourceId, ownerId, employeeId, partnerId, userId, productId, scanCount, scanTimestamp } = params; console.log('📋 [统计参数] 接收到的参数:'); console.log(' - storeId:', storeId); console.log(' - sourceType:', sourceType); console.log(' - sourceId:', sourceId || '无'); console.log(' - ownerId:', ownerId || '无'); console.log(' - employeeId:', employeeId || '无'); console.log(' - partnerId:', partnerId || '无'); console.log(' - userId:', userId || '无'); console.log(' - productId:', productId || '无'); console.log(' - scanCount:', scanCount || '0'); // 检查用户是否已登录 const currentUser = Parse.User.current(); if (!currentUser) { console.log('⚠️ [未登录] 用户未登录,不记录扫码统计'); return; } console.log('👤 [用户信息] 当前登录用户:'); console.log(' - 用户ID:', currentUser.id); console.log(' - 手机号:', currentUser.get('mobile') || '无'); // 如果没有来源信息,不记录统计 if (!sourceId && !ownerId && !employeeId && !partnerId && !userId) { console.log('ℹ️ [跳过] 无来源信息,跳过统计记录'); return; } const hasMobile = !!currentUser.get('mobile'); const scanTs = typeof scanTimestamp === 'number' && scanTimestamp > 0 ? scanTimestamp : Date.now(); if (partnerId && !hasMobile) { const pendingData = { storeId, sourceType, sourceId, ownerId, employeeId, partnerId, userId, productId, scanCount, timestamp: scanTs }; try { const existingPending = wx.getStorageSync('pending_scan_record'); if (!existingPending || existingPending.partnerId !== partnerId || existingPending.storeId !== storeId) { wx.setStorageSync('pending_scan_record', pendingData); } } catch (e) { try { wx.setStorageSync('pending_scan_record', pendingData); } catch (e2) {} } console.log('⏳ [异业扫码] 用户未绑定手机号,延后到注册登录后再统计'); return; } console.log('✅ [开始记录] 用户已登录且有来源信息,开始记录'); // 创建扫码记录 const ScanRecord = Parse.Object.extend('ScanRecord'); const record = new ScanRecord(); record.set('company', { __type: 'Pointer', className: 'Company', objectId: getApp().globalData.company }); record.set('storeId', storeId); record.set('sourceType', sourceType || 'unknown'); record.set('scanCount', parseInt(scanCount) || 0); record.set('scanTime', new Date()); console.log('📦 [记录内容] 准备保存到 ScanRecord 表:'); console.log(' - company:', getApp().globalData.company); console.log(' - storeId:', storeId); console.log(' - sourceType:', sourceType || 'unknown'); console.log(' - scanCount:', parseInt(scanCount) || 0); console.log(' - scanTime:', new Date().toISOString()); // 根据来源类型设置对应的ID if (ownerId) { record.set('ownerId', ownerId); console.log(' - ownerId:', ownerId); } if (employeeId) { record.set('employeeId', employeeId); console.log(' - employeeId:', employeeId); } if (partnerId) { record.set('partnerId', partnerId); console.log(' - partnerId:', partnerId); } if (userId) { record.set('userId', userId); console.log(' - userId:', userId); } if (productId) { record.set('productId', productId); console.log(' - productId:', productId); } // 记录扫码用户 record.set('user', { __type: 'Pointer', className: '_User', objectId: currentUser.id }); console.log(' - user:', currentUser.id); await record.save(); console.log('✅ [保存成功] 扫码记录已保存到数据库'); console.log(' - 记录ID:', record.id); // 如果存在异业合作伙伴ID,处理异业绑定和扫码次数 if (partnerId) { try { console.log('🤝 ==========================================='); console.log('🤝 [异业处理] 开始处理异业扫码逻辑'); console.log('🤝 用户ID:', currentUser.id); console.log('🤝 异业ID:', partnerId); console.log('🤝 门店ID:', storeId); console.log('🤝 ==========================================='); // 检查用户是否已经绑定了异业合作伙伴 const userBoundPartner = currentUser.get('Partner'); const userBoundPartnerId = userBoundPartner ? userBoundPartner.id : null; console.log('🔍 [绑定检查] 用户已绑定的异业ID:', userBoundPartnerId || '无'); // 规则1:如果用户已绑定其他异业,不处理当前异业的扫码 if (userBoundPartnerId && userBoundPartnerId !== partnerId) { console.log('⚠️ [规则1] 用户已绑定其他异业,不处理当前异业的扫码'); console.log(' - 已绑定异业:', userBoundPartnerId); console.log(' - 当前扫码异业:', partnerId); console.log('🤝 ==========================================='); return; } // 规则2:检查该用户在该门店是否已经扫过该异业的码 console.log('🔍 [重复检查] 检查是否已扫过该异业的码...'); const ScanRecord = Parse.Object.extend('ScanRecord'); const scanQuery = new Parse.Query(ScanRecord); scanQuery.equalTo('user', { __type: 'Pointer', className: '_User', objectId: currentUser.id }); scanQuery.equalTo('partnerId', partnerId); scanQuery.equalTo('storeId', storeId); scanQuery.equalTo('partnerNewUserCounted', true); const existingScan = await scanQuery.first(); if (existingScan) { console.log('⚠️ [规则2] 该用户在该门店已计入“异业新用户扫码”,不重复计数'); console.log(' - 计入时间:', existingScan.get('scanTime')); console.log(' - 记录ID:', existingScan.id); console.log('🤝 ==========================================='); return; } let partnerNewUserQualified = false; try { const createdAtMs = currentUser.createdAt instanceof Date ? currentUser.createdAt.getTime() : 0; const diffMs = createdAtMs ? Math.abs(scanTs - createdAtMs) : Number.MAX_SAFE_INTEGER; partnerNewUserQualified = diffMs <= 10 * 60 * 1000; } catch (e) { partnerNewUserQualified = false; } if (!partnerNewUserQualified) { console.log('ℹ️ [规则2] 非“扫码注册登录的新用户”,不计入异业扫码次数'); console.log('🤝 ==========================================='); return; } record.set('partnerNewUserCounted', true); console.log('✅ [首次计入] 该用户在该门店首次计入“异业新用户扫码”'); // 规则3:如果用户还没有绑定异业,绑定当前异业 if (!userBoundPartnerId) { console.log('🔗 [规则3] 用户首次扫异业码,开始绑定异业合作伙伴'); console.log(' - 异业ID:', partnerId); currentUser.set('Partner', { __type: 'Pointer', className: 'Partner', objectId: partnerId }); await currentUser.save(); console.log('✅ [绑定成功] 异业绑定成功'); } else { console.log('ℹ️ [已绑定] 用户已绑定该异业,无需重复绑定'); } // 规则4:为该异业在该门店增加扫码次数 console.log('📊 [规则4] 开始更新异业扫码次数...'); const Partner = Parse.Object.extend('Partner'); const partnerQuery = new Parse.Query(Partner); const partnerObj = await partnerQuery.get(partnerId); if (partnerObj) { console.log('📋 [异业信息] 找到异业合作伙伴记录'); console.log(' - 异业ID:', partnerObj.id); console.log(' - 异业名称:', partnerObj.get('name') || '未设置'); // 获取或初始化门店扫码次数映射 let storeScans = partnerObj.get('storeScans') || {}; const oldCount = storeScans[storeId] || 0; // 为该门店的扫码次数 +1 storeScans[storeId] = oldCount + 1; partnerObj.set('storeScans', storeScans); // 同时更新总扫码次数 const oldTotalCount = partnerObj.get('scanCount') || 0; partnerObj.increment('scanCount', 1); await partnerObj.save(); console.log('✅ [更新成功] 异业扫码次数已更新'); console.log(' - 门店ID:', storeId); console.log(' - 该门店扫码次数: %d → %d', oldCount, storeScans[storeId]); console.log(' - 总扫码次数: %d → %d', oldTotalCount, oldTotalCount + 1); } else { console.error('❌ [错误] 未找到异业合作伙伴记录'); } if (record && record.get && record.get('partnerNewUserCounted') === true) { await record.save(); } console.log('🤝 ==========================================='); } catch (e) { console.error('❌ [异业处理失败] 处理异业合作伙伴绑定失败:', e); console.error(' - 错误信息:', e.message); console.error(' - 错误堆栈:', e.stack); console.log('🤝 ==========================================='); } } } catch (error) { console.warn('⚠️ 保存扫码记录失败:', error); // 不影响主流程 } } }); function normalizeCnMobile(value) { if (value === null || value === undefined) return ''; const digits = String(value).replace(/\D/g, ''); if (!digits) return ''; const last11 = digits.length >= 11 ? digits.slice(-11) : digits; if (/^1\d{10}$/.test(last11)) return last11; if (/^1\d{10}$/.test(digits)) return digits; return ''; } function addCnMobileToSet(set, value) { const m = normalizeCnMobile(value); if (m) set.add(m); } function addCnMobilesFromValue(set, value) { if (!value) return; if (Array.isArray(value)) { value.forEach((v) => addCnMobileToSet(set, v)); return; } addCnMobileToSet(set, value); } function addCnMobilesFromParseObject(set, parseObj, keys) { if (!parseObj || typeof parseObj.get !== 'function') return; keys.forEach((k) => { try { const v = parseObj.get(k); addCnMobilesFromValue(set, v); } catch (e) {} }); } async function addCnMobileFromUserPointer(set, Parse, pointer) { if (!pointer) return; if (typeof pointer.get === 'function') { addCnMobileToSet(set, pointer.get('mobile')); addCnMobileToSet(set, pointer.get('phone')); } const pointerId = pointer && pointer.id ? pointer.id : null; if (!pointerId) return; try { const q = new Parse.Query('_User'); q.select('mobile'); const u = await q.get(pointerId); if (u) addCnMobileToSet(set, u.get('mobile')); } catch (e) {} } function collectUserMobiles(currentUser) { const set = new Set(); addCnMobileToSet(set, currentUser && typeof currentUser.get === 'function' ? currentUser.get('mobile') : null); addCnMobileToSet(set, currentUser && typeof currentUser.get === 'function' ? currentUser.get('phone') : null); try { const storageMobile = wx.getStorageSync('user_mobile'); addCnMobileToSet(set, storageMobile); } catch (e) {} return Array.from(set); } function setSample(set, limit) { const arr = []; let i = 0; for (const v of set) { arr.push(v); i++; if (i >= (limit || 5)) break; } return arr; } function intersectArrSet(arr, set) { return arr.filter((v) => set.has(v)); } async function collectPartnerPhonesForStore(Parse, storeId) { const phones = new Set(); const mobiles = new Set(); try { if (!storeId) return { phones, mobiles }; const currentUser = Parse.User.current(); const sessionToken = currentUser && typeof currentUser.getSessionToken === 'function' ? currentUser.getSessionToken() : null; const requestOptions = sessionToken ? { sessionToken } : undefined; const storePointer = new Parse.Object('ShopStore'); storePointer.id = storeId; const q = new Parse.Query('Partner'); q.equalTo('store', storePointer); q.limit(200); q.select('phone', 'mobile'); const list = await q.find(requestOptions); if (list && list.length) { list.forEach((p) => { addCnMobileToSet(phones, p.get('phone')); addCnMobileToSet(mobiles, p.get('mobile')); }); } } catch (e) { console.warn('⚠️ [traffic] Partner 查询失败:', e?.code, e?.message || e); } return { phones, mobiles }; } async function collectTrafficWhitelistMobiles(Parse, storeId) { const set = new Set(); const mobileKeys = [ 'mobile', 'phone' ]; const userPointerKeys = [ 'owner', 'boss', 'admin', 'manager', 'user', 'ownerUser', 'bossUser', 'adminUser', 'managerUser', 'createdBy' ]; try { if (storeId) { const q = new Parse.Query('ShopStore'); q.equalTo('objectId', storeId); q.select(...mobileKeys, ...userPointerKeys); userPointerKeys.forEach((k) => q.include(k)); const storeObj = await q.first(); if (storeObj) { addCnMobilesFromParseObject(set, storeObj, mobileKeys); for (const k of userPointerKeys) { await addCnMobileFromUserPointer(set, Parse, storeObj.get(k)); } } } } catch (e) {} const userStaffPhoneKeys = ['mobile', 'phone']; try { if (storeId) { const flags = getApp().globalData || {}; const disabled = !!flags.disableUserStaffQuery || wx.getStorageSync('disableUserStaffQuery') === true; if (disabled) { console.log('⚠️ [traffic] userStaff 查询已禁用(检测到类不存在/无权限)'); } else { const currentUser = Parse.User.current(); const sessionToken = currentUser && typeof currentUser.getSessionToken === 'function' ? currentUser.getSessionToken() : null; const requestOptions = sessionToken ? { sessionToken } : undefined; const companyId = getApp().globalData.company; const companyPointer = new Parse.Object('Company'); companyPointer.id = companyId; const q = new Parse.Query('UserStaff'); q.equalTo('company', companyPointer); q.limit(200); q.select(...userStaffPhoneKeys); const list = await q.find(requestOptions); if (list && list.length) { list.forEach((staff) => addCnMobilesFromParseObject(set, staff, userStaffPhoneKeys)); } } } } catch (e) { console.warn('⚠️ [traffic] userStaff 查询失败:', e?.code, e?.message || e); try { const msg = (e?.message || '').toLowerCase(); if ( e?.code === 119 || msg.includes('non-existent class') || msg.includes('class: userstaff') || msg.includes('class: userstaff') || msg.includes('class: userstaff') ) { getApp().globalData.disableUserStaffQuery = true; wx.setStorageSync('disableUserStaffQuery', true); console.warn('⚠️ [traffic] 已禁用后续 userStaff 查询(类不存在或无权限)'); } } catch (_) {} } const partnerPhoneKeys = ['mobile', 'phone']; try { if (storeId) { const currentUser = Parse.User.current(); const sessionToken = currentUser && typeof currentUser.getSessionToken === 'function' ? currentUser.getSessionToken() : null; const requestOptions = sessionToken ? { sessionToken } : undefined; const storePointer = new Parse.Object('ShopStore'); storePointer.id = storeId; const q = new Parse.Query('Partner'); q.equalTo('store', storePointer); q.limit(200); q.select(...partnerPhoneKeys); const list = await q.find(requestOptions); if (list && list.length) { list.forEach((p) => addCnMobilesFromParseObject(set, p, partnerPhoneKeys)); } } } catch (e) { console.warn('⚠️ [traffic] Partner 查询失败:', e?.code, e?.message || e); } return set; } async function isTrafficExemptForStore(Parse, storeId, currentUser) { try { if (!currentUser) return false; if (currentUser.get('trafficExempt') === true) return true; const userNumbers = collectUserMobiles(currentUser); const whitelist = await collectTrafficWhitelistMobiles(Parse, storeId); const partnerSets = await collectPartnerPhonesForStore(Parse, storeId); console.log('🧾 [traffic] 白名单判定开始:', { storeId, userId: currentUser.id, userNumbers }); console.log('🧾 [traffic] Partner 集合:', { phoneCount: partnerSets.phones.size, phoneSample: setSample(partnerSets.phones, 5), mobileCount: partnerSets.mobiles.size, mobileSample: setSample(partnerSets.mobiles, 5) }); const partnerPhoneMatches = intersectArrSet(userNumbers, partnerSets.phones); if (partnerPhoneMatches.length) { console.log('🧾 [traffic] 命中 Partner.phone:', { matches: partnerPhoneMatches }); return true; } const partnerMobileMatches = intersectArrSet(userNumbers, partnerSets.mobiles); if (partnerMobileMatches.length) { console.log('🧾 [traffic] 命中 Partner.mobile:', { matches: partnerMobileMatches }); return true; } console.log('🧾 [traffic] 聚合白名单:', { size: whitelist.size, sample: setSample(whitelist, 10) }); const whitelistMatches = intersectArrSet(userNumbers, whitelist); if (whitelistMatches.length) { console.log('🧾 [traffic] 命中聚合白名单:', { matches: whitelistMatches }); return true; } console.log('🧾 [traffic] 白名单未命中'); return false; } catch (e) { console.warn('⚠️ [traffic] 白名单判定失败,按非白名单处理:', e?.message || e); return false; } } async function decrementStoreTrafficImpl(Parse, storeId) { const currentUser = Parse.User.current(); const sessionToken = currentUser && typeof currentUser.getSessionToken === 'function' ? currentUser.getSessionToken() : null; const requestOptions = sessionToken ? { sessionToken } : undefined; console.log('🧾 [traffic] 开始扣减 ShopStore.traffic'); console.log('🧾 [traffic] storeId:', storeId); console.log('🧾 [traffic] userId:', currentUser ? currentUser.id : '无用户'); console.log('🧾 [traffic] hasSessionToken:', sessionToken ? 'true' : 'false'); const storeQuery = new Parse.Query('ShopStore'); const store = await storeQuery.get(storeId, requestOptions); if (!store) { console.error('🧾 [traffic] 未找到门店记录, storeId:', storeId); throw new Error('未找到门店记录'); } const trafficValue = store.get('traffic'); const trafficNumber = Number(trafficValue); const traffic = Number.isFinite(trafficNumber) ? trafficNumber : 0; const next = Math.max(traffic - 1, 0); console.log('🧾 [traffic] before trafficValue:', trafficValue); console.log('🧾 [traffic] parsed traffic:', traffic); console.log('🧾 [traffic] next:', next); if (next !== traffic || trafficValue === undefined) { store.set('traffic', next); const saved = await store.save(null, requestOptions); console.log('🧾 [traffic] 保存成功 storeObjectId:', saved && saved.id ? saved.id : store.id); } console.log('🧾 [traffic] 结束扣减 ShopStore.traffic'); } async function isInternalVisit(Parse, storeId, currentUser, ids) { try { if (!currentUser) return false; const { ownerId, employeeId, partnerId } = ids || {}; const roles = Array.isArray(currentUser.get('roles')) ? currentUser.get('roles') : []; const roleSet = new Set(roles.map(r => String(r).toLowerCase())); const isOwnerRole = roleSet.has('owner') || roleSet.has('boss'); const isEmployeeRole = roleSet.has('employee') || roleSet.has('staff') || roleSet.has('sales'); const isAdminStaffRole = roleSet.has('userstaff') || roleSet.has('admin_staff') || roleSet.has('manager_staff'); const isPartnerRole = roleSet.has('partner') || roleSet.has('partner_admin') || roleSet.has('partner_staff'); if (partnerId && isPartnerRole) return true; if (employeeId && isEmployeeRole) return true; if (isAdminStaffRole) return true; const storeQuery = new Parse.Query('ShopStore'); const store = await storeQuery.get(storeId); const storeOwner = store ? store.get('user') : null; if (storeOwner && storeOwner.id === currentUser.id) return true; if (ownerId && isOwnerRole) return true; if (employeeId && currentUser.id === employeeId) return true; if (ownerId && currentUser.id === ownerId) return true; return false; } catch (e) { return false; } }