& {
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------