feat: add new file

This commit is contained in:
丹尼尔
2026-03-10 17:13:55 +08:00
commit 551abd1b68
4156 changed files with 805309 additions and 0 deletions

33
frontend/src/App.jsx Normal file
View File

@@ -0,0 +1,33 @@
import { useState } from 'react'
import Header from './components/Header'
import Hero from './components/Hero'
import ClawLoop from './components/ClawLoop'
import CoreFeatures from './components/CoreFeatures'
import Pricing from './components/Pricing'
import NewsDocs from './components/NewsDocs'
import StatsFooter from './components/StatsFooter'
import ActivateModal from './components/ActivateModal'
import QrModal from './components/QrModal'
function App() {
const [activateOpen, setActivateOpen] = useState(false)
const [qrOpen, setQrOpen] = useState(false)
return (
<div className="min-h-screen bg-space-black">
<Header onActivate={() => setActivateOpen(true)} />
<main>
<Hero onLead={() => setQrOpen(true)} />
<ClawLoop />
<CoreFeatures />
<Pricing />
<NewsDocs />
</main>
<StatsFooter />
<ActivateModal open={activateOpen} onClose={() => setActivateOpen(false)} />
<QrModal open={qrOpen} onClose={() => setQrOpen(false)} />
</div>
)
}
export default App

18
frontend/src/api.js Normal file
View File

@@ -0,0 +1,18 @@
const API_BASE = import.meta.env.VITE_API_URL || '/api'
export async function getStats() {
const res = await fetch(`${API_BASE}/stats`)
if (!res.ok) throw new Error('Failed to fetch stats')
return res.json()
}
export async function validateActivationCode(code) {
const res = await fetch(`${API_BASE}/activate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: code.trim() }),
})
const data = await res.json().catch(() => ({}))
if (!res.ok) throw new Error(data.message || '激活失败')
return data
}

View File

@@ -0,0 +1,89 @@
import { useState } from 'react'
import { validateActivationCode } from '../api'
export default function ActivateModal({ open, onClose }) {
const [code, setCode] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [success, setSuccess] = useState(false)
const handleSubmit = async (e) => {
e.preventDefault()
if (!code.trim()) return
setError('')
setLoading(true)
try {
await validateActivationCode(code)
setSuccess(true)
setCode('')
} catch (err) {
setError(err.message || '激活失败,请检查激活码')
} finally {
setLoading(false)
}
}
if (!open) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm" onClick={onClose}>
<div
className="w-full max-w-md rounded-2xl border border-white/10 bg-space-black-light p-6 shadow-xl"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-6">
<h3 className="text-xl font-semibold text-white">线下激活</h3>
<button className="p-2 text-gray-400 hover:text-white" onClick={onClose} aria-label="关闭">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{success ? (
<div className="text-center py-4">
<p className="text-brand-orange font-medium mb-2">激活成功</p>
<p className="text-gray-400 text-sm">请在小程序中绑定此设备继续使用</p>
<button
className="mt-4 px-4 py-2 rounded-lg bg-brand-orange text-white"
onClick={() => { setSuccess(false); onClose(); }}
>
完成
</button>
</div>
) : (
<form onSubmit={handleSubmit}>
<p className="text-gray-400 text-sm mb-4">
请输入现场获得的激活码关联小程序后即可使用阿虾
</p>
<input
type="text"
value={code}
onChange={(e) => setCode(e.target.value)}
placeholder="请输入激活码"
className="w-full px-4 py-3 rounded-xl border border-white/20 bg-space-black text-white placeholder-gray-500 focus:border-brand-orange focus:outline-none"
autoFocus
/>
{error && <p className="mt-2 text-sm text-red-400">{error}</p>}
<div className="mt-6 flex gap-3">
<button
type="button"
className="flex-1 py-3 rounded-xl border border-white/20 text-white hover:bg-white/5"
onClick={onClose}
>
取消
</button>
<button
type="submit"
disabled={loading}
className="flex-1 py-3 rounded-xl bg-brand-orange text-white font-medium hover:bg-brand-orange-dark disabled:opacity-50"
>
{loading ? '验证中…' : '激活'}
</button>
</div>
</form>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,51 @@
const steps = [
{
title: '极速装机',
desc: '一行命令Docker 镜像秒级部署。',
icon: '⚡',
},
{
title: '多端唤醒',
desc: '在熟悉的 IM 软件里指挥 AI 干活。',
icon: '💬',
},
{
title: '算力自由',
desc: '全网最低价 Token 供应,多模型一键切换。',
icon: '🔌',
},
]
export default function ClawLoop() {
return (
<section className="py-16 sm:py-24 px-4 sm:px-6 bg-space-black-light/50">
<div className="max-w-5xl mx-auto">
<h2 className="text-2xl sm:text-3xl font-bold text-white text-center mb-4">
The A-Claw 闭环
</h2>
<p className="text-gray-400 text-center mb-12">
从部署到使用一条龙搞定
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 md:gap-8">
{steps.map((step, i) => (
<div key={i} className="relative">
<div className="card-hover rounded-2xl border border-white/10 bg-space-black-lighter/80 p-6 sm:p-8 h-full">
<div className="text-3xl mb-4">{step.icon}</div>
<h3 className="text-xl font-semibold text-white mb-2">{step.title}</h3>
<p className="text-gray-400">{step.desc}</p>
</div>
{i < steps.length - 1 && (
<div className="hidden md:block absolute top-1/2 -right-4 w-8 text-brand-orange/50" style={{ transform: 'translateY(-50%)' }}>
<svg viewBox="0 0 24 24" fill="currentColor" className="w-6 h-6">
<path d="M8 5v14l11-7z" />
</svg>
</div>
)}
</div>
))}
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,42 @@
/** 3D 机械螯爪A-Claw线条 - 首屏背景 */
export default function ClawSvg() {
return (
<svg
className="absolute inset-0 w-full h-full pointer-events-none animate-claw"
viewBox="0 0 800 600"
fill="none"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid slice"
>
<defs>
<linearGradient id="clawGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#FF6B35" stopOpacity="0.15" />
<stop offset="100%" stopColor="#FF6B35" stopOpacity="0.05" />
</linearGradient>
</defs>
{/* Simplified mechanical claw outline - left pincer */}
<path
d="M 200 280 Q 280 240 360 260 L 400 300 L 380 340 Q 300 360 220 320 Z"
stroke="url(#clawGrad)"
strokeWidth="1.5"
fill="none"
opacity="0.6"
/>
{/* Right pincer */}
<path
d="M 600 280 Q 520 240 440 260 L 400 300 L 420 340 Q 500 360 580 320 Z"
stroke="url(#clawGrad)"
strokeWidth="1.5"
fill="none"
opacity="0.6"
/>
{/* Center joint */}
<circle cx="400" cy="300" r="24" stroke="#FF6B35" strokeWidth="1" fill="none" opacity="0.25" />
<circle cx="400" cy="300" r="8" stroke="#FF6B35" strokeWidth="1" fill="none" opacity="0.4" />
{/* Arm lines */}
<path d="M 400 300 L 400 180" stroke="#FF6B35" strokeWidth="1" opacity="0.2" />
<path d="M 400 300 L 320 220" stroke="#FF6B35" strokeWidth="0.8" opacity="0.15" />
<path d="M 400 300 L 480 220" stroke="#FF6B35" strokeWidth="0.8" opacity="0.15" />
</svg>
)
}

View File

@@ -0,0 +1,34 @@
const features = [
{ title: 'IM 全适配', desc: '原生支持钉钉、企微、飞书、QQ。', emoji: '📱' },
{ title: '模型聚合', desc: '内置 OpenAI, Claude, DeepSeek 等主流模型。', emoji: '🧠' },
{ title: '云端常驻', desc: '7x24 小时在线,无需本地挂机。', emoji: '☁️' },
{ title: '隐私安全', desc: '容器化隔离,密钥存储在用户自己的环境中。', emoji: '🔒' },
]
export default function CoreFeatures() {
return (
<section id="features" className="py-16 sm:py-24 px-4 sm:px-6">
<div className="max-w-5xl mx-auto">
<h2 className="text-2xl sm:text-3xl font-bold text-white text-center mb-4">
核心功能
</h2>
<p className="text-gray-400 text-center mb-12">
技术实力一网打尽
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6">
{features.map((f, i) => (
<div
key={i}
className="card-hover rounded-xl border border-white/10 bg-space-black-lighter/60 p-5 sm:p-6"
>
<div className="text-2xl mb-3">{f.emoji}</div>
<h3 className="font-semibold text-white mb-2">{f.title}</h3>
<p className="text-gray-400 text-sm">{f.desc}</p>
</div>
))}
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,58 @@
import { useState } from 'react'
export default function Header({ onActivate }) {
const [menuOpen, setMenuOpen] = useState(false)
return (
<header className="fixed top-0 left-0 right-0 z-50 bg-space-black/90 backdrop-blur-md border-b border-white/5">
<div className="max-w-6xl mx-auto px-4 sm:px-6 flex items-center justify-between h-14 sm:h-16">
<a href="/" className="flex items-center gap-2">
<img src="/image/logo.png" alt="Aiclw" className="h-8 w-8 sm:h-9 sm:w-9 rounded-lg" />
<span className="font-bold text-lg text-white">Aiclw</span>
</a>
<nav className="hidden md:flex items-center gap-6">
<a href="#features" className="text-gray-400 hover:text-white transition">功能</a>
<a href="#pricing" className="text-gray-400 hover:text-white transition">价格</a>
<a href="#docs" className="text-gray-400 hover:text-white transition">文档</a>
<button
onClick={onActivate}
className="px-4 py-2 rounded-lg bg-brand-orange text-white font-medium hover:bg-brand-orange-dark transition"
>
线下激活
</button>
</nav>
<button
className="md:hidden p-2 text-gray-400 hover:text-white"
onClick={() => setMenuOpen(!menuOpen)}
aria-label="菜单"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{menuOpen ? (
<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>
{menuOpen && (
<div className="md:hidden border-t border-white/5 bg-space-black">
<div className="px-4 py-3 flex flex-col gap-2">
<a href="#features" className="py-2 text-gray-400 hover:text-white" onClick={() => setMenuOpen(false)}>功能</a>
<a href="#pricing" className="py-2 text-gray-400 hover:text-white" onClick={() => setMenuOpen(false)}>价格</a>
<a href="#docs" className="py-2 text-gray-400 hover:text-white" onClick={() => setMenuOpen(false)}>文档</a>
<button
onClick={() => { onActivate(); setMenuOpen(false); }}
className="mt-2 py-3 rounded-lg bg-brand-orange text-white font-medium"
>
线下激活
</button>
</div>
</div>
)}
</header>
)
}

View File

@@ -0,0 +1,48 @@
import ClawSvg from './ClawSvg'
export default function Hero({ onLead }) {
return (
<section className="relative min-h-screen flex items-center justify-center overflow-hidden pt-14">
{/* Background: subtle grid + claw lines */}
<div className="absolute inset-0 bg-gradient-to-b from-space-black via-space-black to-space-black-light" />
<div className="absolute inset-0 opacity-20" style={{ backgroundImage: 'radial-gradient(circle at 50% 50%, #FF6B35 0%, transparent 50%)' }} />
<div className="absolute inset-0">
<ClawSvg />
</div>
<div className="relative z-10 max-w-4xl mx-auto px-4 sm:px-6 text-center">
<h1 className="text-4xl sm:text-5xl md:text-6xl font-bold text-white leading-tight mb-4 sm:mb-6">
AI 长出手<br className="sm:hidden" />在云端走
</h1>
<p className="text-gray-400 text-lg sm:text-xl max-w-2xl mx-auto mb-8 sm:mb-10">
一键部署你的 OpenClaw 数字员工打通钉钉企微飞书让自动化触手可及
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<button
onClick={onLead}
className="px-6 py-3.5 rounded-xl bg-brand-orange text-white font-semibold hover:bg-brand-orange-dark transition shadow-lg shadow-brand-orange/25"
>
立即领养阿虾
</button>
<a
href="https://github.com" target="_blank" rel="noopener noreferrer"
className="px-6 py-3.5 rounded-xl border border-white/20 text-white font-medium hover:border-brand-orange hover:bg-white/5 transition"
>
查看部署文档
</a>
</div>
</div>
{/* Decorative code flow */}
<div className="absolute bottom-8 left-0 right-0 overflow-hidden opacity-40">
<div className="flex animate-flow whitespace-nowrap font-mono text-sm text-brand-orange/80">
<span className="mr-8">$ docker pull aiclw/openclaw</span>
<span className="mr-8"> 钉钉 / 企微 / 飞书 已就绪</span>
<span className="mr-8">token: ********</span>
<span className="mr-8">$ docker pull aiclw/openclaw</span>
<span className="mr-8"> 钉钉 / 企微 / 飞书 已就绪</span>
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,48 @@
const items = [
{
title: '阿虾周报',
desc: '每周更新 AI Agent 的前沿玩法,为小程序导流。',
link: '#',
label: '阅读周报',
},
{
title: '新手村',
desc: '从 Docker 安装到第一个 IM 机器人上线的保姆级教程。',
link: '#',
label: '进入新手村',
},
]
export default function NewsDocs() {
return (
<section id="docs" className="py-16 sm:py-24 px-4 sm:px-6">
<div className="max-w-5xl mx-auto">
<h2 className="text-2xl sm:text-3xl font-bold text-white text-center mb-4">
内容与文档
</h2>
<p className="text-gray-400 text-center mb-12">
阿虾周报 · 新手村
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
{items.map((item, i) => (
<a
key={i}
href={item.link}
className="card-hover block rounded-2xl border border-white/10 bg-space-black-lighter/60 p-6 sm:p-8 hover:border-brand-orange/50"
>
<h3 className="text-xl font-semibold text-white mb-2">{item.title}</h3>
<p className="text-gray-400 text-sm mb-4">{item.desc}</p>
<span className="text-brand-orange font-medium text-sm inline-flex items-center gap-1">
{item.label}
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</span>
</a>
))}
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,76 @@
const plans = [
{
name: '免费版',
price: '0',
unit: '元部署',
desc: '使用自带 Key提供基础社区支持。',
cta: '免费开始',
highlight: false,
},
{
name: '阿虾订阅版',
badge: '推荐',
price: '19.9',
unit: '元/月',
desc: '低至 ¥19.9/月赠送百万级「虾油」Token支持专属模型通道。',
cta: '立即订阅',
highlight: true,
},
{
name: '企业定制',
price: '定制',
unit: '',
desc: '针对线下场景的批量部署与私有化协议。',
cta: '联系商务',
highlight: false,
},
]
export default function Pricing() {
return (
<section id="pricing" className="py-16 sm:py-24 px-4 sm:px-6 bg-space-black-light/50">
<div className="max-w-5xl mx-auto">
<h2 className="text-2xl sm:text-3xl font-bold text-white text-center mb-4">
价格与算力
</h2>
<p className="text-gray-400 text-center mb-12">
按需选择透明定价
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{plans.map((plan, i) => (
<div
key={i}
className={`rounded-2xl border p-6 sm:p-8 flex flex-col ${
plan.highlight
? 'border-brand-orange bg-brand-orange/5 shadow-lg shadow-brand-orange/10'
: 'border-white/10 bg-space-black-lighter/60 card-hover'
}`}
>
{plan.badge && (
<span className="inline-block px-3 py-1 rounded-full bg-brand-orange text-white text-sm font-medium mb-4 w-fit">
{plan.badge}
</span>
)}
<h3 className="text-xl font-semibold text-white mb-2">{plan.name}</h3>
<div className="mb-4">
<span className="text-3xl font-bold text-white">{plan.price}</span>
{plan.unit && <span className="text-gray-400 ml-1">{plan.unit}</span>}
</div>
<p className="text-gray-400 text-sm flex-1 mb-6">{plan.desc}</p>
<button
className={`w-full py-3 rounded-xl font-medium transition ${
plan.highlight
? 'bg-brand-orange text-white hover:bg-brand-orange-dark'
: 'border border-white/20 text-white hover:border-brand-orange hover:bg-white/5'
}`}
>
{plan.cta}
</button>
</div>
))}
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,29 @@
export default function QrModal({ open, onClose }) {
if (!open) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm" onClick={onClose}>
<div
className="w-full max-w-sm rounded-2xl border border-white/10 bg-space-black-light p-6 shadow-xl text-center"
onClick={(e) => e.stopPropagation()}
>
<div className="flex justify-end -mt-2 -mr-2 mb-2">
<button className="p-2 text-gray-400 hover:text-white" onClick={onClose} aria-label="关闭">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<h3 className="text-lg font-semibold text-white mb-2">立即领养阿虾</h3>
<p className="text-gray-400 text-sm mb-6">扫码打开小程序领养你的数字员工</p>
<div className="inline-flex items-center justify-center w-48 h-48 rounded-xl bg-white p-2">
{/* 占位:实际替换为小程序二维码图片 */}
<div className="w-full h-full rounded-lg bg-gray-200 flex items-center justify-center text-gray-500 text-sm">
小程序二维码
</div>
</div>
<p className="mt-4 text-gray-500 text-xs">请使用微信扫码</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,51 @@
import { useState, useEffect } from 'react'
import { getStats } from '../api'
const fallback = {
nodes: 1280,
tasksToday: 45000,
hoursSaved: 8600,
}
function formatNum(n) {
if (n >= 10000) return (n / 10000).toFixed(1) + '万+'
return n.toLocaleString() + '+'
}
export default function StatsFooter() {
const [stats, setStats] = useState(fallback)
const [loading, setLoading] = useState(true)
useEffect(() => {
getStats()
.then(setStats)
.catch(() => setStats(fallback))
.finally(() => setLoading(false))
}, [])
const items = [
{ label: '已激活「阿虾」节点', value: formatNum(stats.nodes) },
{ label: '今日处理自动化任务', value: formatNum(stats.tasksToday) },
{ label: '已节省人力时长', value: formatNum(stats.hoursSaved) + ' 小时' },
]
return (
<footer className="border-t border-white/10 bg-space-black">
<div className="max-w-5xl mx-auto px-4 sm:px-6 py-12">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-8 text-center">
{items.map((item, i) => (
<div key={i}>
<div className="text-2xl sm:text-3xl font-bold text-brand-orange mb-1">
{loading ? '—' : item.value}
</div>
<div className="text-gray-400 text-sm">{item.label}</div>
</div>
))}
</div>
<div className="mt-10 pt-8 border-t border-white/5 text-center text-gray-500 text-sm">
Aiclw · 连接算力执行万物
</div>
</div>
</footer>
)
}

40
frontend/src/index.css Normal file
View File

@@ -0,0 +1,40 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--brand-orange: #FF6B35;
--space-black: #121212;
}
html {
scroll-behavior: smooth;
}
body {
@apply bg-space-black text-gray-100 antialiased;
font-family: 'DM Sans', system-ui, sans-serif;
}
/* Terminal / code flow styling */
.terminal-line {
font-family: 'JetBrains Mono', monospace;
@apply text-brand-orange/90 text-sm;
}
/* Card hover - Agentic UI */
.card-hover {
@apply transition-all duration-300;
}
.card-hover:hover {
@apply border-brand-orange/50 shadow-lg shadow-brand-orange/10 -translate-y-0.5;
}
/* Claw SVG animation */
@keyframes claw-float {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-6px) rotate(1deg); }
}
.animate-claw {
animation: claw-float 4s ease-in-out infinite;
}

10
frontend/src/main.jsx Normal file
View File

@@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)