前言
基于tdesign vue-next + ts
实现
预览地址: time(48*7)
内容
<!-- 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>
学无止境,谦卑而行.