Skip to content

feat: refactor(homepage): 重构首页组件结构,使用动态加载优化性能#314

Merged
xun082 merged 1 commit intoxun082:mainfrom
Hemomoo:feat-new-homepage
Mar 12, 2026
Merged

feat: refactor(homepage): 重构首页组件结构,使用动态加载优化性能#314
xun082 merged 1 commit intoxun082:mainfrom
Hemomoo:feat-new-homepage

Conversation

@Hemomoo
Copy link
Copy Markdown
Collaborator

@Hemomoo Hemomoo commented Mar 12, 2026

将原有首页拆分为多个独立组件,实现按需加载
新增 homepage-loader 组件处理动态导入逻辑
移除未使用的类型定义和旧组件代码
优化页面结构和样式,提升用户体验

PR 描述

PR 类型

  • 🐛 Bug 修复
  • ✨ 新功能
  • 💄 UI/UX 改进
  • ♻️ 重构
  • 🚀 性能优化
  • 📝 文档更新
  • 🔄 其他

Issue 关联

Closes #

其他信息

image

将原有首页拆分为多个独立组件,实现按需加载
新增 homepage-loader 组件处理动态导入逻辑
移除未使用的类型定义和旧组件代码
优化页面结构和样式,提升用户体验
@netlify
Copy link
Copy Markdown

netlify bot commented Mar 12, 2026

Deploy Preview for ornate-blancmange-89db6b failed. Why did it fail? →

Name Link
🔨 Latest commit ec921fd
🔍 Latest deploy log https://app.netlify.com/projects/ornate-blancmange-89db6b/deploys/69b2668143e8e40008679a20

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

本次拉取请求的核心目标是对首页的架构进行全面重构,旨在通过模块化和动态加载策略显著提升页面性能和用户体验。通过将大型首页组件分解为更小、更独立的单元,并实现按需加载,减少了初始加载时间,使得应用程序更加高效和可维护。

Highlights

  • 首页组件结构重构: 重构了首页组件结构,将其拆分为多个独立组件,以提高模块化和可维护性。
  • 性能优化:动态加载: 引入了动态加载机制,通过新增 homepage-loader 组件实现首页内容的按需加载,从而优化了页面性能。
  • 移除旧代码和类型定义: 移除了旧的、未使用的类型定义文件 (types.ts) 和多个旧的首页组件 (constants.ts, contact.tsx, feature-card.tsx, features.tsx, hero.tsx),精简了代码库。
  • 更新页眉和页脚设计: 更新了页眉 (header.tsx) 和页脚 (footer.tsx) 组件的设计和实现,使其与新的首页布局保持一致,并移除了对旧 SITE_CONFIG 的依赖。
  • 新增综合性首页组件: 新增了一个名为 home.tsx 的综合性首页组件,集成了所有新设计的页面模块,包括英雄区域、品牌展示、功能优势、定价、用户评价、常见问题、数据统计和行动号召。
Changelog
  • apps/DocFlow/src/app/_components/homepage/constants.ts
    • 移除了首页相关的常量定义。
  • apps/DocFlow/src/app/_components/homepage/contact.tsx
    • 移除了旧的联系方式组件。
  • apps/DocFlow/src/app/_components/homepage/feature-card.tsx
    • 移除了旧的功能卡片组件。
  • apps/DocFlow/src/app/_components/homepage/features.tsx
    • 移除了旧的功能展示组件。
  • apps/DocFlow/src/app/_components/homepage/footer.tsx
    • 重构了页脚组件,采用了新的设计,并移除了对旧 SITE_CONFIG 的依赖。
  • apps/DocFlow/src/app/_components/homepage/header.tsx
    • 重构了页眉组件,采用了新的设计,并移除了对旧 SITE_CONFIG 的依赖,同时增加了状态管理以处理移动菜单和活跃导航项。
  • apps/DocFlow/src/app/_components/homepage/hero.tsx
    • 移除了旧的英雄区域组件。
  • apps/DocFlow/src/app/_components/homepage/home.tsx
    • 新增了一个综合性的首页组件,包含了英雄区域、品牌展示、功能优势、定价、用户评价、常见问题、数据统计和行动号召等多个新设计的模块。
  • apps/DocFlow/src/app/_components/homepage/homepage-loader.tsx
    • 新增了一个用于动态加载 home 组件的加载器。
  • apps/DocFlow/src/app/_components/homepage/index.ts
    • 更新了导出,以适应新的组件结构,现在只导出了重构后的 HeaderFooter
  • apps/DocFlow/src/app/_components/homepage/types.ts
    • 移除了旧的首页相关类型定义。
  • apps/DocFlow/src/app/page.tsx
    • 更新了页面入口,现在通过 HomepageLoader 动态加载首页内容,并移除了旧的元数据和样式注入。
  • apps/DocFlow/src/extensions/Bilibili/BilibiliDialog.tsx
    • 移除了一个不必要的注释。
Activity
  • 目前没有检测到此拉取请求的任何评论、审查或特定活动。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 PR 对首页进行了重构,通过动态加载优化了性能,这是一个很好的改进。然而,新的实现将几乎所有的首页组件和数据都整合到了一个非常庞大的 home.tsx 文件中。这与 PR 描述中“将原有首页拆分为多个独立组件”的目标背道而驰,并对代码的可维护性造成了损害。我留下了一些关于通过将组件和数据提取到单独的文件中来提高模块化程度的建议。此外,我还发现了一个硬编码版权年份的 bug,以及移除了页面元数据(Metadata)可能影响 SEO 的问题。请查看具体的审查评论。

</div>
</div>
<div className="pt-6 flex flex-col sm:flex-row items-center justify-between gap-3 text-xs text-gray-400">
<p>© 2025 {SITE.name}. 保留所有权利。</p>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

版权年份被硬编码为 2025。这在明年就会变得不正确,并且需要手动更新。应该动态生成以始终显示当前年份。

Suggested change
<p>© 2025 {SITE.name}. 保留所有权利。</p>
<p>© {new Date().getFullYear()} {SITE.name}. 保留所有权利。</p>

Comment on lines +1 to +886
'use client';

import { useState, useEffect } from 'react';
import {
FileEdit,
Menu,
X,
BarChart2,
Target,
TrendingUp,
Users,
GitBranch,
MessageSquare,
Lock,
Shield,
RefreshCw,
CheckCircle,
Plus,
Minus,
Star,
Globe,
FileText,
Github,
Twitter,
Mail,
} from 'lucide-react';

// ─── Data ─────────────────────────────────────────────────────────────────────

const SITE = {
name: 'DocFlow',
url: 'https://www.codecrack.cn',
description: '智能文档协作平台,让写作与团队协同更高效。',
};

const NAV_ITEMS = [
{ text: '功能特性', url: '#features' },
{ text: '价格方案', url: '#pricing' },
{ text: '用户评价', url: '#testimonials' },
];

const BENEFITS = [
{
title: 'AI 智能写作',
description:
'内置强大的 AI 写作助手,支持智能续写、内容改写与创意激发。无论是日报、报告还是创意内容,都能高效产出。',
mockup: 'editor' as const,
bullets: [
{
Icon: BarChart2,
title: '智能内容补全',
desc: '基于上下文理解,自动给出高质量的写作建议与续写内容。',
},
{
Icon: Target,
title: '自定义写作目标',
desc: '设定风格、语气和字数目标,让 AI 按你的要求精准输出。',
},
{
Icon: TrendingUp,
title: '多轮对话润色',
desc: '通过对话式交互持续优化文章,直到满意为止。',
},
],
},
{
title: '实时多人协作',
description:
'基于 Yjs CRDT 算法实现零冲突的实时协作编辑,支持多人同时在线修改,所有变更即时同步,团队效率倍增。',
mockup: 'collab' as const,
bullets: [
{
Icon: Users,
title: '多人同步编辑',
desc: '多位团队成员可同时编辑同一文档,操作实时可见。',
},
{
Icon: GitBranch,
title: '完整版本历史',
desc: '每次修改均自动记录,随时一键回溯到任意历史版本。',
},
{
Icon: MessageSquare,
title: '评论与反馈',
desc: '在文档任意位置添加评论,方便团队沟通与审阅。',
},
],
},
{
title: '安全可靠存储',
description:
'采用企业级数据加密方案,完善的权限管理体系,配合自动云端备份,让你的文档资产始终安全无忧。',
mockup: 'security' as const,
bullets: [
{ Icon: Lock, title: '端到端数据加密', desc: '传输与存储全程加密,你的数据只有你能看到。' },
{
Icon: Shield,
title: '细粒度权限控制',
desc: '对每个文档、每位成员精准设置查看、编辑或管理权限。',
},
{
Icon: RefreshCw,
title: '自动备份恢复',
desc: '增量备份实时运行,即使意外发生也能秒速恢复文档。',
},
],
},
];

const TIERS = [
{
name: '免费版',
price: 0,
features: [
'最多 3 个文档',
'基础 AI 写作(每月 50 次)',
'单人使用',
'云端自动保存',
'邮件支持',
],
},
{
name: '专业版',
price: 99,
features: [
'无限文档数量',
'高级 AI 写作(无限次)',
'最多 10 人协作',
'完整版本历史',
'优先客服支持',
'自定义域名',
],
},
{
name: '企业版',
price: -1,
features: [
'无限文档 & 空间',
'专属 AI 模型微调',
'无限成员数量',
'私有化部署支持',
'专属客户成功经理',
'定制化培训服务',
],
},
];

const FAQS = [
{
q: 'DocFlow 支持哪些文档格式?',
a: 'DocFlow 支持 Markdown、富文本(WYSIWYG)、导入导出 Word/PDF 格式。内置的 Tiptap 编辑器提供超过 50 种内容块,满足绝大多数文档场景需求。',
},
{
q: '实时协作最多支持多少人?',
a: '基于 Yjs CRDT 算法,理论上支持无限人数同时协作编辑。专业版支持 10 人,企业版支持无限成员,零冲突实时同步。',
},
{
q: 'AI 写作助手使用什么模型?',
a: 'DocFlow 集成了主流大语言模型(包括 GPT-4、Claude 等),并通过 RAG 知识库增强,让 AI 能结合你的文档上下文给出更精准的建议。',
},
{
q: '我的文档数据安全吗?',
a: '完全安全。所有数据在传输和存储过程中均采用 AES-256 加密。我们不会将你的数据用于模型训练,企业版还支持私有化本地部署。',
},
{
q: '可以免费试用吗?',
a: '当然!免费版永久有效,无需绑定信用卡。你可以立即注册体验基础功能,随时升级到专业版解锁全部能力。',
},
];

const TESTIMONIALS = [
{
name: '张伟',
role: '某互联网公司产品总监',
initials: '张',
message:
'DocFlow 的 AI 写作功能让我们的产品文档效率提升了 3 倍,实时协作功能更是彻底取代了我们之前繁琐的邮件来回流程。',
},
{
name: '李晓梅',
role: '独立内容创作者',
initials: '李',
message:
'用了很多编辑器,DocFlow 是唯一让我感觉"在用未来工具"的产品。AI 续写和改写功能真的极大提升了我的创作效率。',
},
{
name: '王建国',
role: '技术团队负责人',
initials: '王',
message:
'基于 Yjs 的协作体验非常丝滑,零延迟、零冲突,我们整个研发团队的文档协作效率得到了质的提升。强烈推荐!',
},
];

const STATS = [
{
title: '10 万+',
Icon: FileText,
cls: 'text-violet-600',
desc: '每日新建文档数,用户覆盖个人创作者、企业团队与高校师生。',
},
{
title: '4.9 分',
Icon: Star,
cls: 'text-yellow-500',
desc: '用户综合评分,持续保持在各平台同类产品最高水平。',
},
{
title: '50+',
Icon: Globe,
cls: 'text-green-600',
desc: '国家与地区的用户正在使用 DocFlow 管理他们的文档资产。',
},
];

const BRANDS = ['Notion', 'Stripe', 'Dropbox', 'Shopify', 'Slack', 'Linear'];

// ─── Mockups ──────────────────────────────────────────────────────────────────

function EditorMockup() {
return (
<div className="bg-white rounded-2xl shadow-xl border border-gray-200 overflow-hidden w-full max-w-sm mx-auto">
<div className="flex items-center gap-1.5 px-4 py-3 bg-gray-50 border-b border-gray-200">
<div className="w-3 h-3 rounded-full bg-red-400" />
<div className="w-3 h-3 rounded-full bg-yellow-400" />
<div className="w-3 h-3 rounded-full bg-green-400" />
<span className="ml-3 text-xs text-gray-400">AI 写作助手</span>
</div>
<div className="p-5 space-y-3">
<div className="h-4 w-3/5 rounded bg-gray-800" />
<div className="space-y-1.5">
<div className="h-2.5 w-full rounded bg-gray-200" />
<div className="h-2.5 w-11/12 rounded bg-gray-200" />
<div className="h-2.5 w-4/5 rounded bg-gray-200" />
</div>
<div className="rounded-lg bg-violet-50 border border-violet-200 p-3">
<div className="flex items-center gap-2 mb-2">
<div className="w-3 h-3 rounded-full bg-violet-500" />
<span className="text-xs font-medium text-violet-700">AI 续写建议</span>
</div>
<div className="space-y-1">
<div className="h-2 w-full rounded bg-violet-200" />
<div className="h-2 w-4/5 rounded bg-violet-200" />
</div>
<div className="flex gap-2 mt-3">
<div className="h-6 w-16 rounded-full bg-violet-500 opacity-80" />
<div className="h-6 w-16 rounded-full bg-gray-200" />
</div>
</div>
<div className="space-y-1.5">
<div className="h-2.5 w-full rounded bg-gray-200" />
<div className="h-2.5 w-11/12 rounded bg-gray-200" />
</div>
</div>
</div>
);
}

function CollabMockup() {
const users = [
{ color: '#7c3aed', name: 'A', label: '张三正在编辑第 3 段...' },
{ color: '#059669', name: 'B', label: '李四正在评论...' },
{ color: '#d97706', name: 'C', label: '王五已查看' },
];

return (
<div className="bg-white rounded-2xl shadow-xl border border-gray-200 overflow-hidden w-full max-w-sm mx-auto">
<div className="flex items-center gap-1.5 px-4 py-3 bg-gray-50 border-b border-gray-200">
<div className="w-3 h-3 rounded-full bg-red-400" />
<div className="w-3 h-3 rounded-full bg-yellow-400" />
<div className="w-3 h-3 rounded-full bg-green-400" />
<span className="ml-3 text-xs text-gray-400">实时协作 · 3 人在线</span>
<div className="ml-auto flex -space-x-1">
{users.map((u) => (
<div
key={u.name}
className="w-5 h-5 rounded-full border-2 border-white text-white text-[8px] font-bold flex items-center justify-center"
style={{ backgroundColor: u.color }}
>
{u.name}
</div>
))}
</div>
</div>
<div className="p-5 space-y-3">
<div className="h-4 w-3/5 rounded bg-gray-800" />
<div className="space-y-1.5">
<div className="h-2.5 w-full rounded bg-gray-200" />
<div className="h-2.5 w-11/12 rounded bg-gray-200" />
</div>
<div className="space-y-2 mt-4">
{users.map((u) => (
<div key={u.name} className="flex items-center gap-2">
<div
className="w-5 h-5 rounded-full text-white text-[8px] font-bold flex items-center justify-center flex-shrink-0"
style={{ backgroundColor: u.color }}
>
{u.name}
</div>
<span className="text-xs text-gray-500">{u.label}</span>
</div>
))}
</div>
<div className="flex items-center gap-1.5 p-2 bg-green-50 rounded-lg">
<div className="w-2 h-2 rounded-full bg-green-500" />
<span className="text-xs text-green-700 font-medium">所有更改已实时同步</span>
</div>
</div>
</div>
);
}

function SecurityMockup() {
const rows = [
{ label: '查看权限', on: true },
{ label: '编辑权限', on: true },
{ label: '管理权限', on: false },
{ label: '分享权限', on: false },
];

return (
<div className="bg-white rounded-2xl shadow-xl border border-gray-200 overflow-hidden w-full max-w-sm mx-auto">
<div className="flex items-center gap-1.5 px-4 py-3 bg-gray-50 border-b border-gray-200">
<div className="w-3 h-3 rounded-full bg-red-400" />
<div className="w-3 h-3 rounded-full bg-yellow-400" />
<div className="w-3 h-3 rounded-full bg-green-400" />
<span className="ml-3 text-xs text-gray-400">权限管理</span>
</div>
<div className="p-5 space-y-3">
{rows.map(({ label, on }) => (
<div
key={label}
className="flex items-center justify-between py-2 border-b border-gray-100"
>
<span className="text-sm text-gray-600">{label}</span>
<div
className={`w-10 h-5 rounded-full flex items-center px-0.5 ${on ? 'bg-violet-500 justify-end' : 'bg-gray-200 justify-start'}`}
>
<div className="w-4 h-4 rounded-full bg-white shadow" />
</div>
</div>
))}
<div className="p-2 bg-violet-50 rounded-lg">
<span className="text-xs text-violet-700 font-medium">🔒 AES-256 加密已启用</span>
</div>
</div>
</div>
);
}

function HeroMockup() {
const avatarColors = ['#7c3aed', '#059669', '#d97706'];

return (
<div className="relative w-full max-w-sm mx-auto">
<div className="bg-white rounded-2xl shadow-2xl border border-gray-200 overflow-hidden">
<div className="flex items-center gap-1.5 px-4 py-3 bg-gray-50 border-b border-gray-200">
<div className="w-3 h-3 rounded-full bg-red-400" />
<div className="w-3 h-3 rounded-full bg-yellow-400" />
<div className="w-3 h-3 rounded-full bg-green-400" />
<span className="ml-3 text-xs text-gray-400">产品需求文档.docflow</span>
</div>
<div className="px-6 py-5 space-y-3">
<div className="h-5 w-4/5 rounded bg-gray-800" />
<div className="space-y-1.5">
<div className="h-2.5 w-full rounded bg-gray-200" />
<div className="h-2.5 w-11/12 rounded bg-gray-200" />
<div className="h-2.5 w-4/5 rounded bg-gray-200" />
</div>
<div className="rounded-lg bg-violet-50 border border-violet-200 p-3">
<div className="flex items-center gap-2 mb-2">
<div className="w-4 h-4 rounded-full bg-violet-500 flex items-center justify-center">
<div className="w-2 h-2 rounded-full bg-white" />
</div>
<span className="text-xs font-medium text-violet-700">AI 建议</span>
</div>
<div className="space-y-1">
<div className="h-2 w-full rounded bg-violet-200" />
<div className="h-2 w-3/4 rounded bg-violet-200" />
</div>
</div>
</div>
<div className="flex items-center gap-1 px-6 pb-4">
{avatarColors.map((color, i) => (
<div
key={color}
className="w-6 h-6 rounded-full border-2 border-white flex items-center justify-center text-white text-[9px] font-bold"
style={{ backgroundColor: color, marginLeft: i > 0 ? '-6px' : undefined }}
>
{['A', 'B', 'C'][i]}
</div>
))}
<span className="ml-2 text-xs text-gray-400">3 人正在协作</span>
</div>
</div>
<div className="absolute -right-4 top-20 bg-white rounded-xl shadow-lg border border-gray-100 px-3 py-2 flex items-center gap-2 text-xs font-medium text-gray-700 whitespace-nowrap">
<div className="w-2 h-2 rounded-full bg-green-400" />
已实时同步
</div>
</div>
);
}

// ─── Sections ─────────────────────────────────────────────────────────────────

function SiteHeader() {
const [open, setOpen] = useState(false);
const [activeHash, setActiveHash] = useState('');

useEffect(() => {
const handleHashChange = () => {
setActiveHash(window.location.hash);
};

handleHashChange();

window.addEventListener('hashchange', handleHashChange);

return () => window.removeEventListener('hashchange', handleHashChange);
}, []);

return (
<header className="fixed top-0 left-0 right-0 z-50 bg-white/90 backdrop-blur-sm border-b border-gray-100">
<div className="px-5 max-w-7xl mx-auto flex items-center justify-between h-16">
<a href="/" className="flex items-center gap-2 font-bold text-xl text-gray-900">
<FileEdit size={24} className="text-violet-600" />
{SITE.name}
</a>
<nav className="hidden md:flex items-center gap-8">
{NAV_ITEMS.map((item) => (
<a
key={item.url}
href={item.url}
className={`text-base transition-colors ${
activeHash === item.url
? 'text-violet-600 font-medium'
: 'text-gray-600 hover:text-gray-900'
}`}
>
{item.text}
</a>
))}
</nav>
<div className="hidden md:flex items-center gap-3">
<a href="/auth" className="text-sm text-gray-600 hover:text-gray-900 px-4 py-2">
登录
</a>
<a
href="/auth"
className="text-sm font-semibold bg-violet-600 text-white px-5 py-2 rounded-full hover:bg-violet-700 transition-colors"
>
免费开始
</a>
</div>
<button
className="md:hidden p-2 text-gray-600"
onClick={() => setOpen((v) => !v)}
aria-label="菜单"
>
{open ? <X size={22} /> : <Menu size={22} />}
</button>
</div>
<div
className={`md:hidden overflow-hidden transition-all duration-300 bg-white border-t border-gray-100 ${open ? 'max-h-80 opacity-100' : 'max-h-0 opacity-0'}`}
>
<nav className="px-5 py-4 flex flex-col gap-4">
{NAV_ITEMS.map((item) => (
<a
key={item.url}
href={item.url}
onClick={() => setOpen(false)}
className={`text-base transition-colors ${
activeHash === item.url
? 'text-violet-600 font-medium'
: 'text-gray-700 hover:text-violet-600'
}`}
>
{item.text}
</a>
))}
<div className="flex flex-col gap-3 pt-2 border-t border-gray-100">
<a href="/auth" className="text-base text-gray-600">
登录
</a>
<a
href="/auth"
className="text-base font-semibold bg-violet-600 text-white px-5 py-2.5 rounded-full text-center"
>
免费开始
</a>
</div>
</nav>
</div>
</header>
);
}

function Hero() {
return (
<section className="relative pt-28 pb-16 bg-[#F3F3F5] overflow-hidden">
<div
className="absolute inset-0 opacity-40"
style={{
backgroundImage:
'linear-gradient(rgba(124,58,237,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(124,58,237,.08) 1px,transparent 1px)',
backgroundSize: '40px 40px',
}}
/>
<div className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-[#F3F3F5] to-transparent" />
<div className="relative px-5 max-w-7xl mx-auto flex flex-col lg:flex-row items-center gap-12">
<div className="flex-1 text-center lg:text-left">
<h1 className="text-4xl lg:text-6xl font-bold leading-tight text-gray-900">
智能、高效、简洁的文档协作平台
</h1>
<p className="mt-6 text-lg text-gray-600 max-w-xl mx-auto lg:mx-0 leading-relaxed">
基于 Tiptap + Yjs 构建,支持实时多人协作、AI
智能写作与丰富内容格式,让每一份文档都焕发生命力。
</p>
<div className="mt-8 flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
<a
href="/auth"
className="inline-flex items-center justify-center bg-violet-600 text-white font-semibold px-7 py-3.5 rounded-full hover:bg-violet-700 transition-colors"
>
免费开始使用
</a>
<a
href="#features"
className="inline-flex items-center justify-center bg-white text-gray-800 font-semibold px-7 py-3.5 rounded-full border border-gray-200 hover:bg-gray-50 transition-colors"
>
了解更多功能
</a>
</div>
</div>
<div className="flex-1 w-full max-w-md lg:max-w-none">
<HeroMockup />
</div>
</div>
</section>
);
}

function Logos() {
return (
<section className="py-12 bg-white border-y border-gray-100">
<div className="px-5 max-w-7xl mx-auto text-center">
<p className="text-sm font-medium text-gray-400 uppercase tracking-widest mb-8">
被全球 2000+ 团队与个人创作者信赖
</p>
<div className="flex flex-wrap items-center justify-center gap-10">
{BRANDS.map((b) => (
<span
key={b}
className="text-lg font-bold text-gray-300 hover:text-gray-400 transition-colors tracking-tight"
>
{b}
</span>
))}
</div>
</div>
</section>
);
}

function Benefits() {
const mockupMap = { editor: EditorMockup, collab: CollabMockup, security: SecurityMockup };

return (
<section id="features" className="divide-y divide-gray-100">
{BENEFITS.map((benefit, i) => {
const MockupComp = mockupMap[benefit.mockup];

return (
<div
key={benefit.title}
className={`flex flex-col lg:flex-row items-center gap-12 py-14 ${i % 2 === 0 ? 'lg:flex-row-reverse' : ''}`}
>
<div className="flex-1 space-y-6">
<h3 className="text-3xl lg:text-4xl font-bold text-gray-900">{benefit.title}</h3>
<p className="text-lg text-gray-600 leading-relaxed">{benefit.description}</p>
<div className="space-y-5">
{benefit.bullets.map((b) => (
<div key={b.title} className="flex items-start gap-4">
<div className="flex-shrink-0 w-11 h-11 rounded-xl bg-violet-100 text-violet-600 flex items-center justify-center">
<b.Icon size={22} />
</div>
<div>
<h4 className="text-lg font-semibold text-gray-900">{b.title}</h4>
<p className="text-base text-gray-500 mt-0.5">{b.desc}</p>
</div>
</div>
))}
</div>
</div>
<div className="flex-1 w-full">
<MockupComp />
</div>
</div>
);
})}
</section>
);
}

function Pricing() {
return (
<section id="pricing" className="py-16">
<h2 className="text-3xl lg:text-5xl font-bold text-gray-900 mb-3">价格方案</h2>
<p className="text-lg text-gray-600 mb-10">简单透明,无任何隐藏费用。按需选择,随时升级。</p>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 items-start">
{TIERS.map((tier, i) => {
const hl = i === 1;
const label = tier.price === 0 ? '免费注册' : tier.price === -1 ? '联系销售' : '立即升级';
const priceDisplay =
tier.price === 0 ? '免费' : tier.price === -1 ? '联系我们' : `¥${tier.price}`;

return (
<div
key={tier.name}
className={`rounded-2xl border p-8 flex flex-col gap-6 ${hl ? 'bg-violet-600 border-violet-600 shadow-2xl shadow-violet-200 scale-105' : 'bg-white border-gray-200 shadow-sm'}`}
>
{hl && (
<span className="inline-block text-xs font-semibold bg-white/20 text-white px-3 py-1 rounded-full w-fit">
最受欢迎
</span>
)}
<h3 className={`text-xl font-bold ${hl ? 'text-white' : 'text-gray-900'}`}>
{tier.name}
</h3>
<div className="flex items-end gap-1">
<span className={`text-4xl font-bold ${hl ? 'text-white' : 'text-gray-900'}`}>
{priceDisplay}
</span>
{tier.price > 0 && (
<span className={`text-base mb-1 ${hl ? 'text-violet-200' : 'text-gray-400'}`}>
/月
</span>
)}
</div>
<a
href={label === '免费注册' ? '/auth' : '#'}
className={`block text-center py-3 rounded-full font-semibold text-sm transition-colors ${hl ? 'bg-white text-violet-700 hover:bg-violet-50' : 'bg-violet-600 text-white hover:bg-violet-700'}`}
>
{label}
</a>
<ul className="space-y-3">
{tier.features.map((f) => (
<li key={f} className="flex items-start gap-3">
<CheckCircle
size={18}
className={`flex-shrink-0 mt-0.5 ${hl ? 'text-violet-200' : 'text-violet-500'}`}
/>
<span className={`text-sm ${hl ? 'text-violet-100' : 'text-gray-600'}`}>
{f}
</span>
</li>
))}
</ul>
</div>
);
})}
</div>
</section>
);
}

function Testimonials() {
return (
<section id="testimonials" className="py-16">
<h2 className="text-3xl lg:text-5xl font-bold text-gray-900 mb-3">用户真实评价</h2>
<p className="text-lg text-gray-600 mb-10">听听已经在使用 DocFlow 的用户怎么说。</p>
<div className="grid gap-8 lg:grid-cols-3">
{TESTIMONIALS.map((t) => (
<div key={t.name} className="flex flex-col gap-4 p-6 bg-gray-50 rounded-2xl">
<p className="text-base text-gray-600 leading-relaxed">"{t.message}"</p>
<div className="flex items-center gap-3 mt-auto pt-4 border-t border-gray-200">
<div className="w-10 h-10 rounded-full bg-violet-600 flex items-center justify-center text-white font-bold text-base flex-shrink-0">
{t.initials}
</div>
<div>
<p className="font-semibold text-violet-700 text-sm">{t.name}</p>
<p className="text-xs text-gray-500">{t.role}</p>
</div>
</div>
</div>
))}
</div>
</section>
);
}

function FAQ() {
const [openIdx, setOpenIdx] = useState<number | null>(null);

return (
<section id="faq" className="py-16">
<div className="grid lg:grid-cols-2 gap-12 items-start">
<div>
<h2 className="text-3xl lg:text-5xl font-bold text-gray-900">常见问题</h2>
<p className="mt-4 text-lg text-gray-600">
还有其他问题?随时联系我们:
<a href="mailto:hello@codecrack.cn" className="text-violet-600 hover:underline ml-1">
hello@codecrack.cn
</a>
</p>
</div>
<div className="space-y-3">
{FAQS.map((faq, i) => (
<div key={faq.q} className="border border-gray-200 rounded-xl overflow-hidden">
<button
className="w-full flex items-center justify-between gap-4 px-5 py-4 text-left hover:bg-gray-50 transition-colors"
onClick={() => setOpenIdx(openIdx === i ? null : i)}
>
<span className="font-semibold text-gray-900 text-base">{faq.q}</span>
{openIdx === i ? (
<Minus size={18} className="flex-shrink-0 text-violet-600" />
) : (
<Plus size={18} className="flex-shrink-0 text-gray-500" />
)}
</button>
<div
className={`overflow-hidden transition-all duration-300 ${openIdx === i ? 'max-h-48' : 'max-h-0'}`}
>
<p className="px-5 pb-4 text-base text-gray-600 leading-relaxed">{faq.a}</p>
</div>
</div>
))}
</div>
</div>
</section>
);
}

function Stats() {
return (
<section className="py-16">
<div className="grid sm:grid-cols-3 gap-8">
{STATS.map((s) => (
<div
key={s.title}
className="flex flex-col items-center text-center gap-3 p-6 rounded-2xl bg-gray-50"
>
<s.Icon size={34} className={s.cls} />
<h3 className="text-3xl font-semibold text-gray-900">{s.title}</h3>
<p className="text-base text-gray-500">{s.desc}</p>
</div>
))}
</div>
</section>
);
}

function CTA() {
return (
<section className="relative py-20 lg:py-28 rounded-3xl overflow-hidden bg-[#050a02] my-10 lg:my-20">
<div
className="absolute inset-0 opacity-20"
style={{
backgroundImage:
'linear-gradient(rgba(255,255,255,.15) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.15) 1px,transparent 1px)',
backgroundSize: '40px 40px',
}}
/>
<div
className="absolute inset-0"
style={{
background:
'radial-gradient(ellipse 80% 60% at 50% 50%,rgba(124,58,237,.25),transparent)',
}}
/>
<div className="relative text-center px-5 max-w-2xl mx-auto">
<h2 className="text-3xl lg:text-5xl font-bold text-white">
加入超过 10 万用户,开启智能写作之旅
</h2>
<p className="mt-5 text-lg text-gray-300 leading-relaxed">
你的高效文档创作之路从这里开始。立即免费注册 DocFlow,体验 AI 加持的协作编辑新方式!
</p>
<div className="mt-10 flex flex-col sm:flex-row gap-4 justify-center">
<a
href="/auth"
className="inline-flex items-center justify-center bg-violet-600 text-white font-semibold px-8 py-3.5 rounded-full hover:bg-violet-500 transition-colors"
>
立即免费注册
</a>
<a
href="#features"
className="inline-flex items-center justify-center bg-white/10 text-white font-semibold px-8 py-3.5 rounded-full hover:bg-white/20 transition-colors border border-white/20"
>
了解功能特性
</a>
</div>
</div>
</section>
);
}

function SiteFooter() {
return (
<footer className="bg-[#F3F3F5] border-t border-gray-200 pt-14 pb-8">
<div className="px-5 max-w-7xl mx-auto">
<div className="grid grid-cols-1 md:grid-cols-3 gap-10 pb-10 border-b border-gray-200">
<div>
<a href="/" className="flex items-center gap-2 font-bold text-xl text-gray-900 mb-3">
<FileEdit size={22} className="text-violet-600" />
{SITE.name}
</a>
<p className="text-sm text-gray-500 leading-relaxed max-w-xs">{SITE.description}</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 mb-4">快速导航</h4>
<ul className="space-y-2">
{NAV_ITEMS.map((item) => (
<li key={item.url}>
<a
href={item.url}
className="text-sm text-gray-500 hover:text-violet-600 transition-colors"
>
{item.text}
</a>
</li>
))}
</ul>
</div>
<div>
<h4 className="font-semibold text-gray-900 mb-4">联系我们</h4>
<a
href="mailto:hello@codecrack.cn"
className="flex items-center gap-2 text-sm text-gray-500 hover:text-violet-600 transition-colors mb-5"
>
<Mail size={16} />
hello@codecrack.cn
</a>
<div className="flex items-center gap-3">
<a
href="https://github.com/xun082/DocFlow"
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-violet-600 transition-colors"
>
<Github size={20} />
</a>
<a
href="https://twitter.com"
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-violet-600 transition-colors"
>
<Twitter size={20} />
</a>
</div>
</div>
</div>
<div className="pt-6 flex flex-col sm:flex-row items-center justify-between gap-3 text-xs text-gray-400">
<p>© 2025 {SITE.name}. 保留所有权利。</p>
<p>
由{' '}
<a href={SITE.url} className="hover:text-violet-600 transition-colors">
DocFlow Team
</a>{' '}
用心构建 ♥
</p>
</div>
</div>
</footer>
);
}

// ─── Root ─────────────────────────────────────────────────────────────────────

export default function Homepage() {
return (
<div className="min-h-screen bg-white">
<SiteHeader />
<Hero />
<Logos />
<div className="px-5 max-w-7xl mx-auto">
<Benefits />
<Pricing />
<Testimonials />
<FAQ />
<Stats />
<CTA />
</div>
<SiteFooter />
</div>
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

这个文件超过 800 行,将所有数据常量、区块组件、模拟组件都定义在了一起,形成了一个巨大的“超级组件”。这使得代码难以导航、维护和重用,也与 PR 描述中“拆分组件”的目标相悖。

强烈建议进行拆分:

  • 数据分离: 将 SITE, NAV_ITEMS, BENEFITS 等数据常量提取到独立的 constants.ts 文件中。
  • 组件拆分: 将 SiteHeader, Hero, Pricing 等每个区块拆分为独立的 .tsx 组件文件。

这样做可以显著提高代码的模块化、可读性和长期可维护性。

Comment on lines +1 to 5
import HomepageLoader from './_components/homepage/homepage-loader';

export default function Page() {
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(STRUCTURED_DATA),
}}
/>

<style
dangerouslySetInnerHTML={{
__html: `*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fade-in{animation:f .6s ease-out forwards}@keyframes f{to{opacity:1}}.fade-in{opacity:0}.fade-in-1{animation-delay:.1s}.fade-in-2{animation-delay:.2s}.fade-in-3{animation-delay:.3s}.slide-in{animation:s .25s cubic-bezier(.4,0,.2,1)}@keyframes s{from{transform:translateX(100%);opacity:0}to{transform:translateX(0);opacity:1}}@media(prefers-reduced-motion:reduce){*{animation:none!important;opacity:1!important}}`,
}}
/>

<div
className="min-h-screen bg-gradient-to-br from-white via-violet-50/20 to-purple-50/10"
style={{
background:
'linear-gradient(135deg, #fff 0%, rgba(245,243,255,.2) 50%, rgba(250,245,255,.1) 100%), radial-gradient(circle 600px at 25% 0%, rgba(167,139,250,.15), transparent), radial-gradient(circle 700px at 75% 100%, rgba(192,132,252,.12), transparent)',
}}
>
<Header />
<Hero />
<Features />
<Contact />
<Footer />
</div>
</>
);
return <HomepageLoader />;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

旧版本的页面包含了详细的元数据(title, description, keywords, openGraph 等),这对 SEO 至关重要。新版本完全移除了这些元数据。虽然首页现在是客户端渲染,但元数据仍然可以而且应该在 layout.tsx 文件中静态提供。完全删除元数据将对网站的搜索引擎排名和社交媒体分享效果产生负面影响。建议在根布局文件 app/layout.tsx 中重新添加这些元数据。

Comment on lines +1 to +2
export { default as Header } from './header';
export { default as Footer } from './footer';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这个文件导出了 HeaderFooter 组件,但新的首页实现在 home.tsx 中定义了其自己的 SiteHeaderSiteFooter 组件,并且似乎没有使用这些导出。被修改的 header.tsxfooter.tsx 文件可能是未使用的“死代码”。为了避免混淆和维护成本,请确认这些文件是否仍在使用,如果不是,请将它们移除。

@xun082 xun082 merged commit 3421556 into xun082:main Mar 12, 2026
0 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants