Files
qhmes/jeecgboot-vue3/src/views/print/template/native/components/ElementWrapper.vue

165 lines
5.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="element-wrapper" :class="{ active }" :style="wrapperStyle" @pointerdown.stop="startDrag" @click.stop="emit('select', element.id)">
<slot />
<template v-if="active && resizable">
<span v-for="handle in handles" :key="handle" class="resize-handle" :class="`handle-${handle}`" @pointerdown.stop="startResize($event, handle)" />
</template>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { calcDragRect, calcResizeRect } from '../core/dragResize';
import type { NativeElement } from '../core/types';
const PX_PER_MM = 3.7795275591;
const props = defineProps<{
element: NativeElement;
active: boolean;
scale: number;
gridSize: number;
pageWidth: number;
pageHeight: number;
movable?: boolean;
resizable?: boolean;
dragBounds?: {
minX: number;
maxX: number;
minY: number;
maxY: number;
};
}>();
const emit = defineEmits<{
(e: 'update', payload: { id: string; patch: Partial<NativeElement> }): void;
(e: 'select', id: string): void;
(e: 'dragging', payload: { id: string; rect: { x: number; y: number; w: number; h: number }; active: boolean }): void;
}>();
const handles = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
const wrapperStyle = computed(() => ({
position: 'absolute',
left: `${props.element.x}mm`,
top: `${props.element.y}mm`,
width: `${props.element.w}mm`,
height: `${props.element.h}mm`,
zIndex: props.element.zIndex,
outline: props.active ? '1px solid #1677ff' : '1px dashed transparent',
userSelect: 'none',
}));
const movable = computed(() => props.movable !== false);
const resizable = computed(() => props.resizable !== false);
function clampByBounds(rect: { x: number; y: number; w: number; h: number }) {
const bounds = props.dragBounds;
if (!bounds) return rect;
return {
...rect,
x: Math.max(bounds.minX, Math.min(bounds.maxX, rect.x)),
y: Math.max(bounds.minY, Math.min(bounds.maxY, rect.y)),
};
}
function startDrag(event: PointerEvent) {
emit('select', props.element.id);
if ((event.target as HTMLElement)?.classList.contains('resize-handle')) return;
if (!movable.value) return;
const start = { x: props.element.x, y: props.element.y, w: props.element.w, h: props.element.h };
const startX = event.clientX;
const startY = event.clientY;
const onMove = (moveEvent: PointerEvent) => {
// 鼠标位移是 px画布坐标是 mm这里必须做单位换算才会跟手
const deltaX = (moveEvent.clientX - startX) / props.scale / PX_PER_MM;
const deltaY = (moveEvent.clientY - startY) / props.scale / PX_PER_MM;
const next = calcDragRect(start, { width: props.pageWidth, height: props.pageHeight }, deltaX, deltaY, props.gridSize);
const bounded = clampByBounds(next);
emit('update', { id: props.element.id, patch: bounded });
emit('dragging', { id: props.element.id, rect: bounded, active: true });
};
const onUp = () => {
window.removeEventListener('pointermove', onMove);
window.removeEventListener('pointerup', onUp);
emit('dragging', { id: props.element.id, rect: { x: props.element.x, y: props.element.y, w: props.element.w, h: props.element.h }, active: false });
};
window.addEventListener('pointermove', onMove);
window.addEventListener('pointerup', onUp);
}
function startResize(event: PointerEvent, direction: any) {
emit('select', props.element.id);
if (!resizable.value) return;
const start = { x: props.element.x, y: props.element.y, w: props.element.w, h: props.element.h };
const startX = event.clientX;
const startY = event.clientY;
const onMove = (moveEvent: PointerEvent) => {
// 鼠标位移是 px画布尺寸是 mm缩放时同样要按单位换算
const deltaX = (moveEvent.clientX - startX) / props.scale / PX_PER_MM;
const deltaY = (moveEvent.clientY - startY) / props.scale / PX_PER_MM;
const next = calcResizeRect(direction, start, { width: props.pageWidth, height: props.pageHeight }, deltaX, deltaY, props.gridSize);
emit('update', { id: props.element.id, patch: next });
};
const onUp = () => {
window.removeEventListener('pointermove', onMove);
window.removeEventListener('pointerup', onUp);
};
window.addEventListener('pointermove', onMove);
window.addEventListener('pointerup', onUp);
}
</script>
<style scoped lang="less">
.element-wrapper {
box-sizing: border-box;
.resize-handle {
position: absolute;
width: 6px;
height: 6px;
background: #1677ff;
border-radius: 50%;
margin: -3px;
}
.handle-nw {
left: 0;
top: 0;
cursor: nwse-resize;
}
.handle-n {
left: 50%;
top: 0;
cursor: ns-resize;
}
.handle-ne {
left: 100%;
top: 0;
cursor: nesw-resize;
}
.handle-e {
left: 100%;
top: 50%;
cursor: ew-resize;
}
.handle-se {
left: 100%;
top: 100%;
cursor: nwse-resize;
}
.handle-s {
left: 50%;
top: 100%;
cursor: ns-resize;
}
.handle-sw {
left: 0;
top: 100%;
cursor: nesw-resize;
}
.handle-w {
left: 0;
top: 50%;
cursor: ew-resize;
}
}
</style>