update .gitignore

This commit is contained in:
2021-05-11 20:11:16 +08:00
parent df4159752e
commit 76a6f6ddc1
109 changed files with 34880 additions and 28299 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=localhost;Port=3306;Database=ewide;User ID=root;Password=root;pooling=true;sslmode=none;CharSet=utf8;"
}
}

View File

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

View File

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

View File

@@ -1,33 +1,33 @@
@import './extend.less';
@import './lib/visibility.less';
@import './lib/container.less';
@import './lib/align.less';
@import './lib/font-size.less';
@import './lib/text-color.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/button.less';
@import './lib/card.less';
@import './lib/table.less';
@import './lib/list.less';
@import './lib/form.less';
@import './lib/description.less';
@import './lib/select.less';
@import './lib/cascader.less';
@import './lib/dropdown.less';
@import './lib/modal.less';
@import './lib/tree-layout.less';
@import './lib/authority-view.less';
@import './lib/icon-selector.less';
@import './theme/primary.less';
// @import './lib/font-weight.less';
@import './extend.less';
@import './lib/visibility.less';
@import './lib/container.less';
@import './lib/align.less';
@import './lib/font-size.less';
@import './lib/text-color.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/button.less';
@import './lib/card.less';
@import './lib/table.less';
@import './lib/list.less';
@import './lib/form.less';
@import './lib/description.less';
@import './lib/select.less';
@import './lib/cascader.less';
@import './lib/dropdown.less';
@import './lib/modal.less';
@import './lib/tree-layout.less';
@import './lib/authority-view.less';
@import './lib/icon-selector.less';
@import './theme/primary.less';
// @import './lib/font-weight.less';

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,24 +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;
}
}
}
}
@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

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

View File

@@ -1,39 +1,39 @@
@import (reference) '~@/assets/style/extend.less';
@container-width: 1400px;
.container-base {
margin: 0 auto;
padding: 0 @padding-md;
}
.container {
width: @container-width;
.container-base();
}
@media (max-width: 1400px) {
.container {
width: auto;
}
}
.container-md {
width: @container-width - 200px;
.container-base();
}
.container-sm {
width: @container-width - 400px;
.container-base();
}
.container-xs {
width: @container-width - 600px;
.container-base();
}
.container-xxs {
width: @container-width - 700px;
.container-base();
}
.container-fluid {
.container-base();
}
@import (reference) '~@/assets/style/extend.less';
@container-width: 1400px;
.container-base {
margin: 0 auto;
padding: 0 @padding-md;
}
.container {
width: @container-width;
.container-base();
}
@media (max-width: 1400px) {
.container {
width: auto;
}
}
.container-md {
width: @container-width - 200px;
.container-base();
}
.container-sm {
width: @container-width - 400px;
.container-base();
}
.container-xs {
width: @container-width - 600px;
.container-base();
}
.container-xxs {
width: @container-width - 700px;
.container-base();
}
.container-fluid {
.container-base();
}

View File

@@ -1,6 +1,6 @@
@import (reference) '~@/assets/style/extend.less';
.ant-dropdown-trigger {
.anticon-down {
transform: scaleY(.75);
}
}
@import (reference) '~@/assets/style/extend.less';
.ant-dropdown-trigger {
.anticon-down {
transform: scaleY(.75);
}
}

View File

@@ -1,25 +1,25 @@
@import (reference) '~@/assets/style/extend.less';
h1,
.h1 {
font-size: 36px;
}
h2,
.h2 {
font-size: 32px;
}
h3,
.h3 {
font-size: 24px;
}
h4,
.h4 {
font-size: 18px;
}
h5,
.h5 {
font-size: 14px;
}
h6,
.h6 {
font-size: 12px;
}
@import (reference) '~@/assets/style/extend.less';
h1,
.h1 {
font-size: 36px;
}
h2,
.h2 {
font-size: 32px;
}
h3,
.h3 {
font-size: 24px;
}
h4,
.h4 {
font-size: 18px;
}
h5,
.h5 {
font-size: 14px;
}
h6,
.h6 {
font-size: 12px;
}

View File

@@ -1,24 +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;
}
}
}
@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

@@ -1,284 +1,284 @@
@import (reference) '~@/assets/style/extend.less';
.yo-form {
&--fixed {
width: 660px;
margin: 0 auto;
}
.h1,
.h2,
.h3,
.h4,
.h5,
.h6 {
color: darken(@white, 40%);
}
.h3 {
font-size: 16px;
}
.h4 {
font-size: 15px;
}
.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%);
@control-background: lighten(@black, 95%) !important;
&::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,
.ant-cascader-picker,
.ant-cascader-input.ant-input {
z-index: 1;
text-align: left;
color: lighten(@black, 10%);
border: 0;
background-color: @control-background;
}
.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-cascader-picker:focus {
.ant-cascader-input {
.focus();
}
}
.ant-input-group {
.ant-row-flex {
.ant-select {
width: 100%;
}
}
.ant-input-group-addon {
z-index: 0;
}
}
.ant-cascader-picker-clear {
background-color: @control-background;
}
}
.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;
}
.yo-form--fluid {
.ant-form-item-control-wrapper {
flex: 0 0 100%;
width: 100%;
}
}
.yo-form--short {
.ant-form-item-control-wrapper {
flex: 0 0 38.2%;
width: 38.2%;
}
}
.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-form-link {
display: flex;
align-items: center;
margin-bottom: -1px;
padding: @padding-md;
cursor: pointer;
border: @border-width-base @border-style-base @border-color-split;
background-color: @white;
&:hover {
background-color: darken(@white, 1%);
}
&:active {
background-color: darken(@white, 3%);
}
&--title {
font-size: @font-size-base + 1px;
flex: 1;
}
&--content {
flex: 1;
text-align: right;
color: fade(@black, 35%);
}
&--right-icon {
margin-left: @padding-xs;
color: fade(@black, 50%);
}
}
}
.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-wrapper-body {
display: flex;
flex-direction: column;
}
.ant-drawer-header {
flex: 0 0 auto;
}
.ant-drawer-body {
position: relative;
flex: 1 1 100%;
padding: 0;
}
.yo-drawer-form--body {
position: absolute;
top: 0;
bottom: @border-width-base + 20px + @padding-md * 2;
overflow: auto;
width: 100%;
padding: @padding-lg;
}
.ant-drawer-footer {
position: absolute;
left: 0;
bottom: 0;
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;
}
}
}
@import (reference) '~@/assets/style/extend.less';
.yo-form {
&--fixed {
width: 660px;
margin: 0 auto;
}
.h1,
.h2,
.h3,
.h4,
.h5,
.h6 {
color: darken(@white, 40%);
}
.h3 {
font-size: 16px;
}
.h4 {
font-size: 15px;
}
.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%);
@control-background: lighten(@black, 95%) !important;
&::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,
.ant-cascader-picker,
.ant-cascader-input.ant-input {
z-index: 1;
text-align: left;
color: lighten(@black, 10%);
border: 0;
background-color: @control-background;
}
.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-cascader-picker:focus {
.ant-cascader-input {
.focus();
}
}
.ant-input-group {
.ant-row-flex {
.ant-select {
width: 100%;
}
}
.ant-input-group-addon {
z-index: 0;
}
}
.ant-cascader-picker-clear {
background-color: @control-background;
}
}
.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;
}
.yo-form--fluid {
.ant-form-item-control-wrapper {
flex: 0 0 100%;
width: 100%;
}
}
.yo-form--short {
.ant-form-item-control-wrapper {
flex: 0 0 38.2%;
width: 38.2%;
}
}
.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-form-link {
display: flex;
align-items: center;
margin-bottom: -1px;
padding: @padding-md;
cursor: pointer;
border: @border-width-base @border-style-base @border-color-split;
background-color: @white;
&:hover {
background-color: darken(@white, 1%);
}
&:active {
background-color: darken(@white, 3%);
}
&--title {
font-size: @font-size-base + 1px;
flex: 1;
}
&--content {
flex: 1;
text-align: right;
color: fade(@black, 35%);
}
&--right-icon {
margin-left: @padding-xs;
color: fade(@black, 50%);
}
}
}
.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-wrapper-body {
display: flex;
flex-direction: column;
}
.ant-drawer-header {
flex: 0 0 auto;
}
.ant-drawer-body {
position: relative;
flex: 1 1 100%;
padding: 0;
}
.yo-drawer-form--body {
position: absolute;
top: 0;
bottom: @border-width-base + 20px + @padding-md * 2;
overflow: auto;
width: 100%;
padding: @padding-lg;
}
.ant-drawer-footer {
position: absolute;
left: 0;
bottom: 0;
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

@@ -1,28 +1,28 @@
@import (reference) '~@/assets/style/extend.less';
.ant-list-bordered {
border-color: @border-color-split;
background-color: @white;
}
.yo-list {
&-content--h {
display: flex;
align-items: center;
&--item {
min-width: 120px;
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%);
}
}
}
}
@import (reference) '~@/assets/style/extend.less';
.ant-list-bordered {
border-color: @border-color-split;
background-color: @white;
}
.yo-list {
&-content--h {
display: flex;
align-items: center;
&--item {
min-width: 120px;
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

@@ -1,68 +1,68 @@
@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}-xl {
margin@{position}: @padding-xl !important;
}
.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;
}
.m@{name}-xxs {
margin@{position}: @padding-xxs !important;
}
.p@{name}-xl {
padding@{position}: @padding-xl !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;
}
.p@{name}-xxs {
padding@{position}: @padding-xxs !important;
}
.m@{name}-none {
margin@{position}: 0 !important;
}
.p@{name}-none {
padding@{position}: 0 !important;
}
.margin-padding(@i + 1);
}
@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}-xl {
margin@{position}: @padding-xl !important;
}
.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;
}
.m@{name}-xxs {
margin@{position}: @padding-xxs !important;
}
.p@{name}-xl {
padding@{position}: @padding-xl !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;
}
.p@{name}-xxs {
padding@{position}: @padding-xxs !important;
}
.m@{name}-none {
margin@{position}: 0 !important;
}
.p@{name}-none {
padding@{position}: 0 !important;
}
.margin-padding(@i + 1);
}
.margin-padding(1);

View File

@@ -1,14 +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;
}
@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

@@ -1,6 +1,6 @@
@import (reference) '~@/assets/style/extend.less';
.ant-select-arrow {
.anticon-down {
transform: scaleY(.75);
}
}
@import (reference) '~@/assets/style/extend.less';
.ant-select-arrow {
.anticon-down {
transform: scaleY(.75);
}
}

View File

@@ -1,162 +1,162 @@
@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;
}
.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;
}
}
.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);
}
@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;
}
.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;
}
}
.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

@@ -1,69 +1,69 @@
@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;
left: 0;
bottom: 0;
width: 100%;
.swiper-slide {
height: auto;
min-height: 100%;
}
.swiper-scrollbar {
transition: @animation-duration-slow opacity;
opacity: 0;
}
&:hover {
.swiper-scrollbar {
opacity: 1;
}
}
}
}
&--bar {
line-height: 20px;
height: 20px;
padding: 0 @padding-md;
text-align: right;
>.anticon {
cursor: pointer;
color: fade(@black, 50%);
&:hover {
color: fade(@black, 80%);
}
}
}
}
@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;
left: 0;
bottom: 0;
width: 100%;
.swiper-slide {
height: auto;
min-height: 100%;
}
.swiper-scrollbar {
transition: @animation-duration-slow opacity;
opacity: 0;
}
&:hover {
.swiper-scrollbar {
opacity: 1;
}
}
}
}
&--bar {
line-height: 20px;
height: 20px;
padding: 0 @padding-md;
text-align: right;
>.anticon {
cursor: pointer;
color: fade(@black, 50%);
&:hover {
color: fade(@black, 80%);
}
}
}
}

View File

@@ -1,22 +1,22 @@
@import (reference) '~@/assets/style/extend.less';
.hide {
visibility: hidden !important;
}
.hidden {
display: none !important;
}
.block {
display: block;
}
.inline-block {
display: inline-block;
}
.inline {
display: inline;
}
.inline-flex {
display: inline-flex;
}
.flex {
display: flex;
}
@import (reference) '~@/assets/style/extend.less';
.hide {
visibility: hidden !important;
}
.hidden {
display: none !important;
}
.block {
display: block;
}
.inline-block {
display: inline-block;
}
.inline {
display: inline;
}
.inline-flex {
display: inline-flex;
}
.flex {
display: flex;
}

View File

@@ -1,25 +1,25 @@
@import (reference) '~@/assets/style/extend.less';
.w-100-p {
width: 100%;
}
.w-100 {
width: 100px;
}
.w-200 {
width: 200px;
}
.w-300 {
width: 300px;
}
.h-100-p {
height: 100%;
}
.h-100 {
height: 100px;
}
.h-200 {
height: 200px;
}
.h-300 {
height: 300px;
}
@import (reference) '~@/assets/style/extend.less';
.w-100-p {
width: 100%;
}
.w-100 {
width: 100px;
}
.w-200 {
width: 200px;
}
.w-300 {
width: 300px;
}
.h-100-p {
height: 100%;
}
.h-100 {
height: 100px;
}
.h-200 {
height: 200px;
}
.h-300 {
height: 300px;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
@import '~@/assets/style/app.less';
@primary-color: #007bff;
@font-size-base: 13px;
@border-radius-base: 0;
@import '~@/assets/style/app.less';
@primary-color: #007bff;
@font-size-base: 13px;
@border-radius-base: 0;

View File

@@ -1,169 +1,169 @@
/**
* api
* v1.2
*/
import axios from 'axios'
import { token } from '@/common/token'
import status from './status'
const STATUS = status
import app from '@/main'
axios.defaults.baseURL = '/api'
/**
* 最终直接根据url名称调用接口方法
* 例如
* import { api } from '@/api'
* api.getItemGroupType(parmas).then(...)
*/
import urls from './requests'
import { settings } from 'nprogress'
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, STATUS.Forbidden]
const errorNotification = ({ code, message }) => {
switch (message.constructor) {
case Array:
message.map(p => {
setTimeout(() => {
app.$notification.error({
duration: 30,
message: p.field,
description: p.messages.join('/'),
})
})
})
break
default:
app.$notification.error({
duration: 30,
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}Await`] = 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}Await`](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 }) => {
if (process.env.VUE_APP_NODE_ENV === 'development') {
const { data, status } = response
if (data.constructor === String) {
errorNotification({
message: data,
code: status
})
} else {
errorNotification(data)
}
if (data.code === STATUS.Unauthorized) {
handlerUnauthorized()
}
reject(data)
} 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,
urls,
api,
STATUS
}
/**
* api
* v1.2
*/
import axios from 'axios'
import { token } from '@/common/token'
import status from './status'
const STATUS = status
import app from '@/main'
axios.defaults.baseURL = '/api'
/**
* 最终直接根据url名称调用接口方法
* 例如
* import { api } from '@/api'
* api.getItemGroupType(parmas).then(...)
*/
import urls from './requests'
import { settings } from 'nprogress'
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, STATUS.Forbidden]
const errorNotification = ({ code, message }) => {
switch (message.constructor) {
case Array:
message.map(p => {
setTimeout(() => {
app.$notification.error({
duration: 30,
message: p.field,
description: p.messages.join('/'),
})
})
})
break
default:
app.$notification.error({
duration: 30,
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}Await`] = 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}Await`](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 }) => {
if (process.env.VUE_APP_NODE_ENV === 'development') {
const { data, status } = response
if (data.constructor === String) {
errorNotification({
message: data,
code: status
})
} else {
errorNotification(data)
}
if (data.code === STATUS.Unauthorized) {
handlerUnauthorized()
}
reject(data)
} 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,
urls,
api,
STATUS
}

View File

@@ -1,20 +1,20 @@
/**
* 接口的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 sys from './sys'
import business from './business'
export default {
...sys,
...business
/**
* 接口的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 sys from './sys'
import business from './business'
export default {
...sys,
...business
}

View File

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

@@ -1,26 +1,26 @@
export default {
/**
* 分页查询配置列表
*/
sysConfigPage: ['/sysConfig/page', 'post'],
/**
* 添加系统参数配置
*/
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'],
export default {
/**
* 分页查询配置列表
*/
sysConfigPage: ['/sysConfig/page', 'post'],
/**
* 添加系统参数配置
*/
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

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

View File

@@ -1,26 +1,26 @@
export default {
/**
* 分页查询系统字典类型
*/
sysDictTypePage: ['/sysDictType/page', 'post'],
/**
* 添加系统字典类型
*/
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'],
export default {
/**
* 分页查询系统字典类型
*/
sysDictTypePage: ['/sysDictType/page', 'post'],
/**
* 添加系统字典类型
*/
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

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

View File

@@ -1,36 +1,36 @@
export default {
/**
* 分页查询文件信息表
*/
sysFileInfoPage: ['/sysFileInfo/page', 'post'],
/**
* 获取全部文件信息表
*/
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'],
export default {
/**
* 分页查询文件信息表
*/
sysFileInfoPage: ['/sysFileInfo/page', 'post'],
/**
* 获取全部文件信息表
*/
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

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

View File

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

View File

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

View File

@@ -1,66 +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'],
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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,62 +1,62 @@
export default {
/**
* 获取角色列表
*/
getRolePage: ['/sysRole/page', 'post'],
/**
* 增加角色
*/
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'],
export default {
/**
* 获取角色列表
*/
getRolePage: ['/sysRole/page', 'post'],
/**
* 增加角色
*/
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

@@ -1,17 +1,17 @@
export default {
/**
* 发送记录查询
*/
smsPage: ['/sms/page', 'get'],
/**
* 验证短信验证码
*/
sysSendLoginMessage: ['/sms/sendLoginMessage', 'post'],
/**
* 验证短信验证码
*/
sysValidateMessage: ['/sms/validateMessage', 'post'],
export default {
/**
* 发送记录查询
*/
smsPage: ['/sms/page', 'get'],
/**
* 验证短信验证码
*/
sysSendLoginMessage: ['/sms/sendLoginMessage', 'post'],
/**
* 验证短信验证码
*/
sysValidateMessage: ['/sms/validateMessage', 'post'],
}

View File

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

View File

@@ -1,56 +1,56 @@
export default {
/**
* 分页查询定时任务
*/
sysTimersPage: ['/sysTimers/page', 'post'],
/**
* 获取全部定时任务
*/
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'],
export default {
/**
* 分页查询定时任务
*/
sysTimersPage: ['/sysTimers/page', 'post'],
/**
* 获取全部定时任务
*/
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

@@ -1,76 +1,76 @@
export default {
/**
* 获取用户列表
*/
getUserPage: ['/sysUser/page', 'post'],
/**
* 增加用户
*/
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'],
export default {
/**
* 获取用户列表
*/
getUserPage: ['/sysUser/page', 'post'],
/**
* 增加用户
*/
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

@@ -1,206 +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
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

@@ -1,61 +1,61 @@
import { api } from '@/common/api'
import { token } from '@/common/token'
import { encryptByDES, decryptByDES } from '@/util/des'
import { removeGlobal } from '@/util/global'
import app from '@/main'
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])
}
reject()
})
})
}
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()
}
})
})
}
export {
doLogin,
doLogout,
import { api } from '@/common/api'
import { token } from '@/common/token'
import { encryptByDES, decryptByDES } from '@/util/des'
import { removeGlobal } from '@/util/global'
import app from '@/main'
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])
}
reject()
})
})
}
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()
}
})
})
}
export {
doLogin,
doLogout,
}

View File

@@ -1,9 +1,9 @@
const SESSION_KEY = '__SESSION'
const SETTING_KEY = '__SETTINGS'
const GLOBAL_INFO_KEY = '__GLOBAL_INFO'
export {
SESSION_KEY,
SETTING_KEY,
GLOBAL_INFO_KEY,
const SESSION_KEY = '__SESSION'
const SETTING_KEY = '__SETTINGS'
const GLOBAL_INFO_KEY = '__GLOBAL_INFO'
export {
SESSION_KEY,
SETTING_KEY,
GLOBAL_INFO_KEY,
}

View File

@@ -1,19 +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
}
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

@@ -1,192 +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 && scopedSlots.default()
}
return false
},
/**
* 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 && scopedSlots.default()
}
return false
},
}

View File

@@ -1,14 +1,14 @@
<template>
<section :class="mode || $root.global.settings.container || 'container-fluid'">
<slot />
</section>
</template>
<script>
export default {
props: {
mode: {
type: String,
},
},
};
<template>
<section :class="mode || $root.global.settings.container || 'container-fluid'">
<slot />
</section>
</template>
<script>
export default {
props: {
mode: {
type: String,
},
},
};
</script>

View File

@@ -1,254 +1,254 @@
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">
{data.visibleParent && data.type == 2 &&
<a-tooltip placement="top" title="选中此项才会显示父节点">
<a-icon type="eye" style={{ color: '#1890ff' }} class="mr-xxs" />
</a-tooltip>
}
<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)
const visible = this.getVisible()
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),
// 返回所有选中和半选,但是不返回没有子级选中visibleParent的半选
visible
)
},
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)
})
},
getVisible() {
const checked = this.list.filter(p => p.checked)
const caseChildren = checked.filter(p => p.visibleParent || p.type != 2)
const visibleParents = []
// 递归寻找父级
const findVisibleParents = (children) => {
const parents = []
children.forEach(item => {
if (item.parentId) {
const parent = this.list.find(p => p.id === item.parentId)
if (parent) {
parents.push(parent)
visibleParents.push(parent)
}
}
})
if (parents.length) {
findVisibleParents(parents)
}
}
findVisibleParents(caseChildren)
const checkedIds = checked.map(p => p.id)
const visibleParentsIds = visibleParents.map(p => p.id)
const result = checkedIds
visibleParentsIds.forEach(p => {
if (result.indexOf(p) === -1) {
result.push(p)
}
})
return result
},
},
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>
)
}
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">
{data.visibleParent && data.type == 2 &&
<a-tooltip placement="top" title="选中此项才会显示父节点">
<a-icon type="eye" style={{ color: '#1890ff' }} class="mr-xxs" />
</a-tooltip>
}
<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)
const visible = this.getVisible()
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),
// 返回所有选中和半选,但是不返回没有子级选中visibleParent的半选
visible
)
},
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)
})
},
getVisible() {
const checked = this.list.filter(p => p.checked)
const caseChildren = checked.filter(p => p.visibleParent || p.type != 2)
const visibleParents = []
// 递归寻找父级
const findVisibleParents = (children) => {
const parents = []
children.forEach(item => {
if (item.parentId) {
const parent = this.list.find(p => p.id === item.parentId)
if (parent) {
parents.push(parent)
visibleParents.push(parent)
}
}
})
if (parents.length) {
findVisibleParents(parents)
}
}
findVisibleParents(caseChildren)
const checkedIds = checked.map(p => p.id)
const visibleParentsIds = visibleParents.map(p => p.id)
const result = checkedIds
visibleParentsIds.forEach(p => {
if (result.indexOf(p) === -1) {
result.push(p)
}
})
return result
},
},
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

@@ -1,107 +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>
)
},
}
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

@@ -1,64 +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);
},
},
};
<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

@@ -1,229 +1,229 @@
// 列设置用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
}
}).finally(() => {
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 = {
sortField: sorter.field,
sortOrder: sorter.order,
}
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
},
onQuery() {
this.$emit('query')
},
onResetQuery() {
this.$emit('resetQuery')
this.$emit('reset-query')
}
},
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 || Math.random().toString(16).slice(2),
scroll: { x: 'max-content' }
}
const on = {
change: this.onTableChange,
...this.$listeners
}
const queryOn = {
'submit.native.prevent': () => { }
}
return (
<section>
<a-alert type="warning" closable>
<template slot="message">
字段固定应该遵循左侧固定到最左,右侧固定到最右(此逻辑难以实现)
</template>
</a-alert>
<br />
{
this.$scopedSlots.query &&
<div class="yo-query-bar">
<a-form-model layout="inline" {...{ on: queryOn }}>
{this.$scopedSlots.query()}
<a-form-model-item>
<a-button-group>
<a-button onClick={this.onQuery} html-type="submit" type="primary">查询</a-button>
<a-button onClick={this.onResetQuery}>重置</a-button>
</a-button-group>
</a-form-model-item>
</a-form-model>
</div>
}
<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>
)
},
// 列设置用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
}
}).finally(() => {
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 = {
sortField: sorter.field,
sortOrder: sorter.order,
}
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
},
onQuery() {
this.$emit('query')
},
onResetQuery() {
this.$emit('resetQuery')
this.$emit('reset-query')
}
},
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 || Math.random().toString(16).slice(2),
scroll: { x: 'max-content' }
}
const on = {
change: this.onTableChange,
...this.$listeners
}
const queryOn = {
'submit.native.prevent': () => { }
}
return (
<section>
<a-alert type="warning" closable>
<template slot="message">
字段固定应该遵循左侧固定到最左,右侧固定到最右(此逻辑难以实现)
</template>
</a-alert>
<br />
{
this.$scopedSlots.query &&
<div class="yo-query-bar">
<a-form-model layout="inline" {...{ on: queryOn }}>
{this.$scopedSlots.query()}
<a-form-model-item>
<a-button-group>
<a-button onClick={this.onQuery} html-type="submit" type="primary">查询</a-button>
<a-button onClick={this.onResetQuery}>重置</a-button>
</a-button-group>
</a-form-model-item>
</a-form-model>
</div>
}
<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

@@ -1,23 +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>
)
}
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

@@ -1,303 +1,303 @@
import Swiper from 'swiper'
let timer, swiper
export default {
props: {
loadData: {
type: Function,
require: true,
},
defaultExpandedKeys: {
default: false,
type: Boolean
},
replaceFields: {
default() {
return {
value: 'id',
title: 'title',
children: 'children'
}
},
type: Object
}
},
data() {
return {
loading: false,
data: [],
list: [],
searchValue: '',
selectedKeys: [],
expandedKeys: [],
autoExpandParent: true
}
},
created() {
this.onLoadData()
},
mounted() {
const container = this.$refs.swiper,
scrollBar = container.querySelector('.swiper-scrollbar')
const swiperOptions = {
direction: 'vertical',
slidesPerView: 'auto',
freeMode: true,
scrollbar: {
el: scrollBar,
},
mousewheel: true,
}
swiper = new Swiper(container, swiperOptions)
window.addEventListener('resize', () => {
this.onUpdateSwiper()
})
},
methods: {
renderBreadcrumbItem() {
const path = ['顶级']
const findPath = (data, level) => {
level = level || 1
for (let i = 0; i < data.length; i++) {
const item = data[i]
path[level] = item[this.replaceFields.title]
if (item[this.replaceFields.value] === this.selectedKeys[0]) {
path.length = level + 1
return true
}
if (item[this.replaceFields.children] && item[this.replaceFields.children].length) {
const found = findPath(item[this.replaceFields.children], level + 1)
if (found) {
return true
}
}
}
}
if (this.selectedKeys.length) {
findPath(this.data)
}
return path.map(p => (
<a-breadcrumb-item>{p}</a-breadcrumb-item>
))
},
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.$nextTick(() => {
this.onUpdateSwiper()
})
}).finally(() => {
this.loading = false
})
},
onReloadData() {
this.onLoadData()
},
onExpand(expandedKeys) {
this.expandedKeys = expandedKeys
this.autoExpandParent = false
this.onUpdateSwiper()
},
onUnexpandAll() {
this.expandedKeys = []
},
onSearch(value) {
const expandedKeys = this.list
.map(p => {
if (p[this.replaceFields.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
this.$nextTick(() => {
this.onUpdateSwiper()
})
},
onSelect(selectedKeys) {
const selectedIds = []
selectedKeys.forEach(p => {
const data = this.list.find(m => m.key === p)
selectedIds.push(data[this.replaceFields.value])
})
this.selectedKeys = selectedIds
this.$emit('select', selectedIds)
},
onUpdateSwiper() {
clearTimeout(timer)
timer = setTimeout(() => {
swiper.update()
swiper.update()
}, 300)
},
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[this.replaceFields.children]) {
this.generateKey(p[this.replaceFields.children], Object.assign([], n))
}
})
return data
},
generateList(data) {
// 这里获取不到Key
for (let i = 0; i < data.length; i++) {
const { key } = data[i]
this.list.push({
key,
[this.replaceFields.value]: data[i][this.replaceFields.value],
[this.replaceFields.title]: data[i][this.replaceFields.title]
})
if (data[i][this.replaceFields.children]) {
this.generateList(data[i][this.replaceFields.children])
}
}
},
getParentKey(key, tree) {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i]
if (node[this.replaceFields.children]) {
if (node[this.replaceFields.children].some(item => item.key === key)) {
parentKey = node.key
} else if (this.getParentKey(key, node[this.replaceFields.children])) {
parentKey = this.getParentKey(key, node[this.replaceFields.children])
}
}
}
return parentKey;
},
},
render() {
const props = {
treeData: this.data,
expandedKeys: this.expandedKeys,
autoExpandParent: this.autoExpandParent,
}
const on = {
expand: this.onExpand,
select: this.onSelect
}
const scopedSlots = {
title: (props) => {
const title = props[this.replaceFields.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>
<div class="yo-tree-layout--bar">
<a-tooltip placement="bottom" title="折叠全部">
<a-icon type="switcher" onClick={this.onUnexpandAll} />
</a-tooltip>
</div>
<div class="swiper-container" ref="swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">
<a-spin style={{ height: '100%' }} spinning={this.loading}>
<a-icon slot="indicator" type="loading" spin />
{
!this.loading && !this.list.length ?
<a-empty description={false} class="ant-list-empty-text">
<template slot="image">
<a-icon class="h3 mt-xl mb-md" type="smile" />
<p>暂无数据</p>
</template>
</a-empty>
:
<a-tree {...{ props, on, scopedSlots }} />
}
</a-spin>
</div>
</div>
<div class="swiper-scrollbar" />
</div>
</a-layout-sider>
<a-layout-content>
<container>
<a-breadcrumb class="mt-md mb-md">
{this.renderBreadcrumbItem()}
</a-breadcrumb>
</container>
{this.$scopedSlots.default ? this.$scopedSlots.default() : null}
</a-layout-content>
</a-layout>
)
}
import Swiper from 'swiper'
let timer, swiper
export default {
props: {
loadData: {
type: Function,
require: true,
},
defaultExpandedKeys: {
default: false,
type: Boolean
},
replaceFields: {
default() {
return {
value: 'id',
title: 'title',
children: 'children'
}
},
type: Object
}
},
data() {
return {
loading: false,
data: [],
list: [],
searchValue: '',
selectedKeys: [],
expandedKeys: [],
autoExpandParent: true
}
},
created() {
this.onLoadData()
},
mounted() {
const container = this.$refs.swiper,
scrollBar = container.querySelector('.swiper-scrollbar')
const swiperOptions = {
direction: 'vertical',
slidesPerView: 'auto',
freeMode: true,
scrollbar: {
el: scrollBar,
},
mousewheel: true,
}
swiper = new Swiper(container, swiperOptions)
window.addEventListener('resize', () => {
this.onUpdateSwiper()
})
},
methods: {
renderBreadcrumbItem() {
const path = ['顶级']
const findPath = (data, level) => {
level = level || 1
for (let i = 0; i < data.length; i++) {
const item = data[i]
path[level] = item[this.replaceFields.title]
if (item[this.replaceFields.value] === this.selectedKeys[0]) {
path.length = level + 1
return true
}
if (item[this.replaceFields.children] && item[this.replaceFields.children].length) {
const found = findPath(item[this.replaceFields.children], level + 1)
if (found) {
return true
}
}
}
}
if (this.selectedKeys.length) {
findPath(this.data)
}
return path.map(p => (
<a-breadcrumb-item>{p}</a-breadcrumb-item>
))
},
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.$nextTick(() => {
this.onUpdateSwiper()
})
}).finally(() => {
this.loading = false
})
},
onReloadData() {
this.onLoadData()
},
onExpand(expandedKeys) {
this.expandedKeys = expandedKeys
this.autoExpandParent = false
this.onUpdateSwiper()
},
onUnexpandAll() {
this.expandedKeys = []
},
onSearch(value) {
const expandedKeys = this.list
.map(p => {
if (p[this.replaceFields.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
this.$nextTick(() => {
this.onUpdateSwiper()
})
},
onSelect(selectedKeys) {
const selectedIds = []
selectedKeys.forEach(p => {
const data = this.list.find(m => m.key === p)
selectedIds.push(data[this.replaceFields.value])
})
this.selectedKeys = selectedIds
this.$emit('select', selectedIds)
},
onUpdateSwiper() {
clearTimeout(timer)
timer = setTimeout(() => {
swiper.update()
swiper.update()
}, 300)
},
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[this.replaceFields.children]) {
this.generateKey(p[this.replaceFields.children], Object.assign([], n))
}
})
return data
},
generateList(data) {
// 这里获取不到Key
for (let i = 0; i < data.length; i++) {
const { key } = data[i]
this.list.push({
key,
[this.replaceFields.value]: data[i][this.replaceFields.value],
[this.replaceFields.title]: data[i][this.replaceFields.title]
})
if (data[i][this.replaceFields.children]) {
this.generateList(data[i][this.replaceFields.children])
}
}
},
getParentKey(key, tree) {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i]
if (node[this.replaceFields.children]) {
if (node[this.replaceFields.children].some(item => item.key === key)) {
parentKey = node.key
} else if (this.getParentKey(key, node[this.replaceFields.children])) {
parentKey = this.getParentKey(key, node[this.replaceFields.children])
}
}
}
return parentKey;
},
},
render() {
const props = {
treeData: this.data,
expandedKeys: this.expandedKeys,
autoExpandParent: this.autoExpandParent,
}
const on = {
expand: this.onExpand,
select: this.onSelect
}
const scopedSlots = {
title: (props) => {
const title = props[this.replaceFields.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>
<div class="yo-tree-layout--bar">
<a-tooltip placement="bottom" title="折叠全部">
<a-icon type="switcher" onClick={this.onUnexpandAll} />
</a-tooltip>
</div>
<div class="swiper-container" ref="swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">
<a-spin style={{ height: '100%' }} spinning={this.loading}>
<a-icon slot="indicator" type="loading" spin />
{
!this.loading && !this.list.length ?
<a-empty description={false} class="ant-list-empty-text">
<template slot="image">
<a-icon class="h3 mt-xl mb-md" type="smile" />
<p>暂无数据</p>
</template>
</a-empty>
:
<a-tree {...{ props, on, scopedSlots }} />
}
</a-spin>
</div>
</div>
<div class="swiper-scrollbar" />
</div>
</a-layout-sider>
<a-layout-content>
<container>
<a-breadcrumb class="mt-md mb-md">
{this.renderBreadcrumbItem()}
</a-breadcrumb>
</container>
{this.$scopedSlots.default ? this.$scopedSlots.default() : null}
</a-layout-content>
</a-layout>
)
}
}

View File

@@ -1,107 +1,107 @@
<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);
window.addEventListener("resize",function(){
myChart.resize();
})
},
};
<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);
window.addEventListener("resize",function(){
myChart.resize();
})
},
};
</script>

View File

@@ -1,40 +1,40 @@
@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;
h4 {
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;
}
}
}
@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;
h4 {
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

@@ -1,66 +1,66 @@
<template>
<div>
<div class="home-header">
<container>
<a-row align="middle" justify="space-between" type="flex">
<a-col>
<div class="home-header-row" v-if="$root.global.info">
<div class="home-header-avatar">
<yo-image :id="$root.global.info.avatar" :size="64" icon="user" type="avatar" />
</div>
<div class="home-header-content">
<h4>
{{ $moment().format('A') }}
<span>{{ $root.global.info.nickName || $root.global.info.name }}</span>欢迎您登录系统
</h4>
<p>
<span>上次IP{{ $root.global.info.lastLoginIp }}</span>
<a-divider type="vertical" />
<span>上次登录时间{{ $root.global.info.lastLoginTime }}</span>
</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,
},
};
<template>
<div>
<div class="home-header">
<container>
<a-row align="middle" justify="space-between" type="flex">
<a-col>
<div class="home-header-row" v-if="$root.global.info">
<div class="home-header-avatar">
<yo-image :id="$root.global.info.avatar" :size="64" icon="user" type="avatar" />
</div>
<div class="home-header-content">
<h4>
{{ $moment().format('A') }}
<span>{{ $root.global.info.nickName || $root.global.info.name }}</span>欢迎您登录系统
</h4>
<p>
<span>上次IP{{ $root.global.info.lastLoginIp }}</span>
<a-divider type="vertical" />
<span>上次登录时间{{ $root.global.info.lastLoginTime }}</span>
</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

@@ -1,99 +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;
},
},
};
<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

@@ -1,28 +1,28 @@
<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>
export default {
data() {
return {
data: [
{ title: '关于2020年度房屋征收评估机构信用考核情况的通报' },
{ title: '关于2020年度房屋征收评估机构信用考核情况的通报' },
{ title: '关于2020年度房屋征收评估机构信用考核情况的通报' },
],
};
},
};
<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>
export default {
data() {
return {
data: [
{ title: '关于2020年度房屋征收评估机构信用考核情况的通报' },
{ title: '关于2020年度房屋征收评估机构信用考核情况的通报' },
{ title: '关于2020年度房屋征收评估机构信用考核情况的通报' },
],
};
},
};
</script>

View File

@@ -1,56 +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,
};
},
};
<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

@@ -1,56 +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: '江北区三官堂大桥及接线工程项目备案(成薇)' },
],
};
},
};
<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

@@ -1,5 +1,5 @@
/**
基本上所有列表页都可以通过拷贝此处的种子文件实现增删改查的功能
所有带 ... 的注释是可以依据当前业务添加内容的地方
其他所有尽量不要修改
/**
基本上所有列表页都可以通过拷贝此处的种子文件实现增删改查的功能
所有带 ... 的注释是可以依据当前业务添加内容的地方
其他所有尽量不要修改
**/

View File

@@ -1,114 +1,114 @@
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<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>
/* 表单内容默认值 */
const defaultForm = {
/* ... */
};
export default {
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
/* ... */
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/* ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
...params.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(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/* ... */
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
},
};
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<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>
/* 表单内容默认值 */
const defaultForm = {
/* ... */
};
export default {
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
/* ... */
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/* ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
...params.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(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/* ... */
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
},
};
</script>

View File

@@ -1,216 +1,216 @@
<template>
<!--
普通查询表格
v 1.2
2021-04-30
Lufthafen
-->
<container>
<br />
<a-card :bordered="false">
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="authCode:page" slot="query">
<!-- 此处添加查询表单控件 -->
<!-- ... -->
</Auth>
<Auth auth="authCode: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="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>
<!-- 新增表单 -->
<yo-modal-form :action="$api[api.add]" :title="'新增' + name" @ok="onReloadData" ref="add-form">
<form-body />
</yo-modal-form>
<!-- 编辑表单 -->
<yo-modal-form :action="$api[api.edit]" :title="'编辑' + name" @ok="onReloadData" ref="edit-form">
<form-body />
</yo-modal-form>
</container>
</template>
<script>
import FormBody from './form';
/* 在此管理整个页面需要的接口名称 */
const api = {
page: 'testPageApi...',
add: 'testAddApi',
edit: 'testEditApi',
delete: 'testDeleteApi...',
/* ... */
};
export default {
components: {
FormBody,
},
data() {
return {
api,
name: '...',
/* 查询条件 */
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[api.page]({
...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.sysDictTypeDropDownAwait({ code: 'code1' }),
this.$api.sysDictTypeDropDownAwait({ 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();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api[api.delete](record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
},
};
<template>
<!--
普通查询表格
v 1.2
2021-04-30
Lufthafen
-->
<container>
<br />
<a-card :bordered="false">
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="authCode:page" slot="query">
<!-- 此处添加查询表单控件 -->
<!-- ... -->
</Auth>
<Auth auth="authCode: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="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>
<!-- 新增表单 -->
<yo-modal-form :action="$api[api.add]" :title="'新增' + name" @ok="onReloadData" ref="add-form">
<form-body />
</yo-modal-form>
<!-- 编辑表单 -->
<yo-modal-form :action="$api[api.edit]" :title="'编辑' + name" @ok="onReloadData" ref="edit-form">
<form-body />
</yo-modal-form>
</container>
</template>
<script>
import FormBody from './form';
/* 在此管理整个页面需要的接口名称 */
const api = {
page: 'testPageApi...',
add: 'testAddApi',
edit: 'testEditApi',
delete: 'testDeleteApi...',
/* ... */
};
export default {
components: {
FormBody,
},
data() {
return {
api,
name: '...',
/* 查询条件 */
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[api.page]({
...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.sysDictTypeDropDownAwait({ code: 'code1' }),
this.$api.sysDictTypeDropDownAwait({ 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();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api[api.delete](record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
},
};
</script>

View File

@@ -1,158 +1,158 @@
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<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">
<!-- 表单控件 -->
<!-- ... -->
<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="icon">
<a-input :disabled="true" placeholder="请选择图标" v-model="form.icon">
<a-icon :type="form.icon" slot="addonBefore" v-if="form.icon" />
<a-icon @click="onOpenSelectIcon" slot="addonAfter" type="setting" />
</a-input>
</a-form-model-item>
<a-form-model-item label="图标颜色" prop="color">
<chrome-picker v-model="form.color" />
</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-spin>
<yo-icon-selector ref="icon-selector" v-model="form.icon" />
</a-form-model>
</template>
<script>
import YoIconSelector from '@/components/yoIconSelector';
import { Chrome } from 'vue-color';
/* 表单内容默认值 */
const defaultForm = {
/* ... */
color: '#ffffff',
active: false,
};
export default {
components: {
YoIconSelector,
ChromePicker: Chrome,
},
data() {
return {
/** 表单数据 */
form: {
...defaultForm,
},
/** 验证格式 */
rules: {
/* ... */
name: [{ required: true, message: '请输入应用名称' }],
code: [{ required: true, message: '请输入唯一编码' }],
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/* ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
...params.record,
/** 在此处添加其他默认数据转换 */
/* ... */
});
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
onGetData() {
return new Promise((reslove, reject) => {
this.$refs.form.validate((valid) => {
if (valid) {
const record = this.$_.cloneDeep(this.form);
/** 验证通过后可以对数据进行转换得到想要提交的格式 */
/* ... */
record.color = record.color.constructor === Object ? record.color.hex : record.color;
reslove(record);
} else {
reject();
}
});
});
},
/**
* 必要的方法
* 在外部窗口进行保存时调用表单验证
*/
onValidate(callback) {
this.$refs.form.validate(callback);
},
/**
* 必要的方法
* 在外部窗口关闭或重置时对表单验证进行初始化
*/
onResetFields() {
setTimeout(() => {
this.$refs.form.resetFields();
/** 在这里可以初始化当前组件中其他属性 */
/* ... */
}, 300);
},
/**
* 必要方法
* 加载当前表单中所需要的异步数据
*/
async onInit(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/* ... */
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
onOpenSelectIcon() {
this.$refs['icon-selector'].onOpen(this.form.icon);
},
},
};
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<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">
<!-- 表单控件 -->
<!-- ... -->
<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="icon">
<a-input :disabled="true" placeholder="请选择图标" v-model="form.icon">
<a-icon :type="form.icon" slot="addonBefore" v-if="form.icon" />
<a-icon @click="onOpenSelectIcon" slot="addonAfter" type="setting" />
</a-input>
</a-form-model-item>
<a-form-model-item label="图标颜色" prop="color">
<chrome-picker v-model="form.color" />
</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-spin>
<yo-icon-selector ref="icon-selector" v-model="form.icon" />
</a-form-model>
</template>
<script>
import YoIconSelector from '@/components/yoIconSelector';
import { Chrome } from 'vue-color';
/* 表单内容默认值 */
const defaultForm = {
/* ... */
color: '#ffffff',
active: false,
};
export default {
components: {
YoIconSelector,
ChromePicker: Chrome,
},
data() {
return {
/** 表单数据 */
form: {
...defaultForm,
},
/** 验证格式 */
rules: {
/* ... */
name: [{ required: true, message: '请输入应用名称' }],
code: [{ required: true, message: '请输入唯一编码' }],
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/* ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
...params.record,
/** 在此处添加其他默认数据转换 */
/* ... */
});
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
onGetData() {
return new Promise((reslove, reject) => {
this.$refs.form.validate((valid) => {
if (valid) {
const record = this.$_.cloneDeep(this.form);
/** 验证通过后可以对数据进行转换得到想要提交的格式 */
/* ... */
record.color = record.color.constructor === Object ? record.color.hex : record.color;
reslove(record);
} else {
reject();
}
});
});
},
/**
* 必要的方法
* 在外部窗口进行保存时调用表单验证
*/
onValidate(callback) {
this.$refs.form.validate(callback);
},
/**
* 必要的方法
* 在外部窗口关闭或重置时对表单验证进行初始化
*/
onResetFields() {
setTimeout(() => {
this.$refs.form.resetFields();
/** 在这里可以初始化当前组件中其他属性 */
/* ... */
}, 300);
},
/**
* 必要方法
* 加载当前表单中所需要的异步数据
*/
async onInit(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/* ... */
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
onOpenSelectIcon() {
this.$refs['icon-selector'].onOpen(this.form.icon);
},
},
};
</script>

View File

@@ -1,280 +1,280 @@
<template>
<!--
普通查询表格
v 1.2
2021-04-30
Lufthafen
-->
<container>
<br />
<a-card :bordered="false">
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="sysApp:page" slot="query">
<!-- 此处添加查询表单控件 -->
<!-- ... -->
<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>
</Auth>
<Auth auth="sysApp:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增应用</a-button>
</Auth>
<!-- 格式化字段内容 -->
<!-- ... -->
<span slot="active" slot-scope="text, record">
{{ text ? '是' : '否' }}
<Auth auth="sysApp:setAsDefault" v-if="!record.active">
<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, 'commonStatus') }}</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>
<!-- 新增表单 -->
<yo-modal-form :action="$api[api.add]" :title="'新增' + name" @ok="onReloadData" ref="add-form">
<form-body />
</yo-modal-form>
<!-- 编辑表单 -->
<yo-modal-form :action="$api[api.edit]" :title="'编辑' + name" @ok="onReloadData" ref="edit-form">
<form-body />
</yo-modal-form>
</container>
</template>
<script>
import FormBody from './form';
/* 在此管理整个页面需要的接口名称 */
const api = {
page: 'getAppPage',
add: 'sysAppAdd',
edit: 'sysAppEdit',
delete: 'sysAppDelete',
/* ... */
};
export default {
components: {
FormBody,
},
data() {
return {
api,
name: '应用',
/* 查询条件 */
query: {},
/* 表格字段 */
columns: [
{
title: '应用名称',
dataIndex: 'name',
sorter: true,
},
{
title: '唯一编码',
dataIndex: 'code',
sorter: true,
},
{
title: '是否默认',
dataIndex: 'active',
scopedSlots: {
customRender: 'active',
},
sorter: true,
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: {
customRender: 'status',
},
sorter: true,
},
{
title: '排序',
dataIndex: 'sort',
sorter: true,
},
],
/* 字典编码储存格式 */
codes: {
yesOrNo: [],
commonStatus: [],
},
};
},
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[api.page]({
...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.sysDictTypeDropDownAwait({ code: 'yes_or_no' }),
this.$api.sysDictTypeDropDownAwait({ code: 'common_status' }),
/* ... */
])
.then(([code1, code2]) => {
this.codes.yesOrNo = code1.data;
this.codes.commonStatus = 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();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api[api.delete](record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
onSetDefault(record) {
this.$refs.table.onLoading();
this.$api.sysAppSetAsDefault({ id: record.id }).then(({ success }) => {
this.onResult(success, '设置成功');
});
},
},
};
<template>
<!--
普通查询表格
v 1.2
2021-04-30
Lufthafen
-->
<container>
<br />
<a-card :bordered="false">
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="sysApp:page" slot="query">
<!-- 此处添加查询表单控件 -->
<!-- ... -->
<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>
</Auth>
<Auth auth="sysApp:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增应用</a-button>
</Auth>
<!-- 格式化字段内容 -->
<!-- ... -->
<span slot="active" slot-scope="text, record">
{{ text ? '是' : '否' }}
<Auth auth="sysApp:setAsDefault" v-if="!record.active">
<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, 'commonStatus') }}</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>
<!-- 新增表单 -->
<yo-modal-form :action="$api[api.add]" :title="'新增' + name" @ok="onReloadData" ref="add-form">
<form-body />
</yo-modal-form>
<!-- 编辑表单 -->
<yo-modal-form :action="$api[api.edit]" :title="'编辑' + name" @ok="onReloadData" ref="edit-form">
<form-body />
</yo-modal-form>
</container>
</template>
<script>
import FormBody from './form';
/* 在此管理整个页面需要的接口名称 */
const api = {
page: 'getAppPage',
add: 'sysAppAdd',
edit: 'sysAppEdit',
delete: 'sysAppDelete',
/* ... */
};
export default {
components: {
FormBody,
},
data() {
return {
api,
name: '应用',
/* 查询条件 */
query: {},
/* 表格字段 */
columns: [
{
title: '应用名称',
dataIndex: 'name',
sorter: true,
},
{
title: '唯一编码',
dataIndex: 'code',
sorter: true,
},
{
title: '是否默认',
dataIndex: 'active',
scopedSlots: {
customRender: 'active',
},
sorter: true,
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: {
customRender: 'status',
},
sorter: true,
},
{
title: '排序',
dataIndex: 'sort',
sorter: true,
},
],
/* 字典编码储存格式 */
codes: {
yesOrNo: [],
commonStatus: [],
},
};
},
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[api.page]({
...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.sysDictTypeDropDownAwait({ code: 'yes_or_no' }),
this.$api.sysDictTypeDropDownAwait({ code: 'common_status' }),
/* ... */
])
.then(([code1, code2]) => {
this.codes.yesOrNo = code1.data;
this.codes.commonStatus = 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();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api[api.delete](record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
onSetDefault(record) {
this.$refs.table.onLoading();
this.$api.sysAppSetAsDefault({ id: record.id }).then(({ success }) => {
this.onResult(success, '设置成功');
});
},
},
};
</script>

View File

@@ -1,159 +1,159 @@
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<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">
<!-- 表单控件 -->
<!-- ... -->
<a-form-model-item label="角色名" prop="name">
<a-input placeholder="请输入角色名" v-model="form.name" />
</a-form-model-item>
<a-form-model-item :disabled="editDisabled" label="唯一编码" prop="code">
<a-input placeholder="请输入唯一编码" v-model="form.code" />
</a-form-model-item>
<a-form-model-item label="系统参数" prop="sysFlag">
<a-radio-group :disabled="editDisabled" v-model="form.sysFlag">
<a-radio-button value="Y"></a-radio-button>
<a-radio-button value="N"></a-radio-button>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="所属分类" prop="groupCode">
<a-select :disabled="editDisabled" placeholder="请选择所属分类" v-model="form.groupCode">
<a-select-option
:key="i"
:value="item.code"
v-for="(item, i) in groupCode"
>{{ item.value }}</a-select-option>
</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-spin>
</a-form-model>
</template>
<script>
/* 表单内容默认值 */
const defaultForm = {
/* ... */
};
export default {
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
name: [{ required: true, message: '请输入应用名称' }],
code: [{ required: true, message: '请输入唯一编码' }],
sysFlag: [{ required: true, message: '请选择参数类型' }],
groupCode: [{ required: true, message: '请选择所属分类' }],
value: [{ required: true, message: '请输入参数值' }],
/* ... */
},
/** 加载异步数据状态 */
loading: false,
editDisabled: false,
groupCode: [],
/** 其他成员属性 */
/* ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
...params.record,
/** 在此处添加其他默认数据转换 */
/* ... */
});
this.editDisabled = this.form.sysFlag == 'Y';
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
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(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
this.groupCode = await this.onLoadgroupCodeData();
/* ... */
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
/**
*
* 获取所属分类字典表的内容
*/
onLoadgroupCodeData() {
return this.$api.sysDictTypeDropDown({ code: 'consts_type' }).then(({ data }) => {
return data;
});
},
},
};
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<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">
<!-- 表单控件 -->
<!-- ... -->
<a-form-model-item label="角色名" prop="name">
<a-input placeholder="请输入角色名" v-model="form.name" />
</a-form-model-item>
<a-form-model-item :disabled="editDisabled" label="唯一编码" prop="code">
<a-input placeholder="请输入唯一编码" v-model="form.code" />
</a-form-model-item>
<a-form-model-item label="系统参数" prop="sysFlag">
<a-radio-group :disabled="editDisabled" v-model="form.sysFlag">
<a-radio-button value="Y"></a-radio-button>
<a-radio-button value="N"></a-radio-button>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="所属分类" prop="groupCode">
<a-select :disabled="editDisabled" placeholder="请选择所属分类" v-model="form.groupCode">
<a-select-option
:key="i"
:value="item.code"
v-for="(item, i) in groupCode"
>{{ item.value }}</a-select-option>
</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-spin>
</a-form-model>
</template>
<script>
/* 表单内容默认值 */
const defaultForm = {
/* ... */
};
export default {
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
name: [{ required: true, message: '请输入应用名称' }],
code: [{ required: true, message: '请输入唯一编码' }],
sysFlag: [{ required: true, message: '请选择参数类型' }],
groupCode: [{ required: true, message: '请选择所属分类' }],
value: [{ required: true, message: '请输入参数值' }],
/* ... */
},
/** 加载异步数据状态 */
loading: false,
editDisabled: false,
groupCode: [],
/** 其他成员属性 */
/* ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
...params.record,
/** 在此处添加其他默认数据转换 */
/* ... */
});
this.editDisabled = this.form.sysFlag == 'Y';
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
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(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
this.groupCode = await this.onLoadgroupCodeData();
/* ... */
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
/**
*
* 获取所属分类字典表的内容
*/
onLoadgroupCodeData() {
return this.$api.sysDictTypeDropDown({ code: 'consts_type' }).then(({ data }) => {
return data;
});
},
},
};
</script>

View File

@@ -1,238 +1,238 @@
<template>
<!--
普通查询表格
v 1.2
2021-04-30
Lufthafen
-->
<container>
<br />
<a-card :bordered="false">
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="sysConfig:page" slot="query">
<!-- 此处添加查询表单控件 -->
<!-- ... -->
<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>
</Auth>
<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>
<!-- 新增表单 -->
<yo-modal-form :action="$api[api.add]" :title="'新增' + name" @ok="onReloadData" ref="add-form">
<form-body />
</yo-modal-form>
<!-- 编辑表单 -->
<yo-modal-form :action="$api[api.edit]" :title="'编辑' + name" @ok="onReloadData" ref="edit-form">
<form-body />
</yo-modal-form>
</container>
</template>
<script>
import FormBody from './form';
/* 在此管理整个页面需要的接口名称 */
const api = {
page: 'sysConfigPage',
add: 'sysConfigAdd',
edit: 'sysConfigEdit',
delete: 'sysConfigDelete',
/* ... */
};
export default {
components: {
FormBody,
},
data() {
return {
api,
name: '配置',
/* 查询条件 */
query: {},
/* 表格字段 */
columns: [
{
title: '参数名称',
dataIndex: 'name',
sorter: true,
},
{
title: '唯一编码',
dataIndex: 'code',
sorter: true,
},
{
title: '参数值',
dataIndex: 'value',
sorter: true,
},
{
title: '所属分类',
dataIndex: 'groupCode',
sorter: true,
},
{
title: '备注',
dataIndex: 'remark',
sorter: true,
},
],
/* 字典编码储存格式 */
codes: {
code1: [],
code2: [],
},
};
},
created() {
/** 按需加载字典编码 */
this.onLoadCodes();
/** 根据权限添加操作列 */
const flag = this.$auth({
sysConfig: [['edit'], ['delete']],
});
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return this.$api[api.page]({
...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() {},
/**
* 必要方法
* 绑定数据字典值
*/
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();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api[api.delete](record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
},
};
<template>
<!--
普通查询表格
v 1.2
2021-04-30
Lufthafen
-->
<container>
<br />
<a-card :bordered="false">
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="sysConfig:page" slot="query">
<!-- 此处添加查询表单控件 -->
<!-- ... -->
<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>
</Auth>
<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>
<!-- 新增表单 -->
<yo-modal-form :action="$api[api.add]" :title="'新增' + name" @ok="onReloadData" ref="add-form">
<form-body />
</yo-modal-form>
<!-- 编辑表单 -->
<yo-modal-form :action="$api[api.edit]" :title="'编辑' + name" @ok="onReloadData" ref="edit-form">
<form-body />
</yo-modal-form>
</container>
</template>
<script>
import FormBody from './form';
/* 在此管理整个页面需要的接口名称 */
const api = {
page: 'sysConfigPage',
add: 'sysConfigAdd',
edit: 'sysConfigEdit',
delete: 'sysConfigDelete',
/* ... */
};
export default {
components: {
FormBody,
},
data() {
return {
api,
name: '配置',
/* 查询条件 */
query: {},
/* 表格字段 */
columns: [
{
title: '参数名称',
dataIndex: 'name',
sorter: true,
},
{
title: '唯一编码',
dataIndex: 'code',
sorter: true,
},
{
title: '参数值',
dataIndex: 'value',
sorter: true,
},
{
title: '所属分类',
dataIndex: 'groupCode',
sorter: true,
},
{
title: '备注',
dataIndex: 'remark',
sorter: true,
},
],
/* 字典编码储存格式 */
codes: {
code1: [],
code2: [],
},
};
},
created() {
/** 按需加载字典编码 */
this.onLoadCodes();
/** 根据权限添加操作列 */
const flag = this.$auth({
sysConfig: [['edit'], ['delete']],
});
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return this.$api[api.page]({
...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() {},
/**
* 必要方法
* 绑定数据字典值
*/
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();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api[api.delete](record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
},
};
</script>

View File

@@ -1,221 +1,221 @@
<template>
<a-card>
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="sysDictData:page" slot="query">
<!-- 此处添加查询表单控件 -->
<a-form-model-item label="文本">
<a-input placeholder="请输入文本" v-model="query.value" />
</a-form-model-item>
<a-form-model-item label="字典值">
<a-input placeholder="请输入字典值" v-model="query.code" />
</a-form-model-item>
</Auth>
<Auth auth="sysDictData:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增字典数据</a-button>
</Auth>
<!-- 格式化字段内容 -->
<span slot="status" slot-scope="text">{{ bindCodeValue(text, 'commonStatus') }}</span>
<!-- 添加操作控件 -->
<span slot="action" slot-scope="text, record">
<yo-table-actions>
<Auth auth="sysDictData:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysDictData:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
<!-- 可在此处添加其他操作控件 -->
</yo-table-actions>
</span>
</yo-table>
<yo-modal-form :action="$api.sysDictDataAdd" @ok="onReloadData" ref="add-form" title="新增字典数据">
<form-body />
</yo-modal-form>
<yo-modal-form :action="$api.sysDictDataEdit" @ok="onReloadData" ref="edit-form" title="编辑字典数据">
<form-body />
</yo-modal-form>
</a-card>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
props: {
type: {
type: Object,
},
codes: {
type: Object,
},
},
data() {
return {
query: {
typeId: this.type.id,
},
columns: [
{
title: '文本',
dataIndex: 'value',
sorter: true,
},
{
title: '字典值',
dataIndex: 'code',
sorter: true,
},
{
title: '排序',
dataIndex: 'sort',
sorter: true,
},
{
title: '备注',
dataIndex: 'remark',
width: 200,
sorter: true,
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: { customRender: 'status' },
sorter: true,
},
],
};
},
created() {
this.onLoadCodes();
/** 根据权限添加操作列 */
const flag = this.$auth({ sysDictData: [['edit'], ['delete']] });
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return (
this.$api
/** !!此处必须修改调用的接口方法 */
.sysDictDataPage({
...params,
...this.query,
})
.then((res) => {
return res.data;
})
);
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 有查询功能时的必要方法
* 重置查询条件
*/
onResetQuery() {
/** 在这里重置查询条件时,可对特殊的字段做保留处理 */
Object.keys(this.query).forEach((p) => {
if (p !== 'typeId') {
this.query[p] = undefined;
}
});
this.onQuery();
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 必要方法
* 加载字典数据
* 如果不需要获取相应的字典数据,此方法内容可空
*/
onLoadCodes() {},
/**
* 必要方法
* 绑定数据字典值
*/
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({
typeId: this.type.id,
record,
});
},
/**
* 必要方法
* 可以用做一系列操作的公共回调,此方法中会重新加载当前列表
*/
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
this.$refs.table.onLoaded();
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api
/** !!此处必须修改调用的接口方法 */
.sysDictDataDelete(record)
.then(({ success }) => {
this.onResult(success, '删除成功');
});
},
},
};
<template>
<a-card>
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="sysDictData:page" slot="query">
<!-- 此处添加查询表单控件 -->
<a-form-model-item label="文本">
<a-input placeholder="请输入文本" v-model="query.value" />
</a-form-model-item>
<a-form-model-item label="字典值">
<a-input placeholder="请输入字典值" v-model="query.code" />
</a-form-model-item>
</Auth>
<Auth auth="sysDictData:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增字典数据</a-button>
</Auth>
<!-- 格式化字段内容 -->
<span slot="status" slot-scope="text">{{ bindCodeValue(text, 'commonStatus') }}</span>
<!-- 添加操作控件 -->
<span slot="action" slot-scope="text, record">
<yo-table-actions>
<Auth auth="sysDictData:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysDictData:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
<!-- 可在此处添加其他操作控件 -->
</yo-table-actions>
</span>
</yo-table>
<yo-modal-form :action="$api.sysDictDataAdd" @ok="onReloadData" ref="add-form" title="新增字典数据">
<form-body />
</yo-modal-form>
<yo-modal-form :action="$api.sysDictDataEdit" @ok="onReloadData" ref="edit-form" title="编辑字典数据">
<form-body />
</yo-modal-form>
</a-card>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
props: {
type: {
type: Object,
},
codes: {
type: Object,
},
},
data() {
return {
query: {
typeId: this.type.id,
},
columns: [
{
title: '文本',
dataIndex: 'value',
sorter: true,
},
{
title: '字典值',
dataIndex: 'code',
sorter: true,
},
{
title: '排序',
dataIndex: 'sort',
sorter: true,
},
{
title: '备注',
dataIndex: 'remark',
width: 200,
sorter: true,
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: { customRender: 'status' },
sorter: true,
},
],
};
},
created() {
this.onLoadCodes();
/** 根据权限添加操作列 */
const flag = this.$auth({ sysDictData: [['edit'], ['delete']] });
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return (
this.$api
/** !!此处必须修改调用的接口方法 */
.sysDictDataPage({
...params,
...this.query,
})
.then((res) => {
return res.data;
})
);
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 有查询功能时的必要方法
* 重置查询条件
*/
onResetQuery() {
/** 在这里重置查询条件时,可对特殊的字段做保留处理 */
Object.keys(this.query).forEach((p) => {
if (p !== 'typeId') {
this.query[p] = undefined;
}
});
this.onQuery();
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 必要方法
* 加载字典数据
* 如果不需要获取相应的字典数据,此方法内容可空
*/
onLoadCodes() {},
/**
* 必要方法
* 绑定数据字典值
*/
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({
typeId: this.type.id,
record,
});
},
/**
* 必要方法
* 可以用做一系列操作的公共回调,此方法中会重新加载当前列表
*/
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
this.$refs.table.onLoaded();
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api
/** !!此处必须修改调用的接口方法 */
.sysDictDataDelete(record)
.then(({ success }) => {
this.onResult(success, '删除成功');
});
},
},
};
</script>

View File

@@ -1,232 +1,232 @@
<template>
<container>
<br />
<a-card :bordered="false">
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="sysDictType:page" slot="query">
<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>
</Auth>
<Auth auth="sysDictType:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增字典类型</a-button>
</Auth>
<!-- 格式化字段内容 -->
<span slot="status" slot-scope="text">{{ bindCodeValue(text, 'commonStatus') }}</span>
<!-- 添加操作控件 -->
<span slot="action" slot-scope="text, record">
<yo-table-actions>
<Auth auth="sysDictType:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysDictType:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
<!-- 可在此处添加其他操作控件 -->
</yo-table-actions>
</span>
<div slot="expandedRowRender" slot-scope="record">
<dict-data :codes="codes" :type="record" />
</div>
</yo-table>
</a-card>
<br />
<yo-modal-form :action="$api.sysDictTypeAdd" @ok="onReloadData" ref="add-form" title="新增字典类型">
<form-body />
</yo-modal-form>
<yo-modal-form :action="$api.sysDictTypeEdit" @ok="onReloadData" ref="edit-form" title="编辑字典类型">
<form-body />
</yo-modal-form>
</container>
</template>
<script>
import DictData from './dictdata';
import FormBody from './form';
export default {
components: {
DictData,
FormBody,
},
data() {
return {
query: {},
columns: [
{
title: '类型名称',
dataIndex: 'name',
sorter: true,
},
{
title: '唯一编码',
dataIndex: 'code',
sorter: true,
},
{
title: '排序',
dataIndex: 'sort',
sorter: true,
},
{
title: '备注',
dataIndex: 'remark',
width: 200,
sorter: true,
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: { customRender: 'status' },
sorter: true,
},
],
codes: {
commonStatus: [],
},
};
},
created() {
this.onLoadCodes();
/** 根据权限添加操作列 */
const flag = this.$auth({ sysDictType: [['edit'], ['delete']] });
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return (
this.$api
/** !!此处必须修改调用的接口方法 */
.sysDictTypePage({
...params,
...this.query,
})
.then((res) => {
const data = res.data;
data.rows = data.rows.map(
(p) =>
(p = {
...p,
data: [],
query: {},
})
);
return data;
})
);
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 有查询功能时的必要方法
* 重置查询条件
*/
onResetQuery() {
/** 在这里重置查询条件时,可对特殊的字段做保留处理 */
this.query = {};
this.onQuery();
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 必要方法
* 加载字典数据
* 如果不需要获取相应的字典数据,此方法内容可空
*/
onLoadCodes() {
this.$api.$queue([this.$api.sysDictTypeDropDownAwait({ code: 'common_status' })]).then(([commonStatus]) => {
this.codes.commonStatus = commonStatus.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();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api
/** !!此处必须修改调用的接口方法 */
.sysDictTypeDelete(record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
},
};
<template>
<container>
<br />
<a-card :bordered="false">
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="sysDictType:page" slot="query">
<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>
</Auth>
<Auth auth="sysDictType:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增字典类型</a-button>
</Auth>
<!-- 格式化字段内容 -->
<span slot="status" slot-scope="text">{{ bindCodeValue(text, 'commonStatus') }}</span>
<!-- 添加操作控件 -->
<span slot="action" slot-scope="text, record">
<yo-table-actions>
<Auth auth="sysDictType:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysDictType:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
<!-- 可在此处添加其他操作控件 -->
</yo-table-actions>
</span>
<div slot="expandedRowRender" slot-scope="record">
<dict-data :codes="codes" :type="record" />
</div>
</yo-table>
</a-card>
<br />
<yo-modal-form :action="$api.sysDictTypeAdd" @ok="onReloadData" ref="add-form" title="新增字典类型">
<form-body />
</yo-modal-form>
<yo-modal-form :action="$api.sysDictTypeEdit" @ok="onReloadData" ref="edit-form" title="编辑字典类型">
<form-body />
</yo-modal-form>
</container>
</template>
<script>
import DictData from './dictdata';
import FormBody from './form';
export default {
components: {
DictData,
FormBody,
},
data() {
return {
query: {},
columns: [
{
title: '类型名称',
dataIndex: 'name',
sorter: true,
},
{
title: '唯一编码',
dataIndex: 'code',
sorter: true,
},
{
title: '排序',
dataIndex: 'sort',
sorter: true,
},
{
title: '备注',
dataIndex: 'remark',
width: 200,
sorter: true,
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: { customRender: 'status' },
sorter: true,
},
],
codes: {
commonStatus: [],
},
};
},
created() {
this.onLoadCodes();
/** 根据权限添加操作列 */
const flag = this.$auth({ sysDictType: [['edit'], ['delete']] });
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return (
this.$api
/** !!此处必须修改调用的接口方法 */
.sysDictTypePage({
...params,
...this.query,
})
.then((res) => {
const data = res.data;
data.rows = data.rows.map(
(p) =>
(p = {
...p,
data: [],
query: {},
})
);
return data;
})
);
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 有查询功能时的必要方法
* 重置查询条件
*/
onResetQuery() {
/** 在这里重置查询条件时,可对特殊的字段做保留处理 */
this.query = {};
this.onQuery();
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 必要方法
* 加载字典数据
* 如果不需要获取相应的字典数据,此方法内容可空
*/
onLoadCodes() {
this.$api.$queue([this.$api.sysDictTypeDropDownAwait({ code: 'common_status' })]).then(([commonStatus]) => {
this.codes.commonStatus = commonStatus.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();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api
/** !!此处必须修改调用的接口方法 */
.sysDictTypeDelete(record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
},
};
</script>

View File

@@ -1,213 +1,213 @@
<template>
<container>
<br />
<a-alert closable type="error">
<template slot="message">
<div>后端bug:任何操作的操作类型都是增加</div>
<div>没有记录请求参数.返回结果等信息</div>
</template>
</a-alert>
<br />
<a-card :bordered="false">
<Auth auth="sysOpLog: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-select
:style="{ width: '170px' }"
allow-clear
placeholder="请选择操作类型"
v-model="query.opType"
>
<a-select-option
:key="item.code"
:value="item.code"
v-for="item in codes.opTypeDict"
>{{ item.value }}</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="是否成功">
<a-select
:style="{ width: '170px' }"
allow-clear
placeholder="请选择是否成功"
v-model="query.success"
>
<a-select-option value="true"></a-select-option>
<a-select-option value="false"></a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="操作时间">
<a-range-picker
:show-time="{
hideDisabledOptions: true,
defaultValue: [$moment('00:00:00', 'HH:mm:ss'), $moment('23:59:59', 'HH:mm:ss')],
}"
format="YYYY-MM-DD HH:mm:ss"
v-model="query.dates"
/>
</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="sysOpLog:delete" slot="operator">
<a-popconfirm @confirm="onOpLogClear" placement="bottomLeft" title="是否确认清空日志">
<a-button>清空日志</a-button>
</a-popconfirm>
</Auth>
<span slot="opType" slot-scope="text">{{ bindCodeValue(text, 'opTypeDict') }}</span>
<span slot="success" slot-scope="text">{{ text ? '是' : '否' }}</span>
<a-descriptions bordered size="small" slot="expandedRowRender" slot-scope="record">
<a-descriptions-item :span="1" label="方法名称">{{ record.methodName }}</a-descriptions-item>
<a-descriptions-item :span="2" label="地址">{{ record.location }}</a-descriptions-item>
<a-descriptions-item label="浏览器">{{ record.browser }}</a-descriptions-item>
<a-descriptions-item :span="2" label="操作系统">{{ record.os }}</a-descriptions-item>
<a-descriptions-item :span="3" label="类名称">{{ record.className }}</a-descriptions-item>
<a-descriptions-item :span="3" label="返回结果">{{ record.result }}</a-descriptions-item>
<a-descriptions-item :span="3" label="请求参数">{{ record.param }}</a-descriptions-item>
<a-descriptions-item :span="3" label="具体消息">{{ record.message }}</a-descriptions-item>
</a-descriptions>
</yo-table>
</a-card>
<br />
</container>
</template>
<script>
export default {
data() {
return {
query: {},
columns: [
{
title: '日志名称',
dataIndex: 'name',
sorter: true,
},
{
title: '操作类型',
dataIndex: 'opType',
scopedSlots: { customRender: 'opType' },
sorter: true,
},
{
title: '是否成功',
dataIndex: 'success',
scopedSlots: { customRender: 'success' },
sorter: true,
},
{
title: 'ip',
dataIndex: 'ip',
sorter: true,
},
{
title: '请求地址',
dataIndex: 'url',
scopedSlots: { customRender: 'url' },
sorter: true,
},
{
title: '操作时间',
dataIndex: 'opTime',
scopedSlots: { customRender: 'opTime' },
sorter: true,
},
{
title: '操作人',
dataIndex: 'account',
sorter: true,
},
],
codes: {
opTypeDict: [],
},
};
},
created() {
this.onLoadCodes();
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
const query = this.$_.cloneDeep(this.query);
if (query.dates && query.dates.length) {
query.searchBeginTime = this.$moment(query.dates[0]).format('YYYY-MM-DD HH:mm:ss');
query.searchEndTime = this.$moment(query.dates[1]).format('YYYY-MM-DD HH:mm:ss');
delete query.dates;
}
return this.$api
.sysOpLogPage({
...params,
...query,
})
.then((res) => {
return res.data;
});
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 加载字典数据时的必要方法
*/
onLoadCodes() {
this.$api.$queue([this.$api.sysDictTypeDropDownAwait({ code: 'op_type' })]).then(([commonStatus]) => {
this.codes.opTypeDict = commonStatus.data;
});
},
bindCodeValue(code, name) {
const c = this.codes[name].find((p) => p.code == code);
if (c) {
return c.value;
}
return null;
},
onOpLogClear() {
this.$refs.table.onLoading();
this.$api
.sysOpLogDelete()
.then((res) => {
if (res.success) {
this.$message.success('清空成功');
this.onReloadData();
} else {
this.$message.error('清空失败:' + res.message);
}
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
},
};
<template>
<container>
<br />
<a-alert closable type="error">
<template slot="message">
<div>后端bug:任何操作的操作类型都是增加</div>
<div>没有记录请求参数.返回结果等信息</div>
</template>
</a-alert>
<br />
<a-card :bordered="false">
<Auth auth="sysOpLog: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-select
:style="{ width: '170px' }"
allow-clear
placeholder="请选择操作类型"
v-model="query.opType"
>
<a-select-option
:key="item.code"
:value="item.code"
v-for="item in codes.opTypeDict"
>{{ item.value }}</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="是否成功">
<a-select
:style="{ width: '170px' }"
allow-clear
placeholder="请选择是否成功"
v-model="query.success"
>
<a-select-option value="true"></a-select-option>
<a-select-option value="false"></a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="操作时间">
<a-range-picker
:show-time="{
hideDisabledOptions: true,
defaultValue: [$moment('00:00:00', 'HH:mm:ss'), $moment('23:59:59', 'HH:mm:ss')],
}"
format="YYYY-MM-DD HH:mm:ss"
v-model="query.dates"
/>
</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="sysOpLog:delete" slot="operator">
<a-popconfirm @confirm="onOpLogClear" placement="bottomLeft" title="是否确认清空日志">
<a-button>清空日志</a-button>
</a-popconfirm>
</Auth>
<span slot="opType" slot-scope="text">{{ bindCodeValue(text, 'opTypeDict') }}</span>
<span slot="success" slot-scope="text">{{ text ? '是' : '否' }}</span>
<a-descriptions bordered size="small" slot="expandedRowRender" slot-scope="record">
<a-descriptions-item :span="1" label="方法名称">{{ record.methodName }}</a-descriptions-item>
<a-descriptions-item :span="2" label="地址">{{ record.location }}</a-descriptions-item>
<a-descriptions-item label="浏览器">{{ record.browser }}</a-descriptions-item>
<a-descriptions-item :span="2" label="操作系统">{{ record.os }}</a-descriptions-item>
<a-descriptions-item :span="3" label="类名称">{{ record.className }}</a-descriptions-item>
<a-descriptions-item :span="3" label="返回结果">{{ record.result }}</a-descriptions-item>
<a-descriptions-item :span="3" label="请求参数">{{ record.param }}</a-descriptions-item>
<a-descriptions-item :span="3" label="具体消息">{{ record.message }}</a-descriptions-item>
</a-descriptions>
</yo-table>
</a-card>
<br />
</container>
</template>
<script>
export default {
data() {
return {
query: {},
columns: [
{
title: '日志名称',
dataIndex: 'name',
sorter: true,
},
{
title: '操作类型',
dataIndex: 'opType',
scopedSlots: { customRender: 'opType' },
sorter: true,
},
{
title: '是否成功',
dataIndex: 'success',
scopedSlots: { customRender: 'success' },
sorter: true,
},
{
title: 'ip',
dataIndex: 'ip',
sorter: true,
},
{
title: '请求地址',
dataIndex: 'url',
scopedSlots: { customRender: 'url' },
sorter: true,
},
{
title: '操作时间',
dataIndex: 'opTime',
scopedSlots: { customRender: 'opTime' },
sorter: true,
},
{
title: '操作人',
dataIndex: 'account',
sorter: true,
},
],
codes: {
opTypeDict: [],
},
};
},
created() {
this.onLoadCodes();
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
const query = this.$_.cloneDeep(this.query);
if (query.dates && query.dates.length) {
query.searchBeginTime = this.$moment(query.dates[0]).format('YYYY-MM-DD HH:mm:ss');
query.searchEndTime = this.$moment(query.dates[1]).format('YYYY-MM-DD HH:mm:ss');
delete query.dates;
}
return this.$api
.sysOpLogPage({
...params,
...query,
})
.then((res) => {
return res.data;
});
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 加载字典数据时的必要方法
*/
onLoadCodes() {
this.$api.$queue([this.$api.sysDictTypeDropDownAwait({ code: 'op_type' })]).then(([commonStatus]) => {
this.codes.opTypeDict = commonStatus.data;
});
},
bindCodeValue(code, name) {
const c = this.codes[name].find((p) => p.code == code);
if (c) {
return c.value;
}
return null;
},
onOpLogClear() {
this.$refs.table.onLoading();
this.$api
.sysOpLogDelete()
.then((res) => {
if (res.success) {
this.$message.success('清空成功');
this.onReloadData();
} else {
this.$message.error('清空失败:' + res.message);
}
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
},
};
</script>

View File

@@ -1,205 +1,205 @@
<template>
<container>
<br />
<a-alert closable type="error">
<template slot="message">页面刷新的时候也会有保存登录日志但是没有ip显示</template>
</a-alert>
<br />
<a-card :bordered="false">
<Auth auth="sysVisLog: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-select
:style="{ width: '170px' }"
allow-clear
placeholder="请选择访问类型"
v-model="query.visType"
>
<a-select-option
:key="item.code"
:value="item.code"
v-for="item in codes.visTypeDict"
>{{ item.value }}</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="是否成功">
<a-select
:style="{ width: '170px' }"
allow-clear
placeholder="请选择是否成功"
v-model="query.success"
>
<a-select-option value="true"></a-select-option>
<a-select-option value="false"></a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="操作时间">
<a-range-picker
:show-time="{
hideDisabledOptions: true,
defaultValue: [$moment('00:00:00', 'HH:mm:ss'), $moment('23:59:59', 'HH:mm:ss')],
}"
format="YYYY-MM-DD HH:mm:ss"
v-model="query.dates"
/>
</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="sysVisLog:delete" slot="operator">
<div class="yo-action-bar--actions">
<a-popconfirm @confirm="onVisLogClear" placement="bottomLeft" title="是否确认清空日志">
<a-button>清空日志</a-button>
</a-popconfirm>
</div>
</Auth>
<span slot="visType" slot-scope="text">{{ bindCodeValue(text, 'visTypeDict') }}</span>
<span slot="success" slot-scope="text">{{ text ? '是' : '否' }}</span>
<a-descriptions bordered size="small" slot="expandedRowRender" slot-scope="record">
<a-descriptions-item :span="3" label="具体消息">{{ record.message }}</a-descriptions-item>
</a-descriptions>
</yo-table>
</a-card>
<br />
</container>
</template>
<script>
export default {
data() {
return {
query: {},
columns: [
{
title: '日志名称',
dataIndex: 'name',
sorter: true,
},
{
title: '访问类型',
dataIndex: 'visType',
scopedSlots: { customRender: 'visType' },
sorter: true,
},
{
title: '是否成功',
dataIndex: 'success',
scopedSlots: { customRender: 'success' },
sorter: true,
},
{
title: 'ip',
dataIndex: 'ip',
sorter: true,
},
{
title: '浏览器',
dataIndex: 'browser',
sorter: true,
},
{
title: '访问时间',
dataIndex: 'visTime',
scopedSlots: { customRender: 'visTime' },
sorter: true,
},
{
title: '访问人',
dataIndex: 'account',
sorter: true,
},
],
codes: {
visTypeDict: [],
},
};
},
created() {
this.onLoadCodes();
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
const query = this.$_.cloneDeep(this.query);
if (query.dates && query.dates.length) {
query.searchBeginTime = this.$moment(query.dates[0]).format('YYYY-MM-DD HH:mm:ss');
query.searchEndTime = this.$moment(query.dates[1]).format('YYYY-MM-DD HH:mm:ss');
delete query.dates;
}
return this.$api
.sysVisLogPage({
...params,
...query,
})
.then((res) => {
return res.data;
});
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 加载字典数据时的必要方法
*/
onLoadCodes() {
this.$api.$queue([this.$api.sysDictTypeDropDownAwait({ code: 'vis_type' })]).then(([commonStatus]) => {
this.codes.visTypeDict = commonStatus.data;
});
},
bindCodeValue(code, name) {
const c = this.codes[name].find((p) => p.code == code);
if (c) {
return c.value;
}
return null;
},
onVisLogClear() {
this.$refs.table.onLoading();
this.$api
.sysVisLogDelete()
.then((res) => {
if (res.success) {
this.$message.success('清空成功');
this.onReloadData();
} else {
this.$message.error('清空失败:' + res.message);
}
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
},
};
<template>
<container>
<br />
<a-alert closable type="error">
<template slot="message">页面刷新的时候也会有保存登录日志但是没有ip显示</template>
</a-alert>
<br />
<a-card :bordered="false">
<Auth auth="sysVisLog: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-select
:style="{ width: '170px' }"
allow-clear
placeholder="请选择访问类型"
v-model="query.visType"
>
<a-select-option
:key="item.code"
:value="item.code"
v-for="item in codes.visTypeDict"
>{{ item.value }}</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="是否成功">
<a-select
:style="{ width: '170px' }"
allow-clear
placeholder="请选择是否成功"
v-model="query.success"
>
<a-select-option value="true"></a-select-option>
<a-select-option value="false"></a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="操作时间">
<a-range-picker
:show-time="{
hideDisabledOptions: true,
defaultValue: [$moment('00:00:00', 'HH:mm:ss'), $moment('23:59:59', 'HH:mm:ss')],
}"
format="YYYY-MM-DD HH:mm:ss"
v-model="query.dates"
/>
</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="sysVisLog:delete" slot="operator">
<div class="yo-action-bar--actions">
<a-popconfirm @confirm="onVisLogClear" placement="bottomLeft" title="是否确认清空日志">
<a-button>清空日志</a-button>
</a-popconfirm>
</div>
</Auth>
<span slot="visType" slot-scope="text">{{ bindCodeValue(text, 'visTypeDict') }}</span>
<span slot="success" slot-scope="text">{{ text ? '是' : '否' }}</span>
<a-descriptions bordered size="small" slot="expandedRowRender" slot-scope="record">
<a-descriptions-item :span="3" label="具体消息">{{ record.message }}</a-descriptions-item>
</a-descriptions>
</yo-table>
</a-card>
<br />
</container>
</template>
<script>
export default {
data() {
return {
query: {},
columns: [
{
title: '日志名称',
dataIndex: 'name',
sorter: true,
},
{
title: '访问类型',
dataIndex: 'visType',
scopedSlots: { customRender: 'visType' },
sorter: true,
},
{
title: '是否成功',
dataIndex: 'success',
scopedSlots: { customRender: 'success' },
sorter: true,
},
{
title: 'ip',
dataIndex: 'ip',
sorter: true,
},
{
title: '浏览器',
dataIndex: 'browser',
sorter: true,
},
{
title: '访问时间',
dataIndex: 'visTime',
scopedSlots: { customRender: 'visTime' },
sorter: true,
},
{
title: '访问人',
dataIndex: 'account',
sorter: true,
},
],
codes: {
visTypeDict: [],
},
};
},
created() {
this.onLoadCodes();
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
const query = this.$_.cloneDeep(this.query);
if (query.dates && query.dates.length) {
query.searchBeginTime = this.$moment(query.dates[0]).format('YYYY-MM-DD HH:mm:ss');
query.searchEndTime = this.$moment(query.dates[1]).format('YYYY-MM-DD HH:mm:ss');
delete query.dates;
}
return this.$api
.sysVisLogPage({
...params,
...query,
})
.then((res) => {
return res.data;
});
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 加载字典数据时的必要方法
*/
onLoadCodes() {
this.$api.$queue([this.$api.sysDictTypeDropDownAwait({ code: 'vis_type' })]).then(([commonStatus]) => {
this.codes.visTypeDict = commonStatus.data;
});
},
bindCodeValue(code, name) {
const c = this.codes[name].find((p) => p.code == code);
if (c) {
return c.value;
}
return null;
},
onVisLogClear() {
this.$refs.table.onLoading();
this.$api
.sysVisLogDelete()
.then((res) => {
if (res.success) {
this.$message.success('清空成功');
this.onReloadData();
} else {
this.$message.error('清空失败:' + res.message);
}
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
},
};
</script>

View File

@@ -1,340 +1,340 @@
<template>
<a-form-model :model="form" :rules="rules" class="yo-form" ref="form">
<a-spin :spinning="loading">
<a-icon slot="indicator" spin type="loading" />
<a-alert type="warning">
<template slot="message">当前没有写入字段[重定向]</template>
</a-alert>
<br />
<div class="yo-form-group">
<!-- 表单控件 -->
<h3>基本信息</h3>
<div class="yo-form-group">
<a-form-model-item label="菜单类型" prop="type">
<template slot="help">
目录默认添加在顶级
<br />菜单
<br />按钮
</template>
<a-radio-group @change="onTypeChange" v-model="form.type">
<a-radio-button
:key="type.code"
:value="type.code"
v-for="type in codes.menuType"
>{{ type.value }}</a-radio-button>
</a-radio-group>
</a-form-model-item>
<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="application">
<a-select @change="onLoadMenuTree" placeholder="请选择所属应用" v-model="form.application">
<a-select-option
:key="item.code"
:value="item.code"
v-for="item in appList"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-model-item>
<!-- 父级菜单只有[目录]不可用 -->
<a-form-model-item label="父级菜单" prop="pid" v-if="form.type != 0">
<a-tree-select
:dropdown-style="{ maxHeight: '300px', overflow: 'auto' }"
:tree-data="parentTreeData"
placeholder="请选择父级菜单"
v-model="form.pid"
/>
</a-form-model-item>
</div>
<h3>扩展信息</h3>
<div class="yo-form-group">
<a-form-model-item label="打开方式" prop="openType" v-if="form.type == 1">
<a-radio-group @change="onOpenTypeChange" v-model="form.openType">
<a-radio-button
:key="type.code"
:value="type.code"
v-for="type in codes.openType"
>{{ type.value }}</a-radio-button>
</a-radio-group>
</a-form-model-item>
<!-- 前端组件只有[菜单][组件]可用 -->
<a-form-model-item
label="前端组件"
prop="component"
v-if="form.type == 1 && form.openType == 1"
v-show="form.type == 1 && form.openType == 1"
>
<a-input placeholder="请输入前端组件" v-model="form.component" />
</a-form-model-item>
<!-- 内链地址只有[菜单][内链]可用 -->
<a-form-model-item label="内链地址" prop="router" v-if="form.type == 1 && form.openType == 2">
<a-input placeholder="请输入内链地址" v-model="form.router" />
</a-form-model-item>
<!-- 外链地址只有[菜单][外链]可用 -->
<a-form-model-item label="内外链地址" prop="link" v-if="form.type == 1 && form.openType == 3">
<a-input placeholder="请输入内外链地址" v-model="form.link" />
</a-form-model-item>
<!-- <a-form-model-item label="权重" prop="weight">
<template slot="help">
系统权重菜单可分配给任何角色
<br />业务权重菜单对超级管理员不可见
</template>
<a-radio-group v-model="form.weight">
<a-radio-button
:key="type.code"
:value="type.code"
v-for="type in codes.menuWerght"
>{{type.value}}</a-radio-button>
</a-radio-group>
</a-form-model-item>-->
<a-form-model-item label="权限标识" prop="permission" v-if="form.type == 2">
<a-input placeholder="请输入权限标识" v-model="form.permission" />
</a-form-model-item>
<a-form-model-item label="关联上级菜单显示" prop="visibleParent" v-if="form.type == 2">
<a-switch v-model="form.visibleParent" />
</a-form-model-item>
<a-form-model-item label="可见性">
<a-switch v-model="form.visible" />
</a-form-model-item>
<!-- 图标只有[按钮]不可用 -->
<a-form-model-item label="图标" v-if="form.type != 2">
<a-input :disabled="true" placeholder="请选择图标" v-model="form.icon">
<a-icon :type="form.icon" slot="addonBefore" v-if="form.icon" />
<a-icon @click="onOpenSelectIcon" slot="addonAfter" type="setting" />
</a-input>
</a-form-model-item>
<a-form-model-item label="排序">
<a-input-number
:max="1000"
:min="0"
class="w-100-p"
placeholder="请输入排序"
v-model="form.sort"
/>
</a-form-model-item>
<a-form-model-item label="备注信息">
<a-textarea placeholder="请输入备注信息" v-model="form.remark" />
</a-form-model-item>
</div>
</div>
</a-spin>
<yo-icon-selector ref="icon-selector" v-model="form.icon" />
</a-form-model>
</template>
<script>
import { EMPTY_ID } from '@/util/global';
import YoIconSelector from '@/components/yoIconSelector';
const defaultValue = {
type: '1',
openType: '1',
weight: '1',
visibleParent: false,
visible: true,
sort: 100,
};
export default {
components: {
YoIconSelector,
},
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
type: [{ required: true, message: '请选择菜单类型' }],
name: [{ required: true, message: '请输入名称' }],
code: [{ required: true, message: '请输入唯一编码' }],
application: [{ required: true, message: '请选择所属应用' }],
pid: [{ required: true, message: '请选择父级' }],
component: [{ required: true, message: '请输入前端组件' }],
router: [{ required: true, message: '请输入内链地址' }],
link: [{ required: true, message: '请输入外链地址' }],
permission: [{ required: true, message: '请输入权限标识' }],
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/** ... */
appList: [],
codes: {
menuType: [],
menuWerght: [],
openType: [],
},
parentTreeData: [],
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
/** 将默认数据覆盖到form */
let form = {
...defaultValue,
...params.record,
/** 在此处添加默认数据转换 */
/** ... */
};
if (params.isParent) {
form.pid = params.parent.id;
form.application = params.parent.application;
}
this.form = this.$_.cloneDeep(form);
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
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();
this.form = {
...defaultValue,
};
/** 在这里可以初始化当前组件中其他属性 */
/** ... */
this.parentTreeData = [];
}, 300);
},
/**
* 必要方法
* 加载当前表单中所需要的异步数据
*/
async onInit(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/** ...BEGIN */
this.codes = await this.onLoadCodes();
this.appList = await this.onLoadSysApplist();
if (params.isParent) {
await this.onLoadMenuTree(params.parent.application);
} else if (params.record) {
await this.onLoadMenuTree(params.record.application);
}
/** ...END */
this.loading = false;
},
/** 当前组件的其他方法 */
/** ... */
onLoadCodes() {
return this.$api
.$queue([
this.$api.sysDictTypeDropDownAwait({ code: 'menu_type' }),
this.$api.sysDictTypeDropDownAwait({ code: 'menu_weight' }),
this.$api.sysDictTypeDropDownAwait({ code: 'open_type' }),
])
.then(([menuType, menuWerght, openType]) => {
return {
menuType: menuType.data,
menuWerght: menuWerght.data,
openType: openType.data,
};
});
},
onLoadSysApplist() {
return this.$api.getAppList().then(({ data }) => {
return data;
});
},
onLoadMenuTree(app) {
return this.$api.getMenuTree({ application: app }).then(({ data }) => {
this.parentTreeData = [
{
id: EMPTY_ID,
parentId: undefined,
title: '顶级',
value: EMPTY_ID,
pid: undefined,
children: data,
},
];
});
},
onTypeChangeGroup() {
const { type, openType } = this.form;
if (type == 1 && openType == 2) {
this.$set(this.form, 'component', 'iframe');
} else {
this.$set(this.form, 'component', '');
}
},
onTypeChange() {
this.onTypeChangeGroup();
if (this.form.type == 0 || this.form.type == 2) {
this.form.openType = '0';
} else {
this.form.openType = '1';
}
},
onOpenTypeChange() {
this.onTypeChangeGroup();
},
onOpenSelectIcon() {
this.$refs['icon-selector'].onOpen(this.form.icon);
},
onSelectIcon(icon) {
this.$set(this.form, 'icon', icon);
},
},
};
<template>
<a-form-model :model="form" :rules="rules" class="yo-form" ref="form">
<a-spin :spinning="loading">
<a-icon slot="indicator" spin type="loading" />
<a-alert type="warning">
<template slot="message">当前没有写入字段[重定向]</template>
</a-alert>
<br />
<div class="yo-form-group">
<!-- 表单控件 -->
<h3>基本信息</h3>
<div class="yo-form-group">
<a-form-model-item label="菜单类型" prop="type">
<template slot="help">
目录默认添加在顶级
<br />菜单
<br />按钮
</template>
<a-radio-group @change="onTypeChange" v-model="form.type">
<a-radio-button
:key="type.code"
:value="type.code"
v-for="type in codes.menuType"
>{{ type.value }}</a-radio-button>
</a-radio-group>
</a-form-model-item>
<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="application">
<a-select @change="onLoadMenuTree" placeholder="请选择所属应用" v-model="form.application">
<a-select-option
:key="item.code"
:value="item.code"
v-for="item in appList"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-model-item>
<!-- 父级菜单只有[目录]不可用 -->
<a-form-model-item label="父级菜单" prop="pid" v-if="form.type != 0">
<a-tree-select
:dropdown-style="{ maxHeight: '300px', overflow: 'auto' }"
:tree-data="parentTreeData"
placeholder="请选择父级菜单"
v-model="form.pid"
/>
</a-form-model-item>
</div>
<h3>扩展信息</h3>
<div class="yo-form-group">
<a-form-model-item label="打开方式" prop="openType" v-if="form.type == 1">
<a-radio-group @change="onOpenTypeChange" v-model="form.openType">
<a-radio-button
:key="type.code"
:value="type.code"
v-for="type in codes.openType"
>{{ type.value }}</a-radio-button>
</a-radio-group>
</a-form-model-item>
<!-- 前端组件只有[菜单][组件]可用 -->
<a-form-model-item
label="前端组件"
prop="component"
v-if="form.type == 1 && form.openType == 1"
v-show="form.type == 1 && form.openType == 1"
>
<a-input placeholder="请输入前端组件" v-model="form.component" />
</a-form-model-item>
<!-- 内链地址只有[菜单][内链]可用 -->
<a-form-model-item label="内链地址" prop="router" v-if="form.type == 1 && form.openType == 2">
<a-input placeholder="请输入内链地址" v-model="form.router" />
</a-form-model-item>
<!-- 外链地址只有[菜单][外链]可用 -->
<a-form-model-item label="内外链地址" prop="link" v-if="form.type == 1 && form.openType == 3">
<a-input placeholder="请输入内外链地址" v-model="form.link" />
</a-form-model-item>
<!-- <a-form-model-item label="权重" prop="weight">
<template slot="help">
系统权重菜单可分配给任何角色
<br />业务权重菜单对超级管理员不可见
</template>
<a-radio-group v-model="form.weight">
<a-radio-button
:key="type.code"
:value="type.code"
v-for="type in codes.menuWerght"
>{{type.value}}</a-radio-button>
</a-radio-group>
</a-form-model-item>-->
<a-form-model-item label="权限标识" prop="permission" v-if="form.type == 2">
<a-input placeholder="请输入权限标识" v-model="form.permission" />
</a-form-model-item>
<a-form-model-item label="关联上级菜单显示" prop="visibleParent" v-if="form.type == 2">
<a-switch v-model="form.visibleParent" />
</a-form-model-item>
<a-form-model-item label="可见性">
<a-switch v-model="form.visible" />
</a-form-model-item>
<!-- 图标只有[按钮]不可用 -->
<a-form-model-item label="图标" v-if="form.type != 2">
<a-input :disabled="true" placeholder="请选择图标" v-model="form.icon">
<a-icon :type="form.icon" slot="addonBefore" v-if="form.icon" />
<a-icon @click="onOpenSelectIcon" slot="addonAfter" type="setting" />
</a-input>
</a-form-model-item>
<a-form-model-item label="排序">
<a-input-number
:max="1000"
:min="0"
class="w-100-p"
placeholder="请输入排序"
v-model="form.sort"
/>
</a-form-model-item>
<a-form-model-item label="备注信息">
<a-textarea placeholder="请输入备注信息" v-model="form.remark" />
</a-form-model-item>
</div>
</div>
</a-spin>
<yo-icon-selector ref="icon-selector" v-model="form.icon" />
</a-form-model>
</template>
<script>
import { EMPTY_ID } from '@/util/global';
import YoIconSelector from '@/components/yoIconSelector';
const defaultValue = {
type: '1',
openType: '1',
weight: '1',
visibleParent: false,
visible: true,
sort: 100,
};
export default {
components: {
YoIconSelector,
},
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
type: [{ required: true, message: '请选择菜单类型' }],
name: [{ required: true, message: '请输入名称' }],
code: [{ required: true, message: '请输入唯一编码' }],
application: [{ required: true, message: '请选择所属应用' }],
pid: [{ required: true, message: '请选择父级' }],
component: [{ required: true, message: '请输入前端组件' }],
router: [{ required: true, message: '请输入内链地址' }],
link: [{ required: true, message: '请输入外链地址' }],
permission: [{ required: true, message: '请输入权限标识' }],
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/** ... */
appList: [],
codes: {
menuType: [],
menuWerght: [],
openType: [],
},
parentTreeData: [],
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
/** 将默认数据覆盖到form */
let form = {
...defaultValue,
...params.record,
/** 在此处添加默认数据转换 */
/** ... */
};
if (params.isParent) {
form.pid = params.parent.id;
form.application = params.parent.application;
}
this.form = this.$_.cloneDeep(form);
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
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();
this.form = {
...defaultValue,
};
/** 在这里可以初始化当前组件中其他属性 */
/** ... */
this.parentTreeData = [];
}, 300);
},
/**
* 必要方法
* 加载当前表单中所需要的异步数据
*/
async onInit(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/** ...BEGIN */
this.codes = await this.onLoadCodes();
this.appList = await this.onLoadSysApplist();
if (params.isParent) {
await this.onLoadMenuTree(params.parent.application);
} else if (params.record) {
await this.onLoadMenuTree(params.record.application);
}
/** ...END */
this.loading = false;
},
/** 当前组件的其他方法 */
/** ... */
onLoadCodes() {
return this.$api
.$queue([
this.$api.sysDictTypeDropDownAwait({ code: 'menu_type' }),
this.$api.sysDictTypeDropDownAwait({ code: 'menu_weight' }),
this.$api.sysDictTypeDropDownAwait({ code: 'open_type' }),
])
.then(([menuType, menuWerght, openType]) => {
return {
menuType: menuType.data,
menuWerght: menuWerght.data,
openType: openType.data,
};
});
},
onLoadSysApplist() {
return this.$api.getAppList().then(({ data }) => {
return data;
});
},
onLoadMenuTree(app) {
return this.$api.getMenuTree({ application: app }).then(({ data }) => {
this.parentTreeData = [
{
id: EMPTY_ID,
parentId: undefined,
title: '顶级',
value: EMPTY_ID,
pid: undefined,
children: data,
},
];
});
},
onTypeChangeGroup() {
const { type, openType } = this.form;
if (type == 1 && openType == 2) {
this.$set(this.form, 'component', 'iframe');
} else {
this.$set(this.form, 'component', '');
}
},
onTypeChange() {
this.onTypeChangeGroup();
if (this.form.type == 0 || this.form.type == 2) {
this.form.openType = '0';
} else {
this.form.openType = '1';
}
},
onOpenTypeChange() {
this.onTypeChangeGroup();
},
onOpenSelectIcon() {
this.$refs['icon-selector'].onOpen(this.form.icon);
},
onSelectIcon(icon) {
this.$set(this.form, 'icon', icon);
},
},
};
</script>

View File

@@ -1,227 +1,227 @@
<template>
<container>
<br />
<a-alert type="error">
<template slot="message">按钮编辑关闭时调用resetFields报错,原因为visible=undefined</template>
</a-alert>
<br />
<a-card :bordered="false">
<yo-table :columns="columns" :load-data="loadData" ref="table">
<Auth auth="sysMenu:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增菜单</a-button>
</Auth>
<span slot="type" slot-scope="text">{{ bindCodeValue(text, 'menu_type') }}</span>
<span slot="icon" slot-scope="text">
<div v-if="text">
<a-icon :type="text" />
</div>
</span>
<span slot="action" slot-scope="text, record">
<yo-table-actions>
<Auth auth="sysMenu:add" v-if="record.type < 2">
<a @click="onOpen('add-form', record, true)">新增子菜单</a>
</Auth>
<Auth auth="sysMenu:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysMenu:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
</yo-table-actions>
</span>
</yo-table>
</a-card>
<yo-modal-form
:action="$api.sysMenuAdd"
:width="800"
@ok="onReloadData"
ref="add-form"
title="新增菜单"
type="drawer"
>
<form-body />
</yo-modal-form>
<yo-modal-form
:action="$api.sysMenuEdit"
:width="800"
@ok="onReloadData"
ref="edit-form"
title="编辑菜单"
type="drawer"
>
<form-body />
</yo-modal-form>
</container>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
data() {
return {
query: {},
columns: [
{
title: '菜单名称',
width: '220px',
dataIndex: 'name',
},
{
title: '菜单类型',
width: '100px',
dataIndex: 'type',
scopedSlots: { customRender: 'type' },
},
{
title: '图标',
width: '100px',
dataIndex: 'icon',
scopedSlots: { customRender: 'icon' },
},
{
title: '前端组件',
width: '220px',
dataIndex: 'component',
ellipsis: true,
},
{
title: '权限标识',
width: '220px',
dataIndex: 'permission',
ellipsis: true,
},
{
title: '排序',
width: '100px',
dataIndex: 'sort',
},
],
codes: [
{
code: 'menu_type',
values: [],
},
{
code: 'menu_weight',
values: [],
},
{
code: 'open_type',
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
.getMenuList({
...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.sysDictTypeDropDownAwait({ code: 'menu_type' }),
this.$api.sysDictTypeDropDownAwait({ code: 'menu_weight' }),
this.$api.sysDictTypeDropDownAwait({ code: 'open_type' }),
])
.then(([menuType, menuWerght, openType]) => {
this.codes.find((p) => p.code === 'menu_type').values = menuType.data;
this.codes.find((p) => p.code === 'menu_weight').values = menuWerght.data;
this.codes.find((p) => p.code === 'open_type').values = openType.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, isParent = false) {
const params = isParent
? {
parent: record,
isParent,
}
: {
record,
isParent,
};
this.$refs[formName].onOpen(params);
},
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
this.$refs.table.onLoaded();
},
onDelete(record) {
this.$refs.table.onLoading();
this.$api.sysMenuDelete(record).then(({ success }) => {
this.onResult(success, '删除成功');
});
},
},
};
<template>
<container>
<br />
<a-alert type="error">
<template slot="message">按钮编辑关闭时调用resetFields报错,原因为visible=undefined</template>
</a-alert>
<br />
<a-card :bordered="false">
<yo-table :columns="columns" :load-data="loadData" ref="table">
<Auth auth="sysMenu:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增菜单</a-button>
</Auth>
<span slot="type" slot-scope="text">{{ bindCodeValue(text, 'menu_type') }}</span>
<span slot="icon" slot-scope="text">
<div v-if="text">
<a-icon :type="text" />
</div>
</span>
<span slot="action" slot-scope="text, record">
<yo-table-actions>
<Auth auth="sysMenu:add" v-if="record.type < 2">
<a @click="onOpen('add-form', record, true)">新增子菜单</a>
</Auth>
<Auth auth="sysMenu:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysMenu:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
</yo-table-actions>
</span>
</yo-table>
</a-card>
<yo-modal-form
:action="$api.sysMenuAdd"
:width="800"
@ok="onReloadData"
ref="add-form"
title="新增菜单"
type="drawer"
>
<form-body />
</yo-modal-form>
<yo-modal-form
:action="$api.sysMenuEdit"
:width="800"
@ok="onReloadData"
ref="edit-form"
title="编辑菜单"
type="drawer"
>
<form-body />
</yo-modal-form>
</container>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
data() {
return {
query: {},
columns: [
{
title: '菜单名称',
width: '220px',
dataIndex: 'name',
},
{
title: '菜单类型',
width: '100px',
dataIndex: 'type',
scopedSlots: { customRender: 'type' },
},
{
title: '图标',
width: '100px',
dataIndex: 'icon',
scopedSlots: { customRender: 'icon' },
},
{
title: '前端组件',
width: '220px',
dataIndex: 'component',
ellipsis: true,
},
{
title: '权限标识',
width: '220px',
dataIndex: 'permission',
ellipsis: true,
},
{
title: '排序',
width: '100px',
dataIndex: 'sort',
},
],
codes: [
{
code: 'menu_type',
values: [],
},
{
code: 'menu_weight',
values: [],
},
{
code: 'open_type',
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
.getMenuList({
...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.sysDictTypeDropDownAwait({ code: 'menu_type' }),
this.$api.sysDictTypeDropDownAwait({ code: 'menu_weight' }),
this.$api.sysDictTypeDropDownAwait({ code: 'open_type' }),
])
.then(([menuType, menuWerght, openType]) => {
this.codes.find((p) => p.code === 'menu_type').values = menuType.data;
this.codes.find((p) => p.code === 'menu_weight').values = menuWerght.data;
this.codes.find((p) => p.code === 'open_type').values = openType.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, isParent = false) {
const params = isParent
? {
parent: record,
isParent,
}
: {
record,
isParent,
};
this.$refs[formName].onOpen(params);
},
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
this.$refs.table.onLoaded();
},
onDelete(record) {
this.$refs.table.onLoading();
this.$api.sysMenuDelete(record).then(({ success }) => {
this.onResult(success, '删除成功');
});
},
},
};
</script>

View File

@@ -1,219 +1,219 @@
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<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">
<!-- 表单控件 -->
<!-- ... -->
<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="pid">
<a-tree-select
:dropdown-style="{ maxHeight: '300px', overflow: 'auto' }"
:tree-data="orgData"
placeholder="请选择上级机构"
tree-default-expand-all
v-model="form.pid"
/>
</a-form-model-item>
<a-form-model-item label="所属区域" prop="areaCode">
<a-cascader
:field-names="{ label: 'name', value: 'code', children: 'children' }"
:options="areaData"
change-on-select
placeholder="请选择所属区域"
v-model="form.areaCode"
/>
</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>
<a-form-model-item label="备注" prop="remark">
<a-textarea placeholder="请输入备注" v-model="form.remark" />
</a-form-model-item>
</div>
</a-spin>
</a-form-model>
</template>
<script>
import { EMPTY_ID } from '@/util/global';
/* 表单内容默认值 */
const defaultForm = {
/* ... */
pid: undefined,
sort: 100,
};
export default {
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
name: [{ required: true, message: '请输入机构名称' }],
code: [{ required: true, message: '请输入唯一编码' }],
pid: [{ required: true, message: '请选择上级机构' }],
areaCode: [{ required: true, message: '请选择所属区域' }],
/* ... */
},
/** 加载异步数据状态 */
loading: false,
orgData: [],
areaData: [],
/** 其他成员属性 */
/* ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
// 从字符串areaCode查找到整个层级
const areaCode = [];
const findCode = (data, level) => {
level = level || 0;
for (let i = 0; i < data.length; i++) {
const item = data[i];
areaCode[level] = item.code;
if (item.code === params.record.areaCode) {
areaCode.length = level + 1;
return true;
}
if (item.children && item.children.length) {
const found = findCode(item.children, level + 1);
if (found) {
return true;
}
}
}
};
if (params.record && params.record.areaCode) {
findCode(this.areaData);
}
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
pid: params.orgId,
...params.record,
/** 在此处添加其他默认数据转换 */
/* ... */
areaCode,
});
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
onGetData() {
return new Promise((reslove, reject) => {
this.$refs.form.validate((valid) => {
if (valid) {
const record = this.$_.cloneDeep(this.form);
record.areaCode = record.areaCode[record.areaCode.length - 1];
/** 验证通过后可以对数据进行转换得到想要提交的格式 */
/* ... */
reslove(record);
} else {
reject();
}
});
});
},
/**
* 必要的方法
* 在外部窗口进行保存时调用表单验证
*/
onValidate(callback) {
this.$refs.form.validate(callback);
},
/**
* 必要的方法
* 在外部窗口关闭或重置时对表单验证进行初始化
*/
onResetFields() {
setTimeout(() => {
this.$refs.form.resetFields();
/** 在这里可以初始化当前组件中其他属性 */
/* ... */
}, 300);
},
/**
* 必要方法
* 加载当前表单中所需要的异步数据
*/
async onInit(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
await this.onLoadOrgData();
await this.onLoadAreaData();
/* ... */
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
onLoadOrgData() {
return this.$api.getOrgTree().then(({ data }) => {
this.orgData = [
{
id: EMPTY_ID,
parentId: undefined,
title: '顶级',
value: EMPTY_ID,
pid: undefined,
children: data,
},
];
});
},
onLoadAreaData() {
return this.$api.getAreaTree().then(({ data }) => {
// 为了防止出现空的层级选择,删除所有空children节点
const clearChiildren = (data) => {
data.forEach((item) => {
if (item.children && item.children.length) {
clearChiildren(item.children);
} else {
delete item.children;
}
});
};
clearChiildren(data);
this.areaData = data;
});
},
},
};
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<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">
<!-- 表单控件 -->
<!-- ... -->
<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="pid">
<a-tree-select
:dropdown-style="{ maxHeight: '300px', overflow: 'auto' }"
:tree-data="orgData"
placeholder="请选择上级机构"
tree-default-expand-all
v-model="form.pid"
/>
</a-form-model-item>
<a-form-model-item label="所属区域" prop="areaCode">
<a-cascader
:field-names="{ label: 'name', value: 'code', children: 'children' }"
:options="areaData"
change-on-select
placeholder="请选择所属区域"
v-model="form.areaCode"
/>
</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>
<a-form-model-item label="备注" prop="remark">
<a-textarea placeholder="请输入备注" v-model="form.remark" />
</a-form-model-item>
</div>
</a-spin>
</a-form-model>
</template>
<script>
import { EMPTY_ID } from '@/util/global';
/* 表单内容默认值 */
const defaultForm = {
/* ... */
pid: undefined,
sort: 100,
};
export default {
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
name: [{ required: true, message: '请输入机构名称' }],
code: [{ required: true, message: '请输入唯一编码' }],
pid: [{ required: true, message: '请选择上级机构' }],
areaCode: [{ required: true, message: '请选择所属区域' }],
/* ... */
},
/** 加载异步数据状态 */
loading: false,
orgData: [],
areaData: [],
/** 其他成员属性 */
/* ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
// 从字符串areaCode查找到整个层级
const areaCode = [];
const findCode = (data, level) => {
level = level || 0;
for (let i = 0; i < data.length; i++) {
const item = data[i];
areaCode[level] = item.code;
if (item.code === params.record.areaCode) {
areaCode.length = level + 1;
return true;
}
if (item.children && item.children.length) {
const found = findCode(item.children, level + 1);
if (found) {
return true;
}
}
}
};
if (params.record && params.record.areaCode) {
findCode(this.areaData);
}
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
pid: params.orgId,
...params.record,
/** 在此处添加其他默认数据转换 */
/* ... */
areaCode,
});
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
onGetData() {
return new Promise((reslove, reject) => {
this.$refs.form.validate((valid) => {
if (valid) {
const record = this.$_.cloneDeep(this.form);
record.areaCode = record.areaCode[record.areaCode.length - 1];
/** 验证通过后可以对数据进行转换得到想要提交的格式 */
/* ... */
reslove(record);
} else {
reject();
}
});
});
},
/**
* 必要的方法
* 在外部窗口进行保存时调用表单验证
*/
onValidate(callback) {
this.$refs.form.validate(callback);
},
/**
* 必要的方法
* 在外部窗口关闭或重置时对表单验证进行初始化
*/
onResetFields() {
setTimeout(() => {
this.$refs.form.resetFields();
/** 在这里可以初始化当前组件中其他属性 */
/* ... */
}, 300);
},
/**
* 必要方法
* 加载当前表单中所需要的异步数据
*/
async onInit(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
await this.onLoadOrgData();
await this.onLoadAreaData();
/* ... */
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
onLoadOrgData() {
return this.$api.getOrgTree().then(({ data }) => {
this.orgData = [
{
id: EMPTY_ID,
parentId: undefined,
title: '顶级',
value: EMPTY_ID,
pid: undefined,
children: data,
},
];
});
},
onLoadAreaData() {
return this.$api.getAreaTree().then(({ data }) => {
// 为了防止出现空的层级选择,删除所有空children节点
const clearChiildren = (data) => {
data.forEach((item) => {
if (item.children && item.children.length) {
clearChiildren(item.children);
} else {
delete item.children;
}
});
};
clearChiildren(data);
this.areaData = data;
});
},
},
};
</script>

View File

@@ -1,278 +1,278 @@
<template>
<!--
普通查询表格
v 1.2
2021-04-30
Lufthafen
-->
<yo-tree-layout
:load-data="loadTreeData"
@select="onSelect"
default-expanded-keys
ref="tree-layout"
>
<container>
<br />
<a-card :bordered="false">
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="sysOrg:page" slot="query">
<!-- 此处添加查询表单控件 -->
<!-- ... -->
<a-form-model-item label="机构名称">
<a-input allow-clear placeholder="请输入机构名称" v-model="query.name" />
</a-form-model-item>
</Auth>
<Auth auth="sysOrg: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="sysOrg:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysOrg:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
<!-- 可在此处添加其他操作控件 -->
<!-- ... -->
</yo-table-actions>
</span>
</yo-table>
</a-card>
<!-- 新增表单 -->
<yo-modal-form :action="$api[api.add]" :title="'新增' + name" @ok="onReloadData" ref="add-form">
<form-body />
</yo-modal-form>
<!-- 编辑表单 -->
<yo-modal-form
:action="$api[api.edit]"
:title="'编辑' + name"
@ok="onReloadData"
ref="edit-form"
>
<form-body />
</yo-modal-form>
</container>
</yo-tree-layout>
</template>
<script>
import FormBody from './form';
import YoTreeLayout from '@/components/yoTreeLayout';
/* 在此管理整个页面需要的接口名称 */
const api = {
page: 'getOrgPage',
add: 'sysOrgAdd',
edit: 'sysOrgEdit',
delete: 'sysOrgDelete',
/* ... */
};
export default {
components: {
FormBody,
YoTreeLayout,
},
data() {
return {
api,
name: '机构',
/* 查询条件 */
query: {},
/* 表格字段 */
columns: [
{
title: '机构名称',
width: '400px',
dataIndex: 'name',
sorter: true,
},
{
title: '唯一编码',
width: '200px',
dataIndex: 'code',
sorter: true,
},
{
title: '排序',
width: '80px',
dataIndex: 'sort',
sorter: true,
},
{
title: '备注',
dataIndex: 'remark',
sorter: true,
},
],
/* 字典编码储存格式 */
// 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[api.page]({
...params,
...this.query,
}).then((res) => {
return res.data;
});
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 有查询功能时的必要方法
* 重置查询条件
*/
onResetQuery() {
/** 在这里重置查询条件时,可对特殊的字段做保留处理 */
Object.keys(this.query).forEach((p) => {
if (p !== 'pid') {
this.query[p] = undefined;
}
});
this.onQuery();
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 必要方法
* 加载字典数据
* 如果不需要获取相应的字典数据,此方法内容可空
*/
//onLoadCodes() {
// this.$api
// .$queue([
// this.$api.sysDictTypeDropDownAwait({ code: 'code1' }),
// this.$api.sysDictTypeDropDownAwait({ 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,
/* 按需添加其他参数 */
/* ... */
orgId: this.query['pid'],
});
},
/**
* 必要方法
* 可以用做一系列操作的公共回调,此方法中会重新加载当前列表
*/
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api[api.delete](record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
/**
* 树形选择界面必要的方法
* 传给yo-table-layout以示意数据接口
*/
loadTreeData() {
return this.$api.getOrgTree().then((res) => {
return res.data;
});
},
onSelect([id]) {
this.query = {
pid: id,
};
this.onQuery();
},
},
};
<template>
<!--
普通查询表格
v 1.2
2021-04-30
Lufthafen
-->
<yo-tree-layout
:load-data="loadTreeData"
@select="onSelect"
default-expanded-keys
ref="tree-layout"
>
<container>
<br />
<a-card :bordered="false">
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="sysOrg:page" slot="query">
<!-- 此处添加查询表单控件 -->
<!-- ... -->
<a-form-model-item label="机构名称">
<a-input allow-clear placeholder="请输入机构名称" v-model="query.name" />
</a-form-model-item>
</Auth>
<Auth auth="sysOrg: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="sysOrg:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysOrg:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
<!-- 可在此处添加其他操作控件 -->
<!-- ... -->
</yo-table-actions>
</span>
</yo-table>
</a-card>
<!-- 新增表单 -->
<yo-modal-form :action="$api[api.add]" :title="'新增' + name" @ok="onReloadData" ref="add-form">
<form-body />
</yo-modal-form>
<!-- 编辑表单 -->
<yo-modal-form
:action="$api[api.edit]"
:title="'编辑' + name"
@ok="onReloadData"
ref="edit-form"
>
<form-body />
</yo-modal-form>
</container>
</yo-tree-layout>
</template>
<script>
import FormBody from './form';
import YoTreeLayout from '@/components/yoTreeLayout';
/* 在此管理整个页面需要的接口名称 */
const api = {
page: 'getOrgPage',
add: 'sysOrgAdd',
edit: 'sysOrgEdit',
delete: 'sysOrgDelete',
/* ... */
};
export default {
components: {
FormBody,
YoTreeLayout,
},
data() {
return {
api,
name: '机构',
/* 查询条件 */
query: {},
/* 表格字段 */
columns: [
{
title: '机构名称',
width: '400px',
dataIndex: 'name',
sorter: true,
},
{
title: '唯一编码',
width: '200px',
dataIndex: 'code',
sorter: true,
},
{
title: '排序',
width: '80px',
dataIndex: 'sort',
sorter: true,
},
{
title: '备注',
dataIndex: 'remark',
sorter: true,
},
],
/* 字典编码储存格式 */
// 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[api.page]({
...params,
...this.query,
}).then((res) => {
return res.data;
});
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 有查询功能时的必要方法
* 重置查询条件
*/
onResetQuery() {
/** 在这里重置查询条件时,可对特殊的字段做保留处理 */
Object.keys(this.query).forEach((p) => {
if (p !== 'pid') {
this.query[p] = undefined;
}
});
this.onQuery();
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 必要方法
* 加载字典数据
* 如果不需要获取相应的字典数据,此方法内容可空
*/
//onLoadCodes() {
// this.$api
// .$queue([
// this.$api.sysDictTypeDropDownAwait({ code: 'code1' }),
// this.$api.sysDictTypeDropDownAwait({ 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,
/* 按需添加其他参数 */
/* ... */
orgId: this.query['pid'],
});
},
/**
* 必要方法
* 可以用做一系列操作的公共回调,此方法中会重新加载当前列表
*/
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api[api.delete](record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
/**
* 树形选择界面必要的方法
* 传给yo-table-layout以示意数据接口
*/
loadTreeData() {
return this.$api.getOrgTree().then((res) => {
return res.data;
});
},
onSelect([id]) {
this.query = {
pid: id,
};
this.onQuery();
},
},
};
</script>

View File

@@ -1,135 +1,135 @@
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<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">
<!-- 表单控件 -->
<!-- ... -->
<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>
<a-form-model-item label="备注" prop="remark">
<a-textarea :rows="4" placeholder="请输入备注" v-model="form.remark"></a-textarea>
</a-form-model-item>
</div>
</a-spin>
</a-form-model>
</template>
<script>
/* 表单内容默认值 */
const defaultForm = {
/* ... */
sort: 100,
};
export default {
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
/* ... */
name: [{ required: true, message: '请输入应用名称', trigger: 'blur' }],
code: [{ required: true, message: '请输入唯一编码', trigger: 'blur' }],
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/* ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
...params.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(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/* ... */
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
},
};
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<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">
<!-- 表单控件 -->
<!-- ... -->
<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>
<a-form-model-item label="备注" prop="remark">
<a-textarea :rows="4" placeholder="请输入备注" v-model="form.remark"></a-textarea>
</a-form-model-item>
</div>
</a-spin>
</a-form-model>
</template>
<script>
/* 表单内容默认值 */
const defaultForm = {
/* ... */
sort: 100,
};
export default {
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
/* ... */
name: [{ required: true, message: '请输入应用名称', trigger: 'blur' }],
code: [{ required: true, message: '请输入唯一编码', trigger: 'blur' }],
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/* ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
...params.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(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/* ... */
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
},
};
</script>

View File

@@ -1,233 +1,233 @@
<template>
<!--
普通查询表格
v 1.2
2021-04-30
Lufthafen
-->
<container>
<br />
<a-card :bordered="false">
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="sysPos:page" slot="query">
<!-- 此处添加查询表单控件 -->
<!-- ... -->
<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>
</Auth>
<Auth auth="sysPos: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="sysPos:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysPos:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
<!-- 可在此处添加其他操作控件 -->
<!-- ... -->
</yo-table-actions>
</span>
</yo-table>
</a-card>
<!-- 新增表单 -->
<yo-modal-form :action="$api[api.add]" :title="'新增' + name" @ok="onReloadData" ref="add-form">
<form-body />
</yo-modal-form>
<!-- 编辑表单 -->
<yo-modal-form :action="$api[api.edit]" :title="'编辑' + name" @ok="onReloadData" ref="edit-form">
<form-body />
</yo-modal-form>
</container>
</template>
<script>
import FormBody from './form';
/* 在此管理整个页面需要的接口名称 */
const api = {
page: 'sysPosPage',
add: 'sysPosAdd',
edit: 'sysPosEdit',
delete: 'sysPosDelete',
/* ... */
};
export default {
components: {
FormBody,
},
data() {
return {
api,
name: '职位',
/* 查询条件 */
query: {},
/* 表格字段 */
columns: [
{
title: '职位名称',
dataIndex: 'name',
sorter: true,
},
{
title: '唯一编码',
dataIndex: 'code',
sorter: true,
},
{
title: '排序',
dataIndex: 'sort',
sorter: true,
},
{
title: '备注',
dataIndex: 'remark',
sorter: true,
},
],
/* 字典编码储存格式 */
codes: {
code1: [],
code2: [],
},
};
},
created() {
/** 按需加载字典编码 */
this.onLoadCodes();
/** 根据权限添加操作列 */
const flag = this.$auth({
sysPos: [['edit'], ['delete']],
});
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return this.$api[api.page]({
...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() {},
/**
* 必要方法
* 绑定数据字典值
*/
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();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api[api.delete](record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
},
};
<template>
<!--
普通查询表格
v 1.2
2021-04-30
Lufthafen
-->
<container>
<br />
<a-card :bordered="false">
<yo-table
:columns="columns"
:load-data="loadData"
@query="onQuery"
@resetQuery="onResetQuery"
ref="table"
>
<Auth auth="sysPos:page" slot="query">
<!-- 此处添加查询表单控件 -->
<!-- ... -->
<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>
</Auth>
<Auth auth="sysPos: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="sysPos:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysPos:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
<!-- 可在此处添加其他操作控件 -->
<!-- ... -->
</yo-table-actions>
</span>
</yo-table>
</a-card>
<!-- 新增表单 -->
<yo-modal-form :action="$api[api.add]" :title="'新增' + name" @ok="onReloadData" ref="add-form">
<form-body />
</yo-modal-form>
<!-- 编辑表单 -->
<yo-modal-form :action="$api[api.edit]" :title="'编辑' + name" @ok="onReloadData" ref="edit-form">
<form-body />
</yo-modal-form>
</container>
</template>
<script>
import FormBody from './form';
/* 在此管理整个页面需要的接口名称 */
const api = {
page: 'sysPosPage',
add: 'sysPosAdd',
edit: 'sysPosEdit',
delete: 'sysPosDelete',
/* ... */
};
export default {
components: {
FormBody,
},
data() {
return {
api,
name: '职位',
/* 查询条件 */
query: {},
/* 表格字段 */
columns: [
{
title: '职位名称',
dataIndex: 'name',
sorter: true,
},
{
title: '唯一编码',
dataIndex: 'code',
sorter: true,
},
{
title: '排序',
dataIndex: 'sort',
sorter: true,
},
{
title: '备注',
dataIndex: 'remark',
sorter: true,
},
],
/* 字典编码储存格式 */
codes: {
code1: [],
code2: [],
},
};
},
created() {
/** 按需加载字典编码 */
this.onLoadCodes();
/** 根据权限添加操作列 */
const flag = this.$auth({
sysPos: [['edit'], ['delete']],
});
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return this.$api[api.page]({
...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() {},
/**
* 必要方法
* 绑定数据字典值
*/
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();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api[api.delete](record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.table.onLoaded();
});
},
},
};
</script>

View File

@@ -1,134 +1,134 @@
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<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">
<!-- 表单控件 -->
<!-- ... -->
<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>
<a-form-model-item label="备注" prop="remark">
<a-textarea :rows="4" placeholder="请输入备注" v-model="form.remark"></a-textarea>
</a-form-model-item>
</div>
</a-spin>
</a-form-model>
</template>
<script>
/* 表单内容默认值 */
const defaultForm = {
/* ... */
};
export default {
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
/* ... */
name: [{ required: true, message: '请输入应用名称', trigger: 'blur' }],
code: [{ required: true, message: '请输入唯一编码', trigger: 'blur' }],
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/* ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
...params.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(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/* ... */
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
},
};
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<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">
<!-- 表单控件 -->
<!-- ... -->
<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>
<a-form-model-item label="备注" prop="remark">
<a-textarea :rows="4" placeholder="请输入备注" v-model="form.remark"></a-textarea>
</a-form-model-item>
</div>
</a-spin>
</a-form-model>
</template>
<script>
/* 表单内容默认值 */
const defaultForm = {
/* ... */
};
export default {
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
/* ... */
name: [{ required: true, message: '请输入应用名称', trigger: 'blur' }],
code: [{ required: true, message: '请输入唯一编码', trigger: 'blur' }],
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/* ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
...params.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(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/* ... */
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
},
};
</script>

View File

@@ -1,101 +1,101 @@
<template>
<a-modal
:confirmLoading="confirmLoading"
:visible="visible"
:width="1200"
@cancel="onCancel"
@ok="onOk"
title="授权菜单"
>
<yo-authority-view
:auto-load="false"
:default-selected-keys="selectedKeys"
:load-data="loadData"
@select="onSelect"
ref="authority-view"
/>
</a-modal>
</template>
<script>
import YoAuthorityView from '@/components/yoAuthorityView';
export default {
components: {
YoAuthorityView,
},
data() {
return {
visible: false,
record: {},
selectedKeys: [],
confirmLoading: false,
};
},
methods: {
/**
* 必要的方法
* 从外部调用打开本窗口
*/
onOpen(params) {
this.visible = true;
this.record = params.record;
this.$nextTick(async () => {
const view = this.$refs['authority-view'];
view.loading = true;
view.data = [];
this.selectedKeys = await this.onLoadRoleOwnMenu();
view.onReloadData();
});
},
loadData() {
return this.$api.SysMenuTreeForGrant().then((res) => {
return res.data;
});
},
/**
* 此角色已有菜单权限
*/
onLoadRoleOwnMenu() {
return this.$api.sysRoleOwnMenu({ id: this.record.id }).then(({ data }) => {
return data;
});
},
onSelect(a1, a2, a3) {
this.selectedKeys = a3;
},
onOk() {
this.confirmLoading = true;
this.$api
.sysRoleGrantMenu({
id: this.record.id,
grantMenuIdList: this.selectedKeys,
})
.then(({ success }) => {
if (success) {
this.$message.success('授权成功');
this.$emit('ok');
this.onCancel();
}
})
.finally((res) => {
this.confirmLoading = false;
});
},
onCancel() {
this.visible = false;
},
},
};
</script>
<template>
<a-modal
:confirmLoading="confirmLoading"
:visible="visible"
:width="1200"
@cancel="onCancel"
@ok="onOk"
title="授权菜单"
>
<yo-authority-view
:auto-load="false"
:default-selected-keys="selectedKeys"
:load-data="loadData"
@select="onSelect"
ref="authority-view"
/>
</a-modal>
</template>
<script>
import YoAuthorityView from '@/components/yoAuthorityView';
export default {
components: {
YoAuthorityView,
},
data() {
return {
visible: false,
record: {},
selectedKeys: [],
confirmLoading: false,
};
},
methods: {
/**
* 必要的方法
* 从外部调用打开本窗口
*/
onOpen(params) {
this.visible = true;
this.record = params.record;
this.$nextTick(async () => {
const view = this.$refs['authority-view'];
view.loading = true;
view.data = [];
this.selectedKeys = await this.onLoadRoleOwnMenu();
view.onReloadData();
});
},
loadData() {
return this.$api.SysMenuTreeForGrant().then((res) => {
return res.data;
});
},
/**
* 此角色已有菜单权限
*/
onLoadRoleOwnMenu() {
return this.$api.sysRoleOwnMenu({ id: this.record.id }).then(({ data }) => {
return data;
});
},
onSelect(a1, a2, a3) {
this.selectedKeys = a3;
},
onOk() {
this.confirmLoading = true;
this.$api
.sysRoleGrantMenu({
id: this.record.id,
grantMenuIdList: this.selectedKeys,
})
.then(({ success }) => {
if (success) {
this.$message.success('授权成功');
this.$emit('ok');
this.onCancel();
}
})
.finally((res) => {
this.confirmLoading = false;
});
},
onCancel() {
this.visible = false;
},
},
};
</script>

View File

@@ -1,363 +1,363 @@
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<a-form-model :model="form" :rules="rules" class="yo-form" ref="form">
<a-spin :spinning="loading">
<a-icon slot="indicator" spin type="loading" />
<a-row :gutter="16">
<a-col :span="10">
<h3 class="h3">基本信息</h3>
<div class="yo-form-group">
<a-form-model-item label="账号" prop="account">
<a-input placeholder="请输入账号" v-model="form.account" />
</a-form-model-item>
<a-form-model-item label="姓名" prop="name">
<a-input placeholder="请输入姓名" v-model="form.name" />
</a-form-model-item>
<template v-if="mode == 'add'">
<a-form-model-item label="密码" prop="password">
<a-input-password placeholder="请输入密码" v-model="form.password" />
</a-form-model-item>
<a-form-model-item label="确认密码" prop="confirm">
<a-input-password placeholder="请确认密码" v-model="form.confirm" />
</a-form-model-item>
</template>
<a-form-model-item label="昵称">
<a-input placeholder="请输入昵称" v-model="form.nickName" />
</a-form-model-item>
<a-form-model-item label="生日">
<a-date-picker
@change="(date) => form.birthday = date ? $moment(date).format('YYYY-MM-DD') : null"
class="w-100-p"
placeholder="请选择生日"
v-model="form.birthday"
/>
</a-form-model-item>
<a-form-model-item label="性别" prop="sex">
<a-radio-group v-model="form.sex">
<a-radio-button :value="0">
<a-icon class="mr-xxs" type="stop" />
<span>保密</span>
</a-radio-button>
<a-radio-button :value="1">
<a-icon :style="{ color: '#1890ff' }" class="mr-xxs" type="man" />
<span></span>
</a-radio-button>
<a-radio-button :value="2">
<a-icon :style="{ color: '#eb2f96' }" class="mr-xxs" type="woman" />
<span></span>
</a-radio-button>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="邮箱" prop="email">
<a-input placeholder="请输入邮箱" v-model="form.email" />
</a-form-model-item>
<a-form-model-item label="手机号" prop="phone">
<a-input placeholder="请输入手机号" v-model="form.phone" />
</a-form-model-item>
<a-form-model-item label="电话" prop="tel">
<a-input placeholder="请输入电话" v-model="form.tel" />
</a-form-model-item>
</div>
</a-col>
<a-col :span="14" v-if="form.sysEmpParam">
<h3 class="h3">员工信息</h3>
<div class="yo-form-group">
<a-form-model-item label="所属组织机构" prop="sysEmpParam.orgId">
<a-tree-select
:dropdown-style="{ maxHeight: '300px', overflow: 'auto' }"
:tree-data="orgData"
placeholder="请选择所属组织机构"
tree-default-expand-all
v-model="form.sysEmpParam.orgId"
/>
</a-form-model-item>
<a-form-model-item label="工号">
<a-input placeholder="请输入工号" v-model="form.sysEmpParam.jobNum" />
</a-form-model-item>
<a-form-model-item label="职位信息" prop="sysEmpParam.posIdList">
<a-select mode="multiple" placeholder="请选择职位信息" v-model="form.sysEmpParam.posIdList">
<a-select-option
:key="i"
:value="item.id"
v-for="(item, i) in posData"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-model-item>
</div>
<h4 class="h4">附加信息</h4>
<div class="pl-md pr-md">
<a-table
:columns="extColumns"
:data-source="form.sysEmpParam.extIds"
:pagination="false"
size="small"
>
<template slot="footer">
<a-button @click="onAddExtData" block icon="plus" type="dashed">新增一项</a-button>
</template>
<template slot="orgId" slot-scope="text, record">
<a-tree-select
:default-value="text"
:dropdown-style="{ maxHeight: '300px', overflow: 'auto' }"
:tree-data="orgData"
@change="value => onChangeExtData(value, record, 'orgId')"
placeholder="请选择附加组织机构"
tree-default-expand-all
/>
</template>
<template slot="posId" slot-scope="text, record">
<a-select
:default-value="text"
@change="value => onChangeExtData(value, record, 'posId')"
placeholder="请选择附加职位信息"
>
<a-select-option
:key="i"
:value="item.id"
v-for="(item, i) in posData"
>{{ item.name }}</a-select-option>
</a-select>
</template>
<template slot="action" slot-scope="text, record">
<a-button @click="onRemoveExtData(record)" size="small" type="danger">删除</a-button>
</template>
</a-table>
</div>
</a-col>
</a-row>
</a-spin>
</a-form-model>
</template>
<script>
const validateToNextPassword = (rule, value, callback) => {
callback();
};
const compareToFirstPassword = (rule, value, callback) => {
callback();
};
/* 表单内容默认值 */
const defaultForm = {
/* ... */
sex: 0,
sysEmpParam: {},
};
export default {
props: {
mode: {
type: String,
default: 'edit',
},
},
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
/* ... */
account: [{ required: true, min: 5, message: '请输入至少五个字符的账号', trigger: 'blur' }],
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
password: [
{ required: true, min: 5, message: '请输入至少五个字符的密码', trigger: 'blur' },
{ validator: validateToNextPassword, trigger: 'blur' },
],
confirm: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{ validator: compareToFirstPassword, trigger: 'blur' },
],
phone: [
{
pattern: /^((13[0-9])|(14[5,7])|(15[^4,\\D])|(17[0,1,3,6-8])|(18[0-9])|(19[8,9])|(166))[0-9]{8}$/,
message: '手机号格式不正确',
trigger: 'blur',
},
],
email: [
{ pattern: /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5}){1,2}$/, message: '邮箱格式不正确', trigger: 'blur' },
],
'sysEmpParam.orgId': [{ required: true, message: '请选择所属组织机构' }],
'sysEmpParam.posIdList': [{ required: true, message: '请选择职位信息' }],
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/* ... */
orgData: [],
posData: [],
extColumns: [
{
title: '附属机构',
dataIndex: 'orgId',
width: '45%',
scopedSlots: { customRender: 'orgId' },
},
{
title: '附属岗位',
dataIndex: 'posId',
width: '45%',
scopedSlots: { customRender: 'posId' },
},
{
title: '操作',
key: 'action',
width: '70px',
scopedSlots: { customRender: 'action' },
},
],
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
const form = this.$_.cloneDeep(params.record || {});
// 日期特殊处理
if (form.birthday) {
form.birthday = this.$moment(form.birthday).format('YYYY-MM-DD');
}
// 提交的时候是"param",而获取下来却是"info",在这里转换一下
if (form.sysEmpInfo) {
form.sysEmpParam = form.sysEmpInfo;
delete form.sysEmpInfo;
} else if (!form.sysEmpParam) {
form.sysEmpParam = {
extIds: [],
};
}
// 转换职位信息列表
if (form.sysEmpParam.positions) {
form.sysEmpParam.posIdList = form.sysEmpParam.positions.map((p) => p.posId);
}
// 附加信息
if (form.sysEmpParam.extOrgPos) {
form.sysEmpParam.extIds = form.sysEmpParam.extOrgPos.map((p, i) => {
return {
key: i,
orgId: p.orgId,
posId: p.posId,
};
});
}
if (params.orgId) {
form.sysEmpParam.orgId = orgId;
}
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
...form,
/** 在此处添加其他默认数据转换 */
/* ... */
});
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
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();
/** 在这里可以初始化当前组件中其他属性 */
/* ... */
this.form = {
...defaultForm,
};
}, 300);
},
/**
* 必要方法
* 加载当前表单中所需要的异步数据
*/
async onInit(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/* ... */
this.orgData = await this.onLoadOrgData();
this.posData = await this.onLoadPosData();
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
onLoadOrgData() {
return this.$api.getOrgTree().then(({ data }) => {
return data;
});
},
onLoadPosData() {
return this.$api.sysPosList().then(({ data }) => {
return data;
});
},
onAddExtData() {
this.form.sysEmpParam.extIds.push({
key: this.form.sysEmpParam.extIds.length,
orgId: undefined,
posId: undefined,
});
},
onRemoveExtData(record) {
const ext = this.form.sysEmpParam.extIds,
remove = ext.find((p) => p.key === record.key),
index = ext.indexOf(remove);
ext.splice(index, 1);
},
onChangeExtData(value, record, type) {
record[type] = value;
},
},
};
<template>
<!--
普通编辑窗体
v 1.2
2021-04-30
Lufthafen
-->
<a-form-model :model="form" :rules="rules" class="yo-form" ref="form">
<a-spin :spinning="loading">
<a-icon slot="indicator" spin type="loading" />
<a-row :gutter="16">
<a-col :span="10">
<h3 class="h3">基本信息</h3>
<div class="yo-form-group">
<a-form-model-item label="账号" prop="account">
<a-input placeholder="请输入账号" v-model="form.account" />
</a-form-model-item>
<a-form-model-item label="姓名" prop="name">
<a-input placeholder="请输入姓名" v-model="form.name" />
</a-form-model-item>
<template v-if="mode == 'add'">
<a-form-model-item label="密码" prop="password">
<a-input-password placeholder="请输入密码" v-model="form.password" />
</a-form-model-item>
<a-form-model-item label="确认密码" prop="confirm">
<a-input-password placeholder="请确认密码" v-model="form.confirm" />
</a-form-model-item>
</template>
<a-form-model-item label="昵称">
<a-input placeholder="请输入昵称" v-model="form.nickName" />
</a-form-model-item>
<a-form-model-item label="生日">
<a-date-picker
@change="(date) => form.birthday = date ? $moment(date).format('YYYY-MM-DD') : null"
class="w-100-p"
placeholder="请选择生日"
v-model="form.birthday"
/>
</a-form-model-item>
<a-form-model-item label="性别" prop="sex">
<a-radio-group v-model="form.sex">
<a-radio-button :value="0">
<a-icon class="mr-xxs" type="stop" />
<span>保密</span>
</a-radio-button>
<a-radio-button :value="1">
<a-icon :style="{ color: '#1890ff' }" class="mr-xxs" type="man" />
<span></span>
</a-radio-button>
<a-radio-button :value="2">
<a-icon :style="{ color: '#eb2f96' }" class="mr-xxs" type="woman" />
<span></span>
</a-radio-button>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="邮箱" prop="email">
<a-input placeholder="请输入邮箱" v-model="form.email" />
</a-form-model-item>
<a-form-model-item label="手机号" prop="phone">
<a-input placeholder="请输入手机号" v-model="form.phone" />
</a-form-model-item>
<a-form-model-item label="电话" prop="tel">
<a-input placeholder="请输入电话" v-model="form.tel" />
</a-form-model-item>
</div>
</a-col>
<a-col :span="14" v-if="form.sysEmpParam">
<h3 class="h3">员工信息</h3>
<div class="yo-form-group">
<a-form-model-item label="所属组织机构" prop="sysEmpParam.orgId">
<a-tree-select
:dropdown-style="{ maxHeight: '300px', overflow: 'auto' }"
:tree-data="orgData"
placeholder="请选择所属组织机构"
tree-default-expand-all
v-model="form.sysEmpParam.orgId"
/>
</a-form-model-item>
<a-form-model-item label="工号">
<a-input placeholder="请输入工号" v-model="form.sysEmpParam.jobNum" />
</a-form-model-item>
<a-form-model-item label="职位信息" prop="sysEmpParam.posIdList">
<a-select mode="multiple" placeholder="请选择职位信息" v-model="form.sysEmpParam.posIdList">
<a-select-option
:key="i"
:value="item.id"
v-for="(item, i) in posData"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-model-item>
</div>
<h4 class="h4">附加信息</h4>
<div class="pl-md pr-md">
<a-table
:columns="extColumns"
:data-source="form.sysEmpParam.extIds"
:pagination="false"
size="small"
>
<template slot="footer">
<a-button @click="onAddExtData" block icon="plus" type="dashed">新增一项</a-button>
</template>
<template slot="orgId" slot-scope="text, record">
<a-tree-select
:default-value="text"
:dropdown-style="{ maxHeight: '300px', overflow: 'auto' }"
:tree-data="orgData"
@change="value => onChangeExtData(value, record, 'orgId')"
placeholder="请选择附加组织机构"
tree-default-expand-all
/>
</template>
<template slot="posId" slot-scope="text, record">
<a-select
:default-value="text"
@change="value => onChangeExtData(value, record, 'posId')"
placeholder="请选择附加职位信息"
>
<a-select-option
:key="i"
:value="item.id"
v-for="(item, i) in posData"
>{{ item.name }}</a-select-option>
</a-select>
</template>
<template slot="action" slot-scope="text, record">
<a-button @click="onRemoveExtData(record)" size="small" type="danger">删除</a-button>
</template>
</a-table>
</div>
</a-col>
</a-row>
</a-spin>
</a-form-model>
</template>
<script>
const validateToNextPassword = (rule, value, callback) => {
callback();
};
const compareToFirstPassword = (rule, value, callback) => {
callback();
};
/* 表单内容默认值 */
const defaultForm = {
/* ... */
sex: 0,
sysEmpParam: {},
};
export default {
props: {
mode: {
type: String,
default: 'edit',
},
},
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {
/* ... */
account: [{ required: true, min: 5, message: '请输入至少五个字符的账号', trigger: 'blur' }],
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
password: [
{ required: true, min: 5, message: '请输入至少五个字符的密码', trigger: 'blur' },
{ validator: validateToNextPassword, trigger: 'blur' },
],
confirm: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{ validator: compareToFirstPassword, trigger: 'blur' },
],
phone: [
{
pattern: /^((13[0-9])|(14[5,7])|(15[^4,\\D])|(17[0,1,3,6-8])|(18[0-9])|(19[8,9])|(166))[0-9]{8}$/,
message: '手机号格式不正确',
trigger: 'blur',
},
],
email: [
{ pattern: /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5}){1,2}$/, message: '邮箱格式不正确', trigger: 'blur' },
],
'sysEmpParam.orgId': [{ required: true, message: '请选择所属组织机构' }],
'sysEmpParam.posIdList': [{ required: true, message: '请选择职位信息' }],
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/* ... */
orgData: [],
posData: [],
extColumns: [
{
title: '附属机构',
dataIndex: 'orgId',
width: '45%',
scopedSlots: { customRender: 'orgId' },
},
{
title: '附属岗位',
dataIndex: 'posId',
width: '45%',
scopedSlots: { customRender: 'posId' },
},
{
title: '操作',
key: 'action',
width: '70px',
scopedSlots: { customRender: 'action' },
},
],
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(params) {
const form = this.$_.cloneDeep(params.record || {});
// 日期特殊处理
if (form.birthday) {
form.birthday = this.$moment(form.birthday).format('YYYY-MM-DD');
}
// 提交的时候是"param",而获取下来却是"info",在这里转换一下
if (form.sysEmpInfo) {
form.sysEmpParam = form.sysEmpInfo;
delete form.sysEmpInfo;
} else if (!form.sysEmpParam) {
form.sysEmpParam = {
extIds: [],
};
}
// 转换职位信息列表
if (form.sysEmpParam.positions) {
form.sysEmpParam.posIdList = form.sysEmpParam.positions.map((p) => p.posId);
}
// 附加信息
if (form.sysEmpParam.extOrgPos) {
form.sysEmpParam.extIds = form.sysEmpParam.extOrgPos.map((p, i) => {
return {
key: i,
orgId: p.orgId,
posId: p.posId,
};
});
}
if (params.orgId) {
form.sysEmpParam.orgId = orgId;
}
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...defaultForm,
...form,
/** 在此处添加其他默认数据转换 */
/* ... */
});
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
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();
/** 在这里可以初始化当前组件中其他属性 */
/* ... */
this.form = {
...defaultForm,
};
}, 300);
},
/**
* 必要方法
* 加载当前表单中所需要的异步数据
*/
async onInit(params) {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/* ... */
this.orgData = await this.onLoadOrgData();
this.posData = await this.onLoadPosData();
this.loading = false;
},
/** 当前组件的其他方法 */
/* ... */
onLoadOrgData() {
return this.$api.getOrgTree().then(({ data }) => {
return data;
});
},
onLoadPosData() {
return this.$api.sysPosList().then(({ data }) => {
return data;
});
},
onAddExtData() {
this.form.sysEmpParam.extIds.push({
key: this.form.sysEmpParam.extIds.length,
orgId: undefined,
posId: undefined,
});
},
onRemoveExtData(record) {
const ext = this.form.sysEmpParam.extIds,
remove = ext.find((p) => p.key === record.key),
index = ext.indexOf(remove);
ext.splice(index, 1);
},
onChangeExtData(value, record, type) {
record[type] = value;
},
},
};
</script>

View File

@@ -1,384 +1,384 @@
<template>
<!--
普通树查询表格
v 1.2
2021-04-30
Lufthafen
-->
<yo-tree-layout
:load-data="loadTreeData"
@select="onSelect"
default-expanded-keys
ref="tree-layout"
>
<container>
<a-card :bordered="false">
<Auth auth="sysUser:page">
<div class="yo-query-bar">
<a-form-model :model="query" @submit.native.prevent layout="inline">
<a-form-model-item label="关键词">
<a-input allow-clear placeholder="请输入姓名、账号、手机号" v-model="query.searchValue" />
</a-form-model-item>
<a-form-model-item label="状态">
<a-select
:style="{ width: '170px' }"
allow-clear
placeholder="请选择状态"
v-model="query.searchStatus"
>
<a-select-option
:key="i"
:value="item.code"
v-for="(item, i) in codes.common_status"
>{{ item.value }}</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item>
<a-button-group>
<a-button @click="onQuery" html-type="submit" type="primary">查询</a-button>
<a-button @click="onReset">重置</a-button>
</a-button-group>
</a-form-model-item>
</a-form-model>
</div>
</Auth>
<yo-list :load-data="loadData" item-layout="horizontal" ref="list" size="large">
<Auth auth="sysUser:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增用户</a-button>
</Auth>
<a-list-item key="record.id" slot="renderItem" slot-scope="record">
<Auth auth="sysUser:edit" slot="actions">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysUser:delete" slot="actions">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
<Auth :auth="{ sysUser: [['grantRole'], ['grantData']] }" slot="actions">
<a-dropdown placement="bottomRight">
<a class="ant-dropdown-link">
授权
<a-icon type="down" />
</a>
<a-menu slot="overlay">
<Auth auth="sysUser:grantRole">
<a-menu-item>
<a @click="onOpen('role-form', record)">授权角色</a>
</a-menu-item>
</Auth>
<Auth auth="sysUser:grantData">
<a-menu-item>
<a @click="onOpen('data-form', record)">授权额外数据</a>
</a-menu-item>
</Auth>
</a-menu>
</a-dropdown>
</Auth>
<a-list-item-meta>
<div slot="title">{{ record.nickName || record.name }}</div>
<div slot="description">{{ record.account }}</div>
<yo-image
:id="record.avatar"
:size="48"
icon="user"
shape="square"
slot="avatar"
type="avatar"
/>
</a-list-item-meta>
<div class="yo-list-content--h">
<div class="yo-list-content--h--item">
<span>性别</span>
<p>{{ bindCodeValue(record.sex, 'sex') }}</p>
</div>
<div class="yo-list-content--h--item">
<span>手机</span>
<p>{{ record.phone || '未设置' }}</p>
</div>
<div class="yo-list-content--h--item">
<span>邮箱</span>
<p>{{ record.email || '未设置' }}</p>
</div>
<Auth auth="sysUser:changeStatus">
<div class="yo-list-content--h--item text-center">
<a-switch
:checked="!record.status"
:checked-children="bindCodeValue(0, 'common_status')"
:loading="record.statusChanging"
:un-checked-children="bindCodeValue(1, 'common_status')"
@change="(checked) => onSetUserStatus(record, checked)"
/>
</div>
</Auth>
</div>
</a-list-item>
</yo-list>
</a-card>
</container>
<!-- 新增表单 -->
<yo-modal-form
:action="$api[api.add]"
:title="'新增' + name"
:width="1024"
@ok="onReloadData"
ref="add-form"
>
<form-body mode="add" />
</yo-modal-form>
<!-- 编辑表单 -->
<yo-modal-form
:action="$api[api.edit]"
:title="'编辑' + name"
:width="1024"
@ok="onReloadData"
ref="edit-form"
>
<form-body mode="edit" />
</yo-modal-form>
<role-form @ok="onReloadData" ref="role-form" />
<data-form @ok="onReloadData" ref="data-form" />
</yo-tree-layout>
</template>
<script>
/* 需要引用YoTreeLayout组件 */
import YoTreeLayout from '@/components/yoTreeLayout';
import YoList from '@/components/yoList';
import FormBody from './form';
import RoleForm from './roleForm';
import DataForm from './dataForm';
/* 在此管理整个页面需要的接口名称 */
const api = {
tree: 'getOrgTree',
page: 'getUserPage',
add: 'sysUserAdd',
edit: 'sysUserEdit',
delete: 'sysUserDelete',
/* ... */
};
export default {
components: {
YoTreeLayout,
YoList,
FormBody,
RoleForm,
DataForm,
},
data() {
return {
api,
name: '用户',
/* 查询条件 */
query: {},
/* 表格字段 */
columns: [],
/* 字典编码储存格式 */
codes: {
sex: [],
common_status: [],
},
};
},
created() {
this.onLoadCodes();
/** 根据权限添加操作列 */
const flag = this.$auth({
sysUser: [['edit'], ['delete'], ['grantRole'], ['grantData']],
});
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 树形选择界面必要的方法
* 传给yo-table-layout以示意数据接口
*/
loadTreeData() {
return this.$api[api.tree]().then((res) => {
return res.data;
});
},
/**
* 树形选择界面必要的方法
* 选择树节点事件
*/
onSelect([id]) {
/** 在选择事件中可以对右侧表格添加父节点id的查询条件 */
this.query = {
'sysEmpParam.orgId': id,
};
this.onQuery();
},
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return this.$api[api.page]({
...params,
...this.query,
}).then((res) => {
return res.data;
});
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.list.onReloadData(true);
},
/**
* 必要方法
* 重新列表数据
*/
onReset() {
/* 与普通查询页不同的是,这里的父节点参数不应该在重置后被清空 */
Object.keys(this.query).forEach((p) => {
if (p !== 'pid') {
this.query[p] = undefined;
}
});
this.onQuery();
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.list.onReloadData();
this.$refs['tree-layout'].onReloadData();
},
/**
* 必要方法
* 加载字典数据
* 如果不需要获取相应的字典数据,此方法内容可空
*/
onLoadCodes() {
this.$api
.$queue([
this.$api.sysDictTypeDropDownAwait({ code: 'sex' }),
this.$api.sysDictTypeDropDownAwait({ code: 'common_status' }),
/* ... */
])
.then(([code1, code2]) => {
this.codes.sex = code1.data;
this.codes.common_status = 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,
orgId: this.query['sysEmpParam.orgId'],
/* 按需添加其他参数 */
/* ... */
});
},
/**
* 必要方法
* 可以用做一系列操作的公共回调,此方法中会重新加载当前列表
*/
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.list.onLoading();
this.$api[api.delete](record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.list.onLoaded();
});
},
onSetUserStatus(record, checked) {
this.$set(record, 'statusChanging', true);
this.$api
.sysUserChangeStatus({
id: record.id,
status: +!checked,
})
.then(({ success, message }) => {
if (success) {
this.$message.success('操作成功');
this.onReloadData();
} else {
this.$message.error(message);
}
})
.finally(() => {
record.statusChanging = false;
});
},
onResetPassword(record) {
this.$api
.sysUserResetPwd({
id: record.id,
})
.then(({ success, message }) => {
if (success) {
this.$message.success('重置成功');
} else {
this.$message.error(message);
}
});
},
},
};
<template>
<!--
普通树查询表格
v 1.2
2021-04-30
Lufthafen
-->
<yo-tree-layout
:load-data="loadTreeData"
@select="onSelect"
default-expanded-keys
ref="tree-layout"
>
<container>
<a-card :bordered="false">
<Auth auth="sysUser:page">
<div class="yo-query-bar">
<a-form-model :model="query" @submit.native.prevent layout="inline">
<a-form-model-item label="关键词">
<a-input allow-clear placeholder="请输入姓名、账号、手机号" v-model="query.searchValue" />
</a-form-model-item>
<a-form-model-item label="状态">
<a-select
:style="{ width: '170px' }"
allow-clear
placeholder="请选择状态"
v-model="query.searchStatus"
>
<a-select-option
:key="i"
:value="item.code"
v-for="(item, i) in codes.common_status"
>{{ item.value }}</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item>
<a-button-group>
<a-button @click="onQuery" html-type="submit" type="primary">查询</a-button>
<a-button @click="onReset">重置</a-button>
</a-button-group>
</a-form-model-item>
</a-form-model>
</div>
</Auth>
<yo-list :load-data="loadData" item-layout="horizontal" ref="list" size="large">
<Auth auth="sysUser:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增用户</a-button>
</Auth>
<a-list-item key="record.id" slot="renderItem" slot-scope="record">
<Auth auth="sysUser:edit" slot="actions">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysUser:delete" slot="actions">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
<Auth :auth="{ sysUser: [['grantRole'], ['grantData']] }" slot="actions">
<a-dropdown placement="bottomRight">
<a class="ant-dropdown-link">
授权
<a-icon type="down" />
</a>
<a-menu slot="overlay">
<Auth auth="sysUser:grantRole">
<a-menu-item>
<a @click="onOpen('role-form', record)">授权角色</a>
</a-menu-item>
</Auth>
<Auth auth="sysUser:grantData">
<a-menu-item>
<a @click="onOpen('data-form', record)">授权额外数据</a>
</a-menu-item>
</Auth>
</a-menu>
</a-dropdown>
</Auth>
<a-list-item-meta>
<div slot="title">{{ record.nickName || record.name }}</div>
<div slot="description">{{ record.account }}</div>
<yo-image
:id="record.avatar"
:size="48"
icon="user"
shape="square"
slot="avatar"
type="avatar"
/>
</a-list-item-meta>
<div class="yo-list-content--h">
<div class="yo-list-content--h--item">
<span>性别</span>
<p>{{ bindCodeValue(record.sex, 'sex') }}</p>
</div>
<div class="yo-list-content--h--item">
<span>手机</span>
<p>{{ record.phone || '未设置' }}</p>
</div>
<div class="yo-list-content--h--item">
<span>邮箱</span>
<p>{{ record.email || '未设置' }}</p>
</div>
<Auth auth="sysUser:changeStatus">
<div class="yo-list-content--h--item text-center">
<a-switch
:checked="!record.status"
:checked-children="bindCodeValue(0, 'common_status')"
:loading="record.statusChanging"
:un-checked-children="bindCodeValue(1, 'common_status')"
@change="(checked) => onSetUserStatus(record, checked)"
/>
</div>
</Auth>
</div>
</a-list-item>
</yo-list>
</a-card>
</container>
<!-- 新增表单 -->
<yo-modal-form
:action="$api[api.add]"
:title="'新增' + name"
:width="1024"
@ok="onReloadData"
ref="add-form"
>
<form-body mode="add" />
</yo-modal-form>
<!-- 编辑表单 -->
<yo-modal-form
:action="$api[api.edit]"
:title="'编辑' + name"
:width="1024"
@ok="onReloadData"
ref="edit-form"
>
<form-body mode="edit" />
</yo-modal-form>
<role-form @ok="onReloadData" ref="role-form" />
<data-form @ok="onReloadData" ref="data-form" />
</yo-tree-layout>
</template>
<script>
/* 需要引用YoTreeLayout组件 */
import YoTreeLayout from '@/components/yoTreeLayout';
import YoList from '@/components/yoList';
import FormBody from './form';
import RoleForm from './roleForm';
import DataForm from './dataForm';
/* 在此管理整个页面需要的接口名称 */
const api = {
tree: 'getOrgTree',
page: 'getUserPage',
add: 'sysUserAdd',
edit: 'sysUserEdit',
delete: 'sysUserDelete',
/* ... */
};
export default {
components: {
YoTreeLayout,
YoList,
FormBody,
RoleForm,
DataForm,
},
data() {
return {
api,
name: '用户',
/* 查询条件 */
query: {},
/* 表格字段 */
columns: [],
/* 字典编码储存格式 */
codes: {
sex: [],
common_status: [],
},
};
},
created() {
this.onLoadCodes();
/** 根据权限添加操作列 */
const flag = this.$auth({
sysUser: [['edit'], ['delete'], ['grantRole'], ['grantData']],
});
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 树形选择界面必要的方法
* 传给yo-table-layout以示意数据接口
*/
loadTreeData() {
return this.$api[api.tree]().then((res) => {
return res.data;
});
},
/**
* 树形选择界面必要的方法
* 选择树节点事件
*/
onSelect([id]) {
/** 在选择事件中可以对右侧表格添加父节点id的查询条件 */
this.query = {
'sysEmpParam.orgId': id,
};
this.onQuery();
},
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return this.$api[api.page]({
...params,
...this.query,
}).then((res) => {
return res.data;
});
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.list.onReloadData(true);
},
/**
* 必要方法
* 重新列表数据
*/
onReset() {
/* 与普通查询页不同的是,这里的父节点参数不应该在重置后被清空 */
Object.keys(this.query).forEach((p) => {
if (p !== 'pid') {
this.query[p] = undefined;
}
});
this.onQuery();
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.list.onReloadData();
this.$refs['tree-layout'].onReloadData();
},
/**
* 必要方法
* 加载字典数据
* 如果不需要获取相应的字典数据,此方法内容可空
*/
onLoadCodes() {
this.$api
.$queue([
this.$api.sysDictTypeDropDownAwait({ code: 'sex' }),
this.$api.sysDictTypeDropDownAwait({ code: 'common_status' }),
/* ... */
])
.then(([code1, code2]) => {
this.codes.sex = code1.data;
this.codes.common_status = 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,
orgId: this.query['sysEmpParam.orgId'],
/* 按需添加其他参数 */
/* ... */
});
},
/**
* 必要方法
* 可以用做一系列操作的公共回调,此方法中会重新加载当前列表
*/
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.list.onLoading();
this.$api[api.delete](record)
.then(({ success }) => {
this.onResult(success, '删除成功');
})
.finally(() => {
this.$refs.list.onLoaded();
});
},
onSetUserStatus(record, checked) {
this.$set(record, 'statusChanging', true);
this.$api
.sysUserChangeStatus({
id: record.id,
status: +!checked,
})
.then(({ success, message }) => {
if (success) {
this.$message.success('操作成功');
this.onReloadData();
} else {
this.$message.error(message);
}
})
.finally(() => {
record.statusChanging = false;
});
},
onResetPassword(record) {
this.$api
.sysUserResetPwd({
id: record.id,
})
.then(({ success, message }) => {
if (success) {
this.$message.success('重置成功');
} else {
this.$message.error(message);
}
});
},
},
};
</script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,138 +1,138 @@
<template>
<a-layout-content>
<a-tabs @change="onChange" @edit="onClose" hide-add type="editable-card" v-model="actived">
<a-tab-pane
:closable="pane.closable"
:forceRender="true"
:key="pane.key"
v-for="pane in panes"
>
<a-dropdown :trigger="['contextmenu']" slot="tab">
<div>
<a-icon :type="pane.icon" v-if="pane.icon" />
{{ pane.title }}
</div>
<a-menu slot="overlay">
<template v-if="mode === 'development'">
<a-menu-item @click="onCopyComponent(pane)" key="-1">
复制组件地址
<a-tag color="red">dev</a-tag>
</a-menu-item>
<a-menu-divider />
</template>
<a-menu-item @click="onLoadContentWindow(pane.key)" key="0">重新加载</a-menu-item>
<a-menu-divider />
<a-menu-item :disabled="!pane.closable" @click="$emit('close', pane.key);" key="1">关闭</a-menu-item>
<a-menu-item
:disabled="!hasOther(pane)"
@click="$emit('close-other', pane.key)"
key="2"
>关闭其他标签页</a-menu-item>
<a-menu-item
:disabled="!hasRight(pane)"
@click="$emit('close-right', pane.key)"
key="3"
>关闭右侧标签页</a-menu-item>
</a-menu>
</a-dropdown>
<component
:is="pane.component"
:key="pane.key"
:param="pane.param"
ref="panes"
v-if="pane.loaded"
/>
</a-tab-pane>
</a-tabs>
</a-layout-content>
</template>
<script>
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
NProgress.configure({ parent: '.ant-layout-content > .ant-tabs > .ant-tabs-content' });
export default {
props: {
panes: {
type: Array,
},
tabActived: {
type: [String, Number],
},
},
data() {
return {
mode: process.env.VUE_APP_NODE_ENV,
actived: '',
};
},
watch: {
tabActived() {
if (this.tabActived !== this.actived) {
this.actived = this.tabActived;
}
},
},
created() {
this.actived = this.tabActived;
},
methods: {
onLoadContentWindow(key) {
NProgress.start();
const pane = this.panes.find((p) => p.key === key);
// 打开之前先销毁
const index = this.panes.indexOf(pane);
const component = this.$refs.panes && this.$refs.panes[index];
if (component) {
component.$destroy();
}
const i = import(`@/pages${pane.path}`);
pane.component = () => i;
pane.loaded = false;
i.then(() => {
pane.loaded = true;
NProgress.done();
}).catch(() => {
pane.component = () => import('@/views/error/404');
pane.loaded = true;
NProgress.done();
});
},
onClose(targetKey, action) {
if (action === 'remove') {
this.$emit('close', targetKey);
}
},
onChange(activeKey) {
this.$emit('change', activeKey);
},
onCopyComponent(pane) {
try {
const copy = document.createElement('textarea');
document.body.append(copy);
copy.value = `/pages${pane.path}`;
copy.select();
setTimeout(() => {
document.execCommand('copy');
copy.remove();
this.$message.success('已复制到剪切板');
});
} catch {
this.$message.error('复制错误');
}
},
hasOther(pane) {
return this.panes.filter((p) => p.key !== pane.key && p.closable).length > 0;
},
hasRight(pane) {
return this.panes.length > this.panes.indexOf(pane) + 1;
},
},
};
<template>
<a-layout-content>
<a-tabs @change="onChange" @edit="onClose" hide-add type="editable-card" v-model="actived">
<a-tab-pane
:closable="pane.closable"
:forceRender="true"
:key="pane.key"
v-for="pane in panes"
>
<a-dropdown :trigger="['contextmenu']" slot="tab">
<div>
<a-icon :type="pane.icon" v-if="pane.icon" />
{{ pane.title }}
</div>
<a-menu slot="overlay">
<template v-if="mode === 'development'">
<a-menu-item @click="onCopyComponent(pane)" key="-1">
复制组件地址
<a-tag color="red">dev</a-tag>
</a-menu-item>
<a-menu-divider />
</template>
<a-menu-item @click="onLoadContentWindow(pane.key)" key="0">重新加载</a-menu-item>
<a-menu-divider />
<a-menu-item :disabled="!pane.closable" @click="$emit('close', pane.key);" key="1">关闭</a-menu-item>
<a-menu-item
:disabled="!hasOther(pane)"
@click="$emit('close-other', pane.key)"
key="2"
>关闭其他标签页</a-menu-item>
<a-menu-item
:disabled="!hasRight(pane)"
@click="$emit('close-right', pane.key)"
key="3"
>关闭右侧标签页</a-menu-item>
</a-menu>
</a-dropdown>
<component
:is="pane.component"
:key="pane.key"
:param="pane.param"
ref="panes"
v-if="pane.loaded"
/>
</a-tab-pane>
</a-tabs>
</a-layout-content>
</template>
<script>
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
NProgress.configure({ parent: '.ant-layout-content > .ant-tabs > .ant-tabs-content' });
export default {
props: {
panes: {
type: Array,
},
tabActived: {
type: [String, Number],
},
},
data() {
return {
mode: process.env.VUE_APP_NODE_ENV,
actived: '',
};
},
watch: {
tabActived() {
if (this.tabActived !== this.actived) {
this.actived = this.tabActived;
}
},
},
created() {
this.actived = this.tabActived;
},
methods: {
onLoadContentWindow(key) {
NProgress.start();
const pane = this.panes.find((p) => p.key === key);
// 打开之前先销毁
const index = this.panes.indexOf(pane);
const component = this.$refs.panes && this.$refs.panes[index];
if (component) {
component.$destroy();
}
const i = import(`@/pages${pane.path}`);
pane.component = () => i;
pane.loaded = false;
i.then(() => {
pane.loaded = true;
NProgress.done();
}).catch(() => {
pane.component = () => import('@/views/error/404');
pane.loaded = true;
NProgress.done();
});
},
onClose(targetKey, action) {
if (action === 'remove') {
this.$emit('close', targetKey);
}
},
onChange(activeKey) {
this.$emit('change', activeKey);
},
onCopyComponent(pane) {
try {
const copy = document.createElement('textarea');
document.body.append(copy);
copy.value = `/pages${pane.path}`;
copy.select();
setTimeout(() => {
document.execCommand('copy');
copy.remove();
this.$message.success('已复制到剪切板');
});
} catch {
this.$message.error('复制错误');
}
},
hasOther(pane) {
return this.panes.filter((p) => p.key !== pane.key && p.closable).length > 0;
},
hasRight(pane) {
return this.panes.length > this.panes.indexOf(pane) + 1;
},
},
};
</script>

View File

@@ -1,100 +1,100 @@
<template>
<a-layout-header class="header">
<section
v-if="$root.global.settings.layout === 'left-menu' || $root.global.settings.layout === 'right-menu'"
>
<div class="header-actions">
<a
@click="$root.global.settings.siderCollapsed = !$root.global.settings.siderCollapsed"
class="header-action"
>
<a-icon :type="$root.global.settings.siderCollapsed ? 'menu-unfold' : 'menu-fold'" />
</a>
<search :menus="nav.content" />
</div>
<div class="header-actions">
<User />
<a @click="$emit('reload')" class="header-action">
<a-icon type="reload" />
</a>
<a class="header-action">
<a-badge count="5">
<a-icon type="bell" />
</a-badge>
</a>
<a @click="$emit('setting')" class="header-action">
<a-icon type="setting" />
</a>
</div>
</section>
<container v-else-if="$root.global.settings.layout === 'top-nav'">
<div class="header-actions">
<a @click="showNav = !showNav" class="header-action mr-md">
<a-icon type="menu" />
</a>
<Logo />
<search :menus="nav.content" />
</div>
<div class="header-actions">
<User />
<a @click="$emit('reload')" class="header-action">
<a-icon type="reload" />
</a>
<a class="header-action">
<a-badge count="5">
<a-icon type="bell" />
</a-badge>
</a>
<a @click="$emit('setting')" class="header-action">
<a-icon type="setting" />
</a>
</div>
<a-drawer
:body-style="{ padding: 0 }"
:closable="false"
:get-container="'.ant-layout-content > .ant-tabs'"
:visible="showNav"
:wrap-style="{ position: 'absolute' }"
@close="showNav = false"
placement="left"
width="38.2%"
>
<div @blur="showNav = false" @mouseleave="showNav = false">
<Nav :nav="nav" @open="showNav = false" />
</div>
</a-drawer>
</container>
</a-layout-header>
</template>
<script>
import Logo from '../logo';
import Nav from '../nav';
import User from './user';
import Search from './search';
export default {
components: {
Logo,
Nav,
User,
Search,
},
props: {
nav: {
default() {
return {
content: [],
};
},
type: Object,
},
},
data() {
return {
showNav: false,
};
},
};
<template>
<a-layout-header class="header">
<section
v-if="$root.global.settings.layout === 'left-menu' || $root.global.settings.layout === 'right-menu'"
>
<div class="header-actions">
<a
@click="$root.global.settings.siderCollapsed = !$root.global.settings.siderCollapsed"
class="header-action"
>
<a-icon :type="$root.global.settings.siderCollapsed ? 'menu-unfold' : 'menu-fold'" />
</a>
<search :menus="nav.content" />
</div>
<div class="header-actions">
<User />
<a @click="$emit('reload')" class="header-action">
<a-icon type="reload" />
</a>
<a class="header-action">
<a-badge count="5">
<a-icon type="bell" />
</a-badge>
</a>
<a @click="$emit('setting')" class="header-action">
<a-icon type="setting" />
</a>
</div>
</section>
<container v-else-if="$root.global.settings.layout === 'top-nav'">
<div class="header-actions">
<a @click="showNav = !showNav" class="header-action mr-md">
<a-icon type="menu" />
</a>
<Logo />
<search :menus="nav.content" />
</div>
<div class="header-actions">
<User />
<a @click="$emit('reload')" class="header-action">
<a-icon type="reload" />
</a>
<a class="header-action">
<a-badge count="5">
<a-icon type="bell" />
</a-badge>
</a>
<a @click="$emit('setting')" class="header-action">
<a-icon type="setting" />
</a>
</div>
<a-drawer
:body-style="{ padding: 0 }"
:closable="false"
:get-container="'.ant-layout-content > .ant-tabs'"
:visible="showNav"
:wrap-style="{ position: 'absolute' }"
@close="showNav = false"
placement="left"
width="38.2%"
>
<div @blur="showNav = false" @mouseleave="showNav = false">
<Nav :nav="nav" @open="showNav = false" />
</div>
</a-drawer>
</container>
</a-layout-header>
</template>
<script>
import Logo from '../logo';
import Nav from '../nav';
import User from './user';
import Search from './search';
export default {
components: {
Logo,
Nav,
User,
Search,
},
props: {
nav: {
default() {
return {
content: [],
};
},
type: Object,
},
},
data() {
return {
showNav: false,
};
},
};
</script>

View File

@@ -1,149 +1,149 @@
export default {
props: {
menus: {
type: Array,
require: true
}
},
data() {
return {
searchText: '',
searchResult: [],
timer: null
};
},
methods: {
renderSelect(menu) {
return menu.map((p) => {
return p.children ? this.renderSelectGroup(p) : this.renderSelectOption(p)
})
},
renderSelectGroup(menu) {
return (
<a-select-opt-group key={menu.parents}>
<span slot="label">
{menu.parents}
</span>
{this.renderSelect(menu.children)}
</a-select-opt-group>
)
},
renderSelectOption(menu) {
return (<a-select-option key={menu.id} value={
JSON.stringify(menu)
}>
{menu.meta.icon && <a-icon type={menu.meta.icon} />}
{menu.meta.title}
<small style={{ display: 'block', color: '#aaa' }}>{menu.component}</small>
</a-select-option>)
},
onSearch(value) {
clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.doSearch(value)
}, 300)
},
doSearch(value) {
this.searchText = value
const menus = this.$_.concat.apply(this, this.$_.cloneDeep(this.menus.map(p => p.menu)))
const search = (m) => {
if (!value) return []
return m.filter((p) => {
if (p.children) {
p.children = search(p.children)
} else {
return p.meta.title.indexOf(value) > -1 || (p.component || '').toLowerCase().indexOf(value.toLowerCase()) > -1
}
return p.children.length
})
}
/**
* 拆分层级,只留下 [父级-父级-...] [子级]
* *******在更为复杂的目录下会出现父级联动错误的问题
*/
const unzip = (m) => {
const getSeed = (parent, seed) => {
if (parent.children) {
seed.parents.push(parent.meta.title)
seed.children = parent.children
parent.children.forEach(p => {
getSeed(p, seed)
})
}
return seed
}
const result = []
m.forEach(p => {
const r = { parents: [], children: [] }
result.push(getSeed(p, r))
})
return result
}
const result = unzip(search(menus)).filter(p => p.parents.length).map(p => {
return {
parents: p.parents.join('-'),
children: p.children
}
})
this.searchResult = result
},
onSearchSelect(value, node) {
this.searchText = '';
this.onSearch(this.searchText);
const menu = JSON.parse(node.componentOptions.propsData.value)
this.openContentWindow({
key: menu.id,
title: menu.meta.title,
icon: menu.meta.icon,
path: menu.component,
});
},
},
render() {
const props = {
dropdownMatchSelectWidth: false,
dropdownStyle: { width: '300px' },
optionLabelProp: 'value',
placeholder: '请输入检索关键字',
value: this.searchText
}
const on = {
search: this.onSearch,
select: this.onSearchSelect
}
return (
<a-auto-complete {...{ props, on }}>
<template slot="dataSource">
{this.renderSelect(this.searchResult)}
</template>
<a-input allow-clear>
<a-icon slot="suffix" type="search" class="certain-category-icon" />
</a-input>
</a-auto-complete>
)
}
export default {
props: {
menus: {
type: Array,
require: true
}
},
data() {
return {
searchText: '',
searchResult: [],
timer: null
};
},
methods: {
renderSelect(menu) {
return menu.map((p) => {
return p.children ? this.renderSelectGroup(p) : this.renderSelectOption(p)
})
},
renderSelectGroup(menu) {
return (
<a-select-opt-group key={menu.parents}>
<span slot="label">
{menu.parents}
</span>
{this.renderSelect(menu.children)}
</a-select-opt-group>
)
},
renderSelectOption(menu) {
return (<a-select-option key={menu.id} value={
JSON.stringify(menu)
}>
{menu.meta.icon && <a-icon type={menu.meta.icon} />}
{menu.meta.title}
<small style={{ display: 'block', color: '#aaa' }}>{menu.component}</small>
</a-select-option>)
},
onSearch(value) {
clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.doSearch(value)
}, 300)
},
doSearch(value) {
this.searchText = value
const menus = this.$_.concat.apply(this, this.$_.cloneDeep(this.menus.map(p => p.menu)))
const search = (m) => {
if (!value) return []
return m.filter((p) => {
if (p.children) {
p.children = search(p.children)
} else {
return p.meta.title.indexOf(value) > -1 || (p.component || '').toLowerCase().indexOf(value.toLowerCase()) > -1
}
return p.children.length
})
}
/**
* 拆分层级,只留下 [父级-父级-...] [子级]
* *******在更为复杂的目录下会出现父级联动错误的问题
*/
const unzip = (m) => {
const getSeed = (parent, seed) => {
if (parent.children) {
seed.parents.push(parent.meta.title)
seed.children = parent.children
parent.children.forEach(p => {
getSeed(p, seed)
})
}
return seed
}
const result = []
m.forEach(p => {
const r = { parents: [], children: [] }
result.push(getSeed(p, r))
})
return result
}
const result = unzip(search(menus)).filter(p => p.parents.length).map(p => {
return {
parents: p.parents.join('-'),
children: p.children
}
})
this.searchResult = result
},
onSearchSelect(value, node) {
this.searchText = '';
this.onSearch(this.searchText);
const menu = JSON.parse(node.componentOptions.propsData.value)
this.openContentWindow({
key: menu.id,
title: menu.meta.title,
icon: menu.meta.icon,
path: menu.component,
});
},
},
render() {
const props = {
dropdownMatchSelectWidth: false,
dropdownStyle: { width: '300px' },
optionLabelProp: 'value',
placeholder: '请输入检索关键字',
value: this.searchText
}
const on = {
search: this.onSearch,
select: this.onSearchSelect
}
return (
<a-auto-complete {...{ props, on }}>
<template slot="dataSource">
{this.renderSelect(this.searchResult)}
</template>
<a-input allow-clear>
<a-icon slot="suffix" type="search" class="certain-category-icon" />
</a-input>
</a-auto-complete>
)
}
}

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