目录
一、效果展示
二、代码分析
主页面代码:
<template>
<div class="board">
<div class="left">
<CoursePerson
v-for="({ name }, k) in datas.courses"
:key="k"
:courseName="name"
:courseKey="k"
@handleDragEnd="handleDragEnd"
></CoursePerson>
</div>
<div class="right" @dragover="handleDragOver" @dragenter="handleDragEnter">
<table border="1">
<tr>
<th>时间段 / 星期</th>
<th v-for="(w, i) in datas.weekday" :key="i">{{ w }}</th>
</tr>
<tr v-for="(t, i) in datas.time_slot" :key="i">
<th>{{ t }}</th>
<td v-for="n in 7" :key="n" :data-weekday="n" :data-time-slot="i">
<template v-if="cellData[`${n}-${i}`]">
<CoursePerson
:courseName="datas.courses[cellData[`${n}-${i}`]].name"
:courseKey="cellData[`${n}-${i}`]"
v-CoursePanel="{ cellData, n, timeSlot: i }"
></CoursePerson>
</template>
</td>
</tr>
</table>
</div>
</div>
</template>
<script>
import CoursePerson from "@/components/CoursePerson.vue";
export default {
components: {
CoursePerson,
},
directives: {
// 定义名为CoursePanel的指令,指向一个配置对象
CoursePanel: {
bind(el, bindings) {
const oRemoveBtn = el.querySelector(".remove-btn");
const { cellData, n, timeSlot } = bindings.value;
oRemoveBtn.addEventListener("click", handleRemovePanel, false);
function handleRemovePanel() {
delete cellData[`${n}-${timeSlot}`];
}
},
},
},
data() {
return {
datas: {
weekday: [
"星期一",
"星期二",
"星期三",
"星期四",
"星期五",
"星期六",
"星期日",
],
time_slot: [
"08:00-08:50",
"09:00-09:50",
"10:00-10:50",
"11:00-11:50",
"14:00-14:50",
"15:00-15:50",
"16:00-16:50",
"17:00-17:50",
],
courses: {
chinese: {
name: "语文",
teachers: ["张三", "李四"],
},
math: {
name: "数学",
teachers: ["张二", "李五"],
},
english: {
name: "英语",
teachers: ["张3", "李2"],
},
},
},
cellData: {
"1-1": "chinese",
"2-5": "math",
"3-3": "english",
},
targetCell: null,
};
},
methods: {
handleDragOver(e) {
e.preventDefault();
},
handleDragEnter(e) {
e.preventDefault();
const tar = e.target;
const tagName = tar.tagName.toLowerCase();
this.targetCell = tagName != "td" ? null : tar;
},
handleDragEnd(e) {
if (this.targetCell) {
const weekday = this.targetCell.dataset.weekday;
const timeSlot = this.targetCell.dataset.timeSlot;
const prop = `${weekday}-${timeSlot}`;
if (!this.cellData[prop]) {
//给表格中没有坐标的课程
const key = e.dataset.key;
this.$set(this.cellData, prop, key); //借助$set赋值,更新视图
// this.cellData[prop] = key; //赋值(vue2赋值视图不更新)
}
}
},
},
};
</script>
<style lang='less' scoped>
.board {
position: relative;
.left {
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 100%;
}
.right {
width: 100%;
height: 800px;
padding-left: 300px;
box-sizing: border-box;
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
td {
height: 60px;
text-align: center;
}
}
}
}
</style>
卡片组件代码:
<template>
<!-- draggable="true" 该盒子可被拖动 -->
<div
class="course-panel"
draggable="true"
@dragstart="handleDragStart"
@dragend="handleDragEnd"
:data-key="courseKey"
>
<h1>{{ courseName }}</h1>
<span class="remove-btn">x</span>
</div>
</template>
<script>
export default {
props: ["courseName", "courseKey"],
methods: {
handleDragStart(e) {
const tar = e.target;
tar.style.opacity = ".6";
},
handleDragEnd(e) {
const tar = e.target;
tar.style.opacity = "1";
this.$emit("handleDragEnd", tar);
},
},
};
</script>
<style lang='less' scoped>
.course-panel {
position: relative;
width: 100px;
height: 60px;
background-color: goldenrod;
margin: 10px auto;
padding: 10px;
box-sizing: border-box;
cursor: move;
h1 {
font-size: 20px;
text-align: center;
}
.remove-btn {
position: absolute;
top: 0px;
right: 10px;
}
}
</style>
2.1、表格编写与课程卡片
静态页面主要采用两栏布局。左侧为卡片组件,右侧为表格。
2.2、初始化数据与渲染
td是单元格,现在里面是一个7X7的表格,想要将指定课程的卡片渲染上去,我们可以借助cellData变量里的值,类似二维数组,我们找到坐标,就可以渲染上去。
2.3、拖拽卡片到表格,进行插入
拖拽效果:draggable="true" 意味着该盒子可被拖动,包括很多事件,比如:dragstart、dragend、dragover、dragenter等。
插入实现:看似是将左侧的卡片拖进去,实则是在进入表格时(拖拽结束)进行了数据的改动,就是给cellData这个对象里继续增加一个属性和属性值,达到插入效果。vue2里直接复制,视图不会变化,可以通过$set进行视图更新,this.$set(this.cellData, prop, key);
2.4、自定义指令进行删除
本例中通过视图中 v-CoursePanel="{ cellData, n, timeSlot: i }",结合directives里的CoursePanel指令进行表格内卡片的删除。这里的删除指删除表格内的任意卡片,而不能删除左侧的原始卡片。所以组件结合自定义指令去进行删除操作。
现在它删除有个bug,点击x删除,需要再次拖拽(添加)时,才会看到删除的卡片效果,尚待完善。。。
三、Js实现列表拖拽排序
3.1、效果展示
3.2、代码分析
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
ul {
padding: 0;
margin: 0;
list-style: none;
}
p {
margin: 0;
}
.draggable-list-wrapper {
width: 500px;
margin: 50px auto;
box-shadow: 1px 3px 5px #999;
background-color: white;
padding: 10px 30px 30px;
}
.draggable-item {
height: 50px;
line-height: 50px;
border: 1px solid skyblue;
padding: 0 15px;
margin-top: 20px;
background-color: aquamarine;
}
.dragging {
opacity: 0;
}
</style>
</head>
<body>
<div class="draggable-list-wrapper"></div>
</body>
<script>
const listData = ['11111', '22222', '33333', '44444'];
(() => {
const oWrapper = document.querySelector('.draggable-list-wrapper')
const init = () => {
render();
bindEvent();
};
function bindEvent() {
const oDraggableList = oWrapper.querySelector('.draggable-list')
const oDraggableItems = oDraggableList.querySelectorAll('.draggable-item')
oDraggableList.addEventListener('dragover', handleDragOver, false);
window.addEventListener('dragover', (e) => e.preventDefault(), false);
oDraggableList.addEventListener('dragenter', (e) => e.preventDefault(), false);
window.addEventListener('dragenter', (e) => e.preventDefault(), false);
oDraggableItems.forEach(item => {
item.addEventListener('dragstart', handleDragStart, false);
item.addEventListener('dragend', handleDragEnd, false);
})
}
function handleDragStart() {
const item = this;
setTimeout(() => item.classList.add('dragging'), 0)
}
function handleDragEnd() {
const item = this;
item.classList.remove('dragging')
}
function handleDragOver(e) {
e.preventDefault();
const oDraggableList = this;
const oDraggableItem = oDraggableList.querySelector('.dragging')
const oSibItems = oDraggableList.querySelectorAll('.draggable-item:not(.dragging)')
// 在拖拽时,自身即将到达要插入对象的一半位置时,就将自身插入到某对象之前
const oSibItem = [...oSibItems].find(item => e.clientY <= item.offsetTop + item.offsetHeight / 2);
oDraggableList.insertBefore(oDraggableItem, oSibItem)
}
function createList() {
const oDraggableList = document.createElement('ul')
oDraggableList.className = 'draggable-list';
listData.forEach(item => {
const oItem = document.createElement("li");
oItem.className = 'draggable-item';
oItem.draggable = true;
oItem.innerHTML = `<p>${item}</p>`;
oDraggableList.appendChild(oItem);
console.log(oItem, '111', oDraggableList);
})
console.log(oDraggableList.outerHTML, '222');
return oDraggableList;
}
function render() {
const oList = createList();
oWrapper.appendChild(oList);
}
init()
})();
</script>
</html>
页面初始化时,执行render()和bindEvent()函数,前者是渲染出一个带有内容的Li列表,将其追加(x.appendChild())在主体盒子上.
拖拽插入思想:
在handleDragOver函数里:自身即将到达要插入对象的一半位置(e.clientY <= item.offsetTop + item.offsetHeight / 2)时,就将自身插入(x.insertBefore())到某对象之前.