|
|
@@ -0,0 +1,693 @@
|
|
|
+"use client";
|
|
|
+
|
|
|
+import {
|
|
|
+ CheckCircleOutlined,
|
|
|
+ CloseCircleOutlined,
|
|
|
+ EyeOutlined,
|
|
|
+ PlayCircleOutlined,
|
|
|
+ ReloadOutlined,
|
|
|
+ SearchOutlined,
|
|
|
+ UndoOutlined,
|
|
|
+} from "@ant-design/icons";
|
|
|
+import {
|
|
|
+ App,
|
|
|
+ Button,
|
|
|
+ DatePicker,
|
|
|
+ Descriptions,
|
|
|
+ Form,
|
|
|
+ Image,
|
|
|
+ Input,
|
|
|
+ Modal,
|
|
|
+ Select,
|
|
|
+ Space,
|
|
|
+ Table,
|
|
|
+ Tag,
|
|
|
+ Tooltip,
|
|
|
+} from "antd";
|
|
|
+import type { ColumnsType, TablePaginationConfig } from "antd/es/table";
|
|
|
+import type React from "react";
|
|
|
+import { useEffect, useState } from "react";
|
|
|
+import UserBrief from "@/components/UserBrief";
|
|
|
+import { getTrendAdminPage, reviewTrend } from "@/services/trendApply";
|
|
|
+import type {
|
|
|
+ PagerTrendAdminDTO,
|
|
|
+ TrendAdminDTO,
|
|
|
+ TrendAdminQuery,
|
|
|
+} from "@/types/api/trendApply";
|
|
|
+import { formatTimestamp } from "@/utils/date";
|
|
|
+
|
|
|
+const TREND_TYPE_MAP: Record<number, string> = {
|
|
|
+ 0: "纯文本",
|
|
|
+ 1: "图片",
|
|
|
+ 2: "视频",
|
|
|
+ 3: "图文混合",
|
|
|
+ 4: "视频文本混合",
|
|
|
+};
|
|
|
+
|
|
|
+const GENDER_MAP: Record<number, string> = {
|
|
|
+ 0: "未知",
|
|
|
+ 1: "男",
|
|
|
+ 2: "女",
|
|
|
+};
|
|
|
+
|
|
|
+const TrendApplyPage: React.FC = () => {
|
|
|
+ const { message } = App.useApp();
|
|
|
+ const [searchForm] = Form.useForm();
|
|
|
+ const [reviewForm] = Form.useForm();
|
|
|
+
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
+ const [dataSource, setDataSource] = useState<TrendAdminDTO[]>([]);
|
|
|
+ const [total, setTotal] = useState(0);
|
|
|
+ const [queryParams, setQueryParams] = useState<TrendAdminQuery>({
|
|
|
+ pageSize: 20,
|
|
|
+ pageIndex: 1,
|
|
|
+ });
|
|
|
+
|
|
|
+ const [detailModalVisible, setDetailModalVisible] = useState(false);
|
|
|
+ const [currentRecord, setCurrentRecord] = useState<TrendAdminDTO | null>(
|
|
|
+ null,
|
|
|
+ );
|
|
|
+
|
|
|
+ const [reviewModalVisible, setReviewModalVisible] = useState(false);
|
|
|
+ const [reviewLoading, setReviewLoading] = useState(false);
|
|
|
+
|
|
|
+ const [videoPreviewUrl, setVideoPreviewUrl] = useState<string | null>(null);
|
|
|
+
|
|
|
+ const loadPageData = async () => {
|
|
|
+ setLoading(true);
|
|
|
+ try {
|
|
|
+ const response: PagerTrendAdminDTO = await getTrendAdminPage(queryParams);
|
|
|
+ setDataSource(response.items || []);
|
|
|
+ setTotal(response.total || 0);
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to load trend apply list:", error);
|
|
|
+ message.error("加载动态审核列表失败");
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // biome-ignore lint/correctness/useExhaustiveDependencies: queryParams drives reload
|
|
|
+ useEffect(() => {
|
|
|
+ loadPageData();
|
|
|
+ }, [queryParams]);
|
|
|
+
|
|
|
+ const renderStatusTag = (status?: number) => {
|
|
|
+ if (status === 0) return <Tag color="processing">待审核</Tag>;
|
|
|
+ if (status === 1) return <Tag color="success">审核通过</Tag>;
|
|
|
+ if (status === 2) return <Tag color="error">审核不通过</Tag>;
|
|
|
+ return <Tag>-</Tag>;
|
|
|
+ };
|
|
|
+
|
|
|
+ const renderTrendType = (type?: number) => {
|
|
|
+ if (type === undefined || type === null) return "-";
|
|
|
+ return TREND_TYPE_MAP[type] ?? String(type);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSearch = () => {
|
|
|
+ const values = searchForm.getFieldsValue();
|
|
|
+ const dateRange = values.dateRange;
|
|
|
+ setQueryParams({
|
|
|
+ ...queryParams,
|
|
|
+ pageIndex: 1,
|
|
|
+ searchKeyword: values.searchKeyword || undefined,
|
|
|
+ status:
|
|
|
+ values.status === "" ||
|
|
|
+ values.status === undefined ||
|
|
|
+ values.status === null
|
|
|
+ ? undefined
|
|
|
+ : Number(values.status),
|
|
|
+ type:
|
|
|
+ values.type === "" || values.type === undefined || values.type === null
|
|
|
+ ? undefined
|
|
|
+ : Number(values.type),
|
|
|
+ gender:
|
|
|
+ values.gender === "" ||
|
|
|
+ values.gender === undefined ||
|
|
|
+ values.gender === null
|
|
|
+ ? undefined
|
|
|
+ : Number(values.gender),
|
|
|
+ startDay: dateRange?.[0] ? dateRange[0].format("YYYY-MM-DD") : undefined,
|
|
|
+ endDay: dateRange?.[1] ? dateRange[1].format("YYYY-MM-DD") : undefined,
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleReset = () => {
|
|
|
+ searchForm.resetFields();
|
|
|
+ setQueryParams({ pageSize: 20, pageIndex: 1 });
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleRefresh = () => {
|
|
|
+ loadPageData();
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleTableChange = (pagination: TablePaginationConfig) => {
|
|
|
+ setQueryParams({
|
|
|
+ ...queryParams,
|
|
|
+ pageIndex: pagination.current || 1,
|
|
|
+ pageSize: pagination.pageSize || 20,
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleViewDetail = (record: TrendAdminDTO) => {
|
|
|
+ setCurrentRecord(record);
|
|
|
+ setDetailModalVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleOpenReview = (record: TrendAdminDTO, status: 1 | 2) => {
|
|
|
+ setCurrentRecord(record);
|
|
|
+ reviewForm.resetFields();
|
|
|
+ reviewForm.setFieldsValue({ status, reviewReason: "" });
|
|
|
+ setReviewModalVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleCloseReviewModal = () => {
|
|
|
+ setReviewModalVisible(false);
|
|
|
+ reviewForm.resetFields();
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSubmitReview = async () => {
|
|
|
+ if (!currentRecord?.id) {
|
|
|
+ message.error("缺少当前审核记录");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const values = await reviewForm.validateFields();
|
|
|
+ setReviewLoading(true);
|
|
|
+
|
|
|
+ await reviewTrend({
|
|
|
+ id: currentRecord.id,
|
|
|
+ status: values.status,
|
|
|
+ reviewReason: values.reviewReason || undefined,
|
|
|
+ });
|
|
|
+
|
|
|
+ message.success("审核提交成功");
|
|
|
+ handleCloseReviewModal();
|
|
|
+ loadPageData();
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to submit trend review:", error);
|
|
|
+ if (error instanceof Error) {
|
|
|
+ message.error(`审核提交失败:${error.message}`);
|
|
|
+ } else {
|
|
|
+ message.error("审核提交失败");
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ setReviewLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const columns: ColumnsType<TrendAdminDTO> = [
|
|
|
+ {
|
|
|
+ title: "用户",
|
|
|
+ key: "user",
|
|
|
+ width: 180,
|
|
|
+ fixed: "left",
|
|
|
+ render: (_: unknown, record: TrendAdminDTO) => (
|
|
|
+ <UserBrief
|
|
|
+ avatar={record.avatar}
|
|
|
+ nickname={record.nickname}
|
|
|
+ userNo={record.userNo}
|
|
|
+ size={40}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "性别",
|
|
|
+ dataIndex: "gender",
|
|
|
+ key: "gender",
|
|
|
+ width: 80,
|
|
|
+ render: (v?: number) =>
|
|
|
+ v !== undefined && v !== null ? (GENDER_MAP[v] ?? "-") : "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "动态类型",
|
|
|
+ dataIndex: "type",
|
|
|
+ key: "type",
|
|
|
+ width: 120,
|
|
|
+ render: (v?: number) => renderTrendType(v),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "文本内容",
|
|
|
+ dataIndex: "textContent",
|
|
|
+ key: "textContent",
|
|
|
+ width: 220,
|
|
|
+ ellipsis: true,
|
|
|
+ render: (v?: string) =>
|
|
|
+ v ? (
|
|
|
+ <Tooltip title={v}>
|
|
|
+ <span>{v}</span>
|
|
|
+ </Tooltip>
|
|
|
+ ) : (
|
|
|
+ "-"
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "包含媒体",
|
|
|
+ dataIndex: "medias",
|
|
|
+ key: "medias",
|
|
|
+ width: 90,
|
|
|
+ render: (v?: unknown[]) => {
|
|
|
+ if (Array.isArray(v) && v.length > 0) {
|
|
|
+ return <Tag color="success">是</Tag>;
|
|
|
+ }
|
|
|
+ return <Tag>否</Tag>;
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "状态",
|
|
|
+ dataIndex: "status",
|
|
|
+ key: "status",
|
|
|
+ width: 110,
|
|
|
+ render: (v?: number) => renderStatusTag(v),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "审核备注",
|
|
|
+ dataIndex: "reviewReason",
|
|
|
+ key: "reviewReason",
|
|
|
+ width: 180,
|
|
|
+ ellipsis: true,
|
|
|
+ render: (v?: string) =>
|
|
|
+ v ? (
|
|
|
+ <Tooltip title={v}>
|
|
|
+ <span>{v}</span>
|
|
|
+ </Tooltip>
|
|
|
+ ) : (
|
|
|
+ "-"
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "审核时间",
|
|
|
+ dataIndex: "reviewTime",
|
|
|
+ key: "reviewTime",
|
|
|
+ width: 170,
|
|
|
+ render: (v?: number) => (v ? formatTimestamp(v) : "-"),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "创建时间",
|
|
|
+ dataIndex: "createdAt",
|
|
|
+ key: "createdAt",
|
|
|
+ width: 170,
|
|
|
+ render: (v?: number) => (v ? formatTimestamp(v) : "-"),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "操作",
|
|
|
+ key: "action",
|
|
|
+ width: 240,
|
|
|
+ fixed: "right",
|
|
|
+ render: (_, record) => (
|
|
|
+ <Space size="small">
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ icon={<EyeOutlined />}
|
|
|
+ onClick={() => handleViewDetail(record)}
|
|
|
+ >
|
|
|
+ 查看
|
|
|
+ </Button>
|
|
|
+ {record.status === 0 && (
|
|
|
+ <>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ icon={<CheckCircleOutlined />}
|
|
|
+ onClick={() => handleOpenReview(record, 1)}
|
|
|
+ >
|
|
|
+ 通过
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ danger
|
|
|
+ size="small"
|
|
|
+ icon={<CloseCircleOutlined />}
|
|
|
+ onClick={() => handleOpenReview(record, 2)}
|
|
|
+ >
|
|
|
+ 拒绝
|
|
|
+ </Button>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="p-6">
|
|
|
+ {/* Search form */}
|
|
|
+ <div className="bg-white p-4 rounded-lg shadow mb-4">
|
|
|
+ <Form form={searchForm} layout="inline" className="gap-x-2 gap-y-4">
|
|
|
+ <Form.Item label="关键字" name="searchKeyword">
|
|
|
+ <Input
|
|
|
+ placeholder="昵称/用户编号"
|
|
|
+ allowClear
|
|
|
+ style={{ width: 180 }}
|
|
|
+ />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="状态" name="status">
|
|
|
+ <Select placeholder="请选择状态" allowClear style={{ width: 140 }}>
|
|
|
+ <Select.Option value={0}>待审核</Select.Option>
|
|
|
+ <Select.Option value={1}>审核通过</Select.Option>
|
|
|
+ <Select.Option value={2}>审核不通过</Select.Option>
|
|
|
+ </Select>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="动态类型" name="type">
|
|
|
+ <Select placeholder="请选择类型" allowClear style={{ width: 150 }}>
|
|
|
+ <Select.Option value={0}>纯文本</Select.Option>
|
|
|
+ <Select.Option value={1}>图片</Select.Option>
|
|
|
+ <Select.Option value={2}>视频</Select.Option>
|
|
|
+ <Select.Option value={3}>图文混合</Select.Option>
|
|
|
+ <Select.Option value={4}>视频文本混合</Select.Option>
|
|
|
+ </Select>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="性别" name="gender">
|
|
|
+ <Select placeholder="请选择性别" allowClear style={{ width: 110 }}>
|
|
|
+ <Select.Option value={0}>未知</Select.Option>
|
|
|
+ <Select.Option value={1}>男</Select.Option>
|
|
|
+ <Select.Option value={2}>女</Select.Option>
|
|
|
+ </Select>
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="创建日期" name="dateRange">
|
|
|
+ <DatePicker.RangePicker style={{ width: 240 }} />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item style={{ marginLeft: "auto" }}>
|
|
|
+ <Space>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ icon={<SearchOutlined />}
|
|
|
+ onClick={handleSearch}
|
|
|
+ >
|
|
|
+ 搜索
|
|
|
+ </Button>
|
|
|
+ <Button icon={<UndoOutlined />} onClick={handleReset}>
|
|
|
+ 重置
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ icon={<ReloadOutlined />}
|
|
|
+ onClick={handleRefresh}
|
|
|
+ loading={loading}
|
|
|
+ >
|
|
|
+ 刷新
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* Table */}
|
|
|
+ <div className="bg-white p-4 rounded-lg shadow">
|
|
|
+ <Table
|
|
|
+ columns={columns}
|
|
|
+ dataSource={dataSource}
|
|
|
+ rowKey={(record) => record.id}
|
|
|
+ loading={loading}
|
|
|
+ pagination={{
|
|
|
+ current: queryParams.pageIndex,
|
|
|
+ pageSize: queryParams.pageSize,
|
|
|
+ total,
|
|
|
+ showSizeChanger: true,
|
|
|
+ showTotal: (t) => `共 ${t} 条`,
|
|
|
+ pageSizeOptions: ["10", "20", "30", "40", "50", "100", "200"],
|
|
|
+ }}
|
|
|
+ onChange={handleTableChange}
|
|
|
+ scroll={{ x: 1900 }}
|
|
|
+ size="small"
|
|
|
+ bordered
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* Detail modal */}
|
|
|
+ <Modal
|
|
|
+ title="动态详情"
|
|
|
+ open={detailModalVisible}
|
|
|
+ onCancel={() => setDetailModalVisible(false)}
|
|
|
+ footer={[
|
|
|
+ <Button key="close" onClick={() => setDetailModalVisible(false)}>
|
|
|
+ 关闭
|
|
|
+ </Button>,
|
|
|
+ ...(currentRecord?.status === 0
|
|
|
+ ? [
|
|
|
+ <Button
|
|
|
+ key="approve"
|
|
|
+ type="primary"
|
|
|
+ icon={<CheckCircleOutlined />}
|
|
|
+ onClick={() => {
|
|
|
+ if (!currentRecord) return;
|
|
|
+ setDetailModalVisible(false);
|
|
|
+ handleOpenReview(currentRecord, 1);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 通过
|
|
|
+ </Button>,
|
|
|
+ <Button
|
|
|
+ key="reject"
|
|
|
+ type="primary"
|
|
|
+ danger
|
|
|
+ icon={<CloseCircleOutlined />}
|
|
|
+ onClick={() => {
|
|
|
+ if (!currentRecord) return;
|
|
|
+ setDetailModalVisible(false);
|
|
|
+ handleOpenReview(currentRecord, 2);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 拒绝
|
|
|
+ </Button>,
|
|
|
+ ]
|
|
|
+ : []),
|
|
|
+ ]}
|
|
|
+ width={1000}
|
|
|
+ destroyOnHidden
|
|
|
+ >
|
|
|
+ {currentRecord && (
|
|
|
+ <div className="mt-4 space-y-4">
|
|
|
+ <Descriptions bordered column={2} size="small">
|
|
|
+ <Descriptions.Item label="状态" span={2}>
|
|
|
+ {renderStatusTag(currentRecord.status)}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="用户编号">
|
|
|
+ {currentRecord.userNo || "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="昵称">
|
|
|
+ {currentRecord.nickname || "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="头像">
|
|
|
+ {currentRecord.avatar ? (
|
|
|
+ <Image
|
|
|
+ src={currentRecord.avatar}
|
|
|
+ alt="avatar"
|
|
|
+ width={60}
|
|
|
+ height={60}
|
|
|
+ style={{ borderRadius: "50%", objectFit: "cover" }}
|
|
|
+ preview
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ "-"
|
|
|
+ )}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="性别">
|
|
|
+ {currentRecord.gender !== undefined
|
|
|
+ ? (GENDER_MAP[currentRecord.gender] ?? "-")
|
|
|
+ : "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="动态类型" span={2}>
|
|
|
+ {renderTrendType(currentRecord.type)}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="文本内容" span={2}>
|
|
|
+ {currentRecord.textContent || "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ {currentRecord.medias && currentRecord.medias.length > 0 && (
|
|
|
+ <Descriptions.Item label="媒体内容" span={2}>
|
|
|
+ <Image.PreviewGroup>
|
|
|
+ <Space wrap>
|
|
|
+ {currentRecord.medias.map((media, idx) => (
|
|
|
+ // biome-ignore lint/suspicious/noArrayIndexKey: stable index for media list
|
|
|
+ <div key={idx} className="space-y-1">
|
|
|
+ <div className="text-xs text-gray-500">
|
|
|
+ {media.type === 1 ? "视频" : "图片"}
|
|
|
+ </div>
|
|
|
+ {media.type === 1 ? (
|
|
|
+ // biome-ignore lint/a11y/useKeyWithClickEvents: video thumbnail is a visual-only interaction
|
|
|
+ // biome-ignore lint/a11y/noStaticElementInteractions: video thumbnail preview
|
|
|
+ <div
|
|
|
+ className="relative cursor-pointer"
|
|
|
+ style={{ width: 160, height: 110 }}
|
|
|
+ onClick={() =>
|
|
|
+ media.url && setVideoPreviewUrl(media.url)
|
|
|
+ }
|
|
|
+ >
|
|
|
+ {media.videoCover ? (
|
|
|
+ // biome-ignore lint/performance/noImgElement: antd Image breaks preview group context here
|
|
|
+ <img
|
|
|
+ src={media.videoCover}
|
|
|
+ alt="video cover"
|
|
|
+ style={{
|
|
|
+ width: 160,
|
|
|
+ height: 110,
|
|
|
+ objectFit: "cover",
|
|
|
+ borderRadius: 8,
|
|
|
+ display: "block",
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ width: 160,
|
|
|
+ height: 110,
|
|
|
+ background: "#1f1f1f",
|
|
|
+ borderRadius: 8,
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ position: "absolute",
|
|
|
+ inset: 0,
|
|
|
+ borderRadius: 8,
|
|
|
+ background: "rgba(0,0,0,0.35)",
|
|
|
+ display: "flex",
|
|
|
+ alignItems: "center",
|
|
|
+ justifyContent: "center",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <PlayCircleOutlined
|
|
|
+ style={{ fontSize: 36, color: "#fff" }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <Image
|
|
|
+ src={media.url}
|
|
|
+ alt={`media-${idx}`}
|
|
|
+ width={160}
|
|
|
+ height={110}
|
|
|
+ style={{ objectFit: "cover", borderRadius: 8 }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </Space>
|
|
|
+ </Image.PreviewGroup>
|
|
|
+ </Descriptions.Item>
|
|
|
+ )}
|
|
|
+ <Descriptions.Item label="审核备注">
|
|
|
+ {currentRecord.reviewReason || "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="审核时间">
|
|
|
+ {currentRecord.reviewTime
|
|
|
+ ? formatTimestamp(currentRecord.reviewTime)
|
|
|
+ : "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="创建时间">
|
|
|
+ {currentRecord.createdAt
|
|
|
+ ? formatTimestamp(currentRecord.createdAt)
|
|
|
+ : "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="更新时间">
|
|
|
+ {currentRecord.updatedAt
|
|
|
+ ? formatTimestamp(currentRecord.updatedAt)
|
|
|
+ : "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ </Descriptions>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </Modal>
|
|
|
+
|
|
|
+ {/* Review modal */}
|
|
|
+ <Modal
|
|
|
+ title="动态审核"
|
|
|
+ open={reviewModalVisible}
|
|
|
+ onOk={handleSubmitReview}
|
|
|
+ onCancel={handleCloseReviewModal}
|
|
|
+ confirmLoading={reviewLoading}
|
|
|
+ width={600}
|
|
|
+ destroyOnHidden
|
|
|
+ maskClosable={false}
|
|
|
+ >
|
|
|
+ <Form
|
|
|
+ form={reviewForm}
|
|
|
+ layout="vertical"
|
|
|
+ className="mt-4"
|
|
|
+ initialValues={{ status: 1 }}
|
|
|
+ >
|
|
|
+ {currentRecord && (
|
|
|
+ <div className="mb-4 p-3 bg-gray-50 rounded text-sm text-gray-700">
|
|
|
+ <div>
|
|
|
+ <span className="font-medium">用户:</span>
|
|
|
+ {currentRecord.nickname || "-"}({currentRecord.userNo || "-"})
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span className="font-medium">动态类型:</span>
|
|
|
+ {renderTrendType(currentRecord.type)}
|
|
|
+ </div>
|
|
|
+ {currentRecord.textContent && (
|
|
|
+ <div className="mt-1 line-clamp-2 text-gray-500">
|
|
|
+ {currentRecord.textContent}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ label="审核结果"
|
|
|
+ name="status"
|
|
|
+ rules={[{ required: true, message: "请选择审核结果" }]}
|
|
|
+ >
|
|
|
+ <Select placeholder="请选择审核结果">
|
|
|
+ <Select.Option value={1}>审核通过</Select.Option>
|
|
|
+ <Select.Option value={2}>审核不通过</Select.Option>
|
|
|
+ </Select>
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item shouldUpdate noStyle>
|
|
|
+ {({ getFieldValue }) => {
|
|
|
+ const status = getFieldValue("status") as number | undefined;
|
|
|
+ const required = status === 2;
|
|
|
+ return (
|
|
|
+ <Form.Item
|
|
|
+ label="审核备注"
|
|
|
+ name="reviewReason"
|
|
|
+ rules={
|
|
|
+ required
|
|
|
+ ? [{ required: true, message: "请输入拒绝原因/备注" }]
|
|
|
+ : undefined
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <Input.TextArea
|
|
|
+ rows={4}
|
|
|
+ placeholder={
|
|
|
+ required ? "请输入拒绝原因/备注" : "可选:请输入审核备注"
|
|
|
+ }
|
|
|
+ maxLength={500}
|
|
|
+ showCount
|
|
|
+ />
|
|
|
+ </Form.Item>
|
|
|
+ );
|
|
|
+ }}
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </Modal>
|
|
|
+
|
|
|
+ {/* Video preview modal */}
|
|
|
+ <Modal
|
|
|
+ title="视频预览"
|
|
|
+ open={!!videoPreviewUrl}
|
|
|
+ onCancel={() => setVideoPreviewUrl(null)}
|
|
|
+ footer={null}
|
|
|
+ width={720}
|
|
|
+ destroyOnHidden
|
|
|
+ centered
|
|
|
+ >
|
|
|
+ {videoPreviewUrl && (
|
|
|
+ // biome-ignore lint/a11y/useMediaCaption: admin review tool, captions not applicable
|
|
|
+ <video
|
|
|
+ src={videoPreviewUrl}
|
|
|
+ controls
|
|
|
+ autoPlay
|
|
|
+ style={{ width: "100%", maxHeight: 480, borderRadius: 8 }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </Modal>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default TrendApplyPage;
|