This commit is contained in:
ky_sunl
2021-04-22 13:37:25 +00:00
parent 575a22954f
commit d1c9e5a71e
699 changed files with 1062425 additions and 40640 deletions

3
Web/.env.development Normal file
View File

@@ -0,0 +1,3 @@
VUE_APP_NODE_ENV=development
VUE_APP_BASE_URL=http://localhost:5566
VUE_APP_DEV_KEY=CJm9dFWx4IIGXHm^R1e@Y&mp*n8MQXfDKjDYP6ZVGqEAZQiC9LvX9jq8@uaMTT@T

3
Web/.env.production Normal file
View File

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

23
Web/.gitignore vendored Normal file
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?

24
Web/README.md Normal file
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/).

5
Web/babel.config.js Normal file
View File

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

53
Web/package.json Normal file
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"
]
}

BIN
Web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

17
Web/public/index.html Normal file
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>

24
Web/src/App.vue Normal file
View File

@@ -0,0 +1,24 @@
<template>
<div id="app">
<a-config-provider :locale="zh_CN">
<router-view />
</a-config-provider>
</div>
</template>
<script>
import zh_CN from 'ant-design-vue/es/locale/zh_CN';
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
export default {
name: 'App',
data() {
return {
zh_CN,
};
},
};
</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,27 @@
@import './extend.less';
@import './lib/visibility.less';
@import './lib/container.less';
@import './lib/align.less';
@import './lib/font-size.less';
@import './lib/margin.less';
@import './lib/width-height.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/list.less';
@import './lib/form.less';
@import './lib/select.less';
@import './lib/dropdown.less';
@import './lib/tree-layout.less';
@import './lib/authority-view.less';
@import './theme/primary.less';
// @import './lib/font-weight.less';

View File

@@ -0,0 +1,3 @@
@import '~ant-design-vue/dist/antd.less';
@padding-xxs: 4px;
@padding-xl: 32px;

View File

@@ -0,0 +1,20 @@
@import (reference) '~@/assets/style/main.less';
.dark {
.main(@nav-background: @layout-header-background;
@nav-box-shadow-color: fade(@black, 25%);
@nav-scrollbar-background: fade(@white, 50%);
@logo-color: @white;
@logo-box-shadow: none;
@app-selector-color: @white;
@app-selector-border-color: fade(@white, 10%);
@app-selector-background-color: fade(@white, 30%);
@app-selector-box-shadow-color: fade(@white, 15%);
@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

View File

@@ -0,0 +1,20 @@
@import (reference) '~@/assets/style/main.less';
.light {
.main(@nav-background: @white;
@nav-box-shadow-color: fade(@black, 5%);
@nav-scrollbar-background: fade(@black, 30%);
@logo-color: @black;
@logo-box-shadow: inset -1px -1px 1px @border-color-split;
@app-selector-color: @primary-6;
@app-selector-border-color: @primary-3;
@app-selector-background-color: @primary-1;
@app-selector-box-shadow-color: @primary-1;
@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,24 @@
@import (reference) '~@/assets/style/extend.less';
.yo-authority-view {
.ant-descriptions-item-label {
width: 150px;
}
.ant-descriptions {
margin-bottom: @padding-sm;
&:last-child {
margin-bottom: 0;
}
}
.ant-descriptions-item-content {
padding: @padding-sm @padding-md;
>.yo-authority-view--checkbox {
display: inline-block;
width: 150px;
margin: @padding-xxs 0;
.ant-checkbox-wrapper {
margin: 0;
}
}
}
}

View File

@@ -0,0 +1,4 @@
@import (reference) '~@/assets/style/extend.less';
.ant-card {
margin-bottom: @padding-md;
}

View File

@@ -0,0 +1,10 @@
@import (reference) '~@/assets/style/extend.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,6 @@
@import (reference) '~@/assets/style/extend.less';
.ant-dropdown-trigger {
.anticon-down {
transform: scaleY(.75);
}
}

View File

@@ -0,0 +1 @@
@import (reference) '~@/assets/style/extend.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) '~@/assets/style/extend.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,205 @@
@import (reference) '~@/assets/style/extend.less';
.yo-form {
&--fixed {
width: 660px;
margin: 0 auto;
}
h1,
h2,
h3,
h4,
h5 {
color: darken(@white, 40%);
}
.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;
@box-shadow-focused: 0 0 0 2px fade(@primary-color, 50%);
&::before,
&::after {
content: none;
}
.ant-form-item-control {
text-align: right;
}
.ant-input,
.ant-input-number,
.ant-mentions,
.ant-select-selection,
.ant-input-group-addon {
z-index: 1;
text-align: left;
color: lighten(@black, 10%);
border: 0;
background-color: lighten(@black, 95%);
}
.ant-mentions {
textarea {
background-color: lighten(@black, 95%);
}
}
.focus {
z-index: 2 !important;
box-shadow: @box-shadow-focused;
}
.ant-input {
&:focus {
.focus();
}
}
.ant-input-number-focused,
.ant-mentions-focused {
.focus();
}
.ant-select-focused,
.ant-select-open {
z-index: 2;
.ant-select-selection {
.focus();
}
}
.ant-input-group {
.ant-row-flex {
.ant-select {
width: 100%;
}
}
.ant-input-group-addon {
z-index: 0;
}
}
}
.ant-form-item-label {
overflow: hidden;
flex: 1 1 auto;
margin-right: @padding-md;
text-align: left;
text-overflow: ellipsis;
>label {
color: lighten(@black, 10%);
&::after {
content: none;
}
}
}
.ant-form-item-control-wrapper {
flex: 0 0 61.8%;
width: 61.8%;
min-width: 220px;
}
.ant-form-explain {
font-size: @font-size-base - 1px;
margin-top: @padding-xs;
margin-bottom: @padding-xxs;
margin-left: -61.8%;
transition: none;
animation: none;
text-align: left;
opacity: 1;
color: fade(darken(@primary-color, 38.2%), 61.8%);
}
.has-error {
.ant-form-explain {
color: @error-color;
}
}
// 上下布局
.yo-form--vertical {
display: block;
.ant-form-item-control {
text-align: left;
}
&-radio {
.ant-radio-wrapper {
line-height: @padding-lg;
display: block;
margin-right: 0;
+.ant-radio-wrapper {
margin-top: @padding-sm;
}
}
}
.ant-form-item-control-wrapper {
margin-left: @padding-lg;
}
.ant-form-explain {
margin-left: 0;
}
}
}
.yo-modal-form {
.ant-modal-body {
padding: 0;
}
.yo-form {
h1,
h2,
h3,
h4,
h5 {
margin-top: @padding-sm;
padding: 0 @padding-md;
}
.yo-form-group {
margin-bottom: 0;
}
.ant-form-item {
border-right: 0;
border-left: 0;
&:first-child {
margin-top: -1px;
}
}
}
}
.yo-drawer-form {
.ant-drawer-header {
position: absolute;
top: 0;
left: 0;
z-index: 7;
width: 100%;
}
.ant-drawer-body {
padding: @padding-lg + 56px @padding-lg;
}
.ant-drawer-footer {
position: absolute;
left: 0;
bottom: 0;
z-index: 7;
width: 100%;
padding: 10px @padding-md;
text-align: right;
border-top: @border-width-base @border-style-base @border-color-split;
background: @white;
button+button {
margin-left: @padding-xs;
}
}
}

View File

@@ -0,0 +1,24 @@
@import (reference) '~@/assets/style/extend.less';
.yo-list {
&-content--h {
display: flex;
align-items: center;
&--item {
min-width: 100px;
margin-left: @padding-xl;
>span {
line-height: 20px;
color: lighten(@black, 70%);
}
>p {
line-height: 22px;
margin-top: @padding-xxs;
margin-bottom: 0;
color: lighten(@black, 30%);
}
}
}
}

View File

@@ -0,0 +1,52 @@
@import (reference) '~@/assets/style/extend.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) '~@/assets/style/extend.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) '~@/assets/style/extend.less';
.ant-select-arrow {
.ant-select-arrow-icon {
transform: scaleY(.75);
}
}

View File

@@ -0,0 +1,169 @@
@import (reference) '~@/assets/style/extend.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;
}
}
}
.yo-table {
.border-right-none() {
border-right-width: 0 !important;
&:last-child {
border-right-width: 1px !important;
}
}
.ant-table-content {
.ant-table-body {
overflow-x: auto !important;
>table {
>.ant-table-thead {
>tr {
>th {
.border-right-none();
}
}
}
>.ant-table-tbody {
>tr {
>td {
.border-right-none();
}
}
}
}
}
.ant-table-fixed-left {
.ant-table-thead {
>tr {
>th {
border-right-width: 0 !important;
}
}
}
.ant-table-tbody {
>tr {
>td {
border-right-width: 0 !important;
}
}
}
}
.ant-table-fixed-right {
.ant-table-fixed {
border-left-width: 0 !important;
}
.ant-table-thead {
>tr {
>th {
.border-right-none();
}
}
}
.ant-table-tbody {
>tr {
>td {
.border-right-none();
}
}
}
}
}
}
.yo-table-actions {
display: inline-block;
vertical-align: middle;
&--inner {
display: flex;
align-items: center;
height: 18px;
>a {
color: darken(@primary-color, 20%);
}
}
}
.yo-table--column-setting {
width: 240px;
.ant-dropdown-menu-item {
display: flex;
justify-content: space-between;
align-items: center;
}
.anticon-pushpin {
color: darken(@white, 40%);
transition: @animation-duration-slow;
transform: rotate(45deg);
}
.yo-table--fixed {
transform: rotate(-45deg);
}
}

View File

@@ -0,0 +1,43 @@
@import (reference) '~@/assets/style/extend.less';
.yo-tree-layout {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
.ant-layout-sider {
background-color: @white;
.ant-layout-header {
height: @layout-header-height - 20px;
background-color: @white;
.header-actions {
>.ant-input-search {
.ant-input {
color: @black;
background-color: @white;
&:focus {
background-color: fade(@black, 5%);
}
}
.anticon-search {
color: fade(@black, 45%);
}
}
}
}
.swiper-container {
position: absolute;
top: @layout-header-height - 20px;
left: 0;
bottom: 0;
width: 100%;
.swiper-slide {
height: auto;
min-height: 100%;
}
}
}
}

View File

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

View File

@@ -0,0 +1,3 @@
.w-100-p {
width: 100%;
}

View File

@@ -0,0 +1,3 @@
.w-100-p {
width: 100%;
}

View File

@@ -0,0 +1,534 @@
@import (reference) './extend.less';
@import (reference) './lib/container.less';
.main(@nav-background: @layout-header-background,
@nav-box-shadow-color: fade(@black, 25%),
@nav-scrollbar-background: fade(@white, 30%),
@logo-color: @white,
@logo-box-shadow: none,
@app-selector-color: @white,
@app-selector-border-color: fade(@white, 10%),
@app-selector-background-color: fade(@white, 30%),
@app-selector-box-shadow-color: fade(@white, 15%),
@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,
>.ant-select-auto-complete {
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-select-selection {
background-color: transparent;
}
}
}
.user-container {
z-index: 10;
width: 32px + @padding-sm * 2;
height: @layout-header-height - 24px;
margin: 2px 0;
transition: @animation-duration-slow;
.user-container-inner {
position: relative;
transition: @animation-duration-slow;
border-radius: @border-radius-base;
}
.user {
&--base {
line-height: @layout-header-height - 24px;
position: relative;
display: flex;
overflow: hidden;
align-items: center;
width: 100%;
height: @layout-header-height - 24px;
padding: 0 @padding-sm;
transition: @animation-duration-slow;
}
&--avatar {
box-shadow: 0 0 0 2px @white;
}
&--name {
position: absolute;
left: 32px + @padding-sm * 2;
transition: @animation-duration-slow;
opacity: 0;
}
&--dropdown {
width: 200px;
transition: @animation-duration-base;
transform: scaleY(0);
transform-origin: top;
opacity: 0;
.ant-dropdown-menu {
box-shadow: none;
}
}
}
&.open {
width: 200px;
.user-container-inner {
background-color: @white;
box-shadow: @box-shadow-base;
}
.user {
&--name {
opacity: 1;
}
}
}
&.drop {
.user {
&--dropdown {
transform: scaleY(1);
opacity: 1;
}
}
}
}
}
.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: 5;
margin-bottom: 0;
border-bottom: 0;
background-color: @white;
box-shadow: 0 2px 12px fade(@black, 8%);
&::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%;
}
}
}
}
}
.ant-layout-sider {
.ant-menu-inline {
border-right: 0;
}
}
.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: 6;
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 - 16px;
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-select-auto-complete {
.ant-input {
&:focus {
background-color: fade(@black, 5%);
}
}
}
}
}
>section {
>.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: 11;
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;
}
}
}
.yo-sider-nav {
position: relative;
z-index: 10;
display: flex;
flex: 1 1 100%;
flex-direction: column;
box-shadow: 2px 0 8px @nav-box-shadow-color;
}
.swiper-container {
flex: 1 1 100%;
width: 100%;
height: 100%;
.swiper-scrollbar {
transition: @animation-duration-slow;
transition-property: opacity;
opacity: 0;
}
.swiper-scrollbar-drag {
background-color: @nav-scrollbar-background;
}
&:hover {
.swiper-scrollbar {
opacity: 1;
}
}
}
.swiper-slide {
height: auto;
min-height: 100%;
>.ant-spin-nested-loading {
height: 100%;
.ant-spin-blur {
&::after {
opacity: 0;
}
}
}
}
.yo-apps-selector {
>span {
line-height: @layout-header-height / 2 - 2px;
display: block;
height: @layout-header-height / 2;
margin: @padding-xxs @padding-xs;
cursor: pointer;
transition: @animation-duration-slow box-shadow;
text-align: center;
color: @app-selector-color;
border: @border-width-base @border-style-base @app-selector-border-color;
border-radius: @border-radius-base;
background-color: @app-selector-background-color;
&:hover {
box-shadow: 0 0 15px @app-selector-box-shadow-color;
}
}
}
}
}
}
.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-select-auto-complete {
.ant-input {
color: @header-search-color;
background-color: @header-search-background;
&:focus {
background-color: @header-search-focus-background;
}
}
.anticon-search {
color: @header-search-icon-color;
&:hover {
color: @header-search-icon-hover-color;
}
}
}
}
.user-container {
margin: 12px 0;
}
.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;
border-bottom: 0;
>.ant-menu-submenu {
top: 0;
border-bottom: 0;
}
}
.header-actions {
.header-action {
line-height: @layout-header-height - 16px;
margin: 10px 0;
}
}
}
&--container {
.ant-layout-header {
.ant-menu-horizontal {
width: 400px;
}
}
.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 @@
/** 在此文件夹中添加控制主题颜色的less文件 **/

View File

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

156
Web/src/common/api/index.js Normal file
View File

@@ -0,0 +1,156 @@
/**
* api
* v1.2
*/
import axios from 'axios'
import { token } from '@/common/token'
import status from './status'
import app from '@/main'
axios.defaults.baseURL = '/api'
/**
* 最终直接根据url名称调用接口方法
* 例如
* import { api } from '@/api'
* api.getItemGroupType(parmas).then(...)
*/
import urls from './requests'
const initInstance = (options) => {
const instance = axios
.create({
headers: {
Authorization: 'Bearer ' + token.value
},
...options
})
instance.interceptors.response.use((res) => {
if (res.data.status === status.Unauthorized) {
handlerUnauthorized()
}
return res
}, (err) => {
return Promise.reject(err)
})
return instance
}
const errerCodes = [status.BadRequest, status.InternalServerError]
const errorNotification = ({ code, message }) => {
switch (message.constructor) {
case Array:
message.map(p => {
app.$notification.error({
duration: 0,
message: p.field,
description: p.messages.join('/'),
})
})
break
default:
app.$notification.error({
duration: 0,
message: code || '错误',
description: message,
})
break
}
}
const handlerUnauthorized = () => {
token.value = ''
app.$router.replace({
path: '/login'
}).catch(() => { })
}
const api = {}
for (let key in urls) {
const item = urls[key]
let url = '',
method = 'post',
options = {}
if (item.constructor === String) {
url = item
} else if (item.constructor === Array) {
url = item[0]
if (item[1]) {
method = item[1].toLowerCase()
}
if(item[2]) {
options = item[2]
}
} else if (item.constructor === Object) {
url = item.url
if (item.method) {
method = item.method.toLowerCase()
}
}
api[`${key}Wait`] = function (params = {}) {
if (method === 'post') {
return initInstance(options).post(url, params)
} else {
return initInstance(options).get(url, {
params
})
}
}
api[key] = function (params = {}) {
return new Promise((reslove, reject) => {
api[`${key}Wait`](params)
.then((res) => {
const { data } = res
if (errerCodes.indexOf(data.code) >= 0) {
errorNotification(data)
reject([ArrayBuffer, Blob].indexOf(data.constructor) > -1 ? res : data)
} else if (data.code === status.Unauthorized) {
handlerUnauthorized()
} else {
reslove([ArrayBuffer, Blob].indexOf(data.constructor) > -1 ? res : data)
}
})
.catch(({ response: { data } }) => {
if (process.env.VUE_APP_NODE_ENV === 'development') {
errorNotification(data)
reject(data)
if (data.code === status.Unauthorized) {
handlerUnauthorized()
}
} else {
errorNotification({
message: '发生错误,请联系管理员'
})
}
})
})
}
api[key].prototype.name = key
}
/**
* 并发请求,与axios.all方式相同
* 但是使用的接口函数为this.$api.[接口名]E
*/
api.$queue = function (queue) {
return new Promise((reslove) => {
axios.all(queue).then((results) => {
const res = results.map(p => p.data)
reslove(res)
})
})
}
export {
axios,
api,
status
}

View File

@@ -0,0 +1,54 @@
/**
* 接口的3种配置方式
* 1.string
* 如login: '/login'\
* 将会默认已POST方式请求接口/login
* 2.array
* 如login: ['/login', 'post']
* 数组[0]必填,为接口地址,[1]选填,为请求方式(不区分大小写),默认为POST
* 3.object
* 如login: { url: '/login', method: 'post' }
* [url]必填,为接口地址,[method]选填,为请求方式(不区分大小写),默认为POST
*/
import appManage from './sys/appManage'
import configManage from './sys/configManage'
import dictDataManage from './sys/dictDataManage'
import dictManage from './sys/dictManage'
import emailManage from './sys/emailManage'
import fileManage from './sys/fileManage'
import loginManage from './sys/loginManage'
import logManage from './sys/logManage'
import machineManage from './sys/machineManage'
import menuManage from './sys/menuManage'
import noticeManage from './sys/noticeManage'
import onlineUserManage from './sys/onlineUserManage'
import orgManage from './sys/orgManage'
import posManage from './sys/posManage'
import roleManage from './sys/roleManage'
import smsManage from './sys/smsManage'
import tenantManage from './sys/tenantManage'
import timersManage from './sys/timersManage'
import userManage from './sys/userManage'
export default {
...appManage,
...configManage,
...dictDataManage,
...dictManage,
...emailManage,
...fileManage,
...loginManage,
...logManage,
...machineManage,
...menuManage,
...noticeManage,
...onlineUserManage,
...orgManage,
...posManage,
...roleManage,
...smsManage,
...tenantManage,
...timersManage,
...userManage,
}

View File

@@ -0,0 +1,34 @@
export default {
/**
* 系统应用列表
*/
getAppPage: ['/sysApp/page', 'get'],
/**
* 系统应用列表
*/
getAppList: ['/sysApp/list', 'get'],
/**
* 新增系统应用
*/
sysAppAdd: ['/sysApp/add', 'post'],
/**
* 编辑系统应用
*
*/
sysAppEdit: ['/sysApp/edit', 'post'],
/**
* 删除系统应用
*/
sysAppDelete: ['/sysApp/delete', 'post'],
/**
* 设为默认应用
*/
sysAppSetAsDefault: ['/sysApp/setAsDefault', 'post'],
/**
* 修改应用状态
*/
sysAppChangeStatus: ['/sysApp/changeStatus', 'post'],
}

View File

@@ -0,0 +1,26 @@
export default {
/**
* 分页查询配置列表
*/
sysConfigPage: ['/sysConfig/page', 'get'],
/**
* 添加系统参数配置
*/
sysConfigAdd: ['/sysConfig/add', 'post'],
/**
* 编辑系统参数配置
*/
sysConfigEdit: ['/sysConfig/edit', 'post'],
/**
* 删除系统参数配置
*/
sysConfigDelete: ['/sysConfig/delete', 'post'],
/**
* 获取字典类型下所有字典,举例,返回格式为:[{code:"M",value:"男"},{code:"F",value:"女"}]
*/
sysDictTypeDropDown: ['/sysDictType/dropDown', 'get'],
/**
* 获取系统的所有任务列表
*/
sysTimersGetActionClasses: ['/sysTimers/getActionClasses', 'get'],
}

View File

@@ -0,0 +1,18 @@
export default {
/**
* 查询系统字典值
*/
sysDictDataPage: ['/sysDictData/page', 'get'],
/**
* 添加系统字典值
*/
sysDictDataAdd: ['/sysDictData/add', 'post'],
/**
* 编辑系统字典值
*/
sysDictDataEdit: ['/sysDictData/edit', 'post'],
/**
* 删除系统字典值
*/
sysDictDataDelete: ['/sysDictData/delete', 'post'],
}

View File

@@ -0,0 +1,26 @@
export default {
/**
* 分页查询系统字典类型
*/
sysDictTypePage: ['/sysDictType/page', 'get'],
/**
* 添加系统字典类型
*/
sysDictTypeAdd: ['/sysDictType/add', 'post'],
/**
* 编辑系统字典类型
*/
sysDictTypeEdit: ['/sysDictType/edit', 'post'],
/**
* 删除系统字典类型
*/
sysDictTypeDelete: ['/sysDictType/delete', 'post'],
/**
* 获取字典类型下所有字典,举例,返回格式为:[{code:"M",value:"男"},{code:"F",value:"女"}]
*/
sysDictTypeDropDown: ['/sysDictType/dropDown', 'get'],
/**
* 获取所有字典,启动时加入缓存使用
*/
sysDictTypeTree: ['/sysDictType/tree', 'get'],
}

View File

@@ -0,0 +1,10 @@
export default {
/**
* 发送邮件
*/
emailSendEmail: ['/email/sendEmail', 'post'],
/**
* 发送html邮件
*/
emailSendEmailHtml: ['/email/sendEmailHtml', 'post'],
}

View File

@@ -0,0 +1,36 @@
export default {
/**
* 分页查询文件信息表
*/
sysFileInfoPage: ['/sysFileInfo/page', 'get'],
/**
* 获取全部文件信息表
*/
sysFileInfoList: ['/sysFileInfo/list', 'get'],
/**
* 上传文件
*/
sysFileInfoUpload: ['/sysFileInfo/upload', 'post'],
/**
* 下载文件
*/
sysFileInfoDownload: ['/sysFileInfo/download', 'get', {
responseType: 'blob'
}],
/**
* 查看图片
*/
sysFileInfoPreview: ['/sysFileInfo/preview', 'get', {
responseType: 'arraybuffer'
}],
/**
* 查看详情文件信息表
*/
sysFileInfoDetail: ['/sysFileInfo/detail', 'get'],
/**
* 删除文件信息表
*/
sysFileInfoDelete: ['/sysFileInfo/delete', 'post'],
}

View File

@@ -0,0 +1,23 @@
export default {
/**
* 查询访问日志
*/
sysVisLogPage:['/sysVisLog/page','get'],
/**
* 查询操作日志
*/
sysOpLogPage:['/sysOpLog/page','get'],
/**
* 清空访问日志
*/
sysVisLogDelete:['/sysVisLog/delete','post'],
/**
* 清空登录日志
*/
sysOpLogDelete:['/sysOpLog/delete','post'],
}

View File

@@ -0,0 +1,34 @@
export default {
/**
* 登录
*/
login: ['/login', 'post'],
/**
* 登出
*/
logout: ['/logout', 'get'],
/**
* 获取登录用户信息
*/
getLoginUser: ['/getLoginUser', 'get'],
/**
* 获取租户开关
*/
getTenantOpen: ['/getTenantOpen', 'get'],
/**
* 获取短信验证码
*/
getSmsCaptcha: ['/getSmsCaptcha', 'get'],
/**
* 获取验证码开关
*/
getCaptchaOpen: ['/getCaptchaOpen', 'get'],
/**
* 获取验证图片 以及token
*/
reqGet: ['/captcha/get', 'post'],
/**
* 滑动或者点选验证
*/
reqCheck: ['/captcha/check', 'post'],
}

View File

@@ -0,0 +1,10 @@
export default {
/**
*
* 系统属性监控
*
*/
sysMachineUse:['/sysMachine/use','get'],
sysMachineBase:['/sysMachine/base','get'],
sysMachineNetwork:['/sysMachine/network','get']
}

View File

@@ -0,0 +1,66 @@
export default {
/**
* 获取菜单列表
*
* @author yubaoshan
* @param parameter
* @returns {*}
*/
getMenuList:['/sysMenu/list','get'],
/**
* 获取系统菜单树,用于新增,编辑时选择上级节点
*
* @author yubaoshan
* @date 2020/4/23 12:22
*/
getMenuTree:['/sysMenu/tree','get'],
/**
* 增加菜单
*
* @author yubaoshan
* @date 2020/4/24 23:23
*/
sysMenuAdd:['/sysMenu/add','post'],
/**
* 增加菜单
*
* @author yubaoshan
* @date 2020/4/24 23:23
*/
sysMenuDelete:['/sysMenu/delete','post'],
/**
* 查看菜单详情
*
* @author yubaoshan
* @date 2020/4/25 01:11
*/
sysMenuDetail:['/sysMenu/detail','post'],
/**
* 编辑系统菜单
*
* @author yubaoshan
* @date 2020/4/25 01:11
*/
sysMenuEdit:['/sysMenu/edit','post'],
/**
* 获取系统菜单树,用于给角色授权时选择
*
* @author yubaoshan
* @date 2020/6/2 17:30
*/
SysMenuTreeForGrant:['/sysMenu/treeForGrant','get'],
/**
* 根据系统切换菜单
*
* @author yubaoshan
* @date 2020/6/28 15:25
*/
sysMenuChange:['/sysMenu/change','post'],
}

View File

@@ -0,0 +1,38 @@
export default {
/**
* 查询系统通知公告
*/
sysNoticePage:['/sysNotice/page','get'],
/**
* 添加系统通知公告
*/
sysNoticeAdd:['/sysNotice/add','post'],
/**
* 编辑系统通知公告
*/
sysNoticeEdit:['/sysNotice/edit','post'],
/**
* 删除系统通知公告
*/
sysNoticeDelete:['/sysNotice/delete','post'],
/**
* 通知公告详情
*/
sysNoticeDetail:['/sysNotice/detail','get'],
/**
* 修改状态
*/
sysNoticeChangeStatus:['/sysNotice/changeStatus','post'],
}

View File

@@ -0,0 +1,6 @@
export default {
/**
* 查询我收到的系统通知公告
*/
sysNoticeReceived:['/sysNotice/received','get'],
}

View File

@@ -0,0 +1,12 @@
export default{
/**
* 在线用户列表
*/
sysOnlineUserList:['/sysOnlineUser/list','get'],
/**
* 强制下线
*/
sysOnlineUserForceExist:['/sysOnlineUser/forceExist','post'],
}

View File

@@ -0,0 +1,38 @@
export default {
/**
* 获取机构树
*
*/
getOrgTree:['/sysOrg/tree','get'],
/**
* 获取机构列表
*
*/
getOrgList:['/sysOrg/list','get'],
/**
* 获取机构列表
*
*/
getOrgPage:['/sysOrg/page','get'],
/**
* 新增机构
*
*/
sysOrgAdd:['/sysOrg/add','post'],
/**
* 编辑机构
*
*/
sysOrgEdit:['/sysOrg/edit','post'],
/**
* 删除机构
*
*/
sysOrgDelete:['/sysOrg/delete','post'],
}

View File

@@ -0,0 +1,32 @@
export default {
/**
* 查询系统职位
*
*/
sysPosPage:['/sysPos/page','get'],
/**
* 系统职位列表
*
*/
sysPosList:['/sysPos/list','get'],
/**
* 添加系统职位
*
*/
sysPosAdd:[ '/sysPos/add','post'],
/**
* 编辑系统职位
*
*/
sysPosEdit:['/sysPos/edit','post'],
/**
* 删除系统职位
*
*/
sysPosDelete:['/sysPos/delete','post'],
}

View File

@@ -0,0 +1,62 @@
export default {
/**
* 获取角色列表
*/
getRolePage:['/sysRole/page','get'],
/**
* 增加角色
*/
sysRoleAdd:['/sysRole/add','post'],
/**
* 编辑角色
*/
sysRoleEdit:['/sysRole/edit','post'],
/**
* 删除角色
*/
sysRoleDelete:['/sysRole/delete','post'],
/**
* 删除角色
*/
sysRoleDeteil:['/sysRole/detail','get'],
/**
* 获取授权角色列表
*/
sysRoleDropDown:['/sysRole/dropDown','get'],
/**
* 拥有菜单
*/
sysRoleOwnMenu:['/sysRole/ownMenu','get'],
/**
* 授权菜单
*/
sysRoleGrantMenu:['/sysRole/grantMenu','post'],
/**
* 拥有数据
*/
sysRoleOwnData:['/sysRole/ownData','get'],
/**
* 授权数据
*/
sysRoleGrantData:['/sysRole/grantData','post'],
}

View File

@@ -0,0 +1,26 @@
export default {
/**
* 发送记录查询
*
* @author yubaoshan
* @date 2020/7/3 22:11
*/
smsPage:['/sms/page','get'],
/**
* 验证短信验证码
*
* @author yubaoshan
* @date 2020/7/3 22:12
*/
sysSendLoginMessage:['/sms/sendLoginMessage','post'],
/**
* 验证短信验证码
*
* @author yubaoshan
* @date 2020/7/3 22:12
*/
sysValidateMessage:['/sms/validateMessage','post'],
}

View File

@@ -0,0 +1,26 @@
export default {
/**
* 租户列表
*
*/
sysTenantPage:['/sysTenant/page','get'],
/**
* 新增租户
*
*/
sysTenantAdd:['/sysTenant/add','post'],
/**
* 删除租户
*
*/
sysTenantDelete:['/sysTenant/delete','post'],
/**
* 编辑租户
*
*/
sysTenantEdit:['/sysTenant/edit','post'],
}

View File

@@ -0,0 +1,56 @@
export default {
/**
* 分页查询定时任务
*/
sysTimersPage:['/sysTimers/page','get'],
/**
* 获取全部定时任务
*/
sysTimersList:['/sysTimers/list','get'],
/**
* 查看详情定时任务
*/
sysTimersDetail:['/sysTimers/detail','get'],
/**
* 添加定时任务
*/
sysTimersAdd:['/sysTimers/add','post'],
/**
* 删除定时任务
*/
sysTimersDelete:['/sysTimers/delete','post'],
/**
* 编辑定时任务
*/
sysTimersEdit:['/sysTimers/edit','post'],
/**
* 获取系统的所有任务列表
*/
sysTimersGetActionClasses:['/sysTimers/getActionClasses','post'],
/**
* 启动定时任务
*/
sysTimersStart:['/sysTimers/start','post'],
/**
* 停止定时任务
*/
sysTimersStop:['/sysTimers/stop','post'],
}

View File

@@ -0,0 +1,76 @@
export default {
/**
* 获取用户列表
*/
getUserPage:['/sysUser/page','get'],
/**
* 增加用户
*/
sysUserAdd:['/sysUser/add','post'],
/**
* 编辑用户
*/
sysUserEdit:['/sysUser/edit','post'],
/**
* 获取用户详情
*/
sysUserDetail:['/sysUser/detail','get'],
/**
* 删除用户
*/
sysUserDelete:['/sysUser/delete','post'],
/**
* 拥有角色
*/
sysUserOwnRole:['/sysUser/ownRole','get'],
/**
* 授权角色
*/
sysUserGrantRole:['/sysUser/grantRole','post'],
/**
* 拥有数据
*/
sysUserOwnData:['/sysUser/ownData','get'],
/**
* 授权数据
*/
sysUserGrantData:['/sysUser/grantData','post'],
/**
* 修改状态
*/
sysUserChangeStatus:['/sysUser/changeStatus','post'],
/**
* 重置密码
*/
sysUserResetPwd:['/sysUser/resetPwd','post'],
/**
* 修改密码
*/
sysUserUpdatePwd:['/sysUser/updatePwd','post'],
/**
* 用户选择器
*/
sysUserSelector:['/sysUser/selector','get'],
/**
* 修改头像
*/
sysUserUpdateAvatar:['/sysUser/updateAvatar','post'],
/**
* 更新基本信息
*/
sysUserUpdateInfo:['/sysUser/updateInfo','post'],
}

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 { GLOBAL_INFO_KEY } from '@/common/storage'
import { encryptByDES, decryptByDES } from '@/util/des'
import app from '@/main'
const setGlobal = (info) => {
app.$set(app.global, 'info', info)
window.sessionStorage.setItem(GLOBAL_INFO_KEY, encryptByDES(JSON.stringify(info)))
}
const removeGlobal = () => {
app.$set(app.global, 'info', undefined)
window.sessionStorage.removeItem(GLOBAL_INFO_KEY)
}
const getGlobal = () => {
return JSON.parse(decryptByDES(window.sessionStorage.getItem(GLOBAL_INFO_KEY)))
}
const doLogin = (args) => {
return new Promise((resolve, reject) => {
api.login(args).then(({ success, data, message }) => {
if (success) {
token.value = data
app.$message.success('登录成功')
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(message)
reject()
}
}).catch(({ message }) => {
if (typeof message === 'object' && message[0]) {
app.$message.error(message[0].messages[0])
}
})
})
}
const doLogout = () => {
return new Promise((resolve, reject) => {
api.logout().then(({ 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,
setGlobal,
getGlobal
}

View File

@@ -0,0 +1,11 @@
const SESSION_KEY = '__SESSION'
const SETTING_KEY = '__SETTINGS'
const GLOBAL_INFO_KEY = '__GLOBAL_INFO'
const APP_MENU_KEY = '__APP_MENU'
export {
SESSION_KEY,
SETTING_KEY,
GLOBAL_INFO_KEY,
APP_MENU_KEY
}

View File

@@ -0,0 +1,19 @@
import { SESSION_KEY } from '@/common/storage'
const token = {
get value() {
return window.localStorage.getItem(SESSION_KEY)
},
set value(token) {
if (!token) {
window.localStorage.removeItem(SESSION_KEY)
} else {
window.localStorage.setItem(SESSION_KEY, token)
}
}
}
export {
SESSION_KEY,
token
}

View File

@@ -0,0 +1,192 @@
/**
* auth: 允许的权限
* authExclude: 排除的权限
*
* auth的几种传值方式
* 1.String
* 例: auth="sysApp:page"
* 直接传入字符串,对单项权限进行验证
*
* 2.Array
* 2.1.单项权限
* 例: :auth="['sysApp:page']"
* 2.2.并且关系多项权限
* 例: :auth="['sysApp:page', 'sysApp:add']"
* 数组中传入多个字符串
* 此时验证的是同时拥有"sysApp:page"和"sysApp:add"两项权限才会渲染
* 2.3.或者关系多项权限
* 例: :auth="[['sysApp:page', 'sysApp:add'], ['sysApp:edit']]"
* 二维数组结构,内部数组之间为并且关系
* 此时验证的是"sysApp:page"&"sysApp:add"||"sysApp:edit"
* 注意:或者的条件必须包括在数组中,暴露在外则判定为并且
* 2.4.可直接传入布尔值
* 例: :auth="['sysApp:page', 1 === 1]"
* :auth="[['sysApp:page', 'sysApp:add'], [1 === 1]]"
*
* 3.Json
* 如果觉得多项权限时每次都要写应用编号比较繁琐,可对Array形式进行简化
* 3.1.单项权限
* 例: :auth="{ sysApp: 'page' }"
* 3.2.并且关系多项权限
* 例: :auth="{ sysApp: ['page', 'add'] }"
* 3.3.或者关系多项权限
* 例: :auth="{ sysApp: [['page', 'add'], ['edit']]}"
* 3.4.可直接传入布尔值
* 例: :auth="{ sysApp: ['page', 1 === 1] }"
* :auth="{ sysApp: [['page', 'add'], [1 === 1]] }"
*
*/
import app from '@/main'
const authByArray = (auth, permissions) => {
const flags = []
auth.forEach(p => {
switch (p.constructor) {
case String:
flags.push([permissions.indexOf(p) > -1, '&&'])
break
case Array:
flags.push([authByArray(p, permissions), '||'])
break
case Boolean:
flags.push([p, '&&'])
break
}
})
let result
flags.forEach((p, i) => {
if (p[1] === '&&') {
if (i === 0) {
result = true
}
if (result) {
result = p[0]
}
} else {
if (i === 0) {
result = false
}
if (!result) {
result = p[0]
}
}
//result = p[1] === '&&' ? result && p[0] : result || p[0]
})
return result
}
const authByJson = (auth, permissions) => {
let result = true
const flags = []
const deepName = (arr, key) => {
arr.forEach((p, i) => {
switch (p.constructor) {
case String:
arr[i] = `${key}:${p}`
break
case Array:
p = deepName(p, key)
break
default:
p = p
break
}
})
return arr
}
for (let key in auth) {
const app = auth[key]
switch (app.constructor) {
case String:
flags.push(permissions.indexOf(`${key}:${p}`) > -1)
break
case Array:
flags.push(authByArray(deepName(app, key), permissions))
break
}
}
flags.forEach(p => {
result = result && p
})
return result
}
export const auth = (auth) => {
const { info } = app.global
if (!info) {
return false
}
/**
* 超级管理员
*/
if (info.adminType === 1) {
return true
}
const permissions = info.permissions
let flag = false
if (auth) {
switch (auth.constructor) {
case String:
flag = permissions.indexOf(auth) > -1
break
case Array:
flag = authByArray(auth, permissions)
break
case Object:
flag = authByJson(auth, permissions)
break
}
}
return flag
}
export default {
functional: true,
props: {
auth: {
default() {
return new Array()
},
type: [Array, Object, String],
},
authExclude: {
default() {
return new Array()
},
type: Array,
},
},
render(h, context) {
const { props, scopedSlots } = context
const authExclude = props.authExclude
let flag = auth(props.auth)
if (flag) {
return scopedSlots.default()
}
return false
},
}

View File

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

View File

@@ -0,0 +1,202 @@
export default {
props: {
loadData: {
type: Function,
require: true,
},
autoLoad: {
type: Boolean,
default: true
},
defaultSelectedKeys: {
type: Array,
default: []
}
},
data() {
return {
loading: false,
data: [],
list: []
}
},
created() {
if (this.autoLoad) {
this.onLoadData()
}
},
methods: {
renderDescriptions(data) {
return data.map(p => {
return p.children && p.children.length ? this.renderItem(p) : this.renderCheckbox(p)
})
},
renderItem(data) {
return (
<a-descriptions bordered column={1}>
<a-descriptions-item>
<a-checkbox
slot="label"
value={data.id}
checked={data.checked}
indeterminate={data.indeterminate}
onChange={(e) => this.onChange(e, data)}
>{data.title}</a-checkbox>
{this.renderDescriptions(data.children)}
</a-descriptions-item>
</a-descriptions>
)
},
renderCheckbox(data) {
return (
<div class="yo-authority-view--checkbox">
<a-checkbox
value={data.id}
checked={data.checked}
onChange={(e) => this.onChange(e, data)}
>{data.title}</a-checkbox>
</div>
)
},
onLoadData() {
this.loading = true
this.loadData().then((res) => {
this.data = this.generateCheck(res)
this.list = []
this.generateList(this.data)
if (this.defaultSelectedKeys.length) {
this.list.map(p => {
if (this.defaultSelectedKeys.indexOf(p.id) > -1 && (!p.children || !p.children.length)) {
this.onSelect(true, p)
}
})
}
this.loading = false
})
},
onReloadData() {
this.data = []
this.onLoadData()
},
onChange(e, item) {
this.onSelect(e.target.checked, item)
this.$emit('select', this.list.filter(p => p.checked).map(p => p.id), this.list.filter(p => p.checked || p.indeterminate).map(p => p.id))
},
onSelect(check, item) {
item.checked = check
item.indeterminate = false
if (item.children && item.children.length) {
this.onChangeChildren(item.checked, item.children)
}
if (item.parentId) {
this.onChangeParent(item.checked, item.parentId)
}
},
onChangeParent(checked, parentId) {
const parent = this.list.find(p => p.id === parentId)
if (parent) {
const checkedCount = parent.children.filter(p => p.checked).length
const indeterminateCount = parent.children.filter(p => p.indeterminate).length
if (checkedCount === parent.children.length) {
// 全选
parent.checked = true
parent.indeterminate = false
} else if (!checkedCount && !indeterminateCount) {
// 全不选
parent.checked = false
parent.indeterminate = false
} else {
// 半选
parent.checked = false
parent.indeterminate = true
}
this.onChangeParent(checked, parent.parentId)
}
},
onChangeChildren(checked, children) {
children.forEach(p => {
p.checked = checked
p.indeterminate = false
if (p.children && p.children.length) {
this.onChangeChildren(checked, p.children)
}
})
},
generateCheck(data) {
data.forEach(p => {
if (p.children && p.children.length) {
p.children = this.generateCheck(p.children)
}
p.checked = false
p.indeterminate = false
})
return data
},
generateList(data) {
data.forEach(p => {
if (p.children && p.children.length) {
this.generateList(p.children)
}
this.list.push(p)
})
},
},
render() {
return (
<div class="yo-authority-view">
<a-spin style={{ width: '100%' }} spinning={this.loading}>
<a-icon slot="indicator" type="loading" spin />
{
!this.loading &&
<a-descriptions bordered column={1}>
{
this.data.map(p => {
return (
<a-descriptions-item>
<a-checkbox
slot="label"
value={p.id}
checked={p.checked}
indeterminate={p.indeterminate}
onChange={(e) => this.onChange(e, p)}
>{p.title}</a-checkbox>
{this.renderDescriptions(p.children)}
</a-descriptions-item>
)
})
}
</a-descriptions>
}
</a-spin>
</div>
)
}
}

View File

@@ -0,0 +1,107 @@
export default {
props: {
pageNo: {
default: 1,
type: Number,
},
pageSize: {
default: 10,
type: Number,
},
loadData: {
type: Function,
require: true,
},
},
data() {
return {
loading: false,
data: [],
pagination: {
current: this.pageNo,
pageSize: this.pageSize,
total: 0,
size: 'small',
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `总共${total}条数据`
},
};
},
created() {
this.onLoadData()
},
methods: {
onLoading() {
this.loading = {
indicator: <a-icon type="loading" spin />
}
},
onLoaded() {
this.loading = false
},
onLoadData() {
this.onLoading()
this.loadData({
pageNo: this.pagination.current,
pageSize: this.pagination.pageSize,
...this.sorter
}).then((res) => {
this.data = res.rows
this.pagination.total = res.totalRows
this.onLoaded()
})
},
onReloadData(refresh = false) {
if (refresh && refresh.constructor === Boolean && this.pagination.constructor === Object) {
this.pagination.current = this.pageNo
this.pagination.pageSize = this.pageSize
}
this.onLoadData()
},
},
render() {
const props = {
loading: this.loading,
pagination: this.pagination,
dataSource: this.data,
rowKey: record => record.id,
...this.$attrs
}
const on = {
//change: this.onTableChange
}
return (
<section>
<div class="yo-action-bar">
<div class="yo-action-bar--actions">
{this.$scopedSlots.operator && this.$scopedSlots.operator()}
</div>
<div class="yo-action-bar--actions">
<a-button-group>
<a-button onClick={this.onReloadData}>刷新</a-button>
</a-button-group>
</div>
</div>
<a-list {...{ props, on, scopedSlots: { ...this.$scopedSlots } }}>
{Object.keys(this.$slots).map((name) => (
<template slot={name}>{this.$slots[name]}</template>
))}
</a-list>
</section>
)
},
}

View File

@@ -0,0 +1,64 @@
<template>
<a-dropdown :trigger="['click']" placement="bottomRight" v-model="visible">
<a-button>显示列</a-button>
<a-menu @click="() => { return false; }" class="yo-table--column-setting" slot="overlay">
<a-menu-item>
<a-checkbox :checked="checkedAll" :indeterminate="halfChecked" @change="onCheckAll">全选</a-checkbox>
</a-menu-item>
<a-menu-divider />
<a-menu-item :key="column.dataIndex || column.key" v-for="column in columns">
<a-checkbox :checked="!column.hidden" @change="(e) => onCheck(column, e)">{{column.title}}</a-checkbox>
<a-icon
:class="{ 'yo-table--fixed': column.fixed }"
@click="onFixed(column)"
type="pushpin"
/>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<script>
export default {
props: {
columns: {
type: Array,
require: true,
},
},
data() {
return {
visible: false,
checkedAll: true,
halfChecked: false,
};
},
mounted() {
this.onHalfCheck();
},
methods: {
onHalfCheck() {
this.halfChecked = this.columns.filter((p) => p.hidden).length > 0;
},
onCheck(column, e) {
this.$set(column, 'hidden', !e.target.checked);
this.onHalfCheck();
},
onCheckAll(e) {
this.columns.forEach((column) => {
this.$set(column, 'hidden', !e.target.checked);
});
this.checkedAll = e.target.checked;
},
onFixed(column) {
this.$set(column, 'fixed', !column.fixed);
},
},
};
</script>

View File

@@ -0,0 +1,198 @@
// 列设置用jsx实现起来较为困难
import ColumnSetting from './column'
export default {
props: {
pageNo: {
default: 1,
type: Number,
},
pageSize: {
default: 10,
type: Number,
},
loadData: {
type: Function,
require: true,
},
columns: {
type: Array,
require: true,
},
},
data() {
return {
loading: false,
type: '',
data: [],
pagination: {
current: this.pageNo,
pageSize: this.pageSize,
total: 0,
size: 'small',
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `总共${total}条数据`
},
sorter: {
sortField: '',
sortOrder: '',
},
columnSettingVisible: false
};
},
created() {
this.onLoadData()
},
methods: {
renderColumnSetting() {
const props = {
visible: this.columnSettingVisible,
placement: 'bottomRight'
}
const on = {
visibleChange: (visible) => {
this.columnSettingVisible = visible
}
}
return (
<a-dropdown {...{ props, on }}>
<a-button onClick={() => this.columnSettingVisible = true}>设置列</a-button>
<a-menu slot="overlay" onClick={() => { return false }}>
{
this.columns.map(column => {
return (
<a-menu-item key={column.dataIndex || column.key}>
<a-checkbox checked={column.hidden} onChange={() => { column.hidden = !column.hidden }}>{column.title}</a-checkbox>
</a-menu-item>
)
})
}
</a-menu>
</a-dropdown>
)
},
onLoading() {
this.loading = {
indicator: <a-icon type="loading" spin />
}
},
onLoaded() {
this.loading = false
},
onLoadData() {
this.onLoading()
this.loadData({
pageNo: this.pagination.current,
pageSize: this.pagination.pageSize,
...this.sorter
}).then((res) => {
if (res.rows) {
// 普通表格
this.type = 'table'
this.data = res.rows
this.pagination.total = res.totalRows
} else if (res) {
// 树形表格
this.type = 'tree'
this.data = this.onClearChildren(res)
this.pagination = false
}
this.onLoaded()
})
},
onReloadData(refresh = false) {
if (refresh && refresh.constructor === Boolean && this.pagination.constructor === Object) {
this.pagination.current = this.pageNo
this.pagination.pageSize = this.pageSize
}
this.onLoadData()
},
onTableChange(pagination, filters, sorter) {
this.pagination = pagination
this.sorter = sorter
this.onLoadData()
},
/**
* 清除没有子节点内容的子节点位置
*/
onClearChildren(data) {
data.forEach(p => {
if (p.children) {
if (p.children.length) {
p.children = this.onClearChildren(p.children)
} else {
delete p.children
}
}
})
return data
},
},
render() {
const props = {
loading: this.loading,
pagination: this.pagination,
dataSource: this.data,
columns: this.columns.filter(p => !p.hidden),
bordered: true,
size: 'middle',
rowKey: record => record.id,
scroll: { x: 'max-content' }
}
const on = {
change: this.onTableChange
}
return (
<section>
<a-alert type="warning" closable>
<template slot="message">
后端没有排序参数
<br />
字段固定应该遵循左侧固定到最左,右侧固定到最右(此逻辑难以实现)
</template>
</a-alert>
<br />
<div class="yo-action-bar">
<div class="yo-action-bar--actions">
{this.$scopedSlots.operator && this.$scopedSlots.operator()}
</div>
<div class="yo-action-bar--actions">
<a-button-group>
<a-button onClick={this.onReloadData}>刷新</a-button>
{
this.type === 'table' && <ColumnSetting {...{ props: { columns: this.columns } }} />
}
</a-button-group>
</div>
</div>
<a-table class="yo-table" {...{ props, on, scopedSlots: { ...this.$scopedSlots } }}>
{Object.keys(this.$slots).map((name) => (
<template slot={name}>{this.$slots[name]}</template>
))}
</a-table>
</section>
)
},
}

View File

@@ -0,0 +1,23 @@
export default {
render() {
const components = []
const slots = this.$slots.default.filter(p => p.tag)
slots.forEach((p, i) => {
components.push(p)
if (i < slots.length - 1) {
components.push(<a-divider type="vertical" />)
}
})
return (
<div class="yo-table-actions">
<div class="yo-table-actions--inner">
{components}
</div>
</div>
)
}
}

View File

@@ -0,0 +1,188 @@
export default {
props: {
loadData: {
type: Function,
require: true,
},
defaultExpandedKeys: {
default: false,
type: Boolean
},
},
data() {
return {
loading: false,
data: [],
list: [],
searchValue: '',
expandedKeys: [],
autoExpandParent: true
}
},
created() {
this.onLoadData()
},
methods: {
onLoadData() {
this.loading = true
this.loadData().then((res) => {
const data = this.generateKey(res)
this.list = []
this.generateList(data)
if (this.defaultExpandedKeys) {
this.expandedKeys = this.list.map(p => p.key)
}
this.data = data
this.loading = false
})
},
onReloadData() {
this.onLoadData()
},
onExpand(expandedKeys) {
this.expandedKeys = expandedKeys;
this.autoExpandParent = false;
},
onSearch(value) {
const expandedKeys = this.list
.map(p => {
if (p.title.indexOf(value) > -1) {
return this.getParentKey(p.key, this.data)
}
return null
})
.filter((p, i, self) => p && self.indexOf(p) === i)
this.searchValue = value
this.expandedKeys = expandedKeys
this.autoExpandParent = true
},
onSelect(selectedKeys) {
const selectedIds = []
selectedKeys.forEach(p => {
const data = this.list.find(m => m.key === p)
selectedIds.push(data.id)
})
this.$emit('select', selectedIds)
},
generateKey(data, level) {
const n = level || [0]
n.push(0)
data.forEach((p, i) => {
n[n.length - 1] = i
p.key = n.join('-')
p.scopedSlots = { title: 'title' }
if (p.children) {
this.generateKey(p.children, Object.assign([], n))
}
})
return data
},
generateList(data) {
// 这里获取不到Key
for (let i = 0; i < data.length; i++) {
const { key, id, title, children } = data[i]
this.list.push({ key, id, title });
if (children) {
this.generateList(children);
}
}
},
getParentKey(key, tree) {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some(item => item.key === key)) {
parentKey = node.key;
} else if (this.getParentKey(key, node.children)) {
parentKey = this.getParentKey(key, node.children);
}
}
}
return parentKey;
},
},
render() {
const swiperOptions = {
direction: 'vertical',
slidesPerView: 'auto',
freeMode: true,
scrollbar: true,
mousewheel: true,
}
const props = {
treeData: this.data,
expandedKeys: this.expandedKeys,
autoExpandParent: this.autoExpandParent,
}
const on = {
expand: this.onExpand,
select: this.onSelect
}
const scopedSlots = {
title: ({ title }) => {
return (
<div>
{
title.indexOf(this.searchValue) > -1 ?
<span>
{title.substr(0, title.indexOf(this.searchValue))}
<span style="color: #f50">{this.searchValue}</span>
{title.substr(title.indexOf(this.searchValue) + this.searchValue.length)}
</span>
:
<span>{title}</span>
}
</div>
);
}
}
return (
<a-layout class="yo-tree-layout">
<a-layout-sider width="240px">
<a-layout-header>
<div class="header-actions">
<a-input-search allowClear={true} placeholder="请输入检索关键字" onSearch={this.onSearch} />
</div>
</a-layout-header>
<swiper options={swiperOptions}>
<a-spin style={{ height: '100%' }} spinning={this.loading}>
<a-icon slot="indicator" type="loading" spin />
<swiper-slide>
<a-tree {...{ props, on, scopedSlots }} />
</swiper-slide>
</a-spin>
</swiper>
</a-layout-sider>
<a-layout-content>
{this.$scopedSlots.default ? this.$scopedSlots.default() : null}
</a-layout-content>
</a-layout>
)
}
}

136
Web/src/main.js Normal file
View File

@@ -0,0 +1,136 @@
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 { auth } from './components/authorized'
Vue.prototype.$auth = auth
/**
* 注册全局组件
*/
import Container from './components/container'
Vue.component('Container', Container)
import Authorized from './components/authorized'
Vue.component('Auth', Authorized)
import YoTable from './components/yoTable'
Vue.component('YoTable', YoTable)
import YoTableActions from './components/yoTableActions'
Vue.component('YoTableActions', YoTableActions)
/**
* 引入主题样式
*/
import './assets/style/app.less'
import { SETTING_KEY } from './common/storage'
const settings = JSON.parse(window.localStorage.getItem(SETTING_KEY))
const app = new Vue({
data: {
/**
* 全局属性
* 可通过this.$root.global调用
*/
global: {
defaultWindow: [{
title: '首页',
path: '/home',
icon: 'home',
closable: false,
}],
/**
* 用于存储用户信息
*/
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(SETTING_KEY, 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

View File

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

View File

@@ -0,0 +1,7 @@
<template>
<container>
<a-row :gutter="16">
<a-col></a-col>
</a-row>
</container>
</template>

79
Web/src/pages/form/yo.vue Normal file
View File

@@ -0,0 +1,79 @@
<template>
<container>
<br />
<a-form-model class="yo-form yo-form--fixed">
<h3>输入框</h3>
<Input />
<h3>数字输入框</h3>
<div class="yo-form-group">
<a-form-model-item label="数字输入框">
<span slot="help">
<a-tag color="pink">a-input-number</a-tag>可以输入和选择数字
</span>
<a-input-number autocomplete="off" placeholder="请输入数字" />
</a-form-model-item>
</div>
<h3>文本域</h3>
<div class="yo-form-group">
<a-form-model-item label="文本域">
<a-textarea autocomplete="off" placeholder="请输入文字" />
</a-form-model-item>
</div>
<h3>提及</h3>
<div class="yo-form-group">
<a-form-model-item label="提及">
<a-mentions placeholder="请输入'@'进行提及">
<a-mentions-option value="史莱姆">史莱姆</a-mentions-option>
<a-mentions-option value="哥布林">哥布林</a-mentions-option>
<a-mentions-option value="牛头人">牛头人</a-mentions-option>
</a-mentions>
</a-form-model-item>
</div>
<h3>单选框</h3>
<div class="yo-form-group">
<a-form-model-item label="单选框">
<span slot="help">
<a-tag color="pink">a-radio-group > a-radio</a-tag>横排的单选框请注意因为控件区域宽度的限制在这里无法使用单选框按钮
</span>
<a-radio-group>
<a-radio value="1">攻击</a-radio>
<a-radio value="2">防御</a-radio>
<a-radio value="3">剑技</a-radio>
<a-radio value="4">魔法</a-radio>
<a-radio value="5">回复</a-radio>
<a-radio value="6">虐杀</a-radio>
<a-radio value="7">踩在脚下</a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item class="yo-form--vertical yo-form--vertical-radio" label="垂直单选框">
<span slot="help">
垂直单选框需要在垂直布局控件的基础上添加类
<a-tag color="pink">.yo-form--vertical-radio</a-tag>
</span>
<a-radio-group>
<a-radio value="1">攻击</a-radio>
<a-radio value="2">防御</a-radio>
<a-radio value="3">剑技</a-radio>
<a-radio value="4">魔法</a-radio>
<a-radio value="5">回复</a-radio>
<a-radio value="6">虐杀</a-radio>
<a-radio value="7">踩在脚下</a-radio>
<a-radio value="8">
<a-input />
</a-radio>
</a-radio-group>
</a-form-model-item>
</div>
</a-form-model>
</container>
</template>
<script>
import Input from './yo/Input';
export default {
components: {
Input,
},
};
</script>

View File

@@ -0,0 +1,63 @@
<template>
<div class="yo-form-group">
<a-form-model-item label="输入框">
<a-input autocomplete="off" placeholder="请输入文字" />
</a-form-model-item>
<a-form-model-item label="只读输入框">
<a-input autocomplete="off" placeholder="请输入文字" readonly />
</a-form-model-item>
<a-form-model-item label="前缀和后缀">
<a-input addon-after="后缀" addon-before="前缀" autocomplete="off" placeholder="请输入文字" />
</a-form-model-item>
<a-form-model-item label="输入框组">
<a-input-group compact>
<a-input style="width: 40%" />
<a-input style="width: 60%" />
</a-input-group>
<a-input-group>
<a-row :gutter="8">
<a-col :span="10">
<a-input />
</a-col>
<a-col :span="14">
<a-input />
</a-col>
</a-row>
</a-input-group>
<a-input-group>
<a-row type="flex">
<a-col flex="120px">
<a-select default-value="Zhejiang">
<a-select-option value="Zhejiang">Zhejiang</a-select-option>
<a-select-option value="Jiangsu">Jiangsu</a-select-option>
</a-select>
</a-col>
<a-col flex="auto">
<a-input default-value="Xihu District, Hangzhou" />
</a-col>
</a-row>
</a-input-group>
<a-input-group compact>
<a-select default-value="1">
<a-select-option value="1">Between</a-select-option>
<a-select-option value="2">Except</a-select-option>
</a-select>
<a-input placeholder="Minimum" style=" width: 100px; text-align: center" />
<a-input
disabled
placeholder="~"
style=" width: 30px; border-left: 0; pointer-events: none; backgroundColor: transparent"
/>
<a-input placeholder="Maximum" style=" width: 100px; text-align: center; border-left: 0" />
</a-input-group>
</a-form-model-item>
<a-form-model-item class="yo-form--vertical" label="上下布局">
<span slot="help">
上下布局请在
<a-tag color="pink">a-form-model-item</a-tag>上添加类
<a-tag color="pink">.yo-form--vertical</a-tag>
</span>
<a-input autocomplete="off" placeholder="请输入文字" />
</a-form-model-item>
</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,5 @@
/**
基本上所有列表页都可以通过拷贝此处的种子文件实现增删改查的功能
所有带 ... 的注释是可以依据当前业务添加内容的地方
其他所有尽量不要修改
**/

View File

@@ -0,0 +1,78 @@
<template>
<a-modal
:confirmLoading="confirmLoading"
:visible="visible"
@cancel="onCancel"
@ok="onOk"
class="yo-modal-form"
title="新增XX"
>
<FormBody ref="form-body" />
</a-modal>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
data() {
return {
visible: false,
confirmLoading: false,
};
},
computed: {
formBody() {
return this.$refs['form-body'];
},
},
methods: {
/**
* 必要的方法
* 从外部调用打开本窗口
*/
async onOpen() {
this.visible = true;
this.$nextTick(() => {
this.formBody.onInit();
});
},
/**
* 必要的方法
* 点击保存时的操作
*/
onOk() {
this.formBody.onGetData().then((data) => {
this.confirmLoading = true;
this.$api
/** !!此处必须修改调用的接口方法 */
.testAddApi(data)
.then(({ success }) => {
if (success) {
this.$message.success('新增成功');
this.onCancel();
this.$emit('ok');
}
})
.finally(() => {
this.confirmLoading = false;
});
});
},
/**
* 必要的方法
* 关闭窗口时的操作
*/
onCancel() {
this.formBody.onResetFields();
this.visible = false;
},
},
};
</script>

View File

@@ -0,0 +1,78 @@
<template>
<a-modal
:confirmLoading="confirmLoading"
:visible="visible"
@close="onCancel"
class="yo-modal-form"
title="编辑XX"
>
<FormBody ref="form-body" />
</a-modal>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
data() {
return {
visible: false,
confirmLoading: false,
};
},
computed: {
formBody() {
return this.$refs['form-body'];
},
},
methods: {
/**
* 必要的方法
* 从外部调用打开本窗口,并填充外部传入的数据
*/
onOpen(record) {
this.visible = true;
this.$nextTick(async () => {
await this.formBody.onInit();
this.formBody.onFillData(record);
});
},
/**
* 必要的方法
* 点击保存时的操作
*/
onOk() {
this.formBody.onGetData().then((data) => {
this.confirmLoading = true;
this.$api
/** !!此处必须修改调用的接口方法 */
.testEditApi(data)
.then(({ success }) => {
if (success) {
this.$message.success('编辑成功');
this.onCancel();
this.$emit('ok');
}
})
.finally(() => {
this.confirmLoading = false;
});
});
},
/**
* 必要的方法
* 关闭窗口时的操作
*/
onCancel() {
this.formBody.onResetFields();
this.visible = false;
},
},
};
</script>

View File

@@ -0,0 +1,100 @@
<template>
<a-form-model :model="form" :rules="rules" class="yo-form" ref="form">
<a-spin :spinning="loading">
<a-icon slot="indicator" spin type="loading" />
<div class="yo-form-group">
<!-- 表单控件 -->
</div>
</a-spin>
</a-form-model>
</template>
<script>
export default {
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/** ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(record) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...record,
/** 在此处添加默认数据转换 */
/** ... */
});
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
onGetData() {
return new Promise((reslove, reject) => {
this.$refs.form.validate((valid) => {
if (valid) {
const record = this.$_.cloneDeep(this.form);
/** 验证通过后可以对数据进行转换得到想要提交的格式 */
/** ... */
reslove(record);
} else {
reject();
}
});
});
},
/**
* 必要的方法
* 在外部窗口进行保存时调用表单验证
*/
onValidate(callback) {
this.$refs.form.validate(callback);
},
/**
* 必要的方法
* 在外部窗口关闭或重置时对表单验证进行初始化
*/
onResetFields() {
setTimeout(() => {
this.$refs.form.resetFields();
/** 在这里可以初始化当前组件中其他属性 */
/** ... */
}, 300);
},
/**
* 必要方法
* 加载当前表单中所需要的异步数据
*/
async onInit() {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/** ...BEGIN */
/** ...END */
this.loading = false;
},
/** 当前组件的其他方法 */
/** ... */
},
};
</script>

View File

@@ -0,0 +1,187 @@
<template>
<container>
<br />
<a-card :bordered="false">
<Auth auth="authCode:page">
<div class="yo-query-bar">
<a-form-model :model="query" layout="inline">
<!-- 此处添加查询表单控件 -->
<a-form-model-item>
<a-button-group>
<a-button @click="onQuery" type="primary">查询</a-button>
<a-button @click="onResetQuery">重置</a-button>
</a-button-group>
</a-form-model-item>
</a-form-model>
</div>
</Auth>
<yo-table :columns="columns" :load-data="loadData" ref="table">
<Auth auth="authCode:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增XX</a-button>
</Auth>
<!-- 格式化字段内容 -->
<!-- 添加操作控件 -->
<span slot="action" slot-scope="text, record">
<yo-table-actions>
<Auth auth="authCode:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="authCode:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
<!-- 可在此处添加其他操作控件 -->
</yo-table-actions>
</span>
</yo-table>
</a-card>
<br />
<add-form @ok="onReloadData" ref="add-form" />
<edit-form @ok="onReloadData" ref="edit-form" />
</container>
</template>
<script>
import AddForm from './addForm';
import EditForm from './editForm';
export default {
components: {
AddForm,
EditForm,
},
data() {
return {
query: {},
columns: [],
codes: {
code1: [],
code2: [],
},
};
},
created() {
this.onLoadCodes();
/** 根据权限添加操作列 */
const flag = this.$auth(/** ... */);
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return (
this.$api
/** !!此处必须修改调用的接口方法 */
.testGetApi({
...params,
...this.query,
})
.then((res) => {
return res.data;
})
);
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 有查询功能时的必要方法
* 重置查询条件
*/
onResetQuery() {
/** 在这里重置查询条件时,可对特殊的字段做保留处理 */
this.query = {};
this.onQuery();
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 必要方法
* 加载字典数据
* 如果不需要获取相应的字典数据,此方法内容可空
*/
onLoadCodes() {
this.$api
.$queue([
this.$api.sysDictTypeDropDownWait({ code: 'code1' }),
this.$api.sysDictTypeDropDownWait({ code: 'code2' }),
])
.then(([code1, code2]) => {
this.codes.code1 = code1.data;
this.codes.code2 = code2.data;
});
},
/**
* 必要方法
* 绑定数据字典值
*/
bindCodeValue(code, name) {
const c = this.codes[name].find((p) => p.code == code);
if (c) {
return c.value;
}
return null;
},
/**
* 必要方法
* 从列表页调用窗口的打开方法
*/
onOpen(formName, record) {
this.$refs[formName].onOpen(record);
},
/**
* 必要方法
* 可以用做一系列操作的公共回调,此方法中会重新加载当前列表
*/
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
this.$refs.table.onLoaded();
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api
/** !!此处必须修改调用的接口方法 */
.testDeleteApi(record)
.then(({ success }) => {
this.onResult(success, '删除成功');
});
},
},
};
</script>

View File

@@ -0,0 +1,71 @@
<template>
<a-modal
:confirmLoading="confirmLoading"
:visible="visible"
@cancel="onCancel"
@ok="onOk"
class="yo-modal-form"
title="新增应用"
>
<FormBody ref="form-body" />
</a-modal>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
data() {
return {
visible: false,
confirmLoading: false,
};
},
methods: {
/**
* 必要的方法
* 从外部调用打开本窗口
*/
onOpen() {
this.visible = true;
},
/**
* 必要的方法
* 点击保存时的操作
*/
onOk() {
this.$refs['form-body'].onValidate((valid) => {
if (valid) {
this.confirmLoading = true;
this.$api
.sysAppAdd(this.$refs['form-body'].form)
.then(({ success }) => {
if (success) {
this.$message.success('新增成功');
this.onCancel();
this.$emit('ok');
}
})
.finally(() => {
this.confirmLoading = false;
});
}
});
},
/**
* 必要的方法
* 关闭窗口时的操作
*/
onCancel() {
this.$refs['form-body'].onResetFields();
this.visible = false;
},
},
};
</script>

View File

@@ -0,0 +1,74 @@
<template>
<a-modal
:confirmLoading="confirmLoading"
:visible="visible"
@cancel="onCancel"
@ok="onOk"
class="yo-modal-form"
title="编辑应用"
>
<FormBody ref="form-body" />
</a-modal>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
data() {
return {
visible: false,
confirmLoading: false,
};
},
methods: {
/**
* 必要的方法
* 从外部调用打开本窗口,并填充外部传入的数据
*/
onOpen(record) {
this.visible = true;
this.$nextTick(() => {
this.$refs['form-body'].onFillData(record);
});
},
/**
* 必要的方法
* 点击保存时的操作
*/
onOk() {
this.$refs['form-body'].onValidate((valid) => {
if (valid) {
this.confirmLoading = true;
this.$api
.sysAppEdit(this.$refs['form-body'].form)
.then(({ success }) => {
if (success) {
this.$message.success('编辑成功');
this.onCancel();
this.$emit('ok');
}
})
.finally(() => {
this.confirmLoading = false;
});
}
});
},
/**
* 必要的方法
* 关闭窗口时的操作
*/
onCancel() {
this.$refs['form-body'].onResetFields();
this.visible = false;
},
},
};
</script>

View File

@@ -0,0 +1,63 @@
<template>
<a-form-model :model="form" :rules="rules" class="yo-form" ref="form">
<div class="yo-form-group">
<a-form-model-item label="应用名称" prop="name">
<a-input placeholder="请输入应用名称" v-model="form.name" />
</a-form-model-item>
<a-form-model-item label="唯一编码" prop="code">
<a-input placeholder="请输入唯一编码" v-model="form.code" />
</a-form-model-item>
<a-form-model-item label="排序" prop="sort">
<a-input-number
:max="1000"
:min="0"
class="w-100-p"
placeholder="请输入排序"
v-model="form.sort"
/>
</a-form-model-item>
</div>
</a-form-model>
</template>
<script>
export default {
data() {
return {
form: {
active: 'N',
},
rules: {
name: [{ required: true, message: '请输入应用名称' }],
code: [{ required: true, message: '请输入唯一编码' }],
},
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(record) {
this.form = this.$_.cloneDeep(record);
},
/**
* 必要的方法
* 在外部窗口进行保存时调用表单验证
*/
onValidate(callback) {
this.$refs.form.validate(callback);
},
/**
* 必要的方法
* 在外部窗口关闭或重置时对表单验证进行初始化
*/
onResetFields() {
setTimeout(() => {
this.$refs.form.resetFields();
}, 300);
},
},
};
</script>

View File

@@ -0,0 +1,216 @@
<template>
<container>
<br />
<a-card :bordered="false">
<Auth auth="sysApp:page">
<div class="yo-query-bar">
<a-form-model :model="query" layout="inline">
<a-form-model-item label="应用名称">
<a-input placeholder="请输入应用名称" v-model="query.name" />
</a-form-model-item>
<a-form-model-item label="唯一编码">
<a-input placeholder="请输入唯一编码" v-model="query.code" />
</a-form-model-item>
<a-form-model-item>
<a-button-group>
<a-button @click="onQuery" type="primary">查询</a-button>
<a-button @click="() => { query = {}, onQuery() }">重置</a-button>
</a-button-group>
</a-form-model-item>
</a-form-model>
</div>
</Auth>
<yo-table :columns="columns" :load-data="loadData" ref="table">
<Auth auth="sysApp:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增应用</a-button>
</Auth>
<span slot="active" slot-scope="text, record">
{{ bindCodeValue(text, 'yes_or_no') }}
<Auth auth="sysApp:setAsDefault" v-if="record.active == 'N'">
<yo-table-actions>
<span></span>
<a-popconfirm
@confirm="onSetDefault(record)"
placement="topRight"
title="是否确认设置为默认应用"
>
<a>设为默认</a>
</a-popconfirm>
</yo-table-actions>
</Auth>
</span>
<span slot="status" slot-scope="text">{{ bindCodeValue(text, 'common_status') }}</span>
<span slot="action" slot-scope="text, record">
<yo-table-actions>
<Auth auth="sysApp:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysApp:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
</yo-table-actions>
</span>
</yo-table>
</a-card>
<br />
<add-form @ok="onReloadData" ref="add-form" />
<edit-form @ok="onReloadData" ref="edit-form" />
</container>
</template>
<script>
import AddForm from './addForm';
import EditForm from './editForm';
export default {
components: {
AddForm,
EditForm,
},
data() {
return {
query: {},
columns: [
{
title: '应用名称',
dataIndex: 'name',
},
{
title: '唯一编码',
dataIndex: 'code',
},
{
title: '是否默认',
dataIndex: 'active',
scopedSlots: {
customRender: 'active',
},
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: {
customRender: 'status',
},
},
{
title: '排序',
dataIndex: 'sort',
},
],
codes: [
{
code: 'yes_or_no',
values: [],
},
{
code: 'common_status',
values: [],
},
],
};
},
created() {
this.onLoadCodes();
const flag = this.$auth({
sysApp: [['edit'], ['delete']],
});
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return this.$api
.getAppPage({
...params,
...this.query,
})
.then((res) => {
return res.data;
});
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 加载字典数据时的必要方法
*/
onLoadCodes() {
this.$api
.$queue([
this.$api.sysDictTypeDropDownWait({ code: 'yes_or_no' }),
this.$api.sysDictTypeDropDownWait({ code: 'common_status' }),
])
.then(([yesOrNo, commonStatus]) => {
this.codes.find((p) => p.code === 'yes_or_no').values = yesOrNo.data;
this.codes.find((p) => p.code === 'common_status').values = commonStatus.data;
});
},
bindCodeValue(code, name) {
const c = this.codes.find((p) => p.code == name).values.find((p) => p.code == code);
if (c) {
return c.value;
}
return null;
},
/**
* 有编辑新增功能的必要方法
* 从列表页调用窗口的打开方法
*/
onOpen(formName, record) {
this.$refs[formName].onOpen(record);
},
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
this.$refs.table.onLoaded();
},
onSetDefault(record) {
this.$refs.table.onLoading();
this.$api.sysAppSetAsDefault({ id: record.id }).then(({ success }) => {
this.onResult(success, '设置成功');
});
},
onDelete(record) {
this.$refs.table.onLoading();
this.$api.sysAppDelete(record).then(({ success }) => {
this.onResult(success, '删除成功');
});
},
},
};
</script>

View File

@@ -0,0 +1,78 @@
<template>
<a-modal
:confirmLoading="confirmLoading"
:visible="visible"
@cancel="onCancel"
@ok="onOk"
class="yo-modal-form"
title="新增参数"
>
<FormBody ref="form-body" />
</a-modal>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
data() {
return {
visible: false,
confirmLoading: false,
};
},
computed: {
formBody() {
return this.$refs['form-body'];
},
},
methods: {
/**
* 必要的方法
* 从外部调用打开本窗口
*/
onOpen() {
this.visible = true;
this.$nextTick(async () => {
await this.formBody.onInit();
});
},
/**
* 必要的方法
* 点击保存时的操作
*/
onOk() {
this.$refs['form-body'].onValidate((valid) => {
if (valid) {
this.confirmLoading = true;
this.$api
.sysConfigAdd(this.$refs['form-body'].form)
.then(({ success }) => {
this.confirmLoading = false;
if (success) {
this.$message.success('添加成功');
this.onCancel();
this.$emit('ok');
}
})
.finally(() => {
this.confirmLoading = false;
});
}
});
},
/**
* 必要的方法
* 关闭窗口时的操作
*/
onCancel() {
this.$refs['form-body'].onResetFields();
this.visible = false;
},
},
};
</script>

View File

@@ -0,0 +1,74 @@
<template>
<a-modal
:confirmLoading="confirmLoading"
:visible="visible"
@cancel="onCancel"
@ok="onOk"
title="编辑用户"
>
<FormBody ref="form-body" />
</a-modal>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
data() {
return {
visible: false,
confirmLoading: false,
};
},
methods: {
/**
* 必要的方法
* 从外部调用打开本窗口,并填充外部传入的数据
*/
onOpen(record) {
this.visible = true;
this.$nextTick(async () => {
this.$refs['form-body'].onFillData(record);
});
},
/**
* 必要的方法
* 点击保存时的操作
*/
onOk() {
this.$refs['form-body'].onValidate((valid) => {
if (valid) {
this.confirmLoading = true;
this.$api
.sysConfigEdit(this.$refs['form-body'].form)
.then(({ success }) => {
if (success) {
this.$message.success('编辑成功');
this.onCancel();
this.$emit('ok');
}
})
.finally(() => {
this.confirmLoading = false;
});
}
});
},
/**
* 必要的方法
* 关闭窗口时的操作
*/
onCancel() {
this.$refs['form-body'].onResetFields();
this.visible = false;
},
},
};
</script>

View File

@@ -0,0 +1,114 @@
<template>
<a-form-model :model="form" :rules="rules" class="yo-form" ref="form">
<div class="yo-form-group">
<a-form-model-item label="角色名" prop="name">
<a-input placeholder="请输入角色名" v-model="form.name" />
</a-form-model-item>
<a-form-model-item label="唯一编码" prop="code" :disabled="editDisabled">
<a-input placeholder="请输入唯一编码" v-model="form.code" />
</a-form-model-item>
<a-form-model-item label="系统参数" prop="sysFlag">
<a-radio-group v-model="form.sysFlag" :disabled="editDisabled">
<a-radio-button value="Y">
<a-icon :style="{ color: '#1890ff' }" />
</a-radio-button>
<a-radio-button value="N">
<a-icon :style="{ color: '#eb2f96' }" />
</a-radio-button>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="所属分类" prop="groupCode">
<a-select placeholder="请选择所属分类" v-model="form.groupCode" :disabled="editDisabled">
<a-select-option
:key="i"
:value="item.code"
v-for="(item, i) in groupCode"
>{{ item.value }}</a-select-option>
</a-select>
</a-select>
</a-form-model-item>
<a-form-model-item label="参数值" prop="name">
<a-input placeholder="请输入参数值 " v-model="form.value" />
</a-form-model-item>
<a-form-model-item label="备注" prop="remark">
<a-input placeholder="请输入备注" v-model="form.remark" />
</a-form-model-item>
</div>
</a-form-model>
</template>
<script>
import moment from 'moment';
export default {
data() {
return {
editDisabled:false,
groupCode:[],
form: {
active: "N",
},
rules: {
name: [{ required: true, message: "请输入应用名称" }],
code: [{ required: true, message: "请输入唯一编码" }],
sysFlag: [{ required: true, message: "请选择参数类型" }],
groupCode: [{ required: true, message: "请选择所属分类" }],
value: [{ required: true, message: "请输入参数值" }],
},
};
},
methods: {
moment,
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(record) {
this.form = this.$_.cloneDeep(record);
if(record.sysFlag == 'Y')
{
this.editDisabled = true
}else
{
this.editDisabled = false
}
},
async onInit() {
this.loading = true;
this.groupCode = await this.onLoadgroupCodeData();
this.loading = false;
},
/**
* 必要的方法
* 在外部窗口进行保存时调用表单验证
*/
onValidate(callback) {
this.$refs.form.validate(callback);
},
/**
*
* 获取所属分类字典表的内容
*/
onLoadgroupCodeData() {
return this.$api.sysDictTypeDropDown({ code: 'consts_type' }).then(({ data }) => {
return data;
});
},
/**
* 必要的方法
* 在外部窗口关闭或重置时对表单验证进行初始化
*/
onResetFields() {
setTimeout(() => {
this.form = {};
this.$refs.form.resetFields();
}, 300);
},
},
};
</script>

View File

@@ -0,0 +1,146 @@
<template>
<container>
<br />
<a-card :bordered="false">
<Auth auth="sysConfig:page">
<div class="yo-query-bar">
<a-form-model :model="query" layout="inline">
<a-form-model-item label="参数名称">
<a-input placeholder="请输入参数名称" v-model="query.name" />
</a-form-model-item>
<a-form-model-item label="唯一编码">
<a-input placeholder="请输入唯一编码" v-model="query.code" />
</a-form-model-item>
<a-form-model-item>
<a-button-group>
<a-button @click="onQuery" type="primary">查询</a-button>
<a-button @click="() => {(query = {}), onQuery();}">重置</a-button>
</a-button-group>
</a-form-model-item>
</a-form-model>
</div>
</Auth>
<yo-table :columns="columns" :load-data="loadData" ref="table">
<Auth auth="sysConfig:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增职位</a-button>
</Auth>
<span slot="action" slot-scope="text, record">
<yo-table-actions>
<Auth auth="sysConfig:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysConfig:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
</yo-table-actions>
</span>
</yo-table>
</a-card>
<br />
<edit-form @ok="onReloadData" ref="edit-form" />
<add-form @ok="onReloadData" ref="add-form" />
</container>
</template>
<script>
import AddForm from './addForm';
import editForm from './editForm';
export default {
components: {
AddForm,
editForm,
},
data() {
return {
query: {},
columns: [
{
title: '参数名称',
dataIndex: 'name',
},
{
title: '唯一编码',
dataIndex: 'code',
},
{
title: '参数值',
dataIndex: 'value',
},
{
title: '所属分类',
dataIndex: 'groupCode',
},
{
title: '备注',
dataIndex: 'remark',
},
{
title: '操作',
width: '200px',
dataIndex: 'action',
scopedSlots: {
customRender: 'action',
},
},
],
};
},
created() {},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return this.$api
.sysConfigPage({
...params,
...this.query,
})
.then((res) => {
return res.data;
});
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 有编辑新增功能的必要方法
* 从列表页调用窗口的打开方法
*/
onOpen(formName, record) {
this.$refs[formName].onOpen(record);
},
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
this.$refs.table.onLoaded();
},
onDelete(record) {
this.$refs.table.onLoading();
this.$api.sysConfigDelete(record).then(({ success, message }) => {
this.onResult(success, '删除成功');
});
},
},
};
</script>

Some files were not shown because too many files have changed in this diff Show More