feat: 基础拖拽功能实现

Re-1.0
咬轮猫 3 years ago
parent ed7a38bbdb
commit fc66f96be2

@ -1,6 +1,6 @@
{
"name": "vue-webtopo-svgeditor",
"version": "0.0.5",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",

@ -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) => {
//dragenterdragover drop
e.preventDefault();
};
const dragOverEvent = (e: DragEvent) => {
//dragenterdragover 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>

@ -5,7 +5,7 @@
<div class="source-repo">组件库 :</div>
<el-select v-model="select_lib" placeholder="请选择组件库" @change="libChange">
<el-option
v-for="(item, key) in global_store.config_center"
v-for="(item, key) in globalStore.config_center"
:key="key"
:label="key"
:value="item"
@ -19,15 +19,22 @@
<div style="font-weight: bolder">{{ item.title }}</div>
</template>
<div class="component-group flex flex-warp">
<div v-for="iconitem in item.list" class="ideal" :key="iconitem.name">
<div
v-for="svg_item in item.list"
class="ideal"
:key="svg_item.name"
draggable="true"
@dragstart="createBegin(svg_item)"
@dragend="dragEndEvent"
>
<div class="flex component-item items-center ml-10px">
<el-icon :size="40" class="flex items-center">
<svg-analysis
:name="iconitem.name"
:props="handleIconProps(iconitem.props)"
:name="svg_item.name"
:props="handleIconProps(svg_item.props)"
></svg-analysis>
</el-icon>
<div>{{ iconitem.title }}</div>
<div>{{ svg_item.title }}</div>
</div>
</div>
</div>
@ -38,13 +45,18 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { ElSelect, ElOption, ElCollapse, ElCollapseItem, ElIcon } from 'element-plus';
import { IConfigComponentGroup, IConfigItemProps } from '../../../../config-center/types';
import { ElSelect, ElOption, ElCollapse, ElCollapseItem, ElIcon, ElMessage } from 'element-plus';
import {
IConfigComponentGroup,
IConfigItem,
IConfigItemProps
} from '../../../../config-center/types';
import { useGlobalStore } from '../../../../store/global';
import SvgAnalysis from '../../../svg-analysis/index.vue';
const global_store = useGlobalStore();
import { EGlobalStoreIntention } from '../../../../store/global/types';
const globalStore = useGlobalStore();
const select_lib = ref('电力系统');
const config_center = ref<IConfigComponentGroup[]>(global_store.config_center.电力系统);
const config_center = ref<IConfigComponentGroup[]>(globalStore.config_center.电力系统);
const activeNames = ref(['stateful', 'stateless']);
const libChange = (val: any) => {
config_center.value = [];
@ -52,13 +64,25 @@
console.log(val, 71474);
};
const handleIconProps = (svg_item: IConfigItemProps) => {
const handleIconProps = (svg_item_props: IConfigItemProps) => {
let temp = {};
for (const key in svg_item) {
temp = { ...temp, ...{ [key]: svg_item[key].val } };
for (const key in svg_item_props) {
temp = { ...temp, ...{ [key]: svg_item_props[key].val } };
}
return temp;
};
const createBegin = (svg_item: IConfigItem) => {
globalStore.setCreateInfo(svg_item);
};
const dragEndEvent = (e: DragEvent) => {
//svg
if (e.dataTransfer?.dropEffect !== 'copy') {
ElMessage.warning('请将组件拖到画布中!');
//
globalStore.intention = EGlobalStoreIntention.None;
return;
}
};
</script>
<style scoped lang="less">
.component-item {

@ -11,11 +11,9 @@
</el-scrollbar>
</el-aside>
<el-main class="middle main">
<el-scrollbar class="canvas-main-pc">
<div>
<center-panel></center-panel>
</div>
</el-scrollbar>
<div class="canvas-main-pc">
<center-panel></center-panel>
</div>
</el-main>
<el-aside class="side-nav" :class="svgEditLayoutStore.right_nav ? 'show-nav' : 'hide-nav'">
<el-scrollbar class="elscrooll-pc">
@ -48,12 +46,12 @@
}
.canvas-main-pc {
min-height: calc(100vh - (@headerHeight + @buttomHeight));
width: 100%;
height: 100%;
margin: 0 auto;
}
.middle {
min-height: calc(100vh - (@headerHeight + @buttomHeight));
height: calc(100vh - (@headerHeight + @buttomHeight));
&.main {
margin: 0px 5px;
background-color: #ffffff;

@ -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;
}

@ -7,7 +7,11 @@ export const useSvgEditLayoutStore = defineStore('svgedit-layout-store', {
state: (): ISvgEditLayoutStore => {
return {
left_nav: true,
right_nav: true
right_nav: true,
center_offset: {
x: 0,
y: 0
}
};
},
getters: {},

@ -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…
Cancel
Save