init
This commit is contained in:
		
							
								
								
									
										12
									
								
								src/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/App.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="app">
 | 
			
		||||
    <router-view></router-view>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'app'
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										29
									
								
								src/api/login.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/api/login.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
import fetch from '@/utils/fetch';
 | 
			
		||||
 | 
			
		||||
export function loginByEmail(email, password) {
 | 
			
		||||
  const data = {
 | 
			
		||||
    email,
 | 
			
		||||
    password
 | 
			
		||||
  };
 | 
			
		||||
  return fetch({
 | 
			
		||||
    url: '/login/loginbyemail',
 | 
			
		||||
    method: 'post',
 | 
			
		||||
    data
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function logout() {
 | 
			
		||||
  return fetch({
 | 
			
		||||
    url: '/login/logout',
 | 
			
		||||
    method: 'post'
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getInfo(token) {
 | 
			
		||||
  return fetch({
 | 
			
		||||
    url: '/user/info',
 | 
			
		||||
    method: 'get',
 | 
			
		||||
    params: { token }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/assets/404_images/404.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/404_images/404.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 96 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/404_images/404_cloud.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/404_images/404_cloud.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 4.7 KiB  | 
							
								
								
									
										1
									
								
								src/assets/iconfont/iconfont.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/assets/iconfont/iconfont.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										36
									
								
								src/components/Hamburger/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/components/Hamburger/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <svg t="1492500959545" @click="toggleClick" class="wscn-icon hamburger" :class="{'is-active':isActive}"  style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1691" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M966.8023 568.849776 57.196677 568.849776c-31.397081 0-56.850799-25.452695-56.850799-56.850799l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 543.397081 998.200404 568.849776 966.8023 568.849776z" p-id="1692"></path><path d="M966.8023 881.527125 57.196677 881.527125c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 856.07443 998.200404 881.527125 966.8023 881.527125z" p-id="1693"></path><path d="M966.8023 256.17345 57.196677 256.17345c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.850799 56.850799-56.850799l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.850799l0 0C1023.653099 230.720755 998.200404 256.17345 966.8023 256.17345z" p-id="1694"></path></svg>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    export default {
 | 
			
		||||
      name: 'hamburger',
 | 
			
		||||
      props: {
 | 
			
		||||
        isActive: {
 | 
			
		||||
          type: Boolean,
 | 
			
		||||
          default: false
 | 
			
		||||
        },
 | 
			
		||||
        toggleClick: {
 | 
			
		||||
          type: Function,
 | 
			
		||||
          default: null
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
    .hamburger {
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        width: 20px;
 | 
			
		||||
        height: 20px;
 | 
			
		||||
        transform: rotate(0deg);
 | 
			
		||||
        transition: .38s;
 | 
			
		||||
        transform-origin: 50% 50%;
 | 
			
		||||
    }
 | 
			
		||||
    .hamburger.is-active {
 | 
			
		||||
        transform: rotate(90deg);
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										11
									
								
								src/components/Icon-svg/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/components/Icon-svg/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
import Vue from 'vue'
 | 
			
		||||
 | 
			
		||||
function registerAllComponents(requireContext) {
 | 
			
		||||
  return requireContext.keys().forEach(comp => {
 | 
			
		||||
    const vueComp = requireContext(comp)
 | 
			
		||||
    const compName = vueComp.name ? vueComp.name.toLowerCase() : /\/([\w-]+)\.vue$/.exec(comp)[1]
 | 
			
		||||
    Vue.component(compName, vueComp)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
registerAllComponents(require.context('./', false, /\.vue$/))
 | 
			
		||||
							
								
								
									
										52
									
								
								src/components/Icon-svg/wscn-icon-stack.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/components/Icon-svg/wscn-icon-stack.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="icon-container" :style="containerStyle">
 | 
			
		||||
    <slot class="icon"></slot>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  export default {
 | 
			
		||||
    name: 'wscn-icon-stack',
 | 
			
		||||
    props: {
 | 
			
		||||
      width: {
 | 
			
		||||
        type: Number,
 | 
			
		||||
        default: 20
 | 
			
		||||
      },
 | 
			
		||||
      shape: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: 'circle',
 | 
			
		||||
        validator: val => {
 | 
			
		||||
          const validShapes = ['circle', 'square']
 | 
			
		||||
          return validShapes.indexOf(val) > -1
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
      containerStyle() {
 | 
			
		||||
        return {
 | 
			
		||||
          width: `${this.width}px`,
 | 
			
		||||
          height: `${this.width}px`,
 | 
			
		||||
          fontSize: `${this.width * 0.6}px`,
 | 
			
		||||
          borderRadius: `${this.shape === 'circle' && '50%'}`
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
  .icon-container {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    background: #1482F0;
 | 
			
		||||
 | 
			
		||||
    .icon {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      color: #ffffff;
 | 
			
		||||
      top: 50%;
 | 
			
		||||
      left: 50%;
 | 
			
		||||
      transform: translate(-50%, -50%);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										26
									
								
								src/components/Icon-svg/wscn-icon-svg.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/components/Icon-svg/wscn-icon-svg.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <svg class="wscn-icon" aria-hidden="true">
 | 
			
		||||
    <use :xlink:href="iconName"></use>
 | 
			
		||||
  </svg>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  export default {
 | 
			
		||||
    name: 'wscn-icon-svg',
 | 
			
		||||
    props: {
 | 
			
		||||
      iconClass: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        required: true
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
      iconName() {
 | 
			
		||||
        return `#icon-${this.iconClass}`
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										66
									
								
								src/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/main.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
// The Vue build version to load with the `import` command
 | 
			
		||||
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
 | 
			
		||||
import Vue from 'vue'
 | 
			
		||||
import App from './App'
 | 
			
		||||
import router from './router'
 | 
			
		||||
import store from './store'
 | 
			
		||||
import ElementUI from 'element-ui';
 | 
			
		||||
import 'element-ui/lib/theme-default/index.css'
 | 
			
		||||
import NProgress from 'nprogress'
 | 
			
		||||
import 'normalize.css/normalize.css';// normalize.css 样式格式化
 | 
			
		||||
import '@/styles/index.scss'; // 全局自定义的css样式
 | 
			
		||||
import '@/components/Icon-svg/index'; // 封装的svg组件
 | 
			
		||||
 | 
			
		||||
Vue.config.productionTip = false
 | 
			
		||||
 | 
			
		||||
Vue.use(ElementUI);
 | 
			
		||||
 | 
			
		||||
router.afterEach(() => {
 | 
			
		||||
  NProgress.done(); // 结束Progress
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// const whiteList = ['/login', '/authredirect', '/reset', '/sendpwd'];// 不重定向白名单
 | 
			
		||||
// router.beforeEach((to, from, next) => {
 | 
			
		||||
//   NProgress.start(); // 开启Progress
 | 
			
		||||
//   if (store.getters.token) { // 判断是否有token
 | 
			
		||||
//     if (to.path === '/login') {
 | 
			
		||||
//       next({ path: '/' });
 | 
			
		||||
//     } else {
 | 
			
		||||
//       if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
 | 
			
		||||
//         store.dispatch('GetInfo').then(res => { // 拉取user_info
 | 
			
		||||
//           const roles = res.data.role;
 | 
			
		||||
//           store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表
 | 
			
		||||
//             router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
 | 
			
		||||
//             next(to.path); // hack方法 确保addRoutes已完成
 | 
			
		||||
//           })
 | 
			
		||||
//         }).catch(err => {
 | 
			
		||||
//           console.log(err);
 | 
			
		||||
//         });
 | 
			
		||||
//       } else {
 | 
			
		||||
//         // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
 | 
			
		||||
//         if (hasPermission(store.getters.roles, to.meta.role)) {
 | 
			
		||||
//           next();//
 | 
			
		||||
//         } else {
 | 
			
		||||
//           next({ path: '/401', query: { noGoBack: true } });
 | 
			
		||||
//         }
 | 
			
		||||
//         // 可删 ↑
 | 
			
		||||
//       }
 | 
			
		||||
//     }
 | 
			
		||||
//   } else {
 | 
			
		||||
//     if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
 | 
			
		||||
//       next()
 | 
			
		||||
//     } else {
 | 
			
		||||
//       next('/login'); // 否则全部重定向到登录页
 | 
			
		||||
//       NProgress.done(); // 在hash模式下 改变手动改变hash 重定向回来 不会触发afterEach 暂时hack方案 ps:history模式下无问题,可删除该行!
 | 
			
		||||
//     }
 | 
			
		||||
//   }
 | 
			
		||||
// });
 | 
			
		||||
 | 
			
		||||
new Vue({
 | 
			
		||||
  el: '#app',
 | 
			
		||||
  router,
 | 
			
		||||
  store,
 | 
			
		||||
  template: '<App/>',
 | 
			
		||||
  components: { App }
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										1
									
								
								src/router/_import_development.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/router/_import_development.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
module.exports = file => require('@/views/' + file + '.vue')
 | 
			
		||||
							
								
								
									
										1
									
								
								src/router/_import_production.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/router/_import_production.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
module.exports = file => () => import('@/views/' + file + '.vue')
 | 
			
		||||
							
								
								
									
										60
									
								
								src/router/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/router/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import Router from 'vue-router';
 | 
			
		||||
const _import = require('./_import_' + process.env.NODE_ENV);
 | 
			
		||||
// in development env not use Lazy Loading,because Lazy Loading large page will cause webpack hot update too slow
 | 
			
		||||
// so only in production use Lazy Loading
 | 
			
		||||
 | 
			
		||||
/* layout */
 | 
			
		||||
import Layout from '../views/layout/Layout';
 | 
			
		||||
 | 
			
		||||
/* login */
 | 
			
		||||
const Login = _import('login/index');
 | 
			
		||||
 | 
			
		||||
/* dashboard */
 | 
			
		||||
const dashboard = _import('dashboard/index');
 | 
			
		||||
 | 
			
		||||
/* error page */
 | 
			
		||||
const Err404 = _import('404');
 | 
			
		||||
 | 
			
		||||
const Page = _import('page/index');
 | 
			
		||||
 | 
			
		||||
Vue.use(Router);
 | 
			
		||||
 | 
			
		||||
 /**
 | 
			
		||||
  * icon : the icon show in the sidebar
 | 
			
		||||
  * hidden : if hidden:true will not show in the sidebar
 | 
			
		||||
  * redirect : if redirect:noredirect will not redirct in the levelbar
 | 
			
		||||
  * noDropdown : if noDropdown:true will not has submenu
 | 
			
		||||
  * meta : { role: ['admin'] }  will control the page role
 | 
			
		||||
  **/
 | 
			
		||||
export const constantRouterMap = [
 | 
			
		||||
  { path: '/404', component: Err404, hidden: true },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/',
 | 
			
		||||
    component: Layout,
 | 
			
		||||
    redirect: '/dashboard',
 | 
			
		||||
    name: '首页',
 | 
			
		||||
    hidden: true,
 | 
			
		||||
    children: [{ path: 'dashboard', component: dashboard }]
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export default new Router({
 | 
			
		||||
  // mode: 'history', //后端支持可开
 | 
			
		||||
  scrollBehavior: () => ({ y: 0 }),
 | 
			
		||||
  routes: constantRouterMap
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const asyncRouterMap = [
 | 
			
		||||
  {
 | 
			
		||||
    path: '/example',
 | 
			
		||||
    component: Layout,
 | 
			
		||||
    redirect: 'noredirect',
 | 
			
		||||
    name: 'page',
 | 
			
		||||
    icon: 'zonghe',
 | 
			
		||||
    children: [
 | 
			
		||||
      { path: 'index', component: Page, name: 'page' }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  { path: '*', redirect: '/404', hidden: true }
 | 
			
		||||
];
 | 
			
		||||
							
								
								
									
										14
									
								
								src/store/getters.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/store/getters.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
const getters = {
 | 
			
		||||
  sidebar: state => state.app.sidebar,
 | 
			
		||||
  visitedViews: state => state.app.visitedViews,
 | 
			
		||||
  token: state => state.user.token,
 | 
			
		||||
  avatar: state => state.user.avatar,
 | 
			
		||||
  name: state => state.user.name,
 | 
			
		||||
  uid: state => state.user.uid,
 | 
			
		||||
  email: state => state.user.email,
 | 
			
		||||
  introduction: state => state.user.introduction,
 | 
			
		||||
  roles: state => state.user.roles,
 | 
			
		||||
  permission_routers: state => state.permission.routers,
 | 
			
		||||
  addRouters: state => state.permission.addRouters
 | 
			
		||||
};
 | 
			
		||||
export default getters
 | 
			
		||||
							
								
								
									
										19
									
								
								src/store/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/store/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import Vuex from 'vuex';
 | 
			
		||||
import app from './modules/app';
 | 
			
		||||
import user from './modules/user';
 | 
			
		||||
import permission from './modules/permission';
 | 
			
		||||
import getters from './getters';
 | 
			
		||||
 | 
			
		||||
Vue.use(Vuex);
 | 
			
		||||
 | 
			
		||||
const store = new Vuex.Store({
 | 
			
		||||
  modules: {
 | 
			
		||||
    app,
 | 
			
		||||
    user,
 | 
			
		||||
    permission
 | 
			
		||||
  },
 | 
			
		||||
  getters
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default store
 | 
			
		||||
							
								
								
									
										26
									
								
								src/store/modules/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/store/modules/app.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
import Cookies from 'js-cookie';
 | 
			
		||||
 | 
			
		||||
const app = {
 | 
			
		||||
  state: {
 | 
			
		||||
    sidebar: {
 | 
			
		||||
      opened: !+Cookies.get('sidebarStatus')
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mutations: {
 | 
			
		||||
    TOGGLE_SIDEBAR: state => {
 | 
			
		||||
      if (state.sidebar.opened) {
 | 
			
		||||
        Cookies.set('sidebarStatus', 1);
 | 
			
		||||
      } else {
 | 
			
		||||
        Cookies.set('sidebarStatus', 0);
 | 
			
		||||
      }
 | 
			
		||||
      state.sidebar.opened = !state.sidebar.opened;
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    ToggleSideBar: ({ commit }) => {
 | 
			
		||||
      commit('TOGGLE_SIDEBAR')
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default app;
 | 
			
		||||
							
								
								
									
										62
									
								
								src/store/modules/permission.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/store/modules/permission.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
import { asyncRouterMap, constantRouterMap } from '@/router/index';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 通过meta.role判断是否与当前用户权限匹配
 | 
			
		||||
 * @param roles
 | 
			
		||||
 * @param route
 | 
			
		||||
 */
 | 
			
		||||
function hasPermission(roles, route) {
 | 
			
		||||
  if (route.meta && route.meta.role) {
 | 
			
		||||
    return roles.some(role => route.meta.role.indexOf(role) >= 0)
 | 
			
		||||
  } else {
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 递归过滤异步路由表,返回符合用户角色权限的路由表
 | 
			
		||||
 * @param asyncRouterMap
 | 
			
		||||
 * @param roles
 | 
			
		||||
 */
 | 
			
		||||
function filterAsyncRouter(asyncRouterMap, roles) {
 | 
			
		||||
  const accessedRouters = asyncRouterMap.filter(route => {
 | 
			
		||||
    if (hasPermission(roles, route)) {
 | 
			
		||||
      if (route.children && route.children.length) {
 | 
			
		||||
        route.children = filterAsyncRouter(route.children, roles)
 | 
			
		||||
      }
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
    return false
 | 
			
		||||
  })
 | 
			
		||||
  return accessedRouters
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const permission = {
 | 
			
		||||
  state: {
 | 
			
		||||
    routers: constantRouterMap,
 | 
			
		||||
    addRouters: []
 | 
			
		||||
  },
 | 
			
		||||
  mutations: {
 | 
			
		||||
    SET_ROUTERS: (state, routers) => {
 | 
			
		||||
      state.addRouters = routers;
 | 
			
		||||
      state.routers = constantRouterMap.concat(routers);
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    GenerateRoutes({ commit }, data) {
 | 
			
		||||
      return new Promise(resolve => {
 | 
			
		||||
        const { roles } = data
 | 
			
		||||
        let accessedRouters
 | 
			
		||||
        if (roles.indexOf('admin') >= 0) {
 | 
			
		||||
          accessedRouters = asyncRouterMap
 | 
			
		||||
        } else {
 | 
			
		||||
          accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
 | 
			
		||||
        }
 | 
			
		||||
        commit('SET_ROUTERS', accessedRouters);
 | 
			
		||||
        resolve();
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default permission;
 | 
			
		||||
							
								
								
									
										149
									
								
								src/store/modules/user.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								src/store/modules/user.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,149 @@
 | 
			
		||||
import { loginByEmail, logout, getInfo } from '@/api/login';
 | 
			
		||||
import Cookies from 'js-cookie';
 | 
			
		||||
 | 
			
		||||
const user = {
 | 
			
		||||
  state: {
 | 
			
		||||
    user: '',
 | 
			
		||||
    status: '',
 | 
			
		||||
    email: '',
 | 
			
		||||
    code: '',
 | 
			
		||||
    uid: undefined,
 | 
			
		||||
    auth_type: '',
 | 
			
		||||
    token: Cookies.get('Admin-Token'),
 | 
			
		||||
    name: '',
 | 
			
		||||
    avatar: '',
 | 
			
		||||
    introduction: '',
 | 
			
		||||
    roles: [],
 | 
			
		||||
    setting: {
 | 
			
		||||
      articlePlatform: []
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  mutations: {
 | 
			
		||||
    SET_AUTH_TYPE: (state, type) => {
 | 
			
		||||
      state.auth_type = type;
 | 
			
		||||
    },
 | 
			
		||||
    SET_CODE: (state, code) => {
 | 
			
		||||
      state.code = code;
 | 
			
		||||
    },
 | 
			
		||||
    SET_TOKEN: (state, token) => {
 | 
			
		||||
      state.token = token;
 | 
			
		||||
    },
 | 
			
		||||
    SET_UID: (state, uid) => {
 | 
			
		||||
      state.uid = uid;
 | 
			
		||||
    },
 | 
			
		||||
    SET_EMAIL: (state, email) => {
 | 
			
		||||
      state.email = email;
 | 
			
		||||
    },
 | 
			
		||||
    SET_INTRODUCTION: (state, introduction) => {
 | 
			
		||||
      state.introduction = introduction;
 | 
			
		||||
    },
 | 
			
		||||
    SET_SETTING: (state, setting) => {
 | 
			
		||||
      state.setting = setting;
 | 
			
		||||
    },
 | 
			
		||||
    SET_STATUS: (state, status) => {
 | 
			
		||||
      state.status = status;
 | 
			
		||||
    },
 | 
			
		||||
    SET_NAME: (state, name) => {
 | 
			
		||||
      state.name = name;
 | 
			
		||||
    },
 | 
			
		||||
    SET_AVATAR: (state, avatar) => {
 | 
			
		||||
      state.avatar = avatar;
 | 
			
		||||
    },
 | 
			
		||||
    SET_ROLES: (state, roles) => {
 | 
			
		||||
      state.roles = roles;
 | 
			
		||||
    },
 | 
			
		||||
    LOGIN_SUCCESS: () => {
 | 
			
		||||
      console.log('login success')
 | 
			
		||||
    },
 | 
			
		||||
    LOGOUT_USER: state => {
 | 
			
		||||
      state.user = '';
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  actions: {
 | 
			
		||||
    // 邮箱登录
 | 
			
		||||
    LoginByEmail({ commit }, userInfo) {
 | 
			
		||||
      const email = userInfo.email.trim();
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        loginByEmail(email, userInfo.password).then(response => {
 | 
			
		||||
          const data = response.data;
 | 
			
		||||
          Cookies.set('Admin-Token', response.data.token);
 | 
			
		||||
          commit('SET_TOKEN', data.token);
 | 
			
		||||
          commit('SET_EMAIL', email);
 | 
			
		||||
          resolve();
 | 
			
		||||
        }).catch(error => {
 | 
			
		||||
          reject(error);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // 获取用户信息
 | 
			
		||||
    GetInfo({ commit, state }) {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        getInfo(state.token).then(response => {
 | 
			
		||||
          const data = response.data;
 | 
			
		||||
          commit('SET_ROLES', data.role);
 | 
			
		||||
          commit('SET_NAME', data.name);
 | 
			
		||||
          commit('SET_AVATAR', data.avatar);
 | 
			
		||||
          commit('SET_UID', data.uid);
 | 
			
		||||
          commit('SET_INTRODUCTION', data.introduction);
 | 
			
		||||
          resolve(response);
 | 
			
		||||
        }).catch(error => {
 | 
			
		||||
          reject(error);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 第三方验证登录
 | 
			
		||||
    LoginByThirdparty({ commit, state }, code) {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        commit('SET_CODE', code);
 | 
			
		||||
        loginByThirdparty(state.status, state.email, state.code, state.auth_type).then(response => {
 | 
			
		||||
          commit('SET_TOKEN', response.data.token);
 | 
			
		||||
          Cookies.set('Admin-Token', response.data.token);
 | 
			
		||||
          resolve();
 | 
			
		||||
        }).catch(error => {
 | 
			
		||||
          reject(error);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // 登出
 | 
			
		||||
    LogOut({ commit, state }) {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        logout(state.token).then(() => {
 | 
			
		||||
          commit('SET_TOKEN', '');
 | 
			
		||||
          commit('SET_ROLES', []);
 | 
			
		||||
          Cookies.remove('Admin-Token');
 | 
			
		||||
          resolve();
 | 
			
		||||
        }).catch(error => {
 | 
			
		||||
          reject(error);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 前端 登出
 | 
			
		||||
    FedLogOut({ commit }) {
 | 
			
		||||
      return new Promise(resolve => {
 | 
			
		||||
        commit('SET_TOKEN', '');
 | 
			
		||||
        Cookies.remove('Admin-Token');
 | 
			
		||||
        resolve();
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 动态修改权限
 | 
			
		||||
    ChangeRole({ commit }, role) {
 | 
			
		||||
      return new Promise(resolve => {
 | 
			
		||||
        commit('SET_ROLES', [role]);
 | 
			
		||||
        commit('SET_TOKEN', role);
 | 
			
		||||
        Cookies.set('Admin-Token', role);
 | 
			
		||||
        resolve();
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default user;
 | 
			
		||||
							
								
								
									
										103
									
								
								src/styles/btn.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/styles/btn.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
$blue:#324157;
 | 
			
		||||
$light-blue:#3A71A8;
 | 
			
		||||
$red:#C03639;
 | 
			
		||||
$pink: #E65D6E;
 | 
			
		||||
$green: #30B08F;
 | 
			
		||||
$tiffany: #4AB7BD;
 | 
			
		||||
$yellow:#FEC171;
 | 
			
		||||
 | 
			
		||||
$panGreen: #30B08F;
 | 
			
		||||
 | 
			
		||||
@mixin colorBtn($color) {
 | 
			
		||||
  background: $color;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    color: $color;
 | 
			
		||||
    &:before, &:after {
 | 
			
		||||
      background: $color;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.blue-btn {
 | 
			
		||||
  @include colorBtn($blue)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.light-blue-btn{
 | 
			
		||||
  @include colorBtn($light-blue)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.red-btn {
 | 
			
		||||
  @include colorBtn($red)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pink-btn {
 | 
			
		||||
  @include colorBtn($pink)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.green-btn {
 | 
			
		||||
  @include colorBtn($green)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.tiffany-btn {
 | 
			
		||||
  @include colorBtn($tiffany)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.yellow-btn {
 | 
			
		||||
  @include colorBtn($yellow)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pan-btn {
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  padding: 14px 36px;
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  border: none;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  margin-right: 25px;
 | 
			
		||||
  transition: 600ms ease all;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: #fff;
 | 
			
		||||
    &:before, &:after {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      transition: 600ms ease all;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  &:before, &:after {
 | 
			
		||||
    content: '';
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    height: 2px;
 | 
			
		||||
    width: 0;
 | 
			
		||||
    transition: 400ms ease all;
 | 
			
		||||
  }
 | 
			
		||||
  &::after {
 | 
			
		||||
    right: inherit;
 | 
			
		||||
    top: inherit;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.custom-button{
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    background: #fff;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    -webkit-appearance: none;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    outline: 0;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 10px 15px;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										83
									
								
								src/styles/element-ui.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/styles/element-ui.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
 //覆盖一些element-ui样式
 | 
			
		||||
.block-checkbox {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.operation-container {
 | 
			
		||||
  .cell {
 | 
			
		||||
    padding: 10px !important;
 | 
			
		||||
  }
 | 
			
		||||
  .el-button {
 | 
			
		||||
    &:nth-child(3) {
 | 
			
		||||
      margin-top: 10px;
 | 
			
		||||
      margin-left: 0px;
 | 
			
		||||
    }
 | 
			
		||||
    &:nth-child(4) {
 | 
			
		||||
      margin-top: 10px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-upload {
 | 
			
		||||
  input[type="file"] {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-upload__input {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.cell {
 | 
			
		||||
  .el-tag {
 | 
			
		||||
    margin-right: 8px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.small-padding {
 | 
			
		||||
  .cell {
 | 
			
		||||
    padding-left: 8px;
 | 
			
		||||
    padding-right: 8px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.status-col {
 | 
			
		||||
  .cell {
 | 
			
		||||
    padding: 0 10px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    .el-tag {
 | 
			
		||||
      margin-right: 0px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
 | 
			
		||||
.el-dialog {
 | 
			
		||||
  transform: none;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//文章页textarea修改样式
 | 
			
		||||
.article-textarea {
 | 
			
		||||
  textarea {
 | 
			
		||||
    padding-right: 40px;
 | 
			
		||||
    resize: none;
 | 
			
		||||
    border: none;
 | 
			
		||||
    border-radius: 0px;
 | 
			
		||||
    border-bottom: 1px solid #bfcbd9;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//element ui upload
 | 
			
		||||
.upload-container {
 | 
			
		||||
  .el-upload {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    .el-upload-dragger {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      height: 200px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										266
									
								
								src/styles/index.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								src/styles/index.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,266 @@
 | 
			
		||||
@import './btn.scss';
 | 
			
		||||
@import './element-ui.scss';
 | 
			
		||||
@import "./mixin.scss";
 | 
			
		||||
body {
 | 
			
		||||
  -moz-osx-font-smoothing: grayscale;
 | 
			
		||||
  -webkit-font-smoothing: antialiased;
 | 
			
		||||
  text-rendering: optimizeLegibility;
 | 
			
		||||
  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
label {
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html {
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
*,
 | 
			
		||||
*:before,
 | 
			
		||||
*:after {
 | 
			
		||||
  box-sizing: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.no-padding {
 | 
			
		||||
  padding: 0px !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-content {
 | 
			
		||||
  padding: 4px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:focus,
 | 
			
		||||
a:active {
 | 
			
		||||
  outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a,
 | 
			
		||||
a:focus,
 | 
			
		||||
a:hover {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  color: inherit;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fr {
 | 
			
		||||
  float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fl {
 | 
			
		||||
  float: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pr-5 {
 | 
			
		||||
  padding-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pl-5 {
 | 
			
		||||
  padding-left: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.block {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pointer {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.inlineBlock {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
code {
 | 
			
		||||
  background: #eef1f6;
 | 
			
		||||
  padding: 15px 10px;
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
  display: block;
 | 
			
		||||
  line-height: 36px;
 | 
			
		||||
  a {
 | 
			
		||||
    color: #337ab7;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    &:hover {
 | 
			
		||||
      color: rgb(32, 160, 255);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fade-enter-active,
 | 
			
		||||
.fade-leave-active {
 | 
			
		||||
  transition: all .2s ease
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fade-enter,
 | 
			
		||||
.fade-leave-active {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//main-container全局样式
 | 
			
		||||
.app-container {
 | 
			
		||||
  padding: 20px;
 | 
			
		||||
}
 | 
			
		||||
.components-container {
 | 
			
		||||
  margin: 30px 50px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
.pagination-container {
 | 
			
		||||
  margin-top: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.editor-container .CodeMirror {
 | 
			
		||||
  height: 100%!important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wscn-icon {
 | 
			
		||||
  width: 1em;
 | 
			
		||||
  height: 1em;
 | 
			
		||||
  vertical-align: -0.15em;
 | 
			
		||||
  fill: currentColor;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sub-navbar {
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  line-height: 50px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  text-align: right;
 | 
			
		||||
  padding-right: 20px;
 | 
			
		||||
  transition: 600ms ease position;
 | 
			
		||||
  background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
 | 
			
		||||
  .subtitle {
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
  }
 | 
			
		||||
  &.draft {
 | 
			
		||||
    background: #d0d0d0;
 | 
			
		||||
  }
 | 
			
		||||
  &.deleted {
 | 
			
		||||
    background: #d0d0d0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.link-type,
 | 
			
		||||
.link-type:focus {
 | 
			
		||||
  color: #337ab7;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    color: rgb(32, 160, 255);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.publishedTag,
 | 
			
		||||
.draftTag,
 | 
			
		||||
.deletedTag {
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  background-color: $panGreen;
 | 
			
		||||
  line-height: 1;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 8px 12px;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 20px;
 | 
			
		||||
  top: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.draftTag {
 | 
			
		||||
  background-color: $yellow;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.deletedTag {
 | 
			
		||||
  background-color: $red;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.input-label {
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  color: #48576a;
 | 
			
		||||
  line-height: 1;
 | 
			
		||||
  padding: 11px 5px 11px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.clearfix {
 | 
			
		||||
  &:after {
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
    display: block;
 | 
			
		||||
    font-size: 0;
 | 
			
		||||
    content: " ";
 | 
			
		||||
    clear: both;
 | 
			
		||||
    height: 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.no-marginLeft {
 | 
			
		||||
  .el-checkbox {
 | 
			
		||||
    margin: 0 20px 15px 0;
 | 
			
		||||
  }
 | 
			
		||||
  .el-checkbox+.el-checkbox {
 | 
			
		||||
    margin-left: 0px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.filter-container {
 | 
			
		||||
  padding-bottom: 10px;
 | 
			
		||||
  .filter-item {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//refine vue-multiselect plugin
 | 
			
		||||
.multiselect {
 | 
			
		||||
  line-height: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.multiselect--active {
 | 
			
		||||
  z-index: 1000 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//refine simplemde
 | 
			
		||||
.simplemde-container{
 | 
			
		||||
  .editor-toolbar.fullscreen,.CodeMirror-fullscreen{
 | 
			
		||||
    z-index: 1003;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
 | 
			
		||||
.el-dialog {
 | 
			
		||||
  transform: none;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//github-corner
 | 
			
		||||
.github-corner:hover .octo-arm {
 | 
			
		||||
  animation: octocat-wave 560ms ease-in-out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes octocat-wave {
 | 
			
		||||
  0%,
 | 
			
		||||
  100% {
 | 
			
		||||
    transform: rotate(0)
 | 
			
		||||
  }
 | 
			
		||||
  20%,
 | 
			
		||||
  60% {
 | 
			
		||||
    transform: rotate(-25deg)
 | 
			
		||||
  }
 | 
			
		||||
  40%,
 | 
			
		||||
  80% {
 | 
			
		||||
    transform: rotate(10deg)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width:500px) {
 | 
			
		||||
  .github-corner:hover .octo-arm {
 | 
			
		||||
    animation: none
 | 
			
		||||
  }
 | 
			
		||||
  .github-corner .octo-arm {
 | 
			
		||||
    animation: octocat-wave 560ms ease-in-out
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								src/styles/mixin.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/styles/mixin.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
@mixin clearfix {
 | 
			
		||||
  &:after {
 | 
			
		||||
    content: "";
 | 
			
		||||
    display: table;
 | 
			
		||||
    clear: both;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin scrollBar {
 | 
			
		||||
  &::-webkit-scrollbar-track-piece {
 | 
			
		||||
    background: #d3dce6;
 | 
			
		||||
  }
 | 
			
		||||
  &::-webkit-scrollbar {
 | 
			
		||||
    width: 6px;
 | 
			
		||||
  }
 | 
			
		||||
  &::-webkit-scrollbar-thumb {
 | 
			
		||||
    background: #99a9bf;
 | 
			
		||||
    border-radius: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin relative {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin pct($pct) {
 | 
			
		||||
  width: #{$pct};
 | 
			
		||||
  position: relative;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin triangle($width, $height, $color, $direction) {
 | 
			
		||||
  $width: $width/2;
 | 
			
		||||
  $color-border-style: $height solid $color;
 | 
			
		||||
  $transparent-border-style: $width solid transparent;
 | 
			
		||||
  height: 0;
 | 
			
		||||
  width: 0;
 | 
			
		||||
  @if $direction==up {
 | 
			
		||||
    border-bottom: $color-border-style;
 | 
			
		||||
    border-left: $transparent-border-style;
 | 
			
		||||
    border-right: $transparent-border-style;
 | 
			
		||||
  }
 | 
			
		||||
  @else if $direction==right {
 | 
			
		||||
    border-left: $color-border-style;
 | 
			
		||||
    border-top: $transparent-border-style;
 | 
			
		||||
    border-bottom: $transparent-border-style;
 | 
			
		||||
  }
 | 
			
		||||
  @else if $direction==down {
 | 
			
		||||
    border-top: $color-border-style;
 | 
			
		||||
    border-left: $transparent-border-style;
 | 
			
		||||
    border-right: $transparent-border-style;
 | 
			
		||||
  }
 | 
			
		||||
  @else if $direction==left {
 | 
			
		||||
    border-right: $color-border-style;
 | 
			
		||||
    border-top: $transparent-border-style;
 | 
			
		||||
    border-bottom: $transparent-border-style;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								src/utils/createUniqueString.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/utils/createUniqueString.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Created by jiachenpan on 17/3/8.
 | 
			
		||||
 */
 | 
			
		||||
export default function createUniqueString() {
 | 
			
		||||
  const timestamp = +new Date() + '';
 | 
			
		||||
  const randomNum = parseInt((1 + Math.random()) * 65536) + '';
 | 
			
		||||
  return (+(randomNum + timestamp)).toString(32);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								src/utils/fetch.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/utils/fetch.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import { Message } from 'element-ui';
 | 
			
		||||
import store from '../store';
 | 
			
		||||
// import router from '../router';
 | 
			
		||||
 | 
			
		||||
// 创建axios实例
 | 
			
		||||
const service = axios.create({
 | 
			
		||||
  baseURL: process.env.BASE_API, // api的base_url
 | 
			
		||||
  timeout: 5000                  // 请求超时时间
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// request拦截器
 | 
			
		||||
service.interceptors.request.use(config => {
 | 
			
		||||
  // Do something before request is sent
 | 
			
		||||
  if (store.getters.token) {
 | 
			
		||||
    config.headers['X-Token'] = store.getters.token; // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改
 | 
			
		||||
  }
 | 
			
		||||
  return config;
 | 
			
		||||
}, error => {
 | 
			
		||||
  // Do something with request error
 | 
			
		||||
  console.log(error); // for debug
 | 
			
		||||
  Promise.reject(error);
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// respone拦截器
 | 
			
		||||
service.interceptors.response.use(
 | 
			
		||||
  response => response,
 | 
			
		||||
  /**
 | 
			
		||||
  * 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页
 | 
			
		||||
  * 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中
 | 
			
		||||
  */
 | 
			
		||||
//  const res = response.data;
 | 
			
		||||
//     if (res.code !== 20000) {
 | 
			
		||||
//       Message({
 | 
			
		||||
//         message: res.message,
 | 
			
		||||
//         type: 'error',
 | 
			
		||||
//         duration: 5 * 1000
 | 
			
		||||
//       });
 | 
			
		||||
//       // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
 | 
			
		||||
//       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
 | 
			
		||||
//           });
 | 
			
		||||
//         })
 | 
			
		||||
//       }
 | 
			
		||||
//       return Promise.reject(error);
 | 
			
		||||
//     } else {
 | 
			
		||||
//       return response.data;
 | 
			
		||||
//     }
 | 
			
		||||
  error => {
 | 
			
		||||
    console.log('err' + error);// for debug
 | 
			
		||||
    Message({
 | 
			
		||||
      message: error.message,
 | 
			
		||||
      type: 'error',
 | 
			
		||||
      duration: 5 * 1000
 | 
			
		||||
    });
 | 
			
		||||
    return Promise.reject(error);
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
export default service;
 | 
			
		||||
							
								
								
									
										214
									
								
								src/utils/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								src/utils/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,214 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Created by jiachenpan on 16/11/18.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 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 (('' + time).length === 10) time = parseInt(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];
 | 
			
		||||
     if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1];
 | 
			
		||||
     if (result.length > 0 && value < 10) {
 | 
			
		||||
       value = '0' + value;
 | 
			
		||||
     }
 | 
			
		||||
     return value || 0;
 | 
			
		||||
   });
 | 
			
		||||
   return time_str;
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
 export function formatTime(time, option) {
 | 
			
		||||
   time = +time * 1000;
 | 
			
		||||
   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() + '分'
 | 
			
		||||
   }
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
// 格式化时间
 | 
			
		||||
 export function getQueryObject(url) {
 | 
			
		||||
   url = url == null ? window.location.href : url;
 | 
			
		||||
   const search = url.substring(url.lastIndexOf('?') + 1);
 | 
			
		||||
   const obj = {};
 | 
			
		||||
   const reg = /([^?&=]+)=([^?&=]*)/g;
 | 
			
		||||
   search.replace(reg, (rs, $1, $2) => {
 | 
			
		||||
     const name = decodeURIComponent($1);
 | 
			
		||||
     let val = decodeURIComponent($2);
 | 
			
		||||
     val = String(val);
 | 
			
		||||
     obj[name] = val;
 | 
			
		||||
     return rs;
 | 
			
		||||
   });
 | 
			
		||||
   return obj;
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *get getByteLen
 | 
			
		||||
 * @param {Sting} val input value
 | 
			
		||||
 * @returns {number} output value
 | 
			
		||||
 */
 | 
			
		||||
 export function getByteLen(val) {
 | 
			
		||||
   let len = 0;
 | 
			
		||||
   for (let i = 0; i < val.length; i++) {
 | 
			
		||||
     if (val[i].match(/[^\x00-\xff]/ig) != null) {
 | 
			
		||||
       len += 1;
 | 
			
		||||
     } else { len += 0.5; }
 | 
			
		||||
   }
 | 
			
		||||
   return Math.floor(len);
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
 export function cleanArray(actual) {
 | 
			
		||||
   const newArray = [];
 | 
			
		||||
   for (let i = 0; i < actual.length; i++) {
 | 
			
		||||
     if (actual[i]) {
 | 
			
		||||
       newArray.push(actual[i]);
 | 
			
		||||
     }
 | 
			
		||||
   }
 | 
			
		||||
   return newArray;
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
 export function param(json) {
 | 
			
		||||
   if (!json) return '';
 | 
			
		||||
   return cleanArray(Object.keys(json).map(key => {
 | 
			
		||||
     if (json[key] === undefined) return '';
 | 
			
		||||
     return encodeURIComponent(key) + '=' +
 | 
			
		||||
            encodeURIComponent(json[key]);
 | 
			
		||||
   })).join('&');
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
 export function param2Obj(url) {
 | 
			
		||||
   const search = url.split('?')[1];
 | 
			
		||||
   return JSON.parse('{"' + decodeURIComponent(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}')
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
 export function html2Text(val) {
 | 
			
		||||
   const div = document.createElement('div');
 | 
			
		||||
   div.innerHTML = val;
 | 
			
		||||
   return div.textContent || div.innerText;
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
 export function objectMerge(target, source) {
 | 
			
		||||
    /* Merges two  objects,
 | 
			
		||||
     giving the last one precedence */
 | 
			
		||||
 | 
			
		||||
   if (typeof target !== 'object') {
 | 
			
		||||
     target = {};
 | 
			
		||||
   }
 | 
			
		||||
   if (Array.isArray(source)) {
 | 
			
		||||
     return source.slice();
 | 
			
		||||
   }
 | 
			
		||||
   for (const property in source) {
 | 
			
		||||
     if (source.hasOwnProperty(property)) {
 | 
			
		||||
       const sourceProperty = source[property];
 | 
			
		||||
       if (typeof sourceProperty === 'object') {
 | 
			
		||||
         target[property] = objectMerge(target[property], sourceProperty);
 | 
			
		||||
         continue;
 | 
			
		||||
       }
 | 
			
		||||
       target[property] = sourceProperty;
 | 
			
		||||
     }
 | 
			
		||||
   }
 | 
			
		||||
   return target;
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 export function scrollTo(element, to, duration) {
 | 
			
		||||
   if (duration <= 0) return;
 | 
			
		||||
   const difference = to - element.scrollTop;
 | 
			
		||||
   const perTick = difference / duration * 10;
 | 
			
		||||
   setTimeout(() => {
 | 
			
		||||
     console.log(new Date())
 | 
			
		||||
     element.scrollTop = element.scrollTop + perTick;
 | 
			
		||||
     if (element.scrollTop === to) return;
 | 
			
		||||
     scrollTo(element, to, duration - 10);
 | 
			
		||||
   }, 10);
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
 export function toggleClass(element, className) {
 | 
			
		||||
   if (!element || !className) {
 | 
			
		||||
     return;
 | 
			
		||||
   }
 | 
			
		||||
   let classString = element.className;
 | 
			
		||||
   const nameIndex = classString.indexOf(className);
 | 
			
		||||
   if (nameIndex === -1) {
 | 
			
		||||
     classString += '' + className;
 | 
			
		||||
   } else {
 | 
			
		||||
     classString = classString.substr(0, nameIndex) + classString.substr(nameIndex + className.length);
 | 
			
		||||
   }
 | 
			
		||||
   element.className = classString;
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
 export const pickerOptions = [
 | 
			
		||||
   {
 | 
			
		||||
     text: '今天',
 | 
			
		||||
     onClick(picker) {
 | 
			
		||||
       const end = new Date();
 | 
			
		||||
       const start = new Date(new Date().toDateString());
 | 
			
		||||
       end.setTime(start.getTime());
 | 
			
		||||
       picker.$emit('pick', [start, end]);
 | 
			
		||||
     }
 | 
			
		||||
   }, {
 | 
			
		||||
     text: '最近一周',
 | 
			
		||||
     onClick(picker) {
 | 
			
		||||
       const end = new Date(new Date().toDateString());
 | 
			
		||||
       const start = new Date();
 | 
			
		||||
       start.setTime(end.getTime() - 3600 * 1000 * 24 * 7);
 | 
			
		||||
       picker.$emit('pick', [start, end]);
 | 
			
		||||
     }
 | 
			
		||||
   }, {
 | 
			
		||||
     text: '最近一个月',
 | 
			
		||||
     onClick(picker) {
 | 
			
		||||
       const end = new Date(new Date().toDateString());
 | 
			
		||||
       const start = new Date();
 | 
			
		||||
       start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
 | 
			
		||||
       picker.$emit('pick', [start, end]);
 | 
			
		||||
     }
 | 
			
		||||
   }, {
 | 
			
		||||
     text: '最近三个月',
 | 
			
		||||
     onClick(picker) {
 | 
			
		||||
       const end = new Date(new Date().toDateString());
 | 
			
		||||
       const start = new Date();
 | 
			
		||||
       start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
 | 
			
		||||
       picker.$emit('pick', [start, end]);
 | 
			
		||||
     }
 | 
			
		||||
   }]
 | 
			
		||||
 | 
			
		||||
 export function getTime(type) {
 | 
			
		||||
   if (type === 'start') {
 | 
			
		||||
     return new Date().getTime() - 3600 * 1000 * 24 * 90
 | 
			
		||||
   } else {
 | 
			
		||||
     return new Date(new Date().toDateString())
 | 
			
		||||
   }
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								src/utils/openWindow.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/utils/openWindow.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
/**
 | 
			
		||||
 *Created by jiachenpan on 16/11/29.
 | 
			
		||||
 * @param {Sting} url
 | 
			
		||||
 * @param {Sting} title
 | 
			
		||||
 * @param {Number} w
 | 
			
		||||
 * @param {Number} h
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export default function openWindow(url, title, w, h) {
 | 
			
		||||
      // Fixes dual-screen position                         Most browsers      Firefox
 | 
			
		||||
  const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left;
 | 
			
		||||
  const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top;
 | 
			
		||||
 | 
			
		||||
  const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
 | 
			
		||||
  const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
 | 
			
		||||
 | 
			
		||||
  const left = ((width / 2) - (w / 2)) + dualScreenLeft;
 | 
			
		||||
  const top = ((height / 2) - (h / 2)) + dualScreenTop;
 | 
			
		||||
  const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
 | 
			
		||||
 | 
			
		||||
  // Puts focus on the newWindow
 | 
			
		||||
  if (window.focus) {
 | 
			
		||||
    newWindow.focus();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										41
									
								
								src/utils/validate.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/utils/validate.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Created by jiachenpan on 16/11/18.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/* 是否是公司邮箱*/
 | 
			
		||||
export function isWscnEmail(str) {
 | 
			
		||||
  const reg = /^[a-z0-9](?:[-_.+]?[a-z0-9]+)*@wallstreetcn\.com$/i;
 | 
			
		||||
  return reg.test(str.trim());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 合法uri*/
 | 
			
		||||
export function validateURL(textval) {
 | 
			
		||||
  const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
 | 
			
		||||
  return urlregex.test(textval);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 小写字母*/
 | 
			
		||||
export function validateLowerCase(str) {
 | 
			
		||||
  const reg = /^[a-z]+$/;
 | 
			
		||||
  return reg.test(str);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 验证key*/
 | 
			
		||||
// export function validateKey(str) {
 | 
			
		||||
//     var reg = /^[a-z_\-:]+$/;
 | 
			
		||||
//     return reg.test(str);
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
/* 大写字母*/
 | 
			
		||||
export function validateUpperCase(str) {
 | 
			
		||||
  const reg = /^[A-Z]+$/;
 | 
			
		||||
  return reg.test(str);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 大小写字母*/
 | 
			
		||||
export function validatAlphabets(str) {
 | 
			
		||||
  const reg = /^[A-Za-z]+$/;
 | 
			
		||||
  return reg.test(str);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										228
									
								
								src/views/404.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								src/views/404.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,228 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div style="background:#f0f2f5;margin-top: -20px;">
 | 
			
		||||
    <div class="wscn-http404">
 | 
			
		||||
      <div class="pic-404">
 | 
			
		||||
        <img class="pic-404__parent" :src="img_404" alt="404">
 | 
			
		||||
        <img class="pic-404__child left" :src="img_404_cloud" alt="404">
 | 
			
		||||
        <img class="pic-404__child mid" :src="img_404_cloud" alt="404">
 | 
			
		||||
        <img class="pic-404__child right" :src="img_404_cloud" alt="404">
 | 
			
		||||
      </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>
 | 
			
		||||
        <div class="bullshit__headline">{{ message }}</div>
 | 
			
		||||
        <div class="bullshit__info">请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告</div>
 | 
			
		||||
        <a href="/" class="bullshit__return-home">返回首页</a>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 import img_404 from '@/assets/404_images/404.png'
 | 
			
		||||
 import img_404_cloud from '@/assets/404_images/404_cloud.png'
 | 
			
		||||
 | 
			
		||||
 export default {
 | 
			
		||||
   data: {
 | 
			
		||||
     return: {
 | 
			
		||||
       img_404,
 | 
			
		||||
       img_404_cloud
 | 
			
		||||
     }
 | 
			
		||||
   },
 | 
			
		||||
   computed: {
 | 
			
		||||
     message() {
 | 
			
		||||
       return '特朗普说这个页面你不能进......'
 | 
			
		||||
     }
 | 
			
		||||
   }
 | 
			
		||||
 }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
			
		||||
  .wscn-http404 {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    width: 1200px;
 | 
			
		||||
    margin: 20px auto 60px;
 | 
			
		||||
    padding: 0 100px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    .pic-404 {
 | 
			
		||||
      position: relative;
 | 
			
		||||
      float: left;
 | 
			
		||||
      width: 600px;
 | 
			
		||||
      padding: 150px 0;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      &__parent {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
      }
 | 
			
		||||
      &__child {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        &.left {
 | 
			
		||||
          width: 80px;
 | 
			
		||||
          top: 17px;
 | 
			
		||||
          left: 220px;
 | 
			
		||||
          opacity: 0;
 | 
			
		||||
          animation-name: cloudLeft;
 | 
			
		||||
          animation-duration: 2s;
 | 
			
		||||
          animation-timing-function: linear;
 | 
			
		||||
          animation-fill-mode: forwards;
 | 
			
		||||
          animation-delay: 1s;
 | 
			
		||||
        }
 | 
			
		||||
        &.mid {
 | 
			
		||||
          width: 46px;
 | 
			
		||||
          top: 10px;
 | 
			
		||||
          left: 420px;
 | 
			
		||||
          opacity: 0;
 | 
			
		||||
          animation-name: cloudMid;
 | 
			
		||||
          animation-duration: 2s;
 | 
			
		||||
          animation-timing-function: linear;
 | 
			
		||||
          animation-fill-mode: forwards;
 | 
			
		||||
          animation-delay: 1.2s;
 | 
			
		||||
        }
 | 
			
		||||
        &.right {
 | 
			
		||||
          width: 62px;
 | 
			
		||||
          top: 100px;
 | 
			
		||||
          left: 500px;
 | 
			
		||||
          opacity: 0;
 | 
			
		||||
          animation-name: cloudRight;
 | 
			
		||||
          animation-duration: 2s;
 | 
			
		||||
          animation-timing-function: linear;
 | 
			
		||||
          animation-fill-mode: forwards;
 | 
			
		||||
          animation-delay: 1s;
 | 
			
		||||
        }
 | 
			
		||||
        @keyframes cloudLeft {
 | 
			
		||||
          0% {
 | 
			
		||||
            top: 17px;
 | 
			
		||||
            left: 220px;
 | 
			
		||||
            opacity: 0;
 | 
			
		||||
          }
 | 
			
		||||
          20% {
 | 
			
		||||
            top: 33px;
 | 
			
		||||
            left: 188px;
 | 
			
		||||
            opacity: 1;
 | 
			
		||||
          }
 | 
			
		||||
          80% {
 | 
			
		||||
            top: 81px;
 | 
			
		||||
            left: 92px;
 | 
			
		||||
            opacity: 1;
 | 
			
		||||
          }
 | 
			
		||||
          100% {
 | 
			
		||||
            top: 97px;
 | 
			
		||||
            left: 60px;
 | 
			
		||||
            opacity: 0;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        @keyframes cloudMid {
 | 
			
		||||
          0% {
 | 
			
		||||
            top: 10px;
 | 
			
		||||
            left: 420px;
 | 
			
		||||
            opacity: 0;
 | 
			
		||||
          }
 | 
			
		||||
          20% {
 | 
			
		||||
            top: 40px;
 | 
			
		||||
            left: 360px;
 | 
			
		||||
            opacity: 1;
 | 
			
		||||
          }
 | 
			
		||||
          70% {
 | 
			
		||||
            top: 130px;
 | 
			
		||||
            left: 180px;
 | 
			
		||||
            opacity: 1;
 | 
			
		||||
          }
 | 
			
		||||
          100% {
 | 
			
		||||
            top: 160px;
 | 
			
		||||
            left: 120px;
 | 
			
		||||
            opacity: 0;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        @keyframes cloudRight {
 | 
			
		||||
          0% {
 | 
			
		||||
            top: 100px;
 | 
			
		||||
            left: 500px;
 | 
			
		||||
            opacity: 0;
 | 
			
		||||
          }
 | 
			
		||||
          20% {
 | 
			
		||||
            top: 120px;
 | 
			
		||||
            left: 460px;
 | 
			
		||||
            opacity: 1;
 | 
			
		||||
          }
 | 
			
		||||
          80% {
 | 
			
		||||
            top: 180px;
 | 
			
		||||
            left: 340px;
 | 
			
		||||
            opacity: 1;
 | 
			
		||||
          }
 | 
			
		||||
          100% {
 | 
			
		||||
            top: 200px;
 | 
			
		||||
            left: 300px;
 | 
			
		||||
            opacity: 0;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .bullshit {
 | 
			
		||||
      position: relative;
 | 
			
		||||
      float: left;
 | 
			
		||||
      width: 300px;
 | 
			
		||||
      padding: 150px 0;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      &__oops {
 | 
			
		||||
        font-size: 32px;
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        line-height: 40px;
 | 
			
		||||
        color: #1482f0;
 | 
			
		||||
        opacity: 0;
 | 
			
		||||
        margin-bottom: 20px;
 | 
			
		||||
        animation-name: slideUp;
 | 
			
		||||
        animation-duration: 0.5s;
 | 
			
		||||
        animation-fill-mode: forwards;
 | 
			
		||||
      }
 | 
			
		||||
      &__headline {
 | 
			
		||||
        font-size: 20px;
 | 
			
		||||
        line-height: 24px;
 | 
			
		||||
        color: #1482f0;
 | 
			
		||||
        opacity: 0;
 | 
			
		||||
        margin-bottom: 10px;
 | 
			
		||||
        animation-name: slideUp;
 | 
			
		||||
        animation-duration: 0.5s;
 | 
			
		||||
        animation-delay: 0.1s;
 | 
			
		||||
        animation-fill-mode: forwards;
 | 
			
		||||
      }
 | 
			
		||||
      &__info {
 | 
			
		||||
        font-size: 13px;
 | 
			
		||||
        line-height: 21px;
 | 
			
		||||
        color: grey;
 | 
			
		||||
        opacity: 0;
 | 
			
		||||
        margin-bottom: 30px;
 | 
			
		||||
        animation-name: slideUp;
 | 
			
		||||
        animation-duration: 0.5s;
 | 
			
		||||
        animation-delay: 0.2s;
 | 
			
		||||
        animation-fill-mode: forwards;
 | 
			
		||||
      }
 | 
			
		||||
      &__return-home {
 | 
			
		||||
        display: block;
 | 
			
		||||
        float: left;
 | 
			
		||||
        width: 110px;
 | 
			
		||||
        height: 36px;
 | 
			
		||||
        background: #1482f0;
 | 
			
		||||
        border-radius: 100px;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        color: #ffffff;
 | 
			
		||||
        opacity: 0;
 | 
			
		||||
        font-size: 14px;
 | 
			
		||||
        line-height: 36px;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        animation-name: slideUp;
 | 
			
		||||
        animation-duration: 0.5s;
 | 
			
		||||
        animation-delay: 0.3s;
 | 
			
		||||
        animation-fill-mode: forwards;
 | 
			
		||||
      }
 | 
			
		||||
      @keyframes slideUp {
 | 
			
		||||
        0% {
 | 
			
		||||
          transform: translateY(60px);
 | 
			
		||||
          opacity: 0;
 | 
			
		||||
        }
 | 
			
		||||
        100% {
 | 
			
		||||
          transform: translateY(0);
 | 
			
		||||
          opacity: 1;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										5
									
								
								src/views/dashboard/default/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/views/dashboard/default/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="dashboard-editor-container">
 | 
			
		||||
        dashboard
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
							
								
								
									
										38
									
								
								src/views/dashboard/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/views/dashboard/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="dashboard-container">
 | 
			
		||||
        <component v-bind:is="currentRole"> </component>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    import { mapGetters } from 'vuex';
 | 
			
		||||
    import DefaultDashboard from './default/index';
 | 
			
		||||
    export default {
 | 
			
		||||
      name: 'dashboard',
 | 
			
		||||
      components: { DefaultDashboard },
 | 
			
		||||
      data() {
 | 
			
		||||
        return {
 | 
			
		||||
          currentRole: 'DefaultDashboard'
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      computed: {
 | 
			
		||||
        ...mapGetters([
 | 
			
		||||
          'name',
 | 
			
		||||
          'avatar',
 | 
			
		||||
          'email',
 | 
			
		||||
          'introduction',
 | 
			
		||||
          'roles'
 | 
			
		||||
        ])
 | 
			
		||||
      },
 | 
			
		||||
      created() {
 | 
			
		||||
        if (this.roles.indexOf('admin') >= 0) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        // const isEditor = this.roles.some(v => v.indexOf('editor') >= 0)
 | 
			
		||||
        // if (!isEditor) {
 | 
			
		||||
        //   this.currentRole = 'DefaultDashboard';
 | 
			
		||||
        // }
 | 
			
		||||
        this.currentRole = 'DefaultDashboard';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										18
									
								
								src/views/layout/AppMain.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/views/layout/AppMain.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <section class="app-main" style="min-height: 100%">
 | 
			
		||||
        <transition name="fade" mode="out-in">
 | 
			
		||||
            <router-view :key="key"></router-view>
 | 
			
		||||
        </transition>
 | 
			
		||||
    </section>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    export default {
 | 
			
		||||
      name: 'AppMain',
 | 
			
		||||
      computed: {
 | 
			
		||||
        key() {
 | 
			
		||||
          return this.$route.name !== undefined ? this.$route.name + +new Date() : this.$route + +new Date()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										80
									
								
								src/views/layout/Layout.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/views/layout/Layout.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="app-wrapper" :class="{hideSidebar:!sidebar.opened}">
 | 
			
		||||
        <div class="sidebar-wrapper">
 | 
			
		||||
            <Sidebar class="sidebar-container" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="main-container">
 | 
			
		||||
            <Navbar/>
 | 
			
		||||
            <App-main/>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    import { Navbar, Sidebar, AppMain } from '@/views/layout';
 | 
			
		||||
 | 
			
		||||
    export default {
 | 
			
		||||
      name: 'layout',
 | 
			
		||||
      components: {
 | 
			
		||||
        Navbar,
 | 
			
		||||
        Sidebar,
 | 
			
		||||
        AppMain
 | 
			
		||||
      },
 | 
			
		||||
      computed: {
 | 
			
		||||
        sidebar() {
 | 
			
		||||
          return this.$store.state.app.sidebar;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
			
		||||
    @import "src/styles/mixin.scss";
 | 
			
		||||
    .app-wrapper {
 | 
			
		||||
        @include clearfix;
 | 
			
		||||
        position: relative;
 | 
			
		||||
        height: 100%;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        &.hideSidebar {
 | 
			
		||||
            .sidebar-wrapper {
 | 
			
		||||
                transform: translate(-140px, 0);
 | 
			
		||||
                .sidebar-container {
 | 
			
		||||
                    transform: translate(132px, 0);
 | 
			
		||||
                }
 | 
			
		||||
                &:hover {
 | 
			
		||||
                    transform: translate(0, 0);
 | 
			
		||||
                    .sidebar-container {
 | 
			
		||||
                        transform: translate(0, 0);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .main-container{
 | 
			
		||||
                margin-left: 40px;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .sidebar-wrapper {
 | 
			
		||||
            width: 180px;
 | 
			
		||||
            position: fixed;
 | 
			
		||||
            top: 0;
 | 
			
		||||
            bottom: 0;
 | 
			
		||||
            left: 0;
 | 
			
		||||
            z-index: 1001;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
            transition: all .28s ease-out;
 | 
			
		||||
        }
 | 
			
		||||
        .sidebar-container {
 | 
			
		||||
            transition: all .28s ease-out;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            top: 0;
 | 
			
		||||
            bottom: 0;
 | 
			
		||||
            left: 0;
 | 
			
		||||
            right: -17px;
 | 
			
		||||
            overflow-y: scroll;
 | 
			
		||||
        }
 | 
			
		||||
        .main-container {
 | 
			
		||||
            min-height: 100%;
 | 
			
		||||
            transition: all .28s ease-out;
 | 
			
		||||
            margin-left: 180px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										49
									
								
								src/views/layout/Levelbar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/views/layout/Levelbar.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-breadcrumb class="app-levelbar" separator="/">
 | 
			
		||||
    <el-breadcrumb-item v-for="(item,index)  in levelList" :key="item">
 | 
			
		||||
      <router-link v-if='item.redirect==="noredirect"||index==levelList.length-1' to="" class="no-redirect">{{item.name}}</router-link>
 | 
			
		||||
      <router-link v-else :to="item.path">{{item.name}}</router-link>
 | 
			
		||||
    </el-breadcrumb-item>
 | 
			
		||||
  </el-breadcrumb>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    export default {
 | 
			
		||||
      created() {
 | 
			
		||||
        this.getBreadcrumb()
 | 
			
		||||
      },
 | 
			
		||||
      data() {
 | 
			
		||||
        return {
 | 
			
		||||
          levelList: null
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      methods: {
 | 
			
		||||
        getBreadcrumb() {
 | 
			
		||||
          let matched = this.$route.matched.filter(item => item.name);
 | 
			
		||||
          const first = matched[0];
 | 
			
		||||
          if (first && (first.name !== '首页' || first.path !== '')) {
 | 
			
		||||
            matched = [{ name: '首页', path: '/' }].concat(matched)
 | 
			
		||||
          }
 | 
			
		||||
          this.levelList = matched;
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      watch: {
 | 
			
		||||
        $route() {
 | 
			
		||||
          this.getBreadcrumb();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
			
		||||
    .app-levelbar.el-breadcrumb {
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        font-size: 14px;
 | 
			
		||||
        line-height: 50px;
 | 
			
		||||
        margin-left: 10px;
 | 
			
		||||
        .no-redirect{
 | 
			
		||||
          color: #97a8be;
 | 
			
		||||
          cursor:text;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										105
									
								
								src/views/layout/Navbar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/views/layout/Navbar.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-menu class="navbar" mode="horizontal">
 | 
			
		||||
        <hamburger class="hamburger-container" :toggleClick="toggleSideBar" :isActive="sidebar.opened"></hamburger>
 | 
			
		||||
        <levelbar></levelbar>
 | 
			
		||||
        <el-dropdown class="avatar-container" trigger="click">
 | 
			
		||||
            <div class="avatar-wrapper">
 | 
			
		||||
                <img class="user-avatar" :src="avatar+'?imageView2/1/w/80/h/80'">
 | 
			
		||||
                <i class="el-icon-caret-bottom"></i>
 | 
			
		||||
            </div>
 | 
			
		||||
            <el-dropdown-menu class="user-dropdown" slot="dropdown">
 | 
			
		||||
                <router-link class='inlineBlock' to="/">
 | 
			
		||||
                    <el-dropdown-item>
 | 
			
		||||
                        首页
 | 
			
		||||
                    </el-dropdown-item>
 | 
			
		||||
                </router-link>
 | 
			
		||||
                <router-link class='inlineBlock' to="/admin/profile">
 | 
			
		||||
                    <el-dropdown-item>
 | 
			
		||||
                        设置
 | 
			
		||||
                    </el-dropdown-item>
 | 
			
		||||
                </router-link>
 | 
			
		||||
                <el-dropdown-item divided><span @click="logout" style="display:block;">退出登录</span></el-dropdown-item>
 | 
			
		||||
            </el-dropdown-menu>
 | 
			
		||||
        </el-dropdown>
 | 
			
		||||
    </el-menu>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    import { mapGetters } from 'vuex';
 | 
			
		||||
    import Levelbar from './Levelbar';
 | 
			
		||||
    import Hamburger from '@/components/Hamburger';
 | 
			
		||||
 | 
			
		||||
    export default {
 | 
			
		||||
      components: {
 | 
			
		||||
        Levelbar,
 | 
			
		||||
        Hamburger
 | 
			
		||||
      },
 | 
			
		||||
      computed: {
 | 
			
		||||
        ...mapGetters([
 | 
			
		||||
          'sidebar',
 | 
			
		||||
          'name',
 | 
			
		||||
          '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;
 | 
			
		||||
        border-radius: 0px !important;
 | 
			
		||||
        .hamburger-container {
 | 
			
		||||
            line-height: 58px;
 | 
			
		||||
            height: 50px;
 | 
			
		||||
            float: left;
 | 
			
		||||
            padding: 0 10px;
 | 
			
		||||
        }
 | 
			
		||||
        .errLog-container {
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            right: 150px;
 | 
			
		||||
        }
 | 
			
		||||
        .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;
 | 
			
		||||
                .user-avatar {
 | 
			
		||||
                    width: 40px;
 | 
			
		||||
                    height: 40px;
 | 
			
		||||
                    border-radius: 10px;
 | 
			
		||||
                }
 | 
			
		||||
                .el-icon-caret-bottom {
 | 
			
		||||
                    position: absolute;
 | 
			
		||||
                    right: -20px;
 | 
			
		||||
                    top: 25px;
 | 
			
		||||
                    font-size: 12px;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										24
									
								
								src/views/layout/Sidebar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/views/layout/Sidebar.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-menu mode="vertical" theme="dark" :default-active="$route.path">
 | 
			
		||||
        <sidebar-item :routes='permission_routers'></sidebar-item>
 | 
			
		||||
    </el-menu>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    import { mapGetters } from 'vuex';
 | 
			
		||||
    import SidebarItem from './SidebarItem';
 | 
			
		||||
    export default {
 | 
			
		||||
      components: { SidebarItem },
 | 
			
		||||
      computed: {
 | 
			
		||||
        ...mapGetters([
 | 
			
		||||
          'permission_routers'
 | 
			
		||||
        ])
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
			
		||||
    .el-menu {
 | 
			
		||||
        min-height: 100%;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										47
									
								
								src/views/layout/SidebarItem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/views/layout/SidebarItem.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <template v-for="item in routes">
 | 
			
		||||
            <router-link v-if="!item.hidden&&item.noDropdown&&item.children.length>0" :to="item.path+'/'+item.children[0].path">
 | 
			
		||||
                <el-menu-item :index="item.path+'/'+item.children[0].path">
 | 
			
		||||
                    <wscn-icon-svg v-if='item.icon' :icon-class="item.icon" /> {{item.children[0].name}}
 | 
			
		||||
                </el-menu-item>
 | 
			
		||||
            </router-link>
 | 
			
		||||
            <el-submenu :index="item.name" v-if="!item.noDropdown&&!item.hidden">
 | 
			
		||||
                <template slot="title">
 | 
			
		||||
                    <wscn-icon-svg v-if='item.icon' :icon-class="item.icon" /> {{item.name}}
 | 
			
		||||
                </template>
 | 
			
		||||
                <template v-for="child in item.children" v-if='!child.hidden'>
 | 
			
		||||
                    <sidebar-item class='menu-indent' v-if='child.children&&child.children.length>0' :routes='[child]'> </sidebar-item>
 | 
			
		||||
                    <router-link v-else class="menu-indent" :to="item.path+'/'+child.path">
 | 
			
		||||
                        <el-menu-item :index="item.path+'/'+child.path">
 | 
			
		||||
                            {{child.name}}
 | 
			
		||||
                        </el-menu-item>
 | 
			
		||||
                    </router-link>
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-submenu>
 | 
			
		||||
        </template>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
    export default {
 | 
			
		||||
      name: 'SidebarItem',
 | 
			
		||||
      props: {
 | 
			
		||||
        routes: {
 | 
			
		||||
          type: Array
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
			
		||||
    .wscn-icon {
 | 
			
		||||
        margin-right: 10px;
 | 
			
		||||
    }
 | 
			
		||||
    .hideSidebar .menu-indent{
 | 
			
		||||
        display: block;
 | 
			
		||||
        text-indent: 10px;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										45
									
								
								src/views/layout/TabsView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/views/layout/TabsView.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class='tabs-view-container'>
 | 
			
		||||
    <router-link class="tabs-view" v-for="tag in Array.from(visitedViews)" :to="tag.path" :key="tag.path">
 | 
			
		||||
      <el-tag :closable="true" @close='closeViewTabs(tag,$event)'>
 | 
			
		||||
        {{tag.name}}
 | 
			
		||||
      </el-tag>
 | 
			
		||||
    </router-link>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    export default {
 | 
			
		||||
      computed: {
 | 
			
		||||
        visitedViews() {
 | 
			
		||||
          return this.$store.state.app.visitedViews.slice(-6)
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      methods: {
 | 
			
		||||
        closeViewTabs(view, $event) {
 | 
			
		||||
          this.$store.dispatch('delVisitedViews', view)
 | 
			
		||||
          $event.preventDefault()
 | 
			
		||||
        },
 | 
			
		||||
        addViewTabs() {
 | 
			
		||||
          this.$store.dispatch('addVisitedViews', this.$route.matched[this.$route.matched.length - 1])
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      watch: {
 | 
			
		||||
        $route() {
 | 
			
		||||
          this.addViewTabs()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
			
		||||
  .tabs-view-container{
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    vertical-align: top;
 | 
			
		||||
    margin-left: 10px;
 | 
			
		||||
    .tabs-view{
 | 
			
		||||
      margin-left: 10px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										7
									
								
								src/views/layout/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/views/layout/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
export { default as Navbar } from './Navbar';
 | 
			
		||||
 | 
			
		||||
export { default as Sidebar } from './Sidebar';
 | 
			
		||||
 | 
			
		||||
export { default as Levelbar } from './Levelbar';
 | 
			
		||||
 | 
			
		||||
export { default as AppMain } from './AppMain';
 | 
			
		||||
							
								
								
									
										186
									
								
								src/views/login/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								src/views/login/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,186 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="login-container">
 | 
			
		||||
        <el-form autoComplete="on" :model="loginForm" :rules="loginRules" ref="loginForm" label-position="left"
 | 
			
		||||
                 label-width="0px"
 | 
			
		||||
                 class="card-box login-form">
 | 
			
		||||
            <h3 class="title">系统登录</h3>
 | 
			
		||||
            <el-form-item prop="email">
 | 
			
		||||
                <span class="svg-container"><wscn-icon-svg icon-class="jiedianyoujian"/></span>
 | 
			
		||||
                <el-input name="email" type="text" v-model="loginForm.email" autoComplete="on"
 | 
			
		||||
                          placeholder="邮箱"></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item prop="password">
 | 
			
		||||
                <span class="svg-container"><wscn-icon-svg icon-class="mima"/></span>
 | 
			
		||||
                <el-input name="password" type="password" @keyup.enter.native="handleLogin" v-model="loginForm.password"
 | 
			
		||||
                          autoComplete="on" placeholder="密码"></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item>
 | 
			
		||||
                <el-button type="primary" style="width:100%;" :loading="loading" @click.native.prevent="handleLogin">
 | 
			
		||||
                    登录
 | 
			
		||||
                </el-button>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <div class='tips'>admin账号为:admin@wallstreetcn.com 密码随便填</div>
 | 
			
		||||
            <div class='tips'>editor账号:editor@wallstreetcn.com 密码随便填</div>
 | 
			
		||||
            <router-link to="/sendpwd" class="forget-pwd">
 | 
			
		||||
                忘记密码?(或首次登录)
 | 
			
		||||
            </router-link>
 | 
			
		||||
        </el-form>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    import { mapGetters } from 'vuex';
 | 
			
		||||
    import { isWscnEmail } from '@/utils/validate';
 | 
			
		||||
 | 
			
		||||
    export default {
 | 
			
		||||
      name: 'login',
 | 
			
		||||
      data() {
 | 
			
		||||
        const validateEmail = (rule, value, callback) => {
 | 
			
		||||
          if (!isWscnEmail(value)) {
 | 
			
		||||
            callback(new Error('请输入正确的合法邮箱'));
 | 
			
		||||
          } else {
 | 
			
		||||
            callback();
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
        const validatePass = (rule, value, callback) => {
 | 
			
		||||
          if (value.length < 6) {
 | 
			
		||||
            callback(new Error('密码不能小于6位'));
 | 
			
		||||
          } else {
 | 
			
		||||
            callback();
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
        return {
 | 
			
		||||
          loginForm: {
 | 
			
		||||
            email: 'admin@wallstreetcn.com',
 | 
			
		||||
            password: ''
 | 
			
		||||
          },
 | 
			
		||||
          loginRules: {
 | 
			
		||||
            email: [
 | 
			
		||||
                { required: true, trigger: 'blur', validator: validateEmail }
 | 
			
		||||
            ],
 | 
			
		||||
            password: [
 | 
			
		||||
                { required: true, trigger: 'blur', validator: validatePass }
 | 
			
		||||
            ]
 | 
			
		||||
          },
 | 
			
		||||
          loading: false,
 | 
			
		||||
          showDialog: false
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      computed: {
 | 
			
		||||
        ...mapGetters([
 | 
			
		||||
          'auth_type'
 | 
			
		||||
        ])
 | 
			
		||||
      },
 | 
			
		||||
      methods: {
 | 
			
		||||
        handleLogin() {
 | 
			
		||||
          this.$refs.loginForm.validate(valid => {
 | 
			
		||||
            if (valid) {
 | 
			
		||||
              this.loading = true;
 | 
			
		||||
              this.$store.dispatch('LoginByEmail', this.loginForm).then(() => {
 | 
			
		||||
                this.loading = false;
 | 
			
		||||
                this.$router.push({ path: '/' });
 | 
			
		||||
                // this.showDialog = true;
 | 
			
		||||
              }).catch(err => {
 | 
			
		||||
                this.$message.error(err);
 | 
			
		||||
                this.loading = false;
 | 
			
		||||
              });
 | 
			
		||||
            } else {
 | 
			
		||||
              console.log('error submit!!');
 | 
			
		||||
              return false;
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
        },
 | 
			
		||||
        afterQRScan() {
 | 
			
		||||
          // const hash = window.location.hash.slice(1);
 | 
			
		||||
          // const hashObj = getQueryObject(hash);
 | 
			
		||||
          // const originUrl = window.location.origin;
 | 
			
		||||
          // history.replaceState({}, '', originUrl);
 | 
			
		||||
          // const codeMap = {
 | 
			
		||||
          //   wechat: 'code',
 | 
			
		||||
          //   tencent: 'code'
 | 
			
		||||
          // };
 | 
			
		||||
          // const codeName = hashObj[codeMap[this.auth_type]];
 | 
			
		||||
          // if (!codeName) {
 | 
			
		||||
          //   alert('第三方登录失败');
 | 
			
		||||
          // } else {
 | 
			
		||||
          //   this.$store.dispatch('LoginByThirdparty', codeName).then(() => {
 | 
			
		||||
          //     this.$router.push({ path: '/' });
 | 
			
		||||
          //   });
 | 
			
		||||
          // }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      created() {
 | 
			
		||||
        // window.addEventListener('hashchange', this.afterQRScan);
 | 
			
		||||
      },
 | 
			
		||||
      destroyed() {
 | 
			
		||||
        // window.removeEventListener('hashchange', this.afterQRScan);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style rel="stylesheet/scss" lang="scss">
 | 
			
		||||
    @import "src/styles/mixin.scss";
 | 
			
		||||
    .tips{
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      color: #fff;
 | 
			
		||||
      margin-bottom: 5px;
 | 
			
		||||
    }
 | 
			
		||||
    .login-container {
 | 
			
		||||
        @include relative;
 | 
			
		||||
        height: 100vh;
 | 
			
		||||
        background-color: #2d3a4b;
 | 
			
		||||
 | 
			
		||||
        input:-webkit-autofill {
 | 
			
		||||
            -webkit-box-shadow: 0 0 0px 1000px #293444 inset !important;
 | 
			
		||||
            -webkit-text-fill-color: #fff !important;
 | 
			
		||||
        }
 | 
			
		||||
        input {
 | 
			
		||||
            background: transparent;
 | 
			
		||||
            border: 0px;
 | 
			
		||||
            -webkit-appearance: none;
 | 
			
		||||
            border-radius: 0px;
 | 
			
		||||
            padding: 12px 5px 12px 15px;
 | 
			
		||||
            color: #eeeeee;
 | 
			
		||||
            height: 47px;
 | 
			
		||||
        }
 | 
			
		||||
        .el-input {
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            height: 47px;
 | 
			
		||||
            width: 85%;
 | 
			
		||||
        }
 | 
			
		||||
        .svg-container {
 | 
			
		||||
            padding: 6px 5px 6px 15px;
 | 
			
		||||
            color: #889aa4;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .title {
 | 
			
		||||
            font-size: 26px;
 | 
			
		||||
            font-weight: 400;
 | 
			
		||||
            color: #eeeeee;
 | 
			
		||||
            margin: 0px auto 40px auto;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            font-weight: bold;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .login-form {
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            left: 0;
 | 
			
		||||
            right: 0;
 | 
			
		||||
            width: 400px;
 | 
			
		||||
            padding: 35px 35px 15px 35px;
 | 
			
		||||
            margin: 120px auto;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-form-item {
 | 
			
		||||
            border: 1px solid rgba(255, 255, 255, 0.1);
 | 
			
		||||
            background: rgba(0, 0, 0, 0.1);
 | 
			
		||||
            border-radius: 5px;
 | 
			
		||||
            color: #454545;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .forget-pwd {
 | 
			
		||||
            color: #fff;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										5
									
								
								src/views/page/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/views/page/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="login-container">
 | 
			
		||||
        a
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
		Reference in New Issue
	
	Block a user