完美主持移动端,rem布局(我用了stylus,你可以改成less或者sass),font-size基础值/75,默认开始时间为当前
效果时间
效果图
贴代码
<template>
<div class="datepicker" @touchmove.prevent>
<transition name="fade" mode="out-in">
<div class="pickerBoxBg" v-show="show" @click="show = false" @touchmove="_stopDef" @mousewheel="_stopDef">
<div class="pickerBox" v-show="show" @mousewheel="_stopDef">
<div class="pickerBoxWrapper">
<div class="pickerBoxTitle">
<h2>{{title}}</h2>
<h3>请选择日期</h3>
<ul>
<li>年</li>
<li>月</li>
<li>日</li>
<li>时</li>
<li>分</li>
</ul>
</div>
<div class="pickerBoxContent">
<div class="pickerBoxContentList">
<ul
:class="{'first_dragging': yearState.dragging}"
@touchstart="_onTouchStart('year', $event)"
@mousedown="_onTouchStart('year', $event)"
:style="{'transform' : 'translate3d(0,' + yearState.translateY + 'px, 0)'}">
<li></li>
<li></li>
<li></li>
<li
ref="list-box"
v-for="(item, index)
in yearState.data"
:key="index"
:class="{
'current': item === yearState.selectedId,
'node1': Math.abs(index - yearState.index) == 1,
'node2': Math.abs(index - yearState.index) == 2,
'node3': Math.abs(index - yearState.index) >= 3
}">
{{item}}
</li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
<div class="pickerBoxContentList">
<ul
:class="{'second_dragging': monthState.dragging}"
@touchstart="_onTouchStart('month', $event)"
@mousedown="_onTouchStart('month', $event)"
:style="{'transform' : 'translate3d(0,' + monthState.translateY + 'px, 0)'}">
<li></li>
<li></li>
<li></li>
<li
v-for="(item, index) in monthState.data"
:key="index"
:class="{
'current': item === monthState.selectedId,
'node1': Math.abs(index - monthState.index) == 1,
'node2': Math.abs(index - monthState.index) == 2,
'node3': Math.abs(index - monthState.index) >= 3
}">
{{item}}
</li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
<div class="pickerBoxContentList">
<ul
ref:area-list
:class="{'third_dragging': dayState.dragging}"
@touchstart="_onTouchStart('day', $event)"
@mousedown="_onTouchStart('day', $event)"
:style="{'transform' : 'translate3d(0,' + dayState.translateY + 'px, 0)'}">
<li></li>
<li></li>
<li></li>
<li
v-for="(item, index) in dayState.data"
:key="index"
:class="{
'current': item === dayState.selectedId,
'node1': Math.abs(index - dayState.index) == 1,
'node2': Math.abs(index - dayState.index) == 2,
'node3': Math.abs(index - dayState.index) >= 3
}">
{{item}}
</li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
<div class="pickerBoxContentList">
<ul :class="{'third_dragging': hourState.dragging}"
@touchstart="_onTouchStart('hour', $event)"
@mousedown="_onTouchStart('hour', $event)"
:style="{'transform' : 'translate3d(0,' + hourState.translateY + 'px, 0)'}">
<li></li>
<li></li>
<li></li>
<li
v-for="(item, index) in hourState.data"
:key="index"
:class="{
'current': item == hourState.selectedId,
'node1': index - hourState.index == 1 || index == hourState.selectedId - 1,
'node2': index - hourState.index == 2 || index == hourState.selectedId - 2,
'node3': index - hourState.index >= 3 || index == hourState.selectedId - 3
}">
{{item}}
</li>
</ul>
</div>
<div class="pickerBoxContentList">
<ul :class="{'third_dragging': minuteState.dragging}"
@touchstart="_onTouchStart('minute', $event)"
@mousedown="_onTouchStart('minute', $event)"
:style="{'transform' : 'translate3d(0,' + minuteState.translateY + 'px, 0)'}">
<li></li>
<li></li>
<li></li>
<li
v-for="(item, index) in minuteState.data"
:key="index"
:class="{
'current': item == minuteState.selectedId,
'node1': index - minuteState.index == 1 || index == minuteState.selectedId - 1,
'node2': index - minuteState.index == 2 || index == minuteState.selectedId - 2,
'node3': index - minuteState.index >= 3 || index == minuteState.selectedId - 3
}">
{{item}}
</li>
</ul>
</div>
</div>
<hr class="ProvCitySelectedTop">
<hr class="ProvCitySelectedBottom">
</div>
<div class="pickerBoxHeader">
<div class="pickerBoxHeaderCancle" @click="esc">{{cancel}}</div>
<span></span>
<div class="pickerBoxHeaderConfirm" @click="submit">{{confirm}}</div>
</div>
</div>
</div>
</transition>
</div>
</template>
<script>
export default {
data: function () {
return {
show: this.propsShow,
result: this.propsResult,
firstShow: true,
target: '',
yearState: {
data: null,
selectedId: null,
index: 0,
startPos: null,
translateY: 0,
startTranslateY: 0,
dragging: false
},
monthState: {
data: null,
selectedId: null,
index: 0,
startPos: null,
translateY: 0,
startTranslateY: 0,
dragging: false
},
dayState: {
data: null,
selectedId: null,
index: 0,
startPos: null,
translateY: 0,
startTranslateY: 0,
dragging: false
},
hourState: {
data: [],
selectedId: null,
index: 0,
startPos: null,
translateY: 0,
startTranslateY: 0,
dragging: false
},
minuteState: {
data: [],
selectedId: null,
index: 0,
startPos: null,
translateY: 0,
startTranslateY: 0,
dragging: false
},
delta: 0,
slideEls: null
};
},
mounted: function () {
this.initData();
this._onTouchMove = this._onTouchMove.bind(this);
this._onTouchEnd = this._onTouchEnd.bind(this);
},
methods: {
computedYear() {
const year = this.endYear ? this.endYear : new Date().getFullYear();
let yearData = [];
for (let i = this.startYear; i <= year; i++) {
yearData.push(i);
}
return yearData;
},
computedMonth() {
let monthData = [];
const startControl = this.startControlComputed();
const endControl = this.endControlComputed();
const sameResult = this.sameComputed();
// 开始年份限制 计算最小月
if (startControl.yearFlag && !sameResult.sameYear) {
const minMonth = this.startMonth;
for (let i = minMonth; i <= 12; i++) {
monthData.push(i);
}
return monthData;
}
// 结束年份限制 计算最大月
if (endControl.yearFlag && !sameResult.sameYear) {
const maxMonth = this.endMonth;
for (let i = 1; i <= maxMonth; i++) {
monthData.push(i);
}
return monthData;
}
// 开始与结束年份限制, 且年份相同 计算最小月-最大月
if (startControl.yearFlag && endControl.yearFlag && sameResult.sameYear) {
const minMonth = this.startMonth;
const maxMonth = this.endMonth;
for (let i = minMonth; i <= maxMonth; i++) {
monthData.push(i);
}
return monthData;
}
// 无限制
for (let i = 1; i <= 12; i++) {
monthData.push(i);
}
return monthData;
},
computedDay() {
let dayData = [];
const year = this.yearState.selectedId;
const month = this.monthState.selectedId;
const current = new Date(year, month, 0);
var currentDay = current.getDate();
const startControl = this.startControlComputed();
const endControl = this.endControlComputed();
const sameResult = this.sameComputed();
const startFlag = startControl.yearFlag && startControl.monthFlag;
const endFlag = endControl.yearFlag && endControl.monthFlag;
const startEndSame = sameResult.sameYear && sameResult.sameMonth;
// 开始年月限制 计算最小日
if (startFlag && !startEndSame) {
const minDay = this.startDay;
for (let i = minDay; i <= currentDay; i++) {
dayData.push(i);
}
return dayData;
}
// 结束年月限制 计算最最大日
if (endFlag && !startEndSame) {
const maxDay = this.endDay;
for (let i = 1; i <= maxDay; i++) {
dayData.push(i);
}
return dayData;
}
// 开始年月和结束年月限制 计算最小日-最大日
if (startFlag && endFlag && startEndSame) {
const minDay = this.startDay;
const maxDay = this.endDay;
for (let i = minDay; i <= maxDay; i++) {
dayData.push(i);
}
return dayData;
}
// 无限制
for (let i = 1; i < currentDay + 1; i++) {
dayData.push(i);
}
return dayData;
},
startControlComputed() {
let startControlResult = {};
startControlResult.yearFlag = this.yearState.selectedId === this.startYear;
startControlResult.monthFlag = this.monthState.selectedId === this.startMonth;
return startControlResult;
},
endControlComputed() {
let endControlResult = {};
endControlResult.yearFlag = this.yearState.selectedId === this.endYear;
endControlResult.monthFlag = this.monthState.selectedId === this.endMonth;
return endControlResult;
},
sameComputed() {
let sameResult = {};
sameResult.sameYear = this.startYear === this.endYear;
sameResult.sameMonth = this.startMonth === this.endMonth;
return sameResult;
},
initData() {
const curDate = new Date();
const yearState = this.yearState;
const monthState = this.monthState;
const dayState = this.dayState;
yearState.data = this.computedYear();
yearState.selectedId = curDate.getFullYear();
this.filterMonth();
monthState.selectedId = curDate.getMonth() + 1;
this.filterDay();
dayState.selectedId = curDate.getDate();
this.setHour();
this.setMinute();
},
transToToday() {
const yearState = this.yearState;
const monthState = this.monthState;
const dayState = this.dayState;
const hourState = this.hourState;
const minuteState = this.minuteState;
this.$nextTick(() => {
setTimeout(() => {
const height = this.$refs['list-box'][0].getBoundingClientRect().height;
for (let i = 0; i < yearState.data.length; i++) {
if (yearState.selectedId === yearState.data[i]) {
yearState.index = i;
}
}
yearState.translateY = -height * yearState.index;
for (let i = 0; i < monthState.data.length; i++) {
if (monthState.selectedId === monthState.data[i]) {
monthState.index = i;
}
}
monthState.translateY = -height * monthState.index;
for (let i = 0; i < dayState.data.length; i++) {
if (dayState.selectedId === dayState.data[i]) {
dayState.index = i;
}
}
dayState.translateY = -height * dayState.index;
for (let i = 0; i < hourState.data.length; i++) {
if (hourState.selectedId === hourState.data[i]) {
hourState.index = i;
}
}
hourState.translateY = -height * hourState.index;
for (let i = 0; i < minuteState.data.length; i++) {
if (minuteState.selectedId === minuteState.data[i]) {
minuteState.index = i;
}
}
minuteState.translateY = -height * minuteState.index;
}, 300);
});
},
submit() {
this.result = {
'year': this.yearState.data[this.yearState.index],
'month': this.monthState.data[this.monthState.index],
'day': this.dayState.data[this.dayState.index],
'hour': this.hourState.data[this.hourState.index],
'minute': this.minuteState.data[this.minuteState.index],
'title': this.title
};
this.$emit('setDate', this.result);
this.show = false;
this.initData();
this._onTouchMove = this._onTouchMove.bind(this);
this._onTouchEnd = this._onTouchEnd.bind(this);
},
esc () {
this.show = false;
this.$emit('escDate');
this.initData();
this._onTouchMove = this._onTouchMove.bind(this);
this._onTouchEnd = this._onTouchEnd.bind(this);
},
filterMonth() {
this.monthState.data = this.computedMonth();
this.monthState.selectedId = this.monthState.data[0];
this.monthState.translateY = 0;
this.monthState.index = 0;
},
filterDay() {
this.dayState.data = this.computedDay();
this.dayState.selectedId = this.dayState.data[0];
this.dayState.translateY = 0;
this.dayState.index = 0;
},
getSelectedData(index) {
let target = this.target;
let thisData = this[target + 'State'];
thisData.selectedId = thisData.data[index];
if (target === 'year') {
this.filterMonth();
this.filterDay();
}
if (target === 'month') {
this.filterDay();
}
},
setHour() {
for (let i = 0; i < 24; i++) {
if (i < 10) {
this.hourState.data.push('0' + i);
} else {
this.hourState.data.push(i);
}
}
let oDate = new Date();
this.hourState.selectedId = oDate.getHours();
this.hourState.translateY = 0;
this.hourState.index = 0;
},
setMinute() {
for (let i = 0; i < 60; i++) {
if (i < 10) {
this.minuteState.data.push('0' + i);
} else {
this.minuteState.data.push(i);
}
}
let oDate = new Date();
let Currnetminute = oDate.getMinutes();
if (Currnetminute < 10) {
Currnetminute = '0' + Currnetminute;
}
this.minuteState.selectedId = Currnetminute;
this.minuteState.translateY = 0;
this.minuteState.index = 0;
},
setPage() {
let target = this.target;
let thisData = this[target + 'State'];
let clientHeight = this.slideEls[0].getBoundingClientRect().height;
let total = thisData.data.length;
let goPage = Math.round((thisData.translateY / clientHeight).toFixed(1));
if (goPage > 0) {
goPage = 0;
}
goPage = total - 1 >= Math.abs(goPage) ? goPage : -(total - 1);
let index = Math.abs(goPage);
thisData.index = index;
this.getSelectedData(index);
thisData.translateY = goPage * clientHeight;
},
_getTouchPos(e) {
return e.changedTouches ? e.changedTouches[0]['pageY'] : e['pageY'];
},
_getElem(e) {
return Array.from(e.currentTarget.children).slice(3, -3);
},
_onTouchStart(target, e) {
let thisData = this[target + 'State'];
this.target = target;
this.slideEls = this._getElem(e);
this.delta = 0;
thisData.startPos = this._getTouchPos(e);
thisData.startTranslateY = thisData.translateY;
thisData.dragging = true;
document.addEventListener('touchmove', this._onTouchMove, false);
document.addEventListener('touchend', this._onTouchEnd, false);
document.addEventListener('mousemove', this._onTouchMove, false);
document.addEventListener('mouseup', this._onTouchEnd, false);
},
_onTouchMove(e) {
let target = this.target;
let thisData = this[target + 'State'];
this.delta = this._getTouchPos(e) - thisData.startPos;
thisData.translateY = thisData.startTranslateY + this.delta;
if (Math.abs(this.delta) > 0) {
e.preventDefault();
}
},
_onTouchEnd(e) {
let target = this.target;
let thisData = this[target + 'State'];
thisData.dragging = false;
this.setPage();
document.removeEventListener('touchmove', this._onTouchMove);
document.removeEventListener('touchend', this._onTouchEnd);
document.removeEventListener('mousemove', this._onTouchMove);
document.removeEventListener('mouseup', this._onTouchEnd);
},
_stopDef(e) {
e.preventDefault();
}
},
watch: {
propsShow: function (newVal) {
this.show = newVal;
},
show: function (newVal) {
this.$emit('setDialogVisible', newVal);
this.transToToday();
// if (this.firstShow) {
// this.transToToday();
// this.firstShow = false;
// }
}
},
props: {
'propsResult': {
type: Object,
default: null
},
'propsShow': {
type: Boolean,
default: false
},
'startYear': {
type: Number,
default: 2000
},
'startMonth': {
type: Number,
default: 1
},
'startDay': {
type: Number,
default: 1
},
'endYear': {
type: Number,
default: null
},
'endMonth': {
type: Number,
default: null
},
'endDay': {
type: Number,
default: null
},
'title': {
type: String,
default: '请选择'
},
'confirm': {
type: String,
default: '确定'
},
'cancel': {
type: String,
default: '取消'
}
}
};
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../common/stylus/mixin'
.datepicker
width :100%;
height :100%;
.pickerBoxBg {
width :100%;
height :100%;
background: rgba(0, 0, 0, .35);
z-index: 200;
position: fixed;
top:0;
left :0;
}
.pickerBox
position:absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 300;
background: #fff;
width :cal(670);
border-radius :cal(40);
.pickerBoxWrapper
width :cal(562);
margin :0 auto;
.pickerBoxTitle
border-bottom :1px solid #C9C9C9;
overflow :hidden;
h2
font-size :cal(36);
text-align center;
margin:cal(62) auto cal(24) auto;
font-weight :bold;
h3
font-size :cal(28);
text-align center;
margin-bottom :cal(50);
ul
width :90%;
overflow: hidden;
margin:0 auto cal(24) auto;
li
float: left;
width :20%;
text-align center;
font-size :cal(28);
.pickerBoxHeader
height: cal(121);
line-height: cal(121);
overflow: hidden;
text-align: center;
font-size: cal(30)!important;
border-top :1px solid #C9C9C9;
span
float :left;
width :1px;
height :cal(121);
background-color :#C9C9C9;
.pickerBoxHeaderCancle
float: left;
color: #078ffd;
font-size: cal(30)!important;
width: 49.5%;
text-align center
.pickerBoxHeaderConfirm
float: right;
color: #ff5657;
font-size: cal(30)!important;
width: 49.5%;
text-align center
.pickerBoxContent
width: 90%
margin: 0 auto;
background: #fff;
overflow: hidden;
height: cal(450);
overflow: hidden;
.pickerBoxContentList
float: left;
width: 20%;
text-align: center;
ul
-webkit-transition: all .3s ease;
transition: all .3s ease;
&.first_dragging,
&.second_dragging,
&.third_dragging
-webkit-transition: none;
transition: none;
li
line-height: cal(65);
height: cal(65);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: cal(26)!important;
&.current
font-size: cal(32)!important;
font-weight: bold;
&.node1
font-size: cal(28)!important;
opacity: .7;
&.node2
font-size: cal(26)!important;
opacity: .5;
&.node3
font-size: cal(24)!important;
opacity: .3;
.ProvCitySelectedTop
width: cal(562);
border: none;
border-top: cal(1) solid #eee;
position: absolute;
bottom: cal(384);
margin: 0;
height: 0;
.ProvCitySelectedBottom
width: cal(562);
border: none;
border-top: cal(1) solid #eee;
position: absolute;
bottom: cal(309);
margin: 0;
height: 0;
</style>