完善角色与消息权限:收紧板管内容处置,彻底删除仅站长,并统一管理删帖/评弹窗。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-20 14:30:08 +08:00
parent 1f2e4b2a33
commit c0462a3500
37 changed files with 1340 additions and 472 deletions

View File

@@ -2,10 +2,10 @@
import { useEffect, useState } from "react";
import Modal from "@/components/Modal";
import { POST_DELETE_TYPES } from "@/lib/api";
import StaffDeleteFields from "@/components/StaffDeleteFields";
/**
* Staff 软删帖:必填删除类型 + 理由。
* Staff 软删帖:必选删除类型;仅「其他」须填理由。
* 作者自删仍用 ConfirmDialog,不走此组件。
*/
export default function StaffDeletePostModal({
@@ -36,17 +36,21 @@ export default function StaffDeletePostModal({
if (!open) return null;
const reasonRequired = deleteType === "other";
const submit = () => {
const r = reason.trim();
if (!r) {
setErr("请填写删除理由");
return;
if (reasonRequired) {
if (!r) {
setErr("请填写删除理由");
return;
}
if ([...r].length > 200) {
setErr("删除理由不能超过 200 字");
return;
}
}
if ([...r].length > 200) {
setErr("删除理由不能超过 200 字");
return;
}
onConfirm({ delete_type: deleteType, delete_reason: r });
onConfirm({ delete_type: deleteType, delete_reason: reasonRequired ? r : "" });
};
return (
@@ -55,8 +59,8 @@ export default function StaffDeletePostModal({
title="删除帖子"
description={
title
? `将删除「${title}」。列表不再展示;原链接会显示删除类型与理由。`
: "列表不再展示;原链接会显示删除类型与理由。"
? `将删除「${title}」。列表不再展示;原链接会显示删除类型,选「其他」时还会展示补充理由。`
: "列表不再展示;原链接会显示删除类型,选「其他」时还会展示补充理由。"
}
onClose={onClose}
busy={busy}
@@ -74,51 +78,28 @@ export default function StaffDeletePostModal({
</>
}
>
<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);
<StaffDeleteFields
deleteType={deleteType}
onTypeChange={(value) => {
setDeleteType(value);
setErr("");
if (value !== "other") setReason("");
}}
reason={reason}
onReasonChange={(value) => {
setReason(value);
setErr("");
}}
placeholder="将展示在帖子专用页与作者通知中"
maxLength={200}
reasonId="post-delete-reason"
reasonPlaceholder="向读者说明删除原因(将展示在帖子页)"
typeHint="选「其他」时需补充理由;其余类型直接按标签展示给读者。"
reasonHint="将公开显示在帖子专用页与作者通知中。"
/>
{err && (
{err ? (
<p className="mt-2 text-[12px]" style={{ color: "var(--danger)" }}>
{err}
</p>
)}
) : null}
</Modal>
);
}