first commit

This commit is contained in:
Zopt 2025-09-15 16:58:31 +08:00
parent 05c5677b65
commit 21bb4a5d85
34 changed files with 11131 additions and 1 deletions

3
.eslintrc.json Normal file
View File

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

39
.gitignore vendored Normal file
View File

@ -0,0 +1,39 @@
# 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

11
.prettierrc Normal file
View File

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

View File

@ -1,2 +1,93 @@
# FutureCloud
# Onlook 启动模板
<p align="center">
<img src="app/favicon.ico" />
</p>
这是一个使用 [Next.js](https://nextjs.org/)、[TailwindCSS](https://tailwindcss.com/) 与 [shadcn/ui](https://ui.shadcn.com) 搭建的 [Onlook](https://onlook.com/) 项目模板。
## 快速开始
1. 安装依赖PowerShell 示例,四选一):
```powershell
# npm
npm install
# yarn
yarn install
# pnpm
pnpm install
# bun
bun install
```
2. 启动开发服务器PowerShell
```powershell
# npm
npm run dev
# yarn
yarn dev
# pnpm
pnpm dev
# bun
bun dev
```
打开后在 Onlook 中查看:<http://localhost:3000>
## 目录结构(节选)
- `app/`Next.js App Router 页面与布局
- `components/`:通用组件
- `hooks/`:自定义 Hooks
- `lib/`:工具与内容数据
- `public/`:静态资源
- `app/globals.css`全局样式TailwindCSS
## 常用脚本
```powershell
# 开发
npm run dev
# 构建
npm run build
# 预览(如有 start 脚本)
npm run start
```
使用 `yarn`/`pnpm`/`bun` 时,将 `npm run XXX` 替换为对应命令即可。
## 环境变量
在项目根目录创建 `.env.local`,示例:
```dotenv
# 示例
NEXT_PUBLIC_APP_NAME=Onlook Starter
```
Next.js 会在运行时自动加载 `.env.local`。请勿将敏感变量提交到版本库。
## 部署
- 推荐Vercel零配置支持 Next.js
- 自托管:
```powershell
npm run build
npm run start
```
## 许可证
本模板遵循其依赖库的各自许可证。根据你的项目需要添加 `LICENSE` 文件。

199
app/[lang]/about/page.tsx Normal file
View File

@ -0,0 +1,199 @@
'use client';
import Navigation from '../../../components/Navigation';
import Footer from '../../../components/Footer';
import { useLanguage } from '@/hooks/useLanguage';
interface AboutPageProps {
params: {
lang: string;
};
}
export default function AboutPage({ params }: AboutPageProps) {
const { currentLang, setCurrentLang, currentContent, createLocalizedPath } = useLanguage(
params.lang,
);
return (
<div className="min-h-screen bg-gray-900 text-white">
<Navigation
currentLang={currentLang}
setCurrentLang={setCurrentLang}
content={currentContent}
createLocalizedPath={createLocalizedPath}
/>
{/* Hero Section */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto text-center">
<h1 className="text-4xl md:text-6xl font-bold mb-8 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
{currentContent.about.title}
</h1>
<p className="text-xl text-gray-300 mb-12 max-w-3xl mx-auto">
{currentContent.about.subtitle}
</p>
</div>
</section>
{/* About Content */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto">
<div className="grid lg:grid-cols-2 gap-12 items-center mb-20">
<div>
<h2 className="text-3xl font-bold mb-6 text-cyan-400"></h2>
<p className="text-gray-300 leading-relaxed mb-6">
{currentContent.about.description}
</p>
<p className="text-gray-300 leading-relaxed">
</p>
</div>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-8 border border-cyan-500/20">
<div className="grid grid-cols-2 gap-6 text-center">
<div>
<div className="text-3xl font-bold text-cyan-400 mb-2">
500+
</div>
<div className="text-gray-400"></div>
</div>
<div>
<div className="text-3xl font-bold text-cyan-400 mb-2">
99.9%
</div>
<div className="text-gray-400"></div>
</div>
<div>
<div className="text-3xl font-bold text-cyan-400 mb-2">
24/7
</div>
<div className="text-gray-400"></div>
</div>
<div>
<div className="text-3xl font-bold text-cyan-400 mb-2">
5+
</div>
<div className="text-gray-400"></div>
</div>
</div>
</div>
</div>
{/* Mission, Vision, Values */}
<div className="grid md:grid-cols-3 gap-8 mb-20">
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-8 border border-cyan-500/20 text-center">
<div className="w-16 h-16 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center mx-auto mb-6">
<svg
className="w-8 h-8 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 10V3L4 14h7v7l9-11h-7z"
/>
</svg>
</div>
<h3 className="text-xl font-bold mb-4 text-cyan-400">
{currentContent.about.mission.title}
</h3>
<p className="text-gray-300 leading-relaxed">
{currentContent.about.mission.content}
</p>
</div>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-8 border border-cyan-500/20 text-center">
<div className="w-16 h-16 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center mx-auto mb-6">
<svg
className="w-8 h-8 text-white"
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>
<h3 className="text-xl font-bold mb-4 text-cyan-400">
{currentContent.about.vision.title}
</h3>
<p className="text-gray-300 leading-relaxed">
{currentContent.about.vision.content}
</p>
</div>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-8 border border-cyan-500/20 text-center">
<div className="w-16 h-16 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center mx-auto mb-6">
<svg
className="w-8 h-8 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
/>
</svg>
</div>
<h3 className="text-xl font-bold mb-4 text-cyan-400">
{currentContent.about.values.title}
</h3>
<ul className="space-y-2">
{currentContent.about.values.items.map((value, index) => (
<li key={index} className="text-gray-300">
{value}
</li>
))}
</ul>
</div>
</div>
{/* Team Section */}
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-bold mb-6 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
{currentContent.about.team.title}
</h2>
</div>
<div className="grid md:grid-cols-3 gap-8">
{currentContent.about.team.members.map((member, index) => (
<div
key={index}
className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-8 border border-cyan-500/20 text-center"
>
<div className="w-20 h-20 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full flex items-center justify-center mx-auto mb-6">
<span className="text-white font-bold text-2xl">
{member.name.charAt(0)}
</span>
</div>
<h3 className="text-xl font-bold mb-2 text-cyan-400">
{member.name}
</h3>
<p className="text-gray-400 mb-4">{member.position}</p>
<p className="text-gray-300 text-sm leading-relaxed">
{member.description}
</p>
</div>
))}
</div>
</div>
</section>
<Footer content={currentContent} createLocalizedPath={createLocalizedPath} />
</div>
);
}

289
app/[lang]/contact/page.tsx Normal file
View File

@ -0,0 +1,289 @@
'use client';
import { useState } from 'react';
import Navigation from '../../../components/Navigation';
import Footer from '../../../components/Footer';
import { useLanguage } from '../../../hooks/useLanguage';
interface ContactPageProps {
params: {
lang: string;
};
}
export default function ContactPage({ params }: ContactPageProps) {
const { currentLang, setCurrentLang, currentContent, createLocalizedPath } = useLanguage(
params.lang,
);
const [formData, setFormData] = useState({
name: '',
email: '',
company: '',
phone: '',
message: '',
});
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('感谢您的留言,我们会尽快与您联系!');
setFormData({
name: '',
email: '',
company: '',
phone: '',
message: '',
});
};
return (
<div className="min-h-screen bg-gray-900 text-white">
<Navigation
currentLang={currentLang}
setCurrentLang={setCurrentLang}
content={currentContent}
createLocalizedPath={createLocalizedPath}
/>
{/* Hero Section */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto text-center">
<h1 className="text-4xl md:text-6xl font-bold mb-8 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
{currentContent.contact.title}
</h1>
<p className="text-xl text-gray-300 mb-12 max-w-3xl mx-auto">
{currentContent.contact.subtitle}
</p>
</div>
</section>
{/* Contact Content */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto">
<div className="grid lg:grid-cols-2 gap-12">
{/* Contact Form */}
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-8 border border-cyan-500/20">
<h2 className="text-2xl font-bold mb-6 text-cyan-400"></h2>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium mb-2 text-gray-300">
{currentContent.contact.form.name}
</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleInputChange}
placeholder={
currentContent.contact.form.namePlaceholder
}
className="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:outline-none focus:border-cyan-400 transition-colors"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-2 text-gray-300">
{currentContent.contact.form.email}
</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleInputChange}
placeholder={
currentContent.contact.form.emailPlaceholder
}
className="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:outline-none focus:border-cyan-400 transition-colors"
required
/>
</div>
</div>
<div className="grid md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium mb-2 text-gray-300">
{currentContent.contact.form.company}
</label>
<input
type="text"
name="company"
value={formData.company}
onChange={handleInputChange}
placeholder={
currentContent.contact.form.companyPlaceholder
}
className="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:outline-none focus:border-cyan-400 transition-colors"
/>
</div>
<div>
<label className="block text-sm font-medium mb-2 text-gray-300">
{currentContent.contact.form.phone}
</label>
<input
type="tel"
name="phone"
value={formData.phone}
onChange={handleInputChange}
placeholder={
currentContent.contact.form.phonePlaceholder
}
className="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:outline-none focus:border-cyan-400 transition-colors"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium mb-2 text-gray-300">
{currentContent.contact.form.message}
</label>
<textarea
name="message"
value={formData.message}
onChange={handleInputChange}
placeholder={currentContent.contact.form.messagePlaceholder}
rows={6}
className="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:outline-none focus:border-cyan-400 transition-colors resize-none"
required
/>
</div>
<button
type="submit"
className="w-full py-4 bg-gradient-to-r from-cyan-500 to-blue-600 rounded-lg font-semibold hover:from-cyan-400 hover:to-blue-500 transition-all duration-300 transform hover:scale-105"
>
{currentContent.contact.form.submit}
</button>
</form>
</div>
{/* Contact Info */}
<div className="space-y-8">
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-8 border border-cyan-500/20">
<h2 className="text-2xl font-bold mb-6 text-cyan-400"></h2>
<div className="space-y-6">
<div className="flex items-start space-x-4">
<div className="w-12 h-12 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center flex-shrink-0">
<svg
className="w-6 h-6 text-white"
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>
<h3 className="font-semibold text-white mb-1"></h3>
<p className="text-gray-400">
{currentContent.contact.info.address}
</p>
</div>
</div>
<div className="flex items-start space-x-4">
<div className="w-12 h-12 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center flex-shrink-0">
<svg
className="w-6 h-6 text-white"
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>
<h3 className="font-semibold text-white mb-1"></h3>
<p className="text-gray-400">
{currentContent.contact.info.phone}
</p>
</div>
</div>
<div className="flex items-start space-x-4">
<div className="w-12 h-12 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center flex-shrink-0">
<svg
className="w-6 h-6 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 8l7.89 5.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>
<h3 className="font-semibold text-white mb-1"></h3>
<p className="text-gray-400">
{currentContent.contact.info.email}
</p>
</div>
</div>
<div className="flex items-start space-x-4">
<div className="w-12 h-12 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center flex-shrink-0">
<svg
className="w-6 h-6 text-white"
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>
<h3 className="font-semibold text-white mb-1">
</h3>
<p className="text-gray-400">
{currentContent.contact.info.hours}
</p>
</div>
</div>
</div>
</div>
{/* Map placeholder */}
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-8 border border-cyan-500/20">
<h3 className="text-xl font-bold mb-4 text-cyan-400"></h3>
<div className="w-full h-64 bg-gray-700 rounded-lg flex items-center justify-center">
<p className="text-gray-400"></p>
</div>
</div>
</div>
</div>
</div>
</section>
<Footer content={currentContent} createLocalizedPath={createLocalizedPath} />
</div>
);
}

33
app/[lang]/layout.tsx Normal file
View File

@ -0,0 +1,33 @@
import { ReactNode } from 'react';
import { Metadata } from 'next';
import { content } from '../../lib/content';
interface LanguageLayoutProps {
children: ReactNode;
params: {
lang: string;
};
}
export async function generateMetadata({
params,
}: {
params: { lang: string };
}): Promise<Metadata> {
const lang = params.lang as keyof typeof content;
const currentContent = content[lang] || content['zh-CN'];
return {
title: currentContent.title,
description: currentContent.description,
keywords: currentContent.keywords,
};
}
export default function LanguageLayout({ children, params }: LanguageLayoutProps) {
return children;
}
export async function generateStaticParams() {
return [{ lang: 'zh-CN' }, { lang: 'zh-TW' }, { lang: 'en' }];
}

View File

@ -0,0 +1,212 @@
'use client';
import Navigation from '../../../../components/Navigation';
import Footer from '../../../../components/Footer';
import { useLanguage } from '../../../../hooks/useLanguage';
import Link from 'next/link';
import { LanguageType } from '../../../../lib/content';
interface ArticlePageProps {
params: {
lang: string;
id: string;
};
}
export default function ArticlePage({ params }: ArticlePageProps) {
const { currentLang, setCurrentLang, currentContent, createLocalizedPath } = useLanguage(
params.lang as LanguageType,
);
const articleId = parseInt(params.id, 10);
const article = currentContent.news.articles.find((a) => a.id === articleId);
const relatedArticles = currentContent.news.articles
.filter((a) => a.id !== articleId && a.category === article?.category)
.slice(0, 3);
if (!article) {
return (
<div className="min-h-screen bg-gray-900 text-white">
<Navigation
currentLang={currentLang}
setCurrentLang={setCurrentLang}
content={currentContent}
createLocalizedPath={createLocalizedPath}
/>
<div className="flex items-center justify-center min-h-[60vh]">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4 text-cyan-400"></h1>
<p className="text-gray-400 mb-8">访</p>
<Link
href={createLocalizedPath('/news')}
className="px-6 py-3 bg-gradient-to-r from-cyan-500 to-blue-600 rounded-lg font-semibold hover:from-cyan-400 hover:to-blue-500 transition-all duration-300"
>
</Link>
</div>
</div>
{/* 🔧 完整调用 Footer添加 createLocalizedPath */}
<Footer content={currentContent} createLocalizedPath={createLocalizedPath} />
</div>
);
}
const getCategoryColor = (category: string) => {
const colors: Record<string, string> = {
industry: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
technology: 'bg-green-500/20 text-green-400 border-green-500/30',
company: 'bg-purple-500/20 text-purple-400 border-purple-500/30',
case: 'bg-orange-500/20 text-orange-400 border-orange-500/30',
};
return colors[category] ?? 'bg-gray-500/20 text-gray-400 border-gray-500/30';
};
return (
<div className="min-h-screen bg-gray-900 text-white">
<Navigation
currentLang={currentLang}
setCurrentLang={setCurrentLang}
content={currentContent}
createLocalizedPath={createLocalizedPath}
/>
{/* Breadcrumb */}
<section className="relative z-10 px-6 py-8">
<div className="max-w-4xl mx-auto">
<nav className="flex items-center space-x-2 text-sm text-gray-400">
<Link
href={createLocalizedPath('/')}
className="hover:text-cyan-400 transition-colors"
>
</Link>
<span>/</span>
<Link
href={createLocalizedPath('/news')}
className="hover:text-cyan-400 transition-colors"
>
</Link>
<span>/</span>
<span className="text-gray-300">{article.title}</span>
</nav>
</div>
</section>
{/* Article Header */}
<section className="relative z-10 px-6 py-8">
<div className="max-w-4xl mx-auto">
<div className="mb-6">
<span
className={`px-3 py-1 rounded-full text-sm font-medium border ${getCategoryColor(
article.category,
)}`}
>
{currentContent.news.categories[article.category]}
</span>
</div>
<h1 className="text-3xl md:text-5xl font-bold mb-6 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent leading-tight">
{article.title}
</h1>
<div className="flex flex-wrap items-center gap-4 text-gray-400 mb-8">
<div className="flex items-center">
<svg
className="w-5 h-5 mr-2"
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>
{article.author}
</div>
<div className="flex items-center">
<svg
className="w-5 h-5 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
{article.date}
</div>
<div className="flex items-center">
<svg
className="w-5 h-5 mr-2"
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>
{article.readTime}
</div>
</div>
{/* Tags */}
<div className="flex flex-wrap gap-2 mb-8">
{article.tags.map((tag, idx) => (
<span
key={idx}
className="px-3 py-1 bg-gray-700/50 text-gray-400 text-sm rounded-full"
>
#{tag}
</span>
))}
</div>
</div>
</section>
{/* Article Content */}
<section className="relative z-10 px-6 py-8">
<div className="max-w-4xl mx-auto">
<div className="bg-gray-800/30 backdrop-blur-sm rounded-xl p-8 border border-cyan-500/20">
<div className="prose prose-lg prose-invert max-w-none">
<p className="text-xl text-gray-300 leading-relaxed mb-6">
{article.summary}
</p>
<div className="text-gray-300 leading-relaxed space-y-4">
{article.content.split('\n').map((paragraph, idx) => (
<p key={idx}>{paragraph}</p>
))}
</div>
</div>
</div>
</div>
</section>
{/* Share Section */}
<section className="relative z-10 px-6 py-8">
<div className="max-w-4xl mx-auto">{/* ... 分享按钮等省略 */}</div>
</section>
{/* Related Articles */}
{relatedArticles.length > 0 && (
<section className="relative z-10 px-6 py-20">{/* ... 相关文章省略 */}</section>
)}
{/* 🔧 完整调用 Footer传入 createLocalizedPath */}
<Footer content={currentContent} createLocalizedPath={createLocalizedPath} />
</div>
);
}

195
app/[lang]/news/page.tsx Normal file
View File

@ -0,0 +1,195 @@
'use client';
import { useState } from 'react';
import Navigation from '../../../components/Navigation';
import Footer from '../../../components/Footer';
import { useLanguage } from '../../../hooks/useLanguage';
import Link from 'next/link';
interface NewsPageProps {
params: {
lang: string;
};
}
export default function NewsPage({ params }: NewsPageProps) {
const { currentLang, setCurrentLang, currentContent, createLocalizedPath } = useLanguage(
params.lang,
);
const [selectedCategory, setSelectedCategory] = useState('all');
const filteredArticles =
selectedCategory === 'all'
? currentContent.news.articles
: currentContent.news.articles.filter(
(article) => article.category === selectedCategory,
);
const getCategoryColor = (category: string) => {
const colors: Record<string, string> = {
industry: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
technology: 'bg-green-500/20 text-green-400 border-green-500/30',
company: 'bg-purple-500/20 text-purple-400 border-purple-500/30',
case: 'bg-orange-500/20 text-orange-400 border-orange-500/30',
};
return colors[category] ?? 'bg-gray-500/20 text-gray-400 border-gray-500/30';
};
return (
<div className="min-h-screen bg-gray-900 text-white">
<Navigation
currentLang={currentLang}
setCurrentLang={setCurrentLang}
content={currentContent}
createLocalizedPath={createLocalizedPath}
/>
{/* Hero Section */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto text-center">
<h1 className="text-4xl md:text-6xl font-bold mb-8 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
{currentContent.news.title}
</h1>
<p className="text-xl text-gray-300 mb-12 max-w-3xl mx-auto">
{currentContent.news.subtitle}
</p>
</div>
</section>
{/* Category Filter */}
<section className="relative z-10 px-6 py-8">
<div className="max-w-7xl mx-auto">
<div className="flex flex-wrap justify-center gap-4 mb-12">
{Object.entries(currentContent.news.categories).map(([key, label]) => (
<button
key={key}
onClick={() => setSelectedCategory(key)}
className={`px-6 py-3 rounded-full border transition-all duration-300 ${
selectedCategory === key
? 'bg-cyan-500/20 text-cyan-400 border-cyan-500/50'
: 'bg-gray-800/50 text-gray-400 border-gray-600/50 hover:border-cyan-500/30 hover:text-cyan-400'
}`}
>
{label}
</button>
))}
</div>
</div>
</section>
{/* Articles Grid */}
<section className="relative z-10 px-6 py-8">
<div className="max-w-7xl mx-auto">
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{filteredArticles.map((article) => (
<article
key={article.id}
className="bg-gray-800/50 backdrop-blur-sm rounded-xl overflow-hidden border border-cyan-500/20 hover:border-cyan-400/50 transition-all duration-300 group"
>
{/* Article Image */}
<div className="relative h-48 bg-gradient-to-br from-cyan-500/20 to-blue-500/20 overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-t from-gray-900/50 to-transparent" />
<div className="absolute top-4 left-4">
<span
className={`px-3 py-1 rounded-full text-xs font-medium border ${getCategoryColor(article.category)}`}
>
{currentContent.news.categories[article.category]}
</span>
</div>
</div>
{/* Article Content */}
<div className="p-6">
<div className="flex items-center text-sm text-gray-400 mb-3">
<span>{article.author}</span>
<span className="mx-2"></span>
<span>{article.date}</span>
<span className="mx-2"></span>
<span>{article.readTime}</span>
</div>
<h3 className="text-xl font-bold mb-3 text-white group-hover:text-cyan-400 transition-colors line-clamp-2">
<Link href={createLocalizedPath(`/news/${article.id}`)}>
{article.title}
</Link>
</h3>
<p className="text-gray-300 mb-4 line-clamp-3 leading-relaxed">
{article.summary}
</p>
{/* Tags */}
<div className="flex flex-wrap gap-2 mb-4">
{article.tags.map((tag, index) => (
<span
key={index}
className="px-2 py-1 bg-gray-700/50 text-gray-400 text-xs rounded-md"
>
#{tag}
</span>
))}
</div>
<Link
href={createLocalizedPath(`/news/${article.id}`)}
className="inline-flex items-center text-cyan-400 hover:text-cyan-300 transition-colors group"
>
<svg
className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</Link>
</div>
</article>
))}
</div>
{/* Load More Button */}
{filteredArticles.length > 6 && (
<div className="text-center mt-12">
<button className="px-8 py-4 bg-gradient-to-r from-cyan-500 to-blue-600 rounded-lg font-semibold hover:from-cyan-400 hover:to-blue-500 transition-all duration-300 transform hover:scale-105">
</button>
</div>
)}
</div>
</section>
{/* Newsletter Subscription */}
<section className="relative z-10 px-6 py-20 bg-gray-800/30">
<div className="max-w-4xl mx-auto text-center">
<h2 className="text-3xl md:text-4xl font-bold mb-6 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
</h2>
<p className="text-xl text-gray-300 mb-8">
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center max-w-md mx-auto">
<input
type="email"
placeholder="输入您的邮箱地址"
className="flex-1 px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:outline-none focus:border-cyan-400 transition-colors"
/>
<button className="px-6 py-3 bg-gradient-to-r from-cyan-500 to-blue-600 rounded-lg font-semibold hover:from-cyan-400 hover:to-blue-500 transition-all duration-300">
</button>
</div>
</div>
</section>
<Footer content={currentContent} createLocalizedPath={createLocalizedPath} />
</div>
);
}

168
app/[lang]/page.tsx Normal file
View File

@ -0,0 +1,168 @@
'use client';
import { useState, useEffect } from 'react';
import Navigation from '../../components/Navigation';
import Footer from '../../components/Footer';
import { useLanguage } from '../../hooks/useLanguage';
interface Particle {
id: number;
x: number;
y: number;
size: number;
speed: number;
opacity: number;
}
interface HomePageProps {
params: {
lang: string;
};
}
export default function HomePage({ params }: HomePageProps) {
const { currentLang, setCurrentLang, currentContent, createLocalizedPath } = useLanguage(
params.lang,
);
const [isLoaded, setIsLoaded] = useState(false);
const [particles, setParticles] = useState<Particle[]>([]);
// Initialize particles for background effect
useEffect(() => {
const newParticles = Array.from({ length: 50 }, (_, i) => ({
id: i,
x: Math.random() * 100,
y: Math.random() * 100,
size: Math.random() * 3 + 1,
speed: Math.random() * 0.5 + 0.1,
opacity: Math.random() * 0.5 + 0.2,
}));
setParticles(newParticles);
setIsLoaded(true);
}, []);
// Animate particles
useEffect(() => {
const interval = setInterval(() => {
setParticles((prev) =>
prev.map((particle) => ({
...particle,
y: (particle.y + particle.speed) % 100,
})),
);
}, 50);
return () => clearInterval(interval);
}, []);
return (
<>
<div className="min-h-screen bg-gray-900 text-white relative overflow-hidden">
{/* Animated particle background */}
<div className="absolute inset-0 overflow-hidden">
{particles.map((particle) => (
<div
key={particle.id}
className="absolute w-1 h-1 bg-cyan-400 rounded-full animate-pulse"
style={{
left: `${particle.x}%`,
top: `${particle.y}%`,
width: `${particle.size}px`,
height: `${particle.size}px`,
opacity: particle.opacity,
boxShadow: `0 0 ${particle.size * 2}px rgba(34, 211, 238, 0.5)`,
}}
/>
))}
</div>
{/* Gradient overlay */}
<div className="absolute inset-0 bg-gradient-to-br from-blue-900/20 via-purple-900/20 to-cyan-900/20" />
{/* Navigation */}
<Navigation
currentLang={currentLang}
setCurrentLang={setCurrentLang}
content={currentContent}
createLocalizedPath={createLocalizedPath}
/>
{/* Hero Section */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto text-center">
<div
className={`transition-all duration-1000 ${isLoaded ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10'}`}
>
<h1 className="text-6xl md:text-8xl font-bold mb-6 bg-gradient-to-r from-cyan-400 via-blue-500 to-purple-600 bg-clip-text text-transparent">
{currentContent.hero.title}
</h1>
<p className="text-2xl md:text-3xl text-cyan-300 mb-8 font-light">
{currentContent.hero.subtitle}
</p>
<p className="text-xl text-gray-300 mb-12 max-w-3xl mx-auto leading-relaxed">
{currentContent.hero.description}
</p>
<div className="flex flex-col sm:flex-row gap-6 justify-center">
<button className="px-8 py-4 bg-gradient-to-r from-cyan-500 to-blue-600 rounded-full text-lg font-semibold hover:from-cyan-400 hover:to-blue-500 transition-all duration-300 transform hover:scale-105 shadow-lg shadow-cyan-500/25">
{currentContent.hero.cta}
</button>
<button className="px-8 py-4 border-2 border-cyan-400 rounded-full text-lg font-semibold hover:bg-cyan-400 hover:text-gray-900 transition-all duration-300 transform hover:scale-105">
{currentContent.hero.learn}
</button>
</div>
</div>
</div>
</section>
{/* Services Section */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-4xl md:text-5xl font-bold mb-4 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
{currentContent.services.title}
</h2>
<p className="text-xl text-gray-300">
{currentContent.services.subtitle}
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{currentContent.services.items.map((service, index) => (
<div
key={index}
className="group relative p-6 bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-cyan-500/20 hover:border-cyan-400/50 transition-all duration-300 transform hover:scale-105"
>
{/* 3D Icon effect */}
<div className="w-16 h-16 mb-6 mx-auto bg-gradient-to-br from-cyan-400 to-blue-600 rounded-xl flex items-center justify-center transform group-hover:rotate-12 transition-transform duration-300">
<div className="w-8 h-8 bg-white/20 rounded-lg" />
</div>
<h3 className="text-xl font-bold mb-4 text-cyan-300 group-hover:text-cyan-200 transition-colors">
{service.title}
</h3>
<p className="text-gray-400 group-hover:text-gray-300 transition-colors">
{service.description}
</p>
{/* Glow effect */}
<div className="absolute inset-0 bg-gradient-to-r from-cyan-500/10 to-blue-500/10 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
</div>
))}
</div>
</div>
</section>
{/* Decorative lines */}
<div className="absolute top-1/2 left-0 w-full h-px bg-gradient-to-r from-transparent via-cyan-500/50 to-transparent" />
<div className="absolute top-1/3 left-0 w-full h-px bg-gradient-to-r from-transparent via-blue-500/30 to-transparent" />
<div className="absolute top-2/3 left-0 w-full h-px bg-gradient-to-r from-transparent via-purple-500/30 to-transparent" />
{/* Footer */}
<Footer content={currentContent} createLocalizedPath={createLocalizedPath} />
</div>
</>
);
}

156
app/[lang]/pricing/page.tsx Normal file
View File

@ -0,0 +1,156 @@
'use client';
import Navigation from '../../../components/Navigation';
import Footer from '../../../components/Footer';
import { useLanguage } from '../../../hooks/useLanguage';
interface PricingPageProps {
params: {
lang: string;
};
}
export default function PricingPage({ params }: PricingPageProps) {
const { currentLang, setCurrentLang, currentContent, createLocalizedPath } = useLanguage(
params.lang,
);
return (
<div className="min-h-screen bg-gray-900 text-white">
<Navigation
currentLang={currentLang}
setCurrentLang={setCurrentLang}
content={currentContent}
createLocalizedPath={createLocalizedPath}
/>
{/* Hero Section */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto text-center">
<h1 className="text-4xl md:text-6xl font-bold mb-8 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
{currentContent.pricing.title}
</h1>
<p className="text-xl text-gray-300 mb-12 max-w-3xl mx-auto">
{currentContent.pricing.subtitle}
</p>
</div>
</section>
{/* Pricing Cards */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto">
<div className="grid md:grid-cols-3 gap-8">
{currentContent.pricing.plans.map((plan, index) => (
<div
key={index}
className={`relative bg-gray-800/50 backdrop-blur-sm rounded-xl p-8 border transition-all duration-300 ${
plan.popular
? 'border-cyan-400 scale-105 shadow-2xl shadow-cyan-500/20'
: 'border-cyan-500/20 hover:border-cyan-400/50'
}`}
>
{plan.popular && (
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2">
<span className="bg-gradient-to-r from-cyan-400 to-blue-500 text-white px-4 py-1 rounded-full text-sm font-semibold">
</span>
</div>
)}
<div className="text-center mb-8">
<h3 className="text-2xl font-bold mb-2 text-cyan-400">
{plan.name}
</h3>
<div className="mb-4">
<span className="text-4xl font-bold">{plan.price}</span>
<span className="text-gray-400">{plan.period}</span>
</div>
<p className="text-gray-300">{plan.description}</p>
</div>
<ul className="space-y-4 mb-8">
{plan.features.map((feature, featureIndex) => (
<li
key={featureIndex}
className="flex items-center text-gray-300"
>
<svg
className="w-5 h-5 text-cyan-400 mr-3 flex-shrink-0"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
{feature}
</li>
))}
</ul>
<button
className={`w-full py-4 rounded-lg font-semibold transition-all duration-300 ${
plan.popular
? 'bg-gradient-to-r from-cyan-500 to-blue-600 hover:from-cyan-400 hover:to-blue-500 text-white'
: 'border border-cyan-500 text-cyan-400 hover:bg-cyan-500/10'
}`}
>
</button>
</div>
))}
</div>
</div>
</section>
{/* FAQ Section */}
<section className="relative z-10 px-6 py-20 bg-gray-800/30">
<div className="max-w-4xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-3xl md:text-4xl font-bold mb-6 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
</h2>
<p className="text-xl text-gray-300"></p>
</div>
<div className="space-y-6">
{[
{
question: '如何选择适合的方案?',
answer: '我们建议根据您的业务规模、预期流量和技术需求来选择。我们的专业团队可以为您提供免费咨询,帮助您选择最适合的方案。',
},
{
question: '是否支持方案升级?',
answer: '是的,您可以随时升级到更高级的方案。升级过程无缝衔接,不会影响您的业务运行。',
},
{
question: '技术支持包含哪些内容?',
answer: '我们提供7x24小时技术支持包括系统监控、故障排除、性能优化、安全更新等全方位服务。',
},
{
question: '数据安全如何保障?',
answer: '我们采用AWS企业级安全标准包括数据加密、访问控制、定期备份、灾难恢复等多重安全保障措施。',
},
].map((faq: { question: string; answer: string }, index: number) => (
<div
key={index}
className="bg-gray-800/50 rounded-lg p-6 border border-cyan-500/20"
>
<h3 className="text-xl font-bold mb-3 text-cyan-400">
{faq.question}
</h3>
<p className="text-gray-300 leading-relaxed">{faq.answer}</p>
</div>
))}
</div>
</div>
</section>
<Footer content={currentContent} createLocalizedPath={createLocalizedPath} />
</div>
);
}

View File

@ -0,0 +1,119 @@
'use client';
import Navigation from '../../../components/Navigation';
import Footer from '../../../components/Footer';
import { useLanguage } from '../../../hooks/useLanguage';
interface ServicesPageProps {
params: {
lang: string;
};
}
export default function ServicesPage({ params }: ServicesPageProps) {
const { currentLang, setCurrentLang, currentContent, createLocalizedPath } = useLanguage(
params.lang,
);
return (
<div className="min-h-screen bg-gray-900 text-white">
<Navigation
currentLang={currentLang}
setCurrentLang={setCurrentLang}
content={currentContent}
createLocalizedPath={createLocalizedPath}
/>
{/* Hero Section */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto text-center">
<h1 className="text-4xl md:text-6xl font-bold mb-8 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
{currentContent.services.title}
</h1>
<p className="text-xl text-gray-300 mb-12 max-w-3xl mx-auto">
{currentContent.services.subtitle}
</p>
</div>
</section>
{/* Services Grid */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto">
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{currentContent.services.items.map((service, index) => (
<div
key={index}
className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-8 border border-cyan-500/20 hover:border-cyan-400/50 transition-all duration-300 group"
>
<div className="w-16 h-16 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300">
<svg
className="w-8 h-8 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<h3 className="text-2xl font-bold mb-4 text-cyan-400">
{service.title}
</h3>
<p className="text-gray-300 mb-6 leading-relaxed">
{service.description}
</p>
<ul className="space-y-2">
{service.features.map((feature, featureIndex) => (
<li
key={featureIndex}
className="flex items-center text-gray-400"
>
<svg
className="w-4 h-4 text-cyan-400 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
{feature}
</li>
))}
</ul>
</div>
))}
</div>
</div>
</section>
{/* CTA Section */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-4xl mx-auto text-center">
<h2 className="text-3xl md:text-4xl font-bold mb-6 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
{currentContent.hero.cta}
</h2>
<p className="text-xl text-gray-300 mb-8">{currentContent.hero.description}</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<button className="px-8 py-4 bg-gradient-to-r from-cyan-500 to-blue-600 rounded-lg font-semibold hover:from-cyan-400 hover:to-blue-500 transition-all duration-300 transform hover:scale-105">
{currentContent.hero.cta}
</button>
<button className="px-8 py-4 border border-cyan-500 rounded-lg font-semibold hover:bg-cyan-500/10 transition-all duration-300">
{currentContent.hero.learn}
</button>
</div>
</div>
</section>
<Footer content={currentContent} createLocalizedPath={createLocalizedPath} />
</div>
);
}

View File

@ -0,0 +1,128 @@
'use client';
import Navigation from '../../../components/Navigation';
import Footer from '../../../components/Footer';
import { useLanguage } from '../../../hooks/useLanguage';
interface SolutionsPageProps {
params: {
lang: string;
};
}
export default function SolutionsPage({ params }: SolutionsPageProps) {
const { currentLang, setCurrentLang, currentContent, createLocalizedPath } = useLanguage(
params.lang,
);
return (
<div className="min-h-screen bg-gray-900 text-white">
<Navigation
currentLang={currentLang}
setCurrentLang={setCurrentLang}
content={currentContent}
createLocalizedPath={createLocalizedPath}
/>
{/* Hero Section */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto text-center">
<h1 className="text-4xl md:text-6xl font-bold mb-8 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
{currentContent.solutions.title}
</h1>
<p className="text-xl text-gray-300 mb-12 max-w-3xl mx-auto">
{currentContent.solutions.subtitle}
</p>
</div>
</section>
{/* Solutions Grid */}
<section className="relative z-10 px-6 py-20">
<div className="max-w-7xl mx-auto">
<div className="grid md:grid-cols-2 gap-12">
{currentContent.solutions.items.map((solution, index) => (
<div
key={index}
className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-8 border border-cyan-500/20 hover:border-cyan-400/50 transition-all duration-300 group"
>
<div className="flex items-start space-x-4">
<div className="w-12 h-12 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
<span className="text-white font-bold text-lg">
{index + 1}
</span>
</div>
<div className="flex-1">
<h3 className="text-2xl font-bold mb-4 text-cyan-400">
{solution.title}
</h3>
<p className="text-gray-300 mb-6 leading-relaxed">
{solution.description}
</p>
<div className="grid grid-cols-2 gap-4">
{solution.benefits.map((benefit, benefitIndex) => (
<div
key={benefitIndex}
className="flex items-center text-gray-400"
>
<svg
className="w-4 h-4 text-cyan-400 mr-2 flex-shrink-0"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 10V3L4 14h7v7l9-11h-7z"
/>
</svg>
<span className="text-sm">{benefit}</span>
</div>
))}
</div>
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* Process Section */}
<section className="relative z-10 px-6 py-20 bg-gray-800/30">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-3xl md:text-4xl font-bold mb-6 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
</h2>
<p className="text-xl text-gray-300"></p>
</div>
<div className="grid md:grid-cols-4 gap-8">
{[
{ title: '需求分析', description: '深入了解客户需求和现状' },
{ title: '方案设计', description: '制定详细的技术实施方案' },
{ title: '项目实施', description: '按计划执行迁移和部署' },
{ title: '运维支持', description: '提供持续的运维和优化' },
].map((step: { title: string; description: string }, index: number) => (
<div key={index} className="text-center">
<div className="w-16 h-16 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-white font-bold text-xl">
{index + 1}
</span>
</div>
<h3 className="text-xl font-bold mb-2 text-cyan-400">
{step.title}
</h3>
<p className="text-gray-400">{step.description}</p>
</div>
))}
</div>
</div>
</section>
<Footer content={currentContent} createLocalizedPath={createLocalizedPath} />
</div>
);
}

BIN
app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

104
app/globals.css Normal file
View File

@ -0,0 +1,104 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
: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;
}
}

13
app/layout.tsx Normal file
View File

@ -0,0 +1,13 @@
import type { Metadata } from 'next';
import './globals.css';
export const metadata: Metadata = {
title: 'My New App',
description: 'Generated by Onlook',
};
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<body className="">{children}</body>
</html>
);
}

34
app/page.tsx Normal file
View File

@ -0,0 +1,34 @@
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
export default function RootPage() {
const router = useRouter();
useEffect(() => {
// 检测浏览器语言并重定向到相应的语言路径
const browserLang = navigator.language;
let detectedLang = 'zh-CN';
if (browserLang.startsWith('zh-TW') || browserLang.startsWith('zh-HK')) {
detectedLang = 'zh-TW';
} else if (browserLang.startsWith('zh')) {
detectedLang = 'zh-CN';
} else {
detectedLang = 'en';
}
router.replace(`/${detectedLang}`);
}, [router]);
// 显示加载状态
return (
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center">
<div className="text-center">
<div className="w-16 h-16 border-4 border-cyan-400 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-gray-300">...</p>
</div>
</div>
);
}

87
app/sitemap.xml/route.ts Normal file
View File

@ -0,0 +1,87 @@
import { NextResponse } from 'next/server';
export async function GET() {
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/sitemap.xsl"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<!-- Default language pages -->
<url>
<loc>http://localhost:3000/zh-CN</loc>
<xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:3000/zh-CN"/>
<xhtml:link rel="alternate" hreflang="zh-TW" href="http://localhost:3000/zh-TW"/>
<xhtml:link rel="alternate" hreflang="en" href="http://localhost:3000/en"/>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<!-- About pages -->
<url>
<loc>http://localhost:3000/zh-CN/about</loc>
<xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:3000/zh-CN/about"/>
<xhtml:link rel="alternate" hreflang="zh-TW" href="http://localhost:3000/zh-TW/about"/>
<xhtml:link rel="alternate" hreflang="en" href="http://localhost:3000/en/about"/>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<!-- Services pages -->
<url>
<loc>http://localhost:3000/zh-CN/services</loc>
<xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:3000/zh-CN/services"/>
<xhtml:link rel="alternate" hreflang="zh-TW" href="http://localhost:3000/zh-TW/services"/>
<xhtml:link rel="alternate" hreflang="en" href="http://localhost:3000/en/services"/>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<!-- Solutions pages -->
<url>
<loc>http://localhost:3000/zh-CN/solutions</loc>
<xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:3000/zh-CN/solutions"/>
<xhtml:link rel="alternate" hreflang="zh-TW" href="http://localhost:3000/zh-TW/solutions"/>
<xhtml:link rel="alternate" hreflang="en" href="http://localhost:3000/en/solutions"/>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<!-- Pricing pages -->
<url>
<loc>http://localhost:3000/zh-CN/pricing</loc>
<xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:3000/zh-CN/pricing"/>
<xhtml:link rel="alternate" hreflang="zh-TW" href="http://localhost:3000/zh-TW/pricing"/>
<xhtml:link rel="alternate" hreflang="en" href="http://localhost:3000/en/pricing"/>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<!-- News pages -->
<url>
<loc>http://localhost:3000/zh-CN/news</loc>
<xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:3000/zh-CN/news"/>
<xhtml:link rel="alternate" hreflang="zh-TW" href="http://localhost:3000/zh-TW/news"/>
<xhtml:link rel="alternate" hreflang="en" href="http://localhost:3000/en/news"/>
<changefreq>daily</changefreq>
<priority>0.7</priority>
</url>
<!-- Contact pages -->
<url>
<loc>http://localhost:3000/zh-CN/contact</loc>
<xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:3000/zh-CN/contact"/>
<xhtml:link rel="alternate" hreflang="zh-TW" href="http://localhost:3000/zh-TW/contact"/>
<xhtml:link rel="alternate" hreflang="en" href="http://localhost:3000/en/contact"/>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
</urlset>`;
return new NextResponse(sitemap, {
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
},
});
}

BIN
bun.lockb Normal file

Binary file not shown.

20
components.json Normal file
View File

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

277
components/Footer.tsx Normal file
View File

@ -0,0 +1,277 @@
'use client';
import { useState } from 'react';
import type { ReactNode } from 'react';
export interface FooterProps {
content: {
footer: {
company: {
name: string;
description: string;
slogan: string;
};
social: {
title: string;
links: {
name: string;
href: string;
icon: ReactNode;
}[];
};
quickLinks: {
title: string;
links: {
name: string;
href: string;
}[];
};
services: {
title: string;
items: string[];
};
contact: {
title: string;
address: string;
phone: string;
email: string;
hours: string;
};
newsletter: {
title: string;
description: string;
placeholder: string;
button: string;
};
certifications: {
title: string;
items: string[];
};
copyright: string;
icp: string;
legal: {
links: {
name: string;
href: string;
}[];
};
sitemap: string;
};
};
createLocalizedPath: (path: string) => string;
}
export default function Footer({ content, createLocalizedPath }: FooterProps) {
const [email, setEmail] = useState('');
const [isSubscribed, setIsSubscribed] = useState(false);
const handleSubscribe = (e: React.FormEvent) => {
e.preventDefault();
if (email) {
setIsSubscribed(true);
setEmail('');
setTimeout(() => setIsSubscribed(false), 3000);
}
};
return (
<footer className="relative z-10 bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white">
{/* 主体内容 */}
<div className="max-w-7xl mx-auto px-6 py-16">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12">
{/* 公司信息 */}
<div className="lg:col-span-1">
<div className="mb-6">
<h3 className="text-2xl font-bold bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent mb-3">
{content.footer.company.name}
</h3>
<p className="text-gray-300 text-sm leading-relaxed mb-4">
{content.footer.company.description}
</p>
<p className="text-cyan-400 font-medium text-sm italic">
{content.footer.company.slogan}
</p>
</div>
<div>
<h4 className="text-lg font-semibold mb-4 text-gray-200">
{content.footer.social.title}
</h4>
<div className="flex space-x-4 mb-6">
{content.footer.social.links.map((social, idx) => (
<a
key={idx}
href={social.href}
className="w-10 h-10 bg-slate-700 hover:bg-gradient-to-r hover:from-cyan-500 hover:to-blue-500 rounded-lg flex items-center justify-center transition-all duration-300 transform hover:scale-110 hover:shadow-lg"
title={social.name}
>
<span className="text-lg">{social.icon}</span>
</a>
))}
</div>
</div>
</div>
{/* 快速链接 */}
<div>
<h4 className="text-lg font-semibold mb-6 text-gray-200">
{content.footer.quickLinks.title}
</h4>
<ul className="space-y-3">
{content.footer.quickLinks.links.map((link, idx) => (
<li key={idx}>
<a
href={createLocalizedPath(link.href)}
className="text-gray-400 hover:text-cyan-400 transition-colors duration-200 text-sm flex items-center group"
>
<span className="w-1 h-1 bg-cyan-400 rounded-full mr-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200" />
{link.name}
</a>
</li>
))}
</ul>
</div>
{/* 核心服务 */}
<div>
<h4 className="text-lg font-semibold mb-6 text-gray-200">
{content.footer.services.title}
</h4>
<ul className="space-y-3">
{content.footer.services.items.map((service, idx) => (
<li key={idx} className="text-gray-400 text-sm flex items-center">
<span className="w-1 h-1 bg-blue-400 rounded-full mr-3" />
{service}
</li>
))}
</ul>
</div>
{/* 联系信息 & 订阅 */}
<div>
<h4 className="text-lg font-semibold mb-6 text-gray-200">
{content.footer.contact.title}
</h4>
<div className="space-y-4 mb-8">
<div className="flex items-start space-x-3">
<span className="text-cyan-400 mt-1">📍</span>
<span className="text-gray-400 text-sm">
{content.footer.contact.address}
</span>
</div>
<div className="flex items-center space-x-3">
<span className="text-cyan-400">📞</span>
<a
href={`tel:${content.footer.contact.phone}`}
className="text-gray-400 hover:text-cyan-400 transition-colors text-sm"
>
{content.footer.contact.phone}
</a>
</div>
<div className="flex items-center space-x-3">
<span className="text-cyan-400"></span>
<a
href={`mailto:${content.footer.contact.email}`}
className="text-gray-400 hover:text-cyan-400 transition-colors text-sm"
>
{content.footer.contact.email}
</a>
</div>
<div className="flex items-center space-x-3">
<span className="text-cyan-400">🕒</span>
<span className="text-gray-400 text-sm">
{content.footer.contact.hours}
</span>
</div>
</div>
<div>
<h5 className="text-md font-semibold mb-3 text-gray-200">
{content.footer.newsletter.title}
</h5>
<p className="text-gray-400 text-xs mb-4">
{content.footer.newsletter.description}
</p>
<form onSubmit={handleSubscribe} className="space-y-3">
<div className="relative">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder={content.footer.newsletter.placeholder}
className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-cyan-400 focus:ring-1 focus:ring-cyan-400 text-sm"
required
/>
</div>
<button
type="submit"
className="w-full bg-gradient-to-r from-cyan-500 to-blue-500 hover:from-cyan-600 hover:to-blue-600 text-white py-2 px-4 rounded-lg transition-all duration-200 transform hover:scale-105 text-sm font-medium"
>
{isSubscribed
? `${content.footer.newsletter.button}`
: content.footer.newsletter.button}
</button>
</form>
</div>
</div>
</div>
</div>
{/* 资质认证 */}
<div className="mt-12 pt-8 border-t border-slate-700">
<h4 className="text-lg font-semibold mb-6 text-gray-200 text-center">
{content.footer.certifications.title}
</h4>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{content.footer.certifications.items.map((cert, idx) => (
<div
key={idx}
className="bg-slate-800 rounded-lg p-4 text-center hover:bg-slate-700 transition-colors duration-200"
>
<div className="text-2xl mb-2">🏆</div>
<span className="text-gray-300 text-xs font-medium">{cert}</span>
</div>
))}
</div>
</div>
{/* 底部版权 & 法律链接 */}
<div className="border-t border-slate-700 bg-slate-900">
<div className="max-w-7xl mx-auto px-6 py-6">
<div className="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
<div className="text-center md:text-left">
<p className="text-gray-400 text-sm">{content.footer.copyright}</p>
<p className="text-gray-500 text-xs mt-1">{content.footer.icp}</p>
</div>
<div className="flex flex-wrap justify-center md:justify-end space-x-6">
{content.footer.legal.links.map((link, idx) => (
<a
key={idx}
href={link.href}
className="text-gray-400 hover:text-cyan-400 transition-colors duration-200 text-xs"
>
{link.name}
</a>
))}
<a
href="/sitemap.xml"
className="text-gray-400 hover:text-cyan-400 transition-colors duration-200 text-xs"
target="_blank"
rel="noopener noreferrer"
>
{content.footer.sitemap}
</a>
</div>
</div>
</div>
</div>
{/* 装饰背景 */}
<div className="absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none">
<div className="absolute top-10 left-10 w-32 h-32 bg-cyan-500/5 rounded-full blur-xl" />
<div className="absolute bottom-10 right-10 w-40 h-40 bg-blue-500/5 rounded-full blur-xl" />
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-purple-500/3 rounded-full blur-2xl" />
</div>
</footer>
);
}

156
components/Navigation.tsx Normal file
View File

@ -0,0 +1,156 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useState } from 'react';
import { LanguageType } from '../lib/content';
interface NavigationProps {
/** 🔧 修改 #1currentLang 从 string 改为 LanguageType */
currentLang: LanguageType;
/** 🔧 修改 #2setCurrentLang 返回值改为 void原来写成了 string签名也保持只接受 LanguageType */
setCurrentLang: (lang: LanguageType) => void;
content: {
nav: {
home: string;
services: string;
solutions: string;
pricing: string;
news: string;
contact: string;
};
};
/** 🔧 修改 #3createLocalizedPath 的签名改为 (path: string) => string */
createLocalizedPath?: (path: string) => string;
}
export default function Navigation({
currentLang,
setCurrentLang,
content,
createLocalizedPath,
}: NavigationProps) {
const pathname = usePathname();
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
// Helper to build localized URLs
const getLocalizedPath = (path: string) => {
return createLocalizedPath ? createLocalizedPath(path) : path;
};
const navItems = [
{ label: content.nav.home, href: getLocalizedPath('/') },
{ label: content.nav.services, href: getLocalizedPath('/services') },
{ label: content.nav.solutions, href: getLocalizedPath('/solutions') },
{ label: content.nav.pricing, href: getLocalizedPath('/pricing') },
{ label: content.nav.news, href: getLocalizedPath('/news') },
{ label: content.nav.contact, href: getLocalizedPath('/contact') },
];
return (
<nav className="relative z-10 px-6 py-4 border-b border-cyan-500/20 backdrop-blur-sm">
<div className="max-w-7xl mx-auto flex justify-between items-center">
{/* Logo */}
<Link
href="/"
className="text-2xl font-bold bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent hover:scale-105 transition-transform duration-300"
>
CloudAgency
</Link>
{/* Desktop Nav */}
<div className="hidden md:flex space-x-8">
{navItems.map((item, idx) => {
const isActive = pathname === item.href;
return (
<Link
key={idx}
href={item.href}
className={`relative group transition-colors duration-300 ${
isActive ? 'text-cyan-400' : 'text-gray-300 hover:text-cyan-400'
}`}
>
{item.label}
<span
className={`absolute -bottom-1 left-0 h-0.5 bg-cyan-400 transition-all duration-300 ${
isActive ? 'w-full' : 'w-0 group-hover:w-full'
}`}
/>
</Link>
);
})}
</div>
{/* Right side */}
<div className="flex items-center space-x-4">
{/* 🔧 修改 #4onChange 时将 value 强制断言为 LanguageType */}
<select
value={currentLang}
onChange={(e) => setCurrentLang(e.target.value as LanguageType)}
className="bg-gray-800 border border-cyan-500/30 rounded-lg px-3 py-1 text-sm focus:outline-none focus:border-cyan-400 transition-colors"
>
<option value="zh-CN"></option>
<option value="zh-TW"></option>
<option value="en">English</option>
</select>
{/* Mobile menu button */}
<button
className="md:hidden text-gray-300 hover:text-cyan-400 transition-colors"
onClick={() => setIsMobileMenuOpen((o) => !o)}
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
{isMobileMenuOpen ? (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
) : (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
)}
</svg>
</button>
</div>
</div>
{/* Mobile Menu */}
{isMobileMenuOpen && (
<div className="md:hidden absolute top-full left-0 right-0 bg-gray-900/95 backdrop-blur-sm border-b border-cyan-500/20">
<div className="px-6 py-4 space-y-4">
{navItems.map((item, idx) => {
const isActive = pathname === item.href;
return (
<Link
key={idx}
href={item.href}
className={`block py-2 transition-colors duration-300 ${
isActive
? 'text-cyan-400'
: 'text-gray-300 hover:text-cyan-400'
}`}
onClick={() => setIsMobileMenuOpen(false)}
>
{item.label}
</Link>
);
})}
</div>
</div>
)}
</nav>
);
}

49
hooks/useLanguage.ts Normal file
View File

@ -0,0 +1,49 @@
// hooks/useLanguage.tsx
'use client';
import { useState, useEffect } from 'react';
import { useRouter, usePathname } from 'next/navigation';
import { content, LanguageType, ContentType } from '../lib/content';
export function useLanguage(
initialLang: string
): {
currentLang: LanguageType;
setCurrentLang: (newLang: LanguageType) => void;
createLocalizedPath: (path: string) => string;
currentContent: ContentType;
} {
const router = useRouter();
const pathname = usePathname();
const SUPPORTED = ['en', 'zh-CN', 'zh-TW'] as const;
const normalized = (SUPPORTED.includes(initialLang as any)
? initialLang
: 'en') as LanguageType;
const [currentLang, setLang] = useState<LanguageType>(normalized);
useEffect(() => {
setLang(normalized);
}, [normalized]);
const setCurrentLang = (newLang: LanguageType) => {
const parts = pathname.split('/').filter(Boolean);
const rest = parts.slice(1).join('/');
router.push(`/${newLang}${rest ? '/' + rest : ''}`);
};
const createLocalizedPath = (path: string) =>
path.startsWith('/')
? `/${currentLang}${path}`
: `/${currentLang}/${path}`;
// —— 关键:从 content 里取出当前语言的数据 ——
const currentContent: ContentType = content[currentLang];
return {
currentLang,
setCurrentLang,
createLocalizedPath,
currentContent,
};
}

1326
lib/content copy.ts Normal file

File diff suppressed because it is too large Load Diff

1326
lib/content.ts Normal file

File diff suppressed because it is too large Load Diff

6
lib/utils.ts Normal file
View File

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

10
next.config.mjs Normal file
View File

@ -0,0 +1,10 @@
import path from 'path';
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
// 把 .next 目录改成 build/.next
distDir: 'build',
// 如果你希望所有页面都带尾部斜杠(可选)
trailingSlash: true,
};
export default nextConfig;

5582
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
package.json Normal file
View File

@ -0,0 +1,34 @@
{
"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",
"lucide-react": "^0.438.0",
"next": "14.2.23",
"react": "^18",
"react-dom": "^18",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "^15.1.6",
"postcss": "^8",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}

8
postcss.config.mjs Normal file
View File

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

348
public/sitemap.xsl Normal file
View File

@ -0,0 +1,348 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<html lang="zh-CN">
<head>
<title>网站地图 - Sitemap</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
color: white;
padding: 40px;
text-align: center;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="50" cy="10" r="0.5" fill="rgba(255,255,255,0.05)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
animation: float 20s ease-in-out infinite;
pointer-events: none;
}
@keyframes float {
0%, 100% { transform: translateX(0px) translateY(0px); }
50% { transform: translateX(-20px) translateY(-10px); }
}
.header h1 {
font-size: 3rem;
font-weight: 700;
margin-bottom: 10px;
position: relative;
z-index: 1;
}
.header p {
font-size: 1.2rem;
opacity: 0.9;
position: relative;
z-index: 1;
}
.stats {
display: flex;
justify-content: space-around;
padding: 30px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
}
.stat-item {
text-align: center;
padding: 20px;
background: white;
border-radius: 15px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
min-width: 120px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.stat-item:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
}
.stat-number {
font-size: 2rem;
font-weight: 700;
color: #4f46e5;
display: block;
}
.stat-label {
color: #64748b;
font-size: 0.9rem;
margin-top: 5px;
}
.content {
padding: 40px;
}
.url-list {
display: grid;
gap: 20px;
}
.url-item {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 15px;
padding: 25px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.url-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: linear-gradient(135deg, #4f46e5, #7c3aed);
transform: scaleY(0);
transition: transform 0.3s ease;
}
.url-item:hover {
transform: translateX(10px);
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
border-color: #4f46e5;
}
.url-item:hover::before {
transform: scaleY(1);
}
.url-main {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.url-icon {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #4f46e5, #7c3aed);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
color: white;
font-weight: bold;
}
.url-link {
color: #4f46e5;
text-decoration: none;
font-weight: 600;
font-size: 1.1rem;
transition: color 0.3s ease;
}
.url-link:hover {
color: #7c3aed;
text-decoration: underline;
}
.url-meta {
display: flex;
gap: 20px;
margin-bottom: 15px;
}
.meta-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #f1f5f9;
border-radius: 8px;
font-size: 0.9rem;
color: #64748b;
}
.priority-high { background: #fef3c7; color: #d97706; }
.priority-medium { background: #dbeafe; color: #2563eb; }
.priority-low { background: #f3f4f6; color: #6b7280; }
.lang-links {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.lang-link {
padding: 6px 12px;
background: #e0e7ff;
color: #4338ca;
text-decoration: none;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
transition: all 0.3s ease;
border: 1px solid transparent;
}
.lang-link:hover {
background: #4338ca;
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(67, 56, 202, 0.3);
}
.footer {
background: #1e293b;
color: #94a3b8;
text-align: center;
padding: 30px;
font-size: 0.9rem;
}
.footer a {
color: #60a5fa;
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
.header h1 { font-size: 2rem; }
.stats { flex-direction: column; gap: 15px; }
.url-meta { flex-direction: column; gap: 10px; }
.content { padding: 20px; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🗺️ 网站地图</h1>
<p>探索我们网站的所有页面和多语言版本</p>
</div>
<div class="stats">
<div class="stat-item">
<span class="stat-number"><xsl:value-of select="count(//sitemap:url)"/></span>
<div class="stat-label">总页面数</div>
</div>
<div class="stat-item">
<span class="stat-number">3</span>
<div class="stat-label">支持语言</div>
</div>
<div class="stat-item">
<span class="stat-number"><xsl:value-of select="count(//sitemap:url[sitemap:priority='1.0'])"/></span>
<div class="stat-label">高优先级</div>
</div>
<div class="stat-item">
<span class="stat-number"><xsl:value-of select="count(//sitemap:url[sitemap:changefreq='daily'])"/></span>
<div class="stat-label">每日更新</div>
</div>
</div>
<div class="content">
<div class="url-list">
<xsl:for-each select="sitemap:urlset/sitemap:url">
<div class="url-item">
<div class="url-main">
<div class="url-icon">
<xsl:choose>
<xsl:when test="contains(sitemap:loc, '/about')">👥</xsl:when>
<xsl:when test="contains(sitemap:loc, '/services')">🛠️</xsl:when>
<xsl:when test="contains(sitemap:loc, '/solutions')">💡</xsl:when>
<xsl:when test="contains(sitemap:loc, '/pricing')">💰</xsl:when>
<xsl:when test="contains(sitemap:loc, '/news')">📰</xsl:when>
<xsl:when test="contains(sitemap:loc, '/contact')">📞</xsl:when>
<xsl:otherwise>🏠</xsl:otherwise>
</xsl:choose>
</div>
<a href="{sitemap:loc}" class="url-link" target="_blank">
<xsl:value-of select="sitemap:loc"/>
</a>
</div>
<div class="url-meta">
<div class="meta-item">
<span>📅</span>
<span>更新频率: <xsl:value-of select="sitemap:changefreq"/></span>
</div>
<div>
<xsl:attribute name="class">
meta-item
<xsl:choose>
<xsl:when test="sitemap:priority >= 0.8"> priority-high</xsl:when>
<xsl:when test="sitemap:priority >= 0.7"> priority-medium</xsl:when>
<xsl:otherwise> priority-low</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<span></span>
<span>优先级: <xsl:value-of select="sitemap:priority"/></span>
</div>
</div>
<div class="lang-links">
<xsl:for-each select="xhtml:link">
<a href="{@href}" class="lang-link" target="_blank">
<xsl:choose>
<xsl:when test="@hreflang='zh-CN'">🇨🇳 简体中文</xsl:when>
<xsl:when test="@hreflang='zh-TW'">🇹🇼 繁體中文</xsl:when>
<xsl:when test="@hreflang='en'">🇺🇸 English</xsl:when>
<xsl:otherwise><xsl:value-of select="@hreflang"/></xsl:otherwise>
</xsl:choose>
</a>
</xsl:for-each>
</div>
</div>
</xsl:for-each>
</div>
</div>
<div class="footer">
<p>此站点地图包含了网站的所有公开页面 | 生成时间: <script>document.write(new Date().toLocaleString('zh-CN'))</script></p>
<p>支持多语言访问,点击语言标签可切换到对应版本</p>
</div>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

67
tailwind.config.ts Normal file
View File

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

40
tsconfig.json Normal file
View File

@ -0,0 +1,40 @@
{
"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",
"build/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}