不知道怎么描述,反正是折腾了大半天,前端也不是很精通,看一下文档,如果有人遇到和我一样的问题,希望能给到一点启发吧
刚开始的需求描述
最近项目上需要使用级联选择器,绑定组织机构,组件需要满足以下要求:
- 在编辑详情时只能进行单选,在单选情况下,可以选择任何一级,如下图所示
- 在列表查询时支持多选,在 多选情况下,选择父节点可以全选子节点,父子节点一起作为参数过滤查询列表,如下图所示
以上功能看el-cascader的官方说明文档,是能直接实现的,上一部分代码:
1)封装组件部分
<el-cascader :options="information" ref="cascaderAddr" v-model="dwCodes" :show-all-levels="false" :props="propsOpyions" @change="handleChange" collapse-tags collapse-tags-tooltip clearable filterable :disabled="disabled" > </el-cascader> export default { props: { value: { type: null }, // 是否禁用 disabled: { type: Boolean, default: false, required: false }, // 是否多选 multiple: { type: Boolean, default: false, required: false } }, data() { return { dwCodes: [], //回显绑定的值 information: [], // 选择器数据源 propsOpyions: { multiple: true, // 是否允许多选 emitPath: false, // 是否展示完整路径 checkStrictly: true, // 是否可选择任意一级 expandTrigger: 'click' // 展开事件 } }; }, created() { this.init() this.propsOpyions.multiple = this.multiple // 多选时,若checkStrictly为true,则选择父节点时,只能选到父节点 // 不能全选它下面的所有子节点,所以需要设置为false if(this.multiple) { this.$set(this.propsOpyions, 'checkStrictly', false) } }, watch: { value: { // 数据回显,很重要 handler(newVal, oldVal) { this.dwCodes = newVal if(this.multiple && this.dwCodes) { this.dwCodes = this.dwCodes.split(',') } }, immediate: false, deep: false } }, methods: { async init() { // 初始化数据源 }, // 值改变 handleChange(checked) { if(this.multiple && checked) { let checknodes = this.$refs['cascaderAddr'].getCheckedNodes() let checkNodeids = checknodes.map((obj,index) => { return obj.value }); this.$emit("input", checkNodeids.join()); } else { this.$emit("input", checked); } } } }
复制
2)引入组件部分
// 单选时 <dw-Cascader :multiple="false" v-model="gldw" :disabled="disabledFlag"> </dw-Cascader> // 多选时 <dw-Cascader :multiple="true" v-model="gldw" ></dw-Cascader>
复制
问题来了
- 在允许多选时,子节点全部取消的情况下,父节点也会取消选中状态,但是目前的需求是,有些数据是直接归属到父节点单位下面的,如果只想查父节点下的数据,是实现不了的。(checkStrictly为true,虽然可以选到父节点,但是这个时候父节点勾选,子节点全选就不起作用了)
- 在搜索时,只能检索到最后一级,同样也是受checkStrictly为true的限制,在用户在搜索父级节点时是不显示的,只能搜索最后一级
搜索【长沙市】结果,只显示了子节点
解决
所以在保持原有需求不变的情况下,在面向百度查询了一番,结合互联网好人提供的思路(已经找不到出处了,搜一搜应该也能找到),解决上面说的两个问题。
第一步:在多选情况下,也设置checkStrictly为true,先保证父节点能单独勾选;
created() { this.init() this.propsOpyions.multiple = this.multiple // if(this.multiple) { // this.$set(this.propsOpyions, 'checkStrictly', false) // } },
复制
第二步,监听级联选择框绑定值的变化,这个示例中v-model绑定的是dwcodes,就监听这个值,实现全选和取消全选,取消子节点勾选,父节点保留勾选状态
watch: { value: { handler(newVal, oldVal) { this.dwCodes = newVal if(this.multiple && this.dwCodes) { this.dwCodes = this.dwCodes.split(',') } }, immediate: false, deep: false }, dwCodes: { handler(newVal, oldVal) { this.dwCodes = newVal if(this.multiple && this.dwCodes) { // 判断当前是添加勾选还是取消勾选,并获取当前操作的节点值 const current = this.findCurrentDepartment(newVal, oldVal) if (!current) return this.$nextTick(() => { if (current.type === 'checked') { // 级联选择器当前勾选的节点数组 this.currentCheckedNodes = this.$refs.cascaderAddr.checkedNodes // 从级联选择器中取出内部的节点数组,找到当前节点 const targetNode = this.$refs.cascaderAddr.checkedNodes.find((item) => { return item.value === current.value }) if (targetNode) { // 递归找出所有子孙节点,并手动勾选 this.checkedChildrenDepartment([targetNode]) // 更新视图 this.$refs.cascaderAddr.$refs.panel.calculateMultiCheckedValue() } } else if (current.type === "cancel") { // 从当前勾选的节点数组中递归找出所有子孙节点,并取消勾选 this.cancelChildrenDepartment(current.value, this.currentCheckedNodes) // 更新视图 this.$refs.cascaderAddr.$refs.panel.calculateMultiCheckedValue() } }) } }, immediate: false, deep: true } }, methods: { async init() { // 初始化数据源 }, // 值改变 handleChange(checked) { if(this.multiple && checked) { let checknodes = this.$refs['cascaderAddr'].getCheckedNodes() let checkNodeids = checknodes.map((obj,index) => { return obj.value }); // 回传给父组件的值 this.$emit("input", checkNodeids.join()); } else { this.$emit("input", checked); } }, checkedChildrenDepartment(list = []) { list.forEach((item) => { item.doCheck(true) // 勾选选中当前值,若有子节点,子节点遍历全选 if (item.children.length > 0) { this.checkedChildrenDepartment(item.children) } }) }, cancelChildrenDepartment(cancelNodeValue, list = []) { list.forEach((item) => { // 取消勾选时,取消当前值,若当前值有子节点时,实现取消父节点勾选,子节点取消全选的功能,同时实现子节点取消勾选,不会影响父节点勾选状态 if(cancelNodeValue == item.value || cancelNodeValue == item.parent?.value) { item.doCheck(false) if (item.children.length > 0) { // 遍历 this.cancelChildrenDepartment(item.value, item.children) } } }) }, findCurrentDepartment(newArr, oldArr) { const catchNewArr = [...newArr] const catchOldArr = [...oldArr] if (catchNewArr.length > catchOldArr.length) { for (let i = 0; i < catchNewArr.length; i++) { const targetIndex = catchOldArr.indexOf(catchNewArr[i]) if (targetIndex === -1) { return { value: catchNewArr[i], type: 'checked', } } } } else { for (let i = 0; i < catchOldArr.length; i++) { const targetIndex = catchNewArr.indexOf(catchOldArr[i]) if (targetIndex === -1) { return { value: catchOldArr[i], type: 'cancel', } } } } } }
复制
实现效果
1.有全选功能
取消子节点选择,父节点保持选择状态
2.搜索能检索到父节点
3.绑定数据回显
其他
不知道还有没有其他遗漏的地方,反正是实现了,前端开发忙的马不停蹄,一个后端开发又又又在前端挣扎了一下,再次感谢互联网好人们!
我翻了一下历史记录: 参考代码