From e961e3091b7f9d7eb1e1ece42040a5f74da3c183 Mon Sep 17 00:00:00 2001 From: hexin-codex-bot Date: Mon, 5 Oct 2026 13:50:21 +0800 Subject: [PATCH] ui: mirror rednote creator platform layout --- app/globals.css | 206 ++++++++++++++++++++++++++++++------------ app/layout.tsx | 4 +- components/Studio.tsx | 185 ++++++++++++------------------------- 3 files changed, 211 insertions(+), 184 deletions(-) diff --git a/app/globals.css b/app/globals.css index 93d2b7e..5959ea0 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,57 +1,151 @@ -:root { color-scheme: light; --ink: #242321; --muted: #7e7a73; --line: #e8e2d9; --paper: #fffdf9; --canvas: #f5f1ea; --red: #ff2442; --red-soft: #fff0f2; } +:root { + color-scheme: light; + --red: #ff2442; + --ink: #1f1f1f; + --muted: #8b8b8b; + --sidebar: #f7f7f7; + --panel: #f8f8f8; +} + * { box-sizing: border-box; } -html, body { margin: 0; min-height: 100%; background: var(--canvas); color: var(--ink); font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", sans-serif; } -button, input, textarea, select { font: inherit; } -button { cursor: pointer; } -.studio-shell { min-height: 100vh; padding-bottom: 34px; } -.topbar { height: 72px; padding: 0 24px; display: flex; align-items: center; justify-content: space-between; background: var(--paper); border-bottom: 1px solid var(--line); } -.brand { display: flex; align-items: center; gap: 12px; } -.brand > div { display: flex; flex-direction: column; gap: 3px; } -.brand small { color: var(--muted); font-size: 11px; } -.logo { width: 34px; height: 34px; display: grid; place-items: center; color: white; background: var(--red); border-radius: 11px; font-weight: 850; } -.primary { border: 0; border-radius: 9px; padding: 10px 16px; background: var(--red); color: white; font-weight: 650; } -.workspace { min-height: calc(100vh - 106px); display: grid; grid-template-columns: 230px minmax(540px, 1fr) 300px; } -.sidebar { padding: 22px 14px; background: var(--paper); border-right: 1px solid var(--line); } -.section-head { margin: 0 8px 12px; display: flex; align-items: center; justify-content: space-between; font-size: 12px; font-weight: 750; } -.section-head small { color: var(--muted); font-size: 10px; font-weight: 500; } -.project-row, .empty-project { width: 100%; padding: 11px 12px; border: 0; border-radius: 9px; background: transparent; color: #595550; text-align: left; } -.project-row { display: flex; justify-content: space-between; gap: 8px; } -.project-row small { color: #aaa39a; } -.project-row.selected { background: var(--red-soft); color: #b81830; font-weight: 700; } -.empty-project { border: 1px dashed #cec4b8; text-align: center; } -.sidebar-note { margin-top: 28px; padding: 14px; border-radius: 12px; background: #f7f3ed; } -.sidebar-note b { font-size: 12px; } -.sidebar-note p { margin: 7px 0 0; color: var(--muted); font-size: 11px; line-height: 1.7; } -.project-editor { padding: 34px clamp(24px, 4vw, 60px) 60px; overflow-y: auto; } -.page-title { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; margin-bottom: 24px; } -.page-title span, .eyebrow { color: var(--red); font-size: 11px; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; } -.page-title h1 { margin: 7px 0 6px; font-size: 30px; } -.page-title p { margin: 0; color: var(--muted); } -.form-card { margin-top: 16px; padding: 22px; border: 1px solid var(--line); border-radius: 15px; background: var(--paper); box-shadow: 0 8px 30px rgba(72, 58, 39, .035); } -.form-card h2 { margin: 0 0 18px; font-size: 16px; } -.card-title { display: flex; justify-content: space-between; align-items: flex-start; } -.card-title h2 { margin-bottom: 4px; } -.card-title p { margin: 0 0 18px; color: var(--muted); font-size: 12px; } -.status-pill { padding: 5px 9px; border-radius: 20px; background: #edf7ef; color: #407b4c; font-size: 10px; } -.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } -label { display: flex; flex-direction: column; gap: 7px; margin-top: 14px; color: #5f5a54; font-size: 12px; font-weight: 650; } -input, textarea, select { width: 100%; border: 1px solid var(--line); border-radius: 9px; padding: 10px 11px; outline: none; background: #fff; color: var(--ink); font-weight: 400; resize: vertical; } -input:focus, textarea:focus, select:focus { border-color: #ee8494; box-shadow: 0 0 0 3px var(--red-soft); } -.next-panel { padding: 34px 24px; background: #292724; color: white; } -.next-panel h2 { margin: 8px 0; font-size: 24px; } -.next-panel > p { margin: 0 0 30px; color: #bcb6ae; font-size: 12px; line-height: 1.7; } -.future-step { display: flex; gap: 12px; padding: 16px 0; border-top: 1px solid #45413c; } -.future-step b { color: #817b73; font-size: 11px; } -.future-step span { display: flex; flex-direction: column; gap: 5px; } -.future-step strong { font-size: 13px; } -.future-step small { color: #99928a; } -.next-panel button { width: 100%; margin-top: 24px; padding: 12px; border: 1px solid #4d4943; border-radius: 9px; background: #36332f; color: #827c74; } -.welcome { grid-column: 2 / 4; display: grid; place-content: center; justify-items: center; text-align: center; } -.welcome-mark { font-size: 30px; color: var(--red); } -.welcome h1 { margin-bottom: 4px; } -.welcome p { margin: 0 0 22px; color: var(--muted); } -.statusbar { position: fixed; z-index: 5; left: 0; right: 0; bottom: 0; height: 34px; display: flex; align-items: center; gap: 8px; padding: 0 15px; background: var(--paper); border-top: 1px solid var(--line); color: var(--muted); font-size: 11px; } -.statusbar span { width: 7px; height: 7px; border-radius: 50%; background: #55a26a; } -button:disabled { cursor: not-allowed; opacity: .6; } -@media (max-width: 1050px) { .workspace { grid-template-columns: 210px 1fr; } .next-panel { display: none; } } -@media (max-width: 720px) { .workspace { display: block; } .sidebar { border-right: 0; border-bottom: 1px solid var(--line); } .sidebar-note { display: none; } .form-grid { grid-template-columns: 1fr; } .page-title { flex-direction: column; } } +html, body { margin: 0; min-width: 1024px; min-height: 100%; background: #fff; color: var(--ink); font-family: Inter, "PingFang SC", "Microsoft YaHei", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } +button { border: 0; font: inherit; cursor: pointer; } + +.creator-shell { min-height: 100vh; background: var(--sidebar); } + +.creator-header { + position: fixed; + z-index: 10; + inset: 0 0 auto 0; + height: 64px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 19px 0 20px; + background: #fff; +} + +.creator-brand { display: flex; align-items: center; gap: 13px; white-space: nowrap; } +.creator-brand strong { font-size: 20px; line-height: 1; font-weight: 600; letter-spacing: -.4px; } +.xiaohongshu-logo { + width: 73px; + height: 31px; + display: grid; + place-items: center; + border-radius: 17px; + background: var(--red); + color: #fff; + font-size: 15px; + font-weight: 800; + letter-spacing: -.5px; +} + +.creator-account { height: 46px; display: flex; align-items: center; gap: 7px; padding: 0 2px 0 10px; background: transparent; color: #242424; font-size: 16px; } +.account-photo { width: 28px; height: 28px; display: grid; place-items: center; border-radius: 50%; background: radial-gradient(circle at 65% 25%, #f6a45c 0 10%, transparent 11%), linear-gradient(135deg, #302722, #8b5b38 55%, #191919); color: #fff; font-size: 11px; box-shadow: inset 0 0 0 1px rgba(255,255,255,.18); } +.creator-account svg { color: #a0a0a0; margin-left: 2px; } + +.creator-sidebar { + position: fixed; + z-index: 5; + left: 0; + top: 64px; + bottom: 0; + width: 220px; + padding: 16px 20px 24px; + background: var(--sidebar); +} + +.publish-button { + width: 177px; + height: 44px; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + border-radius: 24px; + background: var(--red); + color: #fff; + font-size: 15px; + font-weight: 600; + box-shadow: none; +} +.publish-button span { margin: 0 2px; } + +.creator-nav { display: flex; flex-direction: column; gap: 1px; margin-top: 18px; } +.creator-nav-item { + width: 100%; + height: 47px; + display: grid; + grid-template-columns: 25px 1fr 18px; + align-items: center; + padding: 0 13px; + border-radius: 9px; + background: transparent; + color: #898989; + text-align: left; + font-size: 14px; +} +.creator-nav-item span { padding-left: 6px; } +.creator-nav-item > svg:last-child { grid-column: 3; } +.creator-nav-item:hover { background: #ededed; color: #555; } + +.creator-main { + position: fixed; + left: 220px; + right: 0; + top: 64px; + bottom: 0; + overflow-y: auto; + background: #fff; + border-top-left-radius: 20px; +} + +.creator-toolbar { + height: 73px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 39px 0 49px; +} +.creator-tabs { height: 100%; display: flex; align-items: center; gap: 17px; } +.creator-tabs button { min-width: 94px; height: 42px; padding: 0 16px; border-radius: 23px; background: transparent; color: #929292; font-size: 16px; font-weight: 600; } +.creator-tabs button.active { background: #f7f7f7; color: #202020; } +.draft-button { height: 40px; display: flex; align-items: center; gap: 7px; padding: 0 18px; border-radius: 22px; background: #f8f8f8; color: #262626; font-size: 14px; } + +.upload-panel { + min-height: 606px; + height: calc(100vh - 199px); + margin: 0 39px 0 34px; + display: grid; + place-items: center; + border-radius: 15px; + background: var(--panel); +} +.upload-center { display: flex; flex-direction: column; align-items: center; transform: translateY(-4px); } +.cloud-upload { height: 103px; display: flex; align-items: center; color: #dfdfdf; } +.cloud-upload svg { fill: #dfdfdf; stroke: #f8f8f8; } +.upload-center p { margin: 10px 0 14px; color: #242424; font-size: 14px; } +.upload-action { min-width: 120px; height: 41px; padding: 0 23px; border-radius: 22px; background: var(--red); color: #fff; font-size: 14px; } +.upload-action:hover, .publish-button:hover { background: #f20f35; } + +.upload-guides { + min-height: 134px; + display: grid; + grid-template-columns: repeat(3, 1fr); + margin: 56px 39px 0 203px; +} +.upload-guides > div { min-height: 112px; padding: 16px 42px; border-left: 1px solid #efefef; } +.upload-guides > div:first-child { border-left: 0; } +.upload-guides strong { display: block; margin-bottom: 10px; font-size: 14px; font-weight: 500; } +.upload-guides p { margin: 0; color: #a0a0a0; font-size: 12px; line-height: 1.8; } + +@media (max-width: 1300px) { + .creator-tabs { gap: 2px; } + .creator-tabs button { min-width: 82px; padding: 0 11px; } + .upload-guides { margin-left: 70px; } +} + +@media (max-height: 800px) { + .upload-panel { min-height: 480px; height: calc(100vh - 185px); } + .upload-guides { margin-top: 34px; } +} diff --git a/app/layout.tsx b/app/layout.tsx index 14a1629..39fcdcd 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,8 +2,8 @@ import type { Metadata } from "next"; import "./globals.css"; export const metadata: Metadata = { - title: "Rednote Content Studio", - description: "以项目为最高层级的小红书内容生成工作台。", + title: "创作服务平台", + description: "内容创作服务平台。", }; export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { diff --git a/components/Studio.tsx b/components/Studio.tsx index 232478c..99caa3c 100644 --- a/components/Studio.tsx +++ b/components/Studio.tsx @@ -1,143 +1,76 @@ "use client"; -import { useCallback, useEffect, useState } from "react"; +import { useState } from "react"; -type Recipe = { - positioning: string; - audience: string; - tone: string; - contentFormat: string; - titleMethod: string; - bodyStructure: string; - defaultTags: string[]; +type IconName = "home" | "note" | "code" | "chart" | "flag" | "bulb" | "school" | "book" | "draft" | "plus" | "chevron" | "upload"; + +const paths: Record = { + home: <>, + note: <>, + code: <>, + chart: <>, + flag: <>, + bulb: <>, + school: <>, + book: <>, + draft: <>, + plus: <>, + chevron: , + upload: <>, }; -type Project = { - id: string; - name: string; - description: string; - recipe: Recipe; - revision: number; - updatedAt: string; -}; - -async function api(url: string, init?: RequestInit): Promise { - const response = await fetch(url, { ...init, headers: { "Content-Type": "application/json", ...(init?.headers || {}) } }); - const data = await response.json(); - if (!response.ok) throw new Error(data.error || `请求失败:${response.status}`); - return data; +function Icon({ name, size = 19, strokeWidth = 1.55 }: { name: IconName; size?: number; strokeWidth?: number }) { + return ; } -const emptyRecipe: Recipe = { - positioning: "", - audience: "", - tone: "真诚自然", - contentFormat: "干货教程", - titleMethod: "", - bodyStructure: "", - defaultTags: [], -}; +const navItems: Array<{ icon: IconName; label: string; expandable?: boolean }> = [ + { icon: "home", label: "首页" }, + { icon: "note", label: "笔记管理" }, + { icon: "code", label: "Builder hub", expandable: true }, + { icon: "chart", label: "数据看板", expandable: true }, + { icon: "flag", label: "活动中心" }, + { icon: "bulb", label: "笔记灵感" }, + { icon: "school", label: "创作学院" }, + { icon: "book", label: "创作百科" }, +]; + +const tabs = ["上传视频", "上传图文", "写长文", "发播客"]; export default function Studio() { - const [projects, setProjects] = useState([]); - const [selected, setSelected] = useState(null); - const [draft, setDraft] = useState(null); - const [busy, setBusy] = useState(""); - const [message, setMessage] = useState("项目是最高层级:一套项目,对应一种稳定的内容生成方式。"); + const [activeTab, setActiveTab] = useState("上传视频"); - const loadProjects = useCallback(async () => { - const data = await api<{ projects: Project[] }>("/api/projects"); - setProjects(data.projects); - setSelected((current) => current || data.projects[0] || null); - }, []); - - useEffect(() => { void loadProjects(); }, [loadProjects]); - useEffect(() => { setDraft(selected ? structuredClone(selected) : null); }, [selected]); - - async function createProject() { - setBusy("create"); - try { - const data = await api<{ project: Project }>("/api/projects", { - method: "POST", - body: JSON.stringify({ name: `内容项目 ${projects.length + 1}` }), - }); - setProjects((items) => [data.project, ...items]); - setSelected(data.project); - setMessage("项目骨架已创建,请完善这类内容的统一生成方法。"); - } catch (error) { setMessage((error as Error).message); } - finally { setBusy(""); } - } - - async function saveProject() { - if (!draft) return; - setBusy("save"); - try { - const data = await api<{ project: Project }>(`/api/projects/${draft.id}`, { - method: "PUT", - body: JSON.stringify({ ...draft, revision: draft.revision }), - }); - setSelected(data.project); - setProjects((items) => items.map((item) => item.id === data.project.id ? data.project : item)); - setMessage(`项目方法已保存 · r${data.project.revision}`); - } catch (error) { setMessage((error as Error).message); } - finally { setBusy(""); } - } - - function updateRecipe(key: keyof Recipe, value: string | string[]) { - setDraft((current) => current ? { ...current, recipe: { ...emptyRecipe, ...current.recipe, [key]: value } } : current); - } - - return
-
-
R
Rednote Content Studio小红书内容生成器 · 起步结构
- + return
+
+
小红书创作服务平台
+
-
- + - {!draft ?
- ✦

先创建一个项目

为一类内容定义固定的定位、受众、语气和结构。

-
: <> -
-
项目配置

{draft.name}

{draft.description}

+
+
+
{tabs.map((tab) => )}
+ +
-
-

基本信息

- -