preface

In the use ofuViewIn the development project, I encountered a business scenario: click the input box to pop up the bottom window with the built-in index list of telephone number area code, click to switch area code:

I ran into a problem when I logically thought I would add u-index-list to u-popup:

  1. [Fixed] Index lists cannot scroll
  2. Select the index character, the list is not the top;
  3. Deviation between aim point and top.

So the author began to investigate, but the API document did not write relevant usage, so began baidu, but found that everyone was asking… (maybe you also baidu such articles: ask.dcloud.net.cn/question/10…).

The official website also has no suitable satisfactory solution, so the author can only modify the source code..

1. I can’t roll

At first it was strange to see that other instances could scroll, but inu-popupNo, the original document was writtenonPageScrollThis method is for the entire page to scroll:

Therefore, for business scenarios, this method is not applicable, the first idea is to add a layer of scroll view in U-POPup, but it does not work… The documentation shows that the original component is already built in, so this is also invalid.

Solution: Listen to the pop-up layer of the scroll, using a child component to the pop-up layerscrollTopPassed to the parent componentu-index-list

  • Find this code in the U-Popup component and add event Scroll:

  • Get in the methodu-popupthescrollTop:
  • Parent component reassign:

2. It doesn’t work

First find the object code inu-index-listIn:Here is the scroll top for the entire page, so remove this code again and pass the height of the container from the top to the parent component: u-popupScroll height added with container height:

3. Height deviation:

Locate the object code:

Note: before modifying source code to understand the logic of the source oh ~

Then add or subtract depending on the height of your pop-up layer.

Here is the complete code:

<u-popup :scrollTopBox="scrollTopBox" v-model="show" mode="bottom" height="1028rpx" @scroll="scroll">
			<! -- tab -->
			<view class="tabs">
				<view class="tabs_wrapper">
					<view class="tabs_wrapper_nav">
						<view class="tabs"><text>The word area code</text></view>
						<view class="tabs__line"></view>
					</view>
					<view class="cancel_button"><text @click="show = false">cancel</text></view>
				</view>
			</view>
			<u-index-list :scrollTop="scrollTop" @select="select" :index-list="indexList" activeColor="#e00202">
				<view v-for="(item, index) in cityArr" :key="index">
					<u-index-anchor :index="item.letter" />
					<view class="list-cell" v-for="(item1, index) in item.data" :key="index" @click="selected(item1.code)">
						<text>{{ item1.en }}</text>
						<text>{{ item1.code }}</text>
					</view>
				</view>
			</u-index-list>
		</u-popup>
Copy the code
data() {
		return {
			show: false.scrollTop: 0.scrollTopBox: 0.indexList: ['A'.'B'.'C'.'D'.'E'.'F'.'G'.'H'.'I'.'J'.'K'.'L'.'M'.'N'.'O'.'P'.'Q'.'R'.'S'.'T'.'U'.'V'.'W'.'Y'.'Z'],},methods: {scroll(val) {
				// console.log(val)
				this.scrollTop = val;
			},
			select(index) {
			this.scrollTopBox = index + this.scrollTop; }},Copy the code

U – popup components:

<template>
	<view v-if="visibleSync" :style="[customStyle, { zIndex: uZindex - 1 }]" class="u-drawer" hover-stop-propagation>
		<u-mask :duration="duration" :custom-style="maskCustomStyle" :maskClickAble="maskCloseAble" :z-index="uZindex - 2" :show="showDrawer && mask" @click="maskClick"></u-mask>
		<view
			class="u-drawer-content"
			@tap="modeCenterClose(mode)"
			:class="[ safeAreaInsetBottom ? 'safe-area-inset-bottom' : '', 'u-drawer-' + mode, showDrawer ? 'u-drawer-content-visible' : '', zoom && mode == 'center' ? 'u-animation-zoom' : '' ]"
			@touchmove.stop.prevent
			@tap.stop.prevent
			:style="[style]"
		>
			<view class="u-mode-center-box" @tap.stop.prevent @touchmove.stop.prevent v-if="mode == 'center'" :style="[centerStyle]">
				<u-icon
					@click="close"
					v-if="closeable"
					class="u-close"
					:class="['u-close--' + closeIconPos]"
					:name="closeIcon"
					:color="closeIconColor"
					:size="closeIconSize"
				></u-icon>
				<scroll-view class="u-drawer__scroll-view" scroll-y="true">
					<slot />
				</scroll-view>
			</view>
			<scroll-view class="u-drawer__scroll-view" scroll-y="true" :scroll-top="scrollTopBox" v-else @scroll="scroll">
				<slot />
			</scroll-view>
			<view @tap="close" class="u-close" :class="['u-close--' + closeIconPos]">
				<u-icon
					v-if="mode ! = 'center' && closeable"
					:name="closeIcon"
					:color="closeIconColor"
					:size="closeIconSize"
				></u-icon>
			</view>
		</view>
	</view>
</template>

<script>
	
		import uMask from ".. /u-mask/u-mask.vue";
		import zIndex from '.. /libs/config/zIndex.js'; /** ** popup * @description Popup layer container, used to display pop-ups, messages, etc., support up, down, left, right and middle popup. Components only provide containers, Internal content by user-defined * * @ @ tutorial https://www.uviewui.com/components/popup.html property {String} mode pop-up direction (left) by default * @ property {Boolean} mask Whether to display a mask (defaulttrue) * @ property {Stringr | Number} length mode = left | see website instructions (default auto) * @ property {Boolean} zoom zoom animation, whether open This parameter is valid only when mode is center (default)true) * @property {Boolean} safe-area-inset-bottom Indicates whether bottom security adaptation is enabled (default)false* @property {Boolean} mask-close-able Whether clicking on the mask can turn off the pop-up layer (default)true) * @ property {Object} the custom - style custom style * @ property {Stringr | Number} negative - top pop up in central, Offset value * @ up property {Numberr | String} border - the radius pop-up the rounded value (the default 0) * @ property {Numberr | String} z - index The z-index value of the pop-up content (default 1075) * @property {Boolean} Closeable whether to display the close icon (default)false) * @property {String} close-icon Name of the close icon, Only uView built-in icon * @property {String} close-icon-pos custom close icon position (default top-right) * @property {String} close-icon-color Close icon color (default)# 909399)* @ property {Number | String} the close icon - size the size of the close icon, Unit RPX (default 30) * @event {Function} open Popup layer open * @event {Function} close Popup layer close * @example <u-popup V-model ="show"</view></u-popup> */export default {
	components:{
		uMask
	},
	name: 'u-popup', props: {// Props :{type: Number, default: 0}, /** * display status */ show: {type: Boolean,
			default: false}, / * * * pop-up directions, left | right | top | | bottom center * / mode: {type: String,
			default: 'left'}, /** * whether to display mask */ mask: {type: Boolean,
			default: true}, / / the width of the drawer (mode = left | right), or high (mode = top | bottom), the unit the RPX, or"auto"// Or percentages"50%", indicating the height or width to be spread by the content length: {type: [Number, String],
			default: 'auto'}, // Whether to enable zoom animation, only valid when mode=center zoom: {type: Boolean,
			default: true}, // whether to enable the bottom security adaption, if enabled, will add a certain margin at the bottom of the iPhoneX model safeAreaInsetBottom: {type: Boolean,
			default: false}, // Can I close maskCloseAble by clicking on the mask: {type: Boolean,
			default: true}, // customStyle customStyle: {type: Object,
			default() {
				return {};
			}
		},
		value: {
			type: Boolean,
			default: false}, // this is an internal parameter and is not used in this document. // This is an internal parameter and is not used in this document. // Popup: {type: Boolean,
			default: true}, // display the rounded corner of the popover, RPX borderRadius: {type: [Number, String],
			default: 0
		},
		zIndex: {
			type: [Number, String],
			default: ' '}, // Whether to display closeable icon: {type: Boolean,
			default: false}, // Close the name of the icon, only uView's built-in icon closeIcon: {type: String,
			default: 'close'}, // Customize closeIconPos with top-left, top-right, bottom-left and bottom-right as closeIconPos: {type: String,
			default: 'top-right'}, // Close the icon color closeIconColor: {type: String,
			default: '# 909399'}, // Close the icon size, in RPX closeIconSize: {type: [String, Number],
			default: '30'}, // Width only applies to left, right, and middle pop-ups, in RPX, or"auto"// Or percentages"50%", indicating the height or width of the content, with priority over the length parameter width: {type: String,
			default: ' '}, // height only applies to up, down, and middle pop-ups, in RPX, or"auto"// Or percentages"50%", indicating the height or width of the content, with priority over the length parameter height: {type: String,
			default: ' '}, // give a negative margin-top to avoid overlap with the keyboard, only when mode=center negativeTop: {type: [String, Number], default: 0}, // Mask style, usually used to modify the transparency of the mask maskCustomStyle: {type: Object,
			default() {
				return{}}}, // Animation transition time of mask opening or folding, unit ms duration: {type: [String, Number],
			default: 250
		}
	},
	data() {
		return {
			visibleSync: false,
			showDrawer: false,
			timer: null,
			closeFromInner: false, // the value change is internal or external}; }, computed: {/ / according to the position of the mode, set the popup window width (mode = left | right), or high (mode = top | bottom)style() {
			letstyle = {}; // If left or up pops, set translate to a negative value to hide itif (this.mode == 'left' || this.mode == 'right') {
				style = {
					width: this.width ? this.getUnitValue(this.width) : this.getUnitValue(this.length),
					height: '100%',
					transform: `translate3D(${this.mode == 'left' ? '100%' : '100%'},0px,0px)`
				};
			} else if (this.mode == 'top' || this.mode == 'bottom') {
				style = {
					width: '100%',
					height: this.height ? this.getUnitValue(this.height) : this.getUnitValue(this.length),
					transform: `translate3D(0px,${this.mode == 'top' ? '100%' : '100%'},0px)` }; } style.zIndex = this.uZindex; // If the user sets the borderRadius value, add the rounded corner of the popoverif (this.borderRadius) {
				switch (this.mode) {
					case 'left':
						style.borderRadius = `0 ${this.borderRadius}rpx ${this.borderRadius}rpx 0`;
						break;
					case 'top':
						style.borderRadius = `0 0 ${this.borderRadius}rpx ${this.borderRadius}rpx`;
						break;
					case 'right':
						style.borderRadius = `${this.borderRadius}rpx 0 0 ${this.borderRadius}rpx`;
						break;
					case 'bottom':
						style.borderRadius = `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`;
						break; Default:} // overflow = overflow'hidden';
			}
			if(this.duration) style.transition = `all ${this.duration / 1000}s linear`;
			returnstyle; }, // The middle popover's unique stylecenterStyle() {
			letstyle = {}; style.width = this.width ? this.getUnitValue(this.width) : this.getUnitValue(this.length); Style. Height = this.height? this.getUnitValue(this.height) :'auto';
			style.zIndex = this.uZindex;
			style.marginTop = `-${this.$u.addUnit(this.negativeTop)}`;
			if (this.borderRadius) {
				style.borderRadius = `${this.borderRadius}rpx`; // No possible rounded corners invalid style. Overflow ='hidden';
			}
			returnstyle; }, // calculate the z-indexuZindex() {
			return this.zIndex ? this.zIndex : zIndex.popup;
		}
	},
	watch: {
		value(val) {
			if (val) {
				this.open();
			} else if(! this.closeFromInner) { this.close(); } this.closeFromInner =false; }},mounted() {// When the component is finished rendering, check if value istrueIf yes, popup this.value && this.open(); // console.log(this.scrolltopbox)}, methods: {// getUnitValue(val) {if(/(%|px|rpx|auto)$/.test(val)) return val;
			else return val + 'rpx'
		},
		scroll(e){
			// console.log(e.detail.scrollTop)
			this.$emit('scroll', e.detail.scrollTop); }, // The mask is clickedmaskClick() {
			this.close();
		},
		closeCloseFromInner = closeFromInner = closeFromInner = closeFromInner = closeFromInner = closeFromInner = closeFromInner = closeFromInner = closeFromInner = closeFromInner = closeFromInnertrue;
			this.change('showDrawer'.'visibleSync'.false); }, // u-drawer-content will center the content, this element will cover the screen, click need to close the popover // modeCenterClose(mode) {if(mode ! ='center'| |! this.maskCloseAble)return;
			this.close();
		},
		open() {
			this.change('visibleSync'.'showDrawer'.true); }, // If this.popup is set to change(param1, param2, status) {// If this.popup is set to param1, param2, statusfalse, meaning that the popUp component is called for picker, Actionsheet, etcif (this.popup == true) {
				this.$emit('input', status);
			}
			this[param1] = status;
			if(status) {
				// #ifdef H5 || MP
				this.timer = setTimeout(() => {
					this[param2] = status;
					this.$emit(status ? 'open' : 'close');
				}, 50);
				// #endif
				// #ifndef H5 || MP
				this.$nextTick(() => {
					this[param2] = status;
					this.$emit(status ? 'open' : 'close'); / /})#endif
			} else {
				this.timer = setTimeout(() => {
					this[param2] = status;
					this.$emit(status ? 'open' : 'close'); }, this.duration); }}}}; </script> <style scoped lang="scss">
@import ".. /libs/css/style.components.scss";

.u-drawer {
	/* #ifndef APP-NVUE */display: block; / *#endif */
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	overflow: hidden;
}

.u-drawer-content {
	/* #ifndef APP-NVUE */display: block; / *#endif */position: absolute; z-index: 1003; The transition: all 0.25 s linear; } .u-drawer__scroll-view { width: 100%; height: 100%; } .u-drawer-left { top: 0; bottom: 0; left: 0; background-color:#ffffff;
}

.u-drawer-right {
	right: 0;
	top: 0;
	bottom: 0;
	background-color: #ffffff;
}

.u-drawer-top {
	top: 0;
	left: 0;
	right: 0;
	background-color: #ffffff;
}

.u-drawer-bottom {
	bottom: 0;
	left: 0;
	right: 0;
	background-color: #ffffff;} .u-drawer-center { @include vue-flex; flex-direction: column; bottom: 0; left: 0; right: 0; top: 0; justify-content: center; align-items: center; opacity: 0; z-index: 99999; } .u-mode-center-box { min-width: 100rpx; min-height: 100rpx; / *#ifndef APP-NVUE */display: block; / *#endif */
	position: relative;
	background-color: #ffffff;
}

.u-drawer-content-visible.u-drawer-center {
	transform: scale(1);
	opacity: 1;
}

.u-animation-zoom {
	transform: scale(1.15);
}

.u-drawer-content-visible {
	transform: translate3D(0px, 0px, 0px) !important;
}

.u-close {
	position: absolute;
	z-index: 3;
}

.u-close--top-left {
	top: 30rpx;
	left: 30rpx;
}

.u-close--top-right {
	top: 30rpx;
	right: 30rpx;
}

.u-close--bottom-left {
	bottom: 30rpx;
	left: 30rpx;
}

.u-close--bottom-right {
	right: 30rpx;
	bottom: 30rpx;
}
</style>

Copy the code

U – index – list component:

<template> <! -- Use alipay mini program$u.getRect() gets the size of the component's root element, so wrap one around it"Shell" -->
	<view>
		<view class="u-index-bar">
			<slot />
			<view v-if="showSidebar" class="u-index-bar__sidebar" @touchstart.stop.prevent="onTouchMove" @touchmove.stop.prevent="onTouchMove"
			 @touchend.stop.prevent="onTouchStop" @touchcancel.stop.prevent="onTouchStop">
				<view v-for="(item, index) in indexList" :key="index" class="u-index-bar__index" :style="{zIndex: zIndex + 1, color: activeAnchorIndex === index ? activeColor : ''}"
				 :data-index="index">
					{{ item }}
				</view>
			</view>
			<view class="u-indexed-list-alert" v-if="touchmove && indexList[touchmoveIndex]" :style="{ zIndex: alertZIndex }">
				<text>{{indexList[touchmoveIndex]}}</text>
			</view>
		</view>
	</view>
</template>

<script>
	
	import zIndex from '.. /libs/config/zIndex.js';
			
	var indexList = function() {
		var indexList = [];
		var charCodeOfA = 'A'.charCodeAt(0);
		for (var i = 0; i < 26; i++) {
			indexList.push(String.fromCharCode(charCodeOfA + i));
		}
		returnindexList; }; /** * indexList * @description wraps the content area by collapsing the panel, using * @tutorial with < U-index-anchor > https://www.uviewui.com/components/indexList.html#indexanchor-props* @property {Number String} Scrolltop Indicates the current scroll height. The customized component cannot obtain scrollbar events, so it depends on the access party to pass * @property {Array} index-list index character list. Array (default A-Z) * @property {Number String} z-index Hierarchy of anchor points (default 965) * @Property {Boolean} Whether to enable automatic anchor points (default)true) * @property {Number String} offset-top Distance between anchor automatic top and top (default 0) * @property {String} highlight-color anchor and right index character highlight color (default)# 2979 ff)* @event {Function} select * @example <u-index-list :scrollTop="scrollTop"></u-index-list>
	 */
	export default {
		name: "u-index-list",
		props: {
			sticky: {
				type: Boolean,
				default: true
			},
			zIndex: {
				type: [Number, String],
				default: ' '
			},
			scrollTop: {
				type: [Number, String],
				default: 0,
			},
			offsetTop: {
				type: [Number, String],
				default: 0
			},
			indexList: {
				type: Array,
				default () {
					return indexList()
				}
			},
			activeColor: {
				type: String,
				default: '#2979ff'}},created() {
			// #ifdef H5
			this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44;
			// #endif
			// #ifndef H5
			this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0;
			// #endifThis.children = []; this.children = []; this.children = []; },data() {
			return {
				activeAnchorIndex: 0,
				showSidebar: true,
				// children: [],
				touchmove: false,
				touchmoveIndex: 0,
			}
		},
		watch: {
			scrollTop() {this.updateData()}}, computed: {// Pop up the z-index value of toastalertZIndex() {
				return zIndex.toast;
			}
		},
		methods: {
			updateData() { this.timer && clearTimeout(this.timer); this.timer = setTimeout(() => { this.showSidebar = !! this.children.length; this.setRect().then(() => {
						this.onScroll();
					});
				}, 0);
			},
			setRect() {
				return Promise.all([
					this.setAnchorsRect(),
					this.setListRect(),
					this.setSiderbarRect()
				]);
			},
			setAnchorsRect() {
				return Promise.all(this.children.map((anchor, index) => anchor
					.$uGetRect('.u-index-anchor-wrapper').then((rect) => {
						Object.assign(anchor, {
							height: rect.height,
							top: rect.top
						});
					})));
			},
			setListRect() {
				return this.$uGetRect('.u-index-bar').then((rect) => {
					Object.assign(this, {
						height: rect.height,
						top: rect.top + this.scrollTop
					});
				});
			},
			setSiderbarRect() {
				return this.$uGetRect('.u-index-bar__sidebar').then(rect => {
					this.sidebar = {
						height: rect.height,
						top: rect.top
					};
				});
			},
			getActiveAnchorIndex() {
				const {
					children
				} = this;
				const {
					sticky
				} = this;
				for (let i = this.children.length - 1; i >= 0; i--) {
					let itemTop = children[i].top - 100;
					const preAnchorHeight = i > 0 ? children[i - 1].height : 0;
					const reachTop = sticky ? preAnchorHeight : 0;
					// console.log(reachTop)
					if (reachTop >= itemTop) {
						returni; }}return- 1; },onScroll() {
				const {
					children = []
				} = this;
				if(! children.length) {return;
				}
				const {
					sticky,
					stickyOffsetTop,
					zIndex,
					scrollTop,
					activeColor
				} = this;
				const active = this.getActiveAnchorIndex();
				this.activeAnchorIndex = active;
				if (sticky) {
					let isActiveAnchorSticky = false;
					if(active ! == -1) { isActiveAnchorSticky = children[active].top - 100 <= 0; } children.forEach((item, index) => {if (index === active) {
							let wrapperStyle = ' ';
							let anchorStyle = {
								color: `${activeColor}`};if (isActiveAnchorSticky) {
								wrapperStyle = {
									height: `${children[index].height}px`
								};
								anchorStyle = {
									position: 'fixed',
									top: `${stickyOffsetTop}px`,
									zIndex: `${zIndex ? zIndex : zIndex.indexListSticky}`,
									color: `${activeColor}`}; } item.active = active; item.wrapperStyle = wrapperStyle; item.anchorStyle = anchorStyle; }else if (index === active - 1) {
							const currentAnchor = children[index];
							const currentOffsetTop = currentAnchor.top;
							const targetOffsetTop = index === children.length - 1 ?
								this.top :
								children[index + 1].top;
							const parentOffsetHeight = targetOffsetTop - currentOffsetTop;
							const translateY = parentOffsetHeight - currentAnchor.height;
							const anchorStyle = {
								position: 'relative',
								transform: `translate3d(0, ${translateY}px, 0)`,
								zIndex: `${zIndex ? zIndex : zIndex.indexListSticky}`,
								color: `${activeColor}`}; item.active = active; item.anchorStyle = anchorStyle; }else {
							item.active = false;
							item.anchorStyle = ' ';
							item.wrapperStyle = ' '; }}); } }, onTouchMove(event) { this.touchmove =true;
				const sidebarLength = this.children.length;
				const touch = event.touches[0];
				const itemHeight = this.sidebar.height / sidebarLength;
				let clientY = 0;
				clientY = touch.clientY;
				let index = Math.floor((clientY - this.sidebar.top) / itemHeight);
				if (index < 0) {
					index = 0;
				} else if (index > sidebarLength - 1) {
					index = sidebarLength - 1;
				}
				this.touchmoveIndex = index;
				this.scrollToAnchor(index);
			},
			onTouchStop() {
				this.touchmove = false;
				this.scrollToAnchorIndex = null;
			},
			scrollToAnchor(index) {
				if (this.scrollToAnchorIndex === index) {
					return;
				}
				this.scrollToAnchorIndex = index;
				const anchor = this.children.find((item) => item.index === this.indexList[index]);
				if (anchor) {
					this.$emit('select', anchor.top); // uni.pageScrollTo({ // duration: 0, // scrollTop: anchor.top + this.scrollTop // }); }}}}; </script> <style lang="scss" scoped>
	@import ".. /libs/css/style.components.scss";
	
	.u-index-bar {
		margin-top: 45px;
		position: relative
	}

	.u-index-bar__sidebar {
		position: fixed;
		top: 304px;
		right: 0;
		@include vue-flex;
		flex-direction: column;
		text-align: center;
		transform: translateY(-50%);
		user-select: none;
		z-index: 99;
		background-color: #c8c8c8;font-family: Helvetica; width: 24px; } .u-index-bar__index { font-weight: 500; padding: 8rpx; font-size: 22rpx; line-height: 1; Color: hsla (% 0, 0, 100%, 5); } .u-indexed-list-alert { position: fixed; width: 120rpx; height: 120rpx; right: 90rpx; top: 50%; margin-top: -60rpx; border-radius: 24rpx; font-size: 50rpx; color:#fff;Background: rgba(0, 0, 0, 0.65); @include vue-flex; justify-content: center; align-items: center; padding: 0; z-index: 9999999; } .u-indexed-list-alert text { line-height: 50rpx; } </style>Copy the code