调整,音乐模块
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { ArrowLeft, Search } from '@lucide/vue'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { useSearchStore } from '@/stores/searchStore'
|
||||
import { registerAllTools, TOOLS_META } from './tools'
|
||||
import {
|
||||
getAllTools, getTool, searchTools,
|
||||
CATEGORY_ORDER, CATEGORY_LABEL, type DevTool
|
||||
} from './registry'
|
||||
|
||||
const searchStore = useSearchStore()
|
||||
|
||||
registerAllTools()
|
||||
|
||||
const allTools = getAllTools()
|
||||
|
||||
// ===== 列表视图:搜索 + 分类筛选 =====
|
||||
const query = ref('')
|
||||
const category = ref<'all' | DevTool['category']>('all')
|
||||
|
||||
const filteredTools = computed(() => {
|
||||
const list = searchTools(query.value)
|
||||
if (category.value === 'all') return list
|
||||
return list.filter(t => t.category === category.value)
|
||||
})
|
||||
|
||||
interface ToolGroup {
|
||||
category: DevTool['category']
|
||||
label: string
|
||||
tools: DevTool[]
|
||||
}
|
||||
|
||||
const groupedTools = computed<ToolGroup[]>(() => {
|
||||
if (category.value !== 'all') {
|
||||
return [{
|
||||
category: category.value as DevTool['category'],
|
||||
label: CATEGORY_LABEL[category.value as DevTool['category']],
|
||||
tools: filteredTools.value
|
||||
}]
|
||||
}
|
||||
const groups: ToolGroup[] = []
|
||||
for (const cat of CATEGORY_ORDER) {
|
||||
const tools = filteredTools.value.filter(t => t.category === cat)
|
||||
if (tools.length > 0) groups.push({ category: cat, label: CATEGORY_LABEL[cat], tools })
|
||||
}
|
||||
return groups
|
||||
})
|
||||
|
||||
// ===== 详情视图 =====
|
||||
const selectedId = ref<string | null>(null)
|
||||
const selectedTool = computed<DevTool | null>(() => {
|
||||
if (!selectedId.value) return null
|
||||
return getTool(selectedId.value) ?? null
|
||||
})
|
||||
|
||||
function openTool(id: string) {
|
||||
selectedId.value = id
|
||||
}
|
||||
|
||||
// ===== 全局搜索跳转:工具索引 → 打开对应工具 =====
|
||||
onMounted(() => {
|
||||
const actions = new Map<number, () => void>()
|
||||
TOOLS_META.forEach((t, i) => {
|
||||
actions.set(i, () => openTool(t.id))
|
||||
})
|
||||
searchStore.registerActions('devtools', actions)
|
||||
})
|
||||
|
||||
watch(selectedId, () => {
|
||||
query.value = ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full p-6 overflow-hidden flex flex-col">
|
||||
<!-- 列表视图 -->
|
||||
<template v-if="!selectedTool">
|
||||
<div class="flex items-center gap-3 mb-4 shrink-0 flex-wrap">
|
||||
<div class="relative flex-1 min-w-[180px] max-w-xs">
|
||||
<Search class="absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||
<Input v-model="query" placeholder="搜索工具..." class="h-8 pl-8 text-sm" />
|
||||
</div>
|
||||
<Button
|
||||
v-for="cat in (['all', ...CATEGORY_ORDER] as const)"
|
||||
:key="cat"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
class="h-7 px-2.5 text-xs"
|
||||
:class="category === cat ? 'bg-secondary text-foreground' : 'text-muted-foreground'"
|
||||
@click="category = cat as typeof category"
|
||||
>
|
||||
{{ cat === 'all' ? '全部' : CATEGORY_LABEL[cat as DevTool['category']] }}
|
||||
</Button>
|
||||
<span class="text-xs text-muted-foreground ml-auto">{{ (category === 'all' ? allTools : filteredTools).length }} 个工具</span>
|
||||
</div>
|
||||
|
||||
<ScrollArea class="flex-1 min-h-0 -mr-3 pr-3">
|
||||
<div v-if="filteredTools.length === 0" class="h-full flex items-center justify-center text-muted-foreground text-sm">
|
||||
没有找到匹配的工具
|
||||
</div>
|
||||
|
||||
<!-- 按分类分组 -->
|
||||
<div v-for="group in groupedTools" :key="group.category" class="mb-5">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-xs font-medium text-muted-foreground">{{ group.label }}</span>
|
||||
<Separator class="flex-1" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-3">
|
||||
<Card
|
||||
v-for="tool in group.tools"
|
||||
:key="tool.id"
|
||||
class="cursor-pointer transition-all hover:border-primary/50 hover:shadow-sm !py-0 !gap-0"
|
||||
@click="openTool(tool.id)"
|
||||
>
|
||||
<CardContent class="p-4 flex flex-col gap-2">
|
||||
<div class="size-9 rounded-lg bg-primary/10 text-primary flex items-center justify-center">
|
||||
<component :is="tool.icon" class="size-5" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<span class="text-sm font-medium leading-tight">{{ tool.name }}</span>
|
||||
<span class="text-xs text-muted-foreground leading-snug line-clamp-2">{{ tool.description }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</template>
|
||||
|
||||
<!-- 工具详情视图 -->
|
||||
<template v-else>
|
||||
<div class="flex items-center gap-3 mb-4 shrink-0">
|
||||
<Button size="sm" variant="ghost" class="gap-1 h-8" @click="selectedId = null">
|
||||
<ArrowLeft class="size-4" />
|
||||
返回
|
||||
</Button>
|
||||
<div class="size-8 rounded-lg bg-primary/10 text-primary flex items-center justify-center">
|
||||
<component :is="selectedTool.icon" class="size-4" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm font-medium leading-tight">{{ selectedTool.name }}</span>
|
||||
<span class="text-xs text-muted-foreground leading-snug">{{ selectedTool.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ScrollArea class="flex-1 min-h-0 -mr-3 pr-3">
|
||||
<div class="max-w-3xl px-1 pb-6">
|
||||
<component :is="selectedTool.component" />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user