Files
jiang13-bbs/frontend/app/compose/page.tsx
freefire 0c2b0d2d6a 首次提交:姜十三论坛
后端 Go+Gin:认证/CSRF/限流、板块、帖子、评论、点赞、通知、用户资料、置顶推荐;前端 Next.js 16:发帖/编辑/删除、搜索、分页、点赞、通知中心、设置;基础设施 docker-compose 与配置模板;添加 .gitignore 与专有许可证(保留所有权利)
2026-09-12 01:11:05 +08:00

123 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { apiCreatePost, apiMe, fetchBoards, type Board } from "@/lib/api";
export default function ComposePage() {
const router = useRouter();
const [boards, setBoards] = useState<Board[]>([]);
const [boardId, setBoardId] = useState<number>(0);
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [tags, setTags] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
// 通过 /api/me 检查登录态(HttpOnly cookie 自动携带)
apiMe()
.then((res) => {
if (!res.user) {
router.push("/login?redirect=/compose");
return;
}
fetchBoards().then((data) => {
setBoards(data.boards);
if (data.boards.length > 0) setBoardId(data.boards[0].id);
});
})
.catch(() => router.push("/login?redirect=/compose"));
}, [router]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError("");
try {
const res = await apiCreatePost({
board_id: boardId,
title,
content,
tags,
post_type: "normal",
});
if (res.post) {
router.push(`/post/${res.post.id}`);
} else {
setError(res.error || "发帖失败");
}
} finally {
setLoading(false);
}
};
return (
<div className="max-w-3xl mx-auto">
<h1 className="text-2xl font-bold mb-6">发布新帖</h1>
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="p-3 rounded-lg bg-red-50 text-red-600 text-sm">{error}</div>
)}
<div>
<label className="block text-sm mb-1" style={{ color: "var(--text-secondary)" }}>板块</label>
<select
value={boardId}
onChange={(e) => setBoardId(Number(e.target.value))}
className="w-full px-3 py-2 rounded-lg border"
style={{ borderColor: "var(--border)", background: "var(--bg)", color: "var(--text)" }}
>
{boards.map((b) => (
<option key={b.id} value={b.id}>{b.name}</option>
))}
</select>
</div>
<div>
<label className="block text-sm mb-1" style={{ color: "var(--text-secondary)" }}>标题</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
maxLength={256}
placeholder="帖子标题"
className="w-full px-3 py-2 rounded-lg border"
style={{ borderColor: "var(--border)", background: "var(--bg)", color: "var(--text)" }}
/>
</div>
<div>
<label className="block text-sm mb-1" style={{ color: "var(--text-secondary)" }}>标签(逗号分隔,可选)</label>
<input
type="text"
value={tags}
onChange={(e) => setTags(e.target.value)}
placeholder="例如:技术,Go"
className="w-full px-3 py-2 rounded-lg border"
style={{ borderColor: "var(--border)", background: "var(--bg)", color: "var(--text)" }}
/>
</div>
<div>
<label className="block text-sm mb-1" style={{ color: "var(--text-secondary)" }}>内容(支持 Markdown)</label>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
required
rows={12}
placeholder="帖子内容,支持 Markdown 格式..."
className="w-full px-3 py-2 rounded-lg border resize-y font-mono"
style={{ borderColor: "var(--border)", background: "var(--bg)", color: "var(--text)" }}
/>
</div>
<button
type="submit"
disabled={loading || !title.trim() || !content.trim()}
className="px-6 py-2 rounded-lg text-white disabled:opacity-50"
style={{ background: "var(--primary)" }}
>
{loading ? "发布中..." : "发布帖子"}
</button>
</form>
</div>
);
}