Browse Source

更改 alive-key 为 alive-id,添加"页签规则"文档和Demo

zhaihaoyi 6 years ago
parent
commit
e81b700b42

+ 1 - 1
README.md

@@ -41,7 +41,7 @@ Vue Router Tab 是基于 `Vue Router` 的路由页签组件。
 
 - [x] 响应路由变化来新增或切换页签
 - [x] 页签关闭和刷新,右键菜单操作
-- [x] [全局](docs/api.md#alive-key)和[针对特定路由](docs/api.md#meta.aliveKey)的页签缓存规则配置
+- [x] 全局和针对特定路由的[页签规则](docs/guide.md#页签规则)配置
 - [x] [初始页签数据](docs/guide.md#初始展示页签),进入页面时默认显示的页签
 - [x] 内置页签和页面[过渡效果](docs/guide.md#过渡效果),支持自定义配置
 - [x] [自定义页签模板](docs/guide.md#自定义页签模板)

+ 8 - 23
docs/api.md

@@ -2,35 +2,20 @@
 
 ## RouterTab Props
 
-### alive-key
+### alive-id
 
-页面组件缓存的
+页面组件缓存的 id
 
 - 类型: `string | Function`
 
-  - 如果类型为 `string` ,则取 `$route[aliveKey]` 的值
+  - 如果类型为 `string` ,则取 `$route[aliveId]` 的值
 
-  - 如果类型为 `Function` ,则取 `aliveKey($route)` 返回的字符串。该函数不应返回随机变化的字符串,以免页签无法与缓存的页面对应
+  - 如果类型为 `Function` ,则取 `aliveId($route)` 返回的字符串。该函数不应返回随机变化的字符串,以免页签无法与缓存的页面对应
 
 - 默认值: `'path'`
   
   根据 `$route.path` 来缓存页面组件。
 
-  - 同一路由-不同 `$route.params` 的页面,各自打开独立的页签,单独缓存
-
-  - 同一路由-相同 `$route.params` -不同 `$route.query` 的页面,共用同一个页签,后打开的页面将会替换之前页签内的页面,并且旧的页面缓存也被清除
-
-  - 仅仅 `$route.hash` 不同的页面,共用同一页签和缓存
-
-- 示例:
-
-  ``` html
-  <!-- 取 $route.fullPath -->
-  <router-tab alive-key="fullPath"/>
-
-  <!-- 函数方式 -->
-  <router-tab :alive-key="route => route.fullPath + '1'"/>
-  ```
 
 ### i18n
 
@@ -47,7 +32,7 @@
 
 ### tabs
 
-**初始页签数据**,进入页面时默认显示的页签。相同 `aliveKey` 的页签只保留第一个
+**初始页签数据**,进入页面时默认显示的页签。相同 `aliveId` 的页签只保留第一个
 
 - 类型: `Array <string | Object>`
   
@@ -175,11 +160,11 @@
 页签提示
 
 
-### meta.aliveKey
+### meta.aliveId
 
-页面组件缓存的,用以设置路由独立的页签缓存规则。
+页面组件缓存的 id,用以设置路由独立的页签缓存规则。
 
-配置参考: [RouterTab Props > alive-key](#alive-key)
+配置参考: [RouterTab Props > alive-id](#alive-id)
 
 
 ## RouterPage 路由页面

+ 95 - 7
docs/guide.md

@@ -188,7 +188,7 @@ this.$routerTab.close({
 **模糊关闭页签**
 ``` js
 // 关闭与给定地址共用页签的地址,即使地址不完全匹配
-// 默认 `alive-key` 规则下,类似 '/page/1?query=2' 这样的页签也能被匹配关闭
+// 默认 `alive-id` 规则下,类似 '/page/1?query=2' 这样的页签也能被匹配关闭
 this.$routerTab.close('/page/1', false)
 ```
 
@@ -222,7 +222,7 @@ this.$routerTab.refresh({
 **模糊刷新页签**
 ``` js
 // 刷新与给定地址共用页签的地址,即使地址不完全匹配
-// 默认 `alive-key` 规则下,类似 '/page/1?query=2' 这样的页签也能被匹配刷新
+// 默认 `alive-id` 规则下,类似 '/page/1?query=2' 这样的页签也能被匹配刷新
 this.$routerTab.close('/page/1', false)
 ```
 
@@ -243,13 +243,101 @@ this.$routerTab.refreshAll(true)
 ```
 
 
+## 页签规则
+
+不同的页签维护着各自的页面缓存,而**页签规则**定义了不同的路由**打开页签的方式**。
+
+### 默认页签规则
+
+`RouterTab` 默认取页面路由的 `$route.path` 作为缓存的 `alive-id`。
+
+这意味着,`$route.path` 相同的页面在同一个页签打开,新打开的页面会替换旧的页面,而 `$route.path` 不同的页面则打开独立的页签。
+
+::: tip 规则说明:
+  - 同一路由、不同 `$route.params` 的页面,各自打开独立的页签,单独缓存
+
+  - 同一路由、相同 `$route.params` 、不同 `$route.query` 的页面,共用同一个页签,后打开的页面将会替换之前页签内的页面,并且旧的页面缓存也被清除
+
+  - 仅仅 `$route.hash` 不同的页面,共用同一页签和缓存
+:::
+
+
+例如:下面表格的前三个地址的 `$route.path` 都是 **/page/1**,它们打开同一个页签。后三个地址的 `$route.path` 都是 **/page/2**,它们打开时共用另一个页签。
+
+| 地址 | `$route.path` |
+| ---- | ---- |
+| /page/1 | /page/1 |
+| /page/1?query=2 | /page/1 |
+| /page/1#hash1 | /page/1 |
+| /page/2 | /page/2 |
+| /page/2?query=2 | /page/2 |
+| /page/2#hash1 | /page/2 |
+
+
+### 全局页签规则
+
+通过配置 `router-tab` 组件的 `alive-id` 属性,您可以定义全局的页签规则
+
+<doc-links api="#alive-id" demo="/global-rule/rule/a/1"></doc-links>
+
+**示例:**
+
+``` html
+<router-tab :alive-id="route => route.fullPath.replace(route.hash, '')"/>
+```
+
+例子中,配置 `alive-id` 为 `fullPath` 去除 `hash` 部分。这样,“page/1” 和 “page/1?query=2”、“page/2”、“page/2?query=2” 这四个地址都是打开不同的页签。而 “page/1” 和 “page/1#hash1” 是同一个页签,因为它们忽略 `hash` 后的路径一致。
+
+
+### 路由页签规则
+
+通过配置**路由**的 `meta.aliveId` 属性,您可以针对特定路由定制页签规则
+
+<doc-links api="#meta-aliveid" demo="/default/route-rule/a/1"></doc-links>
+
+**示例:**
+
+``` javascript {19,21,23,24,25}
+// @/router.js
+import Vue from 'vue'
+import Router from 'vue-router'
+
+// 引入布局和页面
+import Admin from './components/layout/Admin.vue'
+
+// 异步页面组件
+const importPage = view => () => import(/* webpackChunkName: "p-[request]" */ `./views/${view}.vue`)
+
+Vue.use(Router)
+
+export default new Router({
+  routes: [{
+    path: '/',
+    redirect: '/route-rule/1/1',
+    component: Admin,
+    children: [{
+      path: 'route-rule/:catalog/:type',
+      component: importPage('CustomRule'),
+      meta: {
+        title: '定制规则',
+        aliveId (route) {
+          return `route-rule/${route.params.catalog}`
+        }
+      }
+    }]
+  }]
+})
+```
+
+
 ## 进阶
 
 前面的内容已经能满足大部分使用场景了,您还可以根据下面的内容实现更多功能。
 
+
 ### 过渡效果
 
-您可以通过配置组件的 `tab-transition` 和 `page-transition` 属性,分别替换默认的**页签**和**页面**过渡效果
+您可以通过配置 `router-tab` 组件的 `tab-transition` 和 `page-transition` 属性,分别替换默认的**页签**和**页面**过渡效果
 
 ::: warning
 - 如果是组件作用域内的 CSS(配置了 `scoped`),需要在选择器前添加 `/deep/`才能生效
@@ -322,7 +410,7 @@ this.$routerTab.refreshAll(true)
 
 ### 自定义页签模板
 
-通过组件的默认作用域插槽,我们可以自定义页签显示的内容
+通过 `router-tab` 组件的默认作用域插槽,我们可以自定义页签显示的内容
 
 插槽的作用域提供以下属性供模板使用:
   - **tab** {Object} 页签项信息,包括 `id`, `title`, `icon`, `closable` 等
@@ -347,7 +435,7 @@ this.$routerTab.refreshAll(true)
 
 ### 初始展示页签
 
-通过配置组件的 `tabs` 属性,可以设置进入页面时默认显示的页签。
+通过配置 `router-tab` 组件的 `tabs` 属性,可以设置进入页面时默认显示的页签。
 
 <doc-links api="#tabs" demo="/initial-tabs/"></doc-links>
 
@@ -381,7 +469,7 @@ this.$routerTab.refreshAll(true)
             }
           },
           
-          // 此页面与'/page/2'的aliveKey一致,将只保留先设置的页签
+          // 此页面与'/page/2'的aliveId一致,将只保留先设置的页签
           { to: '/page/2?t=1', title: '页面2-1' }
         ]
       }
@@ -421,7 +509,7 @@ export default {
 
 ### 语言配置
 
-通过配置组件的 `i18n` 属性,可以设置组件显示的语言 (主要表现为页签右键菜单)。
+通过配置 `router-tab` 组件的 `i18n` 属性,可以设置组件显示的语言 (主要表现为页签右键菜单)。
 
 
 `RouterTab` 默认语言是 `zh-CN`,另外内置了 `en`。

+ 4 - 28
src/App.vue

@@ -4,7 +4,7 @@
 
     <div class="app-bd">
       <div class="app-sd-mask" @click="sidebarOpen = false"></div>
-      <app-side :menu="menu"/>
+      <app-aside/>
       <router-view/>
     </div>
   </div>
@@ -14,38 +14,14 @@
 
 <script>
 import AppHeader from '@/components/AppHeader.vue'
-import AppSide from '@/components/AppSide.vue'
+import AppAside from '@/components/AppAside.vue'
 
 export default {
   name: 'App',
-  components: { AppHeader, AppSide },
+  components: { AppHeader, AppAside },
   data () {
     return {
-      sidebarOpen: false,
-      menu: [{
-        title: 'RouterTab 配置',
-        data: [
-          { text: '默认配置', to: '/default/' },
-          { text: '过渡效果', to: '/transition/' },
-          { text: '初始展示页签', to: '/initial-tabs/' },
-          {
-            text: '语言配置',
-            to: '/language/',
-            children: {
-              data: [
-                { text: '自定义语言', to: '/language/custom' }
-              ]
-            }
-          },
-          { text: '自定义页签模板', to: '/slot/' }
-        ]
-      }, {
-        title: '页面配置',
-        data: [
-          { text: '动态更新页签配置', to: '/default/tab-dynamic' },
-          { text: '页面离开确认', to: '/initial-tabs/page-leave' }
-        ]
-      }]
+      sidebarOpen: false
     }
   },
 

+ 5 - 0
src/assets/scss/demo.scss

@@ -71,6 +71,11 @@ a:link {
   color: #f50;
 }
 
+// 提示
+.text-tips {
+  color: $color;
+}
+
 // 按钮
 .demo-btn {
   display: inline-block;

+ 86 - 0
src/components/AppAside.vue

@@ -0,0 +1,86 @@
+<template>
+  <aside class="app-sd">
+    <div class="app-sd-menu">
+      <menu-group v-for="(item, index) in menu" :key="index" :data="item"/>
+    </div>
+    <footer class="app-sd-ft">
+      <site-link/>
+    </footer>
+  </aside>
+</template>
+
+<script>
+import MenuGroup from '@/components/MenuGroup.vue'
+import SiteLink from '@/components/SiteLink.vue'
+
+export default {
+  name: 'AppAside',
+  components: { MenuGroup, SiteLink },
+  data () {
+    return {
+      menu: [{
+        title: 'RouterTab 配置',
+        data: [
+          { text: '默认配置', to: '/default/' },
+          { text: '过渡效果', to: '/transition/' },
+          { text: '初始展示页签', to: '/initial-tabs/' },
+          {
+            text: '语言配置',
+            to: '/language/',
+            children: {
+              data: [
+                { text: '自定义语言', to: '/language/custom' }
+              ]
+            }
+          },
+          { text: '自定义页签模板', to: '/slot/' }
+        ]
+      }, {
+        title: '页签规则',
+        data: [
+          { text: '默认页签规则', to: '/default/rule/a/1' },
+          { text: '全局页签规则', to: '/global-rule/rule/a/1' },
+          { text: '路由页签规则', to: '/default/route-rule/a/1' }
+        ]
+      }, {
+        title: '页面配置',
+        data: [
+          { text: '动态更新页签配置', to: '/default/tab-dynamic' },
+          { text: '页面离开确认', to: '/initial-tabs/page-leave' }
+        ]
+      }]
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-sd {
+  display: flex;
+  flex-direction: column;
+  box-sizing: border-box;
+  background-color: #fff;
+  border-right: 1px solid #eaecef;
+
+  &-menu {
+    flex: auto;
+    padding: 2rem 0;
+    height: 0;
+    overflow-y: auto;
+
+    > .menu-group {
+      margin-bottom: 1rem;
+    }
+  }
+
+  &-ft {
+    border-top: 1px solid #eee;
+
+    @include screen-pc {
+      .site-link {
+        display: none;
+      }
+    }
+  }
+}
+</style>

+ 0 - 54
src/components/AppSide.vue

@@ -1,54 +0,0 @@
-<template>
-  <aside class="app-sd">
-    <div class="app-sd-menu">
-      <menu-group v-for="(item, index) in menu" :key="index" :data="item"/>
-    </div>
-    <footer class="app-sd-ft">
-      <site-link/>
-    </footer>
-  </aside>
-</template>
-
-<script>
-import MenuGroup from '@/components/MenuGroup.vue'
-import SiteLink from '@/components/SiteLink.vue'
-
-export default {
-  name: 'AppSide',
-  components: { MenuGroup, SiteLink },
-  props: {
-    menu: Array
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.app-sd {
-  display: flex;
-  flex-direction: column;
-  box-sizing: border-box;
-  background-color: #fff;
-  border-right: 1px solid #eaecef;
-
-  &-menu {
-    flex: auto;
-    padding: 2rem 0;
-    height: 0;
-    overflow-y: auto;
-
-    > .menu-group {
-      margin-bottom: 1rem;
-    }
-  }
-
-  &-ft {
-    border-top: 1px solid #eee;
-
-    @include screen-pc {
-      .site-link {
-        display: none;
-      }
-    }
-  }
-}
-</style>

+ 46 - 0
src/components/PageRouteInfo.vue

@@ -0,0 +1,46 @@
+<template>
+  <table class="route-info">
+    <tr>
+      <th width="80">name</th>
+      <td>{{$route.name}}</td>
+    </tr>
+    <tr>
+      <th>path</th>
+      <td>{{$route.path}}</td>
+    </tr>
+    <tr>
+      <th>params</th>
+      <td>{{$route.params}}</td>
+    </tr>
+    <tr>
+      <th>query</th>
+      <td>{{$route.query}}</td>
+    </tr>
+    <tr>
+      <th>hash</th>
+      <td>{{$route.hash}}</td>
+    </tr>
+    <tr>
+      <th>fullPath</th>
+      <td>{{$route.fullPath}}</td>
+    </tr>
+  </table>
+</template>
+
+<style lang="scss" scoped>
+.route-info {
+  min-width: 300px;
+  border-collapse: collapse;
+
+  th, td {
+    padding: 5px 8px;
+    border: 1px solid #ddd;
+  }
+
+  th {
+    text-align: left;
+    font-weight: 400;
+    background-color: #f7f7f7;
+  }
+}
+</style>

+ 5 - 0
src/components/layout/GlobalRule.vue

@@ -0,0 +1,5 @@
+<template>
+  <main class="app-main">
+    <router-tab :alive-id="route => route.fullPath.replace(route.hash, '')"/>
+  </main>
+</template>

+ 1 - 1
src/components/layout/InitialTabs.vue

@@ -20,7 +20,7 @@ export default {
           },
           title: '页面2'
         }, // <router-link> location 方式配置
-        { to: '/initial-tabs/page/2?t=1', title: '页面2-1' } // 默认 aliveKey 配置下,该页签与 '/initial-tabs/page/2' 页签的 aliveKey 一致,将只保留第一个出现的页签
+        { to: '/initial-tabs/page/2?t=1', title: '页面2-1' } // 默认 aliveId 配置下,该页签与 '/initial-tabs/page/2' 页签的 aliveId 一致,将只保留第一个出现的页签
       ]
     }
   }

+ 4 - 4
src/lib/RouterTab/components/RouterAlive.js

@@ -1,10 +1,10 @@
-import { emptyObj, getAliveKey, getFirstComponentChild, isAlikeRoute, isSameComponentRoute } from '../util'
+import { emptyObj, getAliveId, getFirstComponentChild, isAlikeRoute, isSameComponentRoute } from '../util'
 
 export default {
   name: 'RouterAlive',
   props: {
     // 缓存key,如果为函数,则参数为route
-    aliveKey: {
+    aliveId: {
       type: [ String, Function ],
       default: 'path'
     }
@@ -30,7 +30,7 @@ export default {
 
       if (pageNode && pageNode.componentOptions) {
         // 获取缓存
-        const key = this.getAliveKey()
+        const key = this.getAliveId()
         const cacheItem = cache[key]
         const { vm: cacheVm, route: cacheRoute } = cacheItem || emptyObj
 
@@ -79,7 +79,7 @@ export default {
   },
 
   methods: {
-    getAliveKey,
+    getAliveId,
 
     // 设置缓存项
     set (key, item) {

+ 7 - 7
src/lib/RouterTab/components/RouterTab.js

@@ -1,14 +1,14 @@
 import Vue from 'vue'
 import RouterAlive from './RouterAlive'
 import langs from '../lang'
-import { emptyObj, emptyArray, logPrefix, scrollTo, debounce, promiseQueue, getAliveKey, isAlikeRoute, getPathWithoutHash } from '../util'
+import { emptyObj, emptyArray, logPrefix, scrollTo, debounce, promiseQueue, getAliveId, isAlikeRoute, getPathWithoutHash } from '../util'
 
 export default {
   name: 'RouterTab',
   components: { RouterAlive },
   props: {
     // 缓存key,如果为函数,则参数为route
-    aliveKey: RouterAlive.props.aliveKey,
+    aliveId: RouterAlive.props.aliveId,
 
     // 语言配置
     // - 为字符串时,可以设置为内置的语言 'zh-CN' (默认) 和 'en'
@@ -172,7 +172,7 @@ export default {
   },
 
   methods: {
-    getAliveKey,
+    getAliveId,
 
     // 页面离开导航守卫
     routerPageLeaveGuard (to, from, next) {
@@ -185,7 +185,7 @@ export default {
 
         next()
       } else {
-        const id = this.getAliveKey(to)
+        const id = this.getAliveId(to)
         const $alive = this.$refs.routerAlive
         const { route: cacheRoute } = ($alive && $alive.cache[id]) || emptyObj
 
@@ -230,7 +230,7 @@ export default {
 
     // 更新激活的页签
     updateActivedTab () {
-      this.activedTab = this.getAliveKey()
+      this.activedTab = this.getAliveId()
     },
 
     // 更新tab数据
@@ -264,13 +264,13 @@ export default {
           return matchTab.id
         }
       } else {
-        return this.getAliveKey($route)
+        return this.getAliveId($route)
       }
     },
 
     // 从route中获取tab数据
     getRouteTab (route) {
-      let id = this.getAliveKey(route)
+      let id = this.getAliveId(route)
       let { fullPath: to, meta } = route
       let { title, icon, tips } = meta
 

+ 1 - 1
src/lib/RouterTab/components/RouterTab.vue

@@ -40,7 +40,7 @@
 
     <!-- 页面容器 -->
     <div class="router-tab-container" :class="{ loading }">
-      <router-alive ref="routerAlive" :alive-key="aliveKey" @update="updateTab">
+      <router-alive ref="routerAlive" :alive-id="aliveId" @update="updateTab">
         <transition
           v-bind="typeof pageTransition === 'string' ? { name: pageTransition } : pageTransition"
           @after-enter="onPageTransitionEnd"

+ 1 - 1
src/lib/RouterTab/mixins/RouterPage.js

@@ -10,7 +10,7 @@ export default {
     // 标记为路由页面
     this._isRouterPage = true
 
-    const key = $alive.getAliveKey($route)
+    const key = $alive.getAliveId($route)
 
     // 更新缓存数据
     let cacheItem = $alive.set(key, {

+ 5 - 5
src/lib/RouterTab/util.js

@@ -63,12 +63,12 @@ export function getFirstComponentChild (children) {
 }
 
 // 获取缓存key
-export function getAliveKey (route = this.$route) {
-  let aliveKey = (route.meta && route.meta.aliveKey) || this.aliveKey || 'path'
-  if (typeof aliveKey === 'function') {
-    return aliveKey.bind(this)(route)
+export function getAliveId (route = this.$route) {
+  let aliveId = (route.meta && route.meta.aliveId) || this.aliveId || 'path'
+  if (typeof aliveId === 'function') {
+    return aliveId.bind(this)(route)
   }
-  return route[aliveKey]
+  return route[aliveId]
 }
 
 /* 路由方法 */

+ 22 - 0
src/router.js

@@ -15,6 +15,23 @@ let pageRoutes = [{
     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'),
@@ -65,6 +82,11 @@ export default new Router({
     component: importLayout('Slot'),
     redirect: '/slot/page/1',
     children: pageRoutes
+  }, {
+    path: '/global-rule/',
+    component: importLayout('GlobalRule'),
+    redirect: '/global-rule/rule/a/1',
+    children: pageRoutes
   }, {
     path: '/404',
     component: importPage('404'),

+ 4 - 48
src/views/Page.vue

@@ -40,62 +40,18 @@
 
     <h3>路由信息</h3>
 
-    <table class="route-info">
-      <tr>
-        <th width="80">name</th>
-        <td>{{$route.name}}</td>
-      </tr>
-      <tr>
-        <th>path</th>
-        <td>{{$route.path}}</td>
-      </tr>
-      <tr>
-        <th>params</th>
-        <td>{{$route.params}}</td>
-      </tr>
-      <tr>
-        <th>query</th>
-        <td>{{$route.query}}</td>
-      </tr>
-      <tr>
-        <th>hash</th>
-        <td>{{$route.hash}}</td>
-      </tr>
-      <tr>
-        <th>fullPath</th>
-        <td>{{$route.fullPath}}</td>
-      </tr>
-    </table>
+    <page-route-info/>
   </div>
 </template>
 
-<style lang="scss" scoped>
-.app-page {
-
-  .route-info {
-    min-width: 300px;
-    border-collapse: collapse;
-
-    th, td {
-      padding: 5px 8px;
-      border: 1px solid #ddd;
-    }
-
-    th {
-      text-align: left;
-      font-weight: 400;
-      background-color: #f7f7f7;
-    }
-  }
-}
-</style>
-
 <script>
 import pageTimer from '@/mixins/pageTimer'
+import PageRouteInfo from '@/components/PageRouteInfo'
 
 export default {
   name: 'Page',
-  mixins: [pageTimer],
+  mixins: [ pageTimer ],
+  components: { PageRouteInfo },
   data () {
     let id = this.$route.params.id
     return {

+ 70 - 0
src/views/Rule.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="app-page">
+    <h2>{{$route.meta.title}}:目录{{catalog}},分类{{type}}</h2>
+
+    <p>你在 <strong class="text-strong">{{pageTime}}</strong> 秒前打开本页面</p>
+
+    <h3>{{ruleLabel}}页签规则</h3>
+
+    <p class="text-tips" v-if="ruleType === 'global'">不同“目录”、“分类”或 “query” 的页面打开独立的页签</p>
+
+    <p class="text-tips" v-else-if="ruleType === 'route'">相同“目录”的页面共用同一页签,不同“目录”打开不同页签</p>
+
+    <p class="text-tips" v-else>不同“目录”或“分类”的页面打开独立的页签,相同“目录”、“分类”但不同 “query” 的页面共用页签</p>
+
+    <table>
+      <tr v-for="cat in catalogs" :key="cat">
+        <td v-for="type in types" :key="type">
+          <router-link class="demo-btn" :to="`../../rule/${cat}/${type}`">{{cat}}/{{type}}</router-link>
+        </td>
+        <td>
+          <router-link class="demo-btn" :to="`../../rule/${cat}/1?query=abc`">{{cat}}/1?query=abc</router-link>
+        </td>
+      </tr>
+    </table>
+
+    <h3>路由信息</h3>
+
+    <page-route-info/>
+  </div>
+</template>
+
+<script>
+import pageTimer from '@/mixins/pageTimer'
+import PageRouteInfo from '@/components/PageRouteInfo'
+
+export default {
+  name: 'Rule',
+  mixins: [ pageTimer ],
+  components: { PageRouteInfo },
+  data () {
+    let route = this.$route
+    let { catalog, type } = route.params
+
+    let ruleType = 'default'
+
+    if (route.meta.aliveId) {
+      ruleType = 'route'
+    } else if (route.fullPath.indexOf('/global-rule/') > -1) {
+      ruleType = 'global'
+    }
+
+    let ruleLabel = {
+      default: '默认',
+      route: '路由',
+      global: '全局'
+    }[ruleType]
+
+    return {
+      ruleType,
+      ruleLabel,
+      catalog,
+      type,
+      catalogs: ['a', 'b', 'c'],
+      types: [ 1, 2, 3 ],
+      link: { catalog, type },
+      routeTab: `${ruleLabel}规则${catalog}/${type}`
+    }
+  }
+}
+</script>