add:完善组件删除功能

This commit is contained in:
lhj
2024-11-14 23:59:18 +08:00
parent 63b95b3607
commit 670e71cd7b
5 changed files with 126 additions and 86 deletions

3
auto-imports.d.ts vendored
View File

@ -3,6 +3,7 @@
// @ts-nocheck // @ts-nocheck
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import // Generated by unplugin-auto-import
// biome-ignore lint: disable
export {} export {}
declare global { declare global {
const EffectScope: typeof import('vue')['EffectScope'] const EffectScope: typeof import('vue')['EffectScope']
@ -303,6 +304,6 @@ declare global {
// for type re-export // for type re-export
declare global { declare global {
// @ts-ignore // @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue') import('vue')
} }

View File

@ -55,7 +55,7 @@
</VueDraggable> </VueDraggable>
</div> </div>
<div class="right"> <div class="right">
<PropertyEditor :scheme="store.nowComponentsData"></PropertyEditor> <PropertyEditor v-if="store.nowComponentsData" :scheme="store.nowComponentsData"></PropertyEditor>
</div> </div>
</div> </div>
</div> </div>
@ -76,9 +76,9 @@ import axios from 'axios';
let componentsList = []; let componentsList = [];
const store = useSchemeStore(); const store = useSchemeStore();
// watch(store, (n) => { watch(store, (n) => {
// console.log("store发生了变化", n); console.log("store发生了变化", n);
// }); });
const baseScheme = { const baseScheme = {
"type": "AdaptivePage", "type": "AdaptivePage",
@ -157,24 +157,24 @@ function clone(element: Record<'name' | 'id' | 'type' | 'props' | 'class' | 'tex
} }
const onEnd = (event: DraggableEvent) => { const onEnd = (event: DraggableEvent) => {
console.log("onEnd", event); // console.log("onEnd", event);
store.nowComponentsData = event.clonedData; store.nowComponentsData = event.clonedData;
}; };
const onStart = (event) => { const onStart = (event) => {
console.log("onStart", event); // console.log("onStart", event);
}; };
const onPreviewStart = (event) => { const onPreviewStart = (event) => {
console.log("onPreviewStart", event); // console.log("onPreviewStart", event);
}; };
const onPreviewUpdate = (event) => { const onPreviewUpdate = (event) => {
console.log("onPreviewUpdate", event); // console.log("onPreviewUpdate", event);
}; };
const onPreviewStop = (event) => { const onPreviewStop = (event) => {
console.log(event); // console.log(event);
}; };
const save = async () => { const save = async () => {

View File

@ -1,21 +1,21 @@
<template> <template>
<div <div
style="width: fit-content; display: flex; flex-direction: column; position: relative;" style="width: fit-content; display: flex; flex-direction: column; position: relative;"
v-if="componentVisible || store.designerMode" v-if="store.nowComponentsData!==null&&(componentVisible || store.designerMode)"
:id="componentId" :id="componentId"
:class="[ :class="[
'dynamic-component', 'dynamic-component',
{ 'hover-state': isHovered }, { 'hover-state': isHovered },
{ 'click-state': isClicked } { 'click-state': componentSelected }
]" ]"
@click.stop="handleClick" @click.stop="handleClick"
@mouseover="isHovered = true" @mouseover="isHovered = true"
@mouseleave="isHovered = false" @mouseleave="isHovered = false"
> >
<div v-if="isClicked" class="component-header" :style="headerStyle"> <div v-if="componentSelected" class="component-header" :style="headerStyle">
<span>{{ componentName }}</span> <span>{{ componentName }}</span>
<button style="color: #1057CC" @click="handleFunction('edit')">编辑</button> <button style="color: #1057CC" @click="handleEditFunc">编辑</button>
<button style="color: #1057CC" @click="handleFunction('delete')">删除</button> <button style="color: #1057CC" @click="handleDeleteFunc">删除</button>
</div> </div>
<div class="component-content"> <div class="component-content">
<component <component
@ -26,10 +26,10 @@
> >
{{ componentText }} {{ componentText }}
<template v-for="child in componentChildren" :key="child.id"> <template v-for="child in componentChildren" :key="child.id">
<DynamicComponent :componentData="child" /> <DynamicComponent :componentData="child"/>
</template> </template>
<template v-for="(slot, key, index) in componentSlots" :key="index" v-slot:[key]> <template v-for="(slot, key, index) in componentSlots" :key="index" v-slot:[key]>
<DynamicComponent :component-data="slot" /> <DynamicComponent :component-data="slot"/>
</template> </template>
</component> </component>
</div> </div>
@ -37,9 +37,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineProps, ref, computed, onMounted, watch, markRaw, nextTick } from 'vue'; import {defineProps, ref, computed, onMounted, watch, markRaw, nextTick} from 'vue';
import { componentMapping } from './componentMapping'; import {componentMapping} from './componentMapping';
import { useSchemeStore } from '../stores/useSchemeStore'; import {useSchemeStore} from '../stores/useSchemeStore';
const store = useSchemeStore(); const store = useSchemeStore();
const props = defineProps({ const props = defineProps({
@ -57,6 +57,7 @@ const componentText = computed(() => props.componentData?.text || '');
const componentClass = computed(() => props.componentData?.class || []); const componentClass = computed(() => props.componentData?.class || []);
const componentStyle = computed(() => props.componentData?.style || []); const componentStyle = computed(() => props.componentData?.style || []);
const componentSlots = computed(() => props.componentData?.slots || {}); const componentSlots = computed(() => props.componentData?.slots || {});
const componentSelected = computed(() => store.nowComponentsData?.id && props.componentData?.id === store.nowComponentsData?.id);
// 确保 componentProps 包含 disabled 属性 // 确保 componentProps 包含 disabled 属性
const componentPropsWithDisabled = computed(() => ({ const componentPropsWithDisabled = computed(() => ({
@ -71,24 +72,24 @@ const getCurrentSchemeObj = () => {
// 控制悬停和点击状态 // 控制悬停和点击状态
const isHovered = ref(false); const isHovered = ref(false);
const isClicked = ref(false);
const handleClick = () => { const handleClick = () => {
isClicked.value = !isClicked.value;
const currentComponent = getCurrentSchemeObj(); const currentComponent = getCurrentSchemeObj();
console.log(`Component with id ${currentComponent.id} was clicked.`); if (currentComponent) {
// 你可以在这里执行更多的逻辑,例如发出一个事件或调用一个方法 store.nowComponentsData = currentComponent
}; console.log(`Component with id ${currentComponent?.id} was clicked.`);
// 你可以在这里执行更多的逻辑,例如发出一个事件或调用一个方法
}
const handleFunction = (action: string) => { };
console.log(`Action: ${action}`); const handleEditFunc = () => {
// 处理编辑或删除操作
};
const handleDeleteFunc = () => {
store.deleteScheme(componentId.value);
}; };
const headerStyle = ref({}); const headerStyle = ref({});
const targetContent = ref<HTMLElement | null>(null);
const adjustHeaderPosition = () => { const adjustHeaderPosition = () => {
const componentEl = document.getElementById(componentId.value); const componentEl = document.getElementById(componentId.value);
if (!componentEl) return; if (!componentEl) return;
@ -137,11 +138,10 @@ const adjustHeaderPosition = () => {
onMounted(() => { onMounted(() => {
console.log(props.componentData); console.log(props.componentData);
adjustHeaderPosition();
}); });
watch(() => isClicked.value, () => { watch(() => componentSelected.value, () => {
if (isClicked.value) { if (componentSelected.value) {
nextTick(() => { nextTick(() => {
adjustHeaderPosition(); adjustHeaderPosition();
}); });
@ -153,7 +153,7 @@ watch(() => isClicked.value, () => {
.dynamic-component { .dynamic-component {
position: relative; position: relative;
border: 1px solid transparent; /* 默认透明边框 */ border: 1px solid transparent; /* 默认透明边框 */
transition: border 0.3s, box-shadow 0.3s; transition: box-shadow 0.1s;
width: fit-content; width: fit-content;
} }

View File

@ -14,25 +14,25 @@
<div style="margin-top: 16px;"> <div style="margin-top: 16px;">
<!-- 属性面板 --> <!-- 属性面板 -->
<div> <div>
<a-input-search placeholder="Please enter something" /> <a-input-search placeholder="Please enter something"/>
<a-space direction="vertical" fill style="margin-top: 8px;"> <a-space direction="vertical" fill style="margin-top: 8px;">
<div style="display: flex;justify-content: space-between;vertical-align: middle;"> <div style="display: flex;justify-content: space-between;vertical-align: middle;">
<span>组件ID</span> <span>组件ID</span>
<a-input style="width: 150px;" /> <a-input style="width: 150px;"/>
</div> </div>
<div style="display: flex;justify-content: space-between;vertical-align: middle;"> <div style="display: flex;justify-content: space-between;vertical-align: middle;">
<span>是否可见</span> <span>是否可见</span>
<a-switch v-model="scheme.visible" /> <a-switch v-model="scheme.visible"/>
</div> </div>
<div style="display: flex;justify-content: space-between;vertical-align: middle;"> <div style="display: flex;justify-content: space-between;vertical-align: middle;">
<span>是否禁用</span> <span>是否禁用</span>
<a-switch v-model="scheme.disable" /> <a-switch v-model="scheme.disable"/>
</div> </div>
<a-collapse :expand-icon-position="`right`" :default-active-key="['1', 2]"> <a-collapse :expand-icon-position="`right`" :default-active-key="['1', 2]">
<a-collapse-item header="基本配置" key="1"> <a-collapse-item header="基本配置" key="1">
<div style="display: flex;justify-content: space-between;vertical-align: middle;"> <div style="display: flex;justify-content: space-between;vertical-align: middle;">
<span>内容</span> <span>内容</span>
<a-input v-model="scheme.text" style="width: 150px;" /> <a-input v-model="scheme.text" style="width: 150px;"/>
</div> </div>
</a-collapse-item> </a-collapse-item>
</a-collapse> </a-collapse>
@ -60,9 +60,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { watch, defineProps, computed, ref, onMounted } from 'vue'; import {watch, defineProps, computed, ref, onMounted} from 'vue';
import { IPageComponent } from '@/type/IPageComponent'; import {IPageComponent} from '@/type/IPageComponent';
import { useSchemeStore } from '../stores/useSchemeStore' import {useSchemeStore} from '../stores/useSchemeStore'
import {IComponent} from "@/type/IComponent.ts"; import {IComponent} from "@/type/IComponent.ts";
@ -79,8 +79,9 @@ const scheme = computed<IPageComponent>(() => props.scheme || {} as IPageCompone
// 使用 deep 选项来深度监听对象的变化 // 使用 deep 选项来深度监听对象的变化
watch(scheme, (value, oldValue) => { watch(scheme, (value, oldValue) => {
console.log("scheme Changed", value); console.log("scheme Changed", value);
store.updateScheme(value.id,value as IComponent); if (value)
}, { deep: true }); store.updateScheme(value.id, value as IComponent);
}, {deep: true});
onMounted(() => { onMounted(() => {
// 初始化时的逻辑 // 初始化时的逻辑

View File

@ -5,19 +5,20 @@ import { sha256 } from 'js-sha256';
// 缓存对象 // 缓存对象
const idToObjectCache = new Map<string, IPageComponent>(); const idToObjectCache = new Map<string, IPageComponent>();
function findObjectById(obj, targetId) { function findObjectById(obj: any, targetId: string) {
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
for (let item of obj) { for (let i = 0; i < obj.length; i++) {
let found = findObjectById(item, targetId); const item = obj[i];
if (item.id === targetId) {
return { item, index: i, parent: obj };
}
const found = findObjectById(item, targetId);
if (found) return found; if (found) return found;
} }
} else if (typeof obj === 'object' && obj !== null) { } else if (typeof obj === 'object' && obj !== null) {
if (obj.id === targetId) {
return obj;
}
for (let key in obj) { for (let key in obj) {
if (obj.hasOwnProperty(key)) { if (obj.hasOwnProperty(key)) {
let found = findObjectById(obj[key], targetId); const found = findObjectById(obj[key], targetId);
if (found) return found; if (found) return found;
} }
} }
@ -30,9 +31,9 @@ const HISTORY_LENGTH = 20; // 默认队列长度
export const useSchemeStore = defineStore('scheme', { export const useSchemeStore = defineStore('scheme', {
state: () => ({ state: () => ({
designerMode: true, designerMode: true,
components: [], components: [] as IPageComponent[],
previewScheme: [], previewScheme: [] as IPageComponent[],
nowComponentsData: {}, nowComponentsData: {} as IPageComponent,
history: Array(HISTORY_LENGTH).fill(null), // 循环队列 history: Array(HISTORY_LENGTH).fill(null), // 循环队列
currentIndex: -1, // 当前索引 currentIndex: -1, // 当前索引
currentLength: 0, // 当前队列中有效元素的数量 currentLength: 0, // 当前队列中有效元素的数量
@ -43,19 +44,19 @@ export const useSchemeStore = defineStore('scheme', {
canRedo: (state) => state.currentIndex < state.currentLength - 1, canRedo: (state) => state.currentIndex < state.currentLength - 1,
}, },
actions: { actions: {
initPreviewScheme(value) { initPreviewScheme(value: IPageComponent[]) {
this.previewScheme = value; this.previewScheme = value;
this.nowComponentsData = value[0]; this.nowComponentsData = value[0] || {} as IPageComponent;
idToObjectCache.clear(); idToObjectCache.clear();
this.history.fill(null); // 初始化队列 this.history.fill(null); // 初始化队列
this.currentIndex = -1; this.currentIndex = -1;
this.currentLength = 0; this.currentLength = 0;
this.currentHash = ''; // 初始化当前哈希值 this.currentHash = ''; // 初始化当前哈希值
}, },
initComponents(value) { initComponents(value: IPageComponent[]) {
this.components = value; this.components = value;
}, },
getSchemeObj(id) { getSchemeObj(id: string) {
// 检查缓存 // 检查缓存
if (idToObjectCache.has(id)) { if (idToObjectCache.has(id)) {
return idToObjectCache.get(id); return idToObjectCache.get(id);
@ -63,19 +64,78 @@ export const useSchemeStore = defineStore('scheme', {
// 查找并缓存 // 查找并缓存
const obj = findObjectById(this.previewScheme, id); const obj = findObjectById(this.previewScheme, id);
if (obj) { if (obj) {
idToObjectCache.set(id, obj); idToObjectCache.set(id, obj.item);
} }
return obj; return obj ? obj.item : null;
}, },
updateScheme(id, updates) { updateScheme(id: string, updates: Partial<IPageComponent>) {
const currentStateHash = this.currentHash; const currentStateHash = this.currentHash;
const newObj = this.getSchemeObj(id); const newObj = this.getSchemeObj(id);
if (newObj) { if (newObj) {
Object.assign(newObj, updates); Object.assign(newObj, updates);
} }
const newStateHash = sha256(JSON.stringify(this.previewScheme)); this.saveNewState(currentStateHash);
},
deleteScheme(id: string) {
const currentStateHash = this.currentHash;
const objToDelete = findObjectById(this.previewScheme, id);
if (objToDelete) {
const { item, index, parent } = objToDelete;
parent.splice(index, 1);
idToObjectCache.delete(id); // 从缓存中删除
// 如果新状态与当前状态不同,才保存到历史记录中 // 清理 nowComponentsData
if (this.nowComponentsData.id === id) {
this.nowComponentsData = {} as IPageComponent;
}
// 清理 history 中的引用
this.cleanupReferences(id);
}
this.saveNewState(currentStateHash);
},
cleanupReferences(id: string) {
// 清理 nowComponentsData
if (this.nowComponentsData.id === id) {
this.nowComponentsData = {} as IPageComponent;
}
// 清理 history 中的引用
for (let i = 0; i < this.currentLength; i++) {
const historyState = this.history[i % HISTORY_LENGTH];
if (historyState) {
const obj = findObjectById(historyState, id);
if (obj) {
const { parent, index } = obj;
parent.splice(index, 1);
}
}
}
},
undo() {
if (this.canUndo) {
this.currentIndex--;
const prevState = this.history[this.currentIndex % HISTORY_LENGTH];
if (prevState) {
this.previewScheme = prevState;
this.nowComponentsData = this.previewScheme[0] || {} as IPageComponent;
this.currentHash = sha256(JSON.stringify(this.previewScheme));
}
}
},
redo() {
if (this.canRedo) {
this.currentIndex++;
const nextState = this.history[this.currentIndex % HISTORY_LENGTH];
if (nextState) {
this.previewScheme = nextState;
this.nowComponentsData = this.previewScheme[0] || {} as IPageComponent;
this.currentHash = sha256(JSON.stringify(this.previewScheme));
}
}
},
saveNewState(currentStateHash: string) {
const newStateHash = sha256(JSON.stringify(this.previewScheme));
if (currentStateHash !== newStateHash) { if (currentStateHash !== newStateHash) {
this.currentHash = newStateHash; this.currentHash = newStateHash;
// 如果不是最新的状态,则清除之后的所有状态 // 如果不是最新的状态,则清除之后的所有状态
@ -87,28 +147,6 @@ export const useSchemeStore = defineStore('scheme', {
this.currentLength = this.currentIndex + 1; this.currentLength = this.currentIndex + 1;
this.history[this.currentIndex % HISTORY_LENGTH] = JSON.parse(JSON.stringify(this.previewScheme)); this.history[this.currentIndex % HISTORY_LENGTH] = JSON.parse(JSON.stringify(this.previewScheme));
} }
},
undo() {
if (this.canUndo) {
this.currentIndex--;
const prevState = this.history[this.currentIndex % HISTORY_LENGTH];
if (prevState) {
this.previewScheme = prevState;
this.nowComponentsData = this.previewScheme[0];
this.currentHash = sha256(JSON.stringify(this.previewScheme));
}
}
},
redo() {
if (this.canRedo) {
this.currentIndex++;
const nextState = this.history[this.currentIndex % HISTORY_LENGTH];
if (nextState) {
this.previewScheme = nextState;
this.nowComponentsData = this.previewScheme[0];
this.currentHash = sha256(JSON.stringify(this.previewScheme));
}
}
} }
} }
}); });