Files
huangguo_dhz/src/components/UploadVideo/index.vue
T
谢宇宁andClaude Opus 4.8 c957b45bad 初始化黄果短剧 H5 客户端
对接后端 hgmjapi.mfcg.me:
- 接口解密 key 更新为 resKey360
- 登录 sysType 使用原生 getDevType(后端已放开 pc/android)
- 修正榜单接口路径 /vid/rangking → /vid/ranking,方法改为 GET

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-08 19:41:43 +07:00

348 lines
9.8 KiB
Vue

<template>
<div class="upload-video" @click.stop>
<van-uploader accept="video/*" :max-count="1" :after-read="afterVideo" v-if="!state.startUploadVideo">
<div class="upload-box">
<!-- <SvgIcon name="upload_add" height="15px" width="15px"></SvgIcon>-->
<img class="icon" src="@/assets/images/png/upload_add.png" alt="" />
<span>添加视频</span>
</div>
</van-uploader>
<!-- 上传中样式 -->
<div v-if="state.startUploadVideo" class="uploading">
<video class="previewVideo" id="video" style="object-fit: cover"></video>
<div class="video-mask"></div>
<van-circle
v-model:current-rate="state.currentRate"
:rate="state.uploadStatue.percent"
:speed="100"
color="#00b95b"
:text="state.currentRate + '%'"
size="50px"
class="circle"
/>
<p :class="state.uploadStatue.statusText == '上传成功' ? 'green' : 'F5164E'">
{{ state.uploadStatue.statusText }}
</p>
<div
class="close"
v-if="state.uploadStatue.statusText == '上传成功' || state.uploadStatue.statusText == '上传失败'"
@click="uploadDelete"
></div>
</div>
</div>
</template>
<script lang="ts" setup>
import SparkMD5 from 'spark-md5';
import { Buffer } from 'buffer';
import { reactive, nextTick } from 'vue';
import CommunityApi from '@/service/community';
const state = reactive({
requestAll: <any>[], //总请求
requestNum: 0, //正在请求的数量
requestMaxNum: 6, //同时最大请求数量
chunk_size: 1024 * 1024 * 2, //分片大小
startUploadVideo: false, //视频是否上传中
currentRate: 0,
uploadStatue: {
statusText: '上传中,请勿退出',
percent: 0, //当前上传文件执行的百分比
},
totalProgress: <any>{}, //总进度
sendVideoSucNum: 0, //分片上传视频,成功收到的返回
videoHeight: 0,
uploadData: {
newsType: 'SP', //上传类型 COVER-图集 SP-视频
tags: [], //视频标签数组
playTime: 0, //视频时长
cover: '', //封面
coverThumb: '', //封面小图
seriesCover: [], // 图集图片地址
via: '', //视频来源
coins: 0, //观看金币
size: 0, //文件大小
mimeType: '', //文件类型
actor: '', //演员名字
sourceURL: '', //资源url 为SP时 必传
sourceID: '', //上��视频成功后 返回���ID 为SP时必传
filename: '', //文件名
resolution: '', //分辨率
ratio: 1, //视频比例
md5: '', //文件摘要
freeTime: 0, //免费观影时长
location: {
city: '',
}, //地理位置信息
isActivity: false, //是否参加大赛
},
newUpload: {
sourceID: '', //上��视频成功后 返回���ID
md5: '', //文件摘要
type: 'vid',
src: '',
height: '',
width: '',
},
});
const emit = defineEmits(['uploadVideoSuccess']);
const fileReaderPromisify = (file: any) => {
return new Promise((resolve, reject) => {
let reader = new FileReader();
let Spark = new SparkMD5.ArrayBuffer();
reader.readAsArrayBuffer(file);
reader.onload = function (e: any) {
Spark.append(e.target.result);
let md5 = Spark.end();
resolve(md5);
};
reader.onerror = (err) => {
reject(err);
};
});
};
// 上传视频
const afterVideo = async (e: any) => {
state.startUploadVideo = true;
let file = e.file;
let fileMd5: any = await fileReaderPromisify(file);
let file_chunks = getFileChunks(file);
let length = file_chunks.length;
for (let index in file_chunks) {
let chunk = file_chunks[Number(index)];
let opts = {
pos: Number(index) + 1,
totalPos: length,
data: Buffer.from(await chunk.arrayBuffer(), 'utf8').toString('base64'),
id: fileMd5,
};
state.uploadData.md5 = fileMd5;
state.newUpload.md5 = fileMd5;
state.requestAll.push(opts);
}
getVideoPlayerInfo(file); //获取视频宽高
requestConcurrency();
};
//限制请求数量
const requestConcurrency = () => {
while (state.requestAll.length > 0 && state.requestNum <= state.requestMaxNum && state.requestNum >= 0) {
let opts = state.requestAll.shift();
upload(opts);
}
};
// 将视频分片
const getFileChunks = (file: any) => {
let { size } = file;
let total_chunks = Math.ceil(size / state.chunk_size);
let file_chunks = [];
if (size > state.chunk_size) {
for (let i = 0; i < total_chunks; i++) {
let start = i * state.chunk_size;
let end = (i + 1) * state.chunk_size;
let chunk = file.slice(start, end);
state.totalProgress[i] = 0;
file_chunks.push(chunk);
}
} else {
file_chunks.push(file);
state.totalProgress[0] = 0;
}
return file_chunks;
};
// 开始上传
const upload = async (options: any) => {
try {
state.requestNum++;
let callback = (processCallback: any) => {
let percent = parseInt(((processCallback.loaded / processCallback.total) * 100).toString());
state.totalProgress[options.pos - 1] = percent.toFixed(2);
let strArr = Object.values(state.totalProgress);
let numberArr = strArr.map(Number);
state.uploadStatue.percent = Number(average(numberArr).toFixed(1));
};
let ret: any = await CommunityApi.uploadVideo(options, callback);
state.sendVideoSucNum++;
if (state.uploadStatue.percent == 100) {
// 所有分片请求发送完毕,等待返回
state.uploadStatue.statusText = '处理中,请勿退出';
}
if (state.uploadStatue.percent == 100 && state.sendVideoSucNum == options.totalPos) {
state.uploadStatue.statusText = '上传成功';
state.uploadData.sourceURL = ret.videoUri;
state.uploadData.sourceID = ret.id;
state.newUpload.sourceID = ret.id;
state.newUpload.src = ret.videoUri;
emit('uploadVideoSuccess', state.uploadData);
return;
}
} catch (e) {
state.currentRate = 0;
state.uploadStatue.percent = 0;
state.uploadStatue.statusText = '上传失败';
state.sendVideoSucNum = 0;
} finally {
state.requestNum--;
requestConcurrency();
}
};
// 获取视频信息
const getVideoPlayerInfo = (file: any) => {
state.uploadData.size = file.size;
state.uploadData.filename = file.name;
state.uploadData.mimeType = file.type;
// state.newUpload.type = file.type;
nextTick(() => {
const videoElement: any = document.getElementById('video');
videoElement.src = URL.createObjectURL(file);
videoElement.addEventListener('loadedmetadata', () => {
state.uploadData.playTime = parseInt(videoElement.duration);
state.videoHeight = videoElement.videoHeight;
state.uploadData.ratio = Number((videoElement.videoWidth / videoElement.videoHeight).toFixed(2));
state.uploadData.resolution = videoElement.videoWidth + 'x' + videoElement.videoHeight;
state.newUpload.height = videoElement.videoHeight;
state.newUpload.width = videoElement.videoWidth;
});
});
};
// 还原默认数据
const reductionData = () => {
state.uploadData = {
newsType: 'SP', //上传类型 COVER-图集 SP-视频
tags: [], //视频标签数组
playTime: 0, //视频时长
cover: '', //封面
coverThumb: '', //封面小图
seriesCover: [], // 图集图片地址
via: '', //视频来源
coins: 0, //观看金币
size: 0, //文件大小
mimeType: '', //文件类型
actor: '', //演员名字
sourceURL: '', //资源url 为SP时 必传
sourceID: '', //上��视频成功后 返回���ID 为SP时必传
filename: '', //文件名
resolution: '', //分辨率
ratio: 1, //视频比例
md5: '', //文件摘要
freeTime: 0, //免费观影时长
location: {
city: '',
}, //地理位置信息
isActivity: false, //是否参加大赛
};
state.newUpload = {
sourceID: '',
md5: '', //文件摘要
type: 'vid',
src: '',
height: '',
width: '',
};
};
// 删除视频
const uploadDelete = () => {
state.newUpload.src = 'vid';
state.uploadData.sourceURL = '';
state.uploadStatue.statusText = '上传中,请勿退出';
state.uploadStatue.percent = 0;
state.currentRate = 0;
state.sendVideoSucNum = 0;
state.startUploadVideo = false;
};
// 平均数
const average = (nums: any) => {
return nums.reduce((a: any, b: any) => a + b) / nums.length;
};
defineExpose({
reductionData,
uploadDelete,
});
</script>
<style lang="scss" scoped>
.upload-video {
margin: 0 3px 0 0;
.upload-box {
width: 111px;
height: 111px;
border-radius: 6px;
border: dashed 1px rgb(154, 154, 154);
background: #262626;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
position: relative;
box-sizing: border-box;
span {
font-size: 12px;
color: #9a9a9a;
margin-top: 9px;
}
.icon {
width: 30px;
height: 30px;
}
}
.upload-box span {
font-size: 10px;
color: rgb(129, 129, 129);
margin-top: 5.5px;
}
.uploadCover {
padding-left: 0.16rem;
}
/* 上传中 */
.uploading {
width: 111px;
height: 111px;
border-radius: 6px;
color: #fff;
background: #fff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
font-size: 10px;
font-weight: 400;
}
.previewVideo {
width: 111px;
height: 111px;
border-radius: 6px;
position: absolute;
}
.video-mask {
width: 111px;
height: 111px;
border-radius: 6px;
position: absolute;
background: rgba(0, 0, 0, 0.6);
}
.circle {
margin-bottom: 5px;
}
.uploading .close {
position: absolute;
top: 0;
right: 0;
width: 20px;
height: 20px;
background: url('../../assets/images/png/community_close.png') center center no-repeat;
background-size: 100%;
}
:deep(.van-circle__text) {
color: #fff;
font-size: 12px;
}
}
</style>