上线
This commit is contained in:
commit
55b08a08f4
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
40
.gitignore
vendored
Normal file
40
.gitignore
vendored
Normal 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
11
.prettierrc
Normal 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
25
README.md
Normal 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
476
app/about/page.tsx
Normal 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
510
app/contact/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
114
app/docs/[slug]/DocDetail.tsx
Normal file
114
app/docs/[slug]/DocDetail.tsx
Normal 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
28
app/docs/[slug]/page.tsx
Normal 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
369
app/docs/page.tsx
Normal 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>
|
||||
© 2024 MultiSite.{' '}
|
||||
{currentLang === 'en' ? 'All rights reserved.' : '保留所有权利。'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
BIN
app/favicon.ico
Normal file
BIN
app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 175 KiB |
88
app/globals.css
Normal file
88
app/globals.css
Normal 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
18
app/layout.tsx
Normal 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
237
app/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
20
components.json
Normal file
20
components.json
Normal 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
182
components/DocumentList.tsx
Normal 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
88
components/Footer.tsx
Normal 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>
|
||||
© 2024 MultiSite.{' '}
|
||||
{currentLang === 'en' ? 'All rights reserved.' : '保留所有权利。'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
69
components/Header.tsx
Normal file
69
components/Header.tsx
Normal 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
235
content/en/configuration.md
Normal 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 应用现在已经成功部署到生产环境。
|
||||
```
|
||||
27
content/en/getting-started.md
Normal file
27
content/en/getting-started.md
Normal 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
|
||||
31
content/zh-CN/configuration.md
Normal file
31
content/zh-CN/configuration.md
Normal 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
|
||||
```
|
||||
31
content/zh-CN/content-management.md
Normal file
31
content/zh-CN/content-management.md
Normal 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
|
||||
```
|
||||
31
content/zh-CN/deployment.md
Normal file
31
content/zh-CN/deployment.md
Normal 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
|
||||
```
|
||||
31
content/zh-CN/getting-started.md
Normal file
31
content/zh-CN/getting-started.md
Normal 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
91
lib/content.ts
Normal 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
6
lib/utils.ts
Normal 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
10
next.config.mjs
Normal 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
7157
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
package.json
Normal file
38
package.json
Normal 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
81
pdf.py
Normal 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. 安全与性能",
|
||||
" 输入校验、防注入、速率限流、WAF,Redis缓存加速",
|
||||
]),
|
||||
]
|
||||
|
||||
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
104
pdf2.py
Normal 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 + JWT;RBAC",
|
||||
"- 异步消息:RabbitMQ / Kafka",
|
||||
"- 搜索:Elasticsearch",
|
||||
"- CMS:Strapi 或 Keystone",
|
||||
"- 部署:Docker + Kubernetes / AWS ECS;CI/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
8
postcss.config.mjs
Normal 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
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
67
tailwind.config.ts
Normal 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
42
tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user