├── app ├── icon.png ├── apple-icon.png ├── articles │ ├── loading.tsx │ ├── [id] │ │ └── page.tsx │ └── page.tsx ├── cases │ ├── loading.tsx │ ├── [id] │ │ └── page.tsx │ └── page.tsx ├── layout.tsx ├── forgot-password │ └── page.tsx ├── login │ └── page.tsx ├── globals.css ├── register │ └── page.tsx ├── about │ └── page.tsx ├── page.tsx ├── features │ └── page.tsx └── join │ └── page.tsx ├── public ├── apple-icon.png ├── v0zh-logo.png ├── placeholder.jpg ├── diverse-avatars.png ├── icon-dark-32x32.png ├── icon-light-32x32.png ├── placeholder-logo.png ├── placeholder-user.jpg ├── task-management-kanban.png ├── blog-management-dashboard.jpg ├── modern-ecommerce-homepage.jpg ├── online-learning-platform.png ├── data-visualization-dashboard.png ├── saas-landing-page-with-pricing.jpg ├── icon.svg ├── placeholder-logo.svg └── placeholder.svg ├── postcss.config.mjs ├── lib ├── utils.ts ├── markdown.ts └── mock-data.ts ├── next.config.mjs ├── components ├── theme-provider.tsx ├── ui │ ├── label.tsx │ ├── separator.tsx │ ├── input.tsx │ ├── avatar.tsx │ ├── checkbox.tsx │ ├── badge.tsx │ ├── button.tsx │ └── card.tsx ├── theme-toggle.tsx ├── footer.tsx └── header.tsx ├── .gitignore ├── components.json ├── tsconfig.json ├── data ├── articles │ ├── item-20250115.md │ └── item-20250114.md └── cases │ ├── item-20250114.md │ └── item-20250115.md ├── README.md ├── package.json ├── CONTRIBUTING.md └── styles └── globals.css /app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/app/icon.png -------------------------------------------------------------------------------- /app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/app/apple-icon.png -------------------------------------------------------------------------------- /app/articles/loading.tsx: -------------------------------------------------------------------------------- 1 | export default function Loading() { 2 | return null 3 | } 4 | -------------------------------------------------------------------------------- /app/cases/loading.tsx: -------------------------------------------------------------------------------- 1 | export default function Loading() { 2 | return null 3 | } 4 | -------------------------------------------------------------------------------- /public/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/apple-icon.png -------------------------------------------------------------------------------- /public/v0zh-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/v0zh-logo.png -------------------------------------------------------------------------------- /public/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/placeholder.jpg -------------------------------------------------------------------------------- /public/diverse-avatars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/diverse-avatars.png -------------------------------------------------------------------------------- /public/icon-dark-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/icon-dark-32x32.png -------------------------------------------------------------------------------- /public/icon-light-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/icon-light-32x32.png -------------------------------------------------------------------------------- /public/placeholder-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/placeholder-logo.png -------------------------------------------------------------------------------- /public/placeholder-user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/placeholder-user.jpg -------------------------------------------------------------------------------- /public/task-management-kanban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/task-management-kanban.png -------------------------------------------------------------------------------- /public/blog-management-dashboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/blog-management-dashboard.jpg -------------------------------------------------------------------------------- /public/modern-ecommerce-homepage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/modern-ecommerce-homepage.jpg -------------------------------------------------------------------------------- /public/online-learning-platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/online-learning-platform.png -------------------------------------------------------------------------------- /public/data-visualization-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/data-visualization-dashboard.png -------------------------------------------------------------------------------- /public/saas-landing-page-with-pricing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0zh/main/public/saas-landing-page-with-pricing.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 | eslint: { 4 | ignoreDuringBuilds: true, 5 | }, 6 | typescript: { 7 | ignoreBuildErrors: true, 8 | }, 9 | images: { 10 | unoptimized: true, 11 | }, 12 | } 13 | 14 | export default nextConfig 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # next.js 7 | /.next/ 8 | /out/ 9 | 10 | # production 11 | /build 12 | 13 | # debug 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | .pnpm-debug.log* 18 | 19 | # env files 20 | .env* 21 | 22 | # vercel 23 | .vercel 24 | 25 | # typescript 26 | *.tsbuildinfo 27 | next-env.d.ts 28 | -------------------------------------------------------------------------------- /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/separator.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as SeparatorPrimitive from '@radix-ui/react-separator' 5 | 6 | import { cn } from '@/lib/utils' 7 | 8 | function Separator({ 9 | className, 10 | orientation = 'horizontal', 11 | decorative = true, 12 | ...props 13 | }: React.ComponentProps) { 14 | return ( 15 | 25 | ) 26 | } 27 | 28 | export { Separator } 29 | -------------------------------------------------------------------------------- /data/articles/item-20250115.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "v0.app 入门指南:从零开始构建你的第一个应用" 3 | description: "本文将带你了解 v0.app 的基本概念和使用方法,帮助你快速上手这个强大的 AI 开发工具。" 4 | author: "张三" 5 | authorAvatar: "/diverse-avatars.png" 6 | category: "入门教程" 7 | tags: ["入门", "教程", "基础"] 8 | publishedAt: "2025-01-15" 9 | readTime: "5分钟" 10 | views: 1234 11 | likes: 89 12 | --- 13 | 14 | # v0.app 入门指南 15 | 16 | v0.app 是 Vercel 推出的革命性 AI 开发工具,它能够通过自然语言描述自动生成高质量的 React 和 Next.js 代码。 17 | 18 | ## 什么是 v0.app 19 | 20 | v0.app 结合了大语言模型的智能和现代前端技术栈,让开发变得更快、更简单。你只需要用自然语言描述你想要的功能,v0 就能为你生成相应的代码。 21 | 22 | ## 快速开始 23 | 24 | 1. 访问 v0.dev 并登录你的账号 25 | 2. 在对话框中描述你想要创建的内容 26 | 3. v0 会为你生成代码并提供实时预览 27 | 4. 你可以继续对话来调整和优化代码 28 | 29 | ## 最佳实践 30 | 31 | - 清晰描述你的需求 32 | - 提供具体的设计要求 33 | - 利用迭代对话来完善功能 34 | - 学习生成的代码以提升技能 35 | 36 | ## 总结 37 | 38 | v0.app 让前端开发变得更加高效和有趣。无论你是初学者还是经验丰富的开发者,都能从中受益。 39 | 40 | 开始你的 v0 之旅吧! 41 | -------------------------------------------------------------------------------- /data/articles/item-20250114.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "使用 v0.app 快速构建响应式落地页" 3 | description: "学习如何利用 v0.app 的 AI 能力,在几分钟内创建一个精美的响应式落地页。" 4 | author: "李四" 5 | authorAvatar: "/diverse-avatars.png" 6 | category: "实战案例" 7 | tags: ["落地页", "响应式", "设计"] 8 | publishedAt: "2025-01-14" 9 | readTime: "8分钟" 10 | views: 987 11 | likes: 67 12 | --- 13 | 14 | # 使用 v0.app 快速构建响应式落地页 15 | 16 | 落地页是产品营销的重要工具,而 v0.app 让创建落地页变得前所未有的简单。 17 | 18 | ## 设计要点 19 | 20 | 一个好的落地页需要: 21 | - 清晰的价值主张 22 | - 吸引人的视觉设计 23 | - 明确的行动号召 24 | - 响应式布局 25 | 26 | ## 使用 v0 创建落地页 27 | 28 | 只需告诉 v0 你的产品特点和目标受众,它就能为你生成完整的落地页代码。 29 | 30 | ### 步骤一:描述你的产品 31 | 32 | 清晰地描述你的产品特点、目标用户和核心价值。 33 | 34 | ### 步骤二:指定设计风格 35 | 36 | 告诉 v0 你想要的视觉风格,比如现代、简约、专业等。 37 | 38 | ### 步骤三:迭代优化 39 | 40 | 根据生成的结果,继续对话来调整细节。 41 | 42 | ## 优化技巧 43 | 44 | - 使用高质量的图片 45 | - 保持简洁的文案 46 | - 优化加载速度 47 | - A/B 测试不同版本 48 | 49 | 让 v0 帮你快速实现想法! 50 | -------------------------------------------------------------------------------- /components/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { Moon, Sun } from "lucide-react" 5 | import { useTheme } from "next-themes" 6 | import { Button } from "@/components/ui/button" 7 | 8 | export function ThemeToggle() { 9 | const { theme, setTheme } = useTheme() 10 | const [mounted, setMounted] = React.useState(false) 11 | 12 | // Avoid hydration mismatch 13 | React.useEffect(() => { 14 | setMounted(true) 15 | }, []) 16 | 17 | if (!mounted) { 18 | return ( 19 | 22 | ) 23 | } 24 | 25 | return ( 26 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /data/cases/item-20250114.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SaaS 产品落地页" 3 | description: "为 SaaS 产品设计的高转化率落地页,包含产品介绍、定价方案和用户注册流程。" 4 | thumbnail: "/saas-landing-page-with-pricing.jpg" 5 | category: "营销" 6 | tags: ["SaaS", "落地页", "转化"] 7 | author: "李四" 8 | authorAvatar: "/diverse-avatars.png" 9 | publishedAt: "2025-01-14" 10 | likes: 189 11 | views: 2789 12 | demoUrl: "https://demo.example.com" 13 | difficulty: "初级" 14 | techStack: ["Next.js", "Tailwind CSS", "Framer Motion"] 15 | --- 16 | 17 | # SaaS 产品落地页 18 | 19 | 一个专为 SaaS 产品设计的高转化率落地页案例。 20 | 21 | ## 设计理念 22 | 23 | - 清晰的价值主张 24 | - 吸引人的视觉设计 25 | - 明确的行动号召 26 | - 社会证明元素 27 | 28 | ## 功能特点 29 | 30 | 包含产品特性展示、定价表格、客户评价和注册表单等核心模块。 31 | 32 | ### 页面结构 33 | 34 | 1. **英雄区**:突出产品核心价值 35 | 2. **功能展示**:详细介绍产品特性 36 | 3. **定价方案**:清晰的价格对比 37 | 4. **客户评价**:建立信任 38 | 5. **注册表单**:简化注册流程 39 | 40 | ## 转化优化 41 | 42 | 通过 A/B 测试和用户反馈持续优化,提高转化率。 43 | 44 | ### 优化策略 45 | 46 | - 简化注册流程 47 | - 突出免费试用 48 | - 添加社会证明 49 | - 优化加载速度 50 | 51 | ## 技术实现 52 | 53 | 使用 Next.js 和 Tailwind CSS 构建,Framer Motion 添加动画效果。 54 | 55 | ## 成果 56 | 57 | 上线后转化率提升了 40%,用户注册量显著增加。 58 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type React from "react" 2 | import type { Metadata } from "next" 3 | import { Inter, JetBrains_Mono } from "next/font/google" 4 | import "./globals.css" 5 | import { Header } from "@/components/header" 6 | import { Footer } from "@/components/footer" 7 | import { ThemeProvider } from "@/components/theme-provider" 8 | 9 | const inter = Inter({ subsets: ["latin"] }) 10 | const jetbrainsMono = JetBrains_Mono({ subsets: ["latin"] }) 11 | 12 | export const metadata: Metadata = { 13 | title: "v0.app 中文社区 - 学习资源与案例分享", 14 | description: "v0.app 官方中文社区,提供功能介绍、文章分享、案例展示和社区协作平台", 15 | generator: "v0.app", 16 | } 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: { 21 | children: React.ReactNode 22 | }) { 23 | return ( 24 | 25 | 26 | 27 |
28 |
29 |
{children}
30 |
31 |
32 |
33 | 34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /data/cases/item-20250115.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "电商平台首页设计" 3 | description: "使用 v0.app 快速实现的现代化电商平台首页,包含商品展示、分类导航和购物车功能。" 4 | thumbnail: "/modern-ecommerce-homepage.jpg" 5 | category: "电商" 6 | tags: ["电商", "首页", "响应式"] 7 | author: "张三" 8 | authorAvatar: "/diverse-avatars.png" 9 | publishedAt: "2025-01-15" 10 | likes: 234 11 | views: 3456 12 | demoUrl: "https://demo.example.com" 13 | githubUrl: "https://github.com/example/ecommerce" 14 | difficulty: "中级" 15 | techStack: ["Next.js", "Tailwind CSS", "shadcn/ui", "Stripe"] 16 | --- 17 | 18 | # 电商平台首页设计 19 | 20 | 这是一个使用 v0.app 构建的现代化电商平台首页案例。 21 | 22 | ## 项目概述 23 | 24 | 本项目展示了如何使用 v0.app 快速构建一个功能完整的电商首页,包括: 25 | - 响应式导航栏 26 | - 商品分类展示 27 | - 特色商品轮播 28 | - 购物车功能 29 | - 用户评价展示 30 | 31 | ## 技术实现 32 | 33 | 使用 Next.js 14 App Router 和 Tailwind CSS 实现,集成了 Stripe 支付功能。 34 | 35 | ### 核心功能 36 | 37 | 1. **商品展示**:使用网格布局展示商品,支持筛选和排序 38 | 2. **购物车**:实时更新购物车状态,支持数量调整 39 | 3. **支付集成**:集成 Stripe 实现安全的在线支付 40 | 4. **响应式设计**:完美适配各种设备尺寸 41 | 42 | ## 开发过程 43 | 44 | 1. 使用 v0 生成基础布局 45 | 2. 添加商品数据结构 46 | 3. 实现购物车逻辑 47 | 4. 集成支付功能 48 | 5. 优化性能和 SEO 49 | 50 | ## 学习要点 51 | 52 | - 组件化设计思路 53 | - 状态管理最佳实践 54 | - 支付集成方案 55 | - 性能优化技巧 56 | 57 | ## 总结 58 | 59 | 通过 v0.app,我们在短时间内完成了一个功能完整的电商首页。这个案例展示了 AI 辅助开发的强大能力。 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # V0.app Chinese community 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-v0-app-chinese-community) 6 | [![Built with v0](https://img.shields.io/badge/Built%20with-v0.app-black?style=for-the-badge)](https://v0.app/chat/projects/nepy6tBB0EB) 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/v0zh](https://vercel.com/pecommunity/v0zh)** 18 | 19 | ## Build your app 20 | 21 | Continue building your app on: 22 | 23 | **[https://v0.app/chat/projects/nepy6tBB0EB](https://v0.app/chat/projects/nepy6tBB0EB)** 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 31 | -------------------------------------------------------------------------------- /components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as AvatarPrimitive from '@radix-ui/react-avatar' 5 | 6 | import { cn } from '@/lib/utils' 7 | 8 | function Avatar({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | function AvatarImage({ 25 | className, 26 | ...props 27 | }: React.ComponentProps) { 28 | return ( 29 | 34 | ) 35 | } 36 | 37 | function AvatarFallback({ 38 | className, 39 | ...props 40 | }: React.ComponentProps) { 41 | return ( 42 | 50 | ) 51 | } 52 | 53 | export { Avatar, AvatarImage, AvatarFallback } 54 | -------------------------------------------------------------------------------- /components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox' 5 | import { CheckIcon } from 'lucide-react' 6 | 7 | import { cn } from '@/lib/utils' 8 | 9 | function Checkbox({ 10 | className, 11 | ...props 12 | }: React.ComponentProps) { 13 | return ( 14 | 22 | 26 | 27 | 28 | 29 | ) 30 | } 31 | 32 | export { Checkbox } 33 | -------------------------------------------------------------------------------- /public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | "fs": "0.0.1-security", 48 | "gray-matter": "4.0.3", 49 | "input-otp": "1.4.1", 50 | "lucide-react": "^0.454.0", 51 | "next": "16.0.10", 52 | "next-themes": "^0.4.6", 53 | "path": "0.12.7", 54 | "react": "19.2.0", 55 | "react-day-picker": "9.8.0", 56 | "react-dom": "19.2.0", 57 | "react-hook-form": "^7.60.0", 58 | "react-markdown": "10.1.0", 59 | "react-resizable-panels": "^2.1.7", 60 | "recharts": "2.15.4", 61 | "sonner": "^1.7.4", 62 | "tailwind-merge": "^2.5.5", 63 | "tailwindcss-animate": "^1.0.7", 64 | "vaul": "^0.9.9", 65 | "zod": "3.25.76" 66 | }, 67 | "devDependencies": { 68 | "@tailwindcss/postcss": "^4.1.9", 69 | "@types/node": "^22", 70 | "@types/react": "^19", 71 | "@types/react-dom": "^19", 72 | "postcss": "^8.5", 73 | "tailwindcss": "^4.1.9", 74 | "tw-animate-css": "1.3.3", 75 | "typescript": "^5" 76 | } 77 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 贡献指南 2 | 3 | 感谢你对 v0.app 中文社区的关注!我们欢迎所有形式的贡献,包括但不限于: 4 | 5 | - 分享文章 6 | - 提交案例 7 | - 改进文档 8 | - 报告问题 9 | - 提出建议 10 | 11 | ## 如何贡献文章 12 | 13 | ### 1. Fork 本仓库 14 | 15 | 点击右上角的 "Fork" 按钮,将仓库 fork 到你的账号下。 16 | 17 | ### 2. 创建文章文件 18 | 19 | 在 `data/articles/` 目录下创建一个新的 Markdown 文件,文件名格式为 `item-YYYYMMDD.md`(例如:`item-20250115.md`)。 20 | 21 | ### 3. 编写文章 22 | 23 | 文章需要包含 frontmatter 元数据和正文内容: 24 | 25 | \`\`\`markdown 26 | --- 27 | title: "你的文章标题" 28 | description: "文章简短描述,不超过 200 字" 29 | author: "你的名字" 30 | authorAvatar: "/path/to/avatar.png" # 可选 31 | category: "入门教程" # 可选:入门教程、实战案例、进阶技巧、最佳实践、工作流程 32 | tags: ["标签1", "标签2", "标签3"] 33 | publishedAt: "2025-01-15" # 格式:YYYY-MM-DD 34 | readTime: "5分钟" 35 | views: 0 # 可选,默认为 0 36 | likes: 0 # 可选,默认为 0 37 | --- 38 | 39 | # 文章标题 40 | 41 | 这里是文章正文内容,使用 Markdown 格式编写。 42 | 43 | ## 二级标题 44 | 45 | 文章内容... 46 | 47 | ### 三级标题 48 | 49 | 更多内容... 50 | \`\`\` 51 | 52 | ### 4. 提交 Pull Request 53 | 54 | 1. 提交你的更改:`git commit -m "添加文章:你的文章标题"` 55 | 2. 推送到你的 fork:`git push origin main` 56 | 3. 在 GitHub 上创建 Pull Request 57 | 58 | ## 如何贡献案例 59 | 60 | ### 1. 创建案例文件 61 | 62 | 在 `data/cases/` 目录下创建一个新的 Markdown 文件,文件名格式为 `item-YYYYMMDD.md`。 63 | 64 | ### 2. 编写案例 65 | 66 | 案例需要包含更详细的元数据: 67 | 68 | \`\`\`markdown 69 | --- 70 | title: "你的案例标题" 71 | description: "案例简短描述" 72 | thumbnail: "/path/to/thumbnail.jpg" # 案例缩略图 73 | category: "电商" # 可选:电商、营销、内容管理、生产力工具、教育、数据分析 74 | tags: ["标签1", "标签2"] 75 | author: "你的名字" 76 | authorAvatar: "/path/to/avatar.png" # 可选 77 | publishedAt: "2025-01-15" 78 | likes: 0 79 | views: 0 80 | demoUrl: "https://demo.example.com" # 可选,演示链接 81 | githubUrl: "https://github.com/username/repo" # 可选,代码仓库 82 | difficulty: "中级" # 初级、中级、高级 83 | techStack: ["Next.js", "Tailwind CSS", "Supabase"] # 使用的技术栈 84 | --- 85 | 86 | # 案例标题 87 | 88 | 案例详细介绍... 89 | 90 | ## 项目概述 91 | 92 | 项目背景和目标... 93 | 94 | ## 技术实现 95 | 96 | 技术细节... 97 | 98 | ## 学习要点 99 | 100 | 关键知识点... 101 | \`\`\` 102 | 103 | ### 3. 添加图片 104 | 105 | 如果你的案例包含图片,请将图片放在 `public/` 目录下,并在 Markdown 中使用相对路径引用。 106 | 107 | ### 4. 提交 Pull Request 108 | 109 | 按照上述文章提交流程提交你的案例。 110 | 111 | ## 内容规范 112 | 113 | ### 文章要求 114 | 115 | - 内容原创或已获得授权 116 | - 与 v0.app 相关 117 | - 语言清晰,逻辑连贯 118 | - 代码示例完整可运行 119 | - 排版规范,使用正确的 Markdown 语法 120 | 121 | ### 案例要求 122 | 123 | - 项目真实可用 124 | - 提供演示链接或代码仓库(至少一个) 125 | - 包含项目截图或演示视频 126 | - 技术栈信息完整 127 | - 详细说明实现过程和关键技术点 128 | 129 | ## 审核流程 130 | 131 | 1. 提交 PR 后,维护者会在 3 个工作日内审核 132 | 2. 如有问题,会在 PR 中留言反馈 133 | 3. 修改完成后,维护者会合并 PR 134 | 4. 合并后,你的贡献会立即显示在网站上 135 | 136 | ## 行为准则 137 | 138 | - 尊重他人,友善交流 139 | - 不发布违法、违规内容 140 | - 不抄袭他人作品 141 | - 保护个人隐私 142 | - 遵守开源协议 143 | 144 | ## 获得帮助 145 | 146 | 如果你在贡献过程中遇到问题,可以: 147 | 148 | - 在 GitHub Issues 中提问 149 | - 加入我们的社区讨论群 150 | - 发送邮件至:community@v0zh.com 151 | 152 | 再次感谢你的贡献!让我们一起建设更好的 v0.app 中文社区! 153 | -------------------------------------------------------------------------------- /public/placeholder-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/placeholder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | import Image from "next/image" 3 | import { Github, Twitter } from "lucide-react" 4 | 5 | export function Footer() { 6 | return ( 7 |
8 |
9 |
10 |
11 |
12 |
13 | v0zh Logo 14 |
15 | 中文社区 16 |
17 |

18 | v0.app 中文社区,为中文用户提供学习资源、案例分享和技术交流平台。 19 |

20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 | 30 |
31 |

资源

32 |
    33 |
  • 34 | 35 | 功能介绍 36 | 37 |
  • 38 |
  • 39 | 40 | 文章分享 41 | 42 |
  • 43 |
  • 44 | 45 | 案例展示 46 | 47 |
  • 48 |
49 |
50 | 51 |
52 |

社区

53 |
    54 |
  • 55 | 56 | 加入社区 57 | 58 |
  • 59 |
  • 60 | 61 | 关于我们 62 | 63 |
  • 64 |
  • 65 | 66 | v0.app 官网 67 | 68 |
  • 69 |
70 |
71 |
72 | 73 |
74 |

75 | © 2025 v0.app 中文社区. 本站非 Vercel 官方网站,由社区志愿者维护。 76 |

77 |
78 |
79 |
80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /components/header.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import Link from "next/link" 4 | import Image from "next/image" 5 | import { Button } from "@/components/ui/button" 6 | import { Menu, X } from "lucide-react" 7 | import { useState } from "react" 8 | import { ThemeToggle } from "@/components/theme-toggle" 9 | 10 | export function Header() { 11 | const [mobileMenuOpen, setMobileMenuOpen] = useState(false) 12 | 13 | const navigation = [ 14 | { name: "首页", href: "/" }, 15 | { name: "功能介绍", href: "/features" }, 16 | { name: "文章分享", href: "/articles" }, 17 | { name: "案例展示", href: "/cases" }, 18 | { name: "加入社区", href: "/join" }, 19 | { name: "关于", href: "/about" }, 20 | ] 21 | 22 | return ( 23 |
24 | 62 | 63 | {mobileMenuOpen && ( 64 |
65 |
66 | {navigation.map((item) => ( 67 | setMobileMenuOpen(false)} 72 | > 73 | {item.name} 74 | 75 | ))} 76 |
77 |
78 | 79 |
80 | 83 | 86 |
87 |
88 |
89 | )} 90 |
91 | ) 92 | } 93 | -------------------------------------------------------------------------------- /app/forgot-password/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import type React from "react" 4 | 5 | import { useState } from "react" 6 | import Link from "next/link" 7 | import { Button } from "@/components/ui/button" 8 | import { Input } from "@/components/ui/input" 9 | import { Label } from "@/components/ui/label" 10 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 11 | import { Mail, ArrowLeft } from "lucide-react" 12 | 13 | export default function ForgotPasswordPage() { 14 | const [email, setEmail] = useState("") 15 | const [isLoading, setIsLoading] = useState(false) 16 | const [isSubmitted, setIsSubmitted] = useState(false) 17 | 18 | const handleSubmit = async (e: React.FormEvent) => { 19 | e.preventDefault() 20 | setIsLoading(true) 21 | // TODO: Implement password reset with Supabase 22 | setTimeout(() => { 23 | setIsLoading(false) 24 | setIsSubmitted(true) 25 | }, 1000) 26 | } 27 | 28 | return ( 29 |
30 | 31 | 32 |
33 | v0 34 |
35 | 重置密码 36 | 37 | {isSubmitted ? "我们已发送重置链接到你的邮箱" : "输入你的邮箱地址,我们将发送重置密码的链接"} 38 | 39 |
40 | 41 | {!isSubmitted ? ( 42 | <> 43 |
44 |
45 | 46 |
47 | 48 | setEmail(e.target.value)} 54 | className="pl-10" 55 | required 56 | /> 57 |
58 |
59 | 60 | 63 |
64 | 65 |
66 | 72 |
73 | 74 | ) : ( 75 |
76 |

77 | 请检查你的邮箱 {email},点击邮件中的链接重置密码。 78 |

79 |

如果没有收到邮件,请检查垃圾邮件文件夹。

80 | 83 |
84 | )} 85 |
86 |
87 |
88 | ) 89 | } 90 | -------------------------------------------------------------------------------- /lib/markdown.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs" 2 | import path from "path" 3 | import matter from "gray-matter" 4 | 5 | const articlesDirectory = path.join(process.cwd(), "data/articles") 6 | const casesDirectory = path.join(process.cwd(), "data/cases") 7 | 8 | export interface ArticleMetadata { 9 | title: string 10 | description: string 11 | author: string 12 | authorAvatar?: string 13 | category: string 14 | tags: string[] 15 | publishedAt: string 16 | readTime: string 17 | views?: number 18 | likes?: number 19 | } 20 | 21 | export interface Article extends ArticleMetadata { 22 | id: string 23 | content: string 24 | } 25 | 26 | export interface CaseMetadata { 27 | title: string 28 | description: string 29 | thumbnail: string 30 | category: string 31 | tags: string[] 32 | author: string 33 | authorAvatar?: string 34 | publishedAt: string 35 | likes?: number 36 | views?: number 37 | demoUrl?: string 38 | githubUrl?: string 39 | difficulty: "初级" | "中级" | "高级" 40 | techStack: string[] 41 | } 42 | 43 | export interface CaseStudy extends CaseMetadata { 44 | id: string 45 | content: string 46 | } 47 | 48 | // Get all articles 49 | export function getAllArticles(): Article[] { 50 | if (!fs.existsSync(articlesDirectory)) { 51 | return [] 52 | } 53 | 54 | const fileNames = fs.readdirSync(articlesDirectory) 55 | const articles = fileNames 56 | .filter((fileName) => fileName.endsWith(".md")) 57 | .map((fileName) => { 58 | const id = fileName.replace(/\.md$/, "") 59 | const fullPath = path.join(articlesDirectory, fileName) 60 | const fileContents = fs.readFileSync(fullPath, "utf8") 61 | const { data, content } = matter(fileContents) 62 | 63 | return { 64 | id, 65 | content, 66 | ...(data as ArticleMetadata), 67 | } 68 | }) 69 | .sort((a, b) => (a.publishedAt > b.publishedAt ? -1 : 1)) 70 | 71 | return articles 72 | } 73 | 74 | // Get article by ID 75 | export function getArticleById(id: string): Article | null { 76 | try { 77 | const fullPath = path.join(articlesDirectory, `${id}.md`) 78 | const fileContents = fs.readFileSync(fullPath, "utf8") 79 | const { data, content } = matter(fileContents) 80 | 81 | return { 82 | id, 83 | content, 84 | ...(data as ArticleMetadata), 85 | } 86 | } catch { 87 | return null 88 | } 89 | } 90 | 91 | // Get all cases 92 | export function getAllCases(): CaseStudy[] { 93 | if (!fs.existsSync(casesDirectory)) { 94 | return [] 95 | } 96 | 97 | const fileNames = fs.readdirSync(casesDirectory) 98 | const cases = fileNames 99 | .filter((fileName) => fileName.endsWith(".md")) 100 | .map((fileName) => { 101 | const id = fileName.replace(/\.md$/, "") 102 | const fullPath = path.join(casesDirectory, fileName) 103 | const fileContents = fs.readFileSync(fullPath, "utf8") 104 | const { data, content } = matter(fileContents) 105 | 106 | return { 107 | id, 108 | content, 109 | ...(data as CaseMetadata), 110 | } 111 | }) 112 | .sort((a, b) => (a.publishedAt > b.publishedAt ? -1 : 1)) 113 | 114 | return cases 115 | } 116 | 117 | // Get case by ID 118 | export function getCaseById(id: string): CaseStudy | null { 119 | try { 120 | const fullPath = path.join(casesDirectory, `${id}.md`) 121 | const fileContents = fs.readFileSync(fullPath, "utf8") 122 | const { data, content } = matter(fileContents) 123 | 124 | return { 125 | id, 126 | content, 127 | ...(data as CaseMetadata), 128 | } 129 | } catch { 130 | return null 131 | } 132 | } 133 | 134 | // Get unique categories from articles 135 | export function getArticleCategories(): string[] { 136 | const articles = getAllArticles() 137 | const categories = new Set(articles.map((article) => article.category)) 138 | return ["全部", ...Array.from(categories)] 139 | } 140 | 141 | // Get unique categories from cases 142 | export function getCaseCategories(): string[] { 143 | const cases = getAllCases() 144 | const categories = new Set(cases.map((caseStudy) => caseStudy.category)) 145 | return ["全部", ...Array.from(categories)] 146 | } 147 | -------------------------------------------------------------------------------- /app/login/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import type React from "react" 4 | 5 | import { useState } from "react" 6 | import Link from "next/link" 7 | import { Button } from "@/components/ui/button" 8 | import { Input } from "@/components/ui/input" 9 | import { Label } from "@/components/ui/label" 10 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 11 | import { Separator } from "@/components/ui/separator" 12 | import { Chrome, Mail, Lock } from "lucide-react" 13 | 14 | export default function LoginPage() { 15 | const [email, setEmail] = useState("") 16 | const [password, setPassword] = useState("") 17 | const [isLoading, setIsLoading] = useState(false) 18 | 19 | const handleEmailLogin = async (e: React.FormEvent) => { 20 | e.preventDefault() 21 | setIsLoading(true) 22 | // TODO: Implement email login with Supabase 23 | setTimeout(() => { 24 | setIsLoading(false) 25 | alert("登录功能将在集成 Supabase 后启用") 26 | }, 1000) 27 | } 28 | 29 | const handleGoogleLogin = async () => { 30 | setIsLoading(true) 31 | // TODO: Implement Google OAuth with Supabase 32 | setTimeout(() => { 33 | setIsLoading(false) 34 | alert("Google 登录功能将在集成 Supabase 后启用") 35 | }, 1000) 36 | } 37 | 38 | return ( 39 |
40 | 41 | 42 |
43 | v0 44 |
45 | 欢迎回来 46 | 登录你的 v0.app 中文社区账号 47 |
48 | 49 | {/* Google Login */} 50 | 54 | 55 |
56 |
57 | 58 |
59 |
60 | 或使用邮箱登录 61 |
62 |
63 | 64 | {/* Email Login Form */} 65 |
66 |
67 | 68 |
69 | 70 | setEmail(e.target.value)} 76 | className="pl-10" 77 | required 78 | /> 79 |
80 |
81 | 82 |
83 |
84 | 85 | 86 | 忘记密码? 87 | 88 |
89 |
90 | 91 | setPassword(e.target.value)} 97 | className="pl-10" 98 | required 99 | /> 100 |
101 |
102 | 103 | 106 |
107 | 108 |
109 | 还没有账号?{" "} 110 | 111 | 立即注册 112 | 113 |
114 |
115 |
116 |
117 | ) 118 | } 119 | -------------------------------------------------------------------------------- /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 | :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 | /* optional: --font-sans, --font-serif, --font-mono if they are applied in the layout.tsx */ 79 | --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif; 80 | --font-mono: "JetBrains Mono", ui-monospace, monospace; 81 | 82 | /* Primary brand colors */ 83 | --color-primary: #3b82f6; 84 | --color-primary-foreground: #ffffff; 85 | 86 | /* Semantic design tokens */ 87 | --color-background: #ffffff; 88 | --color-foreground: #0f172a; 89 | --color-card: #ffffff; 90 | --color-card-foreground: #0f172a; 91 | --color-popover: #ffffff; 92 | --color-popover-foreground: #0f172a; 93 | --color-muted: #f1f5f9; 94 | --color-muted-foreground: #64748b; 95 | --color-accent: #8b5cf6; 96 | --color-accent-foreground: #ffffff; 97 | --color-destructive: #ef4444; 98 | --color-destructive-foreground: #ffffff; 99 | --color-border: #e2e8f0; 100 | --color-input: #e2e8f0; 101 | --color-ring: #3b82f6; 102 | 103 | --color-background: var(--background); 104 | --color-foreground: var(--foreground); 105 | --color-card: var(--card); 106 | --color-card-foreground: var(--card-foreground); 107 | --color-popover: var(--popover); 108 | --color-popover-foreground: var(--popover-foreground); 109 | --color-primary: var(--primary); 110 | --color-primary-foreground: var(--primary-foreground); 111 | --color-secondary: var(--secondary); 112 | --color-secondary-foreground: var(--secondary-foreground); 113 | --color-muted: var(--muted); 114 | --color-muted-foreground: var(--muted-foreground); 115 | --color-accent: var(--accent); 116 | --color-accent-foreground: var(--accent-foreground); 117 | --color-destructive: var(--destructive); 118 | --color-destructive-foreground: var(--destructive-foreground); 119 | --color-border: var(--border); 120 | --color-input: var(--input); 121 | --color-ring: var(--ring); 122 | --color-chart-1: var(--chart-1); 123 | --color-chart-2: var(--chart-2); 124 | --color-chart-3: var(--chart-3); 125 | --color-chart-4: var(--chart-4); 126 | --color-chart-5: var(--chart-5); 127 | --radius-sm: calc(var(--radius) - 4px); 128 | --radius-md: calc(var(--radius) - 2px); 129 | --radius-lg: var(--radius); 130 | --radius-xl: calc(var(--radius) + 4px); 131 | --color-sidebar: var(--sidebar); 132 | --color-sidebar-foreground: var(--sidebar-foreground); 133 | --color-sidebar-primary: var(--sidebar-primary); 134 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 135 | --color-sidebar-accent: var(--sidebar-accent); 136 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 137 | --color-sidebar-border: var(--sidebar-border); 138 | --color-sidebar-ring: var(--sidebar-ring); 139 | 140 | --radius: 0.5rem; 141 | } 142 | 143 | @media (prefers-color-scheme: dark) { 144 | @theme inline { 145 | --color-background: #0f172a; 146 | --color-foreground: #f1f5f9; 147 | --color-card: #1e293b; 148 | --color-card-foreground: #f1f5f9; 149 | --color-popover: #1e293b; 150 | --color-popover-foreground: #f1f5f9; 151 | --color-muted: #1e293b; 152 | --color-muted-foreground: #94a3b8; 153 | --color-accent: #8b5cf6; 154 | --color-accent-foreground: #ffffff; 155 | --color-border: #334155; 156 | --color-input: #334155; 157 | } 158 | } 159 | 160 | @layer base { 161 | * { 162 | @apply border-border outline-ring/50; 163 | } 164 | body { 165 | @apply bg-background text-foreground; 166 | } 167 | } 168 | 169 | body { 170 | font-family: var(--font-sans); 171 | background-color: var(--color-background); 172 | color: var(--color-foreground); 173 | } 174 | -------------------------------------------------------------------------------- /app/articles/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation" 2 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" 3 | import { Badge } from "@/components/ui/badge" 4 | import { Button } from "@/components/ui/button" 5 | import { Card, CardContent } from "@/components/ui/card" 6 | import { Separator } from "@/components/ui/separator" 7 | import { Clock, Eye, Heart, Share2, Bookmark, ArrowLeft } from "lucide-react" 8 | import Link from "next/link" 9 | import { getArticleById, getAllArticles } from "@/lib/markdown" 10 | import ReactMarkdown from "react-markdown" 11 | 12 | export default async function ArticleDetailPage({ params }: { params: Promise<{ id: string }> }) { 13 | const { id } = await params 14 | const article = getArticleById(id) 15 | 16 | if (!article) { 17 | notFound() 18 | } 19 | 20 | const allArticles = getAllArticles() 21 | const relatedArticles = allArticles.filter((a) => a.id !== id && a.category === article.category).slice(0, 2) 22 | 23 | return ( 24 |
25 | {/* Back Button */} 26 |
27 |
28 | 34 |
35 |
36 | 37 | {/* Article Header */} 38 |
39 |
40 |
41 | {article.category} 42 | {article.publishedAt} 43 |
44 | 45 |

46 | {article.title} 47 |

48 | 49 |

{article.description}

50 | 51 | {/* Author Info */} 52 |
53 |
54 | 55 | 56 | {article.author[0]} 57 | 58 |
59 |
{article.author}
60 |
61 |
62 | 63 | {article.readTime} 64 |
65 | {article.views !== undefined && ( 66 |
67 | 68 | {article.views} 69 |
70 | )} 71 |
72 |
73 |
74 | 75 |
76 | 79 | 82 | 85 |
86 |
87 | 88 | 89 | 90 | {/* Article Content */} 91 |
92 | {article.content} 93 |
94 | 95 | {/* Tags */} 96 |
97 | {article.tags.map((tag) => ( 98 | 99 | {tag} 100 | 101 | ))} 102 |
103 | 104 | 105 | 106 | {/* Engagement Stats */} 107 |
108 |
109 | 113 | 117 |
118 | {article.views !== undefined &&
{article.views} 次阅读
} 119 |
120 |
121 |
122 | 123 | {/* Related Articles */} 124 | {relatedArticles.length > 0 && ( 125 |
126 |
127 |

相关文章

128 |
129 | {relatedArticles.map((relatedArticle) => ( 130 | 131 | 132 | 133 | 134 | {relatedArticle.category} 135 | 136 |

{relatedArticle.title}

137 |

{relatedArticle.description}

138 |
139 |
140 | 141 | ))} 142 |
143 |
144 |
145 | )} 146 |
147 | ) 148 | } 149 | -------------------------------------------------------------------------------- /app/articles/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 3 | import { Badge } from "@/components/ui/badge" 4 | import { Button } from "@/components/ui/button" 5 | import { Input } from "@/components/ui/input" 6 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" 7 | import { Search, Clock, Eye, Heart, PenSquare } from "lucide-react" 8 | import { getAllArticles, getArticleCategories } from "@/lib/markdown" 9 | 10 | export default function ArticlesPage() { 11 | const articles = getAllArticles() 12 | const categories = getArticleCategories() 13 | 14 | return ( 15 |
16 | {/* Hero Section */} 17 |
18 |
19 |
20 |

文章分享

21 |

22 | 探索 v0.app 的使用技巧、最佳实践和实战经验 23 |

24 |
25 | 26 | {/* Search Bar */} 27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | {/* CTA Button */} 35 |
36 | 42 |
43 |
44 |
45 | 46 | {/* Categories */} 47 |
48 |
49 |
50 | {categories.map((category) => ( 51 | 54 | ))} 55 |
56 |
57 |
58 | 59 | {/* Articles Grid */} 60 |
61 |
62 | {articles.length === 0 ? ( 63 |
64 |

暂无文章,欢迎贡献第一篇文章!

65 | 68 |
69 | ) : ( 70 | <> 71 |
72 | {articles.map((article) => ( 73 | 74 | 75 | 76 |
77 | {article.category} 78 | {article.publishedAt} 79 |
80 | {article.title} 81 | 82 | {article.description} 83 | 84 |
85 | 86 |
87 |
88 | 89 | 90 | {article.author[0]} 91 | 92 | {article.author} 93 |
94 |
95 |
96 | 97 | {article.readTime} 98 |
99 | {article.views !== undefined && ( 100 |
101 | 102 | {article.views} 103 |
104 | )} 105 | {article.likes !== undefined && ( 106 |
107 | 108 | {article.likes} 109 |
110 | )} 111 |
112 |
113 |
114 | {article.tags.map((tag) => ( 115 | 116 | {tag} 117 | 118 | ))} 119 |
120 |
121 |
122 | 123 | ))} 124 |
125 | 126 | {/* Load More - Hidden for now as we show all articles */} 127 | {articles.length > 9 && ( 128 |
129 | 132 |
133 | )} 134 | 135 | )} 136 |
137 |
138 |
139 | ) 140 | } 141 | -------------------------------------------------------------------------------- /app/register/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import type React from "react" 4 | 5 | import { useState } from "react" 6 | import Link from "next/link" 7 | import { Button } from "@/components/ui/button" 8 | import { Input } from "@/components/ui/input" 9 | import { Label } from "@/components/ui/label" 10 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 11 | import { Separator } from "@/components/ui/separator" 12 | import { Checkbox } from "@/components/ui/checkbox" 13 | import { Chrome, Mail, Lock, User } from "lucide-react" 14 | 15 | export default function RegisterPage() { 16 | const [name, setName] = useState("") 17 | const [email, setEmail] = useState("") 18 | const [password, setPassword] = useState("") 19 | const [confirmPassword, setConfirmPassword] = useState("") 20 | const [agreeToTerms, setAgreeToTerms] = useState(false) 21 | const [isLoading, setIsLoading] = useState(false) 22 | 23 | const handleEmailRegister = async (e: React.FormEvent) => { 24 | e.preventDefault() 25 | 26 | if (password !== confirmPassword) { 27 | alert("两次输入的密码不一致") 28 | return 29 | } 30 | 31 | if (!agreeToTerms) { 32 | alert("请同意服务条款和隐私政策") 33 | return 34 | } 35 | 36 | setIsLoading(true) 37 | // TODO: Implement email registration with Supabase 38 | setTimeout(() => { 39 | setIsLoading(false) 40 | alert("注册功能将在集成 Supabase 后启用") 41 | }, 1000) 42 | } 43 | 44 | const handleGoogleRegister = async () => { 45 | setIsLoading(true) 46 | // TODO: Implement Google OAuth with Supabase 47 | setTimeout(() => { 48 | setIsLoading(false) 49 | alert("Google 注册功能将在集成 Supabase 后启用") 50 | }, 1000) 51 | } 52 | 53 | return ( 54 |
55 | 56 | 57 |
58 | v0 59 |
60 | 创建账号 61 | 加入 v0.app 中文社区,开始你的学习之旅 62 |
63 | 64 | {/* Google Register */} 65 | 74 | 75 |
76 |
77 | 78 |
79 |
80 | 或使用邮箱注册 81 |
82 |
83 | 84 | {/* Email Register Form */} 85 |
86 |
87 | 88 |
89 | 90 | setName(e.target.value)} 96 | className="pl-10" 97 | required 98 | /> 99 |
100 |
101 | 102 |
103 | 104 |
105 | 106 | setEmail(e.target.value)} 112 | className="pl-10" 113 | required 114 | /> 115 |
116 |
117 | 118 |
119 | 120 |
121 | 122 | setPassword(e.target.value)} 128 | className="pl-10" 129 | required 130 | minLength={8} 131 | /> 132 |
133 |
134 | 135 |
136 | 137 |
138 | 139 | setConfirmPassword(e.target.value)} 145 | className="pl-10" 146 | required 147 | minLength={8} 148 | /> 149 |
150 |
151 | 152 |
153 | setAgreeToTerms(!!checked)} /> 154 | 164 |
165 | 166 | 169 |
170 | 171 |
172 | 已有账号?{" "} 173 | 174 | 立即登录 175 | 176 |
177 |
178 |
179 |
180 | ) 181 | } 182 | -------------------------------------------------------------------------------- /app/about/page.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 2 | import { Badge } from "@/components/ui/badge" 3 | import { Button } from "@/components/ui/button" 4 | import Link from "next/link" 5 | import { Target, Users, Heart, Zap, Globe, BookOpen } from "lucide-react" 6 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" 7 | 8 | export default function AboutPage() { 9 | const mission = [ 10 | { 11 | icon: Target, 12 | title: "我们的使命", 13 | description: "为中文开发者提供最优质的 v0.app 学习资源和交流平台,降低 AI 驱动开发的学习门槛。", 14 | iconColor: "text-blue-600 dark:text-blue-400", 15 | bgColor: "bg-blue-500/10 dark:bg-blue-500/20", 16 | }, 17 | { 18 | icon: Users, 19 | title: "社区驱动", 20 | description: "由社区成员共同维护和贡献,每个人都可以分享经验、提出建议和参与建设。", 21 | iconColor: "text-purple-600 dark:text-purple-400", 22 | bgColor: "bg-purple-500/10 dark:bg-purple-500/20", 23 | }, 24 | { 25 | icon: Heart, 26 | title: "开放包容", 27 | description: "欢迎所有对 v0.app 感兴趣的开发者,无论你是初学者还是资深专家。", 28 | iconColor: "text-pink-600 dark:text-pink-400", 29 | bgColor: "bg-pink-500/10 dark:bg-pink-500/20", 30 | }, 31 | ] 32 | 33 | const values = [ 34 | { 35 | icon: BookOpen, 36 | title: "知识共享", 37 | description: "鼓励成员分享学习心得和实战经验", 38 | iconColor: "text-green-600 dark:text-green-400", 39 | bgColor: "bg-green-500/10 dark:bg-green-500/20", 40 | }, 41 | { 42 | icon: Zap, 43 | title: "持续创新", 44 | description: "探索 AI 开发的最新技术和最佳实践", 45 | iconColor: "text-orange-600 dark:text-orange-400", 46 | bgColor: "bg-orange-500/10 dark:bg-orange-500/20", 47 | }, 48 | { 49 | icon: Globe, 50 | title: "全球视野", 51 | description: "连接中文社区与国际 v0 生态系统", 52 | iconColor: "text-cyan-600 dark:text-cyan-400", 53 | bgColor: "bg-cyan-500/10 dark:bg-cyan-500/20", 54 | }, 55 | ] 56 | 57 | const team = [ 58 | { 59 | name: "社区创始人", 60 | role: "v0.app 官方大使候选人", 61 | avatar: "/diverse-avatars.png", 62 | description: "致力于推广 v0.app 在中文开发者社区的应用", 63 | }, 64 | ] 65 | 66 | const stats = [ 67 | { label: "社区成员", value: "1,000+" }, 68 | { label: "分享文章", value: "200+" }, 69 | { label: "案例展示", value: "150+" }, 70 | { label: "GitHub Stars", value: "500+" }, 71 | ] 72 | 73 | return ( 74 |
75 | {/* Hero Section */} 76 |
77 |
78 | 79 | 关于我们 80 | 81 |

82 | v0.app 中文社区 83 |

84 |

85 | 我们是一个由热爱 v0.app 的中文开发者组成的社区,致力于为中文用户提供最好的学习资源、实战案例和技术支持。 86 |

87 |
88 |
89 | 90 | {/* Stats Section */} 91 |
92 |
93 |
94 | {stats.map((stat) => ( 95 |
96 |
{stat.value}
97 |
{stat.label}
98 |
99 | ))} 100 |
101 |
102 |
103 | 104 | {/* Mission Section */} 105 |
106 |
107 |
108 |

关于社区

109 |

我们的愿景和价值观

110 |
111 | 112 |
113 | {mission.map((item) => ( 114 | 115 | 116 |
117 | 118 |
119 | {item.title} 120 |
121 | 122 | {item.description} 123 | 124 |
125 | ))} 126 |
127 |
128 |
129 | 130 | {/* Values Section */} 131 |
132 |
133 |
134 |

核心价值观

135 |

指导我们社区发展的原则

136 |
137 | 138 |
139 | {values.map((value) => ( 140 |
141 |
142 | 143 |
144 |
145 |

{value.title}

146 |

{value.description}

147 |
148 |
149 | ))} 150 |
151 |
152 |
153 | 154 | {/* Team Section */} 155 |
156 |
157 |
158 |

团队介绍

159 |

社区的核心成员

160 |
161 | 162 |
163 | {team.map((member) => ( 164 | 165 | 166 | 167 | 168 | {member.name[0]} 169 | 170 | {member.name} 171 | {member.role} 172 | 173 | 174 |

{member.description}

175 |
176 |
177 | ))} 178 |
179 |
180 |
181 | 182 | {/* CTA Section */} 183 |
184 |
185 |

加入我们

186 |

成为 v0.app 中文社区的一员,与我们一起成长

187 |
188 | 191 | 194 |
195 |
196 |
197 |
198 | ) 199 | } 200 | -------------------------------------------------------------------------------- /app/cases/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation" 2 | import Image from "next/image" 3 | import Link from "next/link" 4 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" 5 | import { Badge } from "@/components/ui/badge" 6 | import { Button } from "@/components/ui/button" 7 | import { Card, CardContent } from "@/components/ui/card" 8 | import { Separator } from "@/components/ui/separator" 9 | import { Eye, Heart, Share2, Bookmark, ArrowLeft, ExternalLink, Github } from "lucide-react" 10 | import { getCaseById, getAllCases } from "@/lib/markdown" 11 | import ReactMarkdown from "react-markdown" 12 | 13 | export default async function CaseDetailPage({ params }: { params: Promise<{ id: string }> }) { 14 | const { id } = await params 15 | const caseStudy = getCaseById(id) 16 | 17 | if (!caseStudy) { 18 | notFound() 19 | } 20 | 21 | const allCases = getAllCases() 22 | const relatedCases = allCases.filter((c) => c.id !== id && c.category === caseStudy.category).slice(0, 3) 23 | 24 | return ( 25 |
26 | {/* Back Button */} 27 |
28 |
29 | 35 |
36 |
37 | 38 | {/* Case Header */} 39 |
40 |
41 |
42 | {caseStudy.category} 43 | {caseStudy.difficulty} 44 | {caseStudy.publishedAt} 45 |
46 | 47 |

48 | {caseStudy.title} 49 |

50 | 51 |

{caseStudy.description}

52 | 53 | {/* Author Info and Actions */} 54 |
55 |
56 | 57 | 58 | {caseStudy.author[0]} 59 | 60 |
61 |
{caseStudy.author}
62 |
63 | {caseStudy.views !== undefined && ( 64 |
65 | 66 | {caseStudy.views} 67 |
68 | )} 69 | {caseStudy.likes !== undefined && ( 70 |
71 | 72 | {caseStudy.likes} 73 |
74 | )} 75 |
76 |
77 |
78 | 79 |
80 | {caseStudy.demoUrl && ( 81 | 87 | )} 88 | {caseStudy.githubUrl && ( 89 | 95 | )} 96 |
97 |
98 | 99 | {/* Thumbnail */} 100 |
101 | {caseStudy.title} 107 |
108 | 109 | 110 | 111 | {/* Tech Stack */} 112 |
113 |

技术栈

114 |
115 | {caseStudy.techStack.map((tech) => ( 116 | 117 | {tech} 118 | 119 | ))} 120 |
121 |
122 | 123 | 124 | 125 | {/* Case Content */} 126 |
127 | {caseStudy.content} 128 |
129 | 130 | {/* Tags */} 131 |
132 | {caseStudy.tags.map((tag) => ( 133 | 134 | {tag} 135 | 136 | ))} 137 |
138 | 139 | 140 | 141 | {/* Engagement Actions */} 142 |
143 |
144 | 148 | 152 | 156 |
157 |
158 |
159 |
160 | 161 | {/* Related Cases */} 162 | {relatedCases.length > 0 && ( 163 |
164 |
165 |

相关案例

166 |
167 | {relatedCases.map((relatedCase) => ( 168 | 169 | 170 |
171 | {relatedCase.title} 177 |
178 | 179 | 180 | {relatedCase.category} 181 | 182 |

{relatedCase.title}

183 |

{relatedCase.description}

184 |
185 |
186 | 187 | ))} 188 |
189 |
190 |
191 | )} 192 |
193 | ) 194 | } 195 | -------------------------------------------------------------------------------- /app/cases/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | import Image from "next/image" 3 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 4 | import { Badge } from "@/components/ui/badge" 5 | import { Button } from "@/components/ui/button" 6 | import { Input } from "@/components/ui/input" 7 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" 8 | import { Search, Eye, Heart, ExternalLink, Github, Upload } from "lucide-react" 9 | import { getAllCases, getCaseCategories } from "@/lib/markdown" 10 | 11 | export default function CasesPage() { 12 | const cases = getAllCases() 13 | const caseCategories = getCaseCategories() 14 | 15 | return ( 16 |
17 | {/* Hero Section */} 18 |
19 |
20 |
21 |

案例展示

22 |

23 | 探索使用 v0.app 构建的真实项目案例,获取灵感和学习经验 24 |

25 |
26 | 27 | {/* Search Bar */} 28 |
29 |
30 | 31 | 32 |
33 |
34 | 35 | {/* CTA Button */} 36 |
37 | 43 |
44 |
45 |
46 | 47 | {/* Categories */} 48 |
49 |
50 |
51 | {caseCategories.map((category) => ( 52 | 55 | ))} 56 |
57 |
58 |
59 | 60 | {/* Cases Grid */} 61 |
62 |
63 | {cases.length === 0 ? ( 64 |
65 |

暂无案例,欢迎提交第一个案例!

66 | 69 |
70 | ) : ( 71 | <> 72 |
73 | {cases.map((caseStudy) => ( 74 | 78 | 79 |
80 | {caseStudy.title} 86 |
87 | {caseStudy.difficulty} 88 |
89 |
90 | 91 | 92 |
93 | {caseStudy.category} 94 | {caseStudy.publishedAt} 95 |
96 | 97 | 98 | {caseStudy.title} 99 | 100 | 101 | 102 | {caseStudy.description} 103 | 104 |
105 | 106 |
107 |
108 | 109 | 110 | {caseStudy.author[0]} 111 | 112 | {caseStudy.author} 113 |
114 |
115 | {caseStudy.views !== undefined && ( 116 |
117 | 118 | {caseStudy.views} 119 |
120 | )} 121 | {caseStudy.likes !== undefined && ( 122 |
123 | 124 | {caseStudy.likes} 125 |
126 | )} 127 |
128 |
129 | 130 |
131 | {caseStudy.techStack.slice(0, 3).map((tech) => ( 132 | 133 | {tech} 134 | 135 | ))} 136 | {caseStudy.techStack.length > 3 && ( 137 | 138 | +{caseStudy.techStack.length - 3} 139 | 140 | )} 141 |
142 | 143 |
144 | {caseStudy.demoUrl && ( 145 | 151 | )} 152 | {caseStudy.githubUrl && ( 153 | 159 | )} 160 |
161 |
162 |
163 | ))} 164 |
165 | 166 | {/* Load More - Hidden for now as we show all cases */} 167 | {cases.length > 9 && ( 168 |
169 | 172 |
173 | )} 174 | 175 | )} 176 |
177 |
178 |
179 | ) 180 | } 181 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | import { Button } from "@/components/ui/button" 3 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 4 | import { ArrowRight, BookOpen, Code2, Users, Sparkles, Github, MessageSquare } from "lucide-react" 5 | 6 | export default function HomePage() { 7 | const features = [ 8 | { 9 | icon: Sparkles, 10 | title: "AI 驱动开发", 11 | description: "使用 AI 快速生成高质量的 React 和 Next.js 代码", 12 | iconColor: "text-purple-600 dark:text-purple-400", 13 | bgColor: "bg-purple-500/10 dark:bg-purple-500/20", 14 | }, 15 | { 16 | icon: Code2, 17 | title: "即时预览", 18 | description: "实时查看代码效果,快速迭代和调整设计", 19 | iconColor: "text-blue-600 dark:text-blue-400", 20 | bgColor: "bg-blue-500/10 dark:bg-blue-500/20", 21 | }, 22 | { 23 | icon: BookOpen, 24 | title: "学习资源", 25 | description: "丰富的教程、文章和最佳实践分享", 26 | iconColor: "text-green-600 dark:text-green-400", 27 | bgColor: "bg-green-500/10 dark:bg-green-500/20", 28 | }, 29 | { 30 | icon: Users, 31 | title: "活跃社区", 32 | description: "与其他开发者交流经验,共同成长", 33 | iconColor: "text-orange-600 dark:text-orange-400", 34 | bgColor: "bg-orange-500/10 dark:bg-orange-500/20", 35 | }, 36 | ] 37 | 38 | const stats = [ 39 | { label: "社区成员", value: "1,000+" }, 40 | { label: "分享文章", value: "200+" }, 41 | { label: "案例展示", value: "150+" }, 42 | { label: "每日活跃", value: "500+" }, 43 | ] 44 | 45 | return ( 46 |
47 | {/* Hero Section */} 48 |
49 |
50 |
51 |

52 | 欢迎来到 v0.app 中文社区 53 |

54 |

55 | 为中文开发者提供 v0.app 学习资源、实战案例和技术交流平台。 加入我们,一起探索 AI 驱动的前端开发新时代。 56 |

57 |
58 | 64 | 67 | 70 |
71 |
72 |
73 |
74 | 75 | {/* Stats Section */} 76 |
77 |
78 |
79 | {stats.map((stat) => ( 80 |
81 |
{stat.value}
82 |
{stat.label}
83 |
84 | ))} 85 |
86 |
87 |
88 | 89 | {/* Features Section */} 90 |
91 |
92 |
93 |

94 | 为什么选择 v0.app 95 |

96 |

97 | v0.app 是 Vercel 推出的 AI 驱动的开发工具,帮助开发者快速构建现代化的 Web 应用 98 |

99 |
100 | 101 |
102 | {features.map((feature) => ( 103 | 104 | 105 |
106 | 107 |
108 | {feature.title} 109 |
110 | 111 | {feature.description} 112 | 113 |
114 | ))} 115 |
116 |
117 |
118 | 119 | {/* Latest Content Section */} 120 |
121 |
122 |
123 | {/* Latest Articles */} 124 |
125 |
126 |

最新文章

127 | 133 |
134 |
135 | {[1, 2, 3].map((i) => ( 136 | 137 | 138 | v0.app 入门指南:从零开始构建你的第一个应用 139 | 2025年1月{i}日 · 5分钟阅读 140 | 141 | 142 | ))} 143 |
144 |
145 | 146 | {/* Latest Cases */} 147 |
148 |
149 |

精选案例

150 | 156 |
157 |
158 | {[1, 2, 3].map((i) => ( 159 | 160 | 161 | 电商平台首页设计 - 使用 v0.app 快速实现 162 | 163 | 作者:开发者{i} · 2025年1月{i}日 164 | 165 | 166 | 167 | ))} 168 |
169 |
170 |
171 |
172 |
173 | 174 | {/* CTA Section */} 175 |
176 |
177 | 178 | 179 | 加入 v0.app 中文社区 180 | 181 | 与数千名开发者一起学习、分享和成长。贡献你的文章和案例,帮助更多人了解 v0.app 182 | 183 | 184 | 185 | 191 | 197 | 203 | 204 | 205 |
206 |
207 |
208 | ) 209 | } 210 | -------------------------------------------------------------------------------- /app/features/page.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 2 | import { Badge } from "@/components/ui/badge" 3 | import { Button } from "@/components/ui/button" 4 | import Link from "next/link" 5 | import { 6 | Sparkles, 7 | Code2, 8 | Zap, 9 | Palette, 10 | Database, 11 | Globe, 12 | Layers, 13 | Terminal, 14 | GitBranch, 15 | Rocket, 16 | Shield, 17 | Users, 18 | } from "lucide-react" 19 | 20 | export default function FeaturesPage() { 21 | const coreFeatures = [ 22 | { 23 | icon: Sparkles, 24 | title: "AI 智能生成", 25 | description: "通过自然语言描述,AI 自动生成高质量的 React 和 Next.js 代码", 26 | highlights: ["自然语言交互", "智能代码补全", "上下文理解"], 27 | iconColor: "text-purple-600 dark:text-purple-400", 28 | bgColor: "bg-purple-500/10 dark:bg-purple-500/20", 29 | }, 30 | { 31 | icon: Code2, 32 | title: "实时代码预览", 33 | description: "即时查看代码运行效果,支持热重载和实时编辑", 34 | highlights: ["即时预览", "热重载", "多设备预览"], 35 | iconColor: "text-blue-600 dark:text-blue-400", 36 | bgColor: "bg-blue-500/10 dark:bg-blue-500/20", 37 | }, 38 | { 39 | icon: Zap, 40 | title: "快速迭代", 41 | description: "通过对话式交互快速调整设计和功能,大幅提升开发效率", 42 | highlights: ["对话式开发", "快速修改", "版本管理"], 43 | iconColor: "text-orange-600 dark:text-orange-400", 44 | bgColor: "bg-orange-500/10 dark:bg-orange-500/20", 45 | }, 46 | { 47 | icon: Palette, 48 | title: "现代化设计", 49 | description: "内置 shadcn/ui 组件库和 Tailwind CSS,轻松实现精美界面", 50 | highlights: ["shadcn/ui", "Tailwind CSS", "响应式设计"], 51 | iconColor: "text-pink-600 dark:text-pink-400", 52 | bgColor: "bg-pink-500/10 dark:bg-pink-500/20", 53 | }, 54 | { 55 | icon: Database, 56 | title: "数据库集成", 57 | description: "支持 Supabase、Neon 等主流数据库,快速构建全栈应用", 58 | highlights: ["Supabase", "Neon", "Upstash"], 59 | iconColor: "text-green-600 dark:text-green-400", 60 | bgColor: "bg-green-500/10 dark:bg-green-500/20", 61 | }, 62 | { 63 | icon: Globe, 64 | title: "一键部署", 65 | description: "与 Vercel 深度集成,一键部署到生产环境", 66 | highlights: ["Vercel 部署", "自动 CI/CD", "全球 CDN"], 67 | iconColor: "text-cyan-600 dark:text-cyan-400", 68 | bgColor: "bg-cyan-500/10 dark:bg-cyan-500/20", 69 | }, 70 | ] 71 | 72 | const advancedFeatures = [ 73 | { 74 | icon: Layers, 75 | title: "组件化开发", 76 | description: "自动拆分组件,保持代码结构清晰和可维护性", 77 | iconColor: "text-indigo-600 dark:text-indigo-400", 78 | bgColor: "bg-indigo-500/10 dark:bg-indigo-500/20", 79 | }, 80 | { 81 | icon: Terminal, 82 | title: "TypeScript 支持", 83 | description: "完整的 TypeScript 类型支持,提供更好的开发体验", 84 | iconColor: "text-blue-600 dark:text-blue-400", 85 | bgColor: "bg-blue-500/10 dark:bg-blue-500/20", 86 | }, 87 | { 88 | icon: GitBranch, 89 | title: "版本控制", 90 | description: "支持 GitHub 集成,方便团队协作和代码管理", 91 | iconColor: "text-orange-600 dark:text-orange-400", 92 | bgColor: "bg-orange-500/10 dark:bg-orange-500/20", 93 | }, 94 | { 95 | icon: Rocket, 96 | title: "性能优化", 97 | description: "自动优化代码性能,包括图片优化、代码分割等", 98 | iconColor: "text-red-600 dark:text-red-400", 99 | bgColor: "bg-red-500/10 dark:bg-red-500/20", 100 | }, 101 | { 102 | icon: Shield, 103 | title: "安全可靠", 104 | description: "遵循最佳安全实践,保护你的应用和数据安全", 105 | iconColor: "text-green-600 dark:text-green-400", 106 | bgColor: "bg-green-500/10 dark:bg-green-500/20", 107 | }, 108 | { 109 | icon: Users, 110 | title: "团队协作", 111 | description: "支持多人协作开发,提高团队生产力", 112 | iconColor: "text-purple-600 dark:text-purple-400", 113 | bgColor: "bg-purple-500/10 dark:bg-purple-500/20", 114 | }, 115 | ] 116 | 117 | const useCases = [ 118 | { 119 | title: "快速原型开发", 120 | description: "在几分钟内将想法转化为可交互的原型", 121 | color: "bg-blue-500/10 text-blue-600 dark:bg-blue-500/20 dark:text-blue-400", 122 | }, 123 | { 124 | title: "落地页制作", 125 | description: "快速创建精美的营销落地页和产品展示页", 126 | color: "bg-purple-500/10 text-purple-600 dark:bg-purple-500/20 dark:text-purple-400", 127 | }, 128 | { 129 | title: "管理后台", 130 | description: "构建功能完整的管理后台和数据看板", 131 | color: "bg-green-500/10 text-green-600 dark:bg-green-500/20 dark:text-green-400", 132 | }, 133 | { 134 | title: "全栈应用", 135 | description: "开发包含前后端的完整 Web 应用", 136 | color: "bg-orange-500/10 text-orange-600 dark:bg-orange-500/20 dark:text-orange-400", 137 | }, 138 | ] 139 | 140 | return ( 141 |
142 | {/* Hero Section */} 143 |
144 |
145 | 146 | 功能介绍 147 | 148 |

149 | 强大的 AI 驱动开发工具 150 |

151 |

152 | v0.app 结合了 AI 的智能和现代前端技术栈,让开发变得更快、更简单、更有趣 153 |

154 |
155 |
156 | 157 | {/* Core Features */} 158 |
159 |
160 |
161 |

核心功能

162 |

探索 v0.app 的强大功能特性

163 |
164 | 165 |
166 | {coreFeatures.map((feature) => ( 167 | 168 | 169 |
170 | 171 |
172 | {feature.title} 173 | {feature.description} 174 |
175 | 176 |
177 | {feature.highlights.map((highlight) => ( 178 | 179 | {highlight} 180 | 181 | ))} 182 |
183 |
184 |
185 | ))} 186 |
187 |
188 |
189 | 190 | {/* Advanced Features */} 191 |
192 |
193 |
194 |

更多特性

195 |

为专业开发者提供的高级功能

196 |
197 | 198 |
199 | {advancedFeatures.map((feature) => ( 200 |
201 |
202 | 203 |
204 |
205 |

{feature.title}

206 |

{feature.description}

207 |
208 |
209 | ))} 210 |
211 |
212 |
213 | 214 | {/* Use Cases */} 215 |
216 |
217 |
218 |

应用场景

219 |

v0.app 适用于各种开发场景

220 |
221 | 222 |
223 | {useCases.map((useCase) => ( 224 | 225 | 226 |
227 | {useCase.title} 228 |
229 | {useCase.description} 230 |
231 |
232 | ))} 233 |
234 |
235 |
236 | 237 | {/* CTA Section */} 238 |
239 |
240 |

准备好开始了吗?

241 |

加入 v0.app 中文社区,与其他开发者一起学习和成长

242 |
243 | 246 | 249 |
250 |
251 |
252 |
253 | ) 254 | } 255 | -------------------------------------------------------------------------------- /lib/mock-data.ts: -------------------------------------------------------------------------------- 1 | export interface Article { 2 | id: string 3 | title: string 4 | description: string 5 | content: string 6 | author: { 7 | name: string 8 | avatar: string 9 | } 10 | category: string 11 | tags: string[] 12 | publishedAt: string 13 | readTime: string 14 | views: number 15 | likes: number 16 | } 17 | 18 | export const mockArticles: Article[] = [ 19 | { 20 | id: "1", 21 | title: "v0.app 入门指南:从零开始构建你的第一个应用", 22 | description: "本文将带你了解 v0.app 的基本概念和使用方法,帮助你快速上手这个强大的 AI 开发工具。", 23 | content: ` 24 | # v0.app 入门指南 25 | 26 | v0.app 是 Vercel 推出的革命性 AI 开发工具,它能够通过自然语言描述自动生成高质量的 React 和 Next.js 代码。 27 | 28 | ## 什么是 v0.app 29 | 30 | v0.app 结合了大语言模型的智能和现代前端技术栈,让开发变得更快、更简单。你只需要用自然语言描述你想要的功能,v0 就能为你生成相应的代码。 31 | 32 | ## 快速开始 33 | 34 | 1. 访问 v0.dev 并登录你的账号 35 | 2. 在对话框中描述你想要创建的内容 36 | 3. v0 会为你生成代码并提供实时预览 37 | 4. 你可以继续对话来调整和优化代码 38 | 39 | ## 最佳实践 40 | 41 | - 清晰描述你的需求 42 | - 提供具体的设计要求 43 | - 利用迭代对话来完善功能 44 | - 学习生成的代码以提升技能 45 | 46 | 开始你的 v0 之旅吧! 47 | `, 48 | author: { 49 | name: "张三", 50 | avatar: "/diverse-avatars.png", 51 | }, 52 | category: "入门教程", 53 | tags: ["入门", "教程", "基础"], 54 | publishedAt: "2025-01-15", 55 | readTime: "5分钟", 56 | views: 1234, 57 | likes: 89, 58 | }, 59 | { 60 | id: "2", 61 | title: "使用 v0.app 快速构建响应式落地页", 62 | description: "学习如何利用 v0.app 的 AI 能力,在几分钟内创建一个精美的响应式落地页。", 63 | content: ` 64 | # 使用 v0.app 快速构建响应式落地页 65 | 66 | 落地页是产品营销的重要工具,而 v0.app 让创建落地页变得前所未有的简单。 67 | 68 | ## 设计要点 69 | 70 | 一个好的落地页需要: 71 | - 清晰的价值主张 72 | - 吸引人的视觉设计 73 | - 明确的行动号召 74 | - 响应式布局 75 | 76 | ## 使用 v0 创建落地页 77 | 78 | 只需告诉 v0 你的产品特点和目标受众,它就能为你生成完整的落地页代码。 79 | 80 | ## 优化技巧 81 | 82 | - 使用高质量的图片 83 | - 保持简洁的文案 84 | - 优化加载速度 85 | - A/B 测试不同版本 86 | 87 | 让 v0 帮你快速实现想法! 88 | `, 89 | author: { 90 | name: "李四", 91 | avatar: "/diverse-avatars.png", 92 | }, 93 | category: "实战案例", 94 | tags: ["落地页", "响应式", "设计"], 95 | publishedAt: "2025-01-14", 96 | readTime: "8分钟", 97 | views: 987, 98 | likes: 67, 99 | }, 100 | { 101 | id: "3", 102 | title: "v0.app 与 Supabase 集成:构建全栈应用", 103 | description: "深入了解如何将 v0.app 与 Supabase 结合,快速开发具有数据库功能的全栈应用。", 104 | content: ` 105 | # v0.app 与 Supabase 集成 106 | 107 | 将 v0.app 与 Supabase 结合,你可以快速构建功能完整的全栈应用。 108 | 109 | ## Supabase 简介 110 | 111 | Supabase 是一个开源的 Firebase 替代品,提供: 112 | - PostgreSQL 数据库 113 | - 实时订阅 114 | - 身份认证 115 | - 存储服务 116 | 117 | ## 集成步骤 118 | 119 | 1. 在 v0 中添加 Supabase 集成 120 | 2. 配置数据库连接 121 | 3. 使用 v0 生成数据库操作代码 122 | 4. 实现认证和授权 123 | 124 | ## 实战示例 125 | 126 | 我们将创建一个简单的博客系统,包含文章的增删改查功能。 127 | 128 | 开始构建你的全栈应用吧! 129 | `, 130 | author: { 131 | name: "王五", 132 | avatar: "/diverse-avatars.png", 133 | }, 134 | category: "进阶技巧", 135 | tags: ["Supabase", "全栈", "数据库"], 136 | publishedAt: "2025-01-13", 137 | readTime: "12分钟", 138 | views: 1456, 139 | likes: 123, 140 | }, 141 | { 142 | id: "4", 143 | title: "v0.app 最佳实践:提高代码质量的10个技巧", 144 | description: "分享使用 v0.app 开发时的最佳实践,帮助你生成更高质量、更易维护的代码。", 145 | content: ` 146 | # v0.app 最佳实践 147 | 148 | 虽然 v0 能自动生成代码,但掌握一些技巧能让你获得更好的结果。 149 | 150 | ## 10个实用技巧 151 | 152 | 1. 提供清晰的需求描述 153 | 2. 指定技术栈和组件库 154 | 3. 描述具体的交互行为 155 | 4. 提供设计参考 156 | 5. 分步骤迭代开发 157 | 6. 重视代码组织结构 158 | 7. 关注性能优化 159 | 8. 考虑可访问性 160 | 9. 添加错误处理 161 | 10. 编写测试用例 162 | 163 | ## 实践建议 164 | 165 | 每个技巧都配有详细的示例和说明,帮助你更好地使用 v0。 166 | 167 | 提升你的 v0 使用技能! 168 | `, 169 | author: { 170 | name: "赵六", 171 | avatar: "/diverse-avatars.png", 172 | }, 173 | category: "最佳实践", 174 | tags: ["最佳实践", "代码质量", "技巧"], 175 | publishedAt: "2025-01-12", 176 | readTime: "10分钟", 177 | views: 2103, 178 | likes: 178, 179 | }, 180 | { 181 | id: "5", 182 | title: "从设计到代码:v0.app 的完整工作流程", 183 | description: "了解如何将设计稿转化为代码,以及如何在 v0.app 中实现设计师和开发者的高效协作。", 184 | content: ` 185 | # 从设计到代码 186 | 187 | v0.app 不仅是开发工具,更是连接设计和开发的桥梁。 188 | 189 | ## 工作流程 190 | 191 | 1. 设计阶段:创建原型和设计稿 192 | 2. 描述阶段:用自然语言描述设计 193 | 3. 生成阶段:v0 生成初始代码 194 | 4. 迭代阶段:持续优化和调整 195 | 5. 部署阶段:一键部署到生产环境 196 | 197 | ## 协作技巧 198 | 199 | - 使用统一的设计系统 200 | - 建立清晰的沟通流程 201 | - 利用 v0 的版本管理 202 | - 及时反馈和调整 203 | 204 | 让设计和开发无缝衔接! 205 | `, 206 | author: { 207 | name: "孙七", 208 | avatar: "/diverse-avatars.png", 209 | }, 210 | category: "工作流程", 211 | tags: ["设计", "协作", "工作流"], 212 | publishedAt: "2025-01-11", 213 | readTime: "7分钟", 214 | views: 876, 215 | likes: 54, 216 | }, 217 | { 218 | id: "6", 219 | title: "v0.app AI 提示词工程:如何写出更好的提示", 220 | description: "掌握提示词工程的技巧,让 v0.app 更准确地理解你的需求并生成理想的代码。", 221 | content: ` 222 | # AI 提示词工程 223 | 224 | 好的提示词是获得高质量代码的关键。 225 | 226 | ## 提示词原则 227 | 228 | - 具体而非模糊 229 | - 结构化描述 230 | - 提供上下文 231 | - 明确约束条件 232 | 233 | ## 示例对比 234 | 235 | **不好的提示:** 236 | "做一个按钮" 237 | 238 | **好的提示:** 239 | "创建一个蓝色的主要按钮,带有白色文字和圆角,鼠标悬停时有阴影效果" 240 | 241 | ## 高级技巧 242 | 243 | 学习如何使用多轮对话来完善需求,以及如何提供有效的反馈。 244 | 245 | 成为提示词专家! 246 | `, 247 | author: { 248 | name: "周八", 249 | avatar: "/diverse-avatars.png", 250 | }, 251 | category: "进阶技巧", 252 | tags: ["AI", "提示词", "技巧"], 253 | publishedAt: "2025-01-10", 254 | readTime: "6分钟", 255 | views: 1567, 256 | likes: 134, 257 | }, 258 | ] 259 | 260 | export const categories = ["全部", "入门教程", "实战案例", "进阶技巧", "最佳实践", "工作流程"] 261 | 262 | export interface CaseStudy { 263 | id: string 264 | title: string 265 | description: string 266 | thumbnail: string 267 | category: string 268 | tags: string[] 269 | author: { 270 | name: string 271 | avatar: string 272 | } 273 | publishedAt: string 274 | likes: number 275 | views: number 276 | demoUrl?: string 277 | githubUrl?: string 278 | difficulty: "初级" | "中级" | "高级" 279 | techStack: string[] 280 | content: string 281 | } 282 | 283 | export const mockCases: CaseStudy[] = [ 284 | { 285 | id: "1", 286 | title: "电商平台首页设计", 287 | description: "使用 v0.app 快速实现的现代化电商平台首页,包含商品展示、分类导航和购物车功能。", 288 | thumbnail: "/modern-ecommerce-homepage.jpg", 289 | category: "电商", 290 | tags: ["电商", "首页", "响应式"], 291 | author: { 292 | name: "张三", 293 | avatar: "/diverse-avatars.png", 294 | }, 295 | publishedAt: "2025-01-15", 296 | likes: 234, 297 | views: 3456, 298 | demoUrl: "https://demo.example.com", 299 | githubUrl: "https://github.com/example/ecommerce", 300 | difficulty: "中级", 301 | techStack: ["Next.js", "Tailwind CSS", "shadcn/ui", "Stripe"], 302 | content: ` 303 | # 电商平台首页设计 304 | 305 | 这是一个使用 v0.app 构建的现代化电商平台首页案例。 306 | 307 | ## 项目概述 308 | 309 | 本项目展示了如何使用 v0.app 快速构建一个功能完整的电商首页,包括: 310 | - 响应式导航栏 311 | - 商品分类展示 312 | - 特色商品轮播 313 | - 购物车功能 314 | - 用户评价展示 315 | 316 | ## 技术实现 317 | 318 | 使用 Next.js 14 App Router 和 Tailwind CSS 实现,集成了 Stripe 支付功能。 319 | 320 | ## 开发过程 321 | 322 | 1. 使用 v0 生成基础布局 323 | 2. 添加商品数据结构 324 | 3. 实现购物车逻辑 325 | 4. 集成支付功能 326 | 5. 优化性能和 SEO 327 | 328 | ## 学习要点 329 | 330 | - 组件化设计思路 331 | - 状态管理最佳实践 332 | - 支付集成方案 333 | - 性能优化技巧 334 | `, 335 | }, 336 | { 337 | id: "2", 338 | title: "SaaS 产品落地页", 339 | description: "为 SaaS 产品设计的高转化率落地页,包含产品介绍、定价方案和用户注册流程。", 340 | thumbnail: "/saas-landing-page-with-pricing.jpg", 341 | category: "营销", 342 | tags: ["SaaS", "落地页", "转化"], 343 | author: { 344 | name: "李四", 345 | avatar: "/diverse-avatars.png", 346 | }, 347 | publishedAt: "2025-01-14", 348 | likes: 189, 349 | views: 2789, 350 | demoUrl: "https://demo.example.com", 351 | difficulty: "初级", 352 | techStack: ["Next.js", "Tailwind CSS", "Framer Motion"], 353 | content: ` 354 | # SaaS 产品落地页 355 | 356 | 一个专为 SaaS 产品设计的高转化率落地页案例。 357 | 358 | ## 设计理念 359 | 360 | - 清晰的价值主张 361 | - 吸引人的视觉设计 362 | - 明确的行动号召 363 | - 社会证明元素 364 | 365 | ## 功能特点 366 | 367 | 包含产品特性展示、定价表格、客户评价和注册表单等核心模块。 368 | 369 | ## 转化优化 370 | 371 | 通过 A/B 测试和用户反馈持续优化,提高转化率。 372 | `, 373 | }, 374 | { 375 | id: "3", 376 | title: "博客管理系统", 377 | description: "功能完整的博客管理系统,支持文章发布、分类管理、评论互动和 SEO 优化。", 378 | thumbnail: "/blog-management-dashboard.jpg", 379 | category: "内容管理", 380 | tags: ["博客", "CMS", "全栈"], 381 | author: { 382 | name: "王五", 383 | avatar: "/diverse-avatars.png", 384 | }, 385 | publishedAt: "2025-01-13", 386 | likes: 312, 387 | views: 4567, 388 | githubUrl: "https://github.com/example/blog-cms", 389 | difficulty: "高级", 390 | techStack: ["Next.js", "Supabase", "Markdown", "shadcn/ui"], 391 | content: ` 392 | # 博客管理系统 393 | 394 | 使用 v0.app 和 Supabase 构建的全栈博客系统。 395 | 396 | ## 核心功能 397 | 398 | - Markdown 编辑器 399 | - 文章分类和标签 400 | - 评论系统 401 | - SEO 优化 402 | - 图片上传 403 | 404 | ## 技术架构 405 | 406 | 前端使用 Next.js,后端使用 Supabase 提供数据库和认证服务。 407 | 408 | ## 开发亮点 409 | 410 | 展示了如何使用 v0 快速搭建全栈应用的完整流程。 411 | `, 412 | }, 413 | { 414 | id: "4", 415 | title: "任务管理应用", 416 | description: "类似 Trello 的任务管理应用,支持拖拽排序、团队协作和进度追踪。", 417 | thumbnail: "/task-management-kanban.png", 418 | category: "生产力工具", 419 | tags: ["任务管理", "协作", "拖拽"], 420 | author: { 421 | name: "赵六", 422 | avatar: "/diverse-avatars.png", 423 | }, 424 | publishedAt: "2025-01-12", 425 | likes: 267, 426 | views: 3890, 427 | demoUrl: "https://demo.example.com", 428 | githubUrl: "https://github.com/example/task-manager", 429 | difficulty: "高级", 430 | techStack: ["Next.js", "dnd-kit", "Supabase", "Tailwind CSS"], 431 | content: ` 432 | # 任务管理应用 433 | 434 | 一个功能强大的看板式任务管理工具。 435 | 436 | ## 主要功能 437 | 438 | - 拖拽式任务管理 439 | - 多项目支持 440 | - 团队协作 441 | - 实时同步 442 | - 进度统计 443 | 444 | ## 技术实现 445 | 446 | 使用 dnd-kit 实现拖拽功能,Supabase 实时订阅实现多人协作。 447 | 448 | ## 开发经验 449 | 450 | 分享了复杂交互和实时功能的实现方案。 451 | `, 452 | }, 453 | { 454 | id: "5", 455 | title: "在线教育平台", 456 | description: "包含课程展示、视频播放、学习进度追踪和在线测验的完整教育平台。", 457 | thumbnail: "/online-learning-platform.png", 458 | category: "教育", 459 | tags: ["教育", "视频", "学习"], 460 | author: { 461 | name: "孙七", 462 | avatar: "/diverse-avatars.png", 463 | }, 464 | publishedAt: "2025-01-11", 465 | likes: 198, 466 | views: 2345, 467 | demoUrl: "https://demo.example.com", 468 | difficulty: "高级", 469 | techStack: ["Next.js", "Supabase", "Video.js", "shadcn/ui"], 470 | content: ` 471 | # 在线教育平台 472 | 473 | 一个功能完整的在线学习平台案例。 474 | 475 | ## 平台功能 476 | 477 | - 课程浏览和搜索 478 | - 视频播放器 479 | - 学习进度追踪 480 | - 在线测验 481 | - 证书颁发 482 | 483 | ## 技术选型 484 | 485 | 使用 Next.js 构建前端,Supabase 管理用户和课程数据。 486 | 487 | ## 实现要点 488 | 489 | 重点介绍了视频播放、进度追踪和测验系统的实现。 490 | `, 491 | }, 492 | { 493 | id: "6", 494 | title: "数据可视化仪表板", 495 | description: "企业级数据可视化仪表板,包含多种图表类型和实时数据更新。", 496 | thumbnail: "/data-visualization-dashboard.png", 497 | category: "数据分析", 498 | tags: ["数据可视化", "图表", "仪表板"], 499 | author: { 500 | name: "周八", 501 | avatar: "/diverse-avatars.png", 502 | }, 503 | publishedAt: "2025-01-10", 504 | likes: 345, 505 | views: 5678, 506 | demoUrl: "https://demo.example.com", 507 | difficulty: "中级", 508 | techStack: ["Next.js", "Recharts", "Tailwind CSS", "shadcn/ui"], 509 | content: ` 510 | # 数据可视化仪表板 511 | 512 | 使用 v0.app 构建的企业级数据仪表板。 513 | 514 | ## 可视化组件 515 | 516 | - 折线图和柱状图 517 | - 饼图和环形图 518 | - 数据表格 519 | - 实时更新 520 | - 交互式筛选 521 | 522 | ## 设计原则 523 | 524 | 遵循数据可视化最佳实践,确保信息清晰易读。 525 | 526 | ## 性能优化 527 | 528 | 介绍了大数据量下的性能优化方案。 529 | `, 530 | }, 531 | ] 532 | 533 | export const caseCategories = ["全部", "电商", "营销", "内容管理", "生产力工具", "教育", "数据分析"] 534 | -------------------------------------------------------------------------------- /app/join/page.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 2 | import { Badge } from "@/components/ui/badge" 3 | import { Button } from "@/components/ui/button" 4 | import Link from "next/link" 5 | import { 6 | PenSquare, 7 | Code2, 8 | Github, 9 | MessageSquare, 10 | BookOpen, 11 | Heart, 12 | CheckCircle2, 13 | GitPullRequest, 14 | FileText, 15 | } from "lucide-react" 16 | 17 | export default function JoinPage() { 18 | const ways = [ 19 | { 20 | icon: PenSquare, 21 | title: "分享文章", 22 | description: "通过 GitHub PR 提交 v0.app 使用教程、技巧分享或最佳实践文章", 23 | action: "查看指南", 24 | href: "#contribute", 25 | color: "bg-blue-500/10 text-blue-600 dark:bg-blue-500/20 dark:text-blue-400", 26 | }, 27 | { 28 | icon: Code2, 29 | title: "提交案例", 30 | description: "通过 GitHub PR 展示你使用 v0.app 构建的项目案例", 31 | action: "查看指南", 32 | href: "#contribute", 33 | color: "bg-purple-500/10 text-purple-600 dark:bg-purple-500/20 dark:text-purple-400", 34 | }, 35 | { 36 | icon: Github, 37 | title: "GitHub 协作", 38 | description: "通过 GitHub 贡献代码、修复问题或改进文档", 39 | action: "访问 GitHub", 40 | href: "https://github.com/v0zh/", 41 | color: "bg-green-500/10 text-green-600 dark:bg-green-500/20 dark:text-green-400", 42 | }, 43 | { 44 | icon: MessageSquare, 45 | title: "参与讨论", 46 | description: "在社区论坛中回答问题、分享经验和交流想法", 47 | action: "加入讨论", 48 | href: "#", 49 | color: "bg-orange-500/10 text-orange-600 dark:bg-orange-500/20 dark:text-orange-400", 50 | }, 51 | ] 52 | 53 | const guidelines = [ 54 | { 55 | title: "内容质量", 56 | items: [ 57 | "确保内容准确、实用且易于理解", 58 | "提供清晰的代码示例和截图", 59 | "遵循 Markdown 格式规范", 60 | "注明参考来源和相关链接", 61 | ], 62 | }, 63 | { 64 | title: "社区规范", 65 | items: ["尊重他人,保持友善和专业", "避免发布广告或无关内容", "保护个人隐私和敏感信息", "遵守开源协议和版权规定"], 66 | }, 67 | { 68 | title: "技术要求", 69 | items: [ 70 | "代码示例应该可以运行", 71 | "使用最新版本的 v0.app 和相关技术", 72 | "提供必要的环境配置说明", 73 | "测试你的教程和案例", 74 | ], 75 | }, 76 | ] 77 | 78 | const process = [ 79 | { 80 | step: "1", 81 | title: "Fork 仓库", 82 | description: "在 GitHub 上 Fork v0-chinese-community 仓库到你的账号", 83 | }, 84 | { 85 | step: "2", 86 | title: "创建内容", 87 | description: "在 data/articles/ 或 data/cases/ 目录下创建 Markdown 文件", 88 | }, 89 | { 90 | step: "3", 91 | title: "提交 PR", 92 | description: "将你的更改提交到 GitHub 并创建 Pull Request", 93 | }, 94 | { 95 | step: "4", 96 | title: "社区审核", 97 | description: "维护者会在 3 个工作日内审核你的贡献", 98 | }, 99 | { 100 | step: "5", 101 | title: "合并发布", 102 | description: "通过审核后,内容将自动发布到社区网站", 103 | }, 104 | ] 105 | 106 | const benefits = [ 107 | "获得社区贡献者徽章", 108 | "在个人主页展示你的贡献", 109 | "与其他开发者建立联系", 110 | "提升个人技术影响力", 111 | "优先体验新功能", 112 | "参与社区决策", 113 | ] 114 | 115 | return ( 116 |
117 | {/* Hero Section */} 118 |
119 |
120 | 121 | 加入社区 122 | 123 |

124 | 一起共建 v0.app 中文社区 125 |

126 |

127 | 通过 GitHub 128 | 协作的方式贡献内容,无论你是初学者还是专家,都可以为社区做出贡献。分享你的知识和经验,帮助更多人了解和使用 129 | v0.app。 130 |

131 |
132 | 138 | 141 |
142 |
143 |
144 | 145 | {/* Ways to Contribute */} 146 |
147 |
148 |
149 |

贡献方式

150 |

选择适合你的方式参与社区建设

151 |
152 | 153 |
154 | {ways.map((way) => ( 155 | 156 | 157 |
158 | 159 |
160 | {way.title} 161 | {way.description} 162 |
163 | 164 | 167 | 168 |
169 | ))} 170 |
171 |
172 |
173 | 174 | {/* Detailed GitHub Contribution Guide */} 175 |
176 |
177 |
178 |

如何通过 GitHub 贡献

179 |

我们使用 GitHub 协作的方式管理社区内容

180 |
181 | 182 |
183 | {/* Article Contribution */} 184 | 185 | 186 |
187 | 188 |
189 | 贡献文章 190 | 在 data/articles/ 目录下创建 Markdown 文件 191 |
192 | 193 |
194 |

文件命名

195 | item-YYYYMMDD.md 196 |

例如:item-20250115.md

197 |
198 |
199 |

必需字段

200 |
    201 |
  • • title - 文章标题
  • 202 |
  • • description - 文章描述
  • 203 |
  • • author - 作者名字
  • 204 |
  • • category - 分类(入门教程/实战案例/进阶技巧等)
  • 205 |
  • • tags - 标签数组
  • 206 |
  • • publishedAt - 发布日期(YYYY-MM-DD)
  • 207 |
  • • readTime - 阅读时长
  • 208 |
209 |
210 | 218 |
219 |
220 | 221 | {/* Case Contribution */} 222 | 223 | 224 |
225 | 226 |
227 | 贡献案例 228 | 在 data/cases/ 目录下创建 Markdown 文件 229 |
230 | 231 |
232 |

文件命名

233 | item-YYYYMMDD.md 234 |

例如:item-20250115.md

235 |
236 |
237 |

必需字段

238 |
    239 |
  • • title - 案例标题
  • 240 |
  • • description - 案例描述
  • 241 |
  • • thumbnail - 缩略图路径
  • 242 |
  • • category - 分类(电商/营销/教育等)
  • 243 |
  • • difficulty - 难度(初级/中级/高级)
  • 244 |
  • • techStack - 技术栈数组
  • 245 |
  • • demoUrl/githubUrl - 演示或代码链接(可选)
  • 246 |
247 |
248 | 256 |
257 |
258 |
259 |
260 |
261 | 262 | {/* Contribution Process */} 263 |
264 |
265 |
266 |

贡献流程

267 |

简单五步,通过 GitHub PR 开始你的贡献之旅

268 |
269 | 270 |
271 |
272 | {/* Connection Line */} 273 |
274 | 275 |
276 | {process.map((item) => ( 277 |
278 |
279 | {item.step} 280 |
281 |
282 |

{item.title}

283 |

{item.description}

284 |
285 |
286 | ))} 287 |
288 |
289 |
290 | 291 |
292 | 298 |
299 |
300 |
301 | 302 | {/* Guidelines */} 303 |
304 |
305 |
306 |

内容规范

307 |

遵循这些规范,确保你的贡献符合社区标准

308 |
309 | 310 |
311 | {[ 312 | { 313 | title: "内容质量", 314 | items: [ 315 | "确保内容准确、实用且易于理解", 316 | "提供清晰的代码示例和截图", 317 | "遵循 Markdown 格式规范", 318 | "注明参考来源和相关链接", 319 | ], 320 | }, 321 | { 322 | title: "社区规范", 323 | items: [ 324 | "尊重他人,保持友善和专业", 325 | "避免发布广告或无关内容", 326 | "保护个人隐私和敏感信息", 327 | "遵守开源协议和版权规定", 328 | ], 329 | }, 330 | { 331 | title: "技术要求", 332 | items: [ 333 | "代码示例应该可以运行", 334 | "使用最新版本的 v0.app 和相关技术", 335 | "提供必要的环境配置说明", 336 | "测试你的教程和案例", 337 | ], 338 | }, 339 | ].map((guideline) => ( 340 | 341 | 342 | {guideline.title} 343 | 344 | 345 |
    346 | {guideline.items.map((item) => ( 347 |
  • 348 | 349 | {item} 350 |
  • 351 | ))} 352 |
353 |
354 |
355 | ))} 356 |
357 |
358 |
359 | 360 | {/* Benefits */} 361 |
362 |
363 |
364 |

贡献者权益

365 |

感谢你的贡献,我们为贡献者提供以下权益

366 |
367 | 368 |
369 | {[ 370 | "获得社区贡献者徽章", 371 | "在个人主页展示你的贡献", 372 | "与其他开发者建立联系", 373 | "提升个人技术影响力", 374 | "优先体验新功能", 375 | "参与社区决策", 376 | ].map((benefit) => ( 377 |
378 |
379 | 380 |
381 | {benefit} 382 |
383 | ))} 384 |
385 |
386 |
387 | 388 | {/* CTA Section */} 389 |
390 |
391 | 392 | 393 | 准备好开始贡献了吗? 394 | 395 | Fork 仓库,创建内容,提交 PR,与数千名开发者一起共建 v0.app 中文社区 396 | 397 | 398 | 399 | 405 | 411 | 417 | 418 | 419 |
420 |
421 |
422 | ) 423 | } 424 | --------------------------------------------------------------------------------