345 lines
11 KiB
Vue
345 lines
11 KiB
Vue
<template>
|
||
<div class="property-panel">
|
||
<CollapsibleSection title="基本属性" v-model:isExpanded="propertySectionExpanded" status="default">
|
||
<PropertyEditor :componentData="componentData" :componentConfig="componentConfig" @updateProp="
|
||
(componentId, propName, value) =>
|
||
$emit('updateProp', componentId, propName, value)
|
||
" @updateDirectProp="
|
||
(componentId, propName, value) =>
|
||
$emit('updateDirectProp', componentId, propName, value)
|
||
" />
|
||
</CollapsibleSection>
|
||
|
||
<!-- 信号发生器(DDS)特殊属性编辑器 -->
|
||
<div v-if="isDDSComponent">
|
||
<DDSPropertyEditor v-model="ddsProperties" @update:modelValue="updateDDSProperties" />
|
||
</div>
|
||
|
||
<!-- 如果选中的组件有pins属性,则显示引脚配置区域 -->
|
||
<CollapsibleSection v-if="hasPinsProperty" title="引脚配置" v-model:isExpanded="pinsSectionExpanded" status="default">
|
||
<div class="space-y-4 p-2">
|
||
<!-- 显示现有的pins -->
|
||
<div v-for="(pin, index) in componentPins" :key="index" class="pin-item p-2 border rounded-md bg-base-200">
|
||
<div class="font-medium mb-2">引脚 #{{ index + 1 }}</div>
|
||
|
||
<div class="grid grid-cols-2 gap-2">
|
||
<div class="form-control">
|
||
<label class="label">
|
||
<span class="label-text text-xs">ID</span>
|
||
</label>
|
||
<input type="text" v-model="componentPins[index].pinId" class="input input-bordered input-sm w-full"
|
||
placeholder="引脚ID" @change="updatePins" />
|
||
</div>
|
||
|
||
<div class="form-control">
|
||
<label class="label">
|
||
<span class="label-text text-xs">约束条件</span>
|
||
</label>
|
||
<input type="text" v-model="componentPins[index].constraint" class="input input-bordered input-sm w-full"
|
||
placeholder="约束条件" @change="updatePins" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CollapsibleSection>
|
||
|
||
<CollapsibleSection title="组件功能" v-model:isExpanded="componentCapsExpanded" status="default" class="mt-4">
|
||
<div v-if="componentData && componentData.type">
|
||
<component
|
||
v-if="capabilityComponent"
|
||
:is="capabilityComponent"
|
||
v-bind="componentData.attrs"
|
||
/>
|
||
<div v-else class="text-gray-400">
|
||
该组件没有提供特殊功能
|
||
</div>
|
||
</div>
|
||
<div v-else class="text-gray-400">
|
||
选择元件以查看其功能
|
||
</div>
|
||
</CollapsibleSection>
|
||
|
||
<!-- 未来可以在这里添加更多的分区 -->
|
||
<!-- 例如:
|
||
<CollapsibleSection
|
||
title="连线管理"
|
||
v-model:isExpanded="wireSectionExpanded"
|
||
status="default"
|
||
>
|
||
<div>连线管理内容</div>
|
||
</CollapsibleSection>
|
||
-->
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
// 导入所需的类型和组件
|
||
import { type DiagramPart } from "@/components/diagramManager"; // 图表部件类型定义
|
||
import { type PropertyConfig } from "@/components/equipments/componentConfig"; // 属性配置类型定义
|
||
import CollapsibleSection from "./CollapsibleSection.vue"; // 可折叠区域组件
|
||
import PropertyEditor from "./PropertyEditor.vue"; // 属性编辑器组件
|
||
import DDSPropertyEditor from "./equipments/DDSPropertyEditor.vue"; // DDS专用属性编辑器组件
|
||
import { ref, computed, watch, shallowRef, markRaw, h, createApp } from "vue"; // Vue核心API
|
||
import { isNull, isUndefined } from "lodash";
|
||
import type { JSX } from "vue/jsx-runtime";
|
||
|
||
// 引脚接口定义
|
||
interface Pin {
|
||
pinId: string; // 引脚ID
|
||
constraint: string; // 引脚约束条件
|
||
x: number; // 引脚X坐标位置
|
||
y: number; // 引脚Y坐标位置
|
||
}
|
||
|
||
// 定义组件的输入属性
|
||
const props = defineProps<{
|
||
componentData: DiagramPart | null; // 当前选中的组件数据
|
||
componentConfig: { props: PropertyConfig[] } | null; // 组件的配置信息
|
||
}>();
|
||
|
||
// 控制各个属性分区的展开状态
|
||
const propertySectionExpanded = ref(true); // 基本属性区域默认展开
|
||
const pinsSectionExpanded = ref(false); // 引脚配置区域默认折叠
|
||
const componentCapsExpanded = ref(true); // 组件功能区域默认展开
|
||
const wireSectionExpanded = ref(false); // 连线管理区域默认折叠
|
||
|
||
// DDS组件特殊属性的本地状态
|
||
const ddsProperties = ref({
|
||
frequency: 1000, // 频率,默认1000Hz
|
||
phase: 0, // 相位,默认0度
|
||
waveform: "sine", // 波形类型,默认正弦波
|
||
customWaveformPoints: [], // 自定义波形点数据,默认空数组
|
||
});
|
||
|
||
// 本地维护一个pins数组副本,用于编辑操作
|
||
const componentPins = ref<Pin[]>([]);
|
||
|
||
// 监听组件pins属性变化,更新本地pins数据
|
||
watch(
|
||
() => props.componentData?.attrs?.pins,
|
||
(newPins) => {
|
||
if (newPins) {
|
||
// 深拷贝以避免直接修改原始数据
|
||
componentPins.value = JSON.parse(JSON.stringify(newPins));
|
||
} else {
|
||
componentPins.value = [];
|
||
}
|
||
},
|
||
{ deep: true, immediate: true }, // 深度监听并立即执行
|
||
);
|
||
|
||
// 监听DDS组件数据变化,更新特殊属性
|
||
watch(
|
||
() => props.componentData?.attrs,
|
||
(newAttrs) => {
|
||
if (newAttrs && isDDSComponent.value) {
|
||
// 从组件属性中提取DDS特有属性
|
||
ddsProperties.value = {
|
||
frequency: newAttrs.frequency || 1000,
|
||
phase: newAttrs.phase || 0,
|
||
waveform: newAttrs.waveform || "sine",
|
||
customWaveformPoints: newAttrs.customWaveformPoints || [],
|
||
};
|
||
}
|
||
},
|
||
{ deep: true, immediate: true }, // 深度监听并立即执行
|
||
);
|
||
|
||
// 计算属性:检查组件是否有pins属性
|
||
const hasPinsProperty = computed(() => {
|
||
if (!props.componentData || !props.componentData.attrs) {
|
||
return false;
|
||
}
|
||
|
||
// 方法1:检查配置中是否有pins属性
|
||
if (props.componentConfig && props.componentConfig.props) {
|
||
return props.componentConfig.props.some(
|
||
(prop) => prop.name === "pins" && prop.isArrayType,
|
||
);
|
||
}
|
||
|
||
// 方法2:直接检查attrs中是否有pins属性
|
||
return "pins" in props.componentData.attrs;
|
||
});
|
||
|
||
// 计算属性:检查组件是否为DDS组件
|
||
const isDDSComponent = computed(() => {
|
||
return props.componentData?.type === "DDS";
|
||
});
|
||
|
||
// 定义向父组件发送的事件
|
||
const emit = defineEmits<{
|
||
// 更新嵌套属性(如数组或对象中的属性)
|
||
(e: "updateProp", componentId: string, propName: string, value: any): void;
|
||
// 更新直接属性
|
||
(
|
||
e: "updateDirectProp",
|
||
componentId: string,
|
||
propName: string,
|
||
value: any,
|
||
): void;
|
||
}>();
|
||
|
||
// 更新pins属性的函数
|
||
function updatePins() {
|
||
if (props.componentData && props.componentData.id) {
|
||
// 将编辑后的pins数据发送给父组件
|
||
emit("updateProp", props.componentData.id, "pins", componentPins.value);
|
||
}
|
||
}
|
||
|
||
// 更新DDS特殊属性的函数
|
||
function updateDDSProperties(newProperties: any) {
|
||
// 更新本地状态
|
||
ddsProperties.value = newProperties;
|
||
if (props.componentData && props.componentData.id) {
|
||
// 将各个属性单独更新,而不是作为一个整体更新
|
||
emit(
|
||
"updateProp",
|
||
props.componentData.id,
|
||
"frequency",
|
||
newProperties.frequency,
|
||
);
|
||
emit("updateProp", props.componentData.id, "phase", newProperties.phase);
|
||
emit(
|
||
"updateProp",
|
||
props.componentData.id,
|
||
"waveform",
|
||
newProperties.waveform,
|
||
);
|
||
emit(
|
||
"updateProp",
|
||
props.componentData.id,
|
||
"customWaveformPoints",
|
||
newProperties.customWaveformPoints,
|
||
);
|
||
}
|
||
}
|
||
|
||
// 存储当前选中组件的能力组件
|
||
const capabilityComponent = shallowRef(null);
|
||
|
||
// 获取组件实例上暴露的方法
|
||
async function getExposedCapabilities(componentType: string) {
|
||
try {
|
||
// 动态导入组件
|
||
const module = await import(`./equipments/${componentType}.vue`);
|
||
const Component = module.default;
|
||
|
||
// 创建一个临时div作为挂载点
|
||
const tempDiv = document.createElement('div');
|
||
|
||
// 创建临时应用实例并挂载组件
|
||
let exposedMethods: any = null;
|
||
const app = createApp({
|
||
render() {
|
||
return h(Component, {
|
||
ref: (el: any) => {
|
||
if (el) {
|
||
// 获取组件实例暴露的方法
|
||
exposedMethods = el;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// 挂载应用
|
||
const vm = app.mount(tempDiv);
|
||
|
||
// 确保实例已创建并获取到暴露的方法
|
||
await new Promise(resolve => setTimeout(resolve, 0));
|
||
|
||
// 检查是否有getCapabilities方法
|
||
if (exposedMethods && typeof exposedMethods.getCapabilities === 'function') {
|
||
// 获取能力组件定义
|
||
const CapabilityComponent = exposedMethods.getCapabilities();
|
||
|
||
// 卸载应用,清理DOM
|
||
app.unmount();
|
||
tempDiv.remove();
|
||
|
||
return CapabilityComponent;
|
||
}
|
||
|
||
// 卸载应用,清理DOM
|
||
app.unmount();
|
||
tempDiv.remove();
|
||
|
||
return null;
|
||
} catch (error) {
|
||
console.error(`获取${componentType}能力页面失败:`, error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 监听选中组件变化,动态加载能力组件
|
||
watch(
|
||
() => props.componentData,
|
||
async (newComponentData) => {
|
||
if (newComponentData && newComponentData.type) {
|
||
try {
|
||
// 首先尝试从实例中获取暴露的方法
|
||
const capsComponent = await getExposedCapabilities(newComponentData.type);
|
||
|
||
if (capsComponent) {
|
||
capabilityComponent.value = markRaw(capsComponent);
|
||
console.log(`已从实例加载${newComponentData.type}组件的能力页面`);
|
||
return;
|
||
}
|
||
|
||
// 如果实例方法获取失败,回退到模块导出方法
|
||
const module = await import(`./equipments/${newComponentData.type}.vue`);
|
||
|
||
if (
|
||
(module.default && typeof module.default.getCapabilities === "function") ||
|
||
typeof module.getCapabilities === "function"
|
||
) {
|
||
const getCapsFn =
|
||
typeof module.getCapabilities === "function"
|
||
? module.getCapabilities
|
||
: module.default.getCapabilities;
|
||
|
||
const moduleCapComponent = getCapsFn();
|
||
if (moduleCapComponent) {
|
||
capabilityComponent.value = markRaw(moduleCapComponent);
|
||
console.log(`已从模块加载${newComponentData.type}组件的能力页面`);
|
||
return;
|
||
}
|
||
}
|
||
|
||
capabilityComponent.value = null;
|
||
console.log(`组件${newComponentData.type}没有提供getCapabilities方法`);
|
||
} catch (error) {
|
||
console.error(`加载组件${newComponentData.type}能力页面失败:`, error);
|
||
capabilityComponent.value = null;
|
||
}
|
||
} else {
|
||
capabilityComponent.value = null;
|
||
}
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
|
||
// 修改hasComponentCaps计算属性
|
||
const hasComponentCaps = computed(() => {
|
||
return capabilityComponent.value !== null;
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.property-panel {
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.pin-item {
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.pin-item:hover {
|
||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||
}
|
||
</style>
|