1、该组件可用在当不可选日期又需要跨天选择时间时的场景,交互效果类似elementUI的TimePicker 时间选择器
2、图例

3、父组件代码
<TimePicker :id="'32'" :time="times" @selectTime="(time) => times = time" />
import TimePicker from "@/components/timePicker";
4、子组件代码
<template>
<div class="timeInputBox" :id="'t1-' + id" :ref="'ref-' + id">
<el-input ref="timeInput" :id="'t2-' + id" @focus="focus" v-model="input" :placeholder="placeholder" />
<div class="menuBox" :class="{ menuBox: true, activemenuBox: show }" :id="'t3-' + id">
<div class="triangle" :id="'t4-' + id"></div>
<div class="menuMain" :id="'t5-' + id" @mouseenter="handleMouse('Enter')" @mouseleave="handleMouse('Leave')">
<div class="leftTime container" :id="'t6-' + id" ref="leftTime" @mouseenter="handleMouse('Enter')"
@mouseleave="handleMouse('Leave')">
<div class="timeItem" :id="'t7-' + id" :style="{ height: clientHeight + 'px' }" v-for="item in [1, 2]"
:key="item + '-1'" />
<div @click="changeTime('h', item, index)" :ref="'timeItem' + index" v-for="(item, index) in timeList.hour"
:key="index + '01'" :class="{ activeTime: index == selectHourIndex }" :id="'t8-' + id">{{ textbuil(item) }}
</div>
<div class="timeItem" :id="'t9-' + id" :style="{ height: clientHeight + 'px' }" v-for="item in [1, 2]"
:key="item + '-2'" />
</div>
<div class="rightTime container" :id="'t10-' + id" ref="rightTime" @mouseenter="handleMouse('Enter')"
@mouseleave="handleMouse('Leave')">
<div class="timeItem" :id="'t11-' + id" :style="{ height: clientHeight + 'px' }" v-for="item in [1, 2]"
:key="item + '-3'" />
<div :id="'t12-' + id" @click="changeTime('m', item, index)" v-for="(item, index) in timeList.minute"
:key="index + '02'" :class="{ activeTime: index == selectMinuteIndex }">{{ item }}
</div>
<div class="timeItem" :id="'t13-' + id" :style="{ height: clientHeight + 'px' }" v-for="item in [1, 2]"
:key="item + '-4'" />
</div>
<div class="botBut" :id="'t4-' + id">
<el-button class="elbutton" :id="'t15-' + id" type="text" @click="selectTime('yes', '00:00')">确定</el-button>
<el-button class="elbutton" :id="'t16-' + id" style="color: #333;" type="text"
@click="selectTime('close')">取消</el-button>
</div>
<div class="line" :id="'t17-' + id" :style="{ top: `calc(50% - ${clientHeight - 5}px)` }"></div>
<div class="line" :id="'t18-' + id" :style="{ top: `calc(50% + ${5}px)` }"></div>
</div>
</div>
</div>
</template>
<script>
let inTimepickerPage = false
export default {
name: "timePicker",
components: {},
props: {
id: {
type: String,
default: Math.round(Math.random() * (99999999999999 - 10000000000000) + 10000000000000) + '',
},
time: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '请选择时间',
},
timeList: {
type: Object,
default: () => {
return {
hour: ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30"],
minute: ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59"],
}
}
}
},
data() {
return {
input: '',
show: false,
leftTimeScroll: 0,
rightTimeScroll: 0,
clientHeight: 36,
selectHourIndex: -1,
selectMinuteIndex: -1,
setTimeing: false,
};
},
created() {
inTimepickerPage = true
},
mounted() {
document.addEventListener('click', (e) => {
if (!inTimepickerPage) return
const [sn, ids] = e.target.id?.split("-")
if (this.id != ids) {
this.selectTime('yes')
this.show = false
}
})
const leftTime = this.$refs.leftTime;
const rightTime = this.$refs.rightTime;
leftTime.addEventListener('scroll', () => {
const scrollTop = this.$refs.leftTime.scrollTop
if (!this.time && !this.input && scrollTop < 2) return
this.selectHourIndex = Math.round(scrollTop / this.clientHeight);
this.input = this.textbuil(this.timeList.hour[this.selectHourIndex]) + ':' + this.timeList.minute[this.selectMinuteIndex]
})
rightTime.addEventListener('scroll', () => {
const scrollTop = this.$refs.rightTime.scrollTop;
if (!this.time && !this.input && scrollTop < 2) return
this.selectMinuteIndex = Math.round(scrollTop / this.clientHeight);
this.input = this.textbuil(this.timeList.hour[this.selectHourIndex]) + ':' + this.timeList.minute[this.selectMinuteIndex]
})
},
methods: {
focus() {
this.selectHourIndex = 0;
this.selectMinuteIndex = 0;
this.show = true
this.$emit('focus')
},
changeTime(type, h, m) {
if (type == 'h') {
this.$refs.leftTime.scrollTop = Math.max(h * 1, 0) * this.clientHeight;
} else {
this.$refs.rightTime.scrollTop = m * this.clientHeight;
}
},
handleMouse(type) {
['leftTime', 'rightTime'].map(item => {
const scrollTop = this.$refs[item].scrollTop
const selectIndex = Math.round(scrollTop / this.clientHeight)
this.$refs[item].scrollTop = selectIndex * this.clientHeight;
})
},
selectTime(type, times) {
if (this.setTimeing) return
this.setTimeing = true
setTimeout(() => {
this.setTimeing = false
}, 100);
if (type == 'yes') {
const censend = this.selectHourIndex == -1 || this.selectMinuteIndex == -1 || (!this.time && !this.input)
if (censend && times) return this.$emit('selectTime', times);
if (censend) return
const time = this.timeList.hour[this.selectHourIndex] + ':' + this.timeList.minute[this.selectMinuteIndex]
this.$emit('selectTime', time);
} else {
const [leftDistance = 0, rightDistance = 0] = this.time?.split(':')
if (!this.time && !this.input) return
this.input = this.textbuil(leftDistance) + ':' + rightDistance
}
setTimeout(() => {
this.$nextTick(() => {
this.$refs.timeInput?.blur()
this.show = false
})
}, 50);
},
textbuil(data) {
if (data > 23) {
data = data * 1
return '次日0' + (data - 24)
}
return data
},
},
watch: {
time: {
handler: function (newVal, oldVal) {
console.log('time', newVal?.split(':'), newVal, oldVal);
const [leftDistance, rightDistance] = newVal?.split(':')
if (!newVal) return
this.input = this.textbuil(leftDistance) + ':' + rightDistance
},
deep: true,
immediate: true
},
show: {
handler: function (newVal, oldVal) {
if (newVal) {
this.$nextTick(() => {
this.clientHeight = this.$refs.timeItem0?.[0].clientHeight || 36;
const [leftDistance = 0, rightDistance = 0] = this.time?.split(':')
this.$refs.leftTime.scrollTop = Math.max(leftDistance * 1, 0) * this.clientHeight + 1;
this.$refs.rightTime.scrollTop = rightDistance * this.clientHeight + 1;
setTimeout(() => {
this.$refs.leftTime.scrollTop = this.$refs.leftTime.scrollTop - 1
this.$refs.rightTime.scrollTop = this.$refs.rightTime.scrollTop - 1
}, 10);
})
} else {
document.removeEventListener('scroll', () => { })
document.removeEventListener('click', () => { })
}
}
},
},
destroyed() {
inTimepickerPage = false
document.removeEventListener('scroll', () => { })
document.removeEventListener('click', () => { })
},
};
</script>
<style lang="scss" scoped>
.timeInputBox {
position: relative;
.menuBox {
position: absolute;
width: 182px;
height: 230px;
z-index: 9;
transition: .25s;
overflow: hidden;
transform: scaleY(0);
transform-origin: top;
box-shadow: 10px 9px 20px -10px #e1dfdf;
.triangle {
width: 0;
height: 0;
border-top: 6px solid transparent;
border-bottom: 6px solid #fff;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
margin-left: 10px;
margin-bottom: -1px;
z-index: 10;
position: absolute;
top: 0;
left: 10px;
}
.menuMain {
background-color: #fff;
height: calc(100% - 10px);
width: 100%;
border-radius: 4px;
border: 1px solid #eaeaea;
z-index: 9;
margin-top: 10px;
box-shadow: 2px 1px 20px -11px #333;
.container {
float: left;
height: calc(100% - 36px);
width: 50%;
overflow: auto;
text-align: center;
font-size: 12px;
color: #606266;
cursor: pointer;
:hover {
background-color: #4d7fff16;
}
.timeItem:hover {
background-color: transparent;
}
.activeTime {
font-weight: bold;
color: #000;
&:hover {
background-color: transparent;
}
}
}
.botBut {
width: 99%;
height: 36px;
border-top: 1px solid #d7d7d7;
position: absolute;
bottom: 2px;
background-color: #fff;
.elbutton {
font-size: 12px;
float: right;
margin: auto 10px;
font-weight: 500;
}
}
.line {
position: absolute;
height: 1px;
width: calc((100% - 40px));
left: 20px;
background-color: #e1e1e1;
}
}
}
.activemenuBox {
transform: scaleY(1);
}
}
.container::-webkit-scrollbar {
width: 7px;
}
.container::-webkit-scrollbar-thumb {
background-color: #dfdfdf;
border-radius: 5px;
}
.container::-webkit-scrollbar-track {
background-color: #fafafa;
}
.container::-webkit-scrollbar-button {
background-color: #fff;
border-radius: 5px;
}
.container::-webkit-scrollbar-button:hover {
background-color: #c1c1c1;
}
</style>