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>步进拖拉拽注意事项:
基础版拖拉拽,仅供参考