目录结构更改

pull/9/head
ruibaby 2019-04-10 20:29:41 +08:00
parent c606c8917b
commit 85b449d4bb
63 changed files with 2014 additions and 890 deletions

49
package-lock.json generated
View File

@ -7252,7 +7252,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -7273,12 +7274,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -7293,17 +7296,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -7420,7 +7426,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -7432,6 +7439,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -7446,6 +7454,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -7453,12 +7462,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -7477,6 +7488,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -7557,7 +7569,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -7569,6 +7582,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -7654,7 +7668,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -7690,6 +7705,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -7709,6 +7725,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -7752,12 +7769,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@ -10691,6 +10710,11 @@
"integrity": "sha1-vsECT4WxvZbL6kBbI8FK1kQ6b4E=",
"dev": true
},
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
"lodash.kebabcase": {
"version": "4.1.1",
"resolved": "http://registry.npm.taobao.org/lodash.kebabcase/download/lodash.kebabcase-4.1.1.tgz",
@ -13525,7 +13549,8 @@
"version": "4.0.8",
"resolved": "http://registry.npm.taobao.org/rx-lite/download/rx-lite-4.0.8.tgz",
"integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=",
"dev": true
"dev": true,
"optional": true
},
"rx-lite-aggregates": {
"version": "4.0.8",

View File

@ -13,6 +13,7 @@
"ant-design-vue": "~1.3.7",
"axios": "^0.18.0",
"enquire.js": "^2.1.6",
"lodash.get": "^4.4.2",
"mavon-editor": "^2.7.2",
"moment": "^2.24.0",
"nprogress": "^0.2.0",

View File

@ -1,5 +1,5 @@
<template>
<div :class="['detail-list', size, layout === 'vertical' ? 'vertical': 'horizontal']">
<div :class="['description-list', size, layout === 'vertical' ? 'vertical': 'horizontal']">
<div v-if="title" class="title">{{ title }}</div>
<a-row>
<slot></slot>
@ -24,7 +24,7 @@ const Item = {
type: Number
}
},
render() {
render () {
return (
<Col {...{ props: responsive[this.col] }}>
<div class="term">{this.$props.term}</div>
@ -69,7 +69,7 @@ export default {
default: 'horizontal'
}
},
provide() {
provide () {
return {
col: this.col > 4 ? 4 : this.col
}
@ -79,7 +79,7 @@ export default {
<style lang="less" scoped>
.detail-list {
.description-list {
.title {
color: rgba(0,0,0,.85);

View File

@ -0,0 +1,2 @@
import DescriptionList from './DescriptionList'
export default DescriptionList

View File

@ -0,0 +1,88 @@
<template>
<div class="exception">
<div class="img">
<img :src="config[type].img"/>
</div>
<div class="content">
<h1>{{ config[type].title }}</h1>
<div class="desc">{{ config[type].desc }}</div>
<div class="action">
<a-button type="primary" @click="handleToHome"></a-button>
</div>
</div>
</div>
</template>
<script>
import types from './type'
export default {
name: 'Exception',
props: {
type: {
type: String,
default: '404'
}
},
data() {
return {
config: types
}
},
methods: {
handleToHome() {
this.$router.push({ name: 'dashboard' })
}
}
}
</script>
<style lang="less" scoped>
.exception {
min-height: 500px;
height: 80%;
align-items: center;
text-align: center;
margin-top: 150px;
.img {
display: inline-block;
padding-right: 52px;
zoom: 1;
img {
height: 360px;
max-width: 430px;
}
}
.content {
display: inline-block;
flex: auto;
h1 {
color: #434e59;
font-size: 72px;
font-weight: 600;
line-height: 72px;
margin-bottom: 24px;
}
.desc {
color: rgba(0, 0, 0, .45);
font-size: 20px;
line-height: 28px;
margin-bottom: 16px;
}
}
}
.mobile {
.exception {
margin-top: 30px;
.img {
padding-right: unset;
img {
height: 40%;
max-width: 80%;
}
}
}
}
</style>

View File

@ -0,0 +1,2 @@
import ExceptionPage from './ExceptionPage.vue'
export default ExceptionPage

View File

@ -0,0 +1,19 @@
const types = {
403: {
img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg',
title: '403',
desc: '抱歉,你无权访问该页面'
},
404: {
img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg',
title: '404',
desc: '抱歉,你访问的页面不存在或仍在开发中'
},
500: {
img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg',
title: '500',
desc: '抱歉,服务器出错了'
}
}
export default types

View File

@ -2,11 +2,14 @@
固定在底部的工具栏。
## 何时使用
固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。
引用方式:
```javascript
@ -19,6 +22,8 @@ export default {
}
```
## 代码演示
```html
@ -26,9 +31,7 @@ export default {
<a-button type="primary" @click="validate" :loading="loading">提交</a-button>
</footer-tool-bar>
```
```html
<footer-tool-bar extra="扩展信息提示">
<a-button type="primary" @click="validate" :loading="loading">提交</a-button>
@ -38,8 +41,8 @@ export default {
## API
| 参数 | 说明 | 类型 | 默认值 |
| --------------- | -------------------- | -------------- | ------ |
| children (slot) | 工具栏内容,向右对齐 | - | - |
| extra | 额外信息,向左对齐 | String, Object | - |
参数 | 说明 | 类型 | 默认值
----|------|-----|------
children (slot) | 工具栏内容,向右对齐 | - | -
extra | 额外信息,向左对齐 | String, Object | -

View File

@ -0,0 +1,2 @@
import GlobalFooter from './GlobalFooter'
export default GlobalFooter

View File

@ -0,0 +1,123 @@
<template>
<transition name="showHeader">
<div v-if="visible" class="header-animat">
<a-layout-header
v-if="visible"
:class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', ]"
:style="{ padding: '0' }">
<div v-if="mode === 'sidemenu'" class="header">
<a-icon v-if="device==='mobile'" class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle"/>
<a-icon v-else class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggle"/>
<user-menu></user-menu>
</div>
<div v-else :class="['top-nav-header-index', theme]">
<div class="header-index-wide">
<div class="header-index-left">
<logo class="top-nav-header" :show-title="device !== 'mobile'"/>
<s-menu v-if="device !== 'mobile'" mode="horizontal" :menu="menus" :theme="theme" />
<a-icon v-else class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle" />
</div>
<user-menu class="header-index-right"></user-menu>
</div>
</div>
</a-layout-header>
</div>
</transition>
</template>
<script>
import UserMenu from '../Tools/UserMenu'
import SMenu from '../Menu/'
import Logo from '../Tools/Logo'
import { mixin } from '@/utils/mixin'
export default {
name: 'GlobalHeader',
components: {
UserMenu,
SMenu,
Logo
},
mixins: [mixin],
props: {
mode: {
type: String,
// sidemenu, topmenu
default: 'sidemenu'
},
menus: {
type: Array,
required: true
},
theme: {
type: String,
required: false,
default: 'dark'
},
collapsed: {
type: Boolean,
required: false,
default: false
},
device: {
type: String,
required: false,
default: 'desktop'
}
},
data() {
return {
visible: true,
oldScrollTop: 0
}
},
mounted() {
document.body.addEventListener('scroll', this.handleScroll, { passive: true })
},
methods: {
handleScroll() {
if (!this.autoHideHeader) {
return
}
const scrollTop = document.body.scrollTop + document.documentElement.scrollTop
if (!this.ticking) {
this.ticking = true
requestAnimationFrame(() => {
if (this.oldScrollTop > scrollTop) {
this.visible = true
} else if (scrollTop > 300 && this.visible) {
this.visible = false
} else if (scrollTop < 300 && !this.visible) {
this.visible = true
}
this.oldScrollTop = scrollTop
this.ticking = false
})
}
},
toggle() {
this.$emit('toggle')
}
},
beforeDestroy() {
document.body.removeEventListener('scroll', this.handleScroll, true)
}
}
</script>
<style lang="less">
.header-animat{
position: relative;
z-index: 999;
}
.showHeader-enter-active {
transition: all 0.25s ease;
}
.showHeader-leave-active {
transition: all 0.5s ease;
}
.showHeader-enter, .showHeader-leave-to {
opacity: 0;
}
</style>

View File

@ -0,0 +1,2 @@
import GlobalHeader from './GlobalHeader'
export default GlobalHeader

View File

@ -1,53 +0,0 @@
<template>
<div>
<a-tabs>
<a-tab-pane v-for="(v, i) in icons" :tab="v.title" :key="i">
<ul>
<li v-for="(icon, key) in v.icons" :key="`${v.title}-${key}`" :class="{ 'active': selectedIcon==icon }">
<a-icon :type="icon" :style="{ fontSize: '36px' }" @click="handleSelectedIcon(icon)" />
</li>
</ul>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script>
import icons from './icons'
export default {
name: 'IconSelect',
data() {
return {
selectedIcon: '',
icons
}
},
methods: {
handleSelectedIcon(icon) {
this.selectedIcon = icon
this.$emit('change', icon)
}
}
}
</script>
<style lang="less" scoped>
ul{
list-style: none;
padding: 0;
overflow-y: scroll;
height: 250px;
li{
display: inline-block;
padding:5px;
margin:5px;
&:hover {
cursor: pointer;
}
&.active{
box-shadow: 0px 0px 5px 2px #888888;
}
}
}
</style>

View File

@ -1,48 +0,0 @@
IconSelector
====
> 图标选择组件,常用于为某一个数据设定一个图标时使用
> eg: 设定菜单列表时,为每个菜单设定一个图标
该组件由 [@Saraka](https://github.com/saraka-tsukai) 封装
### 使用方式
```vue
<template>
<div>
<icon-selector @change="handleIconChange"/>
</div>
</template>
<script>
import IconSelector from '@/components/IconSelector'
export default {
name: 'YourView',
components: {
IconSelector
},
data () {
return {
}
},
methods: {
handleIconChange (icon) {
console.log('change Icon', icon)
}
}
}
</script>
```
### 事件
| 名称 | 说明 | 类型 | 默认值 |
| ------ | -------------------------- | ------ | ------ |
| change | 当改变了 `icon` 选中项触发 | String | - |

View File

@ -1,26 +0,0 @@
export default [
{
title: '方向性图标',
icons: ['step-backward', 'step-forward', 'fast-backward', 'fast-forward', 'shrink', 'arrows-alt', 'down', 'up', 'left', 'right', 'caret-up', 'caret-down', 'caret-left', 'caret-right', 'up-circle', 'down-circle', 'left-circle', 'right-circle', 'double-right', 'double-left', 'vertical-left', 'vertical-right', 'forward', 'backward', 'rollback', 'enter', 'retweet', 'swap', 'swap-left', 'swap-right', 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right', 'play-circle', 'up-square', 'down-square', 'left-square', 'right-square', 'login', 'logout', 'menu-fold', 'menu-unfold', 'border-bottom', 'border-horizontal', 'border-inner', 'border-left', 'border-right', 'border-top', 'border-verticle', 'pic-center', 'pic-left', 'pic-right', 'radius-bottomleft', 'radius-bottomright', 'radius-upleft', 'fullscreen', 'fullscreen-exit']
},
{
title: '提示建议性图标',
icons: ['question', 'question-circle', 'plus', 'plus-circle', 'pause', 'pause-circle', 'minus', 'minus-circle', 'plus-square', 'minus-square', 'info', 'info-circle', 'exclamation', 'exclamation-circle', 'close', 'close-circle', 'close-square', 'check', 'check-circle', 'check-square', 'clock-circle', 'warning', 'issues-close', 'stop']
},
{
title: '编辑类图标',
icons: ['edit', 'form', 'copy', 'scissor', 'delete', 'snippets', 'diff', 'highlight', 'align-center', 'align-left', 'align-right', 'bg-colors', 'bold', 'italic', 'underline', 'strikethrough', 'redo', 'undo', 'zoom-in', 'zoom-out', 'font-colors', 'font-size', 'line-height', 'colum-height', 'dash', 'small-dash', 'sort-ascending', 'sort-descending', 'drag', 'ordered-list', 'radius-setting']
},
{
title: '数据类图标',
icons: ['area-chart', 'pie-chart', 'bar-chart', 'dot-chart', 'line-chart', 'radar-chart', 'heat-map', 'fall', 'rise', 'stock', 'box-plot', 'fund', 'sliders']
},
{
title: '网站通用图标',
icons: ['lock', 'unlock', 'bars', 'book', 'calendar', 'cloud', 'cloud-download', 'code', 'copy', 'credit-card', 'delete', 'desktop', 'download', 'ellipsis', 'file', 'file-text', 'file-unknown', 'file-pdf', 'file-word', 'file-excel', 'file-jpg', 'file-ppt', 'file-markdown', 'file-add', 'folder', 'folder-open', 'folder-add', 'hdd', 'frown', 'meh', 'smile', 'inbox', 'laptop', 'appstore', 'link', 'mail', 'mobile', 'notification', 'paper-clip', 'picture', 'poweroff', 'reload', 'search', 'setting', 'share-alt', 'shopping-cart', 'tablet', 'tag', 'tags', 'to-top', 'upload', 'user', 'video-camera', 'home', 'loading', 'loading-3-quarters', 'cloud-upload', 'star', 'heart', 'environment', 'eye', 'camera', 'save', 'team', 'solution', 'phone', 'filter', 'exception', 'export', 'customer-service', 'qrcode', 'scan', 'like', 'dislike', 'message', 'pay-circle', 'calculator', 'pushpin', 'bulb', 'select', 'switcher', 'rocket', 'bell', 'disconnect', 'database', 'compass', 'barcode', 'hourglass', 'key', 'flag', 'layout', 'printer', 'sound', 'usb', 'skin', 'tool', 'sync', 'wifi', 'car', 'schedule', 'user-add', 'user-delete', 'usergroup-add', 'usergroup-delete', 'man', 'woman', 'shop', 'gift', 'idcard', 'medicine-box', 'red-envelope', 'coffee', 'copyright', 'trademark', 'safety', 'wallet', 'bank', 'trophy', 'contacts', 'global', 'shake', 'api', 'fork', 'dashboard', 'table', 'profile', 'alert', 'audit', 'branches', 'build', 'border', 'crown', 'experiment', 'fire', 'money-collect', 'property-safety', 'read', 'reconciliation', 'rest', 'security-scan', 'insurance', 'interation', 'safety-certificate', 'project', 'thunderbolt', 'block', 'cluster', 'deployment-unit', 'dollar', 'euro', 'pound', 'file-done', 'file-exclamation', 'file-protect', 'file-search', 'file-sync', 'gateway', 'gold', 'robot', 'shopping']
},
{
title: '品牌和标识',
icons: ['android', 'apple', 'windows', 'ie', 'chrome', 'github', 'aliwangwang', 'dingding', 'weibo-square', 'weibo-circle', 'taobao-circle', 'html5', 'weibo', 'twitter', 'wechat', 'youtube', 'alipay-circle', 'taobao', 'skype', 'qq', 'medium-workmark', 'gitlab', 'medium', 'linkedin', 'google-plus', 'dropbox', 'facebook', 'codepen', 'code-sandbox', 'amazon', 'google', 'codepen-circle', 'alipay', 'ant-design', 'aliyun', 'zhihu', 'slack', 'slack-square', 'behance', 'behance-square', 'dribbble', 'dribbble-square', 'instagram', 'yuque', 'alibaba', 'yahoo']
}
]

View File

@ -1,2 +0,0 @@
import IconSelector from './IconSelector'
export default IconSelector

View File

@ -18,14 +18,13 @@
</template>
<script>
import ALayoutSider from 'ant-design-vue/es/layout/Sider'
import Logo from '../tools/Logo'
import Logo from '@/components/Tools/Logo'
import SMenu from './index'
import { mixin, mixinDevice } from '@/utils/mixin.js'
import { mixin, mixinDevice } from '@/utils/mixin'
export default {
name: 'SideMenu',
components: { ALayoutSider, Logo, SMenu },
components: { Logo, SMenu },
mixins: [mixin, mixinDevice],
props: {
mode: {

View File

@ -101,16 +101,15 @@ export default {
},
renderMenuItem(menu) {
const target = menu.meta.target || null
const props = {
to: { name: menu.name },
target: target
}
const tag = target && 'a' || 'router-link'
const props = { to: { name: menu.name } }
const attrs = { href: menu.path, target: menu.meta.target }
return (
<Item {...{ key: menu.path }}>
<router-link {...{ props }}>
<tag {...{ props, attrs }}>
{this.renderIcon(menu.meta.icon)}
<span>{menu.meta.title}</span>
</router-link>
</tag>
</Item>
)
},

View File

@ -18,3 +18,8 @@
max-width: 1200px;
margin: 0 auto;
}
.topmenu.content-width-Fluid .@{multi-tab-wrapper-prefix-cls} {
max-width: 100%;
margin: 0 auto;
}

View File

@ -0,0 +1,89 @@
<template>
<a-popover
v-model="visible"
trigger="click"
placement="bottomRight"
overlayClassName="header-notice-wrapper"
:autoAdjustOverflow="true"
:arrowPointAtCenter="true"
:overlayStyle="{ width: '300px', top: '50px' }"
>
<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 style="font-size: 16px; padding: 4px" type="bell" />
</a-badge>
</span>
</a-popover>
</template>
<script>
export default {
name: 'HeaderNotice',
data() {
return {
loadding: false,
visible: false
}
},
methods: {
fetchNotice() {
if (!this.visible) {
this.loadding = true
setTimeout(() => {
this.loadding = false
}, 2000)
} else {
this.loadding = false
}
this.visible = !this.visible
}
}
}
</script>
<style lang="css">
.header-notice-wrapper {
top: 50px !important;
}
</style>
<style lang="less" scoped>
.header-notice{
display: inline-block;
transition: all 0.3s;
span {
vertical-align: initial;
}
}
</style>

View File

@ -0,0 +1,2 @@
import NoticeIcon from './NoticeIcon'
export default NoticeIcon

View File

@ -1,16 +1,7 @@
<template>
<div class="page-header">
<div class="page-header-index-wide">
<a-breadcrumb class="breadcrumb">
<a-breadcrumb-item v-for="(item, index) in breadList" :key="index">
<router-link
v-if="item.name != name && index != 1"
:to="{ path: item.path === '' ? '/' : item.path }"
>{{ item.meta.title }}</router-link>
<span v-else>{{ item.meta.title }}</span>
</a-breadcrumb-item>
</a-breadcrumb>
<s-breadcrumb />
<div class="detail">
<div class="main" v-if="!$route.meta.hiddenHeaderContent">
<div class="row">
@ -22,7 +13,7 @@
</div>
<div class="row">
<div v-if="avatar" class="avatar">
<a-avatar :src="avatar"/>
<a-avatar :src="avatar" />
</div>
<div v-if="this.$slots.content" class="headerContent">
<slot name="content"></slot>
@ -41,7 +32,7 @@
</template>
<script>
import Breadcrumb from '@/components/tools/Breadcrumb'
import Breadcrumb from '@/components/Tools/Breadcrumb'
export default {
name: 'PageHeader',
@ -50,13 +41,8 @@ export default {
},
props: {
title: {
type: String,
default: '',
required: false
},
breadcrumb: {
type: Array,
default: null,
type: [String, Boolean],
default: true,
required: false
},
logo: {
@ -71,36 +57,12 @@ export default {
}
},
data() {
return {
name: '',
breadList: []
}
},
created() {
this.getBreadcrumb()
},
methods: {
getBreadcrumb() {
this.breadList = []
// this.breadList.push({name: 'index', path: '/dashboard/', meta: {title: ''}})
this.name = this.$route.name
this.$route.matched.forEach((item) => {
// item.name !== 'index' && this.breadList.push(item)
this.breadList.push(item)
})
}
},
watch: {
$route() {
this.getBreadcrumb()
}
return {}
}
}
</script>
<style lang="less" scoped>
.page-header {
background: #fff;
padding: 16px 32px 0;
@ -146,10 +108,9 @@ export default {
font-size: 20px;
line-height: 28px;
font-weight: 500;
color: rgba(0,0,0,.85);
color: rgba(0, 0, 0, 0.85);
margin-bottom: 16px;
flex: auto;
}
.logo {
width: 28px;
@ -157,9 +118,10 @@ export default {
border-radius: 4px;
margin-right: 16px;
}
.content, .headerContent {
.content,
.headerContent {
flex: auto;
color: rgba(0,0,0,.45);
color: rgba(0, 0, 0, 0.45);
line-height: 22px;
.link {
@ -192,9 +154,7 @@ export default {
}
.mobile .page-header {
.main {
.row {
flex-wrap: wrap;
@ -203,7 +163,8 @@ export default {
margin: 0 2% 8px 0;
}
.content, .headerContent {
.content,
.headerContent {
flex: 0 1 70%;
.link {

View File

@ -0,0 +1,2 @@
import PageHeader from './PageHeader'
export default PageHeader

View File

@ -0,0 +1,10 @@
import { Spin } from 'ant-design-vue'
export default {
name: 'PageLoading',
render() {
return (<div style={{ paddingTop: 100, textAlign: 'center' }}>
<Spin size="large" />
</div>)
}
}

View File

@ -154,10 +154,10 @@
</template>
<script>
import DetailList from '@/components/tools/DetailList'
import SettingItem from '@/components/setting/SettingItem'
import DetailList from '@/components/Tools/DetailList'
import SettingItem from '@/components/SettingDrawer/SettingItem'
import config from '@/config/defaultSettings'
import { updateTheme, colorList } from '@/components/tools/setting'
import { updateTheme, colorList } from '@/components/Tools/setting'
import { mixin, mixinDevice } from '@/utils/mixin'
export default {

View File

@ -0,0 +1,2 @@
import SettingDrawer from './SettingDrawer'
export default SettingDrawer

View File

@ -0,0 +1,95 @@
import { message } from 'ant-design-vue/es'
// import defaultSettings from '../defaultSettings';
let lessNodesAppended
const colorList = [
{
key: '薄暮', color: '#F5222D'
},
{
key: '火山', color: '#FA541C'
},
{
key: '日暮', color: '#FAAD14'
},
{
key: '明青', color: '#13C2C2'
},
{
key: '极光绿', color: '#52C41A'
},
{
key: '拂晓蓝(默认)', color: '#1890FF'
},
{
key: '极客蓝', color: '#2F54EB'
},
{
key: '酱紫', color: '#722ED1'
}
]
const updateTheme = primaryColor => {
// Don't compile less in production!
/* if (process.env.NODE_ENV === 'production') {
return;
} */
// Determine if the component is remounted
if (!primaryColor) {
return
}
const hideMessage = message.loading('正在编译主题!', 0)
function buildIt() {
if (!window.less) {
return
}
setTimeout(() => {
window.less
.modifyVars({
'@primary-color': primaryColor
})
.then(() => {
hideMessage()
})
.catch(() => {
message.error('Failed to update theme')
hideMessage()
})
}, 200)
}
if (!lessNodesAppended) {
// insert less.js and color.less
const lessStyleNode = document.createElement('link')
const lessConfigNode = document.createElement('script')
const lessScriptNode = document.createElement('script')
lessStyleNode.setAttribute('rel', 'stylesheet/less')
lessStyleNode.setAttribute('href', '/color.less')
lessConfigNode.innerHTML = `
window.less = {
async: true,
env: 'production',
javascriptEnabled: true
};
`
lessScriptNode.src = 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js'
lessScriptNode.async = true
lessScriptNode.onload = () => {
buildIt()
lessScriptNode.onload = null
}
document.body.appendChild(lessStyleNode)
document.body.appendChild(lessConfigNode)
document.body.appendChild(lessScriptNode)
lessNodesAppended = true
} else {
buildIt()
}
}
const updateColorWeak = colorWeak => {
// document.body.className = colorWeak ? 'colorWeak' : '';
colorWeak ? document.body.classList.add('colorWeak') : document.body.classList.remove('colorWeak')
}
export { updateTheme, colorList, updateColorWeak }

View File

@ -0,0 +1,333 @@
Table 重封装组件说明
====
封装说明
----
> 基础的使用方式与 API 与 [官方版(Table)](https://vuecomponent.github.io/ant-design-vue/components/table-cn/) 本一致,在其基础上,封装了加载数据的方法。
>
> 你无需在你是用表格的页面进行分页逻辑处理,仅需向 Table 组件传递绑定 `:data="Promise"` 对象即可
`table` 由 [@Saraka](https://github.com/saraka-tsukai) 完成封装
例子1
----
(基础使用)
```vue
<template>
<s-table
ref="table"
size="default"
:rowKey="(record) => record.data.id"
:columns="columns"
:data="loadData"
:rowSelection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
>
</s-table>
</template>
<script>
import STable from '@/components/table/'
export default {
components: {
STable
},
data() {
return {
columns: [
{
title: '规则编号',
dataIndex: 'no'
},
{
title: '描述',
dataIndex: 'description'
},
{
title: '服务调用次数',
dataIndex: 'callNo',
sorter: true,
needTotal: true,
customRender: (text) => text + ' 次'
},
{
title: '状态',
dataIndex: 'status',
needTotal: true
},
{
title: '更新时间',
dataIndex: 'updatedAt',
sorter: true
}
],
// 查询条件参数
queryParam: {},
// 加载数据方法 必须为 Promise 对象
loadData: parameter => {
return this.$http.get('/service', {
params: Object.assign(parameter, this.queryParam)
}).then(res => {
return res.result
})
},
selectedRowKeys: [],
selectedRows: []
}
},
methods: {
onSelectChange (selectedRowKeys, selectedRows) {
this.selectedRowKeys = selectedRowKeys
this.selectedRows = selectedRows
}
}
}
</script>
```
例子2
----
(简单的表格,最后一列是各种操作)
```vue
<template>
<s-table
ref="table"
size="default"
:columns="columns"
:data="loadData"
>
<span slot="action" slot-scope="text, record">
<a>编辑</a>
<a-divider type="vertical"/>
<a-dropdown>
<a class="ant-dropdown-link">
更多 <a-icon type="down"/>
</a>
<a-menu slot="overlay">
<a-menu-item>
<a href="javascript:;">1st menu item</a>
</a-menu-item>
<a-menu-item>
<a href="javascript:;">2nd menu item</a>
</a-menu-item>
<a-menu-item>
<a href="javascript:;">3rd menu item</a>
</a-menu-item>
</a-menu>
</a-dropdown>
</span>
</s-table>
</template>
<script>
import STable from '@/components/table/'
export default {
components: {
STable
},
data() {
return {
columns: [
{
title: '规则编号',
dataIndex: 'no'
},
{
title: '描述',
dataIndex: 'description'
},
{
title: '服务调用次数',
dataIndex: 'callNo',
},
{
title: '状态',
dataIndex: 'status',
},
{
title: '更新时间',
dataIndex: 'updatedAt',
},
{
table: '操作',
dataIndex: 'action',
scopedSlots: {customRender: 'action'},
}
],
// 查询条件参数
queryParam: {},
// 加载数据方法 必须为 Promise 对象
loadData: parameter => {
return this.$http.get('/service', {
params: Object.assign(parameter, this.queryParam)
}).then(res => {
return res.result
})
},
}
},
methods: {
edit(row) {
// axios 发送数据到后端 修改数据成功后
// 调用 refresh() 重新加载列表数据
// 这里 setTimeout 模拟发起请求的网络延迟..
setTimeout(() => {
this.$refs.table.refresh() // refresh() 不传参默认值 false 不刷新到分页第一页
}, 1500)
}
}
}
</script>
```
内置方法
----
通过 `this.$refs.table` 调用
`this.$refs.table.refresh(true)` 刷新列表 (用户新增/修改数据后,重载列表数据)
> 注意:要调用 `refresh(bool)` 需要给表格组件设定 `ref`
>
> `refresh()` 方法可以传一个 `bool` 值,当有传值 或值为 `true` 时,则刷新时会强制刷新到第一页(常用户页面 搜索 按钮进行搜索时,结果从第一页开始分页)
内置属性
----
> 除去 `a-table` 自带属性外,还而外提供了一些额外属性属性
| 属性 | 说明 | 类型 | 默认值 |
| -------------- | ----------------------------------------------- | ----------------- | ------ |
| alert | 设置是否显示表格信息栏 | [object, boolean] | null |
| showPagination | 显示分页选择器,可传 'auto' \| boolean | [string, boolean] | 'auto' |
| data | 加载数据方法 必须为 `Promise` 对象 **必须绑定** | Promise | - |
`alert` 属性对象:
```javascript
alert: {
show: Boolean,
clear: [Function, Boolean]
}
```
注意事项
----
> 你可能需要为了与后端提供的接口返回结果一致而去修改以下代码:
(需要注意的是,这里的修改是全局性的,意味着整个项目所有使用该 table 组件都需要遵守这个返回结果定义的字段。)
修改 `@/components/table/index.js` 第 132 行起
```javascript
result.then(r => {
this.localPagination = Object.assign({}, this.localPagination, {
current: r.pageNo, // 返回结果中的当前分页数
total: r.totalCount, // 返回结果中的总记录数
showSizeChanger: this.showSizeChanger,
pageSize: (pagination && pagination.pageSize) ||
this.localPagination.pageSize
})
// 为防止删除数据后导致页面当前页面数据长度为 0 ,自动翻页到上一页
if (r.data.length == 0 && this.localPagination.current != 1) {
this.localPagination.current--
this.loadData()
return
}
// 这里用于判断接口是否有返回 r.totalCount 或 this.showPagination = false
// 当情况满足时,表示数据不满足分页大小,关闭 table 分页功能
!r.totalCount && ['auto', false].includes(this.showPagination) && (this.localPagination = false)
this.localDataSource = r.data // 返回结果中的数组数据
this.localLoading = false
});
```
返回 JSON 例子:
```json
{
"message": "",
"result": {
"data": [{
id: 1,
cover: 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png',
title: 'Alipay',
description: '那是一种内在的东西, 他们到达不了,也无法触及的',
status: 1,
updatedAt: '2018-07-26 00:00:00'
},
{
id: 2,
cover: 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png',
title: 'Angular',
description: '希望是一个好东西,也许是最好的,好东西是不会消亡的',
status: 1,
updatedAt: '2018-07-26 00:00:00'
},
{
id: 3,
cover: 'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png',
title: 'Ant Design',
description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
status: 1,
updatedAt: '2018-07-26 00:00:00'
},
{
id: 4,
cover: 'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png',
title: 'Ant Design Pro',
description: '那时候我只会想自己想要什么,从不想自己拥有什么',
status: 1,
updatedAt: '2018-07-26 00:00:00'
},
{
id: 5,
cover: 'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png',
title: 'Bootstrap',
description: '凛冬将至',
status: 1,
updatedAt: '2018-07-26 00:00:00'
},
{
id: 6,
cover: 'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png',
title: 'Vue',
description: '生命就像一盒巧克力,结果往往出人意料',
status: 1,
updatedAt: '2018-07-26 00:00:00'
}
],
"pageSize": 10,
"pageNo": 0,
"totalPage": 6,
"totalCount": 57
},
"status": 200,
"timestamp": 1534955098193
}
```
更新时间
----
该文档最后更新于: 2019-01-21 AM 08:37

View File

@ -0,0 +1,290 @@
import T from 'ant-design-vue/es/table/Table'
import get from 'lodash.get'
export default {
data() {
return {
needTotalList: [],
selectedRows: [],
selectedRowKeys: [],
localLoading: false,
localDataSource: [],
localPagination: Object.assign({}, this.pagination)
}
},
props: Object.assign({}, T.props, {
rowKey: {
type: [String, Function],
default: 'id'
},
data: {
type: Function,
required: true
},
pageNum: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 10
},
showSizeChanger: {
type: Boolean,
default: true
},
size: {
type: String,
default: 'default'
},
/**
* {
* show: true,
* clear: Function
* }
*/
alert: {
type: [Object, Boolean],
default: null
},
rowSelection: {
type: Object,
default: null
},
/** @Deprecated */
showAlertInfo: {
type: Boolean,
default: false
},
showPagination: {
type: String | Boolean,
default: 'auto'
}
}),
watch: {
'localPagination.current'(val) {
this.$router.push({
name: this.$route.name,
params: Object.assign({}, this.$route.params, {
pageNo: val
})
})
},
pageNum(val) {
Object.assign(this.localPagination, {
current: val
})
},
pageSize(val) {
Object.assign(this.localPagination, {
pageSize: val
})
},
showSizeChanger(val) {
Object.assign(this.localPagination, {
showSizeChanger: val
})
}
},
created() {
this.localPagination = ['auto', true].includes(this.showPagination) && Object.assign({}, this.localPagination, {
current: this.pageNum,
pageSize: this.pageSize,
showSizeChanger: this.showSizeChanger
})
this.needTotalList = this.initTotalList(this.columns)
this.loadData()
},
methods: {
/**
* 表格重新加载方法
* 如果参数为 true, 则强制刷新到第一页
* @param Boolean bool
*/
refresh(bool = false) {
bool && (this.localPagination = Object.assign({}, {
current: 1, pageSize: this.pageSize
}))
this.loadData()
},
/**
* 加载数据方法
* @param {Object} pagination 分页选项器
* @param {Object} filters 过滤条件
* @param {Object} sorter 排序条件
*/
loadData(pagination, filters, sorter) {
this.localLoading = true
const parameter = Object.assign({
pageNo: (pagination && pagination.current) ||
this.localPagination.current || this.pageNum,
pageSize: (pagination && pagination.pageSize) ||
this.localPagination.pageSize || this.pageSize
},
(sorter && sorter.field && {
sortField: sorter.field
}) || {},
(sorter && sorter.order && {
sortOrder: sorter.order
}) || {}, {
...filters
}
)
const result = this.data(parameter)
// 对接自己的通用数据接口需要修改下方代码中的 r.pageNo, r.totalCount, r.data
// eslint-disable-next-line
if ((typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') {
result.then(r => {
this.localPagination = Object.assign({}, this.localPagination, {
current: r.pageNo, // 返回结果中的当前分页数
total: r.totalCount, // 返回结果中的总记录数
showSizeChanger: this.showSizeChanger,
pageSize: (pagination && pagination.pageSize) ||
this.localPagination.pageSize
})
// 为防止删除数据后导致页面当前页面数据长度为 0 ,自动翻页到上一页
if (r.data.length === 0 && this.localPagination.current !== 1) {
this.localPagination.current--
this.loadData()
return
}
// 这里用于判断接口是否有返回 r.totalCount 或 this.showPagination = false
// 当情况满足时,表示数据不满足分页大小,关闭 table 分页功能
(!this.showPagination || !r.totalCount && this.showPagination === 'auto') && (this.localPagination.hideOnSinglePage = true)
this.localDataSource = r.data // 返回结果中的数组数据
this.localLoading = false
})
}
},
initTotalList(columns) {
const totalList = []
columns && columns instanceof Array && columns.forEach(column => {
if (column.needTotal) {
totalList.push({
...column,
total: 0
})
}
})
return totalList
},
/**
* 用于更新已选中的列表数据 total 统计
* @param selectedRowKeys
* @param selectedRows
*/
updateSelect(selectedRowKeys, selectedRows) {
this.selectedRows = selectedRows
this.selectedRowKeys = selectedRowKeys
const list = this.needTotalList
this.needTotalList = list.map(item => {
return {
...item,
total: selectedRows.reduce((sum, val) => {
const total = sum + parseInt(get(val, item.dataIndex))
return isNaN(total) ? 0 : total
}, 0)
}
})
},
/**
* 清空 table 已选中项
*/
clearSelected() {
if (this.rowSelection) {
this.rowSelection.onChange([], [])
this.updateSelect([], [])
}
},
/**
* 处理交给 table 使用者去处理 clear 事件时内部选中统计同时调用
* @param callback
* @returns {*}
*/
renderClear(callback) {
if (this.selectedRowKeys.length <= 0) return null
return (
<a style="margin-left: 24px" onClick={() => {
callback()
this.clearSelected()
}}>清空</a>
)
},
renderAlert() {
// 绘制统计列数据
const needTotalItems = this.needTotalList.map((item) => {
return (<span style="margin-right: 12px">
{item.title}总计 <a style="font-weight: 600">{!item.customRender ? item.total : item.customRender(item.total)}</a>
</span>)
})
// 绘制 清空 按钮
const clearItem = (typeof this.alert.clear === 'boolean' && this.alert.clear) ? (
this.renderClear(this.clearSelected)
) : (this.alert !== null && typeof this.alert.clear === 'function') ? (
this.renderClear(this.alert.clear)
) : null
// 绘制 alert 组件
return (
<a-alert showIcon={true} style="margin-bottom: 16px">
<template slot="message">
<span style="margin-right: 12px">已选择: <a style="font-weight: 600">{this.selectedRows.length}</a></span>
{needTotalItems}
{clearItem}
</template>
</a-alert>
)
}
},
render() {
const props = {}
const localKeys = Object.keys(this.$data)
const showAlert = (typeof this.alert === 'object' && this.alert !== null && this.alert.show) && typeof this.rowSelection.selectedRowKeys !== 'undefined' || this.alert
Object.keys(T.props).forEach(k => {
const localKey = `local${k.substring(0, 1).toUpperCase()}${k.substring(1)}`
if (localKeys.includes(localKey)) {
props[k] = this[localKey]
return props[k]
}
if (k === 'rowSelection') {
if (showAlert && this.rowSelection) {
// 如果需要使用alert则重新绑定 rowSelection 事件
props[k] = {
selectedRows: this.selectedRows,
selectedRowKeys: this.selectedRowKeys,
onChange: (selectedRowKeys, selectedRows) => {
this.updateSelect(selectedRowKeys, selectedRows)
typeof this[k].onChange !== 'undefined' && this[k].onChange(selectedRowKeys, selectedRows)
}
}
return props[k]
} else if (!this.rowSelection) {
// 如果没打算开启 rowSelection 则清空默认的选择项
props[k] = null
return props[k]
}
}
props[k] = this[k]
return props[k]
})
const table = (
<a-table {...{ props, scopedSlots: { ...this.$scopedSlots } }} onChange={this.loadData}>
{ Object.keys(this.$slots).map(name => (<template slot={name}>{this.$slots[name]}</template>)) }
</a-table>
)
return (
<div class="table-wrapper">
{ showAlert ? this.renderAlert() : null }
{ table }
</div>
)
}
}

View File

@ -0,0 +1,45 @@
import { Tag } from 'ant-design-vue'
const { CheckableTag } = Tag
export default {
name: 'TagSelectOption',
props: {
prefixCls: {
type: String,
default: 'ant-pro-tag-select-option'
},
value: {
type: [String, Number, Object],
default: ''
},
checked: {
type: Boolean,
default: false
}
},
data() {
return {
localChecked: this.checked || false
}
},
watch: {
'checked'(val) {
this.localChecked = val
},
'$parent.items': {
handler: function(val) {
this.value && val.hasOwnProperty(this.value) && (this.localChecked = val[this.value])
},
deep: true
}
},
render() {
const { $slots, value } = this
const onChange = (checked) => {
this.$emit('change', { value, checked })
}
return (<CheckableTag key={value} vModel={this.localChecked} onChange={onChange}>
{$slots.default}
</CheckableTag>)
}
}

View File

@ -0,0 +1,102 @@
import PropTypes from 'ant-design-vue/es/_util/vue-types'
import Option from './TagSelectOption.jsx'
import { filterEmpty } from '@/components/_util/util'
export default {
Option,
name: 'TagSelect',
model: {
prop: 'checked',
event: 'change'
},
props: {
prefixCls: {
type: String,
default: 'ant-pro-tag-select'
},
defaultValue: {
type: PropTypes.array,
default: null
},
value: {
type: PropTypes.array,
default: null
},
expandable: {
type: Boolean,
default: false
},
hideCheckAll: {
type: Boolean,
default: false
}
},
data() {
return {
expand: false,
localCheckAll: false,
items: this.getItemsKey(filterEmpty(this.$slots.default)),
val: this.value || this.defaultValue || []
}
},
methods: {
onChange(checked) {
const key = Object.keys(this.items).filter(key => key === checked.value)
this.items[key] = checked.checked
const bool = Object.values(this.items).lastIndexOf(false)
if (bool === -1) {
this.localCheckAll = true
} else {
this.localCheckAll = false
}
},
onCheckAll(checked) {
Object.keys(this.items).forEach(v => {
this.items[v] = checked.checked
})
},
getItemsKey(items) {
const totalItem = {}
items.forEach(item => {
totalItem[item.componentOptions.propsData && item.componentOptions.propsData.value] = false
})
return totalItem
},
// CheckAll Button
renderCheckAll() {
return !this.hideCheckAll && (<Option key={'total'} checked={this.localCheckAll} onChange={this.onCheckAll}>All</Option>) || null
},
// expandable
renderExpandable() {
},
// render option
renderTags(items) {
const listeners = {
change: (checked) => {
this.onChange(checked)
this.$emit('change', checked)
}
}
return items.map(vnode => {
const options = vnode.componentOptions
options.listeners = listeners
return vnode
})
}
},
render() {
const { $props: { prefixCls } } = this
const classString = {
[`${prefixCls}`]: true
}
const tagItems = filterEmpty(this.$slots.default)
return (
<div class={classString}>
{this.renderCheckAll()}
{this.renderTags(tagItems)}
</div>
)
}
}

View File

@ -1,9 +1,10 @@
<template>
<a-breadcrumb class="breadcrumb">
<a-breadcrumb-item v-for="(item, index) in breadList" :key="index">
<router-link v-if="item.name != name" :to="{ path: item.path }">
{{ item.meta.title }}
</router-link>
<a-breadcrumb-item v-for="(item, index) in breadList" :key="item.name">
<router-link
v-if="item.name != name && index != 1"
:to="{ path: item.path === '' ? '/' : item.path }"
>{{ item.meta.title }}</router-link>
<span v-else>{{ item.meta.title }}</span>
</a-breadcrumb-item>
</a-breadcrumb>
@ -22,14 +23,12 @@ export default {
},
methods: {
getBreadcrumb() {
console.log('this.$route.matched', this.$route.matched)
this.breadList = []
this.breadList.push({ name: 'index', path: '/dashboard/', meta: { title: '首页' } })
// this.breadList.push({name: 'index', path: '/dashboard/', meta: {title: ''}})
this.name = this.$route.name
this.$route.matched.forEach((item) => {
// item.meta.name === 'dashboard' ? item.path = '/dashboard' : this.$route.path === item.path
this.$route.matched.forEach(item => {
// item.name !== 'index' && this.breadList.push(item)
this.breadList.push(item)
})
}
@ -43,5 +42,4 @@ export default {
</script>
<style scoped>
</style>

View File

@ -0,0 +1,5 @@
<script>
/* WARNING: 兼容老引入,请勿继续使用 */
import DescriptionList from '@/components/DescriptionList'
export default DescriptionList
</script>

View File

@ -1,6 +1,6 @@
<template>
<div class="logo">
<router-link :to="{name:'Dashboard'}">
<router-link :to="{name:'dashboard'}">
<LogoSvg alt="logo" />
<h1 v-if="showTitle">{{ title }}</h1>
</router-link>

View File

@ -37,7 +37,7 @@
<script>
import HeaderComment from './HeaderComment'
import SettingDrawer from '@/components/setting/SettingDrawer'
import SettingDrawer from '@/components/SettingDrawer/SettingDrawer'
import { mapActions, mapGetters } from 'vuex'
export default {

View File

View File

@ -0,0 +1,124 @@
import { Menu, Icon, Input } from 'ant-design-vue'
const { Item, ItemGroup, SubMenu } = Menu
const { Search } = Input
export default {
name: 'Tree',
props: {
dataSource: {
type: Array,
required: true
},
openKeys: {
type: Array,
default: () => []
},
search: {
type: Boolean,
default: false
}
},
created() {
this.localOpenKeys = this.openKeys.slice(0)
},
data() {
return {
localOpenKeys: []
}
},
methods: {
handlePlus(item) {
this.$emit('add', item)
},
handleTitleClick(...args) {
this.$emit('titleClick', { args })
},
renderSearch() {
return (
<Search
placeholder="input search text"
style="width: 100%; margin-bottom: 1rem"
/>
)
},
renderIcon(icon) {
return icon && (<Icon type={icon} />) || null
},
renderMenuItem(item) {
return (
<Item key={item.key}>
{ this.renderIcon(item.icon) }
{ item.title }
<a class="btn" style="width: 20px;z-index:1300" {...{ on: { click: () => this.handlePlus(item) } }}><a-icon type="plus"/></a>
</Item>
)
},
renderItem(item) {
return item.children ? this.renderSubItem(item, item.key) : this.renderMenuItem(item, item.key)
},
renderItemGroup(item) {
const childrenItems = item.children.map(o => {
return this.renderItem(o, o.key)
})
return (
<ItemGroup key={item.key}>
<template slot="title">
<span>{ item.title }</span>
<a-dropdown>
<a class="btn"><a-icon type="ellipsis" /></a>
<a-menu slot="overlay">
<a-menu-item key="1">新增</a-menu-item>
<a-menu-item key="2">合并</a-menu-item>
<a-menu-item key="3">移除</a-menu-item>
</a-menu>
</a-dropdown>
</template>
{ childrenItems }
</ItemGroup>
)
},
renderSubItem(item, key) {
const childrenItems = item.children && item.children.map(o => {
return this.renderItem(o, o.key)
})
const title = (
<span slot="title">
{ this.renderIcon(item.icon) }
<span>{ item.title }</span>
</span>
)
if (item.group) {
return this.renderItemGroup(item)
}
// titleClick={this.handleTitleClick(item)}
return (
<SubMenu key={key}>
{ title }
{ childrenItems }
</SubMenu>
)
}
},
render() {
const { dataSource, search } = this.$props
// this.localOpenKeys = openKeys.slice(0)
const list = dataSource.map(item => {
return this.renderItem(item)
})
return (
<div class="tree-wrapper">
{ search ? this.renderSearch() : null }
<Menu mode="inline" class="custom-tree" {...{ on: { click: item => this.$emit('click', item), 'update:openKeys': val => { this.localOpenKeys = val } } }} openKeys={this.localOpenKeys}>
{ list }
</Menu>
</div>
)
}
}

View File

@ -0,0 +1,41 @@
<template>
<div :class="[prefixCls, reverseColor && 'reverse-color' ]">
<span>
<slot name="term"></slot>
<span class="item-text">
<slot></slot>
</span>
</span>
<span :class="[flag]"><a-icon :type="`caret-${flag}`"/></span>
</div>
</template>
<script>
export default {
name: 'Trend',
props: {
prefixCls: {
type: String,
default: 'ant-pro-trend'
},
/**
* 上升下降标识up|down
*/
flag: {
type: String,
required: true
},
/**
* 颜色反转
*/
reverseColor: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="less" scoped>
@import "index";
</style>

View File

@ -0,0 +1,3 @@
import Trend from './Trend.vue'
export default Trend

View File

@ -0,0 +1,42 @@
@import "../index";
@trend-prefix-cls: ~"@{ant-pro-prefix}-trend";
.@{trend-prefix-cls} {
display: inline-block;
font-size: @font-size-base;
line-height: 22px;
.up,
.down {
margin-left: 4px;
position: relative;
top: 1px;
i {
font-size: 12px;
transform: scale(0.83);
}
}
.item-text {
display: inline-block;
margin-left: 8px;
color: rgba(0,0,0,.85);
}
.up {
color: @red-6;
}
.down {
color: @green-6;
top: -1px;
}
&.reverse-color .up {
color: @green-6;
}
&.reverse-color .down {
color: @red-6;
}
}

View File

@ -0,0 +1,45 @@
# Trend 趋势标记
趋势符号,标记上升和下降趋势。通常用绿色代表“好”,红色代表“不好”,股票涨跌场景除外。
引用方式:
```javascript
import Trend from '@/components/Trend'
export default {
components: {
Trend
}
}
```
## 代码演示 [demo](https://pro.loacg.com/test/home)
```html
<trend flag="up">5%</trend>
```
```html
<trend flag="up">
<span slot="term">工资</span>
5%
</trend>
```
```html
<trend flag="up" term="工资">5%</trend>
```
## API
| 参数 | 说明 | 类型 | 默认值 |
|----------|------------------------------------------|-------------|-------|
| flag | 上升下降标识:`up|down` | string | - |
| reverseColor | 颜色反转 | Boolean | false |

View File

@ -3,9 +3,25 @@ import AvatarList from '@/components/AvatarList'
import Ellipsis from '@/components/Ellipsis'
import FooterToolbar from '@/components/FooterToolbar'
import NumberInfo from '@/components/NumberInfo'
import DetailList from '@/components/tools/DetailList'
import DescriptionList from '@/components/DescriptionList'
import Tree from '@/components/Tree/Tree'
import Trend from '@/components/Trend'
import STable from '@/components/Table'
import MultiTab from '@/components/MultiTab'
import Result from '@/components/Result'
import IconSelector from '@/components/IconSelector'
import ExceptionPage from '@/components/Exception'
export { AvatarList, Ellipsis, FooterToolbar, NumberInfo, DetailList, MultiTab, Result, IconSelector }
export {
AvatarList,
Trend,
Ellipsis,
FooterToolbar,
NumberInfo,
DescriptionList,
DescriptionList as DetailList,
Tree,
STable,
MultiTab,
Result,
ExceptionPage
}

View File

@ -1,53 +0,0 @@
<template>
<global-layout>
<multi-tab v-if="$store.getters.multiTab"></multi-tab>
<transition name="page-transition">
<route-view />
</transition>
</global-layout>
</template>
<script>
import RouteView from '@/components/layouts/RouteView'
import MultiTab from '@/components/MultiTab'
import GlobalLayout from '@/components/page/GlobalLayout'
export default {
name: 'BasicLayout',
components: {
RouteView,
MultiTab,
GlobalLayout
},
data() {
return {}
}
}
</script>
<style lang="less">
@import url('../global.less');
/*
* The following styles are auto-applied to elements with
* transition="page-transition" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the page transition by editing
* these styles.
*/
.page-transition-enter {
opacity: 0;
}
.page-transition-leave-active {
opacity: 0;
}
.page-transition-enter .page-transition-container,
.page-transition-leave-active .page-transition-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
</style>

View File

@ -1,85 +0,0 @@
<template>
<page-layout :desc="description" :title="getTitle" :link-list="linkList" :search="search" :tabs="tabs">
<div slot="extra" class="extra-img">
<img v-if="typeof extraImage !== 'undefined'" :src="extraImage"/>
</div>
<!-- keep-alive -->
<route-view ref="content"></route-view>
</page-layout>
</template>
<script>
import PageLayout from '../page/PageLayout'
import RouteView from './RouteView'
export default {
name: 'PageContent',
components: {
RouteView,
PageLayout
},
data() {
return {
title: '',
description: '',
linkList: [],
extraImage: '',
search: false,
tabs: {}
}
},
mounted() {
this.getPageHeaderInfo()
},
updated() {
this.getPageHeaderInfo()
},
computed: {
getTitle() {
return this.$route.meta.title
}
},
methods: {
getPageHeaderInfo() {
// eslint-disable-next-line
this.title = this.$route.meta.title
// route-view ref
const content = this.$refs.content && this.$refs.content.$children[0]
if (content) {
this.description = content.description
this.linkList = content.linkList
this.extraImage = content.extraImage
this.search = content.search === true
this.tabs = content.tabs
}
}
}
}
</script>
<style lang="less" scoped>
.extra-img {
margin-top: -60px;
text-align: center;
width: 195px;
img {
width: 100%;
}
}
.mobile {
.extra-img{
margin-top: 0;
text-align: center;
width: 96px;
img{
width: 100%;
}
}
}
</style>

View File

@ -1,6 +0,0 @@
import BlankLayout from '@/components/layouts/BlankLayout'
import BasicLayout from '@/components/layouts/BasicLayout'
import RouteView from '@/components/layouts/RouteView'
import PageView from '@/components/layouts/PageView'
export { BasicLayout, BlankLayout, RouteView, PageView }

View File

@ -1,108 +0,0 @@
<template>
<!-- , width: fixedHeader ? `calc(100% - ${sidebarOpened ? 256 : 80}px)` : '100%' -->
<a-layout-header v-if="!headerBarFixed" :class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', ]" :style="{ padding: '0' }">
<div v-if="mode === 'sidemenu'" class="header">
<a-icon
v-if="device==='mobile'"
class="trigger"
:type="collapsed ? 'menu-fold' : 'menu-unfold'"
@click="toggle"></a-icon>
<a-icon
v-else
class="trigger"
:type="collapsed ? 'menu-unfold' : 'menu-fold'"
@click="toggle"/>
<user-menu></user-menu>
</div>
<div v-else :class="['top-nav-header-index', theme]">
<div class="header-index-wide">
<div class="header-index-left">
<logo class="top-nav-header" :show-title="device !== 'mobile'" />
<s-menu
v-if="device !== 'mobile'"
mode="horizontal"
:menu="menus"
:theme="theme"
></s-menu>
<a-icon
v-else
class="trigger"
:type="collapsed ? 'menu-fold' : 'menu-unfold'"
@click="toggle"></a-icon>
</div>
<user-menu class="header-index-right"></user-menu>
</div>
</div>
</a-layout-header>
</template>
<script>
import UserMenu from '../tools/UserMenu'
import SMenu from '../menu/'
import Logo from '../tools/Logo'
import { mixin } from '@/utils/mixin.js'
export default {
name: 'GlobalHeader',
components: {
UserMenu,
SMenu,
Logo
},
mixins: [mixin],
props: {
mode: {
type: String,
// sidemenu, topmenu
default: 'sidemenu'
},
menus: {
type: Array,
required: true
},
theme: {
type: String,
required: false,
default: 'dark'
},
collapsed: {
type: Boolean,
required: false,
default: false
},
device: {
type: String,
required: false,
default: 'desktop'
}
},
data() {
return {
headerBarFixed: false
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
},
methods: {
handleScroll() {
if (this.autoHideHeader) {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
if (scrollTop > 100) {
this.headerBarFixed = true
} else {
this.headerBarFixed = false
}
} else {
this.headerBarFixed = false
}
},
toggle() {
this.$emit('toggle')
}
}
}
</script>

View File

@ -1,156 +0,0 @@
<template>
<a-layout class="layout" :class="[device]">
<template v-if="isSideMenu()">
<a-drawer
v-if="isMobile()"
:wrapClassName="'drawer-sider ' + navTheme"
:closable="false"
:visible="collapsed"
placement="left"
@close="() => this.collapsed = false"
>
<side-menu
:menus="menus"
:theme="navTheme"
:collapsed="false"
:collapsible="true"
mode="inline"
@menuSelect="menuSelect"></side-menu>
</a-drawer>
<side-menu
v-else
mode="inline"
:menus="menus"
:theme="navTheme"
:collapsed="collapsed"
:collapsible="true"></side-menu>
</template>
<!-- 下次优化这些代码 -->
<template v-else>
<a-drawer
v-if="isMobile()"
:wrapClassName="'drawer-sider ' + navTheme"
placement="left"
@close="() => this.collapsed = false"
:closable="false"
:visible="collapsed"
>
<side-menu
:menus="menus"
:theme="navTheme"
:collapsed="false"
:collapsible="true"
mode="inline"
@menuSelect="menuSelect"></side-menu>
</a-drawer>
</template>
<a-layout :class="[layoutMode, `content-width-${contentWidth}`]" :style="{ paddingLeft: contentPaddingLeft, minHeight: '100vh' }">
<!-- layout header -->
<global-header
:mode="layoutMode"
:menus="menus"
:theme="navTheme"
:collapsed="collapsed"
:device="device"
@toggle="toggle"
/>
<!-- layout content -->
<a-layout-content :style="{ margin: $store.getters.multiTab ? '24px 24px 0' : '24px 24px 0', height: '100%', paddingTop: fixedHeader ? '64px' : '0' }">
<slot></slot>
</a-layout-content>
<!-- layout footer -->
<a-layout-footer style="padding: 0">
<global-footer />
</a-layout-footer>
</a-layout>
</a-layout>
</template>
<script>
import SideMenu from '@/components/menu/SideMenu'
import GlobalHeader from '@/components/page/GlobalHeader'
import GlobalFooter from '@/components/page/GlobalFooter'
import { asyncRouterMap } from '@/config/router.config.js'
import { triggerWindowResizeEvent } from '@/utils/util'
import { mapState, mapActions } from 'vuex'
import { mixin, mixinDevice } from '@/utils/mixin.js'
export default {
name: 'GlobalLayout',
components: {
SideMenu,
GlobalHeader,
GlobalFooter
},
mixins: [mixin, mixinDevice],
data() {
return {
collapsed: false,
menus: []
}
},
computed: {
...mapState({
//
mainMenu: state => state.permission.addRouters
}),
contentPaddingLeft() {
if (!this.fixSidebar || this.isMobile()) {
return '0'
}
if (this.sidebarOpened) {
return '256px'
}
return '80px'
}
},
watch: {
sidebarOpened(val) {
this.collapsed = !val
}
},
created() {
this.menus = asyncRouterMap.find((item) => item.path === '/').children
// this.menus = this.mainMenu.find((item) => item.path === '/').children
this.collapsed = !this.sidebarOpened
},
mounted() {
const userAgent = navigator.userAgent
if (userAgent.indexOf('Edge') > -1) {
this.$nextTick(() => {
this.collapsed = !this.collapsed
setTimeout(() => {
this.collapsed = !this.collapsed
}, 16)
})
}
},
methods: {
...mapActions(['setSidebar']),
toggle() {
this.collapsed = !this.collapsed
this.setSidebar(!this.collapsed)
triggerWindowResizeEvent()
},
paddingCalc() {
let left = ''
if (this.sidebarOpened) {
left = this.isDesktop() ? '256px' : '80px'
} else {
left = this.isMobile() && '0' || (this.fixSidebar && '80px' || '0')
}
return left
},
menuSelect() {
if (!this.isDesktop()) {
this.collapsed = false
}
}
}
}
</script>

View File

@ -1,124 +0,0 @@
<template>
<div :style="!$route.meta.pageHeader ? 'margin: -24px -24px 0px;' : null">
<!-- pageHeader , route meta hideHeader:true on hide -->
<page-header v-if="!$route.meta.pageHeader" :title="title" :logo="logo" :avatar="avatar">
<slot slot="action" name="action"></slot>
<slot slot="content" name="headerContent"></slot>
<div slot="content" v-if="!this.$slots.headerContent && desc">
<p style="font-size: 14px;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"/>
<span>{{ link.title }}</span>
</a>
</template>
</div>
</div>
<slot slot="extra" name="extra"></slot>
<div slot="pageMenu">
<div class="page-menu-search" v-if="search">
<a-input-search style="width: 80%; max-width: 522px;" placeholder="请输入..." size="large" enterButton="搜索" />
</div>
<div class="page-menu-tabs" v-if="tabs && tabs.items">
<!-- @change="callback" :activeKey="activeKey" -->
<a-tabs :tabBarStyle="{margin: 0}" @change="tabs.callback" :activeKey="tabs.active()">
<a-tab-pane v-for="item in tabs.items" :tab="item.title" :key="item.key"></a-tab-pane>
</a-tabs>
</div>
</div>
</page-header>
<div class="content">
<div :class="['page-header-index-wide']">
<slot></slot>
</div>
</div>
</div>
</template>
<script>
import PageHeader from './PageHeader'
export default {
name: 'LayoutContent',
components: {
PageHeader
},
// ['desc', 'logo', 'title', 'avatar', 'linkList', 'extraImage']
props: {
desc: {
type: String,
default: null
},
logo: {
type: String,
default: null
},
title: {
type: String,
default: null
},
avatar: {
type: String,
default: null
},
linkList: {
type: Array,
default: null
},
extraImage: {
type: String,
default: null
},
search: {
type: Boolean,
default: false
},
tabs: {
type: Object,
default: () => ({})
}
},
methods: {
}
}
</script>
<style lang="less" scoped>
.content {
margin: 24px 24px 0;
.link {
margin-top: 16px;
&:not(:empty) {
margin-bottom: 16px;
}
a {
margin-right: 32px;
height: 24px;
line-height: 24px;
display: inline-block;
i {
font-size: 24px;
margin-right: 8px;
vertical-align: middle;
}
span {
height: 24px;
line-height: 24px;
display: inline-block;
vertical-align: middle;
}
}
}
}
.page-menu-search {
text-align: center;
margin-bottom: 16px;
}
.page-menu-tabs {
margin-top: 48px;
}
</style>

View File

@ -1,5 +1,5 @@
// eslint-disable-next-line
import { BasicLayout, RouteView, BlankLayout, PageView } from '@/components/layouts'
import { BasicLayout, RouteView, BlankLayout, PageView } from '@/layouts'
export const asyncRouterMap = [
@ -16,7 +16,7 @@ export const asyncRouterMap = [
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/Dashboard'),
meta: { title: '仪表盘', icon: 'dashboard', hiddenHeaderContent: true }
meta: { title: '仪表盘', icon: 'dashboard', hiddenHeaderContent: false }
},
// posts
@ -31,25 +31,25 @@ export const asyncRouterMap = [
path: '/posts/list',
name: 'PostList',
component: () => import('@/views/post/PostList'),
meta: { title: '所有文章', hiddenHeaderContent: true }
meta: { title: '所有文章', hiddenHeaderContent: false }
},
{
path: '/posts/write',
name: 'PostEdit',
component: () => import('@/views/post/PostEdit'),
meta: { title: '写文章', hiddenHeaderContent: true }
meta: { title: '写文章', hiddenHeaderContent: false }
},
{
path: '/categories',
name: 'CategoryList',
component: () => import('@/views/post/CategoryList'),
meta: { title: '分类目录', hiddenHeaderContent: true }
meta: { title: '分类目录', hiddenHeaderContent: false }
},
{
path: '/tags',
name: 'TagList',
component: () => import('@/views/post/TagList'),
meta: { title: '标签', hiddenHeaderContent: true }
meta: { title: '标签', hiddenHeaderContent: false }
}
]
},
@ -66,13 +66,13 @@ export const asyncRouterMap = [
path: '/pages/list',
name: 'PageList',
component: () => import('@/views/page/PageList'),
meta: { title: '所有页面', hiddenHeaderContent: true }
meta: { title: '所有页面', hiddenHeaderContent: false }
},
{
path: '/pages/write',
name: 'PageEdit',
component: () => import('@/views/page/PageEdit'),
meta: { title: '新建页面', hiddenHeaderContent: true }
meta: { title: '新建页面', hiddenHeaderContent: false }
}
]
},
@ -82,7 +82,7 @@ export const asyncRouterMap = [
path: '/attachments',
name: 'Attachments',
component: () => import('@/views/attachment/AttachmentList'),
meta: { title: '附件', icon: 'picture', hiddenHeaderContent: true }
meta: { title: '附件', icon: 'picture', hiddenHeaderContent: false }
},
// comments
@ -90,7 +90,7 @@ export const asyncRouterMap = [
path: '/comments',
name: 'Comments',
component: () => import('@/views/comment/CommentList'),
meta: { title: '评论', icon: 'message', hiddenHeaderContent: true }
meta: { title: '评论', icon: 'message', hiddenHeaderContent: false }
},
// interface
@ -105,19 +105,19 @@ export const asyncRouterMap = [
path: '/interface/themes',
name: 'ThemeList',
component: () => import('@/views/interface/ThemeList'),
meta: { title: '主题', hiddenHeaderContent: true }
meta: { title: '主题', hiddenHeaderContent: false }
},
{
path: '/interface/menus',
name: 'MenuList',
component: () => import('@/views/interface/MenuList'),
meta: { title: '菜单', hiddenHeaderContent: true }
meta: { title: '菜单', hiddenHeaderContent: false }
},
{
path: '/interface/themes/edit',
name: 'ThemeEdit',
component: () => import('@/views/interface/ThemeEdit'),
meta: { title: '主题编辑', hiddenHeaderContent: true }
meta: { title: '主题编辑', hiddenHeaderContent: false }
}
]
},
@ -134,7 +134,7 @@ export const asyncRouterMap = [
path: '/user/profile',
name: 'Profile',
component: () => import('@/views/user/Profile'),
meta: { title: '个人资料', hiddenHeaderContent: true }
meta: { title: '个人资料', hiddenHeaderContent: false }
}
]
},
@ -151,19 +151,19 @@ export const asyncRouterMap = [
path: '/system/options',
name: 'OptionForm',
component: () => import('@/views/system/OptionForm'),
meta: { title: '博客设置', hiddenHeaderContent: true }
meta: { title: '博客设置', hiddenHeaderContent: false }
},
{
path: '/system/backup',
name: 'BackupList',
component: () => import('@/views/system/BackupList'),
meta: { title: '博客备份', hiddenHeaderContent: true }
meta: { title: '博客备份', hiddenHeaderContent: false }
},
{
path: '/system/tools',
name: 'ToolList',
component: () => import('@/views/system/ToolList'),
meta: { title: '小工具', hiddenHeaderContent: true }
meta: { title: '小工具', hiddenHeaderContent: false }
}
]
}

187
src/layouts/BasicLayout.vue Normal file
View File

@ -0,0 +1,187 @@
<template>
<a-layout :class="['layout', device]">
<!-- SideMenu -->
<a-drawer
v-if="isMobile()"
placement="left"
:wrapClassName="`drawer-sider ${navTheme}`"
:closable="false"
:visible="collapsed"
@close="drawerClose"
>
<side-menu
mode="inline"
:menus="menus"
:theme="navTheme"
:collapsed="false"
:collapsible="true"
@menuSelect="menuSelect"
></side-menu>
</a-drawer>
<side-menu
v-else-if="isSideMenu()"
mode="inline"
:menus="menus"
:theme="navTheme"
:collapsed="collapsed"
:collapsible="true"
></side-menu>
<a-layout
:class="[layoutMode, `content-width-${contentWidth}`]"
:style="{ paddingLeft: contentPaddingLeft, minHeight: '100vh' }"
>
<!-- layout header -->
<global-header
:mode="layoutMode"
:menus="menus"
:theme="navTheme"
:collapsed="collapsed"
:device="device"
@toggle="toggle"
/>
<!-- layout content -->
<a-layout-content
:style="{ height: '100%', margin: multiTab ? '24px 24px 0' : '24px 24px 0', paddingTop: fixedHeader ? '64px' : '0' }"
>
<multi-tab v-if="multiTab"></multi-tab>
<transition name="page-transition">
<route-view/>
</transition>
</a-layout-content>
<!-- layout footer -->
<a-layout-footer>
<global-footer/>
</a-layout-footer>
<!-- Setting Drawer (show in development mode) -->
<setting-drawer v-if="!production"></setting-drawer>
</a-layout>
</a-layout>
</template>
<script>
import { triggerWindowResizeEvent } from '@/utils/util'
import { mapState, mapActions } from 'vuex'
import { mixin, mixinDevice } from '@/utils/mixin'
import config from '@/config/defaultSettings'
import { asyncRouterMap } from '@/config/router.config.js'
import RouteView from './RouteView'
import MultiTab from '@/components/MultiTab'
import SideMenu from '@/components/Menu/SideMenu'
import GlobalHeader from '@/components/GlobalHeader'
import GlobalFooter from '@/components/GlobalFooter'
import SettingDrawer from '@/components/SettingDrawer'
export default {
name: 'BasicLayout',
mixins: [mixin, mixinDevice],
components: {
RouteView,
MultiTab,
SideMenu,
GlobalHeader,
GlobalFooter,
SettingDrawer
},
data() {
return {
production: config.production,
collapsed: false,
menus: []
}
},
computed: {
...mapState({
//
mainMenu: state => state.permission.addRouters
}),
contentPaddingLeft() {
if (!this.fixSidebar || this.isMobile()) {
return '0'
}
if (this.sidebarOpened) {
return '256px'
}
return '80px'
}
},
watch: {
sidebarOpened(val) {
this.collapsed = !val
}
},
created() {
this.menus = asyncRouterMap.find(item => item.path === '/').children
// this.menus = this.mainMenu.find((item) => item.path === '/').children
this.collapsed = !this.sidebarOpened
},
mounted() {
const userAgent = navigator.userAgent
if (userAgent.indexOf('Edge') > -1) {
this.$nextTick(() => {
this.collapsed = !this.collapsed
setTimeout(() => {
this.collapsed = !this.collapsed
}, 16)
})
}
},
methods: {
...mapActions(['setSidebar']),
toggle() {
this.collapsed = !this.collapsed
this.setSidebar(!this.collapsed)
triggerWindowResizeEvent()
},
paddingCalc() {
let left = ''
if (this.sidebarOpened) {
left = this.isDesktop() ? '256px' : '80px'
} else {
left = (this.isMobile() && '0') || ((this.fixSidebar && '80px') || '0')
}
return left
},
menuSelect() {
if (!this.isDesktop()) {
this.collapsed = false
}
},
drawerClose() {
this.collapsed = false
}
}
}
</script>
<style lang="less">
@import url('../components/global.less');
/*
* The following styles are auto-applied to elements with
* transition="page-transition" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the page transition by editing
* these styles.
*/
.page-transition-enter {
opacity: 0;
}
.page-transition-leave-active {
opacity: 0;
}
.page-transition-enter .page-transition-container,
.page-transition-leave-active .page-transition-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
</style>

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

@ -0,0 +1,176 @@
<template>
<div :style="!$route.meta.hiddenHeaderContent ? 'margin: -24px -24px 0px;' : null">
<!-- pageHeader , route meta :true on hide -->
<page-header v-if="!$route.meta.hiddenHeaderContent" :title="pageTitle" :logo="logo" :avatar="avatar">
<slot slot="action" name="action"></slot>
<slot slot="content" name="headerContent"></slot>
<div slot="content" v-if="!this.$slots.headerContent && description">
<p style="font-size: 14px;color: rgba(0,0,0,.65)">{{ description }}</p>
<div class="link">
<template v-for="(link, index) in linkList">
<a :key="index" :href="link.href">
<a-icon :type="link.icon" />
<span>{{ link.title }}</span>
</a>
</template>
</div>
</div>
<slot slot="extra" name="extra">
<div class="extra-img">
<img v-if="typeof extraImage !== 'undefined'" :src="extraImage"/>
</div>
</slot>
<div slot="pageMenu">
<div class="page-menu-search" v-if="search">
<a-input-search
style="width: 80%; max-width: 522px;"
placeholder="请输入..."
size="large"
enterButton="搜索"
/>
</div>
<div class="page-menu-tabs" v-if="tabs && tabs.items">
<!-- @change="callback" :activeKey="activeKey" -->
<a-tabs :tabBarStyle="{margin: 0}" :activeKey="tabs.active()" @change="tabs.callback">
<a-tab-pane v-for="item in tabs.items" :tab="item.title" :key="item.key"></a-tab-pane>
</a-tabs>
</div>
</div>
</page-header>
<div class="content">
<div class="page-header-index-wide">
<slot>
<!-- keep-alive -->
<keep-alive v-if="multiTab">
<router-view ref="content" />
</keep-alive>
<router-view v-else ref="content" />
</slot>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import PageHeader from '@/components/PageHeader'
export default {
name: 'PageView',
components: {
PageHeader
},
props: {
avatar: {
type: String,
default: null
},
title: {
type: [String, Boolean],
default: true
},
logo: {
type: String,
default: null
}
},
data() {
return {
pageTitle: null,
description: null,
linkList: [],
extraImage: '',
search: false,
tabs: {}
}
},
computed: {
...mapState({
multiTab: state => state.app.multiTab
})
},
mounted() {
this.getPageMeta()
},
updated() {
this.getPageMeta()
},
methods: {
getPageMeta() {
// eslint-disable-next-line
this.pageTitle = (typeof(this.title) === 'string' || !this.title) ? this.title : this.$route.meta.title
const content = this.$refs.content
if (content) {
if (content.pageMeta) {
Object.assign(this, content.pageMeta)
} else {
this.description = content.description
this.linkList = content.linkList
this.extraImage = content.extraImage
this.search = content.search === true
this.tabs = content.tabs
}
}
}
}
}
</script>
<style lang="less" scoped>
.content {
margin: 24px 24px 0;
.link {
margin-top: 16px;
&:not(:empty) {
margin-bottom: 16px;
}
a {
margin-right: 32px;
height: 24px;
line-height: 24px;
display: inline-block;
i {
font-size: 24px;
margin-right: 8px;
vertical-align: middle;
}
span {
height: 24px;
line-height: 24px;
display: inline-block;
vertical-align: middle;
}
}
}
}
.page-menu-search {
text-align: center;
margin-bottom: 16px;
}
.page-menu-tabs {
margin-top: 48px;
}
.extra-img {
margin-top: -60px;
text-align: center;
width: 195px;
img {
width: 100%;
}
}
.mobile {
.extra-img{
margin-top: 0;
text-align: center;
width: 96px;
img{
width: 100%;
}
}
}
</style>

View File

@ -1,6 +1,12 @@
<script>
export default {
name: 'RouteView',
props: {
keepAlive: {
type: Boolean,
default: true
}
},
data() {
return {}
},
@ -17,10 +23,10 @@ export default {
// multiTab multiTab
//
// return meta.keepAlive ? inKeep : notKeep
if (meta.keepAlive === false) {
if (!getters.multiTab && meta.keepAlive === false) {
return notKeep
}
return getters.multiTab || meta.keepAlive ? inKeep : notKeep
return this.keepAlive || getters.multiTab || meta.keepAlive ? inKeep : notKeep
}
}
</script>

6
src/layouts/index.js Normal file
View File

@ -0,0 +1,6 @@
import BlankLayout from './BlankLayout'
import BasicLayout from './BasicLayout'
import RouteView from './RouteView'
import PageView from './PageView'
export { BasicLayout, BlankLayout, RouteView, PageView }

View File

@ -1,108 +1,56 @@
<template>
<div class="page-header-index-wide">
<page-view>
<a-row :gutter="12" type="flex" align="middle">
<a-col
class="attachment-item"
:xl="4"
:lg="4"
:md="12"
:sm="12"
:xs="24"
>
<a-col class="attachment-item" :xl="4" :lg="4" :md="12" :sm="12" :xs="24">
<a-card :bodyStyle="{ padding: '14px' }">
<img :src="src" slot="cover" />
<img :src="src" slot="cover">
</a-card>
</a-col>
<a-col
class="attachment-item"
:xl="4"
:lg="4"
:md="12"
:sm="12"
:xs="24"
>
<a-col class="attachment-item" :xl="4" :lg="4" :md="12" :sm="12" :xs="24">
<a-card :bodyStyle="{ padding: '14px' }">
<img :src="src" slot="cover" />
<img :src="src" slot="cover">
</a-card>
</a-col>
<a-col
class="attachment-item"
:xl="4"
:lg="4"
:md="12"
:sm="12"
:xs="24"
>
<a-col class="attachment-item" :xl="4" :lg="4" :md="12" :sm="12" :xs="24">
<a-card :bodyStyle="{ padding: '14px' }">
<img :src="src" slot="cover" />
<img :src="src" slot="cover">
</a-card>
</a-col>
<a-col
class="attachment-item"
:xl="4"
:lg="4"
:md="12"
:sm="12"
:xs="24"
>
<a-col class="attachment-item" :xl="4" :lg="4" :md="12" :sm="12" :xs="24">
<a-card :bodyStyle="{ padding: '14px' }">
<img :src="src" slot="cover" />
<img :src="src" slot="cover">
</a-card>
</a-col>
<a-col
class="attachment-item"
:xl="4"
:lg="4"
:md="12"
:sm="12"
:xs="24"
>
<a-col class="attachment-item" :xl="4" :lg="4" :md="12" :sm="12" :xs="24">
<a-card :bodyStyle="{ padding: '14px' }">
<img :src="src" slot="cover" />
<img :src="src" slot="cover">
</a-card>
</a-col>
<a-col
class="attachment-item"
:xl="4"
:lg="4"
:md="12"
:sm="12"
:xs="24"
>
<a-col class="attachment-item" :xl="4" :lg="4" :md="12" :sm="12" :xs="24">
<a-card :bodyStyle="{ padding: '14px' }">
<img :src="src" slot="cover" />
<img :src="src" slot="cover">
</a-card>
</a-col>
<a-col
class="attachment-item"
:xl="4"
:lg="4"
:md="12"
:sm="12"
:xs="24"
>
<a-col class="attachment-item" :xl="4" :lg="4" :md="12" :sm="12" :xs="24">
<a-card :bodyStyle="{ padding: '14px' }">
<img :src="src" slot="cover" />
<img :src="src" slot="cover">
</a-card>
</a-col>
<a-col
class="attachment-item"
:xl="4"
:lg="4"
:md="12"
:sm="12"
:xs="24"
>
<a-col class="attachment-item" :xl="4" :lg="4" :md="12" :sm="12" :xs="24">
<a-card :bodyStyle="{ padding: '14px' }">
<img :src="src" slot="cover" />
<img :src="src" slot="cover">
</a-card>
</a-col>
</a-row>
</div>
</page-view>
</template>
<script>
import { PageView } from '@/layouts'
export default {
components: {
PageView
},
data() {
return {
src: 'https://cdn.ryanc.cc/img/blog/thumbnails/c86b59623c4a9bfeac403824dec3a789.jpg'

View File

@ -1,5 +1,5 @@
<template>
<div class="page-header-index-wide">
<page-view>
<a-card :bordered="false">
<div class="table-page-search-wrapper">
<a-form layout="inline">
@ -50,10 +50,11 @@
</a-table>
</div>
</a-card>
</div>
</page-view>
</template>
<script>
import { PageView } from '@/layouts'
const columns = [
{
title: '#',
@ -84,7 +85,9 @@ const columns = [
]
export default {
name: 'CommentList',
components: {},
components: {
PageView
},
data() {
return {
//

View File

@ -1,13 +1,7 @@
<template>
<div class="page-header-index-wide">
<page-view>
<a-row :gutter="12">
<a-col
:xl="12"
:lg="24"
:md="24"
:sm="24"
:xs="24"
>
<a-col :xl="12" :lg="24" :md="24" :sm="24" :xs="24">
<a-card
:loading="postLoading"
:bordered="false"
@ -20,29 +14,16 @@
:dataSource="formattedPostData"
:pagination="false"
>
<span
slot="status"
slot-scope="status"
>
<a-badge :status="status.status" />{{ status.text }}
<span slot="status" slot-scope="status">
<a-badge :status="status.status"/>
{{ status.text }}
</span>
<span
slot="editTime"
slot-scope="editTime"
>
{{ editTime | timeAgo }}
</span>
<span slot="editTime" slot-scope="editTime">{{ editTime | timeAgo }}</span>
</a-table>
</a-card>
</a-col>
<a-col
:xl="12"
:lg="24"
:md="24"
:sm="24"
:xs="24"
>
<a-col :xl="12" :lg="24" :md="24" :sm="24" :xs="24">
<a-card
:loading="commentLoading"
:bordered="false"
@ -54,15 +35,16 @@
:columns="commentColumns"
:dataSource="commentData"
:pagination="false"
>
</a-table>
></a-table>
</a-card>
</a-col>
</a-row>
</div>
</page-view>
</template>
<script>
import { PageView } from '@/layouts'
import postApi from '@/api/post'
import commentApi from '@/api/comment'
import logApi from '@/api/log'
@ -111,7 +93,9 @@ const commentColumns = [
export default {
name: 'Dashboard',
components: {},
components: {
PageView
},
data() {
return {
postLoading: true,