This commit is contained in:
ky_sunl
2021-04-10 14:59:47 +00:00
parent cb7e07922f
commit d3c4cc3077
63 changed files with 21123 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
VUE_APP_BASE_URL=http://localhost:60161
VUE_APP_DEV_KEY=CJm9dFWx4IIGXHm^R1e@Y&mp*n8MQXfDKjDYP6ZVGqEAZQiC9LvX9jq8@uaMTT@T

View File

@@ -0,0 +1,2 @@
VUE_APP_BASE_URL=http://localhost:60161
VUE_APP_DEV_KEY=%0!qF2BpcVorlNceu#kP4SVS1bPiMUqI71%rITatPIosNOCrot@mV7PJ&br$CVvF

View File

@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,24 @@
# ewidecoreweb
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@@ -0,0 +1,53 @@
{
"name": "ewidecoreweb",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"ant-design-vue": "^1.7.2",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"crypto-js": "^4.0.0",
"echarts": "^5.0.2",
"less": "^3.12.2",
"less-loader": "4.1.0",
"lodash": "^4.17.21",
"nprogress": "^0.2.0",
"swiper": "^6.5.0",
"vue": "^2.6.11",
"vue-awesome-swiper": "^4.1.1",
"vue-router": "^3.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<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">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</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="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -0,0 +1,11 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
};
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,22 @@
@import '~ant-design-vue/dist/antd.less';
@import './lib/visibility.less';
@import './lib/container.less';
@import './lib/align.less';
@import './lib/font-size.less';
@import './lib/margin.less';
@import './lib/scrollbar.less';
@import './main.less';
@import './frame/dark.less';
@import './frame/light.less';
.yo-nav-theme--dark {
.dark();
}
.yo-nav-theme--light {
.light();
}
@import './lib/card.less';
@import './lib/table.less';
@import './lib/form.less';
@import './lib/select.less';
@import './theme/primary.less';
@import './lib/font-weight.less';

View File

@@ -0,0 +1,15 @@
@import (reference) '~@/assets/style/main.less';
.dark {
.main(@nav-background: @layout-header-background;
@nav-box-shadow-color: fade(@black, 25%);
@logo-color: @white;
@logo-box-shadow: none;
@header-action-color: fade(@white, 60%);
@header-action-hover-color: @white;
@header-action-hover-background: fade(@white, 20%);
@header-search-color: @white;
@header-search-background: fade(@white, 15%);
@header-search-focus-background: fade(@white, 30%);
@header-search-icon-color: fade(@white, 60%);
@header-search-icon-hover-color: @white);
}

View File

@@ -0,0 +1,15 @@
@import (reference) '~@/assets/style/main.less';
.light {
.main(@nav-background: @white;
@nav-box-shadow-color: fade(@black, 5%);
@logo-color: @black;
@logo-box-shadow: inset -1px -1px 1px @border-color-split;
@header-action-color: fade(@black, 35%);
@header-action-hover-color: @icon-color-hover;
@header-action-hover-background: fade(@black, 5%);
@header-search-color: @black;
@header-search-background: @white;
@header-search-focus-background: fade(@black, 5%);
@header-search-icon-color: fade(@black, 45%);
@header-search-icon-hover-color: fade(@black, 80%));
}

View File

@@ -0,0 +1,9 @@
.text-left {
text-align: left !important;
}
.text-center {
text-align: center !important;
}
.text-right {
text-align: right !important;
}

View File

@@ -0,0 +1,4 @@
@import (reference) '~ant-design-vue/dist/antd.less';
.ant-card {
margin-bottom: @padding-md;
}

View File

@@ -0,0 +1,10 @@
@import (reference) '~ant-design-vue/dist/antd.less';
@container-width: 1200px;
.container {
width: @container-width;
margin: 0 auto;
padding: 0 @padding-md;
}
.container-fluid {
padding: 0 @padding-md;
}

View File

@@ -0,0 +1 @@
@import (reference) '~ant-design-vue/dist/antd.less';

View File

@@ -0,0 +1,17 @@
body {
font-weight: 100;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 300;
}
.ant-card-meta-title {
font-weight: inherit;
}
.ant-table-thead > tr > th {
font-weight: 500;
}

View File

@@ -0,0 +1,24 @@
@import (reference) '~ant-design-vue/dist/antd.less';
body {
font-weight: 100;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 300;
}
@btn-font-weight: 100;
.ant-card-meta-title {
font-weight: inherit;
}
.ant-table-thead {
>tr {
>th {
font-weight: 500;
}
}
}

View File

@@ -0,0 +1,37 @@
@import (reference) '~ant-design-vue/dist/antd.less';
.yo-form {
width: 660px;
margin: 0 auto;
.yo-form-group {
margin-bottom: @padding-md;
}
.ant-form-item {
display: flex;
justify-content: space-between;
margin-bottom: -1px;
padding: @padding-xs @padding-md;
border: @border-width-base @border-style-base @border-color-split;
background-color: @white;
&::before,
&::after {
content: none;
}
.ant-input,
.ant-select-selection {
border-color: transparent;
background-color: #f1f3f4;
}
.ant-select-focused,
.ant-select-open {
.ant-select-selection {
box-shadow: 0 0 0 2px fade(@primary-color, 50%);
}
}
}
.ant-form-item-control-wrapper {
width: 50%;
min-width: 220px;
}
}

View File

@@ -0,0 +1,52 @@
@import (reference) '~ant-design-vue/dist/antd.less';
@margin-padding-position: ~'', ~'-top', ~'-left', ~'-right', ~'-bottom';
@margin-padding-position-name: ~'', ~'t', ~'l', ~'r', ~'b';
.margin-padding (@i) when (@i <=length(@margin-padding-position)) {
@position: extract(@margin-padding-position, @i);
@name: extract(@margin-padding-position-name, @i);
.m@{name}-lg {
margin@{position}: @padding-lg !important;
}
.m@{name}-md {
margin@{position}: @padding-md !important;
}
.m@{name}-sm {
margin@{position}: @padding-sm !important;
}
.m@{name}-xs {
margin@{position}: @padding-xs !important;
}
.p@{name}-lg {
padding@{position}: @padding-lg !important;
}
.p@{name}-md {
padding@{position}: @padding-md !important;
}
.p@{name}-sm {
padding@{position}: @padding-sm !important;
}
.p@{name}-xs {
padding@{position}: @padding-xs !important;
}
.m@{name}-none {
margin@{position}: 0 !important;
}
.p@{name}-none {
padding@{position}: 0 !important;
}
.margin-padding(@i + 1);
}
.margin-padding(1);

View File

@@ -0,0 +1,14 @@
@import (reference) '~ant-design-vue/dist/antd.less';
::-webkit-scrollbar {
width: 7px;
height: 7px;
background-color: lighten(@primary-color, 35%);
}
::-webkit-scrollbar-thumb {
border-radius: @border-radius-base;
background-color: fade(@primary-color, 70%);
}
::-webkit-scrollbar-thumb:active {
background-color: @primary-color;
}

View File

@@ -0,0 +1,6 @@
@import (reference) '~ant-design-vue/dist/antd.less';
.ant-select-arrow {
.ant-select-arrow-icon {
transform: scaleY(.75);
}
}

View File

@@ -0,0 +1,48 @@
@import (reference) '~ant-design-vue/dist/antd.less';
.yo-query-bar {
margin-bottom: @padding-md;
}
.yo-action-bar {
display: flex;
justify-content: space-between;
margin-bottom: @padding-md;
&--actions {
>.ant-btn,
>.ant-btn-group {
+.ant-btn,
+.ant-btn-group {
margin-left: @padding-xs;
}
}
}
}
.ant-table {
background-color: @white;
.yo-action-bar {
margin-bottom: 0;
}
}
.ant-table-small {
>.ant-table-content {
>.ant-table-body {
margin: 0;
>table {
>.ant-table-thead {
>tr {
>th {
background-color: @table-selected-row-bg;
}
}
}
}
}
}
}
.ant-table-thead {
>tr {
>th {
font-weight: bold;
}
}
}

View File

@@ -0,0 +1,6 @@
.hide {
visibility: hidden !important;
}
.hidden {
display: none !important;
}

View File

@@ -0,0 +1,388 @@
@import (reference) '~ant-design-vue/dist/antd.less';
@import (reference) './lib/container.less';
.main(@nav-background: @layout-header-background,
@nav-box-shadow-color: fade(@black, 25%),
@logo-color: @white,
@logo-box-shadow: none,
@header-action-color: fade(@white, 60%),
@header-action-hover-color: @white,
@header-action-hover-background: fade(@white, 20%),
@header-search-color: @white,
@header-search-background: fade(@white, 15%),
@header-search-focus-background: fade(@white, 30%),
@header-search-icon-color: fade(@white, 60%),
@header-search-icon-hover-color: @white,
) {
.ant-layout-header {
.header-actions {
display: flex;
.header-action {
display: inline-block;
padding: 0 @padding-md;
cursor: pointer;
transition: @animation-duration-slow;
transition-property: background-color;
.anticon {
font-size: @font-size-base + 6px;
transition: @animation-duration-slow;
transition-property: color;
}
&:active {
box-shadow: inset 1px 1px 10px rgba(0, 0, 0, .05);
}
}
>.ant-input-search {
display: flex;
align-items: center;
margin: 5px @padding-md;
.ant-input {
height: 34px;
padding: 5px 30px 5px 11px;
transition: @animation-duration-slow;
transition-property: background-color;
border-color: transparent;
&:focus {
box-shadow: none;
}
}
}
}
}
.ant-layout-content {
position: relative;
>.ant-tabs {
position: absolute;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
width: 100%;
>.ant-tabs-bar {
z-index: 6;
margin-bottom: 0;
border-bottom: 0;
background-color: @white;
box-shadow: 0 2px 12px rgba(0, 0, 0, .05), inset 0 -1px 1px rgba(0, 0, 0, .05);
&::before {
content: none;
}
.ant-tabs-nav-container {
height: 30px;
margin-bottom: 0;
}
.ant-tabs-tab {
line-height: 30px;
height: 30px;
margin-right: 0;
padding: 0;
transition: none;
border: 0;
background-color: transparent;
&:hover {
color: @black;
}
&.ant-tabs-tab-active {
color: @white;
border-color: darken(@primary-color, 10%);
background-color: @primary-color;
.ant-tabs-close-x {
color: fade(@white, 70%);
&:hover {
color: @white;
}
}
}
+.ant-tabs-tab {
margin-left: 0;
}
.ant-dropdown-trigger {
padding: 0 @padding-md * 2 0 @padding-md;
}
.ant-tabs-tab-unclosable {
.ant-dropdown-trigger {
padding: 0 @padding-lg 0 @padding-md;
}
}
.ant-tabs-close-x {
position: absolute;
top: 9px;
right: 9px;
margin: 0;
transition: none;
}
}
}
>.ant-tabs-content {
position: relative;
height: 100%;
>.ant-tabs-tabpane {
position: absolute;
top: 0;
left: 0;
overflow-x: hidden;
overflow-y: auto;
width: 100%;
height: 100%;
>.ant-spin {
position: absolute;
top: 0;
left: 0;
display: flex;
overflow: auto;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
}
}
}
}
.yo-layout--left-menu,
.yo-layout--right-menu {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
.ant-layout-header {
line-height: @layout-header-height - 20px;
z-index: 5;
height: @layout-header-height - 20px;
padding: 0;
background-color: @white;
>section {
display: flex;
justify-content: space-between;
}
.header-actions {
.header-action {
line-height: @layout-header-height - 20px;
.anticon {
color: fade(@black, 35%);
}
&:hover {
background-color: fade(@black, 5%);
.anticon {
color: @icon-color-hover;
}
}
}
>.ant-input-search {
.ant-input {
&:focus {
background-color: fade(@black, 5%);
}
}
}
}
}
.ant-layout-sider {
height: 100%;
background-color: @nav-background;
.ant-layout-sider-children {
display: flex;
flex-direction: column;
}
.logo {
font-size: @font-size-lg * 1.5;
font-weight: 500;
line-height: @layout-header-height + 10px;
z-index: 5;
display: flex;
overflow: hidden;
align-items: center;
flex: 0 0 @layout-header-height + 10px;
height: @layout-header-height + 10px;
padding: 0 @padding-md 0 @padding-lg;
color: @logo-color;
background-color: @nav-background;
box-shadow: @logo-box-shadow;
img {
max-height: 100%;
}
span {
margin-left: @padding-sm;
transition: @animation-duration-slow;
transition-property: opacity;
}
}
&.ant-layout-sider-collapsed {
.logo {
span {
opacity: 0;
}
}
}
.swiper-container {
flex: 1 1 100%;
width: 100%;
height: 100%;
box-shadow: 2px 0 8px @nav-box-shadow-color;
}
.swiper-slide {
height: auto;
}
}
}
.yo-layout--top-nav {
position: absolute;
top: 0;
left: 0;
display: flex;
flex-direction: column;
width: 100%;
min-width: @container-width;
height: 100%;
.ant-layout-header {
flex: 0 0 @layout-header-height;
padding: 0;
background-color: @nav-background;
section {
display: flex;
justify-content: space-between;
height: 100%;
}
.header-actions {
.header-action {
.anticon {
color: @header-action-color;
}
&:hover {
background-color: @header-action-hover-background;
.anticon {
color: @header-action-hover-color;
}
}
}
>.ant-input-search {
.ant-input {
color: @header-search-color;
background-color: @header-search-background;
&:focus {
background-color: @header-search-focus-background;
}
}
.ant-input-search-icon {
color: @header-search-icon-color;
&:hover {
color: @header-search-icon-hover-color;
}
}
}
}
.logo {
font-size: @font-size-lg * 1.5;
font-weight: 500;
line-height: @layout-header-height;
display: flex;
overflow: hidden;
align-items: center;
height: @layout-header-height;
margin-right: @padding-lg;
color: @logo-color;
img {
max-height: 100%;
}
span {
margin-left: @padding-sm;
}
}
.ant-menu-horizontal {
line-height: @layout-header-height;
>.ant-menu-submenu {
top: 0;
border-bottom: 0;
}
}
.header-actions {
.header-action {
line-height: @layout-header-height - 16px;
margin: 10px 0;
}
}
}
.ant-layout-content {
>.ant-tabs {
>.ant-tabs-bar {
.ant-tabs-nav-scroll {
text-align: center;
}
}
}
}
&--container {
.ant-layout-header {
.ant-menu-horizontal {
width: 600px;
}
}
.ant-layout-content {
>.ant-tabs {
>.ant-tabs-bar {
.ant-tabs-nav-container {
width: @container-width - @padding-md * 2;
margin: 0 auto;
}
}
}
}
}
&--container-fluid {
.ant-layout-header {
.ant-menu-horizontal {
width: 800px;
}
@media (max-width: 1400px) {
.ant-menu-horizontal {
width: 600px;
}
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
@import '~@/assets/style/app.less';
@font-size-base: 13px;
@border-radius-base: 0;

View File

@@ -0,0 +1,91 @@
/**
* api
* v1.2
*/
import axios from 'axios'
import { token } from '@/common/token'
import status from './status'
import app from '@/main'
axios.defaults.baseURL = process.env.VUE_APP_BASE_URL + '/api'
/**
* 最终直接根据url名称调用接口方法
* 例如
* import { api } from '@/api'
* api.getItemGroupType(parmas).then(...)
*/
import urls from './requests'
const initInstance = function () {
const instance = axios
.create({
headers: {
Authorization: 'Bearer ' + token.value
}
})
instance.interceptors.response.use((res) => {
if (res.data.status === status.Unauthorized) {
token.value = ''
app.$router.replace({
path: '/login'
}).catch(() => { })
}
return res
}, (err) => {
return Promise.reject(err)
})
return instance
}
const api = {}
for (let key in urls) {
api[`${key}E`] = function (params = {}) {
return initInstance().post(urls[key], params)
}
api[key] = function (params = {}) {
return new Promise((reslove, reject) => {
api[`${key}E`](params)
.then(({ data }) => {
if (data.status === status.OK) {
reslove(data)
} else {
reject(data)
}
})
.catch(err => {
if (process.env.PROD) {
alert('发生错误,请联系管理员')
} else {
console.warn(err, urls[key])
}
})
})
}
api[key].prototype.name = key
}
/**
* 并发请求,与axios.all方式相同
* 但是使用的接口函数为this.$api.[接口名]E
*/
api.$queue = function (queue) {
return new Promise((reslove) => {
axios.all(queue).then(axios.spread(function () {
const res = Array.prototype.slice.apply(arguments).map(p => p.data)
reslove(res)
}))
})
}
export {
axios,
api,
status
}

View File

@@ -0,0 +1,7 @@
export default {
login: '/gate/login',
getMenu: '/menu/get',
test: '/gate/allownullapi'
}

View File

@@ -0,0 +1,206 @@
export default {
//
// 摘要:
// 等效于 HTTP 状态 100。 System.Net.HttpStatusCode.Continue 指示客户端可以继续其请求。
Continue: 100,
//
// 摘要:
// 等效于 HTTP 状态为 101。 System.Net.HttpStatusCode.SwitchingProtocols 指示正在更改的协议版本或协议。
SwitchingProtocols: 101,
//
// 摘要:
// 等效于 HTTP 状态 200。 System.Net.HttpStatusCode.OK 指示请求成功,且请求的信息包含在响应中。 这是要接收的最常见状态代码。
OK: 200,
//
// 摘要:
// 等效于 HTTP 状态 201。 System.Net.HttpStatusCode.Created 指示请求导致已发送响应之前创建一个新的资源。
Created: 201,
//
// 摘要:
// 等效于 HTTP 状态 202。 System.Net.HttpStatusCode.Accepted 指示请求已被接受进行进一步处理。
Accepted: 202,
//
// 摘要:
// 等效于 HTTP 状态 203。 System.Net.HttpStatusCode.NonAuthoritativeInformation 指示返回的元信息来自而不是原始服务器的缓存副本,因此可能不正确。
NonAuthoritativeInformation: 203,
//
// 摘要:
// 等效于 HTTP 状态 204。 System.Net.HttpStatusCode.NoContent 指示已成功处理请求和响应是有意留为空白。
NoContent: 204,
//
// 摘要:
// 等效于 HTTP 状态 205。 System.Net.HttpStatusCode.ResetContent 指示客户端应重置 (而不是重新加载) 的当前资源。
ResetContent: 205,
//
// 摘要:
// 等效于 HTTP 206 状态。 System.Net.HttpStatusCode.PartialContent 指示根据包括字节范围的 GET 请求的请求的响应是部分响应。
PartialContent: 206,
//
// 摘要:
// 等效于 HTTP 状态 300。 System.Net.HttpStatusCode.MultipleChoices 指示所需的信息有多种表示形式。 默认操作是将此状态视为一个重定向,并按照与此响应关联的位置标头的内容。
MultipleChoices: 300,
//
// 摘要:
// 等效于 HTTP 状态 300。 System.Net.HttpStatusCode.Ambiguous 指示所需的信息有多种表示形式。 默认操作是将此状态视为一个重定向,并按照与此响应关联的位置标头的内容。
Ambiguous: 300,
//
// 摘要:
// 等效于 HTTP 状态 301。 System.Net.HttpStatusCode.MovedPermanently 指示已将所需的信息移动到的位置标头中指定的
// URI。 当收到此状态时的默认操作是遵循与响应关联的位置标头。
MovedPermanently: 301,
//
// 摘要:
// 等效于 HTTP 状态 301。 System.Net.HttpStatusCode.Moved 指示已将所需的信息移动到的位置标头中指定的 URI。 当收到此状态时的默认操作是遵循与响应关联的位置标头。
// 当原始请求方法是 POST 时,重定向的请求将使用 GET 方法。
Moved: 301,
//
// 摘要:
// 等效于 HTTP 状态 302。 System.Net.HttpStatusCode.Found 指示所需的信息位于的位置标头中指定的 URI。 当收到此状态时的默认操作是遵循与响应关联的位置标头。
// 当原始请求方法是 POST 时,重定向的请求将使用 GET 方法。
Found: 302,
//
// 摘要:
// 等效于 HTTP 状态 302。 System.Net.HttpStatusCode.Redirect 指示所需的信息位于的位置标头中指定的 URI。 当收到此状态时的默认操作是遵循与响应关联的位置标头。
// 当原始请求方法是 POST 时,重定向的请求将使用 GET 方法。
Redirect: 302,
//
// 摘要:
// 等效于 HTTP 状态 303。 System.Net.HttpStatusCode.SeeOther 自动将客户端重定向到的位置标头中指定作为公告的结果的
// URI。 对指定的位置标头的资源的请求将会执行与 GET。
SeeOther: 303,
//
// 摘要:
// 等效于 HTTP 状态 303。 System.Net.HttpStatusCode.RedirectMethod 自动将客户端重定向到的位置标头中指定作为公告的结果的
// URI。 对指定的位置标头的资源的请求将会执行与 GET。
RedirectMethod: 303,
//
// 摘要:
// 等效于 HTTP 状态 304。 System.Net.HttpStatusCode.NotModified 指示客户端的缓存的副本是最新。 不会传输资源的内容。
NotModified: 304,
//
// 摘要:
// 等效于 HTTP 状态 305。 System.Net.HttpStatusCode.UseProxy 指示该请求应使用的位置标头中指定的 uri 的代理服务器。
UseProxy: 305,
//
// 摘要:
// 等效于 HTTP 状态 306。 System.Net.HttpStatusCode.Unused 是对未完全指定的 HTTP/1.1 规范建议的扩展。
Unused: 306,
//
// 摘要:
// 等效于 HTTP 状态 307。 System.Net.HttpStatusCode.TemporaryRedirect 指示请求信息位于的位置标头中指定的
// URI。 当收到此状态时的默认操作是遵循与响应关联的位置标头。 当原始请求方法是 POST 时,重定向的请求还将使用 POST 方法。
TemporaryRedirect: 307,
//
// 摘要:
// 等效于 HTTP 状态 307。 System.Net.HttpStatusCode.RedirectKeepVerb 指示请求信息位于的位置标头中指定的
// URI。 当收到此状态时的默认操作是遵循与响应关联的位置标头。 当原始请求方法是 POST 时,重定向的请求还将使用 POST 方法。
RedirectKeepVerb: 307,
//
// 摘要:
// 等效于 HTTP 状态 400。 System.Net.HttpStatusCode.BadRequest 指示无法由服务器理解此请求。 System.Net.HttpStatusCode.BadRequest
// 如果没有其他错误适用,或者如果具体的错误是未知的或不具有其自己的错误代码发送。
BadRequest: 400,
//
// 摘要:
// 等效于 HTTP 状态 401。 System.Net.HttpStatusCode.Unauthorized 指示所请求的资源需要身份验证。 Www-authenticate
// 标头包含如何执行身份验证的详细信息。
Unauthorized: 401,
//
// 摘要:
// 等效于 HTTP 状态 402。 System.Net.HttpStatusCode.PaymentRequired 已保留供将来使用。
PaymentRequired: 402,
//
// 摘要:
// 等效于 HTTP 状态 403。 System.Net.HttpStatusCode.Forbidden 指示服务器拒绝无法完成请求。
Forbidden: 403,
//
// 摘要:
// 等效于 HTTP 状态 404。 System.Net.HttpStatusCode.NotFound 指示所请求的资源不存在的服务器上。
NotFound: 404,
//
// 摘要:
// 等效于 HTTP 状态 405。 System.Net.HttpStatusCode.MethodNotAllowed 指示请求方法 POST 或 GET
// 不允许对所请求的资源。
MethodNotAllowed: 405,
//
// 摘要:
// 等效于 HTTP 状态 406。 System.Net.HttpStatusCode.NotAcceptable 表示客户端已指定使用 Accept 标头,它将不接受任何可用的资源表示。
NotAcceptable: 406,
//
// 摘要:
// 等效于 HTTP 状态 407。 System.Net.HttpStatusCode.ProxyAuthenticationRequired 指示请求的代理要求身份验证。
// 代理服务器进行身份验证标头包含如何执行身份验证的详细信息。
ProxyAuthenticationRequired: 407,
//
// 摘要:
// 等效于 HTTP 状态 408。 System.Net.HttpStatusCode.RequestTimeout 指示客户端的服务器预期请求的时间内没有未发送请求。
RequestTimeout: 408,
//
// 摘要:
// 等效于 HTTP 状态 409。 System.Net.HttpStatusCode.Conflict 指示该请求可能不会执行由于在服务器上发生冲突。
Conflict: 409,
//
// 摘要:
// 等效于 HTTP 状态 410。 System.Net.HttpStatusCode.Gone 指示所请求的资源不再可用。
Gone: 410,
//
// 摘要:
// 等效于 HTTP 状态 411。 System.Net.HttpStatusCode.LengthRequired 指示缺少必需的内容长度标头。
LengthRequired: 411,
//
// 摘要:
// 等效于 HTTP 状态 412。 System.Net.HttpStatusCode.PreconditionFailed 表示失败,此请求的设置的条件,无法执行请求。
// 使用条件请求标头,如果匹配项,如设置条件无-If-match或如果-修改-自从。
PreconditionFailed: 412,
//
// 摘要:
// 等效于 HTTP 状态 413。 System.Net.HttpStatusCode.RequestEntityTooLarge 指示请求来说太大的服务器能够处理。
RequestEntityTooLarge: 413,
//
// 摘要:
// 等效于 HTTP 状态 414。 System.Net.HttpStatusCode.RequestUriTooLong 指示 URI 太长。
RequestUriTooLong: 414,
//
// 摘要:
// 等效于 HTTP 状态 415。 System.Net.HttpStatusCode.UnsupportedMediaType 指示该请求是不受支持的类型。
UnsupportedMediaType: 415,
//
// 摘要:
// 等效于 HTTP 416 状态。 System.Net.HttpStatusCode.RequestedRangeNotSatisfiable 指示从资源请求的数据范围不能返回,或者因为范围的开始处,然后该资源的开头或范围的末尾后在资源的结尾。
RequestedRangeNotSatisfiable: 416,
//
// 摘要:
// 等效于 HTTP 状态 417。 System.Net.HttpStatusCode.ExpectationFailed 指示无法由服务器满足 Expect
// 标头中给定。
ExpectationFailed: 417,
//
// 摘要:
// 等效于 HTTP 状态 426。 System.Net.HttpStatusCode.UpgradeRequired 指示客户端应切换到不同的协议,例如
// TLS/1.0。
UpgradeRequired: 426,
//
// 摘要:
// 等效于 HTTP 状态 500。 System.Net.HttpStatusCode.InternalServerError 表示在服务器上发生一般性错误。
InternalServerError: 500,
//
// 摘要:
// 等效于 HTTP 状态 501。 System.Net.HttpStatusCode.NotImplemented 指示服务器不支持所请求的功能。
NotImplemented: 501,
//
// 摘要:
// 等效于 HTTP 状态 502。 System.Net.HttpStatusCode.BadGateway 指示中间代理服务器从另一个代理或原始服务器接收到错误响应。
BadGateway: 502,
//
// 摘要:
// 等效于 HTTP 状态 503。 System.Net.HttpStatusCode.ServiceUnavailable 指示将服务器暂时不可用,通常是由于高负载或维护。
ServiceUnavailable: 503,
//
// 摘要:
// 等效于 HTTP 状态 504。 System.Net.HttpStatusCode.GatewayTimeout 指示中间代理服务器在等待来自另一个代理或原始服务器的响应时已超时。
GatewayTimeout: 504,
//
// 摘要:
// 等效于 HTTP 状态 505。 System.Net.HttpStatusCode.HttpVersionNotSupported 指示服务器不支持请求的
// HTTP 版本。
HttpVersionNotSupported: 505
}

View File

@@ -0,0 +1,89 @@
import { api } from '@/common/api';
import { token } from '@/common/token';
import { encryptByDES, decryptByDES } from '@/util/des';
import app from '@/main';
const GLOBAL_KEY = '__GLOBAL';
const setGlobal = (info) => {
app.$set(app.global, 'info', info);
window.sessionStorage.setItem(GLOBAL_KEY, encryptByDES(JSON.stringify(info)));
}
const removeGlobal = () => {
app.$set(app.global, 'info', undefined);
window.sessionStorage.removeItem(GLOBAL_KEY);
}
const getGlobal = () => {
return JSON.parse(decryptByDES(window.sessionStorage.getItem(GLOBAL_KEY)));
}
const doLogin = (args) => {
return new Promise((resolve, reject) => {
api.login({
account: args.account,
password: args.password
}).then(({ result }) => {
if (result.success) {
token.value = result.data.token;
app.$message.success('登录成功');
setGlobal(result.data.info)
if (app.$route.query.return) {
const r = decryptByDES(app.$route.query.return);
app.$router.replace(r)
} else {
app.$router.replace('/');
}
resolve();
} else {
app.$message.error(result.message);
reject();
}
});
})
}
const doLogout = () => {
return new Promise((resolve, reject) => {
api.logout().then(({ result: { success, message } }) => {
if (success) {
removeGlobal();
token.value = '';
if (app.$route.path === '/') {
app.$router.replace('/login');
} else {
app.$router.replace({
path: '/login',
query: {
return: decodeURIComponent(encryptByDES(app.$route.path))
}
})
}
resolve();
} else {
app.$message.error(message);
reject();
}
})
})
}
const doCheck = () => {
return new Promise((resolve, reject) => {
api.checkLogin().then(({ result }) => {
setGlobal(result);
resolve();
}).catch(() => {
reject();
});
})
}
export {
doLogin,
doLogout,
doCheck,
getGlobal
}

View File

@@ -0,0 +1,19 @@
const key = '__SESSION';
const token = {
get value() {
return window.localStorage.getItem(key);
},
set value(token) {
if (!token) {
window.localStorage.removeItem(key);
} else {
window.localStorage.setItem(key, token);
}
}
}
export {
key,
token
}

View File

@@ -0,0 +1,5 @@
<template>
<section :class="$root.global.settings.container || 'container-fluid'">
<slot />
</section>
</template>

View File

@@ -0,0 +1,117 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
/**
* 引入antd
*/
import Antd from 'ant-design-vue'
Vue.use(Antd)
/**
* 引入swiper
*/
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/swiper-bundle.css'
Vue.use(VueAwesomeSwiper)
import {
Swiper as SwiperClass,
Pagination,
Mousewheel,
Autoplay,
Scrollbar
} from 'swiper/core'
import getAwesomeSwiper from 'vue-awesome-swiper/dist/exporter'
SwiperClass.use([Pagination, Mousewheel, Autoplay, Scrollbar])
Vue.use(getAwesomeSwiper(SwiperClass))
/**
* api全局化
*/
import { api } from './common/api'
Vue.prototype.$api = api
/**
* Lodash全局化
*/
import _ from 'lodash'
Vue.prototype.$_ = _
/**
* 注册全局组件
*/
import Container from './components/container'
Vue.component('Container', Container)
/**
* 引入主题样式
*/
import './assets/style/app.less'
const settings = JSON.parse(window.localStorage.getItem('__SETTINGS'))
const app = new Vue({
data: {
/**
* 全局属性
* 可通过this.$root.global调用
*/
global: {
/**
* 用于存储用户信息
*/
info: undefined,
/**
* 设置
*/
settings: settings || {
/**
* 导航颜色
*/
navTheme: 'dark',
/**
* 布局类型
* left-menu 左侧菜单经典结构
* top-nav 顶部导航菜单
*/
layout: 'left-menu',
/**
* 内容区域宽度
* container-fluid 整宽
* container 1200px宽度并居中
*/
container: 'container-fluid',
/**
* 左侧菜单是否收缩
*/
siderCollapsed: false
}
}
},
mounted() {
this.onChangeNavTheme()
},
watch: {
'global.settings': {
deep: true,
handler() {
window.localStorage.setItem('__SETTINGS', JSON.stringify(this.global.settings))
}
},
'global.settings.navTheme'() {
this.onChangeNavTheme()
}
},
methods: {
onChangeNavTheme() {
document.body.classList.remove('yo-nav-theme--dark', 'yo-nav-theme--light')
document.body.classList.add(`yo-nav-theme--${this.global.settings.navTheme}`)
}
},
router,
render: h => h(App),
}).$mount('#app')
export default app

View File

@@ -0,0 +1,3 @@
<template>
<div></div>
</template>

View File

@@ -0,0 +1,104 @@
<template>
<a-card :bordered="false" title="年度项目总完成情况">
<a-form-model :model="query" layout="inline">
<a-form-model-item label="区域">
<a-dropdown>
<span>
宁波市
<a-icon type="down" />
</span>
<a-menu slot="overlay">
<a-menu-item>宁波市</a-menu-item>
<a-menu-item>鄞州区</a-menu-item>
</a-menu>
</a-dropdown>
</a-form-model-item>
<a-form-model-item label="年份">
<a-dropdown>
<span>
2021
<a-icon type="down" />
</span>
<a-menu slot="overlay">
<a-menu-item>2021</a-menu-item>
<a-menu-item>2020</a-menu-item>
<a-menu-item>2019</a-menu-item>
</a-menu>
</a-dropdown>
</a-form-model-item>
</a-form-model>
<div :style="{ height: '300px' }" ref="chart"></div>
</a-card>
</template>
<script>
import * as echarts from 'echarts';
export default {
data() {
return {
query: {
area: '宁波市',
year: '2021',
},
options: {
tooltip: {
trigger: 'axis',
},
legend: {
show: false,
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410],
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320],
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
},
};
},
mounted() {
var chartDom = this.$refs.chart;
var myChart = echarts.init(chartDom);
myChart.setOption(this.options);
},
};
</script>

View File

@@ -0,0 +1,38 @@
@import (reference) '~@/assets/style/app.less';
.home-header {
margin-bottom: @padding-md;
padding: @padding-lg 0;
background-color: @white;
}
.home-header-row {
display: flex;
}
.home-header-content {
margin-left: @padding-lg;
span {
color: @primary-color;
}
p {
margin: 0;
}
}
.home-container {
.ant-card-meta-title {
font-size: @font-size-base + 1px;
display: -webkit-box;
-webkit-box-orient: vertical;
height: 42px;
white-space: normal;
-webkit-line-clamp: 2;
}
.ant-card-meta-description {
.ant-row-flex {
height: 24px;
}
}
}

View File

@@ -0,0 +1,65 @@
<template>
<div>
<div class="home-header">
<container>
<a-row align="middle" justify="space-between" type="flex">
<a-col>
<div class="home-header-row">
<div class="home-header-avatar">
<a-avatar
:size="64"
src="https://gss0.baidu.com/7Ls0a8Sm2Q5IlBGlnYG/sys/portraith/item/tb.1.9f0e4392.Q2njD2SGV-9wBUKqKFoQqA?t=1507123743"
/>
</div>
<div class="home-header-content">
<h2>
上午好
<span>软件开发人员</span>欢迎您登录系统
</h2>
<p>上次IP115.217.160.156 上次登录时间2021-04-06 13:10:13</p>
</div>
</div>
</a-col>
<a-col>
<a-icon :style="{ fontSize: '20px', color: '#f80000' }" class="mr-xs" type="mail" />您有
<a href="#">0</a>封未读邮件请尽快查收
</a-col>
</a-row>
</container>
</div>
<container class="home-container">
<a-row :gutter="16">
<a-col :span="24">
<Statistics />
</a-col>
<a-col :lg="12" :md="24" :xl="16">
<Task />
<List />
</a-col>
<a-col :lg="12" :md="24" :xl="8">
<Notice />
<Charts />
</a-col>
</a-row>
</container>
</div>
</template>
<script>
import './index.less';
import Statistics from './statistics';
import Task from './task';
import Notice from './notice';
import List from './list';
import Charts from './charts';
export default {
components: {
Statistics,
Task,
Notice,
List,
Charts,
},
};
</script>

View File

@@ -0,0 +1,99 @@
<template>
<a-card
:active-tab-key="key"
:bodyStyle="{ padding: 0 }"
:bordered="false"
:tab-list="tabList"
@tabChange="key => onTabChange(key, 'key')"
>
<a-table :columns="columns" :data-source="data" :pagination="false" />
</a-card>
</template>
<script>
export default {
data() {
return {
key: '1',
tabList: [
{
key: '1',
tab: '新建项目',
},
{
key: '2',
tab: '正在签约项目',
},
{
key: '3',
tab: '完成签约项目',
},
{
key: '4',
tab: '项目进度',
},
],
columns: [
{
title: '区域',
dataIndex: 'area',
},
{
title: '项目名称',
dataIndex: 'title',
},
{
title: '户数',
dataIndex: 'count',
},
{
title: '时间',
dataIndex: 'date',
},
],
data: [
{
key: '1',
area: '海曙区',
title: '曙光电影院地块',
count: 13,
date: '2021-01-01',
},
{
key: '2',
area: '江北区',
title: '大庆新村地块旧城区改建项目',
count: 322,
date: '2021-01-01',
},
{
key: '3',
area: '宁海县',
title: '桥头胡街道旧城区改造华驰文教地块',
count: 1,
date: '2021-01-01',
},
{
key: '4',
area: '慈溪市',
title: '七二三南延道路工程',
count: 1,
date: '2021-01-01',
},
{
key: '5',
area: '北仑区',
title: '原粮食局宿舍楼1号、2号楼太河路北延工程',
count: 32,
date: '2021-01-01',
},
],
};
},
methods: {
onTabChange(key, type) {
this[type] = key;
},
},
};
</script>

View File

@@ -0,0 +1,30 @@
<template>
<a-card :bordered="false" title="通知">
<a href="#" slot="extra">更多</a>
<a-list :data-source="data" item-layout="horizontal">
<a-list-item slot="renderItem" slot-scope="item">
<a-list-item-meta :description="moment().format('YYYY-MM-DD HH:mm:ss')" :title="item.title">
<a-icon :style="{ fontSize: '18px' }" slot="avatar" theme="twoTone" type="message" />
</a-list-item-meta>
</a-list-item>
</a-list>
</a-card>
</template>
<script>
import moment from 'moment';
export default {
data() {
return {
data: [
{ title: '关于2020年度房屋征收评估机构信用考核情况的通报' },
{ title: '关于2020年度房屋征收评估机构信用考核情况的通报' },
{ title: '关于2020年度房屋征收评估机构信用考核情况的通报' },
],
};
},
methods: {
moment,
},
};
</script>

View File

@@ -0,0 +1,56 @@
<template>
<a-row :gutter="16">
<a-col :lg="5" :md="8" :sm="24">
<a-card :bordered="false" :hoverable="true">
<a-statistic :value="0" class="text-center" title="已完成的项目" />
</a-card>
</a-col>
<a-col :lg="5" :md="8" :sm="24">
<a-card :bordered="false" :hoverable="true">
<a-statistic :value="c" class="text-center" title="正在进行的项目" />
</a-card>
</a-col>
<a-col :lg="5" :md="8" :sm="24">
<a-card :bordered="false" :hoverable="true">
<a-statistic :value="0" class="text-center" title="还未开始的项目" />
</a-card>
</a-col>
<a-col :lg="9" :md="24">
<a-card :bordered="false" :hoverable="true">
<a-row>
<a-col :span="12">
<a-statistic :value="8893" class="text-center" title="用户总量" />
</a-col>
<a-col :span="12">
<a-statistic :value="1255" :value-style="{ color: '#3f8600' }" class="text-center">
<template #title>
<a-dropdown>
<span>
当月活跃用户
<a-icon type="down" />
</span>
<a-menu slot="overlay">
<a-menu-item>当月活跃用户</a-menu-item>
<a-menu-item>当年活跃用户</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<template #prefix>
<a-icon :style="{ fontSize: '13px' }" type="arrow-up" />
</template>
</a-statistic>
</a-col>
</a-row>
</a-card>
</a-col>
</a-row>
</template>
<script>
export default {
data() {
return {
c: 6,
};
},
};
</script>

View File

@@ -0,0 +1,56 @@
<template>
<a-card :bordered="false" title="待办">
<a href="#" slot="extra">更多</a>
<a-card-grid :key="n" v-for="(item, n) in data">
<a-card-meta>
<div slot="title">
<a-tooltip placement="top">
<template slot="title">{{ item.title }}</template>
{{ item.title }}
</a-tooltip>
</div>
<a-row align="middle" slot="description" type="flex">
<a-col flex="32px" v-if="item.avatar">
<a-avatar :size="24" :src="item.avatar" shape="square" />
</a-col>
<a-col flex="auto">
<a-row justify="space-between" type="flex">
<a-col>软件开发人员</a-col>
<a-col>2020-01-01</a-col>
</a-row>
</a-col>
</a-row>
</a-card-meta>
</a-card-grid>
</a-card>
</template>
<script>
export default {
data() {
return {
data: [
{
title: '市区雷公巷地块项目选择评估机构及上传相关材料(软件开发人员)',
avatar: 'https://tb1.bdstatic.com/tb/steam.jpeg',
},
{
title: '宁海县山河岭6号地块备案(胡靖)',
avatar:
'https://gss0.bdstatic.com/6LZ1dD3d1sgCo2Kml5_Y_D3/sys/portrait/item/tb.1.ac342cde.2vNGrtpPcIUN6lJpSnty3g?t=1615176031',
},
{ title: '宁海县盛宁线力洋至胡陈段公路工程田交朱村 地块备案(胡靖)' },
{
title: '慈溪市慈溪市危旧房改造一期西门小区A1区块项目备案(陆承)',
avatar:
'https://gss0.bdstatic.com/6LZ1dD3d1sgCo2Kml5_Y_D3/sys/portrait/item/tb.1.54e2faca.uBtqRshdnVUXL9XFfQMTwg?t=1604074726',
},
{ title: '江北区孔浦成片危旧住宅区改造项目(六号区块)备案(成薇)' },
{ title: '镇海区宁镇路改扩建工程(庄市段)备案(董力)' },
{ title: '鄞州区茶桃公路(同谷路—金峨路延伸段)项目备案(软件开发人员)' },
{ title: '鄞州区咸祥大嵩湖工程备案(软件开发人员)' },
{ title: '江北区三官堂大桥及接线工程项目备案(成薇)' },
],
};
},
};
</script>

View File

@@ -0,0 +1,146 @@
<template>
<container>
<br />
<a-card :bordered="false">
<div class="yo-query-bar">
<a-form-model :model="query" layout="inline">
<a-form-model-item label="区域">
<a-select :style="{ width: '100px' }" v-model="query.area">
<a-select-option value="宁波市">宁波市</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="年份">
<a-select :style="{ width: '100px' }" v-model="query.year">
<a-select-option value="2020">2020</a-select-option>
<a-select-option value="2021">2021</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item>
<a-button html-type="submit" type="primary">查询</a-button>
</a-form-model-item>
</a-form-model>
</div>
<a-table
:bordered="true"
:columns="columns"
:data-source="data"
:pagination="{ pageSize: 20}"
:row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
>
<div class="yo-action-bar" slot="title">
<div class="yo-action-bar--actions">
<a-button>Button</a-button>
<a-button>Button</a-button>
<a-button :disabled="true">Button</a-button>
<a-button>Button</a-button>
<a-button-group>
<a-button>Button</a-button>
<a-button>Button</a-button>
<a-button>Button</a-button>
</a-button-group>
<a-dropdown>
<a-menu slot="overlay">
<a-menu-item key="1">1st item</a-menu-item>
<a-menu-item key="2">2nd item</a-menu-item>
<a-menu-item key="3">3rd item</a-menu-item>
</a-menu>
<a-button>
Actions
<a-icon type="down" />
</a-button>
</a-dropdown>
<a-dropdown-button>
Dropdown
<a-menu slot="overlay">
<a-menu-item key="1">
<a-icon type="user" />1st menu item
</a-menu-item>
<a-menu-item key="2">
<a-icon type="user" />2nd menu item
</a-menu-item>
<a-menu-item key="3">
<a-icon type="user" />3rd item
</a-menu-item>
</a-menu>
</a-dropdown-button>
</div>
</div>
</a-table>
</a-card>
<br />
</container>
</template>
<script>
const _data = [
{
area: '海曙区',
title: '曙光电影院地块',
count: 13,
date: '2021-01-01',
},
{
area: '江北区',
title: '大庆新村地块旧城区改建项目',
count: 322,
date: '2021-01-01',
},
{
area: '宁海县',
title: '桥头胡街道旧城区改造华驰文教地块',
count: 1,
date: '2021-01-01',
},
{
area: '慈溪市',
title: '七二三南延道路工程',
count: 1,
date: '2021-01-01',
},
{
area: '北仑区',
title: '原粮食局宿舍楼1号、2号楼太河路北延工程',
count: 32,
date: '2021-01-01',
},
];
const data = Object.assign([], _data, _data, _data);
data.map((p, i) => (p.key = 'abcdefghijklmnopqrstuvwxyz'[i]));
export default {
data() {
return {
query: {
area: '宁波市',
year: '2021',
},
columns: [
{
title: '区域',
dataIndex: 'area',
},
{
title: '项目名称',
dataIndex: 'title',
},
{
title: '户数',
dataIndex: 'count',
},
{
title: '时间',
dataIndex: 'date',
},
],
data,
selectedRowKeys: [],
};
},
methods: {
onSelectChange(selectedRowKeys) {
this.selectedRowKeys = selectedRowKeys;
},
},
};
</script>

View File

@@ -0,0 +1,34 @@
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const router = new Router({
mode: 'history',
routes: [{
path: '/',
component: () => import('@/views/main/index'),
}, {
path: '/login',
component: () => import('@/views/login/index')
}]
})
// 路由守卫
import { token } from '@/common/token'
router.beforeEach((to, from, next) => {
if (token.value) {
if (to.path === '/login') {
next({
path: '/'
})
} else {
next()
}
} else if (!token.value && to.path !== '/login') {
next({ path: '/login' })
}
next()
})
export default router

View File

@@ -0,0 +1,34 @@
import {
TripleDES,
enc,
mode,
pad
} from 'crypto-js';
const KEY = process.env.VUE_APP_DEV_KEY;
const key = enc.Utf8.parse(KEY);
//TripleDES加密
const encryptByDES = (message) => {
let encrypted = TripleDES.encrypt(message, key, {
mode: mode.ECB,
padding: pad.Pkcs7
});
return encrypted.toString();
}
//TripleDES解密
const decryptByDES = (ciphertext) => {
let decrypted = TripleDES.decrypt({
ciphertext: enc.Base64.parse(ciphertext)
}, key, {
mode: mode.ECB,
});
const value = decrypted.toString(enc.Utf8);
return value;
}
export {
encryptByDES,
decryptByDES
}

View File

@@ -0,0 +1,296 @@
@-webkit-keyframes noise-anim {
0% {
clip: rect(32px, 9999px, 16px, 0);
}
5% {
clip: rect(5px, 9999px, 24px, 0);
}
10% {
clip: rect(77px, 9999px, 87px, 0);
}
15% {
clip: rect(91px, 9999px, 95px, 0);
}
20% {
clip: rect(74px, 9999px, 9px, 0);
}
25% {
clip: rect(37px, 9999px, 32px, 0);
}
30% {
clip: rect(56px, 9999px, 27px, 0);
}
35% {
clip: rect(35px, 9999px, 33px, 0);
}
40% {
clip: rect(89px, 9999px, 6px, 0);
}
45% {
clip: rect(81px, 9999px, 77px, 0);
}
50% {
clip: rect(64px, 9999px, 69px, 0);
}
55% {
clip: rect(12px, 9999px, 11px, 0);
}
60% {
clip: rect(59px, 9999px, 11px, 0);
}
65% {
clip: rect(69px, 9999px, 59px, 0);
}
70% {
clip: rect(74px, 9999px, 65px, 0);
}
75% {
clip: rect(56px, 9999px, 79px, 0);
}
80% {
clip: rect(80px, 9999px, 64px, 0);
}
85% {
clip: rect(87px, 9999px, 29px, 0);
}
90% {
clip: rect(16px, 9999px, 21px, 0);
}
95% {
clip: rect(69px, 9999px, 43px, 0);
}
100% {
clip: rect(75px, 9999px, 63px, 0);
}
}
@keyframes noise-anim {
0% {
clip: rect(32px, 9999px, 16px, 0);
}
5% {
clip: rect(5px, 9999px, 24px, 0);
}
10% {
clip: rect(77px, 9999px, 87px, 0);
}
15% {
clip: rect(91px, 9999px, 95px, 0);
}
20% {
clip: rect(74px, 9999px, 9px, 0);
}
25% {
clip: rect(37px, 9999px, 32px, 0);
}
30% {
clip: rect(56px, 9999px, 27px, 0);
}
35% {
clip: rect(35px, 9999px, 33px, 0);
}
40% {
clip: rect(89px, 9999px, 6px, 0);
}
45% {
clip: rect(81px, 9999px, 77px, 0);
}
50% {
clip: rect(64px, 9999px, 69px, 0);
}
55% {
clip: rect(12px, 9999px, 11px, 0);
}
60% {
clip: rect(59px, 9999px, 11px, 0);
}
65% {
clip: rect(69px, 9999px, 59px, 0);
}
70% {
clip: rect(74px, 9999px, 65px, 0);
}
75% {
clip: rect(56px, 9999px, 79px, 0);
}
80% {
clip: rect(80px, 9999px, 64px, 0);
}
85% {
clip: rect(87px, 9999px, 29px, 0);
}
90% {
clip: rect(16px, 9999px, 21px, 0);
}
95% {
clip: rect(69px, 9999px, 43px, 0);
}
100% {
clip: rect(75px, 9999px, 63px, 0);
}
}
@-webkit-keyframes noise-anim-2 {
0% {
clip: rect(12px, 9999px, 52px, 0);
}
5% {
clip: rect(42px, 9999px, 39px, 0);
}
10% {
clip: rect(64px, 9999px, 36px, 0);
}
15% {
clip: rect(52px, 9999px, 15px, 0);
}
20% {
clip: rect(79px, 9999px, 7px, 0);
}
25% {
clip: rect(17px, 9999px, 41px, 0);
}
30% {
clip: rect(15px, 9999px, 20px, 0);
}
35% {
clip: rect(62px, 9999px, 87px, 0);
}
40% {
clip: rect(94px, 9999px, 11px, 0);
}
45% {
clip: rect(49px, 9999px, 10px, 0);
}
50% {
clip: rect(82px, 9999px, 4px, 0);
}
55% {
clip: rect(70px, 9999px, 100px, 0);
}
60% {
clip: rect(62px, 9999px, 23px, 0);
}
65% {
clip: rect(51px, 9999px, 56px, 0);
}
70% {
clip: rect(41px, 9999px, 24px, 0);
}
75% {
clip: rect(6px, 9999px, 85px, 0);
}
80% {
clip: rect(96px, 9999px, 58px, 0);
}
85% {
clip: rect(16px, 9999px, 24px, 0);
}
90% {
clip: rect(40px, 9999px, 31px, 0);
}
95% {
clip: rect(91px, 9999px, 34px, 0);
}
100% {
clip: rect(87px, 9999px, 26px, 0);
}
}
@keyframes noise-anim-2 {
0% {
clip: rect(12px, 9999px, 52px, 0);
}
5% {
clip: rect(42px, 9999px, 39px, 0);
}
10% {
clip: rect(64px, 9999px, 36px, 0);
}
15% {
clip: rect(52px, 9999px, 15px, 0);
}
20% {
clip: rect(79px, 9999px, 7px, 0);
}
25% {
clip: rect(17px, 9999px, 41px, 0);
}
30% {
clip: rect(15px, 9999px, 20px, 0);
}
35% {
clip: rect(62px, 9999px, 87px, 0);
}
40% {
clip: rect(94px, 9999px, 11px, 0);
}
45% {
clip: rect(49px, 9999px, 10px, 0);
}
50% {
clip: rect(82px, 9999px, 4px, 0);
}
55% {
clip: rect(70px, 9999px, 100px, 0);
}
60% {
clip: rect(62px, 9999px, 23px, 0);
}
65% {
clip: rect(51px, 9999px, 56px, 0);
}
70% {
clip: rect(41px, 9999px, 24px, 0);
}
75% {
clip: rect(6px, 9999px, 85px, 0);
}
80% {
clip: rect(96px, 9999px, 58px, 0);
}
85% {
clip: rect(16px, 9999px, 24px, 0);
}
90% {
clip: rect(40px, 9999px, 31px, 0);
}
95% {
clip: rect(91px, 9999px, 34px, 0);
}
100% {
clip: rect(87px, 9999px, 26px, 0);
}
}
.error-result {
padding: 100px;
text-transform: uppercase;
}
.error-result--text {
font-size: 7rem;
line-height: 1;
position: relative;
display: block;
width: 12.5rem;
color: #5a5c69;
}
.error-result--text::after {
position: absolute;
top: 0;
left: 2px;
overflow: hidden;
clip: rect(0, 900px, 0, 0);
content: attr(data-text);
animation: noise-anim 2s infinite linear alternate-reverse;
color: #5a5c69;
background: #f8f9fc;
text-shadow: -1px 0 #e74a3b;
}
.error-result--text::before {
position: absolute;
top: 0;
left: -2px;
overflow: hidden;
clip: rect(0, 900px, 0, 0);
content: attr(data-text);
animation: noise-anim-2 3s infinite linear alternate-reverse;
color: #5a5c69;
background: #f8f9fc;
text-shadow: 1px 0 #4e73df;
}

View File

@@ -0,0 +1,308 @@
@import (reference) '~ant-design-vue/dist/antd.less';
@-webkit-keyframes noise-anim-after {
0% {
clip: rect(32px, 9999px, 16px, 0);
}
5% {
clip: rect(5px, 9999px, 24px, 0);
}
10% {
clip: rect(77px, 9999px, 87px, 0);
}
15% {
clip: rect(91px, 9999px, 95px, 0);
}
20% {
clip: rect(74px, 9999px, 9px, 0);
}
25% {
clip: rect(37px, 9999px, 32px, 0);
}
30% {
clip: rect(56px, 9999px, 27px, 0);
}
35% {
clip: rect(35px, 9999px, 33px, 0);
}
40% {
clip: rect(89px, 9999px, 6px, 0);
}
45% {
clip: rect(81px, 9999px, 77px, 0);
}
50% {
clip: rect(64px, 9999px, 69px, 0);
}
55% {
clip: rect(12px, 9999px, 11px, 0);
}
60% {
clip: rect(59px, 9999px, 11px, 0);
}
65% {
clip: rect(69px, 9999px, 59px, 0);
}
70% {
clip: rect(74px, 9999px, 65px, 0);
}
75% {
clip: rect(56px, 9999px, 79px, 0);
}
80% {
clip: rect(80px, 9999px, 64px, 0);
}
85% {
clip: rect(87px, 9999px, 29px, 0);
}
90% {
clip: rect(16px, 9999px, 21px, 0);
}
95% {
clip: rect(69px, 9999px, 43px, 0);
}
100% {
clip: rect(75px, 9999px, 63px, 0);
}
}
@keyframes noise-anim-after {
0% {
clip: rect(32px, 9999px, 16px, 0);
}
5% {
clip: rect(5px, 9999px, 24px, 0);
}
10% {
clip: rect(77px, 9999px, 87px, 0);
}
15% {
clip: rect(91px, 9999px, 95px, 0);
}
20% {
clip: rect(74px, 9999px, 9px, 0);
}
25% {
clip: rect(37px, 9999px, 32px, 0);
}
30% {
clip: rect(56px, 9999px, 27px, 0);
}
35% {
clip: rect(35px, 9999px, 33px, 0);
}
40% {
clip: rect(89px, 9999px, 6px, 0);
}
45% {
clip: rect(81px, 9999px, 77px, 0);
}
50% {
clip: rect(64px, 9999px, 69px, 0);
}
55% {
clip: rect(12px, 9999px, 11px, 0);
}
60% {
clip: rect(59px, 9999px, 11px, 0);
}
65% {
clip: rect(69px, 9999px, 59px, 0);
}
70% {
clip: rect(74px, 9999px, 65px, 0);
}
75% {
clip: rect(56px, 9999px, 79px, 0);
}
80% {
clip: rect(80px, 9999px, 64px, 0);
}
85% {
clip: rect(87px, 9999px, 29px, 0);
}
90% {
clip: rect(16px, 9999px, 21px, 0);
}
95% {
clip: rect(69px, 9999px, 43px, 0);
}
100% {
clip: rect(75px, 9999px, 63px, 0);
}
}
@-webkit-keyframes noise-anim-before {
0% {
clip: rect(12px, 9999px, 52px, 0);
}
5% {
clip: rect(42px, 9999px, 39px, 0);
}
10% {
clip: rect(64px, 9999px, 36px, 0);
}
15% {
clip: rect(52px, 9999px, 15px, 0);
}
20% {
clip: rect(79px, 9999px, 7px, 0);
}
25% {
clip: rect(17px, 9999px, 41px, 0);
}
30% {
clip: rect(15px, 9999px, 20px, 0);
}
35% {
clip: rect(62px, 9999px, 87px, 0);
}
40% {
clip: rect(94px, 9999px, 11px, 0);
}
45% {
clip: rect(49px, 9999px, 10px, 0);
}
50% {
clip: rect(82px, 9999px, 4px, 0);
}
55% {
clip: rect(70px, 9999px, 100px, 0);
}
60% {
clip: rect(62px, 9999px, 23px, 0);
}
65% {
clip: rect(51px, 9999px, 56px, 0);
}
70% {
clip: rect(41px, 9999px, 24px, 0);
}
75% {
clip: rect(6px, 9999px, 85px, 0);
}
80% {
clip: rect(96px, 9999px, 58px, 0);
}
85% {
clip: rect(16px, 9999px, 24px, 0);
}
90% {
clip: rect(40px, 9999px, 31px, 0);
}
95% {
clip: rect(91px, 9999px, 34px, 0);
}
100% {
clip: rect(87px, 9999px, 26px, 0);
}
}
@keyframes noise-anim-before {
0% {
clip: rect(12px, 9999px, 52px, 0);
}
5% {
clip: rect(42px, 9999px, 39px, 0);
}
10% {
clip: rect(64px, 9999px, 36px, 0);
}
15% {
clip: rect(52px, 9999px, 15px, 0);
}
20% {
clip: rect(79px, 9999px, 7px, 0);
}
25% {
clip: rect(17px, 9999px, 41px, 0);
}
30% {
clip: rect(15px, 9999px, 20px, 0);
}
35% {
clip: rect(62px, 9999px, 87px, 0);
}
40% {
clip: rect(94px, 9999px, 11px, 0);
}
45% {
clip: rect(49px, 9999px, 10px, 0);
}
50% {
clip: rect(82px, 9999px, 4px, 0);
}
55% {
clip: rect(70px, 9999px, 100px, 0);
}
60% {
clip: rect(62px, 9999px, 23px, 0);
}
65% {
clip: rect(51px, 9999px, 56px, 0);
}
70% {
clip: rect(41px, 9999px, 24px, 0);
}
75% {
clip: rect(6px, 9999px, 85px, 0);
}
80% {
clip: rect(96px, 9999px, 58px, 0);
}
85% {
clip: rect(16px, 9999px, 24px, 0);
}
90% {
clip: rect(40px, 9999px, 31px, 0);
}
95% {
clip: rect(91px, 9999px, 34px, 0);
}
100% {
clip: rect(87px, 9999px, 26px, 0);
}
}
.error-result {
padding: 100px;
text-transform: uppercase;
&--text {
font-size: @font-size-base * 8;
line-height: 1;
position: relative;
display: block;
width: 12.5rem;
color: #5a5c69;
&::after {
position: absolute;
top: 0;
left: 2px;
overflow: hidden;
clip: rect(0, 900px, 0, 0);
content: attr(data-text);
animation: noise-anim-after 2s infinite linear alternate-reverse;
color: #5a5c69;
background: @layout-body-background;
text-shadow: -1px 0 #e74a3b;
}
&::before {
position: absolute;
top: 0;
left: -2px;
overflow: hidden;
clip: rect(0, 900px, 0, 0);
content: attr(data-text);
animation: noise-anim-before 3s infinite linear alternate-reverse;
color: #5a5c69;
background: @layout-body-background;
text-shadow: 1px 0 #4e73df;
}
}
}

View File

@@ -0,0 +1,14 @@
<template>
<container>
<div class="error-result">
<div class="error-result--code">
<span class="error-result--text" data-text="404">404</span>
</div>
<p>not found</p>
</div>
</container>
</template>
<style lang="less" scope>
@import './index.less';
</style>

View File

@@ -0,0 +1,43 @@
<template>
<a-form-model :model="formInline" @submit="handleSubmit" @submit.native.prevent layout="inline">
<a-form-model-item>
<a-input placeholder="Username" v-model="formInline.user">
<a-icon slot="prefix" style="color:rgba(0,0,0,.25)" type="user" />
</a-input>
</a-form-model-item>
<a-form-model-item>
<a-input placeholder="Password" type="password" v-model="formInline.password">
<a-icon slot="prefix" style="color:rgba(0,0,0,.25)" type="lock" />
</a-input>
</a-form-model-item>
<a-form-model-item>
<a-button
:disabled="formInline.user === '' || formInline.password === ''"
html-type="submit"
type="primary"
>Log in</a-button>
</a-form-model-item>
</a-form-model>
</template>
<script>
import { doLogin } from '@/common/login';
export default {
data() {
return {
formInline: {
user: '',
password: '',
},
};
},
methods: {
handleSubmit(e) {
doLogin({
account: this.formInline.user,
password: this.formInline.password,
});
},
},
};
</script>

View File

@@ -0,0 +1,84 @@
<template>
<a-layout-content>
<a-tabs @change="onChange" @edit="onClose" hide-add type="editable-card" v-model="actived">
<a-tab-pane
:closable="pane.closable"
:forceRender="true"
:key="pane.key"
v-for="pane in panes"
>
<a-dropdown :trigger="['contextmenu']" slot="tab">
<div>
<a-icon :type="pane.icon" v-if="pane.icon" />
{{ pane.title }}
</div>
<a-menu slot="overlay">
<a-menu-item @click="onLoadContentWindow(pane.key)" key="0">重新加载</a-menu-item>
<a-menu-divider />
<a-menu-item :disabled="!pane.closable" @click="$emit('close', pane.key);" key="1">关闭</a-menu-item>
<a-menu-item @click="$emit('close-other', pane.key)" key="2">关闭其他标签页</a-menu-item>
<a-menu-item @click="$emit('close-right', pane.key)" key="3">关闭右侧标签页</a-menu-item>
</a-menu>
</a-dropdown>
<component :is="pane.component" :key="pane.key" v-if="pane.loaded" />
</a-tab-pane>
</a-tabs>
</a-layout-content>
</template>
<script>
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
NProgress.configure({ parent: '.ant-layout-content > .ant-tabs > .ant-tabs-content' });
export default {
props: {
panes: {
type: Array,
},
tabActived: {
type: String,
},
},
data() {
return {
actived: '',
};
},
watch: {
tabActived() {
if (this.tabActived !== this.actived) {
this.actived = this.tabActived;
}
},
},
created() {
this.actived = this.tabActived;
},
methods: {
onLoadContentWindow(key) {
NProgress.start();
const pane = this.panes.find((p) => p.key === key);
const i = import(`@/pages${pane.path}`);
pane.component = () => i;
pane.loaded = false;
i.then(() => {
pane.loaded = true;
NProgress.done();
}).catch(() => {
pane.component = () => import('@/views/404');
pane.loaded = true;
NProgress.done();
});
},
onClose(targetKey, action) {
if (action === 'remove') {
this.$emit('close', targetKey);
}
},
onChange(activeKey) {
this.$emit('change', activeKey);
},
},
};
</script>

View File

@@ -0,0 +1,61 @@
<template>
<a-layout-header class="header">
<section
v-if="$root.global.settings.layout === 'left-menu' || $root.global.settings.layout === 'right-menu'"
>
<div class="header-actions">
<a
@click="$root.global.settings.siderCollapsed = !$root.global.settings.siderCollapsed"
class="header-action"
>
<a-icon :type="$root.global.settings.siderCollapsed ? 'menu-unfold' : 'menu-fold'" />
</a>
<a-input-search placeholder="请输入检索关键字" />
</div>
<div class="header-actions">
<a @click="$emit('reload')" class="header-action">
<a-icon type="reload" />
</a>
<a class="header-action">
<a-badge count="5">
<a-icon type="bell" />
</a-badge>
</a>
<a @click="$emit('setting')" class="header-action">
<a-icon type="setting" />
</a>
</div>
</section>
<container v-else-if="$root.global.settings.layout === 'top-nav'">
<div class="header-actions">
<Logo />
<Sider @open="(nav) => $emit('open', nav)" />
</div>
<div class="header-actions">
<a-input-search placeholder="请输入检索关键字" />
<a @click="$emit('reload')" class="header-action">
<a-icon type="reload" />
</a>
<a class="header-action">
<a-badge count="5">
<a-icon type="bell" />
</a-badge>
</a>
<a @click="$emit('setting')" class="header-action">
<a-icon type="setting" />
</a>
</div>
</container>
</a-layout-header>
</template>
<script>
import Logo from './logo';
import Sider from './sider';
export default {
components: {
Logo,
Sider,
},
};
</script>

View File

@@ -0,0 +1,6 @@
<template>
<div class="logo">
<img :src="require('@/assets/image/logo32.png')" alt />
<span>LazyOn</span>
</div>
</template>

View File

@@ -0,0 +1,176 @@
<template>
<section>
<a-layout-sider
v-if="$root.global.settings.layout === 'left-menu' || $root.global.settings.layout === 'right-menu'"
v-model="$root.global.settings.siderCollapsed"
width="200"
>
<Logo />
<swiper :options="siderSwiperOptions" :style="{ height: '100%' }" ref="sider-swiper">
<swiper-slide :style="{ height: 'auto' }">
<a-menu
:selectable="false"
:style="{ height: '100%', borderRight: 0 }"
:theme="$root.global.settings.navTheme"
@openChange="onMenuOpenChange"
mode="inline"
>
<template v-for="c1 in menu">
<a-sub-menu :key="c1.id" v-if="c1.children && c1.children.length">
<span slot="title">
<a-icon :type="c1.icon" v-if="c1.icon" />
<span>{{ c1.name }}</span>
</span>
<template v-for="c2 in c1.children">
<a-menu-item :key="c2.id" @click="onOpenContentWindow(c2)">
<a-icon :type="c2.icon" v-if="c2.icon" />
<span>{{ c2.name }}</span>
</a-menu-item>
</template>
</a-sub-menu>
<a-menu-item :key="c1.id" @click="onOpenContentWindow(c1)" v-else>
<a-icon :type="c1.icon" v-if="c1.icon" />
<span>{{ c1.name }}</span>
</a-menu-item>
</template>
</a-menu>
</swiper-slide>
<div class="swiper-scrollbar"></div>
</swiper>
</a-layout-sider>
<template v-else-if="$root.global.settings.layout === 'top-nav'">
<a-menu
:selectable="false"
:style="{ borderBottom: 0 }"
:theme="$root.global.settings.navTheme"
@openChange="onMenuOpenChange"
mode="horizontal"
>
<template v-for="c1 in menu">
<a-sub-menu :key="c1.id" v-if="c1.children && c1.children.length">
<span slot="title">
<a-icon :type="c1.icon" v-if="c1.icon" />
<span>{{ c1.name }}</span>
</span>
<template v-for="c2 in c1.children">
<a-menu-item :key="c2.id" @click="onOpenContentWindow(c2)">
<a-icon :type="c2.icon" v-if="c2.icon" />
<span>{{ c2.name }}</span>
</a-menu-item>
</template>
</a-sub-menu>
<a-menu-item :key="c1.id" @click="onOpenContentWindow(c1)" v-else>
<a-icon :type="c1.icon" v-if="c1.icon" />
<span>{{ c1.name }}</span>
</a-menu-item>
</template>
</a-menu>
</template>
</section>
</template>
<script>
import Logo from './logo';
const menu = [
{
id: '1',
name: '列表',
icon: 'unordered-list',
children: [
{
id: '1-1',
name: '表格及查询',
icon: 'table',
component: '/list/query',
},
],
},
{
id: '2',
name: '表单',
icon: 'form',
children: [
{
id: '2-1',
name: '一般表单',
component: '/form/normal',
},
{
id: '2-2',
name: 'yo表单',
component: '/form/yo',
},
],
},
{
id: '100',
name: '文档',
icon: 'snippets',
children: [
{
id: '100-1',
name: '使用文档',
icon: 'code',
component: '/doc/use',
},
],
},
];
export default {
components: {
Logo,
},
data() {
return {
siderSwiperOptions: {
direction: 'vertical',
slidesPerView: 'auto',
freeMode: true,
scrollbar: {
el: '.swiper-scrollbar',
},
mousewheel: true,
},
menu,
};
},
computed: {
swiper() {
return this.$refs['sider-swiper'].$swiper;
},
},
mounted() {
window.addEventListener('resize', () => {
if (this.$root.global.settings.layout === 'left-menu' || this.$root.global.settings.layout === 'right-menu') {
setTimeout(() => {
this.swiper.update();
}, 300);
}
});
// this.$api.getMenu().then(({ result: { data } }) => {
// this.menu = data;
// });
},
methods: {
onMenuOpenChange() {
this.windowTriggerResize();
},
windowTriggerResize() {
let e = new Event('resize');
window.dispatchEvent(e);
},
onOpenContentWindow(nav) {
this.$emit('open', {
key: nav.id,
title: nav.name,
icon: nav.icon,
path: nav.component,
});
},
},
};
</script>

View File

@@ -0,0 +1,199 @@
<template>
<a-config-provider :locale="zh_CN">
<section>
<a-layout
:class="{
[`yo-layout--top-nav--${$root.global.settings.container}`]: $root.global.settings.layout === 'top-nav',
[`yo-layout--${$root.global.settings.layout}`]: true,
}"
>
<Sider @open="onOpenContentWindow" v-if="$root.global.settings.layout === 'left-menu'" />
<a-layout>
<Header
@open="onOpenContentWindow"
@reload="onReloadContentWindow"
@setting="setting.visible = true"
/>
<a-layout>
<Content
:panes="panes"
:tabActived="tabActived"
@change="onChangeContentWindow"
@close="onCloseContentWindow"
@close-other="onCloseOtherContentWindow"
@close-right="onCloseRightContentWindow"
ref="content"
/>
</a-layout>
</a-layout>
<Sider @open="onOpenContentWindow" v-if="$root.global.settings.layout === 'right-menu'" />
</a-layout>
<Setting :visible="setting.visible" @close="setting.visible = false" />
</section>
</a-config-provider>
</template>
<script>
import Vue from 'vue';
import zh_CN from 'ant-design-vue/es/locale/zh_CN';
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
import Header from './_layout/header';
import Sider from './_layout/sider';
import Content from './_layout/content';
import Setting from './setting';
const getNewID = () => {
return Math.random().toString(16).slice(2);
};
export default {
components: {
Header,
Sider,
Content,
Setting,
},
data() {
return {
zh_CN,
panes: new Array(),
tabActived: '',
setting: {
visible: false,
},
};
},
mounted() {
Vue.prototype.openContentWindow = this.onOpenContentWindow;
Vue.prototype.closeContentWindow = this.onCloseContentWindow;
this.onOpenContentWindow({
title: '首页',
path: '/home',
icon: 'home',
closable: false,
});
},
methods: {
/**
* 打开一个新的标签页
* 第一个参数可以是json
*/
onOpenContentWindow(title, path, icon, closable = true) {
const settings =
typeof title === 'object'
? title
: {
title,
path,
icon,
closable,
};
if (settings.path && settings.path.startsWith('/')) {
const key = settings.key || getNewID();
/**
* 如果当前标签页已打开,则只需要选中
*/
const pane = this.panes.find((p) => p.key === key);
if (pane) {
this.onChangeContentWindow(key);
return;
}
/**
* 向标签页队列中添加一个新的标签页
*/
this.panes.push({
key,
closable: settings.closable === undefined ? true : settings.closable,
icon: settings.icon,
title: settings.title || '新建窗口',
component: null,
path: settings.path,
loaded: false,
});
this.onChangeContentWindow(key);
this.$refs.content.onLoadContentWindow(key);
} else {
console.warn('地址错误');
}
},
onCloseContentWindow(key) {
key = key || this.tabActived;
const i = this.$_.findIndex(this.panes, (p) => p.key === key);
this.panes.splice(i, 1);
if (this.panes.length) {
if (key === this.tabActived) {
const pane = this.panes[i];
if (pane) {
this.onChangeContentWindow(pane.key);
} else {
this.onChangeContentWindow(this.panes[i - 1].key);
}
}
}
},
onCloseOtherContentWindow(key) {
const panes = Object.assign([], this.panes);
let flag = false;
for (let i = panes.length - 1; i >= 0; i--) {
const p = panes[i];
if (p.key !== key && p.closable) {
if (p.key === this.tabActived) {
flag = true;
}
panes.splice(i, 1);
}
}
this.panes = panes;
if (flag) {
this.onChangeContentWindow(this.$_.last(panes).key);
}
},
onCloseRightContentWindow(key) {
const panes = Object.assign([], this.panes);
let flag = false;
for (let i = panes.length - 1; i >= 0; i--) {
const p = panes[i];
if (p.key !== key && p.closable) {
if (p.key === this.tabActived) {
flag = true;
}
panes.splice(i, 1);
} else if (p.key === key) {
break;
}
}
this.panes = panes;
if (flag) {
this.onChangeContentWindow(this.$_.last(panes).key);
}
},
onChangeContentWindow(key) {
this.tabActived = key;
},
onReloadContentWindow(key) {
if (!key) key = this.tabActived;
this.$refs.content.onLoadContentWindow(key);
},
},
};
</script>

View File

@@ -0,0 +1,56 @@
<template>
<a-modal :footer="null" :visible="visible" :width="708" @cancel="$emit('close')">
<a-form-model class="yo-form">
<h3>布局设置</h3>
<div class="yo-form-group">
<a-form-model-item label="导航菜单位置">
<a-select v-model="$root.global.settings.layout">
<a-select-option value="left-menu">左侧菜单经典结构</a-select-option>
<a-select-option value="top-nav">顶部导航菜单</a-select-option>
<a-select-option value="right-menu">右侧菜单结构</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="内容宽度">
<a-select v-model="$root.global.settings.container">
<a-select-option value="container-fluid">整宽</a-select-option>
<a-select-option v-if="$root.global.settings.layout === 'top-nav'" value="container">居中</a-select-option>
</a-select>
</a-form-model-item>
</div>
<h3>主题设置</h3>
<div class="yo-form-group">
<a-form-model-item label="导航颜色">
<a-select v-model="$root.global.settings.navTheme">
<a-select-option value="dark">深色</a-select-option>
<a-select-option value="light">浅色</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="主题颜色">
<a-select>
<a-select-option value="1">蓝色</a-select-option>
<a-select-option value="2">红色</a-select-option>
</a-select>
</a-form-model-item>
</div>
</a-form-model>
</a-modal>
</template>
<script>
export default {
props: {
visible: {
default: false,
type: Boolean,
},
},
watch: {
'$root.global.settings.layout'() {
if (this.$root.global.settings.layout === 'top-nav') {
this.$root.global.settings.container = 'container';
} else {
this.$root.global.settings.container = 'container-fluid';
}
},
},
};
</script>

View File

@@ -0,0 +1,14 @@
module.exports = {
devServer: {
open: true,
port: 6588,
},
lintOnSave: false,
css: {
loaderOptions: {
less: {
javascriptEnabled: true
}
}
},
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff