feat(web): add tabs layout and submit button to Supermarket page

Reorganize Supermarket into a tabbed UI with Skills as the first tab
and MCP as the second tab. Add a GitHub submit button in the header
linking to https://github.com/memohai/supermarket.
This commit is contained in:
Acbox
2026-04-02 02:06:34 +08:00
parent 8281a60d45
commit e59fe9205b
3 changed files with 94 additions and 70 deletions
+2 -1
View File
@@ -1181,6 +1181,7 @@
"viewDetails": "View details", "viewDetails": "View details",
"mcpInstallTitle": "Install MCP Server", "mcpInstallTitle": "Install MCP Server",
"skillInstallTitle": "Install Skill", "skillInstallTitle": "Install Skill",
"loadError": "Failed to load from Supermarket" "loadError": "Failed to load from Supermarket",
"submit": "Submit"
} }
} }
+2 -1
View File
@@ -1177,6 +1177,7 @@
"viewDetails": "查看详情", "viewDetails": "查看详情",
"mcpInstallTitle": "安装 MCP 服务", "mcpInstallTitle": "安装 MCP 服务",
"skillInstallTitle": "安装 Skill", "skillInstallTitle": "安装 Skill",
"loadError": "从市场加载失败" "loadError": "从市场加载失败",
"submit": "提交"
} }
} }
+90 -68
View File
@@ -1,10 +1,25 @@
<template> <template>
<div class="p-6 max-w-6xl mx-auto space-y-6"> <div class="p-6 max-w-6xl mx-auto space-y-6">
<!-- Header + Search --> <!-- Header: Title + Submit Button -->
<div class="space-y-4"> <div class="flex items-center justify-between">
<h1 class="text-lg font-semibold"> <h1 class="text-lg font-semibold">
{{ $t('supermarket.title') }} {{ $t('supermarket.title') }}
</h1> </h1>
<Button
variant="outline"
size="sm"
as="a"
href="https://github.com/memohai/supermarket"
target="_blank"
rel="noopener noreferrer"
>
<Github class="size-4" />
{{ $t('supermarket.submit') }}
</Button>
</div>
<!-- Search -->
<div class="space-y-4">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="relative flex-1"> <div class="relative flex-1">
<Search class="absolute left-3 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground" /> <Search class="absolute left-3 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground" />
@@ -17,7 +32,7 @@
</div> </div>
</div> </div>
<!-- Active tag filter (e.g. from card tag click) --> <!-- Active tag filter -->
<div <div
v-if="activeTag" v-if="activeTag"
class="flex items-center gap-2" class="flex items-center gap-2"
@@ -37,75 +52,82 @@
</div> </div>
</div> </div>
<!-- MCP Section --> <!-- Tabs: Skills / MCP -->
<section class="space-y-3"> <Tabs
<h2 class="text-sm font-semibold"> default-value="skills"
{{ $t('supermarket.mcpSection') }} class="w-full"
</h2> >
<TabsList>
<TabsTrigger value="skills">
{{ $t('supermarket.skillsSection') }}
</TabsTrigger>
<TabsTrigger value="mcp">
{{ $t('supermarket.mcpSection') }}
</TabsTrigger>
</TabsList>
<div <!-- Skills Tab -->
v-if="mcpLoading" <TabsContent value="skills">
class="flex items-center justify-center py-8 text-xs text-muted-foreground" <div
> v-if="skillsLoading"
<Spinner class="mr-2" /> class="flex items-center justify-center py-8 text-xs text-muted-foreground"
{{ $t('common.loading') }} >
</div> <Spinner class="mr-2" />
{{ $t('common.loading') }}
</div>
<div <div
v-else-if="!mcps.length" v-else-if="!skills.length"
class="py-8 text-center text-xs text-muted-foreground" class="py-8 text-center text-xs text-muted-foreground"
> >
{{ $t('supermarket.noMcpResults') }} {{ $t('supermarket.noSkillResults') }}
</div> </div>
<div <div
v-else v-else
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
> >
<McpCard <SkillCard
v-for="mcp in mcps" v-for="skill in skills"
:key="mcp.id" :key="skill.id"
:mcp="mcp" :skill="skill"
@tag-click="setTag" @tag-click="setTag"
@install="openMcpInstall" @install="openSkillInstall"
/> />
</div> </div>
</section> </TabsContent>
<!-- Skills Section --> <!-- MCP Tab -->
<section class="space-y-3"> <TabsContent value="mcp">
<h2 class="text-sm font-semibold"> <div
{{ $t('supermarket.skillsSection') }} v-if="mcpLoading"
</h2> class="flex items-center justify-center py-8 text-xs text-muted-foreground"
>
<Spinner class="mr-2" />
{{ $t('common.loading') }}
</div>
<div <div
v-if="skillsLoading" v-else-if="!mcps.length"
class="flex items-center justify-center py-8 text-xs text-muted-foreground" class="py-8 text-center text-xs text-muted-foreground"
> >
<Spinner class="mr-2" /> {{ $t('supermarket.noMcpResults') }}
{{ $t('common.loading') }} </div>
</div>
<div <div
v-else-if="!skills.length" v-else
class="py-8 text-center text-xs text-muted-foreground" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
> >
{{ $t('supermarket.noSkillResults') }} <McpCard
</div> v-for="mcp in mcps"
:key="mcp.id"
<div :mcp="mcp"
v-else @tag-click="setTag"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" @install="openMcpInstall"
> />
<SkillCard </div>
v-for="skill in skills" </TabsContent>
:key="skill.id" </Tabs>
:skill="skill"
@tag-click="setTag"
@install="openSkillInstall"
/>
</div>
</section>
<!-- Install Dialogs --> <!-- Install Dialogs -->
<InstallMcpDialog <InstallMcpDialog
@@ -123,8 +145,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { Search, X } from 'lucide-vue-next' import { Search, X, Github } from 'lucide-vue-next'
import { Input, Badge, Spinner } from '@memohai/ui' import { Input, Badge, Spinner, Button, Tabs, TabsList, TabsTrigger, TabsContent } from '@memohai/ui'
import { import {
getSupermarketMcps, getSupermarketMcps,
getSupermarketSkills, getSupermarketSkills,