index.js 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197
  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. wx.login({
  25. success: function (res) {
  26. if (res.code) {
  27. console.log(res);
  28. // wx.request({
  29. // url: "https://server.fmode.cn/api/wxapp/auth_wxapp",
  30. // data: {
  31. // c: getApp().globalData.company,
  32. // code: res.code,
  33. // },
  34. // async success(res) {
  35. // wx.setStorageSync("userInfo", res.data);
  36. // resolve(res)
  37. // },
  38. // });
  39. }
  40. },
  41. fail: function (err) {
  42. wx.showToast({
  43. title: '服务器繁忙,请稍后重试',
  44. })
  45. }
  46. });
  47. wx.setStorageSync("invite", null);
  48. // 处理扫码链接(options.q 包含完整的URL或activityId)
  49. if (options.q) {
  50. let str = decodeURIComponent(options.q); // 扫描二维码获得的跳转链接
  51. // 兼容一些环境中把 & 编码成 & 的情况,防止参数解析错误(如 scanCount=0&storeId=...)
  52. if (str.indexOf('&') !== -1) {
  53. console.log('🔧 检测到 URL 中包含 &,自动还原为 &');
  54. str = str.replace(/&/g, '&');
  55. }
  56. // 检查是否是员工邀请链接(app.fmode.cn/dev/pobingfeng/manager/staff?invite=xxx)
  57. if (str.includes('app.fmode.cn/dev/pobingfeng/manager/staff')) {
  58. let obj = this.getParaName(str);
  59. if (obj && obj.invite) {
  60. wx.setStorageSync("invite", obj.invite);
  61. console.log('✅ 检测到员工邀请链接,invite:', obj.invite);
  62. }
  63. // 员工邀请链接不需要跳转到店铺,直接返回
  64. this.setData({ options: options });
  65. plugin.init(config, wx.getStorageSync('invite'));
  66. return;
  67. }
  68. // 检查是否是活动海报二维码(activityId作为qrCode参数值)
  69. // 如果str不包含http/https,且看起来像是一个ID,则可能是activityId
  70. if (!str.includes('http://') && !str.includes('https://') && !str.includes('?')) {
  71. // 可能是活动ID,尝试作为activityId处理
  72. if (str && str.length > 0 && !isNaN(str)) {
  73. options.activityId = str;
  74. console.log('✅ 检测到活动海报二维码,activityId:', str);
  75. // 活动二维码需要特殊处理,先保存activityId
  76. wx.setStorageSync("activityId", str);
  77. }
  78. }
  79. // 处理店铺相关的二维码链接(pwa.fmode.cn/gomini/pmd/)
  80. // 从完整URL中提取参数
  81. if (str.includes('pwa.fmode.cn/gomini/pmd') || str.includes('?')) {
  82. let obj = this.getParaName(str);
  83. if (obj && obj.invite) {
  84. wx.setStorageSync("invite", obj.invite);
  85. }
  86. // 将所有参数合并到 options 中
  87. obj && Object.keys(obj).forEach(key=> options[key] = obj[key]);
  88. }
  89. }
  90. // 兼容从编译参数或页面直达传入的 storeId
  91. if (options && options.scene && !options.storeId) {
  92. try {
  93. const sceneStr = decodeURIComponent(options.scene)
  94. if (sceneStr) {
  95. const pairs = sceneStr.split('&')
  96. for (const p of pairs) {
  97. const [k, v] = p.split('=')
  98. if (k === 'storeId' && v) {
  99. options.storeId = v
  100. break
  101. }
  102. }
  103. }
  104. } catch (e) {
  105. console.warn('解析 scene 失败:', e)
  106. }
  107. }
  108. this.setData({
  109. options: options
  110. })
  111. let {
  112. time,
  113. dramaId,
  114. roomId,
  115. orderId,
  116. shopId,
  117. invite,
  118. activityId,
  119. company,
  120. inviteHost,
  121. storeId
  122. } = options
  123. time && wx.setStorageSync("time", time);
  124. dramaId && wx.setStorageSync("dramaId", dramaId);
  125. roomId && wx.setStorageSync("roomId", roomId);
  126. orderId && wx.setStorageSync("orderId", orderId);
  127. shopId && wx.setStorageSync("shopId", shopId);
  128. invite && wx.setStorageSync("invite", invite);
  129. activityId && wx.setStorageSync("activityId", activityId);
  130. inviteHost && wx.setStorageSync("inviteHost", true);
  131. if (storeId) {
  132. // 扫码进入时,强制设置店铺 ID,标记为扫码来源
  133. wx.setStorageSync('storeId', storeId)
  134. getApp().globalData.storeId = storeId
  135. wx.setStorageSync('storeId_from_scan', true)
  136. console.log('✅ 入口页扫码进入,已设置店铺 ID(优先级最高):', storeId)
  137. }
  138. if (company) getApp().globalData.toCompany = true;
  139. // 检查是否是扫码进入(需要统计扫码次数)
  140. this.checkAndHandleScan(options);
  141. plugin.init(config, wx.getStorageSync('invite'))
  142. },
  143. /**
  144. * 生命周期函数--监听页面初次渲染完成
  145. */
  146. onReady: async function () { },
  147. /**
  148. * 生命周期函数--监听页面显示
  149. */
  150. onShow: async function () {
  151. await this.review()
  152. },
  153. async review(force){
  154. try {
  155. let options = this.data.options
  156. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath
  157. if (options) {
  158. let objArr = Object.keys(options)
  159. if (objArr && objArr.length > 0) {
  160. let parms = '?'
  161. objArr.forEach((o, index) => {
  162. if (index > 0) {
  163. parms += '&' + o + '=' + options[o]
  164. } else {
  165. parms += o + '=' + options[o]
  166. }
  167. })
  168. url += parms
  169. }
  170. }
  171. let currentUser = Parse.User.current()
  172. console.log('===========================================');
  173. console.log('======= index.js review 方法 =======');
  174. console.log('当前用户:', currentUser ? currentUser.id : '无');
  175. console.log('用户手机号:', currentUser?.get('mobile') || '无');
  176. console.log('用户名:', currentUser?.get('username') || '无');
  177. console.log('Session Token:', currentUser?.getSessionToken()?.substring(0, 20) || '无');
  178. console.log('userLogin 存储:', wx.getStorageSync('userLogin') || '无');
  179. console.log('force 参数:', force);
  180. console.log('===========================================');
  181. // 查询 Company 的 isPublishing 字段
  182. let isPublishing = false;
  183. try {
  184. const companyQuery = new Parse.Query('Company');
  185. companyQuery.equalTo('objectId', getApp().globalData.company);
  186. companyQuery.select('isPublishing');
  187. const companyObj = await companyQuery.first();
  188. if (companyObj) {
  189. isPublishing = companyObj.get('isPublishing') === true;
  190. console.log('📋 Company isPublishing:', isPublishing);
  191. }
  192. // 保存到全局,供其他页面使用
  193. getApp().globalData.isPublishing = isPublishing;
  194. wx.setStorageSync('isPublishing', isPublishing);
  195. } catch (error) {
  196. console.error('❌ 查询 Company isPublishing 失败:', error);
  197. // 查询失败时,默认为 false(强制登录)
  198. isPublishing = false;
  199. }
  200. // 根据 isPublishing 决定是否强制登录
  201. if (!currentUser || force) {
  202. console.log('🔄 开始调用 checkAuth...');
  203. // isPublishing == true 时不强制授权,否则强制授权
  204. const forceAuth = !isPublishing;
  205. console.log('🔐 是否强制登录:', forceAuth);
  206. let r = await checkAuth(forceAuth);
  207. console.log('===========================================');
  208. console.log('======= checkAuth 返回结果 =======');
  209. console.log('返回值:', r);
  210. // 重新获取用户信息
  211. currentUser = Parse.User.current();
  212. console.log('checkAuth 后的用户:', currentUser ? currentUser.id : '无');
  213. console.log('checkAuth 后的手机号:', currentUser?.get('mobile') || '无');
  214. console.log('checkAuth 后的 Session Token:', currentUser?.getSessionToken()?.substring(0, 20) || '无');
  215. console.log('===========================================');
  216. // 如果强制登录但用户未登录,不允许继续访问
  217. if (forceAuth && !r) {
  218. console.log('❌ 强制登录模式,用户未登录,停止访问');
  219. return;
  220. }
  221. // 即使登录失败,也允许继续访问(仅在非强制登录模式下)
  222. if(!r) {
  223. console.log('⚠️ 用户未登录或拒绝授权,允许游客访问');
  224. // 不要 return,继续执行后面的跳转逻辑
  225. } else {
  226. // 登录成功,设置 userLogin
  227. if (currentUser && currentUser.get('mobile')) {
  228. wx.setStorageSync("userLogin", currentUser.id);
  229. console.log('✅ 授权登录成功,已设置 userLogin:', currentUser.id);
  230. console.log('✅ 用户手机号:', currentUser.get('mobile'));
  231. }
  232. // 检查是否有待记录的扫码信息
  233. await this.checkAndRecordPendingScan();
  234. }
  235. } else {
  236. // 用户已登录,确保 userLogin 已设置
  237. if (currentUser.get('mobile')) {
  238. wx.setStorageSync("userLogin", currentUser.id);
  239. }
  240. this.updateUser(currentUser.id)
  241. // 用户已登录,检查是否有待记录的扫码信息
  242. await this.checkAndRecordPendingScan();
  243. }
  244. // 游客模式(isPublishing == true)
  245. if (!currentUser || force) {
  246. console.log('🔄 游客模式,开始调用 checkAuth(false)...');
  247. // 尝试静默登录(不强制授权)
  248. let r = await checkAuth(false); // 不强制授权
  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(!r) {
  260. console.log('⚠️ 用户未登录或拒绝授权,允许游客访问');
  261. // 不要 return,继续执行后面的跳转逻辑
  262. } else {
  263. // 登录成功,设置 userLogin❌ 强制登录模式,用户未登录,停止访问');
  264. return;
  265. }
  266. // 即使登录失败,也允许继续访问(仅在非强制登录模式下)
  267. if(!r) {
  268. console.log('⚠️ 用户未登录或拒绝授权,允许游客访问');
  269. // 不要 return,继续执行后面的跳转逻辑
  270. } else {
  271. // 登录成功,设置 userLogin
  272. if (currentUser && currentUser.get('mobile')) {
  273. wx.setStorageSync("userLogin", currentUser.id);
  274. console.log('✅ 授权登录成功,已设置 userLogin:', currentUser.id);
  275. console.log('✅ 用户手机号:', currentUser.get('mobile'));
  276. } else {
  277. console.warn('⚠️ checkAuth 返回成功,但用户没有手机号!');
  278. console.warn(' 用户对象:', currentUser);
  279. }
  280. // 检查是否有待记录的扫码信息
  281. await this.checkAndRecordPendingScan();
  282. }
  283. } else {
  284. console.log('✅ 用户已登录,跳过 checkAuth');
  285. // 用户已登录,确保 userLogin 已设置
  286. if (currentUser.get('mobile')) {
  287. wx.setStorageSync("userLogin", currentUser.id);
  288. console.log('✅ 已确认 userLogin:', currentUser.id);
  289. }
  290. this.updateUser(currentUser.id)
  291. // 用户已登录,检查是否有待记录的扫码信息
  292. await this.checkAndRecordPendingScan();
  293. }
  294. getApp().Parse = Parse
  295. getApp().checkAuth = checkAuth
  296. if (!await this.getCompanyServerExpire(url)) {
  297. return
  298. }
  299. // 检查是否需要跳转到活动页面
  300. if (wx.getStorageSync('need_activity_redirect') === true) {
  301. await this.redirectToActivityPage();
  302. return;
  303. }
  304. // 检查是否需要跳转到扫码统计页面
  305. if (this.shouldRedirectToScanPage()) {
  306. await this.redirectToScanPage();
  307. return;
  308. }
  309. console.log('✅ 准备跳转到:', url);
  310. wx.redirectTo({
  311. url: url,
  312. success: () => {
  313. console.log('✅ redirectTo 跳转成功');
  314. },
  315. fail: (err) => {
  316. console.error('❌ redirectTo 失败:', err);
  317. console.log('⚠️ 尝试使用 reLaunch');
  318. // 降级:尝试使用 reLaunch
  319. wx.reLaunch({
  320. url: url,
  321. success: () => {
  322. console.log('✅ reLaunch 跳转成功');
  323. },
  324. fail: (err2) => {
  325. console.error('❌ reLaunch 也失败:', err2);
  326. // 显示错误提示
  327. this.setData({ loading: false });
  328. wx.showModal({
  329. title: '温馨提示',
  330. content: '页面加载失败,请检查网络后重试。',
  331. showCancel: true,
  332. cancelText: '退出',
  333. confirmText: '重试',
  334. success: (result) => {
  335. if (result.confirm) {
  336. this.review(true);
  337. } else {
  338. wx.exitMiniProgram();
  339. }
  340. }
  341. });
  342. }
  343. });
  344. }
  345. });
  346. }
  347. catch (err) {
  348. console.log(err);
  349. /* 登录身份信息到期,重新登陆 */
  350. if((err?.message.indexOf('Session token is expired') != -1 || err?.message.indexOf('Invalid session token') != -1) && !force){
  351. let invite = wx.getStorageSync('invite')
  352. wx.clearStorageSync()
  353. invite && wx.setStorageSync('invite', invite)
  354. /* 强制重新登录 */
  355. this.review(true)
  356. return
  357. }
  358. // 如果出错,尝试继续跳转到主页(游客模式)
  359. console.log('⚠️ 登录出错,尝试游客模式访问');
  360. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  361. if (this.data.options) {
  362. let objArr = Object.keys(this.data.options)
  363. if (objArr && objArr.length > 0) {
  364. let parms = '?'
  365. objArr.forEach((o, index) => {
  366. if (index > 0) {
  367. parms += '&' + o + '=' + this.data.options[o]
  368. } else {
  369. parms += o + '=' + this.data.options[o]
  370. }
  371. })
  372. url += parms
  373. }
  374. }
  375. wx.redirectTo({
  376. url: url,
  377. fail: () => {
  378. // 如果跳转失败,显示错误提示
  379. this.setData({
  380. loading:false
  381. })
  382. wx.showModal({
  383. title: '温馨提示',
  384. content: '页面加载失败,请检查网络后重试。',
  385. showCancel: true,
  386. cancelText: '退出',
  387. confirmText: '重试',
  388. success: (result) => {
  389. if (result.confirm) {
  390. // 用户选择重试
  391. this.review(true)
  392. } else {
  393. // 用户选择退出
  394. wx.exitMiniProgram()
  395. }
  396. },
  397. });
  398. }
  399. });
  400. }
  401. },
  402. /**
  403. * 检查并记录待处理的扫码统计
  404. */
  405. async checkAndRecordPendingScan() {
  406. try {
  407. const pendingScan = wx.getStorageSync('pending_scan_record');
  408. if (!pendingScan) {
  409. return;
  410. }
  411. console.log('===========================================');
  412. console.log('======= 发现待记录的扫码信息 =======');
  413. console.log('扫码信息:', pendingScan);
  414. console.log('===========================================');
  415. // 检查是否超时(24小时)
  416. const now = Date.now();
  417. const scanTime = pendingScan.timestamp || 0;
  418. const hoursPassed = (now - scanTime) / (1000 * 60 * 60);
  419. if (hoursPassed > 24) {
  420. console.log('⚠️ 扫码信息已超过24小时,不再记录');
  421. wx.removeStorageSync('pending_scan_record');
  422. return;
  423. }
  424. // 记录扫码统计
  425. await this.recordScanStatistics({
  426. storeId: pendingScan.storeId,
  427. sourceType: pendingScan.sourceType,
  428. sourceId: pendingScan.sourceId,
  429. ownerId: pendingScan.ownerId,
  430. employeeId: pendingScan.employeeId,
  431. partnerId: pendingScan.partnerId,
  432. userId: pendingScan.userId,
  433. productId: pendingScan.productId,
  434. scanCount: pendingScan.scanCount
  435. });
  436. // 清除待记录的扫码信息
  437. wx.removeStorageSync('pending_scan_record');
  438. console.log('✅ 扫码统计已记录并清除');
  439. } catch (error) {
  440. console.error('❌ 记录待处理扫码信息失败:', error);
  441. }
  442. },
  443. async updateUser(id) {
  444. let User = new Parse.Query('_User')
  445. let user = await User.get(id)
  446. let invite = wx.getStorageSync('invite')
  447. //查询邀请人user
  448. let query = new Parse.Query("_User")
  449. query.equalTo('objectId', invite)
  450. let result = await query.first()
  451. if (result && result.id && result.get("invite")?.id == user.id) {
  452. console.error('邀请人不能是自己的下级')
  453. return
  454. }
  455. if (invite && !user.get('invite') && user.id != invite && !user.get('agentLevel')) {
  456. console.log('上下级绑定成功');
  457. user.set('invite', {
  458. __type: "Pointer",
  459. className: "_User",
  460. objectId: invite
  461. })
  462. user.set('agent', {
  463. __type: "Pointer",
  464. className: "_User",
  465. objectId: invite
  466. })
  467. await Parse.Cloud.run('user_save', {
  468. userJson: user.toJSON()
  469. })
  470. }
  471. },
  472. async getCompanyServerExpire(url) {
  473. let query = new Parse.Query('Company')
  474. query.equalTo('objectId', getApp().globalData.company)
  475. query.select('expireDate', 'expireMap')
  476. let com = await query.first()
  477. if (com?.id && com?.get('expireDate')) {
  478. let now = + new Date()
  479. let expireTime = + new Date(com?.get('expireDate'))
  480. if (com?.get('expireMap') && com.get('expireMap')[getApp().globalData.appid]) {
  481. expireTime = + new Date(com.get('expireMap')[getApp().globalData.appid])
  482. }
  483. if (now >= expireTime) {
  484. console.log('服务器到期');
  485. wx.reLaunch({
  486. url: `common-page/pages/loading/index?url=${url}`,
  487. });
  488. return
  489. }
  490. }
  491. return true
  492. },
  493. onUnload: function () {
  494. wx.setStorageSync("active", 0);
  495. },
  496. getParaName(url) {
  497. if (!url || url.indexOf('?') == -1) {
  498. return
  499. }
  500. // 兼容 URL 中 & 的情况,先统一还原为 &
  501. if (url.indexOf('&') !== -1) {
  502. url = url.replace(/&/g, '&');
  503. }
  504. // 提取查询参数部分(去除可能的 hash 部分)
  505. let queryString = url.split('?')[1];
  506. // 如果包含 #,只取 # 之前的部分
  507. if (queryString.indexOf('#') !== -1) {
  508. queryString = queryString.split('#')[0];
  509. }
  510. return this.setObject(queryString) //封装成对象
  511. },
  512. setObject(paraArr) {
  513. let obj = {}
  514. let arr1 = paraArr.split('&')
  515. arr1.forEach(item => {
  516. let str = item.split('=')
  517. let key = str[0]
  518. let val = str[1]
  519. obj[key] = val
  520. })
  521. return obj
  522. },
  523. /**
  524. * 检查并处理扫码参数
  525. * 支持所有类型的二维码:
  526. * 1. 推广员二维码: ?scanCount=0&storeId=xxx&userId=xxx
  527. * 2. 产品二维码: ?scanCount=0&storeId=xxx&productId=xxx
  528. * 3. 案例二维码: ?scanCount=0&storeId=xxx&caseId=xxx
  529. * 4. 活动海报二维码: activityId作为qrCode参数值
  530. * 5. 异业合作伙伴二维码(移动端 / PC端统一): ?scanCount=xxx&storeId=xxx&partnerId=xxx
  531. * 6. 员工邀请二维码(移动端): ?scanCount=0&storeId=xxx&employeeId=xxx
  532. * 7. 我的二维码(老板): ?scanCount=0&storeId=xxx&ownerId=xxx
  533. * 8. 我的二维码(员工): ?scanCount=0&storeId=xxx&employeeId=xxx
  534. */
  535. checkAndHandleScan(options) {
  536. const {
  537. scanCount,
  538. ownerId,
  539. employeeId,
  540. partnerId,
  541. storeId,
  542. productId,
  543. caseId,
  544. userId, // 推广员二维码使用userId
  545. activityId
  546. } = options;
  547. // 处理活动海报二维码(activityId作为qrCode参数值)
  548. if (activityId && !storeId) {
  549. console.log('===========================================');
  550. console.log('======= 检测到活动海报二维码 =======');
  551. console.log('活动ID (activityId):', activityId);
  552. console.log('===========================================');
  553. // 活动二维码需要跳转到活动页面,不需要跳转到店铺
  554. wx.setStorageSync('scan_activityId', activityId);
  555. wx.setStorageSync('need_activity_redirect', true);
  556. return;
  557. }
  558. // 如果存在 storeId,说明是扫码进入具体门店(包括异业分享)
  559. if (storeId) {
  560. console.log('===========================================');
  561. console.log('======= 检测到扫码参数 =======');
  562. console.log('店铺ID (storeId):', storeId);
  563. console.log('扫码次数 (scanCount):', scanCount || '0');
  564. // 确定来源类型
  565. let sourceType = 'unknown';
  566. let sourceId = null;
  567. if (employeeId) {
  568. sourceType = 'employee';
  569. sourceId = employeeId;
  570. console.log('来源类型: 员工展业');
  571. console.log('员工ID (employeeId):', employeeId);
  572. } else if (userId) {
  573. // 推广员二维码使用userId
  574. sourceType = 'promoter';
  575. sourceId = userId;
  576. console.log('来源类型: 推广员展业');
  577. console.log('推广员ID (userId):', userId);
  578. } else if (ownerId) {
  579. sourceType = 'owner';
  580. sourceId = ownerId;
  581. console.log('来源类型: 老板展业');
  582. console.log('老板ID (ownerId):', ownerId);
  583. } else if (partnerId) {
  584. sourceType = 'partner';
  585. sourceId = partnerId;
  586. console.log('来源类型: 异业合作伙伴');
  587. console.log('合作伙伴ID (partnerId):', partnerId);
  588. } else {
  589. sourceType = 'store';
  590. console.log('来源类型: 店铺分享');
  591. }
  592. if (productId) {
  593. console.log('产品ID (productId):', productId);
  594. }
  595. if (caseId) {
  596. console.log('案例ID (caseId):', caseId);
  597. }
  598. console.log('===========================================');
  599. // 保存到临时存储,用于后续跳转和统计
  600. wx.setStorageSync('scan_storeId', storeId);
  601. wx.setStorageSync('scan_scanCount', scanCount || '0');
  602. wx.setStorageSync('scan_sourceType', sourceType);
  603. wx.setStorageSync('scan_sourceId', sourceId || '');
  604. wx.setStorageSync('scan_ownerId', ownerId || '');
  605. wx.setStorageSync('scan_employeeId', employeeId || '');
  606. wx.setStorageSync('scan_partnerId', partnerId || '');
  607. wx.setStorageSync('scan_userId', userId || '');
  608. if (productId) {
  609. wx.setStorageSync('scan_productId', productId);
  610. }
  611. if (caseId) {
  612. wx.setStorageSync('scan_caseId', caseId);
  613. }
  614. wx.setStorageSync('need_scan_redirect', true);
  615. } else if (partnerId) {
  616. // 理论上异业二维码现在也必须带 storeId,这里仅作为兜底保护
  617. console.warn('⚠️ 检测到异业合作伙伴二维码缺少 storeId,无法确定门店,请检查二维码生成逻辑');
  618. }
  619. },
  620. /**
  621. * 判断是否需要跳转到扫码统计页面
  622. */
  623. shouldRedirectToScanPage() {
  624. return wx.getStorageSync('need_scan_redirect') === true;
  625. },
  626. /**
  627. * 跳转到扫码对应的店铺页面
  628. * 根据 storeId 跳转到对应店铺,同时记录所有来源信息用于统计
  629. */
  630. async redirectToScanPage() {
  631. try {
  632. // 获取所有扫码相关参数
  633. const storeId = wx.getStorageSync('scan_storeId');
  634. const scanCount = wx.getStorageSync('scan_scanCount');
  635. const sourceType = wx.getStorageSync('scan_sourceType');
  636. const sourceId = wx.getStorageSync('scan_sourceId');
  637. const ownerId = wx.getStorageSync('scan_ownerId');
  638. const employeeId = wx.getStorageSync('scan_employeeId');
  639. const partnerId = wx.getStorageSync('scan_partnerId');
  640. const userId = wx.getStorageSync('scan_userId');
  641. const productId = wx.getStorageSync('scan_productId');
  642. const caseId = wx.getStorageSync('scan_caseId');
  643. // 清除临时存储
  644. wx.removeStorageSync('scan_storeId');
  645. wx.removeStorageSync('scan_scanCount');
  646. wx.removeStorageSync('scan_sourceType');
  647. wx.removeStorageSync('scan_sourceId');
  648. wx.removeStorageSync('scan_ownerId');
  649. wx.removeStorageSync('scan_employeeId');
  650. wx.removeStorageSync('scan_partnerId');
  651. wx.removeStorageSync('scan_userId');
  652. wx.removeStorageSync('scan_productId');
  653. wx.removeStorageSync('scan_caseId');
  654. wx.removeStorageSync('need_scan_redirect');
  655. if (!storeId) {
  656. console.error('❌ 缺少 storeId 参数,无法跳转');
  657. return;
  658. }
  659. console.log('===========================================');
  660. console.log('======= 扫码进入店铺 =======');
  661. console.log('店铺 ID (storeId):', storeId);
  662. console.log('来源类型 (sourceType):', sourceType);
  663. console.log('来源 ID (sourceId):', sourceId || '无');
  664. console.log('扫码次数 (scanCount):', scanCount);
  665. if (ownerId) console.log('老板 ID (ownerId):', ownerId);
  666. if (employeeId) console.log('员工 ID (employeeId):', employeeId);
  667. if (partnerId) console.log('合作伙伴 ID (partnerId):', partnerId);
  668. if (userId) console.log('推广员 ID (userId):', userId);
  669. if (productId) console.log('产品 ID (productId):', productId);
  670. if (caseId) console.log('案例 ID (caseId):', caseId);
  671. console.log('===========================================');
  672. // 设置店铺 ID 到全局和本地存储
  673. wx.setStorageSync('storeId', storeId);
  674. getApp().globalData.storeId = storeId;
  675. // 保存来源信息到本地存储(用于后续统计或绑定关系)
  676. if (sourceId) {
  677. wx.setStorageSync('scan_from_sourceType', sourceType);
  678. wx.setStorageSync('scan_from_sourceId', sourceId);
  679. console.log('✅ 已记录来源信息:', sourceType, sourceId);
  680. }
  681. // 检查用户是否已登录
  682. const currentUser = Parse.User.current();
  683. if (currentUser) {
  684. // 用户已登录,立即记录扫码统计
  685. console.log('✅ 用户已登录,记录扫码统计');
  686. await this.recordScanStatistics({
  687. storeId,
  688. sourceType,
  689. sourceId,
  690. ownerId,
  691. employeeId,
  692. partnerId,
  693. userId,
  694. productId,
  695. scanCount
  696. });
  697. } else {
  698. // 用户未登录,保存扫码信息,等登录后再记录
  699. console.log('⚠️ 用户未登录,保存扫码信息待登录后记录');
  700. wx.setStorageSync('pending_scan_record', {
  701. storeId,
  702. sourceType,
  703. sourceId,
  704. ownerId,
  705. employeeId,
  706. partnerId,
  707. userId,
  708. productId,
  709. scanCount,
  710. timestamp: Date.now()
  711. });
  712. }
  713. // 如果有产品ID,直接跳转到产品详情的 H5 页面
  714. if (productId) {
  715. console.log('🎯 检测到产品ID,跳转到产品详情页');
  716. await this.redirectToProductDetail(storeId, productId, partnerId);
  717. return;
  718. }
  719. // 如果有案例ID,直接跳转到案例详情的 H5 页面
  720. if (caseId) {
  721. console.log('🎯 检测到案例ID,跳转到案例详情页');
  722. await this.redirectToCaseDetail(storeId, caseId, partnerId);
  723. return;
  724. }
  725. // 获取默认首页路径并跳转
  726. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  727. url += `?storeId=${storeId}`;
  728. // 如果有产品ID,添加到URL参数中
  729. if (productId) {
  730. url += `&productId=${productId}`;
  731. }
  732. // 如果有异业合作伙伴ID,添加到URL参数中传回web-view
  733. if (partnerId) {
  734. url += `&partnerId=${partnerId}`;
  735. }
  736. console.log('✅ 跳转到店铺页面:', url);
  737. wx.redirectTo({
  738. url: url,
  739. fail: (err) => {
  740. console.error('❌ 跳转失败:', err);
  741. // 如果 redirectTo 失败,尝试 reLaunch
  742. wx.reLaunch({
  743. url: url,
  744. fail: (err2) => {
  745. console.error('❌ reLaunch 也失败:', err2);
  746. }
  747. });
  748. }
  749. });
  750. } catch (error) {
  751. console.error('❌ 跳转到店铺页面失败:', error);
  752. }
  753. },
  754. /**
  755. * 跳转到活动页面
  756. */
  757. async redirectToActivityPage() {
  758. try {
  759. const activityId = wx.getStorageSync('scan_activityId');
  760. // 清除临时存储
  761. wx.removeStorageSync('scan_activityId');
  762. wx.removeStorageSync('need_activity_redirect');
  763. if (!activityId) {
  764. console.error('❌ 缺少 activityId 参数,无法跳转');
  765. return;
  766. }
  767. console.log('===========================================');
  768. console.log('======= 扫码进入活动页面 =======');
  769. console.log('活动 ID (activityId):', activityId);
  770. console.log('===========================================');
  771. // 保存活动ID
  772. wx.setStorageSync('activityId', activityId);
  773. // 获取默认首页路径并跳转(活动页面可能需要根据实际路由调整)
  774. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  775. url += `?activityId=${activityId}`;
  776. console.log('✅ 跳转到活动页面:', url);
  777. wx.redirectTo({
  778. url: url,
  779. fail: (err) => {
  780. console.error('❌ 跳转失败:', err);
  781. wx.reLaunch({
  782. url: url,
  783. fail: (err2) => {
  784. console.error('❌ reLaunch 也失败:', err2);
  785. }
  786. });
  787. }
  788. });
  789. } catch (error) {
  790. console.error('❌ 跳转到活动页面失败:', error);
  791. }
  792. },
  793. /**
  794. * 跳转到产品详情的 H5 页面
  795. * @param {string} storeId - 店铺 ID
  796. * @param {string} productId - 产品 ID
  797. * @param {string} partnerId - 可选的合作伙伴 ID
  798. */
  799. async redirectToProductDetail(storeId, productId, partnerId = null) {
  800. try {
  801. console.log('===========================================');
  802. console.log('======= 跳转到产品详情页 =======');
  803. console.log('店铺 ID:', storeId);
  804. console.log('产品 ID:', productId);
  805. if (partnerId) console.log('合作伙伴 ID:', partnerId);
  806. console.log('===========================================');
  807. const currentUser = Parse.User.current();
  808. const token = currentUser ? currentUser.getSessionToken() : null;
  809. // 构建产品详情的 H5 URL(不要在这里编码,后面统一编码)
  810. let h5Url = `https://app.fmode.cn/dev/pobingfeng/owner/nav/products?storeId=${storeId}`;
  811. // 如果有 token,添加到 URL
  812. if (token) {
  813. h5Url += `&token=${token}`;
  814. } else {
  815. // 如果没有 token,使用游客模式
  816. h5Url += `&guestMode=true`;
  817. }
  818. // 添加产品ID(不要在这里编码,避免双重编码)
  819. h5Url += `&productId=${productId}`;
  820. // 如果有合作伙伴ID,也添加到URL中
  821. if (partnerId) {
  822. h5Url += `&partnerId=${partnerId}`;
  823. }
  824. console.log('🌐 H5 URL:', h5Url);
  825. // 编码 URL(统一在这里编码一次)
  826. const encodedUrl = encodeURIComponent(h5Url);
  827. // 构建 web-view 页面路径
  828. let webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&storeId=${storeId}`;
  829. console.log('📄 web-view 页面路径:', webViewPath.substring(0, 100) + '...');
  830. console.log('===========================================');
  831. wx.redirectTo({
  832. url: webViewPath,
  833. success: () => {
  834. console.log('✅ 跳转到产品详情页成功');
  835. },
  836. fail: (err) => {
  837. console.error('❌ 跳转失败:', err);
  838. // 如果 redirectTo 失败,尝试 reLaunch
  839. wx.reLaunch({
  840. url: webViewPath,
  841. fail: (err2) => {
  842. console.error('❌ reLaunch 也失败:', err2);
  843. }
  844. });
  845. }
  846. });
  847. } catch (error) {
  848. console.error('❌ 跳转到产品详情页失败:', error);
  849. }
  850. },
  851. /**
  852. * 跳转到案例详情的 H5 页面
  853. * @param {string} storeId - 店铺 ID
  854. * @param {string} caseId - 案例 ID
  855. * @param {string} partnerId - 可选的合作伙伴 ID
  856. */
  857. async redirectToCaseDetail(storeId, caseId, partnerId = null) {
  858. try {
  859. console.log('===========================================');
  860. console.log('======= 跳转到案例详情页 =======');
  861. console.log('店铺 ID:', storeId);
  862. console.log('案例 ID:', caseId);
  863. if (partnerId) console.log('合作伙伴 ID:', partnerId);
  864. console.log('===========================================');
  865. const currentUser = Parse.User.current();
  866. const token = currentUser ? currentUser.getSessionToken() : null;
  867. // 构建案例详情的 H5 URL(不要在这里编码,后面统一编码)
  868. let h5Url = `https://app.fmode.cn/dev/pobingfeng/owner/nav/cases?storeId=${storeId}`;
  869. // 如果有 token,添加到 URL
  870. if (token) {
  871. h5Url += `&token=${token}`;
  872. } else {
  873. // 如果没有 token,使用游客模式
  874. h5Url += `&guestMode=true`;
  875. }
  876. // 添加案例ID(不要在这里编码,避免双重编码)
  877. h5Url += `&caseId=${caseId}`;
  878. // 如果有合作伙伴ID,也添加到URL中
  879. if (partnerId) {
  880. h5Url += `&partnerId=${partnerId}`;
  881. }
  882. console.log('🌐 H5 URL:', h5Url);
  883. // 编码 URL(统一在这里编码一次)
  884. const encodedUrl = encodeURIComponent(h5Url);
  885. // 构建 web-view 页面路径
  886. let webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&storeId=${storeId}`;
  887. console.log('📄 web-view 页面路径:', webViewPath.substring(0, 100) + '...');
  888. console.log('===========================================');
  889. wx.redirectTo({
  890. url: webViewPath,
  891. success: () => {
  892. console.log('✅ 跳转到案例详情页成功');
  893. },
  894. fail: (err) => {
  895. console.error('❌ 跳转失败:', err);
  896. // 如果 redirectTo 失败,尝试 reLaunch
  897. wx.reLaunch({
  898. url: webViewPath,
  899. fail: (err2) => {
  900. console.error('❌ reLaunch 也失败:', err2);
  901. }
  902. });
  903. }
  904. });
  905. } catch (error) {
  906. console.error('❌ 跳转到案例详情页失败:', error);
  907. }
  908. },
  909. /**
  910. * 记录扫码统计信息
  911. * @param {Object} params - 扫码参数对象
  912. * @param {string} params.storeId - 店铺 ID
  913. * @param {string} params.sourceType - 来源类型 (employee/owner/partner/promoter/store)
  914. * @param {string} params.sourceId - 来源 ID
  915. * @param {string} params.ownerId - 老板 ID
  916. * @param {string} params.employeeId - 员工 ID
  917. * @param {string} params.partnerId - 合作伙伴 ID
  918. * @param {string} params.userId - 推广员 ID
  919. * @param {string} params.productId - 产品 ID
  920. * @param {string} params.scanCount - 扫码次数
  921. */
  922. async recordScanStatistics(params) {
  923. try {
  924. const {
  925. storeId,
  926. sourceType,
  927. sourceId,
  928. ownerId,
  929. employeeId,
  930. partnerId,
  931. userId,
  932. productId,
  933. scanCount
  934. } = params;
  935. // 检查用户是否已登录
  936. const currentUser = Parse.User.current();
  937. if (!currentUser) {
  938. console.log('⚠️ 用户未登录,不记录扫码统计');
  939. return;
  940. }
  941. // 如果没有来源信息,不记录统计
  942. if (!sourceId && !ownerId && !employeeId && !partnerId && !userId) {
  943. console.log('ℹ️ 无来源信息,跳过统计记录');
  944. return;
  945. }
  946. console.log('✅ 用户已登录,记录扫码统计');
  947. // 创建扫码记录
  948. const ScanRecord = Parse.Object.extend('ScanRecord');
  949. const record = new ScanRecord();
  950. record.set('company', {
  951. __type: 'Pointer',
  952. className: 'Company',
  953. objectId: getApp().globalData.company
  954. });
  955. record.set('storeId', storeId);
  956. record.set('sourceType', sourceType || 'unknown');
  957. record.set('scanCount', parseInt(scanCount) || 0);
  958. record.set('scanTime', new Date());
  959. // 根据来源类型设置对应的ID
  960. if (ownerId) {
  961. record.set('ownerId', ownerId);
  962. }
  963. if (employeeId) {
  964. record.set('employeeId', employeeId);
  965. }
  966. if (partnerId) {
  967. record.set('partnerId', partnerId);
  968. }
  969. if (userId) {
  970. record.set('userId', userId);
  971. }
  972. if (productId) {
  973. record.set('productId', productId);
  974. }
  975. // 记录扫码用户
  976. record.set('user', {
  977. __type: 'Pointer',
  978. className: '_User',
  979. objectId: currentUser.id
  980. });
  981. await record.save();
  982. console.log('✅ 扫码记录已保存');
  983. // 如果存在异业合作伙伴ID,处理异业绑定和扫码次数
  984. if (partnerId) {
  985. try {
  986. console.log('===========================================');
  987. console.log('======= 处理异业扫码逻辑 =======');
  988. console.log('用户ID:', currentUser.id);
  989. console.log('异业ID:', partnerId);
  990. console.log('门店ID:', storeId);
  991. // 检查用户是否已经绑定了异业合作伙伴
  992. const userBoundPartner = currentUser.get('Partner');
  993. const userBoundPartnerId = userBoundPartner ? userBoundPartner.id : null;
  994. console.log('用户已绑定的异业ID:', userBoundPartnerId || '无');
  995. // 规则1:如果用户已绑定其他异业,不处理当前异业的扫码
  996. if (userBoundPartnerId && userBoundPartnerId !== partnerId) {
  997. console.log('⚠️ 用户已绑定其他异业,不处理当前异业的扫码');
  998. console.log(' 已绑定异业:', userBoundPartnerId);
  999. console.log(' 当前扫码异业:', partnerId);
  1000. console.log('===========================================');
  1001. return;
  1002. }
  1003. // 规则2:检查该用户在该门店是否已经扫过该异业的码
  1004. const ScanRecord = Parse.Object.extend('ScanRecord');
  1005. const scanQuery = new Parse.Query(ScanRecord);
  1006. scanQuery.equalTo('user', {
  1007. __type: 'Pointer',
  1008. className: '_User',
  1009. objectId: currentUser.id
  1010. });
  1011. scanQuery.equalTo('partnerId', partnerId);
  1012. scanQuery.equalTo('storeId', storeId);
  1013. const existingScan = await scanQuery.first();
  1014. if (existingScan) {
  1015. console.log('⚠️ 该用户在该门店已扫过该异业的码,不重复计数');
  1016. console.log(' 首次扫码时间:', existingScan.get('scanTime'));
  1017. console.log('===========================================');
  1018. return;
  1019. }
  1020. console.log('✅ 该用户在该门店首次扫该异业码');
  1021. // 规则3:如果用户还没有绑定异业,绑定当前异业
  1022. if (!userBoundPartnerId) {
  1023. console.log('🔗 用户首次扫异业码,绑定异业合作伙伴:', partnerId);
  1024. currentUser.set('Partner', {
  1025. __type: 'Pointer',
  1026. className: 'Partner',
  1027. objectId: partnerId
  1028. });
  1029. await currentUser.save();
  1030. console.log('✅ 异业绑定成功');
  1031. }
  1032. // 规则4:为该异业在该门店增加扫码次数
  1033. const Partner = Parse.Object.extend('Partner');
  1034. const partnerQuery = new Parse.Query(Partner);
  1035. const partnerObj = await partnerQuery.get(partnerId);
  1036. if (partnerObj) {
  1037. // 获取或初始化门店扫码次数映射
  1038. let storeScans = partnerObj.get('storeScans') || {};
  1039. // 为该门店的扫码次数 +1
  1040. storeScans[storeId] = (storeScans[storeId] || 0) + 1;
  1041. partnerObj.set('storeScans', storeScans);
  1042. // 同时更新总扫码次数
  1043. partnerObj.increment('scanCount', 1);
  1044. await partnerObj.save();
  1045. console.log('✅ 异业扫码次数已更新');
  1046. console.log(' 门店ID:', storeId);
  1047. console.log(' 该门店扫码次数:', storeScans[storeId]);
  1048. console.log(' 总扫码次数:', partnerObj.get('scanCount'));
  1049. }
  1050. console.log('===========================================');
  1051. } catch (e) {
  1052. console.error('❌ 处理异业合作伙伴绑定失败:', e);
  1053. console.log('===========================================');
  1054. }
  1055. }
  1056. } catch (error) {
  1057. console.warn('⚠️ 保存扫码记录失败:', error);
  1058. // 不影响主流程
  1059. }
  1060. }
  1061. });