This commit is contained in:
Zopt 2025-09-12 17:03:28 +08:00
commit 55b08a08f4
36 changed files with 14040 additions and 0 deletions

3
.eslintrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

40
.gitignore vendored Normal file
View File

@ -0,0 +1,40 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# vscode
.vscode
.next-prod

11
.prettierrc Normal file
View File

@ -0,0 +1,11 @@
{
"singleQuote": true,
"printWidth": 100,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"jsxSingleQuote": false,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}

25
README.md Normal file
View File

@ -0,0 +1,25 @@
# Onlook Starter Template
<p align="center">
<img src="app/favicon.ico" />
</p>
This is an [Onlook](https://onlook.com/) project set up with
[Next.js](https://nextjs.org/), [TailwindCSS](https://tailwindcss.com/) and
[ShadCN](https://ui.shadcn.com).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) in Onlook to see the result.

476
app/about/page.tsx Normal file
View File

@ -0,0 +1,476 @@
'use client';
import { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { Footer } from '../../components/Footer';
export default function AboutPage() {
const [currentLang, setCurrentLang] = useState('zh-CN');
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const detectLanguage = () => {
const browserLang = navigator.language || navigator.languages[0];
if (browserLang.startsWith('zh-TW') || browserLang.startsWith('zh-HK')) {
setCurrentLang('zh-TW');
} else if (browserLang.startsWith('en')) {
setCurrentLang('en');
} else {
setCurrentLang('zh-CN');
}
setIsLoading(false);
};
detectLanguage();
}, []);
const content = {
'zh-CN': {
title: '关于我们',
subtitle: '致力于构建下一代多语言网站解决方案',
nav: {
home: '首页',
docs: '文档',
about: '关于',
contact: '联系我们',
},
mission: {
title: '我们的使命',
description:
'MultiSite 致力于为全球企业提供最先进的多语言静态网站解决方案,让跨语言沟通变得简单高效。',
},
vision: {
title: '我们的愿景',
description: '成为全球领先的多语言网站技术提供商,帮助企业轻松实现国际化业务拓展。',
},
values: {
title: '核心价值观',
items: [
{
title: '创新驱动',
description: '持续探索前沿技术,为用户提供最优质的解决方案',
},
{
title: '用户至上',
description: '以用户需求为中心,不断优化产品体验',
},
{
title: '开放合作',
description: '拥抱开源社区,与全球开发者共同成长',
},
{
title: '品质保证',
description: '严格的质量标准,确保产品稳定可靠',
},
],
},
team: {
title: '团队介绍',
description: '我们是一支充满激情的技术团队,专注于多语言网站技术的研发与创新。',
members: [
{
name: '张伟',
role: '技术总监',
description: '10年前端开发经验React/Next.js 技术专家',
},
{
name: '李娜',
role: '产品经理',
description: '专注用户体验设计,国际化产品规划专家',
},
{
name: '王强',
role: '架构师',
description: '全栈开发工程师,云原生技术专家',
},
],
},
technology: {
title: '技术优势',
items: [
{
title: 'React/Next.js',
description: '基于最新的 React 18 和 Next.js 14提供卓越的性能和开发体验',
},
{
title: '静态生成',
description: '利用 SSG 技术,实现极速加载和优秀的 SEO 表现',
},
{
title: '多语言支持',
description: '智能语言检测和切换,支持全球主要语言',
},
{
title: '响应式设计',
description: '完美适配各种设备,提供一致的用户体验',
},
],
},
},
'zh-TW': {
title: '關於我們',
subtitle: '致力於構建下一代多語言網站解決方案',
nav: {
home: '首頁',
docs: '文檔',
about: '關於',
contact: '聯繫我們',
},
mission: {
title: '我們的使命',
description:
'MultiSite 致力於為全球企業提供最先進的多語言靜態網站解決方案,讓跨語言溝通變得簡單高效。',
},
vision: {
title: '我們的願景',
description: '成為全球領先的多語言網站技術提供商,幫助企業輕鬆實現國際化業務拓展。',
},
values: {
title: '核心價值觀',
items: [
{
title: '創新驅動',
description: '持續探索前沿技術,為用戶提供最優質的解決方案',
},
{
title: '用戶至上',
description: '以用戶需求為中心,不斷優化產品體驗',
},
{
title: '開放合作',
description: '擁抱開源社區,與全球開發者共同成長',
},
{
title: '品質保證',
description: '嚴格的質量標準,確保產品穩定可靠',
},
],
},
team: {
title: '團隊介紹',
description: '我們是一支充滿激情的技術團隊,專注於多語言網站技術的研發與創新。',
members: [
{
name: '張偉',
role: '技術總監',
description: '10年前端開發經驗React/Next.js 技術專家',
},
{
name: '李娜',
role: '產品經理',
description: '專注用戶體驗設計,國際化產品規劃專家',
},
{
name: '王強',
role: '架構師',
description: '全棧開發工程師,雲原生技術專家',
},
],
},
technology: {
title: '技術優勢',
items: [
{
title: 'React/Next.js',
description: '基於最新的 React 18 和 Next.js 14提供卓越的性能和開發體驗',
},
{
title: '靜態生成',
description: '利用 SSG 技術,實現極速加載和優秀的 SEO 表現',
},
{
title: '多語言支援',
description: '智能語言檢測和切換,支援全球主要語言',
},
{
title: '響應式設計',
description: '完美適配各種設備,提供一致的用戶體驗',
},
],
},
},
en: {
title: 'About Us',
subtitle: 'Dedicated to building next-generation multi-language website solutions',
nav: {
home: 'Home',
docs: 'Docs',
about: 'About',
contact: 'Contact',
},
mission: {
title: 'Our Mission',
description:
'MultiSite is dedicated to providing the most advanced multi-language static website solutions for global enterprises, making cross-language communication simple and efficient.',
},
vision: {
title: 'Our Vision',
description:
"To become the world's leading multi-language website technology provider, helping enterprises easily achieve international business expansion.",
},
values: {
title: 'Core Values',
items: [
{
title: 'Innovation Driven',
description:
'Continuously explore cutting-edge technologies to provide users with the best solutions',
},
{
title: 'User First',
description:
'Focus on user needs and continuously optimize product experience',
},
{
title: 'Open Collaboration',
description:
'Embrace the open source community and grow together with global developers',
},
{
title: 'Quality Assurance',
description:
'Strict quality standards to ensure stable and reliable products',
},
],
},
team: {
title: 'Our Team',
description:
'We are a passionate technical team focused on the research and innovation of multi-language website technologies.',
members: [
{
name: 'Zhang Wei',
role: 'Technical Director',
description:
'10 years of frontend development experience, React/Next.js expert',
},
{
name: 'Li Na',
role: 'Product Manager',
description:
'Focus on user experience design, internationalization product planning expert',
},
{
name: 'Wang Qiang',
role: 'Architect',
description:
'Full-stack development engineer, cloud-native technology expert',
},
],
},
technology: {
title: 'Technical Advantages',
items: [
{
title: 'React/Next.js',
description:
'Based on the latest React 18 and Next.js 14, providing excellent performance and development experience',
},
{
title: 'Static Generation',
description:
'Utilizing SSG technology for ultra-fast loading and excellent SEO performance',
},
{
title: 'Multi-language Support',
description:
'Intelligent language detection and switching, supporting major global languages',
},
{
title: 'Responsive Design',
description:
'Perfect adaptation to various devices, providing consistent user experience',
},
],
},
},
};
const currentContent = content[currentLang];
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
);
}
return (
<div className="min-h-screen bg-white">
<Header
currentLang={currentLang}
setCurrentLang={setCurrentLang}
currentPage="about"
navContent={currentContent.nav}
/>
{/* Hero Section */}
<section className="bg-gradient-to-br from-blue-50 to-indigo-100 py-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
{currentContent.title}
</h1>
<p className="text-xl text-gray-600 mb-8 max-w-3xl mx-auto">
{currentContent.subtitle}
</p>
</div>
</div>
</section>
{/* Mission & Vision */}
<section className="py-16 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
<div className="text-center">
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-6">
<svg
className="w-8 h-8 text-blue-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 10V3L4 14h7v7l9-11h-7z"
/>
</svg>
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-4">
{currentContent.mission.title}
</h2>
<p className="text-gray-600 leading-relaxed">
{currentContent.mission.description}
</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
<svg
className="w-8 h-8 text-green-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-4">
{currentContent.vision.title}
</h2>
<p className="text-gray-600 leading-relaxed">
{currentContent.vision.description}
</p>
</div>
</div>
</div>
</section>
{/* Core Values */}
<section className="py-16 bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-gray-900 mb-4">
{currentContent.values.title}
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{currentContent.values.items.map((value, index) => (
<div
key={index}
className="bg-white rounded-lg p-6 shadow-sm hover:shadow-md transition-shadow"
>
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
<div className="w-6 h-6 bg-blue-600 rounded"></div>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-3">
{value.title}
</h3>
<p className="text-gray-600 text-sm leading-relaxed">
{value.description}
</p>
</div>
))}
</div>
</div>
</section>
{/* Team */}
<section className="py-16 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-gray-900 mb-4">
{currentContent.team.title}
</h2>
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
{currentContent.team.description}
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{currentContent.team.members.map((member, index) => (
<div key={index} className="text-center">
<div className="w-24 h-24 bg-gray-200 rounded-full mx-auto mb-4 flex items-center justify-center">
<svg
className="w-12 h-12 text-gray-400"
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-1">
{member.name}
</h3>
<p className="text-blue-600 font-medium mb-3">{member.role}</p>
<p className="text-gray-600 text-sm">{member.description}</p>
</div>
))}
</div>
</div>
</section>
{/* Technology */}
<section className="py-16 bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-gray-900 mb-4">
{currentContent.technology.title}
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{currentContent.technology.items.map((tech, index) => (
<div key={index} className="bg-white rounded-lg p-6 shadow-sm">
<h3 className="text-xl font-semibold text-gray-900 mb-3">
{tech.title}
</h3>
<p className="text-gray-600 leading-relaxed">{tech.description}</p>
</div>
))}
</div>
</div>
</section>
<Footer
currentLang={currentLang}
navContent={{
docs: currentContent.nav.docs,
contact: currentContent.nav.contact,
}}
/>
</div>
);
}

510
app/contact/page.tsx Normal file
View File

@ -0,0 +1,510 @@
'use client';
import { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { Footer } from '../../components/Footer';
export default function ContactPage() {
const [currentLang, setCurrentLang] = useState('zh-CN');
const [isLoading, setIsLoading] = useState(true);
const [formData, setFormData] = useState({
name: '',
email: '',
company: '',
message: '',
});
useEffect(() => {
const detectLanguage = () => {
const browserLang = navigator.language || navigator.languages[0];
if (browserLang.startsWith('zh-TW') || browserLang.startsWith('zh-HK')) {
setCurrentLang('zh-TW');
} else if (browserLang.startsWith('en')) {
setCurrentLang('en');
} else {
setCurrentLang('zh-CN');
}
setIsLoading(false);
};
detectLanguage();
}, []);
const content = {
'zh-CN': {
title: '联系我们',
subtitle: '我们很乐意为您提供帮助和支持',
nav: {
home: '首页',
docs: '文档',
about: '关于',
contact: '联系我们',
},
form: {
title: '发送消息',
name: '姓名',
email: '邮箱',
company: '公司',
message: '消息',
submit: '发送消息',
namePlaceholder: '请输入您的姓名',
emailPlaceholder: '请输入您的邮箱地址',
companyPlaceholder: '请输入您的公司名称(可选)',
messagePlaceholder: '请描述您的需求或问题...',
},
contact: {
title: '联系方式',
email: {
title: '邮箱',
value: 'contact@multisite.com',
},
phone: {
title: '电话',
value: '+86 400-123-4567',
},
address: {
title: '地址',
value: '北京市朝阳区科技园区创新大厦 A 座 1001 室',
},
hours: {
title: '工作时间',
value: '周一至周五 9:00 - 18:00',
},
},
support: {
title: '技术支持',
items: [
{
title: '在线文档',
description: '查看详细的使用指南和 API 文档',
link: '/docs',
},
{
title: 'GitHub',
description: '访问我们的开源项目和代码示例',
link: 'https://github.com',
},
{
title: '社区论坛',
description: '与其他开发者交流经验和解决方案',
link: '#',
},
],
},
},
'zh-TW': {
title: '聯繫我們',
subtitle: '我們很樂意為您提供幫助和支援',
nav: {
home: '首頁',
docs: '文檔',
about: '關於',
contact: '聯繫我們',
},
form: {
title: '發送消息',
name: '姓名',
email: '郵箱',
company: '公司',
message: '消息',
submit: '發送消息',
namePlaceholder: '請輸入您的姓名',
emailPlaceholder: '請輸入您的郵箱地址',
companyPlaceholder: '請輸入您的公司名稱(可選)',
messagePlaceholder: '請描述您的需求或問題...',
},
contact: {
title: '聯繫方式',
email: {
title: '郵箱',
value: 'contact@multisite.com',
},
phone: {
title: '電話',
value: '+86 400-123-4567',
},
address: {
title: '地址',
value: '北京市朝陽區科技園區創新大廈 A 座 1001 室',
},
hours: {
title: '工作時間',
value: '週一至週五 9:00 - 18:00',
},
},
support: {
title: '技術支援',
items: [
{
title: '在線文檔',
description: '查看詳細的使用指南和 API 文檔',
link: '/docs',
},
{
title: 'GitHub',
description: '訪問我們的開源項目和代碼示例',
link: 'https://github.com',
},
{
title: '社區論壇',
description: '與其他開發者交流經驗和解決方案',
link: '#',
},
],
},
},
en: {
title: 'Contact Us',
subtitle: "We'd love to help and support you",
nav: {
home: 'Home',
docs: 'Docs',
about: 'About',
contact: 'Contact',
},
form: {
title: 'Send Message',
name: 'Name',
email: 'Email',
company: 'Company',
message: 'Message',
submit: 'Send Message',
namePlaceholder: 'Enter your name',
emailPlaceholder: 'Enter your email address',
companyPlaceholder: 'Enter your company name (optional)',
messagePlaceholder: 'Describe your needs or questions...',
},
contact: {
title: 'Contact Information',
email: {
title: 'Email',
value: 'contact@multisite.com',
},
phone: {
title: 'Phone',
value: '+86 400-123-4567',
},
address: {
title: 'Address',
value: 'Room 1001, Building A, Innovation Tower, Tech Park, Chaoyang District, Beijing',
},
hours: {
title: 'Business Hours',
value: 'Monday - Friday 9:00 AM - 6:00 PM',
},
},
support: {
title: 'Technical Support',
items: [
{
title: 'Documentation',
description: 'View detailed usage guides and API documentation',
link: '/docs',
},
{
title: 'GitHub',
description: 'Access our open source projects and code examples',
link: 'https://github.com',
},
{
title: 'Community Forum',
description: 'Exchange experiences and solutions with other developers',
link: '#',
},
],
},
},
};
const currentContent = content[currentLang];
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// 这里可以添加表单提交逻辑
console.log('Form submitted:', formData);
alert(currentLang === 'en' ? 'Message sent successfully!' : '消息发送成功!');
};
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
);
}
return (
<div className="min-h-screen bg-white">
<Header
currentLang={currentLang}
setCurrentLang={setCurrentLang}
currentPage="contact"
navContent={currentContent.nav}
/>
{/* Hero Section */}
<section className="bg-gradient-to-br from-blue-50 to-indigo-100 py-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
{currentContent.title}
</h1>
<p className="text-xl text-gray-600 mb-8 max-w-3xl mx-auto">
{currentContent.subtitle}
</p>
</div>
</div>
</section>
{/* Main Content */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
{/* Contact Form */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
<h2 className="text-2xl font-bold text-gray-900 mb-6">
{currentContent.form.title}
</h2>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label
htmlFor="name"
className="block text-sm font-medium text-gray-700 mb-2"
>
{currentContent.form.name}
</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleInputChange}
placeholder={currentContent.form.namePlaceholder}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700 mb-2"
>
{currentContent.form.email}
</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleInputChange}
placeholder={currentContent.form.emailPlaceholder}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
<div>
<label
htmlFor="company"
className="block text-sm font-medium text-gray-700 mb-2"
>
{currentContent.form.company}
</label>
<input
type="text"
id="company"
name="company"
value={formData.company}
onChange={handleInputChange}
placeholder={currentContent.form.companyPlaceholder}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div>
<label
htmlFor="message"
className="block text-sm font-medium text-gray-700 mb-2"
>
{currentContent.form.message}
</label>
<textarea
id="message"
name="message"
rows={4}
value={formData.message}
onChange={handleInputChange}
placeholder={currentContent.form.messagePlaceholder}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
></textarea>
</div>
<button
type="submit"
className="w-full bg-blue-600 text-white px-6 py-3 rounded-md font-medium hover:bg-blue-700 transition-colors"
>
{currentContent.form.submit}
</button>
</form>
</div>
{/* Contact Information */}
<div className="space-y-8">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
<h2 className="text-2xl font-bold text-gray-900 mb-6">
{currentContent.contact.title}
</h2>
<div className="space-y-6">
<div className="flex items-start">
<div className="flex-shrink-0">
<svg
className="w-6 h-6 text-blue-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
/>
</svg>
</div>
<div className="ml-4">
<h3 className="text-lg font-medium text-gray-900">
{currentContent.contact.email.title}
</h3>
<p className="text-gray-600">
{currentContent.contact.email.value}
</p>
</div>
</div>
<div className="flex items-start">
<div className="flex-shrink-0">
<svg
className="w-6 h-6 text-blue-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"
/>
</svg>
</div>
<div className="ml-4">
<h3 className="text-lg font-medium text-gray-900">
{currentContent.contact.phone.title}
</h3>
<p className="text-gray-600">
{currentContent.contact.phone.value}
</p>
</div>
</div>
<div className="flex items-start">
<div className="flex-shrink-0">
<svg
className="w-6 h-6 text-blue-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</div>
<div className="ml-4">
<h3 className="text-lg font-medium text-gray-900">
{currentContent.contact.address.title}
</h3>
<p className="text-gray-600">
{currentContent.contact.address.value}
</p>
</div>
</div>
<div className="flex items-start">
<div className="flex-shrink-0">
<svg
className="w-6 h-6 text-blue-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<div className="ml-4">
<h3 className="text-lg font-medium text-gray-900">
{currentContent.contact.hours.title}
</h3>
<p className="text-gray-600">
{currentContent.contact.hours.value}
</p>
</div>
</div>
</div>
</div>
{/* Technical Support */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
<h2 className="text-2xl font-bold text-gray-900 mb-6">
{currentContent.support.title}
</h2>
<div className="space-y-4">
{currentContent.support.items.map((item, index) => (
<div key={index} className="border-l-4 border-blue-600 pl-4">
<h3 className="text-lg font-medium text-gray-900 mb-1">
{item.title}
</h3>
<p className="text-gray-600 text-sm mb-2">
{item.description}
</p>
<a
href={item.link}
className="text-blue-600 hover:text-blue-700 text-sm font-medium"
>
{currentLang === 'en' ? 'Learn more →' : '了解更多 →'}
</a>
</div>
))}
</div>
</div>
</div>
</div>
</div>
<Footer
currentLang={currentLang}
navContent={{
docs: currentContent.nav.docs,
contact: currentContent.nav.contact,
}}
/>
</div>
);
}

View File

@ -0,0 +1,114 @@
'use client';
import { useState, useEffect } from 'react';
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { ContentItem } from '@/lib/content';
import { Header } from '@/components/Header';
import { Footer } from '@/components/Footer';
import Link from 'next/link';
interface DocDetailProps {
doc: ContentItem | null;
relatedDocs: ContentItem[];
currentLang: string;
}
export default function DocDetail({ doc, relatedDocs, currentLang }: DocDetailProps) {
const [lang, setLang] = useState(currentLang);
// 你原有的语言检测逻辑也可以放在这里
useEffect(() => {
const browserLang = navigator.language;
if (browserLang.startsWith('zh-TW') || browserLang.startsWith('zh-HK')) {
setLang('zh-TW');
} else if (browserLang.startsWith('en')) {
setLang('en');
} else {
setLang('zh-CN');
}
}, []);
if (!doc) {
return (
<div className="min-h-screen flex items-center justify-center">
<h1 className="text-2xl font-bold"></h1>
</div>
);
}
// 你原先的多语言文本映射
const contentMap = {
'zh-CN': { backTo: '← 返回文档', related: '相关文档' },
'zh-TW': { backTo: '← 返回文檔', related: '相關文檔' },
en: { backTo: '← Back to Docs', related: 'Related Documents' },
};
const labels = contentMap[lang] || contentMap['zh-CN'];
return (
<div className="min-h-screen bg-white">
<Header
currentLang={lang}
setCurrentLang={setLang}
currentPage="docs"
navContent={{ docs: labels.backTo /* ... */ }}
/>
<div className="max-w-4xl mx-auto px-4 py-8">
<Link href="/docs" className="text-blue-600 hover:underline mb-4 inline-block">
{labels.backTo}
</Link>
<article className="prose prose-lg">
<h1>{doc.metadata.title}</h1>
<p className="text-gray-600">{doc.metadata.description}</p>
<ReactMarkdown
components={{
code({ inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighter
style={tomorrow}
language={match[1]}
PreTag="div"
{...props}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{doc.content}
</ReactMarkdown>
</article>
{relatedDocs.length > 0 && (
<section className="mt-12">
<h2 className="text-xl font-semibold mb-4">{labels.related}</h2>
<ul className="space-y-2">
{relatedDocs.map((item) => (
<li key={item.slug}>
<Link
href={`/docs/${item.slug}`}
className="text-blue-600 hover:underline"
>
{item.metadata.title}
</Link>
</li>
))}
</ul>
</section>
)}
</div>
<Footer currentLang={lang} navContent={{ docs: labels.backTo /* ... */ }} />
</div>
);
}

28
app/docs/[slug]/page.tsx Normal file
View File

@ -0,0 +1,28 @@
import { getContentData, getAllContent, ContentItem } from '@/lib/content';
import DocDetail from './DocDetail';
interface PageProps {
params: { slug: string };
}
export default async function Page({ params: { slug } }: PageProps) {
// 你可以根据需要改为从 headers、cookies 或其它方式获取语言
const currentLang = 'zh-CN';
let doc: ContentItem | null;
let related: ContentItem[];
try {
doc = getContentData(currentLang, slug);
const all = getAllContent(currentLang);
related = all
.filter(
(item) => item.metadata.category === doc.metadata.category && item.slug !== slug,
)
.slice(0, 3);
} catch {
doc = null;
related = [];
}
return <DocDetail doc={doc} relatedDocs={related} currentLang={currentLang} />;
}

369
app/docs/page.tsx Normal file
View File

@ -0,0 +1,369 @@
'use client';
import { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { Footer } from '../../components/Footer';
import { DocumentList } from '../../components/DocumentList';
import { ContentItem } from '../../lib/content';
import Link from 'next/link';
export default function DocsPage() {
const [currentLang, setCurrentLang] = useState('zh-CN');
const [isLoading, setIsLoading] = useState(true);
const [documents, setDocuments] = useState<ContentItem[]>([]);
useEffect(() => {
const detectLanguage = () => {
const browserLang = navigator.language || navigator.languages[0];
if (browserLang.startsWith('zh-TW') || browserLang.startsWith('zh-HK')) {
setCurrentLang('zh-TW');
} else if (browserLang.startsWith('en')) {
setCurrentLang('en');
} else {
setCurrentLang('zh-CN');
}
setIsLoading(false);
};
detectLanguage();
}, []);
useEffect(() => {
const loadDocuments = () => {
// 创建模拟文档数据
const mockDocuments: ContentItem[] = [
{
slug: 'getting-started',
metadata: {
title: currentLang === 'en' ? 'Getting Started' : '快速开始',
description:
currentLang === 'en'
? 'Learn how to quickly get started with MultiSite for building multi-language websites'
: '学习如何快速开始使用 MultiSite 构建多语言网站',
date: '2024-01-15',
author: currentLang === 'en' ? 'MultiSite Team' : 'MultiSite 团队',
category: currentLang === 'en' ? 'Getting Started' : '入门指南',
tags:
currentLang === 'en'
? ['getting-started', 'installation', 'setup']
: ['快速开始', '安装', '设置'],
},
content: '',
},
{
slug: 'configuration',
metadata: {
title: currentLang === 'en' ? 'Configuration Guide' : '配置指南',
description:
currentLang === 'en'
? 'Learn how to configure MultiSite to meet your project requirements'
: '了解如何配置 MultiSite 以满足您的项目需求',
date: '2024-01-16',
author: currentLang === 'en' ? 'MultiSite Team' : 'MultiSite 团队',
category: currentLang === 'en' ? 'Configuration' : '配置',
tags:
currentLang === 'en'
? ['configuration', 'setup', 'customization']
: ['配置', '设置', '自定义'],
},
content: '',
},
{
slug: 'content-management',
metadata: {
title: currentLang === 'en' ? 'Content Management' : '内容管理',
description:
currentLang === 'en'
? 'Learn how to manage and organize your content in MultiSite'
: '学习如何在 MultiSite 中管理和组织您的内容',
date: '2024-01-17',
author: currentLang === 'en' ? 'MultiSite Team' : 'MultiSite 团队',
category: currentLang === 'en' ? 'Content' : '内容',
tags:
currentLang === 'en'
? ['content', 'management', 'markdown']
: ['内容', '管理', 'markdown'],
},
content: '',
},
{
slug: 'deployment',
metadata: {
title: currentLang === 'en' ? 'Deployment Guide' : '部署指南',
description:
currentLang === 'en'
? 'Learn how to deploy your MultiSite project to production'
: '了解如何将您的 MultiSite 项目部署到生产环境',
date: '2024-01-18',
author: currentLang === 'en' ? 'MultiSite Team' : 'MultiSite 团队',
category: currentLang === 'en' ? 'Deployment' : '部署',
tags:
currentLang === 'en'
? ['deployment', 'production', 'CI/CD']
: ['部署', '生产', 'CI/CD'],
},
content: '',
},
];
setDocuments(mockDocuments);
};
if (!isLoading) {
loadDocuments();
}
}, [currentLang, isLoading]);
const content = {
'zh-CN': {
title: '文档',
subtitle: '完整的多语言静态站点开发指南',
description: '了解如何使用我们的多语言静态站点解决方案构建现代化的网站。',
nav: {
home: '首页',
docs: '文档',
about: '关于',
contact: '联系我们',
},
},
'zh-TW': {
title: '文檔',
subtitle: '完整的多語言靜態站點開發指南',
description: '了解如何使用我們的多語言靜態站點解決方案構建現代化的網站。',
nav: {
home: '首頁',
docs: '文檔',
about: '關於',
contact: '聯繫我們',
},
},
en: {
title: 'Documentation',
subtitle: 'Complete guide for multi-language static site development',
description:
'Learn how to build modern websites using our multi-language static site solution.',
nav: {
home: 'Home',
docs: 'Docs',
about: 'About',
contact: 'Contact',
},
},
};
const currentContent = content[currentLang];
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
);
}
return (
<div className="min-h-screen bg-white">
<header className="bg-white shadow-sm border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<div className="flex items-center">
<div className="h-8 w-8 bg-blue-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-sm">MS</span>
</div>
<div className="ml-4">
<span className="text-xl font-semibold text-gray-900">
MultiSite
</span>
</div>
</div>
<nav className="hidden md:flex space-x-8">
<Link
href="/"
className="text-gray-700 hover:text-blue-600 px-3 py-2 text-sm font-medium"
>
{currentLang === 'en' ? 'Home' : '首页'}
</Link>
<Link
href="/docs"
className="text-blue-600 px-3 py-2 text-sm font-medium border-b-2 border-blue-600"
>
{currentLang === 'en' ? 'Docs' : '文档'}
</Link>
<Link
href="/about"
className="text-gray-700 hover:text-blue-600 px-3 py-2 text-sm font-medium"
>
{currentLang === 'en' ? 'About' : '关于'}
</Link>
<Link
href="/contact"
className="text-gray-700 hover:text-blue-600 px-3 py-2 text-sm font-medium"
>
{currentLang === 'en' ? 'Contact' : '联系我们'}
</Link>
</nav>
<div className="flex items-center space-x-4">
<select
value={currentLang}
onChange={(e) => setCurrentLang(e.target.value)}
className="appearance-none bg-white border border-gray-300 rounded-md px-3 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="zh-CN"></option>
<option value="zh-TW"></option>
<option value="en">English</option>
</select>
</div>
</div>
</div>
</header>
<section className="bg-gradient-to-br from-blue-50 to-indigo-100 py-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
{currentContent.title}
</h1>
<p className="text-xl text-gray-600 mb-8 max-w-3xl mx-auto">
{currentContent.subtitle}
</p>
<p className="text-lg text-gray-500 max-w-2xl mx-auto">
{currentContent.description}
</p>
</div>
</div>
</section>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
{/* Quick Start Section */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-8 mb-12">
<h2 className="text-2xl font-bold text-gray-900 mb-6">
{currentLang === 'en' ? 'Getting Started' : '快速开始'}
</h2>
<div className="prose prose-lg max-w-none">
<p className="text-gray-600 mb-4">
{currentLang === 'en'
? 'Welcome to the MultiSite documentation. This guide will help you get started with building multi-language static websites.'
: '欢迎使用 MultiSite 文档。本指南将帮助您开始构建多语言静态网站。'}
</p>
<h3 className="text-xl font-semibold text-gray-800 mb-3">
{currentLang === 'en' ? 'Features' : '特性'}
</h3>
<ul className="list-disc list-inside text-gray-600 space-y-2">
<li>
{currentLang === 'en' ? 'Multi-language support' : '多语言支持'}
</li>
<li>
{currentLang === 'en' ? 'Static site generation' : '静态站点生成'}
</li>
<li>{currentLang === 'en' ? 'SEO optimization' : 'SEO 优化'}</li>
<li>{currentLang === 'en' ? 'Responsive design' : '响应式设计'}</li>
</ul>
</div>
</div>
{/* Documentation Cards Section */}
<div className="mb-12">
<div className="flex items-center justify-between mb-8">
<h2 className="text-3xl font-bold text-gray-900">
{currentLang === 'en' ? 'Documentation' : '文档列表'}
</h2>
<div className="text-sm text-gray-500">
{currentLang === 'en'
? `${documents.length} documents available`
: `${documents.length} 篇文档`}
</div>
</div>
{isLoading ? (
<div className="flex items-center justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span className="ml-3 text-gray-600">
{currentLang === 'en' ? 'Loading documents...' : '加载文档中...'}
</span>
</div>
) : (
<DocumentList documents={documents} currentLang={currentLang} />
)}
</div>
</div>
<footer className="bg-gray-900 text-white py-12 mt-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
<div className="col-span-1 md:col-span-2">
<div className="flex items-center mb-4">
<div className="h-8 w-8 bg-blue-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-sm">MS</span>
</div>
<span className="ml-3 text-xl font-semibold">MultiSite</span>
</div>
<p className="text-gray-400 mb-4">
{currentLang === 'en'
? 'Enterprise-grade multi-language static site solution powered by React/Next.js'
: '基于 React/Next.js 的企业级多语言静态站点解决方案'}
</p>
</div>
<div>
<h3 className="text-lg font-semibold mb-4">
{currentLang === 'en' ? 'Resources' : '资源'}
</h3>
<ul className="space-y-2 text-gray-400">
<li>
<Link href="/docs" className="hover:text-white transition-colors">
{currentLang === 'en' ? 'Docs' : '文档'}
</Link>
</li>
<li>
<Link href="#" className="hover:text-white transition-colors">
API
</Link>
</li>
<li>
<Link href="#" className="hover:text-white transition-colors">
{currentLang === 'en' ? 'Examples' : '示例'}
</Link>
</li>
</ul>
</div>
<div>
<h3 className="text-lg font-semibold mb-4">
{currentLang === 'en' ? 'Support' : '支持'}
</h3>
<ul className="space-y-2 text-gray-400">
<li>
<Link
href="/contact"
className="hover:text-white transition-colors"
>
{currentLang === 'en' ? 'Contact' : '联系我们'}
</Link>
</li>
<li>
<Link href="#" className="hover:text-white transition-colors">
GitHub
</Link>
</li>
<li>
<Link href="#" className="hover:text-white transition-colors">
{currentLang === 'en' ? 'Community' : '社区'}
</Link>
</li>
</ul>
</div>
</div>
<div className="border-t border-gray-800 mt-8 pt-8 text-center text-gray-400">
<p>
&copy; 2024 MultiSite.{' '}
{currentLang === 'en' ? 'All rights reserved.' : '保留所有权利。'}
</p>
</div>
</div>
</footer>
</div>
);
}

BIN
app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

88
app/globals.css Normal file
View File

@ -0,0 +1,88 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

18
app/layout.tsx Normal file
View File

@ -0,0 +1,18 @@
import type { Metadata } from 'next';
import './globals.css';
import Script from 'next/script';
export const metadata: Metadata = {
title: 'MultiSite - 企业级多语言静态站点解决方案',
description: '基于 React/Next.js + gray-matter 构建的高性能多语言静态网站',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN">
<body className="">
{children}
<Script src="/builtwith.js" strategy="afterInteractive" />
</body>
</html>
);
}

237
app/page.tsx Normal file
View File

@ -0,0 +1,237 @@
'use client';
import Link from 'next/link';
import { useState, useEffect } from 'react';
import { Header } from '../components/Header';
import { Footer } from '../components/Footer';
export default function Page() {
const [currentLang, setCurrentLang] = useState('zh-CN');
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Simulate language detection from Accept-Language header
const detectLanguage = () => {
const browserLang = navigator.language || navigator.languages[0];
if (browserLang.startsWith('zh-TW') || browserLang.startsWith('zh-HK')) {
setCurrentLang('zh-TW');
} else if (browserLang.startsWith('en')) {
setCurrentLang('en');
} else {
setCurrentLang('zh-CN');
}
setIsLoading(false);
};
detectLanguage();
}, []);
const languages = {
'zh-CN': {
title: '企业级多语言静态站点解决方案',
subtitle: '基于 React/Next.js + gray-matter 构建的高性能多语言静态网站',
description:
'支持简体中文、繁体中文、英文三种语言,提供完整的 SEO 优化、动态路由和静态生成功能',
features: [
'多语言动态路由系统',
'SEO 友好的静态生成',
'智能语言检测与切换',
'自动站点地图生成',
],
cta: '立即开始',
learnMore: '了解更多',
nav: {
home: '首页',
docs: '文档',
about: '关于',
contact: '联系我们',
},
},
'zh-TW': {
title: '企業級多語言靜態站點解決方案',
subtitle: '基於 React/Next.js + gray-matter 構建的高性能多語言靜態網站',
description:
'支援簡體中文、繁體中文、英文三種語言,提供完整的 SEO 優化、動態路由和靜態生成功能',
features: [
'多語言動態路由系統',
'SEO 友好的靜態生成',
'智能語言檢測與切換',
'自動站點地圖生成',
],
cta: '立即開始',
learnMore: '了解更多',
nav: {
home: '首頁',
docs: '文檔',
about: '關於',
contact: '聯繫我們',
},
},
en: {
title: 'Enterprise Multi-language Static Site Solution',
subtitle:
'High-performance multi-language static website built with React/Next.js + gray-matter',
description:
'Supports Simplified Chinese, Traditional Chinese, and English with complete SEO optimization, dynamic routing, and static generation',
features: [
'Multi-language Dynamic Routing',
'SEO-friendly Static Generation',
'Intelligent Language Detection',
'Automatic Sitemap Generation',
],
cta: 'Get Started',
learnMore: 'Learn More',
nav: {
home: 'Home',
docs: 'Docs',
about: 'About',
contact: 'Contact',
},
},
};
const currentContent = languages[currentLang];
const switchLanguage = (lang) => {
setCurrentLang(lang);
// In real implementation, this would redirect to /{lang}/
// window.location.href = `/${lang}/`;
};
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
);
}
return (
<div className="min-h-screen bg-white">
<Header
currentLang={currentLang}
setCurrentLang={setCurrentLang}
currentPage="home"
navContent={currentContent.nav}
/>
{/* Hero Section */}
<section className="bg-gradient-to-br from-blue-50 to-indigo-100 py-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h1 className="text-4xl md:text-6xl font-bold text-gray-900 mb-6">
{currentContent.title}
</h1>
<p className="text-xl md:text-2xl text-gray-600 mb-8 max-w-4xl mx-auto">
{currentContent.subtitle}
</p>
<p className="text-lg text-gray-500 mb-12 max-w-3xl mx-auto">
{currentContent.description}
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<button className="bg-blue-600 text-white px-8 py-3 rounded-lg text-lg font-medium hover:bg-blue-700 transition-colors shadow-lg">
{currentContent.cta}
</button>
<Link
href="/docs"
className="border border-gray-300 text-gray-700 px-8 py-3 rounded-lg text-lg font-medium hover:bg-gray-50 transition-colors inline-block text-center"
>
{currentContent.learnMore}
</Link>
</div>
</div>
</div>
</section>
{/* Features Section */}
<section className="py-20 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{currentContent.features.map((feature, index) => (
<div
key={index}
className="text-center p-6 rounded-lg border border-gray-200 hover:shadow-lg transition-shadow"
>
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mx-auto mb-4">
<div className="w-6 h-6 bg-blue-600 rounded"></div>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">
{feature}
</h3>
<p className="text-gray-600 text-sm">
{currentLang === 'en'
? 'Advanced functionality for modern web applications'
: '为现代网络应用提供先进功能'}
</p>
</div>
))}
</div>
</div>
</section>
{/* Architecture Diagram */}
<section className="py-20 bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-gray-900 mb-4">
{currentLang === 'en' ? 'System Architecture' : '系统架构'}
</h2>
</div>
<div className="bg-white rounded-lg shadow-lg p-8">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="text-center">
<div className="bg-blue-100 rounded-lg p-6 mb-4">
<div className="text-2xl font-bold text-blue-600">pages/</div>
<div className="text-sm text-gray-600 mt-2">
[lang]/[...slug].tsx
</div>
</div>
<p className="text-sm text-gray-700">
{currentLang === 'en' ? 'Dynamic Routing' : '动态路由'}
</p>
</div>
<div className="text-center">
<div className="bg-green-100 rounded-lg p-6 mb-4">
<div className="text-2xl font-bold text-green-600">
content/
</div>
<div className="text-sm text-gray-600 mt-2">
zh-CN/ zh-TW/ en/
</div>
</div>
<p className="text-sm text-gray-700">
{currentLang === 'en' ? 'Content Management' : '内容管理'}
</p>
</div>
<div className="text-center">
<div className="bg-purple-100 rounded-lg p-6 mb-4">
<div className="text-2xl font-bold text-purple-600">
public/
</div>
<div className="text-sm text-gray-600 mt-2">sitemap-*.xml</div>
</div>
<p className="text-sm text-gray-700">
{currentLang === 'en' ? 'SEO Optimization' : 'SEO优化'}
</p>
</div>
</div>
</div>
</div>
</section>
<Footer
currentLang={currentLang}
navContent={{
docs: currentContent.nav.docs,
contact: currentContent.nav.contact,
}}
/>
</div>
);
}

BIN
bun.lockb Normal file

Binary file not shown.

20
components.json Normal file
View File

@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}

182
components/DocumentList.tsx Normal file
View File

@ -0,0 +1,182 @@
'use client';
import { ContentItem } from '../lib/content';
interface DocumentListProps {
documents: ContentItem[];
currentLang: string;
}
export function DocumentList({ documents, currentLang }: DocumentListProps) {
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleDateString(currentLang === 'en' ? 'en-US' : 'zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
};
const getCategoryColor = (category: string) => {
const colors = {
: 'bg-green-100 text-green-800',
: 'bg-blue-100 text-blue-800',
: 'bg-purple-100 text-purple-800',
: 'bg-orange-100 text-orange-800',
'Getting Started': 'bg-green-100 text-green-800',
Configuration: 'bg-blue-100 text-blue-800',
Content: 'bg-purple-100 text-purple-800',
Deployment: 'bg-orange-100 text-orange-800',
};
return colors[category] || 'bg-gray-100 text-gray-800';
};
if (documents.length === 0) {
return (
<div className="text-center py-12">
<div className="text-gray-400 mb-4">
<svg
className="w-16 h-16 mx-auto"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1}
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
</div>
<p className="text-gray-500">
{currentLang === 'en' ? 'No documents found' : '暂无文档'}
</p>
</div>
);
}
return (
<div className="space-y-6">
{documents.map((doc) => (
<article
key={doc.slug}
className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-shadow"
>
<div className="flex items-start justify-between mb-4">
<div className="flex-1">
<h2 className="text-xl font-semibold text-gray-900 mb-2">
<a
href={`/docs/${doc.slug}`}
className="hover:text-blue-600 transition-colors"
>
{doc.metadata.title}
</a>
</h2>
<p className="text-gray-600 mb-3 leading-relaxed">
{doc.metadata.description}
</p>
</div>
<span
className={`px-3 py-1 rounded-full text-xs font-medium ${getCategoryColor(doc.metadata.category)}`}
>
{doc.metadata.category}
</span>
</div>
<div className="flex items-center justify-between text-sm text-gray-500">
<div className="flex items-center space-x-4">
<span className="flex items-center">
<svg
className="w-4 h-4 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
{doc.metadata.author}
</span>
<span className="flex items-center">
<svg
className="w-4 h-4 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3a1 1 0 011-1h6a1 1 0 011 1v4h3a1 1 0 011 1v9a2 2 0 01-2 2H5a2 2 0 01-2-2V8a1 1 0 011-1h3z"
/>
</svg>
{formatDate(doc.metadata.date)}
</span>
</div>
{doc.metadata.tags && doc.metadata.tags.length > 0 && (
<div className="flex items-center space-x-2">
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"
/>
</svg>
<div className="flex flex-wrap gap-1">
{doc.metadata.tags.slice(0, 3).map((tag) => (
<span
key={tag}
className="px-2 py-1 bg-gray-100 text-gray-600 rounded text-xs"
>
{tag}
</span>
))}
{doc.metadata.tags.length > 3 && (
<span className="text-gray-400 text-xs">
+{doc.metadata.tags.length - 3}
</span>
)}
</div>
</div>
)}
</div>
<div className="mt-4 pt-4 border-t border-gray-100">
<a
href={`/docs/${doc.slug}`}
className="inline-flex items-center text-blue-600 hover:text-blue-700 font-medium text-sm"
>
{currentLang === 'en' ? 'Read more' : '阅读更多'}
<svg
className="w-4 h-4 ml-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</a>
</div>
</article>
))}
</div>
);
}

88
components/Footer.tsx Normal file
View File

@ -0,0 +1,88 @@
'use client';
import Link from "next/link";
interface FooterProps {
currentLang: string;
navContent: {
docs: string;
contact: string;
};
}
export function Footer({ currentLang, navContent }: FooterProps) {
return (
<footer className="bg-gray-900 text-white py-12">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
<div className="col-span-1 md:col-span-2">
<div className="flex items-center mb-4">
<div className="h-8 w-8 bg-blue-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-sm">MS</span>
</div>
<span className="ml-3 text-xl font-semibold">MultiSite</span>
</div>
<p className="text-gray-400 mb-4">
{currentLang === 'en'
? 'Enterprise-grade multi-language static site solution powered by React/Next.js'
: '基于 React/Next.js 的企业级多语言静态站点解决方案'}
</p>
</div>
<div>
<h3 className="text-lg font-semibold mb-4">
{currentLang === 'en' ? 'Resources' : '资源'}
</h3>
<ul className="space-y-2 text-gray-400">
<li>
<Link href="/docs" className="hover:text-white transition-colors">
{navContent.docs}
</Link>
</li>
<li>
<a href="#" className="hover:text-white transition-colors">
API
</a>
</li>
<li>
<a href="#" className="hover:text-white transition-colors">
{currentLang === 'en' ? 'Examples' : '示例'}
</a>
</li>
</ul>
</div>
<div>
<h3 className="text-lg font-semibold mb-4">
{currentLang === 'en' ? 'Support' : '支持'}
</h3>
<ul className="space-y-2 text-gray-400">
<li>
<a href="/contact" className="hover:text-white transition-colors">
{navContent.contact}
</a>
</li>
<li>
<a href="#" className="hover:text-white transition-colors">
GitHub
</a>
</li>
<li>
<a href="#" className="hover:text-white transition-colors">
{currentLang === 'en' ? 'Community' : '社区'}
</a>
</li>
</ul>
</div>
</div>
<div className="border-t border-gray-800 mt-8 pt-8 text-center text-gray-400">
<p>
&copy; 2024 MultiSite.{' '}
{currentLang === 'en' ? 'All rights reserved.' : '保留所有权利。'}
</p>
</div>
</div>
</footer>
);
}

69
components/Header.tsx Normal file
View File

@ -0,0 +1,69 @@
'use client';
import Link from "next/link";
interface HeaderProps {
currentLang: string;
setCurrentLang: (lang: string) => void;
currentPage?: string;
navContent: {
home: string;
docs: string;
about: string;
contact: string;
};
}
export function Header({ currentLang, setCurrentLang, currentPage = '', navContent }: HeaderProps) {
const getNavLinkClass = (page: string) => {
return currentPage === page
? 'text-blue-600 px-3 py-2 text-sm font-medium border-b-2 border-blue-600'
: 'text-gray-700 hover:text-blue-600 px-3 py-2 text-sm font-medium';
};
return (
<header className="bg-white shadow-sm border-b border-gray-200 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<div className="flex items-center">
<div className="flex-shrink-0">
<div className="h-8 w-8 bg-blue-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-sm">MS</span>
</div>
</div>
<div className="ml-4">
<span className="text-xl font-semibold text-gray-900">MultiSite</span>
</div>
</div>
<nav className="hidden md:flex space-x-8">
<Link href="/" className={getNavLinkClass('home')}>
{navContent.home}
</Link>
<Link href="/docs" className={getNavLinkClass('docs')}>
{navContent.docs}
</Link>
<Link href="/about" className={getNavLinkClass('about')}>
{navContent.about}
</Link>
<Link href="/contact" className={getNavLinkClass('contact')}>
{navContent.contact}
</Link>
</nav>
<div className="flex items-center space-x-4">
<select
value={currentLang}
onChange={(e) => setCurrentLang(e.target.value)}
className="appearance-none bg-white border border-gray-300 rounded-md px-3 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="zh-CN"></option>
<option value="zh-TW"></option>
<option value="en">English</option>
</select>
</div>
</div>
</div>
</header>
);
}

235
content/en/configuration.md Normal file
View File

@ -0,0 +1,235 @@
---
title: '部署指南'
description: '了解如何将 MultiSite 部署到各种平台'
date: '2024-01-18'
author: 'MultiSite Team'
category: '部署'
tags: ['部署', 'Vercel', 'Netlify', '服务器']
---
# 部署指南
本指南将介绍如何将 MultiSite 部署到不同的平台和环境。
## 构建准备
在部署之前,确保您的项目已经准备就绪:
### 1. 环境变量配置
创建生产环境的环境变量:
```env:
NEXT_PUBLIC_SITE_URL=https://your-production-domain.com
NEXT_PUBLIC_DEFAULT_LANG=zh-CN
NODE_ENV=production
2. 构建测试
在本地测试生产构建:
npm run build
npm run start
Run command
3. 性能优化
确保已启用所有性能优化选项:
// next.config.mjs
const nextConfig = {
output: 'export', // 静态导出
trailingSlash: true,
images: {
unoptimized: true // 静态导出时需要
}
};
Vercel 部署
Vercel 是部署 Next.js 应用的最佳选择。
1. 通过 Git 部署
将代码推送到 GitHub/GitLab/Bitbucket
在 Vercel 创建账户
导入您的仓库
配置环境变量
部署
2. 通过 CLI 部署
# 安装 Vercel CLI
npm i -g vercel
# 登录
vercel login
# 部署
vercel --prod
Run command
3. 自定义域名
在 Vercel 控制台中:
进入项目设置
点击 "Domains"
添加您的自定义域名
配置 DNS 记录
Netlify 部署
1. 构建配置
创建 netlify.toml
[build]
command = "npm run build"
publish = "out"
[build.environment]
NODE_VERSION = "18"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
2. 部署步骤
在 Netlify 创建账户
连接您的 Git 仓库
配置构建设置
部署
传统服务器部署
1. 使用 PM2
# 安装 PM2
npm install -g pm2
# 构建项目
npm run build
# 启动应用
pm2 start npm --name "multisite" -- start
# 保存 PM2 配置
pm2 save
pm2 startup
Run command
2. Nginx 配置
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
3. SSL 配置
使用 Let's Encrypt
# 安装 Certbot
sudo apt install certbot python3-certbot-nginx
# 获取 SSL 证书
sudo certbot --nginx -d your-domain.com
# 自动续期
sudo crontab -e
# 添加: 0 12 * * * /usr/bin/certbot renew --quiet
Run command
Docker 部署
1. Dockerfile
FROM node:18-alpine AS base
# 安装依赖
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
# 构建应用
FROM base AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN npm run build
# 运行时
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
2. Docker Compose
version: '3.8'
services:
multisite:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- NEXT_PUBLIC_SITE_URL=https://your-domain.com
restart: unless-stopped
CDN 配置
1. Cloudflare
添加您的域名到 Cloudflare
配置 DNS 记录
启用 CDN 和缓存
配置 SSL/TLS
2. 缓存策略
// next.config.mjs
const nextConfig = {
async headers() {
return [
{
source: '/images/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable'
}
]
}
];
}
};
监控和分析
1. 性能监控
// 添加到 _app.tsx
export function reportWebVitals(metric) {
if (metric.label === 'web-vital') {
console.log(metric);
// 发送到分析服务
}
}
2. 错误监控
集成 Sentry
npm install @sentry/nextjs
Run command
// sentry.client.config.js
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: 'YOUR_DSN_HERE',
environment: process.env.NODE_ENV
});
部署检查清单
环境变量已配置
构建成功无错误
性能优化已启用
SSL 证书已配置
CDN 已设置
监控已集成
备份策略已制定
域名已配置
SEO 设置已完成
恭喜!您的 MultiSite 应用现在已经成功部署到生产环境。
```

View File

@ -0,0 +1,27 @@
---
title: "Getting Started"
description: "Learn how to quickly get started with MultiSite for building multi-language websites"
date: "2024-01-15"
author: "MultiSite Team"
category: "Getting Started"
tags: ["getting-started", "installation", "setup"]
---
# Getting Started
Welcome to MultiSite! This guide will help you quickly get started building multi-language static websites.
## System Requirements
Before you begin, make sure your system meets the following requirements:
- Node.js 18.0 or higher
- npm, yarn, pnpm, or bun package manager
- Git (for version control)
## Installation Steps
### 1. Clone the Project
```bash:
git clone https://github.com/your-repo/multisite.git
cd multisite

View File

@ -0,0 +1,31 @@
---
title: 'Configuration Guide'
description: 'Detailed MultiSite configuration options and best practices'
date: '2024-01-16'
author: 'MultiSite Team'
category: 'Configuration'
tags: ['configuration', 'environment', 'customization']
---
# Configuration Guide
This guide will detail how to configure MultiSite to meet your needs.
## Environment Variables
Create a `.env.local` file to configure environment variables:
```env:
# Basic site information
NEXT_PUBLIC_SITE_URL=https://your-domain.com
NEXT_PUBLIC_SITE_NAME=MultiSite
NEXT_PUBLIC_DEFAULT_LANG=zh-CN
# SEO configuration
NEXT_PUBLIC_SEO_TITLE=MultiSite - Enterprise Multi-language Solution
NEXT_PUBLIC_SEO_DESCRIPTION=High-performance multi-language static website based on React/Next.js
# Analytics tools
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
NEXT_PUBLIC_BAIDU_ANALYTICS=your-baidu-id
```

View File

@ -0,0 +1,31 @@
---
title: 'Configuration Guide'
description: 'Detailed MultiSite configuration options and best practices'
date: '2024-01-16'
author: 'MultiSite Team'
category: 'Configuration'
tags: ['configuration', 'environment', 'customization']
---
# Configuration Guide
This guide will detail how to configure MultiSite to meet your needs.
## Environment Variables
Create a `.env.local` file to configure environment variables:
```env:
# Basic site information
NEXT_PUBLIC_SITE_URL=https://your-domain.com
NEXT_PUBLIC_SITE_NAME=MultiSite
NEXT_PUBLIC_DEFAULT_LANG=zh-CN
# SEO configuration
NEXT_PUBLIC_SEO_TITLE=MultiSite - Enterprise Multi-language Solution
NEXT_PUBLIC_SEO_DESCRIPTION=High-performance multi-language static website based on React/Next.js
# Analytics tools
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
NEXT_PUBLIC_BAIDU_ANALYTICS=your-baidu-id
```

View File

@ -0,0 +1,31 @@
---
title: 'Configuration Guide'
description: 'Detailed MultiSite configuration options and best practices'
date: '2024-01-16'
author: 'MultiSite Team'
category: 'Configuration'
tags: ['configuration', 'environment', 'customization']
---
# Configuration Guide
This guide will detail how to configure MultiSite to meet your needs.
## Environment Variables
Create a `.env.local` file to configure environment variables:
```env:
# Basic site information
NEXT_PUBLIC_SITE_URL=https://your-domain.com
NEXT_PUBLIC_SITE_NAME=MultiSite
NEXT_PUBLIC_DEFAULT_LANG=zh-CN
# SEO configuration
NEXT_PUBLIC_SEO_TITLE=MultiSite - Enterprise Multi-language Solution
NEXT_PUBLIC_SEO_DESCRIPTION=High-performance multi-language static website based on React/Next.js
# Analytics tools
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
NEXT_PUBLIC_BAIDU_ANALYTICS=your-baidu-id
```

View File

@ -0,0 +1,31 @@
---
title: 'Configuration Guide'
description: 'Detailed MultiSite configuration options and best practices'
date: '2024-01-16'
author: 'MultiSite Team'
category: 'Configuration'
tags: ['configuration', 'environment', 'customization']
---
# Configuration Guide
This guide will detail how to configure MultiSite to meet your needs.
## Environment Variables
Create a `.env.local` file to configure environment variables:
```env:
# Basic site information
NEXT_PUBLIC_SITE_URL=https://your-domain.com
NEXT_PUBLIC_SITE_NAME=MultiSite
NEXT_PUBLIC_DEFAULT_LANG=zh-CN
# SEO configuration
NEXT_PUBLIC_SEO_TITLE=MultiSite - Enterprise Multi-language Solution
NEXT_PUBLIC_SEO_DESCRIPTION=High-performance multi-language static website based on React/Next.js
# Analytics tools
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
NEXT_PUBLIC_BAIDU_ANALYTICS=your-baidu-id
```

91
lib/content.ts Normal file
View File

@ -0,0 +1,91 @@
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
export interface ContentMetadata {
title: string;
description: string;
date: string;
author: string;
category: string;
tags: string[];
draft?: boolean;
featured?: boolean;
}
export interface ContentItem {
slug: string;
metadata: ContentMetadata;
content: string;
}
const contentDirectory = path.join(process.cwd(), 'content');
export function getContentData(lang: string, slug: string): ContentItem {
const fullPath = path.join(contentDirectory, lang, `${slug}.md`);
if (!fs.existsSync(fullPath)) {
throw new Error(`Content not found: ${fullPath}`);
}
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data, content } = matter(fileContents);
return {
slug,
metadata: data as ContentMetadata,
content,
};
}
export function getAllContent(lang: string): ContentItem[] {
const langDirectory = path.join(contentDirectory, lang);
if (!fs.existsSync(langDirectory)) {
return [];
}
const files = fs.readdirSync(langDirectory);
return files
.filter((file) => file.endsWith('.md'))
.map((file) => {
const slug = file.replace('.md', '');
return getContentData(lang, slug);
})
.filter((item) => !item.metadata.draft)
.sort((a, b) => new Date(b.metadata.date).getTime() - new Date(a.metadata.date).getTime());
}
export function getContentByCategory(lang: string, category: string): ContentItem[] {
const allContent = getAllContent(lang);
return allContent.filter((item) => item.metadata.category === category);
}
export function getContentByTag(lang: string, tag: string): ContentItem[] {
const allContent = getAllContent(lang);
return allContent.filter((item) => item.metadata.tags && item.metadata.tags.includes(tag));
}
export function searchContent(lang: string, query: string): ContentItem[] {
const allContent = getAllContent(lang);
const searchQuery = query.toLowerCase();
return allContent.filter((item) => {
const searchText =
`${item.metadata.title} ${item.metadata.description} ${item.content}`.toLowerCase();
return searchText.includes(searchQuery);
});
}
export function getAllCategories(lang: string): string[] {
const allContent = getAllContent(lang);
const categories = allContent.map((item) => item.metadata.category);
return [...new Set(categories)].filter(Boolean);
}
export function getAllTags(lang: string): string[] {
const allContent = getAllContent(lang);
const tags = allContent.flatMap((item) => item.metadata.tags || []);
return [...new Set(tags)].filter(Boolean);
}

6
lib/utils.ts Normal file
View File

@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

10
next.config.mjs Normal file
View File

@ -0,0 +1,10 @@
import path from 'path';
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
distDir: process.env.NODE_ENV === "production" ? "build" : "out",
typescript: {
ignoreBuildErrors: true
}
};
export default nextConfig;

7157
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

38
package.json Normal file
View File

@ -0,0 +1,38 @@
{
"name": "@onlook/next-template",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"format": "prettier --write ."
},
"dependencies": {
"@radix-ui/react-slot": "^1.1.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"gray-matter": "^4.0.3",
"lucide-react": "^0.438.0",
"next": "14.2.23",
"react": "^18",
"react-dom": "^18",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^15.6.1",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-syntax-highlighter": "^15.5.13",
"eslint": "^8",
"eslint-config-next": "^15.1.6",
"postcss": "^8",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}

81
pdf.py Normal file
View File

@ -0,0 +1,81 @@
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
# Register a Chinese-capable font
pdfmetrics.registerFont(UnicodeCIDFont('STSong-Light'))
# File path
file_path = "E:/island_maids_analysis_v2.pdf"
# Create PDF
c = canvas.Canvas(file_path, pagesize=letter)
width, height = letter
# Title
c.setFont("STSong-Light", 18)
c.drawString(50, height - 50, "Island Maids 网站功能与后端需求分析")
c.setFont("STSong-Light", 12)
y = height - 80
# Content structured manually
sections = [
("前端功能", [
"1. 服务分类与详细介绍",
" 家政服务分多国籍外佣(印尼、缅甸、菲律宾、米佐拉姆)、",
" 坐月嫂、培训课程、送返工服务、宿舍与住宿、续签与保险等,",
" 每项均有独立页面展示申请流程。",
"2. 在线筛选与预约面试Search Maids",
" 浏览候选人简历,支持筛选、收藏、一键预约面试。",
"3. 资源中心",
" • 新闻与动态:行业资讯、政策更新",
" • 常见问题雇主及外佣FAQ解答",
"4. 公司介绍与信任背书",
" • 关于我们:发展历程、匹配案例统计",
" • 客户评价与画廊:好评与服务现场照片",
"5. 人才招募Join Us",
" 外佣及管理岗位在线应聘,填写并提交简历",
"6. 多渠道联系与咨询",
" 分行电话、在线表单、WhatsApp、反馈、社交媒体链接",
"7. 其他辅助功能",
" 网点地图、营业时间提醒、站点地图",
]),
("后端需求", [
"1. 数据存储与查询",
" 候选简历、客户档案、面试记录等存数据库,支持筛选与分页",
"2. 表单提交与流程管理",
" 接收并验证表单,存储后推送邮件/短信,支持多步流程",
"3. 用户与权限",
" 注册登录、JWT/Session验证管理员CMS权限控制",
"4. 内容管理系统",
" 后台管理新闻、FAQ、画廊等实时更新前端",
"5. 第三方服务对接",
" 邮件、SMS、WhatsApp、地图API服务调用",
"6. 安全与性能",
" 输入校验、防注入、速率限流、WAFRedis缓存加速",
]),
]
for title, lines in sections:
# Section title
if y < 100:
c.showPage()
y = height - 50
c.setFont("STSong-Light", 12)
c.setFont("STSong-Light", 14)
c.drawString(50, y, title)
y -= 20
c.setFont("STSong-Light", 12)
for line in lines:
if y < 50:
c.showPage()
y = height - 50
c.setFont("STSong-Light", 12)
c.drawString(60, y, line)
y -= 16
y -= 10
c.save()
file_path

104
pdf2.py Normal file
View File

@ -0,0 +1,104 @@
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
# Register a Chinese-capable font
pdfmetrics.registerFont(UnicodeCIDFont('STSong-Light'))
# File path
file_path = "E:/ministry_of_helpers_requirements.pdf"
# Create PDF
c = canvas.Canvas(file_path, pagesize=letter)
width, height = letter
# Title
c.setFont("STSong-Light", 18)
c.drawString(50, height - 50, "Ministry of Helpers 网站需求分析报告")
c.setFont("STSong-Light", 14)
y = height - 80
c.drawString(50, y, "一、前端功能分析")
y -= 24
c.setFont("STSong-Light", 12)
frontend_lines = [
"1. 首页 / 品牌介绍:平台口号及核心价值展示",
"2. 角色入口切换:雇主 / 家佣 双向导航",
"3. 查找 & 发布:雇主浏览候选人,家佣申请职位",
"4. 订阅计划 & 服务包Lite/Standard/Premium 套餐展示",
"5. 常见问题FAQs费用、合同流程等解答",
"6. 客户评价 & 合作伙伴:好评轮播与合作机构展示",
"7. 社区 & 内容运营:论坛、博客入口",
"8. 移动端推广App 下载按钮App Store / Google Play",
"9. 多渠道联系Sign up/Log in/Contact us/Policy 链接",
]
for line in frontend_lines:
if y < 60:
c.showPage()
y = height - 50
c.setFont("STSong-Light", 12)
c.drawString(60, y, line)
y -= 18
# Backend section
if y < 100:
c.showPage()
y = height - 50
c.setFont("STSong-Light", 14)
c.drawString(50, y, "二、后端开发需求分析")
y -= 24
c.setFont("STSong-Light", 12)
backend_lines = [
"模块 | 职能描述 | 技术要点",
"1. 用户与权限 | 注册登录、角色控制 | JWT/Session, OAuth, RBAC",
"2. 数据存储 | 档案、简历、订阅记录 | PostgreSQL/MySQL, MongoDB",
"3. 搜索与筛选 | 多维度检索、分页、收藏 | ElasticSearch, Redis 缓存",
"4. 订阅与支付 | 计划管理、计费续费 | Stripe/PayPal API, 定时任务",
"5. 表单与流程 | 预约、申请、多步状态机 | 后端验证, 流程引擎, 邮件/SMS",
"6. CMS管理 | 博客、FAQ、评价后台维护 | Strapi/自研 Admin, 富文本",
"7. 社区论坛 | 帖子评论、点赞、审核 | Discourse 或自建, WebSocket",
"8. 移动 App 支持 | API接口, 文件上传 | REST/GraphQL, S3 签名 URL",
"9. 安全与合规 | 防注入、速率限制、WAF | OWASP, Nginx+WAF, HTTPS",
"10. 性能与监控 | 缓存、日志、监控 | Redis, Prometheus/Grafana, ELK",
"11. 第三方集成 | 地图、邮件/SMS、政府 API | Google Maps, Twilio, 错误重试",
]
for line in backend_lines:
if y < 60:
c.showPage()
y = height - 50
c.setFont("STSong-Light", 12)
c.drawString(50, y, line)
y -= 18
# Tech stack section
if y < 100:
c.showPage()
y = height - 50
c.setFont("STSong-Light", 14)
c.drawString(50, y, "三、推荐技术栈示例")
y -= 24
c.setFont("STSong-Light", 12)
tech_lines = [
"- 后端框架Node.js (Express/Koa) 或 Python (FastAPI/Django)",
"- 数据库PostgreSQL + Redis 缓存 + S3 对象存储",
"- 认证OAuth2 + JWTRBAC",
"- 异步消息RabbitMQ / Kafka",
"- 搜索Elasticsearch",
"- CMSStrapi 或 Keystone",
"- 部署Docker + Kubernetes / AWS ECSCI/CD",
]
for line in tech_lines:
if y < 60:
c.showPage()
y = height - 50
c.setFont("STSong-Light", 12)
c.drawString(60, y, line)
y -= 18
c.save()
file_path

8
postcss.config.mjs Normal file
View File

@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;

BIN
public/images/logo-text.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

67
tailwind.config.ts Normal file
View File

@ -0,0 +1,67 @@
import type { Config } from "tailwindcss";
const config: Config = {
darkMode: ["class"],
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))'
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
}
}
}
},
plugins: [require("tailwindcss-animate")],
};
export default config;

42
tsconfig.json Normal file
View File

@ -0,0 +1,42 @@
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next-prod/types/**/*.ts",
"build/types/**/*.ts",
"out/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}

3772
yarn.lock Normal file

File diff suppressed because it is too large Load Diff