[release]4.1.0 (#211)
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="app">
 | 
			
		||||
    <router-view/>
 | 
			
		||||
    <router-view />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,10 @@
 | 
			
		||||
import request from '@/utils/request'
 | 
			
		||||
 | 
			
		||||
export function login(username, password) {
 | 
			
		||||
export function login(data) {
 | 
			
		||||
  return request({
 | 
			
		||||
    url: '/user/login',
 | 
			
		||||
    method: 'post',
 | 
			
		||||
    data: {
 | 
			
		||||
      username,
 | 
			
		||||
      password
 | 
			
		||||
    }
 | 
			
		||||
    data
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
  <el-breadcrumb class="app-breadcrumb" separator="/">
 | 
			
		||||
    <transition-group name="breadcrumb">
 | 
			
		||||
      <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
 | 
			
		||||
        <span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
 | 
			
		||||
        <span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
 | 
			
		||||
        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
 | 
			
		||||
      </el-breadcrumb-item>
 | 
			
		||||
    </transition-group>
 | 
			
		||||
@@ -28,15 +28,23 @@ export default {
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    getBreadcrumb() {
 | 
			
		||||
      let matched = this.$route.matched.filter(item => item.name)
 | 
			
		||||
 | 
			
		||||
      // only show routes with meta.title
 | 
			
		||||
      let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
 | 
			
		||||
      const first = matched[0]
 | 
			
		||||
      if (first && first.name !== 'dashboard') {
 | 
			
		||||
 | 
			
		||||
      if (!this.isDashboard(first)) {
 | 
			
		||||
        matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
 | 
			
		||||
    },
 | 
			
		||||
    isDashboard(route) {
 | 
			
		||||
      const name = route && route.name
 | 
			
		||||
      if (!name) {
 | 
			
		||||
        return false
 | 
			
		||||
      }
 | 
			
		||||
      return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
 | 
			
		||||
    },
 | 
			
		||||
    pathCompile(path) {
 | 
			
		||||
      // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
 | 
			
		||||
      const { params } = this.$route
 | 
			
		||||
@@ -55,15 +63,16 @@ export default {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
			
		||||
  .app-breadcrumb.el-breadcrumb {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    line-height: 50px;
 | 
			
		||||
    margin-left: 10px;
 | 
			
		||||
    .no-redirect {
 | 
			
		||||
      color: #97a8be;
 | 
			
		||||
      cursor: text;
 | 
			
		||||
    }
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.app-breadcrumb.el-breadcrumb {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  line-height: 50px;
 | 
			
		||||
  margin-left: 8px;
 | 
			
		||||
 | 
			
		||||
  .no-redirect {
 | 
			
		||||
    color: #97a8be;
 | 
			
		||||
    cursor: text;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
  <div style="padding: 0 15px;" @click="toggleClick">
 | 
			
		||||
    <svg
 | 
			
		||||
      :class="{'is-active':isActive}"
 | 
			
		||||
      class="hamburger"
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
      xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
      width="64"
 | 
			
		||||
      height="64"
 | 
			
		||||
      @click="toggleClick">
 | 
			
		||||
    >
 | 
			
		||||
      <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
 | 
			
		||||
    </svg>
 | 
			
		||||
  </div>
 | 
			
		||||
@@ -20,10 +20,11 @@ export default {
 | 
			
		||||
    isActive: {
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      default: false
 | 
			
		||||
    },
 | 
			
		||||
    toggleClick: {
 | 
			
		||||
      type: Function,
 | 
			
		||||
      default: null
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    toggleClick() {
 | 
			
		||||
      this.$emit('toggleClick')
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -32,10 +33,11 @@ export default {
 | 
			
		||||
<style scoped>
 | 
			
		||||
.hamburger {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
  width: 20px;
 | 
			
		||||
  height: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hamburger.is-active {
 | 
			
		||||
  transform: rotate(180deg);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <svg :class="svgClass" aria-hidden="true" v-on="$listeners">
 | 
			
		||||
    <use :xlink:href="iconName"/>
 | 
			
		||||
    <use :xlink:href="iconName" />
 | 
			
		||||
  </svg>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import Vue from 'vue'
 | 
			
		||||
import SvgIcon from '@/components/SvgIcon' // svg组件
 | 
			
		||||
import SvgIcon from '@/components/SvgIcon'// svg component
 | 
			
		||||
 | 
			
		||||
// register globally
 | 
			
		||||
Vue.component('svg-icon', SvgIcon)
 | 
			
		||||
 | 
			
		||||
const requireAll = requireContext => requireContext.keys().map(requireContext)
 | 
			
		||||
const req = require.context('./svg', false, /\.svg$/)
 | 
			
		||||
const requireAll = requireContext => requireContext.keys().map(requireContext)
 | 
			
		||||
requireAll(req)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								src/icons/svg/dashboard.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 2.3 KiB  | 
@@ -1 +1 @@
 | 
			
		||||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
 | 
			
		||||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB  | 
@@ -1 +1 @@
 | 
			
		||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><g><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></g></svg>
 | 
			
		||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 292 B After Width: | Height: | Size: 285 B  | 
@@ -1 +1 @@
 | 
			
		||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><g><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></g></svg>
 | 
			
		||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 604 B After Width: | Height: | Size: 597 B  | 
@@ -1,9 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <section class="app-main">
 | 
			
		||||
    <transition name="fade-transform" mode="out-in">
 | 
			
		||||
      <!-- or name="fade" -->
 | 
			
		||||
      <!-- <router-view :key="key"></router-view> -->
 | 
			
		||||
      <router-view/>
 | 
			
		||||
      <router-view :key="key" />
 | 
			
		||||
    </transition>
 | 
			
		||||
  </section>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -12,9 +10,9 @@
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'AppMain',
 | 
			
		||||
  computed: {
 | 
			
		||||
    // key() {
 | 
			
		||||
    //   return this.$route.name !== undefined ? this.$route.name + +new Date() : this.$route + +new Date()
 | 
			
		||||
    // }
 | 
			
		||||
    key() {
 | 
			
		||||
      return this.$route.fullPath
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -23,7 +21,11 @@ export default {
 | 
			
		||||
.app-main {
 | 
			
		||||
  /*50 = navbar  */
 | 
			
		||||
  min-height: calc(100vh - 50px);
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
.fixed-header+.app-main {
 | 
			
		||||
  padding-top: 50px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										139
									
								
								src/layout/components/Navbar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,139 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="navbar">
 | 
			
		||||
    <hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
 | 
			
		||||
 | 
			
		||||
    <breadcrumb class="breadcrumb-container" />
 | 
			
		||||
 | 
			
		||||
    <div class="right-menu">
 | 
			
		||||
      <el-dropdown class="avatar-container" trigger="click">
 | 
			
		||||
        <div class="avatar-wrapper">
 | 
			
		||||
          <img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
 | 
			
		||||
          <i class="el-icon-caret-bottom" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-dropdown-menu slot="dropdown" class="user-dropdown">
 | 
			
		||||
          <router-link to="/">
 | 
			
		||||
            <el-dropdown-item>
 | 
			
		||||
              Home
 | 
			
		||||
            </el-dropdown-item>
 | 
			
		||||
          </router-link>
 | 
			
		||||
          <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
 | 
			
		||||
            <el-dropdown-item>Github</el-dropdown-item>
 | 
			
		||||
          </a>
 | 
			
		||||
          <a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
 | 
			
		||||
            <el-dropdown-item>Docs</el-dropdown-item>
 | 
			
		||||
          </a>
 | 
			
		||||
          <el-dropdown-item divided>
 | 
			
		||||
            <span style="display:block;" @click="logout">Log Out</span>
 | 
			
		||||
          </el-dropdown-item>
 | 
			
		||||
        </el-dropdown-menu>
 | 
			
		||||
      </el-dropdown>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { mapGetters } from 'vuex'
 | 
			
		||||
import Breadcrumb from '@/components/Breadcrumb'
 | 
			
		||||
import Hamburger from '@/components/Hamburger'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    Breadcrumb,
 | 
			
		||||
    Hamburger
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    ...mapGetters([
 | 
			
		||||
      'sidebar',
 | 
			
		||||
      'avatar'
 | 
			
		||||
    ])
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    toggleSideBar() {
 | 
			
		||||
      this.$store.dispatch('app/toggleSideBar')
 | 
			
		||||
    },
 | 
			
		||||
    async logout() {
 | 
			
		||||
      await this.$store.dispatch('user/logout')
 | 
			
		||||
      this.$router.push(`/login?redirect=${this.$route.fullPath}`)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.navbar {
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  box-shadow: 0 1px 4px rgba(0,21,41,.08);
 | 
			
		||||
 | 
			
		||||
  .hamburger-container {
 | 
			
		||||
    line-height: 46px;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    float: left;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    transition: background .3s;
 | 
			
		||||
    -webkit-tap-highlight-color:transparent;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      background: rgba(0, 0, 0, .025)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .breadcrumb-container {
 | 
			
		||||
    float: left;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .right-menu {
 | 
			
		||||
    float: right;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    line-height: 50px;
 | 
			
		||||
 | 
			
		||||
    &:focus {
 | 
			
		||||
      outline: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .right-menu-item {
 | 
			
		||||
      display: inline-block;
 | 
			
		||||
      padding: 0 8px;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      font-size: 18px;
 | 
			
		||||
      color: #5a5e66;
 | 
			
		||||
      vertical-align: text-bottom;
 | 
			
		||||
 | 
			
		||||
      &.hover-effect {
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        transition: background .3s;
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          background: rgba(0, 0, 0, .025)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .avatar-container {
 | 
			
		||||
      margin-right: 30px;
 | 
			
		||||
 | 
			
		||||
      .avatar-wrapper {
 | 
			
		||||
        margin-top: 5px;
 | 
			
		||||
        position: relative;
 | 
			
		||||
 | 
			
		||||
        .user-avatar {
 | 
			
		||||
          cursor: pointer;
 | 
			
		||||
          width: 40px;
 | 
			
		||||
          height: 40px;
 | 
			
		||||
          border-radius: 10px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-icon-caret-bottom {
 | 
			
		||||
          cursor: pointer;
 | 
			
		||||
          position: absolute;
 | 
			
		||||
          right: -20px;
 | 
			
		||||
          top: 25px;
 | 
			
		||||
          font-size: 12px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										26
									
								
								src/layout/components/Sidebar/FixiOSBug.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,26 @@
 | 
			
		||||
export default {
 | 
			
		||||
  computed: {
 | 
			
		||||
    device() {
 | 
			
		||||
      return this.$store.state.app.device
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    // In order to fix the click on menu on the ios device will trigger the mouseleave bug
 | 
			
		||||
    // https://github.com/PanJiaChen/vue-element-admin/issues/1135
 | 
			
		||||
    this.fixBugIniOS()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    fixBugIniOS() {
 | 
			
		||||
      const $subMenu = this.$refs.subMenu
 | 
			
		||||
      if ($subMenu) {
 | 
			
		||||
        const handleMouseleave = $subMenu.handleMouseleave
 | 
			
		||||
        $subMenu.handleMouseleave = (e) => {
 | 
			
		||||
          if (this.device === 'mobile') {
 | 
			
		||||
            return
 | 
			
		||||
          }
 | 
			
		||||
          handleMouseleave(e)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,18 +3,17 @@ export default {
 | 
			
		||||
  name: 'MenuItem',
 | 
			
		||||
  functional: true,
 | 
			
		||||
  props: {
 | 
			
		||||
    meta: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
      default: () => {
 | 
			
		||||
        return {
 | 
			
		||||
          title: '',
 | 
			
		||||
          icon: ''
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    icon: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: ''
 | 
			
		||||
    },
 | 
			
		||||
    title: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: ''
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  render(h, context) {
 | 
			
		||||
    const { icon, title } = context.props.meta
 | 
			
		||||
    const { icon, title } = context.props
 | 
			
		||||
    const vnodes = []
 | 
			
		||||
 | 
			
		||||
    if (icon) {
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <!-- eslint-disable vue/require-component-is -->
 | 
			
		||||
  <component v-bind="linkProps(to)">
 | 
			
		||||
    <slot/>
 | 
			
		||||
    <slot />
 | 
			
		||||
  </component>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										82
									
								
								src/layout/components/Sidebar/Logo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,82 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="sidebar-logo-container" :class="{'collapse':collapse}">
 | 
			
		||||
    <transition name="sidebarLogoFade">
 | 
			
		||||
      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
 | 
			
		||||
        <img v-if="logo" :src="logo" class="sidebar-logo">
 | 
			
		||||
        <h1 v-else class="sidebar-title">{{ title }} </h1>
 | 
			
		||||
      </router-link>
 | 
			
		||||
      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
 | 
			
		||||
        <img v-if="logo" :src="logo" class="sidebar-logo">
 | 
			
		||||
        <h1 class="sidebar-title">{{ title }} </h1>
 | 
			
		||||
      </router-link>
 | 
			
		||||
    </transition>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'SidebarLogo',
 | 
			
		||||
  props: {
 | 
			
		||||
    collapse: {
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      required: true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      title: 'Vue Admin Template',
 | 
			
		||||
      logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.sidebarLogoFade-enter-active {
 | 
			
		||||
  transition: opacity 1.5s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidebarLogoFade-enter,
 | 
			
		||||
.sidebarLogoFade-leave-to {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidebar-logo-container {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  line-height: 50px;
 | 
			
		||||
  background: #2b2f3a;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
 | 
			
		||||
  & .sidebar-logo-link {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
 | 
			
		||||
    & .sidebar-logo {
 | 
			
		||||
      width: 32px;
 | 
			
		||||
      height: 32px;
 | 
			
		||||
      vertical-align: middle;
 | 
			
		||||
      margin-right: 12px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    & .sidebar-title {
 | 
			
		||||
      display: inline-block;
 | 
			
		||||
      margin: 0;
 | 
			
		||||
      color: #fff;
 | 
			
		||||
      font-weight: 600;
 | 
			
		||||
      line-height: 50px;
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
 | 
			
		||||
      vertical-align: middle;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.collapse {
 | 
			
		||||
    .sidebar-logo {
 | 
			
		||||
      margin-right: 0px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,27 +1,26 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div v-if="!item.hidden" class="menu-wrapper">
 | 
			
		||||
 | 
			
		||||
    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
 | 
			
		||||
      <app-link :to="resolvePath(onlyOneChild.path)">
 | 
			
		||||
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
 | 
			
		||||
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
 | 
			
		||||
          <item :meta="Object.assign({},item.meta,onlyOneChild.meta)" />
 | 
			
		||||
          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
 | 
			
		||||
        </el-menu-item>
 | 
			
		||||
      </app-link>
 | 
			
		||||
    </template>
 | 
			
		||||
 | 
			
		||||
    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
 | 
			
		||||
      <template slot="title">
 | 
			
		||||
        <item :meta="item.meta" />
 | 
			
		||||
        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
 | 
			
		||||
      </template>
 | 
			
		||||
      <sidebar-item
 | 
			
		||||
        v-for="child in item.children"
 | 
			
		||||
        :key="child.path"
 | 
			
		||||
        :is-nest="true"
 | 
			
		||||
        :item="child"
 | 
			
		||||
        :key="child.path"
 | 
			
		||||
        :base-path="resolvePath(child.path)"
 | 
			
		||||
        class="nest-menu" />
 | 
			
		||||
        class="nest-menu"
 | 
			
		||||
      />
 | 
			
		||||
    </el-submenu>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -30,10 +29,12 @@ import path from 'path'
 | 
			
		||||
import { isExternal } from '@/utils/validate'
 | 
			
		||||
import Item from './Item'
 | 
			
		||||
import AppLink from './Link'
 | 
			
		||||
import FixiOSBug from './FixiOSBug'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'SidebarItem',
 | 
			
		||||
  components: { Item, AppLink },
 | 
			
		||||
  mixins: [FixiOSBug],
 | 
			
		||||
  props: {
 | 
			
		||||
    // route object
 | 
			
		||||
    item: {
 | 
			
		||||
@@ -84,6 +85,9 @@ export default {
 | 
			
		||||
      if (isExternal(routePath)) {
 | 
			
		||||
        return routePath
 | 
			
		||||
      }
 | 
			
		||||
      if (isExternal(this.basePath)) {
 | 
			
		||||
        return this.basePath
 | 
			
		||||
      }
 | 
			
		||||
      return path.resolve(this.basePath, routePath)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
							
								
								
									
										54
									
								
								src/layout/components/Sidebar/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,54 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div :class="{'has-logo':showLogo}">
 | 
			
		||||
    <logo v-if="showLogo" :collapse="isCollapse" />
 | 
			
		||||
    <el-scrollbar wrap-class="scrollbar-wrapper">
 | 
			
		||||
      <el-menu
 | 
			
		||||
        :default-active="activeMenu"
 | 
			
		||||
        :collapse="isCollapse"
 | 
			
		||||
        :background-color="variables.menuBg"
 | 
			
		||||
        :text-color="variables.menuText"
 | 
			
		||||
        :unique-opened="false"
 | 
			
		||||
        :active-text-color="variables.menuActiveText"
 | 
			
		||||
        :collapse-transition="false"
 | 
			
		||||
        mode="vertical"
 | 
			
		||||
      >
 | 
			
		||||
        <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
 | 
			
		||||
      </el-menu>
 | 
			
		||||
    </el-scrollbar>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { mapGetters } from 'vuex'
 | 
			
		||||
import Logo from './Logo'
 | 
			
		||||
import SidebarItem from './SidebarItem'
 | 
			
		||||
import variables from '@/styles/variables.scss'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: { SidebarItem, Logo },
 | 
			
		||||
  computed: {
 | 
			
		||||
    ...mapGetters([
 | 
			
		||||
      'permission_routes',
 | 
			
		||||
      'sidebar'
 | 
			
		||||
    ]),
 | 
			
		||||
    activeMenu() {
 | 
			
		||||
      const route = this.$route
 | 
			
		||||
      const { meta, path } = route
 | 
			
		||||
      // if set path, the sidebar will highlight the path you set
 | 
			
		||||
      if (meta.activeMenu) {
 | 
			
		||||
        return meta.activeMenu
 | 
			
		||||
      }
 | 
			
		||||
      return path
 | 
			
		||||
    },
 | 
			
		||||
    showLogo() {
 | 
			
		||||
      return this.$store.state.settings.sidebarLogo
 | 
			
		||||
    },
 | 
			
		||||
    variables() {
 | 
			
		||||
      return variables
 | 
			
		||||
    },
 | 
			
		||||
    isCollapse() {
 | 
			
		||||
      return !this.sidebar.opened
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,10 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div :class="classObj" class="app-wrapper">
 | 
			
		||||
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
 | 
			
		||||
    <sidebar class="sidebar-container"/>
 | 
			
		||||
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
 | 
			
		||||
    <sidebar class="sidebar-container" />
 | 
			
		||||
    <div class="main-container">
 | 
			
		||||
      <navbar/>
 | 
			
		||||
      <app-main/>
 | 
			
		||||
      <div :class="{'fixed-header':fixedHeader}">
 | 
			
		||||
        <navbar />
 | 
			
		||||
      </div>
 | 
			
		||||
      <app-main />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -28,6 +30,9 @@ export default {
 | 
			
		||||
    device() {
 | 
			
		||||
      return this.$store.state.app.device
 | 
			
		||||
    },
 | 
			
		||||
    fixedHeader() {
 | 
			
		||||
      return this.$store.state.settings.fixedHeader
 | 
			
		||||
    },
 | 
			
		||||
    classObj() {
 | 
			
		||||
      return {
 | 
			
		||||
        hideSidebar: !this.sidebar.opened,
 | 
			
		||||
@@ -45,8 +50,10 @@ export default {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
			
		||||
  @import "src/styles/mixin.scss";
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
  @import "~@/styles/mixin.scss";
 | 
			
		||||
  @import "~@/styles/variables.scss";
 | 
			
		||||
 | 
			
		||||
  .app-wrapper {
 | 
			
		||||
    @include clearfix;
 | 
			
		||||
    position: relative;
 | 
			
		||||
@@ -66,4 +73,21 @@ export default {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    z-index: 999;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .fixed-header {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    z-index: 9;
 | 
			
		||||
    width: calc(100% - #{$sideBarWidth});
 | 
			
		||||
    transition: width 0.28s;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .hideSidebar .fixed-header {
 | 
			
		||||
    width: calc(100% - 54px)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .mobile .fixed-header {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										45
									
								
								src/layout/mixin/ResizeHandler.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,45 @@
 | 
			
		||||
import store from '@/store'
 | 
			
		||||
 | 
			
		||||
const { body } = document
 | 
			
		||||
const WIDTH = 992 // refer to Bootstrap's responsive design
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  watch: {
 | 
			
		||||
    $route(route) {
 | 
			
		||||
      if (this.device === 'mobile' && this.sidebar.opened) {
 | 
			
		||||
        store.dispatch('app/closeSideBar', { withoutAnimation: false })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  beforeMount() {
 | 
			
		||||
    window.addEventListener('resize', this.$_resizeHandler)
 | 
			
		||||
  },
 | 
			
		||||
  beforeDestroy() {
 | 
			
		||||
    window.removeEventListener('resize', this.$_resizeHandler)
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    const isMobile = this.$_isMobile()
 | 
			
		||||
    if (isMobile) {
 | 
			
		||||
      store.dispatch('app/toggleDevice', 'mobile')
 | 
			
		||||
      store.dispatch('app/closeSideBar', { withoutAnimation: true })
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    // use $_ for mixins properties
 | 
			
		||||
    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
 | 
			
		||||
    $_isMobile() {
 | 
			
		||||
      const rect = body.getBoundingClientRect()
 | 
			
		||||
      return rect.width - 1 < WIDTH
 | 
			
		||||
    },
 | 
			
		||||
    $_resizeHandler() {
 | 
			
		||||
      if (!document.hidden) {
 | 
			
		||||
        const isMobile = this.$_isMobile()
 | 
			
		||||
        store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
 | 
			
		||||
 | 
			
		||||
        if (isMobile) {
 | 
			
		||||
          store.dispatch('app/closeSideBar', { withoutAnimation: true })
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/main.js
									
									
									
									
									
								
							
							
						
						@@ -16,15 +16,15 @@ import '@/icons' // icon
 | 
			
		||||
import '@/permission' // permission control
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This project originally used easy-mock to simulate data,
 | 
			
		||||
 * but its official service is very unstable,
 | 
			
		||||
 * and you can build your own service if you need it.
 | 
			
		||||
 * So here I use Mock.js for local emulation,
 | 
			
		||||
 * it will intercept your request, so you won't see the request in the network.
 | 
			
		||||
 * If you remove `../mock` it will automatically request easy-mock data.
 | 
			
		||||
 * If you don't want to use mock-server
 | 
			
		||||
 * you want to use mockjs for request interception
 | 
			
		||||
 * you can execute:
 | 
			
		||||
 *
 | 
			
		||||
 * import { mockXHR } from '../mock'
 | 
			
		||||
 * mockXHR()
 | 
			
		||||
 */
 | 
			
		||||
import '../mock' // simulation data
 | 
			
		||||
 | 
			
		||||
// set ElementUI lang to EN
 | 
			
		||||
Vue.use(ElementUI, { locale })
 | 
			
		||||
 | 
			
		||||
Vue.config.productionTip = false
 | 
			
		||||
 
 | 
			
		||||
@@ -1,43 +1,74 @@
 | 
			
		||||
import router from './router'
 | 
			
		||||
import store from './store'
 | 
			
		||||
import { Message } from 'element-ui'
 | 
			
		||||
import NProgress from 'nprogress' // progress bar
 | 
			
		||||
import 'nprogress/nprogress.css' // progress bar style
 | 
			
		||||
import { Message } from 'element-ui'
 | 
			
		||||
import { getToken } from '@/utils/auth' // getToken from cookie
 | 
			
		||||
import { getToken } from '@/utils/auth' // get token from cookie
 | 
			
		||||
import getPageTitle from '@/utils/get-page-title'
 | 
			
		||||
 | 
			
		||||
NProgress.configure({ showSpinner: false })// NProgress configuration
 | 
			
		||||
NProgress.configure({ showSpinner: false }) // NProgress Configuration
 | 
			
		||||
 | 
			
		||||
const whiteList = ['/login'] // 不重定向白名单
 | 
			
		||||
router.beforeEach((to, from, next) => {
 | 
			
		||||
const whiteList = ['/login'] // no redirect whitelist
 | 
			
		||||
 | 
			
		||||
router.beforeEach(async(to, from, next) => {
 | 
			
		||||
  // start progress bar
 | 
			
		||||
  NProgress.start()
 | 
			
		||||
  if (getToken()) {
 | 
			
		||||
 | 
			
		||||
  // set page title
 | 
			
		||||
  document.title = getPageTitle(to.meta.title)
 | 
			
		||||
 | 
			
		||||
  // determine whether the user has logged in
 | 
			
		||||
  const hasToken = getToken()
 | 
			
		||||
 | 
			
		||||
  if (hasToken) {
 | 
			
		||||
    if (to.path === '/login') {
 | 
			
		||||
      // if is logged in, redirect to the home page
 | 
			
		||||
      next({ path: '/' })
 | 
			
		||||
      NProgress.done() // if current page is dashboard will not trigger	afterEach hook, so manually handle it
 | 
			
		||||
      NProgress.done()
 | 
			
		||||
    } else {
 | 
			
		||||
      if (store.getters.roles.length === 0) {
 | 
			
		||||
        store.dispatch('GetInfo').then(res => { // 拉取用户信息
 | 
			
		||||
          next()
 | 
			
		||||
        }).catch((err) => {
 | 
			
		||||
          store.dispatch('FedLogOut').then(() => {
 | 
			
		||||
            Message.error(err || 'Verification failed, please login again')
 | 
			
		||||
            next({ path: '/' })
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
      } else {
 | 
			
		||||
      // determine whether the user has obtained his permission roles through getInfo
 | 
			
		||||
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
 | 
			
		||||
      if (hasRoles) {
 | 
			
		||||
        next()
 | 
			
		||||
      } else {
 | 
			
		||||
        try {
 | 
			
		||||
          // get user info
 | 
			
		||||
          // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
 | 
			
		||||
          const { roles } = await store.dispatch('user/getInfo')
 | 
			
		||||
 | 
			
		||||
          // generate accessible routes map based on roles
 | 
			
		||||
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
 | 
			
		||||
 | 
			
		||||
          // dynamically add accessible routes
 | 
			
		||||
          router.addRoutes(accessRoutes)
 | 
			
		||||
 | 
			
		||||
          // hack method to ensure that addRoutes is complete
 | 
			
		||||
          // set the replace: true, so the navigation will not leave a history record
 | 
			
		||||
          next({ ...to, replace: true })
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
          // remove token and go to login page to re-login
 | 
			
		||||
          await store.dispatch('user/resetToken')
 | 
			
		||||
          Message.error(error || 'Has Error')
 | 
			
		||||
          next(`/login?redirect=${to.path}`)
 | 
			
		||||
          NProgress.done()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    /* has no token*/
 | 
			
		||||
 | 
			
		||||
    if (whiteList.indexOf(to.path) !== -1) {
 | 
			
		||||
      // in the free login whitelist, go directly
 | 
			
		||||
      next()
 | 
			
		||||
    } else {
 | 
			
		||||
      next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
 | 
			
		||||
      // other pages that do not have permission to access are redirected to the login page.
 | 
			
		||||
      next(`/login?redirect=${to.path}`)
 | 
			
		||||
      NProgress.done()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
router.afterEach(() => {
 | 
			
		||||
  NProgress.done() // 结束Progress
 | 
			
		||||
  // finish progress bar
 | 
			
		||||
  NProgress.done()
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,40 +1,57 @@
 | 
			
		||||
import Vue from 'vue'
 | 
			
		||||
import Router from 'vue-router'
 | 
			
		||||
 | 
			
		||||
// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
 | 
			
		||||
// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading
 | 
			
		||||
 | 
			
		||||
Vue.use(Router)
 | 
			
		||||
 | 
			
		||||
/* Layout */
 | 
			
		||||
import Layout from '../views/layout/Layout'
 | 
			
		||||
import Layout from '@/layout'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* hidden: true                   if `hidden:true` will not show in the sidebar(default is false)
 | 
			
		||||
* alwaysShow: true               if set true, will always show the root menu, whatever its child routes length
 | 
			
		||||
*                                if not set alwaysShow, only more than one route under the children
 | 
			
		||||
*                                it will becomes nested mode, otherwise not show the root menu
 | 
			
		||||
* redirect: noredirect           if `redirect:noredirect` will no redirect in the breadcrumb
 | 
			
		||||
* name:'router-name'             the name is used by <keep-alive> (must set!!!)
 | 
			
		||||
* meta : {
 | 
			
		||||
    title: 'title'               the name show in subMenu and breadcrumb (recommend set)
 | 
			
		||||
 * Note: sub-menu only appear when route children.length >= 1
 | 
			
		||||
 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
 | 
			
		||||
 *
 | 
			
		||||
 * hidden: true                   if set true, item will not show in the sidebar(default is false)
 | 
			
		||||
 * alwaysShow: true               if set true, will always show the root menu
 | 
			
		||||
 *                                if not set alwaysShow, when item has more than one children route,
 | 
			
		||||
 *                                it will becomes nested mode, otherwise not show the root menu
 | 
			
		||||
 * redirect: noRedirect           if set noRedirect will no redirect in the breadcrumb
 | 
			
		||||
 * name:'router-name'             the name is used by <keep-alive> (must set!!!)
 | 
			
		||||
 * meta : {
 | 
			
		||||
    roles: ['admin','editor']    control the page roles (you can set multiple roles)
 | 
			
		||||
    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
 | 
			
		||||
    icon: 'svg-name'             the icon show in the sidebar
 | 
			
		||||
    breadcrumb: false            if false, the item will hidden in breadcrumb(default is true)
 | 
			
		||||
    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
 | 
			
		||||
    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
 | 
			
		||||
  }
 | 
			
		||||
**/
 | 
			
		||||
export const constantRouterMap = [
 | 
			
		||||
  { path: '/login', component: () => import('@/views/login/index'), hidden: true },
 | 
			
		||||
  { path: '/404', component: () => import('@/views/404'), hidden: true },
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * constantRoutes
 | 
			
		||||
 * a base page that does not have permission requirements
 | 
			
		||||
 * all roles can be accessed
 | 
			
		||||
 */
 | 
			
		||||
export const constantRoutes = [
 | 
			
		||||
  {
 | 
			
		||||
    path: '/login',
 | 
			
		||||
    component: () => import('@/views/login/index'),
 | 
			
		||||
    hidden: true
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    path: '/404',
 | 
			
		||||
    component: () => import('@/views/404'),
 | 
			
		||||
    hidden: true
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    path: '/',
 | 
			
		||||
    component: Layout,
 | 
			
		||||
    redirect: '/dashboard',
 | 
			
		||||
    name: 'Dashboard',
 | 
			
		||||
    hidden: true,
 | 
			
		||||
    children: [{
 | 
			
		||||
      path: 'dashboard',
 | 
			
		||||
      component: () => import('@/views/dashboard/index')
 | 
			
		||||
      name: 'Dashboard',
 | 
			
		||||
      component: () => import('@/views/dashboard/index'),
 | 
			
		||||
      meta: { title: 'Dashboard', icon: 'dashboard' }
 | 
			
		||||
    }]
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
@@ -71,8 +88,14 @@ export const constantRouterMap = [
 | 
			
		||||
        meta: { title: 'Form', icon: 'form' }
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * asyncRoutes
 | 
			
		||||
 * the routes that need to be dynamically loaded based on user roles
 | 
			
		||||
 */
 | 
			
		||||
export const asyncRoutes = [
 | 
			
		||||
  {
 | 
			
		||||
    path: '/nested',
 | 
			
		||||
    component: Layout,
 | 
			
		||||
@@ -142,11 +165,22 @@ export const constantRouterMap = [
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  // 404 page must be placed at the end !!!
 | 
			
		||||
  { path: '*', redirect: '/404', hidden: true }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export default new Router({
 | 
			
		||||
  // mode: 'history', //后端支持可开
 | 
			
		||||
const createRouter = () => new Router({
 | 
			
		||||
  // mode: 'history', // require service support
 | 
			
		||||
  scrollBehavior: () => ({ y: 0 }),
 | 
			
		||||
  routes: constantRouterMap
 | 
			
		||||
  routes: constantRoutes
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const router = createRouter()
 | 
			
		||||
 | 
			
		||||
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
 | 
			
		||||
export function resetRouter() {
 | 
			
		||||
  const newRouter = createRouter()
 | 
			
		||||
  router.matcher = newRouter.matcher // reset router
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default router
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								src/settings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,16 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
 | 
			
		||||
  title: 'Vue Admin Template',
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @type {boolean} true | false
 | 
			
		||||
   * @description Whether fix the header
 | 
			
		||||
   */
 | 
			
		||||
  fixedHeader: false,
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @type {boolean} true | false
 | 
			
		||||
   * @description Whether show the logo in sidebar
 | 
			
		||||
   */
 | 
			
		||||
  sidebarLogo: false
 | 
			
		||||
}
 | 
			
		||||
@@ -4,6 +4,7 @@ const getters = {
 | 
			
		||||
  token: state => state.user.token,
 | 
			
		||||
  avatar: state => state.user.avatar,
 | 
			
		||||
  name: state => state.user.name,
 | 
			
		||||
  roles: state => state.user.roles
 | 
			
		||||
  roles: state => state.user.roles,
 | 
			
		||||
  permission_routes: state => state.permission.routes
 | 
			
		||||
}
 | 
			
		||||
export default getters
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,18 @@
 | 
			
		||||
import Vue from 'vue'
 | 
			
		||||
import Vuex from 'vuex'
 | 
			
		||||
import app from './modules/app'
 | 
			
		||||
import user from './modules/user'
 | 
			
		||||
import getters from './getters'
 | 
			
		||||
import app from './modules/app'
 | 
			
		||||
import permission from './modules/permission'
 | 
			
		||||
import settings from './modules/settings'
 | 
			
		||||
import user from './modules/user'
 | 
			
		||||
 | 
			
		||||
Vue.use(Vuex)
 | 
			
		||||
 | 
			
		||||
const store = new Vuex.Store({
 | 
			
		||||
  modules: {
 | 
			
		||||
    app,
 | 
			
		||||
    permission,
 | 
			
		||||
    settings,
 | 
			
		||||
    user
 | 
			
		||||
  },
 | 
			
		||||
  getters
 | 
			
		||||
 
 | 
			
		||||
@@ -1,43 +1,48 @@
 | 
			
		||||
import Cookies from 'js-cookie'
 | 
			
		||||
 | 
			
		||||
const app = {
 | 
			
		||||
  state: {
 | 
			
		||||
    sidebar: {
 | 
			
		||||
      opened: !+Cookies.get('sidebarStatus'),
 | 
			
		||||
      withoutAnimation: false
 | 
			
		||||
    },
 | 
			
		||||
    device: 'desktop'
 | 
			
		||||
const state = {
 | 
			
		||||
  sidebar: {
 | 
			
		||||
    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
 | 
			
		||||
    withoutAnimation: false
 | 
			
		||||
  },
 | 
			
		||||
  mutations: {
 | 
			
		||||
    TOGGLE_SIDEBAR: state => {
 | 
			
		||||
      if (state.sidebar.opened) {
 | 
			
		||||
        Cookies.set('sidebarStatus', 1)
 | 
			
		||||
      } else {
 | 
			
		||||
        Cookies.set('sidebarStatus', 0)
 | 
			
		||||
      }
 | 
			
		||||
      state.sidebar.opened = !state.sidebar.opened
 | 
			
		||||
      state.sidebar.withoutAnimation = false
 | 
			
		||||
    },
 | 
			
		||||
    CLOSE_SIDEBAR: (state, withoutAnimation) => {
 | 
			
		||||
  device: 'desktop'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mutations = {
 | 
			
		||||
  TOGGLE_SIDEBAR: state => {
 | 
			
		||||
    state.sidebar.opened = !state.sidebar.opened
 | 
			
		||||
    state.sidebar.withoutAnimation = false
 | 
			
		||||
    if (state.sidebar.opened) {
 | 
			
		||||
      Cookies.set('sidebarStatus', 1)
 | 
			
		||||
      state.sidebar.opened = false
 | 
			
		||||
      state.sidebar.withoutAnimation = withoutAnimation
 | 
			
		||||
    },
 | 
			
		||||
    TOGGLE_DEVICE: (state, device) => {
 | 
			
		||||
      state.device = device
 | 
			
		||||
    } else {
 | 
			
		||||
      Cookies.set('sidebarStatus', 0)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    ToggleSideBar: ({ commit }) => {
 | 
			
		||||
      commit('TOGGLE_SIDEBAR')
 | 
			
		||||
    },
 | 
			
		||||
    CloseSideBar({ commit }, { withoutAnimation }) {
 | 
			
		||||
      commit('CLOSE_SIDEBAR', withoutAnimation)
 | 
			
		||||
    },
 | 
			
		||||
    ToggleDevice({ commit }, device) {
 | 
			
		||||
      commit('TOGGLE_DEVICE', device)
 | 
			
		||||
    }
 | 
			
		||||
  CLOSE_SIDEBAR: (state, withoutAnimation) => {
 | 
			
		||||
    Cookies.set('sidebarStatus', 0)
 | 
			
		||||
    state.sidebar.opened = false
 | 
			
		||||
    state.sidebar.withoutAnimation = withoutAnimation
 | 
			
		||||
  },
 | 
			
		||||
  TOGGLE_DEVICE: (state, device) => {
 | 
			
		||||
    state.device = device
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default app
 | 
			
		||||
const actions = {
 | 
			
		||||
  toggleSideBar({ commit }) {
 | 
			
		||||
    commit('TOGGLE_SIDEBAR')
 | 
			
		||||
  },
 | 
			
		||||
  closeSideBar({ commit }, { withoutAnimation }) {
 | 
			
		||||
    commit('CLOSE_SIDEBAR', withoutAnimation)
 | 
			
		||||
  },
 | 
			
		||||
  toggleDevice({ commit }, device) {
 | 
			
		||||
    commit('TOGGLE_DEVICE', device)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  namespaced: true,
 | 
			
		||||
  state,
 | 
			
		||||
  mutations,
 | 
			
		||||
  actions
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										69
									
								
								src/store/modules/permission.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,69 @@
 | 
			
		||||
import { asyncRoutes, constantRoutes } from '@/router'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Use meta.role to determine if the current user has permission
 | 
			
		||||
 * @param roles
 | 
			
		||||
 * @param route
 | 
			
		||||
 */
 | 
			
		||||
function hasPermission(roles, route) {
 | 
			
		||||
  if (route.meta && route.meta.roles) {
 | 
			
		||||
    return roles.some(role => route.meta.roles.includes(role))
 | 
			
		||||
  } else {
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Filter asynchronous routing tables by recursion
 | 
			
		||||
 * @param routes asyncRoutes
 | 
			
		||||
 * @param roles
 | 
			
		||||
 */
 | 
			
		||||
export function filterAsyncRoutes(routes, roles) {
 | 
			
		||||
  const res = []
 | 
			
		||||
 | 
			
		||||
  routes.forEach(route => {
 | 
			
		||||
    const tmp = { ...route }
 | 
			
		||||
    if (hasPermission(roles, tmp)) {
 | 
			
		||||
      if (tmp.children) {
 | 
			
		||||
        tmp.children = filterAsyncRoutes(tmp.children, roles)
 | 
			
		||||
      }
 | 
			
		||||
      res.push(tmp)
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const state = {
 | 
			
		||||
  routes: [],
 | 
			
		||||
  addRoutes: []
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mutations = {
 | 
			
		||||
  SET_ROUTES: (state, routes) => {
 | 
			
		||||
    state.addRoutes = routes
 | 
			
		||||
    state.routes = constantRoutes.concat(routes)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const actions = {
 | 
			
		||||
  generateRoutes({ commit }, roles) {
 | 
			
		||||
    return new Promise(resolve => {
 | 
			
		||||
      let accessedRoutes
 | 
			
		||||
      if (roles.includes('admin')) {
 | 
			
		||||
        accessedRoutes = asyncRoutes || []
 | 
			
		||||
      } else {
 | 
			
		||||
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
 | 
			
		||||
      }
 | 
			
		||||
      commit('SET_ROUTES', accessedRoutes)
 | 
			
		||||
      resolve(accessedRoutes)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  namespaced: true,
 | 
			
		||||
  state,
 | 
			
		||||
  mutations,
 | 
			
		||||
  actions
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								src/store/modules/settings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,31 @@
 | 
			
		||||
import defaultSettings from '@/settings'
 | 
			
		||||
 | 
			
		||||
const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
 | 
			
		||||
 | 
			
		||||
const state = {
 | 
			
		||||
  showSettings: showSettings,
 | 
			
		||||
  fixedHeader: fixedHeader,
 | 
			
		||||
  sidebarLogo: sidebarLogo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mutations = {
 | 
			
		||||
  CHANGE_SETTING: (state, { key, value }) => {
 | 
			
		||||
    if (state.hasOwnProperty(key)) {
 | 
			
		||||
      state[key] = value
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const actions = {
 | 
			
		||||
  changeSetting({ commit }, data) {
 | 
			
		||||
    commit('CHANGE_SETTING', data)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  namespaced: true,
 | 
			
		||||
  state,
 | 
			
		||||
  mutations,
 | 
			
		||||
  actions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1,87 +1,102 @@
 | 
			
		||||
import { login, logout, getInfo } from '@/api/login'
 | 
			
		||||
import { login, logout, getInfo } from '@/api/user'
 | 
			
		||||
import { getToken, setToken, removeToken } from '@/utils/auth'
 | 
			
		||||
import { resetRouter } from '@/router'
 | 
			
		||||
 | 
			
		||||
const user = {
 | 
			
		||||
  state: {
 | 
			
		||||
    token: getToken(),
 | 
			
		||||
    name: '',
 | 
			
		||||
    avatar: '',
 | 
			
		||||
    roles: []
 | 
			
		||||
const state = {
 | 
			
		||||
  token: getToken(),
 | 
			
		||||
  name: '',
 | 
			
		||||
  avatar: '',
 | 
			
		||||
  roles: []
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mutations = {
 | 
			
		||||
  SET_TOKEN: (state, token) => {
 | 
			
		||||
    state.token = token
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  mutations: {
 | 
			
		||||
    SET_TOKEN: (state, token) => {
 | 
			
		||||
      state.token = token
 | 
			
		||||
    },
 | 
			
		||||
    SET_NAME: (state, name) => {
 | 
			
		||||
      state.name = name
 | 
			
		||||
    },
 | 
			
		||||
    SET_AVATAR: (state, avatar) => {
 | 
			
		||||
      state.avatar = avatar
 | 
			
		||||
    },
 | 
			
		||||
    SET_ROLES: (state, roles) => {
 | 
			
		||||
      state.roles = roles
 | 
			
		||||
    }
 | 
			
		||||
  SET_NAME: (state, name) => {
 | 
			
		||||
    state.name = name
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  actions: {
 | 
			
		||||
    // 登录
 | 
			
		||||
    Login({ commit }, userInfo) {
 | 
			
		||||
      const username = userInfo.username.trim()
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        login(username, userInfo.password).then(response => {
 | 
			
		||||
          const data = response.data
 | 
			
		||||
          setToken(data.token)
 | 
			
		||||
          commit('SET_TOKEN', data.token)
 | 
			
		||||
          resolve()
 | 
			
		||||
        }).catch(error => {
 | 
			
		||||
          reject(error)
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 获取用户信息
 | 
			
		||||
    GetInfo({ commit, state }) {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        getInfo(state.token).then(response => {
 | 
			
		||||
          const data = response.data
 | 
			
		||||
          if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
 | 
			
		||||
            commit('SET_ROLES', data.roles)
 | 
			
		||||
          } else {
 | 
			
		||||
            reject('getInfo: roles must be a non-null array !')
 | 
			
		||||
          }
 | 
			
		||||
          commit('SET_NAME', data.name)
 | 
			
		||||
          commit('SET_AVATAR', data.avatar)
 | 
			
		||||
          resolve(response)
 | 
			
		||||
        }).catch(error => {
 | 
			
		||||
          reject(error)
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 登出
 | 
			
		||||
    LogOut({ commit, state }) {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        logout(state.token).then(() => {
 | 
			
		||||
          commit('SET_TOKEN', '')
 | 
			
		||||
          commit('SET_ROLES', [])
 | 
			
		||||
          removeToken()
 | 
			
		||||
          resolve()
 | 
			
		||||
        }).catch(error => {
 | 
			
		||||
          reject(error)
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 前端 登出
 | 
			
		||||
    FedLogOut({ commit }) {
 | 
			
		||||
      return new Promise(resolve => {
 | 
			
		||||
        commit('SET_TOKEN', '')
 | 
			
		||||
        removeToken()
 | 
			
		||||
        resolve()
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  SET_AVATAR: (state, avatar) => {
 | 
			
		||||
    state.avatar = avatar
 | 
			
		||||
  },
 | 
			
		||||
  SET_ROLES: (state, roles) => {
 | 
			
		||||
    state.roles = roles
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default user
 | 
			
		||||
const actions = {
 | 
			
		||||
  // user login
 | 
			
		||||
  login({ commit }, userInfo) {
 | 
			
		||||
    const { username, password } = userInfo
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      login({ username: username.trim(), password: password }).then(response => {
 | 
			
		||||
        const { data } = response
 | 
			
		||||
        commit('SET_TOKEN', data.token)
 | 
			
		||||
        setToken(data.token)
 | 
			
		||||
        resolve()
 | 
			
		||||
      }).catch(error => {
 | 
			
		||||
        reject(error)
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  // get user info
 | 
			
		||||
  getInfo({ commit, state }) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      getInfo(state.token).then(response => {
 | 
			
		||||
        const { data } = response
 | 
			
		||||
 | 
			
		||||
        if (!data) {
 | 
			
		||||
          reject('Verification failed, please Login again.')
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const { roles, name, avatar } = data
 | 
			
		||||
 | 
			
		||||
        // roles must be a non-empty array
 | 
			
		||||
        if (!roles || roles.length <= 0) {
 | 
			
		||||
          reject('getInfo: roles must be a non-null array!')
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        commit('SET_ROLES', roles)
 | 
			
		||||
        commit('SET_NAME', name)
 | 
			
		||||
        commit('SET_AVATAR', avatar)
 | 
			
		||||
        resolve(data)
 | 
			
		||||
      }).catch(error => {
 | 
			
		||||
        reject(error)
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  // user logout
 | 
			
		||||
  logout({ commit, state }) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      logout(state.token).then(() => {
 | 
			
		||||
        commit('SET_TOKEN', '')
 | 
			
		||||
        commit('SET_ROLES', [])
 | 
			
		||||
        removeToken()
 | 
			
		||||
        resetRouter()
 | 
			
		||||
        resolve()
 | 
			
		||||
      }).catch(error => {
 | 
			
		||||
        reject(error)
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  // remove token
 | 
			
		||||
  resetToken({ commit }) {
 | 
			
		||||
    return new Promise(resolve => {
 | 
			
		||||
      commit('SET_TOKEN', '')
 | 
			
		||||
      commit('SET_ROLES', [])
 | 
			
		||||
      removeToken()
 | 
			
		||||
      resolve()
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  namespaced: true,
 | 
			
		||||
  state,
 | 
			
		||||
  mutations,
 | 
			
		||||
  actions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,10 @@
 | 
			
		||||
//to reset element-ui default css
 | 
			
		||||
// cover some element-ui styles
 | 
			
		||||
 | 
			
		||||
.el-breadcrumb__inner,
 | 
			
		||||
.el-breadcrumb__inner a {
 | 
			
		||||
  font-weight: 400 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-upload {
 | 
			
		||||
  input[type="file"] {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
@@ -9,7 +15,8 @@
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
 | 
			
		||||
 | 
			
		||||
// to fixed https://github.com/ElemeFE/element/issues/2461
 | 
			
		||||
.el-dialog {
 | 
			
		||||
  transform: none;
 | 
			
		||||
  left: 0;
 | 
			
		||||
@@ -17,7 +24,7 @@
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//element ui upload
 | 
			
		||||
// refine element ui upload
 | 
			
		||||
.upload-container {
 | 
			
		||||
  .el-upload {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
@@ -28,3 +35,10 @@
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// dropdown
 | 
			
		||||
.el-dropdown-menu {
 | 
			
		||||
  a {
 | 
			
		||||
    display: block
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,19 +31,6 @@ html {
 | 
			
		||||
  box-sizing: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a,
 | 
			
		||||
a:focus,
 | 
			
		||||
a:hover {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  color: inherit;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div:focus {
 | 
			
		||||
  outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:focus,
 | 
			
		||||
a:active {
 | 
			
		||||
  outline: none;
 | 
			
		||||
@@ -57,6 +44,10 @@ a:hover {
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div:focus {
 | 
			
		||||
  outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.clearfix {
 | 
			
		||||
  &:after {
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
@@ -68,11 +59,7 @@ a:hover {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//main-container全局样式
 | 
			
		||||
.app-main {
 | 
			
		||||
  min-height: 100%
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// main-container global css
 | 
			
		||||
.app-container {
 | 
			
		||||
  padding: 20px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#app {
 | 
			
		||||
 | 
			
		||||
  // 主体区域 Main container
 | 
			
		||||
  .main-container {
 | 
			
		||||
    min-height: 100%;
 | 
			
		||||
    transition: margin-left .28s;
 | 
			
		||||
@@ -8,10 +7,10 @@
 | 
			
		||||
    position: relative;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 侧边栏 Sidebar container
 | 
			
		||||
  .sidebar-container {
 | 
			
		||||
    transition: width 0.28s;
 | 
			
		||||
    width: $sideBarWidth !important;
 | 
			
		||||
    background-color: $menuBg;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    font-size: 0px;
 | 
			
		||||
@@ -21,23 +20,29 @@
 | 
			
		||||
    z-index: 1001;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
 | 
			
		||||
    //reset element-ui css
 | 
			
		||||
    // reset element-ui css
 | 
			
		||||
    .horizontal-collapse-transition {
 | 
			
		||||
      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .scrollbar-wrapper {
 | 
			
		||||
      overflow-x: hidden !important;
 | 
			
		||||
 | 
			
		||||
      .el-scrollbar__view {
 | 
			
		||||
        height: 100%;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-scrollbar__bar.is-vertical {
 | 
			
		||||
      right: 0px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-scrollbar {
 | 
			
		||||
      height: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.has-logo {
 | 
			
		||||
      .el-scrollbar {
 | 
			
		||||
        height: calc(100% - 50px);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .is-horizontal {
 | 
			
		||||
      display: none;
 | 
			
		||||
    }
 | 
			
		||||
@@ -100,6 +105,7 @@
 | 
			
		||||
 | 
			
		||||
      .el-tooltip {
 | 
			
		||||
        padding: 0 !important;
 | 
			
		||||
 | 
			
		||||
        .svg-icon {
 | 
			
		||||
          margin-left: 20px;
 | 
			
		||||
        }
 | 
			
		||||
@@ -111,6 +117,7 @@
 | 
			
		||||
 | 
			
		||||
      &>.el-submenu__title {
 | 
			
		||||
        padding: 0 !important;
 | 
			
		||||
 | 
			
		||||
        .svg-icon {
 | 
			
		||||
          margin-left: 20px;
 | 
			
		||||
        }
 | 
			
		||||
@@ -140,7 +147,7 @@
 | 
			
		||||
    min-width: $sideBarWidth !important;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 适配移动端, Mobile responsive
 | 
			
		||||
  // mobile responsive
 | 
			
		||||
  .mobile {
 | 
			
		||||
    .main-container {
 | 
			
		||||
      margin-left: 0px;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
//globl transition css
 | 
			
		||||
// global transition css
 | 
			
		||||
 | 
			
		||||
/*fade*/
 | 
			
		||||
/* fade */
 | 
			
		||||
.fade-enter-active,
 | 
			
		||||
.fade-leave-active {
 | 
			
		||||
  transition: opacity 0.28s;
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*fade-transform*/
 | 
			
		||||
/* fade-transform */
 | 
			
		||||
.fade-transform-leave-active,
 | 
			
		||||
.fade-transform-enter-active {
 | 
			
		||||
  transition: all .5s;
 | 
			
		||||
@@ -27,7 +27,7 @@
 | 
			
		||||
  transform: translateX(30px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*fade*/
 | 
			
		||||
/* breadcrumb transition */
 | 
			
		||||
.breadcrumb-enter-active,
 | 
			
		||||
.breadcrumb-leave-active {
 | 
			
		||||
  transition: all .5s;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
//sidebar
 | 
			
		||||
// sidebar
 | 
			
		||||
$menuText:#bfcbd9;
 | 
			
		||||
$menuActiveText:#409EFF;
 | 
			
		||||
$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/utils/get-page-title.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
import defaultSettings from '@/settings'
 | 
			
		||||
 | 
			
		||||
const title = defaultSettings.title || 'Vue Admin Template'
 | 
			
		||||
 | 
			
		||||
export default function getPageTitle(pageTitle) {
 | 
			
		||||
  if (pageTitle) {
 | 
			
		||||
    return `${pageTitle} - ${title}`
 | 
			
		||||
  }
 | 
			
		||||
  return `${title}`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										90
									
								
								src/utils/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,90 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Created by PanJiaChen on 16/11/18.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Parse the time to string
 | 
			
		||||
 * @param {(Object|string|number)} time
 | 
			
		||||
 * @param {string} cFormat
 | 
			
		||||
 * @returns {string}
 | 
			
		||||
 */
 | 
			
		||||
export function parseTime(time, cFormat) {
 | 
			
		||||
  if (arguments.length === 0) {
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
 | 
			
		||||
  let date
 | 
			
		||||
  if (typeof time === 'object') {
 | 
			
		||||
    date = time
 | 
			
		||||
  } else {
 | 
			
		||||
    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
 | 
			
		||||
      time = parseInt(time)
 | 
			
		||||
    }
 | 
			
		||||
    if ((typeof time === 'number') && (time.toString().length === 10)) {
 | 
			
		||||
      time = time * 1000
 | 
			
		||||
    }
 | 
			
		||||
    date = new Date(time)
 | 
			
		||||
  }
 | 
			
		||||
  const formatObj = {
 | 
			
		||||
    y: date.getFullYear(),
 | 
			
		||||
    m: date.getMonth() + 1,
 | 
			
		||||
    d: date.getDate(),
 | 
			
		||||
    h: date.getHours(),
 | 
			
		||||
    i: date.getMinutes(),
 | 
			
		||||
    s: date.getSeconds(),
 | 
			
		||||
    a: date.getDay()
 | 
			
		||||
  }
 | 
			
		||||
  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
 | 
			
		||||
    let value = formatObj[key]
 | 
			
		||||
    // Note: getDay() returns 0 on Sunday
 | 
			
		||||
    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
 | 
			
		||||
    if (result.length > 0 && value < 10) {
 | 
			
		||||
      value = '0' + value
 | 
			
		||||
    }
 | 
			
		||||
    return value || 0
 | 
			
		||||
  })
 | 
			
		||||
  return time_str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {number} time
 | 
			
		||||
 * @param {string} option
 | 
			
		||||
 * @returns {string}
 | 
			
		||||
 */
 | 
			
		||||
export function formatTime(time, option) {
 | 
			
		||||
  if (('' + time).length === 10) {
 | 
			
		||||
    time = parseInt(time) * 1000
 | 
			
		||||
  } else {
 | 
			
		||||
    time = +time
 | 
			
		||||
  }
 | 
			
		||||
  const d = new Date(time)
 | 
			
		||||
  const now = Date.now()
 | 
			
		||||
 | 
			
		||||
  const diff = (now - d) / 1000
 | 
			
		||||
 | 
			
		||||
  if (diff < 30) {
 | 
			
		||||
    return '刚刚'
 | 
			
		||||
  } else if (diff < 3600) {
 | 
			
		||||
    // less 1 hour
 | 
			
		||||
    return Math.ceil(diff / 60) + '分钟前'
 | 
			
		||||
  } else if (diff < 3600 * 24) {
 | 
			
		||||
    return Math.ceil(diff / 3600) + '小时前'
 | 
			
		||||
  } else if (diff < 3600 * 24 * 2) {
 | 
			
		||||
    return '1天前'
 | 
			
		||||
  }
 | 
			
		||||
  if (option) {
 | 
			
		||||
    return parseTime(time, option)
 | 
			
		||||
  } else {
 | 
			
		||||
    return (
 | 
			
		||||
      d.getMonth() +
 | 
			
		||||
      1 +
 | 
			
		||||
      '月' +
 | 
			
		||||
      d.getDate() +
 | 
			
		||||
      '日' +
 | 
			
		||||
      d.getHours() +
 | 
			
		||||
      '时' +
 | 
			
		||||
      d.getMinutes() +
 | 
			
		||||
      '分'
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,62 +1,74 @@
 | 
			
		||||
import axios from 'axios'
 | 
			
		||||
import { Message, MessageBox } from 'element-ui'
 | 
			
		||||
import store from '../store'
 | 
			
		||||
import { MessageBox, Message } from 'element-ui'
 | 
			
		||||
import store from '@/store'
 | 
			
		||||
import { getToken } from '@/utils/auth'
 | 
			
		||||
 | 
			
		||||
// 创建axios实例
 | 
			
		||||
// create an axios instance
 | 
			
		||||
const service = axios.create({
 | 
			
		||||
  baseURL: process.env.BASE_API, // api 的 base_url
 | 
			
		||||
  timeout: 5000 // 请求超时时间
 | 
			
		||||
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
 | 
			
		||||
  withCredentials: true, // send cookies when cross-domain requests
 | 
			
		||||
  timeout: 5000 // request timeout
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// request拦截器
 | 
			
		||||
// request interceptor
 | 
			
		||||
service.interceptors.request.use(
 | 
			
		||||
  config => {
 | 
			
		||||
    // do something before request is sent
 | 
			
		||||
 | 
			
		||||
    if (store.getters.token) {
 | 
			
		||||
      config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
 | 
			
		||||
      // let each request carry token
 | 
			
		||||
      // ['X-Token'] is a custom headers key
 | 
			
		||||
      // please modify it according to the actual situation
 | 
			
		||||
      config.headers['X-Token'] = getToken()
 | 
			
		||||
    }
 | 
			
		||||
    return config
 | 
			
		||||
  },
 | 
			
		||||
  error => {
 | 
			
		||||
    // Do something with request error
 | 
			
		||||
    // do something with request error
 | 
			
		||||
    console.log(error) // for debug
 | 
			
		||||
    Promise.reject(error)
 | 
			
		||||
    return Promise.reject(error)
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// response 拦截器
 | 
			
		||||
// response interceptor
 | 
			
		||||
service.interceptors.response.use(
 | 
			
		||||
  /**
 | 
			
		||||
   * If you want to get http information such as headers or status
 | 
			
		||||
   * Please return  response => response
 | 
			
		||||
  */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determine the request status by custom code
 | 
			
		||||
   * Here is just an example
 | 
			
		||||
   * You can also judge the status by HTTP Status Code
 | 
			
		||||
   */
 | 
			
		||||
  response => {
 | 
			
		||||
    /**
 | 
			
		||||
     * code为非20000是抛错 可结合自己业务进行修改
 | 
			
		||||
     */
 | 
			
		||||
    const res = response.data
 | 
			
		||||
 | 
			
		||||
    // if the custom code is not 20000, it is judged as an error.
 | 
			
		||||
    if (res.code !== 20000) {
 | 
			
		||||
      Message({
 | 
			
		||||
        message: res.message,
 | 
			
		||||
        message: res.message || 'error',
 | 
			
		||||
        type: 'error',
 | 
			
		||||
        duration: 5 * 1000
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
 | 
			
		||||
      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
 | 
			
		||||
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
 | 
			
		||||
        MessageBox.confirm(
 | 
			
		||||
          '你已被登出,可以取消继续留在该页面,或者重新登录',
 | 
			
		||||
          '确定登出',
 | 
			
		||||
          {
 | 
			
		||||
            confirmButtonText: '重新登录',
 | 
			
		||||
            cancelButtonText: '取消',
 | 
			
		||||
            type: 'warning'
 | 
			
		||||
          }
 | 
			
		||||
        ).then(() => {
 | 
			
		||||
          store.dispatch('FedLogOut').then(() => {
 | 
			
		||||
            location.reload() // 为了重新实例化vue-router对象 避免bug
 | 
			
		||||
        // to re-login
 | 
			
		||||
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
 | 
			
		||||
          confirmButtonText: 'Re-Login',
 | 
			
		||||
          cancelButtonText: 'Cancel',
 | 
			
		||||
          type: 'warning'
 | 
			
		||||
        }).then(() => {
 | 
			
		||||
          store.dispatch('user/resetToken').then(() => {
 | 
			
		||||
            location.reload()
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      return Promise.reject('error')
 | 
			
		||||
      return Promise.reject(res.message || 'error')
 | 
			
		||||
    } else {
 | 
			
		||||
      return response.data
 | 
			
		||||
      return res
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  error => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,20 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Created by jiachenpan on 16/11/18.
 | 
			
		||||
 * Created by PanJiaChen on 16/11/18.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export function isvalidUsername(str) {
 | 
			
		||||
  const valid_map = ['admin', 'editor']
 | 
			
		||||
  return valid_map.indexOf(str.trim()) >= 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {string} path
 | 
			
		||||
 * @returns {Boolean}
 | 
			
		||||
 */
 | 
			
		||||
export function isExternal(path) {
 | 
			
		||||
  return /^(https?:|mailto:|tel:)/.test(path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {string} str
 | 
			
		||||
 * @returns {Boolean}
 | 
			
		||||
 */
 | 
			
		||||
export function validUsername(str) {
 | 
			
		||||
  const valid_map = ['admin', 'editor']
 | 
			
		||||
  return valid_map.indexOf(str.trim()) >= 0
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,12 +9,12 @@
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="bullshit">
 | 
			
		||||
        <div class="bullshit__oops">OOPS!</div>
 | 
			
		||||
        <div class="bullshit__info">版权所有
 | 
			
		||||
          <a class="link-type" href="https://wallstreetcn.com" target="_blank">华尔街见闻</a>
 | 
			
		||||
        <div class="bullshit__info">All rights reserved
 | 
			
		||||
          <a style="color:#20a0ff" href="https://wallstreetcn.com" target="_blank">wallstreetcn</a>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="bullshit__headline">{{ message }}</div>
 | 
			
		||||
        <div class="bullshit__info">请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告</div>
 | 
			
		||||
        <a href="" class="bullshit__return-home">返回首页</a>
 | 
			
		||||
        <div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div>
 | 
			
		||||
        <a href="" class="bullshit__return-home">Back to home</a>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
@@ -26,13 +26,13 @@ export default {
 | 
			
		||||
  name: 'Page404',
 | 
			
		||||
  computed: {
 | 
			
		||||
    message() {
 | 
			
		||||
      return '网管说这个页面你不能进......'
 | 
			
		||||
      return 'The webmaster said that you can not enter this page...'
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.wscn-http404-container{
 | 
			
		||||
  transform: translate(-50%,-50%);
 | 
			
		||||
  position: absolute;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="dashboard-container">
 | 
			
		||||
    <div class="dashboard-text">name:{{ name }}</div>
 | 
			
		||||
    <div class="dashboard-text">roles:<span v-for="role in roles" :key="role">{{ role }}</span></div>
 | 
			
		||||
    <div class="dashboard-text">name: {{ name }}</div>
 | 
			
		||||
    <div class="dashboard-text">roles: <span v-for="role in roles" :key="role">{{ role }}</span></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -19,7 +19,7 @@ export default {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.dashboard {
 | 
			
		||||
  &-container {
 | 
			
		||||
    margin: 30px;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,42 +2,42 @@
 | 
			
		||||
  <div class="app-container">
 | 
			
		||||
    <el-form ref="form" :model="form" label-width="120px">
 | 
			
		||||
      <el-form-item label="Activity name">
 | 
			
		||||
        <el-input v-model="form.name"/>
 | 
			
		||||
        <el-input v-model="form.name" />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="Activity zone">
 | 
			
		||||
        <el-select v-model="form.region" placeholder="please select your zone">
 | 
			
		||||
          <el-option label="Zone one" value="shanghai"/>
 | 
			
		||||
          <el-option label="Zone two" value="beijing"/>
 | 
			
		||||
          <el-option label="Zone one" value="shanghai" />
 | 
			
		||||
          <el-option label="Zone two" value="beijing" />
 | 
			
		||||
        </el-select>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="Activity time">
 | 
			
		||||
        <el-col :span="11">
 | 
			
		||||
          <el-date-picker v-model="form.date1" type="date" placeholder="Pick a date" style="width: 100%;"/>
 | 
			
		||||
          <el-date-picker v-model="form.date1" type="date" placeholder="Pick a date" style="width: 100%;" />
 | 
			
		||||
        </el-col>
 | 
			
		||||
        <el-col :span="2" class="line">-</el-col>
 | 
			
		||||
        <el-col :span="11">
 | 
			
		||||
          <el-time-picker v-model="form.date2" type="fixed-time" placeholder="Pick a time" style="width: 100%;"/>
 | 
			
		||||
          <el-time-picker v-model="form.date2" type="fixed-time" placeholder="Pick a time" style="width: 100%;" />
 | 
			
		||||
        </el-col>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="Instant delivery">
 | 
			
		||||
        <el-switch v-model="form.delivery"/>
 | 
			
		||||
        <el-switch v-model="form.delivery" />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="Activity type">
 | 
			
		||||
        <el-checkbox-group v-model="form.type">
 | 
			
		||||
          <el-checkbox label="Online activities" name="type"/>
 | 
			
		||||
          <el-checkbox label="Promotion activities" name="type"/>
 | 
			
		||||
          <el-checkbox label="Offline activities" name="type"/>
 | 
			
		||||
          <el-checkbox label="Simple brand exposure" name="type"/>
 | 
			
		||||
          <el-checkbox label="Online activities" name="type" />
 | 
			
		||||
          <el-checkbox label="Promotion activities" name="type" />
 | 
			
		||||
          <el-checkbox label="Offline activities" name="type" />
 | 
			
		||||
          <el-checkbox label="Simple brand exposure" name="type" />
 | 
			
		||||
        </el-checkbox-group>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="Resources">
 | 
			
		||||
        <el-radio-group v-model="form.resource">
 | 
			
		||||
          <el-radio label="Sponsor"/>
 | 
			
		||||
          <el-radio label="Venue"/>
 | 
			
		||||
          <el-radio label="Sponsor" />
 | 
			
		||||
          <el-radio label="Venue" />
 | 
			
		||||
        </el-radio-group>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="Activity form">
 | 
			
		||||
        <el-input v-model="form.desc" type="textarea"/>
 | 
			
		||||
        <el-input v-model="form.desc" type="textarea" />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item>
 | 
			
		||||
        <el-button type="primary" @click="onSubmit">Create</el-button>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,95 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="navbar">
 | 
			
		||||
    <hamburger :toggle-click="toggleSideBar" :is-active="sidebar.opened" class="hamburger-container"/>
 | 
			
		||||
    <breadcrumb />
 | 
			
		||||
    <el-dropdown class="avatar-container" trigger="click">
 | 
			
		||||
      <div class="avatar-wrapper">
 | 
			
		||||
        <img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
 | 
			
		||||
        <i class="el-icon-caret-bottom"/>
 | 
			
		||||
      </div>
 | 
			
		||||
      <el-dropdown-menu slot="dropdown" class="user-dropdown">
 | 
			
		||||
        <router-link class="inlineBlock" to="/">
 | 
			
		||||
          <el-dropdown-item>
 | 
			
		||||
            Home
 | 
			
		||||
          </el-dropdown-item>
 | 
			
		||||
        </router-link>
 | 
			
		||||
        <el-dropdown-item divided>
 | 
			
		||||
          <span style="display:block;" @click="logout">LogOut</span>
 | 
			
		||||
        </el-dropdown-item>
 | 
			
		||||
      </el-dropdown-menu>
 | 
			
		||||
    </el-dropdown>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { mapGetters } from 'vuex'
 | 
			
		||||
import Breadcrumb from '@/components/Breadcrumb'
 | 
			
		||||
import Hamburger from '@/components/Hamburger'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    Breadcrumb,
 | 
			
		||||
    Hamburger
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    ...mapGetters([
 | 
			
		||||
      'sidebar',
 | 
			
		||||
      'avatar'
 | 
			
		||||
    ])
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    toggleSideBar() {
 | 
			
		||||
      this.$store.dispatch('ToggleSideBar')
 | 
			
		||||
    },
 | 
			
		||||
    logout() {
 | 
			
		||||
      this.$store.dispatch('LogOut').then(() => {
 | 
			
		||||
        location.reload() // 为了重新实例化vue-router对象 避免bug
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
			
		||||
.navbar {
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  line-height: 50px;
 | 
			
		||||
  box-shadow: 0 1px 3px 0 rgba(0,0,0,.12), 0 0 3px 0 rgba(0,0,0,.04);
 | 
			
		||||
  .hamburger-container {
 | 
			
		||||
    line-height: 58px;
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    float: left;
 | 
			
		||||
    padding: 0 10px;
 | 
			
		||||
  }
 | 
			
		||||
  .screenfull {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 90px;
 | 
			
		||||
    top: 16px;
 | 
			
		||||
    color: red;
 | 
			
		||||
  }
 | 
			
		||||
  .avatar-container {
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 35px;
 | 
			
		||||
    .avatar-wrapper {
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      margin-top: 5px;
 | 
			
		||||
      position: relative;
 | 
			
		||||
      line-height: initial;
 | 
			
		||||
      .user-avatar {
 | 
			
		||||
        width: 40px;
 | 
			
		||||
        height: 40px;
 | 
			
		||||
        border-radius: 10px;
 | 
			
		||||
      }
 | 
			
		||||
      .el-icon-caret-bottom {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        right: -20px;
 | 
			
		||||
        top: 25px;
 | 
			
		||||
        font-size: 12px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@@ -1,39 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-scrollbar wrap-class="scrollbar-wrapper">
 | 
			
		||||
    <el-menu
 | 
			
		||||
      :default-active="$route.path"
 | 
			
		||||
      :collapse="isCollapse"
 | 
			
		||||
      :background-color="variables.menuBg"
 | 
			
		||||
      :text-color="variables.menuText"
 | 
			
		||||
      :active-text-color="variables.menuActiveText"
 | 
			
		||||
      :collapse-transition="false"
 | 
			
		||||
      mode="vertical"
 | 
			
		||||
    >
 | 
			
		||||
      <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path"/>
 | 
			
		||||
    </el-menu>
 | 
			
		||||
  </el-scrollbar>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { mapGetters } from 'vuex'
 | 
			
		||||
import variables from '@/styles/variables.scss'
 | 
			
		||||
import SidebarItem from './SidebarItem'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: { SidebarItem },
 | 
			
		||||
  computed: {
 | 
			
		||||
    ...mapGetters([
 | 
			
		||||
      'sidebar'
 | 
			
		||||
    ]),
 | 
			
		||||
    routes() {
 | 
			
		||||
      return this.$router.options.routes
 | 
			
		||||
    },
 | 
			
		||||
    variables() {
 | 
			
		||||
      return variables
 | 
			
		||||
    },
 | 
			
		||||
    isCollapse() {
 | 
			
		||||
      return !this.sidebar.opened
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,40 +0,0 @@
 | 
			
		||||
import store from '@/store'
 | 
			
		||||
 | 
			
		||||
const { body } = document
 | 
			
		||||
const WIDTH = 992 // refer to Bootstrap's responsive design
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  watch: {
 | 
			
		||||
    $route(route) {
 | 
			
		||||
      if (this.device === 'mobile' && this.sidebar.opened) {
 | 
			
		||||
        store.dispatch('CloseSideBar', { withoutAnimation: false })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  beforeMount() {
 | 
			
		||||
    window.addEventListener('resize', this.resizeHandler)
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    const isMobile = this.isMobile()
 | 
			
		||||
    if (isMobile) {
 | 
			
		||||
      store.dispatch('ToggleDevice', 'mobile')
 | 
			
		||||
      store.dispatch('CloseSideBar', { withoutAnimation: true })
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    isMobile() {
 | 
			
		||||
      const rect = body.getBoundingClientRect()
 | 
			
		||||
      return rect.width - 1 < WIDTH
 | 
			
		||||
    },
 | 
			
		||||
    resizeHandler() {
 | 
			
		||||
      if (!document.hidden) {
 | 
			
		||||
        const isMobile = this.isMobile()
 | 
			
		||||
        store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop')
 | 
			
		||||
 | 
			
		||||
        if (isMobile) {
 | 
			
		||||
          store.dispatch('CloseSideBar', { withoutAnimation: true })
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,57 +1,73 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="login-container">
 | 
			
		||||
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
 | 
			
		||||
      <h3 class="title">vue-admin-template</h3>
 | 
			
		||||
 | 
			
		||||
      <div class="title-container">
 | 
			
		||||
        <h3 class="title">Login Form</h3>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <el-form-item prop="username">
 | 
			
		||||
        <span class="svg-container">
 | 
			
		||||
          <svg-icon icon-class="user" />
 | 
			
		||||
        </span>
 | 
			
		||||
        <el-input v-model="loginForm.username" name="username" type="text" auto-complete="on" placeholder="username" />
 | 
			
		||||
        <el-input
 | 
			
		||||
          ref="username"
 | 
			
		||||
          v-model="loginForm.username"
 | 
			
		||||
          placeholder="Username"
 | 
			
		||||
          name="username"
 | 
			
		||||
          type="text"
 | 
			
		||||
          tabindex="1"
 | 
			
		||||
          auto-complete="on"
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
 | 
			
		||||
      <el-form-item prop="password">
 | 
			
		||||
        <span class="svg-container">
 | 
			
		||||
          <svg-icon icon-class="password" />
 | 
			
		||||
        </span>
 | 
			
		||||
        <el-input
 | 
			
		||||
          :type="pwdType"
 | 
			
		||||
          :key="passwordType"
 | 
			
		||||
          ref="password"
 | 
			
		||||
          v-model="loginForm.password"
 | 
			
		||||
          :type="passwordType"
 | 
			
		||||
          placeholder="Password"
 | 
			
		||||
          name="password"
 | 
			
		||||
          tabindex="2"
 | 
			
		||||
          auto-complete="on"
 | 
			
		||||
          placeholder="password"
 | 
			
		||||
          @keyup.enter.native="handleLogin" />
 | 
			
		||||
          @keyup.enter.native="handleLogin"
 | 
			
		||||
        />
 | 
			
		||||
        <span class="show-pwd" @click="showPwd">
 | 
			
		||||
          <svg-icon :icon-class="pwdType === 'password' ? 'eye' : 'eye-open'" />
 | 
			
		||||
          <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
 | 
			
		||||
        </span>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item>
 | 
			
		||||
        <el-button :loading="loading" type="primary" style="width:100%;" @click.native.prevent="handleLogin">
 | 
			
		||||
          Sign in
 | 
			
		||||
        </el-button>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
 | 
			
		||||
      <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">Login</el-button>
 | 
			
		||||
 | 
			
		||||
      <div class="tips">
 | 
			
		||||
        <span style="margin-right:20px;">username: admin</span>
 | 
			
		||||
        <span> password: admin</span>
 | 
			
		||||
        <span> password: any</span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
    </el-form>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { isvalidUsername } from '@/utils/validate'
 | 
			
		||||
import { validUsername } from '@/utils/validate'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'Login',
 | 
			
		||||
  data() {
 | 
			
		||||
    const validateUsername = (rule, value, callback) => {
 | 
			
		||||
      if (!isvalidUsername(value)) {
 | 
			
		||||
        callback(new Error('请输入正确的用户名'))
 | 
			
		||||
      if (!validUsername(value)) {
 | 
			
		||||
        callback(new Error('Please enter the correct user name'))
 | 
			
		||||
      } else {
 | 
			
		||||
        callback()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    const validatePass = (rule, value, callback) => {
 | 
			
		||||
      if (value.length < 5) {
 | 
			
		||||
        callback(new Error('密码不能小于5位'))
 | 
			
		||||
    const validatePassword = (rule, value, callback) => {
 | 
			
		||||
      if (value.length < 6) {
 | 
			
		||||
        callback(new Error('The password can not be less than 6 digits'))
 | 
			
		||||
      } else {
 | 
			
		||||
        callback()
 | 
			
		||||
      }
 | 
			
		||||
@@ -59,14 +75,14 @@ export default {
 | 
			
		||||
    return {
 | 
			
		||||
      loginForm: {
 | 
			
		||||
        username: 'admin',
 | 
			
		||||
        password: 'admin'
 | 
			
		||||
        password: '111111'
 | 
			
		||||
      },
 | 
			
		||||
      loginRules: {
 | 
			
		||||
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
 | 
			
		||||
        password: [{ required: true, trigger: 'blur', validator: validatePass }]
 | 
			
		||||
        password: [{ required: true, trigger: 'blur', validator: validatePassword }]
 | 
			
		||||
      },
 | 
			
		||||
      loading: false,
 | 
			
		||||
      pwdType: 'password',
 | 
			
		||||
      passwordType: 'password',
 | 
			
		||||
      redirect: undefined
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
@@ -80,19 +96,22 @@ export default {
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    showPwd() {
 | 
			
		||||
      if (this.pwdType === 'password') {
 | 
			
		||||
        this.pwdType = ''
 | 
			
		||||
      if (this.passwordType === 'password') {
 | 
			
		||||
        this.passwordType = ''
 | 
			
		||||
      } else {
 | 
			
		||||
        this.pwdType = 'password'
 | 
			
		||||
        this.passwordType = 'password'
 | 
			
		||||
      }
 | 
			
		||||
      this.$nextTick(() => {
 | 
			
		||||
        this.$refs.password.focus()
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    handleLogin() {
 | 
			
		||||
      this.$refs.loginForm.validate(valid => {
 | 
			
		||||
        if (valid) {
 | 
			
		||||
          this.loading = true
 | 
			
		||||
          this.$store.dispatch('Login', this.loginForm).then(() => {
 | 
			
		||||
            this.loading = false
 | 
			
		||||
          this.$store.dispatch('user/login', this.loginForm).then(() => {
 | 
			
		||||
            this.$router.push({ path: this.redirect || '/' })
 | 
			
		||||
            this.loading = false
 | 
			
		||||
          }).catch(() => {
 | 
			
		||||
            this.loading = false
 | 
			
		||||
          })
 | 
			
		||||
@@ -106,9 +125,19 @@ export default {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss">
 | 
			
		||||
$bg:#2d3a4b;
 | 
			
		||||
$light_gray:#eee;
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
/* 修复input 背景不协调 和光标变色 */
 | 
			
		||||
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
 | 
			
		||||
 | 
			
		||||
$bg:#283443;
 | 
			
		||||
$light_gray:#fff;
 | 
			
		||||
$cursor: #fff;
 | 
			
		||||
 | 
			
		||||
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
 | 
			
		||||
  .login-container .el-input input {
 | 
			
		||||
    color: $cursor;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* reset element-ui css */
 | 
			
		||||
.login-container {
 | 
			
		||||
@@ -116,6 +145,7 @@ $light_gray:#eee;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    height: 47px;
 | 
			
		||||
    width: 85%;
 | 
			
		||||
 | 
			
		||||
    input {
 | 
			
		||||
      background: transparent;
 | 
			
		||||
      border: 0px;
 | 
			
		||||
@@ -124,12 +154,15 @@ $light_gray:#eee;
 | 
			
		||||
      padding: 12px 5px 12px 15px;
 | 
			
		||||
      color: $light_gray;
 | 
			
		||||
      height: 47px;
 | 
			
		||||
      caret-color: $cursor;
 | 
			
		||||
 | 
			
		||||
      &:-webkit-autofill {
 | 
			
		||||
        -webkit-box-shadow: 0 0 0px 1000px $bg inset !important;
 | 
			
		||||
        -webkit-text-fill-color: #fff !important;
 | 
			
		||||
        box-shadow: 0 0 0px 1000px $bg inset !important;
 | 
			
		||||
        -webkit-text-fill-color: $cursor !important;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-form-item {
 | 
			
		||||
    border: 1px solid rgba(255, 255, 255, 0.1);
 | 
			
		||||
    background: rgba(0, 0, 0, 0.1);
 | 
			
		||||
@@ -137,37 +170,40 @@ $light_gray:#eee;
 | 
			
		||||
    color: #454545;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
$bg:#2d3a4b;
 | 
			
		||||
$dark_gray:#889aa4;
 | 
			
		||||
$light_gray:#eee;
 | 
			
		||||
 | 
			
		||||
.login-container {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  min-height: 100%;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background-color: $bg;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
 | 
			
		||||
  .login-form {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    width: 520px;
 | 
			
		||||
    max-width: 100%;
 | 
			
		||||
    padding: 35px 35px 15px 35px;
 | 
			
		||||
    margin: 120px auto;
 | 
			
		||||
    padding: 160px 35px 0;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .tips {
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
 | 
			
		||||
    span {
 | 
			
		||||
      &:first-of-type {
 | 
			
		||||
        margin-right: 16px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .svg-container {
 | 
			
		||||
    padding: 6px 5px 6px 15px;
 | 
			
		||||
    color: $dark_gray;
 | 
			
		||||
@@ -175,14 +211,19 @@ $light_gray:#eee;
 | 
			
		||||
    width: 30px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
  }
 | 
			
		||||
  .title {
 | 
			
		||||
    font-size: 26px;
 | 
			
		||||
    font-weight: 400;
 | 
			
		||||
    color: $light_gray;
 | 
			
		||||
    margin: 0px auto 40px auto;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
 | 
			
		||||
  .title-container {
 | 
			
		||||
    position: relative;
 | 
			
		||||
 | 
			
		||||
    .title {
 | 
			
		||||
      font-size: 26px;
 | 
			
		||||
      color: $light_gray;
 | 
			
		||||
      margin: 0px auto 40px auto;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      font-weight: bold;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .show-pwd {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 10px;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
<template >
 | 
			
		||||
<template>
 | 
			
		||||
  <div style="padding:30px;">
 | 
			
		||||
    <el-alert :closable="false" title="menu 1">
 | 
			
		||||
      <router-view />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
<template >
 | 
			
		||||
<template>
 | 
			
		||||
  <div style="padding:30px;">
 | 
			
		||||
    <el-alert :closable="false" title="menu 1-1" type="success">
 | 
			
		||||
      <router-view />
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,8 @@
 | 
			
		||||
      element-loading-text="Loading"
 | 
			
		||||
      border
 | 
			
		||||
      fit
 | 
			
		||||
      highlight-current-row>
 | 
			
		||||
      highlight-current-row
 | 
			
		||||
    >
 | 
			
		||||
      <el-table-column align="center" label="ID" width="95">
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          {{ scope.$index }}
 | 
			
		||||
@@ -34,7 +35,7 @@
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
      <el-table-column align="center" prop="created_at" label="Display_time" width="200">
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          <i class="el-icon-time"/>
 | 
			
		||||
          <i class="el-icon-time" />
 | 
			
		||||
          <span>{{ scope.row.display_time }}</span>
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
@@ -68,7 +69,7 @@ export default {
 | 
			
		||||
  methods: {
 | 
			
		||||
    fetchData() {
 | 
			
		||||
      this.listLoading = true
 | 
			
		||||
      getList(this.listQuery).then(response => {
 | 
			
		||||
      getList().then(response => {
 | 
			
		||||
        this.list = response.data.items
 | 
			
		||||
        this.listLoading = false
 | 
			
		||||
      })
 | 
			
		||||
 
 | 
			
		||||