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
+5048 -6514
View File
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -1,17 +1,19 @@
{
"name": "d2-admin",
"version": "1.5.9",
"version": "1.6.9",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --open",
"start": "npm run serve",
"dev": "npm run serve",
"build": "vue-cli-service build",
"build:nomock": "vue-cli-service build --mode nomock",
"build:travis": "vue-cli-service build --mode travis",
"lint": "vue-cli-service lint --fix",
"test:unit": "vue-cli-service test:unit"
},
"dependencies": {
"@d2-projects/vue-filters-date": "^1.0.2",
"axios": "^0.17.1",
"babel-polyfill": "^6.26.0",
"better-scroll": "^1.12.1",
@@ -21,11 +23,11 @@
"fuse.js": "^3.2.1",
"hotkeys-js": "^3.3.6",
"js-cookie": "^2.2.0",
"lodash": "^4.17.11",
"lodash": "^4.17.10",
"lowdb": "^1.0.0",
"mockjs": "^1.0.1-beta3",
"nprogress": "^0.2.0",
"particles.js": "^2.0.0",
"qs": "^6.6.0",
"screenfull": "^3.3.2",
"ua-parser-js": "^0.7.18",
"vue": "^2.5.17",
@@ -48,6 +50,7 @@
"sass-loader": "^7.0.1",
"svg-sprite-loader": "^3.8.0",
"text-loader": "0.0.1",
"uglifyjs-webpack-plugin": "^2.0.1",
"vue-template-compiler": "^2.5.17"
},
"lint-staged": {
-1
View File
@@ -1,5 +1,4 @@
// 优化显示
html, body {
margin: 0px;
height: 100%;
-1
View File
@@ -1,5 +1,4 @@
// element 样式补丁
.el-card {
&.is-always-shadow {
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/d2/index.scss';
@@ -3,7 +3,7 @@
<div v-if="$slots.header" class="d2-container-card__header" ref="header">
<slot name="header"/>
</div>
<div class="d2-container-card__body">
<div class="d2-container-card__body" ref="body">
<div class="d2-container-card__body-card">
<slot/>
</div>
@@ -15,7 +15,19 @@
</template>
<script>
import scroll from './mixins/normal'
export default {
name: 'd2-container-card'
name: 'd2-container-card',
mixins: [
scroll
],
mounted () {
// 增加滚动事件监听
this.addScrollListener()
},
beforeDestroy () {
// 移除滚动事件监听
this.removeScrollListener()
}
}
</script>
@@ -3,7 +3,7 @@
<div v-if="$slots.header" class="d2-container-full__header" ref="header">
<slot name="header"/>
</div>
<div class="d2-container-full__body">
<div class="d2-container-full__body" ref="body">
<slot/>
</div>
<div v-if="$slots.footer" class="d2-container-full__footer" ref="footer">
@@ -13,7 +13,19 @@
</template>
<script>
import scroll from './mixins/normal'
export default {
name: 'd2-container-full'
name: 'd2-container-full',
mixins: [
scroll
],
mounted () {
// 增加滚动事件监听
this.addScrollListener()
},
beforeDestroy () {
// 移除滚动事件监听
this.removeScrollListener()
}
}
</script>
@@ -3,7 +3,7 @@
<div v-if="$slots.header" class="d2-container-ghost__header" ref="header">
<slot name="header"/>
</div>
<div class="d2-container-ghost__body">
<div class="d2-container-ghost__body" ref="body">
<slot/>
</div>
<div v-if="$slots.footer" class="d2-container-ghost__footer" ref="footer">
@@ -11,3 +11,21 @@
</div>
</div>
</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'
export default {
props: {
// 滚动优化的选项
betterScrollOptions: {
type: Object,
required: false,
@@ -20,6 +21,7 @@ export default {
},
methods: {
scrollInit () {
// 初始化 bs
this.BS = new BScroll(this.$refs.wrapper, Object.assign({
mouseWheel: true,
scrollbar: {
@@ -27,6 +29,11 @@ export default {
interactive: false
}
}, this.betterScrollOptions))
// 滚动时发出事件 并且统一返回的数据格式
this.BS.on('scroll', ({ x, y }) => this.$emit('scroll', {
x: -x,
y: -y
}))
},
scrollDestroy () {
// https://github.com/d2-projects/d2-admin/issues/75
@@ -36,6 +43,19 @@ export default {
delete this.BS
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">
<slot/>
</div>
<p class="d2-page-cover__title">{{title}}</p>
<p class="d2-page-cover__sub-title d2-mt-0">{{subTitle}}</p>
<p class="d2-page-cover__title">D2 Admin {{$version}}</p>
<p class="d2-page-cover__sub-title">优雅的中后台集成方案</p>
<p class="d2-page-cover__build-time">FINAL BUILD TIME {{$buildTime}}</p>
<slot name="footer"/>
<a target="blank" href="https://github.com/d2-projects/d2-admin">
<img
@@ -15,25 +16,7 @@
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
required: false,
default: 'Title'
},
subTitle: {
type: String,
required: false,
default: 'subTitle'
}
}
}
</script>
<style lang="scss" scoped>
@import '~@/assets/style/public.scss';
.d2-page-cover {
@extend %full;
@extend %unable-select;
@@ -47,10 +30,21 @@ export default {
}
}
.d2-page-cover__title {
margin: 0px;
margin-bottom: 20px;
font-weight: bold;
color: $color-text-main;
}
.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>
@@ -1,8 +1,8 @@
<template>
<el-menu-item :index="menu.path || uniqueId">
<i v-if="menu.icon" :class="`fa fa-${menu.icon}`"></i>
<d2-icon-svg v-else-if="menu.iconSvg" :name="menu.iconSvg"/>
<i v-else class="fa fa-file-o"></i>
<i v-if="menu.icon === undefined & !menu.iconSvg" class="fa fa-file-o"></i>
<d2-icon-svg v-if="menu.iconSvg" :name="menu.iconSvg"/>
<span slot="title">{{menu.title || '未命名菜单'}}</span>
</el-menu-item>
</template>
@@ -2,8 +2,8 @@
<el-submenu :index="menu.path || uniqueId">
<template slot="title">
<i v-if="menu.icon" :class="`fa fa-${menu.icon}`"></i>
<d2-icon-svg v-else-if="menu.iconSvg" :name="menu.iconSvg"/>
<i v-else class="fa fa-folder-o"></i>
<i v-if="menu.icon === undefined & !menu.iconSvg" class="fa fa-folder-o"></i>
<d2-icon-svg v-if="menu.iconSvg" :name="menu.iconSvg"/>
<span slot="title">{{menu.title}}</span>
</template>
<template v-for="(child, childIndex) in menu.children">
@@ -165,7 +165,6 @@ export default {
</script>
<style lang="scss">
@import '~@/assets/style/public.scss';
.d2-error-log-list__expand-group {
.d2-error-log-list__expand {
padding-left: 20px;
@@ -69,7 +69,6 @@ export default {
</script>
<style lang="scss" scoped>
@import '~@/assets/style/public.scss';
.theme-preview {
height: 50px;
width: 100px;
@@ -55,12 +55,12 @@ export default {
}, 500)
},
// 监听路由 控制侧边栏激活状态
'$route.matched': {
handler (val) {
this.active = val[val.length - 1].path
'$route': {
handler ({ fullPath }) {
this.active = fullPath
this.$nextTick(() => {
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>
<style lang="scss" scoped>
@import '~@/assets/style/public.scss';
.d2-panel-search-item {
height: 64px;
margin: 0px -20px;
@@ -183,7 +183,6 @@ export default {
</script>
<style lang="scss" scoped>
@import '~@/assets/style/public.scss';
.panel-search {
margin: 20px;
.panel-search__input-group {
@@ -7,7 +7,7 @@
:x="contentmenuX"
:y="contentmenuY">
<d2-contextmenu-list
:menulist="tagName === 'index' ? contextmenuListIndex : contextmenuList"
:menulist="tagName === '/index' ? contextmenuListIndex : contextmenuList"
@rowClick="contextmenuClick"/>
</d2-contextmenu>
<el-tabs
@@ -19,10 +19,10 @@
@edit="handleTabsEdit"
@contextmenu.native="handleContextmenu">
<el-tab-pane
v-for="(page, index) in opened"
:key="index"
v-for="page in opened"
:key="page.fullPath"
:label="page.meta.title || '未命名'"
:name="page.name"/>
:name="page.fullPath"/>
</el-tabs>
</div>
</div>
@@ -79,7 +79,7 @@ export default {
{ icon: 'times', title: '关闭其它', value: 'other' },
{ icon: 'times-circle', title: '关闭全部', value: 'all' }
],
tagName: 'index'
tagName: '/index'
}
},
computed: {
@@ -163,7 +163,7 @@ export default {
*/
handleClick (tab, event) {
// 找到点击的页面在 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
if (page) {
this.$router.push({ name, params, query })
+18 -9
View File
@@ -81,6 +81,15 @@
</template>
<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 mixinSearch from './mixins/search'
export default {
@@ -89,15 +98,15 @@ export default {
mixinSearch
],
components: {
'd2-menu-side': () => import('./components/menu-side'),
'd2-menu-header': () => import('./components/menu-header'),
'd2-tabs': () => import('./components/tabs'),
'd2-header-fullscreen': () => import('./components/header-fullscreen'),
'd2-header-search': () => import('./components/header-search'),
'd2-header-size': () => import('./components/header-size'),
'd2-header-theme': () => import('./components/header-theme'),
'd2-header-user': () => import('./components/header-user'),
'd2-header-error-log': () => import('./components/header-error-log')
d2MenuSide,
d2MenuHeader,
d2Tabs,
d2HeaderFullscreen,
d2HeaderSearch,
d2HeaderSize,
d2HeaderTheme,
d2HeaderUser,
d2HeaderErrorLog
},
data () {
return {
+3 -4
View File
@@ -1,5 +1,4 @@
import Cookies from 'js-cookie'
import setting from '@/setting.js'
const cookies = {}
@@ -14,7 +13,7 @@ cookies.set = function (name = 'default', value = '', cookieSetting = {}) {
expires: 1
}
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
*/
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
*/
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
+1 -2
View File
@@ -1,8 +1,7 @@
import low from 'lowdb'
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)
db
+7 -5
View File
@@ -1,8 +1,10 @@
import log from './util.log.js'
import cookies from './util.cookies.js'
import cookies from './util.cookies'
import db from './util.db'
import log from './util.log'
let util = {
const util = {
cookies,
db,
log
}
@@ -23,10 +25,10 @@ util.open = function (url) {
var a = document.createElement('a')
a.setAttribute('href', url)
a.setAttribute('target', '_blank')
a.setAttribute('id', 'd2admin-menu-link')
a.setAttribute('id', 'd2admin-link-temp')
document.body.appendChild(a)
a.click()
document.body.removeChild(document.getElementById('d2admin-menu-link'))
document.body.removeChild(document.getElementById('d2admin-link-temp'))
}
export default util
-2
View File
@@ -5,8 +5,6 @@ import Vue from 'vue'
import App from './App'
// store
import store from '@/store/index'
// 模拟数据
import '@/mock'
// 多国语
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)
req(require.context('./api/', true, /\.js$/))
const options = req(require.context('./api/', true, /\.js$/))
.filter(e => e.default)
.map(e => e.default)
// 设置全局延时 没有延时的话有时候会检测不到数据变化 建议保留
Mock.setup({
timeout: '300-600'
options.forEach(option => {
d2Mock.load(option)
})
-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>
<d2-container>
<d2-container :filename="filename">
<template slot="header">Page 1 header</template>
Hello World
<template slot="footer">footer</template>
</d2-container>
</template>
<script>
export default {
name: 'page1'
name: 'page1',
data () {
return {
filename: __filename
}
}
}
</script>
+7 -3
View File
@@ -1,13 +1,17 @@
<template>
<d2-container>
<d2-container :filename="filename">
<template slot="header">Page 2 header</template>
Hello World
<template slot="footer">footer</template>
</d2-container>
</template>
<script>
export default {
name: 'page2'
name: 'page2',
data () {
return {
filename: __filename
}
}
}
</script>
+7 -3
View File
@@ -1,13 +1,17 @@
<template>
<d2-container>
<d2-container :filename="filename">
<template slot="header">Page 3 header</template>
Hello World
<template slot="footer">footer</template>
</d2-container>
</template>
<script>
export default {
name: 'page3'
name: 'page3',
data () {
return {
filename: __filename
}
}
}
</script>
-1
View File
@@ -8,7 +8,6 @@
</template>
<style lang="scss" scoped>
@import '~@/assets/style/public.scss';
.page {
background: #303133;
background-blend-mode: multiply,multiply;
+7 -11
View File
@@ -1,8 +1,6 @@
<template>
<d2-container class="page">
<d2-page-cover
:title="`D2 Admin ${version}`"
sub-title="优雅的中后台集成方案">
<d2-container :filename="filename" class="page">
<d2-page-cover>
<d2-icon-svg
class="page__logo"
name="d2-admin"/>
@@ -35,7 +33,6 @@
</template>
<script>
import { mapState } from 'vuex'
import D2HelpBtn from './components/d2-help-btn'
import D2Badge from './components/d2-badge'
export default {
@@ -43,16 +40,15 @@ export default {
D2HelpBtn,
D2Badge
},
computed: {
...mapState('d2admin/releases', [
'version'
])
data () {
return {
filename: __filename
}
}
}
</script>
<style lang="scss" scoped>
@import '~@/assets/style/public.scss';
.page {
.page__logo {
width: 120px;
@@ -60,7 +56,7 @@ export default {
.page__btn-group {
color: $color-text-placehoder;
font-size: 12px;
margin-top: -10px;
margin-top: 0px;
margin-bottom: 20px;
span {
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

+289 -25
View File
@@ -1,14 +1,32 @@
<template>
<div class="login-page">
<div class="layer bg" id="login"></div>
<div class="layer flex-center">
<!-- logo部分 -->
<div class="logo-group">
<img src="./image/logo.png" alt="logo">
<div class="page-login">
<div class="page-login--layer page-login--layer-area">
<ul class="circles">
<li v-for="n in 10" :key="n"></li>
</ul>
</div>
<!-- 表单部分 -->
<div class="form-group">
<el-card>
<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="用户名">
@@ -31,19 +49,37 @@
<el-button size="default" @click="submit" type="primary" class="button-login">登录</el-button>
</el-form>
</el-card>
</div>
<p
class="page-login--options"
flex="main:justify cross:center">
<span><d2-icon name="question-circle"/> 忘记密码</span>
<span>注册用户</span>
</p>
<!-- 快速登录按钮 -->
<el-button size="default" type="info" class="button-help" @click="dialogVisible = true">
<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>
<el-dialog
title="快速选择用户"
:visible.sync="dialogVisible"
width="400px">
<el-row :gutter="10" style="margin: -20px 0px -10px 0px;">
<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"/>
<span>{{user.name}}</span>
</div>
@@ -54,13 +90,13 @@
</template>
<script>
/* eslint-disable */
require('particles.js')
import config from './config/default'
import dayjs from 'dayjs'
import { mapActions } from 'vuex'
export default {
data () {
return {
timeInterval: null,
time: dayjs().format('HH:mm:ss'),
// 快速选择用户
dialogVisible: false,
users: [
@@ -101,22 +137,20 @@ export default {
}
},
mounted () {
// 初始化例子插件
particlesJS('login', config)
this.timeInterval = setInterval(() => {
this.refreshTime()
}, 1000)
},
beforeDestroy () {
// 销毁 particlesJS
// 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 = []
}
clearInterval(this.timeInterval)
},
methods: {
...mapActions('d2admin/account', [
'login'
]),
refreshTime () {
this.time = dayjs().format('HH:mm:ss')
},
/**
* @description 接收选择一个用户快速登录的事件
* @param {Object} user 用户信息
@@ -141,6 +175,10 @@ export default {
username: this.formLogin.username,
password: this.formLogin.password
})
.then(() => {
// 重定向对象不存在则返回顶层路径
this.$router.replace(this.$route.query.redirect || '/')
})
} else {
// 登录表单校验失败
this.$message.error('表单校验失败')
@@ -152,5 +190,231 @@ export default {
</script>
<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>
-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;
}
}
}
-4
View File
@@ -41,13 +41,9 @@ const service = axios.create({
service.interceptors.request.use(
config => {
// 在请求发送之前做一些处理
if (!(/^https:\/\/|http:\/\//.test(config.url))) {
const token = util.cookies.get('token')
if (token && token !== 'undefined') {
// 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
config.headers['X-Token'] = token
}
}
return config
},
error => {
+10 -1
View File
@@ -7,6 +7,8 @@ import 'flex.css'
import '@/components'
// svg 图标
import '@/assets/svg-icons'
// 过滤器
import d2VueFiltersDate from '@d2-projects/vue-filters-date'
// 功能插件
import pluginError from '@/plugin/error'
import pluginLog from '@/plugin/log'
@@ -14,14 +16,21 @@ import pluginOpen from '@/plugin/open'
export default {
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.prototype.$env = process.env.NODE_ENV
// 当前的 baseUrl
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
Vue.use(ElementUI)
// 过滤器 日期模块
Vue.use(d2VueFiltersDate)
// 插件
Vue.use(pluginError)
Vue.use(pluginLog)
+10 -8
View File
@@ -29,21 +29,23 @@ router.beforeEach((to, from, next) => {
// 关闭搜索面板
store.commit('d2admin/search/set', false)
// 验证当前路由所有的匹配中是否需要有登录验证的
if (to.matched.some(r => r.meta.requiresAuth)) {
if (to.matched.some(r => r.meta.auth)) {
// 这里暂时将cookie里是否存有token作为验证是否登录的条件
// 请根据自身业务需要修改
const token = util.cookies.get('token')
if (token && token !== 'undefined') {
next()
} else {
// 将当前预计打开的页面完整地址临时存储 登录后继续跳转
// 这个 cookie(redirect) 会在登录后自动删除
util.cookies.set('redirect', to.fullPath)
// 没有登录的时候跳转到登录界面
// 携带上登陆成功之后需要跳转的页面完整路径
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 {
// 不需要身份校验 直接通过
@@ -56,9 +58,9 @@ router.afterEach(to => {
NProgress.done()
// 需要的信息
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)
})
+1 -1
View File
@@ -2,7 +2,7 @@ import demo from './modules/demo'
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 {
// 快捷键
// 支持快捷键 例如 ctrl+shift+s
@@ -18,17 +16,14 @@ export default {
opened: [
{
name: 'index',
fullPath: '/index',
meta: {
title: '首页',
requiresAuth: false
auth: false
}
}
]
},
// 版本
releases: {
version
},
// 菜单搜索
search: {
enable: true
+8 -12
View File
@@ -1,5 +1,5 @@
import util from '@/libs/util.js'
import { AccountLogin } from '@/api/sys/login'
import { AccountLogin } from '@api/sys.login'
export default {
namespaced: true,
@@ -10,16 +10,14 @@ export default {
* @param {Object} param vm {Object} vue 实例
* @param {Object} param username {String} 用户账号
* @param {Object} param password {String} 密码
* @param {Object} param route {Object} 登录成功后定向的路由对象
* @param {Object} param route {Object} 登录成功后定向的路由对象 任何 vue-router 支持的格式
*/
login ({ dispatch }, {
vm,
username,
password,
route = {
name: 'index'
}
password
}) {
return new Promise((resolve, reject) => {
// 开始请求登录接口
AccountLogin({
username,
@@ -39,15 +37,13 @@ export default {
}, { root: true })
// 用户登录后从持久化数据加载一系列的设置
await dispatch('load')
// 更新路由 尝试去获取 cookie 里保存的需要重定向的页面完整地址
const path = util.cookies.get('redirect')
// 根据是否存有重定向页面判断如何重定向
vm.$router.replace(path ? { path } : route)
// 删除 cookie 中保存的重定向页面
util.cookies.remove('redirect')
// 结束
resolve()
})
.catch(err => {
console.log('err: ', err)
reject(err)
})
})
},
/**
+29 -22
View File
@@ -1,5 +1,5 @@
import db from '@/libs/db.js'
import util from '@/libs/util.js'
import util from '@/libs/util'
import { cloneDeep } from 'lodash'
/**
* @description 检查路径是否存在 不存在的话初始化
@@ -19,9 +19,16 @@ function pathInit ({
}) {
const uuid = util.cookies.get('uuid') || 'ghost-uuid'
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))) {
db
util.db
.set(currentPath, defaultValue)
.write()
}
@@ -45,7 +52,7 @@ export default {
value = '',
user = false
}) {
db.set(pathInit({
util.db.set(pathInit({
dbName,
path,
user
@@ -66,12 +73,12 @@ export default {
user = false
}) {
return new Promise(resolve => {
resolve(db.get(pathInit({
resolve(cloneDeep(util.db.get(pathInit({
dbName,
path,
user,
defaultValue
})).value())
})).value()))
})
},
/**
@@ -83,7 +90,7 @@ export default {
user = false
} = {}) {
return new Promise(resolve => {
resolve(db.get(pathInit({
resolve(util.db.get(pathInit({
dbName: 'database',
path: '',
user,
@@ -100,7 +107,7 @@ export default {
user = false
} = {}) {
return new Promise(resolve => {
resolve(db.get(pathInit({
resolve(util.db.get(pathInit({
dbName: 'database',
path: '',
user,
@@ -118,11 +125,11 @@ export default {
*/
databasePage (context, {
vm,
basis = 'name',
basis = 'fullPath',
user = false
} = {}) {
return new Promise(resolve => {
resolve(db.get(pathInit({
resolve(util.db.get(pathInit({
dbName: 'database',
path: `$page.${vm.$route[basis]}`,
user,
@@ -139,11 +146,11 @@ export default {
*/
databasePageClear (context, {
vm,
basis = 'name',
basis = 'fullPath',
user = false
} = {}) {
return new Promise(resolve => {
resolve(db.get(pathInit({
resolve(util.db.get(pathInit({
dbName: 'database',
path: `$page.${vm.$route[basis]}`,
user,
@@ -161,16 +168,16 @@ export default {
*/
pageSet (context, {
vm,
basis = 'name',
basis = 'fullPath',
user = false
}) {
return new Promise(resolve => {
resolve(db.get(pathInit({
resolve(util.db.get(pathInit({
dbName: 'database',
path: `$page.${vm.$route[basis]}.$data`,
user,
validator: () => false,
defaultValue: vm.$data
defaultValue: cloneDeep(vm.$data)
})))
})
},
@@ -183,16 +190,16 @@ export default {
*/
pageGet (context, {
vm,
basis = 'name',
basis = 'fullPath',
user = false
}) {
return new Promise(resolve => {
resolve(db.get(pathInit({
resolve(cloneDeep(util.db.get(pathInit({
dbName: 'database',
path: `$page.${vm.$route[basis]}.$data`,
user,
defaultValue: vm.$data
})).value())
defaultValue: cloneDeep(vm.$data)
})).value()))
})
},
/**
@@ -204,11 +211,11 @@ export default {
*/
pageClear (context, {
vm,
basis = 'name',
basis = 'fullPath',
user = false
}) {
return new Promise(resolve => {
resolve(db.get(pathInit({
resolve(util.db.get(pathInit({
dbName: 'database',
path: `$page.${vm.$route[basis]}.$data`,
user,
+41 -43
View File
@@ -1,4 +1,4 @@
import { remove, get } from 'lodash'
import { get } from 'lodash'
// 设置文件
import setting from '@/setting.js'
@@ -34,14 +34,14 @@ export default {
user: true
}, { root: true })
// 在处理函数中进行数据优化 过滤掉现在已经失效的页签或者已经改变了信息的页签
// 以 name 字段为准
// 以 fullPath 字段为准
// 如果页面过多的话可能需要优化算法
// valid 有效列表 1, 1, 0, 1 => 有效, 有效, 失效, 有效
const valid = []
// 处理数据
state.opened = value.map(opened => {
// 忽略首页
if (opened.name === 'index') {
if (opened.fullPath === '/index') {
valid.push(1)
return opened
}
@@ -80,19 +80,16 @@ export default {
* @class opened
* @description 更新页面列表上的某一项
* @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 => {
// 更新页面列表某一项
let page = state.opened[index]
page.params = params || page.params
page.query = query || page.query
page.fullPath = fullPath || page.fullPath
state.opened.splice(index, 1, page)
// 增加缓存设置
if (isKeepAlive(page)) {
commit('keepAlivePush', page.name)
}
// 持久化
await dispatch('opend2db')
// end
@@ -105,12 +102,13 @@ export default {
* @param {Object} state vuex state
* @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 => {
// 设置新的 tag 在新打开一个以前没打开过的页面时使用
let newTag = tag
newTag.params = params || newTag.params
newTag.query = query || newTag.query
newTag.fullPath = fullPath || newTag.fullPath
// 添加进当前显示的页面数组
state.opened.push(newTag)
// 如果这个页面需要缓存 将其添加到缓存设置
@@ -127,25 +125,26 @@ export default {
* @class current
* @description 打开一个新的页面
* @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 => {
// 已经打开的页面
let opened = state.opened
// 判断此页面是否已经打开 并且记录位置
let pageOpendIndex = 0
const pageOpend = opened.find((page, index) => {
const same = page.name === name
const same = page.fullPath === fullPath
pageOpendIndex = same ? index : pageOpendIndex
return same
})
if (pageOpend) {
// 页面以前打开过 但是新的页面可能 name 一样,参数不一样
// 页面以前打开过
await dispatch('openedUpdate', {
index: pageOpendIndex,
params,
query
query,
fullPath
})
} else {
// 页面以前没有打开过
@@ -153,13 +152,14 @@ export default {
// 如果这里没有找到 page 代表这个路由虽然在框架内 但是不参与标签页显示
if (page) {
await dispatch('add', {
tag: page,
tag: Object.assign({}, page),
params,
query
query,
fullPath
})
}
}
commit('currentSet', name)
commit('currentSet', fullPath)
// end
resolve()
})
@@ -180,7 +180,7 @@ export default {
// 去找一个新的页面
let len = state.opened.length
for (let i = 1; i < len; i++) {
if (state.opened[i].name === tagName) {
if (state.opened[i].fullPath === tagName) {
if (i < len - 1) {
newPage = state.opened[i + 1]
} 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) {
// 如果这个页面是缓存的页面 将其在缓存设置中删除
commit('keepAliveRemove', state.opened[index].name)
// 更新数据 删除关闭的页面
state.opened.splice(index, 1)
// 如果这个页面是缓存的页面 将其在缓存设置中删除
commit('keepAliveRemove', tagName)
}
// 持久化
await dispatch('opend2db')
@@ -225,7 +225,7 @@ export default {
const pageAim = pageSelect || state.current
let currentIndex = 0
state.opened.forEach((page, index) => {
if (page.name === pageAim) {
if (page.fullPath === pageAim) {
currentIndex = index
}
})
@@ -234,10 +234,8 @@ export default {
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
})
if (vm && vm.$route.fullPath !== pageAim) {
vm.$router.push(pageAim)
}
// 持久化
await dispatch('opend2db')
@@ -256,7 +254,7 @@ export default {
const pageAim = pageSelect || state.current
let currentIndex = 0
state.opened.forEach((page, index) => {
if (page.name === pageAim) {
if (page.fullPath === pageAim) {
currentIndex = index
}
})
@@ -264,10 +262,8 @@ export default {
state.opened.splice(currentIndex + 1).forEach(({ name }) => commit('keepAliveRemove', name))
// 设置当前的页面
state.current = pageAim
if (vm && vm.$route.name !== pageAim) {
vm.$router.push({
name: pageAim
})
if (vm && vm.$route.fullPath !== pageAim) {
vm.$router.push(pageAim)
}
// 持久化
await dispatch('opend2db')
@@ -286,7 +282,7 @@ export default {
const pageAim = pageSelect || state.current
let currentIndex = 0
state.opened.forEach((page, index) => {
if (page.name === pageAim) {
if (page.fullPath === pageAim) {
currentIndex = index
}
})
@@ -299,10 +295,8 @@ export default {
}
// 设置新的页面
state.current = pageAim
if (vm && vm.$route.name !== pageAim) {
vm.$router.push({
name: pageAim
})
if (vm && vm.$route.fullPath !== pageAim) {
vm.$router.push(pageAim)
}
// 持久化
await dispatch('opend2db')
@@ -349,8 +343,12 @@ export default {
*/
keepAliveRemove (state, name) {
const list = [ ...state.keepAlive ]
remove(list, item => item === name)
const index = list.findIndex(item => item === name)
if (index !== -1) {
list.splice(index, 1)
state.keepAlive = list
}
},
/**
* @description 增加一个页面的缓存设置
@@ -360,7 +358,7 @@ export default {
keepAlivePush (state, name) {
const keep = [ ...state.keepAlive ]
keep.push(name)
state.keepAlive = Array.from(new Set(keep))
state.keepAlive = keep
},
/**
* @description 清空页面缓存设置
@@ -371,12 +369,12 @@ export default {
},
/**
* @class current
* @description 设置当前激活的页面 name
* @description 设置当前激活的页面 fullPath
* @param {Object} state vuex state
* @param {String} name new name
* @param {String} fullPath new fullPath
*/
currentSet (state, name) {
state.current = name
currentSet (state, fullPath) {
state.current = fullPath
},
/**
* @class pool
@@ -1,19 +1,14 @@
import util from '@/libs/util.js'
import setting from '@/setting.js'
export default {
namespaced: true,
state: {
// D2Admin 版本
version: setting.releases.version
},
mutations: {
/**
* @description 显示版本信息
* @param {Object} state vuex state
*/
versionShow (state) {
util.log.capsule('D2Admin', `v${state.version}`)
versionShow () {
util.log.capsule('D2Admin', `v${process.env.VUE_APP_VERSION}`)
console.log('Github https://github.com/d2-projects/d2-admin')
console.log('Doc https://doc.d2admin.fairyever.com/zh/')
console.log('请不要吝啬您的 star,谢谢 ~')
+14 -1
View File
@@ -48,12 +48,25 @@ export default {
load ({ state, commit, dispatch }) {
return new Promise(async resolve => {
// store 赋值
state.activeName = await dispatch('d2admin/db/get', {
let activeName = await dispatch('d2admin/db/get', {
dbName: 'sys',
path: 'theme.activeName',
defaultValue: state.list[0].name,
user: 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
commit('dom')
// end
+58
View File
@@ -1,6 +1,12 @@
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
// 拼接路径
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 = '/'
@@ -10,11 +16,52 @@ module.exports = {
devServer: {
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
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
config.resolve
.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
config.module
.rule('md')
@@ -52,10 +99,21 @@ module.exports = {
// 重新设置 alias
config.resolve.alias
.set('@', resolve('src'))
.set('@api', resolve('src/api'))
// node
config.node
.set('__dirname', true)
.set('__filename', true)
// babel-polyfill 加入 entry
const entry = config.entry('app')
entry
.add('babel-polyfill')
.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