JS中步进拖拉拽效果实现,vue3中做课表业务,对课表实现课程时间拖动修改的操作。拖拉拽代码效果实现案例教程。也可对应排班业务。
拖拉拽课表效果展示:
上述案例拖拉拽思路讲解:
1、用grid网格化布局
2、将顶部的8个单元格循环生成,从第二个单元格开始写入星期一到星期日
3、将左侧的时间单元格用数组生成,然后用v-for循环生成到页面中(这里需要使用grid布局,让单元格垂直排列在左侧)
4、让中间用于选择的单元格也用grid排列
5、给格子添加点击事件,当格子被点击时,给对应的格子添加对应的样式,用来表示选中了该单元格(定位,根据盒子距离顶部左边宽高位置定位)
6、在被点击的格子中拿到该单元格的位置(此位置指距离视窗左边,上边的位置信息)
7、获取鼠标点击的位置到移动的位置的距离(鼠标点击、鼠标移动、鼠标离开的事件)
8、在鼠标移动时,通过移动的位置计算对应盒子展示的位置
拖拉拽案例代码实现:
<script setup> import { ref } from "vue" const title = [ //定义星期数据 "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日", ] const hours = [] //数组对象,生成左侧时间数据 for (let i = 0; i < 24; i++) { const t = i >= 10 ? i : `0${i}` //对个位数的时间补0 // 以半小时为一格 hours.push({ //给左侧时间列表push一个对象 text: `${t}:00`, //text文本对象时间 start: i * 2 + 2, //开始位置 end: i * 2 + 3, //结束位置 }) hours.push({ //后半个小时时间 text: `${t}:30`, start: i * 2 + 3, //开始位置 end: i * 2 + 4, //结束位置(单元格) }) } const blanks = [] //以半小时计算,一周的总格子数,数组总长度 blanks.length = 48 * 7 // 排课的数组 const classes = ref([]) //排课的课程数组 let container = null //值是整个grid表格div节点 const add = index => { //新增方法,传入格子索引(一共48*7个格子)(对应blanks) const row = Math.floor(index / 7) //拿到第几行,一共48行,第一行是0,拿到行数 const columns = index % 7 //拿到列数,一共7列,取余能拿到列数 if (!container) return //没有获取到grid节点结束点击事件 // 元素的宽度,高度,所在的屏幕位置 const conSize = container.getBoundingClientRect() //包含一个DOMRect对象(宽高和位置信息) const blockWidth = conSize.width / 8 const blockHeight = 40 const left = blockWidth * (columns + 1) const top = blockHeight * (row + 2) classes.value.push({ left, top, columns, row, blockWidth, blockHeight, rows: 1, resetRows: 0, }) } let current = { x: 0, y: 0 } /* 上下拖动事件 */ const mouseDown = (e, direction, c) => { //三个参数,事件对象、方向、排课的数组对象的参数 e.preventDefault() //先阻止默认事件 current = { x: e.clientX, y: e.clientY } //利用事件对象拿到鼠标点击的位置 const initRows = c.rows const initResetRow = c.resetRows const mouseMove = e => { // 相对于鼠标按下的位置,移动的距离 const dy = e.clientY - current.y const dr = Math.floor(dy / 40) if (direction > 0) { // 向上移动 const drt = -dr if (initResetRow + drt < 1) { return } c.resetRows = initResetRow + drt c.rows = initRows + drt } if (direction < 0) { // 向下移动 if (initRows + dr < 1) { return } c.rows = initRows + dr } } const mouseUp = () => { document.removeEventListener("mousemove", mouseMove) document.removeEventListener("mouseup", mouseUp) } document.addEventListener("mousemove", mouseMove) // 清空监听,处理结果 document.addEventListener("mouseup", mouseUp) } </script> <template> <!-- 下面这行代码是将ref这个值保存到container里 --> <div :ref="el => (container = el)"> <!-- 第一空白格 --> <div></div> <!-- 第二到第八格,内容标题是星期一到星期天 --> <div v-for="text in title">{{ text }}</div> <!-- 左侧时间格,在grid布局下,设置了左侧布局,因此,垂直于左侧 --> <div v-for="hour in hours" style="grid-column-start: 1; grid-column-end: 2" :style="{ // 从第几行开始,到第几行结束 gridRowStart: hour.start, gridRowEnd: hour.end, }"> {{ hour.text }} </div> <!-- 中心格子,点击传入格子索引 --> <div v-for="(_, index) in blanks" @click="add(index)"></div> <!-- 下面是循环出被点击选中的格子 --> <div v-for="(c, index) in classes" :key="index" :style="{ left: c.left + 'px', top: c.top - c.resetRows * 40 + 'px', width: c.blockWidth + 'px', height: c.blockHeight * c.rows + 'px', }"> <div> <div @mousedown="mouseDown($event, 1, c)"></div> <div @mousedown="mouseDown($event, -1, c)"></div> </div> </div> </div> </template> <style scoped> .class-container { position: relative; display: grid; height: 2000px; grid-template-columns: repeat(8, 1fr); grid-template-rows: 80px repeat(48, 40px); & > * { border: 1px solid #000; } .class-info { position: absolute; width: 200px; height: 200px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); background-color: white; .class-content { position: relative; height: 100%; width: 100%; .top-bar { background-color: #409eff; position: absolute; left: 0; top: 0; width: 100%; height: 10px; cursor: n-resize; } .bottom-bar { background-color: #67c23a; position: absolute; left: 0; bottom: 0; width: 100%; height: 10px; cursor: n-resize; } } } } </style>
步进拖拉拽注意事项:
基础版拖拉拽,仅供参考