Files
boluo-app-main/src/pages/material-upload-video/index.tsx
2025-03-31 22:34:22 +08:00

205 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Button } from '@tarojs/components';
import Taro, { UploadTask, useDidHide, useLoad, useUnload } from '@tarojs/taro';
import { isEqual } from 'lodash-es';
import { useCallback, useEffect, useRef, useState } from 'react';
import MaterialVideoCard from '@/components/material-video-card';
import SafeBottomPadding from '@/components/safe-bottom-padding';
import { EventName, OpenSource, PageUrl } from '@/constants/app';
import { CollectEventName, ReportEventId } from '@/constants/event';
import { MaterialVideoInfo } from '@/types/material';
import { logWithPrefix } from '@/utils/common';
import { collectEvent, reportEvent } from '@/utils/event';
import { chooseMedia, postVideos, requestVideoList } from '@/utils/material';
import { getPageQuery, navigateBack, navigateTo } from '@/utils/route';
import Toast from '@/utils/toast';
import { uploadVideo } from '@/utils/video';
import './index.less';
// 限制 500M
const MAX_FILE_SIZE_LIMIT = 1024 * 1024 * 1000;
const PREFIX = 'page-material-upload-video';
const log = logWithPrefix(PREFIX);
const TEMP_DATA: MaterialVideoInfo = { url: '', coverUrl: '', title: '', type: 'image', isDefault: false };
export default function MaterialUploadVideo() {
const [source, setSource] = useState(OpenSource.None);
const [videoList, setVideoList] = useState<MaterialVideoInfo[]>([]);
const saveRef = useRef<(videos: MaterialVideoInfo[]) => Promise<void>>();
const lastSaveVideosRef = useRef<MaterialVideoInfo[]>([]);
const initVideoList = useCallback(async () => {
try {
const res = await requestVideoList();
lastSaveVideosRef.current = res;
setVideoList(res);
} catch (e) {
console.error(e);
Toast.error('加载失败请重试');
}
}, []);
const handleClickDelete = useCallback(
(video: MaterialVideoInfo) => {
log('handleClickDelete', video);
const newVideoList = videoList.filter(v => v.coverUrl !== video.coverUrl);
setVideoList(newVideoList);
},
[videoList]
);
const handleDefaultChange = useCallback(
(video: MaterialVideoInfo) => {
log('handleDefaultChange', video);
const newVideoList = videoList.map(v => ({ ...v, isDefault: v.coverUrl === video.coverUrl }));
setVideoList(newVideoList);
},
[videoList]
);
const handleTitleChange = useCallback(
(video: MaterialVideoInfo, newTitle: string) => {
// log('handleTitleChange', video, newTitle);
const newVideoList = [...videoList];
const index = newVideoList.findIndex(v => v.coverUrl === video.coverUrl);
if (index < 0) {
return;
}
newVideoList.splice(index, 1, { ...video, title: newTitle });
setVideoList(newVideoList);
},
[videoList]
);
const handleClickUpload = useCallback(async () => {
log('click upload');
let showLoading = false;
try {
reportEvent(ReportEventId.CLICK_UPLOAD_VIDEO);
const media = await chooseMedia();
if (!media) {
return;
}
const { type, tempFiles } = media;
log('upload result', type, tempFiles);
const tempFile = tempFiles[0];
if (!tempFile) {
throw new Error('tempFile is not exist');
}
if (tempFile.size > MAX_FILE_SIZE_LIMIT) {
Toast.info('视频超过1000m请更换', 3000);
collectEvent(CollectEventName.VIDEO_EXCEEDING_LIMITS);
return;
}
showLoading = true;
Taro.showLoading({ title: '准备上传' });
const onProgress: UploadTask.OnProgressUpdateCallback = res => {
log('上传视频进度', res.progress, '总长度', res.totalBytesExpectedToSend, '已上传的长度', res.totalBytesSent);
Taro.showLoading({ title: `上传${res.progress}%` });
};
const { url, coverUrl } = await uploadVideo(tempFile.tempFilePath, tempFile.fileType, onProgress);
const newVideo: MaterialVideoInfo = {
title: '',
isDefault: false,
url: url,
coverUrl: coverUrl,
type: tempFile.fileType === 'video' ? 'video' : 'image',
};
setVideoList([...videoList, newVideo]);
} catch (e) {
console.error('upload fail', e);
Toast.error('上传失败');
collectEvent(CollectEventName.UPLOAD_VIDEO_FAILED, e);
} finally {
showLoading && Taro.hideLoading();
}
}, [videoList]);
const handleClickSubmit = useCallback(async () => {
log('handleClickSubmit', videoList);
reportEvent(ReportEventId.CLICK_SAVE_VIDEOS);
if (videoList.length < 2) {
Toast.info('请至少上传 2 个录屏');
return;
}
try {
Taro.showLoading();
await saveRef.current?.(videoList);
Taro.eventCenter.trigger(EventName.CREATE_PROFILE);
if (source === OpenSource.None) {
navigateTo(PageUrl.MaterialCreateProfile);
} else {
navigateBack();
}
} catch (e) {
Toast.error('保存失败请重试');
collectEvent(CollectEventName.SAVE_VIDEO_LIST_FAILED, e);
} finally {
Taro.hideLoading();
}
}, [videoList, source]);
useEffect(() => {
saveRef.current = async (videos: MaterialVideoInfo[]) => {
if (!videos.length) {
log('长度为空不保存');
return;
}
if (isEqual(lastSaveVideosRef.current, videos)) {
log('没变化不保存');
return;
}
lastSaveVideosRef.current = videos;
await postVideos(videos);
};
}, []);
useLoad(() => {
const query = getPageQuery<{ source: OpenSource }>();
log('query', query);
const { source: openSource } = query;
openSource && setSource(openSource);
initVideoList();
});
useDidHide(() => {
log('didHide', videoList);
saveRef.current?.(videoList);
});
useUnload(() => {
log('unload', videoList);
saveRef.current?.(videoList);
});
return (
<div className={PREFIX}>
<div className={`${PREFIX}__header`}>
<div className={`${PREFIX}__header-title`}></div>
<div className={`${PREFIX}__header-tips`}></div>
</div>
<div className={`${PREFIX}__video-list`}>
{videoList.map(video => (
<MaterialVideoCard
key={video.coverUrl}
videoInfo={video}
onClickDelete={() => handleClickDelete(video)}
onClickSetDefault={() => handleDefaultChange(video)}
onTitleChange={(newTitle: string) => handleTitleChange(video, newTitle)}
/>
))}
{videoList.length < 6 && <MaterialVideoCard videoInfo={TEMP_DATA} onClickUpload={handleClickUpload} isTemp />}
</div>
<SafeBottomPadding />
<div className={`${PREFIX}__footer`}>
<Button className={`${PREFIX}__submit-btn`} onClick={handleClickSubmit}>
{source === OpenSource.None ? '下一步' : '保存'}
</Button>
<SafeBottomPadding />
</div>
</div>
);
}