This commit is contained in:
Pan
2017-06-26 13:38:24 +08:00
commit c528bd8ad0
61 changed files with 3251 additions and 0 deletions

228
src/views/404.vue Normal file
View 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>

View File

@@ -0,0 +1,5 @@
<template>
<div class="dashboard-editor-container">
dashboard
</div>
</template>

View 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>

View 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>

View 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>

View 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
View 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>

View 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>

View 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>

View 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>

View 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
View 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
View File

@@ -0,0 +1,5 @@
<template>
<div class="login-container">
a
</div>
</template>