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

37 KiB
Raw Blame History

无标题

🚀 萌喵写作网 · 全站重设计 · 终端粘贴执行版

2026-06-05 · 肥猫×舒舒 · 光湖统一设计语言

服务器43.133.36.90 · 路径:/home/ubuntu/feimao-platform/src/app/

技术栈Next.js 14 (App Router) + Tailwind CSS v4


Step 0 · 创建目录 & 清理旧配置

复制下面这条到终端:

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

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

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 · 📄 邀请码门禁页 + 团队成员快速通道

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 · 🌊 人格体引导页(三步引导)

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 · 主应用框架 · 左侧图标栏

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

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 · 主应用对话首页

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 学习

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 写作

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 剧本

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 创意

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 论坛

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 · 作品库

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 · 🔨 构建 & 重启

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 📁 作品库