├── public ├── apple-icon.png ├── placeholder.jpg ├── grid-pattern.png ├── icon-dark-32x32.png ├── icon-light-32x32.png ├── placeholder-logo.png ├── placeholder-user.jpg ├── icon.svg ├── placeholder-logo.svg └── placeholder.svg ├── postcss.config.mjs ├── lib └── utils.ts ├── next.config.mjs ├── components ├── theme-provider.tsx ├── ui │ ├── label.tsx │ ├── progress.tsx │ ├── kbd.tsx │ ├── input.tsx │ ├── radio-group.tsx │ ├── badge.tsx │ ├── scroll-area.tsx │ ├── tabs.tsx │ ├── button.tsx │ ├── card.tsx │ ├── dialog.tsx │ ├── sheet.tsx │ ├── command.tsx │ └── dropdown-menu.tsx ├── shortcuts-dialog.tsx ├── theme-toggle.tsx ├── sections │ ├── community-section.tsx │ ├── hero-section.tsx │ ├── resources-section.tsx │ ├── frameworks-section.tsx │ ├── domain-practices-section.tsx │ ├── projects-section.tsx │ └── tools-section.tsx ├── use-keyboard-shortcuts.ts ├── footer.tsx ├── header.tsx └── command-palette.tsx ├── components.json ├── tsconfig.json ├── data ├── community.ts ├── resources.ts ├── navigation.ts ├── tools.ts ├── frameworks.ts ├── projects.ts └── domain-practices.ts ├── README.md ├── app ├── layout.tsx ├── page.tsx └── globals.css ├── package.json └── styles └── globals.css /public/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-platformers-cn/main/public/apple-icon.png -------------------------------------------------------------------------------- /public/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-platformers-cn/main/public/placeholder.jpg -------------------------------------------------------------------------------- /public/grid-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-platformers-cn/main/public/grid-pattern.png -------------------------------------------------------------------------------- /public/icon-dark-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-platformers-cn/main/public/icon-dark-32x32.png -------------------------------------------------------------------------------- /public/icon-light-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-platformers-cn/main/public/icon-light-32x32.png -------------------------------------------------------------------------------- /public/placeholder-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-platformers-cn/main/public/placeholder-logo.png -------------------------------------------------------------------------------- /public/placeholder-user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-platformers-cn/main/public/placeholder-user.jpg -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | '@tailwindcss/postcss': {}, 5 | }, 6 | } 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | typescript: { 4 | ignoreBuildErrors: true, 5 | }, 6 | images: { 7 | unoptimized: true, 8 | }, 9 | 10 | } 11 | 12 | export default nextConfig -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import { 5 | ThemeProvider as NextThemesProvider, 6 | type ThemeProviderProps, 7 | } from 'next-themes' 8 | 9 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 10 | return {children} 11 | } 12 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "target": "ES6", 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as LabelPrimitive from '@radix-ui/react-label' 5 | 6 | import { cn } from '@/lib/utils' 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as ProgressPrimitive from '@radix-ui/react-progress' 5 | 6 | import { cn } from '@/lib/utils' 7 | 8 | function Progress({ 9 | className, 10 | value, 11 | ...props 12 | }: React.ComponentProps) { 13 | return ( 14 | 22 | 27 | 28 | ) 29 | } 30 | 31 | export { Progress } 32 | -------------------------------------------------------------------------------- /data/community.ts: -------------------------------------------------------------------------------- 1 | // 社区介绍数据 2 | export const communityInfo = { 3 | name: "PECommunity 平台工程社区", 4 | tagline: "连接中国平台工程实践者", 5 | description: `PECommunity 是中国领先的平台工程社区,致力于推广平台工程理念、分享最佳实践、促进行业交流。 6 | 7 | 我们相信,优秀的开发者平台能够显著提升工程效能,让开发者专注于创造价值,而非与基础设施搏斗。`, 8 | url: "https://pecommunity.cn/", 9 | features: [ 10 | { 11 | title: "知识分享", 12 | description: "定期举办技术沙龙和线上分享,邀请行业专家分享实践经验", 13 | }, 14 | { 15 | title: "实践案例", 16 | description: "收集和整理国内外平台工程实践案例,提供可参考的经验", 17 | }, 18 | { 19 | title: "工具推荐", 20 | description: "评测和推荐平台工程相关工具,帮助团队做出技术选型", 21 | }, 22 | { 23 | title: "社区交流", 24 | description: "建立活跃的社区群组,促进从业者之间的交流与合作", 25 | }, 26 | ], 27 | stats: [ 28 | { label: "社区成员", value: "5000+" }, 29 | { label: "技术文章", value: "200+" }, 30 | { label: "线下活动", value: "50+" }, 31 | { label: "合作企业", value: "100+" }, 32 | ], 33 | } 34 | -------------------------------------------------------------------------------- /components/ui/kbd.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils' 2 | 3 | function Kbd({ className, ...props }: React.ComponentProps<'kbd'>) { 4 | return ( 5 | 15 | ) 16 | } 17 | 18 | function KbdGroup({ className, ...props }: React.ComponentProps<'div'>) { 19 | return ( 20 | 25 | ) 26 | } 27 | 28 | export { Kbd, KbdGroup } 29 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { cn } from '@/lib/utils' 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<'input'>) { 6 | return ( 7 | 18 | ) 19 | } 20 | 21 | export { Input } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Platform Engineering Website 2 | 3 | *Automatically synced with your [v0.app](https://v0.app) deployments* 4 | 5 | [![Deployed on Vercel](https://img.shields.io/badge/Deployed%20on-Vercel-black?style=for-the-badge&logo=vercel)](https://vercel.com/pecommunity/v0-platform-engineering-website) 6 | [![Built with v0](https://img.shields.io/badge/Built%20with-v0.app-black?style=for-the-badge)](https://v0.app/chat/syObAoPm3PC) 7 | 8 | ## Overview 9 | 10 | This repository will stay in sync with your deployed chats on [v0.app](https://v0.app). 11 | Any changes you make to your deployed app will be automatically pushed to this repository from [v0.app](https://v0.app). 12 | 13 | ## Deployment 14 | 15 | Your project is live at: 16 | 17 | **[https://vercel.com/pecommunity/v0-platform-engineering-website](https://vercel.com/pecommunity/v0-platform-engineering-website)** 18 | 19 | ## Build your app 20 | 21 | Continue building your app on: 22 | 23 | **[https://v0.app/chat/syObAoPm3PC](https://v0.app/chat/syObAoPm3PC)** 24 | 25 | ## How It Works 26 | 27 | 1. Create and modify your project using [v0.app](https://v0.app) 28 | 2. Deploy your chats from the v0 interface 29 | 3. Changes are automatically pushed to this repository 30 | 4. Vercel deploys the latest version from this repository -------------------------------------------------------------------------------- /public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /components/ui/radio-group.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as RadioGroupPrimitive from '@radix-ui/react-radio-group' 5 | import { CircleIcon } from 'lucide-react' 6 | 7 | import { cn } from '@/lib/utils' 8 | 9 | function RadioGroup({ 10 | className, 11 | ...props 12 | }: React.ComponentProps) { 13 | return ( 14 | 19 | ) 20 | } 21 | 22 | function RadioGroupItem({ 23 | className, 24 | ...props 25 | }: React.ComponentProps) { 26 | return ( 27 | 35 | 39 | 40 | 41 | 42 | ) 43 | } 44 | 45 | export { RadioGroup, RadioGroupItem } 46 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type React from "react" 2 | import type { Metadata, Viewport } from "next" 3 | import { Inter, JetBrains_Mono } from "next/font/google" 4 | import { Analytics } from "@vercel/analytics/next" 5 | import { ThemeProvider } from "@/components/theme-provider" 6 | import "./globals.css" 7 | 8 | const inter = Inter({ 9 | subsets: ["latin"], 10 | display: "swap", 11 | }) 12 | 13 | const jetbrainsMono = JetBrains_Mono({ 14 | subsets: ["latin"], 15 | display: "swap", 16 | variable: "--font-mono", 17 | }) 18 | 19 | export const metadata: Metadata = { 20 | title: "平台之道 | PECommunity", 21 | description: "平台工程的方法与框架、度量和测算工具 - 由 PECommunity 平台工程社区出品", 22 | keywords: ["平台工程", "Platform Engineering", "DevOps", "开发者体验", "IDP", "DORA"], 23 | authors: [{ name: "PECommunity", url: "https://pecommunity.cn" }], 24 | openGraph: { 25 | title: "平台之道", 26 | description: "平台工程的方法与框架、度量和测算工具", 27 | url: "https://platformers.cn", 28 | siteName: "平台之道", 29 | type: "website", 30 | }, 31 | generator: 'v0.app' 32 | } 33 | 34 | export const viewport: Viewport = { 35 | themeColor: "#0a0a0a", 36 | width: "device-width", 37 | initialScale: 1, 38 | } 39 | 40 | export default function RootLayout({ 41 | children, 42 | }: Readonly<{ 43 | children: React.ReactNode 44 | }>) { 45 | return ( 46 | 47 | 48 | 49 | {children} 50 | 51 | 52 | 53 | 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /components/shortcuts-dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" 4 | import { Kbd } from "@/components/ui/kbd" 5 | import { shortcuts } from "@/data/navigation" 6 | 7 | interface ShortcutsDialogProps { 8 | open: boolean 9 | onOpenChange: (open: boolean) => void 10 | } 11 | 12 | export function ShortcutsDialog({ open, onOpenChange }: ShortcutsDialogProps) { 13 | return ( 14 | 15 | 16 | 17 | 键盘快捷键 18 | 19 |
20 |
21 |
22 | {shortcuts.map((shortcut, index) => ( 23 |
24 | {shortcut.description} 25 |
26 | {shortcut.keys.map((key, i) => ( 27 | 28 | {key} 29 | {i < shortcut.keys.length - 1 && +} 30 | 31 | ))} 32 |
33 |
34 | ))} 35 |
36 |
37 |

38 | 按 Escape 关闭此对话框 39 |

40 |
41 |
42 |
43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Slot } from '@radix-ui/react-slot' 3 | import { cva, type VariantProps } from 'class-variance-authority' 4 | 5 | import { cn } from '@/lib/utils' 6 | 7 | const badgeVariants = cva( 8 | 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', 14 | secondary: 15 | 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', 16 | destructive: 17 | 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', 18 | outline: 19 | 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', 20 | }, 21 | }, 22 | defaultVariants: { 23 | variant: 'default', 24 | }, 25 | }, 26 | ) 27 | 28 | function Badge({ 29 | className, 30 | variant, 31 | asChild = false, 32 | ...props 33 | }: React.ComponentProps<'span'> & 34 | VariantProps & { asChild?: boolean }) { 35 | const Comp = asChild ? Slot : 'span' 36 | 37 | return ( 38 | 43 | ) 44 | } 45 | 46 | export { Badge, badgeVariants } 47 | -------------------------------------------------------------------------------- /data/resources.ts: -------------------------------------------------------------------------------- 1 | // 资源数据 2 | export interface Resource { 3 | id: string 4 | title: string 5 | description: string 6 | type: "article" | "video" | "book" | "tool" | "community" 7 | url: string 8 | tags: string[] 9 | author?: string 10 | date?: string 11 | } 12 | 13 | export const resources: Resource[] = [ 14 | { 15 | id: "platform-engineering-guide", 16 | title: "平台工程完全指南", 17 | description: "CNCF 发布的平台工程白皮书,全面介绍平台工程的概念和实践", 18 | type: "article", 19 | url: "https://tag-app-delivery.cncf.io/whitepapers/platforms/", 20 | tags: ["入门", "CNCF", "白皮书"], 21 | author: "CNCF TAG App Delivery", 22 | }, 23 | { 24 | id: "backstage", 25 | title: "Backstage", 26 | description: "Spotify 开源的开发者门户平台,用于构建内部开发者平台", 27 | type: "tool", 28 | url: "https://backstage.io/", 29 | tags: ["开源", "开发者门户", "IDP"], 30 | }, 31 | { 32 | id: "team-topologies-book", 33 | title: "Team Topologies", 34 | description: "Matthew Skelton 和 Manuel Pais 的经典著作,介绍团队拓扑方法论", 35 | type: "book", 36 | url: "https://teamtopologies.com/book", 37 | tags: ["团队", "组织设计", "经典"], 38 | author: "Matthew Skelton, Manuel Pais", 39 | }, 40 | { 41 | id: "platform-engineering-org", 42 | title: "Platform Engineering Community", 43 | description: "全球平台工程社区,提供资源、会议和交流机会", 44 | type: "community", 45 | url: "https://platformengineering.org/", 46 | tags: ["社区", "国际"], 47 | }, 48 | { 49 | id: "dora-research", 50 | title: "DORA Research", 51 | description: "DevOps 研究与评估,提供行业基准数据和最佳实践", 52 | type: "article", 53 | url: "https://dora.dev/", 54 | tags: ["研究", "DORA", "度量"], 55 | }, 56 | { 57 | id: "humanitec", 58 | title: "Humanitec", 59 | description: "企业级内部开发者平台解决方案", 60 | type: "tool", 61 | url: "https://humanitec.com/", 62 | tags: ["商业", "IDP", "企业"], 63 | }, 64 | ] 65 | -------------------------------------------------------------------------------- /components/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { Moon, Sun, Monitor } from "lucide-react" 5 | import { useTheme } from "next-themes" 6 | 7 | import { Button } from "@/components/ui/button" 8 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" 9 | 10 | export function ThemeToggle() { 11 | const { theme, setTheme } = useTheme() 12 | const [mounted, setMounted] = React.useState(false) 13 | 14 | React.useEffect(() => { 15 | setMounted(true) 16 | }, []) 17 | 18 | if (!mounted) { 19 | return ( 20 | 23 | ) 24 | } 25 | 26 | return ( 27 | 28 | 29 | 34 | 35 | 36 | setTheme("light")} className="gap-2"> 37 | 38 | 浅色 39 | 40 | setTheme("dark")} className="gap-2"> 41 | 42 | 深色 43 | 44 | setTheme("system")} className="gap-2"> 45 | 46 | 跟随系统 47 | 48 | 49 | 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area' 5 | 6 | import { cn } from '@/lib/utils' 7 | 8 | function ScrollArea({ 9 | className, 10 | children, 11 | ...props 12 | }: React.ComponentProps) { 13 | return ( 14 | 19 | 23 | {children} 24 | 25 | 26 | 27 | 28 | ) 29 | } 30 | 31 | function ScrollBar({ 32 | className, 33 | orientation = 'vertical', 34 | ...props 35 | }: React.ComponentProps) { 36 | return ( 37 | 50 | 54 | 55 | ) 56 | } 57 | 58 | export { ScrollArea, ScrollBar } 59 | -------------------------------------------------------------------------------- /data/navigation.ts: -------------------------------------------------------------------------------- 1 | // 导航配置数据 2 | export interface NavItem { 3 | id: string 4 | title: string 5 | href?: string 6 | shortcut?: string 7 | description?: string 8 | children?: NavItem[] 9 | external?: boolean 10 | } 11 | 12 | export const navigation: NavItem[] = [ 13 | { 14 | id: "home", 15 | title: "首页", 16 | href: "#home", 17 | shortcut: "g h", 18 | description: "返回首页", 19 | }, 20 | { 21 | id: "frameworks", 22 | title: "方法框架", 23 | href: "#frameworks", 24 | shortcut: "g f", 25 | description: "平台工程方法与框架", 26 | }, 27 | { 28 | id: "domain-practices", 29 | title: "领域平台实践", 30 | href: "#domain-practices", 31 | shortcut: "g d", 32 | description: "各领域的平台工程实践", 33 | }, 34 | { 35 | id: "tools", 36 | title: "度量工具", 37 | href: "#tools", 38 | shortcut: "g t", 39 | description: "度量和测算工具", 40 | }, 41 | { 42 | id: "projects", 43 | title: "项目", 44 | href: "#projects", 45 | shortcut: "g p", 46 | description: "平台工程相关开源和商业项目", 47 | }, 48 | { 49 | id: "resources", 50 | title: "资源库", 51 | href: "#resources", 52 | shortcut: "g r", 53 | description: "学习资源和参考资料", 54 | }, 55 | { 56 | id: "community", 57 | title: "社区", 58 | href: "https://pecommunity.cn/", 59 | shortcut: "g c", 60 | description: "PECommunity 平台工程社区", 61 | external: true, 62 | }, 63 | ] 64 | 65 | export const shortcuts = [ 66 | { keys: ["g", "h"], description: "跳转到首页", action: "navigate-home" }, 67 | { keys: ["g", "f"], description: "跳转到方法框架", action: "navigate-frameworks" }, 68 | { keys: ["g", "d"], description: "跳转到领域平台实践", action: "navigate-domain-practices" }, 69 | { keys: ["g", "t"], description: "跳转到度量工具", action: "navigate-tools" }, 70 | { keys: ["g", "p"], description: "跳转到项目", action: "navigate-projects" }, 71 | { keys: ["g", "r"], description: "跳转到资源库", action: "navigate-resources" }, 72 | { keys: ["g", "c"], description: "跳转到社区", action: "navigate-community" }, 73 | { keys: ["/"], description: "打开搜索", action: "open-search" }, 74 | { keys: ["?"], description: "显示快捷键帮助", action: "show-shortcuts" }, 75 | { keys: ["Escape"], description: "关闭弹窗", action: "close-modal" }, 76 | ] 77 | -------------------------------------------------------------------------------- /components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as TabsPrimitive from '@radix-ui/react-tabs' 5 | 6 | import { cn } from '@/lib/utils' 7 | 8 | function Tabs({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 18 | ) 19 | } 20 | 21 | function TabsList({ 22 | className, 23 | ...props 24 | }: React.ComponentProps) { 25 | return ( 26 | 34 | ) 35 | } 36 | 37 | function TabsTrigger({ 38 | className, 39 | ...props 40 | }: React.ComponentProps) { 41 | return ( 42 | 50 | ) 51 | } 52 | 53 | function TabsContent({ 54 | className, 55 | ...props 56 | }: React.ComponentProps) { 57 | return ( 58 | 63 | ) 64 | } 65 | 66 | export { Tabs, TabsList, TabsTrigger, TabsContent } 67 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Slot } from '@radix-ui/react-slot' 3 | import { cva, type VariantProps } from 'class-variance-authority' 4 | 5 | import { cn } from '@/lib/utils' 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 9 | { 10 | variants: { 11 | variant: { 12 | default: 'bg-primary text-primary-foreground hover:bg-primary/90', 13 | destructive: 14 | 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', 15 | outline: 16 | 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', 17 | secondary: 18 | 'bg-secondary text-secondary-foreground hover:bg-secondary/80', 19 | ghost: 20 | 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', 21 | link: 'text-primary underline-offset-4 hover:underline', 22 | }, 23 | size: { 24 | default: 'h-9 px-4 py-2 has-[>svg]:px-3', 25 | sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5', 26 | lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', 27 | icon: 'size-9', 28 | 'icon-sm': 'size-8', 29 | 'icon-lg': 'size-10', 30 | }, 31 | }, 32 | defaultVariants: { 33 | variant: 'default', 34 | size: 'default', 35 | }, 36 | }, 37 | ) 38 | 39 | function Button({ 40 | className, 41 | variant, 42 | size, 43 | asChild = false, 44 | ...props 45 | }: React.ComponentProps<'button'> & 46 | VariantProps & { 47 | asChild?: boolean 48 | }) { 49 | const Comp = asChild ? Slot : 'button' 50 | 51 | return ( 52 | 57 | ) 58 | } 59 | 60 | export { Button, buttonVariants } 61 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { cn } from '@/lib/utils' 4 | 5 | function Card({ className, ...props }: React.ComponentProps<'div'>) { 6 | return ( 7 |
15 | ) 16 | } 17 | 18 | function CardHeader({ className, ...props }: React.ComponentProps<'div'>) { 19 | return ( 20 |
28 | ) 29 | } 30 | 31 | function CardTitle({ className, ...props }: React.ComponentProps<'div'>) { 32 | return ( 33 |
38 | ) 39 | } 40 | 41 | function CardDescription({ className, ...props }: React.ComponentProps<'div'>) { 42 | return ( 43 |
48 | ) 49 | } 50 | 51 | function CardAction({ className, ...props }: React.ComponentProps<'div'>) { 52 | return ( 53 |
61 | ) 62 | } 63 | 64 | function CardContent({ className, ...props }: React.ComponentProps<'div'>) { 65 | return ( 66 |
71 | ) 72 | } 73 | 74 | function CardFooter({ className, ...props }: React.ComponentProps<'div'>) { 75 | return ( 76 |
81 | ) 82 | } 83 | 84 | export { 85 | Card, 86 | CardHeader, 87 | CardFooter, 88 | CardTitle, 89 | CardAction, 90 | CardDescription, 91 | CardContent, 92 | } 93 | -------------------------------------------------------------------------------- /components/sections/community-section.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { communityInfo } from "@/data/community" 4 | import { Button } from "@/components/ui/button" 5 | import { Card, CardContent } from "@/components/ui/card" 6 | import { ExternalLink } from "lucide-react" 7 | 8 | export function CommunitySection() { 9 | return ( 10 |
11 |
12 |
13 |

{communityInfo.name}

14 |

{communityInfo.tagline}

15 |

16 | {communityInfo.description} 17 |

18 |
19 | 20 | {/* Stats */} 21 |
22 | {communityInfo.stats.map((stat) => ( 23 | 24 | 25 |

{stat.value}

26 |

{stat.label}

27 |
28 |
29 | ))} 30 |
31 | 32 | {/* Features */} 33 |
34 | {communityInfo.features.map((feature) => ( 35 | 36 | 37 |

{feature.title}

38 |

{feature.description}

39 |
40 |
41 | ))} 42 |
43 | 44 | {/* CTA */} 45 |
46 | 52 |

加入我们,一起推动平台工程在中国的发展

53 |
54 |
55 |
56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-v0-project", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "next build", 7 | "dev": "next dev", 8 | "lint": "eslint .", 9 | "start": "next start" 10 | }, 11 | "dependencies": { 12 | "@hookform/resolvers": "^3.10.0", 13 | "@radix-ui/react-accordion": "1.2.2", 14 | "@radix-ui/react-alert-dialog": "1.1.4", 15 | "@radix-ui/react-aspect-ratio": "1.1.1", 16 | "@radix-ui/react-avatar": "1.1.2", 17 | "@radix-ui/react-checkbox": "1.1.3", 18 | "@radix-ui/react-collapsible": "1.1.2", 19 | "@radix-ui/react-context-menu": "2.2.4", 20 | "@radix-ui/react-dialog": "1.1.4", 21 | "@radix-ui/react-dropdown-menu": "2.1.4", 22 | "@radix-ui/react-hover-card": "1.1.4", 23 | "@radix-ui/react-label": "2.1.1", 24 | "@radix-ui/react-menubar": "1.1.4", 25 | "@radix-ui/react-navigation-menu": "1.2.3", 26 | "@radix-ui/react-popover": "1.1.4", 27 | "@radix-ui/react-progress": "1.1.1", 28 | "@radix-ui/react-radio-group": "1.2.2", 29 | "@radix-ui/react-scroll-area": "1.2.2", 30 | "@radix-ui/react-select": "2.1.4", 31 | "@radix-ui/react-separator": "1.1.1", 32 | "@radix-ui/react-slider": "1.2.2", 33 | "@radix-ui/react-slot": "1.1.1", 34 | "@radix-ui/react-switch": "1.1.2", 35 | "@radix-ui/react-tabs": "1.1.2", 36 | "@radix-ui/react-toast": "1.2.4", 37 | "@radix-ui/react-toggle": "1.1.1", 38 | "@radix-ui/react-toggle-group": "1.1.1", 39 | "@radix-ui/react-tooltip": "1.1.6", 40 | "@vercel/analytics": "1.3.1", 41 | "autoprefixer": "^10.4.20", 42 | "class-variance-authority": "^0.7.1", 43 | "clsx": "^2.1.1", 44 | "cmdk": "1.0.4", 45 | "date-fns": "4.1.0", 46 | "embla-carousel-react": "8.5.1", 47 | "input-otp": "1.4.1", 48 | "lucide-react": "^0.454.0", 49 | "next": "16.0.10", 50 | "next-themes": "^0.4.6", 51 | "react": "19.2.0", 52 | "react-day-picker": "9.8.0", 53 | "react-dom": "19.2.0", 54 | "react-hook-form": "^7.60.0", 55 | "react-resizable-panels": "^2.1.7", 56 | "recharts": "2.15.4", 57 | "sonner": "^1.7.4", 58 | "tailwind-merge": "^3.3.1", 59 | "tailwindcss-animate": "^1.0.7", 60 | "vaul": "^1.1.2", 61 | "zod": "3.25.76" 62 | }, 63 | "devDependencies": { 64 | "@tailwindcss/postcss": "^4.1.9", 65 | "@types/node": "^22", 66 | "@types/react": "^19", 67 | "@types/react-dom": "^19", 68 | "postcss": "^8.5", 69 | "tailwindcss": "^4.1.9", 70 | "tw-animate-css": "1.3.3", 71 | "typescript": "^5" 72 | } 73 | } -------------------------------------------------------------------------------- /components/use-keyboard-shortcuts.ts: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useEffect, useCallback } from "react" 4 | 5 | interface KeyboardShortcutsOptions { 6 | onNavigate: (section: string) => void 7 | onOpenSearch: () => void 8 | onOpenShortcuts: () => void 9 | } 10 | 11 | export function useKeyboardShortcuts({ onNavigate, onOpenSearch, onOpenShortcuts }: KeyboardShortcutsOptions) { 12 | const handleKeyDown = useCallback( 13 | (event: KeyboardEvent) => { 14 | // Ignore if typing in an input 15 | if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) { 16 | return 17 | } 18 | 19 | // Handle single key shortcuts 20 | if (event.key === "/" && !event.metaKey && !event.ctrlKey) { 21 | event.preventDefault() 22 | onOpenSearch() 23 | return 24 | } 25 | 26 | if (event.key === "?" && event.shiftKey) { 27 | event.preventDefault() 28 | onOpenShortcuts() 29 | return 30 | } 31 | 32 | // Handle g + key navigation 33 | if (event.key === "g") { 34 | const handleNextKey = (e: KeyboardEvent) => { 35 | switch (e.key) { 36 | case "h": 37 | e.preventDefault() 38 | onNavigate("home") 39 | break 40 | case "f": 41 | e.preventDefault() 42 | onNavigate("frameworks") 43 | break 44 | case "d": 45 | e.preventDefault() 46 | onNavigate("domain-practices") 47 | break 48 | case "t": 49 | e.preventDefault() 50 | onNavigate("tools") 51 | break 52 | case "p": 53 | e.preventDefault() 54 | onNavigate("projects") 55 | break 56 | case "r": 57 | e.preventDefault() 58 | onNavigate("resources") 59 | break 60 | case "c": 61 | e.preventDefault() 62 | window.open("https://pecommunity.cn/", "_blank") 63 | break 64 | } 65 | document.removeEventListener("keydown", handleNextKey) 66 | } 67 | 68 | document.addEventListener("keydown", handleNextKey, { once: true }) 69 | 70 | // Remove listener after timeout 71 | setTimeout(() => { 72 | document.removeEventListener("keydown", handleNextKey) 73 | }, 1000) 74 | } 75 | }, 76 | [onNavigate, onOpenSearch, onOpenShortcuts], 77 | ) 78 | 79 | useEffect(() => { 80 | document.addEventListener("keydown", handleKeyDown) 81 | return () => document.removeEventListener("keydown", handleKeyDown) 82 | }, [handleKeyDown]) 83 | } 84 | -------------------------------------------------------------------------------- /components/sections/hero-section.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Button } from "@/components/ui/button" 4 | import { Kbd } from "@/components/ui/kbd" 5 | import { ArrowRight, ExternalLink } from "lucide-react" 6 | 7 | interface HeroSectionProps { 8 | onNavigate: (section: string) => void 9 | } 10 | 11 | export function HeroSection({ onNavigate }: HeroSectionProps) { 12 | return ( 13 |
14 | {/* Background gradient */} 15 |
16 |
17 |
18 |
19 | 20 |
21 |
22 | 34 | 35 |

36 | 构建卓越的 37 |
38 | 开发者平台 39 |

40 | 41 |

42 | 平台之道汇集了平台工程的方法论、框架和实用工具,帮助团队构建高效的内部开发者平台, 43 | 提升开发者体验,加速价值交付。 44 |

45 | 46 |
47 | 51 | 54 |
55 | 56 |
57 |
58 | 59 | / 60 | 快速搜索 61 |
62 |
63 | 64 | ? 65 | 查看快捷键 66 |
67 |
68 |
69 |
70 |
71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /public/placeholder-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/placeholder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | import { ExternalLink } from "lucide-react" 3 | 4 | export function Footer() { 5 | return ( 6 |
7 |
8 |
9 | {/* Brand */} 10 |
11 | 12 |
13 | 14 |
15 | 平台之道 16 | 17 |

18 | 平台之道是 PECommunity 平台工程社区出品的知识平台, 致力于分享平台工程的方法论、框架和实用工具。 19 |

20 |
21 | 22 | {/* Links */} 23 |
24 |

导航

25 |
    26 |
  • 27 | 28 | 方法框架 29 | 30 |
  • 31 |
  • 32 | 33 | 度量工具 34 | 35 |
  • 36 |
  • 37 | 38 | 资源库 39 | 40 |
  • 41 |
42 |
43 | 44 | {/* Community */} 45 |
46 |

社区

47 | 71 |
72 |
73 | 74 |
75 |

76 | © {new Date().getFullYear()} PECommunity. All rights reserved. 77 |

78 |

79 | 域名:{" "} 80 | 81 | platformers.cn 82 | 83 |

84 |
85 |
86 |
87 | ) 88 | } 89 | -------------------------------------------------------------------------------- /components/sections/resources-section.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState } from "react" 4 | import { resources } from "@/data/resources" 5 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 6 | import { Badge } from "@/components/ui/badge" 7 | import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" 8 | import { ExternalLink, FileText, Video, Book, Wrench, Users } from "lucide-react" 9 | 10 | const typeIcons = { 11 | article: FileText, 12 | video: Video, 13 | book: Book, 14 | tool: Wrench, 15 | community: Users, 16 | } 17 | 18 | const typeLabels = { 19 | article: "文章", 20 | video: "视频", 21 | book: "书籍", 22 | tool: "工具", 23 | community: "社区", 24 | } 25 | 26 | export function ResourcesSection() { 27 | const [activeType, setActiveType] = useState("all") 28 | 29 | const types = [ 30 | { id: "all", label: "全部" }, 31 | { id: "article", label: "文章" }, 32 | { id: "book", label: "书籍" }, 33 | { id: "tool", label: "工具" }, 34 | { id: "community", label: "社区" }, 35 | ] 36 | 37 | const filteredResources = activeType === "all" ? resources : resources.filter((r) => r.type === activeType) 38 | 39 | return ( 40 |
41 |
42 |
43 |

资源库

44 |

精选的平台工程学习资源,包括文章、书籍、工具和社区。

45 |
46 | 47 | 48 | 49 | {types.map((type) => ( 50 | 51 | {type.label} 52 | 53 | ))} 54 | 55 | 56 | 57 | 91 |
92 |
93 | ) 94 | } 95 | -------------------------------------------------------------------------------- /data/tools.ts: -------------------------------------------------------------------------------- 1 | // 度量和测算工具数据 2 | export interface Tool { 3 | id: string 4 | title: string 5 | description: string 6 | category: "metric" | "calculator" | "assessment" 7 | icon: string 8 | } 9 | 10 | export interface MetricDefinition { 11 | id: string 12 | name: string 13 | description: string 14 | formula?: string 15 | benchmark?: string 16 | category: string 17 | } 18 | 19 | export interface AssessmentQuestion { 20 | id: string 21 | question: string 22 | category: string 23 | options: { 24 | value: number 25 | label: string 26 | description?: string 27 | }[] 28 | } 29 | 30 | export const tools: Tool[] = [ 31 | { 32 | id: "dora-metrics", 33 | title: "DORA 指标计算器", 34 | description: "计算部署频率、变更前置时间、变更失败率和恢复时间", 35 | category: "calculator", 36 | icon: "chart-bar", 37 | }, 38 | { 39 | id: "space-framework", 40 | title: "SPACE 框架评估", 41 | description: "从满意度、性能、活动、沟通、效率五个维度评估开发者生产力", 42 | category: "assessment", 43 | icon: "compass", 44 | }, 45 | { 46 | id: "platform-roi", 47 | title: "平台 ROI 计算器", 48 | description: "计算平台工程投资回报率", 49 | category: "calculator", 50 | icon: "calculator", 51 | }, 52 | { 53 | id: "cognitive-load", 54 | title: "认知负荷评估", 55 | description: "评估团队的认知负荷水平", 56 | category: "assessment", 57 | icon: "brain", 58 | }, 59 | { 60 | id: "developer-experience", 61 | title: "开发者体验评分", 62 | description: "评估和跟踪开发者体验指标", 63 | category: "metric", 64 | icon: "heart", 65 | }, 66 | ] 67 | 68 | export const doraMetrics: MetricDefinition[] = [ 69 | { 70 | id: "deployment-frequency", 71 | name: "部署频率", 72 | description: "组织成功将代码部署到生产环境的频率", 73 | category: "throughput", 74 | benchmark: "精英: 按需(每天多次)| 高: 每天至每周 | 中: 每周至每月 | 低: 每月至每六个月", 75 | }, 76 | { 77 | id: "lead-time", 78 | name: "变更前置时间", 79 | description: "从代码提交到代码成功运行在生产环境的时间", 80 | category: "throughput", 81 | benchmark: "精英: 少于1天 | 高: 1天至1周 | 中: 1周至1月 | 低: 1月至6月", 82 | }, 83 | { 84 | id: "change-failure-rate", 85 | name: "变更失败率", 86 | description: "部署到生产环境后导致服务降级或需要修复的变更百分比", 87 | category: "stability", 88 | benchmark: "精英: 0-15% | 高: 16-30% | 中: 16-30% | 低: 16-30%", 89 | }, 90 | { 91 | id: "mttr", 92 | name: "服务恢复时间", 93 | description: "从服务中断到恢复正常的时间", 94 | category: "stability", 95 | benchmark: "精英: 少于1小时 | 高: 少于1天 | 中: 1天至1周 | 低: 1周至1月", 96 | }, 97 | ] 98 | 99 | export const platformMaturityQuestions: AssessmentQuestion[] = [ 100 | { 101 | id: "q1", 102 | question: "你们的开发者如何获取开发环境?", 103 | category: "自助服务", 104 | options: [ 105 | { value: 1, label: "手动配置", description: "需要 IT 或运维团队手动配置" }, 106 | { value: 2, label: "部分自动化", description: "有一些脚本,但仍需人工介入" }, 107 | { value: 3, label: "自助服务", description: "开发者可以自行申请和配置" }, 108 | { value: 4, label: "即时可用", description: "几分钟内即可获得完整环境" }, 109 | ], 110 | }, 111 | { 112 | id: "q2", 113 | question: "部署到生产环境需要多长时间?", 114 | category: "部署效率", 115 | options: [ 116 | { value: 1, label: "数周", description: "需要多个审批和手动步骤" }, 117 | { value: 2, label: "数天", description: "有 CI/CD 但仍有手动步骤" }, 118 | { value: 3, label: "数小时", description: "大部分自动化,少量审批" }, 119 | { value: 4, label: "数分钟", description: "完全自动化的流水线" }, 120 | ], 121 | }, 122 | { 123 | id: "q3", 124 | question: "开发者如何了解可用的服务和工具?", 125 | category: "服务发现", 126 | options: [ 127 | { value: 1, label: "口口相传", description: "依赖同事之间的信息传递" }, 128 | { value: 2, label: "文档", description: "有文档但可能不完整或过时" }, 129 | { value: 3, label: "内部门户", description: "有统一的服务目录" }, 130 | { value: 4, label: "智能推荐", description: "平台主动推荐相关服务" }, 131 | ], 132 | }, 133 | { 134 | id: "q4", 135 | question: "平台团队如何收集用户反馈?", 136 | category: "用户反馈", 137 | options: [ 138 | { value: 1, label: "被动接收", description: "等待用户报告问题" }, 139 | { value: 2, label: "定期调查", description: "每季度进行满意度调查" }, 140 | { value: 3, label: "持续收集", description: "内置反馈机制和使用分析" }, 141 | { value: 4, label: "数据驱动", description: "基于数据主动改进体验" }, 142 | ], 143 | }, 144 | { 145 | id: "q5", 146 | question: "你们的基础设施是如何管理的?", 147 | category: "基础设施", 148 | options: [ 149 | { value: 1, label: "手动管理", description: "通过控制台或 SSH 手动操作" }, 150 | { value: 2, label: "脚本化", description: "有自动化脚本但非版本控制" }, 151 | { value: 3, label: "基础设施即代码", description: "使用 Terraform/Pulumi 等工具" }, 152 | { value: 4, label: "GitOps", description: "完全声明式,Git 为唯一真相源" }, 153 | ], 154 | }, 155 | ] 156 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState, useCallback, useEffect } from "react" 4 | import { Header } from "@/components/header" 5 | import { Footer } from "@/components/footer" 6 | import { CommandPalette } from "@/components/command-palette" 7 | import { ShortcutsDialog } from "@/components/shortcuts-dialog" 8 | import { useKeyboardShortcuts } from "@/components/use-keyboard-shortcuts" 9 | import { HeroSection } from "@/components/sections/hero-section" 10 | import { FrameworksSection } from "@/components/sections/frameworks-section" 11 | import { ToolsSection } from "@/components/sections/tools-section" 12 | import { ProjectsSection } from "@/components/sections/projects-section" 13 | import { ResourcesSection } from "@/components/sections/resources-section" 14 | import { CommunitySection } from "@/components/sections/community-section" 15 | import { DomainPracticesSection } from "@/components/sections/domain-practices-section" 16 | 17 | export default function HomePage() { 18 | const [commandPaletteOpen, setCommandPaletteOpen] = useState(false) 19 | const [shortcutsDialogOpen, setShortcutsDialogOpen] = useState(false) 20 | const [activeSection, setActiveSection] = useState("home") 21 | const [selectedFramework, setSelectedFramework] = useState(null) 22 | const [selectedTool, setSelectedTool] = useState(null) 23 | const [selectedPractice, setSelectedPractice] = useState(null) 24 | 25 | const handleNavigate = useCallback((section: string) => { 26 | if (section === "community") { 27 | window.open("https://pecommunity.cn/", "_blank") 28 | return 29 | } 30 | 31 | setActiveSection(section) 32 | const element = document.getElementById(section) 33 | if (element) { 34 | const headerOffset = 80 35 | const elementPosition = element.getBoundingClientRect().top 36 | const offsetPosition = elementPosition + window.pageYOffset - headerOffset 37 | window.scrollTo({ 38 | top: offsetPosition, 39 | behavior: "smooth", 40 | }) 41 | } 42 | }, []) 43 | 44 | useEffect(() => { 45 | const handleScroll = () => { 46 | const sections = ["home", "frameworks", "domain-practices", "tools", "projects", "resources"] 47 | const scrollPosition = window.scrollY + 100 48 | 49 | for (const section of sections) { 50 | const element = document.getElementById(section) 51 | if (element) { 52 | const { offsetTop, offsetHeight } = element 53 | if (scrollPosition >= offsetTop && scrollPosition < offsetTop + offsetHeight) { 54 | setActiveSection(section) 55 | break 56 | } 57 | } 58 | } 59 | } 60 | 61 | window.addEventListener("scroll", handleScroll) 62 | return () => window.removeEventListener("scroll", handleScroll) 63 | }, []) 64 | 65 | useKeyboardShortcuts({ 66 | onNavigate: handleNavigate, 67 | onOpenSearch: () => setCommandPaletteOpen(true), 68 | onOpenShortcuts: () => setShortcutsDialogOpen(true), 69 | }) 70 | 71 | return ( 72 |
73 |
setCommandPaletteOpen(true)} 75 | onOpenShortcuts={() => setShortcutsDialogOpen(true)} 76 | activeSection={activeSection} 77 | /> 78 | 79 |
80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
88 | 89 |
90 | 91 | { 96 | setSelectedFramework(id) 97 | handleNavigate("frameworks") 98 | }} 99 | onSelectPractice={(id) => { 100 | setSelectedPractice(id) 101 | handleNavigate("domain-practices") 102 | }} 103 | onSelectTool={(id) => { 104 | setSelectedTool(id) 105 | handleNavigate("tools") 106 | }} 107 | /> 108 | 109 | 110 |
111 | ) 112 | } 113 | -------------------------------------------------------------------------------- /components/header.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState } from "react" 4 | import Link from "next/link" 5 | import { navigation } from "@/data/navigation" 6 | import { Button } from "@/components/ui/button" 7 | import { Kbd } from "@/components/ui/kbd" 8 | import { ThemeToggle } from "@/components/theme-toggle" 9 | import { Menu, X, ExternalLink, Search, Command } from "lucide-react" 10 | 11 | interface HeaderProps { 12 | onOpenSearch: () => void 13 | onOpenShortcuts: () => void 14 | activeSection: string 15 | } 16 | 17 | export function Header({ onOpenSearch, onOpenShortcuts, activeSection }: HeaderProps) { 18 | const [mobileMenuOpen, setMobileMenuOpen] = useState(false) 19 | 20 | return ( 21 |
22 | 75 | 76 | {/* Mobile Navigation */} 77 | {mobileMenuOpen && ( 78 |
79 |
80 | {navigation.map((item) => ( 81 | setMobileMenuOpen(false)} 92 | > 93 | 94 | {item.title} 95 | {item.external && } 96 | 97 | {item.shortcut && {item.shortcut}} 98 | 99 | ))} 100 |
101 |
102 | )} 103 |
104 | ) 105 | } 106 | -------------------------------------------------------------------------------- /components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as DialogPrimitive from '@radix-ui/react-dialog' 5 | import { XIcon } from 'lucide-react' 6 | 7 | import { cn } from '@/lib/utils' 8 | 9 | function Dialog({ 10 | ...props 11 | }: React.ComponentProps) { 12 | return 13 | } 14 | 15 | function DialogTrigger({ 16 | ...props 17 | }: React.ComponentProps) { 18 | return 19 | } 20 | 21 | function DialogPortal({ 22 | ...props 23 | }: React.ComponentProps) { 24 | return 25 | } 26 | 27 | function DialogClose({ 28 | ...props 29 | }: React.ComponentProps) { 30 | return 31 | } 32 | 33 | function DialogOverlay({ 34 | className, 35 | ...props 36 | }: React.ComponentProps) { 37 | return ( 38 | 46 | ) 47 | } 48 | 49 | function DialogContent({ 50 | className, 51 | children, 52 | showCloseButton = true, 53 | ...props 54 | }: React.ComponentProps & { 55 | showCloseButton?: boolean 56 | }) { 57 | return ( 58 | 59 | 60 | 68 | {children} 69 | {showCloseButton && ( 70 | 74 | 75 | Close 76 | 77 | )} 78 | 79 | 80 | ) 81 | } 82 | 83 | function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) { 84 | return ( 85 |
90 | ) 91 | } 92 | 93 | function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) { 94 | return ( 95 |
103 | ) 104 | } 105 | 106 | function DialogTitle({ 107 | className, 108 | ...props 109 | }: React.ComponentProps) { 110 | return ( 111 | 116 | ) 117 | } 118 | 119 | function DialogDescription({ 120 | className, 121 | ...props 122 | }: React.ComponentProps) { 123 | return ( 124 | 129 | ) 130 | } 131 | 132 | export { 133 | Dialog, 134 | DialogClose, 135 | DialogContent, 136 | DialogDescription, 137 | DialogFooter, 138 | DialogHeader, 139 | DialogOverlay, 140 | DialogPortal, 141 | DialogTitle, 142 | DialogTrigger, 143 | } 144 | -------------------------------------------------------------------------------- /components/ui/sheet.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as SheetPrimitive from '@radix-ui/react-dialog' 5 | import { XIcon } from 'lucide-react' 6 | 7 | import { cn } from '@/lib/utils' 8 | 9 | function Sheet({ ...props }: React.ComponentProps) { 10 | return 11 | } 12 | 13 | function SheetTrigger({ 14 | ...props 15 | }: React.ComponentProps) { 16 | return 17 | } 18 | 19 | function SheetClose({ 20 | ...props 21 | }: React.ComponentProps) { 22 | return 23 | } 24 | 25 | function SheetPortal({ 26 | ...props 27 | }: React.ComponentProps) { 28 | return 29 | } 30 | 31 | function SheetOverlay({ 32 | className, 33 | ...props 34 | }: React.ComponentProps) { 35 | return ( 36 | 44 | ) 45 | } 46 | 47 | function SheetContent({ 48 | className, 49 | children, 50 | side = 'right', 51 | ...props 52 | }: React.ComponentProps & { 53 | side?: 'top' | 'right' | 'bottom' | 'left' 54 | }) { 55 | return ( 56 | 57 | 58 | 74 | {children} 75 | 76 | 77 | Close 78 | 79 | 80 | 81 | ) 82 | } 83 | 84 | function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) { 85 | return ( 86 |
91 | ) 92 | } 93 | 94 | function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) { 95 | return ( 96 |
101 | ) 102 | } 103 | 104 | function SheetTitle({ 105 | className, 106 | ...props 107 | }: React.ComponentProps) { 108 | return ( 109 | 114 | ) 115 | } 116 | 117 | function SheetDescription({ 118 | className, 119 | ...props 120 | }: React.ComponentProps) { 121 | return ( 122 | 127 | ) 128 | } 129 | 130 | export { 131 | Sheet, 132 | SheetTrigger, 133 | SheetClose, 134 | SheetContent, 135 | SheetHeader, 136 | SheetFooter, 137 | SheetTitle, 138 | SheetDescription, 139 | } 140 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import 'tw-animate-css'; 3 | 4 | @custom-variant dark (&:is(.dark *)); 5 | 6 | :root { 7 | --background: oklch(1 0 0); 8 | --foreground: oklch(0.145 0 0); 9 | --card: oklch(1 0 0); 10 | --card-foreground: oklch(0.145 0 0); 11 | --popover: oklch(1 0 0); 12 | --popover-foreground: oklch(0.145 0 0); 13 | --primary: oklch(0.205 0 0); 14 | --primary-foreground: oklch(0.985 0 0); 15 | --secondary: oklch(0.97 0 0); 16 | --secondary-foreground: oklch(0.205 0 0); 17 | --muted: oklch(0.97 0 0); 18 | --muted-foreground: oklch(0.556 0 0); 19 | --accent: oklch(0.97 0 0); 20 | --accent-foreground: oklch(0.205 0 0); 21 | --destructive: oklch(0.577 0.245 27.325); 22 | --destructive-foreground: oklch(0.577 0.245 27.325); 23 | --border: oklch(0.922 0 0); 24 | --input: oklch(0.922 0 0); 25 | --ring: oklch(0.708 0 0); 26 | --chart-1: oklch(0.646 0.222 41.116); 27 | --chart-2: oklch(0.6 0.118 184.704); 28 | --chart-3: oklch(0.398 0.07 227.392); 29 | --chart-4: oklch(0.828 0.189 84.429); 30 | --chart-5: oklch(0.769 0.188 70.08); 31 | --radius: 0.625rem; 32 | --sidebar: oklch(0.985 0 0); 33 | --sidebar-foreground: oklch(0.145 0 0); 34 | --sidebar-primary: oklch(0.205 0 0); 35 | --sidebar-primary-foreground: oklch(0.985 0 0); 36 | --sidebar-accent: oklch(0.97 0 0); 37 | --sidebar-accent-foreground: oklch(0.205 0 0); 38 | --sidebar-border: oklch(0.922 0 0); 39 | --sidebar-ring: oklch(0.708 0 0); 40 | } 41 | 42 | .dark { 43 | --background: oklch(0.145 0 0); 44 | --foreground: oklch(0.985 0 0); 45 | --card: oklch(0.145 0 0); 46 | --card-foreground: oklch(0.985 0 0); 47 | --popover: oklch(0.145 0 0); 48 | --popover-foreground: oklch(0.985 0 0); 49 | --primary: oklch(0.985 0 0); 50 | --primary-foreground: oklch(0.205 0 0); 51 | --secondary: oklch(0.269 0 0); 52 | --secondary-foreground: oklch(0.985 0 0); 53 | --muted: oklch(0.269 0 0); 54 | --muted-foreground: oklch(0.708 0 0); 55 | --accent: oklch(0.269 0 0); 56 | --accent-foreground: oklch(0.985 0 0); 57 | --destructive: oklch(0.396 0.141 25.723); 58 | --destructive-foreground: oklch(0.637 0.237 25.331); 59 | --border: oklch(0.269 0 0); 60 | --input: oklch(0.269 0 0); 61 | --ring: oklch(0.439 0 0); 62 | --chart-1: oklch(0.488 0.243 264.376); 63 | --chart-2: oklch(0.696 0.17 162.48); 64 | --chart-3: oklch(0.769 0.188 70.08); 65 | --chart-4: oklch(0.627 0.265 303.9); 66 | --chart-5: oklch(0.645 0.246 16.439); 67 | --sidebar: oklch(0.205 0 0); 68 | --sidebar-foreground: oklch(0.985 0 0); 69 | --sidebar-primary: oklch(0.488 0.243 264.376); 70 | --sidebar-primary-foreground: oklch(0.985 0 0); 71 | --sidebar-accent: oklch(0.269 0 0); 72 | --sidebar-accent-foreground: oklch(0.985 0 0); 73 | --sidebar-border: oklch(0.269 0 0); 74 | --sidebar-ring: oklch(0.439 0 0); 75 | } 76 | 77 | @theme inline { 78 | --font-sans: 'Geist', 'Geist Fallback'; 79 | --font-mono: 'Geist Mono', 'Geist Mono Fallback'; 80 | --color-background: var(--background); 81 | --color-foreground: var(--foreground); 82 | --color-card: var(--card); 83 | --color-card-foreground: var(--card-foreground); 84 | --color-popover: var(--popover); 85 | --color-popover-foreground: var(--popover-foreground); 86 | --color-primary: var(--primary); 87 | --color-primary-foreground: var(--primary-foreground); 88 | --color-secondary: var(--secondary); 89 | --color-secondary-foreground: var(--secondary-foreground); 90 | --color-muted: var(--muted); 91 | --color-muted-foreground: var(--muted-foreground); 92 | --color-accent: var(--accent); 93 | --color-accent-foreground: var(--accent-foreground); 94 | --color-destructive: var(--destructive); 95 | --color-destructive-foreground: var(--destructive-foreground); 96 | --color-border: var(--border); 97 | --color-input: var(--input); 98 | --color-ring: var(--ring); 99 | --color-chart-1: var(--chart-1); 100 | --color-chart-2: var(--chart-2); 101 | --color-chart-3: var(--chart-3); 102 | --color-chart-4: var(--chart-4); 103 | --color-chart-5: var(--chart-5); 104 | --radius-sm: calc(var(--radius) - 4px); 105 | --radius-md: calc(var(--radius) - 2px); 106 | --radius-lg: var(--radius); 107 | --radius-xl: calc(var(--radius) + 4px); 108 | --color-sidebar: var(--sidebar); 109 | --color-sidebar-foreground: var(--sidebar-foreground); 110 | --color-sidebar-primary: var(--sidebar-primary); 111 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 112 | --color-sidebar-accent: var(--sidebar-accent); 113 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 114 | --color-sidebar-border: var(--sidebar-border); 115 | --color-sidebar-ring: var(--sidebar-ring); 116 | } 117 | 118 | @layer base { 119 | * { 120 | @apply border-border outline-ring/50; 121 | } 122 | body { 123 | @apply bg-background text-foreground; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | @import "tw-animate-css"; 3 | 4 | @custom-variant dark (&:is(.dark *)); 5 | 6 | /* 添加浅色模式配色 */ 7 | :root { 8 | --background: oklch(0.98 0 0); 9 | --foreground: oklch(0.15 0 0); 10 | --card: oklch(1 0 0); 11 | --card-foreground: oklch(0.15 0 0); 12 | --popover: oklch(1 0 0); 13 | --popover-foreground: oklch(0.15 0 0); 14 | --primary: oklch(0.45 0.15 162.48); 15 | --primary-foreground: oklch(0.98 0 0); 16 | --secondary: oklch(0.93 0 0); 17 | --secondary-foreground: oklch(0.15 0 0); 18 | --muted: oklch(0.92 0 0); 19 | --muted-foreground: oklch(0.45 0 0); 20 | --accent: oklch(0.93 0 0); 21 | --accent-foreground: oklch(0.15 0 0); 22 | --destructive: oklch(0.577 0.245 27.325); 23 | --destructive-foreground: oklch(0.98 0 0); 24 | --border: oklch(0.88 0 0); 25 | --input: oklch(0.88 0 0); 26 | --ring: oklch(0.45 0.15 162.48); 27 | --chart-1: oklch(0.45 0.15 162.48); 28 | --chart-2: oklch(0.5 0.118 184.704); 29 | --chart-3: oklch(0.398 0.07 227.392); 30 | --chart-4: oklch(0.7 0.15 84.429); 31 | --chart-5: oklch(0.65 0.15 70.08); 32 | --radius: 0.5rem; 33 | --sidebar: oklch(0.96 0 0); 34 | --sidebar-foreground: oklch(0.15 0 0); 35 | --sidebar-primary: oklch(0.45 0.15 162.48); 36 | --sidebar-primary-foreground: oklch(0.98 0 0); 37 | --sidebar-accent: oklch(0.93 0 0); 38 | --sidebar-accent-foreground: oklch(0.15 0 0); 39 | --sidebar-border: oklch(0.88 0 0); 40 | --sidebar-ring: oklch(0.45 0.15 162.48); 41 | } 42 | 43 | /* 深色模式配色 */ 44 | .dark { 45 | --background: oklch(0.13 0 0); 46 | --foreground: oklch(0.98 0 0); 47 | --card: oklch(0.16 0 0); 48 | --card-foreground: oklch(0.98 0 0); 49 | --popover: oklch(0.16 0 0); 50 | --popover-foreground: oklch(0.98 0 0); 51 | --primary: oklch(0.696 0.17 162.48); 52 | --primary-foreground: oklch(0.13 0 0); 53 | --secondary: oklch(0.2 0 0); 54 | --secondary-foreground: oklch(0.98 0 0); 55 | --muted: oklch(0.22 0 0); 56 | --muted-foreground: oklch(0.65 0 0); 57 | --accent: oklch(0.2 0 0); 58 | --accent-foreground: oklch(0.98 0 0); 59 | --destructive: oklch(0.396 0.141 25.723); 60 | --destructive-foreground: oklch(0.98 0 0); 61 | --border: oklch(0.22 0 0); 62 | --input: oklch(0.22 0 0); 63 | --ring: oklch(0.696 0.17 162.48); 64 | --chart-1: oklch(0.696 0.17 162.48); 65 | --chart-2: oklch(0.6 0.118 184.704); 66 | --chart-3: oklch(0.769 0.188 70.08); 67 | --chart-4: oklch(0.627 0.265 303.9); 68 | --chart-5: oklch(0.645 0.246 16.439); 69 | --sidebar: oklch(0.16 0 0); 70 | --sidebar-foreground: oklch(0.98 0 0); 71 | --sidebar-primary: oklch(0.696 0.17 162.48); 72 | --sidebar-primary-foreground: oklch(0.13 0 0); 73 | --sidebar-accent: oklch(0.2 0 0); 74 | --sidebar-accent-foreground: oklch(0.98 0 0); 75 | --sidebar-border: oklch(0.22 0 0); 76 | --sidebar-ring: oklch(0.696 0.17 162.48); 77 | } 78 | 79 | /* 修复字体配置,使用 CSS 变量引用 Next.js 字体 */ 80 | @theme inline { 81 | --font-sans: var(--font-inter, "Inter", system-ui, sans-serif); 82 | --font-mono: var(--font-mono, "JetBrains Mono", "Fira Code", monospace); 83 | --color-background: var(--background); 84 | --color-foreground: var(--foreground); 85 | --color-card: var(--card); 86 | --color-card-foreground: var(--card-foreground); 87 | --color-popover: var(--popover); 88 | --color-popover-foreground: var(--popover-foreground); 89 | --color-primary: var(--primary); 90 | --color-primary-foreground: var(--primary-foreground); 91 | --color-secondary: var(--secondary); 92 | --color-secondary-foreground: var(--secondary-foreground); 93 | --color-muted: var(--muted); 94 | --color-muted-foreground: var(--muted-foreground); 95 | --color-accent: var(--accent); 96 | --color-accent-foreground: var(--accent-foreground); 97 | --color-destructive: var(--destructive); 98 | --color-destructive-foreground: var(--destructive-foreground); 99 | --color-border: var(--border); 100 | --color-input: var(--input); 101 | --color-ring: var(--ring); 102 | --color-chart-1: var(--chart-1); 103 | --color-chart-2: var(--chart-2); 104 | --color-chart-3: var(--chart-3); 105 | --color-chart-4: var(--chart-4); 106 | --color-chart-5: var(--chart-5); 107 | --radius-sm: calc(var(--radius) - 4px); 108 | --radius-md: calc(var(--radius) - 2px); 109 | --radius-lg: var(--radius); 110 | --radius-xl: calc(var(--radius) + 4px); 111 | --color-sidebar: var(--sidebar); 112 | --color-sidebar-foreground: var(--sidebar-foreground); 113 | --color-sidebar-primary: var(--sidebar-primary); 114 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 115 | --color-sidebar-accent: var(--sidebar-accent); 116 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 117 | --color-sidebar-border: var(--sidebar-border); 118 | --color-sidebar-ring: var(--sidebar-ring); 119 | } 120 | 121 | @layer base { 122 | * { 123 | @apply border-border outline-ring/50; 124 | } 125 | body { 126 | @apply bg-background text-foreground; 127 | } 128 | 129 | /* 自定义滚动条样式 */ 130 | ::-webkit-scrollbar { 131 | width: 8px; 132 | height: 8px; 133 | } 134 | 135 | ::-webkit-scrollbar-track { 136 | background: var(--background); 137 | } 138 | 139 | ::-webkit-scrollbar-thumb { 140 | background: var(--muted); 141 | border-radius: 4px; 142 | } 143 | 144 | ::-webkit-scrollbar-thumb:hover { 145 | background: var(--muted-foreground); 146 | } 147 | } 148 | 149 | /* 选中文本样式 - 支持深浅色模式 */ 150 | ::selection { 151 | background: oklch(0.45 0.15 162.48 / 0.3); 152 | color: oklch(0.15 0 0); 153 | } 154 | 155 | .dark ::selection { 156 | background: oklch(0.696 0.17 162.48 / 0.3); 157 | color: oklch(0.98 0 0); 158 | } 159 | -------------------------------------------------------------------------------- /components/ui/command.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import { Command as CommandPrimitive } from 'cmdk' 5 | import { SearchIcon } from 'lucide-react' 6 | 7 | import { cn } from '@/lib/utils' 8 | import { 9 | Dialog, 10 | DialogContent, 11 | DialogDescription, 12 | DialogHeader, 13 | DialogTitle, 14 | } from '@/components/ui/dialog' 15 | 16 | function Command({ 17 | className, 18 | ...props 19 | }: React.ComponentProps) { 20 | return ( 21 | 29 | ) 30 | } 31 | 32 | function CommandDialog({ 33 | title = 'Command Palette', 34 | description = 'Search for a command to run...', 35 | children, 36 | className, 37 | showCloseButton = true, 38 | ...props 39 | }: React.ComponentProps & { 40 | title?: string 41 | description?: string 42 | className?: string 43 | showCloseButton?: boolean 44 | }) { 45 | return ( 46 | 47 | 48 | {title} 49 | {description} 50 | 51 | 55 | 56 | {children} 57 | 58 | 59 | 60 | ) 61 | } 62 | 63 | function CommandInput({ 64 | className, 65 | ...props 66 | }: React.ComponentProps) { 67 | return ( 68 |
72 | 73 | 81 |
82 | ) 83 | } 84 | 85 | function CommandList({ 86 | className, 87 | ...props 88 | }: React.ComponentProps) { 89 | return ( 90 | 98 | ) 99 | } 100 | 101 | function CommandEmpty({ 102 | ...props 103 | }: React.ComponentProps) { 104 | return ( 105 | 110 | ) 111 | } 112 | 113 | function CommandGroup({ 114 | className, 115 | ...props 116 | }: React.ComponentProps) { 117 | return ( 118 | 126 | ) 127 | } 128 | 129 | function CommandSeparator({ 130 | className, 131 | ...props 132 | }: React.ComponentProps) { 133 | return ( 134 | 139 | ) 140 | } 141 | 142 | function CommandItem({ 143 | className, 144 | ...props 145 | }: React.ComponentProps) { 146 | return ( 147 | 155 | ) 156 | } 157 | 158 | function CommandShortcut({ 159 | className, 160 | ...props 161 | }: React.ComponentProps<'span'>) { 162 | return ( 163 | 171 | ) 172 | } 173 | 174 | export { 175 | Command, 176 | CommandDialog, 177 | CommandInput, 178 | CommandList, 179 | CommandEmpty, 180 | CommandGroup, 181 | CommandItem, 182 | CommandShortcut, 183 | CommandSeparator, 184 | } 185 | -------------------------------------------------------------------------------- /components/command-palette.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import type React from "react" 4 | 5 | import { useEffect, useState } from "react" 6 | import { 7 | CommandDialog, 8 | CommandEmpty, 9 | CommandGroup, 10 | CommandInput, 11 | CommandItem, 12 | CommandList, 13 | CommandSeparator, 14 | } from "@/components/ui/command" 15 | import { navigation } from "@/data/navigation" 16 | import { frameworks } from "@/data/frameworks" 17 | import { tools } from "@/data/tools" 18 | import { projects, projectCategories } from "@/data/projects" 19 | import { domainPractices } from "@/data/domain-practices" 20 | import { Kbd } from "@/components/ui/kbd" 21 | import { 22 | Home, 23 | Layers, 24 | Wrench, 25 | BookOpen, 26 | Users, 27 | ExternalLink, 28 | FileText, 29 | Calculator, 30 | FolderKanban, 31 | Sparkles, 32 | } from "lucide-react" 33 | 34 | interface CommandPaletteProps { 35 | open: boolean 36 | onOpenChange: (open: boolean) => void 37 | onNavigate: (section: string) => void 38 | onSelectFramework: (id: string) => void 39 | onSelectPractice: (id: string) => void 40 | onSelectTool: (id: string) => void 41 | } 42 | 43 | const iconMap: Record = { 44 | home: , 45 | frameworks: , 46 | "domain-practices": , 47 | tools: , 48 | projects: , 49 | resources: , 50 | community: , 51 | } 52 | 53 | export function CommandPalette({ 54 | open, 55 | onOpenChange, 56 | onNavigate, 57 | onSelectFramework, 58 | onSelectPractice, 59 | onSelectTool, 60 | }: CommandPaletteProps) { 61 | const [search, setSearch] = useState("") 62 | 63 | useEffect(() => { 64 | if (!open) setSearch("") 65 | }, [open]) 66 | 67 | return ( 68 | 69 | 70 | 71 | 未找到相关内容 72 | 73 | 74 | {navigation.map((item) => ( 75 | { 79 | if (item.external) { 80 | window.open(item.href, "_blank") 81 | } else { 82 | onNavigate(item.id) 83 | } 84 | onOpenChange(false) 85 | }} 86 | > 87 | {iconMap[item.id]} 88 | {item.title} 89 | {item.external && } 90 | {item.shortcut && {item.shortcut}} 91 | 92 | ))} 93 | 94 | 95 | 96 | 97 | 98 | {frameworks.map((framework) => ( 99 | { 103 | onSelectFramework(framework.id) 104 | onOpenChange(false) 105 | }} 106 | > 107 | 108 |
109 | {framework.title} 110 | {framework.description} 111 |
112 |
113 | ))} 114 |
115 | 116 | 117 | 118 | 119 | {domainPractices.map((practice) => ( 120 | { 124 | onSelectPractice(practice.id) 125 | onOpenChange(false) 126 | }} 127 | > 128 | 129 |
130 | {practice.title} 131 | {practice.description} 132 |
133 |
134 | ))} 135 |
136 | 137 | 138 | 139 | 140 | {tools.map((tool) => ( 141 | { 145 | onSelectTool(tool.id) 146 | onOpenChange(false) 147 | }} 148 | > 149 | 150 |
151 | {tool.title} 152 | {tool.description} 153 |
154 |
155 | ))} 156 |
157 | 158 | 159 | 160 | 161 | {projects.slice(0, 10).map((project) => ( 162 | c.id === project.category)?.name}`} 165 | onSelect={() => { 166 | window.open(project.url, "_blank") 167 | onOpenChange(false) 168 | }} 169 | > 170 | 171 |
172 | {project.name} 173 | {project.description} 174 |
175 | 176 |
177 | ))} 178 |
179 |
180 |
181 | ) 182 | } 183 | -------------------------------------------------------------------------------- /components/sections/frameworks-section.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState } from "react" 4 | import { frameworks } from "@/data/frameworks" 5 | import { Button } from "@/components/ui/button" 6 | import { Badge } from "@/components/ui/badge" 7 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 8 | import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" 9 | import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet" 10 | import { ScrollArea } from "@/components/ui/scroll-area" 11 | import { ExternalLink, ArrowRight } from "lucide-react" 12 | 13 | interface FrameworksSectionProps { 14 | selectedFramework: string | null 15 | onSelectFramework: (id: string | null) => void 16 | } 17 | 18 | export function FrameworksSection({ selectedFramework, onSelectFramework }: FrameworksSectionProps) { 19 | const [activeCategory, setActiveCategory] = useState("all") 20 | 21 | const categories = [ 22 | { id: "all", label: "全部" }, 23 | { id: "methodology", label: "方法论" }, 24 | { id: "framework", label: "框架" }, 25 | { id: "pattern", label: "模式" }, 26 | ] 27 | 28 | const filteredFrameworks = 29 | activeCategory === "all" ? frameworks : frameworks.filter((f) => f.category === activeCategory) 30 | 31 | const selected = frameworks.find((f) => f.id === selectedFramework) 32 | 33 | return ( 34 |
35 |
36 |
37 |

方法框架

38 |

39 | 探索平台工程领域的核心方法论、框架和模式,为你的平台建设提供理论指导。 40 |

41 |
42 | 43 | 44 | 45 | {categories.map((cat) => ( 46 | 47 | {cat.label} 48 | 49 | ))} 50 | 51 | 52 | 53 |
54 | {filteredFrameworks.map((framework) => ( 55 | onSelectFramework(framework.id)} 59 | > 60 | 61 |
62 | 63 | {categories.find((c) => c.id === framework.category)?.label} 64 | 65 |
66 | {framework.title} 67 | {framework.description} 68 |
69 | 70 |
71 | {framework.tags.slice(0, 3).map((tag) => ( 72 | 73 | {tag} 74 | 75 | ))} 76 |
77 | 80 |
81 |
82 | ))} 83 |
84 | 85 | {/* Framework Detail Sheet */} 86 | !open && onSelectFramework(null)}> 87 | 88 | {selected && ( 89 | <> 90 | 91 |
92 | {categories.find((c) => c.id === selected.category)?.label} 93 |
94 | {selected.title} 95 |

{selected.description}

96 |
97 | 98 |
99 |
100 | {selected.content.split("\n").map((line, i) => { 101 | if (line.startsWith("## ")) { 102 | return ( 103 |

104 | {line.replace("## ", "")} 105 |

106 | ) 107 | } 108 | if (line.startsWith("### ")) { 109 | return ( 110 |

111 | {line.replace("### ", "")} 112 |

113 | ) 114 | } 115 | if (line.startsWith("- ")) { 116 | return ( 117 |
  • 118 | {line.replace("- ", "")} 119 |
  • 120 | ) 121 | } 122 | if (line.match(/^\d+\./)) { 123 | return ( 124 |
  • 125 | {line} 126 |
  • 127 | ) 128 | } 129 | if (line.trim() === "") { 130 | return
    131 | } 132 | return ( 133 |

    134 | {line} 135 |

    136 | ) 137 | })} 138 |
    139 |
    140 | 141 | {selected.references && selected.references.length > 0 && ( 142 |
    143 |

    参考资料

    144 |
    145 | {selected.references.map((ref, i) => ( 146 | 153 | 154 | {ref.title} 155 | 156 | ))} 157 |
    158 |
    159 | )} 160 | 161 |
    162 | {selected.tags.map((tag) => ( 163 | 164 | {tag} 165 | 166 | ))} 167 |
    168 |
    169 | 170 | )} 171 |
    172 |
    173 |
    174 |
    175 | ) 176 | } 177 | -------------------------------------------------------------------------------- /components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' 5 | import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react' 6 | 7 | import { cn } from '@/lib/utils' 8 | 9 | function DropdownMenu({ 10 | ...props 11 | }: React.ComponentProps) { 12 | return 13 | } 14 | 15 | function DropdownMenuPortal({ 16 | ...props 17 | }: React.ComponentProps) { 18 | return ( 19 | 20 | ) 21 | } 22 | 23 | function DropdownMenuTrigger({ 24 | ...props 25 | }: React.ComponentProps) { 26 | return ( 27 | 31 | ) 32 | } 33 | 34 | function DropdownMenuContent({ 35 | className, 36 | sideOffset = 4, 37 | ...props 38 | }: React.ComponentProps) { 39 | return ( 40 | 41 | 50 | 51 | ) 52 | } 53 | 54 | function DropdownMenuGroup({ 55 | ...props 56 | }: React.ComponentProps) { 57 | return ( 58 | 59 | ) 60 | } 61 | 62 | function DropdownMenuItem({ 63 | className, 64 | inset, 65 | variant = 'default', 66 | ...props 67 | }: React.ComponentProps & { 68 | inset?: boolean 69 | variant?: 'default' | 'destructive' 70 | }) { 71 | return ( 72 | 82 | ) 83 | } 84 | 85 | function DropdownMenuCheckboxItem({ 86 | className, 87 | children, 88 | checked, 89 | ...props 90 | }: React.ComponentProps) { 91 | return ( 92 | 101 | 102 | 103 | 104 | 105 | 106 | {children} 107 | 108 | ) 109 | } 110 | 111 | function DropdownMenuRadioGroup({ 112 | ...props 113 | }: React.ComponentProps) { 114 | return ( 115 | 119 | ) 120 | } 121 | 122 | function DropdownMenuRadioItem({ 123 | className, 124 | children, 125 | ...props 126 | }: React.ComponentProps) { 127 | return ( 128 | 136 | 137 | 138 | 139 | 140 | 141 | {children} 142 | 143 | ) 144 | } 145 | 146 | function DropdownMenuLabel({ 147 | className, 148 | inset, 149 | ...props 150 | }: React.ComponentProps & { 151 | inset?: boolean 152 | }) { 153 | return ( 154 | 163 | ) 164 | } 165 | 166 | function DropdownMenuSeparator({ 167 | className, 168 | ...props 169 | }: React.ComponentProps) { 170 | return ( 171 | 176 | ) 177 | } 178 | 179 | function DropdownMenuShortcut({ 180 | className, 181 | ...props 182 | }: React.ComponentProps<'span'>) { 183 | return ( 184 | 192 | ) 193 | } 194 | 195 | function DropdownMenuSub({ 196 | ...props 197 | }: React.ComponentProps) { 198 | return 199 | } 200 | 201 | function DropdownMenuSubTrigger({ 202 | className, 203 | inset, 204 | children, 205 | ...props 206 | }: React.ComponentProps & { 207 | inset?: boolean 208 | }) { 209 | return ( 210 | 219 | {children} 220 | 221 | 222 | ) 223 | } 224 | 225 | function DropdownMenuSubContent({ 226 | className, 227 | ...props 228 | }: React.ComponentProps) { 229 | return ( 230 | 238 | ) 239 | } 240 | 241 | export { 242 | DropdownMenu, 243 | DropdownMenuPortal, 244 | DropdownMenuTrigger, 245 | DropdownMenuContent, 246 | DropdownMenuGroup, 247 | DropdownMenuLabel, 248 | DropdownMenuItem, 249 | DropdownMenuCheckboxItem, 250 | DropdownMenuRadioGroup, 251 | DropdownMenuRadioItem, 252 | DropdownMenuSeparator, 253 | DropdownMenuShortcut, 254 | DropdownMenuSub, 255 | DropdownMenuSubTrigger, 256 | DropdownMenuSubContent, 257 | } 258 | -------------------------------------------------------------------------------- /data/frameworks.ts: -------------------------------------------------------------------------------- 1 | // 平台工程方法与框架数据 2 | export interface Framework { 3 | id: string 4 | title: string 5 | description: string 6 | category: "methodology" | "framework" | "pattern" 7 | tags: string[] 8 | content: string 9 | references?: { title: string; url: string }[] 10 | } 11 | 12 | export const frameworks: Framework[] = [ 13 | { 14 | id: "team-topologies", 15 | title: "团队拓扑 (Team Topologies)", 16 | description: "一种组织设计和团队交互的方法论,定义了四种基本团队类型和三种交互模式", 17 | category: "methodology", 18 | tags: ["组织设计", "团队协作", "康威定律"], 19 | content: `## 核心概念 20 | 21 | 团队拓扑定义了四种基本团队类型: 22 | 23 | 1. **流对齐团队 (Stream-aligned Team)**: 与业务流程对齐,负责端到端的价值交付 24 | 2. **赋能团队 (Enabling Team)**: 帮助其他团队克服障碍,传播知识和能力 25 | 3. **复杂子系统团队 (Complicated Subsystem Team)**: 处理需要专业知识的复杂领域 26 | 4. **平台团队 (Platform Team)**: 提供内部服务,减少其他团队的认知负荷 27 | 28 | ## 三种交互模式 29 | 30 | - **协作 (Collaboration)**: 紧密合作,共同发现新解决方案 31 | - **服务即产品 (X-as-a-Service)**: 提供标准化服务,最小化协调成本 32 | - **促进 (Facilitating)**: 帮助团队学习和成长`, 33 | references: [ 34 | { title: "Team Topologies 官网", url: "https://teamtopologies.com/" }, 35 | { title: "相关书籍", url: "https://teamtopologies.com/book" }, 36 | ], 37 | }, 38 | { 39 | id: "platform-as-product", 40 | title: "平台即产品 (Platform as a Product)", 41 | description: "将内部开发者平台视为产品来运营,关注用户体验和价值交付", 42 | category: "framework", 43 | tags: ["产品思维", "开发者体验", "平台运营"], 44 | content: `## 核心理念 45 | 46 | 平台即产品将内部开发者平台视为面向内部用户(开发者)的产品,强调: 47 | 48 | - **用户中心**: 深入了解开发者的需求和痛点 49 | - **价值驱动**: 关注平台为组织带来的实际价值 50 | - **持续迭代**: 采用产品管理实践进行持续改进 51 | 52 | ## 关键实践 53 | 54 | 1. **建立产品团队**: 配备产品经理、设计师和工程师 55 | 2. **用户研究**: 定期收集开发者反馈 56 | 3. **度量驱动**: 建立关键指标体系 57 | 4. **文档优先**: 提供优秀的文档和自助服务`, 58 | references: [{ title: "CNCF Platform Engineering", url: "https://platformengineering.org/" }], 59 | }, 60 | { 61 | id: "platform-product-way", 62 | title: "平台产品之道", 63 | description: "以产品思维运营内部平台,将开发者视为客户,通过持续迭代提升平台价值和采用率", 64 | category: "methodology", 65 | tags: ["产品管理", "客户思维", "价值度量", "迭代优化"], 66 | content: `## 核心原则 67 | 68 | ### 1. 开发者即客户 69 | 将开发者视为真正的客户,而非仅仅是用户: 70 | - 深入理解他们的问题、偏好和工作流程 71 | - 定期进行对话,了解真实需求 72 | - 优先考虑开发者体验,而非技术优雅性 73 | 74 | ### 2. 以采用率衡量价值 75 | 平台的价值不在于功能数量,而在于使用程度: 76 | - 少量功能被多数开发者使用,比大量功能被少数人使用更有价值 77 | - 追踪使用率、满意度和平台如何帮助开发者更快交付 78 | - 关注实际业务影响,而非技术指标 79 | 80 | ### 3. 基于用户需求迭代 81 | 根据用户最需要的内容调整优先级: 82 | - 采用敏捷产品管理方法,而非固定技术路线图 83 | - 快速验证假设,小步快跑 84 | - 建立快速反馈循环 85 | 86 | ## 关键实践 87 | 88 | ### 自助服务能力 89 | - 提供按需更改的能力,无需等待其他团队 90 | - 降低使用门槛,提高开发者自主性 91 | - 通过自动化减少人工干预 92 | 93 | ### 可访问性设计 94 | - 平台支持和知识易于获取 95 | - 简化平台理解和使用难度 96 | - 提供清晰的文档和示例 97 | 98 | ### 打造吸引力 99 | - 让平台成为开发者完成工作的最简单选择 100 | - 促进自愿使用,而非强制推行 101 | - 创建内部品牌,向同事"推销"平台价值 102 | 103 | ### 产品经理职责 104 | - 路线图规划和优先级排序 105 | - 收集和分析使用指标数据 106 | - 质量监控和持续改进 107 | - 平衡多方利益相关者需求 108 | 109 | ## 度量体系 110 | 111 | ### 满意度指标 112 | - **NPS (净推荐值)**: 衡量开发者推荐平台的意愿(-100 到 +100) 113 | - **CSAT (客户满意度)**: 直接衡量对平台或特定功能的满意度(1-5分) 114 | - **CES (客户费力度)**: 衡量使用平台所需的努力程度 115 | 116 | ### 使用指标 117 | - 平台采用率和活跃用户数 118 | - 功能使用频率和覆盖率 119 | - 新用户入职时间 120 | - 自助服务完成率 121 | 122 | ### 价值指标 123 | - 开发者生产力提升 124 | - 交付速度改善 125 | - 事故响应时间缩短 126 | - 基础设施成本优化 127 | 128 | ## 平台团队组织 129 | 130 | ### 团队所有制 131 | 平台工程团队应对特定平台部分负责: 132 | - 创建内部可见的品牌标识 133 | - 明确所有权和责任边界 134 | - 建立支持和反馈机制 135 | 136 | ### 跨职能协作 137 | - 产品经理:定义愿景和路线图 138 | - 设计师:优化用户体验 139 | - 工程师:构建和维护平台 140 | - 支持协调员:管理反馈和问题 141 | 142 | ## 成功要素 143 | 144 | 1. **理解开发者工作流程**: 深入了解日常工作模式和痛点 145 | 2. **构建他们想用的解决方案**: 关注真实需求,而非自认为的需求 146 | 3. **基于使用情况持续改进**: 数据驱动决策,快速响应反馈 147 | 4. **平衡创新与稳定**: 在快速迭代和系统稳定性之间找到平衡 148 | 5. **获得高层支持**: 确保组织层面的认可和资源投入`, 149 | references: [ 150 | { 151 | title: "Platform as a Product Guide - Jellyfish", 152 | url: "https://jellyfish.co/library/platform-engineering/platform-as-a-product/", 153 | }, 154 | { title: "Platform Engineering Org", url: "https://platformengineering.org/talks-library/platform-as-a-product" }, 155 | ], 156 | }, 157 | { 158 | id: "platform-architecture-way", 159 | title: "平台架构之道", 160 | description: "构建可扩展、可靠、安全的平台架构,通过分层设计和模式复用支撑企业级应用", 161 | category: "methodology", 162 | tags: ["架构设计", "分层架构", "设计模式", "可扩展性"], 163 | content: `## 核心原则 164 | 165 | ### 1. 平衡创新与稳定 166 | 现代企业架构面临的核心挑战: 167 | - 支持快速创新和新功能交付 168 | - 维护系统稳定性和可靠性 169 | - 在敏捷性和一致性之间找到平衡点 170 | 171 | ### 2. 标准化解决方案 172 | 采用经过验证的架构模式: 173 | - 为常见结构问题提供可复用解决方案 174 | - 指导开发者和架构师构建应用 175 | - 满足复杂业务需求并保持灵活性 176 | 177 | ### 3. 关注点分离 178 | 通过分层架构实现关注点分离: 179 | - 提高系统模块化程度 180 | - 降低组件耦合度,提高内聚性 181 | - 便于独立扩展和维护 182 | 183 | ## 平台架构分层模型 184 | 185 | ### 第一层:基础设施层 (Infrastructure Services) 186 | 提供基础的计算、存储和网络资源: 187 | - **IaaS 提供商**: AWS、GCP、Azure 或私有云 OpenStack 188 | - **核心资源**: 虚拟机、容器、块存储、对象存储、虚拟网络 189 | - **特性**: 可扩展的基础环境,按需分配资源 190 | 191 | ### 第二层:系统服务层 (System Services) 192 | 处理底层系统管理、编排和安全: 193 | - **容器编排**: Kubernetes、Nomad、Docker Swarm 194 | - **服务网格**: Istio、Linkerd、Consul Connect 195 | - **网络服务**: VPN、DNS、负载均衡 196 | - **安全基础**: 身份认证、授权、密钥管理 197 | 198 | ### 第三层:平台服务层 (Platform Services) 199 | 提供应用开发所需的中间件和工具: 200 | - **数据服务**: 数据库、缓存、消息队列 201 | - **CI/CD 流水线**: 自动化构建、测试、部署 202 | - **可观测性**: 日志、监控、追踪、告警 203 | - **服务目录**: API 网关、服务注册与发现 204 | - **配置管理**: 配置中心、特性开关 205 | 206 | ### 第四层:应用服务层 (Application Services) 207 | 面向业务的应用和服务: 208 | - **业务应用**: 微服务、API、前端应用 209 | - **开发者门户**: Backstage、内部文档 210 | - **自助服务界面**: 资源申请、环境管理 211 | 212 | ## 关键架构模式 213 | 214 | ### 微服务架构 215 | 将应用拆分为松耦合的服务: 216 | - **优势**: 可扩展性、故障隔离、独立部署 217 | - **适用场景**: 大型复杂系统,需要灵活性和持续演进 218 | - **考虑因素**: 服务治理、数据一致性、网络延迟 219 | 220 | ### 事件驱动架构 221 | 基于事件的异步通信: 222 | - **模式**: Event Sourcing、CQRS、Saga 223 | - **优势**: 解耦、可扩展、弹性 224 | - **组件**: 消息队列、事件总线、流处理 225 | 226 | ### 云原生设计模式 227 | 构建云环境中可靠高效的应用: 228 | - **Ambassador 模式**: 代理客户端网络连接 229 | - **Anti-Corruption Layer**: 隔离遗留系统 230 | - **Backends for Frontends**: 为不同客户端优化后端 231 | - **Circuit Breaker**: 防止级联失败 232 | - **Retry/Timeout**: 处理瞬时故障 233 | - **Bulkhead**: 隔离资源池 234 | 235 | ### 分布式系统模式 236 | 确保系统可扩展、弹性和高效: 237 | - **状态管理**: 分区、复制、分片 238 | - **一致性**: 最终一致性、Gossip 协议 239 | - **流量控制**: 限流、熔断、降级 240 | - **故障恢复**: 自动重试、补偿事务 241 | 242 | ## 架构设计要点 243 | 244 | ### 可扩展性 (Scalability) 245 | - 水平扩展优于垂直扩展 246 | - 无状态服务设计 247 | - 数据分片和缓存策略 248 | - 弹性伸缩机制 249 | 250 | ### 可靠性 (Reliability) 251 | - 多可用区部署 252 | - 自动故障检测和恢复 253 | - 优雅降级和限流 254 | - 数据备份和灾难恢复 255 | 256 | ### 安全性 (Security) 257 | - 零信任网络架构 258 | - 最小权限原则 259 | - 加密传输和存储 260 | - 安全扫描和合规检查 261 | 262 | ### 可维护性 (Maintainability) 263 | - 清晰的代码结构和文档 264 | - 自动化测试覆盖 265 | - 标准化的部署流程 266 | - 版本管理和回滚机制 267 | 268 | ### 可观测性 (Observability) 269 | - 统一的日志聚合 270 | - 全链路追踪 271 | - 关键指标监控 272 | - 智能告警和根因分析 273 | 274 | ## IDP 核心组件架构 275 | 276 | ### 应用配置管理 277 | - 定义应用期望状态 278 | - 声明式配置 279 | - GitOps 工作流 280 | 281 | ### 基础设施编排 282 | - Infrastructure as Code 283 | - 自动化资源配置 284 | - 多云抽象层 285 | 286 | ### 环境管理 287 | - 开发、测试、生产环境一致性 288 | - 环境隔离和复制 289 | - 临时环境快速创建 290 | 291 | ### 部署流水线 292 | - 自动化 CI/CD 293 | - 渐进式交付(金丝雀、蓝绿) 294 | - 自动化测试和质量门 295 | 296 | ### 服务目录 297 | - API 和服务发现 298 | - 文档自动生成 299 | - 版本管理和兼容性 300 | 301 | ## 架构决策考虑 302 | 303 | ### 技术选型 304 | - 评估业务需求和技术成熟度 305 | - 考虑团队技能和学习曲线 306 | - 权衡成本和收益 307 | - 避免过度工程 308 | 309 | ### 迁移策略 310 | - 采用绞杀者模式(Strangler Pattern) 311 | - 逐步迁移,降低风险 312 | - 建立防腐层隔离遗留系统 313 | - 保证业务连续性 314 | 315 | ### 演进路径 316 | - 从简单开始,逐步复杂化 317 | - 基于实际需求演进 318 | - 定期架构审查和重构 319 | - 持续学习和改进`, 320 | references: [ 321 | { title: "Azure Cloud Design Patterns", url: "https://learn.microsoft.com/en-us/azure/architecture/patterns/" }, 322 | { title: "Platform Specification", url: "https://platformspec.io/" }, 323 | { 324 | title: "The Complete Guide to Platform Engineering", 325 | url: "https://jellyfish.co/library/platform-engineering/", 326 | }, 327 | ], 328 | }, 329 | { 330 | id: "internal-developer-platform", 331 | title: "内部开发者平台 (IDP)", 332 | description: "为开发者提供自助服务能力的工具链和工作流程", 333 | category: "framework", 334 | tags: ["自助服务", "开发者工具", "自动化"], 335 | content: `## 什么是 IDP 336 | 337 | 内部开发者平台是一套集成的工具和服务,帮助开发者: 338 | 339 | - 快速创建和部署应用 340 | - 管理基础设施和环境 341 | - 监控和调试应用 342 | - 遵循组织最佳实践 343 | 344 | ## 核心组件 345 | 346 | 1. **应用配置管理**: 定义应用的期望状态 347 | 2. **基础设施编排**: 自动化资源配置 348 | 3. **环境管理**: 开发、测试、生产环境一致性 349 | 4. **部署流水线**: CI/CD 自动化 350 | 5. **可观测性**: 日志、指标、追踪一体化`, 351 | references: [ 352 | { title: "Backstage", url: "https://backstage.io/" }, 353 | { title: "Port", url: "https://getport.io/" }, 354 | ], 355 | }, 356 | { 357 | id: "golden-paths", 358 | title: "黄金路径 (Golden Paths)", 359 | description: "为开发者提供经过验证的最佳实践路径,降低认知负荷", 360 | category: "pattern", 361 | tags: ["最佳实践", "标准化", "开发者体验"], 362 | content: `## 概念 363 | 364 | 黄金路径是组织推荐的、经过验证的技术选择和实践方式。它们: 365 | 366 | - 不是强制性的,而是推荐性的 367 | - 经过优化,能够快速交付价值 368 | - 降低开发者的决策负担 369 | 370 | ## 实施要点 371 | 372 | 1. **模板化**: 提供项目模板和脚手架 373 | 2. **自动化**: 集成 CI/CD 和基础设施即代码 374 | 3. **文档化**: 清晰说明使用方法和原因 375 | 4. **可扩展**: 允许在需要时偏离黄金路径`, 376 | references: [], 377 | }, 378 | { 379 | id: "platform-maturity-model", 380 | title: "平台成熟度模型", 381 | description: "评估和规划平台工程实践成熟度的框架", 382 | category: "methodology", 383 | tags: ["成熟度评估", "路线图", "能力建设"], 384 | content: `## 成熟度级别 385 | 386 | ### Level 1: 临时性 (Ad-hoc) 387 | - 没有统一的平台战略 388 | - 工具选择分散 389 | - 手动流程为主 390 | 391 | ### Level 2: 基础性 (Basic) 392 | - 开始建立平台团队 393 | - 基础的 CI/CD 流水线 394 | - 部分自动化 395 | 396 | ### Level 3: 标准化 (Standardized) 397 | - 统一的开发者门户 398 | - 标准化的黄金路径 399 | - 自助服务能力 400 | 401 | ### Level 4: 优化 (Optimized) 402 | - 数据驱动的决策 403 | - 持续优化开发者体验 404 | - 高度自动化 405 | 406 | ### Level 5: 创新 (Innovative) 407 | - 平台赋能业务创新 408 | - 行业领先实践 409 | - 开源贡献`, 410 | references: [], 411 | }, 412 | ] 413 | -------------------------------------------------------------------------------- /data/projects.ts: -------------------------------------------------------------------------------- 1 | // 平台工程相关项目数据 2 | export type ProjectCategory = 3 | | "platform" 4 | | "devops" 5 | | "observability" 6 | | "security" 7 | | "infrastructure" 8 | | "developer-portal" 9 | | "service-mesh" 10 | | "gitops" 11 | 12 | export type ProjectType = "opensource" | "commercial" | "hybrid" 13 | 14 | export interface Project { 15 | id: string 16 | name: string 17 | description: string 18 | category: ProjectCategory 19 | type: ProjectType 20 | tags: string[] 21 | url: string 22 | github?: string 23 | logo?: string 24 | stars?: number 25 | featured?: boolean 26 | } 27 | 28 | export const projectCategories: { id: ProjectCategory; name: string; description: string }[] = [ 29 | { id: "platform", name: "开发者平台", description: "内部开发者平台和门户" }, 30 | { id: "devops", name: "DevOps 工具", description: "CI/CD、自动化和部署工具" }, 31 | { id: "observability", name: "可观测性", description: "监控、日志、追踪工具" }, 32 | { id: "security", name: "安全", description: "安全扫描、策略管理工具" }, 33 | { id: "infrastructure", name: "基础设施", description: "基础设施即代码和云原生工具" }, 34 | { id: "developer-portal", name: "开发者门户", description: "API 文档和服务目录" }, 35 | { id: "service-mesh", name: "服务网格", description: "服务间通信和流量管理" }, 36 | { id: "gitops", name: "GitOps", description: "基于 Git 的持续交付" }, 37 | ] 38 | 39 | export const projects: Project[] = [ 40 | // 开发者平台 41 | { 42 | id: "backstage", 43 | name: "Backstage", 44 | description: "Spotify 开源的开发者门户平台,用于构建内部开发者平台,提供服务目录、模板和插件生态", 45 | category: "platform", 46 | type: "opensource", 47 | tags: ["开发者门户", "服务目录", "Spotify", "CNCF"], 48 | url: "https://backstage.io/", 49 | github: "https://github.com/backstage/backstage", 50 | stars: 28000, 51 | featured: true, 52 | }, 53 | { 54 | id: "port", 55 | name: "Port", 56 | description: "企业级内部开发者平台,提供服务目录、自助服务和开发者体验管理", 57 | category: "platform", 58 | type: "commercial", 59 | tags: ["开发者门户", "服务目录", "自助服务"], 60 | url: "https://getport.io/", 61 | featured: true, 62 | }, 63 | { 64 | id: "humanitec", 65 | name: "Humanitec", 66 | description: "平台编排器,帮助团队构建和管理内部开发者平台", 67 | category: "platform", 68 | type: "commercial", 69 | tags: ["平台编排", "IDP", "企业级"], 70 | url: "https://humanitec.com/", 71 | }, 72 | { 73 | id: "kratix", 74 | name: "Kratix", 75 | description: "开源的平台即代码框架,用于构建内部开发者平台", 76 | category: "platform", 77 | type: "opensource", 78 | tags: ["平台即代码", "Kubernetes", "GitOps"], 79 | url: "https://kratix.io/", 80 | github: "https://github.com/syntasso/kratix", 81 | }, 82 | { 83 | id: "crossplane", 84 | name: "Crossplane", 85 | description: "开源的云原生控制平面,将 Kubernetes 扩展为通用控制平面", 86 | category: "platform", 87 | type: "opensource", 88 | tags: ["控制平面", "Kubernetes", "CNCF", "多云"], 89 | url: "https://www.crossplane.io/", 90 | github: "https://github.com/crossplane/crossplane", 91 | stars: 9000, 92 | featured: true, 93 | }, 94 | 95 | // DevOps 工具 96 | { 97 | id: "tekton", 98 | name: "Tekton", 99 | description: "云原生 CI/CD 框架,提供 Kubernetes 原生的流水线能力", 100 | category: "devops", 101 | type: "opensource", 102 | tags: ["CI/CD", "Kubernetes", "CNCF", "流水线"], 103 | url: "https://tekton.dev/", 104 | github: "https://github.com/tektoncd/pipeline", 105 | stars: 8500, 106 | }, 107 | { 108 | id: "jenkins-x", 109 | name: "Jenkins X", 110 | description: "基于 Kubernetes 的自动化 CI/CD 解决方案", 111 | category: "devops", 112 | type: "opensource", 113 | tags: ["CI/CD", "Kubernetes", "GitOps"], 114 | url: "https://jenkins-x.io/", 115 | github: "https://github.com/jenkins-x/jx", 116 | }, 117 | { 118 | id: "github-actions", 119 | name: "GitHub Actions", 120 | description: "GitHub 内置的 CI/CD 和自动化平台", 121 | category: "devops", 122 | type: "commercial", 123 | tags: ["CI/CD", "自动化", "GitHub"], 124 | url: "https://github.com/features/actions", 125 | featured: true, 126 | }, 127 | { 128 | id: "gitlab-ci", 129 | name: "GitLab CI/CD", 130 | description: "GitLab 内置的持续集成和持续部署工具", 131 | category: "devops", 132 | type: "hybrid", 133 | tags: ["CI/CD", "DevOps", "GitLab"], 134 | url: "https://docs.gitlab.com/ee/ci/", 135 | }, 136 | { 137 | id: "dagger", 138 | name: "Dagger", 139 | description: "可编程的 CI/CD 引擎,使用代码定义流水线", 140 | category: "devops", 141 | type: "opensource", 142 | tags: ["CI/CD", "可编程", "容器化"], 143 | url: "https://dagger.io/", 144 | github: "https://github.com/dagger/dagger", 145 | stars: 10000, 146 | }, 147 | 148 | // 可观测性 149 | { 150 | id: "prometheus", 151 | name: "Prometheus", 152 | description: "开源的监控和告警系统,云原生监控的事实标准", 153 | category: "observability", 154 | type: "opensource", 155 | tags: ["监控", "告警", "CNCF", "时序数据库"], 156 | url: "https://prometheus.io/", 157 | github: "https://github.com/prometheus/prometheus", 158 | stars: 54000, 159 | featured: true, 160 | }, 161 | { 162 | id: "grafana", 163 | name: "Grafana", 164 | description: "开源的可视化和分析平台,支持多种数据源", 165 | category: "observability", 166 | type: "hybrid", 167 | tags: ["可视化", "仪表盘", "监控"], 168 | url: "https://grafana.com/", 169 | github: "https://github.com/grafana/grafana", 170 | stars: 62000, 171 | featured: true, 172 | }, 173 | { 174 | id: "jaeger", 175 | name: "Jaeger", 176 | description: "开源的端到端分布式追踪系统", 177 | category: "observability", 178 | type: "opensource", 179 | tags: ["追踪", "分布式系统", "CNCF"], 180 | url: "https://www.jaegertracing.io/", 181 | github: "https://github.com/jaegertracing/jaeger", 182 | stars: 20000, 183 | }, 184 | { 185 | id: "opentelemetry", 186 | name: "OpenTelemetry", 187 | description: "云原生可观测性框架,提供统一的遥测数据收集标准", 188 | category: "observability", 189 | type: "opensource", 190 | tags: ["遥测", "标准化", "CNCF", "追踪"], 191 | url: "https://opentelemetry.io/", 192 | github: "https://github.com/open-telemetry", 193 | featured: true, 194 | }, 195 | { 196 | id: "datadog", 197 | name: "Datadog", 198 | description: "云规模的监控和安全平台", 199 | category: "observability", 200 | type: "commercial", 201 | tags: ["APM", "监控", "安全", "企业级"], 202 | url: "https://www.datadoghq.com/", 203 | }, 204 | 205 | // 安全 206 | { 207 | id: "trivy", 208 | name: "Trivy", 209 | description: "全面的安全扫描器,支持容器、文件系统、Git 仓库等", 210 | category: "security", 211 | type: "opensource", 212 | tags: ["安全扫描", "漏洞检测", "容器安全"], 213 | url: "https://trivy.dev/", 214 | github: "https://github.com/aquasecurity/trivy", 215 | stars: 22000, 216 | featured: true, 217 | }, 218 | { 219 | id: "falco", 220 | name: "Falco", 221 | description: "云原生运行时安全项目,检测异常行为和威胁", 222 | category: "security", 223 | type: "opensource", 224 | tags: ["运行时安全", "CNCF", "威胁检测"], 225 | url: "https://falco.org/", 226 | github: "https://github.com/falcosecurity/falco", 227 | stars: 7000, 228 | }, 229 | { 230 | id: "opa", 231 | name: "Open Policy Agent (OPA)", 232 | description: "通用策略引擎,用于统一策略执行", 233 | category: "security", 234 | type: "opensource", 235 | tags: ["策略引擎", "CNCF", "访问控制"], 236 | url: "https://www.openpolicyagent.org/", 237 | github: "https://github.com/open-policy-agent/opa", 238 | stars: 9500, 239 | }, 240 | { 241 | id: "vault", 242 | name: "HashiCorp Vault", 243 | description: "密钥管理和数据保护平台", 244 | category: "security", 245 | type: "hybrid", 246 | tags: ["密钥管理", "加密", "访问控制"], 247 | url: "https://www.vaultproject.io/", 248 | github: "https://github.com/hashicorp/vault", 249 | stars: 30000, 250 | featured: true, 251 | }, 252 | 253 | // 基础设施 254 | { 255 | id: "terraform", 256 | name: "Terraform", 257 | description: "基础设施即代码工具,支持多云和混合云环境", 258 | category: "infrastructure", 259 | type: "hybrid", 260 | tags: ["IaC", "多云", "基础设施"], 261 | url: "https://www.terraform.io/", 262 | github: "https://github.com/hashicorp/terraform", 263 | stars: 42000, 264 | featured: true, 265 | }, 266 | { 267 | id: "pulumi", 268 | name: "Pulumi", 269 | description: "使用通用编程语言定义基础设施的现代 IaC 工具", 270 | category: "infrastructure", 271 | type: "hybrid", 272 | tags: ["IaC", "多语言", "云原生"], 273 | url: "https://www.pulumi.com/", 274 | github: "https://github.com/pulumi/pulumi", 275 | stars: 21000, 276 | }, 277 | { 278 | id: "kubernetes", 279 | name: "Kubernetes", 280 | description: "容器编排平台,云原生应用的事实标准", 281 | category: "infrastructure", 282 | type: "opensource", 283 | tags: ["容器编排", "CNCF", "云原生"], 284 | url: "https://kubernetes.io/", 285 | github: "https://github.com/kubernetes/kubernetes", 286 | stars: 109000, 287 | featured: true, 288 | }, 289 | { 290 | id: "helm", 291 | name: "Helm", 292 | description: "Kubernetes 包管理器,简化应用部署和管理", 293 | category: "infrastructure", 294 | type: "opensource", 295 | tags: ["包管理", "Kubernetes", "CNCF"], 296 | url: "https://helm.sh/", 297 | github: "https://github.com/helm/helm", 298 | stars: 26500, 299 | }, 300 | 301 | // 开发者门户 302 | { 303 | id: "developer-portal", 304 | name: "Spotify Developer Portal", 305 | description: "基于 Backstage 的开发者门户参考实现", 306 | category: "developer-portal", 307 | type: "opensource", 308 | tags: ["开发者体验", "文档", "API"], 309 | url: "https://backstage.io/docs/features/software-catalog/", 310 | github: "https://github.com/backstage/backstage", 311 | }, 312 | { 313 | id: "redocly", 314 | name: "Redocly", 315 | description: "API 文档和开发者门户平台", 316 | category: "developer-portal", 317 | type: "commercial", 318 | tags: ["API 文档", "OpenAPI", "开发者体验"], 319 | url: "https://redocly.com/", 320 | }, 321 | { 322 | id: "readme", 323 | name: "ReadMe", 324 | description: "交互式 API 文档和开发者中心平台", 325 | category: "developer-portal", 326 | type: "commercial", 327 | tags: ["API 文档", "开发者体验", "分析"], 328 | url: "https://readme.com/", 329 | }, 330 | 331 | // 服务网格 332 | { 333 | id: "istio", 334 | name: "Istio", 335 | description: "开源服务网格,提供流量管理、安全和可观测性", 336 | category: "service-mesh", 337 | type: "opensource", 338 | tags: ["服务网格", "流量管理", "CNCF"], 339 | url: "https://istio.io/", 340 | github: "https://github.com/istio/istio", 341 | stars: 35000, 342 | featured: true, 343 | }, 344 | { 345 | id: "linkerd", 346 | name: "Linkerd", 347 | description: "轻量级服务网格,专注于简单性和性能", 348 | category: "service-mesh", 349 | type: "opensource", 350 | tags: ["服务网格", "轻量级", "CNCF"], 351 | url: "https://linkerd.io/", 352 | github: "https://github.com/linkerd/linkerd2", 353 | stars: 10500, 354 | }, 355 | { 356 | id: "cilium", 357 | name: "Cilium", 358 | description: "基于 eBPF 的网络、安全和可观测性解决方案", 359 | category: "service-mesh", 360 | type: "opensource", 361 | tags: ["eBPF", "网络", "安全", "CNCF"], 362 | url: "https://cilium.io/", 363 | github: "https://github.com/cilium/cilium", 364 | stars: 19500, 365 | featured: true, 366 | }, 367 | 368 | // GitOps 369 | { 370 | id: "argocd", 371 | name: "Argo CD", 372 | description: "声明式 GitOps 持续交付工具", 373 | category: "gitops", 374 | type: "opensource", 375 | tags: ["GitOps", "Kubernetes", "CNCF", "CD"], 376 | url: "https://argo-cd.readthedocs.io/", 377 | github: "https://github.com/argoproj/argo-cd", 378 | stars: 17000, 379 | featured: true, 380 | }, 381 | { 382 | id: "flux", 383 | name: "Flux", 384 | description: "Kubernetes 的 GitOps 工具包,用于保持集群与配置同步", 385 | category: "gitops", 386 | type: "opensource", 387 | tags: ["GitOps", "Kubernetes", "CNCF"], 388 | url: "https://fluxcd.io/", 389 | github: "https://github.com/fluxcd/flux2", 390 | stars: 6200, 391 | }, 392 | { 393 | id: "kustomize", 394 | name: "Kustomize", 395 | description: "Kubernetes 原生的配置管理工具", 396 | category: "gitops", 397 | type: "opensource", 398 | tags: ["配置管理", "Kubernetes", "声明式"], 399 | url: "https://kustomize.io/", 400 | github: "https://github.com/kubernetes-sigs/kustomize", 401 | stars: 10800, 402 | }, 403 | ] 404 | -------------------------------------------------------------------------------- /components/sections/domain-practices-section.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import type React from "react" 4 | 5 | import { useState } from "react" 6 | import { domainPractices, domainCategories } from "@/data/domain-practices" 7 | import { Badge } from "@/components/ui/badge" 8 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 9 | import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" 10 | import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet" 11 | import { ScrollArea } from "@/components/ui/scroll-area" 12 | import { 13 | ExternalLink, 14 | Code, 15 | Database, 16 | Brain, 17 | Layout, 18 | Bot, 19 | Globe, 20 | Smile, 21 | CheckCircle2, 22 | AlertTriangle, 23 | Lightbulb, 24 | PackageOpen, 25 | } from "lucide-react" 26 | 27 | interface DomainPracticesSectionProps { 28 | selectedPractice?: string | null 29 | onSelectPractice?: (id: string | null) => void 30 | } 31 | 32 | const iconMap: Record = { 33 | Code: , 34 | Database: , 35 | Brain: , 36 | Layout: , 37 | Bot: , 38 | Globe: , 39 | Smile: , 40 | } 41 | 42 | export function DomainPracticesSection({ selectedPractice, onSelectPractice }: DomainPracticesSectionProps) { 43 | const [activeDomain, setActiveDomain] = useState("all") 44 | const [localSelectedPractice, setLocalSelectedPractice] = useState(null) 45 | 46 | const currentSelectedId = selectedPractice ?? localSelectedPractice 47 | const handleSelectPractice = onSelectPractice ?? setLocalSelectedPractice 48 | 49 | const filteredPractices = 50 | activeDomain === "all" ? domainPractices : domainPractices.filter((p) => p.domain === activeDomain) 51 | 52 | const selected = domainPractices.find((p) => p.id === currentSelectedId) 53 | 54 | return ( 55 |
    56 |
    57 |
    58 |

    领域平台实践

    59 |

    60 | 探索不同领域的平台工程实践,从软件开发到 AI、数据、前端等各个技术领域的平台建设经验。 61 |

    62 |
    63 | 64 | 65 | 66 | {domainCategories.map((cat) => ( 67 | 68 | {cat.name} 69 | 70 | ))} 71 | 72 | 73 | 74 |
    75 | {filteredPractices.map((practice) => ( 76 | handleSelectPractice(practice.id)} 80 | > 81 | 82 |
    83 |
    {iconMap[practice.icon]}
    84 | 85 | {practice.domain} 86 | 87 |
    88 | {practice.title} 89 | {practice.description} 90 |
    91 | 92 |
    93 | {practice.tags.slice(0, 3).map((tag) => ( 94 | 95 | {tag} 96 | 97 | ))} 98 |
    99 |
    100 |
    101 | ))} 102 |
    103 | 104 | {/* Practice Detail Sheet */} 105 | !open && handleSelectPractice(null)}> 106 | 107 | {selected && ( 108 | <> 109 | 110 |
    111 |
    {iconMap[selected.icon]}
    112 | {selected.domain} 113 |
    114 | {selected.title} 115 |

    {selected.description}

    116 |
    117 | 118 | 119 | {/* Main Content */} 120 |
    121 |
    122 | {selected.content.split("\n").map((line, i) => { 123 | if (line.startsWith("## ")) { 124 | return ( 125 |

    126 | {line.replace("## ", "")} 127 |

    128 | ) 129 | } 130 | if (line.startsWith("### ")) { 131 | return ( 132 |

    133 | {line.replace("### ", "")} 134 |

    135 | ) 136 | } 137 | if (line.startsWith("- ")) { 138 | return ( 139 |
  • 140 | {line.replace("- ", "")} 141 |
  • 142 | ) 143 | } 144 | if (line.match(/^\d+\./)) { 145 | return ( 146 |
  • 147 | {line} 148 |
  • 149 | ) 150 | } 151 | if (line.trim() === "") { 152 | return
    153 | } 154 | return ( 155 |

    156 | {line} 157 |

    158 | ) 159 | })} 160 |
    161 |
    162 | 163 | {/* Key Capabilities */} 164 |
    165 |
    166 | 167 |

    核心能力

    168 |
    169 |
      170 | {selected.keyCapabilities.map((capability, i) => ( 171 |
    • 172 | 173 | {capability} 174 |
    • 175 | ))} 176 |
    177 |
    178 | 179 | {/* Technical Stack */} 180 |
    181 |
    182 | 183 |

    技术栈

    184 |
    185 |
    186 | {selected.technicalStack.map((tech, i) => ( 187 |
    188 | 189 | {tech} 190 | 191 |
    192 | ))} 193 |
    194 |
    195 | 196 | {/* Challenges */} 197 |
    198 |
    199 | 200 |

    主要挑战

    201 |
    202 |
      203 | {selected.challenges.map((challenge, i) => ( 204 |
    • 205 | 206 | {challenge} 207 |
    • 208 | ))} 209 |
    210 |
    211 | 212 | {/* Best Practices */} 213 |
    214 |
    215 | 216 |

    最佳实践

    217 |
    218 |
      219 | {selected.bestPractices.map((practice, i) => ( 220 |
    • 221 | 222 | {practice} 223 |
    • 224 | ))} 225 |
    226 |
    227 | 228 | {/* References */} 229 | {selected.references && selected.references.length > 0 && ( 230 |
    231 |

    参考资料

    232 |
    233 | {selected.references.map((ref, i) => ( 234 | 241 | 242 | {ref.title} 243 | 244 | ))} 245 |
    246 |
    247 | )} 248 | 249 | {/* Tags */} 250 |
    251 | {selected.tags.map((tag) => ( 252 | 253 | {tag} 254 | 255 | ))} 256 |
    257 |
    258 | 259 | )} 260 |
    261 |
    262 |
    263 |
    264 | ) 265 | } 266 | -------------------------------------------------------------------------------- /data/domain-practices.ts: -------------------------------------------------------------------------------- 1 | // 领域平台实践数据 2 | export interface DomainPractice { 3 | id: string 4 | title: string 5 | description: string 6 | domain: string 7 | icon: string 8 | tags: string[] 9 | content: string 10 | keyCapabilities: string[] 11 | technicalStack: string[] 12 | challenges: string[] 13 | bestPractices: string[] 14 | references?: { title: string; url: string }[] 15 | } 16 | 17 | export const domainPractices: DomainPractice[] = [ 18 | { 19 | id: "software-dev-platform", 20 | title: "软件开发平台工程", 21 | description: "构建支持应用开发全生命周期的内部开发者平台,提供自助服务能力和黄金路径", 22 | domain: "软件开发", 23 | icon: "Code", 24 | tags: ["IDP", "CI/CD", "自助服务", "黄金路径"], 25 | content: `软件开发平台工程是平台工程最核心的领域,专注于为应用开发者提供标准化、自动化的开发工具链和工作流程。通过内部开发者平台(IDP),开发者可以自助完成从代码提交到生产部署的全流程操作。 26 | 27 | ## 核心价值 28 | 29 | - **提升开发效率**: 通过自动化和标准化减少重复性工作,让开发者专注于业务价值 30 | - **降低认知负荷**: 提供开箱即用的最佳实践和预配置环境,减少技术选型和配置工作 31 | - **确保一致性**: 统一的工具链和流程保证不同团队间的协作效率 32 | - **加速交付**: 缩短从开发到生产的周期时间,支持快速迭代 33 | 34 | ## 平台架构 35 | 36 | 现代软件开发平台通常包含以下层次: 37 | 38 | 1. **开发者门户**: 统一的界面,提供服务目录、文档、API 参考等 39 | 2. **应用配置层**: 定义应用期望状态,如 Score、Acorn 等标准 40 | 3. **基础设施编排**: 自动化资源配置,如 Terraform、Pulumi 41 | 4. **CI/CD 流水线**: 自动化构建、测试、部署流程 42 | 5. **可观测性平台**: 日志、指标、追踪一体化`, 43 | keyCapabilities: [ 44 | "自助服务的应用创建和部署", 45 | "标准化的项目模板和脚手架", 46 | "自动化的 CI/CD 流水线", 47 | "统一的开发者门户", 48 | "环境管理和配置管理", 49 | "集成的可观测性和监控", 50 | "安全和合规自动化", 51 | "文档和知识库", 52 | ], 53 | technicalStack: [ 54 | "Backstage - 开发者门户", 55 | "GitLab/GitHub - 代码管理和 CI/CD", 56 | "ArgoCD/Flux - GitOps 部署", 57 | "Terraform/Pulumi - 基础设施即代码", 58 | "Kubernetes - 容器编排", 59 | "Prometheus/Grafana - 监控告警", 60 | "ELK/Loki - 日志聚合", 61 | "Vault - 密钥管理", 62 | ], 63 | challenges: [ 64 | "平衡标准化与灵活性,避免过度约束创新", 65 | "管理不同技术栈和遗留系统的复杂性", 66 | "确保平台采纳率,避免开发者绕过平台", 67 | "持续维护和演进平台能力", 68 | "度量平台价值和开发者满意度", 69 | ], 70 | bestPractices: [ 71 | "采用产品思维运营平台,将开发者视为客户", 72 | "建立快速反馈机制,定期收集开发者需求", 73 | "提供清晰的文档和培训材料", 74 | "实施渐进式推广,从试点团队开始", 75 | "建立平台 SLA 和支持渠道", 76 | "使用度量数据驱动平台优化决策", 77 | ], 78 | references: [ 79 | { title: "Platform Engineering Best Practices", url: "https://platformengineering.org/" }, 80 | { title: "Backstage Documentation", url: "https://backstage.io/docs/overview/what-is-backstage" }, 81 | { title: "CNCF Platform Engineering Whitepaper", url: "https://tag-app-delivery.cncf.io/whitepapers/platforms/" }, 82 | ], 83 | }, 84 | { 85 | id: "data-platform", 86 | title: "数据开发平台工程", 87 | description: "构建现代数据栈,提供数据摄取、存储、转换、编排的完整能力,支持数据驱动决策", 88 | domain: "数据工程", 89 | icon: "Database", 90 | tags: ["现代数据栈", "ELT", "数据质量", "数据治理"], 91 | content: `数据开发平台工程专注于构建可扩展、可靠的数据基础设施,使数据工程师和分析师能够高效地处理和分析数据。现代数据平台采用模块化、云原生的架构,强调自助服务和数据质量。 92 | 93 | ## 现代数据栈架构 94 | 95 | 现代数据平台通常包含四个核心层次: 96 | 97 | 1. **数据摄取层**: 从各种数据源提取和加载数据 98 | 2. **数据存储层**: 云数据仓库或数据湖存储 99 | 3. **数据转换层**: 使用 ELT 模式进行数据清洗和建模 100 | 4. **数据编排层**: 调度和监控数据管道 101 | 102 | ## ELT vs ETL 103 | 104 | 现代数据平台普遍采用 ELT(提取-加载-转换)而非传统 ETL: 105 | 106 | - **性能优势**: 利用云数据仓库的计算能力进行转换 107 | - **灵活性**: 原始数据保留,支持多样化转换需求 108 | - **简化架构**: 减少中间层,降低维护成本`, 109 | keyCapabilities: [ 110 | "多源数据集成和同步", 111 | "云数据仓库管理", 112 | "数据转换和建模(dbt)", 113 | "数据质量监控和验证", 114 | "数据血缘和影响分析", 115 | "数据目录和元数据管理", 116 | "数据合约和 SLA 管理", 117 | "访问控制和安全治理", 118 | ], 119 | technicalStack: [ 120 | "Airbyte/Fivetran - 数据摄取", 121 | "Snowflake/BigQuery/Redshift - 数据仓库", 122 | "dbt - 数据转换", 123 | "Airflow/Dagster/Prefect - 数据编排", 124 | "Great Expectations - 数据质量", 125 | "Apache Iceberg/Delta Lake - 数据湖", 126 | "Datahub/Amundsen - 数据目录", 127 | "Monte Carlo/Soda - 数据可观测性", 128 | ], 129 | challenges: [ 130 | "数据质量管理,防止脏数据传播", 131 | "成本控制,避免云数据仓库费用失控", 132 | "数据治理和合规性要求", 133 | "跨团队的数据协作和共享", 134 | "实时数据处理需求", 135 | ], 136 | bestPractices: [ 137 | "实施数据合约,明确数据生产者和消费者责任", 138 | "采用元数据驱动的架构,提升自动化和可观测性", 139 | "优先考虑数据质量和可观测性", 140 | "使用 Python + SQL 混合工作流", 141 | "建立数据 SLA 和监控告警", 142 | "推行自助服务的数据分析能力", 143 | ], 144 | references: [ 145 | { title: "Modern Data Stack Guide", url: "https://www.getdbt.com/blog/data-engineering" }, 146 | { title: "Data Platform Modernization", url: "https://blog.dataengineerthings.org/" }, 147 | { title: "dbt Documentation", url: "https://docs.getdbt.com/" }, 148 | ], 149 | }, 150 | { 151 | id: "ai-platform", 152 | title: "AI开发平台工程", 153 | description: "构建 MLOps 平台,支持机器学习模型的训练、部署、监控全生命周期管理", 154 | domain: "AI/ML", 155 | icon: "Brain", 156 | tags: ["MLOps", "模型训练", "模型部署", "AI基础设施"], 157 | content: `AI开发平台工程(也称 MLOps 平台)将软件工程的最佳实践应用于机器学习工作流,实现 ML 模型的规模化、自动化部署和运维。现代 AI 平台需要提供从数据准备到模型服务的端到端能力。 158 | 159 | ## MLOps 生命周期 160 | 161 | 完整的 MLOps 平台覆盖以下阶段: 162 | 163 | 1. **数据管理**: 数据版本化、特征工程、数据验证 164 | 2. **模型开发**: 实验跟踪、超参数调优、模型版本管理 165 | 3. **模型训练**: 分布式训练、GPU/TPU 资源调度 166 | 4. **模型部署**: 模型注册、A/B 测试、渐进式发布 167 | 5. **模型监控**: 性能监控、数据漂移检测、模型再训练 168 | 169 | ## AI 基础设施 170 | 171 | 现代 AI 平台需要强大的硬件和软件基础设施: 172 | 173 | - **计算资源**: GPU/TPU 集群、弹性伸缩 174 | - **存储**: 高性能数据湖、模型仓库 175 | - **框架**: TensorFlow、PyTorch、JAX 176 | - **容器化**: Docker、Kubernetes for ML`, 177 | keyCapabilities: [ 178 | "实验管理和跟踪", 179 | "分布式模型训练", 180 | "特征存储和管理", 181 | "模型版本控制和注册", 182 | "自动化模型部署", 183 | "在线和批量推理服务", 184 | "模型性能监控", 185 | "数据和模型漂移检测", 186 | ], 187 | technicalStack: [ 188 | "MLflow/Weights & Biases - 实验跟踪", 189 | "Kubeflow/Ray - 分布式训练", 190 | "Feast - 特征存储", 191 | "Seldon/KServe - 模型服务", 192 | "TensorFlow Serving - 模型部署", 193 | "Evidently/WhyLabs - 模型监控", 194 | "DVC - 数据版本控制", 195 | "Airflow - ML 工作流编排", 196 | ], 197 | challenges: [ 198 | "GPU 资源的高效调度和成本控制", 199 | "模型可解释性和公平性", 200 | "数据隐私和安全合规", 201 | "模型性能退化的检测和处理", 202 | "跨团队的模型共享和复用", 203 | ], 204 | bestPractices: [ 205 | "建立标准化的 ML 流水线模板", 206 | "实施严格的模型版本管理", 207 | "自动化模型测试和验证", 208 | "建立模型监控和告警机制", 209 | "实施特征复用和共享", 210 | "建立模型治理和合规流程", 211 | ], 212 | references: [ 213 | { title: "MLOps Platforms Guide", url: "https://www.qwak.com/post/top-mlops-end-to-end" }, 214 | { title: "Kubeflow Documentation", url: "https://www.kubeflow.org/" }, 215 | { title: "ML Infrastructure Best Practices", url: "https://rafay.co/ai-and-cloud-native-blog/" }, 216 | ], 217 | }, 218 | { 219 | id: "frontend-platform", 220 | title: "大前端平台工程", 221 | description: "构建前端基础设施,包括微前端、构建工具链、组件库、开发者工具等", 222 | domain: "前端工程", 223 | icon: "Layout", 224 | tags: ["微前端", "构建工具", "组件库", "开发者工具"], 225 | content: `大前端平台工程专注于提升前端开发效率和应用性能,通过统一的工具链、组件库和最佳实践,支持多团队独立开发和部署。微前端架构是现代大前端平台的核心模式之一。 226 | 227 | ## 微前端架构 228 | 229 | 微前端允许将单个应用拆分为多个独立部署的单元: 230 | 231 | - **团队自治**: 每个团队独立选择技术栈和发布节奏 232 | - **独立部署**: 各模块可独立发布,降低变更风险 233 | - **技术栈灵活**: 支持不同框架共存 234 | - **边缘路由**: 在 CDN 层面进行应用组合 235 | 236 | ## 构建工具链 237 | 238 | 现代前端平台需要高效的构建工具: 239 | 240 | - **Turbopack**: 新一代打包工具,显著提升构建速度 241 | - **Turborepo**: Monorepo 管理工具 242 | - **Vite**: 快速的开发服务器和构建工具`, 243 | keyCapabilities: [ 244 | "微前端应用编排和路由", 245 | "统一的组件库和设计系统", 246 | "快速构建和热更新", 247 | "代码质量和规范检查", 248 | "性能监控和优化", 249 | "多环境部署和灰度发布", 250 | "开发者工具和调试支持", 251 | "文档和示例平台", 252 | ], 253 | technicalStack: [ 254 | "Single-SPA/Module Federation - 微前端框架", 255 | "Turbopack/Vite - 构建工具", 256 | "Turborepo/Nx - Monorepo 管理", 257 | "React/Vue/Angular - 前端框架", 258 | "Vercel/Netlify - 部署平台", 259 | "Storybook - 组件开发", 260 | "ESLint/Prettier - 代码规范", 261 | "Playwright/Cypress - 测试工具", 262 | ], 263 | challenges: ["微前端间的通信和状态共享", "样式冲突和隔离", "构建产物体积控制", "首屏加载性能优化", "跨框架兼容性"], 264 | bestPractices: [ 265 | "采用 Monorepo 管理共享代码", 266 | "建立统一的设计系统和组件库", 267 | "使用特性标志控制功能发布", 268 | "优化构建流程,使用缓存和增量构建", 269 | "实施自动化测试和视觉回归测试", 270 | "建立性能预算和监控", 271 | ], 272 | references: [ 273 | { title: "Microfrontends on Vercel", url: "https://vercel.com/docs/frameworks/microfrontends" }, 274 | { title: "Turborepo Documentation", url: "https://turbo.build/repo" }, 275 | { title: "Module Federation", url: "https://module-federation.io/" }, 276 | ], 277 | }, 278 | { 279 | id: "agent-platform", 280 | title: "Agent开发平台工程", 281 | description: "构建 AI Agent 开发和运行平台,支持多步骤、自主决策的智能代理系统", 282 | domain: "AI Agent", 283 | icon: "Bot", 284 | tags: ["LangChain", "LangGraph", "多智能体", "工作流编排"], 285 | content: `Agent开发平台工程是 2024 年快速兴起的新领域,专注于构建支持 AI Agent 开发、测试、部署和监控的基础设施。随着大语言模型能力的提升,AI Agent 正在从简单的对话式应用演进为能够自主完成复杂任务的智能系统。 286 | 287 | ## AI Agent 架构演进 288 | 289 | 从简单到复杂的 Agent 架构: 290 | 291 | 1. **单步 Agent**: 简单的提示-响应模式 292 | 2. **工具调用 Agent**: 能够调用外部 API 和工具 293 | 3. **多步推理 Agent**: ReAct 模式,思考-行动循环 294 | 4. **多智能体系统**: 多个 Agent 协作完成任务 295 | 296 | ## LangChain vs LangGraph 297 | 298 | - **LangChain**: 适合线性工作流和简单集成 299 | - **LangGraph**: 提供强大的状态管理、分支、循环能力,适合复杂多 Agent 场景`, 300 | keyCapabilities: [ 301 | "Agent 工作流设计和编排", 302 | "工具和 API 集成管理", 303 | "对话上下文和记忆管理", 304 | "多智能体协作框架", 305 | "Agent 行为监控和调试", 306 | "安全和权限控制", 307 | "提示词版本管理", 308 | "性能和成本优化", 309 | ], 310 | technicalStack: [ 311 | "LangChain - Agent 框架", 312 | "LangGraph - 复杂工作流编排", 313 | "AutoGPT - 自主 Agent", 314 | "OpenAI/Anthropic API - LLM 后端", 315 | "Vector Databases - 知识检索", 316 | "FastAPI/Flask - Agent 服务", 317 | "Redis - 会话状态管理", 318 | "LangSmith - 监控和调试", 319 | ], 320 | challenges: [ 321 | "Agent 行为的可预测性和可靠性", 322 | "工具调用的安全性控制", 323 | "上下文窗口和成本管理", 324 | "Agent 决策的可解释性", 325 | "多 Agent 协作的复杂性", 326 | ], 327 | bestPractices: [ 328 | "明确定义 Agent 的目标和边界", 329 | "实施严格的工具调用权限控制", 330 | "建立完善的测试和评估机制", 331 | "使用结构化输出提升可靠性", 332 | "实施成本监控和限流", 333 | "保留 Agent 决策日志用于审计", 334 | ], 335 | references: [ 336 | { title: "LangChain Documentation", url: "https://python.langchain.com/" }, 337 | { title: "LangGraph Guide", url: "https://langchain-ai.github.io/langgraph/" }, 338 | { title: "Building Autonomous AI Agents", url: "https://www.incentius.com/blog-posts/" }, 339 | ], 340 | }, 341 | { 342 | id: "developer-portal", 343 | title: "开发者门户与平台工程", 344 | description: "构建统一的开发者门户,提供服务目录、文档、API 管理、自助服务等能力", 345 | domain: "开发者门户", 346 | icon: "Globe", 347 | tags: ["Backstage", "服务目录", "API 管理", "文档平台"], 348 | content: `开发者门户是平台工程的核心界面,为开发者提供统一的服务发现、文档查阅、资源申请入口。Backstage 是目前最流行的开源开发者门户框架,由 Spotify 开源并被 CNCF 孵化。 349 | 350 | ## 开发者门户的价值 351 | 352 | - **提升可发现性**: 统一的服务目录,快速找到所需资源 353 | - **减少重复**: 通过模板和最佳实践避免重复造轮子 354 | - **标准化**: 统一的工具和流程,降低协作成本 355 | - **自助服务**: 开发者自主完成常见任务,减少等待 356 | 357 | ## Backstage 核心能力 358 | 359 | 1. **软件目录**: 管理所有软件组件、服务、库的元数据 360 | 2. **软件模板**: 快速创建符合最佳实践的新项目 361 | 3. **TechDocs**: 文档即代码,与项目同步维护 362 | 4. **插件生态**: 丰富的插件系统,集成各类工具`, 363 | keyCapabilities: [ 364 | "统一的服务和组件目录", 365 | "项目模板和脚手架", 366 | "集成文档平台", 367 | "API 目录和测试工具", 368 | "资源申请和审批流程", 369 | "插件市场和扩展性", 370 | "搜索和发现能力", 371 | "权限和访问控制", 372 | ], 373 | technicalStack: [ 374 | "Backstage - 门户框架", 375 | "GitHub/GitLab - 代码仓库集成", 376 | "Kubernetes - 服务发现", 377 | "Swagger/OpenAPI - API 文档", 378 | "MkDocs/Docusaurus - 文档生成", 379 | "LDAP/OAuth - 身份认证", 380 | "PostgreSQL - 元数据存储", 381 | "各类 Backstage 插件", 382 | ], 383 | challenges: [ 384 | "初期实施需要较大工程投入", 385 | "保持服务目录数据的准确性和时效性", 386 | "推动开发者采纳和使用", 387 | "插件集成和维护成本", 388 | "跨组织的数据治理", 389 | ], 390 | bestPractices: [ 391 | "采用产品化思维运营门户", 392 | "自动化服务元数据的收集和更新", 393 | "提供清晰的入门指南和培训", 394 | "建立反馈机制,持续改进", 395 | "从核心功能开始,逐步扩展", 396 | "重视文档质量和搜索体验", 397 | ], 398 | references: [ 399 | { title: "Backstage Official Site", url: "https://backstage.io/" }, 400 | { title: "Backstage Documentation", url: "https://backstage.io/docs/" }, 401 | { 402 | title: "Platform Engineering with Backstage", 403 | url: "https://www.forrester.com/blogs/how-backstage-is-transforming-platform-engineering/", 404 | }, 405 | ], 406 | }, 407 | { 408 | id: "devex-platform", 409 | title: "开发者体验平台工程", 410 | description: "通过度量和数据驱动的方法,持续优化开发者生产力和满意度", 411 | domain: "开发者体验", 412 | icon: "Smile", 413 | tags: ["DX 指标", "生产力", "DORA", "SPACE"], 414 | content: `开发者体验(DX)平台工程专注于度量和优化开发者的日常工作体验。通过结合定性和定量数据,识别瓶颈并转化为可衡量的业务价值。DX 平台由 DORA 和 SPACE 框架的研究者设计。 415 | 416 | ## DX Core 4 框架 417 | 418 | 平衡四个维度来衡量开发者生产力: 419 | 420 | 1. **速度 (Speed)**: 交付速度和周期时间 421 | 2. **效能 (Effectiveness)**: 任务完成质量和成功率 422 | 3. **质量 (Quality)**: 代码质量和系统可靠性 423 | 4. **业务影响 (Impact)**: 对业务目标的实际贡献 424 | 425 | ## 开发者体验指数 (DXI) 426 | 427 | DXI 是一个综合性指标,衡量 14 个维度: 428 | 429 | - 深度工作时间 430 | - 本地迭代速度 431 | - 发布便捷性 432 | - 代码可维护性 433 | - 文档质量 434 | - 工具链效率 435 | - ... 436 | 437 | 研究表明,DXI 每提高 1 分,每位工程师每周可节省 13 分钟。`, 438 | keyCapabilities: [ 439 | "开发者满意度调查", 440 | "生产力指标采集和分析", 441 | "工具链性能监控", 442 | "流程瓶颈识别", 443 | "ROI 计算和报告", 444 | "团队对比和基准测试", 445 | "改进建议和优先级排序", 446 | "持续反馈循环", 447 | ], 448 | technicalStack: [ 449 | "DX Platform - 开发者智能平台", 450 | "DORA Metrics - 交付性能指标", 451 | "SPACE Framework - 生产力框架", 452 | "Datadog/New Relic - 应用监控", 453 | "GitHub/GitLab Analytics - 代码分析", 454 | "Slack/Teams - 协作数据", 455 | "Jira/Linear - 项目管理数据", 456 | "自定义调查工具", 457 | ], 458 | challenges: [ 459 | "选择正确的指标,避免度量误导", 460 | "平衡定量数据和定性反馈", 461 | "保护开发者隐私和信任", 462 | "将 DX 改进转化为业务价值", 463 | "跨团队和组织的对比公平性", 464 | ], 465 | bestPractices: [ 466 | "结合定性和定量方法", 467 | "关注结果指标而非活动指标", 468 | "建立安全的反馈环境", 469 | "定期而非过度频繁地度量", 470 | "将 DX 改进与业务目标对齐", 471 | "持续迭代度量框架本身", 472 | ], 473 | references: [ 474 | { title: "DX Platform", url: "https://getdx.com/" }, 475 | { title: "Developer Productivity Guide", url: "https://getdx.com/blog/developer-productivity/" }, 476 | { title: "Developer Experience Index", url: "https://getdx.com/report/developer-experience-index" }, 477 | ], 478 | }, 479 | ] 480 | 481 | export const domainCategories = [ 482 | { id: "all", name: "全部领域" }, 483 | { id: "软件开发", name: "软件开发" }, 484 | { id: "数据工程", name: "数据工程" }, 485 | { id: "AI/ML", name: "AI/ML" }, 486 | { id: "前端工程", name: "前端工程" }, 487 | { id: "AI Agent", name: "AI Agent" }, 488 | { id: "开发者门户", name: "开发者门户" }, 489 | { id: "开发者体验", name: "开发者体验" }, 490 | ] 491 | -------------------------------------------------------------------------------- /components/sections/projects-section.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState, useMemo } from "react" 4 | import { projects, projectCategories, type ProjectCategory, type ProjectType } from "@/data/projects" 5 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 6 | import { Button } from "@/components/ui/button" 7 | import { Badge } from "@/components/ui/badge" 8 | import { Kbd } from "@/components/ui/kbd" 9 | import { LayoutGrid, List, ExternalLink, Github, Star, Filter, X } from "lucide-react" 10 | import { 11 | DropdownMenu, 12 | DropdownMenuContent, 13 | DropdownMenuCheckboxItem, 14 | DropdownMenuLabel, 15 | DropdownMenuSeparator, 16 | DropdownMenuTrigger, 17 | } from "@/components/ui/dropdown-menu" 18 | 19 | type ViewMode = "grid" | "list" 20 | 21 | const typeLabels: Record = { 22 | opensource: "开源", 23 | commercial: "商业", 24 | hybrid: "混合", 25 | } 26 | 27 | const typeColors: Record = { 28 | opensource: "bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/20", 29 | commercial: "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20", 30 | hybrid: "bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/20", 31 | } 32 | 33 | export function ProjectsSection() { 34 | const [viewMode, setViewMode] = useState("grid") 35 | const [selectedCategories, setSelectedCategories] = useState([]) 36 | const [selectedTypes, setSelectedTypes] = useState([]) 37 | 38 | const filteredProjects = useMemo(() => { 39 | return projects.filter((project) => { 40 | const categoryMatch = selectedCategories.length === 0 || selectedCategories.includes(project.category) 41 | const typeMatch = selectedTypes.length === 0 || selectedTypes.includes(project.type) 42 | return categoryMatch && typeMatch 43 | }) 44 | }, [selectedCategories, selectedTypes]) 45 | 46 | const toggleCategory = (category: ProjectCategory) => { 47 | setSelectedCategories((prev) => 48 | prev.includes(category) ? prev.filter((c) => c !== category) : [...prev, category], 49 | ) 50 | } 51 | 52 | const toggleType = (type: ProjectType) => { 53 | setSelectedTypes((prev) => (prev.includes(type) ? prev.filter((t) => t !== type) : [...prev, type])) 54 | } 55 | 56 | const clearFilters = () => { 57 | setSelectedCategories([]) 58 | setSelectedTypes([]) 59 | } 60 | 61 | const hasFilters = selectedCategories.length > 0 || selectedTypes.length > 0 62 | 63 | const formatStars = (stars: number) => { 64 | if (stars >= 1000) { 65 | return `${(stars / 1000).toFixed(1)}k` 66 | } 67 | return stars.toString() 68 | } 69 | 70 | return ( 71 |
    72 |
    73 | {/* 标题区域 */} 74 |
    75 |

    项目

    76 |

    77 | 平台工程领域相关的开源和商业项目集合,助你构建内部开发者平台 78 |

    79 |
    80 | 快捷键 81 | g p 82 |
    83 |
    84 | 85 | {/* 工具栏 */} 86 |
    87 | {/* 筛选器 */} 88 |
    89 | {/* 分类筛选 */} 90 | 91 | 92 | 101 | 102 | 103 | 按分类筛选 104 | 105 | {projectCategories.map((category) => ( 106 | toggleCategory(category.id)} 110 | > 111 | {category.name} 112 | 113 | ))} 114 | 115 | 116 | 117 | {/* 类型筛选 */} 118 | 119 | 120 | 129 | 130 | 131 | 按类型筛选 132 | 133 | toggleType("opensource")} 136 | > 137 | 开源项目 138 | 139 | toggleType("commercial")} 142 | > 143 | 商业项目 144 | 145 | toggleType("hybrid")} 148 | > 149 | 混合模式 150 | 151 | 152 | 153 | 154 | {/* 清除筛选 */} 155 | {hasFilters && ( 156 | 160 | )} 161 | 162 | {/* 结果计数 */} 163 | 共 {filteredProjects.length} 个项目 164 |
    165 | 166 | {/* 视图切换 */} 167 |
    168 | 177 | 186 |
    187 |
    188 | 189 | {/* 项目列表 - 宫格视图 */} 190 | {viewMode === "grid" && ( 191 |
    192 | {filteredProjects.map((project) => ( 193 | 197 | {project.featured && ( 198 |
    199 | 200 | 推荐 201 | 202 |
    203 | )} 204 | 205 |
    206 |
    207 | {project.name.charAt(0)} 208 |
    209 |
    210 | {project.name} 211 |
    212 | 213 | {typeLabels[project.type]} 214 | 215 | 216 | {projectCategories.find((c) => c.id === project.category)?.name} 217 | 218 |
    219 |
    220 |
    221 |
    222 | 223 | {project.description} 224 |
    225 | {project.tags.slice(0, 3).map((tag) => ( 226 | 227 | {tag} 228 | 229 | ))} 230 | {project.tags.length > 3 && ( 231 | 232 | +{project.tags.length - 3} 233 | 234 | )} 235 |
    236 |
    237 |
    238 | {project.stars && ( 239 | 240 | 241 | {formatStars(project.stars)} 242 | 243 | )} 244 |
    245 |
    246 | {project.github && ( 247 | 252 | )} 253 | 258 |
    259 |
    260 |
    261 |
    262 | ))} 263 |
    264 | )} 265 | 266 | {/* 项目列表 - 列表视图 */} 267 | {viewMode === "list" && ( 268 |
    269 | {filteredProjects.map((project) => ( 270 | 271 |
    272 |
    273 | {project.name.charAt(0)} 274 |
    275 |
    276 |
    277 |

    {project.name}

    278 | {project.featured && ( 279 | 280 | 推荐 281 | 282 | )} 283 | 284 | {typeLabels[project.type]} 285 | 286 | 287 | {projectCategories.find((c) => c.id === project.category)?.name} 288 | 289 |
    290 |

    {project.description}

    291 |
    292 | {project.tags.slice(0, 4).map((tag) => ( 293 | 294 | {tag} 295 | 296 | ))} 297 |
    298 |
    299 |
    300 | {project.stars && ( 301 | 302 | 303 | {formatStars(project.stars)} 304 | 305 | )} 306 | {project.github && ( 307 | 313 | )} 314 | 320 |
    321 |
    322 |
    323 | ))} 324 |
    325 | )} 326 | 327 | {/* 空状态 */} 328 | {filteredProjects.length === 0 && ( 329 |
    330 |

    没有找到符合条件的项目

    331 | 334 |
    335 | )} 336 |
    337 |
    338 | ) 339 | } 340 | -------------------------------------------------------------------------------- /components/sections/tools-section.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState } from "react" 4 | import { doraMetrics, platformMaturityQuestions } from "@/data/tools" 5 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 6 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" 7 | import { Button } from "@/components/ui/button" 8 | import { Input } from "@/components/ui/input" 9 | import { Label } from "@/components/ui/label" 10 | import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" 11 | import { Progress } from "@/components/ui/progress" 12 | import { Badge } from "@/components/ui/badge" 13 | import { BarChart3, Calculator, ClipboardCheck, ArrowRight, RotateCcw } from "lucide-react" 14 | 15 | interface ToolsSectionProps { 16 | selectedTool: string | null 17 | onSelectTool: (id: string | null) => void 18 | } 19 | 20 | export function ToolsSection({ selectedTool, onSelectTool }: ToolsSectionProps) { 21 | const [activeTool, setActiveTool] = useState(selectedTool || "dora-metrics") 22 | 23 | // DORA Calculator State 24 | const [doraValues, setDoraValues] = useState({ 25 | deploymentFrequency: "", 26 | leadTime: "", 27 | changeFailureRate: "", 28 | mttr: "", 29 | }) 30 | 31 | // Platform Maturity Assessment State 32 | const [maturityAnswers, setMaturityAnswers] = useState>({}) 33 | const [showMaturityResult, setShowMaturityResult] = useState(false) 34 | 35 | const calculateMaturityScore = () => { 36 | const answered = Object.keys(maturityAnswers).length 37 | if (answered === 0) return 0 38 | const total = Object.values(maturityAnswers).reduce((a, b) => a + b, 0) 39 | return (total / (answered * 4)) * 100 40 | } 41 | 42 | const getMaturityLevel = (score: number) => { 43 | if (score >= 90) return { level: "创新级", color: "text-green-400" } 44 | if (score >= 70) return { level: "优化级", color: "text-blue-400" } 45 | if (score >= 50) return { level: "标准化", color: "text-yellow-400" } 46 | if (score >= 30) return { level: "基础级", color: "text-orange-400" } 47 | return { level: "临时级", color: "text-red-400" } 48 | } 49 | 50 | const getDoraLevel = (metric: string, value: string) => { 51 | const numValue = Number.parseFloat(value) 52 | if (isNaN(numValue)) return null 53 | 54 | switch (metric) { 55 | case "deploymentFrequency": 56 | if (numValue >= 7) return "精英" 57 | if (numValue >= 1) return "高" 58 | if (numValue >= 0.25) return "中" 59 | return "低" 60 | case "leadTime": 61 | if (numValue <= 1) return "精英" 62 | if (numValue <= 7) return "高" 63 | if (numValue <= 30) return "中" 64 | return "低" 65 | case "changeFailureRate": 66 | if (numValue <= 15) return "精英" 67 | if (numValue <= 30) return "高/中" 68 | return "低" 69 | case "mttr": 70 | if (numValue <= 1) return "精英" 71 | if (numValue <= 24) return "高" 72 | if (numValue <= 168) return "中" 73 | return "低" 74 | default: 75 | return null 76 | } 77 | } 78 | 79 | const getLevelColor = (level: string | null) => { 80 | switch (level) { 81 | case "精英": 82 | return "text-green-400" 83 | case "高": 84 | return "text-blue-400" 85 | case "中": 86 | return "text-yellow-400" 87 | case "高/中": 88 | return "text-yellow-400" 89 | case "低": 90 | return "text-red-400" 91 | default: 92 | return "text-muted-foreground" 93 | } 94 | } 95 | 96 | return ( 97 |
    98 |
    99 |
    100 |

    度量工具

    101 |

    102 | 使用专业的度量和测算工具,评估你的平台工程成熟度和效能指标。 103 |

    104 |
    105 | 106 | 107 | 108 | 109 | 110 | DORA 指标 111 | 112 | 113 | 114 | 成熟度评估 115 | 116 | 117 | 118 | ROI 计算器 119 | 120 | 121 | 122 | {/* DORA Metrics Calculator */} 123 | 124 |
    125 | 126 | 127 | DORA 指标计算器 128 | 输入你的团队数据,评估软件交付效能 129 | 130 | 131 |
    132 | 133 | setDoraValues({ ...doraValues, deploymentFrequency: e.target.value })} 139 | /> 140 | {doraValues.deploymentFrequency && ( 141 |

    144 | 级别: {getDoraLevel("deploymentFrequency", doraValues.deploymentFrequency)} 145 |

    146 | )} 147 |
    148 | 149 |
    150 | 151 | setDoraValues({ ...doraValues, leadTime: e.target.value })} 157 | /> 158 | {doraValues.leadTime && ( 159 |

    160 | 级别: {getDoraLevel("leadTime", doraValues.leadTime)} 161 |

    162 | )} 163 |
    164 | 165 |
    166 | 167 | setDoraValues({ ...doraValues, changeFailureRate: e.target.value })} 173 | /> 174 | {doraValues.changeFailureRate && ( 175 |

    178 | 级别: {getDoraLevel("changeFailureRate", doraValues.changeFailureRate)} 179 |

    180 | )} 181 |
    182 | 183 |
    184 | 185 | setDoraValues({ ...doraValues, mttr: e.target.value })} 191 | /> 192 | {doraValues.mttr && ( 193 |

    194 | 级别: {getDoraLevel("mttr", doraValues.mttr)} 195 |

    196 | )} 197 |
    198 | 199 | 209 |
    210 |
    211 | 212 | 213 | 214 | 指标说明 215 | 了解每个 DORA 指标的含义和基准 216 | 217 | 218 | {doraMetrics.map((metric) => ( 219 |
    220 |

    {metric.name}

    221 |

    {metric.description}

    222 |

    223 | 基准: {metric.benchmark} 224 |

    225 |
    226 | ))} 227 |
    228 |
    229 |
    230 |
    231 | 232 | {/* Platform Maturity Assessment */} 233 | 234 |
    235 |
    236 | 237 | 238 | 平台成熟度评估 239 | 回答以下问题,评估你的平台工程成熟度 240 | 241 | 242 | {platformMaturityQuestions.map((q, index) => ( 243 |
    244 |
    245 | 246 | {index + 1} 247 | 248 |
    249 |

    {q.question}

    250 | 251 | {q.category} 252 | 253 |
    254 |
    255 | { 258 | setMaturityAnswers({ ...maturityAnswers, [q.id]: Number.parseInt(value) }) 259 | setShowMaturityResult(false) 260 | }} 261 | className="ml-9 space-y-2" 262 | > 263 | {q.options.map((option) => ( 264 |
    268 | 269 | 275 |
    276 | ))} 277 |
    278 |
    279 | ))} 280 | 281 |
    282 | 289 | 300 |
    301 |
    302 |
    303 |
    304 | 305 |
    306 | 307 | 308 | 评估结果 309 | 310 | 311 | {showMaturityResult ? ( 312 |
    313 |
    314 |

    315 | {Math.round(calculateMaturityScore())}% 316 |

    317 |

    318 | {getMaturityLevel(calculateMaturityScore()).level} 319 |

    320 |
    321 | 322 |

    323 | 你的平台工程成熟度处于 {getMaturityLevel(calculateMaturityScore()).level} 阶段。 324 | 继续关注自动化、自助服务和开发者体验,持续提升平台能力。 325 |

    326 |
    327 | ) : ( 328 |
    329 |

    完成所有问题后查看评估结果

    330 |

    331 | 已完成 {Object.keys(maturityAnswers).length} / {platformMaturityQuestions.length} 332 |

    333 | 337 |
    338 | )} 339 |
    340 |
    341 |
    342 |
    343 |
    344 | 345 | {/* Platform ROI Calculator */} 346 | 347 | 348 | 349 |
    350 |
    351 |
    352 | ) 353 | } 354 | 355 | function ROICalculator() { 356 | const [values, setValues] = useState({ 357 | developerCount: "", 358 | avgSalary: "", 359 | timeSavedPerWeek: "", 360 | platformCost: "", 361 | }) 362 | 363 | const calculateROI = () => { 364 | const devCount = Number.parseFloat(values.developerCount) || 0 365 | const salary = Number.parseFloat(values.avgSalary) || 0 366 | const timeSaved = Number.parseFloat(values.timeSavedPerWeek) || 0 367 | const cost = Number.parseFloat(values.platformCost) || 0 368 | 369 | const hourlyRate = salary / 52 / 40 370 | const annualSavings = devCount * timeSaved * 52 * hourlyRate 371 | const roi = cost > 0 ? ((annualSavings - cost) / cost) * 100 : 0 372 | 373 | return { 374 | annualSavings: annualSavings.toFixed(0), 375 | roi: roi.toFixed(0), 376 | paybackMonths: cost > 0 ? ((cost / annualSavings) * 12).toFixed(1) : 0, 377 | } 378 | } 379 | 380 | const results = calculateROI() 381 | 382 | return ( 383 |
    384 | 385 | 386 | 平台 ROI 计算器 387 | 估算平台工程投资的回报率 388 | 389 | 390 |
    391 | 392 | setValues({ ...values, developerCount: e.target.value })} 398 | /> 399 |
    400 | 401 |
    402 | 403 | setValues({ ...values, avgSalary: e.target.value })} 409 | /> 410 |
    411 | 412 |
    413 | 414 | setValues({ ...values, timeSavedPerWeek: e.target.value })} 420 | /> 421 |
    422 | 423 |
    424 | 425 | setValues({ ...values, platformCost: e.target.value })} 431 | /> 432 |
    433 | 434 | 442 |
    443 |
    444 | 445 | 446 | 447 | 计算结果 448 | 基于你的输入估算的投资回报 449 | 450 | 451 |
    452 |

    年度节省成本

    453 |

    454 | ¥{Number.parseInt(results.annualSavings).toLocaleString()} 455 |

    456 |
    457 | 458 |
    459 |

    投资回报率 (ROI)

    460 |

    0 ? "text-green-400" : "text-red-400"}`} 462 | > 463 | {results.roi}% 464 |

    465 |
    466 | 467 |
    468 |

    投资回收期

    469 |

    {results.paybackMonths} 个月

    470 |
    471 | 472 |

    473 | * 此计算仅为估算,实际收益可能因多种因素而异。建议结合实际情况进行更详细的评估。 474 |

    475 |
    476 |
    477 |
    478 | ) 479 | } 480 | --------------------------------------------------------------------------------