index.js 65 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763
  1. // var Parse = getApp().Parse;
  2. // var app = getApp()
  3. // const { wxLogin } = require('./utils/login')
  4. const CONFIG = require("config.js");
  5. let config = {
  6. appid: CONFIG.default.appid,
  7. company: CONFIG.default.company,
  8. rootPage: CONFIG.default.rootPage,
  9. }
  10. const plugin = requirePlugin('fm-plugin')
  11. const { Parse, checkAuth } = plugin
  12. Page({
  13. /**
  14. * 页面的初始数据
  15. */
  16. data: {
  17. splashUrl: wx.getStorageSync("enabledOptions")[0],
  18. loading:true
  19. },
  20. /**
  21. * 生命周期函数--监听页面加载
  22. */
  23. onLoad: async function (options) {
  24. // 拦截插件的"登录信息已过期"提示
  25. const originalShowModal = wx.showModal;
  26. wx.showModal = function(options) {
  27. // 如果是"登录信息已过期"的提示,改为友好的重新登录提示
  28. if (options.content && options.content.includes('登录信息过期')) {
  29. console.log('⚠️ 拦截到"登录信息已过期"提示,改为友好提示');
  30. return originalShowModal.call(this, {
  31. title: '提示',
  32. content: '登录状态异常,是否重新登录?',
  33. showCancel: true,
  34. cancelText: '取消',
  35. confirmText: '重新登录',
  36. success: (result) => {
  37. if (result.confirm) {
  38. // 清除登录状态
  39. wx.removeStorageSync("sessionToken");
  40. wx.removeStorageSync("userLogin");
  41. // 重新加载首页
  42. const rootPage = getApp().globalData.rootPage || getApp().globalData.defaultTabBar?.list?.[0]?.pagePath || '/pages/index/index';
  43. wx.reLaunch({
  44. url: rootPage
  45. });
  46. }
  47. // 调用原始的 success 回调
  48. if (options.success) {
  49. options.success(result);
  50. }
  51. }
  52. });
  53. }
  54. // 其他提示正常显示
  55. return originalShowModal.call(this, options);
  56. };
  57. wx.login({
  58. success: function (res) {
  59. if (res.code) {
  60. console.log(res);
  61. // wx.request({
  62. // url: "https://server.fmode.cn/api/wxapp/auth_wxapp",
  63. // data: {
  64. // c: getApp().globalData.company,
  65. // code: res.code,
  66. // },
  67. // async success(res) {
  68. // wx.setStorageSync("userInfo", res.data);
  69. // resolve(res)
  70. // },
  71. // });
  72. }
  73. },
  74. fail: function (err) {
  75. wx.showToast({
  76. title: '服务器繁忙,请稍后重试',
  77. })
  78. }
  79. });
  80. wx.setStorageSync("invite", null);
  81. // 处理扫码链接(options.q 包含完整的URL或activityId)
  82. if (options.q) {
  83. let str = decodeURIComponent(options.q); // 扫描二维码获得的跳转链接
  84. // 兼容一些环境中把 & 编码成 & 的情况,防止参数解析错误(如 scanCount=0&storeId=...)
  85. if (str.indexOf('&') !== -1) {
  86. console.log('🔧 检测到 URL 中包含 &,自动还原为 &');
  87. str = str.replace(/&/g, '&');
  88. }
  89. // 检查是否是员工邀请链接(app.fmode.cn/dev/pobingfeng/manager/staff?invite=xxx)
  90. if (str.includes('app.fmode.cn/dev/pobingfeng/manager/staff')) {
  91. let obj = this.getParaName(str);
  92. if (obj && obj.invite) {
  93. wx.setStorageSync("invite", obj.invite);
  94. console.log('✅ 检测到员工邀请链接,invite:', obj.invite);
  95. }
  96. // 员工邀请链接不需要跳转到店铺,直接返回
  97. this.setData({ options: options });
  98. plugin.init(config, wx.getStorageSync('invite'));
  99. return;
  100. }
  101. // 检查是否是活动海报二维码(activityId作为qrCode参数值)
  102. // 如果str不包含http/https,且看起来像是一个ID,则可能是activityId
  103. if (!str.includes('http://') && !str.includes('https://') && !str.includes('?')) {
  104. // 可能是活动ID,尝试作为activityId处理
  105. if (str && str.length > 0 && !isNaN(str)) {
  106. options.activityId = str;
  107. console.log('✅ 检测到活动海报二维码,activityId:', str);
  108. // 活动二维码需要特殊处理,先保存activityId
  109. wx.setStorageSync("activityId", str);
  110. }
  111. }
  112. // 处理店铺相关的二维码链接(pwa.fmode.cn/gomini/pmd/)
  113. // 从完整URL中提取参数
  114. if (str.includes('pwa.fmode.cn/gomini/pmd') || str.includes('?')) {
  115. let obj = this.getParaName(str);
  116. if (obj && obj.invite) {
  117. wx.setStorageSync("invite", obj.invite);
  118. }
  119. // 将所有参数合并到 options 中
  120. obj && Object.keys(obj).forEach(key=> options[key] = obj[key]);
  121. }
  122. }
  123. // 兼容从编译参数或页面直达传入的 storeId
  124. if (options && options.scene && !options.storeId) {
  125. try {
  126. const sceneStr = decodeURIComponent(options.scene)
  127. if (sceneStr) {
  128. const pairs = sceneStr.split('&')
  129. for (const p of pairs) {
  130. const [k, v] = p.split('=')
  131. if (k === 'storeId' && v) {
  132. options.storeId = v
  133. break
  134. }
  135. }
  136. }
  137. } catch (e) {
  138. console.warn('解析 scene 失败:', e)
  139. }
  140. }
  141. this.setData({
  142. options: options
  143. })
  144. let {
  145. time,
  146. dramaId,
  147. roomId,
  148. orderId,
  149. shopId,
  150. invite,
  151. activityId,
  152. company,
  153. inviteHost,
  154. storeId
  155. } = options
  156. time && wx.setStorageSync("time", time);
  157. dramaId && wx.setStorageSync("dramaId", dramaId);
  158. roomId && wx.setStorageSync("roomId", roomId);
  159. orderId && wx.setStorageSync("orderId", orderId);
  160. shopId && wx.setStorageSync("shopId", shopId);
  161. invite && wx.setStorageSync("invite", invite);
  162. activityId && wx.setStorageSync("activityId", activityId);
  163. inviteHost && wx.setStorageSync("inviteHost", true);
  164. if (storeId) {
  165. // 扫码进入时,强制设置店铺 ID,标记为扫码来源
  166. wx.setStorageSync('storeId', storeId)
  167. getApp().globalData.storeId = storeId
  168. wx.setStorageSync('storeId_from_scan', true)
  169. console.log('✅ 入口页扫码进入,已设置店铺 ID(优先级最高):', storeId)
  170. }
  171. if (company) getApp().globalData.toCompany = true;
  172. // 检查是否是扫码进入(需要统计扫码次数)
  173. this.checkAndHandleScan(options);
  174. plugin.init(config, wx.getStorageSync('invite'))
  175. },
  176. /**
  177. * 生命周期函数--监听页面初次渲染完成
  178. */
  179. onReady: async function () { },
  180. /**
  181. * 生命周期函数--监听页面显示
  182. */
  183. onShow: async function () {
  184. await this.review()
  185. },
  186. async review(force){
  187. try {
  188. let options = this.data.options
  189. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath
  190. if (options) {
  191. let objArr = Object.keys(options)
  192. if (objArr && objArr.length > 0) {
  193. let parms = '?'
  194. objArr.forEach((o, index) => {
  195. if (index > 0) {
  196. parms += '&' + o + '=' + options[o]
  197. } else {
  198. parms += o + '=' + options[o]
  199. }
  200. })
  201. url += parms
  202. }
  203. }
  204. let currentUser = Parse.User.current()
  205. console.log('===========================================');
  206. console.log('======= index.js review 方法 =======');
  207. console.log('当前用户:', currentUser ? currentUser.id : '无');
  208. console.log('用户手机号:', currentUser?.get('mobile') || '无');
  209. console.log('用户名:', currentUser?.get('username') || '无');
  210. console.log('Session Token:', currentUser?.getSessionToken()?.substring(0, 20) || '无');
  211. console.log('userLogin 存储:', wx.getStorageSync('userLogin') || '无');
  212. console.log('force 参数:', force);
  213. console.log('===========================================');
  214. // 查询 Company 的 isPublishing 字段
  215. let isPublishing = false;
  216. try {
  217. const companyQuery = new Parse.Query('Company');
  218. companyQuery.equalTo('objectId', getApp().globalData.company);
  219. companyQuery.select('isPublishing');
  220. try {
  221. const companyObj = await companyQuery.first();
  222. if (companyObj) {
  223. isPublishing = companyObj.get('isPublishing') === true;
  224. console.log('📋 Company isPublishing:', isPublishing);
  225. } else {
  226. console.log('⚠️ 未找到 Company 记录,默认 isPublishing = false');
  227. isPublishing = false;
  228. }
  229. } catch (queryError) {
  230. console.error('❌ 查询 Company 失败(可能 token 无效):', queryError.message);
  231. // 查询失败时,默认为 false(强制登录)
  232. isPublishing = false;
  233. }
  234. // 保存到全局,供其他页面使用
  235. getApp().globalData.isPublishing = isPublishing;
  236. wx.setStorageSync('isPublishing', isPublishing);
  237. } catch (error) {
  238. console.error('❌ 查询 Company isPublishing 失败:', error);
  239. // 查询失败时,默认为 false(强制登录)
  240. isPublishing = false;
  241. }
  242. // 根据 isPublishing 决定是否强制登录
  243. if (!currentUser || force) {
  244. console.log('🔄 开始调用 checkAuth...');
  245. // isPublishing == true 时不强制授权,否则强制授权
  246. const forceAuth = !isPublishing;
  247. console.log('🔐 是否强制登录:', forceAuth);
  248. let r = await checkAuth(forceAuth);
  249. console.log('===========================================');
  250. console.log('======= checkAuth 返回结果 =======');
  251. console.log('返回值:', r);
  252. // 重新获取用户信息
  253. currentUser = Parse.User.current();
  254. console.log('checkAuth 后的用户:', currentUser ? currentUser.id : '无');
  255. console.log('checkAuth 后的手机号:', currentUser?.get('mobile') || '无');
  256. console.log('checkAuth 后的 Session Token:', currentUser?.getSessionToken()?.substring(0, 20) || '无');
  257. console.log('===========================================');
  258. // 如果强制登录但用户未登录,不允许继续访问
  259. if (forceAuth && !r) {
  260. console.log('❌ 强制登录模式,用户未登录,停止访问');
  261. return;
  262. }
  263. // 即使登录失败,也允许继续访问(仅在非强制登录模式下)
  264. if(!r) {
  265. console.log('⚠️ 用户未登录或拒绝授权,允许游客访问');
  266. if (wx.getStorageSync('need_scan_redirect') === true) {
  267. await this.ensureTrackingUser();
  268. await this.checkAndRecordUserSourceOnLogin();
  269. }
  270. // 不要 return,继续执行后面的跳转逻辑
  271. } else {
  272. // 登录成功,设置 userLogin
  273. if (currentUser && currentUser.get('mobile')) {
  274. wx.setStorageSync("userLogin", currentUser.id);
  275. console.log('✅ 授权登录成功,已设置 userLogin:', currentUser.id);
  276. console.log('✅ 用户手机号:', currentUser.get('mobile'));
  277. } else {
  278. console.warn('⚠️ checkAuth 返回成功,但用户没有手机号!');
  279. console.warn(' 用户对象:', currentUser);
  280. }
  281. // 检查是否有待记录的扫码信息
  282. await this.checkAndRecordPendingScan();
  283. // 如果用户没有来源信息,且当前有扫码参数,记录来源
  284. await this.checkAndRecordUserSourceOnLogin();
  285. }
  286. } else {
  287. console.log('✅ 用户已登录,跳过 checkAuth');
  288. // 用户已登录,确保 userLogin 已设置
  289. if (currentUser.get('mobile')) {
  290. wx.setStorageSync("userLogin", currentUser.id);
  291. console.log('✅ 已确认 userLogin:', currentUser.id);
  292. }
  293. this.updateUser(currentUser.id);
  294. // 用户已登录,检查是否有待记录的扫码信息
  295. await this.checkAndRecordPendingScan();
  296. // 如果用户没有来源信息,且当前有扫码参数,记录来源
  297. await this.checkAndRecordUserSourceOnLogin();
  298. }
  299. getApp().Parse = Parse
  300. getApp().checkAuth = checkAuth
  301. if (!await this.getCompanyServerExpire(url)) {
  302. return
  303. }
  304. // 检查是否需要跳转到活动页面
  305. if (wx.getStorageSync('need_activity_redirect') === true) {
  306. await this.redirectToActivityPage();
  307. return;
  308. }
  309. // 检查是否需要跳转到扫码统计页面
  310. if (this.shouldRedirectToScanPage()) {
  311. await this.redirectToScanPage();
  312. return;
  313. }
  314. console.log('✅ 准备跳转到:', url);
  315. wx.redirectTo({
  316. url: url,
  317. success: () => {
  318. console.log('✅ redirectTo 跳转成功');
  319. },
  320. fail: (err) => {
  321. console.error('❌ redirectTo 失败:', err);
  322. console.log('⚠️ 尝试使用 reLaunch');
  323. // 降级:尝试使用 reLaunch
  324. wx.reLaunch({
  325. url: url,
  326. success: () => {
  327. console.log('✅ reLaunch 跳转成功');
  328. },
  329. fail: (err2) => {
  330. console.error('❌ reLaunch 也失败:', err2);
  331. // 显示错误提示
  332. this.setData({ loading: false });
  333. wx.showModal({
  334. title: '温馨提示',
  335. content: '页面加载失败,请检查网络后重试。',
  336. showCancel: true,
  337. cancelText: '退出',
  338. confirmText: '重试',
  339. success: (result) => {
  340. if (result.confirm) {
  341. this.review(true);
  342. } else {
  343. wx.exitMiniProgram();
  344. }
  345. }
  346. });
  347. }
  348. });
  349. }
  350. });
  351. }
  352. catch (err) {
  353. console.log('❌ review 方法出错:', err);
  354. /* 登录身份信息到期,重新登陆 */
  355. if((err?.message?.indexOf('Session token is expired') != -1 || err?.message?.indexOf('Invalid session token') != -1) && !force){
  356. console.log('⚠️ Session Token 过期,准备重新登录');
  357. // 保存需要保留的数据
  358. const invite = wx.getStorageSync('invite');
  359. const agreementAccepted = wx.getStorageSync('user_agreement_accepted');
  360. const storeId = wx.getStorageSync('storeId');
  361. const isPublishing = wx.getStorageSync('isPublishing');
  362. // 先登出 Parse 用户
  363. try {
  364. await Parse.User.logOut();
  365. console.log('✅ Parse 用户已登出');
  366. } catch (logoutErr) {
  367. console.warn('⚠️ Parse 登出失败:', logoutErr);
  368. }
  369. // 清除所有存储
  370. wx.clearStorageSync();
  371. // 恢复需要保留的数据
  372. if (invite) wx.setStorageSync('invite', invite);
  373. if (agreementAccepted) wx.setStorageSync('user_agreement_accepted', agreementAccepted);
  374. if (storeId) wx.setStorageSync('storeId', storeId);
  375. if (isPublishing !== undefined) wx.setStorageSync('isPublishing', isPublishing);
  376. console.log('✅ 已清除过期登录信息,保留必要数据');
  377. console.log(' 保留的 invite:', invite || '无');
  378. console.log(' 保留的协议状态:', agreementAccepted || '无');
  379. console.log(' 保留的店铺ID:', storeId || '无');
  380. /* 强制重新登录 */
  381. this.review(true);
  382. return;
  383. }
  384. // 如果出错,尝试继续跳转到主页(游客模式)
  385. console.log('⚠️ 登录出错,尝试游客模式访问');
  386. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  387. if (this.data.options) {
  388. let objArr = Object.keys(this.data.options)
  389. if (objArr && objArr.length > 0) {
  390. let parms = '?'
  391. objArr.forEach((o, index) => {
  392. if (index > 0) {
  393. parms += '&' + o + '=' + this.data.options[o]
  394. } else {
  395. parms += o + '=' + this.data.options[o]
  396. }
  397. })
  398. url += parms
  399. }
  400. }
  401. wx.redirectTo({
  402. url: url,
  403. fail: () => {
  404. // 如果跳转失败,显示错误提示
  405. this.setData({
  406. loading:false
  407. })
  408. wx.showModal({
  409. title: '温馨提示',
  410. content: '页面加载失败,请检查网络后重试。',
  411. showCancel: true,
  412. cancelText: '退出',
  413. confirmText: '重试',
  414. success: (result) => {
  415. if (result.confirm) {
  416. // 用户选择重试
  417. this.review(true)
  418. } else {
  419. // 用户选择退出
  420. wx.exitMiniProgram()
  421. }
  422. },
  423. });
  424. }
  425. });
  426. }
  427. },
  428. /**
  429. * 检查并记录待处理的扫码统计
  430. * 同时记录用户来源信息到 _User 表
  431. */
  432. async checkAndRecordPendingScan() {
  433. try {
  434. const pendingScan = wx.getStorageSync('pending_scan_record');
  435. if (!pendingScan) {
  436. return;
  437. }
  438. console.log('===========================================');
  439. console.log('======= 发现待记录的扫码信息 =======');
  440. console.log('扫码信息:', pendingScan);
  441. console.log('===========================================');
  442. // 检查是否超时(24小时)
  443. const now = Date.now();
  444. const scanTime = pendingScan.timestamp || 0;
  445. const hoursPassed = (now - scanTime) / (1000 * 60 * 60);
  446. if (hoursPassed > 24) {
  447. console.log('⚠️ 扫码信息已超过24小时,不再记录');
  448. wx.removeStorageSync('pending_scan_record');
  449. return;
  450. }
  451. // 记录用户来源信息
  452. await this.recordUserSource(pendingScan);
  453. // 记录扫码统计
  454. await this.recordScanStatistics({
  455. storeId: pendingScan.storeId,
  456. sourceType: pendingScan.sourceType,
  457. sourceId: pendingScan.sourceId,
  458. ownerId: pendingScan.ownerId,
  459. employeeId: pendingScan.employeeId,
  460. partnerId: pendingScan.partnerId,
  461. userId: pendingScan.userId,
  462. productId: pendingScan.productId,
  463. scanCount: pendingScan.scanCount
  464. });
  465. // 清除待记录的扫码信息
  466. wx.removeStorageSync('pending_scan_record');
  467. console.log('✅ 扫码统计已记录并清除');
  468. } catch (error) {
  469. console.error('❌ 记录待处理扫码信息失败:', error);
  470. }
  471. },
  472. async ensureTrackingUser() {
  473. try {
  474. let currentUser = Parse.User.current();
  475. if (currentUser) {
  476. return currentUser;
  477. }
  478. if (Parse.AnonymousUtils && typeof Parse.AnonymousUtils.logIn === 'function') {
  479. await Parse.AnonymousUtils.logIn();
  480. currentUser = Parse.User.current();
  481. return currentUser || null;
  482. }
  483. return null;
  484. } catch (e) {
  485. console.warn('ensureTrackingUser 失败:', e?.message || e);
  486. return null;
  487. }
  488. },
  489. /**
  490. * 记录用户来源信息到 _User 表
  491. * 根据扫码参数判断来源类型并保存到用户的 source 字段
  492. *
  493. * 来源类型:
  494. * 1. 渠道xxx→异业xxx(老板后台添加的异业)
  495. * 2. 渠道xxx→异业xxx→员工xxx(员工后台添加的异业)
  496. * 3. 员工xxx
  497. * 4. 老板
  498. * 5. 自主进入(无任何推荐)
  499. */
  500. async recordUserSource(scanInfo) {
  501. try {
  502. const currentUser = Parse.User.current();
  503. if (!currentUser) {
  504. console.log('⚠️ 用户未登录,无法记录来源');
  505. return;
  506. }
  507. const existingSource = currentUser.get('source');
  508. console.log('📊 ===========================================');
  509. console.log('📊 [记录来源] 开始记录用户来源信息');
  510. console.log('📊 用户ID:', currentUser.id);
  511. console.log('📊 ===========================================');
  512. const { partnerId, employeeId, ownerId, userId, storeId } = scanInfo;
  513. console.log('📌 [扣减前置] storeId:', storeId || '无');
  514. console.log('📌 [扣减前置] existingSource:', existingSource ? '有' : '无');
  515. console.log('📌 [扣减前置] trafficDeducted:', currentUser.get('trafficDeducted') === true ? 'true' : 'false');
  516. console.log('📌 [扣减前置] trafficDeductedStoreId:', currentUser.get('trafficDeductedStoreId') || '无');
  517. console.log('📌 [扣减前置] trafficDeductedAt:', currentUser.get('trafficDeductedAt') || '无');
  518. if (!existingSource) {
  519. let sourceInfo = {
  520. type: 'self_entry',
  521. label: '自主进入',
  522. timestamp: new Date(),
  523. storeId: storeId
  524. };
  525. if (partnerId && employeeId) {
  526. console.log('🔍 [来源类型] 渠道→异业→员工');
  527. try {
  528. const partnerQuery = new Parse.Query('Partner');
  529. const partner = await partnerQuery.get(partnerId);
  530. const partnerName = partner.get('name') || '未知异业';
  531. const channelName = partner.get('channelName') || partner.get('category') || '未知渠道';
  532. const employeeQuery = new Parse.Query('Employee');
  533. const employee = await employeeQuery.get(employeeId);
  534. const employeeName = employee.get('name') || '未知员工';
  535. sourceInfo = {
  536. type: 'channel_partner_employee',
  537. label: `${channelName},${partnerName},员工${employeeName}`,
  538. channelName: channelName,
  539. partnerId: partnerId,
  540. partnerName: partnerName,
  541. employeeId: employeeId,
  542. employeeName: employeeName,
  543. timestamp: new Date(),
  544. storeId: storeId
  545. };
  546. console.log('✅ [来源信息] 渠道→异业→员工:', sourceInfo.label);
  547. } catch (error) {
  548. console.error('❌ 查询异业或员工信息失败:', error);
  549. }
  550. } else if (partnerId && !employeeId) {
  551. console.log('🔍 [来源类型] 渠道→异业');
  552. try {
  553. const partnerQuery = new Parse.Query('Partner');
  554. const partner = await partnerQuery.get(partnerId);
  555. const partnerName = partner.get('name') || '未知异业';
  556. const channelName = partner.get('channelName') || partner.get('category') || '未知渠道';
  557. sourceInfo = {
  558. type: 'channel_partner',
  559. label: `${channelName},${partnerName}`,
  560. channelName: channelName,
  561. partnerId: partnerId,
  562. partnerName: partnerName,
  563. timestamp: new Date(),
  564. storeId: storeId
  565. };
  566. console.log('✅ [来源信息] 渠道→异业:', sourceInfo.label);
  567. } catch (error) {
  568. console.error('❌ 查询异业信息失败:', error);
  569. }
  570. } else if (employeeId && !partnerId) {
  571. console.log('🔍 [来源类型] 员工');
  572. try {
  573. const employeeQuery = new Parse.Query('Employee');
  574. const employee = await employeeQuery.get(employeeId);
  575. const employeeName = employee.get('name') || '未知员工';
  576. sourceInfo = {
  577. type: 'employee',
  578. label: employeeName,
  579. employeeId: employeeId,
  580. employeeName: employeeName,
  581. timestamp: new Date(),
  582. storeId: storeId
  583. };
  584. console.log('✅ [来源信息] 员工:', sourceInfo.label);
  585. } catch (error) {
  586. console.error('❌ 查询员工信息失败:', error);
  587. }
  588. } else if (ownerId) {
  589. console.log('🔍 [来源类型] 老板');
  590. sourceInfo = {
  591. type: 'owner',
  592. label: '老板',
  593. ownerId: ownerId,
  594. timestamp: new Date(),
  595. storeId: storeId
  596. };
  597. console.log('✅ [来源信息] 老板');
  598. } else if (userId) {
  599. console.log('🔍 [来源类型] 推广员');
  600. try {
  601. const userQuery = new Parse.Query('_User');
  602. const promoter = await userQuery.get(userId);
  603. const promoterName = promoter.get('username') || promoter.get('mobile') || '未知推广员';
  604. sourceInfo = {
  605. type: 'promoter',
  606. label: `推广员${promoterName}`,
  607. userId: userId,
  608. promoterName: promoterName,
  609. timestamp: new Date(),
  610. storeId: storeId
  611. };
  612. console.log('✅ [来源信息] 推广员:', sourceInfo.label);
  613. } catch (error) {
  614. console.error('❌ 查询推广员信息失败:', error);
  615. }
  616. } else {
  617. console.log('🔍 [来源类型] 自主进入');
  618. console.log('✅ [来源信息] 自主进入');
  619. }
  620. currentUser.set('source', sourceInfo);
  621. await currentUser.save();
  622. console.log('✅ [保存成功] 用户来源信息已保存');
  623. console.log(' - 来源类型:', sourceInfo.type);
  624. console.log(' - 来源标签:', sourceInfo.label);
  625. console.log('📊 ===========================================');
  626. } else {
  627. console.log('ℹ️ 用户已有来源信息,跳过覆盖:', existingSource);
  628. }
  629. const trafficDeducted =
  630. currentUser.get('trafficDeducted') === true ||
  631. !!currentUser.get('trafficDeductedAt') ||
  632. !!currentUser.get('trafficDeductedStoreId');
  633. if (storeId && !trafficDeducted) {
  634. console.log('🧮 [扣减流量] 检测到新用户首次扣减,准备扣减门店流量');
  635. try {
  636. await decrementStoreTrafficImpl(Parse, storeId);
  637. currentUser.set('trafficDeducted', true);
  638. currentUser.set('trafficDeductedStoreId', storeId);
  639. currentUser.set('trafficDeductedAt', new Date());
  640. await currentUser.save();
  641. console.log('✅ [扣减成功] 已为店铺扣减 1 个流量,storeId:', storeId);
  642. } catch (decErr) {
  643. console.error('❌ [扣减失败] 扣减门店流量失败:', decErr?.message || decErr);
  644. }
  645. } else if (!storeId) {
  646. console.warn('⚠️ [扣减跳过] storeId 为空,无法扣减 ShopStore.traffic');
  647. } else if (trafficDeducted) {
  648. console.log('ℹ️ [扣减跳过] 已扣减过流量,不重复扣减');
  649. }
  650. } catch (error) {
  651. console.error('❌ 记录用户来源失败:', error);
  652. }
  653. },
  654. /**
  655. * 用户登录后检查并记录来源信息
  656. * 如果用户是首次登录且没有来源信息,根据当前的扫码参数记录来源
  657. */
  658. async checkAndRecordUserSourceOnLogin() {
  659. try {
  660. const currentUser = Parse.User.current();
  661. if (!currentUser) {
  662. return;
  663. }
  664. // 检查是否有扫码参数
  665. const scanStoreId = wx.getStorageSync('scan_storeId');
  666. const scanPartnerId = wx.getStorageSync('scan_partnerId');
  667. const scanEmployeeId = wx.getStorageSync('scan_employeeId');
  668. const scanOwnerId = wx.getStorageSync('scan_ownerId');
  669. const scanUserId = wx.getStorageSync('scan_userId');
  670. // 如果有任何扫码参数,记录来源
  671. if (scanStoreId || scanPartnerId || scanEmployeeId || scanOwnerId || scanUserId) {
  672. console.log('📊 检测到扫码参数,记录用户来源');
  673. await this.recordUserSource({
  674. storeId: scanStoreId,
  675. partnerId: scanPartnerId,
  676. employeeId: scanEmployeeId,
  677. ownerId: scanOwnerId,
  678. userId: scanUserId
  679. });
  680. } else {
  681. // 没有任何扫码参数,标记为自主进入
  682. console.log('📊 无扫码参数,标记为自主进入');
  683. await this.recordUserSource({
  684. storeId: wx.getStorageSync('storeId') || null
  685. });
  686. }
  687. } catch (error) {
  688. console.error('❌ 检查并记录用户来源失败:', error);
  689. }
  690. },
  691. async updateUser(id) {
  692. try {
  693. let User = new Parse.Query('_User')
  694. let user = await User.get(id)
  695. let invite = wx.getStorageSync('invite')
  696. //查询邀请人user
  697. let query = new Parse.Query("_User")
  698. query.equalTo('objectId', invite)
  699. let result = await query.first()
  700. if (result && result.id && result.get("invite")?.id == user.id) {
  701. console.error('邀请人不能是自己的下级')
  702. return
  703. }
  704. if (invite && !user.get('invite') && user.id != invite && !user.get('agentLevel')) {
  705. console.log('上下级绑定成功');
  706. user.set('invite', {
  707. __type: "Pointer",
  708. className: "_User",
  709. objectId: invite
  710. })
  711. user.set('agent', {
  712. __type: "Pointer",
  713. className: "_User",
  714. objectId: invite
  715. })
  716. await Parse.Cloud.run('user_save', {
  717. userJson: user.toJSON()
  718. })
  719. }
  720. } catch (error) {
  721. console.error('❌ updateUser 失败:', error.message);
  722. // 不阻断流程,继续执行
  723. }
  724. },
  725. async getCompanyServerExpire(url) {
  726. try {
  727. let query = new Parse.Query('Company')
  728. query.equalTo('objectId', getApp().globalData.company)
  729. query.select('expireDate', 'expireMap')
  730. let com = await query.first()
  731. if (com?.id && com?.get('expireDate')) {
  732. let now = + new Date()
  733. let expireTime = + new Date(com?.get('expireDate'))
  734. if (com?.get('expireMap') && com.get('expireMap')[getApp().globalData.appid]) {
  735. expireTime = + new Date(com.get('expireMap')[getApp().globalData.appid])
  736. }
  737. if (now >= expireTime) {
  738. console.log('服务器到期');
  739. wx.reLaunch({
  740. url: `common-page/pages/loading/index?url=${url}`,
  741. });
  742. return
  743. }
  744. }
  745. return true
  746. } catch (error) {
  747. console.error('❌ getCompanyServerExpire 失败:', error.message);
  748. // 查询失败时,允许继续访问
  749. return true
  750. }
  751. },
  752. onUnload: function () {
  753. wx.setStorageSync("active", 0);
  754. },
  755. getParaName(url) {
  756. if (!url || url.indexOf('?') == -1) {
  757. return
  758. }
  759. // 兼容 URL 中 & 的情况,先统一还原为 &
  760. if (url.indexOf('&') !== -1) {
  761. url = url.replace(/&/g, '&');
  762. }
  763. // 提取查询参数部分(去除可能的 hash 部分)
  764. let queryString = url.split('?')[1];
  765. // 如果包含 #,只取 # 之前的部分
  766. if (queryString.indexOf('#') !== -1) {
  767. queryString = queryString.split('#')[0];
  768. }
  769. return this.setObject(queryString) //封装成对象
  770. },
  771. setObject(paraArr) {
  772. let obj = {}
  773. let arr1 = paraArr.split('&')
  774. arr1.forEach(item => {
  775. let str = item.split('=')
  776. let key = str[0]
  777. let val = str[1]
  778. obj[key] = val
  779. })
  780. return obj
  781. },
  782. /**
  783. * 检查并处理扫码参数
  784. * 支持所有类型的二维码:
  785. * 1. 推广员二维码: ?scanCount=0&storeId=xxx&userId=xxx
  786. * 2. 产品二维码: ?scanCount=0&storeId=xxx&productId=xxx
  787. * 3. 案例二维码: ?scanCount=0&storeId=xxx&caseId=xxx
  788. * 4. 活动海报二维码: activityId作为qrCode参数值
  789. * 5. 异业合作伙伴二维码(移动端 / PC端统一): ?scanCount=xxx&storeId=xxx&partnerId=xxx
  790. * 6. 员工邀请二维码(移动端): ?scanCount=0&storeId=xxx&employeeId=xxx
  791. * 7. 我的二维码(老板): ?scanCount=0&storeId=xxx&ownerId=xxx
  792. * 8. 我的二维码(员工): ?scanCount=0&storeId=xxx&employeeId=xxx
  793. * 9. 方案分享二维码: ?scanCount=0&storeId=xxx&planId=xxx&shareUserId=xxx
  794. */
  795. checkAndHandleScan(options) {
  796. console.log('🔍🔍🔍 ========================================');
  797. console.log('🔍 [扫码检测] 开始检查扫码参数');
  798. console.log('🔍 完整 options 对象:', JSON.stringify(options, null, 2));
  799. console.log('🔍🔍🔍 ========================================');
  800. const {
  801. scanCount,
  802. ownerId,
  803. employeeId,
  804. partnerId,
  805. storeId,
  806. productId,
  807. caseId,
  808. userId, // 推广员二维码使用userId
  809. activityId,
  810. schemeId, // 方案ID(旧参数名)
  811. planId, // 方案ID(新参数名,优先使用)
  812. shareUserId, // 分享方案的用户ID
  813. q // 扫码链接
  814. } = options;
  815. // planId 和 schemeId 兼容处理,优先使用 planId
  816. const finalPlanId = planId || schemeId;
  817. console.log('📋 [扫码参数] 提取的参数:');
  818. console.log(' - scanCount:', scanCount || '无');
  819. console.log(' - storeId:', storeId || '无');
  820. console.log(' - ownerId:', ownerId || '无');
  821. console.log(' - employeeId:', employeeId || '无');
  822. console.log(' - partnerId:', partnerId || '无');
  823. console.log(' - userId:', userId || '无');
  824. console.log(' - productId:', productId || '无');
  825. console.log(' - caseId:', caseId || '无');
  826. console.log(' - activityId:', activityId || '无');
  827. console.log(' - planId:', planId || '无');
  828. console.log(' - schemeId:', schemeId || '无');
  829. console.log(' - finalPlanId (最终使用):', finalPlanId || '无');
  830. console.log(' - shareUserId:', shareUserId || '无');
  831. console.log(' - q (扫码链接):', q || '无');
  832. // 处理活动海报二维码(activityId作为qrCode参数值)
  833. if (activityId && !storeId) {
  834. console.log('🎯 ===========================================');
  835. console.log('🎯 [活动二维码] 检测到活动海报二维码');
  836. console.log('🎯 活动ID (activityId):', activityId);
  837. console.log('🎯 来源类型: 活动海报');
  838. console.log('🎯 ===========================================');
  839. // 活动二维码需要跳转到活动页面,不需要跳转到店铺
  840. wx.setStorageSync('scan_activityId', activityId);
  841. wx.setStorageSync('need_activity_redirect', true);
  842. return;
  843. }
  844. // 如果存在 storeId,说明是扫码进入具体门店(包括异业分享)
  845. if (storeId) {
  846. console.log('🏪 ===========================================');
  847. console.log('🏪 [店铺扫码] 检测到扫码参数');
  848. console.log('🏪 店铺ID (storeId):', storeId);
  849. console.log('🏪 扫码次数 (scanCount):', scanCount || '0');
  850. // 确定来源类型
  851. // 规则:
  852. // 1. 扫描异业码(partnerId)→ sourceType = "partner"
  853. // 2. 扫描员工码(employeeId、老板/员工)→ sourceType = "sales" 或 "promoter"
  854. // 3. 扫描其他码(产品码、案例码等)→ sourceType = "scan"
  855. let sourceType = 'scan'; // 默认为通用扫码
  856. let sourceId = null;
  857. if (partnerId) {
  858. // 异业合作伙伴二维码
  859. sourceType = 'partner';
  860. sourceId = partnerId;
  861. console.log('🤝 [来源识别] 来源类型: 异业合作伙伴 (partner)');
  862. console.log('🤝 合作伙伴ID (partnerId):', partnerId);
  863. } else if (employeeId) {
  864. // 员工二维码(包括老板和员工)
  865. sourceType = 'sales';
  866. sourceId = employeeId;
  867. console.log('👤 [来源识别] 来源类型: 员工展业 (sales)');
  868. console.log('👤 员工ID (employeeId):', employeeId);
  869. } else if (ownerId) {
  870. // 老板二维码
  871. sourceType = 'sales';
  872. sourceId = ownerId;
  873. console.log('👔 [来源识别] 来源类型: 老板展业 (sales)');
  874. console.log('👔 老板ID (ownerId):', ownerId);
  875. } else if (userId) {
  876. // 推广员二维码
  877. sourceType = 'promoter';
  878. sourceId = userId;
  879. console.log('📢 [来源识别] 来源类型: 推广员展业 (promoter)');
  880. console.log('📢 推广员ID (userId):', userId);
  881. } else {
  882. // 其他情况(产品码、案例码、店铺分享等)
  883. sourceType = 'scan';
  884. console.log('🏬 [来源识别] 来源类型: 通用扫码 (scan)');
  885. }
  886. if (productId) {
  887. console.log('📦 [目标内容] 产品ID (productId):', productId);
  888. }
  889. if (caseId) {
  890. console.log('📸 [目标内容] 案例ID (caseId):', caseId);
  891. }
  892. if (finalPlanId) {
  893. console.log('📋 [目标内容] 方案ID (planId/schemeId):', finalPlanId);
  894. console.log('👤 [分享人] 分享用户ID (shareUserId):', shareUserId || '无');
  895. }
  896. console.log('🏪 ===========================================');
  897. // 保存到临时存储,用于后续跳转和统计
  898. console.log('💾 [保存扫码信息] 开始保存到本地存储...');
  899. wx.setStorageSync('scan_storeId', storeId);
  900. wx.setStorageSync('scan_scanCount', scanCount || '0');
  901. wx.setStorageSync('scan_sourceType', sourceType);
  902. wx.setStorageSync('scan_sourceId', sourceId || '');
  903. wx.setStorageSync('scan_ownerId', ownerId || '');
  904. wx.setStorageSync('scan_employeeId', employeeId || '');
  905. wx.setStorageSync('scan_partnerId', partnerId || '');
  906. wx.setStorageSync('scan_userId', userId || '');
  907. if (productId) {
  908. wx.setStorageSync('scan_productId', productId);
  909. }
  910. if (caseId) {
  911. wx.setStorageSync('scan_caseId', caseId);
  912. }
  913. if (finalPlanId) {
  914. wx.setStorageSync('scan_planId', finalPlanId);
  915. if (shareUserId) {
  916. wx.setStorageSync('scan_shareUserId', shareUserId);
  917. }
  918. }
  919. wx.setStorageSync('need_scan_redirect', true);
  920. console.log('✅ [保存完成] 扫码信息已保存');
  921. console.log('📋 [保存内容摘要]:');
  922. console.log(' - 来源类型:', sourceType);
  923. console.log(' - 来源ID:', sourceId || '无');
  924. console.log(' - 店铺ID:', storeId);
  925. console.log(' - 需要跳转:', true);
  926. } else if (partnerId) {
  927. // 理论上异业二维码现在也必须带 storeId,这里仅作为兜底保护
  928. console.warn('⚠️ 检测到异业合作伙伴二维码缺少 storeId,无法确定门店,请检查二维码生成逻辑');
  929. }
  930. },
  931. /**
  932. * 判断是否需要跳转到扫码统计页面
  933. */
  934. shouldRedirectToScanPage() {
  935. return wx.getStorageSync('need_scan_redirect') === true;
  936. },
  937. /**
  938. * 跳转到扫码对应的店铺页面
  939. * 根据 storeId 跳转到对应店铺,同时记录所有来源信息用于统计
  940. */
  941. async redirectToScanPage() {
  942. try {
  943. console.log('🚀 ===========================================');
  944. console.log('🚀 [跳转处理] 开始处理扫码跳转');
  945. console.log('🚀 ===========================================');
  946. // 获取所有扫码相关参数
  947. const storeId = wx.getStorageSync('scan_storeId');
  948. const scanCount = wx.getStorageSync('scan_scanCount');
  949. const sourceType = wx.getStorageSync('scan_sourceType');
  950. const sourceId = wx.getStorageSync('scan_sourceId');
  951. const ownerId = wx.getStorageSync('scan_ownerId');
  952. const employeeId = wx.getStorageSync('scan_employeeId');
  953. const partnerId = wx.getStorageSync('scan_partnerId');
  954. const userId = wx.getStorageSync('scan_userId');
  955. const productId = wx.getStorageSync('scan_productId');
  956. const caseId = wx.getStorageSync('scan_caseId');
  957. const planId = wx.getStorageSync('scan_planId');
  958. const shareUserId = wx.getStorageSync('scan_shareUserId');
  959. console.log('📖 [读取存储] 从本地存储读取扫码信息:');
  960. console.log(' - storeId:', storeId || '无');
  961. console.log(' - sourceType:', sourceType || '无');
  962. console.log(' - sourceId:', sourceId || '无');
  963. console.log(' - scanCount:', scanCount || '无');
  964. console.log(' - ownerId:', ownerId || '无');
  965. console.log(' - employeeId:', employeeId || '无');
  966. console.log(' - partnerId:', partnerId || '无');
  967. console.log(' - userId:', userId || '无');
  968. console.log(' - productId:', productId || '无');
  969. console.log(' - caseId:', caseId || '无');
  970. console.log(' - planId:', planId || '无');
  971. console.log(' - shareUserId:', shareUserId || '无');
  972. // 清除临时存储
  973. console.log('🧹 [清理存储] 清除临时扫码信息...');
  974. wx.removeStorageSync('scan_storeId');
  975. wx.removeStorageSync('scan_scanCount');
  976. wx.removeStorageSync('scan_sourceType');
  977. wx.removeStorageSync('scan_sourceId');
  978. wx.removeStorageSync('scan_ownerId');
  979. wx.removeStorageSync('scan_employeeId');
  980. wx.removeStorageSync('scan_partnerId');
  981. wx.removeStorageSync('scan_userId');
  982. wx.removeStorageSync('scan_productId');
  983. wx.removeStorageSync('scan_caseId');
  984. wx.removeStorageSync('scan_planId');
  985. wx.removeStorageSync('scan_shareUserId');
  986. wx.removeStorageSync('need_scan_redirect');
  987. console.log('✅ [清理完成] 临时存储已清除');
  988. if (!storeId) {
  989. console.error('❌ [错误] 缺少 storeId 参数,无法跳转');
  990. return;
  991. }
  992. console.log('📊 ===========================================');
  993. console.log('📊 [扫码来源汇总] 扫码进入店铺');
  994. console.log('📊 店铺 ID:', storeId);
  995. console.log('📊 来源类型:', sourceType);
  996. console.log('📊 来源 ID:', sourceId || '无');
  997. console.log('📊 扫码次数:', scanCount);
  998. if (ownerId) console.log('📊 老板 ID:', ownerId);
  999. if (employeeId) console.log('📊 员工 ID:', employeeId);
  1000. if (partnerId) console.log('📊 合作伙伴 ID:', partnerId);
  1001. if (userId) console.log('📊 推广员 ID:', userId);
  1002. if (productId) console.log('📊 产品 ID:', productId);
  1003. if (caseId) console.log('📊 案例 ID:', caseId);
  1004. if (planId) {
  1005. console.log('📊 方案 ID:', planId);
  1006. console.log('📊 分享用户 ID:', shareUserId || '无');
  1007. }
  1008. console.log('📊 ===========================================');
  1009. // 设置店铺 ID 到全局和本地存储
  1010. wx.setStorageSync('storeId', storeId);
  1011. getApp().globalData.storeId = storeId;
  1012. // 保存来源信息到本地存储(用于后续统计或绑定关系)
  1013. if (sourceId) {
  1014. wx.setStorageSync('scan_from_sourceType', sourceType);
  1015. wx.setStorageSync('scan_from_sourceId', sourceId);
  1016. console.log('✅ 已记录来源信息:', sourceType, sourceId);
  1017. }
  1018. // 检查用户是否已登录
  1019. const currentUser = Parse.User.current();
  1020. console.log('👤 [登录检查] 检查用户登录状态...');
  1021. console.log(' - 当前用户:', currentUser ? currentUser.id : '未登录');
  1022. console.log(' - 手机号:', currentUser?.get('mobile') || '无');
  1023. if (currentUser) {
  1024. // 用户已登录,立即记录扫码统计
  1025. console.log('✅ [已登录] 用户已登录,立即记录扫码统计');
  1026. await this.recordScanStatistics({
  1027. storeId,
  1028. sourceType,
  1029. sourceId,
  1030. ownerId,
  1031. employeeId,
  1032. partnerId,
  1033. userId,
  1034. productId,
  1035. scanCount
  1036. });
  1037. } else {
  1038. // 用户未登录,保存扫码信息,等登录后再记录
  1039. console.log('⚠️ [未登录] 用户未登录,保存扫码信息待登录后记录');
  1040. const pendingData = {
  1041. storeId,
  1042. sourceType,
  1043. sourceId,
  1044. ownerId,
  1045. employeeId,
  1046. partnerId,
  1047. userId,
  1048. productId,
  1049. scanCount,
  1050. timestamp: Date.now()
  1051. };
  1052. console.log('💾 [待处理记录] 保存内容:', JSON.stringify(pendingData, null, 2));
  1053. wx.setStorageSync('pending_scan_record', pendingData);
  1054. console.log('✅ [保存成功] 待处理扫码记录已保存');
  1055. }
  1056. // 如果有产品ID,直接跳转到产品详情的 H5 页面
  1057. if (productId) {
  1058. console.log('🎯 检测到产品ID,跳转到产品详情页');
  1059. await this.redirectToProductDetail(storeId, productId, partnerId);
  1060. return;
  1061. }
  1062. // 如果有案例ID,直接跳转到案例详情的 H5 页面
  1063. if (caseId) {
  1064. console.log('🎯 检测到案例ID,跳转到案例详情页');
  1065. await this.redirectToCaseDetail(storeId, caseId, partnerId);
  1066. return;
  1067. }
  1068. // 如果有方案ID,跳转到门店首页并传递方案ID
  1069. if (planId) {
  1070. console.log('🎯 检测到方案ID,跳转到门店首页展示方案');
  1071. await this.redirectToStoreWithPlan(storeId, planId, shareUserId, partnerId);
  1072. return;
  1073. }
  1074. // 获取默认首页路径并跳转
  1075. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  1076. url += `?storeId=${storeId}`;
  1077. // 如果有产品ID,添加到URL参数中
  1078. if (productId) {
  1079. url += `&productId=${productId}`;
  1080. }
  1081. // 如果有异业合作伙伴ID,添加到URL参数中传回web-view
  1082. if (partnerId) {
  1083. url += `&partnerId=${partnerId}`;
  1084. }
  1085. console.log('✅ 跳转到店铺页面:', url);
  1086. wx.redirectTo({
  1087. url: url,
  1088. fail: (err) => {
  1089. console.error('❌ 跳转失败:', err);
  1090. // 如果 redirectTo 失败,尝试 reLaunch
  1091. wx.reLaunch({
  1092. url: url,
  1093. fail: (err2) => {
  1094. console.error('❌ reLaunch 也失败:', err2);
  1095. }
  1096. });
  1097. }
  1098. });
  1099. } catch (error) {
  1100. console.error('❌ 跳转到店铺页面失败:', error);
  1101. }
  1102. },
  1103. /**
  1104. * 跳转到活动页面
  1105. */
  1106. async redirectToActivityPage() {
  1107. try {
  1108. const activityId = wx.getStorageSync('scan_activityId');
  1109. // 清除临时存储
  1110. wx.removeStorageSync('scan_activityId');
  1111. wx.removeStorageSync('need_activity_redirect');
  1112. if (!activityId) {
  1113. console.error('❌ 缺少 activityId 参数,无法跳转');
  1114. return;
  1115. }
  1116. console.log('===========================================');
  1117. console.log('======= 扫码进入活动页面 =======');
  1118. console.log('活动 ID (activityId):', activityId);
  1119. console.log('===========================================');
  1120. // 保存活动ID
  1121. wx.setStorageSync('activityId', activityId);
  1122. // 获取默认首页路径并跳转(活动页面可能需要根据实际路由调整)
  1123. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  1124. url += `?activityId=${activityId}`;
  1125. console.log('✅ 跳转到活动页面:', url);
  1126. wx.redirectTo({
  1127. url: url,
  1128. fail: (err) => {
  1129. console.error('❌ 跳转失败:', err);
  1130. wx.reLaunch({
  1131. url: url,
  1132. fail: (err2) => {
  1133. console.error('❌ reLaunch 也失败:', err2);
  1134. }
  1135. });
  1136. }
  1137. });
  1138. } catch (error) {
  1139. console.error('❌ 跳转到活动页面失败:', error);
  1140. }
  1141. },
  1142. /**
  1143. * 跳转到产品详情的 H5 页面
  1144. * @param {string} storeId - 店铺 ID
  1145. * @param {string} productId - 产品 ID
  1146. * @param {string} partnerId - 可选的合作伙伴 ID
  1147. */
  1148. async redirectToProductDetail(storeId, productId, partnerId = null) {
  1149. try {
  1150. console.log('===========================================');
  1151. console.log('======= 跳转到产品详情页 =======');
  1152. console.log('店铺 ID:', storeId);
  1153. console.log('产品 ID:', productId);
  1154. if (partnerId) console.log('合作伙伴 ID:', partnerId);
  1155. console.log('===========================================');
  1156. const currentUser = Parse.User.current();
  1157. const token = currentUser ? currentUser.getSessionToken() : null;
  1158. // 构建产品详情的 H5 URL(不要在这里编码,后面统一编码)
  1159. let h5Url = `https://app.fmode.cn/dev/pobingfeng/owner/nav/products?storeId=${storeId}`;
  1160. // 如果有 token,添加到 URL
  1161. if (token) {
  1162. h5Url += `&token=${token}`;
  1163. } else {
  1164. // 如果没有 token,使用游客模式
  1165. h5Url += `&guestMode=true`;
  1166. }
  1167. // 添加产品ID(不要在这里编码,避免双重编码)
  1168. h5Url += `&productId=${productId}`;
  1169. // 如果有合作伙伴ID,也添加到URL中
  1170. if (partnerId) {
  1171. h5Url += `&partnerId=${partnerId}`;
  1172. }
  1173. console.log('🌐 H5 URL:', h5Url);
  1174. // 编码 URL(统一在这里编码一次)
  1175. const encodedUrl = encodeURIComponent(h5Url);
  1176. // 构建 web-view 页面路径
  1177. let webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&storeId=${storeId}`;
  1178. console.log('📄 web-view 页面路径:', webViewPath.substring(0, 100) + '...');
  1179. console.log('===========================================');
  1180. wx.redirectTo({
  1181. url: webViewPath,
  1182. success: () => {
  1183. console.log('✅ 跳转到产品详情页成功');
  1184. },
  1185. fail: (err) => {
  1186. console.error('❌ 跳转失败:', err);
  1187. // 如果 redirectTo 失败,尝试 reLaunch
  1188. wx.reLaunch({
  1189. url: webViewPath,
  1190. fail: (err2) => {
  1191. console.error('❌ reLaunch 也失败:', err2);
  1192. }
  1193. });
  1194. }
  1195. });
  1196. } catch (error) {
  1197. console.error('❌ 跳转到产品详情页失败:', error);
  1198. }
  1199. },
  1200. /**
  1201. * 跳转到案例详情的 H5 页面
  1202. * @param {string} storeId - 店铺 ID
  1203. * @param {string} caseId - 案例 ID
  1204. * @param {string} partnerId - 可选的合作伙伴 ID
  1205. */
  1206. async redirectToCaseDetail(storeId, caseId, partnerId = null) {
  1207. try {
  1208. console.log('===========================================');
  1209. console.log('======= 跳转到案例详情页 =======');
  1210. console.log('店铺 ID:', storeId);
  1211. console.log('案例 ID:', caseId);
  1212. if (partnerId) console.log('合作伙伴 ID:', partnerId);
  1213. console.log('===========================================');
  1214. const currentUser = Parse.User.current();
  1215. const token = currentUser ? currentUser.getSessionToken() : null;
  1216. // 构建案例详情的 H5 URL(不要在这里编码,后面统一编码)
  1217. let h5Url = `https://app.fmode.cn/dev/pobingfeng/owner/nav/cases?storeId=${storeId}`;
  1218. // 如果有 token,添加到 URL
  1219. if (token) {
  1220. h5Url += `&token=${token}`;
  1221. } else {
  1222. // 如果没有 token,使用游客模式
  1223. h5Url += `&guestMode=true`;
  1224. }
  1225. // 添加案例ID(不要在这里编码,避免双重编码)
  1226. h5Url += `&caseId=${caseId}`;
  1227. // 如果有合作伙伴ID,也添加到URL中
  1228. if (partnerId) {
  1229. h5Url += `&partnerId=${partnerId}`;
  1230. }
  1231. console.log('🌐 H5 URL:', h5Url);
  1232. // 编码 URL(统一在这里编码一次)
  1233. const encodedUrl = encodeURIComponent(h5Url);
  1234. // 构建 web-view 页面路径
  1235. let webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&storeId=${storeId}`;
  1236. console.log('📄 web-view 页面路径:', webViewPath.substring(0, 100) + '...');
  1237. console.log('===========================================');
  1238. wx.redirectTo({
  1239. url: webViewPath,
  1240. success: () => {
  1241. console.log('✅ 跳转到案例详情页成功');
  1242. },
  1243. fail: (err) => {
  1244. console.error('❌ 跳转失败:', err);
  1245. // 如果 redirectTo 失败,尝试 reLaunch
  1246. wx.reLaunch({
  1247. url: webViewPath,
  1248. fail: (err2) => {
  1249. console.error('❌ reLaunch 也失败:', err2);
  1250. }
  1251. });
  1252. }
  1253. });
  1254. } catch (error) {
  1255. console.error('❌ 跳转到案例详情页失败:', error);
  1256. }
  1257. },
  1258. /**
  1259. * 跳转到门店首页并展示方案
  1260. * @param {string} storeId - 店铺 ID
  1261. * @param {string} planId - 方案 ID
  1262. * @param {string} shareUserId - 分享方案的用户 ID
  1263. * @param {string} partnerId - 可选的合作伙伴 ID
  1264. */
  1265. async redirectToStoreWithPlan(storeId, planId, shareUserId = null, partnerId = null) {
  1266. try {
  1267. console.log('===========================================');
  1268. console.log('======= 跳转到门店首页展示方案 =======');
  1269. console.log('店铺 ID:', storeId);
  1270. console.log('方案 ID:', planId);
  1271. if (shareUserId) console.log('分享用户 ID:', shareUserId);
  1272. if (partnerId) console.log('合作伙伴 ID:', partnerId);
  1273. console.log('===========================================');
  1274. const currentUser = Parse.User.current();
  1275. const token = currentUser ? currentUser.getSessionToken() : null;
  1276. // 构建门店首页的 H5 URL,带上方案ID
  1277. let h5Url = `https://app.fmode.cn/dev/pobingfeng/owner/nav/home?storeId=${storeId}`;
  1278. // 如果有 token,添加到 URL
  1279. if (token) {
  1280. h5Url += `&token=${token}`;
  1281. } else {
  1282. // 如果没有 token,使用游客模式
  1283. h5Url += `&guestMode=true`;
  1284. }
  1285. // 添加方案ID(使用planId参数名)
  1286. h5Url += `&planId=${planId}`;
  1287. // 如果有分享用户ID,也添加到URL中
  1288. if (shareUserId) {
  1289. h5Url += `&shareUserId=${shareUserId}`;
  1290. }
  1291. // 如果有合作伙伴ID,也添加到URL中
  1292. if (partnerId) {
  1293. h5Url += `&partnerId=${partnerId}`;
  1294. }
  1295. console.log('🌐 H5 URL:', h5Url);
  1296. // 编码 URL
  1297. const encodedUrl = encodeURIComponent(h5Url);
  1298. // 构建 web-view 页面路径
  1299. let webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&storeId=${storeId}`;
  1300. console.log('📄 web-view 页面路径:', webViewPath.substring(0, 100) + '...');
  1301. console.log('===========================================');
  1302. wx.redirectTo({
  1303. url: webViewPath,
  1304. success: () => {
  1305. console.log('✅ 跳转到门店首页展示方案成功');
  1306. },
  1307. fail: (err) => {
  1308. console.error('❌ 跳转失败:', err);
  1309. // 如果 redirectTo 失败,尝试 reLaunch
  1310. wx.reLaunch({
  1311. url: webViewPath,
  1312. fail: (err2) => {
  1313. console.error('❌ reLaunch 也失败:', err2);
  1314. }
  1315. });
  1316. }
  1317. });
  1318. } catch (error) {
  1319. console.error('❌ 跳转到门店首页展示方案失败:', error);
  1320. }
  1321. },
  1322. /**
  1323. * 记录扫码统计信息
  1324. * @param {Object} params - 扫码参数对象
  1325. * @param {string} params.storeId - 店铺 ID
  1326. * @param {string} params.sourceType - 来源类型 (employee/owner/partner/promoter/store)
  1327. * @param {string} params.sourceId - 来源 ID
  1328. * @param {string} params.ownerId - 老板 ID
  1329. * @param {string} params.employeeId - 员工 ID
  1330. * @param {string} params.partnerId - 合作伙伴 ID
  1331. * @param {string} params.userId - 推广员 ID
  1332. * @param {string} params.productId - 产品 ID
  1333. * @param {string} params.scanCount - 扫码次数
  1334. */
  1335. async recordScanStatistics(params) {
  1336. try {
  1337. console.log('📝 ===========================================');
  1338. console.log('📝 [扫码统计] 开始记录扫码统计');
  1339. console.log('📝 ===========================================');
  1340. const {
  1341. storeId,
  1342. sourceType,
  1343. sourceId,
  1344. ownerId,
  1345. employeeId,
  1346. partnerId,
  1347. userId,
  1348. productId,
  1349. scanCount
  1350. } = params;
  1351. console.log('📋 [统计参数] 接收到的参数:');
  1352. console.log(' - storeId:', storeId);
  1353. console.log(' - sourceType:', sourceType);
  1354. console.log(' - sourceId:', sourceId || '无');
  1355. console.log(' - ownerId:', ownerId || '无');
  1356. console.log(' - employeeId:', employeeId || '无');
  1357. console.log(' - partnerId:', partnerId || '无');
  1358. console.log(' - userId:', userId || '无');
  1359. console.log(' - productId:', productId || '无');
  1360. console.log(' - scanCount:', scanCount || '0');
  1361. // 检查用户是否已登录
  1362. const currentUser = Parse.User.current();
  1363. if (!currentUser) {
  1364. console.log('⚠️ [未登录] 用户未登录,不记录扫码统计');
  1365. return;
  1366. }
  1367. console.log('👤 [用户信息] 当前登录用户:');
  1368. console.log(' - 用户ID:', currentUser.id);
  1369. console.log(' - 手机号:', currentUser.get('mobile') || '无');
  1370. // 如果没有来源信息,不记录统计
  1371. if (!sourceId && !ownerId && !employeeId && !partnerId && !userId) {
  1372. console.log('ℹ️ [跳过] 无来源信息,跳过统计记录');
  1373. return;
  1374. }
  1375. console.log('✅ [开始记录] 用户已登录且有来源信息,开始记录');
  1376. // 创建扫码记录
  1377. const ScanRecord = Parse.Object.extend('ScanRecord');
  1378. const record = new ScanRecord();
  1379. record.set('company', {
  1380. __type: 'Pointer',
  1381. className: 'Company',
  1382. objectId: getApp().globalData.company
  1383. });
  1384. record.set('storeId', storeId);
  1385. record.set('sourceType', sourceType || 'unknown');
  1386. record.set('scanCount', parseInt(scanCount) || 0);
  1387. record.set('scanTime', new Date());
  1388. console.log('📦 [记录内容] 准备保存到 ScanRecord 表:');
  1389. console.log(' - company:', getApp().globalData.company);
  1390. console.log(' - storeId:', storeId);
  1391. console.log(' - sourceType:', sourceType || 'unknown');
  1392. console.log(' - scanCount:', parseInt(scanCount) || 0);
  1393. console.log(' - scanTime:', new Date().toISOString());
  1394. // 根据来源类型设置对应的ID
  1395. if (ownerId) {
  1396. record.set('ownerId', ownerId);
  1397. console.log(' - ownerId:', ownerId);
  1398. }
  1399. if (employeeId) {
  1400. record.set('employeeId', employeeId);
  1401. console.log(' - employeeId:', employeeId);
  1402. }
  1403. if (partnerId) {
  1404. record.set('partnerId', partnerId);
  1405. console.log(' - partnerId:', partnerId);
  1406. }
  1407. if (userId) {
  1408. record.set('userId', userId);
  1409. console.log(' - userId:', userId);
  1410. }
  1411. if (productId) {
  1412. record.set('productId', productId);
  1413. console.log(' - productId:', productId);
  1414. }
  1415. // 记录扫码用户
  1416. record.set('user', {
  1417. __type: 'Pointer',
  1418. className: '_User',
  1419. objectId: currentUser.id
  1420. });
  1421. console.log(' - user:', currentUser.id);
  1422. await record.save();
  1423. console.log('✅ [保存成功] 扫码记录已保存到数据库');
  1424. console.log(' - 记录ID:', record.id);
  1425. // 如果存在异业合作伙伴ID,处理异业绑定和扫码次数
  1426. if (partnerId) {
  1427. try {
  1428. console.log('🤝 ===========================================');
  1429. console.log('🤝 [异业处理] 开始处理异业扫码逻辑');
  1430. console.log('🤝 用户ID:', currentUser.id);
  1431. console.log('🤝 异业ID:', partnerId);
  1432. console.log('🤝 门店ID:', storeId);
  1433. console.log('🤝 ===========================================');
  1434. // 检查用户是否已经绑定了异业合作伙伴
  1435. const userBoundPartner = currentUser.get('Partner');
  1436. const userBoundPartnerId = userBoundPartner ? userBoundPartner.id : null;
  1437. console.log('🔍 [绑定检查] 用户已绑定的异业ID:', userBoundPartnerId || '无');
  1438. // 规则1:如果用户已绑定其他异业,不处理当前异业的扫码
  1439. if (userBoundPartnerId && userBoundPartnerId !== partnerId) {
  1440. console.log('⚠️ [规则1] 用户已绑定其他异业,不处理当前异业的扫码');
  1441. console.log(' - 已绑定异业:', userBoundPartnerId);
  1442. console.log(' - 当前扫码异业:', partnerId);
  1443. console.log('🤝 ===========================================');
  1444. return;
  1445. }
  1446. // 规则2:检查该用户在该门店是否已经扫过该异业的码
  1447. console.log('🔍 [重复检查] 检查是否已扫过该异业的码...');
  1448. const ScanRecord = Parse.Object.extend('ScanRecord');
  1449. const scanQuery = new Parse.Query(ScanRecord);
  1450. scanQuery.equalTo('user', {
  1451. __type: 'Pointer',
  1452. className: '_User',
  1453. objectId: currentUser.id
  1454. });
  1455. scanQuery.equalTo('partnerId', partnerId);
  1456. scanQuery.equalTo('storeId', storeId);
  1457. const existingScan = await scanQuery.first();
  1458. if (existingScan) {
  1459. console.log('⚠️ [规则2] 该用户在该门店已扫过该异业的码,不重复计数');
  1460. console.log(' - 首次扫码时间:', existingScan.get('scanTime'));
  1461. console.log(' - 记录ID:', existingScan.id);
  1462. console.log('🤝 ===========================================');
  1463. return;
  1464. }
  1465. console.log('✅ [首次扫码] 该用户在该门店首次扫该异业码');
  1466. // 规则3:如果用户还没有绑定异业,绑定当前异业
  1467. if (!userBoundPartnerId) {
  1468. console.log('🔗 [规则3] 用户首次扫异业码,开始绑定异业合作伙伴');
  1469. console.log(' - 异业ID:', partnerId);
  1470. currentUser.set('Partner', {
  1471. __type: 'Pointer',
  1472. className: 'Partner',
  1473. objectId: partnerId
  1474. });
  1475. await currentUser.save();
  1476. console.log('✅ [绑定成功] 异业绑定成功');
  1477. } else {
  1478. console.log('ℹ️ [已绑定] 用户已绑定该异业,无需重复绑定');
  1479. }
  1480. // 规则4:为该异业在该门店增加扫码次数
  1481. console.log('📊 [规则4] 开始更新异业扫码次数...');
  1482. const Partner = Parse.Object.extend('Partner');
  1483. const partnerQuery = new Parse.Query(Partner);
  1484. const partnerObj = await partnerQuery.get(partnerId);
  1485. if (partnerObj) {
  1486. console.log('📋 [异业信息] 找到异业合作伙伴记录');
  1487. console.log(' - 异业ID:', partnerObj.id);
  1488. console.log(' - 异业名称:', partnerObj.get('name') || '未设置');
  1489. // 获取或初始化门店扫码次数映射
  1490. let storeScans = partnerObj.get('storeScans') || {};
  1491. const oldCount = storeScans[storeId] || 0;
  1492. // 为该门店的扫码次数 +1
  1493. storeScans[storeId] = oldCount + 1;
  1494. partnerObj.set('storeScans', storeScans);
  1495. // 同时更新总扫码次数
  1496. const oldTotalCount = partnerObj.get('scanCount') || 0;
  1497. partnerObj.increment('scanCount', 1);
  1498. await partnerObj.save();
  1499. console.log('✅ [更新成功] 异业扫码次数已更新');
  1500. console.log(' - 门店ID:', storeId);
  1501. console.log(' - 该门店扫码次数: %d → %d', oldCount, storeScans[storeId]);
  1502. console.log(' - 总扫码次数: %d → %d', oldTotalCount, oldTotalCount + 1);
  1503. } else {
  1504. console.error('❌ [错误] 未找到异业合作伙伴记录');
  1505. }
  1506. console.log('🤝 ===========================================');
  1507. } catch (e) {
  1508. console.error('❌ [异业处理失败] 处理异业合作伙伴绑定失败:', e);
  1509. console.error(' - 错误信息:', e.message);
  1510. console.error(' - 错误堆栈:', e.stack);
  1511. console.log('🤝 ===========================================');
  1512. }
  1513. }
  1514. } catch (error) {
  1515. console.warn('⚠️ 保存扫码记录失败:', error);
  1516. // 不影响主流程
  1517. }
  1518. }
  1519. });
  1520. async function decrementStoreTrafficImpl(Parse, storeId) {
  1521. const currentUser = Parse.User.current();
  1522. const sessionToken = currentUser && typeof currentUser.getSessionToken === 'function'
  1523. ? currentUser.getSessionToken()
  1524. : null;
  1525. const requestOptions = sessionToken ? { sessionToken } : undefined;
  1526. console.log('🧾 [traffic] 开始扣减 ShopStore.traffic');
  1527. console.log('🧾 [traffic] storeId:', storeId);
  1528. console.log('🧾 [traffic] userId:', currentUser ? currentUser.id : '无用户');
  1529. console.log('🧾 [traffic] hasSessionToken:', sessionToken ? 'true' : 'false');
  1530. const storeQuery = new Parse.Query('ShopStore');
  1531. const store = await storeQuery.get(storeId, requestOptions);
  1532. if (!store) {
  1533. console.error('🧾 [traffic] 未找到门店记录, storeId:', storeId);
  1534. throw new Error('未找到门店记录');
  1535. }
  1536. const trafficValue = store.get('traffic');
  1537. const trafficNumber = Number(trafficValue);
  1538. const traffic = Number.isFinite(trafficNumber) ? trafficNumber : 0;
  1539. const next = Math.max(traffic - 1, 0);
  1540. console.log('🧾 [traffic] before trafficValue:', trafficValue);
  1541. console.log('🧾 [traffic] parsed traffic:', traffic);
  1542. console.log('🧾 [traffic] next:', next);
  1543. if (next !== traffic || trafficValue === undefined) {
  1544. store.set('traffic', next);
  1545. const saved = await store.save(null, requestOptions);
  1546. console.log('🧾 [traffic] 保存成功 storeObjectId:', saved && saved.id ? saved.id : store.id);
  1547. }
  1548. console.log('🧾 [traffic] 结束扣减 ShopStore.traffic');
  1549. }