qrScene.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /**
  2. * 扫码 / 小程序 scene 解析:店铺、桌号、业务线 m。
  3. * 无 m 或 m=1:美食(原 /store/dining、/store/order 等)。
  4. * m=2:通用价目,仅文档所列接口改为 generic-dining / generic-order(见 utils/m2GenericApiPath.js)。
  5. * 其它显式 m:不允许进入扫码点餐链路。
  6. */
  7. export function isDiningBusinessMode(m) {
  8. if (m == null || String(m).trim() === '') return true;
  9. const s = String(m).trim();
  10. const n = Number(s);
  11. if (!Number.isNaN(n)) return n === 1;
  12. return s === '1';
  13. }
  14. /** 是否允许通过扫码进入点餐流程(美食 m=1/无 m,或通用价目 m=2) */
  15. export function isScanEntryAllowed(m) {
  16. if (m == null || String(m).trim() === '') return true;
  17. const s = String(m).trim();
  18. const n = Number(s);
  19. if (!Number.isNaN(n)) return n === 1 || n === 2;
  20. return s === '1' || s === '2';
  21. }
  22. function trim(v) {
  23. return v == null ? '' : String(v).trim();
  24. }
  25. /**
  26. * 解析单段 scene 字符串(已解码或待解码的 query 片段)
  27. * @returns {{ storeId: string, tableId: string, m: string }}
  28. */
  29. export function parseSceneToStoreTable(sceneStr) {
  30. let storeId = '';
  31. let tableId = '';
  32. let m = '';
  33. if (!sceneStr) return { storeId, tableId, m };
  34. let decoded = String(sceneStr).trim();
  35. try {
  36. decoded = decodeURIComponent(decoded);
  37. } catch (_) {}
  38. decoded = trim(decoded);
  39. const parseKv = (text) => {
  40. const out = { storeId: '', tableId: '', m: '' };
  41. text.split('&').forEach((pair) => {
  42. const eq = pair.indexOf('=');
  43. if (eq > 0) {
  44. const k = pair.substring(0, eq).trim().toLowerCase();
  45. let v = pair.substring(eq + 1).trim();
  46. try {
  47. v = decodeURIComponent(v);
  48. } catch (_) {}
  49. v = trim(v);
  50. if (['s', 'storeid', 'store_id'].includes(k)) out.storeId = v;
  51. if (['t', 'tableid', 'table_id', 'tableno', 'table'].includes(k)) out.tableId = v;
  52. if (k === 'm' || k === 'mode') out.m = v;
  53. }
  54. });
  55. return out;
  56. };
  57. try {
  58. // 小程序码 scene 为 base64(JSON)
  59. if (/^[A-Za-z0-9+/=]+$/.test(decoded) && decoded.length > 20) {
  60. try {
  61. const jsonStr = typeof atob !== 'undefined' ? atob(decoded) : '';
  62. if (jsonStr && jsonStr.startsWith('{')) {
  63. const obj = JSON.parse(jsonStr);
  64. return {
  65. storeId: trim(obj?.storeId ?? obj?.store_id ?? obj?.s ?? ''),
  66. tableId: trim(obj?.tableId ?? obj?.table_id ?? obj?.tableid ?? obj?.t ?? obj?.tableNo ?? obj?.table ?? ''),
  67. m: trim(obj?.m ?? obj?.mode ?? '')
  68. };
  69. }
  70. } catch (_) {}
  71. }
  72. if (decoded.startsWith('{') && decoded.endsWith('}')) {
  73. const obj = JSON.parse(decoded);
  74. return {
  75. storeId: trim(obj?.storeId ?? obj?.store_id ?? obj?.s ?? ''),
  76. tableId: trim(obj?.tableId ?? obj?.table_id ?? obj?.tableid ?? obj?.t ?? obj?.tableNo ?? obj?.table ?? ''),
  77. m: trim(obj?.m ?? obj?.mode ?? '')
  78. };
  79. }
  80. if (/^\d+_\d+$/.test(decoded)) {
  81. const [s, t] = decoded.split('_');
  82. return { storeId: trim(s), tableId: trim(t), m: '' };
  83. }
  84. if (/^\d+$/.test(decoded)) return { storeId: '', tableId: decoded, m: '' };
  85. const kv = parseKv(decoded);
  86. return { storeId: kv.storeId, tableId: kv.tableId, m: kv.m };
  87. } catch (err) {
  88. console.warn('parseSceneToStoreTable', err);
  89. }
  90. return { storeId, tableId, m };
  91. }
  92. /**
  93. * 微信扫一扫 / 小程序码 path 等完整字符串 → 店铺、桌号、m
  94. */
  95. export function parseQrScanResult(str) {
  96. const empty = { storeId: '', tableId: '', m: '' };
  97. if (!str) return empty;
  98. const s = trim(str);
  99. try {
  100. const qIdx = s.indexOf('?');
  101. const hIdx = s.indexOf('#');
  102. if (qIdx >= 0) {
  103. const queryPart = hIdx >= 0 ? s.substring(qIdx + 1, hIdx) : s.substring(qIdx + 1);
  104. const params = new Map();
  105. queryPart.split('&').forEach((pair) => {
  106. const eq = pair.indexOf('=');
  107. if (eq > 0) {
  108. const k = pair.substring(0, eq).trim();
  109. let v = pair.substring(eq + 1).trim();
  110. try {
  111. v = decodeURIComponent(v);
  112. } catch (_) {}
  113. params.set(k, v);
  114. }
  115. });
  116. const scene = params.get('scene') || params.get('Scene');
  117. if (scene) return parseSceneToStoreTable(scene);
  118. return parseSceneToStoreTable(queryPart);
  119. }
  120. if ((s.startsWith('{') && s.endsWith('}')) || (s.startsWith('[') && s.endsWith(']'))) {
  121. const obj = JSON.parse(s);
  122. return {
  123. storeId: trim(obj?.storeId ?? obj?.store_id ?? obj?.s ?? ''),
  124. tableId: trim(obj?.tableId ?? obj?.table_id ?? obj?.tableid ?? obj?.t ?? obj?.tableNo ?? obj?.table ?? ''),
  125. m: trim(obj?.m ?? obj?.mode ?? '')
  126. };
  127. }
  128. if (/^[A-Za-z0-9+/=]+$/.test(s) && s.length > 20) {
  129. try {
  130. const jsonStr = typeof atob !== 'undefined' ? atob(s) : '';
  131. if (jsonStr && jsonStr.startsWith('{')) {
  132. return parseSceneToStoreTable(jsonStr);
  133. }
  134. } catch (_) {}
  135. }
  136. return parseSceneToStoreTable(s);
  137. } catch (err) {
  138. console.warn('parseQrScanResult', err);
  139. }
  140. return empty;
  141. }