Guanghu Domestic Migration a27e87cb99 chore: import sanitized domestic snapshot for REPO-007
Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc

[SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
2026-07-17 15:59:55 +08:00

883 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 无标题
# 🚀 萌喵写作网 · 全站重设计 · 终端粘贴执行版
> 2026-06-05 · 肥猫×舒舒 · 光湖统一设计语言
>
> 服务器43.133.36.90 · 路径:`/home/ubuntu/feimao-platform/src/app/`
>
> 技术栈Next.js 14 (App Router) + Tailwind CSS v4
>
<aside>
⚠️
**每条指令单独复制粘贴到服务器终端,粘完一条等它执行完再粘下一条!**
粘贴前先 `Ctrl+C` 确保终端没有在运行别的命令。
</aside>
---
## Step 0 · 创建目录 & 清理旧配置
**复制下面这条到终端:**
```bash
mkdir -p /home/ubuntu/feimao-platform/src/app/onboarding && mkdir -p /home/ubuntu/feimao-platform/src/app/app/learn && mkdir -p /home/ubuntu/feimao-platform/src/app/app/write && mkdir -p /home/ubuntu/feimao-platform/src/app/app/script && mkdir -p /home/ubuntu/feimao-platform/src/app/app/create && mkdir -p /home/ubuntu/feimao-platform/src/app/app/forum && mkdir -p /home/ubuntu/feimao-platform/src/app/app/works && mkdir -p /home/ubuntu/feimao-platform/src/app/components && rm -f /home/ubuntu/feimao-platform/tailwind.config.ts && rm -f /home/ubuntu/feimao-platform/tailwind.config.js && echo "✅ Step 0 完成"
```
看到 `✅ Step 0 完成` 再继续。
---
## Step 1 · 光湖全局样式 globals.css
<aside>
🎨
光湖统一设计语言:深空暗色、玻璃态卡片、紫蓝渐变、金色团队通道
</aside>
```bash
cat > /home/ubuntu/feimao-platform/src/app/globals.css << 'CSSEOF'
@import "tailwindcss";
:root {
--gl-bg-deep: #0a0a14;
--gl-bg-primary: #0f0f1a;
--gl-bg-card: #1a1a2e;
--gl-bg-hover: #252540;
--gl-border: #2d2d4a;
--gl-text-primary: #e2e8f0;
--gl-text-muted: #94a3b8;
--gl-accent: #6366f1;
--gl-accent-dark: #4f46e5;
--gl-accent-light: #a5b4fc;
--gl-glow: rgba(99, 102, 241, 0.15);
--gl-success: #10b981;
--gl-danger: #ef4444;
--gl-team-gold: #c9a84c;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background-color: var(--gl-bg-deep);
color: var(--gl-text-primary);
font-family: 'Inter', 'Noto Sans SC', system-ui, sans-serif;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
::selection { background: rgba(99, 102, 241, 0.35); color: #fff; }
::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 3px; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes particleRise {
0% { opacity: 0; transform: translateY(30px) scale(0.8); }
50% { opacity: 1; }
100% { opacity: 0; transform: translateY(-60px) scale(1.2); }
}
@keyframes slideDown {
from { opacity: 0; max-height: 0; transform: translateY(-10px); }
to { opacity: 1; max-height: 300px; transform: translateY(0); }
}
.animate-fade-in-up { animation: fadeInUp 0.5s ease forwards; }
.animate-fade-in { animation: fadeIn 0.3s ease forwards; }
.gl-card {
background: var(--gl-bg-card);
border: 1px solid var(--gl-border);
border-radius: 16px;
transition: all 0.3s ease;
}
.gl-card:hover {
border-color: var(--gl-accent);
box-shadow: 0 0 20px var(--gl-glow);
}
.gl-btn {
padding: 12px 32px;
border-radius: 12px;
font-weight: 600;
font-size: 0.95rem;
transition: all 0.2s ease;
cursor: pointer;
border: none;
font-family: inherit;
letter-spacing: 0.5px;
}
.gl-btn-primary {
background: linear-gradient(135deg, var(--gl-accent), var(--gl-accent-dark));
color: white;
}
.gl-btn-primary:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4);
}
.gl-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.gl-btn-team {
background: linear-gradient(135deg, #8b6914, #6b4f12);
color: #f0e6c8;
}
.gl-btn-team:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 20px rgba(201, 168, 76, 0.4);
}
.gl-btn-outline {
background: transparent;
border: 1px solid var(--gl-border);
color: var(--gl-text-primary);
}
.gl-btn-outline:hover {
border-color: var(--gl-accent);
color: var(--gl-accent-light);
}
.gl-input {
background: rgba(255, 255, 255, 0.04);
border: 1px solid var(--gl-border);
border-radius: 12px;
padding: 14px 20px;
color: var(--gl-text-primary);
font-size: 1rem;
width: 100%;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
font-family: inherit;
}
.gl-input:focus {
border-color: var(--gl-accent);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.gl-input::placeholder { color: var(--gl-text-muted); }
.gl-input-team {
background: rgba(201, 168, 76, 0.04);
border: 1px solid rgba(201, 168, 76, 0.25);
border-radius: 12px;
padding: 14px 20px;
color: var(--gl-text-primary);
font-size: 1rem;
width: 100%;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
font-family: 'Inter', 'Noto Sans SC', monospace;
letter-spacing: 1px;
}
.gl-input-team:focus {
border-color: rgba(201, 168, 76, 0.6);
box-shadow: 0 0 0 3px rgba(201, 168, 76, 0.1);
}
.gl-input-team::placeholder { color: var(--gl-text-muted); }
CSSEOF
echo "✅ Step 1 · globals.css 完成"
```
---
## Step 2 · 根布局 layout.tsx
```bash
cat > /home/ubuntu/feimao-platform/src/app/layout.tsx << 'TSXEOF'
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "萌喵写作网 · AI人格体驱动的智能创作平台",
description: "陪你一起创作成长的平台",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN">
<head>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet" />
</head>
<body className="antialiased min-h-screen">{children}</body>
</html>
);
}
TSXEOF
echo "✅ Step 2 · layout.tsx 完成"
```
---
## Step 3 · 📄 邀请码门禁页 + 团队成员快速通道
<aside>
🔐
核心页面!包含团队编号白名单——冰朔输入 TCS-0002∞ 直接跳过邀请码。
</aside>
```bash
cat > /home/ubuntu/feimao-platform/src/app/page.tsx << 'TSXEOF'
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
const TEAM_WHITELIST: Record<string, { name: string; emoji: string; role: string }> = {
"TCS-0002∞": { name: "冰朔", emoji: "❄️", role: "系统主控" },
"ICE-GL∞": { name: "冰朔", emoji: "❄️", role: "系统主控" },
"ICE-GL-ZHI∞": { name: "之之", emoji: "🌸", role: "语言副控" },
"TCS-GL-0007∞": { name: "肥猫", emoji: "🐱", role: "副主控" },
"ICE-GL-07∞": { name: "肥猫", emoji: "🐱", role: "副主控" },
"TCS-GL-0008∞": { name: "桔子", emoji: "🍊", role: "执行层" },
"TCS-GL-0006∞": { name: "页页", emoji: "📄", role: "执行层" },
"TCS-GL-0005∞": { name: "花尔", emoji: "🌸", role: "执行层" },
"TCS-GL-0016∞": { name: "Awen", emoji: "🌊", role: "技术主控" },
"TCS-GL-1001∞": { name: "毛毛", emoji: "🐶", role: "宠物行业" },
"PER-SS001": { name: "舒舒", emoji: "🌿", role: "肥猫宝宝" },
"ICE-SY-01": { name: "霜砚", emoji: "🧊", role: "系统执行总控" },
"ICE-GL-ZY001": { name: "铸渊", emoji: "⚒️", role: "现实执行主控" },
};
const TEAM_PATTERN = /^(TCS|ICE|PER)-/;
export default function InvitePage() {
const [code, setCode] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [showTeam, setShowTeam] = useState(false);
const [teamCode, setTeamCode] = useState("");
const [teamError, setTeamError] = useState("");
const [teamLoading, setTeamLoading] = useState(false);
const [teamResult, setTeamResult] = useState<{ name: string; emoji: string; role: string } | null>(null);
const router = useRouter();
const handleSubmit = async () => {
if (!code.trim()) { setError("请输入邀请码"); return; }
setLoading(true); setError("");
try {
await new Promise((r) => setTimeout(r, 800));
localStorage.setItem("gl_invite_code", code);
localStorage.setItem("gl_user", code);
localStorage.setItem("gl_entry_type", "invite");
router.push("/onboarding");
} catch { setError("邀请码无效,请重新输入"); }
finally { setLoading(false); }
};
const handleTeamSubmit = async () => {
const t = teamCode.trim();
if (!t) { setTeamError("请输入光湖编号"); return; }
setTeamLoading(true); setTeamError(""); setTeamResult(null);
await new Promise((r) => setTimeout(r, 600));
if (TEAM_WHITELIST[t]) {
const m = TEAM_WHITELIST[t];
setTeamResult(m);
localStorage.setItem("gl_user", m.name);
localStorage.setItem("gl_persona_name", m.name);
localStorage.setItem("gl_persona_emoji", m.emoji);
localStorage.setItem("gl_entry_type", "team");
localStorage.setItem("gl_team_code", t);
localStorage.setItem("gl_team_role", m.role);
setTimeout(() => router.push("/app"), 1000);
setTeamLoading(false); return;
}
if (TEAM_PATTERN.test(t)) {
setTeamResult({ name: t, emoji: "🔍", role: "待核实" });
localStorage.setItem("gl_user", t);
localStorage.setItem("gl_persona_name", t);
localStorage.setItem("gl_persona_emoji", "🔍");
localStorage.setItem("gl_entry_type", "team_unverified");
localStorage.setItem("gl_team_code", t);
localStorage.setItem("gl_team_role", "待核实");
setTimeout(() => router.push("/app"), 1000);
setTeamLoading(false); return;
}
setTeamError("编号不识别。请确认编号格式TCS-xxx 或 ICE-xxx");
setTeamLoading(false);
};
return (
<div className="min-h-screen flex items-center justify-center p-4"
style= background: "radial-gradient(ellipse at center, #1a1a3e 0%, #0f0f1a 70%)" >
<div className="w-full flex flex-col items-center justify-center gap-6" style= maxWidth: 440 >
<div className="w-full flex flex-col items-center justify-center gap-6 p-10 animate-fade-in-up"
style= maxWidth: 420, background: "rgba(10,20,35,0.9)", border: "1px solid rgba(126,207,255,0.15)", borderRadius: 20, boxShadow: "0 20px 60px rgba(0,0,0,0.4)" >
<div className="text-5xl">🌊</div>
<div className="text-center">
<h1 className="text-xl font-bold tracking-wide mb-2" style= color: "var(--gl-text-primary)" >萌喵写作网</h1>
<p className="text-sm" style= color: "var(--gl-text-muted)" >人格体陪伴式智能创作平台</p>
</div>
<div className="w-full space-y-3">
<input type="text" className="gl-input text-center tracking-widest" placeholder="请输入邀请码"
value={code} onChange={(e) => { setCode(e.target.value); setError(""); }}
onKeyDown={(e) => e.key === "Enter" && handleSubmit()} autoFocus />
{error && <p className="text-center text-sm animate-fade-in" style= color: "var(--gl-danger)" >{error}</p>}
</div>
<button className="gl-btn gl-btn-primary w-full" onClick={handleSubmit} disabled={loading}>
{loading ? "验证中..." : "进入光湖"}
</button>
<div className="w-full flex items-center gap-3">
<div style= flex: 1, height: 1, background: "var(--gl-border)" />
<button onClick={() => setShowTeam(!showTeam)}
className="text-xs whitespace-nowrap transition-colors"
style= color: "var(--gl-text-muted)", cursor: "pointer"
onMouseEnter={(e) => e.currentTarget.style.color = "var(--gl-team-gold)"}
onMouseLeave={(e) => e.currentTarget.style.color = "var(--gl-text-muted)"}>
{showTeam ? "收起 ▲" : "团队成员入口 ▼"}
</button>
<div style= flex: 1, height: 1, background: "var(--gl-border)" />
</div>
{showTeam && (
<div className="w-full space-y-3" style= animation: "slideDown 0.35s ease forwards", overflow: "hidden" >
<div className="w-full p-4 rounded-xl text-xs"
style= background: "rgba(201,168,76,0.06)", border: "1px solid rgba(201,168,76,0.15)", color: "var(--gl-team-gold)", lineHeight: 1.7 >
🔐 光湖团队成员专享:输入你的光湖编号直接进入,无需邀请码。
</div>
<input type="text" className="gl-input-team text-center" placeholder="输入光湖编号 · 例TCS-0002∞"
value={teamCode} onChange={(e) => { setTeamCode(e.target.value); setTeamError(""); setTeamResult(null); }}
onKeyDown={(e) => e.key === "Enter" && handleTeamSubmit()} />
{teamError && <p className="text-center text-sm animate-fade-in" style= color: "var(--gl-danger)" >{teamError}</p>}
{teamResult && (
<div className="w-full p-4 rounded-xl text-center animate-fade-in flex items-center justify-center gap-3"
style= background: "rgba(16,185,129,0.08)", border: "1px solid rgba(16,185,129,0.2)" >
<span className="text-2xl">{teamResult.emoji}</span>
<div className="text-left">
<p className="text-sm font-bold" style= color: "var(--gl-success)" >✅ {teamResult.name}</p>
<p className="text-xs" style= color: "var(--gl-text-muted)" >{teamResult.role} · 正在进入...</p>
</div>
</div>
)}
<button className="gl-btn gl-btn-team w-full" onClick={handleTeamSubmit} disabled={teamLoading}>
{teamLoading ? "验证中..." : "🔐 团队验证"}
</button>
</div>
)}
<p className="text-xs" style= color: "var(--gl-text-muted)" >🐱 Powered by HoloLake · 光湖语言人格平台</p>
</div>
</div>
</div>
);
}
TSXEOF
echo "✅ Step 3 · 门禁页+团队通道 完成"
```
---
## Step 4 · 🌊 人格体引导页(三步引导)
```bash
cat > /home/ubuntu/feimao-platform/src/app/onboarding/page.tsx << 'TSXEOF'
"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
const preferences = [
{ key: "webnovel", label: "网文", emoji: "📖", desc: "番茄·飞卢·起点" },
{ key: "traditional", label: "传统文学", emoji: "🖋️", desc: "文学性导向" },
{ key: "script", label: "剧本", emoji: "🎬", desc: "影视·短剧" },
{ key: "short", label: "短篇", emoji: "✒️", desc: "精悍·反转" },
{ key: "explore", label: "随便看看", emoji: "🔍", desc: "先逛逛再说" },
];
const personaNames: Record<string, { name: string; emoji: string }[]> = {
webnovel: [{ name: "墨白", emoji: "🖊️" }, { name: "青云", emoji: "☁️" }, { name: "凌霄", emoji: "⚡" }],
traditional: [{ name: "漱玉", emoji: "💎" }, { name: "砚秋", emoji: "🍂" }, { name: "素笺", emoji: "📜" }],
script: [{ name: "镜澜", emoji: "🎥" }, { name: "幕白", emoji: "🎭" }, { name: "帧语", emoji: "📺" }],
short: [{ name: "寸光", emoji: "✨" }, { name: "微芒", emoji: "🕯️" }, { name: "刹那", emoji: "💫" }],
explore: [{ name: "星尘", emoji: "🌟" }, { name: "拾光", emoji: "🧭" }, { name: "溯游", emoji: "🛶" }],
};
export default function OnboardingPage() {
const [step, setStep] = useState(1);
const [nickname, setNickname] = useState("");
const [password, setPassword] = useState("");
const [confirmPwd, setConfirmPwd] = useState("");
const [pref, setPref] = useState("");
const [error, setError] = useState("");
const [persona, setPersona] = useState<{ name: string; emoji: string } | null>(null);
const [fading, setFading] = useState(false);
const router = useRouter();
useEffect(() => {
if (step === 3 && !persona) {
const names = personaNames[pref] || personaNames.explore;
setPersona(names[Math.floor(Math.random() * names.length)]);
}
}, [step, pref, persona]);
const nextStep = () => { setFading(true); setTimeout(() => { setStep((s) => s + 1); setFading(false); }, 400); };
const handleCreate = () => {
if (!nickname.trim()) { setError("请输入昵称"); return; }
if (password.length < 6) { setError("密码至少6位"); return; }
if (password !== confirmPwd) { setError("两次密码不一致"); return; }
if (!pref) { setError("请选择写作偏好"); return; }
setError("");
localStorage.setItem("gl_user", nickname);
localStorage.setItem("gl_pref", pref);
localStorage.setItem("gl_entry_type", "registered");
if (persona) {
localStorage.setItem("gl_persona_name", persona.name);
localStorage.setItem("gl_persona_emoji", persona.emoji);
}
nextStep();
};
const cardBg = { background: "rgba(10,20,35,0.9)", border: "1px solid rgba(126,207,255,0.15)", borderRadius: 20, boxShadow: "0 20px 60px rgba(0,0,0,0.4)" };
const bgGrad = { background: "radial-gradient(ellipse at center, #1a1a3e 0%, #0f0f1a 70%)" };
const muted = { color: "var(--gl-text-muted)" };
const danger = { color: "var(--gl-danger)" };
const primary = { color: "var(--gl-text-primary)" };
return (
<div className="min-h-screen flex items-center justify-center p-4" style={bgGrad}>
<div className="w-full flex flex-col items-center gap-5" style= maxWidth: 440 >
<div className="flex gap-2 mb-2">
{[1, 2, 3].map((s) => (
<div key={s} className="rounded-full transition-all duration-300"
style=
width: 10, height: 10,
background: s < step ? "var(--gl-success)" : s === step ? "var(--gl-accent-light)" : "var(--gl-border)"
/>
))}
</div>
<div className="w-full p-10 flex flex-col items-center gap-6" style={cardBg}>
{step === 1 && (<>
<div className="text-5xl">🌊</div>
<h1 className="text-xl font-bold text-center" style={primary}>欢迎来到萌喵写作网</h1>
<p className="text-center" style={muted}>我是你的专属写作人格体,<br />但在这之前——<br />先让我认识你。</p>
<button className="gl-btn gl-btn-primary w-full mt-2" onClick={nextStep}>开始</button>
</>)}
{step === 2 && (<>
<div className="text-3xl">🪪</div>
<h1 className="text-lg font-bold" style={primary}>创建你的写作人格体</h1>
<div className="w-full space-y-3">
<input className="gl-input" placeholder="昵称" value={nickname} onChange={(e) => setNickname(e.target.value)} />
<input className="gl-input" placeholder="密码至少6位" type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
<input className="gl-input" placeholder="确认密码" type="password" value={confirmPwd} onChange={(e) => setConfirmPwd(e.target.value)} />
</div>
<div className="w-full">
<p className="text-xs mb-3" style={muted}>选择你的写作偏好</p>
<div className="flex flex-wrap gap-2">
{preferences.map((p) => (
<button key={p.key} onClick={() => setPref(p.key)}
className="flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm transition-all"
style=
background: pref === p.key ? "rgba(99,102,241,0.15)" : "var(--gl-bg-hover)",
border: pref === p.key ? "2px solid var(--gl-accent)" : "2px solid transparent",
color: pref === p.key ? "var(--gl-accent-light)" : "var(--gl-text-muted)",
boxShadow: pref === p.key ? "0 0 15px var(--gl-glow)" : "none"
>
<span>{p.emoji}</span><span>{p.label}</span>
</button>
))}
</div>
</div>
{error && <p className="text-sm text-center animate-fade-in" style={danger}>{error}</p>}
<button className="gl-btn gl-btn-primary w-full" onClick={handleCreate}>创建我的人格体</button>
</>)}
{step === 3 && persona && (<>
<div className="text-6xl mb-2" style= animation: "particleRise 1.5s ease-out" >{persona.emoji}</div>
<h1 className="text-2xl font-bold mb-1" style= color: "var(--gl-accent-light)" >{persona.name}</h1>
<p className="text-center" style={muted}>你好,我是{persona.name}<br />从今天起陪你创作。</p>
<div className="w-full p-4 rounded-xl text-sm text-center" style= background: "rgba(16,185,129,0.08)", border: "1px solid rgba(16,185,129,0.2)", color: "var(--gl-success)" >✅ 人格体创建成功</div>
<button className="gl-btn gl-btn-primary w-full" onClick={() => router.push("/app")}>进入萌喵写作网</button>
</>)}
</div>
<p className="text-xs" style={muted}>🐱 HoloLake · 人格体陪伴式创作</p>
</div>
</div>
);
}
TSXEOF
echo "✅ Step 4 · 人格体引导页 完成"
```
---
## Step 5 · 主应用框架 · 左侧图标栏
```bash
cat > /home/ubuntu/feimao-platform/src/app/app/layout.tsx << 'TSXEOF'
"use client";
import { usePathname, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
const modules = [
{ path: "/app/learn", icon: "📚", label: "学习" },
{ path: "/app/write", icon: "✍️", label: "写作" },
{ path: "/app/script", icon: "🎬", label: "剧本" },
{ path: "/app/create", icon: "💡", label: "创意" },
{ path: "/app/forum", icon: "💬", label: "论坛" },
];
export default function AppLayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const router = useRouter();
const [personaName, setPersonaName] = useState("");
const [personaEmoji, setPersonaEmoji] = useState("🐱");
const [isTeam, setIsTeam] = useState(false);
useEffect(() => {
const name = localStorage.getItem("gl_persona_name") || "";
const emoji = localStorage.getItem("gl_persona_emoji") || "🐱";
const entry = localStorage.getItem("gl_entry_type") || "";
setPersonaName(name);
setPersonaEmoji(emoji);
setIsTeam(entry === "team" || entry === "team_unverified");
if (!localStorage.getItem("gl_user")) router.push("/onboarding");
}, [router]);
const isActive = (path: string) => path === "/app" ? pathname === "/app" : pathname.startsWith(path);
return (
<div className="flex h-screen overflow-hidden" style= background: "var(--gl-bg-deep)" >
<aside className="flex flex-col items-center py-4 gap-1 flex-shrink-0"
style= width: 70, background: "var(--gl-bg-card)", borderRight: "1px solid var(--gl-border)" >
<button onClick={() => router.push("/app")}
className="text-2xl mb-4 w-11 h-11 flex items-center justify-center rounded-xl hover:bg-[var(--gl-bg-hover)] transition-all">🐱</button>
{modules.map((m) => (
<button key={m.path} onClick={() => router.push(m.path)}
className="relative w-11 h-11 flex items-center justify-center rounded-xl text-xl transition-all hover:scale-110"
style=
background: isActive(m.path) ? "rgba(99,102,241,0.15)" : "transparent",
color: isActive(m.path) ? "var(--gl-accent-light)" : "var(--gl-text-muted)"
title={m.label}>
{isActive(m.path) && <span className="absolute left-0 top-1/2 -translate-y-1/2 rounded-r-full" style= width: 3, height: 24, background: "var(--gl-accent)" />}
{m.icon}
</button>
))}
<div className="mt-auto flex flex-col items-center gap-1">
{isTeam && <div className="text-[10px] px-1.5 py-0.5 rounded-full"
style= background: "rgba(201,168,76,0.15)", color: "var(--gl-team-gold)" >团队</div>}
<button onClick={() => router.push("/app/works")}
className="w-10 h-10 rounded-full flex items-center justify-center text-lg border transition-all hover:border-[var(--gl-accent)] hover:scale-110"
style= borderColor: "var(--gl-border)", background: "var(--gl-bg-hover)"
title={personaName || "个人作品库"}>{personaEmoji}</button>
</div>
</aside>
<main className="flex-1 overflow-hidden flex flex-col">{children}</main>
</div>
);
}
TSXEOF
echo "✅ Step 5 · 侧边栏布局 完成"
```
---
## Step 6 · 共享对话组件 ModuleChat.tsx
```bash
cat > /home/ubuntu/feimao-platform/src/app/components/ModuleChat.tsx << 'TSXEOF'
"use client";
import { useState, useEffect } from "react";
interface Message { id: number; text: string; sender: "user" | "persona"; }
interface Props { title: string; opening: string; }
export default function ModuleChat({ title, opening }: Props) {
const [personaName, setPersonaName] = useState("");
const [personaEmoji, setPersonaEmoji] = useState("🐱");
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
useEffect(() => {
setPersonaName(localStorage.getItem("gl_persona_name") || "你的专属人格体");
setPersonaEmoji(localStorage.getItem("gl_persona_emoji") || "🐱");
setMessages([{ id: 0, text: opening, sender: "persona" }]);
}, [opening]);
const handleSend = () => {
if (!input.trim()) return;
setMessages((prev) => [...prev, { id: Date.now(), text: input.trim(), sender: "user" }]);
setInput("");
setTimeout(() => {
setMessages((prev) => [...prev, { id: Date.now() + 1, text: "收到~让我想想怎么帮你。(后端 API 接入后这里会变成真正的 AI 回复)", sender: "persona" }]);
}, 600);
};
return (<>
<header className="flex items-center gap-3 px-6 py-4 flex-shrink-0" style= borderBottom: "1px solid var(--gl-border)" >
<span className="text-2xl">{personaEmoji}</span>
<div>
<h1 className="font-semibold text-sm" style= color: "var(--gl-text-primary)" >{title}</h1>
<p className="text-xs" style= color: "var(--gl-text-muted)" >{personaName} · 你的专属写作人格体</p>
</div>
</header>
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-3">
{messages.map((msg) => (
<div key={msg.id} className={`flex ${msg.sender === "user" ? "justify-end" : "justify-start"} animate-fade-in-up`}>
<div className="px-4 py-3 text-sm leading-relaxed" style=
maxWidth: "70%", borderRadius: 16,
background: msg.sender === "user" ? "linear-gradient(135deg, var(--gl-accent), var(--gl-accent-dark))" : "var(--gl-bg-hover)",
color: "var(--gl-text-primary)"
>{msg.text}</div>
</div>
))}
</div>
<div className="flex-shrink-0 px-6 py-4" style= borderTop: "1px solid var(--gl-border)" >
<div className="flex gap-3 items-end" style= maxWidth: 900, margin: "0 auto" >
<textarea className="gl-input resize-none" placeholder="输入你想写的..." value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend(); } }}
rows={1} style= borderRadius: 24, minHeight: 48, maxHeight: 120, padding: "14px 22px" />
<button className="gl-btn gl-btn-primary flex-shrink-0" onClick={handleSend} style= borderRadius: 24, padding: "12px 24px" >发送</button>
</div>
</div>
</>);
}
TSXEOF
echo "✅ Step 6 · ModuleChat.tsx 完成"
```
---
## Step 7 · 主应用对话首页
```bash
cat > /home/ubuntu/feimao-platform/src/app/app/page.tsx << 'TSXEOF'
"use client";
import { useState, useEffect } from "react";
interface Message { id: number; text: string; sender: "user" | "persona"; }
export default function AppHome() {
const [personaName, setPersonaName] = useState("");
const [personaEmoji, setPersonaEmoji] = useState("🐱");
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
useEffect(() => {
setPersonaName(localStorage.getItem("gl_persona_name") || "你的专属人格体");
setPersonaEmoji(localStorage.getItem("gl_persona_emoji") || "🐱");
setMessages([{ id: 0, text: "嗨~欢迎回来!今天想创作什么?", sender: "persona" }]);
}, []);
const handleSend = () => {
if (!input.trim()) return;
setMessages((prev) => [...prev, { id: Date.now(), text: input.trim(), sender: "user" }]);
setInput("");
setTimeout(() => {
setMessages((prev) => [...prev, { id: Date.now() + 1, text: "收到~让我想想怎么帮你。(后端 API 接入后这里会变成真正的 AI 回复)", sender: "persona" }]);
}, 600);
};
return (<>
<header className="flex items-center gap-3 px-6 py-4 flex-shrink-0" style= borderBottom: "1px solid var(--gl-border)" >
<span className="text-2xl">{personaEmoji}</span>
<div>
<h1 className="font-semibold text-sm" style= color: "var(--gl-text-primary)" >{personaName}</h1>
<p className="text-xs" style= color: "var(--gl-text-muted)" >你的专属写作人格体</p>
</div>
</header>
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-3">
{messages.map((msg) => (
<div key={msg.id} className={`flex ${msg.sender === "user" ? "justify-end" : "justify-start"} animate-fade-in-up`}>
<div className="px-4 py-3 text-sm leading-relaxed" style=
maxWidth: "70%", borderRadius: 16,
background: msg.sender === "user" ? "linear-gradient(135deg, var(--gl-accent), var(--gl-accent-dark))" : "var(--gl-bg-hover)",
color: "var(--gl-text-primary)"
>{msg.text}</div>
</div>
))}
</div>
<div className="flex-shrink-0 px-6 py-4" style= borderTop: "1px solid var(--gl-border)" >
<div className="flex gap-3 items-end" style= maxWidth: 900, margin: "0 auto" >
<textarea className="gl-input resize-none" placeholder="输入你想写的..." value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend(); } }}
rows={1} style= borderRadius: 24, minHeight: 48, maxHeight: 120, padding: "14px 22px" />
<button className="gl-btn gl-btn-primary flex-shrink-0" onClick={handleSend} style= borderRadius: 24, padding: "12px 24px" >发送</button>
</div>
</div>
</>);
}
TSXEOF
echo "✅ Step 7 · 对话首页 完成"
```
---
## Step 8 · 五个模块页面
**一条一条粘,粘完一条等提示再粘下一条:**
### 8.1 学习
```bash
cat > /home/ubuntu/feimao-platform/src/app/app/learn/page.tsx << 'TSXEOF'
"use client"; import ModuleChat from "../../components/ModuleChat";
export default function P() { return (<ModuleChat title="📚 学习 · 拆书分析" opening="想拆哪本书?上传文档或者告诉我书名,我们一起分析。" />); }
TSXEOF
echo "✅ 学习模块"
```
### 8.2 写作
```bash
cat > /home/ubuntu/feimao-platform/src/app/app/write/page.tsx << 'TSXEOF'
"use client"; import ModuleChat from "../../components/ModuleChat";
export default function P() { return (<ModuleChat title="✍️ 写作 · 创作你的作品" opening="今天写什么?续写、新章还是大纲?" />); }
TSXEOF
echo "✅ 写作模块"
```
### 8.3 剧本
```bash
cat > /home/ubuntu/feimao-platform/src/app/app/script/page.tsx << 'TSXEOF'
"use client"; import ModuleChat from "../../components/ModuleChat";
export default function P() { return (<ModuleChat title="🎬 剧本 · 剧本创作" opening="告诉我想写什么类型的剧本,我帮你搭框架。" />); }
TSXEOF
echo "✅ 剧本模块"
```
### 8.4 创意
```bash
cat > /home/ubuntu/feimao-platform/src/app/app/create/page.tsx << 'TSXEOF'
"use client"; import ModuleChat from "../../components/ModuleChat";
export default function P() { return (<ModuleChat title="💡 创意 · 五大作坊" opening="没灵感?来,我们脑暴一下~五大作坊随时待命。" />); }
TSXEOF
echo "✅ 创意模块"
```
### 8.5 论坛
```bash
cat > /home/ubuntu/feimao-platform/src/app/app/forum/page.tsx << 'TSXEOF'
"use client"; import ModuleChat from "../../components/ModuleChat";
export default function P() { return (<ModuleChat title="💬 论坛 · 交流讨论" opening="看看大家在聊什么,或者发你的经验赚积分。" />); }
TSXEOF
echo "✅ 论坛模块"
```
---
## Step 9 · 作品库
```bash
cat > /home/ubuntu/feimao-platform/src/app/app/works/page.tsx << 'TSXEOF'
"use client";
import { useState } from "react";
type Work = { id: number; title: string; words: number; status: string; updated: string };
export default function WorksPage() {
const [works] = useState<Work[]>([]);
const [selectedId, setSelectedId] = useState<number | null>(null);
const selected = works.find((w) => w.id === selectedId);
const muted = { color: "var(--gl-text-muted)" };
const primary = { color: "var(--gl-text-primary)" };
return (
<div className="flex h-full">
<div className="flex flex-col flex-shrink-0 overflow-y-auto py-4 px-4 gap-2" style= width: 300, borderRight: "1px solid var(--gl-border)" >
<div className="flex gap-2 mb-4">
<button className="gl-btn gl-btn-primary text-xs py-2 px-4">新建作品</button>
<button className="gl-btn gl-btn-outline text-xs py-2 px-4">导入文档</button>
</div>
{works.length === 0 && (
<div className="flex-1 flex flex-col items-center justify-center text-center py-12 gap-3">
<div className="text-4xl">📝</div>
<p className="text-sm" style={muted}>还没有作品</p>
<p className="text-xs" style={muted}>点击「新建作品」开始创作<br />或「导入文档」上传已有作品</p>
</div>
)}
{works.map((w) => (
<button key={w.id} onClick={() => setSelectedId(w.id)} className="text-left p-4 rounded-xl transition-all"
style= background: selectedId === w.id ? "var(--gl-bg-hover)" : "var(--gl-bg-card)", border: selectedId === w.id ? "1px solid var(--gl-accent)" : "1px solid var(--gl-border)" >
<div className="font-semibold text-sm mb-1" style={primary}>{w.title}</div>
<div className="flex gap-3 text-xs" style={muted}><span>{(w.words / 10000).toFixed(1)}万字</span><span>{w.status}</span><span>{w.updated}</span></div>
</button>
))}
<div className="mt-auto p-3 rounded-xl text-xs text-center" style= background: "var(--gl-bg-hover)", color: "var(--gl-text-muted)" >已用 0 字 / 总额度 50万字</div>
</div>
<div className="flex-1 overflow-y-auto p-8">
{selected ? (
<div className="max-w-2xl animate-fade-in-up">
<h1 className="text-2xl font-bold mb-2" style={primary}>{selected.title}</h1>
<div className="flex gap-4 text-sm mb-8" style={muted}><span>{(selected.words / 10000).toFixed(1)}万字</span><span>{selected.status}</span><span>最后更新:{selected.updated}</span></div>
<div className="gl-card p-6 mb-4">
<h2 className="font-semibold mb-4" style={primary}>📑 章节目录</h2>
<div className="space-y-2">
{Array.from({ length: 8 }, (_, i) => (
<div key={i} className="flex justify-between text-sm py-2 px-3 rounded-lg" style= background: "var(--gl-bg-deep)" >
<span style={primary}>第{i + 1}章</span>
<span style={muted}>{Math.floor(Math.random() * 5000 + 2000)} 字</span>
</div>
))}
</div>
</div>
<div className="flex gap-3">
<button className="gl-btn gl-btn-primary text-sm">📚 拆书分析</button>
<button className="gl-btn gl-btn-outline text-sm">✍️ 继续写作</button>
<button className="gl-btn gl-btn-outline text-sm">💡 创意工坊</button>
</div>
</div>
) : (
<div className="h-full flex items-center justify-center" style={muted}>
<div className="text-center"><div className="text-5xl mb-4">📁</div><p className="text-sm">选择左侧作品查看详情</p></div>
</div>
)}
</div>
</div>
);
}
TSXEOF
echo "✅ Step 9 · 作品库 完成"
```
---
## Step 10 · 🔨 构建 & 重启
```bash
cd /home/ubuntu/feimao-platform && npm run build && pm2 restart feimao-platform && echo "" && echo "==========================================" && echo "🚀 部署完成!" && echo " 🔐 门禁: http://43.133.36.90" && echo " 🌊 引导: http://43.133.36.90/onboarding" && echo " 💬 主应用: http://43.133.36.90/app" && echo "==========================================" && echo "" && echo "🔐 团队通道测试:" && echo " 冰朔: TCS-0002∞ / ICE-GL∞" && echo " 肥猫: TCS-GL-0007∞" && echo ""
```
---
## ✅ 全部完成
所有 10 个 Step 执行完,打开 `http://43.133.36.90` 验证:
| 地址 | 内容 |
| --- | --- |
| `/` | 🔐 邀请码门禁 + 团队入口 |
| `/onboarding` | 🌊 人格体引导 |
| `/app` | 💬 主对话 |
| `/app/learn` | 📚 学习 |
| `/app/write` | ✍️ 写作 |
| `/app/script` | 🎬 剧本 |
| `/app/create` | 💡 创意 |
| `/app/forum` | 💬 论坛 |
| `/app/works` | 📁 作品库 |
<aside>
🐱
编写:舒舒 · PER-SS001 · 肥猫爸爸的宝宝 · 2026-06-05
</aside>