基于uni-app的血糖血压刻度滑动控件

想要做一个基于uni-app的血糖血压刻度滑动控件,hbuilder市场没有好的,参照别人的写了一个。如图:

源码,自己放入components里面。

html 复制代码
<!-- 刻度滑动选择 -->
<template>
	<view>
		<view class="slide-title">
			<view class="slide-title-left">{{subTitle}}</view>
			<view class="slide-title-num">{{selvalue}}</view>
			<view class="slide-title-right">{{subUnit}}</view>
		</view>
		<view class="slide-scroll-border">
			<scroll-view scroll-x class="slide-scroll" :enable-flex="true" show-scrollbar="false"
				:style="{width: setUnit(screenW)}" @scroll="slideScroll"
				:scroll-left="scrollLeft" @touchend="slideMoveEnd" :scroll-with-animation="true">
				<view class="slide-scroll slide-border">
					<view class="empty-none" :style="{width: screenW /2 + 'px'}"></view>
						<view v-for="(s_item, s_index) in list" :key="s_index" class="" :class="{'slide-list':true,'selected': s_index === selInd,'selected1': (s_index === selInd + 1 || s_index === selInd - 1),'selected2': (s_index === selInd + 2 || s_index === selInd - 2),'selected3': (s_index === selInd + 3 || s_index === selInd - 3) }">
							<view class="list-num" v-if="s_index%10==0">{{s_item}}</view>
							<view class="slide-list-ba"></view>
						</view>
					<view class="empty-none" :style="{width: screenW / 2 + 'px'}"></view>
				</view>
			</scroll-view>
		</view>
		<view class="slide-message" v-if="message">{{message}}</view>
	</view>
</template>

<script>
	export default {
        name:'zlSlider',
		data() {
			return {
				scrollLeft: 0, // 仅用于核准中间位置
				d_len:20,
				list: [],
				selvalue:0,
				selInd: 0,
				cellWidth:12,// 每小格宽度
				screenW:uni.getSystemInfoSync().screenWidth
			};
		},
		props: {
			subTitle: 	{	type: String,	default: '' },
			subUnit: 	{	type: String,	default: '' },
			message: 	{	type: String,	default: '' },
			startNum: 	{	type: Number,	default: 0 },	// 开始数值
			endNum: 	{	type: Number,	default: 20 },	// 结束数值
			step: 		{	type: Number,	default: 1 },	// 结束数值
			value: 		{	type: Number,	default: 0 },	// 用v-model双向绑定
		},
		mounted() {
			// 获取滑动显示内容的宽度。
			uni.createSelectorQuery().in(this).select('.slide-scroll-border').boundingClientRect(data => {
				if (data) this.screenW = data.width-60;
			}).exec();
			this.initSlide();
		},
		methods: {
			setUnit(unit) {
				if (typeof unit === 'number') {
					return (unit + 'px')
				} else {
					return unit;
				}
			},
			// 滚动触发
			slideScroll(e) {
				const { cellWidth} = this;
				const scrollL = e.detail.scrollLeft; // 当前滚动距离左边的距离
				let ind = parseInt(scrollL / cellWidth); // 当前选中是第几个
				if (ind > this.d_len) {
					ind = this.d_len;
				}
				if (ind !== this.selInd) {
					this.selInd = ind;
					this.selvalue = this.getIntNum(this.selInd*this.step+this.startNum)
				}
			},
			// 触摸结束
			slideMoveEnd(e) {
				// const end_t = setTimeout(() => {
				// 	const { selInd } = this;
				// 	this.slideTo(selInd);
				// 	this.setEmitFunc();
				// 	clearTimeout(end_t);
				// }, 400);
				const { selInd } = this;
				this.slideTo(selInd);
				this.setEmitFunc();
			},
			// 初始化
			initSlide() {
				const { startNum, endNum, step } = this;
				this.d_len = (endNum - startNum)*(1/step);
				// let list = [...Array(this.d_len).keys()].map(index=>this.getIntNum(startNum + index*this.step))
				// for (let i = 0; i <= this.d_len; i++) {
				// 	const l_num = this.getIntNum(startNum + i*this.step);
				// 	list.push(l_num);
				// }
				this.list = [...Array(this.d_len).keys()].map(index=>this.getIntNum(startNum + index*step));
				this.getSelInd();
			},
			// 滚动到正确的刻度
			slideTo(ind) {
				const { screenW, cellWidth, scrollLeft } = this;
				const newLeft = ind * cellWidth + (cellWidth / 2);
				this.$nextTick(()=>{
					this.scrollLeft = (scrollLeft === newLeft) ? (scrollLeft + 0.001) : newLeft;
				})
			},
			// 四舍五入
			getIntNum(n) {
				return parseInt(n * 100) / 100
			},
			// value改变后,计算选中的selInd
			getSelInd() {
				const { endNum, startNum } = this;
				let value = this.value,
				isChange = false,
				noHave = false;
				// 不能超过最小最大值
				if (value > endNum) {
					value = endNum;
					isChange = true;
				} else if (value < startNum) {
					value = startNum;
					isChange = true;
				}
				let defaultInd = -1;
				const itemValue = this.list.map((item,index)=>{
					return (item===value)? index :0
				}).filter(item=>item>0);
				// for (let d = 0; d <= this.d_len; d++) {
				// 	const item = this.getIntNum(startNum + d*this.step);
				// 	if (item === value) {
				// 		defaultInd = d;
				// 		break;
				// 	}
				// }
				if(itemValue && itemValue.length>0){
					defaultInd = itemValue[0]
				}else {
					defaultInd = 0;
					noHave = true;
				}
				// 没有匹配到的情况 以及 超过了最大最小值 需要通知父组件修改value
				(isChange || noHave) && this.setEmitFunc();
				if (defaultInd === this.selInd) return;
				this.selInd = defaultInd;
				this.selvalue = this.getIntNum(this.selInd*this.step+this.startNum)
				this.slideTo(defaultInd);
			},
			// 父组件通知事件
			setEmitFunc() {
				const { selvalue } = this;
				this.$emit('input', selvalue);
				this.$listeners.change && this.$emit('change', selvalue);
			},
		},
		watch: {
			value: function(newVal, oldVal) {
				this.getSelInd();
			}
		},
	}
</script>

<style lang="scss">
	.slide-title{
		display: flex;
		flex-direction: row;
		align-items: flex-end;
		justify-content: center;
		margin-top: 10px;
		margin-bottom: 5px;
		&-left,&-right{
			color:$uni-text-color-grey;
			font-size: 14px;
		}
		&-num{
			color:$uni-text-color;
			text-align: center;
			font-size: 30px;
			margin: 0 10px;
			line-height: 100%;
			/* #ifdef H5 */
			margin-bottom: 5px !important;
			/* #endif */
		}
	}
	.slide-message{
		color:$uni-text-color-grey;
		font-size: $uni-font-size-sm;
		margin: 0 20px;
	}
	.slide-scroll {
		display: flex;
		flex-wrap: nowrap;
		flex-direction: row;
		height: 50px;
		::-webkit-scrollbar {width: 0 !important;height: 0 !important;-webkit-appearance: none;background: transparent;color: transparent;}
	}
	.slide-scroll-border{
		padding: 5px 30px;
		border: 1px solid #808080;
		border-radius: 40px;
		overflow: hidden;
	}

	.slide-list {
		flex-shrink: 0;
		position: relative;
		border-radius: 4px;
		transition: background-color .1s;
		width:4px;
		margin: 0 4px;
		height: 50px;
	}
	.slide-list.selected .slide-list-ba{
		background-color: red !important;
		height: 27px;
	}
	.slide-list.selected1 .slide-list-ba{
		background-color: red !important;
		height: 24px;
	}
	.slide-list.selected2 .slide-list-ba{
		background-color: red !important;
		height: 21px;
	}
	.slide-list.selected3 .slide-list-ba{
		background-color: red !important;
		height: 18px;
	}
	.slide-list-ba{
		position: absolute;
		bottom: 0;
		left: 0;
		right: 0;
		height: 15px;
		background-color: #D3D3D3;
	}

	.list-num {
		top: 0;
		position: absolute;
		transform: translateX(-50%);
	}

	.empty-none {
		flex-shrink: 0;
		height: 5px;
	}
</style>

使用示例

html 复制代码
<template>
	<view>
......
<zl-slider v-model="bloodSugar.value" :step="0.1" :startNum="0" :endNum="50" subTitle="血糖" subUnit="mmol/L" message="范围通常>3.9,空腹<7.0, 饭后两小时内<11.1 mmol/L"></zl-slider>
......
	</view>
</template>
<script>
	import zlSlider from '@/components/zl-slider/zl-slider.vue';
	export default {
		components:{zlSlider},
		data() {
			return {
				bloodSugar:{
					value:0
                }
            }
        }
    }
</script>