index.js 38 KB

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