升级ui vue3版本
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
@@ -0,0 +1,39 @@
|
||||
@font-face {
|
||||
font-family: PoetsenOne;
|
||||
src: url(../fonts/PoetsenOne.woff2) format('woff2');
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
}
|
||||
body{
|
||||
--el-dialog__wrapper-bottom: 15vh;
|
||||
--el-dialog__wrapper-top: 15vh;
|
||||
--el-dialog__body-max-height: 60vh;
|
||||
|
||||
--mb-main-color: #409EFF;
|
||||
--mb-sidebar-width: 240px;
|
||||
--mb-main-icon-color: #909399;
|
||||
--mb-header-height: 60px;
|
||||
--mb-avatar-text-size: 18px;
|
||||
}
|
||||
.el-header{
|
||||
--el-header-height: var(--mb-header-height);
|
||||
}
|
||||
.el-avatar{
|
||||
--el-avatar-text-size: var(--mb-avatar-text-size);
|
||||
--el-avatar-bg-color: var(--mb-main-color);
|
||||
border: 1px solid white;
|
||||
}
|
||||
.app-container hr {
|
||||
border: none;
|
||||
height: 1px;
|
||||
background: #F3F3F3;
|
||||
}
|
||||
.toolbar-container{
|
||||
margin-bottom: 10px
|
||||
}
|
||||
.clear{
|
||||
clear: both;
|
||||
}
|
||||
a{
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin:auto;background:#7aceff;display:block;z-index:1;position:relative" width="1920" height="1080" preserveAspectRatio="xMidYMid" viewBox="0 0 1920 1080">
|
||||
<g transform="translate(960,540) scale(1,1) translate(-960,-540)"><g transform="translate(-100 416.03196572326607) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 416.03196572326607;1920 416.03196572326607" dur="500s" repeatCount="indefinite" begin="-5.139372331517178s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(0.55)"></path>
|
||||
</g><g transform="translate(-100 596.4001678117764) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 596.4001678117764;1920 596.4001678117764" dur="500s" repeatCount="indefinite" begin="-130.3016475134896s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(0.55)"></path>
|
||||
</g><g transform="translate(-100 26.545882090192208) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 26.545882090192208;1920 26.545882090192208" dur="500s" repeatCount="indefinite" begin="-418.3960343898864s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(0.55)"></path>
|
||||
</g><g transform="translate(-100 571.769019413317) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 571.769019413317;1920 571.769019413317" dur="500s" repeatCount="indefinite" begin="-479.907328466289s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(0.55)"></path>
|
||||
</g><g transform="translate(-100 299.0294986971487) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 299.0294986971487;1920 299.0294986971487" dur="500s" repeatCount="indefinite" begin="-227.05389635543605s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(0.55)"></path>
|
||||
</g><g transform="translate(-100 963.5414388315442) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 963.5414388315442;1920 963.5414388315442" dur="500s" repeatCount="indefinite" begin="-257.66135282842185s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(0.55)"></path>
|
||||
</g><g transform="translate(-100 381.5964835751399) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 381.5964835751399;1920 381.5964835751399" dur="500s" repeatCount="indefinite" begin="-307.0477827472562s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(0.55)"></path>
|
||||
</g><g transform="translate(-100 645.4789577105932) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 645.4789577105932;1920 645.4789577105932" dur="500s" repeatCount="indefinite" begin="-202.62184460542164s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(0.55)"></path>
|
||||
</g><g transform="translate(-100 869.0735193051235) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 869.0735193051235;1920 869.0735193051235" dur="500s" repeatCount="indefinite" begin="-255.24368861928292s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(0.55)"></path>
|
||||
</g><g transform="translate(-100 361.72520676524846) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 361.72520676524846;1920 361.72520676524846" dur="500s" repeatCount="indefinite" begin="-483.79414705548476s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(0.55)"></path>
|
||||
</g><g transform="translate(-100 86.68805096063784) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 86.68805096063784;1920 86.68805096063784" dur="500s" repeatCount="indefinite" begin="-320.8829128692832s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(0.55)"></path>
|
||||
</g><g transform="translate(-100 488.9772963132972) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 488.9772963132972;1920 488.9772963132972" dur="250s" repeatCount="indefinite" begin="-170.39139507921476s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#ffffff" transform="scale(0.7)"></path>
|
||||
</g><g transform="translate(-100 4.576774043616565) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 4.576774043616565;1920 4.576774043616565" dur="250s" repeatCount="indefinite" begin="-316.15263864899003s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#ffffff" transform="scale(0.7)"></path>
|
||||
</g><g transform="translate(-100 214.60993590372678) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 214.60993590372678;1920 214.60993590372678" dur="250s" repeatCount="indefinite" begin="-398.4521377828001s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#ffffff" transform="scale(0.7)"></path>
|
||||
</g><g transform="translate(-100 1078.2795968790304) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 1078.2795968790304;1920 1078.2795968790304" dur="250s" repeatCount="indefinite" begin="-91.39136730030228s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#ffffff" transform="scale(0.7)"></path>
|
||||
</g><g transform="translate(-100 696.282800231443) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 696.282800231443;1920 696.282800231443" dur="250s" repeatCount="indefinite" begin="-9.791027044726896s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#ffffff" transform="scale(0.7)"></path>
|
||||
</g><g transform="translate(-100 561.7712540738951) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 561.7712540738951;1920 561.7712540738951" dur="250s" repeatCount="indefinite" begin="-193.89859611923643s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#ffffff" transform="scale(0.7)"></path>
|
||||
</g><g transform="translate(-100 1027.434371957216) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 1027.434371957216;1920 1027.434371957216" dur="250s" repeatCount="indefinite" begin="-208.13490311748794s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#ffffff" transform="scale(0.7)"></path>
|
||||
</g><g transform="translate(-100 723.5576871943198) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 723.5576871943198;1920 723.5576871943198" dur="250s" repeatCount="indefinite" begin="-397.71421883595184s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#ffffff" transform="scale(0.7)"></path>
|
||||
</g><g transform="translate(-100 138.14742803728817) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 138.14742803728817;1920 138.14742803728817" dur="250s" repeatCount="indefinite" begin="-468.4075429967608s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#ffffff" transform="scale(0.7)"></path>
|
||||
</g><g transform="translate(-100 1064.1245308500543) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 1064.1245308500543;1920 1064.1245308500543" dur="250s" repeatCount="indefinite" begin="-127.52573851087668s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#ffffff" transform="scale(0.7)"></path>
|
||||
</g><g transform="translate(-100 867.7256355602567) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 867.7256355602567;1920 867.7256355602567" dur="250s" repeatCount="indefinite" begin="-404.38720114464013s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#ffffff" transform="scale(0.7)"></path>
|
||||
</g><g transform="translate(-100 585.4335678795345) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 585.4335678795345;1920 585.4335678795345" dur="166.66666666666666s" repeatCount="indefinite" begin="-232.32351534757112s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#f1f2f3" transform="scale(0.85)"></path>
|
||||
</g><g transform="translate(-100 55.826384663633284) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 55.826384663633284;1920 55.826384663633284" dur="166.66666666666666s" repeatCount="indefinite" begin="-310.35651449581314s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#f1f2f3" transform="scale(0.85)"></path>
|
||||
</g><g transform="translate(-100 550.8735693730529) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 550.8735693730529;1920 550.8735693730529" dur="166.66666666666666s" repeatCount="indefinite" begin="-250.08402103222537s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#f1f2f3" transform="scale(0.85)"></path>
|
||||
</g><g transform="translate(-100 468.8701205905754) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 468.8701205905754;1920 468.8701205905754" dur="166.66666666666666s" repeatCount="indefinite" begin="-487.8153302121583s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#f1f2f3" transform="scale(0.85)"></path>
|
||||
</g><g transform="translate(-100 507.2526567834844) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 507.2526567834844;1920 507.2526567834844" dur="166.66666666666666s" repeatCount="indefinite" begin="-233.64601277250242s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#f1f2f3" transform="scale(0.85)"></path>
|
||||
</g><g transform="translate(-100 345.3058813907401) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 345.3058813907401;1920 345.3058813907401" dur="166.66666666666666s" repeatCount="indefinite" begin="-487.12855908306886s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#f1f2f3" transform="scale(0.85)"></path>
|
||||
</g><g transform="translate(-100 1031.1827146601443) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 1031.1827146601443;1920 1031.1827146601443" dur="166.66666666666666s" repeatCount="indefinite" begin="-10.720882299359303s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#f1f2f3" transform="scale(0.85)"></path>
|
||||
</g><g transform="translate(-100 18.27432163445976) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 18.27432163445976;1920 18.27432163445976" dur="166.66666666666666s" repeatCount="indefinite" begin="-239.34624845076246s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#f1f2f3" transform="scale(0.85)"></path>
|
||||
</g><g transform="translate(-100 254.00652063210185) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 254.00652063210185;1920 254.00652063210185" dur="166.66666666666666s" repeatCount="indefinite" begin="-97.67550616205378s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#f1f2f3" transform="scale(0.85)"></path>
|
||||
</g><g transform="translate(-100 853.1521302679544) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 853.1521302679544;1920 853.1521302679544" dur="166.66666666666666s" repeatCount="indefinite" begin="-215.52668400815244s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#f1f2f3" transform="scale(0.85)"></path>
|
||||
</g><g transform="translate(-100 120.29600539710484) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 120.29600539710484;1920 120.29600539710484" dur="166.66666666666666s" repeatCount="indefinite" begin="-121.43545841045444s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#f1f2f3" transform="scale(0.85)"></path>
|
||||
</g><g transform="translate(-100 739.4135223183714) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 739.4135223183714;1920 739.4135223183714" dur="125s" repeatCount="indefinite" begin="-433.4250149061785s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(1)"></path>
|
||||
</g><g transform="translate(-100 239.0788419164232) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 239.0788419164232;1920 239.0788419164232" dur="125s" repeatCount="indefinite" begin="-407.9383077190322s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(1)"></path>
|
||||
</g><g transform="translate(-100 573.1972400632184) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 573.1972400632184;1920 573.1972400632184" dur="125s" repeatCount="indefinite" begin="-456.49760543175864s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(1)"></path>
|
||||
</g><g transform="translate(-100 915.5433684236687) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 915.5433684236687;1920 915.5433684236687" dur="125s" repeatCount="indefinite" begin="-40.03727141414903s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(1)"></path>
|
||||
</g><g transform="translate(-100 824.8766336499561) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 824.8766336499561;1920 824.8766336499561" dur="125s" repeatCount="indefinite" begin="-16.039906214160293s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(1)"></path>
|
||||
</g><g transform="translate(-100 499.265059924676) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 499.265059924676;1920 499.265059924676" dur="125s" repeatCount="indefinite" begin="-35.858363803996426s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(1)"></path>
|
||||
</g><g transform="translate(-100 1022.1802798581422) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 1022.1802798581422;1920 1022.1802798581422" dur="125s" repeatCount="indefinite" begin="-190.07909527181744s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(1)"></path>
|
||||
</g><g transform="translate(-100 1062.112161307141) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 1062.112161307141;1920 1062.112161307141" dur="125s" repeatCount="indefinite" begin="-108.16335769140706s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(1)"></path>
|
||||
</g><g transform="translate(-100 249.76500277280388) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 249.76500277280388;1920 249.76500277280388" dur="125s" repeatCount="indefinite" begin="-360.72984703184443s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(1)"></path>
|
||||
</g><g transform="translate(-100 989.5257835559386) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 989.5257835559386;1920 989.5257835559386" dur="125s" repeatCount="indefinite" begin="-358.39639108375485s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(1)"></path>
|
||||
</g><g transform="translate(-100 763.1858129038242) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 763.1858129038242;1920 763.1858129038242" dur="125s" repeatCount="indefinite" begin="-57.071504489237036s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(1)"></path>
|
||||
</g><g transform="translate(-100 394.96112430266106) rotate(0)">
|
||||
<animateTransform attributeName="transform" type="translate" keyTimes="0;1" values="-100 394.96112430266106;1920 394.96112430266106" dur="125s" repeatCount="indefinite" begin="-175.51156196107908s"></animateTransform>
|
||||
<path d="M84.717,33.597c0.791-2.503,1.186-5.138,1.186-7.773C85.903,11.594,74.308,0,60.079,0 c-9.881,0-18.445,5.534-22.793,13.702c-1.581-0.527-3.426-0.791-5.138-0.791c-9.486,0-17.128,7.642-17.128,17.128 c0,1.186,0.132,2.372,0.395,3.426C6.719,34.783,0,42.424,0,51.515C0,61.66,8.169,69.829,18.314,69.829h63.373 C91.831,69.829,100,61.66,100,51.515C99.868,42.556,93.281,35.046,84.717,33.597z" fill="#bddeff" transform="scale(1)"></path>
|
||||
</g></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,206 @@
|
||||
import * as SFCCompiler from '@vue/compiler-sfc'
|
||||
|
||||
const COMP_IDENTIFIER = `__sfc__`
|
||||
|
||||
export function compileFile(filename, code, compiled) {
|
||||
if (!code.trim()) {
|
||||
compiled.errors = []
|
||||
return
|
||||
}
|
||||
|
||||
if (!filename.endsWith('.vue')) {
|
||||
compiled.js = compiled.ssr = code
|
||||
compiled.errors = []
|
||||
return
|
||||
}
|
||||
|
||||
const id = hashId(filename)
|
||||
const { errors, descriptor } = SFCCompiler.parse(code, {
|
||||
filename,
|
||||
sourceMap: true
|
||||
})
|
||||
// console.log(descriptor)
|
||||
if (errors.length) {
|
||||
compiled.errors = errors
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
(descriptor.script && descriptor.script.lang) ||
|
||||
(descriptor.scriptSetup && descriptor.scriptSetup.lang) ||
|
||||
descriptor.styles.some((s) => s.lang) ||
|
||||
(descriptor.template && descriptor.template.lang)
|
||||
) {
|
||||
compiled.errors = [
|
||||
'lang="x" pre-processors are not supported in the in-browser playground.'
|
||||
]
|
||||
return
|
||||
}
|
||||
|
||||
const hasScoped = descriptor.styles.some((s) => s.scoped)
|
||||
let clientCode = ''
|
||||
let ssrCode = ''
|
||||
|
||||
const appendSharedCode = (code) => {
|
||||
clientCode += code
|
||||
ssrCode += code
|
||||
}
|
||||
|
||||
const clientScriptResult = doCompileScript(descriptor, id, false)
|
||||
if (!clientScriptResult) {
|
||||
return
|
||||
}
|
||||
const [clientScript, bindings] = clientScriptResult
|
||||
clientCode += clientScript
|
||||
|
||||
// script ssr only needs to be performed if using <script setup> where
|
||||
// the render fn is inlined.
|
||||
if (descriptor.scriptSetup) {
|
||||
const ssrScriptResult = doCompileScript(descriptor, id, true)
|
||||
if (!ssrScriptResult) {
|
||||
return
|
||||
}
|
||||
ssrCode += ssrScriptResult[0]
|
||||
} else {
|
||||
// when no <script setup> is used, the script result will be identical.
|
||||
ssrCode += clientScript
|
||||
}
|
||||
|
||||
// template
|
||||
// only need dedicated compilation if not using <script setup>
|
||||
if (descriptor.template && !descriptor.scriptSetup) {
|
||||
const clientTemplateResult = doCompileTemplate(
|
||||
descriptor,
|
||||
id,
|
||||
bindings,
|
||||
false
|
||||
)
|
||||
if (!clientTemplateResult) {
|
||||
return
|
||||
}
|
||||
clientCode += clientTemplateResult
|
||||
|
||||
const ssrTemplateResult = doCompileTemplate(descriptor, id, bindings, true)
|
||||
if (!ssrTemplateResult) {
|
||||
return
|
||||
}
|
||||
ssrCode += ssrTemplateResult
|
||||
}
|
||||
|
||||
if (hasScoped) {
|
||||
appendSharedCode(
|
||||
`\n${COMP_IDENTIFIER}.__scopeId = ${JSON.stringify(`data-v-${id}`)}`
|
||||
)
|
||||
}
|
||||
|
||||
if (clientCode || ssrCode) {
|
||||
appendSharedCode(
|
||||
`\n${COMP_IDENTIFIER}.__file = ${JSON.stringify(filename)}` +
|
||||
`\nexport default ${COMP_IDENTIFIER}`
|
||||
)
|
||||
compiled.js = clientCode.trimStart()
|
||||
compiled.ssr = ssrCode.trimStart()
|
||||
}
|
||||
|
||||
// styles
|
||||
let css = ''
|
||||
for (const style of descriptor.styles) {
|
||||
if (style.module) {
|
||||
compiled.errors = [`<style module> is not supported in the playground.`]
|
||||
return
|
||||
}
|
||||
|
||||
const styleResult = SFCCompiler.compileStyle({
|
||||
source: style.content,
|
||||
filename,
|
||||
id,
|
||||
scoped: style.scoped,
|
||||
modules: !!style.module
|
||||
})
|
||||
if (styleResult.errors.length) {
|
||||
// postcss uses pathToFileURL which isn't polyfilled in the browser
|
||||
// ignore these errors for now
|
||||
if (!styleResult.errors[0].message.includes('pathToFileURL')) {
|
||||
compiled.errors = styleResult.errors
|
||||
}
|
||||
// proceed even if css compile errors
|
||||
} else {
|
||||
css += styleResult.code + '\n'
|
||||
}
|
||||
}
|
||||
if (css) {
|
||||
compiled.css = css.trim()
|
||||
} else {
|
||||
compiled.css = '/* No <style> tags present */'
|
||||
}
|
||||
|
||||
// clear errors
|
||||
compiled.errors = []
|
||||
}
|
||||
|
||||
function doCompileScript(descriptor, id, ssr) {
|
||||
if (descriptor.script || descriptor.scriptSetup) {
|
||||
try {
|
||||
const compiledScript = SFCCompiler.compileScript(descriptor, {
|
||||
id,
|
||||
refSugar: true,
|
||||
inlineTemplate: true,
|
||||
templateOptions: {
|
||||
ssr,
|
||||
ssrCssVars: descriptor.cssVars
|
||||
}
|
||||
})
|
||||
let code = ''
|
||||
if (compiledScript.bindings) {
|
||||
code += `\n/* Analyzed bindings: ${JSON.stringify(
|
||||
compiledScript.bindings,
|
||||
null,
|
||||
2
|
||||
)} */`
|
||||
}
|
||||
code +=
|
||||
`\n` +
|
||||
SFCCompiler.rewriteDefault(compiledScript.content, COMP_IDENTIFIER)
|
||||
// console.log( SFCCompiler.rewriteDefault(compiledScript.content, COMP_IDENTIFIER))
|
||||
return [code, compiledScript.bindings]
|
||||
} catch (e) {
|
||||
recordFileErrors([e])
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return [`\nconst ${COMP_IDENTIFIER} = {}`, undefined]
|
||||
}
|
||||
}
|
||||
|
||||
function doCompileTemplate(descriptor, id, bindingMetadata, ssr) {
|
||||
const templateResult = SFCCompiler.compileTemplate({
|
||||
source: descriptor.template && descriptor.template.content,
|
||||
filename: descriptor.filename,
|
||||
id,
|
||||
scoped: descriptor.styles.some(s => s.scoped),
|
||||
slotted: descriptor.slotted,
|
||||
ssr,
|
||||
ssrCssVars: descriptor.cssVars,
|
||||
isProd: false,
|
||||
compilerOptions: {
|
||||
bindingMetadata
|
||||
}
|
||||
})
|
||||
if (templateResult.errors.length) {
|
||||
recordFileErrors(templateResult.errors)
|
||||
return
|
||||
}
|
||||
|
||||
const fnName = ssr ? `ssrRender` : `render`
|
||||
|
||||
return (
|
||||
`\n${templateResult.code.replace(
|
||||
/\nexport (function|const) (render|ssrRender)/,
|
||||
`$1 ${fnName}`
|
||||
)}` + `\n${COMP_IDENTIFIER}.${fnName} = ${fnName}`
|
||||
)
|
||||
}
|
||||
|
||||
function hashId(filename) {
|
||||
return btoa(filename).slice(0, 8)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export const testCode = `
|
||||
<template>
|
||||
<div class="aaa">
|
||||
{{ msg }}
|
||||
<el-button type="primary" @click="$emit('xxClick')">阿斯顿发</el-button>
|
||||
{{ props.aaaaaa }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const msg = '11111'
|
||||
const props = defineProps({
|
||||
aaaaaa: String
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aaa{
|
||||
font-size: 30px;
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
@@ -0,0 +1,9 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
const components = import.meta.glob('./*/*.vue')
|
||||
export default function install (app) {
|
||||
for (const [key, value] of Object.entries(components)) {
|
||||
const name = key.substring(key.lastIndexOf('/') + 1, key.lastIndexOf('.'))
|
||||
app.component(name, defineAsyncComponent(value))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<el-button
|
||||
v-bind="el_"
|
||||
@click="buttonClick"
|
||||
>
|
||||
{{ el_.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getToken } from '@/scripts/auth'
|
||||
|
||||
export default {
|
||||
name: 'MbButton',
|
||||
props: {
|
||||
el: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
btnType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
requestMethod: {
|
||||
type: String,
|
||||
default: 'get'
|
||||
},
|
||||
requestUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
requestData: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
beforeConfirm: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
successTips: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
failTips: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: 'warning'
|
||||
},
|
||||
afterHandler: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
el_: this.el || {},
|
||||
requestMethod_: this.requestMethod,
|
||||
beforeConfirm_: this.beforeConfirm,
|
||||
successTips_: this.successTips,
|
||||
failTips_: this.failTips
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.btnType) {
|
||||
if (this.btnType === 'delete') {
|
||||
this.requestMethod_ = 'post'
|
||||
this.el_.type = 'danger'
|
||||
this.el_.text = '删除'
|
||||
this.el_.icon = 'ElDelete'
|
||||
this.beforeConfirm_ = '确定删除吗?'
|
||||
this.successTips_ = '删除成功!'
|
||||
this.failTips_ = '删除失败!'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async buttonClick() {
|
||||
if (this.beforeConfirm_) {
|
||||
this.$confirm(this.beforeConfirm_, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: this.confirmType
|
||||
}).then((res) => {
|
||||
this.buttonClickRequest().then(() => {
|
||||
this.afterHandler()
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
buttonClickRequest() {
|
||||
// var requestOptions = {}
|
||||
// requestOptions.url = this.requestUrl
|
||||
// requestOptions.method = this.requestMethod_
|
||||
// if (requestOptions.method === 'get') {
|
||||
// requestOptions.params = this.requestData
|
||||
// } else {
|
||||
// requestOptions.data = this.requestData
|
||||
// }
|
||||
if (this.isOpen) {
|
||||
return new Promise(() => {
|
||||
window.open(this.$common.getUrl(process.env.VUE_APP_BASE_API + this.requestUrl, this.requestData) + '&token=' + getToken())
|
||||
})
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.requestMethod_ === 'get') {
|
||||
this.$get(this.requestUrl, this.requestData).then(res => {
|
||||
const { data } = res
|
||||
if (data) {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: this.successTips_
|
||||
})
|
||||
} else {
|
||||
this.$message.error(this.failTips_)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
} else {
|
||||
this.$post(this.requestUrl, this.requestData).then(res => {
|
||||
const { data } = res
|
||||
if (data) {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: this.successTips_
|
||||
})
|
||||
} else {
|
||||
this.$message.error(this.failTips_)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<el-dialog :fullscreen="fullscreen" :width="width" :title="title" v-model="dialogVisible" :close-on-click-modal="false" :append-to-body="true" draggable @opened="opened">
|
||||
<slot name="content" />
|
||||
<template #footer>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<slot name="btns">
|
||||
<el-button @click="dialogVisible = false">
|
||||
关闭
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="confirmLoading" @click="confirmClick">
|
||||
确认
|
||||
</el-button>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
emits: ['confirm-click'],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '50%'
|
||||
},
|
||||
fullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
opened: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
confirmLoading: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.fullscreen) {
|
||||
document.body.style.setProperty('--el-dialog__wrapper-bottom', '0vh')
|
||||
document.body.style.setProperty('--el-dialog__wrapper-top', '0vh')
|
||||
document.body.style.setProperty('--el-dialog__body-max-height', '100vh')
|
||||
} else {
|
||||
document.body.style.setProperty('--el-dialog__wrapper-bottom', '15vh')
|
||||
document.body.style.setProperty('--el-dialog__wrapper-top', '15vh')
|
||||
document.body.style.setProperty('--el-dialog__body-max-height', '60vh')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
confirmClick() {
|
||||
this.$emit('confirm-click', this)
|
||||
},
|
||||
loading(){
|
||||
this.confirmLoading = true
|
||||
},
|
||||
hideLoading(){
|
||||
this.confirmLoading = false
|
||||
},
|
||||
show() {
|
||||
this.dialogVisible = true
|
||||
},
|
||||
hide() {
|
||||
this.dialogVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.el-dialog__wrapper{
|
||||
padding-bottom: var(--el-dialog__wrapper-bottom);
|
||||
padding-top: var(--el-dialog__wrapper-top);
|
||||
overflow: hidden;
|
||||
}
|
||||
.el-dialog__wrapper >>> .el-dialog{
|
||||
margin-top: 0vh!important;
|
||||
}
|
||||
.el-dialog__wrapper >>> .el-dialog__body{
|
||||
max-height: var(--el-dialog__body-max-height);
|
||||
overflow: auto;
|
||||
padding: 25px!important;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row style="margin-bottom: 6px">
|
||||
<el-button type="primary" @click="tableOptions.data.push({})">添加一行</el-button>
|
||||
</el-row>
|
||||
<mb-table v-bind="tableOptions">
|
||||
<template v-for="col in cols" #[col.field]="{ index }">
|
||||
<el-input v-if="col.type === 'input'" v-bind="col.properties" v-model="tableOptions.data[index][col.field]" @change="dataChange" />
|
||||
<mb-select v-else-if="col.type === 'select'" v-bind="col.properties" v-model="tableOptions.data[index][col.field]" @change="dataChange" />
|
||||
</template>
|
||||
</mb-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'MbEditorTable',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
value: {
|
||||
required: true
|
||||
},
|
||||
cols: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
showNo: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableOptions: {
|
||||
data: [],
|
||||
cols: [],
|
||||
showNo: this.showNo
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
for (var i in this.cols) {
|
||||
var col = this.cols[i]
|
||||
this.tableOptions.cols.push({
|
||||
type: 'dynamic',
|
||||
field: col.field,
|
||||
title: col.title
|
||||
})
|
||||
}
|
||||
this.tableOptions.cols.push({
|
||||
title: '操作',
|
||||
type: 'btns',
|
||||
width: 85,
|
||||
fixed: 'right',
|
||||
btns: [{
|
||||
title: '删除',
|
||||
type: 'danger',
|
||||
click: (row, index) => {
|
||||
this.tableOptions.data.splice(index, 1)
|
||||
}
|
||||
}]
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
dataChange() {
|
||||
console.log('更新')
|
||||
this.$emit('update:value', this.tableOptions.data)
|
||||
this.$emit('change', this.tableOptions.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<svg aria-hidden="true" class="mb-icon">
|
||||
<use :xlink:href="symbolId" :class="className"/>
|
||||
</svg>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
const props = defineProps({
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'mb-icon'
|
||||
},
|
||||
icon: String,
|
||||
size: String,
|
||||
});
|
||||
const symbolId = computed(() => props.icon&&props.icon.startsWith('#') ? props.icon : `#${props.prefix}-${props.icon}`)
|
||||
const className = computed(() => props.icon&&props.icon.startsWith('#') ? props.icon.substring(1) : `${props.prefix}-${props.icon}`)
|
||||
</script>
|
||||
<style scoped>
|
||||
svg {
|
||||
width: 1.3em;
|
||||
height: 1.3em;
|
||||
vertical-align: -0.25em;
|
||||
overflow: hidden;
|
||||
fill: var(--mb-main-icon-color)
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div :class="{'hidden':hidden}" class="pagination-container">
|
||||
<el-pagination
|
||||
:background="background"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:layout="layout"
|
||||
:page-sizes="pageSizes"
|
||||
:total="total"
|
||||
v-bind="$attrs"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { scrollTo } from '@/scripts/scroll-to'
|
||||
|
||||
const props = defineProps({
|
||||
total: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [10, 20, 30, 50]
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'total, sizes, prev, pager, next, jumper'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:page', 'update:limit', 'pagination'])
|
||||
|
||||
const currentPage = computed({
|
||||
get: () => props.page,
|
||||
set: (val) => {
|
||||
emit('update:page', val)
|
||||
}
|
||||
})
|
||||
|
||||
const pageSize = computed({
|
||||
get: () => props.limit,
|
||||
set: (val) => {
|
||||
emit('update:limit', val)
|
||||
}
|
||||
})
|
||||
|
||||
function handleSizeChange(val) {
|
||||
emit('pagination', { page: currentPage, limit: val })
|
||||
if (props.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
}
|
||||
|
||||
function handleCurrentChange(val) {
|
||||
emit('pagination', { page: val, limit: pageSize })
|
||||
if (props.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination-container {
|
||||
background: #fff;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
.pagination-container.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="filter-container">
|
||||
<el-form :inline="true" @keyup.enter="search">
|
||||
<el-form-item :label="it.label" v-for="(it, i) in where" :key="i">
|
||||
<el-input v-if="it.type == 'input'" @input="input(it.input)" v-model="it.value" :placeholder="it.placeholder || ('请输入' + it.label)" style="width: 200px;" class="filter-item" />
|
||||
<mb-select v-else-if="it.type == 'select'" v-model="it.value" :placeholder="'请输入' + it.label" v-bind="it.properties" />
|
||||
<el-date-picker
|
||||
v-else-if="it.type == 'date' || it.type == 'datetime' || it.type == 'daterange' || it.type == 'datetimerange'"
|
||||
v-model="it.value"
|
||||
align="right"
|
||||
:format="it.type.startsWith('datetime') ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd'"
|
||||
:value-format="it.type.startsWith('datetime') ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd'"
|
||||
:type="it.type"
|
||||
:start-placeholder="it.type.startsWith('datetime') ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd'"
|
||||
:end-placeholder="it.type.startsWith('datetime') ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd'"
|
||||
:placeholder="it.type.startsWith('datetime') ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd'"
|
||||
>
|
||||
</el-date-picker>
|
||||
<component v-else :is="it.type" v-model="it.value" v-bind="it.properties" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button class="filter-item" type="primary" icon="ElSearch" @click="search">
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button class="filter-item" icon="ElDelete" @click="reset">
|
||||
清空
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<slot name="btns" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { nextTick, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
where: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
notReset: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.where,() => {
|
||||
console.log(props.where)
|
||||
})
|
||||
|
||||
const emit = defineEmits(['search'])
|
||||
|
||||
function input(input){
|
||||
if(input){
|
||||
emit('search')
|
||||
}
|
||||
}
|
||||
|
||||
function search(){
|
||||
for(var key in props.where){
|
||||
if(props.where[key] instanceof Object){
|
||||
if(props.where[key].type.startsWith('date') && props.where[key].value instanceof Array){
|
||||
props.where[key].value = props.where[key].value.join(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
nextTick(() => {
|
||||
emit('search')
|
||||
for(var key in props.where){
|
||||
if(props.where[key] instanceof Object){
|
||||
if(props.where[key].type.startsWith('date')){
|
||||
props.where[key].value = props.where[key].value.split(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function reset() {
|
||||
for(var key in props.where){
|
||||
if(props.notReset.indexOf(key) == -1){
|
||||
if(props.where[key] instanceof Object){
|
||||
props.where[key].value = null
|
||||
}else{
|
||||
props.where[key] = null
|
||||
}
|
||||
}
|
||||
}
|
||||
nextTick(() => emit('search'))
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<el-select v-if="mbType === 'select'" v-model="selectValue" v-bind="el" :style="{ width }" :placeholder="placeholder || '请选择'" filterable clearable>
|
||||
<el-option
|
||||
v-for="item in selectList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-radio-group v-if="mbType === 'radio'" v-model="selectValue">
|
||||
<el-radio v-for="item in selectList" :key="item.value" :label="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref, watch, onMounted, getCurrentInstance } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
modelValue: {
|
||||
required: true
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
allOption: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
labelField: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
valueField: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
mbType: {
|
||||
type: String,
|
||||
default: 'select'
|
||||
},
|
||||
el: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const selectList = ref([])
|
||||
const selectValue = ref('')
|
||||
|
||||
watch(() => props.type, () => {
|
||||
if (props.modelValue instanceof Array || props.modelValue.toString().indexOf(',') !== -1) {
|
||||
selectValue.value = []
|
||||
} else {
|
||||
selectValue.value = ''
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
watch(selectValue, (value) => {
|
||||
if (props.el && props.el.multiple && value.length > 0) {
|
||||
value = value.join(',')
|
||||
}
|
||||
emit('update:modelValue', value)
|
||||
emit('change', value)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
async function loadData() {
|
||||
if (props.modelValue || props.modelValue == '') {
|
||||
if ((!(props.modelValue instanceof Array) && props.modelValue.toString().indexOf(',') !== -1)) {
|
||||
selectValue.value = props.modelValue.split(',')
|
||||
} else {
|
||||
if (props.el && props.el.multiple && !(props.modelValue instanceof Array)) {
|
||||
selectValue.value = [props.modelValue]
|
||||
} else {
|
||||
selectValue.value = props.modelValue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (props.el && props.el.multiple) {
|
||||
selectValue.value = []
|
||||
}
|
||||
}
|
||||
if (props.data && props.data.length > 0) {
|
||||
listConcat(handlerData(props.data))
|
||||
} else if (props.url) {
|
||||
proxy.$get(props.url, props.params).then(res => {
|
||||
listConcat(handlerData(res.data))
|
||||
})
|
||||
} else {
|
||||
listConcat(proxy.$common.getDictType(props.type))
|
||||
}
|
||||
}
|
||||
|
||||
function listConcat(dictData) {
|
||||
if (props.allOption) {
|
||||
selectList.value = [{
|
||||
value: '',
|
||||
label: '全部'
|
||||
}]
|
||||
selectList.value = selectList.value.concat(dictData)
|
||||
} else {
|
||||
selectList.value = dictData
|
||||
}
|
||||
}
|
||||
|
||||
function handlerData(data) {
|
||||
var newData = []
|
||||
data.forEach(it => {
|
||||
newData.push({
|
||||
label: it[props.labelField],
|
||||
value: it[props.valueField].toString()
|
||||
})
|
||||
})
|
||||
return newData
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<el-table-column
|
||||
:key="col.field"
|
||||
:label="col.title"
|
||||
:prop="col.field"
|
||||
:align="col.align || 'center'"
|
||||
:width="col.width"
|
||||
:fixed="col.fixed"
|
||||
:sortable="col.sortable"
|
||||
>
|
||||
<template v-if="!col.cols" #default="scope">
|
||||
<span v-if="col.templet" v-html="col.templet(scope.row)" />
|
||||
<span v-else-if="col.dictType">
|
||||
{{ $common.getDictLabel(col.dictType, scope.row[col.field] + '') }}
|
||||
</span>
|
||||
<slot v-else-if="col.type == 'dynamic'" :name="col.field" :row="scope.row" :index="scope.$index" />
|
||||
<el-switch
|
||||
v-else-if="col.type == 'switch'"
|
||||
v-model="scope.row[col.field]"
|
||||
:active-value="col.activeValue || 1"
|
||||
:inactive-value="col.inactiveValue || 0"
|
||||
@change="col.change(scope.row)"
|
||||
/>
|
||||
<div v-else-if="col.type == 'btns'">
|
||||
<template v-for="btn in col.btns">
|
||||
<el-button v-if="btn.if === undefined ? true : btn.if(scope.row)" :icon="btn.icon" :key="btn.title" v-permission="btn.permission" :type="btn.type" :size="btn.size || 'small'" :class="btn.class" @click="btn.click(scope.row, scope.$index)">
|
||||
{{ btn.title }}
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
<el-image v-else-if="col.type === 'image'" :src="scope.row[col.field]" :preview-src-list="[scope.row[col.field]]" />
|
||||
<span v-else-if="col.type === 'html'" v-html="scope.row[col.field]"></span>
|
||||
<span v-else-if="col.click">
|
||||
<a style="color: blue" @click="col.click(scope.row)">{{ scope.row[col.field] }}</a>
|
||||
</span>
|
||||
<span v-else-if="col.field">{{ scope.row[col.field] }}</span>
|
||||
</template>
|
||||
<mb-table-column v-for="(col2, j) in col.cols" :key="j" :col="col2">
|
||||
<template v-for="(value, key) in $slots" #[key]="{ row, index }">
|
||||
<slot :row="row" :index="index" :name="key" />
|
||||
</template>
|
||||
</mb-table-column>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
col: Object
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table
|
||||
:key="tableKey"
|
||||
v-loading="listLoading"
|
||||
:data="list"
|
||||
border
|
||||
fit
|
||||
highlight-current-row
|
||||
v-bind="el"
|
||||
style="width: 100%;"
|
||||
@sort-change="sortChange"
|
||||
@selection-change="selectionChange"
|
||||
>
|
||||
|
||||
<el-table-column v-if="selection" align="center" type="selection" width="50" />
|
||||
|
||||
<el-table-column v-if="showNo" label="序号" prop="num" align="center" width="65">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.$index+1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<mb-table-column v-for="(col, i) in cols" :key="i" :col="col">
|
||||
<template v-for="(value, key) in $slots" #[key]="{ row, index }">
|
||||
<slot :row="row" :index="index" :name="key" />
|
||||
</template>
|
||||
</mb-table-column>
|
||||
|
||||
<template empty>
|
||||
<el-empty :description="emptyText" />
|
||||
</template>
|
||||
|
||||
</el-table>
|
||||
<mb-pagination v-show="total > 0 && page" :total="total || 0" v-model:page="listCurrent" v-model:limit="limit" @pagination="handlerPagination" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch, onMounted, getCurrentInstance,defineExpose } from 'vue'
|
||||
import request from '@/scripts/request'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const props = defineProps({
|
||||
el: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
page: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
done: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
where: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
showNo: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
selection: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
method: {
|
||||
type: String,
|
||||
default: 'get'
|
||||
},
|
||||
cols: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: '暂无数据'
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['selection-change'])
|
||||
|
||||
const listCurrent = ref(1)
|
||||
const total = ref(0)
|
||||
const list = ref([])
|
||||
const listLoading = ref(false)
|
||||
const tableKey = ref(0)
|
||||
let newWhere = reactive({})
|
||||
|
||||
function renderWhere(){
|
||||
newWhere = reactive(proxy.$common.renderWhere(props.where))
|
||||
}
|
||||
|
||||
function getList() {
|
||||
renderWhere()
|
||||
listLoading.value = true
|
||||
if (props.page) {
|
||||
newWhere.current = listCurrent.value
|
||||
newWhere.size = props.limit
|
||||
} else {
|
||||
newWhere.size = 99999999
|
||||
}
|
||||
request({
|
||||
url: props.url,
|
||||
method: props.method,
|
||||
params: newWhere
|
||||
}).then(res => {
|
||||
const { data } = res
|
||||
total.value = data.total
|
||||
list.value = data.list
|
||||
listLoading.value = false
|
||||
props.done()
|
||||
})
|
||||
}
|
||||
|
||||
function sortChange(column) {
|
||||
let order = column.order
|
||||
if (order) {
|
||||
order = order === 'descending' ? 'desc' : ''
|
||||
order = column.prop + ' ' + order
|
||||
} else {
|
||||
order = null
|
||||
}
|
||||
newWhere.orderBy = order
|
||||
reloadList()
|
||||
}
|
||||
|
||||
function selectionChange(columns) {
|
||||
emit('selection-change', columns)
|
||||
}
|
||||
|
||||
function reloadList() {
|
||||
if (props.url) {
|
||||
newWhere.current = 1
|
||||
listCurrent.value = 1
|
||||
getList()
|
||||
}
|
||||
}
|
||||
|
||||
function handlerData() {
|
||||
listLoading.value = true
|
||||
total.value = props.data.length
|
||||
var currPageData = []
|
||||
props.data.forEach((it, i) => {
|
||||
if (i >= ((listCurrent.value - 1) * props.limit) && i < (listCurrent.value * props.limit) && currPageData.length < props.limit) {
|
||||
currPageData.push(it)
|
||||
}
|
||||
})
|
||||
list.value = currPageData
|
||||
props.done()
|
||||
listLoading.value = false
|
||||
}
|
||||
|
||||
function handlerPagination() {
|
||||
if (props.url) {
|
||||
getList()
|
||||
}
|
||||
if (props.data) {
|
||||
handlerData()
|
||||
}
|
||||
}
|
||||
|
||||
function keyup(){
|
||||
document.onkeyup = (e) => {
|
||||
if(e.target.nodeName != 'INPUT'){
|
||||
if (e && e.keyCode == 37) {
|
||||
if(listCurrent.value != 1){
|
||||
listCurrent.value -= 1
|
||||
handlerPagination()
|
||||
}
|
||||
} else if (e && e.keyCode == 39) {
|
||||
if(listCurrent.value != parseInt((total.value + props.limit - 1) / props.limit)){
|
||||
listCurrent.value += 1
|
||||
handlerPagination()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.data, () => {
|
||||
listCurrent.value = 1
|
||||
handlerData()
|
||||
})
|
||||
|
||||
watch(() => props.where,() => {
|
||||
renderWhere()
|
||||
},{ deep: true })
|
||||
|
||||
renderWhere()
|
||||
|
||||
onMounted(() => {
|
||||
keyup()
|
||||
if (props.data) {
|
||||
handlerData()
|
||||
}
|
||||
if (props.url) {
|
||||
getList()
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({ reloadList })
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-image >>> .el-image__inner {
|
||||
max-height: 100px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.el-table-column--selection .cell {
|
||||
padding:0px 15px!important;
|
||||
}
|
||||
.el-table th {
|
||||
background: #F5F7FA;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 5px;" v-if="expand || checked">
|
||||
<el-button v-if="expand" type="primary" icon="ElSort" plain @click="doExpand">展开/折叠</el-button>
|
||||
<el-button v-if="checked" type="primary" icon="ElCheck" plain @click="() => { treeAllChecked = !treeAllChecked; checkedAll(searchData, treeAllChecked) }">全选/全不选</el-button>
|
||||
</div>
|
||||
<div style="margin-bottom: 5px;" v-if="search">
|
||||
<el-input v-model="searchValue" placeholder="输入关键字进行过滤" @input="searchTree" :style="{ width: searchWidth }" />
|
||||
</div>
|
||||
<el-tree
|
||||
v-if="refreshTree"
|
||||
ref="tree"
|
||||
:data="searchData"
|
||||
v-bind="el"
|
||||
node-key="id"
|
||||
:default-expand-all="defaultExpandAll"
|
||||
:default-checked-keys="checkedIds"
|
||||
@check-change="checkChange"
|
||||
@node-click="nodeClick"
|
||||
:props="defaultProps"
|
||||
:style="{ 'max-height': maxHeight ? maxHeight : '100%' }"
|
||||
style="overflow: auto"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { watch, ref, reactive, defineExpose, nextTick, getCurrentInstance, onBeforeMount } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const emit = defineEmits(['update:select-values', 'check-change', 'node-click'])
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
selectValues: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxHeight: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
el: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
expand: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
search: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
searchWidth: {
|
||||
type: String,
|
||||
default: '230px'
|
||||
},
|
||||
checkedIds: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const tree = ref()
|
||||
const treeData = ref([])
|
||||
const searchData = ref([])
|
||||
const defaultProps = reactive({
|
||||
children: 'children',
|
||||
label: 'name'
|
||||
})
|
||||
const defaultExpandAll = ref(true)
|
||||
const refreshTree = ref(true)
|
||||
const treeAllChecked = ref(false)
|
||||
const searchValue = ref('')
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await loadTreeData()
|
||||
})
|
||||
|
||||
watch(() => props.selectValues, async () => {
|
||||
await loadTreeData()
|
||||
checkedAll(searchData.value, false)
|
||||
var values = props.selectValues.split(',');
|
||||
for(var i in values){
|
||||
tree.value.setChecked(values[i], true, false)
|
||||
}
|
||||
})
|
||||
|
||||
function searchTree() {
|
||||
if(searchValue.value){
|
||||
searchData.value = proxy.$treeTable.recursionSearch(['name'], proxy.$common.copyNew(treeData.value), searchValue.value, false)
|
||||
}else{
|
||||
searchData.value = treeData.value
|
||||
}
|
||||
}
|
||||
|
||||
function doExpand() {
|
||||
refreshTree.value = false
|
||||
defaultExpandAll.value = !defaultExpandAll.value
|
||||
nextTick(() => refreshTree.value = true)
|
||||
}
|
||||
|
||||
async function loadTreeData() {
|
||||
if(treeData.value.length == 0){
|
||||
await proxy.$get(props.url, props.params).then((res) => {
|
||||
treeData.value = res.data.list
|
||||
searchData.value = treeData.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getTree() {
|
||||
return tree.value
|
||||
}
|
||||
|
||||
function checkChange(node) {
|
||||
var selectMenus = []
|
||||
var checkedNodes = tree.value.getCheckedNodes(false, true)
|
||||
for (var i = 0; i < checkedNodes.length; i++) {
|
||||
selectMenus.push(checkedNodes[i].id)
|
||||
}
|
||||
emit('update:select-values', selectMenus.join(','))
|
||||
emit('check-change', selectMenus.join(','))
|
||||
}
|
||||
|
||||
function nodeClick(param1, param2, param3){
|
||||
emit('node-click', param1, param2, param3)
|
||||
}
|
||||
|
||||
function checkedAll(children, checked) {
|
||||
if (tree.value) {
|
||||
for (var i in children) {
|
||||
var id = children[i].id
|
||||
if(children[i].children && children[i].children.length > 0){
|
||||
checkedAll(children[i].children, checked)
|
||||
}
|
||||
tree.value.setChecked(id, checked, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ getTree })
|
||||
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<vue-ueditor-wrap v-model="content" :config="editorConfig" editor-id="mb-ueditor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'MbUeditor',
|
||||
emits: ['change', 'update:modelValue'],
|
||||
model: {
|
||||
prop: 'modelValue',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
content: '',
|
||||
editorConfig: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
content(value) {
|
||||
this.$emit('update:modelValue', value)
|
||||
this.$emit('change', value)
|
||||
},
|
||||
modelValue(value) {
|
||||
this.content = value
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.content = this.value
|
||||
this.editorConfig = this.config || {}
|
||||
this.editorConfig.UEDITOR_HOME_URL = this.editorConfig.UEDITOR_HOME_URL || '/UEditor/'
|
||||
this.editorConfig.serverUrl = this.editorConfig.serverUrl || import.meta.env.VITE_APP_BASE_API + 'ueditor/main'
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<el-upload
|
||||
:id="uploadDomId"
|
||||
class="upload-demo"
|
||||
:action="action"
|
||||
:headers="headers"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="handleRemove"
|
||||
:before-remove="beforeRemove"
|
||||
:multiple="multiple"
|
||||
:limit="limit"
|
||||
:on-exceed="handleExceed"
|
||||
:file-list="fileList"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:on-success="handleAvatarSuccess"
|
||||
>
|
||||
<el-button size="small" type="primary" :disabled="!multiple && fileList.length == 1">点击上传</el-button>
|
||||
<div slot="tip" class="el-upload__tip">支持上传{{ getSettingSuffixs().replaceAll(',', ',') }}文件,且不超过{{ maxFileSize }}MB</div>
|
||||
</el-upload>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getToken } from '@/scripts/auth'
|
||||
export default {
|
||||
name: 'MbUploadFile',
|
||||
emits: ['change', 'update:modelValue'],
|
||||
model: {
|
||||
prop: 'modelValue',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
required: false
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
maxFileSize: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
externalId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
externalType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
formats: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
acceptList: {
|
||||
image: 'png,jpg,gif,jpeg',
|
||||
wps: 'pdf,pptx,xls,xlsx,csv,docx,doc',
|
||||
compress: 'zip,rar,7z',
|
||||
video: 'avi,flv,mp4,mpeg,mov'
|
||||
},
|
||||
imageUrl: '',
|
||||
action: import.meta.env.VITE_APP_BASE_API + 'file/upload',
|
||||
headers: {
|
||||
token: getToken()
|
||||
},
|
||||
urls: [],
|
||||
uploadDomId: Math.random(),
|
||||
fileList: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.renderFile()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.externalId) {
|
||||
this.$get('file/files', { externalId: this.externalId, externalType: this.externalType }).then(res => {
|
||||
const { data } = res
|
||||
this.fileList = data
|
||||
})
|
||||
this.action = this.action + `?externalId=${this.externalId}&externalType=${this.externalType}`
|
||||
} else {
|
||||
this.renderFile()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
renderFile() {
|
||||
if (this.value instanceof Array && this.value.length > 0) {
|
||||
this.fileList = this.value.map(it => {
|
||||
return {
|
||||
name: it.substring(it.lastIndexOf('/') + 1),
|
||||
response: {
|
||||
data: {
|
||||
url: it
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (this.value) {
|
||||
this.fileList.push({
|
||||
name: this.value.substring(this.value.lastIndexOf('/') + 1),
|
||||
response: {
|
||||
data: {
|
||||
url: this.value
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
handleRemove(file, fileList) {
|
||||
var url = file.response.data.url
|
||||
this.urls.splice(this.urls.indexOf(url), 1)
|
||||
this.fileList.forEach((it, i) => {
|
||||
if (it && it.response.data.url.indexOf(url) !== -1) {
|
||||
this.fileList.splice(i, 1)
|
||||
}
|
||||
})
|
||||
if (this.multiple) {
|
||||
this.$emit('update:modelValue', this.urls)
|
||||
this.$emit('change', this.urls)
|
||||
} else {
|
||||
document.getElementById(this.uploadDomId).getElementsByClassName('el-upload__input')[0].removeAttribute('disabled')
|
||||
this.$emit('update:modelValue', '')
|
||||
this.$emit('change', '')
|
||||
}
|
||||
this.$get('file/delete', { url: encodeURI(url) })
|
||||
},
|
||||
handlePreview(file) {
|
||||
window.open(this.$global.filePrefix + file.response.data.url)
|
||||
},
|
||||
handleExceed(files, fileList) {
|
||||
this.$message.warning(`当前限制选择 ${this.limit} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`)
|
||||
},
|
||||
beforeRemove(file, fileList) {
|
||||
return this.$confirm(`确定移除 ${file.name}?`)
|
||||
},
|
||||
handleAvatarSuccess(res, file, fileList) {
|
||||
if (res.data) {
|
||||
if (this.multiple) {
|
||||
this.urls.push(res.data.url)
|
||||
this.$emit('update:modelValue', this.urls)
|
||||
this.$emit('change', this.urls)
|
||||
} else {
|
||||
document.getElementById(this.uploadDomId).getElementsByClassName('el-upload__input')[0].setAttribute('disabled', '')
|
||||
this.$emit('update:modelValue', res.data.url)
|
||||
this.$emit('change', res.data.url)
|
||||
}
|
||||
}
|
||||
},
|
||||
getSettingSuffixs() {
|
||||
if (this.formats) {
|
||||
return this.formats
|
||||
}
|
||||
var suffixs = this.acceptList[this.accept]
|
||||
if (!suffixs) {
|
||||
suffixs = this.getAllSuffixs()
|
||||
}
|
||||
return suffixs
|
||||
},
|
||||
beforeAvatarUpload(file, fileList) {
|
||||
var fileName = file.name
|
||||
var accepts = this.accept.split(',')
|
||||
if (accepts) {
|
||||
for (var i = 0; i < accepts.length; i++) {
|
||||
if (!this.validAccept(fileName, accepts[i])) {
|
||||
this.$message.error('上传文件格式只能为:' + this.getSettingSuffixs().replaceAll(',', ','))
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!this.validAccept(fileName, 'null')) {
|
||||
this.$message.error('上传文件格式只能为:' + this.getAllSuffixs().replaceAll(',', ','))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const isLt2M = file.size / 1024 / 1024 < this.maxFileSize
|
||||
if (!isLt2M) {
|
||||
this.$message.error(`上传文件大小不能超过 ${this.maxFileSize}MB!`)
|
||||
return isLt2M
|
||||
}
|
||||
},
|
||||
getAllSuffixs() {
|
||||
var suffixs = ''
|
||||
for (const key in this.acceptList) {
|
||||
suffixs += this.acceptList[key] + ','
|
||||
}
|
||||
suffixs = suffixs.substring(0, suffixs.length - 1)
|
||||
return suffixs
|
||||
},
|
||||
validAccept(fileName, accept) {
|
||||
if (this.formats) {
|
||||
return this.validEndsWith(fileName, this.formats)
|
||||
}
|
||||
if (accept && this.acceptList[accept]) {
|
||||
return this.validEndsWith(fileName, this.acceptList[accept])
|
||||
} else {
|
||||
return this.validEndsWith(fileName, this.getAllSuffixs())
|
||||
}
|
||||
},
|
||||
validEndsWith(fileName, suffixs) {
|
||||
suffixs = suffixs.split(',')
|
||||
for (var i = 0; i < suffixs.length; i++) {
|
||||
const suffix = suffixs[i]
|
||||
if (fileName.toLowerCase().endsWith('.' + suffix)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,367 @@
|
||||
<template>
|
||||
<div>
|
||||
<vuedraggable
|
||||
v-model="urls"
|
||||
class="vue-draggable"
|
||||
tag="div"
|
||||
draggable=".draggable-item"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div
|
||||
class="draggable-item"
|
||||
:style="{ width: width.replace('px', '') + 'px', height: height.replace('px', '') + 'px' }"
|
||||
>
|
||||
<el-image
|
||||
:src="$global.filePrefix + element"
|
||||
:preview-src-list="[$global.filePrefix + element]"
|
||||
/>
|
||||
<div class="tools">
|
||||
<div class="shadow" @click="handleRemove(element)">
|
||||
<el-icon>
|
||||
<ElDelete />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="shadow" @click="beforeCropper(element)">
|
||||
<el-icon>
|
||||
<ElScissor />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-upload
|
||||
v-if="(!multiple && urls.length == 0) || (multiple && urls.length < limit)"
|
||||
ref="uploadRef"
|
||||
class="uploadBox"
|
||||
:style="{ width: width.replace('px', '') + 'px', height: height.replace('px', '') + 'px' }"
|
||||
:action="action"
|
||||
:file-list="fileList"
|
||||
:headers="headers"
|
||||
accept=".jpg,.jpeg,.png,.gif"
|
||||
:show-file-list="false"
|
||||
:multiple="multiple"
|
||||
:limit="limit"
|
||||
:on-success="handleAvatarSuccess"
|
||||
:on-exceed="onExceed"
|
||||
>
|
||||
<el-icon class="uploadIcon">
|
||||
<ElPlus />
|
||||
<span v-show="isUploading" class="uploading">正在上传...</span>
|
||||
<span
|
||||
v-if="!isUploading && limit && limit!==99 && multiple"
|
||||
class="limitTxt"
|
||||
>最多{{ limit }}张</span>
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</template>
|
||||
</vuedraggable>
|
||||
<mb-dialog ref="cropperDialog" @confirm-click="cropper">
|
||||
<template #content>
|
||||
<div class="cropper-content">
|
||||
<div class="cropper" style="text-align:center">
|
||||
<vueCropper
|
||||
ref="cropper"
|
||||
v-bind="cropperOption"
|
||||
:outputSize="cropperOption.outputSize === undefined ? 0.8 : cropperOption.outputSize"
|
||||
:outputType="cropperOption.outputType === undefined ? 'jpeg' : cropperOption.outputType"
|
||||
:canMove="cropperOption.canMove === undefined ? true : cropperOption.canMove"
|
||||
:canMoveBox="cropperOption.canMoveBox === undefined ? true : cropperOption.canMoveBox"
|
||||
:autoCrop="cropperOption.autoCrop === undefined ? true : cropperOption.autoCrop"
|
||||
:centerBox="cropperOption.centerBox === undefined ? true : cropperOption.centerBox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</mb-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'vue-cropper/dist/index.css'
|
||||
import { VueCropper } from 'vue-cropper'
|
||||
import vuedraggable from 'vuedraggable'
|
||||
import { getToken } from '@/scripts/auth'
|
||||
|
||||
export default {
|
||||
name: 'MbUploadImage',
|
||||
emits: ['update:modelValue', 'change'],
|
||||
components: { vuedraggable, VueCropper },
|
||||
model: {
|
||||
prop: 'modelValue',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
required: false
|
||||
},
|
||||
externalId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
externalType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
cropperConfig: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '100'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
action: import.meta.env.VITE_APP_BASE_API + 'file/upload',
|
||||
headers: {
|
||||
token: getToken()
|
||||
},
|
||||
dialogImageUrl: '',
|
||||
dialogVisible: false,
|
||||
disabled: false,
|
||||
isUploading: false,
|
||||
cropperOption: {},
|
||||
urls: [],
|
||||
fileList: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(newValue) {
|
||||
if (newValue instanceof Array) {
|
||||
this.urls = newValue
|
||||
this.fileList = this.urls.map(it => { return { response: { data: { url: it }}} })
|
||||
} else {
|
||||
if (newValue && this.urls.length === 0) {
|
||||
this.urls.push(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.cropperOption = this.cropperConfig || {}
|
||||
this.cropperOption.img = ''
|
||||
if (this.externalId) {
|
||||
this.$get('file/files', { externalId: this.externalId, externalType: this.externalType }).then(res => {
|
||||
this.urls = res.data
|
||||
})
|
||||
this.action = this.action + `?externalId=${this.externalId}&externalType=${this.externalType}`
|
||||
} else {
|
||||
if (this.modelValue instanceof Array) {
|
||||
this.urls = this.modelValue
|
||||
this.fileList = this.urls.map(it => { return { response: { data: { url: it }}} })
|
||||
} else {
|
||||
if (this.modelValue) {
|
||||
this.urls.push(this.modelValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRemove(url) {
|
||||
this.urls.splice(this.urls.indexOf(url), 1)
|
||||
this.fileList.forEach((it, i) => {
|
||||
if (it && it.response.data.url.indexOf(url) !== -1) {
|
||||
this.fileList.splice(i, 1)
|
||||
}
|
||||
})
|
||||
this.$get('file/delete', { url: encodeURI(url) })
|
||||
if (this.multiple) {
|
||||
this.$emit('update:modelValue', this.urls)
|
||||
this.$emit('change', this.urls)
|
||||
} else {
|
||||
this.$emit('update:modelValue', '')
|
||||
this.$emit('change', '')
|
||||
}
|
||||
},
|
||||
handlePictureCardPreview(file) {
|
||||
this.dialogImageUrl = file.url
|
||||
this.dialogVisible = true
|
||||
},
|
||||
handleDownload(file) {
|
||||
console.log(file)
|
||||
},
|
||||
handleAvatarSuccess(res, file, fileList) {
|
||||
this.fileList = fileList
|
||||
if (res.data) {
|
||||
this.urls.push(res.data.url)
|
||||
console.log(this.urls)
|
||||
if (this.multiple) {
|
||||
this.$emit('update:modelValue', this.urls)
|
||||
this.$emit('change', this.urls)
|
||||
} else {
|
||||
this.$emit('update:modelValue', res.data.url)
|
||||
this.$emit('change', res.data.url)
|
||||
}
|
||||
this.onDragEnd()
|
||||
} else {
|
||||
this.$message({ type: 'error', message: res.msg })
|
||||
}
|
||||
this.isUploading = false
|
||||
},
|
||||
onDragEnd() {
|
||||
var newUrls = []
|
||||
this.urls.forEach(url => {
|
||||
newUrls.push(encodeURI(url))
|
||||
})
|
||||
this.$get('file/resort', { urls: newUrls.join(',') })
|
||||
},
|
||||
onExceed() {
|
||||
this.$message({
|
||||
type: 'warning',
|
||||
message: `图片超限,最多可上传${this.limit}张图片`
|
||||
})
|
||||
},
|
||||
beforeCropper(url) {
|
||||
this.cropperOption.img = this.$global.filePrefix + url
|
||||
this.cropperOption.relativeImg = url
|
||||
this.$refs.cropperDialog.show()
|
||||
},
|
||||
cropper() {
|
||||
this.$refs.cropper.getCropBlob((data) => {
|
||||
var dataFile = new File([data], this.cropperOption.relativeImg.substring(this.cropperOption.relativeImg.lastIndexOf('/') + 1), { type: data.type, lastModified: Date.now() })
|
||||
var formData = new FormData()
|
||||
formData.append('file', dataFile)
|
||||
formData.append('url', encodeURI(this.cropperOption.relativeImg))
|
||||
this.$request({
|
||||
url: 'file/cropper',
|
||||
method: 'post',
|
||||
data: formData
|
||||
}).then(res => {
|
||||
this.urls.forEach((it, i) => {
|
||||
if (this.cropperOption.img.indexOf(it) !== -1) {
|
||||
this.urls[i] = res.data.url
|
||||
this.$refs.cropperDialog.hide()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.vue-draggable >>> .el-upload {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 上传按钮
|
||||
.uploadIcon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed #c0ccda;
|
||||
background-color: #fbfdff;
|
||||
border-radius: 6px;
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
|
||||
.limitTxt,
|
||||
.uploading {
|
||||
position: absolute;
|
||||
bottom: 10%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽
|
||||
.vue-draggable {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.draggable-item {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.el-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.tools {
|
||||
position: absolute;
|
||||
top:0px;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
}
|
||||
.shadow {
|
||||
display: inline-block;
|
||||
background-color: rgba(0,0,0,.5);
|
||||
opacity: 0;
|
||||
transition: opacity .3s;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
&:hover {
|
||||
.shadow {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.hideShadow {
|
||||
.shadow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.single {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.draggable-item {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
&.maxHidden {
|
||||
.uploadBox {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
// el-image
|
||||
.el-image-viewer__wrapper {
|
||||
.el-image-viewer__mask {
|
||||
opacity: .8;
|
||||
}
|
||||
.el-icon-circle-close {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.cropper-content {
|
||||
.cropper {
|
||||
width: auto;
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641017183540" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5317" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M259.2 0H896a64 64 0 0 1 64 64v748.608a64 64 0 0 1-64 64H259.2a64 64 0 0 1-64-64V64a64 64 0 0 1 64-64z m132.8 183.04V640h171.264c76.096 0 133.12-20.48 172.544-61.44 37.376-39.04 56.384-94.72 56.384-167.04 0-72.96-19.008-128.64-56.384-167.04-39.36-40.96-96.448-61.44-172.544-61.44H392z m76.8 64h80c58.432 0 101.056 12.8 128 39.04 26.24 25.6 39.296 67.84 39.296 125.44 0 56.32-13.12 97.92-39.36 124.8-26.88 26.24-69.504 39.68-127.936 39.68h-80V247.04zM64 384h75.968v568.512h580.096V1024H128a64 64 0 0 1-64-64V384z" p-id="5318"></path></svg>
|
||||
|
After Width: | Height: | Size: 915 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641017365938" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6713" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M922.317764 0H101.64469A100.312307 100.312307 0 0 0 1.634036 100.010654V923.989346a100.312307 100.312307 0 0 0 100.010654 100.010654h820.673074a100.299739 100.299739 0 0 0 99.998085-100.010654V100.06093A100.299739 100.299739 0 0 0 922.317764 0zM427.933063 402.04107l-177.321894 103.064894 177.321894 97.094672v81.257871L179.684925 534.831388v-57.477532l248.248138-160.529857z m70.938812 364.661192h-53.970812l78.643542-509.303973h53.958243z m345.393085-231.845736L596.004253 683.445938v-81.257871l177.321894-97.094672-177.321894-103.064894v-85.217071l248.260707 160.542426z" p-id="6714"></path></svg>
|
||||
|
After Width: | Height: | Size: 977 B |
@@ -0,0 +1 @@
|
||||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>
|
||||
|
After Width: | Height: | Size: 944 B |
@@ -0,0 +1 @@
|
||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M38.47 52L52 38.462l-23.648-23.67L43.209 0H.035L0 43.137l14.757-14.865L38.47 52zm74.773 47.726L89.526 76 76 89.536l23.648 23.672L84.795 128h43.174L128 84.863l-14.757 14.863zM89.538 52l23.668-23.648L128 43.207V.038L84.866 0 99.73 14.76 76 38.472 89.538 52zM38.46 76L14.792 99.651 0 84.794v43.173l43.137.033-14.865-14.757L52 89.53 38.46 76z"/></svg>
|
||||
|
After Width: | Height: | Size: 421 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641017648725" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19289" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M555.541333 117.994667l312.874667 224.565333A117.333333 117.333333 0 0 1 917.333333 437.866667V800c0 64.8-52.533333 117.333333-117.333333 117.333333H640V746.666667c0-70.688-57.312-128-128-128s-128 57.312-128 128v170.666666H224c-64.8 0-117.333333-52.533333-117.333333-117.333333V437.877333a117.333333 117.333333 0 0 1 48.917333-95.317333l312.874667-224.565333a74.666667 74.666667 0 0 1 87.082666 0z" p-id="19290"></path></svg>
|
||||
|
After Width: | Height: | Size: 803 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641017024921" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2935" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M892.928 128q28.672 0 48.64 19.968t19.968 48.64l0 52.224q0 28.672-19.968 48.64t-48.64 19.968l-759.808 0q-28.672 0-48.64-19.968t-19.968-48.64l0-52.224q0-28.672 19.968-48.64t48.64-19.968l759.808 0zM892.928 448.512q28.672 0 48.64 19.968t19.968 48.64l0 52.224q0 28.672-19.968 48.64t-48.64 19.968l-759.808 0q-28.672 0-48.64-19.968t-19.968-48.64l0-52.224q0-28.672 19.968-48.64t48.64-19.968l759.808 0zM892.928 769.024q28.672 0 48.64 19.968t19.968 48.64l0 52.224q0 28.672-19.968 48.64t-48.64 19.968l-759.808 0q-28.672 0-48.64-19.968t-19.968-48.64l0-52.224q0-28.672 19.968-48.64t48.64-19.968l759.808 0z" p-id="2936"></path></svg>
|
||||
|
After Width: | Height: | Size: 997 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641720023979" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15481" width="240" height="240" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M960 1024h-320v-384h128V576H256v64h128v384H0v-384h128V448h320V384H320V0h384v384H576v64h320v192h128v384h-64zM128 896h128v-128H128v128zM576 128H448v128h128V128z m320 640h-128v128h128v-128z" p-id="15482"></path></svg>
|
||||
|
After Width: | Height: | Size: 592 B |
@@ -0,0 +1 @@
|
||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>
|
||||
|
After Width: | Height: | Size: 623 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641017706517" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20110" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M792.703177 534.452774c-49.288352 33.217439-108.257215 52.515189-171.718341 52.515189-63.271312 0-122.050361-19.171208-171.1489-52.135561-132.996298 65.042909-224.866244 203.290726-224.866244 363.303875 0 169.314031 793.232441 166.972993 793.232441 0C1018.138862 737.7435 926.079103 599.305869 792.703177 534.452774L792.703177 534.452774 792.703177 534.452774zM354.359526 270.99103c0-149.636653 119.329695-270.99103 266.62531-270.99103 147.295615 0 266.62531 121.291105 266.62531 270.99103 0 149.636653-119.329695 270.864488-266.62531 270.864488C473.68922 541.855518 354.359526 420.627684 354.359526 270.99103L354.359526 270.99103 354.359526 270.99103 354.359526 270.99103zM177.959107 912.182508c0 6.200589 0.94907 12.084821 2.783938 17.905781C80.584558 913.827562 5.797867 881.749007 5.797867 833.409724c0-138.500902 79.468768-258.146954 194.622556-314.458422 40.177283 27.01685 88.010395 43.340849 139.576515 45.049174C241.040605 646.696082 177.959107 771.97328 177.959107 912.182508L177.959107 912.182508 177.959107 912.182508zM310.006336 270.99103c0-68.775916 21.638789-132.426856 58.399421-184.309332C361.952083 86.112256 355.561681 85.859171 349.108007 85.859171c-121.227834 0-219.488182 99.842131-219.488182 223.031376s98.197077 223.031376 219.424911 223.031376c27.269936 0 53.400988-5.061705 77.444086-14.362588C355.498409 459.666083 310.006336 370.76989 310.006336 270.99103L310.006336 270.99103zM310.006336 270.99103" p-id="20111"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M124.884 109.812L94.256 79.166c-.357-.357-.757-.629-1.129-.914a50.366 50.366 0 0 0 8.186-27.59C101.327 22.689 78.656 0 50.67 0 22.685 0 0 22.688 0 50.663c0 27.989 22.685 50.663 50.656 50.663 10.186 0 19.643-3.03 27.6-8.201.286.385.557.771.9 1.114l30.628 30.632a10.633 10.633 0 0 0 7.543 3.129c2.728 0 5.457-1.043 7.543-3.115 4.171-4.157 4.171-10.915.014-15.073M50.671 85.338C31.557 85.338 16 69.78 16 50.663c0-19.102 15.557-34.661 34.67-34.661 19.115 0 34.657 15.559 34.657 34.675 0 19.102-15.557 34.661-34.656 34.661"/></svg>
|
||||
|
After Width: | Height: | Size: 600 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641015979047" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2139" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M519.9 647.4c-44.8 7.4-90.4-6.9-122.5-38.3-32.1-31.4-46.6-76.2-39-120 9.4-56.8 56.2-102.7 114.2-112.1 44.9-7.6 90.7 6.7 122.9 38.2 32.2 31.6 46.7 76.5 38.9 120.4-9.7 56.9-56.5 102.6-114.5 111.8M848.4 559l66-40.4c10.5-6.4 15.6-18.6 12.9-30.4L895 349.3c-2.7-11.8-12.8-20.6-25.1-21.9l-77.5-8.4c-7.7-0.8-14.7-4.6-19.5-10.5l-13-16c-4.8-5.9-7-13.5-6.1-21l8.9-75.7c1.4-12-5.1-23.6-16.2-28.9l-131-61.8c-11.1-5.2-24.4-3-33.2 5.5l-55 54c-5.4 5.3-12.8 8.4-20.6 8.4H486c-7.7 0-15.1-3-20.6-8.4l-55-54c-8.7-8.6-22-10.8-33.2-5.5l-130.9 61.8c-11.1 5.3-17.6 16.8-16.2 28.9l8.9 75.8c0.9 7.5-1.3 15-6.1 21L220 308.4c-4.8 5.9-11.8 9.7-19.5 10.5l-77.5 8.5c-12.3 1.3-22.3 10.1-25.1 21.9L65.5 488.1c-2.7 11.8 2.4 24 12.9 30.4l66 40.5c6.5 4 11.2 10.4 12.9 17.8l4.6 19.9c1.7 7.4 0.4 15.1-3.7 21.5l-41.6 64.5c-6.6 10.2-5.8 23.4 1.9 32.9l90.7 111.3c7.7 9.5 20.7 13.1 32.3 9.1l73.5-25.3c7.3-2.5 15.3-2.1 22.2 1.2l18.7 8.8c7 3.3 12.3 9.1 14.8 16.3l25.6 72c4.1 11.4 15.1 19.1 27.4 19.1H569c12.4 0 23.4-7.7 27.4-19.1l25.6-72c2.5-7.1 7.9-13 14.8-16.3l18.7-8.8c6.9-3.3 14.9-3.7 22.2-1.2l73.5 25.3c11.7 4 24.6 0.4 32.3-9.1l90.7-111.3c7.7-9.5 8.4-22.7 1.9-32.9l-41.6-64.5c-4.1-6.4-5.5-14.1-3.8-21.5l4.6-19.8c1.9-7.5 6.5-13.9 13.1-17.9" fill="" p-id="2140"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>
|
||||
|
After Width: | Height: | Size: 440 B |
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<el-container class="container">
|
||||
<el-header>
|
||||
<div class="title">
|
||||
{{ $global.title }}
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="avatar">
|
||||
<el-avatar :size="40" fit="contain" :src="$global.filePrefix + $global.user.info.headPortrait">
|
||||
{{ !$global.user.info.headPortrait ? $global.user.info.name.substring(0,1) : '' }}
|
||||
</el-avatar>
|
||||
<el-dropdown>
|
||||
<span class="username">{{ $global.user.info.username }}</span>
|
||||
<el-icon class="el-icon--right" color="white">
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<router-link to="/user-center">
|
||||
<el-dropdown-item>个人中心</el-dropdown-item>
|
||||
</router-link>
|
||||
<el-dropdown-item @click="logout">退出</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-container class="main-container">
|
||||
<el-aside width="var(--mb-sidebar-width)">
|
||||
<sidebar />
|
||||
</el-aside>
|
||||
<el-container>
|
||||
<el-header class="tabs">
|
||||
<tabs />
|
||||
</el-header>
|
||||
<div class="main-box">
|
||||
<el-main>
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive>
|
||||
<component v-if="$route.meta.keepAlive" :is="Component" :key="$route.path" />
|
||||
</keep-alive>
|
||||
<component v-if="!$route.meta.keepAlive" :is="Component" :key="$route.path" />
|
||||
</router-view>
|
||||
</el-main>
|
||||
</div>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ArrowDown } from '@element-plus/icons-vue'
|
||||
import Tabs from './tabs.vue'
|
||||
import Sidebar from './sidebar/sidebar.vue'
|
||||
import { logout } from '@/scripts/auth'
|
||||
import { getCurrentInstance } from 'vue'
|
||||
const { proxy } = getCurrentInstance()
|
||||
console.log(proxy.$global.user.info.headPortrait)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container{
|
||||
height: 100%;
|
||||
}
|
||||
.main-container{
|
||||
height: calc(100% - var(--mb-header-height));
|
||||
}
|
||||
.title{
|
||||
font-family: PoetsenOne;
|
||||
width: var(--mb-sidebar-width);
|
||||
line-height: 60px;
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
padding-left: 20px;
|
||||
box-sizing: border-box;
|
||||
float: left;
|
||||
}
|
||||
.header-right{
|
||||
float: right;
|
||||
height: 100%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.header-right .username{
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
margin: 0px 5px;
|
||||
}
|
||||
.header-right .avatar{
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.header-right .avatar :deep(.el-avatar--circle){
|
||||
align-items: center;
|
||||
}
|
||||
.container > .el-header{
|
||||
background: #409EFF;
|
||||
padding: 0px;
|
||||
}
|
||||
.tabs{
|
||||
margin-left: -21px;
|
||||
height: 40px;
|
||||
}
|
||||
.tabs :deep(.el-tabs__header){
|
||||
margin: 0px;
|
||||
}
|
||||
.main-box{
|
||||
padding: 10px;
|
||||
background-color: #F0F2F5;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
.el-main{
|
||||
border-radius: 10px;
|
||||
background-color: white;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive>
|
||||
<component v-if="$route.meta.keepAlive" :is="Component" :key="$route.path" />
|
||||
</keep-alive>
|
||||
<component v-if="!$route.meta.keepAlive" :is="Component" :key="$route.path" />
|
||||
</router-view>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<mb-icon v-if="icon" :icon="icon" style="margin-right: 5px;"/>
|
||||
<span v-if="title" slot='title'>{{ title }}</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
icon: String,
|
||||
title: String
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-menu-item.is-active svg{
|
||||
fill: var(--mb-main-color);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
<template>
|
||||
<a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
|
||||
<slot />
|
||||
</a>
|
||||
<router-link v-else :to="to">
|
||||
<slot />
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { isExternal } from '@/scripts/validate'
|
||||
|
||||
const props = defineProps({
|
||||
to: String
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div v-if="!item.hidden">
|
||||
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
|
||||
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
|
||||
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
|
||||
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
|
||||
</el-menu-item>
|
||||
</app-link>
|
||||
</template>
|
||||
|
||||
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
|
||||
<template #title>
|
||||
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
|
||||
</template>
|
||||
<sidebar-item
|
||||
v-for="child in item.children"
|
||||
:key="child.path"
|
||||
:is-nest="true"
|
||||
:item="child"
|
||||
:base-path="resolvePath(child.path)"
|
||||
class="nest-menu"
|
||||
/>
|
||||
</el-sub-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import path from 'path-browserify'
|
||||
import { isExternal } from '@/scripts/validate'
|
||||
import AppLink from './link.vue'
|
||||
import Item from './item.vue'
|
||||
|
||||
export default {
|
||||
name: 'SidebarItem',
|
||||
components: { Item, AppLink },
|
||||
props: {
|
||||
// route object
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isNest: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
basePath: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
|
||||
// TODO: refactor with render function
|
||||
this.onlyOneChild = null
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
hasOneShowingChild(children = [], parent) {
|
||||
const showingChildren = children.filter(item => {
|
||||
if (item.hidden) {
|
||||
return false
|
||||
} else {
|
||||
// Temp set(will be used if only has one showing child)
|
||||
this.onlyOneChild = item
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
// When there is only one child router, the child router is displayed by default
|
||||
if (showingChildren.length === 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Show parent if there are no child router to display
|
||||
if (showingChildren.length === 0) {
|
||||
this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
resolvePath(routePath) {
|
||||
if (isExternal(routePath)) {
|
||||
return routePath
|
||||
}
|
||||
if (isExternal(this.basePath)) {
|
||||
return this.basePath
|
||||
}
|
||||
return path.resolve(this.basePath, routePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
class="aside-menu"
|
||||
>
|
||||
<sidebar-item v-for="(route,index) in permissionRoutes" :key="route.path + index" :item="route" :base-path="route.path" />
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SidebarItem from './sidebar-item.vue'
|
||||
import { getCurrentInstance, computed } from 'vue'
|
||||
const { proxy } = getCurrentInstance()
|
||||
const permissionRoutes = proxy.$global.user.permissionRoutes
|
||||
const activeMenu = computed(() => {
|
||||
const route = proxy.$route
|
||||
const { meta, path } = route
|
||||
// if set path, the sidebar will highlight the path you set
|
||||
if (meta.activeMenu) {
|
||||
return meta.activeMenu
|
||||
}
|
||||
return path
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aside-menu{
|
||||
height: 100%;
|
||||
}
|
||||
.aside-menu :deep(a){
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<el-tabs
|
||||
v-model="tabValue"
|
||||
type="card"
|
||||
class="demo-tabs"
|
||||
@tab-click="openTab"
|
||||
@tab-remove="removeTab"
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="(view, key) in global.visitedViews" :key="key"
|
||||
:label="view.name"
|
||||
:name="view.path"
|
||||
:closable="!(view.path == '/home')"
|
||||
>
|
||||
<template #label>
|
||||
<el-dropdown trigger="contextmenu">
|
||||
<div class="el-tabs__item is-top is-closable is-focus" style="padding: 0px;" v-if="$route.path != view.path">
|
||||
{{ view.name }}
|
||||
</div>
|
||||
<div class="el-tabs__item is-top is-active is-closable is-focus" style="padding: 0px;" v-else>
|
||||
{{ view.name }}
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="refresh(view.path)">刷新</el-dropdown-item>
|
||||
<el-dropdown-item @click="close('left', view.path)">关闭左侧</el-dropdown-item>
|
||||
<el-dropdown-item @click="close('right', view.path)">关闭右侧</el-dropdown-item>
|
||||
<el-dropdown-item @click="close('other', view.path)">关闭其他</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance, ref, watch } from 'vue'
|
||||
import global from '@/scripts/global.js'
|
||||
const { proxy } = getCurrentInstance()
|
||||
const tabValue = ref(proxy.$route.path)
|
||||
watch(global.tabValue, () => {
|
||||
tabValue.value = global.tabValue.value
|
||||
})
|
||||
function openTab(item){
|
||||
proxy.$router.push({
|
||||
path: item.props.name
|
||||
})
|
||||
}
|
||||
function removeTab(path){
|
||||
if(global.visitedViews.length == 1){
|
||||
global.visitedViews.splice(0, 1)
|
||||
proxy.$router.push({
|
||||
path: '/home'
|
||||
})
|
||||
}else{
|
||||
global.visitedViews.forEach((it, i) => {
|
||||
if(it.path == path){
|
||||
global.visitedViews.splice(i, 1)
|
||||
proxy.$router.push({
|
||||
path: global.visitedViews[global.visitedViews.length - 1].path
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
function refresh(path){
|
||||
proxy.$router.replace({
|
||||
path: `/redirect${path}`
|
||||
})
|
||||
}
|
||||
function close(type, path){
|
||||
if(type == 'other'){
|
||||
for(var i = global.visitedViews.length - 1; i >= 0; i--){
|
||||
if(global.visitedViews[i].path != path){
|
||||
global.visitedViews.splice(i, 1)
|
||||
}
|
||||
}
|
||||
}else if(type == 'right'){
|
||||
for(var i = global.visitedViews.length - 1; i >= 0; i--){
|
||||
if(global.visitedViews[i].path != path){
|
||||
global.visitedViews.splice(i, 1)
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
for(var i = 0; i < global.visitedViews.length; i++){
|
||||
if(global.visitedViews[i].path != path){
|
||||
global.visitedViews.splice(i, 1)
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
proxy.$router.push({
|
||||
path: path
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as vue from 'vue'
|
||||
const libs = { vue }
|
||||
window.___magic__import__ = function(lib, name){
|
||||
return (libs[lib] || {})[name]
|
||||
}
|
||||
|
||||
import { createApp } from 'vue'
|
||||
const app = createApp(App)
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import '@/assets/css/common.css'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import 'vite-plugin-svg-icons/register'
|
||||
import App from './App.vue'
|
||||
import router from './scripts/router'
|
||||
import components from '@/components/index'
|
||||
import globalProperties from './scripts/globalProperties'
|
||||
import hasPermission from './scripts/hasPermission'
|
||||
|
||||
import '@/permission'
|
||||
import global from '@/scripts/global.js'
|
||||
document.title = global.title
|
||||
router.beforeEach((to, from) => {
|
||||
global.tabValue.value = to.path
|
||||
if((to.name && global.visitedViews.length === 0 || global.visitedViews.every(it => it.path !== to.path)) && !to.path.startsWith('/redirect')){
|
||||
global.visitedViews.push(to)
|
||||
}
|
||||
return true
|
||||
})
|
||||
app.use(globalProperties)
|
||||
app.use(hasPermission)
|
||||
app.use(components)
|
||||
app.use(ElementPlus, {
|
||||
// size: 'small',
|
||||
locale: zhCn
|
||||
})
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,77 @@
|
||||
import router from './scripts/router'
|
||||
import { getUserInfo, removeToken } from './scripts/auth'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import NProgress from 'nprogress' // progress bar
|
||||
import 'nprogress/nprogress.css' // progress bar style
|
||||
import { getToken } from '@/scripts/auth' // get token from cookie
|
||||
import global from '@/scripts/global'
|
||||
import common from '@/scripts/common'
|
||||
import { generateRoutes } from '@/scripts/routerPermission'
|
||||
|
||||
NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
||||
|
||||
const whiteList = ['/login'] // no redirect whitelist
|
||||
|
||||
router.beforeEach(async(to, from, next) => {
|
||||
// start progress bar
|
||||
NProgress.start()
|
||||
|
||||
// determine whether the user has logged in
|
||||
const hasToken = getToken()
|
||||
|
||||
if (hasToken) {
|
||||
if (to.path === '/login') {
|
||||
// if is logged in, redirect to the home page
|
||||
next({ path: '/' })
|
||||
NProgress.done()
|
||||
} else {
|
||||
// determine whether the user has obtained his permission roles through getInfo
|
||||
if (global.user.authorities.length > 0) {
|
||||
next()
|
||||
} else {
|
||||
try {
|
||||
// get user info
|
||||
// note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
|
||||
await getUserInfo()
|
||||
await common.getDictData()
|
||||
await common.loadConfig()
|
||||
// generate accessible routes map based on roles
|
||||
await generateRoutes().then(accessRoutes => {
|
||||
global.user.permissionRoutes.push(...accessRoutes)
|
||||
accessRoutes.forEach(it => {
|
||||
router.addRoute(it)
|
||||
})
|
||||
})
|
||||
// dynamically add accessible routes
|
||||
// hack method to ensure that addRoutes is complete
|
||||
// set the replace: true, so the navigation will not leave a history record
|
||||
next({ ...to, replace: true })
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
// remove token and go to login page to re-login
|
||||
removeToken()
|
||||
ElMessage.error(error.data || 'Has Error')
|
||||
next(`/login`)
|
||||
NProgress.done()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* has no token*/
|
||||
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
// in the free login whitelist, go directly
|
||||
next()
|
||||
} else {
|
||||
// other pages that do not have permission to access are redirected to the login page.
|
||||
next(`/login`)
|
||||
NProgress.done()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
router.afterEach(() => {
|
||||
// finish progress bar
|
||||
NProgress.done()
|
||||
})
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import global from './global'
|
||||
import request from '@/scripts/request'
|
||||
const TokenKey = 'magic_boot_token'
|
||||
|
||||
export function getToken() {
|
||||
return localStorage.getItem(TokenKey);
|
||||
}
|
||||
|
||||
export function setToken(token) {
|
||||
localStorage.setItem(TokenKey, token);
|
||||
}
|
||||
|
||||
export function removeToken() {
|
||||
localStorage.removeItem(TokenKey);
|
||||
global.user = {
|
||||
token: '',
|
||||
authorities: [],
|
||||
info: {},
|
||||
permissionRoutes: []
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserInfo() {
|
||||
await request({
|
||||
url: 'user/info',
|
||||
method: 'get'
|
||||
}).then(response => {
|
||||
const { data } = response
|
||||
if(data){
|
||||
var authorities_ = []
|
||||
for (var i = 0; i < data.authorities.length; i++) {
|
||||
authorities_.push(data.authorities[i])
|
||||
}
|
||||
global.user.authorities = authorities_
|
||||
global.user.info = data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function login(data){
|
||||
return new Promise((resolve, reject) => {
|
||||
request({
|
||||
url: 'security/login',
|
||||
method: 'post',
|
||||
data
|
||||
}).then(res => {
|
||||
var token = res.data
|
||||
setToken(token)
|
||||
resolve(token)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function logout(){
|
||||
request({
|
||||
url: 'security/logout',
|
||||
method: 'get'
|
||||
}).then(() => {
|
||||
removeToken()
|
||||
location.reload()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import request from '@/scripts/request'
|
||||
import { ElMessageBox, ElNotification } from 'element-plus'
|
||||
import global from '@/scripts/global'
|
||||
|
||||
const common = {}
|
||||
|
||||
let dictData = []
|
||||
common.getDictData = async function() {
|
||||
await request({
|
||||
url: 'dict/items/all',
|
||||
method: 'get'
|
||||
}).then((response) => {
|
||||
const { data } = response
|
||||
dictData = data
|
||||
})
|
||||
}
|
||||
|
||||
common.getDictType = (type) => {
|
||||
return dictData.filter(it => it.type === type)
|
||||
}
|
||||
|
||||
common.getDictLabel = (type, value) => {
|
||||
var values = []
|
||||
value.split(',').forEach(v => {
|
||||
const list = dictData.filter(it => it.type === type && it.value === v + '')
|
||||
values.push(list && list[0] && list[0].label || '')
|
||||
})
|
||||
return values.join(',')
|
||||
}
|
||||
|
||||
common.handleDelete = (options) => {
|
||||
const url = options.url
|
||||
const id = options.id
|
||||
ElMessageBox.confirm('此操作将永久删除该数据, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
request({
|
||||
url: url,
|
||||
method: 'post',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
}).then(() => {
|
||||
ElNotification({
|
||||
title: '成功',
|
||||
message: '删除成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
options && options.done()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const formatJson = (list, filterVal) => {
|
||||
return list.map(v => filterVal.map(j => {
|
||||
return v[j]
|
||||
}))
|
||||
}
|
||||
|
||||
common.renderWhere = (where) => {
|
||||
var newWhere = {}
|
||||
for(var key in where) {
|
||||
if(where[key] instanceof Object){
|
||||
newWhere[key] = where[key].value
|
||||
}else{
|
||||
newWhere[key] = where[key]
|
||||
}
|
||||
}
|
||||
return newWhere
|
||||
}
|
||||
|
||||
common.exportExcel = (options) => {
|
||||
var where = options.where || {}
|
||||
where = common.renderWhere(where)
|
||||
where.current = 1
|
||||
where.size = 99999999
|
||||
const url = options.url
|
||||
const headers = options.headers
|
||||
const columns = options.columns
|
||||
request({
|
||||
url: url,
|
||||
method: 'post',
|
||||
params: where
|
||||
}).then(res => {
|
||||
import('@/vendor/Export2Excel').then(excel => {
|
||||
const data = formatJson(res.data, columns)
|
||||
excel.export_json_to_excel({
|
||||
header: headers,
|
||||
data,
|
||||
filename: 'table-list'
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
common.handlerTreeData = (data, id, pid, sort, pidVal) => {
|
||||
var treeData = []
|
||||
var addChildren = (it) => {
|
||||
var children = data.filter(d => d[pid] === it[id])
|
||||
if (children && children.length > 0) {
|
||||
children.sort((a, b) => {
|
||||
return a[sort] - b[sort]
|
||||
})
|
||||
it.children = children
|
||||
children.forEach(chi => {
|
||||
addChildren(chi)
|
||||
})
|
||||
}
|
||||
}
|
||||
data.sort((a, b) => {
|
||||
return a[sort] - b[sort]
|
||||
})
|
||||
data.filter(it => it[pid] === pidVal).forEach(it => {
|
||||
addChildren(it)
|
||||
treeData.push(it)
|
||||
})
|
||||
return treeData
|
||||
}
|
||||
|
||||
common.uuid = () => {
|
||||
function S4() {
|
||||
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
|
||||
}
|
||||
return (S4()+S4()+S4()+S4()+S4()+S4()+S4()+S4());
|
||||
}
|
||||
|
||||
common.objAssign = (obj1, obj2, exclude) => {
|
||||
exclude = exclude || ''
|
||||
for (var o1 in obj1) {
|
||||
for (var o2 in obj2) {
|
||||
if (o1 === o2) {
|
||||
if(exclude.indexOf(o1) == -1){
|
||||
obj1[o1] = obj2[o2]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
common.copyNew = (obj) => {
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
}
|
||||
|
||||
common.getParam = (data) => {
|
||||
let url = ''
|
||||
for (var k in data) {
|
||||
const value = data[k] !== undefined ? data[k] : ''
|
||||
url += `&${k}=${encodeURIComponent(value)}`
|
||||
}
|
||||
return url ? url.substring(1) : ''
|
||||
}
|
||||
|
||||
common.getUrl = (url, data) => {
|
||||
url += (url.indexOf('?') < 0 ? '?' : '') + common.getParam(data)
|
||||
return url
|
||||
}
|
||||
|
||||
common.loadConfig = async() => {
|
||||
await request({
|
||||
url: 'config/list'
|
||||
}).then(res => {
|
||||
const { data } = res
|
||||
global.filePrefix = data.filePrefix
|
||||
})
|
||||
}
|
||||
|
||||
export default common
|
||||
@@ -0,0 +1,14 @@
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
export default {
|
||||
title: 'Magic Boot',
|
||||
user: {
|
||||
token: '',
|
||||
authorities: [],
|
||||
info: {},
|
||||
permissionRoutes: []
|
||||
},
|
||||
filePrefix: '',
|
||||
visitedViews: reactive([]),
|
||||
tabValue: ref('')
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as PlusIcons from '@element-plus/icons-vue'
|
||||
import VueUeditorWrap from 'vue-ueditor-wrap'
|
||||
import request from './request'
|
||||
import global from './global'
|
||||
import common from './common'
|
||||
import treeTable from './treeTable'
|
||||
|
||||
const install = (app) => {
|
||||
app.config.globalProperties.$request = request
|
||||
app.config.globalProperties.$post = (url, data) => request.post(url, data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
transformRequest: [data => Object.keys(data).map(it => encodeURIComponent(it) + '=' + encodeURIComponent(data[it] === null || data[it] === undefined ? '' : data[it])).join('&')]
|
||||
})
|
||||
app.config.globalProperties.$postJson = (url, data) => request.post(url, JSON.stringify(data), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
app.config.globalProperties.$get = (url, data) => request({ url, params: data })
|
||||
app.config.globalProperties.$global = global
|
||||
app.config.globalProperties.$common = common
|
||||
app.config.globalProperties.$treeTable = treeTable
|
||||
for(var key in PlusIcons) {
|
||||
app.component(`El${key}`, PlusIcons[key])
|
||||
}
|
||||
app.use(VueUeditorWrap)
|
||||
}
|
||||
export default install
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
const hasPermission = {
|
||||
install(app) {
|
||||
app.directive('permission', {
|
||||
beforeMount(el, binding) {
|
||||
if (binding.value) {
|
||||
const permissionList = app.config.globalProperties.$global.user.authorities
|
||||
if (permissionList && permissionList.length && !permissionList.includes(binding.value) && import.meta.env.NODE_ENV != 'preview') {
|
||||
el.style.display = 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default hasPermission
|
||||
@@ -0,0 +1,115 @@
|
||||
import axios from 'axios'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { logout, login } from '@/scripts/auth'
|
||||
import { getToken } from '@/scripts/auth'
|
||||
import global from '@/scripts/global'
|
||||
|
||||
// create an axios instance
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API, // url = base url + request url
|
||||
// withCredentials: true, // send cookies when cross-domain requests
|
||||
timeout: 1000 * 60 // request timeout
|
||||
})
|
||||
|
||||
// request interceptor
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
// do something before request is sent
|
||||
|
||||
if (getToken()) {
|
||||
// let each request carry token
|
||||
// ['X-Token'] is a custom headers key
|
||||
// please modify it according to the actual situation
|
||||
config.headers['token'] = getToken()
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
// do something with request error
|
||||
console.log(error) // for debug
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
var currentMessage
|
||||
// response interceptor
|
||||
service.interceptors.response.use(
|
||||
/**
|
||||
* If you want to get http information such as headers or status
|
||||
* Please return response => response
|
||||
*/
|
||||
|
||||
/**
|
||||
* Determine the request status by custom code
|
||||
* Here is just an example
|
||||
* You can also judge the status by HTTP Status Code
|
||||
*/
|
||||
response => {
|
||||
if (response.config.url.indexOf('user/info') !== -1 && response.data.code === 402) {
|
||||
logout()
|
||||
}
|
||||
return new Promise((reslove, reject) => {
|
||||
const res = response.data
|
||||
if (res.code !== 200) {
|
||||
var duration = 5
|
||||
if (res.code === 402) {
|
||||
duration = 1
|
||||
ElMessageBox.prompt(`当前账号:${global.user.info.username}凭证已过期,请输入密码重新登录`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '退出',
|
||||
inputType: 'password',
|
||||
closeOnClickModal: false,
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
login({
|
||||
username: global.user.info.username,
|
||||
password: instance.inputValue
|
||||
}).then((res) => {
|
||||
if (res) {
|
||||
done()
|
||||
service(response.config).then(ret => reslove(ret))
|
||||
}
|
||||
})
|
||||
} else if (action === 'cancel') {
|
||||
logout()
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (res.code !== 402) {
|
||||
if(currentMessage){
|
||||
currentMessage.close()
|
||||
}
|
||||
if(res.code == 403 && import.meta.env.NODE_ENV == 'preview'){
|
||||
res.message = '演示模式,不允许操作!'
|
||||
}
|
||||
currentMessage = ElMessage({
|
||||
message: res.message || 'Error',
|
||||
type: 'error',
|
||||
duration: duration * 1000,
|
||||
showClose: true
|
||||
})
|
||||
reject(res)
|
||||
}
|
||||
} else {
|
||||
reslove(res)
|
||||
}
|
||||
})
|
||||
},
|
||||
error => {
|
||||
// console.log('err' + error) // for debug
|
||||
if(currentMessage){
|
||||
currentMessage.close()
|
||||
}
|
||||
currentMessage = ElMessage({
|
||||
message: error.message,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default service
|
||||
@@ -0,0 +1,67 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
|
||||
import Layout from '@/layout/layout.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/redirect',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '/redirect/:path(.*)',
|
||||
component: () => import('@/views/redirect/index')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
redirect: '/home',
|
||||
children: [
|
||||
{
|
||||
name: '首页',
|
||||
path: '/home',
|
||||
component: () => import('@/views/home.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
path: '/user-center',
|
||||
redirect: '/system/user/user-center',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [{
|
||||
path: '/system/user/user-center',
|
||||
name: '个人中心',
|
||||
component: () => import('@/views/system/user/user-center'),
|
||||
meta: { title: '个人中心' }
|
||||
}]
|
||||
},
|
||||
|
||||
{
|
||||
path: '/login',
|
||||
component: () => import('@/views/login.vue'),
|
||||
hidden: true
|
||||
},
|
||||
// {
|
||||
// path: '/404',
|
||||
// name: '404',
|
||||
// component: () => import('@/views/404.vue'),
|
||||
// hidden: true
|
||||
// },
|
||||
// {
|
||||
// path: '/:pathMatch(.*)*',
|
||||
// redirect: '/404',
|
||||
// hidden: true
|
||||
// }
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,55 @@
|
||||
import request from '@/scripts/request'
|
||||
import common from '@/scripts/common'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
export const filterAsyncRouter = (routers, level) => {
|
||||
level = level || 0
|
||||
const accessedRouters = routers.filter(router => {
|
||||
if (router.isShow === 1) {
|
||||
if (router.componentName) {
|
||||
router.component = loadView(`/common/parse-component`)
|
||||
router.props = {
|
||||
name: router.componentName,
|
||||
code: router.code
|
||||
}
|
||||
} else if (router.component) {
|
||||
const component = router.component
|
||||
if (component === 'Layout') {
|
||||
router.path = "/" + common.uuid()
|
||||
router.component = level > 0 ? import(`../layout/none.vue`) : loadLayoutView(component)
|
||||
} else {
|
||||
router.path = router.path = router.path.startsWith('/') ? router.path : '/' + router.path
|
||||
router.component = loadView(component)
|
||||
}
|
||||
}
|
||||
if (router.children && router.children.length) {
|
||||
router.children = filterAsyncRouter(router.children, level + 1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
return accessedRouters
|
||||
}
|
||||
|
||||
export const loadLayoutView = (view) => {
|
||||
return import(`../layout/layout.vue`)
|
||||
}
|
||||
|
||||
export const loadView = (view) => {
|
||||
view = view.substring(0, 1) === '/' ? view : '/' + view
|
||||
return defineAsyncComponent(() => import(`../views${view}.vue`))
|
||||
}
|
||||
|
||||
export function generateRoutes(){
|
||||
return new Promise((resolve, reject) => {
|
||||
request({
|
||||
url: 'menu/current/menus',
|
||||
method: 'post'
|
||||
}).then(response => {
|
||||
const { data } = response
|
||||
const asyncRouter = filterAsyncRouter(data)
|
||||
resolve(asyncRouter)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
Math.easeInOutQuad = function(t, b, c, d) {
|
||||
t /= d / 2
|
||||
if (t < 1) {
|
||||
return c / 2 * t * t + b
|
||||
}
|
||||
t--
|
||||
return -c / 2 * (t * (t - 2) - 1) + b
|
||||
}
|
||||
|
||||
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
|
||||
var requestAnimFrame = (function() {
|
||||
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
|
||||
})()
|
||||
|
||||
/**
|
||||
* Because it's so fucking difficult to detect the scrolling element, just move them all
|
||||
* @param {number} amount
|
||||
*/
|
||||
function move(amount) {
|
||||
document.documentElement.scrollTop = amount
|
||||
document.body.parentNode.scrollTop = amount
|
||||
document.body.scrollTop = amount
|
||||
}
|
||||
|
||||
function position() {
|
||||
return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} to
|
||||
* @param {number} duration
|
||||
* @param {Function} callback
|
||||
*/
|
||||
export function scrollTo(to, duration, callback) {
|
||||
const start = position()
|
||||
const change = to - start
|
||||
const increment = 20
|
||||
let currentTime = 0
|
||||
duration = (typeof (duration) === 'undefined') ? 500 : duration
|
||||
var animateScroll = function() {
|
||||
// increment the time
|
||||
currentTime += increment
|
||||
// find the value with the quadratic in-out easing function
|
||||
var val = Math.easeInOutQuad(currentTime, start, change, duration)
|
||||
// move the document.body
|
||||
move(val)
|
||||
// do the animation unless its over
|
||||
if (currentTime < duration) {
|
||||
requestAnimFrame(animateScroll)
|
||||
} else {
|
||||
if (callback && typeof (callback) === 'function') {
|
||||
// the animation is done so lets callback
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
animateScroll()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
const svgs = import.meta.glob('../icons/*.svg')
|
||||
const svgNames = []
|
||||
for (const [key, value] of Object.entries(svgs)) {
|
||||
svgNames.push(key.substring(key.lastIndexOf('/') + 1, key.lastIndexOf('.')))
|
||||
}
|
||||
export default svgNames
|
||||
@@ -0,0 +1,96 @@
|
||||
import common from "@/scripts/common";
|
||||
|
||||
const treeTable = {}
|
||||
|
||||
treeTable.isChildren = (children, id) => {
|
||||
var result = false
|
||||
for(var i in children) {
|
||||
var chi = children[i]
|
||||
if(chi.id == id){
|
||||
result = true
|
||||
}
|
||||
if(chi.children && children.length > 0){
|
||||
if(treeTable.isChildren(chi.children, id)){
|
||||
result = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
treeTable.queryChildren = (children, id) => {
|
||||
var result = []
|
||||
for(var i in children){
|
||||
var chi = children[i]
|
||||
if(chi.id == id){
|
||||
if(chi.children && chi.children.length > 0){
|
||||
result = chi.children
|
||||
}
|
||||
}else{
|
||||
var qc = treeTable.queryChildren(chi.children, id)
|
||||
if(qc.length > 0){
|
||||
result = qc
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
treeTable.genTree = (children) => {
|
||||
var treeData = []
|
||||
for(var i in children){
|
||||
var chi = {}
|
||||
chi.label = children[i].name
|
||||
chi.id = children[i].id
|
||||
if(children[i].children && children[i].children.length > 0){
|
||||
chi.children = treeTable.genTree(children[i].children)
|
||||
}
|
||||
treeData.push(chi)
|
||||
}
|
||||
return treeData
|
||||
}
|
||||
|
||||
treeTable.deleteEmptyChildren = (children) => {
|
||||
for(var i in children){
|
||||
var chi = children[i]
|
||||
if(chi.children && chi.children.length == 0){
|
||||
delete chi.children
|
||||
}else{
|
||||
treeTable.deleteEmptyChildren(chi.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
treeTable.recursionSearch = (fields, data, text, html) => {
|
||||
html = html != undefined ? html : true
|
||||
var searchData = []
|
||||
for(var i in data){
|
||||
var treeNode = data[i]
|
||||
var children = treeNode.children
|
||||
if(children && children.length > 0){
|
||||
var childrenSearch = treeTable.recursionSearch(fields, children, text, html)
|
||||
treeNode.children = childrenSearch && childrenSearch.length > 0 ? childrenSearch : treeNode.children
|
||||
treeTable.treeNodeReplace(fields, searchData, treeNode, text, childrenSearch, html)
|
||||
}else{
|
||||
treeTable.treeNodeReplace(fields, searchData, treeNode, text, null, html)
|
||||
}
|
||||
}
|
||||
return searchData
|
||||
}
|
||||
|
||||
treeTable.treeNodeReplace = (fields, searchData, treeNode, text, childrenSearch, html) => {
|
||||
var exist = false
|
||||
fields.forEach((f) => {
|
||||
if(treeNode[f] && treeNode[f].indexOf(text) != -1){
|
||||
if(html){
|
||||
treeNode[f] = treeNode[f].replace(text, `<font color="#FAA353">${text}</font>`)
|
||||
}
|
||||
exist = true
|
||||
}
|
||||
})
|
||||
if(exist || (childrenSearch && childrenSearch.length > 0)){
|
||||
searchData.push(treeNode)
|
||||
}
|
||||
}
|
||||
|
||||
export default treeTable
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Created by PanJiaChen on 16/11/18.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function isExternal(path) {
|
||||
return /^(https?:|mailto:|tel:)/.test(path)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function validUsername(str) {
|
||||
const valid_map = ['admin', 'editor']
|
||||
return valid_map.indexOf(str.trim()) >= 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function validURL(url) {
|
||||
const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
|
||||
return reg.test(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function validLowerCase(str) {
|
||||
const reg = /^[a-z]+$/
|
||||
return reg.test(str)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function validUpperCase(str) {
|
||||
const reg = /^[A-Z]+$/
|
||||
return reg.test(str)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function validAlphabets(str) {
|
||||
const reg = /^[A-Za-z]+$/
|
||||
return reg.test(str)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} email
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function validEmail(email) {
|
||||
const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
return reg.test(email)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function isString(str) {
|
||||
if (typeof str === 'string' || str instanceof String) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} arg
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function isArray(arg) {
|
||||
if (typeof Array.isArray === 'undefined') {
|
||||
return Object.prototype.toString.call(arg) === '[object Array]'
|
||||
}
|
||||
return Array.isArray(arg)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
404
|
||||
</template>
|
||||
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<el-form
|
||||
ref="dataForm"
|
||||
:rules="rules"
|
||||
:model="temp"
|
||||
v-bind="form.el"
|
||||
:label-position="form.el.labelPosition === undefined ? 'right' : form.el.labelPosition"
|
||||
:label-width="form.el.labelWidth === undefined ? '120px' : form.el.labelWidth"
|
||||
:style="form.el.style === undefined ? '' : form.el.style"
|
||||
>
|
||||
<el-row v-for="(row,i) in form.rows" :key="i" :gutter="row.gutter">
|
||||
<el-col v-for="(col,j) in row.cols" :key="j" :span="col.span" v-bind="col.colEl">
|
||||
<el-form-item :label="col.label" :label-width="col.labelWidth" :prop="col.name" v-bind="col.formItemEl">
|
||||
<el-switch
|
||||
v-if="col.type === 'switch'"
|
||||
v-model="temp[col.name]"
|
||||
:active-value="col.activeValue"
|
||||
:inactive-value="col.inactiveValue"
|
||||
/>
|
||||
<el-checkbox-group v-else-if="col.type === 'checkboxGroup'" v-model="temp[col.name]" size="small">
|
||||
<el-checkbox v-for="checkboxIt in temp['_'+col.name]" :key="checkboxIt[col.defaultValue.value]" :label="checkboxIt[col.defaultValue.value]">{{ checkboxIt[col.defaultValue.text] }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<mb-select v-else-if="col.type === 'select'" v-model="temp[col.name]" v-bind="col.el" />
|
||||
<el-input v-else v-model="temp[col.name]" :type="col.type" :value="col.defaultValue" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'CommonForm',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
detail: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rules: {
|
||||
},
|
||||
temp: this.getTemp()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(newVal) {
|
||||
this.temp = this.getTemp()
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.form.el = this.form.el || {}
|
||||
this.form.rows.forEach(row => {
|
||||
row.cols.forEach(col => {
|
||||
if (col.rule) {
|
||||
this.$set(this.rules, col.name, col.rule)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
getTemp() {
|
||||
var _temp = {}
|
||||
this.form.rows.forEach(row => {
|
||||
row.cols.forEach(col => {
|
||||
if (col.type === 'checkboxGroup') {
|
||||
_temp[col.name] = []
|
||||
this.$request({
|
||||
url: col.defaultValue.request.url,
|
||||
method: col.defaultValue.request.method
|
||||
}).then(res => {
|
||||
const { data } = res
|
||||
this.$set(this.temp, '_' + col.name, data)
|
||||
})
|
||||
} else {
|
||||
_temp[col.name] = col.defaultValue || ''
|
||||
}
|
||||
})
|
||||
})
|
||||
return _temp
|
||||
},
|
||||
save() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$post(this.form.request.url, this.temp).then(res => {
|
||||
this.dialogVisible = false
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: (!this.temp.id ? '创建' : '修改') + '成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.$emit('reload-table')
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getDetail(row) {
|
||||
this.temp.id = row.id
|
||||
this.$get(this.detail.request.url, { id: row.id }).then(res => {
|
||||
const { data } = res
|
||||
for (var t in this.temp) {
|
||||
if (data[t] && (!this.detail.excludeAssign || this.detail.excludeAssign.indexOf(t) === -1)) {
|
||||
this.$set(this.temp, t, data[t])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="filter-container">
|
||||
<el-form :inline="true">
|
||||
<el-form-item v-for="col in queryOptions.cols" :key="col.field" :label="col.title">
|
||||
<el-input v-if="col.type === 'input'" v-model="tableOptions.where[col.field]" v-bind="col.el" :placeholder="'请输入'+col.title" :style="col.style" />
|
||||
<mb-select v-else-if="col.type === 'select'" v-model="tableOptions.where[col.field]" v-bind="col.el" width="100%" />
|
||||
</el-form-item>
|
||||
<el-form-item v-for="(btn, i) in queryOptions.btns" :key="i">
|
||||
<el-button v-if="btn.type == 'query'" class="filter-item" type="primary" icon="el-icon-search" @click="reloadTable">
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button v-if="btn.type == 'export'" :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleDownload">
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<template v-if="toolOptions && toolOptions.btns">
|
||||
<hr>
|
||||
<el-row class="tool-row">
|
||||
<div v-for="(btn, i) in toolOptions.btns" :key="'toolBtn' + i">
|
||||
<el-button v-if="btn.btnType == 'add'" class="filter-item" type="primary" icon="el-icon-edit" @click="handleCreate">
|
||||
添加
|
||||
</el-button>
|
||||
<mb-button
|
||||
v-else
|
||||
:el="btn.el || {}"
|
||||
:request-url="btn.request.url"
|
||||
:request-data="toolBtnData[i]"
|
||||
:btn-type="btn.btnType"
|
||||
:before-confirm="btn.beforeConfirm"
|
||||
:is-open="btn.isOpen"
|
||||
:success-tips="btn.successTips"
|
||||
:fail-tips="btn.failTips"
|
||||
:after-handler="btn.afterHandler"
|
||||
/>
|
||||
</div>
|
||||
</el-row>
|
||||
<hr>
|
||||
</template>
|
||||
|
||||
<mb-table ref="table" v-bind="tableOptions" @selection-change="selectionChange" />
|
||||
|
||||
<mb-dialog :params="formParams" v-bind="formConfig && formConfig.dialog" :visible.sync="dialogFormVisible" @confirm-click="$refs.inputForm.save()">
|
||||
<template #content>
|
||||
<common-form ref="inputForm" v-bind="formConfig" :visible.sync="dialogFormVisible" :dialog-status="dialogStatus" @reload-table="reloadTable" />
|
||||
</template>
|
||||
</mb-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CommonForm from './form'
|
||||
|
||||
var _this
|
||||
export default {
|
||||
name: 'CommonList',
|
||||
components: { CommonForm },
|
||||
props: {
|
||||
formConfig: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
queryOptions: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
toolOptions: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
tableOptions: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formParams: {},
|
||||
formComponent: '',
|
||||
dialogFormVisible: false,
|
||||
dialogStatus: 'create',
|
||||
downloadLoading: false,
|
||||
selectionData: [],
|
||||
toolBtnData: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
_this = this
|
||||
if (this.toolOptions && this.toolOptions.btns) {
|
||||
this.toolOptions.btns.forEach((it, i) => {
|
||||
if (it.btnType) {
|
||||
if (it.btnType === 'delete') {
|
||||
it.afterHandler = () => {
|
||||
_this.reloadTable()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
this.tableOptions.where = this.tableOptions.where || _this.$set(this.tableOptions, 'where', {})
|
||||
this.queryOptions.cols.forEach(it => this.$set(_this.tableOptions.where, it.field, ''))
|
||||
this.tableOptions.cols.forEach(it => {
|
||||
if (it.type === 'switch') {
|
||||
it.change = (row) => {
|
||||
this.$request(this.handlerRequest(it, row, {
|
||||
id: row.id,
|
||||
[it.field]: row[it.field]
|
||||
}))
|
||||
}
|
||||
}
|
||||
if (it.type === 'btns') {
|
||||
it.btns.forEach(btn => {
|
||||
btn.click = (row) => {
|
||||
if (btn.btnType === 'delete') {
|
||||
this.$common.handleDelete({
|
||||
url: btn.request.url,
|
||||
id: row.id,
|
||||
done: () => this.reloadTable()
|
||||
})
|
||||
} else if (btn.btnType === 'update') {
|
||||
this.handleUpdate(row)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
selectionChange(columns) {
|
||||
if (this.toolOptions && this.toolOptions.btns) {
|
||||
this.toolOptions.btns.forEach((it, i) => { it && it.request && it.request.data && this.$set(this.toolBtnData, i, ({ ...it.request.data })) })
|
||||
this.toolBtnData.forEach((it, i) => Object.keys(it).filter(key => it[key] === 'rowField').forEach(key => { this.$set(this.toolBtnData[i], key, columns.map(it => it[key]).join(',')) }))
|
||||
}
|
||||
},
|
||||
handlerRequest(it, row, defaultData) {
|
||||
var requestOptions = {}
|
||||
requestOptions.url = it.request.url
|
||||
requestOptions.method = it.request.method || 'get'
|
||||
|
||||
var requestData = {}
|
||||
if (!it.request.data && defaultData) {
|
||||
requestData = defaultData
|
||||
} else {
|
||||
requestData = it.request.data
|
||||
for (var d in it.request.data) {
|
||||
var value = requestData[d]
|
||||
if (value === 'rowField') {
|
||||
requestData[d] = row[d]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (requestOptions.method === 'get') {
|
||||
requestOptions.params = requestData
|
||||
} else {
|
||||
requestOptions.data = requestData
|
||||
}
|
||||
return requestOptions
|
||||
},
|
||||
reloadTable() {
|
||||
this.$refs.table.reloadList()
|
||||
},
|
||||
handleCreate() {
|
||||
this.dialogStatus = 'create'
|
||||
this.dialogFormVisible = true
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.dialogStatus = 'update'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['inputForm'].getDetail(row)
|
||||
})
|
||||
},
|
||||
handleDownload() {
|
||||
this.$common.exportExcel({
|
||||
url: this.tableOptions.url,
|
||||
headers: this.queryOptions.btns.filter(it => it.type === 'export')[0].headers,
|
||||
columns: this.queryOptions.btns.filter(it => it.type === 'export')[0].columns,
|
||||
where: this.tableOptions.where
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tool-row {
|
||||
margin-bottom: 6px
|
||||
}
|
||||
.tool-row > div {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,49 @@
|
||||
<script setup>
|
||||
import { babelParse } from '@vue/compiler-sfc'
|
||||
import { compileFile } from '@/compiler/sfc-compiler.js'
|
||||
import { getCurrentInstance } from '@vue/runtime-core'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
code: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
var compiled = {}
|
||||
compileFile('TestCode.vue', props.code, compiled)
|
||||
var code = compiled.js
|
||||
// console.log(code)
|
||||
var ast = babelParse(code, {
|
||||
sourceType: 'module'
|
||||
})
|
||||
var replaceCode = (node, subCode) => code.substring(0, node.start) + subCode + code.substring(node.end);
|
||||
for(var i = ast.program.body.length - 1; i>=0; i--){
|
||||
var node = ast.program.body[i]
|
||||
if(node.type === 'ImportDeclaration'){
|
||||
code = replaceCode(node, node.specifiers.map(it => `const ${it.local?.name || it.imported?.name || '*'} = ___magic__import__('${node.source.value}', '${it.imported?.name || '*'}');`).join('\r\n'));
|
||||
} else if (node.type === 'ExportDefaultDeclaration'){
|
||||
code = replaceCode(node, `return ${node.declaration.name}`)
|
||||
}
|
||||
}
|
||||
code = `(function(){
|
||||
${code}
|
||||
})()`
|
||||
var componentStyle = document.createElement("style");
|
||||
componentStyle.innerHTML = compiled.css
|
||||
document.head.appendChild(componentStyle);
|
||||
console.log(getCurrentInstance())
|
||||
getCurrentInstance().appContext.app.component(props.name, eval(code))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="props.name"></component>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="dashboard-container">
|
||||
<div class="dashboard-text">欢迎</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'name'
|
||||
])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dashboard {
|
||||
&-container {
|
||||
margin: 30px;
|
||||
background: white;
|
||||
}
|
||||
&-text {
|
||||
font-size: 30px;
|
||||
line-height: 46px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div style="padding: 50px;">
|
||||
<el-button type="primary" @click="getData" style="margin-bottom: 10px">获取数据</el-button>
|
||||
<mb-editor-table v-model="tableDatas" :cols="cols" :show-no="false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'EditorTable',
|
||||
data() {
|
||||
return {
|
||||
tableDatas: [],
|
||||
cols: [{
|
||||
type: 'input',
|
||||
field: 'name',
|
||||
title: '名称'
|
||||
}, {
|
||||
type: 'select',
|
||||
field: 'role',
|
||||
title: '角色',
|
||||
properties: {
|
||||
type: 'dict_type'
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getData() {
|
||||
console.log(this.tableDatas)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div class="app-container" style="padding:0;width:100%;height: calc(100vh - 85px);">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div style="padding: 50px;">
|
||||
<h2>多选</h2>
|
||||
<el-button type="primary" @click="getData" style="margin-bottom: 10px">获取数据</el-button>
|
||||
<mb-select v-model="dictType" type="dict_type" :el="{ multiple: true }" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'SelectExample',
|
||||
data() {
|
||||
return {
|
||||
dictType: '0,1'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getData() {
|
||||
console.log(this.dictType)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-select v-for="(data, i) in dataList" :key="i" v-model="dataValues[i]" :placeholder="'请选择' + (i+1) + '级'" @change="selectChange">
|
||||
<el-option
|
||||
v-for="item in data"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
var dataList = reactive([[], [], []])
|
||||
var dataValues = reactive([])
|
||||
var threeLinkageData = reactive([{
|
||||
"id": 1,
|
||||
"name": "1",
|
||||
"pid": 0,
|
||||
"level": 0
|
||||
}, {
|
||||
"id": 2,
|
||||
"name": "2",
|
||||
"pid": 0,
|
||||
"level": 0
|
||||
}, {
|
||||
"id": 3,
|
||||
"name": "1-1",
|
||||
"pid": 1,
|
||||
"level": 1
|
||||
}, {
|
||||
"id": 4,
|
||||
"name": "1-2",
|
||||
"pid": 1,
|
||||
"level": 1
|
||||
}, {
|
||||
"id": 5,
|
||||
"name": "2-1",
|
||||
"pid": 2,
|
||||
"level": 1
|
||||
}, {
|
||||
"id": 6,
|
||||
"name": "2-2",
|
||||
"pid": 2,
|
||||
"level": 1
|
||||
}, {
|
||||
"id": 7,
|
||||
"name": "1-1-3-1",
|
||||
"pid": 3,
|
||||
"level": 2
|
||||
}, {
|
||||
"id": 8,
|
||||
"name": "1-1-3-2",
|
||||
"pid": 3,
|
||||
"level": 2
|
||||
}, {
|
||||
"id": 9,
|
||||
"name": "1-2-3-1",
|
||||
"pid": 4,
|
||||
"level": 2
|
||||
}, {
|
||||
"id": 10,
|
||||
"name": "1-2-3-2",
|
||||
"pid": 4,
|
||||
"level": 2
|
||||
}])
|
||||
dataList[0] = threeLinkageData.filter(it => it.level === 0)
|
||||
function selectChange(id){
|
||||
var currLevel = threeLinkageData.filter(it => it.id === id).map(it => it.level)[0]
|
||||
dataList.forEach((it, i) => {
|
||||
if (i > currLevel) {
|
||||
dataList[i] = []
|
||||
dataValues[i] = ''
|
||||
}
|
||||
})
|
||||
if (currLevel + 1 !== dataList.length) {
|
||||
dataList[currLevel + 1] = threeLinkageData.filter(it => it.pid == id)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<mb-ueditor v-model="content" />
|
||||
<button @click="getContent">获取内容</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'UeditorExample',
|
||||
data() {
|
||||
return {
|
||||
content: '默认content'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getContent() {
|
||||
console.log(this.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div style="padding: 50px;">
|
||||
<h2>上传文件</h2>
|
||||
<mb-upload-file v-model="fileUrl" @change="fileChange" />
|
||||
<h2>上传图片(id)</h2>
|
||||
<mb-upload-image :external-id="externalId" multiple :external-type="externalType" />
|
||||
<h2>上传图片(url、单图)</h2>
|
||||
<mb-upload-image v-model="imgUrl" @change="imgChange" />
|
||||
<h2>上传图片(url、多图)</h2>
|
||||
<mb-upload-image v-model="multipleImgUrl" width="120" height="120" multiple :limit="3" @change="multipleImgChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'UploadFile',
|
||||
data() {
|
||||
return {
|
||||
externalId: this.$common.uuid(),
|
||||
externalType: '营业执照',
|
||||
imgUrl: '',
|
||||
multipleImgUrl: '',
|
||||
fileUrl: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fileChange() {
|
||||
console.log(this.fileUrl)
|
||||
},
|
||||
imgChange() {
|
||||
console.log(this.imgUrl)
|
||||
},
|
||||
multipleImgChange() {
|
||||
console.log(this.multipleImgUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,42 @@
|
||||
<script setup>
|
||||
import { babelParse } from '@vue/compiler-sfc'
|
||||
import { compileFile } from '@/compiler/sfc-compiler.js'
|
||||
import { testCode } from '@/compiler/testCode.js'
|
||||
import { getCurrentInstance } from '@vue/runtime-core'
|
||||
import {ref} from 'vue'
|
||||
var compiled = {}
|
||||
compileFile('TestCode.vue', testCode, compiled)
|
||||
var code = compiled.js
|
||||
// console.log(code)
|
||||
var ast = babelParse(code, {
|
||||
sourceType: 'module'
|
||||
})
|
||||
var replaceCode = (node, subCode) => code.substring(0, node.start) + subCode + code.substring(node.end);
|
||||
for(var i = ast.program.body.length - 1; i>=0; i--){
|
||||
var node = ast.program.body[i]
|
||||
if(node.type === 'ImportDeclaration'){
|
||||
code = replaceCode(node, node.specifiers.map(it => `const ${it.local?.name || it.imported?.name || '*'} = ___magic__import__('${node.source.value}', '${it.imported?.name || '*'}');`).join('\r\n'));
|
||||
} else if (node.type === 'ExportDefaultDeclaration'){
|
||||
code = replaceCode(node, `return ${node.declaration.name}`)
|
||||
}
|
||||
}
|
||||
code = `(function(){
|
||||
${code}
|
||||
})()`
|
||||
var componentStyle = document.createElement("style");
|
||||
componentStyle.innerHTML = compiled.css
|
||||
document.head.appendChild(componentStyle);
|
||||
console.log(getCurrentInstance())
|
||||
getCurrentInstance().appContext.app.component('TestComp', eval(code))
|
||||
const xxClick = () => {
|
||||
console.log('click')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="'TestComp'"></component>
|
||||
<!-- <TestComp aaaaaa="666" @xxClick="xxClick"/> -->
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
|
||||
<div class="login-box">
|
||||
<div class="title-container">
|
||||
Magic-Boot
|
||||
</div>
|
||||
<el-form-item prop="username">
|
||||
<span class="svg-container">
|
||||
<svg-icon icon-class="user" />
|
||||
</span>
|
||||
<el-input ref="username" v-model="loginForm.username" placeholder="用户名" name="username" type="text" tabindex="1" auto-complete="on" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<span class="svg-container">
|
||||
<svg-icon icon-class="password" />
|
||||
</span>
|
||||
<el-input :key="passwordType" ref="password" v-model="loginForm.password" :type="passwordType" placeholder="密码" name="password" tabindex="2" auto-complete="on" @keyup.enter.native="handleLogin" />
|
||||
<span class="show-pwd" @click="showPwd">
|
||||
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-button :loading="loading" type="primary" size="medium" style="width:100%;margin-bottom:20px;font-size: 14px;" @click.native.prevent="handleLogin">登录</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
<div class="copyright">Copyright © 2020-{{new Date().getYear() + 1900}} <a href="https://ssssssss.org.cn" target="_blank">ssssssss.org.cn</a> All rights reserved.</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { login } from '@/scripts/auth'
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
data() {
|
||||
return {
|
||||
loginForm: {
|
||||
username: '',
|
||||
password: ''
|
||||
},
|
||||
loginRules: {
|
||||
username: [{ required: true, trigger: 'blur', message: '请输入用户名' }],
|
||||
password: [{ required: true, trigger: 'blur', message: '请输入密码' }]
|
||||
},
|
||||
loading: false,
|
||||
passwordType: 'password',
|
||||
redirect: undefined
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route: {
|
||||
handler: function(route) {
|
||||
this.redirect = route.query && route.query.redirect
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showPwd() {
|
||||
if (this.passwordType === 'password') {
|
||||
this.passwordType = ''
|
||||
} else {
|
||||
this.passwordType = 'password'
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.password.focus()
|
||||
})
|
||||
},
|
||||
handleLogin() {
|
||||
this.$refs.loginForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.loading = true
|
||||
login(this.loginForm).then((res) => {
|
||||
console.log(this.$router)
|
||||
this.$router.push({ path: '/home' })
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
/* 修复input 背景不协调 和光标变色 */
|
||||
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
|
||||
|
||||
$bg: white;
|
||||
$light_gray: rgba(0,0,0,.65);
|
||||
$cursor: rgba(0,0,0,.65);
|
||||
|
||||
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
|
||||
.login-container .el-input input {
|
||||
color: $cursor;
|
||||
}
|
||||
}
|
||||
|
||||
/* reset element-ui css */
|
||||
.login-container {
|
||||
.el-input {
|
||||
display: inline-block;
|
||||
height: 30px;
|
||||
width: 85%;
|
||||
|
||||
input {
|
||||
background: transparent;
|
||||
border: 0px;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0px;
|
||||
padding: 3px;
|
||||
color: $light_gray;
|
||||
height: 30px;
|
||||
caret-color: $cursor;
|
||||
|
||||
&:-webkit-autofill {
|
||||
box-shadow: 0 0 0px 1000px $bg inset !important;
|
||||
-webkit-text-fill-color: $cursor !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
border: 1px solid #D9D9D9;
|
||||
background: white;
|
||||
color: rgba(0,0,0,.65);
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$dark_gray:#889aa4;
|
||||
$light_gray: rgba(0,0,0,.65);
|
||||
|
||||
.login-container {
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
background-image: url(../assets/images/login-bg.svg);
|
||||
overflow: hidden;
|
||||
|
||||
.login-form {
|
||||
position: relative;
|
||||
width: 450px;
|
||||
max-width: 100%;
|
||||
padding: 160px 35px 35px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
|
||||
.login-box {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 7px 25px rgba(0,0,0,.08);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.tips {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin-bottom: 10px;
|
||||
|
||||
span {
|
||||
&:first-of-type {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.svg-container {
|
||||
padding-left: 10px;
|
||||
color: $dark_gray;
|
||||
width: 25px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.title-container {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
font-family: PoetsenOne;
|
||||
color: #808080;
|
||||
font-size: 26px;
|
||||
|
||||
.title {
|
||||
font-size: 26px;
|
||||
color: $light_gray;
|
||||
margin: 0px auto 40px auto;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.show-pwd {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
font-size: 16px;
|
||||
color: $dark_gray;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
.copyright{
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
font-family: Avenir,Helvetica,Arial,sans-serif;
|
||||
position: absolute;
|
||||
bottom: 50px;
|
||||
width: 100%;
|
||||
}
|
||||
.copyright a{
|
||||
text-decoration: none;
|
||||
color: #2196f3;
|
||||
outline: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
|
||||
<mb-search :where="tableOptions.where" @search="reloadTable" />
|
||||
|
||||
<el-row class="toolbar-container">
|
||||
<el-button v-permission="'role:save'" class="filter-item" type="primary" icon="ElPlus" @click="handleCreate">
|
||||
添加
|
||||
</el-button>
|
||||
</el-row>
|
||||
|
||||
<mb-table ref="table" v-bind="tableOptions" />
|
||||
|
||||
<mb-dialog ref="formDialog" :fullscreen="true" :title="dialogTitle" width="900px" @confirm-click="save($event)">
|
||||
<template #content>
|
||||
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="80px">
|
||||
<el-form-item label="组件名称" prop="name">
|
||||
<el-input v-model="temp.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="组件代码" prop="code">
|
||||
<el-input v-model="temp.code" :rows="30" type="textarea" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</mb-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive,getCurrentInstance, nextTick } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const table = ref()
|
||||
const formDialog = ref()
|
||||
const dataForm = ref()
|
||||
const tableOptions = reactive({
|
||||
url: 'component/list',
|
||||
where: {
|
||||
name: {
|
||||
type: 'input',
|
||||
label: '组件名称',
|
||||
value: ''
|
||||
}
|
||||
},
|
||||
cols: [
|
||||
{
|
||||
field: 'name',
|
||||
title: '组件名称'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
type: 'btns',
|
||||
width: 270,
|
||||
fixed: 'right',
|
||||
btns: [
|
||||
{
|
||||
permission: 'component:save',
|
||||
title: '修改',
|
||||
type: 'text',
|
||||
icon: 'ElEdit',
|
||||
click: (row) => {
|
||||
handleUpdate(row)
|
||||
}
|
||||
},
|
||||
{
|
||||
permission: 'component:delete',
|
||||
title: '删除',
|
||||
type: 'text',
|
||||
icon: 'ElDelete',
|
||||
click: (row) => {
|
||||
proxy.$common.handleDelete({
|
||||
url: 'component/delete',
|
||||
id: row.id,
|
||||
done: () => reloadTable()
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
const dialogTitle = ref('')
|
||||
const temp = ref(getTemp())
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入组件名称', trigger: 'change' }],
|
||||
code: [{ required: true, message: '请输入组件代码', trigger: 'change' }]
|
||||
})
|
||||
const downloadLoading = ref(false)
|
||||
|
||||
function reloadTable() {
|
||||
table.value.reloadList()
|
||||
}
|
||||
|
||||
function getTemp(){
|
||||
return {
|
||||
id: '',
|
||||
name: '',
|
||||
code: '',
|
||||
}
|
||||
}
|
||||
|
||||
function resetTemp() {
|
||||
temp.value = getTemp()
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
resetTemp()
|
||||
dialogTitle.value = '添加'
|
||||
formDialog.value.show()
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
function save(d) {
|
||||
dataForm.value.validate((valid) => {
|
||||
if (valid) {
|
||||
d.loading()
|
||||
proxy.$post('role/save', temp.value).then(() => {
|
||||
d.hideLoading()
|
||||
reloadTable()
|
||||
formDialog.value.hide()
|
||||
proxy.$notify({
|
||||
title: '成功',
|
||||
message: dialogTitle.value + '成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
}).catch(() => d.hideLoading())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
for (var t in temp.value) {
|
||||
temp.value[t] = row[t]
|
||||
}
|
||||
dialogTitle.value = '修改'
|
||||
formDialog.value.show()
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,10 @@
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
const { params, query } = this.$route
|
||||
const { path } = params
|
||||
this.$router.replace({ path: '/' + path, query })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
|
||||
<mb-search :where="tableOptions.where" @search="reloadTable" not-reset="dictId" />
|
||||
|
||||
<el-row class="toolbar-container">
|
||||
<el-button v-permission="'dict:items:save'" class="filter-item" type="primary" icon="ElEdit" @click="handleCreate">
|
||||
添加
|
||||
</el-button>
|
||||
</el-row>
|
||||
|
||||
<mb-table ref="table" v-bind="tableOptions" />
|
||||
|
||||
<mb-dialog ref="formDialog" :title="dialogTitle" width="640px" @confirm-click="save($event)">
|
||||
<template #content>
|
||||
<el-form ref="dataForm" :inline="true" :rules="rules" :model="temp" label-position="right" label-width="80px">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="标签名" prop="label">
|
||||
<el-input v-model="temp.label" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="值" prop="value">
|
||||
<el-input v-model="temp.value" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input v-model="temp.sort" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input v-model="temp.remarks" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
</mb-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref, reactive, getCurrentInstance, nextTick } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const props = defineProps({
|
||||
dictId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const tableOptions = reactive({
|
||||
url: 'dict/items/list',
|
||||
page: true,
|
||||
where: {
|
||||
label: {
|
||||
type: 'input',
|
||||
label: '标签',
|
||||
value: ''
|
||||
},
|
||||
value: {
|
||||
type: 'input',
|
||||
label: '值',
|
||||
value: ''
|
||||
},
|
||||
dictId: props.dictId
|
||||
},
|
||||
cols: [
|
||||
{
|
||||
field: 'label',
|
||||
title: '类型'
|
||||
},
|
||||
{
|
||||
field: 'value',
|
||||
title: '值'
|
||||
},
|
||||
{
|
||||
field: 'sort',
|
||||
title: '排序'
|
||||
},
|
||||
{
|
||||
field: 'remarks',
|
||||
title: '备注'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
type: 'btns',
|
||||
width: 160,
|
||||
fixed: 'right',
|
||||
btns: [
|
||||
{
|
||||
permission: 'dict:items:save',
|
||||
title: '修改',
|
||||
type: 'text',
|
||||
icon: 'ElEdit',
|
||||
click: (row) => {
|
||||
handleUpdate(row)
|
||||
}
|
||||
},
|
||||
{
|
||||
permission: 'dict:items:delete',
|
||||
title: '删除',
|
||||
type: 'text',
|
||||
icon: 'ElDelete',
|
||||
click: (row) => {
|
||||
proxy.$common.handleDelete({
|
||||
url: 'dict/items/delete',
|
||||
id: row.id,
|
||||
done: () => {
|
||||
reloadTable()
|
||||
proxy.$common.getDictData()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
const dialogTitle = ref('')
|
||||
const rules = reactive({
|
||||
value: [{ required: true, message: '请输入值', trigger: 'change' }],
|
||||
label: [{ required: true, message: '请输入标签名', trigger: 'change' }],
|
||||
sort: [{ required: true, message: '请输入排序', trigger: 'change' }]
|
||||
})
|
||||
const temp = ref(getTemp())
|
||||
const table = ref()
|
||||
const formDialog = ref()
|
||||
const dataForm = ref()
|
||||
|
||||
function reloadTable() {
|
||||
table.value.reloadList()
|
||||
}
|
||||
|
||||
function getTemp() {
|
||||
return {
|
||||
id: '',
|
||||
value: '',
|
||||
label: '',
|
||||
dictId: props.dictId,
|
||||
sort: 0,
|
||||
remarks: ''
|
||||
}
|
||||
}
|
||||
|
||||
function getSort() {
|
||||
proxy.$get('dict/items/sort', { dictId: props.dictId }).then(res => {
|
||||
temp.value.sort = res.data
|
||||
})
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
temp.value = getTemp()
|
||||
getSort()
|
||||
dialogTitle.value = '添加'
|
||||
formDialog.value.show()
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
function save(d) {
|
||||
dataForm.value.validate((valid) => {
|
||||
if (valid) {
|
||||
d.loading()
|
||||
proxy.$post('dict/items/save', temp.value).then(() => {
|
||||
d.hideLoading()
|
||||
formDialog.value.hide()
|
||||
proxy.$notify({
|
||||
title: '成功',
|
||||
message: dialogTitle.value + '成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
reloadTable()
|
||||
proxy.$common.getDictData()
|
||||
}).catch(() => d.hideLoading())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
proxy.$common.objAssign(temp.value, row)
|
||||
dialogTitle.value = '修改'
|
||||
formDialog.value.show()
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
|
||||
<mb-search :where="tableOptions.where" @search="reloadTable" />
|
||||
|
||||
<el-row class="toolbar-container">
|
||||
<el-button v-permission="'dict:save'" class="filter-item" type="primary" icon="ElPlus" @click="handleCreate">
|
||||
添加
|
||||
</el-button>
|
||||
</el-row>
|
||||
|
||||
<mb-table ref="table" v-bind="tableOptions" />
|
||||
|
||||
<mb-dialog ref="dictDialog" :title="dialogTitle" width="640px" @confirm-click="save($event)">
|
||||
<template #content>
|
||||
<el-form ref="dataForm" :inline="true" :rules="rules" :model="temp" label-position="right" label-width="80px">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="字典类型" prop="dictType">
|
||||
<mb-select v-model="temp.dictType" type="dict_type" width="185px" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-input v-model="temp.type" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input v-model="temp.sort" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="描述" prop="descRibe">
|
||||
<el-input v-model="temp.descRibe" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input v-model="temp.remarks" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
</mb-dialog>
|
||||
|
||||
<mb-dialog ref="dictItemsDialog" title="字典项" width="1400px">
|
||||
<template #content>
|
||||
<dict-items :key="Math.random()" v-model:dict-id="dictId" />
|
||||
</template>
|
||||
</mb-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DictItems from './dict-items'
|
||||
|
||||
import { ref, reactive, getCurrentInstance, nextTick } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const tableOptions = reactive({
|
||||
url: 'dict/list',
|
||||
page: true,
|
||||
where: {
|
||||
type: {
|
||||
type: 'input',
|
||||
label: '类型',
|
||||
value: ''
|
||||
},
|
||||
dictType: {
|
||||
type: 'select',
|
||||
label: '字典类型',
|
||||
value: '',
|
||||
properties: {
|
||||
'all-option': true,
|
||||
type: 'dict_type'
|
||||
}
|
||||
}
|
||||
},
|
||||
cols: [
|
||||
{
|
||||
field: 'type',
|
||||
title: '类型'
|
||||
},
|
||||
{
|
||||
field: 'descRibe',
|
||||
title: '描述'
|
||||
},
|
||||
{
|
||||
field: 'dictType',
|
||||
title: '字典类型',
|
||||
width: 200,
|
||||
dictType: 'dict_type'
|
||||
},
|
||||
{
|
||||
field: 'remarks',
|
||||
title: '备注',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
field: 'sort',
|
||||
title: '排序',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
type: 'btns',
|
||||
width: 220,
|
||||
fixed: 'right',
|
||||
btns: [
|
||||
{
|
||||
permission: 'dict:save',
|
||||
title: '修改',
|
||||
type: 'text',
|
||||
icon: 'ElEdit',
|
||||
click: (row) => {
|
||||
handleUpdate(row)
|
||||
}
|
||||
},
|
||||
{
|
||||
permission: 'dict:delete',
|
||||
title: '删除',
|
||||
type: 'text',
|
||||
icon: 'ElDelete',
|
||||
click: (row) => {
|
||||
proxy.$common.handleDelete({
|
||||
url: 'dict/delete',
|
||||
id: row.id,
|
||||
done: () => {
|
||||
reloadTable()
|
||||
proxy.$common.getDictData()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
permission: 'dict:items:view',
|
||||
title: '字典项',
|
||||
type: 'text',
|
||||
icon: 'ElList',
|
||||
click: (row) => {
|
||||
dictItemsDialog.value.show()
|
||||
dictId.value = row.id
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const dictId = ref('')
|
||||
const temp = ref(getTemp())
|
||||
const dialogTitle = ref('')
|
||||
const rules = reactive({
|
||||
dictType: [{ required: true, message: '请输入标签', trigger: 'change' }],
|
||||
type: [{ required: true, message: '请输入类型', trigger: 'change' }],
|
||||
sort: [{ required: true, message: '请输入排序', trigger: 'change' }],
|
||||
descRibe: [{ required: true, message: '请输入描述', trigger: 'change' }]
|
||||
})
|
||||
const table = ref()
|
||||
const dictDialog = ref()
|
||||
const dataForm = ref()
|
||||
const dictItemsDialog = ref()
|
||||
|
||||
function getTemp() {
|
||||
return {
|
||||
id: '',
|
||||
dictType: '',
|
||||
type: '',
|
||||
sort: 0,
|
||||
descRibe: '',
|
||||
remarks: ''
|
||||
}
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
table.value.reloadList()
|
||||
}
|
||||
|
||||
function getSort() {
|
||||
proxy.$get('dict/sort').then(res => {
|
||||
temp.value.sort = res.data
|
||||
})
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
temp.value = getTemp()
|
||||
getSort()
|
||||
dialogTitle.value = '添加'
|
||||
dictDialog.value.show()
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
function save(d) {
|
||||
dataForm.value.validate((valid) => {
|
||||
if (valid) {
|
||||
d.loading()
|
||||
proxy.$post('dict/save', temp.value).then((response) => {
|
||||
d.hideLoading()
|
||||
temp.value.id = response.data
|
||||
dictDialog.value.hide()
|
||||
proxy.$notify({
|
||||
title: '成功',
|
||||
message: dialogTitle.value + '成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
reloadTable()
|
||||
proxy.$common.getDictData()
|
||||
}).catch(() => d.hideLoading())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
proxy.$common.objAssign(temp.value, row)
|
||||
dialogTitle.value = '修改'
|
||||
dictDialog.value.show()
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,250 @@
|
||||
<style>
|
||||
.el-input-number .el-input__inner{
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="80px">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="菜单类型" prop="type">
|
||||
<el-radio-group v-model="menuType">
|
||||
<el-radio-button label="menu">菜单</el-radio-button>
|
||||
<el-radio-button label="button">按钮</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="上级菜单" prop="pid">
|
||||
<treeselect v-model="temp.pid" :options="menuTree" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="菜单名称" prop="name">
|
||||
<el-input v-model="temp.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单链接" prop="url" v-if="menuType == 'menu'">
|
||||
<el-input v-model="temp.url" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联组件" prop="componentId" v-if="menuType == 'menu'">
|
||||
<mb-select v-model="temp.componentId" url="/component/select" width="100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限标识" prop="permission" v-if="menuType == 'button'">
|
||||
<el-input v-model="temp.permission" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="temp.sort" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="选择图标" prop="icon" v-if="menuType == 'menu'">
|
||||
<a @click="openIcons">
|
||||
<el-input v-model="temp.icon" class="input-with-select">
|
||||
<template #append>
|
||||
<el-button style="height:40px;">
|
||||
<mb-icon :icon="temp.icon" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</a>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="菜单显示" v-if="menuType == 'menu'">
|
||||
<el-radio-group v-model="temp.isShow">
|
||||
<el-radio-button label="1">显示</el-radio-button>
|
||||
<el-radio-button label="0">不显示</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="路由缓存" v-if="menuType == 'menu'">
|
||||
<el-radio-group v-model="temp.keepAlive">
|
||||
<el-radio-button label="1">缓存</el-radio-button>
|
||||
<el-radio-button label="0">不缓存</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<mb-dialog ref="iconDialog">
|
||||
<template #content>
|
||||
<menu-icons :select-icon="selectIcon" />
|
||||
</template>
|
||||
</mb-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch, nextTick, getCurrentInstance, defineExpose } from 'vue'
|
||||
import MenuIcons from './menu-icons'
|
||||
import Treeselect from 'vue3-treeselect'
|
||||
import 'vue3-treeselect/dist/vue3-treeselect.css'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const props = defineProps({
|
||||
menuTree: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
menuData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['reload-table'])
|
||||
|
||||
const dataForm = ref()
|
||||
const iconDialog = ref()
|
||||
const menuType = ref('menu')
|
||||
const getTemp = () => {
|
||||
return {
|
||||
id: '',
|
||||
name: '',
|
||||
url: '',
|
||||
permission: '',
|
||||
sort: 0,
|
||||
descRibe: '',
|
||||
isShow: 1,
|
||||
pid: 0,
|
||||
icon: '',
|
||||
keepAlive: 1,
|
||||
componentId: ''
|
||||
}
|
||||
}
|
||||
const temp = ref(getTemp())
|
||||
|
||||
var validateUrl = (rule, value, callback) => {
|
||||
if(menuType.value == 'menu'){
|
||||
if(!value){
|
||||
callback(new Error('请输入菜单链接'))
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
var validatePermission = (rule, value, callback) => {
|
||||
if(menuType.value == 'button'){
|
||||
if(!value){
|
||||
callback(new Error('请输入权限标识'))
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const rules = reactive({
|
||||
pid: [{ required: true, message: '请选择上级菜单', trigger: 'change' }],
|
||||
name: [{ required: true, message: '请输入菜单名称', trigger: 'change' }],
|
||||
url: [{ required: true, trigger: 'change', validator: validateUrl }],
|
||||
permission: [{ required: true, trigger: 'change', validator: validatePermission }]
|
||||
})
|
||||
|
||||
watch(menuType, (type) => {
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
if(type == 'menu'){
|
||||
temp.value.isShow = 1
|
||||
}else{
|
||||
temp.value.isShow = 0
|
||||
}
|
||||
})
|
||||
|
||||
function save(d) {
|
||||
dataForm.value.validate((valid) => {
|
||||
if (valid) {
|
||||
d.loading()
|
||||
if(temp.value.pid == temp.value.id){
|
||||
this.$notify({
|
||||
title: '失败',
|
||||
message: '上级菜单不能选当前菜单',
|
||||
type: 'error',
|
||||
duration: 2000
|
||||
})
|
||||
return
|
||||
}
|
||||
if(proxy.$treeTable.isChildren(proxy.$treeTable.queryChildren(props.menuData, temp.value.id), temp.value.pid)){
|
||||
proxy.$notify({
|
||||
title: '失败',
|
||||
message: '上级菜单不能选当前菜单子级',
|
||||
type: 'error',
|
||||
duration: 2000
|
||||
})
|
||||
return
|
||||
}
|
||||
if(menuType.value == 'menu'){
|
||||
temp.value.permission = ''
|
||||
}else{
|
||||
temp.value.isShow = 0
|
||||
temp.value.keepAlive = 0
|
||||
temp.value.icon = ''
|
||||
temp.value.url = ''
|
||||
}
|
||||
proxy.$post('menu/save', temp.value).then(() => {
|
||||
d.hideLoading()
|
||||
proxy.$notify({
|
||||
title: '成功',
|
||||
message: d.title + '成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
d.hide()
|
||||
emit('reload-table')
|
||||
}).catch(() => d.hideLoading())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addSubMenu(id) {
|
||||
resetTemp()
|
||||
menuType.value = 'menu'
|
||||
temp.value.pid = id
|
||||
temp.value.id = proxy.$common.uuid()
|
||||
getSort()
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
function getInfo(row) {
|
||||
for (var t in temp.value) {
|
||||
temp.value[t] = row[t]
|
||||
}
|
||||
menuType.value = temp.value.url ? 'menu' : 'button'
|
||||
temp.value.name = temp.value.name.replaceAll(/<font.*?>(.*?)<\/font>/g,'$1')
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
function resetTemp() {
|
||||
temp.value = getTemp()
|
||||
}
|
||||
|
||||
function selectIcon(symbol) {
|
||||
temp.value.icon = symbol
|
||||
iconDialog.value.hide()
|
||||
}
|
||||
|
||||
function openIcons() {
|
||||
iconDialog.value.show()
|
||||
}
|
||||
|
||||
function getSort() {
|
||||
proxy.$get('menu/sort', { pid: temp.value.pid }).then(res => {
|
||||
temp.value.sort = res.data
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ addSubMenu, getInfo, save })
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="icons-container">
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="Icons">
|
||||
<div class="grid">
|
||||
<div v-for="item of svgIcons" :key="item" @click="selectIcon(item)">
|
||||
<el-tooltip placement="top">
|
||||
<template #content>
|
||||
<mb-icon :icon="item" />
|
||||
</template>
|
||||
<div class="icon-item">
|
||||
<mb-icon :icon="item" />
|
||||
<svg-icon :icon-class="item" class-name="disabled" />
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import svgIcons from '@/scripts/svg-icons'
|
||||
const props = defineProps({
|
||||
selectIcon: Function
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icons-container {
|
||||
margin: 10px 20px 0;
|
||||
overflow: hidden;
|
||||
|
||||
.grid {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
}
|
||||
|
||||
.icon-item {
|
||||
margin: 20px;
|
||||
height: 85px;
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
float: left;
|
||||
font-size: 30px;
|
||||
color: #24292e;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
|
||||
<div class="filter-container">
|
||||
<el-form :inline="true">
|
||||
<el-form-item label="菜单搜索">
|
||||
<el-input v-model="searchValue" @input="searchMenu" placeholder="菜单名称、链接、权限标识" style="width: 200px"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button class="filter-item" type="primary" icon="ElSearch" @click="searchMenu">
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button class="filter-item" icon="ElDelete" @click="() => { searchValue = ''; searchMenu() }">
|
||||
清空
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<el-row class="toolbar-container">
|
||||
<el-button class="filter-item" type="primary" icon="ElPlus" @click="addSubMenu('0')" v-permission="'menu:save'">
|
||||
添加菜单
|
||||
</el-button>
|
||||
<el-button type="primary" icon="ElSort" plain @click="expand">展开/折叠</el-button>
|
||||
</el-row>
|
||||
|
||||
<mb-table ref="table" v-bind="tableOptions" v-if="menuData && menuData.length > 0 && refreshTable" />
|
||||
|
||||
<mb-dialog ref="menuFormDialog" width="970px" :title="dialogTitle" @confirm-click="menuFormRef.save($event)">
|
||||
<template #content>
|
||||
<menu-form ref="menuFormRef" :title="dialogTitle" :menu-tree="menuTree" :menu-data="menuData" @reload-table="reloadTable" />
|
||||
</template>
|
||||
</mb-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref,reactive, onMounted, getCurrentInstance, nextTick, watch } from 'vue'
|
||||
import MenuForm from './menu-form'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const refreshTable = ref(true)
|
||||
let menuTree = ref([])
|
||||
const menuData = ref([])
|
||||
let searchValue = ref('')
|
||||
const tableOptions = reactive({
|
||||
el: {
|
||||
'default-expand-all': true,
|
||||
'tree-props': { children: 'children', hasChildren: 'hasChildren' },
|
||||
'row-key': 'id'
|
||||
},
|
||||
showNo: false,
|
||||
page: false,
|
||||
cols: [
|
||||
{
|
||||
field: 'name',
|
||||
title: '菜单名称',
|
||||
align: 'left',
|
||||
type: 'html'
|
||||
},
|
||||
{
|
||||
field: 'url',
|
||||
title: '路径',
|
||||
align: 'left',
|
||||
type: 'html'
|
||||
},
|
||||
{
|
||||
field: 'permission',
|
||||
title: '权限标识',
|
||||
width: 150,
|
||||
align: 'left',
|
||||
type: 'html'
|
||||
},
|
||||
{
|
||||
field: 'icon',
|
||||
title: '图标',
|
||||
width: 55,
|
||||
align: 'center',
|
||||
templet: (row) => {
|
||||
return generateIconCode(row.icon)
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'sort',
|
||||
title: '序号',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
type: 'btns',
|
||||
width: 150,
|
||||
btns: [
|
||||
{
|
||||
title: '上移',
|
||||
type: 'text',
|
||||
icon: 'ElSortUp',
|
||||
click: (row) => {
|
||||
proxy.$get('menu/sort/up',{
|
||||
id: row.id,
|
||||
pid: row.pid,
|
||||
sort: row.sort
|
||||
}).then(() => {
|
||||
reloadTable()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '下移',
|
||||
type: 'text',
|
||||
icon: 'ElSortDown',
|
||||
click: (row) => {
|
||||
proxy.$get('menu/sort/down',{
|
||||
id: row.id,
|
||||
pid: row.pid,
|
||||
sort: row.sort
|
||||
}).then(() => {
|
||||
reloadTable()
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
field: 'isShow',
|
||||
title: '是否显示',
|
||||
type: 'switch',
|
||||
width: 100,
|
||||
change: (row) => {
|
||||
proxy.$get('menu/change', {
|
||||
id: row.id,
|
||||
isShow: row.isShow
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'keepAlive',
|
||||
title: '是否缓存',
|
||||
type: 'switch',
|
||||
width: 100,
|
||||
change: (row) => {
|
||||
proxy.$get('menu/change', {
|
||||
id: row.id,
|
||||
keepAlive: row.keepAlive
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
type: 'btns',
|
||||
width: 260,
|
||||
fixed: 'right',
|
||||
align: 'left',
|
||||
btns: [
|
||||
{
|
||||
title: '添加下级菜单',
|
||||
type: 'text',
|
||||
permission: 'menu:save',
|
||||
icon: 'ElPlus',
|
||||
click: (row) => {
|
||||
addSubMenu(row.id)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '修改',
|
||||
type: 'text',
|
||||
permission: 'menu:save',
|
||||
icon: 'ElEdit',
|
||||
click: (row) => {
|
||||
handleUpdate(row)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '删除',
|
||||
type: 'text',
|
||||
permission: 'menu:delete',
|
||||
icon: 'ElDelete',
|
||||
click: (row) => {
|
||||
proxy.$common.handleDelete({
|
||||
url: 'menu/delete',
|
||||
id: row.id,
|
||||
done: () => reloadTable()
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
let dialogTitle = ref('')
|
||||
let searchTimeout = reactive()
|
||||
const menuFormDialog = ref()
|
||||
const menuFormRef = ref()
|
||||
|
||||
function reloadTable(){
|
||||
proxy.$get('menu/tree').then(res => {
|
||||
menuData.value = res.data.list
|
||||
tableOptions.data = menuData.value
|
||||
})
|
||||
}
|
||||
|
||||
function expand(){
|
||||
refreshTable.value = false
|
||||
tableOptions.el["default-expand-all"] = !tableOptions.el["default-expand-all"]
|
||||
nextTick(() => refreshTable.value = true)
|
||||
}
|
||||
|
||||
function searchMenu() {
|
||||
clearTimeout(searchTimeout)
|
||||
searchTimeout = setTimeout(() => {
|
||||
if(searchValue.value){
|
||||
tableOptions.data = proxy.$treeTable.recursionSearch(['name', 'url', 'permission'], proxy.$common.copyNew(menuData.value), searchValue.value)
|
||||
}else{
|
||||
tableOptions.data = menuData.value
|
||||
}
|
||||
},500)
|
||||
}
|
||||
|
||||
function addSubMenu(id) {
|
||||
dialogTitle.value = '添加'
|
||||
menuFormDialog.value.show()
|
||||
nextTick(() => {
|
||||
menuFormRef.value.addSubMenu(id)
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
dialogTitle.value = '修改'
|
||||
menuFormDialog.value.show()
|
||||
nextTick(() => {
|
||||
menuFormRef.value.getInfo(row);
|
||||
})
|
||||
}
|
||||
|
||||
function generateIconCode(symbol) {
|
||||
return `<svg style="width: 20px;height: 20px;fill: #999" aria-hidden="true" class="svg-icon disabled"><use href="#icon-${symbol}"></use></svg>`
|
||||
}
|
||||
|
||||
onMounted(() => reloadTable())
|
||||
|
||||
watch(menuData, () => {
|
||||
menuTree.value = [{
|
||||
label: '根节点',
|
||||
id: '0',
|
||||
children: proxy.$treeTable.genTree(menuData.value)
|
||||
}]
|
||||
})
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,339 @@
|
||||
<style>
|
||||
.el-input-number .el-input__inner{
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
|
||||
<div class="filter-container">
|
||||
<el-form :inline="true">
|
||||
<el-form-item label="机构搜索">
|
||||
<el-input v-model="searchValue" @input="searchOffice" placeholder="机构名称、链接、权限标识"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button class="filter-item" type="primary" icon="ElSearch" @click="searchOffice">
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button class="filter-item" icon="ElDelete" @click="() => { this.searchValue = ''; searchOffice() }">
|
||||
清空
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<el-row class="toolbar-container">
|
||||
<el-button class="filter-item" type="primary" icon="ElEdit" @click="addSubOffice('0')" v-permission="'office:save'">
|
||||
添加机构
|
||||
</el-button>
|
||||
<el-button type="primary" icon="ElSort" plain @click="expand">展开/折叠</el-button>
|
||||
</el-row>
|
||||
|
||||
<mb-table ref="table" v-bind="tableOptions" v-if="officeData && officeData.length > 0 && refreshTable" />
|
||||
|
||||
<mb-dialog ref="officeFormDialog" width="700px" :title="dialogTitle" @confirm-click="save($event)">
|
||||
<template #content>
|
||||
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="80px">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="机构类型" prop="type">
|
||||
<mb-select v-model="temp.type" type="office_type" width="100%" :key="temp.type" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="上级机构" prop="pid">
|
||||
<treeselect v-model="temp.pid" :options="officeTree" :key="temp.pid" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="机构名称" prop="name">
|
||||
<el-input v-model="temp.name" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="机构编码" prop="code">
|
||||
<el-input v-model="temp.code" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="temp.sort" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
</mb-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Treeselect from 'vue3-treeselect'
|
||||
import 'vue3-treeselect/dist/vue3-treeselect.css'
|
||||
|
||||
import { ref, reactive, onMounted, watch, nextTick, getCurrentInstance } from 'vue'
|
||||
const { proxy } = getCurrentInstance()
|
||||
const refreshTable = ref(true)
|
||||
const officeData = ref([])
|
||||
const officeTree = ref([])
|
||||
const searchValue = ref('')
|
||||
const tableOptions = reactive({
|
||||
el: {
|
||||
'default-expand-all': true,
|
||||
'tree-props': { children: 'children', hasChildren: 'hasChildren' },
|
||||
'row-key': 'id'
|
||||
},
|
||||
showNo: false,
|
||||
page: false,
|
||||
cols: [
|
||||
{
|
||||
field: 'name',
|
||||
title: '机构名称',
|
||||
align: 'left',
|
||||
type: 'html'
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
title: '机构编码',
|
||||
width: 300,
|
||||
align: 'left',
|
||||
type: 'html'
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
dictType: 'office_type',
|
||||
title: '机构类型',
|
||||
width: 300,
|
||||
align: 'left'
|
||||
},
|
||||
{
|
||||
field: 'sort',
|
||||
title: '序号',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
type: 'btns',
|
||||
width: 150,
|
||||
btns: [
|
||||
{
|
||||
title: '上移',
|
||||
type: 'text',
|
||||
icon: 'ElSortUp',
|
||||
click: (row) => {
|
||||
proxy.$get('office/sort/up',{
|
||||
id: row.id,
|
||||
pid: row.pid,
|
||||
sort: row.sort
|
||||
}).then(() => {
|
||||
reloadTable()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '下移',
|
||||
type: 'text',
|
||||
icon: 'ElSortDown',
|
||||
click: (row) => {
|
||||
proxy.$get('office/sort/down',{
|
||||
id: row.id,
|
||||
pid: row.pid,
|
||||
sort: row.sort
|
||||
}).then(() => {
|
||||
reloadTable()
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
type: 'btns',
|
||||
width: 310,
|
||||
fixed: 'right',
|
||||
align: 'left',
|
||||
btns: [
|
||||
{
|
||||
title: '添加下级机构',
|
||||
type: 'text',
|
||||
permission: 'office:save',
|
||||
icon: 'ElPlus',
|
||||
click: (row) => {
|
||||
addSubOffice(row.id)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '修改',
|
||||
type: 'text',
|
||||
permission: 'office:save',
|
||||
icon: 'ElEdit',
|
||||
click: (row) => {
|
||||
handleUpdate(row)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '删除',
|
||||
type: 'text',
|
||||
permission: 'office:delete',
|
||||
icon: 'ElDelete',
|
||||
click: (row) => {
|
||||
proxy.$common.handleDelete({
|
||||
url: 'office/delete',
|
||||
id: row.id,
|
||||
done: () => reloadTable()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
permission: 'office:user:list',
|
||||
title: '用户列表',
|
||||
type: 'text',
|
||||
icon: 'ElUserFilled',
|
||||
click: (row) => {
|
||||
proxy.$router.push({ path: `/system/user/user-list?officeId=${row.id}` })
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const dialogTitle = ref('')
|
||||
const temp = ref(getTemp())
|
||||
const rules = reactive({
|
||||
type: [{ required: true, message: '请选择机构类型', trigger: 'change' }],
|
||||
pid: [{ required: true, message: '请选择上级机构', trigger: 'change' }],
|
||||
name: [{ required: true, message: '请输入机构名称', trigger: 'change' }],
|
||||
code: [{ required: true, message: '请输入机构编码', trigger: 'change' }]
|
||||
})
|
||||
const searchTimeout = null
|
||||
const officeFormDialog = ref()
|
||||
const dataForm = ref()
|
||||
|
||||
onMounted(() => {
|
||||
reloadTable()
|
||||
})
|
||||
|
||||
watch(officeData, () => {
|
||||
officeTree.value = [{
|
||||
label: '根节点',
|
||||
id: '0',
|
||||
children: proxy.$treeTable.genTree(officeData.value)
|
||||
}]
|
||||
})
|
||||
|
||||
function expand(){
|
||||
refreshTable.value = false
|
||||
tableOptions.el["default-expand-all"] = !tableOptions.el["default-expand-all"]
|
||||
nextTick(() => {
|
||||
refreshTable.value = true
|
||||
})
|
||||
}
|
||||
|
||||
function searchOffice() {
|
||||
clearTimeout(searchTimeout)
|
||||
searchTimeout = setTimeout(() => {
|
||||
if(searchValue.value){
|
||||
tableOptions.data = proxy.$treeTable.recursionSearch(['name', 'code'], proxy.$common.copyNew(officeData.value), searchValue.value)
|
||||
}else{
|
||||
tableOptions.data = officeData.value
|
||||
}
|
||||
},1000)
|
||||
}
|
||||
|
||||
function getTemp() {
|
||||
return {
|
||||
id: '',
|
||||
name: '',
|
||||
sort: 0,
|
||||
pid: '',
|
||||
type: '',
|
||||
code: ''
|
||||
}
|
||||
}
|
||||
|
||||
function resetTemp() {
|
||||
temp.value = getTemp()
|
||||
}
|
||||
|
||||
function getSort() {
|
||||
proxy.$get('office/sort', { pid: temp.value.pid }).then(res => {
|
||||
temp.value.sort = res.data
|
||||
})
|
||||
}
|
||||
|
||||
function addSubOffice(id) {
|
||||
resetTemp()
|
||||
temp.value.pid = id
|
||||
temp.value.id = proxy.$common.uuid()
|
||||
getSort()
|
||||
dialogTitle.value = '添加'
|
||||
officeFormDialog.value.show()
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
function save(d) {
|
||||
dataForm.value.validate((valid) => {
|
||||
if (valid) {
|
||||
d.loading()
|
||||
if(temp.value.pid == temp.value.id){
|
||||
proxy.$notify({
|
||||
title: '失败',
|
||||
message: '上级机构不能选当前机构',
|
||||
type: 'error',
|
||||
duration: 2000
|
||||
})
|
||||
return
|
||||
}
|
||||
if(proxy.$treeTable.isChildren(proxy.$treeTable.queryChildren(officeData.value, temp.value.id), temp.value.pid)){
|
||||
proxy.$notify({
|
||||
title: '失败',
|
||||
message: '上级机构不能选当前机构子级',
|
||||
type: 'error',
|
||||
duration: 2000
|
||||
})
|
||||
return
|
||||
}
|
||||
proxy.$post('office/save', temp.value).then(() => {
|
||||
d.hideLoading()
|
||||
proxy.$notify({
|
||||
title: '成功',
|
||||
message: dialogTitle.value + '成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
reloadTable()
|
||||
officeFormDialog.value.hide()
|
||||
}).catch(() => d.hideLoading())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
proxy.$get('office/tree').then(res => {
|
||||
officeData.value = res.data.list
|
||||
tableOptions.data = officeData.value
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
for (var t in temp.value) {
|
||||
temp.value[t] = row[t]
|
||||
}
|
||||
temp.value.name = temp.value.name.replaceAll(/<font.*?>(.*?)<\/font>/g,'$1')
|
||||
dialogTitle.value = '修改'
|
||||
officeFormDialog.value.show()
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<mb-tree ref="tree" :el="{ 'show-checkbox': true }" url="menu/tree" :search="true" search-width="230px" :select-values.sync="menus" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'RoleAssignPermissions',
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menus: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$get('menu/by/role',{ roleId: this.id }).then(res => {
|
||||
this.menus = res.data.join(',')
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
save(d) {
|
||||
d.loading()
|
||||
this.$post('role/save', {
|
||||
id: this.id,
|
||||
menus: this.menus
|
||||
}).then((response) => {
|
||||
d.hideLoading()
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '分配成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.$emit('close')
|
||||
}).catch(() => d.hideLoading())
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
|
||||
<mb-search :where="tableOptions.where" @search="reloadTable" />
|
||||
|
||||
<el-row class="toolbar-container">
|
||||
<el-button v-permission="'role:save'" class="filter-item" type="primary" icon="ElPlus" @click="handleCreate">
|
||||
添加
|
||||
</el-button>
|
||||
</el-row>
|
||||
|
||||
<mb-table ref="table" v-bind="tableOptions" />
|
||||
|
||||
<mb-dialog ref="roleFormDialog" :title="dialogTitle" width="900px" @confirm-click="save($event)">
|
||||
<template #content>
|
||||
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="80px">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="角色名称" prop="name">
|
||||
<el-input v-model="temp.name" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="角色编码" prop="code">
|
||||
<el-input v-model="temp.code" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="角色描述" prop="descRibe">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入描述"
|
||||
v-model="temp.descRibe">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="菜单权限" prop="menus">
|
||||
<mb-tree ref="tree" :el="{ 'show-checkbox': true }" max-height="270px" url="menu/tree" :search="true" v-model:select-values="temp.menus" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="数据权限" prop="permission">
|
||||
<mb-select v-model="temp.permission" :data="permissionData" />
|
||||
<mb-tree v-if="temp.permission == 1" max-height="270px" :el="{ 'check-strictly': true, 'show-checkbox': true }" ref="office" url="office/tree" v-model:select-values="temp.offices" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
</mb-dialog>
|
||||
|
||||
<mb-dialog ref="assignPermissionsDialog" title="分配权限" width="750px" @confirm-click="assignPermissions.save($event)">
|
||||
<template #content>
|
||||
<role-assign-permissions ref="assignPermissions" :key="Math.random()" :id="temp.id" @close="() => { assignPermissionsDialog.value.hide(); temp.id = '' }" />
|
||||
</template>
|
||||
</mb-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive,getCurrentInstance, watch, nextTick } from 'vue'
|
||||
import RoleAssignPermissions from './role-assign-permissions'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const permissionData = reactive([{
|
||||
label: '全部',
|
||||
value: '0'
|
||||
}, {
|
||||
label: '自定义',
|
||||
value: '1'
|
||||
}, {
|
||||
label: '本级及子级',
|
||||
value: '2'
|
||||
}, {
|
||||
label: '本级',
|
||||
value: '3'
|
||||
}])
|
||||
const assignPermissionsDialog = ref()
|
||||
const table = ref()
|
||||
const roleFormDialog = ref()
|
||||
const dataForm = ref()
|
||||
const tableOptions = reactive({
|
||||
url: 'role/list',
|
||||
where: {
|
||||
name: {
|
||||
type: 'input',
|
||||
label: '角色名称',
|
||||
value: ''
|
||||
}
|
||||
},
|
||||
cols: [
|
||||
{
|
||||
field: 'name',
|
||||
title: '角色名称'
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
title: '角色编码'
|
||||
},
|
||||
{
|
||||
field: 'descRibe',
|
||||
title: '角色描述'
|
||||
},
|
||||
{
|
||||
field: 'permission',
|
||||
title: '数据权限',
|
||||
templet: (row) => {
|
||||
return permissionData[row.permission].label
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
type: 'btns',
|
||||
width: 270,
|
||||
fixed: 'right',
|
||||
btns: [
|
||||
{
|
||||
permission: 'role:save',
|
||||
title: '修改',
|
||||
type: 'text',
|
||||
icon: 'ElEdit',
|
||||
click: (row) => {
|
||||
handleUpdate(row)
|
||||
}
|
||||
},
|
||||
{
|
||||
permission: 'role:delete',
|
||||
title: '删除',
|
||||
type: 'text',
|
||||
icon: 'ElDelete',
|
||||
click: (row) => {
|
||||
proxy.$common.handleDelete({
|
||||
url: 'role/delete',
|
||||
id: row.id,
|
||||
done: () => reloadTable()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
permission: 'role:permission',
|
||||
title: '权限',
|
||||
type: 'text',
|
||||
icon: 'ElPlus',
|
||||
click: (row) => {
|
||||
temp.id = row.id
|
||||
assignPermissionsDialog.value.show()
|
||||
}
|
||||
},
|
||||
{
|
||||
permission: 'role:user:list',
|
||||
title: '用户列表',
|
||||
type: 'text',
|
||||
icon: 'ElUserFilled',
|
||||
click: (row) => {
|
||||
proxy.$router.push({
|
||||
path: '/system/user/user-list',
|
||||
query: { roleId: row.id }
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
const dialogTitle = ref('')
|
||||
const temp = ref(getTemp())
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入角色名称', trigger: 'change' }],
|
||||
code: [{ required: true, message: '请输入角色编码', trigger: 'change' }]
|
||||
})
|
||||
const downloadLoading = ref(false)
|
||||
|
||||
watch(() => temp.value.permission,() => {
|
||||
if(temp.value.permission != 1){
|
||||
temp.value.offices = ''
|
||||
}
|
||||
})
|
||||
|
||||
function reloadTable() {
|
||||
table.value.reloadList()
|
||||
}
|
||||
|
||||
function getTemp(){
|
||||
return {
|
||||
id: '',
|
||||
name: '',
|
||||
menus: '',
|
||||
offices: '',
|
||||
permission: '0',
|
||||
code: '',
|
||||
descRibe: ''
|
||||
}
|
||||
}
|
||||
|
||||
function resetTemp() {
|
||||
temp.value = getTemp()
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
resetTemp()
|
||||
dialogTitle.value = '添加'
|
||||
roleFormDialog.value.show()
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
function save(d) {
|
||||
dataForm.value.validate((valid) => {
|
||||
if (valid) {
|
||||
d.loading()
|
||||
proxy.$post('role/save', temp.value).then(() => {
|
||||
d.hideLoading()
|
||||
reloadTable()
|
||||
roleFormDialog.value.hide()
|
||||
proxy.$notify({
|
||||
title: '成功',
|
||||
message: dialogTitle.value + '成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
}).catch(() => d.hideLoading())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
for (var t in temp.value) {
|
||||
temp.value[t] = row[t]
|
||||
}
|
||||
proxy.$get('menu/by/role',{ roleId: row.id }).then(res => {
|
||||
temp.value.menus = res.data.join(',')
|
||||
})
|
||||
proxy.$get('office/by/role',{ roleId: row.id }).then(res => {
|
||||
temp.value.offices = res.data.join(',')
|
||||
})
|
||||
dialogTitle.value = '修改'
|
||||
roleFormDialog.value.show()
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="filter-container">
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<el-button class="filter-item" type="primary" icon="ElEdit" @click="handleCreate">
|
||||
添加
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<mb-table ref="table" v-bind="tableOptions" />
|
||||
|
||||
<el-dialog :title="textMap[dialogStatus]" v-model="dialogFormVisible" :close-on-click-modal="false" width="700px">
|
||||
<el-form ref="dataForm" :inline="true" :model="temp" label-position="right" label-width="100px" style="margin-left: 20px">
|
||||
<el-form-item label="name" prop="name">
|
||||
<el-input v-model="temp.name" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">
|
||||
关闭
|
||||
</el-button>
|
||||
<el-button type="primary" @click="save()">
|
||||
确认
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref, reactive, nextTick, getCurrentInstance } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const tableOptions = reactive({
|
||||
url: 'test/list',
|
||||
page: true,
|
||||
cols: [
|
||||
{
|
||||
field: 'name',
|
||||
title: 'name'
|
||||
},
|
||||
]
|
||||
})
|
||||
const temp = ref(getTemp())
|
||||
const dialogFormVisible = ref(false)
|
||||
const dialogStatus = ref('')
|
||||
const textMap = reactive({
|
||||
update: '修改',
|
||||
create: '添加'
|
||||
})
|
||||
const table = ref()
|
||||
const dataForm = ref()
|
||||
|
||||
function getTemp() {
|
||||
return {
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
table.value.reloadList()
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
temp.value = getTemp()
|
||||
dialogStatus.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
function save() {
|
||||
dataForm.value.validate((valid) => {
|
||||
if (valid) {
|
||||
proxy.$post('test/save', temp.value).then((response) => {
|
||||
dialogFormVisible.value = false
|
||||
proxy.$notify({
|
||||
title: '成功',
|
||||
message: (dialogStatus.value === 'create' ? '创建' : '修改') + '成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
reloadTable()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="80px" style="width: 800px">
|
||||
<el-form-item label="头像" prop="headPortrait">
|
||||
<mb-upload-image v-model="temp.headPortrait" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名/昵称" prop="name">
|
||||
<el-input v-model="temp.name" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model.integer="temp.phone" maxlength="11" autocomplete="new-password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="原密码" prop="password">
|
||||
<el-input v-model="temp.password" type="password" autocomplete="new-password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input v-model="temp.newPassword" type="password" autocomplete="new-password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input v-model="temp.confirmPassword" type="password" autocomplete="new-password" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button class="filter-item" type="primary" @click="save">
|
||||
提交
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref, reactive, getCurrentInstance, nextTick } from 'vue'
|
||||
const temp = ref(getTemp())
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
var validatePass2 = (rule, value, callback) => {
|
||||
if(temp.value.newPassword){
|
||||
if (value === '') {
|
||||
callback(new Error('请再次输入密码'));
|
||||
} else if (value !== this.temp.newPassword) {
|
||||
callback(new Error('两次输入密码不一致!'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
}
|
||||
const rules = reactive({
|
||||
password: [{ required: true, message: '请输入原密码', trigger: 'change' }, { min: 6, message: '密码不少于6位' }],
|
||||
phone: [{ min: 11, message: '请输入11位手机号', trigger: 'change' }],
|
||||
newPassword: [{ min: 6, message: '密码不少于6位' }],
|
||||
confirmPassword: [{ min: 6, message: '密码不少于6位' }, { validator: validatePass2 }],
|
||||
})
|
||||
const dataForm = ref()
|
||||
|
||||
function getTemp() {
|
||||
return {
|
||||
id: '',
|
||||
name: '',
|
||||
password: '',
|
||||
phone: '',
|
||||
headPortrait: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
}
|
||||
}
|
||||
|
||||
function resetTemp() {
|
||||
temp.value = getTemp()
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
function save() {
|
||||
dataForm.value.validate((valid) => {
|
||||
if (valid) {
|
||||
proxy.$post('user/center/update', temp.value).then(() => {
|
||||
proxy.$notify({
|
||||
title: '成功',
|
||||
message: '修改成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
proxy.$common.objAssign(temp.value, proxy.$global.user.info, ['password'])
|
||||
</script>
|
||||
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="80px" v-if="isForm">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="登录名称" prop="username">
|
||||
<el-input v-model="temp.username" autocomplete="new-password" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input v-model="temp.password" type="password" autocomplete="new-password" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名/昵称" prop="name">
|
||||
<el-input v-model="temp.name" autocomplete="new-password" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="temp.phone" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="组织机构" prop="officeId">
|
||||
<treeselect v-model="temp.officeId" :options="officeTree" :show-count="true" placeholder="请选择组织机构" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="选择角色" prop="roles">
|
||||
<mb-select v-model="temp.roles" url="role/list?size=999999" placeholder="请选择角色" labelField="name" valueField="id" :el="{ multiple: true }" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="登录状态" prop="isLogin">
|
||||
<el-radio-group v-model="temp.isLogin" size="small">
|
||||
<el-radio-button label="0">有效</el-radio-button>
|
||||
<el-radio-button label="1">锁定</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Treeselect from 'vue3-treeselect'
|
||||
import 'vue3-treeselect/dist/vue3-treeselect.css'
|
||||
|
||||
import { ref, reactive, onBeforeMount, getCurrentInstance, nextTick, defineExpose } from 'vue'
|
||||
|
||||
const emit = defineEmits(['reload-table'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const rules = reactive({
|
||||
username: [{ required: true, message: '请输入登录名称', trigger: 'change' }],
|
||||
roles: [{ required: true, message: '请选择角色', trigger: 'change' }],
|
||||
officeId: [{ required: true, message: '请选择组织机构', trigger: 'change' }]
|
||||
})
|
||||
const temp = ref(getTemp())
|
||||
const officeTree = ref([])
|
||||
const isForm = ref(true)
|
||||
const dataForm = ref()
|
||||
|
||||
onBeforeMount(() => {
|
||||
proxy.$get('user/offices').then(res => {
|
||||
officeTree.value = res.data.list
|
||||
proxy.$treeTable.deleteEmptyChildren(officeTree.value)
|
||||
})
|
||||
})
|
||||
|
||||
function getTemp() {
|
||||
return {
|
||||
id: '',
|
||||
name: '',
|
||||
username: '',
|
||||
password: '',
|
||||
phone: '',
|
||||
isLogin: 0,
|
||||
roles: null,
|
||||
officeId: null
|
||||
}
|
||||
}
|
||||
|
||||
function resetTemp() {
|
||||
isForm.value = false
|
||||
rules.password = [{ required: true, message: '请输入密码', trigger: 'change' }]
|
||||
temp.value = getTemp()
|
||||
nextTick(() => {
|
||||
isForm.value = true
|
||||
nextTick(() => {
|
||||
dataForm.value.clearValidate()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function save(d) {
|
||||
dataForm.value.validate((valid) => {
|
||||
if (valid) {
|
||||
d.loading()
|
||||
proxy.$post('user/save', temp.value).then(() => {
|
||||
d.hideLoading()
|
||||
proxy.$notify({
|
||||
title: '成功',
|
||||
message: d.title + '成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
emit('reload-table')
|
||||
}).catch(() => d.hideLoading())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getInfo(row) {
|
||||
isForm.value = false
|
||||
delete rules.password
|
||||
nextTick(() => {
|
||||
isForm.value = true
|
||||
})
|
||||
for (var t in temp.value) {
|
||||
if (t !== 'roles') {
|
||||
temp.value[t] = row[t]
|
||||
}
|
||||
}
|
||||
proxy.$get('user/roles', { userId: temp.value.id }).then((res) => {
|
||||
temp.value.roles = res.data.join(',')
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ getInfo, resetTemp })
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,214 @@
|
||||
<style scoped>
|
||||
.left{
|
||||
width: calc(20% - 20px);
|
||||
margin-right: 20px;
|
||||
float: left;
|
||||
}
|
||||
.right{
|
||||
width: 80%;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="left">
|
||||
<mb-tree url="office/tree" :el="{ 'expand-on-click-node': false,'show-checkbox': true }" :checked-ids="[tableOptions.where.officeId]" :expand="false" :search="true" search-width="100%" :checked="false" @check-change="checkChange" />
|
||||
</div>
|
||||
<div class="right">
|
||||
|
||||
<mb-search :where="tableOptions.where" @search="reloadTable">
|
||||
<template #btns>
|
||||
<el-button :loading="downloadLoading" class="filter-item" icon="ElDownload" @click="handleDownload">
|
||||
导出
|
||||
</el-button>
|
||||
</template>
|
||||
</mb-search>
|
||||
|
||||
<el-row style="margin-bottom: 15px">
|
||||
<el-button v-permission="'user:save'" class="filter-item" type="primary" icon="ElEdit" @click="handleCreate">
|
||||
添加
|
||||
</el-button>
|
||||
<mb-button v-permission="'user:delete'" :el="{ plain: true }" :request-url="'user/delete'" :btn-type="'delete'" :request-data="{ id: ids }" :after-handler="reloadTable" />
|
||||
</el-row>
|
||||
|
||||
<mb-table ref="table" v-bind="tableOptions" @selection-change="selectionChange" />
|
||||
|
||||
<mb-dialog ref="userFormDialog" :title="dialogTitle" @confirm-click="userForm.save($event)" width="670px">
|
||||
<template #content>
|
||||
<user-form ref="userForm" :key="'userForm'" @reload-table="reloadTable" />
|
||||
</template>
|
||||
</mb-dialog>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import UserForm from './user-form.vue'
|
||||
|
||||
import { ref, reactive, getCurrentInstance, onMounted, nextTick } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const tableOptions = reactive({
|
||||
url: 'user/list',
|
||||
page: true,
|
||||
selection: true,
|
||||
where: {
|
||||
username: {
|
||||
type: 'input',
|
||||
label: '登录名称',
|
||||
value: ''
|
||||
},
|
||||
name: {
|
||||
type: 'input',
|
||||
label: '姓名/昵称',
|
||||
value: ''
|
||||
},
|
||||
roleId: {
|
||||
type: 'select',
|
||||
label: '角色',
|
||||
value: '',
|
||||
properties: {
|
||||
url: 'role/list?size=999999',
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
el: { multiple: true }
|
||||
}
|
||||
}
|
||||
},
|
||||
cols: [
|
||||
{
|
||||
field: 'username',
|
||||
title: '登录名称',
|
||||
sortable: 'custom'
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '姓名/昵称',
|
||||
sortable: 'custom'
|
||||
},
|
||||
{
|
||||
field: 'officeName',
|
||||
title: '所属机构'
|
||||
},
|
||||
{
|
||||
field: 'roles',
|
||||
title: '角色'
|
||||
},
|
||||
{
|
||||
field: 'phone',
|
||||
title: '手机号',
|
||||
sortable: 'custom'
|
||||
},
|
||||
{
|
||||
field: 'isLogin',
|
||||
title: '禁止登录',
|
||||
type: 'switch',
|
||||
width: 100,
|
||||
change: (row) => {
|
||||
proxy.$get('/user/change/login/status', {
|
||||
id: row.id,
|
||||
isLogin: row.isLogin
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'createDate',
|
||||
title: '创建时间',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
type: 'btns',
|
||||
width: 140,
|
||||
fixed: 'right',
|
||||
btns: [
|
||||
{
|
||||
permission: 'user:save',
|
||||
title: '修改',
|
||||
type: 'text',
|
||||
icon: 'ElEdit',
|
||||
click: (row) => {
|
||||
handleUpdate(row)
|
||||
}
|
||||
},
|
||||
{
|
||||
permission: 'user:delete',
|
||||
title: '删除',
|
||||
type: 'text',
|
||||
icon: 'ElDelete',
|
||||
click: (row) => {
|
||||
proxy.$common.handleDelete({
|
||||
url: 'user/delete',
|
||||
id: row.id,
|
||||
done: () => reloadTable()
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
const dialogTitle = ref('')
|
||||
const downloadLoading = ref(false)
|
||||
const ids = ref([])
|
||||
const userFormDialog = ref()
|
||||
const table = ref()
|
||||
const userForm = ref()
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(function(){
|
||||
console.log(proxy.$route.query.roleId)
|
||||
if(proxy.$route.query.roleId){
|
||||
tableOptions.where.roleId.value = proxy.$route.query.roleId
|
||||
}
|
||||
if(proxy.$route.query.officeId){
|
||||
tableOptions.where.officeId = proxy.$route.query.officeId
|
||||
}
|
||||
},1000)
|
||||
})
|
||||
|
||||
function checkChange(values) {
|
||||
tableOptions.where.officeId = values
|
||||
nextTick(() => reloadTable())
|
||||
}
|
||||
|
||||
function selectionChange(columns) {
|
||||
ids.value = columns.map(it => it['id']).join(',')
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
userFormDialog.value.hide()
|
||||
table.value.reloadList()
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
dialogTitle.value = '添加'
|
||||
userFormDialog.value.show()
|
||||
nextTick(() => {
|
||||
userForm.value.resetTemp()
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
dialogTitle.value = '修改'
|
||||
userFormDialog.value.show()
|
||||
nextTick(() => {
|
||||
userForm.value.getInfo(row)
|
||||
})
|
||||
}
|
||||
|
||||
function handleDownload() {
|
||||
proxy.$common.exportExcel({
|
||||
url: tableOptions.url,
|
||||
headers: ['登录名称', '姓名'],
|
||||
columns: ['username', 'name'],
|
||||
where: tableOptions.where
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||