refactor: update to new project structure

This commit is contained in:
chenghx
2018-09-11 11:45:32 +08:00
parent 263f3064a7
commit f8483833f1
48 changed files with 205 additions and 246 deletions

View File

@@ -0,0 +1,44 @@
<template>
<div class="footer">
<div class="links">
<a target="_blank" :key="index" :href="item.link ? item.link : 'javascript: void(0)'" v-for="(item, index) in linkList">
<a-icon v-if="item.icon" :type="item.icon"/>{{item.name}}
</a>
</div>
<div class="copyright">
Copyright<a-icon type="copyright" />{{copyright}}
</div>
</div>
</template>
<script>
import AIcon from 'ant-design-vue/es/icon/icon'
export default {
name: 'GlobalFooter',
props: ['copyright', 'linkList'],
components: {AIcon}
}
</script>
<style lang="less" scoped>
.footer{
padding: 0 16px;
margin: 48px 0 24px;
text-align: center;
.copyright{
color: rgba(0,0,0,.45);
font-size: 14px;
}
.links{
margin-bottom: 8px;
a:not(:last-child) {
margin-right: 40px;
}
a{
color: rgba(0,0,0,.45);
-webkit-transition: all .3s;
transition: all .3s;
}
}
}
</style>

View File

@@ -0,0 +1,157 @@
<template>
<a-layout-header :class="[theme, 'global-header']">
<div :class="['global-header-wide', layout]">
<router-link v-if="isMobile || layout === 'head'" to="/" :class="['logo', isMobile ? null : 'pc', theme]">
<img width="32" src="static/img/vue-antd-logo.png" />
<h1 v-if="!isMobile">{{systemName}}</h1>
</router-link>
<a-divider v-if="isMobile" type="vertical" />
<a-icon v-if="layout === 'side'" class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggleCollapse"/>
<div v-if="layout === 'head'" class="global-header-menu">
<i-menu style="height: 64px; line-height: 64px;" :theme="theme" mode="horizontal" :menuData="menuData" @select="onSelect"/>
</div>
<div :class="['global-header-right', theme]">
<header-search class="header-item" />
<a-tooltip class="header-item" title="帮助文档" placement="bottom" >
<a>
<a-icon type="question-circle-o" />
</a>
</a-tooltip>
<header-notice class="header-item"/>
<header-avatar class="header-item"/>
</div>
</div>
</a-layout-header>
</template>
<script>
import ALayout from 'ant-design-vue/es/layout'
import AIcon from 'ant-design-vue/es/icon/icon'
import AInputSearch from 'ant-design-vue/es/input/Search'
import HeaderSearch from './HeaderSearch'
import HeaderNotice from './HeaderNotice'
import ATooltip from 'ant-design-vue/es/tooltip/Tooltip'
import HeaderAvatar from './HeaderlAvatar'
import ADivider from 'ant-design-vue/es/divider/index'
import IMenu from '../components/menu/menu'
const ALayoutSider = ALayout.Sider
const ALayoutHeader = ALayout.Header
export default {
name: 'GlobalHeader',
components: {
IMenu,
ADivider,
HeaderAvatar,
ATooltip,
HeaderNotice,
HeaderSearch,
AInputSearch,
AIcon,
ALayout,
ALayoutSider,
ALayoutHeader},
props: ['collapsed', 'menuData'],
computed: {
isMobile () {
return this.$store.state.setting.isMobile
},
layout () {
return this.$store.state.setting.layout
},
theme () {
return this.layout === 'side' ? 'light' : this.$store.state.setting.theme
},
systemName () {
return this.$store.state.setting.systemName
}
},
methods: {
toggleCollapse () {
this.$emit('toggleCollapse')
},
onSelect (obj) {
this.$emit('menuSelect', obj)
}
}
}
</script>
<style lang="less" scoped>
.trigger {
font-size: 20px;
line-height: 64px;
padding: 0 24px;
cursor: pointer;
transition: color .3s;
&:hover{
color: #1890ff;
}
}
.header-item{
padding: 0 12px;
display: inline-block;
height: 100%;
cursor: pointer;
vertical-align: middle;
i{
font-size: 16px;
color: rgba(0,0,0,.65);
}
}
.global-header{
padding: 0 12px 0 0;
-webkit-box-shadow: 0 1px 4px rgba(0,21,41,.08);
box-shadow: 0 1px 4px rgba(0,21,41,.08);
position: relative;
&.light{
background: #fff;
}
&.dark{
background: #001529;
}
.global-header-wide{
&.head{
max-width: 1400px;
margin: auto;
}
&.side{
}
.logo {
height: 64px;
line-height: 58px;
vertical-align: top;
display: inline-block;
padding: 0 12px 0 24px;
cursor: pointer;
font-size: 20px;
&.pc{
padding: 0 12px 0 0;
}
img {
display: inline-block;
vertical-align: middle;
}
h1{
display: inline-block;
font-size: 16px;
}
&.dark h1{
color: #fff;
}
}
.global-header-menu{
display: inline-block;
}
.global-header-right{
float: right;
&.dark{
color: #fff;
i{
color: #fff;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<a-layout>
<drawer v-if="isMobile" :openDrawer="collapsed" @change="onDrawerChange">
<sider-menu :theme="theme" :menuData="menuData" :collapsed="false" :collapsible="false" @menuSelect="onMenuSelect"/>
</drawer>
<sider-menu :theme="theme" v-else-if="layout === 'side'" :menuData="menuData" :collapsed="collapsed" :collapsible="true" />
<drawer :open-drawer="showSetting" placement="right" @change="onSettingDrawerChange">
<div class="setting" slot="handler">
<a-icon :type="showSetting ? 'close' : 'setting'" />
</div>
<setting />
</drawer>
<a-layout>
<global-header :menuData="menuData" :collapsed="collapsed" @toggleCollapse="toggleCollapse"/>
<a-layout-content :style="{minHeight: minHeight, margin: '24px 24px 0'}">
<slot></slot>
</a-layout-content>
<a-layout-footer style="padding: 0px">
<global-footer :link-list="linkList" :copyright="copyright" />
</a-layout-footer>
</a-layout>
</a-layout>
</template>
<script>
import ALayout from 'ant-design-vue/es/layout'
import GlobalHeader from './GlobalHeader'
import AIcon from 'ant-design-vue/es/icon/icon'
import GlobalFooter from './GlobalFooter'
import Drawer from '../components/tool/Drawer'
import SiderMenu from '../components/menu/SiderMenu'
import Setting from '../components/setting/Setting'
const ALayoutSider = ALayout.Sider
const ALayoutHeader = ALayout.Header
const ALayoutContent = ALayout.Content
const ALayoutFooter = ALayout.Footer
const minHeight = window.innerHeight - 64 - 24 - 122
let menuData = []
export default {
name: 'GlobalLayout',
components: {
Setting,
SiderMenu,
Drawer,
GlobalFooter,
AIcon,
GlobalHeader,
ALayout,
ALayoutSider,
ALayoutHeader,
ALayoutContent,
ALayoutFooter},
data () {
return {
minHeight: minHeight + 'px',
collapsed: false,
menuData: menuData,
showSetting: false
}
},
computed: {
isMobile () {
return this.$store.state.setting.isMobile
},
theme () {
return this.$store.state.setting.theme
},
layout () {
return this.$store.state.setting.layout
},
linkList () {
return this.$store.state.setting.footerLinks
},
copyright () {
return this.$store.state.setting.copyright
}
},
methods: {
toggleCollapse () {
this.collapsed = !this.collapsed
},
onDrawerChange (show) {
this.collapsed = show
},
onMenuSelect () {
this.toggleCollapse()
},
onSettingDrawerChange (val) {
this.showSetting = val
}
},
beforeCreate () {
menuData = this.$router.options.routes.find((item) => item.path === '/').children
}
}
</script>
<style lang="less" scoped>
.setting{
background-color: #1890ff;
color: #fff;
border-radius: 5px 0 0 5px;
line-height: 40px;
font-size: 22px;
width: 40px;
height: 40px;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
}
</style>

View File

@@ -0,0 +1,101 @@
<template>
<a-popover trigger="click" placement="bottomRight">
<template slot="content">
<a-spin :spinning="loadding">
<a-tabs>
<a-tab-pane tab="通知" key="1">
<a-list>
<a-list-item>
<a-list-item-meta title="你收到了 14 份新周报" description="一年前">
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"/>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-list-item-meta title="你推荐的 曲妮妮 已通过第三轮面试" description="一年前">
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-list-item-meta title="这种模板可以区分多种通知类型" description="一年前">
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/>
</a-list-item-meta>
</a-list-item>
</a-list>
</a-tab-pane>
<a-tab-pane tab="消息" key="2">
123
</a-tab-pane>
<a-tab-pane tab="待办" key="3">
123
</a-tab-pane>
</a-tabs>
</a-spin>
</template>
<span @click="fetchNotice" class="header-notice">
<a-badge count="12">
<a-icon :class="['header-notice-icon', theme]" type="bell" />
</a-badge>
</span>
</a-popover>
</template>
<script>
import APopover from 'ant-design-vue/es/popover/index'
import AIcon from 'ant-design-vue/es/icon/icon'
import ABadge from 'ant-design-vue/es/badge/Badge'
import ATabs from 'ant-design-vue/es/tabs'
import AList from 'ant-design-vue/es/list/index'
import AListItem from 'ant-design-vue/es/list/Item'
import AAvatar from 'ant-design-vue/es/avatar/Avatar'
import ASpin from 'ant-design-vue/es/spin/Spin'
const ATabPane = ATabs.TabPane
const AListItemMeta = AListItem.Meta
export default {
name: 'HeaderNotice',
components: {ASpin, AAvatar, AListItem, AList, ATabs, ABadge, AIcon, APopover, ATabPane, AListItemMeta},
data () {
return {
loadding: false
}
},
computed: {
theme () {
return this.$store.state.setting.layout === 'side' ? 'light' : this.$store.state.setting.theme
}
},
methods: {
fetchNotice () {
if (this.loadding) {
this.loadding = false
return
}
this.loadding = true
setTimeout(() => {
this.loadding = false
}, 2000)
}
}
}
</script>
<style lang="less">
.header-notice{
display: inline-block;
transition: all 0.3s;
span {
vertical-align: initial;
}
.header-notice-icon{
font-size: 16px;
padding: 4px;
&.dark{
color: #fff;
}
&.light{
color: rgba(0,0,0,.65);
}
}
}
</style>

View File

@@ -0,0 +1,68 @@
<template>
<span class="header-search">
<a-icon type="search" class="search-icon" @click="enterSearchMode"/>
<a-auto-complete
ref="input"
:dataSource="dataSource"
:class="['search-input', searchMode ? 'enter' : 'leave']"
placeholder="站内搜索"
@blur="leaveSearchMode"
>
</a-auto-complete>
</span>
</template>
<script>
import AIcon from 'ant-design-vue/es/icon/icon'
import AAutoComplete from 'ant-design-vue/es/auto-complete/index'
import AInput from 'ant-design-vue/es/input/Input'
export default {
name: 'HeaderSearch',
components: {AInput, AAutoComplete, AIcon},
data () {
return {
dataSource: ['选项一', '选项二'],
searchMode: false
}
},
methods: {
enterSearchMode () {
this.searchMode = true
setTimeout(() => this.$refs.input.focus(), 300)
},
leaveSearchMode () {
this.searchMode = false
}
}
}
</script>
<style lang="less">
.header-search{
.search-icon{
font-size: 16px;
cursor: pointer;
}
.search-input{
border: 0;
border-bottom: 1px rgba(3, 5, 6, 0.23) solid;
transition: width 0.3s ease-in-out;
input{
border: 0;
box-shadow: 0 0 0 0;
}
&.leave{
width: 0px;
input{
display: none;
}
}
&.enter{
width: 200px;
input:focus{
box-shadow: 0 0 0 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<a-dropdown style="display: inline-block; height: 100%; vertical-align: initial" >
<span style="cursor: pointer">
<a-avatar class="avatar" size="small" shape="circle" :src="currUser.avatar"/>
<span>{{currUser.name}}</span>
</span>
<a-menu style="width: 150px" slot="overlay">
<a-menu-item>
<a-icon type="user" />
<span>个人中心</span>
</a-menu-item>
<a-menu-item>
<a-icon type="setting" />
<span>设置</span>
</a-menu-item>
<a-menu-divider />
<a-menu-item>
<router-link to="/login">
<a-icon type="poweroff" />
<span>退出登录</span>
</router-link>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<script>
import ADropdown from 'ant-design-vue/es/dropdown'
import AAvatar from 'ant-design-vue/es/avatar/Avatar'
import AIcon from 'ant-design-vue/es/icon/icon'
import AMenu from 'ant-design-vue/es/menu/index'
const AMenuItem = AMenu.Item
const AMenuDivider = AMenu.Divider
export default {
name: 'HeaderAvatar',
components: {AMenu, AMenuItem, AMenuDivider, AIcon, AAvatar, ADropdown},
computed: {
currUser () {
return this.$store.state.account.user
}
}
}
</script>
<style lang="less" scoped>
.avatar{
margin: 20px 4px 20px 0;
color: #1890ff;
background: hsla(0,0%,100%,.85);
vertical-align: middle;
}
</style>

19
src/layouts/MenuView.vue Normal file
View File

@@ -0,0 +1,19 @@
<template>
<global-layout>
<transition name="page-toggle">
<router-view />
</transition>
</global-layout>
</template>
<script>
import GlobalLayout from './GlobalLayout'
export default {
name: 'MenuView',
components: {GlobalLayout}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,75 @@
<template>
<div style="margin: -24px -24px 0px">
<page-header :breadcrumb="breadcrumb" :title="title" :logo="logo" :avatar="avatar">
<slot name="action" slot="action"></slot>
<slot slot="content" name="headerContent"></slot>
<div slot="content" v-if="!this.$slots.headerContent && desc">
<p style="font-size: 14px;line-height: 1.5;color: rgba(0,0,0,.65)">{{desc}}</p>
<div class="link">
<template v-for="(link, index) in linkList">
<a :key="index" :href="link.href"><a-icon :type="link.icon" />{{link.title}}</a>
</template>
</div>
</div>
<slot slot="extra" name="extra"></slot>
</page-header>
<div ref="page" :class="['page-content', layout]" >
<slot ></slot>
</div>
</div>
</template>
<script>
import PageHeader from '../components/page/PageHeader'
import AIcon from 'ant-design-vue/es/icon/icon'
export default {
name: 'PageLayout',
components: {AIcon, PageHeader},
props: ['desc', 'logo', 'title', 'avatar', 'linkList', 'extraImage'],
data () {
return {
breadcrumb: []
}
},
computed: {
layout () {
return this.$store.state.setting.layout
}
},
mounted () {
this.getBreadcrumb()
},
updated () {
this.getBreadcrumb()
},
methods: {
getBreadcrumb () {
this.breadcrumb = this.$route.matched
}
}
}
</script>
<style lang="less" scoped>
.link{
margin-top: 16px;
line-height: 24px;
a{
font-size: 14px;
margin-right: 32px;
i{
font-size: 22px;
margin-right: 8px;
}
}
}
.page-content{
&.side{
margin: 24px 24px 0px;
}
&.head{
margin: 24px auto 0;
max-width: 1400px;
}
}
</style>

55
src/layouts/PageView.vue Normal file
View File

@@ -0,0 +1,55 @@
<template>
<page-layout :desc="desc" :title="title" :linkList="linkList">
<div slot="extra" class="extraImg">
<img :src="extraImage"/>
</div>
<transition name="page-toggle">
<router-view ref="page"/>
</transition>
</page-layout>
</template>
<script>
import PageHeader from '../components/page/PageHeader'
import PageLayout from './PageLayout'
export default {
name: 'PageView',
components: {PageLayout, PageHeader},
data () {
return {
title: '',
desc: '',
linkList: [],
extraImage: ''
}
},
mounted () {
this.getPageHeaderInfo()
},
updated () {
this.getPageHeaderInfo()
},
methods: {
getPageHeaderInfo () {
this.title = this.$route.name
const page = this.$refs.page
if (page) {
this.desc = page.desc
this.linkList = page.linkList
this.extraImage = page.extraImage
}
}
}
}
</script>
<style lang="less" scoped>
.extraImg{
margin-top: -60px;
text-align: center;
width: 195px;
img{
width: 100%;
}
}
</style>

15
src/layouts/RouteView.vue Normal file
View File

@@ -0,0 +1,15 @@
<template>
<transition name="page-toggle">
<router-view />
</transition>
</template>
<script>
export default {
name: 'RouteView'
}
</script>
<style scoped>
</style>