基于@vueuse/core + @vue/composition-api 实现多行元素展开/折叠功能
列表查询是很常见的功能,复杂的系统模块会有很多查询条件,默认只展示第一行查询条件是个不错的方案。
产品的方案是百变的,可能会让你一行展示固定数目的查询组件,也可能是根据屏幕宽度动态确定数量。
这个时候就很有必要来一个专门处理展开/折叠功能的组件,专注于展开/折叠功能。内部布局用css控制就行了。
实现方案灵感来至于https://segmentfault.com/a/1190000040030723
基于vue2+ @vueuse/core + @vue/composition-api 实现
废话不多说,直接上代码
1 <template> 2 <!-- 折叠容器 3 'is-collapse': isCollapse 根据isCollapse判断是否应用折叠样式 4 --> 5 <div 6 ref="containerEl" 7 class="collapse-container" 8 :class="{ 9 'is-collapse': isCollapse, 10 'collapse-right-bottom': collapseInRightBottom 11 }" 12 > 13 <!-- 通过控制max-height来达到折叠/展开的效果 --> 14 <div class="collapse-el" :class="isExpand ? 'expand' : ''"> 15 <!-- 利用css使折叠组始终在最右侧 --> 16 <div class="right-group box-row box-center-end"> 17 <slot name="prefix"></slot> 18 <div @click="isExpand = !isExpand"> 19 <slot v-if="isCollapse" name="collapse" v-bind="{ isExpand }"> 20 <el-button type="text" class="expand-btn"> 21 {{ isExpand ? '收起' : '展开' }} 22 <i 23 :class="isExpand ? 'el-icon-arrow-up' : 'el-icon-arrow-down'" 24 ></i> 25 </el-button> 26 </slot> 27 </div> 28 <slot name="suffix"></slot> 29 </div> 30 31 <!-- 折叠面板 --> 32 <div ref="collapsePanel" class="panel-el"> 33 <slot></slot> 34 </div> 35 </div> 36 </div> 37 </template> 38 <script> 39 import { defineComponent, ref } from '@vue/composition-api'; 40 import { useElementSize, useCssVar } from '@vueuse/core'; 41 42 import { find } from 'lodash'; 43 /** 44 * 45 */ 46 export default defineComponent({ 47 name: 'UxCollapse', 48 props: { 49 // 是否默认展开 50 defaultExpand: { 51 type: Boolean, 52 default: false 53 }, 54 // 折叠容器是否始终在右下角 55 collapseInRightBottom: { 56 type: Boolean, 57 default: true 58 } 59 }, 60 setup() { 61 // 动态获取collapsePanel的高度 62 // 要监听的元素必须是ref引用的元素 63 const collapsePanel = ref(null); 64 const { height } = useElementSize(collapsePanel); 65 66 // 支持js设置var变量 67 // var变量设置元素位置,必须是ref引用的元素 68 const containerEl = ref(null); 69 const maxHeight = useCssVar('--max-height', containerEl); 70 return { 71 collapsePanel, 72 height, 73 containerEl, 74 maxHeight 75 }; 76 }, 77 data() { 78 return { 79 // 是否开启折叠/展开功能(自动计算获取) 80 isCollapse: false, 81 // 折叠面板是否展开(isCollapse为true时生效) 82 isExpand: false 83 }; 84 }, 85 86 watch: { 87 height() { 88 // console.log('height', v); 89 // 高度变化时,重新计算是否开启折叠功能 90 this.adjustLayout(); 91 }, 92 isExpand(v) { 93 // 抛出事件 94 this.$emit('expandchange', v); 95 } 96 }, 97 98 methods: { 99 /** 100 * @description 计算是否开启折叠/展开功能 101 */ 102 adjustLayout() { 103 const me = this; 104 me.$nextTick(() => { 105 // 获取容器dom 106 const el = me.$el; 107 if (el) { 108 // 延迟执行保证获取到子元素 109 setTimeout(() => { 110 // 获取折叠面对dom 111 const collapsePanel = me.$refs.collapsePanel; 112 const children = collapsePanel.children; 113 // 如果折叠面板有子元素 114 if (children.length) { 115 // 获取第一个元素的offsetTop 116 const firstOffsetTop = collapsePanel.firstChild.offsetTop; 117 // 获取下一行第一个元素 118 const nextRowChild = find(children, (item) => { 119 return item.offsetTop > firstOffsetTop; 120 }); 121 // 如果找不到下一行第一个元素,说明无需折叠 122 this.isCollapse = !!nextRowChild; 123 if (nextRowChild) { 124 // 用下一行第一个元素的offsetTop减去第一个元素的offsetTop就是每一行的高 125 const maxHeight = nextRowChild.offsetTop - firstOffsetTop; 126 // 设置css变量 127 this.maxHeight = maxHeight + 'px'; 128 // 设置默认展开状态 129 this.isExpand = this.defaultExpand; 130 } 131 } 132 }); 133 } 134 }); 135 } 136 } 137 }); 138 </script> 139 <style scoped lang="scss"> 140 @import '~@/assets/sass/all.scss'; 141 142 // 开启折叠/展开功能后容器样式 143 .is-collapse { 144 // flex布局高度计算才能使用百分百 145 display: flex; 146 // 收起/展开按钮 147 .expand-btn { 148 padding: 0; 149 } 150 } 151 .collapse-right-bottom { 152 .collapse-el { 153 // 伪类动态设置高度实现将折叠组挤到最下方的效果 154 &::before { 155 content: ''; 156 height: calc(100% - var(--max-height)); 157 float: right; 158 } 159 } 160 } 161 162 // 折叠容器 163 ::v-deep.collapse-container { 164 --max-height: none; 165 width: 100%; 166 .collapse-el { 167 width: 100%; 168 overflow: hidden; 169 // 通过设置最大高度来实现折叠时只显示一行的效果 170 max-height: var(--max-height); 171 &.expand { 172 // 展开状态取消最大高度限制,实现展开效果 173 max-height: none; 174 } 175 } 176 .right-group { 177 line-height: var(--max-height); 178 // 保证折叠组始终在最右方 179 float: right; 180 clear: both; 181 } 182 } 183 184 // 面板默认布局 185 ::v-deep.panel-el { 186 .item-el { 187 display: inline-block; 188 } 189 } 190 </style>
简单用法
1 <template> 2 <uxCollapse> 3 <div v-for="(item, i) in data" :key="i" class="item-el">{{ item }}</div> 4 </uxCollapse> 5 </template>
1 <script> 2 import { defineComponent } from '@vue/composition-api'; 3 4 import uxCollapse from '@/components/collapse'; 5 export default defineComponent({ 6 name: 'vueuseCollapse', 7 components: { uxCollapse }, 8 data() { 9 return { 10 data: ['张三', '李四', '王五', '赵六', '田七', '钱八', '孙九', '周十', '张三', '李四', '王五', '赵六', '田七', '钱八', '孙九', '周十'] 11 }; 12 } 13 }); 14 </script>
<style lang="scss"> .item-el { margin: 10px; padding: 10px; border: 1px solid #ccc; background-color: #fff; } </style>
效果
每个子元素使用 display: inline-block; 布局即可,通过css可灵活控制内部布局。
复杂效果实现
同一页面窗口放大时效果