init
12
src/App.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div id="vue-admin-better">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'App',
|
||||
mounted() {},
|
||||
}
|
||||
</script>
|
||||
8
src/api/ad.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getList() {
|
||||
return request({
|
||||
url: 'https://api.vuejs-core.cn/getAd',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
33
src/api/appManagement.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getList(data) {
|
||||
return request({
|
||||
url: '/management/api/app/list',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doAdd(data) {
|
||||
return request({
|
||||
url: '/management/api/app',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doEdit(id, data) {
|
||||
return request({
|
||||
url: `/management/api/app/${id}`,
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doDelete(data) {
|
||||
return request({
|
||||
url: '/management/api/app/delete',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
9
src/api/colorfulIcon.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getIconList(data) {
|
||||
return request({
|
||||
url: '/colorfulIcon/getList',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
19
src/api/github.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import request from 'axios'
|
||||
|
||||
export function getRepos(params) {
|
||||
return request({
|
||||
url: 'https://api.github.com/repos/zxwk1998/vue-admin-better',
|
||||
method: 'get',
|
||||
params,
|
||||
timeout: 10000,
|
||||
})
|
||||
}
|
||||
|
||||
export function getStargazers(params) {
|
||||
return request({
|
||||
url: 'https://api.github.com/repos/zxwk1998/vue-admin-better/stargazers',
|
||||
method: 'get',
|
||||
params,
|
||||
timeout: 10000,
|
||||
})
|
||||
}
|
||||
9
src/api/goodsList.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getList(data) {
|
||||
return request({
|
||||
url: '/goodsList/getList',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
9
src/api/icon.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getIconList(data) {
|
||||
return request({
|
||||
url: '/icon/getList',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
8
src/api/markdown.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from 'axios'
|
||||
|
||||
export function getList() {
|
||||
return request({
|
||||
url: 'https://gcore.jsdelivr.net/gh/prettier/prettier@master/docs/options.md',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
25
src/api/menuManagement.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getTree(data) {
|
||||
return request({
|
||||
url: '/menuManagement/getTree',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doEdit(data) {
|
||||
return request({
|
||||
url: '/menuManagement/doEdit',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doDelete(data) {
|
||||
return request({
|
||||
url: '/menuManagement/doDelete',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
33
src/api/miniAnliCategory.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getList(data) {
|
||||
return request({
|
||||
url: '/management/api/role/list',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doAdd(data) {
|
||||
return request({
|
||||
url: '/management/api/role',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doEdit(id, data) {
|
||||
return request({
|
||||
url: `/management/api/role/${id}`,
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doDelete(data) {
|
||||
return request({
|
||||
url: '/management/api/role/delete',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
8
src/api/notice.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getNoticeList() {
|
||||
return request({
|
||||
url: 'https://api.vuejs-core.cn/getNotice',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
25
src/api/personalCenter.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getList(data) {
|
||||
return request({
|
||||
url: '/personalCenter/getList',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doEdit(data) {
|
||||
return request({
|
||||
url: '/personalCenter/doEdit',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doDelete(data) {
|
||||
return request({
|
||||
url: '/personalCenter/doDelete',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
8
src/api/publicKey.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getPublicKey() {
|
||||
return request({
|
||||
url: '/api/auth/publicKey',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
9
src/api/remixIcon.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getIconList(data) {
|
||||
return request({
|
||||
url: '/remixIcon/getList',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
33
src/api/roleManagement.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getList(data) {
|
||||
return request({
|
||||
url: '/management/api/role/list',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doAdd(data) {
|
||||
return request({
|
||||
url: '/management/api/role',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doEdit(id, data) {
|
||||
return request({
|
||||
url: `/management/api/role/${id}`,
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doDelete(data) {
|
||||
return request({
|
||||
url: '/management/api/role/delete',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
9
src/api/router.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getRouterList(data) {
|
||||
return request({
|
||||
url: '/menu/navigate',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
25
src/api/table.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getList(data) {
|
||||
return request({
|
||||
url: '/table/getList',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doEdit(data) {
|
||||
return request({
|
||||
url: '/table/doEdit',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doDelete(data) {
|
||||
return request({
|
||||
url: '/table/doDelete',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
9
src/api/tree.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getTreeList(data) {
|
||||
return request({
|
||||
url: '/tree/list',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
38
src/api/user.js
Normal file
@@ -0,0 +1,38 @@
|
||||
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: '/api/auth/login',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function getUserInfo(accessToken) {
|
||||
return request({
|
||||
url: '/management/api/user/current',
|
||||
method: 'post',
|
||||
data: {
|
||||
[tokenName]: accessToken,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
return request({
|
||||
url: '/api/auth/logout',
|
||||
method: 'post',
|
||||
})
|
||||
}
|
||||
|
||||
export function register() {
|
||||
return request({
|
||||
url: '/register',
|
||||
method: 'post',
|
||||
})
|
||||
}
|
||||
25
src/api/userManagement.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getList(data) {
|
||||
return request({
|
||||
url: `/management/api/user/list`,
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doEdit(data) {
|
||||
return request({
|
||||
url: '/management/api/user/create',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function doDelete(data) {
|
||||
return request({
|
||||
url: '/userManagement/doDelete',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
BIN
src/assets/comparison/left.jpg
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
src/assets/comparison/right.jpg
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
src/assets/error_images/401.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
src/assets/error_images/404.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
src/assets/error_images/cloud.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/ewm.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/assets/login_images/background.jpg
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
src/assets/pro.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
src/assets/qr_logo/lqr_logo.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
src/assets/zfb_100.jpg
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
src/assets/zfb_699.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
src/assets/zfb_799.jpg
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
src/assets/zfb_kf.jpg
Normal file
|
After Width: | Height: | Size: 122 KiB |
13
src/colorfulIcon/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
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)
|
||||
6
src/colorfulIcon/svg/alphabetical_sorting.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 479 B |
24
src/colorfulIcon/svg/vab.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
187
src/components/SelectTree/index.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<div class="select-tree-template">
|
||||
<el-select
|
||||
v-model="selectValue"
|
||||
class="vab-tree-select"
|
||||
:clearable="clearable"
|
||||
:collapse-tags="selectType == 'multiple'"
|
||||
:multiple="selectType == 'multiple'"
|
||||
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"
|
||||
node-key="id"
|
||||
:props="defaultProps"
|
||||
:show-checkbox="selectType == 'multiple'"
|
||||
@check="checkNode"
|
||||
@node-click="nodeClick"
|
||||
/>
|
||||
</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() {
|
||||
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() {
|
||||
this.$refs.treeOption.setCheckedKeys([])
|
||||
},
|
||||
// 点击叶子节点
|
||||
nodeClick(data) {
|
||||
if (data.rank >= this.selectLevel) {
|
||||
this.selectValue = data.name
|
||||
this.selectKey = data.id
|
||||
}
|
||||
},
|
||||
// 节点选中操作
|
||||
checkNode() {
|
||||
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"></style>
|
||||
120
src/components/SingleUpload/index.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<div class="single-upload">
|
||||
<el-upload
|
||||
:action="uploadUrl"
|
||||
:before-upload="beforeUpload"
|
||||
class="uploader"
|
||||
:file-list="fileList"
|
||||
:headers="uploadHeaders"
|
||||
:on-error="handleError"
|
||||
:on-success="handleSuccess"
|
||||
:show-file-list="false"
|
||||
>
|
||||
<img v-if="fileUrl" alt="avatar" class="avatar" :src="fileUrl" />
|
||||
<i v-else class="el-icon-plus uploader-icon"></i>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAccessToken } from '@/utils/accessToken'
|
||||
export default {
|
||||
name: 'SingleUpload',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
uploadUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fileUrl: this.value,
|
||||
fileList: [],
|
||||
token: getAccessToken() || '',
|
||||
loadingInstance: null, // 存储 loading 实例
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 设置上传时的 headers
|
||||
uploadHeaders() {
|
||||
return {
|
||||
Authorization: `${this.token}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(val) {
|
||||
this.fileUrl = val
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isImage() {
|
||||
return /\.(png|jpg|jpeg|gif|webp)$/i.test(this.fileUrl)
|
||||
},
|
||||
handleSuccess(response) {
|
||||
if (response.code === 200) {
|
||||
this.fileUrl = `https://file.lihailezzc.com/${response.data.key}`
|
||||
this.$message.success('上传成功!')
|
||||
// this.$emit('input', this.fileUrl)
|
||||
this.$emit('upload-success', this.fileUrl)
|
||||
} else {
|
||||
this.$message.error('上传失败,请重试!')
|
||||
}
|
||||
this.loadingInstance.close()
|
||||
},
|
||||
handleError() {
|
||||
this.$message.error('上传失败,请重试!')
|
||||
this.loadingInstance.close()
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const isValidSize = file.size / 1024 / 1024 < 5
|
||||
if (!isValidSize) {
|
||||
this.$message.error('文件大小不能超过 5MB!')
|
||||
return false
|
||||
}
|
||||
this.loadingInstance = this.$loading({
|
||||
target: this.$el, // 加载指示器的父容器
|
||||
text: '上传中...',
|
||||
spinner: true, // 显示加载动画
|
||||
background: 'rgba(0, 0, 0, 0.5)', // 背景遮罩
|
||||
})
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.single-upload {
|
||||
.uploader {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
.uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
.avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
178
src/components/VabCharge/index.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<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>
|
||||
305
src/components/VabProfile/index.vue
Normal file
@@ -0,0 +1,305 @@
|
||||
<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 class="avatar" :src="avatar" />
|
||||
<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>
|
||||
81
src/components/VabSnow/index.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<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>
|
||||
217
src/components/VabUpload/index.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<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"
|
||||
/>
|
||||
<br />
|
||||
<el-upload
|
||||
ref="upload"
|
||||
accept="image/png, image/jpeg"
|
||||
:action="action"
|
||||
:auto-upload="false"
|
||||
class="upload-content"
|
||||
:close-on-click-modal="false"
|
||||
:data="data"
|
||||
:file-list="fileList"
|
||||
:headers="headers"
|
||||
:limit="limit"
|
||||
list-type="picture-card"
|
||||
: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"
|
||||
>
|
||||
<i slot="trigger" class="el-icon-plus"></i>
|
||||
<el-dialog append-to-body title="查看大图" :visible.sync="dialogVisible">
|
||||
<div>
|
||||
<img alt="" :src="dialogImageUrl" 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>
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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>
|
||||
7
src/config/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* @description 3个子配置,通用配置|主题配置|网络配置导出
|
||||
*/
|
||||
const setting = require('./setting.config')
|
||||
const theme = require('./theme.config')
|
||||
const network = require('./net.config')
|
||||
module.exports = Object.assign({}, setting, theme, network)
|
||||
20
src/config/net.config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @description 导出默认网路配置
|
||||
**/
|
||||
const network = {
|
||||
// 默认的接口地址 如果是开发环境和生产环境走vab-mock-server,当然你也可以选择自己配置成需要的接口地址
|
||||
baseURL: process.env.NODE_ENV === 'development' ? 'http://127.0.0.1:3999' : 'http://127.0.0.1:3999',
|
||||
//配后端数据的接收方式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
|
||||
76
src/config/permission.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留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()
|
||||
})
|
||||
70
src/config/setting.config.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @description 导出默认通用配置
|
||||
*/
|
||||
const setting = {
|
||||
// 开发以及部署时的URL
|
||||
publicPath: '',
|
||||
// 生产环境构建文件的目录名
|
||||
outputDir: 'dist',
|
||||
// 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。
|
||||
assetsDir: 'static',
|
||||
// 开发环境每次保存时是否输出为eslint编译警告
|
||||
lintOnSave: true,
|
||||
// 进行编译的依赖
|
||||
transpileDependencies: [],
|
||||
//标题 (包括初次加载雪花屏的标题 页面的标题 浏览器的标题)
|
||||
title: '管理后台',
|
||||
//简写
|
||||
abbreviation: 'vab',
|
||||
//开发环境端口号
|
||||
devPort: '81',
|
||||
//版本号
|
||||
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-better-2024',
|
||||
//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: {},
|
||||
//代码生成机生成在view下的文件夹名称
|
||||
templateFolder: 'project',
|
||||
//是否显示终端donation打印
|
||||
donation: true,
|
||||
//是否开启图片压缩
|
||||
imageCompression: true,
|
||||
}
|
||||
module.exports = setting
|
||||
6
src/config/settings.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* @description 3个子配置,通用配置|主题配置|网络配置
|
||||
*/
|
||||
//默认配置
|
||||
const { setting, theme, network } = require('./')
|
||||
module.exports = Object.assign({}, setting, theme, network)
|
||||
14
src/config/theme.config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @description 导出默认主题配置
|
||||
*/
|
||||
const theme = {
|
||||
//是否国定头部 固定fixed 不固定noFixed
|
||||
header: 'fixed',
|
||||
//横纵布局 horizontal vertical
|
||||
layout: 'vertical',
|
||||
//是否开启主题配置按钮
|
||||
themeBar: true,
|
||||
//是否显示多标签页
|
||||
tabsBar: true,
|
||||
}
|
||||
module.exports = theme
|
||||
3
src/layouts/EmptyLayout.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
46
src/layouts/components/VabAd/index.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="vab-ad">
|
||||
<el-carousel v-if="adList" :autoplay="true" :interval="3000" direction="vertical" height="30px" indicator-position="none">
|
||||
<el-carousel-item v-for="(item, index) in adList" :key="index">
|
||||
<el-tag type="warning">Ad</el-tag>
|
||||
<a :href="item.url" target="_blank">{{ item.title }}</a>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getList } from '@/api/ad'
|
||||
|
||||
export default {
|
||||
name: 'VabAd',
|
||||
data() {
|
||||
return {
|
||||
nodeEnv: process.env.NODE_ENV,
|
||||
adList: [],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
const { data } = await getList()
|
||||
this.adList = data
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.vab-ad {
|
||||
height: 30px;
|
||||
padding-right: $base-padding;
|
||||
padding-left: $base-padding;
|
||||
margin-bottom: -20px;
|
||||
line-height: 30px;
|
||||
cursor: pointer;
|
||||
|
||||
a {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
107
src/layouts/components/VabAppMain/index.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div v-if="routerView" class="app-main-container">
|
||||
<!-- <vab-github-corner /> -->
|
||||
<transition mode="out-in" name="fade-transform">
|
||||
<keep-alive :include="cachedRoutes" :max="keepAliveMaxNum">
|
||||
<router-view :key="key" class="app-main-height" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
<footer v-show="footerCopyright" class="footer-copyright">
|
||||
Copyright
|
||||
<vab-icon :icon="['fas', 'copyright']"></vab-icon>
|
||||
{{ titleName }} {{ fullYear }}
|
||||
</footer>
|
||||
</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',
|
||||
titleName: 'settings/title',
|
||||
}),
|
||||
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() {
|
||||
const handleReloadRouterView = () => {
|
||||
this.routerView = false
|
||||
this.$nextTick(() => {
|
||||
this.routerView = true
|
||||
})
|
||||
};
|
||||
|
||||
//重载所有路由
|
||||
this.$baseEventBus.$on('reload-router-view', handleReloadRouterView)
|
||||
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
this.$baseEventBus.$off('reload-router-view', handleReloadRouterView);
|
||||
});
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
...mapActions({
|
||||
foldSideBar: 'settings/foldSideBar',
|
||||
}),
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-main-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.vab-keel {
|
||||
margin: $base-padding;
|
||||
}
|
||||
|
||||
.app-main-height {
|
||||
min-height: $base-app-main-height;
|
||||
}
|
||||
|
||||
.footer-copyright {
|
||||
min-height: 55px;
|
||||
line-height: 55px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
text-align: center;
|
||||
border-top: 1px dashed $base-border-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
100
src/layouts/components/VabAvatar/index.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<el-dropdown @command="handleCommand">
|
||||
<span class="avatar-dropdown">
|
||||
<!--<el-avatar class="user-avatar" :src="avatar"></el-avatar>-->
|
||||
<img :src="avatar" alt="" class="user-avatar" />
|
||||
<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="github">github地址</el-dropdown-item>
|
||||
<el-dropdown-item command="gitee" divided>码云地址</el-dropdown-item>
|
||||
<el-dropdown-item command="pro" divided>pro付费版地址</el-dropdown-item>
|
||||
<el-dropdown-item command="plus" divided>plus付费版地址</el-dropdown-item>
|
||||
<el-dropdown-item command="shop" divided>shop-vite付费版地址</el-dropdown-item>
|
||||
<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
|
||||
case 'personalCenter':
|
||||
this.personalCenter()
|
||||
break
|
||||
case 'github':
|
||||
window.open('https://github.com/zxwk1998/vue-admin-better')
|
||||
break
|
||||
case 'gitee':
|
||||
window.open('https://gitee.com/chu1204505056/vue-admin-better')
|
||||
break
|
||||
case 'pro':
|
||||
window.open('https://vuejs-core.cn/admin-pro/')
|
||||
break
|
||||
case 'plus':
|
||||
window.open('https://vuejs-core.cn/admin-plus/')
|
||||
case 'shop':
|
||||
window.open('https://vuejs-core.cn/shop-vite/')
|
||||
}
|
||||
},
|
||||
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>
|
||||
61
src/layouts/components/VabBreadcrumb/index.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<el-breadcrumb class="breadcrumb-container" separator=">">
|
||||
<el-breadcrumb-item v-for="item in list" :key="item.path">
|
||||
{{ item.meta.title }}
|
||||
</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)
|
||||
},
|
||||
},
|
||||
}
|
||||
</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>
|
||||
85
src/layouts/components/VabLogo/index.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div :class="'logo-container-' + layout">
|
||||
<router-link to="/">
|
||||
<!-- 这里是logo变更的位置 -->
|
||||
<vab-remix-icon v-if="logo" :icon-class="logo" class="logo" />
|
||||
<span :class="{ 'hidden-xs-only': layout === 'horizontal' }" :title="title" class="title">
|
||||
{{ title }}
|
||||
</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'VabLogo',
|
||||
computed: {
|
||||
...mapGetters({
|
||||
logo: 'settings/logo',
|
||||
layout: 'settings/layout',
|
||||
title: 'settings/title',
|
||||
}),
|
||||
},
|
||||
}
|
||||
</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;
|
||||
}
|
||||
|
||||
@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;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.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>
|
||||
136
src/layouts/components/VabNavBar/index.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div class="nav-bar-container">
|
||||
<el-row :gutter="15">
|
||||
<el-col :lg="12" :md="12" :sm="12" :xl="12" :xs="4">
|
||||
<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 :lg="12" :md="12" :sm="12" :xl="12" :xs="20">
|
||||
<div class="right-panel">
|
||||
<vab-error-log />
|
||||
<vab-full-screen-bar @refresh="refreshRoute" />
|
||||
<vab-theme-bar class="hidden-xs-only" />
|
||||
<vab-icon :icon="['fas', 'redo']" :pulse="pulse" title="重载所有路由" @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,
|
||||
timeOutID: null
|
||||
}
|
||||
},
|
||||
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
|
||||
this.timeOutID = setTimeout(() => {
|
||||
this.pulse = false
|
||||
}, 1000)
|
||||
},
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
clearTimeout(this.timeOutID);
|
||||
}
|
||||
};
|
||||
</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>
|
||||
259
src/layouts/components/VabThemeBar/index.vue
Normal file
@@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<span v-if="themeBar">
|
||||
<vab-icon :icon="['fas', 'palette']" title="主题配置" @click="handleOpenThemeBar" />
|
||||
<div class="theme-bar-setting">
|
||||
<div @click="handleOpenThemeBar">
|
||||
<vab-icon :icon="['fas', 'palette']" />
|
||||
<p>主题配置</p>
|
||||
</div>
|
||||
<div @click="handleGetCode">
|
||||
<vab-icon :icon="['fas', 'laptop-code']"></vab-icon>
|
||||
<p>拷贝源码</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-drawer :visible.sync="drawerVisible" append-to-body direction="rtl" size="300px" title="主题配置">
|
||||
<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 handleTheme = () => {
|
||||
this.handleOpenThemeBar()
|
||||
}
|
||||
|
||||
this.$baseEventBus.$on('theme', handleTheme)
|
||||
const theme = localStorage.getItem('vue-admin-better-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
|
||||
}
|
||||
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
this.$baseEventBus.$off('theme', handleTheme)
|
||||
})
|
||||
},
|
||||
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-better-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-better-theme-${name}`
|
||||
this.drawerVisible = false
|
||||
},
|
||||
handleSaveTheme() {
|
||||
this.handleSetTheme()
|
||||
},
|
||||
handleSetDfaultTheme() {
|
||||
let { name } = this.theme
|
||||
document.getElementsByTagName('body')[0].classList.remove(`vue-admin-better-theme-${name}`)
|
||||
localStorage.removeItem('vue-admin-better-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()
|
||||
},
|
||||
handleGetCode() {
|
||||
const url = 'https://github.com/zxwk1998/vue-admin-better/tree/master/src/views'
|
||||
let path = this.$route.path + '/index.vue'
|
||||
if (path === '/vab/menu1/menu1-1/menu1-1-1/index.vue') {
|
||||
path = '/vab/nested/menu1/menu1-1/menu1-1-1/index.vue'
|
||||
}
|
||||
if (path === '/vab/icon/awesomeIcon/index.vue') {
|
||||
path = '/vab/icon/index.vue'
|
||||
}
|
||||
if (path === '/vab/icon/remixIcon/index.vue') {
|
||||
path = '/vab/icon/remixIcon.vue'
|
||||
}
|
||||
if (path === '/vab/icon/colorfulIcon/index.vue') {
|
||||
path = '/vab/icon/colorfulIcon.vue'
|
||||
}
|
||||
if (path === '/vab/table/comprehensiveTable/index.vue') {
|
||||
path = '/vab/table/index.vue'
|
||||
}
|
||||
if (path === '/vab/table/inlineEditTable/index.vue') {
|
||||
path = '/vab/table/inlineEditTable.vue'
|
||||
}
|
||||
window.open(url + path)
|
||||
},
|
||||
},
|
||||
}
|
||||
</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>
|
||||
25
src/layouts/export.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留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)}`)
|
||||
})
|
||||
298
src/layouts/index.vue
Normal file
@@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<div :class="classObj" class="vue-admin-better-wrapper">
|
||||
<div
|
||||
v-if="'horizontal' === layout"
|
||||
:class="{
|
||||
fixed: header === 'fixed',
|
||||
'no-tabs-bar': tabsBar === 'false' || tabsBar === false,
|
||||
}"
|
||||
class="layout-container-horizontal"
|
||||
>
|
||||
<div :class="header === 'fixed' ? 'fixed-header' : ''">
|
||||
<vab-top-bar />
|
||||
<div v-if="tabsBar === 'true' || tabsBar === true" :class="{ 'tag-view-show': tabsBar }">
|
||||
<div class="vab-main">
|
||||
<vab-tabs-bar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vab-main main-padding">
|
||||
<!-- <vab-ad /> -->
|
||||
<vab-app-main />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
:class="{
|
||||
fixed: header === 'fixed',
|
||||
'no-tabs-bar': tabsBar === 'false' || tabsBar === false,
|
||||
}"
|
||||
class="layout-container-vertical"
|
||||
>
|
||||
<div v-if="device === 'mobile' && collapse === false" class="mask" @click="handleFoldSideBar" />
|
||||
<vab-side-bar />
|
||||
<div :class="collapse ? 'is-collapse-main' : ''" class="vab-main">
|
||||
<div :class="header === 'fixed' ? 'fixed-header' : ''">
|
||||
<vab-nav-bar />
|
||||
<vab-tabs-bar v-if="tabsBar === 'true' || tabsBar === true" />
|
||||
</div>
|
||||
<!-- <vab-ad /> -->
|
||||
<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: '',
|
||||
controller: new window.AbortController(),
|
||||
timeOutID: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
layout: 'settings/layout',
|
||||
tabsBar: 'settings/tabsBar',
|
||||
collapse: 'settings/collapse',
|
||||
header: 'settings/header',
|
||||
device: 'settings/device',
|
||||
}),
|
||||
classObj() {
|
||||
return {
|
||||
mobile: this.device === 'mobile',
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
this.controller.abort()
|
||||
clearTimeout(this.timeOutID)
|
||||
},
|
||||
mounted() {
|
||||
this.oldLayout = this.layout
|
||||
const userAgent = navigator.userAgent
|
||||
const isMobile = this.handleIsMobile()
|
||||
if (isMobile) {
|
||||
if (isMobile) {
|
||||
//横向布局时如果是手机端访问那么改成纵向版
|
||||
this.$store.dispatch('settings/changeLayout', 'vertical')
|
||||
} else {
|
||||
this.$store.dispatch('settings/changeLayout', this.oldLayout)
|
||||
}
|
||||
this.$store.dispatch('settings/toggleDevice', 'mobile')
|
||||
this.timeOutID = setTimeout(() => {
|
||||
this.$store.dispatch('settings/foldSideBar')
|
||||
}, 2000)
|
||||
} else {
|
||||
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()
|
||||
},
|
||||
{
|
||||
capture: false,
|
||||
signal: this.controller?.signal,
|
||||
}
|
||||
)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
handleFoldSideBar: 'settings/foldSideBar',
|
||||
}),
|
||||
handleIsMobile() {
|
||||
return document.body.getBoundingClientRect().width - 1 < 992
|
||||
},
|
||||
handleResize() {
|
||||
if (!document.hidden) {
|
||||
const isMobile = this.handleIsMobile()
|
||||
if (isMobile) {
|
||||
//横向布局时如果是手机端访问那么改成纵向版
|
||||
this.$store.dispatch('settings/changeLayout', 'vertical')
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
.vue-admin-better-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.layout-container-horizontal {
|
||||
position: relative;
|
||||
|
||||
&.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: 88%;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.main-padding {
|
||||
.app-main-container {
|
||||
margin-top: $base-padding;
|
||||
margin-bottom: $base-padding;
|
||||
background: $base-color-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-container-vertical {
|
||||
position: relative;
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: $base-z-index - 1;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.fixed {
|
||||
padding-top: calc(#{$base-nav-bar-height} + #{$base-tabs-bar-height});
|
||||
}
|
||||
|
||||
&.fixed.no-tabs-bar {
|
||||
padding-top: $base-nav-bar-height;
|
||||
}
|
||||
|
||||
.vab-main {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
margin-left: $base-left-menu-width;
|
||||
background: #f6f8f9;
|
||||
transition: $base-transition;
|
||||
|
||||
::v-deep {
|
||||
.fixed-header {
|
||||
@include fix-header;
|
||||
|
||||
left: $base-left-menu-width;
|
||||
width: $base-right-content-width;
|
||||
box-shadow: $base-box-shadow;
|
||||
transition: $base-transition;
|
||||
}
|
||||
|
||||
.nav-bar-container {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tabs-bar-container {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.app-main-container {
|
||||
width: calc(100% - #{$base-padding} - #{$base-padding});
|
||||
margin: $base-padding auto;
|
||||
background: $base-color-white;
|
||||
border-radius: $base-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-collapse-main {
|
||||
margin-left: $base-left-menu-width-min;
|
||||
|
||||
::v-deep {
|
||||
.fixed-header {
|
||||
left: $base-left-menu-width-min;
|
||||
width: calc(100% - 65px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端开始 */
|
||||
&.mobile {
|
||||
::v-deep {
|
||||
.el-pager,
|
||||
.el-pagination__jump {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-container-vertical {
|
||||
.el-scrollbar.side-bar-container.is-collapse {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.vab-main {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-main {
|
||||
.fixed-header {
|
||||
left: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端结束 */
|
||||
}
|
||||
</style>
|
||||
24
src/main.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import store from './store'
|
||||
import router from './router'
|
||||
import './plugins'
|
||||
import '@/layouts/export'
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 生产环境默认都使用mock,如果正式用于生产环境时,记得去掉
|
||||
*/
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const { mockXHR } = require('@/utils/static')
|
||||
mockXHR()
|
||||
}
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
el: '#vue-admin-better',
|
||||
router,
|
||||
store,
|
||||
render: (h) => h(App),
|
||||
})
|
||||
4
src/plugins/echarts.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import 'echarts'
|
||||
import VabChart from 'vue-echarts'
|
||||
|
||||
export default VabChart
|
||||
9
src/plugins/element.js
Normal file
@@ -0,0 +1,9 @@
|
||||
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',
|
||||
})
|
||||
15
src/plugins/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/* 公共引入,勿随意修改,修改时需经过确认 */
|
||||
import Vue from 'vue'
|
||||
import './element'
|
||||
import './support'
|
||||
import '@/styles/vab.scss'
|
||||
import '@/remixIcon'
|
||||
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)
|
||||
18
src/plugins/support.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { MessageBox } from 'element-ui'
|
||||
import { dependencies } 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 = ''
|
||||
4
src/plugins/vabIcon.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import Vue from 'vue'
|
||||
import VabIcon from 'vab-icon'
|
||||
|
||||
Vue.component('VabIcon', VabIcon)
|
||||
13
src/remixIcon/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
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)
|
||||
5
src/remixIcon/svg/qq-fill.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path fill="none" d="M0 0h24v24H0z"/>
|
||||
<path
|
||||
d="M19.913 14.529a31.977 31.977 0 00-.675-1.886l-.91-2.246c0-.026.012-.468.012-.696C18.34 5.86 16.507 2 12 2S5.66 5.86 5.66 9.7c0 .229.011.671.012.697l-.91 2.246a32.777 32.777 0 00-.675 1.886c-.86 2.737-.581 3.87-.369 3.895.455.054 1.771-2.06 1.771-2.06 0 1.224.637 2.822 2.016 3.976-.515.157-1.147.399-1.554.695-.365.267-.319.54-.253.65.289.481 4.955.307 6.303.157 1.347.15 6.014.324 6.302-.158.066-.11.112-.382-.253-.649-.407-.296-1.039-.538-1.555-.696 1.379-1.153 2.016-2.751 2.016-3.976 0 0 1.316 2.115 1.771 2.06.212-.025.49-1.157-.37-3.894"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 669 B |
4
src/remixIcon/svg/vuejs-fill.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path fill="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M1 3h4l7 12 7-12h4L12 22 1 3zm8.667 0L12 7l2.333-4h4.035L12 14 5.632 3h4.035z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 200 B |
407
src/router/index.js
Normal file
@@ -0,0 +1,407 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description router全局配置,如有必要可分文件抽离,其中asyncRoutes只有在intelligence模式下才会用到,vip文档中已提供路由的基础图标与小清新图标的配置方案,请仔细阅读
|
||||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import Layout from '@/layouts'
|
||||
import EmptyLayout from '@/layouts/EmptyLayout'
|
||||
import { publicPath, routerMode } from '@/config'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
export const constantRoutes = [
|
||||
{
|
||||
path: '/login',
|
||||
component: () => import('@/views/login/index'),
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
component: () => import('@/views/register/index'),
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
path: '/401',
|
||||
name: '401',
|
||||
component: () => import('@/views/401'),
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
name: '404',
|
||||
component: () => import('@/views/404'),
|
||||
hidden: true,
|
||||
},
|
||||
]
|
||||
|
||||
export const asyncRoutes = [
|
||||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
redirect: '/index',
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'Index',
|
||||
component: () => import('@/views/index/index'),
|
||||
meta: {
|
||||
title: '首页',
|
||||
icon: 'home',
|
||||
affix: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/cxshMini',
|
||||
component: Layout,
|
||||
redirect: 'noRedirect',
|
||||
name: 'CxshMini',
|
||||
meta: { title: '小程序', icon: 'comment', permissions: ['admin'] },
|
||||
children: [
|
||||
{
|
||||
path: 'home',
|
||||
component: EmptyLayout,
|
||||
alwaysShow: true,
|
||||
redirect: 'noRedirect',
|
||||
name: 'Home',
|
||||
meta: {
|
||||
title: '首页',
|
||||
icon: 'home',
|
||||
permissions: ['admin'],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'banner',
|
||||
name: 'Banner',
|
||||
component: () => import('@/views/cxshMini/home/banner/index'),
|
||||
meta: { title: '轮播图' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'anli',
|
||||
component: EmptyLayout,
|
||||
alwaysShow: true,
|
||||
redirect: 'noRedirect',
|
||||
name: 'Anli',
|
||||
meta: {
|
||||
title: '精彩案例',
|
||||
icon: 'handshake',
|
||||
permissions: ['admin'],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'article',
|
||||
name: 'Article',
|
||||
component: () => import('@/views/cxshMini/anli/article/index'),
|
||||
meta: { title: '案例' },
|
||||
},
|
||||
{
|
||||
path: 'category',
|
||||
name: 'Category',
|
||||
component: () => import('@/views/cxshMini/anli/category/index'),
|
||||
meta: { title: '案例分类' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'lianxi',
|
||||
component: EmptyLayout,
|
||||
alwaysShow: true,
|
||||
redirect: 'noRedirect',
|
||||
name: 'Lianxi',
|
||||
meta: {
|
||||
title: '联系我们',
|
||||
icon: 'user-friends',
|
||||
permissions: ['admin'],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'member',
|
||||
name: 'Member',
|
||||
component: () => import('@/views/cxshMini/lianxi/member/index'),
|
||||
meta: { title: '成员' },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
/* {
|
||||
path: "/test",
|
||||
component: Layout,
|
||||
redirect: "noRedirect",
|
||||
children: [
|
||||
{
|
||||
path: "test",
|
||||
name: "Test",
|
||||
component: () => import("@/views/test/index"),
|
||||
meta: {
|
||||
title: "test",
|
||||
icon: "marker",
|
||||
permissions: ["admin"],
|
||||
},
|
||||
},
|
||||
],
|
||||
}, */
|
||||
|
||||
{
|
||||
path: '/vab',
|
||||
component: Layout,
|
||||
redirect: 'noRedirect',
|
||||
name: 'Vab',
|
||||
alwaysShow: true,
|
||||
meta: { title: '组件', icon: 'box-open' },
|
||||
children: [
|
||||
{
|
||||
path: 'permissions',
|
||||
name: 'Permission',
|
||||
component: () => import('@/views/vab/permissions/index'),
|
||||
meta: {
|
||||
title: '角色权限',
|
||||
permissions: ['admin', 'editor'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'icon',
|
||||
component: EmptyLayout,
|
||||
redirect: 'noRedirect',
|
||||
name: 'Icon',
|
||||
meta: {
|
||||
title: '图标',
|
||||
permissions: ['admin'],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'awesomeIcon',
|
||||
name: 'AwesomeIcon',
|
||||
component: () => import('@/views/vab/icon/index'),
|
||||
meta: { title: '常规图标' },
|
||||
},
|
||||
{
|
||||
path: 'colorfulIcon',
|
||||
name: 'ColorfulIcon',
|
||||
component: () => import('@/views/vab/icon/colorfulIcon'),
|
||||
meta: { title: '多彩图标' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'table',
|
||||
component: () => import('@/views/vab/table/index'),
|
||||
name: 'Table',
|
||||
meta: {
|
||||
title: '表格',
|
||||
permissions: ['admin'],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
path: 'webSocket',
|
||||
name: 'WebSocket',
|
||||
component: () => import('@/views/vab/webSocket/index'),
|
||||
meta: { title: 'webSocket', permissions: ['admin'] },
|
||||
},
|
||||
{
|
||||
path: 'form',
|
||||
name: 'Form',
|
||||
component: () => import('@/views/vab/form/index'),
|
||||
meta: { title: '表单', permissions: ['admin'] },
|
||||
},
|
||||
{
|
||||
path: 'element',
|
||||
name: 'Element',
|
||||
component: () => import('@/views/vab/element/index'),
|
||||
meta: { title: '常用组件', permissions: ['admin'] },
|
||||
},
|
||||
{
|
||||
path: 'tree',
|
||||
name: 'Tree',
|
||||
component: () => import('@/views/vab/tree/index'),
|
||||
meta: { title: '树', permissions: ['admin'] },
|
||||
},
|
||||
{
|
||||
path: 'menu1',
|
||||
component: () => import('@/views/vab/nested/menu1/index'),
|
||||
name: 'Menu1',
|
||||
alwaysShow: true,
|
||||
meta: {
|
||||
title: '嵌套路由 1',
|
||||
permissions: ['admin'],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1',
|
||||
name: 'Menu1-1',
|
||||
alwaysShow: true,
|
||||
meta: { title: '嵌套路由 1-1' },
|
||||
component: () => import('@/views/vab/nested/menu1/menu1-1/index'),
|
||||
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1-1',
|
||||
name: 'Menu1-1-1',
|
||||
meta: { title: '嵌套路由 1-1-1' },
|
||||
component: () => import('@/views/vab/nested/menu1/menu1-1/menu1-1-1/index'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'loading',
|
||||
name: 'Loading',
|
||||
component: () => import('@/views/vab/loading/index'),
|
||||
meta: { title: 'loading', permissions: ['admin'] },
|
||||
},
|
||||
{
|
||||
path: 'backToTop',
|
||||
name: 'BackToTop',
|
||||
component: () => import('@/views/vab/backToTop/index'),
|
||||
meta: { title: '返回顶部', permissions: ['admin'] },
|
||||
},
|
||||
{
|
||||
path: 'lodash',
|
||||
name: 'Lodash',
|
||||
component: () => import('@/views/vab/lodash/index'),
|
||||
meta: { title: 'lodash', permissions: ['admin'] },
|
||||
},
|
||||
|
||||
{
|
||||
path: 'upload',
|
||||
name: 'Upload',
|
||||
component: () => import('@/views/vab/upload/index'),
|
||||
meta: { title: '上传', permissions: ['admin'] },
|
||||
},
|
||||
{
|
||||
path: 'log',
|
||||
name: 'Log',
|
||||
component: () => import('@/views/vab/errorLog/index'),
|
||||
meta: { title: '错误日志模拟', permissions: ['admin'] },
|
||||
},
|
||||
{
|
||||
path: 'https://github.com/zxwk1998/vue-admin-better/',
|
||||
name: 'ExternalLink',
|
||||
meta: {
|
||||
title: '外链',
|
||||
target: '_blank',
|
||||
permissions: ['admin', 'editor'],
|
||||
badge: 'New',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'more',
|
||||
name: 'More',
|
||||
component: () => import('@/views/vab/more/index'),
|
||||
meta: { title: '关于', permissions: ['admin'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/personnelManagement',
|
||||
component: Layout,
|
||||
redirect: 'noRedirect',
|
||||
name: 'PersonnelManagement',
|
||||
meta: { title: '配置', icon: 'users-cog', permissions: ['admin'] },
|
||||
children: [
|
||||
{
|
||||
path: 'userManagement',
|
||||
name: 'UserManagement',
|
||||
component: () => import('@/views/personnelManagement/userManagement/index'),
|
||||
meta: { title: '用户管理' },
|
||||
},
|
||||
{
|
||||
path: 'appManagement',
|
||||
name: 'AppManagement',
|
||||
component: () => import('@/views/personnelManagement/appManagement/index'),
|
||||
meta: { title: '应用管理' },
|
||||
},
|
||||
{
|
||||
path: 'roleManagement',
|
||||
name: 'RoleManagement',
|
||||
component: () => import('@/views/personnelManagement/roleManagement/index'),
|
||||
meta: { title: '角色管理' },
|
||||
},
|
||||
{
|
||||
path: 'menuManagement',
|
||||
name: 'MenuManagement',
|
||||
component: () => import('@/views/personnelManagement/menuManagement/index'),
|
||||
meta: { title: '菜单管理', badge: 'New' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/mall',
|
||||
component: Layout,
|
||||
redirect: 'noRedirect',
|
||||
name: 'Mall',
|
||||
meta: {
|
||||
title: '商城',
|
||||
icon: 'shopping-cart',
|
||||
permissions: ['admin'],
|
||||
},
|
||||
|
||||
children: [
|
||||
{
|
||||
path: 'pay',
|
||||
name: 'Pay',
|
||||
component: () => import('@/views/mall/pay/index'),
|
||||
meta: {
|
||||
title: '支付',
|
||||
noKeepAlive: true,
|
||||
},
|
||||
children: null,
|
||||
},
|
||||
{
|
||||
path: 'goodsList',
|
||||
name: 'GoodsList',
|
||||
component: () => import('@/views/mall/goodsList/index'),
|
||||
meta: {
|
||||
title: '商品列表',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/error',
|
||||
component: EmptyLayout,
|
||||
redirect: 'noRedirect',
|
||||
name: 'Error',
|
||||
meta: { title: '错误页', icon: 'bug' },
|
||||
children: [
|
||||
{
|
||||
path: '401',
|
||||
name: 'Error401',
|
||||
component: () => import('@/views/401'),
|
||||
meta: { title: '401' },
|
||||
},
|
||||
{
|
||||
path: '404',
|
||||
name: 'Error404',
|
||||
component: () => import('@/views/404'),
|
||||
meta: { title: '404' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
redirect: '/404',
|
||||
hidden: true,
|
||||
},
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
base: publicPath,
|
||||
mode: routerMode,
|
||||
scrollBehavior: () => ({
|
||||
y: 0,
|
||||
}),
|
||||
routes: constantRoutes,
|
||||
})
|
||||
|
||||
export function resetRouter() {
|
||||
location.reload()
|
||||
}
|
||||
|
||||
export default router
|
||||
22
src/store/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留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
|
||||
28
src/store/modules/errorLog.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留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 }
|
||||
47
src/store/modules/routes.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留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 }
|
||||
88
src/store/modules/settings.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 所有全局配置的状态管理,如无必要请勿修改
|
||||
*/
|
||||
|
||||
import defaultSettings from '@/config'
|
||||
|
||||
const { tabsBar, logo, layout, header, themeBar, title } = defaultSettings
|
||||
const theme = JSON.parse(localStorage.getItem('vue-admin-better-theme')) || ''
|
||||
const state = () => ({
|
||||
tabsBar: theme.tabsBar || tabsBar,
|
||||
logo,
|
||||
collapse: false,
|
||||
layout: theme.layout || layout,
|
||||
header: theme.header || header,
|
||||
device: 'desktop',
|
||||
themeBar,
|
||||
title,
|
||||
})
|
||||
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,
|
||||
title: (state) => state.title,
|
||||
}
|
||||
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
|
||||
},
|
||||
changeLogo: (state, logo) => {
|
||||
state.logo = logo
|
||||
},
|
||||
changeTitle: (state, title) => {
|
||||
state.title = title
|
||||
},
|
||||
}
|
||||
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)
|
||||
},
|
||||
changeLogo({ commit }, logo) {
|
||||
commit('changeLogo', logo)
|
||||
},
|
||||
changeTitle({ commit }, title) {
|
||||
commit('changeTitle', title)
|
||||
},
|
||||
}
|
||||
export default { state, getters, mutations, actions }
|
||||
23
src/store/modules/table.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 代码生成机状态管理
|
||||
*/
|
||||
|
||||
const state = () => ({
|
||||
srcCode: '',
|
||||
})
|
||||
const getters = {
|
||||
srcTableCode: (state) => state.srcCode,
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
setTableCode(state, srcCode) {
|
||||
state.srcCode = srcCode
|
||||
},
|
||||
}
|
||||
const actions = {
|
||||
setTableCode({ commit }, srcCode) {
|
||||
commit('setTableCode', srcCode)
|
||||
},
|
||||
}
|
||||
export default { state, getters, mutations, actions }
|
||||
110
src/store/modules/tabsBar.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description tabsBar多标签页逻辑,前期借鉴了很多开源项目发现都有个共同的特点很繁琐并不符合框架设计的初衷,后来在github用户hipi的启发下完成了重构,请勿修改
|
||||
*/
|
||||
|
||||
const state = () => ({
|
||||
visitedRoutes: [],
|
||||
})
|
||||
const getters = {
|
||||
visitedRoutes: (state) => state.visitedRoutes,
|
||||
}
|
||||
const mutations = {
|
||||
addVisitedRoute(state, route) {
|
||||
let target = state.visitedRoutes.find((item) => item.path === route.path)
|
||||
if (target) {
|
||||
if (route.fullPath !== target.fullPath) Object.assign(target, route)
|
||||
return
|
||||
}
|
||||
state.visitedRoutes.push(Object.assign({}, route))
|
||||
},
|
||||
delVisitedRoute(state, route) {
|
||||
state.visitedRoutes.forEach((item, index) => {
|
||||
if (item.path === route.path) state.visitedRoutes.splice(index, 1)
|
||||
})
|
||||
},
|
||||
delOthersVisitedRoute(state, route) {
|
||||
state.visitedRoutes = state.visitedRoutes.filter((item) => item.meta.affix || item.path === route.path)
|
||||
},
|
||||
delLeftVisitedRoute(state, route) {
|
||||
let index = state.visitedRoutes.length
|
||||
state.visitedRoutes = state.visitedRoutes.filter((item) => {
|
||||
if (item.name === route.name) index = state.visitedRoutes.indexOf(item)
|
||||
return item.meta.affix || index <= state.visitedRoutes.indexOf(item)
|
||||
})
|
||||
},
|
||||
delRightVisitedRoute(state, route) {
|
||||
let index = state.visitedRoutes.length
|
||||
state.visitedRoutes = state.visitedRoutes.filter((item) => {
|
||||
if (item.name === route.name) index = state.visitedRoutes.indexOf(item)
|
||||
return item.meta.affix || index >= state.visitedRoutes.indexOf(item)
|
||||
})
|
||||
},
|
||||
delAllVisitedRoutes(state) {
|
||||
state.visitedRoutes = state.visitedRoutes.filter((item) => item.meta.affix)
|
||||
},
|
||||
updateVisitedRoute(state, route) {
|
||||
state.visitedRoutes.forEach((item) => {
|
||||
if (item.path === route.path) item = Object.assign(item, route)
|
||||
})
|
||||
},
|
||||
}
|
||||
const actions = {
|
||||
addVisitedRoute({ commit }, route) {
|
||||
commit('addVisitedRoute', route)
|
||||
},
|
||||
async delRoute({ dispatch, state }, route) {
|
||||
await dispatch('delVisitedRoute', route)
|
||||
return {
|
||||
visitedRoutes: [...state.visitedRoutes],
|
||||
}
|
||||
},
|
||||
delVisitedRoute({ commit, state }, route) {
|
||||
commit('delVisitedRoute', route)
|
||||
return [...state.visitedRoutes]
|
||||
},
|
||||
async delOthersRoutes({ dispatch, state }, route) {
|
||||
await dispatch('delOthersVisitedRoute', route)
|
||||
return {
|
||||
visitedRoutes: [...state.visitedRoutes],
|
||||
}
|
||||
},
|
||||
async delLeftRoutes({ dispatch, state }, route) {
|
||||
await dispatch('delLeftVisitedRoute', route)
|
||||
return {
|
||||
visitedRoutes: [...state.visitedRoutes],
|
||||
}
|
||||
},
|
||||
async delRightRoutes({ dispatch, state }, route) {
|
||||
await dispatch('delRightVisitedRoute', route)
|
||||
return {
|
||||
visitedRoutes: [...state.visitedRoutes],
|
||||
}
|
||||
},
|
||||
delOthersVisitedRoute({ commit, state }, route) {
|
||||
commit('delOthersVisitedRoute', route)
|
||||
return [...state.visitedRoutes]
|
||||
},
|
||||
delLeftVisitedRoute({ commit, state }, route) {
|
||||
commit('delLeftVisitedRoute', route)
|
||||
return [...state.visitedRoutes]
|
||||
},
|
||||
delRightVisitedRoute({ commit, state }, route) {
|
||||
commit('delRightVisitedRoute', route)
|
||||
return [...state.visitedRoutes]
|
||||
},
|
||||
async delAllRoutes({ dispatch, state }, route) {
|
||||
await dispatch('delAllVisitedRoutes', route)
|
||||
return {
|
||||
visitedRoutes: [...state.visitedRoutes],
|
||||
}
|
||||
},
|
||||
delAllVisitedRoutes({ commit, state }) {
|
||||
commit('delAllVisitedRoutes')
|
||||
return [...state.visitedRoutes]
|
||||
},
|
||||
updateVisitedRoute({ commit }, route) {
|
||||
commit('updateVisitedRoute', route)
|
||||
},
|
||||
}
|
||||
export default { state, getters, mutations, actions }
|
||||
90
src/store/modules/user.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 登录、获取用户信息、退出登录、清除accessToken逻辑,不建议修改
|
||||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import { getUserInfo, login, logout } from '@/api/user'
|
||||
import { getAccessToken, removeAccessToken, setAccessToken } from '@/utils/accessToken'
|
||||
import { resetRouter } from '@/router'
|
||||
import { title, tokenName } from '@/config'
|
||||
|
||||
const state = () => ({
|
||||
accessToken: getAccessToken(),
|
||||
username: '',
|
||||
avatar: '',
|
||||
permissions: [],
|
||||
})
|
||||
const getters = {
|
||||
accessToken: (state) => state.accessToken,
|
||||
username: (state) => state.username,
|
||||
avatar: (state) => state.avatar,
|
||||
permissions: (state) => state.permissions,
|
||||
}
|
||||
const mutations = {
|
||||
setAccessToken(state, accessToken) {
|
||||
state.accessToken = accessToken
|
||||
setAccessToken(accessToken)
|
||||
},
|
||||
setUsername(state, username) {
|
||||
state.username = username
|
||||
},
|
||||
setAvatar(state, avatar) {
|
||||
state.avatar = avatar
|
||||
},
|
||||
setPermissions(state, permissions) {
|
||||
state.permissions = permissions
|
||||
},
|
||||
}
|
||||
const actions = {
|
||||
setPermissions({ commit }, permissions) {
|
||||
commit('setPermissions', permissions)
|
||||
},
|
||||
async login({ commit }, userInfo) {
|
||||
const { data } = await login(userInfo)
|
||||
if (data?.code) {
|
||||
Vue.prototype.$baseMessage(data?.msg, 'error')
|
||||
return
|
||||
}
|
||||
const accessToken = data[tokenName]
|
||||
|
||||
if (accessToken) {
|
||||
commit('setAccessToken', accessToken)
|
||||
const hour = new Date().getHours()
|
||||
const thisTime = hour < 8 ? '早上好' : hour <= 11 ? '上午好' : hour <= 13 ? '中午好' : hour < 18 ? '下午好' : '晚上好'
|
||||
Vue.prototype.$baseNotify(`欢迎登录${title}`, `${thisTime}!`)
|
||||
} else {
|
||||
Vue.prototype.$baseMessage(`登录接口异常,未正确返回${tokenName}...`, 'error')
|
||||
}
|
||||
},
|
||||
async getUserInfo({ commit, state, dispatch }) {
|
||||
const { data } = await getUserInfo(state.accessToken)
|
||||
if (!data) {
|
||||
Vue.prototype.$baseMessage('验证失败,请重新登录...', 'error')
|
||||
return false
|
||||
}
|
||||
let { permissions, username, avatar, appIcon, appName } = data
|
||||
if (permissions && username && Array.isArray(permissions)) {
|
||||
commit('setPermissions', permissions)
|
||||
commit('setUsername', username)
|
||||
commit('setAvatar', avatar)
|
||||
dispatch('settings/changeLogo', appIcon, { root: true })
|
||||
dispatch('settings/changeTitle', appName, { root: true })
|
||||
return permissions
|
||||
} else {
|
||||
Vue.prototype.$baseMessage('用户信息接口异常', 'error')
|
||||
return false
|
||||
}
|
||||
},
|
||||
async logout({ dispatch }) {
|
||||
await logout(state.accessToken)
|
||||
await dispatch('resetAccessToken')
|
||||
await resetRouter()
|
||||
},
|
||||
resetAccessToken({ commit }) {
|
||||
commit('setPermissions', [])
|
||||
commit('setAccessToken', '')
|
||||
removeAccessToken()
|
||||
},
|
||||
}
|
||||
export default { state, getters, mutations, actions }
|
||||
1035
src/styles/element-variables.scss
Normal file
346
src/styles/loading.scss
Normal file
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 全局加载动画
|
||||
*/
|
||||
|
||||
@charset "utf-8";
|
||||
|
||||
@import './spinner/dots.css';
|
||||
@import './spinner/gauge.css';
|
||||
@import './spinner/inner-circles.css';
|
||||
@import './spinner/plus.css';
|
||||
|
||||
$base-loading: '.vab-loading-type';
|
||||
|
||||
/* 自定义loading开始 */
|
||||
#{$base-loading}1 {
|
||||
display: flex;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin: 0 auto 15px;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: $base-color-blue;
|
||||
border-bottom-color: $base-color-blue;
|
||||
border-radius: 50%;
|
||||
animation: vabLoading1-0 0.8s linear infinite;
|
||||
}
|
||||
|
||||
#{$base-loading}1::before {
|
||||
display: block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin: auto;
|
||||
content: '';
|
||||
border: 3px solid $base-color-blue;
|
||||
border-radius: 50%;
|
||||
animation: vabLoading1 0.5s alternate ease-in infinite;
|
||||
}
|
||||
|
||||
@keyframes vabLoading1-0 {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vabLoading1 {
|
||||
from {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
#{$base-loading}2 {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: -40px;
|
||||
margin-left: -10px;
|
||||
animation: vabLoading2 1s linear reverse infinite;
|
||||
}
|
||||
|
||||
#{$base-loading}2::before {
|
||||
display: block;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-top: -17px;
|
||||
margin-left: -18px;
|
||||
content: '';
|
||||
animation: vabLoading2 0.4s linear infinite;
|
||||
}
|
||||
|
||||
#{$base-loading}2::after {
|
||||
display: block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-top: -3px;
|
||||
margin-left: -4px;
|
||||
content: '';
|
||||
animation: vabLoading2 0.4s linear infinite;
|
||||
}
|
||||
|
||||
#{$base-loading}2::before,
|
||||
#{$base-loading}2,
|
||||
#{$base-loading}2::after {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: $base-color-blue;
|
||||
border-right-color: $base-color-blue;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@keyframes vabLoading2 {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#{$base-loading}3 {
|
||||
display: inline-block;
|
||||
width: 2.5em;
|
||||
height: 3em;
|
||||
margin-bottom: 15px;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: $base-color-blue;
|
||||
border-bottom-color: $base-color-blue;
|
||||
border-radius: 50%;
|
||||
animation: vabLoading3 2s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes vabLoading3 {
|
||||
50% {
|
||||
border-width: 8px;
|
||||
transform: rotate(360deg) scale(0.4, 0.33);
|
||||
}
|
||||
|
||||
100% {
|
||||
border-width: 3px;
|
||||
transform: rotate(720deg) scale(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#{$base-loading}4 {
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 0 auto 10px;
|
||||
border: 8px solid transparent;
|
||||
border-bottom-color: $base-color-blue;
|
||||
border-left-color: $base-color-blue;
|
||||
border-radius: 50%;
|
||||
animation: vabLoading4 1s linear infinite normal;
|
||||
}
|
||||
|
||||
#{$base-loading}4::after {
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin: 0;
|
||||
content: ' ';
|
||||
border: 6px solid $base-color-blue;
|
||||
border-bottom-color: transparent;
|
||||
border-left-color: transparent;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@keyframes vabLoading4 {
|
||||
0% {
|
||||
opacity: 0.2;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.2;
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#{$base-loading}5 {
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin: 0 auto 15px;
|
||||
border: solid 1.5em $base-color-blue;
|
||||
border-right: solid 1.5em transparent;
|
||||
border-left: solid 1.5em transparent;
|
||||
border-radius: 100%;
|
||||
animation: vabLoading5 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes vabLoading5 {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(60deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#{$base-loading}6 {
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin: 0 auto 25px auto;
|
||||
perspective: 200px;
|
||||
}
|
||||
|
||||
#{$base-loading}6::before,
|
||||
#{$base-loading}6::after {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
content: '';
|
||||
background: rgba(0, 0, 0, 0);
|
||||
animation: vabLoading6 0.5s infinite alternate;
|
||||
}
|
||||
|
||||
#{$base-loading}6::before {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#{$base-loading}6::after {
|
||||
right: 0;
|
||||
animation-delay: 0.15s;
|
||||
}
|
||||
|
||||
@keyframes vabLoading6 {
|
||||
0% {
|
||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
||||
transform: scale(1) translateY(0) rotateX(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
background: $base-color-blue;
|
||||
box-shadow: 0 25px 40px rgba($base-color-blue, 0.5);
|
||||
transform: scale(1.2) translateY(-25px) rotateX(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
#{$base-loading}7 {
|
||||
display: block;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin: 0 auto 15px auto;
|
||||
border: 2px solid $base-color-blue;
|
||||
border-top-color: rgba($base-color-blue, 0.2);
|
||||
border-right-color: rgba($base-color-blue, 0.2);
|
||||
border-bottom-color: rgba($base-color-blue, 0.2);
|
||||
border-radius: 100%;
|
||||
animation: vabLoading7 infinite 0.75s linear;
|
||||
}
|
||||
|
||||
@keyframes vabLoading7 {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#{$base-loading}8 {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 0 auto 15px auto;
|
||||
background-color: $base-color-blue;
|
||||
border-radius: 50%;
|
||||
box-shadow: 30px 0 0 0 $base-color-blue;
|
||||
transform: translateX(-15px);
|
||||
}
|
||||
|
||||
#{$base-loading}8::after {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 9px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
content: '';
|
||||
background-color: $base-color-white;
|
||||
border-radius: 50%;
|
||||
box-shadow: 30px 0 0 0 $base-color-white;
|
||||
animation: vabLoading8 2s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes vabLoading8 {
|
||||
0% {
|
||||
left: 9px;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
#{$base-loading}9 {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 0 auto 15px auto;
|
||||
border: 1px $base-color-blue solid;
|
||||
animation: vabLoading9 5s linear infinite;
|
||||
}
|
||||
|
||||
#{$base-loading}9::after {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
content: '';
|
||||
background-color: $base-color-blue;
|
||||
animation: vabLoading9_check 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes vabLoading9_check {
|
||||
25% {
|
||||
top: -8px;
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
50% {
|
||||
top: 22px;
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
75% {
|
||||
top: 22px;
|
||||
left: -9px;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: -7px;
|
||||
left: -9px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vabLoading9 {
|
||||
0% {
|
||||
box-shadow: inset 0 0 0 0 rgba($base-color-blue, 0.5);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: inset 0 -20px 0 0 $base-color-blue;
|
||||
}
|
||||
}
|
||||
|
||||
/* 自定义loading结束 */
|
||||
353
src/styles/normalize.scss
vendored
Normal file
@@ -0,0 +1,353 @@
|
||||
@charset "utf-8";
|
||||
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
margin: 0.67em 0;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
border-bottom: none; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
margin: 0; /* 2 */
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
/* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
/* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type='button']::-moz-focus-inner,
|
||||
[type='reset']::-moz-focus-inner,
|
||||
[type='submit']::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type='button']:-moz-focusring,
|
||||
[type='reset']:-moz-focusring,
|
||||
[type='submit']:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
color: inherit; /* 2 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type='checkbox'],
|
||||
[type='radio'] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type='number']::-webkit-inner-spin-button,
|
||||
[type='number']::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type='search']::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
68
src/styles/spinner/dots.css
Normal file
@@ -0,0 +1,68 @@
|
||||
.dots-loader:not(:required) {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
margin-bottom: 30px;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
background: transparent;
|
||||
border-radius: 100%;
|
||||
box-shadow: #f86 -14px -14px 0 7px, #fc6 14px -14px 0 7px, #6d7 14px 14px 0 7px, #4ae -14px 14px 0 7px;
|
||||
transform-origin: 50% 50%;
|
||||
animation: dots-loader 5s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes dots-loader {
|
||||
0% {
|
||||
box-shadow: #f86 -14px -14px 0 7px, #fc6 14px -14px 0 7px, #6d7 14px 14px 0 7px, #4ae -14px 14px 0 7px;
|
||||
}
|
||||
|
||||
8.33% {
|
||||
box-shadow: #f86 14px -14px 0 7px, #fc6 14px -14px 0 7px, #6d7 14px 14px 0 7px, #4ae -14px 14px 0 7px;
|
||||
}
|
||||
|
||||
16.67% {
|
||||
box-shadow: #f86 14px 14px 0 7px, #fc6 14px 14px 0 7px, #6d7 14px 14px 0 7px, #4ae -14px 14px 0 7px;
|
||||
}
|
||||
|
||||
25% {
|
||||
box-shadow: #f86 -14px 14px 0 7px, #fc6 -14px 14px 0 7px, #6d7 -14px 14px 0 7px, #4ae -14px 14px 0 7px;
|
||||
}
|
||||
|
||||
33.33% {
|
||||
box-shadow: #f86 -14px -14px 0 7px, #fc6 -14px 14px 0 7px, #6d7 -14px -14px 0 7px, #4ae -14px -14px 0 7px;
|
||||
}
|
||||
|
||||
41.67% {
|
||||
box-shadow: #f86 14px -14px 0 7px, #fc6 -14px 14px 0 7px, #6d7 -14px -14px 0 7px, #4ae 14px -14px 0 7px;
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: #f86 14px 14px 0 7px, #fc6 -14px 14px 0 7px, #6d7 -14px -14px 0 7px, #4ae 14px -14px 0 7px;
|
||||
}
|
||||
|
||||
58.33% {
|
||||
box-shadow: #f86 -14px 14px 0 7px, #fc6 -14px 14px 0 7px, #6d7 -14px -14px 0 7px, #4ae 14px -14px 0 7px;
|
||||
}
|
||||
|
||||
66.67% {
|
||||
box-shadow: #f86 -14px -14px 0 7px, #fc6 -14px -14px 0 7px, #6d7 -14px -14px 0 7px, #4ae 14px -14px 0 7px;
|
||||
}
|
||||
|
||||
75% {
|
||||
box-shadow: #f86 14px -14px 0 7px, #fc6 14px -14px 0 7px, #6d7 14px -14px 0 7px, #4ae 14px -14px 0 7px;
|
||||
}
|
||||
|
||||
83.33% {
|
||||
box-shadow: #f86 14px 14px 0 7px, #fc6 14px -14px 0 7px, #6d7 14px 14px 0 7px, #4ae 14px 14px 0 7px;
|
||||
}
|
||||
|
||||
91.67% {
|
||||
box-shadow: #f86 -14px 14px 0 7px, #fc6 14px -14px 0 7px, #6d7 14px 14px 0 7px, #4ae -14px 14px 0 7px;
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: #f86 -14px -14px 0 7px, #fc6 14px -14px 0 7px, #6d7 14px 14px 0 7px, #4ae -14px 14px 0 7px;
|
||||
}
|
||||
}
|
||||
104
src/styles/spinner/gauge.css
Normal file
@@ -0,0 +1,104 @@
|
||||
.gauge-loader:not(:required) {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 64px;
|
||||
height: 32px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
background: #6ca;
|
||||
border-top-left-radius: 32px;
|
||||
border-top-right-radius: 32px;
|
||||
}
|
||||
|
||||
.gauge-loader:not(:required)::before {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 30px;
|
||||
width: 4px;
|
||||
height: 27px;
|
||||
content: '';
|
||||
background: white;
|
||||
border-radius: 2px;
|
||||
transform-origin: 50% 100%;
|
||||
animation: gauge-loader 4000ms infinite ease;
|
||||
}
|
||||
|
||||
.gauge-loader:not(:required)::after {
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
left: 26px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
content: '';
|
||||
background: white;
|
||||
-moz-border-radius: 8px;
|
||||
-webkit-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@keyframes gauge-loader {
|
||||
0% {
|
||||
transform: rotate(-50deg);
|
||||
}
|
||||
|
||||
10% {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: rotate(60deg);
|
||||
}
|
||||
|
||||
24% {
|
||||
transform: rotate(60deg);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: rotate(-20deg);
|
||||
}
|
||||
|
||||
54% {
|
||||
transform: rotate(70deg);
|
||||
}
|
||||
|
||||
56% {
|
||||
transform: rotate(78deg);
|
||||
}
|
||||
|
||||
58% {
|
||||
transform: rotate(73deg);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: rotate(75deg);
|
||||
}
|
||||
|
||||
62% {
|
||||
transform: rotate(70deg);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: rotate(-20deg);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
83% {
|
||||
transform: rotate(25deg);
|
||||
}
|
||||
|
||||
86% {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
89% {
|
||||
transform: rotate(25deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(-50deg);
|
||||
}
|
||||
}
|
||||
51
src/styles/spinner/inner-circles.css
Normal file
@@ -0,0 +1,51 @@
|
||||
.inner-circles-loader:not(:required) {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
background: rgba(25, 165, 152, 0.5);
|
||||
border-radius: 50%;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.inner-circles-loader:not(:required)::before,
|
||||
.inner-circles-loader:not(:required)::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
content: '';
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.inner-circles-loader:not(:required)::before {
|
||||
left: 0;
|
||||
background: #c7efcf;
|
||||
transform-origin: 0 50%;
|
||||
animation: inner-circles-loader 3s infinite;
|
||||
}
|
||||
|
||||
.inner-circles-loader:not(:required)::after {
|
||||
right: 0;
|
||||
background: #eef5db;
|
||||
transform-origin: 100% 50%;
|
||||
animation: inner-circles-loader 3s 0.2s reverse infinite;
|
||||
}
|
||||
|
||||
@keyframes inner-circles-loader {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
341
src/styles/spinner/plus.css
Normal file
@@ -0,0 +1,341 @@
|
||||
.plus-loader:not(:required) {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
background: #f86;
|
||||
-moz-border-radius: 24px;
|
||||
-webkit-border-radius: 24px;
|
||||
border-radius: 24px;
|
||||
-moz-transform: rotateZ(90deg);
|
||||
-ms-transform: rotateZ(90deg);
|
||||
-webkit-transform: rotateZ(90deg);
|
||||
transform: rotateZ(90deg);
|
||||
-moz-transform-origin: 50% 50%;
|
||||
-ms-transform-origin: 50% 50%;
|
||||
-webkit-transform-origin: 50% 50%;
|
||||
transform-origin: 50% 50%;
|
||||
-moz-animation: plus-loader-background 3s infinite ease-in-out;
|
||||
-webkit-animation: plus-loader-background 3s infinite ease-in-out;
|
||||
animation: plus-loader-background 3s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.plus-loader:not(:required)::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 50%;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
background: #f86;
|
||||
-moz-border-radius: 24px 0 0 24px;
|
||||
-webkit-border-radius: 24px;
|
||||
border-radius: 24px 0 0 24px;
|
||||
-moz-transform-origin: 100% 50%;
|
||||
-ms-transform-origin: 100% 50%;
|
||||
-webkit-transform-origin: 100% 50%;
|
||||
transform-origin: 100% 50%;
|
||||
-moz-animation: plus-loader-top 3s infinite linear;
|
||||
-webkit-animation: plus-loader-top 3s infinite linear;
|
||||
animation: plus-loader-top 3s infinite linear;
|
||||
}
|
||||
|
||||
.plus-loader:not(:required)::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 50%;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
background: #fc6;
|
||||
-moz-border-radius: 24px 0 0 24px;
|
||||
-webkit-border-radius: 24px;
|
||||
border-radius: 24px 0 0 24px;
|
||||
-moz-transform-origin: 100% 50%;
|
||||
-ms-transform-origin: 100% 50%;
|
||||
-webkit-transform-origin: 100% 50%;
|
||||
transform-origin: 100% 50%;
|
||||
-moz-animation: plus-loader-bottom 3s infinite linear;
|
||||
-webkit-animation: plus-loader-bottom 3s infinite linear;
|
||||
animation: plus-loader-bottom 3s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes plus-loader-top {
|
||||
2.5% {
|
||||
background: #f86;
|
||||
-moz-transform: rotateY(0deg);
|
||||
-ms-transform: rotateY(0deg);
|
||||
-webkit-transform: rotateY(0deg);
|
||||
transform: rotateY(0deg);
|
||||
-moz-animation-timing-function: ease-in;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
13.75% {
|
||||
background: #ff430d;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
13.76% {
|
||||
background: #ffae0d;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: ease-out;
|
||||
-webkit-animation-timing-function: ease-out;
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
|
||||
25% {
|
||||
background: #fc6;
|
||||
-moz-transform: rotateY(180deg);
|
||||
-ms-transform: rotateY(180deg);
|
||||
-webkit-transform: rotateY(180deg);
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
27.5% {
|
||||
background: #fc6;
|
||||
-moz-transform: rotateY(180deg);
|
||||
-ms-transform: rotateY(180deg);
|
||||
-webkit-transform: rotateY(180deg);
|
||||
transform: rotateY(180deg);
|
||||
-moz-animation-timing-function: ease-in;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
41.25% {
|
||||
background: #ffae0d;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
41.26% {
|
||||
background: #2cc642;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: ease-out;
|
||||
-webkit-animation-timing-function: ease-out;
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: #6d7;
|
||||
-moz-transform: rotateY(0deg);
|
||||
-ms-transform: rotateY(0deg);
|
||||
-webkit-transform: rotateY(0deg);
|
||||
transform: rotateY(0deg);
|
||||
}
|
||||
|
||||
52.5% {
|
||||
background: #6d7;
|
||||
-moz-transform: rotateY(0deg);
|
||||
-ms-transform: rotateY(0deg);
|
||||
-webkit-transform: rotateY(0deg);
|
||||
transform: rotateY(0deg);
|
||||
-moz-animation-timing-function: ease-in;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
63.75% {
|
||||
background: #2cc642;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
63.76% {
|
||||
background: #1386d2;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: ease-out;
|
||||
-webkit-animation-timing-function: ease-out;
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
|
||||
75% {
|
||||
background: #4ae;
|
||||
-moz-transform: rotateY(180deg);
|
||||
-ms-transform: rotateY(180deg);
|
||||
-webkit-transform: rotateY(180deg);
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
77.5% {
|
||||
background: #4ae;
|
||||
-moz-transform: rotateY(180deg);
|
||||
-ms-transform: rotateY(180deg);
|
||||
-webkit-transform: rotateY(180deg);
|
||||
transform: rotateY(180deg);
|
||||
-moz-animation-timing-function: ease-in;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
91.25% {
|
||||
background: #1386d2;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
91.26% {
|
||||
background: #ff430d;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: ease-in;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
100% {
|
||||
background: #f86;
|
||||
-moz-transform: rotateY(0deg);
|
||||
-ms-transform: rotateY(0deg);
|
||||
-webkit-transform: rotateY(0deg);
|
||||
transform: rotateY(0deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes plus-loader-bottom {
|
||||
0% {
|
||||
background: #fc6;
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: #fc6;
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
75% {
|
||||
background: #4ae;
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
100% {
|
||||
background: #4ae;
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes plus-loader-background {
|
||||
0% {
|
||||
background: #f86;
|
||||
-moz-transform: rotateZ(180deg);
|
||||
-ms-transform: rotateZ(180deg);
|
||||
-webkit-transform: rotateZ(180deg);
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
|
||||
25% {
|
||||
background: #f86;
|
||||
-moz-transform: rotateZ(180deg);
|
||||
-ms-transform: rotateZ(180deg);
|
||||
-webkit-transform: rotateZ(180deg);
|
||||
transform: rotateZ(180deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
27.5% {
|
||||
background: #6d7;
|
||||
-moz-transform: rotateZ(90deg);
|
||||
-ms-transform: rotateZ(90deg);
|
||||
-webkit-transform: rotateZ(90deg);
|
||||
transform: rotateZ(90deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
background: #6d7;
|
||||
-moz-transform: rotateZ(90deg);
|
||||
-ms-transform: rotateZ(90deg);
|
||||
-webkit-transform: rotateZ(90deg);
|
||||
transform: rotateZ(90deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
52.5% {
|
||||
background: #6d7;
|
||||
-moz-transform: rotateZ(0deg);
|
||||
-ms-transform: rotateZ(0deg);
|
||||
-webkit-transform: rotateZ(0deg);
|
||||
transform: rotateZ(0deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
background: #6d7;
|
||||
-moz-transform: rotateZ(0deg);
|
||||
-ms-transform: rotateZ(0deg);
|
||||
-webkit-transform: rotateZ(0deg);
|
||||
transform: rotateZ(0deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
77.5% {
|
||||
background: #f86;
|
||||
-moz-transform: rotateZ(270deg);
|
||||
-ms-transform: rotateZ(270deg);
|
||||
-webkit-transform: rotateZ(270deg);
|
||||
transform: rotateZ(270deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
background: #f86;
|
||||
-moz-transform: rotateZ(270deg);
|
||||
-ms-transform: rotateZ(270deg);
|
||||
-webkit-transform: rotateZ(270deg);
|
||||
transform: rotateZ(270deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
}
|
||||
1
src/styles/themes/default.scss
Normal file
@@ -0,0 +1 @@
|
||||
/* 绿荫草场主题、荣耀典藏主题、暗黑之子主题加QQ讨论群972435319、1139183756后私聊群主获取,获取后将主题放到themes文件夹根目录即可 */
|
||||
19
src/styles/transition.scss
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description vue过渡动画
|
||||
*/
|
||||
|
||||
@charset "utf-8";
|
||||
|
||||
.fade-transform-leave-active,
|
||||
.fade-transform-enter-active {
|
||||
transition: $base-transition;
|
||||
}
|
||||
|
||||
.fade-transform-enter {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-transform-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
281
src/styles/vab.scss
Normal file
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 全局样式
|
||||
*/
|
||||
|
||||
@charset "utf-8";
|
||||
|
||||
@import './normalize.scss';
|
||||
@import './transition.scss';
|
||||
@import './loading.scss';
|
||||
|
||||
$base: '.vab';
|
||||
|
||||
@mixin scrollbar {
|
||||
max-height: 88vh;
|
||||
margin-bottom: 0.5vh;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin base-scrollbar {
|
||||
&::-webkit-scrollbar {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
background-clip: padding-box;
|
||||
border: 3px solid transparent;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track:hover {
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $base-color-blue;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
* {
|
||||
transition: $base-transition;
|
||||
}
|
||||
|
||||
svg {
|
||||
transition: none;
|
||||
|
||||
* {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
body {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: $base-font-size-default;
|
||||
color: #2c3e50;
|
||||
background: #f6f8f9;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@include base-scrollbar;
|
||||
|
||||
div {
|
||||
@include base-scrollbar;
|
||||
}
|
||||
|
||||
svg,
|
||||
i {
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.v-modal {
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.el-tag + .el-tag {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.editor-toolbar {
|
||||
.no-mobile,
|
||||
.fa-question-circle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.el-divider--horizontal {
|
||||
margin: 10px 0 25px 0;
|
||||
|
||||
.el-divider__text {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
|
||||
.el-image-viewer {
|
||||
&__close {
|
||||
.el-icon-circle-close {
|
||||
color: $base-color-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vue-admin-better-wrapper {
|
||||
.app-main-container {
|
||||
@include base-scrollbar;
|
||||
|
||||
> [class*='-container'] {
|
||||
* {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
padding: $base-padding;
|
||||
background: $base-color-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 进度条开始 */
|
||||
#nprogress {
|
||||
position: fixed;
|
||||
z-index: $base-z-index;
|
||||
|
||||
.bar {
|
||||
background: $base-color-blue !important;
|
||||
}
|
||||
|
||||
.peg {
|
||||
box-shadow: 0 0 10px $base-color-blue, 0 0 5px $base-color-blue !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table {
|
||||
.el-table__body-wrapper {
|
||||
@include base-scrollbar;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 7.5px 0;
|
||||
|
||||
.cell {
|
||||
font-size: $base-font-size-default;
|
||||
font-weight: normal;
|
||||
color: #606266;
|
||||
|
||||
.el-image {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: $base-border-radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-pagination {
|
||||
padding: 2px 5px;
|
||||
margin: 15px 0 0 0;
|
||||
font-weight: normal;
|
||||
color: $base-color-black;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-menu.el-menu--popup.el-menu--popup-right-start {
|
||||
@include scrollbar;
|
||||
}
|
||||
|
||||
.el-menu.el-menu--popup.el-menu--popup-bottom-start {
|
||||
@include scrollbar;
|
||||
}
|
||||
|
||||
.el-submenu__title i {
|
||||
color: $base-color-white;
|
||||
}
|
||||
|
||||
.el-dialog,
|
||||
.el-message-box {
|
||||
&__body {
|
||||
border-top: 1px solid $base-border-color;
|
||||
|
||||
.el-form {
|
||||
padding-right: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
padding: $base-padding;
|
||||
text-align: right;
|
||||
border-top: 1px solid $base-border-color;
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 20px 20px 20px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-card {
|
||||
margin-bottom: 15px;
|
||||
|
||||
&__body {
|
||||
padding: $base-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.select-tree-popper {
|
||||
.el-scrollbar {
|
||||
.el-scrollbar__view {
|
||||
.el-select-dropdown__item {
|
||||
height: auto;
|
||||
max-height: 274px;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
line-height: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.side-bar-container {
|
||||
.el-menu-item,
|
||||
.el-submenu {
|
||||
margin: 7px !important;
|
||||
border-radius: 5px !important;
|
||||
|
||||
&:hover {
|
||||
border-radius: 5px !important;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: $base-color-default !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
src/styles/variables.scss
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 全局主题变量配置
|
||||
*/
|
||||
/* stylelint-disable */
|
||||
@charset "utf-8";
|
||||
//框架默认主题色
|
||||
$base-color-default: #409eff;
|
||||
//默认层级
|
||||
$base-z-index: 999;
|
||||
//横向布局纵向布局时菜单背景色
|
||||
$base-menu-background: #21252b;
|
||||
//菜单文字颜色
|
||||
$base-menu-color: hsla(0, 0%, 100%, 0.95);
|
||||
//菜单选中文字颜色
|
||||
$base-menu-color-active: hsla(0, 0%, 100%, 0.95);
|
||||
//菜单选中背景色
|
||||
$base-menu-background-active: $base-color-default;
|
||||
//标题颜色
|
||||
$base-title-color: #fff;
|
||||
//字体大小配置
|
||||
$base-font-size-small: 12px;
|
||||
$base-font-size-default: 14px;
|
||||
$base-font-size-big: 16px;
|
||||
$base-font-size-bigger: 18px;
|
||||
$base-font-size-max: 22px;
|
||||
$base-font-color: #606266;
|
||||
$base-color-blue: $base-color-default;
|
||||
$base-color-green: #41b882;
|
||||
$base-color-white: #fff;
|
||||
$base-color-black: #000;
|
||||
$base-color-yellow: #ffa91b;
|
||||
$base-color-orange: #ff6700;
|
||||
$base-color-red: #f34d37;
|
||||
$base-color-gray: rgba(0, 0, 0, 0.65);
|
||||
$base-main-width: 1279px;
|
||||
$base-border-radius: 4px;
|
||||
$base-border-color: #dcdfe6;
|
||||
//输入框高度
|
||||
$base-input-height: 32px;
|
||||
//默认paddiing
|
||||
$base-padding: 20px;
|
||||
//默认阴影
|
||||
$base-box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
//横向布局时top-bar、logo、一级菜单的高度
|
||||
$base-top-bar-height: 65px;
|
||||
//纵向布局时logo的高度
|
||||
$base-logo-height: 75px;
|
||||
//顶部nav-bar的高度
|
||||
$base-nav-bar-height: 60px;
|
||||
//顶部多标签页tabs-bar的高度
|
||||
$base-tabs-bar-height: 55px;
|
||||
//顶部多标签页tabs-bar中每一个item的高度
|
||||
$base-tag-item-height: 34px;
|
||||
//菜单li标签的高度
|
||||
$base-menu-item-height: 50px;
|
||||
//app-main的高度
|
||||
$base-app-main-height: calc(100vh - #{$base-nav-bar-height} - #{$base-tabs-bar-height} - #{$base-padding} - #{$base-padding} - 55px - 55px);
|
||||
//纵向布局时左侧导航未折叠时的宽度
|
||||
$base-left-menu-width: 256px;
|
||||
//纵向布局时左侧导航未折叠时右侧内容的宽度
|
||||
$base-right-content-width: calc(100% - #{$base-left-menu-width});
|
||||
//纵向布局时左侧导航已折叠时的宽度
|
||||
$base-left-menu-width-min: 65px;
|
||||
//纵向布局时左侧导航已折叠时右侧内容的宽度
|
||||
$base-right-content-width-min: calc(100% - #{$base-left-menu-width-min});
|
||||
//默认动画
|
||||
$base-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border 0s, background 0s, color 0s, font-size 0s;
|
||||
//默认动画长
|
||||
$base-transition-time: 0.3s;
|
||||
|
||||
:export {
|
||||
//菜单文字颜色变量导出
|
||||
menu-color: $base-menu-color;
|
||||
//菜单选中文字颜色变量导出
|
||||
menu-color-active: $base-menu-color-active;
|
||||
//菜单背景色变量导出
|
||||
menu-background: $base-menu-background;
|
||||
}
|
||||
59
src/utils/accessToken.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { storage, tokenTableName } from '@/config'
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 获取accessToken
|
||||
* @returns {string|ActiveX.IXMLDOMNode|Promise<any>|any|IDBRequest<any>|MediaKeyStatus|FormDataEntryValue|Function|Promise<Credential | null>}
|
||||
*/
|
||||
export function getAccessToken() {
|
||||
if (storage) {
|
||||
if ('localStorage' === storage) {
|
||||
return localStorage.getItem(tokenTableName)
|
||||
} else if ('sessionStorage' === storage) {
|
||||
return sessionStorage.getItem(tokenTableName)
|
||||
} else {
|
||||
return localStorage.getItem(tokenTableName)
|
||||
}
|
||||
} else {
|
||||
return localStorage.getItem(tokenTableName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 存储accessToken
|
||||
* @param accessToken
|
||||
* @returns {void|*}
|
||||
*/
|
||||
export function setAccessToken(accessToken) {
|
||||
if (storage) {
|
||||
if ('localStorage' === storage) {
|
||||
return localStorage.setItem(tokenTableName, accessToken)
|
||||
} else if ('sessionStorage' === storage) {
|
||||
return sessionStorage.setItem(tokenTableName, accessToken)
|
||||
} else {
|
||||
return localStorage.setItem(tokenTableName, accessToken)
|
||||
}
|
||||
} else {
|
||||
return localStorage.setItem(tokenTableName, accessToken)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 移除accessToken
|
||||
* @returns {void|Promise<void>}
|
||||
*/
|
||||
export function removeAccessToken() {
|
||||
if (storage) {
|
||||
if ('localStorage' === storage) {
|
||||
return localStorage.removeItem(tokenTableName)
|
||||
} else if ('sessionStorage' === storage) {
|
||||
return sessionStorage.clear()
|
||||
} else {
|
||||
return localStorage.removeItem(tokenTableName)
|
||||
}
|
||||
} else {
|
||||
return localStorage.removeItem(tokenTableName)
|
||||
}
|
||||
}
|
||||
31
src/utils/clipboard.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import Vue from 'vue'
|
||||
import Clipboard from 'clipboard'
|
||||
|
||||
function clipboardSuccess() {
|
||||
Vue.prototype.$baseMessage('复制成功', 'success')
|
||||
}
|
||||
|
||||
function clipboardError() {
|
||||
Vue.prototype.$baseMessage('复制失败', 'error')
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 复制数据
|
||||
* @param text
|
||||
* @param event
|
||||
*/
|
||||
export default function handleClipboard(text, event) {
|
||||
const clipboard = new Clipboard(event.target, {
|
||||
text: () => text,
|
||||
})
|
||||
clipboard.on('success', () => {
|
||||
clipboardSuccess()
|
||||
clipboard.destroy()
|
||||
})
|
||||
clipboard.on('error', () => {
|
||||
clipboardError()
|
||||
clipboard.destroy()
|
||||
})
|
||||
clipboard.onClick(event)
|
||||
}
|
||||
42
src/utils/encrypt.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import JSEncrypt from 'jsencrypt'
|
||||
import { getPublicKey } from '@/api/publicKey'
|
||||
|
||||
const privateKey =
|
||||
'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMFPa+v52FkSUXvcUnrGI/XzW3EpZRI0s9BCWJ3oNQmEYA5luWW5p8h0uadTIoTyYweFPdH4hveyxlwmS7oefvbIdiP+o+QIYW/R4Wjsb4Yl8MhR4PJqUE3RCy6IT9fM8ckG4kN9ECs6Ja8fQFc6/mSl5dJczzJO3k1rWMBhKJD/AgMBAAECgYEAucMakH9dWeryhrYoRHcXo4giPVJsH9ypVt4KzmOQY/7jV7KFQK3x//27UoHfUCak51sxFw9ek7UmTPM4HjikA9LkYeE7S381b4QRvFuf3L6IbMP3ywJnJ8pPr2l5SqQ00W+oKv+w/VmEsyUHr+k4Z+4ik+FheTkVWp566WbqFsECQQDjYaMcaKw3j2Zecl8T6eUe7fdaRMIzp/gcpPMfT/9rDzIQk+7ORvm1NI9AUmFv/FAlfpuAMrdL2n7p9uznWb7RAkEA2aP934kbXg5bdV0R313MrL+7WTK/qdcYxATUbMsMuWWQBoS5irrt80WCZbG48hpocJavLNjbtrjmUX3CuJBmzwJAOJg8uP10n/+ZQzjEYXh+BszEHDuw+pp8LuT/fnOy5zrJA0dO0RjpXijO3vuiNPVgHXT9z1LQPJkNrb5ACPVVgQJBALPeb4uV0bNrJDUb5RB4ghZnIxv18CcaqNIft7vuGCcFBAIPIRTBprR+RuVq+xHDt3sNXdsvom4h49+Hky1b0ksCQBBwUtVaqH6ztCtwUF1j2c/Zcrt5P/uN7IHAd44K0gIJc1+Csr3qPG+G2yoqRM8KVqLI8Z2ZYn9c+AvEE+L9OQY='
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description RSA加密
|
||||
* @param data
|
||||
* @returns {Promise<{param: PromiseLike<ArrayBuffer>}|*>}
|
||||
*/
|
||||
export async function encryptedData(data) {
|
||||
let publicKey = ''
|
||||
const res = await getPublicKey()
|
||||
publicKey = res.data.publicKey
|
||||
if (res.data.mockServer) {
|
||||
publicKey = ''
|
||||
}
|
||||
if (publicKey == '') {
|
||||
return data
|
||||
}
|
||||
const encrypt = new JSEncrypt()
|
||||
encrypt.setPublicKey(`-----BEGIN PUBLIC KEY-----${publicKey}-----END PUBLIC KEY-----`)
|
||||
data = encrypt.encrypt(JSON.stringify(data))
|
||||
return {
|
||||
param: data,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description RSA解密
|
||||
* @param data
|
||||
* @returns {PromiseLike<ArrayBuffer>}
|
||||
*/
|
||||
export function decryptedData(data) {
|
||||
const decrypt = new JSEncrypt()
|
||||
decrypt.setPrivateKey(`-----BEGIN RSA PRIVATE KEY-----${privateKey}-----END RSA PRIVATE KEY-----`)
|
||||
data = decrypt.decrypt(JSON.stringify(data))
|
||||
return data
|
||||
}
|
||||
25
src/utils/errorLog.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import Vue from 'vue'
|
||||
import store from '@/store'
|
||||
import { isArray, isString } from '@/utils/validate'
|
||||
import { errorLog } from '@/config'
|
||||
|
||||
const needErrorLog = errorLog
|
||||
const checkNeed = () => {
|
||||
const env = process.env.NODE_ENV
|
||||
if (isString(needErrorLog)) {
|
||||
return env === needErrorLog
|
||||
}
|
||||
if (isArray(needErrorLog)) {
|
||||
return needErrorLog.includes(env)
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (checkNeed()) {
|
||||
Vue.config.errorHandler = (err, vm, info) => {
|
||||
console.error('vue-admin-beautiful错误拦截:', err, vm, info)
|
||||
const url = window.location.href
|
||||
Vue.nextTick(() => {
|
||||
store.dispatch('errorLog/addErrorLog', { err, vm, info, url })
|
||||
})
|
||||
}
|
||||
}
|
||||
60
src/utils/handleRoutes.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description all模式渲染后端返回路由
|
||||
* @param constantRoutes
|
||||
* @returns {*}
|
||||
*/
|
||||
export function convertRouter(asyncRoutes) {
|
||||
return asyncRoutes.map((route) => {
|
||||
if (route.component) {
|
||||
if (route.component === 'Layout') {
|
||||
route.component = (resolve) => require(['@/layouts'], resolve)
|
||||
} else if (route.component === 'EmptyLayout') {
|
||||
route.component = (resolve) => require(['@/layouts/EmptyLayout'], resolve)
|
||||
} else {
|
||||
const index = route.component.indexOf('views')
|
||||
const path = index > 0 ? route.component.slice(index) : `views/${route.component}`
|
||||
route.component = (resolve) => require([`@/${path}`], resolve)
|
||||
}
|
||||
}
|
||||
if (route.children && route.children.length) route.children = convertRouter(route.children)
|
||||
if (route.children && route.children.length === 0) delete route.children
|
||||
return route
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 判断当前路由是否包含权限
|
||||
* @param permissions
|
||||
* @param route
|
||||
* @returns {boolean|*}
|
||||
*/
|
||||
function hasPermission(permissions, route) {
|
||||
if (route.meta && route.meta.permissions) {
|
||||
return permissions.some((role) => route.meta.permissions.includes(role))
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description intelligence模式根据permissions数组拦截路由
|
||||
* @param routes
|
||||
* @param permissions
|
||||
* @returns {[]}
|
||||
*/
|
||||
export function filterAsyncRoutes(routes, permissions) {
|
||||
const finallyRoutes = []
|
||||
routes.forEach((route) => {
|
||||
const item = { ...route }
|
||||
if (hasPermission(permissions, item)) {
|
||||
if (item.children) {
|
||||
item.children = filterAsyncRoutes(item.children, permissions)
|
||||
}
|
||||
finallyRoutes.push(item)
|
||||
}
|
||||
})
|
||||
return finallyRoutes
|
||||
}
|
||||
250
src/utils/index.js
Normal file
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 格式化时间
|
||||
* @param time
|
||||
* @param cFormat
|
||||
* @returns {string|null}
|
||||
*/
|
||||
export function parseTime(time, cFormat) {
|
||||
if (arguments.length === 0) {
|
||||
return null
|
||||
}
|
||||
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||
let date
|
||||
if (typeof time === 'object') {
|
||||
date = time
|
||||
} else {
|
||||
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
|
||||
time = parseInt(time)
|
||||
}
|
||||
if (typeof time === 'number' && time.toString().length === 10) {
|
||||
time = time * 1000
|
||||
}
|
||||
date = new Date(time)
|
||||
}
|
||||
const formatObj = {
|
||||
y: date.getFullYear(),
|
||||
m: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
i: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
a: date.getDay(),
|
||||
}
|
||||
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||
let value = formatObj[key]
|
||||
if (key === 'a') {
|
||||
return ['日', '一', '二', '三', '四', '五', '六'][value]
|
||||
}
|
||||
if (result.length > 0 && value < 10) {
|
||||
value = `0${value}`
|
||||
}
|
||||
return value || 0
|
||||
})
|
||||
return time_str
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 格式化时间
|
||||
* @param time
|
||||
* @param option
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatTime(time, option) {
|
||||
if (typeof time === 'string' && !isNaN(Date.parse(time))) {
|
||||
// 处理 ISO 8601 格式的时间
|
||||
time = new Date(time)
|
||||
} else if (`${time}`.length === 10) {
|
||||
// 处理秒级时间戳
|
||||
time = new Date(parseInt(time) * 1000)
|
||||
} else {
|
||||
// 处理毫秒级时间戳
|
||||
time = new Date(+time)
|
||||
}
|
||||
|
||||
if (isNaN(time.getTime())) {
|
||||
return 'Invalid Date'
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
const diff = (now - time) / 1000
|
||||
|
||||
if (diff < 30) {
|
||||
return '刚刚'
|
||||
} else if (diff < 3600) {
|
||||
return `${Math.ceil(diff / 60)}分钟前`
|
||||
} else if (diff < 3600 * 24) {
|
||||
return `${Math.ceil(diff / 3600)}小时前`
|
||||
} else if (diff < 3600 * 24 * 2) {
|
||||
return '1天前'
|
||||
}
|
||||
|
||||
if (option) {
|
||||
return parseTime(time, option)
|
||||
} else {
|
||||
return `${time.getMonth() + 1}月${time.getDate()}日${time.getHours()}时${time.getMinutes()}分`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 将url请求参数转为json格式
|
||||
* @param url
|
||||
* @returns {{}|any}
|
||||
*/
|
||||
export function paramObj(url) {
|
||||
const search = url.split('?')[1]
|
||||
if (!search) {
|
||||
return {}
|
||||
}
|
||||
return JSON.parse(`{"${decodeURIComponent(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"').replace(/\+/g, ' ')}"}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 父子关系的数组转换成树形结构数据
|
||||
* @param data
|
||||
* @returns {*}
|
||||
*/
|
||||
export function translateDataToTree(data) {
|
||||
const parent = data.filter((value) => value.parentId === 'undefined' || value.parentId == null)
|
||||
const children = data.filter((value) => value.parentId !== 'undefined' && value.parentId != null)
|
||||
const translator = (parent, children) => {
|
||||
parent.forEach((parent) => {
|
||||
children.forEach((current, index) => {
|
||||
if (current.parentId === parent.id) {
|
||||
const temp = JSON.parse(JSON.stringify(children))
|
||||
temp.splice(index, 1)
|
||||
translator([current], temp)
|
||||
typeof parent.children !== 'undefined' ? parent.children.push(current) : (parent.children = [current])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
translator(parent, children)
|
||||
return parent
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 树形结构数据转换成父子关系的数组
|
||||
* @param data
|
||||
* @returns {[]}
|
||||
*/
|
||||
export function translateTreeToData(data) {
|
||||
const result = []
|
||||
data.forEach((item) => {
|
||||
const loop = (data) => {
|
||||
result.push({
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
parentId: data.parentId,
|
||||
})
|
||||
const child = data.children
|
||||
if (child) {
|
||||
for (let i = 0; i < child.length; i++) {
|
||||
loop(child[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
loop(item)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 10位时间戳转换
|
||||
* @param time
|
||||
* @returns {string}
|
||||
*/
|
||||
export function tenBitTimestamp(time) {
|
||||
const date = new Date(time * 1000)
|
||||
const y = date.getFullYear()
|
||||
let m = date.getMonth() + 1
|
||||
m = m < 10 ? `${m}` : m
|
||||
let d = date.getDate()
|
||||
d = d < 10 ? `${d}` : d
|
||||
let h = date.getHours()
|
||||
h = h < 10 ? `0${h}` : h
|
||||
let minute = date.getMinutes()
|
||||
let second = date.getSeconds()
|
||||
minute = minute < 10 ? `0${minute}` : minute
|
||||
second = second < 10 ? `0${second}` : second
|
||||
return `${y}年${m}月${d}日 ${h}:${minute}:${second}` //组合
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 13位时间戳转换
|
||||
* @param time
|
||||
* @returns {string}
|
||||
*/
|
||||
export function thirteenBitTimestamp(time) {
|
||||
const date = new Date(time / 1)
|
||||
const y = date.getFullYear()
|
||||
let m = date.getMonth() + 1
|
||||
m = m < 10 ? `${m}` : m
|
||||
let d = date.getDate()
|
||||
d = d < 10 ? `${d}` : d
|
||||
let h = date.getHours()
|
||||
h = h < 10 ? `0${h}` : h
|
||||
let minute = date.getMinutes()
|
||||
let second = date.getSeconds()
|
||||
minute = minute < 10 ? `0${minute}` : minute
|
||||
second = second < 10 ? `0${second}` : second
|
||||
return `${y}年${m}月${d}日 ${h}:${minute}:${second}` //组合
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 获取随机id
|
||||
* @param length
|
||||
* @returns {string}
|
||||
*/
|
||||
export function uuid(length = 32) {
|
||||
const num = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
|
||||
let str = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
str += num.charAt(Math.floor(Math.random() * num.length))
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description m到n的随机数
|
||||
* @param m
|
||||
* @param n
|
||||
* @returns {number}
|
||||
*/
|
||||
export function random(m, n) {
|
||||
return Math.floor(Math.random() * (m - n) + n)
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description addEventListener
|
||||
* @type {function(...[*]=)}
|
||||
*/
|
||||
export const on = (function () {
|
||||
return function (element, event, handler, useCapture = false) {
|
||||
if (element && event && handler) {
|
||||
element.addEventListener(event, handler, useCapture)
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description removeEventListener
|
||||
* @type {function(...[*]=)}
|
||||
*/
|
||||
export const off = (function () {
|
||||
return function (element, event, handler, useCapture = false) {
|
||||
if (element && event) {
|
||||
element.removeEventListener(event, handler, useCapture)
|
||||
}
|
||||
}
|
||||
})()
|
||||
12
src/utils/pageTitle.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { title } from '@/config'
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 设置标题
|
||||
* @param pageTitle
|
||||
* @returns {string}
|
||||
*/
|
||||
export default function getPageTitle(pageTitle) {
|
||||
if (pageTitle) return `${pageTitle}-${title}`
|
||||
return `${title}`
|
||||
}
|
||||
20
src/utils/permission.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import store from '@/store'
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 检查权限
|
||||
* @param value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export default function checkPermission(value) {
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
const permissions = store.getters['user/permissions']
|
||||
const permissionPermissions = value
|
||||
|
||||
return permissions.some((role) => {
|
||||
return permissionPermissions.includes(role)
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
126
src/utils/request.js
Normal file
@@ -0,0 +1,126 @@
|
||||
import Vue from 'vue'
|
||||
import axios from 'axios'
|
||||
import {
|
||||
baseURL,
|
||||
contentType,
|
||||
debounce,
|
||||
invalidCode,
|
||||
loginInterception,
|
||||
noPermissionCode,
|
||||
requestTimeout,
|
||||
successCode,
|
||||
tokenName,
|
||||
} from '@/config'
|
||||
import store from '@/store'
|
||||
import qs from 'qs'
|
||||
import router from '@/router'
|
||||
import { isArray } from '@/utils/validate'
|
||||
|
||||
let loadingInstance
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 处理code异常
|
||||
* @param {*} code
|
||||
* @param {*} msg
|
||||
*/
|
||||
const handleCode = (code, msg) => {
|
||||
switch (code) {
|
||||
case invalidCode:
|
||||
Vue.prototype.$baseMessage(msg || `后端接口${code}异常`, 'error')
|
||||
store.dispatch('user/resetAccessToken').catch(() => {})
|
||||
if (loginInterception) {
|
||||
location.reload()
|
||||
}
|
||||
break
|
||||
case noPermissionCode:
|
||||
router.push({ path: '/401' }).catch(() => {})
|
||||
break
|
||||
default:
|
||||
Vue.prototype.$baseMessage(msg || `后端接口${code}异常`, 'error')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL,
|
||||
timeout: requestTimeout,
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
},
|
||||
})
|
||||
|
||||
instance.interceptors.request.use(
|
||||
(config) => {
|
||||
if (store.getters['user/accessToken']) {
|
||||
config.headers['authorization'] = store.getters['user/accessToken']
|
||||
}
|
||||
// 处理 GET 请求参数
|
||||
if (config.method === 'get' && config.params) {
|
||||
// 过滤掉空值参数
|
||||
config.params = Vue.prototype.$baseLodash.pickBy(config.params, Vue.prototype.$baseLodash.identity)
|
||||
}
|
||||
//这里会过滤所有为空、0、false的key,如果不需要请自行注释
|
||||
if (config.data) config.data = Vue.prototype.$baseLodash.pickBy(config.data, Vue.prototype.$baseLodash.identity)
|
||||
if (config.data && config.headers['Content-Type'] === 'application/x-www-form-urlencoded;charset=UTF-8')
|
||||
config.data = qs.stringify(config.data)
|
||||
if (debounce.some((item) => config.url.includes(item))) loadingInstance = Vue.prototype.$baseLoading()
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
instance.interceptors.response.use(
|
||||
(response) => {
|
||||
if (loadingInstance) loadingInstance.close()
|
||||
|
||||
const { data, config } = response
|
||||
const { code, msg } = data
|
||||
// 操作正常Code数组
|
||||
const codeVerificationArray = isArray(successCode) ? [...successCode] : [...[successCode]]
|
||||
// 是否操作正常
|
||||
if (codeVerificationArray.includes(code)) {
|
||||
if (data?.data?.code >= 400) {
|
||||
Vue.prototype.$baseMessage(data?.data?.msg, 'error')
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
} else {
|
||||
handleCode(code, msg)
|
||||
return Promise.reject(
|
||||
`vue-admin-beautiful请求异常拦截:${JSON.stringify({
|
||||
url: config.url,
|
||||
code,
|
||||
msg,
|
||||
})}` || 'Error'
|
||||
)
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
if (loadingInstance) loadingInstance.close()
|
||||
const { response, message } = error
|
||||
if (error.response && error.response.data) {
|
||||
const { status, data } = response
|
||||
handleCode(status, data.msg || message)
|
||||
return Promise.reject(error)
|
||||
} else {
|
||||
let { message } = error
|
||||
if (message === 'Network Error') {
|
||||
message = '后端接口连接异常'
|
||||
}
|
||||
if (message.includes('timeout')) {
|
||||
message = '后端接口请求超时'
|
||||
}
|
||||
if (message.includes('Request failed with status code')) {
|
||||
const code = message.substr(message.length - 3)
|
||||
message = `后端接口${code}异常`
|
||||
}
|
||||
Vue.prototype.$baseMessage(message || `后端接口未知异常`, 'error')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default instance
|
||||
48
src/utils/static.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @author https://vuejs-core.cn
|
||||
* @description 导入所有 controller 模块,浏览器环境中自动输出controller文件夹下Mock接口,请勿修改。
|
||||
*/
|
||||
import Mock from 'mockjs'
|
||||
import { paramObj } from '@/utils'
|
||||
|
||||
const mocks = []
|
||||
const files = require.context('../../mock/controller', false, /\.js$/)
|
||||
|
||||
files.keys().forEach((key) => {
|
||||
mocks.push(...files(key))
|
||||
})
|
||||
|
||||
export function mockXHR() {
|
||||
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
|
||||
Mock.XHR.prototype.send = function () {
|
||||
if (this.custom.xhr) {
|
||||
this.custom.xhr.withCredentials = this.withCredentials || false
|
||||
|
||||
if (this.responseType) {
|
||||
this.custom.xhr.responseType = this.responseType
|
||||
}
|
||||
}
|
||||
this.proxy_send(...arguments)
|
||||
}
|
||||
|
||||
function XHRHttpRequst(respond) {
|
||||
return function (options) {
|
||||
let result
|
||||
if (respond instanceof Function) {
|
||||
const { body, type, url } = options
|
||||
result = respond({
|
||||
method: type,
|
||||
body: JSON.parse(body),
|
||||
query: paramObj(url),
|
||||
})
|
||||
} else {
|
||||
result = respond
|
||||
}
|
||||
return Mock.mock(result)
|
||||
}
|
||||
}
|
||||
|
||||
mocks.forEach((item) => {
|
||||
Mock.mock(new RegExp(item.url), item.type || 'get', XHRHttpRequst(item.response))
|
||||
})
|
||||
}
|
||||
156
src/utils/vab.js
Normal file
@@ -0,0 +1,156 @@
|
||||
import { loadingText, messageDuration, title } from '@/config'
|
||||
import * as lodash from 'lodash'
|
||||
import { Loading, Message, MessageBox, Notification } from 'element-ui'
|
||||
import store from '@/store'
|
||||
import { getAccessToken } from '@/utils/accessToken'
|
||||
|
||||
const accessToken = store.getters['user/accessToken']
|
||||
const layout = store.getters['settings/layout']
|
||||
|
||||
const install = (Vue) => {
|
||||
/* 全局accessToken */
|
||||
Vue.prototype.$baseAccessToken = () => {
|
||||
return accessToken || getAccessToken()
|
||||
}
|
||||
/* 全局标题 */
|
||||
Vue.prototype.$baseTitle = (() => {
|
||||
return title
|
||||
})()
|
||||
/* 全局加载层 */
|
||||
Vue.prototype.$baseLoading = (index, text) => {
|
||||
let loading
|
||||
if (!index) {
|
||||
loading = Loading.service({
|
||||
lock: true,
|
||||
text: text || loadingText,
|
||||
background: 'hsla(0,0%,100%,.8)',
|
||||
})
|
||||
} else {
|
||||
loading = Loading.service({
|
||||
lock: true,
|
||||
text: text || loadingText,
|
||||
spinner: `vab-loading-type${index}`,
|
||||
background: 'hsla(0,0%,100%,.8)',
|
||||
})
|
||||
}
|
||||
return loading
|
||||
}
|
||||
/* 全局多彩加载层 */
|
||||
Vue.prototype.$baseColorfullLoading = (index, text) => {
|
||||
let loading
|
||||
if (!index) {
|
||||
loading = Loading.service({
|
||||
lock: true,
|
||||
text: text || loadingText,
|
||||
spinner: 'dots-loader',
|
||||
background: 'hsla(0,0%,100%,.8)',
|
||||
})
|
||||
} else {
|
||||
switch (index) {
|
||||
case 1:
|
||||
index = 'dots'
|
||||
break
|
||||
case 2:
|
||||
index = 'gauge'
|
||||
break
|
||||
case 3:
|
||||
index = 'inner-circles'
|
||||
break
|
||||
case 4:
|
||||
index = 'plus'
|
||||
break
|
||||
}
|
||||
loading = Loading.service({
|
||||
lock: true,
|
||||
text: text || loadingText,
|
||||
spinner: `${index}-loader`,
|
||||
background: 'hsla(0,0%,100%,.8)',
|
||||
})
|
||||
}
|
||||
return loading
|
||||
}
|
||||
/* 全局Message */
|
||||
Vue.prototype.$baseMessage = (message, type) => {
|
||||
Message({
|
||||
offset: 60,
|
||||
showClose: true,
|
||||
message: message,
|
||||
type: type,
|
||||
dangerouslyUseHTMLString: true,
|
||||
duration: messageDuration,
|
||||
})
|
||||
}
|
||||
|
||||
/* 全局Alert */
|
||||
Vue.prototype.$baseAlert = (content, title, callback) => {
|
||||
MessageBox.alert(content, title || '温馨提示', {
|
||||
confirmButtonText: '确定',
|
||||
dangerouslyUseHTMLString: true,
|
||||
callback: () => {
|
||||
if (callback) {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/* 全局Confirm */
|
||||
Vue.prototype.$baseConfirm = (content, title, callback1, callback2) => {
|
||||
MessageBox.confirm(content, title || '温馨提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
closeOnClickModal: false,
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
if (callback1) {
|
||||
callback1()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (callback2) {
|
||||
callback2()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* 全局Notification */
|
||||
Vue.prototype.$baseNotify = (message, title, type, position) => {
|
||||
Notification({
|
||||
title: title,
|
||||
message: message,
|
||||
position: position || 'top-right',
|
||||
type: type || 'success',
|
||||
duration: messageDuration,
|
||||
})
|
||||
}
|
||||
|
||||
/* 全局TableHeight */
|
||||
Vue.prototype.$baseTableHeight = (formType) => {
|
||||
let height = window.innerHeight
|
||||
let paddingHeight = 400
|
||||
const formHeight = 50
|
||||
|
||||
if (layout === 'vertical') {
|
||||
paddingHeight = 365
|
||||
}
|
||||
|
||||
if ('number' == typeof formType) {
|
||||
height = height - paddingHeight - formHeight * formType
|
||||
} else {
|
||||
height = height - paddingHeight
|
||||
}
|
||||
return height
|
||||
}
|
||||
|
||||
/* 全局lodash */
|
||||
Vue.prototype.$baseLodash = lodash
|
||||
/* 全局事件总线 */
|
||||
Vue.prototype.$baseEventBus = new Vue()
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && window.Vue) {
|
||||
install(window.Vue)
|
||||
}
|
||||
|
||||
export default install
|
||||
53
src/utils/validate.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @author zxwk1998 (不想保留author可删除)
|
||||
* @description 判读是否为外链
|
||||
* @param path
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isExternal(path) {
|
||||
return /^(https?:|mailto:|tel:)/.test(path)
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://github.com/zxwk1998/vue-admin-better (不想保留author可删除)
|
||||
* @description 校验密码是否小于6位
|
||||
* @param str
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPassword(str) {
|
||||
return str.length >= 6
|
||||
}
|
||||
|
||||
/**
|
||||
* @author zxwk1998 (不想保留author可删除)
|
||||
* @description 判断是否是字符串
|
||||
* @param str
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isString(str) {
|
||||
return typeof str === 'string' || str instanceof String
|
||||
}
|
||||
|
||||
/**
|
||||
* @author zxwk1998 (不想保留author可删除)
|
||||
* @description 判断是否是数组
|
||||
* @param arg
|
||||
* @returns {arg is any[]|boolean}
|
||||
*/
|
||||
export function isArray(arg) {
|
||||
if (typeof Array.isArray === 'undefined') {
|
||||
return Object.prototype.toString.call(arg) === '[object Array]'
|
||||
}
|
||||
return Array.isArray(arg)
|
||||
}
|
||||
|
||||
/**
|
||||
* @author zxwk1998 (不想保留author可删除)
|
||||
* @description 判断是否是手机号
|
||||
* @param str
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPhone(str) {
|
||||
const reg = /^1\d{10}$/
|
||||
return reg.test(str)
|
||||
}
|
||||
284
src/views/401.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div class="error-container">
|
||||
<div class="error-content">
|
||||
<el-row :gutter="20">
|
||||
<el-col :lg="12" :md="12" :sm="24" :xl="12" :xs="24">
|
||||
<div class="pic-error">
|
||||
<img alt="401" class="pic-error-parent" src="@/assets/error_images/401.png" />
|
||||
<img alt="401" class="pic-error-child left" src="@/assets/error_images/cloud.png" />
|
||||
<img alt="401" class="pic-error-child" src="@/assets/error_images/cloud.png" />
|
||||
<img alt="401" class="pic-error-child" src="@/assets/error_images/cloud.png" />
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="12" :md="12" :sm="24" :xl="12" :xs="24">
|
||||
<div class="bullshit">
|
||||
<div class="bullshit-oops">
|
||||
{{ oops }}
|
||||
</div>
|
||||
<div class="bullshit-headline">
|
||||
{{ headline }}
|
||||
</div>
|
||||
<div class="bullshit-info">
|
||||
{{ info }}
|
||||
</div>
|
||||
<a class="bullshit-return-home" href="#/index">{{ jumpTime }}s {{ btn }}</a>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Page401',
|
||||
data() {
|
||||
return {
|
||||
jumpTime: 5,
|
||||
oops: '抱歉!',
|
||||
headline: '您没有操作权限...',
|
||||
info: '当前帐号没有操作权限,请联系管理员。',
|
||||
btn: '返回',
|
||||
timer: 0,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.timeChange()
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer)
|
||||
},
|
||||
methods: {
|
||||
timeChange() {
|
||||
this.timer = setInterval(() => {
|
||||
if (this.jumpTime) {
|
||||
this.jumpTime--
|
||||
} else {
|
||||
this.$router.push({ path: '/' })
|
||||
this.$store.dispatch('tabsBar/delOthersRoutes', {
|
||||
path: '/',
|
||||
})
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.error-container {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
.error-content {
|
||||
.pic-error {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 120%;
|
||||
overflow: hidden;
|
||||
|
||||
&-parent {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-child {
|
||||
position: absolute;
|
||||
|
||||
&.left {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
width: 80px;
|
||||
opacity: 0;
|
||||
animation-name: cloudLeft;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-delay: 1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&.mid {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
width: 46px;
|
||||
opacity: 0;
|
||||
animation-name: cloudMid;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-delay: 1.2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&.right {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
width: 62px;
|
||||
opacity: 0;
|
||||
animation-name: cloudRight;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-delay: 1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes cloudLeft {
|
||||
0% {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 33px;
|
||||
left: 188px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
top: 81px;
|
||||
left: 92px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 97px;
|
||||
left: 60px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cloudMid {
|
||||
0% {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 40px;
|
||||
left: 360px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
70% {
|
||||
top: 130px;
|
||||
left: 180px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 160px;
|
||||
left: 120px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cloudRight {
|
||||
0% {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 120px;
|
||||
left: 460px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
top: 180px;
|
||||
left: 340px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 200px;
|
||||
left: 300px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bullshit {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 300px;
|
||||
padding: 30px 0;
|
||||
overflow: hidden;
|
||||
|
||||
&-oops {
|
||||
margin-bottom: 20px;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
line-height: 40px;
|
||||
color: $base-color-blue;
|
||||
opacity: 0;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&-headline {
|
||||
margin-bottom: 10px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
color: #222;
|
||||
opacity: 0;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&-info {
|
||||
margin-bottom: 30px;
|
||||
font-size: 13px;
|
||||
line-height: 21px;
|
||||
color: $base-color-gray;
|
||||
opacity: 0;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&-return-home {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 110px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
line-height: 36px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background: $base-color-blue;
|
||||
border-radius: 100px;
|
||||
opacity: 0;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.3s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(60px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
284
src/views/404.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div class="error-container">
|
||||
<div class="error-content">
|
||||
<el-row :gutter="20">
|
||||
<el-col :lg="12" :md="12" :sm="24" :xl="12" :xs="24">
|
||||
<div class="pic-error">
|
||||
<img alt="401" class="pic-error-parent" src="@/assets/error_images/404.png" />
|
||||
<img alt="401" class="pic-error-child left" src="@/assets/error_images/cloud.png" />
|
||||
<img alt="401" class="pic-error-child" src="@/assets/error_images/cloud.png" />
|
||||
<img alt="401" class="pic-error-child" src="@/assets/error_images/cloud.png" />
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="12" :md="12" :sm="24" :xl="12" :xs="24">
|
||||
<div class="bullshit">
|
||||
<div class="bullshit-oops">
|
||||
{{ oops }}
|
||||
</div>
|
||||
<div class="bullshit-headline">
|
||||
{{ headline }}
|
||||
</div>
|
||||
<div class="bullshit-info">
|
||||
{{ info }}
|
||||
</div>
|
||||
<a class="bullshit-return-home" href="#/index">{{ jumpTime }}s {{ btn }}</a>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Page404',
|
||||
data() {
|
||||
return {
|
||||
jumpTime: 5,
|
||||
oops: '抱歉!',
|
||||
headline: '当前页面不存在...',
|
||||
info: '请检查您输入的网址是否正确,或点击下面的按钮返回首页。',
|
||||
btn: '返回首页',
|
||||
timer: 0,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.timeChange()
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer)
|
||||
},
|
||||
methods: {
|
||||
timeChange() {
|
||||
this.timer = setInterval(() => {
|
||||
if (this.jumpTime) {
|
||||
this.jumpTime--
|
||||
} else {
|
||||
this.$router.push({ path: '/' })
|
||||
this.$store.dispatch('tabsBar/delOthersRoutes', {
|
||||
path: '/',
|
||||
})
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.error-container {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
.error-content {
|
||||
.pic-error {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 120%;
|
||||
overflow: hidden;
|
||||
|
||||
&-parent {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-child {
|
||||
position: absolute;
|
||||
|
||||
&.left {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
width: 80px;
|
||||
opacity: 0;
|
||||
animation-name: cloudLeft;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-delay: 1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&.mid {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
width: 46px;
|
||||
opacity: 0;
|
||||
animation-name: cloudMid;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-delay: 1.2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&.right {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
width: 62px;
|
||||
opacity: 0;
|
||||
animation-name: cloudRight;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-delay: 1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes cloudLeft {
|
||||
0% {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 33px;
|
||||
left: 188px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
top: 81px;
|
||||
left: 92px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 97px;
|
||||
left: 60px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cloudMid {
|
||||
0% {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 40px;
|
||||
left: 360px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
70% {
|
||||
top: 130px;
|
||||
left: 180px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 160px;
|
||||
left: 120px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cloudRight {
|
||||
0% {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 120px;
|
||||
left: 460px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
top: 180px;
|
||||
left: 340px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 200px;
|
||||
left: 300px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bullshit {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 300px;
|
||||
padding: 30px 0;
|
||||
overflow: hidden;
|
||||
|
||||
&-oops {
|
||||
margin-bottom: 20px;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
line-height: 40px;
|
||||
color: $base-color-blue;
|
||||
opacity: 0;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&-headline {
|
||||
margin-bottom: 10px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
color: #222;
|
||||
opacity: 0;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&-info {
|
||||
margin-bottom: 30px;
|
||||
font-size: 13px;
|
||||
line-height: 21px;
|
||||
color: $base-color-gray;
|
||||
opacity: 0;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&-return-home {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 110px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
line-height: 36px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background: $base-color-blue;
|
||||
border-radius: 100px;
|
||||
opacity: 0;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.3s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(60px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||