Source: Router.js

  1. /***
  2. * Created by lzq on 2018/11/02
  3. * Router.js
  4. */
  5. import $ from "jquery";
  6. import React from 'react';
  7. import PropTypes from 'prop-types';
  8. import CtMobileFactory from "./CtMobile";
  9. import Constant from "./Constant";
  10. import {Consumer} from "./GlobalContext";
  11. /**
  12. * 初始化
  13. * @access private
  14. */
  15. function initial() {
  16. const self = this;
  17. onHashchange = onHashchange.bind(this);
  18. /***
  19. * 注册hashchange事件
  20. */
  21. window.addEventListener("hashchange", onHashchange, false);
  22. /***
  23. * 注册页面转场事件
  24. */
  25. $(window.document).on("pageBeforeChange", function (e, params) {
  26. self.setParameter(params);
  27. });
  28. }
  29. /**
  30. * hashchange的回调函数
  31. * @access private
  32. * @callback
  33. */
  34. function onHashchange() {
  35. // #page1_134567890232323?id=123_456
  36. const hash = window.location.hash;
  37. hashChange.call(this, hash);
  38. }
  39. /**
  40. * hashChange的处理
  41. * @access private
  42. * @param {string} hash - 哈希值
  43. * @param {Object} option - {
  44. * reload :[true | false]
  45. * }
  46. */
  47. function hashChange(hash, option) {
  48. const self = this;
  49. /***
  50. * 转场的id(没有重复)
  51. * @type {string}
  52. */
  53. let id = "";
  54. if (hash) {
  55. id = hash.indexOf("?") !== -1
  56. ? hash.substring(1, hash.indexOf("?"))
  57. : hash.substring(1);
  58. const index = id.lastIndexOf("_");
  59. /***
  60. * 用户自定义的锚点跳转
  61. */
  62. if (index === -1) {
  63. return false;
  64. } else {
  65. const pageId = id.substring(0, index);
  66. /***
  67. * 用户自定义的锚点跳转
  68. */
  69. if (!window.document.querySelector("[data-ct-data-role='page'],#" + pageId)) {
  70. return false;
  71. }
  72. self.ctmobile.fireEvent(window.document, "pageBeforeChange", [CtMobileFactory.getUrlParam(hash)]);
  73. }
  74. }
  75. /***
  76. * 首页
  77. */
  78. else {
  79. self.ctmobile.fireEvent(window.document, "pageBeforeChange", [CtMobileFactory.getUrlParam(hash)]);
  80. if (/*如果栈顶元素的pageId == 模板中第一页的id*/
  81. self.ctmobile.getFirstPage().getPageId() === self.ctmobile.getFirstPageId()) {
  82. // 说明是从模板的第一页进入主应用的
  83. id = self.ctmobile.getFirstPage().getId();
  84. }
  85. else {
  86. // 不是从模板的第一页进入主应用的
  87. self.ctmobile.createPage(self.ctmobile.getId(self.ctmobile.getFirstPageId()), (Component) => {
  88. Component.start(Constant._SLIDEDURATION, () => {
  89. self.removeFirstPage();
  90. });
  91. });
  92. return;
  93. }
  94. }
  95. //hash ? (hash.indexOf("?") != -1
  96. // ? id = hash.substring(1, hash.indexOf("?"))
  97. // : id = hash.substring(1))
  98. // : id = root.getFirstPage().getId();
  99. /***
  100. * 新的页面
  101. */
  102. const curPage = self.getPageById(id);
  103. if (!curPage) {
  104. self.ctmobile.createPage(id, (Component) => {
  105. Component.start(Constant._SLIDEDURATION, function () {
  106. // 如果不改变浏览器历史且历史栈长度大于1
  107. if (option && option.reload && self.getHistoryLength() > 1) {
  108. const preHistoryIndex = self.getHistoryLength() - 2;
  109. const preHistoryPage = self.getPageByIndex(preHistoryIndex);
  110. if (preHistoryPage) {
  111. preHistoryPage.finish(0, null, option);
  112. }
  113. }
  114. });
  115. });
  116. }
  117. /***
  118. * 回退
  119. */
  120. else {
  121. // 如果刷新的是栈顶的页面
  122. if (id === self.getLastPage().getId()) {
  123. return false;
  124. } else {
  125. // 依次出栈
  126. const index = self.ctmobile.indexOfById(id);
  127. for (let i = self.getHistoryLength() - 1; i > index; i--) {
  128. if (i === index + 1) {
  129. self.getPageByIndex(i).finish(Constant._SLIDEDURATION);
  130. } else {
  131. self.getPageByIndex(i).finish(0);
  132. }
  133. }
  134. }
  135. }
  136. }
  137. /**
  138. * history中是否有pageId开头的page对象
  139. * @access private
  140. * @param {string} pageId
  141. * @return {number}
  142. */
  143. function indexOfHistoryByPageId(pageId) {
  144. if (pageId.indexOf("?") !== -1) {
  145. pageId = pageId.substring(0, pageId.indexOf("?"));
  146. }
  147. let index = -1;
  148. for (let i = 0, len = this.getHistoryLength(); i < len; i++) {
  149. if (this.getPageByIndex(i).getPageId() === pageId) {
  150. index = i;
  151. break;
  152. }
  153. }
  154. return index;
  155. }
  156. /**
  157. * Link
  158. * @class Link
  159. * @classdesc 用来管理跳转的链接
  160. */
  161. class Link extends React.Component {
  162. render() {
  163. const {className = '', style = {}} = this.props;
  164. return (
  165. <Consumer>
  166. {(ctmobile) => {
  167. return (
  168. <a
  169. className={className}
  170. style={style}
  171. onClick={() => {
  172. const {pageId = '', parameter = '', reload = ctmobile.config.linkCaptureReload} = this.props;
  173. const href = `${"#" + pageId}?pageId=${pageId}${parameter}`;
  174. ctmobile.router.startPage(href, {
  175. reload,
  176. });
  177. }}>{this.props.children}</a>
  178. );
  179. }}
  180. </Consumer>
  181. );
  182. }
  183. }
  184. /**
  185. * CheckLinkComponentProps
  186. * @param {string} className - className
  187. * @param {Object} style - style
  188. * @param {string} pageId - pageId
  189. * @param {string} parameter - 页面参数
  190. * @param {boolean} reload - 是否替换历史
  191. */
  192. Link.propTypes = {
  193. className: PropTypes.string,
  194. style: PropTypes.object,
  195. pageId: PropTypes.string,
  196. parameter: PropTypes.string,
  197. reload: PropTypes.bool
  198. };
  199. /**
  200. * Back
  201. * @class Back
  202. * @classdesc 用来管理返回的操作
  203. */
  204. class Back extends React.Component {
  205. render() {
  206. const {className = '', style = {}} = this.props;
  207. return (
  208. <Consumer>
  209. {(ctmobile) => {
  210. return (
  211. <a
  212. className={className}
  213. style={style}
  214. onClick={() => {
  215. ctmobile.router.go(-1);
  216. }}>{this.props.children}</a>
  217. );
  218. }}
  219. </Consumer>
  220. );
  221. }
  222. }
  223. /**
  224. * CheckBackComponentProps
  225. * @param {string} className - className
  226. * @param {Object} style - style
  227. */
  228. Back.propTypes = {
  229. className: PropTypes.string,
  230. style: PropTypes.object,
  231. };
  232. export {Link, Back};
  233. /**
  234. * Router
  235. * @class Router
  236. * @classdesc 管理所有路由相关操作
  237. */
  238. class Router {
  239. /**
  240. * @constructor
  241. * @param {CtMobile} ctmobile
  242. */
  243. constructor(ctmobile) {
  244. Object.assign(this, {
  245. ctmobile,
  246. parameter: null,
  247. history: [],
  248. });
  249. initial.call(this);
  250. }
  251. /**
  252. * 页面跳转
  253. * @param {string} href (pageId = pageId + params) 如: page1?a=1&b=2;
  254. * @param {object} option {
  255. * reload : [true | false]
  256. * }
  257. */
  258. startPage(href, option) {
  259. const self = this;
  260. let pageId = "", paramsQuery = "";
  261. // Ajax加载
  262. if (href.indexOf("#") !== 0) {
  263. let params = CtMobileFactory.getUrlParam(href);
  264. pageId = params.pageId;
  265. delete params.pageId;
  266. let paramsQueryArr = [];
  267. for (let p in params) {
  268. if (params.hasOwnProperty(p)) {
  269. paramsQueryArr.push(p + "=" + params[p]);
  270. }
  271. }
  272. paramsQuery = paramsQueryArr.join("&") ? ("?" + paramsQueryArr.join("&")) : "";
  273. dispatcher();
  274. }
  275. // 本地锚点加载
  276. else {
  277. href = href.substring(1);
  278. if (href.indexOf("?") !== -1) {
  279. paramsQuery = href.substring(href.indexOf("?"));
  280. pageId = href.substring(0, href.indexOf("?"));
  281. } else {
  282. pageId = href;
  283. }
  284. dispatcher();
  285. }
  286. /***
  287. * 分派
  288. */
  289. function dispatcher() {
  290. /***
  291. * 获取page的模式
  292. */
  293. const mode = self.ctmobile.getPageConfigAttribute(pageId, 'mode');
  294. let hash;
  295. //if(/*单例*/mode === "single" || /*完全单例*/mode === "singleInstance" || /*带返回值的完全单例*/mode === "singleInstanceResult") {
  296. if (mode.toLowerCase().indexOf("single") !== -1) {
  297. const index = indexOfHistoryByPageId.call(self, pageId);
  298. if (/*_history中没有以pageId开头的page*/index === -1) {
  299. //1.用pageId生成真实的id转换锚点
  300. //if("singleInstance" === mode || "singleInstanceResult" === mode) {
  301. if (mode/*.toLowerCase()*/.indexOf("singleInstance") !== -1) {
  302. if (self.ctmobile.getSingleInstance(pageId)) {
  303. hash = self.ctmobile.getSingleInstance(pageId).getId();
  304. } else {
  305. hash = self.ctmobile.getId(pageId + paramsQuery);
  306. }
  307. } else {
  308. hash = self.ctmobile.getId(pageId + paramsQuery);
  309. }
  310. if (option && option.reload) {
  311. window.history.replaceState(null, "", "#" + hash);
  312. hashChange.call(self, "#" + hash, option);
  313. } else {
  314. window.location.hash = "#" + hash;
  315. }
  316. } else {
  317. if (/*如果page不是栈顶,依次出栈,调用finish*/
  318. self.ctmobile.getPageByIndex(index) !== self.getLastPage()
  319. ) {
  320. // 假如现在_history中的顺序是1,2,3,4,5,6,3是单例,现在要跳转到3,那么6,5,4所代表的page依次调用finish方法
  321. // history.go(找到3相对于当前页的阈值)
  322. self.go(-(self.getHistoryLength() - 1 - index));
  323. } else {
  324. self.ctmobile.fireEvent(self.getLastPage().getPageDOM(), "pageShow");
  325. }
  326. }
  327. } else if (/*多例*/mode === "standard" || mode === "result") {
  328. //1.用pageId生成真实的id转换锚点
  329. hash = self.ctmobile.getId(pageId + paramsQuery);
  330. if (option && option.reload) {
  331. window.history.replaceState(null, "", "#" + hash);
  332. hashChange.call(self, "#" + hash, option);
  333. } else {
  334. window.location.hash = "#" + hash;
  335. }
  336. }
  337. }
  338. }
  339. /**
  340. * 跳转到指定的历史
  341. * @param {number} index - 历史位置
  342. * 注释:当前的位置为0 index负值为回退,index正数为前进 都以1开始
  343. * 例如 -1 为当前页之前的页面,1为当前页之后的页面,0为刷新当前页面
  344. */
  345. go(index) {
  346. window.history.go(index);
  347. }
  348. /**
  349. * 返回
  350. */
  351. back() {
  352. this.go(-1);
  353. }
  354. /**
  355. * 设置转场参数
  356. * @param {Object} parameter
  357. */
  358. setParameter(parameter) {
  359. this.parameter = parameter;
  360. }
  361. /**
  362. * 获取转场的参数
  363. * @return {Object}
  364. */
  365. getParameter() {
  366. let parameter = Object.assign({}, this.parameter);
  367. if(parameter.pageId) {
  368. delete parameter.pageId;
  369. }
  370. return parameter;
  371. }
  372. /**
  373. * 根据ID获取page对象
  374. * @param {string} id
  375. * @return {*}
  376. */
  377. getPageById(id) {
  378. return this.history[this.ctmobile.indexOfById(id)];
  379. }
  380. /**
  381. * 根据索引获取page对象
  382. * @param {number} index
  383. * @returns {*}
  384. */
  385. getPageByIndex(index) {
  386. return this.history[index];
  387. }
  388. /**
  389. * 获取历史记录中的栈顶的元素
  390. * @returns {*}
  391. */
  392. getLastPage() {
  393. return this.history[this.history.length - 1];
  394. }
  395. /**
  396. * 获取历史栈长度
  397. * @return {number}
  398. */
  399. getHistoryLength() {
  400. return this.history.length;
  401. }
  402. /**
  403. * 添加页面的历史栈
  404. * @param {Page} page
  405. */
  406. addPage(page) {
  407. this.history.push(page);
  408. }
  409. /**
  410. * 删除历史栈中的第一个page
  411. */
  412. removeFirstPage() {
  413. this.history.shift();
  414. }
  415. /**
  416. * 删除历史栈中的最后一个页面
  417. */
  418. removeLastPage() {
  419. this.history.pop();
  420. }
  421. /**
  422. * 删除历史栈中指定的页面
  423. * @param {number} index
  424. */
  425. removePageByIndex(index) {
  426. this.history.splice(index, 1);
  427. }
  428. }
  429. export default Router;