FPGA_WebLab/src/components/PropertyPanel.vue

345 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>