init project
This commit is contained in:
130
src/utils/directives/Draggable.ts
Normal file
130
src/utils/directives/Draggable.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import type { Directive, DirectiveBinding } from "vue";
|
||||
|
||||
export type DraggableConfig = {
|
||||
|
||||
/** 点下 */
|
||||
onMouseDown?: (e: MouseEvent | TouchEvent) => void;
|
||||
|
||||
/** 中断条件(返回 true 时不触发 onDragging 及往后事件) */
|
||||
interruptWhen?: (e: MouseEvent | TouchEvent) => boolean;
|
||||
|
||||
/** 拖动 */
|
||||
onDragging: (e: MouseEvent | TouchEvent, relX: number, relY: number, offsetX: number, offsetY: number) => void;
|
||||
|
||||
/** 释放 */
|
||||
onDragged?: (e: MouseEvent | TouchEvent, x: number, y: number) => void;
|
||||
}
|
||||
|
||||
const getEventPosition = (e: MouseEvent | TouchEvent): { clientX: number, clientY: number, pageX: number, pageY: number } => {
|
||||
if ("touches" in e && e.touches.length > 0) {
|
||||
const touch = e.touches[0];
|
||||
return {
|
||||
clientX: touch.clientX,
|
||||
clientY: touch.clientY,
|
||||
pageX: touch.pageX,
|
||||
pageY: touch.pageY
|
||||
};
|
||||
} else if (e instanceof MouseEvent) {
|
||||
return {
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
pageX: e.pageX,
|
||||
pageY: e.pageY
|
||||
};
|
||||
}
|
||||
return { clientX: 0, clientY: 0, pageX: 0, pageY: 0 };
|
||||
};
|
||||
|
||||
const VDraggable: Directive = {
|
||||
// 挂载
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding<DraggableConfig>) {
|
||||
const config = binding.value as DraggableConfig;
|
||||
let isClicked = false, ox = 0, oy = 0, ol = 0, ot = 0, opx = 0, opy = 0;
|
||||
|
||||
// 按下
|
||||
const handleStart = (e: MouseEvent | TouchEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const pos = getEventPosition(e);
|
||||
ox = pos.clientX;
|
||||
oy = pos.clientY;
|
||||
ol = el.offsetLeft;
|
||||
ot = el.offsetTop;
|
||||
opx = pos.pageX;
|
||||
opy = pos.pageY;
|
||||
|
||||
config.onMouseDown?.(e);
|
||||
if (config.interruptWhen?.(e)) return;
|
||||
|
||||
isClicked = true;
|
||||
};
|
||||
// 移动
|
||||
const handleMove = (e: MouseEvent | TouchEvent) => {
|
||||
if (!isClicked) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
const pos = getEventPosition(e);
|
||||
const relX = pos.clientX - (ox - ol);
|
||||
const relY = pos.clientY - (oy - ot);
|
||||
const offsetX = pos.pageX - opx;
|
||||
const offsetY = pos.pageY - opy;
|
||||
|
||||
config.onDragging(e, relX, relY, offsetX, offsetY);
|
||||
};
|
||||
// 释放
|
||||
const handleEnd = (e: MouseEvent | TouchEvent) => {
|
||||
if (!isClicked) {
|
||||
return;
|
||||
}
|
||||
const pos = getEventPosition(e);
|
||||
config.onDragged?.(e, pos.clientX - ox, pos.clientY - oy);
|
||||
isClicked = false;
|
||||
};
|
||||
|
||||
(el.style as any)["user-drag"] = "none";
|
||||
(el.style as any)["touch-action"] = "none";
|
||||
|
||||
// 鼠标
|
||||
el.addEventListener("mousedown", handleStart as EventListener);
|
||||
document.addEventListener("mousemove", handleMove as EventListener);
|
||||
document.addEventListener("mouseup", handleEnd as EventListener);
|
||||
|
||||
// 触控
|
||||
el.addEventListener("touchstart", handleStart as EventListener, { passive: false });
|
||||
document.addEventListener("touchmove", handleMove as EventListener, { passive: false });
|
||||
document.addEventListener("touchend", handleEnd as EventListener, { passive: false });
|
||||
|
||||
// 保存事件处理器以便卸载时使用
|
||||
(el as any)._vDraggableHandlers = {
|
||||
handleStart,
|
||||
handleMove,
|
||||
handleEnd
|
||||
};
|
||||
},
|
||||
// 卸载
|
||||
unmounted(el: HTMLElement) {
|
||||
const handlers = (el as any)._vDraggableHandlers as {
|
||||
handleStart: EventListener,
|
||||
handleMove: EventListener,
|
||||
handleEnd: EventListener
|
||||
};
|
||||
if (handlers) {
|
||||
// 鼠标
|
||||
el.removeEventListener("mousedown", handlers.handleStart);
|
||||
document.removeEventListener("mousemove", handlers.handleMove);
|
||||
document.removeEventListener("mouseup", handlers.handleEnd);
|
||||
|
||||
// 触控
|
||||
el.removeEventListener("touchstart", handlers.handleStart);
|
||||
document.removeEventListener("touchmove", handlers.handleMove);
|
||||
document.removeEventListener("touchend", handlers.handleEnd);
|
||||
|
||||
// 引用
|
||||
delete (el as any)._vDraggableHandlers;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default VDraggable;
|
||||
120
src/utils/directives/Popup.ts
Normal file
120
src/utils/directives/Popup.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import type { Directive, DirectiveBinding } from "vue";
|
||||
import Toolkit from "../Toolkit";
|
||||
|
||||
export enum PopupType {
|
||||
TEXT,
|
||||
IMG,
|
||||
HTML,
|
||||
EL
|
||||
}
|
||||
|
||||
/** */
|
||||
export type PopupConfig = {
|
||||
|
||||
type: PopupType,
|
||||
value?: string | HTMLElement;
|
||||
canShow?: () => boolean;
|
||||
beforeShow?: (type: PopupType, value: string | HTMLElement) => Promise<void>;
|
||||
afterHidden?: (type: PopupType, value: string | HTMLElement) => Promise<void>;
|
||||
}
|
||||
|
||||
// Popup 弹出提示 DOM 节点,全局唯一
|
||||
let popup: HTMLElement | null;
|
||||
|
||||
const VPopup: Directive = {
|
||||
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding<PopupConfig>) {
|
||||
// 转配置
|
||||
let config: PopupConfig;
|
||||
if (binding.arg && binding.arg === "config") {
|
||||
config = binding.value as PopupConfig;
|
||||
} else {
|
||||
config = {
|
||||
type: PopupType.TEXT,
|
||||
value: binding.value as any as string,
|
||||
canShow: () => true
|
||||
};
|
||||
}
|
||||
// Popup 节点
|
||||
if (!popup) {
|
||||
popup = document.getElementById("tui-popup");
|
||||
}
|
||||
let isShowing = false;
|
||||
// 显示
|
||||
el.addEventListener("mouseenter", async e => {
|
||||
if (!config.value) {
|
||||
console.warn("not found popup value", config);
|
||||
return;
|
||||
}
|
||||
if (config.beforeShow) {
|
||||
await config.beforeShow(config.type, config.value);
|
||||
}
|
||||
if (config.canShow && config.canShow() && popup) {
|
||||
let el: HTMLElement | null = null;
|
||||
if (!config) {
|
||||
el = document.createElement("div");
|
||||
el.className = "text";
|
||||
el.textContent = config as string;
|
||||
popup.appendChild(el);
|
||||
}
|
||||
switch (config.type) {
|
||||
case PopupType.TEXT:
|
||||
// 文本
|
||||
el = document.createElement("div");
|
||||
el.className = "text";
|
||||
el.textContent = config.value as string;
|
||||
popup.appendChild(el);
|
||||
break;
|
||||
case PopupType.IMG:
|
||||
// 图片
|
||||
el = document.createElement("img");
|
||||
(el as HTMLImageElement).src = config.value as string;
|
||||
popup.appendChild(el);
|
||||
break;
|
||||
case PopupType.HTML:
|
||||
// HTML 字符串
|
||||
popup.appendChild(Toolkit.toDOM(config.value as string));
|
||||
break;
|
||||
case PopupType.EL:
|
||||
// DOM 节点
|
||||
if (config.value instanceof HTMLElement) {
|
||||
const valueEl = config.value as HTMLElement;
|
||||
valueEl.style.display = "block";
|
||||
popup.appendChild(valueEl);
|
||||
break;
|
||||
} else {
|
||||
console.error(config);
|
||||
throw new Error("Vue 指令错误:v-popup:el 的值不是 HTML 元素");
|
||||
}
|
||||
}
|
||||
popup.style.left = (e.x + 20) + "px";
|
||||
popup.style.top = (e.y + 14) + "px";
|
||||
popup.style.visibility = "visible";
|
||||
isShowing = true;
|
||||
}
|
||||
}, false);
|
||||
// 移动
|
||||
el.addEventListener("mousemove", async (e) => {
|
||||
if (config.canShow && config.canShow() && isShowing && popup) {
|
||||
popup.style.left = (e.x + 20) + "px";
|
||||
popup.style.top = (e.y + 14) + "px";
|
||||
}
|
||||
}, false);
|
||||
// 隐藏
|
||||
el.addEventListener("mouseleave", async () => {
|
||||
if (popup) {
|
||||
popup.style.visibility = "hidden";
|
||||
popup.innerText = "";
|
||||
popup.style.left = "0px";
|
||||
popup.style.top = "0px";
|
||||
|
||||
// 隐藏后事件
|
||||
if (config.afterHidden && config.value) {
|
||||
await config.afterHidden(config.type, config.value);
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
};
|
||||
|
||||
export default VPopup;
|
||||
Reference in New Issue
Block a user