bridge.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. (function (window) {
  2. 'use strict';
  3. // Logger Utility
  4. const Logger = {
  5. _isDev: false,
  6. init: function(isDev) {
  7. this._isDev = isDev;
  8. },
  9. log: function() {
  10. if (this._isDev) {
  11. console.log.apply(console, arguments);
  12. }
  13. },
  14. warn: function() {
  15. if (this._isDev) {
  16. console.warn.apply(console, arguments);
  17. }
  18. },
  19. error: function() {
  20. console.error.apply(console, arguments); // Always log errors
  21. }
  22. };
  23. // Determine _isDev status from main window's URL query params
  24. // This function is defined here to be self-contained for bridge.js
  25. function getQueryParam(name) {
  26. const params = new URLSearchParams(window.location.search);
  27. return params.get(name);
  28. }
  29. const env = (getQueryParam('env') || '').toLowerCase();
  30. Logger.init(env === 'mock'); // Initialize Logger
  31. /**
  32. * ColorMapRun JSBridge SDK (Compatible Version)
  33. * 用于 H5 页面与 Flutter App 进行交互
  34. * 兼容性说明:
  35. * - 优先检测 uni.webView 标准通道
  36. * - 降级适配旧版 App 的 action:// 协议拦截和 window.share_wx 注入对象
  37. */
  38. var Bridge = {
  39. version: '1.0.2',
  40. /**
  41. * 内部核心发送方法
  42. */
  43. _post: function (action, data) {
  44. data = data || {};
  45. Logger.log('[Bridge] Call:', action, data);
  46. // 1. 优先尝试标准 uni 通道 (新版 App)
  47. if (window.uni && window.uni.postMessage) {
  48. window.uni.postMessage({
  49. data: {
  50. action: action,
  51. data: data
  52. }
  53. });
  54. return;
  55. }
  56. // 2. 降级适配 (旧版 App)
  57. this._fallback(action, data);
  58. },
  59. /**
  60. * 旧版 App 适配逻辑
  61. */
  62. _fallback: function (action, data) {
  63. var url = '';
  64. switch (action) {
  65. case 'back':
  66. // 尝试关闭页面或返回
  67. window.history.back();
  68. break;
  69. case 'toHome':
  70. // 协议: action://to_home/
  71. url = 'action://to_home/';
  72. break;
  73. case 'toLogin':
  74. // 协议: action://to_login/
  75. url = 'action://to_login/';
  76. break;
  77. case 'openMap':
  78. // 协议: action://to_map_app?title=xxx&latitude=xxx&longitude=xxx
  79. url = 'action://to_map_app?title=' + encodeURIComponent(data.name || '') +
  80. '&latitude=' + data.latitude +
  81. '&longitude=' + data.longitude;
  82. break;
  83. case 'openMatch':
  84. // 协议: action://to_detail/?id=xxx&matchType=xxx
  85. url = 'action://to_detail/?id=' + data.id +
  86. '&matchType=' + (data.type || 1);
  87. break;
  88. case 'openActivityList':
  89. // 协议: action://to_activity_list/?id=xxx&mapName=xxx
  90. url = 'action://to_activity_list/?id=' + data.id +
  91. '&mapName=' + encodeURIComponent(data.mapName || '');
  92. break;
  93. case 'shareWx':
  94. // 旧版使用注入对象 share_wx
  95. if (window.share_wx && window.share_wx.postMessage) {
  96. window.share_wx.postMessage(JSON.stringify(data));
  97. } else {
  98. Logger.error('[Bridge] share_wx injection not found');
  99. alert('微信分享功能不可用(环境不支持)');
  100. }
  101. return; // shareWx 不需要走 URL 拦截
  102. case 'launchWxMini':
  103. // 旧版使用注入对象 wx_launch_mini
  104. if (window.wx_launch_mini && window.wx_launch_mini.postMessage) {
  105. window.wx_launch_mini.postMessage(JSON.stringify(data));
  106. } else {
  107. Logger.error('[Bridge] wx_launch_mini injection not found');
  108. }
  109. return;
  110. case 'saveImage':
  111. // 旧版使用注入对象 save_base64
  112. if (window.save_base64 && window.save_base64.postMessage) {
  113. window.save_base64.postMessage(data.base64);
  114. } else {
  115. Logger.error('[Bridge] save_base64 injection not found');
  116. }
  117. return;
  118. case 'makePhoneCall':
  119. url = 'tel:' + data.phoneNumber;
  120. break;
  121. case 'showToast':
  122. // 降级为 alert,体验稍差但保证可见
  123. // setTimeout 避免阻塞当前执行流
  124. setTimeout(function() { alert(data.title); }, 10);
  125. return;
  126. case 'showModal':
  127. setTimeout(function() {
  128. var result = confirm(data.content || data.title);
  129. // 无法同步返回结果给 App 逻辑,仅做展示
  130. }, 10);
  131. return;
  132. default:
  133. Logger.warn('[Bridge] No legacy fallback for action:', action);
  134. }
  135. if (url) {
  136. Logger.log('[Bridge] Legacy URL jump:', url);
  137. // 触发 URL 拦截
  138. window.location.href = url;
  139. }
  140. },
  141. // ==============================
  142. // Ported from common/tools.js
  143. // ==============================
  144. /**
  145. * 对url追加项目版本号
  146. */
  147. urlAddVer: function(url) {
  148. var newUrl = url;
  149. try {
  150. if (window.uni && window.uni.getSystemInfoSync) {
  151. var systemInfo = window.uni.getSystemInfoSync();
  152. var version_number = systemInfo.appVersion;
  153. if (version_number) {
  154. if (newUrl.indexOf('_v=') !== -1) {
  155. return newUrl;
  156. }
  157. if (newUrl.indexOf('?') !== -1) {
  158. newUrl += "&_v=" + version_number;
  159. } else {
  160. newUrl += "?_v=" + version_number;
  161. }
  162. }
  163. }
  164. } catch (e) {
  165. Logger.warn('[Bridge] urlAddVer error:', e);
  166. }
  167. Logger.log("[Bridge] urlAddVer newUrl:", newUrl);
  168. return newUrl;
  169. },
  170. /**
  171. * 导航到APP内的某个页面或执行APP内部的某些功能
  172. */
  173. appAction: function(url, actType) {
  174. actType = actType || "";
  175. Logger.log("[Bridge] appAction:", url, "actType:", actType);
  176. if (url.indexOf('http') !== -1) {
  177. window.location.href = this.urlAddVer(url);
  178. } else if (url == "reload") {
  179. window.location.reload();
  180. } else if (actType == "uni.navigateTo" && window.uni && window.uni.navigateTo) {
  181. window.uni.navigateTo({
  182. url: this.urlAddVer(url)
  183. });
  184. } else {
  185. window.location.href = url;
  186. }
  187. },
  188. // ==============================
  189. // 公开 API
  190. // ==============================
  191. back: function () {
  192. this._post('back');
  193. },
  194. toHome: function() {
  195. this._post('toHome');
  196. },
  197. toLogin: function() {
  198. this._post('toLogin');
  199. },
  200. setTitle: function (title) {
  201. this._post('setTitle', { title: title });
  202. },
  203. openMap: function (latitude, longitude, name) {
  204. this._post('openMap', {
  205. latitude: latitude,
  206. longitude: longitude,
  207. name: name
  208. });
  209. },
  210. openMatch: function (id, type) {
  211. this._post('openMatch', {
  212. id: id,
  213. type: type
  214. });
  215. },
  216. openActivityList: function(id, mapName) {
  217. this._post('openActivityList', {
  218. id: id,
  219. mapName: mapName
  220. });
  221. },
  222. shareWx: function (options) {
  223. this._post('shareWx', options);
  224. },
  225. launchWxMini: function (username, path) {
  226. this._post('launchWxMini', { username: username, path: path });
  227. },
  228. saveImage: function (base64Str) {
  229. this._post('saveImage', { base64: base64Str });
  230. },
  231. // --- 新增完善功能 ---
  232. /**
  233. * 预览图片
  234. * @param {Array} urls 图片地址数组
  235. * @param {String} current 当前显示图片的地址
  236. */
  237. previewImage: function(urls, current) {
  238. this._post('previewImage', { urls: urls, current: current });
  239. },
  240. /**
  241. * 拨打电话
  242. * @param {String} phoneNumber 电话号码
  243. */
  244. makePhoneCall: function(phoneNumber) {
  245. this._post('makePhoneCall', { phoneNumber: phoneNumber });
  246. },
  247. /**
  248. * 设置剪贴板内容
  249. * @param {String} data 文本内容
  250. */
  251. setClipboardData: function(data) {
  252. this._post('setClipboardData', { data: data });
  253. },
  254. /**
  255. * 显示 Toast 提示
  256. * @param {String} title 提示内容
  257. * @param {String} icon 图标 (success/loading/none)
  258. * @param {Number} duration 持续时间(ms)
  259. */
  260. showToast: function(title, icon, duration) {
  261. this._post('showToast', {
  262. title: title,
  263. icon: icon || 'none',
  264. duration: duration || 1500
  265. });
  266. },
  267. /**
  268. * 显示 Loading 提示框
  269. * @param {String} title 提示内容
  270. */
  271. showLoading: function(title) {
  272. this._post('showLoading', { title: title || '加载中' });
  273. },
  274. /**
  275. * 隐藏 Loading 提示框
  276. */
  277. hideLoading: function() {
  278. this._post('hideLoading');
  279. },
  280. /**
  281. * 显示模态确认框
  282. * @param {Object} options { title, content, showCancel, confirmText, cancelText }
  283. * @param {Function} successCallback 点击确定/取消的回调 (仅在支持的双向通信环境有效)
  284. */
  285. showModal: function(options, successCallback) {
  286. // TODO: 这里的 callback 在单向 Bridge 中难以实现,通常需要 App 回调 JS 方法
  287. this._post('showModal', options);
  288. },
  289. // ------------------
  290. getToken: function () {
  291. this._post('getToken');
  292. },
  293. _tokenCallback: null,
  294. onToken: function(callback) {
  295. this._tokenCallback = callback;
  296. },
  297. receiveToken: function(token) {
  298. Logger.log('[Bridge] Received token:', token);
  299. if (this._tokenCallback) {
  300. this._tokenCallback(token);
  301. }
  302. }
  303. };
  304. window.Bridge = Bridge;
  305. })(window);