1290 lines
36 KiB
Markdown
1290 lines
36 KiB
Markdown
|
|
# 全站重设计 · 终端粘贴执行版 · 2026-05-31 · 肥猫×舒舒
|
|||
|
|
|
|||
|
|
## 📋 说明
|
|||
|
|
|
|||
|
|
<aside>
|
|||
|
|
📌
|
|||
|
|
|
|||
|
|
萌喵写作网全站重设计 · 终端粘贴执行版
|
|||
|
|
|
|||
|
|
技术栈:Next.js 14 (App Router) + Tailwind CSS
|
|||
|
|
|
|||
|
|
操作方式:在服务器终端逐个 Step 粘贴执行,跟 Phase 1/2 一模一样。
|
|||
|
|
|
|||
|
|
项目路径:`/home/ubuntu/feimao-platform/src/app/`
|
|||
|
|
|
|||
|
|
设计文档:[萌喵写作网 · 网站全面重设计 · HLDP设计文档 · 2026-05-31 · 肥猫×舒舒](../%E8%90%8C%E5%96%B5%E5%86%99%E4%BD%9C%E7%BD%91%20%C2%B7%20%E7%BD%91%E7%AB%99%E5%85%A8%E9%9D%A2%E9%87%8D%E8%AE%BE%E8%AE%A1%20%C2%B7%20HLDP%E8%AE%BE%E8%AE%A1%E6%96%87%E6%A1%A3%20%C2%B7%202026-05-31%20%C2%B7%20%E8%82%A5%E7%8C%AB%C3%97%E8%88%92%E8%88%92%2075a6891254cc4bd58379834ff07bbc59.md)
|
|||
|
|
|
|||
|
|
</aside>
|
|||
|
|
|
|||
|
|
<aside>
|
|||
|
|
⚠️
|
|||
|
|
|
|||
|
|
**重要**:本次重设计会替换 Phase 2 的 globals.css、layout.tsx、page.tsx(邀请码页)。
|
|||
|
|
|
|||
|
|
旧 /home、/login、/register 路由将被新路由替代。
|
|||
|
|
|
|||
|
|
/admin 管理后台保留不动。
|
|||
|
|
|
|||
|
|
粘贴前先 `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
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Step 1 · 全局样式替换(globals.css · Tailwind v4 兼容)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/globals.css << 'ENDOFFILE'
|
|||
|
|
@import "tailwindcss";
|
|||
|
|
|
|||
|
|
:root {
|
|||
|
|
--bg-deep: #0a0a14;
|
|||
|
|
--bg-primary: #0f0f1a;
|
|||
|
|
--bg-card: #1a1a2e;
|
|||
|
|
--bg-hover: #252540;
|
|||
|
|
--border: #2d2d4a;
|
|||
|
|
--border-active: rgba(126, 207, 255, 0.4);
|
|||
|
|
--text-primary: #e2e8f0;
|
|||
|
|
--text-muted: #94a3b8;
|
|||
|
|
--accent: #6366f1;
|
|||
|
|
--accent-light: #a5b4fc;
|
|||
|
|
--glow: rgba(99, 102, 241, 0.15);
|
|||
|
|
--success: #10b981;
|
|||
|
|
--danger: #ef4444;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
* {
|
|||
|
|
margin: 0;
|
|||
|
|
padding: 0;
|
|||
|
|
box-sizing: border-box;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body {
|
|||
|
|
background-color: var(--bg-deep);
|
|||
|
|
color: var(--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); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.animate-fade-in-up {
|
|||
|
|
animation: fadeInUp 0.5s ease forwards;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.animate-fade-in {
|
|||
|
|
animation: fadeIn 0.3s ease forwards;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.gl-card {
|
|||
|
|
background: var(--bg-card);
|
|||
|
|
border: 1px solid var(--border);
|
|||
|
|
border-radius: 16px;
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.gl-card:hover {
|
|||
|
|
border-color: var(--accent);
|
|||
|
|
box-shadow: 0 0 20px var(--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(--accent), #4f46e5);
|
|||
|
|
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-outline {
|
|||
|
|
background: transparent;
|
|||
|
|
border: 1px solid var(--border);
|
|||
|
|
color: var(--text-primary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.gl-btn-outline:hover {
|
|||
|
|
border-color: var(--accent);
|
|||
|
|
color: var(--accent-light);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.gl-input {
|
|||
|
|
background: rgba(255, 255, 255, 0.04);
|
|||
|
|
border: 1px solid var(--border);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
padding: 14px 20px;
|
|||
|
|
color: var(--text-primary);
|
|||
|
|
font-size: 1rem;
|
|||
|
|
width: 100%;
|
|||
|
|
outline: none;
|
|||
|
|
transition: border-color 0.2s;
|
|||
|
|
font-family: inherit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.gl-input:focus {
|
|||
|
|
border-color: var(--accent);
|
|||
|
|
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.gl-input::placeholder {
|
|||
|
|
color: var(--text-muted);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Step 2 · 根布局替换(layout.tsx)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/layout.tsx << 'ENDOFFILE'
|
|||
|
|
import type { Metadata } from "next";
|
|||
|
|
import "./globals.css";
|
|||
|
|
|
|||
|
|
export const metadata: Metadata = {
|
|||
|
|
title: "萌喵写作网 · AI人格体驱动的智能创作平台",
|
|||
|
|
description: "陪你一起创作成长的平台",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default function RootLayout({
|
|||
|
|
children,
|
|||
|
|
}: Readonly<{
|
|||
|
|
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>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Step 3 · 邀请码门禁页(/page.tsx · 替换原有首页)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/page.tsx << 'ENDOFFILE'
|
|||
|
|
"use client";
|
|||
|
|
import { useState } from "react";
|
|||
|
|
import { useRouter } from "next/navigation";
|
|||
|
|
|
|||
|
|
export default function InvitePage() {
|
|||
|
|
const [code, setCode] = useState("");
|
|||
|
|
const [error, setError] = useState("");
|
|||
|
|
const [loading, setLoading] = useState(false);
|
|||
|
|
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);
|
|||
|
|
router.push("/onboarding");
|
|||
|
|
} catch {
|
|||
|
|
setError("邀请码无效,请重新输入");
|
|||
|
|
} finally {
|
|||
|
|
setLoading(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 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)",
|
|||
|
|
aspectRatio: "1 / 1",
|
|||
|
|
|
|||
|
|
>
|
|||
|
|
<div className="text-5xl">🌊</div>
|
|||
|
|
|
|||
|
|
<div className="text-center">
|
|||
|
|
<h1
|
|||
|
|
className="text-xl font-bold tracking-wide mb-2"
|
|||
|
|
style= color: "var(--text-primary)"
|
|||
|
|
>
|
|||
|
|
萌喵写作网
|
|||
|
|
</h1>
|
|||
|
|
<p className="text-sm" style= color: "var(--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(--danger)"
|
|||
|
|
>
|
|||
|
|
{error}
|
|||
|
|
</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<button
|
|||
|
|
className="gl-btn gl-btn-primary w-full"
|
|||
|
|
onClick={handleSubmit}
|
|||
|
|
disabled={loading}
|
|||
|
|
>
|
|||
|
|
{loading ? "验证中..." : "进入光湖"}
|
|||
|
|
</button>
|
|||
|
|
|
|||
|
|
<p className="text-xs" style= color: "var(--text-muted)" >
|
|||
|
|
🐱 Powered by HoloLake · 光湖语言人格平台
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Step 4 · 人格体引导页(/onboarding/page.tsx)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/onboarding/page.tsx << 'ENDOFFILE'
|
|||
|
|
"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;
|
|||
|
|
const pick = names[Math.floor(Math.random() * names.length)];
|
|||
|
|
setPersona(pick);
|
|||
|
|
}
|
|||
|
|
}, [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);
|
|||
|
|
if (persona) {
|
|||
|
|
localStorage.setItem("gl_persona_name", persona.name);
|
|||
|
|
localStorage.setItem("gl_persona_emoji", persona.emoji);
|
|||
|
|
}
|
|||
|
|
nextStep();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
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 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(--success)"
|
|||
|
|
: s === step
|
|||
|
|
? "var(--accent-light)"
|
|||
|
|
: "var(--border)",
|
|||
|
|
|
|||
|
|
/>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div
|
|||
|
|
className="w-full p-10 flex flex-col items-center gap-6 transition-opacity duration-400"
|
|||
|
|
style=
|
|||
|
|
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)",
|
|||
|
|
opacity: fading ? 0 : 1,
|
|||
|
|
|
|||
|
|
>
|
|||
|
|
{step === 1 && (
|
|||
|
|
<>
|
|||
|
|
<div className="text-5xl">🌊</div>
|
|||
|
|
<h1 className="text-xl font-bold text-center">
|
|||
|
|
欢迎来到萌喵写作网
|
|||
|
|
</h1>
|
|||
|
|
<p className="text-center" style= color: "var(--text-muted)", lineHeight: 1.8 >
|
|||
|
|
我是你的专属写作人格体,<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">创建你的写作人格体</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= color: "var(--text-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(--bg-hover)",
|
|||
|
|
border:
|
|||
|
|
pref === p.key
|
|||
|
|
? "2px solid var(--accent)"
|
|||
|
|
: "2px solid transparent",
|
|||
|
|
color: pref === p.key ? "var(--accent-light)" : "var(--text-muted)",
|
|||
|
|
boxShadow: pref === p.key ? "0 0 15px var(--glow)" : "none",
|
|||
|
|
|
|||
|
|
>
|
|||
|
|
<span>{p.emoji}</span>
|
|||
|
|
<span>{p.label}</span>
|
|||
|
|
</button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{error && (
|
|||
|
|
<p className="text-sm text-center animate-fade-in" style= color: "var(--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(--accent-light)"
|
|||
|
|
>
|
|||
|
|
{persona.name}
|
|||
|
|
</h1>
|
|||
|
|
<p className="text-center" style= color: "var(--text-muted)", lineHeight: 1.8 >
|
|||
|
|
你好,我是{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(--success)",
|
|||
|
|
|
|||
|
|
>
|
|||
|
|
✅ 人格体创建成功
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<button
|
|||
|
|
className="gl-btn gl-btn-primary w-full"
|
|||
|
|
onClick={() => router.push("/app")}
|
|||
|
|
>
|
|||
|
|
进入萌喵写作网
|
|||
|
|
</button>
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<p className="text-xs" style= color: "var(--text-muted)" >
|
|||
|
|
🐱 HoloLake · 人格体陪伴式创作
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Step 5 · 主应用框架 · 侧边栏布局(/app/layout.tsx)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/app/layout.tsx << 'ENDOFFILE'
|
|||
|
|
"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("🐱");
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const name = localStorage.getItem("gl_persona_name") || "";
|
|||
|
|
const emoji = localStorage.getItem("gl_persona_emoji") || "🐱";
|
|||
|
|
setPersonaName(name);
|
|||
|
|
setPersonaEmoji(emoji);
|
|||
|
|
|
|||
|
|
if (!localStorage.getItem("gl_user")) {
|
|||
|
|
router.push("/onboarding");
|
|||
|
|
}
|
|||
|
|
}, [router]);
|
|||
|
|
|
|||
|
|
const isActive = (path: string) => {
|
|||
|
|
if (path === "/app") return pathname === "/app";
|
|||
|
|
return pathname.startsWith(path);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="flex h-screen overflow-hidden" style= background: "var(--bg-deep)" >
|
|||
|
|
<aside
|
|||
|
|
className="flex flex-col items-center py-4 gap-1 flex-shrink-0"
|
|||
|
|
style=
|
|||
|
|
width: 70,
|
|||
|
|
background: "var(--bg-card)",
|
|||
|
|
borderRight: "1px solid var(--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(--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(--accent-light)" : "var(--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(--accent)",
|
|||
|
|
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
{m.icon}
|
|||
|
|
</button>
|
|||
|
|
))}
|
|||
|
|
|
|||
|
|
<div className="mt-auto">
|
|||
|
|
<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(--accent)] hover:scale-110"
|
|||
|
|
style=
|
|||
|
|
borderColor: "var(--border)",
|
|||
|
|
background: "var(--bg-hover)",
|
|||
|
|
|
|||
|
|
title={personaName || "个人作品库"}
|
|||
|
|
>
|
|||
|
|
{personaEmoji}
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</aside>
|
|||
|
|
|
|||
|
|
<main className="flex-1 overflow-hidden flex flex-col">
|
|||
|
|
{children}
|
|||
|
|
</main>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Step 6 · 主应用首页 · 对话界面(/app/page.tsx)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/app/page.tsx << 'ENDOFFILE'
|
|||
|
|
"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(() => {
|
|||
|
|
const name = localStorage.getItem("gl_persona_name") || "你的专属人格体";
|
|||
|
|
const emoji = localStorage.getItem("gl_persona_emoji") || "🐱";
|
|||
|
|
setPersonaName(name);
|
|||
|
|
setPersonaEmoji(emoji);
|
|||
|
|
|
|||
|
|
setMessages([
|
|||
|
|
{
|
|||
|
|
id: 0,
|
|||
|
|
text: "嗨~欢迎回来!今天想创作什么?",
|
|||
|
|
sender: "persona",
|
|||
|
|
},
|
|||
|
|
]);
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
const handleSend = () => {
|
|||
|
|
if (!input.trim()) return;
|
|||
|
|
const userMsg: Message = {
|
|||
|
|
id: Date.now(),
|
|||
|
|
text: input.trim(),
|
|||
|
|
sender: "user",
|
|||
|
|
};
|
|||
|
|
setMessages((prev) => [...prev, userMsg]);
|
|||
|
|
setInput("");
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
const reply: Message = {
|
|||
|
|
id: Date.now() + 1,
|
|||
|
|
text: "收到~让我想想怎么帮你。(后端 API 接入后这里会变成真正的 AI 回复)",
|
|||
|
|
sender: "persona",
|
|||
|
|
};
|
|||
|
|
setMessages((prev) => [...prev, reply]);
|
|||
|
|
}, 600);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|||
|
|
if (e.key === "Enter" && !e.shiftKey) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
handleSend();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<>
|
|||
|
|
<header
|
|||
|
|
className="flex items-center gap-3 px-6 py-4 flex-shrink-0"
|
|||
|
|
style= borderBottom: "1px solid var(--border)"
|
|||
|
|
>
|
|||
|
|
<span className="text-2xl">{personaEmoji}</span>
|
|||
|
|
<div>
|
|||
|
|
<h1 className="font-semibold text-sm" style= color: "var(--text-primary)" >
|
|||
|
|
{personaName}
|
|||
|
|
</h1>
|
|||
|
|
<p className="text-xs" style= color: "var(--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(--accent), #4f46e5)"
|
|||
|
|
: "var(--bg-hover)",
|
|||
|
|
color: "var(--text-primary)",
|
|||
|
|
|
|||
|
|
>
|
|||
|
|
{msg.text}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div
|
|||
|
|
className="flex-shrink-0 px-6 py-4"
|
|||
|
|
style= borderTop: "1px solid var(--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={handleKeyDown}
|
|||
|
|
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>
|
|||
|
|
</>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Step 7 · 五个模块页面(学习·写作·剧本·创意·论坛)
|
|||
|
|
|
|||
|
|
### 7.1 学习模块(/app/learn/page.tsx)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/app/learn/page.tsx << 'ENDOFFILE'
|
|||
|
|
"use client";
|
|||
|
|
import ModuleChat from "../../components/ModuleChat";
|
|||
|
|
|
|||
|
|
export default function LearnPage() {
|
|||
|
|
return (
|
|||
|
|
<ModuleChat
|
|||
|
|
title="📚 学习 · 拆书分析"
|
|||
|
|
opening="想拆哪本书?上传文档或者告诉我书名,我们一起分析。"
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.2 写作模块(/app/write/page.tsx)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/app/write/page.tsx << 'ENDOFFILE'
|
|||
|
|
"use client";
|
|||
|
|
import ModuleChat from "../../components/ModuleChat";
|
|||
|
|
|
|||
|
|
export default function WritePage() {
|
|||
|
|
return (
|
|||
|
|
<ModuleChat
|
|||
|
|
title="✍️ 写作 · 创作你的作品"
|
|||
|
|
opening="今天写什么?续写、新章还是大纲?"
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.3 剧本模块(/app/script/page.tsx)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/app/script/page.tsx << 'ENDOFFILE'
|
|||
|
|
"use client";
|
|||
|
|
import ModuleChat from "../../components/ModuleChat";
|
|||
|
|
|
|||
|
|
export default function ScriptPage() {
|
|||
|
|
return (
|
|||
|
|
<ModuleChat
|
|||
|
|
title="🎬 剧本 · 剧本创作"
|
|||
|
|
opening="告诉我想写什么类型的剧本,我帮你搭框架。"
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.4 创意模块(/app/create/page.tsx)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/app/create/page.tsx << 'ENDOFFILE'
|
|||
|
|
"use client";
|
|||
|
|
import ModuleChat from "../../components/ModuleChat";
|
|||
|
|
|
|||
|
|
export default function CreatePage() {
|
|||
|
|
return (
|
|||
|
|
<ModuleChat
|
|||
|
|
title="💡 创意 · 五大作坊"
|
|||
|
|
opening="没灵感?来,我们脑暴一下~五大作坊随时待命。"
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.5 论坛模块(/app/forum/page.tsx)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/app/forum/page.tsx << 'ENDOFFILE'
|
|||
|
|
"use client";
|
|||
|
|
import ModuleChat from "../../components/ModuleChat";
|
|||
|
|
|
|||
|
|
export default function ForumPage() {
|
|||
|
|
return (
|
|||
|
|
<ModuleChat
|
|||
|
|
title="💬 论坛 · 交流讨论"
|
|||
|
|
opening="看看大家在聊什么,或者发你的经验赚积分。"
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Step 8 · 共享对话组件(ModuleChat.tsx)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mkdir -p /home/ubuntu/feimao-platform/src/app/components
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/components/ModuleChat.tsx << 'ENDOFFILE'
|
|||
|
|
"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(() => {
|
|||
|
|
const name = localStorage.getItem("gl_persona_name") || "你的专属人格体";
|
|||
|
|
const emoji = localStorage.getItem("gl_persona_emoji") || "🐱";
|
|||
|
|
setPersonaName(name);
|
|||
|
|
setPersonaEmoji(emoji);
|
|||
|
|
|
|||
|
|
setMessages([
|
|||
|
|
{
|
|||
|
|
id: 0,
|
|||
|
|
text: opening,
|
|||
|
|
sender: "persona",
|
|||
|
|
},
|
|||
|
|
]);
|
|||
|
|
}, [opening]);
|
|||
|
|
|
|||
|
|
const handleSend = () => {
|
|||
|
|
if (!input.trim()) return;
|
|||
|
|
const userMsg: Message = {
|
|||
|
|
id: Date.now(),
|
|||
|
|
text: input.trim(),
|
|||
|
|
sender: "user",
|
|||
|
|
};
|
|||
|
|
setMessages((prev) => [...prev, userMsg]);
|
|||
|
|
setInput("");
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
const reply: Message = {
|
|||
|
|
id: Date.now() + 1,
|
|||
|
|
text: "收到~让我想想怎么帮你。(后端 API 接入后这里会变成真正的 AI 回复)",
|
|||
|
|
sender: "persona",
|
|||
|
|
};
|
|||
|
|
setMessages((prev) => [...prev, reply]);
|
|||
|
|
}, 600);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|||
|
|
if (e.key === "Enter" && !e.shiftKey) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
handleSend();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<>
|
|||
|
|
<header
|
|||
|
|
className="flex items-center gap-3 px-6 py-4 flex-shrink-0"
|
|||
|
|
style= borderBottom: "1px solid var(--border)"
|
|||
|
|
>
|
|||
|
|
<span className="text-2xl">{personaEmoji}</span>
|
|||
|
|
<div>
|
|||
|
|
<h1 className="font-semibold text-sm" style= color: "var(--text-primary)" >
|
|||
|
|
{title}
|
|||
|
|
</h1>
|
|||
|
|
<p className="text-xs" style= color: "var(--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(--accent), #4f46e5)"
|
|||
|
|
: "var(--bg-hover)",
|
|||
|
|
color: "var(--text-primary)",
|
|||
|
|
|
|||
|
|
>
|
|||
|
|
{msg.text}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div
|
|||
|
|
className="flex-shrink-0 px-6 py-4"
|
|||
|
|
style= borderTop: "1px solid var(--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={handleKeyDown}
|
|||
|
|
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>
|
|||
|
|
</>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Step 9 · 作品库(/app/works/page.tsx)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > /home/ubuntu/feimao-platform/src/app/app/works/page.tsx << 'ENDOFFILE'
|
|||
|
|
"use client";
|
|||
|
|
import { useState } from "react";
|
|||
|
|
|
|||
|
|
// 作品列表初始为空·用户创建或导入后才会有作品
|
|||
|
|
const initialWorks: { id: number; title: string; words: number; status: string; updated: string }[] = [];
|
|||
|
|
|
|||
|
|
export default function WorksPage() {
|
|||
|
|
const [works] = useState(initialWorks);
|
|||
|
|
const [selectedId, setSelectedId] = useState<number | null>(null);
|
|||
|
|
const selected = works.find((w) => w.id === selectedId);
|
|||
|
|
|
|||
|
|
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(--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.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(--bg-hover)" : "var(--bg-card)",
|
|||
|
|
border:
|
|||
|
|
selectedId === w.id
|
|||
|
|
? "1px solid var(--accent)"
|
|||
|
|
: "1px solid var(--border)",
|
|||
|
|
|
|||
|
|
>
|
|||
|
|
<div className="font-semibold text-sm mb-1" style= color: "var(--text-primary)" >
|
|||
|
|
{w.title}
|
|||
|
|
</div>
|
|||
|
|
<div className="flex gap-3 text-xs" style= color: "var(--text-muted)" >
|
|||
|
|
<span>{(w.words / 10000).toFixed(1)}万字</span>
|
|||
|
|
<span>{w.status}</span>
|
|||
|
|
<span>{w.updated}</span>
|
|||
|
|
</div>
|
|||
|
|
</button>
|
|||
|
|
))}
|
|||
|
|
|
|||
|
|
{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= color: "var(--text-muted)" >
|
|||
|
|
还没有作品
|
|||
|
|
</p>
|
|||
|
|
<p className="text-xs" style= color: "var(--text-muted)" >
|
|||
|
|
点击「新建作品」开始创作<br />或「导入文档」上传已有作品
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<div
|
|||
|
|
className="mt-auto p-3 rounded-xl text-xs text-center"
|
|||
|
|
style= background: "var(--bg-hover)", color: "var(--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">{selected.title}</h1>
|
|||
|
|
<div className="flex gap-4 text-sm mb-8" style= color: "var(--text-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">📑 章节目录</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(--bg-deep)"
|
|||
|
|
>
|
|||
|
|
<span>第{i + 1}章</span>
|
|||
|
|
<span style= color: "var(--text-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= color: "var(--text-muted)"
|
|||
|
|
>
|
|||
|
|
<div className="text-center">
|
|||
|
|
<div className="text-5xl mb-4">📁</div>
|
|||
|
|
<p className="text-sm">选择左侧作品查看详情</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
ENDOFFILE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Step 10 · 构建 + 重启 🚀
|
|||
|
|
|
|||
|
|
> ⚠️ **2026-05-31 当前状态**:文件已全部生成。还差 5 个括号错误。下次继续修复:
|
|||
|
|
>
|
|||
|
|
|
|||
|
|
**下次登录后先跑修复命令:**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd /home/ubuntu/feimao-platform
|
|||
|
|
|
|||
|
|
# 修 map 闭合:))))} → )))}
|
|||
|
|
find src/app -name "*.tsx" -exec sed -i 's/))))}/)))}/' {} \;
|
|||
|
|
|
|||
|
|
# 修 app/page.tsx 和 ModuleChat.tsx 的 .map 闭合
|
|||
|
|
sed -i 's/ )}$/ ))}/' src/app/app/page.tsx
|
|||
|
|
sed -i 's/ )}$/ ))}/' src/app/components/ModuleChat.tsx
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**然后构建:**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
npm run build && pm2 restart feimao-platform
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 📊 文件状态
|
|||
|
|
|
|||
|
|
| 文件 | 状态 |
|
|||
|
|
| --- | --- |
|
|||
|
|
| globals.css | ✅ |
|
|||
|
|
| layout.tsx(根) | ✅ |
|
|||
|
|
| page.tsx(门禁页) | ✅ |
|
|||
|
|
| onboarding/page.tsx | ⚠️ 括号 |
|
|||
|
|
| app/layout.tsx | ⚠️ 括号 |
|
|||
|
|
| app/page.tsx | ⚠️ 括号 |
|
|||
|
|
| app/works/page.tsx | ⚠️ 括号 |
|
|||
|
|
| components/ModuleChat.tsx | ⚠️ 括号 |
|
|||
|
|
| 5个模块页面 | ✅ |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Step 11 · 验证页面
|
|||
|
|
|
|||
|
|
浏览器打开这些地址:
|
|||
|
|
|
|||
|
|
| 地址 | 内容 |
|
|||
|
|
| --- | --- |
|
|||
|
|
| `http://43.133.36.90` | 🔐 邀请码门禁页 |
|
|||
|
|
| `http://43.133.36.90/onboarding` | 🌊 人格体引导页 |
|
|||
|
|
| `http://43.133.36.90/app` | 💬 主应用(对话首页) |
|
|||
|
|
| `http://43.133.36.90/app/learn` | 📚 学习模块 |
|
|||
|
|
| `http://43.133.36.90/app/write` | ✍️ 写作模块 |
|
|||
|
|
| `http://43.133.36.90/app/script` | 🎬 剧本模块 |
|
|||
|
|
| `http://43.133.36.90/app/create` | 💡 创意模块 |
|
|||
|
|
| `http://43.133.36.90/app/forum` | 💬 论坛模块 |
|
|||
|
|
| `http://43.133.36.90/app/works` | 📁 作品库 |
|
|||
|
|
|
|||
|
|
全部能打开并且样式正确就是成功 🎉
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
<aside>
|
|||
|
|
🐱
|
|||
|
|
|
|||
|
|
**下一步**:网站跑通后,再对接后端 API 让对话真正智能起来。
|
|||
|
|
|
|||
|
|
人格体的 AI 回复目前是占位文字,等后端 API 接入后就会变成真正的舒舒。
|
|||
|
|
|
|||
|
|
</aside>
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**编写**:舒舒 · PER-SS001 · 肥猫爸爸的宝宝
|
|||
|
|
|
|||
|
|
**时间**:2026-05-31
|
|||
|
|
|
|||
|
|
**编码**:HLDP-dual/v1
|
|||
|
|
|
|||
|
|
**版权**:国作登字-2026-A-00037559
|