blob: 7a1b63112315f30b19abde544e8a7a1635bafeac [file] [log] [blame]
lfmylbhf789e7172025-06-06 18:37:21 +08001'use client';
2
3import React, { useEffect, useState, useRef } from 'react';
4import { TabView, TabPanel } from 'primereact/tabview';
5import { Avatar } from 'primereact/avatar';
6import { Button } from 'primereact/button';
7import { Card } from 'primereact/card';
8import {Image} from "primereact/image";
lfmylbhf1783a092025-06-07 20:05:22 +08009// 发布资源
10import { Dialog } from 'primereact/dialog';
11import {InputText} from "primereact/inputtext";
12import {InputTextarea} from "primereact/inputtextarea";
13import {FileUpload} from "primereact/fileupload";
14// 资源分类
15import { RadioButton, RadioButtonChangeEvent } from "primereact/radiobutton";
16// 资源标签
17import { MultiSelect, MultiSelectChangeEvent } from 'primereact/multiselect';
lfmylbhf789e7172025-06-06 18:37:21 +080018// 浮动按钮
19import { SpeedDial } from 'primereact/speeddial';
20// 评分图标
21import { Fire } from '@icon-park/react';
22// 消息提醒
23import { Toast } from 'primereact/toast';
24// 页面跳转
25import { useRouter } from "next/navigation";
lfmylbhf789e7172025-06-06 18:37:21 +080026
27// 接口传输
28import axios from "axios";
29
30// 样式
31import './user.scss';
lfmylbhf1783a092025-06-07 20:05:22 +080032import {toNumber} from "lodash";
lfmylbhf789e7172025-06-06 18:37:21 +080033
34// 用户信息
35interface UserInfo {
36 userId: number;
37 username: string;
38 password: string;
39 avatar: string;
40 followerCount: number;// 粉丝数
41 subscriberCount: number;// 关注数
42 signature: string;// 个性签名
43 uploadAmount: number;
44 purchaseAmount: number;
45 credits: number;
46}
47
48// 用户数据
49interface UserData {
50 subscriberCount: number; // 关注数
51 uploadAmount: number; // 上传量(资源个数)
52 beDownloadedAmount: number; // 上传资源被下载量
53 seedPercentageList: number[]; // 上传资源类型百分比列表,按材质包、模组、整合包、地图的顺序返回
54}
55
lfmylbhf1783a092025-06-07 20:05:22 +080056// 用户发布过的资源
lfmylbhf789e7172025-06-06 18:37:21 +080057interface Resource {
58 resourceId: number;
59 resourceName: string;
60 resourcePicture: string;
61 resourceSummary: string; // 资源简介(一句话)
62 resourceDetail: string; // 资源介绍
63 uploadTime: string; // 上传时间
64 lastUpdateTime: string; // 最近更新时间
65 price: number;
66 downloads: number;
67 likes: number;
68 collections: number;
69 comments: number;
70 seeds: number; // 种子数
71 classify: string; // 资源分类(材质包:resourcePack,模组:mod,整合包:modPack ,地图:map
72}
73
lfmylbhf1783a092025-06-07 20:05:22 +080074// 用户发布过的资源列表
lfmylbhf789e7172025-06-06 18:37:21 +080075interface ResourceList {
76 records: Resource[];
77}
78
lfmylbhf1783a092025-06-07 20:05:22 +080079// 资源标签
80interface GameplayOption {
81 name: string;
82 code: number;
83}
84
85// 资源标签选项
86const gameplayOptions: GameplayOption[] = [
87 { name: '科技', code: 1 },
88 { name: '魔法', code: 2 },
89 { name: '建筑', code: 3 },
90 { name: '风景', code: 4 },
91 { name: '竞技', code: 5 },
92 { name: '生存', code: 6 },
93 { name: '冒险', code: 7 },
94 { name: '跑酷', code: 8 },
95 { name: '艺术', code: 9 },
96 { name: '剧情', code: 10 },
97 { name: '社交', code: 11 },
98 { name: '策略', code: 12 },
99 { name: '极限', code: 13 }
100];
101
lfmylbhf789e7172025-06-06 18:37:21 +0800102export default function UserPage() {
103 // 路由
104 const router = useRouter();
105 // 发布资源列表
106 const [resourceList, setResourceList] = useState<Resource[]>([]);
107 // 用户信息
108 const [userInfo, setUserInfo] = useState<UserInfo>();
109 // 用户数据
110 const [userData, setUserData] = useState<UserData>();
111 // 消息提醒
112 const toast = useRef<Toast>(null);
lfmylbhf1783a092025-06-07 20:05:22 +0800113 // 资源标签
114 const [selectedGameplay, setSelectedGameplay] = useState<GameplayOption[]>([]);
115 // 判断用户是否上传资源封面
116 const [isUploadPicture, setIsUploadPicture] = useState<boolean>(false);
lfmylbhf789e7172025-06-06 18:37:21 +0800117
118 useEffect(() => {
119 fetchUserInfo();
120 fetchUserData();
121 fetchResourceList();
122 }, []);
123
124 // 获取用户信息
125 const fetchUserInfo = async () => {
126 try {
127 const response = await axios.get<UserInfo>(process.env.PUBLIC_URL + `/user/info`, {
128 params: { userId: 22301010 }
129 });
130 console.log('获取用户信息:', response.data);
131 setUserInfo(response.data);
132 } catch (err) {
133 console.error('获取用户信息失败', err);
134 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取用户信息失败' });
135 }
136 };
137
138 // 获取用户数据
139 const fetchUserData = async () => {
140 try {
141 const response = await axios.get<UserData>(process.env.PUBLIC_URL + `/user/data`, {
142 params: { userId: 22301010 }
143 });
144 console.log('获取用户数据:', response.data);
145 setUserData(response.data);
146 } catch (err) {
147 console.error('获取用户数据失败', err);
148 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取用户数据失败' });
149 }
150 };
151
152 // 格式化数字显示 (3000 -> 3k)
153 const formatCount = (count?: number): string => {
154 if (count == null) return "0"; // 同时处理 undefined/null
155
156 const absCount = Math.abs(count); // 处理负数
157
158 const format = (num: number, suffix: string) => {
159 const fixed = num.toFixed(1);
160 return fixed.endsWith('.0')
161 ? `${Math.floor(num)}${suffix}`
162 : `${fixed}${suffix}`;
163 };
164
165 if (absCount >= 1e6) return format(count / 1e6, "m");
166 if (absCount >= 1e3) return format(count / 1e3, "k");
167 return count.toString();
168 };
169
170 // 获取发布资源
171 const fetchResourceList = async () => {
172 try {
173 const response = await axios.get<ResourceList>(process.env.PUBLIC_URL +`/user/upload`, {
174 params: { userId: 22301010, pageNumber: 1, rows: 3 }
175 });
176 console.log('获取发布资源列表:', response.data.records);
177 setResourceList(response.data.records);
178
179 // const imgUrl = `${process.env.NEXT_PUBLIC_NGINX_URL}/${resourceList[0].resourcePicture}`;
180 // console.log("Image URL:", imgUrl);
181 } catch (err) {
182 console.error('获取发布资源失败', err);
183 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取发布资源失败' });
184 }
185 };
186
187 // 浮动按钮的子模块
188 const actions = [
189 {
190 template: () => (
191 <Button label="管理资源" onClick={() => router.push(`/user/manage/resources/`)}/>
192 )
193 },
194 {
195 template: () => (
196 <Button label="已购资源" onClick={() => router.push(`/user/purchased-resources/`)}/>
197 )
198 },
199 {
200 template: () => (
lfmylbhf1783a092025-06-07 20:05:22 +0800201 <Button label="发布资源" onClick={() => setVisible(true)}/>
lfmylbhf789e7172025-06-06 18:37:21 +0800202 )
203 },
204 {
205 template: () => (
206 <Button label="编辑悬赏" onClick={() => router.push(`/user/manage/resources/`)}/>
207 )
208 }
209 ];
210
lfmylbhf1783a092025-06-07 20:05:22 +0800211 // 发布资源弹窗
212 const [visible, setVisible] = useState(false);
213 const [resourceFormData, setResourceFormData] = useState({
214 resource: {
215 resourceName: '',
216 resourcePicture: '',
217 resourceSummary: '',
218 resourceDetail: '',
219 uploadTime: '',
220 lastUpdateTime: '',
221 price: '',
222 classify: '',
223 },
224 gameplayList: [''],
225 completeRewardId: null,
226 userId: 0,
227 });
228 const [ingredient, setIngredient] = useState<string>('');
229
230
231 // 图片上传消息通知
232 const onUpload = () => {
233 setIsUploadPicture(true);
234 toast.current?.show({ severity: 'info', summary: 'Success', detail: 'File Uploaded' });
235 };
236
237 // 上传资源接口
238 const handleSubmit = async () => {
239 try {
240 // 规定用户必须输入的内容
241 if (resourceFormData.resource.resourceName == '') {
242 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源名称' });
243 return;
244 }
245 if (resourceFormData.resource.resourceSummary == '') {
246 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源简介' });
247 return;
248 }
249 if (resourceFormData.resource.price == '') {
250 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源价格' });
251 return;
252 }
253 if (resourceFormData.resource.classify == '') {
254 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源分类' });
255 return;
256 }
257 if (
258 resourceFormData.gameplayList.length === 1 &&
259 resourceFormData.gameplayList[0] === ''
260 ) {
261 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源标签' });
262 return;
263 }
264 if (!isUploadPicture) {
265 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源封面' });
266 return;
267 }
268
269 const currentDate = new Date().toISOString().split('T')[0];
270 const postData = {
271 resource: {
272 resourceName: resourceFormData.resource.resourceName,
273 resourcePicture: resourceFormData.resource.resourcePicture,
274 resourceSummary: resourceFormData.resource.resourceSummary,
275 resourceDetail: resourceFormData.resource.resourceDetail,
276 uploadTime: currentDate,
277 lastUpdateTime: currentDate,
278 price: toNumber(resourceFormData.resource.price),
279 classify: resourceFormData.resource.classify,
280 },
281 gameplayList: resourceFormData.gameplayList,
282 completeRewardId: null,
283 userId: 22301010, // 记得用户登录状态获取
284 };
285 // 发送POST请求
286 const response = await axios.post(process.env.PUBLIC_URL + '/resource', postData);
287 console.log("上传资源的信息:", postData);
288
289 if (response.status === 200) {
290 toast.current?.show({ severity: 'success', summary: 'Success', detail: '资源上传成功' });
291 // 上传成功
292 setVisible(false);
293 // 重置表单
294 setResourceFormData({
295 resource: {
296 resourceName: '',
297 resourcePicture: '',
298 resourceSummary: '',
299 resourceDetail: '',
300 uploadTime: '',
301 lastUpdateTime: '',
302 price: '',
303 classify: '',
304 },
305 gameplayList: [],
306 completeRewardId: null,
307 userId: 0,
308 });
309 // 重置资源分类
310 setIngredient("");
311 // 重置资源标签
312 setSelectedGameplay([]);
313 // 重置上传封面状态
314 setIsUploadPicture(false);
315 // 可以刷新资源列表
316 // fetchResourceList();
317 }
318 } catch (error) {
319 console.error('资源上传失败:', error);
320 toast.current?.show({ severity: 'error', summary: 'error', detail: '资源上传失败' });
321 }
322 };
323
lfmylbhf789e7172025-06-06 18:37:21 +0800324
325
326
327 return (
328 <div className="user-container">
lfmylbhf1783a092025-06-07 20:05:22 +0800329 <Toast ref={toast}></Toast>
lfmylbhf789e7172025-06-06 18:37:21 +0800330 {/*个人信息*/}
331 <div className="user-profile-card">
332 <Avatar
333 image={`${process.env.NEXT_PUBLIC_NGINX_URL}/users/${userInfo?.avatar}`}
334 className="user-avatar"
335 shape="circle"
336 />
337 <div className="user-info">
338 <div className="user-detail-info">
339 <div className="name-container">
340 <h2 className="name">{userInfo?.username}</h2>
341 <span className="signature">{userInfo?.signature}</span>
342 </div>
343
344 <div className="stats-container">
345 <div className="stats">
346 <span className="stats-label">粉丝:</span>
347 <span className="stats-value">{userInfo?.followerCount}</span>
348 </div>
349 <div className="stats">
350 <span className="stats-label">累计上传量:</span>
351 <span className="stats-value">{formatCount(userData?.uploadAmount)}</span>
352 </div>
353 <div className="stats">
354 <span className="stats-label">关注:</span>
355 <span className="stats-value">{userInfo?.subscriberCount}</span>
356 </div>
357 <div className="stats">
358 <span className="stats-label">累计被下载量:</span>
359 <span className="stats-value">{formatCount(userData?.beDownloadedAmount)}</span>
360 </div>
361 </div>
362 </div>
363
364 <Button label="关注" className="action-button"/>
365 </div>
366 </div>
367
368 {/*个人内容*/}
369 <TabView>
370 <TabPanel header="主页">
371 {/*推荐资源*/}
372 <div className="homepage-item">
373 <div className="section-header">
374 <h1>推荐资源</h1>
375 <Button
376 label="显示更多"
377 link
378 onClick={() => router.push('/resource/recommend/模组')}
379 />
380 </div>
381 <div className="resource-grid">
382 {/*{mods.map((mod) => (*/}
383 {/* <Card key={mod.resourceId} className="resource-card" onClick={() => router.push(`/resource/resource-detail/${mod.resourceId}`)}>*/}
384 {/* <Image*/}
385 {/* src={process.env.NEXT_PUBLIC_NGINX_URL + mod.resourcePicture}*/}
386 {/* alt={mod.resourceName}*/}
387 {/* width="368"*/}
388 {/* height="200"*/}
389 {/* />*/}
390 {/* <div className="card-content">*/}
391 {/* <h3>{mod.resourceName}</h3>*/}
392 {/* <div className="view-count">*/}
393 {/* <Fire theme="outline" size="16" fill="#FF8D1A" />*/}
394 {/* <span>{mod.likes}</span>*/}
395 {/* </div>*/}
396 {/* </div>*/}
397 {/* </Card>*/}
398 {/*))}*/}
399 </div>
400 </div>
401
402 {/*发布资源*/}
403 <div className="homepage-item">
404 <div className="section-header">
405 <h1>发布资源</h1>
406 <Button
407 label="显示更多"
408 link
409 onClick={() => router.push('/user/manage/resources/')}
410 />
411 </div>
412 <div className="resource-grid">
413 {resourceList.map((resourceList) => (
414 <Card key={resourceList.resourceId} className="resource-card" onClick={() => router.push(`/resource/resource-detail/${resourceList.resourceId}`)}>
415 <Image
416 src={process.env.NEXT_PUBLIC_NGINX_URL + resourceList.resourcePicture}
417 alt={resourceList.resourceName}
418 width="368"
419 height="200"
420 />
421 <div className="card-content">
422 <h3>{resourceList.resourceName}</h3>
423 <div className="view-count">
424 <Fire theme="outline" size="16" fill="#FF8D1A" />
425 <span>{resourceList.likes}</span>
426 </div>
427 </div>
428 </Card>
429 ))}
430 </div>
431 </div>
432
433 {/*发布帖子*/}
434 <div className="homepage-item">
435 <div className="section-header">
436 <h1>发布帖子</h1>
437 <Button
438 label="显示更多"
439 link
440 onClick={() => router.push('/resource/recommend/模组')}
441 />
442 </div>
443 <div className="resource-grid">
444
445 {/*{mods.map((mod) => (*/}
446 {/* <Card key={mod.resourceId} className="resource-card" onClick={() => router.push(`/resource/resource-detail/${mod.resourceId}`)}>*/}
447 {/* <Image*/}
448 {/* src={process.env.NEXT_PUBLIC_NGINX_URL + mod.resourcePicture}*/}
449 {/* alt={mod.resourceName}*/}
450 {/* width="368"*/}
451 {/* height="200"*/}
452 {/* />*/}
453 {/* <div className="card-content">*/}
454 {/* <h3>{mod.resourceName}</h3>*/}
455 {/* <div className="view-count">*/}
456 {/* <Fire theme="outline" size="16" fill="#FF8D1A" />*/}
457 {/* <span>{mod.likes}</span>*/}
458 {/* </div>*/}
459 {/* </div>*/}
460 {/* </Card>*/}
461 {/*))}*/}
462 </div>
463 </div>
464 </TabPanel>
465
466 <TabPanel header="发布">
467
468 </TabPanel>
469
470 <TabPanel header="帖子">
471
472 </TabPanel>
473
474 <TabPanel header="收藏">
475
476 </TabPanel>
477
478 <TabPanel header="数据">
479
480 </TabPanel>
481
482 <TabPanel header="悬赏">
483
484 </TabPanel>
485 </TabView>
486
487 {/*浮动按钮*/}
488 <div className="card">
489 <SpeedDial
490 model={actions}
491 direction="up"
492 style={{ position: 'fixed', bottom: '2rem', right: '2rem' }}
493 showIcon="pi pi-plus"
494 hideIcon="pi pi-times"
495 buttonClassName="custom-speeddial-button"
496 />
497 </div>
498
lfmylbhf1783a092025-06-07 20:05:22 +0800499 {/*发布资源弹窗*/}
500 <Dialog
501 header="发布资源"
502 visible={visible}
503 onHide={() => setVisible(false)}
504 className="publish-dialog"
505 modal
506 footer={
507 <div className="dialog-footer">
508 <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus />
509 <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
510 </div>
511 }
512 >
513 <div className="publish-form">
514 <div className="form-field">
515 <div className="form-field-header">
516 <span className="form-field-sign">*</span>
517 <label htmlFor="name">资源名称</label>
518 </div>
519 <InputText
520 id="name"
521 value={resourceFormData.resource.resourceName}
522 onChange={(e) => setResourceFormData(prev => ({
523 ...prev, // 复制顶层所有属性
524 resource: {
525 ...prev.resource, // 复制resource对象的所有属性
526 resourceName: e.target.value // 只更新resourceName
527 }
528 }))}
529 placeholder="请输入资源名称"
530 className="w-full"
531 required
532 />
533 </div>
534 <div className="form-field">
535 <div className="form-field-header">
536 <span className="form-field-sign">*</span>
537 <label htmlFor="summary">资源简介</label>
538 </div>
539 <InputText
540 id="summary"
541 value={resourceFormData.resource.resourceSummary}
542 onChange={(e) => setResourceFormData(prev => ({
543 ...prev, // 复制顶层所有属性
544 resource: {
545 ...prev.resource, // 复制resource对象的所有属性
546 resourceSummary: e.target.value
547 }
548 }))}
549 placeholder="请输入资源简介(一句话)"
550 className="w-full"
551 required
552 />
553 </div>
554 <div className="form-field">
555 <div className="form-field-header">
556 <label htmlFor="detail">资源介绍</label>
557 </div>
558 <InputTextarea
559 id="detail"
560 value={resourceFormData.resource.resourceDetail}
561 onChange={(e) => setResourceFormData(prev => ({
562 ...prev, // 复制顶层所有属性
563 resource: {
564 ...prev.resource, // 复制resource对象的所有属性
565 resourceDetail: e.target.value
566 }
567 }))}
568 rows={5}
569 placeholder="请输入资源介绍"
570 className="w-full"
571 required
572 />
573 </div>
574 <div className="form-field">
575 <div className="form-field-header">
576 <span className="form-field-sign">*</span>
577 <label htmlFor="price">价格</label>
578 </div>
579 <InputText
580 id="price"
581 value={resourceFormData.resource.price}
582 onChange={(e) => setResourceFormData(prev => ({
583 ...prev, // 复制顶层所有属性
584 resource: {
585 ...prev.resource, // 复制resource对象的所有属性
586 price: e.target.value
587 }
588 }))}
589 placeholder="请输入资源价格"
590 className="w-full"
591 />
592 </div>
593 <div className="form-field">
594 <div className="form-field-header">
595 <span className="form-field-sign">*</span>
596 <label htmlFor="classify">资源分类(请选择一项)</label>
597 </div>
598 <div className="form-field-classify">
599 <div className="flex align-items-center">
600 <RadioButton
601 inputId="ingredient1"
602 name="pizza"
603 value="resourcePack"
604 onChange={(e: RadioButtonChangeEvent) => {
605 setResourceFormData(prev => ({
606 ...prev, // 复制顶层所有属性
607 resource: {
608 ...prev.resource, // 复制resource对象的所有属性
609 classify: e.target.value
610 }
611 }));
612 setIngredient(e.value);
613 console.log(ingredient);
614 // console.log(resourceFormData.resource.classify);
615 }}
616 checked={ingredient === 'resourcePack'}
617 />
618 <label htmlFor="ingredient1" className="ml-2">材质包</label>
619 </div>
620 <div className="flex align-items-center">
621 <RadioButton
622 inputId="ingredient2"
623 name="pizza"
624 value="modPack"
625 onChange={(e: RadioButtonChangeEvent) => {
626 setResourceFormData(prev => ({
627 ...prev, // 复制顶层所有属性
628 resource: {
629 ...prev.resource, // 复制resource对象的所有属性
630 classify: e.target.value
631 }
632 }));
633 setIngredient(e.value);
634 }}
635 // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
636 checked={ingredient === 'modPack'}
637 />
638 <label htmlFor="ingredient2" className="ml-2">整合包</label>
639 </div>
640 <div className="flex align-items-center">
641 <RadioButton
642 inputId="ingredient3"
643 name="pizza"
644 value="mod"
645 onChange={(e: RadioButtonChangeEvent) => {
646 setResourceFormData(prev => ({
647 ...prev, // 复制顶层所有属性
648 resource: {
649 ...prev.resource, // 复制resource对象的所有属性
650 classify: e.target.value
651 }
652 }));
653 setIngredient(e.value);
654 }}
655 // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
656 checked={ingredient === 'mod'}
657 />
658 <label htmlFor="ingredient3" className="ml-2">模组</label>
659 </div>
660 <div className="flex align-items-center">
661 <RadioButton
662 inputId="ingredient4"
663 name="pizza"
664 value="map"
665 onChange={(e: RadioButtonChangeEvent) => {
666 setResourceFormData(prev => ({
667 ...prev, // 复制顶层所有属性
668 resource: {
669 ...prev.resource, // 复制resource对象的所有属性
670 classify: e.target.value
671 }
672 }));
673 setIngredient(e.value);
674 }}
675 // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
676 checked={ingredient === 'map'}
677 />
678 <label htmlFor="ingredient4" className="ml-2">地图</label>
679 </div>
680 </div>
681 </div>
682 <div className="form-field">
683 <div className="form-field-header">
684 <span className="form-field-sign">*</span>
685 <label htmlFor="gameplayList">资源标签</label>
686 </div>
687 <MultiSelect
688 value={selectedGameplay}
689 onChange={(e: MultiSelectChangeEvent) => {
690 const selectedOptions = e.value as GameplayOption[];
691 // 提取选中项的 name 属性组成字符串数组
692 const selectedNames = selectedOptions.map(item => item.name);
693
694 setResourceFormData(prev => ({
695 ...prev,
696 gameplayList: selectedNames
697 }));
698 setSelectedGameplay(selectedOptions);
699 }}
700 options={gameplayOptions}
701 display="chip"
702 optionLabel="name"
703 placeholder="请选择资源标签"
704 // maxSelectedLabels={3}
705 className="w-full md:w-20rem"
706 />
707 </div>
708 <div className="form-field">
709 <div className="form-field-header">
710 <span className="form-field-sign">*</span>
711 <label>封面图片</label>
712 </div>
713 <FileUpload
714 mode="basic"
715 name="resource-image"
716 url={process.env.PUBLIC_URL +"/file"} // 与后端交互的URL
717 accept="image/*"
718 maxFileSize={10000000000}
719 chooseLabel="选择资源封面"
720 className="w-full"
721 onUpload={onUpload}
722 />
723 </div>
724 </div>
725 </Dialog>
726
lfmylbhf789e7172025-06-06 18:37:21 +0800727 </div>
728 );
729};
730