You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

481 lines
13 KiB
Vue

1 year ago
<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>