commit
64c6e4c2e1
@ -0,0 +1,9 @@
|
||||
VUE_APP_PUBLIC_PATH=/
|
||||
VUE_APP_NAME=Admin
|
||||
VUE_APP_ROUTES_KEY=admin.routes
|
||||
VUE_APP_PERMISSIONS_KEY=admin.permissions
|
||||
VUE_APP_ROLES_KEY=admin.roles
|
||||
VUE_APP_USER_KEY=admin.user
|
||||
VUE_APP_SETTING_KEY=admin.setting
|
||||
VUE_APP_TBAS_KEY=admin.tabs
|
||||
VUE_APP_TBAS_TITLES_KEY=admin.tabs.titles
|
||||
@ -0,0 +1,6 @@
|
||||
VUE_ENV = development
|
||||
|
||||
VUE_APP_PUBLIC_PATH = '/admin'
|
||||
|
||||
# VUE_APP_API_BASE_URL=https://mock.localhost.com
|
||||
VUE_APP_API_URL=https://card.h888.fun/adminapi/v1
|
||||
@ -0,0 +1,5 @@
|
||||
VUE_ENV = production
|
||||
|
||||
VUE_APP_PUBLIC_PATH = '/admin'
|
||||
|
||||
VUE_APP_API_URL=https://utel.vip/adminapi/v1
|
||||
@ -0,0 +1,6 @@
|
||||
VUE_ENV = production
|
||||
|
||||
VUE_APP_PUBLIC_PATH = '/admin'
|
||||
|
||||
VUE_APP_API_URL=https://card.h888.fun/adminapi/v1
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
VUE_ENV = stage
|
||||
|
||||
VUE_APP_PUBLIC_PATH = '/admin'
|
||||
|
||||
VUE_APP_API_URL=https://utel.zltest.com.tw/adminapi/v1
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
admindb/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
/test/unit/coverage/
|
||||
/test/e2e/reports/
|
||||
selenium-debug.log
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
package-lock.json
|
||||
.env.production.local
|
||||
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 iczer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -0,0 +1,13 @@
|
||||
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
|
||||
|
||||
const plugins = []
|
||||
if (IS_PROD) {
|
||||
plugins.push('transform-remove-console')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
],
|
||||
plugins
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "vue-antd-admin",
|
||||
"version": "0.7.4",
|
||||
"homepage": "https://iczer.github.io/vue-antd-admin",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"build:dev": "vue-cli-service build --mode development",
|
||||
"build:sta": "vue-cli-service build --mode stage",
|
||||
"build:slash": "vue-cli-service build --mode slash",
|
||||
"lint": "vue-cli-service lint",
|
||||
"predeploy": "yarn build",
|
||||
"deploy": "gh-pages -d dist -b pages -r https://gitee.com/iczer/vue-antd-admin.git",
|
||||
"docs:dev": "vuepress dev docs",
|
||||
"docs:build": "vuepress build docs",
|
||||
"docs:deploy": "vuepress build docs && gh-pages -d docs/.vuepress/dist -b master -r https://gitee.com/iczer/vue-antd-admin-docs.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/data-set": "^0.11.4",
|
||||
"animate.css": "^4.1.0",
|
||||
"ant-design-vue": "1.7.2",
|
||||
"axios": "^0.19.2",
|
||||
"ckeditor4-vue": "^2.0.0",
|
||||
"clipboard": "^2.0.6",
|
||||
"core-js": "^3.6.5",
|
||||
"date-fns": "^2.14.0",
|
||||
"echarts": "^4.9.0",
|
||||
"enquire.js": "^2.1.6",
|
||||
"highlight.js": "^10.2.1",
|
||||
"js-cookie": "^2.2.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"postcss-loader": "^4.3.0",
|
||||
"qrcodejs2": "^0.0.2",
|
||||
"raw-loader": "^4.0.2",
|
||||
"viser-vue": "^2.4.8",
|
||||
"vue": "^2.6.11",
|
||||
"vue-clipboard2": "^0.3.3",
|
||||
"vue-echarts": "^4.0.3",
|
||||
"vue-element-resize-event": "^0.1.0",
|
||||
"vue-i18n": "^8.18.2",
|
||||
"vue-print-nb": "^1.7.5",
|
||||
"vue-router": "^3.3.4",
|
||||
"vuedraggable": "^2.23.2",
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/colors": "^4.0.1",
|
||||
"@vue/cli-plugin-babel": "^4.4.0",
|
||||
"@vue/cli-plugin-eslint": "^4.4.0",
|
||||
"@vue/cli-service": "^4.4.0",
|
||||
"@vuepress/plugin-back-to-top": "^1.5.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"compression-webpack-plugin": "^2.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"gh-pages": "^3.1.0",
|
||||
"less-loader": "^6.1.1",
|
||||
"style-resources-loader": "^1.3.2",
|
||||
"vue-cli-plugin-style-resources-loader": "^0.1.4",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuepress": "^1.5.2",
|
||||
"webpack-theme-color-replacer": "1.3.18",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 10"
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,349 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
@ -0,0 +1,222 @@
|
||||
@charset "utf-8";
|
||||
body,
|
||||
div,
|
||||
dl,
|
||||
footer,
|
||||
html,
|
||||
img,
|
||||
menu,
|
||||
p,
|
||||
span {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
body {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
background-color: #fffff6 !important;
|
||||
padding-bottom: 49px;
|
||||
}
|
||||
a,
|
||||
a:hover,
|
||||
a:visited {
|
||||
color: #999;
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
}
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
@-webkit-keyframes pop-hide {
|
||||
0% {
|
||||
-webkit-transform: scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
||||
2% {
|
||||
-webkit-transform: scale(1.1);
|
||||
opacity: 1;
|
||||
}
|
||||
6% {
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
90% {
|
||||
-webkit-transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(0.9);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes pop {
|
||||
0% {
|
||||
-webkit-transform: scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1.1);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes slideup {
|
||||
0% {
|
||||
-webkit-transform: translateY(100%);
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: translateY(-10%);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.left {
|
||||
float: left;
|
||||
}
|
||||
.rel {
|
||||
position: relative;
|
||||
}
|
||||
a,
|
||||
a:visited {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
}
|
||||
.text-icon {
|
||||
font-family: base_icon;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
font-style: normal;
|
||||
}
|
||||
.my-account {
|
||||
color: #333;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
height: 6rem;
|
||||
}
|
||||
.account-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
.account-bg img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.my-account > img {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
.my-account .user-info {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 70px;
|
||||
box-sizing: border-box;
|
||||
padding-left: 1.9em;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
.my-account .uname {
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
margin-top: 0.1em;
|
||||
margin-bottom: 0.2em;
|
||||
text-shadow: 0.07em 0.07em #333;
|
||||
}
|
||||
.my-account .umoney {
|
||||
color: #fff;
|
||||
margin-bottom: 0.06em;
|
||||
text-shadow: 0.05em 0.05em #333;
|
||||
}
|
||||
.my-account .avatar_box {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
left: 1em;
|
||||
width: 5em;
|
||||
height: 5em;
|
||||
z-index: 1;
|
||||
border-radius: 100%;
|
||||
border: 2px solid #ffd44a;
|
||||
-moz-border-radius: 100%;
|
||||
-webkit-border-radius: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.my-account .avater {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.phone {
|
||||
width: 105px;
|
||||
float: left;
|
||||
z-index: 100;
|
||||
}
|
||||
.set {
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
right: 10px;
|
||||
top: 20px;
|
||||
z-index: 100;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
background-color: #fdaf00;
|
||||
text-align: center;
|
||||
margin-top: -7px;
|
||||
padding: 2px 2px;
|
||||
}
|
||||
.set a {
|
||||
color: #fff !important;
|
||||
}
|
||||
.dl01 {
|
||||
padding: 0 10px 10px;
|
||||
background-color: #fff;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.titleImg {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-right: 10px;
|
||||
margin-top: 15px;
|
||||
float: left;
|
||||
}
|
||||
.dl02 {
|
||||
padding: 0 10px;
|
||||
background-color: #fff;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.dl02 a .menu {
|
||||
border-bottom: 1px solid #ffe9b7;
|
||||
background: url(../images/right.png) no-repeat right center;
|
||||
background-size: 10px;
|
||||
}
|
||||
.dl02 a .menu div {
|
||||
padding-top: 16px;
|
||||
font-size: 15px;
|
||||
color: #666;
|
||||
}
|
||||
.dl02 a .menu div.left {
|
||||
float: left;
|
||||
width: 40%;
|
||||
}
|
||||
.dl02 a .menu div.right {
|
||||
float: left;
|
||||
text-align: right;
|
||||
width: 45%;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
After Width: | Height: | Size: 484 B |
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-TW" class="beauty-scroll">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= process.env.VUE_APP_NAME %></title>
|
||||
<!-- require cdn assets css -->
|
||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
|
||||
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="popContainer" class="beauty-scroll" style="height: 100vh; overflow-y: scroll">
|
||||
<div id="app"></div>
|
||||
</div>
|
||||
<!-- require cdn assets js -->
|
||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
|
||||
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
|
||||
<% } %>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<a-config-provider :locale="locale" :get-popup-container="popContainer">
|
||||
<router-view/>
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {enquireScreen} from './utils/util'
|
||||
import {mapState, mapMutations} from 'vuex'
|
||||
import themeUtil from '@/utils/themeUtil';
|
||||
import {getI18nKey} from '@/utils/routerUtil'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
data() {
|
||||
return {
|
||||
locale: {}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.setHtmlTitle()
|
||||
this.setLanguage(this.lang)
|
||||
enquireScreen(isMobile => this.setDevice(isMobile))
|
||||
},
|
||||
mounted() {
|
||||
this.setWeekModeTheme(this.weekMode)
|
||||
},
|
||||
watch: {
|
||||
weekMode(val) {
|
||||
this.setWeekModeTheme(val)
|
||||
},
|
||||
lang(val) {
|
||||
this.setLanguage(val)
|
||||
this.setHtmlTitle()
|
||||
},
|
||||
$route() {
|
||||
this.setHtmlTitle()
|
||||
},
|
||||
'theme.mode': function(val) {
|
||||
let closeMessage = this.$message.loading(`您選擇了主題模式 ${val}, 正在切換...`)
|
||||
themeUtil.changeThemeColor(this.theme.color, val).then(closeMessage)
|
||||
},
|
||||
'theme.color': function(val) {
|
||||
let closeMessage = this.$message.loading(`您選擇了主題色 ${val}, 正在切換...`)
|
||||
themeUtil.changeThemeColor(val, this.theme.mode).then(closeMessage)
|
||||
},
|
||||
'layout': function() {
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('setting', ['layout', 'theme', 'weekMode', 'lang'])
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('setting', ['setDevice']),
|
||||
setWeekModeTheme(weekMode) {
|
||||
if (weekMode) {
|
||||
document.body.classList.add('week-mode')
|
||||
} else {
|
||||
document.body.classList.remove('week-mode')
|
||||
}
|
||||
},
|
||||
setLanguage(lang) {
|
||||
this.$i18n.locale = lang
|
||||
switch (lang) {
|
||||
case 'TW':
|
||||
this.locale = require('ant-design-vue/es/locale-provider/zh_TW').default
|
||||
break
|
||||
case 'CN':
|
||||
this.locale = require('ant-design-vue/es/locale-provider/zh_CN').default
|
||||
break
|
||||
case 'US':
|
||||
default:
|
||||
this.locale = require('ant-design-vue/es/locale-provider/en_US').default
|
||||
break
|
||||
}
|
||||
},
|
||||
setHtmlTitle() {
|
||||
const route = this.$route
|
||||
const key = route.path === '/' ? 'home.name' : getI18nKey(route.matched[route.matched.length - 1].path)
|
||||
document.title = process.env.VUE_APP_NAME + ' | ' + this.$t(key)
|
||||
},
|
||||
popContainer() {
|
||||
return document.getElementById("popContainer")
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
#id{
|
||||
}
|
||||
|
||||
.edit-btn{
|
||||
color: #239DFA;
|
||||
}
|
||||
|
||||
.delete-btn{
|
||||
color: #F95838;
|
||||
}
|
||||
|
||||
.ant-drawer-header{
|
||||
background-color: #87e8de !important;
|
||||
.ant-drawer-title{
|
||||
color: #FFF !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.ant-drawer-content-wrapper {
|
||||
width: 60% !important;
|
||||
}
|
||||
@media only screen and (max-width: @screen-md) {
|
||||
.ant-drawer-content-wrapper {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@ -0,0 +1,349 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
@ -0,0 +1,222 @@
|
||||
@charset "utf-8";
|
||||
body,
|
||||
div,
|
||||
dl,
|
||||
footer,
|
||||
html,
|
||||
img,
|
||||
menu,
|
||||
p,
|
||||
span {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
body {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
background-color: #fffff6 !important;
|
||||
padding-bottom: 49px;
|
||||
}
|
||||
a,
|
||||
a:hover,
|
||||
a:visited {
|
||||
color: #999;
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
}
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
@-webkit-keyframes pop-hide {
|
||||
0% {
|
||||
-webkit-transform: scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
||||
2% {
|
||||
-webkit-transform: scale(1.1);
|
||||
opacity: 1;
|
||||
}
|
||||
6% {
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
90% {
|
||||
-webkit-transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(0.9);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes pop {
|
||||
0% {
|
||||
-webkit-transform: scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1.1);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes slideup {
|
||||
0% {
|
||||
-webkit-transform: translateY(100%);
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: translateY(-10%);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.left {
|
||||
float: left;
|
||||
}
|
||||
.rel {
|
||||
position: relative;
|
||||
}
|
||||
a,
|
||||
a:visited {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
}
|
||||
.text-icon {
|
||||
font-family: base_icon;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
font-style: normal;
|
||||
}
|
||||
.my-account {
|
||||
color: #333;
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
height: 6rem;
|
||||
}
|
||||
.account-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
.account-bg img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.my-account > img {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
.my-account .user-info {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 70px;
|
||||
box-sizing: border-box;
|
||||
padding-left: 1.9em;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
.my-account .uname {
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
margin-top: 0.1em;
|
||||
margin-bottom: 0.2em;
|
||||
text-shadow: 0.07em 0.07em #333;
|
||||
}
|
||||
.my-account .umoney {
|
||||
color: #fff;
|
||||
margin-bottom: 0.06em;
|
||||
text-shadow: 0.05em 0.05em #333;
|
||||
}
|
||||
.my-account .avatar_box {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
left: 1em;
|
||||
width: 5em;
|
||||
height: 5em;
|
||||
z-index: 1;
|
||||
border-radius: 100%;
|
||||
border: 2px solid #ffd44a;
|
||||
-moz-border-radius: 100%;
|
||||
-webkit-border-radius: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.my-account .avater {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.phone {
|
||||
width: 105px;
|
||||
float: left;
|
||||
z-index: 100;
|
||||
}
|
||||
.set {
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
right: 10px;
|
||||
top: 20px;
|
||||
z-index: 100;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
background-color: #fdaf00;
|
||||
text-align: center;
|
||||
margin-top: -7px;
|
||||
padding: 2px 2px;
|
||||
}
|
||||
.set a {
|
||||
color: #fff !important;
|
||||
}
|
||||
.dl01 {
|
||||
padding: 0 10px 10px;
|
||||
background-color: #fff;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.titleImg {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-right: 10px;
|
||||
margin-top: 15px;
|
||||
float: left;
|
||||
}
|
||||
.dl02 {
|
||||
padding: 0 10px;
|
||||
background-color: #fff;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.dl02 a .menu {
|
||||
border-bottom: 1px solid #ffe9b7;
|
||||
background: url(../images/right.png) no-repeat right center;
|
||||
background-size: 10px;
|
||||
}
|
||||
.dl02 a .menu div {
|
||||
padding-top: 16px;
|
||||
font-size: 15px;
|
||||
color: #666;
|
||||
}
|
||||
.dl02 a .menu div.left {
|
||||
float: left;
|
||||
width: 40%;
|
||||
}
|
||||
.dl02 a .menu div.right {
|
||||
float: left;
|
||||
text-align: right;
|
||||
width: 45%;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
After Width: | Height: | Size: 596 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1,25 @@
|
||||
import {loadRoutes, loadGuards, setAppOptions} from '@/utils/routerUtil'
|
||||
import {loadInterceptors} from '@/utils/request'
|
||||
import guards from '@/router/guards'
|
||||
import interceptors from '@/utils/axios-interceptors'
|
||||
|
||||
/**
|
||||
* 啟動引導方法
|
||||
* 應用啟動時需要執行的操作放在這裡
|
||||
* @param router 應用的路由例項
|
||||
* @param store 應用的 vuex.store 例項
|
||||
* @param i18n 應用的 vue-i18n 例項
|
||||
* @param i18n 應用的 message 例項
|
||||
*/
|
||||
function bootstrap({router, store, i18n, message}) {
|
||||
// 設定應用配置
|
||||
setAppOptions({router, store, i18n})
|
||||
// 載入 axios 攔截器
|
||||
loadInterceptors(interceptors, {router, store, i18n, message})
|
||||
// 載入路由
|
||||
loadRoutes()
|
||||
// 載入路由守衛
|
||||
loadGuards(guards, {router, store, i18n, message})
|
||||
}
|
||||
|
||||
export default bootstrap
|
||||
@ -0,0 +1,172 @@
|
||||
import {isDef, isRegExp, remove} from '@/utils/util'
|
||||
|
||||
const patternTypes = [String, RegExp, Array]
|
||||
|
||||
function matches (pattern, name) {
|
||||
if (Array.isArray(pattern)) {
|
||||
if (pattern.indexOf(name) > -1) {
|
||||
return true
|
||||
} else {
|
||||
for (let item of pattern) {
|
||||
if (isRegExp(item) && item.test(name)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
} else if (typeof pattern === 'string') {
|
||||
return pattern.split(',').indexOf(name) > -1
|
||||
} else if (isRegExp(pattern)) {
|
||||
return pattern.test(name)
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
return false
|
||||
}
|
||||
|
||||
function getComponentName (opts) {
|
||||
return opts && (opts.Ctor.options.name || opts.tag)
|
||||
}
|
||||
|
||||
function getComponentKey (vnode) {
|
||||
const {componentOptions, key} = vnode
|
||||
return key == null
|
||||
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
|
||||
: key + componentOptions.Ctor.cid
|
||||
}
|
||||
|
||||
function getFirstComponentChild (children) {
|
||||
if (Array.isArray(children)) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const c = children[i]
|
||||
if (isDef(c) && (isDef(c.componentOptions) || c.isAsyncPlaceholder)) {
|
||||
return c
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pruneCache (keepAliveInstance, filter) {
|
||||
const { cache, keys, _vnode } = keepAliveInstance
|
||||
for (const key in cache) {
|
||||
const cachedNode = cache[key]
|
||||
if (cachedNode) {
|
||||
const name = getComponentName(cachedNode.componentOptions)
|
||||
const componentKey = getComponentKey(cachedNode)
|
||||
if (name && !filter(name, componentKey)) {
|
||||
pruneCacheEntry(cache, key, keys, _vnode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pruneCacheEntry2(cache, key, keys) {
|
||||
const cached = cache[key]
|
||||
if (cached) {
|
||||
cached.componentInstance.$destroy()
|
||||
}
|
||||
cache[key] = null
|
||||
remove(keys, key)
|
||||
}
|
||||
|
||||
function pruneCacheEntry (cache, key, keys, current) {
|
||||
const cached = cache[key]
|
||||
if (cached && (!current || cached.tag !== current.tag)) {
|
||||
cached.componentInstance.$destroy()
|
||||
}
|
||||
cache[key] = null
|
||||
remove(keys, key)
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'AKeepAlive',
|
||||
abstract: true,
|
||||
model: {
|
||||
prop: 'clearCaches',
|
||||
event: 'clear',
|
||||
},
|
||||
props: {
|
||||
include: patternTypes,
|
||||
exclude: patternTypes,
|
||||
excludeKeys: patternTypes,
|
||||
max: [String, Number],
|
||||
clearCaches: Array
|
||||
},
|
||||
watch: {
|
||||
clearCaches: function(val) {
|
||||
if (val && val.length > 0) {
|
||||
const {cache, keys} = this
|
||||
val.forEach(key => {
|
||||
pruneCacheEntry2(cache, key, keys)
|
||||
})
|
||||
this.$emit('clear', [])
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.cache = Object.create(null)
|
||||
this.keys = []
|
||||
},
|
||||
|
||||
destroyed () {
|
||||
for (const key in this.cache) {
|
||||
pruneCacheEntry(this.cache, key, this.keys)
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.$watch('include', val => {
|
||||
pruneCache(this, (name) => matches(val, name))
|
||||
})
|
||||
this.$watch('exclude', val => {
|
||||
pruneCache(this, (name) => !matches(val, name))
|
||||
})
|
||||
this.$watch('excludeKeys', val => {
|
||||
pruneCache(this, (name, key) => !matches(val, key))
|
||||
})
|
||||
},
|
||||
|
||||
render () {
|
||||
const slot = this.$slots.default
|
||||
const vnode = getFirstComponentChild(slot)
|
||||
const componentOptions = vnode && vnode.componentOptions
|
||||
if (componentOptions) {
|
||||
// check pattern
|
||||
const name = getComponentName(componentOptions)
|
||||
const componentKey = getComponentKey(vnode)
|
||||
const { include, exclude, excludeKeys } = this
|
||||
if (
|
||||
// not included
|
||||
(include && (!name || !matches(include, name))) ||
|
||||
// excluded
|
||||
(exclude && name && matches(exclude, name)) ||
|
||||
(excludeKeys && componentKey && matches(excludeKeys, componentKey))
|
||||
) {
|
||||
return vnode
|
||||
}
|
||||
|
||||
const { cache, keys } = this
|
||||
const key = vnode.key == null
|
||||
// same constructor may get registered as different local components
|
||||
// so cid alone is not enough (#3269)
|
||||
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
|
||||
: vnode.key + componentOptions.Ctor.cid
|
||||
if (cache[key]) {
|
||||
vnode.componentInstance = cache[key].componentInstance
|
||||
// make current key freshest
|
||||
remove(keys, key)
|
||||
keys.push(key)
|
||||
} else {
|
||||
cache[key] = vnode
|
||||
keys.push(key)
|
||||
// prune oldest entry
|
||||
if (this.max && keys.length > parseInt(this.max)) {
|
||||
pruneCacheEntry(cache, keys[0], keys, this._vnode)
|
||||
}
|
||||
}
|
||||
|
||||
vnode.data.keepAlive = true
|
||||
}
|
||||
return vnode || (slot && slot[0])
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<a-card :loading="loading" :body-style="{padding: '20px 24px 8px'}" :bordered="false">
|
||||
<div class="chart-card-header">
|
||||
<div class="meta">
|
||||
<span class="chart-card-title">{{title}}</span>
|
||||
<span class="chart-card-action">
|
||||
<slot name="action"></slot>
|
||||
</span>
|
||||
</div>
|
||||
<div class="total"><span>{{total}}</span></div>
|
||||
</div>
|
||||
<div class="chart-card-content">
|
||||
<div class="content-fix">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-card-footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ChartCard',
|
||||
props: ['title', 'total', 'loading']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.chart-card-header{
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.chart-card-header .meta{
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
color: @text-color-second;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
.chart-card-action{
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.total {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0;
|
||||
font-size: 30px;
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
.chart-card-footer{
|
||||
border-top: 1px solid @border-color-base;
|
||||
padding-top: 9px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.chart-card-content{
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
height: 46px;
|
||||
width: 100%;
|
||||
}
|
||||
.chart-card-content .content-fix{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="bar">
|
||||
<h4>{{title}}</h4>
|
||||
<div class="chart">
|
||||
<v-chart :force-fit="true" height="312" :data="data" :padding="[24, 0, 0, 0]">
|
||||
<v-tooltip />
|
||||
<v-axis />
|
||||
<v-bar position="x*y"/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
const data = []
|
||||
for (let i = 0; i < 12; i += 1) {
|
||||
data.push({
|
||||
x: `${i + 1}月`,
|
||||
y: Math.floor(Math.random() * 1000) + 200
|
||||
})
|
||||
}
|
||||
const tooltip = [
|
||||
'x*y',
|
||||
(x, y) => ({
|
||||
name: x,
|
||||
value: y
|
||||
})
|
||||
]
|
||||
|
||||
const scale = [{
|
||||
dataKey: 'x',
|
||||
min: 2
|
||||
}, {
|
||||
dataKey: 'y',
|
||||
title: '时间',
|
||||
min: 1,
|
||||
max: 22
|
||||
}]
|
||||
export default {
|
||||
name: 'Bar',
|
||||
props: ['title'],
|
||||
data () {
|
||||
return {
|
||||
data,
|
||||
scale,
|
||||
tooltip
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.bar{
|
||||
position: relative;
|
||||
.chart{
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="mini-chart">
|
||||
<div class="chart-content" :style="{height: 46}">
|
||||
<v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 5, 18, 5]">
|
||||
<v-tooltip />
|
||||
<v-smooth-area position="x*y" />
|
||||
</v-chart>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {format} from 'date-fns'
|
||||
|
||||
const data = []
|
||||
const beginDay = new Date().getTime()
|
||||
|
||||
const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5]
|
||||
for (let i = 0; i < fakeY.length; i += 1) {
|
||||
data.push({
|
||||
x: format(new Date(beginDay + 1000 * 60 * 60 * 24 * i), 'yyyy-MM-dd'),
|
||||
y: fakeY[i]
|
||||
})
|
||||
}
|
||||
|
||||
const tooltip = [
|
||||
'x*y',
|
||||
(x, y) => ({
|
||||
name: x,
|
||||
value: y
|
||||
})
|
||||
]
|
||||
|
||||
const scale = [{
|
||||
dataKey: 'x',
|
||||
min: 2
|
||||
}, {
|
||||
dataKey: 'y',
|
||||
title: '时间',
|
||||
min: 1,
|
||||
max: 22
|
||||
}]
|
||||
|
||||
export default {
|
||||
name: 'MiniArea',
|
||||
data () {
|
||||
return {
|
||||
data,
|
||||
scale,
|
||||
tooltip,
|
||||
height: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mini-chart {
|
||||
position: relative;
|
||||
width: 100%
|
||||
}
|
||||
.mini-chart .chart-content{
|
||||
position: absolute;
|
||||
bottom: -28px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="mini-chart">
|
||||
<div class="chart-content" :style="{height: 46}">
|
||||
<v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 5, 18, 5]">
|
||||
<v-tooltip />
|
||||
<v-bar position="x*y" />
|
||||
</v-chart>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {format} from 'date-fns'
|
||||
|
||||
const data = []
|
||||
const beginDay = new Date().getTime()
|
||||
|
||||
const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5]
|
||||
for (let i = 0; i < fakeY.length; i += 1) {
|
||||
data.push({
|
||||
x: format(new Date(beginDay + 1000 * 60 * 60 * 24 * i), 'yyyy-MM-dd'),
|
||||
y: fakeY[i]
|
||||
})
|
||||
}
|
||||
|
||||
const tooltip = [
|
||||
'x*y',
|
||||
(x, y) => ({
|
||||
name: x,
|
||||
value: y
|
||||
})
|
||||
]
|
||||
|
||||
const scale = [{
|
||||
dataKey: 'x',
|
||||
min: 2
|
||||
}, {
|
||||
dataKey: 'y',
|
||||
title: '时间',
|
||||
min: 1,
|
||||
max: 22
|
||||
}]
|
||||
|
||||
export default {
|
||||
name: 'MiniBar',
|
||||
data () {
|
||||
return {
|
||||
data,
|
||||
scale,
|
||||
tooltip,
|
||||
height: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "index.less";
|
||||
</style>
|
||||
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="mini-progress">
|
||||
<a-tooltip :title="'目标值:' + target + '%'">
|
||||
<div class="target" :style="{left: target + '%'}">
|
||||
<span :style="{backgroundColor: color}" />
|
||||
<span :style="{backgroundColor: color}" />
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<div class="wrap">
|
||||
<div class="progress" :style="{backgroundColor: color, width: percent + '%', height: height}" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MiniProgress',
|
||||
props: ['target', 'color', 'percent', 'height']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.mini-progress {
|
||||
padding: 5px 0;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.wrap {
|
||||
background-color: @layout-bg-color;
|
||||
position: relative;
|
||||
}
|
||||
.progress {
|
||||
transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s;
|
||||
border-radius: 1px 0 0 1px;
|
||||
background-color: #13C2C2;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.target {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
span {
|
||||
border-radius: 100px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 4px;
|
||||
width: 2px;
|
||||
}
|
||||
span:last-child {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<v-chart :forceFit="true" height="400" :data="data" :padding="[20, 20, 95, 20]" :scale="scale">
|
||||
<v-tooltip />
|
||||
<v-axis :dataKey="axis1Opts.dataKey" :line="axis1Opts.line" :tickLine="axis1Opts.tickLine" :grid="axis1Opts.grid" />
|
||||
<v-axis :dataKey="axis2Opts.dataKey" :line="axis2Opts.line" :tickLine="axis2Opts.tickLine" :grid="axis2Opts.grid" />
|
||||
<v-legend dataKey="user" marker="circle" :offset="30" />
|
||||
<v-coord type="polar" radius="0.8" />
|
||||
<v-line position="item*score" color="user" :size="2" />
|
||||
<v-point position="item*score" color="user" :size="4" shape="circle" />
|
||||
</v-chart>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const DataSet = require('@antv/data-set')
|
||||
|
||||
const sourceData = [
|
||||
{item: '引用', a: 70, b: 30, c: 40},
|
||||
{item: '口碑', a: 60, b: 70, c: 40},
|
||||
{item: '产量', a: 50, b: 60, c: 40},
|
||||
{item: '贡献', a: 40, b: 50, c: 40},
|
||||
{item: '热度', a: 60, b: 70, c: 40},
|
||||
{item: '引用', a: 70, b: 50, c: 40}
|
||||
]
|
||||
|
||||
const dv = new DataSet.View().source(sourceData)
|
||||
dv.transform({
|
||||
type: 'fold',
|
||||
fields: ['a', 'b', 'c'],
|
||||
key: 'user',
|
||||
value: 'score'
|
||||
})
|
||||
|
||||
const scale = [{
|
||||
dataKey: 'score',
|
||||
min: 0,
|
||||
max: 80
|
||||
}]
|
||||
|
||||
const data = dv.rows
|
||||
|
||||
const axis1Opts = {
|
||||
dataKey: 'item',
|
||||
line: null,
|
||||
tickLine: null,
|
||||
grid: {
|
||||
lineStyle: {
|
||||
lineDash: null
|
||||
},
|
||||
hideFirstLine: false
|
||||
}
|
||||
}
|
||||
const axis2Opts = {
|
||||
dataKey: 'score',
|
||||
line: null,
|
||||
tickLine: null,
|
||||
grid: {
|
||||
type: 'polygon',
|
||||
lineStyle: {
|
||||
lineDash: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'Radar',
|
||||
data () {
|
||||
return {
|
||||
sourceData,
|
||||
data,
|
||||
axis1Opts,
|
||||
axis2Opts,
|
||||
scale
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="rank">
|
||||
<h4 class="title">{{title}}</h4>
|
||||
<ul class="list">
|
||||
<li :key="index" v-for="(item, index) in list">
|
||||
<span :class="index < 3 ? 'active' : null">{{index + 1}}</span>
|
||||
<span >{{item.name}}</span>
|
||||
<span >{{item.total}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'RankingList',
|
||||
props: ['title', 'list']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.rank{
|
||||
padding: 0 32px 32px 72px;
|
||||
.title{
|
||||
}
|
||||
.list{
|
||||
margin: 25px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
li {
|
||||
margin-top: 16px;
|
||||
span {
|
||||
color: @text-color-second;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
span:first-child {
|
||||
background-color: @layout-bg-color;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-right: 24px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
span.active {
|
||||
background-color: #314659 !important;
|
||||
color: @text-color-inverse !important;
|
||||
}
|
||||
span:last-child {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="chart-trend">
|
||||
{{term}}
|
||||
<span>{{rate}}%</span>
|
||||
<span :class="['chart-trend-icon', trend]" style=""><a-icon :type="'caret-' + trend" /></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Trend',
|
||||
props: {
|
||||
term: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
target: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
isIncrease: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
percent: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
scale: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 2
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
trend: this.isIncrease ? 'up' : 'down',
|
||||
rate: this.percent
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.trend = this.caulateTrend()
|
||||
this.rate = this.caulateRate()
|
||||
},
|
||||
methods: {
|
||||
caulateRate () {
|
||||
return (this.percent === null ? Math.abs(this.value - this.target) * 100 / this.target : this.percent).toFixed(this.scale)
|
||||
},
|
||||
caulateTrend () {
|
||||
let isIncrease = this.isIncrease === null ? this.value >= this.target : this.isIncrease
|
||||
return isIncrease ? 'up' : 'down'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart-trend{
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
.chart-trend-icon{
|
||||
font-size: 12px;
|
||||
&.up{
|
||||
color: @red-6;
|
||||
}
|
||||
&.down{
|
||||
color: @green-6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,9 @@
|
||||
.mini-chart{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.chart-content{
|
||||
position: absolute;
|
||||
bottom: -28px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div class="theme-color" :style="{backgroundColor: color}" @click="toggle">
|
||||
<a-icon v-if="sChecked" type="check" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const Group = {
|
||||
name: 'ColorCheckboxGroup',
|
||||
props: {
|
||||
defaultValues: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
values: [],
|
||||
options: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
colors () {
|
||||
let colors = []
|
||||
this.options.forEach(item => {
|
||||
if (item.sChecked) {
|
||||
colors.push(item.color)
|
||||
}
|
||||
})
|
||||
return colors
|
||||
}
|
||||
},
|
||||
provide () {
|
||||
return {
|
||||
groupContext: this
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
values(value) {
|
||||
this.$emit('change', value, this.colors)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (option) {
|
||||
if (!option.checked) {
|
||||
if (this.values.indexOf(option.value) > -1) {
|
||||
this.values = this.values.filter(item => item != option.value)
|
||||
}
|
||||
} else {
|
||||
if (!this.multiple) {
|
||||
this.values = [option.value]
|
||||
this.options.forEach(item => {
|
||||
if (item.value != option.value) {
|
||||
item.sChecked = false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.values.push(option.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
const clear = h('div', {attrs: {style: 'clear: both'}})
|
||||
return h(
|
||||
'div',
|
||||
{},
|
||||
[this.$slots.default, clear]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'ColorCheckbox',
|
||||
Group: Group,
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
sChecked: this.initChecked()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
inject: ['groupContext'],
|
||||
watch: {
|
||||
'sChecked': function () {
|
||||
const value = {
|
||||
value: this.value,
|
||||
color: this.color,
|
||||
checked: this.sChecked
|
||||
}
|
||||
this.$emit('change', value)
|
||||
const groupContext = this.groupContext
|
||||
if (groupContext) {
|
||||
groupContext.handleChange(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const groupContext = this.groupContext
|
||||
if (groupContext) {
|
||||
groupContext.options.push(this)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggle () {
|
||||
if (this.groupContext.multiple || !this.sChecked) {
|
||||
this.sChecked = !this.sChecked
|
||||
}
|
||||
},
|
||||
initChecked() {
|
||||
let groupContext = this.groupContext
|
||||
if (!groupContext) {
|
||||
return this.checked
|
||||
}else if (groupContext.multiple) {
|
||||
return groupContext.defaultValues.indexOf(this.value) > -1
|
||||
} else {
|
||||
return groupContext.defaultValues[0] == this.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.theme-color{
|
||||
float: left;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
margin-right: 8px;
|
||||
text-align: center;
|
||||
color: @base-bg-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,7 @@
|
||||
import ColorCheckbox from '@/components/checkbox/ColorCheckbox'
|
||||
import ImgCheckbox from '@/components/checkbox/ImgCheckbox'
|
||||
|
||||
export {
|
||||
ColorCheckbox,
|
||||
ImgCheckbox
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="exception-page">
|
||||
<div class="img">
|
||||
<img :src="config[type].img" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<h1>{{config[type].title}}</h1>
|
||||
<div class="desc">{{config[type].desc}}</div>
|
||||
<div class="action">
|
||||
<a-button type="primary" @click="backHome">返回首页</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Config from './typeConfig'
|
||||
|
||||
export default {
|
||||
name: 'ExceptionPage',
|
||||
props: ['type', 'homeRoute'],
|
||||
data () {
|
||||
return {
|
||||
config: Config
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
backHome() {
|
||||
if (this.homeRoute) {
|
||||
this.$router.push(this.homeRoute)
|
||||
}
|
||||
this.$emit('backHome', this.type)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.exception-page{
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: @base-bg-color;
|
||||
.img{
|
||||
padding-right: 52px;
|
||||
zoom: 1;
|
||||
img{
|
||||
max-width: 430px;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
h1{
|
||||
color: #434e59;
|
||||
font-size: 72px;
|
||||
font-weight: 600;
|
||||
line-height: 72px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.desc{
|
||||
color: @text-color-second;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -0,0 +1,19 @@
|
||||
const config = {
|
||||
403: {
|
||||
img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg',
|
||||
title: '403',
|
||||
desc: '抱歉,你無權訪問該頁面'
|
||||
},
|
||||
404: {
|
||||
img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg',
|
||||
title: '404',
|
||||
desc: '抱歉,你訪問的頁面不存在或仍在開發中'
|
||||
},
|
||||
500: {
|
||||
img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg',
|
||||
title: '500',
|
||||
desc: '抱歉,伺服器出錯了'
|
||||
}
|
||||
}
|
||||
|
||||
export default config
|
||||
@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<a-input
|
||||
:addon-after="addonAfter"
|
||||
:addon-before="addonBefore"
|
||||
:default-value="defaultValue"
|
||||
:disabled="disabled"
|
||||
:id="id"
|
||||
:max-length="maxLength"
|
||||
:prefix="prefix"
|
||||
:size="size"
|
||||
:suffix="suffix || lenSuffix"
|
||||
:type="type"
|
||||
:allow-clear="allowClear"
|
||||
v-model="sValue"
|
||||
:value="value"
|
||||
@change="onChange"
|
||||
@input="onInput"
|
||||
@pressEnter="onPressEnter"
|
||||
@keydown="onKeydown"
|
||||
>
|
||||
<template :slot="slot" v-for="slot in Object.keys($slots)">
|
||||
<slot :name="slot"></slot>
|
||||
</template>
|
||||
</a-input>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'IInput',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change.value'
|
||||
},
|
||||
props: ['addonAfter', 'addonBefore', 'defaultValue', 'disabled', 'id', 'maxLength', 'prefix', 'size', 'suffix', 'type', 'value', 'allowClear'],
|
||||
data() {
|
||||
return {
|
||||
sValue: this.value || this.defaultValue || ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.sValue = val
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lenSuffix() {
|
||||
return this.maxLength && `${(this.sValue + '').length}/${this.maxLength}`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(e) {
|
||||
this.$emit('change', e)
|
||||
this.$emit('change.value', e.target.value)
|
||||
},
|
||||
onInput(e) {
|
||||
this.$emit('input', e)
|
||||
},
|
||||
onPressEnter(e) {
|
||||
this.$emit('pressEnter', e)
|
||||
},
|
||||
onKeydown(e) {
|
||||
this.$emit('keydown', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<a-menu
|
||||
v-show="visible"
|
||||
class="contextmenu"
|
||||
:style="style"
|
||||
:selectedKeys="selectedKeys"
|
||||
@click="handleClick"
|
||||
>
|
||||
<a-menu-item :key="item.key" v-for="item in itemList">
|
||||
<a-icon v-if="item.icon" :type="item.icon" />
|
||||
<span>{{ item.text }}</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Contextmenu',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
itemList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
left: 0,
|
||||
top: 0,
|
||||
target: null,
|
||||
meta: null,
|
||||
selectedKeys: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style () {
|
||||
return {
|
||||
left: this.left + 'px',
|
||||
top: this.top + 'px'
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('click', this.closeMenu)
|
||||
window.addEventListener('contextmenu', this.setPosition)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('click', this.closeMenu)
|
||||
window.removeEventListener('contextmenu', this.setPosition)
|
||||
},
|
||||
methods: {
|
||||
closeMenu () {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
setPosition (e) {
|
||||
this.left = e.clientX
|
||||
this.top = e.clientY
|
||||
this.target = e.target
|
||||
this.meta = e.meta
|
||||
},
|
||||
handleClick ({ key }) {
|
||||
this.$emit('select', key, this.target, this.meta)
|
||||
this.closeMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.contextmenu{
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
border-radius: 4px;
|
||||
box-shadow: -4px 4px 16px 1px @shadow-color !important;
|
||||
}
|
||||
.ant-menu-item {
|
||||
margin: 0 !important // 菜单项之间的缝隙会影响点击
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<a-layout-sider :theme="sideTheme" :class="['side-menu', 'beauty-scroll', isMobile ? null : 'shadow']" width="200px" :collapsible="collapsible" v-model="collapsed" :trigger="null">
|
||||
<div :class="['logo', theme]">
|
||||
<router-link to="/dashboard/workplace">
|
||||
<img src="@/assets/images/logo.png">
|
||||
<h1>{{systemName}}</h1>
|
||||
</router-link>
|
||||
</div>
|
||||
<i-menu :theme="theme" :collapsed="collapsed" :options="menuData" @select="onSelect" class="menu"/>
|
||||
</a-layout-sider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IMenu from './menu'
|
||||
import {mapState} from 'vuex'
|
||||
export default {
|
||||
name: 'SideMenu',
|
||||
components: {IMenu},
|
||||
props: {
|
||||
collapsible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
menuData: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'dark'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sideTheme() {
|
||||
return this.theme == 'light' ? this.theme : 'dark'
|
||||
},
|
||||
...mapState('setting', ['isMobile', 'systemName'])
|
||||
},
|
||||
methods: {
|
||||
onSelect (obj) {
|
||||
this.$emit('menuSelect', obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "index";
|
||||
</style>
|
||||
@ -0,0 +1,38 @@
|
||||
.shadow{
|
||||
box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
|
||||
}
|
||||
.side-menu{
|
||||
min-height: 100vh;
|
||||
overflow-y: auto;
|
||||
z-index: 10;
|
||||
.logo{
|
||||
height: 64px;
|
||||
position: relative;
|
||||
line-height: 64px;
|
||||
padding-left: 24px;
|
||||
-webkit-transition: all .3s;
|
||||
transition: all .3s;
|
||||
overflow: hidden;
|
||||
background-color: @layout-trigger-background;
|
||||
&.light{
|
||||
background-color: #fff;
|
||||
h1{
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
h1{
|
||||
color: @menu-dark-highlight-color;
|
||||
font-size: 20px;
|
||||
margin: 0 0 0 12px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
img{
|
||||
width: 32px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
.menu{
|
||||
padding: 16px 0;
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div :class="['page-header', layout, pageWidth]">
|
||||
<div class="page-header-wide">
|
||||
<div class="breadcrumb">
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item :key="index" v-for="(item, index) in breadcrumb">
|
||||
<span>{{item}}</span>
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
export default {
|
||||
name: 'PageHeader',
|
||||
props: {
|
||||
title: {
|
||||
type: [String, Boolean],
|
||||
required: false
|
||||
},
|
||||
breadcrumb: {
|
||||
type: Array,
|
||||
required: false
|
||||
},
|
||||
logo: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
avatar: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState('setting', ['layout', 'showPageTitle', 'pageWidth'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "index";
|
||||
</style>
|
||||
@ -0,0 +1,40 @@
|
||||
.page-header{
|
||||
background: @base-bg-color;
|
||||
padding: 16px 24px;
|
||||
&.head.fixed{
|
||||
margin: auto;
|
||||
max-width: 1400px;
|
||||
}
|
||||
.page-header-wide{
|
||||
.breadcrumb{
|
||||
margin: 0px 5px;
|
||||
}
|
||||
.detail{
|
||||
display: flex;
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.avatar {
|
||||
margin:0 24px 0 0;
|
||||
}
|
||||
.main{
|
||||
width: 100%;
|
||||
.title{
|
||||
font-size: 20px;
|
||||
color: @title-color;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.content{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
color: @text-color-second;
|
||||
}
|
||||
.extra{
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="result">
|
||||
<div >
|
||||
<a-icon :class="[isSuccess ? 'success' : 'error' ,'icon']" :type="isSuccess ? 'check-circle' : 'close-circle'" />
|
||||
</div>
|
||||
<div class="title" v-if="title">{{title}}</div>
|
||||
<div class="desc" v-if="description">{{description}}</div>
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="action">
|
||||
<slot name="action"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Result',
|
||||
props: ['isSuccess', 'title', 'description']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.result{
|
||||
text-align: center;
|
||||
width: 72%;
|
||||
margin: 0 auto;
|
||||
.icon{
|
||||
font-size: 72px;
|
||||
line-height: 72px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.success {
|
||||
color: @success-color;
|
||||
}
|
||||
.error {
|
||||
color: @error-color;
|
||||
}
|
||||
.title{
|
||||
font-size: 24px;
|
||||
color: @title-color;
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.desc{
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: @text-color-second;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.content{
|
||||
background-color: @background-color-light;
|
||||
padding: 24px 40px;
|
||||
border-radius: 2px;
|
||||
text-align: left;
|
||||
}
|
||||
.action{
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="setting-item">
|
||||
<h3 v-if="title" class="title">{{title}}</h3>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SettingItem',
|
||||
props: ['title']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.setting-item{
|
||||
margin-bottom: 24px;
|
||||
.title{
|
||||
font-size: 14px;
|
||||
color: @title-color;
|
||||
line-height: 22px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,117 @@
|
||||
module.exports = {
|
||||
messages: {
|
||||
CN: {
|
||||
theme: {
|
||||
title: '整体风格设置',
|
||||
light: '亮色菜单风格',
|
||||
dark: '暗色菜单风格',
|
||||
night: '深夜模式',
|
||||
color: '主题色'
|
||||
},
|
||||
navigate: {
|
||||
title: '导航设置',
|
||||
side: '侧边导航',
|
||||
head: '顶部导航',
|
||||
mix: '混合导航',
|
||||
content: {
|
||||
title: '内容区域宽度',
|
||||
fluid: '流式',
|
||||
fixed: '定宽'
|
||||
},
|
||||
fixedHeader: '固定Header',
|
||||
fixedSideBar: '固定侧边栏',
|
||||
},
|
||||
other: {
|
||||
title: '其他设置',
|
||||
weekMode: '色弱模式',
|
||||
multiPages: '多页签模式',
|
||||
hideSetting: '隐藏设置抽屉'
|
||||
},
|
||||
animate: {
|
||||
title: '页面切换动画',
|
||||
disable: '禁用动画',
|
||||
effect: '动画效果',
|
||||
direction: '动画方向'
|
||||
},
|
||||
alert: '拷贝配置后,直接覆盖文件 src/config/config.js 中的全部内容,然后重启即可。(注意:仅会拷贝与默认配置不同的项)',
|
||||
copy: '拷贝配置',
|
||||
save: '保存配置',
|
||||
reset: '重置配置',
|
||||
},
|
||||
TW: {
|
||||
theme: {
|
||||
title: '整體風格設置',
|
||||
light: '亮色菜單風格',
|
||||
dark: '暗色菜單風格',
|
||||
night: '深夜模式',
|
||||
color: '主題色'
|
||||
},
|
||||
navigate: {
|
||||
title: '導航設置',
|
||||
side: '側邊導航',
|
||||
head: '頂部導航',
|
||||
content: {
|
||||
title: '內容區域寬度',
|
||||
fluid: '流式',
|
||||
fixed: '定寬'
|
||||
},
|
||||
fixedHeader: '固定Header',
|
||||
fixedSideBar: '固定側邊欄',
|
||||
},
|
||||
other: {
|
||||
title: '其他設置',
|
||||
weekMode: '色弱模式',
|
||||
multiPages: '多頁簽模式',
|
||||
hideSetting: '隱藏設置抽屜'
|
||||
},
|
||||
animate: {
|
||||
title: '頁面切換動畫',
|
||||
disable: '禁用動畫',
|
||||
effect: '動畫效果',
|
||||
direction: '動畫方向'
|
||||
},
|
||||
alert: '拷貝配置后,直接覆蓋文件 src/config/config.js 中的全部內容,然後重啟即可。(注意:僅會拷貝與默認配置不同的項)',
|
||||
copy: '拷貝配置',
|
||||
save: '保存配置',
|
||||
reset: '重置配置',
|
||||
},
|
||||
US: {
|
||||
theme: {
|
||||
title: 'Page Style Setting',
|
||||
light: 'Light Style',
|
||||
dark: 'Dark Style',
|
||||
night: 'Night Style',
|
||||
color: 'Theme Color'
|
||||
},
|
||||
navigate: {
|
||||
title: 'Navigation Mode',
|
||||
side: 'Side Menu Layout',
|
||||
head: 'Top Menu Layout',
|
||||
mix: 'Mix Menu Layout',
|
||||
content: {
|
||||
title: 'Content Width',
|
||||
fluid: 'Fluid',
|
||||
fixed: 'Fixed'
|
||||
},
|
||||
fixedHeader: 'Fixed Header',
|
||||
fixedSideBar: 'Fixed SideBar',
|
||||
},
|
||||
other: {
|
||||
title: 'Other Setting',
|
||||
weekMode: 'Week Mode',
|
||||
multiPages: 'Multi Pages',
|
||||
hideSetting: 'Hide Setting Drawer'
|
||||
},
|
||||
animate: {
|
||||
title: 'Page Toggle Animation',
|
||||
disable: 'Disable',
|
||||
effect: 'Effect',
|
||||
direction: 'Direction'
|
||||
},
|
||||
alert: 'After copying the configuration code, directly cover all contents in the file src/config/config.js, then restart the server. (Note: only items that are different from the default configuration will be copied)',
|
||||
copy: 'Copy Setting',
|
||||
save: 'Save',
|
||||
reset: 'Reset',
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div class="standard-table">
|
||||
<div class="alert">
|
||||
<a-alert type="info" :show-icon="true" v-if="selectedRows">
|
||||
<div class="message" slot="message">
|
||||
已選擇 <a>{{selectedRows.length}}</a> 項 <a class="clear" @click="onClear">清空</a>
|
||||
<template v-for="(item, index) in needTotalList" >
|
||||
<div v-if="item.needTotal" :key="index">
|
||||
{{item.title}}總計
|
||||
<a>{{item.customRender ? item.customRender(item.total) : item.total}}</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</a-alert>
|
||||
</div>
|
||||
<a-table
|
||||
:bordered="bordered"
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:rowKey="rowKey"
|
||||
:pagination="pagination"
|
||||
:expandedRowKeys="expandedRowKeys"
|
||||
:expandedRowRender="expandedRowRender"
|
||||
@change="onChange"
|
||||
:rowSelection="selectedRows ? {selectedRowKeys: selectedRowKeys, onChange: updateSelect} : undefined"
|
||||
:scroll="scroll"
|
||||
>
|
||||
<template slot-scope="text, record, index" :slot="slot" v-for="slot in Object.keys($scopedSlots).filter(key => key !== 'expandedRowRender') ">
|
||||
<slot :name="slot" v-bind="{text, record, index}"></slot>
|
||||
</template>
|
||||
<template :slot="slot" v-for="slot in Object.keys($slots)">
|
||||
<slot :name="slot"></slot>
|
||||
</template>
|
||||
<template slot-scope="record, index, indent, expanded" :slot="$scopedSlots.expandedRowRender ? 'expandedRowRender' : ''">
|
||||
<slot v-bind="{record, index, indent, expanded}" :name="$scopedSlots.expandedRowRender ? 'expandedRowRender' : ''"></slot>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'StandardTable',
|
||||
props: {
|
||||
// bordered: Boolean,
|
||||
loading: [Boolean, Object],
|
||||
columns: Array,
|
||||
dataSource: Array,
|
||||
rowKey: {
|
||||
type: [String, Function],
|
||||
default: 'key'
|
||||
},
|
||||
scroll: Object,
|
||||
pagination: [Boolean, Object],
|
||||
selectedRows: Array,
|
||||
expandedRowKeys: Array,
|
||||
expandedRowRender: Function
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
bordered: true,
|
||||
needTotalList: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateSelect (selectedRowKeys, selectedRows) {
|
||||
this.$emit('update:selectedRows', selectedRows)
|
||||
this.$emit('selectedRowChange', selectedRowKeys, selectedRows)
|
||||
},
|
||||
initTotalList (columns) {
|
||||
const totalList = columns.filter(item => item.needTotal)
|
||||
.map(item => {
|
||||
return {
|
||||
...item,
|
||||
total: 0
|
||||
}
|
||||
})
|
||||
return totalList
|
||||
},
|
||||
onClear() {
|
||||
this.updateSelect([], [])
|
||||
this.$emit('clear')
|
||||
},
|
||||
onChange(pagination, filters, sorter, {currentDataSource}) {
|
||||
this.$emit('change', pagination, filters, sorter, {currentDataSource})
|
||||
},
|
||||
onShowSizeChange(current, size){
|
||||
this.$emit('showSizeChange', current, size)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.needTotalList = this.initTotalList(this.columns)
|
||||
},
|
||||
watch: {
|
||||
selectedRows (selectedRows) {
|
||||
this.needTotalList = this.needTotalList.map(item => {
|
||||
return {
|
||||
...item,
|
||||
total: selectedRows.reduce((sum, val) => {
|
||||
let v
|
||||
try{
|
||||
v = val[item.dataIndex] ? val[item.dataIndex] : eval(`val.${item.dataIndex}`);
|
||||
}catch(_){
|
||||
v = val[item.dataIndex];
|
||||
}
|
||||
v = !isNaN(parseFloat(v)) ? parseFloat(v) : 0;
|
||||
return sum + v
|
||||
}, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedRowKeys() {
|
||||
return this.selectedRows.map(record => {
|
||||
return (typeof this.rowKey === 'function') ? this.rowKey(record) : record[this.rowKey]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.standard-table{
|
||||
.alert{
|
||||
margin-bottom: 16px;
|
||||
.message{
|
||||
a{
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
.clear{
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination{
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="action-columns" ref="root">
|
||||
<a-popover v-model="visible" placement="bottomRight" trigger="click" :get-popup-container="() => $refs.root">
|
||||
<div slot="title">
|
||||
<a-checkbox :indeterminate="indeterminate" :checked="checkAll" @change="onCheckAllChange" class="check-all" />列展示
|
||||
<a-button @click="resetColumns" style="float: right" type="link" size="small">重置</a-button>
|
||||
</div>
|
||||
<a-list style="width: 100%" size="small" :key="i" v-for="(col, i) in columns" slot="content">
|
||||
<a-list-item>
|
||||
<a-checkbox v-model="col.visible" @change="e => onCheckChange(e, col)"/>
|
||||
<template v-if="col.title">
|
||||
{{col.title}}:
|
||||
</template>
|
||||
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||
<template slot="actions">
|
||||
<a-tooltip title="固定在列头" :mouseEnterDelay="0.5" :get-popup-container="() => $refs.root">
|
||||
<a-icon :class="['left', {active: col.fixed === 'left'}]" @click="fixColumn('left', col)" type="vertical-align-top" />
|
||||
</a-tooltip>
|
||||
<a-tooltip title="固定在列尾" :mouseEnterDelay="0.5" :get-popup-container="() => $refs.root">
|
||||
<a-icon :class="['right', {active: col.fixed === 'right'}]" @click="fixColumn('right', col)" type="vertical-align-bottom" />
|
||||
</a-tooltip>
|
||||
<a-tooltip title="添加搜索" :mouseEnterDelay="0.5" :get-popup-container="() => $refs.root">
|
||||
<a-icon :class="{active: col.searchAble}" @click="setSearch(col)" type="search" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
<a-icon class="action" type="setting" />
|
||||
</a-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
|
||||
export default {
|
||||
name: 'ActionColumns',
|
||||
props: ['columns', 'visibleColumns'],
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
indeterminate: false,
|
||||
checkAll: true,
|
||||
checkedCounts: this.columns.length,
|
||||
backColumns: cloneDeep(this.columns)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
checkedCounts(val) {
|
||||
this.checkAll = val === this.columns.length
|
||||
this.indeterminate = val > 0 && val < this.columns.length
|
||||
},
|
||||
columns(newVal, oldVal) {
|
||||
if (newVal != oldVal) {
|
||||
this.checkedCounts = newVal.length
|
||||
this.formatColumns(newVal)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.formatColumns(this.columns)
|
||||
},
|
||||
methods: {
|
||||
onCheckChange(e, col) {
|
||||
if (!col.visible) {
|
||||
this.checkedCounts -= 1
|
||||
} else {
|
||||
this.checkedCounts += 1
|
||||
}
|
||||
},
|
||||
fixColumn(fixed, col) {
|
||||
if (fixed !== col.fixed) {
|
||||
this.$set(col, 'fixed', fixed)
|
||||
} else {
|
||||
this.$set(col, 'fixed', undefined)
|
||||
}
|
||||
},
|
||||
setSearch(col) {
|
||||
this.$set(col, 'searchAble', !col.searchAble)
|
||||
if (!col.searchAble && col.search) {
|
||||
this.resetSearch(col)
|
||||
}
|
||||
},
|
||||
resetSearch(col) {
|
||||
// col.search.value = col.dataType === 'boolean' ? false : undefined
|
||||
col.search.value = undefined
|
||||
col.search.backup = undefined
|
||||
},
|
||||
resetColumns() {
|
||||
const {columns, backColumns} = this
|
||||
let counts = columns.length
|
||||
backColumns.forEach((back, index) => {
|
||||
const column = columns[index]
|
||||
column.visible = back.visible === undefined || back.visible
|
||||
if (!column.visible) {
|
||||
counts -= 1
|
||||
}
|
||||
if (back.fixed !== undefined) {
|
||||
column.fixed = back.fixed
|
||||
} else {
|
||||
this.$set(column, 'fixed', undefined)
|
||||
}
|
||||
this.$set(column, 'searchAble', back.searchAble)
|
||||
// column.searchAble = back.searchAble
|
||||
this.resetSearch(column)
|
||||
})
|
||||
this.checkedCounts = counts
|
||||
this.visible = false
|
||||
this.$emit('reset', this.getConditions(columns))
|
||||
},
|
||||
onCheckAllChange(e) {
|
||||
if (e.target.checked) {
|
||||
this.checkedCounts = this.columns.length
|
||||
this.columns.forEach(col => col.visible = true)
|
||||
} else {
|
||||
this.checkedCounts = 0
|
||||
this.columns.forEach(col => col.visible = false)
|
||||
}
|
||||
},
|
||||
getConditions(columns) {
|
||||
const conditions = {}
|
||||
columns.filter(item => item.search.value !== undefined && item.search.value !== '' && item.search.value !== null)
|
||||
.forEach(col => {
|
||||
conditions[col.dataIndex] = col.search.value
|
||||
})
|
||||
return conditions
|
||||
},
|
||||
formatColumns(columns) {
|
||||
for (let col of columns) {
|
||||
if (col.visible === undefined) {
|
||||
this.$set(col, 'visible', true)
|
||||
}
|
||||
if (!col.visible) {
|
||||
this.checkedCounts -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.action-columns{
|
||||
display: inline-block;
|
||||
.check-all{
|
||||
margin-right: 8px;
|
||||
}
|
||||
.left,.right{
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.active{
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="action-size" ref="root">
|
||||
<a-tooltip title="密度">
|
||||
<a-dropdown placement="bottomCenter" :trigger="['click']" :get-popup-container="() => $refs.root">
|
||||
<a-icon class="action" type="column-height" />
|
||||
<a-menu :selected-keys="[value]" slot="overlay" @click="onClick">
|
||||
<a-menu-item key="default">
|
||||
默认
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
中等
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
紧密
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ActionSize',
|
||||
props: ['value'],
|
||||
inject: ['table'],
|
||||
data() {
|
||||
return {
|
||||
selectedKeys: ['middle']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick({key}) {
|
||||
this.$emit('input', key)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.action-size{
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div ref="table" :id="id" class="advanced-table">
|
||||
<a-spin :spinning="loading">
|
||||
<div :class="['header-bar', size]">
|
||||
<div class="title">
|
||||
<template v-if="title">{{title}}</template>
|
||||
<slot v-else-if="$slots.title" name="title"></slot>
|
||||
<template v-else>高级表格</template>
|
||||
</div>
|
||||
<div class="search">
|
||||
<search-area :format-conditions="formatConditions" @change="onSearchChange" :columns="columns" >
|
||||
<template :slot="slot" v-for="slot in slots">
|
||||
<slot :name="slot"></slot>
|
||||
</template>
|
||||
</search-area>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a-tooltip title="刷新">
|
||||
<a-icon @click="refresh" class="action" :type="loading ? 'loading' : 'reload'" />
|
||||
</a-tooltip>
|
||||
<action-size v-model="sSize" class="action" />
|
||||
<a-tooltip title="列配置">
|
||||
<action-columns :columns="columns" @reset="onColumnsReset" class="action">
|
||||
<template :slot="slot" v-for="slot in slots">
|
||||
<slot :name="slot"></slot>
|
||||
</template>
|
||||
</action-columns>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="全屏">
|
||||
<a-icon @click="toggleScreen" class="action" :type="fullScreen ? 'fullscreen-exit' : 'fullscreen'" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<a-table
|
||||
v-bind="{...$props, columns: visibleColumns, title: undefined, loading: false}"
|
||||
:size="sSize"
|
||||
@expandedRowsChange="onExpandedRowsChange"
|
||||
@change="onChange"
|
||||
@expand="onExpand"
|
||||
>
|
||||
<template slot-scope="text, record, index" :slot="slot" v-for="slot in scopedSlots ">
|
||||
<slot :name="slot" v-bind="{text, record, index}"></slot>
|
||||
</template>
|
||||
<template :slot="slot" v-for="slot in slots">
|
||||
<slot :name="slot"></slot>
|
||||
</template>
|
||||
<template slot-scope="record, index, indent, expanded" :slot="$scopedSlots.expandedRowRender ? 'expandedRowRender' : ''">
|
||||
<slot v-bind="{record, index, indent, expanded}" :name="$scopedSlots.expandedRowRender ? 'expandedRowRender' : ''"></slot>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActionSize from '@/components/table/advance/ActionSize'
|
||||
import ActionColumns from '@/components/table/advance/ActionColumns'
|
||||
import SearchArea from '@/components/table/advance/SearchArea'
|
||||
export default {
|
||||
name: 'AdvanceTable',
|
||||
components: {SearchArea, ActionColumns, ActionSize},
|
||||
props: {
|
||||
tableLayout: String,
|
||||
bordered: Boolean,
|
||||
childrenColumnName: {type: String, default: 'children'},
|
||||
columns: Array,
|
||||
components: Object,
|
||||
dataSource: Array,
|
||||
defaultExpandAllRows: Array[String],
|
||||
expandedRowKeys: Array[String],
|
||||
expandedRowRender: Function,
|
||||
expandIcon: Function,
|
||||
expandRowByClick: Boolean,
|
||||
expandIconColumnIndex: Number,
|
||||
footer: Function,
|
||||
indentSize: Number,
|
||||
loading: Boolean,
|
||||
locale: Object,
|
||||
pagination: [Object, Boolean],
|
||||
rowClassName: Function,
|
||||
rowKey: [String, Function],
|
||||
rowSelection: Object,
|
||||
scroll: Object,
|
||||
showHeader: {type: Boolean, default: true},
|
||||
size: String,
|
||||
title: String,
|
||||
customHeaderRow: Function,
|
||||
customRow: Function,
|
||||
getPopupContainer: Function,
|
||||
transformCellText: Function,
|
||||
formatConditions: Boolean
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
table: this
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: `${new Date().getTime()}-${Math.floor(Math.random() * 10)}`,
|
||||
sSize: this.size || 'default',
|
||||
fullScreen: false,
|
||||
conditions: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
slots() {
|
||||
return Object.keys(this.$slots).filter(slot => slot !== 'title')
|
||||
},
|
||||
scopedSlots() {
|
||||
return Object.keys(this.$scopedSlots).filter(slot => slot !== 'expandedRowRender' && slot !== 'title')
|
||||
},
|
||||
visibleColumns(){
|
||||
return this.columns.filter(col => col.visible)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.addListener()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.removeListener()
|
||||
},
|
||||
methods: {
|
||||
refresh() {
|
||||
this.$emit('refresh', this.conditions)
|
||||
},
|
||||
onSearchChange(conditions, searchOptions) {
|
||||
this.conditions = conditions
|
||||
this.$emit('search', conditions, searchOptions)
|
||||
},
|
||||
toggleScreen() {
|
||||
if (this.fullScreen) {
|
||||
this.outFullScreen()
|
||||
} else {
|
||||
this.inFullScreen()
|
||||
}
|
||||
},
|
||||
inFullScreen() {
|
||||
const el = this.$refs.table
|
||||
el.classList.add('beauty-scroll')
|
||||
if (el.requestFullscreen) {
|
||||
el.requestFullscreen()
|
||||
return true
|
||||
} else if (el.webkitRequestFullScreen) {
|
||||
el.webkitRequestFullScreen()
|
||||
return true
|
||||
} else if (el.mozRequestFullScreen) {
|
||||
el.mozRequestFullScreen()
|
||||
return true
|
||||
} else if (el.msRequestFullscreen) {
|
||||
el.msRequestFullscreen()
|
||||
return true
|
||||
}
|
||||
this.$message.warn('对不起,您的浏览器不支持全屏模式')
|
||||
el.classList.remove('beauty-scroll')
|
||||
return false
|
||||
},
|
||||
outFullScreen() {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
} else if (document.webkitCancelFullScreen) {
|
||||
document.webkitCancelFullScreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen()
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen()
|
||||
}
|
||||
this.$refs.table.classList.remove('beauty-scroll')
|
||||
},
|
||||
onColumnsReset(conditions) {
|
||||
this.$emit('reset', conditions)
|
||||
},
|
||||
onExpandedRowsChange(expandedRows) {
|
||||
this.$emit('expandedRowsChange', expandedRows)
|
||||
},
|
||||
onChange(pagination, filters, sorter, options) {
|
||||
this.$emit('change', pagination, filters, sorter, options)
|
||||
},
|
||||
onExpand(expanded, record) {
|
||||
this.$emit('expand', expanded, record)
|
||||
},
|
||||
addListener() {
|
||||
document.addEventListener('fullscreenchange', this.fullScreenListener)
|
||||
document.addEventListener('webkitfullscreenchange', this.fullScreenListener)
|
||||
document.addEventListener('mozfullscreenchange', this.fullScreenListener)
|
||||
document.addEventListener('msfullscreenchange', this.fullScreenListener)
|
||||
},
|
||||
removeListener() {
|
||||
document.removeEventListener('fullscreenchange', this.fullScreenListener)
|
||||
document.removeEventListener('webkitfullscreenchange', this.fullScreenListener)
|
||||
document.removeEventListener('mozfullscreenchange', this.fullScreenListener)
|
||||
document.removeEventListener('msfullscreenchange', this.fullScreenListener)
|
||||
},
|
||||
fullScreenListener(e) {
|
||||
if (e.target.id === this.id) {
|
||||
this.fullScreen = !this.fullScreen
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.advanced-table{
|
||||
overflow-y: auto;
|
||||
background-color: @component-background;
|
||||
.header-bar{
|
||||
padding: 16px 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
&.middle{
|
||||
padding: 12px 16px;
|
||||
}
|
||||
&.small{
|
||||
padding: 8px 12px;
|
||||
border: 1px solid @border-color;
|
||||
border-bottom: 0;
|
||||
.title{
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.title{
|
||||
transition: all 0.3s;
|
||||
font-size: 18px;
|
||||
color: @title-color;
|
||||
font-weight: 700;
|
||||
}
|
||||
.search{
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
margin: 0 24px;
|
||||
}
|
||||
.actions{
|
||||
text-align: right;
|
||||
font-size: 17px;
|
||||
color: @text-color;
|
||||
.action{
|
||||
margin: 0 8px;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<div class="search-area" ref="root">
|
||||
<div class="select-root" ref="selectRoot"></div>
|
||||
<div class="search-item" :key="index" v-for="(col, index) in searchCols">
|
||||
<div v-if="col.dataType === 'boolean'" :class="['title', {active: col.search.value !== undefined}]">
|
||||
<template v-if="col.title">
|
||||
{{col.title}}:
|
||||
</template>
|
||||
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||
<a-switch @change="onSwitchChange(col)" class="switch" v-model="col.search.value" size="small"
|
||||
:checked-children="(col.search.switchOptions && col.search.switchOptions.checkedText) || '是'"
|
||||
:un-checked-children="(col.search.switchOptions && col.search.switchOptions.uncheckedText) || '否'"
|
||||
/>
|
||||
<a-icon v-if="col.search.value !== undefined" class="close" @click="e => onCloseClick(e, col)" type="close-circle" theme="filled" />
|
||||
</div>
|
||||
<div v-else-if="col.dataType === 'time'" :class="['title', {active: col.search.value}]">
|
||||
<template v-if="col.title">
|
||||
{{col.title}}:
|
||||
</template>
|
||||
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||
<a-time-picker :format="col.search.format" v-model="col.search.value" placeholder="选择时间" @change="(time, timeStr) => onCalendarChange(time, timeStr, col)" @openChange="open => onCalendarOpenChange(open, col)" class="time-picker" size="small" :get-popup-container="() => $refs.root"/>
|
||||
</div>
|
||||
<div v-else-if="col.dataType === 'date'" :class="['title', {active: col.search.value}]">
|
||||
<template v-if="col.title">
|
||||
{{col.title}}:
|
||||
</template>
|
||||
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||
<a-date-picker :format="col.search.format" v-model="col.search.value" @change="onDateChange(col)" class="date-picker" size="small" :getCalendarContainer="() => $refs.root"/>
|
||||
</div>
|
||||
<div v-else-if="col.dataType === 'datetime'" class="title datetime active">
|
||||
<template v-if="col.title">
|
||||
{{col.title}}:
|
||||
</template>
|
||||
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||
<a-date-picker :format="col.search.format" v-model="col.search.value" @change="(date, dateStr) => onCalendarChange(date, dateStr, col)" @openChange="open => onCalendarOpenChange(open, col)" class="datetime-picker" size="small" show-time :getCalendarContainer="() => $refs.root"/>
|
||||
</div>
|
||||
<div v-else-if="col.dataType === 'select'" :class="['title', {active: col.search.value !== undefined}]">
|
||||
<template v-if="col.title">
|
||||
{{col.title}}:
|
||||
</template>
|
||||
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||
<a-select :allowClear="true" :options="col.search.selectOptions" v-model="col.search.value" placeholder="请选择..." @change="onSelectChange(col)" class="select" slot="content" size="small" :get-popup-container="() => $refs.selectRoot">
|
||||
</a-select>
|
||||
</div>
|
||||
<div v-else :class="['title', {active: col.search.value}]">
|
||||
<a-popover @visibleChange="onVisibleChange(col, index)" v-model="col.search.visible" placement="bottom" :trigger="['click']" :get-popup-container="() => $refs.root">
|
||||
<template v-if="col.title">
|
||||
{{col.title}}
|
||||
</template>
|
||||
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||
<div class="value " v-if="col.search.value">: {{col.search.format && typeof col.search.format === 'function' ? col.search.format(col.search.value) : col.search.value}}</div>
|
||||
<a-icon v-if="!col.search.value" class="icon-down" type="down"/>
|
||||
<div class="operations" slot="content">
|
||||
<a-button @click="onCancel(col)" class="btn" size="small" type="link">取消</a-button>
|
||||
<a-button @click="onConfirm(col)" class="btn" size="small" type="primary">确认</a-button>
|
||||
</div>
|
||||
<div class="search-overlay" slot="title">
|
||||
<a-input :id="`${searchIdPrefix}${index}`" :allow-clear="true" @keyup.esc="onCancel(col)" @keyup.enter="onConfirm(col)" v-model="col.search.value" size="small" />
|
||||
</div>
|
||||
</a-popover>
|
||||
<a-icon v-if="col.search.value" @click="e => onCloseClick(e, col)" class="close" type="close-circle" theme="filled"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import fastEqual from 'fast-deep-equal'
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
name: 'SearchArea',
|
||||
props: ['columns', 'formatConditions'],
|
||||
inject: ['table'],
|
||||
created() {
|
||||
this.formatColumns(this.columns)
|
||||
},
|
||||
watch: {
|
||||
columns(newVal, oldVal) {
|
||||
if (newVal != oldVal) {
|
||||
this.formatColumns(newVal)
|
||||
}
|
||||
},
|
||||
searchCols(newVal, oldVal) {
|
||||
if (newVal.length != oldVal.length) {
|
||||
const newConditions = this.getConditions(newVal)
|
||||
const newSearchOptions = this.getSearchOptions(newVal)
|
||||
if (!fastEqual(newConditions, this.conditions)) {
|
||||
this.conditions = newConditions
|
||||
this.searchOptions = newSearchOptions
|
||||
this.$emit('change', this.conditions, this.searchOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
conditions: {},
|
||||
searchOptions: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
searchCols() {
|
||||
return this.columns.filter(item => item.searchAble)
|
||||
},
|
||||
searchIdPrefix() {
|
||||
return this.table.id + '-ipt-'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onCloseClick(e, col) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
col.search.value = undefined
|
||||
const {backup, value} = col.search
|
||||
if (backup !== value) {
|
||||
this.backupAndEmitChange(col)
|
||||
}
|
||||
},
|
||||
onCancel(col) {
|
||||
col.search.value = col.search.backup
|
||||
col.search.visible = false
|
||||
},
|
||||
onConfirm(col) {
|
||||
const {backup, value} = col.search
|
||||
col.search.visible = false
|
||||
if (backup !== value) {
|
||||
this.backupAndEmitChange(col)
|
||||
}
|
||||
},
|
||||
onSwitchChange(col) {
|
||||
const {backup, value} = col.search
|
||||
if (backup !== value) {
|
||||
this.backupAndEmitChange(col)
|
||||
}
|
||||
},
|
||||
onSelectChange(col) {
|
||||
this.backupAndEmitChange(col)
|
||||
},
|
||||
onCalendarOpenChange(open, col) {
|
||||
col.search.visible = open
|
||||
const {momentEqual, backupAndEmitChange} = this
|
||||
const {value, backup, format} = col.search
|
||||
if (!open && !momentEqual(value, backup, format)) {
|
||||
backupAndEmitChange(col, moment(value))
|
||||
}
|
||||
},
|
||||
onCalendarChange(date, dateStr, col) {
|
||||
const {momentEqual, backupAndEmitChange} = this
|
||||
const {value, backup, format} = col.search
|
||||
if (!col.search.visible && !momentEqual(value, backup, format)) {
|
||||
backupAndEmitChange(col, moment(value))
|
||||
}
|
||||
},
|
||||
onDateChange(col) {
|
||||
const {momentEqual, backupAndEmitChange} = this
|
||||
const {value, backup, format} = col.search
|
||||
if (!momentEqual(value, backup, format)) {
|
||||
backupAndEmitChange(col, moment(value))
|
||||
}
|
||||
},
|
||||
getFormat(col) {
|
||||
if (col.search && col.search.format) {
|
||||
return col.search.format
|
||||
}
|
||||
const dataType = col.dataType
|
||||
switch(dataType) {
|
||||
case 'time': return 'HH:mm:ss'
|
||||
case 'date': return 'YYYY-MM-DD'
|
||||
case 'datetime': return 'YYYY-MM-DD HH:mm:ss'
|
||||
default: return undefined
|
||||
}
|
||||
},
|
||||
backupAndEmitChange(col, backValue = col.search.value) {
|
||||
const {getConditions, getSearchOptions} = this
|
||||
col.search.backup = backValue
|
||||
this.conditions = getConditions(this.searchCols)
|
||||
this.searchOptions = getSearchOptions(this.searchCols)
|
||||
this.$emit('change', this.conditions, this.searchOptions)
|
||||
},
|
||||
getConditions(columns) {
|
||||
const conditions = {}
|
||||
columns.filter(item => item.search.value !== undefined && item.search.value !== '' && item.search.value !== null)
|
||||
.forEach(col => {
|
||||
const {value, format} = col.search
|
||||
if (this.formatConditions && format) {
|
||||
if (typeof format === 'function') {
|
||||
conditions[col.dataIndex] = format(col.search.value)
|
||||
} else if (typeof format === 'string' && value.constructor.name === 'Moment') {
|
||||
conditions[col.dataIndex] = value.format(format)
|
||||
} else {
|
||||
conditions[col.dataIndex] = value
|
||||
}
|
||||
} else {
|
||||
conditions[col.dataIndex] = value
|
||||
}
|
||||
})
|
||||
return conditions
|
||||
},
|
||||
getSearchOptions(columns) {
|
||||
return columns.filter(item => item.search.value !== undefined && item.search.value !== '' && item.search.value !== null)
|
||||
.map(({dataIndex, search}) => ({field: dataIndex, value: search.value, format: search.format}))
|
||||
},
|
||||
onVisibleChange(col, index) {
|
||||
if (!col.search.visible) {
|
||||
col.search.value = col.search.backup
|
||||
} else {
|
||||
let input = document.getElementById(`${this.searchIdPrefix}${index}`)
|
||||
if (input) {
|
||||
setTimeout(() => {input.focus()}, 0)
|
||||
} else {
|
||||
this.$nextTick(() => {
|
||||
input = document.getElementById(`${this.searchIdPrefix}${index}`)
|
||||
input.focus()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
momentEqual(target, source, format) {
|
||||
if (target === source) {
|
||||
return true
|
||||
} else if (target && source && target.format(format) === source.format(format)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
formatColumns(columns) {
|
||||
columns.forEach(item => {
|
||||
this.$set(item, 'search', {...item.search, visible: false, value: undefined, format: this.getFormat(item)})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.search-area{
|
||||
.select-root{
|
||||
text-align: left;
|
||||
}
|
||||
margin: -4px 0;
|
||||
.search-item{
|
||||
margin: 4px 4px;
|
||||
display: inline-block;
|
||||
.title{
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
user-select: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
.close{
|
||||
color: @text-color-second;
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
vertical-align: middle;
|
||||
:hover{
|
||||
color: @text-color;
|
||||
}
|
||||
}
|
||||
.switch{
|
||||
margin-left: 4px;
|
||||
}
|
||||
.time-picker{
|
||||
margin-left: 4px;
|
||||
width: 96px;
|
||||
}
|
||||
.date-picker{
|
||||
margin-left: 4px;
|
||||
width: 120px;
|
||||
}
|
||||
.datetime-picker{
|
||||
margin-left: 4px;
|
||||
width: 195px;
|
||||
}
|
||||
.value{
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
flex:1;
|
||||
vertical-align: middle;
|
||||
max-width: 144px;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
&.active{
|
||||
background-color: @layout-bg-color;
|
||||
}
|
||||
}
|
||||
.icon-down{
|
||||
vertical-align: middle;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.search-overlay{
|
||||
padding: 8px 0px;
|
||||
text-align: center;
|
||||
}
|
||||
.select{
|
||||
margin-left: 4px;
|
||||
max-width: 144px;
|
||||
min-width: 96px;
|
||||
text-align: left;
|
||||
}
|
||||
.operations{
|
||||
display: flex;
|
||||
margin: -6px 0;
|
||||
justify-content: space-between;
|
||||
.btn{
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,2 @@
|
||||
import AdvanceTable from './AdvanceTable'
|
||||
export default AdvanceTable
|
||||
@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="task-group">
|
||||
<div class="task-head">
|
||||
<h3 class="title"><span v-if="count">{{count}}</span>{{title}}</h3>
|
||||
<div class="actions" style="float: right">
|
||||
<a-icon class="add" type="plus" draggable="true"/>
|
||||
<a-icon class="more" style="margin-left: 8px" type="ellipsis" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-content">
|
||||
<draggable :options="dragOptions">
|
||||
<slot></slot>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
const dragOptions = {
|
||||
sort: true,
|
||||
scroll: true,
|
||||
scrollSpeed: 2,
|
||||
animation: 150,
|
||||
ghostClass: 'dragable-ghost',
|
||||
chosenClass: 'dragable-chose',
|
||||
dragClass: 'dragable-drag'
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'TaskGroup',
|
||||
components: {Draggable},
|
||||
props: ['title', 'group'],
|
||||
data () {
|
||||
return {
|
||||
dragOptions: {...dragOptions, group: this.group}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
count () {
|
||||
return this.$slots.default.length
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.task-group{
|
||||
width: 33.33%;
|
||||
padding: 8px 8px;
|
||||
background-color: @background-color-light;
|
||||
border-radius: 6px;
|
||||
border: 1px solid @shadow-color;
|
||||
.task-head{
|
||||
margin-bottom: 8px;
|
||||
.title{
|
||||
display: inline-block;
|
||||
span{
|
||||
display: inline-block;
|
||||
border-radius: 10px;
|
||||
margin: 0 8px;
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
background-color: @base-bg-color;
|
||||
}
|
||||
}
|
||||
.actions{
|
||||
display: inline-block;
|
||||
float: right;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
i{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<a-card class="task-item" type="inner">
|
||||
{{content}}
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TaskItem',
|
||||
props: ['content']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.task-item{
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 1px 1px @shadow-color;
|
||||
border-radius: 6px;
|
||||
& :hover{
|
||||
cursor: move;
|
||||
box-shadow: 0 1px 2px @shadow-color;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div
|
||||
:class="['step-item', link ? 'linkable' : null]"
|
||||
@click="go"
|
||||
>
|
||||
<span :style="titleStyle">{{title}}</span>
|
||||
<a-icon v-if="icon" :style="iconStyle" :type="icon" />
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const Group = {
|
||||
name: 'AStepItemGroup',
|
||||
props: {
|
||||
align: {
|
||||
type: String,
|
||||
default: 'center',
|
||||
validator(value) {
|
||||
return ['left', 'center', 'right'].indexOf(value) != -1
|
||||
}
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
return h(
|
||||
'div',
|
||||
{attrs: {style: `text-align: ${this.align}; margin-top: 8px`}},
|
||||
[h('div', {attrs: {style: 'text-align: left; display: inline-block;'}}, [this.$slots.default])]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'AStepItem',
|
||||
Group: Group,
|
||||
props: ['title', 'icon', 'link', 'titleStyle', 'iconStyle'],
|
||||
methods: {
|
||||
go () {
|
||||
const link = this.link
|
||||
if (link) {
|
||||
this.$router.push(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.step-item{
|
||||
cursor: pointer;
|
||||
}
|
||||
:global{
|
||||
.ant-steps-item-process{
|
||||
.linkable{
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="avatar-list">
|
||||
<slot>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AAvatar from 'ant-design-vue/es/avatar/Avatar'
|
||||
import ATooltip from 'ant-design-vue/es/tooltip/Tooltip'
|
||||
const Item = {
|
||||
name: 'AvatarListItem',
|
||||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'small'
|
||||
},
|
||||
src: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
tips: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
renderAvatar (h, size, src) {
|
||||
return h(AAvatar, {props: {size: size, src: src}}, [])
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
const avatar = this.renderAvatar(h, this.$props.size, this.$props.src)
|
||||
return h(
|
||||
'li',
|
||||
{class: 'avatar-item'},
|
||||
[this.$props.tips ? h(ATooltip, {props: {title: this.$props.tips}}, [avatar]) : avatar]
|
||||
)
|
||||
}
|
||||
}
|
||||
export default {
|
||||
name: 'AvatarList',
|
||||
Item: Item
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.avatar-list {
|
||||
display: inline-block;
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
font-size: 0;
|
||||
.avatar-item {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
margin-left: -8px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
:global {
|
||||
.ant-avatar {
|
||||
border: 1px solid #fff;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div :class="['detail-list', size === 'small' ? 'small' : 'large', layout === 'vertical' ? 'vertical': 'horizontal']">
|
||||
<div v-if="title" class="title">{{title}}</div>
|
||||
<a-row>
|
||||
<slot></slot>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ACol from 'ant-design-vue/es/grid/Col'
|
||||
const Item = {
|
||||
name: 'DetailListItem',
|
||||
props: {
|
||||
term: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
inject: {
|
||||
col: {
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
renderTerm (h, term) {
|
||||
return term ? h(
|
||||
'div',
|
||||
{
|
||||
attrs: {
|
||||
class: 'term'
|
||||
}
|
||||
},
|
||||
[term]
|
||||
) : null
|
||||
},
|
||||
renderContent (h, content) {
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
attrs: {
|
||||
class: 'content'
|
||||
}
|
||||
},
|
||||
[content]
|
||||
)
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
const term = this.renderTerm(h, this.$props.term)
|
||||
const content = this.renderContent(h, this.$slots.default)
|
||||
return h(
|
||||
ACol,
|
||||
{
|
||||
props: responsive[this.col]
|
||||
},
|
||||
[term, content]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const responsive = {
|
||||
1: { xs: 24 },
|
||||
2: { xs: 24, sm: 12 },
|
||||
3: { xs: 24, sm: 12, md: 8 },
|
||||
4: { xs: 24, sm: 12, md: 6 }
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'DetailList',
|
||||
Item: Item,
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
col: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 3
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'large'
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'horizontal'
|
||||
}
|
||||
},
|
||||
provide () {
|
||||
return {
|
||||
col: this.col > 4 ? 4 : this.col
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.detail-list{
|
||||
.title {
|
||||
font-size: 16px;
|
||||
color: @title-color;
|
||||
font-weight: bolder;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.term {
|
||||
// Line-height is 22px IE dom height will calculate error
|
||||
line-height: 20px;
|
||||
padding-bottom: 16px;
|
||||
margin-right: 8px;
|
||||
color: @title-color;
|
||||
white-space: nowrap;
|
||||
display: table-cell;
|
||||
&:after {
|
||||
content: ':';
|
||||
margin: 0 8px 0 2px;
|
||||
position: relative;
|
||||
top: -0.5px;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
line-height: 22px;
|
||||
width: 100%;
|
||||
padding-bottom: 16px;
|
||||
color: @text-color;
|
||||
display: table-cell;
|
||||
}
|
||||
&.small{
|
||||
.title{
|
||||
font-size: 14px;
|
||||
color: @text-color;
|
||||
font-weight: normal;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.term,.content{
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
&.large{
|
||||
.term,.content{
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
}
|
||||
&.vertical{
|
||||
.term {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.term,.content{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div >
|
||||
<div :class="['mask', visible ? 'open' : 'close']" @click="close"></div>
|
||||
<div :class="['drawer', placement, visible ? 'open' : 'close']">
|
||||
<div ref="drawer" class="content beauty-scroll">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div v-if="showHandler" :class="['handler-container', placement, visible ? 'open' : 'close']" ref="handler" @click="toggle">
|
||||
<slot v-if="$slots.handler" name="handler"></slot>
|
||||
<div v-else class="handler">
|
||||
<a-icon :type="visible ? 'close' : 'bars'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Drawer',
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'visible',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'left'
|
||||
},
|
||||
showHandler: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open () {
|
||||
this.$emit('change', true)
|
||||
},
|
||||
close () {
|
||||
this.$emit('change', false)
|
||||
},
|
||||
toggle () {
|
||||
this.$emit('change', !this.visible)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.mask{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
background-color: @shadow-color;
|
||||
transition: all 0.5s;
|
||||
z-index: 100;
|
||||
&.open{
|
||||
display: inline-block;
|
||||
}
|
||||
&.close{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.drawer{
|
||||
position: fixed;
|
||||
transition: all 0.5s;
|
||||
height: 100vh;
|
||||
z-index: 100;
|
||||
&.left{
|
||||
left: 0px;
|
||||
&.open{
|
||||
.content{
|
||||
box-shadow: 2px 0 8px @shadow-color;
|
||||
}
|
||||
}
|
||||
&.close{
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
&.right{
|
||||
right: 0px;
|
||||
.content{
|
||||
float: right;
|
||||
}
|
||||
&.open{
|
||||
.content{
|
||||
box-shadow: -2px 0 8px @shadow-color;
|
||||
}
|
||||
}
|
||||
&.close{
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.content{
|
||||
display: inline-block;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.handler-container{
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
top: 200px;
|
||||
z-index: 100;
|
||||
.handler {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
background-color: @base-bg-color;
|
||||
font-size: 26px;
|
||||
box-shadow: 0 2px 8px @shadow-color;
|
||||
line-height: 40px;
|
||||
}
|
||||
&.left{
|
||||
right: -40px;
|
||||
.handler{
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
}
|
||||
&.right{
|
||||
left: -40px;
|
||||
.handler{
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="toolbar">
|
||||
<div style="float: left">
|
||||
<slot name="extra"></slot>
|
||||
</div>
|
||||
<div style="float: right">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FooterToolBar'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.toolbar{
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
box-shadow: 0 -1px 2px @shadow-color;
|
||||
background: @base-bg-color;
|
||||
border-top: 1px solid @border-color-split;
|
||||
padding: 12px 24px;
|
||||
z-index: 9;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="head-info">
|
||||
<span>{{title}}</span>
|
||||
<p>{{content}}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HeadInfo',
|
||||
props: ['title', 'content', 'bordered']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.head-info{
|
||||
text-align: center;
|
||||
padding: 0 24px;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
span{
|
||||
color: @text-color-second;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
p{
|
||||
color: @text-color;
|
||||
font-size: 24px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<a-checkable-tag @change="$emit('click')" class="tag-default" v-model="checked">
|
||||
<slot></slot>
|
||||
</a-checkable-tag>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TagSelectOption',
|
||||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'default'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
checked: false,
|
||||
isTagSelectOption: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tag-default{
|
||||
font-size: 14px;
|
||||
padding: 0 8px;
|
||||
height: auto;
|
||||
margin-right: 24px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<transition
|
||||
v-if="!disabled"
|
||||
:enter-active-class="`animated ${enterAnimate} page-toggle-enter-active`"
|
||||
:leave-active-class="`animated ${leaveAnimate} page-toggle-leave-active`"
|
||||
>
|
||||
<slot></slot>
|
||||
</transition>
|
||||
<div v-else><slot></slot></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {preset as animates} from '@/config/default/animate.config'
|
||||
|
||||
export default {
|
||||
name: 'PageToggleTransition',
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
animate: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
return animates.findIndex(item => item.name == value) != -1
|
||||
},
|
||||
default: 'bounce'
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
return ['x', 'y', 'left', 'right', 'up', 'down', 'downLeft', 'upRight', 'downRight', 'upLeft', 'downBig',
|
||||
'upBig', 'downLeft', 'downRight', 'topRight', 'bottomLeft', 'topLeft', 'bottomRight', 'default'].indexOf(value) > -1
|
||||
}
|
||||
},
|
||||
reverse: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
enterAnimate() {
|
||||
return this.activeClass(false)
|
||||
},
|
||||
leaveAnimate() {
|
||||
return this.activeClass(true)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
activeClass(isLeave) {
|
||||
let animate = animates.find(item => this.animate == item.name)
|
||||
if (animate == undefined) {
|
||||
return ''
|
||||
}
|
||||
let direction = ''
|
||||
if (this.direction == undefined) {
|
||||
direction = animate.directions[0]
|
||||
} else {
|
||||
direction = animate.directions.find(item => item == this.direction)
|
||||
}
|
||||
direction = (direction == undefined || direction === 'default') ? '' : direction
|
||||
if (direction != '') {
|
||||
direction = isLeave && this.reverse ? this.reversePosition(direction, animate.directions) : direction
|
||||
direction = direction[0].toUpperCase() + direction.substring(1)
|
||||
}
|
||||
let t = isLeave ? 'Out' : 'In'
|
||||
return animate.name + t + direction
|
||||
},
|
||||
reversePosition(direction, directions) {
|
||||
if(direction.length == 0 || direction == 'x' || direction == 'y') {
|
||||
return direction
|
||||
}
|
||||
let index = directions.indexOf(direction)
|
||||
index = (index % 2 == 1) ? index - 1 : index + 1
|
||||
return directions[index]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.page-toggle-enter-active{
|
||||
position: absolute !important;
|
||||
animation-duration: 0.8s !important;
|
||||
width: calc(100%) !important;
|
||||
}
|
||||
.page-toggle-leave-active{
|
||||
position: absolute !important;
|
||||
animation-duration: 0.8s !important;
|
||||
width: calc(100%) !important;
|
||||
}
|
||||
.page-toggle-enter{
|
||||
}
|
||||
.page-toggle-leave-to{
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,18 @@
|
||||
// admin 配置
|
||||
const ADMIN = {
|
||||
palettes: ['#f5222d', '#fa541c', '#fadb14', '#3eaf7c', '#13c2c2', '#1890ff', '#722ed1', '#eb2f96'],
|
||||
animates: require('./animate.config').preset,
|
||||
theme: {
|
||||
mode: {
|
||||
DARK: 'dark',
|
||||
LIGHT: 'light',
|
||||
NIGHT: 'night'
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
SIDE: 'side',
|
||||
HEAD: 'head'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ADMIN
|
||||
@ -0,0 +1,21 @@
|
||||
const direct_s = ['left', 'right']
|
||||
const direct_1 = ['left', 'right', 'down', 'up']
|
||||
const direct_1_b = ['downBig', 'upBig', 'leftBig', 'rightBig']
|
||||
const direct_2 = ['topLeft', 'bottomRight', 'topRight', 'bottomLeft']
|
||||
const direct_3 = ['downLeft', 'upRight', 'downRight', 'upLeft']
|
||||
|
||||
// animate.css 配置
|
||||
const ANIMATE = {
|
||||
preset: [ //预设动画配置
|
||||
{name: 'back', alias: '渐近', directions: direct_1},
|
||||
{name: 'bounce', alias: '弹跳', directions: direct_1.concat('default')},
|
||||
{name: 'fade', alias: '淡化', directions: direct_1.concat(direct_1_b).concat(direct_2).concat('default')},
|
||||
{name: 'flip', alias: '翻转', directions: ['x', 'y']},
|
||||
{name: 'lightSpeed', alias: '光速', directions: direct_s},
|
||||
{name: 'rotate', alias: '旋转', directions: direct_3.concat('default')},
|
||||
{name: 'roll', alias: '翻滚', directions: ['default']},
|
||||
{name: 'zoom', alias: '缩放', directions: direct_1.concat('default')},
|
||||
{name: 'slide', alias: '滑动', directions: direct_1},
|
||||
]
|
||||
}
|
||||
module.exports = ANIMATE
|
||||
@ -0,0 +1,84 @@
|
||||
// antd 配置
|
||||
const ANTD = {
|
||||
primary: {
|
||||
color: '#1890ff',
|
||||
warning: '#faad14',
|
||||
success: '#52c41a',
|
||||
error: '#f5222d',
|
||||
light: {
|
||||
menuColors: ['#000c17', '#001529', '#002140']
|
||||
},
|
||||
dark: {
|
||||
menuColors: ['#000c17', '#001529', '#002140']
|
||||
},
|
||||
night: {
|
||||
menuColors: ['#151515', '#1f1f1f', '#1e1e1e'],
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
dark: {
|
||||
'layout-body-background': '#f0f2f5',
|
||||
'body-background': '#fff',
|
||||
'component-background': '#fff',
|
||||
'heading-color': 'rgba(0, 0, 0, 0.85)',
|
||||
'text-color': 'rgba(0, 0, 0, 0.65)',
|
||||
'text-color-inverse': '#fff',
|
||||
'text-color-secondary': 'rgba(0, 0, 0, 0.45)',
|
||||
'shadow-color': 'rgba(0, 0, 0, 0.15)',
|
||||
'border-color-split': '#f0f0f0',
|
||||
'background-color-light': '#fafafa',
|
||||
'background-color-base': '#f5f5f5',
|
||||
'table-selected-row-bg': '#fafafa',
|
||||
'table-expanded-row-bg': '#fbfbfb',
|
||||
'checkbox-check-color': '#fff',
|
||||
'disabled-color': 'rgba(0, 0, 0, 0.25)',
|
||||
'menu-dark-color': 'rgba(254, 254, 254, 0.65)',
|
||||
'menu-dark-highlight-color': '#fefefe',
|
||||
'menu-dark-arrow-color': '#fefefe',
|
||||
'btn-primary-color': '#fff',
|
||||
},
|
||||
light: {
|
||||
'layout-body-background': '#f0f2f5',
|
||||
'body-background': '#fff',
|
||||
'component-background': '#fff',
|
||||
'heading-color': 'rgba(0, 0, 0, 0.85)',
|
||||
'text-color': 'rgba(0, 0, 0, 0.65)',
|
||||
'text-color-inverse': '#fff',
|
||||
'text-color-secondary': 'rgba(0, 0, 0, 0.45)',
|
||||
'shadow-color': 'rgba(0, 0, 0, 0.15)',
|
||||
'border-color-split': '#f0f0f0',
|
||||
'background-color-light': '#fafafa',
|
||||
'background-color-base': '#f5f5f5',
|
||||
'table-selected-row-bg': '#fafafa',
|
||||
'table-expanded-row-bg': '#fbfbfb',
|
||||
'checkbox-check-color': '#fff',
|
||||
'disabled-color': 'rgba(0, 0, 0, 0.25)',
|
||||
'menu-dark-color': 'rgba(1, 1, 1, 0.65)',
|
||||
'menu-dark-highlight-color': '#fefefe',
|
||||
'menu-dark-arrow-color': '#fefefe',
|
||||
'btn-primary-color': '#fff',
|
||||
},
|
||||
night: {
|
||||
'layout-body-background': '#000',
|
||||
'body-background': '#141414',
|
||||
'component-background': '#141414',
|
||||
'heading-color': 'rgba(255, 255, 255, 0.85)',
|
||||
'text-color': 'rgba(255, 255, 255, 0.85)',
|
||||
'text-color-inverse': '#141414',
|
||||
'text-color-secondary': 'rgba(255, 255, 255, 0.45)',
|
||||
'shadow-color': 'rgba(255, 255, 255, 0.15)',
|
||||
'border-color-split': '#303030',
|
||||
'background-color-light': '#ffffff0a',
|
||||
'background-color-base': '#2a2a2a',
|
||||
'table-selected-row-bg': '#ffffff0a',
|
||||
'table-expanded-row-bg': '#ffffff0b',
|
||||
'checkbox-check-color': '#141414',
|
||||
'disabled-color': 'rgba(255, 255, 255, 0.25)',
|
||||
'menu-dark-color': 'rgba(254, 254, 254, 0.65)',
|
||||
'menu-dark-highlight-color': '#fefefe',
|
||||
'menu-dark-arrow-color': '#fefefe',
|
||||
'btn-primary-color': '#141414',
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = ANTD
|
||||
@ -0,0 +1,6 @@
|
||||
const ANTD = require('./antd.config')
|
||||
const ADMIN = require('./admin.config')
|
||||
const ANIMATE = require('./animate.config')
|
||||
const setting = require('./setting.config')
|
||||
|
||||
module.exports = {ANTD, ADMIN, ANIMATE, setting}
|
||||
@ -0,0 +1,6 @@
|
||||
const deepMerge = require('deepmerge')
|
||||
const _config = require('./config')
|
||||
const {setting} = require('./default')
|
||||
const config = deepMerge(setting, _config)
|
||||
|
||||
module.exports = config
|
||||
@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<a-layout :class="['admin-layout', 'beauty-scroll']">
|
||||
|
||||
<drawer v-if="isMobile" v-model="drawerOpen">
|
||||
<side-menu :theme="theme.mode" :menuData="menuData" :collapsed="false" :collapsible="false" @menuSelect="onMenuSelect"/>
|
||||
</drawer>
|
||||
<side-menu :class="[fixedSideBar ? 'fixed-side' : '']" :theme="theme.mode" v-else-if="layout === 'side' || layout === 'mix'" :menuData="sideMenuData" :collapsed="collapsed" :collapsible="true" />
|
||||
|
||||
<div v-if="fixedSideBar && !isMobile" :style="`width: ${sideMenuWidth}; min-width: ${sideMenuWidth};max-width: ${sideMenuWidth};`" class="virtual-side"></div>
|
||||
|
||||
<drawer v-if="!hideSetting" v-model="showSetting" placement="right">
|
||||
<div class="setting" slot="handler">
|
||||
<a-icon :type="showSetting ? 'close' : 'setting'"/>
|
||||
</div>
|
||||
<setting />
|
||||
</drawer>
|
||||
|
||||
<a-layout class="admin-layout-main beauty-scroll">
|
||||
<admin-header :class="[{'fixed-tabs': fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage}]" :style="headerStyle" :menuData="headMenuData" :collapsed="collapsed" @toggleCollapse="toggleCollapse"/>
|
||||
|
||||
<a-layout-header :class="['virtual-header', {'fixed-tabs' : fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage}]" v-show="fixedHeader"></a-layout-header>
|
||||
|
||||
<a-layout-content class="admin-layout-content" :style="`min-height: ${minHeight}px;`">
|
||||
<div style="position: relative">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</a-layout-content>
|
||||
|
||||
<a-layout-footer style="padding: 0px">
|
||||
<page-footer :link-list="footerLinks" :copyright="copyright" />
|
||||
</a-layout-footer>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AdminHeader from './header/AdminHeader'
|
||||
import PageFooter from './footer/PageFooter'
|
||||
import Drawer from '../components/tool/Drawer'
|
||||
import SideMenu from '../components/menu/SideMenu'
|
||||
import Setting from '../components/setting/Setting'
|
||||
import {mapState, mapMutations, mapGetters} from 'vuex'
|
||||
|
||||
// const minHeight = window.innerHeight - 64 - 122
|
||||
|
||||
export default {
|
||||
name: 'AdminLayout',
|
||||
components: {Setting, SideMenu, Drawer, PageFooter, AdminHeader},
|
||||
data () {
|
||||
return {
|
||||
minHeight: window.innerHeight - 64 - 122,
|
||||
collapsed: false,
|
||||
showSetting: false,
|
||||
drawerOpen: false
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
adminLayout: this
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route(val) {
|
||||
this.setActivated(val)
|
||||
},
|
||||
layout() {
|
||||
this.setActivated(this.$route)
|
||||
},
|
||||
isMobile(val) {
|
||||
if(!val) {
|
||||
this.drawerOpen = false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('setting', ['isMobile', 'theme', 'layout', 'footerLinks', 'copyright', 'fixedHeader', 'fixedSideBar',
|
||||
'fixedTabs', 'hideSetting', 'multiPage']),
|
||||
...mapGetters('setting', ['firstMenu', 'subMenu', 'menuData']),
|
||||
sideMenuWidth() {
|
||||
return this.collapsed ? '80px' : '200px'
|
||||
},
|
||||
headerStyle() {
|
||||
let width = (this.fixedHeader && this.layout !== 'head' && !this.isMobile) ? `calc(100% - ${this.sideMenuWidth})` : '100%'
|
||||
let position = this.fixedHeader ? 'fixed' : 'static'
|
||||
return `width: ${width}; position: ${position};`
|
||||
},
|
||||
headMenuData() {
|
||||
const {layout, menuData, firstMenu} = this
|
||||
return layout === 'mix' ? firstMenu : menuData
|
||||
},
|
||||
sideMenuData() {
|
||||
const {layout, menuData, subMenu} = this
|
||||
return layout === 'mix' ? subMenu : menuData
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('setting', ['correctPageMinHeight', 'setActivatedFirst']),
|
||||
toggleCollapse () {
|
||||
this.collapsed = !this.collapsed
|
||||
},
|
||||
onMenuSelect () {
|
||||
this.toggleCollapse()
|
||||
},
|
||||
setActivated(route) {
|
||||
if (this.layout === 'mix') {
|
||||
let matched = route.matched
|
||||
matched = matched.slice(0, matched.length - 1)
|
||||
const {firstMenu} = this
|
||||
for (let menu of firstMenu) {
|
||||
if (matched.findIndex(item => item.path === menu.fullPath) !== -1) {
|
||||
this.setActivatedFirst(menu.fullPath)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.correctPageMinHeight(this.minHeight - 24)
|
||||
this.setActivated(this.$route)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.correctPageMinHeight(-this.minHeight + 24)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.admin-layout{
|
||||
.side-menu{
|
||||
&.fixed-side{
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.virtual-side{
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.virtual-header{
|
||||
transition: all 0.2s;
|
||||
opacity: 0;
|
||||
&.fixed-tabs.multi-page:not(.fixed-header){
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
.admin-layout-main{
|
||||
.admin-header{
|
||||
top: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s;
|
||||
&.fixed-tabs.multi-page:not(.fixed-header){
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.admin-layout-content{
|
||||
padding: 24px 5px 0;
|
||||
/*overflow-x: hidden;*/
|
||||
/*min-height: calc(100vh - 64px - 122px);*/
|
||||
}
|
||||
.setting{
|
||||
background-color: @primary-color;
|
||||
color: @base-bg-color;
|
||||
border-radius: 5px 0 0 5px;
|
||||
line-height: 40px;
|
||||
font-size: 22px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
box-shadow: -2px 0 8px @shadow-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<page-toggle-transition :disabled="animate.disabled" :animate="animate.name" :direction="animate.direction">
|
||||
<router-view />
|
||||
</page-toggle-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageToggleTransition from '../components/transition/PageToggleTransition';
|
||||
import {mapState} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'BlankView',
|
||||
components: {PageToggleTransition},
|
||||
computed: {
|
||||
...mapState('setting', ['multiPage', 'animate'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="common-layout">
|
||||
<div class="content"><slot></slot></div>
|
||||
<page-footer :link-list="footerLinks" :copyright="copyright"></page-footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageFooter from '@/layouts/footer/PageFooter'
|
||||
import {mapState} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'CommonLayout',
|
||||
components: {PageFooter},
|
||||
computed: {
|
||||
...mapState('setting', ['footerLinks', 'copyright'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.common-layout{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
background-color: @layout-body-background;
|
||||
background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position-x: center;
|
||||
background-position-y: 110px;
|
||||
background-size: 100%;
|
||||
.content{
|
||||
padding: 32px 0;
|
||||
flex: 1;
|
||||
@media (min-width: 768px){
|
||||
|
||||
padding: 112px 0 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div class="page-layout">
|
||||
<page-header ref="pageHeader" :style="`margin-top: ${multiPage ? 0 : -24}px`" :breadcrumb="breadcrumb" :title="pageTitle" :logo="logo" :avatar="avatar">
|
||||
<slot name="action" slot="action"></slot>
|
||||
<slot slot="content" name="headerContent"></slot>
|
||||
<div slot="content" v-if="!this.$slots.headerContent && desc">
|
||||
<p>{{desc}}</p>
|
||||
<div v-if="this.linkList" class="link">
|
||||
<template v-for="(link, index) in linkList">
|
||||
<a :key="index" :href="link.href"><a-icon :type="link.icon" />{{link.title}}</a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<slot v-if="this.$slots.extra" slot="extra" name="extra"></slot>
|
||||
</page-header>
|
||||
<div ref="page" :class="['page-content', layout, pageWidth]" >
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageHeader from '@/components/page/header/PageHeader'
|
||||
import {mapState, mapMutations} from 'vuex'
|
||||
import {getI18nKey} from '@/utils/routerUtil'
|
||||
|
||||
export default {
|
||||
name: 'PageLayout',
|
||||
components: {PageHeader},
|
||||
props: ['desc', 'logo', 'title', 'avatar', 'linkList', 'extraImage'],
|
||||
data () {
|
||||
return {
|
||||
page: {},
|
||||
pageHeaderHeight: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.page = this.$route.meta.page
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
if (!this._inactive) {
|
||||
this.updatePageHeight()
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.updatePageHeight()
|
||||
},
|
||||
deactivated() {
|
||||
this.updatePageHeight(0)
|
||||
},
|
||||
mounted() {
|
||||
this.updatePageHeight()
|
||||
},
|
||||
created() {
|
||||
this.page = this.$route.meta.page
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.updatePageHeight(0)
|
||||
},
|
||||
computed: {
|
||||
...mapState('setting', ['layout', 'multiPage', 'pageMinHeight', 'pageWidth', 'customTitles']),
|
||||
pageTitle() {
|
||||
let pageTitle = this.page && this.page.title
|
||||
return this.customTitle || (pageTitle && this.$t(pageTitle)) || this.title || this.routeName
|
||||
},
|
||||
routeName() {
|
||||
const route = this.$route
|
||||
return this.$t(getI18nKey(route.matched[route.matched.length - 1].path))
|
||||
},
|
||||
breadcrumb() {
|
||||
let page = this.page
|
||||
let breadcrumb = page && page.breadcrumb
|
||||
if (breadcrumb) {
|
||||
let i18nBreadcrumb = []
|
||||
breadcrumb.forEach(item => {
|
||||
i18nBreadcrumb.push(this.$t(item))
|
||||
})
|
||||
return i18nBreadcrumb
|
||||
} else {
|
||||
return this.getRouteBreadcrumb()
|
||||
}
|
||||
},
|
||||
marginCorrect() {
|
||||
return this.multiPage ? 24 : 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('setting', ['correctPageMinHeight']),
|
||||
getRouteBreadcrumb() {
|
||||
let routes = this.$route.matched
|
||||
const path = this.$route.path
|
||||
let breadcrumb = []
|
||||
routes.filter(item => path.includes(item.path))
|
||||
.forEach(route => {
|
||||
const path = route.path.length === 0 ? '/home' : route.path
|
||||
breadcrumb.push(this.$t(getI18nKey(path)))
|
||||
})
|
||||
let pageTitle = this.page && this.page.title
|
||||
if (this.customTitle || pageTitle) {
|
||||
breadcrumb[breadcrumb.length - 1] = this.customTitle || pageTitle
|
||||
}
|
||||
return breadcrumb
|
||||
},
|
||||
/**
|
||||
* 用于计算页面内容最小高度
|
||||
* @param newHeight
|
||||
*/
|
||||
updatePageHeight(newHeight = this.$refs.pageHeader.$el.offsetHeight + this.marginCorrect) {
|
||||
this.correctPageMinHeight(this.pageHeaderHeight - newHeight)
|
||||
this.pageHeaderHeight = newHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.page-header{
|
||||
margin: 0 -24px 0;
|
||||
}
|
||||
.link{
|
||||
/*margin-top: 16px;*/
|
||||
line-height: 24px;
|
||||
a{
|
||||
font-size: 14px;
|
||||
margin-right: 32px;
|
||||
i{
|
||||
font-size: 22px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.page-content{
|
||||
position: relative;
|
||||
padding: 5px 0 0;
|
||||
&.side{
|
||||
}
|
||||
&.head.fixed{
|
||||
margin: 0 auto;
|
||||
max-width: 1400px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<page-layout :desc="desc" :linkList="linkList">
|
||||
<div v-if="this.extraImage && !isMobile" slot="extra" class="extraImg">
|
||||
<img :src="extraImage"/>
|
||||
</div>
|
||||
<page-toggle-transition :disabled="animate.disabled" :animate="animate.name" :direction="animate.direction">
|
||||
<router-view ref="page" />
|
||||
</page-toggle-transition>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageLayout from './PageLayout'
|
||||
import PageToggleTransition from '../components/transition/PageToggleTransition';
|
||||
import {mapState} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'PageView',
|
||||
components: {PageToggleTransition, PageLayout},
|
||||
data () {
|
||||
return {
|
||||
page: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('setting', ['isMobile', 'multiPage', 'animate']),
|
||||
desc() {
|
||||
return this.page.desc
|
||||
},
|
||||
linkList() {
|
||||
return this.page.linkList
|
||||
},
|
||||
extraImage() {
|
||||
return this.page.extraImage
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.page = this.$refs.page
|
||||
},
|
||||
updated () {
|
||||
this.page = this.$refs.page
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.extraImg{
|
||||
margin-top: -60px;
|
||||
text-align: center;
|
||||
width: 195px;
|
||||
img{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="footer">
|
||||
<div class="links">
|
||||
<a target="_blank" :key="index" :href="item.link ? item.link : 'javascript: void(0)'" v-for="(item, index) in linkList">
|
||||
<a-icon v-if="item.icon" :type="item.icon"/>{{item.name}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="copyright">
|
||||
Copyright<a-icon type="copyright" />{{copyright}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PageFooter',
|
||||
props: ['copyright', 'linkList']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.footer{
|
||||
padding: 48px 16px 24px;
|
||||
/*margin: 48px 0 24px;*/
|
||||
text-align: center;
|
||||
.copyright{
|
||||
color: @text-color-second;
|
||||
font-size: 14px;
|
||||
i {
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
.links{
|
||||
margin-bottom: 8px;
|
||||
a:not(:last-child) {
|
||||
margin-right: 40px;
|
||||
}
|
||||
a{
|
||||
color: @text-color-second;
|
||||
-webkit-transition: all .3s;
|
||||
transition: all .3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<a-layout-header :class="[headerTheme, 'admin-header']">
|
||||
<div :class="['admin-header-wide', layout, pageWidth]">
|
||||
<router-link v-if="isMobile || layout === 'head'" to="/" :class="['logo', isMobile ? null : 'pc', headerTheme]">
|
||||
<img width="32" src="@/assets/images/logo.png" />
|
||||
<h1 v-if="!isMobile">{{systemName}}</h1>
|
||||
</router-link>
|
||||
<!-- <a-divider v-if="isMobile" type="vertical" /> -->
|
||||
<a-icon v-if="layout !== 'head' && !isMobile" class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggleCollapse"/>
|
||||
<div v-if="layout !== 'side' && !isMobile" class="admin-header-menu" :style="`width: ${menuWidth};`">
|
||||
<i-menu class="head-menu" :theme="headerTheme" mode="horizontal" :options="menuData" @select="onSelect"/>
|
||||
</div>
|
||||
<div :class="['admin-header-right', headerTheme]">
|
||||
<header-avatar class="header-item"/>
|
||||
<!-- <a-dropdown class="lang header-item">
|
||||
<div>
|
||||
<a-icon type="global"/> {{langAlias}}
|
||||
</div>
|
||||
<a-menu @click="val => setLang(val.key)" :selected-keys="[lang]" slot="overlay">
|
||||
<a-menu-item v-for=" lang in langList" :key="lang.key">{{lang.key.toLowerCase() + ' ' + lang.name}}</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown> -->
|
||||
</div>
|
||||
</div>
|
||||
</a-layout-header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import HeaderNotice from './HeaderNotice'
|
||||
import HeaderAvatar from './HeaderAvatar'
|
||||
import IMenu from '@/components/menu/menu'
|
||||
import {mapState, mapMutations} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'AdminHeader',
|
||||
components: {IMenu, HeaderAvatar},
|
||||
props: ['collapsed', 'menuData'],
|
||||
data() {
|
||||
return {
|
||||
langList: [
|
||||
{key: 'TW', name: '繁體中文', alias: '繁體'},
|
||||
{key: 'CN', name: '简体中文', alias: '简体'},
|
||||
{key: 'US', name: 'English', alias: 'EN'}
|
||||
],
|
||||
searchActive: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('setting', ['theme', 'isMobile', 'layout', 'systemName', 'lang', 'pageWidth']),
|
||||
headerTheme () {
|
||||
if (this.layout == 'side' && this.theme.mode == 'dark' && !this.isMobile) {
|
||||
return 'light'
|
||||
}
|
||||
return this.theme.mode
|
||||
},
|
||||
langAlias() {
|
||||
let lang = this.langList.find(item => item.key == this.lang)
|
||||
return lang.alias
|
||||
},
|
||||
menuWidth() {
|
||||
const {layout, searchActive} = this
|
||||
const headWidth = layout === 'head' ? '100% - 188px' : '100%'
|
||||
const extraWidth = searchActive ? '600px' : '400px'
|
||||
return `calc(${headWidth} - ${extraWidth})`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleCollapse () {
|
||||
this.$emit('toggleCollapse')
|
||||
},
|
||||
onSelect (obj) {
|
||||
this.$emit('menuSelect', obj)
|
||||
},
|
||||
...mapMutations('setting', ['setLang'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "index";
|
||||
</style>
|
||||
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<a-dropdown>
|
||||
<div class="header-avatar" style="cursor: pointer">
|
||||
<!-- <a-avatar class="avatar" size="small" shape="circle" :src="user.avatar"/> -->
|
||||
<span class="name">{{user.name}}</span>
|
||||
</div>
|
||||
<a-menu :class="['avatar-menu']" slot="overlay">
|
||||
<!-- <a-menu-item>
|
||||
<a-icon type="user" />
|
||||
<span>個人中心</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a-icon type="setting" />
|
||||
<span>設定</span>
|
||||
</a-menu-item> -->
|
||||
<!-- <a-menu-divider /> -->
|
||||
<a-menu-item @click="logout">
|
||||
<a-icon style="margin-right: 8px;" type="poweroff" />
|
||||
<span>登出</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters} from 'vuex'
|
||||
import {logout} from '@/services/admin'
|
||||
|
||||
export default {
|
||||
name: 'HeaderAvatar',
|
||||
computed: {
|
||||
...mapGetters('account', ['user']),
|
||||
},
|
||||
methods: {
|
||||
logout() {
|
||||
logout()
|
||||
this.$router.push('/login')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.header-avatar{
|
||||
display: inline-flex;
|
||||
.avatar, .name{
|
||||
align-self: center;
|
||||
}
|
||||
.avatar{
|
||||
margin-right: 8px;
|
||||
}
|
||||
.name{
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
.avatar-menu{
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<a-dropdown :trigger="['click']" v-model="show">
|
||||
<div slot="overlay">
|
||||
<a-spin :spinning="loading">
|
||||
<a-tabs class="dropdown-tabs" :tabBarStyle="{textAlign: 'center'}" :style="{width: '297px'}">
|
||||
<a-tab-pane tab="通知" key="1">
|
||||
<a-list class="tab-pane">
|
||||
<a-list-item>
|
||||
<a-list-item-meta title="你收到了 14 份新周报" description="一年前">
|
||||
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"/>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta title="你推荐的 曲妮妮 已通过第三轮面试" description="一年前">
|
||||
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta title="这种模板可以区分多种通知类型" description="一年前">
|
||||
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="消息" key="2">
|
||||
<a-list class="tab-pane"></a-list>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="待办" key="3">
|
||||
<a-list class="tab-pane"></a-list>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-spin>
|
||||
</div>
|
||||
<span @click="fetchNotice" class="header-notice">
|
||||
<a-badge class="notice-badge" count="12">
|
||||
<a-icon :class="['header-notice-icon']" type="bell" />
|
||||
</a-badge>
|
||||
</span>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HeaderNotice',
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
show: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
fetchNotice () {
|
||||
if (this.loading) {
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
this.loadding = true
|
||||
setTimeout(() => {
|
||||
this.loadding = false
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.header-notice{
|
||||
display: inline-block;
|
||||
transition: all 0.3s;
|
||||
span {
|
||||
vertical-align: initial;
|
||||
}
|
||||
.notice-badge{
|
||||
color: inherit;
|
||||
.header-notice-icon{
|
||||
font-size: 16px;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dropdown-tabs{
|
||||
background-color: @base-bg-color;
|
||||
box-shadow: 0 2px 8px @shadow-color;
|
||||
border-radius: 4px;
|
||||
.tab-pane{
|
||||
padding: 0 24px 12px;
|
||||
min-height: 250px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="header-search">
|
||||
<a-icon type="search" class="search-icon" @click="enterSearchMode"/>
|
||||
<a-auto-complete
|
||||
ref="input"
|
||||
:getPopupContainer="e => {return e.parentNode || document.body}"
|
||||
:dataSource="dataSource"
|
||||
:class="['search-input', searchMode ? 'enter' : 'leave']"
|
||||
placeholder="站内搜索"
|
||||
@blur="leaveSearchMode"
|
||||
>
|
||||
</a-auto-complete>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HeaderSearch',
|
||||
data () {
|
||||
return {
|
||||
dataSource: ['选项一', '选项二'],
|
||||
searchMode: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
enterSearchMode () {
|
||||
this.searchMode = true
|
||||
this.$emit('active', true)
|
||||
setTimeout(() => this.$refs.input.focus(), 300)
|
||||
},
|
||||
leaveSearchMode () {
|
||||
this.searchMode = false
|
||||
setTimeout(() => this.$emit('active', false), 300)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.header-search{
|
||||
.search-icon{
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.search-input{
|
||||
border: 0;
|
||||
border-bottom: 1px solid @border-color-split;
|
||||
transition: width 0.3s ease-in-out;
|
||||
input{
|
||||
border: 0;
|
||||
box-shadow: 0 0 0 0;
|
||||
}
|
||||
&.leave{
|
||||
width: 0px;
|
||||
input{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.enter{
|
||||
width: 200px;
|
||||
input:focus{
|
||||
box-shadow: 0 0 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,92 @@
|
||||
.admin-header{
|
||||
padding: 0;
|
||||
z-index: 2;
|
||||
box-shadow: @shadow-down;
|
||||
position: relative;
|
||||
background: @base-bg-color;
|
||||
.head-menu{
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
vertical-align: middle;
|
||||
box-shadow: none;
|
||||
}
|
||||
&.dark{
|
||||
background: @header-bg-color-dark;
|
||||
color: white;
|
||||
}
|
||||
&.night{
|
||||
.head-menu{
|
||||
background: @base-bg-color;
|
||||
}
|
||||
}
|
||||
.admin-header-wide{
|
||||
padding-left: 24px;
|
||||
&.head.fixed{
|
||||
max-width: 1400px;
|
||||
margin: auto;
|
||||
padding-left: 0;
|
||||
}
|
||||
&.side{
|
||||
padding-right: 12px;
|
||||
}
|
||||
.logo {
|
||||
height: 64px;
|
||||
line-height: 58px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
padding: 0 12px 0 24px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
color: inherit;
|
||||
&.pc{
|
||||
padding: 0 12px 0 0;
|
||||
}
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
h1{
|
||||
color: inherit;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.trigger {
|
||||
font-size: 20px;
|
||||
line-height: 64px;
|
||||
padding: 0 0px;
|
||||
cursor: pointer;
|
||||
transition: color .3s;
|
||||
&:hover{
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
.admin-header-menu{
|
||||
display: inline-block;
|
||||
}
|
||||
.admin-header-right{
|
||||
float: right;
|
||||
display: flex;
|
||||
color: inherit;
|
||||
.header-item{
|
||||
color: inherit;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
align-self: center;
|
||||
a{
|
||||
color: inherit;
|
||||
i{
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
each(@theme-list, {
|
||||
&.@{value} .header-item{
|
||||
&:hover{
|
||||
@class: ~'hover-bg-color-@{value}';
|
||||
background-color: @@class;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<div :class="['tabs-head', layout, pageWidth]">
|
||||
<a-tabs
|
||||
type="editable-card"
|
||||
:class="['tabs-container', layout, pageWidth, {'affixed' : affixed, 'fixed-header' : fixedHeader, 'collapsed' : adminLayout.collapsed}]"
|
||||
:active-key="active"
|
||||
:hide-add="true"
|
||||
>
|
||||
<a-tooltip placement="left" :title="lockTitle" slot="tabBarExtraContent">
|
||||
<a-icon
|
||||
theme="filled"
|
||||
@click="onLockClick"
|
||||
class="header-lock"
|
||||
:type="fixedTabs ? 'lock' : 'unlock'"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tab-pane v-for="page in pageList" :key="page.path">
|
||||
<div slot="tab" class="tab" @contextmenu="e => onContextmenu(page.path, e)">
|
||||
<a-icon @click="onRefresh(page)" :class="['icon-sync', {'hide': page.path !== active && !page.loading}]" :type="page.loading ? 'loading' : 'sync'" />
|
||||
<div class="title" @click="onTabClick(page.path)" >{{pageName(page)}}</div>
|
||||
<a-icon v-if="!page.unclose" @click="onClose(page.path)" class="icon-close" type="close"/>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<div v-if="affixed" class="virtual-tabs"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState, mapMutations} from 'vuex'
|
||||
import {getI18nKey} from '@/utils/routerUtil'
|
||||
|
||||
export default {
|
||||
name: 'TabsHead',
|
||||
i18n: {
|
||||
messages: {
|
||||
TW: {
|
||||
lock: '點擊鎖定頁簽頭',
|
||||
unlock: '點擊解除鎖定',
|
||||
},
|
||||
CN: {
|
||||
lock: '点击锁定页签头',
|
||||
unlock: '点击解除锁定',
|
||||
},
|
||||
US: {
|
||||
lock: 'click to lock the tabs head',
|
||||
unlock: 'click to unlock',
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
pageList: Array,
|
||||
active: String,
|
||||
fixed: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
affixed: false,
|
||||
}
|
||||
},
|
||||
inject:['adminLayout'],
|
||||
created() {
|
||||
this.affixed = this.fixedTabs
|
||||
},
|
||||
computed: {
|
||||
...mapState('setting', ['layout', 'pageWidth', 'fixedHeader', 'fixedTabs', 'customTitles']),
|
||||
lockTitle() {
|
||||
return this.$t(this.fixedTabs ? 'unlock' : 'lock')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('setting', ['setFixedTabs']),
|
||||
onLockClick() {
|
||||
this.setFixedTabs(!this.fixedTabs)
|
||||
if (this.fixedTabs) {
|
||||
setTimeout(() => {
|
||||
this.affixed = true
|
||||
}, 200)
|
||||
} else {
|
||||
this.affixed = false
|
||||
}
|
||||
},
|
||||
onTabClick(key) {
|
||||
if (this.active !== key) {
|
||||
this.$emit('change', key)
|
||||
}
|
||||
},
|
||||
onClose(key) {
|
||||
this.$emit('close', key)
|
||||
},
|
||||
onRefresh(page) {
|
||||
this.$emit('refresh', page.path, page)
|
||||
},
|
||||
onContextmenu(pageKey, e) {
|
||||
this.$emit('contextmenu', pageKey, e)
|
||||
},
|
||||
pageName(page) {
|
||||
const pagePath = page.fullPath.split('?')[0]
|
||||
const custom = this.customTitles.find(item => item.path === pagePath)
|
||||
return (custom && custom.title) || page.title || this.$t(getI18nKey(page.keyPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.tab{
|
||||
margin: 0 -16px;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
transition: all 0.2s;
|
||||
.title{
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
}
|
||||
.icon-close{
|
||||
font-size: 12px;
|
||||
margin-left: 6px;
|
||||
margin-right: -4px !important;
|
||||
color: @text-color-second;
|
||||
&:hover{
|
||||
color: @text-color;
|
||||
}
|
||||
}
|
||||
.icon-sync{
|
||||
margin-left: -4px;
|
||||
color: @primary-4;
|
||||
transition: all 0.3s ease-in-out;
|
||||
&:hover{
|
||||
color: @primary-color;
|
||||
}
|
||||
font-size: 14px;
|
||||
&.hide{
|
||||
font-size: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabs-head{
|
||||
margin: 0 auto;
|
||||
&.head.fixed{
|
||||
width: 1400px;
|
||||
}
|
||||
}
|
||||
.tabs-container{
|
||||
margin: -16px auto 8px;
|
||||
transition: top,left 0.2s;
|
||||
.header-lock{
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
color: @primary-3;
|
||||
&:hover{
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
&.affixed{
|
||||
margin: 0 auto;
|
||||
top: 0px;
|
||||
padding: 8px 24px 0;
|
||||
position: fixed;
|
||||
height: 48px;
|
||||
z-index: 1;
|
||||
background-color: @layout-body-background;
|
||||
&.side,&.mix{
|
||||
right: 0;
|
||||
left: 200px;
|
||||
&.collapsed{
|
||||
left: 80px;
|
||||
}
|
||||
}
|
||||
&.head{
|
||||
width: inherit;
|
||||
padding: 8px 0 0;
|
||||
&.fluid{
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 8px 24px 0;
|
||||
}
|
||||
}
|
||||
&.fixed-header{
|
||||
top: 64px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.virtual-tabs{
|
||||
height: 48px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,25 @@
|
||||
module.exports = {
|
||||
messages: {
|
||||
TW: {
|
||||
closeLeft: '關閉左側',
|
||||
closeRight: '關閉右側',
|
||||
closeOthers: '關閉其它',
|
||||
refresh: '刷新頁面',
|
||||
warn: '這是最後一頁,不能再關閉了',
|
||||
},
|
||||
CN: {
|
||||
closeLeft: '关闭左侧',
|
||||
closeRight: '关闭右侧',
|
||||
closeOthers: '关闭其它',
|
||||
refresh: '刷新页面',
|
||||
warn: '这是最后一页,不能再关闭了',
|
||||
},
|
||||
US: {
|
||||
closeLeft: 'close left',
|
||||
closeRight: 'close right',
|
||||
closeOthers: 'close others',
|
||||
refresh: 'refresh the page',
|
||||
warn: 'This is the last page, you can\'t close it',
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
import TabsView from './TabsView'
|
||||
export default TabsView
|
||||
@ -0,0 +1,37 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import {initRouter} from './router'
|
||||
import './theme/index.less'
|
||||
import Antd from 'ant-design-vue'
|
||||
import Viser from 'viser-vue'
|
||||
// import '@/mock'
|
||||
import store from './store'
|
||||
import 'animate.css/source/animate.css'
|
||||
import Plugins from '@/plugins'
|
||||
import {initI18n} from '@/utils/i18n'
|
||||
import bootstrap from '@/bootstrap'
|
||||
import 'moment/locale/zh-tw'
|
||||
import CKEditor from 'ckeditor4-vue';
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
|
||||
const router = initRouter(store.state.setting.asyncRoutes)
|
||||
const i18n = initI18n('TW', 'CN', 'US')
|
||||
|
||||
import * as ElResize from 'vue-element-resize-event'
|
||||
Vue.use(ElResize)
|
||||
|
||||
Vue.use(Antd)
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(Viser)
|
||||
Vue.use(Plugins)
|
||||
Vue.use(CKEditor)
|
||||
Vue.use(VueClipboard)
|
||||
|
||||
bootstrap({router, store, i18n, message: Vue.prototype.$message})
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
@ -0,0 +1,32 @@
|
||||
const avatars = [
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/ubnKSIfAJTxIgXOKlciN.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png'
|
||||
]
|
||||
|
||||
const positions = [
|
||||
{
|
||||
CN: 'Java工程师 | 蚂蚁金服-计算服务事业群-微信平台部',
|
||||
TW: 'Java工程師 | 螞蟻金服-計算服務事業群-微信平台部',
|
||||
US: 'Java engineer | Ant financial - Computing services business group - WeChat platform division'
|
||||
},{
|
||||
CN: '前端工程师 | 蚂蚁金服-计算服务事业群-VUE平台',
|
||||
TW: '前端工程師 | 螞蟻金服-計算服務事業群-VUE平台',
|
||||
US: 'Front-end engineer | Ant Financial - Computing services business group - VUE platform'
|
||||
},{
|
||||
CN: '前端工程师 | 蚂蚁金服-计算服务事业群-REACT平台',
|
||||
TW: '前端工程師 | 螞蟻金服-計算服務事業群-REACT平台',
|
||||
US: 'Front-end engineer | Ant Financial - Computing services business group - REACT platform'
|
||||
},{
|
||||
CN: '产品分析师 | 蚂蚁金服-计算服务事业群-IOS平台部',
|
||||
TW: '產品分析師 | 螞蟻金服-計算服務事業群-IOS平台部',
|
||||
US: 'Product analyst | Ant Financial - Computing services business group - IOS platform division'
|
||||
}
|
||||
]
|
||||
|
||||
const admins = ['ICZER', 'JACK', 'LUIS', 'DAVID']
|
||||
|
||||
export {positions, avatars, admins}
|
||||
@ -0,0 +1,46 @@
|
||||
import Mock from 'mockjs'
|
||||
import {positions, avatars, admins} from '../common'
|
||||
|
||||
const Random = Mock.Random
|
||||
|
||||
const timeList = [
|
||||
{
|
||||
CN: '早上好',
|
||||
TW: '早晨啊',
|
||||
US: 'Good morning',
|
||||
},{
|
||||
CN: '上午好',
|
||||
TW: '上午好',
|
||||
US: 'Good morning',
|
||||
},{
|
||||
CN: '中午好',
|
||||
TW: '中午好',
|
||||
US: 'Good afternoon',
|
||||
},{
|
||||
CN: '下午好',
|
||||
TW: '下午好',
|
||||
US: 'Good afternoon',
|
||||
},{
|
||||
CN: '晚上好',
|
||||
TW: '晚上好',
|
||||
US: 'Good evening',
|
||||
}
|
||||
]
|
||||
|
||||
Random.extend({
|
||||
admin () {
|
||||
return this.pick(admins)
|
||||
},
|
||||
timeFix () {
|
||||
const time = new Date()
|
||||
const hour = time.getHours()
|
||||
return hour < 9
|
||||
? timeList[0] : (hour <= 11 ? timeList[1] : (hour <= 13 ? timeList[2] : (hour <= 20 ? timeList[3] : timeList[4])))
|
||||
},
|
||||
avatar () {
|
||||
return this.pick(avatars)
|
||||
},
|
||||
position () {
|
||||
return this.pick(positions)
|
||||
}
|
||||
})
|
||||
@ -0,0 +1,8 @@
|
||||
import Mock from 'mockjs'
|
||||
import '@/mock/user/login'
|
||||
import '@/mock/user/routes'
|
||||
|
||||
// 设置全局延时
|
||||
Mock.setup({
|
||||
timeout: '300-600'
|
||||
})
|
||||
@ -0,0 +1,45 @@
|
||||
import Mock from 'mockjs'
|
||||
|
||||
Mock.mock(`${process.env.VUE_APP_API_BASE_URL}/auth/getRoute`, 'get', () => {
|
||||
console.log('mock routes')
|
||||
let result = {}
|
||||
result.code = 0
|
||||
result.data = [{
|
||||
router: 'root',
|
||||
children: ['demo',
|
||||
{
|
||||
router: 'parent1',
|
||||
children: [{
|
||||
router: 'demo',
|
||||
name: 'demo1',
|
||||
authority: {
|
||||
permission: 'demo',
|
||||
role: 'admin'
|
||||
}
|
||||
}],
|
||||
},
|
||||
{
|
||||
router: 'parent2',
|
||||
children: [{
|
||||
router: 'demo',
|
||||
name: 'demo2'
|
||||
}],
|
||||
},
|
||||
{
|
||||
router: 'exception',
|
||||
children: ['exp404', 'exp403', 'exp500'],
|
||||
},
|
||||
{
|
||||
router: 'demo',
|
||||
icon: 'file-ppt',
|
||||
path: 'auth/demo',
|
||||
name: '验权页面',
|
||||
authority: {
|
||||
permission: 'form',
|
||||
role: 'manager'
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
return result
|
||||
})
|
||||
@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<a-card>
|
||||
<div>
|
||||
<a-space class="operator">
|
||||
</a-space>
|
||||
<standard-table
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:pagination="pagination"
|
||||
@clear="onClear"
|
||||
@change="onChange"
|
||||
@selectedRowChange="onSelectChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
:scroll="{x: 600}"
|
||||
>
|
||||
<div slot="description" slot-scope="{text}">
|
||||
{{text}}
|
||||
</div>
|
||||
|
||||
<div slot="action" slot-scope="{text, record}">
|
||||
<a style="margin-right: 8px">
|
||||
<a-icon type="edit"/>編輯
|
||||
</a>
|
||||
<a @click="deleteRecord(record.key)">
|
||||
<a-icon type="delete" />刪除
|
||||
</a>
|
||||
|
||||
<router-link :to="`/list/query/detail/${record.key}`" >
|
||||
<a-icon type="delete" />詳情
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<template slot="statusTitle">
|
||||
<a-icon @click.native="onStatusTitleClick" type="info-circle" />
|
||||
</template>
|
||||
|
||||
</standard-table>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StandardTable from '@/components/table/StandardTable'
|
||||
|
||||
import { getAdminLogs } from '@/services/admin'
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '編號',
|
||||
width: 50,
|
||||
// dataIndex: 'id'
|
||||
customRender: (text, record, index) => `${index + 1}`
|
||||
},
|
||||
{
|
||||
title: '操作者',
|
||||
dataIndex: 'admin_name'
|
||||
},
|
||||
{
|
||||
title: '操作日期',
|
||||
dataIndex: 'time'
|
||||
},
|
||||
{
|
||||
title: 'IP位址',
|
||||
dataIndex: 'ip',
|
||||
},
|
||||
{
|
||||
title: '操作紀錄',
|
||||
dataIndex: 'content',
|
||||
}
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'AdminLog',
|
||||
components: {StandardTable},
|
||||
data () {
|
||||
return {
|
||||
advanced: true,
|
||||
columns: columns,
|
||||
pagination: {
|
||||
size: 'small',
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: total => `共 ${total} 筆資料`
|
||||
},
|
||||
dataSource: []
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.genTable()
|
||||
},
|
||||
methods: {
|
||||
async genTable(){
|
||||
try{
|
||||
const {data : res} = await getAdminLogs({
|
||||
current: this.pagination.current,
|
||||
size: this.pagination.pageSize,
|
||||
search: this.search
|
||||
})
|
||||
this.pagination.total = res.total
|
||||
this.dataSource = res.data
|
||||
}catch (e){
|
||||
this.dataSource = []
|
||||
}
|
||||
},
|
||||
deleteRecord(key) {
|
||||
this.dataSource = this.dataSource.filter(item => item.key !== key)
|
||||
},
|
||||
toggleAdvanced () {
|
||||
this.advanced = !this.advanced
|
||||
},
|
||||
remove () {
|
||||
},
|
||||
onClear() {
|
||||
this.$message.info('您清空了勾選的所有行')
|
||||
},
|
||||
onStatusTitleClick() {
|
||||
this.$message.info('你點選了狀態列表頭')
|
||||
},
|
||||
onChange() {
|
||||
this.$message.info('表格狀態改變了')
|
||||
},
|
||||
onSelectChange() {
|
||||
this.$message.info('選中行改變了')
|
||||
},
|
||||
addNew () {
|
||||
this.dataSource.unshift({
|
||||
key: this.dataSource.length,
|
||||
no: 'NO ' + this.dataSource.length,
|
||||
description: '這是一段描述',
|
||||
callNo: Math.floor(Math.random() * 1000),
|
||||
status: Math.floor(Math.random() * 10) % 4,
|
||||
updatedAt: '2018-07-26'
|
||||
})
|
||||
},
|
||||
handleMenuClick (e) {
|
||||
if (e.key === 'delete') {
|
||||
this.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search{
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
.fold{
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block
|
||||
}
|
||||
.operator{
|
||||
// margin-bottom: 18px;
|
||||
}
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-card>
|
||||
<div>
|
||||
<a-space class="operator">
|
||||
<a-button @click="handleAddDraw" type="primary" size="small">新增</a-button>
|
||||
</a-space>
|
||||
<standard-table
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:pagination="false"
|
||||
@clear="onClear"
|
||||
@change="onChange"
|
||||
@selectedRowChange="onSelectChange"
|
||||
:scroll="{x: 600}"
|
||||
>
|
||||
|
||||
<div slot="action" slot-scope="{text, record}">
|
||||
<a class="edit-btn" @click="handleEditDraw(record.id)" style="margin-right: 8px">
|
||||
<a-icon type="edit"/>編輯
|
||||
</a>
|
||||
<a class="delete-btn" @click="deleteRole(record.id)">
|
||||
<a-icon type="delete" />刪除
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</standard-table>
|
||||
</div>
|
||||
</a-card>
|
||||
<add-form
|
||||
:visible="showAddDraw"
|
||||
@close="onAddDrawClose"
|
||||
@submit="onAddSubmit"
|
||||
>
|
||||
</add-form>
|
||||
<edit-form
|
||||
:editid="editId"
|
||||
:visible="showEditDraw"
|
||||
@close="onEditDrawClose"
|
||||
@submit="onEditSubmit"
|
||||
>
|
||||
</edit-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StandardTable from '@/components/table/StandardTable'
|
||||
import AddForm from './components/AddForm.vue'
|
||||
import EditForm from './components/EditForm.vue'
|
||||
|
||||
import {getRoles, deleteRole} from '@/services/role'
|
||||
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '#',
|
||||
width: 50,
|
||||
customRender: (text, record, index) => `${index + 1}`
|
||||
},
|
||||
{
|
||||
title: '角色名稱',
|
||||
width: 200,
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: '角色描述',
|
||||
dataIndex: 'desc',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 180,
|
||||
scopedSlots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'RoleList',
|
||||
i18n: require('./i18n'),
|
||||
components: {StandardTable, AddForm, EditForm},
|
||||
data () {
|
||||
return {
|
||||
advanced: true,
|
||||
editId: 0,
|
||||
columns: columns,
|
||||
showAddDraw: false,
|
||||
showEditDraw: false,
|
||||
dataSource: [],
|
||||
}
|
||||
},
|
||||
async mounted(){
|
||||
this.genTable()
|
||||
},
|
||||
authorize: {
|
||||
// deleteRecord: 'delete'
|
||||
},
|
||||
methods: {
|
||||
async genTable(){
|
||||
try{
|
||||
const res = await getRoles()
|
||||
this.dataSource = res.data.data
|
||||
|
||||
}catch (e){
|
||||
this.dataSource = []
|
||||
}
|
||||
},
|
||||
deleteRole(id) {
|
||||
console.log(id)
|
||||
this.$confirm({
|
||||
title: '確認刪除?',
|
||||
content: '確定刪除此筆資料',
|
||||
okText: '確定',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
let res = await deleteRole({id:id})
|
||||
if(res.code === 200){
|
||||
this.genTable()
|
||||
this.$message.success('刪除成功')
|
||||
}
|
||||
},
|
||||
onCancel() {
|
||||
return false
|
||||
},
|
||||
});
|
||||
},
|
||||
toggleAdvanced () {
|
||||
this.advanced = !this.advanced
|
||||
},
|
||||
remove () {
|
||||
},
|
||||
onClear() {
|
||||
this.$message.info('您清空了勾選的所有行')
|
||||
},
|
||||
onStatusTitleClick() {
|
||||
this.$message.info('你點選了狀態列表頭')
|
||||
},
|
||||
onChange() {
|
||||
this.$message.info('表格狀態改變了')
|
||||
},
|
||||
onSelectChange() {
|
||||
this.$message.info('選中行改變了')
|
||||
},
|
||||
//新增
|
||||
handleAddDraw () {
|
||||
this.showAddDraw = true
|
||||
},
|
||||
onAddDrawClose() {
|
||||
this.showAddDraw = false
|
||||
},
|
||||
onAddSubmit () {
|
||||
this.genTable()
|
||||
this.showAddDraw = false
|
||||
this.$message.success('新增成功')
|
||||
},
|
||||
//編輯
|
||||
handleEditDraw(id) {
|
||||
this.editId = id
|
||||
this.showEditDraw = true
|
||||
},
|
||||
onEditDrawClose() {
|
||||
this.editId=0
|
||||
this.showEditDraw = false
|
||||
},
|
||||
onEditSubmit () {
|
||||
this.editId=0
|
||||
this.genTable()
|
||||
this.showEditDraw = false
|
||||
this.$message.success('編輯成功')
|
||||
},
|
||||
handleMenuClick (e) {
|
||||
if (e.key === 'delete') {
|
||||
this.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search{
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
.fold{
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block
|
||||
}
|
||||
.operator{
|
||||
// margin-bottom: 18px;
|
||||
}
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-drawer
|
||||
title="新增角色"
|
||||
:destroyOnClose="true"
|
||||
:visible="visible"
|
||||
:body-style="{ paddingBottom: '80px' }"
|
||||
@close="onClose"
|
||||
>
|
||||
<a-form-model
|
||||
ref="ruleForm"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-model-item label="角色名稱" ref="name" prop="name">
|
||||
<a-input
|
||||
v-model="form.name"
|
||||
@blur="
|
||||
() => {
|
||||
$refs.name.onFieldBlur();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item label="角色描述">
|
||||
<a-input v-model="form.desc" type="textarea" />
|
||||
</a-form-model-item>
|
||||
<!-- <a-form-model-item label="角色權限">
|
||||
<a-tree
|
||||
v-model="form.permission"
|
||||
checkable
|
||||
:tree-data="treeData"
|
||||
defaultExpandAll
|
||||
@select="onSelect"
|
||||
@check="onCheck"
|
||||
>
|
||||
<span slot="title0010" style="color: #1890ff">sss</span>
|
||||
</a-tree>
|
||||
</a-form-model-item> -->
|
||||
|
||||
</a-form-model>
|
||||
<div
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
borderTop: '1px solid #e9e9e9',
|
||||
padding: '10px 16px',
|
||||
background: '#fff',
|
||||
textAlign: 'right',
|
||||
zIndex: 1,
|
||||
}"
|
||||
>
|
||||
<a-button :style="{ marginRight: '8px' }" @click="onClose">
|
||||
關閉
|
||||
</a-button>
|
||||
<a-button type="primary" @click="onSubmit">
|
||||
送出
|
||||
</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {addRole} from '@/services/role'
|
||||
// import {getTree} from '@/services/right'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'AddForm',
|
||||
data(){
|
||||
return {
|
||||
labelCol: { span: 8 },
|
||||
wrapperCol: { span: 16 },
|
||||
treeData: [],
|
||||
form: {
|
||||
name:'',
|
||||
desc:'',
|
||||
permission: [],
|
||||
status: true,
|
||||
},
|
||||
rules: {
|
||||
name: [
|
||||
{ required: true, message: '請輸入角色名稱', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
visible: Boolean
|
||||
},
|
||||
async created(){
|
||||
// let res = await getTree()
|
||||
// console.log(res.data)
|
||||
// this.treeData = res.data
|
||||
},
|
||||
methods:{
|
||||
onClose(){
|
||||
this.$refs.ruleForm.resetFields();
|
||||
this.$emit('close',true)
|
||||
},
|
||||
onSubmit(){
|
||||
console.log(this.form)
|
||||
this.$refs.ruleForm.validate(async valid => {
|
||||
if (valid) {
|
||||
let res = await addRole(this.form)
|
||||
if(res.code===200){
|
||||
this.$refs.ruleForm.resetFields();
|
||||
this.$emit('submit',true)
|
||||
}else{
|
||||
this.$message.error('新增失敗')
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
onSelect(selectedKeys, info) {
|
||||
console.log('selected', selectedKeys, info);
|
||||
},
|
||||
onCheck(checkedKeys, info) {
|
||||
console.log('onCheck', checkedKeys, info);
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-drawer-header{
|
||||
background-color: #87e8de !important;
|
||||
.ant-drawer-title{
|
||||
color: #FFF !important;
|
||||
}
|
||||
}
|
||||
.ant-drawer-content-wrapper{
|
||||
width: 50% !important;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-drawer
|
||||
title="編輯角色"
|
||||
:destroyOnClose="true"
|
||||
:visible="visible"
|
||||
:body-style="{ paddingBottom: '80px' }"
|
||||
@close="onClose"
|
||||
>
|
||||
<a-spin :spinning="spinning">
|
||||
<div class="spin-content">
|
||||
<a-form-model
|
||||
ref="ruleForm"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-model-item label="角色名稱" ref="name" prop="name">
|
||||
<a-input
|
||||
v-model="form.name"
|
||||
@blur="
|
||||
() => {
|
||||
$refs.name.onFieldBlur();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item label="角色描述">
|
||||
<a-input v-model="form.desc" type="textarea" />
|
||||
</a-form-model-item>
|
||||
<!-- <a-form-model-item label="角色權限">
|
||||
<a-tree
|
||||
v-model="form.permission"
|
||||
checkable
|
||||
:tree-data="treeData"
|
||||
defaultExpandAll
|
||||
@select="onSelect"
|
||||
@check="onCheck"
|
||||
>
|
||||
<span slot="title0010" style="color: #1890ff">sss</span>
|
||||
</a-tree>
|
||||
</a-form-model-item> -->
|
||||
|
||||
</a-form-model>
|
||||
</div>
|
||||
</a-spin>
|
||||
<div
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
borderTop: '1px solid #e9e9e9',
|
||||
padding: '10px 16px',
|
||||
background: '#fff',
|
||||
textAlign: 'right',
|
||||
zIndex: 1,
|
||||
}"
|
||||
>
|
||||
<a-button :style="{ marginRight: '8px' }" @click="onClose">
|
||||
關閉
|
||||
</a-button>
|
||||
<a-button type="primary" @click="onSubmit">
|
||||
送出
|
||||
</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getRoleById, updateRole} from '@/services/role'
|
||||
// import {getTree} from '@/services/right'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'EditForm',
|
||||
data(){
|
||||
return {
|
||||
spinning: false,
|
||||
labelCol: { span: 8 },
|
||||
wrapperCol: { span: 16 },
|
||||
treeData: [],
|
||||
form: {},
|
||||
rules: {
|
||||
name: [
|
||||
{ required: true, message: '請輸入角色名稱', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
visible: Boolean,
|
||||
editid: Number,
|
||||
},
|
||||
async created(){
|
||||
// let res = await getTree()
|
||||
// console.log(res.data)
|
||||
// this.treeData = res.data
|
||||
},
|
||||
watch: {
|
||||
editid: async function (val) {
|
||||
if(val){
|
||||
this.spinning=true
|
||||
let res=await getRoleById({id: val})
|
||||
this.form = res.data
|
||||
this.spinning=false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
onClose(){
|
||||
this.form={}
|
||||
this.$refs.ruleForm.resetFields();
|
||||
this.$emit('close',true)
|
||||
},
|
||||
onSubmit(){
|
||||
console.log(this.form)
|
||||
this.$refs.ruleForm.validate(async valid => {
|
||||
if (valid || 1===2) {
|
||||
let res = await updateRole(this.form)
|
||||
if(res.code===200){
|
||||
this.$refs.ruleForm.resetFields();
|
||||
this.$emit('submit',true)
|
||||
}else{
|
||||
this.$message.error('編輯失敗')
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
onSelect(selectedKeys, info) {
|
||||
console.log('selected', selectedKeys, info);
|
||||
},
|
||||
onCheck(checkedKeys, info) {
|
||||
// console.log(checkedKeys.concat(info.halfCheckedKeys))
|
||||
// this.form.permission=checkedKeys.concat(info.halfCheckedKeys)
|
||||
console.log('onCheck', checkedKeys, info);
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-drawer-header{
|
||||
background-color: #87e8de !important;
|
||||
.ant-drawer-title{
|
||||
color: #FFF !important;
|
||||
}
|
||||
}
|
||||
.ant-drawer-content-wrapper{
|
||||
width: 50% !important;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue