ui: mirror rednote creator platform layout
Verify and deploy production / verify-and-deploy (push) Successful in 2m29s
Verify and deploy production / verify-and-deploy (push) Successful in 2m29s
This commit is contained in:
+150
-56
@@ -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; }
|
||||
}
|
||||
|
||||
+2
-2
@@ -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 }>) {
|
||||
|
||||
+59
-126
@@ -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<IconName, React.ReactNode> = {
|
||||
home: <><path d="m4 10 8-6 8 6v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2Z"/><path d="M9 14h6"/></>,
|
||||
note: <><rect x="5" y="3" width="14" height="18" rx="3"/><path d="M9 8h6M9 12h5"/></>,
|
||||
code: <><rect x="3" y="4" width="18" height="16" rx="3"/><path d="m9 9-3 3 3 3m6-6 3 3-3 3m-2-7-2 8"/></>,
|
||||
chart: <><rect x="3" y="4" width="18" height="16" rx="3"/><path d="m7 15 3-3 3 2 4-5"/></>,
|
||||
flag: <><path d="M6 21V5m0 1c4-3 7 3 12 0v9c-5 3-8-3-12 0"/></>,
|
||||
bulb: <><path d="M9 18h6m-5 3h4"/><path d="M8.2 15.5a7 7 0 1 1 7.6 0c-.8.5-.8 1.5-.8 2.5H9c0-1 0-2-.8-2.5Z"/></>,
|
||||
school: <><path d="m3 9 9-5 9 5-9 5Z"/><path d="M7 12v5c3 2 7 2 10 0v-5m4-3v6"/></>,
|
||||
book: <><path d="M6 3h11a2 2 0 0 1 2 2v16H7a3 3 0 0 1-3-3V5a2 2 0 0 1 2-2Z"/><path d="M7 15h12M9 7h6"/></>,
|
||||
draft: <><path d="M5 5h14v15H5z"/><path d="M9 3h6v4H9zM8 11h8m-8 4h6"/></>,
|
||||
plus: <><rect x="3" y="5" width="18" height="14" rx="3"/><path d="M12 9v6m-3-3h6"/></>,
|
||||
chevron: <path d="m8 10 4 4 4-4"/>,
|
||||
upload: <><path d="M7 18a5 5 0 0 1-.5-10A7 7 0 0 1 20 10.5 4 4 0 0 1 19 18"/><path d="m9 12 3-3 3 3m-3-3v10"/></>,
|
||||
};
|
||||
|
||||
type Project = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
recipe: Recipe;
|
||||
revision: number;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
async function api<T>(url: string, init?: RequestInit): Promise<T> {
|
||||
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 <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">{paths[name]}</svg>;
|
||||
}
|
||||
|
||||
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<Project[]>([]);
|
||||
const [selected, setSelected] = useState<Project | null>(null);
|
||||
const [draft, setDraft] = useState<Project | null>(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 <main className="studio-shell">
|
||||
<header className="topbar">
|
||||
<div className="brand"><span className="logo">R</span><div><strong>Rednote Content Studio</strong><small>小红书内容生成器 · 起步结构</small></div></div>
|
||||
<button className="primary" onClick={createProject} disabled={Boolean(busy)}>+ 新建项目</button>
|
||||
return <main className="creator-shell">
|
||||
<header className="creator-header">
|
||||
<div className="creator-brand"><span className="xiaohongshu-logo">小红书</span><strong>创作服务平台</strong></div>
|
||||
<button className="creator-account"><span className="account-photo">创</span><span>内容工作室</span><Icon name="chevron" size={14} /></button>
|
||||
</header>
|
||||
|
||||
<div className="workspace">
|
||||
<aside className="sidebar">
|
||||
<div className="section-head"><span>项目</span><small>最高层级</small></div>
|
||||
{projects.length === 0 && <button className="empty-project" onClick={createProject}>创建第一个内容项目</button>}
|
||||
{projects.map((project) => <button key={project.id} className={`project-row ${selected?.id === project.id ? "selected" : ""}`} onClick={() => setSelected(project)}>
|
||||
<span>{project.name}</span><small>r{project.revision}</small>
|
||||
</button>)}
|
||||
<div className="sidebar-note"><b>项目是什么?</b><p>项目代表一类用同样方式生成的内容,例如“每周护肤成分拆解”或“上海周末探店”。</p></div>
|
||||
</aside>
|
||||
<aside className="creator-sidebar">
|
||||
<button className="publish-button"><Icon name="plus" size={19} strokeWidth={1.8} /><span>发布笔记</span><Icon name="chevron" size={18} strokeWidth={2} /></button>
|
||||
<nav className="creator-nav">
|
||||
{navItems.map((item) => <button className="creator-nav-item" key={item.label}><Icon name={item.icon} /><span>{item.label}</span>{item.expandable && <Icon name="chevron" size={17} />}</button>)}
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
{!draft ? <section className="welcome">
|
||||
<span className="welcome-mark">✦</span><h1>先创建一个项目</h1><p>为一类内容定义固定的定位、受众、语气和结构。</p><button className="primary" onClick={createProject}>创建项目</button>
|
||||
</section> : <>
|
||||
<section className="project-editor">
|
||||
<div className="page-title"><div><span>项目配置</span><h1>{draft.name}</h1><p>{draft.description}</p></div><button className="primary" onClick={saveProject} disabled={Boolean(busy)}>{busy === "save" ? "保存中…" : "保存项目方法"}</button></div>
|
||||
<section className="creator-main">
|
||||
<div className="creator-toolbar">
|
||||
<div className="creator-tabs">{tabs.map((tab) => <button key={tab} className={activeTab === tab ? "active" : ""} onClick={() => setActiveTab(tab)}>{tab}</button>)}</div>
|
||||
<button className="draft-button"><Icon name="draft" size={18} /><span>草稿箱(0)</span></button>
|
||||
</div>
|
||||
|
||||
<div className="form-card">
|
||||
<h2>基本信息</h2>
|
||||
<label>项目名称<input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></label>
|
||||
<label>项目说明<textarea rows={2} value={draft.description} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></label>
|
||||
</div>
|
||||
<div className="upload-panel">
|
||||
<div className="upload-center">
|
||||
<div className="cloud-upload"><Icon name="upload" size={104} strokeWidth={1.15} /></div>
|
||||
<p>{activeTab === "上传视频" ? "拖拽视频到此或点击上传" : activeTab === "上传图文" ? "拖拽图片到此或点击上传" : activeTab === "写长文" ? "点击开始创作长文" : "拖拽音频到此或点击上传"}</p>
|
||||
<button className="upload-action">{activeTab === "上传视频" ? "上传视频" : activeTab === "上传图文" ? "上传图文" : activeTab === "写长文" ? "开始写作" : "上传音频"}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-card">
|
||||
<div className="card-title"><div><h2>统一生成方法</h2><p>项目下的每篇内容都会继承这些规则。</p></div><span className="status-pill">结构已建立</span></div>
|
||||
<div className="form-grid">
|
||||
<label>内容定位<textarea rows={3} value={draft.recipe?.positioning || ""} onChange={(event) => updateRecipe("positioning", event.target.value)} placeholder="这个项目持续提供什么价值?" /></label>
|
||||
<label>目标受众<textarea rows={3} value={draft.recipe?.audience || ""} onChange={(event) => updateRecipe("audience", event.target.value)} placeholder="内容主要写给谁?" /></label>
|
||||
<label>表达语气<select value={draft.recipe?.tone || "真诚自然"} onChange={(event) => updateRecipe("tone", event.target.value)}><option>真诚自然</option><option>专业可信</option><option>轻松活泼</option><option>温柔治愈</option><option>犀利直接</option></select></label>
|
||||
<label>内容形式<select value={draft.recipe?.contentFormat || "干货教程"} onChange={(event) => updateRecipe("contentFormat", event.target.value)}><option>干货教程</option><option>清单合集</option><option>经验故事</option><option>产品种草</option><option>测评对比</option></select></label>
|
||||
<label>标题方法<textarea rows={3} value={draft.recipe?.titleMethod || ""} onChange={(event) => updateRecipe("titleMethod", event.target.value)} placeholder="标题采用什么固定方法?" /></label>
|
||||
<label>正文结构<textarea rows={3} value={draft.recipe?.bodyStructure || ""} onChange={(event) => updateRecipe("bodyStructure", event.target.value)} placeholder="例如:痛点开场 → 3 个步骤 → 总结" /></label>
|
||||
</div>
|
||||
<label>默认标签<input value={(draft.recipe?.defaultTags || []).join(",")} onChange={(event) => updateRecipe("defaultTags", event.target.value.split(/[,,]/).map((item) => item.trim()).filter(Boolean))} placeholder="经验分享,实用干货" /></label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside className="next-panel">
|
||||
<span className="eyebrow">下一阶段</span><h2>内容生成区</h2><p>本次只搭建项目层和生成方法,不开始实际文案生成。</p>
|
||||
<div className="future-step"><b>01</b><span><strong>输入选题简报</strong><small>主题、角度、要点和素材</small></span></div>
|
||||
<div className="future-step"><b>02</b><span><strong>按项目方法生成</strong><small>标题、正文与标签</small></span></div>
|
||||
<div className="future-step"><b>03</b><span><strong>保存内容草稿</strong><small>编辑、审核与导出</small></span></div>
|
||||
<button disabled>生成内容(待接入)</button>
|
||||
</aside>
|
||||
</>}
|
||||
</div>
|
||||
<footer className="statusbar"><span />{message}</footer>
|
||||
<div className="upload-guides">
|
||||
<div><strong>{activeTab === "上传视频" ? "视频大小" : "文件大小"}</strong><p>{activeTab === "上传视频" ? <>支持时长4小时以内,<br />最大20GB的视频文件</> : <>单个文件不超过20GB,<br />支持批量选择上传</>}</p></div>
|
||||
<div><strong>{activeTab === "上传视频" ? "视频格式" : "支持格式"}</strong><p>{activeTab === "上传视频" ? <>支持常用视频格式,<br />推荐使用mp4、mov</> : <>支持平台常用格式,<br />上传后可继续编辑</>}</p></div>
|
||||
<div><strong>{activeTab === "上传视频" ? "视频分辨率" : "内容质量"}</strong><p>{activeTab === "上传视频" ? <>超过1080P的视频用网页端上传画质更清晰</> : <>建议上传清晰、完整的原创内容</>}</p></div>
|
||||
</div>
|
||||
</section>
|
||||
</main>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user