Browse Source

添加一批demo实例

zhaihaoyi 6 years ago
parent
commit
9527f5c7f3

+ 1 - 0
public/index.html

@@ -6,6 +6,7 @@
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
     <title>vue-router-tab</title>
+    <link rel="stylesheet" href="//at.alicdn.com/t/font_1048253_cq80r020h5.css"/>
   </head>
   <body>
     <noscript>

+ 53 - 1
src/App.vue

@@ -1,3 +1,55 @@
 <template>
-  <router-view/>
+  <div class="app-ct">
+    <header class="app-hd">
+      <h2>
+        <strong>Vue Router Tab</strong> - Demo
+      </h2>
+    </header>
+
+    <div class="app-bd">
+      <aside class="app-sd">
+        <menu-group v-for="(item, index) in menu" :key="index" :data="item"/>
+      </aside>
+
+      <router-view/>
+    </div>
+  </div>
 </template>
+
+<style lang="scss" src="./assets/scss/demo.scss"></style>
+
+<script>
+import MenuGroup from '@/components/MenuGroup.vue'
+export default {
+  name: 'App',
+  components: { MenuGroup },
+  data () {
+    return {
+      menu: [{
+        title: 'RouterTab 配置',
+        data: [
+          { text: '默认配置', to: '/default/' },
+          { 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: '/default/page-leave' },
+          { text: '页签操作', to: '/default/tab-operate' }
+        ]
+      }]
+    }
+  }
+}
+</script>

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

@@ -0,0 +1,117 @@
+html, body {
+	width: 100%;
+	height: 100%;
+}
+
+body {
+	margin: 0;
+	font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    font-size: 16px;
+    color: #2c3e50;
+}
+
+[class^="rt-icon-"],
+[class*=" rt-icon-"] {
+  font-family: "rtfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+
+.app-ct {
+	display: flex;
+	flex-direction: column;
+	width: 100%;
+	height: 100%;
+
+	.app-hd {
+		flex: none;
+		position: relative;
+		z-index: 99;
+		box-shadow: 0 0 8px rgba(0,0,0,.2);
+
+		h2 {
+			margin: 0 0 0 1.5rem;
+			line-height: 50px;
+			font-weight: 400;
+			font-size: 1.3rem;
+
+			strong {
+				font-weight: 400;
+				color: $color;
+			}
+		}
+	}
+
+	.app-bd {
+		display: flex;
+		flex: auto;
+	}
+}
+
+.app-sd {
+	flex: 0 0 200px;
+	padding-top: 2rem;
+	border-right: 1px solid #eaecef;
+
+	
+	> .menu-group {
+		margin-bottom: 1rem;
+	}
+
+	.menu-group {
+
+		.menu-title {
+			padding: 0 1.25rem;
+			margin-top: 0;
+			margin-bottom: .5rem;
+			font-size: 1.1em;
+			font-weight: 700;
+		}
+
+		.menu-list {
+			margin: 0;
+			padding: 0;
+		}
+	
+		.menu-item {
+			padding: .35rem 1rem .35rem 1.25rem;
+			list-style: none;
+			font-size: 15px;
+
+			> a {
+				color: #2c3e50;
+				text-decoration: none;
+				cursor: pointer;
+		
+				&.router-link-active,
+				&:hover {
+					color: $color;
+				}
+		
+				&.router-link-active {
+					font-weight: 700;
+				}
+			}
+
+			.menu-group .router-link-active {
+					font-weight: 400;
+			}
+		}
+	}
+}
+
+.app-main {
+	flex: auto;
+	overflow: hidden;
+}
+
+.app-page {
+  padding: 15px;
+  font-size: 14px;
+	line-height: 1.5;
+}

+ 1 - 0
src/assets/scss/variables.scss

@@ -0,0 +1 @@
+$color: #42b983;

+ 20 - 0
src/components/MenuGroup.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="menu-group">
+    <h3 class="menu-title" v-if="data.title">{{data.title}}</h3>
+    <ul class="menu-list">
+      <li class="menu-item" v-for="item in data.data" :key="item.to">
+        <router-link :to="item.to">{{item.text}}</router-link>
+        <menu-group v-if="item.children" :data="item.children"/>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'MenuGroup',
+  props: {
+    data: Object
+  }
+}
+</script>

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

@@ -0,0 +1,5 @@
+<template>
+  <main class="app-main">
+    <router-tab/>
+  </main>
+</template>

+ 28 - 0
src/components/layout/InitialTabs.vue

@@ -0,0 +1,28 @@
+<template>
+  <main class="app-main">
+    <router-tab :tabs="tabs"/>
+  </main>
+</template>
+
+<script>
+export default {
+  name: 'InitialTabs',
+  data () {
+    return {
+      tabs: [
+        '/initial-tabs/page-leave', // 只需设置 fullpath,程序将自动从 router 配置中获取页签的标题/图标等信息
+        { to: '/initial-tabs/tab-dynamic', closable: false }, // closable 页签是否可关闭,这个只能在初始页签中配置,其他地方配置没有意义
+        { to: '/initial-tabs/page/1', title: '页面1' }, // 具有动态页签标题的页签,需要设置初始页签标题
+        {
+          to: {
+            path: '/initial-tabs/page/2',
+            query: { t: 2 }
+          },
+          title: '页面2'
+        }, // <router-link> location 方式配置
+        { to: '/initial-tabs/page/2?t=1', title: '页面2-1' } // 默认 aliveKey 配置下,该页签与 '/initial-tabs/page/2' 页签的 aliveKey 一致,将只保留第一个出现的页签
+      ]
+    }
+  }
+}
+</script>

+ 31 - 0
src/components/layout/Language.vue

@@ -0,0 +1,31 @@
+<template>
+  <main class="app-main">
+    <router-tab :i18n="'en'"/>
+  </main>
+</template>
+
+<script>
+export default {
+  name: 'Language',
+  data () {
+    return {
+      lang: {
+        tab: {
+          untitled: 'Untitled Page'
+        },
+        contextmenu: {
+          refresh: 'Refresh This',
+          refreshAll: 'Refresh All',
+          close: 'Close This',
+          closeLefts: 'Close to the Left',
+          closeRights: 'Close to the Right',
+          closeOthers: 'Close Others'
+        },
+        msg: {
+          keepOneTab: 'Keep at least 1 tab'
+        }
+      }
+    }
+  }
+}
+</script>

+ 28 - 0
src/components/layout/LanguageCustom.vue

@@ -0,0 +1,28 @@
+<template>
+  <main class="app-main">
+    <router-tab :i18n="lang"/>
+  </main>
+</template>
+
+<script>
+export default {
+  name: 'LanguageCustom',
+  data () {
+    return {
+      lang: {
+        tab: {
+          untitled: 'Untitled Page'
+        },
+        contextmenu: {
+          refresh: 'Refresh This',
+          refreshAll: 'Refresh All',
+          close: 'Close This',
+          closeLefts: 'Close to the Left',
+          closeRights: 'Close to the Right',
+          closeOthers: 'Close Others'
+        }
+      }
+    }
+  }
+}
+</script>

+ 29 - 0
src/components/layout/Slot.vue

@@ -0,0 +1,29 @@
+<template>
+  <main class="app-main">
+    <router-tab>
+      <template v-slot="{ tab: { id, title, icon, closable }, tabs, index}">
+      <i v-if="icon" class="tab-icon" :class="icon"></i>
+      <span class="tab-index">{{index}}</span>
+      <span class="tab-title">{{title || '未命名页签'}}</span>
+      <i class="tab-close el-icon-close" v-if="closable !== false &&tabs.length > 1" @click.prevent="close(id)"></i>
+    </template>
+    </router-tab>
+  </main>
+</template>
+
+<style lang="scss" scoped>
+.tab-index {
+  display: inline-block;
+  margin-right: 3px;
+  width: 1.2em;
+  height: 1.2em;
+  line-height: 1.2;
+  text-align: center;
+  background-color: #f5f5f5;
+  border-radius: 100%;
+}
+
+.router-tab-item.actived .tab-index {
+  background-color: rgba(255,255,255,.2);
+}
+</style>

+ 40 - 0
src/mixins/pageTimer.js

@@ -0,0 +1,40 @@
+// 页面计时器
+export default {
+  data () {
+    return {
+      openTime: new Date(),
+      pageTime: 0
+    }
+  },
+
+  activated () {
+    this.updatePageTime()
+  },
+
+  deactivated () {
+    this.clearPageTimer()
+  },
+
+  beforeDestroy () {
+    this.clearPageTimer()
+  },
+
+  methods: {
+    calcPageTime () {
+      this.pageTime = Math.floor((new Date() - this.openTime) / 1000)
+    },
+
+    updatePageTime () {
+      this.calcPageTime()
+
+      this.clearPageTimer()
+
+      // 定时更新事件
+      this.pageTimer = setInterval(this.calcPageTime, 1000)
+    },
+
+    clearPageTimer () {
+      clearInterval(this.pageTimer)
+    }
+  }
+}

+ 63 - 13
src/router.js

@@ -1,21 +1,71 @@
 import Vue from 'vue'
 import Router from 'vue-router'
 
-const importView = view => () => import(/* webpackChunkName: "v-[request]" */ `./views/${view}.vue`)
+const importPage = view => () => import(/* webpackChunkName: "p-[request]" */ `./views/${view}.vue`)
+
+const importLayout = view => () => import(/* webpackChunkName: "ly-[request]" */ `./components/layout/${view}.vue`)
 
 Vue.use(Router)
 
+// 路由页面
+let pageRoutes = [{
+  path: 'page/:id',
+  component: importPage('Page'),
+  meta: {
+    title: '页面',
+    icon: 'rt-icon-doc'
+  }
+}, {
+  path: 'tab-operate',
+  component: importPage('TabOperate'),
+  meta: {
+    title: '页签操作',
+    icon: 'rt-icon-doc'
+  }
+}, {
+  path: 'tab-dynamic',
+  component: importPage('TabDynamic'),
+  meta: {
+    title: '动态页签',
+    icon: 'rt-icon-log'
+  }
+}, {
+  path: 'page-leave',
+  component: importPage('PageLeave'),
+  meta: {
+    title: '页面离开提示',
+    icon: 'rt-icon-contact'
+  }
+}]
+
 export default new Router({
-  routes: [
-    {
-      path: '/',
-      name: 'home',
-      component: importView('Home'),
-      redirect: '/page/1',
-      children: [{
-        path: '/page/:id',
-        component: importView('Page')
-      }]
-    }
-  ]
+  routes: [{
+    path: '/',
+    redirect: '/default/page/1'
+  }, {
+    path: '/default/',
+    component: importLayout('Default'),
+    redirect: '/default/page/1',
+    children: pageRoutes
+  }, {
+    path: '/initial-tabs/',
+    component: importLayout('InitialTabs'),
+    redirect: '/initial-tabs/page/1',
+    children: pageRoutes
+  }, {
+    path: '/language/',
+    component: importLayout('Language'),
+    redirect: '/language/page/1',
+    children: pageRoutes
+  }, {
+    path: '/language/custom/',
+    component: importLayout('LanguageCustom'),
+    redirect: '/language/custom/page/1',
+    children: pageRoutes
+  }, {
+    path: '/slot/',
+    component: importLayout('Slot'),
+    redirect: '/slot/page/1',
+    children: pageRoutes
+  }]
 })

+ 0 - 30
src/views/Home.vue

@@ -1,30 +0,0 @@
-<template>
-  <div>
-    <nav class="demo-nav">
-      <router-link to="/page/2">params:2</router-link>
-      <router-link to="/page/2?t=3">params:2 query:t=3</router-link>
-      <router-link to="/page/3">params:3</router-link>
-    </nav>
-    <router-tab/>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.demo-nav {
-  margin-bottom: 10px;
-
-  a {
-    margin-right: 15px;
-    font-size: 13px;
-    color: blue;
-
-    &:hover {
-      color: orange;
-    }
-
-    &.router-link-exact-active {
-      font-weight: 700;
-    }
-  }
-}
-</style>

+ 9 - 60
src/views/Page.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="app-page-container">
-    <h1 @click="click">RouterTab 实例页</h1>
-    <p>你在<strong>{{second}}</strong>秒前打开本页面</p>
+  <div class="app-page">
+    <h2 @click="click">RouterTab 实例页</h2>
+    <p>你在<strong>{{pageTime}}</strong>秒前打开本页面</p>
     <input type="text">
     <dl>
       <dt>name</dt>
@@ -21,11 +21,7 @@
 </template>
 
 <style lang="scss" scoped>
-.app-page-container {
-  padding: 15px;
-  font-size: 14px;
-  line-height: 1.5;
-
+.app-page {
   dt {
     float: left;
     width: 150px;
@@ -39,67 +35,20 @@
 </style>
 
 <script>
+import pageTimer from '@/mixins/pageTimer'
+
 export default {
-  name: 'router-tab-page',
+  name: 'Page',
+  mixins: [ pageTimer ],
   data () {
     return {
-      openTime: new Date(),
-      second: 0,
       routerTab: {
-        title: '页签实例' + this.$route.params.id
+        title: '页面' + this.$route.params.id
       }
     }
   },
 
-  activated () {
-    this.updateOpenTime()
-  },
-
-  deactivated () {
-    this.clearOpenTimeInterval()
-  },
-
-  beforeDestroy () {
-    this.clearOpenTimeInterval()
-  },
-
-  // 页面离开前提示
-  beforePageLeave (resolve, reject, tab, type) {
-    const action = (type === 'close' && '关闭') ||
-      (type === 'refresh' && '刷新') ||
-      (type === 'replace' && '替换')
-
-    const msg = `您确认要${action}页签“${tab.title}”吗?`
-
-    if (confirm(msg)) {
-      resolve()
-    } else {
-      reject('拒绝了页面离开')
-    }
-
-    /* this.$confirm(msg, '提示', { closeOnHashChange: false })
-      .then(resolve)
-      .catch(reject) */
-  },
-
   methods: {
-    update () {
-      this.second = Math.floor((new Date() - this.openTime) / 1000)
-    },
-
-    updateOpenTime () {
-      this.update()
-
-      this.clearOpenTimeInterval()
-
-      // 定时更新事件
-      this.openTimeInterval = setInterval(this.update, 1000)
-    },
-
-    clearOpenTimeInterval () {
-      clearInterval(this.openTimeInterval)
-    },
-
     click () {
       console.log('aaa')
     }

+ 49 - 0
src/views/PageLeave.vue

@@ -0,0 +1,49 @@
+<template>
+  <div class="app-page">
+    <h2>页面离开提示</h2>
+    <p>
+      修改输入框的值后,页面在页签关闭/刷新/被替换时将会确认提示
+    </p>
+    <input type="text" v-model="editValue">
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'PageLeave',
+
+  data () {
+    let value = '初始值'
+    return {
+      value,
+      editValue: value
+    }
+  },
+
+  // 页面离开前提示
+  beforePageLeave (resolve, reject, tab, type) {
+    const action = (type === 'close' && '关闭') ||
+      (type === 'refresh' && '刷新') ||
+      (type === 'replace' && '替换')
+
+    const msg = `您确认要${action}页签“${tab.title}”吗?`
+
+    // 值未改变,直接离开;值改变则确认提示
+    if (this.editValue === this.value) {
+      resolve()
+    } else if (confirm(msg)) {
+      resolve()
+    } else {
+      reject('拒绝了页面离开')
+    }
+
+    /*
+    // 此处使用了 Element 的 confirm 组件
+    // 需将 closeOnHashChange 配置为 false,以避免路由切换导致确认框关闭
+    this.$confirm(msg, '提示', { closeOnHashChange: false })
+      .then(resolve)
+      .catch(reject)
+    */
+  }
+}
+</script>

+ 70 - 0
src/views/TabDynamic.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="app-page">
+    <h2>动态页签</h2>
+
+    <div class="form-item">
+      <label>
+        <p>修改页签标题</p>
+        <input type="text" v-model="routerTab.title">
+      </label>
+    </div>
+
+    <div class="form-item">
+      <label>
+        <p>修改页签提示</p>
+        <input type="text" v-model="routerTab.tips">
+      </label>
+    </div>
+
+    <div class="form-item tab-icons">
+      <p>切换图标</p>
+      <a
+        class="tab-icon"
+        v-for="icon in icons"
+        :key="icon"
+        :class="`${icon === routerTab.icon ? 'actived' : ''} ${icon}`"
+        title="设置页签图标"
+        @click="routerTab.icon = icon"
+      ></a>
+    </div>
+
+  </div>
+</template>
+
+<script>
+export default {
+  data () {
+    return {
+      routerTab: {
+        title: '动态页签',
+        icon: 'rt-icon-log',
+        tips: ''
+      },
+
+      icons: ['rt-icon-log', 'rt-icon-doc', 'rt-icon-contact']
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.form-item {
+  margin-bottom: 1rem;
+
+  p {
+    margin: .5em 0;
+  }
+}
+
+.tab-icons {
+  a {
+    margin-right: 1em;
+    font-size: 20px;
+    cursor: pointer;
+
+    &.actived {
+      color: $color;
+    }
+  }
+}
+</style>

+ 52 - 0
src/views/TabOperate.vue

@@ -0,0 +1,52 @@
+<template>
+  <div class="app-page">
+    <h2>页签操作</h2>
+
+    <p>你在<strong>{{pageTime}}</strong>秒前打开本页面</p>
+
+    <div class="btn-list">
+      <router-link class="btn" to="/default/page/1">打开“页面1”</router-link>
+
+      <a class="btn" @click="$routerTab.refresh('/default/page/1')">刷新“页面1”</a>
+
+      <router-link class="btn" to="/default/tab-dynamic">打开“动态页签”</router-link>
+
+      <a class="btn" @click="$routerTab.close('/default/tab-dynamic')">关闭“动态页签”</a>
+
+      <a class="btn" @click="$routerTab.refresh()">刷新当前页面</a>
+
+      <a class="btn" @click="$routerTab.close()">关闭当前页面</a>
+    </div>
+  </div>
+</template>
+
+<script>
+import pageTimer from '@/mixins/pageTimer'
+
+export default {
+  mixins: [ pageTimer ]
+}
+</script>
+
+<style lang="scss" scoped>
+.btn-list {
+  display: inline-flex;
+  flex-direction: column;
+}
+
+.btn {
+  display: inline-block;
+  margin-bottom: 1em;
+  padding: 2px 8px;
+  font-size: 14px;
+  color: #333;
+  text-decoration: none;
+  cursor: pointer;
+  border-radius: 5px;
+  border: 1px solid #ccc;
+
+  &:hover {
+    color: $color;
+  }
+}
+</style>

+ 16 - 1
vue.config.js

@@ -1,3 +1,18 @@
 module.exports = {
-  publicPath: ''
+  publicPath: '', // 相对路径
+
+  // webpack 链式配置
+  chainWebpack: config => {
+    // 移除 prefetch 插件
+    config.plugins.delete('prefetch')
+  },
+
+  css: {
+    loaderOptions: {
+      sass: {
+        // scss公共变量
+        data: `@import "@/assets/scss/variables.scss";`
+      }
+    }
+  }
 }