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' ENV = 'development'
# 花至管理系统/开发环境 # 花至管理系统/开发环境
VUE_APP_BASE_API = '/dev-api' VUE_APP_BASE_API = '/prod-api'
# 路由懒加载 # 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true VUE_CLI_BABEL_TRANSPILE_MODULES = true
# 设置端口号
port=8080

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

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

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

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

@ -1,114 +1,148 @@
import axios from 'axios' import axios from "axios";
import { Notification, MessageBox, Message, Loading } from 'element-ui' import { Notification, MessageBox, Message, Loading } from "element-ui";
import store from '@/store' import store from "@/store";
import { getToken } from '@/utils/auth' import { getToken } from "@/utils/auth";
import errorCode from '@/utils/errorCode' import errorCode from "@/utils/errorCode";
import { tansParams, blobValidate } from "@/utils/ruoyi"; import { tansParams, blobValidate } from "@/utils/ruoyi";
import cache from '@/plugins/cache' import cache from "@/plugins/cache";
import { saveAs } from 'file-saver' import { saveAs } from "file-saver";
let downloadLoadingInstance; let downloadLoadingInstance;
// 是否显示重新登录 // 是否显示重新登录
export let isRelogin = { show: false }; 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实例 // 创建axios实例
const service = axios.create({ const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分 // axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API, baseURL: process.env.VUE_APP_BASE_API,
// 超时 // 超时
timeout: 10000 timeout: 10000,
}) });
// request拦截器 // request拦截器
service.interceptors.request.use(config => { service.interceptors.request.use(
// 是否需要设置 token (config) => {
const isToken = (config.headers || {}).isToken === false // 是否需要设置 token
// 是否需要防止数据重复提交 const isToken = (config.headers || {}).isToken === false;
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false // 是否需要防止数据重复提交
if (getToken() && !isToken) { const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 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()
} }
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小 // get请求映射params参数
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M if (config.method === "get" && config.params) {
if (requestSize >= limitSize) { let url = config.url + "?" + tansParams(config.params);
console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制无法进行防重复提交验证。') url = url.slice(0, -1);
return config; config.params = {};
config.url = url;
} }
const sessionObj = cache.session.getJSON('sessionObj') if (
if (sessionObj === undefined || sessionObj === null || sessionObj === '') { !isRepeatSubmit &&
cache.session.setJSON('sessionObj', requestObj) (config.method === "post" || config.method === "put")
} else { ) {
const s_url = sessionObj.url; // 请求地址 const requestObj = {
const s_data = sessionObj.data; // 请求数据 url: config.url,
const s_time = sessionObj.time; // 请求时间 data:
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 typeof config.data === "object"
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { ? JSON.stringify(config.data)
const message = '数据正在处理,请勿重复提交'; : config.data,
console.warn(`[${s_url}]: ` + message) time: new Date().getTime(),
return Promise.reject(new Error(message)) };
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 { } 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 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') { if (
return res.data res.request.responseType === "blob" ||
res.request.responseType === "arraybuffer"
) {
return res.data;
} }
if (code === 401) { if (code === 401) {
if (!isRelogin.show) { if (!isRelogin.show) {
isRelogin.show = true; isRelogin.show = true;
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { MessageBox.confirm(
isRelogin.show = false; "登录状态已过期,您可以继续留在该页面,或者重新登录",
store.dispatch('LogOut').then(() => { "系统提示",
location.href = '/index'; {
confirmButtonText: "重新登录",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
isRelogin.show = false;
store.dispatch("LogOut").then(() => {
location.href = "/index";
});
}) })
}).catch(() => { .catch(() => {
isRelogin.show = false; isRelogin.show = false;
}); });
} }
return Promise.reject('无效的会话,或者会话已过期,请重新登录。') return Promise.reject("无效的会话,或者会话已过期,请重新登录。");
} else if (code === 500) { } else if (code === 500) {
Message({ message: msg, type: 'error' }) Message({ message: msg, type: "error" });
return Promise.reject(new Error(msg)) return Promise.reject(new Error(msg));
} else if (code === 601) { } else if (code === 601) {
Message({ message: msg, type: 'warning' }) Message({ message: msg, type: "warning" });
return Promise.reject('error') return Promise.reject("error");
} else if (code !== 200) { } else if (code !== 200) {
Notification.error({ title: msg }) Notification.error({ title: msg });
return Promise.reject('error') return Promise.reject("error");
} else { } else {
return res.data return res.data;
} }
}, },
error => { (error) => {
console.log('err' + error) console.log("err" + error);
let { message } = error; let { message } = error;
if (message == "Network Error") { if (message == "Network Error") {
message = "后端接口连接异常"; message = "后端接口连接异常";
@ -117,36 +151,48 @@ service.interceptors.response.use(res => {
} else if (message.includes("Request failed with status code")) { } else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常"; message = "系统接口" + message.substr(message.length - 3) + "异常";
} }
Message({ message: message, type: 'error', duration: 5 * 1000 }) Message({ message: message, type: "error", duration: 5 * 1000 });
return Promise.reject(error) return Promise.reject(error);
} }
) );
// 通用下载方法 // 通用下载方法
export function download(url, params, filename, config) { export function download(url, params, filename, config) {
downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) downloadLoadingInstance = Loading.service({
return service.post(url, params, { text: "正在下载数据,请稍候",
transformRequest: [(params) => { return tansParams(params) }], spinner: "el-icon-loading",
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, background: "rgba(0, 0, 0, 0.7)",
responseType: 'blob', });
...config return service
}).then(async (data) => { .post(url, params, {
const isBlob = blobValidate(data); transformRequest: [
if (isBlob) { (params) => {
const blob = new Blob([data]) return tansParams(params);
saveAs(blob, filename) },
} else { ],
const resText = await data.text(); headers: { "Content-Type": "application/x-www-form-urlencoded" },
const rspObj = JSON.parse(resText); responseType: "blob",
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] ...config,
Message.error(errMsg); })
} .then(async (data) => {
downloadLoadingInstance.close(); const isBlob = blobValidate(data);
}).catch((r) => { if (isBlob) {
console.error(r) const blob = new Blob([data]);
Message.error('下载文件出现错误,请联系管理员!') saveAs(blob, filename);
downloadLoadingInstance.close(); } 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> <template>
<div class="login"> <div class="login">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form"> <el-form
<h3 class="title">花至后台管理系统</h3> ref="loginForm"
<el-form-item prop="username"> :model="loginForm"
<el-input :rules="loginRules"
v-model="loginForm.username" class="login-form"
type="text" >
auto-complete="off" <h3 class="title">花至后台管理系统</h3>
placeholder="账号" <el-form-item prop="username">
> <el-input
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" /> v-model="loginForm.username"
</el-input> type="text"
</el-form-item> auto-complete="off"
<el-form-item prop="password"> placeholder="账号"
<el-input >
v-model="loginForm.password" <svg-icon
type="password" slot="prefix"
auto-complete="off" icon-class="user"
placeholder="密码" class="el-input__icon input-icon"
@keyup.enter.native="handleLogin" />
> </el-input>
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> </el-form-item>
</el-input> <el-form-item prop="password">
</el-form-item> <el-input
<el-form-item prop="code" v-if="captchaEnabled"> v-model="loginForm.password"
<el-input type="password"
v-model="loginForm.code" auto-complete="off"
auto-complete="off" placeholder="密码"
placeholder="验证码" @keyup.enter.native="handleLogin"
style="width: 63%" >
@keyup.enter.native="handleLogin" <svg-icon
> slot="prefix"
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> icon-class="password"
</el-input> class="el-input__icon input-icon"
<div class="login-code"> />
<img :src="codeUrl" @click="getCode" class="login-code-img"/> </el-input>
</div> </el-form-item>
</el-form-item> <el-form-item prop="code" v-if="captchaEnabled">
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;"></el-checkbox> <el-input
<el-form-item style="width:100%;"> v-model="loginForm.code"
<el-button auto-complete="off"
:loading="loading" placeholder="验证码"
size="medium" style="width: 63%"
type="primary" @keyup.enter.native="handleLogin"
style="width:100%;" >
@click.native.prevent="handleLogin" <svg-icon
> slot="prefix"
<span v-if="!loading"> </span> icon-class="validCode"
<span v-else> ...</span> class="el-input__icon input-icon"
</el-button> />
<div style="float: right;" v-if="register"> </el-input>
<router-link class="link-type" :to="'/register'">立即注册</router-link> <div class="login-code">
</div> <img :src="codeUrl" @click="getCode" class="login-code-img" />
</el-form-item> </div>
</el-form> </el-form-item>
<!-- 底部 --> <el-checkbox
<div class="el-login-footer"> v-model="loginForm.rememberMe"
<span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span> style="margin: 0px 0px 25px 0px"
</div> >记住密码</el-checkbox
</div> >
<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> </template>
<script> <script>
import { getCodeImg } from "@/api/login"; import { getCodeImg } from '@/api/login'
import Cookies from "js-cookie"; import Cookies from 'js-cookie'
import { encrypt, decrypt } from '@/utils/jsencrypt' import { encrypt, decrypt } from '@/utils/jsencrypt'
export default { export default {
name: "Login", name: 'Login',
data() { data() {
return { return {
codeUrl: "", codeUrl: '',
loginForm: { loginForm: {
username: "admin", username: 'admin',
password: "admin123", password: 'admin123',
rememberMe: false, rememberMe: false,
code: "", code: '',
uuid: "" uuid: '',
}, },
loginRules: { loginRules: {
username: [ username: [
{ required: true, trigger: "blur", message: "请输入您的账号" } { required: true, trigger: 'blur', message: '请输入您的账号' },
], ],
password: [ password: [
{ required: true, trigger: "blur", message: "请输入您的密码" } { required: true, trigger: 'blur', message: '请输入您的密码' },
], ],
code: [{ required: true, trigger: "change", message: "请输入验证码" }] code: [{ required: true, trigger: 'change', message: '请输入验证码' }],
}, },
loading: false, loading: false,
// //
captchaEnabled: true, captchaEnabled: true,
// //
register: false, register: false,
redirect: undefined redirect: undefined,
}; }
}, },
watch: { watch: {
$route: { $route: {
handler: function(route) { handler: function (route) {
this.redirect = route.query && route.query.redirect; this.redirect = route.query && route.query.redirect
}, },
immediate: true immediate: true,
} },
}, },
created() { created() {
this.getCode(); this.getCode()
this.getCookie(); this.getCookie()
}, },
methods: { methods: {
getCode() { getCode() {
getCodeImg().then(res => { getCodeImg().then((res) => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled; this.captchaEnabled =
if (this.captchaEnabled) { res.captchaEnabled === undefined ? true : res.captchaEnabled
this.codeUrl = "data:image/gif;base64," + res.img; if (this.captchaEnabled) {
this.loginForm.uuid = res.uuid; this.codeUrl = 'data:image/gif;base64,' + res.img
} this.loginForm.uuid = res.uuid
}); }
}, })
getCookie() { },
const username = Cookies.get("username"); getCookie() {
const password = Cookies.get("password"); const username = Cookies.get('username')
const rememberMe = Cookies.get('rememberMe') const password = Cookies.get('password')
this.loginForm = { const rememberMe = Cookies.get('rememberMe')
username: username === undefined ? this.loginForm.username : username, this.loginForm = {
password: password === undefined ? this.loginForm.password : decrypt(password), username: username === undefined ? this.loginForm.username : username,
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe) password:
}; password === undefined ? this.loginForm.password : decrypt(password),
}, rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
handleLogin() { }
this.$refs.loginForm.validate(valid => { },
if (valid) { handleLogin() {
this.loading = true; this.$refs.loginForm.validate((valid) => {
if (this.loginForm.rememberMe) { if (valid) {
Cookies.set("username", this.loginForm.username, { expires: 30 }); this.loading = true
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 }); if (this.loginForm.rememberMe) {
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 }); Cookies.set('username', this.loginForm.username, { expires: 30 })
} else { Cookies.set('password', encrypt(this.loginForm.password), {
Cookies.remove("username"); expires: 30,
Cookies.remove("password"); })
Cookies.remove('rememberMe'); Cookies.set('rememberMe', this.loginForm.rememberMe, {
} expires: 30,
this.$store.dispatch("Login", this.loginForm).then(() => { })
this.$router.push({ path: this.redirect || "/" }).catch(()=>{}); } else {
}).catch(() => { Cookies.remove('username')
this.loading = false; Cookies.remove('password')
if (this.captchaEnabled) { Cookies.remove('rememberMe')
this.getCode(); }
} this.$store
}); .dispatch('Login', this.loginForm)
} .then(() => {
}); this.$router.push({ path: this.redirect || '/' }).catch(() => {})
} })
} .catch(() => {
}; this.loading = false
if (this.captchaEnabled) {
this.getCode()
}
})
}
})
},
},
}
</script> </script>
<style rel="stylesheet/scss" lang="scss"> <style rel="stylesheet/scss" lang="scss">
.login { .login {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100%; height: 100%;
background-image: url("../assets/images/login-background.jpg"); background-image: url('../assets/images/login-background.jpg');
background-size: cover; background-size: cover;
} }
.title { .title {
margin: 0px auto 30px auto; margin: 0px auto 30px auto;
text-align: center; text-align: center;
color: #707070; color: #707070;
} }
.login-form { .login-form {
border-radius: 6px; border-radius: 6px;
background: #ffffff; background: #ffffff;
width: 400px; width: 400px;
padding: 25px 25px 5px 25px; padding: 25px 25px 5px 25px;
.el-input { .el-input {
height: 38px; height: 38px;
input { input {
height: 38px; height: 38px;
} }
} }
.input-icon { .input-icon {
height: 39px; height: 39px;
width: 14px; width: 14px;
margin-left: 2px; margin-left: 2px;
} }
} }
.login-tip { .login-tip {
font-size: 13px; font-size: 13px;
text-align: center; text-align: center;
color: #bfbfbf; color: #bfbfbf;
} }
.login-code { .login-code {
width: 33%; width: 33%;
height: 38px; height: 38px;
float: right; float: right;
img { img {
cursor: pointer; cursor: pointer;
vertical-align: middle; vertical-align: middle;
} }
} }
.el-login-footer { .el-login-footer {
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
text-align: center; text-align: center;
color: #fff; color: #fff;
font-family: Arial; font-family: Arial;
font-size: 12px; font-size: 12px;
letter-spacing: 1px; letter-spacing: 1px;
} }
.login-code-img { .login-code-img {
height: 38px; height: 38px;
} }
</style> </style>

File diff suppressed because it is too large Load Diff

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

Loading…
Cancel
Save