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