index.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  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. wx.setStorageSync('storeId', storeId)
  133. getApp().globalData.storeId = storeId
  134. console.log('✅ 入口页已设置店铺 ID:', storeId)
  135. }
  136. if (company) getApp().globalData.toCompany = true;
  137. // 检查是否是扫码进入(需要统计扫码次数)
  138. this.checkAndHandleScan(options);
  139. plugin.init(config, wx.getStorageSync('invite'))
  140. },
  141. /**
  142. * 生命周期函数--监听页面初次渲染完成
  143. */
  144. onReady: async function () { },
  145. /**
  146. * 生命周期函数--监听页面显示
  147. */
  148. onShow: async function () {
  149. await this.review()
  150. },
  151. async review(force){
  152. try {
  153. let options = this.data.options
  154. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath
  155. if (options) {
  156. let objArr = Object.keys(options)
  157. if (objArr && objArr.length > 0) {
  158. let parms = '?'
  159. objArr.forEach((o, index) => {
  160. if (index > 0) {
  161. parms += '&' + o + '=' + options[o]
  162. } else {
  163. parms += o + '=' + options[o]
  164. }
  165. })
  166. url += parms
  167. }
  168. }
  169. let currentUser = Parse.User.current()
  170. console.log(Parse.User.current())
  171. // 修改:不强制登录,允许游客访问
  172. if (!currentUser || force) {
  173. // 尝试静默登录(不强制授权)
  174. let r = await checkAuth(false) // 改为 false,不强制授权
  175. console.log(r);
  176. // 即使登录失败,也允许继续访问
  177. if(!r) {
  178. console.log('⚠️ 用户未登录或拒绝授权,允许游客访问');
  179. // 不要 return,继续执行后面的跳转逻辑
  180. } else {
  181. // 登录成功,检查是否有待记录的扫码信息
  182. await this.checkAndRecordPendingScan();
  183. }
  184. } else {
  185. this.updateUser(currentUser.id)
  186. // 用户已登录,检查是否有待记录的扫码信息
  187. await this.checkAndRecordPendingScan();
  188. }
  189. getApp().Parse = Parse
  190. getApp().checkAuth = checkAuth
  191. if (!await this.getCompanyServerExpire(url)) {
  192. return
  193. }
  194. // 检查是否需要跳转到活动页面
  195. if (wx.getStorageSync('need_activity_redirect') === true) {
  196. await this.redirectToActivityPage();
  197. return;
  198. }
  199. // 检查是否需要跳转到扫码统计页面
  200. if (this.shouldRedirectToScanPage()) {
  201. await this.redirectToScanPage();
  202. return;
  203. }
  204. console.log('✅ 准备跳转到:', url);
  205. wx.redirectTo({
  206. url: url,
  207. success: () => {
  208. console.log('✅ redirectTo 跳转成功');
  209. },
  210. fail: (err) => {
  211. console.error('❌ redirectTo 失败:', err);
  212. console.log('⚠️ 尝试使用 reLaunch');
  213. // 降级:尝试使用 reLaunch
  214. wx.reLaunch({
  215. url: url,
  216. success: () => {
  217. console.log('✅ reLaunch 跳转成功');
  218. },
  219. fail: (err2) => {
  220. console.error('❌ reLaunch 也失败:', err2);
  221. // 显示错误提示
  222. this.setData({ loading: false });
  223. wx.showModal({
  224. title: '温馨提示',
  225. content: '页面加载失败,请检查网络后重试。',
  226. showCancel: true,
  227. cancelText: '退出',
  228. confirmText: '重试',
  229. success: (result) => {
  230. if (result.confirm) {
  231. this.review(true);
  232. } else {
  233. wx.exitMiniProgram();
  234. }
  235. }
  236. });
  237. }
  238. });
  239. }
  240. });
  241. }
  242. catch (err) {
  243. console.log(err);
  244. /* 登录身份信息到期,重新登陆 */
  245. if((err?.message.indexOf('Session token is expired') != -1 || err?.message.indexOf('Invalid session token') != -1) && !force){
  246. let invite = wx.getStorageSync('invite')
  247. wx.clearStorageSync()
  248. invite && wx.setStorageSync('invite', invite)
  249. /* 强制重新登录 */
  250. this.review(true)
  251. return
  252. }
  253. // 如果出错,尝试继续跳转到主页(游客模式)
  254. console.log('⚠️ 登录出错,尝试游客模式访问');
  255. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  256. if (this.data.options) {
  257. let objArr = Object.keys(this.data.options)
  258. if (objArr && objArr.length > 0) {
  259. let parms = '?'
  260. objArr.forEach((o, index) => {
  261. if (index > 0) {
  262. parms += '&' + o + '=' + this.data.options[o]
  263. } else {
  264. parms += o + '=' + this.data.options[o]
  265. }
  266. })
  267. url += parms
  268. }
  269. }
  270. wx.redirectTo({
  271. url: url,
  272. fail: () => {
  273. // 如果跳转失败,显示错误提示
  274. this.setData({
  275. loading:false
  276. })
  277. wx.showModal({
  278. title: '温馨提示',
  279. content: '页面加载失败,请检查网络后重试。',
  280. showCancel: true,
  281. cancelText: '退出',
  282. confirmText: '重试',
  283. success: (result) => {
  284. if (result.confirm) {
  285. // 用户选择重试
  286. this.review(true)
  287. } else {
  288. // 用户选择退出
  289. wx.exitMiniProgram()
  290. }
  291. },
  292. });
  293. }
  294. });
  295. }
  296. },
  297. /**
  298. * 检查并记录待处理的扫码统计
  299. */
  300. async checkAndRecordPendingScan() {
  301. try {
  302. const pendingScan = wx.getStorageSync('pending_scan_record');
  303. if (!pendingScan) {
  304. return;
  305. }
  306. console.log('===========================================');
  307. console.log('======= 发现待记录的扫码信息 =======');
  308. console.log('扫码信息:', pendingScan);
  309. console.log('===========================================');
  310. // 检查是否超时(24小时)
  311. const now = Date.now();
  312. const scanTime = pendingScan.timestamp || 0;
  313. const hoursPassed = (now - scanTime) / (1000 * 60 * 60);
  314. if (hoursPassed > 24) {
  315. console.log('⚠️ 扫码信息已超过24小时,不再记录');
  316. wx.removeStorageSync('pending_scan_record');
  317. return;
  318. }
  319. // 记录扫码统计
  320. await this.recordScanStatistics({
  321. storeId: pendingScan.storeId,
  322. sourceType: pendingScan.sourceType,
  323. sourceId: pendingScan.sourceId,
  324. ownerId: pendingScan.ownerId,
  325. employeeId: pendingScan.employeeId,
  326. partnerId: pendingScan.partnerId,
  327. userId: pendingScan.userId,
  328. productId: pendingScan.productId,
  329. scanCount: pendingScan.scanCount
  330. });
  331. // 清除待记录的扫码信息
  332. wx.removeStorageSync('pending_scan_record');
  333. console.log('✅ 扫码统计已记录并清除');
  334. } catch (error) {
  335. console.error('❌ 记录待处理扫码信息失败:', error);
  336. }
  337. },
  338. async updateUser(id) {
  339. let User = new Parse.Query('_User')
  340. let user = await User.get(id)
  341. let invite = wx.getStorageSync('invite')
  342. //查询邀请人user
  343. let query = new Parse.Query("_User")
  344. query.equalTo('objectId', invite)
  345. let result = await query.first()
  346. if (result && result.id && result.get("invite")?.id == user.id) {
  347. console.error('邀请人不能是自己的下级')
  348. return
  349. }
  350. if (invite && !user.get('invite') && user.id != invite && !user.get('agentLevel')) {
  351. console.log('上下级绑定成功');
  352. user.set('invite', {
  353. __type: "Pointer",
  354. className: "_User",
  355. objectId: invite
  356. })
  357. user.set('agent', {
  358. __type: "Pointer",
  359. className: "_User",
  360. objectId: invite
  361. })
  362. await Parse.Cloud.run('user_save', {
  363. userJson: user.toJSON()
  364. })
  365. }
  366. },
  367. async getCompanyServerExpire(url) {
  368. let query = new Parse.Query('Company')
  369. query.equalTo('objectId', getApp().globalData.company)
  370. query.select('expireDate', 'expireMap')
  371. let com = await query.first()
  372. if (com?.id && com?.get('expireDate')) {
  373. let now = + new Date()
  374. let expireTime = + new Date(com?.get('expireDate'))
  375. if (com?.get('expireMap') && com.get('expireMap')[getApp().globalData.appid]) {
  376. expireTime = + new Date(com.get('expireMap')[getApp().globalData.appid])
  377. }
  378. if (now >= expireTime) {
  379. console.log('服务器到期');
  380. wx.reLaunch({
  381. url: `common-page/pages/loading/index?url=${url}`,
  382. });
  383. return
  384. }
  385. }
  386. return true
  387. },
  388. onUnload: function () {
  389. wx.setStorageSync("active", 0);
  390. },
  391. getParaName(url) {
  392. if (!url || url.indexOf('?') == -1) {
  393. return
  394. }
  395. // 兼容 URL 中 & 的情况,先统一还原为 &
  396. if (url.indexOf('&') !== -1) {
  397. url = url.replace(/&/g, '&');
  398. }
  399. // 提取查询参数部分(去除可能的 hash 部分)
  400. let queryString = url.split('?')[1];
  401. // 如果包含 #,只取 # 之前的部分
  402. if (queryString.indexOf('#') !== -1) {
  403. queryString = queryString.split('#')[0];
  404. }
  405. return this.setObject(queryString) //封装成对象
  406. },
  407. setObject(paraArr) {
  408. let obj = {}
  409. let arr1 = paraArr.split('&')
  410. arr1.forEach(item => {
  411. let str = item.split('=')
  412. let key = str[0]
  413. let val = str[1]
  414. obj[key] = val
  415. })
  416. return obj
  417. },
  418. /**
  419. * 检查并处理扫码参数
  420. * 支持所有类型的二维码:
  421. * 1. 推广员二维码: ?scanCount=0&storeId=xxx&userId=xxx
  422. * 2. 产品二维码: ?scanCount=0&storeId=xxx&productId=xxx
  423. * 3. 案例二维码: ?scanCount=0&storeId=xxx&caseId=xxx
  424. * 4. 活动海报二维码: activityId作为qrCode参数值
  425. * 5. 异业合作伙伴二维码(移动端 / PC端统一): ?scanCount=xxx&storeId=xxx&partnerId=xxx
  426. * 6. 员工邀请二维码(移动端): ?scanCount=0&storeId=xxx&employeeId=xxx
  427. * 7. 我的二维码(老板): ?scanCount=0&storeId=xxx&ownerId=xxx
  428. * 8. 我的二维码(员工): ?scanCount=0&storeId=xxx&employeeId=xxx
  429. */
  430. checkAndHandleScan(options) {
  431. const {
  432. scanCount,
  433. ownerId,
  434. employeeId,
  435. partnerId,
  436. storeId,
  437. productId,
  438. caseId,
  439. userId, // 推广员二维码使用userId
  440. activityId
  441. } = options;
  442. // 处理活动海报二维码(activityId作为qrCode参数值)
  443. if (activityId && !storeId) {
  444. console.log('===========================================');
  445. console.log('======= 检测到活动海报二维码 =======');
  446. console.log('活动ID (activityId):', activityId);
  447. console.log('===========================================');
  448. // 活动二维码需要跳转到活动页面,不需要跳转到店铺
  449. wx.setStorageSync('scan_activityId', activityId);
  450. wx.setStorageSync('need_activity_redirect', true);
  451. return;
  452. }
  453. // 如果存在 storeId,说明是扫码进入具体门店(包括异业分享)
  454. if (storeId) {
  455. console.log('===========================================');
  456. console.log('======= 检测到扫码参数 =======');
  457. console.log('店铺ID (storeId):', storeId);
  458. console.log('扫码次数 (scanCount):', scanCount || '0');
  459. // 确定来源类型
  460. let sourceType = 'unknown';
  461. let sourceId = null;
  462. if (employeeId) {
  463. sourceType = 'employee';
  464. sourceId = employeeId;
  465. console.log('来源类型: 员工展业');
  466. console.log('员工ID (employeeId):', employeeId);
  467. } else if (userId) {
  468. // 推广员二维码使用userId
  469. sourceType = 'promoter';
  470. sourceId = userId;
  471. console.log('来源类型: 推广员展业');
  472. console.log('推广员ID (userId):', userId);
  473. } else if (ownerId) {
  474. sourceType = 'owner';
  475. sourceId = ownerId;
  476. console.log('来源类型: 老板展业');
  477. console.log('老板ID (ownerId):', ownerId);
  478. } else if (partnerId) {
  479. sourceType = 'partner';
  480. sourceId = partnerId;
  481. console.log('来源类型: 异业合作伙伴');
  482. console.log('合作伙伴ID (partnerId):', partnerId);
  483. } else {
  484. sourceType = 'store';
  485. console.log('来源类型: 店铺分享');
  486. }
  487. if (productId) {
  488. console.log('产品ID (productId):', productId);
  489. }
  490. if (caseId) {
  491. console.log('案例ID (caseId):', caseId);
  492. }
  493. console.log('===========================================');
  494. // 保存到临时存储,用于后续跳转和统计
  495. wx.setStorageSync('scan_storeId', storeId);
  496. wx.setStorageSync('scan_scanCount', scanCount || '0');
  497. wx.setStorageSync('scan_sourceType', sourceType);
  498. wx.setStorageSync('scan_sourceId', sourceId || '');
  499. wx.setStorageSync('scan_ownerId', ownerId || '');
  500. wx.setStorageSync('scan_employeeId', employeeId || '');
  501. wx.setStorageSync('scan_partnerId', partnerId || '');
  502. wx.setStorageSync('scan_userId', userId || '');
  503. if (productId) {
  504. wx.setStorageSync('scan_productId', productId);
  505. }
  506. if (caseId) {
  507. wx.setStorageSync('scan_caseId', caseId);
  508. }
  509. wx.setStorageSync('need_scan_redirect', true);
  510. } else if (partnerId) {
  511. // 理论上异业二维码现在也必须带 storeId,这里仅作为兜底保护
  512. console.warn('⚠️ 检测到异业合作伙伴二维码缺少 storeId,无法确定门店,请检查二维码生成逻辑');
  513. }
  514. },
  515. /**
  516. * 判断是否需要跳转到扫码统计页面
  517. */
  518. shouldRedirectToScanPage() {
  519. return wx.getStorageSync('need_scan_redirect') === true;
  520. },
  521. /**
  522. * 跳转到扫码对应的店铺页面
  523. * 根据 storeId 跳转到对应店铺,同时记录所有来源信息用于统计
  524. */
  525. async redirectToScanPage() {
  526. try {
  527. // 获取所有扫码相关参数
  528. const storeId = wx.getStorageSync('scan_storeId');
  529. const scanCount = wx.getStorageSync('scan_scanCount');
  530. const sourceType = wx.getStorageSync('scan_sourceType');
  531. const sourceId = wx.getStorageSync('scan_sourceId');
  532. const ownerId = wx.getStorageSync('scan_ownerId');
  533. const employeeId = wx.getStorageSync('scan_employeeId');
  534. const partnerId = wx.getStorageSync('scan_partnerId');
  535. const userId = wx.getStorageSync('scan_userId');
  536. const productId = wx.getStorageSync('scan_productId');
  537. const caseId = wx.getStorageSync('scan_caseId');
  538. // 清除临时存储
  539. wx.removeStorageSync('scan_storeId');
  540. wx.removeStorageSync('scan_scanCount');
  541. wx.removeStorageSync('scan_sourceType');
  542. wx.removeStorageSync('scan_sourceId');
  543. wx.removeStorageSync('scan_ownerId');
  544. wx.removeStorageSync('scan_employeeId');
  545. wx.removeStorageSync('scan_partnerId');
  546. wx.removeStorageSync('scan_userId');
  547. wx.removeStorageSync('scan_productId');
  548. wx.removeStorageSync('scan_caseId');
  549. wx.removeStorageSync('need_scan_redirect');
  550. if (!storeId) {
  551. console.error('❌ 缺少 storeId 参数,无法跳转');
  552. return;
  553. }
  554. console.log('===========================================');
  555. console.log('======= 扫码进入店铺 =======');
  556. console.log('店铺 ID (storeId):', storeId);
  557. console.log('来源类型 (sourceType):', sourceType);
  558. console.log('来源 ID (sourceId):', sourceId || '无');
  559. console.log('扫码次数 (scanCount):', scanCount);
  560. if (ownerId) console.log('老板 ID (ownerId):', ownerId);
  561. if (employeeId) console.log('员工 ID (employeeId):', employeeId);
  562. if (partnerId) console.log('合作伙伴 ID (partnerId):', partnerId);
  563. if (userId) console.log('推广员 ID (userId):', userId);
  564. if (productId) console.log('产品 ID (productId):', productId);
  565. if (caseId) console.log('案例 ID (caseId):', caseId);
  566. console.log('===========================================');
  567. // 设置店铺 ID 到全局和本地存储
  568. wx.setStorageSync('storeId', storeId);
  569. getApp().globalData.storeId = storeId;
  570. // 保存来源信息到本地存储(用于后续统计或绑定关系)
  571. if (sourceId) {
  572. wx.setStorageSync('scan_from_sourceType', sourceType);
  573. wx.setStorageSync('scan_from_sourceId', sourceId);
  574. console.log('✅ 已记录来源信息:', sourceType, sourceId);
  575. }
  576. // 检查用户是否已登录
  577. const currentUser = Parse.User.current();
  578. if (currentUser) {
  579. // 用户已登录,立即记录扫码统计
  580. console.log('✅ 用户已登录,记录扫码统计');
  581. await this.recordScanStatistics({
  582. storeId,
  583. sourceType,
  584. sourceId,
  585. ownerId,
  586. employeeId,
  587. partnerId,
  588. userId,
  589. productId,
  590. scanCount
  591. });
  592. } else {
  593. // 用户未登录,保存扫码信息,等登录后再记录
  594. console.log('⚠️ 用户未登录,保存扫码信息待登录后记录');
  595. wx.setStorageSync('pending_scan_record', {
  596. storeId,
  597. sourceType,
  598. sourceId,
  599. ownerId,
  600. employeeId,
  601. partnerId,
  602. userId,
  603. productId,
  604. scanCount,
  605. timestamp: Date.now()
  606. });
  607. }
  608. // 如果有产品ID,直接跳转到产品详情的 H5 页面
  609. if (productId) {
  610. console.log('🎯 检测到产品ID,跳转到产品详情页');
  611. await this.redirectToProductDetail(storeId, productId, partnerId);
  612. return;
  613. }
  614. // 如果有案例ID,直接跳转到案例详情的 H5 页面
  615. if (caseId) {
  616. console.log('🎯 检测到案例ID,跳转到案例详情页');
  617. await this.redirectToCaseDetail(storeId, caseId, partnerId);
  618. return;
  619. }
  620. // 获取默认首页路径并跳转
  621. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  622. url += `?storeId=${storeId}`;
  623. // 如果有产品ID,添加到URL参数中
  624. if (productId) {
  625. url += `&productId=${productId}`;
  626. }
  627. // 如果有异业合作伙伴ID,添加到URL参数中传回web-view
  628. if (partnerId) {
  629. url += `&partnerId=${partnerId}`;
  630. }
  631. console.log('✅ 跳转到店铺页面:', url);
  632. wx.redirectTo({
  633. url: url,
  634. fail: (err) => {
  635. console.error('❌ 跳转失败:', err);
  636. // 如果 redirectTo 失败,尝试 reLaunch
  637. wx.reLaunch({
  638. url: url,
  639. fail: (err2) => {
  640. console.error('❌ reLaunch 也失败:', err2);
  641. }
  642. });
  643. }
  644. });
  645. } catch (error) {
  646. console.error('❌ 跳转到店铺页面失败:', error);
  647. }
  648. },
  649. /**
  650. * 跳转到活动页面
  651. */
  652. async redirectToActivityPage() {
  653. try {
  654. const activityId = wx.getStorageSync('scan_activityId');
  655. // 清除临时存储
  656. wx.removeStorageSync('scan_activityId');
  657. wx.removeStorageSync('need_activity_redirect');
  658. if (!activityId) {
  659. console.error('❌ 缺少 activityId 参数,无法跳转');
  660. return;
  661. }
  662. console.log('===========================================');
  663. console.log('======= 扫码进入活动页面 =======');
  664. console.log('活动 ID (activityId):', activityId);
  665. console.log('===========================================');
  666. // 保存活动ID
  667. wx.setStorageSync('activityId', activityId);
  668. // 获取默认首页路径并跳转(活动页面可能需要根据实际路由调整)
  669. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  670. url += `?activityId=${activityId}`;
  671. console.log('✅ 跳转到活动页面:', url);
  672. wx.redirectTo({
  673. url: url,
  674. fail: (err) => {
  675. console.error('❌ 跳转失败:', err);
  676. wx.reLaunch({
  677. url: url,
  678. fail: (err2) => {
  679. console.error('❌ reLaunch 也失败:', err2);
  680. }
  681. });
  682. }
  683. });
  684. } catch (error) {
  685. console.error('❌ 跳转到活动页面失败:', error);
  686. }
  687. },
  688. /**
  689. * 跳转到产品详情的 H5 页面
  690. * @param {string} storeId - 店铺 ID
  691. * @param {string} productId - 产品 ID
  692. * @param {string} partnerId - 可选的合作伙伴 ID
  693. */
  694. async redirectToProductDetail(storeId, productId, partnerId = null) {
  695. try {
  696. console.log('===========================================');
  697. console.log('======= 跳转到产品详情页 =======');
  698. console.log('店铺 ID:', storeId);
  699. console.log('产品 ID:', productId);
  700. if (partnerId) console.log('合作伙伴 ID:', partnerId);
  701. console.log('===========================================');
  702. const currentUser = Parse.User.current();
  703. const token = currentUser ? currentUser.getSessionToken() : null;
  704. // 构建产品详情的 H5 URL(不要在这里编码,后面统一编码)
  705. let h5Url = `https://app.fmode.cn/dev/pobingfeng/owner/nav/products?storeId=${storeId}`;
  706. // 如果有 token,添加到 URL
  707. if (token) {
  708. h5Url += `&token=${token}`;
  709. } else {
  710. // 如果没有 token,使用游客模式
  711. h5Url += `&guestMode=true`;
  712. }
  713. // 添加产品ID(不要在这里编码,避免双重编码)
  714. h5Url += `&productId=${productId}`;
  715. // 如果有合作伙伴ID,也添加到URL中
  716. if (partnerId) {
  717. h5Url += `&partnerId=${partnerId}`;
  718. }
  719. console.log('🌐 H5 URL:', h5Url);
  720. // 编码 URL(统一在这里编码一次)
  721. const encodedUrl = encodeURIComponent(h5Url);
  722. // 构建 web-view 页面路径
  723. let webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&storeId=${storeId}`;
  724. console.log('📄 web-view 页面路径:', webViewPath.substring(0, 100) + '...');
  725. console.log('===========================================');
  726. wx.redirectTo({
  727. url: webViewPath,
  728. success: () => {
  729. console.log('✅ 跳转到产品详情页成功');
  730. },
  731. fail: (err) => {
  732. console.error('❌ 跳转失败:', err);
  733. // 如果 redirectTo 失败,尝试 reLaunch
  734. wx.reLaunch({
  735. url: webViewPath,
  736. fail: (err2) => {
  737. console.error('❌ reLaunch 也失败:', err2);
  738. }
  739. });
  740. }
  741. });
  742. } catch (error) {
  743. console.error('❌ 跳转到产品详情页失败:', error);
  744. }
  745. },
  746. /**
  747. * 跳转到案例详情的 H5 页面
  748. * @param {string} storeId - 店铺 ID
  749. * @param {string} caseId - 案例 ID
  750. * @param {string} partnerId - 可选的合作伙伴 ID
  751. */
  752. async redirectToCaseDetail(storeId, caseId, partnerId = null) {
  753. try {
  754. console.log('===========================================');
  755. console.log('======= 跳转到案例详情页 =======');
  756. console.log('店铺 ID:', storeId);
  757. console.log('案例 ID:', caseId);
  758. if (partnerId) console.log('合作伙伴 ID:', partnerId);
  759. console.log('===========================================');
  760. const currentUser = Parse.User.current();
  761. const token = currentUser ? currentUser.getSessionToken() : null;
  762. // 构建案例详情的 H5 URL(不要在这里编码,后面统一编码)
  763. let h5Url = `https://app.fmode.cn/dev/pobingfeng/owner/nav/cases?storeId=${storeId}`;
  764. // 如果有 token,添加到 URL
  765. if (token) {
  766. h5Url += `&token=${token}`;
  767. } else {
  768. // 如果没有 token,使用游客模式
  769. h5Url += `&guestMode=true`;
  770. }
  771. // 添加案例ID(不要在这里编码,避免双重编码)
  772. h5Url += `&caseId=${caseId}`;
  773. // 如果有合作伙伴ID,也添加到URL中
  774. if (partnerId) {
  775. h5Url += `&partnerId=${partnerId}`;
  776. }
  777. console.log('🌐 H5 URL:', h5Url);
  778. // 编码 URL(统一在这里编码一次)
  779. const encodedUrl = encodeURIComponent(h5Url);
  780. // 构建 web-view 页面路径
  781. let webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&storeId=${storeId}`;
  782. console.log('📄 web-view 页面路径:', webViewPath.substring(0, 100) + '...');
  783. console.log('===========================================');
  784. wx.redirectTo({
  785. url: webViewPath,
  786. success: () => {
  787. console.log('✅ 跳转到案例详情页成功');
  788. },
  789. fail: (err) => {
  790. console.error('❌ 跳转失败:', err);
  791. // 如果 redirectTo 失败,尝试 reLaunch
  792. wx.reLaunch({
  793. url: webViewPath,
  794. fail: (err2) => {
  795. console.error('❌ reLaunch 也失败:', err2);
  796. }
  797. });
  798. }
  799. });
  800. } catch (error) {
  801. console.error('❌ 跳转到案例详情页失败:', error);
  802. }
  803. },
  804. /**
  805. * 记录扫码统计信息
  806. * @param {Object} params - 扫码参数对象
  807. * @param {string} params.storeId - 店铺 ID
  808. * @param {string} params.sourceType - 来源类型 (employee/owner/partner/promoter/store)
  809. * @param {string} params.sourceId - 来源 ID
  810. * @param {string} params.ownerId - 老板 ID
  811. * @param {string} params.employeeId - 员工 ID
  812. * @param {string} params.partnerId - 合作伙伴 ID
  813. * @param {string} params.userId - 推广员 ID
  814. * @param {string} params.productId - 产品 ID
  815. * @param {string} params.scanCount - 扫码次数
  816. */
  817. async recordScanStatistics(params) {
  818. try {
  819. const {
  820. storeId,
  821. sourceType,
  822. sourceId,
  823. ownerId,
  824. employeeId,
  825. partnerId,
  826. userId,
  827. productId,
  828. scanCount
  829. } = params;
  830. // 检查用户是否已登录
  831. const currentUser = Parse.User.current();
  832. if (!currentUser) {
  833. console.log('⚠️ 用户未登录,不记录扫码统计');
  834. return;
  835. }
  836. // 如果没有来源信息,不记录统计
  837. if (!sourceId && !ownerId && !employeeId && !partnerId && !userId) {
  838. console.log('ℹ️ 无来源信息,跳过统计记录');
  839. return;
  840. }
  841. console.log('✅ 用户已登录,记录扫码统计');
  842. // 创建扫码记录
  843. const ScanRecord = Parse.Object.extend('ScanRecord');
  844. const record = new ScanRecord();
  845. record.set('company', {
  846. __type: 'Pointer',
  847. className: 'Company',
  848. objectId: getApp().globalData.company
  849. });
  850. record.set('storeId', storeId);
  851. record.set('sourceType', sourceType || 'unknown');
  852. record.set('scanCount', parseInt(scanCount) || 0);
  853. record.set('scanTime', new Date());
  854. // 根据来源类型设置对应的ID
  855. if (ownerId) {
  856. record.set('ownerId', ownerId);
  857. }
  858. if (employeeId) {
  859. record.set('employeeId', employeeId);
  860. }
  861. if (partnerId) {
  862. record.set('partnerId', partnerId);
  863. }
  864. if (userId) {
  865. record.set('userId', userId);
  866. }
  867. if (productId) {
  868. record.set('productId', productId);
  869. }
  870. // 记录扫码用户
  871. record.set('user', {
  872. __type: 'Pointer',
  873. className: '_User',
  874. objectId: currentUser.id
  875. });
  876. await record.save();
  877. console.log('✅ 扫码记录已保存');
  878. // 如果存在异业合作伙伴ID,处理异业绑定和扫码次数
  879. if (partnerId) {
  880. try {
  881. // 检查用户是否已经绑定了异业合作伙伴
  882. const userBoundPartner = currentUser.get('Partner');
  883. const userBoundPartnerId = userBoundPartner ? userBoundPartner.id : null;
  884. if (!userBoundPartnerId) {
  885. // 用户还没有绑定异业合作伙伴,绑定当前的
  886. console.log('✅ 用户首次扫异业码,绑定异业合作伙伴:', partnerId);
  887. // 设置 Partner 指针
  888. currentUser.set('Partner', {
  889. __type: 'Pointer',
  890. className: 'Partner',
  891. objectId: partnerId
  892. });
  893. await currentUser.save();
  894. // 为该异业合作伙伴增加扫码次数
  895. const Partner = Parse.Object.extend('Partner');
  896. const partnerQuery = new Parse.Query(Partner);
  897. const partnerObj = await partnerQuery.get(partnerId);
  898. if (partnerObj) {
  899. partnerObj.increment('scanCount', 1);
  900. await partnerObj.save();
  901. console.log('✅ 已为异业合作伙伴累加一次扫码次数,partnerId:', partnerId);
  902. }
  903. } else if (userBoundPartnerId === partnerId) {
  904. // 用户已绑定,且扫的是同一个异业合作伙伴的码
  905. console.log('✅ 用户扫已绑定的异业码,增加扫码次数,partnerId:', partnerId);
  906. const Partner = Parse.Object.extend('Partner');
  907. const partnerQuery = new Parse.Query(Partner);
  908. const partnerObj = await partnerQuery.get(partnerId);
  909. if (partnerObj) {
  910. partnerObj.increment('scanCount', 1);
  911. await partnerObj.save();
  912. console.log('✅ 已为异业合作伙伴累加一次扫码次数,partnerId:', partnerId);
  913. }
  914. } else {
  915. // 用户已绑定其他异业合作伙伴,不增加当前异业的扫码次数
  916. console.log('⚠️ 用户已绑定其他异业合作伙伴:', userBoundPartnerId);
  917. console.log('⚠️ 当前扫码的异业:', partnerId);
  918. console.log('⚠️ 不增加当前异业的扫码次数');
  919. }
  920. } catch (e) {
  921. console.warn('⚠️ 处理异业合作伙伴绑定失败:', e);
  922. }
  923. }
  924. } catch (error) {
  925. console.warn('⚠️ 保存扫码记录失败:', error);
  926. // 不影响主流程
  927. }
  928. }
  929. });