bobo-router.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. class Router {
  2. constructor(arg) {
  3. if (arg && arg.constructor !== Object) {
  4. return console.error(`Routing configuration must be an Object`)
  5. }
  6. Router.$root = this;
  7. }
  8. // 路由跳转方法映射
  9. routeMap = {
  10. push: 'navigateTo',
  11. replace: 'redirectTo',
  12. replaceAll: 'reLaunch',
  13. pushTab: 'switchTab'
  14. }
  15. /**
  16. * 执行路由跳转
  17. */
  18. _pushTo() {
  19. return new Promise((resolve, reject) => {
  20. let {
  21. page,
  22. params,
  23. method
  24. } = this.tempRoute
  25. // 对首次进入页面执行路由守卫时如果放行,method page params都为空 此时应该直接中断流程,无需抛出异常
  26. if (!method && !page && !params) {
  27. return
  28. }
  29. let urlParams = '?'
  30. if (!page) {
  31. reject(new Error('参数page未填写'))
  32. return
  33. } else if (params && typeof(params) === 'object') {
  34. // 处理参数,转换为url字符串
  35. Object.keys(params).forEach(k => {
  36. // 深度对象转为json字符串(包含数组)
  37. if (typeof(params[k]) === 'object') {
  38. if (params[k]) {
  39. const json = JSON.stringify(params[k])
  40. urlParams += `${k}=${json}&`
  41. } else {
  42. urlParams += `${k}=&`
  43. }
  44. } else if (typeof(params[k]) === 'number' || typeof(params[k]) === 'string' || typeof(params[k]) ===
  45. 'boolean') {
  46. // 基础值直接写入
  47. urlParams += `${k}=${params[k]}&`
  48. } else if (typeof(params[k]) === 'undefined') {
  49. urlParams += `${k}=&`
  50. }
  51. })
  52. }
  53. // 参数组装
  54. if (urlParams.length === 1) {
  55. urlParams = ''
  56. } else {
  57. urlParams = urlParams.substr(0, urlParams.length - 1)
  58. }
  59. // 设置路由跳转方式
  60. if (!method) {
  61. method = 'navigateTo'
  62. }
  63. if (this.routeMap[method]) {
  64. method = this.routeMap[method]
  65. }
  66. // 调用系统跳转方法
  67. uni[method]({
  68. url: page + urlParams,
  69. success: () => {
  70. // 执行路由后置守卫
  71. if (this._afterEach && typeof(this._afterEach) === 'function') {
  72. this._afterEach.call(this, this.tempRoute, this.route)
  73. }
  74. // 更新路由信息
  75. this.route = {
  76. path: page + urlParams,
  77. params: params || {},
  78. page
  79. }
  80. this.tempRoute = null
  81. resolve()
  82. },
  83. fail: (e) => {
  84. reject(new Error('路由跳转失败!'))
  85. }
  86. })
  87. })
  88. }
  89. /**动态的导航到一个新 URL 保留浏览历史
  90. * navigateTo
  91. * @param {Object} rule
  92. */
  93. push(arg) {
  94. const rule = {
  95. method: 'navigateTo'
  96. }
  97. if (typeof(arg) === 'string') {
  98. rule.page = arg
  99. } else if (typeof(arg) === 'object') {
  100. rule.page = arg.page
  101. rule.params = arg.params
  102. }
  103. this.next(rule)
  104. }
  105. /**动态的导航到一个新 URL 关闭当前页面,跳转到的某个页面。
  106. * redirectTo
  107. * @param {Object} rule
  108. */
  109. replace(arg) {
  110. const rule = {
  111. method: 'redirectTo'
  112. }
  113. if (typeof(arg) === 'string') {
  114. rule.page = arg
  115. } else if (typeof(arg) === 'object') {
  116. rule.page = arg.page
  117. rule.params = arg.params
  118. }
  119. this.next(rule)
  120. }
  121. /**动态的导航到一个新 URL 关闭所有页面,打开到应用内的某个页面
  122. * reLaunch
  123. * @param {Object} rule
  124. */
  125. replaceAll(arg) {
  126. const rule = {
  127. method: 'reLaunch'
  128. }
  129. if (typeof(arg) === 'string') {
  130. rule.page = arg
  131. } else if (typeof(arg) === 'object') {
  132. rule.page = arg.page
  133. rule.params = arg.params
  134. }
  135. this.next(rule)
  136. }
  137. /** 跳转Tabbar
  138. * switchTab
  139. * @param {Object} rule
  140. */
  141. pushTab(arg) {
  142. const rule = {
  143. method: 'switchTab'
  144. }
  145. if (typeof(arg) === 'string') {
  146. rule.page = arg
  147. } else if (typeof(arg) === 'object') {
  148. rule.page = arg.page
  149. rule.params = arg.params
  150. }
  151. this.next(rule)
  152. }
  153. /**
  154. * 返回到指定层级页面上
  155. */
  156. back(delta = 1) {
  157. // 返回上级
  158. if (delta.constructor != Number) {
  159. this._errorHandler(new Error('返回层级参数必须是一个Number类型且必须大于0:'))
  160. return
  161. }
  162. uni.navigateBack({
  163. delta
  164. })
  165. }
  166. /**
  167. * 分发路由
  168. * @param {Object} args
  169. */
  170. _next() {
  171. return new Promise((resolve, reject) => {
  172. if (this._beforeEach && typeof(this._beforeEach) === 'function') {
  173. // 需要传给守卫 to from next
  174. this._beforeEach.call(this, this.tempRoute, this.route, resolve)
  175. } else {
  176. this._pushTo().catch(e => {
  177. reject(e)
  178. })
  179. }
  180. })
  181. }
  182. next(args) {
  183. if (args) {
  184. // 保存临时数据
  185. if (typeof(args) === 'object') {
  186. this.tempRoute = {
  187. // 第一次调用next一定存在method,后续循环调用可能不会存在,不存在时使用上次缓存的method
  188. method: args.method || this.tempRoute.method,
  189. page: args.page,
  190. params: args.params
  191. }
  192. } else if (typeof(args) === 'string') {
  193. this.tempRoute = {
  194. page: args
  195. }
  196. } else if (!args) {
  197. // 中断路由 args = false
  198. this.tempRoute = null
  199. return
  200. }
  201. if (!this.route) {
  202. this.route = {
  203. page: '/' + getCurrentPages()[0].route
  204. }
  205. }
  206. this._next().then(args => {
  207. this.next(args)
  208. }).catch(e => {
  209. this.tempRoute = null
  210. this._errorHandler(e)
  211. })
  212. } else {
  213. this._pushTo().catch(e => {
  214. this.tempRoute = null
  215. this._errorHandler(e)
  216. })
  217. }
  218. }
  219. /**
  220. * 应用启动时执行一次路由检查(前置守卫,若通过则不做事情)
  221. */
  222. doBeforeHooks() {
  223. this.tempRoute = {}
  224. this.next({})
  225. }
  226. // 设置路由前置/后置守卫
  227. beforeEach(fn) {
  228. this._beforeEach = fn
  229. }
  230. afterEach(fn) {
  231. this._afterEach = fn
  232. }
  233. // 设置路由跳转错误处理
  234. onError(fn) {
  235. if (fn && typeof(fn) === 'function') {
  236. this._errorHandler = fn
  237. }
  238. }
  239. // 获取当前路由信息
  240. getCurrentRoute() {
  241. return this.route
  242. }
  243. }
  244. // 路由对象属性定义
  245. Router.$root = null
  246. // 当前路由内容
  247. Router.route = null
  248. // 临时路由信息
  249. Router.tempRoute = null
  250. // 路由前置后置守卫
  251. Router._beforeEach = null
  252. Router._afterEach = null
  253. // 路由跳转错误处理
  254. Router._errorHandler = function(e) {
  255. console.error(e)
  256. }
  257. Router.install = function(Vue) {
  258. Vue.mixin({
  259. onLaunch: function() {},
  260. onLoad: function(props) {
  261. // 首次进入页面时,缓存中不存在当前路由信息,需要初始化路由信息
  262. if (!Router.$root.getCurrentRoute()) {
  263. const rt = {
  264. params: {},
  265. page: '/' + getCurrentPages()[0].route
  266. }
  267. if (props) {
  268. Object.keys(props).forEach(k => {
  269. // url转的对象全部都是字符串,需要识别其中的对象和基本数据类型
  270. try {
  271. const obj = JSON.parse(props[k])
  272. if (typeof(obj) === 'string') {
  273. // 只有字符串还会是字符串,数字、布尔、数组均会转换为正常类型
  274. rt.params[k] = props[k]
  275. } else {
  276. rt.params[k] = obj
  277. }
  278. } catch (e) {
  279. rt.params[k] = props[k]
  280. }
  281. })
  282. }
  283. Router.$root.route = rt
  284. // 执行路由前置守卫
  285. Router.$root.doBeforeHooks()
  286. }
  287. // 自动获取页面标题(app端可能获取不到)
  288. const pages = getCurrentPages()
  289. let pageTitle = pages[pages.length - 1].pageTitle
  290. // #ifdef H5
  291. if (!pageTitle) {
  292. pageTitle = document.title
  293. }
  294. // #endif
  295. Router.$root.route.pageTitle = pageTitle
  296. },
  297. onShow() {
  298. if (!getCurrentPages().length) {
  299. return
  300. }
  301. // 获取当前路由信息
  302. const pages = getCurrentPages()
  303. const page = pages[pages.length - 1]
  304. let pageTitle = page.pageTitle
  305. const rt = {
  306. params: {},
  307. page: '/' + page.route,
  308. }
  309. if (!pageTitle) {
  310. // #ifdef H5
  311. rt.pageTitle = document.title
  312. // #endif
  313. } else {
  314. rt.pageTitle = pageTitle
  315. }
  316. const route = Router.$root.route
  317. // 若当前页面地址不等于缓存中地址,则更新缓存路由信息
  318. if (!route || route.page !== rt.page) {
  319. Router.$root.route = rt
  320. }
  321. }
  322. })
  323. Object.defineProperty(Vue.prototype, "$Router", {
  324. get: function() {
  325. return Router.$root
  326. }
  327. })
  328. Object.defineProperty(Vue.prototype, "$Route", {
  329. get: function() {
  330. return Router.$root.getCurrentRoute()
  331. }
  332. })
  333. }
  334. export default Router