launchScene.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. /**
  2. * 太阳码 / 扫码进入参数解析(冷启动 onLoad + 热启动 App.onShow 共用)
  3. */
  4. /** 微信「扫小程序码 / 长按识别 / 相册选取」等进入场景值 */
  5. export const SCAN_ENTRY_SCENES = [1011, 1012, 1013, 1047, 1048, 1049]
  6. /**
  7. * 解析 scene 字符串为键值对,如 s=123&a=456&id=789
  8. */
  9. export function parseSceneString(sceneRaw) {
  10. if (!sceneRaw) {
  11. return {}
  12. }
  13. const scene = decodeURIComponent(String(sceneRaw))
  14. const params = {}
  15. scene.split('&').forEach((pair) => {
  16. if (!pair) {
  17. return
  18. }
  19. const eq = pair.indexOf('=')
  20. if (eq === -1) {
  21. params[pair] = ''
  22. return
  23. }
  24. const key = pair.slice(0, eq)
  25. const val = pair.slice(eq + 1)
  26. if (key) {
  27. params[key] = val != null ? decodeURI(val) : ''
  28. }
  29. })
  30. return params
  31. }
  32. /**
  33. * 将 scene 内字段展开为页面 query(account / hdId / id 等)
  34. */
  35. export function sceneParamsToQuery(sceneParams, baseQuery) {
  36. const q = Object.assign({}, baseQuery || {})
  37. if (sceneParams.s) {
  38. q.account = sceneParams.s
  39. }
  40. if (sceneParams.a) {
  41. if (!q.account) {
  42. q.account = sceneParams.a
  43. }
  44. q.staffId = sceneParams.a
  45. }
  46. if (sceneParams.id) {
  47. q.id = sceneParams.id
  48. }
  49. if (sceneParams.hdId) {
  50. q.hdId = sceneParams.hdId
  51. }
  52. delete q.scene
  53. return q
  54. }
  55. /**
  56. * 合并 onLoad 的 option:scene 展开 + 普通二维码 q 参数
  57. */
  58. export function applyLaunchQuery(option) {
  59. const merged = Object.assign({}, option || {})
  60. if (merged.scene) {
  61. const sceneParams = parseSceneString(merged.scene)
  62. Object.assign(merged, sceneParamsToQuery(sceneParams, merged))
  63. delete merged.scene
  64. }
  65. if (merged.q) {
  66. const currentUrl = decodeURIComponent(String(merged.q))
  67. const marker = 'account='
  68. const idx = currentUrl.lastIndexOf(marker)
  69. if (idx >= 0) {
  70. let account = currentUrl.substring(idx + marker.length)
  71. const amp = account.indexOf('&')
  72. if (amp >= 0) {
  73. account = account.substring(0, amp)
  74. }
  75. merged.account = account
  76. }
  77. }
  78. return merged
  79. }
  80. /**
  81. * 拼 reLaunch 地址
  82. */
  83. export function buildLaunchUrl(path, query) {
  84. const normalizedPath = path && path.indexOf('/') === 0 ? path : '/' + (path || '')
  85. const parts = []
  86. const q = query || {}
  87. Object.keys(q).forEach((key) => {
  88. const val = q[key]
  89. if (val !== undefined && val !== null && val !== '') {
  90. parts.push(key + '=' + encodeURIComponent(val))
  91. }
  92. })
  93. return parts.length ? normalizedPath + '?' + parts.join('&') : normalizedPath
  94. }
  95. /**
  96. * 旧入口页(花束分类 / 花材)立刻转到店铺首页。
  97. * 太阳码、分享链接仍可能打开 category / item,需带上 account、hdId、邀请人等参数,
  98. * 避免进店后丢门店或拉新绑定。redirectTo 失败时用 reLaunch 兜底。
  99. */
  100. export function redirectToShopHome(option) {
  101. const query = option || {}
  102. const url = buildLaunchUrl('/pages/home/index', {
  103. account: query.account || uni.getStorageSync('account') || '',
  104. hdId: query.hdId || '',
  105. inviterCustomId: query.inviterCustomId || '',
  106. staffId: query.staffId || '',
  107. token: query.token || ''
  108. })
  109. uni.redirectTo({
  110. url,
  111. fail() {
  112. uni.reLaunch({ url })
  113. }
  114. })
  115. }
  116. /**
  117. * 写入 account / hdId / currentQuery(与原 globalMixins 一致)
  118. * hdId:仅当本次入参带有效值时写入;缺省不覆盖本地已有绑定,
  119. * 避免分享进页(故意不带 hdId)或跳转漏参时把接口回填的 hdId 冲成 0
  120. */
  121. export function persistLaunchStorage(option) {
  122. if (option.account) {
  123. uni.setStorageSync('account', option.account)
  124. } else {
  125. uni.setStorageSync('account', 0)
  126. }
  127. // 无有效 hdId 时保留 storage,防止全局 onLoad 误清空门店绑定
  128. if (option.hdId) {
  129. uni.setStorageSync('hdId', option.hdId)
  130. }
  131. uni.setStorageSync('currentQuery', JSON.stringify(option))
  132. }
  133. /** 分享邀请人 customId 的 storage key(按门店隔离) */
  134. export function getShareInviterStorageKey(account) {
  135. const shopId = account || uni.getStorageSync('account') || ''
  136. return shopId ? 'shareInviter_' + shopId : ''
  137. }
  138. /**
  139. * 缓存分享链接中的邀请人 xhCustom.id,供注册后拉新绑定
  140. */
  141. export function persistShareInviter(option) {
  142. const inviterCustomId = option && option.inviterCustomId ? Number(option.inviterCustomId) : 0
  143. const account = option && option.account ? option.account : uni.getStorageSync('account')
  144. const key = getShareInviterStorageKey(account)
  145. if (!key || inviterCustomId <= 0) {
  146. return
  147. }
  148. uni.setStorageSync(key, inviterCustomId)
  149. }
  150. /** 读取当前门店缓存的邀请人 ID */
  151. export function getShareInviterCustomId(account) {
  152. const key = getShareInviterStorageKey(account)
  153. if (!key) {
  154. return 0
  155. }
  156. const val = uni.getStorageSync(key)
  157. return val ? Number(val) : 0
  158. }
  159. /** 绑定成功后清除邀请人缓存 */
  160. export function clearShareInviter(account) {
  161. const key = getShareInviterStorageKey(account)
  162. if (key) {
  163. uni.removeStorageSync(key)
  164. }
  165. }
  166. /**
  167. * 热启动:根据 getEnterOptionsSync 判断是否需要 reLaunch 到展开 query 的目标页
  168. * @returns {boolean} 是否已触发 reLaunch
  169. */
  170. export function tryReLaunchFromScanEnter(globalData) {
  171. if (!uni.getEnterOptionsSync) {
  172. return false
  173. }
  174. const enter = uni.getEnterOptionsSync()
  175. if (!enter || !enter.query || !enter.query.scene) {
  176. return false
  177. }
  178. const launchKey = (enter.path || '') + '|' + enter.query.scene
  179. const sceneCode = Number(enter.scene)
  180. const isScanEntry = SCAN_ENTRY_SCENES.indexOf(sceneCode) >= 0
  181. if (!isScanEntry && globalData.lastLaunchKey === launchKey) {
  182. return false
  183. }
  184. const sceneParams = parseSceneString(enter.query.scene)
  185. const expandedQuery = sceneParamsToQuery(sceneParams, Object.assign({}, enter.query))
  186. const targetUrl = buildLaunchUrl(enter.path, expandedQuery)
  187. const targetRoute = (enter.path || '').replace(/^\//, '')
  188. const pages = getCurrentPages()
  189. const current = pages.length ? pages[pages.length - 1] : null
  190. if (current) {
  191. const curRoute = current.route || ''
  192. const curOpts = current.options || {}
  193. // 已在目标页且参数已展开(无 scene),无需重复 reLaunch
  194. if (curRoute === targetRoute && !curOpts.scene && (curOpts.account || curOpts.id || curOpts.hdId)) {
  195. globalData.lastLaunchKey = launchKey
  196. return false
  197. }
  198. }
  199. globalData.lastLaunchKey = launchKey
  200. uni.reLaunch({ url: targetUrl })
  201. return true
  202. }