Merge pull request #855 from iflytek/feat/nav-console-reorganization
Some checks failed
Security / Dependency Review (push) Has been cancelled
Security / CodeQL (java-kotlin) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled

feat(web): reorganize marketplace and console navigation
This commit is contained in:
XiaoSeS 2026-09-11 17:15:58 +08:00 committed by GitHub
commit 37619bd96c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 53 additions and 6 deletions

View file

@ -46,6 +46,8 @@ services:
SKILLHUB_SECURITY_SCANNER_ENABLED: "true"
SKILLHUB_SECURITY_SCANNER_URL: http://skill-scanner:8000
SKILLHUB_SECURITY_SCANNER_MODE: upload
# Local staging needs typed Suite review writes so public Suite market fixtures can be submitted and approved.
SKILLHUB_SUITE_REVIEW_WRITES_ENABLED: "true"
depends_on:
postgres:
condition: service_healthy

View file

@ -28,6 +28,8 @@
|------|------|------|
| 首页 | `/` | 精选/热门/最新、搜索入口 |
| 搜索页 | `/search` | 关键词搜索 + 过滤 + 排序 |
| 技能套件市场 | `/suites` | 浏览和搜索已发布的技能套件 |
| 技能套件详情 | `/suite/{namespace}/{slug}` | 套件说明、成员技能和安装计划 |
| 命名空间主页 | `/@{namespace}` | 空间介绍 + 技能列表 |
| 技能详情页 | `/@{namespace}/{slug}` | README 渲染、版本、评分、收藏、下载 |
| 版本历史 | `/@{namespace}/{slug}/versions` | 版本列表 + changelog |
@ -39,6 +41,7 @@
| 页面 | 路径 | 说明 |
|------|------|------|
| 我的技能 | `/dashboard/skills` | 我发布的技能 + 统一生命周期状态 |
| 我的技能套件 | `/dashboard/suites` | 创建和管理我维护的技能套件 |
| 发布技能 | `/dashboard/publish` | zip 上传 + 预览 + 提交审核 |
| 我的收藏 | `/dashboard/stars` | 收藏列表 |
| Token 管理 | `/dashboard/tokens` | 创建/查看/吊销 |

View file

@ -15,7 +15,9 @@ test.describe('Dashboard Shell (Real API)', () => {
const sidebar = page.getByRole('complementary')
await expect(sidebar.getByRole('link', { name: 'Profile', exact: true })).toBeVisible()
await expect(sidebar.getByRole('link', { name: 'My Skills', exact: true })).toBeVisible()
await expect(sidebar.getByRole('link', { name: 'My Suites', exact: true })).toBeVisible()
await expect(sidebar.getByRole('link', { name: 'API Tokens', exact: true })).toBeVisible()
await expect(page.getByText('View and manage all your published skills')).toBeVisible()
await expect(page.getByText('View and manage your skill suites')).toBeVisible()
})
})

View file

@ -166,7 +166,7 @@ test.describe('Light and dark theme', () => {
window as Window & { __themeAtFirstReactContent?: boolean }
).__themeAtFirstReactContent)).toBe(true)
await page.getByRole('link', { name: 'Search', exact: true }).first().click()
await page.getByRole('link', { name: 'Skill Marketplace', exact: true }).first().click()
await expect(page).toHaveURL(/\/search(?:\?|$)/)
await expect(page.locator('html')).toHaveClass(/dark/)
await expect(page.getByPlaceholder('Search skills...')).toBeVisible()

View file

@ -84,11 +84,9 @@ export function Layout() {
}> = [
{ label: t('nav.landing'), to: '/', exact: true },
{ label: t('nav.publish'), to: '/dashboard/publish', auth: true },
{ label: t('nav.search'), to: '/search' },
{ label: t('nav.suites', { defaultValue: '技能套件' }), to: '/suites' },
{ label: t('nav.marketplace'), to: '/search' },
{ label: t('nav.suites'), to: '/suites' },
{ label: t('nav.dashboard'), to: '/dashboard', auth: true },
{ label: t('nav.mySkills'), to: '/dashboard/skills', auth: true },
{ label: t('nav.mySuites'), to: '/dashboard/suites', auth: true },
]
const isActive = (to: string, exact?: boolean) => {

View file

@ -13,6 +13,7 @@
"landing": "Home",
"home": "Skill Center",
"search": "Search",
"marketplace": "Skill Marketplace",
"skillDetail": "Skill Detail",
"explore": "Explore Skills",
"dashboard": "Dashboard",
@ -471,6 +472,7 @@
"notifications": "Notifications",
"skillsAndData": "Skills & Data",
"mySkills": "My Skills",
"mySuites": "My Suites",
"publish": "Publish",
"namespaces": "Namespaces",
"stars": "My Stars",
@ -484,6 +486,8 @@
"overview": {
"mySkills": "My Skills",
"mySkillsDesc": "View and manage all your published skills",
"mySuites": "My Suites",
"mySuitesDesc": "View and manage your skill suites",
"publish": "Publish",
"publishDesc": "Upload and publish a new skill",
"tokens": "API Tokens",
@ -1559,6 +1563,7 @@
"menu": {
"dashboard": "Dashboard",
"mySkills": "My Skills",
"mySuites": "My Suites",
"myNamespaces": "My Namespaces",
"governance": "Governance Center",
"stars": "Starred",

View file

@ -13,6 +13,7 @@
"landing": "Главная",
"home": "Центр скиллов",
"search": "Поиск",
"marketplace": "Каталог навыков",
"skillDetail": "Карточка скилла",
"explore": "Обзор скиллов",
"dashboard": "Панель управления",
@ -471,6 +472,7 @@
"notifications": "Уведомления",
"skillsAndData": "Скиллы и данные",
"mySkills": "Мои скиллы",
"mySuites": "Мои наборы",
"publish": "Публикация",
"namespaces": "Пространства",
"stars": "Мои звёзды",
@ -484,6 +486,8 @@
"overview": {
"mySkills": "Мои скиллы",
"mySkillsDesc": "Просмотр и управление опубликованными скиллами",
"mySuites": "Мои наборы",
"mySuitesDesc": "Просмотр и управление наборами навыков",
"publish": "Публикация",
"publishDesc": "Загрузить и опубликовать новый скилл",
"tokens": "Токены API",
@ -1878,6 +1882,7 @@
"menu": {
"dashboard": "Панель",
"mySkills": "Мои скиллы",
"mySuites": "Мои наборы",
"myNamespaces": "Мои пространства имён",
"namespacesAdmin": "Управление пространствами имён",
"governance": "Центр управления",

View file

@ -13,6 +13,7 @@
"landing": "首页",
"home": "技能中心",
"search": "搜索",
"marketplace": "技能市场",
"skillDetail": "技能详情",
"explore": "探索",
"dashboard": "控制台",
@ -471,6 +472,7 @@
"notifications": "通知设置",
"skillsAndData": "技能与数据",
"mySkills": "我的技能",
"mySuites": "我的套件",
"publish": "技能发布",
"namespaces": "命名空间",
"stars": "我的收藏",
@ -484,6 +486,8 @@
"overview": {
"mySkills": "我的技能",
"mySkillsDesc": "查看和管理你发布的所有技能",
"mySuites": "我的套件",
"mySuitesDesc": "查看和管理你的技能套件",
"publish": "技能发布",
"publishDesc": "上传并发布新的技能到平台",
"tokens": "Token 凭证",
@ -1558,6 +1562,7 @@
"menu": {
"dashboard": "控制台",
"mySkills": "我的技能",
"mySuites": "我的套件",
"myNamespaces": "我的命名空间",
"governance": "治理中心",
"stars": "我的收藏",

View file

@ -67,6 +67,8 @@ describe('DashboardPage', () => {
expect(html).toContain('sidebar.skillsAndData')
expect(html).toContain('overview.mySkills')
expect(html).toContain('sidebar.mySuites')
expect(html).toContain('overview.mySuites')
expect(html).not.toContain('overview.publish')
})
})

View file

@ -5,7 +5,7 @@ import { canViewGovernanceCenter } from '@/shared/lib/governance-access'
import { APP_SHELL_PAGE_CLASS_NAME } from '@/app/page-shell-style'
import { DashboardPageHeader } from '@/shared/components/dashboard-page-header'
import {
Star, Heart, Package, Key, Shield, Flag, Globe,
Star, Heart, Package, Boxes, Key, Shield, Flag, Globe,
UserCog, Lock, Bell, Clock, ChevronRight,
} from 'lucide-react'
@ -44,6 +44,7 @@ export const SIDEBAR_GROUPS: SidebarGroup[] = [
label: 'sidebar.skillsAndData',
items: [
{ key: 'skills', icon: Package, label: 'sidebar.mySkills', to: '/dashboard/skills' },
{ key: 'suites', icon: Boxes, label: 'sidebar.mySuites', to: '/dashboard/suites' },
{ key: 'namespaces', icon: Globe, label: 'sidebar.namespaces', to: '/dashboard/namespaces' },
{ key: 'stars', icon: Star, label: 'sidebar.stars', to: '/dashboard/stars' },
{ key: 'subscriptions', icon: Heart, label: 'sidebar.subscriptions', to: '/dashboard/subscriptions' },
@ -79,6 +80,7 @@ export const SIDEBAR_NAV = SIDEBAR_NAV_ITEMS.map(({ key, icon, label, to, admin,
*/
const OVERVIEW_CARDS = [
{ key: 'skills', icon: Package, label: 'overview.mySkills', to: '/dashboard/skills', desc: 'overview.mySkillsDesc' },
{ key: 'suites', icon: Boxes, label: 'overview.mySuites', to: '/dashboard/suites', desc: 'overview.mySuitesDesc' },
{ key: 'tokens', icon: Key, label: 'overview.tokens', to: '/dashboard/tokens', desc: 'overview.tokensDesc' },
{ key: 'stars', icon: Star, label: 'overview.stars', to: '/dashboard/stars', desc: 'overview.starsDesc' },
{ key: 'profile', icon: UserCog, label: 'overview.profile', to: '/settings/profile', desc: 'overview.profileDesc' },

View file

@ -77,6 +77,23 @@ describe('user-menu module exports', () => {
})
describe('UserMenu navigation', () => {
it('lists personal destinations in the requested order', () => {
const html = renderToStaticMarkup(
<UserMenu
user={{
displayName: 'Skill Author',
platformRoles: ['USER'],
}}
/>,
)
expect(html).toContain('user.menu.mySkills')
expect(html).toContain('user.menu.mySuites')
expect(html).toContain('user.menu.dashboard')
expect(html.indexOf('user.menu.mySkills')).toBeLessThan(html.indexOf('user.menu.mySuites'))
expect(html.indexOf('user.menu.mySuites')).toBeLessThan(html.indexOf('user.menu.dashboard'))
})
it('keeps dashboard-only personal links out of the compact avatar menu', () => {
const html = renderToStaticMarkup(
<UserMenu

View file

@ -142,6 +142,12 @@ export function UserMenu({ user, triggerClassName }: UserMenuProps) {
role="menu"
className="overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
>
<Link to="/dashboard/skills" className={menuItemClassName} onClick={closeMenu}>
{t('user.menu.mySkills')}
</Link>
<Link to="/dashboard/suites" className={menuItemClassName} onClick={closeMenu}>
{t('user.menu.mySuites')}
</Link>
<Link to="/dashboard" className={menuItemClassName} onClick={closeMenu}>
{t('user.menu.dashboard')}
</Link>