This commit is contained in:
lijing
2026-08-12 17:19:37 +08:00
commit e73d22744e
131 changed files with 66997 additions and 0 deletions
+473
View File
@@ -0,0 +1,473 @@
$tabBtnColor: #577ae9;//tab主题色
$treeCheckColor: #577ae9;//树主题色
$treeCheckBorderColor: #dddee1;//边框色
$treeCheckGroupColor: #f7f7f7;
.fix {
*zoom: 1;
&::before,
&::after {
display: table;
content: "";
line-height: 0;
}
&::after {
clear: both;
}
}
.fl {
float: left;
}
.fr {
float: right;
}
.align-middle {
position: relative;
top: 50%;
transform: translateY(-50%);
}
.my-tab_btn {
margin: 10px auto;
position: relative;
.item-tab_btn {
font-size: 16px;
font-weight: 550;
color: #656667;
float: left;
text-align: center;
position: relative;
box-sizing: border-box;
height: 32px;
line-height: 32px;
margin-left: 15px;
}
.item-tab_btn.active {
color: #272829;
}
.my-tab_slider {
width: 42px;
position: absolute;
left: 0px;
bottom: -1px;
z-index: 1;
transition: transform 0.3s ease-in-out;
transform-origin: 0 0;
& > .indicator {
height: 2px;
width: 17px;
background: $tabBtnColor;
margin: 0 auto;
}
}
}
.my-tab_card {
margin: 15px 0 15px 10px;
padding-right: 30px;
position: relative;
.item-wrap{
white-space: nowrap;
display: flex;
align-items: center;
overflow: hidden;
overflow-x: scroll;
}
.item-tab_card {
float: left;
font-size: 13px;
padding: 0px 15px;
color: #808080;
height: 27px;
line-height: 27px;
transition: all 200ms;
}
.item-tab_card.active {
color: #272829;
background: #f7f7f7;
border-radius: 27px;
}
}
.my-modal {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0.5;
background: #000;
z-index: 100;
}
.tree-popup {
position: fixed;
background: #fff;
top: 100%;
left: 0;
transform: translateY(0);
backface-visibility: hidden;
transition: 0.2s ease-in;
width: 100%;
height: 85%;
z-index: 101;
border-radius: 8px 8px 0 0;
&_show {
transform: translateY(-100%);
}
&_hide {
transform: translateY(0%);
}
&_header {
position: relative;
overflow: hidden;
@extend .my-border_bottom;
& .close-btn {
position: absolute;
padding: 5px;
right: 3%;
top: 10px;
}
}
&_body {
overflow-y: auto;
min-height: 250px;
}
&_footer {
height: 60px;
width: 100%;
box-sizing: border-box;
background: #fff;
position: relative;
padding: 0 10px 0 20px;
@extend .my-border_top;
.check-box {
float: left;
&_text {
margin-left: 3px;
margin-right: 30px;
vertical-align: middle;
font-size: 16px;
}
}
& p {
font-size: 16px;
color: #808080;
span {
font-size: 18px;
color: $treeCheckColor;
}
}
& button {
width: 80px;
height: 40px;
color: #fff;
background: $treeCheckColor;
border-radius: 5px;
outline: none;
}
}
}
.search-list {
background: #f7f7f7;
height: calc(100% - 46px);
margin-top:46px;
box-sizing: border-box;
overflow-y: auto;
.search-head {
height: 46px;
line-height: 46px;
padding: 0 10px 0 15px;
width: 100%;
position: fixed;
box-sizing: border-box;
background: #f7f7f7;
input {
background: #efefef;
-webkit-appearance: none;
outline: none;
width: calc(100% - 70px);
height: 30px;
line-height: 30px;
padding: 0 15px;
border-radius: 28px;
font-size: 14px;
&::-webkit-input-placeholder {
font-size: 14px;
}
}
span {
margin-left: 5px;
color: #272829;
font-size: 14px;
}
.close-icon {
width: 18px;
line-height: 18px;
height: 18px;
position: absolute;
right: 60px;
top: 15px;
}
}
.search-item {
background: #fff;
height: 46px;
line-height: 46px;
padding-left: 12px;
a {
position: relative;
display: block;
width: 100%;
color: #333;
outline: none;
@extend .my-border_bottom;
}
&_text {
margin-left: 2px;
vertical-align: middle;
}
&_group {
float: right;
color: #808080;
font-size: 12px;
margin-right: 15px;
}
}
}
.user-popup {
position: fixed;
background: #fff;
top: 100%;
left: 0;
transform: translateY(0);
backface-visibility: hidden;
transition: 0.2s ease-in;
width: 100%;
height: 75%;
z-index: 105;
border-radius: 8px 8px 0 0;
&_show {
transform: translateY(-100%);
}
&_hide {
transform: translateY(0%);
}
&_header {
position: relative;
overflow: hidden;
@extend .my-border_bottom;
h4 {
font-size: 18px;
margin: 12px 0 5px;
color: #272829;
text-align: center;
}
h6 {
color: #595959;
font-size: 13px;
margin: 5px 0 15px;
text-align: center;
}
& .close-btn {
position: absolute;
padding: 5px;
right: 3%;
top: 10px;
}
}
&_body {
overflow-y: auto;
height: calc(100% - 70px);
padding: 12px 12px;
box-sizing: border-box;
}
&_ul {
@extend .fix;
li {
background: #f4f8ff;
float: left;
margin: 4px;
width: calc(33.333% - 8px);
border-radius: 4px;
padding: 8px;
box-sizing: border-box;
h4,
h6 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
h4 {
font-size: 16px;
margin: 6px 0;
color: #272829;
text-align: center;
}
h6 {
color: #595959;
font-size: 12px;
height: 16px;
margin: 6px 0;
text-align: center;
}
}
}
}
.search-box {
position: absolute;
right: 10px;
width: 20px;
height: 20px;
box-sizing: border-box;
padding: 2px;
top: 2px;
background: #fff;
}
.bg-arrow_grey {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABE0lEQVRYR+3VMQ6CMBQG4PdImPUIHsEjeBRZCJ30KDqVsNSjeASPwBF0Y6KmiSQM0NfXdiDmsZDQ0n79Wx4IG7twYx4QELUjkpAkRCVAtcsZ+p+EjDH7YRj2SqmeWhWnveu6Y1mWfVVV76X3Vs9Q27Y3RLyM41gppR6cSdf6aq3PRVEYa+29aZorC+RWYq19AsAuB2rCAMAHEU91Xb9YINc5FyoU4+YkP/tUFAcTBEpJiosJBsWgYjAsEAcVi2GDQlApmCiQD5WKiQYtodwzV/SoOkMVWPKz9w0wLwm/ft6iR2GSEpoGn6HAV4FDMFlAbhCt9cHdc/yIk7YsdNWcfgKi0pKEJCEqAapdzpAkRCVAtX8BqXjGJWAVZBoAAAAASUVORK5CYII=");
background-repeat: no-repeat;
display: inline-block;
width: 64px;
height: 64px;
zoom: 0.3;
background-size: contain;
}
.conta-wrap {
position: relative;
.tree-item {
padding: 12px;
color: #333;
position: relative;
a {
color: #333;
display: inline-block;
outline: none;
text-decoration: none;
}
input {
display: none;
}
label {
position: relative;
vertical-align: middle;
margin-left: 2px;
}
&_group {
background: $treeCheckGroupColor;
@extend .my-border_bottom;
}
&_arrow {
&::before {
@extend .bg-arrow_grey;
content: "";
display: block;
position: absolute;
right: 2%;
top: 0;
bottom: 0;
margin: auto 0;
transition: 300ms cubic-bezier(0.47, 0, 0.745, 0.715);
}
}
&_open,
&_close {
@extend .tree-item_arrow;
}
&_close::before {
transform: rotate(180deg);
}
}
}
.my-checkbox-core {
border-radius: 3px;
width: 20px;
height: 20px;
display: inline-block;
background-color: #fff;
border: 1px solid $treeCheckBorderColor;
position: relative;
vertical-align: middle;
}
.my-checkbox-core.active {
background-color: $treeCheckColor;
border-color: $treeCheckColor;
&::after {
border: 2px solid transparent;
border-left: 0;
border-top: 0;
content: " ";
top: 3px;
left: 7px;
position: absolute;
width: 4px;
height: 8px;
-webkit-transform: rotate(45deg) scale(0);
transform: rotate(45deg) scale(0);
transition: -webkit-transform 0.2s;
-webkit-transition: -webkit-transform 0.2s;
transition: transform 0.2s;
transition: transform 0.2s, -webkit-transform 0.2s;
border-color: #fff;
-webkit-transform: rotate(45deg) scale(1);
transform: rotate(45deg) scale(1);
}
}
.my-border_bottom::after {
content: " ";
position: absolute;
color: #d9d9d9;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
border-bottom: 1px solid #d9d9d9;
height: 1px;
left: 0;
bottom: 0;
right: 0;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
transition: 0;
}
.my-border_top::after {
content: " ";
position: absolute;
color: #d9d9d9;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
border-top: 1px solid #d9d9d9;
width: 100%;
height: 1px;
left: 0;
top: 0;
right: 0;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
transition: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 300ms ease-in-out;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
+505
View File
@@ -0,0 +1,505 @@
<template>
<div>
<div :id="domId" :class="['tree-popup', isPopupShow ? 'tree-popup_show' : 'tree-popup_hide']">
<div class="tree-popup_header">
<div class="my-tab_btn fix">
<div
:class="{
'item-tab_btn': true,
active: firstTabIndex == tindex,
}"
@click="tabBtnChange(tindex)"
v-for="(titem, tindex) in firstTabData"
>
{{ titem }}
</div>
<div class="my-tab_slider" v-if="firstTabData.length > 0" :style="tabSliderStyle">
<div class="indicator"></div>
</div>
</div>
<div class="my-tab_card fix" v-if="secondTabData.length > 1">
<div class="item-wrap">
<div
:class="{
'item-tab_card': true,
active: secondTabIndex == tindex,
}"
@click="tabCardChange(tindex)"
v-for="(titem, tindex) in secondTabData"
>
{{ titem }}
</div>
</div>
<div class="search-box" @click="isSearchShow = !isSearchShow">
<svg t="1626398915347" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4152" width="16" height="16">
<path
d="M921.3 874L738.1 690.8c51.3-62.6 82.1-142.5 82.1-229.6 0-200.1-162.8-363-363-363-200.1 0-363 162.8-363 363s162.8 363 363 363c87 0 167-30.8 229.6-82.1L870 925.3c7.1 7.1 16.4 10.6 25.7 10.6s18.6-3.5 25.7-10.6c14.1-14.2 14.1-37.2-0.1-51.3zM166.8 461.2c0-160.1 130.3-290.4 290.4-290.4s290.4 130.3 290.4 290.4-130.3 290.4-290.4 290.4-290.4-130.3-290.4-290.4z"
p-id="4153"
fill="#808080"
></path>
</svg>
</div>
</div>
<div class="close-btn" @click="popupColse">
<svg t="1626397122082" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3133" width="14" height="14">
<path d="M877.216 491.808" p-id="3134" fill="#595959"></path>
<path
d="M575.328 510.496 946.784 140.672c17.568-17.504 17.664-45.824 0.192-63.424-17.504-17.632-45.792-17.664-63.36-0.192L512.032 446.944 143.712 77.216C126.304 59.712 97.92 59.648 80.384 77.12 62.848 94.624 62.816 123.008 80.288 140.576l368.224 369.632L77.216 879.808c-17.568 17.504-17.664 45.824-0.192 63.424 8.736 8.8 20.256 13.216 31.776 13.216 11.424 0 22.848-4.352 31.584-13.056l371.36-369.696 371.68 373.088C892.192 955.616 903.68 960 915.168 960c11.456 0 22.912-4.384 31.648-13.088 17.504-17.504 17.568-45.824 0.096-63.392L575.328 510.496 575.328 510.496zM575.328 510.496"
p-id="3135"
fill="#595959"
></path>
</svg>
</div>
</div>
<div class="tree-popup_body">
<tree v-if="!isSearchShow" :treeData="currentTree" :userids="tempUserIds"></tree>
<div v-else class="search-list">
<div class="search-head" :style="searchHeadStyle">
<input type="text" placeholder="请输入关键字" v-model="searchText" />
<div class="close-icon" v-show="searchText" @click="searchText = null">
<svg t="1626484787749" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2043" width="18" height="18">
<path
d="M512 51.2C258.048 51.2 51.2 258.048 51.2 512s206.848 460.8 460.8 460.8 460.8-206.848 460.8-460.8S765.952 51.2 512 51.2z m181.248 612.352c8.192 8.192 8.192 20.48 0 28.672-4.096 4.096-9.216 6.144-14.336 6.144s-10.24-2.048-14.336-6.144L512 540.672 360.448 693.248c-4.096 4.096-9.216 6.144-14.336 6.144s-10.24-2.048-14.336-6.144c-8.192-8.192-8.192-20.48 0-28.672L483.328 512 330.752 360.448c-8.192-8.192-8.192-20.48 0-28.672s20.48-8.192 28.672 0L512 483.328l151.552-151.552c8.192-8.192 20.48-8.192 28.672 0s8.192 20.48 0 28.672L540.672 512l152.576 151.552z"
p-id="2044"
fill="#cccccc"
></path>
</svg>
</div>
<span @click="isSearchShow = false">取消</span>
</div>
<div class="search-item" v-for="item in searchList">
<a href="javascript:;" @click="checkTreeById(item.id, !item.isCheck)">
<span :class="['my-checkbox-core', item.isCheck ? 'active' : '']"></span>
<span class="search-item_text">{{ item.name }}</span>
<span class="search-item_group">{{ item.group }}</span>
</a>
</div>
</div>
</div>
<div class="tree-popup_footer fix">
<div class="check-box align-middle" v-show="!isSearchShow">
<label @click="saveAllCheck">
<span :class="['my-checkbox-core', isAllTreeCheck ? 'active' : '']"></span>
<span class="check-box_text">全选</span>
</label>
</div>
<p class="fl align-middle" @click="isUserModalShow = true">
已选人数:
<span>{{ tempUserIds.length }}</span>
</p>
<button v-show="isSearchShow" class="fr align-middle" @click="isSearchShow = false">确认</button>
<button v-show="!isSearchShow" class="fr align-middle" @click="submit">提交</button>
</div>
</div>
<div v-if="isModalShow" class="my-modal" @click="popupColse"></div>
<div :class="['user-popup', isUserModalShow ? 'user-popup_show' : 'user-popup_hide']">
<div class="user-popup_header">
<h4>已选人员</h4>
<h6>共{{ tempUserToNames.length }}人</h6>
<div class="close-btn" @click="isUserModalShow = false">
<svg t="1626397122082" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3133" width="14" height="14">
<path d="M877.216 491.808" p-id="3134" fill="#595959"></path>
<path
d="M575.328 510.496 946.784 140.672c17.568-17.504 17.664-45.824 0.192-63.424-17.504-17.632-45.792-17.664-63.36-0.192L512.032 446.944 143.712 77.216C126.304 59.712 97.92 59.648 80.384 77.12 62.848 94.624 62.816 123.008 80.288 140.576l368.224 369.632L77.216 879.808c-17.568 17.504-17.664 45.824-0.192 63.424 8.736 8.8 20.256 13.216 31.776 13.216 11.424 0 22.848-4.352 31.584-13.056l371.36-369.696 371.68 373.088C892.192 955.616 903.68 960 915.168 960c11.456 0 22.912-4.384 31.648-13.088 17.504-17.504 17.568-45.824 0.096-63.392L575.328 510.496 575.328 510.496zM575.328 510.496"
p-id="3135"
fill="#595959"
></path>
</svg>
</div>
</div>
<div class="user-popup_body">
<ul class="user-popup_ul">
<li v-for="item in tempUserToNames">
<h4>{{ item.name }}</h4>
<h6>{{ item.group }}</h6>
</li>
</ul>
</div>
</div>
<div v-if="isUserModalShow" style="z-index: 103" class="my-modal" @click="isUserModalShow = false"></div>
</div>
</template>
<script>
// import { Toast } from "vant";
require("./index.scss");
import tree from "./tree.vue";
export default {
components: { tree },
props: {
treeData: {
type: Array,
default: [],
},
userids: {
type: Array,
default: [],
},
},
model: {
prop: "userids",
event: "change",
},
provide() {
return {
checkTreeById: this.checkTreeById,
};
},
data() {
return {
tempUserIds: [],
firstTabIndex: 0,
secondTabIndex: 0,
tabData: [],
currentTree: [],
tabSliderStyle: {},
searchHeadStyle: {},
isPopupShow: false,
isModalShow: false,
isUserModalShow: false,
isAllTreeCheck: false,
isSearchShow: false,
searchText: null,
cacheMap: new Map(),
duplicateIds: [],
domId: "dom_" + new Date().getTime(),
};
},
computed: {
firstTabData() {
if (this.treeData.length > 0) return this.treeData.map(x => x.name);
else return [];
},
secondTabData() {
if (this.treeData.length == 0) return [];
return this.treeData[this.firstTabIndex].data.map(x => x.name);
},
currentTreeFlatten() {
const flatten = (data, pid, pname) => {
return data.reduce((arr, { id, name, children = [], type, isCheck }) => arr.concat([{ id, name, pid, type, isCheck, group: pname }], flatten(children, id, name)), []);
};
if (!this.currentTree) return [];
var newArr = flatten(this.currentTree, 0, "");
return newArr;
},
searchList() {
const _this = this;
let flattenArr = _this.currentTreeFlatten;
flattenArr = flattenArr.filter(x => x.type == 2);
if (!_this.searchText) return flattenArr;
else return flattenArr.filter(x => x.name.indexOf(_this.searchText) > -1);
},
tempUserToNames() {
const _this = this;
let flattenArr = _this.currentTreeFlatten;
var arr = flattenArr.filter(x => _this.tempUserIds.includes(x.id));
return arr;
},
},
created() {
this.tabData = this.treeData.map(x => x.name);
},
mounted() {},
methods: {
show() {
let timer = null;
if (this.firstTabData.length == 0) {
Toast.loading({ forbidClick: true });
timer = setTimeout(() => {
this.show();
}, 100);
return;
} else {
Toast.clear();
if (!!timer) {
clearTimeout(timer);
}
}
this.isModalShow = this.isPopupShow = true;
this.tabBtnChange(0);
var parentId = "#" + this.domId;
setTimeout(() => {
let treeH = document.querySelector(parentId + ".tree-popup").offsetHeight,
headerH = document.querySelector(parentId + " .tree-popup_header").offsetHeight;
document.querySelector(parentId + " .tree-popup_body").style.height = treeH - headerH - 60 + "px";
this.searchHeadStyle = { top: headerH + "px" };
}, 0);
},
popupColse() {
this.isPopupShow = false;
this.firstTabIndex = 0;
this.secondTabIndex = 0;
setTimeout(() => {
this.isModalShow = false;
}, 250);
},
tabBtnChange(index) {
this.firstTabIndex = index;
this.getTabSliderStyle(index);
this.changeEmpty();
this.tabCardChange(0);
},
tabCardChange(index) {
const _this = this;
var wrap = document.querySelector("#" + this.domId + " .item-wrap");
var tabs = document.querySelectorAll("#" + this.domId + " .item-tab_card");
let currentItem = tabs[index];
var pageX = document.documentElement.clientWidth,
blockW = currentItem.scrollWidth,
moveX = 0;
for (let i = 0; i < tabs.length; i++) {
const element = tabs[i];
if (i > index) break;
moveX += element.scrollWidth;
}
var left = moveX - pageX / 2 + blockW / 2;
wrap.scrollLeft = left;
this.secondTabIndex = index;
this.isAllTreeCheck = false;
this.isSearchShow = false;
this.initTreeData();
},
initTreeData() {
const _this = this;
const checkTree = tree => {
_this.currentTree = tree;
_this.tempUserIds = JSON.parse(JSON.stringify(_this.userids));
let duplArr = _this.duplicateIds.map(x => x.id);
_this.tempUserIds.forEach(element => {
let id = element;
if (duplArr.length > 0 && duplArr.includes(element)) {
let parent = _this.duplicateIds.find(x => x.id == element);
//处理重复id逻辑,仅选择第一个
parent && (id += "," + parent.pid[0]);
}
_this.checkTreeById(id, true);
});
};
let first = _this.firstTabIndex,
second = _this.secondTabIndex,
cacheMap = _this.cacheMap,
tData = _this.treeData;
var tree = tData[first].data[second].data;
var cacheKey = first + "," + second;
if (!!tree && typeof tree === "function") {
var cacheData = cacheMap.get(cacheKey);
if (!cacheData) {
tree().then(data => {
_this.duplicateIds = _this.dupTreeIds(data);
var jtree = JSON.parse(JSON.stringify(data));
cacheMap.set(cacheKey, jtree);
var ftree = _this.formatTree(jtree);
checkTree(ftree);
});
} else {
var ftree = _this.formatTree(cacheData);
checkTree(ftree);
}
} else {
_this.duplicateIds = _this.dupTreeIds(tree);
var jtree = JSON.parse(JSON.stringify(tree));
var ftree = _this.formatTree(jtree);
checkTree(ftree);
}
},
getTabSliderStyle(index) {
var dom = document.querySelectorAll(".item-tab_btn")[index];
if (!dom) return;
this.tabSliderStyle = {
width: dom.offsetWidth + "px",
transform: `translateX(${dom.offsetLeft}px)`,
};
},
saveAllCheck() {
const _this = this;
_this.isAllTreeCheck = !_this.isAllTreeCheck;
const recursionCheckAll = (tree, checked) => {
tree.forEach(element => {
element.isCheck = checked;
if (typeof element.children !== "undefined" && element.children.length > 0) recursionCheckAll(element.children, checked);
});
};
recursionCheckAll(_this.currentTree, _this.isAllTreeCheck);
_this.getTreeCheckIds();
},
changeEmpty() {
this.tempUserIds = [];
},
submit() {
const _this = this;
let objArr = [],
idsArr = this.tempUserIds.map(x => _this.getSplitId(x));
idsArr = Array.from(new Set(idsArr));
this.$emit("change", idsArr);
function delKey(obj, ...args) {
args.forEach(v => delete obj[v]);
return obj;
}
idsArr.forEach(element => {
let findObj = _this.currentTreeFlatten.find(x => _this.getSplitId(x.id) == element);
delKey(findObj, "id", "isCheck", "pid", "type");
objArr.push({ ...findObj, id: element });
});
this.$emit("on-submit", objArr);
this.popupColse();
},
getSplitId(strId) {
if (typeof strId === "string" && strId.indexOf(",") != -1) return parseInt(strId.split(",")[0]);
else return strId;
},
//#region treeOpera
formatTree(tree, level = 0) {
const _this = this;
tree.forEach(element => {
var findObj = _this.duplicateIds.map(x => x.id).find(x => x == element.id);
if (!!findObj) element.id = element.id + "," + element.pid; //重复id加上组id以免重复
element.show = level == 0;
element.isCheck = false;
element.type = 1;
if (typeof element.children !== "undefined" && element.children.length > 0) element.children = _this.formatTree(element.children, level + 1);
else {
// element.id = element.id + "," + element.pid; //防止用户在多个组重复id,加上组id以免重复
element.type = 2;
}
});
return tree;
},
flattenTree(tree) {
const flatten = (data, pid) => {
return data.reduce((arr, { id, name, children = [], type }) => arr.concat([{ id, name, type, pid }], flatten(children, id)), []);
};
return flatten(tree, 0);
},
dupTreeIds(tree) {
//一个人可能在多个组,取出这些id
const flatten = (data, pid) => {
return data.reduce((arr, { id, name, children = [] }) => arr.concat([{ id, name, pid }], flatten(children, id)), []);
};
// var flattenArr = flatten(tree, 0);
var flattenArr = this.flattenTree(tree);
var ctnArr = flattenArr.reduce((arr, { id, pid }) => {
var findObj = arr.find(x => x.id == id);
if (!!findObj) {
findObj.count += 1;
findObj.pid.push(pid);
} else arr.push({ id, count: 1, pid: [pid] });
return arr;
}, []);
// var dupArr = ctnArr.filter((x) => x.count > 1).map((x) => x.id);
var dupArr = ctnArr.filter(x => x.count > 1);
return dupArr;
},
findTreePids(id, tree) {
const _this = this;
var flattenArr = _this.flattenTree(tree);
var arr = [];
while (id > 0) {
var findObj = flattenArr.find(x => x.id == id);
if (findObj) {
id = findObj.pid;
arr.push(id);
} else {
id = -1;
}
}
return arr;
},
findTreeChildIds(id, tree) {
const _this = this;
const recursionChildIds = tree => {
let arrIds = [];
for (const key in tree) {
const element = tree[key];
arrIds.push(element.id);
if (typeof element.children !== "undefined" && element.children) {
var childIds = recursionChildIds(element.children);
arrIds = [...arrIds, ...childIds];
}
}
return arrIds;
};
let childTree = [];
if (id == 0) childTree = tree;
else {
var ctree = _this.findTreeByid(id, tree);
if (ctree && typeof ctree.children !== "undefined" && ctree.children) childTree = ctree.children;
else return [];
}
var ids = recursionChildIds(childTree);
return ids;
},
findTreeByid(id, tree) {
const _this = this;
var findTree;
for (const key in tree) {
const element = tree[key];
if (element.id == id) findTree = element;
if (typeof element.children !== "undefined" && element.children && !findTree) {
findTree = _this.findTreeByid(id, element.children);
}
}
return findTree;
},
findTreeByPid(id, tree) {
const _this = this;
var findId;
for (const key in tree) {
const element = tree[key];
if (element.id == id) {
findId = element.pid;
}
if (typeof element.children !== "undefined" && element.children && !findId) {
findId = _this.findTreeByPid(id, element.children);
}
}
return findId;
},
checkTreeById(id, checked) {
const _this = this;
var pids = this.findTreePids(id, this.currentTree);
var cids = this.findTreeChildIds(id, this.currentTree);
var idArr = [...cids, id];
checked && (idArr = [...pids, ...idArr]);
idArr.forEach(element => {
var t = this.findTreeByid(element, this.currentTree);
t && (t.isCheck = checked);
});
if (!checked) {
pids.forEach(element => {
var t = _this.findTreeByid(element, this.currentTree);
if (!t || typeof t.children === "undefined") return;
var findIndex = t.children.findIndex(x => x.isCheck && x.id != id);
findIndex === -1 && (t.isCheck = false);
});
}
this.getTreeCheckIds();
},
getTreeCheckIds() {
const _this = this;
var isEndWithType = true; //是否只返回最末端的结点(type=2)
const recursionCheckIds = tree => {
let arrIds = [];
for (const key in tree) {
const element = tree[key];
if (element.isCheck && (isEndWithType ? element.type == 2 : true)) arrIds.push(element.id);
if (typeof element.children !== "undefined" && element.children) {
var childIds = recursionCheckIds(element.children);
arrIds = [...arrIds, ...childIds];
}
}
return arrIds;
};
var t = recursionCheckIds(_this.currentTree);
this.tempUserIds = t;
},
//#endregion
},
};
</script>
+80
View File
@@ -0,0 +1,80 @@
<template>
<ul class="conta-wrap">
<li v-for="(item, index) in treeData">
<div
:class="['tree-item', getItemClass(item)]"
:style="{ 'padding-left': 30 * level + 12 + 'px' }"
@click="itemClick(item)"
>
<a
href="javascript:;"
style="dispaly: block"
@click.stop="itemCheck(item, $event)"
>
<input type="checkbox" v-model="item.isCheck" />
<span
:class="[
'my-checkbox-core',
item.isCheck ? 'active' : '',
]"
></span>
<label>{{ item.name }}</label>
</a>
</div>
<transition name="fade">
<tree
v-if="item.show"
:userids="userids"
:treeData="item.children"
:level="level + 1"
></tree>
</transition>
</li>
</ul>
</template>
<script>
require("./index.scss");
export default {
name: "tree",
inject: ["checkTreeById"],
props: {
level: {
type: Number,
default: 0,
},
treeData: {
type: Array,
default: [],
},
userids: {
type: Array,
default: [],
},
},
methods: {
getItemClass(item) {
if (item.type == 2) return;
let className = " tree-item_" + (item.show ? "open" : "close");
className += this.level === 0 ? " tree-item_group " : "";
return className;
},
itemCheck(item, e) {
if (
typeof e !== "undefined" &&
e.target.tagName.toLowerCase() === "input"
)
return;
this.checkTreeById(item.id, !item.isCheck);
},
itemClick(item) {
if (item.type == 2) {
// this.itemCheck(item);
this.checkTreeById(item.id, !item.isCheck);
} else if (item.type == 1) {
item.show = !item.show;
}
},
},
};
</script>
+54
View File
@@ -0,0 +1,54 @@
<template>
<Modal v-model="modal" :title="name" @on-visible-change="showChange" width="70%" footer-hide>
<Button class="js_copy_code" data-clipboard-action="copy" :data-clipboard-text="currentHtml">复制代码</Button>
<div v-highlight>
<pre v-show="isLoad">
<code id="test" class="html"></code>
</pre>
</div>
</Modal>
</template>
<script>
import { load } from "../../assets/js/code";
export default {
props: {
name: String,
url: String,
type: String,
},
data() {
return {
modal: false,
isLoad: false,
currentHtml: null,
clipboard: null,
};
},
async mounted() {
const _this = this;
this.modal = true;
this.isLoad = false;
let { html, encode } = await load(_this.url);
$("#test").attr("class", _this.type).html(encode);
this.isLoad = true;
this.currentHtml = html;
this.clipboard = new ClipboardJS(".js_copy_code");
this.clipboard.on("success", function (e) {
_this.$Message.success("copy success!");
e.clearSelection();
});
},
methods: {
showChange(state) {
if (!state) this.destroy();
},
destroy() {
this.clipboard.destroy();
setTimeout(() => {
document.body.removeChild(this.$el);
}, 300);
},
},
};
</script>
+24
View File
@@ -0,0 +1,24 @@
import Vue from "vue";
import codemodal from "./code_modal.vue";
import { Message } from "view-design";
export default props => {
// 创建实例
const vm = new Vue({
render(h) {
// h为createElement,返回VNode虚拟Node
return h(codemodal, { props });
}
}).$mount();
// 手动挂载实例到body
document.body.appendChild(vm.$el);
const comp = vm.$children[0];
// 销毁方法
// comp.remove = function() {
// document.body.removeChild(vm.$el);
// vm.$destroy();
// };
// 返回组件
return comp;
};
+240
View File
@@ -0,0 +1,240 @@
<template>
<div>
<!-- 富文本编辑器 -->
<quill-editor v-model="content"
ref="myTextEditor"
:options="editorOption"
@change="onChange"
>
<div id="toolbar" slot="toolbar">
<span class="ql-formats"><button type="button" class="ql-bold"></button></span>
<span class="ql-formats"><button type="button" class="ql-italic"></button></span>
<span class="ql-formats"><button type="button" class="ql-underline"></button></span>
<span class="ql-formats"><button type="button" class="ql-strike"></button></span>
<!-- 有序和无序 -->
<span class="ql-formats"><button type="button" class="ql-list" value="ordered"></button></span>
<span class="ql-formats"><button type="button" class="ql-list" value="bullet"></button></span>
<!-- 缩进 -->
<span class="ql-formats"><button type="button" class="ql-indent" value="-1"></button></span>
<span class="ql-formats"><button type="button" class="ql-indent" value="+1"></button></span>
<span class="ql-formats"> <button type="button" class="ql-direction" value="rtl"></button></span>
<!-- 自定义下拉 -->
<span class="ql-formats">
<select class="ql-size" id="ql-size">
<option value="small">小号</option>
<option value="">正常</option>
<option value="large">大号</option>
<option value="huge">特大</option>
</select>
<!-- <i-select size="small" @on-change="mychange">
<i-option value="small">小号</i-option>
<i-option value="large">正常</i-option>
<i-option value="huge">特大</i-option>
</i-select> -->
</span>
<span class="ql-formats">
<select class="ql-header">
<option value="1">标题1</option>
<option value="2">标题2</option>
<option value="3">标题3</option>
<option value="4">标题4</option>
<option value="5">标题5</option>
<option value="6">标题6</option>
<option selected="selected">正文</option>
</select>
</span>
<span class="ql-formats">
<select class="ql-color" v-model="fontColor">
<option v-for="item in colorArry" :key="item" :value="'#'+item"></option>
</select>
</span>
<span class="ql-formats">
<select class="ql-background" v-model="bgColor">
<option v-for="item in colorArry" :key="item" :value="'#'+item"></option>
</select>
</span>
<span class="ql-formats">
<select class="ql-font">
<option selected="selected"></option>
<option value="serif"></option>
<option value="monospace"></option>
</select>
</span>
<span class="ql-formats">
<select class="ql-align">
<option selected="selected"></option>
<option value="center"></option>
<option value="right"></option>
<option value="justify"></option>
</select>
</span>
<!-- <span class="ql-formats">
<button type="button" @click="isLink=true" class="ql-link"></button>
</span> -->
<span class="ql-formats">
<button type="button" class="my-formats-image">
<Upload
:action="formItem.uploadUrl"
:on-success="handleSuccess"
:data="uploadData"
accept="image/gif,image/jpeg,image/jpg,image/png">
<Icon type="image" size="16"></Icon>
</Upload>
</button>
</span>
</div>
</quill-editor>
<!-- 添加链接 -->
<Modal v-model="isLink" title="添加链接" width="450px">
<i-form :label-width="85" class="myform" ref="formItem.link" :model="formItem.link">
<Form-Item label="链接地址:" prop="url">
<i-input v-model="formItem.link.url" placeholder="请输入链接地址"></i-input>
</Form-Item>
<Form-Item label="链接名称:" prop="tit">
<i-input v-model="formItem.link.tit" placeholder="请输入链接名称"></i-input>
</Form-Item>
</i-form>
<div slot="footer">
<i-Button type="primary" @click="addLinkSave()">提交</i-Button>
<i-Button type="ghost" @click="myCancel('isLink','formItem.link')" style="margin-left: 8px">取消</i-Button>
</div>
</Modal>
</div>
</template>
<script>
// import {quillEditor} from 'vue-quill-editor'
export default {
// components: {quillEditor},
props: {
// 提示内容
placeholder:{
type: String
},
/*编辑器内容*/
value: {
type: String
},
/*上传图片的地址*/
uploadUrl: {
type: String
},
readonly:Boolean,
},
data() {
return {
content:this.value,
size:'',
fontColor:"000000",
bgColor:"ffffff",
colorArry:['000000','e60000','ff9900','ffff00','008a00','0066cc','9933ff','ffffff','facccc','ffebcc','ffffcc','cce8cc','cce0f5','ebd6ff','bbbbbb','f06666','ffc266','ffff66','66b966','66a3e0','c285ff','888888','a10000','b26b00','b2b200','006100','0047b2','6b24b2','444444','5c0000','663d00','666600','003700','002966','3d1466'],
editorOption: {
modules: {
toolbar: '#toolbar',
},
placeholder:this.placeholder || '请输入内容……',
readOnly: this.readonly,
},
showCrop:false,
isLink:false,
isImage:false,
formItem:{
link:{
url:'',
tit:'',
},
uploadUrl:this.uploadUrl || '',
},
uploadData:{
AppType:window.location.pathname.split('/')[1],
},
}
},
methods: {
onChange(){
this.$emit('input', this.content)
},
myCancel(name, resetName) {
var vm = this;
vm[name] = false;
if (resetName) {
console.log(vm.$refs[resetName])
this.$refs[resetName].resetFields();
}
},
addLinkSave(){
var _that=this;
_that.isLink=false;
_that.editor.focus();
_that.editor.insertText(_that.editor.getSelection().index, ' ', '', true);
_that.editor.insertText(_that.editor.getSelection().index-1, _that.formItem.link.tit+' ', 'link','http://'+ _that.formItem.link.url);
},
handleSuccess(res, file){
this.editor.focus();
console.log(res);
var url=res.host+res.fileurl;
this.editor.insertEmbed(this.editor.getSelection().index, 'image', url);
},
// onFocus(){
// $(".ql-editor img").removeClass("active");
// },
},
computed: {
editor() {
return this.$refs.myTextEditor.quill;
}
},
mounted(){
var _that=this;
// $(".ql-editor img").on('click',function(){
// _that.editor.blur();
// $(this).addClass("active");
// })
},
watch: {
'value'(newVal, oldVal) {
if (this.editor) {
if (newVal !== this.content) {
this.content = newVal
}
}
},
}
}
</script>
<style lang="scss">
.ql-tooltip{
display: none !important;
}
.my-formats-image{
outline: none;
ul.ivu-upload-list{
display: none;
}
}
.ql-container .ql-editor {
min-height: 20em;
padding-bottom: 1em;
max-height: 25em;
}
.ql-editor{
position: relative;
img{
position: relative;
}
img::after {
content:'123123';
display: block;
width: 12px;
height: 12px;
position: absolute;
top:0;right: 0;
background: red;
}
}
</style>
+64
View File
@@ -0,0 +1,64 @@
<template>
<Modal v-model="modal" @on-visible-change="showChange" :title="title || '提示'">
<Alert type="warning" class="mb-1" show-icon>
<Icon type="ios-bulb-outline" :class="{ 'mt-5': !info }" slot="icon" :size="28"></Icon>
<div v-if="msg" :class="['message-box', info ? '' : 'mt-10 mb-10 ml-10']">{{ msg }}</div>
<span v-if="info" slot="desc">{{ info }}</span>
</Alert>
<div slot="footer">
<Button type="primary" @click="ok">确定</Button>
<Button @click="cancle">取消</Button>
</div>
</Modal>
</template>
<script>
export default {
props: {
title: String,
msg: String,
info: String,
onOk: Function,
onCancel: Function,
},
data() {
return {
modal: false,
};
},
mounted() {
this.$nextTick(() => {
this.modal = true;
});
},
methods: {
ok() {
this.modal = false;
typeof this.onOk === "function" && this.onOk();
},
cancle() {
this.modal = false;
typeof this.onCancel === "function" && this.onCancel();
},
showChange(state) {
if (!state) this.destroy();
},
destroy() {
setTimeout(() => {
document.body.removeChild(this.$el);
}, 300);
},
},
};
</script>
<style scoped>
.my-confirwm {
margin-bottom: 0;
}
.message-box {
font-size: 16px;
color: #17233d;
display: block;
}
</style>
+23
View File
@@ -0,0 +1,23 @@
import Vue from "vue";
import confirm from "./confirm.vue";
export default props => {
// 创建实例
const vm = new Vue({
render(h) {
// h为createElement,返回VNode虚拟Node
return h(confirm, { props });
}
}).$mount();
// 手动挂载实例到body
document.body.appendChild(vm.$el);
const comp = vm.$children[0];
// 销毁方法
// comp.remove = function() {
// document.body.removeChild(vm.$el);
// vm.$destroy();
// };
// 返回组件
return comp;
};
+95
View File
@@ -0,0 +1,95 @@
<template>
<div :id="domId" :ref="domId" class="ztree"></div>
</template>
<script>
require("@public/libs/ztree/zTreeStyle.css");
import "@public/libs/ztree/jquery.ztree.all.min.js";
export default {
props: {
treeData: {
type: Object,
},
setting: {
type: Object,
},
},
data() {
return {
domId: "",
treeSetting: {
view: {
dblClickExpand: false,
showIcon: false,
},
check: {
enable: false,
},
callback: {},
},
};
},
created() {
this.$watch("treeData", (val) => {
if (val) {
this.$nextTick(() => {
this.initTree(this.treeData, this.domId);
});
}
});
},
mounted() {
this.domId = this.uuid();
},
methods: {
uuid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
}
);
},
initTree(_data, _domId) {
const _this = this;
_this.treeSetting.callback.onClick = (
event,
treeId,
treeNode,
clickFlag
) => {
// console.log(event, treeId, treeNode, clickFlag);
_this.$emit("on-tree-click", treeNode);
};
var zNodes = _this.openFistNode(_data);
$.fn.zTree.init($("#" + _domId), _this.treeSetting, zNodes);
var treeObj = $.fn.zTree.getZTreeObj(_domId);
var nodes = treeObj.getNodes();
if (nodes && nodes.length > 0) {
setTimeout(function () {
$("#" + nodes[0].tId + "_a").click();
}, 0);
}
},
openFistNode(_zNodes) {
//展开一级节点
if (_zNodes && _zNodes.length > 0) {
for (var key in _zNodes) {
if (_zNodes.hasOwnProperty(key)) {
var element = _zNodes[key];
element.open = true;
}
}
} else {
_zNodes.open = true;
}
return _zNodes;
},
},
};
</script>
<style>
</style>
+159
View File
@@ -0,0 +1,159 @@
<template>
<div :id="domId" :ref="domId" class="ztree"></div>
</template>
<script>
require("@public/libs/ztree/zTreeStyle.css");
import "@public/libs/ztree/jquery.ztree.all.min.js";
export default {
props: {
treeData: {
type: Object,
default: () => ({})
},
setting: {
type: Object,
default: () => ({})
},
// 外部传入需要默认选中的节点id,不传则选中第一个一级节点
selectId: {
type: [Number, String],
default: null
}
},
data() {
return {
domId: "",
treeSetting: {
view: {
dblClickExpand: false,
showIcon: false,
},
check: {
enable: false,
},
callback: {},
},
};
},
watch: {
treeData(val) {
if (val && val.length) {
this.$nextTick(() => {
this.initTree(this.treeData, this.domId);
});
}
},
// 监听外部传入选中id变化,自动重新选中
selectId() {
this.$nextTick(() => {
this.handleDefaultSelect();
})
}
},
created() {
this.$watch("treeData", (val) => {
if (val) {
this.$nextTick(() => {
this.initTree(this.treeData, this.domId);
});
}
});
},
mounted() {
this.domId = this.uuid();
},
methods: {
getZtreeInstance() {
return $.fn.zTree.getZTreeObj(this.domId);
},
uuid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
}
);
},
/**
* 核心方法:根据节点id选中节点,并逐层展开所有父节点
* @param {String|Number} targetId 目标节点id
*/
selectNodeById(targetId) {
const treeObj = $.fn.zTree.getZTreeObj(this.domId);
if (!treeObj || !targetId) return;
const targetNode = treeObj.getNodeByParam('id', targetId);
if (!targetNode) return;
// 高亮选中节点
treeObj.selectNode(targetNode);
// 循环向上展开所有父节点
let currNode = targetNode;
while (currNode) {
treeObj.expandNode(currNode, true, false, false);
currNode = currNode.getParentNode();
}
},
/**
* 默认选中逻辑:有selectId选对应节点,无则选第一个一级节点
*/
handleDefaultSelect() {
const treeObj = $.fn.zTree.getZTreeObj(this.domId);
if (!treeObj) return;
const rootNodes = treeObj.getNodes();
if (!rootNodes || rootNodes.length === 0) return;
if (this.selectId !== null && this.selectId !== undefined) {
// 外部传入id,选中指定节点
this.selectNodeById(this.selectId);
} else {
// 无传入id,默认选中第一个一级节点
const firstRoot = rootNodes[0];
treeObj.selectNode(firstRoot);
treeObj.expandNode(firstRoot, true, false, false);
}
},
initTree(_data, _domId) {
const _this = this;
// 合并自定义setting
_this.treeSetting = Object.assign({}, _this.treeSetting, _this.setting);
// 绑定点击回调
_this.treeSetting.callback.onClick = (event, treeId, treeNode) => {
_this.$emit("on-tree-click", treeNode);
};
// 绑定复选框点击回调
_this.treeSetting.callback.onCheck = (event, treeId, treeNode) => {
const zTreeObj = $.fn.zTree.getZTreeObj(treeId);
// getCheckedNodes(true) 获取所有手动勾选节点,不分层级
const allChecked = zTreeObj.getCheckedNodes(true);
this.$emit('on-tree-Check', allChecked);
};
// 展开所有一级节点
var zNodes = _this.openFistNode(_data);
// 初始化树
$.fn.zTree.init($("#" + _domId), _this.treeSetting, zNodes);
// 执行默认选中逻辑
this.$nextTick(() => {
this.handleDefaultSelect();
})
},
openFistNode(_zNodes) {
if (_zNodes && _zNodes.length > 0) {
for (var i = 0; i < _zNodes.length; i++) {
_zNodes[i].open = true;
}
}
return _zNodes;
}
},
};
</script>
<style>
</style>
+112
View File
@@ -0,0 +1,112 @@
<template>
<div>
<Table
:columns="columns"
:data="listData"
@on-selection-change="selectChange"
class="link-table table-bottom-box"
:height="tableHeight"
no-data-text="暂无数据"
></Table>
<div class="page-panel">
<div class="fl">
<slot :sel-ids="selectList"></slot>
</div>
<Page
class="fr"
:total="total"
:current="dataParmas.page"
:page-size="dataParmas.count"
:page-size-opts="[10, 20, 30, 40]"
@on-page-size-change="pageSizeUpdate"
@on-change="pageUpdate"
placement="top"
show-total
show-sizer
></Page>
</div>
</div>
</template>
<script>
export default {
props: {
tableHeight: {
default: 0,
type: Number,
},
dataUrl: {
default: "",
type: String,
},
dataParmas: {
default: {},
type: Object,
},
dataFilter: {
type: Function,
default: null,
},
columns: {
default: [],
type: Array,
},
autoLoad: {
default: true,
Type: Boolean,
},
},
data() {
return {
listData: [],
selectList: [],
total: 30,
};
},
created() {
this.dataParmas.page = 1;
this.dataParmas.count = parseInt(localStorage.getItem("customPageCount") || 10);
if (this.autoLoad) this.getData();
},
methods: {
getData() {
const _this = this;
let item = {},
{ dataUrl, dataParmas } = this;
// console.log("dataParmas", dataParmas);
for (const key in dataParmas) {
const element = dataParmas[key];
item[key] = element;
}
_this.$api.get(dataUrl, item, resp => {
let rData = resp.data;
if (typeof _this.dataFilter === "function") {
let fData = _this.dataFilter(rData);
_this.listData = JSON.parse(JSON.stringify(fData));
} else _this.listData = rData;
});
},
selectChange(selection) {
this.selectList = selection;
},
pageUpdate(page) {
this.dataParmas.page = page;
this.getData();
},
pageSizeUpdate(size) {
this.dataParmas.page = 1;
this.dataParmas.count = size;
localStorage.setItem("customPageCount", size);
this.getData();
},
reloadData() {
this.getData();
},
},
};
</script>
<style>
</style>
+169
View File
@@ -0,0 +1,169 @@
<template>
<div class="type_set-box">
<div class="type_name">
<i class="iconfont icon-stop fl mr-10" v-if="!isEdit && !active" style="color: #ff5959"></i>
<Input v-if="isEdit" v-model="type.name" ref="type_input" @on-blur="update" @on-enter="update" />
<span v-else class="fl">{{ type.name }}</span>
</div>
<div class="type_action flex">
<div class="action_item" @click="open">
<span v-if="active">停用</span>
<span v-else>启用</span>
</div>
<div class="action_edit" @click="edit">
<span>编辑</span>
</div>
<div class="action_delete" @click="remove">
<span>删除</span>
</div>
</div>
<div class="divider"></div>
<div class="divider2"></div>
</div>
</template>
<script>
export default {
name: "TypeSetBox",
props: {
type: {
name: { type: String, default: "" },
id: { type: String, default: "" },
},
active: { type: Boolean, default: true },
},
data() {
return {
isEdit: false,
eventType: "",
};
},
watch: {
type: {
deep: true,
handler(newVal) {
if (this.isEdit) {
this.isEdit = false;
}
},
},
"type.name"(newVal) {
if (this.isEdit) {
this.isEdit = false;
}
},
},
methods: {
open() {
this.$emit("active", this.type.id);
},
edit() {
this.isEdit = !this.isEdit;
if (this.isEdit) this.$nextTick(() => this.$refs["type_input"].focus());
},
update(e) {
if (!this.type.name) {
this.$Message.error("值不得为空!");
return;
}
if (this.eventType == "") {
this.eventType = e.type;
this.isEdit = false;
this.$emit("edit", this.type.id, this.type.name);
setTimeout(() => {
this.eventType = "";
}, 500);
}
},
remove() {
this.$emit("remove", this.type.id);
},
},
};
</script>
<style lang="scss" scoped>
.flex {
display: flex;
}
.type_set-box {
width: 222px;
background: #ffffff;
margin-right: 15px;
margin-bottom: 15px;
.type_name {
height: 66px;
border: 1px solid #e9e9e9;
border-bottom: none;
padding-left: 15px;
padding-right: 15px;
line-height: 66px;
display: flex;
justify-content: center;
align-items: center;
span {
height: 24px;
font-size: 15px;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 500;
color: #272829;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 222px;
display: inline-block;
}
.iconfont {
font-size: 26px;
}
}
.type_action {
flex-wrap: wrap;
justify-content: center;
align-items: center;
width: 222px;
height: 30px;
line-height: 30px;
background: #f7f7f7;
border-radius: 0px 0px 2px 2px;
border-top: none;
border: 1px solid #e9e9e9;
span {
font-size: 12px;
}
div {
cursor: pointer;
flex: 1;
text-align: center;
}
div:hover {
color: #6c91f0;
}
}
.divider {
// position: relative;
height: 14px;
width: 1px;
background: #e8e8e8;
// left: 73px;
// top: -23px;
float: left;
margin-left: 71px;
margin-top: -22px;
}
.divider2 {
// position: relative;
// left: 146px;
// top: -37px;
height: 14px;
width: 1px;
background: #e8e8e8;
float: left;
margin-left: 147px;
margin-top: -22px;
}
}
</style>
+100
View File
@@ -0,0 +1,100 @@
<template>
<div>
<ul class="public-upload-img fix">
<li v-for="(item, index) in uploadList">
<template v-if="item.status === 'finished'">
<img :src="item.url" alt="" />
<div class="upload-list-cover">
<Icon type="ios-eye-outline" size="24" title="预览图片" @click.native="handleView(item.url)"></Icon>
<Icon size="24" class="ml-10" title="删除图片" type="ios-trash-outline" @click.native="handleRemove(index)"></Icon>
</div>
</template>
<template v-else>
<Progress v-if="item.showProgress" :percent="item.percentage" class="mt-24" hide-info></Progress>
</template>
</li>
<li class="add-li">
<Upload
ref="upload"
:show-upload-list="false"
:on-success="handleSuccess"
:format="['jpg', 'jpeg', 'png', 'gif']"
accept="image/gif,image/jpeg,image/jpg,image/png"
:max-size="2048"
:on-format-error="handleFormatError"
:on-exceeded-size="handleMaxSize"
:default-file-list="defaultList"
type="drag"
:action="uploadUrl"
>
<div class="add-box">
<Icon type="ios-add" size="50"></Icon>
</div>
</Upload>
</li>
</ul>
<Modal title="图片预览" v-model="visible" footer-hide>
<img :src="imgName" v-if="visible" style="width: 100%" />
</Modal>
</div>
</template>
<script>
export default {
props: {
baseUrl: { type: String, default: "" },
imgList: { type: Array, default: [] },
uploadUrl: { type: String, default: "" },
},
data() {
return {
imgName: "",
visible: false,
uploadList: [],
defaultList: [],
};
},
created() {
let arr = [];
this.imgList.forEach(element => {
arr.push({
name: "",
url: this.baseUrl + element,
});
});
this.defaultList = arr;
},
mounted() {
console.log(this.$refs.upload.fileList);
this.uploadList = this.$refs.upload.fileList;
},
methods: {
handleView(name) {
this.imgName = name;
this.visible = true;
},
handleRemove(index) {
this.$refs.upload.fileList.splice(index, 1);
this.imgList.splice(index, 1);
},
handleSuccess(res, file) {
console.log(res, file);
file.url = this.baseUrl + res.data;
this.imgList.push(res.data);
},
handleFormatError(file) {
console.error(file);
},
handleMaxSize(file) {
this.$Notice.warning({
title: "图片过大",
desc: "图片最大上传大小为20M.",
});
},
},
};
</script>
<style>
</style>
+456
View File
@@ -0,0 +1,456 @@
<template>
<Modal v-model="modal" title="选择用户" width="720px" :mask-closable="false" :closable="false">
<Tabs class="tabs-panel" type="card" v-if="firstTabIndex && firstTabIndex.length > 1" v-model="firstTabIndex" @on-click="firstTabChange">
<TabPane v-for="(item, index) in firstTabData" :label="item" :name="index + ''"></TabPane>
</Tabs>
<AutoComplete @on-search="handleSearch" @on-select="handleSelect" placeholder="输入名字搜索" v-model="searchTreeName" :clearable="true" class="auto-box">
<Option v-for="item in currentFlatten" :value="item.name">
<span>{{ item.name }}</span>
<span class="fr info">{{ item.group }}</span>
</Option>
</AutoComplete>
<div class="tree-wrap fix">
<div class="tree-panel fl">
<ul class="tree-tab-ul fix">
<li
@click="secondTabChange(index)"
:style="{ width: 100 / secondTabData.length + '%' }"
:class="{ active: index == secondTabIndex, fl: true }"
v-for="(item, index) in secondTabData"
>
{{ item }}
</li>
</ul>
<div class="tree-box">
<div :id="ztreeDomId" class="ztree"></div>
</div>
</div>
<div class="arrow-box fl">
<svg t="1626916477711" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10021" width="36" height="36">
<path
d="M138.935 256.078v511.844h63.42V256.078h-63.42z m126.842 0v511.844h127.215V256.078H265.777zM885.064 512L583.254 64.323v191.755H456.414v511.844h126.842v191.755L885.064 512z"
p-id="10022"
fill="#2d8cf0"
></path>
</svg>
</div>
<div class="user-panel fr">
<div class="title-box fix">
<Checkbox @on-change="checkAllUser" v-model="isCheckUserAll">全选</Checkbox>
<div class="fr">{{ tempUserList.length }}</div>
<a href="javascript:;" @click="delCheckUser" v-if="tempUserList.findIndex(x => x.isCheck) > -1">删除选中</a>
</div>
<ul class="user-box">
<li :class="{ active: item.isCheck }" v-for="(item, index) in tempUserList" @click="checkUserOne(item)">
{{ item.name }}
<span class="info">{{ item.group }}</span>
</li>
</ul>
</div>
</div>
<div slot="footer">
<i-Button type="primary" @click="submit">提交</i-Button>
<i-Button @click="modal = false" style="margin-left: 8px">取消</i-Button>
</div>
</Modal>
</template>
<script>
require("@public/libs/ztree/zTreeStyle.css");
import "@public/libs/ztree/jquery.ztree.all.min.js";
export default {
props: {
treeData: {
type: Array,
default() {
return [];
},
},
userids: {
type: Array,
default() {
return [];
},
},
ignoreIds: {
type: Array,
default() {
return [];
},
},
},
model: {
prop: "userids",
event: "change",
},
data() {
return {
ztreeDomId: null,
modal: false,
isCheckUserAll: false,
firstTabIndex: 0,
secondTabIndex: 0,
cacheMap: new Map(),
currentTree: [],
currentFlatten: [],
searchTreeName: null,
tempUserList: [],
isDisabled: false,
};
},
watch: {
searchTreeName(val) {
this.handleSearch(val);
},
},
computed: {
firstTabData() {
if (this.treeData.length > 1) return this.treeData.map(x => x.name);
else return [];
},
secondTabData() {
if (this.treeData.length == 0) return [];
return this.treeData[this.firstTabIndex].data.map(x => x.name);
},
currentTreeFlatten() {
const flatten = (data, pid, pname) => {
return data.reduce((arr, { id, name, children = [], type }) => arr.concat([{ id, name, pid, type, group: pname }], flatten(children, id, name)), []);
};
if (!this.currentTree) return [];
var newArr = flatten(this.currentTree, 0, "");
return newArr;
},
},
mounted() {
this.isDisabled = this.ignoreIds.length > 0;
this.ztreeDomId = this.uuid();
},
methods: {
show() {
this.modal = true;
this.firstTabChange("0");
},
firstTabChange(index) {
this.firstTabIndex = index;
this.secondTabChange(0);
},
secondTabChange(index) {
const _this = this;
if (index != 0 && this.secondTabIndex == index) return;
this.secondTabIndex = index;
this.changeEmpty();
this.initTree();
},
handleSearch(query) {
const _this = this;
let arr = [],
searchArr = this.currentTreeFlatten.filter(x => x.type == 2 && x.name.indexOf(query) != -1);
for (const item of searchArr) {
!arr.map(x => _this.getSplitId(x.id)).includes(_this.getSplitId(item.id)) && arr.push(item);
}
this.currentFlatten = arr;
},
handleSelect(query) {
const _this = this;
if (query.indexOf(",") != -1) query = query.split(",")[0];
var treeObj = $.fn.zTree.getZTreeObj(this.ztreeDomId);
var node = treeObj.getNodeByParam("name", query, null);
if (!!node) treeObj.checkNode(node, true, true, true);
},
formatTree(tree, level = 0) {
const _this = this;
tree.forEach(element => {
element.open = level == 0;
element.type = 1;
element.id = element.id + "," + element.pid;
element.checked = false;
if (typeof element.children !== "undefined" && element.children.length > 0) element.children = _this.formatTree(element.children, level + 1);
else {
element.type = 2;
}
});
return tree;
},
initTree() {
const _this = this;
let first = _this.firstTabIndex,
second = _this.secondTabIndex,
cacheKey = first + "," + second,
cacheMap = _this.cacheMap,
tree = this.treeData[first].data[second].data;
if (!!tree && typeof tree === "function") {
var cacheData = cacheMap.get(cacheKey);
if (!cacheData) {
tree().then(data => {
cacheMap.set(cacheKey, data);
_this.createTree(data, _this.ztreeDomId);
});
} else {
_this.createTree(cacheData, _this.ztreeDomId);
}
} else {
_this.createTree(tree, _this.ztreeDomId);
}
},
createTree(_data, _domId) {
const _this = this;
var jtree = JSON.parse(JSON.stringify(_data));
var ftree = _this.formatTree(jtree);
_this.currentTree = ftree;
//_this.tempUserList = JSON.parse(JSON.stringify(_this.userids));
let treeSetting = {
view: {
dblClickExpand: false,
showIcon: false,
},
check: {
enable: true,
chkboxType: { Y: "ps", N: "ps" },
},
callback: {},
};
function formatUser(tree, checked) {
const recursionCheckIds = tree => {
let arrIds = [];
for (const key in tree) {
const element = tree[key];
if (element.type == 2 && !element.chkDisabled) arrIds.push({ id: element.id, tid: element.tId });
if (typeof element.children !== "undefined" && element.children) {
var childIds = recursionCheckIds(element.children);
arrIds = [...arrIds, ...childIds];
}
}
return arrIds;
};
var checkIds = recursionCheckIds([tree]);
var checkUserList = _this.currentTreeFlatten.filter(x => checkIds.map(x => x.id).includes(x.id));
let checkUserArr = [];
for (const element of checkUserList) {
var findObj = checkIds.find(x => x.id == element.id);
if (!!findObj && checkUserArr.findIndex(x => x.id == element.id) === -1 && _this.tempUserList.findIndex(x => x.id == element.id) === -1) {
checkUserArr.push({ ...element, isCheck: false, tid: findObj.tid });
}
}
if (checked) _this.tempUserList = _this.tempUserList.concat(checkUserArr);
else {
var ids = checkUserList.map(x => x.id);
_this.tempUserList = _this.tempUserList.filter(x => !ids.includes(x.id));
}
}
//当禁止时,是否还显示在右侧
var isCheckFormat = treeNode => {
let id = _this.getSplitId(treeNode.id);
return _this.ignoreIds.findIndex(x => x == id) === -1;
};
treeSetting.callback.onClick = (event, treeId, treeNode, clickFlag) => {
let check = treeNode.checked;
let treeObj = $.fn.zTree.getZTreeObj(treeId);
treeObj.checkNode(treeNode, !check, true);
if (isCheckFormat(treeNode)) formatUser(treeNode, treeNode.checked);
};
treeSetting.callback.onCheck = (event, treeId, treeNode) => {
if (isCheckFormat(treeNode)) formatUser(treeNode, treeNode.checked);
};
var treeObj = $.fn.zTree.init($("#" + _domId), treeSetting, ftree);
//checkUser
var nodes = treeObj.getNodesByFilter(x => _this.userids.includes(_this.getSplitId(x.id)));
var arr = [];
nodes.forEach(element => {
var splitId = _this.getSplitId(element.id); //重复id只选择第一个
if (!arr.includes(splitId)) treeObj.checkNode(element, true, true, true);
arr.push(splitId);
});
var ignorNodes = treeObj.getNodesByFilter(x => _this.ignoreIds.includes(_this.getSplitId(x.id)));
ignorNodes.forEach(element => {
treeObj.checkNode(element, true, true, true);
treeObj.setChkDisabled(element, true);
});
},
checkAllUser(check) {
//全选用户
const _this = this;
_this.tempUserList.forEach(element => {
element.isCheck = check;
});
},
checkUserOne(item) {
item.isCheck = !item.isCheck;
let tlist = this.tempUserList;
this.isCheckUserAll = tlist.length > 0 && tlist.filter(x => x.isCheck).length == tlist.length;
},
delCheckUser() {
//删除选中用户
const _this = this;
let tlist = _this.tempUserList,
delArr = [];
for (const item of tlist) {
item.isCheck && delArr.push(item);
}
var treeObj = $.fn.zTree.getZTreeObj(_this.ztreeDomId);
delArr.forEach(element => {
var node = treeObj.getNodeByTId(element.tid);
treeObj.checkNode(node, false, true, true);
});
_this.isCheckUserAll = false;
},
changeEmpty() {
this.tempUserList = [];
this.searchTreeName = "";
},
submit() {
const _this = this;
let objArr = [],
idsArr = this.tempUserList.map(x => _this.getSplitId(x.id));
idsArr = Array.from(new Set(idsArr));
this.$emit("change", idsArr);
function delKey(obj, ...args) {
args.forEach(v => {
delete obj[v];
});
return obj;
}
idsArr.forEach(element => {
let findObj = _this.tempUserList.find(x => _this.getSplitId(x.id) == element);
delKey(findObj, "id", "isCheck", "tid", "pid", "type");
let item = { ...findObj, id: element };
if (_this.isDisabled) item.disabled = this.userids.findIndex(x => x == element) !== -1;
objArr.push(item);
});
if (_this.isDisabled) {
var treeObj = $.fn.zTree.getZTreeObj(_this.ztreeDomId);
var nodes = treeObj.getNodesByFilter(x => x.chkDisabled);
var disIds = nodes.map(x => _this.getSplitId(x.id));
objArr.forEach(element => {
element.disabled = disIds.findIndex(x => element.id == x) > -1;
});
}
this.$emit("on-submit", objArr);
this.modal = false;
},
getSplitId(strId) {
if (typeof strId === "string" && strId.indexOf(",") != -1) return parseInt(strId.split(",")[0]);
else return strId;
},
uuid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
},
},
};
</script>
<style lang="scss" scoped>
.tree-wrap {
height: 430px;
position: relative;
}
.tree-panel {
border: 1px solid #eee;
width: 280px;
height: 430px;
overflow: hidden;
}
.tree-tab-ul {
li {
background: #f7f7f7;
border-bottom: 1px solid #eee;
height: 34px;
line-height: 34px;
text-align: center;
cursor: pointer;
}
li.active {
background: #fff;
color: #2d8cf0;
border-bottom: none;
cursor: default;
}
}
.tree-box {
padding: 4px 0;
height: 394px;
width: auto;
overflow-y: auto;
border: none;
}
.user-panel {
border: 1px solid #eee;
width: 280px;
.title-box {
background: #f7f7f7;
height: 34px;
line-height: 34px;
padding: 0 12px;
a {
text-decoration: none;
float: right;
margin-right: 20px;
outline: none;
}
}
.user-box {
height: 396px;
overflow-y: auto;
li {
line-height: 32px;
padding-left: 12px;
cursor: pointer;
.info {
float: right;
margin-right: 12px;
font-size: 12px;
color: gray;
}
}
li:hover {
background: #f3f3f3;
}
li.active {
background: #e4edf6;
}
}
}
.arrow-box {
width: 128px;
text-align: center;
position: relative;
top: 50%;
margin-top: -17px;
}
.auto-box {
width: 200px;
margin-bottom: 10px;
.ivu-select-item .info {
font-size: 12px;
color: gray;
position: relative;
top: 2px;
}
}
.ztree li {
overflow: hidden;
}
.ztree li a {
width: 100%;
}
.ztree li a:hover {
text-decoration: none;
}
.ivu-auto-complete .ivu-select-dropdown-list {
max-height: 310px;
overflow-y: auto;
}
</style>