<template> <view class="fulled tm-slider " :class="[step > 0 ? 'pb-36' : 'pb-24']" :style="{ height: vertical ? heightpx + 'px' : 'auto', width: vertical ? '5px' : '100%'}"> <view class=" tm-slider-id fulled " :class="[vertical ? 'vertical' : 'flex-between']"> <!-- 左边label --> <view v-if="showLeft" class="label_slider left flex-col text-size-xs text-grey-darken-1" :class="[vertical ? '' : 'flex-center']"> <slot name="left" :data="{ value: value, color: color_tmeme, icon: leftIcon,max:max }"> <tm-icons v-if="leftIcon && !vertical" size="28" :name="leftIcon" :color="color_tmeme"></tm-icons> <view class="text-size-xs text-grey-darken-1 ">{{ value }}</view> </slot> </view> <!-- 条子内容 --> <view class="slider_id " :style="{ width: vertical ? 'auto' : sliderWidth + 'px', height: vertical ? sliderWidth + 'px' : '5px' }"> <view class="slider_id_bg round-10" :class="[bgColor,black_tmeme?'bk':'']"></view> <view :style="{ width: vertical ? '100%' : active_width + '%', height: vertical ? active_width + '%' : '100%' }" class="slider_id_active round-10 " :class="[color_tmeme,black_tmeme?'bk':'']"></view> <view :style="{ left: vertical ? 0 : barLeft + 'px', top: vertical ? barLeft + 'px' : 0 }" @touchcancel="barEnd" @touchstart="barStart" @touchend="barEnd" @touchmove.stop.prevent="barMove" @mouseleave="barEnd" @mousedown="barStart" @mouseup="barEnd" @mousemove.stop.prevent="barMove" class="slider_bar border-white-a-2 rounded" :class="[color_tmeme,black_tmeme?'bk':'', ` shadow-${color_tmeme}-10`]"> <view v-if="showTopTips||showTip" class="slider_bar_showbg border-white-a-1" :class="[color_tmeme,black_tmeme?'bk':'']"></view> <view v-if="showTopTips||showTip" class="slider_bar_num text-size-xs flex-center" :class="[color_tmeme,black_tmeme?'bk':'']"> <slot name="tips" :data="value">{{ value }}</slot> </view> </view> <!-- 步长刻度尺 --> <view class="kdc_wk "> <view v-for="(item, index) in kdcNum" :key="index" :style="{ left: vertical ? 0 : item.left, top: vertical ? item.left : 0 }" :class="[color_tmeme,black_tmeme?'bk':'']" class="kdc_wk_item rounded border-white-a-1 "> <view class="kdc_wk_item_label pl-0" :class="[vertical ? 'pl-24' : 'pa-24']"> <text class="text-size-xs text-grey-darken-1 ">{{ item.kedu }}</text> </view> </view> </view> </view> <!-- 右边label --> <view v-if="showRight" class="label_slider right flex-col text-size-xs text-grey-darken-1" :class="[vertical ? 'flex-end' : 'flex-center']"> <slot name="right" :data="{ value: value, color: color_tmeme, icon: rightIcon,max:max }"> <tm-icons v-if="rightIcon && !vertical" size="28" :name="rightIcon" :color="color_tmeme"></tm-icons> <view class="text-size-xs text-grey-darken-1 ">{{ max }}</view> </slot> </view> </view> </view> </template> <script> /** * 滑块 * @property {Number} value = [0] 赋值,如果需要同步使用value.sync推荐使用v-model绑定。 * @property {Boolean} vertical = [true|false] 默认:false,是否启用竖向模式。 * @property {Number} height = [200] 默认:200,竖向模式时才有作用。 * @property {Number} width = [] 默认:0,横向时起作用,如果为0自动获取外层宽度。 * @property {Boolean} show-left = [true|false] 默认:false,显示左边数据。 * @property {Boolean} show-right = [true|false] 默认:false,显示右边数据。 * @property {Number} max = [] 默认:100,显示的最大刻度。 * @property {Number} value-diog = [] 默认:0,取值小数点后几位 * @property {Boolean} disabled = [true|false] 默认:false, 是否禁用。 * @property {Boolean} showTip = [true|false] 默认:false, 始终显示进度标签。 * @property {Number} step = [10|20] 默认:0, 步长,设置请尽量大于20.太小滑动容易过小出问题。 * @property {String} color = [primary] 默认:primary, 主题颜色名称。 * @property {String} bg-color = [grey-lighten-2] 默认:grey-lighten-2, 底部不活动的背景色,颜色名称。 * @property {String} left-icon = [] 默认:icon-minus, 左边图标 * @property {String} right-icon = [] 默认:icon-plus, 右边图标 * @property {String} name = [] 默认:'',提交表单时的的字段名称标识 * @property {Function} change 同v-model和value相等的参数,变动时触发。 * @example <tm-slider v-model="checked" ></tm-slider> * */ import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue" export default { name:'tm-slider', components:{tmIcons}, model:{ prop:'value', event:'input' }, props: { //提交表单时的的字段名称 name:{ type:String, default:'' }, vertical: Boolean, //是否启用竖向模式。需要和height配合使用。 // 单位upx. height: { type: Number, default: 200 }, width: { type: Number, default: 0 }, showLeft: Boolean, //显示左边 showRight: Boolean, //显示右边 // 最大刻度。 max: { type: Number, default: 100 }, // 默认的数据。不能大于max.,使用.sync修饰。双向绑定数据。 value: { type: Number, default: 0 }, // 取值小数点后几位。默认为0 valueDiog: { type: Number, default: 0 }, // 是否禁用。 disabled: Boolean, //步长。默认为0,设置请尽量大于0.太小滑动容易过小出问题。 step: { type: Number, default: 0 }, // 主题颜色名称。 color: { type: String, default: 'primary' }, // 底部不活动的背景色,颜色名称。。 bgColor: { type: String, default: 'grey-lighten-2' }, // 左边图标名称。 leftIcon: { type: String | Boolean, default: 'icon-minus' }, // 右边图标名称。 rightIcon: { type: String | Boolean, default: 'icon-plus' }, // 始终显示进度提示窗 showTip: { type: Boolean, default: false }, // 跟随主题色的改变而改变。 fllowTheme:{ type:Boolean|String, default:true }, black: { type: Boolean, default: null }, }, watch: { value: function(val) { let rdl = this.sliderWidth * (Math.abs(val) / Math.abs(this.max)); this.barLeft = rdl >= this.sliderWidth || rdl < 0 ? this.sliderWidth : rdl; }, barLeft: function() { let value = (this.barLeft / this.sliderWidth) * this.max; if (!isNaN(value)) { if (this.valueDiog > 0) { let s = value.toString(); let st = s.split('.'); let ruslt = 0; if (st.length > 1) { ruslt = parseFloat(st[0] + '.' + st[1].substring(0, this.valueDiog)); } else { ruslt = parseFloat(s); } this.$emit('update:value', ruslt); this.$emit('input', ruslt); this.$emit('change', ruslt); } else { this.$emit('update:value', parseInt(value)); this.$emit('input', parseInt(value)); this.$emit('change', parseInt(value)); } } } }, computed: { black_tmeme: function() { if (this.black !== null) return this.black; return this.$tm.vx.state().tmVuetify.black; }, // 计算刻度尺的数量 kdcNum: function() { if (Math.abs(this.step) <= 0) return []; let jlv = Math.abs(this.step) / Math.abs(this.max); let left_width = jlv * (this.sliderWidth - 0); let kd_num = parseInt(this.sliderWidth / left_width); let ar = []; for (let i = 0; i <= kd_num; i++) { ar.push({ index: i, //顺序 left: i * left_width + 'px', //距离左边距离 kedu: this.step * i //当前刻度数量。 }); } return ar; }, //比例 active_width: function() { return (this.barLeft / this.sliderWidth) * 100; }, heightpx: function() { return uni.upx2px(this.height); }, color_tmeme:function(){ if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){ return this.$tm.vx.state().tmVuetify.color; } return this.color; }, }, data() { return { sliderWidth: 0, x: 0, startMove: false, barLeft: 0, isError: false, showTopTips: false }; }, async mounted() { this.$nextTick(async function() { await this.getwidth(); if (Math.abs(this.value) > Math.abs(this.max)) { this.isError = true; return; } let rdl = this.sliderWidth * (Math.abs(this.value) / Math.abs(this.max)); this.barLeft = rdl >= this.sliderWidth || rdl < 0 ? this.sliderWidth : rdl; }); }, async updated() { this.$nextTick(async function() { await this.getwidth(); if (Math.abs(this.value) > Math.abs(this.max)) { this.isError = true; return; } let rdl = this.sliderWidth * (Math.abs(this.value) / Math.abs(this.max)); this.barLeft = rdl >= this.sliderWidth || rdl < 0 ? this.sliderWidth : rdl; }); }, methods: { barStart(e) { if (this.disabled || this.isError) return; if(e.type.indexOf('mouse')>-1&&e.changedTouches.length==0){ this.x = (this.vertical ? e.pageY : e.pageX) - this.barLeft; this.startMove = true; this.showTopTips = true; }else{ if (e.changedTouches.length > 0) { this.x = (this.vertical ? e.changedTouches[0].pageY : e.changedTouches[0].pageX) - this.barLeft; this.startMove = true; this.showTopTips = true; } } this.$nextTick(function(){ this.$emit('start',this.value) }) }, barEnd(e) { if (this.disabled || this.isError) return; this.startMove = false; this.showTopTips = false; this.$nextTick(function(){ this.$emit('end',this.value) }) }, barMove(e) { if (this.disabled || this.isError) return; if (!this.startMove) return; let left = 0; if(e.type.indexOf('mouse')>-1&&e.changedTouches.length==0){ left = (this.vertical ? e.pageY : e.pageX) - this.x; }else{ left = (this.vertical ? e.changedTouches[0].pageY : e.changedTouches[0].pageX) - this.x; } if (left <= 0) { this.barLeft = 0; return; } if (left >= this.sliderWidth) { this.barLeft = this.sliderWidth; return; } let nowStep = parseInt(Math.abs(left - this.barLeft)); let bdi = parseInt((this.step / this.max) * this.sliderWidth); if (nowStep >= bdi) { // 每一小段的值。 if(this.step!==0){ let kedud = this.sliderWidth / (this.max / this.step); this.barLeft = Math.round(left / kedud) * kedud; }else{ this.barLeft = left; } } }, async getwidth() { let res = await this.$Querey('.tm-slider-id', this,0).catch(e=>{}); res[0].width = res[0].width||uni.upx2px(this.width); res[0].height = res[0].height||uni.upx2px(this.height); if (this.showLeft === false && this.showRight === false) { this.sliderWidth = this.vertical ? res[0].height : res[0].width; return; } if (this.showLeft !== false && this.showRight !== false) { this.sliderWidth = (this.vertical ? res[0].height : res[0].width) - uni.upx2px(this.vertical ? 50 : 100) * 2; return; } if (this.showLeft === true || this.showRight === true) { this.sliderWidth = (this.vertical ? res[0].height : res[0].width) - uni.upx2px(this.vertical ? 50 : 100); } } } }; </script> <style lang="scss" scoped> .label_slider { flex-shrink: 0; &.left, &.right { width: 100upx; } } .tm-slider-id { width: 100%; .slider_id { position: relative; height: 4px; .slider_id_bg { width: 100%; height: 100%; } .slider_id_active { width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: 5; } .slider_bar { position: absolute; width: 40upx; height: 40upx; left: 0; top: 0; margin-top: -22upx; z-index: 10; .slider_bar_showbg { position: absolute; width: 50upx; height: 50upx; border-radius: 30upx; border-bottom-left-radius: 5upx; transform: rotate(-45deg); bottom: 60upx; left: -9upx; animation: roteScaleTop_BG 0.3s ease-in-out; } .slider_bar_num { position: absolute; width: 50upx; height: 50upx; border-radius: 50upx; left: -8upx; bottom: 60upx; line-height: 50upx; text-align: center; background: transparent !important; animation: roteScaleTop 0.3s ease-in-out; } } .kdc_wk { width: 100%; height: 100%; position: absolute; z-index: 6; left: 0; top: -2upx; .kdc_wk_item { width: 10upx; height: 10upx; position: absolute; z-index: 7; text-align: center; .kdc_wk_item_label { margin-left: -100%; } } } } &.vertical { height: 100%; .label_slider { flex-shrink: 0; &.left, &.right { width: auto; height: 50upx; } } .slider_bar { margin-left: -18upx; } .kdc_wk { top: 0upx; .kdc_wk_item { .kdc_wk_item_label { margin-left: 100%; margin-top: -15upx; } } } } } @keyframes roteScaleTop_BG{ 0%{ transform: scale(0.9) translateY(20rpx) rotate(-45deg); opacity: 0; } 100%{ transform: scale(1) translateY(0rpx) rotate(-45deg); opacity: 1; } } @keyframes roteScaleTop{ 0%{ transform: scale(0.9) translateY(20rpx); opacity: 0; } 100%{ transform: scale(1) translateY(0rpx); opacity: 1; } } </style>