feat: first commit

This commit is contained in:
eleanor.mao
2025-03-31 22:34:22 +08:00
commit d25187c9c8
390 changed files with 57031 additions and 0 deletions

View File

@ -0,0 +1,204 @@
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>
);
}