index.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233
  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. // 不要 return,继续执行后面的跳转逻辑
  267. } else {
  268. // 登录成功,设置 userLogin
  269. if (currentUser && currentUser.get('mobile')) {
  270. wx.setStorageSync("userLogin", currentUser.id);
  271. console.log('✅ 授权登录成功,已设置 userLogin:', currentUser.id);
  272. console.log('✅ 用户手机号:', currentUser.get('mobile'));
  273. } else {
  274. console.warn('⚠️ checkAuth 返回成功,但用户没有手机号!');
  275. console.warn(' 用户对象:', currentUser);
  276. }
  277. // 检查是否有待记录的扫码信息
  278. await this.checkAndRecordPendingScan();
  279. }
  280. } else {
  281. console.log('✅ 用户已登录,跳过 checkAuth');
  282. // 用户已登录,确保 userLogin 已设置
  283. if (currentUser.get('mobile')) {
  284. wx.setStorageSync("userLogin", currentUser.id);
  285. console.log('✅ 已确认 userLogin:', currentUser.id);
  286. }
  287. this.updateUser(currentUser.id);
  288. // 用户已登录,检查是否有待记录的扫码信息
  289. await this.checkAndRecordPendingScan();
  290. }
  291. getApp().Parse = Parse
  292. getApp().checkAuth = checkAuth
  293. if (!await this.getCompanyServerExpire(url)) {
  294. return
  295. }
  296. // 检查是否需要跳转到活动页面
  297. if (wx.getStorageSync('need_activity_redirect') === true) {
  298. await this.redirectToActivityPage();
  299. return;
  300. }
  301. // 检查是否需要跳转到扫码统计页面
  302. if (this.shouldRedirectToScanPage()) {
  303. await this.redirectToScanPage();
  304. return;
  305. }
  306. console.log('✅ 准备跳转到:', url);
  307. wx.redirectTo({
  308. url: url,
  309. success: () => {
  310. console.log('✅ redirectTo 跳转成功');
  311. },
  312. fail: (err) => {
  313. console.error('❌ redirectTo 失败:', err);
  314. console.log('⚠️ 尝试使用 reLaunch');
  315. // 降级:尝试使用 reLaunch
  316. wx.reLaunch({
  317. url: url,
  318. success: () => {
  319. console.log('✅ reLaunch 跳转成功');
  320. },
  321. fail: (err2) => {
  322. console.error('❌ reLaunch 也失败:', err2);
  323. // 显示错误提示
  324. this.setData({ loading: false });
  325. wx.showModal({
  326. title: '温馨提示',
  327. content: '页面加载失败,请检查网络后重试。',
  328. showCancel: true,
  329. cancelText: '退出',
  330. confirmText: '重试',
  331. success: (result) => {
  332. if (result.confirm) {
  333. this.review(true);
  334. } else {
  335. wx.exitMiniProgram();
  336. }
  337. }
  338. });
  339. }
  340. });
  341. }
  342. });
  343. }
  344. catch (err) {
  345. console.log('❌ review 方法出错:', err);
  346. /* 登录身份信息到期,重新登陆 */
  347. if((err?.message?.indexOf('Session token is expired') != -1 || err?.message?.indexOf('Invalid session token') != -1) && !force){
  348. console.log('⚠️ Session Token 过期,准备重新登录');
  349. // 保存需要保留的数据
  350. const invite = wx.getStorageSync('invite');
  351. const agreementAccepted = wx.getStorageSync('user_agreement_accepted');
  352. const storeId = wx.getStorageSync('storeId');
  353. const isPublishing = wx.getStorageSync('isPublishing');
  354. // 先登出 Parse 用户
  355. try {
  356. await Parse.User.logOut();
  357. console.log('✅ Parse 用户已登出');
  358. } catch (logoutErr) {
  359. console.warn('⚠️ Parse 登出失败:', logoutErr);
  360. }
  361. // 清除所有存储
  362. wx.clearStorageSync();
  363. // 恢复需要保留的数据
  364. if (invite) wx.setStorageSync('invite', invite);
  365. if (agreementAccepted) wx.setStorageSync('user_agreement_accepted', agreementAccepted);
  366. if (storeId) wx.setStorageSync('storeId', storeId);
  367. if (isPublishing !== undefined) wx.setStorageSync('isPublishing', isPublishing);
  368. console.log('✅ 已清除过期登录信息,保留必要数据');
  369. console.log(' 保留的 invite:', invite || '无');
  370. console.log(' 保留的协议状态:', agreementAccepted || '无');
  371. console.log(' 保留的店铺ID:', storeId || '无');
  372. /* 强制重新登录 */
  373. this.review(true);
  374. return;
  375. }
  376. // 如果出错,尝试继续跳转到主页(游客模式)
  377. console.log('⚠️ 登录出错,尝试游客模式访问');
  378. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  379. if (this.data.options) {
  380. let objArr = Object.keys(this.data.options)
  381. if (objArr && objArr.length > 0) {
  382. let parms = '?'
  383. objArr.forEach((o, index) => {
  384. if (index > 0) {
  385. parms += '&' + o + '=' + this.data.options[o]
  386. } else {
  387. parms += o + '=' + this.data.options[o]
  388. }
  389. })
  390. url += parms
  391. }
  392. }
  393. wx.redirectTo({
  394. url: url,
  395. fail: () => {
  396. // 如果跳转失败,显示错误提示
  397. this.setData({
  398. loading:false
  399. })
  400. wx.showModal({
  401. title: '温馨提示',
  402. content: '页面加载失败,请检查网络后重试。',
  403. showCancel: true,
  404. cancelText: '退出',
  405. confirmText: '重试',
  406. success: (result) => {
  407. if (result.confirm) {
  408. // 用户选择重试
  409. this.review(true)
  410. } else {
  411. // 用户选择退出
  412. wx.exitMiniProgram()
  413. }
  414. },
  415. });
  416. }
  417. });
  418. }
  419. },
  420. /**
  421. * 检查并记录待处理的扫码统计
  422. */
  423. async checkAndRecordPendingScan() {
  424. try {
  425. const pendingScan = wx.getStorageSync('pending_scan_record');
  426. if (!pendingScan) {
  427. return;
  428. }
  429. console.log('===========================================');
  430. console.log('======= 发现待记录的扫码信息 =======');
  431. console.log('扫码信息:', pendingScan);
  432. console.log('===========================================');
  433. // 检查是否超时(24小时)
  434. const now = Date.now();
  435. const scanTime = pendingScan.timestamp || 0;
  436. const hoursPassed = (now - scanTime) / (1000 * 60 * 60);
  437. if (hoursPassed > 24) {
  438. console.log('⚠️ 扫码信息已超过24小时,不再记录');
  439. wx.removeStorageSync('pending_scan_record');
  440. return;
  441. }
  442. // 记录扫码统计
  443. await this.recordScanStatistics({
  444. storeId: pendingScan.storeId,
  445. sourceType: pendingScan.sourceType,
  446. sourceId: pendingScan.sourceId,
  447. ownerId: pendingScan.ownerId,
  448. employeeId: pendingScan.employeeId,
  449. partnerId: pendingScan.partnerId,
  450. userId: pendingScan.userId,
  451. productId: pendingScan.productId,
  452. scanCount: pendingScan.scanCount
  453. });
  454. // 清除待记录的扫码信息
  455. wx.removeStorageSync('pending_scan_record');
  456. console.log('✅ 扫码统计已记录并清除');
  457. } catch (error) {
  458. console.error('❌ 记录待处理扫码信息失败:', error);
  459. }
  460. },
  461. async updateUser(id) {
  462. try {
  463. let User = new Parse.Query('_User')
  464. let user = await User.get(id)
  465. let invite = wx.getStorageSync('invite')
  466. //查询邀请人user
  467. let query = new Parse.Query("_User")
  468. query.equalTo('objectId', invite)
  469. let result = await query.first()
  470. if (result && result.id && result.get("invite")?.id == user.id) {
  471. console.error('邀请人不能是自己的下级')
  472. return
  473. }
  474. if (invite && !user.get('invite') && user.id != invite && !user.get('agentLevel')) {
  475. console.log('上下级绑定成功');
  476. user.set('invite', {
  477. __type: "Pointer",
  478. className: "_User",
  479. objectId: invite
  480. })
  481. user.set('agent', {
  482. __type: "Pointer",
  483. className: "_User",
  484. objectId: invite
  485. })
  486. await Parse.Cloud.run('user_save', {
  487. userJson: user.toJSON()
  488. })
  489. }
  490. } catch (error) {
  491. console.error('❌ updateUser 失败:', error.message);
  492. // 不阻断流程,继续执行
  493. }
  494. },
  495. async getCompanyServerExpire(url) {
  496. try {
  497. let query = new Parse.Query('Company')
  498. query.equalTo('objectId', getApp().globalData.company)
  499. query.select('expireDate', 'expireMap')
  500. let com = await query.first()
  501. if (com?.id && com?.get('expireDate')) {
  502. let now = + new Date()
  503. let expireTime = + new Date(com?.get('expireDate'))
  504. if (com?.get('expireMap') && com.get('expireMap')[getApp().globalData.appid]) {
  505. expireTime = + new Date(com.get('expireMap')[getApp().globalData.appid])
  506. }
  507. if (now >= expireTime) {
  508. console.log('服务器到期');
  509. wx.reLaunch({
  510. url: `common-page/pages/loading/index?url=${url}`,
  511. });
  512. return
  513. }
  514. }
  515. return true
  516. } catch (error) {
  517. console.error('❌ getCompanyServerExpire 失败:', error.message);
  518. // 查询失败时,允许继续访问
  519. return true
  520. }
  521. },
  522. onUnload: function () {
  523. wx.setStorageSync("active", 0);
  524. },
  525. getParaName(url) {
  526. if (!url || url.indexOf('?') == -1) {
  527. return
  528. }
  529. // 兼容 URL 中 & 的情况,先统一还原为 &
  530. if (url.indexOf('&') !== -1) {
  531. url = url.replace(/&/g, '&');
  532. }
  533. // 提取查询参数部分(去除可能的 hash 部分)
  534. let queryString = url.split('?')[1];
  535. // 如果包含 #,只取 # 之前的部分
  536. if (queryString.indexOf('#') !== -1) {
  537. queryString = queryString.split('#')[0];
  538. }
  539. return this.setObject(queryString) //封装成对象
  540. },
  541. setObject(paraArr) {
  542. let obj = {}
  543. let arr1 = paraArr.split('&')
  544. arr1.forEach(item => {
  545. let str = item.split('=')
  546. let key = str[0]
  547. let val = str[1]
  548. obj[key] = val
  549. })
  550. return obj
  551. },
  552. /**
  553. * 检查并处理扫码参数
  554. * 支持所有类型的二维码:
  555. * 1. 推广员二维码: ?scanCount=0&storeId=xxx&userId=xxx
  556. * 2. 产品二维码: ?scanCount=0&storeId=xxx&productId=xxx
  557. * 3. 案例二维码: ?scanCount=0&storeId=xxx&caseId=xxx
  558. * 4. 活动海报二维码: activityId作为qrCode参数值
  559. * 5. 异业合作伙伴二维码(移动端 / PC端统一): ?scanCount=xxx&storeId=xxx&partnerId=xxx
  560. * 6. 员工邀请二维码(移动端): ?scanCount=0&storeId=xxx&employeeId=xxx
  561. * 7. 我的二维码(老板): ?scanCount=0&storeId=xxx&ownerId=xxx
  562. * 8. 我的二维码(员工): ?scanCount=0&storeId=xxx&employeeId=xxx
  563. */
  564. checkAndHandleScan(options) {
  565. const {
  566. scanCount,
  567. ownerId,
  568. employeeId,
  569. partnerId,
  570. storeId,
  571. productId,
  572. caseId,
  573. userId, // 推广员二维码使用userId
  574. activityId
  575. } = options;
  576. // 处理活动海报二维码(activityId作为qrCode参数值)
  577. if (activityId && !storeId) {
  578. console.log('===========================================');
  579. console.log('======= 检测到活动海报二维码 =======');
  580. console.log('活动ID (activityId):', activityId);
  581. console.log('===========================================');
  582. // 活动二维码需要跳转到活动页面,不需要跳转到店铺
  583. wx.setStorageSync('scan_activityId', activityId);
  584. wx.setStorageSync('need_activity_redirect', true);
  585. return;
  586. }
  587. // 如果存在 storeId,说明是扫码进入具体门店(包括异业分享)
  588. if (storeId) {
  589. console.log('===========================================');
  590. console.log('======= 检测到扫码参数 =======');
  591. console.log('店铺ID (storeId):', storeId);
  592. console.log('扫码次数 (scanCount):', scanCount || '0');
  593. // 确定来源类型
  594. let sourceType = 'unknown';
  595. let sourceId = null;
  596. if (employeeId) {
  597. sourceType = 'employee';
  598. sourceId = employeeId;
  599. console.log('来源类型: 员工展业');
  600. console.log('员工ID (employeeId):', employeeId);
  601. } else if (userId) {
  602. // 推广员二维码使用userId
  603. sourceType = 'promoter';
  604. sourceId = userId;
  605. console.log('来源类型: 推广员展业');
  606. console.log('推广员ID (userId):', userId);
  607. } else if (ownerId) {
  608. sourceType = 'owner';
  609. sourceId = ownerId;
  610. console.log('来源类型: 老板展业');
  611. console.log('老板ID (ownerId):', ownerId);
  612. } else if (partnerId) {
  613. sourceType = 'partner';
  614. sourceId = partnerId;
  615. console.log('来源类型: 异业合作伙伴');
  616. console.log('合作伙伴ID (partnerId):', partnerId);
  617. } else {
  618. sourceType = 'store';
  619. console.log('来源类型: 店铺分享');
  620. }
  621. if (productId) {
  622. console.log('产品ID (productId):', productId);
  623. }
  624. if (caseId) {
  625. console.log('案例ID (caseId):', caseId);
  626. }
  627. console.log('===========================================');
  628. // 保存到临时存储,用于后续跳转和统计
  629. wx.setStorageSync('scan_storeId', storeId);
  630. wx.setStorageSync('scan_scanCount', scanCount || '0');
  631. wx.setStorageSync('scan_sourceType', sourceType);
  632. wx.setStorageSync('scan_sourceId', sourceId || '');
  633. wx.setStorageSync('scan_ownerId', ownerId || '');
  634. wx.setStorageSync('scan_employeeId', employeeId || '');
  635. wx.setStorageSync('scan_partnerId', partnerId || '');
  636. wx.setStorageSync('scan_userId', userId || '');
  637. if (productId) {
  638. wx.setStorageSync('scan_productId', productId);
  639. }
  640. if (caseId) {
  641. wx.setStorageSync('scan_caseId', caseId);
  642. }
  643. wx.setStorageSync('need_scan_redirect', true);
  644. } else if (partnerId) {
  645. // 理论上异业二维码现在也必须带 storeId,这里仅作为兜底保护
  646. console.warn('⚠️ 检测到异业合作伙伴二维码缺少 storeId,无法确定门店,请检查二维码生成逻辑');
  647. }
  648. },
  649. /**
  650. * 判断是否需要跳转到扫码统计页面
  651. */
  652. shouldRedirectToScanPage() {
  653. return wx.getStorageSync('need_scan_redirect') === true;
  654. },
  655. /**
  656. * 跳转到扫码对应的店铺页面
  657. * 根据 storeId 跳转到对应店铺,同时记录所有来源信息用于统计
  658. */
  659. async redirectToScanPage() {
  660. try {
  661. // 获取所有扫码相关参数
  662. const storeId = wx.getStorageSync('scan_storeId');
  663. const scanCount = wx.getStorageSync('scan_scanCount');
  664. const sourceType = wx.getStorageSync('scan_sourceType');
  665. const sourceId = wx.getStorageSync('scan_sourceId');
  666. const ownerId = wx.getStorageSync('scan_ownerId');
  667. const employeeId = wx.getStorageSync('scan_employeeId');
  668. const partnerId = wx.getStorageSync('scan_partnerId');
  669. const userId = wx.getStorageSync('scan_userId');
  670. const productId = wx.getStorageSync('scan_productId');
  671. const caseId = wx.getStorageSync('scan_caseId');
  672. // 清除临时存储
  673. wx.removeStorageSync('scan_storeId');
  674. wx.removeStorageSync('scan_scanCount');
  675. wx.removeStorageSync('scan_sourceType');
  676. wx.removeStorageSync('scan_sourceId');
  677. wx.removeStorageSync('scan_ownerId');
  678. wx.removeStorageSync('scan_employeeId');
  679. wx.removeStorageSync('scan_partnerId');
  680. wx.removeStorageSync('scan_userId');
  681. wx.removeStorageSync('scan_productId');
  682. wx.removeStorageSync('scan_caseId');
  683. wx.removeStorageSync('need_scan_redirect');
  684. if (!storeId) {
  685. console.error('❌ 缺少 storeId 参数,无法跳转');
  686. return;
  687. }
  688. console.log('===========================================');
  689. console.log('======= 扫码进入店铺 =======');
  690. console.log('店铺 ID (storeId):', storeId);
  691. console.log('来源类型 (sourceType):', sourceType);
  692. console.log('来源 ID (sourceId):', sourceId || '无');
  693. console.log('扫码次数 (scanCount):', scanCount);
  694. if (ownerId) console.log('老板 ID (ownerId):', ownerId);
  695. if (employeeId) console.log('员工 ID (employeeId):', employeeId);
  696. if (partnerId) console.log('合作伙伴 ID (partnerId):', partnerId);
  697. if (userId) console.log('推广员 ID (userId):', userId);
  698. if (productId) console.log('产品 ID (productId):', productId);
  699. if (caseId) console.log('案例 ID (caseId):', caseId);
  700. console.log('===========================================');
  701. // 设置店铺 ID 到全局和本地存储
  702. wx.setStorageSync('storeId', storeId);
  703. getApp().globalData.storeId = storeId;
  704. // 保存来源信息到本地存储(用于后续统计或绑定关系)
  705. if (sourceId) {
  706. wx.setStorageSync('scan_from_sourceType', sourceType);
  707. wx.setStorageSync('scan_from_sourceId', sourceId);
  708. console.log('✅ 已记录来源信息:', sourceType, sourceId);
  709. }
  710. // 检查用户是否已登录
  711. const currentUser = Parse.User.current();
  712. if (currentUser) {
  713. // 用户已登录,立即记录扫码统计
  714. console.log('✅ 用户已登录,记录扫码统计');
  715. await this.recordScanStatistics({
  716. storeId,
  717. sourceType,
  718. sourceId,
  719. ownerId,
  720. employeeId,
  721. partnerId,
  722. userId,
  723. productId,
  724. scanCount
  725. });
  726. } else {
  727. // 用户未登录,保存扫码信息,等登录后再记录
  728. console.log('⚠️ 用户未登录,保存扫码信息待登录后记录');
  729. wx.setStorageSync('pending_scan_record', {
  730. storeId,
  731. sourceType,
  732. sourceId,
  733. ownerId,
  734. employeeId,
  735. partnerId,
  736. userId,
  737. productId,
  738. scanCount,
  739. timestamp: Date.now()
  740. });
  741. }
  742. // 如果有产品ID,直接跳转到产品详情的 H5 页面
  743. if (productId) {
  744. console.log('🎯 检测到产品ID,跳转到产品详情页');
  745. await this.redirectToProductDetail(storeId, productId, partnerId);
  746. return;
  747. }
  748. // 如果有案例ID,直接跳转到案例详情的 H5 页面
  749. if (caseId) {
  750. console.log('🎯 检测到案例ID,跳转到案例详情页');
  751. await this.redirectToCaseDetail(storeId, caseId, partnerId);
  752. return;
  753. }
  754. // 获取默认首页路径并跳转
  755. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  756. url += `?storeId=${storeId}`;
  757. // 如果有产品ID,添加到URL参数中
  758. if (productId) {
  759. url += `&productId=${productId}`;
  760. }
  761. // 如果有异业合作伙伴ID,添加到URL参数中传回web-view
  762. if (partnerId) {
  763. url += `&partnerId=${partnerId}`;
  764. }
  765. console.log('✅ 跳转到店铺页面:', url);
  766. wx.redirectTo({
  767. url: url,
  768. fail: (err) => {
  769. console.error('❌ 跳转失败:', err);
  770. // 如果 redirectTo 失败,尝试 reLaunch
  771. wx.reLaunch({
  772. url: url,
  773. fail: (err2) => {
  774. console.error('❌ reLaunch 也失败:', err2);
  775. }
  776. });
  777. }
  778. });
  779. } catch (error) {
  780. console.error('❌ 跳转到店铺页面失败:', error);
  781. }
  782. },
  783. /**
  784. * 跳转到活动页面
  785. */
  786. async redirectToActivityPage() {
  787. try {
  788. const activityId = wx.getStorageSync('scan_activityId');
  789. // 清除临时存储
  790. wx.removeStorageSync('scan_activityId');
  791. wx.removeStorageSync('need_activity_redirect');
  792. if (!activityId) {
  793. console.error('❌ 缺少 activityId 参数,无法跳转');
  794. return;
  795. }
  796. console.log('===========================================');
  797. console.log('======= 扫码进入活动页面 =======');
  798. console.log('活动 ID (activityId):', activityId);
  799. console.log('===========================================');
  800. // 保存活动ID
  801. wx.setStorageSync('activityId', activityId);
  802. // 获取默认首页路径并跳转(活动页面可能需要根据实际路由调整)
  803. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  804. url += `?activityId=${activityId}`;
  805. console.log('✅ 跳转到活动页面:', url);
  806. wx.redirectTo({
  807. url: url,
  808. fail: (err) => {
  809. console.error('❌ 跳转失败:', err);
  810. wx.reLaunch({
  811. url: url,
  812. fail: (err2) => {
  813. console.error('❌ reLaunch 也失败:', err2);
  814. }
  815. });
  816. }
  817. });
  818. } catch (error) {
  819. console.error('❌ 跳转到活动页面失败:', error);
  820. }
  821. },
  822. /**
  823. * 跳转到产品详情的 H5 页面
  824. * @param {string} storeId - 店铺 ID
  825. * @param {string} productId - 产品 ID
  826. * @param {string} partnerId - 可选的合作伙伴 ID
  827. */
  828. async redirectToProductDetail(storeId, productId, partnerId = null) {
  829. try {
  830. console.log('===========================================');
  831. console.log('======= 跳转到产品详情页 =======');
  832. console.log('店铺 ID:', storeId);
  833. console.log('产品 ID:', productId);
  834. if (partnerId) console.log('合作伙伴 ID:', partnerId);
  835. console.log('===========================================');
  836. const currentUser = Parse.User.current();
  837. const token = currentUser ? currentUser.getSessionToken() : null;
  838. // 构建产品详情的 H5 URL(不要在这里编码,后面统一编码)
  839. let h5Url = `https://app.fmode.cn/dev/pobingfeng/owner/nav/products?storeId=${storeId}`;
  840. // 如果有 token,添加到 URL
  841. if (token) {
  842. h5Url += `&token=${token}`;
  843. } else {
  844. // 如果没有 token,使用游客模式
  845. h5Url += `&guestMode=true`;
  846. }
  847. // 添加产品ID(不要在这里编码,避免双重编码)
  848. h5Url += `&productId=${productId}`;
  849. // 如果有合作伙伴ID,也添加到URL中
  850. if (partnerId) {
  851. h5Url += `&partnerId=${partnerId}`;
  852. }
  853. console.log('🌐 H5 URL:', h5Url);
  854. // 编码 URL(统一在这里编码一次)
  855. const encodedUrl = encodeURIComponent(h5Url);
  856. // 构建 web-view 页面路径
  857. let webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&storeId=${storeId}`;
  858. console.log('📄 web-view 页面路径:', webViewPath.substring(0, 100) + '...');
  859. console.log('===========================================');
  860. wx.redirectTo({
  861. url: webViewPath,
  862. success: () => {
  863. console.log('✅ 跳转到产品详情页成功');
  864. },
  865. fail: (err) => {
  866. console.error('❌ 跳转失败:', err);
  867. // 如果 redirectTo 失败,尝试 reLaunch
  868. wx.reLaunch({
  869. url: webViewPath,
  870. fail: (err2) => {
  871. console.error('❌ reLaunch 也失败:', err2);
  872. }
  873. });
  874. }
  875. });
  876. } catch (error) {
  877. console.error('❌ 跳转到产品详情页失败:', error);
  878. }
  879. },
  880. /**
  881. * 跳转到案例详情的 H5 页面
  882. * @param {string} storeId - 店铺 ID
  883. * @param {string} caseId - 案例 ID
  884. * @param {string} partnerId - 可选的合作伙伴 ID
  885. */
  886. async redirectToCaseDetail(storeId, caseId, partnerId = null) {
  887. try {
  888. console.log('===========================================');
  889. console.log('======= 跳转到案例详情页 =======');
  890. console.log('店铺 ID:', storeId);
  891. console.log('案例 ID:', caseId);
  892. if (partnerId) console.log('合作伙伴 ID:', partnerId);
  893. console.log('===========================================');
  894. const currentUser = Parse.User.current();
  895. const token = currentUser ? currentUser.getSessionToken() : null;
  896. // 构建案例详情的 H5 URL(不要在这里编码,后面统一编码)
  897. let h5Url = `https://app.fmode.cn/dev/pobingfeng/owner/nav/cases?storeId=${storeId}`;
  898. // 如果有 token,添加到 URL
  899. if (token) {
  900. h5Url += `&token=${token}`;
  901. } else {
  902. // 如果没有 token,使用游客模式
  903. h5Url += `&guestMode=true`;
  904. }
  905. // 添加案例ID(不要在这里编码,避免双重编码)
  906. h5Url += `&caseId=${caseId}`;
  907. // 如果有合作伙伴ID,也添加到URL中
  908. if (partnerId) {
  909. h5Url += `&partnerId=${partnerId}`;
  910. }
  911. console.log('🌐 H5 URL:', h5Url);
  912. // 编码 URL(统一在这里编码一次)
  913. const encodedUrl = encodeURIComponent(h5Url);
  914. // 构建 web-view 页面路径
  915. let webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&storeId=${storeId}`;
  916. console.log('📄 web-view 页面路径:', webViewPath.substring(0, 100) + '...');
  917. console.log('===========================================');
  918. wx.redirectTo({
  919. url: webViewPath,
  920. success: () => {
  921. console.log('✅ 跳转到案例详情页成功');
  922. },
  923. fail: (err) => {
  924. console.error('❌ 跳转失败:', err);
  925. // 如果 redirectTo 失败,尝试 reLaunch
  926. wx.reLaunch({
  927. url: webViewPath,
  928. fail: (err2) => {
  929. console.error('❌ reLaunch 也失败:', err2);
  930. }
  931. });
  932. }
  933. });
  934. } catch (error) {
  935. console.error('❌ 跳转到案例详情页失败:', error);
  936. }
  937. },
  938. /**
  939. * 记录扫码统计信息
  940. * @param {Object} params - 扫码参数对象
  941. * @param {string} params.storeId - 店铺 ID
  942. * @param {string} params.sourceType - 来源类型 (employee/owner/partner/promoter/store)
  943. * @param {string} params.sourceId - 来源 ID
  944. * @param {string} params.ownerId - 老板 ID
  945. * @param {string} params.employeeId - 员工 ID
  946. * @param {string} params.partnerId - 合作伙伴 ID
  947. * @param {string} params.userId - 推广员 ID
  948. * @param {string} params.productId - 产品 ID
  949. * @param {string} params.scanCount - 扫码次数
  950. */
  951. async recordScanStatistics(params) {
  952. try {
  953. const {
  954. storeId,
  955. sourceType,
  956. sourceId,
  957. ownerId,
  958. employeeId,
  959. partnerId,
  960. userId,
  961. productId,
  962. scanCount
  963. } = params;
  964. // 检查用户是否已登录
  965. const currentUser = Parse.User.current();
  966. if (!currentUser) {
  967. console.log('⚠️ 用户未登录,不记录扫码统计');
  968. return;
  969. }
  970. // 如果没有来源信息,不记录统计
  971. if (!sourceId && !ownerId && !employeeId && !partnerId && !userId) {
  972. console.log('ℹ️ 无来源信息,跳过统计记录');
  973. return;
  974. }
  975. console.log('✅ 用户已登录,记录扫码统计');
  976. // 创建扫码记录
  977. const ScanRecord = Parse.Object.extend('ScanRecord');
  978. const record = new ScanRecord();
  979. record.set('company', {
  980. __type: 'Pointer',
  981. className: 'Company',
  982. objectId: getApp().globalData.company
  983. });
  984. record.set('storeId', storeId);
  985. record.set('sourceType', sourceType || 'unknown');
  986. record.set('scanCount', parseInt(scanCount) || 0);
  987. record.set('scanTime', new Date());
  988. // 根据来源类型设置对应的ID
  989. if (ownerId) {
  990. record.set('ownerId', ownerId);
  991. }
  992. if (employeeId) {
  993. record.set('employeeId', employeeId);
  994. }
  995. if (partnerId) {
  996. record.set('partnerId', partnerId);
  997. }
  998. if (userId) {
  999. record.set('userId', userId);
  1000. }
  1001. if (productId) {
  1002. record.set('productId', productId);
  1003. }
  1004. // 记录扫码用户
  1005. record.set('user', {
  1006. __type: 'Pointer',
  1007. className: '_User',
  1008. objectId: currentUser.id
  1009. });
  1010. await record.save();
  1011. console.log('✅ 扫码记录已保存');
  1012. // 如果存在异业合作伙伴ID,处理异业绑定和扫码次数
  1013. if (partnerId) {
  1014. try {
  1015. console.log('===========================================');
  1016. console.log('======= 处理异业扫码逻辑 =======');
  1017. console.log('用户ID:', currentUser.id);
  1018. console.log('异业ID:', partnerId);
  1019. console.log('门店ID:', storeId);
  1020. // 检查用户是否已经绑定了异业合作伙伴
  1021. const userBoundPartner = currentUser.get('Partner');
  1022. const userBoundPartnerId = userBoundPartner ? userBoundPartner.id : null;
  1023. console.log('用户已绑定的异业ID:', userBoundPartnerId || '无');
  1024. // 规则1:如果用户已绑定其他异业,不处理当前异业的扫码
  1025. if (userBoundPartnerId && userBoundPartnerId !== partnerId) {
  1026. console.log('⚠️ 用户已绑定其他异业,不处理当前异业的扫码');
  1027. console.log(' 已绑定异业:', userBoundPartnerId);
  1028. console.log(' 当前扫码异业:', partnerId);
  1029. console.log('===========================================');
  1030. return;
  1031. }
  1032. // 规则2:检查该用户在该门店是否已经扫过该异业的码
  1033. const ScanRecord = Parse.Object.extend('ScanRecord');
  1034. const scanQuery = new Parse.Query(ScanRecord);
  1035. scanQuery.equalTo('user', {
  1036. __type: 'Pointer',
  1037. className: '_User',
  1038. objectId: currentUser.id
  1039. });
  1040. scanQuery.equalTo('partnerId', partnerId);
  1041. scanQuery.equalTo('storeId', storeId);
  1042. const existingScan = await scanQuery.first();
  1043. if (existingScan) {
  1044. console.log('⚠️ 该用户在该门店已扫过该异业的码,不重复计数');
  1045. console.log(' 首次扫码时间:', existingScan.get('scanTime'));
  1046. console.log('===========================================');
  1047. return;
  1048. }
  1049. console.log('✅ 该用户在该门店首次扫该异业码');
  1050. // 规则3:如果用户还没有绑定异业,绑定当前异业
  1051. if (!userBoundPartnerId) {
  1052. console.log('🔗 用户首次扫异业码,绑定异业合作伙伴:', partnerId);
  1053. currentUser.set('Partner', {
  1054. __type: 'Pointer',
  1055. className: 'Partner',
  1056. objectId: partnerId
  1057. });
  1058. await currentUser.save();
  1059. console.log('✅ 异业绑定成功');
  1060. }
  1061. // 规则4:为该异业在该门店增加扫码次数
  1062. const Partner = Parse.Object.extend('Partner');
  1063. const partnerQuery = new Parse.Query(Partner);
  1064. const partnerObj = await partnerQuery.get(partnerId);
  1065. if (partnerObj) {
  1066. // 获取或初始化门店扫码次数映射
  1067. let storeScans = partnerObj.get('storeScans') || {};
  1068. // 为该门店的扫码次数 +1
  1069. storeScans[storeId] = (storeScans[storeId] || 0) + 1;
  1070. partnerObj.set('storeScans', storeScans);
  1071. // 同时更新总扫码次数
  1072. partnerObj.increment('scanCount', 1);
  1073. await partnerObj.save();
  1074. console.log('✅ 异业扫码次数已更新');
  1075. console.log(' 门店ID:', storeId);
  1076. console.log(' 该门店扫码次数:', storeScans[storeId]);
  1077. console.log(' 总扫码次数:', partnerObj.get('scanCount'));
  1078. }
  1079. console.log('===========================================');
  1080. } catch (e) {
  1081. console.error('❌ 处理异业合作伙伴绑定失败:', e);
  1082. console.log('===========================================');
  1083. }
  1084. }
  1085. } catch (error) {
  1086. console.warn('⚠️ 保存扫码记录失败:', error);
  1087. // 不影响主流程
  1088. }
  1089. }
  1090. });