<template> <view> <view class="cu-progress-main" :style="{'border-radius':bgBR,'width': progressMainW,'height':progressMainH,'background-color': backgroundColor}"> <view class="cu-progress" :style="{'left':areaLeft, 'flex-direction':flexDirection,'width':areaW,height:areaH}"> <view class="cu-progress-bar" :style="{'bottom':pgBarBottom,'background':pgBarBg,'margin-left':pgBarML, 'margin-bottom':pgBarMB,'width': pgBarW,'height':pgBarH,'border-radius':pgBarBR,'background-color':noActiveColor}"></view> <movable-area class="cu-area" :style="{'flex-direction':flexDirection,'width':areaW,height:areaH}" @touchcancel="touchCancel" @touchstart="areaTouchStart" @touchmove="areaTouchMove" @touchend="touchEnd"> <movable-view class="cu-handle" disabled="disabled" :animation="false" :style="{ 'top':handleT, 'width':handleS,'height':handleS,'border-radius': handleBR,'background-color':handleColor}" @change="change" :damping="damping" :x="handleX" :y="handleY" :direction="direction == 'vertical' ? 'vertical' : 'horizontal'"> <text :class="handleIcon" :style="{'backgroundColor':iconColor,'font-size':iconS,'border-radius':iconBR}"></text> </movable-view> </movable-area> </view> <text v-if="getShowInfoStatus2view" class="cu-showInfo" :style="{'right':infoRt, 'left':infoLt, 'color':infoColor,'font-size':infoS,width:infoW}">{{showValue}}{{infoEndText}}</text> </view> </view> </template> <script> export default { data() { return { scale: 1, percent: 0, // progress进度 // pgbw: '10px', // progress-bar width showValue: 0, //显示进度 showVL: 0, // showValue 最大长度 handleMoveStatus: false, handleX: 0, // 拖柄位置 handleY: 0, progressBarInfo: { left: 0, bottom: 0, width: 0, height: 0 }, area: { left: 0, bottom: 0, width: 0, height: 0, top: 0 }, handle: { height: 0 } }; }, beforeMount: function() { const res = uni.getSystemInfoSync() this.scale = 750 / res.windowWidth; // 0 为 false if (this.maxValue - this.minValue == 0) { console.error("min不能等于max:" + this.minValue + "->" + this.maxValue) return } else{ this.showValue = this.valueFormat(this.value) } this.percent = Math.abs(this.showValue - this.minValue) / Math.abs(this.maxValue - this.minValue) * 100 let minl = this.textLength(this.minValue + this.infoEndText) let maxl = this.textLength(this.maxValue + this.infoEndText) this.showVL = maxl > minl ? maxl : minl }, mounted: function() { this.$nextTick(function() { const query = uni.createSelectorQuery().in(this) query.select(".cu-progress-bar").boundingClientRect(data => { this.progressBarInfo.width = data.width this.progressBarInfo.left = data.left this.progressBarInfo.bottom = data.bottom this.progressBarInfo.height = data.height if (this.direction == 'vertical'){ this.handleY = this.progressBarInfo.height * (100 - this.percent) / 100 } else{ this.handleX = this.progressBarInfo.width * this.percent / 100 } }).exec() query.select(".cu-area").boundingClientRect(data => { this.area.width = data.width this.area.left = data.left this.area.height = data.height this.area.bottom = data.bottom this.area.top = data.top }).exec() query.select(".cu-handle").boundingClientRect(data => { this.handle.height = data.height }).exec() }) }, props: { infoAlign: { default: 'right', type: String }, step: { default: 1, type: [String, Number] }, direction: { default: 'horizontal', //vertical 方向 type: String }, disabled: { default: false, type: [String, Boolean] }, bgBorderRadius: { default: 0, type: [Number, String] }, iconBorderRadius: { default: '8px', type: [Number, String] }, iconColor: { default: 'inherit', type: String }, iconSize: { default: '8px', type: [Number, String] }, handleIcon: { default: '', type: String }, backgroundColor: { default: 'inherit', type: String }, max: { default: 100, type: [String, Number] }, min: { default: 0, type: [String, Number] }, value: { default: null, // 设置进度条进度 type: [String, Number] }, activeColor: { default: '#444444', // 已经过区域颜色 type: String }, noActiveColor: { default: '#888888', // 为经过区域颜色 type: String }, strokeWidth: { default: '3', // 行程宽度 type: [String, Number] }, damping: { default: 100, // 阻尼系数,越大移动越快 type: [String, Number] }, handleColor: { default: '#000000', // 拖柄颜色 type: String }, handleSize: { default: '8px', // 拖柄尺寸 type: [String, Number] }, handleBorderRadius: { default: '8px', // 拖柄圆角半径 type: [String, Number] }, showInfo: { default: false, // 是否在进度条右侧显示百分比 type: [Boolean, String] }, infoSize: { default: '16px', // 百分比显示字体尺寸 type: [String, Number] }, infoColor: { default: '#000000', // 百分比显示字体颜色 type: String }, infoEndText: { default: '', type: String }, borderRadius: { default: 0, // 进度条圆角半径 type: [String, Number] }, width: { default: '200px', // 进度条总宽,如果显示百分比,则包括百分比在内的宽度 type: [String, Number] }, }, computed: { getShowInfoStatus2view(){ if ((this.showInfo == true || this.showInfo == 'true') && this.direction != "vertical"){ return true } else{ return false } }, showVLC (){ let minl = this.textLength(this.minValue.toFixed(0) + this.infoEndText) let maxl = this.textLength(this.maxValue.toFixed(0) + this.infoEndText) let L = maxl > minl ? maxl : minl return L }, flexDirection (){ if (this.direction == 'vertical'){ return 'column' } else{ return 'row' } }, pgBarBottom (){ // progress- if (this.direction == 'vertical'){ return '0px' } else{ return 'unset' } }, showInfoStatus (){ if (this.getShowInfoStatus() && this.direction != "vertical"){ return true } else{ return false } }, pgBarBg(){ let bg1 if (this.direction == 'vertical'){ bg1 = "linear-gradient(to top," + this.activeColor + ' ' + this.percent + "%," + this.noActiveColor + ' ' + this.percent + "%)" } else{ bg1 = "linear-gradient(to right," + this.activeColor + ' ' + this.percent + "%," + this.noActiveColor + ' ' + this.percent + "%)" // qq let bg2 = "-webkit-linear-gradient(left," + this.activeColor + ' ' + this.percent + "%," + this.noActiveColor + ' ' + this.percent + "%)" let bg3 = "-o-linear-gradient(right," + this.activeColor + ' ' + this.percent + "%," + this.noActiveColor + ' ' + this.percent + "%)" let bg4 = "-moz-linear-gradient(right," + this.activeColor + ' ' + this.percent + "%," + this.noActiveColor + ' ' + this.percent + "%)" } return bg1 }, pgBarBR(){ return this.sizeDeal(this.borderRadius)[2] }, bgBR() { return this.sizeDeal(this.bgBorderRadius)[2] }, iconBR() { return this.sizeDeal(this.iconBorderRadius)[2] }, iconS() { return this.sizeDeal(this.iconSize)[2] }, infoW() { const s = this.sizeDeal(this.infoSize) const size = s[0] * this.showVL + s[1] return size }, infoS() { return this.sizeDeal(this.infoSize)[2] }, infoRt(){ // showinfo right if (this.infoAlign == 'left' && this.direction != 'vertical'){ return 'unset' } else{ return 0 } }, infoLt(){ if (this.infoAlign == 'left' && this.direction != 'vertical'){ return 0 } else if (this.infoAlign == 'center' && this.direction != 'vertical'){ let aw = this.area.width / 2 const s = this.sizeDeal(this.infoSize) const size = aw - (s[0] * this.showVL) / 2 + 'px' return size } else{ return 'unset' } }, areaW: { get (){ if (this.direction == 'vertical'){ return this.maxHeight(true)[2] } else{ const s = this.sizeDeal(this.infoSize) const h = this.maxHeight() let w if (this.getShowInfoStatus()) { w = 'calc(' + this.width + ' - ' + s[0] * this.showVL + s[1] + ')' } else { w = this.width } return w } }, set (w){ return w } }, areaH: { get(){ if (this.direction == 'vertical'){ return this.width } else{ return this.maxHeight()[2] } }, set (v){ return v } }, areaLeft() { if (this.infoAlign == 'left' && this.direction != 'vertical'){ const s = this.sizeDeal(this.infoSize) const size = s[0] * this.showVL + s[1] return size } else{ return 0 } }, pgBarW() { // width if (this.direction == 'vertical'){ return this.sizeDeal(this.strokeWidth)[2] } else{ const s = this.sizeDeal(this.infoSize) const w = this.sizeDeal(this.width) const s2 = this.sizeDeal(this.handleSize) let w2 if (this.getShowInfoStatus()) { w2 = 'calc(' + w[2] + ' - ' + s[0] * this.showVL + s[1] + ' - ' + s2[2] + ')' } else { w2 = 'calc(' + w[2] + ' - ' + s2[2] + ')' } return w2 } }, pgBarH() { // height if (this.direction == 'vertical'){ const w = this.sizeDeal(this.width) const s2 = this.sizeDeal(this.handleSize) let w2 = 'calc(' + w[2] + ' - ' + s2[2] + ')' return w2 } else{ return this.sizeDeal(this.strokeWidth)[2] } }, pgBarML() { // margin-left if (this.direction == 'vertical'){ const s = this.sizeDeal(this.progressBarInfo.width) const ah = Number(this.area.width) / 2 const t = ah - s[0] / 2 + 'px' return t } else{ const s2 = this.sizeDeal(this.handleSize) return s2[0] / 2 + s2[1] } }, pgBarMB() { // margin-bottom if (this.direction == 'vertical'){ const s2 = this.sizeDeal(this.handleSize) return s2[0] / 2 + s2[1] } else{ return 0 } }, handleS() { const s = this.sizeDeal(this.handleSize) return s[2] }, handleL (){ if (this.direction == 'vertical') { const s = this.sizeDeal(this.handleSize) const ah = Number(this.area.width) / 2 const t = ah - s[0] / 2 + 'px' return t } else{ return 'unset' } }, handleT() { if (this.area.height && this.direction != 'vertical') { const s = this.sizeDeal(this.handleSize) const ah = Number(this.area.height) / 2 const t = ah - s[0] / 2 + 'px' return t } else{ return 'unset' } }, handleBR() { const r = this.sizeDeal(this.handleBorderRadius) return r[2] }, progressMainW() { let w if (this.direction == 'vertical'){ w = this.maxHeight(true)[2] } else{ w = this.width } return w }, progressMainH() { let h if (this.direction == 'vertical'){ h = this.width } else{ h = this.maxHeight()[2] } return h }, maxValue() { let max = Number.isNaN(parseFloat(this.max)) ? 100 : parseFloat(this.max) return this.valueFormat(max) }, minValue() { let min = Number.isNaN(parseFloat(this.min)) ? 0 : parseFloat(this.min) let si = this.getStepInfo() return Number(min.toFixed(si[1])) // return this.valueFormat(min) }, }, watch: { showVLC (v){ this.showVL = v if (this.direction != 'vertical'){ const s = this.sizeDeal(this.infoSize) const h = this.maxHeight() let w if (this.getShowInfoStatus()) { w = 'calc(' + this.width + ' - ' + s[0] * this.showVL + s[1] + ')' } else { w = this.width } this.areaW = w this.areaH = h[2] } this.clientInit() }, value(v) { // 当处于拖动状态时,禁止进度条外部触发变化 if (!this.handleMoveStatus) { this.showValue = this.valueFormat(v) this.percent = (this.showValue - this.minValue) / Math.abs(this.maxValue - this.minValue) * 100 if (this.direction == 'vertical'){ this.handleY = this.progressBarInfo.height * this.percent / 100 } else{ this.handleX = this.progressBarInfo.width * this.percent / 100 } } }, }, methods: { valueFormat (v){ // set step v = Number(v - this.minValue).toFixed(7) let stepInfo = this.getStepInfo() let valueE = v * 10 ** stepInfo[1] let remainder = valueE % (stepInfo[0] * 10 ** stepInfo[1]) let remainderInt = Math.floor(remainder) let value = (Math.floor(valueE) - remainderInt) / (10 ** stepInfo[1]) return Number((value + this.minValue).toFixed(6)) }, getStepInfo() { // return step, decimal位数 let step = Number(this.step) if (step <= 0 || !step){ return [1, 0] } else{ let steps = step.toString().split('.') if (steps.length == 1){ return [step,0] } else { return [step,steps[1].length] } } }, clientInit() { this.$nextTick(function() { const query = uni.createSelectorQuery().in(this) query.select(".cu-progress-bar").boundingClientRect(data => { this.progressBarInfo.width = data.width this.progressBarInfo.left = data.left this.progressBarInfo.bottom = data.bottom this.progressBarInfo.height = data.height if (this.direction == 'vertical'){ this.handleY = this.progressBarInfo.height * this.percent / 100 } else{ this.handleX = this.progressBarInfo.width * this.percent / 100 } }).exec() query.select(".cu-area").boundingClientRect(data => { this.area.width = data.width this.area.left = data.left this.area.height = data.height this.area.bottom = data.bottom this.area.top = data.top }).exec() if (this.maxValue - this.minValue == 0) { console.error("min不能等于max:" + this.minValue + "->" + this.maxValue) } else{ let stepInfo = this.getStepInfo() let v = this.percent * (this.maxValue - this.minValue) / 100 let valueE = v * 10 ** stepInfo[1] let remainder = valueE % (stepInfo[0] * 10 ** stepInfo[1]) let remainderInt = Math.floor(remainder) let sv = (Math.floor(valueE) - remainderInt) / (10 ** stepInfo[1]) this.showValue = sv } }) }, getShowInfoStatus(){ if ((this.showInfo == true || this.showInfo == 'true') && this.direction != "vertical" && this.infoAlign !== 'center'){ return true } else{ return false } }, textLength(t) { t = t.toString() let int = t.split('.')[0] let subt = t.match(/[^\x00-\xff]/g) let subl = 0 if (subt) { subl = subt.length } let l = (int.length - subl + this.getStepInfo()[1]) / 3 * 2 + subl + 0.2 return Number(l.toFixed(2)) }, maxHeight(vertical) { let h = [] if (!vertical){ // direction 为 vertical 时不显示info let subt = this.infoEndText.match(/[^\x00-\xff]/g) if (subt){ h.push(this.sizeDeal(this.infoSize)[0] * 1.1) } else{ h.push(this.sizeDeal(this.infoSize)[0]) } } h.push(this.sizeDeal(this.strokeWidth)[0]) h.push(this.sizeDeal(this.handleSize)[0]) h.sort(function(a, b) { return b - a }) // 降序 return [h[0], 'px', h[0] + 'px'] }, sizeDeal(size) { // 分离字体大小和单位,rpx 转 px let s = Number.isNaN(parseFloat(size)) ? 0 : parseFloat(size) let u = size.toString().replace(/[0-9]/g, '') if (u == 'rpx') { s /= this.scale u = 'px' } else if (u == '') { u = 'px' }else if (u == 'vw') { u = 'px' s = s / 100 * 750 / this.scale } return [s, u, s + u] }, change (){ this.$emit('change', { type: 'change', value: this.showValue, }) }, handleMove(x_) { let x = x_ x = x >= 0 ? x : 0 let s = this.sizeDeal(this.handleSize) let cp let sv if (this.direction == 'vertical'){ // #ifdef H5 x = x - s[0] / 2 - this.area.bottom // ? cp = x / (this.area.height - s[0] - 1) // #endif // #ifndef H5 x = x - this.area.top - s[0] / 2 cp = x / (this.area.height - s[0] - 1) // #endif cp = cp <= 1 ? cp : 1 cp = cp > 0 ? cp : 0 let value = Number(((1 - cp) * (this.maxValue - this.minValue))) + this.minValue this.percent = (1 - cp) * 100 this.showValue = this.valueFormat(value) this.handleY = x } else{ cp = x / (this.area.width - s[0] - 1) // 多减 1 避免达不到maxValue cp = cp <= 1 ? cp : 1 cp = cp > 0 ? cp : 0 let value = Number((cp * (this.maxValue - this.minValue))) + this.minValue this.percent = cp * 100 this.showValue = this.valueFormat(value) this.handleX = x } this.$emit('dragging', { type: 'dragging', value: this.showValue }) }, touchEnd() { if (!this.disabled){ this.handleMoveStatus = false this.$emit('dragged', { type: 'dragged', value: this.showValue }) } }, areaTouchStart(e) { if (!this.disabled){ this.handleMoveStatus = true let s = this.sizeDeal(this.handleSize) let tapX if (this.direction == 'vertical'){ // #ifdef H5 tapX = this.area.height + e.changedTouches[0].pageY // #endif // #ifndef H5 tapX = e.changedTouches[0].pageY // #endif } else{ tapX = e.changedTouches[0].pageX - this.area.left - s[0] / 2 // 拖柄中心与鼠标箭头一致 } tapX = tapX >= 0 ? tapX : 0 this.handleMove(tapX) this.$emit('dragstart', { type: 'dragstart', value: this.showValue }) } }, areaTouchMove(e) { if (!this.disabled){ let s = this.sizeDeal(this.handleSize) let tapX if (this.direction == 'vertical'){ // #ifdef H5 tapX = this.area.height + e.changedTouches[0].pageY // #endif // #ifndef H5 tapX = e.changedTouches[0].pageY // #endif } else{ tapX = e.changedTouches[0].pageX - this.area.left - s[0] / 2 } tapX = tapX >= 0 ? tapX : 0 this.handleMove(tapX) } }, touchCancel() { if (!this.disabled){ this.touchEnd() this.$emit('dragcancel', { type: 'dragcancel', value: this.showValue }) } }, } } </script> <style scoped> @import 'cu-progress.css' </style>