273 lines
6.8 KiB
Vue
273 lines
6.8 KiB
Vue
<template>
|
||
<div ref="pickerView" class="ch-picker-view" @touchstart="touchstart" @touchmove.prevent="touchmove"
|
||
@touchend="touchend">
|
||
|
||
<div class="ch-picker-view__scroll" :style="{
|
||
height: `${_itemHeight * visibleCount}px`,
|
||
'--background': `linear-gradient(to bottom, #fff 0%, transparent ${_itemHeight*(_getMaskTopCount+0.4)}px, transparent ${_itemHeight*(_getMaskTopCount+1-0.4)}px, #fff 100%)`,
|
||
}">
|
||
<div class="ch-picker-view__scroll-masktop" :style="{height:`${_itemHeight * _getMaskTopCount}px`}"></div>
|
||
<div class="ch-picker-view__scroll-maskbottom" :style="{height:`${_itemHeight*_getMaskBottomCount}px`}"></div>
|
||
|
||
<div class="ch-picker-view__scroll-box" :style="{
|
||
marginTop:`${_itemHeight * _getMaskTopCount}px`,
|
||
transform: `translateY(${translateY}px)`,
|
||
}">
|
||
<div class="ch-picker-view__scroll-box-item textHide-span" :style="[_itemStyle]" v-for="(item,index) in column"
|
||
:key="index" @tap="handleColumn(item,index)">
|
||
<template v-if="_fieldsMode==='default'">{{item}}</template>
|
||
<template v-else-if="_fieldsMode==='json'">{{item[_fields.label]}}</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
const screenWidth = uni.getSystemInfoSync().screenWidth
|
||
|
||
export default {
|
||
props: {
|
||
// 列数
|
||
colCount: {
|
||
type: Number,
|
||
default: 0
|
||
},
|
||
// 列下标
|
||
colIndex: {
|
||
type: Number,
|
||
default: 0
|
||
},
|
||
// 列数据
|
||
column: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
// 默认值
|
||
defaultIndex: {
|
||
type: Number,
|
||
default: 0
|
||
},
|
||
// value:数据值 label:数据名
|
||
fields: {
|
||
type: Object,
|
||
default: () => {
|
||
return {}
|
||
}
|
||
},
|
||
// 单个选项的高度,会覆盖_itemStyle内的height;
|
||
// 单位是px
|
||
itemHeight: {
|
||
type: [String, Number],
|
||
default: 44
|
||
},
|
||
// 单个选项样式
|
||
itemStyle: {
|
||
type: Object,
|
||
default: () => {}
|
||
},
|
||
// 每列中可见选项的数量,最大为5,最小为3
|
||
visibleCount: {
|
||
type: Number,
|
||
default: 5
|
||
},
|
||
},
|
||
data() {
|
||
return {
|
||
touchStartY: 0, // 触屏起始点y
|
||
deltaY: 0, // 手指位移,
|
||
translateY: 0, // box位移
|
||
isTouching: false, // 正在滑动
|
||
|
||
default: {
|
||
fieldsValue: 'value',
|
||
fieldsLabel: 'label',
|
||
},
|
||
}
|
||
},
|
||
computed: {
|
||
_fields() {
|
||
return {
|
||
value: this.fields.value || this.default.fieldsValue,
|
||
label: this.fields.label || this.default.fieldsLabel,
|
||
}
|
||
},
|
||
// 格式: 默认default 数组json
|
||
_fieldsMode() {
|
||
if (this.column && this.column.length && typeof this.column[0] === 'object') {
|
||
return 'json'
|
||
} else {
|
||
return 'default'
|
||
}
|
||
},
|
||
// 实际单个选项的高度 主要是统一变量类型
|
||
_itemHeight() {
|
||
return typeof this.itemHeight === 'string' ? parseFloat(this.itemHeight) : this.itemHeight
|
||
},
|
||
// 实际单个选项的样式
|
||
_itemStyle() {
|
||
return {
|
||
...this.itemStyle,
|
||
height: `${this._itemHeight}px`,
|
||
lineHeight: `${this._itemHeight}px`,
|
||
}
|
||
},
|
||
// 实际滚动区域内容的高度
|
||
_scrollInnerH() {
|
||
return this._itemHeight * (this.column.length - 1)
|
||
},
|
||
_getMaskTopCount() {
|
||
return Math.floor((this.visibleCount - 1) / 2)
|
||
},
|
||
_getMaskBottomCount() {
|
||
return Math.ceil((this.visibleCount - 1) / 2)
|
||
}
|
||
},
|
||
watch: {
|
||
defaultIndex: {
|
||
handler(nVal, oVal) {
|
||
if (this.isTouching) {
|
||
return
|
||
}
|
||
this.translateY = nVal * this._itemHeight * -1
|
||
},
|
||
immediate: true
|
||
},
|
||
translateY(nVal, oVal) {
|
||
let tIndex = Math.floor(Math.abs(nVal) / this._itemHeight)
|
||
tIndex = tIndex > this.column.length - 1 ? this.column.length - 1 : tIndex
|
||
this.$emit('change', tIndex)
|
||
},
|
||
},
|
||
mounted() {
|
||
// #ifdef H5
|
||
// 监听鼠标滚轮
|
||
this.$refs.pickerView.addEventListener('mousewheel', this.mouseHandle, false)
|
||
// #endif
|
||
},
|
||
methods: {
|
||
// 鼠标滚轮触发 - start
|
||
mouseHandle(e) {
|
||
let clientX = e.clientX
|
||
let rangeWidth1 = screenWidth / this.colCount * (this.colIndex)
|
||
let rangeWidth2 = screenWidth / this.colCount * (this.colIndex + 1)
|
||
if (clientX < rangeWidth1 || clientX >= rangeWidth2) {
|
||
return
|
||
}
|
||
|
||
if (e.target.className === 'ch-picker-mask') {
|
||
return
|
||
}
|
||
|
||
let tTranslateY = this.translateY
|
||
if (e.deltaY > 0) {
|
||
// console.log('向下');
|
||
tTranslateY -= this._itemHeight
|
||
tTranslateY = tTranslateY < this._scrollInnerH * -1 ? this._scrollInnerH * -1 : tTranslateY
|
||
} else {
|
||
// console.log('向上');
|
||
tTranslateY += this._itemHeight
|
||
tTranslateY = tTranslateY > 0 ? 0 : tTranslateY
|
||
}
|
||
this.translateY = tTranslateY
|
||
},
|
||
// 鼠标滚轮触发 - end
|
||
|
||
// 触摸触发 - start
|
||
touchstart(e) {
|
||
this.isTouching = true
|
||
|
||
let tTouche = e.touches[0]
|
||
// console.log('touchstart', tTouche.clientY);
|
||
this.touchStartY = tTouche.clientY
|
||
this.deltaY = 0
|
||
},
|
||
touchmove(e) {
|
||
this.isTouching = true
|
||
|
||
let tTouche = e.touches[0]
|
||
// console.log('touchmove', tTouche.clientY);
|
||
let preDeltaY = this.deltaY
|
||
this.deltaY = tTouche.clientY - this.touchStartY
|
||
let tDiff = this.deltaY - preDeltaY
|
||
this.translateY += tDiff
|
||
},
|
||
touchend(e) {
|
||
// console.log('touchend');
|
||
this.isTouching = false
|
||
|
||
let tTranslateY = 0
|
||
let divisor = this.translateY / this._itemHeight
|
||
|
||
divisor = this.deltaY > 0 ? Math.ceil(divisor) : Math.floor(divisor)
|
||
tTranslateY = divisor * this._itemHeight
|
||
tTranslateY = tTranslateY > 0 ? 0 : tTranslateY
|
||
tTranslateY = tTranslateY < this._scrollInnerH * -1 ? this._scrollInnerH * -1 : tTranslateY
|
||
// console.log(tTranslateY);
|
||
this.translateY = tTranslateY
|
||
},
|
||
// 触摸触发 - end
|
||
handleColumn(item, index) {
|
||
this.translateY += (this.defaultIndex - index) * this._itemHeight
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
<style lang="scss" scoped>
|
||
.ch-picker-view {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.ch-picker-view__scroll {
|
||
position: relative;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
z-index: 1;
|
||
|
||
&::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
background: var(--background);
|
||
pointer-events: none;
|
||
z-index: 99;
|
||
}
|
||
|
||
.ch-picker-view__scroll-masktop,
|
||
.ch-picker-view__scroll-maskbottom {
|
||
position: absolute;
|
||
width: 100%;
|
||
z-index: 1;
|
||
border: 0 solid transparent;
|
||
border-image: linear-gradient(to right, transparent, #ddd, transparent) 1;
|
||
pointer-events: none;
|
||
z-index: 101;
|
||
}
|
||
|
||
.ch-picker-view__scroll-masktop {
|
||
top: 0;
|
||
border-bottom-width: 1px;
|
||
}
|
||
|
||
.ch-picker-view__scroll-maskbottom {
|
||
bottom: 0;
|
||
border-top-width: 1px;
|
||
}
|
||
|
||
.ch-picker-view__scroll-box {
|
||
transition: all 200ms linear;
|
||
|
||
.ch-picker-view__scroll-box-item {
|
||
padding: 0 10rpx;
|
||
text-align: center;
|
||
}
|
||
}
|
||
}
|
||
|
||
.textHide-span {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
word-break: break-all;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
</style> |