feat: 基础拖拽功能实现
parent
ed7a38bbdb
commit
fc66f96be2
@ -1,6 +1,10 @@
|
||||
<!-- eslint-disable vue/html-indent -->
|
||||
<template>
|
||||
<div class="flex justify-center items-center pt-2">
|
||||
<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>
|
||||
<script lang="ts" setup></script>
|
||||
|
@ -1,3 +1,181 @@
|
||||
<!-- eslint-disable vue/html-indent -->
|
||||
<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>
|
||||
<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 { 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', {
|
||||
state: (): IGlobalStore => {
|
||||
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: {},
|
||||
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 {
|
||||
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 {
|
||||
left_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