Merge branch 'hotfix/12-16'

Former-commit-id: a24414bc5eb7e16004e918424d8c2fcf434779c1
This commit is contained in:
liyang
2018-12-16 20:10:06 +08:00
61 changed files with 8316 additions and 9112 deletions
+7
View File
@@ -0,0 +1,7 @@
# No Mock 构建
# 指定构建模式
NODE_ENV=production
# 标记当前是 No Mock 构建
VUE_APP_BUILD_MODE=nomock
+4589 -6055
View File
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -1,17 +1,19 @@
{ {
"name": "d2-admin", "name": "d2-admin",
"version": "1.5.9", "version": "1.6.9",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve --open", "serve": "vue-cli-service serve --open",
"start": "npm run serve", "start": "npm run serve",
"dev": "npm run serve", "dev": "npm run serve",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"build:nomock": "vue-cli-service build --mode nomock",
"build:travis": "vue-cli-service build --mode travis", "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/vue-filters-date": "^1.0.2",
"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",
@@ -21,11 +23,11 @@
"fuse.js": "^3.2.1", "fuse.js": "^3.2.1",
"hotkeys-js": "^3.3.6", "hotkeys-js": "^3.3.6",
"js-cookie": "^2.2.0", "js-cookie": "^2.2.0",
"lodash": "^4.17.11", "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", "nprogress": "^0.2.0",
"particles.js": "^2.0.0", "qs": "^6.6.0",
"screenfull": "^3.3.2", "screenfull": "^3.3.2",
"ua-parser-js": "^0.7.18", "ua-parser-js": "^0.7.18",
"vue": "^2.5.17", "vue": "^2.5.17",
@@ -48,6 +50,7 @@
"sass-loader": "^7.0.1", "sass-loader": "^7.0.1",
"svg-sprite-loader": "^3.8.0", "svg-sprite-loader": "^3.8.0",
"text-loader": "0.0.1", "text-loader": "0.0.1",
"uglifyjs-webpack-plugin": "^2.0.1",
"vue-template-compiler": "^2.5.17" "vue-template-compiler": "^2.5.17"
}, },
"lint-staged": { "lint-staged": {
-1
View File
@@ -1,5 +1,4 @@
// 优化显示 // 优化显示
html, body { html, body {
margin: 0px; margin: 0px;
height: 100%; height: 100%;
-1
View File
@@ -1,5 +1,4 @@
// element 样式补丁 // element 样式补丁
.el-card { .el-card {
&.is-always-shadow { &.is-always-shadow {
box-shadow: 0 0 8px 0 rgba(232,237,250,.6), 0 2px 4px 0 rgba(232,237,250,.5); box-shadow: 0 0 8px 0 rgba(232,237,250,.6), 0 2px 4px 0 rgba(232,237,250,.5);
-1
View File
@@ -1,4 +1,3 @@
@import '~@/assets/style/public.scss';
@import '~@/assets/style/theme/theme-base.scss'; @import '~@/assets/style/theme/theme-base.scss';
@import '~@/assets/style/theme/d2/index.scss'; @import '~@/assets/style/theme/d2/index.scss';
@@ -3,7 +3,7 @@
<div v-if="$slots.header" class="d2-container-card__header" ref="header"> <div v-if="$slots.header" class="d2-container-card__header" ref="header">
<slot name="header"/> <slot name="header"/>
</div> </div>
<div class="d2-container-card__body"> <div class="d2-container-card__body" ref="body">
<div class="d2-container-card__body-card"> <div class="d2-container-card__body-card">
<slot/> <slot/>
</div> </div>
@@ -15,7 +15,19 @@
</template> </template>
<script> <script>
import scroll from './mixins/normal'
export default { export default {
name: 'd2-container-card' name: 'd2-container-card',
mixins: [
scroll
],
mounted () {
// 增加滚动事件监听
this.addScrollListener()
},
beforeDestroy () {
// 移除滚动事件监听
this.removeScrollListener()
}
} }
</script> </script>
@@ -3,7 +3,7 @@
<div v-if="$slots.header" class="d2-container-full__header" ref="header"> <div v-if="$slots.header" class="d2-container-full__header" ref="header">
<slot name="header"/> <slot name="header"/>
</div> </div>
<div class="d2-container-full__body"> <div class="d2-container-full__body" ref="body">
<slot/> <slot/>
</div> </div>
<div v-if="$slots.footer" class="d2-container-full__footer" ref="footer"> <div v-if="$slots.footer" class="d2-container-full__footer" ref="footer">
@@ -13,7 +13,19 @@
</template> </template>
<script> <script>
import scroll from './mixins/normal'
export default { export default {
name: 'd2-container-full' name: 'd2-container-full',
mixins: [
scroll
],
mounted () {
// 增加滚动事件监听
this.addScrollListener()
},
beforeDestroy () {
// 移除滚动事件监听
this.removeScrollListener()
}
} }
</script> </script>
@@ -3,7 +3,7 @@
<div v-if="$slots.header" class="d2-container-ghost__header" ref="header"> <div v-if="$slots.header" class="d2-container-ghost__header" ref="header">
<slot name="header"/> <slot name="header"/>
</div> </div>
<div class="d2-container-ghost__body"> <div class="d2-container-ghost__body" ref="body">
<slot/> <slot/>
</div> </div>
<div v-if="$slots.footer" class="d2-container-ghost__footer" ref="footer"> <div v-if="$slots.footer" class="d2-container-ghost__footer" ref="footer">
@@ -11,3 +11,21 @@
</div> </div>
</div> </div>
</template> </template>
<script>
import scroll from './mixins/normal'
export default {
name: 'd2-container-ghost',
mixins: [
scroll
],
mounted () {
// 增加滚动事件监听
this.addScrollListener()
},
beforeDestroy () {
// 移除滚动事件监听
this.removeScrollListener()
}
}
</script>
@@ -0,0 +1,74 @@
<template>
<div
class="d2-source"
:class="{ 'd2-source--active': isActive }"
@click="handleClick">
<d2-icon name="code"/> SourceCode
</div>
</template>
<script>
export default {
props: {
// 文件路径
filename: {
type: String,
default: ''
}
},
data () {
return {
isActive: false
}
},
mounted () {
// 一秒后显示按钮
setTimeout(() => {
this.isActive = true
}, 500)
},
methods: {
// 点击按钮的时候跳转到源代码
handleClick () {
const file = this.filename.split('?')[0]
const url = file
? `https://github.com/d2-projects/d2-admin-start-kit/blob/master/${file}`
: 'https://github.com/d2-projects/d2-admin-start-kit'
this.$open(url)
}
}
}
</script>
<style lang="scss" scoped>
.d2-source {
$borderRadius: 4px;
$paddingLR: 15px;
$paddingTB: 7px;
$fontSize: 12px;
$rightOuter: $paddingLR / 2;
opacity: 0;
position: fixed;
z-index: 9999;
right: - $borderRadius - $rightOuter;
bottom: 20px;
font-size: $fontSize;
line-height: $fontSize;
font-weight: bold;
border-radius: $borderRadius;
padding: $paddingTB $paddingLR;
padding-right: $borderRadius + $paddingLR;
background-color: rgba(#000, .7);
border: 1px solid #000;
color: #FFF;
transition: all .3s;
@extend %unable-select;
&.d2-source--active {
opacity: 1;
}
&:hover {
right: - $borderRadius;
background-color: rgba(#000, .9);
}
}
</style>
@@ -1,6 +1,7 @@
import BScroll from 'better-scroll' import BScroll from 'better-scroll'
export default { export default {
props: { props: {
// 滚动优化的选项
betterScrollOptions: { betterScrollOptions: {
type: Object, type: Object,
required: false, required: false,
@@ -20,6 +21,7 @@ export default {
}, },
methods: { methods: {
scrollInit () { scrollInit () {
// 初始化 bs
this.BS = new BScroll(this.$refs.wrapper, Object.assign({ this.BS = new BScroll(this.$refs.wrapper, Object.assign({
mouseWheel: true, mouseWheel: true,
scrollbar: { scrollbar: {
@@ -27,6 +29,11 @@ export default {
interactive: false interactive: false
} }
}, this.betterScrollOptions)) }, this.betterScrollOptions))
// 滚动时发出事件 并且统一返回的数据格式
this.BS.on('scroll', ({ x, y }) => this.$emit('scroll', {
x: -x,
y: -y
}))
}, },
scrollDestroy () { scrollDestroy () {
// https://github.com/d2-projects/d2-admin/issues/75 // https://github.com/d2-projects/d2-admin/issues/75
@@ -36,6 +43,19 @@ export default {
delete this.BS delete this.BS
this.BS = null this.BS = null
} }
},
// 外部调用的方法 返回顶部
scrollToTop () {
if (this.BS) this.BS.scrollTo(0, 0, 300)
},
// 手动发出滚动事件
scroll () {
if (this.BS) {
this.$emit('scroll', {
x: -this.BS.x,
y: -this.BS.y
})
}
} }
} }
} }
@@ -0,0 +1,67 @@
// 提供滚动方面的功能
// 非滚动优化模式通用
import { throttle } from 'lodash'
// 生成滚动事件的 handler
function handleMaker (wait) {
return throttle(e => {
this.$emit('scroll', {
x: e.target.scrollLeft,
y: e.target.scrollTop
})
}, wait)
}
export default {
props: {
// 滚动事件节流间隔
scrollDelay: {
type: Number,
required: false,
default: 10
}
},
data () {
return {
handleScroll: null
}
},
watch: {
scrollDelay (val) {
// 移除旧的监听
this.removeScrollListener()
// 生成新的 handle 方法
this.handleScroll = handleMaker.call(this, val)
// 添加新的监听
this.addScrollListener()
}
},
methods: {
// 增加滚动事件监听
addScrollListener () {
if (typeof this.handleScroll !== 'function') {
// mounted 生命周期内调用这个方法的时候会进入这里的判断
this.handleScroll = handleMaker.call(this, this.scrollDelay)
}
// 添加监听
this.$refs.body.addEventListener('scroll', this.handleScroll)
},
// 移除滚动事件监听
removeScrollListener () {
this.$refs.body.removeEventListener('scroll', this.handleScroll)
},
// 外部调用的方法 返回顶部
scrollToTop () {
const smoothscroll = () => {
const body = this.$refs.body
const currentScroll = body.scrollTop
if (currentScroll > 0) {
window.requestAnimationFrame(smoothscroll)
body.scrollTo(0, currentScroll - (currentScroll / 5))
}
}
smoothscroll()
}
}
}
+111
View File
@@ -0,0 +1,111 @@
// 组件
import d2ContainerFull from './components/d2-container-full.vue'
import d2ContainerFullBs from './components/d2-container-full-bs.vue'
import d2ContainerGhost from './components/d2-container-ghost.vue'
import d2ContainerGhostBs from './components/d2-container-ghost-bs.vue'
import d2ContainerCard from './components/d2-container-card.vue'
import d2ContainerCardBs from './components/d2-container-card-bs.vue'
import d2Source from './components/d2-source.vue'
export default {
name: 'd2-container',
props: {
// 容器样式
type: {
type: String,
required: false,
default: 'full'
},
// 滚动优化
betterScroll: {
type: Boolean,
required: false,
default: false
}
},
computed: {
// 始终返回渲染组件
component () {
if (this.type === 'card' && !this.betterScroll) return d2ContainerCard
if (this.type === 'card' && this.betterScroll) return d2ContainerCardBs
if (this.type === 'ghost' && !this.betterScroll) return d2ContainerGhost
if (this.type === 'ghost' && this.betterScroll) return d2ContainerGhostBs
if (this.type === 'full' && !this.betterScroll) return d2ContainerFull
if (this.type === 'full' && this.betterScroll) return d2ContainerFullBs
else {
return 'div'
}
}
},
render (h) {
const slots = [
h('div', this.$slots.default)
]
if (this.$slots.header) slots.push(h('div', { slot: 'header' }, [ this.$slots.header ]))
if (this.$slots.footer) slots.push(h('div', { slot: 'footer' }, [ this.$slots.footer ]))
return h('div', {
ref: 'container',
class: 'container-component'
}, [
h(this.component, {
ref: 'component',
props: this.$attrs,
on: {
scroll: e => this.$emit('scroll', e)
}
}, slots),
// 只在预览和开发模式下显示源码按钮
process.env.VUE_APP_BUILD_MODE === 'TRAVIS' || process.env.NODE_ENV === 'development' ? h(d2Source, {
props: this.$attrs
}) : undefined
])
},
methods: {
// 返回顶部
scrollToTop () {
this.$refs.component.scrollToTop()
// 如果开启了 better scroll 还需要手动触发一遍 scroll 事件
const bs = this.$refs.component.BS
if (bs) this.$refs.component.scroll()
},
// 用法同原生方法 scrollBy
scrollBy (x = 0, y = 0, time = 300) {
if (this.betterScroll) {
const bs = this.$refs.component.BS
if (bs) {
bs.scrollBy(-x, -y, time)
// 手动触发一遍 scroll 事件
this.$refs.component.scroll()
}
} else {
this.$refs.component.$refs.body.scrollBy(x, y)
}
},
// 用法同原生方法 scrollTo
scrollTo (x = 0, y = 0, time = 300) {
if (this.betterScroll) {
const bs = this.$refs.component.BS
if (bs) {
bs.scrollTo(-x, -y, time)
// 手动触发一遍 scroll 事件
this.$refs.component.scroll()
}
} else {
this.$refs.component.$refs.body.scrollTo(x, y)
}
},
// 用法同原生方法 scrollTop
scrollTop (top = 0, time = 300) {
if (this.betterScroll) {
const bs = this.$refs.component.BS
if (bs) {
bs.scrollTo(bs.x, -top, time)
// 手动触发一遍 scroll 事件
this.$refs.component.scroll()
}
} else {
this.$refs.component.$refs.body.scrollTop = top
}
}
}
}
-75
View File
@@ -1,75 +0,0 @@
<template>
<div class="container-component" ref="container">
<!-- [card] 卡片容器 -->
<d2-container-card v-if="type === 'card' && !betterScroll">
<slot v-if="$slots.header" name="header" slot="header"/>
<slot/>
<slot v-if="$slots.footer" name="footer" slot="footer"/>
</d2-container-card>
<!-- [card] 卡片容器 滚动优化 -->
<d2-container-card-bs v-bind="$attrs" v-if="type === 'card' && betterScroll">
<slot v-if="$slots.header" name="header" slot="header"/>
<slot/>
<slot v-if="$slots.footer" name="footer" slot="footer"/>
</d2-container-card-bs>
<!-- [ghost] 隐形容器 -->
<d2-container-ghost v-if="type === 'ghost' && !betterScroll">
<slot v-if="$slots.header" name="header" slot="header"/>
<slot/>
<slot v-if="$slots.footer" name="footer" slot="footer"/>
</d2-container-ghost>
<!-- [ghost] 隐形容器 滚动优化 -->
<d2-container-ghost-bs v-bind="$attrs" v-if="type === 'ghost' && betterScroll">
<slot v-if="$slots.header" name="header" slot="header"/>
<slot/>
<slot v-if="$slots.footer" name="footer" slot="footer"/>
</d2-container-ghost-bs>
<!-- [container-full] 填充 -->
<d2-container-full v-if="type === 'full' && !betterScroll">
<slot v-if="$slots.header" name="header" slot="header"/>
<slot/>
<slot v-if="$slots.footer" name="footer" slot="footer"/>
</d2-container-full>
<!-- [container-full-bs] 填充 滚动优化 -->
<d2-container-full-bs v-bind="$attrs" v-if="type === 'full' && betterScroll">
<slot v-if="$slots.header" name="header" slot="header"/>
<slot/>
<slot v-if="$slots.footer" name="footer" slot="footer"/>
</d2-container-full-bs>
</div>
</template>
<script>
// 组件
import d2ContainerFull from './components/d2-container-full.vue'
import d2ContainerFullBs from './components/d2-container-full-bs.vue'
import d2ContainerGhost from './components/d2-container-ghost.vue'
import d2ContainerGhostBs from './components/d2-container-ghost-bs.vue'
import d2ContainerCard from './components/d2-container-card.vue'
import d2ContainerCardBs from './components/d2-container-card-bs.vue'
export default {
name: 'd2-container',
components: {
'd2-container-full': d2ContainerFull,
'd2-container-full-bs': d2ContainerFullBs,
'd2-container-ghost': d2ContainerGhost,
'd2-container-ghost-bs': d2ContainerGhostBs,
'd2-container-card': d2ContainerCard,
'd2-container-card-bs': d2ContainerCardBs
},
props: {
// 容器样式
type: {
type: String,
required: false,
default: 'full'
},
// 滚动优化
betterScroll: {
type: Boolean,
required: false,
default: false
}
}
}
</script>
+15 -21
View File
@@ -3,8 +3,9 @@
<div class="d2-page-cover__logo"> <div class="d2-page-cover__logo">
<slot/> <slot/>
</div> </div>
<p class="d2-page-cover__title">{{title}}</p> <p class="d2-page-cover__title">D2 Admin {{$version}}</p>
<p class="d2-page-cover__sub-title d2-mt-0">{{subTitle}}</p> <p class="d2-page-cover__sub-title">优雅的中后台集成方案</p>
<p class="d2-page-cover__build-time">FINAL BUILD TIME {{$buildTime}}</p>
<slot name="footer"/> <slot name="footer"/>
<a target="blank" href="https://github.com/d2-projects/d2-admin"> <a target="blank" href="https://github.com/d2-projects/d2-admin">
<img <img
@@ -15,25 +16,7 @@
</div> </div>
</template> </template>
<script>
export default {
props: {
title: {
type: String,
required: false,
default: 'Title'
},
subTitle: {
type: String,
required: false,
default: 'subTitle'
}
}
}
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '~@/assets/style/public.scss';
.d2-page-cover { .d2-page-cover {
@extend %full; @extend %full;
@extend %unable-select; @extend %unable-select;
@@ -47,10 +30,21 @@ export default {
} }
} }
.d2-page-cover__title { .d2-page-cover__title {
margin: 0px;
margin-bottom: 20px;
font-weight: bold;
color: $color-text-main; color: $color-text-main;
} }
.d2-page-cover__sub-title { .d2-page-cover__sub-title {
color: $color-text-sub; margin: 0px;
margin-bottom: 5px;
color: $color-text-normal;
}
.d2-page-cover__build-time {
margin: 0px;
margin-bottom: 10px;
font-size: 12px;
color: $color-text-placehoder;
} }
} }
</style> </style>
@@ -1,8 +1,8 @@
<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>
<d2-icon-svg v-else-if="menu.iconSvg" :name="menu.iconSvg"/> <i v-if="menu.icon === undefined & !menu.iconSvg" class="fa fa-file-o"></i>
<i v-else class="fa fa-file-o"></i> <d2-icon-svg v-if="menu.iconSvg" :name="menu.iconSvg"/>
<span slot="title">{{menu.title || '未命名菜单'}}</span> <span slot="title">{{menu.title || '未命名菜单'}}</span>
</el-menu-item> </el-menu-item>
</template> </template>
@@ -2,8 +2,8 @@
<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>
<d2-icon-svg v-else-if="menu.iconSvg" :name="menu.iconSvg"/> <i v-if="menu.icon === undefined & !menu.iconSvg" class="fa fa-folder-o"></i>
<i v-else class="fa fa-folder-o"></i> <d2-icon-svg v-if="menu.iconSvg" :name="menu.iconSvg"/>
<span slot="title">{{menu.title}}</span> <span slot="title">{{menu.title}}</span>
</template> </template>
<template v-for="(child, childIndex) in menu.children"> <template v-for="(child, childIndex) in menu.children">
@@ -165,7 +165,6 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
@import '~@/assets/style/public.scss';
.d2-error-log-list__expand-group { .d2-error-log-list__expand-group {
.d2-error-log-list__expand { .d2-error-log-list__expand {
padding-left: 20px; padding-left: 20px;
@@ -69,7 +69,6 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '~@/assets/style/public.scss';
.theme-preview { .theme-preview {
height: 50px; height: 50px;
width: 100px; width: 100px;
@@ -55,12 +55,12 @@ export default {
}, 500) }, 500)
}, },
// 监听路由 控制侧边栏激活状态 // 监听路由 控制侧边栏激活状态
'$route.matched': { '$route': {
handler (val) { handler ({ fullPath }) {
this.active = val[val.length - 1].path this.active = fullPath
this.$nextTick(() => { this.$nextTick(() => {
if (this.aside.length > 0 && this.$refs.menu) { if (this.aside.length > 0 && this.$refs.menu) {
this.$refs.menu.activeIndex = this.active this.$refs.menu.activeIndex = fullPath
} }
}) })
}, },
@@ -35,7 +35,6 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '~@/assets/style/public.scss';
.d2-panel-search-item { .d2-panel-search-item {
height: 64px; height: 64px;
margin: 0px -20px; margin: 0px -20px;
@@ -183,7 +183,6 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '~@/assets/style/public.scss';
.panel-search { .panel-search {
margin: 20px; margin: 20px;
.panel-search__input-group { .panel-search__input-group {
@@ -7,7 +7,7 @@
:x="contentmenuX" :x="contentmenuX"
:y="contentmenuY"> :y="contentmenuY">
<d2-contextmenu-list <d2-contextmenu-list
:menulist="tagName === 'index' ? contextmenuListIndex : contextmenuList" :menulist="tagName === '/index' ? contextmenuListIndex : contextmenuList"
@rowClick="contextmenuClick"/> @rowClick="contextmenuClick"/>
</d2-contextmenu> </d2-contextmenu>
<el-tabs <el-tabs
@@ -19,10 +19,10 @@
@edit="handleTabsEdit" @edit="handleTabsEdit"
@contextmenu.native="handleContextmenu"> @contextmenu.native="handleContextmenu">
<el-tab-pane <el-tab-pane
v-for="(page, index) in opened" v-for="page in opened"
:key="index" :key="page.fullPath"
:label="page.meta.title || '未命名'" :label="page.meta.title || '未命名'"
:name="page.name"/> :name="page.fullPath"/>
</el-tabs> </el-tabs>
</div> </div>
</div> </div>
@@ -79,7 +79,7 @@ export default {
{ icon: 'times', title: '关闭其它', value: 'other' }, { icon: 'times', title: '关闭其它', value: 'other' },
{ icon: 'times-circle', title: '关闭全部', value: 'all' } { icon: 'times-circle', title: '关闭全部', value: 'all' }
], ],
tagName: 'index' tagName: '/index'
} }
}, },
computed: { computed: {
@@ -163,7 +163,7 @@ export default {
*/ */
handleClick (tab, event) { handleClick (tab, event) {
// 找到点击的页面在 tag 列表里是哪个 // 找到点击的页面在 tag 列表里是哪个
const page = this.opened.find(page => page.name === tab.name) const page = this.opened.find(page => page.fullPath === tab.name)
const { name, params, query } = page const { name, params, query } = page
if (page) { if (page) {
this.$router.push({ name, params, query }) this.$router.push({ name, params, query })
+18 -9
View File
@@ -81,6 +81,15 @@
</template> </template>
<script> <script>
import d2MenuSide from './components/menu-side'
import d2MenuHeader from './components/menu-header'
import d2Tabs from './components/tabs'
import d2HeaderFullscreen from './components/header-fullscreen'
import d2HeaderSearch from './components/header-search'
import d2HeaderSize from './components/header-size'
import d2HeaderTheme from './components/header-theme'
import d2HeaderUser from './components/header-user'
import d2HeaderErrorLog from './components/header-error-log'
import { mapState, mapGetters, mapActions } from 'vuex' import { mapState, mapGetters, mapActions } from 'vuex'
import mixinSearch from './mixins/search' import mixinSearch from './mixins/search'
export default { export default {
@@ -89,15 +98,15 @@ export default {
mixinSearch mixinSearch
], ],
components: { components: {
'd2-menu-side': () => import('./components/menu-side'), d2MenuSide,
'd2-menu-header': () => import('./components/menu-header'), d2MenuHeader,
'd2-tabs': () => import('./components/tabs'), d2Tabs,
'd2-header-fullscreen': () => import('./components/header-fullscreen'), d2HeaderFullscreen,
'd2-header-search': () => import('./components/header-search'), d2HeaderSearch,
'd2-header-size': () => import('./components/header-size'), d2HeaderSize,
'd2-header-theme': () => import('./components/header-theme'), d2HeaderTheme,
'd2-header-user': () => import('./components/header-user'), d2HeaderUser,
'd2-header-error-log': () => import('./components/header-error-log') d2HeaderErrorLog
}, },
data () { data () {
return { return {
+3 -4
View File
@@ -1,5 +1,4 @@
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import setting from '@/setting.js'
const cookies = {} const cookies = {}
@@ -14,7 +13,7 @@ cookies.set = function (name = 'default', value = '', cookieSetting = {}) {
expires: 1 expires: 1
} }
Object.assign(currentCookieSetting, cookieSetting) Object.assign(currentCookieSetting, cookieSetting)
Cookies.set(`d2admin-${setting.releases.version}-${name}`, value, currentCookieSetting) Cookies.set(`d2admin-${process.env.VUE_APP_VERSION}-${name}`, value, currentCookieSetting)
} }
/** /**
@@ -22,7 +21,7 @@ cookies.set = function (name = 'default', value = '', cookieSetting = {}) {
* @param {String} name cookie name * @param {String} name cookie name
*/ */
cookies.get = function (name = 'default') { cookies.get = function (name = 'default') {
return Cookies.get(`d2admin-${setting.releases.version}-${name}`) return Cookies.get(`d2admin-${process.env.VUE_APP_VERSION}-${name}`)
} }
/** /**
@@ -37,7 +36,7 @@ cookies.getAll = function () {
* @param {String} name cookie name * @param {String} name cookie name
*/ */
cookies.remove = function (name = 'default') { cookies.remove = function (name = 'default') {
return Cookies.remove(`d2admin-${setting.releases.version}-${name}`) return Cookies.remove(`d2admin-${process.env.VUE_APP_VERSION}-${name}`)
} }
export default cookies export default cookies
+1 -2
View File
@@ -1,8 +1,7 @@
import low from 'lowdb' import low from 'lowdb'
import LocalStorage from 'lowdb/adapters/LocalStorage' import LocalStorage from 'lowdb/adapters/LocalStorage'
import setting from '@/setting.js'
const adapter = new LocalStorage(`d2admin-${setting.releases.version}`) const adapter = new LocalStorage(`d2admin-${process.env.VUE_APP_VERSION}`)
const db = low(adapter) const db = low(adapter)
db db
+7 -5
View File
@@ -1,8 +1,10 @@
import log from './util.log.js' import cookies from './util.cookies'
import cookies from './util.cookies.js' import db from './util.db'
import log from './util.log'
let util = { const util = {
cookies, cookies,
db,
log log
} }
@@ -23,10 +25,10 @@ util.open = function (url) {
var a = document.createElement('a') var a = document.createElement('a')
a.setAttribute('href', url) a.setAttribute('href', url)
a.setAttribute('target', '_blank') a.setAttribute('target', '_blank')
a.setAttribute('id', 'd2admin-menu-link') a.setAttribute('id', 'd2admin-link-temp')
document.body.appendChild(a) document.body.appendChild(a)
a.click() a.click()
document.body.removeChild(document.getElementById('d2admin-menu-link')) document.body.removeChild(document.getElementById('d2admin-link-temp'))
} }
export default util export default util
-2
View File
@@ -5,8 +5,6 @@ import Vue from 'vue'
import App from './App' import App from './App'
// store // store
import store from '@/store/index' import store from '@/store/index'
// 模拟数据
import '@/mock'
// 多国语 // 多国语
import i18n from './i18n' import i18n from './i18n'
// 核心插件 // 核心插件
+31
View File
@@ -0,0 +1,31 @@
const userDB = [
{ username: 'admin', password: 'admin', uuid: 'admin-uuid', name: '管理员' },
{ username: 'editor', password: 'editor', uuid: 'editor-uuid', name: '编辑' },
{ username: 'user1', password: 'user1', uuid: 'user1-uuid', name: '用户1' }
]
export default [
{
path: '/api/login',
method: 'post',
handle ({ body }) {
const user = userDB.find(e => e.username === body.username && e.password === body.password)
if (user) {
return {
code: 0,
msg: '登录成功',
data: {
...user,
token: '8dfhassad0asdjwoeiruty'
}
}
} else {
return {
code: 401,
msg: '用户名或密码错误',
data: {}
}
}
}
}
]
-43
View File
@@ -1,43 +0,0 @@
import Mock from 'mockjs'
const userDB = [
{
username: 'admin',
password: 'admin',
uuid: 'admin-uuid',
name: '管理员'
},
{
username: 'editor',
password: 'editor',
uuid: 'editor-uuid',
name: '编辑'
},
{
username: 'user1',
password: 'user1',
uuid: 'user1-uuid',
name: '用户1'
}
]
Mock.mock('/api/login', 'post', ({ url, type, body }) => {
const bodyObj = JSON.parse(body)
const user = userDB.find(e => e.username === bodyObj.username && e.password === bodyObj.password)
if (user) {
return {
code: 0,
msg: '登录成功',
data: {
...user,
token: 'd787syv8dys8cas80d9s0a0d8f79ads56f7s4d56f879a8as89fd980s7dg'
}
}
} else {
return {
code: 401,
msg: '用户名或密码错误',
data: {}
}
}
})
+73
View File
@@ -0,0 +1,73 @@
import Mock from 'mockjs'
import qs from 'qs'
import withCredentials from './patch/withCredentials'
/* 补丁 */
withCredentials(Mock)
/* Mock 默认配置 */
Mock.setup({ timeout: '200-300' })
/* 扩展 [生成器] */
const Generator = (prop, template) => {
const obj = {}
obj[prop] = [template]
return Mock.mock(obj)
}
/* 扩展 [循环] */
const Repeat = (num, itemTemplate) => Generator(`data|${num}`, itemTemplate).data
const CustomExtends = {
Generator,
Repeat,
Mock,
Random: Mock.Random
}
const extend = (prop, value) => {
CustomExtends[prop] = value
}
/* 装配配置组 */
const wired = ({ url, type, body }) => ({
method: type,
params: qs.parse(url.split('?').length > 1 ? url.split('?')[1] : ''),
body: JSON.parse(body),
url: qs.parse(url.split('?')[0]),
...CustomExtends
})
const setup = (path, method, handle) => {
Mock.mock(
RegExp(path),
method,
typeof handle === 'function' ? o => handle(wired(o)) : handle
)
}
const load = (collection) => {
collection.map(({ path, method, handle }) => {
if (method === '*') {
method = [
'get',
'head',
'post',
'put',
'delete',
'connect',
'options',
'trace',
'patch'
]
}
if (typeof method === 'string' && method.indexOf('|') > -1) method = method.split('|')
if (method instanceof Array) {
method.map(item => setup(path, item, handle))
} else {
setup(path, method, handle)
}
})
}
export default { setup, load, extend }
@@ -0,0 +1,8 @@
export default function (Mock) {
// http://cnine.me/note/FrontEnd/mock-lose-cookies-dbg.html
Mock.XHR.prototype.__send = Mock.XHR.prototype.send
Mock.XHR.prototype.send = function () {
if (this.custom.xhr) this.custom.xhr.withCredentials = this.withCredentials || false
this.__send.apply(this, arguments)
}
}
+6 -11
View File
@@ -1,15 +1,10 @@
import Mock from 'mockjs' import d2Mock from './d2-mock'
// 补丁 解决 mock.js 影响 Cookie 携带
import PatchCookie from './patch/cookie'
PatchCookie(Mock)
// 导入所有的接口
const req = context => context.keys().map(context) const req = context => context.keys().map(context)
req(require.context('./api/', true, /\.js$/)) const options = req(require.context('./api/', true, /\.js$/))
.filter(e => e.default)
.map(e => e.default)
// 设置全局延时 没有延时的话有时候会检测不到数据变化 建议保留 options.forEach(option => {
Mock.setup({ d2Mock.load(option)
timeout: '300-600'
}) })
-10
View File
@@ -1,10 +0,0 @@
export default function (Mock) {
// 解决 Mock 情况下,携带 withCredentials = true,且未被拦截的跨域请求丢失 Cookies 的问题
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
Mock.XHR.prototype.send = function () {
if (this.custom.xhr) {
this.custom.xhr.withCredentials = this.withCredentials || false
}
this.proxy_send(...arguments)
}
}
+7 -3
View File
@@ -1,13 +1,17 @@
<template> <template>
<d2-container> <d2-container :filename="filename">
<template slot="header">Page 1 header</template> <template slot="header">Page 1 header</template>
Hello World Hello World
<template slot="footer">footer</template>
</d2-container> </d2-container>
</template> </template>
<script> <script>
export default { export default {
name: 'page1' name: 'page1',
data () {
return {
filename: __filename
}
}
} }
</script> </script>
+7 -3
View File
@@ -1,13 +1,17 @@
<template> <template>
<d2-container> <d2-container :filename="filename">
<template slot="header">Page 2 header</template> <template slot="header">Page 2 header</template>
Hello World Hello World
<template slot="footer">footer</template>
</d2-container> </d2-container>
</template> </template>
<script> <script>
export default { export default {
name: 'page2' name: 'page2',
data () {
return {
filename: __filename
}
}
} }
</script> </script>
+7 -3
View File
@@ -1,13 +1,17 @@
<template> <template>
<d2-container> <d2-container :filename="filename">
<template slot="header">Page 3 header</template> <template slot="header">Page 3 header</template>
Hello World Hello World
<template slot="footer">footer</template>
</d2-container> </d2-container>
</template> </template>
<script> <script>
export default { export default {
name: 'page3' name: 'page3',
data () {
return {
filename: __filename
}
}
} }
</script> </script>
-1
View File
@@ -8,7 +8,6 @@
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '~@/assets/style/public.scss';
.page { .page {
background: #303133; background: #303133;
background-blend-mode: multiply,multiply; background-blend-mode: multiply,multiply;
+7 -11
View File
@@ -1,8 +1,6 @@
<template> <template>
<d2-container class="page"> <d2-container :filename="filename" class="page">
<d2-page-cover <d2-page-cover>
:title="`D2 Admin ${version}`"
sub-title="优雅的中后台集成方案">
<d2-icon-svg <d2-icon-svg
class="page__logo" class="page__logo"
name="d2-admin"/> name="d2-admin"/>
@@ -35,7 +33,6 @@
</template> </template>
<script> <script>
import { mapState } from 'vuex'
import D2HelpBtn from './components/d2-help-btn' import D2HelpBtn from './components/d2-help-btn'
import D2Badge from './components/d2-badge' import D2Badge from './components/d2-badge'
export default { export default {
@@ -43,16 +40,15 @@ export default {
D2HelpBtn, D2HelpBtn,
D2Badge D2Badge
}, },
computed: { data () {
...mapState('d2admin/releases', [ return {
'version' filename: __filename
]) }
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '~@/assets/style/public.scss';
.page { .page {
.page__logo { .page__logo {
width: 120px; width: 120px;
@@ -60,7 +56,7 @@ 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-top: 0px;
margin-bottom: 20px; margin-bottom: 20px;
span { span {
color: $color-text-sub; color: $color-text-sub;
-2
View File
@@ -1,2 +0,0 @@
// 例子插件配置地址
// https://vincentgarreau.com/particles.js/#default
-112
View File
@@ -1,112 +0,0 @@
/* eslint-disable */
export default {
"particles": {
"number": {
"value": 6,
"density": {
"enable": true,
"value_area": 800
}
},
"color": {
"value": "#444"
},
"shape": {
"type": "polygon",
"stroke": {
"width": 0,
"color": "#000"
},
"polygon": {
"nb_sides": 6
},
"image": {
"src": "img/github.svg",
"width": 100,
"height": 100
}
},
"opacity": {
"value": 0.3,
"random": true,
"anim": {
"enable": false,
"speed": 1,
"opacity_min": 0.1,
"sync": false
}
},
"size": {
"value": 160,
"random": false,
"anim": {
"enable": true,
"speed": 10,
"size_min": 40,
"sync": false
}
},
"line_linked": {
"enable": false,
"distance": 200,
"color": "#ffffff",
"opacity": 1,
"width": 2
},
"move": {
"enable": true,
"speed": 8,
"direction": "none",
"random": false,
"straight": false,
"out_mode": "out",
"bounce": false,
"attract": {
"enable": false,
"rotateX": 600,
"rotateY": 1200
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": false,
"mode": "grab"
},
"onclick": {
"enable": false,
"mode": "push"
},
"resize": true
},
"modes": {
"grab": {
"distance": 400,
"line_linked": {
"opacity": 1
}
},
"bubble": {
"distance": 400,
"size": 40,
"duration": 2,
"opacity": 8,
"speed": 3
},
"repulse": {
"distance": 200,
"duration": 0.4
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true
}
-112
View File
@@ -1,112 +0,0 @@
/* eslint-disable */
export default {
"particles": {
"number": {
"value": 80,
"density": {
"enable": true,
"value_area": 800
}
},
"color": {
"value": "#DCDFE6"
},
"shape": {
"type": "circle",
"stroke": {
"width": 0,
"color": "#000000"
},
"polygon": {
"nb_sides": 5
},
"image": {
"src": "img/github.svg",
"width": 100,
"height": 100
}
},
"opacity": {
"value": 1,
"random": false,
"anim": {
"enable": false,
"speed": 1,
"opacity_min": 0.1,
"sync": false
}
},
"size": {
"value": 4,
"random": true,
"anim": {
"enable": false,
"speed": 40,
"size_min": 0.1,
"sync": false
}
},
"line_linked": {
"enable": true,
"distance": 150,
"color": "#DCDFE6",
"opacity": 0.4,
"width": 2
},
"move": {
"enable": true,
"speed": 6,
"direction": "none",
"random": false,
"straight": false,
"out_mode": "out",
"bounce": false,
"attract": {
"enable": false,
"rotateX": 600,
"rotateY": 1200
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": true,
"mode": "repulse"
},
"onclick": {
"enable": true,
"mode": "push"
},
"resize": true
},
"modes": {
"grab": {
"distance": 400,
"line_linked": {
"opacity": 1
}
},
"bubble": {
"distance": 400,
"size": 40,
"duration": 2,
"opacity": 8,
"speed": 3
},
"repulse": {
"distance": 200,
"duration": 0.4
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true
}
-112
View File
@@ -1,112 +0,0 @@
/* eslint-disable */
export default {
"particles": {
"number": {
"value": 160,
"density": {
"enable": true,
"value_area": 800
}
},
"color": {
"value": "#ffffff"
},
"shape": {
"type": "circle",
"stroke": {
"width": 0,
"color": "#000000"
},
"polygon": {
"nb_sides": 5
},
"image": {
"src": "img/github.svg",
"width": 100,
"height": 100
}
},
"opacity": {
"value": 1,
"random": true,
"anim": {
"enable": true,
"speed": 1,
"opacity_min": 0,
"sync": false
}
},
"size": {
"value": 3,
"random": true,
"anim": {
"enable": false,
"speed": 4,
"size_min": 0.3,
"sync": false
}
},
"line_linked": {
"enable": false,
"distance": 150,
"color": "#ffffff",
"opacity": 0.4,
"width": 1
},
"move": {
"enable": true,
"speed": 1,
"direction": "none",
"random": true,
"straight": false,
"out_mode": "out",
"bounce": false,
"attract": {
"enable": false,
"rotateX": 600,
"rotateY": 600
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": true,
"mode": "bubble"
},
"onclick": {
"enable": true,
"mode": "repulse"
},
"resize": true
},
"modes": {
"grab": {
"distance": 400,
"line_linked": {
"opacity": 1
}
},
"bubble": {
"distance": 250,
"size": 0,
"duration": 2,
"opacity": 0,
"speed": 3
},
"repulse": {
"distance": 400,
"duration": 0.4
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true
}
-112
View File
@@ -1,112 +0,0 @@
/* eslint-disable */
export default {
"particles": {
"number": {
"value": 400,
"density": {
"enable": true,
"value_area": 800
}
},
"color": {
"value": "#fff"
},
"shape": {
"type": "circle",
"stroke": {
"width": 0,
"color": "#000000"
},
"polygon": {
"nb_sides": 5
},
"image": {
"src": "img/github.svg",
"width": 100,
"height": 100
}
},
"opacity": {
"value": 0.5,
"random": true,
"anim": {
"enable": false,
"speed": 1,
"opacity_min": 0.1,
"sync": false
}
},
"size": {
"value": 10,
"random": true,
"anim": {
"enable": false,
"speed": 40,
"size_min": 0.1,
"sync": false
}
},
"line_linked": {
"enable": false,
"distance": 500,
"color": "#ffffff",
"opacity": 0.4,
"width": 2
},
"move": {
"enable": true,
"speed": 6,
"direction": "bottom",
"random": false,
"straight": false,
"out_mode": "out",
"bounce": false,
"attract": {
"enable": false,
"rotateX": 600,
"rotateY": 1200
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": true,
"mode": "bubble"
},
"onclick": {
"enable": true,
"mode": "repulse"
},
"resize": true
},
"modes": {
"grab": {
"distance": 400,
"line_linked": {
"opacity": 0.5
}
},
"bubble": {
"distance": 400,
"size": 4,
"duration": 0.3,
"opacity": 1,
"speed": 3
},
"repulse": {
"distance": 200,
"duration": 0.4
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+314 -50
View File
@@ -1,41 +1,77 @@
<template> <template>
<div class="login-page"> <div class="page-login">
<div class="layer bg" id="login"></div> <div class="page-login--layer page-login--layer-area">
<div class="layer flex-center"> <ul class="circles">
<!-- logo部分 --> <li v-for="n in 10" :key="n"></li>
<div class="logo-group"> </ul>
<img src="./image/logo.png" alt="logo"> </div>
<div
class="page-login--layer page-login--layer-time"
flex="main:center cross:center">
{{time}}
</div>
<div class="page-login--layer">
<div
class="page-login--content"
flex="dir:top main:justify cross:center box:justify">
<div class="page-login--content-header">
<p class="page-login--content-header-motto">
时间是一切财富中最宝贵的财富。 <span>—— 德奥弗拉斯多</span>
</p>
</div>
<div
class="page-login--content-main"
flex="dir:top main:center cross:center">
<!-- logo -->
<img class="page-login--logo" src="./image/logo@2x.png">
<!-- 表单 -->
<div class="page-login--form">
<el-card shadow="never">
<el-form ref="loginForm" label-position="top" :rules="rules" :model="formLogin" size="default">
<el-form-item prop="username">
<el-input type="text" v-model="formLogin.username" placeholder="用户名">
<i slot="prepend" class="fa fa-user-circle-o"></i>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" v-model="formLogin.password" placeholder="密码">
<i slot="prepend" class="fa fa-keyboard-o"></i>
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input type="text" v-model="formLogin.code" placeholder="- - - -">
<template slot="prepend">验证码</template>
<template slot="append">
<img class="login-code" src="./image/login-code.png">
</template>
</el-input>
</el-form-item>
<el-button size="default" @click="submit" type="primary" class="button-login">登录</el-button>
</el-form>
</el-card>
<p
class="page-login--options"
flex="main:justify cross:center">
<span><d2-icon name="question-circle"/> 忘记密码</span>
<span>注册用户</span>
</p>
<!-- 快速登录按钮 -->
<el-button class="page-login--quick" size="default" type="info" @click="dialogVisible = true">
快速选择用户(测试功能)
</el-button>
</div>
</div>
<div class="page-login--content-footer">
<p class="page-login--content-footer-options">
<a href="#">帮助</a>
<a href="#">隐私</a>
<a href="#">条款</a>
</p>
<p class="page-login--content-footer-copyright">
Copyright <d2-icon name="copyright"/> 2018 D2 Projects 开源组织出品 <a href="https://github.com/FairyEver">@FairyEver</a>
</p>
</div>
</div> </div>
<!-- 表单部分 -->
<div class="form-group">
<el-card>
<el-form ref="loginForm" label-position="top" :rules="rules" :model="formLogin" size="default">
<el-form-item prop="username">
<el-input type="text" v-model="formLogin.username" placeholder="用户名">
<i slot="prepend" class="fa fa-user-circle-o"></i>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" v-model="formLogin.password" placeholder="密码">
<i slot="prepend" class="fa fa-keyboard-o"></i>
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input type="text" v-model="formLogin.code" placeholder="- - - -">
<template slot="prepend">验证码</template>
<template slot="append">
<img class="login-code" src="./image/login-code.png">
</template>
</el-input>
</el-form-item>
<el-button size="default" @click="submit" type="primary" class="button-login">登录</el-button>
</el-form>
</el-card>
</div>
<!-- 快速登录按钮 -->
<el-button size="default" type="info" class="button-help" @click="dialogVisible = true">
快速选择用户(测试功能)
</el-button>
</div> </div>
<el-dialog <el-dialog
title="快速选择用户" title="快速选择用户"
@@ -43,7 +79,7 @@
width="400px"> width="400px">
<el-row :gutter="10" style="margin: -20px 0px -10px 0px;"> <el-row :gutter="10" style="margin: -20px 0px -10px 0px;">
<el-col v-for="(user, index) in users" :key="index" :span="8"> <el-col v-for="(user, index) in users" :key="index" :span="8">
<div class="user-btn" @click="handleUserBtnClick(user)"> <div class="page-login--quick-user" @click="handleUserBtnClick(user)">
<d2-icon name="user-circle-o"/> <d2-icon name="user-circle-o"/>
<span>{{user.name}}</span> <span>{{user.name}}</span>
</div> </div>
@@ -54,13 +90,13 @@
</template> </template>
<script> <script>
/* eslint-disable */ import dayjs from 'dayjs'
require('particles.js')
import config from './config/default'
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
export default { export default {
data () { data () {
return { return {
timeInterval: null,
time: dayjs().format('HH:mm:ss'),
// 快速选择用户 // 快速选择用户
dialogVisible: false, dialogVisible: false,
users: [ users: [
@@ -101,22 +137,20 @@ export default {
} }
}, },
mounted () { mounted () {
// 初始化例子插件 this.timeInterval = setInterval(() => {
particlesJS('login', config) this.refreshTime()
}, 1000)
}, },
beforeDestroy () { beforeDestroy () {
// 销毁 particlesJS clearInterval(this.timeInterval)
// thanks https://github.com/d2-projects/d2-admin/issues/65
// ref https://github.com/VincentGarreau/particles.js/issues/63
if (pJSDom && pJSDom.length > 0) {
pJSDom[0].pJS.fn.vendors.destroypJS()
pJSDom = []
}
}, },
methods: { methods: {
...mapActions('d2admin/account', [ ...mapActions('d2admin/account', [
'login' 'login'
]), ]),
refreshTime () {
this.time = dayjs().format('HH:mm:ss')
},
/** /**
* @description 接收选择一个用户快速登录的事件 * @description 接收选择一个用户快速登录的事件
* @param {Object} user 用户信息 * @param {Object} user 用户信息
@@ -141,6 +175,10 @@ export default {
username: this.formLogin.username, username: this.formLogin.username,
password: this.formLogin.password password: this.formLogin.password
}) })
.then(() => {
// 重定向对象不存在则返回顶层路径
this.$router.replace(this.$route.query.redirect || '/')
})
} else { } else {
// 登录表单校验失败 // 登录表单校验失败
this.$message.error('表单校验失败') this.$message.error('表单校验失败')
@@ -152,5 +190,231 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
@import './style.scss'; .page-login {
@extend %unable-select;
$backgroundColor: #F0F2F5;
// ---
background-color: $backgroundColor;
height: 100%;
position: relative;
// 层
.page-login--layer {
@extend %full;
overflow: auto;
}
.page-login--layer-area {
overflow: hidden;
}
// 时间
.page-login--layer-time {
font-size: 24em;
font-weight: bold;
color: rgba(0, 0, 0, 0.03);
overflow: hidden;
}
// 登陆页面控件的容器
.page-login--content {
height: 100%;
min-height: 500px;
}
// header
.page-login--content-header {
padding: 1em 0;
.page-login--content-header-motto {
margin: 0px;
padding: 0px;
color: $color-text-normal;
text-align: center;
font-size: 12px;
span {
color: $color-text-sub;
}
}
}
// main
.page-login--logo {
width: 240px;
margin-bottom: 2em;
margin-top: -2em;
}
// 登录表单
.page-login--form {
width: 280px;
// 卡片
.el-card {
margin-bottom: 15px;
}
// 登录按钮
.button-login {
width: 100%;
}
// 输入框左边的图表区域缩窄
.el-input-group__prepend {
padding: 0px 14px;
}
.login-code {
height: 40px - 2px;
display: block;
margin: 0px -20px;
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
// 登陆选项
.page-login--options {
margin: 0px;
padding: 0px;
font-size: 14px;
color: $color-primary;
margin-bottom: 15px;
font-weight: bold;
}
.page-login--quick {
width: 100%;
}
}
// 快速选择用户面板
.page-login--quick-user {
@extend %flex-center-col;
padding: 10px 0px;
border-radius: 4px;
&:hover {
background-color: $color-bg;
i {
color: $color-text-normal;
}
span {
color: $color-text-normal;
}
}
i {
font-size: 36px;
color: $color-text-sub;
}
span {
font-size: 12px;
margin-top: 10px;
color: $color-text-sub;
}
}
// footer
.page-login--content-footer {
padding: 1em 0;
.page-login--content-footer-options {
padding: 0px;
margin: 0px;
margin-bottom: 10px;
font-size: 14px;
text-align: center;
a {
color: $color-text-normal;
margin: 0 1em;
}
}
.page-login--content-footer-copyright {
padding: 0px;
margin: 0px;
font-size: 12px;
color: $color-text-normal;
a {
color: $color-text-normal;
}
}
}
// 背景
.circles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
li {
position: absolute;
display: block;
list-style: none;
width: 20px;
height: 20px;
background: #FFF;
animation: animate 25s linear infinite;
bottom: -200px;
@keyframes animate {
0%{
transform: translateY(0) rotate(0deg);
opacity: 1;
border-radius: 0;
}
100%{
transform: translateY(-1000px) rotate(720deg);
opacity: 0;
border-radius: 50%;
}
}
&:nth-child(1) {
left: 15%;
width: 80px;
height: 80px;
animation-delay: 0s;
}
&:nth-child(2) {
left: 5%;
width: 20px;
height: 20px;
animation-delay: 2s;
animation-duration: 12s;
}
&:nth-child(3) {
left: 70%;
width: 20px;
height: 20px;
animation-delay: 4s;
}
&:nth-child(4) {
left: 40%;
width: 60px;
height: 60px;
animation-delay: 0s;
animation-duration: 18s;
}
&:nth-child(5) {
left: 65%;
width: 20px;
height: 20px;
animation-delay: 0s;
}
&:nth-child(6) {
left: 75%;
width: 150px;
height: 150px;
animation-delay: 3s;
}
&:nth-child(7) {
left: 35%;
width: 200px;
height: 200px;
animation-delay: 7s;
}
&:nth-child(8) {
left: 50%;
width: 25px;
height: 25px;
animation-delay: 15s;
animation-duration: 45s;
}
&:nth-child(9) {
left: 20%;
width: 15px;
height: 15px;
animation-delay: 2s;
animation-duration: 35s;
}
&:nth-child(10) {
left: 85%;
width: 150px;
height: 150px;
animation-delay: 0s;
animation-duration: 11s;
}
}
}
}
</style> </style>
-90
View File
@@ -1,90 +0,0 @@
@import '~@/assets/style/public.scss';
.login-page {
background-color: #EDF4FA;
height: 100%;
position: relative;
// 层
.layer {
position: absolute;
height: 100%;
width: 100%;
&.flex-center {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
}
// 背景
.bg {
canvas {
display: block;
margin: 0px;
padding: 0px;
}
}
// logo
.logo-group {
margin-top: -75px - 70px;
position: relative;
top: 75px;
img {
height: 140px;
}
}
// 登录表单
.form-group {
width: 300px;
// 重新设置卡片阴影
.el-card {
box-shadow: 0 0 8px 0 rgba(232,237,250,.6), 0 2px 4px 0 rgba(232,237,250,.5);
.el-card__body {
padding-top: 70px;
}
}
// 登录按钮
.button-login {
width: 100%;
}
// 输入框左边的图表区域缩窄
.el-input-group__prepend {
padding: 0px 14px;
}
.login-code {
height: 40px - 2px;
display: block;
margin: 0px -20px;
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
}
// 帮助按钮
.button-help {
width: 300px;
margin-top: 20px;
}
.user-btn {
@extend %flex-center-col;
@extend %unable-select;
padding: 10px 0px;
border-radius: 4px;
&:hover {
background-color: $color-bg;
i {
color: $color-text-normal;
}
span {
color: $color-text-normal;
}
}
i {
font-size: 36px;
color: $color-text-sub;
}
span {
font-size: 12px;
margin-top: 10px;
color: $color-text-sub;
}
}
}
+3 -7
View File
@@ -41,13 +41,9 @@ const service = axios.create({
service.interceptors.request.use( service.interceptors.request.use(
config => { config => {
// 在请求发送之前做一些处理 // 在请求发送之前做一些处理
if (!(/^https:\/\/|http:\/\//.test(config.url))) { const token = util.cookies.get('token')
const token = util.cookies.get('token') // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
if (token && token !== 'undefined') { config.headers['X-Token'] = token
// 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
config.headers['X-Token'] = token
}
}
return config return config
}, },
error => { error => {
+10 -1
View File
@@ -7,6 +7,8 @@ import 'flex.css'
import '@/components' import '@/components'
// svg 图标 // svg 图标
import '@/assets/svg-icons' import '@/assets/svg-icons'
// 过滤器
import d2VueFiltersDate from '@d2-projects/vue-filters-date'
// 功能插件 // 功能插件
import pluginError from '@/plugin/error' import pluginError from '@/plugin/error'
import pluginLog from '@/plugin/log' import pluginLog from '@/plugin/log'
@@ -14,14 +16,21 @@ import pluginOpen from '@/plugin/open'
export default { export default {
async install (Vue, options) { async install (Vue, options) {
// 设置为 false 以阻止 vue 在启动时生成生产提示。https://cn.vuejs.org/v2/api/#productionTip // 设置为 false 以阻止 vue 在启动时生成生产提示
// https://cn.vuejs.org/v2/api/#productionTip
Vue.config.productionTip = false Vue.config.productionTip = false
// 当前环境 // 当前环境
Vue.prototype.$env = process.env.NODE_ENV Vue.prototype.$env = process.env.NODE_ENV
// 当前的 baseUrl // 当前的 baseUrl
Vue.prototype.$baseUrl = process.env.BASE_URL Vue.prototype.$baseUrl = process.env.BASE_URL
// 当前版本
Vue.prototype.$version = process.env.VUE_APP_VERSION
// 构建时间
Vue.prototype.$buildTime = process.env.VUE_APP_BUILD_TIME
// Element // Element
Vue.use(ElementUI) Vue.use(ElementUI)
// 过滤器 日期模块
Vue.use(d2VueFiltersDate)
// 插件 // 插件
Vue.use(pluginError) Vue.use(pluginError)
Vue.use(pluginLog) Vue.use(pluginLog)
+10 -8
View File
@@ -29,21 +29,23 @@ router.beforeEach((to, from, next) => {
// 关闭搜索面板 // 关闭搜索面板
store.commit('d2admin/search/set', false) store.commit('d2admin/search/set', false)
// 验证当前路由所有的匹配中是否需要有登录验证的 // 验证当前路由所有的匹配中是否需要有登录验证的
if (to.matched.some(r => r.meta.requiresAuth)) { if (to.matched.some(r => r.meta.auth)) {
// 这里暂时将cookie里是否存有token作为验证是否登录的条件 // 这里暂时将cookie里是否存有token作为验证是否登录的条件
// 请根据自身业务需要修改 // 请根据自身业务需要修改
const token = util.cookies.get('token') const token = util.cookies.get('token')
if (token && token !== 'undefined') { if (token && token !== 'undefined') {
next() next()
} else { } else {
// 将当前预计打开的页面完整地址临时存储 登录后继续跳转
// 这个 cookie(redirect) 会在登录后自动删除
util.cookies.set('redirect', to.fullPath)
// 没有登录的时候跳转到登录界面 // 没有登录的时候跳转到登录界面
// 携带上登陆成功之后需要跳转的页面完整路径
next({ next({
name: 'login' name: 'login',
query: {
redirect: to.fullPath
}
}) })
NProgress.done() // next(...)重定向不会触发router.afterEach钩子,需要手动hack一下 // https://github.com/d2-projects/d2-admin/issues/138
NProgress.done()
} }
} else { } else {
// 不需要身份校验 直接通过 // 不需要身份校验 直接通过
@@ -56,9 +58,9 @@ router.afterEach(to => {
NProgress.done() NProgress.done()
// 需要的信息 // 需要的信息
const app = router.app const app = router.app
const { name, params, query } = to const { name, params, query, fullPath } = to
// 多页控制 打开新的页面 // 多页控制 打开新的页面
app.$store.dispatch('d2admin/page/open', { name, params, query }) app.$store.dispatch('d2admin/page/open', { name, params, query, fullPath })
// 更改标题 // 更改标题
util.title(to.meta.title) util.title(to.meta.title)
}) })
+1 -1
View File
@@ -2,7 +2,7 @@ import demo from './modules/demo'
import layoutHeaderAside from '@/layout/header-aside' import layoutHeaderAside from '@/layout/header-aside'
const meta = { requiresAuth: true } const meta = { auth: true }
/** /**
* 在主框架内显示 * 在主框架内显示
+2 -7
View File
@@ -1,5 +1,3 @@
import { version } from '../package'
export default { export default {
// 快捷键 // 快捷键
// 支持快捷键 例如 ctrl+shift+s // 支持快捷键 例如 ctrl+shift+s
@@ -18,17 +16,14 @@ export default {
opened: [ opened: [
{ {
name: 'index', name: 'index',
fullPath: '/index',
meta: { meta: {
title: '首页', title: '首页',
requiresAuth: false auth: false
} }
} }
] ]
}, },
// 版本
releases: {
version
},
// 菜单搜索 // 菜单搜索
search: { search: {
enable: true enable: true
+30 -34
View File
@@ -1,5 +1,5 @@
import util from '@/libs/util.js' import util from '@/libs/util.js'
import { AccountLogin } from '@/api/sys/login' import { AccountLogin } from '@api/sys.login'
export default { export default {
namespaced: true, namespaced: true,
@@ -10,45 +10,41 @@ export default {
* @param {Object} param vm {Object} vue 实例 * @param {Object} param vm {Object} vue 实例
* @param {Object} param username {String} 用户账号 * @param {Object} param username {String} 用户账号
* @param {Object} param password {String} 密码 * @param {Object} param password {String} 密码
* @param {Object} param route {Object} 登录成功后定向的路由对象 * @param {Object} param route {Object} 登录成功后定向的路由对象 任何 vue-router 支持的格式
*/ */
login ({ dispatch }, { login ({ dispatch }, {
vm, vm,
username, username,
password, password
route = {
name: 'index'
}
}) { }) {
// 开始请求登录接口 return new Promise((resolve, reject) => {
AccountLogin({ // 开始请求登录接口
username, AccountLogin({
password username,
password
})
.then(async res => {
// 设置 cookie 一定要存 uuid 和 token 两个 cookie
// 整个系统依赖这两个数据进行校验和存储
// uuid 是用户身份唯一标识 用户注册的时候确定 并且不可改变 不可重复
// token 代表用户当前登录状态 建议在网络请求中携带 token
// 如有必要 token 需要定时更新,默认保存一天
util.cookies.set('uuid', res.uuid)
util.cookies.set('token', res.token)
// 设置 vuex 用户信息
await dispatch('d2admin/user/set', {
name: res.name
}, { root: true })
// 用户登录后从持久化数据加载一系列的设置
await dispatch('load')
// 结束
resolve()
})
.catch(err => {
console.log('err: ', err)
reject(err)
})
}) })
.then(async res => {
// 设置 cookie 一定要存 uuid 和 token 两个 cookie
// 整个系统依赖这两个数据进行校验和存储
// uuid 是用户身份唯一标识 用户注册的时候确定 并且不可改变 不可重复
// token 代表用户当前登录状态 建议在网络请求中携带 token
// 如有必要 token 需要定时更新,默认保存一天
util.cookies.set('uuid', res.uuid)
util.cookies.set('token', res.token)
// 设置 vuex 用户信息
await dispatch('d2admin/user/set', {
name: res.name
}, { root: true })
// 用户登录后从持久化数据加载一系列的设置
await dispatch('load')
// 更新路由 尝试去获取 cookie 里保存的需要重定向的页面完整地址
const path = util.cookies.get('redirect')
// 根据是否存有重定向页面判断如何重定向
vm.$router.replace(path ? { path } : route)
// 删除 cookie 中保存的重定向页面
util.cookies.remove('redirect')
})
.catch(err => {
console.log('err: ', err)
})
}, },
/** /**
* @description 注销用户并返回登录页面 * @description 注销用户并返回登录页面
+29 -22
View File
@@ -1,5 +1,5 @@
import db from '@/libs/db.js' import util from '@/libs/util'
import util from '@/libs/util.js' import { cloneDeep } from 'lodash'
/** /**
* @description 检查路径是否存在 不存在的话初始化 * @description 检查路径是否存在 不存在的话初始化
@@ -19,9 +19,16 @@ function pathInit ({
}) { }) {
const uuid = util.cookies.get('uuid') || 'ghost-uuid' const uuid = util.cookies.get('uuid') || 'ghost-uuid'
const currentPath = `${dbName}.${user ? `user.${uuid}` : 'public'}${path ? `.${path}` : ''}` const currentPath = `${dbName}.${user ? `user.${uuid}` : 'public'}${path ? `.${path}` : ''}`
const value = db.get(currentPath).value() const value = util.db.get(currentPath).value()
// console.group('pathInit')
// console.log('dbName', dbName)
// console.log('path', path)
// console.log('user', user)
// console.log('defaultValue', defaultValue)
// console.log('value', value)
// console.groupEnd()
if (!(value !== undefined && validator(value))) { if (!(value !== undefined && validator(value))) {
db util.db
.set(currentPath, defaultValue) .set(currentPath, defaultValue)
.write() .write()
} }
@@ -45,7 +52,7 @@ export default {
value = '', value = '',
user = false user = false
}) { }) {
db.set(pathInit({ util.db.set(pathInit({
dbName, dbName,
path, path,
user user
@@ -66,12 +73,12 @@ export default {
user = false user = false
}) { }) {
return new Promise(resolve => { return new Promise(resolve => {
resolve(db.get(pathInit({ resolve(cloneDeep(util.db.get(pathInit({
dbName, dbName,
path, path,
user, user,
defaultValue defaultValue
})).value()) })).value()))
}) })
}, },
/** /**
@@ -83,7 +90,7 @@ export default {
user = false user = false
} = {}) { } = {}) {
return new Promise(resolve => { return new Promise(resolve => {
resolve(db.get(pathInit({ resolve(util.db.get(pathInit({
dbName: 'database', dbName: 'database',
path: '', path: '',
user, user,
@@ -100,7 +107,7 @@ export default {
user = false user = false
} = {}) { } = {}) {
return new Promise(resolve => { return new Promise(resolve => {
resolve(db.get(pathInit({ resolve(util.db.get(pathInit({
dbName: 'database', dbName: 'database',
path: '', path: '',
user, user,
@@ -118,11 +125,11 @@ export default {
*/ */
databasePage (context, { databasePage (context, {
vm, vm,
basis = 'name', basis = 'fullPath',
user = false user = false
} = {}) { } = {}) {
return new Promise(resolve => { return new Promise(resolve => {
resolve(db.get(pathInit({ resolve(util.db.get(pathInit({
dbName: 'database', dbName: 'database',
path: `$page.${vm.$route[basis]}`, path: `$page.${vm.$route[basis]}`,
user, user,
@@ -139,11 +146,11 @@ export default {
*/ */
databasePageClear (context, { databasePageClear (context, {
vm, vm,
basis = 'name', basis = 'fullPath',
user = false user = false
} = {}) { } = {}) {
return new Promise(resolve => { return new Promise(resolve => {
resolve(db.get(pathInit({ resolve(util.db.get(pathInit({
dbName: 'database', dbName: 'database',
path: `$page.${vm.$route[basis]}`, path: `$page.${vm.$route[basis]}`,
user, user,
@@ -161,16 +168,16 @@ export default {
*/ */
pageSet (context, { pageSet (context, {
vm, vm,
basis = 'name', basis = 'fullPath',
user = false user = false
}) { }) {
return new Promise(resolve => { return new Promise(resolve => {
resolve(db.get(pathInit({ resolve(util.db.get(pathInit({
dbName: 'database', dbName: 'database',
path: `$page.${vm.$route[basis]}.$data`, path: `$page.${vm.$route[basis]}.$data`,
user, user,
validator: () => false, validator: () => false,
defaultValue: vm.$data defaultValue: cloneDeep(vm.$data)
}))) })))
}) })
}, },
@@ -183,16 +190,16 @@ export default {
*/ */
pageGet (context, { pageGet (context, {
vm, vm,
basis = 'name', basis = 'fullPath',
user = false user = false
}) { }) {
return new Promise(resolve => { return new Promise(resolve => {
resolve(db.get(pathInit({ resolve(cloneDeep(util.db.get(pathInit({
dbName: 'database', dbName: 'database',
path: `$page.${vm.$route[basis]}.$data`, path: `$page.${vm.$route[basis]}.$data`,
user, user,
defaultValue: vm.$data defaultValue: cloneDeep(vm.$data)
})).value()) })).value()))
}) })
}, },
/** /**
@@ -204,11 +211,11 @@ export default {
*/ */
pageClear (context, { pageClear (context, {
vm, vm,
basis = 'name', basis = 'fullPath',
user = false user = false
}) { }) {
return new Promise(resolve => { return new Promise(resolve => {
resolve(db.get(pathInit({ resolve(util.db.get(pathInit({
dbName: 'database', dbName: 'database',
path: `$page.${vm.$route[basis]}.$data`, path: `$page.${vm.$route[basis]}.$data`,
user, user,
+42 -44
View File
@@ -1,4 +1,4 @@
import { remove, get } from 'lodash' import { get } from 'lodash'
// 设置文件 // 设置文件
import setting from '@/setting.js' import setting from '@/setting.js'
@@ -34,14 +34,14 @@ export default {
user: true user: true
}, { root: true }) }, { root: true })
// 在处理函数中进行数据优化 过滤掉现在已经失效的页签或者已经改变了信息的页签 // 在处理函数中进行数据优化 过滤掉现在已经失效的页签或者已经改变了信息的页签
// 以 name 字段为准 // 以 fullPath 字段为准
// 如果页面过多的话可能需要优化算法 // 如果页面过多的话可能需要优化算法
// valid 有效列表 1, 1, 0, 1 => 有效, 有效, 失效, 有效 // valid 有效列表 1, 1, 0, 1 => 有效, 有效, 失效, 有效
const valid = [] const valid = []
// 处理数据 // 处理数据
state.opened = value.map(opened => { state.opened = value.map(opened => {
// 忽略首页 // 忽略首页
if (opened.name === 'index') { if (opened.fullPath === '/index') {
valid.push(1) valid.push(1)
return opened return opened
} }
@@ -80,19 +80,16 @@ export default {
* @class opened * @class opened
* @description 更新页面列表上的某一项 * @description 更新页面列表上的某一项
* @param {Object} state vuex state * @param {Object} state vuex state
* @param {Object} param { index, params, query } 路由信息 * @param {Object} param { index, params, query, fullPath } 路由信息
*/ */
openedUpdate ({ state, commit, dispatch }, { index, params, query }) { openedUpdate ({ state, commit, dispatch }, { index, params, query, fullPath }) {
return new Promise(async resolve => { return new Promise(async resolve => {
// 更新页面列表某一项 // 更新页面列表某一项
let page = state.opened[index] let page = state.opened[index]
page.params = params || page.params page.params = params || page.params
page.query = query || page.query page.query = query || page.query
page.fullPath = fullPath || page.fullPath
state.opened.splice(index, 1, page) state.opened.splice(index, 1, page)
// 增加缓存设置
if (isKeepAlive(page)) {
commit('keepAlivePush', page.name)
}
// 持久化 // 持久化
await dispatch('opend2db') await dispatch('opend2db')
// end // end
@@ -105,12 +102,13 @@ export default {
* @param {Object} state vuex state * @param {Object} state vuex state
* @param {Object} param new tag info * @param {Object} param new tag info
*/ */
add ({ state, commit, dispatch }, { tag, params, query }) { add ({ state, commit, dispatch }, { tag, params, query, fullPath }) {
return new Promise(async resolve => { return new Promise(async resolve => {
// 设置新的 tag 在新打开一个以前没打开过的页面时使用 // 设置新的 tag 在新打开一个以前没打开过的页面时使用
let newTag = tag let newTag = tag
newTag.params = params || newTag.params newTag.params = params || newTag.params
newTag.query = query || newTag.query newTag.query = query || newTag.query
newTag.fullPath = fullPath || newTag.fullPath
// 添加进当前显示的页面数组 // 添加进当前显示的页面数组
state.opened.push(newTag) state.opened.push(newTag)
// 如果这个页面需要缓存 将其添加到缓存设置 // 如果这个页面需要缓存 将其添加到缓存设置
@@ -127,25 +125,26 @@ export default {
* @class current * @class current
* @description 打开一个新的页面 * @description 打开一个新的页面
* @param {Object} state vuex state * @param {Object} state vuex state
* @param {Object} param { name, params, query } 路由信息 * @param {Object} param { name, params, query, fullPath } 路由信息
*/ */
open ({ state, commit, dispatch }, { name, params, query }) { open ({ state, commit, dispatch }, { name, params, query, fullPath }) {
return new Promise(async resolve => { return new Promise(async resolve => {
// 已经打开的页面 // 已经打开的页面
let opened = state.opened let opened = state.opened
// 判断此页面是否已经打开 并且记录位置 // 判断此页面是否已经打开 并且记录位置
let pageOpendIndex = 0 let pageOpendIndex = 0
const pageOpend = opened.find((page, index) => { const pageOpend = opened.find((page, index) => {
const same = page.name === name const same = page.fullPath === fullPath
pageOpendIndex = same ? index : pageOpendIndex pageOpendIndex = same ? index : pageOpendIndex
return same return same
}) })
if (pageOpend) { if (pageOpend) {
// 页面以前打开过 但是新的页面可能 name 一样,参数不一样 // 页面以前打开过
await dispatch('openedUpdate', { await dispatch('openedUpdate', {
index: pageOpendIndex, index: pageOpendIndex,
params, params,
query query,
fullPath
}) })
} else { } else {
// 页面以前没有打开过 // 页面以前没有打开过
@@ -153,13 +152,14 @@ export default {
// 如果这里没有找到 page 代表这个路由虽然在框架内 但是不参与标签页显示 // 如果这里没有找到 page 代表这个路由虽然在框架内 但是不参与标签页显示
if (page) { if (page) {
await dispatch('add', { await dispatch('add', {
tag: page, tag: Object.assign({}, page),
params, params,
query query,
fullPath
}) })
} }
} }
commit('currentSet', name) commit('currentSet', fullPath)
// end // end
resolve() resolve()
}) })
@@ -180,7 +180,7 @@ export default {
// 去找一个新的页面 // 去找一个新的页面
let len = state.opened.length let len = state.opened.length
for (let i = 1; i < len; i++) { for (let i = 1; i < len; i++) {
if (state.opened[i].name === tagName) { if (state.opened[i].fullPath === tagName) {
if (i < len - 1) { if (i < len - 1) {
newPage = state.opened[i + 1] newPage = state.opened[i + 1]
} else { } else {
@@ -191,12 +191,12 @@ export default {
} }
} }
// 找到这个页面在已经打开的数据里是第几个 // 找到这个页面在已经打开的数据里是第几个
const index = state.opened.findIndex(page => page.name === tagName) const index = state.opened.findIndex(page => page.fullPath === tagName)
if (index >= 0) { if (index >= 0) {
// 如果这个页面是缓存的页面 将其在缓存设置中删除
commit('keepAliveRemove', state.opened[index].name)
// 更新数据 删除关闭的页面 // 更新数据 删除关闭的页面
state.opened.splice(index, 1) state.opened.splice(index, 1)
// 如果这个页面是缓存的页面 将其在缓存设置中删除
commit('keepAliveRemove', tagName)
} }
// 持久化 // 持久化
await dispatch('opend2db') await dispatch('opend2db')
@@ -225,7 +225,7 @@ export default {
const pageAim = pageSelect || state.current const pageAim = pageSelect || state.current
let currentIndex = 0 let currentIndex = 0
state.opened.forEach((page, index) => { state.opened.forEach((page, index) => {
if (page.name === pageAim) { if (page.fullPath === pageAim) {
currentIndex = index currentIndex = index
} }
}) })
@@ -234,10 +234,8 @@ export default {
state.opened.splice(1, currentIndex - 1).forEach(({ name }) => commit('keepAliveRemove', name)) state.opened.splice(1, currentIndex - 1).forEach(({ name }) => commit('keepAliveRemove', name))
} }
state.current = pageAim state.current = pageAim
if (vm && vm.$route.name !== pageAim) { if (vm && vm.$route.fullPath !== pageAim) {
vm.$router.push({ vm.$router.push(pageAim)
name: pageAim
})
} }
// 持久化 // 持久化
await dispatch('opend2db') await dispatch('opend2db')
@@ -256,7 +254,7 @@ export default {
const pageAim = pageSelect || state.current const pageAim = pageSelect || state.current
let currentIndex = 0 let currentIndex = 0
state.opened.forEach((page, index) => { state.opened.forEach((page, index) => {
if (page.name === pageAim) { if (page.fullPath === pageAim) {
currentIndex = index currentIndex = index
} }
}) })
@@ -264,10 +262,8 @@ export default {
state.opened.splice(currentIndex + 1).forEach(({ name }) => commit('keepAliveRemove', name)) state.opened.splice(currentIndex + 1).forEach(({ name }) => commit('keepAliveRemove', name))
// 设置当前的页面 // 设置当前的页面
state.current = pageAim state.current = pageAim
if (vm && vm.$route.name !== pageAim) { if (vm && vm.$route.fullPath !== pageAim) {
vm.$router.push({ vm.$router.push(pageAim)
name: pageAim
})
} }
// 持久化 // 持久化
await dispatch('opend2db') await dispatch('opend2db')
@@ -286,7 +282,7 @@ export default {
const pageAim = pageSelect || state.current const pageAim = pageSelect || state.current
let currentIndex = 0 let currentIndex = 0
state.opened.forEach((page, index) => { state.opened.forEach((page, index) => {
if (page.name === pageAim) { if (page.fullPath === pageAim) {
currentIndex = index currentIndex = index
} }
}) })
@@ -299,10 +295,8 @@ export default {
} }
// 设置新的页面 // 设置新的页面
state.current = pageAim state.current = pageAim
if (vm && vm.$route.name !== pageAim) { if (vm && vm.$route.fullPath !== pageAim) {
vm.$router.push({ vm.$router.push(pageAim)
name: pageAim
})
} }
// 持久化 // 持久化
await dispatch('opend2db') await dispatch('opend2db')
@@ -349,8 +343,12 @@ export default {
*/ */
keepAliveRemove (state, name) { keepAliveRemove (state, name) {
const list = [ ...state.keepAlive ] const list = [ ...state.keepAlive ]
remove(list, item => item === name) const index = list.findIndex(item => item === name)
state.keepAlive = list
if (index !== -1) {
list.splice(index, 1)
state.keepAlive = list
}
}, },
/** /**
* @description 增加一个页面的缓存设置 * @description 增加一个页面的缓存设置
@@ -360,7 +358,7 @@ export default {
keepAlivePush (state, name) { keepAlivePush (state, name) {
const keep = [ ...state.keepAlive ] const keep = [ ...state.keepAlive ]
keep.push(name) keep.push(name)
state.keepAlive = Array.from(new Set(keep)) state.keepAlive = keep
}, },
/** /**
* @description 清空页面缓存设置 * @description 清空页面缓存设置
@@ -371,12 +369,12 @@ export default {
}, },
/** /**
* @class current * @class current
* @description 设置当前激活的页面 name * @description 设置当前激活的页面 fullPath
* @param {Object} state vuex state * @param {Object} state vuex state
* @param {String} name new name * @param {String} fullPath new fullPath
*/ */
currentSet (state, name) { currentSet (state, fullPath) {
state.current = name state.current = fullPath
}, },
/** /**
* @class pool * @class pool
@@ -1,19 +1,14 @@
import util from '@/libs/util.js' import util from '@/libs/util.js'
import setting from '@/setting.js'
export default { export default {
namespaced: true, namespaced: true,
state: {
// D2Admin 版本
version: setting.releases.version
},
mutations: { mutations: {
/** /**
* @description 显示版本信息 * @description 显示版本信息
* @param {Object} state vuex state * @param {Object} state vuex state
*/ */
versionShow (state) { versionShow () {
util.log.capsule('D2Admin', `v${state.version}`) util.log.capsule('D2Admin', `v${process.env.VUE_APP_VERSION}`)
console.log('Github https://github.com/d2-projects/d2-admin') console.log('Github https://github.com/d2-projects/d2-admin')
console.log('Doc https://doc.d2admin.fairyever.com/zh/') console.log('Doc https://doc.d2admin.fairyever.com/zh/')
console.log('请不要吝啬您的 star,谢谢 ~') console.log('请不要吝啬您的 star,谢谢 ~')
+14 -1
View File
@@ -48,12 +48,25 @@ export default {
load ({ state, commit, dispatch }) { load ({ state, commit, dispatch }) {
return new Promise(async resolve => { return new Promise(async resolve => {
// store 赋值 // store 赋值
state.activeName = await dispatch('d2admin/db/get', { let activeName = await dispatch('d2admin/db/get', {
dbName: 'sys', dbName: 'sys',
path: 'theme.activeName', path: 'theme.activeName',
defaultValue: state.list[0].name, defaultValue: state.list[0].name,
user: true user: true
}, { root: true }) }, { root: true })
// 检查这个主题在主题列表里是否存在
if (state.list.find(e => e.name === activeName)) {
state.activeName = activeName
} else {
state.activeName = state.list[0].name
// 持久化
await dispatch('d2admin/db/set', {
dbName: 'sys',
path: 'theme.activeName',
value: state.activeName,
user: true
}, { root: true })
}
// 将 vuex 中的主题应用到 dom // 将 vuex 中的主题应用到 dom
commit('dom') commit('dom')
// end // end
+58
View File
@@ -1,6 +1,12 @@
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
// 拼接路径 // 拼接路径
const resolve = dir => require('path').join(__dirname, dir) const resolve = dir => require('path').join(__dirname, dir)
// 增加环境变量
process.env.VUE_APP_VERSION = require('./package.json').version
process.env.VUE_APP_BUILD_TIME = require('dayjs')().format('YYYY-M-D HH:mm:ss')
// 基础路径 注意发布之前要先修改这里 // 基础路径 注意发布之前要先修改这里
let baseUrl = '/' let baseUrl = '/'
@@ -10,11 +16,52 @@ module.exports = {
devServer: { devServer: {
publicPath: baseUrl // 和 baseUrl 保持一致 publicPath: baseUrl // 和 baseUrl 保持一致
}, },
css: {
loaderOptions: {
// 设置 scss 公用变量文件
sass: {
data: `@import '~@/assets/style/public.scss';`
}
}
},
// 默认设置: 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 => {
/**
* 删除懒加载模块的 prefetch preload,降低带宽压力
* https://cli.vuejs.org/zh/guide/html-and-static-assets.html#prefetch
* https://cli.vuejs.org/zh/guide/html-and-static-assets.html#preload
* 而且预渲染时生成的 prefetch 标签是 modern 版本的,低版本浏览器是不需要的
*/
config.plugins
.delete('prefetch')
.delete('preload')
// 解决 cli3 热更新失效 https://github.com/vuejs/vue-cli/issues/1559 // 解决 cli3 热更新失效 https://github.com/vuejs/vue-cli/issues/1559
config.resolve config.resolve
.symlinks(true) .symlinks(true)
config
// 开发环境
.when(process.env.NODE_ENV === 'development',
// sourcemap不包含列信息
config => config.devtool('cheap-source-map')
)
// 非开发环境
.when(process.env.NODE_ENV !== 'development', config => {
config.optimization
.minimizer([
new UglifyJsPlugin({
uglifyOptions: {
// 移除 console
// 其它优化选项 https://segmentfault.com/a/1190000010874406
compress: {
warnings: false,
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log']
}
}
})
])
})
// markdown // markdown
config.module config.module
.rule('md') .rule('md')
@@ -52,10 +99,21 @@ module.exports = {
// 重新设置 alias // 重新设置 alias
config.resolve.alias config.resolve.alias
.set('@', resolve('src')) .set('@', resolve('src'))
.set('@api', resolve('src/api'))
// node
config.node
.set('__dirname', true)
.set('__filename', true)
// babel-polyfill 加入 entry // babel-polyfill 加入 entry
const entry = config.entry('app') const entry = config.entry('app')
entry entry
.add('babel-polyfill') .add('babel-polyfill')
.end() .end()
// 判断环境加入模拟数据
if (process.env.VUE_APP_BUILD_MODE !== 'nomock') {
entry
.add('@/mock')
.end()
}
} }
} }
+2676 -2103
View File
File diff suppressed because it is too large Load Diff