403 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			403 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
<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>
 |