最终效果图
一、 DragSelect插件说明
1.该插件为一款简单的javascript拖动选择选中插件DragSelect文档说明,类似 window 系统文件拖动选择效果,文档链接:DragSelectDoc: 简单的javascript拖动选择选中插件DragSelect文档说明,类似 window 系统文件拖动选择效果
2.该插件使用npm下载与实际不符,可能是插件作者并未上传npm库导致,故在本篇中,请将上述文档链接中的ds.min.js文件复制到本地,并对外暴露DragSelect即可。
修改本地ds.min.js文件重命名为dragSelect.js并在源码中暴露相关方法。
使用时通过import 引用即可
import DragSelect from '@/utils/dragSelect';
二、技术梳理
1.确认盒子数量
以06:00 – 22:30,每半个小时为一个单元为例,默认需要33个小盒子,若当前有预约数据,以10:00 – 11:30为例,则需要将对应时间段的三个小盒子替换为一个长度为三倍小盒子的长盒子。
2.补充所需数据
后台只会返回当前会议室的预约数据,所以我们需要动态补足其余时间段的数据,以半个小时为单位添加timeFrom、timeTo、status等相关所需数据。
3.响应式开发,兼容1280 – 1920屏幕分辨率
盒子的数量是固定的,屏幕的宽度确是不确定的,所以需要实时获取屏幕宽度或父盒子宽度,获取屏幕宽度也要通过计算实际盒子宽度,故本篇推荐只实时获取父盒子宽度,来实时计算每个小盒子的宽度。
三、实现方案
1.填充数据
1.首先,初始化06:00-22:30的时间数据
const initTime = computed(() => {
const arr = []
for (let i = 6; i <= 22; i++) {
let h = i < 10 ? `0${i}` : i
arr.push(`${h}:00`, `${h}:30`)
}
return arr
})
这样就拿到了间隔为30分钟的时间数组
2.后台返回数据如下
后台只会返回预约数据,而我们需要06:00 – 22:30的所有时间段的数据,所以需要判断时间是否连续,如果不连续则手动添加时间数据
const checkTime = (time, bookingList) => {
return bookingList.some(item => time >= item.timeFrom && time < item.timeTo)
}
判断条件为存在时间间隙且时间不是22:30,手动添加开始时间、结束时间、 及是否超过当前时间
const meetingTime = () => {
meetingDetail.value.map(meeting => {
initTime.value.map((item, index) => {
if (!checkTime(item, meeting.bookingList) && item !== '22:30') {
meeting.bookingList.push({
timeFrom: item,
timeTo: initTime.value[index + 1],
status: Date.parse(`${props.date} ${initTime.value[index + 1]}`) <
Date.parse(nowDate.value) ? -1 : 0,
})
}
})
})
meetingDetail.value.map(item => {
item.bookingList.sort((prev, next) => {
let pTime = parseInt(prev.timeFrom.split(':').join(''))
let nTime = parseInt(next.timeFrom.split(':').join(''))
return pTime - nTime
})
})
}
最后拿到时间连贯的最终数据
2.使用DragSelect绑定DOM
1.绑定dom
area属性通过id绑定最外层的大盒子,代表可选中区域;selectables通过类名绑定,代表可选择的元素节点,这里代表每个小盒子(每30分钟时间段)
因为在执行new DragSelect时要首先保证DOM节点是存在的,所以放到onMounted里执行。
onMounted(() => {
new DragSelect({
selectables: document.getElementsByClassName('li'),
area: document.getElementById('main'),
multiSelectMode: false,
onElementSelect: el => {},
onElementUnselect: el => {},
callback: ele => {}
});
3.渲染小盒子(时间节点)
1.在拿到数据后,我们就要根据数据去动态渲染一个一个的小盒子,由于是响应式开发,所以需要先实时获取父盒子的宽度,这里推荐使用 ResizeObserver。同样放到onMounted里执行。
const boxWidth = ref(null)
const getBoxWidth = () => {
const resizeObserver = new ResizeObserver(entries => {
boxWidth.value = entries[0].contentRect.width - 46
});
const box = document.getElementById('main');
if (box) resizeObserver.observe(box);
}
更详细的关于ResizeObserver说明可以查看下面链接,这里不做补充。
2.在实时获取父盒子宽度后,则可以动态渲染小盒子宽度。
在小盒子dom上绑定我们所需数据,以便进行dom处理。
<div class="ul" :meet="JSON.stringify({ roomId: item.id,roomName: item.roomName,location: item.cityName + '-' + item.floorName})">
<template v-for="meet in item.bookingList">
<el-popover v-if="[-1,1,2].includes(meet.status)" placement="top-start" :width="200" trigger="hover">
<template #reference>
<div class="li flex justify-center items-center" :timeFrom="meet.timeFrom" :timeTo="meet.timeTo" :status="meet.status" :isMine="meet.isMine">
<p class="line-clamp-1 text-sm">{{ meet.subject }}p>
div>
template>
<div v-if="meet.status == 1 || meet.status == 2" class="w-full h-30">
<div class="w-full flex justify-between">
<div class="mb-4">
<el-icon>
<WarningFilled />
el-icon>
<span class="h-5 font-bold text-base ml-1" style="color: black;">{{ meet.isMine == 1 ? '我的预约' : '已被预约' }}span>
div>
<p v-if="meet.isMine == '1' && meet.status == '1'" class="text-xsblue-level_2 text-xs mt-1 cursor-pointer" @click="showDialog('2',meet)">修改会议p>
div>
<div class="w-40 mx-auto">
<p>时间:{{ meet.timeFrom }} - {{ meet.timeTo }}p>
<p>姓名:{{meet.bookingUserName }}p>
<p>部门:{{ meet.deptName }}p>
<p>手机:{{ meet.phone }}p>
<el-tooltip class="box-item" effect="dark" :content="meet.subject" placement="top-start">
<p class="line-clamp-1">主题:{{ meet.subject }}p>
el-tooltip>
div>
div>
<div v-if="meet.status == -1" class="w-full h-5">
<div class="w-full mb-4">
<el-icon>
<WarningFilled />
el-icon>
<span class="h-5 font-bold text-base ml-1" style="color: black;">已超时span>
div>
div>
el-popover>
<div class="li" v-else :timeFrom="meet.timeFrom" :timeTo="meet.timeTo" :status="meet.status" :isMine="meet.isMine">
div>
template>
div>
通过watch属性监视父盒子宽度,触发渲染小盒子宽高
// 定义子盒子样式
const definitionChildBox = () => {
const childBox = document.querySelectorAll('.ul .li')
childBox.forEach(item => {
item.style.height = boxWidth.value / 33 + 'px'
let timeFrom = item.getAttribute('timeFrom')
let timeTo = item.getAttribute('timeTo')
let status = item.getAttribute('status')
let isMine = item.getAttribute('isMine')
// 判断时间差
let multiplier = (timeTo.split(':')[0] - timeFrom.split(':')[0]) * 2 + ((timeTo.split(':')[1] - timeFrom.split(':')[1]) / 30)
item.style.width = multiplier * boxWidth.value / 33 + 'px'
// 工作时间
if (timeFrom == '09:00' || timeFrom == '18:00') item.style.borderLeft = "1px solid rgb(232,110,48)"
// 依据情况显示不同高亮
// 重置
item.classList.remove('mine')
item.classList.remove('timeout')
item.classList.remove('appointment')
// 我的预约
if (isMine == 1) item.classList.add('mine')
else if (status == 1 || status == 2) item.classList.add('appointment')
else if (status == -1) item.classList.add('timeout')
})
}
// 渲染子盒子样式
watch(boxWidth, () => {
definitionChildBox()
//渲染时间线用 在父盒子宽度小于1270的情况下,时间线会特别拥挤,此时作相应处理
if (boxWidth.value > 1270) windowWidth.value = true
else windowWidth.value = false
})
4.处理时间线
在屏幕分辨率大于1640时展示效果如下:
在屏幕分辨率小于1640时展示效果如下:
在watch监视父盒子宽度时,同步判断是否展示完全时间线标识 。
watch(boxWidth, () => {
definitionChildBox()
if (boxWidth.value > 1270) windowWidth.value = true
else windowWidth.value = false
})
通过windowWidth标识展示或隐藏 30分钟时间点
width: windowWidth ? '100%' : (boxWidth + 35 + 'px')}" class="h-10 text-sm text-gray-400 flex justify-around">
<div v-for="time in initTime">
<span v-if="windowWidth || time.split(':')[1] == '00'">{{ time }}span>
div>
div>
5.用户选择校验
在用户在main区域内进行滑动选取时,整个区域都是可以捕捉的,所以要校验用户所选取区域是否合规,例如我们存在多个会议室时,用户可能同时选择两个会议室中的时间节点,亦或是用户选择两个会议室中的空白处,并没选择时间节点,又或是用户进行了跨区域操作,例如当前10:00 – 11:00已经有人预约了会议室,此时用户所选区域为09:00 – 12:00… 以上这些操作都是不被允许的,所以我们要在用户选择后进行校验。
callback: ele => {
if (ele.length == 0) return false
let flag = true
const parentFirstNode = ele[0].parentNode
const parentlastNode = ele[ele.length - 1].parentNode
const childNodeArr = Array.from(parentFirstNode.children)
childNodeArr.forEach(item => {
if (item.getAttribute('status') == 1 && ele[0].getAttribute('timeFrom') < item.getAttribute('timeFrom') && item.getAttribute('timeFrom') < ele[ele.length - 1].getAttribute('timeFrom')) {
ElMessage.error('禁止跨区域操作')
flag = false
}
})
if (!flag) return false
ele.forEach(item => {
if (item.getAttribute('status') == 0 && parentFirstNode == parentlastNode) item.classList.add('checked')
else flag = false
})
if (!flag) return false
checkedNode.value = ele
showDialog('1', JSON.parse(parentFirstNode.getAttribute('meet')), ele)
}
四、总结
以上就是基于Vue3 + DragSelect实现仿钉钉/企业微信会议室预约模块的二次开发方案,在本方案中,我们进行了填充数据、渲染dom、响应式开发等等…内容过多,所以有些地方并没有详细说明,若您有什么疑问或对我的内容进行指正,欢迎您在下方进行评论探讨。
希望本篇内容对您有所帮助。
阅读全文
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=20864,转载请注明出处。
评论0