1.实现多选级联样式表功能,根据自身情况 引入到自己的项目中
(借鉴doit-ui-web ,基于ant.design封装的功能组件,遇到很多坑已踩过,需要的直接拿走可以用复用~分享)
组件目录如图
Cascader.vue
<template>
<div
class="gb-ant-select-multiple-cascader"
:class="{ 'multiple-cascader-outside': !isClickOutSide }"
v-clickoutside="handleDropDownOutSide"
>
<a-select
v-bind="$attrs"
v-on="$listeners"
mode="multiple"
:maxTagTextLength="maxTagTextLength"
:maxTagCount="maxTagCount"
style="width:100%"
:placeholder="placeholder"
:value="selectedValueArr"
:notFoundContent="null"
:open="isOpen"
showArrow
:allowClear="allowClear"
optionLabelProp="text"
:dropdownMatchSelectWidth="false"
:getPopupContainer="tirggerNode => tirggerNode.parentNode"
@focus="handleFocus"
@deselect="handleDeselect"
>
<a-icon
type="close-circle"
slot="clearIcon"
theme="filled"
@click="handleClearAll"
/>
<template slot="dropdownRender" slot-scope="menu">
<v-nodes :vnodes="menu" />
<div :class="dropdownClassName">
<div class="cascader-not-content" v-if="treeDataList.length <= 0">
<a-empty :image="simpleImage" :description="noDataText"></a-empty>
</div>
<div class="cascader-content-wrap" v-else>
<div class="cascader-content-container">
<ul
class="cascader-content-list"
v-for="(itemList, levelIndex) in treeDataList"
:key="levelIndex"
>
<li
class="cascader-content-item cascader-content-item-check-all"
@click="handleCascaderAllItemClick(isCheckAll)"
v-if="levelIndex === 0"
>
<a-checkbox
:indeterminate="isIndeterminateAll"
v-model="isCheckAll"
/>
<span class="checkbox-text">{{ allText }}</span>
</li>
<li
class="cascader-content-item"
v-for="(subItem, subIndex) in itemList"
:key="subItem[selectOptionsConfig.key]"
:title="subItem[selectOptionsConfig.text]"
:class="{ 'cascader-content-item-active': subItem.$active }"
@click="
handleCascaderItemClick(subItem, subIndex, levelIndex)
"
>
<a-checkbox
:indeterminate="!!subItem.$indeterminate"
:checked="!!subItem.$checked"
@click.self="
e => handleCheckClick(e, subItem, subIndex, levelIndex)
"
/>
<span class="checkbox-text">{{
subItem[selectOptionsConfig.text]
}}</span>
<a-icon
type="right"
style="font-size:10px;"
v-if="
subItem[selectOptionsConfig.children] &&
subItem[selectOptionsConfig.children].length > 0
"
/>
</li>
</ul>
</div>
</div>
</div>
</template>
<a-select-option
v-for="item in treeToList"
:key="item[selectOptionsConfig.key]"
:value="item[selectOptionsConfig.value]"
:text="item[selectOptionsConfig.text]"
>
{{ item[selectOptionsConfig.text] }}
</a-select-option>
</a-select>
</div>
</template>
<script>
import { Empty } from "ant-design-vue";
import { cloneDeep } from "lodash";
const clickoutside = {
// 初始化指令
bind(el, binding) {
function documentHandler(e) {
if (el.contains(e.target)) {
return false;
}
if (binding.expression) {
binding.value(e);
}
}
el.__vueClickOutside__ = documentHandler;
document.addEventListener("click", documentHandler);
},
update() {},
unbind(el) {
document.removeEventListener("click", el.__vueClickOutside__);
delete el.__vueClickOutside__;
}
};
export default {
name: "MultipleCascader",
components: {
VNodes: {
functional: true,
render: (h, ctx) => {
return ctx.props.vnodes;
}
}
},
directives: { clickoutside },
props: {
allowClear: {
type: Boolean,
default: true
},
allText: {
type: String,
default: "全部"
},
noDataText: {
type: String,
default: "暂无数据"
},
treeData: {
type: Array,
default: () => {
return [];
}
},
dropdownClassName: {
type: String
},
selectOptionsConfig: {
type: Object,
default: () => {
return {
key: "id",
value: "value",
text: "title",
children: "children"
};
}
},
maxTagCount: {
type: Number
},
maxTagTextLength: {
type: Number
},
placeholder: {
type: String,
default: "请搜索"
},
checkAll: {
type: Boolean,
default: false
},
value: {
type: Array
},
},
beforeCreate() {
this.simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
},
destroyed() {
this.timer && clearInterval(this.timer);
},
data() {
return {
isOpen: false,
isCheckAll: this.checkAll,
isIndeterminateAll: false,
isClickOutSide: true,
selectedValueArr: this.value || undefined,
treeDataList: cloneDeep(this.treeData),
treeToList: [],
selectedValueArrAll:[]
};
},
watch: {
checkAll: {
handler(newVal) {
if (newVal) {
this.timer = setInterval(() => {
if (this.treeDataList.length > 0) {
clearInterval(this.timer);
this.handleCascaderAllItemClick(!newVal);
}
}, 30);
}
},
immediate: true
},
value: {
handler(newVal) {
console.log('6666selectedValueArr77777 :>> ', newVal);
this.selectedValueArr = newVal;
this.initDefaultKeys(newVal, this.treeDataList);
return newVal;
},
immediate: true
},
selectedValueArrAll: {
handler(newVal) {
this.selectedValueArr = newVal;
this.initDefaultKeys(newVal, this.treeDataList);
},
immediate: true
},
treeData(newVal) {
this.treeListToArrList(newVal[0]);
this.initDefaultKeys(this.selectedValueArr, newVal);
this.treeDataList = newVal;
return newVal;
}
},
methods: {
handleClearAll() {
this.treeDataList = this.treeData;
this.isOpen = false;
this.isCheckAll = false;
this.isClickOutSide = true;
this.isIndeterminateAll = false;
this.updatePostData();
},
initDefaultKeys(selectedArr, treeDataList) {
if (
selectedArr &&
selectedArr.length > 0 &&
treeDataList &&
treeDataList.length > 0
) {
const treeDataListClone = cloneDeep(treeDataList);
const treeToListClone = cloneDeep(this.treeToList);
selectedArr.forEach(item => {
const curretnItemList = [];
this.findKeysItem(treeDataListClone[0], item, curretnItemList);
let currentItem = {};
if (curretnItemList.length > 0) {
currentItem = curretnItemList[0];
}
if (
!treeToListClone.find(
listItem =>
listItem[this.selectOptionsConfig.value] ===
currentItem[this.selectOptionsConfig.value]
)
) {
treeToListClone.push(currentItem);
}
currentItem.$checked = true;
if (
currentItem[this.selectOptionsConfig.children] &&
currentItem[this.selectOptionsConfig.children].length > 0
) {
this.setAllDataTreeChecked(
currentItem[this.selectOptionsConfig.children],
true
);
}
});
this.setCheckBoxIndeteriminate(treeDataListClone[0]);
this.$nextTick(() => {
this.treeToList = treeToListClone;
this.treeDataList = treeDataListClone;
});
}
},
handleCascaderAllItemClick(checkAll) {
const treeDataListClone = cloneDeep(this.treeDataList);
// 触发事件的时候v-model中的数据还没有改变,所以要取反
this.setAllDataTreeChecked(treeDataListClone[0], !checkAll);
this.clearAllDataActive(treeDataListClone[0]);
this.setCheckBoxIndeteriminate(treeDataListClone[0]);
this.treeDataList = treeDataListClone;
this.updatePostData();
},
handleDeselect(value) {
const treeDataListClone = cloneDeep(this.treeDataList);
const currentItemList = [];
this.findKeysItem(treeDataListClone[0], value, currentItemList);
let currentItem = {};
if (currentItemList.length > 0) {
currentItem = currentItemList[0];
}
currentItem.$checked = false;
currentItem.$active = false;
if (
currentItem[this.selectOptionsConfig.children] &&
currentItem[this.selectOptionsConfig.children].length > 0
) {
this.setAllDataTreeChecked(
currentItem[this.selectOptionsConfig.children],
false
);
}
this.setCheckBoxIndeteriminate(treeDataListClone[0]);
this.treeDataList = treeDataListClone;
this.updatePostData();
},
handleCheckClick(e, item, index, levelIndex) {
const treeDataListClone = cloneDeep(this.treeDataList);
const childrenTag = this.selectOptionsConfig.children;
const valueTag = this.selectOptionsConfig.value;
const currentItemList = [];
this.findKeysItem(treeDataListClone[0], item[valueTag], currentItemList);
let currentItem = {};
if (currentItemList.length > 0) {
currentItem = currentItemList[0];
}
e.stopPropagation();
e.cancelBubble = true;
const checked = e.target.checked;
console.log('checked :>> ', checked);
if (!currentItem.$active && levelIndex !== treeDataListClone.length - 1) {
// 清空所有选中状态
this.clearAllDataActive(treeDataListClone[0]);
}
if (currentItem[childrenTag] && currentItem[childrenTag].length > 0) {
// 可展开
if (!currentItem.$active) {
const nextIndex = levelIndex + 1;
if (treeDataListClone[nextIndex]) {
treeDataListClone.splice(nextIndex);
}
}
} else {
// 不可展开
const nextIndex = levelIndex + 1;
if (treeDataListClone[nextIndex]) {
treeDataListClone.splice(nextIndex);
}
}
if (currentItem[childrenTag] && currentItem[childrenTag].length > 0) {
console.log('777 :>> ', 7777);
this.setAllDataTreeChecked(currentItem[childrenTag], checked);
} else {
currentItem.$checked = checked;
}
console.log('777 :>> ', treeDataListClone[0]);
this.setCheckBoxIndeteriminate(treeDataListClone[0]);
this.treeDataList = treeDataListClone;
this.$emit("handleCheckboxChange", {
event: e,
item,
index,
levelIndex
});
this.updatePostData();
},
// 单个item点击事件
handleCascaderItemClick(item, index, levelIndex) {
const treeDataListClone = cloneDeep(this.treeDataList);
const childrenTag = this.selectOptionsConfig.children;
const valueTag = this.selectOptionsConfig.value;
const currentItemList = [];
this.findKeysItem(treeDataListClone[0], item[valueTag], currentItemList);
let currentItem = {};
if (currentItemList.length > 0) {
currentItem = currentItemList[0];
}
const parentItemsList = [];
this.findAllParentItems(
currentItem[valueTag],
treeDataListClone[0],
treeDataListClone[0],
parentItemsList
);
parentItemsList.push(currentItem);
this.clearAllDataActive(treeDataListClone[0]);
parentItemsList.forEach(activeItem => {
if (activeItem[childrenTag] && activeItem[childrenTag].length > 0) {
activeItem.$active = true;
}
});
if (currentItem[childrenTag] && currentItem[childrenTag].length > 0) {
const nextIndex = levelIndex + 1;
if (treeDataListClone[nextIndex]) {
treeDataListClone.splice(nextIndex);
treeDataListClone[nextIndex] = currentItem[childrenTag];
} else {
treeDataListClone.push(currentItem[childrenTag]);
}
this.addHideSelectedList(currentItem[childrenTag]);
} else {
const nextIndex = levelIndex + 1;
if (treeDataListClone[nextIndex]) {
treeDataListClone.splice(nextIndex);
}
currentItem.$checked = !currentItem.$checked;
}
this.setCheckBoxIndeteriminate(treeDataListClone[0]);
this.treeDataList = treeDataListClone;
this.updatePostData();
},
handleFocus() {
this.isOpen = true;
this.isClickOutSide = false;
},
updatePostData() {
const childrenTag = this.selectOptionsConfig.children;
const valueTag = this.selectOptionsConfig.value;
let selectedValueArr = [];
_getAllItemChecked(this.treeDataList[0]);
this.$emit("change", selectedValueArr)
// console.log('currentCheckedAll :>> ', selectedValueArr);
this.selectedValueArrAll=selectedValueArr
// this.selectedValueArr=selectedValueArr
function _getAllItemChecked(treeDataList) {
let parentItem;
treeDataList.forEach(item => {
if (item[childrenTag] && item[childrenTag].length > 0) {
parentItem = item;
const currentCheckedAll = [];
const currentCheckedAllItems = [];
_getSubChecked(parentItem[childrenTag], currentCheckedAll);
_getSubCheckedItems(
parentItem[childrenTag],
currentCheckedAllItems
);
if (currentCheckedAll.every(checkItem => checkItem === true)) {
if (parentItem[valueTag]) {
selectedValueArr.push(parentItem[valueTag]);
}
} else if (
currentCheckedAll.some(checkItem => checkItem === true)
) {
currentCheckedAllItems.forEach(selectedItem => {
if (selectedItem[valueTag]) {
selectedValueArr.push(selectedItem[valueTag]);
}
});
}
} else {
if (item.$checked) {
selectedValueArr.push(item[valueTag]);
}
}
});
}
function _getSubChecked(itemList, arr) {
itemList.forEach(item => {
arr.push(!!item.$checked);
if (item[childrenTag] && item[childrenTag].length > 0) {
_getSubChecked(item[childrenTag], arr);
}
});
}
function _getSubCheckedItems(itemList, arr) {
itemList.forEach(item => {
if (item.$checked) {
arr.push(item);
} else {
if (item[childrenTag] && item[childrenTag].length > 0) {
_getSubCheckedItems(item[childrenTag], arr);
}
}
});
}
},
// 点击组件外部区域
handleDropDownOutSide() {
this.isOpen = false;
this.isClickOutSide = true;
},
// 设置当前的选中框的全选等状态
setCheckBoxIndeteriminate(treeDataList) {
const childrenTag = this.selectOptionsConfig.children;
_getAllItemChecked(treeDataList);
const checkedAllArr = [];
_getAllItemCheckedAll(treeDataList, checkedAllArr);
if (checkedAllArr.every(item => item === true)) {
this.isCheckAll = true;
this.isIndeterminateAll = false;
} else if (checkedAllArr.some(item => item === true)) {
this.isCheckAll = false;
this.isIndeterminateAll = true;
} else if (checkedAllArr.every(item => item === false)) {
this.isCheckAll = false;
this.isIndeterminateAll = false;
}
function _getAllItemCheckedAll(itemList, arr) {
itemList.forEach(item => {
arr.push(!!item.$checked);
if (item[childrenTag] && item[childrenTag].length > 0) {
_getAllItemCheckedAll(item[childrenTag], arr);
}
});
}
function _getAllItemChecked(treeDataList) {
let parentItem;
treeDataList.forEach(item => {
if (item[childrenTag] && item[childrenTag].length > 0) {
parentItem = item;
_getAllItemChecked(item[childrenTag]);
const currentCheckedAll = [];
_getSubChecked(parentItem[childrenTag], currentCheckedAll);
if (currentCheckedAll.every(checkItem => checkItem === true)) {
parentItem.$checked = true;
parentItem.$indeterminate = false;
} else if (
currentCheckedAll.some(checkItem => checkItem === true)
) {
parentItem.$checked = false;
parentItem.$indeterminate = true;
} else if (
currentCheckedAll.every(checkItem => checkItem === false)
) {
parentItem.$checked = false;
parentItem.$indeterminate = false;
}
}
});
}
function _getSubChecked(itemList, arr) {
itemList.forEach(item => {
arr.push(!!item.$checked);
if (item[childrenTag] && item[childrenTag].length > 0) {
_getSubChecked(item[childrenTag], arr);
}
});
}
},
// 设置所有选中和非选中
setAllDataTreeChecked(treeList, checked) {
const childrenTag = this.selectOptionsConfig.children;
treeList.forEach(item => {
item.$checked = checked;
if (item[childrenTag] && item[childrenTag].length > 0) {
this.setAllDataTreeChecked(item[childrenTag], checked);
}
});
},
// 清空所有的active状态
clearAllDataActive(treeList) {
const childrenTag = this.selectOptionsConfig.children;
treeList.forEach(item => {
item.$active = false;
if (item[childrenTag] && item[childrenTag].length > 0) {
this.clearAllDataActive(item[childrenTag]);
}
});
},
findKeysItem(treeDataList, currentKey, currentItem) {
const childrenTag = this.selectOptionsConfig.children;
treeDataList.forEach(item => {
if (item[this.selectOptionsConfig.value] === currentKey) {
currentItem.push(item);
} else {
if (item[childrenTag] && item[childrenTag].length > 0) {
this.findKeysItem(item[childrenTag], currentKey, currentItem);
}
}
});
},
findAllParentItems(
currentKey,
treeDataList,
originTreeDataList,
parentItemsList,
parentItem,
parentItemsHasKey
) {
if (parentItem && parentItemsHasKey) {
parentItemsList.unshift(parentItem);
this.findAllParentItems(
parentItem[this.selectOptionsConfig.value],
treeDataList,
originTreeDataList,
parentItemsList
);
} else {
this.findKeysAndParentItem(
currentKey,
treeDataList,
originTreeDataList,
parentItemsList,
parentItem,
parentItemsHasKey
);
}
},
findKeysAndParentItem(
currentKey,
treeDataList,
originTreeDataList,
parentItemsList,
parentItem
) {
const childrenTag = this.selectOptionsConfig.children;
treeDataList.forEach(item => {
if (item[this.selectOptionsConfig.value] === currentKey && parentItem) {
this.findAllParentItems(
parentItem[this.selectOptionsConfig.value],
originTreeDataList,
originTreeDataList,
parentItemsList,
parentItem,
true
);
} else {
if (item[childrenTag] && item[childrenTag].length > 0) {
this.findKeysAndParentItem(
currentKey,
item[childrenTag],
originTreeDataList,
parentItemsList,
item
);
}
}
});
},
// 将树形结构数据转换成平面结构
treeListToArrList(treeList) {
const arrList = [];
if (treeList && treeList[0]) {
treeList.forEach(item => {
arrList.push(item);
});
this.treeToList = arrList;
}
},
addHideSelectedList(treeList) {
const treeToListClone = Array.from(this.treeToList);
treeList.forEach(item => {
if (
!treeToListClone.find(
listItem =>
listItem[this.selectOptionsConfig.value] ===
item[this.selectOptionsConfig.value]
)
) {
treeToListClone.push(item);
}
});
this.treeToList = treeToListClone;
}
}
};
</script>
<style lang="less">
.gb-ant-select-multiple-cascader {
.ant-select-dropdown-menu-item {
display: none;
}
.cascader-content-item {
.checkbox-text {
padding-left: 8px;
font-size: 12px;
color: #4a4a4a;
cursor: default;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
&.cascader-content-item-active {
background-color: rgba(66, 70, 86, 0.05);
.checkbox-text {
color: #1d47aa;
}
}
.ant-checkbox-wrapper {
.ant-checkbox-indeterminate {
.ant-checkbox-inner {
&:after {
background-color: #1d47aa;
left: 48%;
}
}
}
.ant-checkbox-inner {
width: 14px;
height: 14px;
background-color: #fff;
border-color: #7d8292;
&:after {
border-color: #1d47aa;
left: 20%;
}
}
}
}
.ant-select-dropdown--multiple {
min-width: auto !important;
}
&.multiple-cascader-outside {
.ant-select-arrow {
transform: rotate(180deg);
}
}
.ant-select-arrow {
margin-top: -8px;
transition: transform 0.3s;
}
.ant-select-dropdown-menu-root {
display: none;
}
.cascader-content-right {
.right-wrap {
.ant-checkbox {
top: 0;
}
}
}
}
</style>
<style lang="less" scoped>
.gb-ant-select-multiple-cascader {
position: relative;
.cascader-content-wrap {
height: 100%;
.cascader-content-container {
display: flex;
flex-direction: row;
.cascader-content-list {
list-style: none;
padding: 0;
margin: 0;
width: 153px;
box-sizing: border-box;
padding: 11px 0;
max-height: 160px;
overflow-y: auto;
border-right: 1px solid rgba(222, 223, 226, 0.3);
.cascader-content-item {
padding: 5px 16px;
margin-top: 5px;
display: flex;
flex-direction: row;
align-items: center;
line-height: 1;
&:hover {
background-color: rgba(66, 70, 86, 0.05);
}
}
}
}
}
.cascader-not-content {
padding: 10px;
}
}
</style>
页面使用
<template>
<CascaderSelect
allowClear
style="width:450px;"
:maxTagCount="3"
:maxTagTextLength="4"
placeholder="请选择"
:selectOptionsConfig="{
key: 'fullPath',
value: 'fullPath',
text: 'name',
children: 'children'
}"
allText="全选"
noDataText="暂无数据"
dropdownClassName="customer-multiple-cascader"
:treeData="[multipleTreeData]"
@handleCheckboxChange="handleCheckboxChange"
/>
</template>
<script>
export default {
name: "PayOrderModal",
components: {
CascaderSelect
},
data() {
return {
multipleTreeData: [
{
fullPath: '01',
name: '广东省',
children: [
{
fullPath: '0101',
name: '广州市',
children: [
{ fullPath: '010101', name: '南沙区', children: [] },
{ fullPath: '010102', name: '越秀区', children: [] },
],
},
{
fullPath: '0102',
name: '深圳市',
children: [
{ fullPath: '010201', name: '南山区', children: [] },
{ fullPath: '010202', name: '福田区', children: [] },
{ fullPath: '010203', name: '宝安区', children: [] },
{ fullPath: '010204', name: '龙华区', children: [] },
{ fullPath: '010205', name: '龙岗区', children: [] },
{ fullPath: '010206', name: '盐田区', children: [] },
{ fullPath: '010207', name: '光明新区', children: [] },
{ fullPath: '010208', name: '罗湖区', children: [] },
],
},
],
},
{
fullPath: '02',
name: '江西省',
children: [
{
fullPath: '0201',
name: '南昌市',
children: [
{ fullPath: '020101', name: '南昌县', children: [] },
],
},
{
fullPath: '0202',
name: '宜春市',
children: [
{ fullPath: '020201', name: '铜鼓县', children: [] },
{ fullPath: '020202', name: '宜丰县', children: [] },
{ fullPath: '020203', name: '上高县', children: [] },
{ fullPath: '020204', name: '万载县', children: [] },
],
},
],
},
],
}
},
methods:{
handleCheckboxChange(val){
console.log(555, val)
// this.currentProd = value;
// this.form.receivableMoney = value.price
},
}
}
</script>