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

165 lines
5.3 KiB
Vue
Raw Normal View History

<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>