first commit

This commit is contained in:
2026-01-18 09:52:48 +08:00
commit 836bdc9409
584 changed files with 40891 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
package-lock.json
yarn.lock
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 sakuya
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
### sentcms后台管理系统
1.安装依赖
~~~
npm install
~~~
2.启动项目
~~~
npm run dev
~~~
3.打包项目
~~~
npm run build
~~~
+111
View File
@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="/favicon.ico">
<title>Admin</title>
<script type="text/javascript">
document.write("<script src='admin.js?"+new Date().getTime()+"'><\/script>");
</script>
</head>
<body>
<noscript>
<strong>We're sorry but Admin doesn't work properly without JavaScript
enabled. Please enable it to continue.</strong>
</noscript>
<script type="text/javascript">
var dark = window.localStorage.getItem('APP_DARK');
if(dark){
document.documentElement.classList.add("dark")
}
</script>
<div id="app" class="aminui">
<div class="app-loading">
<div class="app-loading__logo">
<img src="/static/images/logo.png"/>
</div>
<div class="app-loading__loader"></div>
<div class="app-loading__title">Admin</div>
</div>
<style>
.app-loading {position: absolute;top:0px;left:0px;right:0px;bottom:0px;display: flex;justify-content: center;align-items: center;flex-direction: column;background: #fff;}
.app-loading__logo {margin-bottom: 30px;}
.app-loading__logo img {width: 90px;vertical-align: bottom;}
.app-loading__loader {box-sizing: border-box;width: 35px;height: 35px;border: 5px solid transparent;border-top-color: #000;border-radius: 50%;animation: .5s loader linear infinite;position: relative;}
.app-loading__loader:before {box-sizing: border-box;content: '';display: block;width: inherit;height: inherit;position: absolute;top: -5px;left: -5px;border: 5px solid #ccc;border-radius: 50%;opacity: .5;}
.app-loading__title {font-size: 24px;color: #333;margin-top: 30px;}
.dark .app-loading {background: #222225;}
.dark .app-loading__loader {border-top-color: #fff;}
.dark .app-loading__title {color: #d0d0d0;}
@keyframes loader {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</div>
<!-- built files will be auto injected -->
</body>
<div id="versionCheck" style="display: none;position: absolute;z-index: 99;top:0;left:0;right:0;bottom:0;padding:40px;background:rgba(255,255,255,0.9);color: #333;">
<h2 style="line-height: 1;margin: 0;font-size: 24px;">当前使用的浏览器内核版本过低 :(</h2>
<p style="line-height: 1;margin: 0;font-size: 14px;margin-top: 20px;opacity: 0.8;">当前版本:<span id="versionCheck-type">--</span> <span id="versionCheck-version">--</span></p>
<p style="line-height: 1;margin: 0;font-size: 14px;margin-top: 10px;opacity: 0.8;">最低版本要求:Chrome 71+、Firefox 65+、Safari 12+、Edge 97+。</p>
<p style="line-height: 1;margin: 0;font-size: 14px;margin-top: 10px;opacity: 0.8;">请升级浏览器版本,或更换现代浏览器,如果你使用的是双核浏览器,请切换到极速/高速模式。</p>
</div>
<script type="text/javascript">
function getBrowerInfo(){
var userAgent = window.navigator.userAgent;
var browerInfo = {
type: 'unknown',
version: 'unknown',
userAgent: userAgent
};
if(document.documentMode){
browerInfo.type = "IE"
browerInfo.version = document.documentMode + ''
}else if(indexOf(userAgent, "Firefox")){
browerInfo.type = "Firefox"
browerInfo.version = userAgent.match(/Firefox\/([\d.]+)/)[1]
}else if(indexOf(userAgent, "Opera")){
browerInfo.type = "Opera"
browerInfo.version = userAgent.match(/Opera\/([\d.]+)/)[1]
}else if(indexOf(userAgent, "Edg")){
browerInfo.type = "Edg"
browerInfo.version = userAgent.match(/Edg\/([\d.]+)/)[1]
}else if(indexOf(userAgent, "Chrome")){
browerInfo.type = "Chrome"
browerInfo.version = userAgent.match(/Chrome\/([\d.]+)/)[1]
}else if(indexOf(userAgent, "Safari")){
browerInfo.type = "Safari"
browerInfo.version = userAgent.match(/Safari\/([\d.]+)/)[1]
}
return browerInfo
}
function indexOf(userAgent, brower) {
return userAgent.indexOf(brower) > -1
}
function isSatisfyBrower(){
var minVer = {
"Chrome": 71,
"Firefox": 65,
"Safari": 12,
"Edg": 97,
"IE": 999
}
var browerInfo = getBrowerInfo()
var materVer = browerInfo.version.split('.')[0]
return materVer >= minVer[browerInfo.type]
}
if(!isSatisfyBrower()){
document.getElementById('versionCheck').style.display = 'block';
document.getElementById('versionCheck-type').innerHTML = getBrowerInfo().type;
document.getElementById('versionCheck-version').innerHTML = getBrowerInfo().version;
}
</script>
<script type="module" src="/src/main.js"></script>
</html>
+41
View File
@@ -0,0 +1,41 @@
{
"name": "sentos-admin",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@ckeditor/ckeditor5-vue": "^7.2.0",
"@element-plus/icons-vue": "^2.3.1",
"aieditor": "^1.3.5",
"axios": "^1.7.2",
"ckeditor5": "^43.2.0",
"codemirror": "5.65.5",
"core-js": "3.29.0",
"cropperjs": "1.5.13",
"crypto-js": "4.2.0",
"echarts": "5.6.0",
"element-plus": "2.8.4",
"nprogress": "0.2.0",
"pinyin-match": "^1.2.4",
"qrcodejs2": "0.0.2",
"sortablejs": "1.15.0",
"vue": "^3.4.21",
"vue-i18n": "^9.13.1",
"vue-router": "^4.3.2",
"vuedraggable": "^4.0.3",
"vuex": "^4.1.0",
"xgplayer": "2.32.2",
"xgplayer-hls": "2.5.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"sass": "1.77.2",
"terser": "^5.31.0",
"vite": "^5.2.0"
}
}
+11
View File
@@ -0,0 +1,11 @@
// 此文件非必要,在生产环境下此文件配置可覆盖运行配置,开发环境下不起效
// 详情见 src/config/index.js
const APP_CONFIG = {
//标题
APP_NAME: "后台管理系统",
//接口地址,如遇跨域需使用nginx代理
API_URL: "http://www.sentcms.com/admin/"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 750 750" style="enable-background:new 0 0 750 750;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.35;fill:#B3B3B3;}
.st1{opacity:0.1;fill:#B3B3B3;}
.st2{opacity:0.3;fill:#B3B3B3;}
.st3{opacity:0.1;}
.st4{fill:#B3B3B3;}
</style>
<g>
<path class="st0" d="M465.1,261.4H264c-1.3,0-2.4,1.1-2.4,2.4v255.6c0,1.3,1.1,2.4,2.4,2.4h201.1c1.3,0,2.4-1.1,2.4-2.4V263.8
C467.5,262.4,466.4,261.4,465.1,261.4z M417.9,443c0,1.3-1.1,2.4-2.4,2.4h-102c-1.3,0-2.4-1.1-2.4-2.4v-11.3c0-1.3,1.1-2.4,2.4-2.4
h102c1.3,0,2.4,1.1,2.4,2.4V443z M417.9,397.2c0,1.3-1.1,2.4-2.4,2.4h-102c-1.3,0-2.4-1.1-2.4-2.4v-11.3c0-1.3,1.1-2.4,2.4-2.4h102
c1.3,0,2.4,1.1,2.4,2.4V397.2z M417.9,351.5c0,1.3-1.1,2.4-2.4,2.4h-102c-1.3,0-2.4-1.1-2.4-2.4v-11.3c0-1.3,1.1-2.4,2.4-2.4h102
c1.3,0,2.4,1.1,2.4,2.4V351.5z"/>
<g>
<path class="st1" d="M462.1,236.8L462.1,236.8C384.8,236.2,321,295.1,314,370.7c-18.5-19.1-44.4-31.1-73.1-31.3h0
c-56.8-0.4-103.2,45.3-103.6,102.1l-0.8,101.4l175.6,1.3l30.1,0.2l265.1,2l1.2-160.9C609.2,304,543.6,237.4,462.1,236.8z"/>
<path class="st2" d="M216.9,227.4c-3.4,0-6.5,1.1-9,2.9c0.2-1,0.3-2,0.3-3c0.1-8.3-6.6-15.1-15-15.2s-15.1,6.6-15.2,15
c0,0.3,0,0.6,0,0.9c-1.6-0.6-3.4-1-5.2-1c-8.3-0.1-15.1,6.6-15.2,15c-0.1,8.2,6.4,14.9,14.5,15.2l0,0l44.6,0.3
c8.3,0.1,15.1-6.6,15.2-15S225.2,227.5,216.9,227.4z"/>
<path class="st2" d="M596.4,194.2c-3.4,0-6.5,1.1-9,2.9c0.2-1,0.3-2,0.3-3c0.1-8.3-6.6-15.1-15-15.2s-15.1,6.6-15.2,15
c0,0.3,0,0.6,0,0.9c-1.6-0.6-3.4-1-5.2-1c-8.3-0.1-15.1,6.6-15.2,15c-0.1,8.2,6.4,14.9,14.5,15.2l0,0l44.6,0.3
c8.3,0.1,15.1-6.6,15.2-15S604.7,194.3,596.4,194.2z"/>
<g>
<g class="st3">
<path class="st4" d="M496.9,497.5c-2.1,0-3.7,1.6-3.7,3.7c0,1.5,0.8,2.7,2,3.3l-0.5,65.1l3.5,0l0.5-65.3
c1.1-0.6,1.8-1.8,1.8-3.1C500.6,499.1,499,497.6,496.9,497.5z"/>
<path class="st4" d="M572.3,501.7c0-1.9-1.6-3.6-3.7-3.7c-2.1,0-3.7,1.6-3.7,3.7c0,1.4,0.8,2.6,1.9,3.2l-0.5,65.2l3.5,0
l0.5-65.2C571.5,504.3,572.2,503.1,572.3,501.7z"/>
</g>
<rect x="522.7" y="472.2" transform="matrix(7.448311e-03 -1 1 7.448311e-03 8.6828 1045.4733)" class="st1" width="16.5" height="92.3"/>
<polygon class="st1" points="495.4,509.8 495.2,510.1 485.5,526.3 484.8,526.3 484.9,509.8 "/>
<polygon class="st1" points="518.7,510 508.8,526.5 496.3,526.4 500.2,519.8 506,509.9 "/>
<polygon class="st1" points="542,510.2 532.1,526.6 519.6,526.5 529.3,510.1 "/>
<polygon class="st1" points="565.3,510.4 555.5,526.8 542.9,526.7 552.7,510.3 "/>
<polygon class="st1" points="577.2,510.4 577.1,527 566.2,526.9 576,510.4 "/>
<rect x="522.5" y="497.7" transform="matrix(7.448311e-03 -1 1 7.448311e-03 -17.0149 1070.603)" class="st1" width="16.5" height="92.3"/>
<polygon class="st1" points="495.2,535.3 495,535.6 485.3,551.8 484.6,551.8 484.7,535.3 "/>
<polygon class="st1" points="518.5,535.5 508.6,552 496.1,551.9 500,545.3 505.8,535.4 "/>
<polygon class="st1" points="541.8,535.7 531.9,552.1 519.4,552 529.1,535.6 "/>
<polygon class="st1" points="565.1,535.9 555.4,552.3 542.7,552.2 552.5,535.8 "/>
<polygon class="st1" points="577,536 576.9,552.5 566,552.4 575.8,536 "/>
<path class="st1" d="M577.1,527c0,0,0-0.1,0-0.3l0-0.9c0-0.7,0-1.8,0-3.2c0-2.8,0.1-6.9,0.1-12.2l0.1,0.1l-92.3-0.5l0,0l0.1-0.1
c0,5.6-0.1,11.2-0.1,16.5l-0.1-0.1l65.8,0.6l19.5,0.1l5.3,0l0,0l-5.3,0l-19.5-0.1l-65.8-0.3l-0.3,0l0.1-16.8l0.1,0l92.3,0.8
l0.1,0l0,0.1c0,5.3-0.1,9.4-0.1,12.2c0,1.5,0,2.5,0,3.2l0,0.7C577.1,526.8,577.1,527,577.1,527z"/>
<path class="st1" d="M576.9,552.5c0,0,0-0.1,0-0.3l0-0.9c0-0.7,0-1.8,0-3.2c0-2.8,0.1-6.9,0.1-12.2l0.1,0.1l-92.3-0.5l0,0
l0.1-0.1c0,5.6-0.1,11.2-0.1,16.5l-0.1-0.1l65.8,0.6l19.5,0.1l5.3,0l0,0l-5.3,0l-19.5-0.1l-65.8-0.3l-0.3,0l0.1-16.7l0.1,0
l92.3,0.8l0.1,0l0,0.1c0,5.3-0.1,9.4-0.1,12.2c0,1.5,0,2.5,0,3.2l0,0.7C576.9,552.3,576.9,552.5,576.9,552.5z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,236 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 456 262.1" style="enable-background:new 0 0 456 262.1;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.4;fill:url(#SVGID_1_);enable-background:new ;}
.st1{opacity:0.7;}
.st2{opacity:0.4;fill:url(#SVGID_2_);enable-background:new ;}
.st3{opacity:0.4;fill:url(#SVGID_3_);enable-background:new ;}
.st4{opacity:0.4;fill:url(#SVGID_4_);enable-background:new ;}
.st5{opacity:0.4;fill:url(#SVGID_5_);enable-background:new ;}
.st6{opacity:0.6;}
.st7{fill:#0073CD;}
.st8{fill:#40A8F5;}
.st9{fill:#53B9F5;}
.st10{fill:#85D3FF;}
.st11{fill:#8CD7FF;}
.st12{fill:#EBFCFF;}
.st13{fill:none;stroke:url(#SVGID_6_);stroke-width:2;stroke-miterlimit:10;}
.st14{fill:none;stroke:url(#SVGID_7_);stroke-width:2;stroke-miterlimit:10;}
.st15{fill:none;stroke:url(#SVGID_8_);stroke-width:2;stroke-miterlimit:10;}
.st16{fill:none;stroke:url(#SVGID_9_);stroke-width:2;stroke-miterlimit:10;}
.st17{fill:none;stroke:url(#SVGID_10_);stroke-width:2;stroke-miterlimit:10;}
.st18{fill:none;stroke:url(#SVGID_11_);stroke-width:2;stroke-miterlimit:10;}
</style>
<title>升级中</title>
<g id="图层_2_1_">
<g id="图层_1-2">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="232.745" y1="39.57" x2="232.745" y2="1.88" gradientTransform="matrix(1 0 0 -1 0 264)">
<stop offset="0" style="stop-color:#81CFFF"/>
<stop offset="1" style="stop-color:#5ECFFF;stop-opacity:0"/>
</linearGradient>
<path class="st0" d="M412.3,262.1c-23-23-61-37.7-179.5-37.7S76.2,239.1,53.2,262.1H412.3z"/>
<g class="st1">
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="349.365" y1="237.3224" x2="349.365" y2="59.9676" gradientTransform="matrix(1 0 0 -1 0 264)">
<stop offset="0" style="stop-color:#81CFFF"/>
<stop offset="1" style="stop-color:#5ECFFF;stop-opacity:0"/>
</linearGradient>
<path class="st2" d="M380.7,26.7h-62.6c-1.5-0.1-2.8,1.1-2.8,2.6v172.2c0.1,1.5,1.3,2.7,2.8,2.6h62.6c1.5,0.1,2.7-1.1,2.8-2.6
V29.3C383.4,27.8,382.2,26.6,380.7,26.7z M328.3,147c0,0.5-0.4,0.9-0.9,0.9c0,0,0,0,0,0h-3.6c-0.5,0-0.9-0.4-0.9-0.8c0,0,0,0,0,0
v-19.7c0-0.5,0.4-0.9,0.9-0.9c0,0,0,0,0,0h3.6c0.5,0,0.9,0.4,0.9,0.9c0,0,0,0,0,0V147z M328.3,116.8c0,0.5-0.4,0.9-0.9,0.9
c0,0,0,0,0,0h-3.6c-0.5,0-0.9-0.4-0.9-0.9c0,0,0,0,0,0V97c0-0.5,0.4-0.9,0.9-0.9c0,0,0,0,0,0h3.6c0.5,0,0.9,0.4,0.9,0.9
c0,0,0,0,0,0V116.8z M328.3,86.5c0,0.5-0.4,0.9-0.9,0.9c0,0,0,0,0,0h-3.6c-0.5,0-0.9-0.4-0.9-0.9c0,0,0,0,0,0V66.8
c0-0.5,0.4-0.9,0.9-0.9c0,0,0,0,0,0h3.6c0.5,0,0.9,0.4,0.9,0.9c0,0,0,0,0,0V86.5z M328.3,56.3c0,0.5-0.4,0.9-0.9,0.9c0,0,0,0,0,0
h-3.6c-0.5,0-0.9-0.4-0.9-0.9c0,0,0,0,0,0V36.6c0-0.5,0.4-0.9,0.9-0.9c0,0,0,0,0,0h3.6c0.5,0,0.9,0.4,0.9,0.9c0,0,0,0,0,0V56.3z
M340,147c0,0.5-0.4,0.9-0.9,0.9c0,0,0,0,0,0h-3.6c-0.5,0-0.9-0.4-0.9-0.9c0,0,0,0,0,0v-19.7c0-0.5,0.4-0.9,1-0.9h3.6
c0.5,0,0.9,0.4,0.9,0.9V147z M340,116.8c0,0.5-0.4,0.9-0.9,0.9h-3.6c-0.5,0-0.9-0.4-1-0.9V97c0-0.5,0.4-0.9,1-0.9h3.6
c0.5,0,0.9,0.4,0.9,0.9V116.8z M340,86.5c0,0.5-0.4,0.9-0.9,0.9h-3.6c-0.5,0-0.9-0.4-1-0.9V66.8c0-0.5,0.4-0.9,1-0.9h3.6
c0.5,0,0.9,0.4,0.9,0.9V86.5z M340,56.3c0,0.5-0.4,0.9-0.9,0.9h-3.6c-0.5,0-0.9-0.4-1-0.9V36.6c0-0.5,0.4-0.9,1-0.9h3.6
c0.5,0,0.9,0.4,0.9,0.9V56.3z M351.7,147c0,0.5-0.4,0.9-0.9,0.9c0,0,0,0,0,0h-3.6c-0.5,0-0.9-0.4-0.9-0.9c0,0,0,0,0,0v-19.7
c0-0.5,0.4-0.9,0.9-0.9h3.6c0.5,0,0.9,0.4,0.9,0.9V147z M351.7,116.8c0,0.5-0.4,0.9-0.9,0.9h-3.6c-0.5,0-0.9-0.4-0.9-0.9V97
c0-0.5,0.4-0.9,0.9-0.9h3.6c0.5,0,0.9,0.4,0.9,0.9V116.8z M351.7,86.5c0,0.5-0.4,0.9-0.9,0.9h-3.6c-0.5,0-0.9-0.4-0.9-0.9V66.8
c0-0.5,0.4-0.9,0.9-0.9h3.6c0.5,0,0.9,0.4,0.9,0.9V86.5z M351.7,56.3c0,0.5-0.4,0.9-0.9,0.9h-3.6c-0.5,0-0.9-0.4-0.9-0.9V36.6
c0-0.5,0.4-0.9,0.9-0.9h3.6c0.5,0,0.9,0.4,0.9,0.9V56.3z M363.4,147c0,0.5-0.4,0.9-0.9,0.9c0,0,0,0,0,0h-3.6
c-0.5,0-0.9-0.4-0.9-0.9c0,0,0,0,0,0v-19.7c0-0.5,0.4-0.9,0.9-0.9h3.6c0.5,0,0.9,0.4,0.9,0.9V147z M363.4,116.8
c0,0.5-0.4,0.9-0.9,0.9h-3.6c-0.5,0-0.9-0.4-0.9-0.9V97c0-0.5,0.4-0.9,0.9-0.9h3.6c0.5,0,0.9,0.4,0.9,0.9V116.8z M363.4,86.5
c0,0.5-0.4,0.9-0.9,0.9h-3.6c-0.5,0-0.9-0.4-0.9-0.9V66.8c0-0.5,0.4-0.9,0.9-0.9h3.6c0.5,0,0.9,0.4,0.9,0.9V86.5z M363.4,56.3
c0,0.5-0.4,0.9-0.9,0.9h-3.6c-0.5,0-0.9-0.4-0.9-0.9V36.6c0-0.5,0.4-0.9,0.9-0.9h3.6c0.5,0,0.9,0.4,0.9,0.9V56.3z M375.1,147
c0,0.5-0.4,0.9-0.9,0.9c0,0,0,0,0,0h-3.6c-0.5,0-0.9-0.4-0.9-0.9c0,0,0,0,0,0v-19.7c0-0.5,0.4-0.9,0.9-0.9h3.6
c0.5,0,0.9,0.4,1,0.9V147z M375.1,116.8c0,0.5-0.4,0.9-1,0.9h-3.6c-0.5,0-0.9-0.4-0.9-0.9V97c0-0.5,0.4-0.9,0.9-0.9h3.6
c0.5,0,0.9,0.4,1,0.9V116.8z M375.1,86.5c0,0.5-0.4,0.9-1,0.9h-3.6c-0.5,0-0.9-0.4-0.9-0.9V66.8c0-0.5,0.4-0.9,0.9-0.9h3.6
c0.5,0,0.9,0.4,1,0.9V86.5z M375.1,56.3c0,0.5-0.4,0.9-1,0.9h-3.6c-0.5,0-0.9-0.4-0.9-0.9V36.6c0-0.5,0.4-0.9,0.9-0.9h3.6
c0.5,0,0.9,0.4,1,0.9V56.3z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="201.46" y1="208.3924" x2="201.46" y2="59.9976" gradientTransform="matrix(1 0 0 -1 0 264)">
<stop offset="0" style="stop-color:#81CFFF"/>
<stop offset="1" style="stop-color:#5ECFFF;stop-opacity:0"/>
</linearGradient>
<path class="st3" d="M231.1,55.6h-59.3c-1.5-0.1-2.7,1.1-2.8,2.6v143.2c0.1,1.5,1.3,2.6,2.8,2.6h59.3c1.5,0.1,2.8-1.1,2.8-2.6
V58.2C233.9,56.7,232.6,55.5,231.1,55.6z M182.5,159.4c0,0.6-0.6,1.1-1.2,1.1h-5.4c-0.6,0-1.2-0.5-1.2-1.1v-5.2
c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h5.4c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0V159.4z M182.5,146.5c0,0.6-0.6,1.1-1.2,1.1
c0,0,0,0,0,0h-5.4c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2c0-0.6,0.6-1.1,1.2-1.1h5.4c0.6,0,1.2,0.5,1.2,1.1V146.5z
M182.5,133.6c0,0.6-0.6,1.1-1.2,1.1h-5.4c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h5.4
c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0V133.6z M182.5,120.7c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0h-5.4c-0.6,0-1.2-0.5-1.2-1.1
c0,0,0,0,0,0v-5.2c0-0.6,0.6-1.1,1.2-1.1h5.4c0.6,0,1.2,0.5,1.2,1.1V120.7z M182.5,107.8c0,0.6-0.6,1.1-1.2,1.1h-5.4
c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h5.4c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0V107.8z M182.5,94.9
c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0h-5.4c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2c0-0.6,0.6-1.1,1.2-1.1h5.4
c0.6,0,1.2,0.5,1.2,1.1V94.9z M182.5,82.1c0,0.6-0.6,1.1-1.2,1.1h-5.4c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1
c0,0,0,0,0,0h5.4c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0V82.1z M182.5,69.2c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0h-5.4
c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0V64c0-0.6,0.6-1.1,1.2-1.1h5.4c0.6,0,1.2,0.5,1.2,1.1V69.2z M192.6,159.4
c0,0.6-0.6,1.1-1.2,1.1H186c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2c0-0.6,0.6-1.1,1.2-1.1h5.4c0.6,0,1.2,0.5,1.2,1.1
c0,0,0,0,0,0L192.6,159.4z M192.6,146.5c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0H186c-0.6,0-1.2-0.5-1.2-1.1v-5.2
c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h5.4c0.6,0,1.2,0.5,1.2,1.1L192.6,146.5z M192.6,133.6c0,0.6-0.6,1.1-1.2,1.1H186
c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2c0-0.6,0.6-1.1,1.2-1.1h5.4c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0L192.6,133.6z
M192.6,120.7c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0H186c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h5.4
c0.6,0,1.2,0.5,1.2,1.1L192.6,120.7z M192.6,107.8c0,0.6-0.6,1.1-1.2,1.1H186c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2
c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h5.4c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0L192.6,107.8z M192.6,94.9c0,0.6-0.6,1.1-1.2,1.1
c0,0,0,0,0,0H186c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h5.4c0.6,0,1.2,0.5,1.2,1.1L192.6,94.9z
M192.6,82.1c0,0.6-0.6,1.1-1.2,1.1H186c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h5.4
c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0L192.6,82.1z M192.6,69.2c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0H186c-0.6,0-1.2-0.5-1.2-1.1V64
c0-0.6,0.5-1.2,1.2-1.2c0,0,0,0,0,0h5.4c0.6,0,1.2,0.5,1.2,1.1L192.6,69.2z M202.6,159.4c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0H196
c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h5.4c0.6,0,1.2,0.5,1.2,1.1L202.6,159.4z M202.6,146.5
c0,0.6-0.6,1.1-1.2,1.1H196c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2c0-0.6,0.6-1.1,1.2-1.1h5.4c0.6,0,1.2,0.5,1.2,1.1
c0,0,0,0,0,0L202.6,146.5z M202.6,133.6c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0H196c-0.6,0-1.2-0.5-1.2-1.1v-5.2
c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h5.4c0.6,0,1.2,0.5,1.2,1.1L202.6,133.6z M202.6,120.7c0,0.6-0.6,1.1-1.2,1.1H196
c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2c0-0.6,0.6-1.1,1.2-1.1h5.4c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0L202.6,120.7z
M202.6,107.8c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0H196c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h5.4
c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0L202.6,107.8z M202.6,94.9c0,0.6-0.6,1.1-1.2,1.1H196c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0
v-5.2c0-0.6,0.6-1.1,1.2-1.1h5.4c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0L202.6,94.9z M202.6,82.1c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0
H196c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h5.4c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0L202.6,82.1z
M202.6,69.2c0,0.6-0.6,1.1-1.2,1.1H196c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0V64c0-0.6,0.6-1.1,1.2-1.1h5.4c0.6,0,1.2,0.4,1.2,1.1
c0,0,0,0,0,0V69.2z M227.8,159.4c0,0.6-0.6,1.1-1.2,1.1h-20.5c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0
h20.5c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0L227.8,159.4z M227.8,146.5c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0h-20.5
c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2c0-0.6,0.6-1.1,1.2-1.1h20.5c0.6,0,1.2,0.5,1.2,1.1L227.8,146.5z M227.8,133.6
c0,0.6-0.6,1.1-1.2,1.1h-20.5c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h20.5c0.6,0,1.2,0.5,1.2,1.1
c0,0,0,0,0,0L227.8,133.6z M227.8,120.7c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0h-20.5c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2
c0-0.6,0.6-1.1,1.2-1.1h20.5c0.6,0,1.2,0.5,1.2,1.1L227.8,120.7z M227.8,107.8c0,0.6-0.6,1.1-1.2,1.1h-20.5
c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h20.5c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0L227.8,107.8z
M227.8,94.9c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0h-20.5c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2c0-0.6,0.6-1.1,1.2-1.1h20.5
c0.6,0,1.2,0.5,1.2,1.1L227.8,94.9z M227.8,82.1c0,0.6-0.6,1.1-1.2,1.1h-20.5c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1
c0,0,0,0,0,0h20.5c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0L227.8,82.1z M227.8,69.2c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0h-20.5
c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0V64c0-0.6,0.6-1.1,1.2-1.1h20.5c0.6,0,1.2,0.5,1.2,1.1L227.8,69.2z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="122.975" y1="237.3228" x2="122.975" y2="59.9971" gradientTransform="matrix(1 0 0 -1 0 264)">
<stop offset="0" style="stop-color:#81CFFF"/>
<stop offset="1" style="stop-color:#5ECFFF;stop-opacity:0"/>
</linearGradient>
<path class="st4" d="M161.1,26.7H84.8c-1.5-0.1-2.8,1.1-2.8,2.6c0,0,0,0,0,0v172.2c0.1,1.5,1.3,2.6,2.8,2.6h76.3
c1.5,0.1,2.8-1.1,2.8-2.6V29.3C163.9,27.8,162.6,26.6,161.1,26.7z M154.3,161c0,0.6-0.6,1.1-1.2,1.1H92.8c-0.6,0-1.2-0.5-1.2-1.1
c0,0,0,0,0,0v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h60.3c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0V161z M154.3,146.3
c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0H92.8c-0.6,0-1.2-0.5-1.2-1.1v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h60.3
c0.6,0,1.2,0.5,1.2,1.1V146.3z M154.3,131.6c0,0.6-0.6,1.1-1.2,1.1H92.8c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2
c0-0.6,0.6-1.1,1.2-1.1h60.3c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0V131.6z M154.3,117c0,0.6-0.6,1.1-1.2,1.1H92.8
c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.2c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h60.3c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0V117z
M154.3,102.3c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0H92.8c-0.6,0-1.2-0.5-1.2-1.1V97c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h60.3
c0.6,0,1.2,0.5,1.2,1.1V102.3z M154.3,87.6c0,0.6-0.6,1.1-1.2,1.1H92.8c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.3
c0-0.6,0.6-1.1,1.2-1.1h60.3c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0V87.6z M154.3,72.9c0,0.6-0.6,1.1-1.2,1.1H92.8
c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.3c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h60.3c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0V72.9z
M154.3,58.3c0,0.6-0.6,1.1-1.2,1.1c0,0,0,0,0,0H92.8c-0.6,0-1.2-0.5-1.2-1.1V53c0-0.6,0.6-1.1,1.2-1.1c0,0,0,0,0,0h60.3
c0.6,0,1.2,0.5,1.2,1.1V58.3z M154.3,43.6c0,0.6-0.6,1.1-1.2,1.1H92.8c-0.6,0-1.2-0.5-1.2-1.1c0,0,0,0,0,0v-5.3
c0-0.6,0.6-1.1,1.2-1.1h60.3c0.6,0,1.2,0.5,1.2,1.1c0,0,0,0,0,0V43.6z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="273.51" y1="264" x2="273.51" y2="54.46" gradientTransform="matrix(1 0 0 -1 0 264)">
<stop offset="0" style="stop-color:#81CFFF"/>
<stop offset="1" style="stop-color:#5ECFFF;stop-opacity:0"/>
</linearGradient>
<path class="st5" d="M306.2,0h-65.4c-1.4,0-2.5,1.2-2.4,2.6v204.4c-0.1,1.4,1,2.5,2.4,2.6c0,0,0,0,0,0h65.4
c1.4-0.1,2.5-1.2,2.4-2.6V2.6C308.7,1.2,307.6,0.1,306.2,0z M300.4,119.6c0,0.6-0.4,1.1-1,1.1h-51.7c-0.6,0-1-0.5-1-1.1v-5.2
c0-0.6,0.4-1.1,1-1.1h51.7c0.6,0,1,0.5,1,1.1L300.4,119.6z M300.4,90.3c0,0.6-0.4,1.1-1,1.1h-51.7c-0.6,0-1-0.5-1-1.1V85
c0-0.6,0.4-1.1,1-1.1h51.7c0.6,0,1,0.5,1,1.1L300.4,90.3z M300.4,60.9c0,0.6-0.4,1.1-1,1.1h-51.7c-0.6,0-1-0.5-1-1.1v-5.2
c0-0.6,0.4-1.1,1-1.1h51.7c0.6,0,1,0.5,1,1.1L300.4,60.9z M300.4,31.5c0,0.6-0.4,1.1-1,1.1h-51.7c-0.6,0-1-0.5-1-1.1v-5.2
c0-0.6,0.4-1.1,1-1.1h51.7c0.6,0,1,0.5,1,1.1L300.4,31.5z M300.4,16.9c0,0.6-0.4,1.1-1,1.1h-51.7c-0.6,0-1-0.5-1-1.1v-5.2
c0-0.6,0.4-1.1,1-1.1h51.7c0.6,0,1,0.5,1,1.1L300.4,16.9z"/>
</g>
<g class="st6">
<path class="st7" d="M244.2,130.7c3.3,1.9,5.7,13.6,1,20.7c-5.3,6.8-7,9-6.1,14c-4.3-7.3-0.6-7.4-5.8-14.2
c-4.6-6.2-2.5-17.4,0.7-20.5C237.4,131.3,240.8,131.3,244.2,130.7L244.2,130.7z"/>
<path class="st8" d="M280.4,211.9c-0.1,0.6-0.1,1.2-0.1,1.7c0,0.7,0.1,1.3,0.2,2c-2.1-0.8-4.3-1.2-6.5-1.2
c-2.6,0-5.2,0.6-7.5,1.7c-0.8-4.2-4.8-7-9-6.2c-2.8,0.5-5,2.5-5.9,5.2c-3.4-2.1-7.7-2-11.1,0.1c-0.4-5.7-4.1-0.8-9.8-0.8
c-6,0-11.8-4.4-11.8,1.7c0,1.1,0.2,2.2,0.4,3.2c-1.1,0.1-2.1,0.5-3,1c-0.9-7-6.8-12.4-13.9-12.7c22.6-10.3,22.4-44.6,24.8-74.6
c0.1-1.7,1.2-4.1,1.8-4.4l0,0c1.5,1.1,3.2,1.8,5.1,2.1c-3.2,3.1-5.4,14.3-0.7,20.5c5.3,6.8,1.6,6.9,5.8,14.2
c-0.9-5,0.8-7.2,6.1-14c4.8-7.2,2.3-18.8-1-20.7c1.8-0.3,3.6-1,5.1-2.1l0,0c0.6,0.3,1.7,2.7,1.8,4.4
C253.6,164.6,250.4,198.5,280.4,211.9z"/>
<path class="st9" d="M303.3,203.2c-5.4,0-9.8,4.4-9.8,9.8c0,0.3,0,0.6,0,0.9c-1.5-1.7-3.7-2.6-6-2.5c-0.7,0-1.4,0.1-2.1,0.3
c0.7-1.3,1.1-2.8,1.1-4.3c-0.1-4.6-3.9-8.3-8.5-8.2c-4,0.1-7.4,3-8.1,6.9c-0.1,0.4-0.1,0.9-0.1,1.3c0,0.5,0,1,0.1,1.6
c-3.5-1.4-7.4-1.3-10.8,0.4c-0.7-3.2-3.9-5.3-7.2-4.6c-2,0.4-3.6,1.9-4.3,3.8c-1.3-0.8-2.7-1.2-4.2-1.2c-1.5,0-3.1,0.4-4.4,1.3
c-0.2-4.6-4.1-8.2-8.7-8s-8.2,4.1-8,8.7l0,0c0,0.8,0.1,1.7,0.3,2.5c-0.8,0.1-1.6,0.4-2.3,0.8c-0.7-5.4-5.2-9.6-10.7-9.8h-0.4
c-3.4,0-6.7,1.6-8.8,4.3c-3-6.2-10.4-8.9-16.7-5.9c-2.9,1.4-5.2,3.9-6.3,7c4.1,3,6.6,7.8,6.6,12.9c0,2.3-0.5,4.6-1.5,6.6
c2-2.2,4.8-3.4,7.8-3.4c0.9,0,1.8,0.1,2.7,0.3c-2.9-5-1.2-11.5,3.8-14.4c1.2-0.7,2.6-1.2,3.9-1.3c0.5-0.1,0.9-0.1,1.4-0.1
c5.8,0,10.6,4.7,10.6,10.6l0,0c0,0.6-0.1,1.3-0.2,1.9c4.4-1.7,9.3-1.6,13.6,0.4c0.9-4.1,4.9-6.7,9-5.8c2.6,0.6,4.7,2.4,5.5,4.9
c1.6-0.9,3.5-1.4,5.3-1.4c1.9,0,3.8,0.5,5.4,1.5l0.1,0.1c0.3-4.5,3.6-8.4,8-9.4c0.8-0.2,1.7-0.3,2.5-0.3
c5.8,0,10.6,4.7,10.6,10.6c0,1-0.2,2-0.4,3c1.1,0.1,2.1,0.5,3,1c1-7,7-12.1,14-12.1c1.5,0,3,0.2,4.4,0.7c2.6,0.8,5,2.5,6.7,4.6
c2.4-4.8,7-8.2,12.4-8.8C311.4,205.9,307.6,203.2,303.3,203.2z"/>
<path class="st10" d="M314.4,209.9c-0.6,0-1.2,0-1.8,0.1c-5.4,0.6-10.1,3.9-12.5,8.7c-1.7-2.2-4-3.8-6.7-4.6
c-1.4-0.5-2.9-0.7-4.4-0.7c-7,0-13,5.2-14,12.1c-0.9-0.5-1.9-0.9-3-1c0.3-1,0.4-2,0.4-3c0-5.8-4.7-10.5-10.5-10.5
c-0.8,0-1.7,0.1-2.5,0.3c-4.4,1.1-7.7,4.9-8,9.4l-0.1-0.1c-1.6-1-3.5-1.5-5.4-1.5c-1.9,0-3.7,0.5-5.3,1.4c-1.4-3.9-5.7-6-9.6-4.6
c-2.5,0.9-4.3,3-4.9,5.5c-4.3-2-9.2-2.1-13.6-0.4c0.1-0.6,0.2-1.3,0.2-1.9c0-5.8-4.7-10.6-10.6-10.6l0,0c-0.5,0-0.9,0-1.4,0.1
c-5.8,0.8-9.9,6.1-9.1,11.8c0.2,1.4,0.6,2.7,1.3,3.9c-0.9-0.2-1.8-0.3-2.7-0.3c-2.9,0-5.8,1.2-7.8,3.4c1-2.1,1.5-4.3,1.5-6.6
c0-5.1-2.4-9.9-6.6-12.9l0,0c-7.1-5.2-17-3.6-22.2,3.5c-5.2,7.1-3.6,17,3.5,22.2c6.8,5,16.4,3.7,21.7-2.8
c-2.1,5.5,0.7,11.6,6.1,13.6c5.5,2.1,11.6-0.7,13.6-6.1c0.5-1.2,0.7-2.5,0.7-3.7c0-1.9,0.4-0.1,0.4,2.7c0,9.7,7.8,17.6,17.5,17.6
c9.7,0,17.6-7.8,17.6-17.5c0-2.4-0.5-4.8-1.4-7l0.5-0.1c0.5,5.8,5.7,10.1,11.5,9.5c5-0.5,9-4.4,9.5-9.4c1.7,1,3.6,1.6,5.5,1.6
c0.6,0,1.2,0,1.7-0.1v0.1c0,4.2,3.4,7.6,7.6,7.5c2.6,0,5-1.4,6.4-3.6c4.7,6.3,13.5,7.5,19.8,2.9c1.5-1.1,2.8-2.5,3.7-4.2
c4.9,7.3,14.8,9.1,22.1,4.2s9.1-14.8,4.2-22.1C324.5,212.5,319.6,209.9,314.4,209.9L314.4,209.9z"/>
</g>
<path class="st11" d="M239.1,70c6.4,0,11.6,5.2,11.6,11.6c0,6.4-5.2,11.6-11.6,11.6c-6.4,0-11.6-5.2-11.6-11.6c0,0,0,0,0,0
C227.5,75.2,232.7,70,239.1,70z"/>
<path class="st8" d="M239.1,137.2c8.3,0,13.5-1.7,16.6-7.9c0,3.2-1.5,6.2-4.1,8.1c-1.9,1.3-4,2.2-6.2,2.6c-4.1,0.8-8.4,0.8-12.5,0
c-2.2-0.4-4.3-1.3-6.2-2.6c-2.6-1.9-4.2-4.9-4.2-8.1C225.6,135.5,230.7,137.2,239.1,137.2z"/>
<path class="st8" d="M288.4,151.7c0,0.5-0.3,0.8-0.8,0.8c-0.3,0-0.5-0.1-0.7-0.3c-7.6-10.3-25.5-25-30.4-24.7
c2.4-6.1,3.4-15.9,3.6-31.2C271.5,100.3,288.3,122.4,288.4,151.7z"/>
<path class="st8" d="M218.1,96.3c0.3,15.2,1.2,25,3.6,31.1l-0.2,0.1c-4.9-0.3-22.8,14.5-30.4,24.7c-0.3,0.4-0.8,0.5-1.1,0.2
c-0.2-0.2-0.4-0.4-0.3-0.7C189.7,122.3,206.6,100.2,218.1,96.3L218.1,96.3z"/>
<path class="st8" d="M250.7,81.6c0-6.4-5.2-11.6-11.6-11.6c-6.4,0-11.6,5.2-11.6,11.6s5.2,11.6,11.6,11.6l0,0
C245.5,93.2,250.7,88,250.7,81.6z M252.9,81.6c0,7.6-6.2,13.8-13.8,13.8c-7.6,0-13.8-6.2-13.8-13.8c0-7.6,6.2-13.8,13.8-13.8
c0,0,0,0,0,0C246.7,67.8,252.9,74,252.9,81.6z"/>
<path class="st8" d="M239.1,48.7c5.7,0,10.7-2,13.8-4.9c0.5,1.3,1,2.6,1.4,4c-3.3,3.3-8.9,5.4-15.3,5.4s-11.9-2.1-15.3-5.4
c0.5-1.3,1-2.7,1.4-4C228.4,46.8,233.4,48.7,239.1,48.7z"/>
<path class="st9" d="M252.9,43.8c-3.1,3-8.1,4.9-13.8,4.9s-10.7-2-13.8-4.9c2-5.1,4.5-10,7.6-14.5c0.5-0.7,0.9-1.3,1.4-1.9
c2-2.7,5.8-3.2,8.5-1.2c0.4,0.3,0.8,0.7,1.2,1.2c0.5,0.6,0.9,1.2,1.4,1.9C248.4,33.8,250.9,38.7,252.9,43.8z"/>
<path class="st10" d="M260.1,96.4c-0.3,15.3-1.2,25-3.6,31.2c-0.2,0.6-0.5,1.2-0.8,1.7c-3.1,6.2-8.3,7.9-16.6,7.9
s-13.5-1.7-16.6-7.9c-0.3-0.6-0.5-1.1-0.8-1.8c-2.4-6.2-3.4-15.9-3.6-31.1c-0.1-3.4-0.1-7.1-0.1-11.1c-0.1-12.7,1.8-25.4,5.8-37.5
c3.3,3.3,8.9,5.4,15.3,5.4s11.9-2.1,15.3-5.4c4,12.1,6,24.8,5.8,37.5C260.2,89.2,260.1,92.9,260.1,96.4z M252.9,81.6
c0-7.6-6.2-13.8-13.8-13.8c-7.6,0-13.8,6.2-13.8,13.8c0,7.6,6.2,13.8,13.8,13.8c0,0,0,0,0,0C246.7,95.4,252.9,89.2,252.9,81.6
L252.9,81.6z"/>
<path class="st12" d="M139.2,246.1l18.4,0.4v0.7l-19.4-0.4v-0.7V246C138.4,246.1,138.6,246.1,139.2,246.1z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="112.2357" y1="190.775" x2="112.2357" y2="101.005" gradientTransform="matrix(1 0 0 -1 0 264)">
<stop offset="0" style="stop-color:#81CFFF"/>
<stop offset="1" style="stop-color:#5ECFFF;stop-opacity:0"/>
</linearGradient>
<line class="st13" x1="112.2" y1="73.2" x2="112.2" y2="163"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="348.955" y1="195.605" x2="348.955" y2="105.835" gradientTransform="matrix(1 0 0 -1 0 264)">
<stop offset="0" style="stop-color:#81CFFF"/>
<stop offset="1" style="stop-color:#5ECFFF;stop-opacity:0"/>
</linearGradient>
<line class="st14" x1="349" y1="68.4" x2="349" y2="158.2"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="40.9" y1="120.12" x2="40.9" y2="64.49" gradientTransform="matrix(1 0 0 -1 0 264)">
<stop offset="0" style="stop-color:#81CFFF"/>
<stop offset="1" style="stop-color:#5ECFFF;stop-opacity:0"/>
</linearGradient>
<line class="st15" x1="40.9" y1="143.9" x2="40.9" y2="199.5"/>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="64.97" y1="168.64" x2="64.97" y2="140.83" gradientTransform="matrix(1 0 0 -1 0 264)">
<stop offset="0" style="stop-color:#81CFFF"/>
<stop offset="1" style="stop-color:#5ECFFF;stop-opacity:0"/>
</linearGradient>
<line class="st16" x1="65" y1="95.4" x2="65" y2="123.2"/>
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="397.23" y1="159.8" x2="397.23" y2="131.98" gradientTransform="matrix(1 0 0 -1 0 264)">
<stop offset="0" style="stop-color:#81CFFF"/>
<stop offset="1" style="stop-color:#5ECFFF;stop-opacity:0"/>
</linearGradient>
<line class="st17" x1="397.2" y1="104.2" x2="397.2" y2="132"/>
<linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="424.75" y1="130.51" x2="424.75" y2="74.87" gradientTransform="matrix(1 0 0 -1 0 264)">
<stop offset="0" style="stop-color:#81CFFF"/>
<stop offset="1" style="stop-color:#5ECFFF;stop-opacity:0"/>
</linearGradient>
<line class="st18" x1="424.8" y1="133.5" x2="424.8" y2="189.1"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

+64
View File
@@ -0,0 +1,64 @@
<template>
<el-config-provider :locale="locale" :size="config.size" :zIndex="config.zIndex" :button="config.button">
<router-view></router-view>
</el-config-provider>
</template>
<script>
import colorTool from '@/utils/color'
export default {
name: 'App',
data() {
return {
config: {
size: "small",
zIndex: 2000,
button: {
autoInsertSpace: false
}
}
}
},
computed: {
locale(){
return this.$i18n.messages[this.$i18n.locale].el
},
},
created() {
//设置主题颜色
const app_color = this.$CONFIG.COLOR || this.$TOOL.data.get('APP_COLOR')
if(app_color){
document.documentElement.style.setProperty('--el-color-primary', app_color);
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, colorTool.lighten(app_color,i/10));
}
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, colorTool.darken(app_color,i/10));
}
}
const debounce = (fn, delay) => {
let timer = null;
return function () {
let context = this;
let args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
}
}
const _ResizeObserver = window.ResizeObserver;
window.ResizeObserver = class ResizeObserver extends _ResizeObserver{
constructor(callback) {
callback = debounce(callback, 16);
super(callback);
}
}
}
}
</script>
<style lang="scss">
@import '@/assets/style/style.scss';
</style>
+11
View File
@@ -0,0 +1,11 @@
/**
* @description 自动import导入所有 api 模块
*/
const files = import.meta.glob('./module/*.js', { eager: true })
const modules = {}
Object.keys(files).forEach(key => {
modules[key.replace(/^\.\/module\/(.*)\.js$/g, '$1')] = files[key].default
})
export default modules
+153
View File
@@ -0,0 +1,153 @@
import config from "@/config"
import http from "@/utils/request"
export default {
family: {
list: {
url: `${config.API_URL}account/families/index`,
name: "家庭列表",
get: async function(params){
return await http.get(this.url, params);
}
},
detail: {
url: `${config.API_URL}account/families/detail`,
name: "家庭详情",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}account/families/add`,
name: "家庭添加",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}account/families/edit`,
name: "家庭编辑",
post: async function(params){
return await http.put(this.url, params);
}
},
delete: {
url: `${config.API_URL}account/families/delete`,
name: "家庭删除",
post: async function(params){
return await http.delete(this.url, params);
}
}
},
accounts: {
list: {
url: `${config.API_URL}account/accounts/index`,
name: "账户列表",
get: async function(params){
return await http.get(this.url, params);
}
},
detail: {
url: `${config.API_URL}account/accounts/detail`,
name: "账户详情",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}account/accounts/add`,
name: "账户添加",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}account/accounts/edit`,
name: "账户编辑",
post: async function(params){
return await http.put(this.url, params);
}
},
delete: {
url: `${config.API_URL}account/accounts/delete`,
name: "账户删除",
post: async function(params){
return await http.delete(this.url, params);
}
}
},
members: {
list: {
url: `${config.API_URL}account/members/index`,
name: "成员列表",
get: async function(params){
return await http.get(this.url, params);
}
},
detail: {
url: `${config.API_URL}account/members/detail`,
name: "成员详情",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}account/members/add`,
name: "成员添加",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}account/members/edit`,
name: "成员编辑",
post: async function(params){
return await http.put(this.url, params);
}
},
delete: {
url: `${config.API_URL}account/members/delete`,
name: "成员删除",
post: async function(params){
return await http.delete(this.url, params);
}
}
},
records: {
list: {
url: `${config.API_URL}account/records/index`,
name: "记录列表",
get: async function(params){
return await http.get(this.url, params);
}
},
detail: {
url: `${config.API_URL}account/records/detail`,
name: "记录详情",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}account/records/add`,
name: "记录添加",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}account/records/edit`,
name: "记录编辑",
post: async function(params){
return await http.put(this.url, params);
}
},
delete: {
url: `${config.API_URL}account/records/delete`,
name: "记录删除",
post: async function(params){
return await http.delete(this.url, params);
}
}
}
}
+33
View File
@@ -0,0 +1,33 @@
import config from "@/config";
import http from "@/utils/request";
export default {
list: {
url: `${config.API_URL}ads/index`,
name: "获得广告列表",
get: async function (params) {
return await http.get(this.url, params);
},
},
add: {
url: `${config.API_URL}ads/add`,
name: "添加广告",
post: async function (params) {
return await http.post(this.url, params);
},
},
edit: {
url: `${config.API_URL}ads/edit`,
name: "编辑广告",
post: async function (params) {
return await http.put(this.url, params);
},
},
delete: {
url: `${config.API_URL}ads/delete`,
name: "删除广告",
post: async function (params) {
return await http.delete(this.url, params);
},
},
};
+174
View File
@@ -0,0 +1,174 @@
import config from "@/config"
import http from "@/utils/request"
export default {
login: {
url: `${config.API_URL}auth/login`,
name: "登录获取TOKEN",
post: async function(data={}){
return await http.post(this.url, data);
}
},
logout:{
url: `${config.API_URL}auth/logout`,
name: "登出",
get: async function(data={}){
return await http.get(this.url, data);
}
},
user: {
url: `${config.API_URL}auth/user`,
name: "获取当前登录用户",
get: async function(data={}){
return await http.get(this.url, data);
}
},
users: {
list: {
url: `${config.API_URL}auth/users/index`,
name: "获得用户列表",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}auth/users/add`,
name: "添加用户",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}auth/users/edit`,
name: "编辑用户",
post: async function(params){
return await http.put(this.url, params);
}
},
uppasswd:{
url: `${config.API_URL}auth/users/passwd`,
name: "修改密码",
post: async function(params){
return await http.put(this.url, params);
}
},
uprole: {
url: `${config.API_URL}auth/users/uprole`,
name: "设置角色",
post: async function(params){
return await http.put(this.url, params);
}
},
delete: {
url: `${config.API_URL}auth/users/delete`,
name: "删除用户",
post: async function(params){
return await http.delete(this.url, params);
}
}
},
role: {
list: {
url: `${config.API_URL}auth/role/index`,
name: "获得角色列表",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}auth/role/add`,
name: "添加角色",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}auth/role/edit`,
name: "编辑角色",
post: async function(params){
return await http.put(this.url, params);
}
},
auth: {
url: `${config.API_URL}auth/role/auth`,
name: "角色授权",
post: async function(params){
return await http.put(this.url, params);
}
},
delete: {
url: `${config.API_URL}auth/role/delete`,
name: "删除角色",
post: async function(params){
return await http.delete(this.url, params);
}
}
},
department: {
list: {
url: `${config.API_URL}auth/department/index`,
name: "获得部门列表",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}auth/department/add`,
name: "添加部门",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}auth/department/edit`,
name: "编辑部门",
post: async function(params){
return await http.put(this.url, params);
}
},
delete: {
url: `${config.API_URL}auth/department/delete`,
name: "删除部门",
post: async function(params){
return await http.delete(this.url, params);
}
}
},
menu: {
myMenus: {
url: `${config.API_URL}auth/menu/my`,
name: "获取我的菜单",
get: async function(){
return await http.get(this.url);
}
},
list: {
url: `${config.API_URL}auth/menu/index`,
name: "获取菜单",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}auth/menu/add`,
name: "添加菜单",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}auth/menu/edit`,
name: "编辑菜单",
post: async function(params){
return await http.put(this.url, params);
}
},
delete: {
url: `${config.API_URL}auth/menu/delete`,
name: "删除菜单",
post: async function(params){
return await http.delete(this.url, params);
}
}
},
}
+29
View File
@@ -0,0 +1,29 @@
import config from "@/config"
import http from "@/utils/request"
export default {
upload: {
url: `${config.API_URL}system/file/upload`,
name: "文件上传",
post: async function(data, config={}){
return await http.post(this.url, data, config);
}
},
ckeditor: `${config.API_URL}system/file/ckeditor`,
file: {
menu: {
url: `${config.API_URL}system/file/menu`,
name: "获取文件分类",
get: async function(){
return await http.get(this.url);
}
},
list: {
url: `${config.API_URL}system/file/list`,
name: "获取文件列表",
get: async function(params){
return await http.get(this.url, params);
}
}
}
}
+132
View File
@@ -0,0 +1,132 @@
import config from "@/config";
import http from "@/utils/request";
export default {
lists: {
list: {
url: `${config.API_URL}member/index/index`,
name: "获得会员列表",
get: async function (params) {
return await http.get(this.url, params);
},
},
add: {
url: `${config.API_URL}member/index/add`,
name: "添加会员",
post: async function (params) {
return await http.post(this.url, params);
},
},
edit: {
url: `${config.API_URL}member/index/edit`,
name: "编辑会员",
post: async function (params) {
return await http.put(this.url, params);
},
},
delete: {
url: `${config.API_URL}member/index/delete`,
name: "删除会员",
post: async function (params) {
return await http.delete(this.url, params);
},
},
import: {
url: `${config.API_URL}member/index/import`,
name: "导入会员",
post: async function (params) {
return await http.post(this.url, params);
},
},
export: {
url: `${config.API_URL}member/index/export`,
name: "导出会员",
get: async function (params) {
return await http.get(this.url, params);
},
},
},
level: {
list: {
url: `${config.API_URL}member/level/index`,
name: "获得会员等级列表",
get: async function (params) {
return await http.get(this.url, params);
},
},
add: {
url: `${config.API_URL}member/level/add`,
name: "添加会员等级",
post: async function (params) {
return await http.post(this.url, params);
},
},
edit: {
url: `${config.API_URL}member/level/edit`,
name: "编辑会员等级",
post: async function (params) {
return await http.put(this.url, params);
},
},
delete: {
url: `${config.API_URL}member/level/delete`,
name: "删除会员等级",
post: async function (params) {
return await http.delete(this.url, params);
},
},
},
field: {
list: {
url: `${config.API_URL}member/field/index`,
name: "获得会员字段列表",
get: async function (params) {
return await http.get(this.url, params);
},
},
add: {
url: `${config.API_URL}member/field/add`,
name: "添加会员字段",
post: async function (params) {
return await http.post(this.url, params);
},
},
edit: {
url: `${config.API_URL}member/field/edit`,
name: "编辑会员字段",
post: async function (params) {
return await http.put(this.url, params);
},
},
field: {
url: `${config.API_URL}member/field/fields`,
name: "获得会员字段",
get: async function (params) {
return await http.get(this.url, params);
},
},
delete: {
url: `${config.API_URL}member/field/delete`,
name: "删除会员字段",
post: async function (params) {
return await http.delete(this.url, params);
},
},
},
extend: {
list: {
url: `${config.API_URL}member/extend/index`,
name: "获得会员扩展列表",
get: async function (params) {
return await http.get(this.url, params);
},
},
audit: {
url: `${config.API_URL}member/extend/audit`,
name: "申请审核",
post: async function (params) {
return await http.put(this.url, params);
},
},
},
};
+342
View File
@@ -0,0 +1,342 @@
import config from "@/config"
import http from "@/utils/request"
export default {
version:{
url: `${config.API_URL}system/index/version`,
name: "获取最新版本号",
get: async function(){
return await http.get(this.url);
}
},
clearcache: {
url: `${config.API_URL}system/index/clearcache`,
name: "清除缓存",
post: async function(){
return await http.post(this.url);
}
},
info: {
url: `${config.API_URL}system/index/info`,
name: "系统信息",
get: function(data){
return http.get(this.url, data);
}
},
setting:{
list: {
url: `${config.API_URL}system/setting/index`,
name: "获取配置信息",
get: function(params){
return http.get(this.url, params);
}
},
fields: {
url: `${config.API_URL}system/setting/fields`,
name: "获取配置字段",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}system/setting/add`,
name: "保存配置信息",
post: function(data){
return http.post(this.url, data);
}
},
edit: {
url: `${config.API_URL}system/setting/edit`,
name: "编辑配置信息",
post: function(data){
return http.put(this.url, data);
}
},
save: {
url: `${config.API_URL}system/setting/save`,
name: "保存配置信息",
post: function(data){
return http.put(this.url, data);
}
}
},
dictionary: {
category: {
url: `${config.API_URL}system/dict/category`,
name: "获取字典树",
get: async function(params){
return await http.get(this.url, params);
}
},
editcate:{
url: `${config.API_URL}system/dict/editcate`,
name: "编辑字典树",
post: async function(data = {}){
return await http.put(this.url, data);
}
},
addcate:{
url: `${config.API_URL}system/dict/addcate`,
name: "添加字典树",
post: async function(data = {}){
return await http.post(this.url, data);
}
},
delCate:{
url: `${config.API_URL}system/dict/deletecate`,
name: "删除字典树",
post: async function(data = {}){
return await http.delete(this.url, data);
}
},
list: {
url: `${config.API_URL}system/dict/lists`,
name: "字典明细",
get: async function(params){
return await http.get(this.url, params);
}
},
get: {
url: `${config.API_URL}system/dict/detail`,
name: "获取字典数据",
get: async function(params){
return await http.get(this.url, params);
}
},
edit:{
url: `${config.API_URL}system/dict/edit`,
name: "编辑字典明细",
post: async function(data = {}){
return await http.put(this.url, data);
}
},
add:{
url: `${config.API_URL}system/dict/add`,
name: "添加字典明细",
post: async function(data = {}){
return await http.post(this.url, data);
}
},
delete:{
url: `${config.API_URL}system/dict/delete`,
name: "删除字典明细",
post: async function(data = {}){
return await http.delete(this.url, data);
}
},
detail: {
url: `${config.API_URL}system/dict/detail`,
name: "字典明细",
get: async function(params){
return await http.get(this.url, params);
}
},
alldic: {
url: `${config.API_URL}system/dict/all`,
name: "全部字典",
get: async function(params){
return await http.get(this.url, params);
}
}
},
area: {
list: {
url: `${config.API_URL}system/area/index`,
name: "地区列表",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}system/area/add`,
name: "地区添加",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}system/area/edit`,
name: "地区编辑",
post: async function(params){
return await http.put(this.url, params);
}
},
},
app: {
list: {
url: `${config.API_URL}system/app/list`,
name: "应用列表",
get: async function(){
return await http.get(this.url);
}
}
},
client: {
list: {
url: `${config.API_URL}system/client/index`,
name: "客户端列表",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}system/client/add`,
name: "客户端添加",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}system/client/edit`,
name: "客户端编辑",
post: async function(params){
return await http.put(this.url, params);
}
},
delete: {
url: `${config.API_URL}system/client/delete`,
name: "客户端删除",
post: async function(params){
return await http.delete(this.url, params);
}
},
menu: {
list: {
url: `${config.API_URL}system/menu/index`,
name: "客户端菜单列表",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}system/menu/add`,
name: "客户端菜单添加",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}system/menu/edit`,
name: "客户端菜单编辑",
post: async function(params){
return await http.put(this.url, params);
}
},
delete: {
url: `${config.API_URL}system/menu/delete`,
name: "客户端菜单删除",
post: async function(params){
return await http.delete(this.url, params);
}
}
}
},
log: {
list: {
url: `${config.API_URL}system/log/index`,
name: "日志列表",
get: async function(params){
return await http.get(this.url, params);
}
},
my: {
url: `${config.API_URL}system/log/my`,
name: "我的日志",
get: async function(params){
return await http.get(this.url, params);
}
},
delete: {
url: `${config.API_URL}system/log/delete`,
name: "日志删除",
post: async function(params){
return await http.delete(this.url, params);
}
}
},
tasks: {
list: {
url: `${config.API_URL}system/tasks/index`,
name: "任务列表",
get: async function(params){
return await http.get(this.url, params);
}
},
delete: {
url: `${config.API_URL}system/tasks/delete`,
name: "任务删除",
post: async function(params){
return await http.delete(this.url, params);
}
},
},
crontab: {
list: {
url: `${config.API_URL}system/crontab/index`,
name: "定时任务列表",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}system/crontab/add`,
name: "定时任务添加",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}system/crontab/edit`,
name: "定时任务编辑",
post: async function(params){
return await http.put(this.url, params);
}
},
delete: {
url: `${config.API_URL}system/crontab/delete`,
name: "定时任务删除",
post: async function(params){
return await http.delete(this.url, params);
}
},
log: {
url: `${config.API_URL}system/crontab/log`,
name: "定时任务日志",
get: async function(params){
return await http.get(this.url, params);
}
},
reload: {
url: `${config.API_URL}system/crontab/reload`,
name: "定时任务重载",
post: async function(params){
return await http.put(this.url, params);
}
}
},
modules:{
list: {
url: `${config.API_URL}system/modules/index`,
name: "模块列表",
get: async function(params){
return await http.get(this.url, params);
}
},
update: {
url: `${config.API_URL}system/modules/update`,
name: "更新模块",
post: async function(params){
return await http.post(this.url, params);
}
}
},
sms: {
count: {
url: `${config.API_URL}system/sms/count`,
name: "短信发送统计",
get: async function(params){
return await http.get(this.url, params);
}
}
}
}
@@ -0,0 +1,3 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M468.1 320.3V158.7c0.9-8.1-1.3-16.4-7.5-22.7-10.9-10.8-28.4-10.8-39.3 0L147.6 436.1c-5.8 5.7-8.3 13.4-7.9 20.9-0.4 7.5 2.1 15.1 7.9 20.9l272.2 298.4c10.2 8.7 28.8 13.6 40.8 1.6 6.2-6.2 8.9-11.4 8-19.5V594c180.8 0 344.4 130.2 376.8 301.6 21.9-50.2 34.1-105.6 34.1-163.9 0.1-227.2-184.1-411.4-411.4-411.4z" p-id="1450"></path></svg>
</template>
@@ -0,0 +1,3 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M258.389333 354.133333a299.093333 299.093333 0 0 1 8.490667-12.8h490.24c2.944 4.181333 5.76 8.490667 8.490667 12.8l86.186666-49.749333 42.666667 73.898667-94.421333 54.528c6.912 25.173333 10.624 51.754667 10.624 79.189333v42.666667h128v85.333333h-128a296.96 296.96 0 0 1-22.869334 114.773333l106.666667 61.610667-42.666667 73.898667-107.776-62.208A298.325333 298.325333 0 0 1 554.666667 935.637333V597.333333h-85.333334v338.346667a298.325333 298.325333 0 0 1-189.354666-107.605333l-107.776 62.208-42.666667-73.898667 106.666667-61.568A297.770667 297.770667 0 0 1 213.333333 640H85.333333v-85.333333h128v-42.666667c0-27.434667 3.712-53.973333 10.624-79.189333L129.536 378.282667l42.666667-73.898667L258.389333 354.133333zM341.333333 256a170.666667 170.666667 0 1 1 341.333334 0H341.333333z" p-id="26147"></path></svg>
</template>
@@ -0,0 +1,3 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M554.666667 849.066667a213.418667 213.418667 0 0 0 170.666666-209.066667v-128a212.48 212.48 0 0 0-17.706666-85.333333h-391.253334A212.48 212.48 0 0 0 298.666667 512v128a213.418667 213.418667 0 0 0 170.666666 209.066667V597.333333h85.333334v251.733334z m-318.464-94.293334A297.770667 297.770667 0 0 1 213.333333 640H85.333333v-85.333333h128v-42.666667c0-27.434667 3.712-53.973333 10.624-79.189333L129.536 378.282667l42.666667-73.898667L258.389333 354.133333a299.093333 299.093333 0 0 1 8.490667-12.8h490.24c2.944 4.181333 5.76 8.490667 8.490667 12.8l86.186666-49.749333 42.666667 73.898667-94.421333 54.528c6.912 25.173333 10.624 51.754667 10.624 79.189333v42.666667h128v85.333333h-128a296.96 296.96 0 0 1-22.869334 114.773333l106.666667 61.610667-42.666667 73.898667-107.776-62.208A298.069333 298.069333 0 0 1 512 938.666667a298.069333 298.069333 0 0 1-232.021333-110.592l-107.776 62.208-42.666667-73.898667 106.666667-61.568zM341.333333 256a170.666667 170.666667 0 1 1 341.333334 0H341.333333z" p-id="25873"></path></svg>
</template>
@@ -0,0 +1,3 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M981.333333 512l-301.696 301.696-60.330666-60.330667L860.672 512l-241.365333-241.365333 60.330666-60.330667L981.333333 512zM163.328 512l241.365333 241.365333-60.330666 60.330667L42.666667 512l301.696-301.696 60.330666 60.330667L163.328 512z" p-id="4503"></path></svg>
</template>
@@ -0,0 +1,3 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M554.666667 426.666667h213.333333l-256 256-256-256h213.333333V128h85.333334v298.666667z m-384 384h682.666666v-298.666667h85.333334v341.333333a42.666667 42.666667 0 0 1-42.666667 42.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667v-341.333333h85.333334v298.666667z" p-id="26056"></path></svg>
</template>
@@ -0,0 +1,3 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M121.984 122.752l536.32-76.586667a21.333333 21.333333 0 0 1 24.362667 21.12v889.429334a21.333333 21.333333 0 0 1-24.32 21.12L121.941333 901.248a42.666667 42.666667 0 0 1-36.650666-42.24V164.992a42.666667 42.666667 0 0 1 36.650666-42.24zM725.333333 128h170.666667a42.666667 42.666667 0 0 1 42.666667 42.666667v682.666666a42.666667 42.666667 0 0 1-42.666667 42.666667h-170.666667V128z m-290.133333 384L554.666667 341.333333h-102.4L384 438.869333 315.733333 341.333333H213.333333l119.466667 170.666667L213.333333 682.666667h102.4L384 585.130667 452.266667 682.666667H554.666667l-119.466667-170.666667z" p-id="3794"></path></svg>
</template>
@@ -0,0 +1,3 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M725.333333 128h170.666667a42.666667 42.666667 0 0 1 42.666667 42.666667v682.666666a42.666667 42.666667 0 0 1-42.666667 42.666667h-170.666667V128zM121.984 122.752l536.32-76.586667a21.333333 21.333333 0 0 1 24.362667 21.12v889.429334a21.333333 21.333333 0 0 1-24.32 21.12L121.941333 901.248a42.666667 42.666667 0 0 1-36.650666-42.24V164.992a42.666667 42.666667 0 0 1 36.650666-42.24zM213.333333 341.333333v341.333334h85.333334v-85.333334h256V341.333333H213.333333z m85.333334 85.333334h170.666666v85.333333H298.666667v-85.333333z" p-id="3925"></path></svg>
</template>
@@ -0,0 +1,3 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M725.333333 128h170.666667a42.666667 42.666667 0 0 1 42.666667 42.666667v682.666666a42.666667 42.666667 0 0 1-42.666667 42.666667h-170.666667V128zM121.984 122.752l536.32-76.586667a21.333333 21.333333 0 0 1 24.362667 21.12v889.429334a21.333333 21.333333 0 0 1-24.32 21.12L121.941333 901.248a42.666667 42.666667 0 0 1-36.650666-42.24V164.992a42.666667 42.666667 0 0 1 36.650666-42.24zM469.333333 341.333333v212.864L384 469.333333l-84.906667 85.333334L298.666667 341.333333H213.333333v341.333334h85.333334l85.333333-85.333334 85.333333 85.333334h85.333334V341.333333h-85.333334z" p-id="3663"></path></svg>
</template>
@@ -0,0 +1,3 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M640 128a42.666667 42.666667 0 0 1 42.666667 42.666667v170.666666a42.666667 42.666667 0 0 1-42.666667 42.666667h-85.333333v85.333333h170.666666a42.666667 42.666667 0 0 1 42.666667 42.666667v128h85.333333a42.666667 42.666667 0 0 1 42.666667 42.666667v170.666666a42.666667 42.666667 0 0 1-42.666667 42.666667h-256a42.666667 42.666667 0 0 1-42.666666-42.666667v-170.666666a42.666667 42.666667 0 0 1 42.666666-42.666667h85.333334v-85.333333H341.333333v85.333333h85.333334a42.666667 42.666667 0 0 1 42.666666 42.666667v170.666666a42.666667 42.666667 0 0 1-42.666666 42.666667H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666667v-170.666666a42.666667 42.666667 0 0 1 42.666667-42.666667h85.333333v-128a42.666667 42.666667 0 0 1 42.666667-42.666667h170.666666V384H384a42.666667 42.666667 0 0 1-42.666667-42.666667V170.666667a42.666667 42.666667 0 0 1 42.666667-42.666667h256zM384 725.333333H213.333333v85.333334h170.666667v-85.333334z m426.666667 0h-170.666667v85.333334h170.666667v-85.333334zM597.333333 213.333333h-170.666666v85.333334h170.666666V213.333333z" p-id="51975"></path></svg>
</template>
@@ -0,0 +1,3 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M170.666667 810.666667h682.666666v-298.666667h85.333334v341.333333a42.666667 42.666667 0 0 1-42.666667 42.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667v-341.333333h85.333334v298.666667z m384-426.666667v298.666667h-85.333334V384H256l256-256 256 256h-213.333333z" p-id="25917"></path></svg>
</template>
+3
View File
@@ -0,0 +1,3 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M42.666667 128h170.666666l298.666667 512 298.666667-512h170.666666L512 938.666667 42.666667 128z m369.792 0L512 298.666667l99.541333-170.666667h172.16L512 597.333333 240.298667 128h172.16z" p-id="4634"></path></svg>
</template>
@@ -0,0 +1,3 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M792.490667 585.002667a38.826667 38.826667 0 0 0 38.314666-38.314667c0-21.248-17.024-38.314667-38.314666-38.314667s-38.314667 17.066667-38.314667 38.314667c0 21.333333 17.066667 38.314667 38.314667 38.314667z m-188.8 0a38.826667 38.826667 0 0 0 38.314666-38.314667c0-21.248-17.066667-38.314667-38.314666-38.314667-21.333333 0-38.314667 17.066667-38.314667 38.314667 0 21.333333 17.024 38.314667 38.314667 38.314667z m280.192 215.04a14.805333 14.805333 0 0 0-7.338667 15.786666c0 2.048 0 4.138667 1.066667 6.272 4.181333 17.792 12.544 46.122667 12.544 47.189334 0 3.114667 1.066667 5.205333 1.066666 7.338666a9.386667 9.386667 0 0 1-9.429333 9.386667c-2.133333 0-3.157333-1.024-5.248-2.048l-61.824-35.669333a34.090667 34.090667 0 0 0-14.677333-4.181334c-3.114667 0-6.272 0-8.362667 1.024-29.354667 8.405333-59.733333 12.586667-92.202667 12.586667-156.16 0-281.898667-104.832-281.898666-234.88 0-130.005333 125.738667-234.88 281.898666-234.88 156.117333 0 281.856 104.874667 281.856 234.88 0 70.272-37.717333 134.229333-97.450666 177.237333zM711.381333 345.557333a388.48 388.48 0 0 0-11.946666-0.213333c-178.090667 0-324.522667 122.026667-324.522667 277.546667 0 23.637333 3.413333 46.506667 9.728 68.266666h-3.797333a425.088 425.088 0 0 1-110.250667-15.701333c-3.157333-1.066667-6.314667-1.066667-9.472-1.066667a35.498667 35.498667 0 0 0-17.834667 5.248l-74.581333 42.88c-2.133333 1.066667-4.224 2.133333-6.314667 2.133334a11.648 11.648 0 0 1-11.52-11.52c0-3.157333 1.024-5.248 2.090667-8.405334 1.024-1.024 10.496-35.584 15.744-56.490666 0-2.133333 1.024-5.248 1.024-7.338667a23.722667 23.722667 0 0 0-9.429333-18.858667C87.808 570.709333 42.666667 494.336 42.666667 409.514667 42.666667 253.653333 194.986667 128 381.866667 128c160.64 0 295.68 92.544 329.514666 217.514667z m-219.904 17.834667c24.448 0 43.776-20.352 43.776-43.776 0-24.448-19.328-43.776-43.776-43.776s-43.776 19.328-43.776 43.776 19.328 43.776 43.776 43.776z m-224.426666 0c24.448 0 43.818667-20.352 43.818666-43.776 0-24.448-19.370667-43.776-43.818666-43.776-24.405333 0-43.776 19.328-43.776 43.776s19.370667 43.776 43.776 43.776z" p-id="4372"></path></svg>
</template>
+12
View File
@@ -0,0 +1,12 @@
export { default as Vue } from './Vue.vue'
export { default as Code } from './Code.vue'
export { default as Wechat } from './Wechat.vue'
export { default as BugFill } from './BugFill.vue'
export { default as BugLine } from './BugLine.vue'
export { default as FileWord } from './FileWord.vue'
export { default as FileExcel } from './FileExcel.vue'
export { default as FilePpt } from './FilePpt.vue'
export { default as Organization } from './Organization.vue'
export { default as Upload } from './Upload.vue'
export { default as Download } from './Download.vue'
export { default as Back } from './Back.vue'
+103
View File
@@ -0,0 +1,103 @@
/* 全局 */
#app, body, html {width: 100%;height: 100%;background-color: #f6f8f9;font-size: 12px;}
a {color: #333;text-decoration: none;}
a:hover, a:focus {color: #000;text-decoration: none;}
a:link {text-decoration: none;}
a:-webkit-any-link {text-decoration: none;}
a,button,input,textarea{-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing: border-box;outline:none !important; -webkit-appearance: none;}
* {margin: 0;padding: 0;box-sizing: border-box;outline: none;}
/* 大布局样式 */
.aminui {display: flex;flex-flow: column;}
.aminui-wrapper {display: flex;flex:1;overflow: auto;}
/* 全局滚动条样式 */
.scrollable {-webkit-overflow-scrolling: touch;}
::-webkit-scrollbar {width: 5px;height: 5px;}
::-webkit-scrollbar-thumb {background-color: rgba(50, 50, 50, 0.3);}
::-webkit-scrollbar-thumb:hover {background-color: rgba(50, 50, 50, 0.6);}
::-webkit-scrollbar-track {background-color: rgba(50, 50, 50, 0.1);}
::-webkit-scrollbar-track:hover {background-color: rgba(50, 50, 50, 0.2);}
/*布局设置*/
.layout-setting {position: fixed;width: 40px;height: 40px;border-radius: 3px 0 0 3px;bottom: 100px;right: 0px;z-index: 100;background: #409EFF;display: flex;flex-direction: column;align-items: center;justify-content: center;cursor: pointer;}
.layout-setting i {font-size: 18px;color: #fff;}
/* 头部 */
.adminui-header {height: 58px;background: #fff;color: #222b45; border-bottom: 1px solid rgba(0, 0, 0, 0.05); display: flex;justify-content:space-between;}
.adminui-header-left {display: flex;align-items: center;padding-left:20px;}
.adminui-header-right {display: flex;align-items: center;}
.adminui-header .logo-bar {font-size: 20px;font-weight: bold;display: flex;align-items: center;}
.adminui-header .logo-bar .logo {margin-right: 10px;height: 35px;}
.adminui-header .nav {display: flex;height: 100%;margin-left: 40px;}
.adminui-header .nav li {padding:0 10px;margin: 0 10px 0 0;font-size: 14px;color: rgba(255, 255, 255, 0.6);list-style: none;height: 100%;display: flex;align-items: center;cursor: pointer;}
.adminui-header .nav li i {margin-right: 5px;}
.adminui-header .nav li:hover {color: #222b45;}
.adminui-header .nav li.active {background: rgba(255, 255, 255, 0.1);color: #222b45;}
.adminui-header .user-bar .panel-item:hover {background: rgba(255, 255, 255, 0.1)!important;}
.adminui-header .user-bar .user label{color: #222b45;}
/* 左侧菜单 */
.aminui-side-split {width:65px;flex-shrink:0;background: #222b45;display: flex;flex-flow: column;}
.aminui-side-split-top {height: 49px;}
.aminui-side-split-top a {display: inline-block;width: 100%;height: 100%;display: flex;align-items: center;justify-content: center;}
.aminui-side-split-top .logo {height:30px;vertical-align: bottom;}
.adminui-side-split-scroll {overflow: auto;overflow-x:hidden;height: 100%;flex: 1;}
.aminui-side-split li {cursor: pointer;width: 65px;height: 65px;color: #fff;text-align: center;display: flex;flex-direction: column;align-items: center;justify-content: center;}
.aminui-side-split li i {font-size: 18px;}
.aminui-side-split li p {margin-top:5px;}
.aminui-side-split li:hover {background: rgba(255, 255, 255, 0.1);}
.aminui-side-split li.active {background: #409EFF;}
.adminui-side-split-scroll::-webkit-scrollbar-thumb {background-color: rgba(255, 255, 255, 0.4);border-radius:5px;}
.adminui-side-split-scroll::-webkit-scrollbar-thumb:hover {background-color: rgba(255, 255, 255, 0.5);}
.adminui-side-split-scroll::-webkit-scrollbar-track {background-color: rgba(255, 255, 255, 0);}
.adminui-side-split-scroll::-webkit-scrollbar-track:hover {background-color: rgba(255, 255, 255, 0);}
.aminui-side {display: flex;flex-flow: column;flex-shrink:0;width:210px;background: #fff;box-shadow: 2px 0 8px 0 rgba(29,35,41,.05);border-right: 1px solid #e6e6e6;transition:width 0.3s;}
.adminui-side-top {border-bottom: 1px solid #ebeef5;height:50px;line-height: 50px;}
.adminui-side-top h2 {padding:0 20px;font-size: 17px;color: #3c4a54;}
.adminui-side-scroll {overflow: auto;overflow-x:hidden;flex: 1;}
.adminui-side-bottom {border-top: 1px solid #ebeef5;height:51px;cursor: pointer;display: flex;align-items: center;justify-content: center;}
.adminui-side-bottom i {font-size: 16px;}
.adminui-side-bottom:hover {color: var(--el-color-primary);}
.aminui-side.isCollapse {width: 65px;}
.el-menu .menu-tag {position: absolute;height: 18px;line-height: 18px;background: var(--el-color-danger);font-size: 12px;color: #fff;right: 20px;border-radius:18px;padding:0 6px;}
.el-menu .el-sub-menu__title .menu-tag {right: 40px;}
.el-menu--horizontal > li .menu-tag {display: none;}
/* 右侧内容 */
.aminui-body {flex: 1;display: flex;flex-flow: column;}
.adminui-topbar {height: 50px;border-bottom: 1px solid #ebeef5;background: #fff;box-shadow: 0 1px 4px rgba(0,21,41,.08);display: flex;justify-content:space-between;}
.adminui-topbar .left-panel {display: flex;align-items: center;}
.adminui-topbar .right-panel {display: flex;align-items: center;}
.right-panel-search {display: flex;align-items: center;}
.right-panel-search > * + * {margin-left:10px;}
.adminui-tags {height:35px;background: #fff;border-bottom: 1px solid #e6e6e6;}
.adminui-tags ul {display: flex;overflow: hidden;}
.adminui-tags li {cursor: pointer;display: inline-block;float: left;height:34px;line-height: 34px;position: relative;flex-shrink: 0;}
.adminui-tags li::after {content: " ";width:1px;height:100%;position: absolute;right:0px;background-image: linear-gradient(#fff, #e6e6e6);}
.adminui-tags li a {display: inline-block;padding:0 10px;width:100%;height:100%;color: #999;text-decoration:none;display: flex;align-items: center;}
.adminui-tags li i {margin-left:10px;border-radius: 3px;width:18px;height:18px;display: flex;align-items: center;justify-content: center;}
.adminui-tags li i:hover {background: rgba(0,0,0,.2);color: #fff;}
.adminui-tags li:hover {background: #ecf5ff;}
.adminui-tags li.active {background: #409EFF;}
.adminui-tags li.active a {color: #fff;}
.adminui-tags li.sortable-ghost {opacity: 0;}
.adminui-main {overflow: auto;background-color: #f6f8f9;flex: 1;}
/*页面最大化*/
.aminui.main-maximize {
.main-maximize-exit {display: block;}
.aminui-side-split, .aminui-side, .adminui-header, .adminui-topbar, .adminui-tags {display: none;}
}
.main-maximize-exit {display: none;position: fixed;z-index: 3000;top:-20px;left:50%;margin-left: -20px;border-radius: 50%;width: 40px;height: 40px;cursor: pointer;background: rgba(0,0,0,0.2);text-align: center;}
.main-maximize-exit i {font-size: 14px;margin-top: 22px;color: #fff;}
.main-maximize-exit:hover {background: rgba(0,0,0,0.4);}
/*定宽页面*/
.sc-page {width: 1230px;margin: 0 auto;}
@@ -0,0 +1,37 @@
@import 'element-plus/theme-chalk/src/dark/css-vars.scss';
html.dark {
//变量
--el-text-color-primary: #d0d0d0;
--el-color-primary-dark-2: var(--el-color-primary-light-2) !important;
--el-color-primary-light-9: var(--el-color-primary-dark-8) !important;
--el-color-primary-light-8: var(--el-color-primary-dark-7) !important;
--el-color-primary-light-7: var(--el-color-primary-dark-6) !important;
--el-color-primary-light-5: var(--el-color-primary-dark-4) !important;
--el-color-primary-light-3: var(--el-color-primary-dark-3) !important;
//背景
#app {background: var(--el-bg-color);}
//登录背景
.login_bg {background: var(--el-bg-color);}
//框架
.adminui-header {background: var(--el-bg-color-overlay);border-bottom: 1px solid var(--el-border-color-light);height:59px;}
.aminui-side-split {background: var(--el-bg-color);}
.aminui-side-split li {color: var(--el-text-color-primary);}
.aminui-side {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);}
.adminui-side-top, .adminui-side-bottom {border-color: var(--el-border-color-light);}
.adminui-side-top h2 {color: var(--el-text-color-primary);}
.adminui-topbar, .adminui-tags {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);}
.adminui-main {background: var(--el-bg-color);}
.drawerBG {background: var(--el-bg-color);}
.adminui-header-menu .el-menu {--el-menu-bg-color:var(--el-bg-color-overlay) !important;--el-menu-hover-bg-color: #171819 !important;}
//组件
.el-header, .el-main.nopadding, .el-footer {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);}
.el-main {background: var(--el-bg-color);}
.el-aside {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);}
.el-table .el-table__body-wrapper {background: var(--el-bg-color);}
.el-table th.is-sortable:hover {background: #111;}
}
+12
View File
@@ -0,0 +1,12 @@
input::-webkit-outer-spin-button,input::-webkit-inner-spin-button { -webkit-appearance: none;}
.action-icon{cursor: pointer; width: 1.2em; height: 1.2em; color: #409EFC; vertical-align: middle; margin-right: 5px;}
.el-tabs__new-tab{margin: 10px;}
.el-drawer__header{margin-bottom: 10px;}
.el-card__header{padding: 10px; padding-bottom: 0; font-size: 14px;}
.el-card__body{padding: 10px;}
.el-dialog__body{padding: calc(var(--el-dialog-padding-primary)) var(--el-dialog-padding-primary);}
.el-upload-dragger, .el-upload-dragger.is-dragover{padding: 0; border: none;}
+83
View File
@@ -0,0 +1,83 @@
/* 覆盖element-plus样式 */
:root {
--el-color-primary: #409EFF;
--el-color-primary-light-1: #53a7ff;
--el-color-primary-light-2: #66b1ff;
--el-color-primary-light-3: #79bbff;
--el-color-primary-light-4: #8cc4ff;
--el-color-primary-light-5: #9fceff;
--el-color-primary-light-6: #b2d8ff;
--el-color-primary-light-7: #c5e1ff;
--el-color-primary-light-8: #d8ebff;
--el-color-primary-light-9: #ebf5ff;
--el-color-primary-dark-1: #398ee5;
--el-color-primary-dark-2: #337ecc;
--el-color-primary-dark-3: #2c6eb2;
--el-color-primary-dark-4: #265e99;
--el-color-primary-dark-5: #204f7f;
--el-color-primary-dark-6: #193f66;
--el-color-primary-dark-7: #132f4c;
--el-color-primary-dark-8: #0c1f32;
--el-color-primary-dark-9: #060f19;
}
.el-menu {border: none!important;}
.el-menu .el-menu-item a {color: inherit;text-decoration: none;display: block;width:100%;height:100%;position: absolute;top:0px;left:0px;}
.el-form-item-msg {font-size: 12px;color: #999;clear: both;width: 100%;}
.el-container {height: 100%;}
.el-aside {border-right: 1px solid var(--el-border-color-light);}
.el-container + .el-aside {border-right: 0;border-left: 1px solid var(--el-border-color-light);}
.el-header {background: #fff;border-bottom: 1px solid var(--el-border-color-light);padding:13px 15px;display: flex;justify-content: space-between;align-items: center;}
.el-header .left-panel {display: flex;align-items: center;}
.el-header .right-panel {display: flex;align-items: center;}
.el-header .right-panel > * + * {margin-left:10px;}
.el-footer {background: #fff;border-top: 1px solid var(--el-border-color-light);padding:13px 15px;height: 51px;}
.el-main {padding:15px;}
.el-main.nopadding {padding:0;background: #fff;}
.el-drawer__body {overflow: auto;padding:0;}
.el-popconfirm__main {margin: 14px 0;}
.el-card__header {border-bottom: 0;font-size: 17px;font-weight: bold;padding:15px 20px 0px 20px;}
.el-dialog__title {font-size: 17px;font-weight: bold;}
.el-drawer__header>:first-child {font-size: 17px;font-weight: bold;}
.el-tree.menu .el-tree-node__content {height:36px;}
.el-tree.menu .el-tree-node__content .el-tree-node__label .icon {margin-right: 5px;}
.el-progress__text {font-size: 12px!important;}
.el-progress__text i {font-size: 14.4px!important;}
.el-step.is-horizontal .el-step__line {height:1px;}
.el-step__title {font-size: 14px;}
.drawerBG {background: #f6f8f9;}
.el-button+.el-dropdown {margin-left: 10px;}
.el-button-group+.el-dropdown {margin-left: 10px;}
.el-tag+.el-tag {margin-left: 10px;}
.el-button-group+.el-button-group {margin-left: 10px;}
.el-tabs__nav-wrap::after {height: 1px;}
.el-table th.is-sortable {transition: .1s;}
.el-table th.is-sortable:hover {background: #eee;}
.el-table .el-table__body-wrapper {background: #f6f8f9;}
.el-col .el-card {margin-bottom: 15px;}
.el-main {flex-basis: 100%;}
.el-main > .scTable .el-table--border::before {display: none;}
.el-main > .scTable .el-table--border::after {display: none;}
.el-main > .scTable .el-table--border .el-table__inner-wrapper::after {display: none;}
.el-main > .scTable .el-table__border-left-patch {display: none;}
.el-main > .scTable .el-table--border .el-table__inner-wrapper tr:first-child td:first-child {border-left: 0;}
.el-main > .scTable .el-table--border .el-table__inner-wrapper tr:first-child th:first-child {border-left: 0;}
.el-table.el-table--large {font-size: 14px;}
.el-table.el-table--small {font-size: 12px;}
.el-table {font-size: 12px;}
.el-radio-button__inner {font-size: 12px;}
.el-checkbox-button__inner {font-size: 12px;}
.el-sub-menu .el-icon {font-size: 17px;}
.el-sub-menu .el-sub-menu__icon-arrow {font-size: 12px;}
.aminui-side-split li.active {background-color: var(--el-color-primary);}
.adminui-tags li:hover {background-color: var(--el-color-primary-light-9);}
.adminui-tags li.active {background-color: var(--el-color-primary)!important;}
.contextmenu li:hover {background-color: var(--el-color-primary-light-9)!important;color: var(--el-color-primary-light-2)!important;}
.data-box .item-background {background-color: var(--el-color-primary)!important;}
.layout-setting,.diy-grid-setting {background-color: var(--el-color-primary)!important;}
/* 覆盖tinymce样式 */
.sceditor .tox-tinymce {border: 1px solid #DCDFE6;border-radius: 0;}
body .tox-tinymce-aux {z-index: 5700;}
@@ -0,0 +1,50 @@
@media (max-width: 992px){
// 移动端样式覆盖
.el-form-item {display: block;}
.el-form-item__label {display: block;text-align: left;padding: 0 0 10px;}
.el-dialog {width: 90%!important;}
.el-dialog.is-fullscreen {width: 100%!important;}
.el-drawer.rtl {width: 90%!important;}
.el-form-item__content {margin-left: 0px!important;}
.adminui-main {
>.el-container {display: block;height:auto;}
>.el-container > .el-aside {width: 100%!important;border: 0}
}
.scTable {
.el-table,
.el-table__body-wrapper {display: block!important;height:auto!important;}
.el-scrollbar__wrap {height:auto!important;}
.scTable-page {padding: 0 5px!important;}
.el-pagination__total,
.el-pagination__jump,
.el-pagination__sizes {display: none!important;}
}
.headerPublic {
height: auto!important;display: block;
.left-panel {overflow: auto;}
.left-panel::-webkit-scrollbar{display: none;}
.right-panel {display: block;border-top: 1px solid var(--el-border-color-light);margin-top: 15px;}
.right-panel .right-panel-search {display: block;}
.right-panel .right-panel-search >* {width: 100%;margin: 0;margin-top: 15px;}
}
.adminui-main > .el-container >*:first-child:not(.el-aside):not(.el-header) {border: 0;margin-top: 0;}
.adminui-main > .el-container >*:first-child:not(.el-aside):not(.el-header) + .el-aside {margin-top: 0;}
.adminui-main > .el-container > .el-aside {border-bottom: 1px solid var(--el-border-color-light)!important;}
.adminui-main > .el-container > .el-container {border-top: 1px solid var(--el-border-color-light);border-bottom: 1px solid var(--el-border-color-light);margin-top: 15px;}
.adminui-main > .el-container > .el-container + .el-aside {border-top: 1px solid var(--el-border-color-light);margin-top: 15px;}
.adminui-main > .el-container > .el-header {@extend .headerPublic;}
.adminui-main > .el-container > .el-main.nopadding {border-top: 1px solid var(--el-border-color-light);border-bottom: 1px solid var(--el-border-color-light);margin-top: 15px;}
.adminui-main > .el-container > .el-main + .el-aside {border-left: 0!important;border-top: 1px solid var(--el-border-color-light);margin-top: 15px;}
.adminui-main > .el-container > .el-footer {margin-top: 15px;border-bottom: 1px solid var(--el-border-color-light);}
.adminui-main > .el-container > .el-container > .el-header {@extend .headerPublic}
.adminui-main > .el-container > .el-container > .el-header .left-panel {display: block;}
.adminui-main > .el-container > .el-container > .el-header .right-panel {display: block;margin-top: 15px;}
.sc-page {width: 100%;margin: 0;}
.common-main .el-form {width: 100% !important;}
.common-header-logo label {display: none;}
.common-header-title {display: none;}
}
@@ -0,0 +1,44 @@
/* USERCENTER */
.page-user {
.user-info-top {text-align: center;}
.user-info-top h2 {font-size: 18px;margin-top: 5px;}
.user-info-top p {margin: 8px 0 10px 0;}
.menu {background: none;}
.menu .el-menu-item {font-size: 12px;--el-menu-item-height:50px;}
.menu .el-menu-item-group {border-top: 1px solid var(--el-border-color-light);}
.menu .el-menu-item-group:first-child {border: 0;}
}
/*static-table*/
.static-table {border-collapse: collapse;width: 100%;font-size: 14px;margin-bottom: 45px;line-height: 1.5em;}
.static-table th {text-align: left;white-space: nowrap;color: #909399;font-weight: 400;border-bottom: 1px solid #dcdfe6;padding: 15px;max-width: 250px;}
.static-table td {border-bottom: 1px solid #dcdfe6;padding: 15px;max-width: 250px;color: #606266;}
/*header-tabs*/
.header-tabs {padding:10px 0 0 0;display:block;border:0!important;height:50px;background: none;}
.header-tabs .el-tabs__header {padding-left:10px;margin: 0;}
.header-tabs .el-tabs__content {display: none;}
.header-tabs .el-tabs__nav {border-radius: 0 !important;}
.header-tabs .el-tabs__item {font-size: 13px;}
.header-tabs .el-tabs__item.is-active {background-color: var(--el-bg-color-overlay);}
/*common-page*/
.common-page {}
.common-header-left {display: flex;align-items: center;}
.common-header-logo {display: flex;align-items: center;}
.common-header-logo img {height:30px;margin-right: 10px;vertical-align: bottom;}
.common-header-logo label {font-size: 20px;}
.common-header-title {font-size: 16px;border-left: 1px solid var(--el-border-color-light);margin-left: 15px;padding-left: 15px;}
.common-header-right {display: flex;align-items: center;}
.common-header-right a {font-size: 14px;color: var(--el-color-primary);cursor: pointer;}
.common-header-right a:hover {color: var(--el-color-primary-light-3);}
.common-container {max-width: 1240px;margin:30px auto 30px auto;}
.common-main {padding:20px;}
.common-title {font-size: 26px;margin-bottom: 20px;font-weight: normal;}
.common-main .el-form {width: 500px;margin:30px auto;}
.common-main .el-steps .el-step__title {font-size: 14px;}
.common-main .el-steps .el-step__icon {border: 1px solid;}
.common-main .yzm {display: flex;width: 100%;}
.common-main .yzm .el-button {margin-left: 10px;}
.common-main .link {color: var(--el-color-primary);cursor: pointer;}
.common-main .link:hover {color: var(--el-color-primary-light-3);}
@@ -0,0 +1,6 @@
@import 'app.scss';
@import 'fix.scss';
@import 'pages.scss';
@import 'media.scss';
@import 'dark.scss';
@import 'diy.scss';
@@ -0,0 +1,115 @@
<!--
* @Descripttion: 代码编辑器
* @version: 1.0
* @Author: sakuya
* @Date: 2022年5月20日21:46:29
* @LastEditors:
* @LastEditTime:
-->
<template>
<div class="sc-code-editor" :style="{'height':_height}">
<textarea ref="textarea" v-model="contentValue"></textarea>
</div>
</template>
<script>
import { markRaw } from "vue"
//框架
import CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
//主题
import 'codemirror/theme/idea.css'
import 'codemirror/theme/darcula.css'
//功能
import 'codemirror/addon/selection/active-line'
//语言
import 'codemirror/mode/javascript/javascript'
import 'codemirror/mode/sql/sql'
export default {
props: {
modelValue: {
type: String,
default: ""
},
mode: {
type: String,
default: "javascript"
},
height: {
type: [String,Number],
default: 300,
},
options: {
type: Object,
default: () => {}
},
theme: {
type: String,
default: "idea"
},
readOnly: {
type: Boolean,
default: false
},
},
data() {
return {
contentValue: this.modelValue,
coder: null,
opt: {
theme: this.theme, //主题
styleActiveLine: true, //高亮当前行
lineNumbers: true, //行号
lineWrapping: false, //自动换行
tabSize: 4, //Tab缩进
indentUnit: 4, //缩进单位
indentWithTabs : true, //自动缩进
mode : this.mode, //语言
readOnly: this.readOnly, //只读
...this.options
}
}
},
computed: {
_height() {
return Number(this.height)?Number(this.height)+'px':this.height
},
},
watch: {
modelValue(val) {
this.contentValue = val
if (val !== this.coder.getValue()) {
this.coder.setValue(val)
}
}
},
mounted() {
this.init()
//获取挂载的所有modes
//console.log(CodeMirror.modes)
},
methods: {
init(){
this.coder = markRaw(CodeMirror.fromTextArea(this.$refs.textarea, this.opt))
this.coder.on('change', (coder) => {
this.contentValue = coder.getValue()
this.$emit('update:modelValue', this.contentValue)
})
},
formatStrInJson(strValue) {
return JSON.stringify(JSON.parse(strValue), null, 4)
}
}
}
</script>
<style scoped>
.sc-code-editor {font-size: 14px;border: 1px solid #ddd;line-height: 150%;}
.sc-code-editor:deep(.CodeMirror) {height: 100%;}
</style>
@@ -0,0 +1,100 @@
<!--
* @Descripttion: scContextmenu组件
* @version: 1.1
* @Author: sakuya
* @Date: 2021年7月23日09:25:57
* @LastEditors: sakuya
* @LastEditTime: 2022年5月30日20:17:42
* @other: 代码完全开源欢迎参考也欢迎PR
-->
<template>
<transition name="el-zoom-in-top">
<div v-if="visible" ref="contextmenu" class="sc-contextmenu" :style="{left:left+'px',top:top+'px'}" @contextmenu.prevent="fun">
<ul class="sc-contextmenu__menu">
<slot></slot>
</ul>
</div>
</transition>
</template>
<script>
export default {
provide() {
return {
menuClick: this.menuClick
}
},
data() {
return {
visible: false,
top: 0,
left: 0
}
},
watch: {
visible(value) {
if (value) {
document.body.addEventListener('click', this.cm, true)
}else{
document.body.removeEventListener('click', this.cm, true)
}
}
},
mounted() {
},
methods: {
cm(e){
let sp = this.$refs.contextmenu
if(sp&&!sp.contains(e.target)){
this.closeMenu()
}
},
menuClick(command){
this.closeMenu()
this.$emit('command', command)
},
openMenu(e) {
e.preventDefault()
this.visible = true
this.left = e.clientX + 1
this.top = e.clientY + 1
this.$nextTick(() => {
var ex = e.clientX + 1
var ey = e.clientY + 1
var innerWidth = window.innerWidth
var innerHeight = window.innerHeight
var menuHeight = this.$refs.contextmenu.offsetHeight
var menuWidth = this.$refs.contextmenu.offsetWidth
//位置修正公示
//left = (当前点击X + 菜单宽度 > 可视区域宽度 ? 可视区域宽度 - 菜单宽度 : 当前点击X)
//top = (当前点击Y + 菜单高度 > 可视区域高度 ? 当前点击Y - 菜单高度 : 当前点击Y)
this.left = ex + menuWidth > innerWidth ? innerWidth - menuWidth : ex
this.top = ey + menuHeight > innerHeight ? ey - menuHeight : ey
})
this.$emit('visibleChange', true)
},
closeMenu() {
this.visible = false;
this.$emit('visibleChange', false)
},
fun(){
return false;
}
}
}
</script>
<style>
.sc-contextmenu {position: fixed;z-index: 3000;font-size: 12px;}
.sc-contextmenu__menu {display: inline-block;min-width: 120px;border: 1px solid #e4e7ed;background: #fff;box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);z-index: 3000;list-style-type: none;padding: 10px 0;}
.sc-contextmenu__menu > hr {margin:5px 0;border: none;height: 1px;font-size: 0px;background-color: #ebeef5;}
.sc-contextmenu__menu > li {margin:0;cursor: pointer;line-height: 30px;padding: 0 17px 0 10px;color: #606266;display: flex;justify-content: space-between;white-space: nowrap;text-decoration: none;position: relative;}
.sc-contextmenu__menu > li:hover {background-color: #ecf5ff;color: #66b1ff;}
.sc-contextmenu__menu > li.disabled {cursor: not-allowed;color: #bbb;background: transparent;}
.sc-contextmenu__icon {display: inline-block;width: 14px;font-size: 14px;margin-right: 10px;}
.sc-contextmenu__suffix {margin-left: 40px;color: #999;}
.sc-contextmenu__menu li ul {position: absolute;top:0px;left:100%;display: none;margin: -11px 0;}
</style>
@@ -0,0 +1,84 @@
<!--
* @Descripttion: scContextmenuItem组件
* @version: 1.2
* @Author: sakuya
* @Date: 2021年7月23日16:29:36
* @LastEditors: sakuya
* @LastEditTime: 2022年2月8日15:51:07
-->
<template>
<hr v-if="divided">
<li :class="disabled?'disabled':''" @click.stop="liClick" @mouseenter="openSubmenu($event)" @mouseleave="closeSubmenu($event)">
<span class="title">
<el-icon class="sc-contextmenu__icon"><component v-if="icon" :is="icon" /></el-icon>
{{title}}
</span>
<span class="sc-contextmenu__suffix">
<el-icon v-if="$slots.default"><el-icon-arrow-right /></el-icon>
<template v-else>{{suffix}}</template>
</span>
<ul v-if="$slots.default" class="sc-contextmenu__menu">
<slot></slot>
</ul>
</li>
</template>
<script>
export default {
props: {
command: { type: String, default: "" },
title: { type: String, default: "" },
suffix: { type: String, default: "" },
icon: { type: String, default: "" },
divided: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
},
inject: ['menuClick'],
methods: {
liClick(){
if(this.$slots.default){
return false
}
if(this.disabled){
return false
}
this.menuClick(this.command)
},
openSubmenu(e){
var menu = e.target.querySelector('ul')
if(!menu){
return false
}
menu.style.display = 'inline-block'
var rect = menu.getBoundingClientRect()
var menuX = rect.left
var menuY = rect.top
var innerWidth = window.innerWidth
var innerHeight = window.innerHeight
var menuHeight = menu.offsetHeight
var menuWidth = menu.offsetWidth
if(menuX + menuWidth > innerWidth){
menu.style.left = 'auto'
menu.style.right = '100%'
}
if(menuY + menuHeight > innerHeight){
menu.style.top = 'auto'
menu.style.bottom = '0'
}
},
closeSubmenu(e){
var menu = e.target.querySelector('ul')
if(!menu){
return false
}
menu.removeAttribute("style")
menu.style.display = 'none'
}
}
}
</script>
<style>
</style>
@@ -0,0 +1,97 @@
<template>
<el-form>
<el-form-item label="类型">
<el-radio-group v-model="form.type">
<el-radio-button label="任意值" :value="0"></el-radio-button>
<el-radio-button label="范围" :value="1"></el-radio-button>
<el-radio-button label="间隔" :value="2"></el-radio-button>
<el-radio-button label="指定" :value="3"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="范围" v-if="form.type==1">
<el-input-number v-model="form.range.start" :min="1" :max="31" controls-position="right"></el-input-number>
<span style="padding:0 15px;">-</span>
<el-input-number v-model="form.range.end" :min="1" :max="31" controls-position="right"></el-input-number>
</el-form-item>
<el-form-item label="间隔" v-if="form.type==2">
<el-input-number v-model="form.loop.start" :min="1" :max="31" controls-position="right"></el-input-number>
号开始
<el-input-number v-model="form.loop.end" :min="1" :max="31" controls-position="right"></el-input-number>
天执行一次
</el-form-item>
<el-form-item label="指定" v-if="form.type==3">
<el-select v-model="form.appoint" multiple style="width: 100%;">
<el-option v-for="(item, index) in day" :key="index" :label="item" :value="item"></el-option>
</el-select>
</el-form-item>
</el-form>
</template>
<script>
export default {
emits: ['update:modelValue'],
props: {
modelValue: { type: String, default: "*" },
},
data(){
return {
form: {
type: '0',
range: {
start: 1,
end: 1
},
loop: {
start: 1,
end: 1
},
appoint: []
},
day: []
}
},
watch:{
modelValue:{
handler(val){
if(val == '*'){
this.form.type = 0
}else if(val.indexOf('-') > -1){
this.form.type = 1
}else if(val.indexOf('/') > -1){
this.form.type = 2
}else if(val.indexOf(',') > -1){
this.form.type = 3
}else{
this.form.type = 3
}
},
immediate: true
},
"form": {
handler(val){
let data = ''
if(val.type == 0){
data = '*'
}else if(val.type==1){
data = this.form.range.start + '-' + this.form.range.end
}else if(val.type==2){
data = this.form.loop.start + '/' + this.form.loop.end
}else if(val.type==3){
data = this.form.appoint.length>0 ? this.form.appoint.join(',') : '1'
}else{
data = '*'
}
this.$emit('update:modelValue', data)
},
deep: true
}
},
mounted(){
},
methods:{
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,97 @@
<template>
<el-form>
<el-form-item label="类型">
<el-radio-group v-model="form.type">
<el-radio-button label="任意值" :value="0"></el-radio-button>
<el-radio-button label="范围" :value="1"></el-radio-button>
<el-radio-button label="间隔" :value="2"></el-radio-button>
<el-radio-button label="指定" :value="3"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="范围" v-if="form.type==1">
<el-input-number v-model="form.range.start" :min="0" :max="23" controls-position="right"></el-input-number>
<span style="padding:0 15px;">-</span>
<el-input-number v-model="form.range.end" :min="0" :max="23" controls-position="right"></el-input-number>
</el-form-item>
<el-form-item label="间隔" v-if="form.type==2">
<el-input-number v-model="form.loop.start" :min="0" :max="23" controls-position="right"></el-input-number>
小时开始
<el-input-number v-model="form.loop.end" :min="0" :max="23" controls-position="right"></el-input-number>
小时执行一次
</el-form-item>
<el-form-item label="指定" v-if="form.type==3">
<el-select v-model="form.appoint" multiple style="width: 100%;">
<el-option v-for="(item, index) in hour" :key="index" :label="item" :value="item"></el-option>
</el-select>
</el-form-item>
</el-form>
</template>
<script>
export default {
emits: ['update:modelValue'],
props: {
modelValue: { type: String, default: "*" },
},
data(){
return {
form: {
type: '0',
range: {
start: 1,
end: 1
},
loop: {
start: 1,
end: 1
},
appoint: []
},
hour: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]
}
},
watch:{
modelValue:{
handler(val){
if(val == '*'){
this.form.type = 0
}else if(val.indexOf('-') > -1){
this.form.type = 1
}else if(val.indexOf('/') > -1){
this.form.type = 2
}else if(val.indexOf(',') > -1){
this.form.type = 3
}else{
this.form.type = 3
}
},
immediate: true
},
"form": {
handler(val){
let data = ''
if(val.type == 0){
data = '*'
}else if(val.type==1){
data = this.form.range.start + '-' + this.form.range.end
}else if(val.type==2){
data = this.form.loop.start + '/' + this.form.loop.end
}else if(val.type==3){
data = this.form.appoint.length>0 ? this.form.appoint.join(',') : '1'
}else{
data = '*'
}
this.$emit('update:modelValue', data)
},
deep: true
}
},
mounted(){
},
methods:{
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,97 @@
<template>
<el-form>
<el-form-item label="类型">
<el-radio-group v-model="form.type">
<el-radio-button label="任意值" :value="0"></el-radio-button>
<el-radio-button label="范围" :value="1"></el-radio-button>
<el-radio-button label="间隔" :value="2"></el-radio-button>
<el-radio-button label="指定" :value="3"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="范围" v-if="form.type==1">
<el-input-number v-model="form.range.start" :min="0" :max="23" controls-position="right"></el-input-number>
<span style="padding:0 15px;">-</span>
<el-input-number v-model="form.range.end" :min="0" :max="23" controls-position="right"></el-input-number>
</el-form-item>
<el-form-item label="间隔" v-if="form.type==2">
<el-input-number v-model="form.loop.start" :min="0" :max="23" controls-position="right"></el-input-number>
分钟开始
<el-input-number v-model="form.loop.end" :min="0" :max="23" controls-position="right"></el-input-number>
分钟执行一次
</el-form-item>
<el-form-item label="指定" v-if="form.type==3">
<el-select v-model="form.appoint" multiple style="width: 100%;">
<el-option v-for="(item, index) in minute" :key="index" :label="item" :value="item"></el-option>
</el-select>
</el-form-item>
</el-form>
</template>
<script>
export default {
emits: ['update:modelValue'],
props: {
modelValue: { type: String, default: "*" },
},
data(){
return {
form: {
type: '0',
range: {
start: 1,
end: 1
},
loop: {
start: 1,
end: 1
},
appoint: []
},
minute: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,51, 52, 53, 54, 55, 56, 57, 58, 59]
}
},
watch:{
modelValue:{
handler(val){
if(val == '*'){
this.form.type = 0
}else if(val.indexOf('-') > -1){
this.form.type = 1
}else if(val.indexOf('/') > -1){
this.form.type = 2
}else if(val.indexOf(',') > -1){
this.form.type = 3
}else{
this.form.type = 3
}
},
immediate: true
},
"form": {
handler(val){
let data = ''
if(val.type == 0){
data = '*'
}else if(val.type==1){
data = this.form.range.start + '-' + this.form.range.end
}else if(val.type==2){
data = this.form.loop.start + '/' + this.form.loop.end
}else if(val.type==3){
data = this.form.appoint.length>0 ? this.form.appoint.join(',') : '1'
}else{
data = '*'
}
this.$emit('update:modelValue', data)
},
deep: true
}
},
mounted(){
},
methods:{
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,99 @@
<template>
<el-form>
<el-form-item label="类型">
<el-radio-group v-model="form.type">
<el-radio-button label="任意值" :value="0"></el-radio-button>
<el-radio-button label="范围" :value="1"></el-radio-button>
<el-radio-button label="间隔" :value="2"></el-radio-button>
<el-radio-button label="指定" :value="3"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="范围" v-if="form.type==1">
<el-input-number v-model="form.range.start" :min="1" :max="12" controls-position="right"></el-input-number>
<span style="padding:0 15px;">-</span>
<el-input-number v-model="form.range.end" :min="1" :max="12" controls-position="right"></el-input-number>
</el-form-item>
<el-form-item label="间隔" v-if="form.type==2">
<el-input-number v-model="form.loop.start" :min="1" :max="12" controls-position="right"></el-input-number>
月开始
<el-input-number v-model="form.loop.end" :min="1" :max="12" controls-position="right"></el-input-number>
月执行一次
</el-form-item>
<el-form-item label="指定" v-if="form.type==3">
<el-select v-model="form.appoint" multiple style="width: 100%;">
<el-option v-for="(item, index) in data.month" :key="index" :label="item" :value="item"></el-option>
</el-select>
</el-form-item>
</el-form>
</template>
<script>
export default {
emits: ['update:modelValue'],
props: {
modelValue: { type: String, default: "*" },
},
data(){
return {
form: {
type: '0',
range: {
start: 1,
end: 1
},
loop: {
start: 1,
end: 1
},
appoint: []
},
data: {
month: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
}
}
},
watch:{
modelValue:{
handler(val){
if(val == '*'){
this.form.type = 0
}else if(val.indexOf('-') > -1){
this.form.type = 1
}else if(val.indexOf('/') > -1){
this.form.type = 2
}else if(val.indexOf(',') > -1){
this.form.type = 3
}else{
this.form.type = 3
}
},
immediate: true
},
"form": {
handler(val){
let data = ''
if(val.type == 0){
data = '*'
}else if(val.type==1){
data = this.form.range.start + '-' + this.form.range.end
}else if(val.type==2){
data = this.form.loop.start + '/' + this.form.loop.end
}else if(val.type==3){
data = this.form.appoint.length>0 ? this.form.appoint.join(',') : '1'
}else{
data = '*'
}
this.$emit('update:modelValue', data)
},
deep: true
}
},
mounted(){
},
methods:{
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,97 @@
<template>
<el-form>
<el-form-item label="类型">
<el-radio-group v-model="form.type">
<el-radio-button label="任意值" :value="0"></el-radio-button>
<el-radio-button label="范围" :value="1"></el-radio-button>
<el-radio-button label="间隔" :value="2"></el-radio-button>
<el-radio-button label="指定" :value="3"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="范围" v-if="form.type==1">
<el-input-number v-model="form.range.start" :min="0" :max="59" controls-position="right"></el-input-number>
<span style="padding:0 15px;">-</span>
<el-input-number v-model="form.range.end" :min="0" :max="59" controls-position="right"></el-input-number>
</el-form-item>
<el-form-item label="间隔" v-if="form.type==2">
<el-input-number v-model="form.loop.start" :min="0" :max="59" controls-position="right"></el-input-number>
秒开始
<el-input-number v-model="form.loop.end" :min="0" :max="59" controls-position="right"></el-input-number>
秒执行一次
</el-form-item>
<el-form-item label="指定" v-if="form.type==3">
<el-select v-model="form.appoint" multiple style="width: 100%;">
<el-option v-for="(item, index) in second" :key="index" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-form>
</template>
<script>
export default {
emits: ['update:modelValue'],
props: {
modelValue: { type: String, default: "*" },
},
data(){
return {
form: {
type: '0',
range: {
start: 0,
end: 59
},
loop: {
start: 0,
end: 59
},
appoint: []
},
second: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59]
}
},
watch:{
modelValue:{
handler(val){
if(val == '*'){
this.form.type = 0
}else if(val.indexOf('-') > -1){
this.form.type = 1
}else if(val.indexOf('/') > -1){
this.form.type = 2
}else if(val.indexOf(',') > -1){
this.form.type = 3
}else{
this.form.type = 3
}
},
immediate: true
},
"form": {
handler(val){
let data = ''
if(val.type == 0){
data = '*'
}else if(val.type==1){
data = this.form.range.start + '-' + this.form.range.end
}else if(val.type==2){
data = this.form.loop.start + '/' + this.form.loop.end
}else if(val.type==3){
data = this.form.appoint.length>0 ? this.form.appoint.join(',') : '1'
}else{
data = '*'
}
this.$emit('update:modelValue', data)
},
deep: true
}
},
mounted(){
},
methods:{
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,125 @@
<template>
<el-input v-model="defaultValue" v-bind="$attrs">
<template #append>
<el-dropdown size="medium" @command="handleShortcuts">
<el-button icon="el-icon-arrow-down"></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="* * * * *">每分钟</el-dropdown-item>
<el-dropdown-item command="0 * * * *">每小时</el-dropdown-item>
<el-dropdown-item command="0 0 * * *">每天零点</el-dropdown-item>
<el-dropdown-item command="0 0 1 * *">每月一号零点</el-dropdown-item>
<el-dropdown-item command="0 0 L * *">每月最后一天零点</el-dropdown-item>
<el-dropdown-item command="0 0 ? * 1">每周星期日零点</el-dropdown-item>
<el-dropdown-item v-for="(item, index) in shortcuts" :key="item.value" :divided="index==0" :command="item.value">{{item.text}}</el-dropdown-item>
<el-dropdown-item icon="el-icon-plus" divided command="custom">自定义</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-input>
<el-dialog title="cron规则生成器" v-model="dialogVisible" :width="580" destroy-on-close append-to-body>
<div class="sc-cron">
<el-tabs v-model="activeName">
<el-tab-pane v-for="(item, index) in tabsList" :key="index" :name="item.name">
<template #label>
<div class="sc-cron-num">
<h2>{{item.label}}</h2>
<h4>{{item.value}}</h4>
</div>
</template>
<component :is="item.name" v-model="item.value" />
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<el-button @click="dialogVisible=false" > </el-button>
<el-button type="primary" @click="submit()"> </el-button>
</template>
</el-dialog>
</template>
<script>
import second from './components/second.vue'
import minute from './components/minute.vue'
import hour from './components/hour.vue'
import day from './components/day.vue'
import month from './components/month.vue'
export default {
props: {
modelValue: { type: String, default: "* * * * * ?" },
shortcuts: { type: Array, default: () => [] }
},
data(){
return {
defaultValue: '',
dialogVisible: false,
activeName: 'second',
tabsList: [
{name: 'second', label: '秒', value: '*'},
{name: 'minute', label: '分钟', value: '*'},
{name: 'hour', label: '小时', value: '*'},
{name: 'day', label: '日', value: '*'},
{name: 'month', label: '月', value: '*'},
// {name: 'week', label: '周', value: '*'}
// {name: 'year', label: '年', value: '*'}
]
}
},
watch:{
modelValue: {
handler(val){
this.defaultValue = val
let arr = val.split(' ')
this.tabsList.forEach((item, index) => {
item.value = arr[index]
})
},
immediate: true,
deep: true
}
},
components:{
second,
minute,
hour,
day,
month
},
mounted(){
},
methods:{
handleShortcuts(command){
if(command == 'custom'){
this.open()
}else{
this.defaultValue = command
this.$emit('update:modelValue', this.defaultValue)
}
},
open(){
this.dialogVisible = true
},
submit(){
let data = []
for(let item of this.tabsList) {
data.push(item.value)
}
this.defaultValue = data.join(' ')
this.dialogVisible = false
this.$emit('update:modelValue', this.defaultValue)
}
}
}
</script>
<style scoped>
.sc-cron:deep(.el-tabs__item) {height: auto;line-height: 1;padding:0 7px;vertical-align: bottom;}
.sc-cron-num {text-align: center;margin-bottom: 15px;width: 100%;}
.sc-cron-num h2 {font-size: 12px;margin-bottom: 15px;font-weight: normal;}
.sc-cron-num h4 {display: block;height: 32px;line-height: 30px;width: 100%;font-size: 12px;padding:0 15px;background: var(--el-color-primary-light-9);border-radius:4px;}
.sc-cron:deep(.el-tabs__item.is-active) .sc-cron-num h4 {background: var(--el-color-primary);color: #fff;}
[data-theme='dark'] .sc-cron-num h4 {background: var(--el-color-white);}
</style>
@@ -0,0 +1,84 @@
<!--
* @Descripttion: 图像裁剪组件
* @version: 1.0
* @Author: sakuya
* @Date: 2021年7月24日17:05:43
* @LastEditors:
* @LastEditTime:
* @other: 代码完全开源欢迎参考也欢迎PR
-->
<template>
<div class="sc-cropper">
<div class="sc-cropper__img">
<img :src="src" ref="img">
</div>
<div class="sc-cropper__preview">
<h4>图像预览</h4>
<div class="sc-cropper__preview__img" ref="preview"></div>
</div>
</div>
</template>
<script>
import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.css'
export default {
props: {
src: { type: String, default: "" },
compress: {type: Number, default: 1},
aspectRatio: {type: Number, default: NaN},
},
data() {
return {
crop: null
}
},
watch:{
aspectRatio(val){
this.crop.setAspectRatio(val)
}
},
mounted() {
this.init()
},
methods: {
init(){
this.crop = new Cropper(this.$refs.img, {
viewMode: 2,
dragMode: 'move',
responsive: false,
aspectRatio: this.aspectRatio,
preview: this.$refs.preview
})
},
setAspectRatio(aspectRatio){
this.crop.setAspectRatio(aspectRatio)
},
getCropData(cb, type='image/jpeg'){
cb(this.crop.getCroppedCanvas().toDataURL(type, this.compress))
},
getCropBlob(cb, type='image/jpeg'){
this.crop.getCroppedCanvas().toBlob((blob) => {
cb(blob)
}, type, this.compress)
},
getCropFile(cb, fileName='fileName.jpg', type='image/jpeg'){
this.crop.getCroppedCanvas().toBlob((blob) => {
let file = new File([blob], fileName, {type: type})
cb(file)
}, type, this.compress)
}
}
}
</script>
<style scoped>
.sc-cropper {height:300px;}
.sc-cropper__img {height:100%;width:400px;float: left;background: #EBEEF5;}
.sc-cropper__img img {display: none;}
.sc-cropper__preview {width: 120px;margin-left: 20px;float: left;}
.sc-cropper__preview h4 {font-weight: normal;font-size: 12px;color: #999;margin-bottom: 20px;}
.sc-cropper__preview__img {overflow: hidden;width: 120px;height: 120px;border: 1px solid #ebeef5;}
</style>
@@ -0,0 +1,84 @@
<!--
* @Descripttion: 弹窗扩展组件
* @version: 2.0
* @Author: sakuya
* @Date: 2021年8月27日08:51:52
* @LastEditors: sakuya
* @LastEditTime: 2022年5月14日15:13:41
-->
<template>
<div class="sc-dialog" ref="scDialog">
<el-dialog ref="dialog" v-model="dialogVisible" :fullscreen="isFullscreen" v-bind="$attrs" :show-close="false">
<template #header>
<slot name="header">
<span class="el-dialog__title">{{ title }}</span>
</slot>
<div class="sc-dialog__headerbtn">
<button v-if="showFullscreen" aria-label="fullscreen" type="button" @click="setFullscreen">
<el-icon v-if="isFullscreen" class="el-dialog__close"><el-icon-bottom-left /></el-icon>
<el-icon v-else class="el-dialog__close"><el-icon-full-screen /></el-icon>
</button>
<button v-if="showClose" aria-label="close" type="button" @click="closeDialog">
<el-icon class="el-dialog__close"><el-icon-close /></el-icon>
</button>
</div>
</template>
<div v-loading="loading">
<slot></slot>
</div>
<template #footer>
<slot name="footer"></slot>
</template>
</el-dialog>
</div>
</template>
<script>
export default {
props: {
modelValue: { type: Boolean, default: false },
title: { type: String, default: "" },
showClose: { type: Boolean, default: true },
showFullscreen: { type: Boolean, default: true },
loading: { type: Boolean, default: false }
},
data() {
return {
dialogVisible: false,
isFullscreen: false
}
},
watch:{
modelValue(){
this.dialogVisible = this.modelValue
if(this.dialogVisible){
this.isFullscreen = false
}
}
},
mounted() {
this.dialogVisible = this.modelValue
},
methods: {
//关闭
closeDialog(){
this.dialogVisible = false
},
//最大化
setFullscreen(){
this.isFullscreen = !this.isFullscreen
}
}
}
</script>
<style scoped>
.sc-dialog__headerbtn {position: absolute;top: var(--el-dialog-padding-primary);right: var(--el-dialog-padding-primary);}
.sc-dialog__headerbtn button {padding: 0;background: transparent;border: none;outline: none;cursor: pointer;font-size: var(--el-message-close-size,16px);margin-left: 15px;color: var(--el-color-info);}
.sc-dialog__headerbtn button:hover .el-dialog__close {color: var(--el-color-primary);}
.sc-dialog:deep(.el-dialog).is-fullscreen {display: flex;flex-direction: column;top:0px !important;left:0px !important;}
.sc-dialog:deep(.el-dialog).is-fullscreen .el-dialog__header {}
.sc-dialog:deep(.el-dialog).is-fullscreen .el-dialog__body {flex:1;overflow: auto;}
.sc-dialog:deep(.el-dialog).is-fullscreen .el-dialog__footer {padding-bottom: 10px;border-top: 1px solid var(--el-border-color-base);}
</style>
@@ -0,0 +1,74 @@
const T = {
"color": [
"#409EFF",
"#36CE9E",
"#f56e6a",
"#626c91",
"#edb00d",
"#909399"
],
'grid': {
'left': '3%',
'right': '3%',
'bottom': '10',
'top': '40',
'containLabel': true
},
"legend": {
"textStyle": {
"color": "#999"
},
"inactiveColor": "rgba(128,128,128,0.4)"
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "rgba(128,128,128,0.2)",
"width": 1
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"color": "#999"
},
"splitLine": {
"show": false,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(255,255,255,0.01)",
"rgba(0,0,0,0.01)"
]
}
}
},
"valueAxis": {
"axisLine": {
"show": false,
"lineStyle": {
"color": "#999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": "rgba(128,128,128,0.2)"
}
}
}
}
export default T
@@ -0,0 +1,64 @@
<template>
<div ref="scEcharts" :style="{height:height, width:width}"></div>
</template>
<script>
import * as echarts from 'echarts';
import T from './echarts-theme-T.js';
echarts.registerTheme('T', T);
const unwarp = (obj) => obj && (obj.__v_raw || obj.valueOf() || obj);
export default {
...echarts,
name: "scEcharts",
props: {
height: { type: String, default: "100%" },
width: { type: String, default: "100%" },
nodata: {type: Boolean, default: false },
option: { type: Object, default: () => {} }
},
data() {
return {
isActivat: false,
myChart: null
}
},
watch: {
option: {
deep:true,
handler (v) {
unwarp(this.myChart).setOption(v);
}
}
},
computed: {
myOptions: function() {
return this.option || {};
}
},
activated(){
if(!this.isActivat){
this.$nextTick(() => {
this.myChart.resize()
})
}
},
deactivated(){
this.isActivat = false;
},
mounted(){
this.isActivat = true;
this.$nextTick(() => {
this.draw();
})
},
methods: {
draw(){
var myChart = echarts.init(this.$refs.scEcharts, 'T');
myChart.setOption(this.myOptions);
this.myChart = myChart;
window.addEventListener('resize', () => myChart.resize());
}
}
}
</script>
@@ -0,0 +1,80 @@
export default class UploadAdapter {
constructor( loader, options ) {
this.loader = loader;
this.options = options;
}
upload() {
return this.loader.file
.then( file => new Promise( ( resolve, reject ) => {
this._initRequest();
this._initListeners( resolve, reject, file );
this._sendRequest( file );
} ) );
}
abort() {
if ( this.xhr ) {
this.xhr.abort();
}
}
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
xhr.open( 'POST', this.options.upload.uploadUrl, true );
xhr.responseType = 'json';
}
_initListeners( resolve, reject, file ) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${ file.name }.`;
xhr.addEventListener( 'error', () => reject( genericErrorText ) );
xhr.addEventListener( 'abort', () => reject() );
xhr.addEventListener( 'load', () => {
const response = xhr.response;
if ( !response || response.code == 0 ) {
return reject( response && response.code == 0 ? response.message : genericErrorText );
}
resolve( {
default: response.data.url ? response.data.url : response.data.src
} );
} );
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', evt => {
if ( evt.lengthComputable ) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
} );
}
}
_sendRequest( file ) {
// Set headers if specified.
const headers = this.options.upload.headers || {};
const extendData = this.options.upload.extendData || {};
// Use the withCredentials flag if specified.
const withCredentials = this.options.upload.withCredentials || false;
const uploadName = this.options.upload.uploadName || 'file';
for (const headerName of Object.keys(headers)) {
this.xhr.setRequestHeader(headerName, headers[headerName]);
}
this.xhr.withCredentials = withCredentials;
const data = new FormData();
for (const key of Object.keys(extendData)) {
data.append(key, extendData[key]);
}
data.append( uploadName, file );
this.xhr.send( data );
}
}
export function UploadAdapterPlugin( editor ) {
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
return new UploadAdapter( loader, editor.config._config );
};
}
@@ -0,0 +1,180 @@
<template>
<div :style="{'--editor-height': editorHeight}">
<ckeditor :editor="editor" v-model="editorData" :config="editorConfig" :disabled="disabled" @blur="onBlur" @focus="onFocus"></ckeditor>
</div>
</template>
<script>
import { ClassicEditor, Alignment, AutoImage, Autoformat, BlockQuote, Bold, CodeBlock, DataFilter, DataSchema, Essentials, FindAndReplace,
FontBackgroundColor, FontColor, FontFamily, FontSize, GeneralHtmlSupport, Heading, Highlight, HorizontalLine, Image, ImageCaption, ImageInsert, ImageResize,
ImageStyle, ImageToolbar, ImageUpload, Indent, IndentBlock, Italic, Link, LinkImage, List, MediaEmbed, MediaEmbedToolbar, Mention, Paragraph, PasteFromOffice,
RemoveFormat, SelectAll, ShowBlocks, SourceEditing, SpecialCharacters, SpecialCharactersArrows, SpecialCharactersCurrency, SpecialCharactersEssentials, SpecialCharactersLatin,
SpecialCharactersMathematical, SpecialCharactersText, Style, Subscript, Superscript, Table, TableCaption, TableCellProperties, TableColumnResize,
TableProperties, TableToolbar, TextTransformation, TodoList, Underline, Undo, WordCount
} from 'ckeditor5';
import { Ckeditor } from '@ckeditor/ckeditor5-vue';
import { UploadAdapterPlugin } from './UploadAdapter.js'
import coreTranslations from 'ckeditor5/translations/zh-cn.js'
import 'ckeditor5/ckeditor5.css';
export default {
name: 'scCkeditor',
props: {
modelValue: {
type: String,
default: ''
},
placeholder: {
type: String,
default: '请输入内容……'
},
toolbar: {
type: String,
default: 'basic'
},
height: {
type: String,
default: '400px'
},
disabled: {
type: Boolean,
default: false
}
},
components: {
Ckeditor
},
data(){
return {
editorData: '',
editor: ClassicEditor,
editorHeight: 0,
toolbars: {
full: [
'sourceEditing', 'undo', 'redo', 'heading', 'style',
'|', 'superscript', 'subscript', 'removeFormat', 'bold', 'italic', 'underline', 'link', 'fontBackgroundColor', 'fontFamily', 'fontSize', 'fontColor',
'|', 'outdent', 'indent', 'alignment', 'bulletedList', 'numberedList', 'todoList',
'|', 'blockQuote', 'insertTable', 'imageInsert', 'mediaEmbed', 'highlight', 'horizontalLine', 'selectAll', 'showBlocks', 'specialCharacters', 'codeBlock', 'findAndReplace'
],
basic: [
'sourceEditing', 'undo', 'redo', 'heading',
'|', 'removeFormat', 'bold', 'italic', 'underline', 'link', 'fontBackgroundColor', 'fontFamily', 'fontSize', 'fontColor',
'|', 'outdent', 'indent', 'alignment', 'bulletedList', 'numberedList', 'todoList',
'|', 'insertTable', 'imageInsert', 'mediaEmbed'
],
simple: [
'undo', 'redo', 'heading',
'|', 'removeFormat', 'bold', 'italic', 'underline', 'link', 'fontBackgroundColor', 'fontFamily', 'fontSize', 'fontColor',
'|', 'insertTable', 'imageInsert', 'mediaEmbed'
]
},
editorConfig: {
language: {ui: 'zh-cn', content: 'zh-cn'},
translations: [ coreTranslations ],
plugins: [ Alignment, AutoImage, Autoformat, BlockQuote, Bold, CodeBlock, DataFilter, DataSchema, Essentials, FindAndReplace,
FontBackgroundColor, FontColor, FontFamily, FontSize, GeneralHtmlSupport, Heading, Highlight, HorizontalLine, Image, ImageCaption, ImageInsert, ImageResize,
ImageStyle, ImageToolbar, ImageUpload, Indent, IndentBlock, Italic, Link, LinkImage, List, MediaEmbed, MediaEmbedToolbar, Mention, Paragraph, PasteFromOffice,
RemoveFormat, SelectAll, ShowBlocks, SourceEditing, SpecialCharacters, SpecialCharactersArrows, SpecialCharactersCurrency, SpecialCharactersEssentials, SpecialCharactersLatin,
SpecialCharactersMathematical, SpecialCharactersText, Style, Subscript, Superscript, Table, TableCaption, TableCellProperties, TableColumnResize,
TableProperties, TableToolbar, TextTransformation, TodoList, Underline, Undo, WordCount, UploadAdapterPlugin
],
toolbar: {shouldNotGroupWhenFull: true},
placeholder: '',
image: {
styles: ['alignLeft', 'alignCenter', 'alignRight'],
toolbar: ['imageTextAlternative', 'toggleImageCaption', '|', 'imageStyle:alignLeft', 'imageStyle:alignCenter', 'imageStyle:alignRight', '|', 'linkImage']
},
mediaEmbed: {
providers: [
{name: 'mp4', url: /\.(mp4|avi|mov|flv|wmv|mkv)$/i, html: match => {
const url = match['input']
return ('<video controls width="100%" height="100%" src="${url}"></video>')
}
}
]
},
fontSize: {
options: [
10, 12, 14, 16, 18, 20, 22, 24, 26, 30, 32, 36
]
},
style: {
definitions: [
{
name: 'Article category',
element: 'h3',
classes: [ 'category' ]
},
{
name: 'Info box',
element: 'p',
classes: [ 'info-box' ]
},
]
},
upload: {
uploadUrl: this.$API.common.upload.url,
withCredentials: false,
extendData: {type: 'images'},
headers: {
Authorization: 'Bearer ' + this.$TOOL.data.get('TOKEN')
}
},
}
}
},
watch: {
modelValue: {
handler(newVal) {
this.editorData = newVal ?? ''
},
immediate: true
},
placeholder: {
handler(newVal) {
this.editorConfig.placeholder = newVal
},
immediate: true,
deep: true
},
toolbar: {
handler(newVal) {
this.editorConfig.toolbar.items = this.toolbars[newVal]
},
immediate: true,
deep: true
},
height: {
handler(newVal) {
this.editorHeight = newVal
},
immediate: true,
deep: true
}
},
mounted() {
},
methods: {
onBlur(event, editor) {
this.editorData = this.editorData.replace(/<img[^>]*>/gi, (match) => {
return match.replace(/width="[^"]*"/gi, '').replace(/height="[^"]*"/gi, '')
})
this.$emit('update:modelValue', this.editorData)
},
onFocus(event, editor) {
}
}
}
</script>
<style>
:root{
--ck-z-panel: 9999
}
.ck-content{
height: var(--editor-height);
}
.ck-source-editing-area, .ck-source-editing-area textarea{height: var(--editor-height);}
.ck-source-editing-area textarea{overflow-y: scroll !important;}
</style>
@@ -0,0 +1,210 @@
<template>
<div ref="aiEditor" :style="{height: editorHeight, width: '100%'}"></div>
</template>
<script>
import {AiEditor} from "aieditor";
import "aieditor/dist/style.css"
export default {
name: 'scCkeditor',
emits: ['update:modelValue'],
props: {
modelValue: {
type: String,
default: ''
},
placeholder: {
type: String,
default: '请输入内容……'
},
toolbar: {
type: String,
default: 'basic'
},
height: {
type: String,
default: '400px'
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
editor: null,
defaultValue: '',
editorHeight: '300px',
toolbars: {
full: [
"undo", "redo", "brush", "eraser",
"|", "heading", "font-family", "font-size",
"|", "bold", "italic", "underline", "strike", "link", "code", "subscript", "superscript", "hr", "todo", "emoji",
"|", "highlight", "font-color",
"|", "align", "line-height",
"|", "bullet-list", "ordered-list", "indent-decrease", "indent-increase", "break",
"|", "image", "video", "attachment", "quote", "code-block", "table",
"|", "source-code", "printer", "fullscreen", "ai"
],
basic: [
"undo", "redo", "brush", "eraser",
"|", "heading", "font-family", "font-size",
"|", "bold", "italic", "underline", "strike", "link", "code", "subscript", "superscript", "hr", "todo", "emoji",
"|", "highlight", "font-color",
"|", "align", "line-height",
"|", "bullet-list", "ordered-list", "indent-decrease", "indent-increase", "break",
"|", "image", "video", "attachment", "quote", "code-block", "table",
],
simple: ["undo", "redo",
"|", "font-family", "font-size",
"|", "bold", "italic", "underline", "strike", "emoji",
"|", "highlight", "font-color",
"|", "image", "link"]
}
}
},
watch: {
modelValue(newValue) {
if (newValue !== this.defaultValue) {
this.defaultValue = newValue
}
},
height: {
handler(newValue) {
if (indexOf(newValue, 'px') === -1){
this.editorHeight = newValue > 200 ? newValue + 'px' : '200px'
}else{
newValue = newValue.replace('px', '')
this.editorHeight = newValue > 200 ? newValue + 'px' : '200px'
}
},
immediate: true
}
},
mounted() {
this.editor = new AiEditor({
element: this.$refs.aiEditor,
placeholder: this.placeholder,
content: this.defaultValue,
toolbarKeys: this.toolbars[this.toolbar],
onChange: (editor) => {
this.$emit('update:modelValue', editor.getHtml())
},
editable: !this.disabled,
textSelectionBubbleMenu: {
enable: true,
items: ["Bold", "Italic", "Underline", "Strike", "code", "comment"],
},
image: {
uploadUrl: this.$API.common.upload.url,
uploadHeaders: {
Authorization: 'Bearer ' + this.$TOOL.data.get('TOKEN')
},
uploadFormName: 'file',
uploader: (file, uploadUrl, headers, formName) => {
const formData = new FormData();
formData.append(formName, file);
formData.append('type', 'images');
return new Promise((resolve, reject) => {
fetch(uploadUrl, {
method: "post",
headers: {'Accept': 'application/json', ...headers},
body: formData,
}).then((resp) => resp.json())
.then(json => {
resolve({json});
}).catch((error) => {
reject(error);
})
});
},
uploaderEvent: {
onSuccess: (file, response) => {
let res = response.json
if (res.code === 1) {
return {errorCode: 0, data: {
src: res.data.url
}}
} else {
return false
}
}
}
},
video: {
uploadUrl: this.$API.common.upload.url,
uploadHeaders: {
Authorization: 'Bearer ' + this.$TOOL.data.get('TOKEN')
},
uploadFormName: 'file',
uploader: (file, uploadUrl, headers, formName) => {
const formData = new FormData();
formData.append(formName, file);
formData.append('type', 'video');
return new Promise((resolve, reject) => {
fetch(uploadUrl, {
method: "post",
headers: {'Accept': 'application/json', ...headers},
body: formData,
}).then((resp) => resp.json())
.then(json => {
resolve(json);
}).catch((error) => {
reject(error);
})
});
},
uploaderEvent: {
onSuccess: (file, response) => {
let res = response.json
if (res.code === 1) {
return {errorCode: 0, data: {
src: res.data.url
}}
} else {
return false
}
}
}
},
attachment: {
uploadUrl: this.$API.common.upload.url,
uploadHeaders: {
Authorization: 'Bearer ' + this.$TOOL.data.get('TOKEN')
},
uploadFormName: 'file',
uploader: (file, uploadUrl, headers, formName) => {
const formData = new FormData();
formData.append(formName, file);
formData.append('type', 'files');
return new Promise((resolve, reject) => {
fetch(uploadUrl, {
method: "post",
headers: {'Accept': 'application/json', ...headers},
body: formData,
}).then((resp) => resp.json())
.then(json => {
resolve(json);
}).catch((error) => {
reject(error);
})
});
},
uploaderEvent: {
onSuccess: (file, response) => {
let res = response.json
if (res.code === 1) {
return {errorCode: 0, data: {
src: res.data.url
}}
} else {
return false
}
}
}
},
})
}
}
</script>
@@ -0,0 +1,53 @@
<template>
<el-table ref="table" :data="columnData" row-key="prop" style="width: 100%" border>
<el-table-column prop="" label="排序" width="60">
<el-tag class="move" style="cursor: move;"><el-icon style="cursor: move;"><el-icon-d-caret/></el-icon></el-tag>
</el-table-column>
<el-table-column prop="label" label="列名">
<template #default="scope">
<el-tag round :effect="scope.row.hide?'light':'dark'" :type="scope.row.hide?'info':''">{{ scope.row.label }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="hide" label="显示" width="60">
<template #default="scope">
<el-switch v-model="scope.row.hide" :active-value="false" :inactive-value="true"/>
</template>
</el-table-column>
</el-table>
</template>
<script>
import Sortable from 'sortablejs'
export default {
emits: ['success'],
props: {
column: { type: Array, default: () => [] }
},
data() {
return {
columnData: this.column
}
},
mounted() {
this.rowDrop()
},
methods: {
rowDrop(){
const _this = this
const tbody = this.$refs.table.$el.querySelector('.el-table__body-wrapper tbody')
Sortable.create(tbody, {
handle: ".move",
animation: 200,
ghostClass: "ghost",
onEnd({ newIndex, oldIndex }) {
const tableData = _this.columnData
const currRow = tableData.splice(oldIndex, 1)[0]
tableData.splice(newIndex, 0, currRow)
}
})
}
}
}
</script>
@@ -0,0 +1,199 @@
<!--
* @Descripttion: 文件导出
* @version: 1.1
* @Author: sakuya
* @Date: 2022年5月24日16:20:12
* @LastEditors: sakuya
* @LastEditTime: 2022年6月13日17:32:05
-->
<template>
<slot :open="open">
<el-button type="primary" plain @click="open">导出</el-button>
</slot>
<el-drawer v-model="dialog" title="导出" :size="400" direction="rtl" append-to-body destroy-on-close>
<el-main style="padding: 0 20px 20px 20px;">
<div v-loading="downLoading" element-loading-text="正在处理中...">
<div v-if="downLoading && progress" style="position: absolute;width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;z-index: 3000;">
<el-progress :text-inside="true" :stroke-width="20" :percentage="downLoadProgress" style="width: 100%;margin-bottom: 120px;"/>
</div>
<el-tabs>
<el-tab-pane label="常规" lazy>
<el-form label-width="100px" label-position="left" style="margin: 10px 0 20px 0;">
<el-form-item label="文件名">
<el-input v-model="formData.fileName" placeholder="请输入文件名" />
</el-form-item>
<el-form-item label="文件类型">
<el-select v-model="formData.fileType" placeholder="请选择文件类型">
<el-option v-for="item in fileTypes" :key="item" :label="'*.'+item" :value="item" />
</el-select>
</el-form-item>
<slot name="form" :formData="formData"></slot>
</el-form>
<el-button v-if="async" type="primary" icon="el-icon-plus" style="width: 100%;" @click="download" :loading="asyncLoading">发起导出任务</el-button>
<el-button v-else type="primary" icon="el-icon-download" style="width: 100%;" @click="download"> </el-button>
</el-tab-pane>
<el-tab-pane label="列设置" v-if="columnData.length>0" lazy>
<columnSet :column="columnData"></columnSet>
</el-tab-pane>
<el-tab-pane label="其他参数" v-if="data && showData" lazy>
<el-descriptions :column="1" border>
<el-descriptions-item v-for=" (val, key) in data" :key="key" :label="key">{{val}}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
</el-tabs>
</div>
</el-main>
</el-drawer>
</template>
<script>
import columnSet from './column'
export default {
components: {
columnSet
},
props: {
apiObj: { type: Object, default: () => {} },
fileName: { type: String, default: "" },
fileTypes: { type: Array, default: () => ['xlsx'] },
data: { type: Object, default: () => {} },
showData: { type: Boolean, default: false },
async: { type: Boolean, default: false },
column: { type: Array, default: () => [] },
blob: { type: Boolean, default: false },
progress: { type: Boolean, default: true }
},
data() {
return {
dialog: false,
formData: {
fileName: this.fileName,
fileType: this.fileTypes[0]
},
columnData: [],
downLoading: false,
downLoadProgress: 0,
asyncLoading: false
}
},
watch:{
'formData.fileType'(val) {
if(this.formData.fileName.includes(".")){
this.formData.fileName = this.formData.fileName.substring(0, this.formData.fileName.lastIndexOf('.')) + "." + val
}else{
this.formData.fileName = this.formData.fileName + "." + val
}
}
},
mounted() {
},
methods: {
open() {
this.dialog = true
this.formData = {
fileName: (this.fileName?this.fileName:(new Date().getTime()+'')) + "." + this.fileTypes[0],
fileType: this.fileTypes[0]
}
this.columnData = JSON.parse(JSON.stringify(this.column))
},
close() {
this.dialog = false
},
download() {
let columnArr = {
column: this.columnData.filter(n => !n.hide).map(n => n.prop).join(",")
}
let assignData = {...this.data, ...this.formData, ...columnArr}
if(this.async){
this.asyncDownload(this.apiObj, this.formData.fileName, assignData)
}else if(this.blob){
this.downloadFile(this.apiObj, this.formData.fileName, assignData)
}else{
this.linkFile(this.apiObj.url, this.formData.fileName, assignData)
}
},
linkFile(url, fileName, data={}){
let a = document.createElement("a")
a.style = "display: none"
a.target = "_blank"
//a.download = fileName
a.href = url + this.toQueryString(data)
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
},
downloadFile(apiObj, fileName, data={}){
this.downLoading = true
var _this = this
apiObj.get(data, {
responseType: 'blob',
onDownloadProgress(e){
if(e.lengthComputable){
_this.downLoadProgress = parseInt(e.loaded / e.total * 100)
}
}
}).then(res => {
this.downLoading = false
this.downLoadProgress = 0
// let url = URL.createObjectURL(res.data)
let url = res.data
let a = document.createElement("a")
a.style = "display: none"
a.target = "_blank"
a.download = fileName
a.href = url
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
// URL.revokeObjectURL(url)
}).catch(err => {
this.downLoading = false
this.downLoadProgress = 0
this.$notify.error({
title: '下载文件失败',
message: err
})
})
},
asyncDownload(apiObj, fileName, data={}){
this.asyncLoading = true
apiObj.get(data).then(res => {
this.asyncLoading = false
if(res.code == 1){
this.dialog = false
this.$msgbox({
title: "成功发起任务",
message: `<div><img style="height:200px" :src="'static/images/tasks-example.png'"/></div><p>已成功发起导出任务,您可以操作其他事务</p><p>稍后可在 <b>任务中心</b> 查看执行结果</p>`,
type: "success",
confirmButtonText: "知道了",
dangerouslyUseHTMLString: true,
center: true
}).catch(() => {})
}else{
this.$alert(res.message || "未知错误", "发起任务失败", {
type: "error",
center: true
}).catch(() => {})
}
}).catch(() => {
this.asyncLoading = false
})
},
toQueryString(obj){
let arr = []
for (var k in obj) {
arr.push(`${k}=${obj[k]}`)
}
return (arr.length>0?"?":"") + arr.join('&')
}
}
}
</script>
<style>
</style>
@@ -0,0 +1,133 @@
<!--
* @Descripttion: 文件导入
* @version: 1.0
* @Author: sakuya
* @Date: 2022年5月24日11:30:03
* @LastEditors:
* @LastEditTime:
-->
<template>
<slot :open="open">
<el-button type="primary" plain @click="open">导入</el-button>
</slot>
<el-dialog v-model="dialog" title="导入" :width="550" :close-on-click-modal="false" append-to-body destroy-on-close>
<el-progress v-if="loading" :text-inside="true" :stroke-width="20" :percentage="percentage" style="margin-bottom: 15px;"/>
<div v-loading="loading">
<el-upload ref="uploader"
drag
:accept="accept"
:maxSize="maxSize"
:limit="1"
:data="data"
:show-file-list="false"
:http-request="request"
:before-upload="before"
:on-progress="progress"
:on-success="success"
:on-error="error"
>
<slot name="uploader">
<el-icon class="el-icon--upload"><el-icon-upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处或 <em>点击选择文件上传</em>
</div>
</slot>
<template #tip>
<div class="el-upload__tip">
<template v-if="tip">{{tip}}</template>
<template v-else>请上传小于或等于 {{maxSize}}M {{accept}} 格式文件</template>
<p v-if="templateUrl" style="margin-top: 7px;">
<el-link :href="templateUrl" target="_blank" type="primary" :underline="false">下载导入模板</el-link>
</p>
</div>
</template>
</el-upload>
<el-form v-if="$slots.form" inline label-width="100px" label-position="left" style="margin-top: 18px;">
<slot name="form" :formData="formData"></slot>
</el-form>
</div>
</el-dialog>
</template>
<script>
export default {
emits: ['success'],
props: {
apiObj: { type: Object, default: () => {} },
data: { type: Object, default: () => {} },
accept: { type: String, default: ".xls, .xlsx" },
maxSize: { type: Number, default: 10 },
tip: { type: String, default: "" },
templateUrl: { type: String, default: "" }
},
data() {
return {
dialog: false,
loading: false,
percentage: 0,
formData: {}
}
},
mounted() {
},
methods: {
open(){
this.dialog = true
this.formData = {}
},
close(){
this.dialog = false
},
before(file){
const maxSize = file.size / 1024 / 1024 < this.maxSize;
if (!maxSize) {
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
return false;
}
this.loading = true
},
progress(e){
this.percentage = e.percent
},
success(res, file){
this.$refs.uploader.handleRemove(file)
this.$refs.uploader.clearFiles()
this.loading = false
this.percentage = 0
this.$emit('success', res, this.close)
},
error(err){
this.loading = false
this.percentage = 0
this.$notify.error({
title: '上传文件未成功',
message: err
})
},
request(param){
Object.assign(param.data, this.formData)
const data = new FormData();
data.append(param.filename, param.file);
for (const key in param.data) {
data.append(key, param.data[key]);
}
this.apiObj.post(data, {
onUploadProgress: e => {
const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
param.onProgress({percent: complete})
}
}).then(res => {
param.onSuccess(res)
}).catch(err => {
param.onError(err)
})
}
}
}
</script>
<style>
</style>
@@ -0,0 +1,283 @@
<!--
* @Descripttion: 资源文件选择器
* @version: 1.0
* @Author: sakuya
* @Date: 2021年10月11日16:01:40
* @LastEditors:
* @LastEditTime:
-->
<template>
<div class="sc-file-select">
<div class="sc-file-select__side" v-loading="menuLoading">
<div class="sc-file-select__side-menu">
<el-tree ref="group" class="menu" :data="menu" :node-key="treeProps.key" :props="treeProps" :current-node-key="menu.length>0?menu[0][treeProps.key]:''" highlight-current @node-click="groupClick">
<template #default="{ node }">
<span class="el-tree-node__label">
<el-icon class="icon"><el-icon-folder /></el-icon>{{node.label}}
</span>
</template>
</el-tree>
</div>
<div class="sc-file-select__side-msg" v-if="multiple">
已选择 <b>{{value.length}}</b> / <b>{{max}}</b>
</div>
</div>
<div class="sc-file-select__files" v-loading="listLoading">
<div class="sc-file-select__top">
<div class="upload" v-if="!hideUpload">
<el-upload class="sc-file-select__upload" action="" multiple :show-file-list="false" :accept="accept" :on-change="uploadChange" :before-upload="uploadBefore" :on-progress="uploadProcess" :on-success="uploadSuccess" :on-error="uploadError" :http-request="uploadRequest">
<el-button type="primary" icon="el-icon-upload">本地上传</el-button>
</el-upload>
<span class="tips"><el-icon><el-icon-warning /></el-icon>大小不超过{{maxSize}}MB</span>
</div>
<div class="keyword">
<el-input v-model="keyword" prefix-icon="el-icon-search" placeholder="文件名搜索" clearable @keyup.enter="search" @clear="search"></el-input>
</div>
</div>
<div class="sc-file-select__list">
<el-scrollbar ref="scrollbar">
<el-empty v-if="fileList.length==0 && data.length==0" description="无数据" :image-size="80"></el-empty>
<div v-for="(file, index) in fileList" :key="index" class="sc-file-select__item">
<div class="sc-file-select__item__file">
<div class="sc-file-select__item__upload">
<el-progress type="circle" :percentage="file.progress" :width="70"></el-progress>
</div>
<el-image :src="file.tempImg" fit="contain"></el-image>
</div>
<p>{{file.name}}</p>
</div>
<div v-for="item in data" :key="item[fileProps.key]" class="sc-file-select__item" :class="{active: value.includes(item[fileProps.url]) }" @click="select(item)">
<div class="sc-file-select__item__file">
<div class="sc-file-select__item__checkbox" v-if="multiple">
<el-icon><el-icon-check /></el-icon>
</div>
<div class="sc-file-select__item__select" v-else>
<el-icon><el-icon-check /></el-icon>
</div>
<div class="sc-file-select__item__box"></div>
<el-image v-if="_isImg(item[fileProps.url])" :src="item[fileProps.url]" fit="contain" lazy></el-image>
<div v-else class="item-file item-file-doc">
<i v-if="files[_getExt(item[fileProps.url])]" :class="files[_getExt(item[fileProps.url])].icon" :style="{color:files[_getExt(item[fileProps.url])].color}"></i>
<i v-else class="sc-icon-file-list-fill" style="color: #999;"></i>
</div>
</div>
<p :title="item[fileProps.fileName]">{{item[fileProps.fileName]}}</p>
</div>
</el-scrollbar>
</div>
<div class="sc-file-select__pagination">
<el-pagination small background layout="prev, pager, next" :total="total" :page-size="pageSize" v-model:currentPage="currentPage" @current-change="reload"></el-pagination>
</div>
<div class="sc-file-select__do">
<slot name="do"></slot>
<el-button type="primary" :disabled="value.length<=0" @click="submit"> </el-button>
</div>
</div>
</div>
</template>
<script>
import config from "@/config/fileSelect"
export default {
props: {
modelValue: null,
hideUpload: { type: Boolean, default: false },
multiple: { type: Boolean, default: false },
max: {type: Number, default: config.max},
onlyImage: { type: Boolean, default: false },
maxSize: {type: Number, default: config.maxSize},
},
data() {
return {
keyword: null,
pageSize: 20,
total: 0,
currentPage: 1,
data: [],
menu: [],
menuId: '',
value: this.multiple ? [] : '',
fileList: [],
accept: this.onlyImage ? "image/gif, image/jpeg, image/png" : "",
listLoading: false,
menuLoading: false,
treeProps: config.menuProps,
fileProps: config.fileProps,
files: config.files
}
},
watch: {
multiple(){
this.value = this.multiple ? [] : ''
this.$emit('update:modelValue', JSON.parse(JSON.stringify(this.value)));
}
},
mounted() {
this.getMenu()
this.getData()
},
methods: {
//获取分类数据
async getMenu(){
this.menuLoading = true
var res = await config.menuApiObj.get()
this.menu = res.data
this.menuLoading = false
},
//获取列表数据
async getData(){
this.listLoading = true
var reqData = {
[config.request.menuKey]: this.menuId,
[config.request.page]: this.currentPage,
[config.request.pageSize]: this.pageSize,
[config.request.keyword]: this.keyword
}
if(this.onlyImage){
reqData.type = 'image'
}
var res = await config.listApiObj.get(reqData)
var parseData = config.listParseData(res)
this.data = parseData.rows
this.total = parseData.total
this.listLoading = false
this.$refs.scrollbar.setScrollTop(0)
},
//树点击事件
groupClick(data){
this.menuId = data.id
this.currentPage = 1
this.keyword = null
this.getData()
},
//分页刷新表格
reload(){
this.getData()
},
search(){
this.currentPage = 1
this.getData()
},
select(item){
const itemUrl = item[this.fileProps.url]
if(this.multiple){
if(this.value.includes(itemUrl)){
this.value.splice(this.value.findIndex(f => f == itemUrl), 1)
}else{
this.value.push(itemUrl)
}
}else{
if(this.value.includes(itemUrl)){
this.value = ''
}else{
this.value = itemUrl
}
}
},
submit(){
const value = JSON.parse(JSON.stringify(this.value))
this.$emit('update:modelValue', value);
this.$emit('submit', value);
},
//上传处理
uploadChange(file, fileList){
file.tempImg = URL.createObjectURL(file.raw);
this.fileList = fileList
},
uploadBefore(file){
const maxSize = file.size / 1024 / 1024 < this.maxSize;
if (!maxSize) {
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
return false;
}
},
uploadRequest(param){
var apiObj = config.apiObj;
const data = new FormData();
data.append("file", param.file);
data.append([config.request.menuKey], this.menuId);
apiObj.post(data, {
onUploadProgress: e => {
param.onProgress(e)
}
}).then(res => {
param.onSuccess(res)
}).catch(err => {
param.onError(err)
})
},
uploadProcess(event, file){
file.progress = Number((event.loaded / event.total * 100).toFixed(2))
},
uploadSuccess(res, file){
this.fileList.splice(this.fileList.findIndex(f => f.uid == file.uid), 1)
var response = config.uploadParseData(res);
this.data.unshift({
[this.fileProps.key]: response.id,
[this.fileProps.fileName]: response.fileName,
[this.fileProps.url]: response.url
})
if(!this.multiple){
this.value = response.url
}
},
uploadError(err){
this.$notify.error({
title: '上传文件错误',
message: err
})
},
//内置函数
_isImg(fileUrl){
const imgExt = ['.jpg', '.jpeg', '.png', '.gif', '.bmp']
const fileExt = fileUrl.substring(fileUrl.lastIndexOf("."))
return imgExt.indexOf(fileExt) != -1
},
_getExt(fileUrl){
return fileUrl.substring(fileUrl.lastIndexOf(".") + 1)
}
}
}
</script>
<style scoped>
.sc-file-select {display: flex;}
.sc-file-select__files {flex: 1;}
.sc-file-select__list {height:400px;}
.sc-file-select__item {display: inline-block;float: left;margin:0 15px 25px 0;width:110px;cursor: pointer;}
.sc-file-select__item__file {width:110px;height:110px;position: relative;}
.sc-file-select__item__file .el-image {width:110px;height:110px;}
.sc-file-select__item__box {position: absolute;top:0;right:0;bottom:0;left:0;border: 2px solid var(--el-color-success);z-index: 1;display: none;}
.sc-file-select__item__box::before {content: '';position: absolute;top:0;right:0;bottom:0;left:0;background: var(--el-color-success);opacity: 0.2;display: none;}
.sc-file-select__item:hover .sc-file-select__item__box {display: block;}
.sc-file-select__item.active .sc-file-select__item__box {display: block;}
.sc-file-select__item.active .sc-file-select__item__box::before {display: block;}
.sc-file-select__item p {margin-top: 10px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;-webkit-text-overflow:ellipsis;text-align: center;}
.sc-file-select__item__checkbox {position: absolute;width: 20px;height: 20px;top:7px;right:7px;z-index: 2;background: rgba(0,0,0,0.2);border: 1px solid #fff;display: flex;flex-direction: column;align-items: center;justify-content: center;}
.sc-file-select__item__checkbox i {font-size: 14px;color: #fff;font-weight: bold;display: none;}
.sc-file-select__item__select {position: absolute;width: 20px;height: 20px;top:0px;right:0px;z-index: 2;background: var(--el-color-success);display: none;flex-direction: column;align-items: center;justify-content: center;}
.sc-file-select__item__select i {font-size: 14px;color: #fff;font-weight: bold;}
.sc-file-select__item.active .sc-file-select__item__checkbox {background: var(--el-color-success);}
.sc-file-select__item.active .sc-file-select__item__checkbox i {display: block;}
.sc-file-select__item.active .sc-file-select__item__select {display: flex;}
.sc-file-select__item__file .item-file {width:110px;height:110px;display: flex;flex-direction: column;align-items: center;justify-content: center;}
.sc-file-select__item__file .item-file i {font-size: 40px;}
.sc-file-select__item__file .item-file.item-file-doc {color: #409eff;}
.sc-file-select__item__upload {position: absolute;top:0;right:0;bottom:0;left:0;z-index: 1;background: rgba(255,255,255,0.7);display: flex;flex-direction: column;align-items: center;justify-content: center;}
.sc-file-select__side {width: 200px;margin-right: 15px;border-right: 1px solid rgba(128,128,128,0.2);display: flex;flex-flow: column;}
.sc-file-select__side-menu {flex: 1;}
.sc-file-select__side-msg {height:32px;line-height: 32px;}
.sc-file-select__top {margin-bottom: 15px;display: flex;justify-content: space-between;}
.sc-file-select__upload {display: inline-block;}
.sc-file-select__top .tips {font-size: 12px;margin-left: 10px;color: #999;}
.sc-file-select__top .tips i {font-size: 14px;margin-right: 5px;position: relative;bottom: -0.125em;}
.sc-file-select__pagination {margin:15px 0;}
.sc-file-select__do {text-align: right;}
</style>
@@ -0,0 +1,310 @@
<!--
* @Descripttion: 过滤器V2
* @version: 2.5
* @Author: sakuya
* @Date: 2021年7月30日14:48:41
* @LastEditors: sakuya
* @LastEditTime: 2022年5月13日21:15:44
-->
<template>
<div class="sc-filterBar">
<slot :filterLength="filterObjLength" :openFilter="openFilter">
<el-badge :value="filterObjLength" type="danger" :hidden="filterObjLength<=0">
<el-button icon="el-icon-filter" @click="openFilter"></el-button>
</el-badge>
</slot>
<el-drawer title="过滤器" v-model="drawer" :size="650" append-to-body>
<el-container v-loading="saveLoading">
<el-main style="padding:0">
<el-tabs class="root">
<el-tab-pane lazy>
<template #label>
<div class="tabs-label">过滤项</div>
</template>
<el-scrollbar>
<div class="sc-filter-main">
<h2>设置过滤条件</h2>
<div v-if="filter.length<=0" class="nodata">
没有默认过滤条件请点击增加过滤项
</div>
<table v-else>
<colgroup>
<col width="50">
<col width="140">
<col v-if="showOperator" width="120">
<col>
<col width="40">
</colgroup>
<tr v-for="(item,index) in filter" :key="index">
<td>
<el-tag>{{index+1}}</el-tag>
</td>
<td>
<py-select v-model="item.field" :options="fields" placeholder="过滤字段" filterable @change="fieldChange(item)">
</py-select>
</td>
<td v-if="showOperator">
<el-select v-model="item.operator" placeholder="运算符">
<el-option v-for="ope in item.field.operators || operator" :key="ope.value" :label="ope.label" :value="ope.value"></el-option>
</el-select>
</td>
<td>
<el-input v-if="!item.field.type" v-model="item.value" placeholder="请选择过滤字段" disabled></el-input>
<!-- 输入框 -->
<el-input v-if="item.field.type=='text'" v-model="item.value" :placeholder="item.field.placeholder||'请输入'"></el-input>
<!-- 下拉框 -->
<el-select v-if="item.field.type=='select'" v-model="item.value" :placeholder="item.field.placeholder||'请选择'" filterable :multiple="item.field.extend.multiple" :loading="item.selectLoading" @visible-change="visibleChange($event, item)" :remote="item.field.extend.remote" :remote-method="(query)=>{remoteMethod(query, item)}">
<el-option v-for="field in item.field.extend.data" :key="field.value" :label="field.label" :value="field.value"></el-option>
</el-select>
<!-- 日期 -->
<el-date-picker v-if="item.field.type=='date'" v-model="item.value" type="date" value-format="YYYY-MM-DD" :placeholder="item.field.placeholder||'请选择日期'" style="width: 100%;"></el-date-picker>
<!-- 日期范围 -->
<el-date-picker v-if="item.field.type=='daterange'" v-model="item.value" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" style="width: 100%;"></el-date-picker>
<!-- 日期时间 -->
<el-date-picker v-if="item.field.type=='datetime'" v-model="item.value" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" :placeholder="item.field.placeholder||'请选择日期'" style="width: 100%;"></el-date-picker>
<!-- 日期时间范围 -->
<el-date-picker v-if="item.field.type=='datetimerange'" v-model="item.value" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" start-placeholder="开始日期" end-placeholder="结束日期" style="width: 100%;"></el-date-picker>
<!-- 开关 -->
<el-switch v-if="item.field.type=='switch'" v-model="item.value" active-value="1" inactive-value="0"></el-switch>
<!-- 标签 -->
<el-select v-if="item.field.type=='tags'" v-model="item.value" multiple filterable allow-create default-first-option no-data-text="输入关键词后按回车确认" :placeholder="item.field.placeholder||'请输入'"></el-select>
</td>
<td>
<el-icon class="del" @click="delFilter(index)"><el-icon-delete /></el-icon>
</td>
</tr>
</table>
<el-button type="primary" text icon="el-icon-plus" @click="addFilter">增加过滤项</el-button>
</div>
</el-scrollbar>
</el-tab-pane>
<el-tab-pane lazy>
<template #label>
<div class="tabs-label">常用</div>
</template>
<el-scrollbar>
<my ref="my" :data="myFilter" :filterName="filterName" @selectMyfilter="selectMyfilter"></my>
</el-scrollbar>
</el-tab-pane>
</el-tabs>
</el-main>
<el-footer>
<el-button type="primary" @click="ok" :disabled="filter.length<=0">立即过滤</el-button>
<el-button type="primary" plain @click="saveMy" :disabled="filter.length<=0">另存为常用</el-button>
<el-button @click="clear">清空过滤</el-button>
</el-footer>
</el-container>
</el-drawer>
</div>
</template>
<script>
import config from "@/config/filterBar"
import pySelect from './pySelect'
import my from './my'
export default {
name: 'filterBar',
components: {
pySelect,
my
},
props: {
filterName: { type: String, default: "" },
showOperator: { type: Boolean, default: true },
options: { type: Object, default: () => {} }
},
data() {
return {
drawer: false,
operator: config.operator,
fields: this.options,
filter: [],
myFilter: [],
filterObjLength: 0,
saveLoading: false
}
},
computed: {
filterObj(){
const obj = {}
this.filter.forEach((item) => {
obj[item.field.value] = this.showOperator ? `${item.value}${config.separator}${item.operator}` : `${item.value}`
})
return obj
}
},
mounted(){
//默认显示的过滤项
this.fields.forEach((item) => {
if(item.selected){
this.filter.push({
field: item,
operator: item.operator || 'include',
value: ''
})
}
})
},
methods: {
//打开过滤器
openFilter(){
this.drawer = true
},
//增加过滤项
addFilter(){
if(this.fields.length<=0){
this.$message.warning('无过滤项');
return false
}
const filterNum = this.fields[this.filter.length] || this.fields[0]
this.filter.push({
field: filterNum,
operator: filterNum.operator || 'include',
value: ''
})
},
//删除过滤项
delFilter(index){
this.filter.splice(index, 1)
},
//过滤项字段变更事件
fieldChange(tr){
let oldType = tr.field.type
tr.field.type = ''
this.$nextTick(() => {
tr.field.type = oldType
})
tr.operator = tr.field.operator || 'include'
tr.value = ''
},
//下拉框显示事件处理异步
async visibleChange(isopen, item){
if(isopen && item.field.extend.request && !item.field.extend.remote){
item.selectLoading = true;
try {
var data = await item.field.extend.request()
}catch (error) {
console.log(error);
}
item.field.extend.data = data;
item.selectLoading = false;
}
},
//下拉框显示事件处理异步搜索
async remoteMethod(query, item){
if(query !== ''){
item.selectLoading = true;
try {
var data = await item.field.extend.request(query);
}catch (error) {
console.log(error);
}
item.field.extend.data = data;
item.selectLoading = false;
}else{
item.field.extend.data = [];
}
},
//选择常用过滤
selectMyfilter(item){
//常用过滤回显当前过滤项
this.filter = []
this.fields.forEach((field) => {
var filterValue = item.filterObj[field.value]
if(filterValue){
var operator = filterValue.split("|")[1]
var value = filterValue.split("|")[0]
if(field.type=='select' && field.extend.multiple){
value = value.split(",")
}else if(field.type=='daterange'){
value = value.split(",")
}
this.filter.push({
field: field,
operator: operator,
value: value
})
}
})
this.filterObjLength = Object.keys(item.filterObj).length
this.$emit('filterChange',item.filterObj)
this.drawer = false
},
//立即过滤
ok(){
this.filterObjLength = this.filter.length
this.$emit('filterChange',this.filterObj)
this.drawer = false
},
//保存常用
saveMy(){
this.$prompt('常用过滤名称', '另存为常用', {
inputPlaceholder: '请输入识别度较高的常用过滤名称',
inputPattern: /\S/,
inputErrorMessage: '名称不能为空'
})
.then(async ({ value }) => {
this.saveLoading = true
const saveObj = {
title: value,
filterObj: this.filterObj
}
try {
var save = await config.saveMy(this.filterName, saveObj)
}catch (error) {
this.saveLoading = false
console.log(error);
return false
}
if(!save){
return false
}
this.myFilter.push(saveObj)
this.$message.success(`${this.filterName} 保存常用成功`)
this.saveLoading = false
})
.catch(() => {
//
})
},
//清空过滤
clear(){
this.filter = []
this.filterObjLength = 0
this.$emit('filterChange',this.filterObj)
}
}
}
</script>
<style scoped>
.tabs-label {padding:0 20px;}
.nodata {height:46px;line-height: 46px;margin:15px 0;border: 1px dashed #e6e6e6;color: #999;text-align: center;border-radius: 3px;}
.sc-filter-main {padding:20px;border-bottom: 1px solid #e6e6e6;background: #fff;}
.sc-filter-main h2 {font-size: 12px;color: #999;font-weight: normal;}
.sc-filter-main table {width: 100%;margin: 15px 0;}
.sc-filter-main table tr {}
.sc-filter-main table td {padding:5px 10px 5px 0;}
.sc-filter-main table td:deep(.el-input .el-input__inner) {vertical-align: top;}
.sc-filter-main table td .el-select {display: block;}
.sc-filter-main table td .el-date-editor.el-input {display: block;width: 100%;}
.sc-filter-main table td .del {background: #fff;color: #999;width: 32px;height: 32px;line-height: 32px;text-align: center;border-radius:50%;font-size: 12px;cursor: pointer;}
.sc-filter-main table td .del:hover {background: #F56C6C;color: #fff;}
.root {display: flex;height: 100%;flex-direction: column}
.root:deep(.el-tabs__header) {margin: 0;}
.root:deep(.el-tabs__content) {flex: 1;background: #f6f8f9;}
.root:deep(.el-tabs__content) .el-tab-pane{overflow: auto;height:100%;}
.dark .root:deep(.el-tabs__content) {background: var(--el-bg-color-overlay);}
.dark .sc-filter-main {background: var(--el-bg-color);border-color:var(--el-border-color-light);}
.dark .sc-filter-main table td .del {background: none;}
.dark .sc-filter-main table td .del:hover {background: #F56C6C;}
.dark .nodata {border-color:var(--el-border-color-light);}
</style>
@@ -0,0 +1,112 @@
<!--
* @Descripttion: 过滤器V2 常用组件
* @version: 2.0
* @Author: sakuya
* @Date: 2021年7月31日16:49:56
* @LastEditors:
* @LastEditTime:
-->
<template>
<div class="sc-filter-my">
<div v-if="loading" class="sc-filter-my-loading">
<el-skeleton :rows="2" animated />
</div>
<template v-else>
<el-empty v-if="myFilter.length<=0" :image-size="100">
<template #description>
<h2>没有常用的过滤</h2>
<p style="margin-top: 10px;max-width: 300px;">常用过滤可以将多个过滤条件保存为一个集合方便下次进行相同条件的过滤</p>
</template>
</el-empty>
<ul v-else class="sc-filter-my-list">
<h2>我的常用过滤</h2>
<li v-for="(item, index) in myFilter" :key="index" @click="selectMyfilter(item)">
<label>{{item.title}}</label>
<el-popconfirm title="确认删除此常用过滤吗?" @confirm="closeMyfilter(item, index)">
<template #reference>
<el-icon class="del" @click.stop="()=>{}"><el-icon-delete /></el-icon>
</template>
</el-popconfirm>
</li>
</ul>
</template>
</div>
</template>
<script>
import config from "@/config/filterBar";
export default {
props: {
filterName: { type: String, default: "" },
data: { type: Object, default: () => {} }
},
data() {
return {
loading: false,
myFilter: []
}
},
watch:{
data: {
handler(){
this.myFilter = this.data
},
deep: true
}
},
mounted() {
this.myFilter = this.data
this.getMyfilter()
},
methods: {
//选择常用过滤
selectMyfilter(item){
this.$emit('selectMyfilter', item)
},
//删除常用过滤
async closeMyfilter(item, index){
try {
var del = await config.delMy(this.filterName)
}catch (error) {
return false
}
if(!del){
return false
}
this.myFilter.splice(index, 1)
this.$message.success('删除常用成功')
},
//远程获取我的常用
async getMyfilter(){
this.loading = true
try {
this.myFilter = await config.getMy(this.filterName)
}catch (error) {
return false
}
this.loading = false
}
}
}
</script>
<style scoped>
.sc-filter-my {}
.sc-filter-my-loading {padding:15px;}
.sc-filter-my-list {list-style-type: none;background: #fff;border-bottom: 1px solid #e6e6e6;}
.sc-filter-my-list h2 {font-size: 12px;color: #999;font-weight: normal;padding:20px;}
.sc-filter-my-list li {padding:12px 20px;cursor: pointer;position: relative;color: #3c4a54;padding-right:80px;}
.sc-filter-my-list li:hover {background: #ecf5ff;color: #409EFF;}
.sc-filter-my-list li label {cursor: pointer;font-size: 14px;line-height: 1.8;}
.sc-filter-my-list li label span {color: #999;margin-right: 10px;}
.sc-filter-my-list li .del {position: absolute;right:20px;top:8px;border-radius:50%;width: 32px;height: 32px;display: flex;align-items: center;justify-content: center;color: #999;}
.sc-filter-my-list li .del:hover {background: #F56C6C;color: #fff;}
[data-theme='dark'] .sc-filter-my .el-empty h2 {color: #fff;}
[data-theme='dark'] .sc-filter-my-list {background: none;border-color:var(--el-border-color-base);}
[data-theme='dark'] .sc-filter-my-list li {color: #d0d0d0;}
[data-theme='dark'] .sc-filter-my-list li:hover {background: var(--el-color-white);}
</style>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,51 @@
<!--
* @Descripttion: 二次封装el-select 支持拼音
* @version: 1.0
* @Author: sakuya
* @Date: 2021年7月31日22:26:56
* @LastEditors:
* @LastEditTime:
-->
<template>
<el-select v-bind="$attrs" :filter-method="filterMethod" @visible-change="visibleChange">
<el-option v-for="field in optionsList" :key="field.value" :label="field.label" :value="field"></el-option>
</el-select>
</template>
<script>
import pinyin from 'pinyin-match'
export default {
props: {
options: { type: Array, default: () => [] }
},
data() {
return {
optionsList: [],
optionsList_: []
}
},
mounted() {
this.optionsList = this.options
this.optionsList_ = [...this.options]
},
methods: {
filterMethod(keyword){
if(keyword){
this.optionsList = this.optionsList_
this.optionsList = this.optionsList.filter((item) =>
pinyin.match(item.label, keyword)
);
}else{
this.optionsList = this.optionsList_
}
},
visibleChange(isopen){
if(isopen){
this.optionsList = this.optionsList_
}
}
}
}
</script>
@@ -0,0 +1,295 @@
<!--
* @Descripttion: 动态表单渲染器
* @version: 1.0
* @Author: sakuya
* @Date: 2021年9月22日09:26:25
* @LastEditors:
* @LastEditTime:
-->
<template>
<el-skeleton v-if="renderLoading || Object.keys(form).length==0" animated />
<el-form v-else ref="form" :model="form" :label-width="config.labelWidth" :label-position="config.labelPosition" v-loading="loading" element-loading-text="Loading...">
<el-row :gutter="15">
<template v-for="(item, index) in config.formItems" :key="index">
<el-col :span="item.span || 24" v-if="!hideHandle(item)">
<sc-title v-if="item.type=='title'" :title="item.label"></sc-title>
<el-form-item v-else :prop="item.name" :rules="rulesHandle(item)">
<template #label>
{{item.label}}
<el-tooltip v-if="item.tips" :content="item.tips">
<el-icon><el-icon-question-filled /></el-icon>
</el-tooltip>
</template>
<!-- input -->
<template v-if="item.type=='input' || item.type=='string'" >
<el-input v-model="form[item.name]" :placeholder="item.options?.placeholder" clearable :maxlength="item.options?.maxlength" show-word-limit :disabled="item.disabled" />
</template>
<!-- input -->
<template v-else-if="item.type=='textarea'" >
<el-input type="textarea" v-model="form[item.name]" :placeholder="item.options?.placeholder" clearable :maxlength="item.options.maxlength" show-word-limit :disabled="item.disabled" />
</template>
<!-- checkbox -->
<template v-else-if="item.type=='checkbox'" >
<template v-if="item.name" >
<el-checkbox v-model="form[item.name][_item.name]" :label="_item.label" v-for="(_item, _index) in item.options.items" :key="_index" :disabled="item.disabled"></el-checkbox>
</template>
<template v-else >
<el-checkbox v-model="form[_item.name]" :label="_item.label" v-for="(_item, _index) in item.options.items" :key="_index" :disabled="item.disabled"></el-checkbox>
</template>
</template>
<!-- checkboxGroup -->
<template v-else-if="item.type=='checkboxGroup'" >
<el-checkbox-group v-model="form[item.name]" :disabled="item.disabled">
<el-checkbox v-for="_item in item.options.items" :key="_item.value" :label="_item.value">{{_item.label}}</el-checkbox>
</el-checkbox-group>
</template>
<!-- upload -->
<template v-else-if="item.type=='upload' || item.type == 'image'" >
<el-form-item :prop="item.name">
<sc-upload v-model="form[item.name]" :title="item.label"></sc-upload>
</el-form-item>
</template>
<!-- switch -->
<template v-else-if="item.type=='switch' || item.type=='boolean'" >
<el-switch v-model="form[item.name]" :active-value="1" :inactive-value="0" :disabled="item.disabled" />
</template>
<!-- select -->
<template v-else-if="item.type=='select'" >
<el-select v-model="form[item.name]" :multiple="item.options.multiple" :placeholder="item.options?.placeholder" clearable filterable style="width: 100%;" :disabled="item.disabled">
<el-option v-for="option in item.options.items" :key="option.value" :label="option.label" :value="option.value"></el-option>
</el-select>
</template>
<template v-else-if="item.type=='scSelect'" >
<sc-select v-model="form[item.name]" :dic="item.options?.dic" :apiObj="item.options?.apiObj" :props="item.options?.props" style="min-width: 240px;" />
</template>
<template v-else-if="item.type=='scSelectTree'" >
<sc-select-tree v-model="form[item.name]" :dic="item.options?.dic" :apiObj="item.options?.apiObj" :props="item.options?.props" style="min-width: 240px;" />
</template>
<!-- cascader -->
<template v-else-if="item.type=='cascader'" >
<el-cascader v-model="form[item.name]" :options="item.options.items" clearable></el-cascader>
</template>
<!-- date -->
<template v-else-if="item.type=='date'" >
<el-date-picker v-model="form[item.name]" :type="item.options.type" :shortcuts="item.options.shortcuts" :default-time="item.options.defaultTime" :value-format="item.options.valueFormat" :placeholder="item.options?.placeholder || '请选择'"></el-date-picker>
</template>
<!-- number -->
<template v-else-if="item.type=='number'" >
<el-input-number v-model="form[item.name]" controls-position="right" :placeholder="item.options?.placeholder" />
</template>
<!-- radio -->
<template v-else-if="item.type=='radio'" >
<el-radio-group v-model="form[item.name]">
<el-radio v-for="_item in item.options.items" :key="_item.value" :label="_item.value">{{_item.label}}</el-radio>
</el-radio-group>
</template>
<!-- color -->
<template v-else-if="item.type=='color'" >
<el-color-picker v-model="form[item.name]" />
</template>
<!-- rate -->
<template v-else-if="item.type=='rate'" >
<el-rate style="margin-top: 6px;" v-model="form[item.name]"></el-rate>
</template>
<!-- slider -->
<template v-else-if="item.type=='slider'" >
<el-slider v-model="form[item.name]" :marks="item.options.marks"></el-slider>
</template>
<!-- tableselect -->
<template v-else-if="item.type=='tableselect'" >
<tableselect-render v-model="form[item.name]" :item="item"></tableselect-render>
</template>
<!-- editor -->
<template v-else-if="item.type=='editor'" >
<sc-editor v-model="form[item.name]" placeholder="请输入" :height="400"></sc-editor>
</template>
<!-- noComponent -->
<template v-else>
<el-tag type="danger">[{{item.type}}] Component not found</el-tag>
</template>
<div v-if="item.message" class="el-form-item-msg">{{item.message}}</div>
</el-form-item>
</el-col>
</template>
<el-col :span="24">
<el-form-item>
<slot>
<el-button type="primary" @click="submit">提交</el-button>
</slot>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
import http from "@/utils/request"
import { defineAsyncComponent } from 'vue'
const tableselectRender = defineAsyncComponent(() => import('./items/tableselect'))
const scEditor = defineAsyncComponent(() => import('@/components/scEditor'))
export default {
props: {
modelValue: { type: Object, default: () => {} },
config: { type: Object, default: () => {} },
loading: { type: Boolean, default: false },
apiObj: { type: Object, default: () => {} },
},
components: {
tableselectRender,
scEditor
},
data() {
return {
form: {},
renderLoading: false
}
},
watch:{
modelValue(){
if(this.hasConfig){
this.deepMerge(this.form, this.modelValue)
}
},
config(){
this.render()
},
form:{
handler(val){
this.$emit("update:modelValue", val)
},
deep: true
}
},
computed: {
hasConfig(){
return Object.keys(this.config).length>0
},
hasValue(){
return Object.keys(this.modelValue).length>0
}
},
created() {
},
mounted() {
if(this.hasConfig){
this.render()
}
},
methods: {
//构建form对象
render() {
this.config.formItems.forEach((item) => {
if(item.type == 'checkbox'){
if(item.name){
const value = {}
item.options.items.forEach((option) => {
value[option.name] = option.value
})
this.form[item.name] = value
}else{
item.options.items.forEach((option) => {
this.form[option.name] = option.value
})
}
}else if(item.type == 'upload'){
if(item.name){
const value = {}
item.options.items.forEach((option) => {
value[option.name] = option.value
})
this.form[item.name] = value
}else{
item.options.items.forEach((option) => {
this.form[option.name] = option.value
})
}
}else{
this.form[item.name] = item.value
}
})
if(this.hasValue){
this.form = this.deepMerge(this.form, this.modelValue)
}
this.getData()
},
//处理远程选项数据
getData() {
this.renderLoading = true
var remoteData = []
this.config.formItems.forEach((item) => {
if(item.options && item.options.remote){
var req = http.get(item.options.remote.api, item.options.remote.data).then(res=>{
item.options.items = res.data
})
remoteData.push(req)
}
})
Promise.all(remoteData).then(()=>{
this.renderLoading = false
})
},
//合并深结构对象
deepMerge(obj1, obj2) {
let key;
for (key in obj2) {
obj1[key] = obj1[key] && obj1[key].toString() === "[object Object]" && (obj2[key] && obj2[key].toString() === "[object Object]") ? this.deepMerge(obj1[key], obj2[key]) : (obj1[key] = obj2[key])
}
return obj1
//return JSON.parse(JSON.stringify(obj1))
},
//处理动态隐藏
hideHandle(item){
if(item.hideHandle){
const exp = eval(item.hideHandle.replace(/\$/g,"this.form"))
return exp
}
return false
},
//处理动态必填
rulesHandle(item){
if(item.requiredHandle){
const exp = eval(item.requiredHandle.replace(/\$/g,"this.form"))
var requiredRule = item.rules.find(t => 'required' in t)
requiredRule.required = exp
}
return item.rules
},
//数据验证
validate(valid, obj){
return this.$refs.form.validate(valid, obj)
},
scrollToField(prop){
return this.$refs.form.scrollToField(prop)
},
resetFields(){
return this.$refs.form.resetFields()
},
//提交
submit(){
if(!this.apiObj){
this.$emit("submit", this.form)
}else{
this.$refs.form.validate().then(async () => {
let res = await this.apiObj.post(this.form)
if(res.code == 1){
this.$emit('update:modelValue', this.form)
this.$emit('onSuccess', res)
}else{
this.$message.error(res.message)
}
}).catch(error => {
this.$message.error('请认真填写信息!')
})
}
}
}
}
</script>
<style>
</style>
@@ -0,0 +1,37 @@
<template>
<sc-table-select v-model="value" :apiObj="apiObj" :table-width="600" :multiple="item.options.multiple" :props="item.options.props" style="width: 100%;">
<el-table-column v-for="(_item, _index) in item.options.column" :key="_index" :prop="_item.prop" :label="_item.label" :width="_item.width"></el-table-column>
</sc-table-select>
</template>
<script>
export default {
name: 'uploadRender',
props: {
modelValue: [String, Number, Boolean, Date, Object, Array],
item: { type: Object, default: () => {} }
},
data() {
return {
value: this.modelValue,
apiObj: this.getApiObj()
}
},
watch:{
value(val){
this.$emit("update:modelValue", val)
}
},
mounted() {
},
methods: {
getApiObj(){
return eval(`this.`+this.item.options.apiObj)
}
}
}
</script>
<style>
</style>
@@ -0,0 +1,123 @@
<!--
* @Descripttion: 表单表格
* @version: 1.3
* @Author: sakuya
* @Date: 2023年2月9日12:32:26
* @LastEditors: sakuya
* @LastEditTime: 2023年2月17日10:41:47
-->
<template>
<div class="sc-form-table" ref="scFormTable">
<el-table :data="data" ref="table" border stripe>
<el-table-column type="index" width="50" fixed="left">
<template #header>
<el-button v-if="!hideAdd" type="primary" icon="el-icon-plus" circle @click="rowAdd"></el-button>
</template>
<template #default="scope">
<div :class="['sc-form-table-handle', {'sc-form-table-handle-delete':!hideDelete}]">
<span>{{scope.$index + 1}}</span>
<el-button v-if="!hideDelete" type="danger" icon="el-icon-delete" plain circle @click="rowDel(scope.row, scope.$index)"></el-button>
</div>
</template>
</el-table-column>
<el-table-column label="" width="50" v-if="dragSort">
<template #default>
<div class="move" style="cursor: move;"><el-icon-d-caret style="width: 1em; height: 1em;"/></div>
</template>
</el-table-column>
<slot></slot>
<template #empty>
{{placeholder}}
</template>
</el-table>
</div>
</template>
<script>
import Sortable from 'sortablejs'
export default {
props: {
modelValue: { type: Array, default: () => [] },
addTemplate: { type: Object, default: () => {} },
placeholder: { type: String, default: "暂无数据" },
dragSort: { type: Boolean, default: false },
hideAdd: { type: Boolean, default: false },
hideDelete: { type: Boolean, default: false }
},
data(){
return {
data: []
}
},
mounted(){
this.data = this.modelValue
if(this.dragSort){
this.rowDrop()
}
},
watch:{
modelValue(){
this.data = this.modelValue
},
data: {
handler(){
this.$emit('update:modelValue', this.data);
},
deep: true
}
},
methods: {
rowDrop(){
const _this = this
const tbody = this.$refs.table.$el.querySelector('.el-table__body-wrapper tbody')
Sortable.create(tbody, {
handle: ".move",
animation: 300,
ghostClass: "ghost",
onEnd({ newIndex, oldIndex }) {
_this.data.splice(newIndex, 0, _this.data.splice(oldIndex, 1)[0])
const newArray = _this.data.slice(0)
const tmpHeight = _this.$refs.scFormTable.offsetHeight
_this.$refs.scFormTable.style.setProperty('height', tmpHeight + 'px')
_this.data = []
_this.$nextTick(() => {
_this.data = newArray
_this.$nextTick(() => {
_this.$refs.scFormTable.style.removeProperty('height')
})
})
}
})
},
rowAdd(){
const temp = JSON.parse(JSON.stringify(this.addTemplate))
this.data.push(temp)
},
rowDel(row, index){
this.data.splice(index, 1)
},
//插入行
pushRow(row){
const temp = row || JSON.parse(JSON.stringify(this.addTemplate))
this.data.push(temp)
},
//根据index删除
deleteRow(index){
this.data.splice(index, 1)
}
}
}
</script>
<style scoped>
.sc-form-table {width: 100%;}
.sc-form-table .sc-form-table-handle {text-align: center;}
.sc-form-table .sc-form-table-handle span {display: inline-block;}
.sc-form-table .sc-form-table-handle button {display: none;}
.sc-form-table .hover-row .sc-form-table-handle-delete span {display: none;}
.sc-form-table .hover-row .sc-form-table-handle-delete button {display: inline-block;}
.sc-form-table .move {text-align: center;font-size: 14px;margin-top: 3px;}
</style>
@@ -0,0 +1,128 @@
<!--
* @Descripttion: 图标选择器组件
* @version: 2.0
* @Author: sakuya
* @Date: 2021年7月27日10:02:46
* @LastEditors: sakuya
* @LastEditTime: 2022年6月6日13:48:49
-->
<template>
<div class="sc-icon-select">
<div class="sc-icon-select__wrapper" :class="{'hasValue':value}" @click="open">
<el-input :prefix-icon="value||'el-icon-plus'" v-model="value" :disabled="disabled" readonly></el-input>
</div>
<el-dialog title="图标选择器" v-model="dialogVisible" :width="760" destroy-on-close append-to-body>
<div class="sc-icon-select__dialog" style="margin: -20px 0 -10px 0;">
<el-form :rules="{}">
<el-form-item prop="searchText">
<el-input class="sc-icon-select__search-input" prefix-icon="el-icon-search" v-model="searchText" placeholder="搜索" clearable/>
</el-form-item>
</el-form>
<el-tabs>
<el-tab-pane v-for="item in data" :key="item.name" lazy>
<template #label>
{{item.name}} <el-tag type="info">{{item.icons.length}}</el-tag>
</template>
<div class="sc-icon-select__list">
<el-scrollbar>
<ul @click="selectIcon">
<el-empty v-if="item.icons.length==0" :image-size="100" description="未查询到相关图标" />
<li v-for="icon in item.icons" :key="icon">
<span :data-icon="icon"></span>
<el-icon><component :is="icon" /></el-icon>
</li>
</ul>
</el-scrollbar>
</div>
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<el-button @click="clear" text>清除</el-button>
<el-button @click="dialogVisible=false">取消</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import config from "@/config/iconSelect"
export default {
props: {
modelValue: { type: String, default: "" },
disabled: { type: Boolean, default: false },
},
data() {
return {
value: "",
dialogVisible: false,
data: [],
searchText: ""
}
},
watch:{
modelValue(val){
this.value = val
},
value(val){
this.$emit('update:modelValue', val)
},
searchText(val){
this.search(val)
}
},
mounted() {
this.value = this.modelValue
this.data.push(...config.icons)
},
methods: {
open(){
if(this.disabled){
return false
}
this.dialogVisible = true
},
selectIcon(e){
if(e.target.tagName != 'SPAN'){
return false
}
this.value = e.target.dataset.icon
this.dialogVisible = false
},
clear(){
this.value = ""
this.dialogVisible = false
},
search(text){
if(text){
const filterData = JSON.parse(JSON.stringify(config.icons))
filterData.forEach(t => {
t.icons = t.icons.filter(n => n.includes(text))
})
this.data = filterData
}else{
this.data = JSON.parse(JSON.stringify(config.icons))
}
}
}
}
</script>
<style scoped>
.sc-icon-select {display: inline-flex;}
.sc-icon-select__wrapper {cursor: pointer;display: inline-flex;}
.sc-icon-select__wrapper:deep(.el-input__wrapper).is-focus {box-shadow: 0 0 0 1px var(--el-input-hover-border-color) inset;}
.sc-icon-select__wrapper:deep(.el-input__inner) {flex-grow:0;width: 0;}
.sc-icon-select__wrapper:deep(.el-input__icon) {margin: 0;font-size: 16px;}
.sc-icon-select__wrapper.hasValue:deep(.el-input__icon) {color: var(--el-text-color-regular);}
.sc-icon-select__list {height:270px;overflow: auto;}
.sc-icon-select__list ul {}
.sc-icon-select__list li {display: inline-block;width:80px;height:80px;margin:5px;vertical-align: top;transition: all 0.1s;border-radius: 4px;position: relative;}
.sc-icon-select__list li span {position: absolute;top:0;left:0;right:0;bottom:0;z-index: 1;cursor: pointer;}
.sc-icon-select__list li i {display: inline-block;width: 100%;height:100%;font-size: 26px;color: #6d7882;display: flex;justify-content: center;align-items: center;border-radius: 4px;}
.sc-icon-select__list li:hover {box-shadow: 0 0 1px 4px var(--el-color-primary);background: var(--el-color-primary-light-9);}
.sc-icon-select__list li:hover i {color: var(--el-color-primary);}
</style>
@@ -0,0 +1,78 @@
<template>
<div>
<el-input v-bind="$attrs" v-model="defaultValue" placeholder="请输入关键字" style="width: 100%;" readonly @foucs="showMap" @click="showMap" />
<el-dialog title="地图" v-model="visible" width="800px">
<template #default="scope">
<b-map ref="map" width="100%" height="500px" ak="Mnx3XVAmyovK9wohstKt94nRfDpTy8qd" :center="point.lng ? point : '南昌'" :zoom="zoom" :minZoom="3" :mapType="'BMAP_NORMAL_MAP'" :enableDragging="true" :enableScrollWheelZoom="true" @dblclick="mapClick">
<BCityList :offset="{ x: 20, y: 20 }" />
<b-zoom :offset="{ x: 22, y: 40 }" />
<template v-if="point.lng != ''">
<BMarker :position="point"></BMarker>
<BCircle strokeStyle="solid" strokeColor="#0099ff" :strokeOpacity="0.8" fillColor="#0099ff" :fillOpacity="0.5" :center="point" />
</template>
</b-map>
</template>
</el-dialog>
</div>
</template>
<script>
import { BMap, BZoom, BCityList, BMarker, BCircle } from 'vue3-baidu-map-gl'
export default {
props: {
modelValue: {
type: String,
default: ''
}
},
data() {
return {
visible: false,
point: {lng: '', lat: ''},
zoom: 12,
defaultValue: ''
}
},
mounted() {
this.defaultValue = this.modelValue
},
watch: {
modelValue:{
handler(val) {
this.defaultValue = val
},
deep: true,
immediate: true
}
},
components: {
BMap,
BZoom,
BCityList,
BMarker,
BCircle
},
methods: {
showMap(){
this.visible = true
if(this.defaultValue) {
let arr = this.defaultValue.split(',')
this.point = {
lng: arr[1],
lat: arr[0]
}
}
this.$nextTick(() => {
this.zoom = 20
this.$refs.map.resetCenter()
})
},
mapClick(e) {
this.$emit('update:modelValue', e.latlng.lat+','+e.latlng.lng)
this.visible = false
}
}
}
</script>
@@ -0,0 +1,47 @@
<!--
* @Descripttion: 状态指示器
* @version: 1.0
* @Author: sakuya
* @Date: 2021年11月11日09:30:12
* @LastEditors:
* @LastEditTime:
-->
<template>
<span class="sc-state" :class="[{'sc-status-processing':pulse}, 'sc-state-bg--'+type]"></span>
</template>
<script>
export default {
props: {
type: { type: String, default: "primary" },
pulse: { type: Boolean, default: false }
}
}
</script>
<style scoped>
.sc-state {display: inline-block;background: #000;width: 8px;height: 8px;border-radius: 50%;vertical-align: middle;}
.sc-status-processing {position: relative;}
.sc-status-processing:after {position: absolute;top:0px;left:0px;width: 100%;height: 100%;border-radius: 50%;background: inherit;content: '';animation: warn 1.2s ease-in-out infinite;}
.sc-state-bg--primary {background: var(--el-color-primary);}
.sc-state-bg--success {background: var(--el-color-success);}
.sc-state-bg--warning {background: var(--el-color-warning);}
.sc-state-bg--danger {background: var(--el-color-danger);}
.sc-state-bg--info {background: var(--el-color-info);}
@keyframes warn {
0% {
transform: scale(0.5);
opacity: 1;
}
30% {
opacity: 0.7;
}
100% {
transform: scale(2.5);
opacity: 0;
}
}
</style>
@@ -0,0 +1,66 @@
<!--
* @Descripttion: 趋势标记
* @version: 1.0
* @Author: sakuya
* @Date: 2021年11月11日11:07:10
* @LastEditors:
* @LastEditTime:
-->
<template>
<span class="sc-trend" :class="'sc-trend--'+type">
<el-icon v-if="iconType=='P'" class="sc-trend-icon"><el-icon-top /></el-icon>
<el-icon v-if="iconType=='N'" class="sc-trend-icon"><el-icon-bottom /></el-icon>
<el-icon v-if="iconType=='Z'" class="sc-trend-icon"><el-icon-right /></el-icon>
<em class="sc-trend-prefix">{{prefix}}</em>
<em class="sc-trend-value">{{modelValue}}</em>
<em class="sc-trend-suffix">{{suffix}}</em>
</span>
</template>
<script>
export default {
props: {
modelValue: { type: Number, default: 0 },
prefix: { type: String, default: "" },
suffix: { type: String, default: "" },
reverse: { type: Boolean, default: false }
},
computed: {
absValue(){
return Math.abs(this.modelValue);
},
iconType(v){
if(this.modelValue == 0){
v = 'Z'
}else if(this.modelValue < 0){
v = 'N'
}else if(this.modelValue > 0){
v = 'P'
}
return v
},
type(v){
if(this.modelValue == 0){
v = 'Z'
}else if(this.modelValue < 0){
v = this.reverse?'P':'N'
}else if(this.modelValue > 0){
v = this.reverse?'N':'P'
}
return v
}
}
}
</script>
<style scoped>
.sc-trend {display: flex;align-items: center;}
.sc-trend-icon {margin-right: 2px;}
.sc-trend em {font-style: normal;}
.sc-trend-prefix {margin-right: 2px;}
.sc-trend-suffix {margin-left: 2px;}
.sc-trend--P {color: #f56c6c;}
.sc-trend--N {color: #67c23a;}
.sc-trend--Z {color: #555;}
</style>
@@ -0,0 +1,52 @@
<!--
* @Descripttion: 页面头部样式组件
* @version: 1.0
* @Author: sakuya
* @Date: 2021年7月20日08:49:07
* @LastEditors:
* @LastEditTime:
-->
<template>
<div class="sc-page-header">
<div v-if="icon" class="sc-page-header__icon">
<span>
<el-icon><component :is="icon" /></el-icon>
</span>
</div>
<div class="sc-page-header__title">
<h2>{{ title }}</h2>
<p v-if="description || $slots.default">
<slot>
{{ description }}
</slot>
</p>
</div>
<div v-if="$slots.main" class="sc-page-header__main">
<slot name="main"></slot>
</div>
</div>
</template>
<script>
export default {
props: {
title: { type: String, required: true, default: "" },
description: { type: String, default: "" },
icon: { type: String, default: "" },
}
}
</script>
<style scoped>
.sc-page-header {background: #fff;border-bottom: 1px solid #e6e6e6;padding:20px 25px;display: flex;}
.sc-page-header__icon {width: 50px;}
.sc-page-header__icon span {display: inline-block;width: 30px;height: 30px;background: #409EFF;border-radius: 40%;display: flex;align-items: center;justify-content: center;}
.sc-page-header__icon span i {color: #fff;font-size: 14px;}
.sc-page-header__title {flex: 1;}
.sc-page-header__title h2 {font-size: 17px;color: #3c4a54;font-weight: bold;margin-top: 3px;}
.sc-page-header__title p {font-size: 13px;color: #999;margin-top: 15px;}
[data-theme='dark'] .sc-page-header {background:#2b2b2b ;border-color:var(--el-border-color-base);}
[data-theme='dark'] .sc-page-header__title h2 {color: #d0d0d0;}
</style>
@@ -0,0 +1,92 @@
<!--
* @Descripttion: 密码强度检测
* @version: 1.0
* @Author: sakuya
* @Date: 2022年6月2日15:36:01
* @LastEditors:
* @LastEditTime:
-->
<template>
<div class="sc-password-strength">
<div class="sc-password-strength-bar" :class="`sc-password-strength-level-${level}`"></div>
</div>
</template>
<script>
export default {
props: {
modelValue: { type: String, default: "" },
},
data() {
return {
level: 0
}
},
watch: {
modelValue() {
this.strength(this.modelValue)
}
},
mounted() {
this.strength(this.modelValue)
},
methods: {
strength(v){
var _level = 0
//长度
var has_length = v.length >= 6
//包含数字
var has_number = /\d/.test(v)
//包含小写英文
var has_lovercase = /[a-z]/.test(v)
//包含大写英文
var has_uppercase = /[A-Z]/.test(v)
//没有连续的字符3位
var no_continuity = !/(\w)\1{2}/.test(v)
//包含特殊字符
var has_special = /[`~!@#$%^&*()_+<>?:"{},./;'[\]]/.test(v)
if(v.length <= 0){
_level = 0
this.level = _level
return false
}
if(!has_length){
_level = 1
this.level = _level
return false
}
if(has_number){
_level += 1
}
if(has_lovercase){
_level += 1
}
if(has_uppercase){
_level += 1
}
if(no_continuity){
_level += 1
}
if(has_special){
_level += 1
}
this.level = _level
}
}
}
</script>
<style scoped>
.sc-password-strength {height: 5px;width: 100%;background: var(--el-color-info-light-5);border-radius: 5px;position: relative;margin:10px 0;}
.sc-password-strength:before {left: 20%;}
.sc-password-strength:after {right: 20%;}
.sc-password-strength:before, .sc-password-strength:after {position: absolute;content: "";display: block;width: 20%;height: inherit;border: 5px solid var(--el-bg-color-overlay);border-top: 0;border-bottom: 0;z-index: 1;background-color: transparent;box-sizing: border-box;}
.sc-password-strength-bar {position: absolute;height: inherit;width: 0%;border-radius: inherit;transition: width .5s ease-in-out,background .25s;background: transparent;}
.sc-password-strength-level-1 {width: 20%;background-color: var(--el-color-error);}
.sc-password-strength-level-2 {width: 40%;background-color: var(--el-color-error);}
.sc-password-strength-level-3 {width: 60%;background-color: var(--el-color-warning);}
.sc-password-strength-level-4 {width: 80%;background-color: var(--el-color-success);}
.sc-password-strength-level-5 {width: 100%;background-color: var(--el-color-success);}
</style>
@@ -0,0 +1,88 @@
<!--
* @Descripttion: 生成二维码组件
* @version: 1.0
* @Author: sakuya
* @Date: 2021年12月20日14:22:20
* @LastEditors:
* @LastEditTime:
-->
<template>
<img ref="img"/>
</template>
<script>
import QRcode from "./qrcode"
export default {
props: {
text: { type: String, required: true, default: "" },
size: { type: Number, default: 100 },
logo: { type: String, default: "" },
logoSize: { type: Number, default: 30 },
logoPadding: { type: Number, default: 5 },
colorDark: { type: String, default: "#000000" },
colorLight: { type: String, default: "#ffffff" },
correctLevel: { type: Number, default: 2 },
},
data() {
return {
qrcode: null
}
},
watch:{
text(){
this.draw()
}
},
mounted() {
this.draw()
},
methods: {
//创建原始二维码DOM
async create(){
return new Promise((resolve) => {
var element = document.createElement("div");
new QRcode(element, {
text: this.text,
width: this.size,
height: this.size,
colorDark: this.colorDark,
colorLight: this.colorLight,
correctLevel: this.correctLevel
})
if (element.getElementsByTagName("canvas")[0]) {
this.qrcode = element
resolve()
}
})
},
//绘制LOGO
async drawLogo(){
return new Promise((resolve) => {
var logo = new Image()
logo.src = this.logo
const logoPos = (this.size - this.logoSize) / 2
const rectSize = this.logoSize + this.logoPadding
const rectPos = (this.size - rectSize) / 2
var ctx = this.qrcode.getElementsByTagName("canvas")[0].getContext("2d")
logo.onload = ()=>{
ctx.fillRect(rectPos, rectPos, rectSize, rectSize)
ctx.drawImage(logo, logoPos, logoPos, this.logoSize, this.logoSize)
resolve()
}
})
},
async draw(){
await this.create()
if(this.logo){
await this.drawLogo()
}
this.$refs.img.src = this.qrcode.getElementsByTagName("canvas")[0].toDataURL("image/png")
},
}
}
</script>
<style>
</style>
@@ -0,0 +1,618 @@
/**
* @fileoverview
* - Using the 'QRCode for Javascript library'
* - Fixed dataset of 'QRCode for Javascript library' for support full-spec.
* - this library has no dependencies.
* - source page: https://github.com/makevoid/qrcodejs
*
*
* @author davidshimjs
* @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
* @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
*/
var QRCode;
(function () {
//---------------------------------------------------------------------
// QRCode for JavaScript
//
// Copyright (c) 2009 Kazuhiko Arase
//
// URL: http://www.d-project.com/
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license.php
//
// The word "QR Code" is registered trademark of
// DENSO WAVE INCORPORATED
// http://www.denso-wave.com/qrcode/faqpatent-e.html
//
//---------------------------------------------------------------------
function QR8bitByte(data) {
this.mode = QRMode.MODE_8BIT_BYTE;
this.data = data;
this.parsedData = [];
// Added to support UTF-8 Characters
for (var i = 0, l = this.data.length; i < l; i++) {
var byteArray = [];
var code = this.data.charCodeAt(i);
if (code > 0x10000) {
byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);
byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);
byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);
byteArray[3] = 0x80 | (code & 0x3F);
} else if (code > 0x800) {
byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);
byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);
byteArray[2] = 0x80 | (code & 0x3F);
} else if (code > 0x80) {
byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);
byteArray[1] = 0x80 | (code & 0x3F);
} else {
byteArray[0] = code;
}
this.parsedData.push(byteArray);
}
this.parsedData = Array.prototype.concat.apply([], this.parsedData);
if (this.parsedData.length != this.data.length) {
this.parsedData.unshift(191);
this.parsedData.unshift(187);
this.parsedData.unshift(239);
}
}
QR8bitByte.prototype = {
getLength: function (buffer) {
return this.parsedData.length;
},
write: function (buffer) {
for (var i = 0, l = this.parsedData.length; i < l; i++) {
buffer.put(this.parsedData[i], 8);
}
}
};
function QRCodeModel(typeNumber, errorCorrectLevel) {
this.typeNumber = typeNumber;
this.errorCorrectLevel = errorCorrectLevel;
this.modules = null;
this.moduleCount = 0;
this.dataCache = null;
this.dataList = [];
}
QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);}
return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}
if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}
this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}
return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
this.modules[r][6]=(r%2==0);}
for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}
for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}
for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}
this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}
var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}
this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}
row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
if(buffer.getLengthInBits()>totalDataCount*8){throw new Error("code length overflow. ("
+buffer.getLengthInBits()
+">"
+totalDataCount*8
+")");}
if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}
while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}
while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}
buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}
buffer.put(QRCodeModel.PAD1,8);}
return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}
return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}
return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}
return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
if(r==0&&c==0){continue;}
if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
while(n>=256){n-=255;}
return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}
for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}
if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}
this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
function _isSupportCanvas() {
return typeof CanvasRenderingContext2D != "undefined";
}
// android 2.x doesn't support Data-URI spec
function _getAndroid() {
var android = false;
var sAgent = navigator.userAgent;
if (/android/i.test(sAgent)) { // android
android = true;
var aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i);
if (aMat && aMat[1]) {
android = parseFloat(aMat[1]);
}
}
return android;
}
var svgDrawer = (function() {
var Drawing = function (el, htOption) {
this._el = el;
this._htOption = htOption;
};
Drawing.prototype.draw = function (oQRCode) {
var _htOption = this._htOption;
var _el = this._el;
var nCount = oQRCode.getModuleCount();
var nWidth = Math.floor(_htOption.width / nCount);
var nHeight = Math.floor(_htOption.height / nCount);
this.clear();
function makeSVG(tag, attrs) {
var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
for (var k in attrs)
if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);
return el;
}
var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});
svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
_el.appendChild(svg);
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorLight, "width": "100%", "height": "100%"}));
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"}));
for (var row = 0; row < nCount; row++) {
for (var col = 0; col < nCount; col++) {
if (oQRCode.isDark(row, col)) {
var child = makeSVG("use", {"x": String(col), "y": String(row)});
child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template")
svg.appendChild(child);
}
}
}
};
Drawing.prototype.clear = function () {
while (this._el.hasChildNodes())
this._el.removeChild(this._el.lastChild);
};
return Drawing;
})();
var useSVG = document.documentElement.tagName.toLowerCase() === "svg";
// Drawing in DOM by using Table tag
var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {
var Drawing = function (el, htOption) {
this._el = el;
this._htOption = htOption;
};
/**
* Draw the QRCode
*
* @param {QRCode} oQRCode
*/
Drawing.prototype.draw = function (oQRCode) {
var _htOption = this._htOption;
var _el = this._el;
var nCount = oQRCode.getModuleCount();
var nWidth = Math.floor(_htOption.width / nCount);
var nHeight = Math.floor(_htOption.height / nCount);
var aHTML = ['<table style="border:0;border-collapse:collapse;">'];
for (var row = 0; row < nCount; row++) {
aHTML.push('<tr>');
for (var col = 0; col < nCount; col++) {
aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
}
aHTML.push('</tr>');
}
aHTML.push('</table>');
_el.innerHTML = aHTML.join('');
// Fix the margin values as real size.
var elTable = _el.childNodes[0];
var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;
var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;
if (nLeftMarginTable > 0 && nTopMarginTable > 0) {
elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px";
}
};
/**
* Clear the QRCode
*/
Drawing.prototype.clear = function () {
this._el.innerHTML = '';
};
return Drawing;
})() : (function () { // Drawing in Canvas
function _onMakeImage() {
this._elImage.src = this._elCanvas.toDataURL("image/png");
this._elImage.style.display = "block";
this._elCanvas.style.display = "none";
}
// Android 2.1 bug workaround
// http://code.google.com/p/android/issues/detail?id=5141
if (this && this._android && this._android <= 2.1) {
var factor = 1 / window.devicePixelRatio;
var drawImage = CanvasRenderingContext2D.prototype.drawImage;
CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
if (("nodeName" in image) && /img/i.test(image.nodeName)) {
for (var i = arguments.length - 1; i >= 1; i--) {
arguments[i] = arguments[i] * factor;
}
} else if (typeof dw == "undefined") {
arguments[1] *= factor;
arguments[2] *= factor;
arguments[3] *= factor;
arguments[4] *= factor;
}
drawImage.apply(this, arguments);
};
}
/**
* Check whether the user's browser supports Data URI or not
*
* @private
* @param {Function} fSuccess Occurs if it supports Data URI
* @param {Function} fFail Occurs if it doesn't support Data URI
*/
function _safeSetDataURI(fSuccess, fFail) {
var self = this;
self._fFail = fFail;
self._fSuccess = fSuccess;
// Check it just once
if (self._bSupportDataURI === null) {
var el = document.createElement("img");
var fOnError = function() {
self._bSupportDataURI = false;
if (self._fFail) {
self._fFail.call(self);
}
};
var fOnSuccess = function() {
self._bSupportDataURI = true;
if (self._fSuccess) {
self._fSuccess.call(self);
}
};
el.onabort = fOnError;
el.onerror = fOnError;
el.onload = fOnSuccess;
el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data.
return;
} else if (self._bSupportDataURI === true && self._fSuccess) {
self._fSuccess.call(self);
} else if (self._bSupportDataURI === false && self._fFail) {
self._fFail.call(self);
}
};
/**
* Drawing QRCode by using canvas
*
* @constructor
* @param {HTMLElement} el
* @param {Object} htOption QRCode Options
*/
var Drawing = function (el, htOption) {
this._bIsPainted = false;
this._android = _getAndroid();
this._htOption = htOption;
this._elCanvas = document.createElement("canvas");
this._elCanvas.width = htOption.width;
this._elCanvas.height = htOption.height;
el.appendChild(this._elCanvas);
this._el = el;
this._oContext = this._elCanvas.getContext("2d");
this._bIsPainted = false;
this._elImage = document.createElement("img");
this._elImage.alt = "Scan me!";
this._elImage.style.display = "none";
this._el.appendChild(this._elImage);
this._bSupportDataURI = null;
};
/**
* Draw the QRCode
*
* @param {QRCode} oQRCode
*/
Drawing.prototype.draw = function (oQRCode) {
var _elImage = this._elImage;
var _oContext = this._oContext;
var _htOption = this._htOption;
var nCount = oQRCode.getModuleCount();
var nWidth = _htOption.width / nCount;
var nHeight = _htOption.height / nCount;
var nRoundedWidth = Math.round(nWidth);
var nRoundedHeight = Math.round(nHeight);
_elImage.style.display = "none";
this.clear();
for (var row = 0; row < nCount; row++) {
for (var col = 0; col < nCount; col++) {
var bIsDark = oQRCode.isDark(row, col);
var nLeft = col * nWidth;
var nTop = row * nHeight;
_oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
_oContext.lineWidth = 1;
_oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
_oContext.fillRect(nLeft, nTop, nWidth, nHeight);
// 안티 앨리어싱 방지 처리
_oContext.strokeRect(
Math.floor(nLeft) + 0.5,
Math.floor(nTop) + 0.5,
nRoundedWidth,
nRoundedHeight
);
_oContext.strokeRect(
Math.ceil(nLeft) - 0.5,
Math.ceil(nTop) - 0.5,
nRoundedWidth,
nRoundedHeight
);
}
}
this._bIsPainted = true;
};
/**
* Make the image from Canvas if the browser supports Data URI.
*/
Drawing.prototype.makeImage = function () {
if (this._bIsPainted) {
_safeSetDataURI.call(this, _onMakeImage);
}
};
/**
* Return whether the QRCode is painted or not
*
* @return {Boolean}
*/
Drawing.prototype.isPainted = function () {
return this._bIsPainted;
};
/**
* Clear the QRCode
*/
Drawing.prototype.clear = function () {
this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);
this._bIsPainted = false;
};
/**
* @private
* @param {Number} nNumber
*/
Drawing.prototype.round = function (nNumber) {
if (!nNumber) {
return nNumber;
}
return Math.floor(nNumber * 1000) / 1000;
};
return Drawing;
})();
/**
* Get the type by string length
*
* @private
* @param {String} sText
* @param {Number} nCorrectLevel
* @return {Number} type
*/
function _getTypeNumber(sText, nCorrectLevel) {
var nType = 1;
var length = _getUTF8Length(sText);
for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {
var nLimit = 0;
switch (nCorrectLevel) {
case QRErrorCorrectLevel.L :
nLimit = QRCodeLimitLength[i][0];
break;
case QRErrorCorrectLevel.M :
nLimit = QRCodeLimitLength[i][1];
break;
case QRErrorCorrectLevel.Q :
nLimit = QRCodeLimitLength[i][2];
break;
case QRErrorCorrectLevel.H :
nLimit = QRCodeLimitLength[i][3];
break;
}
if (length <= nLimit) {
break;
} else {
nType++;
}
}
if (nType > QRCodeLimitLength.length) {
throw new Error("Too long data");
}
return nType;
}
function _getUTF8Length(sText) {
var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a');
return replacedText.length + (replacedText.length != sText ? 3 : 0);
}
/**
* @class QRCode
* @constructor
* @example
* new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie");
*
* @example
* var oQRCode = new QRCode("test", {
* text : "http://naver.com",
* width : 128,
* height : 128
* });
*
* oQRCode.clear(); // Clear the QRCode.
* oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode.
*
* @param {HTMLElement|String} el target element or 'id' attribute of element.
* @param {Object|String} vOption
* @param {String} vOption.text QRCode link data
* @param {Number} [vOption.width=256]
* @param {Number} [vOption.height=256]
* @param {String} [vOption.colorDark="#000000"]
* @param {String} [vOption.colorLight="#ffffff"]
* @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]
*/
QRCode = function (el, vOption) {
this._htOption = {
width : 256,
height : 256,
typeNumber : 4,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRErrorCorrectLevel.H
};
if (typeof vOption === 'string') {
vOption = {
text : vOption
};
}
// Overwrites options
if (vOption) {
for (var i in vOption) {
this._htOption[i] = vOption[i];
}
}
if (typeof el == "string") {
el = document.getElementById(el);
}
if (this._htOption.useSVG) {
Drawing = svgDrawer;
}
this._android = _getAndroid();
this._el = el;
this._oQRCode = null;
this._oDrawing = new Drawing(this._el, this._htOption);
if (this._htOption.text) {
this.makeCode(this._htOption.text);
}
};
/**
* Make the QRCode
*
* @param {String} sText link data
*/
QRCode.prototype.makeCode = function (sText) {
this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);
this._oQRCode.addData(sText);
this._oQRCode.make();
this._el.title = sText;
this._oDrawing.draw(this._oQRCode);
this.makeImage();
};
/**
* Make the Image from Canvas element
* - It occurs automatically
* - Android below 3 doesn't support Data-URI spec.
*
* @private
*/
QRCode.prototype.makeImage = function () {
if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) {
this._oDrawing.makeImage();
}
};
/**
* Clear the QRCode
*/
QRCode.prototype.clear = function () {
this._oDrawing.clear();
};
/**
* @name QRCode.CorrectLevel
*/
QRCode.CorrectLevel = QRErrorCorrectLevel;
})();
export default QRCode;
@@ -0,0 +1,94 @@
<!--
* @Descripttion: 异步选择器
* @version: 1.1
* @Author: sakuya
* @Date: 2021年8月3日15:53:37
* @LastEditors: sakuya
* @LastEditTime: 2023年2月23日15:17:24
-->
<template>
<div class="sc-select">
<div v-if="initloading" class="sc-select-loading">
<el-icon class="is-loading"><el-icon-loading /></el-icon>
</div>
<el-select v-bind="$attrs" :loading="loading" @visible-change="visibleChange">
<el-option v-for="item in options" :key="item[props.value]" :label="item[props.label]" :value="objValueType ? item : item[props.value]">
<slot name="option" :data="item"></slot>
</el-option>
</el-select>
</div>
</template>
<script>
import config from "@/config/select";
export default {
props: {
apiObj: { type: Object, default: () => {} },
dic: { type: String, default: "" },
objValueType: { type: Boolean, default: false },
params: { type: Object, default: () => ({}) },
props: { type: Object, default: () => ({
label: config.props.label,
value: config.props.value
}) }
},
data() {
return {
dicParams: this.params,
loading: false,
options: [],
initloading: false
}
},
created() {
//如果有默认值就去请求接口获取options
if(this.hasValue()){
this.initloading = true
this.getRemoteData()
}
},
methods: {
//选项显示隐藏事件
visibleChange(ispoen){
if(ispoen && this.options.length==0 && (this.dic || this.apiObj)){
this.getRemoteData()
}
},
//获取数据
async getRemoteData(){
this.loading = true
this.dicParams[config.request.name] = this.dic
var res = {}
if(this.apiObj){
res = await this.apiObj.get(this.params)
}else if(this.dic){
res = await config.dicApiObj.get(this.params)
}
var response = config.parseData(res)
this.options = response.data
this.loading = false
this.initloading = false
},
//判断是否有回显默认值
hasValue(){
if(Array.isArray(this.$attrs.modelValue) && this.$attrs.modelValue.length <= 0){
return false
}else if(this.$attrs.modelValue){
return true
}else{
return false
}
}
}
}
</script>
<style scoped>
.sc-select {display: inline-block;position: relative;}
.sc-select-loading {position: absolute;top:0;left:0;right:0;bottom:0;background: #fff;z-index: 100;border-radius: 5px;border: 1px solid #EBEEF5;display: flex;align-items: center;padding-left:10px;}
.sc-select-loading i {font-size: 14px;}
.dark .sc-select-loading {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);}
</style>
@@ -0,0 +1,127 @@
<!--
* @Descripttion: 分类筛选器
* @version: 1.0
* @Author: sakuya
* @Date: 2022年5月26日15:59:52
* @LastEditors:
* @LastEditTime:
-->
<template>
<div class="sc-select-filter">
<div v-if="data.length<=0" class="sc-select-filter__no-data">
暂无数据
</div>
<div v-for="item in data" :key="item.key" class="sc-select-filter__item">
<div class="sc-select-filter__item-title" :style="{'width':labelWidth+'px'}"><label>{{item.title}}</label></div>
<div class="sc-select-filter__item-options">
<ul>
<li :class="{'active':selected[item.key]&&selected[item.key].includes(option.value)}" v-for="option in item.options" :key="option.value" @click="select(option, item)">
<el-icon v-if="option.icon"><component :is="option.icon" /></el-icon>
<span>{{option.label}}</span>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
data: { type: Array, default: () => [] },
selectedValues: { type: Object, default: () => { return {} } },
labelWidth: {type: Number, default: 80},
outputValueTypeToArray: { type: Boolean, default: false }
},
data() {
return {
selected: {}
}
},
watch:{
data(val) {
val.forEach(item => {
this.selected[item.key] = this.selectedValues[item.key] ||
(Array.isArray(item.options) && item.options.length) ? [item.options[0].value] : []
})
}
},
computed: {
selectedString() {
var outputData = JSON.parse(JSON.stringify(this.selected))
for (var key in outputData) {
outputData[key] = outputData[key].join(",")
}
return outputData
}
},
mounted() {
//默认赋值
this.data.forEach(item => {
this.selected[item.key] = this.selectedValues[item.key] ||
(Array.isArray(item.options) && item.options.length) ? [item.options[0].value] : []
})
},
methods: {
select(option, item){
//判断单选多选
if(item.multiple){
//如果多选选择的第一个
if(option.value === item.options[0].value){
//就赋值第一个的值
this.selected[item.key] = [option.value]
}else{
//如果选择的值已有
if(this.selected[item.key].includes(option.value)){
//删除选择的值
this.selected[item.key].splice(this.selected[item.key].findIndex(s => s === option.value), 1)
//当全删光时,把第一个选中
if(this.selected[item.key].length == 0){
this.selected[item.key] = [item.options[0].value]
}
}else{
//未有值的时候,追加选中值
this.selected[item.key].push(option.value)
//当含有第一个的值的时候,把第一个删除
if(this.selected[item.key].includes(item.options[0].value)){
this.selected[item.key].splice(this.selected[item.key].findIndex(s => s === item.options[0].value), 1)
}
}
}
}else{
//单选时,如果点击了已有值就赋值
if(!this.selected[item.key].includes(option.value)){
this.selected[item.key] = [option.value]
}else{
return false
}
}
this.change()
},
change(){
if(this.outputValueTypeToArray){
this.$emit('onChange', this.selected)
}else{
this.$emit('onChange', this.selectedString)
}
}
}
}
</script>
<style scoped>
.sc-select-filter {width: 100%;}
.sc-select-filter__item {display: flex;}
.sc-select-filter__item-title {width: 80px;}
.sc-select-filter__item-title label {font-size: 14px;padding-top:13px;display: inline-block;color: #999;}
.sc-select-filter__item-options {flex: 1;border-bottom: 1px dashed var(--el-border-color-light);}
.sc-select-filter__item-options ul {display: flex;flex-wrap: wrap;padding-top: 10px;}
.sc-select-filter__item-options li {list-style: none;cursor: pointer;height:28px;padding:0 15px;border-radius:32px;margin: 0 10px 10px 0;display: flex;align-items: center;background: var(--el-color-primary-light-9);}
.sc-select-filter__item-options li .el-icon {margin-right: 3px;font-size: 16px;}
.sc-select-filter__item-options li:hover {color: var(--el-color-primary);}
.sc-select-filter__item-options li.active {background: var(--el-color-primary);color: #fff;font-weight: bold;}
.sc-select-filter__item:last-of-type .sc-select-filter__item-options {border: 0;}
.sc-select-filter__no-data {color: #999;}
</style>
@@ -0,0 +1,48 @@
<template>
<el-select v-bind="$attrs" remote-show-suffix clearable remote :remote-method="querySearchAsync" :loading="loading">
<el-option v-for="item in options" :key="item[props.value]" :label="item[props.label]" :value="item[props.value]"/>
</el-select>
</template>
<script>
export default{
props: {
apiObj: { type: Object, default: () => {} },
props: { type: Object, default: () => {return {label: 'name', value: 'id'}} }
},
data(){
return {
loading: false,
options: []
}
},
created() {
//如果有默认值就去请求接口获取options
if(this.hasValue()){
this.initloading = true
}
},
methods: {
async querySearchAsync(queryString){
let params = {}
params[this.props.value] = queryString
let res = await this.apiObj.get(params)
if(res.code == 1){
this.options = res.data
this.loading = false
}
},
//判断是否有回显默认值
hasValue(){
if(Array.isArray(this.$attrs.modelValue) && this.$attrs.modelValue.length <= 0){
return false
}else if(this.$attrs.modelValue){
this.querySearchAsync(this.$attrs.modelValue)
return true
}else{
return false
}
}
}
}
</script>
@@ -0,0 +1,45 @@
<template>
<div>
<el-tree-select v-bind="$attrs" :data="treeData" check-strictly :render-after-expand="false" :default-expand-all="true" :props="defaultProps" clearable style="min-width: 240px;" />
</div>
</template>
<script>
import config from '@/config/select.js'
export default {
props: {
apiObj: { type: Object, default: () => {} },
props: { type: Object, default: () => {} },
hasTop: { type: Boolean, default: false }
},
data(){
return {
treeData: [],
defaultProps: {label: config.props.label, value: config.props.value}
}
},
mounted(){
this.defaultProps = Object.assign(this.defaultProps, this.props);
this.$nextTick(() => {
this.getData()
})
},
methods:{
async getData(){
let data = {}
data[this.defaultProps.label] = '顶级'
data[this.defaultProps.value] = 0
let res = await this.apiObj.get({is_tree: 1})
if(res.code == 1){
this.treeData = this.hasTop ? [data, ...res.data] : res.data
}else{
this.treeData = this.hasTop ? [data] : []
}
},
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,70 @@
<!--
* @Descripttion: 统计数值组件
* @version: 1.1
* @Author: sakuya
* @Date: 2021年6月23日13:11:32
* @LastEditors: sakuya
* @LastEditTime: 2022年5月14日19:55:09
-->
<template>
<div class="sc-statistic">
<div class="sc-statistic-title">
{{ title }}
<el-tooltip v-if="tips" effect="light">
<template #content>
<div style="width: 200px;line-height: 2;">
{{ tips }}
</div>
</template>
<el-icon class="sc-statistic-tips"><el-icon-question-filled/></el-icon>
</el-tooltip>
</div>
<div class="sc-statistic-content">
<span v-if="prefix" class="sc-statistic-content-prefix">{{ prefix }}</span>
<span class="sc-statistic-content-value">{{ cmtValue }}</span>
<span v-if="suffix" class="sc-statistic-content-suffix">{{ suffix }}</span>
</div>
<div v-if="description || $slots.default" class="sc-statistic-description">
<slot>
{{ description }}
</slot>
</div>
</div>
</template>
<script>
export default {
props: {
title: { type: String, required: true, default: "" },
value: { type: String, required: true, default: "" },
prefix: { type: String, default: "" },
suffix: { type: String, default: "" },
description: { type: String, default: "" },
tips: { type: String, default: "" },
groupSeparator: { type: Boolean, default: false }
},
data() {
return {
}
},
computed: {
cmtValue(){
return this.groupSeparator ? this.$TOOL.groupSeparator(this.value) : this.value
}
}
}
</script>
<style scoped>
.sc-statistic-title {font-size: 12px;color: #999;margin-bottom: 10px;display: flex;align-items: center;}
.sc-statistic-tips {margin-left: 5px;}
.sc-statistic-content {font-size: 20px;color: #333;}
.sc-statistic-content-value {font-weight: bold;}
.sc-statistic-content-prefix {margin-right: 5px;}
.sc-statistic-content-suffix {margin-left: 5px;font-size: 12px;}
.sc-statistic-description {margin-top: 10px;color: #999;}
.dark .sc-statistic-content {color: #d0d0d0;}
</style>
@@ -0,0 +1,23 @@
import { h, resolveComponent } from 'vue'
export default {
render() {
return h (
resolveComponent("el-table-column"),
{
index: this.index,
...this.$attrs
},
this.$slots
)
},
methods: {
index(index){
if(this.$attrs.type=="index"){
let page = this.$parent.$parent.currentPage
let pageSize = this.$parent.$parent.pageSize
return (page - 1) * pageSize + index + 1
}
}
}
}
@@ -0,0 +1,120 @@
<template>
<div v-if="usercolumn.length>0" class="setting-column" v-loading="isSave">
<div class="setting-column__title">
<span class="move_b"></span>
<span class="show_b">显示</span>
<span class="name_b">名称</span>
<span class="width_b">宽度</span>
<span class="sortable_b">排序</span>
<span class="fixed_b">固定</span>
</div>
<div class="setting-column__list" ref="list">
<ul>
<li v-for="item in usercolumn" :key="item.prop">
<span class="move_b">
<el-tag class="move" style="cursor: move;"><el-icon-d-caret style="width: 1em; height: 1em;"/></el-tag>
</span>
<span class="show_b">
<el-switch v-model="item.hide" :active-value="false" :inactive-value="true"></el-switch>
</span>
<span class="name_b" :title="item.prop">{{ item.label }}</span>
<span class="width_b">
<el-input v-model="item.width" placeholder="auto"></el-input>
</span>
<span class="sortable_b">
<el-switch v-model="item.sortable"></el-switch>
</span>
<span class="fixed_b">
<el-switch v-model="item.fixed"></el-switch>
</span>
</li>
</ul>
</div>
<div class="setting-column__bottom">
<el-button @click="backDefaul" :disabled="isSave">重置</el-button>
<el-button @click="save" type="primary">保存</el-button>
</div>
</div>
<el-empty v-else description="暂无可配置的列" :image-size="80"></el-empty>
</template>
<script>
import Sortable from 'sortablejs'
export default {
components: {
Sortable
},
props: {
column: { type: Object, default: () => {} }
},
data() {
return {
isSave: false,
usercolumn: JSON.parse(JSON.stringify(this.column||[]))
}
},
watch:{
usercolumn: {
handler(){
this.$emit('userChange', this.usercolumn)
},
deep: true
}
},
mounted() {
this.usercolumn.length>0 && this.rowDrop()
},
methods: {
rowDrop(){
const _this = this
const tbody = this.$refs.list.querySelector('ul')
Sortable.create(tbody, {
handle: ".move",
animation: 300,
ghostClass: "ghost",
onEnd({ newIndex, oldIndex }) {
const tableData = _this.usercolumn
const currRow = tableData.splice(oldIndex, 1)[0]
tableData.splice(newIndex, 0, currRow)
}
})
},
backDefaul(){
this.$emit('back', this.usercolumn)
},
save(){
this.$emit('save', this.usercolumn)
}
}
}
</script>
<style scoped>
.setting-column {}
.setting-column__title {border-bottom: 1px solid #EBEEF5;padding-bottom:15px;}
.setting-column__title span {display: inline-block;font-weight: bold;color: #909399;font-size: 12px;}
.setting-column__title span.move_b {width: 30px;margin-right:15px;}
.setting-column__title span.show_b {width: 60px;}
.setting-column__title span.name_b {width: 140px;}
.setting-column__title span.width_b {width: 60px;margin-right:15px;}
.setting-column__title span.sortable_b {width: 60px;}
.setting-column__title span.fixed_b {width: 60px;}
.setting-column__list {max-height:314px;overflow: auto;}
.setting-column__list li {list-style: none;margin:10px 0;display: flex;align-items: center;}
.setting-column__list li>span {display: inline-block;font-size: 12px;}
.setting-column__list li span.move_b {width: 30px;margin-right:15px;}
.setting-column__list li span.show_b {width: 60px;}
.setting-column__list li span.name_b {width: 140px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;cursor:default;}
.setting-column__list li span.width_b {width: 60px;margin-right:15px;}
.setting-column__list li span.sortable_b {width: 60px;}
.setting-column__list li span.fixed_b {width: 60px;}
.setting-column__list li.ghost {opacity: 0.3;}
.setting-column__bottom {border-top: 1px solid #EBEEF5;padding-top:15px;text-align: right;}
.dark .setting-column__title {border-color: var(--el-border-color-light);}
.dark .setting-column__bottom {border-color: var(--el-border-color-light);}
</style>
@@ -0,0 +1,409 @@
<!--
* @Descripttion: 数据表格组件
* @version: 1.10
* @Author: sakuya
* @Date: 2021年11月29日21:51:15
* @LastEditors: sakuya
* @LastEditTime: 2022年6月4日17:35:26
-->
<template>
<div class="scTable" :style="{'height':_height}" ref="scTableMain" v-loading="loading">
<div class="scTable-table" :style="{'height':_table_height}">
<el-table v-bind="$attrs" :data="tableData" :row-key="rowKey" :key="toggleIndex" ref="scTable" :height="height=='auto'?null:'100%'" :size="config.size" :border="config.border" :stripe="config.stripe" :summary-method="remoteSummary?remoteSummaryMethod:summaryMethod" @sort-change="sortChange" @filter-change="filterChange">
<slot></slot>
<template v-for="(item, index) in userColumn" :key="index">
<el-table-column v-if="!item.hide" :column-key="item.prop" :label="item.label" :prop="item.prop" :width="item.width" :sortable="item.sortable" :fixed="item.fixed" :filters="item.filters" :filter-method="remoteFilter||!item.filters?null:filterHandler" :show-overflow-tooltip="item.showOverflowTooltip">
<template #default="scope">
<slot :name="item.prop" v-bind="scope">
{{scope.row[item.prop]}}
</slot>
</template>
</el-table-column>
</template>
<el-table-column min-width="1"></el-table-column>
<template #empty>
<el-empty :description="emptyText" :image-size="100"></el-empty>
</template>
</el-table>
</div>
<div class="scTable-page" v-if="!hidePagination || !hideDo">
<div class="scTable-pagination">
<el-pagination v-if="!hidePagination" background :layout="paginationLayout" :total="total" :page-size="scPageSize" :page-sizes="pageSizes" v-model:currentPage="currentPage" @current-change="paginationChange" @update:page-size="pageSizeChange"></el-pagination>
</div>
<div class="scTable-do" v-if="!hideDo">
<el-button v-if="!hideRefresh" @click="refresh" icon="el-icon-refresh" circle style="margin-left:15px"></el-button>
<el-popover v-if="column" placement="top" title="列设置" :width="500" trigger="click" :hide-after="0" @show="customColumnShow=true" @after-leave="customColumnShow=false">
<template #reference>
<el-button icon="el-icon-set-up" circle style="margin-left:15px"></el-button>
</template>
<columnSetting v-if="customColumnShow" ref="columnSetting" @userChange="columnSettingChange" @save="columnSettingSave" @back="columnSettingBack" :column="userColumn"></columnSetting>
</el-popover>
<el-popover v-if="!hideSetting" placement="top" title="表格设置" :width="400" trigger="click" :hide-after="0">
<template #reference>
<el-button icon="el-icon-setting" circle style="margin-left:15px"></el-button>
</template>
<el-form label-width="80px" label-position="left">
<el-form-item label="表格尺寸">
<el-radio-group v-model="config.size" @change="configSizeChange">
<el-radio-button value="large"></el-radio-button>
<el-radio-button value="default">正常</el-radio-button>
<el-radio-button value="small"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="样式">
<el-checkbox v-model="config.border" label="纵向边框"></el-checkbox>
<el-checkbox v-model="config.stripe" label="斑马纹"></el-checkbox>
</el-form-item>
</el-form>
</el-popover>
</div>
</div>
</div>
</template>
<script>
import config from "@/config/table";
import columnSetting from './columnSetting'
export default {
name: 'scTable',
components: {
columnSetting
},
props: {
tableName: { type: String, default: "" },
apiObj: { type: Object, default: () => {} },
params: { type: Object, default: () => ({}) },
data: { type: Object, default: () => {} },
height: { type: [String,Number], default: "100%" },
size: { type: String, default: "small" },
border: { type: Boolean, default: true },
stripe: { type: Boolean, default: false },
pageSize: { type: Number, default: config.pageSize },
pageSizes: { type: Array, default: config.pageSizes },
rowKey: { type: String, default: "" },
summaryMethod: { type: Function, default: null },
column: { type: Object, default: () => {} },
remoteSort: { type: Boolean, default: false },
remoteFilter: { type: Boolean, default: false },
remoteSummary: { type: Boolean, default: false },
hidePagination: { type: Boolean, default: false },
hideDo: { type: Boolean, default: false },
hideRefresh: { type: Boolean, default: false },
hideSetting: { type: Boolean, default: false },
paginationLayout: { type: String, default: config.paginationLayout },
},
watch: {
//监听从props里拿到值了
data(){
this.tableData = this.data;
this.total = this.tableData.length;
},
apiObj(){
this.tableParams = this.params;
this.refresh();
},
column(){
this.userColumn=this.column;
}
},
computed: {
_height() {
return Number(this.height)?Number(this.height)+'px':this.height
},
_table_height() {
return this.hidePagination && this.hideDo ? "100%" : "calc(100% - 50px)"
}
},
data() {
return {
scPageSize: this.pageSize,
isActivat: true,
emptyText: "暂无数据",
toggleIndex: 0,
tableData: [],
total: 0,
currentPage: 1,
prop: null,
order: null,
loading: false,
tableHeight:'100%',
tableParams: this.params,
userColumn: [],
customColumnShow: false,
summary: {},
config: {
size: this.size,
border: this.border,
stripe: this.stripe
}
}
},
mounted() {
//判断是否开启自定义列
if(this.column){
this.getCustomColumn()
}else{
this.userColumn = this.column
}
//判断是否静态数据
if(this.apiObj){
this.getData();
}else if(this.data){
this.tableData = this.data;
this.total = this.tableData.length
}
},
activated(){
if(!this.isActivat){
this.$refs.scTable.doLayout()
}
},
deactivated(){
this.isActivat = false;
},
methods: {
//获取列
async getCustomColumn(){
const userColumn = await config.columnSettingGet(this.tableName, this.column)
this.userColumn = userColumn
},
//获取数据
async getData(){
this.tableData = []
this.loading = true;
var reqData = {
[config.request.page]: this.currentPage,
[config.request.pageSize]: this.scPageSize,
[config.request.prop]: this.prop,
[config.request.order]: this.order
}
if(this.hidePagination){
delete reqData[config.request.page]
delete reqData[config.request.pageSize]
}
Object.assign(reqData, this.tableParams)
try {
var res = await this.apiObj.get(reqData);
}catch(error){
this.loading = false;
this.emptyText = error.statusText;
return false;
}
try {
var response = config.parseData(res);
}catch(error){
this.loading = false;
this.emptyText = "数据格式错误";
return false;
}
if(response.code != config.successCode){
this.loading = false;
this.emptyText = response.msg;
}else{
this.emptyText = "暂无数据";
if(this.hidePagination){
this.tableData = response.data || [];
}else{
this.tableData = response.rows || [];
}
this.total = response.total || 0;
this.summary = response.summary || {};
this.loading = false;
}
// this.$refs.scTable.setScrollTop(0)
this.$emit('dataChange', res, this.tableData)
},
//分页点击
paginationChange(){
this.getData();
},
//条数变化
pageSizeChange(size){
this.scPageSize = size
this.getData();
},
//刷新数据
refresh(){
this.$refs.scTable.clearSelection();
this.getData();
},
//更新数据 合并上一次params
upData(params, page=1){
this.currentPage = page;
this.$refs.scTable.clearSelection();
Object.assign(this.tableParams, params || {})
this.getData()
},
//重载数据 替换params
reload(params, page=1){
this.currentPage = page;
this.tableParams = params || {}
this.$refs.scTable.clearSelection();
this.$refs.scTable.clearSort()
this.$refs.scTable.clearFilter()
this.getData()
},
//自定义变化事件
columnSettingChange(userColumn){
this.userColumn = userColumn;
this.toggleIndex += 1;
},
//自定义列保存
async columnSettingSave(userColumn){
this.$refs.columnSetting.isSave = true
try {
await config.columnSettingSave(this.tableName, userColumn)
}catch(error){
this.$message.error('保存失败')
this.$refs.columnSetting.isSave = false
}
this.$message.success('保存成功')
this.$refs.columnSetting.isSave = false
},
//自定义列重置
async columnSettingBack(){
this.$refs.columnSetting.isSave = true
try {
const column = await config.columnSettingReset(this.tableName, this.column)
this.userColumn = column
this.$refs.columnSetting.usercolumn = JSON.parse(JSON.stringify(this.userColumn||[]))
}catch(error){
this.$message.error('重置失败')
this.$refs.columnSetting.isSave = false
}
this.$refs.columnSetting.isSave = false
},
//排序事件
sortChange(obj){
if(!this.remoteSort){
return false
}
if(obj.column && obj.prop){
this.prop = obj.prop
this.order = obj.order
}else{
this.prop = null
this.order = null
}
this.getData()
},
//本地过滤
filterHandler(value, row, column){
const property = column.property;
return row[property] === value;
},
//过滤事件
filterChange(filters){
if(!this.remoteFilter){
return false
}
Object.keys(filters).forEach(key => {
filters[key] = filters[key].join(',')
})
this.upData(filters)
},
//远程合计行处理
remoteSummaryMethod(param){
const {columns} = param
const sums = []
columns.forEach((column, index) => {
if(index === 0) {
sums[index] = '合计'
return
}
const values = this.summary[column.property]
if(values){
sums[index] = values
}else{
sums[index] = ''
}
})
return sums
},
configSizeChange(){
this.$refs.scTable.doLayout()
},
//插入行 unshiftRow
unshiftRow(row){
this.tableData.unshift(row)
},
//插入行 pushRow
pushRow(row){
this.tableData.push(row)
},
//根据key覆盖数据
updateKey(row, rowKey=this.rowKey){
this.tableData.filter(item => item[rowKey]===row[rowKey] ).forEach(item => {
Object.assign(item, row)
})
},
//根据index覆盖数据
updateIndex(row, index){
Object.assign(this.tableData[index], row)
},
//根据index删除
removeIndex(index){
this.tableData.splice(index, 1)
},
//根据index批量删除
removeIndexes(indexes=[]){
indexes.forEach(index => {
this.tableData.splice(index, 1)
})
},
//根据key删除
removeKey(key, rowKey=this.rowKey){
this.tableData.splice(this.tableData.findIndex(item => item[rowKey]===key), 1)
},
//根据keys批量删除
removeKeys(keys=[], rowKey=this.rowKey){
keys.forEach(key => {
this.tableData.splice(this.tableData.findIndex(item => item[rowKey]===key), 1)
})
},
//原生方法转发
clearSelection(){
this.$refs.scTable.clearSelection()
},
toggleRowSelection(row, selected){
this.$refs.scTable.toggleRowSelection(row, selected)
},
toggleAllSelection(){
this.$refs.scTable.toggleAllSelection()
},
toggleRowExpansion(row, expanded){
this.$refs.scTable.toggleRowExpansion(row, expanded)
},
setCurrentRow(row){
this.$refs.scTable.setCurrentRow(row)
},
clearSort(){
this.$refs.scTable.clearSort()
},
clearFilter(columnKey){
this.$refs.scTable.clearFilter(columnKey)
},
doLayout(){
this.$refs.scTable.doLayout()
},
sort(prop, order){
this.$refs.scTable.sort(prop, order)
}
}
}
</script>
<style scoped>
.scTable {}
.scTable-table {height: calc(100% - 50px);}
.scTable-page {height:50px;display: flex;align-items: center;justify-content: space-between;padding:0 15px;}
.scTable-do {white-space: nowrap;}
.scTable:deep(.el-table__footer) .cell {font-weight: bold;}
.scTable:deep(.el-table__body-wrapper) .el-scrollbar__bar.is-horizontal {height: 12px;border-radius: 12px;}
.scTable:deep(.el-table__body-wrapper) .el-scrollbar__bar.is-vertical {width: 12px;border-radius: 12px;}
</style>
@@ -0,0 +1,233 @@
<!--
* @Descripttion: 表格选择器组件
* @version: 1.3
* @Author: sakuya
* @Date: 2021年6月10日10:04:07
* @LastEditors: sakuya
* @LastEditTime: 2022年6月6日21:50:36
-->
<template>
<el-select ref="select" v-model="defaultLable" :size="size" :clearable="clearable" :multiple="multiple" :collapse-tags="collapseTags" :collapse-tags-tooltip="collapseTagsTooltip" :filterable="filterable" :placeholder="placeholder" :disabled="disabled" :filter-method="filterMethod" @remove-tag="removeTag" @visible-change="visibleChange" @clear="clear">
<template #header>
<slot name="header" :form="formData" :submit="formSubmit"></slot>
</template>
<template #empty>
<div :style="{width: tableWidth+'px'}" v-loading="loading">
<el-table ref="table" :data="tableData" :height="245" :highlight-current-row="!multiple" @row-click="click" @select="select" @select-all="selectAll">
<el-table-column v-if="multiple" type="selection" width="45"></el-table-column>
<el-table-column v-else type="index" width="45">
<template #default="scope"><span>{{scope.$index+(currentPage - 1) * pageSize + 1}}</span></template>
</el-table-column>
<slot></slot>
</el-table>
</div>
</template>
<template #footer>
<el-pagination small background layout="prev, pager, next" :total="total" :page-size="pageSize" v-model:currentPage="currentPage" @current-change="reload"></el-pagination>
</template>
</el-select>
</template>
<script>
import config from "@/config/tableSelect";
export default {
props: {
modelValue: null,
apiObj: { type: Object, default: () => {} },
params: { type: Object, default: () => {} },
placeholder: { type: String, default: "请选择" },
size: { type: String, default: "default" },
clearable: { type: Boolean, default: false },
multiple: { type: Boolean, default: false },
filterable: { type: Boolean, default: false },
collapseTags: { type: Boolean, default: false },
collapseTagsTooltip: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
tableWidth: {type: Number, default: 400},
mode: { type: String, default: "popover" },
props: { type: Object, default: () => {} }
},
data() {
return {
loading: false,
keyword: null,
defaultValue: [],
defaultLable: [],
tableData: [],
pageSize: config.pageSize,
total: 0,
currentPage: 1,
defaultProps: {
label: config.props.label,
value: config.props.value,
page: config.request.page,
pageSize: config.request.pageSize,
keyword: config.request.keyword
},
formData: {}
}
},
computed: {
},
watch: {
modelValue:{
handler(){
this.defaultValue = this.modelValue
this.autoCurrentLabel()
},
deep: true
}
},
mounted() {
this.defaultProps = Object.assign(this.defaultProps, this.props);
this.defaultValue = this.modelValue
this.autoCurrentLabel()
},
methods: {
//表格显示隐藏回调
visibleChange(visible){
if(visible){
this.currentPage = 1
this.keyword = null
this.formData = {}
this.getData()
}else{
this.autoCurrentLabel()
}
},
//获取表格数据
async getData(){
this.loading = true;
var reqData = {
[this.defaultProps.page]: this.currentPage,
[this.defaultProps.pageSize]: this.pageSize,
[this.defaultProps.keyword]: this.keyword
}
Object.assign(reqData, this.params, this.formData)
var res = await this.apiObj.get(reqData);
var parseData = config.parseData(res)
this.tableData = parseData.rows;
this.total = parseData.total;
this.loading = false;
//表格默认赋值
this.$nextTick(() => {
if(this.multiple){
this.defaultValue.forEach(row => {
var setrow = this.tableData.filter(item => item[this.defaultProps.value]===row[this.defaultProps.value] )
if(setrow.length > 0){
this.$refs.table.toggleRowSelection(setrow[0], true);
}
})
}else{
var setrow = this.tableData.filter(item => item[this.defaultProps.value]===this.defaultValue[this.defaultProps.value] )
this.$refs.table.setCurrentRow(setrow[0]);
}
this.$refs.table.setScrollTop(0)
})
},
//插糟表单提交
formSubmit(){
this.currentPage = 1
this.keyword = null
this.getData()
},
//分页刷新表格
reload(){
this.getData()
},
//自动模拟options赋值
autoCurrentLabel(){
this.$nextTick(() => {
if(this.multiple){
this.defaultValue.map(item => {
this.defaultLable.push(item[this.props.label])
})
}else{
this.defaultLable = this.defaultValue[this.defaultProps.label]
}
})
},
//表格勾选事件
select(rows, row){
var isSelect = rows.length && rows.indexOf(row) !== -1
if(isSelect){
this.defaultValue.push(row)
}else{
this.defaultValue.splice(this.defaultValue.findIndex(item => item[this.defaultProps.value] == row[this.defaultProps.value]), 1)
}
this.autoCurrentLabel()
this.$emit('update:modelValue', this.defaultValue);
this.$emit('change', this.defaultValue);
},
//表格全选事件
selectAll(rows){
var isAllSelect = rows.length > 0
if(isAllSelect){
rows.forEach(row => {
var isHas = this.defaultValue.find(item => item[this.defaultProps.value] == row[this.defaultProps.value])
if(!isHas){
this.defaultValue.push(row)
}
})
}else{
this.tableData.forEach(row => {
var isHas = this.defaultValue.find(item => item[this.defaultProps.value] == row[this.defaultProps.value])
if(isHas){
this.defaultValue.splice(this.defaultValue.findIndex(item => item[this.defaultProps.value] == row[this.defaultProps.value]), 1)
}
})
}
this.autoCurrentLabel()
this.$emit('update:modelValue', this.defaultValue);
this.$emit('change', this.defaultValue);
},
click(row){
if(this.multiple){
//处理多选点击行
}else{
this.defaultValue = row
this.$refs.select.blur()
this.autoCurrentLabel()
this.$emit('update:modelValue', this.defaultValue);
this.$emit('change', this.defaultValue);
}
},
//tags删除后回调
removeTag(tag){
var row = this.findRowByKey(tag[this.defaultProps.value])
this.$refs.table.toggleRowSelection(row, false);
this.$emit('update:modelValue', this.defaultValue);
},
//清空后的回调
clear(){
this.$emit('update:modelValue', this.defaultValue);
},
// 关键值查询表格数据行
findRowByKey (value) {
return this.tableData.find(item => item[this.defaultProps.value] === value)
},
filterMethod(keyword){
if(!keyword){
this.keyword = null;
return false;
}
this.keyword = keyword;
this.getData()
},
// 触发select隐藏
blur(){
this.$refs.select.blur();
},
// 触发select显示
focus(){
this.$refs.select.focus();
}
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,27 @@
<template>
<div class="sc-title">
<span class="title">{{title}}</span>
<span><slot name="right"></slot></span>
</div>
</template>
<script>
export default {
props: {
title: { type: String, required: true, default: "" },
},
data() {
return {
}
},
computed: {
}
}
</script>
<style scoped>
.sc-title {border-bottom: 1px solid #eee;padding: 5px; margin: 15px 0 5px 0; display: flex;justify-content: space-between;}
.sc-title span.title{font-size: 14px; color: #3c4a54;font-weight: bold;}
</style>
@@ -0,0 +1,193 @@
<template>
<div class="sc-upload-file">
<el-upload
:disabled="disabled"
:auto-upload="autoUpload"
:action="action"
:name="name"
:data="data"
:http-request="request"
v-model:file-list="defaultFileList"
:show-file-list="showFileList"
:drag="drag"
:accept="accept"
:multiple="multiple"
:limit="limit"
:before-upload="before"
:on-success="success"
:on-error="error"
:on-preview="handlePreview"
:on-exceed="handleExceed">
<slot>
<el-button type="primary" :disabled="disabled">点击上传</el-button>
</slot>
<template #tip>
<div v-if="tip" class="el-upload__tip">{{tip}}</div>
</template>
</el-upload>
<span style="display:none!important"><el-input v-model="value"></el-input></span>
</div>
</template>
<script>
import config from "@/config/upload"
export default {
props: {
modelValue: { type: [String, Array], default: "" },
tip: { type: String, default: "" },
action: { type: String, default: "" },
apiObj: { type: Object, default: () => {} },
name: { type: String, default: config.filename },
data: { type: Object, default: () => {} },
accept: { type: String, default: "" },
maxSize: { type: Number, default: config.maxSizeFile },
limit: { type: Number, default: 0 },
autoUpload: { type: Boolean, default: true },
showFileList: { type: Boolean, default: true },
drag: { type: Boolean, default: false },
multiple: { type: Boolean, default: true },
disabled: { type: Boolean, default: false },
onSuccess: { type: Function, default: () => { return true } }
},
data() {
return {
value: "",
defaultFileList: []
}
},
watch:{
modelValue(val){
if(Array.isArray(val)){
if (JSON.stringify(val) != JSON.stringify(this.formatArr(this.defaultFileList))) {
this.defaultFileList = val
this.value = val
}
}else{
if (val != this.toStr(this.defaultFileList)) {
this.defaultFileList = this.toArr(val)
this.value = val
}
}
},
defaultFileList: {
handler(val){
this.$emit('update:modelValue', Array.isArray(this.modelValue) ? this.formatArr(val) : this.toStr(val))
this.value = this.toStr(val)
},
deep: true
}
},
mounted() {
this.defaultFileList = Array.isArray(this.modelValue) ? this.modelValue : this.toArr(this.modelValue)
this.value = this.modelValue
},
methods: {
//默认值转换为数组
toArr(str){
if(!str){
return []
}
var _arr = []
var arr = str.split(",")
arr.forEach(item => {
if(item){
var urlArr = item.split('/');
var fileName = urlArr[urlArr.length - 1]
_arr.push({
name: fileName,
url: item
})
}
})
return _arr
},
//数组转换为原始值
toStr(arr){
return arr.map(v => v.url).join(",")
},
//格式化数组值
formatArr(arr){
var _arr = []
arr.forEach(item => {
if(item){
_arr.push({
name: item.name,
url: item.url
})
}
})
return _arr
},
before(file){
const maxSize = file.size / 1024 / 1024 < this.maxSize;
if (!maxSize) {
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
return false;
}
},
success(res, file){
var os = this.onSuccess(res, file)
if(os!=undefined && os==false){
return false
}
var response = config.parseData(res)
file.name = response.fileName
file.url = response.src
},
error(err){
this.$notify.error({
title: '上传文件未成功',
message: err
})
},
beforeRemove(uploadFile){
return this.$confirm(`是否移除 ${uploadFile.name} ?`, '提示', {
type: 'warning',
}).then(() => {
return true
}).catch(() => {
return false
})
},
handleExceed(){
this.$message.warning(`当前设置最多上传 ${this.limit} 个文件,请移除后上传!`)
},
handlePreview(uploadFile){
window.open(uploadFile.url)
},
request(param){
var apiObj = config.apiObjFile;
if(this.apiObj){
apiObj = this.apiObj;
}
const data = new FormData();
data.append(param.filename, param.file);
for (const key in param.data) {
data.append(key, param.data[key]);
}
apiObj.post(data, {
onUploadProgress: e => {
const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
param.onProgress({percent: complete})
}
}).then(res => {
var response = config.parseData(res);
if(response.code == config.successCode){
param.onSuccess(res)
}else{
param.onError(response.msg || "未知错误")
}
}).catch(err => {
param.onError(err)
})
}
}
}
</script>
<style scoped>
.el-form-item.is-error .sc-upload-file:deep(.el-upload-dragger) {border-color: var(--el-color-danger);}
.sc-upload-file {width: 100%;}
.sc-upload-file:deep(.el-upload-list__item) {transition: none !important;}
</style>
@@ -0,0 +1,282 @@
<template>
<div class="sc-upload" :class="{'sc-upload-round':round}" :style="style">
<div v-if="file && file.status != 'success'" class="sc-upload__uploading">
<div class="sc-upload__progress">
<el-progress :percentage="file.percentage" :text-inside="true" :stroke-width="16"/>
</div>
<el-image class="image" :src="file.tempFile" fit="cover"></el-image>
</div>
<div v-if="file && file.status=='success'" class="sc-upload__img">
<el-image class="image" :src="file.url" :preview-src-list="[file.url]" fit="cover" hide-on-click-modal append-to-body :z-index="9999">
<template #placeholder>
<div class="sc-upload__img-slot">
Loading...
</div>
</template>
</el-image>
<div class="sc-upload__img-actions" v-if="!disabled">
<span class="del" @click="handleRemove()"><el-icon><el-icon-delete /></el-icon></span>
</div>
</div>
<el-upload v-if="!file" class="uploader" ref="uploader"
:auto-upload="cropper?false:autoUpload"
:disabled="disabled"
:show-file-list="showFileList"
:action="action"
:name="name"
:data="data"
:accept="accept"
:limit="1"
:http-request="request"
:on-change="change"
:before-upload="before"
:on-success="success"
:on-error="error"
:on-exceed="handleExceed"
drag>
<slot>
<div class="el-upload--picture-card">
<div class="file-empty">
<el-icon><component :is="icon" /></el-icon>
<h4 v-if="title">{{title}}</h4>
</div>
</div>
</slot>
</el-upload>
<span style="display:none!important"><el-input v-model="value"></el-input></span>
<el-dialog title="剪裁" draggable v-model="cropperDialogVisible" :width="680" @closed="cropperClosed" destroy-on-close>
<sc-cropper :src="cropperFile.tempCropperFile" :compress="compress" :aspectRatio="aspectRatio" ref="cropper"></sc-cropper>
<template #footer>
<el-button @click="cropperDialogVisible=false" > </el-button>
<el-button type="primary" @click="cropperSave"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { defineAsyncComponent } from 'vue'
import { genFileId } from 'element-plus'
const scCropper = defineAsyncComponent(() => import('@/components/scCropper'))
import config from "@/config/upload"
export default {
props: {
modelValue: { type: String, default: "" },
height: {type: Number, default: 148},
width: {type: Number, default: 148},
title: { type: String, default: "" },
icon: { type: String, default: "el-icon-plus" },
action: { type: String, default: "" },
apiObj: { type: Object, default: () => {} },
name: { type: String, default: config.filename },
data: { type: Object, default: () => {} },
accept: { type: String, default: "image/gif, image/jpeg, image/png" },
maxSize: { type: Number, default: config.maxSizeFile },
limit: { type: Number, default: 1 },
autoUpload: { type: Boolean, default: true },
showFileList: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
round: { type: Boolean, default: false },
onSuccess: { type: Function, default: () => { return true } },
cropper: { type: Boolean, default: false },
compress: {type: Number, default: 1},
aspectRatio: {type: Number, default: NaN}
},
components: {
scCropper
},
data() {
return {
value: "",
file: null,
style: {
width: this.width + "px",
height: this.height + "px"
},
cropperDialogVisible: false,
cropperFile: null
}
},
watch:{
modelValue(val){
this.value = val
this.newFile(val)
},
value(val){
this.$emit('update:modelValue', val)
}
},
mounted() {
this.value = this.modelValue
this.newFile(this.modelValue)
},
methods: {
newFile(url){
if(url){
this.file = {
status: "success",
url: url
}
}else{
this.file = null
}
},
cropperSave(){
this.$refs.cropper.getCropFile(file => {
file.uid = this.cropperFile.uid
this.cropperFile.raw = file
this.file = this.cropperFile
this.file.tempFile = URL.createObjectURL(this.file.raw)
this.$refs.uploader.submit()
}, this.cropperFile.name, this.cropperFile.type)
this.cropperDialogVisible = false
},
cropperClosed(){
URL.revokeObjectURL(this.cropperFile.tempCropperFile)
delete this.cropperFile.tempCropperFile
},
handleRemove(){
this.clearFiles()
},
clearFiles(){
URL.revokeObjectURL(this.file.tempFile)
this.value = ""
this.file = null
this.$nextTick(()=>{
this.$refs.uploader.clearFiles()
})
},
change(file,files){
if(files.length > 1){
files.splice(0, 1)
}
if(this.cropper && file.status=='ready'){
const acceptIncludes = ["image/gif", "image/jpeg", "image/png"].includes(file.raw.type)
if(!acceptIncludes){
this.$notify.warning({
title: '上传文件警告',
message: '选择的文件非图像类文件'
})
return false
}
this.cropperFile = file
this.cropperFile.tempCropperFile = URL.createObjectURL(file.raw)
this.cropperDialogVisible = true
return false
}
this.file = file
if(file.status=='ready'){
file.tempFile = URL.createObjectURL(file.raw)
}
},
before(file){
const acceptIncludes = this.accept.replace(/\s/g,"").split(",").includes(file.type)
if(!acceptIncludes){
this.$notify.warning({
title: '上传文件警告',
message: '选择的文件非图像类文件'
})
this.clearFiles()
return false
}
const maxSize = file.size / 1024 / 1024 < this.maxSize;
if (!maxSize) {
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
this.clearFiles()
return false
}
},
handleExceed(files){
const file = files[0]
file.uid = genFileId()
this.$refs.uploader.handleStart(file)
},
success(res, file){
//释放内存删除blob
URL.revokeObjectURL(file.tempFile)
delete file.tempFile
var os = this.onSuccess(res, file)
if(os!=undefined && os==false){
this.$nextTick(() => {
this.file = null
this.value = ""
})
return false
}
var response = config.parseData(res)
file.url = response.src
this.value = file.url
},
error(err){
this.$nextTick(()=>{
this.clearFiles()
})
this.$notify.error({
title: '上传文件未成功',
message: err
})
},
request(param){
var apiObj = config.apiObj;
if(this.apiObj){
apiObj = this.apiObj;
}
const data = new FormData();
data.append(param.filename, param.file);
for (const key in param.data) {
data.append(key, param.data[key]);
}
apiObj.post(data, {
onUploadProgress: e => {
const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
param.onProgress({percent: complete})
}
}).then(res => {
var response = config.parseData(res);
if(response.code == config.successCode){
param.onSuccess(res)
}else{
param.onError(response.msg || "未知错误")
}
}).catch(err => {
param.onError(err)
})
}
}
}
</script>
<style scoped>
.el-form-item.is-error .sc-upload .el-upload--picture-card {border-color: var(--el-color-danger);}
.sc-upload .el-upload--picture-card {border-radius: 0;}
.sc-upload .uploader,.sc-upload:deep(.el-upload) {width: 100%;height: 100%;}
.sc-upload__img {width: 100%;height: 100%;position: relative;}
.sc-upload__img .image {width: 100%;height: 100%;}
.sc-upload__img-actions {position: absolute;top:0;right: 0;display: none;}
.sc-upload__img-actions span {display: flex;justify-content: center;align-items: center;width: 25px;height:25px;cursor: pointer;color: #fff;}
.sc-upload__img-actions span i {font-size: 12px;}
.sc-upload__img-actions .del {background: #F56C6C;}
.sc-upload__img:hover .sc-upload__img-actions {display: block;}
.sc-upload__img-slot {display: flex;justify-content: center;align-items: center;width: 100%;height: 100%;font-size: 12px;background-color: var(--el-fill-color-lighter);}
.sc-upload__uploading {width: 100%;height: 100%;position: relative;}
.sc-upload__progress {position: absolute;width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;background-color: var(--el-overlay-color-lighter);z-index: 1;padding:10px;}
.sc-upload__progress .el-progress {width: 100%;}
.sc-upload__uploading .image {width: 100%;height: 100%;}
.sc-upload .file-empty {width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;flex-direction: column;}
.sc-upload .file-empty i {font-size: 28px;}
.sc-upload .file-empty h4 {font-size: 12px;font-weight: normal;color: #8c939d;margin-top: 8px;}
.sc-upload.sc-upload-round {border-radius: 50%;overflow: hidden;}
.sc-upload.sc-upload-round .el-upload--picture-card {border-radius: 50%;}
.sc-upload.sc-upload-round .sc-upload__img-actions {top: auto;left: 0;right: 0;bottom: 0;}
.sc-upload.sc-upload-round .sc-upload__img-actions span {width: 100%;}
</style>
@@ -0,0 +1,249 @@
<template>
<div class="sc-upload-multiple">
<el-upload ref="uploader" list-type="picture-card"
:auto-upload="autoUpload"
:disabled="disabled"
:action="action"
:name="name"
:data="data"
:http-request="request"
v-model:file-list="defaultFileList"
:show-file-list="showFileList"
:accept="accept"
:multiple="multiple"
:limit="limit"
:before-upload="before"
:on-success="success"
:on-error="error"
:on-preview="handlePreview"
:on-exceed="handleExceed">
<slot>
<el-icon><el-icon-plus/></el-icon>
</slot>
<template #tip>
<div v-if="tip" class="el-upload__tip">{{tip}}</div>
</template>
<template #file="{ file }">
<div class="sc-upload-list-item">
<el-image class="el-upload-list__item-thumbnail" :src="file.url" fit="cover" :preview-src-list="preview" :initial-index="preview.findIndex(n=>n==file.url)" hide-on-click-modal append-to-body :z-index="9999">
<template #placeholder>
<div class="sc-upload-multiple-image-slot">
Loading...
</div>
</template>
</el-image>
<div v-if="!disabled && file.status=='success'" class="sc-upload__item-actions">
<span class="del" @click="handleRemove(file)"><el-icon><el-icon-delete /></el-icon></span>
</div>
<div v-if="file.status=='ready' || file.status=='uploading'" class="sc-upload__item-progress">
<el-progress :percentage="file.percentage" :text-inside="true" :stroke-width="16"/>
</div>
</div>
</template>
</el-upload>
<span style="display:none!important"><el-input v-model="value"></el-input></span>
</div>
</template>
<script>
import config from "@/config/upload"
import Sortable from 'sortablejs'
export default {
props: {
modelValue: { type: [String, Array], default: "" },
tip: { type: String, default: "" },
action: { type: String, default: "" },
apiObj: { type: Object, default: () => {} },
name: { type: String, default: config.filename },
data: { type: Object, default: () => {} },
accept: { type: String, default: "image/gif, image/jpeg, image/png" },
maxSize: { type: Number, default: config.maxSizeFile },
limit: { type: Number, default: 0 },
autoUpload: { type: Boolean, default: true },
showFileList: { type: Boolean, default: true },
multiple: { type: Boolean, default: true },
disabled: { type: Boolean, default: false },
draggable: { type: Boolean, default: false },
onSuccess: { type: Function, default: () => { return true } }
},
data(){
return {
value: "",
defaultFileList: []
}
},
watch:{
modelValue(val){
if(Array.isArray(val)){
if (JSON.stringify(val) != JSON.stringify(this.formatArr(this.defaultFileList))) {
this.defaultFileList = val
this.value = val
}
}else{
if (val != this.toStr(this.defaultFileList)) {
this.defaultFileList = this.toArr(val)
this.value = val
}
}
},
defaultFileList: {
handler(val){
this.$emit('update:modelValue', Array.isArray(this.modelValue) ? this.formatArr(val) : this.toStr(val))
this.value = this.toStr(val)
},
deep: true
}
},
computed: {
preview(){
return this.defaultFileList.map(v => v.url)
}
},
mounted() {
this.defaultFileList = Array.isArray(this.modelValue) ? this.modelValue : this.toArr(this.modelValue)
this.value = this.modelValue
if(!this.disabled && this.draggable){
this.rowDrop()
}
},
methods: {
//默认值转换为数组
toArr(str){
var _arr = [];
var arr = str.split(",");
arr.forEach(item => {
if(item){
var urlArr = item.split('/');
var fileName = urlArr[urlArr.length - 1]
_arr.push({
name: fileName,
url: item
})
}
})
return _arr;
},
//数组转换为原始值
toStr(arr){
return arr.map(v => v.url).join(",")
},
//格式化数组值
formatArr(arr){
var _arr = []
arr.forEach(item => {
if(item){
_arr.push({
name: item.name,
url: item.url
})
}
})
return _arr
},
//拖拽
rowDrop(){
const _this = this
const itemBox = this.$refs.uploader.$el.querySelector('.el-upload-list')
Sortable.create(itemBox, {
handle: ".el-upload-list__item",
animation: 200,
ghostClass: "ghost",
onEnd({ newIndex, oldIndex }) {
const tableData = _this.defaultFileList
const currRow = tableData.splice(oldIndex, 1)[0]
tableData.splice(newIndex, 0, currRow)
}
})
},
before(file){
if(!['image/jpeg','image/png','image/gif'].includes(file.type)){
this.$message.warning(`选择的文件类型 ${file.type} 非图像类文件`);
return false;
}
const maxSize = file.size / 1024 / 1024 < this.maxSize;
if (!maxSize) {
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
return false;
}
},
success(res, file){
var os = this.onSuccess(res, file)
if(os!=undefined && os==false){
return false
}
var response = config.parseData(res)
file.name = response.fileName
file.url = response.src
},
error(err){
this.$notify.error({
title: '上传文件未成功',
message: err
})
},
beforeRemove(uploadFile){
return this.$confirm(`是否移除 ${uploadFile.name} ?`, '提示', {
type: 'warning',
}).then(() => {
return true
}).catch(() => {
return false
})
},
handleRemove(file){
this.$refs.uploader.handleRemove(file)
//this.defaultFileList.splice(this.defaultFileList.findIndex(item => item.uid===file.uid), 1)
},
handleExceed(){
this.$message.warning(`当前设置最多上传 ${this.limit} 个文件,请移除后上传!`)
},
handlePreview(uploadFile){
window.open(uploadFile.url)
},
request(param){
var apiObj = config.apiObj;
if(this.apiObj){
apiObj = this.apiObj;
}
const data = new FormData();
data.append(param.filename, param.file);
for (const key in param.data) {
data.append(key, param.data[key]);
}
apiObj.post(data, {
onUploadProgress: e => {
const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
param.onProgress({percent: complete})
}
}).then(res => {
var response = config.parseData(res);
if(response.code == config.successCode){
param.onSuccess(res)
}else{
param.onError(response.msg || "未知错误")
}
}).catch(err => {
param.onError(err)
})
}
}
}
</script>
<style scoped>
.el-form-item.is-error .sc-upload-multiple:deep(.el-upload--picture-card) {border-color: var(--el-color-danger);}
:deep(.el-upload-list__item) {transition:none;border-radius: 0;}
.sc-upload-multiple:deep(.el-upload-list__item.el-list-leave-active) {position: static!important;}
.sc-upload-multiple:deep(.el-upload--picture-card) {border-radius: 0;}
.sc-upload-list-item {width: 100%;height: 100%;position: relative;}
.sc-upload-multiple .el-image {display: block;}
.sc-upload-multiple .el-image:deep(img) {-webkit-user-drag: none;}
.sc-upload-multiple-image-slot {display: flex;justify-content: center;align-items: center;width: 100%;height: 100%;font-size: 12px;}
.sc-upload-multiple .el-upload-list__item:hover .sc-upload__item-actions{display: block;}
.sc-upload__item-actions {position: absolute;top:0;right: 0;display: none;}
.sc-upload__item-actions span {display: flex;justify-content: center;align-items: center;;width: 25px;height:25px;cursor: pointer;color: #fff;}
.sc-upload__item-actions span i {font-size: 12px;}
.sc-upload__item-actions .del {background: #F56C6C;}
.sc-upload__item-progress {position: absolute;width: 100%;height: 100%;top: 0;left: 0;background-color: var(--el-overlay-color-lighter);}
</style>
@@ -0,0 +1,265 @@
<!--
* @Descripttion: 表格选择器组件
* @version: 1.2
* @Author: sakuya
* @Date: 2021年6月10日10:04:07
* @LastEditors: sakuya
* @LastEditTime: 2022年2月28日09:39:03
-->
<template>
<el-select ref="select" v-model="defaultValue" clearable :multiple="multiple" filterable :placeholder="placeholder" :disabled="disabled" :filter-method="filterMethod" @remove-tag="removeTag" @visible-change="visibleChange" @clear="clear">
<template #empty>
<div class="sc-table-select__table" :style="{width: tableWidth+'px'}" v-loading="loading">
<el-container>
<el-header>
<el-form :inline="true" :model="formData">
<el-form-item>
<el-input v-model="formData.name" placeholder="姓名" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="formSubmit">查询</el-button>
</el-form-item>
</el-form>
</el-header>
<el-container>
<el-aside width="210px">
<div style="height:300px">
<el-tree :props="departmentProps" :data="department" node-key="id" @node-click="groupClick"/>
</div>
</el-aside>
<el-main>
<el-table ref="table" :data="tableData" :height="245" :highlight-current-row="!multiple" @row-click="click" @select="select" @select-all="selectAll">
<el-table-column v-if="multiple" type="selection" width="45"></el-table-column>
<el-table-column prop="uid" label="ID" width="80"></el-table-column>
<el-table-column prop="username" label="用户名" width="150"></el-table-column>
<el-table-column prop="nickname" label="姓名" width="150"></el-table-column>
</el-table>
<div class="sc-table-select__page">
<el-pagination small background layout="prev, pager, next" :total="total" :page-size="pageSize" :page-sizes="[100, 200, 300, 400]" v-model:currentPage="currentPage" @current-change="reload"></el-pagination>
</div>
</el-main>
</el-container>
</el-container>
</div>
</template>
</el-select>
</template>
<script>
import config from "@/config/tableSelect";
export default {
props: {
modelValue: null,
params: { type: Object, default: () => {} },
placeholder: { type: String, default: "请选择" },
multiple: { type: Boolean, default: true },
disabled: { type: Boolean, default: false },
tableWidth: {type: Number, default: 620},
mode: { type: String, default: "popover" },
},
data() {
return {
apiObj: this.$API.user.list,
props: {
label: 'nickname',
value: 'uid',
keyword: "name"
},
//所需数据选项
department: [],
departmentProps: {
value: "id",
label: "title"
},
loading: false,
keyword: null,
defaultValue: [],
tableData: [],
pageSize: config.pageSize,
total: 0,
currentPage: 1,
defaultProps: {
label: config.props.label,
value: config.props.value,
page: config.request.page,
pageSize: config.request.pageSize,
keyword: config.request.keyword
},
formData: {}
}
},
computed: {
},
watch: {
modelValue:{
handler(){
this.defaultValue = this.modelValue
this.autoCurrentLabel()
},
deep: true
}
},
mounted() {
this.defaultProps = Object.assign(this.defaultProps, this.props);
this.defaultValue = this.modelValue
this.autoCurrentLabel()
this.getDepartment();
},
methods: {
async getDepartment(){
var res = await this.$API.user.department.list.get();
this.department = res.data;
},
groupClick(data){
this.formData.department_id = data.id;
this.getData();
},
//表格显示隐藏回调
visibleChange(visible){
if(visible){
this.currentPage = 1
this.keyword = null
this.formData = {}
this.getData()
}else{
this.autoCurrentLabel()
}
},
//获取表格数据
async getData(){
this.loading = true;
var reqData = {
[this.defaultProps.page]: this.currentPage,
[this.defaultProps.pageSize]: this.pageSize,
[this.defaultProps.keyword]: this.keyword
}
Object.assign(reqData, this.params, this.formData)
var res = await this.apiObj.get(reqData);
var parseData = config.parseData(res)
this.tableData = parseData.rows;
this.total = parseData.total;
this.loading = false;
//表格默认赋值
this.$nextTick(() => {
if(this.multiple){
this.defaultValue.forEach(row => {
var setrow = this.tableData.filter(item => item[this.defaultProps.value]===row[this.defaultProps.value] )
if(setrow.length > 0){
this.$refs.table.toggleRowSelection(setrow[0], true);
}
})
}else{
var setrow = this.tableData.filter(item => item[this.defaultProps.value]===this.defaultValue[this.defaultProps.value] )
this.$refs.table.setCurrentRow(setrow[0]);
}
this.$refs.table.$el.querySelector('.el-table__body-wrapper').scrollTop = 0
})
},
//插糟表单提交
formSubmit(){
this.currentPage = 1
this.keyword = null
this.getData()
},
//分页刷新表格
reload(){
this.getData()
},
//自动模拟options赋值
autoCurrentLabel(){
this.$nextTick(() => {
if(this.multiple){
this.$refs.select.selected.forEach(item => {
item.currentLabel = item.value[this.defaultProps.label]
})
}else{
this.$refs.select.selectedLabel = this.defaultValue[this.defaultProps.label]
}
})
},
//表格勾选事件
select(rows, row){
var isSelect = rows.length && rows.indexOf(row) !== -1
if(isSelect){
this.defaultValue.push(row)
}else{
this.defaultValue.splice(this.defaultValue.findIndex(item => item[this.defaultProps.value] == row[this.defaultProps.value]), 1)
}
this.autoCurrentLabel()
this.$emit('update:modelValue', this.defaultValue);
this.$emit('change', this.defaultValue);
},
//表格全选事件
selectAll(rows){
var isAllSelect = rows.length > 0
if(isAllSelect){
rows.forEach(row => {
var isHas = this.defaultValue.find(item => item[this.defaultProps.value] == row[this.defaultProps.value])
if(!isHas){
this.defaultValue.push(row)
}
})
}else{
this.tableData.forEach(row => {
var isHas = this.defaultValue.find(item => item[this.defaultProps.value] == row[this.defaultProps.value])
if(isHas){
this.defaultValue.splice(this.defaultValue.findIndex(item => item[this.defaultProps.value] == row[this.defaultProps.value]), 1)
}
})
}
this.autoCurrentLabel()
this.$emit('update:modelValue', this.defaultValue);
this.$emit('change', this.defaultValue);
},
click(row){
if(this.multiple){
//处理多选点击行
}else{
this.defaultValue = row
this.$refs.select.blur()
this.autoCurrentLabel()
this.$emit('update:modelValue', this.defaultValue);
this.$emit('change', this.defaultValue);
}
},
//tags删除后回调
removeTag(tag){
var row = this.findRowByKey(tag[this.defaultProps.value])
this.$refs.table.toggleRowSelection(row, false);
this.$emit('update:modelValue', this.defaultValue);
},
//清空后的回调
clear(){
this.$emit('update:modelValue', this.defaultValue);
},
// 关键值查询表格数据行
findRowByKey (value) {
return this.tableData.find(item => item[this.defaultProps.value] === value)
},
filterMethod(keyword){
if(!keyword){
this.keyword = null;
return false;
}
this.keyword = keyword;
this.getData()
},
// 触发select隐藏
blur(){
this.$refs.select.blur();
},
// 触发select显示
focus(){
this.$refs.select.focus();
}
}
}
</script>
<style scoped>
.sc-table-select__table {padding:12px;}
.sc-table-select__page {padding-top: 12px;}
</style>
@@ -0,0 +1,84 @@
<!--
* @Descripttion: xgplayer二次封装
* @version: 1.1
* @Author: sakuya
* @Date: 2021年11月29日12:10:06
* @LastEditors: sakuya
* @LastEditTime: 2022年5月30日21:02:50
-->
<template>
<div class="sc-video" ref="scVideo"></div>
</template>
<script>
import Player from 'xgplayer'
import HlsPlayer from 'xgplayer-hls'
export default {
props: {
src: { type: String, required: true, default: "" },
autoplay: { type: Boolean, default: false },
controls: { type: Boolean, default: true },
loop: { type: Boolean, default: false },
isLive: { type: Boolean, default: false },
options: { type: Object, default: () => {} }
},
data() {
return {
player: null
}
},
watch:{
src(val){
if(this.player.hasStart){
this.player.src = val
}else{
this.player.start(val)
}
}
},
mounted() {
if(this.isLive){
this.initHls()
}else{
this.init()
}
},
methods: {
init(){
this.player = new Player({
el: this.$refs.scVideo,
url: this.src,
autoplay: this.autoplay,
loop: this.loop,
controls: this.controls,
fluid: true,
lang: 'zh-cn',
...this.options
})
},
initHls(){
this.player = new HlsPlayer({
el: this.$refs.scVideo,
url: this.src,
autoplay: this.autoplay,
loop: this.loop,
controls: this.controls,
fluid: true,
isLive: true,
ignores: ['time','progress'],
lang: 'zh-cn',
...this.options
})
}
}
}
</script>
<style scoped>
.sc-video:deep(.danmu) > * {color: #fff;font-size:20px;font-weight:bold;text-shadow:1px 1px 0 #000,-1px -1px 0 #000,-1px 1px 0 #000,1px -1px 0 #000;}
.sc-video:deep(.xgplayer-controls) {background-image: linear-gradient(180deg, transparent, rgba(0,0,0,0.3));}
.sc-video:deep(.xgplayer-progress-tip) {border:0;color: #fff;background: rgba(0,0,0,.5);line-height: 25px;padding: 0 10px;border-radius: 25px;}
.sc-video:deep(.xgplayer-enter-spinner) {width: 50px;height: 50px;}
</style>
@@ -0,0 +1,66 @@
<!--
* @Descripttion: 局部水印组件
* @version: 1.1
* @Author: sakuya
* @Date: 2021年12月18日12:16:16
* @LastEditors: sakuya
* @LastEditTime: 2022年1月5日09:52:59
-->
<template>
<div class="sc-water-mark" ref="scWaterMark">
<slot></slot>
</div>
</template>
<script>
export default {
props: {
text: { type: String, required: true, default: "" },
subtext: { type: String, default: "" },
color: { type: String, default: "rgba(128,128,128,0.2)" }
},
data() {
return {
}
},
mounted() {
this.create()
},
methods: {
create(){
this.clear()
//创建画板
var canvas = document.createElement('canvas')
canvas.width = 150
canvas.height = 150
canvas.style.display = 'none'
//绘制文字
var text = canvas.getContext('2d')
text.rotate(-45 * Math.PI / 180)
text.translate(-75, 25)
text.fillStyle = this.color
text.font = "bold 20px SimHei"
text.textAlign = "center"
text.fillText(this.text, canvas.width / 2, canvas.height / 2)
text.font = "14px Microsoft YaHei"
text.fillText(this.subtext, canvas.width / 2, canvas.height / 2 + 20)
//创建水印容器
var watermark = document.createElement('div')
watermark.setAttribute('class', 'watermark')
const styleStr = `position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;pointer-events:none;background-repeat:repeat;background-image:url('${canvas.toDataURL("image/png")}');`
watermark.setAttribute('style', styleStr);
this.$refs.scWaterMark.appendChild(watermark)
},
clear(){
var wmDom = this.$refs.scWaterMark.querySelector('.watermark')
wmDom && wmDom.remove()
}
}
}
</script>
<style scoped>
.sc-water-mark {position: relative;display: inherit;width: 100%;height: 100%;}
</style>
@@ -0,0 +1,154 @@
<!--
* @Descripttion: 仿钉钉流程设计器
* @version: 1.3
* @Author: sakuya
* @Date: 2021年9月14日08:38:35
* @LastEditors: sakuya
* @LastEditTime: 2022年5月14日19:43:46
-->
<template>
<div class="sc-workflow-design">
<div class="box-scale">
<node-wrap v-if="nodeConfig" v-model="nodeConfig"></node-wrap>
<div class="end-node">
<div class="end-node-circle"></div>
<div class="end-node-text">流程结束</div>
</div>
</div>
<use-select v-if="selectVisible" ref="useselect" @closed="selectVisible=false"></use-select>
</div>
</template>
<script>
import nodeWrap from './nodeWrap'
import useSelect from './select'
export default {
provide(){
return {
select: this.selectHandle
}
},
props: {
modelValue: { type: Object, default: () => {} }
},
components: {
nodeWrap,
useSelect
},
data() {
return {
nodeConfig: this.modelValue,
selectVisible: false
}
},
watch:{
modelValue(val){
this.nodeConfig = val
},
nodeConfig(val){
this.$emit("update:modelValue", val)
}
},
mounted() {
},
methods: {
selectHandle(type, data){
this.selectVisible = true
this.$nextTick(() => {
this.$refs.useselect.open(type, data)
})
}
}
}
</script>
<style lang="scss">
.sc-workflow-design {width: 100%;}
.sc-workflow-design .box-scale {display: inline-block;position: relative;width: 100%;padding: 54.5px 0px;align-items: flex-start;justify-content: center;flex-wrap: wrap;min-width: min-content;}
.sc-workflow-design {
.node-wrap {display: inline-flex;width: 100%;flex-flow: column wrap;justify-content: flex-start;align-items: center;padding: 0px 50px;position: relative;z-index: 1;}
.node-wrap-box {display: inline-flex;flex-direction: column;position: relative;width: 220px;min-height: 72px;flex-shrink: 0;background: rgb(255, 255, 255);border-radius: 4px;cursor: pointer;box-shadow: 0 2px 5px 0 rgba(0,0,0,.1);}
.node-wrap-box::before {content: "";position: absolute;top: -12px;left: 50%;transform: translateX(-50%);width: 0px;border-style: solid;border-width: 8px 6px 4px;border-color: rgb(202, 202, 202) transparent transparent;background: #f6f8f9;}
.node-wrap-box.start-node:before {content: none}
.node-wrap-box .title {height:24px;line-height: 24px;color: #fff;padding-left: 16px;padding-right: 30px;border-radius: 4px 4px 0 0;position: relative;display: flex;align-items: center;}
.node-wrap-box .title .icon {margin-right: 5px;}
.node-wrap-box .title .close {font-size: 15px;position: absolute;top:50%;transform: translateY(-50%);right:10px;display: none;}
.node-wrap-box .content {position: relative;padding: 15px;}
.node-wrap-box .content .placeholder {color: #999;}
.node-wrap-box:hover .close {display: block;}
.add-node-btn-box {width: 240px;display: inline-flex;flex-shrink: 0;position: relative;z-index: 1;}
.add-node-btn-box:before {content: "";position: absolute;top: 0px;left: 0px;right: 0px;bottom: 0px;z-index: -1;margin: auto;width: 2px;height: 100%;background-color: rgb(202, 202, 202);}
.add-node-btn {user-select: none;width: 240px;padding: 20px 0px 32px;display: flex;justify-content: center;flex-shrink: 0;flex-grow: 1;}
.add-node-btn span {}
.add-branch {justify-content: center;padding: 0px 10px;position: absolute;top: -16px;left: 50%;transform: translateX(-50%);transform-origin: center center;z-index: 1;display: inline-flex;align-items: center;}
.branch-wrap {display: inline-flex;width: 100%;}
.branch-box-wrap {display: flex;flex-flow: column wrap;align-items: center;min-height: 270px;width: 100%;flex-shrink: 0;}
.col-box {display: inline-flex;flex-direction: column;align-items: center;position: relative;background: #f6f8f9;}
.branch-box {display: flex;overflow: visible;min-height: 180px;height: auto;border-bottom: 2px solid #ccc;border-top: 2px solid #ccc;position: relative;margin-top: 15px;}
.branch-box .col-box::before {content: "";position: absolute;top: 0px;left: 0px;right: 0px;bottom: 0px;z-index: 0;margin: auto;width: 2px;height: 100%;background-color: rgb(202, 202, 202);}
.condition-node {display: inline-flex;flex-direction: column;min-height: 220px;}
.condition-node-box {padding-top: 30px;padding-right: 50px;padding-left: 50px;justify-content: center;align-items: center;flex-grow: 1;position: relative;display: inline-flex;flex-direction: column;}
.condition-node-box::before {content: "";position: absolute;top: 0px;left: 0px;right: 0px;bottom: 0px;margin: auto;width: 2px;height: 100%;background-color: rgb(202, 202, 202);}
.auto-judge {position: relative;width: 220px;min-height: 72px;background: rgb(255, 255, 255);border-radius: 4px;padding: 15px 15px;cursor: pointer;box-shadow: 0 2px 5px 0 rgba(0,0,0,.1);}
.auto-judge::before {content: "";position: absolute;top: -12px;left: 50%;transform: translateX(-50%);width: 0px;border-style: solid;border-width: 8px 6px 4px;border-color: rgb(202, 202, 202) transparent transparent;background: rgb(245, 245, 247);}
.auto-judge .title {line-height: 16px;}
.auto-judge .title .node-title {color: #15BC83;}
.auto-judge .title .close {font-size: 15px;position: absolute;top:15px;right:15px;color: #999;display: none;}
.auto-judge .title .priority-title {position: absolute;top:15px;right:15px;color: #999;}
.auto-judge .content {position: relative;padding-top: 15px;}
.auto-judge .content .placeholder {color: #999;}
.auto-judge:hover {
.close {display: block;}
.priority-title {display: none;}
}
.top-left-cover-line, .top-right-cover-line {position: absolute;height: 3px;width: 50%;background-color: #f6f8f9;top: -2px;}
.bottom-left-cover-line, .bottom-right-cover-line {position: absolute;height: 3px;width: 50%;background-color: #f6f8f9;bottom: -2px;}
.top-left-cover-line {left: -1px;}
.top-right-cover-line {right: -1px;}
.bottom-left-cover-line {left: -1px;}
.bottom-right-cover-line {right: -1px;}
.end-node {border-radius: 50%;font-size: 14px;color: rgba(25,31,37,.4);text-align: left;}
.end-node-circle {width: 10px;height: 10px;margin: auto;border-radius: 50%;background: #dbdcdc;}
.end-node-text {margin-top: 5px;text-align: center;}
.auto-judge:hover {
.sort-left {display: flex;}
.sort-right {display: flex;}
}
.auto-judge .sort-left {position: absolute;top: 0;bottom: 0;z-index: 1;left: 0;display: none;justify-content: center;align-items: center;flex-direction: column;}
.auto-judge .sort-right {position: absolute;top: 0;bottom: 0;z-index: 1;right: 0;display: none;justify-content: center;align-items: center;flex-direction: column;}
.auto-judge .sort-left:hover, .auto-judge .sort-right:hover {background: #eee;}
.auto-judge:after {pointer-events: none;content: "";position: absolute;top:0;bottom:0;left:0;right:0;z-index: 2;border-radius: 4px;transition: all .1s;}
.auto-judge:hover:after {border: 1px solid #3296fa;box-shadow: 0 0 6px 0 rgba(50,150,250,.3);}
.node-wrap-box:after {pointer-events: none;content: "";position: absolute;top:0;bottom:0;left:0;right:0;z-index: 2;border-radius: 4px;transition: all .1s;}
.node-wrap-box:hover:after {border: 1px solid #3296fa;box-shadow: 0 0 6px 0 rgba(50,150,250,.3);}
}
.tags-list {margin-top: 15px;width: 100%;}
.add-node-popover-body {}
.add-node-popover-body li {display: inline-block;width: 80px;text-align: center;padding:10px 0;}
.add-node-popover-body li i {border: 1px solid var(--el-border-color-light);width:40px;height:40px;border-radius: 50%;text-align: center;line-height: 38px;font-size: 18px;cursor: pointer;}
.add-node-popover-body li i:hover {border: 1px solid #3296fa;background: #3296fa;color: #fff!important;}
.add-node-popover-body li p {font-size: 12px;margin-top: 5px;}
.node-wrap-drawer__title {padding-right:40px;}
.node-wrap-drawer__title label {cursor: pointer;}
.node-wrap-drawer__title label:hover {border-bottom: 1px dashed #409eff;}
.node-wrap-drawer__title .node-wrap-drawer__title-edit {color: #409eff;margin-left: 10px;vertical-align: middle;}
.dark .sc-workflow-design {
.node-wrap-box,.auto-judge {background: #2b2b2b;}
.col-box {background: var(--el-bg-color);}
.top-left-cover-line,
.top-right-cover-line,
.bottom-left-cover-line,
.bottom-right-cover-line {background-color: var(--el-bg-color);}
.node-wrap-box::before,.auto-judge::before {background-color: var(--el-bg-color);}
.branch-box .add-branch {background: var(--el-bg-color);}
.end-node .end-node-text {color: #d0d0d0;}
.auto-judge .sort-left:hover, .auto-judge .sort-right:hover {background: var(--el-bg-color);}
}
</style>
@@ -0,0 +1,57 @@
<template>
<promoter v-if="nodeConfig.type==0" v-model="nodeConfig"></promoter>
<approver v-if="nodeConfig.type==1" v-model="nodeConfig"></approver>
<send v-if="nodeConfig.type==2" v-model="nodeConfig"></send>
<branch v-if="nodeConfig.type==4" v-model="nodeConfig">
<template v-slot="slot">
<node-wrap v-if="slot.node" v-model="slot.node.childNode"></node-wrap>
</template>
</branch>
<node-wrap v-if="nodeConfig.childNode" v-model="nodeConfig.childNode"></node-wrap>
</template>
<script>
import approver from './nodes/approver'
import promoter from './nodes/promoter'
import branch from './nodes/branch'
import send from './nodes/send'
export default {
props: {
modelValue: { type: Object, default: () => {} }
},
components: {
approver,
promoter,
branch,
send
},
data() {
return {
nodeConfig: {},
}
},
watch:{
modelValue(val){
this.nodeConfig = val
},
nodeConfig(val){
this.$emit("update:modelValue", val)
}
},
mounted() {
this.nodeConfig = this.modelValue
},
methods: {
}
}
</script>
<style>
</style>
@@ -0,0 +1,102 @@
<template>
<div class="add-node-btn-box">
<div class="add-node-btn">
<el-popover placement="right-start" :width="270" trigger="click" :hide-after="0" :show-after="0">
<template #reference>
<el-button type="primary" icon="el-icon-plus" circle></el-button>
</template>
<div class="add-node-popover-body">
<ul>
<li>
<el-icon style="color: #ff943e;" @click="addType(1)"><el-icon-user-filled /></el-icon>
<p>审批节点</p>
</li>
<li>
<el-icon style="color: #3296fa;" @click="addType(2)"><el-icon-promotion /></el-icon>
<p>抄送节点</p>
</li>
<li>
<el-icon style="color: #15BC83;" @click="addType(4)"><el-icon-share /></el-icon>
<p>条件分支</p>
</li>
</ul>
</div>
</el-popover>
</div>
</div>
</template>
<script>
export default {
props: {
modelValue: { type: Object, default: () => {} }
},
data() {
return {
}
},
mounted() {
},
methods: {
addType(type){
var node = {}
if (type == 1) {
node = {
nodeName: "审核人",
type: 1, //节点类型
setType: 1, //审核人类型
nodeUserList: [], //审核人成员
nodeRoleList: [], //审核角色
examineLevel: 1, //指定主管层级
directorLevel: 1, //自定义连续主管审批层级
selectMode: 1, //发起人自选类型
termAuto: false, //审批期限超时自动审批
term: 0, //审批期限
termMode: 1, //审批期限超时后执行类型
examineMode: 1, //多人审批时审批方式
directorMode: 0, //连续主管审批方式
childNode: this.modelValue
}
}else if(type == 2){
node = {
nodeName: "抄送人",
type: 2,
userSelectFlag: true,
nodeUserList: [],
childNode: this.modelValue
}
}else if(type == 4){
node = {
nodeName: "条件路由",
type: 4,
conditionNodes: [
{
nodeName: "条件1",
type: 3,
priorityLevel: 1,
conditionMode: 1,
conditionList: []
},
{
nodeName: "条件2",
type: 3,
priorityLevel: 2,
conditionMode: 1,
conditionList: []
}
],
childNode: this.modelValue
}
}
this.$emit("update:modelValue", node)
}
}
}
</script>
<style>
</style>
@@ -0,0 +1,193 @@
<template>
<div class="node-wrap">
<div class="node-wrap-box" @click="show">
<div class="title" style="background: #ff943e;">
<el-icon class="icon"><el-icon-user-filled /></el-icon>
<span>{{ nodeConfig.nodeName }}</span>
<el-icon class="close" @click.stop="delNode()"><el-icon-close /></el-icon>
</div>
<div class="content">
<span v-if="toText(nodeConfig)">{{ toText(nodeConfig) }}</span>
<span v-else class="placeholder">请选择</span>
</div>
</div>
<add-node v-model="nodeConfig.childNode"></add-node>
<el-drawer title="审批人设置" v-model="drawer" destroy-on-close append-to-body :size="500">
<template #header>
<div class="node-wrap-drawer__title">
<label @click="editTitle" v-if="!isEditTitle">{{form.nodeName}}<el-icon class="node-wrap-drawer__title-edit"><el-icon-edit /></el-icon></label>
<el-input v-if="isEditTitle" ref="nodeTitle" v-model="form.nodeName" clearable @blur="saveTitle" @keyup.enter="saveTitle"></el-input>
</div>
</template>
<el-container>
<el-main style="padding:0 20px 20px 20px">
<el-form label-position="top">
<el-form-item label="审批人员类型">
<el-select v-model="form.setType">
<el-option :value="1" label="指定成员"></el-option>
<el-option :value="2" label="主管"></el-option>
<el-option :value="3" label="角色"></el-option>
<el-option :value="4" label="发起人自选"></el-option>
<el-option :value="5" label="发起人自己"></el-option>
<el-option :value="7" label="连续多级主管"></el-option>
</el-select>
</el-form-item>
<el-form-item v-if="form.setType==1" label="选择成员">
<el-button type="primary" icon="el-icon-plus" round @click="selectHandle(1, form.nodeUserList)">选择人员</el-button>
<div class="tags-list">
<el-tag v-for="(user, index) in form.nodeUserList" :key="user.id" closable @close="delUser(index)">{{user.name}}</el-tag>
</div>
</el-form-item>
<el-form-item v-if="form.setType==2" label="指定主管">
发起人的第 <el-input-number v-model="form.examineLevel" :min="1"/> 级主管
</el-form-item>
<el-form-item v-if="form.setType==3" label="选择角色">
<el-button type="primary" icon="el-icon-plus" round @click="selectHandle(2, form.nodeRoleList)">选择角色</el-button>
<div class="tags-list">
<el-tag v-for="(role, index) in form.nodeRoleList" :key="role.id" type="info" closable @close="delRole(index)">{{role.name}}</el-tag>
</div>
</el-form-item>
<el-form-item v-if="form.setType==4" label="发起人自选">
<el-radio-group v-model="form.selectMode">
<el-radio :label="1">自选一个人</el-radio>
<el-radio :label="2">自选多个人</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.setType==7" label="连续主管审批终点">
<el-radio-group v-model="form.directorMode">
<el-radio :label="0">直到最上层主管</el-radio>
<el-radio :label="1">自定义审批终点</el-radio>
</el-radio-group>
<p v-if="form.directorMode==1">直到发起人的第 <el-input-number v-model="form.directorLevel" :min="1"/> 级主管</p>
</el-form-item>
<el-divider></el-divider>
<el-form-item label="">
<el-checkbox v-model="form.termAuto" label="超时自动审批"></el-checkbox>
</el-form-item>
<template v-if="form.termAuto">
<el-form-item label="审批期限(为 0 则不生效)">
<el-input-number v-model="form.term" :min="0"/> 小时
</el-form-item>
<el-form-item label="审批期限超时后执行">
<el-radio-group v-model="form.termMode">
<el-radio :label="0">自动通过</el-radio>
<el-radio :label="1">自动拒绝</el-radio>
</el-radio-group>
</el-form-item>
</template>
<el-divider></el-divider>
<el-form-item label="多人审批时审批方式">
<el-radio-group v-model="form.examineMode">
<p style="width: 100%;"><el-radio :label="1">按顺序依次审批</el-radio></p>
<p style="width: 100%;"><el-radio :label="2">会签 (可同时审批每个人必须审批通过)</el-radio></p>
<p style="width: 100%;"><el-radio :label="3">或签 (有一人审批通过即可)</el-radio></p>
</el-radio-group>
</el-form-item>
</el-form>
</el-main>
<el-footer>
<el-button type="primary" @click="save">保存</el-button>
<el-button @click="drawer=false">取消</el-button>
</el-footer>
</el-container>
</el-drawer>
</div>
</template>
<script>
import addNode from './addNode'
export default {
inject: ['select'],
props: {
modelValue: { type: Object, default: () => {} }
},
components: {
addNode
},
data() {
return {
nodeConfig: {},
drawer: false,
isEditTitle: false,
form: {}
}
},
watch:{
modelValue(){
this.nodeConfig = this.modelValue
}
},
mounted() {
this.nodeConfig = this.modelValue
},
methods: {
show(){
this.form = {}
this.form = JSON.parse(JSON.stringify(this.nodeConfig))
this.drawer = true
},
editTitle(){
this.isEditTitle = true
this.$nextTick(()=>{
this.$refs.nodeTitle.focus()
})
},
saveTitle(){
this.isEditTitle = false
},
save(){
this.$emit("update:modelValue", this.form)
this.drawer = false
},
delNode(){
this.$emit("update:modelValue", this.nodeConfig.childNode)
},
delUser(index){
this.form.nodeUserList.splice(index, 1)
},
delRole(index){
this.form.nodeRoleList.splice(index, 1)
},
selectHandle(type, data){
this.select(type, data)
},
toText(nodeConfig){
if(nodeConfig.setType == 1){
if (nodeConfig.nodeUserList && nodeConfig.nodeUserList.length>0) {
const users = nodeConfig.nodeUserList.map(item=>item.name).join("、")
return users
}else{
return false
}
}else if (nodeConfig.setType == 2) {
return nodeConfig.examineLevel == 1 ? '直接主管' : `发起人的第${nodeConfig.examineLevel}级主管`
}else if (nodeConfig.setType == 3) {
if (nodeConfig.nodeRoleList && nodeConfig.nodeRoleList.length>0) {
const roles = nodeConfig.nodeRoleList.map(item=>item.name).join("、")
return '角色-' + roles
}else{
return false
}
}else if (nodeConfig.setType == 4) {
return "发起人自选"
}else if (nodeConfig.setType == 5) {
return "发起人自己"
}else if (nodeConfig.setType == 7) {
return "连续多级主管"
}
}
}
}
</script>
<style>
</style>

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