返回 AiToEarn
mineInfo.tsx
1 /*
2 * @Author: nevin
3 * @Date: 2025-03-01 19:27:35
4 * @LastEditTime: 2025-03-24 14:10:00
5 * @LastEditors: nevin
6 * @Description: 用户任务信息
7 */
8 import {
9 Button,
10 Modal,
11 Form,
12 Input,
13 Upload,
14 message,
15 Tag,
16 Divider,
17 Space,
18 Tooltip,
19 Alert,
20 } from 'antd';
21 import {
22 Task,
23 TaskProduct,
24 TaskPromotion,
25 TaskTypeName,
26 TaskVideo,
27 } from '@@/types/task';
28 import { forwardRef, useImperativeHandle, useState } from 'react';
29 import { taskApi } from '@/api/task';
30 import { UserTask, UserTaskStatus } from '@/api/types/task';
31 import styles from './mineInfo.module.scss';
32 import {
33 LinkOutlined,
34 QrcodeOutlined,
35 ClockCircleOutlined,
36 FileTextOutlined,
37 DollarOutlined,
38 InfoCircleOutlined,
39 PictureOutlined,
40 } from '@ant-design/icons';
41 import dayjs from 'dayjs';
42
43 const FILE_BASE_URL = import.meta.env.VITE_APP_FILE_HOST;
44 const VITE_APP_URL = import.meta.env.VITE_APP_URL;
45
46 export interface MineTaskInfoRef {
47 init: (
48 inData: UserTask<Task<TaskProduct | TaskPromotion | TaskVideo>>,
49 ) => Promise<void>;
50 }
51
52 // 可以提交的任务状态
53 const SUBMITTABLE_STATUSES = [UserTaskStatus.DODING, UserTaskStatus.REJECTED];
54
55 const Com = forwardRef<MineTaskInfoRef>((props: any, ref) => {
56 const [isModalOpen, setIsModalOpen] = useState(false);
57 const [mineTaskInfo, setMineTaskInfo] = useState<UserTask<
58 Task<TaskProduct | TaskPromotion | TaskVideo>
59 > | null>();
60 const [form] = Form.useForm();
61 const [submitting, setSubmitting] = useState(false);
62 const [fileList, setFileList] = useState<any[]>([]);
63
64 // 从props中获取刷新函数
65 const { onTaskSubmitted } = props;
66
67 async function init(
68 inData: UserTask<Task<TaskProduct | TaskPromotion | TaskVideo>>,
69 ) {
70 setMineTaskInfo(inData);
71 setIsModalOpen(true);
72
73 // 重置表单
74 form.resetFields();
75 setFileList([]);
76
77 // 如果有已提交的数据,填充表单
78 if (
79 inData.submissionUrl ||
80 inData.qrCodeScanResult ||
81 inData.screenshotUrls?.length
82 ) {
83 form.setFieldsValue({
84 submissionUrl: inData.submissionUrl || '',
85 qrCodeScanResult: inData.qrCodeScanResult || '',
86 });
87
88 if (inData.screenshotUrls?.length) {
89 const initialFileList = inData.screenshotUrls.map((url, index) => ({
90 uid: `-${index}`,
91 name: `截图${index + 1}.jpg`,
92 status: 'done',
93 url: url.startsWith('http') ? url : `${FILE_BASE_URL}/${url}`,
94 }));
95 setFileList(initialFileList);
96 }
97 }
98 }
99
100 useImperativeHandle(ref, () => ({
101 init: init,
102 }));
103
104 async function taskDone() {
105 if (!mineTaskInfo) return;
106
107 try {
108 await form.validateFields();
109 const values = form.getFieldsValue();
110
111 setSubmitting(true);
112
113 // 处理截图文件
114 const screenshotUrls = fileList
115 .filter((file) => file.status === 'done')
116 .map((file) => {
117 console.log('file', file);
118 return file.response?.data.name;
119 });
120
121 const doneInfo = {
122 submissionUrl: values.submissionUrl,
123 qrCodeScanResult: values.qrCodeScanResult,
124 screenshotUrls,
125 };
126
127 const res = await taskApi.taskDone(mineTaskInfo.id, doneInfo);
128
129 setMineTaskInfo({
130 ...mineTaskInfo,
131 status: res.status, // 更新为待审核
132 ...doneInfo, // 更新提交的信息
133 });
134
135 message.success('任务提交成功!');
136 setIsModalOpen(false);
137
138 // 调用父组件传递的刷新函数
139 if (onTaskSubmitted && typeof onTaskSubmitted === 'function') {
140 onTaskSubmitted();
141 }
142 } catch (error) {
143 console.error('提交任务失败', error);
144 message.error('提交任务失败,请检查表单并重试');
145 } finally {
146 setSubmitting(false);
147 }
148 }
149
150 const handleCancel = () => {
151 setIsModalOpen(false);
152 };
153
154 // 格式化日期
155 const formatDate = (dateString?: string) => {
156 if (!dateString) return '暂无日期';
157 return dayjs(dateString).format('YYYY/MM/DD HH:mm');
158 };
159
160 // 判断任务是否可提交
161 const canSubmitTask = () => {
162 if (!mineTaskInfo) return false;
163
164 // 只有在进行中或已拒绝状态才允许提交
165 return SUBMITTABLE_STATUSES.includes(mineTaskInfo.status as UserTaskStatus);
166 };
167
168 // 处理上传图片变化
169 const handleUploadChange = ({ fileList }: any) => {
170 setFileList(fileList);
171 };
172
173 // 上传前检查
174 const beforeUpload = (file: any) => {
175 const isImage = file.type.startsWith('image/');
176 if (!isImage) {
177 message.error('只能上传图片文件!');
178 }
179
180 const isLt5M = file.size / 1024 / 1024 < 5;
181 if (!isLt5M) {
182 message.error('图片大小不能超过5MB!');
183 }
184
185 return isImage && isLt5M;
186 };
187
188 return (
189 <Modal
190 title={null}
191 open={isModalOpen}
192 onCancel={handleCancel}
193 footer={null}
194 width={800}
195 destroyOnClose
196 className={styles.taskInfoModal}
197 >
198 {mineTaskInfo ? (
199 <div className={styles.taskInfoContainer}>
200 <div className={styles.taskInfoHeader}>
201 <h2 className={styles.taskTitle}>
202 {mineTaskInfo.taskId?.title || '未知任务'}
203 <span className={styles.taskId}>
204 订单号: {(mineTaskInfo as any)._id}
205 </span>
206 </h2>
207 <Tag
208 color={
209 mineTaskInfo.status === UserTaskStatus.DODING
210 ? 'processing'
211 : mineTaskInfo.status === UserTaskStatus.PENDING
212 ? 'warning'
213 : mineTaskInfo.status === UserTaskStatus.APPROVED
214 ? 'success'
215 : mineTaskInfo.status === UserTaskStatus.REJECTED
216 ? 'error'
217 : 'default'
218 }
219 className={styles.statusTag}
220 >
221 {mineTaskInfo.status === UserTaskStatus.DODING
222 ? '进行中'
223 : mineTaskInfo.status === UserTaskStatus.PENDING
224 ? '待审核'
225 : mineTaskInfo.status === UserTaskStatus.APPROVED
226 ? '已通过'
227 : mineTaskInfo.status === UserTaskStatus.REJECTED
228 ? '已拒绝'
229 : '未知状态'}
230 </Tag>
231 </div>
232
233 <div className={styles.taskInfoContent}>
234 <div className={styles.taskDetails}>
235 <div className={styles.taskDetail}>
236 <ClockCircleOutlined className={styles.detailIcon} />
237 <span className={styles.detailLabel}>接单时间:</span>
238 <span className={styles.detailValue}>
239 {formatDate(mineTaskInfo.createTime)}
240 </span>
241 </div>
242
243 <div className={styles.taskDetail}>
244 <FileTextOutlined className={styles.detailIcon} />
245 <span className={styles.detailLabel}>任务类型:</span>
246 <span className={styles.detailValue}>
247 {TaskTypeName.get(mineTaskInfo.taskId?.type) || '未知类型'}
248 </span>
249 </div>
250
251 <div className={styles.taskDetail}>
252 <DollarOutlined className={styles.detailIcon} />
253 <span className={styles.detailLabel}>任务奖励:</span>
254 <span className={styles.detailValue}>
255 ¥{mineTaskInfo.taskId?.reward || 5}
256 </span>
257 </div>
258 </div>
259
260 {mineTaskInfo.status === UserTaskStatus.REJECTED && mineTaskInfo.verificationNote && (
261 <div className={styles.rejectionNote}>
262 <Alert
263 type="error"
264 showIcon
265 message={
266 <div>
267 <div className={styles.rejectionTitle}>拒绝原因:</div>
268 <div className={styles.rejectionContent}>{mineTaskInfo.verificationNote}</div>
269 </div>
270 }
271 />
272 </div>
273 )}
274
275 <Divider />
276
277 <div className={styles.taskDescription}>
278 <h3 className={styles.sectionTitle}>任务描述</h3>
279 <div
280 className={styles.descriptionContent}
281 dangerouslySetInnerHTML={{
282 __html: mineTaskInfo.taskId?.description || '暂无描述',
283 }}
284 />
285 </div>
286
287 <Divider />
288
289 <div className={styles.submissionSection}>
290 <h3 className={styles.sectionTitle}>
291 提交任务
292 {!canSubmitTask() && (
293 <Tooltip title="当前任务状态不允许提交">
294 <InfoCircleOutlined className={styles.infoIcon} />
295 </Tooltip>
296 )}
297 </h3>
298
299 <Form
300 form={form}
301 layout="vertical"
302 disabled={!canSubmitTask() || submitting}
303 >
304 <Form.Item
305 name="submissionUrl"
306 label="提交链接"
307 rules={[{ required: true, message: '请输入提交链接' }]}
308 >
309 <Input
310 placeholder="请输入任务完成后的链接"
311 prefix={<LinkOutlined />}
312 />
313 </Form.Item>
314
315 <Form.Item
316 name="qrCodeScanResult"
317 label="二维码扫描结果"
318 rules={[{ required: false, message: '请输入二维码扫描结果' }]}
319 >
320 <Input
321 placeholder="请输入二维码扫描结果(如有)"
322 prefix={<QrcodeOutlined />}
323 />
324 </Form.Item>
325
326 <Form.Item
327 name="screenshotUrls"
328 label="任务截图"
329 rules={[{ required: true, message: '请上传任务截图' }]}
330 >
331 <Upload
332 listType="picture-card"
333 fileList={fileList}
334 onChange={handleUploadChange}
335 beforeUpload={beforeUpload}
336 action={VITE_APP_URL + '/oss/upload/permanent'}
337 multiple
338 maxCount={5}
339 >
340 {fileList.length >= 5 ? null : (
341 <div>
342 <PictureOutlined />
343 <div style={{ marginTop: 8 }}>上传截图</div>
344 </div>
345 )}
346 </Upload>
347 </Form.Item>
348
349 <div className={styles.uploadTips}>
350 <p>提示:</p>
351 <ul>
352 <li>请上传清晰的任务完成截图,最多5张</li>
353 <li>图片格式支持JPG、PNG,单张大小不超过5MB</li>
354 <li>截图应包含任务完成的关键信息,如发布内容、评论等</li>
355 </ul>
356 </div>
357 </Form>
358 </div>
359 </div>
360
361 <div className={styles.taskInfoFooter}>
362 <Space>
363 <Button onClick={handleCancel}>关闭</Button>
364 {canSubmitTask() && (
365 <Button type="primary" onClick={taskDone} loading={submitting}>
366 提交任务
367 </Button>
368 )}
369 </Space>
370 </div>
371 </div>
372 ) : (
373 <div className={styles.noDataContainer}>暂无任务信息</div>
374 )}
375 </Modal>
376 );
377 });
378
379 export default Com;
380
380 lines Plain Text