From cda723dc36ae4d497e6140e39c313ccc544e5181 Mon Sep 17 00:00:00 2001 From: bhuh12 Date: Mon, 24 Jun 2019 01:25:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(nest):=20=E9=A1=B5=E7=AD=BE=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=B5=8C=E5=A5=97=E8=B7=AF=E7=94=B1=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #7 --- demo/assets/scss/demo.scss | 13 +++ demo/components/AppAside.vue | 20 ++-- demo/router.js | 144 ------------------------- demo/router/index.js | 100 +++++++++++++++++ demo/router/page.js | 76 +++++++++++++ demo/utils.js | 3 + demo/views/Nest.vue | 73 +++++++++++++ demo/views/Page1.vue | 5 + demo/views/Page2.vue | 5 + demo/views/Rule.vue | 33 ++---- docs/api/README.md | 5 +- docs/guide/essentials/rule.md | 12 +-- package.json | 5 +- src/components/RouterAlive.js | 31 +++--- src/components/RouterTab/RouterTab.vue | 2 +- src/components/RouterTab/iframe.js | 6 +- src/components/RouterTab/index.js | 49 ++++----- src/components/RouterTab/matched.js | 73 +++++++++++++ src/components/RouterTab/pageLeave.js | 7 +- src/components/RouterTab/rule.js | 20 ++-- src/components/RouterTab/scroll.js | 4 + src/index.js | 2 + src/util/route.js | 21 ---- src/util/warn.js | 2 +- 24 files changed, 443 insertions(+), 268 deletions(-) delete mode 100644 demo/router.js create mode 100644 demo/router/index.js create mode 100644 demo/router/page.js create mode 100644 demo/utils.js create mode 100644 demo/views/Nest.vue create mode 100644 demo/views/Page1.vue create mode 100644 demo/views/Page2.vue create mode 100644 src/components/RouterTab/matched.js delete mode 100644 src/util/route.js diff --git a/demo/assets/scss/demo.scss b/demo/assets/scss/demo.scss index 1ff94636..eb631381 100644 --- a/demo/assets/scss/demo.scss +++ b/demo/assets/scss/demo.scss @@ -89,6 +89,19 @@ a:link { color: $activeColor; border-color: $activeColor; } + + &.link { + &:hover { + color: $color-primary; + border-color: $color-primary; + } + + &.router-link-exact-active { + color: #fff; + background-color: $color-primary; + border-color: rgba(0,0,0,.05); + } + } &.primary { color: #fff; diff --git a/demo/components/AppAside.vue b/demo/components/AppAside.vue index 5e3a5875..ff514851 100644 --- a/demo/components/AppAside.vue +++ b/demo/components/AppAside.vue @@ -28,16 +28,15 @@ export default { { text: '默认配置', to: '/default/' }, { text: '过渡效果', to: '/transition/' }, { text: '初始展示页签', to: '/initial-tabs/' }, - { - text: '多语言支持', - children: [ - { text: '页签国际化', to: '/i18n/' }, - { text: '组件语言', to: '/lang-en/' }, - { text: '组件自定义语言', to: '/lang-custom' } - ] - }, { text: '自定义页签模板', to: '/slot/' } ] + }, { + text: '多语言支持', + children: [ + { text: '页签国际化', to: '/i18n/' }, + { text: '组件语言', to: '/lang-en/' }, + { text: '组件自定义语言', to: '/lang-custom' } + ] }, { text: '页签规则', children: [ @@ -46,10 +45,11 @@ export default { { text: '路由页签规则', to: '/default/route-rule/a/1' } ] }, { - text: '页面配置', + text: '页面功能', children: [ { text: '动态更新页签配置', to: '/default/tab-dynamic' }, - { text: '页面离开确认', to: '/initial-tabs/page-leave' } + { text: '页面离开确认', to: '/initial-tabs/page-leave' }, + { text: '嵌套路由', to: '/default/nest/1/page1' } ] }] } diff --git a/demo/router.js b/demo/router.js deleted file mode 100644 index d1ffd18a..00000000 --- a/demo/router.js +++ /dev/null @@ -1,144 +0,0 @@ -import Vue from 'vue' -import Router from 'vue-router' -import { RouterTabRoutes } from '../src' - -const importPage = view => () => import(/* webpackChunkName: "p-[request]" */ `./views/${view}.vue`) - -const importLayout = view => () => import(/* webpackChunkName: "ly-[request]" */ `./components/layout/${view}.vue`) - -Vue.use(Router) - -// 404 路由 -const route404 = { - path: '404', - component: importPage('404'), - meta: { - title: '找不到页面', - icon: 'rt-icon-warning' - } -} - -// 页面路由 -const pageRoutes = [{ - path: 'page/:id', - component: importPage('Page'), - meta: { - title: '页面', - icon: 'rt-icon-doc' - } -}, { - path: 'rule/:catalog/:type', - component: importPage('Rule'), - meta: { - title: '默认规则', - icon: 'rt-icon-log' - } -}, { - path: 'route-rule/:catalog/:type', - component: importPage('Rule'), - meta: { - title: '路由规则', - icon: 'rt-icon-log', - aliveId (route) { - return `route-rule/${route.params.catalog}` - } - } -}, { - path: 'tab-dynamic', - component: importPage('TabDynamic'), - meta: { - title: '动态更新页签', - icon: 'rt-icon-log' - } -}, { - path: 'page-leave', - component: importPage('PageLeave'), - meta: { - title: '页面离开确认', - icon: 'rt-icon-contact' - } -}, route404, ...RouterTabRoutes] - -// Vue Router 实例 -const $router = new Router({ - routes: [{ - path: '/', - redirect: '/default/page/1' - }, { - path: '/default/', - component: importLayout('Default'), - redirect: '/default/page/1', - children: pageRoutes - }, { - path: '/transition/', - component: importLayout('Transition'), - redirect: '/transition/page/1', - children: pageRoutes - }, { - path: '/initial-tabs/', - component: importLayout('InitialTabs'), - redirect: '/initial-tabs/page/1', - children: pageRoutes - }, { - path: '/i18n/', - component: importLayout('I18n'), - redirect: '/i18n/lang', - children: [{ - path: 'lang', - component: importPage('I18n'), - meta: { - title: 'i18n:i18n', - icon: 'rt-icon-doc' - } - }, { - path: 'page/:id', - component: importPage('Page'), - meta: { - title: 'i18n:page', - icon: 'rt-icon-doc' - } - }, route404, ...RouterTabRoutes] - }, { - path: '/lang-en/', - component: importLayout('LangEn'), - redirect: '/lang-en/page/1', - children: pageRoutes - }, { - path: '/lang-custom/', - component: importLayout('LangCustom'), - redirect: '/lang-custom/page/1', - children: pageRoutes - }, { - path: '/slot/', - component: importLayout('Slot'), - redirect: '/slot/page/1', - children: pageRoutes - }, { - path: '/global-rule/', - component: importLayout('GlobalRule'), - redirect: '/global-rule/rule/a/1', - children: pageRoutes - }, Object.assign({}, route404, { - path: '/404' - }), { - path: '*', - redirect (to) { - const match = /^(\/[^/]+\/)/.exec(to.path) - - if (match) { - const base = match[1] - const matchParent = $router.options.routes.find(item => item.path === base) - - // 跳转子路由 404 - if (matchParent) { - return base + '404' - } - } - - // 全局 404 - return '/404' - } - }] -}) - -export default $router diff --git a/demo/router/index.js b/demo/router/index.js new file mode 100644 index 00000000..cd116892 --- /dev/null +++ b/demo/router/index.js @@ -0,0 +1,100 @@ +import Vue from 'vue' +import Router from 'vue-router' +import { RouterTabRoutes } from '../../src' +import pageRoutes, { route404 } from './page' +import { importPage, importLayout } from '../utils' + +Vue.use(Router) + +// 根据 layout 组件名生成标准路由 +function getStandardRoutes (comps) { + return comps.map(comp => { + // 路径由 'CompName' 转换为 'comp-name' + const path = '/' + comp.replace(/([A-Z])/g, function (val, $1, index, str) { + let lower = $1.toLowerCase() + return index ? '-' + lower : lower + }) + '/' + + return { + path, + component: importLayout(comp), + redirect: path + 'page/1', + children: pageRoutes + } + }) +} + +// 标准路由 +const standardRoutes = getStandardRoutes([ + 'Default', + 'Transition', + 'InitialTabs', + 'LangEn', + 'LangCustom', + 'Slot' +]) + +// Vue Router 实例 +const $router = new Router({ + routes: [{ + path: '/', + redirect: '/default/page/1' + }, + ...standardRoutes, + { + path: '/default/:did/dd/:iid', + component: importLayout('Default'), + redirect: '/default/2/dd/gg/nest/67/page/1', + children: pageRoutes + }, { + path: '/i18n/', + component: importLayout('I18n'), + redirect: '/i18n/lang', + children: [{ + path: 'lang', + component: importPage('I18n'), + meta: { + title: 'i18n:i18n', + icon: 'rt-icon-doc' + } + }, { + path: 'page/:id', + component: importPage('Page'), + meta: { + title: 'i18n:page', + icon: 'rt-icon-doc' + } + }, route404, ...RouterTabRoutes] + }, { + path: '/global-rule/', + component: importLayout('GlobalRule'), + redirect: '/global-rule/rule/a/1', + children: pageRoutes + }, + + // 根路由 404 + Object.assign({}, route404, { + path: '/404' + }), + + // 未匹配的路由 404 + { + path: '*', + redirect (to) { + const match = /^(\/[^/]+\/)/.exec(to.path) + + if (match) { + const base = match[1] + const matchParent = $router.options.routes.find(item => item.path === base) + + // 子路由 404 + if (matchParent) return base + '404' + } + + // 根路由 404 + return '/404' + } + }] +}) + +export default $router diff --git a/demo/router/page.js b/demo/router/page.js new file mode 100644 index 00000000..92e68c4d --- /dev/null +++ b/demo/router/page.js @@ -0,0 +1,76 @@ +import { RouterTabRoutes } from '../../src' +import { importPage } from '../utils' + +// 404 路由 +export const route404 = { + path: '404', + component: importPage('404'), + meta: { + title: '找不到页面', + icon: 'rt-icon-warning' + } +} + +// 页面路由 +const pageRoutes = [{ + path: 'page/:id', + component: importPage('Page'), + meta: { + title: '页面', + icon: 'rt-icon-doc' + } +}, { + path: 'rule/:catalog/:type', + component: importPage('Rule'), + meta: { + title: '默认规则', + icon: 'rt-icon-log' + } +}, { + path: 'route-rule/:catalog/:type', + component: importPage('Rule'), + meta: { + title: '路由规则', + icon: 'rt-icon-log', + aliveId (route, pagePath) { + return `route-rule/${route.params.catalog}` + } + } +}, { + path: 'tab-dynamic', + component: importPage('TabDynamic'), + meta: { + title: '动态更新页签', + icon: 'rt-icon-log' + } +}, { + path: 'page-leave', + component: importPage('PageLeave'), + meta: { + title: '页面离开确认', + icon: 'rt-icon-contact' + } +}, route404, ...RouterTabRoutes] + +// 嵌套路由 +const nestRoute = { + path: 'nest/:nestId/', + component: importPage('Nest'), + meta: { + title: '嵌套路由', + icon: 'rt-icon-doc' + }, + children: [{ + path: 'page1', + component: importPage('Page1') + }, { + path: 'page2', + component: importPage('Page2') + }] +} + +// 插入嵌套路由 +pageRoutes.unshift(nestRoute) + +// 页面路由 +export default pageRoutes diff --git a/demo/utils.js b/demo/utils.js new file mode 100644 index 00000000..5fdcc6d6 --- /dev/null +++ b/demo/utils.js @@ -0,0 +1,3 @@ +export const importPage = view => () => import(/* webpackChunkName: "p-[request]" */ `./views/${view}.vue`) + +export const importLayout = view => () => import(/* webpackChunkName: "ly-[request]" */ `./components/layout/${view}.vue`) diff --git a/demo/views/Nest.vue b/demo/views/Nest.vue new file mode 100644 index 00000000..3c3ea3e9 --- /dev/null +++ b/demo/views/Nest.vue @@ -0,0 +1,73 @@ + + + diff --git a/demo/views/Page1.vue b/demo/views/Page1.vue new file mode 100644 index 00000000..0c5d4132 --- /dev/null +++ b/demo/views/Page1.vue @@ -0,0 +1,5 @@ + diff --git a/demo/views/Page2.vue b/demo/views/Page2.vue new file mode 100644 index 00000000..867ed689 --- /dev/null +++ b/demo/views/Page2.vue @@ -0,0 +1,5 @@ + diff --git a/demo/views/Rule.vue b/demo/views/Rule.vue index ea53c1e6..89a58846 100644 --- a/demo/views/Rule.vue +++ b/demo/views/Rule.vue @@ -31,21 +31,15 @@ {{ cat }}/{{ t }} - + {{ cat }}/1?q=abc - + {{ cat }}/1?q=def @@ -81,20 +75,20 @@ export default { default: { label: '默认', type: '内置规则:"path"', - fn: 'route => route.path', - desc: '相同route.params的路由共用页签' + fn: '(route, pagePath) => pagePath || route.path', + desc: '相同 route.params 的路由共用页签,嵌套路由页签根据 pagePath' }, global: { label: '全局', type: '内置规则:"fullPath"', - fn: 'route => route.fullPath.replace(route.hash, \'\')', - desc: '相同route.params和route.query的路由共用页签' + fn: '(route, pagePath) => pagePath || route.fullPath.replace(route.hash, \'\')', + desc: '相同 route.params 和 route.query 的路由共用页签,嵌套路由页签根据 pagePath' }, route: { label: '路由', type: '自定义规则', fn: 'route => \'route-rule/\' + route.params.catalog', - desc: '相同catalog参数的路由共用页签' + desc: '相同 catalog 参数的路由共用页签' } } @@ -128,17 +122,6 @@ export default { .demo-btn { margin-right: .5em; - - &:hover { - color: $color-primary; - border-color: $color-primary; - } - - &.router-link-exact-active { - color: #fff; - background-color: $color-primary; - border-color: rgba(0,0,0,.05); - } } } } diff --git a/docs/api/README.md b/docs/api/README.md index ccd29e09..9ee879f0 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -14,7 +14,10 @@ sidebar: auto - 如果类型为 `String` ,则可使用内置的缓存规则,`path` (默认) 和 `fullPath` - - 如果类型为 `Function` ,则取 `aliveId(route)` 返回的字符串。该函数传入相同的 `route` 应返回固定的字符串,以免页签无法与缓存的页面对应 + - 如果类型为 `Function` ,则取 `aliveId(route, pagePath)` 返回的字符串。 + - `route` 为页面路由对象。 + - `pagePath` 当页面存在嵌套路由时生效,为匹配页面所在路由链的路径 + - 该函数传入相同的 `route` 应返回固定的字符串,以免页签无法与缓存的页面对应 - 默认值: `'path'` diff --git a/docs/guide/essentials/rule.md b/docs/guide/essentials/rule.md index afadbf7a..026bd18f 100644 --- a/docs/guide/essentials/rule.md +++ b/docs/guide/essentials/rule.md @@ -6,13 +6,13 @@ ## 内置规则 - `path` (默认规则) - - 规则:`route => route.path` - - 说明:相同route.params的路由共用页签 + - 规则:`(route, pagePath) => pagePath || route.path` + - 说明:相同 route.params 的路由共用页签,嵌套路由页签根据 pagePath - - `fullPath` - - 规则:`route => route.fullPath.replace(route.hash, '')` - - 说明:相同route.params和route.query的路由共用页签 + - 规则:`(route, pagePath) => pagePath || route.fullPath.replace(route.hash, '')` + - 说明:相同 route.params 和 route.query 的路由共用页签,嵌套路由页签根据 pagePath - @@ -25,7 +25,7 @@ **示例:** ``` html - + ``` 例子中,配置 `alive-id` 为 `fullPath` 去除 `hash` 部分。 @@ -55,7 +55,7 @@ const route = { }, meta: { title: '定制规则', - aliveId (route) { + aliveId (route, pagePath) { return `/my-page/${route.params.catalog}` } } diff --git a/package.json b/package.json index 186b614a..6e3e6dd9 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,10 @@ "@vue/standard" ], "rules": { - "vue/require-default-prop": false + "vue/require-default-prop": false, + "vue/max-attributes-per-line": ["error", { + "singleline": 3 + }] }, "parserOptions": { "parser": "babel-eslint" diff --git a/src/components/RouterAlive.js b/src/components/RouterAlive.js index 4897b299..ffd2e631 100644 --- a/src/components/RouterAlive.js +++ b/src/components/RouterAlive.js @@ -1,6 +1,5 @@ import { emptyObj } from '../util' import { getFirstComponentChild } from '../util/dom' -import { isAlikeRoute, isSameComponentRoute } from '../util/route' import rule from './RouterTab/rule' @@ -8,7 +7,7 @@ export default { name: 'RouterAlive', mixins: [ rule ], - beforeCreate () { + created () { Object.assign(this, { cache: Object.create(null), lastRoute: this.$route @@ -33,41 +32,43 @@ export default { const { vm: cacheVm, route: cacheRoute } = cacheItem || emptyObj // 是否需要重载路由强制刷新页面组件 - let needReloadRouter = false + let needReloadView = false // 路由是否改变 let isRouteChange = lastRoute !== $route // 是否跟上次路由共用组件 - let isSameComponent = isRouteChange && isSameComponentRoute($route, lastRoute) + let isShareComp = isRouteChange && + !this.isAlikeRoute($route, lastRoute) && + this.getPageComp($route) === this.getPageComp(lastRoute) if (isRouteChange) { // 更新上次路由 this.lastRoute = $route // 添加缓存 - if (!cacheItem) this.set(key, { route: $route }) + this.set(key, { route: $route }) } if (cacheVm) { - // 缓存组件的路由地址除hash外一致则取缓存的组件 - if (isAlikeRoute(cacheRoute, $route)) { + // 缓存组件的路由地址匹配则取缓存的组件 + if (this.isAlikeRoute(cacheRoute, $route)) { pageNode.componentInstance = cacheVm } else { // 缓存组件路由地址不匹配则销毁缓存并重载路由 cacheVm.$destroy() cacheItem.vm = null - needReloadRouter = true + needReloadView = true } } - // 路由改变后但组件相同需重载路由 - if (isSameComponent) needReloadRouter = true + // 共用组件的路由切换需重载路由 + if (isShareComp) needReloadView = true // 重载路由以强制更新页面 - needReloadRouter && this.$routerTab.reloadRouter() + needReloadView && this.$routerTab.reloadView() - // 标记为keepAlive和routerAlive + // 标记为 keepAlive和 routerAlive pageNode.data.keepAlive = true pageNode.data.routerAlive = this } @@ -80,6 +81,11 @@ export default { // 设置缓存项 set (key, item) { const { cache } = this + const origin = cache[key] + + if (origin) { + item = Object.assign(origin, item) + } this.$emit('update', key, item) @@ -95,6 +101,7 @@ export default { // 销毁组件实例 if (item) { item.vm && item.vm.$destroy() + item.vm = null delete cache[key] } diff --git a/src/components/RouterTab/RouterTab.vue b/src/components/RouterTab/RouterTab.vue index c948e596..da0da969 100644 --- a/src/components/RouterTab/RouterTab.vue +++ b/src/components/RouterTab/RouterTab.vue @@ -74,7 +74,7 @@ @after-leave="onPageTransitionEnd" > to.split('#')[0] === matchPath) if (matchTab) { return matchTab.id } } else { - return this.getAliveId($route) + return this.getAliveId(route) } }, // 从 route 中获取 tab 数据 - getRouteTab (route) { + getRouteTab (route, matchRoutes = this.matchRoutes(route)) { let id = this.getAliveId(route) - let { fullPath: to, meta } = route - let { title, icon, tips } = meta + let { title, icon, tips } = matchRoutes.pageRoute.meta - return { id, to, title, icon, tips } + return { id, to: route.fullPath, title, icon, tips } }, // 移除 tab 项 @@ -201,7 +190,7 @@ export default { $router.replace(nextTab.to) } } catch (e) { - console.warn(e) + warn(false, e) } }, @@ -222,7 +211,7 @@ export default { try { await this.pageLeavePromise(id, 'refresh') this.$refs.routerAlive.clear(id) - if (id === this.activedTab) this.reloadRouter() + if (id === this.activedTab) this.reloadView() } catch (e) {} }, @@ -243,18 +232,18 @@ export default { $alive.clear(id) } } - this.reloadRouter() + this.reloadView() }, - // 重载路由组件 - async reloadRouter (ignoreTransition = false) { - this.isRouterAlive = false + // 重载路由视图 + async reloadView (ignoreTransition = false) { + this.isViewAlive = false - // 默认在页面过渡结束后会设置 isRouterAlive 为 true + // 默认在页面过渡结束后会设置 isViewAlive 为 true // 如果过渡事件失效,则需传入 ignoreTransition 为 true 手动更改 if (ignoreTransition) { await this.$nextTick() - this.isRouterAlive = true + this.isViewAlive = true } }, @@ -265,13 +254,13 @@ export default { // 页面过渡结束 onPageTransitionEnd () { - if (!this.isRouterAlive) this.isRouterAlive = true + if (!this.isViewAlive) this.isViewAlive = true }, // 修复:当快速频繁切换页签时,旧页面离开过渡效果尚未完成,新页面内容无法正常 mount,内容节点为 comment 类型 fixCommentPage () { if (this.$refs.routerAlive.$el.nodeType === 8) { - this.reloadRouter(true) + this.reloadView(true) } } } diff --git a/src/components/RouterTab/matched.js b/src/components/RouterTab/matched.js new file mode 100644 index 00000000..a1b01b13 --- /dev/null +++ b/src/components/RouterTab/matched.js @@ -0,0 +1,73 @@ +import { warn } from '../../util/warn' + +// 匹配路由链 +export default { + methods: { + // 匹配路由 + matchRoutes (route = this.$route) { + const { matched } = route + + // 页面所在路由 index + let pageRouteIdx = matched.findIndex(({ instances }) => + !instances.default || // instances 为空 + Object.values(instances).find(vm => vm && vm._isRouterPage) // mounted 时 instances 会包含路由页面实例 + ) + + warn(pageRouteIdx > -1, '未能匹配到路由信息') + + return { + baseRoute: matched[pageRouteIdx - 1], + pageRoute: matched[pageRouteIdx], + pageRouteIdx + } + }, + + // 解析匹配的路径 + parsePath (path, params) { + for (const key in params) { + path = path.replace(':' + key, params[key]) + } + + return path + }, + + // 获取跟路径 + getBasePath () { + let { path } = this.matchRoutes().baseRoute + let { params } = this.$route + + return this.parsePath(path, params) + }, + + // 获取嵌套路由的页面路径 + getPagePath (route = this.$route, matchRoutes = this.matchRoutes(route)) { + let { pageRoute, pageRouteIdx } = matchRoutes + + // 页面嵌套路由 + if (pageRouteIdx !== route.matched.length - 1) { + return this.parsePath(pageRoute.path, route.params) + } + }, + + // 获取嵌套路由的页面组件 + getPageComp (route = this.$route) { + return this.matchRoutes(route).pageRoute.components.default + }, + + // 获取路由不带hash的路径 + getPathWithoutHash (route) { + return route.hash + ? route.fullPath.replace(route.hash, '') + : route.fullPath + }, + + // 是否相似路由 + isAlikeRoute (route1, route2) { + const route1Path = this.getPagePath(route1) + const route2Path = this.getPagePath(route2) + + return this.getPathWithoutHash(route1) === this.getPathWithoutHash(route2) || + (route1Path && route2Path && route1Path === route2Path) + } + } +} diff --git a/src/components/RouterTab/pageLeave.js b/src/components/RouterTab/pageLeave.js index 1f219e49..78b81325 100644 --- a/src/components/RouterTab/pageLeave.js +++ b/src/components/RouterTab/pageLeave.js @@ -1,5 +1,4 @@ import { emptyObj } from '../../util' -import { isAlikeRoute } from '../../util/route' // 页面离开 export default { @@ -14,7 +13,7 @@ export default { let hooks = this.$router.beforeHooks let idx = hooks.indexOf(this.routerPageLeaveGuard) - // 移除已销毁的RouterTab实例注册的导航守卫 + // 移除已销毁的 RouterTab 实例注册的导航守卫 if (idx > -1) hooks.splice(idx, 1) next() @@ -23,8 +22,8 @@ export default { const $alive = this.$refs.routerAlive const { route: cacheRoute } = ($alive && $alive.cache[id]) || emptyObj - // 如果不是相同路由则检查beforePageLeave - if (cacheRoute && !isAlikeRoute(to, cacheRoute)) { + // 如果不是相同路由则检查 beforePageLeave + if (cacheRoute && !this.isAlikeRoute(to, cacheRoute)) { this.pageLeavePromise(id, 'replace') .then(() => next()) .catch(() => next(false)) diff --git a/src/components/RouterTab/rule.js b/src/components/RouterTab/rule.js index 77d9c465..64a7fd2f 100644 --- a/src/components/RouterTab/rule.js +++ b/src/components/RouterTab/rule.js @@ -1,29 +1,33 @@ +import matched from './matched' + // 内置规则 const rules = { // 地址,例如:"/page/1?type=a#title" 则取 "/page/1" - path (route) { - return route.path + path (route, pagePath) { + return pagePath || route.path }, - // 完整地址 (忽略hash),例如:"/page/1?type=a#title" 则取 "/page/1?type=a" - fullpath (route) { - return route.fullPath.replace(route.hash, '') + // 完整地址 (忽略 hash),例如:"/page/1?type=a#title" 则取 "/page/1?type=a" + fullpath (route, pagePath) { + return pagePath || route.fullPath.replace(route.hash, '') } } // 页签缓存规则 export default { props: { - // 缓存id,如果为函数,则参数为route + // 缓存 id,如果为函数,则参数为 route aliveId: { type: [ String, Function ], default: 'path' } }, + mixins: [ matched ], + methods: { // 获取缓存 id - getAliveId (route = this.$route) { + getAliveId (route = this.$route, matchRoutes = this.matchRoutes(route)) { let rule = (route.meta && route.meta.aliveId) || this.aliveId if (typeof rule === 'string') { @@ -34,7 +38,7 @@ export default { rule = rules.path } - return rule.bind(this)(route) + return rule.bind(this)(route, this.getPagePath(route, matchRoutes)) } } } diff --git a/src/components/RouterTab/scroll.js b/src/components/RouterTab/scroll.js index 2ff32e8b..44bd8933 100644 --- a/src/components/RouterTab/scroll.js +++ b/src/components/RouterTab/scroll.js @@ -5,6 +5,8 @@ import { scrollTo } from '../../util/dom' export default { watch: { async activedTab () { + if (!this.$el) return + // 激活页签时,如果当前页签不在可视区域,则滚动显示页签 await this.$nextTick() @@ -46,6 +48,8 @@ export default { // 调整Tab滚动显示 adjust () { + if (!this.$el) return + let $tab = this.$el.querySelector('.router-tab-header') let $scr = $tab.querySelector('.router-tab-scroll') let $nav = $scr.querySelector('.router-tab-nav') diff --git a/src/index.js b/src/index.js index e7fea42f..556a70ec 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,5 @@ import RouterTab from './components/RouterTab/RouterTab.vue' +import RouterAlive from './components/RouterAlive' import routerPage from './mixins/routerPage' import routes from './util/routes' @@ -8,6 +9,7 @@ RouterTab.install = function install (Vue, options) { install.installed = true Vue.component(RouterTab.name, RouterTab) + Vue.component(RouterAlive.name, RouterAlive) Vue.mixin(routerPage) } diff --git a/src/util/route.js b/src/util/route.js deleted file mode 100644 index 21d82b91..00000000 --- a/src/util/route.js +++ /dev/null @@ -1,21 +0,0 @@ -// 获取路由不带hash的路径 -export function getPathWithoutHash (route) { - return route.hash - ? route.fullPath.replace(route.hash, '') - : route.fullPath -} - -// 是否相似路由 -export function isAlikeRoute (route1, route2) { - return getPathWithoutHash(route1) === getPathWithoutHash(route2) -} - -// 获取路由页面组件 -export function getRouteComponent ({ matched }) { - return matched[matched.length - 1].components.default -} - -// 路由是否共用组件 -export function isSameComponentRoute (route1, route2) { - return getRouteComponent(route1) === getRouteComponent(route2) -} diff --git a/src/util/warn.js b/src/util/warn.js index a5ea0250..dac6424e 100644 --- a/src/util/warn.js +++ b/src/util/warn.js @@ -1,4 +1,4 @@ -const prefix = '[vue-router-tab]' +const prefix = '[Vue Router Tab]' // 错误 export function assert (condition, message) {