feat: 导入导出组件树预览等功能完成
parent
e02f7349ff
commit
8209dae48e
@ -1,9 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 10V44H39V10H9Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
|
||||
<path d="M20 20V33" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M28 20V33" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 10H44" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16 10L19.289 4H28.7771L32 10H16Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
|
||||
<g stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M9 10V44H39V10H9Z" fill="none" />
|
||||
<path d="M20 20V33" />
|
||||
<path d="M28 20V33" />
|
||||
<path d="M4 10H44" />
|
||||
<path d="M16 10L19.289 4H28.7771L32 10H16Z" fill="none" />
|
||||
</g>
|
||||
|
||||
</svg>
|
Before Width: | Height: | Size: 674 B After Width: | Height: | Size: 451 B |
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9998 8L6 14L12.9998 21" stroke="#333" stroke-width="4" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path
|
||||
d="M6 14H28.9938C35.8768 14 41.7221 19.6204 41.9904 26.5C42.2739 33.7696 36.2671 40 28.9938 40H11.9984"
|
||||
stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
After Width: | Height: | Size: 482 B |
@ -0,0 +1,242 @@
|
||||
<!-- eslint-disable prettier/prettier -->
|
||||
<template>
|
||||
<div
|
||||
class="canvas"
|
||||
@mousedown="onCanvasMouseDown"
|
||||
@mousemove="onCanvasMouseMove"
|
||||
@mouseup="onCanvasMouseUp"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:style="{ backgroundColor: preview_data.config.background_color }"
|
||||
width="100%"
|
||||
height="100%"
|
||||
>
|
||||
<g
|
||||
:transform="`translate(${
|
||||
preview_data.config.position_center.x + preview_data.layout_center.x
|
||||
},${
|
||||
preview_data.config.position_center.y + preview_data.layout_center.y
|
||||
})rotate(${0})scale(${preview_data.config.scale})`"
|
||||
>
|
||||
<g
|
||||
v-for="item in preview_data.done_json"
|
||||
:key="item.id"
|
||||
:transform="`translate(${item.x},${item.y})rotate(0)scale(1)`"
|
||||
v-show="item.display"
|
||||
>
|
||||
<g
|
||||
:transform="`translate(${item.actual_bound.x + item.actual_bound.width / 2},${
|
||||
item.actual_bound.y + item.actual_bound.height / 2
|
||||
})rotate(${item.rotate}) scale(1) translate(${-(
|
||||
item.actual_bound.x +
|
||||
item.actual_bound.width / 2
|
||||
)},${-(item.actual_bound.y + item.actual_bound.height / 2)})`"
|
||||
>
|
||||
<connection-line
|
||||
v-if="item.type === EDoneJsonType.ConnectionLine"
|
||||
:item-info="item"
|
||||
></connection-line>
|
||||
<use
|
||||
v-else-if="item.type === EDoneJsonType.File"
|
||||
:xlink:href="`#svg-${item.name}`"
|
||||
v-bind="prosToVBind(item)"
|
||||
width="100"
|
||||
height="100"
|
||||
:id="item.id"
|
||||
:transform="`translate(${item.actual_bound.x + item.actual_bound.width / 2},${
|
||||
item.actual_bound.y + item.actual_bound.height / 2
|
||||
}) scale(${item.scale_x},${item.scale_y}) translate(${-(
|
||||
item.actual_bound.x +
|
||||
item.actual_bound.width / 2
|
||||
)},${-(item.actual_bound.y + item.actual_bound.height / 2)})`"
|
||||
></use>
|
||||
<component
|
||||
v-else-if="item.type === EDoneJsonType.CustomSvg"
|
||||
:is="item.tag"
|
||||
v-bind="prosToVBind(item)"
|
||||
width="100"
|
||||
height="100"
|
||||
:id="item.id"
|
||||
:transform="`translate(${item.actual_bound.x + item.actual_bound.width / 2},${
|
||||
item.actual_bound.y + item.actual_bound.height / 2
|
||||
}) scale(${item.scale_x},${item.scale_y}) translate(${-(
|
||||
item.actual_bound.x +
|
||||
item.actual_bound.width / 2
|
||||
)},${-(item.actual_bound.y + item.actual_bound.height / 2)})`"
|
||||
></component>
|
||||
<foreignObject
|
||||
v-else-if="item.type === EDoneJsonType.Vue"
|
||||
v-bind="getActualBoundScale(item.actual_bound, item.scale_x, item.scale_y)"
|
||||
:id="`foreign-object${item.id}`"
|
||||
>
|
||||
<component
|
||||
:is="item.tag"
|
||||
v-bind="prosToVBind(item)"
|
||||
:id="item.id"
|
||||
:transform="`translate(${item.actual_bound.x + item.actual_bound.width / 2},${
|
||||
item.actual_bound.y + item.actual_bound.height / 2
|
||||
}) scale(${item.scale_x},${item.scale_y}) translate(${-(
|
||||
item.actual_bound.x +
|
||||
item.actual_bound.width / 2
|
||||
)},${-(item.actual_bound.y + item.actual_bound.height / 2)})`"
|
||||
>{{ item.tag_slot }}</component
|
||||
>
|
||||
</foreignObject>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getCurrentInstance, PropType, reactive } from 'vue';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { EGlobalStoreIntention, EMouseInfoState } from '@/store/global/types';
|
||||
import { prosToVBind } from '@/utils';
|
||||
|
||||
import { EDoneJsonType } from '@/config-center/types';
|
||||
import ConnectionLine from '@/components/webtopo-svgedit/components/connection-line/index.vue';
|
||||
|
||||
import { ComponentImport } from '@/config-center';
|
||||
import { IDataModel } from '../webtopo-svgedit/types';
|
||||
import 'element-plus/dist/index.css';
|
||||
// import HandlePanel from '../handle-panel/index.vue';
|
||||
//注册所有组件
|
||||
const instance = getCurrentInstance();
|
||||
Object.keys(ComponentImport).forEach((key) => {
|
||||
if (!Object.keys(instance?.appContext?.components as any).includes(key)) {
|
||||
instance?.appContext.app.component(key, ComponentImport[key]);
|
||||
}
|
||||
});
|
||||
const props = defineProps({
|
||||
dataModel: {
|
||||
type: [Object, null] as PropType<IDataModel | null>,
|
||||
default: null
|
||||
},
|
||||
canvasDrag: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
const preview_data = reactive(
|
||||
props.dataModel ?? {
|
||||
layout_center: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
config: {
|
||||
background_color: '#fff',
|
||||
scale: 1,
|
||||
position_center: {
|
||||
x: -295,
|
||||
y: -95
|
||||
},
|
||||
svg_position_center: {
|
||||
x: 50,
|
||||
y: 50
|
||||
}
|
||||
},
|
||||
done_json: []
|
||||
}
|
||||
);
|
||||
const globalStore = useGlobalStore();
|
||||
const onCanvasMouseMove = (e: MouseEvent) => {
|
||||
//如果鼠标不是按下状态 连线除外
|
||||
if (
|
||||
globalStore.mouse_info.state != EMouseInfoState.Down &&
|
||||
globalStore.intention !== EGlobalStoreIntention.Connection
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!props.canvasDrag) {
|
||||
console.log(props.canvasDrag);
|
||||
|
||||
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.intention == EGlobalStoreIntention.MoveCanvas) {
|
||||
//移动画布
|
||||
preview_data.layout_center.x = globalStore.mouse_info.new_position_x;
|
||||
preview_data.layout_center.y = globalStore.mouse_info.new_position_y;
|
||||
}
|
||||
};
|
||||
const onCanvasMouseUp = () => {
|
||||
//如果鼠标不是按下状态
|
||||
if (globalStore.mouse_info.state != EMouseInfoState.Down) {
|
||||
return;
|
||||
}
|
||||
if (globalStore.intention != EGlobalStoreIntention.Select) {
|
||||
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) => {
|
||||
console.log('onCanvasMouseDown', e);
|
||||
const { clientX, clientY } = e;
|
||||
//点击画布 未选中组件 拖动画布
|
||||
globalStore.intention = EGlobalStoreIntention.MoveCanvas;
|
||||
globalStore.setMouseInfo({
|
||||
state: EMouseInfoState.Down,
|
||||
position_x: clientX,
|
||||
position_y: clientY,
|
||||
now_position_x: preview_data.layout_center.x,
|
||||
now_position_y: preview_data.layout_center.y,
|
||||
new_position_x: preview_data.layout_center.x,
|
||||
new_position_y: preview_data.layout_center.y
|
||||
});
|
||||
};
|
||||
const getActualBoundScale = (
|
||||
actual_bound: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
},
|
||||
scale_x: number,
|
||||
scale_y: number
|
||||
) => {
|
||||
return {
|
||||
x: actual_bound.x - (actual_bound.width / 2) * scale_x + actual_bound.width / 2,
|
||||
y: actual_bound.y - (actual_bound.height / 2) * scale_y + actual_bound.height / 2,
|
||||
width: actual_bound.width * scale_x,
|
||||
height: actual_bound.height * scale_y
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.canvas {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.svg-item-none {
|
||||
cursor: move;
|
||||
|
||||
&:hover {
|
||||
outline: 1px solid #0cf;
|
||||
}
|
||||
}
|
||||
|
||||
.svg-item-move {
|
||||
cursor: move;
|
||||
outline: 1px dashed rgb(23, 222, 30);
|
||||
}
|
||||
|
||||
.svg-item-select {
|
||||
cursor: move;
|
||||
outline: 1px solid rgb(23, 222, 30);
|
||||
}
|
||||
</style>
|
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<el-tree
|
||||
:data="data"
|
||||
:props="defaultProps"
|
||||
@node-click="handleNodeClick"
|
||||
:default-expand-all="true"
|
||||
:expand-on-click-node="false"
|
||||
:highlight-current="true"
|
||||
node-key="id"
|
||||
:current-node-key="current_node_key"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { EGlobalStoreIntention, IDoneJson } from '@/store/global/types';
|
||||
import { ElTree } from 'element-plus';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const global_store = useGlobalStore();
|
||||
const data = ref<IDoneJson[]>([]);
|
||||
const current_node_key = ref(global_store.handle_svg_info?.info.id);
|
||||
const handleNodeClick = (data: IDoneJson) => {
|
||||
global_store.intention = EGlobalStoreIntention.Select;
|
||||
global_store.setHandleSvgInfo(data);
|
||||
};
|
||||
onMounted(() => {
|
||||
data.value = global_store.done_json;
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'title'
|
||||
};
|
||||
</script>
|
@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { VAceEditor } from 'vue3-ace-editor';
|
||||
import '@/config-center/ace-edit';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { useSvgEditLayoutStore } from '@/store/svgedit-layout';
|
||||
import { useConfigStore } from '@/store/config';
|
||||
import { IDataModel } from '../../types';
|
||||
const content = ref('');
|
||||
const globalStore = useGlobalStore();
|
||||
const svgEditLayoutStore = useSvgEditLayoutStore();
|
||||
const configStore = useConfigStore();
|
||||
onMounted(() => {
|
||||
const export_json: IDataModel = {
|
||||
layout_center: svgEditLayoutStore.center_offset,
|
||||
config: configStore.svg,
|
||||
done_json: globalStore.done_json
|
||||
};
|
||||
content.value = JSON.stringify(export_json, null, 2);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-ace-editor
|
||||
v-model:value="content"
|
||||
lang="json"
|
||||
theme="monokai"
|
||||
style="height: 400px"
|
||||
:options="{
|
||||
useWorker: true,
|
||||
enableBasicAutocompletion: true,
|
||||
enableSnippets: true,
|
||||
enableLiveAutocompletion: true
|
||||
}"
|
||||
/>
|
||||
</template>
|
@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { VAceEditor } from 'vue3-ace-editor';
|
||||
import '@/config-center/ace-edit';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { useSvgEditLayoutStore } from '@/store/svgedit-layout';
|
||||
import { useConfigStore } from '@/store/config';
|
||||
import { IDataModel } from '../../types';
|
||||
import { ElMessage } from 'element-plus';
|
||||
const content = ref<string>('');
|
||||
const globalStore = useGlobalStore();
|
||||
const svgEditLayoutStore = useSvgEditLayoutStore();
|
||||
const configStore = useConfigStore();
|
||||
const onImportJson = () => {
|
||||
try {
|
||||
const json: IDataModel = JSON.parse(content.value);
|
||||
console.log(json, json.layout_center, configStore, 15);
|
||||
if (!json.config || !json.layout_center || !json.done_json) {
|
||||
ElMessage.error('请导入正确的数据模型!');
|
||||
return;
|
||||
}
|
||||
configStore.svg = json.config;
|
||||
svgEditLayoutStore.center_offset = json.layout_center;
|
||||
globalStore.setDoneJson(json.done_json);
|
||||
ElMessage.success('导入成功');
|
||||
} catch (error) {
|
||||
ElMessage.error('请导入正确的数据模型!');
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
defineExpose({
|
||||
onImportJson
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-ace-editor
|
||||
v-model:value="content"
|
||||
lang="json"
|
||||
theme="monokai"
|
||||
style="height: 400px"
|
||||
:options="{
|
||||
useWorker: true,
|
||||
enableBasicAutocompletion: true,
|
||||
enableSnippets: true,
|
||||
enableLiveAutocompletion: true
|
||||
}"
|
||||
/>
|
||||
</template>
|
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-button type="primary" plain round @click="dialogVisible = true">点击编辑</el-button>
|
||||
<el-dialog v-model="dialogVisible" title="配置编辑" width="60%">
|
||||
<v-ace-editor
|
||||
v-model:value="content"
|
||||
lang="json"
|
||||
theme="monokai"
|
||||
style="height: 400px"
|
||||
:options="{
|
||||
useWorker: true,
|
||||
enableBasicAutocompletion: true,
|
||||
enableSnippets: true,
|
||||
enableLiveAutocompletion: true
|
||||
}"
|
||||
/>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="onYesBtnClick">确定</el-button>
|
||||
<el-button type="primary" @click="dialogVisible = false">关闭</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { VAceEditor } from 'vue3-ace-editor';
|
||||
import '@/config-center/ace-edit';
|
||||
import { ElButton, ElDialog } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
const props = defineProps({
|
||||
contentObj: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
});
|
||||
const dialogVisible = ref(false);
|
||||
const emits = defineEmits(['updateAttrItemVal']);
|
||||
const content = ref(JSON.stringify(props.contentObj, null, 2));
|
||||
const onYesBtnClick = () => {
|
||||
emits('updateAttrItemVal', JSON.parse(content.value));
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
</script>
|
@ -0,0 +1,19 @@
|
||||
import { IPositionCenterSvg } from '@/store/config/types';
|
||||
import { IDoneJson } from '@/store/global/types';
|
||||
|
||||
export type IVisibleConf = {
|
||||
[key in EVisibleConfKey]: boolean;
|
||||
};
|
||||
export enum EVisibleConfKey {
|
||||
ImportJson = 'ImportJson',
|
||||
ExportJson = 'ExportJson',
|
||||
ComponentTree = 'ComponentTree'
|
||||
}
|
||||
export interface IDataModel {
|
||||
layout_center: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
config: IPositionCenterSvg;
|
||||
done_json: IDoneJson[];
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import ace from 'ace-builds';
|
||||
|
||||
import themeMonokaiUrl from 'ace-builds/src-noconflict/theme-monokai?url';
|
||||
ace.config.setModuleUrl('ace/theme/monokai', themeMonokaiUrl);
|
||||
|
||||
import workerBaseUrl from 'ace-builds/src-noconflict/worker-base?url';
|
||||
ace.config.setModuleUrl('ace/mode/base', workerBaseUrl);
|
||||
|
||||
import modeJsonUrl from 'ace-builds/src-noconflict/mode-json?url';
|
||||
ace.config.setModuleUrl('ace/mode/json', modeJsonUrl);
|
||||
import workerJsonUrl from 'ace-builds/src-noconflict/worker-json?url';
|
||||
ace.config.setModuleUrl('ace/mode/json_worker', workerJsonUrl);
|
||||
import snippetsJsonUrl from 'ace-builds/src-noconflict/snippets/json?url';
|
||||
ace.config.setModuleUrl('ace/snippets/json', snippetsJsonUrl);
|
||||
|
||||
import modeJavascriptUrl from 'ace-builds/src-noconflict/mode-javascript?url';
|
||||
ace.config.setModuleUrl('ace/mode/javascript', modeJavascriptUrl);
|
||||
import workerJavascriptUrl from 'ace-builds/src-noconflict/worker-javascript?url';
|
||||
ace.config.setModuleUrl('ace/mode/javascript_worker', workerJavascriptUrl);
|
||||
import snippetsJavascriptUrl from 'ace-builds/src-noconflict/snippets/javascript?url';
|
||||
ace.config.setModuleUrl('ace/snippets/javascript', snippetsJavascriptUrl);
|
||||
|
||||
import 'ace-builds/src-noconflict/ext-language_tools';
|
||||
ace.require('ace/ext/language_tools');
|
@ -1,14 +1,15 @@
|
||||
export interface IPositionCenter {
|
||||
svg: {
|
||||
background_color: string;
|
||||
scale: number;
|
||||
position_center: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
svg_position_center: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
svg: IPositionCenterSvg;
|
||||
}
|
||||
export interface IPositionCenterSvg {
|
||||
background_color: string;
|
||||
scale: number;
|
||||
position_center: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
svg_position_center: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<webtopo-svgedit></webtopo-svgedit>
|
||||
<webtopo-svgedit @on-return="onReturn"></webtopo-svgedit>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import WebtopoSvgedit from '../../components/webtopo-svgedit/index.vue';
|
||||
const onReturn = () => {
|
||||
console.log('点击了返回按钮');
|
||||
};
|
||||
</script>
|
||||
|
@ -1,3 +1,15 @@
|
||||
<template>
|
||||
<div>预览页</div>
|
||||
<webtopo-svg-preview :data-model="data_model" :canvas-drag="true"></webtopo-svg-preview>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import WebtopoSvgPreview from '@/components/webtopo-svg-preview/index.vue';
|
||||
import { IDataModel } from '@/components/webtopo-svgedit/types';
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
const route = useRoute();
|
||||
const data_model = ref<IDataModel | null>(null);
|
||||
|
||||
if (route.params.data_model) {
|
||||
data_model.value = JSON.parse(route.params.data_model as string);
|
||||
}
|
||||
</script>
|
||||
|
Loading…
Reference in New Issue