mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
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:
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1177,6 +1177,7 @@
|
|||||||
"viewDetails": "查看详情",
|
"viewDetails": "查看详情",
|
||||||
"mcpInstallTitle": "安装 MCP 服务",
|
"mcpInstallTitle": "安装 MCP 服务",
|
||||||
"skillInstallTitle": "安装 Skill",
|
"skillInstallTitle": "安装 Skill",
|
||||||
"loadError": "从市场加载失败"
|
"loadError": "从市场加载失败",
|
||||||
|
"submit": "提交"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user