v0.10.5 首页 H2 产品线入口 ×6:ImageBoxWidget 重构为 Elementor 忠实 DOM + slim 白名单补齐 + H2 scoped CSS(post-18 对齐,桌面/移动/hover 实测一致)
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* # 路由 /admin/contents/$id 内容编辑页
|
||||
* id 为 new 时新建;否则加载正本(默认语言)编辑。HTML 使用 TextArea 明文编辑。
|
||||
*/
|
||||
import { createFileRoute, useNavigate, useRouter } from "@tanstack/react-router";
|
||||
import { App, Button, Card, Form, Input, Select, Space } from "antd";
|
||||
import { ArrowLeftOutlined, SaveOutlined } from "@ant-design/icons";
|
||||
import { adminGetContent, adminSaveContent } from "~/server/admin";
|
||||
import { CONTENT_STATUS_OPTIONS, Forbidden, guardAdmin } from "~/components/admin/common";
|
||||
|
||||
/** # 类型 编辑表单数据形状(新建时给默认值) **/
|
||||
type ContentForm = {
|
||||
id: number;
|
||||
type: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
excerpt: string;
|
||||
html: string;
|
||||
status: string;
|
||||
template: string;
|
||||
seoTitle: string;
|
||||
seoDescription: string;
|
||||
};
|
||||
|
||||
export const Route = createFileRoute("/admin/contents/$id")({
|
||||
loader: ({ params }) =>
|
||||
guardAdmin(async () => {
|
||||
if (params.id === "new") {
|
||||
// 权限探测 + 返回新建默认值
|
||||
await adminGetContent({ data: { id: 0 } });
|
||||
const fresh: ContentForm = {
|
||||
id: 0,
|
||||
type: "page",
|
||||
slug: "",
|
||||
title: "",
|
||||
excerpt: "",
|
||||
html: "",
|
||||
status: "draft",
|
||||
template: "default",
|
||||
seoTitle: "",
|
||||
seoDescription: "",
|
||||
};
|
||||
return fresh;
|
||||
}
|
||||
const c = await adminGetContent({ data: { id: Number(params.id) } });
|
||||
if (!c) return null;
|
||||
const form: ContentForm = {
|
||||
id: c.id,
|
||||
type: c.type,
|
||||
slug: c.slug,
|
||||
title: c.title,
|
||||
excerpt: c.excerpt,
|
||||
html: c.html,
|
||||
status: c.status,
|
||||
template: c.template,
|
||||
seoTitle: c.seoTitle ?? "",
|
||||
seoDescription: c.seoDescription ?? "",
|
||||
};
|
||||
return form;
|
||||
}),
|
||||
component: ContentEditPage,
|
||||
});
|
||||
|
||||
/** # 函数 内容编辑页面组件 */
|
||||
function ContentEditPage() {
|
||||
const data = Route.useLoaderData();
|
||||
const navigate = useNavigate();
|
||||
const router = useRouter();
|
||||
const { message } = App.useApp();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
if (!data) return <Forbidden />;
|
||||
// 收窄后的非空引用(闭包内使用)
|
||||
const content = data;
|
||||
|
||||
/** # 函数 保存内容:提交到服务端 → 提示 → 返回列表 */
|
||||
async function handleSave(values: Omit<ContentForm, "id">) {
|
||||
try {
|
||||
await adminSaveContent({ data: { ...values, id: content.id || undefined } });
|
||||
message.success("保存成功");
|
||||
await router.invalidate();
|
||||
void navigate({ to: "/admin/contents/" });
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : "保存失败");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card size="small">
|
||||
<Space style={{ marginBottom: 16 }}>
|
||||
<Button icon={<ArrowLeftOutlined aria-hidden />} onClick={() => void navigate({ to: "/admin/contents/" })}>
|
||||
返回列表
|
||||
</Button>
|
||||
<h2 style={{ margin: 0, fontSize: 18 }}>{content.id ? `编辑内容 #${content.id}` : "新建内容"}</h2>
|
||||
</Space>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={content}
|
||||
style={{ maxWidth: 860 }}
|
||||
onFinish={(v) => void handleSave(v)}
|
||||
>
|
||||
<Space size={16} style={{ display: "flex" }} align="start">
|
||||
<Form.Item name="title" label="标题" rules={[{ required: true, message: "请输入标题" }]} style={{ minWidth: 420, flex: 1 }}>
|
||||
<Input placeholder="内容标题" />
|
||||
</Form.Item>
|
||||
<Form.Item name="slug" label="别名(留空自动生成)">
|
||||
<Input placeholder="url-slug" style={{ width: 260 }} />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
<Space size={16} style={{ display: "flex" }}>
|
||||
<Form.Item name="type" label="类型">
|
||||
<Select
|
||||
style={{ width: 140 }}
|
||||
options={[
|
||||
{ value: "page", label: "页面 page" },
|
||||
{ value: "post", label: "文章 post" },
|
||||
{ value: "landing", label: "落地页 landing" },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="status" label="状态">
|
||||
<Select style={{ width: 140 }} options={CONTENT_STATUS_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item name="template" label="模板">
|
||||
<Input style={{ width: 200 }} placeholder="default / home / product …" />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
<Form.Item name="excerpt" label="摘要">
|
||||
<Input.TextArea rows={2} />
|
||||
</Form.Item>
|
||||
<Form.Item name="html" label="正文 HTML">
|
||||
<Input.TextArea rows={18} style={{ fontFamily: "monospace", fontSize: 12 }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="seoTitle" label="SEO 标题">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="seoDescription" label="SEO 描述">
|
||||
<Input.TextArea rows={2} />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit" icon={<SaveOutlined aria-hidden />}>
|
||||
保存
|
||||
</Button>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user