Merge branch 'hotfix/from1.4.8'

Former-commit-id: a3f063630e5c8094dc5579c42e0137a769c109e8
This commit is contained in:
liyang
2018-09-20 14:37:30 +08:00
42 changed files with 2174 additions and 11050 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# 所有环境 # 所有环境默认
# 页面 title 前缀 # 页面 title 前缀
VUE_APP_TITLE=D2Admin VUE_APP_TITLE=D2Admin
+10
View File
@@ -0,0 +1,10 @@
# travis
# 页面 title 前缀
VUE_APP_TITLE=D2Admin Preview
# 页面 指定使用的模式
NODE_ENV=production
# 标记当前编译版本
VUE_APP_TRAVIS=TRUE
+1 -1
View File
@@ -9,7 +9,7 @@ branches:
- master - master
script: script:
- npm run build - npm run build:travis
deploy: deploy:
provider: pages provider: pages
+1195 -1175
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -1,16 +1,18 @@
{ {
"name": "d2-admin", "name": "d2-admin",
"version": "1.3.0", "version": "1.4.8",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve --open", "serve": "vue-cli-service serve --open",
"start": "vue-cli-service serve --open", "start": "vue-cli-service serve --open",
"dev": "vue-cli-service serve --open", "dev": "vue-cli-service serve --open",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"build:travis": "vue-cli-service build --mode travis",
"lint": "vue-cli-service lint --fix", "lint": "vue-cli-service lint --fix",
"test:unit": "vue-cli-service test:unit" "test:unit": "vue-cli-service test:unit"
}, },
"dependencies": { "dependencies": {
"@d2-projects/d2-crud": "^1.2.0",
"axios": "^0.17.1", "axios": "^0.17.1",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"better-scroll": "^1.12.1", "better-scroll": "^1.12.1",
@@ -23,6 +25,7 @@
"lodash": "^4.17.10", "lodash": "^4.17.10",
"lowdb": "^1.0.0", "lowdb": "^1.0.0",
"mockjs": "^1.0.1-beta3", "mockjs": "^1.0.1-beta3",
"nprogress": "^0.2.0",
"particles.js": "^2.0.0", "particles.js": "^2.0.0",
"screenfull": "^3.3.2", "screenfull": "^3.3.2",
"ua-parser-js": "^0.7.18", "ua-parser-js": "^0.7.18",
@@ -48,9 +51,6 @@
"text-loader": "0.0.1", "text-loader": "0.0.1",
"vue-template-compiler": "^2.5.17" "vue-template-compiler": "^2.5.17"
}, },
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": { "lint-staged": {
"*.js": [ "*.js": [
"vue-cli-service lint", "vue-cli-service lint",
+3 -2
View File
@@ -146,8 +146,9 @@
s.parentNode.insertBefore(hm, s); s.parentNode.insertBefore(hm, s);
})(); })();
</script> </script>
<script src="./script/ua-parser.min.js"></script> <!-- ua -->
<script src="./script/ua.js"></script> <script src="<%= BASE_URL %>script/ua-parser.min.js"></script>
<script src="<%= BASE_URL %>script/ua.js"></script>
</head> </head>
<body> <body>
<noscript> <noscript>
+8
View File
@@ -0,0 +1,8 @@
#nprogress {
.bar {
background: $color-primary !important;
}
.peg {
box-shadow: 0 0 10px $color-primary, 0 0 5px $color-primary !important;
}
}
+2
View File
@@ -4,6 +4,8 @@
@import '~@/assets/style/fixed/base.scss'; @import '~@/assets/style/fixed/base.scss';
// 补丁 element // 补丁 element
@import '~@/assets/style/fixed/element.scss'; @import '~@/assets/style/fixed/element.scss';
// 补丁 n-progress
@import '~@/assets/style/fixed/n-progress.scss';
// 动画 // 动画
@import '~@/assets/style/animate/vue-transition.scss'; @import '~@/assets/style/animate/vue-transition.scss';
+44 -10
View File
@@ -26,9 +26,51 @@
// 内容层 // 内容层
.d2-layout-header-aside-content { .d2-layout-header-aside-content {
@extend %full; @extend %full;
$d2-theme-header-height: 60px;
.d2-theme-header { .d2-theme-header {
height: $d2-theme-header-height; height: 60px;
.d2-theme-header-menu {
overflow: hidden;
&.is-scrollable {
position: relative;
padding: 0 20px;
.d2-theme-header-menu__prev, .d2-theme-header-menu__next {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
}
.d2-theme-header-menu__content {
overflow: hidden;
.d2-theme-header-menu__scroll {
white-space: nowrap;
position: relative;
-webkit-transition: -webkit-transform .3s;
transition: -webkit-transform .3s;
transition: transform .3s;
transition: transform .3s, -webkit-transform .3s;
transition: transform .3s,-webkit-transform .3s;
float: left;
}
}
.d2-theme-header-menu__prev, .d2-theme-header-menu__next {
height: 60px;
position: absolute;
top: 0;
font-size: 20px;
cursor: pointer;
display: none;
}
.d2-theme-header-menu__prev {
left: 0;
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}
.d2-theme-header-menu__next {
right: 0;
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
}
} }
.d2-theme-container { .d2-theme-container {
.d2-theme-container-aside { .d2-theme-container-aside {
@@ -58,13 +100,6 @@
} }
} }
// 主题公用 // 主题公用
.d2-layout-header-aside-group { .d2-layout-header-aside-group {
&.grayMode { &.grayMode {
@@ -135,7 +170,6 @@
padding: 14px 12px; padding: 14px 12px;
border-radius: 4px; border-radius: 4px;
margin: 0px !important; margin: 0px !important;
margin-left: 10px !important;
} }
.el-dropdown { .el-dropdown {
@extend %unable-select; @extend %unable-select;
+11
View File
@@ -63,6 +63,17 @@
// 顶栏 // 顶栏
.d2-theme-header { .d2-theme-header {
// 顶栏菜单空间不足时显示的滚动控件
.d2-theme-header-menu {
.d2-theme-header-menu__prev, .d2-theme-header-menu__next {
color: $theme-header-item-color;
background: $theme-header-item-background-color;
&:hover {
color: $theme-header-item-color-hover;
background: $theme-header-item-background-color-hover;
}
}
}
// 切换按钮 // 切换按钮
.toggle-aside-btn { .toggle-aside-btn {
i { i {
@@ -1,9 +1,6 @@
<template> <template>
<el-menu-item :index="menu.path || uniqueId"> <el-menu-item :index="menu.path || uniqueId">
<i v-if="menu.icon" :class="`fa fa-${menu.icon}`"></i> <i v-if="menu.icon" :class="`fa fa-${menu.icon}`"></i>
<!-- <i v-else-if="menu.iconSvg">
<d2-icon-svg :name="menu.iconSvg"/>
</i> -->
<d2-icon-svg v-else-if="menu.iconSvg" :name="menu.iconSvg"/> <d2-icon-svg v-else-if="menu.iconSvg" :name="menu.iconSvg"/>
<i v-else class="fa fa-file-o"></i> <i v-else class="fa fa-file-o"></i>
<span slot="title">{{menu.title || '未命名菜单'}}</span> <span slot="title">{{menu.title || '未命名菜单'}}</span>
@@ -2,9 +2,6 @@
<el-submenu :index="menu.path || uniqueId"> <el-submenu :index="menu.path || uniqueId">
<template slot="title"> <template slot="title">
<i v-if="menu.icon" :class="`fa fa-${menu.icon}`"></i> <i v-if="menu.icon" :class="`fa fa-${menu.icon}`"></i>
<!-- <i v-else-if="menu.iconSvg">
<d2-icon-svg :name="menu.iconSvg"/>
</i> -->
<d2-icon-svg v-else-if="menu.iconSvg" :name="menu.iconSvg"/> <d2-icon-svg v-else-if="menu.iconSvg" :name="menu.iconSvg"/>
<i v-else class="fa fa-folder-o"></i> <i v-else class="fa fa-folder-o"></i>
<span slot="title">{{menu.title}}</span> <span slot="title">{{menu.title}}</span>
@@ -11,7 +11,7 @@
</template> </template>
<script> <script>
import { mapState, mapMutations } from 'vuex' import { mapState, mapActions } from 'vuex'
export default { export default {
computed: { computed: {
...mapState('d2admin/fullscreen', [ ...mapState('d2admin/fullscreen', [
@@ -19,7 +19,7 @@ export default {
]) ])
}, },
methods: { methods: {
...mapMutations('d2admin/fullscreen', [ ...mapActions('d2admin/fullscreen', [
'toggle' 'toggle'
]) ])
} }
@@ -0,0 +1,67 @@
<template>
<el-dropdown placement="bottom" size="small" @command="handleChange">
<el-button class="d2-mr btn-text can-hover" type="text">
<d2-icon name="font" style="font-size: 16px;"/>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="default">
<d2-icon :name="iconName('default')" class="d2-mr-5"/>默认
</el-dropdown-item>
<el-dropdown-item command="medium">
<d2-icon :name="iconName('medium')" class="d2-mr-5"/>中
</el-dropdown-item>
<el-dropdown-item command="small">
<d2-icon :name="iconName('small')" class="d2-mr-5"/>小
</el-dropdown-item>
<el-dropdown-item command="mini">
<d2-icon :name="iconName('mini')" class="d2-mr-5"/>最小
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
import { mapState, mapMutations, mapActions } from 'vuex'
export default {
name: 'd2-header-size',
computed: {
...mapState('d2admin/size', [
'value'
])
},
watch: {
// 注意 这里是关键
// 因为需要访问 this.$ELEMENT 所以只能在这里使用这种方式
value: {
handler (val) {
if (this.$ELEMENT.size !== val) {
// 设置 element 全局尺寸
this.$ELEMENT.size = val
// 清空缓存设置
this.pageKeepAliveClean()
// 刷新此页面
const { path, query } = this.$route
this.$router.replace({
path: '/redirect/' + JSON.stringify({ path, query })
})
}
},
immediate: true
}
},
methods: {
...mapMutations({
pageKeepAliveClean: 'd2admin/page/keepAliveClean'
}),
...mapActions({
sizeSet: 'd2admin/size/set'
}),
handleChange (value) {
this.sizeSet(value)
},
iconName (name) {
return name === this.value ? 'dot-circle-o' : 'circle-o'
}
}
}
</script>
@@ -18,7 +18,7 @@
</template> </template>
<script> <script>
import { mapState, mapMutations } from 'vuex' import { mapState, mapActions } from 'vuex'
export default { export default {
name: 'd2-theme-list', name: 'd2-theme-list',
data () { data () {
@@ -36,7 +36,7 @@ export default {
]) ])
}, },
methods: { methods: {
...mapMutations('d2admin/theme', [ ...mapActions('d2admin/theme', [
'set' 'set'
]), ]),
handleSelectTheme (name) { handleSelectTheme (name) {
@@ -1,5 +1,5 @@
<template> <template>
<el-dropdown class="d2-mr"> <el-dropdown size="small" class="d2-mr">
<span class="btn-text">你好 {{info.name}}</span> <span class="btn-text">你好 {{info.name}}</span>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="logOff"> <el-dropdown-item @click.native="logOff">
@@ -1,13 +1,26 @@
<template> <template>
<el-menu mode="horizontal" :default-active="active" @select="handleMenuSelect"> <div class="d2-theme-header-menu" ref="page" :class="{'is-scrollable': isScroll}" flex="cross:center">
<template v-for="(menu, menuIndex) in header"> <div class="d2-theme-header-menu__content" ref="content" flex-box="1" flex>
<d2-layout-header-aside-menu-item v-if="menu.children === undefined" :menu="menu" :key="menuIndex"/> <div class="d2-theme-header-menu__scroll" ref="scroll" flex-box="0" :style="'transform: translateX(' + currentTranslateX + 'px);'">
<d2-layout-header-aside-menu-sub v-else :menu="menu" :key="menuIndex"/> <el-menu mode="horizontal" :default-active="active" @select="handleMenuSelect">
</template> <template v-for="(menu, menuIndex) in header">
</el-menu> <d2-layout-header-aside-menu-item v-if="menu.children === undefined" :menu="menu" :key="menuIndex"/>
<d2-layout-header-aside-menu-sub v-else :menu="menu" :key="menuIndex"/>
</template>
</el-menu>
</div>
</div>
<div v-if="isScroll" class="d2-theme-header-menu__prev" flex-box="0" @click="scroll('left')" flex="main:center cross:center">
<i class="el-icon-arrow-left"></i>
</div>
<div v-if="isScroll" class="d2-theme-header-menu__next" flex-box="0" @click="scroll('right')" flex="cross:center">
<i class="el-icon-arrow-right"></i>
</div>
</div>
</template> </template>
<script> <script>
import { throttle } from 'lodash'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import menuMixin from '../mixin/menu' import menuMixin from '../mixin/menu'
import d2LayoutMainMenuItem from '../components/menu-item/index.vue' import d2LayoutMainMenuItem from '../components/menu-item/index.vue'
@@ -28,7 +41,12 @@ export default {
}, },
data () { data () {
return { return {
active: '' active: '',
isScroll: false,
scrollWidth: 0,
contentWidth: 0,
currentTranslateX: 0,
throttledCheckScroll: null
} }
}, },
watch: { watch: {
@@ -38,6 +56,67 @@ export default {
}, },
immediate: true immediate: true
} }
},
methods: {
scroll (direction) {
if (direction === 'left') {
// 向右滚动
this.currentTranslateX = 0
} else {
// 向左滚动
if (this.contentWidth * 2 - this.currentTranslateX <= this.scrollWidth) {
this.currentTranslateX -= this.contentWidth
} else {
this.currentTranslateX = this.contentWidth - this.scrollWidth
}
}
},
checkScroll () {
let contentWidth = this.$refs.content.clientWidth
let scrollWidth = this.$refs.scroll.clientWidth
if (this.isScroll) {
// 页面依旧允许滚动的情况,需要更新width
if (this.contentWidth - this.scrollWidth === this.currentTranslateX) {
// currentTranslateX 也需要相应变化【在右端到头的情况时】
this.currentTranslateX = contentWidth - scrollWidth
// 快速的滑动依旧存在判断和计算时对应的contentWidth变成正数,所以需要限制一下
if (this.currentTranslateX > 0) {
this.currentTranslateX = 0
}
}
// 更新元素数据
this.contentWidth = contentWidth
this.scrollWidth = scrollWidth
// 判断何时滚动消失: 当page > content + btns(40)
if (contentWidth > scrollWidth + 40) {
this.isScroll = false
}
}
// 判断何时滚动出现: 当page < content
if (!this.isScroll && contentWidth < scrollWidth) {
this.isScroll = true
// 注意,当isScroll变为true,对应的元素盒子大小会发生变化
this.$nextTick(() => {
contentWidth = this.$refs.content.clientWidth
scrollWidth = this.$refs.scroll.clientWidth
this.contentWidth = contentWidth
this.scrollWidth = scrollWidth
this.currentTranslateX = 0
})
}
}
},
mounted () {
// 初始化判断
// 默认判断父元素和子元素的大小,以确定初始情况是否显示滚动
this.checkScroll()
// 全局窗口变化监听,判断父元素和子元素的大小,从而控制isScroll的开关
this.throttledCheckScroll = throttle(this.checkScroll, 300)
window.addEventListener('resize', this.throttledCheckScroll)
},
beforeDestroy () {
// 取消监听
window.removeEventListener('resize', this.throttledCheckScroll)
} }
} }
</script> </script>
@@ -1,6 +1,12 @@
<template> <template>
<div class="panel-search" flex="dir:top"> <div
<div class="panel-search__input-group" flex-box="0" flex="dir:top main:center cross:center"> class="panel-search"
flex="dir:top">
<div
class="panel-search__input-group"
flex-box="0"
flex="dir:top main:center cross:center"
@click.self="handlePanelClick">
<d2-icon-svg <d2-icon-svg
class="panel-search__logo" class="panel-search__logo"
name="d2-admin-text"/> name="d2-admin-text"/>
@@ -15,7 +21,9 @@
:clearable="true" :clearable="true"
@keydown.esc.native="handleEsc" @keydown.esc.native="handleEsc"
@select="handleSelect"> @select="handleSelect">
<d2-panel-search-item slot-scope="{ item }" :item="item"/> <d2-panel-search-item
slot-scope="{ item }"
:item="item"/>
</el-autocomplete> </el-autocomplete>
<div class="panel-search__tip"> <div class="panel-search__tip">
您可以使用快捷键 您可以使用快捷键
@@ -155,6 +163,12 @@ export default {
this.$refs.input.activated = false this.$refs.input.activated = false
} }
}, },
/**
* @augments 接收用户点击空白区域的关闭
*/
handlePanelClick () {
this.handleEsc()
},
/** /**
* @augments 接收用户触发的关闭 * @augments 接收用户触发的关闭
*/ */
@@ -30,6 +30,7 @@
class="d2-multiple-page-control-btn" class="d2-multiple-page-control-btn"
flex-box="0"> flex-box="0">
<el-dropdown <el-dropdown
size="default"
split-button split-button
@click="handleControlBtnClick" @click="handleControlBtnClick"
@command="command => handleControlItemClick(command)"> @command="command => handleControlItemClick(command)">
@@ -58,7 +59,7 @@
</template> </template>
<script> <script>
import { mapState, mapMutations } from 'vuex' import { mapState, mapActions } from 'vuex'
export default { export default {
components: { components: {
D2Contextmenu: () => import('../contextmenu'), D2Contextmenu: () => import('../contextmenu'),
@@ -88,7 +89,7 @@ export default {
]) ])
}, },
methods: { methods: {
...mapMutations('d2admin/page', [ ...mapActions('d2admin/page', [
'close', 'close',
'closeLeft', 'closeLeft',
'closeRight', 'closeRight',
@@ -100,7 +101,6 @@ export default {
*/ */
handleContextmenu (event) { handleContextmenu (event) {
let target = event.target let target = event.target
// 解决 https://github.com/d2-projects/d2-admin/issues/54 // 解决 https://github.com/d2-projects/d2-admin/issues/54
let flag = false let flag = false
if (target.className.indexOf('el-tabs__item') > -1) flag = true if (target.className.indexOf('el-tabs__item') > -1) flag = true
@@ -108,7 +108,6 @@ export default {
target = target.parentNode target = target.parentNode
flag = true flag = true
} }
if (flag) { if (flag) {
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
+16 -13
View File
@@ -11,24 +11,26 @@
<div <div
class="d2-theme-header" class="d2-theme-header"
:style="{ :style="{
opacity: this.searchActive ? 0.3 : 1 opacity: this.searchActive ? 0.5 : 1
}" }"
flex-box="0"> flex-box="0"
<div class="logo-group" :style="{width: asideCollapse ? asideWidthCollapse : asideWidth}"> flex>
<div class="logo-group" :style="{width: asideCollapse ? asideWidthCollapse : asideWidth}" flex-box="0">
<img v-if="asideCollapse" :src="`${$baseUrl}image/theme/${themeActiveSetting.name}/logo/icon-only.png`"> <img v-if="asideCollapse" :src="`${$baseUrl}image/theme/${themeActiveSetting.name}/logo/icon-only.png`">
<img v-else :src="`${$baseUrl}image/theme/${themeActiveSetting.name}/logo/all.png`"> <img v-else :src="`${$baseUrl}image/theme/${themeActiveSetting.name}/logo/all.png`">
</div> </div>
<div class="toggle-aside-btn" @click="handleToggleAside"> <div class="toggle-aside-btn" @click="handleToggleAside" flex-box="0">
<d2-icon name="bars"/> <d2-icon name="bars"/>
</div> </div>
<d2-menu-header/> <d2-menu-header flex-box="1"/>
<!-- 顶栏右侧 --> <!-- 顶栏右侧 -->
<div class="d2-header-right"> <div class="d2-header-right" flex-box="0">
<!-- 如果你只想在开发环境显示这个按钮请添加 v-if="$env === 'development'" --> <!-- 如果你只想在开发环境显示这个按钮请添加 v-if="$env === 'development'" -->
<d2-header-search @click="handleSearchClick"/> <d2-header-search @click="handleSearchClick"/>
<d2-header-error-log/> <d2-header-error-log/>
<d2-header-fullscreen/> <d2-header-fullscreen/>
<d2-header-theme/> <d2-header-theme/>
<d2-header-size/>
<d2-header-user/> <d2-header-user/>
</div> </div>
</div> </div>
@@ -41,7 +43,7 @@
class="d2-theme-container-aside" class="d2-theme-container-aside"
:style="{ :style="{
width: asideCollapse ? asideWidthCollapse : asideWidth, width: asideCollapse ? asideWidthCollapse : asideWidth,
opacity: this.searchActive ? 0.3 : 1 opacity: this.searchActive ? 0.5 : 1
}"> }">
<d2-menu-side/> <d2-menu-side/>
</div> </div>
@@ -79,7 +81,7 @@
</template> </template>
<script> <script>
import { mapState, mapGetters, mapMutations } from 'vuex' import { mapState, mapGetters, mapActions } from 'vuex'
import mixinSearch from './mixins/search' import mixinSearch from './mixins/search'
export default { export default {
name: 'd2-layout-header-aside', name: 'd2-layout-header-aside',
@@ -92,6 +94,7 @@ export default {
'd2-tabs': () => import('./components/tabs'), 'd2-tabs': () => import('./components/tabs'),
'd2-header-fullscreen': () => import('./components/header-fullscreen'), 'd2-header-fullscreen': () => import('./components/header-fullscreen'),
'd2-header-search': () => import('./components/header-search'), 'd2-header-search': () => import('./components/header-search'),
'd2-header-size': () => import('./components/header-size'),
'd2-header-theme': () => import('./components/header-theme'), 'd2-header-theme': () => import('./components/header-theme'),
'd2-header-user': () => import('./components/header-user'), 'd2-header-user': () => import('./components/header-user'),
'd2-header-error-log': () => import('./components/header-error-log') 'd2-header-error-log': () => import('./components/header-error-log')
@@ -106,12 +109,12 @@ export default {
}, },
computed: { computed: {
...mapState('d2admin', { ...mapState('d2admin', {
keepAlive: state => state.page.keepAlive,
grayActive: state => state.gray.active, grayActive: state => state.gray.active,
transitionActive: state => state.transition.active, transitionActive: state => state.transition.active,
asideCollapse: state => state.menu.asideCollapse asideCollapse: state => state.menu.asideCollapse
}), }),
...mapGetters('d2admin', { ...mapGetters('d2admin', {
keepAlive: 'page/keepAlive',
themeActiveSetting: 'theme/activeSetting' themeActiveSetting: 'theme/activeSetting'
}), }),
/** /**
@@ -126,14 +129,14 @@ export default {
} }
}, },
methods: { methods: {
...mapMutations({ ...mapActions('d2admin/menu', [
menuAsideCollapseToggle: 'd2admin/menu/asideCollapseToggle' 'asideCollapseToggle'
}), ]),
/** /**
* 接收点击切换侧边栏的按钮 * 接收点击切换侧边栏的按钮
*/ */
handleToggleAside () { handleToggleAside () {
this.menuAsideCollapseToggle() this.asideCollapseToggle()
} }
} }
} }
+8 -4
View File
@@ -12,6 +12,9 @@ import i18n from './i18n'
// 核心插件 // 核心插件
import d2Admin from '@/plugin/d2admin' import d2Admin from '@/plugin/d2admin'
// [ 可选插件组件 ]D2-Crud
import D2Crud from '@d2-projects/d2-crud'
// 菜单和路由设置 // 菜单和路由设置
import router from './router' import router from './router'
import menuHeader from '@/menu/header' import menuHeader from '@/menu/header'
@@ -21,6 +24,9 @@ import { frameInRoutes } from '@/router/routes'
// 核心插件 // 核心插件
Vue.use(d2Admin) Vue.use(d2Admin)
// 可选插件组件
Vue.use(D2Crud)
new Vue({ new Vue({
router, router,
store, store,
@@ -39,13 +45,11 @@ new Vue({
mounted () { mounted () {
// 展示系统信息 // 展示系统信息
this.$store.commit('d2admin/releases/versionShow') this.$store.commit('d2admin/releases/versionShow')
// 检查最新版本
this.$store.dispatch('d2admin/releases/checkUpdate')
// 用户登录后从数据库加载一系列的设置 // 用户登录后从数据库加载一系列的设置
this.$store.commit('d2admin/account/load') this.$store.dispatch('d2admin/account/load')
// 获取并记录用户 UA // 获取并记录用户 UA
this.$store.commit('d2admin/ua/get') this.$store.commit('d2admin/ua/get')
// 初始化全屏监听 // 初始化全屏监听
this.$store.commit('d2admin/fullscreen/listen') this.$store.dispatch('d2admin/fullscreen/listen')
} }
}).$mount('#app') }).$mount('#app')
Executable → Regular
View File
Executable → Regular
View File
@@ -0,0 +1,36 @@
<template>
<div class="d2-badge">
<p align="center">
<a><img src="https://img.shields.io/github/release/d2-projects/d2-admin.svg"/></a>
<a><img src="https://img.shields.io/github/forks/d2-projects/d2-admin.svg"/></a>
<a><img src="https://img.shields.io/github/stars/d2-projects/d2-admin.svg"/></a>
<a><img src="https://img.shields.io/github/issues/d2-projects/d2-admin.svg"/></a>
<a><img src="https://img.shields.io/github/issues-closed/d2-projects/d2-admin.svg"/></a>
<a><img src="https://img.shields.io/github/issues-pr/d2-projects/d2-admin.svg"/></a>
<a><img src="https://img.shields.io/github/issues-pr-closed/d2-projects/d2-admin.svg"/></a>
</p>
<p align="center">
<a><img src="https://img.shields.io/npm/v/@d2-admin/ice-scaffold.svg"/></a>
<a href="https://www.travis-ci.org/d2-projects/d2-admin"><img src="https://www.travis-ci.org/d2-projects/d2-admin.svg?branch=master"/></a>
<a><img src="https://img.shields.io/github/last-commit/d2-projects/d2-admin.svg"/></a>
<a><img src="https://img.shields.io/badge/code_style-standard-brightgreen.svg"/></a>
</p>
</div>
</template>
<style lang="scss" scoped>
.d2-badge {
margin-bottom: 20px;
p {
margin: 0px;
margin-bottom: 2px;
:nth-last-child() {
margin-bottom: 0px;
}
a {
display: inline-block;
margin: 0px 2px;
}
}
}
</style>
+7 -2
View File
@@ -27,7 +27,8 @@
</p> </p>
</el-popover> </el-popover>
</div> </div>
<d2-help-btn class="d2-mt"/> <d2-badge/>
<d2-help-btn/>
</template> </template>
</d2-page-cover> </d2-page-cover>
</d2-container> </d2-container>
@@ -35,9 +36,11 @@
<script> <script>
import D2HelpBtn from './components/d2-help-btn' import D2HelpBtn from './components/d2-help-btn'
import D2Badge from './components/d2-badge'
export default { export default {
components: { components: {
D2HelpBtn D2HelpBtn,
D2Badge
} }
} }
</script> </script>
@@ -48,6 +51,8 @@ export default {
.page__btn-group { .page__btn-group {
color: $color-text-placehoder; color: $color-text-placehoder;
font-size: 12px; font-size: 12px;
margin-top: -10px;
margin-bottom: 20px;
span { span {
color: $color-text-sub; color: $color-text-sub;
&:hover { &:hover {
+3 -3
View File
@@ -9,7 +9,7 @@
<!-- 表单部分 --> <!-- 表单部分 -->
<div class="form-group"> <div class="form-group">
<el-card> <el-card>
<el-form ref="loginForm" label-position="top" :rules="rules" :model="formLogin"> <el-form ref="loginForm" label-position="top" :rules="rules" :model="formLogin" size="default">
<el-form-item prop="username"> <el-form-item prop="username">
<el-input type="text" v-model="formLogin.username" placeholder="用户名"> <el-input type="text" v-model="formLogin.username" placeholder="用户名">
<i slot="prepend" class="fa fa-user-circle-o"></i> <i slot="prepend" class="fa fa-user-circle-o"></i>
@@ -28,12 +28,12 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-button @click="submit" type="primary" class="button-login">登录</el-button> <el-button size="default" @click="submit" type="primary" class="button-login">登录</el-button>
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
<!-- 快速登录按钮 --> <!-- 快速登录按钮 -->
<el-button type="info" class="button-help" @click="dialogVisible = true"> <el-button size="default" type="info" class="button-help" @click="dialogVisible = true">
快速选择用户(测试功能) 快速选择用户(测试功能)
</el-button> </el-button>
</div> </div>
+12
View File
@@ -0,0 +1,12 @@
<script>
// 跳板页面
export default {
beforeCreate () {
const path = this.$route.params.path
this.$router.replace(JSON.parse(path))
},
render: function (h) {
return h()
}
}
</script>
+13 -2
View File
@@ -3,6 +3,8 @@ import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css' import 'element-ui/lib/theme-chalk/index.css'
// flex 布局库 // flex 布局库
import 'flex.css' import 'flex.css'
// store
import store from '@/store/index'
// 组件 // 组件
import '@/components' import '@/components'
// svg 图标 // svg 图标
@@ -13,9 +15,18 @@ import pluginLog from '@/plugin/log'
import pluginOpen from '@/plugin/open' import pluginOpen from '@/plugin/open'
export default { export default {
install (Vue, options) { async install (Vue, options) {
// 获得用户设置的全局尺寸
const size = await store.dispatch('d2admin/db/get', {
dbName: 'sys',
path: 'size.value',
defaultValue: '',
user: true
})
// Element // Element
Vue.use(ElementUI) Vue.use(ElementUI, {
size
})
// 插件 // 插件
Vue.use(pluginError) Vue.use(pluginError)
Vue.use(pluginLog) Vue.use(pluginLog)
+9 -1
View File
@@ -1,6 +1,10 @@
import Vue from 'vue' import Vue from 'vue'
import VueRouter from 'vue-router' import VueRouter from 'vue-router'
// 进度条
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import store from '@/store/index' import store from '@/store/index'
import util from '@/libs/util.js' import util from '@/libs/util.js'
@@ -20,6 +24,8 @@ const router = new VueRouter({
* 权限验证 * 权限验证
*/ */
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
// 进度条
NProgress.start()
// 关闭搜索面板 // 关闭搜索面板
store.commit('d2admin/search/set', false) store.commit('d2admin/search/set', false)
// 验证当前路由所有的匹配中是否需要有登录验证的 // 验证当前路由所有的匹配中是否需要有登录验证的
@@ -45,11 +51,13 @@ router.beforeEach((to, from, next) => {
}) })
router.afterEach(to => { router.afterEach(to => {
// 进度条
NProgress.done()
// 需要的信息 // 需要的信息
const app = router.app const app = router.app
const { name, params, query } = to const { name, params, query } = to
// 多页控制 打开新的页面 // 多页控制 打开新的页面
app.$store.commit('d2admin/page/open', { name, params, query }) app.$store.dispatch('d2admin/page/open', { name, params, query })
// 更改标题 // 更改标题
util.title(to.meta.title) util.title(to.meta.title)
}) })
+6 -1
View File
@@ -44,7 +44,12 @@ const frameIn = [
* 在主框架之外显示 * 在主框架之外显示
*/ */
const frameOut = [ const frameOut = [
// 登陆 // 页面重定向使用 必须保留
{
path: '/redirect/:path*',
component: () => import('@/pages/redirect')
},
// 登录
{ {
path: '/login', path: '/login',
name: 'login', name: 'login',
+3 -1
View File
@@ -11,6 +11,7 @@ import gray from './modules/gray'
import page from './modules/page' import page from './modules/page'
import transition from './modules/transition' import transition from './modules/transition'
import search from './modules/search' import search from './modules/search'
import size from './modules/size'
export default { export default {
namespaced: true, namespaced: true,
@@ -27,6 +28,7 @@ export default {
gray, gray,
page, page,
transition, transition,
search search,
size
} }
} }
+22 -22
View File
@@ -12,9 +12,7 @@ export default {
* @param {Object} param password {String} 密码 * @param {Object} param password {String} 密码
* @param {Object} param route {Object} 登录成功后定向的路由对象 * @param {Object} param route {Object} 登录成功后定向的路由对象
*/ */
login ({ login ({ dispatch }, {
commit
}, {
vm, vm,
username, username,
password, password,
@@ -27,7 +25,7 @@ export default {
username, username,
password password
}) })
.then(res => { .then(async res => {
// 设置 cookie 一定要存 uuid 和 token 两个 cookie // 设置 cookie 一定要存 uuid 和 token 两个 cookie
// 整个系统依赖这两个数据进行校验和存储 // 整个系统依赖这两个数据进行校验和存储
// uuid 是用户身份唯一标识 用户注册的时候确定 并且不可改变 不可重复 // uuid 是用户身份唯一标识 用户注册的时候确定 并且不可改变 不可重复
@@ -36,11 +34,11 @@ export default {
util.cookies.set('uuid', res.uuid) util.cookies.set('uuid', res.uuid)
util.cookies.set('token', res.token) util.cookies.set('token', res.token)
// 设置 vuex 用户信息 // 设置 vuex 用户信息
commit('d2admin/user/set', { await dispatch('d2admin/user/set', {
name: res.name name: res.name
}, { root: true }) }, { root: true })
// 用户登录后从持久化数据加载一系列的设置 // 用户登录后从持久化数据加载一系列的设置
commit('load') await dispatch('load')
// 更新路由 尝试去获取 cookie 里保存的需要重定向的页面完整地址 // 更新路由 尝试去获取 cookie 里保存的需要重定向的页面完整地址
const path = util.cookies.get('redirect') const path = util.cookies.get('redirect')
// 根据是否存有重定向页面判断如何重定向 // 根据是否存有重定向页面判断如何重定向
@@ -49,9 +47,7 @@ export default {
util.cookies.remove('redirect') util.cookies.remove('redirect')
}) })
.catch(err => { .catch(err => {
console.group('登录结果')
console.log('err: ', err) console.log('err: ', err)
console.groupEnd()
}) })
}, },
/** /**
@@ -92,24 +88,28 @@ export default {
} else { } else {
logout() logout()
} }
} },
},
mutations: {
/** /**
* @description 用户登录后从持久化数据加载一系列的设置 * @description 用户登录后从持久化数据加载一系列的设置
* @param {Object} state vuex state * @param {Object} state vuex state
*/ */
load (state) { load ({ commit, dispatch }) {
// DB -> store 加载用户名 return new Promise(async resolve => {
this.commit('d2admin/user/load') // DB -> store 加载用户名
// DB -> store 加载主题 await dispatch('d2admin/user/load', null, { root: true })
this.commit('d2admin/theme/load') // DB -> store 加载主题
// DB -> store 加载页面过渡效果设置 await dispatch('d2admin/theme/load', null, { root: true })
this.commit('d2admin/transition/load') // DB -> store 加载页面过渡效果设置
// DB -> store 持久化数据加载上次退出时的多页列表 await dispatch('d2admin/transition/load', null, { root: true })
this.commit('d2admin/page/openedLoad') // DB -> store 持久化数据加载上次退出时的多页列表
// DB -> store 持久化数据加载这个用户之前设置的侧边栏折叠状态 await dispatch('d2admin/page/openedLoad', null, { root: true })
this.commit('d2admin/menu/asideCollapseLoad') // DB -> store 持久化数据加载侧边栏折叠状态
await dispatch('d2admin/menu/asideCollapseLoad', null, { root: true })
// DB -> store 持久化数据加载全局尺寸
await dispatch('d2admin/size/load', null, { root: true })
// end
resolve()
})
} }
} }
} }
+29 -18
View File
@@ -6,31 +6,42 @@ export default {
// 全屏激活 // 全屏激活
active: false active: false
}, },
mutations: { actions: {
/** /**
* @description 初始化监听 * @description 初始化监听
*/ */
listen () { listen ({ commit }) {
if (screenfull.enabled) { return new Promise(resolve => {
screenfull.on('change', () => { if (screenfull.enabled) {
if (!screenfull.isFullscreen) { screenfull.on('change', () => {
this.commit('d2admin/fullscreen/set', false) console.log('1')
} if (!screenfull.isFullscreen) {
}) commit('set', false)
} }
})
}
// end
resolve()
})
}, },
/** /**
* @description 切换全屏 * @description 切换全屏
*/ */
toggle () { toggle ({ commit }) {
if (screenfull.isFullscreen) { return new Promise(resolve => {
screenfull.exit() if (screenfull.isFullscreen) {
this.commit('d2admin/fullscreen/set', false) screenfull.exit()
} else { commit('set', false)
screenfull.request() } else {
this.commit('d2admin/fullscreen/set', true) screenfull.request()
} commit('set', true)
}, }
// end
resolve()
})
}
},
mutations: {
/** /**
* @description 设置 store 里的全屏状态 * @description 设置 store 里的全屏状态
* @param {Object} state vuex state * @param {Object} state vuex state
+10 -10
View File
@@ -24,16 +24,6 @@ export default {
return state.list.filter(l => l.type === 'error').length return state.list.filter(l => l.type === 'error').length
} }
}, },
mutations: {
/**
* @description 清空日志
* @param {Object} state vuex state
*/
clean (state) {
// store 赋值
state.list = []
}
},
actions: { actions: {
/** /**
* @description 添加一个日志 * @description 添加一个日志
@@ -70,5 +60,15 @@ export default {
info: toString(info) info: toString(info)
})) }))
} }
},
mutations: {
/**
* @description 清空日志
* @param {Object} state vuex state
*/
clean (state) {
// store 赋值
state.list = []
}
} }
} }
+58 -44
View File
@@ -11,6 +11,64 @@ export default {
// 侧边栏收缩 // 侧边栏收缩
asideCollapse: setting.menu.asideCollapse asideCollapse: setting.menu.asideCollapse
}, },
actions: {
/**
* 设置侧边栏展开或者收缩
* @param {Object} state vuex state
* @param {Boolean} collapse is collapse
*/
asideCollapseSet ({ state, dispatch }, collapse) {
return new Promise(async resolve => {
// store 赋值
state.asideCollapse = collapse
// 持久化
await dispatch('d2admin/db/set', {
dbName: 'sys',
path: 'menu.asideCollapse',
value: state.asideCollapse,
user: true
}, { root: true })
// end
resolve()
})
},
/**
* 切换侧边栏展开和收缩
* @param {Object} state vuex state
*/
asideCollapseToggle ({ state, dispatch }) {
return new Promise(async resolve => {
// store 赋值
state.asideCollapse = !state.asideCollapse
// 持久化
await dispatch('d2admin/db/set', {
dbName: 'sys',
path: 'menu.asideCollapse',
value: state.asideCollapse,
user: true
}, { root: true })
// end
resolve()
})
},
/**
* 从持久化数据读取侧边栏展开或者收缩
* @param {Object} state vuex state
*/
asideCollapseLoad ({ state, dispatch }) {
return new Promise(async resolve => {
// store 赋值
state.asideCollapse = await dispatch('d2admin/db/get', {
dbName: 'sys',
path: 'menu.asideCollapse',
defaultValue: setting.menu.asideCollapse,
user: true
}, { root: true })
// end
resolve()
})
}
},
mutations: { mutations: {
/** /**
* @description 设置顶栏菜单 * @description 设置顶栏菜单
@@ -29,50 +87,6 @@ export default {
asideSet (state, menu) { asideSet (state, menu) {
// store 赋值 // store 赋值
state.aside = menu state.aside = menu
},
/**
* 设置侧边栏展开或者收缩
* @param {Object} state vuex state
* @param {Boolean} collapse is collapse
*/
asideCollapseSet (state, collapse) {
// store 赋值
state.asideCollapse = collapse
// 持久化
this.dispatch('d2admin/db/set', {
dbName: 'sys',
path: 'menu.asideCollapse',
value: state.asideCollapse,
user: true
})
},
/**
* 切换侧边栏展开和收缩
* @param {Object} state vuex state
*/
asideCollapseToggle (state) {
// store 赋值
state.asideCollapse = !state.asideCollapse
// 持久化
this.dispatch('d2admin/db/set', {
dbName: 'sys',
path: 'menu.asideCollapse',
value: state.asideCollapse,
user: true
})
},
/**
* 从持久化数据读取侧边栏展开或者收缩
* @param {Object} state vuex state
*/
async asideCollapseLoad (state) {
// store 赋值
state.asideCollapse = await this.dispatch('d2admin/db/get', {
dbName: 'sys',
path: 'menu.asideCollapse',
defaultValue: setting.menu.asideCollapse,
user: true
})
} }
} }
} }
+351 -259
View File
@@ -1,3 +1,5 @@
import { remove, get } from 'lodash'
// 设置文件 // 设置文件
import setting from '@/setting.js' import setting from '@/setting.js'
@@ -9,15 +11,330 @@ export default {
// 当前显示的多页面列表 // 当前显示的多页面列表
opened: setting.page.opened, opened: setting.page.opened,
// 当前页面 // 当前页面
current: '' current: '',
// 需要缓存的页面 name
keepAlive: []
}, },
getters: { actions: {
/** /**
* @description 从当前所有打开的多标签页里返回需要缓存的页面 name * @class opened
* @param {*} state vuex state * @description 从持久化数据载入分页列表
* @param {Object} state vuex state
*/ */
keepAlive (state) { openedLoad ({ state, commit, dispatch }) {
return state.opened.filter(item => { return new Promise(async resolve => {
// store 赋值
const value = await dispatch('d2admin/db/get', {
dbName: 'sys',
path: 'page.opened',
defaultValue: setting.page.opened,
user: true
}, { root: true })
// 在处理函数中进行数据优化 过滤掉现在已经失效的页签或者已经改变了信息的页签
// 以 name 字段为准
// 如果页面过多的话可能需要优化算法
// valid 有效列表 1, 1, 0, 1 => 有效, 有效, 失效, 有效
const valid = []
// 处理数据
state.opened = value.map(opened => {
// 忽略首页
if (opened.name === 'index') {
valid.push(1)
return opened
}
// 尝试在所有的支持多标签页的页面里找到 name 匹配的页面
const find = state.pool.find(item => item.name === opened.name)
// 记录有效或无效信息
valid.push(find ? 1 : 0)
// 返回合并后的数据 新的覆盖旧的
// 新的数据中一般不会携带 params 和 query, 所以旧的参数会留存
return Object.assign({}, opened, find)
}).filter((opened, index) => valid[index] === 1)
// 根据 opened 数据生成缓存设置
commit('keepAliveRefresh')
// end
resolve()
})
},
/**
* 将 opened 属性赋值并持久化 在这之前请先确保已经更新了 state.opened
* @param {Object} state vuex state
*/
opend2db ({ state, dispatch }) {
return new Promise(async resolve => {
// 设置数据
dispatch('d2admin/db/set', {
dbName: 'sys',
path: 'page.opened',
value: state.opened,
user: true
}, { root: true })
// end
resolve()
})
},
/**
* @class opened
* @description 更新页面列表上的某一项
* @param {Object} state vuex state
* @param {Object} param { index, params, query } 路由信息
*/
openedUpdate ({ state, commit, dispatch }, { index, params, query }) {
return new Promise(async resolve => {
// 更新页面列表某一项
let page = state.opened[index]
page.params = params || page.params
page.query = query || page.query
state.opened.splice(index, 1, page)
// 增加缓存设置
commit('keepAlivePush', page.name)
// 持久化
await dispatch('opend2db')
// end
resolve()
})
},
/**
* @class opened
* @description 新增一个 tag (打开一个页面)
* @param {Object} state vuex state
* @param {Object} param new tag info
*/
add ({ state, commit, dispatch }, { tag, params, query }) {
return new Promise(async resolve => {
// 设置新的 tag 在新打开一个以前没打开过的页面时使用
let newTag = tag
newTag.params = params || newTag.params
newTag.query = query || newTag.query
// 添加进当前显示的页面数组
state.opened.push(newTag)
// 如果这个页面需要缓存 将其添加到缓存设置
if (get(newTag, 'meta.requiresAuth', false)) {
commit('keepAlivePush', tag.name)
}
// 持久化
await dispatch('opend2db')
// end
resolve()
})
},
/**
* @class current
* @description 打开一个新的页面
* @param {Object} state vuex state
* @param {Object} param { name, params, query } 路由信息
*/
open ({ state, commit, dispatch }, { name, params, query }) {
return new Promise(async resolve => {
// 已经打开的页面
let opened = state.opened
// 判断此页面是否已经打开 并且记录位置
let pageOpendIndex = 0
const pageOpend = opened.find((page, index) => {
const same = page.name === name
pageOpendIndex = same ? index : pageOpendIndex
return same
})
if (pageOpend) {
// 页面以前打开过 但是新的页面可能 name 一样,参数不一样
await dispatch('openedUpdate', {
index: pageOpendIndex,
params,
query
})
} else {
// 页面以前没有打开过
let page = state.pool.find(t => t.name === name)
if (page) {
await dispatch('add', {
tag: page,
params,
query
})
}
}
commit('currentSet', name)
// end
resolve()
})
},
/**
* @class opened
* @description 关闭一个 tag (关闭一个页面)
* @param {Object} state vuex state
* @param {Object} param { tagName: 要关闭的标签名字, vm: vue }
*/
close ({ state, commit, dispatch }, { tagName, vm }) {
return new Promise(async resolve => {
// 下个新的页面
let newPage = state.opened[0]
const isCurrent = state.current === tagName
// 如果关闭的页面就是当前显示的页面
if (isCurrent) {
// 去找一个新的页面
let len = state.opened.length
for (let i = 1; i < len; i++) {
if (state.opened[i].name === tagName) {
if (i < len - 1) {
newPage = state.opened[i + 1]
} else {
newPage = state.opened[i - 1]
}
break
}
}
}
// 找到这个页面在已经打开的数据里是第几个
const index = state.opened.findIndex(page => page.name === tagName)
if (index >= 0) {
// 更新数据 删除关闭的页面
state.opened.splice(index, 1)
// 如果这个页面是缓存的页面 将其在缓存设置中删除
commit('keepAliveRemove', tagName)
}
// 持久化
await dispatch('opend2db')
// 最后需要判断是否需要跳到首页
if (isCurrent) {
const { name = '', params = {}, query = {} } = newPage
let routerObj = {
name,
params,
query
}
vm.$router.push(routerObj)
}
// end
resolve()
})
},
/**
* @class opened
* @description 关闭当前标签左边的标签
* @param {Object} state vuex state
* @param {Object} param { pageSelect: 当前选中的tagName, vm: vue }
*/
closeLeft ({ state, commit, dispatch }, { pageSelect, vm } = {}) {
return new Promise(async resolve => {
const pageAim = pageSelect || state.current
let currentIndex = 0
state.opened.forEach((page, index) => {
if (page.name === pageAim) {
currentIndex = index
}
})
if (currentIndex > 0) {
// 删除打开的页面 并在缓存设置中删除
state.opened.splice(1, currentIndex - 1).forEach(({ name }) => commit('keepAliveRemove', name))
}
state.current = pageAim
if (vm && vm.$route.name !== pageAim) {
vm.$router.push({
name: pageAim
})
}
// 持久化
await dispatch('opend2db')
// end
resolve()
})
},
/**
* @class opened
* @description 关闭当前标签右边的标签
* @param {Object} state vuex state
* @param {Object} param { pageSelect: 当前选中的tagName, vm: vue }
*/
closeRight ({ state, commit, dispatch }, { pageSelect, vm } = {}) {
return new Promise(async resolve => {
const pageAim = pageSelect || state.current
let currentIndex = 0
state.opened.forEach((page, index) => {
if (page.name === pageAim) {
currentIndex = index
}
})
// 删除打开的页面 并在缓存设置中删除
state.opened.splice(currentIndex + 1).forEach(({ name }) => commit('keepAliveRemove', name))
// 设置当前的页面
state.current = pageAim
if (vm && vm.$route.name !== pageAim) {
vm.$router.push({
name: pageAim
})
}
// 持久化
await dispatch('opend2db')
// end
resolve()
})
},
/**
* @class opened
* @description 关闭当前激活之外的 tag
* @param {Object} state vuex state
* @param {Object} param { pageSelect: 当前选中的tagName, vm: vue }
*/
closeOther ({ state, commit, dispatch }, { pageSelect, vm } = {}) {
return new Promise(async resolve => {
const pageAim = pageSelect || state.current
let currentIndex = 0
state.opened.forEach((page, index) => {
if (page.name === pageAim) {
currentIndex = index
}
})
// 删除打开的页面数据 并更新缓存设置
if (currentIndex === 0) {
state.opened.splice(1).forEach(({ name }) => commit('keepAliveRemove', name))
} else {
state.opened.splice(currentIndex + 1).forEach(({ name }) => commit('keepAliveRemove', name))
state.opened.splice(1, currentIndex - 1).forEach(({ name }) => commit('keepAliveRemove', name))
}
// 设置新的页面
state.current = pageAim
if (vm && vm.$route.name !== pageAim) {
vm.$router.push({
name: pageAim
})
}
// 持久化
await dispatch('opend2db')
// end
resolve()
})
},
/**
* @class opened
* @description 关闭所有 tag
* @param {Object} state vuex state
* @param {Object} vm vue
*/
closeAll ({ state, commit, dispatch }, vm) {
return new Promise(async resolve => {
// 删除打开的页面 并在缓存设置中删除
state.opened.splice(1).forEach(({ name }) => commit('keepAliveRemove', name))
// 持久化
await dispatch('opend2db')
// 关闭所有的标签页后需要判断一次现在是不是在首页
if (vm.$route.name !== 'index') {
vm.$router.push({
name: 'index'
})
}
// end
resolve()
})
}
},
mutations: {
/**
* @class keepAlive
* @description 从已经打开的页面记录中更新需要缓存的页面记录
* @param {Object} state vuex state
*/
keepAliveRefresh (state) {
state.keepAlive = state.opened.filter(item => {
if (item.meta) { if (item.meta) {
if (item.meta.notCache) { if (item.meta.notCache) {
return false return false
@@ -25,44 +342,33 @@ export default {
} }
return true return true
}).map(e => e.name) }).map(e => e.name)
} },
},
mutations: {
/** /**
* @class current * @description 删除一个页面的缓存设置
* @description 打开一个新的页面
* @param {Object} state vuex state * @param {Object} state vuex state
* @param {Object} param { name, params, query } 路由信息 * @param {String} name name
*/ */
open (state, { name, params, query }) { keepAliveRemove (state, name) {
// 已经打开的页面 const list = [ ...state.keepAlive ]
let opened = state.opened remove(list, item => item === name)
// 判断此页面是否已经打开 并且记录位置 state.keepAlive = list
let pageOpendIndex = 0 },
const pageOpend = opened.find((page, index) => { /**
const same = page.name === name * @description 增加一个页面的缓存设置
pageOpendIndex = same ? index : pageOpendIndex * @param {Object} state vuex state
return same * @param {String} name name
}) */
if (pageOpend) { keepAlivePush (state, name) {
// 页面以前打开过 但是新的页面可能 name 一样,参数不一样 const keep = [ ...state.keepAlive ]
this.commit('d2admin/page/openedUpdate', { keep.push(name)
index: pageOpendIndex, state.keepAlive = Array.from(new Set(keep))
params, },
query /**
}) * @description 清空页面缓存设置
} else { * @param {Object} state vuex state
// 页面以前没有打开过 */
let page = state.pool.find(t => t.name === name) keepAliveClean (state) {
if (page) { state.keepAlive = []
this.commit('d2admin/page/add', {
tag: page,
params,
query
})
}
}
this.commit('d2admin/page/currentSet', name)
}, },
/** /**
* @class current * @class current
@@ -73,222 +379,6 @@ export default {
currentSet (state, name) { currentSet (state, name) {
state.current = name state.current = name
}, },
/**
* @class opened
* @description 更新页面列表上的某一项
* @param {Object} state vuex state
* @param {Object} param { index, params, query } 路由信息
*/
openedUpdate (state, { index, params, query }) {
// 更新页面列表某一项
let page = state.opened[index]
page.params = params || page.params
page.query = query || page.query
state.opened.splice(index, 1, page)
// 持久化
this.commit('d2admin/page/opend2db')
},
/**
* 将 opened 属性赋值并持久化 在这之前请先确保已经更新了 state.opened
* @param {Object} state vuex state
*/
opend2db (state) {
this.dispatch('d2admin/db/set', {
dbName: 'sys',
path: 'page.opened',
value: state.opened,
user: true
})
},
/**
* @class opened
* @description 从持久化数据载入分页列表
* @param {Object} state vuex state
*/
async openedLoad (state) {
// store 赋值
const value = await this.dispatch('d2admin/db/get', {
dbName: 'sys',
path: 'page.opened',
defaultValue: setting.page.opened,
user: true
})
// 在处理函数中进行数据优化 过滤掉现在已经失效的页签或者已经改变了信息的页签
// 以 name 字段为准
// 如果页面过多的话可能需要优化算法
// valid 有效列表 1, 1, 0, 1 => 有效, 有效, 失效, 有效
const valid = []
// 处理数据
state.opened = value.map(opened => {
// 忽略首页
if (opened.name === 'index') {
valid.push(1)
return opened
}
// 尝试在所有的支持多标签页的页面里找到 name 匹配的页面
const find = state.pool.find(item => item.name === opened.name)
// 记录有效或无效信息
valid.push(find ? 1 : 0)
// 返回合并后的数据 新的覆盖旧的
// 新的数据中一般不会携带 params 和 query, 所以旧的参数会留存
return Object.assign({}, opened, find)
}).filter((opened, index) => valid[index] === 1)
},
/**
* @class opened
* @description 新增一个 tag (打开一个页面)
* @param {Object} state vuex state
* @param {Object} param new tag info
*/
add (state, { tag, params, query }) {
// 设置新的 tag 在新打开一个以前没打开过的页面时使用
let newTag = tag
newTag.params = params || newTag.params
newTag.query = query || newTag.query
// 添加进当前显示的页面数组
state.opened.push(newTag)
// 持久化
this.commit('d2admin/page/opend2db')
},
/**
* @class opened
* @description 关闭一个 tag (关闭一个页面)
* @param {Object} state vuex state
* @param {Object} param { tagName: 要关闭的标签名字, vm: vue }
*/
close (state, { tagName, vm }) {
// 下个新的页面
let newPage = state.opened[0]
const isCurrent = state.current === tagName
// 如果关闭的页面就是当前显示的页面
if (isCurrent) {
// 去找一个新的页面
let len = state.opened.length
for (let i = 1; i < len; i++) {
if (state.opened[i].name === tagName) {
if (i < len - 1) {
newPage = state.opened[i + 1]
} else {
newPage = state.opened[i - 1]
}
break
}
}
}
// 找到这个页面在已经打开的数据里是第几个
const index = state.opened.findIndex(page => page.name === tagName)
if (index >= 0) {
state.opened.splice(index, 1)
}
// 持久化
this.commit('d2admin/page/opend2db')
// 最后需要判断是否需要跳到首页
if (isCurrent) {
const { name = '', params = {}, query = {} } = newPage
let routerObj = {
name,
params,
query
}
vm.$router.push(routerObj)
}
},
/**
* @class opened
* @description 关闭当前标签左边的标签
* @param {Object} state vuex state
* @param {Object} param { pageSelect: 当前选中的tagName, vm: vue }
*/
closeLeft (state, { pageSelect, vm } = {}) {
const pageAim = pageSelect || state.current
let currentIndex = 0
state.opened.forEach((page, index) => {
if (page.name === pageAim) {
currentIndex = index
}
})
if (currentIndex > 0) {
state.opened.splice(1, currentIndex - 1)
}
state.current = pageAim
if (vm && vm.$route.name !== pageAim) {
vm.$router.push({
name: pageAim
})
}
// 持久化
this.commit('d2admin/page/opend2db')
},
/**
* @class opened
* @description 关闭当前标签右边的标签
* @param {Object} state vuex state
* @param {Object} param { pageSelect: 当前选中的tagName, vm: vue }
*/
closeRight (state, { pageSelect, vm } = {}) {
const pageAim = pageSelect || state.current
let currentIndex = 0
state.opened.forEach((page, index) => {
if (page.name === pageAim) {
currentIndex = index
}
})
state.opened.splice(currentIndex + 1)
state.current = pageAim
if (vm && vm.$route.name !== pageAim) {
vm.$router.push({
name: pageAim
})
}
// 持久化
this.commit('d2admin/page/opend2db')
},
/**
* @class opened
* @description 关闭当前激活之外的 tag
* @param {Object} state vuex state
* @param {Object} param { pageSelect: 当前选中的tagName, vm: vue }
*/
closeOther (state, { pageSelect, vm } = {}) {
const pageAim = pageSelect || state.current
let currentIndex = 0
state.opened.forEach((page, index) => {
if (page.name === pageAim) {
currentIndex = index
}
})
if (currentIndex === 0) {
state.opened.splice(1)
} else {
state.opened.splice(currentIndex + 1)
state.opened.splice(1, currentIndex - 1)
}
state.current = pageAim
if (vm && vm.$route.name !== pageAim) {
vm.$router.push({
name: pageAim
})
}
// 持久化
this.commit('d2admin/page/opend2db')
},
/**
* @class opened
* @description 关闭所有 tag
* @param {Object} state vuex state
* @param {Object} vm vue
*/
closeAll (state, vm) {
state.opened.splice(1)
// 持久化
this.commit('d2admin/page/opend2db')
// 关闭所有的标签页后需要判断一次现在是不是在首页
if (vm.$route.name !== 'index') {
vm.$router.push({
name: 'index'
})
}
},
/** /**
* @class pool * @class pool
* @description 保存 pool (候选池) * @description 保存 pool (候选池)
@@ -302,8 +392,10 @@ export default {
if (route.children) { if (route.children) {
push(route.children) push(route.children)
} else { } else {
const { meta, name, path } = route if (!route.hidden) {
pool.push({ meta, name, path }) const { meta, name, path } = route
pool.push({ meta, name, path })
}
} }
}) })
} }
+46
View File
@@ -0,0 +1,46 @@
export default {
namespaced: true,
state: {
// 尺寸
value: '' // medium small mini
},
actions: {
/**
* @description 设置尺寸
* @param {Object} state vuex state
* @param {String} size 尺寸
*/
set ({ state, dispatch }, size) {
return new Promise(async resolve => {
// store 赋值
state.value = size
// 持久化
await dispatch('d2admin/db/set', {
dbName: 'sys',
path: 'size.value',
value: state.value,
user: true
}, { root: true })
// end
resolve()
})
},
/**
* @description 从持久化数据读取尺寸设置
* @param {Object} state vuex state
*/
load ({ state, dispatch }) {
return new Promise(async resolve => {
// store 赋值
state.value = await dispatch('d2admin/db/get', {
dbName: 'sys',
path: 'size.value',
defaultValue: 'default',
user: true
}, { root: true })
// end
resolve()
})
}
}
}
+32 -22
View File
@@ -18,40 +18,50 @@ export default {
return state.list.find(theme => theme.name === state.activeName) return state.list.find(theme => theme.name === state.activeName)
} }
}, },
mutations: { actions: {
/** /**
* @description 激活一个主题 * @description 激活一个主题
* @param {Object} state vuex state * @param {Object} state vuex state
* @param {String} themeValue 需要激活的主题名称 * @param {String} themeValue 需要激活的主题名称
*/ */
set (state, themeName) { set ({ state, commit, dispatch }, themeName) {
// 检查这个主题在主题列表里是否存在 return new Promise(async resolve => {
state.activeName = state.list.find(e => e.name === themeName) ? themeName : state.list[0].name // 检查这个主题在主题列表里是否存在
// 将 vuex 中的主题应用到 dom state.activeName = state.list.find(e => e.name === themeName) ? themeName : state.list[0].name
this.commit('d2admin/theme/dom') // 将 vuex 中的主题应用到 dom
// 持久化 commit('dom')
this.dispatch('d2admin/db/set', { // 持久化
dbName: 'sys', await dispatch('d2admin/db/set', {
path: 'theme.activeName', dbName: 'sys',
value: state.activeName, path: 'theme.activeName',
user: true value: state.activeName,
user: true
}, { root: true })
// end
resolve()
}) })
}, },
/** /**
* @description 从持久化数据加载主题设置 * @description 从持久化数据加载主题设置
* @param {Object} state vuex state * @param {Object} state vuex state
*/ */
async load (state) { load ({ state, commit, dispatch }) {
// store 赋值 return new Promise(async resolve => {
state.activeName = await this.dispatch('d2admin/db/get', { // store 赋值
dbName: 'sys', state.activeName = await dispatch('d2admin/db/get', {
path: 'theme.activeName', dbName: 'sys',
defaultValue: state.list[0].name, path: 'theme.activeName',
user: true defaultValue: state.list[0].name,
user: true
}, { root: true })
// 将 vuex 中的主题应用到 dom
commit('dom')
// end
resolve()
}) })
// 更新到页面 }
this.commit('d2admin/theme/dom') },
}, mutations: {
/** /**
* @description 将 vuex 中的主题应用到 dom * @description 将 vuex 中的主题应用到 dom
* @param {Object} state vuex state * @param {Object} state vuex state
+25 -17
View File
@@ -7,34 +7,42 @@ export default {
// 是否开启页面过度动画 // 是否开启页面过度动画
active: setting.transition.active active: setting.transition.active
}, },
mutations: { actions: {
/** /**
* @description 设置开启状态 * @description 设置开启状态
* @param {Object} state vuex state * @param {Object} state vuex state
* @param {Boolean} active 新的状态 * @param {Boolean} active 新的状态
*/ */
set (state, active) { set ({ state, dispatch }, active) {
// store 赋值 return new Promise(async resolve => {
state.active = active // store 赋值
// 持久化 state.active = active
this.dispatch('d2admin/db/set', { // 持久化
dbName: 'sys', await dispatch('d2admin/db/set', {
path: 'transition.active', dbName: 'sys',
value: state.active, path: 'transition.active',
user: true value: state.active,
user: true
}, { root: true })
// end
resolve()
}) })
}, },
/** /**
* 从数据库读取页面过渡动画设置 * 从数据库读取页面过渡动画设置
* @param {Object} state vuex state * @param {Object} state vuex state
*/ */
async load (state) { load ({ state, dispatch }) {
// store 赋值 return new Promise(async resolve => {
state.active = await this.dispatch('d2admin/db/get', { // store 赋值
dbName: 'sys', state.active = await dispatch('d2admin/db/get', {
path: 'transition.active', dbName: 'sys',
defaultValue: setting.transition.active, path: 'transition.active',
user: true defaultValue: setting.transition.active,
user: true
}, { root: true })
// end
resolve()
}) })
} }
} }
+25 -17
View File
@@ -7,34 +7,42 @@ export default {
// 用户信息 // 用户信息
info: setting.user.info info: setting.user.info
}, },
mutations: { actions: {
/** /**
* @description 设置用户数据 * @description 设置用户数据
* @param {Object} state vuex state * @param {Object} state vuex state
* @param {*} info info * @param {*} info info
*/ */
set (state, info) { set ({ state, dispatch }, info) {
// store 赋值 return new Promise(async resolve => {
state.info = info // store 赋值
// 持久化 state.info = info
this.dispatch('d2admin/db/set', { // 持久化
dbName: 'sys', await dispatch('d2admin/db/set', {
path: 'user.info', dbName: 'sys',
value: info, path: 'user.info',
user: true value: info,
user: true
}, { root: true })
// end
resolve()
}) })
}, },
/** /**
* @description 从数据库取用户数据 * @description 从数据库取用户数据
* @param {Object} state vuex state * @param {Object} state vuex state
*/ */
async load (state) { load ({ state, dispatch }) {
// store 赋值 return new Promise(async resolve => {
state.info = await this.dispatch('d2admin/db/get', { // store 赋值
dbName: 'sys', state.info = await dispatch('d2admin/db/get', {
path: 'user.info', dbName: 'sys',
defaultValue: setting.user.info, path: 'user.info',
user: true defaultValue: setting.user.info,
user: true
}, { root: true })
// end
resolve()
}) })
} }
} }
+6 -2
View File
@@ -6,7 +6,9 @@ function resolve (dir) {
} }
// 基础路径 注意发布之前要先修改这里 // 基础路径 注意发布之前要先修改这里
const baseUrl = '/d2-admin-start-kit/' let baseUrl = '/'
// 演示项目自动构建使用
if (process.env.VUE_APP_TRAVIS === 'TRUE') baseUrl = '/d2-admin-start-kit/'
module.exports = { module.exports = {
baseUrl: baseUrl, // 根据你的实际情况更改这里 baseUrl: baseUrl, // 根据你的实际情况更改这里
@@ -14,9 +16,11 @@ module.exports = {
devServer: { devServer: {
publicPath: baseUrl // 和 baseUrl 保持一致 publicPath: baseUrl // 和 baseUrl 保持一致
}, },
// webpack 设置
// 默认设置: https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-service/lib/config/base.js // 默认设置: https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-service/lib/config/base.js
chainWebpack: config => { chainWebpack: config => {
// 解决 cli3 热更新失效 https://github.com/vuejs/vue-cli/issues/1559
config.resolve
.symlinks(true)
// markdown // markdown
config.module config.module
.rule('md') .rule('md')
-9394
View File
File diff suppressed because it is too large Load Diff