feat: upload and download bitstream from the component of project view

This commit is contained in:
SikongJueluo 2025-05-13 18:14:57 +08:00
parent eae67d04d4
commit eea03f5bc8
No known key found for this signature in database
15 changed files with 4243 additions and 1365 deletions

View File

@ -192,26 +192,6 @@ public class JtagController : ControllerBase
return "This is Jtag Controller";
}
/// <summary>
/// 执行一个Jtag命令
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="port"> 设备端口 </param>
/// <param name="hexDevAddr"> 16进制设备目的地址(Jtag) </param>
/// <param name="hexCmd"> 16进制命令 </param>
[HttpPost("RunCommand")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> RunCommand(string address, int port, string hexDevAddr, string hexCmd)
{
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.WriteFIFO(Convert.ToUInt32(hexDevAddr, 16), Convert.ToUInt32(hexCmd, 16));
if (ret.IsSuccessful) { return TypedResults.Ok(ret.Value); }
else { return TypedResults.InternalServerError(ret.Error); }
}
/// <summary>
/// 获取Jtag ID Code
/// </summary>

View File

@ -1,7 +1,10 @@
<script setup lang="ts">
import Navbar from "./components/Navbar.vue";
import Dialog from "./components/Dialog.vue";
import { ref, provide, onMounted } from "vue";
import { ref, provide, computed, onMounted } from "vue";
import { useRouter } from "vue-router";
const router = useRouter();
//
const isDarkMode = ref(
@ -42,6 +45,10 @@ provide("theme", {
isDarkMode,
toggleTheme,
});
const currentRoutePath = computed(() => {
return router.currentRoute.value.path;
});
</script>
<template>
@ -54,7 +61,7 @@ provide("theme", {
<main>
<RouterView />
</main>
<footer class="footer footer-center p-4 bg-base-300 text-base-content">
<footer v-if="currentRoutePath != '/project'" class="footer footer-center p-4 bg-base-300 text-base-content">
<div>
<p>Copyright © 2023 - All right reserved by OurEDA</p>
</div>

View File

@ -3,35 +3,39 @@
interface Props {
title: string;
isExpanded?: boolean;
status?: 'default' | 'success' | 'error';
status?: "default" | "success" | "error";
}
const props = withDefaults(defineProps<Props>(), {
isExpanded: false,
status: 'default'
status: "default",
});
const emit = defineEmits<{
(e: 'update:isExpanded', value: boolean): void
(e: "update:isExpanded", value: boolean): void;
}>();
// /
const toggleExpand = () => {
emit('update:isExpanded', !props.isExpanded);
emit("update:isExpanded", !props.isExpanded);
};
//
const enter = (element: Element, done: () => void) => {
if (element instanceof HTMLElement) {
const height = element.scrollHeight;
element.style.height = '0px';
element.style.height = "0px";
//
element.offsetHeight;
element.style.height = height + 'px';
element.style.height = height + "px";
element.addEventListener('transitionend', () => {
element.addEventListener(
"transitionend",
() => {
done();
}, { once: true });
},
{ once: true },
);
} else {
done();
}
@ -39,21 +43,25 @@ const enter = (element: Element, done: () => void) => {
const afterEnter = (element: Element) => {
if (element instanceof HTMLElement) {
element.style.height = 'auto';
element.style.height = "auto";
}
};
const leave = (element: Element, done: () => void) => {
if (element instanceof HTMLElement) {
const height = element.scrollHeight;
element.style.height = height + 'px';
element.style.height = height + "px";
//
element.offsetHeight;
element.style.height = '0px';
element.style.height = "0px";
element.addEventListener('transitionend', () => {
element.addEventListener(
"transitionend",
() => {
done();
}, { once: true });
},
{ once: true },
);
} else {
done();
}
@ -61,17 +69,12 @@ const leave = (element: Element, done: () => void) => {
</script>
<template>
<div class="section" :class="[`status-${status}`]">
<div class="section-header" @click="toggleExpand">
<div class="section m-4 shadow-xl" :class="[`status-${status}`]">
<div class="section-header bg-primary text-primary-content" @click="toggleExpand">
<h2>{{ title }}</h2>
<span class="expand-icon" :class="{ 'is-expanded': isExpanded }"></span>
</div>
<transition
name="collapse"
@enter="enter"
@after-enter="afterEnter"
@leave="leave"
>
<transition name="collapse" @enter="enter" @after-enter="afterEnter" @leave="leave">
<div v-show="isExpanded" class="section-content">
<div class="section-inner">
<slot></slot>
@ -88,7 +91,6 @@ const leave = (element: Element, done: () => void) => {
border-radius: var(--radius-md, 0.375rem);
overflow: hidden;
background-color: hsl(var(--b1));
box-shadow: var(--shadow-sm, 0 1px 2px 0 rgba(0, 0, 0, 0.05));
transition: all var(--transition-normal, 0.3s);
}
@ -119,26 +121,58 @@ const leave = (element: Element, done: () => void) => {
}
@keyframes borderPulseSuccess {
0% { border-color: hsl(var(--b3)); box-shadow: 0px 0px 0px transparent;}
50% { border-color: hsl(var(--su)); box-shadow: 0px 0px 5px hsl(var(--su));}
100% { border-color: hsl(var(--su)); box-shadow: 0px 0px 3px hsl(var(--su));}
0% {
border-color: hsl(var(--b3));
box-shadow: 0px 0px 0px transparent;
}
50% {
border-color: hsl(var(--su));
box-shadow: 0px 0px 5px hsl(var(--su));
}
100% {
border-color: hsl(var(--su));
box-shadow: 0px 0px 3px hsl(var(--su));
}
}
@keyframes borderPulseError {
0% { border-color: hsl(var(--b3)); box-shadow: 0px 0px 0px transparent;}
50% { border-color: hsl(var(--er)); box-shadow: 0px 0px 5px hsl(var(--er));}
100% { border-color: hsl(var(--er)); box-shadow: 0px 0px 3px hsl(var(--er));}
0% {
border-color: hsl(var(--b3));
box-shadow: 0px 0px 0px transparent;
}
50% {
border-color: hsl(var(--er));
box-shadow: 0px 0px 5px hsl(var(--er));
}
100% {
border-color: hsl(var(--er));
box-shadow: 0px 0px 3px hsl(var(--er));
}
}
@keyframes borderPulseInfo {
0% { border-color: hsl(var(--b3)); box-shadow: 0px 0px 0px transparent;}
50% { border-color: hsl(var(--in)); box-shadow: 0px 0px 5px hsl(var(--in));}
100% { border-color: hsl(var(--in)); box-shadow: 0px 0px 3px hsl(var(--in));}
0% {
border-color: hsl(var(--b3));
box-shadow: 0px 0px 0px transparent;
}
50% {
border-color: hsl(var(--in));
box-shadow: 0px 0px 5px hsl(var(--in));
}
100% {
border-color: hsl(var(--in));
box-shadow: 0px 0px 3px hsl(var(--in));
}
}
.section-header {
padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 0.75rem);
background-color: hsl(var(--b1));
cursor: pointer;
display: flex;
justify-content: space-between;
@ -148,10 +182,6 @@ const leave = (element: Element, done: () => void) => {
transition: all var(--transition-normal, 0.3s);
}
.section-header:hover {
background-color: hsl(var(--b2));
}
.section-header h2 {
margin: 0;
font-size: 1.1em;
@ -188,10 +218,12 @@ const leave = (element: Element, done: () => void) => {
}
.content-wrapper {
overflow: visible; /* 允许内容溢出 */
overflow: visible;
/* 允许内容溢出 */
}
.card-body {
overflow: visible; /* 允许内容溢出 */
overflow: visible;
/* 允许内容溢出 */
}
</style>

View File

@ -49,17 +49,6 @@
测试功能
</router-link>
</li>
<li class="my-1 hover:translate-x-1 transition-all duration-300">
<router-link to="/test/jtag" class="text-base font-medium">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 opacity-70" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
<line x1="8" y1="21" x2="16" y2="21"></line>
<line x1="12" y1="17" x2="12" y2="21"></line>
</svg>
JTAG测试
</router-link>
</li>
<li class="my-1 hover:translate-x-1 transition-all duration-300">
<a href="http://localhost:5000/swagger" target="_self" rel="noopener noreferrer"
class="text-base font-medium">

View File

@ -4,51 +4,50 @@
<div v-else>
<div class="mb-4 pb-4 border-b border-base-300">
<h4 class="font-semibold text-lg mb-1">{{ componentData?.type }}</h4>
<p class="text-xs text-base-content opacity-70">ID: {{ componentData?.id }}</p>
<p class="text-xs text-base-content opacity-70">类型: {{ componentData?.type }}</p>
<p class="text-xs text-base-content opacity-70">
ID: {{ componentData?.id }}
</p>
<p class="text-xs text-base-content opacity-70">
类型: {{ componentData?.type }}
</p>
</div>
<!-- 通用属性部分 -->
<CollapsibleSection
title="通用属性"
v-model:isExpanded="generalPropsExpanded"
status="default"
>
<CollapsibleSection title="通用属性" v-model:isExpanded="generalPropsExpanded" status="default">
<div class="space-y-4">
<div v-for="prop in getGeneralProps()" :key="prop.name" class="form-control">
<label class="label">
<span class="label-text">{{ prop.label || prop.name }}</span>
</label>
<!-- 根据 prop 类型选择输入控件 -->
<input
v-if="prop.type === 'number'"
type="number"
:placeholder="prop.label || prop.name"
class="input input-bordered input-sm w-full"
:value="getPropValue(componentData, prop.name)"
:disabled="prop.isReadOnly"
:min="prop.min"
:max="prop.max"
:step="prop.step"
@input="updateDirectProp(componentData.id, prop.name, parseFloat(($event.target as HTMLInputElement).value) || prop.default)"
/>
<input
v-else-if="prop.type === 'string'"
type="text"
:placeholder="prop.label || prop.name"
class="input input-bordered input-sm w-full"
:value="getPropValue(componentData, prop.name)"
:disabled="prop.isReadOnly"
@input="updateDirectProp(componentData.id, prop.name, ($event.target as HTMLInputElement).value)"
/>
<input v-if="prop.type === 'number'" type="number" :placeholder="prop.label || prop.name"
class="input input-bordered input-sm w-full" :value="getPropValue(componentData, prop.name)"
:disabled="prop.isReadOnly" :min="prop.min" :max="prop.max" :step="prop.step" @input="
updateDirectProp(
componentData.id,
prop.name,
parseFloat(($event.target as HTMLInputElement).value) ||
prop.default,
)
" />
<input v-else-if="prop.type === 'string'" type="text" :placeholder="prop.label || prop.name"
class="input input-bordered input-sm w-full" :value="getPropValue(componentData, prop.name)"
:disabled="prop.isReadOnly" @input="
updateDirectProp(
componentData.id,
prop.name,
($event.target as HTMLInputElement).value,
)
" />
<div v-else-if="prop.type === 'boolean'" class="flex items-center">
<input
type="checkbox"
class="checkbox checkbox-sm mr-2"
:checked="getPropValue(componentData, prop.name)"
:disabled="prop.isReadOnly"
@change="updateDirectProp(componentData.id, prop.name, ($event.target as HTMLInputElement).checked)"
/>
<input type="checkbox" class="checkbox checkbox-sm mr-2" :checked="getPropValue(componentData, prop.name)"
:disabled="prop.isReadOnly" @change="
updateDirectProp(
componentData.id,
prop.name,
($event.target as HTMLInputElement).checked,
)
" />
<span>{{ prop.label || prop.name }}</span>
</div>
</div>
@ -56,74 +55,69 @@
</CollapsibleSection>
<!-- 组件特有属性部分 -->
<CollapsibleSection
title="组件特有属性"
v-model:isExpanded="componentPropsExpanded"
status="default"
class="mt-4"
>
<CollapsibleSection title="组件特有属性" v-model:isExpanded="componentPropsExpanded" status="default" class="mt-4">
<div v-if="componentConfig && componentConfig.props" class="space-y-4">
<div v-for="prop in getComponentProps()" :key="prop.name" class="form-control">
<label class="label">
<span class="label-text">{{ prop.label || prop.name }}</span>
</label>
<!-- 根据 prop 类型选择输入控件 -->
<input
v-if="prop.type === 'string'"
type="text"
:placeholder="prop.label || prop.name"
class="input input-bordered input-sm w-full"
:value="componentData?.attrs?.[prop.name]"
:disabled="prop.isReadOnly"
@input="updateProp(componentData.id, prop.name, ($event.target as HTMLInputElement).value)"
/>
<input
v-else-if="prop.type === 'number'"
type="number"
:placeholder="prop.label || prop.name"
class="input input-bordered input-sm w-full"
:value="componentData?.attrs?.[prop.name]"
:disabled="prop.isReadOnly"
:min="prop.min"
:max="prop.max"
:step="prop.step"
@input="updateProp(componentData.id, prop.name, parseFloat(($event.target as HTMLInputElement).value) || prop.default)"
/>
<input v-if="prop.type === 'string'" type="text" :placeholder="prop.label || prop.name"
class="input input-bordered input-sm w-full" :value="componentData?.attrs?.[prop.name]"
:disabled="prop.isReadOnly" @input="
updateProp(
componentData.id,
prop.name,
($event.target as HTMLInputElement).value,
)
" />
<input v-else-if="prop.type === 'number'" type="number" :placeholder="prop.label || prop.name"
class="input input-bordered input-sm w-full" :value="componentData?.attrs?.[prop.name]"
:disabled="prop.isReadOnly" :min="prop.min" :max="prop.max" :step="prop.step" @input="
updateProp(
componentData.id,
prop.name,
parseFloat(($event.target as HTMLInputElement).value) ||
prop.default,
)
" />
<div v-else-if="prop.type === 'boolean'" class="flex items-center">
<input
type="checkbox"
class="checkbox checkbox-sm mr-2"
:checked="componentData?.attrs?.[prop.name]"
:disabled="prop.isReadOnly"
@change="updateProp(componentData.id, prop.name, ($event.target as HTMLInputElement).checked)"
/>
<input type="checkbox" class="checkbox checkbox-sm mr-2" :checked="componentData?.attrs?.[prop.name]"
:disabled="prop.isReadOnly" @change="
updateProp(
componentData.id,
prop.name,
($event.target as HTMLInputElement).checked,
)
" />
<span>{{ prop.label || prop.name }}</span>
</div>
<select
v-else-if="prop.type === 'select' && prop.options"
class="select select-bordered select-sm w-full"
:value="componentData?.attrs?.[prop.name]"
:disabled="prop.isReadOnly"
@change="(event) => {
<select v-else-if="prop.type === 'select' && prop.options" class="select select-bordered select-sm w-full"
:value="componentData?.attrs?.[prop.name]" :disabled="prop.isReadOnly" @change="
(event) => {
const selectElement = event.target as HTMLSelectElement;
const value = selectElement.value;
if (componentData) {
updateProp(componentData.id, prop.name, value);
}
}"
>
}
">
<option v-for="option in prop.options" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
<p v-else class="text-xs text-warning">不支持的属性类型: {{ prop.type }}</p>
<p v-else class="text-xs text-warning">
不支持的属性类型: {{ prop.type }}
</p>
</div>
</div>
<div v-else-if="componentData && !componentConfig" class="text-base-content opacity-70 text-sm">
正在加载组件配置...
</div>
<div v-else-if="componentData && componentConfig && getComponentProps().length === 0" class="text-base-content opacity-70 text-sm">
<div v-else-if="
componentData && componentConfig && getComponentProps().length === 0
" class="text-base-content opacity-70 text-sm">
此组件没有特有属性可配置
</div>
</CollapsibleSection>
@ -132,13 +126,13 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import CollapsibleSection from './CollapsibleSection.vue';
import { type DiagramPart } from '@/components/diagramManager';
import { ref } from "vue";
import CollapsibleSection from "./CollapsibleSection.vue";
import { type DiagramPart } from "@/components/diagramManager";
import {
type PropertyConfig,
getPropValue
} from '@/components/equipments/componentConfig';
getPropValue,
} from "@/components/equipments/componentConfig";
//
const props = defineProps<{
@ -148,8 +142,13 @@ const props = defineProps<{
//
const emit = defineEmits<{
(e: 'updateProp', componentId: string, propName: string, value: any): void;
(e: 'updateDirectProp', componentId: string, propName: string, value: any): void;
(e: "updateProp", componentId: string, propName: string, value: any): void;
(
e: "updateDirectProp",
componentId: string,
propName: string,
value: any,
): void;
}>();
//
@ -158,23 +157,32 @@ const componentPropsExpanded = ref(true);
//
function updateProp(componentId: string, propName: string, value: any) {
emit('updateProp', componentId, propName, value);
emit("updateProp", componentId, propName, value);
}
//
function updateDirectProp(componentId: string, propName: string, value: any) {
emit('updateDirectProp', componentId, propName, value);
emit("updateDirectProp", componentId, propName, value);
}
//
function getGeneralProps(): PropertyConfig[] {
return props.componentConfig?.props.filter(p => p.isDirectProp && p.name !== 'pins') || [];
return (
props.componentConfig?.props.filter(
(p) => p.isDirectProp && p.name !== "pins",
) || []
);
}
//
function getComponentProps(): PropertyConfig[] {
return props.componentConfig?.props.filter(p => !p.isDirectProp && p.name !== 'pins') || [];
return (
props.componentConfig?.props.filter(
(p) => !p.isDirectProp && p.name !== "pins",
) || []
);
}
</script>
<style scoped>

View File

@ -1,32 +1,22 @@
<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)"
/>
<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"
/>
<DDSPropertyEditor v-model="ddsProperties" @update:modelValue="updateDDSProperties" />
</div>
<!-- 如果选中的组件有pins属性则显示引脚配置区域 -->
<CollapsibleSection
v-if="hasPinsProperty"
title="引脚配置"
v-model:isExpanded="pinsSectionExpanded"
status="default"
>
<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">
@ -37,32 +27,34 @@
<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"
/>
<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"
/>
<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" />
<div v-else class="text-gray-400">
该组件没有提供特殊功能
</div>
</div>
<div v-else class="text-gray-400">
选择元件以查看其功能
</div>
</CollapsibleSection>
<!-- 未来可以在这里添加更多的分区 -->
<!-- 例如
<CollapsibleSection
@ -77,63 +69,77 @@
</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';
import { ref, computed, watch } from 'vue';
//
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"; // VueAPI
import { isNull, isUndefined } from "lodash";
import type { JSX } from "vue/jsx-runtime";
// Pin
//
interface Pin {
pinId: string;
constraint: string;
x: number;
y: number;
pinId: string; // ID
constraint: string; //
x: number; // X
y: number; // Y
}
//
//
const props = defineProps<{
componentData: DiagramPart | null;
componentConfig: { props: PropertyConfig[] } | null;
componentData: DiagramPart | null; //
componentConfig: { props: PropertyConfig[] } | null; //
}>();
//
const propertySectionExpanded = ref(true);
const pinsSectionExpanded = ref(false);
const wireSectionExpanded = ref(false);
//
const propertySectionExpanded = ref(true); //
const pinsSectionExpanded = ref(false); //
const componentCapsExpanded = ref(true); //
const wireSectionExpanded = ref(false); // 线
// DDS
// DDS
const ddsProperties = ref({
frequency: 1000,
phase: 0,
waveform: 'sine',
customWaveformPoints: []
frequency: 1000, // 1000Hz
phase: 0, // 0
waveform: "sine", //
customWaveformPoints: [], //
});
// pins
// pins
const componentPins = ref<Pin[]>([]);
// pins
watch(() => props.componentData?.attrs?.pins, (newPins) => {
// pinspins
watch(
() => props.componentData?.attrs?.pins,
(newPins) => {
if (newPins) {
//
componentPins.value = JSON.parse(JSON.stringify(newPins));
} else {
componentPins.value = [];
}
}, { deep: true, immediate: true });
},
{ deep: true, immediate: true }, //
);
// DDS
watch(() => props.componentData?.attrs, (newAttrs) => {
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 || []
waveform: newAttrs.waveform || "sine",
customWaveformPoints: newAttrs.customWaveformPoints || [],
};
}
}, { deep: true, immediate: true });
},
{ deep: true, immediate: true }, //
);
// pins
const hasPinsProperty = computed(() => {
@ -141,56 +147,180 @@ const hasPinsProperty = computed(() => {
return false;
}
// pins
// 1pins
if (props.componentConfig && props.componentConfig.props) {
return props.componentConfig.props.some(prop => prop.name === 'pins' && prop.isArrayType);
return props.componentConfig.props.some(
(prop) => prop.name === "pins" && prop.isArrayType,
);
}
// attrspins
return 'pins' in props.componentData.attrs;
// 2attrspins
return "pins" in props.componentData.attrs;
});
// DDS
const isDDSComponent = computed(() => {
return props.componentData?.type === 'DDS';
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;
//
(e: "updateProp", componentId: string, propName: string, value: any): void;
//
(
e: "updateDirectProp",
componentId: string,
propName: string,
value: any,
): void;
}>();
// pins
// pins
function updatePins() {
if (props.componentData && props.componentData.id) {
emit('updateProp', props.componentData.id, 'pins', componentPins.value);
// pins
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
// 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);
//
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') {
// JSX
const capsComponent = exposedMethods.getCapabilities();
// DOM
app.unmount();
tempDiv.remove();
return capsComponent;
}
// 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>

View File

@ -1,3 +1,5 @@
import type { JSX } from "vue/jsx-runtime";
// 定义 diagram.json 的类型结构
export interface DiagramData {
version: number;
@ -15,6 +17,7 @@ export interface DiagramPart {
x: number;
y: number;
attrs: Record<string, any>;
capsPage?: JSX.Element;
rotate: number;
group: string;
positionlock: boolean;

View File

@ -1,45 +1,65 @@
<template> <div class="eth-component" :style="{ width: width + 'px', height: height + 'px' }">
<img
src="../equipments/svg/eth.svg"
:width="width"
:height="height"
alt="以太网接口"
class="svg-image"
draggable="false"
/>
<template>
<div class="eth-component" :style="{ width: width + 'px', height: height + 'px' }">
<img src="../equipments/svg/eth.svg" :width="width" :height="height" alt="以太网接口" class="svg-image"
draggable="false" />
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { computed } from "vue";
interface Props {
size?: number;
devIPAddress?: string;
devPort?: string;
}
const props = withDefaults(defineProps<Props>(), {
size: 1
});
const props = withDefaults(defineProps<Props>(), getDefaultProps());
//
const width = computed(() => 100 * props.size);
const height = computed(() => 60 * props.size);
//
defineExpose({
getInfo: () => ({
size: props.size,
type: "ETH",
}),
// getPinPosition
getPinPosition: () => null,
});
</script>
<script lang="ts">
// props
export function getDefaultProps() {
return {
size: 1,
devIPAddress: "127.0.0.1",
devPort: "1234",
};
}
</script>
<style scoped>
.eth-component {
display: block;
user-select: none;
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
-webkit-user-select: none;
/* Safari */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
/* IE/Edge */
}
.svg-image {
width: 100%;
height: 100%;
object-fit: contain;
pointer-events: none; /* 禁止鼠标交互 */
pointer-events: none;
/* 禁止鼠标交互 */
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;

View File

@ -1,56 +1,126 @@
<template>
<div class="motherboard-container" :style="{ width: width + 'px', height: height + 'px', position: 'relative' }">
<svg
xmlns="http://www.w3.org/2000/svg"
:width="width"
:height="height"
:viewBox="`0 0 800 600`"
class="motherboard-svg"
>
<image
href="../equipments/svg/motherboard.svg"
width="100%"
height="100%"
preserveAspectRatio="xMidYMid meet"
/>
<div class="motherboard-container" :style="{
width: width + 'px',
height: height + 'px',
position: 'relative',
}">
<svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" :viewBox="`0 0 800 600`"
class="motherboard-svg">
<image href="../equipments/svg/motherboard.svg" width="100%" height="100%" preserveAspectRatio="xMidYMid meet" />
</svg>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
<script setup lang="tsx">
import { JtagClient, type FileParameter } from "@/APIClient";
import UploadCard from "@/components/UploadCard.vue";
import { useDialogStore } from "@/stores/dialog";
import { toNumber } from "lodash";
import { computed, ref } from "vue";
//
interface MotherBoardProps {
size?: number;
jtagAddr?: string;
jtagPort?: string;
}
const props = withDefaults(defineProps<MotherBoardProps>(), {
size: 1
});
const props = withDefaults(defineProps<MotherBoardProps>(), getDefaultProps());
//
const width = computed(() => 800 * props.size);
const height = computed(() => 600 * props.size);
//
const jtagController = new JtagClient();
const dialog = useDialogStore();
async function uploadBitstream(event: Event, bitstream: File) {
const boardAddress = "127.0.0.1"; //
const boardPort = "1234"; //
if (!boardAddress || !boardPort) {
dialog.error("开发板地址或端口空缺");
return;
}
const fileParam = {
data: bitstream,
fileName: bitstream.name,
};
try {
const resp = await jtagController.uploadBitstream(boardAddress, fileParam);
return resp;
} catch (e) {
dialog.error("上传错误");
}
}
async function downloadBitstream() {
const boardAddress = "127.0.0.1"; //
const boardPort = "1234"; //
if (!boardAddress || !boardPort) {
dialog.error("开发板地址或端口空缺");
return;
}
try {
const resp = await jtagController.downloadBitstream(
boardAddress,
parseInt(boardPort),
);
return resp;
} catch (e) {
dialog.error("上传错误");
console.error(e);
}
}
//
defineExpose({
getInfo: () => ({
size: props.size,
type: 'motherboard'
type: "motherboard",
}),
// getPinPosition
getPinPosition: () => null
getPinPosition: () => null,
getCapabilities: () => (
<div>
<UploadCard
class={"bg-base-200"}
upload-event={uploadBitstream}
download-event={downloadBitstream}
>
{" "}
</UploadCard>
</div>
),
});
</script>
<script lang="ts">
<script lang="tsx">
// props
export function getDefaultProps() {
return {
size: 1
size: 1,
};
}
//
// export function getCapabilities() {
// return (
// <div>
// <UploadCard
// class={"bg-base-200"}
// upload-event={uploadBitstream}
// download-event={downloadBitstream}
// >
// {" "}
// </UploadCard>
// </div>
// );
// }
</script>
<style scoped>
@ -58,14 +128,18 @@ export function getDefaultProps() {
display: block;
position: relative;
user-select: none;
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
-webkit-user-select: none;
/* Safari */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
/* IE/Edge */
}
.motherboard-svg {
width: 100%;
height: 100%;
pointer-events: none; /* 禁止鼠标交互 */
pointer-events: none;
/* 禁止鼠标交互 */
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@ import { createWebHistory, createRouter } from "vue-router";
import LoginView from "../views/LoginView.vue";
import UserView from "../views/UserView.vue";
import TestView from "../views/TestView.vue";
import JtagTest from "../views/JtagTest.vue";
import ProjectView from "../views/ProjectView.vue";
import HomeView from "@/views/HomeView.vue";
@ -11,7 +10,6 @@ const routes = [
{ path: "/login", name: "Login", component: LoginView },
{ path: "/user", name: "User", component: UserView },
{ path: "/test", name: "Test", component: TestView },
{ path: "/test/jtag", name: "JtagTest", component: JtagTest },
{ path: "/project", name: "Project", component: ProjectView },
];

View File

@ -1,45 +0,0 @@
<template>
<div class="h-screen flex flex-col overflow-hidden">
<!-- 主要内容 -->
<div class="flex-1 flex justify-center">
<div class="h-full w-32"></div>
<div class="h-full w-[70%] shadow-2xl flex items-start p-4">
<button class="btn btn-primary h-10 w-30">获取ID Code</button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from "vue";
const eventSource = ref<EventSource>();
const initSource = () => {
eventSource.value = new EventSource("http://localhost:500/api/log/example");
eventSource.value.onmessage = (event) => {
console.log("收到消息内容是:", event.data);
};
eventSource.value.onerror = (error) => {
console.error("SSE 连接出错:", error);
eventSource.value!.close();
};
};
onMounted(() => {
initSource();
});
onUnmounted(() => {
if (eventSource) {
eventSource.value?.close();
}
});
</script>
<style scoped lang="postcss">
@import "../assets/main.css";
</style>

View File

@ -3,78 +3,63 @@
<div class="flex flex-1 overflow-hidden relative">
<!-- 左侧图形化区域 -->
<div class="relative bg-base-200 overflow-hidden h-full" :style="{ width: leftPanelWidth + '%' }">
<DiagramCanvas
ref="diagramCanvas"
:componentModules="componentModules"
@component-selected="handleComponentSelected"
@component-moved="handleComponentMoved"
@component-delete="handleComponentDelete"
@wire-created="handleWireCreated"
@wire-deleted="handleWireDeleted"
@diagram-updated="handleDiagramUpdated"
@open-components="openComponentsMenu"
@load-component-module="handleLoadComponentModule"
/>
<DiagramCanvas ref="diagramCanvas" :componentModules="componentModules"
@component-selected="handleComponentSelected" @component-moved="handleComponentMoved"
@component-delete="handleComponentDelete" @wire-created="handleWireCreated" @wire-deleted="handleWireDeleted"
@diagram-updated="handleDiagramUpdated" @open-components="openComponentsMenu"
@load-component-module="handleLoadComponentModule" />
</div>
<!-- 拖拽分割线 -->
<div
class="resizer cursor-col-resize bg-base-300 hover:bg-primary hover:opacity-70 active:bg-primary active:opacity-90 transition-colors"
@mousedown="startResize"
></div>
class="resizer bg-base-100 hover:bg-primary hover:opacity-70 active:bg-primary active:opacity-90 transition-colors"
@mousedown="startResize"></div>
<!-- 右侧编辑区域 -->
<div class="bg-base-100 h-full overflow-hidden flex flex-col" :style="{ width: (100 - leftPanelWidth) + '%' }">
<div class="overflow-y-auto p-4 flex-1">
<PropertyPanel
:componentData="selectedComponentData"
:componentConfig="selectedComponentConfig"
@updateProp="updateComponentProp"
@updateDirectProp="updateComponentDirectProp"
/>
<div class="bg-base-200 h-full overflow-hidden flex flex-col" :style="{ width: 100 - leftPanelWidth + '%' }">
<div class="overflow-y-auto flex-1">
<PropertyPanel :componentData="selectedComponentData" :componentConfig="selectedComponentConfig"
@updateProp="updateComponentProp" @updateDirectProp="updateComponentDirectProp" />
</div>
</div>
</div> <!-- 元器件选择组件 -->
<ComponentSelector
:open="showComponentsMenu"
@update:open="showComponentsMenu = $event"
@add-component="handleAddComponent"
@add-template="handleAddTemplate"
@close="showComponentsMenu = false"
/>
</div>
<!-- 元器件选择组件 -->
<ComponentSelector :open="showComponentsMenu" @update:open="showComponentsMenu = $event"
@add-component="handleAddComponent" @add-template="handleAddTemplate" @close="showComponentsMenu = false" />
</div>
</template>
<script setup lang="ts">
// wokwi-elements
// import "@wokwi/elements"; // wokwi
import { ref, reactive, computed, onMounted, onUnmounted, defineAsyncComponent, shallowRef } from 'vue'; // defineAsyncComponent shallowRef
import DiagramCanvas from '@/components/DiagramCanvas.vue';
import ComponentSelector from '@/components/ComponentSelector.vue';
import PropertyPanel from '@/components/PropertyPanel.vue';
import type { DiagramData, DiagramPart, ConnectionArray } from '@/components/diagramManager';
import { validateDiagramData } from '@/components/diagramManager';
import { ref, computed, onMounted, onUnmounted, shallowRef } from "vue"; // defineAsyncComponent shallowRef
import DiagramCanvas from "@/components/DiagramCanvas.vue";
import ComponentSelector from "@/components/ComponentSelector.vue";
import PropertyPanel from "@/components/PropertyPanel.vue";
import type { DiagramData, DiagramPart } from "@/components/diagramManager";
import {
type PropertyConfig,
generatePropertyConfigs,
generatePropsFromDefault,
generatePropsFromAttrs,
getPropValue
} from '@/components/equipments/componentConfig'; //
} from "@/components/equipments/componentConfig"; //
// --- ---
const showComponentsMenu = ref(false);
const diagramData = ref<DiagramData>({
version: 1,
author: 'admin',
editor: 'me',
author: "admin",
editor: "me",
parts: [],
connections: []
connections: [],
});
const selectedComponentId = ref<string | null>(null);
const selectedComponentData = computed(() => {
return diagramData.value.parts.find(p => p.id === selectedComponentId.value) || null;
return (
diagramData.value.parts.find((p) => p.id === selectedComponentId.value) ||
null
);
});
const diagramCanvas = ref(null);
@ -88,7 +73,9 @@ interface ComponentModule {
}
const componentModules = shallowRef<Record<string, ComponentModule>>({});
const selectedComponentConfig = shallowRef<{ props: PropertyConfig[] } | null>(null); //
const selectedComponentConfig = shallowRef<{ props: PropertyConfig[] } | null>(
null,
); //
//
async function loadComponentModule(type: string) {
@ -100,7 +87,7 @@ async function loadComponentModule(type: string) {
// 使 markRaw
componentModules.value = {
...componentModules.value,
[type]: module
[type]: module,
};
console.log(`Loaded module for ${type}:`, module);
@ -114,7 +101,7 @@ async function loadComponentModule(type: string) {
//
async function handleLoadComponentModule(type: string) {
console.log('Handling load component module request for:', type);
console.log("Handling load component module request for:", type);
await loadComponentModule(type);
}
@ -125,8 +112,8 @@ const isResizing = ref(false);
//
function startResize(e: MouseEvent) {
isResizing.value = true;
document.addEventListener('mousemove', onResize);
document.addEventListener('mouseup', stopResize);
document.addEventListener("mousemove", onResize);
document.addEventListener("mouseup", stopResize);
e.preventDefault(); //
}
@ -134,7 +121,9 @@ function onResize(e: MouseEvent) {
if (!isResizing.value) return;
//
const container = document.querySelector('.flex-1.overflow-hidden') as HTMLElement;
const container = document.querySelector(
".flex-1.overflow-hidden",
) as HTMLElement;
if (!container) return;
const containerWidth = container.clientWidth;
@ -152,8 +141,8 @@ function onResize(e: MouseEvent) {
function stopResize() {
isResizing.value = false;
document.removeEventListener('mousemove', onResize);
document.removeEventListener('mouseup', stopResize);
document.removeEventListener("mousemove", onResize);
document.removeEventListener("mouseup", stopResize);
}
// --- ---
@ -162,9 +151,13 @@ function openComponentsMenu() {
}
// ComponentSelector
async function handleAddComponent(componentData: { type: string; name: string; props: Record<string, any> }) {
async function handleAddComponent(componentData: {
type: string;
name: string;
props: Record<string, any>;
}) {
// 便使
await loadComponentModule(componentData.type);
const componentModule = await loadComponentModule(componentData.type);
//
const canvasInstance = diagramCanvas.value as any;
@ -174,7 +167,11 @@ async function handleAddComponent(componentData: { type: string; name: string; p
let scale = 1;
try {
if (canvasInstance && canvasInstance.getCanvasPosition && canvasInstance.getScale) {
if (
canvasInstance &&
canvasInstance.getCanvasPosition &&
canvasInstance.getScale
) {
position = canvasInstance.getCanvasPosition();
scale = canvasInstance.getScale();
@ -191,13 +188,28 @@ async function handleAddComponent(componentData: { type: string; name: string; p
}
}
} catch (error) {
console.error('获取画布位置时出错:', error);
console.error("获取画布位置时出错:", error);
}
//
const offsetX = Math.floor(Math.random() * 100) - 50;
const offsetY = Math.floor(Math.random() * 100) - 50;
//
let capsPage = null;
if (
componentModule &&
componentModule.default &&
typeof componentModule.default.getCapabilities === "function"
) {
try {
capsPage = componentModule.default.getCapabilities();
console.log(`获取到${componentData.type}组件的能力页面`);
} catch (error) {
console.error(`获取${componentData.type}组件能力页面失败:`, error);
}
}
// 使diagramManager
const newComponent: DiagramPart = {
id: `component-${Date.now()}`,
@ -205,17 +217,22 @@ async function handleAddComponent(componentData: { type: string; name: string; p
x: Math.round(position.x + offsetX),
y: Math.round(position.y + offsetY),
attrs: componentData.props,
capsPage: capsPage, //
rotate: 0,
group: '',
group: "",
positionlock: false,
hidepins: true,
isOn: true,
index: 0
index: 0,
};
console.log('添加新组件:', newComponent);
console.log("添加新组件:", newComponent);
//
if (canvasInstance && canvasInstance.getDiagramData && canvasInstance.updateDiagramDataDirectly) {
if (
canvasInstance &&
canvasInstance.getDiagramData &&
canvasInstance.updateDiagramDataDirectly
) {
const currentData = canvasInstance.getDiagramData();
currentData.parts.push(newComponent);
canvasInstance.updateDiagramDataDirectly(currentData);
@ -223,19 +240,27 @@ 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);
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.updateDiagramDataDirectly) {
console.error('没有可用的画布实例添加模板');
if (
!canvasInstance ||
!canvasInstance.getDiagramData ||
!canvasInstance.updateDiagramDataDirectly
) {
console.error("没有可用的画布实例添加模板");
return;
}
//
const currentData = canvasInstance.getDiagramData();
console.log('=== 当前图表组件数量:', currentData.parts.length);
console.log("=== 当前图表组件数量:", currentData.parts.length);
// IDID
const idPrefix = `template-${Date.now()}-`;
@ -244,7 +269,11 @@ async function handleAddTemplate(templateData: { id: string; name: string; templ
//
let viewportCenter = { x: 300, y: 200 }; //
try {
if (canvasInstance && canvasInstance.getCanvasPosition && canvasInstance.getScale) {
if (
canvasInstance &&
canvasInstance.getCanvasPosition &&
canvasInstance.getScale
) {
const position = canvasInstance.getCanvasPosition();
const scale = canvasInstance.getScale();
@ -258,26 +287,44 @@ async function handleAddTemplate(templateData: { id: string; name: string; templ
// (handleAddComponent)
viewportCenter.x = (viewportWidth / 2 - position.x) / scale;
viewportCenter.y = (viewportHeight / 2 - position.y) / scale;
console.log(`=== 计算的视口中心: x=${viewportCenter.x}, y=${viewportCenter.y}, scale=${scale}`);
console.log(
`=== 计算的视口中心: x=${viewportCenter.x}, y=${viewportCenter.y}, scale=${scale}`,
);
}
}
} catch (error) {
console.error('获取视口中心位置时出错:', error);
console.error("获取视口中心位置时出错:", error);
}
console.log('=== 使用视口中心添加模板组件:', viewportCenter);
console.log("=== 使用视口中心添加模板组件:", viewportCenter);
//
const mainPart = templateData.template.parts[0];
//
const newParts = templateData.template.parts.map((part: any) => {
const newParts = await Promise.all(
templateData.template.parts.map(async (part: any) => {
// ID
const newPart = JSON.parse(JSON.stringify(part));
newPart.id = `${idPrefix}${part.id}`;
//
try {
const componentModule = await loadComponentModule(part.type);
if (
componentModule &&
componentModule.default &&
typeof componentModule.default.getCapabilities === "function"
) {
newPart.capsPage = componentModule.default.getCapabilities();
console.log(`加载模板组件${part.type}组件的能力页面成功`);
}
} catch (error) {
console.error(`加载模板组件${part.type}的能力页面失败:`, error);
}
//
if (typeof newPart.x === 'number' && typeof newPart.y === 'number') {
if (typeof newPart.x === "number" && typeof newPart.y === "number") {
const oldX = newPart.x;
const oldY = newPart.y;
@ -289,11 +336,14 @@ async function handleAddTemplate(templateData: { id: string; name: string; templ
newPart.x = viewportCenter.x + relativeX;
newPart.y = viewportCenter.y + relativeY;
console.log(`=== 组件[${newPart.id}]位置调整: (${oldX},${oldY}) -> (${newPart.x},${newPart.y})`);
console.log(
`=== 组件[${newPart.id}]位置调整: (${oldX},${oldY}) -> (${newPart.x},${newPart.y})`,
);
}
return newPart;
});
}),
);
//
currentData.parts.push(...newParts);
@ -307,14 +357,15 @@ async function handleAddTemplate(templateData: { id: string; name: string; templ
});
// ID
const newConnections = templateData.template.connections.map((conn: any) => {
const newConnections = templateData.template.connections.map(
(conn: any) => {
// ( [from, to, type, path])
if (Array.isArray(conn)) {
const [from, to, type, path] = conn;
// IDID
const fromParts = from.split(':');
const toParts = to.split(':');
const fromParts = from.split(":");
const toParts = to.split(":");
if (fromParts.length === 2 && toParts.length === 2) {
const fromComponentId = fromParts[0];
@ -330,20 +381,21 @@ async function handleAddTemplate(templateData: { id: string; name: string; templ
}
}
return conn; //
});
},
);
//
currentData.connections.push(...newConnections);
}
//
canvasInstance.updateDiagramDataDirectly(currentData);
console.log('=== 更新图表数据完成,新组件数量:', currentData.parts.length);
console.log("=== 更新图表数据完成,新组件数量:", currentData.parts.length);
//
showToast(`已添加 ${templateData.name} 模板`, 'success');
showToast(`已添加 ${templateData.name} 模板`, "success");
} else {
console.error('模板格式错误缺少parts数组');
showToast('模板格式错误', 'error');
console.error("模板格式错误缺少parts数组");
showToast("模板格式错误", "error");
}
}
@ -366,16 +418,21 @@ async function handleComponentSelected(componentData: DiagramPart | null) {
propConfigs.push(...directPropConfigs);
// 2. 使getDefaultProps
if (typeof moduleRef.getDefaultProps === 'function') {
if (typeof moduleRef.getDefaultProps === "function") {
// getDefaultProps
const defaultProps = moduleRef.getDefaultProps();
const defaultPropConfigs = generatePropsFromDefault(defaultProps);
propConfigs.push(...defaultPropConfigs);
selectedComponentConfig.value = { props: propConfigs };
console.log(`Built config for ${componentData.type} from getDefaultProps:`, selectedComponentConfig.value);
console.log(
`Built config for ${componentData.type} from getDefaultProps:`,
selectedComponentConfig.value,
);
} else {
console.warn(`Component ${componentData.type} does not export getDefaultProps method.`);
console.warn(
`Component ${componentData.type} does not export getDefaultProps method.`,
);
//
const attrs = componentData.attrs || {};
const attrPropConfigs = generatePropsFromAttrs(attrs);
@ -384,7 +441,10 @@ async function handleComponentSelected(componentData: DiagramPart | null) {
selectedComponentConfig.value = { props: propConfigs };
}
} catch (error) {
console.error(`Error building config for ${componentData.type}:`, error);
console.error(
`Error building config for ${componentData.type}:`,
error,
);
selectedComponentConfig.value = { props: [] };
}
} else {
@ -397,12 +457,12 @@ async function handleComponentSelected(componentData: DiagramPart | null) {
//
function handleDiagramUpdated(data: DiagramData) {
diagramData.value = data;
console.log('Diagram data updated:', data);
console.log("Diagram data updated:", data);
}
//
function handleComponentMoved(moveData: { id: string; x: number; y: number }) {
const part = diagramData.value.parts.find(p => p.id === moveData.id);
const part = diagramData.value.parts.find((p) => p.id === moveData.id);
if (part) {
part.x = moveData.x;
part.y = moveData.y;
@ -412,57 +472,73 @@ function handleComponentMoved(moveData: { id: string; x: number; y: number }) {
//
function handleComponentDelete(componentId: string) {
//
const component = diagramData.value.parts.find(p => p.id === componentId);
const component = diagramData.value.parts.find((p) => p.id === componentId);
if (!component) return;
// ID
const componentsToDelete: string[] = [componentId];
//
if (component.group && component.group !== '') {
if (component.group && component.group !== "") {
const groupMembers = diagramData.value.parts.filter(
p => p.group === component.group && p.id !== componentId
(p) => p.group === component.group && p.id !== componentId,
);
// ID
componentsToDelete.push(...groupMembers.map(p => p.id));
console.log(`删除组件 ${componentId} 及其组 ${component.group} 中的 ${groupMembers.length} 个组件`);
componentsToDelete.push(...groupMembers.map((p) => p.id));
console.log(
`删除组件 ${componentId} 及其组 ${component.group} 中的 ${groupMembers.length} 个组件`,
);
}
//
diagramData.value.parts = diagramData.value.parts.filter(
p => !componentsToDelete.includes(p.id)
(p) => !componentsToDelete.includes(p.id),
);
//
diagramData.value.connections = diagramData.value.connections.filter(
connection => {
(connection) => {
for (const id of componentsToDelete) {
if (connection[0].startsWith(`${id}:`) || connection[1].startsWith(`${id}:`)) {
if (
connection[0].startsWith(`${id}:`) ||
connection[1].startsWith(`${id}:`)
) {
return false;
}
}
return true;
}
},
);
//
if (selectedComponentId.value && componentsToDelete.includes(selectedComponentId.value)) {
if (
selectedComponentId.value &&
componentsToDelete.includes(selectedComponentId.value)
) {
selectedComponentId.value = null;
selectedComponentConfig.value = null;
}
}
//
function updateComponentProp(componentId: string, propName: string, value: any) {
function updateComponentProp(
componentId: string,
propName: string,
value: any,
) {
const canvasInstance = diagramCanvas.value as any;
if (!canvasInstance || !canvasInstance.getDiagramData || !canvasInstance.updateDiagramDataDirectly) {
console.error('没有可用的画布实例进行属性更新');
if (
!canvasInstance ||
!canvasInstance.getDiagramData ||
!canvasInstance.updateDiagramDataDirectly
) {
console.error("没有可用的画布实例进行属性更新");
return;
}
// value使
if (value !== null && typeof value === 'object' && 'value' in value) {
if (value !== null && typeof value === "object" && "value" in value) {
value = value.value;
}
@ -482,15 +558,27 @@ function updateComponentProp(componentId: string, propName: string, value: any)
}
canvasInstance.updateDiagramDataDirectly(currentData);
console.log(`更新组件${componentId}的属性${propName}为:`, value, typeof value);
console.log(
`更新组件${componentId}的属性${propName}为:`,
value,
typeof value,
);
}
}
//
function updateComponentDirectProp(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.updateDiagramDataDirectly) {
console.error('没有可用的画布实例进行属性更新');
if (
!canvasInstance ||
!canvasInstance.getDiagramData ||
!canvasInstance.updateDiagramDataDirectly
) {
console.error("没有可用的画布实例进行属性更新");
return;
}
@ -502,7 +590,11 @@ function updateComponentDirectProp(componentId: string, propName: string, value:
part[propName] = value;
canvasInstance.updateDiagramDataDirectly(currentData);
console.log(`更新组件${componentId}的直接属性${propName}为:`, value, typeof value);
console.log(
`更新组件${componentId}的直接属性${propName}为:`,
value,
typeof value,
);
}
}
@ -510,12 +602,12 @@ function updateComponentDirectProp(componentId: string, propName: string, value:
// 线
function handleWireCreated(wireData: any) {
console.log('Wire created:', wireData);
console.log("Wire created:", wireData);
}
// 线
function handleWireDeleted(wireId: string) {
console.log('Wire deleted:', wireId);
console.log("Wire deleted:", wireId);
}
// diagram
@ -529,10 +621,15 @@ function exportDiagram() {
// --- ---
const showNotification = ref(false);
const notificationMessage = ref('');
const notificationType = ref<'success' | 'error' | 'info'>('info');
const notificationMessage = ref("");
const notificationType = ref<"success" | "error" | "info">("info");
function showToast(message: string, type: 'success' | 'error' | 'info' = 'info', duration = 3000) { const canvasInstance = diagramCanvas.value as any;
function showToast(
message: string,
type: "success" | "error" | "info" = "info",
duration = 3000,
) {
const canvasInstance = diagramCanvas.value as any;
if (canvasInstance && canvasInstance.showToast) {
canvasInstance.showToast(message, type, duration);
} else {
@ -555,7 +652,7 @@ function showToast(message: string, type: 'success' | 'error' | 'info' = 'info',
// --- ---
onMounted(async () => {
//
console.log('ProjectView mounted, diagram canvas ref:', diagramCanvas.value);
console.log("ProjectView mounted, diagram canvas ref:", diagramCanvas.value);
//
const canvasInstance = diagramCanvas.value as any;
@ -564,24 +661,24 @@ onMounted(async () => {
// 使
const componentTypes = new Set<string>();
diagramData.value.parts.forEach(part => {
diagramData.value.parts.forEach((part) => {
componentTypes.add(part.type);
});
console.log('Preloading component modules:', Array.from(componentTypes));
console.log("Preloading component modules:", Array.from(componentTypes));
//
await Promise.all(
Array.from(componentTypes).map(type => loadComponentModule(type))
Array.from(componentTypes).map((type) => loadComponentModule(type)),
);
console.log('All component modules loaded');
console.log("All component modules loaded");
}
});
onUnmounted(() => {
document.removeEventListener('mousemove', onResize);
document.removeEventListener('mouseup', stopResize);
document.removeEventListener("mousemove", onResize);
document.removeEventListener("mouseup", stopResize);
});
</script>
@ -593,13 +690,13 @@ onUnmounted(() => {
.resizer {
width: 6px;
height: 100%;
background-color: hsl(var(--b3));
cursor: col-resize;
transition: background-color 0.3s;
z-index: 10;
}
.resizer:hover, .resizer:active {
.resizer:hover,
.resizer:active {
width: 6px;
}
@ -618,6 +715,7 @@ onUnmounted(() => {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
@ -625,7 +723,8 @@ onUnmounted(() => {
}
/* 确保滚动行为仅在需要时出现 */
html, body {
html,
body {
overflow: hidden;
height: 100%;
margin: 0;

View File

@ -6,7 +6,7 @@
</div>
<div class="divider"></div>
<div class="w-full">
<div class="collapse bg-primary border-base-300 border">
<div class="collapse bg-primary">
<input type="checkbox" />
<div class="collapse-title font-semibold text-lg text-white">
自定义开发板参数
@ -73,7 +73,7 @@ async function uploadBitstream(event: Event, bitstream: File) {
boardAddress.value,
fileParam,
);
return resp;
return resp
} catch (e) {
dialog.error("上传错误");
}

View File

@ -7,5 +7,8 @@
{
"path": "./tsconfig.app.json"
}
]
],
"compilerOptions": {
"jsx": "preserve"
}
}