Files
jiang13-bbs/frontend/components/StaffDeletePostModal.tsx
freefire bcb4f6a603 feat(moderation): 帖评删除占位感知与硬删清场
管理删帖须填类型与理由并展示专用页;评论软删占位、版主可硬删不占楼。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 07:01:04 +08:00

125 lines
3.4 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 { useEffect, useState } from "react";
import Modal from "@/components/Modal";
import { POST_DELETE_TYPES } from "@/lib/api";
/**
* Staff 软删帖:必填删除类型 + 理由。
* 作者自删仍用 ConfirmDialog,不走此组件。
*/
export default function StaffDeletePostModal({
open,
title,
busy,
onClose,
onConfirm,
}: {
open: boolean;
/** 帖子标题,展示在说明里 */
title?: string;
busy?: boolean;
onClose: () => void;
onConfirm: (opts: { delete_type: string; delete_reason: string }) => void;
}) {
const [deleteType, setDeleteType] = useState("spam");
const [reason, setReason] = useState("");
const [err, setErr] = useState("");
useEffect(() => {
if (open) {
setDeleteType("spam");
setReason("");
setErr("");
}
}, [open]);
if (!open) return null;
const submit = () => {
const r = reason.trim();
if (!r) {
setErr("请填写删除理由");
return;
}
if ([...r].length > 200) {
setErr("删除理由不能超过 200 字");
return;
}
onConfirm({ delete_type: deleteType, delete_reason: r });
};
return (
<Modal
open
title="删除帖子"
description={
title
? `将删除「${title}」。列表不再展示;原链接会显示删除类型与理由。`
: "列表不再展示;原链接会显示删除类型与理由。"
}
onClose={onClose}
busy={busy}
maxWidthClass="max-w-[440px]"
panelClassName="p-5"
ariaLabel="删除帖子"
footer={
<>
<button type="button" className="btn btn-line" onClick={onClose} disabled={busy}>
取消
</button>
<button type="button" className="btn btn-danger" onClick={submit} disabled={busy}>
{busy ? "删除中…" : "确认删除"}
</button>
</>
}
>
<p className="mb-2 text-[12.5px]" style={{ color: "var(--ink)" }}>
删除类型
</p>
<div className="flex flex-col gap-1.5 mb-4" role="radiogroup" aria-label="删除类型">
{POST_DELETE_TYPES.map((t) => {
const active = deleteType === t.value;
return (
<button
key={t.value}
type="button"
role="radio"
aria-checked={active}
onClick={() => setDeleteType(t.value)}
className="text-left rounded-lg px-3 py-2 text-[13px] transition-colors"
style={{
border: `1.5px solid ${active ? "var(--accent)" : "var(--line)"}`,
background: active ? "var(--accent-soft)" : "var(--panel-2)",
color: active ? "var(--accent)" : "var(--ink)",
}}
>
{t.label}
</button>
);
})}
</div>
<label className="block mb-1.5 text-[12.5px]" style={{ color: "var(--ink)" }} htmlFor="delete-reason">
删除理由 <span style={{ color: "var(--danger)" }}>*</span>
</label>
<textarea
id="delete-reason"
className="field"
rows={3}
value={reason}
onChange={(e) => {
setReason(e.target.value);
setErr("");
}}
placeholder="将展示在帖子专用页与作者通知中"
maxLength={200}
/>
{err && (
<p className="mt-2 text-[12px]" style={{ color: "var(--danger)" }}>
{err}
</p>
)}
</Modal>
);
}