feat: upload and download bitstream from the component of project view
This commit is contained in:
		@@ -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>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/App.vue
									
									
									
									
									
								
							@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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.addEventListener('transitionend', () => {
 | 
			
		||||
      done();
 | 
			
		||||
    }, { once: true });
 | 
			
		||||
    element.style.height = height + "px";
 | 
			
		||||
 | 
			
		||||
    element.addEventListener(
 | 
			
		||||
      "transitionend",
 | 
			
		||||
      () => {
 | 
			
		||||
        done();
 | 
			
		||||
      },
 | 
			
		||||
      { 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.addEventListener('transitionend', () => {
 | 
			
		||||
      done();
 | 
			
		||||
    }, { once: true });
 | 
			
		||||
    element.style.height = "0px";
 | 
			
		||||
 | 
			
		||||
    element.addEventListener(
 | 
			
		||||
      "transitionend",
 | 
			
		||||
      () => {
 | 
			
		||||
        done();
 | 
			
		||||
      },
 | 
			
		||||
      { 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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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">
 | 
			
		||||
 
 | 
			
		||||
@@ -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) => {
 | 
			
		||||
                const selectElement = event.target as HTMLSelectElement;
 | 
			
		||||
                const value = selectElement.value;
 | 
			
		||||
                if (componentData) {
 | 
			
		||||
                  updateProp(componentData.id, prop.name, value);
 | 
			
		||||
            </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) => {
 | 
			
		||||
                  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 { 
 | 
			
		||||
  type PropertyConfig, 
 | 
			
		||||
  getPropValue 
 | 
			
		||||
} from '@/components/equipments/componentConfig';
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import CollapsibleSection from "./CollapsibleSection.vue";
 | 
			
		||||
import { type DiagramPart } from "@/components/diagramManager";
 | 
			
		||||
import {
 | 
			
		||||
  type PropertyConfig,
 | 
			
		||||
  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>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,68 +1,60 @@
 | 
			
		||||
<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">
 | 
			
		||||
          <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"
 | 
			
		||||
              />
 | 
			
		||||
              <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,120 +69,258 @@
 | 
			
		||||
</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"; // Vue核心API
 | 
			
		||||
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) => {
 | 
			
		||||
  if (newPins) {
 | 
			
		||||
    componentPins.value = JSON.parse(JSON.stringify(newPins));
 | 
			
		||||
  } else {
 | 
			
		||||
    componentPins.value = [];
 | 
			
		||||
  }
 | 
			
		||||
}, { deep: true, immediate: true });
 | 
			
		||||
// 监听组件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) {
 | 
			
		||||
    ddsProperties.value = {
 | 
			
		||||
      frequency: newAttrs.frequency || 1000,
 | 
			
		||||
      phase: newAttrs.phase || 0,
 | 
			
		||||
      waveform: newAttrs.waveform || 'sine',
 | 
			
		||||
      customWaveformPoints: newAttrs.customWaveformPoints || []
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}, { deep: true, immediate: true });
 | 
			
		||||
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;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  // 检查配置中是否有pins属性
 | 
			
		||||
 | 
			
		||||
  // 方法1:检查配置中是否有pins属性
 | 
			
		||||
  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,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  // 或直接检查attrs中是否有pins属性
 | 
			
		||||
  return 'pins' in props.componentData.attrs;
 | 
			
		||||
 | 
			
		||||
  // 方法2:直接检查attrs中是否有pins属性
 | 
			
		||||
  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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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
											
										
									
								
							@@ -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 },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -1,80 +1,65 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="h-screen flex flex-col overflow-hidden">
 | 
			
		||||
    <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 { 
 | 
			
		||||
  type PropertyConfig, 
 | 
			
		||||
  generatePropertyConfigs, 
 | 
			
		||||
  generatePropsFromDefault, 
 | 
			
		||||
  generatePropsFromAttrs, 
 | 
			
		||||
  getPropValue 
 | 
			
		||||
} from '@/components/equipments/componentConfig'; // 引入组件配置工具
 | 
			
		||||
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,
 | 
			
		||||
} 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) {
 | 
			
		||||
@@ -96,13 +83,13 @@ async function loadComponentModule(type: string) {
 | 
			
		||||
    try {
 | 
			
		||||
      // 假设组件都在 src/components/equipments/ 目录下,且文件名与 type 相同
 | 
			
		||||
      const module = await import(`../components/equipments/${type}.vue`);
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      // 使用 markRaw 包装模块,避免不必要的响应式处理
 | 
			
		||||
      componentModules.value = {
 | 
			
		||||
        ...componentModules.value,
 | 
			
		||||
        [type]: module
 | 
			
		||||
        [type]: module,
 | 
			
		||||
      };
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      console.log(`Loaded module for ${type}:`, module);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error(`Failed to load component module ${type}:`, error);
 | 
			
		||||
@@ -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,35 +112,37 @@ 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(); // 防止文本选择
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
  const mouseX = e.clientX;
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  // 计算左侧面板应占的百分比
 | 
			
		||||
  let newWidth = (mouseX / containerWidth) * 100;
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  // 限制最小宽度和最大宽度
 | 
			
		||||
  newWidth = Math.max(20, Math.min(newWidth, 80));
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  // 更新宽度
 | 
			
		||||
  leftPanelWidth.value = newWidth;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function stopResize() {
 | 
			
		||||
  isResizing.value = false;
 | 
			
		||||
  document.removeEventListener('mousemove', onResize);
 | 
			
		||||
  document.removeEventListener('mouseup', stopResize);
 | 
			
		||||
  document.removeEventListener("mousemove", onResize);
 | 
			
		||||
  document.removeEventListener("mouseup", stopResize);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- 元器件操作 ---
 | 
			
		||||
@@ -162,42 +151,65 @@ 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;
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  // 获取当前画布的位置信息
 | 
			
		||||
  let position = { x: 100, y: 100 };
 | 
			
		||||
  let scale = 1;
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    if (canvasInstance && canvasInstance.getCanvasPosition && canvasInstance.getScale) {
 | 
			
		||||
    if (
 | 
			
		||||
      canvasInstance &&
 | 
			
		||||
      canvasInstance.getCanvasPosition &&
 | 
			
		||||
      canvasInstance.getScale
 | 
			
		||||
    ) {
 | 
			
		||||
      position = canvasInstance.getCanvasPosition();
 | 
			
		||||
      scale = canvasInstance.getScale();
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      // 获取画布容器
 | 
			
		||||
      const canvasContainer = canvasInstance.$el as HTMLElement;
 | 
			
		||||
      if (canvasContainer) {
 | 
			
		||||
        // 计算可视区域中心点在画布坐标系中的位置
 | 
			
		||||
        const viewportWidth = canvasContainer.clientWidth;
 | 
			
		||||
        const viewportHeight = canvasContainer.clientHeight;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // 计算画布中心点的坐标
 | 
			
		||||
        position.x = (viewportWidth / 2 - position.x) / scale;
 | 
			
		||||
        position.y = (viewportHeight / 2 - position.y) / scale;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } 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,81 +240,114 @@ 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);
 | 
			
		||||
 | 
			
		||||
  // 生成唯一ID前缀,以确保添加的组件ID不重复
 | 
			
		||||
  const idPrefix = `template-${Date.now()}-`;
 | 
			
		||||
    // 处理模板组件并添加到图表
 | 
			
		||||
  // 处理模板组件并添加到图表
 | 
			
		||||
  if (templateData.template && templateData.template.parts) {
 | 
			
		||||
    // 获取当前视口中心位置
 | 
			
		||||
    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();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // 获取画布容器
 | 
			
		||||
        const canvasContainer = canvasInstance.$el as HTMLElement;
 | 
			
		||||
        if (canvasContainer) {
 | 
			
		||||
          // 计算可视区域中心点在画布坐标系中的位置
 | 
			
		||||
          const viewportWidth = canvasContainer.clientWidth;
 | 
			
		||||
          const viewportHeight = canvasContainer.clientHeight;
 | 
			
		||||
          
 | 
			
		||||
 | 
			
		||||
          // 计算视口中心点的坐标 (与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) => {
 | 
			
		||||
      // 创建组件副本并分配新ID
 | 
			
		||||
      const newPart = JSON.parse(JSON.stringify(part));
 | 
			
		||||
      newPart.id = `${idPrefix}${part.id}`;
 | 
			
		||||
      
 | 
			
		||||
      // 计算相对于主要组件的偏移量,保持模板内部组件的相对位置关系
 | 
			
		||||
      if (typeof newPart.x === 'number' && typeof newPart.y === 'number') {
 | 
			
		||||
        const oldX = newPart.x;
 | 
			
		||||
        const oldY = newPart.y;
 | 
			
		||||
        
 | 
			
		||||
        // 计算相对位置(相对于主要组件)
 | 
			
		||||
        const relativeX = part.x - mainPart.x;
 | 
			
		||||
        const relativeY = part.y - mainPart.y;
 | 
			
		||||
        
 | 
			
		||||
        // 应用到视口中心位置
 | 
			
		||||
        newPart.x = viewportCenter.x + relativeX;
 | 
			
		||||
        newPart.y = viewportCenter.y + relativeY;
 | 
			
		||||
        
 | 
			
		||||
        console.log(`=== 组件[${newPart.id}]位置调整: (${oldX},${oldY}) -> (${newPart.x},${newPart.y})`);
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      return newPart;
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    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") {
 | 
			
		||||
          const oldX = newPart.x;
 | 
			
		||||
          const oldY = newPart.y;
 | 
			
		||||
 | 
			
		||||
          // 计算相对位置(相对于主要组件)
 | 
			
		||||
          const relativeX = part.x - mainPart.x;
 | 
			
		||||
          const relativeY = part.y - mainPart.y;
 | 
			
		||||
 | 
			
		||||
          // 应用到视口中心位置
 | 
			
		||||
          newPart.x = viewportCenter.x + relativeX;
 | 
			
		||||
          newPart.y = viewportCenter.y + relativeY;
 | 
			
		||||
 | 
			
		||||
          console.log(
 | 
			
		||||
            `=== 组件[${newPart.id}]位置调整: (${oldX},${oldY}) -> (${newPart.x},${newPart.y})`,
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return newPart;
 | 
			
		||||
      }),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // 向图表添加新组件
 | 
			
		||||
    currentData.parts.push(...newParts);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // 处理连接关系
 | 
			
		||||
    if (templateData.template.connections) {
 | 
			
		||||
      // 创建一个映射表用于转换旧组件ID到新组件ID
 | 
			
		||||
@@ -305,45 +355,47 @@ async function handleAddTemplate(templateData: { id: string; name: string; templ
 | 
			
		||||
      templateData.template.parts.forEach((part: any) => {
 | 
			
		||||
        idMap[part.id] = `${idPrefix}${part.id}`;
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      // 添加连接,更新组件ID引用
 | 
			
		||||
      const newConnections = templateData.template.connections.map((conn: any) => {
 | 
			
		||||
        // 处理连接数据 (格式为 [from, to, type, path])
 | 
			
		||||
        if (Array.isArray(conn)) {
 | 
			
		||||
          const [from, to, type, path] = conn;
 | 
			
		||||
          
 | 
			
		||||
          // 从连接字符串中提取组件ID和引脚ID
 | 
			
		||||
          const fromParts = from.split(':');
 | 
			
		||||
          const toParts = to.split(':');
 | 
			
		||||
          
 | 
			
		||||
          if (fromParts.length === 2 && toParts.length === 2) {
 | 
			
		||||
            const fromComponentId = fromParts[0];
 | 
			
		||||
            const fromPinId = fromParts[1];
 | 
			
		||||
            const toComponentId = toParts[0];
 | 
			
		||||
            const toPinId = toParts[1];
 | 
			
		||||
            
 | 
			
		||||
            // 创建新的连接字符串,使用新的组件ID
 | 
			
		||||
            const newFrom = `${idMap[fromComponentId] || fromComponentId}:${fromPinId}`;
 | 
			
		||||
            const newTo = `${idMap[toComponentId] || toComponentId}:${toPinId}`;
 | 
			
		||||
            
 | 
			
		||||
            return [newFrom, newTo, type, path];
 | 
			
		||||
      const newConnections = templateData.template.connections.map(
 | 
			
		||||
        (conn: any) => {
 | 
			
		||||
          // 处理连接数据 (格式为 [from, to, type, path])
 | 
			
		||||
          if (Array.isArray(conn)) {
 | 
			
		||||
            const [from, to, type, path] = conn;
 | 
			
		||||
 | 
			
		||||
            // 从连接字符串中提取组件ID和引脚ID
 | 
			
		||||
            const fromParts = from.split(":");
 | 
			
		||||
            const toParts = to.split(":");
 | 
			
		||||
 | 
			
		||||
            if (fromParts.length === 2 && toParts.length === 2) {
 | 
			
		||||
              const fromComponentId = fromParts[0];
 | 
			
		||||
              const fromPinId = fromParts[1];
 | 
			
		||||
              const toComponentId = toParts[0];
 | 
			
		||||
              const toPinId = toParts[1];
 | 
			
		||||
 | 
			
		||||
              // 创建新的连接字符串,使用新的组件ID
 | 
			
		||||
              const newFrom = `${idMap[fromComponentId] || fromComponentId}:${fromPinId}`;
 | 
			
		||||
              const newTo = `${idMap[toComponentId] || toComponentId}:${toPinId}`;
 | 
			
		||||
 | 
			
		||||
              return [newFrom, newTo, type, path];
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return conn; // 如果格式不匹配,保持原样
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
          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");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -355,36 +407,44 @@ async function handleComponentSelected(componentData: DiagramPart | null) {
 | 
			
		||||
  if (componentData) {
 | 
			
		||||
    // 先加载组件模块
 | 
			
		||||
    const moduleRef = await loadComponentModule(componentData.type);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    if (moduleRef) {
 | 
			
		||||
      try {
 | 
			
		||||
        // 使用组件配置工具创建配置项
 | 
			
		||||
        const propConfigs: PropertyConfig[] = [];
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // 1. 自动获取组件属性信息 - 利用DiagramPart接口结构
 | 
			
		||||
        const directPropConfigs = generatePropertyConfigs(componentData);
 | 
			
		||||
        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);
 | 
			
		||||
          propConfigs.push(...attrPropConfigs);
 | 
			
		||||
          
 | 
			
		||||
 | 
			
		||||
          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,63 +472,79 @@ 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;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  const currentData = canvasInstance.getDiagramData();
 | 
			
		||||
  const part = currentData.parts.find((p: DiagramPart) => p.id === componentId);
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  if (part) {
 | 
			
		||||
    // 检查是否为基本属性
 | 
			
		||||
    if (propName in part) {
 | 
			
		||||
@@ -480,29 +556,45 @@ function updateComponentProp(componentId: string, propName: string, value: any)
 | 
			
		||||
      }
 | 
			
		||||
      part.attrs[propName] = value;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  const currentData = canvasInstance.getDiagramData();
 | 
			
		||||
  const part = currentData.parts.find((p: DiagramPart) => p.id === componentId);
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  if (part) {
 | 
			
		||||
    // @ts-ignore: 动态属性赋值
 | 
			
		||||
    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 {
 | 
			
		||||
@@ -540,7 +637,7 @@ function showToast(message: string, type: 'success' | 'error' | 'info' = 'info',
 | 
			
		||||
    notificationMessage.value = message;
 | 
			
		||||
    notificationType.value = type;
 | 
			
		||||
    showNotification.value = true;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // 设置自动消失
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      showNotification.value = false;
 | 
			
		||||
@@ -555,33 +652,33 @@ 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;
 | 
			
		||||
  if (canvasInstance && canvasInstance.getDiagramData) {
 | 
			
		||||
    diagramData.value = canvasInstance.getDiagramData();
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // 预加载所有使用的组件模块,以确保它们在渲染时可用
 | 
			
		||||
    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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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("上传错误");
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -7,5 +7,8 @@
 | 
			
		||||
    {
 | 
			
		||||
      "path": "./tsconfig.app.json"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
  ],
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "jsx": "preserve"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user