| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 | <template>	<view class="u-index-list">		<!-- #ifdef APP-NVUE -->		<list			:scrollTop="scrollTop"			enable-back-to-top			:offset-accuracy="1"			:style="{				maxHeight: $u.addUnit(scrollViewHeight)			}"			@scroll="scrollHandler"			ref="uList"		>			<cell				v-if="$slots.header"				ref="header"			>				<slot name="header" />			</cell>			<slot />			<cell v-if="$slots.footer">				<slot name="footer" />			</cell>		</list>		<!-- #endif -->		<!-- #ifndef APP-NVUE -->		<scroll-view			:scrollTop="scrollTop"			:scrollIntoView="scrollIntoView"			:offset-accuracy="1"			:style="{				maxHeight: $u.addUnit(scrollViewHeight)			}"			scroll-y			@scroll="scrollHandler"			ref="uList"		>			<view v-if="$slots.header">				<slot name="header" />			</view>			<slot />			<view v-if="$slots.footer">				<slot name="footer" />			</view>		</scroll-view>		<!-- #endif -->		<view			class="u-index-list__letter"			ref="u-index-list__letter"			:style="{ top: $u.addUnit(letterInfo.top || 100) }"			@touchstart="touchStart"			@touchmove.stop.prevent="touchMove"			@touchend.stop.prevent="touchEnd"			@touchcancel.stop.prevent="touchEnd"		>			<view				class="u-index-list__letter__item"				v-for="(item, index) in uIndexList"				:key="index"				:style="{					backgroundColor: activeIndex === index ? activeColor : 'transparent'				}"			>				<text					class="u-index-list__letter__item__index"					:style="{color: activeIndex === index ? '#fff' : inactiveColor}"				>{{ item }}</text>			</view>		</view>		<u-transition			mode="fade"			:show="touching"			:customStyle="{				position: 'fixed',				right: '50px',				top: $u.addUnit(indicatorTop),				zIndex: 2			}"		>			<view				class="u-index-list__indicator"				:class="['u-index-list__indicator--show']"				:style="{					height: $u.addUnit(indicatorHeight),					width: $u.addUnit(indicatorHeight)				}"			>				<text class="u-index-list__indicator__text">{{ uIndexList[activeIndex] }}</text>			</view>		</u-transition>	</view></template><script>	const indexList = () => {		const indexList = [];		const charCodeOfA = 'A'.charCodeAt(0);		for (let i = 0; i < 26; i++) {			indexList.push(String.fromCharCode(charCodeOfA + i));		}		return indexList;	}	import props from './props.js';	// #ifdef APP-NVUE	// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度	const dom = uni.requireNativePlugin('dom')	// #endif	/**	 * IndexList 索引列表	 * @description  通过折叠面板收纳内容区域	 * @tutorial https://uviewui.com/components/indexList.html	 * @property {String}			inactiveColor	右边锚点非激活的颜色 ( 默认 '#606266' )	 * @property {String}			activeColor		右边锚点激活的颜色 ( 默认 '#5677fc' )	 * @property {Array}			indexList		索引字符列表,数组形式	 * @property {Boolean}			sticky			是否开启锚点自动吸顶 ( 默认 true )	 * @property {String | Number}	customNavHeight	自定义导航栏的高度 ( 默认 0 )	 * */ 	export default {		name: 'u-index-list',		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],		// #ifdef MP-WEIXIN		// 将自定义节点设置成虚拟的,更加接近Vue组件的表现,能更好的使用flex属性		options: {			virtualHost: true		},		// #endif		data() {			return {				// 当前正在被选中的字母索引				activeIndex: -1,				touchmoveIndex: 1,				// 索引字母的信息				letterInfo: {					height: 0,					itemHeight: 0,					top: 0				},				// 设置字母指示器的高度,后面为了让指示器跟随字母,并将尖角部分指向字母的中部,需要依赖此值				indicatorHeight: 50,				// 字母放大指示器的top值,为了让其指向当前激活的字母				// indicatorTop: 0				// 当前是否正在被触摸状态				touching: false,				// 滚动条顶部top值				scrollTop: 0,				// scroll-view的高度				scrollViewHeight: 0,				// 系统信息				sys: uni.$u.sys(),				scrolling: false,				scrollIntoView: '',			}		},		computed: {			// 如果有传入外部的indexList锚点数组则使用,否则使用内部生成A-Z字母			uIndexList() {				return this.indexList.length ? this.indexList : indexList()			},			// 字母放大指示器的top值,为了让其指向当前激活的字母			indicatorTop() {				const {					top,					itemHeight				} = this.letterInfo				return Math.floor(top + itemHeight * this.activeIndex + itemHeight / 2 - this.indicatorHeight / 2)			}		},		watch: {			// 监听字母索引的变化,重新设置尺寸			uIndexList: {				immediate: true,				handler() {					uni.$u.sleep().then(() => {						this.setIndexListLetterInfo()					})				}			}		},		created() {			this.children = []			this.anchors = []			this.init()		},		mounted() {			this.setIndexListLetterInfo()		},		methods: {			init() {				// 设置列表的高度为整个屏幕的高度				//减去this.customNavHeight,并将this.scrollViewHeight设置为maxHeight				//解决当u-index-list组件放在tabbar页面时,scroll-view内容较少时,还能滚动				this.scrollViewHeight = this.sys.windowHeight - this.customNavHeight			},			// 索引列表被触摸			touchStart(e) {				// 获取触摸点信息				const touchStart = e.changedTouches[0]				if (!touchStart) return				this.touching = true				const {					pageY				} = touchStart				// 根据当前触摸点的坐标,获取当前触摸的为第几个字母				const currentIndex = this.getIndexListLetter(pageY)				this.setValueForTouch(currentIndex)			},			// 索引字母列表被触摸滑动中			touchMove(e) {				// 获取触摸点信息				let touchMove = e.changedTouches[0]				if (!touchMove) return;				// 滑动结束后迅速开始第二次滑动时候 touching 为 false 造成不显示 indicator 问题				if (!this.touching) {					this.touching = true				}				const {					pageY				} = touchMove				const currentIndex = this.getIndexListLetter(pageY)				this.setValueForTouch(currentIndex)			},			// 触摸结束			touchEnd(e) {				// 延时一定时间后再隐藏指示器,为了让用户看的更直观,同时也是为了消除快速切换u-transition的show带来的影响				uni.$u.sleep(300).then(() => {					this.touching = false				})			},			// 获取索引列表的尺寸以及单个字符的尺寸信息			getIndexListLetterRect() {				return new Promise(resolve => {					// 延时一定时间,以获取dom尺寸					// #ifndef APP-NVUE					this.$uGetRect('.u-index-list__letter').then(size => {						resolve(size)					})					// #endif					// #ifdef APP-NVUE					const ref = this.$refs['u-index-list__letter']					dom.getComponentRect(ref, res => {						resolve(res.size)					})					// #endif				})			},			// 设置indexList索引的尺寸信息			setIndexListLetterInfo() {				this.getIndexListLetterRect().then(size => {					const {						height					} = size					const sys = uni.$u.sys()					const windowHeight = sys.windowHeight					let customNavHeight = 0					// 消除各端导航栏非原生和原生导致的差异,让索引列表字母对屏幕垂直居中					if (this.customNavHeight == 0) {						// #ifdef H5						customNavHeight = sys.windowTop						// #endif						// #ifndef H5						// 在非H5中,为原生导航栏,其高度不算在windowHeight内,这里设置为负值,后面相加时变成减去其高度的一半						customNavHeight = -(sys.statusBarHeight + 44)						// #endif					} else {						customNavHeight = uni.$u.getPx(this.customNavHeight)					}					this.letterInfo = {						height,						// 为了让字母列表对屏幕绝对居中,让其对导航栏进行修正,也即往上偏移导航栏的一半高度						top: (windowHeight - height) / 2 + customNavHeight / 2,						itemHeight: Math.floor(height / this.uIndexList.length)					}				})			},			// 获取当前被触摸的索引字母			getIndexListLetter(pageY) {				const {					top,					height,					itemHeight				} = this.letterInfo				// 对H5的pageY进行修正,这是由于uni-app自作多情在H5中将触摸点的坐标跟H5的导航栏结合导致的问题				// #ifdef H5				pageY += uni.$u.sys().windowTop				// #endif				// 对第一和最后一个字母做边界处理,因为用户可能在字母列表上触摸到两端的尽头后依然继续滑动				if (pageY < top) {					return 0				} else if (pageY >= top + height) {					// 如果超出了,取最后一个字母					return this.uIndexList.length - 1				} else {					// 将触摸点的Y轴偏移值,减去索引字母的top值,除以每个字母的高度,即可得到当前触摸点落在哪个字母上					return Math.floor((pageY - top) / itemHeight);				}			},			// 设置各项由触摸而导致变化的值			setValueForTouch(currentIndex) {				// 如果偏移量太小,前后得出的会是同一个索引字母,为了防抖,进行返回				if (currentIndex === this.activeIndex) return				this.activeIndex = currentIndex				// #ifndef APP-NVUE || MP-WEIXIN				// 在非nvue中,由于anchor和item都在u-index-item中,所以需要对index-item进行偏移				this.scrollIntoView = `u-index-item-${this.uIndexList[currentIndex].charCodeAt(0)}`				// #endif				// #ifdef MP-WEIXIN				// 微信小程序下,scroll-view的scroll-into-view属性无法对slot中的内容的id生效,只能通过设置scrollTop的形式去移动滚动条				this.scrollTop = this.children[currentIndex].top				// #endif				// #ifdef APP-NVUE				// 在nvue中,由于cell和header为同级元素,所以实际是需要对header(anchor)进行偏移				const anchor = `u-index-anchor-${this.uIndexList[currentIndex]}`				dom.scrollToElement(this.anchors[currentIndex].$refs[anchor], {					offset: 0,					animated: false				})				// #endif			},			getHeaderRect() {				// 获取header slot的高度,因为list组件中获取元素的尺寸是没有top值的				return new Promise(resolve => {					dom.getComponentRect(this.$refs.header, res => {						resolve(res.size)					})				})			},			// scroll-view的滚动事件			async scrollHandler(e) {				if (this.touching || this.scrolling) return				// 每过一定时间取样一次,减少资源损耗以及可能带来的卡顿				this.scrolling = true				uni.$u.sleep(10).then(() => {					this.scrolling = false				})				let scrollTop = 0				const len = this.children.length				let children = this.children				const anchors = this.anchors				// #ifdef APP-NVUE				// nvue下获取的滚动条偏移为负数,需要转为正数				scrollTop = Math.abs(e.contentOffset.y)				// 获取header slot的尺寸信息				const header = await this.getHeaderRect()				// item的top值,在nvue下,模拟出的anchor的top,类似非nvue下的index-item的top				let top = header.height				// 由于list组件无法获取cell的top值,这里通过header slot和各个item之间的height,模拟出类似非nvue下的位置信息				children = this.children.map((item, index) => {					const child = {						height: item.height,						top					}					// 进行累加,给下一个item提供计算依据					top += item.height + anchors[index].height					return child				})				// #endif				// #ifndef APP-NVUE				// 非nvue通过detail获取滚动条位移				scrollTop = e.detail.scrollTop				// #endif				for (let i = 0; i < len; i++) {					const item = children[i],						nextItem = children[i + 1]					// 如果滚动条高度小于第一个item的top值,此时无需设置任意字母为高亮					if (scrollTop <= children[0].top || scrollTop >= children[len - 1].top + children[len -							1].height) {						this.activeIndex = -1						break					} else if (!nextItem) { 						// 当不存在下一个item时,意味着历遍到了最后一个						this.activeIndex = len - 1						break					} else if (scrollTop > item.top && scrollTop < nextItem.top) {						this.activeIndex = i						break					}				}			},		},	}</script><style lang="scss" scoped>	@import "../../libs/css/components.scss";	.u-index-list {		&__letter {			position: fixed;			right: 0;			text-align: center;			z-index: 3;			padding: 0 6px;			&__item {				width: 16px;				height: 16px;				border-radius: 100px;				margin: 1px 0;				@include flex;				align-items: center;				justify-content: center;				&--active {					background-color: $u-primary;				}				&__index {					font-size: 12px;					text-align: center;					line-height: 12px;				}			}		}		&__indicator {			width: 50px;			height: 50px;			border-radius: 100px 100px 0 100px;			text-align: center;			color: #ffffff;			background-color: #c9c9c9;			transform: rotate(-45deg);			@include flex;			justify-content: center;			align-items: center;			&__text {				font-size: 28px;				line-height: 28px;				font-weight: bold;				color: #fff;				transform: rotate(45deg);				text-align: center;			}		}	}</style>
 |