101 lines
3.0 KiB
Vue
101 lines
3.0 KiB
Vue
<template>
|
|
<div class="px-6 py-4 overflow-auto flex-1">
|
|
<div v-if="filteredItems.length > 0" class="grid grid-cols-2 gap-4">
|
|
<div
|
|
v-for="(item, index) in filteredItems"
|
|
:key="index"
|
|
class="card bg-base-200 hover:bg-base-300 transition-all duration-300 hover:shadow-md cursor-pointer"
|
|
@click="handleItemClick(item)"
|
|
>
|
|
<div class="card-body p-3 items-center text-center">
|
|
<div
|
|
class="bg-base-100 rounded-lg w-full h-[90px] flex items-center justify-center overflow-hidden p-2"
|
|
>
|
|
<!-- 组件预览 -->
|
|
<component
|
|
v-if="item.type && componentModules[item.type]"
|
|
:is="componentModules[item.type].default"
|
|
class="component-preview"
|
|
:size="getPreviewSize(item.type)"
|
|
/>
|
|
<!-- 模板预览 -->
|
|
<img
|
|
v-else-if="item.thumbnailUrl"
|
|
:src="item.thumbnailUrl || '/placeholder-template.png'"
|
|
alt="Template thumbnail"
|
|
class="max-h-full max-w-full object-contain"
|
|
/>
|
|
<!-- 加载中状态 -->
|
|
<span v-else class="text-xs text-gray-400">加载中...</span>
|
|
</div>
|
|
<h3 class="card-title text-sm mt-2">{{ item.name }}</h3>
|
|
<p class="text-xs opacity-70">
|
|
{{ item.description || item.type || getItemSubtitle(item) }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- 无搜索结果 -->
|
|
<div v-else class="py-16 text-center">
|
|
<SearchX :size="48" class="mx-auto text-base-300 mb-3" />
|
|
<p class="text-base-content opacity-70">{{ noResultsMessage }}</p>
|
|
<button class="btn btn-sm btn-ghost mt-3" @click="$emit('clear-search')">
|
|
清除搜索
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from "vue";
|
|
import { SearchX } from "lucide-vue-next";
|
|
import { getPreviewSize } from "./index.ts";
|
|
|
|
interface Props {
|
|
items: any[];
|
|
searchQuery: string;
|
|
componentModules: Record<string, any>;
|
|
noResultsMessage: string;
|
|
itemType: "component" | "template" | "virtual";
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
const emit = defineEmits(["item-click", "clear-search"]);
|
|
|
|
// 过滤后的项目列表
|
|
const filteredItems = computed(() => {
|
|
if (!props.searchQuery) {
|
|
return props.items;
|
|
}
|
|
const query = props.searchQuery.toLowerCase();
|
|
return props.items.filter(
|
|
(item) =>
|
|
item.name.toLowerCase().includes(query) ||
|
|
(item.type && item.type.toLowerCase().includes(query)) ||
|
|
(item.description && item.description.toLowerCase().includes(query)),
|
|
);
|
|
});
|
|
|
|
// 获取项目副标题
|
|
function getItemSubtitle(item: any): string {
|
|
if (props.itemType === "template") {
|
|
return "模板";
|
|
}
|
|
return item.type || "";
|
|
}
|
|
|
|
// 处理项目点击
|
|
function handleItemClick(item: any) {
|
|
emit("item-click", item);
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.component-preview {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
</style>
|