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); } }