feat: 基础拖拽功能实现
parent
ed7a38bbdb
commit
fc66f96be2
@ -1,6 +1,10 @@
|
|||||||
|
<!-- eslint-disable vue/html-indent -->
|
||||||
<template>
|
<template>
|
||||||
<div class="flex justify-center items-center pt-2">
|
<div class="flex justify-center items-center pt-2">
|
||||||
<div> Copyright (c) 2022</div>
|
<div> Copyright (c) 2022</div>
|
||||||
<a class="mx-2 hover:text-blue-400" href="#" target="_blank">咬轮猫</a></div
|
<a class="mx-2 hover:text-blue-400" href="https://github.com/yaolunmao" target="_blank"
|
||||||
|
>咬轮猫</a
|
||||||
|
></div
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
|
<script lang="ts" setup></script>
|
||||||
|
@ -1,3 +1,181 @@
|
|||||||
|
<!-- eslint-disable vue/html-indent -->
|
||||||
<template>
|
<template>
|
||||||
<div>中间</div>
|
<div
|
||||||
|
class="canvas"
|
||||||
|
@drop="dropEvent"
|
||||||
|
@dragenter="dragEnterEvent"
|
||||||
|
@dragover="dragOverEvent"
|
||||||
|
@mousedown="onCanvasMouseDown"
|
||||||
|
@mousemove="onCanvasMouseMove"
|
||||||
|
@mouseup="onCanvasMouseUp"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
style="background-color: #fff;:hover:border: 1px;"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
:transform="`translate(${
|
||||||
|
configStore.position_center.x + svgEditLayoutStore.center_offset.x
|
||||||
|
},${configStore.position_center.y + svgEditLayoutStore.center_offset.y})rotate(0)scale(1)`"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
v-for="(item, index) in globalStore.done_json"
|
||||||
|
:key="item.id"
|
||||||
|
:transform="`translate(${item.x},${item.y})rotate(0)scale(1)`"
|
||||||
|
:class="`${globalStore.intention == EGlobalStoreIntention.None ? 'svg-item-none' : ''}
|
||||||
|
${
|
||||||
|
globalStore.intention == EGlobalStoreIntention.Move &&
|
||||||
|
globalStore.handle_svg_info?.info.id == item.id
|
||||||
|
? 'svg-item-move'
|
||||||
|
: ''
|
||||||
|
}`"
|
||||||
|
@mousedown="onSvgMouseDown(item, index, $event)"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
fill="black"
|
||||||
|
fill-opacity="0"
|
||||||
|
style="stroke: none; stroke-width: 2; stroke-miterlimit: 10"
|
||||||
|
></rect>
|
||||||
|
|
||||||
|
<use :xlink:href="`#svg-${item.name}`" fill="#ff0000" width="100" height="100"></use>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useConfigStore } from '../../../../store/config';
|
||||||
|
import { useGlobalStore } from '../../../../store/global';
|
||||||
|
import {
|
||||||
|
EGlobalStoreIntention,
|
||||||
|
EMouseInfoState,
|
||||||
|
IDoneJson
|
||||||
|
} from '../../../../store/global/types';
|
||||||
|
import { useSvgEditLayoutStore } from '../../../../store/svgedit-layout';
|
||||||
|
import { randomString } from '../../../../utils';
|
||||||
|
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
const svgEditLayoutStore = useSvgEditLayoutStore();
|
||||||
|
const dropEvent = (e: DragEvent) => {
|
||||||
|
if (globalStore.intention == EGlobalStoreIntention.None) {
|
||||||
|
return;
|
||||||
|
} else if (globalStore.intention == EGlobalStoreIntention.Create) {
|
||||||
|
if (!globalStore.create_svg_info) {
|
||||||
|
console.error('要创建的数据获取失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const done_item_json = {
|
||||||
|
id: randomString(),
|
||||||
|
x: e.offsetX - svgEditLayoutStore.center_offset.x,
|
||||||
|
y: e.offsetY - svgEditLayoutStore.center_offset.y,
|
||||||
|
...globalStore.create_svg_info
|
||||||
|
};
|
||||||
|
globalStore.setHandleSvgInfo(done_item_json, globalStore.done_json.length);
|
||||||
|
globalStore.setDoneJson(done_item_json);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const dragEnterEvent = (e: DragEvent) => {
|
||||||
|
//dragenter和dragover一定要阻止浏览器默认行为 不然不会触发drop
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
const dragOverEvent = (e: DragEvent) => {
|
||||||
|
//dragenter和dragover一定要阻止浏览器默认行为 不然不会触发drop
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
const onSvgMouseDown = (select_item: IDoneJson, index: number, e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.cancelBubble = true;
|
||||||
|
//鼠标在画布上的组件按下记录选中的组件信息和鼠标位置信息等
|
||||||
|
globalStore.intention = EGlobalStoreIntention.Move;
|
||||||
|
globalStore.setHandleSvgInfo(select_item, index);
|
||||||
|
globalStore.setMouseInfo({
|
||||||
|
state: EMouseInfoState.Down,
|
||||||
|
position_x: e.clientX,
|
||||||
|
position_y: e.clientY,
|
||||||
|
now_position_x: select_item.x,
|
||||||
|
now_position_y: select_item.y,
|
||||||
|
new_position_x: select_item.x,
|
||||||
|
new_position_y: select_item.y
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onCanvasMouseMove = (e: MouseEvent) => {
|
||||||
|
//如果鼠标不是按下状态
|
||||||
|
if (globalStore.mouse_info.state != EMouseInfoState.Down) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { clientX, clientY } = e;
|
||||||
|
globalStore.mouse_info.new_position_x =
|
||||||
|
globalStore.mouse_info.now_position_x + clientX - globalStore.mouse_info.position_x;
|
||||||
|
globalStore.mouse_info.new_position_y =
|
||||||
|
globalStore.mouse_info.now_position_y + clientY - globalStore.mouse_info.position_y;
|
||||||
|
if (globalStore.handle_svg_info?.info && globalStore.intention == EGlobalStoreIntention.Move) {
|
||||||
|
//有选中组件 移动组件
|
||||||
|
globalStore.handle_svg_info.info.x = globalStore.mouse_info.new_position_x;
|
||||||
|
globalStore.handle_svg_info.info.y = globalStore.mouse_info.new_position_y;
|
||||||
|
} else if (globalStore.intention == EGlobalStoreIntention.MoveCanvas) {
|
||||||
|
//移动画布
|
||||||
|
svgEditLayoutStore.center_offset.x = globalStore.mouse_info.new_position_x;
|
||||||
|
svgEditLayoutStore.center_offset.y = globalStore.mouse_info.new_position_y;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onCanvasMouseUp = () => {
|
||||||
|
//如果鼠标不是按下状态或者没有选择组件
|
||||||
|
if (globalStore.mouse_info.state != EMouseInfoState.Down) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (globalStore.handle_svg_info?.info && globalStore.intention == EGlobalStoreIntention.Move) {
|
||||||
|
globalStore.done_json[globalStore.handle_svg_info.index].x =
|
||||||
|
globalStore.mouse_info.new_position_x;
|
||||||
|
globalStore.done_json[globalStore.handle_svg_info.index].y =
|
||||||
|
globalStore.mouse_info.new_position_y;
|
||||||
|
globalStore.setDoneJson(globalStore.done_json);
|
||||||
|
globalStore.setHandleSvgInfo(undefined, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
globalStore.intention = EGlobalStoreIntention.None;
|
||||||
|
globalStore.setMouseInfo({
|
||||||
|
state: EMouseInfoState.Up,
|
||||||
|
position_x: 0,
|
||||||
|
position_y: 0,
|
||||||
|
now_position_x: 0,
|
||||||
|
now_position_y: 0,
|
||||||
|
new_position_x: 0,
|
||||||
|
new_position_y: 0
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onCanvasMouseDown = (e: MouseEvent) => {
|
||||||
|
//点击画布 未选中组件 拖动画布
|
||||||
|
globalStore.intention = EGlobalStoreIntention.MoveCanvas;
|
||||||
|
globalStore.setMouseInfo({
|
||||||
|
state: EMouseInfoState.Down,
|
||||||
|
position_x: e.clientX,
|
||||||
|
position_y: e.clientY,
|
||||||
|
now_position_x: svgEditLayoutStore.center_offset.x,
|
||||||
|
now_position_y: svgEditLayoutStore.center_offset.y,
|
||||||
|
new_position_x: svgEditLayoutStore.center_offset.x,
|
||||||
|
new_position_y: svgEditLayoutStore.center_offset.y
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
cursor: v-bind('globalStore.intention == EGlobalStoreIntention.MoveCanvas?"grab":"default"');
|
||||||
|
}
|
||||||
|
.svg-item-none {
|
||||||
|
cursor: move;
|
||||||
|
&:hover {
|
||||||
|
outline: 1px solid #0cf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.svg-item-move {
|
||||||
|
cursor: move;
|
||||||
|
outline: 1px dashed rgb(23, 222, 30);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { IPositionCenter } from './types';
|
||||||
|
/**
|
||||||
|
* 配置文件
|
||||||
|
*/
|
||||||
|
export const useConfigStore = defineStore('config-store', {
|
||||||
|
state: (): IPositionCenter => {
|
||||||
|
return {
|
||||||
|
position_center: {
|
||||||
|
x: -50,
|
||||||
|
y: -50
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {}
|
||||||
|
});
|
@ -0,0 +1,6 @@
|
|||||||
|
export interface IPositionCenter {
|
||||||
|
position_center: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
}
|
@ -1,16 +1,62 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { configCenter } from '../../config-center';
|
import { configCenter } from '../../config-center';
|
||||||
import { IGlobalStore } from './types';
|
import { IConfigItem } from '../../config-center/types';
|
||||||
|
import { isOfType } from '../../utils';
|
||||||
|
import {
|
||||||
|
EGlobalStoreIntention,
|
||||||
|
EMouseInfoState,
|
||||||
|
IDoneJson,
|
||||||
|
IGlobalStore,
|
||||||
|
IMouseInfo
|
||||||
|
} from './types';
|
||||||
/**
|
/**
|
||||||
* 全局共享状态
|
* 全局共享状态
|
||||||
*/
|
*/
|
||||||
export const useGlobalStore = defineStore('global-store', {
|
export const useGlobalStore = defineStore('global-store', {
|
||||||
state: (): IGlobalStore => {
|
state: (): IGlobalStore => {
|
||||||
return {
|
return {
|
||||||
config_center: configCenter
|
config_center: configCenter,
|
||||||
|
intention: EGlobalStoreIntention.None,
|
||||||
|
create_svg_info: undefined,
|
||||||
|
done_json: [],
|
||||||
|
mouse_info: {
|
||||||
|
state: EMouseInfoState.Up,
|
||||||
|
position_x: 0,
|
||||||
|
position_y: 0,
|
||||||
|
now_position_x: 0,
|
||||||
|
now_position_y: 0,
|
||||||
|
new_position_x: 0,
|
||||||
|
new_position_y: 0
|
||||||
|
},
|
||||||
|
handle_svg_info: undefined
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getters: {},
|
getters: {},
|
||||||
actions: {}
|
actions: {
|
||||||
|
setCreateInfo(create_svg_info: IConfigItem | undefined) {
|
||||||
|
this.intention = EGlobalStoreIntention.Create;
|
||||||
|
this.create_svg_info = create_svg_info;
|
||||||
|
},
|
||||||
|
setDoneJson(done_json: IDoneJson[] | IDoneJson) {
|
||||||
|
// console.log('这里要记录操作历史记录');
|
||||||
|
if (isOfType(done_json, 'id')) {
|
||||||
|
this.done_json.push(done_json);
|
||||||
|
} else {
|
||||||
|
this.done_json = done_json;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setMouseInfo(mouse_info: IMouseInfo) {
|
||||||
|
this.mouse_info = mouse_info;
|
||||||
|
},
|
||||||
|
setHandleSvgInfo(info: IDoneJson | undefined, index: number) {
|
||||||
|
if (info) {
|
||||||
|
this.handle_svg_info = {
|
||||||
|
info: info,
|
||||||
|
index: index
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.handle_svg_info = info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,38 @@
|
|||||||
import { IConfigCenter } from '../../config-center/types';
|
import { IConfigCenter, IConfigItem } from '../../config-center/types';
|
||||||
|
|
||||||
export interface IGlobalStore {
|
export interface IGlobalStore {
|
||||||
config_center: IConfigCenter;
|
config_center: IConfigCenter;
|
||||||
|
intention: EGlobalStoreIntention;
|
||||||
|
create_svg_info: IConfigItem | undefined;
|
||||||
|
done_json: IDoneJson[];
|
||||||
|
mouse_info: IMouseInfo;
|
||||||
|
handle_svg_info: IHandleSvgInfo | undefined;
|
||||||
|
}
|
||||||
|
export interface IDoneJson extends IConfigItem {
|
||||||
|
id: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
export enum EGlobalStoreIntention {
|
||||||
|
None = 'None',
|
||||||
|
Create = 'Create',
|
||||||
|
Move = 'Move',
|
||||||
|
MoveCanvas = 'MoveCanvas'
|
||||||
|
}
|
||||||
|
export interface IMouseInfo {
|
||||||
|
state: EMouseInfoState;
|
||||||
|
position_x: number;
|
||||||
|
position_y: number;
|
||||||
|
now_position_x: number;
|
||||||
|
now_position_y: number;
|
||||||
|
new_position_x: number;
|
||||||
|
new_position_y: number;
|
||||||
|
}
|
||||||
|
export enum EMouseInfoState {
|
||||||
|
Down = 'Down',
|
||||||
|
Up = 'Up'
|
||||||
|
}
|
||||||
|
export interface IHandleSvgInfo {
|
||||||
|
info: IDoneJson;
|
||||||
|
index: number;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
export interface ISvgEditLayoutStore {
|
export interface ISvgEditLayoutStore {
|
||||||
left_nav: boolean;
|
left_nav: boolean;
|
||||||
right_nav: boolean;
|
right_nav: boolean;
|
||||||
|
center_offset: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 生成随机字符串
|
||||||
|
* @param len 生成个数
|
||||||
|
*/
|
||||||
|
export const randomString = (len?: number) => {
|
||||||
|
len = len || 10;
|
||||||
|
const str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
const maxPos = str.length;
|
||||||
|
let random_str = '';
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
random_str += str.charAt(Math.floor(Math.random() * maxPos));
|
||||||
|
}
|
||||||
|
return random_str;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 通过泛型定义通用类型保护函数
|
||||||
|
export const isOfType = <T>(target: unknown, prop: keyof T): target is T => {
|
||||||
|
return (target as T)[prop] !== undefined;
|
||||||
|
};
|
Loading…
Reference in New Issue