From 91838ff63268c72db7cabbdd3aafca3d38f51f2c Mon Sep 17 00:00:00 2001
From: alivender <13898766233@163.com>
Date: Sun, 11 May 2025 14:41:38 +0800
Subject: [PATCH] add: DDS virtual component
---
package-lock.json | 98 ++
package.json | 1 +
src/components/ComponentSelector.vue | 68 +-
src/components/DiagramCanvas.vue | 22 +-
src/components/PropertyPanel.vue | 58 +
src/components/equipments/DDS.vue | 339 +++++
.../equipments/DDSPropertyEditor.vue | 1137 +++++++++++++++++
src/views/ProjectView.vue | 25 +-
8 files changed, 1722 insertions(+), 26 deletions(-)
create mode 100644 src/components/equipments/DDS.vue
create mode 100644 src/components/equipments/DDSPropertyEditor.vue
diff --git a/package-lock.json b/package-lock.json
index 4581b3c..29b917b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"@types/lodash": "^4.17.16",
"lodash": "^4.17.21",
"log-symbols": "^7.0.0",
+ "mathjs": "^14.4.0",
"pinia": "^3.0.1",
"tinypool": "^1.0.2",
"ts-log": "^2.2.7",
@@ -474,6 +475,15 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
+ "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
@@ -2130,6 +2140,19 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/complex.js": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.2.tgz",
+ "integrity": "sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -2231,6 +2254,12 @@
}
}
},
+ "node_modules/decimal.js": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
+ "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
+ "license": "MIT"
+ },
"node_modules/default-browser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz",
@@ -2378,6 +2407,12 @@
"node": ">=6"
}
},
+ "node_modules/escape-latex": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
+ "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==",
+ "license": "MIT"
+ },
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
@@ -2667,6 +2702,12 @@
"node": ">=16"
}
},
+ "node_modules/javascript-natural-sort": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
+ "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
+ "license": "MIT"
+ },
"node_modules/jiti": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
@@ -3020,6 +3061,42 @@
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
+ "node_modules/mathjs": {
+ "version": "14.4.0",
+ "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.4.0.tgz",
+ "integrity": "sha512-CpoYDhNENefjIG9wU9epr+0pBHzlaySfpWcblZdAf5qXik/j/U8eSmx/oNbmXO0F5PyfwPGVD/wK4VWsTho1SA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/runtime": "^7.26.10",
+ "complex.js": "^2.2.5",
+ "decimal.js": "^10.4.3",
+ "escape-latex": "^1.2.0",
+ "fraction.js": "^5.2.1",
+ "javascript-natural-sort": "^0.7.1",
+ "seedrandom": "^3.0.5",
+ "tiny-emitter": "^2.1.0",
+ "typed-function": "^4.2.1"
+ },
+ "bin": {
+ "mathjs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/mathjs/node_modules/fraction.js": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.2.2.tgz",
+ "integrity": "sha512-uXBDv5knpYmv/2gLzWQ5mBHGBRk9wcKTeWu6GLTUEQfjCxO09uM/mHDrojlL+Q1mVGIIFo149Gba7od1XPgSzQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
"node_modules/memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
@@ -3429,6 +3506,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/seedrandom": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
+ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
+ "license": "MIT"
+ },
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@@ -3563,6 +3646,12 @@
"node": ">=6"
}
},
+ "node_modules/tiny-emitter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
+ "license": "MIT"
+ },
"node_modules/tinyglobby": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
@@ -3611,6 +3700,15 @@
"integrity": "sha512-HjX/7HxQe2bXkbp8pHTjy4Ir9eHIDnDDsLDphhGqy6I9iZ/vD4QXWEIlrVRZsEX+kS2jIiiF/mnl0nKnPTiYFw==",
"license": "MIT"
},
+ "node_modules/typed-function": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.1.tgz",
+ "integrity": "sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
diff --git a/package.json b/package.json
index 72a1321..238417f 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"@types/lodash": "^4.17.16",
"lodash": "^4.17.21",
"log-symbols": "^7.0.0",
+ "mathjs": "^14.4.0",
"pinia": "^3.0.1",
"tinypool": "^1.0.2",
"ts-log": "^2.2.7",
diff --git a/src/components/ComponentSelector.vue b/src/components/ComponentSelector.vue
index 9671b6e..be4779c 100644
--- a/src/components/ComponentSelector.vue
+++ b/src/components/ComponentSelector.vue
@@ -115,16 +115,40 @@
-
-
+
-
+
+
+
+
+
+
+
+ 加载中...
+
+
{{ device.name }}
+
{{ device.type }}
+
+
+
+
+
-
虚拟外设功能即将推出
+
没有找到匹配的虚拟外设
+
@@ -182,6 +206,11 @@ const availableComponents = [
{ type: 'PG2L100H_FBG676', name: 'PG2L100H FBG676芯片' }
];
+// --- 可用虚拟外设列表 ---
+const availableVirtualDevices = [
+ { type: 'DDS', name: '信号发生器' }
+];
+
// --- 可用模板列表 ---
const availableTemplates = ref([
{
@@ -226,6 +255,7 @@ async function loadComponentModule(type: string) {
// 预加载组件模块
async function preloadComponentModules() {
+ // 加载基础组件
for (const component of availableComponents) {
try {
await loadComponentModule(component.type);
@@ -233,6 +263,15 @@ async function preloadComponentModules() {
console.error(`Failed to preload component ${component.type}:`, error);
}
}
+
+ // 加载虚拟外设组件
+ for (const device of availableVirtualDevices) {
+ try {
+ await loadComponentModule(device.type);
+ } catch (error) {
+ console.error(`Failed to preload virtual device ${device.type}:`, error);
+ }
+ }
}
// 获取组件预览时适合的尺寸
@@ -250,7 +289,8 @@ function getPreviewSize(componentType: string): number {
'SD': 0.6, // SD卡插槽适中
'SFP': 0.4, // SFP光纤模块较大
'SMA': 0.7, // SMA连接器可以适中
- 'MotherBoard': 0.13 // 主板最大,需要最小尺寸
+ 'MotherBoard': 0.13, // 主板最大,需要最小尺寸
+ 'DDS': 0.3 // 信号发生器较大,需要较小尺寸
};
// 返回对应尺寸,如果没有特定配置则返回默认值0.5
@@ -350,6 +390,18 @@ const filteredTemplates = computed(() => {
);
});
+// 过滤后的虚拟外设列表 (用于菜单)
+const filteredVirtualDevices = computed(() => {
+ if (!searchQuery.value || activeTab.value !== 'virtual') {
+ return availableVirtualDevices;
+ }
+ const query = searchQuery.value.toLowerCase();
+ return availableVirtualDevices.filter(device =>
+ device.name.toLowerCase().includes(query) ||
+ device.type.toLowerCase().includes(query)
+ );
+});
+
// 生命周期钩子
onMounted(() => {
// 预加载组件模块
diff --git a/src/components/DiagramCanvas.vue b/src/components/DiagramCanvas.vue
index 7304b6a..a6432e5 100644
--- a/src/components/DiagramCanvas.vue
+++ b/src/components/DiagramCanvas.vue
@@ -114,9 +114,7 @@
'alert-info'}`">
{{ notificationMessage }}
-
-
-
+
@@ -1002,10 +1000,26 @@ function setDiagramData(data: DiagramData) {
emit('diagram-updated', data);
}
+// 无加载动画的数据更新方法
+function updateDiagramDataDirectly(data: DiagramData) {
+ // 检查组件是否仍然挂载
+ if (!document.body.contains(canvasContainer.value)) {
+ return; // 如果组件已经卸载,不执行后续操作
+ }
+
+ diagramData.value = data;
+ saveDiagramData(data);
+
+ // 发出diagram-updated事件
+ emit('diagram-updated', data);
+}
+
// 暴露方法给父组件
defineExpose({
// 基本数据操作
- getDiagramData: () => diagramData.value, setDiagramData: (data: DiagramData) => {
+ getDiagramData: () => diagramData.value,
+ updateDiagramDataDirectly,
+ setDiagramData: (data: DiagramData) => {
// 检查组件是否仍然挂载
if (!document.body.contains(canvasContainer.value)) {
return; // 如果组件已经卸载,不执行后续操作
diff --git a/src/components/PropertyPanel.vue b/src/components/PropertyPanel.vue
index e95b27c..bb7a1b9 100644
--- a/src/components/PropertyPanel.vue
+++ b/src/components/PropertyPanel.vue
@@ -11,6 +11,14 @@
@updateDirectProp="(componentId, propName, value) => $emit('updateDirectProp', componentId, propName, value)"
/>
+
+
+
+
+
([]);
@@ -106,6 +123,18 @@ watch(() => props.componentData?.attrs?.pins, (newPins) => {
}
}, { deep: true, immediate: true });
+// 监听DDS组件数据变化,更新特殊属性
+watch(() => props.componentData?.attrs, (newAttrs) => {
+ if (newAttrs && isDDSComponent.value) {
+ 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) {
@@ -121,6 +150,11 @@ const hasPinsProperty = computed(() => {
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;
@@ -133,6 +167,30 @@ function updatePins() {
emit('updateProp', props.componentData.id, 'pins', componentPins.value);
}
}
+
+// 监听DDS组件数据变化,更新特殊属性
+watch(() => props.componentData?.attrs, (newAttrs) => {
+ if (newAttrs && isDDSComponent.value) {
+ ddsProperties.value = {
+ frequency: newAttrs.frequency || 1000,
+ phase: newAttrs.phase || 0,
+ waveform: newAttrs.waveform || 'sine',
+ customWaveformPoints: newAttrs.customWaveformPoints || []
+ };
+ }
+}, { deep: true, immediate: true });
+
+// 更新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);
+ }
+}
+
+
+
diff --git a/src/components/equipments/DDSPropertyEditor.vue b/src/components/equipments/DDSPropertyEditor.vue
new file mode 100644
index 0000000..2ea5a43
--- /dev/null
+++ b/src/components/equipments/DDSPropertyEditor.vue
@@ -0,0 +1,1137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/ProjectView.vue b/src/views/ProjectView.vue
index 94a73c4..f93d7d1 100644
--- a/src/views/ProjectView.vue
+++ b/src/views/ProjectView.vue
@@ -213,13 +213,12 @@ async function handleAddComponent(componentData: { type: string; name: string; p
index: 0
};
- console.log('添加新组件:', newComponent);
-
+ console.log('添加新组件:', newComponent);
// 通过画布实例添加组件
- if (canvasInstance && canvasInstance.getDiagramData && canvasInstance.setDiagramData) {
+ if (canvasInstance && canvasInstance.getDiagramData && canvasInstance.updateDiagramDataDirectly) {
const currentData = canvasInstance.getDiagramData();
currentData.parts.push(newComponent);
- canvasInstance.setDiagramData(currentData);
+ canvasInstance.updateDiagramDataDirectly(currentData);
}
}
@@ -227,10 +226,9 @@ async function handleAddComponent(componentData: { type: string; name: string; p
async function handleAddTemplate(templateData: { id: string; name: string; template: any }) {
console.log('添加模板:', templateData);
console.log('=== 模板组件数量:', templateData.template?.parts?.length || 0);
-
- // 获取画布实例
+ // 获取画布实例
const canvasInstance = diagramCanvas.value as any;
- if (!canvasInstance || !canvasInstance.getDiagramData || !canvasInstance.setDiagramData) {
+ if (!canvasInstance || !canvasInstance.getDiagramData || !canvasInstance.updateDiagramDataDirectly) {
console.error('没有可用的画布实例添加模板');
return;
}
@@ -337,9 +335,8 @@ async function handleAddTemplate(templateData: { id: string; name: string; templ
// 添加到当前连接列表
currentData.connections.push(...newConnections);
}
-
- // 更新图表数据
- canvasInstance.setDiagramData(currentData);
+ // 更新图表数据
+ canvasInstance.updateDiagramDataDirectly(currentData);
console.log('=== 更新图表数据完成,新组件数量:', currentData.parts.length);
// 显示成功消息
@@ -459,7 +456,7 @@ function handleComponentDelete(componentId: string) {
// 更新组件属性的方法
function updateComponentProp(componentId: string, propName: string, value: any) {
const canvasInstance = diagramCanvas.value as any;
- if (!canvasInstance || !canvasInstance.getDiagramData || !canvasInstance.setDiagramData) {
+ if (!canvasInstance || !canvasInstance.getDiagramData || !canvasInstance.updateDiagramDataDirectly) {
console.error('没有可用的画布实例进行属性更新');
return;
}
@@ -484,7 +481,7 @@ function updateComponentProp(componentId: string, propName: string, value: any)
part.attrs[propName] = value;
}
- canvasInstance.setDiagramData(currentData);
+ canvasInstance.updateDiagramDataDirectly(currentData);
console.log(`更新组件${componentId}的属性${propName}为:`, value, typeof value);
}
}
@@ -492,7 +489,7 @@ function updateComponentProp(componentId: string, propName: string, value: any)
// 更新组件的直接属性
function updateComponentDirectProp(componentId: string, propName: string, value: any) {
const canvasInstance = diagramCanvas.value as any;
- if (!canvasInstance || !canvasInstance.getDiagramData || !canvasInstance.setDiagramData) {
+ if (!canvasInstance || !canvasInstance.getDiagramData || !canvasInstance.updateDiagramDataDirectly) {
console.error('没有可用的画布实例进行属性更新');
return;
}
@@ -504,7 +501,7 @@ function updateComponentDirectProp(componentId: string, propName: string, value:
// @ts-ignore: 动态属性赋值
part[propName] = value;
- canvasInstance.setDiagramData(currentData);
+ canvasInstance.updateDiagramDataDirectly(currentData);
console.log(`更新组件${componentId}的直接属性${propName}为:`, value, typeof value);
}
}