|
|
@@ -0,0 +1,538 @@
|
|
|
+"use client";
|
|
|
+
|
|
|
+import {
|
|
|
+ CheckCircleOutlined,
|
|
|
+ CloseCircleOutlined,
|
|
|
+ EyeOutlined,
|
|
|
+ ReloadOutlined,
|
|
|
+ SearchOutlined,
|
|
|
+ UndoOutlined,
|
|
|
+} from "@ant-design/icons";
|
|
|
+import {
|
|
|
+ App,
|
|
|
+ Button,
|
|
|
+ DatePicker,
|
|
|
+ Descriptions,
|
|
|
+ Form,
|
|
|
+ Input,
|
|
|
+ Modal,
|
|
|
+ Select,
|
|
|
+ Space,
|
|
|
+ Table,
|
|
|
+ Tag,
|
|
|
+} from "antd";
|
|
|
+import type { ColumnsType, TablePaginationConfig } from "antd/es/table";
|
|
|
+import type React from "react";
|
|
|
+import { useEffect, useState } from "react";
|
|
|
+import {
|
|
|
+ getPlaymateWelcomePage,
|
|
|
+ reviewPlaymateWelcome,
|
|
|
+} from "@/services/playmateWelcomeApply";
|
|
|
+import type {
|
|
|
+ PagerPlaymateWelcomeAdminDTO,
|
|
|
+ PlaymateWelcomeAdminDTO,
|
|
|
+ PlaymateWelcomeAdminQuery,
|
|
|
+} from "@/types/api/playmateWelcomeApply";
|
|
|
+import { formatTimestamp } from "@/utils/date";
|
|
|
+
|
|
|
+const PlaymateWelcomeApplyPage: React.FC = () => {
|
|
|
+ const { message } = App.useApp();
|
|
|
+ const [searchForm] = Form.useForm();
|
|
|
+ const [approvalForm] = Form.useForm();
|
|
|
+
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
+ const [dataSource, setDataSource] = useState<PlaymateWelcomeAdminDTO[]>([]);
|
|
|
+ const [total, setTotal] = useState(0);
|
|
|
+ const [queryParams, setQueryParams] = useState<PlaymateWelcomeAdminQuery>({
|
|
|
+ pageSize: 20,
|
|
|
+ pageIndex: 1,
|
|
|
+ });
|
|
|
+
|
|
|
+ const [detailModalVisible, setDetailModalVisible] = useState(false);
|
|
|
+ const [currentRecord, setCurrentRecord] =
|
|
|
+ useState<PlaymateWelcomeAdminDTO | null>(null);
|
|
|
+
|
|
|
+ const [approvalModalVisible, setApprovalModalVisible] = useState(false);
|
|
|
+ const [approvalLoading, setApprovalLoading] = useState(false);
|
|
|
+
|
|
|
+ const loadPageData = async () => {
|
|
|
+ setLoading(true);
|
|
|
+ try {
|
|
|
+ const response: PagerPlaymateWelcomeAdminDTO =
|
|
|
+ await getPlaymateWelcomePage(queryParams);
|
|
|
+ setDataSource(response.items || []);
|
|
|
+ setTotal(response.total || 0);
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to load playmate welcome 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 renderTypeTag = (type?: number) => {
|
|
|
+ if (type === 0) return <Tag>文本</Tag>;
|
|
|
+ if (type === 1) return <Tag color="blue">语音</Tag>;
|
|
|
+ return <Tag>-</Tag>;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSearch = () => {
|
|
|
+ const values = searchForm.getFieldsValue();
|
|
|
+ setQueryParams({
|
|
|
+ ...queryParams,
|
|
|
+ pageIndex: 1,
|
|
|
+ searchKeyword: values.searchKeyword?.trim() || 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),
|
|
|
+ startDay: values.dateRange?.[0]?.format("YYYY-MM-DD") || undefined,
|
|
|
+ endDay: values.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: PlaymateWelcomeAdminDTO) => {
|
|
|
+ setCurrentRecord(record);
|
|
|
+ setDetailModalVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleOpenApproval = (
|
|
|
+ record: PlaymateWelcomeAdminDTO,
|
|
|
+ status: 1 | 2,
|
|
|
+ ) => {
|
|
|
+ setCurrentRecord(record);
|
|
|
+ approvalForm.resetFields();
|
|
|
+ approvalForm.setFieldsValue({
|
|
|
+ status,
|
|
|
+ reviewReason: "",
|
|
|
+ });
|
|
|
+ setApprovalModalVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleCloseApprovalModal = () => {
|
|
|
+ setApprovalModalVisible(false);
|
|
|
+ approvalForm.resetFields();
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSubmitApproval = async () => {
|
|
|
+ if (!currentRecord?.id) {
|
|
|
+ message.error("缺少当前审核记录");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const values = await approvalForm.validateFields();
|
|
|
+ setApprovalLoading(true);
|
|
|
+
|
|
|
+ await reviewPlaymateWelcome({
|
|
|
+ id: currentRecord.id,
|
|
|
+ status: values.status,
|
|
|
+ reviewReason: values.reviewReason || undefined,
|
|
|
+ });
|
|
|
+
|
|
|
+ message.success("审核提交成功");
|
|
|
+ handleCloseApprovalModal();
|
|
|
+ loadPageData();
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to submit playmate welcome approval:", error);
|
|
|
+ if (error instanceof Error) {
|
|
|
+ message.error(`审核提交失败:${error.message}`);
|
|
|
+ } else {
|
|
|
+ message.error("审核提交失败");
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ setApprovalLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const columns: ColumnsType<PlaymateWelcomeAdminDTO> = [
|
|
|
+ {
|
|
|
+ title: "用户编号",
|
|
|
+ dataIndex: "userNo",
|
|
|
+ key: "userNo",
|
|
|
+ width: 140,
|
|
|
+ fixed: "left",
|
|
|
+ render: (v) => v || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "昵称",
|
|
|
+ dataIndex: "nickname",
|
|
|
+ key: "nickname",
|
|
|
+ width: 140,
|
|
|
+ render: (v) => v || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "类型",
|
|
|
+ dataIndex: "type",
|
|
|
+ key: "type",
|
|
|
+ width: 90,
|
|
|
+ render: (v?: number) => renderTypeTag(v),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "欢迎语内容",
|
|
|
+ key: "content",
|
|
|
+ width: 280,
|
|
|
+ render: (_: unknown, record: PlaymateWelcomeAdminDTO) => {
|
|
|
+ if (record.type === 1) {
|
|
|
+ return record.voiceUrl ? (
|
|
|
+ <audio controls src={record.voiceUrl} style={{ width: "100%" }}>
|
|
|
+ <track kind="captions" />
|
|
|
+ 您的浏览器不支持音频播放
|
|
|
+ </audio>
|
|
|
+ ) : (
|
|
|
+ <span className="text-gray-400">-</span>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ const text = record.textContent ?? "";
|
|
|
+ return text ? (
|
|
|
+ <span className="line-clamp-2" title={text}>
|
|
|
+ {text}
|
|
|
+ </span>
|
|
|
+ ) : (
|
|
|
+ "-"
|
|
|
+ );
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 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 || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "创建时间",
|
|
|
+ dataIndex: "createdAt",
|
|
|
+ key: "createdAt",
|
|
|
+ width: 170,
|
|
|
+ render: (v?: number) => (v ? formatTimestamp(v) : "-"),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "操作",
|
|
|
+ key: "action",
|
|
|
+ width: 220,
|
|
|
+ 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={() => handleOpenApproval(record, 1)}
|
|
|
+ >
|
|
|
+ 通过
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ danger
|
|
|
+ size="small"
|
|
|
+ icon={<CloseCircleOutlined />}
|
|
|
+ onClick={() => handleOpenApproval(record, 2)}
|
|
|
+ >
|
|
|
+ 拒绝
|
|
|
+ </Button>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="p-6">
|
|
|
+ <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="type">
|
|
|
+ <Select placeholder="请选择类型" allowClear style={{ width: 120 }}>
|
|
|
+ <Select.Option value={0}>文本</Select.Option>
|
|
|
+ <Select.Option value={1}>语音</Select.Option>
|
|
|
+ </Select>
|
|
|
+ </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="dateRange">
|
|
|
+ <DatePicker.RangePicker />
|
|
|
+ </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>
|
|
|
+
|
|
|
+ <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: 1400 }}
|
|
|
+ size="small"
|
|
|
+ bordered
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <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);
|
|
|
+ handleOpenApproval(currentRecord, 1);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 通过
|
|
|
+ </Button>,
|
|
|
+ <Button
|
|
|
+ key="reject"
|
|
|
+ type="primary"
|
|
|
+ danger
|
|
|
+ icon={<CloseCircleOutlined />}
|
|
|
+ onClick={() => {
|
|
|
+ if (!currentRecord) return;
|
|
|
+ setDetailModalVisible(false);
|
|
|
+ handleOpenApproval(currentRecord, 2);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 拒绝
|
|
|
+ </Button>,
|
|
|
+ ]
|
|
|
+ : []),
|
|
|
+ ]}
|
|
|
+ width={700}
|
|
|
+ destroyOnHidden
|
|
|
+ >
|
|
|
+ {currentRecord && (
|
|
|
+ <div className="mt-4 space-y-4">
|
|
|
+ <Descriptions bordered column={1} size="small">
|
|
|
+ <Descriptions.Item label="状态">
|
|
|
+ {renderStatusTag(currentRecord.status)}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="类型">
|
|
|
+ {renderTypeTag(currentRecord.type)}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="用户编号">
|
|
|
+ {currentRecord.userNo || "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="昵称">
|
|
|
+ {currentRecord.nickname || "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="创建时间">
|
|
|
+ {currentRecord.createdAt
|
|
|
+ ? formatTimestamp(currentRecord.createdAt)
|
|
|
+ : "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="审核原因">
|
|
|
+ {currentRecord.reviewReason || "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ {currentRecord.type === 0 && (
|
|
|
+ <Descriptions.Item label="文本内容">
|
|
|
+ {currentRecord.textContent || "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ )}
|
|
|
+ {currentRecord.type === 1 && (
|
|
|
+ <>
|
|
|
+ <Descriptions.Item label="语音时长">
|
|
|
+ {currentRecord.voiceDuration != null
|
|
|
+ ? `${currentRecord.voiceDuration}秒`
|
|
|
+ : "-"}
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="语音">
|
|
|
+ {currentRecord.voiceUrl ? (
|
|
|
+ <audio
|
|
|
+ controls
|
|
|
+ src={currentRecord.voiceUrl}
|
|
|
+ style={{ width: "100%" }}
|
|
|
+ >
|
|
|
+ <track kind="captions" />
|
|
|
+ 您的浏览器不支持音频播放
|
|
|
+ </audio>
|
|
|
+ ) : (
|
|
|
+ "-"
|
|
|
+ )}
|
|
|
+ </Descriptions.Item>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </Descriptions>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </Modal>
|
|
|
+
|
|
|
+ <Modal
|
|
|
+ title="欢迎语审核"
|
|
|
+ open={approvalModalVisible}
|
|
|
+ onOk={handleSubmitApproval}
|
|
|
+ onCancel={handleCloseApprovalModal}
|
|
|
+ confirmLoading={approvalLoading}
|
|
|
+ width={520}
|
|
|
+ destroyOnHidden
|
|
|
+ maskClosable={false}
|
|
|
+ >
|
|
|
+ <Form
|
|
|
+ form={approvalForm}
|
|
|
+ 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>
|
|
|
+ {renderTypeTag(currentRecord.type)}
|
|
|
+ </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>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default PlaymateWelcomeApplyPage;
|