index.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  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. // 检查是否是员工邀请链接(app.fmode.cn/dev/pobingfeng/manager/staff?invite=xxx)
  52. if (str.includes('app.fmode.cn/dev/pobingfeng/manager/staff')) {
  53. let obj = this.getParaName(str);
  54. if (obj && obj.invite) {
  55. wx.setStorageSync("invite", obj.invite);
  56. console.log('✅ 检测到员工邀请链接,invite:', obj.invite);
  57. }
  58. // 员工邀请链接不需要跳转到店铺,直接返回
  59. this.setData({ options: options });
  60. plugin.init(config, wx.getStorageSync('invite'));
  61. return;
  62. }
  63. // 检查是否是活动海报二维码(activityId作为qrCode参数值)
  64. // 如果str不包含http/https,且看起来像是一个ID,则可能是activityId
  65. if (!str.includes('http://') && !str.includes('https://') && !str.includes('?')) {
  66. // 可能是活动ID,尝试作为activityId处理
  67. if (str && str.length > 0 && !isNaN(str)) {
  68. options.activityId = str;
  69. console.log('✅ 检测到活动海报二维码,activityId:', str);
  70. // 活动二维码需要特殊处理,先保存activityId
  71. wx.setStorageSync("activityId", str);
  72. }
  73. }
  74. // 处理店铺相关的二维码链接(pwa.fmode.cn/gomini/pmd/)
  75. // 从完整URL中提取参数
  76. if (str.includes('pwa.fmode.cn/gomini/pmd') || str.includes('?')) {
  77. let obj = this.getParaName(str);
  78. if (obj && obj.invite) {
  79. wx.setStorageSync("invite", obj.invite);
  80. }
  81. // 将所有参数合并到 options 中
  82. obj && Object.keys(obj).forEach(key=> options[key] = obj[key]);
  83. }
  84. }
  85. // 兼容从编译参数或页面直达传入的 storeId
  86. if (options && options.scene && !options.storeId) {
  87. try {
  88. const sceneStr = decodeURIComponent(options.scene)
  89. if (sceneStr) {
  90. const pairs = sceneStr.split('&')
  91. for (const p of pairs) {
  92. const [k, v] = p.split('=')
  93. if (k === 'storeId' && v) {
  94. options.storeId = v
  95. break
  96. }
  97. }
  98. }
  99. } catch (e) {
  100. console.warn('解析 scene 失败:', e)
  101. }
  102. }
  103. this.setData({
  104. options: options
  105. })
  106. let {
  107. time,
  108. dramaId,
  109. roomId,
  110. orderId,
  111. shopId,
  112. invite,
  113. activityId,
  114. company,
  115. inviteHost,
  116. storeId
  117. } = options
  118. time && wx.setStorageSync("time", time);
  119. dramaId && wx.setStorageSync("dramaId", dramaId);
  120. roomId && wx.setStorageSync("roomId", roomId);
  121. orderId && wx.setStorageSync("orderId", orderId);
  122. shopId && wx.setStorageSync("shopId", shopId);
  123. invite && wx.setStorageSync("invite", invite);
  124. activityId && wx.setStorageSync("activityId", activityId);
  125. inviteHost && wx.setStorageSync("inviteHost", true);
  126. if (storeId) {
  127. wx.setStorageSync('storeId', storeId)
  128. getApp().globalData.storeId = storeId
  129. console.log('✅ 入口页已设置店铺 ID:', storeId)
  130. }
  131. if (company) getApp().globalData.toCompany = true;
  132. // 检查是否是扫码进入(需要统计扫码次数)
  133. this.checkAndHandleScan(options);
  134. plugin.init(config, wx.getStorageSync('invite'))
  135. },
  136. /**
  137. * 生命周期函数--监听页面初次渲染完成
  138. */
  139. onReady: async function () { },
  140. /**
  141. * 生命周期函数--监听页面显示
  142. */
  143. onShow: async function () {
  144. await this.review()
  145. },
  146. async review(force){
  147. try {
  148. let options = this.data.options
  149. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath
  150. if (options) {
  151. let objArr = Object.keys(options)
  152. if (objArr && objArr.length > 0) {
  153. let parms = '?'
  154. objArr.forEach((o, index) => {
  155. if (index > 0) {
  156. parms += '&' + o + '=' + options[o]
  157. } else {
  158. parms += o + '=' + options[o]
  159. }
  160. })
  161. url += parms
  162. }
  163. }
  164. let currentUser = Parse.User.current()
  165. console.log(Parse.User.current())
  166. if (!currentUser || force) {
  167. // let data = await wxLogin()
  168. // if (data.statusCode == 200) {
  169. let r = await checkAuth(true)
  170. console.log(r);
  171. // getApp().Parse = Parse
  172. // getApp().checkAuth = checkAuth
  173. if(!r) return
  174. // }
  175. } else {
  176. this.updateUser(currentUser.id)
  177. }
  178. getApp().Parse = Parse
  179. getApp().checkAuth = checkAuth
  180. if (!await this.getCompanyServerExpire(url)) {
  181. return
  182. }
  183. // 检查是否需要跳转到活动页面
  184. if (wx.getStorageSync('need_activity_redirect') === true) {
  185. await this.redirectToActivityPage();
  186. return;
  187. }
  188. // 检查是否需要跳转到扫码统计页面
  189. if (this.shouldRedirectToScanPage()) {
  190. await this.redirectToScanPage();
  191. return;
  192. }
  193. wx.redirectTo({
  194. url: url,
  195. });
  196. }
  197. catch (err) {
  198. console.log(err);
  199. /* 登录身份信息到期,重新登陆 */
  200. if((err?.message.indexOf('Session token is expired') != -1 || err?.message.indexOf('Invalid session token') != -1) && !force){
  201. let invite = wx.getStorageSync('invite')
  202. wx.clearStorageSync()
  203. invite && wx.setStorageSync('invite', invite)
  204. /* 强制重新登录 */
  205. this.review(true)
  206. return
  207. }
  208. this.setData({
  209. loading:false
  210. })
  211. wx.showModal({
  212. title: '温馨提示',
  213. content: '服务器正在升级,请稍后重试。',
  214. showCancel: false,
  215. cancelText: '取消',
  216. cancelColor: '#000000',
  217. confirmText: '确定',
  218. confirmColor: '#3CC51F',
  219. success: (result) => {
  220. if (result.confirm) {
  221. wx.exitMiniProgram()
  222. }
  223. },
  224. });
  225. }
  226. },
  227. async updateUser(id) {
  228. let User = new Parse.Query('_User')
  229. let user = await User.get(id)
  230. let invite = wx.getStorageSync('invite')
  231. //查询邀请人user
  232. let query = new Parse.Query("_User")
  233. query.equalTo('objectId', invite)
  234. let result = await query.first()
  235. if (result && result.id && result.get("invite")?.id == user.id) {
  236. console.error('邀请人不能是自己的下级')
  237. return
  238. }
  239. if (invite && !user.get('invite') && user.id != invite && !user.get('agentLevel')) {
  240. console.log('上下级绑定成功');
  241. user.set('invite', {
  242. __type: "Pointer",
  243. className: "_User",
  244. objectId: invite
  245. })
  246. user.set('agent', {
  247. __type: "Pointer",
  248. className: "_User",
  249. objectId: invite
  250. })
  251. await Parse.Cloud.run('user_save', {
  252. userJson: user.toJSON()
  253. })
  254. }
  255. },
  256. async getCompanyServerExpire(url) {
  257. let query = new Parse.Query('Company')
  258. query.equalTo('objectId', getApp().globalData.company)
  259. query.select('expireDate', 'expireMap')
  260. let com = await query.first()
  261. if (com?.id && com?.get('expireDate')) {
  262. let now = + new Date()
  263. let expireTime = + new Date(com?.get('expireDate'))
  264. if (com?.get('expireMap') && com.get('expireMap')[getApp().globalData.appid]) {
  265. expireTime = + new Date(com.get('expireMap')[getApp().globalData.appid])
  266. }
  267. if (now >= expireTime) {
  268. console.log('服务器到期');
  269. wx.reLaunch({
  270. url: `common-page/pages/loading/index?url=${url}`,
  271. });
  272. return
  273. }
  274. }
  275. return true
  276. },
  277. onUnload: function () {
  278. wx.setStorageSync("active", 0);
  279. },
  280. getParaName(url) {
  281. if (!url || url.indexOf('?') == -1) {
  282. return
  283. }
  284. // 提取查询参数部分(去除可能的hash部分)
  285. let queryString = url.split('?')[1];
  286. // 如果包含 #,只取 # 之前的部分
  287. if (queryString.indexOf('#') !== -1) {
  288. queryString = queryString.split('#')[0];
  289. }
  290. return this.setObject(queryString) //封装成对象
  291. },
  292. setObject(paraArr) {
  293. let obj = {}
  294. let arr1 = paraArr.split('&')
  295. arr1.forEach(item => {
  296. let str = item.split('=')
  297. let key = str[0]
  298. let val = str[1]
  299. obj[key] = val
  300. })
  301. return obj
  302. },
  303. /**
  304. * 检查并处理扫码参数
  305. * 支持所有类型的二维码:
  306. * 1. 推广员二维码: ?scanCount=0&storeId=xxx&userId=xxx
  307. * 2. 产品二维码: ?scanCount=0&storeId=xxx&productId=xxx
  308. * 3. 活动海报二维码: activityId作为qrCode参数值
  309. * 4. 异业合作伙伴二维码(移动端 / PC端统一): ?scanCount=xxx&storeId=xxx&partnerId=xxx
  310. * 5. 员工邀请二维码(移动端): ?scanCount=0&storeId=xxx&employeeId=xxx
  311. * 6. 我的二维码(老板): ?scanCount=0&storeId=xxx&ownerId=xxx
  312. * 7. 我的二维码(员工): ?scanCount=0&storeId=xxx&employeeId=xxx
  313. */
  314. checkAndHandleScan(options) {
  315. const {
  316. scanCount,
  317. ownerId,
  318. employeeId,
  319. partnerId,
  320. storeId,
  321. productId,
  322. userId, // 推广员二维码使用userId
  323. activityId
  324. } = options;
  325. // 处理活动海报二维码(activityId作为qrCode参数值)
  326. if (activityId && !storeId) {
  327. console.log('===========================================');
  328. console.log('======= 检测到活动海报二维码 =======');
  329. console.log('活动ID (activityId):', activityId);
  330. console.log('===========================================');
  331. // 活动二维码需要跳转到活动页面,不需要跳转到店铺
  332. wx.setStorageSync('scan_activityId', activityId);
  333. wx.setStorageSync('need_activity_redirect', true);
  334. return;
  335. }
  336. // 如果存在 storeId,说明是扫码进入具体门店(包括异业分享)
  337. if (storeId) {
  338. console.log('===========================================');
  339. console.log('======= 检测到扫码参数 =======');
  340. console.log('店铺ID (storeId):', storeId);
  341. console.log('扫码次数 (scanCount):', scanCount || '0');
  342. // 确定来源类型
  343. let sourceType = 'unknown';
  344. let sourceId = null;
  345. if (employeeId) {
  346. sourceType = 'employee';
  347. sourceId = employeeId;
  348. console.log('来源类型: 员工展业');
  349. console.log('员工ID (employeeId):', employeeId);
  350. } else if (userId) {
  351. // 推广员二维码使用userId
  352. sourceType = 'promoter';
  353. sourceId = userId;
  354. console.log('来源类型: 推广员展业');
  355. console.log('推广员ID (userId):', userId);
  356. } else if (ownerId) {
  357. sourceType = 'owner';
  358. sourceId = ownerId;
  359. console.log('来源类型: 老板展业');
  360. console.log('老板ID (ownerId):', ownerId);
  361. } else if (partnerId) {
  362. sourceType = 'partner';
  363. sourceId = partnerId;
  364. console.log('来源类型: 异业合作伙伴');
  365. console.log('合作伙伴ID (partnerId):', partnerId);
  366. } else {
  367. sourceType = 'store';
  368. console.log('来源类型: 店铺分享');
  369. }
  370. if (productId) {
  371. console.log('产品ID (productId):', productId);
  372. }
  373. console.log('===========================================');
  374. // 保存到临时存储,用于后续跳转和统计
  375. wx.setStorageSync('scan_storeId', storeId);
  376. wx.setStorageSync('scan_scanCount', scanCount || '0');
  377. wx.setStorageSync('scan_sourceType', sourceType);
  378. wx.setStorageSync('scan_sourceId', sourceId || '');
  379. wx.setStorageSync('scan_ownerId', ownerId || '');
  380. wx.setStorageSync('scan_employeeId', employeeId || '');
  381. wx.setStorageSync('scan_partnerId', partnerId || '');
  382. wx.setStorageSync('scan_userId', userId || '');
  383. if (productId) {
  384. wx.setStorageSync('scan_productId', productId);
  385. }
  386. wx.setStorageSync('need_scan_redirect', true);
  387. } else if (partnerId) {
  388. // 理论上异业二维码现在也必须带 storeId,这里仅作为兜底保护
  389. console.warn('⚠️ 检测到异业合作伙伴二维码缺少 storeId,无法确定门店,请检查二维码生成逻辑');
  390. }
  391. },
  392. /**
  393. * 判断是否需要跳转到扫码统计页面
  394. */
  395. shouldRedirectToScanPage() {
  396. return wx.getStorageSync('need_scan_redirect') === true;
  397. },
  398. /**
  399. * 跳转到扫码对应的店铺页面
  400. * 根据 storeId 跳转到对应店铺,同时记录所有来源信息用于统计
  401. */
  402. async redirectToScanPage() {
  403. try {
  404. // 获取所有扫码相关参数
  405. const storeId = wx.getStorageSync('scan_storeId');
  406. const scanCount = wx.getStorageSync('scan_scanCount');
  407. const sourceType = wx.getStorageSync('scan_sourceType');
  408. const sourceId = wx.getStorageSync('scan_sourceId');
  409. const ownerId = wx.getStorageSync('scan_ownerId');
  410. const employeeId = wx.getStorageSync('scan_employeeId');
  411. const partnerId = wx.getStorageSync('scan_partnerId');
  412. const userId = wx.getStorageSync('scan_userId');
  413. const productId = wx.getStorageSync('scan_productId');
  414. // 清除临时存储
  415. wx.removeStorageSync('scan_storeId');
  416. wx.removeStorageSync('scan_scanCount');
  417. wx.removeStorageSync('scan_sourceType');
  418. wx.removeStorageSync('scan_sourceId');
  419. wx.removeStorageSync('scan_ownerId');
  420. wx.removeStorageSync('scan_employeeId');
  421. wx.removeStorageSync('scan_partnerId');
  422. wx.removeStorageSync('scan_userId');
  423. wx.removeStorageSync('scan_productId');
  424. wx.removeStorageSync('need_scan_redirect');
  425. if (!storeId) {
  426. console.error('❌ 缺少 storeId 参数,无法跳转');
  427. return;
  428. }
  429. console.log('===========================================');
  430. console.log('======= 扫码进入店铺 =======');
  431. console.log('店铺 ID (storeId):', storeId);
  432. console.log('来源类型 (sourceType):', sourceType);
  433. console.log('来源 ID (sourceId):', sourceId || '无');
  434. console.log('扫码次数 (scanCount):', scanCount);
  435. if (ownerId) console.log('老板 ID (ownerId):', ownerId);
  436. if (employeeId) console.log('员工 ID (employeeId):', employeeId);
  437. if (partnerId) console.log('合作伙伴 ID (partnerId):', partnerId);
  438. if (userId) console.log('推广员 ID (userId):', userId);
  439. if (productId) console.log('产品 ID (productId):', productId);
  440. console.log('===========================================');
  441. // 设置店铺 ID 到全局和本地存储
  442. wx.setStorageSync('storeId', storeId);
  443. getApp().globalData.storeId = storeId;
  444. // 保存来源信息到本地存储(用于后续统计或绑定关系)
  445. if (sourceId) {
  446. wx.setStorageSync('scan_from_sourceType', sourceType);
  447. wx.setStorageSync('scan_from_sourceId', sourceId);
  448. console.log('✅ 已记录来源信息:', sourceType, sourceId);
  449. }
  450. // 记录扫码统计(调用后端接口记录扫码次数)
  451. await this.recordScanStatistics({
  452. storeId,
  453. sourceType,
  454. sourceId,
  455. ownerId,
  456. employeeId,
  457. partnerId,
  458. userId,
  459. productId,
  460. scanCount
  461. });
  462. // 获取默认首页路径并跳转
  463. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  464. url += `?storeId=${storeId}`;
  465. // 如果有产品ID,添加到URL参数中
  466. if (productId) {
  467. url += `&productId=${productId}`;
  468. }
  469. console.log('✅ 跳转到店铺页面:', url);
  470. wx.redirectTo({
  471. url: url,
  472. fail: (err) => {
  473. console.error('❌ 跳转失败:', err);
  474. // 如果 redirectTo 失败,尝试 reLaunch
  475. wx.reLaunch({
  476. url: url,
  477. fail: (err2) => {
  478. console.error('❌ reLaunch 也失败:', err2);
  479. }
  480. });
  481. }
  482. });
  483. } catch (error) {
  484. console.error('❌ 跳转到店铺页面失败:', error);
  485. }
  486. },
  487. /**
  488. * 跳转到活动页面
  489. */
  490. async redirectToActivityPage() {
  491. try {
  492. const activityId = wx.getStorageSync('scan_activityId');
  493. // 清除临时存储
  494. wx.removeStorageSync('scan_activityId');
  495. wx.removeStorageSync('need_activity_redirect');
  496. if (!activityId) {
  497. console.error('❌ 缺少 activityId 参数,无法跳转');
  498. return;
  499. }
  500. console.log('===========================================');
  501. console.log('======= 扫码进入活动页面 =======');
  502. console.log('活动 ID (activityId):', activityId);
  503. console.log('===========================================');
  504. // 保存活动ID
  505. wx.setStorageSync('activityId', activityId);
  506. // 获取默认首页路径并跳转(活动页面可能需要根据实际路由调整)
  507. let url = getApp().globalData.rootPage || getApp().globalData.defaultTabBar.list[0].pagePath;
  508. url += `?activityId=${activityId}`;
  509. console.log('✅ 跳转到活动页面:', url);
  510. wx.redirectTo({
  511. url: url,
  512. fail: (err) => {
  513. console.error('❌ 跳转失败:', err);
  514. wx.reLaunch({
  515. url: url,
  516. fail: (err2) => {
  517. console.error('❌ reLaunch 也失败:', err2);
  518. }
  519. });
  520. }
  521. });
  522. } catch (error) {
  523. console.error('❌ 跳转到活动页面失败:', error);
  524. }
  525. },
  526. /**
  527. * 记录扫码统计信息
  528. * @param {Object} params - 扫码参数对象
  529. * @param {string} params.storeId - 店铺 ID
  530. * @param {string} params.sourceType - 来源类型 (employee/owner/partner/promoter/store)
  531. * @param {string} params.sourceId - 来源 ID
  532. * @param {string} params.ownerId - 老板 ID
  533. * @param {string} params.employeeId - 员工 ID
  534. * @param {string} params.partnerId - 合作伙伴 ID
  535. * @param {string} params.userId - 推广员 ID
  536. * @param {string} params.productId - 产品 ID
  537. * @param {string} params.scanCount - 扫码次数
  538. */
  539. async recordScanStatistics(params) {
  540. try {
  541. const {
  542. storeId,
  543. sourceType,
  544. sourceId,
  545. ownerId,
  546. employeeId,
  547. partnerId,
  548. userId,
  549. productId,
  550. scanCount
  551. } = params;
  552. // 如果没有来源信息,不记录统计
  553. if (!sourceId && !ownerId && !employeeId && !partnerId && !userId) {
  554. console.log('ℹ️ 无来源信息,跳过统计记录');
  555. return;
  556. }
  557. // 创建扫码记录
  558. const ScanRecord = Parse.Object.extend('ScanRecord');
  559. const record = new ScanRecord();
  560. record.set('company', {
  561. __type: 'Pointer',
  562. className: 'Company',
  563. objectId: getApp().globalData.company
  564. });
  565. record.set('storeId', storeId);
  566. record.set('sourceType', sourceType || 'unknown');
  567. record.set('scanCount', parseInt(scanCount) || 0);
  568. record.set('scanTime', new Date());
  569. // 根据来源类型设置对应的ID
  570. if (ownerId) {
  571. record.set('ownerId', ownerId);
  572. }
  573. if (employeeId) {
  574. record.set('employeeId', employeeId);
  575. }
  576. if (partnerId) {
  577. record.set('partnerId', partnerId);
  578. }
  579. if (userId) {
  580. record.set('userId', userId);
  581. }
  582. if (productId) {
  583. record.set('productId', productId);
  584. }
  585. // 如果用户已登录,记录扫码用户
  586. const currentUser = Parse.User.current();
  587. if (currentUser) {
  588. record.set('user', {
  589. __type: 'Pointer',
  590. className: '_User',
  591. objectId: currentUser.id
  592. });
  593. }
  594. await record.save();
  595. console.log('✅ 扫码记录已保存');
  596. // 如果存在异业合作伙伴ID,则在其 scanCount 字段上自增 1(数据库中的 Partner 表)
  597. if (partnerId) {
  598. try {
  599. const Partner = Parse.Object.extend('Partner');
  600. const partnerQuery = new Parse.Query(Partner);
  601. const partnerObj = await partnerQuery.get(partnerId);
  602. if (partnerObj) {
  603. partnerObj.increment('scanCount', 1);
  604. await partnerObj.save();
  605. console.log('✅ 已为异业合作伙伴累加一次扫码次数,partnerId:', partnerId);
  606. }
  607. } catch (e) {
  608. console.warn('⚠️ 更新异业合作伙伴扫码次数失败:', e);
  609. }
  610. }
  611. } catch (error) {
  612. console.warn('⚠️ 保存扫码记录失败:', error);
  613. // 不影响主流程
  614. }
  615. }
  616. });