当前位置: 首页 > ds >正文

vue封装一个cascade级联 多选 全选组件 ,原生写法Input,Checkbox,Button

今天遇到需求 :要求级联改成多选,并且支持全选,如下:

自定义封装了一个组件,基本上就少了搜索功能,等他需要我再加, 我这个就2层,而且前面第一列不让选, 需要扩展多级可以在这个基础上改,组件就iview的 Input,Checkbox,Button,这个替换其他组件也很容易

使用方法

 resetData() {this.selectedValues = []},handleChange(values, options) {// console.log('选中的值:', values)// console.log('选中的选项:', options)this.condition.scene_id = values.join(',')},selectedValues: [],
auditScenario: [],<MultipleCascader style="width:395px;" :value="selectedValues" :data="auditScenario" placeholder="请选择场景名称":field-names="{ label: 'dict_value', value: 'dict_key', children: 'children' }" @on-change="handleChange" />import MultipleCascader from '@/business/components/MultipleCascader.vue'
components: { MultipleCascader },

完整代码

<template><div class="multiple-cascader"><div class="cascader-input" @click="toggleDropdown"><Input :icon="visible ? 'ios-arrow-up' : 'ios-arrow-down'" :value="displayText":placeholder="placeholder" :disabled="disabled" :readonly="true" class="cascader-input-field"></Input></div><transition name="cascader-fade"><div v-show="visible" class="cascader-dropdown"><div class="cascader-panel"><div v-for="(level, levelIndex) in cascaderLevels" :key="levelIndex" class="cascader-menu"><!-- 全选/取消全选按钮 --><div class="select-all-container" v-if="levelIndex === 0"><div class="select-all-buttons"><Button size="small" type="text" @click="handleSelectAll(levelIndex)">全选</Button><Button size="small" type="text" @click="handleUnselectAll(levelIndex)">取消全选</Button></div></div><!-- 全选/取消全选按钮 --><div class="select-all-container" v-if="levelIndex === 1"><div class="select-all-buttons"><Button size="small" type="text" @click="handleSelectAll(levelIndex)":disabled="!hasSelectableItems(levelIndex)">全选</Button><Button size="small" type="text" @click="handleUnselectAll(levelIndex)":disabled="!hasSelectedItems(levelIndex)">取消全选</Button></div></div><div class="cascader-menu-item" v-for="(item, index) in level" :key="index":class="{ 'active': isActive(item, levelIndex), 'disabled': item.disabled }"@click="handleItemClick(item, levelIndex)"><Checkbox v-if="isLeafNode(item)" :value="isSelected(item)"@on-change="(checked) => handleCheckboxChange(item, checked)" :disabled="item.disabled" /><span class="item-label">{{ item.label }}</span><Icon v-if="hasChildren(item)" type="ios-arrow-right" class="arrow-right" /></div></div></div><div v-if="selectedItems.length > 0" class="selected-tags"><div class="tags-title">已选择:</div><div class="tags-container"><Tag v-for="item in selectedItems" :key="item.value" closable @on-close="removeSelected(item)">{{ item.label }}</Tag></div></div></div></transition></div>
</template><script>
export default {name: 'MultipleCascader',props: {data: { type: Array, default: () => [] },value: { type: Array, default: () => [] },placeholder: { type: String, default: '请选择' },disabled: { type: Boolean, default: false },fieldNames: {type: Object,default: () => ({ label: 'label', value: 'value', children: 'children' })}},data() {return {visible: false,cascaderLevels: [],selectedItems: [],activePath: [],allItems: []}},computed: {displayText() {if (this.selectedItems.length === 0) return ''if (this.selectedItems.length === 1) return this.selectedItems[0].labelreturn `已选择 ${this.selectedItems.length} 项`}},watch: {value: {handler(newVal) {this.updateSelectedItems(newVal)},immediate: true},data: {handler(newVal) {this.initCascaderLevels(newVal)},immediate: true}},mounted() {document.addEventListener('click', this.handleClose)this.initCascaderLevels()},beforeDestroy() {document.removeEventListener('click', this.handleClose)},methods: {initCascaderLevels() {this.cascaderLevels = [this.processData(this.data)]console.log('cascaderLevels', this.cascaderLevels)// 递归获取所有idthis.allItems = []this.cascaderLevels[0] && this.cascaderLevels[0].forEach(item => {if (item.children && item.children.length > 0) {item.children.forEach(itemT => {this.allItems.push(itemT)})}})},processData(data) {if (!Array.isArray(data)) return []return data.map(item => {if (!item) return nullconst processedItem = {label: item[this.fieldNames.label] || item.label || '',value: item[this.fieldNames.value] || item.value || '',children: undefined,disabled: item.disabled || false}const childrenData = item[this.fieldNames.children] || item.childrenif (childrenData && Array.isArray(childrenData) && childrenData.length > 0) {processedItem.children = this.processData(childrenData)}return processedItem}).filter(item => item !== null)},handleClose(event) {// 检查点击是否在组件内部const cascaderElement = this.$el;if (cascaderElement && cascaderElement.contains(event.target)) {return; // 如果点击在组件内部,不关闭}this.visible = false},toggleDropdown() {if (this.disabled) returnthis.visible = !this.visible},handleItemClick(item, levelIndex) {if (item.disabled) returnthis.activePath = this.activePath.slice(0, levelIndex)this.activePath[levelIndex] = itemif (this.isLeafNode(item)) {this.toggleItemSelection(item)} else {this.expandChildren(item, levelIndex)}},handleCheckboxChange(item, checked) {if (checked) {this.addSelectedItem(item)} else {this.removeSelectedItem(item)}},expandChildren(item, levelIndex) {this.cascaderLevels = this.cascaderLevels.slice(0, levelIndex + 1)if (item.children && item.children.length > 0) {this.cascaderLevels.push(item.children)}},isLeafNode(item) {return !item.children || !Array.isArray(item.children) || item.children.length === 0},hasChildren(item) {return item.children && Array.isArray(item.children) && item.children.length > 0},isActive(item, levelIndex) {return this.activePath[levelIndex] && this.activePath[levelIndex].value === item.value},isSelected(item) {return this.selectedItems.some(selected => selected.value === item.value)},toggleItemSelection(item) {if (this.isSelected(item)) {this.removeSelectedItem(item)} else {this.addSelectedItem(item)}},addSelectedItem(item) {if (!this.isSelected(item)) {this.selectedItems.push({value: item.value,label: item.label})this.emitChange()}},removeSelectedItem(item) {const index = this.selectedItems.findIndex(selected => selected.value === item.value)if (index > -1) {this.selectedItems.splice(index, 1)this.emitChange()}},removeSelected(item) {this.removeSelectedItem(item)},getFullPathLabel(item) {const path = this.findItemPath(item)return path.map(p => p.label).join(' / ')},findItemPath(targetItem) {const findPath = (data, target, currentPath = []) => {for (const item of data) {const newPath = [...currentPath, item]if (item.value === target.value) {return newPath}if (item.children && item.children.length > 0) {const found = findPath(item.children, target, newPath)if (found) return found}}return null}// 从原始数据中查找路径const path = findPath(this.data, targetItem)if (path) return path// 如果没找到,尝试从当前激活路径的子级中查找if (this.activePath.length > 0) {const activeItem = this.activePath[0]if (activeItem.children) {const childPath = findPath(activeItem.children, targetItem, [activeItem])if (childPath) return childPath}}return [targetItem]},updateSelectedItems(values) {this.selectedItems = []if (!Array.isArray(values) || values.length === 0) returnvalues.forEach(value => {const item = this.findItemByValue(this.data, value)if (item) {this.selectedItems.push({value: item.value,label: item.label})}})},findItemByValue(data, targetValue) {for (const item of data) {if (item.value === targetValue) {return item}if (item.children && item.children.length > 0) {const found = this.findItemByValue(item.children, targetValue)if (found) return found}}return null},emitChange() {const values = this.selectedItems.map(item => item.value)this.$emit('input', values)this.$emit('on-change', values, this.selectedItems)},handleSelectAll(levelIndex) {if (levelIndex === 0) {// 左侧面板:全选所有叶子节点this.selectedItems = this.allItemsthis.emitChange();} else if (levelIndex === 1) {// 右侧面板:全选当前选中左侧项的子级叶子节点const activeLeftItem = this.activePath[0];if (activeLeftItem && activeLeftItem.children) {this.selectAllLeafNodes(activeLeftItem.children);}}},handleUnselectAll(levelIndex) {if (levelIndex === 0) {// 左侧面板:取消全选所有叶子节点this.selectedItems = [];this.emitChange();} else if (levelIndex === 1) {// 右侧面板:取消全选当前选中左侧项的子级叶子节点const activeLeftItem = this.activePath[0];if (activeLeftItem && activeLeftItem.children) {this.unselectAllLeafNodes(activeLeftItem.children);}}},// 全选所有叶子节点selectAllLeafNodes(data) {const newSelected = [...this.selectedItems];const traverse = (items) => {items.forEach(item => {if (this.isLeafNode(item)) {if (!newSelected.some(selected => selected.value === item.value)) {newSelected.push({value: item.value,label: item.label});}} else if (item.children && item.children.length > 0) {traverse(item.children);}});};traverse(data);this.selectedItems = newSelected;this.emitChange();},// 取消全选所有叶子节点unselectAllLeafNodes(data) {const newSelected = [...this.selectedItems];const traverse = (items) => {items.forEach(item => {if (this.isLeafNode(item)) {const index = newSelected.findIndex(selected => selected.value === item.value);if (index > -1) {newSelected.splice(index, 1);}} else if (item.children && item.children.length > 0) {traverse(item.children);}});};traverse(data);this.selectedItems = newSelected;this.emitChange();},hasSelectableItems(levelIndex) {if (levelIndex === 0) {// 左侧面板:检查是否有未选择的叶子节点return this.hasUnselectedLeafNodes(this.data);} else if (levelIndex === 1) {// 右侧面板:检查当前选中左侧项的子级是否有未选择的叶子节点const activeLeftItem = this.activePath[0];if (activeLeftItem && activeLeftItem.children) {return this.hasUnselectedLeafNodes(activeLeftItem.children);}}return false;},hasSelectedItems(levelIndex) {if (levelIndex === 0) {// 左侧面板:检查是否有已选择的叶子节点return this.hasSelectedLeafNodes(this.data);} else if (levelIndex === 1) {// 右侧面板:检查当前选中左侧项的子级是否有已选择的叶子节点const activeLeftItem = this.activePath[0];if (activeLeftItem && activeLeftItem.children) {return this.hasSelectedLeafNodes(activeLeftItem.children);}}return false;},// 检查是否有未选择的叶子节点hasUnselectedLeafNodes(data) {let hasUnselected = false;const traverse = (items) => {for (const item of items) {if (this.isLeafNode(item)) {if (!this.isSelected(item) && !item.disabled) {hasUnselected = true;return;}} else if (item.children && item.children.length > 0) {traverse(item.children);}}};traverse(data);return hasUnselected;},// 检查是否有已选择的叶子节点hasSelectedLeafNodes(data) {let hasSelected = false;const traverse = (items) => {for (const item of items) {if (this.isLeafNode(item)) {if (this.isSelected(item)) {hasSelected = true;return;}} else if (item.children && item.children.length > 0) {traverse(item.children);}}};traverse(data);return hasSelected;}}
}
</script><style scoped>
.multiple-cascader {position: relative;width: 100%;
}.cascader-input {position: relative;cursor: pointer;
}.cascader-input-field {cursor: pointer;
}.cascader-dropdown {position: absolute;top: 100%;left: 0;right: 0;z-index: 1000;background: #fff;border: 1px solid #dcdee2;border-radius: 4px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);margin-top: 3px;max-height: 500px;overflow: hidden;width: 710px;
}.cascader-panel {display: flex;max-height: 300px;
}.cascader-menu {min-width: 150px;border-right: 1px solid #e8eaec;overflow-y: auto;width: 100%;position: relative;
}.cascader-menu:last-child {border-right: none;
}.cascader-menu-item {cursor: pointer;display: flex;align-items: center;transition: background-color 0.2s ease;
}.cascader-menu-item:hover {background-color: #f8f9fa;
}.cascader-menu-item.active {background-color: #e6f7ff;color: #1890ff;
}.cascader-menu-item.disabled {cursor: not-allowed;color: #c5c8ce;
}.item-label {flex: 1;margin-left: 4px;
}.arrow-right {margin-left: auto;color: #c5c8ce;
}.selected-tags {padding: 8px 12px;border-top: 1px solid #e8eaec;background-color: #f8f9fa;
}.tags-title {font-size: 12px;color: #666;margin-bottom: 4px;
}.tags-container {display: flex;flex-wrap: wrap;gap: 4px;max-height: 130px;overflow: auto;
}.select-all-container {display: flex;justify-content: space-between;padding: 8px 12px;border-bottom: 1px solid #e8eaec;background-color: #f8f9fa;position: sticky;top: 0;z-index: 10;
}.select-all-buttons {display: flex;gap: 8px;
}.select-all-buttons .ivu-btn {font-size: 12px;padding: 2px 6px;height: 24px;line-height: 1.2;
}.select-all-buttons .ivu-btn:disabled {color: #c5c8ce;cursor: not-allowed;
}/* 过渡效果 */
.cascader-fade-enter-active,
.cascader-fade-leave-active {transition: all 0.3s ease;
}.cascader-fade-enter,
.cascader-fade-leave-to {opacity: 0;transform: translateY(-10px);
}.cascader-fade-enter-to,
.cascader-fade-leave {opacity: 1;transform: translateY(0);
}
</style>

http://www.xdnf.cn/news/17321.html

相关文章:

  • 看不见的伪造痕迹:AI时代的鉴伪攻防战
  • Codeforces Round 987 (Div. 2)
  • 数据结构—队列和栈
  • 问题定位排查手记1 | 从Windows端快速检查连接状态
  • Java面试宝典:类加载器分层设计与核心机制解析
  • PyCharm vs. VSCode 到底哪个更好用
  • C++、STL面试题总结(二)
  • 图论(邻接表)DFS
  • SpringBoot 接入SSE实现消息实时推送的优点,原理以及实现
  • 【Linux系统】进程间通信:命名管道
  • 生成模型实战 | Transformer详解与实现
  • 分布式光伏气象站:安装与维护
  • 人大金仓数据库逻辑备份与恢复命令
  • 基于模式识别的订单簿大单自动化处理系统
  • Git 分支迁移完整指南(结合分支图分析)
  • JavaWeb(04)
  • 每日五个pyecharts可视化图表-bars(5)
  • SQL的条件查询
  • PDF注释的加载和保存的实现
  • jspdf或react-to-pdf等pdf报错解决办法
  • QT自定义控件
  • 学习日志29 python
  • 微信小程序多媒体功能实现
  • 大型音频语言模型论文总结
  • 使用Nginx部署前后端分离项目
  • 0806线程
  • MCU程序段的分类
  • http请求结构体解析
  • 【注意】HCIE-Datacom华为数通考试,第四季度将变题!
  • 时隔六年!OpenAI 首发 GPT-OSS 120B / 20B 开源模型:性能、安全与授权细节全解