TypeScript
复制代码
<!-- TimeRangePicker.vue -->
<template>
<div class="time-range-picker">
<span>{{title}} </span>
<!-- 输入框 -->
<el-input ref="inputRef" v-model="displayValue" placeholder="请选择时间范围" readonly @click="togglePicker" clearable @clear="clearSelection" style="margin-right:16px" :style="{'width':width?width:'200px'}"/>
<Teleport to="body">
<!-- 弹出面板 -->
<div v-if="showPicker" class="picker-panel" @click.stop :style="panelStyle">
<!-- 时间网格 -->
<div class="time-grid">
<div v-for="(item, index) in currentOption" :key="index" class="time-cell" :class="{
'start':item.start,
'end':item.end,
'in-range':item.inRange,
}" @click="handleCellClick(item)" @mouseenter="handleCellEnter(item)">
{{ item.label }}
</div>
</div>
</div>
</Teleport>
<div v-if="showPicker" class="picker-mask" @click="closePicker">
</div>
</div>
</template>
<script setup name='TimeRangePicker'>
import { ref, computed, watch, onMounted } from 'vue';
import { point96_0 } from '@bdss-ui/bdss-core/utils/common'
const emit = defineEmits(['update:modelValue']);
const props = defineProps({
modelValue: {
type:Array,
default:()=>[]
},
title:{
type:String,
default:''
},
width:{
type:[String,Number],
}
})
const inputRef=ref()
const showPicker = ref(false);
const startTime = ref(null);
const endTime = ref(null);
const hoverTime = ref(null);
const startIndex = ref(null)
const endIndex = ref(null)
const curEndIndex = ref(null)
const currentOption = ref(point96_0.map((i, inx) => {
return {
value: inx + 1,
label: i,
start: false,
end: false,
inRange: false
}
}))
const panelStyle=ref({})
const togglePicker = () => {
showPicker.value = !showPicker.value;
if (showPicker.value) {
const inputEl = inputRef.value.$el;
inputRef.value.focus()
const rect = inputEl.getBoundingClientRect();
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
panelStyle.value = {
position: 'fixed',
top: `${rect.bottom + scrollTop + 10}px`,
left: `${rect.left + scrollLeft}px`,
zIndex: 3000,
};
currentOption.value = point96_0.map((i, inx) => {
return {
value: inx + 1,
label: i,
start: false,
end: false,
inRange: false
}
})
if (props.modelValue.length > 0) {
startIndex.value = props.modelValue[0]
endIndex.value = props.modelValue[1]
currentOption.value[props.modelValue[0] - 1].start = true
currentOption.value[props.modelValue[1] - 1].end = true
for (let i = props.modelValue[0]; i < props.modelValue[1] - 1; i++) {
currentOption.value[i].inRange = true
}
} else {
startIndex.value = null
endIndex.value = null
}
}
};
const displayValue = computed(() => {
if (props.modelValue.length == 0) return '';
let startT = currentOption.value.find(i => props.modelValue[0] == i.value).label
let startE = currentOption.value.find(i => props.modelValue[1] == i.value).label
return `${startT}~${startE}`;
})
const closePicker = () => {
showPicker.value = false;
};
const handleCellClick = (item) => {
if ((startIndex.value && endIndex.value) || !startIndex.value) {
inputRef.value.focus()
currentOption.value = point96_0.map((i, inx) => {
return {
value: inx + 1,
label: i,
start: false,
end: false,
inRange: false
}
})
startIndex.value = item.value
endIndex.value = null;
currentOption.value[item.value - 1].start = true
} else if (!endIndex.value) {
endIndex.value = item.value
currentOption.value[item.value - 1].end = true
// props.modelValue = [startIndex.value, endIndex.value].slice().sort((a, b) => a - b)
emit('update:modelValue',[startIndex.value, endIndex.value].slice().sort((a, b) => a - b))
showPicker.value = false;
}
};
const handleCellEnter = (item) => {
if (startIndex.value && !endIndex.value) {
currentOption.value = point96_0.map((i, inx) => {
return {
value: inx + 1,
label: i,
start: false,
end: false,
inRange: false
}
})
currentOption.value[startIndex.value - 1].start = true
currentOption.value[item.value - 1].end = true
let arr = [startIndex.value, item.value].slice().sort((a, b) => a - b)
for (let i = arr[0]; i < arr[1] - 1; i++) {
currentOption.value[i].inRange = true
}
}
};
const clearSelection = () => {
startTime.value = null;
endTime.value = null;
};
onMounted(() => {
});
</script>
<style scoped>
.time-range-picker {
position: relative;
display:flex;
align-items: center;
width: auto;
}
.picker-panel {
/* position: absolute;
top: calc(100% + 10px);
left: 0; */
width: 680px;
background: #fff;
border: 1px solid #dcdfe6;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 12px;
/* z-index: 3000; */
font-size: 14px;
pointer-events: none;
}
.picker-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100vh;
width: 100vw;
z-index: 1999;
pointer-events: auto; /* 关键!允许事件穿透 */
}
.picker-mask-content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: transparent;
pointer-events: all; /* 允许点击 */
z-index: 1999;
}
/* .picker-mask::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.1);
pointer-events: all;
} */
.month-row {
display: flex;
gap: 8px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.month-item {
padding: 6px 10px;
border: 1px solid #ebeef5;
border-radius: 4px;
font-size: 12px;
color: #606266;
cursor: pointer;
transition: all 0.2s;
}
.month-item:hover {
background-color: #f5f7fa;
}
.month-item.active {
background-color: #409eff;
color: white;
border-color: #409eff;
}
.time-grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-template-rows: repeat(8, 1fr);
}
.time-cell {
padding: 8px 0;
margin: 5px 0;
text-align: center;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
pointer-events: auto;
}
.time-cell:hover {
color: var(--qctc-primary-color);
}
.time-cell.start,
.time-cell.end {
background-color: #409eff;
color: white;
}
.time-cell.in-range {
/* background-color: #f2f6fc; */
background-color: #edf5fa;
}
.time-cell.hovered {
background-color: #f0f7ff;
border-color: #409eff;
}
</style>