356 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Vue
		
	
	
	
		
		
			
		
	
	
			356 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Vue
		
	
	
	
|  | <template> | |||
|  | 	<view class="u-subsection" :style="[subsectionStyle]"> | |||
|  | 		<view class="u-item u-line-1" :style="[itemStyle(index)]" @tap="click(index)" :class="[noBorderRight(index), 'u-item-' + index]" | |||
|  | 		 v-for="(item, index) in listInfo" :key="index"> | |||
|  | 			<view :style="[textStyle(index)]" class="u-item-text u-line-1">{{ item.name }}</view> | |||
|  | 		</view> | |||
|  | 		<view class="u-item-bg" :style="[itemBarStyle]"></view> | |||
|  | 	</view> | |||
|  | </template> | |||
|  | 
 | |||
|  | <script> | |||
|  | 	/** | |||
|  | 	 * subsection 分段器 | |||
|  | 	 * @description 该分段器一般用于用户从几个选项中选择某一个的场景 | |||
|  | 	 * @tutorial https://www.uviewui.com/components/subsection.html
 | |||
|  | 	 * @property {Array} list 选项的数组,形式见上方"基本使用" | |||
|  | 	 * @property {String Number} current 初始化时默认选中的选项索引值(默认0) | |||
|  | 	 * @property {String} active-color 激活时的颜色,mode为subsection时固定为白色(默认#303133) | |||
|  | 	 * @property {String} inactive-color 未激活时字体的颜色,mode为subsection时无效(默认#606266) | |||
|  | 	 * @property {String} mode 模式选择,见官网"模式选择"说明(默认button) | |||
|  | 	 * @property {String Number} font-size 字体大小,单位rpx(默认28) | |||
|  | 	 * @property {String Number} height 组件高度,单位rpx(默认70) | |||
|  | 	 * @property {Boolean} animation 是否开启动画效果,见上方说明(默认true) | |||
|  | 	 * @property {Boolean} bold 激活选项的字体是否加粗(默认true) | |||
|  | 	 * @property {String} bg-color 组件背景颜色,mode为button时有效(默认#eeeeef) | |||
|  | 	 * @property {String} button-color 按钮背景颜色,mode为button时有效(默认#ffffff) | |||
|  | 	 * @event {Function} change 分段器选项发生改变时触发 | |||
|  | 	 * @example <u-subsection active-color="#ff9900"></u-subsection> | |||
|  | 	 */ | |||
|  | 	export default { | |||
|  | 		name: "u-subsection", | |||
|  | 		props: { | |||
|  | 			// tab的数据
 | |||
|  | 			list: { | |||
|  | 				type: Array, | |||
|  | 				default () { | |||
|  | 					return []; | |||
|  | 				} | |||
|  | 			}, | |||
|  | 			// 当前活动的tab的index
 | |||
|  | 			current: { | |||
|  | 				type: [Number, String], | |||
|  | 				default: 0 | |||
|  | 			}, | |||
|  | 			// 激活的颜色
 | |||
|  | 			activeColor: { | |||
|  | 				type: String, | |||
|  | 				default: '#303133' | |||
|  | 			}, | |||
|  | 			// 未激活的颜色
 | |||
|  | 			inactiveColor: { | |||
|  | 				type: String, | |||
|  | 				default: '#606266' | |||
|  | 			}, | |||
|  | 			// 模式选择,mode=button为按钮形式,mode=subsection时为分段模式
 | |||
|  | 			mode: { | |||
|  | 				type: String, | |||
|  | 				default: 'button' | |||
|  | 			}, | |||
|  | 			// 字体大小,单位rpx
 | |||
|  | 			fontSize: { | |||
|  | 				type: [Number, String], | |||
|  | 				default: 28 | |||
|  | 			}, | |||
|  | 			// 是否开启动画效果
 | |||
|  | 			animation: { | |||
|  | 				type: Boolean, | |||
|  | 				default: true | |||
|  | 			}, | |||
|  | 			// 组件的高度,单位rpx
 | |||
|  | 			height: { | |||
|  | 				type: [Number, String], | |||
|  | 				default: 70 | |||
|  | 			}, | |||
|  | 			// 激活tab的字体是否加粗
 | |||
|  | 			bold: { | |||
|  | 				type: Boolean, | |||
|  | 				default: true | |||
|  | 			}, | |||
|  | 			// mode=button时,组件背景颜色
 | |||
|  | 			bgColor: { | |||
|  | 				type: String, | |||
|  | 				default: '#eeeeef' | |||
|  | 			}, | |||
|  | 			// mode = button时,滑块背景颜色
 | |||
|  | 			buttonColor: { | |||
|  | 				type: String, | |||
|  | 				default: '#ffffff' | |||
|  | 			}, | |||
|  | 			// 在切换分段器的时候,是否让设备震一下
 | |||
|  | 			vibrateShort: { | |||
|  | 				type: Boolean, | |||
|  | 				default: false | |||
|  | 			} | |||
|  | 		}, | |||
|  | 		data() { | |||
|  | 			return { | |||
|  | 				listInfo: [], | |||
|  | 				itemBgStyle: { | |||
|  | 					width: 0, | |||
|  | 					left: 0, | |||
|  | 					backgroundColor: '#ffffff', | |||
|  | 					height: '100%', | |||
|  | 					transition: '' | |||
|  | 				}, | |||
|  | 				currentIndex: this.current, | |||
|  | 				buttonPadding: 3, // mode = button 时,组件的内边距
 | |||
|  | 				borderRadius: 5, // 圆角值
 | |||
|  | 				firstTimeVibrateShort: true // 组件初始化时,会触发current变化,此时不应震动
 | |||
|  | 			}; | |||
|  | 		}, | |||
|  | 		watch: { | |||
|  | 			current: { | |||
|  | 				immediate: true, | |||
|  | 				handler(nVal) { | |||
|  | 					this.currentIndex = nVal; | |||
|  | 					this.changeSectionStatus(nVal); | |||
|  | 				} | |||
|  | 			} | |||
|  | 		}, | |||
|  | 		created() { | |||
|  | 			// 将list的数据,传入listInfo数组,因为不能修改props传递的list值
 | |||
|  | 			// 可以接受直接数组形式,或者数组元素为对象的形式,如:['简介', '评论'],或者[{name: '简介'}, {name: '评论'}]
 | |||
|  | 			this.listInfo = this.list.map((val, index) => { | |||
|  | 				if (typeof val != 'object') { | |||
|  | 					let obj = { | |||
|  | 						width: 0, | |||
|  | 						name: val | |||
|  | 					}; | |||
|  | 					return obj; | |||
|  | 				} else { | |||
|  | 					val.width = 0; | |||
|  | 					return val; | |||
|  | 				} | |||
|  | 			}); | |||
|  | 		}, | |||
|  | 		computed: { | |||
|  | 			// 设置mode=subsection时,滑块特有的样式
 | |||
|  | 			noBorderRight() { | |||
|  | 				return index => { | |||
|  | 					if (this.mode != 'subsection') return; | |||
|  | 					let classs = ''; | |||
|  | 					// 不显示右边的边框
 | |||
|  | 					if (index < this.list.length - 1) classs += ' u-none-border-right'; | |||
|  | 					// 显示整个组件的左右边圆角
 | |||
|  | 					if (index == 0) classs += ' u-item-first'; | |||
|  | 					if (index == this.list.length - 1) classs += ' u-item-last'; | |||
|  | 					return classs; | |||
|  | 				}; | |||
|  | 			}, | |||
|  | 			// 文字的样式
 | |||
|  | 			textStyle() { | |||
|  | 				return index => { | |||
|  | 					let style = {}; | |||
|  | 					// 设置字体颜色
 | |||
|  | 					if (this.mode == 'subsection') { | |||
|  | 						if (index == this.currentIndex) { | |||
|  | 							style.color = '#ffffff'; | |||
|  | 						} else { | |||
|  | 							style.color = this.activeColor; | |||
|  | 						} | |||
|  | 					} else { | |||
|  | 						if (index == this.currentIndex) { | |||
|  | 							style.color = this.activeColor; | |||
|  | 						} else { | |||
|  | 							style.color = this.inactiveColor; | |||
|  | 						} | |||
|  | 					} | |||
|  | 					// 字体加粗
 | |||
|  | 					if (index == this.currentIndex && this.bold) style.fontWeight = 'bold'; | |||
|  | 					// 文字大小
 | |||
|  | 					style.fontSize = this.fontSize + 'rpx'; | |||
|  | 					return style; | |||
|  | 				}; | |||
|  | 			}, | |||
|  | 			// 每个分段器item的样式
 | |||
|  | 			itemStyle() { | |||
|  | 				return index => { | |||
|  | 					let style = {}; | |||
|  | 					if (this.mode == 'subsection') { | |||
|  | 						// 设置border的样式
 | |||
|  | 						style.borderColor = this.activeColor; | |||
|  | 						style.borderWidth = '1px'; | |||
|  | 						style.borderStyle = 'solid'; | |||
|  | 					} | |||
|  | 					return style; | |||
|  | 				}; | |||
|  | 			}, | |||
|  | 			// mode=button时,外层view的样式
 | |||
|  | 			subsectionStyle() { | |||
|  | 				let style = {}; | |||
|  | 				style.height = uni.upx2px(this.height) + 'px'; | |||
|  | 				if (this.mode == 'button') { | |||
|  | 					style.backgroundColor = this.bgColor; | |||
|  | 					style.padding = `${this.buttonPadding}px`; | |||
|  | 					style.borderRadius = `${this.borderRadius}px`; | |||
|  | 				} | |||
|  | 				return style; | |||
|  | 			}, | |||
|  | 			// 滑块的样式
 | |||
|  | 			itemBarStyle() { | |||
|  | 				let style = {}; | |||
|  | 				style.backgroundColor = this.activeColor; | |||
|  | 				style.zIndex = 1; | |||
|  | 				if (this.mode == 'button') { | |||
|  | 					style.backgroundColor = this.buttonColor; | |||
|  | 					style.borderRadius = `${this.borderRadius}px`; | |||
|  | 					style.bottom = `${this.buttonPadding}px`; | |||
|  | 					style.height = uni.upx2px(this.height) - this.buttonPadding * 2 + 'px'; | |||
|  | 					style.zIndex = 0; | |||
|  | 				} | |||
|  | 				return Object.assign(this.itemBgStyle, style); | |||
|  | 			} | |||
|  | 		}, | |||
|  | 		mounted() { | |||
|  | 			setTimeout(() => { | |||
|  | 				this.getTabsInfo(); | |||
|  | 			}, 10); | |||
|  | 		}, | |||
|  | 		methods: { | |||
|  | 			// 改变滑块的样式
 | |||
|  | 			changeSectionStatus(nVal) { | |||
|  | 				if (this.mode == 'subsection') { | |||
|  | 					// 根据滑块在最左边和最右边时,显示左边和右边的圆角
 | |||
|  | 					if (nVal == this.list.length - 1) { | |||
|  | 						this.itemBgStyle.borderRadius = `0 ${this.buttonPadding}px ${this.buttonPadding}px 0`; | |||
|  | 					} | |||
|  | 					if (nVal == 0) { | |||
|  | 						this.itemBgStyle.borderRadius = `${this.buttonPadding}px 0 0 ${this.buttonPadding}px`; | |||
|  | 					} | |||
|  | 					if (nVal > 0 && nVal < this.list.length - 1) { | |||
|  | 						this.itemBgStyle.borderRadius = '0'; | |||
|  | 					} | |||
|  | 				} | |||
|  | 				// 更新滑块的位置
 | |||
|  | 				setTimeout(() => { | |||
|  | 					this.itemBgLeft(); | |||
|  | 				}, 10); | |||
|  | 				if (this.vibrateShort && !this.firstTimeVibrateShort) { | |||
|  | 					// 使手机产生短促震动,微信小程序有效,APP(HX 2.6.8)和H5无效
 | |||
|  | 					// #ifndef H5
 | |||
|  | 					uni.vibrateShort(); | |||
|  | 					// #endif
 | |||
|  | 				} | |||
|  | 				// 第一次过后,设置firstTimeVibrateShort为false,让其下一次可以震动(如果允许震动的话)
 | |||
|  | 				this.firstTimeVibrateShort = false; | |||
|  | 			}, | |||
|  | 			click(index) { | |||
|  | 				// 不允许点击当前激活选项
 | |||
|  | 				if (index == this.currentIndex) return; | |||
|  | 				this.currentIndex = index; | |||
|  | 				this.changeSectionStatus(index); | |||
|  | 				this.$emit('change', Number(index)); | |||
|  | 			}, | |||
|  | 			// 获取各个tab的节点信息
 | |||
|  | 			getTabsInfo() { | |||
|  | 				let view = uni.createSelectorQuery().in(this); | |||
|  | 				for (let i = 0; i < this.list.length; i++) { | |||
|  | 					view.select('.u-item-' + i).boundingClientRect(); | |||
|  | 				} | |||
|  | 				view.exec(res => { | |||
|  | 					if (!res.length) { | |||
|  | 						setTimeout(() => { | |||
|  | 							this.getTabsInfo(); | |||
|  | 							return; | |||
|  | 						}, 10); | |||
|  | 					} | |||
|  | 					// 将分段器每个item的宽度,放入listInfo数组
 | |||
|  | 					res.map((val, index) => { | |||
|  | 						this.listInfo[index].width = val.width; | |||
|  | 					}); | |||
|  | 					// 初始化滑块的宽度
 | |||
|  | 					if (this.mode == 'subsection') { | |||
|  | 						this.itemBgStyle.width = this.listInfo[0].width + 'px'; | |||
|  | 					} else if (this.mode == 'button') { | |||
|  | 						this.itemBgStyle.width = this.listInfo[0].width + 'px'; | |||
|  | 					} | |||
|  | 					// 初始化滑块的位置
 | |||
|  | 					this.itemBgLeft(); | |||
|  | 				}); | |||
|  | 			}, | |||
|  | 			itemBgLeft() { | |||
|  | 				// 根据是否开启动画效果,
 | |||
|  | 				if (this.animation) { | |||
|  | 					this.itemBgStyle.transition = 'all 0.35s'; | |||
|  | 				} else { | |||
|  | 					this.itemBgStyle.transition = 'all 0s'; | |||
|  | 				} | |||
|  | 				let left = 0; | |||
|  | 				// 计算当前活跃item到组件左边的距离
 | |||
|  | 				this.listInfo.map((val, index) => { | |||
|  | 					if (index < this.currentIndex) left += val.width; | |||
|  | 				}); | |||
|  | 				// 根据mode不同模式,计算滑块需要移动的距离
 | |||
|  | 				if (this.mode == 'subsection') { | |||
|  | 					this.itemBgStyle.left = left + 'px'; | |||
|  | 				} else if (this.mode == 'button') { | |||
|  | 					this.itemBgStyle.left = left + this.buttonPadding + 'px'; | |||
|  | 				} | |||
|  | 			} | |||
|  | 		} | |||
|  | 	}; | |||
|  | </script> | |||
|  | 
 | |||
|  | <style lang="scss" scoped> | |||
|  | 	@import "../../libs/css/style.components.scss"; | |||
|  | 	 | |||
|  | 	.u-subsection { | |||
|  | 		@include vue-flex; | |||
|  | 		align-items: center; | |||
|  | 		overflow: hidden; | |||
|  | 		position: relative; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-item { | |||
|  | 		flex: 1; | |||
|  | 		text-align: center; | |||
|  | 		font-size: 26rpx; | |||
|  | 		height: 100%; | |||
|  | 		@include vue-flex; | |||
|  | 		align-items: center; | |||
|  | 		justify-content: center; | |||
|  | 		color: $u-main-color; | |||
|  | 		padding: 0 6rpx; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-item-bg { | |||
|  | 		background-color: $u-type-primary; | |||
|  | 		position: absolute; | |||
|  | 		z-index: -1; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-none-border-right { | |||
|  | 		border-right: none !important; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-item-first { | |||
|  | 		border-top-left-radius: 8rpx; | |||
|  | 		border-bottom-left-radius: 8rpx; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-item-last { | |||
|  | 		border-top-right-radius: 8rpx; | |||
|  | 		border-bottom-right-radius: 8rpx; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-item-text { | |||
|  | 		transition: all 0.35s; | |||
|  | 		color: $u-main-color; | |||
|  | 		@include vue-flex; | |||
|  | 		align-items: center; | |||
|  | 		position: relative; | |||
|  | 		z-index: 3; | |||
|  | 	} | |||
|  | </style> |