<template> <view class="uni-collapse-item"> <!-- onClick(!isOpen) --> <view @click="onClick(!isOpen)" class="uni-collapse-item__title" :class="{'is-open':isOpen &&titleBorder === 'auto' ,'uni-collapse-item-border':titleBorder !== 'none'}"> <view class="uni-collapse-item__title-wrap"> <slot name="title"> <view class="uni-collapse-item__title-box" :class="{'is-disabled':disabled}"> <image v-if="thumb" :src="thumb" class="uni-collapse-item__title-img" /> <text class="uni-collapse-item__title-text">{{ title }}</text> </view> </slot> </view> <view v-if="showArrow" :class="{ 'uni-collapse-item__title-arrow-active': isOpen, 'uni-collapse-item--animation': showAnimation === true }" class="uni-collapse-item__title-arrow"> <uni-icons :color="disabled?'#ddd':'#bbb'" size="14" type="bottom" /> </view> </view> <view class="uni-collapse-item__wrap" :class="{'is--transition':showAnimation}" :style="{height: (isOpen?height:0) +'px'}"> <view :id="elId" ref="collapse--hook" class="uni-collapse-item__wrap-content" :class="{open:isheight,'uni-collapse-item--border':border&&isOpen}"> <slot></slot> </view> </view> </view> </template> <script> // #ifdef APP-NVUE const dom = weex.requireModule('dom') // #endif /** * CollapseItem 折叠面板子组件 * @description 折叠面板子组件 * @property {String} title 标题文字 * @property {String} thumb 标题左侧缩略图 * @property {String} name 唯一标志符 * @property {Boolean} open = [true|false] 是否展开组件 * @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线 * @property {Boolean} border = [true|false] 是否显示分隔线 * @property {Boolean} disabled = [true|false] 是否展开面板 * @property {Boolean} showAnimation = [true|false] 开启动画 * @property {Boolean} showArrow = [true|false] 是否显示右侧箭头 */ export default { name: 'uniCollapseItem', props: { // 列表标题 title: { type: String, default: '' }, name: { type: [Number, String], default: '' }, // 是否禁用 disabled: { type: Boolean, default: false }, // #ifdef APP-PLUS // 是否显示动画,app 端默认不开启动画,卡顿严重 showAnimation: { type: Boolean, default: false }, // #endif // #ifndef APP-PLUS // 是否显示动画 showAnimation: { type: Boolean, default: true }, // #endif // 是否展开 open: { type: Boolean, default: false }, // 缩略图 thumb: { type: String, default: '' }, // 标题分隔线显示类型 titleBorder: { type: String, default: 'auto' }, border: { type: Boolean, default: true }, showArrow: { type: Boolean, default: true } }, data() { // TODO 随机生生元素ID,解决百度小程序获取同一个元素位置信息的bug const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}` return { isOpen: false, isheight: null, height: 0, elId, nameSync: 0 } }, watch: { open(val) { this.isOpen = val this.onClick(val, 'init') } }, updated(e) { this.$nextTick(() => { this.init(true) }) }, created() { this.collapse = this.getCollapse() this.oldHeight = 0 this.onClick(this.open, 'init') }, // #ifndef VUE3 // TODO vue2 destroyed() { if (this.__isUnmounted) return this.uninstall() }, // #endif // #ifdef VUE3 // TODO vue3 unmounted() { this.__isUnmounted = true this.uninstall() }, // #endif mounted() { if (!this.collapse) return if (this.name !== '') { this.nameSync = this.name } else { this.nameSync = this.collapse.childrens.length + '' } if (this.collapse.names.indexOf(this.nameSync) === -1) { this.collapse.names.push(this.nameSync) } else { console.warn(`name 值 ${this.nameSync} 重复`); } if (this.collapse.childrens.indexOf(this) === -1) { this.collapse.childrens.push(this) } this.init() }, methods: { init(type) { // #ifndef APP-NVUE this.getCollapseHeight(type) // #endif // #ifdef APP-NVUE this.getNvueHwight(type) // #endif }, uninstall() { if (this.collapse) { this.collapse.childrens.forEach((item, index) => { if (item === this) { this.collapse.childrens.splice(index, 1) } }) this.collapse.names.forEach((item, index) => { if (item === this.nameSync) { this.collapse.names.splice(index, 1) } }) } }, onClick(isOpen, type) { if (this.disabled) return this.isOpen = isOpen if (this.isOpen && this.collapse) { this.collapse.setAccordion(this) } if (type !== 'init') { this.collapse.onChange(isOpen, this) } }, getCollapseHeight(type, index = 0) { const views = uni.createSelectorQuery().in(this) views .select(`#${this.elId}`) .fields({ size: true }, data => { // TODO 百度中可能获取不到节点信息 ,需要循环获取 if (index >= 10) return if (!data) { index++ this.getCollapseHeight(false, index) return } // #ifdef APP-NVUE this.height = data.height + 1 // #endif // #ifndef APP-NVUE this.height = data.height // #endif this.isheight = true if (type) return this.onClick(this.isOpen, 'init') }) .exec() }, getNvueHwight(type) { const result = dom.getComponentRect(this.$refs['collapse--hook'], option => { if (option && option.result && option.size) { // #ifdef APP-NVUE this.height = option.size.height + 1 // #endif // #ifndef APP-NVUE this.height = option.size.height // #endif this.isheight = true if (type) return this.onClick(this.open, 'init') } }) }, /** * 获取父元素实例 */ getCollapse(name = 'uniCollapse') { let parent = this.$parent; let parentName = parent.$options.name; while (parentName !== name) { parent = parent.$parent; if (!parent) return false; parentName = parent.$options.name; } return parent; } } } </script> <style lang="scss"> .uni-collapse-item { /* #ifndef APP-NVUE */ box-sizing: border-box; /* #endif */ &__title { /* #ifndef APP-NVUE */ display: flex; width: 100%; box-sizing: border-box; /* #endif */ flex-direction: row; align-items: center; transition: border-bottom-color .3s; // transition-property: border-bottom-color; // transition-duration: 5s; &-wrap { width: 100%; flex: 1; } &-box { padding: 0 15px; /* #ifndef APP-NVUE */ display: flex; width: 100%; box-sizing: border-box; /* #endif */ flex-direction: row; justify-content: space-between; align-items: center; height: 48px; line-height: 48px; background-color: #fff; color: #303133; font-size: 13px; font-weight: 500; /* #ifdef H5 */ cursor: pointer; outline: none; /* #endif */ &.is-disabled { .uni-collapse-item__title-text { color: #999; } } } &.uni-collapse-item-border { border-bottom: 1px solid #ebeef5; } &.is-open { border-bottom-color: transparent; } &-img { height: 22px; width: 22px; margin-right: 10px; } &-text { flex: 1; font-size: 14px; /* #ifndef APP-NVUE */ white-space: nowrap; color: inherit; /* #endif */ /* #ifdef APP-NVUE */ lines: 1; /* #endif */ overflow: hidden; text-overflow: ellipsis; } &-arrow { /* #ifndef APP-NVUE */ display: flex; box-sizing: border-box; /* #endif */ align-items: center; justify-content: center; width: 20px; height: 20px; margin-right: 10px; transform: rotate(0deg); &-active { transform: rotate(-180deg); } } } &__wrap { /* #ifndef APP-NVUE */ will-change: height; box-sizing: border-box; /* #endif */ background-color: #fff; overflow: hidden; position: relative; height: 0; &.is--transition { // transition: all 0.3s; transition-property: height, border-bottom-width; transition-duration: 0.3s; /* #ifndef APP-NVUE */ will-change: height; /* #endif */ } &-content { position: absolute; font-size: 13px; color: #303133; // transition: height 0.3s; border-bottom-color: transparent; border-bottom-style: solid; border-bottom-width: 0; &.uni-collapse-item--border { border-bottom-width: 1px; border-bottom-color: red; border-bottom-color: #ebeef5; } &.open { position: relative; } } } &--animation { transition-property: transform; transition-duration: 0.3s; transition-timing-function: ease; } } </style>