no message
Former-commit-id: 13cddc6e80583fea2d48d618c9ff9de4d8e7dfea
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import request from '@/plugin/axios'
|
||||
|
||||
export function httpGet (url, params = {}) {
|
||||
return request({
|
||||
url,
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function httpPost (url, data = {}) {
|
||||
return request({
|
||||
url,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import request from '@/plugin/axios'
|
||||
|
||||
export function AccountLogin (data) {
|
||||
return request({
|
||||
url: '/login',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// markdown 样式补丁
|
||||
|
||||
.markdown-body {
|
||||
ul {
|
||||
list-style: disc;
|
||||
}
|
||||
h1, h2 {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.tree-view-wrapper.tree-view-small {
|
||||
.tree-view-item {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// vue-splitpane 样式补丁
|
||||
|
||||
.vue-grid-item {
|
||||
&.vue-grid-placeholder {
|
||||
border: 1px solid $color-border-1;
|
||||
background-color: rgba(#FFF, .3);
|
||||
opacity: 1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// vue-splitpane 样式补丁
|
||||
|
||||
@import '~@/assets/style/unit/color.scss';
|
||||
|
||||
.splitter-pane-resizer {
|
||||
background-color: $color-border-1 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
@@ -4,14 +4,6 @@
|
||||
@import '~@/assets/style/fixed/base.scss';
|
||||
// 补丁 element
|
||||
@import '~@/assets/style/fixed/element.scss';
|
||||
// 补丁 markdown
|
||||
@import '~@/assets/style/fixed/markdown.scss';
|
||||
// 补丁 vue-splitpane
|
||||
@import '~@/assets/style/fixed/vue-splitpane.scss';
|
||||
// 补丁 vue-grid-layout
|
||||
@import '~@/assets/style/fixed/vue-grid-layout.scss';
|
||||
// 补丁 tree-view
|
||||
@import '~@/assets/style/fixed/tree-view.scss';
|
||||
|
||||
// 动画
|
||||
@import '~@/assets/style/animate/vue-transition.scss';
|
||||
|
||||
@@ -43,8 +43,14 @@
|
||||
padding: 0px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
.d2-theme-container-main-layer {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
.d2-theme-container-main-body {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,13 +29,15 @@
|
||||
%el-menu-icon {
|
||||
i {
|
||||
display: inline-block;
|
||||
width: 15px;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
margin-right: 5px;
|
||||
svg {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
margin: 0px;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.el-submenu__title {
|
||||
@@ -95,6 +97,13 @@
|
||||
}
|
||||
}
|
||||
.el-submenu {
|
||||
&.is-active {
|
||||
.el-submenu__title {
|
||||
color: $theme-header-item-color-active;
|
||||
background: $theme-header-item-background-color-active;
|
||||
i.fa { color: inherit; }
|
||||
}
|
||||
}
|
||||
.el-submenu__title {
|
||||
transition: border-top-color 0s;
|
||||
color: $theme-header-item-color;
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.8 KiB |
@@ -1,5 +1,5 @@
|
||||
|
||||
<svg width="60px" height="54px" viewBox="0 0 60 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<svg viewBox="0 0 60 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<desc>D2Admin</desc>
|
||||
<defs></defs>
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<el-menu-item :index="menu.path || uniqueId">
|
||||
<i v-if="menu.icon" :class="`fa fa-${menu.icon}`"></i>
|
||||
<i v-else-if="menu.iconSvg">
|
||||
<!-- <i v-else-if="menu.iconSvg">
|
||||
<d2-icon-svg :name="menu.iconSvg"/>
|
||||
</i>
|
||||
</i> -->
|
||||
<d2-icon-svg v-else-if="menu.iconSvg" :name="menu.iconSvg"/>
|
||||
<i v-else class="fa fa-file-o"></i>
|
||||
<span slot="title">{{menu.title || '未命名菜单'}}</span>
|
||||
</el-menu-item>
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
<el-submenu :index="menu.path || uniqueId">
|
||||
<template slot="title">
|
||||
<i v-if="menu.icon" :class="`fa fa-${menu.icon}`"></i>
|
||||
<i v-else-if="menu.iconSvg">
|
||||
<!-- <i v-else-if="menu.iconSvg">
|
||||
<d2-icon-svg :name="menu.iconSvg"/>
|
||||
</i>
|
||||
</i> -->
|
||||
<d2-icon-svg v-else-if="menu.iconSvg" :name="menu.iconSvg"/>
|
||||
<i v-else class="fa fa-folder-o"></i>
|
||||
<span slot="title">{{menu.title}}</span>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<el-button class="d2-mr btn-text can-hover" type="text" @click="handleClick">
|
||||
<d2-icon name="search" style="font-size: 18px;"/>
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
handleClick () {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-menu mode="horizontal" @select="handleMenuSelect">
|
||||
<el-menu mode="horizontal" :default-active="active" @select="handleMenuSelect">
|
||||
<template v-for="(menu, menuIndex) in header">
|
||||
<d2-layout-header-aside-menu-item v-if="menu.children === undefined" :menu="menu" :key="menuIndex"/>
|
||||
<d2-layout-header-aside-menu-sub v-else :menu="menu" :key="menuIndex"/>
|
||||
@@ -25,6 +25,19 @@ export default {
|
||||
...mapState('d2admin/menu', [
|
||||
'header'
|
||||
])
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
active: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route.matched': {
|
||||
handler (val) {
|
||||
this.active = val[val.length - 1].path
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@ import util from '@/libs/util.js'
|
||||
export default {
|
||||
methods: {
|
||||
handleMenuSelect (index, indexPath) {
|
||||
if (/^d2-menu-empty-\d+$/.test(index)) {
|
||||
if (/^d2-menu-empty-\d+$/.test(index) || index === undefined) {
|
||||
this.$message.warning('临时菜单')
|
||||
} else if (/^https:\/\/|http:\/\//.test(index)) {
|
||||
util.open(index)
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="d2-panel-search-item" :class="hoverMode ? 'can-hover' : ''" flex>
|
||||
<div class="d2-panel-search-item__icon" flex-box="0">
|
||||
<div class="d2-panel-search-item__icon-box" flex="main:center cross:center">
|
||||
<d2-icon v-if="item.icon" :name="item.icon"/>
|
||||
<d2-icon-svg v-else-if="item.iconSvg" :name="item.iconSvg"/>
|
||||
<d2-icon v-else name="file-o"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d2-panel-search-item__info" flex-box="1" flex="dir:top">
|
||||
<div class="d2-panel-search-item__info-title" flex-box="1" flex="cross:center">
|
||||
<span>{{item.title}}</span>
|
||||
</div>
|
||||
<div class="d2-panel-search-item__info-fullTitle" flex-box="0">
|
||||
<span>{{item.fullTitle}}</span>
|
||||
</div>
|
||||
<div class="d2-panel-search-item__info-path" flex-box="0">
|
||||
<span>{{item.path}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
item: {
|
||||
default: () => ({})
|
||||
},
|
||||
hoverMode: {
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/style/public.scss';
|
||||
.d2-panel-search-item {
|
||||
height: 64px;
|
||||
margin: 0px -20px;
|
||||
&.can-hover {
|
||||
@extend %unable-select;
|
||||
margin: 0px;
|
||||
&:hover {
|
||||
background-color: #F5F7FA;
|
||||
.d2-panel-search-item__icon {
|
||||
.d2-panel-search-item__icon-box {
|
||||
i {
|
||||
font-size: 24px;
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
.d2-panel-search-item__info {
|
||||
.d2-panel-search-item__info-title {
|
||||
color: $color-text-main;
|
||||
}
|
||||
.d2-panel-search-item__info-fullTitle {
|
||||
color: $color-text-normal;
|
||||
}
|
||||
.d2-panel-search-item__info-path {
|
||||
color: $color-text-normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.d2-panel-search-item__icon {
|
||||
width: 64px;
|
||||
.d2-panel-search-item__icon-box {
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
border-right: 1px solid $color-border-3;
|
||||
i {
|
||||
font-size: 20px;
|
||||
color: $color-text-sub;
|
||||
}
|
||||
svg {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.d2-panel-search-item__info {
|
||||
margin-left: 10px;
|
||||
.d2-panel-search-item__info-title {
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
font-weight: bold;
|
||||
color: $color-text-normal;
|
||||
}
|
||||
.d2-panel-search-item__info-fullTitle {
|
||||
font-size: 10px;
|
||||
line-height: 14px;
|
||||
color: $color-text-placehoder;
|
||||
}
|
||||
.d2-panel-search-item__info-path {
|
||||
margin-bottom: 4px;
|
||||
font-size: 10px;
|
||||
line-height: 14px;
|
||||
color: $color-text-placehoder;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div class="panel-search" flex="dir:top">
|
||||
<div class="panel-search__input-group" flex-box="0" flex="dir:top main:center cross:center">
|
||||
<d2-icon-svg
|
||||
class="panel-search__logo"
|
||||
name="d2-admin-text"/>
|
||||
<el-autocomplete
|
||||
class="panel-search__input"
|
||||
ref="input"
|
||||
v-model="searchText"
|
||||
suffix-icon="el-icon-search"
|
||||
placeholder="搜索页面"
|
||||
:fetch-suggestions="querySearch"
|
||||
:trigger-on-focus="false"
|
||||
:clearable="true"
|
||||
@keydown.esc.native="handleEsc"
|
||||
@select="handleSelect">
|
||||
<d2-panel-search-item slot-scope="{ item }" :item="item"/>
|
||||
</el-autocomplete>
|
||||
<div class="panel-search__tip">
|
||||
您可以使用快捷键
|
||||
<span class="panel-search__key">{{hotkey.open}}</span>
|
||||
唤醒搜索面板,按
|
||||
<span class="panel-search__key">{{hotkey.close}}</span>
|
||||
关闭
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="resultsList.length > 0"
|
||||
class="panel-search__results-group"
|
||||
flex-box="1">
|
||||
<el-card>
|
||||
<div class="panel-search__results-group-inner">
|
||||
<d2-panel-search-item
|
||||
v-for="(item, index) in resultsList"
|
||||
:key="index"
|
||||
:item="item"
|
||||
:hover-mode="true"
|
||||
@click.native="handleResultsGroupItemClick(item.path)"/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Fuse from 'fuse.js'
|
||||
import { mapState } from 'vuex'
|
||||
import mixin from '../mixin/menu'
|
||||
export default {
|
||||
mixins: [
|
||||
mixin
|
||||
],
|
||||
components: {
|
||||
'd2-panel-search-item': () => import('./components/panel-search-item/index.vue')
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
searchText: '',
|
||||
results: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('d2admin/search', [
|
||||
'hotkey',
|
||||
'pool'
|
||||
]),
|
||||
// 这份数据是展示在搜索面板下面的
|
||||
resultsList () {
|
||||
return (this.results.length === 0 && this.searchText === '') ? this.pool.map(e => ({
|
||||
value: e.fullTitle,
|
||||
...e
|
||||
})) : this.results
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @description 过滤选项 这个方法在每次输入框的值发生变化时会触发
|
||||
*/
|
||||
querySearch (queryString, callback) {
|
||||
var pool = this.pool
|
||||
const results = this.query(queryString ? pool : [], queryString)
|
||||
this.results = results
|
||||
callback(results)
|
||||
},
|
||||
/**
|
||||
* @description 指定的数据源中根据指定的查询字符串过滤数据
|
||||
* @param {Object} pool 需要过滤的数据
|
||||
* @param {String} queryString 查询字符串
|
||||
*/
|
||||
query (pool, queryString) {
|
||||
return new Fuse(pool, {
|
||||
shouldSort: true,
|
||||
tokenize: true,
|
||||
threshold: 0.6,
|
||||
location: 0,
|
||||
distance: 100,
|
||||
maxPatternLength: 32,
|
||||
minMatchCharLength: 1,
|
||||
keys: [
|
||||
'fullTitle',
|
||||
'path'
|
||||
]
|
||||
})
|
||||
.search(queryString)
|
||||
.map(e => ({
|
||||
value: e.fullTitle,
|
||||
...e
|
||||
}))
|
||||
},
|
||||
/**
|
||||
* @description 聚焦输入框
|
||||
*/
|
||||
focus () {
|
||||
this.input = ''
|
||||
setTimeout(() => {
|
||||
this.$refs.input.focus()
|
||||
// 还原
|
||||
this.searchText = ''
|
||||
this.results = []
|
||||
}, 500)
|
||||
},
|
||||
/**
|
||||
* @description 接收用户在列表中选择项目的事件
|
||||
*/
|
||||
handleResultsGroupItemClick (path) {
|
||||
// 如果用户选择的就是当前页面 就直接关闭搜索面板
|
||||
if (path === this.$route.path) {
|
||||
this.handleEsc()
|
||||
return
|
||||
}
|
||||
// 用户选择的是其它页面
|
||||
this.handleMenuSelect(path)
|
||||
},
|
||||
/**
|
||||
* @description 接收用户在下拉菜单中选中事件
|
||||
*/
|
||||
handleSelect ({ path }) {
|
||||
// 如果用户选择的就是当前页面 就直接关闭搜索面板
|
||||
if (path === this.$route.path) {
|
||||
this.handleEsc()
|
||||
return
|
||||
}
|
||||
// 用户选择的是其它页面
|
||||
this.$nextTick(() => {
|
||||
this.handleMenuSelect(path)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* @augments 关闭输入框的下拉菜单
|
||||
*/
|
||||
closeSuggestion () {
|
||||
if (this.$refs.input.activated) {
|
||||
this.$refs.input.suggestions = []
|
||||
this.$refs.input.activated = false
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @augments 接收用户触发的关闭
|
||||
*/
|
||||
handleEsc () {
|
||||
this.closeSuggestion()
|
||||
this.$nextTick(() => {
|
||||
this.$emit('close')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/style/public.scss';
|
||||
.panel-search {
|
||||
margin: 20px;
|
||||
.panel-search__input-group {
|
||||
height: 240px;
|
||||
.panel-search__logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.panel-search__input {
|
||||
width: 500px;
|
||||
}
|
||||
.panel-search__tip {
|
||||
@extend %unable-select;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 40px;
|
||||
font-size: 12px;
|
||||
color: $color-text-sub;
|
||||
.panel-search__key {
|
||||
padding: 1px 5px;
|
||||
margin: 0px 2px;
|
||||
border-radius: 2px;
|
||||
background-color: $color-text-normal;
|
||||
color: $color-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
.panel-search__results-group {
|
||||
overflow: auto;
|
||||
.panel-search__results-group-inner {
|
||||
margin: -20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -8,7 +8,12 @@
|
||||
<!-- 主体内容 -->
|
||||
<div class="d2-layout-header-aside-content" flex="dir:top">
|
||||
<!-- 顶栏 -->
|
||||
<div class="d2-theme-header" flex-box="0">
|
||||
<div
|
||||
class="d2-theme-header"
|
||||
:style="{
|
||||
opacity: this.searchActive ? 0.3 : 1
|
||||
}"
|
||||
flex-box="0">
|
||||
<div class="logo-group" :style="{width: asideCollapse ? asideWidthCollapse : asideWidth}">
|
||||
<img v-if="asideCollapse" :src="`${$baseUrl}image/theme/${themeActiveSetting.name}/logo/icon-only.png`">
|
||||
<img v-else :src="`${$baseUrl}image/theme/${themeActiveSetting.name}/logo/all.png`">
|
||||
@@ -20,6 +25,7 @@
|
||||
<!-- 顶栏右侧 -->
|
||||
<div class="d2-header-right">
|
||||
<!-- 如果你只想在开发环境显示这个按钮请添加 v-if="$env === 'development'" -->
|
||||
<d2-header-search @click="handleSearchClick"/>
|
||||
<d2-header-error-log/>
|
||||
<d2-header-fullscreen/>
|
||||
<d2-header-theme/>
|
||||
@@ -33,21 +39,39 @@
|
||||
flex-box="0"
|
||||
ref="aside"
|
||||
class="d2-theme-container-aside"
|
||||
:style="{width: asideCollapse ? asideWidthCollapse : asideWidth}">
|
||||
:style="{
|
||||
width: asideCollapse ? asideWidthCollapse : asideWidth,
|
||||
opacity: this.searchActive ? 0.3 : 1
|
||||
}">
|
||||
<d2-menu-side/>
|
||||
</div>
|
||||
<!-- 主体 -->
|
||||
<div class="d2-theme-container-main" flex-box="1" flex="dir:top">
|
||||
<div class="d2-theme-container-main-header" flex-box="0">
|
||||
<d2-tabs/>
|
||||
</div>
|
||||
<div class="d2-theme-container-main-body" flex-box="1">
|
||||
<transition :name="transitionActive ? 'fade-transverse' : ''">
|
||||
<keep-alive :include="keepAlive">
|
||||
<router-view/>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</div>
|
||||
<div class="d2-theme-container-main" flex-box="1" flex>
|
||||
<!-- 搜索 -->
|
||||
<transition name="fade-scale">
|
||||
<div v-show="searchActive" class="d2-theme-container-main-layer" flex="dir:top">
|
||||
<d2-panel-search
|
||||
ref="panelSearch"
|
||||
@close="searchPanelClose"/>
|
||||
</div>
|
||||
</transition>
|
||||
<!-- 内容 -->
|
||||
<transition name="fade-scale">
|
||||
<div v-show="!searchActive" class="d2-theme-container-main-layer" flex="dir:top">
|
||||
<!-- tab -->
|
||||
<div class="d2-theme-container-main-header" flex-box="0">
|
||||
<d2-tabs/>
|
||||
</div>
|
||||
<!-- 页面 -->
|
||||
<div class="d2-theme-container-main-body" flex-box="1">
|
||||
<transition :name="transitionActive ? 'fade-transverse' : ''">
|
||||
<keep-alive :include="keepAlive">
|
||||
<router-view/>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,13 +80,18 @@
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters, mapMutations } from 'vuex'
|
||||
import mixinSearch from './mixins/search'
|
||||
export default {
|
||||
name: 'd2-layout-header-aside',
|
||||
mixins: [
|
||||
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-theme': () => import('./components/header-theme'),
|
||||
'd2-header-user': () => import('./components/header-user'),
|
||||
'd2-header-error-log': () => import('./components/header-error-log')
|
||||
@@ -97,14 +126,14 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('d2admin/menu', [
|
||||
'asideCollapseToggle'
|
||||
]),
|
||||
...mapMutations({
|
||||
menuAsideCollapseToggle: 'd2admin/menu/asideCollapseToggle'
|
||||
}),
|
||||
/**
|
||||
* 接收点击切换侧边栏的按钮
|
||||
*/
|
||||
handleToggleAside () {
|
||||
this.asideCollapseToggle()
|
||||
this.menuAsideCollapseToggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
|
||||
import hotkeys from 'hotkeys-js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
'd2-panel-search': () => import('../components/panel-search')
|
||||
},
|
||||
mounted () {
|
||||
// 绑定搜索功能快捷键 [ 打开 ]
|
||||
hotkeys(this.searchHotkey.open, event => {
|
||||
event.preventDefault()
|
||||
this.searchPanelOpen()
|
||||
})
|
||||
// 绑定搜索功能快捷键 [ 关闭 ]
|
||||
hotkeys(this.searchHotkey.close, event => {
|
||||
event.preventDefault()
|
||||
this.searchPanelClose()
|
||||
})
|
||||
},
|
||||
beforeDestroy () {
|
||||
hotkeys.unbind(this.searchHotkey.open)
|
||||
hotkeys.unbind(this.searchHotkey.close)
|
||||
},
|
||||
computed: {
|
||||
...mapState('d2admin', {
|
||||
searchActive: state => state.search.active,
|
||||
searchHotkey: state => state.search.hotkey
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({
|
||||
searchToggle: 'd2admin/search/toggle',
|
||||
searchSet: 'd2admin/search/set'
|
||||
}),
|
||||
/**
|
||||
* 接收点击搜索按钮
|
||||
*/
|
||||
handleSearchClick () {
|
||||
this.searchToggle()
|
||||
if (this.searchActive) {
|
||||
this.$refs.panelSearch.focus()
|
||||
}
|
||||
},
|
||||
searchPanelOpen () {
|
||||
if (!this.searchActive) {
|
||||
this.searchSet(true)
|
||||
this.$refs.panelSearch.focus()
|
||||
}
|
||||
},
|
||||
// 关闭搜索面板
|
||||
searchPanelClose () {
|
||||
if (this.searchActive) {
|
||||
this.searchSet(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -33,13 +33,15 @@ new Vue({
|
||||
this.$store.commit('d2admin/menu/headerSet', menuHeader)
|
||||
// 设置侧边栏菜单
|
||||
this.$store.commit('d2admin/menu/asideSet', menuAside)
|
||||
// 初始化菜单搜索功能
|
||||
this.$store.commit('d2admin/search/init', menuHeader)
|
||||
},
|
||||
mounted () {
|
||||
// 展示系统信息
|
||||
this.$store.commit('d2admin/releases/versionShow')
|
||||
// 检查最新版本
|
||||
this.$store.dispatch('d2admin/releases/checkUpdate')
|
||||
// 用户登陆后从数据库加载一系列的设置
|
||||
// 用户登录后从数据库加载一系列的设置
|
||||
this.$store.commit('d2admin/account/load')
|
||||
// 获取并记录用户 UA
|
||||
this.$store.commit('d2admin/ua/get')
|
||||
|
||||
@@ -21,13 +21,13 @@ const userDB = [
|
||||
}
|
||||
]
|
||||
|
||||
Mock.mock('/login', 'post', ({url, type, body}) => {
|
||||
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: '登陆成功',
|
||||
msg: '登录成功',
|
||||
data: {
|
||||
...user,
|
||||
token: 'd787syv8dys8cas80d9s0a0d8f79ads56f7s4d56f879a8as89fd980s7dg'
|
||||
@@ -36,7 +36,8 @@ Mock.mock('/login', 'post', ({url, type, body}) => {
|
||||
} else {
|
||||
return {
|
||||
code: 401,
|
||||
msg: '用户名或密码错误'
|
||||
msg: '用户名或密码错误',
|
||||
data: {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<d2-icon name="github" class="d2-mr-5"/>
|
||||
主页
|
||||
</el-button>
|
||||
<el-button @click="$open('http://d2admin.fairyever.com/zh/')">
|
||||
<el-button @click="$open('https://d2-projects.github.io/d2-admin-doc/zh/')">
|
||||
<d2-icon name="book" class="d2-mr-5"/>
|
||||
中文文档
|
||||
</el-button>
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<d2-page-cover
|
||||
title="I AM D2ADMIN"
|
||||
sub-title="追求简约美感的后台管理系统集成方案">
|
||||
<d2-icon-svg style="width: 120px;" name="d2admin"/>
|
||||
<d2-icon-svg style="width: 120px;" name="d2-admin"/>
|
||||
<template slot="footer">
|
||||
<div class="page__btn-group">
|
||||
<span @click="$open('https://github.com/d2-projects')">项目组</span> |
|
||||
<span @click="$open('http://d2admin.fairyever.com/zh/')">使用文档</span> |
|
||||
<span @click="$open('https://d2-projects.github.io/d2-admin-doc/zh/')">使用文档</span> |
|
||||
<span @click="$open('https://github.com/d2-projects/d2-admin-start-kit')">简化版脚手架</span> |
|
||||
<span @click="$open('https://alibaba.github.io/ice/scaffold?type=vue')">飞冰物料</span> |
|
||||
<span @click="$open('https://juejin.im/user/57a48b632e958a006691b946/posts')">掘金专栏</span> |
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
<!-- 快速登陆按钮 -->
|
||||
<!-- 快速登录按钮 -->
|
||||
<el-button type="info" class="button-help" @click="dialogVisible = true">
|
||||
快速选择用户(测试功能)
|
||||
</el-button>
|
||||
@@ -118,7 +118,7 @@ export default {
|
||||
'login'
|
||||
]),
|
||||
/**
|
||||
* @description 接收选择一个用户快速登陆的事件
|
||||
* @description 接收选择一个用户快速登录的事件
|
||||
* @param {Object} user 用户信息
|
||||
*/
|
||||
handleUserBtnClick (user) {
|
||||
@@ -129,11 +129,11 @@ export default {
|
||||
/**
|
||||
* @description 提交表单
|
||||
*/
|
||||
// 提交登陆信息
|
||||
// 提交登录信息
|
||||
submit () {
|
||||
this.$refs.loginForm.validate((valid) => {
|
||||
if (valid) {
|
||||
// 登陆
|
||||
// 登录
|
||||
// 注意 这里的演示没有传验证码
|
||||
// 具体需要传递的数据请自行修改代码
|
||||
this.login({
|
||||
@@ -142,7 +142,7 @@ export default {
|
||||
password: this.formLogin.password
|
||||
})
|
||||
} else {
|
||||
// 登陆表单校验失败
|
||||
// 登录表单校验失败
|
||||
this.$message.error('表单校验失败')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
height: 140px;
|
||||
}
|
||||
}
|
||||
// 登陆表单
|
||||
// 登录表单
|
||||
.form-group {
|
||||
width: 300px;
|
||||
// 重新设置卡片阴影
|
||||
@@ -42,7 +42,7 @@
|
||||
padding-top: 70px;
|
||||
}
|
||||
}
|
||||
// 登陆按钮
|
||||
// 登录按钮
|
||||
.button-login {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
+105
-8
@@ -1,13 +1,110 @@
|
||||
import store from '@/store'
|
||||
import axios from 'axios'
|
||||
import { Message } from 'element-ui'
|
||||
import util from '@/libs/util'
|
||||
|
||||
axios.interceptors.response.use(res => {
|
||||
return res.data
|
||||
}, err => {
|
||||
return Promise.reject(err)
|
||||
// 创建一个错误
|
||||
function errorCreat (msg) {
|
||||
const err = new Error(msg)
|
||||
errorLog(err)
|
||||
throw err
|
||||
}
|
||||
|
||||
// 记录和显示错误
|
||||
function errorLog (err) {
|
||||
// 添加到日志
|
||||
store.dispatch('d2admin/log/add', {
|
||||
type: 'error',
|
||||
err,
|
||||
info: '数据请求异常'
|
||||
})
|
||||
// 打印到控制台
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
util.log.danger('>>>>>> Error >>>>>>')
|
||||
console.log(err)
|
||||
}
|
||||
// 显示提示
|
||||
Message({
|
||||
message: err.message,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
}
|
||||
|
||||
// 创建一个 axios 实例
|
||||
const service = axios.create({
|
||||
baseURL: process.env.VUE_APP_API,
|
||||
timeout: 5000 // 请求超时时间
|
||||
})
|
||||
|
||||
export default {
|
||||
install (Vue, options) {
|
||||
Vue.prototype.$axios = axios
|
||||
// 请求拦截器
|
||||
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 => {
|
||||
// 发送失败
|
||||
console.log(error)
|
||||
Promise.reject(error)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
// dataAxios 是 axios 返回数据中的 data
|
||||
const dataAxios = response.data
|
||||
// 这个状态码是和后端约定的
|
||||
const { code } = dataAxios
|
||||
// 根据 code 进行判断
|
||||
if (code === undefined) {
|
||||
// 如果没有 code 代表这不是项目后端开发的接口 比如可能是 D2Admin 请求最新版本
|
||||
return dataAxios
|
||||
} else {
|
||||
// 有 code 代表这是一个后端接口 可以进行进一步的判断
|
||||
switch (code) {
|
||||
case 0:
|
||||
// [ 示例 ] code === 0 代表没有错误
|
||||
return dataAxios.data
|
||||
case 'xxx':
|
||||
// [ 示例 ] 其它和后台约定的 code
|
||||
errorCreat(`[ code: xxx ] ${dataAxios.msg}: ${response.config.url}`)
|
||||
break
|
||||
default:
|
||||
// 不是正确的 code
|
||||
errorCreat(`${dataAxios.msg}: ${response.config.url}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
error => {
|
||||
if (error && error.response) {
|
||||
switch (error.response.status) {
|
||||
case 400: error.message = '请求错误'; break
|
||||
case 401: error.message = '未授权,请登录'; break
|
||||
case 403: error.message = '拒绝访问'; break
|
||||
case 404: error.message = `请求地址出错: ${error.response.config.url}`; break
|
||||
case 408: error.message = '请求超时'; break
|
||||
case 500: error.message = '服务器内部错误'; break
|
||||
case 501: error.message = '服务未实现'; break
|
||||
case 502: error.message = '网关错误'; break
|
||||
case 503: error.message = '服务不可用'; break
|
||||
case 504: error.message = '网关超时'; break
|
||||
case 505: error.message = 'HTTP版本不受支持'; break
|
||||
default: break
|
||||
}
|
||||
}
|
||||
errorLog(error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default service
|
||||
|
||||
@@ -8,7 +8,6 @@ import '@/components'
|
||||
// svg 图标
|
||||
import '@/assets/svg-icons'
|
||||
// 功能插件
|
||||
import pluginAxios from '@/plugin/axios'
|
||||
import pluginError from '@/plugin/error'
|
||||
import pluginLog from '@/plugin/log'
|
||||
import pluginOpen from '@/plugin/open'
|
||||
@@ -18,7 +17,6 @@ export default {
|
||||
// Element
|
||||
Vue.use(ElementUI)
|
||||
// 插件
|
||||
Vue.use(pluginAxios)
|
||||
Vue.use(pluginError)
|
||||
Vue.use(pluginLog)
|
||||
Vue.use(pluginOpen)
|
||||
|
||||
+10
-3
@@ -1,6 +1,8 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
|
||||
import store from '@/store/index'
|
||||
|
||||
import util from '@/libs/util.js'
|
||||
|
||||
// 路由数据
|
||||
@@ -18,15 +20,20 @@ const router = new VueRouter({
|
||||
* 权限验证
|
||||
*/
|
||||
router.beforeEach((to, from, next) => {
|
||||
// 验证当前路由所有的匹配中是否需要有登陆验证的
|
||||
// 关闭搜索面板
|
||||
store.commit('d2admin/search/set', false)
|
||||
// 验证当前路由所有的匹配中是否需要有登录验证的
|
||||
if (to.matched.some(r => r.meta.requiresAuth)) {
|
||||
// 这里暂时将cookie里是否存有token作为验证是否登陆的条件
|
||||
// 这里暂时将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'
|
||||
})
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { version } from '../package'
|
||||
|
||||
const setting = {
|
||||
// 快捷键
|
||||
// 支持快捷键 例如 ctrl+shift+s
|
||||
hotkey: {
|
||||
search: {
|
||||
open: 's',
|
||||
close: 'esc'
|
||||
}
|
||||
},
|
||||
// 侧边栏默认折叠状态
|
||||
menu: {
|
||||
asideCollapse: false
|
||||
@@ -22,6 +30,10 @@ const setting = {
|
||||
version: version,
|
||||
api: 'https://api.github.com/repos/FairyEver/d2-admin/releases/latest'
|
||||
},
|
||||
// 菜单搜索
|
||||
search: {
|
||||
enable: true
|
||||
},
|
||||
// 注册的主题
|
||||
theme: {
|
||||
list: [
|
||||
|
||||
@@ -10,6 +10,7 @@ import ua from './modules/ua'
|
||||
import gray from './modules/gray'
|
||||
import page from './modules/page'
|
||||
import transition from './modules/transition'
|
||||
import search from './modules/search'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
@@ -25,6 +26,7 @@ export default {
|
||||
ua,
|
||||
gray,
|
||||
page,
|
||||
transition
|
||||
transition,
|
||||
search
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
import util from '@/libs/util.js'
|
||||
import { AccountLogin } from '@/api/sys/login'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
actions: {
|
||||
/**
|
||||
* @description 登陆
|
||||
* @description 登录
|
||||
* @param {Object} param context
|
||||
* @param {Object} param vm {Object} vue 实例
|
||||
* @param {Object} param username {String} 用户账号
|
||||
* @param {Object} param password {String} 密码
|
||||
* @param {Object} param route {Object} 登录成功后定向的路由对象
|
||||
*/
|
||||
login ({ commit }, { vm, username, password }) {
|
||||
login ({
|
||||
commit
|
||||
}, {
|
||||
vm,
|
||||
username,
|
||||
password,
|
||||
route = {
|
||||
name: 'index'
|
||||
}
|
||||
}) {
|
||||
// 开始请求登录接口
|
||||
vm.$axios({
|
||||
method: 'post',
|
||||
url: '/login',
|
||||
data: {
|
||||
username,
|
||||
password
|
||||
}
|
||||
AccountLogin({
|
||||
username,
|
||||
password
|
||||
})
|
||||
.then(res => {
|
||||
// 设置 cookie 一定要存 uuid 和 token 两个 cookie
|
||||
@@ -26,27 +33,29 @@ export default {
|
||||
// uuid 是用户身份唯一标识 用户注册的时候确定 并且不可改变 不可重复
|
||||
// token 代表用户当前登录状态 建议在网络请求中携带 token
|
||||
// 如有必要 token 需要定时更新,默认保存一天
|
||||
util.cookies.set('uuid', res.data.uuid)
|
||||
util.cookies.set('token', res.data.token)
|
||||
util.cookies.set('uuid', res.uuid)
|
||||
util.cookies.set('token', res.token)
|
||||
// 设置 vuex 用户信息
|
||||
commit('d2admin/user/set', {
|
||||
name: res.data.name
|
||||
name: res.name
|
||||
}, { root: true })
|
||||
// 用户登陆后从持久化数据加载一系列的设置
|
||||
// 用户登录后从持久化数据加载一系列的设置
|
||||
commit('load')
|
||||
// 跳转路由
|
||||
vm.$router.push({
|
||||
name: 'index'
|
||||
})
|
||||
// 更新路由 尝试去获取 cookie 里保存的需要重定向的页面完整地址
|
||||
const path = util.cookies.get('redirect')
|
||||
// 根据是否存有重定向页面判断如何重定向
|
||||
vm.$router.replace(path ? { path } : route)
|
||||
// 删除 cookie 中保存的重定向页面
|
||||
util.cookies.remove('redirect')
|
||||
})
|
||||
.catch(err => {
|
||||
console.group('登陆结果')
|
||||
console.group('登录结果')
|
||||
console.log('err: ', err)
|
||||
console.groupEnd()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* @description 注销用户并返回登陆页面
|
||||
* @description 注销用户并返回登录页面
|
||||
* @param {Object} param context
|
||||
* @param {Object} param vm {Object} vue 实例
|
||||
* @param {Object} param confirm {Boolean} 是否需要确认
|
||||
@@ -87,7 +96,7 @@ export default {
|
||||
},
|
||||
mutations: {
|
||||
/**
|
||||
* @description 用户登陆后从持久化数据加载一系列的设置
|
||||
* @description 用户登录后从持久化数据加载一系列的设置
|
||||
* @param {Object} state vuex state
|
||||
*/
|
||||
load (state) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import axios from 'axios'
|
||||
import { httpGet } from '@/api/sys/http'
|
||||
import semver from 'semver'
|
||||
import util from '@/libs/util.js'
|
||||
import setting from '@/setting.js'
|
||||
@@ -19,7 +19,7 @@ export default {
|
||||
* @param {Object} param context
|
||||
*/
|
||||
checkUpdate ({ state, commit }) {
|
||||
axios.get(setting.releases.api)
|
||||
httpGet(setting.releases.api)
|
||||
.then(res => {
|
||||
let versionGet = res.tag_name
|
||||
const update = semver.lt(state.version, versionGet)
|
||||
@@ -43,7 +43,7 @@ export default {
|
||||
versionShow (state) {
|
||||
util.log.capsule('D2Admin', `v${state.version}`)
|
||||
console.log('Github https://github.com/d2-projects/d2-admin')
|
||||
console.log('Doc http://d2admin.fairyever.com/zh/')
|
||||
console.log('Doc https://d2-projects.github.io/d2-admin-doc/zh/')
|
||||
},
|
||||
/**
|
||||
* @description 设置是否有新的 D2Admin 版本
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import setting from '@/setting.js'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
// 搜索面板激活状态
|
||||
active: false,
|
||||
// 快捷键
|
||||
hotkey: {
|
||||
open: setting.hotkey.search.open,
|
||||
close: setting.hotkey.search.close
|
||||
},
|
||||
// 所有可以搜索的页面
|
||||
pool: []
|
||||
},
|
||||
mutations: {
|
||||
/**
|
||||
* @description 切换激活状态
|
||||
* @param {Object} state vuex state
|
||||
*/
|
||||
toggle (state) {
|
||||
state.active = !state.active
|
||||
},
|
||||
/**
|
||||
* @description 设置激活模式
|
||||
* @param {Object} state vuex state
|
||||
* @param {Boolean} active active
|
||||
*/
|
||||
set (state, active) {
|
||||
state.active = active
|
||||
},
|
||||
/**
|
||||
* @description 初始化
|
||||
* @param {Object} state vuex state
|
||||
* @param {Array} menu menu
|
||||
*/
|
||||
init (state, menu) {
|
||||
const pool = []
|
||||
const push = function (menu, titlePrefix = []) {
|
||||
menu.forEach(m => {
|
||||
if (m.children) {
|
||||
push(m.children, [ ...titlePrefix, m.title ])
|
||||
} else {
|
||||
pool.push({
|
||||
...m,
|
||||
fullTitle: [ ...titlePrefix, m.title ].join(' / ')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
push(menu)
|
||||
state.pool = pool
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user