2026-06-02 12:38:01 +08:00
|
|
|
|
import { CheckCircleOutlined, FlagOutlined, MailOutlined, PhoneOutlined } from "@ant-design/icons";
|
2026-06-04 16:03:49 +08:00
|
|
|
|
import { useEffect, useState, type FormEvent } from "react";
|
|
|
|
|
|
import { publicConfigClient, type WebPublicConfig } from "../../api/publicConfigClient";
|
2026-06-02 12:38:01 +08:00
|
|
|
|
import { reportClient, type ReportInput } from "../../api/reportClient";
|
2026-06-08 16:32:16 +08:00
|
|
|
|
import "../../styles/pages/compliance.css";
|
2026-06-02 12:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
type SubmitState = "idle" | "loading" | "success" | "error";
|
|
|
|
|
|
|
|
|
|
|
|
const REPORT_TYPES = [
|
|
|
|
|
|
{ value: "spam", label: "垃圾内容" },
|
|
|
|
|
|
{ value: "abuse", label: "滥用 / 骚扰" },
|
|
|
|
|
|
{ value: "copyright", label: "侵权 / 版权" },
|
|
|
|
|
|
{ value: "illegal", label: "违法违规" },
|
|
|
|
|
|
{ value: "other", label: "其他" },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const TARGET_TYPES = [
|
|
|
|
|
|
{ value: "project", label: "项目" },
|
|
|
|
|
|
{ value: "community_post", label: "社区作品" },
|
|
|
|
|
|
{ value: "user", label: "用户" },
|
|
|
|
|
|
{ value: "comment", label: "评论" },
|
|
|
|
|
|
{ value: "other", label: "其他" },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
function ReportPage() {
|
|
|
|
|
|
const [reportType, setReportType] = useState("");
|
|
|
|
|
|
const [targetType, setTargetType] = useState("");
|
|
|
|
|
|
const [targetId, setTargetId] = useState("");
|
|
|
|
|
|
const [title, setTitle] = useState("");
|
|
|
|
|
|
const [description, setDescription] = useState("");
|
|
|
|
|
|
const [contactName, setContactName] = useState("");
|
|
|
|
|
|
const [contactEmail, setContactEmail] = useState("");
|
|
|
|
|
|
const [contactPhone, setContactPhone] = useState("");
|
|
|
|
|
|
const [submitState, setSubmitState] = useState<SubmitState>("idle");
|
|
|
|
|
|
const [errorMsg, setErrorMsg] = useState("");
|
2026-06-04 16:03:49 +08:00
|
|
|
|
const [publicConfig, setPublicConfig] = useState<WebPublicConfig>({});
|
2026-06-02 12:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
const canSubmit =
|
|
|
|
|
|
submitState !== "loading" && reportType !== "" && title.trim() !== "" && description.trim() !== "";
|
|
|
|
|
|
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
|
setReportType("");
|
|
|
|
|
|
setTargetType("");
|
|
|
|
|
|
setTargetId("");
|
|
|
|
|
|
setTitle("");
|
|
|
|
|
|
setDescription("");
|
|
|
|
|
|
setContactName("");
|
|
|
|
|
|
setContactEmail("");
|
|
|
|
|
|
setContactPhone("");
|
|
|
|
|
|
setSubmitState("idle");
|
|
|
|
|
|
setErrorMsg("");
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-04 16:03:49 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
let cancelled = false;
|
|
|
|
|
|
publicConfigClient
|
|
|
|
|
|
.get()
|
|
|
|
|
|
.then((config) => {
|
|
|
|
|
|
if (!cancelled) setPublicConfig(config);
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
if (!cancelled) setPublicConfig({});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
cancelled = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-06-02 12:38:01 +08:00
|
|
|
|
const handleSubmit = async (event: FormEvent) => {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
if (!canSubmit) return;
|
|
|
|
|
|
setSubmitState("loading");
|
|
|
|
|
|
setErrorMsg("");
|
|
|
|
|
|
try {
|
|
|
|
|
|
const input: ReportInput = {
|
|
|
|
|
|
reportType,
|
|
|
|
|
|
targetType: targetType || undefined,
|
|
|
|
|
|
targetId: targetId.trim() || undefined,
|
|
|
|
|
|
title: title.trim(),
|
|
|
|
|
|
description: description.trim(),
|
|
|
|
|
|
contactName: contactName.trim() || undefined,
|
|
|
|
|
|
contactEmail: contactEmail.trim() || undefined,
|
|
|
|
|
|
contactPhone: contactPhone.trim() || undefined,
|
|
|
|
|
|
pageUrl: window.location.href,
|
|
|
|
|
|
};
|
|
|
|
|
|
await reportClient.submit(input);
|
|
|
|
|
|
setSubmitState("success");
|
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
|
setErrorMsg(error instanceof Error ? error.message : "提交失败,请稍后重试");
|
|
|
|
|
|
setSubmitState("error");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<section className="report-page page-motion">
|
|
|
|
|
|
<div className="report-page__inner">
|
|
|
|
|
|
<header className="report-hero">
|
|
|
|
|
|
<span className="report-hero__icon"><FlagOutlined /></span>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h1>投诉举报</h1>
|
|
|
|
|
|
<p>提交后会同步到服务器举报工单,工作人员会在审核台中查看处理。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="report-contact-strip">
|
2026-06-04 16:03:49 +08:00
|
|
|
|
<span><MailOutlined /> {publicConfig.contactEmail || "由服务器配置"}</span>
|
|
|
|
|
|
<span><PhoneOutlined /> {publicConfig.contactPhone || "由服务器配置"}</span>
|
|
|
|
|
|
<span>{publicConfig.icpRecord || "由服务器配置"}</span>
|
2026-06-02 12:38:01 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{submitState === "success" ? (
|
|
|
|
|
|
<div className="report-success">
|
|
|
|
|
|
<CheckCircleOutlined />
|
|
|
|
|
|
<h2>提交成功</h2>
|
|
|
|
|
|
<p>感谢反馈,举报内容已进入服务器工单列表。</p>
|
|
|
|
|
|
<button type="button" onClick={resetForm}>继续举报</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<form className="report-form" onSubmit={handleSubmit}>
|
|
|
|
|
|
<div className="report-form__grid">
|
|
|
|
|
|
<label>
|
|
|
|
|
|
举报类型 *
|
|
|
|
|
|
<select value={reportType} onChange={(event) => setReportType(event.target.value)}>
|
|
|
|
|
|
<option value="">请选择</option>
|
|
|
|
|
|
{REPORT_TYPES.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label>
|
|
|
|
|
|
对象类型
|
|
|
|
|
|
<select value={targetType} onChange={(event) => setTargetType(event.target.value)}>
|
|
|
|
|
|
<option value="">请选择</option>
|
|
|
|
|
|
{TARGET_TYPES.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label>
|
|
|
|
|
|
对象 ID
|
|
|
|
|
|
<input value={targetId} onChange={(event) => setTargetId(event.target.value)} placeholder="作品、项目或用户 ID" />
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<label>
|
|
|
|
|
|
标题 *
|
|
|
|
|
|
<input
|
|
|
|
|
|
value={title}
|
|
|
|
|
|
onChange={(event) => setTitle(event.target.value)}
|
|
|
|
|
|
placeholder="简要描述问题"
|
|
|
|
|
|
maxLength={100}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label>
|
|
|
|
|
|
详细说明 *
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
value={description}
|
|
|
|
|
|
onChange={(event) => setDescription(event.target.value)}
|
|
|
|
|
|
placeholder="请说明举报原因、相关页面或证据线索"
|
|
|
|
|
|
maxLength={2000}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="report-form__grid">
|
|
|
|
|
|
<label>
|
|
|
|
|
|
联系人
|
|
|
|
|
|
<input value={contactName} onChange={(event) => setContactName(event.target.value)} placeholder="可选" />
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label>
|
|
|
|
|
|
联系邮箱
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="email"
|
|
|
|
|
|
value={contactEmail}
|
|
|
|
|
|
onChange={(event) => setContactEmail(event.target.value)}
|
|
|
|
|
|
placeholder="可选"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label>
|
|
|
|
|
|
联系电话
|
|
|
|
|
|
<input value={contactPhone} onChange={(event) => setContactPhone(event.target.value)} placeholder="可选" />
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{submitState === "error" ? <p className="report-form__error">{errorMsg}</p> : null}
|
|
|
|
|
|
|
|
|
|
|
|
<div className="report-form__actions">
|
|
|
|
|
|
<button type="submit" disabled={!canSubmit}>
|
|
|
|
|
|
{submitState === "loading" ? "提交中..." : "提交举报"}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<span>必填字段完整后才可提交。</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default ReportPage;
|