feat: upload and download bitstream from the component of project view
This commit is contained in:
@@ -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
Reference in New Issue
Block a user