mirror of
https://gitee.com/chu1204505056/vue-admin-beautiful.git
synced 2025-05-20 19:39:21 +08:00
🚀 feat: add layouts
This commit is contained in:
parent
349e4c8902
commit
ae66791f74
7
.eslintignore
Normal file
7
.eslintignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
src/assets
|
||||||
|
src/icons
|
||||||
|
public
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
vab-icon
|
||||||
|
layouts
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -17,7 +17,7 @@ yarn-error.log*
|
|||||||
public/video
|
public/video
|
||||||
*.zip
|
*.zip
|
||||||
*.7z
|
*.7z
|
||||||
/src/layouts/components/zx-layouts
|
/src/layouts/components/layouts
|
||||||
/zx-templates
|
/zx-templates
|
||||||
/package-lock.json
|
/package-lock.json
|
||||||
/src/styles/themes/green.scss
|
/src/styles/themes/green.scss
|
||||||
|
13
layouts/Permissions/index.js
Normal file
13
layouts/Permissions/index.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import permissions from './permissions'
|
||||||
|
|
||||||
|
const install = function (Vue) {
|
||||||
|
Vue.directive('permissions', permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.Vue) {
|
||||||
|
window['permissions'] = permissions
|
||||||
|
Vue.use(install)
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions.install = install
|
||||||
|
export default permissions
|
13
layouts/Permissions/permissions.js
Normal file
13
layouts/Permissions/permissions.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import store from '@/store'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inserted(element, binding) {
|
||||||
|
const { value } = binding
|
||||||
|
const permissions = store.getters['user/permissions']
|
||||||
|
if (value && value instanceof Array && value.length > 0) {
|
||||||
|
const hasPermission = permissions.some((role) => value.includes(role))
|
||||||
|
if (!hasPermission)
|
||||||
|
element.parentNode && element.parentNode.removeChild(element)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
65
layouts/VabColorfullIcon/index.vue
Normal file
65
layouts/VabColorfullIcon/index.vue
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<img
|
||||||
|
v-if="isExternal"
|
||||||
|
:src="styleExternalIcon"
|
||||||
|
class="svg-external-icon svg-icon"
|
||||||
|
v-on="$listeners"
|
||||||
|
/>
|
||||||
|
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
|
||||||
|
<use :xlink:href="iconName" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { isExternal } from '@/utils/validate'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VabColorfulIcon',
|
||||||
|
props: {
|
||||||
|
iconClass: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isExternal() {
|
||||||
|
return isExternal(this.iconClass)
|
||||||
|
},
|
||||||
|
iconName() {
|
||||||
|
return `#colorful-icon-${this.iconClass}`
|
||||||
|
},
|
||||||
|
svgClass() {
|
||||||
|
if (this.className) {
|
||||||
|
return 'svg-icon ' + this.className
|
||||||
|
} else {
|
||||||
|
return 'svg-icon'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
styleExternalIcon() {
|
||||||
|
return this.iconClass
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.svg-icon {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
overflow: hidden;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
fill: currentColor;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-external-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
</style>
|
128
layouts/VabErrorLog/index.vue
Normal file
128
layouts/VabErrorLog/index.vue
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="errorLogs.length > 0">
|
||||||
|
<el-badge
|
||||||
|
:value="errorLogs.length"
|
||||||
|
@click.native="dialogTableVisible = true"
|
||||||
|
>
|
||||||
|
<el-button type="danger">
|
||||||
|
<vab-icon :icon="['fas', 'bug']" />
|
||||||
|
</el-button>
|
||||||
|
</el-badge>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
:visible.sync="dialogTableVisible"
|
||||||
|
append-to-body
|
||||||
|
width="70%"
|
||||||
|
title="vue-admin-beautiful异常捕获(温馨提示:错误必须解决)"
|
||||||
|
>
|
||||||
|
<el-table :data="errorLogs">
|
||||||
|
<el-table-column label="报错路由">
|
||||||
|
<template slot-scope="{ row }">
|
||||||
|
<a :href="row.url" target="_blank">
|
||||||
|
<el-tag type="success">{{ row.url }}</el-tag>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="错误信息">
|
||||||
|
<template slot-scope="{ row }">
|
||||||
|
<el-tag type="danger">{{ decodeUnicode(row.err.message) }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="错误详情" width="120">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-popover placement="top-start" trigger="hover">
|
||||||
|
<div style="color: red">
|
||||||
|
{{ scope.row.err.stack }}
|
||||||
|
</div>
|
||||||
|
<el-button slot="reference">查看</el-button>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column width="380" label="操作">
|
||||||
|
<template slot-scope="{ row }">
|
||||||
|
<a
|
||||||
|
v-for="(item, index) in searchList"
|
||||||
|
:key="index"
|
||||||
|
:href="item.url + decodeUnicode(row.err.message)"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<el-button style="margin-left: 5px" type="primary">
|
||||||
|
<vab-icon :icon="['fas', 'search']" />
|
||||||
|
{{ item.title }}
|
||||||
|
</el-button>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<span slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="dialogTableVisible = false">取 消</el-button>
|
||||||
|
<el-button type="danger" icon="el-icon-delete" @click="clearAll">
|
||||||
|
暂不显示
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { abbreviation, title } from '@/config'
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VabErrorLog',
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dialogTableVisible: false,
|
||||||
|
title: title,
|
||||||
|
abbreviation: abbreviation,
|
||||||
|
searchList: [
|
||||||
|
{
|
||||||
|
title: '百度搜索',
|
||||||
|
url: 'https://www.baidu.com/baidu?wd=',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '谷歌搜索',
|
||||||
|
url: 'https://www.google.com/search?q=',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Magi搜索',
|
||||||
|
url: 'https://magi.com/search?q=',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
errorLogs: 'errorLog/errorLogs',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clearAll() {
|
||||||
|
this.dialogTableVisible = false
|
||||||
|
this.$store.dispatch('errorLog/clearErrorLog')
|
||||||
|
},
|
||||||
|
decodeUnicode(str) {
|
||||||
|
str = str.replace(/\\/g, '%')
|
||||||
|
str = unescape(str)
|
||||||
|
str = str.replace(/%/g, '\\')
|
||||||
|
str = str.replace(/\\/g, '')
|
||||||
|
return str
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
::v-deep {
|
||||||
|
.el-badge {
|
||||||
|
.el-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
53
layouts/VabFullScreenBar/index.vue
Normal file
53
layouts/VabFullScreenBar/index.vue
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<span :title="isFullscreen ? '退出全屏' : '进入全屏'">
|
||||||
|
<vab-icon
|
||||||
|
:icon="[
|
||||||
|
'fas',
|
||||||
|
isFullscreen ? 'compress-arrows-alt' : 'expand-arrows-alt',
|
||||||
|
]"
|
||||||
|
@click="click"
|
||||||
|
></vab-icon>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import screenfull from 'screenfull'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VabFullScreenBar',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isFullscreen: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.destroy()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
click() {
|
||||||
|
if (!screenfull.isEnabled) {
|
||||||
|
this.$baseMessage('开启全屏失败', 'error')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
screenfull.toggle()
|
||||||
|
this.$emit('refresh')
|
||||||
|
},
|
||||||
|
change() {
|
||||||
|
this.isFullscreen = screenfull.isFullscreen
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (screenfull.isEnabled) {
|
||||||
|
screenfull.on('change', this.change)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (screenfull.isEnabled) {
|
||||||
|
screenfull.off('change', this.change)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
75
layouts/VabGithubCorner/index.vue
Normal file
75
layouts/VabGithubCorner/index.vue
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<a
|
||||||
|
href="https://github.com/chuzhixin/vue-admin-beautiful"
|
||||||
|
target="_blank"
|
||||||
|
class="github-corner"
|
||||||
|
aria-label="View source on Github"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="80"
|
||||||
|
height="80"
|
||||||
|
viewBox="0 0 250 250"
|
||||||
|
class="github-color"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
|
||||||
|
<path
|
||||||
|
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
|
||||||
|
fill="currentColor"
|
||||||
|
style="transform-origin: 130px 106px"
|
||||||
|
class="octo-arm"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
|
||||||
|
fill="currentColor"
|
||||||
|
class="octo-body"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'VabGithubCorner',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.github-corner {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: $base-z-index - 3;
|
||||||
|
|
||||||
|
.octo-arm {
|
||||||
|
animation: octocat-wave 560ms ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.octo-arm {
|
||||||
|
animation: octocat-wave 560ms ease-in-out infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-color {
|
||||||
|
color: #fff;
|
||||||
|
fill: $base-color-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes octocat-wave {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
20%,
|
||||||
|
60% {
|
||||||
|
transform: rotate(-25deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
40%,
|
||||||
|
80% {
|
||||||
|
transform: rotate(100deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
20
layouts/VabQueryForm/VabQueryFormBottomPanel.vue
Normal file
20
layouts/VabQueryForm/VabQueryFormBottomPanel.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<el-col :span="24">
|
||||||
|
<div class="bottom-panel">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "VabQueryFormBottomPanel",
|
||||||
|
props: {},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
created() {},
|
||||||
|
mounted() {},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
25
layouts/VabQueryForm/VabQueryFormLeftPanel.vue
Normal file
25
layouts/VabQueryForm/VabQueryFormLeftPanel.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<el-col :xs="24" :sm="24" :md="24" :lg="span" :xl="span">
|
||||||
|
<div class="left-panel">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'VabQueryFormLeftPanel',
|
||||||
|
props: {
|
||||||
|
span: {
|
||||||
|
type: Number,
|
||||||
|
default: 14,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {},
|
||||||
|
mounted() {},
|
||||||
|
methods: {},
|
||||||
|
}
|
||||||
|
</script>
|
25
layouts/VabQueryForm/VabQueryFormRightPanel.vue
Normal file
25
layouts/VabQueryForm/VabQueryFormRightPanel.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<el-col :xs="24" :sm="24" :md="24" :lg="span" :xl="span">
|
||||||
|
<div class="right-panel">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'VabQueryFormRightPanel',
|
||||||
|
props: {
|
||||||
|
span: {
|
||||||
|
type: Number,
|
||||||
|
default: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {},
|
||||||
|
mounted() {},
|
||||||
|
methods: {},
|
||||||
|
}
|
||||||
|
</script>
|
20
layouts/VabQueryForm/VabQueryFormTopPanel.vue
Normal file
20
layouts/VabQueryForm/VabQueryFormTopPanel.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<el-col :span="24">
|
||||||
|
<div class="top-panel">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'VabQueryFormTopPanel',
|
||||||
|
props: {},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {},
|
||||||
|
mounted() {},
|
||||||
|
methods: {},
|
||||||
|
}
|
||||||
|
</script>
|
63
layouts/VabQueryForm/index.vue
Normal file
63
layouts/VabQueryForm/index.vue
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<el-row :gutter="0" class="vab-query-form">
|
||||||
|
<slot></slot>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'VabQueryForm',
|
||||||
|
props: {},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {},
|
||||||
|
mounted() {},
|
||||||
|
methods: {},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@mixin panel {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vab-query-form {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
.top-panel {
|
||||||
|
@include panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-panel {
|
||||||
|
@include panel;
|
||||||
|
|
||||||
|
padding-top: 14px;
|
||||||
|
border-top: 1px solid #dcdfe6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel {
|
||||||
|
@include panel;
|
||||||
|
|
||||||
|
> .el-button,
|
||||||
|
.el-form-item {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel {
|
||||||
|
@include panel;
|
||||||
|
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.el-form-item {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
69
layouts/VabRemixIcon/index.vue
Normal file
69
layouts/VabRemixIcon/index.vue
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="isExternal"
|
||||||
|
:style="styleExternalIcon"
|
||||||
|
class="svg-external-icon svg-icon"
|
||||||
|
v-on="$listeners"
|
||||||
|
/>
|
||||||
|
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
|
||||||
|
<use :xlink:href="iconName" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { isExternal } from '@/utils/validate'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VabRemixIcon',
|
||||||
|
props: {
|
||||||
|
iconClass: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isExternal() {
|
||||||
|
return isExternal(this.iconClass)
|
||||||
|
},
|
||||||
|
iconName() {
|
||||||
|
return `#remix-icon-${this.iconClass}`
|
||||||
|
},
|
||||||
|
svgClass() {
|
||||||
|
if (this.className) {
|
||||||
|
return 'svg-icon ' + this.className
|
||||||
|
} else {
|
||||||
|
return 'svg-icon'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
styleExternalIcon() {
|
||||||
|
return {
|
||||||
|
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
|
||||||
|
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.svg-icon {
|
||||||
|
width: 1.125em;
|
||||||
|
height: 1.125em;
|
||||||
|
overflow: hidden;
|
||||||
|
fill: currentColor;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-external-icon {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: currentColor;
|
||||||
|
mask-size: cover !important;
|
||||||
|
}
|
||||||
|
</style>
|
84
layouts/VabSideBar/components/VabMenuItem.vue
Normal file
84
layouts/VabSideBar/components/VabMenuItem.vue
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<el-menu-item :index="handlePath(routeChildren.path)" @click="handleLink">
|
||||||
|
<vab-icon
|
||||||
|
v-if="routeChildren.meta.icon"
|
||||||
|
:icon="['fas', routeChildren.meta.icon]"
|
||||||
|
class="vab-fas-icon"
|
||||||
|
/>
|
||||||
|
<span>{{ routeChildren.meta.title }}</span>
|
||||||
|
<el-tag
|
||||||
|
v-if="routeChildren.meta && routeChildren.meta.badge"
|
||||||
|
type="danger"
|
||||||
|
effect="dark"
|
||||||
|
>
|
||||||
|
{{ routeChildren.meta.badge }}
|
||||||
|
</el-tag>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { isExternal } from '@/utils/validate'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VabMenuItem',
|
||||||
|
props: {
|
||||||
|
routeChildren: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fullPath: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handlePath(routePath) {
|
||||||
|
if (isExternal(routePath)) {
|
||||||
|
return routePath
|
||||||
|
}
|
||||||
|
if (isExternal(this.fullPath)) {
|
||||||
|
return this.fullPath
|
||||||
|
}
|
||||||
|
return path.resolve(this.fullPath, routePath)
|
||||||
|
},
|
||||||
|
handleLink() {
|
||||||
|
const routePath = this.routeChildren.path
|
||||||
|
const target = this.routeChildren.meta.target
|
||||||
|
|
||||||
|
if (target === '_blank') {
|
||||||
|
if (isExternal(routePath)) {
|
||||||
|
window.open(routePath)
|
||||||
|
} else if (isExternal(this.fullPath)) {
|
||||||
|
window.open(this.fullPath)
|
||||||
|
} else if (
|
||||||
|
this.$route.path !== path.resolve(this.fullPath, routePath)
|
||||||
|
) {
|
||||||
|
let routeData = this.$router.resolve(
|
||||||
|
path.resolve(this.fullPath, routePath)
|
||||||
|
)
|
||||||
|
window.open(routeData.href)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isExternal(routePath)) {
|
||||||
|
window.location.href = routePath
|
||||||
|
} else if (isExternal(this.fullPath)) {
|
||||||
|
window.location.href = this.fullPath
|
||||||
|
} else if (
|
||||||
|
this.$route.path !== path.resolve(this.fullPath, routePath)
|
||||||
|
) {
|
||||||
|
this.$router.push(path.resolve(this.fullPath, routePath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
108
layouts/VabSideBar/components/VabSideBarItem.vue
Normal file
108
layouts/VabSideBar/components/VabSideBarItem.vue
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="menuComponent"
|
||||||
|
v-if="!item.hidden"
|
||||||
|
:item="item"
|
||||||
|
:full-path="fullPath"
|
||||||
|
:route-children="routeChildren"
|
||||||
|
>
|
||||||
|
<template v-if="item.children && item.children.length">
|
||||||
|
<vab-side-bar-item
|
||||||
|
v-for="route in item.children"
|
||||||
|
:key="route.path"
|
||||||
|
:full-path="handlePath(route.path)"
|
||||||
|
:item="route"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { isExternal } from '@/utils/validate'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VabSideBarItem',
|
||||||
|
props: {
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
fullPath: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
this.onlyOneChild = null
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
menuComponent() {
|
||||||
|
if (
|
||||||
|
this.handleChildren(this.item.children, this.item) &&
|
||||||
|
(!this.routeChildren.children ||
|
||||||
|
this.routeChildren.notShowChildren) &&
|
||||||
|
!this.item.alwaysShow
|
||||||
|
) {
|
||||||
|
return 'VabMenuItem'
|
||||||
|
} else {
|
||||||
|
return 'VabSubmenu'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleChildren(children = [], parent) {
|
||||||
|
if (children === null) children = []
|
||||||
|
const showChildren = children.filter((item) => {
|
||||||
|
if (item.hidden) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
this.routeChildren = item
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (showChildren.length === 1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showChildren.length === 0) {
|
||||||
|
this.routeChildren = {
|
||||||
|
...parent,
|
||||||
|
path: '',
|
||||||
|
notShowChildren: true,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
handlePath(routePath) {
|
||||||
|
if (isExternal(routePath)) {
|
||||||
|
return routePath
|
||||||
|
}
|
||||||
|
if (isExternal(this.fullPath)) {
|
||||||
|
return this.fullPath
|
||||||
|
}
|
||||||
|
return path.resolve(this.fullPath, routePath)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.vab-nav-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
.el-tag {
|
||||||
|
float: right;
|
||||||
|
height: 16px;
|
||||||
|
padding-right: 4px;
|
||||||
|
padding-left: 4px;
|
||||||
|
margin-top: calc((#{$base-menu-item-height} - 16px) / 2);
|
||||||
|
line-height: 16px;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
60
layouts/VabSideBar/components/VabSubmenu.vue
Normal file
60
layouts/VabSideBar/components/VabSubmenu.vue
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<el-submenu
|
||||||
|
ref="subMenu"
|
||||||
|
:index="handlePath(item.path)"
|
||||||
|
:popper-append-to-body="false"
|
||||||
|
>
|
||||||
|
<template slot="title">
|
||||||
|
<vab-icon
|
||||||
|
v-if="item.meta && item.meta.icon"
|
||||||
|
:icon="['fas', item.meta.icon]"
|
||||||
|
class="vab-fas-icon"
|
||||||
|
/>
|
||||||
|
<vab-remix-icon
|
||||||
|
v-if="item.meta && item.meta.remixIcon"
|
||||||
|
:icon-class="item.meta.remixIcon"
|
||||||
|
class="vab-remix-icon"
|
||||||
|
/>
|
||||||
|
<span>{{ item.meta.title }}</span>
|
||||||
|
</template>
|
||||||
|
<slot />
|
||||||
|
</el-submenu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { isExternal } from '@/utils/validate'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VabSubmenu',
|
||||||
|
props: {
|
||||||
|
routeChildren: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fullPath: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handlePath(routePath) {
|
||||||
|
if (isExternal(routePath)) {
|
||||||
|
return routePath
|
||||||
|
}
|
||||||
|
if (isExternal(this.fullPath)) {
|
||||||
|
return this.fullPath
|
||||||
|
}
|
||||||
|
return path.resolve(this.fullPath, routePath)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
141
layouts/VabSideBar/index.vue
Normal file
141
layouts/VabSideBar/index.vue
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<el-scrollbar class="side-bar-container" :class="{ 'is-collapse': collapse }">
|
||||||
|
<vab-logo />
|
||||||
|
<el-menu
|
||||||
|
:background-color="variables['menu-background']"
|
||||||
|
:text-color="variables['menu-color']"
|
||||||
|
:active-text-color="variables['menu-color-active']"
|
||||||
|
:default-active="activeMenu"
|
||||||
|
:collapse="collapse"
|
||||||
|
:collapse-transition="false"
|
||||||
|
:default-openeds="defaultOpens"
|
||||||
|
:unique-opened="uniqueOpened"
|
||||||
|
mode="vertical"
|
||||||
|
>
|
||||||
|
<template v-for="route in routes">
|
||||||
|
<vab-side-bar-item
|
||||||
|
:key="route.path"
|
||||||
|
:full-path="route.path"
|
||||||
|
:item="route"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import variables from '@/styles/variables.scss'
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import { defaultOopeneds, uniqueOpened } from '@/config'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VabSideBar',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
uniqueOpened,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
collapse: 'settings/collapse',
|
||||||
|
routes: 'routes/routes',
|
||||||
|
}),
|
||||||
|
defaultOpens() {
|
||||||
|
if (this.collapse) {
|
||||||
|
}
|
||||||
|
return defaultOopeneds
|
||||||
|
},
|
||||||
|
activeMenu() {
|
||||||
|
const route = this.$route
|
||||||
|
const { meta, path } = route
|
||||||
|
if (meta.activeMenu) {
|
||||||
|
return meta.activeMenu
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@mixin active {
|
||||||
|
&:hover {
|
||||||
|
color: $base-color-white;
|
||||||
|
background-color: $base-menu-background-active !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
color: $base-color-white;
|
||||||
|
background-color: $base-menu-background-active !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-bar-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: $base-z-index;
|
||||||
|
width: $base-left-menu-width;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
background: $base-menu-background;
|
||||||
|
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
|
||||||
|
transition: width $base-transition-time;
|
||||||
|
|
||||||
|
&.is-collapse {
|
||||||
|
width: $base-left-menu-width-min;
|
||||||
|
border-right: 0;
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
.el-menu {
|
||||||
|
transition: width $base-transition-time;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu--collapse {
|
||||||
|
border-right: 0;
|
||||||
|
|
||||||
|
.el-submenu__icon-arrow {
|
||||||
|
right: 10px;
|
||||||
|
margin-top: -3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
.el-scrollbar__wrap {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu {
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
.vab-fas-icon {
|
||||||
|
padding-right: 3px;
|
||||||
|
font-size: $base-font-size-default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vab-remix-icon {
|
||||||
|
padding-right: 3px;
|
||||||
|
font-size: $base-font-size-default + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu-item,
|
||||||
|
.el-submenu__title {
|
||||||
|
height: $base-menu-item-height;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: $base-menu-item-height;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu-item {
|
||||||
|
@include active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
293
layouts/VabTabsBar/index.vue
Normal file
293
layouts/VabTabsBar/index.vue
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
<template>
|
||||||
|
<div id="tabs-bar-container" class="tabs-bar-container">
|
||||||
|
<el-tabs
|
||||||
|
v-model="tabActive"
|
||||||
|
type="card"
|
||||||
|
class="tabs-content"
|
||||||
|
@tab-click="handleTabClick"
|
||||||
|
@tab-remove="handleTabRemove"
|
||||||
|
>
|
||||||
|
<el-tab-pane
|
||||||
|
v-for="item in visitedRoutes"
|
||||||
|
:key="item.path"
|
||||||
|
:label="item.meta.title"
|
||||||
|
:name="item.path"
|
||||||
|
:closable="!isAffix(item)"
|
||||||
|
></el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
<el-dropdown @command="handleCommand">
|
||||||
|
<span style="cursor: pointer">
|
||||||
|
更多操作
|
||||||
|
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||||
|
</span>
|
||||||
|
<el-dropdown-menu slot="dropdown" class="tabs-more">
|
||||||
|
<el-dropdown-item command="closeOtherstabs">
|
||||||
|
<vab-icon :icon="['fas', 'times-circle']" />
|
||||||
|
关闭其他
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="closeLefttabs">
|
||||||
|
<vab-icon :icon="['fas', 'arrow-alt-circle-left']"></vab-icon>
|
||||||
|
关闭左侧
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="closeRighttabs">
|
||||||
|
<vab-icon :icon="['fas', 'arrow-alt-circle-right']"></vab-icon>
|
||||||
|
关闭右侧
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="closeAlltabs">
|
||||||
|
<vab-icon :icon="['fas', 'ban']"></vab-icon>
|
||||||
|
关闭全部
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import path from 'path'
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VabTabsBar',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
affixtabs: [],
|
||||||
|
tabActive: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
visitedRoutes: 'tabsBar/visitedRoutes',
|
||||||
|
routes: 'routes/routes',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route: {
|
||||||
|
handler(route) {
|
||||||
|
this.inittabs()
|
||||||
|
this.addtabs()
|
||||||
|
let tabActive = ''
|
||||||
|
this.visitedRoutes.forEach((item, index) => {
|
||||||
|
if (item.path === this.$route.path) {
|
||||||
|
tabActive = item.path
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.tabActive = tabActive
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
//console.log(this.visitedRoutes);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async handleTabRemove(tabActive) {
|
||||||
|
let view
|
||||||
|
this.visitedRoutes.forEach((item, index) => {
|
||||||
|
if (tabActive == item.path) {
|
||||||
|
view = item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const { visitedRoutes } = await this.$store.dispatch(
|
||||||
|
'tabsBar/delRoute',
|
||||||
|
view
|
||||||
|
)
|
||||||
|
if (this.isActive(view)) {
|
||||||
|
this.toLastTag(visitedRoutes, view)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleTabClick(tab) {
|
||||||
|
const route = this.visitedRoutes.filter((item, index) => {
|
||||||
|
if (tab.index == index) return item
|
||||||
|
})[0]
|
||||||
|
if (this.$route.path !== route.path) {
|
||||||
|
this.$router.push({
|
||||||
|
path: route.path,
|
||||||
|
query: route.query,
|
||||||
|
fullPath: route.fullPath,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isActive(route) {
|
||||||
|
return route.path === this.$route.path
|
||||||
|
},
|
||||||
|
isAffix(tag) {
|
||||||
|
return tag.meta && tag.meta.affix
|
||||||
|
},
|
||||||
|
filterAffixtabs(routes, basePath = '/') {
|
||||||
|
let tabs = []
|
||||||
|
routes.forEach((route) => {
|
||||||
|
if (route.meta && route.meta.affix) {
|
||||||
|
const tagPath = path.resolve(basePath, route.path)
|
||||||
|
tabs.push({
|
||||||
|
fullPath: tagPath,
|
||||||
|
path: tagPath,
|
||||||
|
name: route.name,
|
||||||
|
meta: { ...route.meta },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (route.children) {
|
||||||
|
const temptabs = this.filterAffixtabs(route.children, route.path)
|
||||||
|
if (temptabs.length >= 1) {
|
||||||
|
tabs = [...tabs, ...temptabs]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return tabs
|
||||||
|
},
|
||||||
|
inittabs() {
|
||||||
|
const affixtabs = (this.affixtabs = this.filterAffixtabs(this.routes))
|
||||||
|
for (const tag of affixtabs) {
|
||||||
|
if (tag.name) {
|
||||||
|
this.$store.dispatch('tabsBar/addVisitedRoute', tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addtabs() {
|
||||||
|
const { name } = this.$route
|
||||||
|
if (name) {
|
||||||
|
this.$store.dispatch('tabsBar/addVisitedRoute', this.$route)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
handleCommand(command) {
|
||||||
|
switch (command) {
|
||||||
|
case 'refreshRoute':
|
||||||
|
this.refreshRoute()
|
||||||
|
break
|
||||||
|
case 'closeOtherstabs':
|
||||||
|
this.closeOtherstabs()
|
||||||
|
break
|
||||||
|
case 'closeLefttabs':
|
||||||
|
this.closeLefttabs()
|
||||||
|
break
|
||||||
|
case 'closeRighttabs':
|
||||||
|
this.closeRighttabs()
|
||||||
|
break
|
||||||
|
case 'closeAlltabs':
|
||||||
|
this.closeAlltabs()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async refreshRoute() {
|
||||||
|
this.$baseEventBus.$emit('reloadrouter-view')
|
||||||
|
},
|
||||||
|
async closeSelectedTag(view) {
|
||||||
|
const { visitedRoutes } = await this.$store.dispatch(
|
||||||
|
'tabsBar/delRoute',
|
||||||
|
view
|
||||||
|
)
|
||||||
|
if (this.isActive(view)) {
|
||||||
|
this.toLastTag(visitedRoutes, view)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async closeOtherstabs() {
|
||||||
|
const view = await this.toThisTag()
|
||||||
|
await this.$store.dispatch('tabsBar/delOthersRoutes', view)
|
||||||
|
},
|
||||||
|
async closeLefttabs() {
|
||||||
|
const view = await this.toThisTag()
|
||||||
|
await this.$store.dispatch('tabsBar/delLeftRoutes', view)
|
||||||
|
},
|
||||||
|
async closeRighttabs() {
|
||||||
|
const view = await this.toThisTag()
|
||||||
|
await this.$store.dispatch('tabsBar/delRightRoutes', view)
|
||||||
|
},
|
||||||
|
async closeAlltabs() {
|
||||||
|
const view = await this.toThisTag()
|
||||||
|
const { visitedRoutes } = await this.$store.dispatch(
|
||||||
|
'tabsBar/delAllRoutes'
|
||||||
|
)
|
||||||
|
if (this.affixtabs.some((tag) => tag.path === view.path)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.toLastTag(visitedRoutes, view)
|
||||||
|
},
|
||||||
|
toLastTag(visitedRoutes, view) {
|
||||||
|
const latestView = visitedRoutes.slice(-1)[0]
|
||||||
|
if (latestView) {
|
||||||
|
this.$router.push(latestView)
|
||||||
|
} else {
|
||||||
|
this.$router.push('/')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async toThisTag() {
|
||||||
|
const view = this.visitedRoutes.filter((item, index) => {
|
||||||
|
if (item.path === this.$route.fullPath) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
})[0]
|
||||||
|
if (this.$route.path !== view.path) this.$router.push(view)
|
||||||
|
return view
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tabs-bar-container {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: $base-tabs-bar-height;
|
||||||
|
padding-right: $base-padding;
|
||||||
|
padding-left: $base-padding;
|
||||||
|
user-select: none;
|
||||||
|
background: $base-color-white;
|
||||||
|
border-top: 1px solid #f6f6f6;
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
.fold-unfold {
|
||||||
|
margin-right: $base-padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-content {
|
||||||
|
width: calc(100% - 90px);
|
||||||
|
height: $base-tag-item-height;
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
.el-tabs__nav-next,
|
||||||
|
.el-tabs__nav-prev {
|
||||||
|
height: $base-tag-item-height;
|
||||||
|
line-height: $base-tag-item-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs__header {
|
||||||
|
border-bottom: 0;
|
||||||
|
|
||||||
|
.el-tabs__nav {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs__item {
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: $base-tag-item-height;
|
||||||
|
margin-right: 5px;
|
||||||
|
line-height: $base-tag-item-height;
|
||||||
|
border: 1px solid $base-border-color;
|
||||||
|
border-radius: $base-border-radius;
|
||||||
|
transition: padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !important;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
border: 1px solid $base-color-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.more {
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
224
layouts/VabTopBar/index.vue
Normal file
224
layouts/VabTopBar/index.vue
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
<template>
|
||||||
|
<div class="top-bar-container">
|
||||||
|
<div class="vab-main">
|
||||||
|
<el-row>
|
||||||
|
<el-col :xl="7" :lg="7" :md="7" :sm="7" :xs="7">
|
||||||
|
<vab-logo />
|
||||||
|
</el-col>
|
||||||
|
<el-col :xl="12" :lg="12" :md="12" :sm="12" :xs="12">
|
||||||
|
<el-menu
|
||||||
|
:background-color="variables['menu-background']"
|
||||||
|
:text-color="variables['menu-color']"
|
||||||
|
:active-text-color="variables['menu-color-active']"
|
||||||
|
:default-active="activeMenu"
|
||||||
|
mode="horizontal"
|
||||||
|
menu-trigger="hover"
|
||||||
|
>
|
||||||
|
<template v-for="route in routes">
|
||||||
|
<vab-side-bar-item
|
||||||
|
v-if="!route.hidden"
|
||||||
|
:key="route.path"
|
||||||
|
:full-path="route.path"
|
||||||
|
:item="route"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-menu>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xl="5" :lg="5" :md="5" :sm="5" :xs="5">
|
||||||
|
<div class="right-panel">
|
||||||
|
<vab-error-log />
|
||||||
|
<vab-full-screen-bar @refresh="refreshRoute" />
|
||||||
|
<vab-theme-bar class="hidden-md-and-down" />
|
||||||
|
<vab-icon
|
||||||
|
title="重载路由"
|
||||||
|
:pulse="pulse"
|
||||||
|
:icon="['fas', 'redo']"
|
||||||
|
@click="refreshRoute"
|
||||||
|
/>
|
||||||
|
<vab-avatar />
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import variables from '@/styles/variables.scss'
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VabTopBar',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
pulse: false,
|
||||||
|
menuTrigger: 'hover',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
routes: 'routes/routes',
|
||||||
|
visitedRoutes: 'tabsBar/visitedRoutes',
|
||||||
|
}),
|
||||||
|
activeMenu() {
|
||||||
|
const route = this.$route
|
||||||
|
const { meta, path } = route
|
||||||
|
if (meta.activeMenu) {
|
||||||
|
return meta.activeMenu
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async refreshRoute() {
|
||||||
|
this.$baseEventBus.$emit('reload-router-view')
|
||||||
|
this.pulse = true
|
||||||
|
setTimeout(() => {
|
||||||
|
this.pulse = false
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.top-bar-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: flex-end;
|
||||||
|
height: $base-top-bar-height;
|
||||||
|
background: $base-menu-background;
|
||||||
|
|
||||||
|
.vab-main {
|
||||||
|
background: $base-menu-background;
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
.el-menu {
|
||||||
|
&.el-menu--horizontal {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
height: $base-top-bar-height;
|
||||||
|
border-bottom: 0 solid transparent !important;
|
||||||
|
|
||||||
|
.el-menu-item,
|
||||||
|
.el-submenu__title {
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 767px) {
|
||||||
|
.el-menu-item,
|
||||||
|
.el-submenu__title {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:nth-child(4),
|
||||||
|
li:nth-child(5) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .el-menu-item {
|
||||||
|
height: $base-top-bar-height;
|
||||||
|
line-height: $base-top-bar-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .el-submenu {
|
||||||
|
.el-submenu__title {
|
||||||
|
height: $base-top-bar-height;
|
||||||
|
line-height: $base-top-bar-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1rem;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--horizontal {
|
||||||
|
.el-menu {
|
||||||
|
.el-menu-item,
|
||||||
|
.el-submenu__title {
|
||||||
|
height: $base-menu-item-height;
|
||||||
|
line-height: $base-menu-item-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-submenu,
|
||||||
|
.el-menu-item {
|
||||||
|
&.is-active {
|
||||||
|
background-color: $base-color-blue !important;
|
||||||
|
border-bottom: 0 solid transparent !important;
|
||||||
|
|
||||||
|
.el-submenu__title {
|
||||||
|
border-bottom: 0 solid transparent !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .el-menu-item {
|
||||||
|
.el-tag {
|
||||||
|
margin-top: calc(#{$base-top-bar-height} / 2 - 7.5px);
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1199px) {
|
||||||
|
.el-tag {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border-bottom: 3px solid $base-color-blue !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
height: $base-top-bar-height;
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
.user-name {
|
||||||
|
color: rgba($base-color-white, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name + i {
|
||||||
|
color: rgba($base-color-white, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
margin-right: 15px;
|
||||||
|
font-size: $base-font-size-big;
|
||||||
|
color: rgba($base-color-white, 0.9);
|
||||||
|
cursor: pointer;
|
||||||
|
fill: rgba($base-color-white, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
svg {
|
||||||
|
margin-right: 0;
|
||||||
|
color: rgba($base-color-white, 0.9);
|
||||||
|
cursor: pointer;
|
||||||
|
fill: rgba($base-color-white, 0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-badge {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
35
layouts/index.js
Normal file
35
layouts/index.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
module.exports = {
|
||||||
|
webpackBarName: 'vue-admin-better',
|
||||||
|
webpackBanner:
|
||||||
|
' build: vue-admin-better \n vue-admin-beautiful.com \n https://gitee.com/chu1204505056/vue-admin-better \n time: ',
|
||||||
|
donationConsole() {
|
||||||
|
const chalk = require('chalk')
|
||||||
|
console.log(
|
||||||
|
chalk.green(
|
||||||
|
`> 欢迎使用vue-admin-better,github开源地址:https://github.com/chuzhixin/vue-admin-better`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
console.log(
|
||||||
|
chalk.green(
|
||||||
|
`> 欢迎使用vue-admin-better,码云开源地址:https://gitee.com/chu1204505056/vue-admin-better`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
chalk.green(`> pro版演示地址:http://vue-admin-beautiful.com/admin-pro`)
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
chalk.green(`> plus版演示地址:http://vue-admin-beautiful.com/admin-plus`)
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
chalk.green(
|
||||||
|
`> 使用中出现任何问题可加QQ群反馈,获取基础版、文档,请我们喝杯咖啡(如若情况不允许,请勿勉强):https://gitee.com/chu1204505056/vue-admin-better#-%E5%89%8D%E7%AB%AF%E8%AE%A8%E8%AE%BA-qq-%E7%BE%A4`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(chalk.green(`> 如果您不希望显示以上信息,可在config中配置关闭`))
|
||||||
|
console.log('\n')
|
||||||
|
},
|
||||||
|
}
|
4
layouts/package.json
Normal file
4
layouts/package.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "layouts",
|
||||||
|
"main": "index.js"
|
||||||
|
}
|
16
layouts/prettier.config.js
Normal file
16
layouts/prettier.config.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module.exports = {
|
||||||
|
printWidth: 80,
|
||||||
|
tabWidth: 2,
|
||||||
|
useTabs: false,
|
||||||
|
semi: false,
|
||||||
|
singleQuote: true,
|
||||||
|
quoteProps: 'as-needed',
|
||||||
|
jsxSingleQuote: false,
|
||||||
|
trailingComma: 'es5',
|
||||||
|
bracketSpacing: true,
|
||||||
|
jsxBracketSameLine: false,
|
||||||
|
arrowParens: 'always',
|
||||||
|
htmlWhitespaceSensitivity: 'ignore',
|
||||||
|
vueIndentScriptAndStyle: true,
|
||||||
|
endOfLine: 'lf',
|
||||||
|
}
|
@ -146,7 +146,7 @@ const data = [
|
|||||||
timestamp: '2020-07-11',
|
timestamp: '2020-07-11',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: '修改zx-layouts引入方式',
|
content: '修改layouts引入方式',
|
||||||
timestamp: '2020-07-15',
|
timestamp: '2020-07-15',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -158,36 +158,12 @@ const data = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'magnifier',
|
|
||||||
name: 'Magnifier',
|
|
||||||
component: '@/views/vab/magnifier/index',
|
|
||||||
meta: { title: '放大镜', permissions: ['admin'] },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'loading',
|
path: 'loading',
|
||||||
name: 'Loading',
|
name: 'Loading',
|
||||||
component: '@/views/vab/loading/index',
|
component: '@/views/vab/loading/index',
|
||||||
meta: { title: 'loading', permissions: ['admin'] },
|
meta: { title: 'loading', permissions: ['admin'] },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'player',
|
|
||||||
name: 'Player',
|
|
||||||
component: '@/views/vab/player/index',
|
|
||||||
meta: { title: '视频播放器', permissions: ['admin'] },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'markdownEditor',
|
|
||||||
name: 'MarkdownEditor',
|
|
||||||
component: '@/views/vab/markdownEditor/index',
|
|
||||||
meta: { title: 'markdown编辑器', permissions: ['admin'] },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'editor',
|
|
||||||
name: 'Editor',
|
|
||||||
component: '@/views/vab/editor/index',
|
|
||||||
meta: { title: '富文本编辑器', permissions: ['admin'], badge: 'New' },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'backToTop',
|
path: 'backToTop',
|
||||||
name: 'BackToTop',
|
name: 'BackToTop',
|
||||||
|
25
package.json
25
package.json
@ -1,26 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-admin-beautiful",
|
"name": "vue-admin-better",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "vue-admin-beautiful",
|
"author": "vue-admin-better",
|
||||||
"participants": [],
|
"participants": [],
|
||||||
"homepage": "https://chu1204505056.gitee.io/vue-admin-better",
|
"homepage": "https://chu1204505056.gitee.io/vue-admin-better",
|
||||||
"publishConfig": {
|
|
||||||
"registry": "https://npm.pkg.github.com/"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
"build:report": "vue-cli-service build --report",
|
|
||||||
"globle": "npm install -g cnpm --registry=http://mirrors.cloud.tencent.com/npm/&&cnpm i rimraf npm-check-updates nrm -g&&rimraf node_modules&&cnpm i",
|
|
||||||
"lint": "vue-cli-service lint",
|
"lint": "vue-cli-service lint",
|
||||||
"lint:style": "stylelint-config-prettier-check",
|
|
||||||
"inspect": "vue-cli-service inspect",
|
|
||||||
"clear": "rimraf node_modules&&npm install --registry=http://mirrors.cloud.tencent.com/npm/",
|
"clear": "rimraf node_modules&&npm install --registry=http://mirrors.cloud.tencent.com/npm/",
|
||||||
"image-webpack-loader": "cnpm i image-webpack-loader -D",
|
"image-webpack-loader": "cnpm i image-webpack-loader -D",
|
||||||
"update": "ncu -u --reject sass-loader,sass,screenfull,eslint,chalk,vue-echarts,vue,vue-template-compiler,vue-router,vuex,@vue/cli-plugin-babel,@vue/cli-plugin-eslint,@vue/cli-service,eslint-plugin-vue --registry=http://mirrors.cloud.tencent.com/npm/&&npm i --registry=http://mirrors.cloud.tencent.com/npm/",
|
"update": "ncu -u --reject sass-loader,sass,screenfull,eslint,chalk,vue-echarts,vue,vue-template-compiler,vue-router,vuex,@vue/cli-plugin-babel,@vue/cli-plugin-eslint,@vue/cli-service,eslint-plugin-vue --registry=http://mirrors.cloud.tencent.com/npm/&&npm i --registry=http://mirrors.cloud.tencent.com/npm/",
|
||||||
"update:globle": "ncu -g --concurrency 10 --timeout 80000",
|
"push": "start ./push.sh"
|
||||||
"push": "start ./push.sh",
|
|
||||||
"deploy": "start ./deploy.sh"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -58,14 +49,7 @@
|
|||||||
"vue-router": "^3.5.3",
|
"vue-router": "^3.5.3",
|
||||||
"vue-template-compiler": "~2.6.14",
|
"vue-template-compiler": "~2.6.14",
|
||||||
"vuex": "^3.6.2",
|
"vuex": "^3.6.2",
|
||||||
"zx-count": "^0.3.7",
|
"layouts": "file:layouts"
|
||||||
"zx-layouts": "^0.6.29",
|
|
||||||
"zx-magnifie": "^0.4.0",
|
|
||||||
"zx-markdown-editor": "^0.0.2",
|
|
||||||
"zx-player": "^1.0.2",
|
|
||||||
"zx-quill": "^0.0.3",
|
|
||||||
"zx-templates": "^0.0.26",
|
|
||||||
"zx-verify": "^0.0.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "^4.5.15",
|
"@vue/cli-plugin-babel": "^4.5.15",
|
||||||
@ -83,7 +67,6 @@
|
|||||||
"filemanager-webpack-plugin": "^8.0.0",
|
"filemanager-webpack-plugin": "^8.0.0",
|
||||||
"image-webpack-loader": "^8.1.0",
|
"image-webpack-loader": "^8.1.0",
|
||||||
"lint-staged": "^13.1.0",
|
"lint-staged": "^13.1.0",
|
||||||
"plop": "^3.1.1",
|
|
||||||
"prettier": "^2.8.1",
|
"prettier": "^2.8.1",
|
||||||
"sass": "~1.32.13",
|
"sass": "~1.32.13",
|
||||||
"sass-loader": "^10.1.1",
|
"sass-loader": "^10.1.1",
|
||||||
|
16
plopfile.js
16
plopfile.js
@ -1,16 +0,0 @@
|
|||||||
/**
|
|
||||||
* @author https://vue-admin-beautiful.com (不想保留author可删除)
|
|
||||||
* @description 代码生成机
|
|
||||||
*/
|
|
||||||
const viewGenerator = require('zx-templates/view/prompt')
|
|
||||||
const curdGenerator = require('zx-templates/curd/prompt')
|
|
||||||
const componentGenerator = require('zx-templates/component/prompt')
|
|
||||||
const mockGenerator = require('zx-templates/mock/prompt')
|
|
||||||
const vuexGenerator = require('zx-templates/vuex/prompt')
|
|
||||||
module.exports = (plop) => {
|
|
||||||
plop.setGenerator('view', viewGenerator)
|
|
||||||
plop.setGenerator('curd', curdGenerator)
|
|
||||||
plop.setGenerator('component', componentGenerator)
|
|
||||||
plop.setGenerator('mock&api', mockGenerator)
|
|
||||||
plop.setGenerator('vuex', vuexGenerator)
|
|
||||||
}
|
|
@ -2,15 +2,7 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="g-container" :style="styleObj">
|
<div class="g-container" :style="styleObj">
|
||||||
<div class="g-number">
|
<div class="g-number">
|
||||||
<vab-count
|
{{ endVal }}
|
||||||
:start-val="startVal"
|
|
||||||
:end-val="endVal"
|
|
||||||
:duration="duration"
|
|
||||||
:separator="separator"
|
|
||||||
:prefix="prefix"
|
|
||||||
:suffix="suffix"
|
|
||||||
:decimals="decimals"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="g-contrast">
|
<div class="g-contrast">
|
||||||
<div class="g-circle"></div>
|
<div class="g-circle"></div>
|
||||||
|
@ -12,7 +12,7 @@ requireComponents.keys().forEach((fileName) => {
|
|||||||
Vue.component(componentName, componentConfig.default || componentConfig)
|
Vue.component(componentName, componentConfig.default || componentConfig)
|
||||||
})
|
})
|
||||||
|
|
||||||
const requireZxLayouts = require.context('zx-layouts', true, /\.vue$/)
|
const requireZxLayouts = require.context('layouts', true, /\.vue$/)
|
||||||
requireZxLayouts.keys().forEach((fileName) => {
|
requireZxLayouts.keys().forEach((fileName) => {
|
||||||
const componentConfig = requireZxLayouts(fileName)
|
const componentConfig = requireZxLayouts(fileName)
|
||||||
const componentName = componentConfig.default.name
|
const componentName = componentConfig.default.name
|
||||||
|
@ -8,10 +8,8 @@ import '@/colorfulIcon'
|
|||||||
import '@/config/permission'
|
import '@/config/permission'
|
||||||
import '@/utils/errorLog'
|
import '@/utils/errorLog'
|
||||||
import './vabIcon'
|
import './vabIcon'
|
||||||
import VabPermissions from 'zx-layouts/Permissions'
|
import VabPermissions from 'layouts/Permissions'
|
||||||
import Vab from '@/utils/vab'
|
import Vab from '@/utils/vab'
|
||||||
import VabCount from 'zx-count'
|
|
||||||
|
|
||||||
Vue.use(Vab)
|
Vue.use(Vab)
|
||||||
Vue.use(VabPermissions)
|
Vue.use(VabPermissions)
|
||||||
Vue.use(VabCount)
|
|
||||||
|
@ -16,5 +16,5 @@ if (!!window.ActiveXObject || 'ActiveXObject' in window) {
|
|||||||
dangerouslyUseHTMLString: true,
|
dangerouslyUseHTMLString: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (!dependencies['vab-icon'] || !dependencies['zx-layouts'])
|
if (!dependencies['vab-icon'] || !dependencies['layouts'])
|
||||||
document.body.innerHTML = ''
|
document.body.innerHTML = ''
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
import VabMagnifier from 'zx-magnifie'
|
|
||||||
|
|
||||||
export default VabMagnifier
|
|
@ -1,5 +0,0 @@
|
|||||||
import ZxMarkdownEditor from 'zx-markdown-editor'
|
|
||||||
import 'zx-markdown-editor/dist/zx-markdown-editor.css'
|
|
||||||
|
|
||||||
const VabMarkdownEditor = ZxMarkdownEditor
|
|
||||||
export default VabMarkdownEditor
|
|
@ -1,3 +0,0 @@
|
|||||||
import { VabPlayerMp4, VabPlayerHls, VabPlayerFlv } from 'zx-player'
|
|
||||||
|
|
||||||
export { VabPlayerMp4, VabPlayerHls, VabPlayerFlv }
|
|
@ -1,4 +0,0 @@
|
|||||||
import 'zx-quill/dist/zx-quill.css'
|
|
||||||
import VabQuill from 'zx-quill'
|
|
||||||
|
|
||||||
export default VabQuill
|
|
@ -1,4 +0,0 @@
|
|||||||
import VabVerify from 'zx-verify'
|
|
||||||
import 'zx-verify/dist/zx-verify.css'
|
|
||||||
|
|
||||||
export default VabVerify
|
|
@ -155,12 +155,6 @@ export const asyncRoutes = [
|
|||||||
component: () => import('@/views/vab/tree/index'),
|
component: () => import('@/views/vab/tree/index'),
|
||||||
meta: { title: '树', permissions: ['admin'] },
|
meta: { title: '树', permissions: ['admin'] },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'verify',
|
|
||||||
name: 'Verify',
|
|
||||||
component: () => import('@/views/vab/verify/index'),
|
|
||||||
meta: { title: '验证码', permissions: ['admin'] },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'menu1',
|
path: 'menu1',
|
||||||
component: () => import('@/views/vab/nested/menu1/index'),
|
component: () => import('@/views/vab/nested/menu1/index'),
|
||||||
@ -190,40 +184,12 @@ export const asyncRoutes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'magnifier',
|
|
||||||
name: 'Magnifier',
|
|
||||||
component: () => import('@/views/vab/magnifier/index'),
|
|
||||||
meta: { title: '放大镜', permissions: ['admin'] },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'loading',
|
path: 'loading',
|
||||||
name: 'Loading',
|
name: 'Loading',
|
||||||
component: () => import('@/views/vab/loading/index'),
|
component: () => import('@/views/vab/loading/index'),
|
||||||
meta: { title: 'loading', permissions: ['admin'] },
|
meta: { title: 'loading', permissions: ['admin'] },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'player',
|
|
||||||
name: 'Player',
|
|
||||||
component: () => import('@/views/vab/player/index'),
|
|
||||||
meta: { title: '视频播放器', permissions: ['admin'] },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'markdownEditor',
|
|
||||||
name: 'MarkdownEditor',
|
|
||||||
component: () => import('@/views/vab/markdownEditor/index'),
|
|
||||||
meta: { title: 'markdown编辑器', permissions: ['admin'] },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'editor',
|
|
||||||
name: 'Editor',
|
|
||||||
component: () => import('@/views/vab/editor/index'),
|
|
||||||
meta: {
|
|
||||||
title: '富文本编辑器',
|
|
||||||
permissions: ['admin'],
|
|
||||||
badge: 'New',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'backToTop',
|
path: 'backToTop',
|
||||||
name: 'BackToTop',
|
name: 'BackToTop',
|
||||||
|
@ -29,15 +29,7 @@
|
|||||||
<span>
|
<span>
|
||||||
日均访问量:
|
日均访问量:
|
||||||
|
|
||||||
<vab-count
|
{{ config1.endVal }}
|
||||||
:start-val="config1.startVal"
|
|
||||||
:end-val="config1.endVal"
|
|
||||||
:duration="config1.duration"
|
|
||||||
:separator="config1.separator"
|
|
||||||
:prefix="config1.prefix"
|
|
||||||
:suffix="config1.suffix"
|
|
||||||
:decimals="config1.decimals"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@ -51,15 +43,7 @@
|
|||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<span>
|
<span>
|
||||||
总授权数:
|
总授权数:
|
||||||
<vab-count
|
{{ config2.endVal }}
|
||||||
:start-val="config2.startVal"
|
|
||||||
:end-val="config2.endVal"
|
|
||||||
:duration="config2.duration"
|
|
||||||
:separator="config2.separator"
|
|
||||||
:prefix="config2.prefix"
|
|
||||||
:suffix="config2.suffix"
|
|
||||||
:decimals="config2.decimals"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@ -154,8 +138,8 @@
|
|||||||
<td>{{ dependencies['mockjs'] }}</td>
|
<td>{{ dependencies['mockjs'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>zx-layouts版本</td>
|
<td>layouts版本</td>
|
||||||
<td>{{ dependencies['zx-layouts'] }}</td>
|
<td>{{ dependencies['layouts'] }}</td>
|
||||||
<td>lodash版本</td>
|
<td>lodash版本</td>
|
||||||
<td>{{ dependencies['lodash'] }}</td>
|
<td>{{ dependencies['lodash'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -1,156 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="editor-container">
|
|
||||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
|
||||||
<el-form-item label="标题" prop="title">
|
|
||||||
<el-input v-model="form.title" maxlength="20"></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="所属模块" prop="module">
|
|
||||||
<el-select v-model="form.module">
|
|
||||||
<el-option label="新闻动态" value="1"></el-option>
|
|
||||||
<el-option label="实时热点" value="2"></el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="内容" prop="content" class="vab-quill-content">
|
|
||||||
<vab-quill
|
|
||||||
v-model="form.content"
|
|
||||||
:min-height="400"
|
|
||||||
:options="options"
|
|
||||||
></vab-quill>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="handleSee">预览效果</el-button>
|
|
||||||
<el-button type="primary" @click="handleSave">保存</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<el-dialog title="预览效果" :visible.sync="dialogTableVisible">
|
|
||||||
<div style="min-height: 60vh">
|
|
||||||
<h1 class="news-title">{{ form.title }}</h1>
|
|
||||||
<div class="news-content" v-html="form.content"></div>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import vabQuill from '@/plugins/vabQuill'
|
|
||||||
export default {
|
|
||||||
name: 'Editor',
|
|
||||||
components: { vabQuill },
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
options: {
|
|
||||||
theme: 'snow',
|
|
||||||
bounds: document.body,
|
|
||||||
debug: 'warn',
|
|
||||||
modules: {
|
|
||||||
toolbar: [
|
|
||||||
['bold', 'italic', 'underline', 'strike'],
|
|
||||||
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
|
||||||
[{ size: ['small', false, 'large', 'huge'] }],
|
|
||||||
[{ color: [] }, { background: [] }],
|
|
||||||
['blockquote', 'code-block'],
|
|
||||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
|
||||||
[{ script: 'sub' }, { script: 'super' }],
|
|
||||||
[{ indent: '-1' }, { indent: '+1' }],
|
|
||||||
[{ align: [] }],
|
|
||||||
[{ direction: 'rtl' }],
|
|
||||||
[{ font: [] }],
|
|
||||||
['clean'],
|
|
||||||
['link', 'image'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
placeholder: '内容...',
|
|
||||||
readOnly: false,
|
|
||||||
},
|
|
||||||
borderColor: '#dcdfe6',
|
|
||||||
dialogTableVisible: false,
|
|
||||||
form: {
|
|
||||||
title: '',
|
|
||||||
module: '',
|
|
||||||
content: '',
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
title: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请输入标题',
|
|
||||||
trigger: 'blur',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
module: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请选择模块',
|
|
||||||
trigger: 'change',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请输入内容',
|
|
||||||
trigger: 'blur',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleSee() {
|
|
||||||
this.$refs['form'].validate((valid) => {
|
|
||||||
this.$refs.form.validateField('content', (errorMsg) => {})
|
|
||||||
if (valid) {
|
|
||||||
this.dialogTableVisible = true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
handleSave() {
|
|
||||||
this.$refs['form'].validate((valid) => {
|
|
||||||
this.$refs.form.validateField('content', (errorMsg) => {
|
|
||||||
this.borderColor = '#dcdfe6'
|
|
||||||
if (errorMsg) {
|
|
||||||
this.borderColor = '#F56C6C'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (valid) {
|
|
||||||
this.$baseMessage('submit!', 'success')
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.editor-container {
|
|
||||||
.news {
|
|
||||||
&-title {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-content {
|
|
||||||
::v-deep {
|
|
||||||
p {
|
|
||||||
line-height: 30px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vab-quill-content {
|
|
||||||
::v-deep {
|
|
||||||
.el-form-item__content {
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,35 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="magnifier-container">
|
|
||||||
<el-row :gutter="20">
|
|
||||||
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
|
|
||||||
<el-card shadow="hover">
|
|
||||||
<div slot="header"><span>放大镜1</span></div>
|
|
||||||
<vab-magnifier
|
|
||||||
url="https://picsum.photos/960/540?random=1"
|
|
||||||
type="circle"
|
|
||||||
></vab-magnifier>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
|
|
||||||
<el-card shadow="hover">
|
|
||||||
<div slot="header"><span>放大镜2</span></div>
|
|
||||||
<vab-magnifier
|
|
||||||
url="https://picsum.photos/960/540?random=2"
|
|
||||||
type="square"
|
|
||||||
></vab-magnifier>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import VabMagnifier from '@/plugins/vabMagnifier.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Magnifier',
|
|
||||||
components: {
|
|
||||||
VabMagnifier,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,51 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="markdown-editor-container">
|
|
||||||
<el-row :gutter="20">
|
|
||||||
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
|
|
||||||
<vab-markdown-editor
|
|
||||||
ref="mde"
|
|
||||||
v-model="value"
|
|
||||||
@show-html="handleShowHtml"
|
|
||||||
></vab-markdown-editor>
|
|
||||||
<el-button @click="handleAddText">增加文本</el-button>
|
|
||||||
<el-button @click="handleAddImg">增加图片</el-button>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
|
|
||||||
<el-card shadow="hover">
|
|
||||||
<div slot="header">
|
|
||||||
<span>markdown转换html实时演示区域</span>
|
|
||||||
</div>
|
|
||||||
<div v-html="html"></div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import VabMarkdownEditor from '@/plugins/vabMarkdownEditor'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'MarkdownEditor',
|
|
||||||
components: { VabMarkdownEditor },
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
value: '# vue-admin-beautiful',
|
|
||||||
html: '<h1 id="vue-admin-beautiful">vue-admin-beautiful</h1>',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleAddText() {
|
|
||||||
this.$refs.mde.add('\n### 新增加的内容')
|
|
||||||
},
|
|
||||||
handleAddImg() {
|
|
||||||
this.$refs.mde.add(
|
|
||||||
'\n'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
handleShowHtml(html) {
|
|
||||||
this.html = html
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,73 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="player-container">
|
|
||||||
<el-divider content-position="left">
|
|
||||||
视频地址采用cdn加速服务,开发时需部署到到本地
|
|
||||||
</el-divider>
|
|
||||||
<el-row :gutter="20">
|
|
||||||
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
|
|
||||||
<el-card shadow="hover">
|
|
||||||
<div slot="header">播放传统MP4</div>
|
|
||||||
<vab-player-mp4 :config="config1" @player="Player1 = $event" />
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
|
|
||||||
<el-card shadow="hover">
|
|
||||||
<div slot="header">播放m3u8,且不暴露视频地址</div>
|
|
||||||
<vab-player-hls
|
|
||||||
:config="config2"
|
|
||||||
@player="Player2 = $event"
|
|
||||||
></vab-player-hls>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<!--<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
|
|
||||||
<el-card shadow="hover">
|
|
||||||
<div slot="header">播放flv,且不暴露视频地址</div>
|
|
||||||
<vab-player-flv
|
|
||||||
:config="config3"
|
|
||||||
@player="Player3 = $event"
|
|
||||||
></vab-player-flv>
|
|
||||||
</el-card>
|
|
||||||
</el-col>-->
|
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { VabPlayerMp4, VabPlayerHls } from '@/plugins/vabPlayer.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Player',
|
|
||||||
components: {
|
|
||||||
VabPlayerMp4,
|
|
||||||
VabPlayerHls,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
config1: {
|
|
||||||
id: 'mse1',
|
|
||||||
url: 'https://fastly.jsdelivr.net/gh/chuzhixin/videos@master/video.mp4',
|
|
||||||
volume: 1,
|
|
||||||
autoplay: false,
|
|
||||||
},
|
|
||||||
Player1: null,
|
|
||||||
config2: {
|
|
||||||
id: 'mse2',
|
|
||||||
url: 'https://fastly.jsdelivr.net/gh/chuzhixin/videos@master/video.m3u8',
|
|
||||||
volume: 1,
|
|
||||||
autoplay: false,
|
|
||||||
},
|
|
||||||
Player2: null,
|
|
||||||
config3: {
|
|
||||||
id: 'mse3',
|
|
||||||
url: 'https://fastly.jsdelivr.net/gh/chuzhixin/videos@master/video.flv',
|
|
||||||
volume: 1,
|
|
||||||
autoplay: false,
|
|
||||||
},
|
|
||||||
Player3: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {},
|
|
||||||
mounted() {},
|
|
||||||
methods: {},
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,35 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="verify-container">
|
|
||||||
<vab-verify
|
|
||||||
ref="slideDiv"
|
|
||||||
:w="350"
|
|
||||||
:slider-text="text"
|
|
||||||
:h="175"
|
|
||||||
@success="handleSuccess"
|
|
||||||
@fail="handleError"
|
|
||||||
></vab-verify>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import VabVerify from '@/plugins/vabVerify'
|
|
||||||
export default {
|
|
||||||
name: 'Verify',
|
|
||||||
components: { VabVerify },
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
text: '向右滑动',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {},
|
|
||||||
mounted() {},
|
|
||||||
methods: {
|
|
||||||
handleSuccess() {
|
|
||||||
this.$baseMessage('校验成功', 'success')
|
|
||||||
},
|
|
||||||
handleError() {
|
|
||||||
this.$baseMessage('校验失败', 'error')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
1
vab-icon
Symbolic link
1
vab-icon
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
D:/Development/VSCodeProjects/vue-admin-beautiful/node_modules/_vab-icon@0.0.1@vab-icon
|
@ -18,7 +18,7 @@ const {
|
|||||||
donation,
|
donation,
|
||||||
imageCompression,
|
imageCompression,
|
||||||
} = require('./src/config')
|
} = require('./src/config')
|
||||||
const { webpackBarName, webpackBanner, donationConsole } = require('zx-layouts')
|
const { webpackBarName, webpackBanner, donationConsole } = require('layouts')
|
||||||
|
|
||||||
if (donation) donationConsole()
|
if (donation) donationConsole()
|
||||||
const { version, author } = require('./package.json')
|
const { version, author } = require('./package.json')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user