前言
基于tdesign vue-next ts
实现
参考: byte-weektime-picker
预览地址: time(48*7)
内容
代码语言:javascript复制<!-- eslint-disable no-bitwise -->
<template>
<div class="weektime">
<div class="weektime-main">
<div class="weektime-hd">
<div class="weektime-hd-title">星期时间</div>
<div class="weektime-hd-con">
<div class="weektime-hd-con-top">
<div class="weektime-date-range">00:00 - 12:00</div>
<div class="weektime-date-range">12:00 - 24:00</div>
</div>
<div class="weektime-hd-con-bottom">
<span v-for="hour in 24" :key="hour" class="weektime-date-cell">{{ hour - 1 }}</span>
</div>
</div>
</div>
<div class="weektime-bd">
<div class="week-body">
<div v-for="week in weekDays" :key="week" class="week-item">{{ week }}</div>
</div>
<div class="time-body" @mousedown="handleMousedown" @mouseup="handleMouseup" @mousemove="handleMousemove">
<t-tooltip v-for="(i, key) in weekTimes" :key="key" :content="tipTitle(key)">
<div
:data-index="key"
class="time-cell"
:class="{
active: list[key] === '1',
'pre-active': preViewIndex.includes(key),
disable: disableTimes.includes(key),
}"
></div>
</t-tooltip>
</div>
</div>
</div>
<div class="weektime-help">
<div class="weektime-help-tx">
<div class="weektime-help-bd">
<span class="color-box"></span>
<span class="text-box">未选</span>
<span class="color-box color-active"></span>
<span class="text-box">已选</span>
</div>
<div class="weektime-help-ft" @click="initList">清空选择</div>
</div>
<div class="weektime-help-select">
<p v-for="(week, key) in weekDays" v-show="showTimeText[key]" :key="key">
<span class="weektime-help-week-tx">{{ week ':' }}</span>
<span>{{ showTimeText[key] }}</span>
</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
const props = defineProps({
value: {
type: String,
},
// 自定义开始时段,0-47,每1代表半小时,星期一到星期日都生效
startTime: {
type: Number,
},
// 自定义结束时段,0-47,每1代表半小时,星期一到星期日都生效
endTime: {
type: Number,
},
// 自定义禁用时段,数字数组,0-335
customDisableTimes: {
type: Array,
},
});
const DayTimes = 24 * 2;
const list = ref([]);
const isMove = ref(false);
const weekTimes = ref(7 * DayTimes);
const weekDays = ref(['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日']);
const timeTextList = ref([]);
const startIndex = ref(0);
const axis = ref({
startX: null,
startY: null,
endX: null,
endY: null,
});
const preViewIndex = ref([]);
const showTimeText = ref([]);
watch(
() => props.value,
(n) => {
if (n === list.value.join('')) return;
initList(n);
},
);
const emit = defineEmits(['update:value']);
const disableTimes = computed(() => {
if (Array.isArray(props.customDisableTimes) && props.customDisableTimes.every((num) => typeof num === 'number'))
return props.customDisableTimes;
if (props.startTime > -1 && props.endTime > -1) {
const disabled = [];
for (let index = 0; index < weekTimes.value; index ) {
const firstIdx = index % DayTimes;
if (props.startTime > firstIdx || props.endTime < firstIdx) disabled.push(index);
}
return disabled;
}
return [];
});
/**
* 鼠标停留时提示当前时间段
*/
const tipTitle = (index) => {
const timeIndex = index % DayTimes;
// eslint-disable-next-line no-bitwise
const weekIndex = ~~(index / DayTimes);
return `${weekDays.value[weekIndex]} ${timeTextList.value[timeIndex]}~${timeTextList.value[timeIndex 1]}`;
};
/**
* 初始化显示的时间数组
* @return {Array} ["00:00","00:30","01:00",...]
*/
const initTimeText = () => {
const timeTextList = [];
const hours = [];
const minutes = ['00', '30'];
for (let i = 0; i <= 24; i ) {
// eslint-disable-next-line no-unused-expressions
i < 10 ? hours.push(`0${i}`) : hours.push(i.toString());
}
for (const hour of hours) {
for (const minute of minutes) {
timeTextList.push(`${hour}:${minute}`);
}
}
return timeTextList;
};
const handleMousedown = (event) => {
startIndex.value = event.target.getAttribute('data-index');
// eslint-disable-next-line no-bitwise
if (disableTimes.value.includes(~~startIndex.value)) return;
isMove.value = true;
axis.value.startX = startIndex.value % DayTimes;
// eslint-disable-next-line no-bitwise
axis.value.startY = ~~(startIndex.value / DayTimes);
};
const handleMouseup = (event) => {
handleMousemove(event);
resetMousemove();
};
const handleMousemove = (event) => {
if (!isMove.value) return;
const index = event.target.getAttribute('data-index');
axis.value.endX = index % DayTimes;
// eslint-disable-next-line no-bitwise
axis.value.endY = ~~(index / DayTimes);
preViewIndex.value = getSelectIndex();
};
const resetMousemove = () => {
if (!isMove.value) return;
setSelectIndex(preViewIndex.value);
isMove.value = false;
axis.value = {
startX: null,
startY: null,
endX: null,
endY: null,
};
preViewIndex.value = [];
};
/**
* 获取拖动鼠标选择的index数组
*/
const getSelectIndex = () => {
const indexList = [];
const newAxis = {
startX: Math.min(axis.value.startX, axis.value.endX),
startY: Math.min(axis.value.startY, axis.value.endY),
endX: Math.max(axis.value.startX, axis.value.endX),
endY: Math.max(axis.value.startY, axis.value.endY),
};
for (let y = newAxis.startY; y <= newAxis.endY; y ) {
for (let x = newAxis.startX; x <= newAxis.endX; x ) {
indexList.push(x y * DayTimes);
}
}
return indexList.filter((v) => !disableTimes.value.includes(v));
};
/**
* 设置选择的时间段并赋给绑定的值
* @param {Array} indexList 选择的index数组
*/
const setSelectIndex = (indexList) => {
if (!Array.isArray(indexList)) return;
const listLength = indexList.length;
const newData = list.value[startIndex.value] === '1' ? '0' : '1';
for (let i = 0; i < listLength; i ) {
list.value.splice(indexList[i], 1, newData);
}
emit('update:value', list.value.join(''));
showSelectTime(list.value);
};
/**
* 展示选择的时间段
* @param {Array} list 已选择的list数组
*/
const showSelectTime = (list) => {
if (!Array.isArray(list)) return;
const weeksSelect = [];
const listLength = list.length;
showTimeText.value = [];
if (listLength === 0) return;
// 把 336长度的 list 分成 7 组,每组 48 个
for (let i = 0; i < listLength; i = DayTimes) {
weeksSelect.push(list.slice(i, i DayTimes));
}
weeksSelect.forEach((item) => {
showTimeText.value.push(getTimeText(item));
});
};
const getTimeText = (arrIndex) => {
if (!Array.isArray(arrIndex)) return '';
const timeLength = arrIndex.length;
let isSelect = false;
let timeText = '';
arrIndex.forEach((value, index) => {
if (value === '1') {
if (!isSelect) {
timeText = timeTextList.value[index];
isSelect = true;
}
if (index === timeLength - 1) timeText = `~${timeTextList.value[index 1]}、`;
} else if (isSelect) {
timeText = `~${timeTextList.value[index]}、`;
isSelect = false;
}
});
return timeText.slice(0, -1);
};
// eslint-disable-next-line consistent-return
const initList = (value: any) => {
const reg = new RegExp(`^[01]{${weekTimes.value}}$`);
if (value && reg.test(value)) {
list.value = value.split('');
return showSelectTime(list.value);
}
list.value = new Array(weekTimes.value).fill('0');
emit('update:value', list.value.join(''));
showSelectTime(list.value);
};
onMounted(() => {
timeTextList.value = initTimeText();
document.addEventListener('mouseup', resetMousemove);
initList(props.value);
});
onUnmounted(() => {
document.removeEventListener('mouseup', resetMousemove);
});
</script>
<style lang="less" scoped>
div,
span,
p {
margin: 0;
padding: 0;
border: 0;
font-weight: normal;
vertical-align: baseline;
-webkit-tap-highlight-color: transparent;
-ms-tap-highlight-color: transparent;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.weektime {
width: 658px;
font-size: 14px;
line-height: 32px;
color: #515a6e;
user-select: none;
}
.weektime .weektime-main {
border: 1px solid #dcdee2;
position: relative;
}
.weektime .weektime-hd {
display: flex;
background: #f8f8f9;
}
.weektime .weektime-hd-title {
display: flex;
align-items: center;
padding: 0 6px;
width: 80px;
height: 65px;
}
.weektime .weektime-hd-con {
flex: 1;
display: flex;
-webkit-box-orient: vertical;
flex-direction: column;
}
.weektime .weektime-hd-con-top {
display: flex;
border-bottom: 1px solid #dcdee2;
}
.weektime .weektime-date-range {
width: 288px;
height: 32px;
line-height: 32px;
text-align: center;
border-left: 1px solid #dcdee2;
}
.weektime .weektime-hd-con-bottom {
display: flex;
}
.weektime .weektime-date-cell {
width: 24px;
height: 32px;
line-height: 32px;
text-align: center;
border-left: 1px solid #dcdee2;
}
.weektime .weektime-bd {
display: flex;
}
.weektime .week-body {
width: 80px;
flex-shrink: 0;
}
.weektime .week-item {
border-top: 1px solid #dcdee2;
text-align: center;
height: 30px;
line-height: 30px;
}
.weektime .time-body {
width: 576px;
height: 210px;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
position: relative;
}
.weektime .time-cell {
position: relative;
width: 12px;
height: 30px;
border-left: 1px solid #efefef;
border-top: 1px solid #efefef;
overflow: hidden;
transition: all 0.3s ease;
outline-width: 0;
}
.weektime .time-cell.active {
background: #2d8cf0;
}
.weektime .time-cell.disable {
cursor: no-drop;
}
.weektime .time-cell::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
opacity: 0.5;
transition: all 866ms ease;
z-index: 99999;
}
.weektime .pre-active::after {
background: #113860;
}
.weektime .disable::after {
background: #cccccc;
}
.time-area {
width: 576px;
height: 210px;
position: absolute;
top: 0;
left: 0;
z-index: 100;
background: transparent;
}
.weektime .weektime-help {
width: 658px;
border: 1px solid #dcdee2;
border-top: none;
padding: 5px 15px;
}
.weektime .weektime-help-tx {
display: flex;
align-items: center;
justify-content: space-between;
}
.weektime .weektime-help-week-tx {
color: #999;
}
.weektime .weektime-help-bd {
display: flex;
align-items: center;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
padding: 4px 0;
}
.weektime .weektime-help .color-box {
width: 14px;
height: 20px;
background: #fff;
border: 1px solid #dddddd;
display: block;
margin-right: 6px;
}
.weektime .weektime-help-bd .color-box.color-active {
background: #2d8cf0;
}
.weektime .weektime-help .text-box {
margin-right: 15px;
}
.weektime .weektime-help .weektime-help-ft {
color: #2d8cf0;
cursor: pointer;
}
</style>