This repository has been archived on 2025-10-29. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
FPGA_WebLab/src/components/LabCanvas/ComponentSelector.vue

315 lines
8.3 KiB
Vue

<template>
<div>
<!-- 元器件选择菜单 (Drawer) -->
<div class="drawer drawer-end z-50">
<input
id="component-drawer"
type="checkbox"
class="drawer-toggle"
v-model="showComponentsMenu"
/>
<div class="drawer-side">
<label
for="component-drawer"
aria-label="close sidebar"
class="drawer-overlay !bg-opacity-50"
></label>
<div
class="menu p-0 w-[460px] min-h-full bg-base-100 shadow-xl flex flex-col"
>
<!-- 菜单头部 -->
<div
class="p-6 border-b bg-base-200 border-base-300 flex justify-between items-center"
>
<h3 class="text-xl font-bold text-primary flex items-center gap-2">
<Plus :size="20" class="text-primary" />
添加元器件
</h3>
<button
class="btn btn-ghost btn-sm btn-circle"
@click="closeMenu"
>
<X :size="20" />
</button>
</div>
<!-- 导航栏 -->
<div class="tabs tabs-boxed bg-base-200 mx-6 mt-4 rounded-box">
<a
class="tab"
:class="{ 'tab-active': activeTab === 'components' }"
@click="activeTab = 'components'"
>元器件</a
>
<a
class="tab"
:class="{ 'tab-active': activeTab === 'templates' }"
@click="activeTab = 'templates'"
>模板</a
>
<a
class="tab"
:class="{ 'tab-active': activeTab === 'virtual' }"
@click="activeTab = 'virtual'"
>虚拟外设</a
>
</div>
<!-- 搜索框 -->
<div class="px-6 py-4 w-full">
<label class="input w-full">
<Search :size="16" class="h-[1em] opacity-50" />
<input
type="text"
placeholder="搜索..."
class="grow"
v-model="searchQuery"
/>
</label>
</div>
<!-- 统一的项目列表 -->
<ItemList
v-if="activeTab === 'components'"
:items="availableComponents"
:search-query="searchQuery"
:component-modules="componentModules"
:no-results-message="'没有找到匹配的元器件'"
item-type="component"
@item-click="addComponent"
@clear-search="searchQuery = ''"
/>
<ItemList
v-if="activeTab === 'templates'"
:items="availableTemplates"
:search-query="searchQuery"
:component-modules="componentModules"
:no-results-message="'没有找到匹配的模板'"
item-type="template"
@item-click="addTemplate"
@clear-search="searchQuery = ''"
/>
<ItemList
v-if="activeTab === 'virtual'"
:items="availableVirtualDevices"
:search-query="searchQuery"
:component-modules="componentModules"
:no-results-message="'没有找到匹配的虚拟外设'"
item-type="virtual"
@item-click="addComponent"
@clear-search="searchQuery = ''"
/>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, shallowRef, onMounted } from "vue";
import { Plus, X, Search } from "lucide-vue-next";
import ItemList from "./ItemList.vue";
import { useAlertStore } from "@/components/Alert";
import {
availableComponents,
availableVirtualDevices,
availableTemplates,
getAllComponentTypes,
type ComponentConfig,
type VirtualDeviceConfig,
type TemplateConfig,
useComponentManager, // 导入 componentManager
} from "./index.ts";
// Props 定义
interface Props {
open: boolean;
}
const props = defineProps<Props>();
// 定义组件发出的事件(保留部分必要的事件)
const emit = defineEmits([
"close",
"update:open",
]);
// 使用 componentManager
const componentManager = useComponentManager();
// 使用 Alert 系统
const alert = useAlertStore();
// 当前激活的选项卡
const activeTab = ref("components");
// --- 搜索功能 ---
const searchQuery = ref("");
// 显示/隐藏组件菜单
const showComponentsMenu = computed({
get: () => props.open,
set: (value) => emit("update:open", value),
});
// 组件模块缓存
const componentModules = shallowRef<Record<string, any>>({});
// 动态加载组件定义
async function loadComponentModule(type: string) {
if (!componentModules.value[type]) {
try {
// 假设组件都在 src/components/equipments/ 目录下,且文件名与 type 相同
const module = await import(`@/components/equipments/${type}.vue`);
// 将模块添加到缓存中
componentModules.value = {
...componentModules.value,
[type]: module,
};
console.log(`Loaded module for ${type}:`, module);
} catch (error) {
console.error(`Failed to load component module ${type}:`, error);
return null;
}
}
return componentModules.value[type];
}
// 预加载组件模块
async function preloadComponentModules() {
const allTypes = getAllComponentTypes();
for (const type of allTypes) {
try {
await loadComponentModule(type);
} catch (error) {
console.error(`Failed to preload component ${type}:`, error);
}
}
}
// 关闭菜单
function closeMenu() {
emit("update:open", false);
emit("close");
}
// 添加新元器件 - 使用 componentManager
async function addComponent(
componentTemplate: ComponentConfig | VirtualDeviceConfig,
) {
if (!componentManager) {
console.error("ComponentManager not available");
return;
}
// 先加载组件模块
const moduleRef = await loadComponentModule(componentTemplate.type);
let defaultProps: Record<string, any> = {};
// 尝试直接调用组件导出的getDefaultProps方法
if (moduleRef) {
if (typeof moduleRef.getDefaultProps === "function") {
defaultProps = moduleRef.getDefaultProps();
console.log(
`Got default props from ${componentTemplate.type}:`,
defaultProps,
);
} else {
// 回退到配置文件
console.log(`No getDefaultProps found for ${componentTemplate.type}`);
}
} else {
console.log(`Failed to load module for ${componentTemplate.type}`);
}
try {
// 使用 componentManager 添加组件
await componentManager.addComponent({
type: componentTemplate.type,
name: componentTemplate.name,
props: defaultProps,
});
// 显示成功消息
alert?.success(`成功添加元器件: ${componentTemplate.name}`);
// 关闭菜单
closeMenu();
} catch (error) {
console.error("添加元器件失败:", error);
alert?.error("添加元器件失败,请检查控制台错误信息");
}
}
// 添加模板 - 使用 componentManager
async function addTemplate(template: TemplateConfig) {
if (!componentManager) {
console.error("ComponentManager not available");
return;
}
try {
// 加载模板JSON文件
const response = await fetch(template.path);
if (!response.ok) {
throw new Error(`Failed to load template: ${response.statusText}`);
}
const templateData = await response.json();
console.log("加载模板:", templateData);
// 使用 componentManager 添加模板
const result = await componentManager.addTemplate({
id: template.id,
name: template.name,
template: templateData,
});
if (result) {
// 使用 Alert 显示结果消息
if (result.success) {
alert?.success(result.message);
} else {
alert?.error(result.message);
}
}
// 关闭菜单
closeMenu();
} catch (error) {
console.error("加载模板出错:", error);
alert?.error("无法加载模板文件,请检查控制台错误信息");
}
}
// 生命周期钩子
onMounted(() => {
// 预加载组件模块
preloadComponentModules();
});
</script>
<style scoped>
/* 动画效果 */
.animate-slideUp {
animation: slideUp 0.3s ease-out forwards;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>