К счастью, я инкапсулировал компоненты, которые использовал и которых может не быть в стандартной библиотеке компонентов, в небольшую библиотеку компонентов.startЭто больше сотни,vue-gn-components, следующим шагом будет шаг за шагом обогащать этот проект~. Ждем всехstart~, что также является движущей силой для меня, чтобы продолжать обогащать эту библиотеку компонентов!
Особая благодарность за этот буклет,Знакомство с компонентами Vue.js, помощь огромная, а автор буклетаiViewАвтор, заслуживаешь доверия!
Прежде всего, первое дополнение — это компонент перетаскивания, функция очень проста, то есть пусть выходит рендеринг.domможно перетащить. Что касается конкретногоdomчто, этому компоненту все равно, используйтеslotПрими это, просто запихни это внутрь себя.
vueПо назначению компоненты можно разделить на три категории (сложность разработки возрастает по порядку):
- Компоненты отображения: то есть те, которые восстанавливают эскизы дизайна в бизнес-разработке, отображают информацию на странице и используют
routerвыключатель. - Бизнес-компоненты: Компоненты, извлеченные из бизнес-пакета текущей компании, не обладают сильной универсальностью.
- Независимые компоненты: не для конкретного бизнеса, такие как даты, формы, то есть те, что в стандартной библиотеке компонентов, которые очень универсальны.
vueинтерфейс компонента
Существует три типа интерфейсов компонентов:props, пользовательские события, слоты. То есть рассказать другим, как использовать ваш компонент, поэтому компонент должен планировать эти три вещи в начале дизайна: пользователи привыкли к тому, что вы добавляете функции, но они не будут привыкать к тому, что вы меняете интерфейс. Этот компонент перетаскивания разработан следующим образом:
-
DragWrap:<组件>Два компонента разработаны. Компонент самого внешнего контейнера готовDomдвижение и прочая логика. -
DragItem:<组件>Для элемента, который необходимо перетащить, информация о перетаскивании отправляется компоненту-контейнеру. -
data:<props>Получите массив, перетащите данные рендеринга, соответствующие компоненту, а затем перетащитеDomИзменено, исходный визуализированный массив также необходимо изменить. Например, вы можете указать фону, что при следующем входе он будет отображаться в соответствии с измененными данными. -
watchData:<事件>Распространяйте измененные иDomОднозначное соответствие исходным данным. -
drag:<具名插槽>Если вы не пишете именованный слот, вы можете перетащить весь перетаскиваемый элемент, щелкнув по нему, в противном случае можно перетащить только именованный слот.DomДля управления перетаскиванием всего элемента.
Реализовать этапы перетаскивания компонентов
1. Перетащите, чтобы изменить текущий
Domпорядок.
2. После завершения перетаскивания измененные данные отправляются.
3. Завершите интерфейс слота и взаимодействие.
1. Перетащите, чтобы изменить текущийDomпорядок
1.1 Введение в события и свойства перетаскивания
h5событие перетаскивания
标记: Это очень важно!!!Не знаю,почему многие не говорят про перетаскивание,т.е вышеперечисленноеgifЖелтое начало координат на дисплее, его смещение определяет поведение события перетаскивания. После щелчка, чтобы начать перетаскивание, место щелчка мыши становится маркером.
dragstart: ↓ выполняется при щелчке и перемещении мыши. ↓
drag:↓ВdragstartПосле выполнения мышь непрерывно срабатывает при движении. ↓
dragend:↓ Запускается, когда поведение перетаскивания заканчивается, то есть когда мышь отпускают. ↓
dragenter:↓ Когда тег перетаскиваемого элемента входит в определенноеDomЗапускается при срабатывании элемента, сам срабатывает первым. вошелDomЭлемент запускает это событие. ↓
dragover: Когда тег перетаскиваемого элемента находится во входящемDomСрабатывает при перемещении элемента на него, а также при перемещении элемента на себя.
dragleave:↓ Когда перетаскиваемый элемент уходит и входитDomсрабатывает когда. ↓
h5свойство перетаскивания
draggable: когда элемент необходимо перетащить, для него необходимо установитьtrue, По умолчаниюfalse. Выбранный текст, изображения и ссылки по умолчанию можно перетаскивать.
DataTransfer对象: это свойство используется для сохранения перетаскиваемых данных и информации о взаимодействии.Этот компонент не используется и пока будет игнорироваться.
1.2 Написание компонентов
Через приведенное выше понимание событий мы думаем об этом, нам нужно только прослушать три событияdragstart,dragenter,dragend. Вам нужно знать, кем был элемент, когда началось перетаскивание, к какому элементу он перешел после перетаскивания и в конце последнего перетаскивания. Поскольку каждый перетаскиваемый элемент является компонентом, эти три события запускаются каждый раз при перетаскивании элемента. Итак, пишем следующий код:
drag-item.vue
<template>
<div
@dragstart.stop="onDragstart" // 拖拽开始时
@dragenter.stop="onDragenter" // 拖拽进入当前组件时
@dragend.stop="onDragend" // 拖拽结束时
draggable // 可以拖拽
class="__drag_item"
>
<slot />
</div>
</template>
<script>
import Emitter from "../../mixins/emitter";
export default {
name: "DragItem",
mixins: [Emitter],
mounted() {
this.dispatch("DragWrap", "putChild", this.$el); // this.$el为当前组件实例对应的真实Dom。
// 触发DragWrap这个组件上的putChild方法,参数是当前组件的真实Dom。
},
methods: {
onDragstart() {
this.$el.style.opacity = "0.3";
this.dispatch("DragWrap", "dragstart", this.$el); // 触发dragstart
},
onDragenter() {
this.dispatch("DragWrap", "dragenter", this.$el); // 触发dragenter
},
onDragend() {
this.$el.style.opacity = "1";
this.dispatch("DragWrap", "dragend"); // 触发dragend
}
}
};
</script>
Это может показаться немного запутанным, вот объяснениеEmitterтакойmixin, также изiViewвнутриcopyДа, именно внедрение двух методов часто используется в библиотеке компонентов, потому что независимые компоненты использоваться не будут.vuexилиbusЧтобы общаться, поэтому межкомпонентная связь должна иметь свою собственную работу.
Я объясню это здесьvueПринцип пользовательских событий, родительский компонент проходитthis.$onЗарегистрируйте событие в центре событий субкомпонента, и субкомпонент пройдет проверку.this.$emitИнициировать событие своего собственного центра событий, но, поскольку инициированное событие находится в области действия родительского компонента, взаимодействие пользовательского события между родителем и дочерним элементом завершается, фактически дочерний компонент воспроизводит сам себя. Простое понимание заключается в следующем:
<template>
<button @click="onClick">btn</button>
</template>
<script>
export default {
created() {
this.$on("testEvent", sayHi => {
alert(sayHi);
});
},
methods: {
onClick() {
this.$emit("testEvent", "hello Vue~");
}
}
};
</script>
Следующие два методаbroadcastиdispatchИх принцип заключается в том, чтобы найти экземпляр целевого компонента в текущем компоненте, но один из них не работает, а другой работает. затем пройтиthis.$emitДля запуска целевой компонент прошелthis.$onЗарегистрированные события, поэтому связь между компонентами может быть завершена, а способ поиска компонентов определяется компонентами.nameАтрибуты.
function broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
const name = child.$options.name;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root;
let name = parent.$options.name;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.name;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
Первая статья будет немного многословной, там действительно много независимых компонентов, которые нужно сначала объяснить. Далее пишем следующееDragWrapКод компонента:
drag-wrap.vue
<template>
<div ref="wrap" @dragenter.prevent @dragover.prevent> // 阻止浏览器默认行为,不然会显示一个叉叉,不好看
<slot />
</div>
</template>
<script>
export default {
name: "DragWrap", // 组件名,很重要!
created() {
this.toDom = ""; // 拖拽时进入的元素
this.fromDom = ""; // 拖拽起始的元素
this.children = []; // 存放所有子组件元素的集合,之后说明用途
this.$on("dragstart", this.onDragstart); // 子组件会$emit触发dragstart,所以要先注册
this.$on("dragenter", this.onDragenter); // 子组件会$emit触发dragenter,所以要先注册
this.$on("dragend", this.onDragend); // 子组件会$emit触发dragend,所以要先注册
this.$on("putChild", child => { // 这里的child对应的是子组件的this.$el
this.children.push(child); // 将所有的子组件的Dom元素收集起来
});
},
methods: {
onDragstart(el) {
this.fromDom = el; // 记录拖拽时开始的元素
},
onDragenter(el) {
this.toDom = el; // 因为拖拽会不停的触发enter事件,所以进入的哪个元素也要记录下来
if (this.fromDom === this.toDom) {
return;
}
},
onDragend() {}
}
};
</script>
Вот несколько моментов, которые следует отметить в первую очередь,this.$onдолжно быть лучше, чемthis.$emitВыполните сначала, потому что вы должны сначала зарегистрироваться, чтобы быть запущенным, иначе событие будет запущено. Существует также порядок выполнения хуков родительского и дочернего компонентов,mountedзаключается в том, что подкомпонент выполняется первым,createdЭто родительский компонент, который выполняется первым.
Хорошо, тогда у нас есть элементы, которые нужно начать перетаскивать, и элементы, которые входят, а затем начинаем перетаскивать и использоватьinsertBeforeПросто поменяйтесь местами. Тем не менее, примечание здесь состоит в том, чтобы знать, перетаскивается ли текущий перетаскиваемый элемент вперед или назад, поэтому мы находимся вDragWrapДобавьте в компонент следующий код:
drag-wrap.vue
...
methods: {
onDragenter(el) {
this.toDom = el;
if (this.fromDom === this.toDom) {
return;
}
if(this.isPrevNode(this.fromDom, this.toDom)) { // 判断进入节点是否在起始节点的前面
this.$refs["wrap"].insertBefore(this.fromDom, this.toDom);
// 将起始节点插入到进入节点的前面
} else { // 否则就是在之后
this.$refs["wrap"].insertBefore(this.fromDom, this.toDom.nextSibling);
// 将起始节点插入到进入节点下一个兄弟节点的前面
}
},
isPrevNode(from, to) { // to是否在from的前面
while(from.previousSibling !== null) {
if(from.previousSibling === to) {
return true;
}
from = from.previousSibling;
}
}
}
...
2. После завершения перетаскивания измененные данные отправляются.
После написания приведенного выше кода элементы теперь можно перетаскивать и переключать, как мы и ожидали.Domположение, но этого недостаточно,DomЕсли порядок изменен, вам также необходимо знать, как должны выглядеть соответствующие данные, иначе это будет бессмысленно, если вы обновите страницу.
2.1 Сравнение двух деревьев Домов
Помните, когда мы былиcreatedопределено вthis.children = []Ну, он содержит все настоящие компоненты перетаскиванияDomэлемент, но на этот раз он был нарушен перетаскиванием. ↓
В это время нам нужно знать реальный порядок
DomКак насчет дерева, а то с этим побеспокоилиDomСделайте сравнение, чтобы вычислить, какой порядок соответствующего массива перемешивается, поэтому мы находимся вDragWrapДобавьте в компонент следующий код:
drag-wrap.vue
...
methods: {
onDragend() {
if (!this.data.length) return;
const realDomOrder = [...this.$el.children].filter(child => //获取真实的Dom树
child.classList.contains("__drag_item")
);
this.getDataOrder(realDomOrder, this.children); // 对比两颗树
},
getDataOrder(realList, dragAfterList) {
const order = realList.map(realItem => { // 拿到打乱Dom树对应的序号
return dragAfterList.findIndex(dragItem => realItem === dragItem);
});
const newData = [];
order.forEach((item, i) => { // 将原数组的数据按照打乱的序号赋值给新数组
newData[i] = this.data[item];
});
this.$emit("watchData", newData); // 新数组的顺序就对应打乱Dom的序号,派发出去
}
}
...
3. Завершите интерфейс слота и взаимодействие.
3.1 Полный интерфейс именованных слотов
Перетащите весьdrag-itemЛюбое положение компонента можно перетаскивать, но иногда пользователь хочет определить положение, в котором может срабатывать перетаскивание, поэтому нам нужно предоставить пользователю этот интерфейс, а затемDragItemВнесите следующие изменения внутри:
<template>
<div
@dragstart.stop="onDragstart"
@dragenter.stop="onDragenter"
@dragend.stop="onDragend"
:draggable="!$slots.drag || isDrag" // 如果有设置具名插槽,当前整个不能被拖拽
:style="{cursor: !$slots.drag ? 'move': ''}" // 具名插槽决定这个组件的交互手势
class="__drag_item"
>
<slot name="drag" /> // 提供一个具名插槽drag
<slot />
</div>
</template>
export default {
data() {
return {
isDrag: false
};
},
mounted() {
if(this.$slots.drag) { // 如果有定义具名插槽drag
this.setSlotAttr();
}
this.dispatch("DragWrap", "putChild", this.$el);
},
methods: {
setSlotAttr() {
const slotVNode = this.$slots.default.find( // 找到vnode的第一个有效节点
vnode => !vnode.data && vnode.text !== " "
);
const dragDom = slotVNode.elm.previousSibling;
// 具名插槽对应的真实Dom
if (dragDom.previousSibling !== null) {
// 规定具名插槽内只能有一个根元素,否则报错~
throw "具名插槽内只能有一个根节点~";
}
dragDom.addEventListener("mouseenter", () => { // 进入具名插槽的Dom,设置可拖动
this.isDrag = true;
});
dragDom.addEventListener("mouseleave", () => { // 离开具名插槽的Dom,设置不可拖动
this.isDrag = false;
});
dragDom.style.cursor = "move"; // 手势变为可移动
}
}
}
не знаю почему,vueСоответствующий слот по умолчанию может напрямую получить реальныйDom, и названный слот не может быть получен, что немного похоже на яму~ Здесь мы используем такой менее элегантный способ получить его,slotVNode.elm.previousSibling, про тест не влияет на использование.
Затем оговариваем, что в именованном слоте может быть только один корневой элемент, иначе установленные ниже свойства могут работать только на один элемент.
3.2 Завершите взаимодействие
обменDomположение, есть10%Встряхни его~
<style scoped>
.__drag_item {
animation: shake .3s;
}
@keyframes shake {
0% {
transform: translate3d(-10%, 0, 0);
}
50% {
transform: translate3d(10%, 0, 0);
}
100% {
transform: translate3d(0, 0, 0);
}
}
</style>
Установка компонента
npm i vue-gn-components
import { DragWrap, DragItem } from 'vue-gn-components';
import "vue-gn-components/lib/style/index.css";
Vue.use(DragWrap).use(DragItem)
вызов компонента
<template>
<drag-wrap class="wrap" :data="list" @watchData="watchData">
<drag-item class="item" v-for="(item, index) in list" :key="index">
<template #drag>
<div>拖拽Dom</div>
</template>
<div>{{item}}</div>
</drag-item>
</drag-wrap>
</template>
export default {
data() {
return {
list: [111, 222, 333, 444, 555, 666, 777, 888, 999]
};
},
methods: {
watchData(newList) {
console.log("newList", newList);
}
}
}
Наконец
Расположение исходного кода >>>vue-gn-components, думаю, все в порядке, пожалуйста, дайте
startДавай~ Другие существующие компоненты >>>Библиотека функциональных компонентов vue, которую вы можете использовать, и она постоянно совершенствуется...