skyouc
2024-12-21 c9c263dc43ad90f95f24a036cee9e6b47afb596c
uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue
@@ -1,402 +1,402 @@
<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>
<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>