Commit 4cc07118 authored by whlviolin's avatar whlviolin

数据分析工具系统 前端

parent 18ce536f
Pipeline #344 canceled with stages
# 支持浏览器配置
> 1%
last 2 versions
not dead
# 编辑器配置
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
src/assets
src/icons
public
dist
node_modules
vab-icon
layouts
\ No newline at end of file
/**
* @author https://vue-admin-beautiful.com (不想保留author可删除)
* @description .eslintrc.js
*/
module.exports = {
root: true,
env: {
node: true,
},
extends: ['plugin:vue/recommended', '@vue/prettier'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'vue/no-v-html': 'off',
'vue/multi-word-component-names': 'off',
'vue/no-useless-template-attributes': 'off',
'vue/no-reserved-component-names': 'off',
},
parserOptions: {
parser: 'babel-eslint',
},
overrides: [
{
files: [
'**/__tests__/*.{j,t}s?(x)',
'**/tests/unit/**/*.spec.{j,t}s?(x)',
],
env: {
jest: true,
},
},
],
}
# .gitattributes
*.html text eol=lf
*.css text eol=lf
*.js text eol=lf
*.scss text eol=lf
*.vue text eol=lf
*.hbs text eol=lf
*.sh text eol=lf
*.md text eol=lf
*.json text eol=lf
*.yml text eol=lf
.browserslistrc text eol=lf
.gitignore text eol=lf
*.js linguist-language=vue
# .gitignore
.DS_Store
node_modules
dist
.env.local
.env.*.local
npm-debug.log*
yarn.lock
yarn-debug.log*
yarn-error.log*
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
public/video
*.zip
*.7z
/src/layouts/components/layouts
/zx-templates
/package-lock.json
/src/styles/themes/green.scss
/src/styles/themes/dark.scss
/src/styles/themes/glory.scss
/.history
/**
* @author https://vue-admin-beautiful.com (不想保留author可删除)
* @description stylelint
*/
module.exports = {
extends: ['stylelint-config-recess-order', 'stylelint-config-prettier'],
}
MIT License
Copyright (c) 2020 good luck
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# .gitignore
.DS_Store
node_modules
dist
.env.local
.env.*.local
npm-debug.log*
yarn.lock
yarn-debug.log*
yarn-error.log*
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
public/video
*.zip
*.7z
/src/layouts/components/layouts
/zx-templates
/package-lock.json
/src/styles/themes/green.scss
/src/styles/themes/dark.scss
/src/styles/themes/glory.scss
/.history
/**
* @author https://vue-admin-beautiful.com (不想保留author可删除)
* @description babel.config
*/
module.exports = {
presets: ['@vue/cli-plugin-babel/preset'],
}
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
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)
}
},
}
<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>
<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>
<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>
<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>
<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>
<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;
}
.el-menu-item,
.el-submenu {
text-align: center;
}
}
}
}
::v-deep {
.el-scrollbar__wrap {
overflow-x: hidden;
}
.el-menu {
border: 0;
.vab-fas-icon {
padding-right: 3px;
font-size: $base-font-size-default;
display: inline-block;
width: 14px;
}
.vab-remix-icon {
padding-right: 3px;
font-size: $base-font-size-default + 2;
}
}
.el-menu-item,
.el-submenu__title {
height: $base-menu-item-height;
line-height: $base-menu-item-height;
vertical-align: middle;
}
.el-menu-item {
@include active;
}
}
}
</style>
<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>
<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-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>
module.exports = {
webpackBarName: 'admin',
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')
},
}
{
"name": "layouts",
"version": "1.0.0",
"main": "index.js"
}
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',
}
const accessTokens = {
admin: 'admin-accessToken',
editor: 'editor-accessToken',
test: 'test-accessToken',
}
module.exports = [
{
url: '/publicKey',
type: 'post',
response() {
return {
code: 200,
msg: 'success',
data: {
mockServer: true,
publicKey:
'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBT2vr+dhZElF73FJ6xiP181txKWUSNLPQQlid6DUJhGAOZblluafIdLmnUyKE8mMHhT3R+Ib3ssZcJku6Hn72yHYj/qPkCGFv0eFo7G+GJfDIUeDyalBN0QsuiE/XzPHJBuJDfRArOiWvH0BXOv5kpeXSXM8yTt5Na1jAYSiQ/wIDAQAB',
},
}
},
},
{
url: '/login',
type: 'post',
response(config) {
const { username } = config.body
const accessToken = accessTokens[username]
if (!accessToken) {
return {
code: 500,
msg: '帐户或密码不正确。',
}
}
return {
code: 200,
msg: 'success',
data: { accessToken },
}
},
},
{
url: '/register',
type: 'post',
response() {
return {
code: 200,
msg: '模拟注册成功',
}
},
},
{
url: '/userInfo',
type: 'post',
response(config) {
const { accessToken } = config.body
let permissions = ['admin']
let username = 'admin'
if ('admin-accessToken' === accessToken) {
permissions = ['admin']
username = 'admin'
}
if ('editor-accessToken' === accessToken) {
permissions = ['editor']
username = 'editor'
}
if ('test-accessToken' === accessToken) {
permissions = ['admin', 'editor']
username = 'test'
}
return {
code: 200,
msg: 'success',
data: {
permissions,
username,
'avatar|1': [
'https://i.gtimg.cn/club/item/face/img/2/15922_100.gif',
'https://i.gtimg.cn/club/item/face/img/8/15918_100.gif',
],
},
}
},
},
{
url: '/logout',
type: 'post',
response() {
return {
code: 200,
msg: 'success',
}
},
},
]
const chokidar = require('chokidar')
const bodyParser = require('body-parser')
const chalk = require('chalk')
const path = require('path')
const { mock } = require('mockjs')
const { baseURL } = require('../src/config')
const mockDir = path.join(process.cwd(), 'mock')
const { handleMockArray } = require('./utils')
/**
*
* @param app
* @returns {{mockStartIndex: number, mockRoutesLength: number}}
*/
const registerRoutes = (app) => {
let mockLastIndex
const mocks = []
const mockArray = handleMockArray()
mockArray.forEach((item) => {
const obj = require(item)
mocks.push(...obj)
})
const mocksForServer = mocks.map((route) => {
return responseFake(route.url, route.type, route.response)
})
for (const mock of mocksForServer) {
app[mock.type](mock.url, mock.response)
mockLastIndex = app._router.stack.length
}
const mockRoutesLength = Object.keys(mocksForServer).length
return {
mockRoutesLength: mockRoutesLength,
mockStartIndex: mockLastIndex - mockRoutesLength,
}
}
/**
*
* @param url
* @param type
* @param respond
* @returns {{response(*=, *=): void, type: (*|string), url: RegExp}}
*/
const responseFake = (url, type, respond) => {
return {
url: new RegExp(`${baseURL}${url}`),
type: type || 'get',
response(req, res) {
res.status(200)
if (JSON.stringify(req.body) !== '{}') {
console.log(chalk.green(`> 请求地址:${req.path}`))
console.log(chalk.green(`> 请求参数:${JSON.stringify(req.body)}\n`))
} else {
console.log(chalk.green(`> 请求地址:${req.path}\n`))
}
res.json(mock(respond instanceof Function ? respond(req, res) : respond))
},
}
}
/**
*
* @param app
*/
module.exports = (app) => {
app.use(bodyParser.json())
app.use(
bodyParser.urlencoded({
extended: true,
})
)
const mockRoutes = registerRoutes(app)
let mockRoutesLength = mockRoutes.mockRoutesLength
let mockStartIndex = mockRoutes.mockStartIndex
chokidar
.watch(mockDir, {
ignored: /mock-server/,
ignoreInitial: true,
})
.on('all', (event) => {
if (event === 'change' || event === 'add') {
try {
app._router.stack.splice(mockStartIndex, mockRoutesLength)
Object.keys(require.cache).forEach((item) => {
if (item.includes(mockDir)) {
delete require.cache[require.resolve(item)]
}
})
const mockRoutes = registerRoutes(app)
mockRoutesLength = mockRoutes.mockRoutesLength
mockStartIndex = mockRoutes.mockStartIndex
} catch (error) {
console.log(chalk.red(error))
}
}
})
}
const { Random } = require('mockjs')
const { join } = require('path')
const fs = require('fs')
/**
* @author https://vue-admin-beautiful.com (不想保留author可删除)
* @description 随机生成图片url。
* @param width
* @param height
* @returns {string}
*/
function handleRandomImage(width = 50, height = 50) {
return `https://picsum.photos/${width}/${height}?random=${Random.guid()}`
}
/**
* @author https://vue-admin-beautiful.com (不想保留author可删除)
* @description 处理所有 controller 模块,npm run serve时在node环境中自动输出controller文件夹下Mock接口,请勿修改。
* @returns {[]}
*/
function handleMockArray() {
const mockArray = []
const getFiles = (jsonPath) => {
const jsonFiles = []
const findJsonFile = (path) => {
const files = fs.readdirSync(path)
files.forEach((item) => {
const fPath = join(path, item)
const stat = fs.statSync(fPath)
if (stat.isDirectory() === true) findJsonFile(item)
if (stat.isFile() === true) jsonFiles.push(item)
})
}
findJsonFile(jsonPath)
jsonFiles.forEach((item) => mockArray.push(`./controller/${item}`))
}
getFiles('mock/controller')
return mockArray
}
module.exports = {
handleRandomImage,
handleMockArray,
}
{
"name": "vue-admin-better",
"version": "2.2.0",
"author": "vue-admin-better",
"participants": [],
"homepage": "https://chu1204505056.gitee.io/vue-admin-better",
"scripts": {
"serve": "vue-cli-service serve",
"serve:node18": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
"build": "vue-cli-service build",
"build:node18": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
"lint": "vue-cli-service lint",
"clear": "rimraf node_modules&&npm install --registry=--registry=https://registry.npmmirror.com",
"image-webpack-loader": "cnpm i image-webpack-loader -D",
"update": "ncu -u --reject layouts,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=https://registry.npmmirror.com&&cnpm i",
"push": "start ./push.sh"
},
"repository": {
"type": "git",
"url": "git+https://github.com/chuzhixin/vue-admin-beautiful.git"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.{js,jsx,vue}": [
"vue-cli-service lint",
"git add"
]
},
"dependencies": {
"axios": "^1.4.0",
"caniuse-lite": "^1.0.30001482",
"clipboard": "^2.0.11",
"core-js": "^3.30.1",
"dayjs": "^1.11.7",
"echarts": "5.4.2",
"element-ui": "^2.15.13",
"globalthis": "^1.0.3",
"jsencrypt": "^3.3.2",
"layouts": "file:layouts",
"lodash": "^4.17.21",
"maptalks": "^0.49.5",
"mapv": "^2.0.62",
"mockjs": "^1.1.0",
"monaco-editor": "^0.21.2",
"monaco-editor-webpack-plugin": "^2.0.0",
"nprogress": "^0.2.0",
"qs": "^6.11.1",
"screenfull": "^5.2.0",
"sortablejs": "^1.15.0",
"vab-icon": "file:vab-icon",
"vue": "~2.7.14 ",
"vue-echarts": "5.0.0-beta.0",
"vue-router": "^3.6.5",
"vue-template-compiler": "~2.7.14",
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.15",
"@vue/cli-plugin-eslint": "^4.5.15",
"@vue/cli-service": "^4.5.15",
"@vue/composition-api": "^1.7.1",
"@vue/eslint-config-prettier": "^7.1.0",
"babel-cli": "^6.26.0",
"babel-eslint": "^10.1.0",
"babel-preset-env": "^1.7.0",
"body-parser": "^1.20.2",
"chalk": "^4.1.2",
"chokidar": "^3.5.3",
"eslint": "^7.32.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.1.1",
"filemanager-webpack-plugin": "^8.0.0",
"image-webpack-loader": "^8.1.0",
"lint-staged": "^13.2.2",
"prettier": "^2.8.8",
"sass": "~1.32.13",
"sass-loader": "^10.1.1",
"stylelint": "^15.6.1",
"stylelint-config-prettier": "^9.0.5",
"stylelint-config-recess-order": "^4.0.0",
"svg-sprite-loader": "^6.0.11",
"webpackbar": "^5.0.2"
},
"keywords": [
"vue",
"admin",
"dashboard",
"element-ui",
"vue-admin",
"element-admin",
"boilerplate",
"admin-template",
"management-system"
],
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
}
}
/**
* @author https://vue-admin-beautiful.com (不想保留author可删除)
* @description 代码规范
*/
module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: true,
quoteProps: 'as-needed',
jsxSingleQuote: false,
trailingComma: 'es5',
bracketSpacing: true,
arrowParens: 'always',
htmlWhitespaceSensitivity: 'ignore',
vueIndentScriptAndStyle: true,
endOfLine: 'lf',
}
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title>vue-admin-better</title>
<meta name="author" content="<%= VUE_APP_AUTHOR %>" />
<link rel="stylesheet" href="<%= BASE_URL %>static/css/loading.css" />
</head>
<body>
<div id="vue-admin-beautiful">
<div class="first-loading-wrp">
<div class="loading-wrp">
<span class="dot dot-spin">
<i></i>
<i></i>
<i></i>
<i></i>
</span>
</div>
<h1><%= VUE_APP_TITLE %></h1>
</div>
</div>
</body>
</html>
User-agent: *
Allow: /
/**
* @description 雪花屏代码,基于ant-design修改
**/
.first-loading-wrp {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 90vh;
min-height: 90vh;
}
.first-loading-wrp > h1 {
font-size: 30px;
font-weight: bolder;
}
.first-loading-wrp .loading-wrp {
display: flex;
align-items: center;
justify-content: center;
padding: 98px;
}
.dot {
position: relative;
box-sizing: border-box;
display: inline-block;
width: 64px;
height: 64px;
font-size: 64px;
transform: rotate(45deg);
animation: antRotate 1.2s infinite linear;
}
.dot i {
position: absolute;
display: block;
width: 28px;
height: 28px;
background-color: #1890ff;
border-radius: 100%;
opacity: 0.3;
transform: scale(0.75);
transform-origin: 50% 50%;
animation: antSpinMove 1s infinite linear alternate;
}
.dot i:nth-child(1) {
top: 0;
left: 0;
}
.dot i:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.dot i:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.dot i:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
@keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@-webkit-keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes antSpinMove {
to {
opacity: 1;
}
}
@-webkit-keyframes antSpinMove {
to {
opacity: 1;
}
}
<template>
<div id="vue-admin-beautiful">
<router-view />
</div>
</template>
<script>
// 引入socket.js
import { socket } from '@/plugins/socket'
export default {
name: 'App',
created() {
// 发起连接
socket.init()
},
mounted() {
},
}
</script>
import request from '@/utils/request'
export function getIconList(data) {
return request({
url: '/colorfulIcon/getList',
method: 'post',
data,
})
}
import request from '@/utils/request'
export function getyear() {
return request({
url: 'http://localhost:5000/getyears',
method: 'get',
})
}
export function getIndustry() {
return request({
url: 'http://localhost:5000/getindustry',
method: 'get',
})
}
export function getAddress() {
return request({
url: 'http://localhost:5000/getaddrs',
method: 'get',
})
}
export function detDataTitle() {
return request({
url: 'http://localhost:5000/getdatatitles',
method: 'get',
})
}
export function run(condition) {
return request({
url: 'http://localhost:5000/run',
method: 'post',
data: condition
})
}
export function getTree() {
return request({
url: "http://localhost:5000/getallIndex",
method: 'get',
})
}
export function readFile(params) {
return request({
url: "http://localhost:5000/readfile",
method: 'get',
params
})
}
export function saveTempFile(params) {
return request({
url: "http://localhost:5000/saveTempFile",
method: 'get',
params
})
}
export function runAlgo(params) {
return request({
url: "http://localhost:5000/algo/run",
method: 'get',
params
})
}
export function runDEAAlgo(params) {
return request({
url: "http://localhost:5000/algo/runDEA",
method: 'get',
params
})
}
export function getDataHeader(params) {
return request({
url: "http://localhost:5000/getDataHeader",
method: 'get',
params
})
}
export function getProject() {
return request({
url: "http://localhost:5000/getProject",
method: 'get'
})
}
export function getFileList() {
return request({
url: "http://localhost:5000/getFileList",
method: 'get'
})
}
export function createProject(params) {
return request({
url: "http://localhost:5000/createProject",
method: 'get',
params
})
}
\ No newline at end of file
import request from '@/utils/request'
export function getIconList(data) {
return request({
url: '/icon/getList',
method: 'post',
data,
})
}
import request from '@/utils/request'
export function getPublicKey() {
return request({
url: '/publicKey',
method: 'post',
})
}
import request from '@/utils/request'
export function getIconList(data) {
return request({
url: '/remixIcon/getList',
method: 'post',
data,
})
}
import request from '@/utils/request'
export function getRouterList(data) {
return request({
url: '/menu/navigate',
method: 'post',
data,
})
}
import request from '@/utils/request'
import { encryptedData } from '@/utils/encrypt'
import { loginRSA, tokenName } from '@/config'
export async function login(data) {
if (loginRSA) {
data = await encryptedData(data)
}
return request({
url: '/login',
method: 'post',
data,
})
}
export function getUserInfo(accessToken) {
return request({
url: '/userInfo',
method: 'post',
data: {
[tokenName]: accessToken,
},
})
}
export function logout() {
return request({
url: '/logout',
method: 'post',
})
}
export function register() {
return request({
url: '/register',
method: 'post',
})
}
@font-face {
font-family: "iconfont"; /* Project id 4063829 */
src: url('iconfont.woff2?t=1684825847333') format('woff2'),
url('iconfont.woff?t=1684825847333') format('woff'),
url('iconfont.ttf?t=1684825847333') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.iconelementui:before {
content: "\e604";
}
.icon-404:before {
content: "\e61c";
}
.icon-tongji2:before {
content: "\e609";
}
.icon-tongji7:before {
content: "\e6d2";
}
.icon-tongji:before {
content: "\e637";
}
.icon-24gf-chartPie:before {
content: "\e849";
}
.icon-kongjianguanxijianmo:before {
content: "\ec3d";
}
.icon-people-network-full:before {
content: "\e865";
}
.icon-jiegou:before {
content: "\e638";
}
.icon-nav-23:before {
content: "\e66f";
}
.icon-web-icon-:before {
content: "\e6df";
}
.icon-csv:before {
content: "\e800";
}
.icon-yulan:before {
content: "\e671";
}
.icon-homefill:before {
content: "\e6bb";
}
.icon-icon-standard:before {
content: "\e61a";
}
.icon-database-set:before {
content: "\e7f6";
}
.icon-hangyebiaozhun:before {
content: "\e790";
}
.icon-shujushaixuan:before {
content: "\e76a";
}
.icon-yonghu:before {
content: "\e873";
}
.icon-tuichu:before {
content: "\ec63";
}
@font-face {
font-family: "iconfont"; /* Project id 4063829 */
src: url('iconfont.woff2?t=1685324547319') format('woff2'),
url('iconfont.woff?t=1685324547319') format('woff'),
url('iconfont.ttf?t=1685324547319') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-fenlei:before {
content: "\e60d";
}
.icon-detailsoriginator:before {
content: "\e61d";
}
.icon-fenlei1:before {
content: "\e653";
}
.icon-fenlei2:before {
content: "\e62f";
}
.icon-sort:before {
content: "\e65e";
}
.icon-fenlei3:before {
content: "\e639";
}
.icon-huiguifenxi:before {
content: "\e656";
}
.icon-julei:before {
content: "\e650";
}
.icon-guanlian:before {
content: "\e651";
}
.icon-juleixishu:before {
content: "\e6a5";
}
.icon-juleifenbu:before {
content: "\ec3e";
}
.icon-midujulei:before {
content: "\ec8e";
}
.icon-cengcijulei:before {
content: "\e72c";
}
.icon-guanlianguize:before {
content: "\e746";
}
.icon-icon_xinyong_xianxing_jijin-222:before {
content: "\e664";
}
.icon-xianxinghuigui:before {
content: "\e750";
}
.icon--_shujuyuchuli:before {
content: "\e63a";
}
.icon--_shujujiangwei:before {
content: "\e63b";
}
.icon-guanlian1:before {
content: "\e687";
}
.icon-404:before {
content: "\e61c";
}
.icon-tongji2:before {
content: "\e609";
}
.icon-tongji7:before {
content: "\e6d2";
}
.icon-tongji:before {
content: "\e637";
}
.icon-24gf-chartPie:before {
content: "\e849";
}
.icon-kongjianguanxijianmo:before {
content: "\ec3d";
}
.icon-people-network-full:before {
content: "\e865";
}
.icon-jiegou:before {
content: "\e638";
}
.icon-nav-23:before {
content: "\e66f";
}
.icon-web-icon-:before {
content: "\e6df";
}
.icon-csv:before {
content: "\e800";
}
.icon-yulan:before {
content: "\e671";
}
.icon-homefill:before {
content: "\e6bb";
}
.icon-icon-standard:before {
content: "\e61a";
}
.icon-database-set:before {
content: "\e7f6";
}
.icon-hangyebiaozhun:before {
content: "\e790";
}
.icon-shujushaixuan:before {
content: "\e76a";
}
.icon-yonghu:before {
content: "\e873";
}
.icon-tuichu:before {
content: "\ec63";
}
This diff is collapsed.
const req = require.context('./svg', false, /\.svg$/),
requireAll = (requireContext) => {
/*let a = requireContext.keys().map(requireContext);
let arr = [];
for (let i = 0; i < a.length; i++) {
console.log();
let icon = a[i].default.id;
arr.push(icon);
}
console.log(JSON.stringify(arr));*/
return requireContext.keys().map(requireContext)
}
requireAll(req)
<svg class="icon" width="128" height="128" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M358.4 853.333H245.333l-23.466 64H147.2l121.6-324.266h61.867l119.466 324.266h-68.266l-23.467-64zm-98.133-57.6h81.066l-40.533-121.6-40.533 121.6zm4.266-418.133h162.134v53.333H179.2V390.4L341.333 160H179.2v-53.333h243.2v36.266L264.533 377.6z" fill="#2196F3"/><path d="M810.667 704V106.667h-85.334V704h-128L768 917.333 938.667 704z" fill="#546E7A"/></svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
width="550px" height="400px"
xml:space="preserve">
<g id="PathID_1" transform="matrix(10.7099, 0, 0, 10.7099, 76.4, 396.15)" opacity="1">
<path style="fill: #41b882; fill-opacity: 1;" d="M3.75 -36.65L18.4 -36.65Q22.75 -36.65 24.85 -36.25Q27 -35.9 28.7 -34.75Q30.4 -33.6 31.5 -31.7Q32.65 -29.8 32.65 -27.4Q32.65 -24.85 31.25 -22.7Q29.85 -20.55 27.5 -19.5Q30.85 -18.5 32.65 -16.15Q34.45 -13.8 34.45 -10.6Q34.45 -8.1 33.25 -5.75Q32.1 -3.4 30.1 -1.95Q28.1 -0.55 25.15 -0.25Q23.3 -0.05 16.2 0L3.75 0L3.75 -36.65M11.15 -30.55L11.15 -22.1L16 -22.1Q20.3 -22.1 21.35 -22.2Q23.25 -22.4 24.35 -23.5Q25.45 -24.6 25.45 -26.35Q25.45 -28.05 24.5 -29.1Q23.55 -30.2 21.7 -30.4Q20.6 -30.55 15.4 -30.55L11.15 -30.55M11.15 -16L11.15 -6.2L18 -6.2Q22 -6.2 23.05 -6.4Q24.7 -6.7 25.75 -7.85Q26.8 -9.05 26.8 -11Q26.8 -12.65 26 -13.8Q25.2 -14.95 23.65 -15.45Q22.15 -16 17.1 -16L11.15 -16" />
</g>
<g id="PathID_2" transform="matrix(10.7099, 0, 0, 10.7099, 76.4, 396.15)" opacity="1">
</g>
<g id="PathID_3" transform="matrix(5.31826, 0, 0, 2.59618, 172.9, 161.55)" opacity="1">
<path style="fill: #35495e; fill-opacity: 1;" d="M3.75 -36.65L17.25 -36.65Q21.8 -36.65 24.2 -35.95Q27.45 -35 29.75 -32.55Q32.05 -30.15 33.25 -26.6Q34.45 -23.1 34.45 -17.95Q34.45 -13.45 33.3 -10.15Q31.95 -6.15 29.4 -3.7Q27.45 -1.8 24.2 -0.75Q21.75 0 17.65 0L3.75 0L3.75 -36.65M11.15 -30.45L11.15 -6.2L16.65 -6.2Q19.75 -6.2 21.1 -6.55Q22.9 -6.95 24.1 -8Q25.3 -9.1 26.05 -11.55Q26.8 -14.05 26.8 -18.3Q26.8 -22.55 26.05 -24.8Q25.3 -27.1 23.95 -28.35Q22.6 -29.65 20.5 -30.1Q18.95 -30.45 14.45 -30.45L11.15 -30.45" />
</g>
<g id="PathID_4" transform="matrix(5.31826, 0, 0, 2.59618, 172.9, 161.55)" opacity="1">
</g>
<g id="PathID_5" transform="matrix(5.78477, 0, 0, 3.1825, 171.7, 333.8)" opacity="1">
<path style="fill: #35495e; fill-opacity: 1;" d="M3.75 -36.65L17.25 -36.65Q21.8 -36.65 24.2 -35.95Q27.45 -35 29.75 -32.55Q32.05 -30.15 33.25 -26.6Q34.45 -23.1 34.45 -17.95Q34.45 -13.45 33.3 -10.15Q31.95 -6.15 29.4 -3.7Q27.45 -1.8 24.2 -0.75Q21.75 0 17.65 0L3.75 0L3.75 -36.65M11.15 -30.45L11.15 -6.2L16.65 -6.2Q19.75 -6.2 21.1 -6.55Q22.9 -6.95 24.1 -8Q25.3 -9.1 26.05 -11.55Q26.8 -14.05 26.8 -18.3Q26.8 -22.55 26.05 -24.8Q25.3 -27.1 23.95 -28.35Q22.6 -29.65 20.5 -30.1Q18.95 -30.45 14.45 -30.45L11.15 -30.45" />
</g>
<g id="PathID_6" transform="matrix(5.78477, 0, 0, 3.1825, 171.7, 333.8)" opacity="1">
</g>
</svg>
<template>
<div class="select-tree-template">
<el-select
v-model="selectValue"
:clearable="clearable"
:collapse-tags="selectType == 'multiple'"
:multiple="selectType == 'multiple'"
class="vab-tree-select"
value-key="id"
@clear="clearHandle"
@remove-tag="removeTag"
>
<el-option :value="selectKey">
<el-tree
id="treeOption"
ref="treeOption"
:current-node-key="currentNodeKey"
:data="treeOptions"
:default-checked-keys="defaultSelectedKeys"
:default-expanded-keys="defaultSelectedKeys"
:highlight-current="true"
:props="defaultProps"
:show-checkbox="selectType == 'multiple'"
node-key="id"
@check="checkNode"
@node-click="nodeClick"
></el-tree>
</el-option>
</el-select>
</div>
</template>
<script>
export default {
name: 'SelectTreeTemplate',
props: {
/* 树形结构数据 */
treeOptions: {
type: Array,
default: () => {
return []
},
},
/* 单选/多选 */
selectType: {
type: String,
default: () => {
return 'single'
},
},
/* 初始选中值key */
selectedKey: {
type: String,
default: () => {
return ''
},
},
/* 初始选中值name */
selectedValue: {
type: String,
default: () => {
return ''
},
},
/* 可做选择的层级 */
selectLevel: {
type: [String, Number],
default: () => {
return ''
},
},
/* 可清空选项 */
clearable: {
type: Boolean,
default: () => {
return true
},
},
},
data() {
return {
defaultProps: {
children: 'children',
label: 'name',
},
defaultSelectedKeys: [], //初始选中值数组
currentNodeKey: this.selectedKey,
selectValue:
this.selectType == 'multiple'
? this.selectedValue.split(',')
: this.selectedValue, //下拉框选中值label
selectKey:
this.selectType == 'multiple'
? this.selectedKey.split(',')
: this.selectedKey, //下拉框选中值value
}
},
mounted() {
const that = this
this.initTree()
},
methods: {
// 初始化树的值
initTree() {
const that = this
if (that.selectedKey) {
that.defaultSelectedKeys = that.selectedKey.split(',') // 设置默认展开
if (that.selectType == 'single') {
that.$refs.treeOption.setCurrentKey(that.selectedKey) // 设置默认选中
} else {
that.$refs.treeOption.setCheckedKeys(that.defaultSelectedKeys)
}
}
},
// 清除选中
clearHandle() {
const that = this
this.selectValue = ''
this.selectKey = ''
this.defaultSelectedKeys = []
this.currentNodeKey = ''
this.clearSelected()
if (that.selectType == 'single') {
that.$refs.treeOption.setCurrentKey('') // 设置默认选中
} else {
that.$refs.treeOption.setCheckedKeys([])
}
},
/* 清空选中样式 */
clearSelected() {
const allNode = document.querySelectorAll('#treeOption .el-tree-node')
allNode.forEach((element) => element.classList.remove('is-current'))
},
// select多选时移除某项操作
removeTag(val) {
this.$refs.treeOption.setCheckedKeys([])
},
// 点击叶子节点
nodeClick(data, node, el) {
if (data.rank >= this.selectLevel) {
this.selectValue = data.name
this.selectKey = data.id
}
},
// 节点选中操作
checkNode(data, node, el) {
const checkedNodes = this.$refs.treeOption.getCheckedNodes()
const keyArr = []
const valueArr = []
checkedNodes.forEach((item) => {
if (item.rank >= this.selectLevel) {
keyArr.push(item.id)
valueArr.push(item.name)
}
})
this.selectValue = valueArr
this.selectKey = keyArr
},
},
}
</script>
<style lang="scss" scoped>
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
height: auto;
max-height: 274px;
padding: 0;
overflow-y: auto;
}
.el-select-dropdown__item.selected {
font-weight: normal;
}
ul li > .el-tree .el-tree-node__content {
height: auto;
padding: 0 20px;
}
.el-tree-node__label {
font-weight: normal;
}
.el-tree > .is-current .el-tree-node__label {
font-weight: 700;
color: #409eff;
}
.el-tree > .is-current .el-tree-node__children .el-tree-node__label {
font-weight: normal;
color: #606266;
}
</style>
<style lang="scss">
/* .vab-tree-select{
.el-tag__close.el-icon-close{
width:0;
overflow:hidden;
}
} */
</style>
<template>
<div class="content">
<div class="g-container" :style="styleObj">
<div class="g-number">
{{ endVal }}
</div>
<div class="g-contrast">
<div class="g-circle"></div>
<ul class="g-bubbles">
<li v-for="(item, index) in 15" :key="index"></li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VabCharge',
props: {
styleObj: {
type: Object,
default: () => {
return {}
},
},
startVal: {
type: Number,
default: 0,
},
endVal: {
type: Number,
default: 100,
},
},
data() {
return {
decimals: 2,
prefix: '',
suffix: '%',
separator: ',',
duration: 3000,
}
},
created() {},
mounted() {},
methods: {},
}
</script>
<style lang="scss" scoped>
.content {
position: relative;
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
width: 100%;
background: #000;
.g-number {
position: absolute;
top: 27%;
z-index: 99;
width: 300px;
font-size: 32px;
color: #fff;
text-align: center;
}
.g-container {
position: relative;
width: 300px;
height: 400px;
margin: auto;
}
.g-contrast {
width: 300px;
height: 400px;
overflow: hidden;
background-color: #000;
filter: contrast(15) hue-rotate(0);
animation: hueRotate 10s infinite linear;
}
.g-circle {
position: relative;
box-sizing: border-box;
width: 300px;
height: 300px;
filter: blur(8px);
&::after {
position: absolute;
top: 40%;
left: 50%;
width: 200px;
height: 200px;
content: '';
background-color: #00ff6f;
border-radius: 42% 38% 62% 49% / 45%;
transform: translate(-50%, -50%) rotate(0);
animation: rotate 10s infinite linear;
}
&::before {
position: absolute;
top: 40%;
left: 50%;
z-index: 99;
width: 176px;
height: 176px;
content: '';
background-color: #000;
border-radius: 50%;
transform: translate(-50%, -50%);
}
}
.g-bubbles {
position: absolute;
bottom: 0;
left: 50%;
width: 100px;
height: 40px;
background-color: #00ff6f;
filter: blur(5px);
border-radius: 100px 100px 0 0;
transform: translate(-50%, 0);
}
li {
position: absolute;
background: #00ff6f;
border-radius: 50%;
}
@for $i from 0 through 15 {
li:nth-child(#{$i}) {
$width: 15 + random(15) + px;
top: 50%;
left: 15 + random(70) + px;
width: $width;
height: $width;
transform: translate(-50%, -50%);
animation: moveToTop
#{random(6) +
3}s
ease-in-out -#{random(5000) /
1000}s
infinite;
}
}
@keyframes rotate {
50% {
border-radius: 45% / 42% 38% 58% 49%;
}
100% {
transform: translate(-50%, -50%) rotate(720deg);
}
}
@keyframes moveToTop {
90% {
opacity: 1;
}
100% {
opacity: 0.1;
transform: translate(-50%, -180px);
}
}
@keyframes hueRotate {
100% {
filter: contrast(15) hue-rotate(360deg);
}
}
}
</style>
<template>
<div class="card" :style="styleObj">
<div class="card-borders">
<div class="border-top"></div>
<div class="border-right"></div>
<div class="border-bottom"></div>
<div class="border-left"></div>
</div>
<div class="card-content">
<el-image :src="avatar" class="avatar"></el-image>
<div class="username">{{ username }}</div>
<div class="social-icons">
<a
v-for="(item, index) in iconArray"
:key="index"
class="social-icon"
:href="item.url"
target="_blank"
>
<vab-icon :icon="['fas', item.icon]" />
</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VabProfile',
props: {
styleObj: {
type: Object,
default: () => {
return {}
},
},
username: {
type: String,
default: '',
},
avatar: {
type: String,
default: '',
},
iconArray: {
type: Array,
default: () => {
return [
{ icon: 'bell', url: '' },
{ icon: 'bookmark', url: '' },
{ icon: 'cloud-sun', url: '' },
]
},
},
},
data() {
return {}
},
created() {},
mounted() {},
methods: {},
}
</script>
<style lang="scss" scoped>
.card {
--card-bg-color: hsl(240, 31%, 25%);
--card-bg-color-transparent: hsla(240, 31%, 25%, 0.7);
position: relative;
width: 100%;
height: 100%;
.card-borders {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
.border-top {
position: absolute;
top: 0;
width: 100%;
height: 2px;
background: var(--card-bg-color);
transform: translateX(-100%);
animation: slide-in-horizontal 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
forwards;
}
.border-right {
position: absolute;
right: 0;
width: 2px;
height: 100%;
background: var(--card-bg-color);
transform: translateY(100%);
animation: slide-in-vertical 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
forwards;
}
.border-bottom {
position: absolute;
bottom: 0;
width: 100%;
height: 2px;
background: var(--card-bg-color);
transform: translateX(100%);
animation: slide-in-horizontal-reverse 0.8s
cubic-bezier(0.645, 0.045, 0.355, 1) forwards;
}
.border-left {
position: absolute;
top: 0;
width: 2px;
height: 100%;
background: var(--card-bg-color);
transform: translateY(-100%);
animation: slide-in-vertical-reverse 0.8s
cubic-bezier(0.645, 0.045, 0.355, 1) forwards;
}
}
.card-content {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
padding: 40px 0 40px 0;
background: var(--card-bg-color-transparent);
opacity: 0;
transform: scale(0.6);
animation: bump-in 0.5s 0.8s forwards;
.avatar {
width: 80px;
height: 80px;
border: 1px solid $base-color-white;
border-radius: 50%;
opacity: 0;
transform: scale(0.6);
animation: bump-in 0.5s 1s forwards;
}
.username {
position: relative;
margin-top: 20px;
margin-bottom: 20px;
font-size: 26px;
color: transparent;
letter-spacing: 2px;
animation: fill-text-white 1.2s 2s forwards;
&::before {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
color: black;
content: '';
background: #35b9f1;
transform: scaleX(0);
transform-origin: left;
animation: slide-in-out 1.2s 1.2s cubic-bezier(0.75, 0, 0, 1) forwards;
}
}
.social-icons {
display: flex;
.social-icon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 2.5em;
height: 2.5em;
margin: 0 15px;
color: white;
text-decoration: none;
border-radius: 50%;
@for $i from 1 through 3 {
&:nth-child(#{$i}) {
&::before {
animation-delay: 2s + 0.1s * $i;
}
&::after {
animation-delay: 2.1s + 0.1s * $i;
}
svg {
animation-delay: 2.2s + 0.1s * $i;
}
}
}
&::before,
&::after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
content: '';
border-radius: inherit;
transform: scale(0);
}
&::before {
background: #f7f1e3;
animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
}
&::after {
background: #2c3e50;
animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
}
svg {
z-index: 99;
transform: scale(0);
animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
}
}
}
}
}
@keyframes bump-in {
50% {
transform: scale(1.05);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes slide-in-horizontal {
50% {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
@keyframes slide-in-horizontal-reverse {
50% {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
@keyframes slide-in-vertical {
50% {
transform: translateY(0);
}
to {
transform: translateY(-100%);
}
}
@keyframes slide-in-vertical-reverse {
50% {
transform: translateY(0);
}
to {
transform: translateY(100%);
}
}
@keyframes slide-in-out {
50% {
transform: scaleX(1);
transform-origin: left;
}
50.1% {
transform-origin: right;
}
100% {
transform: scaleX(0);
transform-origin: right;
}
}
@keyframes fill-text-white {
to {
color: white;
}
}
@keyframes scale-in {
to {
transform: scale(1);
}
}
</style>
<template>
<div class="content" :style="styleObj">
<div v-for="(item, index) in 200" :key="index" class="snow"></div>
</div>
</template>
<script>
export default {
name: 'VabSnow',
props: {
styleObj: {
type: Object,
default: () => {
return {}
},
},
},
data() {
return {}
},
created() {},
mounted() {},
methods: {},
}
</script>
<style lang="scss" scoped>
.content {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
filter: drop-shadow(0 0 10px white);
}
@function random_range($min, $max) {
$rand: random();
$random_range: $min + floor($rand * (($max - $min) + 1));
@return $random_range;
}
.snow {
$total: 200;
position: absolute;
width: 10px;
height: 10px;
background: white;
border-radius: 50%;
@for $i from 1 through $total {
$random-x: random(1000000) * 0.0001vw;
$random-offset: random_range(-100000, 100000) * 0.0001vw;
$random-x-end: $random-x + $random-offset;
$random-x-end-yoyo: $random-x + ($random-offset / 2);
$random-yoyo-time: random_range(30000, 80000) / 100000;
$random-yoyo-y: $random-yoyo-time * 100vh;
$random-scale: random(10000) * 0.0001;
$fall-duration: random_range(10, 30) * 1s;
$fall-delay: random(30) * -1s;
&:nth-child(#{$i}) {
opacity: random(10000) * 0.0001;
transform: translate($random-x, -10px) scale($random-scale);
animation: fall-#{$i} $fall-duration $fall-delay linear infinite;
}
@keyframes fall-#{$i} {
#{percentage($random-yoyo-time)} {
transform: translate($random-x-end, $random-yoyo-y)
scale($random-scale);
}
to {
transform: translate($random-x-end-yoyo, 100vh) scale($random-scale);
}
}
}
}
</style>
<template>
<el-dialog
:before-close="handleClose"
:close-on-click-modal="false"
:title="title"
:visible.sync="dialogFormVisible"
width="909px"
>
<div class="upload">
<el-alert
:closable="false"
:title="`支持jpg、jpeg、png格式,单次可最多选择${limit}张图片,每张不可大于${size}M,如果大于${size}M会自动为您过滤`"
type="info"
></el-alert>
<br />
<el-upload
ref="upload"
:action="action"
:auto-upload="false"
:close-on-click-modal="false"
:data="data"
:file-list="fileList"
:headers="headers"
:limit="limit"
:multiple="true"
:name="name"
:on-change="handleChange"
:on-error="handleError"
:on-exceed="handleExceed"
:on-preview="handlePreview"
:on-progress="handleProgress"
:on-remove="handleRemove"
:on-success="handleSuccess"
accept="image/png, image/jpeg"
class="upload-content"
list-type="picture-card"
>
<i slot="trigger" class="el-icon-plus"></i>
<el-dialog
:visible.sync="dialogVisible"
append-to-body
title="查看大图"
>
<div>
<img :src="dialogImageUrl" alt="" width="100%" />
</div>
</el-dialog>
</el-upload>
</div>
<div
slot="footer"
class="dialog-footer"
style="position: relative; padding-right: 15px; text-align: right"
>
<div
v-if="show"
style="position: absolute; top: 10px; left: 15px; color: #999"
>
正在上传中... 当前上传成功数:{{ imgSuccessNum }}张 当前上传失败数:{{
imgErrorNum
}}
</div>
<el-button type="primary" @click="handleClose">关闭</el-button>
<el-button
:loading="loading"
size="small"
style="margin-left: 10px"
type="success"
@click="submitUpload"
>
开始上传
</el-button>
</div>
</el-dialog>
</template>
<script>
import { baseURL, tokenName } from '@/config'
export default {
name: 'VabUpload',
props: {
url: {
type: String,
default: '/upload',
required: true,
},
name: {
type: String,
default: 'file',
required: true,
},
limit: {
type: Number,
default: 50,
required: true,
},
size: {
type: Number,
default: 1,
required: true,
},
},
data() {
return {
show: false,
loading: false,
dialogVisible: false,
dialogImageUrl: '',
action: 'https://vab-unicloud-3a9da9.service.tcloudbase.com/upload',
headers: {},
fileList: [],
picture: 'picture',
imgNum: 0,
imgSuccessNum: 0,
imgErrorNum: 0,
typeList: null,
title: '上传',
dialogFormVisible: false,
data: {},
}
},
computed: {
percentage() {
if (this.allImgNum == 0) return 0
return this.$baseLodash.round(this.imgNum / this.allImgNum, 2) * 100
},
},
methods: {
submitUpload() {
this.$refs.upload.submit()
},
handleProgress(event, file, fileList) {
this.loading = true
this.show = true
},
handleChange(file, fileList) {
if (file.size > 1048576 * this.size) {
fileList.map((item, index) => {
if (item === file) {
fileList.splice(index, 1)
}
})
this.fileList = fileList
} else {
this.allImgNum = fileList.length
}
},
handleSuccess(response, file, fileList) {
this.imgNum = this.imgNum + 1
this.imgSuccessNum = this.imgSuccessNum + 1
if (fileList.length === this.imgNum) {
setTimeout(() => {
this.$baseMessage(
`上传完成! 共上传${fileList.length}张图片`,
'success'
)
}, 1000)
}
setTimeout(() => {
this.loading = false
this.show = false
}, 1000)
},
handleError(err, file, fileList) {
this.imgNum = this.imgNum + 1
this.imgErrorNum = this.imgErrorNum + 1
this.$baseMessage(
`文件[${file.raw.name}]上传失败,文件大小为${this.$baseLodash.round(
file.raw.size / 1024,
0
)}KB`,
'error'
)
setTimeout(() => {
this.loading = false
this.show = false
}, 1000)
},
handleRemove(file, fileList) {
this.imgNum = this.imgNum - 1
this.allNum = this.allNum - 1
},
handlePreview(file) {
this.dialogImageUrl = file.url
this.dialogVisible = true
},
handleExceed(files, fileList) {
this.$baseMessage(
`当前限制选择 ${this.limit} 个文件,本次选择了
${files.length}
个文件`,
'error'
)
},
handleShow(data) {
this.title = '上传'
this.data = data
this.dialogFormVisible = true
},
handleClose() {
this.fileList = []
this.picture = 'picture'
this.allImgNum = 0
this.imgNum = 0
this.imgSuccessNum = 0
this.imgErrorNum = 0
/* if ("development" === process.env.NODE_ENV) {
this.api = process.env.VUE_APP_BASE_API;
} else {
this.api = `${window.location.protocol}//${window.location.host}`;
}
this.action = this.api + this.url; */
this.dialogFormVisible = false
},
},
}
</script>
<style lang="scss" scoped>
.upload {
height: 500px;
.upload-content {
.el-upload__tip {
display: block;
height: 30px;
line-height: 30px;
}
::v-deep {
.el-upload--picture-card {
width: 128px;
height: 128px;
margin: 3px 8px 8px 8px;
border: 2px dashed #c0ccda;
}
.el-upload-list--picture {
margin-bottom: 20px;
}
.el-upload-list--picture-card {
.el-upload-list__item {
width: 128px;
height: 128px;
margin: 3px 8px 8px 8px;
}
}
}
}
}
</style>
<template>
<div :id="id" ></div>
</template>
<script>
import * as monaco from 'monaco-editor'
export default {
props: {
content: {
type: String,
default: ""
},
readOnly: {
type: Boolean,
default: false,
},
id: {
type: String,
default: "container"
}
},
data(){
return {
editor:null,//文本编辑器
}
},
mounted(){
this.initEditor();
},
methods:{
initEditor(){
let thiz = this;
/**
* 自定义主题
*/
monaco.editor.defineTheme('BlackTheme', {
base: 'vs',
inherit: true,
rules: [{ background: '#08090d' }], //#202431
colors: {
// 相关颜色属性配置
'editor.background': '#2B2B2B', //背景色
'editor.lineHighlightBackground': '#515a6e20',
'editorLineNumber.foreground': '#008800',
// 'editor.selectionBackground': '#56beff7d',
// 'editor.inactiveSelectionBackground': '#88000015'
}
});
//设置自定义主题
monaco.editor.setTheme('BlackTheme');
// 初始化编辑器,确保dom已经渲染
this.editor = monaco.editor.create(document.getElementById(this.id), {
value: this.content,//编辑器初始显示文字
language:'sql',//语言支持自行查阅demo
// wordWrap: 'on',
automaticLayout: true,//自动布局
cursorStyle: "line", // 光标样式
mouseWheelZoom: false, //滚轮改变字体大小
overviewRulerBorder: true, //不要滚动条的边框
// scrollBeyondLastLine: false, //滚动是否超出最后一行
fontSize: 14,
readOnly: this.readOnly,
quickSuggestionsDelay: 500,
scrollbar: {
            useShadows:false,
                vertical:'visible',
                horizontal:'visible',
                horizontalSliderSize: 5,
                verticalSliderSize:16,
                horizontalScrollbarSize:15,
                verticalScrollbarSize:15,
            },
minimap: { enabled: false },
// lineNumbers: ,
theme:'vs-dark' //官方自带三种主题vs, hc-black, or vs-dark
});
this.editor.onDidChangeModelContent(function (e) {
thiz.$emit('changeCode', thiz.getValue())
});
var myBinding = this.editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S, function() {
thiz.$events.$emit(thiz.$eventType.saveEditor);
});
},
getValue(){
return this.editor.getValue(); //获取编辑器中的文本
},
setValue(val) {
this.editor.setValue(val);
}
}
}
</script>
<style>
/* .minimap.slider-mouseover {
display: none;
} */
</style>
/**
* @description 3个子配置,通用配置|主题配置|网络配置导出
*/
const setting = require('./setting.config')
const theme = require('./theme.config')
const network = require('./net.config')
module.exports = Object.assign({}, setting, theme, network)
/**
* @description 导出默认网路配置
**/
const network = {
// 默认的接口地址 如果是开发环境和生产环境走vab-mock-server,当然你也可以选择自己配置成需要的接口地址
baseURL:
process.env.NODE_ENV === 'development'
? 'vab-mock-server'
: 'vab-mock-server',
//配后端数据的接收方式application/json;charset=UTF-8或者application/x-www-form-urlencoded;charset=UTF-8
contentType: 'application/json;charset=UTF-8',
//消息框消失时间
messageDuration: 3000,
//最长请求时间
requestTimeout: 5000,
//操作正常code,支持String、Array、int多种类型
successCode: [200, 0],
//登录失效code
invalidCode: 402,
//无权限code
noPermissionCode: 401,
}
module.exports = network
/**
* @author https://vue-admin-beautiful.com (不想保留author可删除)
* @description 路由守卫,目前两种模式:all模式与intelligence模式
*/
import router from '@/router'
import store from '@/store'
import VabProgress from 'nprogress'
import 'nprogress/nprogress.css'
import getPageTitle from '@/utils/pageTitle'
import {
authentication,
loginInterception,
progressBar,
recordRoute,
routesWhiteList,
} from '@/config'
VabProgress.configure({
easing: 'ease',
speed: 500,
trickleSpeed: 200,
showSpinner: false,
})
router.beforeResolve(async (to, from, next) => {
if (progressBar) VabProgress.start()
let hasToken = store.getters['user/accessToken']
if (!loginInterception) hasToken = true
if (hasToken) {
if (to.path === '/login') {
next({ path: '/' })
if (progressBar) VabProgress.done()
} else {
const hasPermissions =
store.getters['user/permissions'] &&
store.getters['user/permissions'].length > 0
if (hasPermissions) {
next()
} else {
try {
let permissions
if (!loginInterception) {
//settings.js loginInterception为false时,创建虚拟权限
await store.dispatch('user/setPermissions', ['admin'])
permissions = ['admin']
} else {
permissions = await store.dispatch('user/getUserInfo')
}
let accessRoutes = []
if (authentication === 'intelligence') {
accessRoutes = await store.dispatch('routes/setRoutes', permissions)
} else if (authentication === 'all') {
accessRoutes = await store.dispatch('routes/setAllRoutes')
}
accessRoutes.forEach((item) => {
router.addRoute(item)
})
next({ ...to, replace: true })
} catch {
await store.dispatch('user/resetAccessToken')
if (progressBar) VabProgress.done()
}
}
}
} else {
if (routesWhiteList.indexOf(to.path) !== -1) {
next()
} else {
if (recordRoute) {
next(`/login?redirect=${to.path}`)
} else {
next('/login')
}
if (progressBar) VabProgress.done()
}
}
document.title = getPageTitle(to.meta.title)
})
router.afterEach(() => {
if (progressBar) VabProgress.done()
})
/**
* @description 导出默认通用配置
*/
const setting = {
// 开发以及部署时的URL
publicPath: '',
// 生产环境构建文件的目录名
outputDir: 'dist',
// 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。
assetsDir: 'static',
// 开发环境每次保存时是否输出为eslint编译警告
lintOnSave: false,
// 进行编译的依赖
transpileDependencies: [],
//标题 (包括初次加载雪花屏的标题 页面的标题 浏览器的标题)
title: '数据分析工具系统',
//简写
abbreviation: 'vab',
//开发环境端口号
devPort: '8081',
//版本号
version: process.env.VUE_APP_VERSION,
//这一项非常重要!请务必保留MIT协议下package.json及copyright作者信息 即可免费商用,不遵守此项约定你将无法使用该框架,如需自定义版权信息请联系QQ1204505056
copyright: 'vab',
//是否显示页面底部自定义版权信息
footerCopyright: true,
//是否显示顶部进度条
progressBar: true,
//缓存路由的最大数量
keepAliveMaxNum: 99,
// 路由模式,可选值为 history 或 hash
routerMode: 'hash',
//不经过token校验的路由
routesWhiteList: ['/login', '/register', '/404', '/401'],
//加载时显示文字
loadingText: '正在加载中...',
//token名称
tokenName: 'accessToken',
//token在localStorage、sessionStorage存储的key的名称
tokenTableName: 'vue-admin-beautiful-2021',
//token存储位置localStorage sessionStorage
storage: 'localStorage',
//token失效回退到登录页时是否记录本次的路由
recordRoute: true,
//是否显示logo,不显示时设置false,显示时请填写remixIcon图标名称,暂时只支持设置remixIcon
logo: 'vuejs-fill',
//是否显示在页面高亮错误
errorLog: ['development', 'production'],
//是否开启登录拦截
loginInterception: true,
//是否开启登录RSA加密
loginRSA: true,
//intelligence和all两种方式,前者后端权限只控制permissions不控制view文件的import(前后端配合,减轻后端工作量),all方式完全交给后端前端只负责加载
authentication: 'intelligence',
//vertical布局时是否只保持一个子菜单的展开
uniqueOpened: true,
//vertical布局时默认展开的菜单path,使用逗号隔开建议只展开一个
defaultOopeneds: ['/vab'],
//需要加loading层的请求,防止重复提交
debounce: ['doEdit'],
//需要自动注入并加载的模块
providePlugin: { maptalks: 'maptalks', 'window.maptalks': 'maptalks' },
//npm run build时是否自动生成7z压缩包
build7z: false,
//代码生成机生成在view下的文件夹名称
templateFolder: 'project',
//是否显示终端donation打印
donation: false,
//是否开启图片压缩
imageCompression: true,
}
module.exports = setting
/**
* @description 3个子配置,通用配置|主题配置|网络配置
*/
//默认配置
const { setting, theme, network } = require('./')
module.exports = Object.assign({}, setting, theme, network)
/**
* @description 导出默认主题配置
*/
const theme = {
//是否国定头部 固定fixed 不固定noFixed
header: 'fixed',
//横纵布局 horizontal vertical
layout: 'horizontal',
//是否开启主题配置按钮
themeBar: true,
//是否显示多标签页
tabsBar: false,
}
module.exports = theme
<template>
<router-view />
</template>
<template>
<div v-if="routerView" class="app-main-container">
<transition mode="out-in" name="fade-transform">
<keep-alive :include="cachedRoutes" :max="keepAliveMaxNum">
<router-view :key="key" class="app-main-height" />
</keep-alive>
</transition>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { copyright, footerCopyright, keepAliveMaxNum, title } from '@/config'
export default {
name: 'VabAppMain',
data() {
return {
show: false,
fullYear: new Date().getFullYear(),
copyright,
title,
keepAliveMaxNum,
routerView: true,
footerCopyright,
}
},
computed: {
...mapGetters({
visitedRoutes: 'tabsBar/visitedRoutes',
device: 'settings/device',
}),
cachedRoutes() {
const cachedRoutesArr = []
this.visitedRoutes.forEach((item) => {
if (!item.meta.noKeepAlive) {
cachedRoutesArr.push(item.name)
}
})
return cachedRoutesArr
},
key() {
return this.$route.path
},
},
watch: {
$route: {
handler(route) {
if ('mobile' === this.device) this.foldSideBar()
},
immediate: true,
},
},
created() {
//重载所有路由
this.$baseEventBus.$on('reload-router-view', () => {
this.routerView = false
this.$nextTick(() => {
this.routerView = true
})
})
},
mounted() {},
methods: {
...mapActions({
foldSideBar: 'settings/foldSideBar',
}),
},
}
</script>
<style lang="scss" scoped>
.app-main-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
.vab-keel {
margin: $base-padding;
}
.app-main-height {
min-height: $base-app-main-height;
}
.footer-copyright {
position: fixed;
bottom: 0px;
background-color: white;
width: 88%;
min-height: 55px;
line-height: 55px;
color: rgba(0, 0, 0, 0.45);
text-align: center;
border-top: 1px dashed $base-border-color;
}
}
</style>
<template>
<el-dropdown @command="handleCommand">
<span class="avatar-dropdown">
<img class="user-avatar" :src="avatar" alt="" />
<div class="user-name">
{{ username }}
<i class="el-icon-arrow-down el-icon--right"></i>
</div>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
import { mapGetters } from 'vuex'
import { recordRoute } from '@/config'
export default {
name: 'VabAvatar',
computed: {
...mapGetters({
avatar: 'user/avatar',
username: 'user/username',
}),
},
methods: {
handleCommand(command) {
switch (command) {
case 'logout':
this.logout()
break
}
},
personalCenter() {
this.$router.push('/personalCenter/personalCenter')
},
logout() {
this.$baseConfirm(
'您确定要退出' + this.$baseTitle + '吗?',
null,
async () => {
await this.$store.dispatch('user/logout')
if (recordRoute) {
const fullPath = this.$route.fullPath
this.$router.push(`/login?redirect=${fullPath}`)
} else {
this.$router.push('/login')
}
}
)
},
},
}
</script>
<style lang="scss" scoped>
.avatar-dropdown {
display: flex;
align-content: center;
align-items: center;
justify-content: center;
justify-items: center;
height: 50px;
padding: 0;
.user-avatar {
width: 40px;
height: 40px;
cursor: pointer;
border-radius: 50%;
}
.user-name {
position: relative;
margin-left: 5px;
margin-left: 5px;
cursor: pointer;
}
}
</style>
<template>
<el-breadcrumb class="breadcrumb-container" separator="/">
<el-breadcrumb-item v-for="item in list" :key="item.path" :to="{ path: item.redirect}">
<template v-if="item.path == $route.path">
{{ $route.query.name }}
</template>
<template v-else>
{{ item.meta.title }}
</template>
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script>
export default {
name: 'VabBreadcrumb',
data() {
return {
list: this.getBreadcrumb(),
}
},
watch: {
$route() {
this.list = this.getBreadcrumb()
},
},
methods: {
getBreadcrumb() {
return this.$route.matched.filter(
(item) => item.name && item.meta.title
)
},
goBack(item) {
console.log(item)
}
},
}
</script>
<style lang="scss" scoped>
.breadcrumb-container {
height: $base-nav-bar-height;
font-size: $base-font-size-default;
line-height: $base-nav-bar-height;
::v-deep {
.el-breadcrumb__item {
.el-breadcrumb__inner {
a {
display: flex;
float: left;
font-weight: normal;
color: #515a6e;
i {
margin-right: 3px;
}
}
}
&:last-child {
.el-breadcrumb__inner {
a {
color: #999;
}
}
}
}
}
}
</style>
<template>
<div :class="'logo-container-' + layout">
<router-link to="/">
<!-- 这里是logo变更的位置 -->
<vab-remix-icon v-if="logo" class="logo" :icon-class="logo" />
<span
class="title"
:class="{ 'hidden-xs-only': layout === 'horizontal' }"
:title="title"
>
{{ title }}
</span>
</router-link>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'VabLogo',
data() {
return {
title: this.$baseTitle,
}
},
computed: {
...mapGetters({
logo: 'settings/logo',
layout: 'settings/layout',
}),
},
}
</script>
<style lang="scss" scoped>
@mixin container {
position: relative;
height: $base-top-bar-height;
overflow: hidden;
line-height: $base-top-bar-height;
background: $base-menu-background;
box-shadow: 1px 1px 1px 1px red;
}
@mixin logo {
display: inline-block;
width: 34px;
height: 34px;
margin-right: 3px;
color: $base-title-color;
vertical-align: middle;
}
@mixin title {
display: inline-block;
overflow: hidden;
font-size: 24px;
line-height: 55px;
color: $base-title-color;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
.logo-container-horizontal {
@include container;
.logo {
@include logo;
}
.title {
@include title;
}
}
.logo-container-vertical {
@include container;
height: $base-logo-height;
line-height: $base-logo-height;
text-align: center;
.logo {
@include logo;
}
.title {
@include title;
max-width: calc(#{$base-left-menu-width} - 60px);
}
}
</style>
<template>
<div class="nav-bar-container">
<el-row :gutter="15">
<el-col :xs="4" :sm="12" :md="12" :lg="12" :xl="12">
<div class="left-panel">
<i
:class="collapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'"
:title="collapse ? '展开' : '收起'"
class="fold-unfold"
@click="handleCollapse"
></i>
<vab-breadcrumb class="hidden-xs-only" />
</div>
</el-col>
<el-col :xs="20" :sm="12" :md="12" :lg="12" :xl="12">
<div class="right-panel">
<vab-theme-bar class="hidden-xs-only" />
<vab-icon
title="重载所有路由"
:pulse="pulse"
:icon="['fas', 'redo']"
@click="refreshRoute"
/>
<vab-avatar />
<!-- <vab-icon
title="退出系统"
:icon="['fas', 'sign-out-alt']"
@click="logout"
/>-->
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'VabNavBar',
data() {
return {
pulse: false,
}
},
computed: {
...mapGetters({
collapse: 'settings/collapse',
visitedRoutes: 'tabsBar/visitedRoutes',
device: 'settings/device',
routes: 'routes/routes',
}),
},
methods: {
...mapActions({
changeCollapse: 'settings/changeCollapse',
}),
handleCollapse() {
this.changeCollapse()
},
async refreshRoute() {
this.$baseEventBus.$emit('reload-router-view')
this.pulse = true
setTimeout(() => {
this.pulse = false
}, 1000)
},
},
}
</script>
<style lang="scss" scoped>
.nav-bar-container {
position: relative;
height: $base-nav-bar-height;
padding-right: $base-padding;
padding-left: $base-padding;
overflow: hidden;
user-select: none;
background: $base-color-white;
box-shadow: $base-box-shadow;
.left-panel {
display: flex;
align-items: center;
justify-items: center;
height: $base-nav-bar-height;
.fold-unfold {
color: $base-color-gray;
cursor: pointer;
}
::v-deep {
.breadcrumb-container {
margin-left: 10px;
}
}
}
.right-panel {
display: flex;
align-content: center;
align-items: center;
justify-content: flex-end;
height: $base-nav-bar-height;
::v-deep {
svg {
width: 1em;
height: 1em;
margin-right: 15px;
font-size: $base-font-size-small;
color: $base-color-gray;
cursor: pointer;
fill: $base-color-gray;
}
button {
svg {
margin-right: 0;
color: $base-color-white;
cursor: pointer;
fill: $base-color-white;
}
}
.el-badge {
margin-right: 15px;
}
}
}
}
</style>
<template>
<span v-if="false">
<vab-icon
title="主题配置"
:icon="['fas', 'palette']"
@click="handleOpenThemeBar"
/>
<el-drawer
title="主题配置"
:visible.sync="drawerVisible"
direction="rtl"
append-to-body
size="300px"
>
<el-scrollbar style="height: 80vh; overflow: hidden">
<div class="el-drawer__body">
<el-form ref="form" :model="theme" label-position="top">
<el-form-item label="主题">
<el-radio-group v-model="theme.name">
<el-radio-button label="default">默认</el-radio-button>
<el-radio-button label="green">绿荫草场</el-radio-button>
<el-radio-button label="glory">荣耀典藏</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="布局">
<el-radio-group v-model="theme.layout">
<el-radio-button label="vertical">纵向布局</el-radio-button>
<el-radio-button label="horizontal">横向布局</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="头部">
<el-radio-group v-model="theme.header">
<el-radio-button label="fixed">固定头部</el-radio-button>
<el-radio-button label="noFixed">不固定头部</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="多标签">
<el-radio-group v-model="theme.tabsBar">
<el-radio-button label="true">开启</el-radio-button>
<el-radio-button label="false">不开启</el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
</div>
</el-scrollbar>
<div class="el-drawer__footer">
<el-button type="primary" @click="handleSaveTheme">保存</el-button>
<el-button type="" @click="drawerVisible = false">取消</el-button>
</div>
</el-drawer>
</span>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { layout as defaultLayout } from '@/config'
export default {
name: 'VabThemeBar',
data() {
return {
drawerVisible: false,
theme: {
name: 'default',
layout: '',
header: 'fixed',
tabsBar: '',
},
}
},
computed: {
...mapGetters({
layout: 'settings/layout',
header: 'settings/header',
tabsBar: 'settings/tabsBar',
themeBar: 'settings/themeBar',
}),
},
created() {
const theme = localStorage.getItem('vue-admin-beautiful-theme')
if (null !== theme) {
this.theme = JSON.parse(theme)
this.handleSetTheme()
} else {
this.theme.layout = this.layout
this.theme.header = this.header
this.theme.tabsBar = this.tabsBar
}
},
methods: {
...mapActions({
changeLayout: 'settings/changeLayout',
changeHeader: 'settings/changeHeader',
changeTabsBar: 'settings/changeTabsBar',
}),
handleIsMobile() {
return document.body.getBoundingClientRect().width - 1 < 992
},
handleOpenThemeBar() {
this.drawerVisible = true
},
handleSetTheme() {
let { name, layout, header, tabsBar } = this.theme
localStorage.setItem(
'vue-admin-beautiful-theme',
`{
"name":"${name}",
"layout":"${layout}",
"header":"${header}",
"tabsBar":"${tabsBar}"
}`
)
if (!this.handleIsMobile()) this.changeLayout(layout)
this.changeHeader(header)
this.changeTabsBar(tabsBar)
document.getElementsByTagName(
'body'
)[0].className = `vue-admin-beautiful-theme-${name}`
this.drawerVisible = false
},
handleSaveTheme() {
this.handleSetTheme()
},
handleSetDfaultTheme() {
let { name } = this.theme
document
.getElementsByTagName('body')[0]
.classList.remove(`vue-admin-beautiful-theme-${name}`)
localStorage.removeItem('vue-admin-beautiful-theme')
this.$refs['form'].resetFields()
Object.assign(this.$data, this.$options.data())
this.changeHeader(defaultLayout)
this.theme.name = 'default'
this.theme.layout = this.layout
this.theme.header = this.header
this.theme.tabsBar = this.tabsBar
this.drawerVisible = false
location.reload()
},
},
}
</script>
<style lang="scss" scoped>
@mixin right-bar {
position: fixed;
right: 0;
z-index: $base-z-index;
width: 60px;
min-height: 60px;
text-align: center;
cursor: pointer;
background: $base-color-blue;
border-radius: $base-border-radius;
> div {
padding-top: 10px;
border-bottom: 0 !important;
&:hover {
opacity: 0.9;
}
& + div {
border-top: 1px solid $base-color-white;
}
p {
padding: 0;
margin: 0;
font-size: $base-font-size-small;
line-height: 30px;
color: $base-color-white;
}
}
}
.theme-bar-setting {
@include right-bar;
top: calc((100vh - 110px) / 2);
::v-deep {
svg:not(:root).svg-inline--fa {
display: block;
margin-right: auto;
margin-left: auto;
color: $base-color-white;
}
.svg-icon {
display: block;
margin-right: auto;
margin-left: auto;
font-size: 20px;
color: $base-color-white;
fill: $base-color-white;
}
}
}
.el-drawer__body {
padding: 20px;
}
.el-drawer__footer {
border-top: 1px solid #dedede;
position: fixed;
bottom: 0;
width: 100%;
padding: 10px 0 0 20px;
height: 50px;
}
</style>
<style lang="scss">
.el-drawer__wrapper {
outline: none !important;
* {
outline: none !important;
}
}
.vab-color-picker {
.el-color-dropdown__link-btn {
display: none;
}
}
</style>
/**
* @author https://vue-admin-beautiful.com (不想保留author可删除)
* @description 公共布局及样式自动引入
*/
import Vue from 'vue'
const requireComponents = require.context('./components', true, /\.vue$/)
requireComponents.keys().forEach((fileName) => {
const componentConfig = requireComponents(fileName)
const componentName = componentConfig.default.name
Vue.component(componentName, componentConfig.default || componentConfig)
})
const requireZxLayouts = require.context('layouts', true, /\.vue$/)
requireZxLayouts.keys().forEach((fileName) => {
const componentConfig = requireZxLayouts(fileName)
const componentName = componentConfig.default.name
Vue.component(componentName, componentConfig.default || componentConfig)
})
const requireThemes = require.context('@/styles/themes', true, /\.scss$/)
requireThemes.keys().forEach((fileName) => {
require(`@/styles/themes/${fileName.slice(2)}`)
})
<template>
<div class="vue-admin-beautiful-wrapper" >
<div
v-if="true"
class="layout-container-horizontal"
:class="{
fixed: header === 'fixed',
'no-tabs-bar': tabsBar === 'false' || tabsBar === false,
}"
>
<div :class="header === 'fixed' ? 'fixed-header' : ''">
<vab-top-bar />
<div v-if="tabsBar === 'true' || tabsBar === true" :class="{ 'tag-view-show': tabsBar }">
<div >
<vab-tabs-bar />
</div>
</div>
</div>
<div class="vab-main">
<vab-app-main />
</div>
</div>
<el-backtop />
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { tokenName } from '@/config'
export default {
name: 'Layout',
data() {
return { oldLayout: '' }
},
computed: {
...mapGetters({
layout: 'settings/layout',
tabsBar: 'settings/tabsBar',
collapse: 'settings/collapse',
header: 'settings/header',
device: 'settings/device',
}),
},
beforeMount() {
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
},
mounted() {
this.oldLayout = this.layout
const userAgent = navigator.userAgent
if (userAgent.includes('Juejin')) {
this.$baseAlert(
'vue-admin-beautiful不支持在掘金内置浏览器演示,请手动复制以下地址到浏览器中查看http://mpfhrd48.sanxing.uz7.cn/vue-admin-beautiful'
)
}
this.$store.dispatch('settings/openSideBar')
this.$nextTick(() => {
window.addEventListener(
'storage',
(e) => {
if (e.key === tokenName || e.key === null) window.location.reload()
if (e.key === tokenName && e.value === null)
window.location.reload()
},
false
)
})
},
methods: {
...mapActions({
handleFoldSideBar: 'settings/foldSideBar',
}),
handleResize() {
if (!document.hidden) {
this.$store.dispatch('settings/changeLayout', this.oldLayout)
this.$store.dispatch(
'settings/toggleDevice',
isMobile ? 'mobile' : 'desktop'
)
}
},
},
}
</script>
<style lang="scss" scoped>
@mixin fix-header {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: $base-z-index - 2;
width: 100%;
overflow: hidden;
box-shadow: 0px -17px 14px 17px #dfdfdf;
}
.vue-admin-beautiful-wrapper {
position: relative;
width: 100%;
height: 100%;
.layout-container-horizontal {
position: relative;
height: 90vh;
&.fixed {
padding-top: calc(#{$base-top-bar-height} + #{$base-tabs-bar-height});
}
&.fixed.no-tabs-bar {
padding-top: $base-top-bar-height;
}
::v-deep {
.vab-main {
width: 100%;
height: 100%;
margin: auto;
}
.fixed-header {
@include fix-header;
}
.tag-view-show {
background: $base-color-white;
box-shadow: $base-box-shadow;
}
.nav-bar-container {
.fold-unfold {
display: none;
}
}
}
}
}
</style>
import Vue from 'vue'
import App from './App'
import store from './store'
import router from './router'
import './plugins'
import '@/layouts/export'
import './assets/iconfont/iconfont.css'
import './assets/iconfont/iconfont.js'
if (process.env.NODE_ENV === 'production') {
const { mockXHR } = require('@/utils/static')
mockXHR()
}
Vue.config.productionTip = false
new Vue({
el: '#vue-admin-beautiful',
router,
store,
render: (h) => h(App),
})
import 'echarts'
import VabChart from 'vue-echarts'
export default VabChart
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/display.css'
import '@/styles/element-variables.scss'
Vue.use(ElementUI, {
size: 'small',
})
/* 公共引入,勿随意修改,修改时需经过确认 */
import Vue from 'vue'
import './element'
import './support'
import '@/styles/vab.scss'
import '@/colorfulIcon'
import '@/config/permission'
import '@/utils/errorLog'
import './vabIcon'
import VabPermissions from 'layouts/Permissions'
import Vab from '@/utils/vab'
Vue.use(Vab)
Vue.use(VabPermissions)
// 在Vue中使用,不需要可以去除以下引用
import Vue from 'vue'
import {storage} from './store.js'
// 导出socket对象
export {
socket
}
// socket主要对象
var socket = {
websock: null,
// 固定的WebSocket地址:此处是从env文件中读取socket地址,可以自行从其他config文件中读取或直接写死
// 如需使用动态WebSocket地址,请自行作ajax通讯后扩展
ws_url: "ws://localhost:8071/ws/message",
headers: {
Authorization: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3R5cGUiOiJjYXJlciIsImdyYW50X3R5cGUiOiJzbXMiLCJpc3MiOiJoc3B6LmNvbSIsImNsaWVudCI6Ind4QXBwbGV0IiwidHlwZSI6ImFjY2VzcyIsImdyYW50IjoxNjg3MDczMzU5OTU0LCJleHAiOjE2OTU3MTMzNTksInVzZXIiOiIxMzQwOTYxNjkwNjAzMTEwNCIsImlhdCI6MTY4NzA3MzE3OX0.k9B0JxwyBJ_sNQcLR8TOYbW5i6YwHKyaxHcl5MAtJ-fGfb31c1cUmo5Djq4MeHMFHcblZ0v3W9mPE6bZPMskkCxgV_yceirL8R31fDG7rKeIacIJws_NnpXIgADiTwPD4Swbrax_vT8A01bR_j_TsLiR_aduiINNd3pI2yG-DFs"
},
// 开启标识
socket_open: false,
// 心跳timer
hearbeat_timer: null,
// 心跳发送频率
hearbeat_interval: 5000,
// 是否自动重连
is_reonnect: true,
// 重连次数
reconnect_count: 3,
// 已发起重连次数
reconnect_current: 0,
// 重连timer
reconnect_timer: null,
// 重连频率
reconnect_interval: 3000,
/**
* 初始化连接
*/
init: () => {
if (!("WebSocket" in window)) {
console.log('浏览器不支持WebSocket')
return null
}
console.log("init")
// 已经创建过连接不再重复创建
if (socket.websock) {
return socket.websock
}
console.log("新建连接")
let token = "?Authorization=" + socket.headers.Authorization
socket.websock = new WebSocket(socket.ws_url+token)
socket.websock.onmessage = function (e) {
socket.receive(e)
}
// 关闭连接
socket.websock.onclose = function (e) {
console.log('连接已断开')
console.log('connection closed (' + e.code + ')')
console.log(e)
clearInterval(socket.hearbeat_interval)
socket.socket_open = false
// 需要重新连接
if (socket.is_reonnect) {
socket.reconnect_timer = setTimeout(() => {
// 超过重连次数
if (socket.reconnect_current > socket.reconnect_count) {
clearTimeout(socket.reconnect_timer)
clearInterval(socket.hearbeat_timer)
return
}
// 记录重连次数
socket.reconnect_current++
socket.reconnect()
}, socket.reconnect_interval)
}
}
// 连接成功
socket.websock.onopen = function () {
console.log('连接成功')
socket.socket_open = true
socket.is_reonnect = true
// 开启心跳
socket.heartbeat()
}
// 连接发生错误
socket.websock.onerror = function (e) {
console.log(e)
console.log('WebSocket连接发生错误')
}
},
/**
* 发送消息
* @param {*} data 发送数据
* @param {*} callback 发送后的自定义回调函数
*/
send: (data, callback = null) => {
// 开启状态直接发送
if (socket.websock.readyState === socket.websock.OPEN) {
socket.websock.send(JSON.stringify(data))
if (callback) {
callback()
}
// 正在开启状态,则等待1s后重新调用
} else if (socket.websock.readyState === socket.websock.CONNECTING) {
setTimeout(function () {
socket.send(data, callback)
}, 2000)
// 未开启,则等待1s后重新调用
} else {
console.log("发送")
socket.init()
setTimeout(function () {
socket.send(data, callback)
}, 2000)
}
},
/**
* 接收消息
* @param {*} message 接收到的消息
*/
receive: (message) => {
var params = JSON.parse(message.data)
console.log("收到消息")
console.log(message)
// 自行扩展其他业务处理...
},
/**
* 心跳
*/
heartbeat: () => {
console.log('socket', 'ping')
if (socket.hearbeat_timer) {
clearInterval(socket.hearbeat_timer)
}
socket.hearbeat_timer = setInterval(() => {
console.log("发送心跳")
// const token = storage.get('testpwdapp_access_token')
var data = {
kind: 0, //请求类型 kind 0 心跳包
}
socket.send(data)
}, socket.hearbeat_interval)
},
/**
* 主动关闭连接
*/
close: () => {
console.log('主动断开连接')
clearInterval(socket.hearbeat_interval)
socket.is_reonnect = false
socket.websock.close()
},
/**
* 重新连接
*/
reconnect: () => {
console.log('发起重新连接', socket.reconnect_current)
if (socket.websock && socket.socket_open) {
socket.websock.close()
}
socket.init()
},
}
let storage = {
get(key) {
return localStorage.getItem(key) || ''
}
}
// 导出socket对象
export {
storage
}
\ No newline at end of file
import { MessageBox } from 'element-ui'
import { donation } from '@/config'
import { dependencies, repository } from '../../package.json'
if (!!window.ActiveXObject || 'ActiveXObject' in window) {
MessageBox({
title: '温馨提示',
message:
'自2015年3月起,微软已宣布弃用IE,且不再对IE提供任何更新维护,请<a target="_blank" style="color:blue" href="https://www.microsoft.com/zh-cn/edge/">点击此处</a>访问微软官网更新浏览器,如果您使用的是双核浏览器,请您切换浏览器内核为极速模式',
type: 'warning',
showClose: false,
showConfirmButton: false,
closeOnClickModal: false,
closeOnPressEscape: false,
closeOnHashChange: false,
dangerouslyUseHTMLString: true,
})
}
if (!dependencies['vab-icon'] || !dependencies['layouts'])
document.body.innerHTML = ''
import Vue from 'vue'
import VabIcon from 'vab-icon'
Vue.component('VabIcon', VabIcon)
This diff is collapsed.
/**
* @author https://vue-admin-beautiful.com (不想保留author可删除)
* @description 导入所有 vuex 模块,自动加入namespaced:true,用于解决vuex命名冲突,请勿修改。
*/
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const files = require.context('./modules', false, /\.js$/)
const modules = {}
files.keys().forEach((key) => {
modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
})
Object.keys(modules).forEach((key) => {
modules[key]['namespaced'] = true
})
const store = new Vuex.Store({
modules,
})
export default store
/**
* @author https://vue-admin-beautiful.com (不想保留author可删除)
* @description 异常捕获的状态拦截,请勿修改
*/
const state = () => ({
errorLogs: [],
})
const getters = {
errorLogs: (state) => state.errorLogs,
}
const mutations = {
addErrorLog(state, errorLog) {
state.errorLogs.push(errorLog)
},
clearErrorLog: (state) => {
state.errorLogs.splice(0)
},
}
const actions = {
addErrorLog({ commit }, errorLog) {
commit('addErrorLog', errorLog)
},
clearErrorLog({ commit }) {
commit('clearErrorLog')
},
}
export default { state, getters, mutations, actions }
/**
* @author https://vue-admin-beautiful.com (不想保留author可删除)
* @description 路由拦截状态管理,目前两种模式:all模式与intelligence模式,其中partialRoutes是菜单暂未使用
*/
import { asyncRoutes, constantRoutes } from '@/router'
import { getRouterList } from '@/api/router'
import { convertRouter, filterAsyncRoutes } from '@/utils/handleRoutes'
const state = () => ({
routes: [],
partialRoutes: [],
})
const getters = {
routes: (state) => state.routes,
partialRoutes: (state) => state.partialRoutes,
}
const mutations = {
setRoutes(state, routes) {
state.routes = constantRoutes.concat(routes)
},
setAllRoutes(state, routes) {
state.routes = constantRoutes.concat(routes)
},
setPartialRoutes(state, routes) {
state.partialRoutes = constantRoutes.concat(routes)
},
}
const actions = {
async setRoutes({ commit }, permissions) {
//开源版只过滤动态路由permissions,admin不再默认拥有全部权限
const finallyAsyncRoutes = await filterAsyncRoutes(
[...asyncRoutes],
permissions
)
commit('setRoutes', finallyAsyncRoutes)
return finallyAsyncRoutes
},
async setAllRoutes({ commit }) {
let { data } = await getRouterList()
data.push({ path: '*', redirect: '/404', hidden: true })
let accessRoutes = convertRouter(data)
commit('setAllRoutes', accessRoutes)
return accessRoutes
},
setPartialRoutes({ commit }, accessRoutes) {
commit('setPartialRoutes', accessRoutes)
return accessRoutes
},
}
export default { state, getters, mutations, actions }
/**
* @author https://vue-admin-beautiful.com (不想保留author可删除)
* @description 所有全局配置的状态管理,如无必要请勿修改
*/
import defaultSettings from '@/config'
const { tabsBar, logo, layout, header, themeBar } = defaultSettings
const theme =
JSON.parse(localStorage.getItem('vue-admin-beautiful-theme')) || ''
const state = () => ({
tabsBar: theme.tabsBar || tabsBar,
logo,
collapse: false,
layout: theme.layout || layout,
header: theme.header || header,
device: 'desktop',
themeBar,
})
const getters = {
collapse: (state) => state.collapse,
device: (state) => state.device,
header: (state) => state.header,
layout: (state) => state.layout,
logo: (state) => state.logo,
tabsBar: (state) => state.tabsBar,
themeBar: (state) => state.themeBar,
}
const mutations = {
changeLayout: (state, layout) => {
if (layout) state.layout = layout
},
changeHeader: (state, header) => {
if (header) state.header = header
},
changeTabsBar: (state, tabsBar) => {
if (tabsBar) state.tabsBar = tabsBar
},
changeCollapse: (state) => {
state.collapse = !state.collapse
},
foldSideBar: (state) => {
state.collapse = true
},
openSideBar: (state) => {
state.collapse = false
},
toggleDevice: (state, device) => {
state.device = device
},
}
const actions = {
changeLayout({ commit }, layout) {
commit('changeLayout', layout)
},
changeHeader({ commit }, header) {
commit('changeHeader', header)
},
changeTabsBar({ commit }, tabsBar) {
commit('changeTabsBar', tabsBar)
},
changeCollapse({ commit }) {
commit('changeCollapse')
},
foldSideBar({ commit }) {
commit('foldSideBar')
},
openSideBar({ commit }) {
commit('openSideBar')
},
toggleDevice({ commit }, device) {
commit('toggleDevice', device)
},
}
export default { state, getters, mutations, actions }
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment