feat: 管理端板块管理与旧版数据导入,补充部署运营文档

- 新增管理端板块管理页面与后端接口(admin_board)
- 新增旧版数据导入:legacyimport 服务、导入面板、importusers 命令行工具
- 聊天用户卡片、板块图标等 UI 组件与界面优化
- 补充 about/公告/1Panel 部署等文档
- gitignore 排除 dist/ 构建产物与 .agents/ 本地工具目录
This commit is contained in:
2026-09-24 03:37:44 +08:00
parent c1f6a6636b
commit 3e55b5d230
53 changed files with 5078 additions and 409 deletions

View File

@@ -3,7 +3,7 @@
import { useEffect, useMemo, useRef, useState } from "react";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
import { ArrowLeft, Copy, ExternalLink, Loader2 } from "lucide-react";
import { ArrowLeft, Copy, ExternalLink, FileText, Loader2 } from "lucide-react";
import {
apiAdminCreateSitePage,
apiAdminUpdateSitePage,
@@ -283,34 +283,68 @@ export default function PageComposeClient({
return (
<div className="admin-doc-page admin-doc-editor">
<header className="admin-doc-head">
<div className="min-w-0">
<Link href={backHref} className="admin-doc-back">
<ArrowLeft size={14} aria-hidden /> 返回列表
</Link>
<div className="admin-doc-head-title">
<header className="admin-doc-sticky">
<div className="admin-doc-sticky-inner">
<div className="admin-doc-sticky-title">
<Link href={backHref} className="admin-doc-back" aria-label="返回单页列表">
<ArrowLeft size={15} aria-hidden />
<span className="admin-doc-back-text">返回列表</span>
</Link>
<span className="admin-doc-sticky-icon" aria-hidden>
<FileText size={15} />
</span>
<h1>{isEdit ? "编辑单页" : "新建单页"}</h1>
<AdminStatusChip tone={savedPublished ? "ok" : savedPublished === false ? "warn" : "accent"}>
{savedPublished ? "已发布" : savedPublished === false ? "草稿" : "尚未保存"}
</AdminStatusChip>
</div>
<div className="admin-doc-sticky-actions">
{canViewOnline && savedPage ? (
<a
href={sitePagePublicPath(savedPage.slug)}
target="_blank"
rel="noopener noreferrer"
className="btn btn-line btn-sm"
title="打开已发布内容。未保存的修改不会出现在这里。"
>
<ExternalLink size={14} aria-hidden /> 线上页面
</a>
) : null}
<AdminSegmented<PublishKey>
role="group"
ariaLabel="保存后状态"
size="sm"
value={form.published ? "published" : "draft"}
onChange={(key) => patchForm({ published: key === "published" })}
options={[
{ key: "draft", label: "草稿" },
{ key: "published", label: "发布" },
]}
/>
<button type="button" className="btn btn-line btn-sm" disabled={!dirty || saving} onClick={discard}>
撤销修改
</button>
<button
type="button"
className="btn btn-primary btn-sm"
disabled={!dirty || saving}
onClick={requestSave}
aria-busy={saving || undefined}
>
{saving ? <Loader2 size={14} className="animate-spin" aria-hidden /> : null}
{saving ? "保存中…" : saveLabel}
</button>
<p className={`admin-doc-status${errors.form ? " is-error" : ""}`} aria-live="polite">
{statusText}
</p>
</div>
</div>
{canViewOnline && savedPage ? (
<a
href={sitePagePublicPath(savedPage.slug)}
target="_blank"
rel="noopener noreferrer"
className="btn btn-line"
title="打开已发布内容。未保存的修改不会出现在这里。"
>
<ExternalLink size={14} aria-hidden /> 查看线上页面
</a>
) : null}
</header>
<div className="admin-doc-split">
<div className="admin-doc-main">
<AdminField
className="admin-doc-title"
id="page-title"
label="标题"
maxLength={200}
@@ -437,50 +471,8 @@ export default function PageComposeClient({
/>
<p className="admin-doc-hint">数字越小越靠前,可以为负数。</p>
</div>
<div className="admin-doc-field">
<p className="admin-doc-label">保存后状态</p>
<AdminSegmented<PublishKey>
role="group"
ariaLabel="保存后状态"
size="sm"
value={form.published ? "published" : "draft"}
onChange={(key) => patchForm({ published: key === "published" })}
options={[
{ key: "draft", label: "草稿" },
{ key: "published", label: "已发布" },
]}
/>
<p className="admin-doc-hint">点选只改变这次要保存的状态,不会立刻发布或下线。</p>
</div>
</div>
</details>
<div className="admin-doc-actions" aria-busy={saving || undefined}>
<div className="admin-doc-actions-btns">
<button type="button" className="btn btn-line" disabled={!dirty || saving} onClick={discard}>
撤销修改
</button>
<button
type="button"
className="btn btn-primary"
disabled={!dirty || saving}
onClick={requestSave}
>
{saving ? <Loader2 size={15} className="animate-spin" aria-hidden /> : null}
{saving ? "保存中…" : saveLabel}
</button>
<p className={`admin-doc-status${errors.form ? " is-error" : ""}`} aria-live="polite">
{statusText}
</p>
</div>
<p className="admin-doc-hint">一次保存会写入标题、正文和页面设置。Ctrl/⌘ + Enter 与这个按钮相同。</p>
{canViewOnline ? (
<p className="admin-doc-hint">查看线上页面打开的是已发布内容,未保存的修改不会出现在那里。</p>
) : (
<p className="admin-doc-hint">草稿没有公开地址。要看正文,用编辑器里的预览。</p>
)}
</div>
</div>
<ConfirmDialog