This commit is contained in:
296
Web/src/views/404/index.css
Normal file
296
Web/src/views/404/index.css
Normal file
@@ -0,0 +1,296 @@
|
||||
@-webkit-keyframes noise-anim {
|
||||
0% {
|
||||
clip: rect(32px, 9999px, 16px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(5px, 9999px, 24px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(77px, 9999px, 87px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(91px, 9999px, 95px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(74px, 9999px, 9px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(37px, 9999px, 32px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(56px, 9999px, 27px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(35px, 9999px, 33px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(89px, 9999px, 6px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(81px, 9999px, 77px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(64px, 9999px, 69px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(12px, 9999px, 11px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(59px, 9999px, 11px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(69px, 9999px, 59px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(74px, 9999px, 65px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(56px, 9999px, 79px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(80px, 9999px, 64px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(87px, 9999px, 29px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(16px, 9999px, 21px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(69px, 9999px, 43px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(75px, 9999px, 63px, 0);
|
||||
}
|
||||
}
|
||||
@keyframes noise-anim {
|
||||
0% {
|
||||
clip: rect(32px, 9999px, 16px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(5px, 9999px, 24px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(77px, 9999px, 87px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(91px, 9999px, 95px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(74px, 9999px, 9px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(37px, 9999px, 32px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(56px, 9999px, 27px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(35px, 9999px, 33px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(89px, 9999px, 6px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(81px, 9999px, 77px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(64px, 9999px, 69px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(12px, 9999px, 11px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(59px, 9999px, 11px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(69px, 9999px, 59px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(74px, 9999px, 65px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(56px, 9999px, 79px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(80px, 9999px, 64px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(87px, 9999px, 29px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(16px, 9999px, 21px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(69px, 9999px, 43px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(75px, 9999px, 63px, 0);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes noise-anim-2 {
|
||||
0% {
|
||||
clip: rect(12px, 9999px, 52px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(42px, 9999px, 39px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(64px, 9999px, 36px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(52px, 9999px, 15px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(79px, 9999px, 7px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(17px, 9999px, 41px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(15px, 9999px, 20px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(62px, 9999px, 87px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(94px, 9999px, 11px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(49px, 9999px, 10px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(82px, 9999px, 4px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(70px, 9999px, 100px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(62px, 9999px, 23px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(51px, 9999px, 56px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(41px, 9999px, 24px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(6px, 9999px, 85px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(96px, 9999px, 58px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(16px, 9999px, 24px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(40px, 9999px, 31px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(91px, 9999px, 34px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(87px, 9999px, 26px, 0);
|
||||
}
|
||||
}
|
||||
@keyframes noise-anim-2 {
|
||||
0% {
|
||||
clip: rect(12px, 9999px, 52px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(42px, 9999px, 39px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(64px, 9999px, 36px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(52px, 9999px, 15px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(79px, 9999px, 7px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(17px, 9999px, 41px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(15px, 9999px, 20px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(62px, 9999px, 87px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(94px, 9999px, 11px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(49px, 9999px, 10px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(82px, 9999px, 4px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(70px, 9999px, 100px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(62px, 9999px, 23px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(51px, 9999px, 56px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(41px, 9999px, 24px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(6px, 9999px, 85px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(96px, 9999px, 58px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(16px, 9999px, 24px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(40px, 9999px, 31px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(91px, 9999px, 34px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(87px, 9999px, 26px, 0);
|
||||
}
|
||||
}
|
||||
.error-result {
|
||||
padding: 100px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.error-result--text {
|
||||
font-size: 7rem;
|
||||
line-height: 1;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 12.5rem;
|
||||
color: #5a5c69;
|
||||
}
|
||||
.error-result--text::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 2px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 900px, 0, 0);
|
||||
content: attr(data-text);
|
||||
animation: noise-anim 2s infinite linear alternate-reverse;
|
||||
color: #5a5c69;
|
||||
background: #f8f9fc;
|
||||
text-shadow: -1px 0 #e74a3b;
|
||||
}
|
||||
.error-result--text::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -2px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 900px, 0, 0);
|
||||
content: attr(data-text);
|
||||
animation: noise-anim-2 3s infinite linear alternate-reverse;
|
||||
color: #5a5c69;
|
||||
background: #f8f9fc;
|
||||
text-shadow: 1px 0 #4e73df;
|
||||
}
|
||||
308
Web/src/views/404/index.less
Normal file
308
Web/src/views/404/index.less
Normal file
@@ -0,0 +1,308 @@
|
||||
@import (reference) '~ant-design-vue/dist/antd.less';
|
||||
@-webkit-keyframes noise-anim-after {
|
||||
0% {
|
||||
clip: rect(32px, 9999px, 16px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(5px, 9999px, 24px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(77px, 9999px, 87px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(91px, 9999px, 95px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(74px, 9999px, 9px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(37px, 9999px, 32px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(56px, 9999px, 27px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(35px, 9999px, 33px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(89px, 9999px, 6px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(81px, 9999px, 77px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(64px, 9999px, 69px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(12px, 9999px, 11px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(59px, 9999px, 11px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(69px, 9999px, 59px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(74px, 9999px, 65px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(56px, 9999px, 79px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(80px, 9999px, 64px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(87px, 9999px, 29px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(16px, 9999px, 21px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(69px, 9999px, 43px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(75px, 9999px, 63px, 0);
|
||||
}
|
||||
}
|
||||
@keyframes noise-anim-after {
|
||||
0% {
|
||||
clip: rect(32px, 9999px, 16px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(5px, 9999px, 24px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(77px, 9999px, 87px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(91px, 9999px, 95px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(74px, 9999px, 9px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(37px, 9999px, 32px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(56px, 9999px, 27px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(35px, 9999px, 33px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(89px, 9999px, 6px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(81px, 9999px, 77px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(64px, 9999px, 69px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(12px, 9999px, 11px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(59px, 9999px, 11px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(69px, 9999px, 59px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(74px, 9999px, 65px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(56px, 9999px, 79px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(80px, 9999px, 64px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(87px, 9999px, 29px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(16px, 9999px, 21px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(69px, 9999px, 43px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(75px, 9999px, 63px, 0);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes noise-anim-before {
|
||||
0% {
|
||||
clip: rect(12px, 9999px, 52px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(42px, 9999px, 39px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(64px, 9999px, 36px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(52px, 9999px, 15px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(79px, 9999px, 7px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(17px, 9999px, 41px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(15px, 9999px, 20px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(62px, 9999px, 87px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(94px, 9999px, 11px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(49px, 9999px, 10px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(82px, 9999px, 4px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(70px, 9999px, 100px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(62px, 9999px, 23px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(51px, 9999px, 56px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(41px, 9999px, 24px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(6px, 9999px, 85px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(96px, 9999px, 58px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(16px, 9999px, 24px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(40px, 9999px, 31px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(91px, 9999px, 34px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(87px, 9999px, 26px, 0);
|
||||
}
|
||||
}
|
||||
@keyframes noise-anim-before {
|
||||
0% {
|
||||
clip: rect(12px, 9999px, 52px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(42px, 9999px, 39px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(64px, 9999px, 36px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(52px, 9999px, 15px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(79px, 9999px, 7px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(17px, 9999px, 41px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(15px, 9999px, 20px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(62px, 9999px, 87px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(94px, 9999px, 11px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(49px, 9999px, 10px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(82px, 9999px, 4px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(70px, 9999px, 100px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(62px, 9999px, 23px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(51px, 9999px, 56px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(41px, 9999px, 24px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(6px, 9999px, 85px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(96px, 9999px, 58px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(16px, 9999px, 24px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(40px, 9999px, 31px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(91px, 9999px, 34px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(87px, 9999px, 26px, 0);
|
||||
}
|
||||
}
|
||||
.error-result {
|
||||
padding: 100px;
|
||||
|
||||
text-transform: uppercase;
|
||||
&--text {
|
||||
font-size: @font-size-base * 8;
|
||||
line-height: 1;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: block;
|
||||
|
||||
width: 12.5rem;
|
||||
|
||||
color: #5a5c69;
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 2px;
|
||||
|
||||
overflow: hidden;
|
||||
clip: rect(0, 900px, 0, 0);
|
||||
|
||||
content: attr(data-text);
|
||||
animation: noise-anim-after 2s infinite linear alternate-reverse;
|
||||
|
||||
color: #5a5c69;
|
||||
background: @layout-body-background;
|
||||
text-shadow: -1px 0 #e74a3b;
|
||||
}
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -2px;
|
||||
|
||||
overflow: hidden;
|
||||
clip: rect(0, 900px, 0, 0);
|
||||
|
||||
content: attr(data-text);
|
||||
animation: noise-anim-before 3s infinite linear alternate-reverse;
|
||||
|
||||
color: #5a5c69;
|
||||
background: @layout-body-background;
|
||||
text-shadow: 1px 0 #4e73df;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Web/src/views/404/index.vue
Normal file
14
Web/src/views/404/index.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<container>
|
||||
<div class="error-result">
|
||||
<div class="error-result--code">
|
||||
<span class="error-result--text" data-text="404">404</span>
|
||||
</div>
|
||||
<p>not found</p>
|
||||
</div>
|
||||
</container>
|
||||
</template>
|
||||
<style lang="less" scope>
|
||||
@import './index.less';
|
||||
|
||||
</style>
|
||||
296
Web/src/views/error/404/index.css
Normal file
296
Web/src/views/error/404/index.css
Normal file
@@ -0,0 +1,296 @@
|
||||
@-webkit-keyframes noise-anim {
|
||||
0% {
|
||||
clip: rect(32px, 9999px, 16px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(5px, 9999px, 24px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(77px, 9999px, 87px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(91px, 9999px, 95px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(74px, 9999px, 9px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(37px, 9999px, 32px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(56px, 9999px, 27px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(35px, 9999px, 33px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(89px, 9999px, 6px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(81px, 9999px, 77px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(64px, 9999px, 69px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(12px, 9999px, 11px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(59px, 9999px, 11px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(69px, 9999px, 59px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(74px, 9999px, 65px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(56px, 9999px, 79px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(80px, 9999px, 64px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(87px, 9999px, 29px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(16px, 9999px, 21px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(69px, 9999px, 43px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(75px, 9999px, 63px, 0);
|
||||
}
|
||||
}
|
||||
@keyframes noise-anim {
|
||||
0% {
|
||||
clip: rect(32px, 9999px, 16px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(5px, 9999px, 24px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(77px, 9999px, 87px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(91px, 9999px, 95px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(74px, 9999px, 9px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(37px, 9999px, 32px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(56px, 9999px, 27px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(35px, 9999px, 33px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(89px, 9999px, 6px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(81px, 9999px, 77px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(64px, 9999px, 69px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(12px, 9999px, 11px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(59px, 9999px, 11px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(69px, 9999px, 59px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(74px, 9999px, 65px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(56px, 9999px, 79px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(80px, 9999px, 64px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(87px, 9999px, 29px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(16px, 9999px, 21px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(69px, 9999px, 43px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(75px, 9999px, 63px, 0);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes noise-anim-2 {
|
||||
0% {
|
||||
clip: rect(12px, 9999px, 52px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(42px, 9999px, 39px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(64px, 9999px, 36px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(52px, 9999px, 15px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(79px, 9999px, 7px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(17px, 9999px, 41px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(15px, 9999px, 20px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(62px, 9999px, 87px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(94px, 9999px, 11px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(49px, 9999px, 10px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(82px, 9999px, 4px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(70px, 9999px, 100px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(62px, 9999px, 23px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(51px, 9999px, 56px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(41px, 9999px, 24px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(6px, 9999px, 85px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(96px, 9999px, 58px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(16px, 9999px, 24px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(40px, 9999px, 31px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(91px, 9999px, 34px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(87px, 9999px, 26px, 0);
|
||||
}
|
||||
}
|
||||
@keyframes noise-anim-2 {
|
||||
0% {
|
||||
clip: rect(12px, 9999px, 52px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(42px, 9999px, 39px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(64px, 9999px, 36px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(52px, 9999px, 15px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(79px, 9999px, 7px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(17px, 9999px, 41px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(15px, 9999px, 20px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(62px, 9999px, 87px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(94px, 9999px, 11px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(49px, 9999px, 10px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(82px, 9999px, 4px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(70px, 9999px, 100px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(62px, 9999px, 23px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(51px, 9999px, 56px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(41px, 9999px, 24px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(6px, 9999px, 85px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(96px, 9999px, 58px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(16px, 9999px, 24px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(40px, 9999px, 31px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(91px, 9999px, 34px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(87px, 9999px, 26px, 0);
|
||||
}
|
||||
}
|
||||
.error-result {
|
||||
padding: 100px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.error-result--text {
|
||||
font-size: 7rem;
|
||||
line-height: 1;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 12.5rem;
|
||||
color: #5a5c69;
|
||||
}
|
||||
.error-result--text::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 2px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 900px, 0, 0);
|
||||
content: attr(data-text);
|
||||
animation: noise-anim 2s infinite linear alternate-reverse;
|
||||
color: #5a5c69;
|
||||
background: #f8f9fc;
|
||||
text-shadow: -1px 0 #e74a3b;
|
||||
}
|
||||
.error-result--text::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -2px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 900px, 0, 0);
|
||||
content: attr(data-text);
|
||||
animation: noise-anim-2 3s infinite linear alternate-reverse;
|
||||
color: #5a5c69;
|
||||
background: #f8f9fc;
|
||||
text-shadow: 1px 0 #4e73df;
|
||||
}
|
||||
308
Web/src/views/error/404/index.less
Normal file
308
Web/src/views/error/404/index.less
Normal file
@@ -0,0 +1,308 @@
|
||||
@import (reference) '~ant-design-vue/dist/antd.less';
|
||||
@-webkit-keyframes noise-anim-after {
|
||||
0% {
|
||||
clip: rect(32px, 9999px, 16px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(5px, 9999px, 24px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(77px, 9999px, 87px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(91px, 9999px, 95px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(74px, 9999px, 9px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(37px, 9999px, 32px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(56px, 9999px, 27px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(35px, 9999px, 33px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(89px, 9999px, 6px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(81px, 9999px, 77px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(64px, 9999px, 69px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(12px, 9999px, 11px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(59px, 9999px, 11px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(69px, 9999px, 59px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(74px, 9999px, 65px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(56px, 9999px, 79px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(80px, 9999px, 64px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(87px, 9999px, 29px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(16px, 9999px, 21px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(69px, 9999px, 43px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(75px, 9999px, 63px, 0);
|
||||
}
|
||||
}
|
||||
@keyframes noise-anim-after {
|
||||
0% {
|
||||
clip: rect(32px, 9999px, 16px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(5px, 9999px, 24px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(77px, 9999px, 87px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(91px, 9999px, 95px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(74px, 9999px, 9px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(37px, 9999px, 32px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(56px, 9999px, 27px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(35px, 9999px, 33px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(89px, 9999px, 6px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(81px, 9999px, 77px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(64px, 9999px, 69px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(12px, 9999px, 11px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(59px, 9999px, 11px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(69px, 9999px, 59px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(74px, 9999px, 65px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(56px, 9999px, 79px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(80px, 9999px, 64px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(87px, 9999px, 29px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(16px, 9999px, 21px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(69px, 9999px, 43px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(75px, 9999px, 63px, 0);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes noise-anim-before {
|
||||
0% {
|
||||
clip: rect(12px, 9999px, 52px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(42px, 9999px, 39px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(64px, 9999px, 36px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(52px, 9999px, 15px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(79px, 9999px, 7px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(17px, 9999px, 41px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(15px, 9999px, 20px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(62px, 9999px, 87px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(94px, 9999px, 11px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(49px, 9999px, 10px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(82px, 9999px, 4px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(70px, 9999px, 100px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(62px, 9999px, 23px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(51px, 9999px, 56px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(41px, 9999px, 24px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(6px, 9999px, 85px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(96px, 9999px, 58px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(16px, 9999px, 24px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(40px, 9999px, 31px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(91px, 9999px, 34px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(87px, 9999px, 26px, 0);
|
||||
}
|
||||
}
|
||||
@keyframes noise-anim-before {
|
||||
0% {
|
||||
clip: rect(12px, 9999px, 52px, 0);
|
||||
}
|
||||
5% {
|
||||
clip: rect(42px, 9999px, 39px, 0);
|
||||
}
|
||||
10% {
|
||||
clip: rect(64px, 9999px, 36px, 0);
|
||||
}
|
||||
15% {
|
||||
clip: rect(52px, 9999px, 15px, 0);
|
||||
}
|
||||
20% {
|
||||
clip: rect(79px, 9999px, 7px, 0);
|
||||
}
|
||||
25% {
|
||||
clip: rect(17px, 9999px, 41px, 0);
|
||||
}
|
||||
30% {
|
||||
clip: rect(15px, 9999px, 20px, 0);
|
||||
}
|
||||
35% {
|
||||
clip: rect(62px, 9999px, 87px, 0);
|
||||
}
|
||||
40% {
|
||||
clip: rect(94px, 9999px, 11px, 0);
|
||||
}
|
||||
45% {
|
||||
clip: rect(49px, 9999px, 10px, 0);
|
||||
}
|
||||
50% {
|
||||
clip: rect(82px, 9999px, 4px, 0);
|
||||
}
|
||||
55% {
|
||||
clip: rect(70px, 9999px, 100px, 0);
|
||||
}
|
||||
60% {
|
||||
clip: rect(62px, 9999px, 23px, 0);
|
||||
}
|
||||
65% {
|
||||
clip: rect(51px, 9999px, 56px, 0);
|
||||
}
|
||||
70% {
|
||||
clip: rect(41px, 9999px, 24px, 0);
|
||||
}
|
||||
75% {
|
||||
clip: rect(6px, 9999px, 85px, 0);
|
||||
}
|
||||
80% {
|
||||
clip: rect(96px, 9999px, 58px, 0);
|
||||
}
|
||||
85% {
|
||||
clip: rect(16px, 9999px, 24px, 0);
|
||||
}
|
||||
90% {
|
||||
clip: rect(40px, 9999px, 31px, 0);
|
||||
}
|
||||
95% {
|
||||
clip: rect(91px, 9999px, 34px, 0);
|
||||
}
|
||||
100% {
|
||||
clip: rect(87px, 9999px, 26px, 0);
|
||||
}
|
||||
}
|
||||
.error-result {
|
||||
padding: 100px;
|
||||
|
||||
text-transform: uppercase;
|
||||
&--text {
|
||||
font-size: @font-size-base * 8;
|
||||
line-height: 1;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: block;
|
||||
|
||||
width: 12.5rem;
|
||||
|
||||
color: #5a5c69;
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 2px;
|
||||
|
||||
overflow: hidden;
|
||||
clip: rect(0, 900px, 0, 0);
|
||||
|
||||
content: attr(data-text);
|
||||
animation: noise-anim-after 2s infinite linear alternate-reverse;
|
||||
|
||||
color: #5a5c69;
|
||||
background: @layout-body-background;
|
||||
text-shadow: -1px 0 #e74a3b;
|
||||
}
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -2px;
|
||||
|
||||
overflow: hidden;
|
||||
clip: rect(0, 900px, 0, 0);
|
||||
|
||||
content: attr(data-text);
|
||||
animation: noise-anim-before 3s infinite linear alternate-reverse;
|
||||
|
||||
color: #5a5c69;
|
||||
background: @layout-body-background;
|
||||
text-shadow: 1px 0 #4e73df;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Web/src/views/error/404/index.vue
Normal file
14
Web/src/views/error/404/index.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<container>
|
||||
<div class="error-result">
|
||||
<div class="error-result--code">
|
||||
<span class="error-result--text" data-text="404">404</span>
|
||||
</div>
|
||||
<p>not found</p>
|
||||
</div>
|
||||
</container>
|
||||
</template>
|
||||
<style lang="less" scope>
|
||||
@import './index.less';
|
||||
|
||||
</style>
|
||||
48
Web/src/views/login/index.vue
Normal file
48
Web/src/views/login/index.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<a-form-model :model="form" @submit="handleSubmit" @submit.native.prevent layout="inline">
|
||||
<a-form-model-item>
|
||||
<a-input placeholder="Username" v-model="form.user">
|
||||
<a-icon slot="prefix" style="color:rgba(0,0,0,.25)" type="user" />
|
||||
</a-input>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item>
|
||||
<a-input placeholder="Password" type="password" v-model="form.password">
|
||||
<a-icon slot="prefix" style="color:rgba(0,0,0,.25)" type="lock" />
|
||||
</a-input>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item>
|
||||
<a-button
|
||||
:disabled="form.user === '' || form.password === ''"
|
||||
:loading="loading"
|
||||
html-type="submit"
|
||||
type="primary"
|
||||
>Log in</a-button>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</template>
|
||||
<script>
|
||||
import { doLogin } from '@/common/login';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
form: {
|
||||
user: '',
|
||||
password: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSubmit(e) {
|
||||
this.loading = true;
|
||||
doLogin({
|
||||
account: this.form.user,
|
||||
password: this.form.password,
|
||||
}).then(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
108
Web/src/views/main/_layout/content.vue
Normal file
108
Web/src/views/main/_layout/content.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<a-layout-content>
|
||||
<a-tabs @change="onChange" @edit="onClose" hide-add type="editable-card" v-model="actived">
|
||||
<a-tab-pane
|
||||
:closable="pane.closable"
|
||||
:forceRender="true"
|
||||
:key="pane.key"
|
||||
v-for="pane in panes"
|
||||
>
|
||||
<a-dropdown :trigger="['contextmenu']" slot="tab">
|
||||
<div>
|
||||
<a-icon :type="pane.icon" v-if="pane.icon" />
|
||||
{{ pane.title }}
|
||||
</div>
|
||||
<a-menu slot="overlay">
|
||||
<template v-if="mode === 'development'">
|
||||
<a-menu-item @click="onCopyComponent(pane)" key="-1">
|
||||
复制组件地址
|
||||
<a-tag color="red">dev</a-tag>
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
</template>
|
||||
<a-menu-item @click="onLoadContentWindow(pane.key)" key="0">重新加载</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item :disabled="!pane.closable" @click="$emit('close', pane.key);" key="1">关闭</a-menu-item>
|
||||
<a-menu-item @click="$emit('close-other', pane.key)" key="2">关闭其他标签页</a-menu-item>
|
||||
<a-menu-item @click="$emit('close-right', pane.key)" key="3">关闭右侧标签页</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
<component :is="pane.component" :key="pane.key" v-if="pane.loaded" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-layout-content>
|
||||
</template>
|
||||
<script>
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
NProgress.configure({ parent: '.ant-layout-content > .ant-tabs > .ant-tabs-content' });
|
||||
|
||||
export default {
|
||||
props: {
|
||||
panes: {
|
||||
type: Array,
|
||||
},
|
||||
tabActived: {
|
||||
type: [String, Number],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mode: process.env.VUE_APP_NODE_ENV,
|
||||
actived: '',
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
tabActived() {
|
||||
if (this.tabActived !== this.actived) {
|
||||
this.actived = this.tabActived;
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.actived = this.tabActived;
|
||||
},
|
||||
methods: {
|
||||
onLoadContentWindow(key) {
|
||||
NProgress.start();
|
||||
const pane = this.panes.find((p) => p.key === key);
|
||||
const i = import(`@/pages${pane.path}`);
|
||||
pane.component = () => i;
|
||||
pane.loaded = false;
|
||||
i.then(() => {
|
||||
pane.loaded = true;
|
||||
NProgress.done();
|
||||
}).catch(() => {
|
||||
pane.component = () => import('@/views/error/404');
|
||||
pane.loaded = true;
|
||||
NProgress.done();
|
||||
});
|
||||
},
|
||||
onClose(targetKey, action) {
|
||||
if (action === 'remove') {
|
||||
this.$emit('close', targetKey);
|
||||
}
|
||||
},
|
||||
onChange(activeKey) {
|
||||
this.$emit('change', activeKey);
|
||||
},
|
||||
|
||||
onCopyComponent(pane) {
|
||||
try {
|
||||
const copy = document.createElement('textarea');
|
||||
document.body.append(copy);
|
||||
copy.value = `/pages${pane.path}`;
|
||||
copy.select();
|
||||
setTimeout(() => {
|
||||
document.execCommand('copy');
|
||||
copy.remove();
|
||||
this.$message.success('已复制到剪切板');
|
||||
});
|
||||
} catch {
|
||||
this.$message.error('复制错误');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
80
Web/src/views/main/_layout/header/index.vue
Normal file
80
Web/src/views/main/_layout/header/index.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<a-layout-header class="header">
|
||||
<section
|
||||
v-if="$root.global.settings.layout === 'left-menu' || $root.global.settings.layout === 'right-menu'"
|
||||
>
|
||||
<div class="header-actions">
|
||||
<a
|
||||
@click="$root.global.settings.siderCollapsed = !$root.global.settings.siderCollapsed"
|
||||
class="header-action"
|
||||
>
|
||||
<a-icon :type="$root.global.settings.siderCollapsed ? 'menu-unfold' : 'menu-fold'" />
|
||||
</a>
|
||||
<search :menus="nav.menus" />
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<User />
|
||||
<a @click="$emit('reload')" class="header-action">
|
||||
<a-icon type="reload" />
|
||||
</a>
|
||||
<a class="header-action">
|
||||
<a-badge count="5">
|
||||
<a-icon type="bell" />
|
||||
</a-badge>
|
||||
</a>
|
||||
<a @click="$emit('setting')" class="header-action">
|
||||
<a-icon type="setting" />
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
<container v-else-if="$root.global.settings.layout === 'top-nav'">
|
||||
<div class="header-actions">
|
||||
<Logo />
|
||||
<Sider :nav="nav" @open="(nav) => $emit('open', nav)" />
|
||||
<search :menus="nav.menus" />
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<User />
|
||||
<a @click="$emit('reload')" class="header-action">
|
||||
<a-icon type="reload" />
|
||||
</a>
|
||||
<a class="header-action">
|
||||
<a-badge count="5">
|
||||
<a-icon type="bell" />
|
||||
</a-badge>
|
||||
</a>
|
||||
<a @click="$emit('setting')" class="header-action">
|
||||
<a-icon type="setting" />
|
||||
</a>
|
||||
</div>
|
||||
</container>
|
||||
</a-layout-header>
|
||||
</template>
|
||||
<script>
|
||||
import Logo from '../logo';
|
||||
import Sider from '../sider';
|
||||
|
||||
import User from './user';
|
||||
import Search from './search';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Logo,
|
||||
Sider,
|
||||
|
||||
User,
|
||||
Search,
|
||||
},
|
||||
props: {
|
||||
nav: {
|
||||
default() {
|
||||
return {
|
||||
apps: [],
|
||||
menus: [],
|
||||
};
|
||||
},
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
134
Web/src/views/main/_layout/header/search.js
Normal file
134
Web/src/views/main/_layout/header/search.js
Normal file
@@ -0,0 +1,134 @@
|
||||
export default {
|
||||
props: {
|
||||
menus: {
|
||||
type: Array,
|
||||
require: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
searchText: '',
|
||||
searchResult: [],
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
renderSelect(menu) {
|
||||
return menu.map((p) => {
|
||||
return p.children ? this.renderSelectGroup(p) : this.renderSelectOption(p)
|
||||
})
|
||||
},
|
||||
|
||||
renderSelectGroup(menu) {
|
||||
return (
|
||||
<a-select-opt-group key={menu.parents}>
|
||||
<span slot="label">{menu.parents}</span>
|
||||
{this.renderSelect(menu.children)}
|
||||
</a-select-opt-group>
|
||||
)
|
||||
},
|
||||
|
||||
renderSelectOption(menu) {
|
||||
return (<a-select-option key={menu.id} value={
|
||||
JSON.stringify(menu)
|
||||
}>
|
||||
{menu.meta.title}
|
||||
<small style={{ display: 'block', color: '#aaa' }}>{menu.component}</small>
|
||||
</a-select-option>)
|
||||
},
|
||||
|
||||
|
||||
onSearch(value) {
|
||||
this.searchText = value
|
||||
|
||||
const menus = this.$_.cloneDeep(this.menus)
|
||||
|
||||
const search = (m) => {
|
||||
if (!value) return []
|
||||
return m.filter((p) => {
|
||||
if (p.children) {
|
||||
p.children = search(p.children)
|
||||
} else {
|
||||
return p.meta.title.indexOf(value) > -1 || (p.component || '').toLowerCase().indexOf(value.toLowerCase()) > -1
|
||||
}
|
||||
return p.children.length
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 拆分层级,只留下 [父级-父级-...] [子级]
|
||||
* *******在更为复杂的目录下会出现父级联动错误的问题
|
||||
*/
|
||||
const unzip = (m) => {
|
||||
const getSeed = (parent, seed) => {
|
||||
if (parent.children) {
|
||||
seed.parents.push(parent.meta.title)
|
||||
seed.children = parent.children
|
||||
parent.children.forEach(p => {
|
||||
getSeed(p, seed)
|
||||
})
|
||||
}
|
||||
return seed
|
||||
}
|
||||
const result = []
|
||||
m.forEach(p => {
|
||||
const r = { parents: [], children: [] }
|
||||
result.push(getSeed(p, r))
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
this.searchResult = unzip(search(menus)).map(p => {
|
||||
return {
|
||||
parents: p.parents.join('-'),
|
||||
children: p.children
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onSearchSelect(value, node) {
|
||||
this.searchText = '';
|
||||
this.onSearch(this.searchText);
|
||||
|
||||
const menu = JSON.parse(node.componentOptions.propsData.value)
|
||||
|
||||
this.openContentWindow({
|
||||
key: menu.id,
|
||||
title: menu.meta.title,
|
||||
icon: menu.meta.icon,
|
||||
path: menu.component,
|
||||
});
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
render() {
|
||||
|
||||
const props = {
|
||||
dropdownMatchSelectWidth: false,
|
||||
dropdownStyle: { width: '300px' },
|
||||
optionLabelProp: 'value',
|
||||
placeholder: '请输入检索关键字',
|
||||
value: this.searchText
|
||||
}
|
||||
|
||||
const on = {
|
||||
search: this.onSearch,
|
||||
select: this.onSearchSelect
|
||||
}
|
||||
|
||||
return (
|
||||
<a-auto-complete {...{ props, on }}>
|
||||
<template slot="dataSource">
|
||||
{this.renderSelect(this.searchResult)}
|
||||
</template>
|
||||
<a-input>
|
||||
<a-icon slot="suffix" type="search" class="certain-category-icon" />
|
||||
</a-input>
|
||||
</a-auto-complete>
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
78
Web/src/views/main/_layout/header/user.js
Normal file
78
Web/src/views/main/_layout/header/user.js
Normal file
@@ -0,0 +1,78 @@
|
||||
let userOpenTimer, userCloseTimer
|
||||
|
||||
let initDropdownHeight
|
||||
|
||||
import { doLogout } from '@/common/login'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dropdownHeight: 0
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
initDropdownHeight = this.$refs.dropdown.scrollHeight
|
||||
},
|
||||
methods: {
|
||||
onOpen(e) {
|
||||
clearTimeout(userCloseTimer)
|
||||
e.target.classList.add('open')
|
||||
userOpenTimer = setTimeout(() => {
|
||||
e.target.classList.add('drop')
|
||||
this.dropdownHeight = initDropdownHeight
|
||||
}, 300)
|
||||
},
|
||||
|
||||
onClose(e) {
|
||||
clearTimeout(userOpenTimer)
|
||||
e.target.classList.remove('drop')
|
||||
this.dropdownHeight = 0
|
||||
userCloseTimer = setTimeout(() => {
|
||||
e.target.classList.remove('open')
|
||||
}, 300)
|
||||
},
|
||||
|
||||
onLogout() {
|
||||
this.$confirm({
|
||||
title: '提示',
|
||||
content: '是否确定退出登录',
|
||||
onOk: () => {
|
||||
doLogout()
|
||||
},
|
||||
onCancel() {
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div onMouseenter={this.onOpen} onMouseleave={this.onClose} class="user-container" >
|
||||
<div class="user-container-inner">
|
||||
<div class="user--base">
|
||||
<a-avatar
|
||||
src={this.$root.global.info && this.$root.global.info.avatar}
|
||||
class="user--avatar"
|
||||
icon="user"
|
||||
/>
|
||||
{
|
||||
this.$root.global.info &&
|
||||
<span
|
||||
class="user--name"
|
||||
>{this.$root.global.info.nickName || this.$root.global.info.name}</span>
|
||||
}
|
||||
</div>
|
||||
<div class="user--dropdown" ref="dropdown" style={{ height: `${this.dropdownHeight}px` }}>
|
||||
<ul class="ant-dropdown-menu ant-dropdown-menu-vertical">
|
||||
<li class="ant-dropdown-menu-item-divider"></li>
|
||||
<li class="ant-dropdown-menu-item" onClick={this.onLogout}>
|
||||
<a-icon type="logout" />
|
||||
退出登录
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
}
|
||||
6
Web/src/views/main/_layout/logo.vue
Normal file
6
Web/src/views/main/_layout/logo.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<div class="logo">
|
||||
<img :src="require('@/assets/image/logo32.png')" alt />
|
||||
<span>LazyOn</span>
|
||||
</div>
|
||||
</template>
|
||||
50
Web/src/views/main/_layout/sider/app.vue
Normal file
50
Web/src/views/main/_layout/sider/app.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<a-popover :placement="placement" trigger="click">
|
||||
<template slot="content">
|
||||
<a-button-group>
|
||||
<a-button :key="app.code" @click="onChangeApp(app)" v-for="app in apps">{{ app.name }}</a-button>
|
||||
</a-button-group>
|
||||
</template>
|
||||
<div class="yo-apps-selector">
|
||||
<span>{{ appActived.name }}</span>
|
||||
</div>
|
||||
</a-popover>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
apps: {
|
||||
type: Array,
|
||||
require: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
placement() {
|
||||
const layout = this.$root.global.settings.layout;
|
||||
switch (layout) {
|
||||
case 'left-menu':
|
||||
return 'right';
|
||||
case 'right-menu':
|
||||
return 'left';
|
||||
default:
|
||||
return 'bottom';
|
||||
}
|
||||
},
|
||||
|
||||
appActived() {
|
||||
return this.apps.find((p) => p.active);
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
console.log(this.apps);
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChangeApp(app) {
|
||||
this.$emit('change-app', app);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
113
Web/src/views/main/_layout/sider/index.vue
Normal file
113
Web/src/views/main/_layout/sider/index.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<section>
|
||||
<a-layout-sider
|
||||
:collapsed="siderCollapsed === undefined ? $root.global.settings.siderCollapsed : siderCollapsed"
|
||||
@collapse="onCollapse"
|
||||
v-if="$root.global.settings.layout === 'left-menu' || $root.global.settings.layout === 'right-menu'"
|
||||
width="200"
|
||||
>
|
||||
<Logo />
|
||||
<div class="yo-sider-nav">
|
||||
<App :apps="nav.apps" @change-app="(app) => $emit('change-app', app)" />
|
||||
<swiper :options="siderSwiperOptions" ref="sider-swiper">
|
||||
<swiper-slide>
|
||||
<a-spin :spinning="nav.loading">
|
||||
<a-icon slot="indicator" spin type="loading" />
|
||||
<Menu
|
||||
:menu-style="{ height: '100%', borderRight: 0 }"
|
||||
:nav="nav"
|
||||
@openChange="onMenuOpenChange"
|
||||
mode="inline"
|
||||
/>
|
||||
</a-spin>
|
||||
</swiper-slide>
|
||||
<div class="swiper-scrollbar" id="layout--swiper-scrollbar" slot="scrollbar"></div>
|
||||
</swiper>
|
||||
</div>
|
||||
</a-layout-sider>
|
||||
<template v-else-if="$root.global.settings.layout === 'top-nav'">
|
||||
<Menu
|
||||
:menu-style="{ borderBottom: 0 }"
|
||||
:nav="nav"
|
||||
@openChange="onMenuOpenChange"
|
||||
mode="horizontal"
|
||||
/>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
<script>
|
||||
import Logo from '../logo';
|
||||
import App from './app';
|
||||
import Menu from './menu';
|
||||
|
||||
let timer;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Logo,
|
||||
App,
|
||||
Menu,
|
||||
},
|
||||
props: {
|
||||
nav: {
|
||||
default() {
|
||||
return {
|
||||
apps: [],
|
||||
menus: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
siderSwiperOptions: {
|
||||
direction: 'vertical',
|
||||
slidesPerView: 'auto',
|
||||
freeMode: true,
|
||||
scrollbar: {
|
||||
el: '#layout--swiper-scrollbar',
|
||||
},
|
||||
mousewheel: true,
|
||||
},
|
||||
|
||||
siderCollapsed: undefined,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('resize', () => {
|
||||
if (this.$root.global.settings.layout === 'left-menu' || this.$root.global.settings.layout === 'right-menu') {
|
||||
if (!this.$root.global.settings.siderCollapsed) {
|
||||
if (window.innerWidth < 1000) {
|
||||
this.siderCollapsed = true;
|
||||
} else {
|
||||
this.siderCollapsed = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
if (this.$refs['sider-swiper']) {
|
||||
this.$refs['sider-swiper'].$swiper.update();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
onMenuOpenChange() {
|
||||
this.windowTriggerResize();
|
||||
},
|
||||
|
||||
onCollapse() {
|
||||
this.windowTriggerResize();
|
||||
},
|
||||
|
||||
windowTriggerResize() {
|
||||
let e = new Event('resize');
|
||||
window.dispatchEvent(e);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
80
Web/src/views/main/_layout/sider/menu.js
Normal file
80
Web/src/views/main/_layout/sider/menu.js
Normal file
@@ -0,0 +1,80 @@
|
||||
export default {
|
||||
props: {
|
||||
nav: {
|
||||
default() {
|
||||
return {
|
||||
modules: [],
|
||||
menus: [],
|
||||
}
|
||||
},
|
||||
type: Object,
|
||||
},
|
||||
menuStyle: {
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
type: Object || String,
|
||||
},
|
||||
mode: {
|
||||
default: 'inline',
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
renderMenu(menu) {
|
||||
return menu.map((p) => {
|
||||
return p.children ? this.renderSubMenu(p) : this.renderMenuItem(p)
|
||||
})
|
||||
},
|
||||
|
||||
renderSubMenu(menu) {
|
||||
return (
|
||||
<a-sub-menu key={menu.id}>
|
||||
<span slot="title">
|
||||
{menu.meta.icon ? <a-icon type={menu.meta.icon} /> : null}
|
||||
<span>{menu.meta.title}</span>
|
||||
</span>
|
||||
{this.renderMenu(menu.children)}
|
||||
</a-sub-menu>
|
||||
)
|
||||
},
|
||||
|
||||
renderMenuItem(menu) {
|
||||
return (
|
||||
<a-menu-item key={menu.id} onClick={() => this.onOpenContentWindow(menu)}>
|
||||
{menu.meta.icon ? <a-icon type={menu.meta.icon} /> : null}
|
||||
<span>{menu.meta.title}</span>
|
||||
</a-menu-item>
|
||||
)
|
||||
},
|
||||
|
||||
onMenuOpenChange() {
|
||||
this.$emit('openChange')
|
||||
},
|
||||
|
||||
onOpenContentWindow(menu) {
|
||||
this.openContentWindow({
|
||||
key: menu.id,
|
||||
title: menu.meta.title,
|
||||
icon: menu.meta.icon,
|
||||
path: menu.component,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
const props = {
|
||||
mode: this.mode,
|
||||
selectable: false,
|
||||
style: this.menuStyle,
|
||||
theme: this.$root.global.settings.navTheme,
|
||||
}
|
||||
|
||||
const on = {
|
||||
openChange: this.onMenuOpenChange,
|
||||
}
|
||||
|
||||
return <a-menu {...{ props, on }}>{this.renderMenu(this.nav.menus)}</a-menu>
|
||||
},
|
||||
}
|
||||
267
Web/src/views/main/index.vue
Normal file
267
Web/src/views/main/index.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<section>
|
||||
<a-layout
|
||||
:class="{
|
||||
[`yo-layout--top-nav--${$root.global.settings.container}`]: $root.global.settings.layout === 'top-nav',
|
||||
[`yo-layout--${$root.global.settings.layout}`]: true,
|
||||
}"
|
||||
>
|
||||
<Sider
|
||||
:nav="nav"
|
||||
@change-app="onChangeApp"
|
||||
v-if="$root.global.settings.layout === 'left-menu'"
|
||||
/>
|
||||
<a-layout>
|
||||
<Header :nav="nav" @reload="onReloadContentWindow" @setting="setting.visible = true" />
|
||||
<a-layout>
|
||||
<Content
|
||||
:panes="panes"
|
||||
:tabActived="tabActived"
|
||||
@change="onChangeContentWindow"
|
||||
@close="onCloseContentWindow"
|
||||
@close-other="onCloseOtherContentWindow"
|
||||
@close-right="onCloseRightContentWindow"
|
||||
ref="content"
|
||||
/>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
<Sider :nav="nav" v-if="$root.global.settings.layout === 'right-menu'" />
|
||||
</a-layout>
|
||||
<Setting :visible="setting.visible" @close="setting.visible = false" />
|
||||
</section>
|
||||
</template>
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
|
||||
import Header from './_layout/header';
|
||||
import Sider from './_layout/sider/index';
|
||||
import Content from './_layout/content';
|
||||
|
||||
import Setting from './setting';
|
||||
|
||||
import { setGlobal } from '@/common/login';
|
||||
import { APP_MENU_KEY } from '@/common/storage';
|
||||
|
||||
const getNewID = () => {
|
||||
return Math.random().toString(16).slice(2);
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Header,
|
||||
Sider,
|
||||
Content,
|
||||
Setting,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
panes: new Array(),
|
||||
tabActived: '',
|
||||
|
||||
setting: {
|
||||
visible: false,
|
||||
},
|
||||
nav: {
|
||||
loading: false,
|
||||
apps: [],
|
||||
menus: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
Vue.prototype.openContentWindow = this.onOpenContentWindow;
|
||||
Vue.prototype.closeContentWindow = this.onCloseContentWindow;
|
||||
|
||||
this.nav.loading = true;
|
||||
|
||||
this.$api.getLoginUser().then(({ data }) => {
|
||||
// 去除应用和菜单信息,存储基本信息
|
||||
const info = this.$_.cloneDeep(data);
|
||||
delete info.apps;
|
||||
delete info.menus;
|
||||
setGlobal(info);
|
||||
|
||||
data.apps.map((p) => (p.active = p.active === 'Y'));
|
||||
this.onSetNav(data);
|
||||
this.nav.loading = false;
|
||||
|
||||
this.$root.global.defaultWindow.map((options) => {
|
||||
this.onOpenContentWindow(options);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 打开一个新的标签页
|
||||
* 第一个参数可以是json
|
||||
*/
|
||||
onOpenContentWindow(title, path, icon, closable = true) {
|
||||
const settings =
|
||||
typeof title === 'object'
|
||||
? title
|
||||
: {
|
||||
title,
|
||||
path,
|
||||
icon,
|
||||
closable,
|
||||
};
|
||||
|
||||
if (settings.path) {
|
||||
const key = settings.key || getNewID();
|
||||
|
||||
/**
|
||||
* 如果当前标签页已打开,则只需要选中
|
||||
*/
|
||||
const pane = this.panes.find((p) => p.key === key);
|
||||
if (pane) {
|
||||
this.onChangeContentWindow(key);
|
||||
return;
|
||||
}
|
||||
|
||||
const path = settings.path.startsWith('/') ? settings.path : `/${settings.path}`;
|
||||
|
||||
/**
|
||||
* 向标签页队列中添加一个新的标签页
|
||||
*/
|
||||
this.panes.push({
|
||||
key,
|
||||
closable: settings.closable === undefined ? true : settings.closable,
|
||||
icon: settings.icon,
|
||||
title: settings.title || '新建窗口',
|
||||
component: null,
|
||||
path,
|
||||
loaded: false,
|
||||
});
|
||||
|
||||
this.onChangeContentWindow(key);
|
||||
|
||||
this.$refs.content.onLoadContentWindow(key);
|
||||
} else {
|
||||
console.warn('地址错误');
|
||||
}
|
||||
},
|
||||
|
||||
onCloseContentWindow(key) {
|
||||
key = key || this.tabActived;
|
||||
const i = this.$_.findIndex(this.panes, (p) => p.key === key);
|
||||
this.panes.splice(i, 1);
|
||||
|
||||
if (this.panes.length) {
|
||||
if (key === this.tabActived) {
|
||||
const pane = this.panes[i];
|
||||
if (pane) {
|
||||
this.onChangeContentWindow(pane.key);
|
||||
} else {
|
||||
this.onChangeContentWindow(this.panes[i - 1].key);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onCloseOtherContentWindow(key) {
|
||||
const panes = Object.assign([], this.panes);
|
||||
let flag = false;
|
||||
for (let i = panes.length - 1; i >= 0; i--) {
|
||||
const p = panes[i];
|
||||
if (p.key !== key && p.closable) {
|
||||
if (p.key === this.tabActived) {
|
||||
flag = true;
|
||||
}
|
||||
panes.splice(i, 1);
|
||||
}
|
||||
}
|
||||
this.panes = panes;
|
||||
if (flag) {
|
||||
this.onChangeContentWindow(this.$_.last(panes).key);
|
||||
}
|
||||
},
|
||||
|
||||
onCloseRightContentWindow(key) {
|
||||
const panes = Object.assign([], this.panes);
|
||||
let flag = false;
|
||||
for (let i = panes.length - 1; i >= 0; i--) {
|
||||
const p = panes[i];
|
||||
if (p.key !== key && p.closable) {
|
||||
if (p.key === this.tabActived) {
|
||||
flag = true;
|
||||
}
|
||||
panes.splice(i, 1);
|
||||
} else if (p.key === key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.panes = panes;
|
||||
if (flag) {
|
||||
this.onChangeContentWindow(this.$_.last(panes).key);
|
||||
}
|
||||
},
|
||||
|
||||
onChangeContentWindow(key) {
|
||||
this.tabActived = key;
|
||||
},
|
||||
|
||||
onReloadContentWindow(key) {
|
||||
if (!key) key = this.tabActived;
|
||||
this.$refs.content.onLoadContentWindow(key);
|
||||
},
|
||||
|
||||
onChangeApp(app) {
|
||||
this.nav.loading = true;
|
||||
this.$api.sysMenuChange({ application: app.code }).then(({ data }) => {
|
||||
this.nav.apps.map((p) => (p.active = p.code === app.code));
|
||||
window.localStorage.setItem(
|
||||
APP_MENU_KEY,
|
||||
JSON.stringify({
|
||||
...this.nav.apps.find((p) => p.active),
|
||||
menus: data,
|
||||
})
|
||||
);
|
||||
this.onSetNav({
|
||||
apps: this.nav.apps,
|
||||
menus: data,
|
||||
});
|
||||
|
||||
this.nav.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
onSetNav(nav) {
|
||||
// 从本地存储获取当前选中的应用及菜单
|
||||
this.nav.apps = nav.apps;
|
||||
const storage = JSON.parse(window.localStorage.getItem(APP_MENU_KEY));
|
||||
if (storage) {
|
||||
this.nav.apps.map((p) => (p.active = p.code === storage.code));
|
||||
this.serializeMenu(storage.menus);
|
||||
} else {
|
||||
// 将默认选中菜单存储
|
||||
window.localStorage.setItem(
|
||||
APP_MENU_KEY,
|
||||
JSON.stringify({
|
||||
...nav.apps.find((p) => p.active),
|
||||
menus: nav.menus,
|
||||
})
|
||||
);
|
||||
this.serializeMenu(nav.menus);
|
||||
}
|
||||
},
|
||||
|
||||
serializeMenu(menus) {
|
||||
const menu = this.$_.cloneDeep(menus);
|
||||
const children = this.$_.groupBy(menu, 'pid');
|
||||
|
||||
const serialize = (m) => {
|
||||
m.map((p) => {
|
||||
if (children[p.id]) {
|
||||
p.children = serialize(children[p.id]);
|
||||
}
|
||||
});
|
||||
return m;
|
||||
};
|
||||
|
||||
this.nav.menus = children[0] ? serialize(children[0]) : new Array();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
56
Web/src/views/main/setting.vue
Normal file
56
Web/src/views/main/setting.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<a-modal :footer="null" :visible="visible" :width="708" @cancel="$emit('close')">
|
||||
<a-form-model class="yo-form">
|
||||
<h3>布局设置</h3>
|
||||
<div class="yo-form-group">
|
||||
<a-form-model-item label="导航菜单位置">
|
||||
<a-select v-model="$root.global.settings.layout">
|
||||
<a-select-option value="left-menu">左侧菜单经典结构</a-select-option>
|
||||
<a-select-option value="top-nav">顶部导航菜单</a-select-option>
|
||||
<a-select-option value="right-menu">右侧菜单结构</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="内容宽度">
|
||||
<a-select v-model="$root.global.settings.container">
|
||||
<a-select-option value="container-fluid">整宽</a-select-option>
|
||||
<a-select-option v-if="$root.global.settings.layout === 'top-nav'" value="container">居中</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
<h3>主题设置</h3>
|
||||
<div class="yo-form-group">
|
||||
<a-form-model-item label="导航颜色">
|
||||
<a-select v-model="$root.global.settings.navTheme">
|
||||
<a-select-option value="dark">深色</a-select-option>
|
||||
<a-select-option value="light">浅色</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="主题颜色">
|
||||
<a-select>
|
||||
<a-select-option value="1">蓝色</a-select-option>
|
||||
<a-select-option value="2">红色</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-form-model>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
visible: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$root.global.settings.layout'() {
|
||||
if (this.$root.global.settings.layout === 'top-nav') {
|
||||
this.$root.global.settings.container = 'container';
|
||||
} else {
|
||||
this.$root.global.settings.container = 'container-fluid';
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user