18 changed files with 1106 additions and 31 deletions
-
3src/api/system/menu.js
-
BINsrc/assets/401_images/401.gif
-
BINsrc/assets/404_images/404.png
-
BINsrc/assets/404_images/404_cloud.png
-
60src/assets/styles/mixin.scss
-
33src/assets/styles/variables.scss
-
13src/components/Permission/index.js
-
21src/components/Permission/permission.js
-
149src/components/RightPanel/index.vue
-
0src/components/ThemePicker/index.vue
-
44src/layout/components/Sidebar/TopMenu.vue
-
44src/layout/mixin/ResizeHandler.js
-
4src/main.js
-
56src/router/routers.js
-
384src/utils/index.js
-
89src/views/features/401.vue
-
225src/views/features/404.vue
-
12src/views/features/redirect.vue
After Width: 313 | Height: 428 | Size: 160 KiB |
After Width: 1014 | Height: 556 | Size: 96 KiB |
After Width: 152 | Height: 138 | Size: 4.7 KiB |
@ -0,0 +1,60 @@ |
|||||
|
@mixin clearfix { |
||||
|
&:after { |
||||
|
content: ''; |
||||
|
display: table; |
||||
|
clear: both; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@mixin scrollBar { |
||||
|
&::-webkit-scrollbar-track-piece { |
||||
|
background: #d3dce6; |
||||
|
} |
||||
|
|
||||
|
&::-webkit-scrollbar { |
||||
|
width: 6px; |
||||
|
} |
||||
|
|
||||
|
&::-webkit-scrollbar-thumb { |
||||
|
background: #99a9bf; |
||||
|
border-radius: 20px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@mixin relative { |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
@mixin pct($pct) { |
||||
|
width: #{$pct}; |
||||
|
position: relative; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
|
||||
|
@mixin triangle($width, $height, $color, $direction) { |
||||
|
$width: $width/2; |
||||
|
$color-border-style: $height solid $color; |
||||
|
$transparent-border-style: $width solid transparent; |
||||
|
height: 0; |
||||
|
width: 0; |
||||
|
|
||||
|
@if $direction==up { |
||||
|
border-bottom: $color-border-style; |
||||
|
border-left: $transparent-border-style; |
||||
|
border-right: $transparent-border-style; |
||||
|
} @else if $direction==right { |
||||
|
border-left: $color-border-style; |
||||
|
border-top: $transparent-border-style; |
||||
|
border-bottom: $transparent-border-style; |
||||
|
} @else if $direction==down { |
||||
|
border-top: $color-border-style; |
||||
|
border-left: $transparent-border-style; |
||||
|
border-right: $transparent-border-style; |
||||
|
} @else if $direction==left { |
||||
|
border-right: $color-border-style; |
||||
|
border-top: $transparent-border-style; |
||||
|
border-bottom: $transparent-border-style; |
||||
|
} |
||||
|
} |
@ -0,0 +1,33 @@ |
|||||
|
// base color |
||||
|
$blue: #324157; |
||||
|
$light-blue: #3a71a8; |
||||
|
$red: #c03639; |
||||
|
$pink: #e65d6e; |
||||
|
$green: #30b08f; |
||||
|
$tiffany: #4ab7bd; |
||||
|
$yellow: #fec171; |
||||
|
$panGreen: #30b08f; |
||||
|
|
||||
|
// sidebar |
||||
|
$menuText: #bfcbd9; |
||||
|
$menuActiveText: #409eff; |
||||
|
$subMenuActiveText: #f4f4f5; |
||||
|
|
||||
|
$menuBg: #304156; |
||||
|
$menuHover: #263445; |
||||
|
|
||||
|
$subMenuBg: #1f2d3d; |
||||
|
$subMenuHover: #001528; |
||||
|
|
||||
|
$sideBarWidth: 205px; |
||||
|
|
||||
|
:export { |
||||
|
menuText: $menuText; |
||||
|
menuActiveText: $menuActiveText; |
||||
|
subMenuActiveText: $subMenuActiveText; |
||||
|
menuBg: $menuBg; |
||||
|
menuHover: $menuHover; |
||||
|
subMenuBg: $subMenuBg; |
||||
|
subMenuHover: $subMenuHover; |
||||
|
sideBarWidth: $sideBarWidth; |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
import permission from './permission' |
||||
|
|
||||
|
const install = function(Vue) { |
||||
|
Vue.directive('permission', permission) |
||||
|
} |
||||
|
|
||||
|
if (window.Vue) { |
||||
|
window['permission'] = permission |
||||
|
Vue.use(install); // eslint-disable-line
|
||||
|
} |
||||
|
|
||||
|
permission.install = install |
||||
|
export default permission |
@ -0,0 +1,21 @@ |
|||||
|
import store from '@/store' |
||||
|
|
||||
|
export default { |
||||
|
inserted(el, binding) { |
||||
|
const { value } = binding |
||||
|
const roles = store.getters && store.getters.roles |
||||
|
if (value && value instanceof Array) { |
||||
|
if (value.length > 0) { |
||||
|
const permissionRoles = value |
||||
|
const hasPermission = roles.some(role => { |
||||
|
return permissionRoles.includes(role) |
||||
|
}) |
||||
|
if (!hasPermission) { |
||||
|
el.parentNode && el.parentNode.removeChild(el) |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
throw new Error(`使用方式: v-permission="['admin','editor']"`) |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,149 @@ |
|||||
|
<template> |
||||
|
<div ref="rightPanel" :class="{show:show}" class="rightPanel-container"> |
||||
|
<div class="rightPanel-background" /> |
||||
|
<div class="rightPanel"> |
||||
|
<div class="rightPanel-items"> |
||||
|
<slot /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { addClass, removeClass } from '@/utils' |
||||
|
|
||||
|
export default { |
||||
|
name: 'RightPanel', |
||||
|
props: { |
||||
|
clickNotClose: { |
||||
|
default: false, |
||||
|
type: Boolean |
||||
|
}, |
||||
|
buttonTop: { |
||||
|
default: 250, |
||||
|
type: Number |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
show: { |
||||
|
get() { |
||||
|
return this.$store.state.settings.showSettings |
||||
|
}, |
||||
|
set(val) { |
||||
|
this.$store.dispatch('settings/changeSetting', { |
||||
|
key: 'showSettings', |
||||
|
value: val |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
theme() { |
||||
|
return this.$store.state.settings.theme |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
show(value) { |
||||
|
if (value && !this.clickNotClose) { |
||||
|
this.addEventClick() |
||||
|
} |
||||
|
if (value) { |
||||
|
addClass(document.body, 'showRightPanel') |
||||
|
} else { |
||||
|
removeClass(document.body, 'showRightPanel') |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.insertToBody() |
||||
|
this.addEventClick() |
||||
|
}, |
||||
|
beforeDestroy() { |
||||
|
const elx = this.$refs.rightPanel |
||||
|
elx.remove() |
||||
|
}, |
||||
|
methods: { |
||||
|
addEventClick() { |
||||
|
window.addEventListener('click', this.closeSidebar) |
||||
|
}, |
||||
|
closeSidebar(evt) { |
||||
|
const parent = evt.target.closest('.rightPanel') |
||||
|
if (!parent) { |
||||
|
this.show = false |
||||
|
window.removeEventListener('click', this.closeSidebar) |
||||
|
} |
||||
|
}, |
||||
|
insertToBody() { |
||||
|
const elx = this.$refs.rightPanel |
||||
|
const body = document.querySelector('body') |
||||
|
body.insertBefore(elx, body.firstChild) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
.showRightPanel { |
||||
|
overflow: hidden; |
||||
|
position: relative; |
||||
|
width: calc(100% - 15px); |
||||
|
} |
||||
|
</style> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.rightPanel-background { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
opacity: 0; |
||||
|
transition: opacity 0.3s cubic-bezier(0.7, 0.3, 0.1, 1); |
||||
|
background: rgba(0, 0, 0, 0.2); |
||||
|
z-index: -1; |
||||
|
} |
||||
|
|
||||
|
.rightPanel { |
||||
|
width: 100%; |
||||
|
max-width: 260px; |
||||
|
height: 100vh; |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.05); |
||||
|
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1); |
||||
|
transform: translate(100%); |
||||
|
background: #fff; |
||||
|
z-index: 40000; |
||||
|
} |
||||
|
|
||||
|
.show { |
||||
|
transition: all 0.3s cubic-bezier(0.7, 0.3, 0.1, 1); |
||||
|
|
||||
|
.rightPanel-background { |
||||
|
z-index: 20000; |
||||
|
opacity: 1; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.rightPanel { |
||||
|
transform: translate(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.handle-button { |
||||
|
width: 48px; |
||||
|
height: 48px; |
||||
|
position: absolute; |
||||
|
left: -48px; |
||||
|
text-align: center; |
||||
|
font-size: 24px; |
||||
|
border-radius: 6px 0 0 6px !important; |
||||
|
z-index: 0; |
||||
|
pointer-events: auto; |
||||
|
cursor: pointer; |
||||
|
color: #fff; |
||||
|
line-height: 48px; |
||||
|
i { |
||||
|
font-size: 24px; |
||||
|
line-height: 48px; |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,44 @@ |
|||||
|
<!-- |
||||
|
* @Author: Liu_li |
||||
|
* @Descripttion: |
||||
|
* @Date: 2021-09-26 11:03:50 |
||||
|
--> |
||||
|
<template> |
||||
|
<div> |
||||
|
<template> |
||||
|
<app-link> |
||||
|
<el-menu-item> |
||||
|
<item :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" :title="onlyOneChild.meta.title" /> |
||||
|
</el-menu-item> |
||||
|
</app-link> |
||||
|
</template> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import path from 'path' |
||||
|
import {isExternal} from '@/utils/validate' |
||||
|
|
||||
|
export default { |
||||
|
name:'SidebarItem', |
||||
|
props:{ |
||||
|
item:{ |
||||
|
type:Object, |
||||
|
required:true |
||||
|
}, |
||||
|
isNest:{ |
||||
|
type:Boolean, |
||||
|
default:false |
||||
|
}, |
||||
|
basePath:{ |
||||
|
type:String, |
||||
|
default:'' |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
this.onlyOneChild=null |
||||
|
return { |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
</script> |
@ -0,0 +1,44 @@ |
|||||
|
import store from '@/store' |
||||
|
|
||||
|
const { body} =document |
||||
|
const WIDTH=992 |
||||
|
|
||||
|
export default { |
||||
|
watch: { |
||||
|
$route(route) { |
||||
|
if (this.device === 'mobile' && this.sidebar.opened) { |
||||
|
store.dispatch('app/closeSideBar', { withoutAnimation: false }) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
beforeMount() { |
||||
|
window.addEventListener('resize', this.$_resizeHandler) |
||||
|
}, |
||||
|
beforeDestroy() { |
||||
|
window.removeEventListener('resize', this.$_resizeHandler) |
||||
|
}, |
||||
|
mounted() { |
||||
|
const isMobile = this.$_isMobile() |
||||
|
if (isMobile) { |
||||
|
store.dispatch('app/toggleDevice', 'mobile') |
||||
|
store.dispatch('app/closeSideBar', { withoutAnimation: true }) |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
|
||||
|
$_isMobile() { |
||||
|
const rect = body.getBoundingClientRect() |
||||
|
return rect.width - 1 < WIDTH |
||||
|
}, |
||||
|
$_resizeHandler() { |
||||
|
if (!document.hidden) { |
||||
|
const isMobile = this.$_isMobile() |
||||
|
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') |
||||
|
|
||||
|
if (isMobile) { |
||||
|
store.dispatch('app/closeSideBar', { withoutAnimation: true }) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,384 @@ |
|||||
|
/** |
||||
|
* |
||||
|
* @param {(Object|string|number)} time |
||||
|
* @param {string} cFormat |
||||
|
* @returns {string} |
||||
|
*/ |
||||
|
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 === 'undefined' || time === null || time === 'null') { |
||||
|
return '' |
||||
|
} else 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] |
||||
|
// Note: getDay() returns 0 on Sunday
|
||||
|
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } |
||||
|
if (result.length > 0 && value < 10) { |
||||
|
value = '0' + value |
||||
|
} |
||||
|
return value || 0 |
||||
|
}) |
||||
|
return time_str |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param {number} time |
||||
|
* @param {string} option |
||||
|
* @returns {string} |
||||
|
*/ |
||||
|
export function formatTime(time, option) { |
||||
|
if (('' + time).length === 10) { |
||||
|
time = parseInt(time) * 1000 |
||||
|
} else { |
||||
|
time = +time |
||||
|
} |
||||
|
const d = new Date(time) |
||||
|
const now = Date.now() |
||||
|
|
||||
|
const diff = (now - d) / 1000 |
||||
|
|
||||
|
if (diff < 30) { |
||||
|
return '刚刚' |
||||
|
} else if (diff < 3600) { |
||||
|
// less 1 hour
|
||||
|
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 ( |
||||
|
d.getMonth() + |
||||
|
1 + |
||||
|
'月' + |
||||
|
d.getDate() + |
||||
|
'日' + |
||||
|
d.getHours() + |
||||
|
'时' + |
||||
|
d.getMinutes() + |
||||
|
'分' |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param {string} url |
||||
|
* @returns {Object} |
||||
|
*/ |
||||
|
export function getQueryObject(url) { |
||||
|
url = url == null ? window.location.href : url |
||||
|
const search = url.substring(url.lastIndexOf('?') + 1) |
||||
|
const obj = {} |
||||
|
const reg = /([^?&=]+)=([^?&=]*)/g |
||||
|
search.replace(reg, (rs, $1, $2) => { |
||||
|
const name = decodeURIComponent($1) |
||||
|
let val = decodeURIComponent($2) |
||||
|
val = String(val) |
||||
|
obj[name] = val |
||||
|
return rs |
||||
|
}) |
||||
|
return obj |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param {string} input value |
||||
|
* @returns {number} output value |
||||
|
*/ |
||||
|
export function byteLength(str) { |
||||
|
// returns the byte length of an utf8 string
|
||||
|
let s = str.length |
||||
|
for (var i = str.length - 1; i >= 0; i--) { |
||||
|
const code = str.charCodeAt(i) |
||||
|
if (code > 0x7f && code <= 0x7ff) s++ |
||||
|
else if (code > 0x7ff && code <= 0xffff) s += 2 |
||||
|
if (code >= 0xDC00 && code <= 0xDFFF) i-- |
||||
|
} |
||||
|
return s |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param {Array} actual |
||||
|
* @returns {Array} |
||||
|
*/ |
||||
|
export function cleanArray(actual) { |
||||
|
const newArray = [] |
||||
|
for (let i = 0; i < actual.length; i++) { |
||||
|
if (actual[i]) { |
||||
|
newArray.push(actual[i]) |
||||
|
} |
||||
|
} |
||||
|
return newArray |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param {Object} json |
||||
|
* @returns {Array} |
||||
|
*/ |
||||
|
export function param(json) { |
||||
|
if (!json) return '' |
||||
|
return cleanArray( |
||||
|
Object.keys(json).map(key => { |
||||
|
if (json[key] === undefined) return '' |
||||
|
return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) |
||||
|
}) |
||||
|
).join('&') |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param {string} url |
||||
|
* @returns {Object} |
||||
|
*/ |
||||
|
export function param2Obj(url) { |
||||
|
const search = url.split('?')[1] |
||||
|
if (!search) { |
||||
|
return {} |
||||
|
} |
||||
|
return JSON.parse( |
||||
|
'{"' + |
||||
|
decodeURIComponent(search) |
||||
|
.replace(/"/g, '\\"') |
||||
|
.replace(/&/g, '","') |
||||
|
.replace(/=/g, '":"') |
||||
|
.replace(/\+/g, ' ') + |
||||
|
'"}' |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param {string} val |
||||
|
* @returns {string} |
||||
|
*/ |
||||
|
export function html2Text(val) { |
||||
|
const div = document.createElement('div') |
||||
|
div.innerHTML = val |
||||
|
return div.textContent || div.innerText |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Merges two objects, giving the last one precedence |
||||
|
* @param {Object} target |
||||
|
* @param {(Object|Array)} source |
||||
|
* @returns {Object} |
||||
|
*/ |
||||
|
export function objectMerge(target, source) { |
||||
|
if (typeof target !== 'object') { |
||||
|
target = {} |
||||
|
} |
||||
|
if (Array.isArray(source)) { |
||||
|
return source.slice() |
||||
|
} |
||||
|
Object.keys(source).forEach(property => { |
||||
|
const sourceProperty = source[property] |
||||
|
if (typeof sourceProperty === 'object') { |
||||
|
target[property] = objectMerge(target[property], sourceProperty) |
||||
|
} else { |
||||
|
target[property] = sourceProperty |
||||
|
} |
||||
|
}) |
||||
|
return target |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param {HTMLElement} element |
||||
|
* @param {string} className |
||||
|
*/ |
||||
|
export function toggleClass(element, className) { |
||||
|
if (!element || !className) { |
||||
|
return |
||||
|
} |
||||
|
let classString = element.className |
||||
|
const nameIndex = classString.indexOf(className) |
||||
|
if (nameIndex === -1) { |
||||
|
classString += '' + className |
||||
|
} else { |
||||
|
classString = |
||||
|
classString.substr(0, nameIndex) + |
||||
|
classString.substr(nameIndex + className.length) |
||||
|
} |
||||
|
element.className = classString |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param {string} type |
||||
|
* @returns {Date} |
||||
|
*/ |
||||
|
export function getTime(type) { |
||||
|
if (type === 'start') { |
||||
|
return new Date().getTime() - 3600 * 1000 * 24 * 90 |
||||
|
} else { |
||||
|
return new Date(new Date().toDateString()) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param {Function} func |
||||
|
* @param {number} wait |
||||
|
* @param {boolean} immediate |
||||
|
* @return {*} |
||||
|
*/ |
||||
|
export function debounce(func, wait, immediate) { |
||||
|
let timeout, args, context, timestamp, result |
||||
|
|
||||
|
const later = function() { |
||||
|
// 据上一次触发时间间隔
|
||||
|
const last = +new Date() - timestamp |
||||
|
|
||||
|
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
|
||||
|
if (last < wait && last > 0) { |
||||
|
timeout = setTimeout(later, wait - last) |
||||
|
} else { |
||||
|
timeout = null |
||||
|
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
|
||||
|
if (!immediate) { |
||||
|
result = func.apply(context, args) |
||||
|
if (!timeout) context = args = null |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return function(...args) { |
||||
|
context = this |
||||
|
timestamp = +new Date() |
||||
|
const callNow = immediate && !timeout |
||||
|
// 如果延时不存在,重新设定延时
|
||||
|
if (!timeout) timeout = setTimeout(later, wait) |
||||
|
if (callNow) { |
||||
|
result = func.apply(context, args) |
||||
|
context = args = null |
||||
|
} |
||||
|
|
||||
|
return result |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* This is just a simple version of deep copy |
||||
|
* Has a lot of edge cases bug |
||||
|
* If you want to use a perfect deep copy, use lodash's _.cloneDeep |
||||
|
* @param {Object} source |
||||
|
* @returns {Object} |
||||
|
*/ |
||||
|
export function deepClone(source) { |
||||
|
if (!source && typeof source !== 'object') { |
||||
|
throw new Error('error arguments', 'deepClone') |
||||
|
} |
||||
|
const targetObj = source.constructor === Array ? [] : {} |
||||
|
Object.keys(source).forEach(keys => { |
||||
|
if (source[keys] && typeof source[keys] === 'object') { |
||||
|
targetObj[keys] = deepClone(source[keys]) |
||||
|
} else { |
||||
|
targetObj[keys] = source[keys] |
||||
|
} |
||||
|
}) |
||||
|
return targetObj |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param {Array} arr |
||||
|
* @returns {Array} |
||||
|
*/ |
||||
|
export function uniqueArr(arr) { |
||||
|
return Array.from(new Set(arr)) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @returns {string} |
||||
|
*/ |
||||
|
export function createUniqueString() { |
||||
|
const timestamp = +new Date() + '' |
||||
|
const randomNum = parseInt((1 + Math.random()) * 65536) + '' |
||||
|
return (+(randomNum + timestamp)).toString(32) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if an element has a class |
||||
|
* @param {HTMLElement} elm |
||||
|
* @param {string} cls |
||||
|
* @returns {boolean} |
||||
|
*/ |
||||
|
export function hasClass(ele, cls) { |
||||
|
return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Add class to element |
||||
|
* @param {HTMLElement} elm |
||||
|
* @param {string} cls |
||||
|
*/ |
||||
|
export function addClass(ele, cls) { |
||||
|
if (!hasClass(ele, cls)) ele.className += ' ' + cls |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Remove class from element |
||||
|
* @param {HTMLElement} elm |
||||
|
* @param {string} cls |
||||
|
*/ |
||||
|
export function removeClass(ele, cls) { |
||||
|
if (hasClass(ele, cls)) { |
||||
|
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') |
||||
|
ele.className = ele.className.replace(reg, ' ') |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 替换邮箱字符
|
||||
|
export function regEmail(email) { |
||||
|
if (String(email).indexOf('@') > 0) { |
||||
|
const str = email.split('@') |
||||
|
let _s = '' |
||||
|
if (str[0].length > 3) { |
||||
|
for (var i = 0; i < str[0].length - 3; i++) { |
||||
|
_s += '*' |
||||
|
} |
||||
|
} |
||||
|
var new_email = str[0].substr(0, 3) + _s + '@' + str[1] |
||||
|
} |
||||
|
return new_email |
||||
|
} |
||||
|
|
||||
|
// 替换手机字符
|
||||
|
export function regMobile(mobile) { |
||||
|
if (mobile.length > 7) { |
||||
|
var new_mobile = mobile.substr(0, 3) + '****' + mobile.substr(7) |
||||
|
} |
||||
|
return new_mobile |
||||
|
} |
||||
|
|
||||
|
// 下载文件
|
||||
|
export function downloadFile(obj, name, suffix) { |
||||
|
const url = window.URL.createObjectURL(new Blob([obj])) |
||||
|
const link = document.createElement('a') |
||||
|
link.style.display = 'none' |
||||
|
link.href = url |
||||
|
const fileName = parseTime(new Date()) + '-' + name + '.' + suffix |
||||
|
link.setAttribute('download', fileName) |
||||
|
document.body.appendChild(link) |
||||
|
link.click() |
||||
|
document.body.removeChild(link) |
||||
|
} |
@ -0,0 +1,89 @@ |
|||||
|
<template> |
||||
|
<div class="errPage-container"> |
||||
|
<el-button icon="arrow-left" class="pan-back-btn" @click="back"> |
||||
|
返回 |
||||
|
</el-button> |
||||
|
<el-row> |
||||
|
<el-col :span="12"> |
||||
|
<h1 class="text-jumbo text-ginormous"> |
||||
|
Oops! |
||||
|
</h1> |
||||
|
<h2>你没有权限去该页面</h2> |
||||
|
<h6>如有不满请联系你领导</h6> |
||||
|
<ul class="list-unstyled"> |
||||
|
<li>或者你可以去:</li> |
||||
|
<li class="link-type"> |
||||
|
<router-link to="/dashboard"> |
||||
|
回首页 |
||||
|
</router-link> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</el-col> |
||||
|
<el-col :span="12"> |
||||
|
<img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream."> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import errGif from '@/assets/401_images/401.gif' |
||||
|
|
||||
|
export default { |
||||
|
name: 'Page401', |
||||
|
data() { |
||||
|
return { |
||||
|
errGif: errGif + '?' + +new Date() |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
back() { |
||||
|
if (this.$route.query.noGoBack) { |
||||
|
this.$router.push({ path: '/dashboard' }) |
||||
|
} else { |
||||
|
this.$router.go(-1) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.errPage-container { |
||||
|
width: 800px; |
||||
|
max-width: 100%; |
||||
|
margin: 100px auto; |
||||
|
.pan-back-btn { |
||||
|
background: #008489; |
||||
|
color: #fff; |
||||
|
border: none !important; |
||||
|
} |
||||
|
.pan-gif { |
||||
|
margin: 0 auto; |
||||
|
display: block; |
||||
|
} |
||||
|
.pan-img { |
||||
|
display: block; |
||||
|
margin: 0 auto; |
||||
|
width: 100%; |
||||
|
} |
||||
|
.text-jumbo { |
||||
|
font-size: 60px; |
||||
|
font-weight: 700; |
||||
|
color: #484848; |
||||
|
} |
||||
|
.list-unstyled { |
||||
|
font-size: 14px; |
||||
|
li { |
||||
|
padding-bottom: 5px; |
||||
|
} |
||||
|
a { |
||||
|
color: #008489; |
||||
|
text-decoration: none; |
||||
|
&:hover { |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,225 @@ |
|||||
|
<template> |
||||
|
<div class="wscn-http404-container"> |
||||
|
<div class="wscn-http404"> |
||||
|
<div class="pic-404"> |
||||
|
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404"> |
||||
|
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404"> |
||||
|
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404"> |
||||
|
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404"> |
||||
|
</div> |
||||
|
<div class="bullshit"> |
||||
|
<div class="bullshit__oops">OOPS!</div> |
||||
|
<div class="bullshit__headline">{{ message }}</div> |
||||
|
<div class="bullshit__info">请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告</div> |
||||
|
<a href="/" class="bullshit__return-home">返回首页</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
|
||||
|
export default { |
||||
|
name: 'Page404', |
||||
|
computed: { |
||||
|
message() { |
||||
|
return '网管说这个页面你不能进......' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style rel="stylesheet/scss" lang="scss" scoped> |
||||
|
.wscn-http404-container { |
||||
|
transform: translate(-50%, -50%); |
||||
|
position: absolute; |
||||
|
top: 40%; |
||||
|
left: 50%; |
||||
|
} |
||||
|
.wscn-http404 { |
||||
|
position: relative; |
||||
|
width: 1200px; |
||||
|
padding: 0 50px; |
||||
|
overflow: hidden; |
||||
|
.pic-404 { |
||||
|
position: relative; |
||||
|
float: left; |
||||
|
width: 600px; |
||||
|
overflow: hidden; |
||||
|
&__parent { |
||||
|
width: 100%; |
||||
|
} |
||||
|
&__child { |
||||
|
position: absolute; |
||||
|
&.left { |
||||
|
width: 80px; |
||||
|
top: 17px; |
||||
|
left: 220px; |
||||
|
opacity: 0; |
||||
|
animation-name: cloudLeft; |
||||
|
animation-duration: 2s; |
||||
|
animation-timing-function: linear; |
||||
|
animation-fill-mode: forwards; |
||||
|
animation-delay: 1s; |
||||
|
} |
||||
|
&.mid { |
||||
|
width: 46px; |
||||
|
top: 10px; |
||||
|
left: 420px; |
||||
|
opacity: 0; |
||||
|
animation-name: cloudMid; |
||||
|
animation-duration: 2s; |
||||
|
animation-timing-function: linear; |
||||
|
animation-fill-mode: forwards; |
||||
|
animation-delay: 1.2s; |
||||
|
} |
||||
|
&.right { |
||||
|
width: 62px; |
||||
|
top: 100px; |
||||
|
left: 500px; |
||||
|
opacity: 0; |
||||
|
animation-name: cloudRight; |
||||
|
animation-duration: 2s; |
||||
|
animation-timing-function: linear; |
||||
|
animation-fill-mode: forwards; |
||||
|
animation-delay: 1s; |
||||
|
} |
||||
|
@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 { |
||||
|
font-size: 32px; |
||||
|
font-weight: bold; |
||||
|
line-height: 40px; |
||||
|
color: #1482f0; |
||||
|
opacity: 0; |
||||
|
margin-bottom: 20px; |
||||
|
animation-name: slideUp; |
||||
|
animation-duration: 0.5s; |
||||
|
animation-fill-mode: forwards; |
||||
|
} |
||||
|
&__headline { |
||||
|
font-size: 20px; |
||||
|
line-height: 24px; |
||||
|
color: #222; |
||||
|
font-weight: bold; |
||||
|
opacity: 0; |
||||
|
margin-bottom: 10px; |
||||
|
animation-name: slideUp; |
||||
|
animation-duration: 0.5s; |
||||
|
animation-delay: 0.1s; |
||||
|
animation-fill-mode: forwards; |
||||
|
} |
||||
|
&__info { |
||||
|
font-size: 13px; |
||||
|
line-height: 21px; |
||||
|
color: grey; |
||||
|
opacity: 0; |
||||
|
margin-bottom: 30px; |
||||
|
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; |
||||
|
background: #1482f0; |
||||
|
border-radius: 100px; |
||||
|
text-align: center; |
||||
|
color: #ffffff; |
||||
|
opacity: 0; |
||||
|
font-size: 14px; |
||||
|
line-height: 36px; |
||||
|
cursor: pointer; |
||||
|
animation-name: slideUp; |
||||
|
animation-duration: 0.5s; |
||||
|
animation-delay: 0.3s; |
||||
|
animation-fill-mode: forwards; |
||||
|
} |
||||
|
@keyframes slideUp { |
||||
|
0% { |
||||
|
transform: translateY(60px); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
100% { |
||||
|
transform: translateY(0); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,12 @@ |
|||||
|
<script> |
||||
|
export default { |
||||
|
created() { |
||||
|
const { params, query } = this.$route |
||||
|
const { path } = params |
||||
|
this.$router.replace({ path: '/' + path, query }) |
||||
|
}, |
||||
|
render: function(h) { |
||||
|
return h() |
||||
|
} |
||||
|
} |
||||
|
</script> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue