Merge remote-tracking branch 'origin/feature-20240104' into feature-20240104

master
382696293@qq.com 2 years ago
commit 487738f53a

@ -5,7 +5,10 @@ VUE_APP_TITLE = 花至管理系统
ENV = 'development'
# 花至管理系统/开发环境
VUE_APP_BASE_API = '/dev-api'
VUE_APP_BASE_API = '/prod-api'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true
# 设置端口号
port=8080

@ -6,3 +6,6 @@ ENV = 'production'
# 花至管理系统/生产环境
VUE_APP_BASE_API = '/prod-api'
# 设置端口号
# port=8080

@ -5,7 +5,8 @@
"author": "花至",
"license": "MIT",
"scripts": {
"dev": "set NODE_OPTIONS=--openssl-legacy-provider & vue-cli-service serve",
"local": "vue-cli-service serve --mode local",
"dev": "vue-cli-service serve --mode development",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
@ -45,11 +46,14 @@
"file-saver": "2.0.5",
"fuse.js": "6.4.3",
"highlight.js": "9.18.5",
"i": "^0.3.7",
"js-beautify": "1.13.0",
"js-cookie": "3.0.1",
"jsencrypt": "3.0.0-rc.1",
"npm": "^10.2.5",
"nprogress": "0.2.0",
"quill": "1.3.7",
"quill": "^2.0.0-dev.3",
"quill-better-table": "^1.2.10",
"screenfull": "5.0.2",
"sortablejs": "1.10.2",
"vue": "2.6.12",

@ -0,0 +1,978 @@
/*!
* Quill Editor v2.0.0-dev.3
* https://quilljs.com/
* Copyright (c) 2014, Jason Chen
* Copyright (c) 2013, salesforce.com
*/
.ql-container {
box-sizing: border-box;
font-family: Helvetica, Arial, sans-serif;
font-size: 13px;
height: 100%;
margin: 0;
position: relative;
}
.ql-container.ql-disabled .ql-tooltip {
visibility: hidden;
}
.ql-container:not(.ql-disabled) li[data-list="checked"] > .ql-ui,
.ql-container:not(.ql-disabled) li[data-list="unchecked"] > .ql-ui {
cursor: pointer;
}
.ql-clipboard {
left: -100000px;
height: 1px;
overflow-y: hidden;
position: absolute;
top: 50%;
}
.ql-clipboard p {
margin: 0;
padding: 0;
}
.ql-editor {
box-sizing: border-box;
counter-reset: list-0;
line-height: 1.42;
height: 100%;
outline: 0;
overflow-y: auto;
padding: 12px 15px;
tab-size: 4;
-moz-tab-size: 4;
text-align: left;
white-space: pre-wrap;
word-wrap: break-word;
}
.ql-editor > * {
cursor: text;
}
.ql-editor blockquote,
.ql-editor h1,
.ql-editor h2,
.ql-editor h3,
.ql-editor h4,
.ql-editor h5,
.ql-editor h6,
.ql-editor ol,
.ql-editor p,
.ql-editor pre {
margin: 0;
padding: 0;
}
.ql-editor h1,
.ql-editor h2,
.ql-editor h3,
.ql-editor h4,
.ql-editor h5,
.ql-editor h6,
.ql-editor p {
counter-reset: list-0 list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8
list-9;
}
.ql-editor table {
border-collapse: collapse;
}
.ql-editor td {
border: 1px solid #000;
padding: 2px 5px;
}
.ql-editor ol {
padding-left: 1.5em;
}
.ql-editor li {
list-style-type: none;
padding-left: 1.5em;
position: relative;
}
.ql-editor li > .ql-ui:before {
display: inline-block;
margin-left: -1.5em;
margin-right: 0.3em;
text-align: right;
white-space: nowrap;
width: 1.2em;
}
.ql-editor li[data-list="checked"] > .ql-ui,
.ql-editor li[data-list="unchecked"] > .ql-ui {
color: #777;
}
.ql-editor li[data-list="bullet"] > .ql-ui:before {
content: "\2022";
}
.ql-editor li[data-list="checked"] > .ql-ui:before {
content: "\2611";
}
.ql-editor li[data-list="unchecked"] > .ql-ui:before {
content: "\2610";
}
.ql-editor li[data-list="ordered"] {
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
counter-increment: list-0;
}
.ql-editor li[data-list="ordered"] > .ql-ui:before {
content: counter(list-0, decimal) ". ";
}
.ql-editor li[data-list="ordered"].ql-indent-1 {
counter-increment: list-1;
}
.ql-editor li[data-list="ordered"].ql-indent-1 > .ql-ui:before {
content: counter(list-1, lower-alpha) ". ";
}
.ql-editor li[data-list="ordered"].ql-indent-1 {
counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor li[data-list="ordered"].ql-indent-2 {
counter-increment: list-2;
}
.ql-editor li[data-list="ordered"].ql-indent-2 > .ql-ui:before {
content: counter(list-2, lower-roman) ". ";
}
.ql-editor li[data-list="ordered"].ql-indent-2 {
counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor li[data-list="ordered"].ql-indent-3 {
counter-increment: list-3;
}
.ql-editor li[data-list="ordered"].ql-indent-3 > .ql-ui:before {
content: counter(list-3, decimal) ". ";
}
.ql-editor li[data-list="ordered"].ql-indent-3 {
counter-reset: list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor li[data-list="ordered"].ql-indent-4 {
counter-increment: list-4;
}
.ql-editor li[data-list="ordered"].ql-indent-4 > .ql-ui:before {
content: counter(list-4, lower-alpha) ". ";
}
.ql-editor li[data-list="ordered"].ql-indent-4 {
counter-reset: list-5 list-6 list-7 list-8 list-9;
}
.ql-editor li[data-list="ordered"].ql-indent-5 {
counter-increment: list-5;
}
.ql-editor li[data-list="ordered"].ql-indent-5 > .ql-ui:before {
content: counter(list-5, lower-roman) ". ";
}
.ql-editor li[data-list="ordered"].ql-indent-5 {
counter-reset: list-6 list-7 list-8 list-9;
}
.ql-editor li[data-list="ordered"].ql-indent-6 {
counter-increment: list-6;
}
.ql-editor li[data-list="ordered"].ql-indent-6 > .ql-ui:before {
content: counter(list-6, decimal) ". ";
}
.ql-editor li[data-list="ordered"].ql-indent-6 {
counter-reset: list-7 list-8 list-9;
}
.ql-editor li[data-list="ordered"].ql-indent-7 {
counter-increment: list-7;
}
.ql-editor li[data-list="ordered"].ql-indent-7 > .ql-ui:before {
content: counter(list-7, lower-alpha) ". ";
}
.ql-editor li[data-list="ordered"].ql-indent-7 {
counter-reset: list-8 list-9;
}
.ql-editor li[data-list="ordered"].ql-indent-8 {
counter-increment: list-8;
}
.ql-editor li[data-list="ordered"].ql-indent-8 > .ql-ui:before {
content: counter(list-8, lower-roman) ". ";
}
.ql-editor li[data-list="ordered"].ql-indent-8 {
counter-reset: list-9;
}
.ql-editor li[data-list="ordered"].ql-indent-9 {
counter-increment: list-9;
}
.ql-editor li[data-list="ordered"].ql-indent-9 > .ql-ui:before {
content: counter(list-9, decimal) ". ";
}
.ql-editor .ql-indent-1:not(.ql-direction-rtl) {
padding-left: 3em;
}
.ql-editor li.ql-indent-1:not(.ql-direction-rtl) {
padding-left: 4.5em;
}
.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right {
padding-right: 3em;
}
.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right {
padding-right: 4.5em;
}
.ql-editor .ql-indent-2:not(.ql-direction-rtl) {
padding-left: 6em;
}
.ql-editor li.ql-indent-2:not(.ql-direction-rtl) {
padding-left: 7.5em;
}
.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right {
padding-right: 6em;
}
.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right {
padding-right: 7.5em;
}
.ql-editor .ql-indent-3:not(.ql-direction-rtl) {
padding-left: 9em;
}
.ql-editor li.ql-indent-3:not(.ql-direction-rtl) {
padding-left: 10.5em;
}
.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right {
padding-right: 9em;
}
.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right {
padding-right: 10.5em;
}
.ql-editor .ql-indent-4:not(.ql-direction-rtl) {
padding-left: 12em;
}
.ql-editor li.ql-indent-4:not(.ql-direction-rtl) {
padding-left: 13.5em;
}
.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right {
padding-right: 12em;
}
.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right {
padding-right: 13.5em;
}
.ql-editor .ql-indent-5:not(.ql-direction-rtl) {
padding-left: 15em;
}
.ql-editor li.ql-indent-5:not(.ql-direction-rtl) {
padding-left: 16.5em;
}
.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right {
padding-right: 15em;
}
.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right {
padding-right: 16.5em;
}
.ql-editor .ql-indent-6:not(.ql-direction-rtl) {
padding-left: 18em;
}
.ql-editor li.ql-indent-6:not(.ql-direction-rtl) {
padding-left: 19.5em;
}
.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right {
padding-right: 18em;
}
.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right {
padding-right: 19.5em;
}
.ql-editor .ql-indent-7:not(.ql-direction-rtl) {
padding-left: 21em;
}
.ql-editor li.ql-indent-7:not(.ql-direction-rtl) {
padding-left: 22.5em;
}
.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right {
padding-right: 21em;
}
.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right {
padding-right: 22.5em;
}
.ql-editor .ql-indent-8:not(.ql-direction-rtl) {
padding-left: 24em;
}
.ql-editor li.ql-indent-8:not(.ql-direction-rtl) {
padding-left: 25.5em;
}
.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right {
padding-right: 24em;
}
.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right {
padding-right: 25.5em;
}
.ql-editor .ql-indent-9:not(.ql-direction-rtl) {
padding-left: 27em;
}
.ql-editor li.ql-indent-9:not(.ql-direction-rtl) {
padding-left: 28.5em;
}
.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right {
padding-right: 27em;
}
.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right {
padding-right: 28.5em;
}
.ql-editor li.ql-direction-rtl {
padding-right: 1.5em;
}
.ql-editor li.ql-direction-rtl > .ql-ui:before {
margin-left: 0.3em;
margin-right: -1.5em;
text-align: left;
}
.ql-editor table {
table-layout: fixed;
width: 100%;
}
.ql-editor table td {
outline: 0;
}
.ql-editor .ql-code-block-container {
font-family: monospace;
}
.ql-editor .ql-video {
display: block;
max-width: 100%;
}
.ql-editor .ql-video.ql-align-center {
margin: 0 auto;
}
.ql-editor .ql-video.ql-align-right {
margin: 0 0 0 auto;
}
.ql-editor .ql-bg-black {
background-color: #000;
}
.ql-editor .ql-bg-red {
background-color: #e60000;
}
.ql-editor .ql-bg-orange {
background-color: #f90;
}
.ql-editor .ql-bg-yellow {
background-color: #ff0;
}
.ql-editor .ql-bg-green {
background-color: #008a00;
}
.ql-editor .ql-bg-blue {
background-color: #06c;
}
.ql-editor .ql-bg-purple {
background-color: #93f;
}
.ql-editor .ql-color-white {
color: #fff;
}
.ql-editor .ql-color-red {
color: #e60000;
}
.ql-editor .ql-color-orange {
color: #f90;
}
.ql-editor .ql-color-yellow {
color: #ff0;
}
.ql-editor .ql-color-green {
color: #008a00;
}
.ql-editor .ql-color-blue {
color: #06c;
}
.ql-editor .ql-color-purple {
color: #93f;
}
.ql-editor .ql-font-serif {
font-family: Georgia, Times New Roman, serif;
}
.ql-editor .ql-font-monospace {
font-family: Monaco, Courier New, monospace;
}
.ql-editor .ql-size-small {
font-size: 0.75em;
}
.ql-editor .ql-size-large {
font-size: 1.5em;
}
.ql-editor .ql-size-huge {
font-size: 2.5em;
}
.ql-editor .ql-direction-rtl {
direction: rtl;
text-align: inherit;
}
.ql-editor .ql-align-center {
text-align: center;
}
.ql-editor .ql-align-justify {
text-align: justify;
}
.ql-editor .ql-align-right {
text-align: right;
}
.ql-editor .ql-ui {
position: absolute;
}
.ql-editor.ql-blank::before {
color: rgba(0, 0, 0, 0.6);
content: attr(data-placeholder);
font-style: italic;
left: 15px;
pointer-events: none;
position: absolute;
right: 15px;
}
.ql-snow .ql-toolbar:after,
.ql-snow.ql-toolbar:after {
clear: both;
content: "";
display: table;
}
.ql-snow .ql-toolbar button,
.ql-snow.ql-toolbar button {
background: 0 0;
border: none;
cursor: pointer;
display: inline-block;
float: left;
height: 24px;
padding: 3px 5px;
width: 28px;
}
.ql-snow .ql-toolbar button svg,
.ql-snow.ql-toolbar button svg {
float: left;
height: 100%;
}
.ql-snow .ql-toolbar button:active:hover,
.ql-snow.ql-toolbar button:active:hover {
outline: 0;
}
.ql-snow .ql-toolbar input.ql-image[type="file"],
.ql-snow.ql-toolbar input.ql-image[type="file"] {
display: none;
}
.ql-snow .ql-toolbar .ql-picker-item.ql-selected,
.ql-snow .ql-toolbar .ql-picker-item:hover,
.ql-snow .ql-toolbar .ql-picker-label.ql-active,
.ql-snow .ql-toolbar .ql-picker-label:hover,
.ql-snow .ql-toolbar button.ql-active,
.ql-snow .ql-toolbar button:focus,
.ql-snow .ql-toolbar button:hover,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected,
.ql-snow.ql-toolbar .ql-picker-item:hover,
.ql-snow.ql-toolbar .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker-label:hover,
.ql-snow.ql-toolbar button.ql-active,
.ql-snow.ql-toolbar button:focus,
.ql-snow.ql-toolbar button:hover {
color: #06c;
}
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button.ql-active .ql-fill,
.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button:focus .ql-fill,
.ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button:hover .ql-fill,
.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,
.ql-snow.ql-toolbar button.ql-active .ql-fill,
.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill,
.ql-snow.ql-toolbar button:focus .ql-fill,
.ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill,
.ql-snow.ql-toolbar button:hover .ql-fill,
.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill {
fill: #06c;
}
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter,
.ql-snow .ql-toolbar button.ql-active .ql-stroke,
.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter,
.ql-snow .ql-toolbar button:focus .ql-stroke,
.ql-snow .ql-toolbar button:focus .ql-stroke-miter,
.ql-snow .ql-toolbar button:hover .ql-stroke,
.ql-snow .ql-toolbar button:hover .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter,
.ql-snow.ql-toolbar button.ql-active .ql-stroke,
.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter,
.ql-snow.ql-toolbar button:focus .ql-stroke,
.ql-snow.ql-toolbar button:focus .ql-stroke-miter,
.ql-snow.ql-toolbar button:hover .ql-stroke,
.ql-snow.ql-toolbar button:hover .ql-stroke-miter {
stroke: #06c;
}
@media (pointer: coarse) {
.ql-snow .ql-toolbar button:hover:not(.ql-active),
.ql-snow.ql-toolbar button:hover:not(.ql-active) {
color: #444;
}
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill,
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill,
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill {
fill: #444;
}
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter,
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter {
stroke: #444;
}
}
.ql-snow {
box-sizing: border-box;
}
.ql-snow * {
box-sizing: border-box;
}
.ql-snow .ql-hidden {
display: none;
}
.ql-snow .ql-out-bottom,
.ql-snow .ql-out-top {
visibility: hidden;
}
.ql-snow .ql-tooltip {
position: absolute;
transform: translateY(10px);
}
.ql-snow .ql-tooltip a {
cursor: pointer;
text-decoration: none;
}
.ql-snow .ql-tooltip.ql-flip {
transform: translateY(-10px);
}
.ql-snow .ql-formats {
display: inline-block;
vertical-align: middle;
}
.ql-snow .ql-formats:after {
clear: both;
content: "";
display: table;
}
.ql-snow .ql-stroke {
fill: none;
stroke: #444;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2;
}
.ql-snow .ql-stroke-miter {
fill: none;
stroke: #444;
stroke-miterlimit: 10;
stroke-width: 2;
}
.ql-snow .ql-fill,
.ql-snow .ql-stroke.ql-fill {
fill: #444;
}
.ql-snow .ql-empty {
fill: none;
}
.ql-snow .ql-even {
fill-rule: evenodd;
}
.ql-snow .ql-stroke.ql-thin,
.ql-snow .ql-thin {
stroke-width: 1;
}
.ql-snow .ql-transparent {
opacity: 0.4;
}
.ql-snow .ql-direction svg:last-child {
display: none;
}
.ql-snow .ql-direction.ql-active svg:last-child {
display: inline;
}
.ql-snow .ql-direction.ql-active svg:first-child {
display: none;
}
.ql-snow .ql-editor h1 {
font-size: 2em;
}
.ql-snow .ql-editor h2 {
font-size: 1.5em;
}
.ql-snow .ql-editor h3 {
font-size: 1.17em;
}
.ql-snow .ql-editor h4 {
font-size: 1em;
}
.ql-snow .ql-editor h5 {
font-size: 0.83em;
}
.ql-snow .ql-editor h6 {
font-size: 0.67em;
}
.ql-snow .ql-editor a {
text-decoration: underline;
}
.ql-snow .ql-editor blockquote {
border-left: 4px solid #ccc;
margin-bottom: 5px;
margin-top: 5px;
padding-left: 16px;
}
.ql-snow .ql-editor .ql-code-block-container,
.ql-snow .ql-editor code {
background-color: #f0f0f0;
border-radius: 3px;
}
.ql-snow .ql-editor .ql-code-block-container {
margin-bottom: 5px;
margin-top: 5px;
padding: 5px 10px;
}
.ql-snow .ql-editor code {
font-size: 85%;
padding: 2px 4px;
}
.ql-snow .ql-editor .ql-code-block-container {
background-color: #23241f;
color: #f8f8f2;
overflow: visible;
}
.ql-snow .ql-editor img {
max-width: 100%;
}
.ql-snow .ql-picker {
color: #444;
display: inline-block;
float: left;
font-size: 14px;
font-weight: 500;
height: 24px;
position: relative;
vertical-align: middle;
}
.ql-snow .ql-picker-label {
cursor: pointer;
display: inline-block;
height: 100%;
padding-left: 8px;
padding-right: 2px;
position: relative;
width: 100%;
}
.ql-snow .ql-picker-label::before {
display: inline-block;
line-height: 22px;
}
.ql-snow .ql-picker-options {
background-color: #fff;
display: none;
min-width: 100%;
padding: 4px 8px;
position: absolute;
white-space: nowrap;
}
.ql-snow .ql-picker-options .ql-picker-item {
cursor: pointer;
display: block;
padding-bottom: 5px;
padding-top: 5px;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label {
color: #ccc;
z-index: 2;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill {
fill: #ccc;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke {
stroke: #ccc;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-options {
display: block;
margin-top: -1px;
top: 100%;
z-index: 1;
}
.ql-snow .ql-color-picker,
.ql-snow .ql-icon-picker {
width: 28px;
}
.ql-snow .ql-color-picker .ql-picker-label,
.ql-snow .ql-icon-picker .ql-picker-label {
padding: 2px 4px;
}
.ql-snow .ql-color-picker .ql-picker-label svg,
.ql-snow .ql-icon-picker .ql-picker-label svg {
right: 4px;
}
.ql-snow .ql-icon-picker .ql-picker-options {
padding: 4px 0;
}
.ql-snow .ql-icon-picker .ql-picker-item {
height: 24px;
width: 24px;
padding: 2px 4px;
}
.ql-snow .ql-color-picker .ql-picker-options {
padding: 3px 5px;
width: 152px;
}
.ql-snow .ql-color-picker .ql-picker-item {
border: 1px solid transparent;
float: left;
height: 16px;
margin: 2px;
padding: 0;
width: 16px;
}
.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg {
position: absolute;
margin-top: -9px;
right: 0;
top: 50%;
width: 18px;
}
.ql-snow
.ql-picker.ql-font
.ql-picker-item[data-label]:not([data-label=""])::before,
.ql-snow
.ql-picker.ql-font
.ql-picker-label[data-label]:not([data-label=""])::before,
.ql-snow
.ql-picker.ql-header
.ql-picker-item[data-label]:not([data-label=""])::before,
.ql-snow
.ql-picker.ql-header
.ql-picker-label[data-label]:not([data-label=""])::before,
.ql-snow
.ql-picker.ql-size
.ql-picker-item[data-label]:not([data-label=""])::before,
.ql-snow
.ql-picker.ql-size
.ql-picker-label[data-label]:not([data-label=""])::before {
content: attr(data-label);
}
.ql-snow .ql-picker.ql-header {
width: 98px;
}
.ql-snow .ql-picker.ql-header .ql-picker-item::before,
.ql-snow .ql-picker.ql-header .ql-picker-label::before {
content: "Normal";
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before {
content: "Heading 1";
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before {
content: "Heading 2";
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before {
content: "Heading 3";
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before {
content: "Heading 4";
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before {
content: "Heading 5";
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before {
content: "Heading 6";
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
font-size: 2em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
font-size: 1.5em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
font-size: 1.17em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
font-size: 1em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
font-size: 0.83em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
font-size: 0.67em;
}
.ql-snow .ql-picker.ql-font {
width: 108px;
}
.ql-snow .ql-picker.ql-font .ql-picker-item::before,
.ql-snow .ql-picker.ql-font .ql-picker-label::before {
content: "Sans Serif";
}
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before {
content: "Serif";
}
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before {
content: "Monospace";
}
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
font-family: Georgia, Times New Roman, serif;
}
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
font-family: Monaco, Courier New, monospace;
}
.ql-snow .ql-picker.ql-size {
width: 98px;
}
.ql-snow .ql-picker.ql-size .ql-picker-item::before,
.ql-snow .ql-picker.ql-size .ql-picker-label::before {
content: "Normal";
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before {
content: "Small";
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before {
content: "Large";
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before {
content: "Huge";
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
font-size: 10px;
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
font-size: 18px;
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
font-size: 32px;
}
.ql-snow .ql-color-picker.ql-background .ql-picker-item {
background-color: #fff;
}
.ql-snow .ql-color-picker.ql-color .ql-picker-item {
background-color: #000;
}
.ql-code-block-container {
position: relative;
}
.ql-code-block-container .ql-ui {
right: 5px;
top: 5px;
}
.ql-toolbar.ql-snow {
border: 1px solid #ccc;
box-sizing: border-box;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
padding: 8px;
}
.ql-toolbar.ql-snow .ql-formats {
margin-right: 15px;
}
.ql-toolbar.ql-snow .ql-picker-label {
border: 1px solid transparent;
}
.ql-toolbar.ql-snow .ql-picker-options {
border: 1px solid transparent;
box-shadow: rgba(0, 0, 0, 0.2) 0 2px 8px;
}
.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label {
border-color: #ccc;
}
.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options {
border-color: #ccc;
}
.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected,
.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover {
border-color: #000;
}
.ql-toolbar.ql-snow + .ql-container.ql-snow {
border-top: 0;
}
.ql-snow .ql-tooltip {
background-color: #fff;
border: 1px solid #ccc;
box-shadow: 0 0 5px #ddd;
color: #444;
padding: 5px 12px;
white-space: nowrap;
}
.ql-snow .ql-tooltip::before {
content: "Visit URL:";
line-height: 26px;
margin-right: 8px;
}
.ql-snow .ql-tooltip input[type="text"] {
display: none;
border: 1px solid #ccc;
font-size: 13px;
height: 26px;
margin: 0;
padding: 3px 5px;
width: 170px;
}
.ql-snow .ql-tooltip a.ql-preview {
display: inline-block;
max-width: 200px;
overflow-x: hidden;
text-overflow: ellipsis;
vertical-align: top;
}
.ql-snow .ql-tooltip a.ql-action::after {
border-right: 1px solid #ccc;
content: "Edit";
margin-left: 16px;
padding-right: 8px;
}
.ql-snow .ql-tooltip a.ql-remove::before {
content: "Remove";
margin-left: 8px;
}
.ql-snow .ql-tooltip a {
line-height: 26px;
}
.ql-snow .ql-tooltip.ql-editing a.ql-preview,
.ql-snow .ql-tooltip.ql-editing a.ql-remove {
display: none;
}
.ql-snow .ql-tooltip.ql-editing input[type="text"] {
display: inline-block;
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0;
content: "Save";
padding-right: 0;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
content: "Enter link:";
}
.ql-snow .ql-tooltip[data-mode="formula"]::before {
content: "Enter formula:";
}
.ql-snow .ql-tooltip[data-mode="video"]::before {
content: "Enter video:";
}
.ql-snow a {
color: #06c;
}
.ql-container.ql-snow {
border: 1px solid #ccc;
}
/*# sourceMappingURL=quill.snow.min.css.map */

File diff suppressed because it is too large Load Diff

@ -1,274 +1,326 @@
<template>
<div>
<el-upload
:action="uploadUrl"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
name="file"
:show-file-list="false"
:headers="headers"
style="display: none"
ref="upload"
v-if="this.type == 'url'"
>
</el-upload>
<div class="editor" ref="editor" :style="styles"></div>
</div>
<div>
<el-upload
:action="uploadUrl"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
name="file"
:show-file-list="false"
:headers="headers"
style="display: none"
ref="upload"
v-if="this.type == 'url'"
>
</el-upload>
<div class="editor" ref="editor" :style="styles">
<div>
{{ this.currentValue }}
</div>
</div>
</div>
</template>
<script>
import Quill from "quill";
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
import { getToken } from "@/utils/auth";
import Quill from 'quill'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import { getToken } from '@/utils/auth'
import QuillBetterTable from 'quill-better-table'
import 'quill-better-table/dist/quill-better-table.css'
Quill.register(
{
'modules/better-table': QuillBetterTable,
},
true
)
export default {
name: "Editor",
props: {
/* 编辑器的内容 */
value: {
type: String,
default: "",
},
/* 高度 */
height: {
type: Number,
default: null,
},
/* 最小高度 */
minHeight: {
type: Number,
default: null,
},
/* 只读 */
readOnly: {
type: Boolean,
default: false,
},
/* 上传文件大小限制(MB) */
fileSize: {
type: Number,
default: 5,
},
/* 类型base64格式、url格式 */
type: {
type: String,
default: "url",
}
},
data() {
return {
uploadUrl: process.env.VUE_APP_BASE_API + "/file/upload", //
headers: {
Authorization: "Bearer " + getToken()
},
Quill: null,
currentValue: "",
options: {
theme: "snow",
bounds: document.body,
debug: "warn",
modules: {
//
toolbar: [
["bold", "italic", "underline", "strike"], // 线 线
["blockquote", "code-block"], //
[{ list: "ordered" }, { list: "bullet" }], //
[{ indent: "-1" }, { indent: "+1" }], //
[{ size: ["small", false, "large", "huge"] }], //
[{ header: [1, 2, 3, 4, 5, 6, false] }], //
[{ color: [] }, { background: [] }], //
[{ align: [] }], //
["clean"], //
["link", "image", "video"] //
],
},
placeholder: "请输入内容",
readOnly: this.readOnly,
},
};
},
computed: {
styles() {
let style = {};
if (this.minHeight) {
style.minHeight = `${this.minHeight}px`;
}
if (this.height) {
style.height = `${this.height}px`;
}
return style;
},
},
watch: {
value: {
handler(val) {
if (val !== this.currentValue) {
this.currentValue = val === null ? "" : val;
if (this.Quill) {
this.Quill.pasteHTML(this.currentValue);
}
}
},
immediate: true,
},
},
mounted() {
this.init();
},
beforeDestroy() {
this.Quill = null;
},
methods: {
init() {
const editor = this.$refs.editor;
this.Quill = new Quill(editor, this.options);
//
if (this.type == 'url') {
let toolbar = this.Quill.getModule("toolbar");
toolbar.addHandler("image", (value) => {
if (value) {
this.$refs.upload.$children[0].$refs.input.click();
} else {
this.quill.format("image", false);
}
});
}
this.Quill.pasteHTML(this.currentValue);
this.Quill.on("text-change", (delta, oldDelta, source) => {
const html = this.$refs.editor.children[0].innerHTML;
const text = this.Quill.getText();
const quill = this.Quill;
this.currentValue = html;
this.$emit("input", html);
this.$emit("on-change", { html, text, quill });
});
this.Quill.on("text-change", (delta, oldDelta, source) => {
this.$emit("on-text-change", delta, oldDelta, source);
});
this.Quill.on("selection-change", (range, oldRange, source) => {
this.$emit("on-selection-change", range, oldRange, source);
});
this.Quill.on("editor-change", (eventName, ...args) => {
this.$emit("on-editor-change", eventName, ...args);
});
},
//
handleBeforeUpload(file) {
const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];
const isJPG = type.includes(file.type);
//
if (!isJPG) {
this.$message.error(`图片格式错误!`);
return false;
}
//
if (this.fileSize) {
const isLt = file.size / 1024 / 1024 < this.fileSize;
if (!isLt) {
this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`);
return false;
}
}
return true;
},
handleUploadSuccess(res, file) {
//
if (res.code == 200) {
//
let quill = this.Quill;
//
let length = quill.getSelection().index;
// res.url
quill.insertEmbed(length, "image", res.data.url);
//
quill.setSelection(length + 1);
} else {
this.$message.error("图片插入失败");
}
},
handleUploadError() {
this.$message.error("图片插入失败");
},
},
};
name: 'Editor',
props: {
/* 编辑器的内容 */
value: {
type: String,
default: '',
},
/* 高度 */
height: {
type: Number,
default: null,
},
/* 最小高度 */
minHeight: {
type: Number,
default: null,
},
/* 只读 */
readOnly: {
type: Boolean,
default: false,
},
/* 上传文件大小限制(MB) */
fileSize: {
type: Number,
default: 5,
},
/* 类型base64格式、url格式 */
type: {
type: String,
default: 'url',
},
/* 私有ID 用于同一页面下的不同富文本的表格修改 */
tableName: {
type: String,
default: 'mytable',
},
},
data() {
return {
uploadUrl: process.env.VUE_APP_BASE_API + '/file/upload', //
headers: {
Authorization: 'Bearer ' + getToken(),
},
Quill: null,
currentValue: '',
options: {
theme: 'snow',
bounds: document.body,
debug: 'warn',
modules: {
table: false, // disable table module
'better-table': {
operationMenu: {
items: {
unmergeCells: {
text: 'Another unmerge cells name',
},
},
},
},
keyboard: {
bindings: QuillBetterTable.keyboardBindings,
},
//
toolbar: [
['bold', 'italic', 'underline', 'strike'], // 线 线
['blockquote', 'code-block'], //
[{ list: 'ordered' }, { list: 'bullet' }], //
[{ indent: '-1' }, { indent: '+1' }], //
[{ size: ['small', false, 'large', 'huge'] }], //
[{ header: [1, 2, 3, 4, 5, 6, false] }], //
[{ color: [] }, { background: [] }], //
[{ align: [] }], //
['clean'], //
['table'],
['link', 'image', 'video'], //
],
},
placeholder: '请输入内容',
readOnly: this.readOnly,
},
}
},
computed: {
styles() {
let style = {}
if (this.minHeight) {
style.minHeight = `${this.minHeight}px`
}
if (this.height) {
style.height = `${this.height}px`
}
return style
},
},
watch: {
value: {
handler(val) {
if (val !== this.currentValue) {
this.currentValue = val === null ? '' : val
let ele = document.body.querySelector(`#${this.tableName} .editor`)
if (ele) {
ele.children[0].innerHTML = this.currentValue
}
}
},
immediate: true,
},
},
mounted() {
this.init()
// this.addTableButton()
},
beforeDestroy() {
this.Quill = null
},
methods: {
init() {
const editor = this.$refs.editor
this.Quill = new Quill(editor, this.options)
//
if (this.type == 'url') {
let toolbar = this.Quill.getModule('toolbar')
toolbar.addHandler('image', (value) => {
if (value) {
this.$refs.upload.$children[0].$refs.input.click()
} else {
this.quill.format('image', false)
}
})
}
// this.Quill.pasteHTML(this.currentValue)
// this.Quill.setText(this.currentValue)
let tableModule = this.Quill.getModule('better-table')
let ele = document.body.querySelector(`#${this.tableName} .ql-table`)
if (ele) {
ele.onclick = () => {
tableModule.insertTable(3, 3)
}
}
this.Quill.on('text-change', (delta, oldDelta, source) => {
const html = this.$refs.editor.children[0].innerHTML
const text = this.Quill.getText()
const quill = this.Quill
this.currentValue = html
this.$emit('input', html)
this.$emit('on-change', { html, text, quill })
})
this.Quill.on('text-change', (delta, oldDelta, source) => {
this.$emit('on-text-change', delta, oldDelta, source)
})
this.Quill.on('selection-change', (range, oldRange, source) => {
this.$emit('on-selection-change', range, oldRange, source)
})
this.Quill.on('editor-change', (eventName, ...args) => {
this.$emit('on-editor-change', eventName, ...args)
})
},
addTableButton() {
let ele = document.getElementsByClassName('.ql-better-table')
console.log('ADD table', ele)
},
//
handleBeforeUpload(file) {
const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg']
const isJPG = type.includes(file.type)
//
if (!isJPG) {
this.$message.error(`图片格式错误!`)
return false
}
//
if (this.fileSize) {
const isLt = file.size / 1024 / 1024 < this.fileSize
if (!isLt) {
this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`)
return false
}
}
return true
},
handleUploadSuccess(res, file) {
//
if (res.code == 200) {
//
let quill = this.Quill
//
let length = quill.getSelection().index
// res.url
quill.insertEmbed(length, 'image', res.data.url)
//
quill.setSelection(length + 1)
} else {
this.$message.error('图片插入失败')
}
},
handleUploadError() {
this.$message.error('图片插入失败')
},
},
}
</script>
<style>
.editor, .ql-toolbar {
white-space: pre-wrap !important;
line-height: normal !important;
.editor,
.ql-toolbar {
white-space: pre-wrap !important;
line-height: normal !important;
}
.quill-img {
display: none;
display: none;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
content: "请输入链接地址:";
.ql-snow .ql-tooltip[data-mode='link']::before {
content: '请输入链接地址:';
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: "保存";
padding-right: 0px;
border-right: 0px;
content: '保存';
padding-right: 0px;
}
.ql-snow .ql-tooltip[data-mode="video"]::before {
content: "请输入视频地址:";
.ql-snow .ql-tooltip[data-mode='video']::before {
content: '请输入视频地址:';
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: "14px";
content: '14px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
content: "10px";
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
content: '10px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
content: "18px";
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
content: '18px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
content: "32px";
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
content: '32px';
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: "文本";
content: '文本';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: "标题1";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
content: '标题1';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: "标题2";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
content: '标题2';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: "标题3";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
content: '标题3';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: "标题4";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
content: '标题4';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: "标题5";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
content: '标题5';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: "标题6";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
content: '标题6';
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: "标准字体";
content: '标准字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
content: "衬线字体";
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
content: '衬线字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
content: "等宽字体";
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
content: '等宽字体';
}
</style>

@ -1,56 +1,58 @@
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isRelogin } from '@/utils/request'
import router from "./router";
import store from "./store";
import { Message } from "element-ui";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { getToken } from "@/utils/auth";
import { isRelogin } from "@/utils/request";
NProgress.configure({ showSpinner: false })
NProgress.configure({ showSpinner: false });
const whiteList = ['/login', '/register']
const whiteList = ["/login", "/register"];
router.beforeEach((to, from, next) => {
NProgress.start()
NProgress.start();
if (getToken()) {
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
to.meta.title && store.dispatch("settings/setTitle", to.meta.title);
/* has token*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
if (to.path === "/login") {
next({ path: "/" });
NProgress.done();
} else {
if (store.getters.roles.length === 0) {
isRelogin.show = true
isRelogin.show = true;
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => {
isRelogin.show = false
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
store.dispatch('LogOut').then(() => {
Message.error(err)
next({ path: '/' })
})
store
.dispatch("GetInfo")
.then(() => {
isRelogin.show = false;
store.dispatch("GenerateRoutes").then((accessRoutes) => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes); // 动态添加可访问路由表
next({ ...to, replace: true }); // hack方法 确保addRoutes已完成
});
})
.catch((err) => {
store.dispatch("LogOut").then(() => {
Message.error(err);
next({ path: "/" });
});
});
} else {
next()
next();
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
next();
} else {
next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) // 否则全部重定向到登录页
NProgress.done()
next(`/login?redirect=${encodeURIComponent(to.fullPath)}`); // 否则全部重定向到登录页
NProgress.done();
}
}
})
});
router.afterEach(() => {
NProgress.done()
})
NProgress.done();
});

@ -1,10 +1,10 @@
import Vue from 'vue'
import Router from 'vue-router'
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router)
Vue.use(Router);
/* Layout */
import Layout from '@/layout'
import Layout from "@/layout";
/**
* Note: 路由配置项
@ -31,153 +31,153 @@ import Layout from '@/layout'
// 公共路由
export const constantRoutes = [
{
path: '/redirect',
path: "/redirect",
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect')
}
]
path: "/redirect/:path(.*)",
component: () => import("@/views/redirect"),
},
],
},
{
path: '/login',
component: () => import('@/views/login'),
hidden: true
path: "/login",
component: () => import("@/views/login"),
hidden: true,
},
{
path: '/register',
component: () => import('@/views/register'),
hidden: true
path: "/register",
component: () => import("@/views/register"),
hidden: true,
},
{
path: '/404',
component: () => import('@/views/error/404'),
hidden: true
path: "/404",
component: () => import("@/views/error/404"),
hidden: true,
},
{
path: '/401',
component: () => import('@/views/error/401'),
hidden: true
path: "/401",
component: () => import("@/views/error/401"),
hidden: true,
},
{
path: '',
path: "",
component: Layout,
redirect: 'index',
redirect: "index",
children: [
{
path: 'index',
component: () => import('@/views/index'),
name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true }
}
]
path: "index",
component: () => import("@/views/index"),
name: "Index",
meta: { title: "首页", icon: "dashboard", affix: true },
},
],
},
{
path: '/user',
path: "/user",
component: Layout,
hidden: true,
redirect: 'noredirect',
redirect: "noredirect",
children: [
{
path: 'profile',
component: () => import('@/views/system/user/profile/index'),
name: 'Profile',
meta: { title: '个人中心', icon: 'user' }
}
]
}
]
path: "profile",
component: () => import("@/views/system/user/profile/index"),
name: "Profile",
meta: { title: "个人中心", icon: "user" },
},
],
},
];
// 动态路由,基于用户权限动态去加载
export const dynamicRoutes = [
{
path: '/system/user-auth',
path: "/system/user-auth",
component: Layout,
hidden: true,
permissions: ['system:user:edit'],
permissions: ["system:user:edit"],
children: [
{
path: 'role/:userId(\\d+)',
component: () => import('@/views/system/user/authRole'),
name: 'AuthRole',
meta: { title: '分配角色', activeMenu: '/system/user' }
}
]
path: "role/:userId(\\d+)",
component: () => import("@/views/system/user/authRole"),
name: "AuthRole",
meta: { title: "分配角色", activeMenu: "/system/user" },
},
],
},
{
path: '/system/role-auth',
path: "/system/role-auth",
component: Layout,
hidden: true,
permissions: ['system:role:edit'],
permissions: ["system:role:edit"],
children: [
{
path: 'user/:roleId(\\d+)',
component: () => import('@/views/system/role/authUser'),
name: 'AuthUser',
meta: { title: '分配用户', activeMenu: '/system/role' }
}
]
path: "user/:roleId(\\d+)",
component: () => import("@/views/system/role/authUser"),
name: "AuthUser",
meta: { title: "分配用户", activeMenu: "/system/role" },
},
],
},
{
path: '/system/dict-data',
path: "/system/dict-data",
component: Layout,
hidden: true,
permissions: ['system:dict:list'],
hidden: false,
// permissions: ["system:dict:list"],
children: [
{
path: 'index/:dictId(\\d+)',
component: () => import('@/views/system/dict/data'),
name: 'Data',
meta: { title: '字典数据', activeMenu: '/system/dict' }
}
]
path: "index/:dictId(\\d+)",
component: () => import("@/views/system/dict/data"),
name: "Data",
meta: { title: "字典数据", activeMenu: "/system/dict" },
},
],
},
{
path: '/monitor/job-log',
path: "/monitor/job-log",
component: Layout,
hidden: true,
permissions: ['monitor:job:list'],
permissions: ["monitor:job:list"],
children: [
{
path: 'index/:jobId(\\d+)',
component: () => import('@/views/monitor/job/log'),
name: 'JobLog',
meta: { title: '调度日志', activeMenu: '/monitor/job' }
}
]
path: "index/:jobId(\\d+)",
component: () => import("@/views/monitor/job/log"),
name: "JobLog",
meta: { title: "调度日志", activeMenu: "/monitor/job" },
},
],
},
{
path: '/tool/gen-edit',
path: "/tool/gen-edit",
component: Layout,
hidden: true,
permissions: ['tool:gen:edit'],
permissions: ["tool:gen:edit"],
children: [
{
path: 'index/:tableId(\\d+)',
component: () => import('@/views/tool/gen/editTable'),
name: 'GenEdit',
meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
}
]
}
]
path: "index/:tableId(\\d+)",
component: () => import("@/views/tool/gen/editTable"),
name: "GenEdit",
meta: { title: "修改生成配置", activeMenu: "/tool/gen" },
},
],
},
];
// 防止连续点击多次路由报错
let routerPush = Router.prototype.push;
let routerReplace = Router.prototype.replace;
// push
Router.prototype.push = function push(location) {
return routerPush.call(this, location).catch(err => err)
}
return routerPush.call(this, location).catch((err) => err);
};
// replace
Router.prototype.replace = function push(location) {
return routerReplace.call(this, location).catch(err => err)
}
return routerReplace.call(this, location).catch((err) => err);
};
export default new Router({
mode: 'history', // 去掉url中的#
mode: "history", // 去掉url中的#
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
routes: constantRoutes,
});

@ -1,114 +1,148 @@
import axios from 'axios'
import { Notification, MessageBox, Message, Loading } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import axios from "axios";
import { Notification, MessageBox, Message, Loading } from "element-ui";
import store from "@/store";
import { getToken } from "@/utils/auth";
import errorCode from "@/utils/errorCode";
import { tansParams, blobValidate } from "@/utils/ruoyi";
import cache from '@/plugins/cache'
import { saveAs } from 'file-saver'
import cache from "@/plugins/cache";
import { saveAs } from "file-saver";
let downloadLoadingInstance;
// 是否显示重新登录
export let isRelogin = { show: false };
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
axios.defaults.headers["Content-Type"] = "application/json;charset=utf-8";
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 10000
})
timeout: 10000,
});
// request拦截器
service.interceptors.request.use(config => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
service.interceptors.request.use(
(config) => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false;
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
if (getToken() && !isToken) {
config.headers["Authorization"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
}
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
if (requestSize >= limitSize) {
console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制无法进行防重复提交验证。')
return config;
// get请求映射params参数
if (config.method === "get" && config.params) {
let url = config.url + "?" + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
if (
!isRepeatSubmit &&
(config.method === "post" || config.method === "put")
) {
const requestObj = {
url: config.url,
data:
typeof config.data === "object"
? JSON.stringify(config.data)
: config.data,
time: new Date().getTime(),
};
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
if (requestSize >= limitSize) {
console.warn(
`[${config.url}]: ` +
"请求数据大小超出允许的5M限制无法进行防重复提交验证。"
);
return config;
}
const sessionObj = cache.session.getJSON("sessionObj");
if (
sessionObj === undefined ||
sessionObj === null ||
sessionObj === ""
) {
cache.session.setJSON("sessionObj", requestObj);
} else {
cache.session.setJSON('sessionObj', requestObj)
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (
s_data === requestObj.data &&
requestObj.time - s_time < interval &&
s_url === requestObj.url
) {
const message = "数据正在处理,请勿重复提交";
console.warn(`[${s_url}]: ` + message);
return Promise.reject(new Error(message));
} else {
cache.session.setJSON("sessionObj", requestObj);
}
}
}
return config;
},
(error) => {
console.log(error);
Promise.reject(error);
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
);
// 响应拦截器
service.interceptors.response.use(res => {
service.interceptors.response.use(
(res) => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
const msg = errorCode[code] || res.data.msg || errorCode["default"];
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
if (
res.request.responseType === "blob" ||
res.request.responseType === "arraybuffer"
) {
return res.data;
}
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true;
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin.show = false;
store.dispatch('LogOut').then(() => {
location.href = '/index';
MessageBox.confirm(
"登录状态已过期,您可以继续留在该页面,或者重新登录",
"系统提示",
{
confirmButtonText: "重新登录",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
isRelogin.show = false;
store.dispatch("LogOut").then(() => {
location.href = "/index";
});
})
}).catch(() => {
isRelogin.show = false;
});
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
.catch(() => {
isRelogin.show = false;
});
}
return Promise.reject("无效的会话,或者会话已过期,请重新登录。");
} else if (code === 500) {
Message({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
Message({ message: msg, type: "error" });
return Promise.reject(new Error(msg));
} else if (code === 601) {
Message({ message: msg, type: 'warning' })
return Promise.reject('error')
Message({ message: msg, type: "warning" });
return Promise.reject("error");
} else if (code !== 200) {
Notification.error({ title: msg })
return Promise.reject('error')
Notification.error({ title: msg });
return Promise.reject("error");
} else {
return res.data
return res.data;
}
},
error => {
console.log('err' + error)
(error) => {
console.log("err" + error);
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
@ -117,36 +151,48 @@ service.interceptors.response.use(res => {
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
Message({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error)
Message({ message: message, type: "error", duration: 5 * 1000 });
return Promise.reject(error);
}
)
);
// 通用下载方法
export function download(url, params, filename, config) {
downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", })
return service.post(url, params, {
transformRequest: [(params) => { return tansParams(params) }],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob',
...config
}).then(async (data) => {
const isBlob = blobValidate(data);
if (isBlob) {
const blob = new Blob([data])
saveAs(blob, filename)
} else {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
Message.error(errMsg);
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
Message.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
})
downloadLoadingInstance = Loading.service({
text: "正在下载数据,请稍候",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)",
});
return service
.post(url, params, {
transformRequest: [
(params) => {
return tansParams(params);
},
],
headers: { "Content-Type": "application/x-www-form-urlencoded" },
responseType: "blob",
...config,
})
.then(async (data) => {
const isBlob = blobValidate(data);
if (isBlob) {
const blob = new Blob([data]);
saveAs(blob, filename);
} else {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg =
errorCode[rspObj.code] || rspObj.msg || errorCode["default"];
Message.error(errMsg);
}
downloadLoadingInstance.close();
})
.catch((r) => {
console.error(r);
Message.error("下载文件出现错误,请联系管理员!");
downloadLoadingInstance.close();
});
}
export default service
export default service;

@ -1,219 +1,251 @@
<template>
<div class="login">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">花至后台管理系统</h3>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
type="text"
auto-complete="off"
placeholder="账号"
>
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
auto-complete="off"
placeholder="密码"
@keyup.enter.native="handleLogin"
>
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaEnabled">
<el-input
v-model="loginForm.code"
auto-complete="off"
placeholder="验证码"
style="width: 63%"
@keyup.enter.native="handleLogin"
>
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;"></el-checkbox>
<el-form-item style="width:100%;">
<el-button
:loading="loading"
size="medium"
type="primary"
style="width:100%;"
@click.native.prevent="handleLogin"
>
<span v-if="!loading"> </span>
<span v-else> ...</span>
</el-button>
<div style="float: right;" v-if="register">
<router-link class="link-type" :to="'/register'">立即注册</router-link>
</div>
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span>
</div>
</div>
<div class="login">
<el-form
ref="loginForm"
:model="loginForm"
:rules="loginRules"
class="login-form"
>
<h3 class="title">花至后台管理系统</h3>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
type="text"
auto-complete="off"
placeholder="账号"
>
<svg-icon
slot="prefix"
icon-class="user"
class="el-input__icon input-icon"
/>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
auto-complete="off"
placeholder="密码"
@keyup.enter.native="handleLogin"
>
<svg-icon
slot="prefix"
icon-class="password"
class="el-input__icon input-icon"
/>
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaEnabled">
<el-input
v-model="loginForm.code"
auto-complete="off"
placeholder="验证码"
style="width: 63%"
@keyup.enter.native="handleLogin"
>
<svg-icon
slot="prefix"
icon-class="validCode"
class="el-input__icon input-icon"
/>
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img" />
</div>
</el-form-item>
<el-checkbox
v-model="loginForm.rememberMe"
style="margin: 0px 0px 25px 0px"
>记住密码</el-checkbox
>
<el-form-item style="width: 100%">
<el-button
:loading="loading"
size="medium"
type="primary"
style="width: 100%"
@click.native.prevent="handleLogin"
>
<span v-if="!loading"> </span>
<span v-else> ...</span>
</el-button>
<div style="float: right" v-if="register">
<router-link class="link-type" :to="'/register'"
>立即注册</router-link
>
</div>
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span>
</div>
</div>
</template>
<script>
import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie";
import { getCodeImg } from '@/api/login'
import Cookies from 'js-cookie'
import { encrypt, decrypt } from '@/utils/jsencrypt'
export default {
name: "Login",
data() {
return {
codeUrl: "",
loginForm: {
username: "admin",
password: "admin123",
rememberMe: false,
code: "",
uuid: ""
},
loginRules: {
username: [
{ required: true, trigger: "blur", message: "请输入您的账号" }
],
password: [
{ required: true, trigger: "blur", message: "请输入您的密码" }
],
code: [{ required: true, trigger: "change", message: "请输入验证码" }]
},
loading: false,
//
captchaEnabled: true,
//
register: false,
redirect: undefined
};
},
watch: {
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect;
},
immediate: true
}
},
created() {
this.getCode();
this.getCookie();
},
methods: {
getCode() {
getCodeImg().then(res => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
if (this.captchaEnabled) {
this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid;
}
});
},
getCookie() {
const username = Cookies.get("username");
const password = Cookies.get("password");
const rememberMe = Cookies.get('rememberMe')
this.loginForm = {
username: username === undefined ? this.loginForm.username : username,
password: password === undefined ? this.loginForm.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
};
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true;
if (this.loginForm.rememberMe) {
Cookies.set("username", this.loginForm.username, { expires: 30 });
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
} else {
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove('rememberMe');
}
this.$store.dispatch("Login", this.loginForm).then(() => {
this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
}).catch(() => {
this.loading = false;
if (this.captchaEnabled) {
this.getCode();
}
});
}
});
}
}
};
name: 'Login',
data() {
return {
codeUrl: '',
loginForm: {
username: 'admin',
password: 'admin123',
rememberMe: false,
code: '',
uuid: '',
},
loginRules: {
username: [
{ required: true, trigger: 'blur', message: '请输入您的账号' },
],
password: [
{ required: true, trigger: 'blur', message: '请输入您的密码' },
],
code: [{ required: true, trigger: 'change', message: '请输入验证码' }],
},
loading: false,
//
captchaEnabled: true,
//
register: false,
redirect: undefined,
}
},
watch: {
$route: {
handler: function (route) {
this.redirect = route.query && route.query.redirect
},
immediate: true,
},
},
created() {
this.getCode()
this.getCookie()
},
methods: {
getCode() {
getCodeImg().then((res) => {
this.captchaEnabled =
res.captchaEnabled === undefined ? true : res.captchaEnabled
if (this.captchaEnabled) {
this.codeUrl = 'data:image/gif;base64,' + res.img
this.loginForm.uuid = res.uuid
}
})
},
getCookie() {
const username = Cookies.get('username')
const password = Cookies.get('password')
const rememberMe = Cookies.get('rememberMe')
this.loginForm = {
username: username === undefined ? this.loginForm.username : username,
password:
password === undefined ? this.loginForm.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
}
},
handleLogin() {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.loading = true
if (this.loginForm.rememberMe) {
Cookies.set('username', this.loginForm.username, { expires: 30 })
Cookies.set('password', encrypt(this.loginForm.password), {
expires: 30,
})
Cookies.set('rememberMe', this.loginForm.rememberMe, {
expires: 30,
})
} else {
Cookies.remove('username')
Cookies.remove('password')
Cookies.remove('rememberMe')
}
this.$store
.dispatch('Login', this.loginForm)
.then(() => {
this.$router.push({ path: this.redirect || '/' }).catch(() => {})
})
.catch(() => {
this.loading = false
if (this.captchaEnabled) {
this.getCode()
}
})
}
})
},
},
}
</script>
<style rel="stylesheet/scss" lang="scss">
.login {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: url("../assets/images/login-background.jpg");
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: url('../assets/images/login-background.jpg');
background-size: cover;
}
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
}
.login-form {
border-radius: 6px;
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
.el-input {
height: 38px;
input {
height: 38px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 2px;
}
border-radius: 6px;
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
.el-input {
height: 38px;
input {
height: 38px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 2px;
}
}
.login-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.login-code {
width: 33%;
height: 38px;
float: right;
img {
cursor: pointer;
vertical-align: middle;
}
width: 33%;
height: 38px;
float: right;
img {
cursor: pointer;
vertical-align: middle;
}
}
.el-login-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial;
font-size: 12px;
letter-spacing: 1px;
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial;
font-size: 12px;
letter-spacing: 1px;
}
.login-code-img {
height: 38px;
height: 38px;
}
</style>

File diff suppressed because it is too large Load Diff

@ -1,15 +1,23 @@
'use strict'
const path = require('path')
"use strict";
const path = require("path");
function resolve(dir) {
return path.join(__dirname, dir)
return path.join(__dirname, dir);
}
const CompressionPlugin = require('compression-webpack-plugin')
const CompressionPlugin = require("compression-webpack-plugin");
const name = process.env.VUE_APP_TITLE || '花至管理系统' // 网页标题
const name = process.env.VUE_APP_TITLE || "花至管理系统"; // 网页标题
const port = process.env.port || process.env.npm_config_port || 80 // 端口
const port = process.env.port || process.env.npm_config_port || 80; // 端口
function getTargetUrl() {
if (process.env.NODE_ENV === "local") {
return "http://110.41.134.131:8080";
} else {
return `http://localhost:8080`;
}
}
// vue.config.js 配置说明
//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
@ -20,115 +28,116 @@ module.exports = {
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
// 在npm run build 或 yarn build 时 生成文件的目录名称要和baseUrl的生产环境路径一致默认dist
outputDir: 'dist',
outputDir: "dist",
// 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
assetsDir: 'static',
assetsDir: "static",
// 是否开启eslint保存检测有效值ture | false | 'error'
lintOnSave: process.env.NODE_ENV === 'development',
lintOnSave: process.env.NODE_ENV === "development",
// 如果你不需要生产环境的 source map可以将其设置为 false 以加速生产环境构建。
productionSourceMap: false,
// webpack-dev-server 相关配置
devServer: {
host: '0.0.0.0',
host: "0.0.0.0",
port: port,
open: true,
proxy: {
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
target: `http://localhost:8080`,
// target: `http://110.41.134.131:8080`,
target: `http://192.168.10.147:8080`,
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
}
["^" + process.env.VUE_APP_BASE_API]: "",
},
},
},
disableHostCheck: true
disableHostCheck: true,
},
css: {
loaderOptions: {
sass: {
sassOptions: { outputStyle: "expanded" }
}
}
sassOptions: { outputStyle: "expanded" },
},
},
},
configureWebpack: {
name: name,
resolve: {
alias: {
'@': resolve('src')
}
"@": resolve("src"),
},
},
plugins: [
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
new CompressionPlugin({
cache: false, // 不启用文件缓存
test: /\.(js|css|html)?$/i, // 压缩文件格式
filename: '[path].gz[query]', // 压缩后的文件名
algorithm: 'gzip', // 使用gzip压缩
minRatio: 0.8 // 压缩率小于1才会压缩
})
cache: false, // 不启用文件缓存
test: /\.(js|css|html)?$/i, // 压缩文件格式
filename: "[path].gz[query]", // 压缩后的文件名
algorithm: "gzip", // 使用gzip压缩
minRatio: 0.8, // 压缩率小于1才会压缩
}),
],
},
chainWebpack(config) {
config.plugins.delete('preload') // TODO: need test
config.plugins.delete('prefetch') // TODO: need test
config.plugins.delete("preload"); // TODO: need test
config.plugins.delete("prefetch"); // TODO: need test
// set svg-sprite-loader
config.module.rule("svg").exclude.add(resolve("src/assets/icons")).end();
config.module
.rule('svg')
.exclude.add(resolve('src/assets/icons'))
.end()
config.module
.rule('icons')
.rule("icons")
.test(/\.svg$/)
.include.add(resolve('src/assets/icons'))
.include.add(resolve("src/assets/icons"))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.use("svg-sprite-loader")
.loader("svg-sprite-loader")
.options({
symbolId: 'icon-[name]'
symbolId: "icon-[name]",
})
.end()
.end();
config.when(process.env.NODE_ENV !== 'development', config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
config.when(process.env.NODE_ENV !== "development", (config) => {
config
.plugin("ScriptExtHtmlWebpackPlugin")
.after("html")
.use("script-ext-html-webpack-plugin", [
{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}])
.end()
inline: /runtime\..*\.js$/,
},
])
.end();
config.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
priority: 20 // the weight needs to be larger than libs and app or it will be packaged into libs or app
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
})
config.optimization.splitChunks({
chunks: "all",
cacheGroups: {
libs: {
name: "chunk-libs",
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: "initial", // only package third parties that are initially dependent
},
elementUI: {
name: "chunk-elementUI", // split elementUI into a single package
test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
},
commons: {
name: "chunk-commons",
test: resolve("src/components"), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true,
},
},
});
config.optimization.runtimeChunk('single'),
{
from: path.resolve(__dirname, './public/robots.txt'), //防爬虫文件
to: './' //到根目录下
}
})
}
}
config.optimization.runtimeChunk("single"),
{
from: path.resolve(__dirname, "./public/robots.txt"), //防爬虫文件
to: "./", //到根目录下
};
});
},
};

Loading…
Cancel
Save