11.21提交

This commit is contained in:
zoujiandong
2025-11-21 17:49:39 +08:00
parent 6b37f0899d
commit 91726b87c5
18 changed files with 2483 additions and 119 deletions
+898
View File
@@ -0,0 +1,898 @@
<template>
<view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
title="视频缓存"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eeeeee"
>
<template #right>
<view class="nav-actions">
<view class="nav-edit" @click="toggleEdit">
<text>{{ isEditMode ? "取消" : "编辑" }}</text>
</view>
</view>
</template>
</uni-nav-bar>
</view>
<view class="download-video-page">
<!-- 标签页 -->
<view class="tabs-container">
<view
class="tab-item"
:class="{ active: activeTab === 'completed' }"
@click="switchTab('completed')"
>
已完成
</view>
<view class="tab-divider"></view>
<view
class="tab-item"
:class="{ active: activeTab === 'downloading' }"
@click="switchTab('downloading')"
>
缓存中
</view>
</view>
<!-- 内容列表 -->
<scroll-view class="content-scroll" scroll-y>
<view v-if="currentList.length === 0" class="empty-state">
<text>暂无数据</text>
</view>
<view
v-for="(item, index) in currentList"
:key="item.id || index"
class="video-item"
@click="handleItemClick(item)"
>
<!-- 编辑模式下的多选框 -->
<view
v-if="isEditMode"
class="checkbox-wrapper"
@click.stop="toggleSelectItem(item)"
>
<view
class="checkbox"
:class="{ checked: isItemSelected(item) }"
>
<uni-icons
v-if="isItemSelected(item)"
type="checkmarkempty"
size="16"
color="#ffffff"
></uni-icons>
</view>
</view>
<!-- 缩略图 -->
<image
class="video-thumbnail"
:src="docUrl + item.imgpath"
mode="aspectFill"
/>
<!-- 内容信息 -->
<view class="video-info">
<view class="video-title">{{ item.name || "视频标题" }}</view>
<view class="video-author">{{ item.author || "作者" }}</view>
<!-- 时长和大小 -->
<view class="video-meta" v-if="item.status === 'completed'">
<uni-icons
type="clock"
size="12"
color="#999999"
class="meta-icon"
></uni-icons>
<text class="meta-text"><text v-if="item.duration">{{ formatDuration(item.duration) }}</text></text>
<text class="meta-size">{{ formatSize(item.size) }}</text>
</view>
<view class="video-meta video-status" v-if="item.status == 'downloading' || item.status == 'paused'">
<view class="status-icon">
<image :src="item.status == 'paused' ? playImg : pauseImg" mode="aspectFill"></image>
<text>{{ item.status == 'paused' ? '继续' : '暂停' }}</text>
</view>
<view class="box">
<!-- <view class="video-progress-bar">
<view class="video-progress-bar-inner" :style="{ width: item.progress + '%' }"></view>
</view> -->
<!-- <text>{{ item.progress + '%' }}</text> -->
<text>{{ formatSize(item.downloadSize) }} / {{ formatSize(item.totalSize) }}</text>
</view>
</view>
</view>
<!-- 编辑模式下的删除按钮 -->
<!-- <view
v-if="isEditMode"
class="delete-btn"
@click.stop="deleteItem(item, index)"
>
<text>删除</text>
</view> -->
</view>
</scroll-view>
<!-- 底部存储信息 -->
<view class="bottom-box">
<view class="storage-info">
<text
>手机存储:总空间{{ storageInfo.total }}G/剩余{{
storageInfo.available
}}G可用</text
>
</view>
<view class="footer-btn" v-if="isEditMode">
<view class="footer-btn-item select" @click="clearAll">
<text v-if="!isAllSelect">全选</text>
<text v-else>取消全选</text>
</view>
<view class="bar"></view>
<view class="footer-btn-item del" @click="delTask">
删除<text v-if="selectedItems.length > 0">({{ selectedItems.length }})</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, nextTick } from "vue";
import downloadStore from "@/store/downloadStoreVideo.js";
import { onLoad, onShow, onUnload } from "@dcloudio/uni-app";
import playImg from "@/static/down_true.png";
import pauseImg from "@/static/down_false.png";
import navTo from "@/utils/navTo";
import docUrl from "@/utils/docUrl";
const downloadTasks = ref([]);
const unsubscribe = ref(null);
const isAllSelect = ref(false);
const selectedItems = ref([]);
// 响应式数据
const activeTab = ref("completed"); // completed: 已完成, downloading: 缓存中
const isEditMode = ref(false);
const storageInfo = ref({
total: "461.31",
available: "312.92",
});
const delTask=()=>{
if(selectedItems.value.length === 0){
uni.showToast({
title: "请选择要删除的缓存",
icon: "none",
});
return;
}
uni.showModal({
title: "提示",
content: "确定要删除选中的缓存吗?",
success: (res) => {
if (res.confirm) {
// 从后往前删除,避免索引变化问题
const idsToDelete = [...selectedItems.value];
idsToDelete.forEach(id => {
const taskIndex = downloadTasks.value.findIndex(t => t.id === id);
if (taskIndex !== -1) {
removeTask(taskIndex);
}
});
// 清空选择
selectedItems.value = [];
isAllSelect.value = false;
}
}
});
}
const clearAll=()=>{
if(!isAllSelect.value){
// 全选当前列表的所有项
selectedItems.value = currentList.value.map(item => item.id);
isAllSelect.value = true;
}else{
// 取消全选
selectedItems.value = [];
isAllSelect.value = false;
}
}
// 判断项是否被选中
const isItemSelected = (item) => {
return selectedItems.value.includes(item.id);
}
// 切换项的选中状态
const toggleSelectItem = (item) => {
const index = selectedItems.value.indexOf(item.id);
if (index > -1) {
selectedItems.value.splice(index, 1);
} else {
selectedItems.value.push(item.id);
}
// 更新全选状态
isAllSelect.value = selectedItems.value.length === currentList.value.length && currentList.value.length > 0;
}
// 计算当前显示的列表
const currentList = computed(() => {
let list = [];
if (activeTab.value === "completed") {
list = downloadTasks.value.filter((task) => task.status === "completed");
} else {
list = downloadTasks.value.filter(
(task) => task.status === "downloading" || task.status === "paused"
);
}
// 当列表变化时,更新全选状态
nextTick(() => {
if (isAllSelect.value && selectedItems.value.length !== list.length) {
isAllSelect.value = false;
}
});
return list;
});
// 方法
const goBack = () => {
uni.navigateBack({
fail() {
uni.redirectTo({
url: "/pages/index/index",
});
},
});
};
const switchTab = (tab) => {
activeTab.value = tab;
isEditMode.value = false;
// 切换标签时清空选择
selectedItems.value = [];
isAllSelect.value = false;
};
const toggleEdit = () => {
isEditMode.value = !isEditMode.value;
// 退出编辑模式时清空选择
if (!isEditMode.value) {
selectedItems.value = [];
isAllSelect.value = false;
}
};
const handleItemClick = (item) => {
if (isEditMode.value) {
// 编辑模式下,点击项切换选中状态
toggleSelectItem(item);
return;
}
// 跳转到视频详情或播放页面
if (item.status === "completed") {
uni.navigateTo({
url: `/pages_app/videoDetail/videoDetail?id=${item.id}&from=download`,
});
}else if(item.status === "downloading"){
let index=downloadTasks.value.findIndex(t => t.id === item.id);
if(index !== -1){
pauseTask(index);
}
}else if(item.status === "paused"){
let index=downloadTasks.value.findIndex(t => t.id === item.id);
if(index !== -1){
resumeTask(index);
}
}
};
const deleteItem = (item, index) => {
uni.showModal({
title: "提示",
content: "确定要删除这个缓存吗?",
success: (res) => {
if (res.confirm) {
// 从store中删除
const taskIndex = downloadTasks.value.findIndex((t) => t.id === item.id);
if (taskIndex !== -1) {
removeTask(taskIndex);
}
}
},
});
};
const formatDuration = (duration) => {
if (!duration) return "00:00:00";
// 如果duration是秒数
if (typeof duration === "number") {
const hours = Math.floor(duration / 3600);
const minutes = Math.floor((duration % 3600) / 60);
const seconds = duration % 60;
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(
2,
"0"
)}:${String(seconds).padStart(2, "0")}`;
}
// 如果已经是格式化字符串
return duration;
};
const formatSize = (size) => {
if (!size) return "0M";
// 如果size是字节数
const mb = (Number(size) / 1024 / 1024).toFixed(2);
return `${mb}M`;
// 如果已经是格式化字符串
return size;
};
onLoad(() => {
// 从store同步任务列表
syncTasksFromStore();
// 监听store变化
unsubscribe.value = downloadStore.addListener((tasks) => {
downloadTasks.value = tasks;
});
console.log(11111);
console.log(downloadTasks.value);
// 恢复正在下载的任务
resumeDownloadingTasks();
}),
onShow(() => {
// 页面显示时同步最新任务列表
syncTasksFromStore();
// 检查并恢复下载任务
resumeDownloadingTasks();
}),
onUnload(() => {
// 页面 卸载时取消监听
if (unsubscribe.value) {
unsubscribe.value();
}
});
const getStorageInfo = () => {
// 获取手机存储信息
// #ifdef APP-PLUS
if (uni.getSystemInfoSync().platform !== 'android') {
console.log('此方法仅适用于Android平台');
return;
}
// 1. 导入所需的Android类
const Environment = plus.android.importClass("android.os.Environment");
const StatFs = plus.android.importClass("android.os.StatFs");
try {
// 2. 获取内部存储的数据目录(File对象)
const dataDirectory = Environment.getDataDirectory(); // 这是一个Java File对象
// 3. 关键修正:使用 plus.android.invoke 调用 File 对象的 getPath 方法
const dataDirectoryPath = plus.android.invoke(dataDirectory, "getPath");
// 现在 dataDirectoryPath 是一个字符串,表示路径
// 4. 创建 StatFs 对象,传入路径字符串
const statFs = new StatFs(dataDirectoryPath);
// 5. 使用 invoke 调用 StatFs 的方法获取存储块信息
const blockSize = plus.android.invoke(statFs, "getBlockSize");
const availableBlocks = plus.android.invoke(statFs, "getAvailableBlocks");
const totalBlocks = plus.android.invoke(statFs, "getBlockCount");
// 6. 计算可用空间和总空间(单位:字节)
const availableSizeInBytes = blockSize * availableBlocks;
const totalSizeInBytes = blockSize * totalBlocks;
// 7. 转换为常用单位
const availableSizeInGB = (availableSizeInBytes / (1024 * 1024 * 1024)).toFixed(2);
const availableSizeInMB = (availableSizeInBytes / (1024 * 1024)).toFixed(2);
const totalSizeInGB = (totalSizeInBytes / (1024 * 1024 * 1024)).toFixed(2);
// console.log(`可用存储空间:${availableSizeInBytes} 字节`);
// console.log(`可用存储空间:${availableSizeInMB} MB`);
// console.log(`可用存储空间:${availableSizeInGB} GB`);
// 可以根据需要返回不同单位的值
storageInfo.value = {
total: totalSizeInGB,
available: availableSizeInGB,
};
} catch (error) {
console.error('获取存储信息时发生错误:', error);
return null;
}
// #endif
};
const syncTasksFromStore=()=> {
downloadTasks.value = downloadStore.getTasks();
};
// 添加下载任务
const addDownloadTask = (item) => {
// 使用store添加任务
const taskIndex = downloadStore.addTask(item);
// 同步任务列表
syncTasksFromStore();
// 开始下载
startDownload(taskIndex);
};
// 开始下载
const startDownload = (index) => {
const taskItem = downloadStore.getTask(index);
if (!taskItem) return;
const task = uni.downloadFile({
url: taskItem.url,
success: (res) => {
if (res.statusCode === 200) {
downloadStore.updateTask(index, {
status: "completed",
filePath: res.tempFilePath,
});
uni.showToast({
title: "下载成功",
icon: "success",
});
console.log(res);
uni.saveFile({
tempFilePath: res.tempFilePath,
success: function (res) {
uni.getSavedFileInfo({
filePath: res.savedFilePath,
success: function (res) {
downloadStore.updateTask(index, {
status: "completed",
realyPath: res.savedFilePath,
size: res.size,
});
},
});
},
});
} else {
downloadStore.updateTask(index, {
status: "failed",
});
uni.showToast({
title: "下载失败",
icon: "none",
});
}
},
fail: (err) => {
downloadStore.updateTask(index, {
status: "failed",
});
console.error("下载失败:", err);
uni.showToast({
title: "下载失败: " + (err.errMsg || "未知错误"),
icon: "none",
duration: 2000,
});
},
});
// 监听下载进度
task.onProgressUpdate((update) => {
const currentTask = downloadStore.getTask(index);
if (currentTask && currentTask.status === "downloading") {
console.log(update.progress);
downloadStore.updateTask(index, {
progress: update.progress,
downloadSize: update.totalBytesWritten,
totalSize: update.totalBytesExpectedToWrite,
});
}
});
// 保存task对象到store(用于暂停/继续)
downloadStore.updateTask(index, {
task: task,
});
};
// 暂停任务
const pauseTask = (index) => {
const taskItem = downloadStore.getTask(index);
if (taskItem && taskItem.task) {
taskItem.task.abort();
downloadStore.updateTask(index, {
status: "paused",
task: null,
});
uni.showToast({
title: "已暂停",
icon: "none",
});
}
};
// 继续任务
const resumeTask = (index) => {
downloadStore.updateTask(index, {
status: "downloading",
progress: 0, // 重新开始(uni.downloadFile不支持断点续传)
});
startDownload(index);
};
// 删除任务
const removeTask = (index) => {
downloadStore.removeTask(index);
syncTasksFromStore();
};
// 打开文件
const openFile = (filePath) => {
// #ifdef APP-PLUS
plus.runtime.openFile(filePath);
// #endif
// #ifndef APP-PLUS
uni.showToast({
title: "请在APP中打开",
icon: "none",
});
// #endif
};
// 获取文件名
const getFileName = (url) => {
if (!url) return "未知文件";
try {
const urlObj = new URL(url);
const pathname = urlObj.pathname;
const fileName = pathname.split("/").pop();
return fileName || "下载文件";
} catch (e) {
// 如果不是完整URL,尝试从路径中提取
const parts = url.split("/");
return parts[parts.length - 1] || "下载文件";
}
};
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
downloading: "下载中",
paused: "已暂停",
completed: "已完成",
failed: "下载失败",
};
return statusMap[status] || "未知";
};
// 恢复正在下载的任务
const resumeDownloadingTasks = () => {
// 使用 nextTick 确保在页面渲染后再恢复下载任务
nextTick(() => {
downloadStore.resumeDownloadingTasks((index) => {
// 重新开始下载(uni.downloadFile不支持断点续传,所以从0开始)
console.log("恢复下载任务:", downloadStore.getTask(index)?.url);
startDownload(index);
});
});
};
onMounted(() => {
getStorageInfo();
});
</script>
<style lang="scss" scoped>
page{
background-color: #ffffff;
}
.box{
display: flex;
flex:1;
align-items: center;
margin-left: 30rpx;
text{
width: 40rpx;
font-size: 24rpx;
color:#8b2316
}
}
.download-video-page {
width: 100%;
margin-top: calc(var(--status-bar-height) + 44px);
height: calc(100vh - var(--status-bar-height) - 44px);
background-color: #ffffff;
display: flex;
flex-direction: column;
}
.footer-btn{
display: flex;
align-items: center;
justify-content: space-between;
height: 100rpx;
background-color: #ffffff;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: center;
.select{
flex:1;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
color: #333;
}
.del{
height: 100rpx;
flex:1;
color:red;
display: flex;
align-items: center;
justify-content: center;
}
.bar{
width: 1px;
height: 40rpx;
background-color: #999;
}
}
.video-progress-bar {
flex:1;
height: 10rpx;
background-color: #f0f0f0;
border-radius: 5px;
}
.video-progress-bar-inner {
height: 10rpx;
background-color: #8b2316;
border-radius: 5px;
}
.nav-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 15px;
background-color: #ffffff;
position: relative;
}
.nav-back {
width: 40px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
font-size: 28px;
color: #8b2316;
font-weight: bold;
line-height: 1;
}
.nav-title {
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 18px;
font-weight: 500;
color: #8b2316;
}
.nav-edit {
width: 40px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #333333;
}
.tabs-container {
display: flex;
align-items: center;
height: 50px;
background-color: #ffffff;
border-bottom: 1px solid #f0f0f0;
position: sticky;
top: calc(var(--status-bar-height) + 44px);
z-index: 100;
}
.tab-item {
flex: 1;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #999999;
position: relative;
&.active {
color: #8b2316;
font-weight: 500;
}
}
.tab-divider {
width: 1px;
height: 20px;
background-color: #e0e0e0;
}
.content-scroll {
flex: 1;
overflow-y: auto;
padding-bottom: 200rpx;
background-color: #ffffff;
}
.empty-state {
display: flex;
align-items: center;
justify-content: center;
padding: 100px 0;
color: #999999;
font-size: 14px;
}
.video-item {
display: flex;
align-items: center;
padding: 15px;
border-bottom: 1px solid #f0f0f0;
position: relative;
}
.video-thumbnail {
width: 120px;
height: 80px;
border-radius: 4px;
background-color: #f5f5f5;
margin-right: 12px;
flex-shrink: 0;
}
.video-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 80px;
}
.video-title {
font-size: 15px;
color: #333333;
line-height: 1.4;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
.video-author {
font-size: 13px;
color: #666666;
margin-bottom: 8px;
}
.video-meta {
display: flex;
align-items: center;
font-size: 12px;
color: #999999;
}
.video-status{
display: flex;
align-items: center;
margin-right: 20rpx;
justify-content: space-between;
.status-icon{
display: flex;
align-items: center;
white-space: nowrap;
justify-content: center;
image{
width:30rpx;
height: 29rpx;
margin-right: 10rpx;
}
text{
white-space: nowrap;
font-size: 24rpx;
color: #999999;
}
}
}
.meta-icon {
margin-right: 4px;
display: inline-flex;
align-items: center;
}
.meta-text {
margin-right: 12px;
}
.meta-size {
margin-left: auto;
}
.checkbox-wrapper {
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.checkbox {
width: 20px;
height: 20px;
border: 1px solid #d0d0d0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: #ffffff;
transition: all 0.2s;
&.checked {
background-color: #8b2316;
border-color: #8b2316;
}
}
.delete-btn {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
padding: 8px 16px;
background-color: #ff4444;
color: #ffffff;
border-radius: 4px;
font-size: 14px;
}
.bottom-box {
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: 100%;
background-color: #ffffff;
z-index: 100;
}
.storage-info {
padding: 12px 15px;
background-color: #f8f8f8;
border-top: 1px solid #f0f0f0;
font-size: 12px;
color: #666666;
text-align: center;
}
</style>
+306 -119
View File
@@ -10,7 +10,7 @@
backgroundColor="#eeeeee"
>
<template #right>
<view class="nav-actions">
<view class="nav-actions" v-if="from != 'download'">
<view class="collect-img" @click="shareToggle">
<image class="share-img-icon" :src="shareIcon" mode="aspectFill" />
</view>
@@ -26,35 +26,42 @@
</uni-nav-bar>
</view>
<video
<!-- <video
v-if="showVideo"
class="player-wrapper"
:style="{width: videoWidth + 'px'}"
:src="videoSrc"
controls
object-fit="contain"
:autoplay="false"
></video>
<!-- 标签切换 -->
<cover-view class="tabs">
<cover-view
class="tab"
:class="{ active: activeTab === 'info' }"
@click="switchTab('info')"
>
视频简介
<view-cover class="tab-line" v-if="activeTab === 'info'"></view-cover>
</cover-view>
<cover-view
class="tab"
:class="{ active: activeTab === 'comment' }"
@click="switchTab('comment')"
>
评论
<view-cover class="tab-line" v-if="activeTab === 'comment'"></view-cover>
</cover-view
>
</cover-view>
<view class="video-detail-page">
></video> -->
<sunny-video
v-if="showVideo"
class="player-wrapper"
:src="videoSrc"
:videoHeight="220"
:seekTime="0"
/>
<!-- 标签切换 -->
<cover-view class="tabs" v-if="from != 'download'">
<cover-view
class="tab"
:class="{ active: activeTab === 'info' }"
@click="switchTab('info')"
>
视频简介
<view-cover class="tab-line" v-if="activeTab === 'info'"></view-cover>
</cover-view>
<cover-view
class="tab"
:class="{ active: activeTab === 'comment' }"
@click="switchTab('comment')"
>
评论
<view-cover class="tab-line" v-if="activeTab === 'comment'"></view-cover>
</cover-view>
</cover-view>
<view class="video-detail-page" v-if="from != 'download'">
<!-- <scroll-view
class="content-scroll"
scroll-y
@@ -115,11 +122,20 @@
</view>
<!-- 底部区域信息页为下载条评论页为上传图片+输入+发送 -->
<view v-if="activeTab === 'info'" class="bottom-download" @click="onDownload">
<text class="download-text" v-if="!hasDownload">点击下载<text v-if="!isVideoDownloadRecord">(限时<text class="discount">5</text>仅需{{videoInfo.point}}积分)</text></text>
<text class="download-text" v-else>查看缓存</text>
</view>
<view v-if="activeTab !== 'info'" class="bottom-comment">
<view v-if="activeTab === 'info' && from != 'download'" class="bottom-download" @click="onDownload">
<text class="download-text" v-if="!hasDownload"
>点击下载<text v-if="!isVideoDownloadRecord && welfareNum <= 0"
>(限时<text class="discount">5</text>仅需{{
videoInfo.point
}}积分)</text
>
<text v-if="!isVideoDownloadRecord && welfareNum > 0">(剩余免费下载{{ welfareNum }}次数)</text>
</text
>
<text class="download-text" v-else-if="downLoadStatus === 'loading'">视频缓存中</text>
<text class="download-text" v-else-if="downLoadStatus === 'completed'">查看缓存</text>
</view>
<view v-if="activeTab !== 'info' && from != 'download'" class="bottom-comment">
<input
class="comment-input"
v-model="commentText"
@@ -148,7 +164,7 @@
@confirm="notEnoughConfirm"
></unidialog>
<!-- 分享弹窗 -->
<uni-popup ref="shareRef" type="bottom" safeArea backgroundColor="#fff" >
<uni-popup ref="shareRef" type="bottom" safeArea backgroundColor="#fff">
<view class="share-popup">
<view class="share-title">分享到</view>
<view class="share-content">
@@ -179,7 +195,7 @@
</template>
<script setup>
import { ref,nextTick } from "vue";
import { ref, nextTick } from "vue";
import uniVideo from "@/components/uniVideo/uniVideo.vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import unidialog from "@/components/dialog/dialog.vue";
@@ -207,10 +223,106 @@ const welfareNum = ref(0);
const showVideo = ref(false);
const videoWidth = ref(375);
const hasDownload = ref(false);
let downList = uni.getStorageSync('downLoadVideo') || [];
const downLoadStatus = ref('start');
let downList = uni.getStorageSync("downLoadVideo") || [];
const downLoadList = ref(downList);
//import DomVideoPlayer from 'uniapp-video-player'
import sunnyVideo from "@/uni_modules/sunny-video/components/sunny-video/sunny-video.vue";
import downloadStore from "@/store/downloadStoreVideo.js";
const downloadTasks = ref([]);
// 添加下载任务
const addDownloadTask = (item) => {
// 使用store添加任务
const taskIndex = downloadStore.addTask(item);
// 同步任务列表
syncTasksFromStore();
// 开始下载
startDownload(taskIndex);
};
// 开始下载
const startDownload = (index) => {
const taskItem = downloadStore.getTask(index);
if (!taskItem) return;
hasDownload.value = true;
downLoadStatus.value = 'loading';
const task = uni.downloadFile({
url: taskItem.url,
success: (res1) => {
//console.log("res1:"+JSON.stringify(res1));
if (res1.statusCode === 200) {
downloadStore.updateTask(index, {
status: "completed",
filePath: res1.tempFilePath,
});
downLoadStatus.value = 'completed';
uni.showToast({
title: "下载成功",
icon: "none",
});
downLoadStatus.value = 'completed';
uni.saveFile({
tempFilePath: res1.tempFilePath,
success: function (res2) {
console.log("res2:"+JSON.stringify(res2));
uni.getSavedFileInfo({
filePath: res2.savedFilePath,
success: function (res) {
console.log("res:"+JSON.stringify(res));
console.log("size:"+res.size);
downloadStore.updateTask(index, {
status: "completed",
localPath: res2.savedFilePath,
size: res.size,
});
},
});
},
});
} else {
downloadStore.updateTask(index, {
status: "failed",
});
uni.showToast({
title: "下载失败",
icon: "none",
});
}
},
fail: (err) => {
downloadStore.updateTask(index, {
status: "failed",
});
uni.showToast({
title: "下载失败: " + (err.errMsg || "未知错误"),
icon: "none",
duration: 2000,
});
},
});
// 监听下载进度
task.onProgressUpdate((update) => {
const currentTask = downloadStore.getTask(index);
if (currentTask && currentTask.status === "downloading") {
downloadStore.updateTask(index, {
progress: update.progress,
downloadSize: update.totalBytesWritten,
totalSize: update.totalBytesExpectedToWrite,
});
}
});
// 保存task对象到store(用于暂停/继续)
downloadStore.updateTask(index, {
task: task,
});
};
const syncTasksFromStore = () => {
downloadTasks.value = downloadStore.getTasks();
};
const notEnoughConfirm = () => {
notEnoughVisible.value = false;
navTo({
@@ -231,7 +343,18 @@ const toCollection = () => {
const networkConfirm = () => {
networkVisible.value = false;
pointVisible.value = true;
downAndSave(videoSrc.value,video_uuid.value);
if(welfareNum.value > 0) {
useWelfareNum();
}else{
addDownloadTask({
url: videoSrc.value,
id: video_uuid.value,
imgpath: videoInfo.value.imgpath,
author: videoInfo.value.public_name,
name: videoInfo.value.name,
});
}
};
const addVideoWatchRecord = async () => {
const res = await api.addVideoWatchRecord({
@@ -241,25 +364,54 @@ const addVideoWatchRecord = async () => {
}
};
const onLoadedmetadata = (e) => {
un.showModal({
content: '加载完成:'+JSON.stringify(e),
showCancel: false
})
un.showModal({
content: "加载完成:" + JSON.stringify(e),
showCancel: false,
});
};
const onVideoError = (e) => {
uni.showModal({
content: JSON.stringify(e),
showCancel: false
})
content: JSON.stringify(e),
showCancel: false,
});
};
// 接收页面参数
const pageParams = ref({});
const useWelfareNum = async () => {
const res = await api.useWelfareNum({
type: 1,
other_uuid:video_uuid.value,
});
if (res.code == 1) {
uni.showToast({ title: "使用次数成功", icon: "none" });
welfareNum.value--;
addDownloadTask({
url: videoSrc.value,
id: video_uuid.value,
imgpath: videoInfo.value.imgpath,
author: videoInfo.value.public_name,
name: videoInfo.value.name,
});
}
};
const videoDetail = async () => {
const res = await api.videoDetail({ video_uuid: video_uuid.value });
if (res.code == 200) {
const userInfo = uni.getStorageSync("userInfo");
videoInfo.value = res.video;
hasDownload.value = downLoadList.value.includes(video_uuid.value);
downloadTasks.value.forEach(item => {
if (item.id == video_uuid.value) {
uni.getSavedFileInfo({
filePath: item.localPath, //仅做示例用,非真正的文件路径
success: function (res) {
hasDownload.value = true;
downLoadStatus.value = 'completed';
}
});
}
});
//hasDownload.value = downLoadList.value.includes(video_uuid.value);
let vid = res.video.polyv_uuid;
let uuid = vid.substring(0, 10);
let index = vid.lastIndexOf("_");
@@ -300,27 +452,9 @@ const VideoDownloadRecord = async () => {
const res = await api.isVideoDownloadRecord({ video_uuid: video_uuid.value });
if (res.code == 200) {
isVideoDownloadRecord.value = res.result == 0 ? false : true;
console.log(isVideoDownloadRecord.value);
}
};
const downAndSave = (url,uuid) => {
//let url='https://view.xdocin.com/view?src='+encodeURIComponent(pdfUrl);
uni.downloadFile({
url: url,
success: (res) => {
if (res.statusCode === 200) {
let index = downLoadList.value.find(cell => uuid == cell);
if (!index) {
downLoadList.value.push(item.uuid);
uni.setStorageSync('downLoadVideo', downLoadList.value);
};
}
},
});
};
const getWelfareNum = async () => {
const res = await api.getWelfareNum({
type: 1,
@@ -347,13 +481,34 @@ const videoCommentListV2 = async () => {
loading.value = false;
}
};
// 恢复正在下载的任务
const resumeDownloadingTasks = () => {
// 使用 nextTick 确保在页面渲染后再恢复下载任务
nextTick(() => {
downloadStore.resumeDownloadingTasks((index) => {
// 重新开始下载(uni.downloadFile不支持断点续传,所以从0开始)
console.log("恢复下载任务:", downloadStore.getTask(index)?.url);
startDownload(index);
});
});
};
onShow(() => {
hasDownload.value = false;
downLoadStatus.value = 'start';
syncTasksFromStore();
// 监听store变化
downloadStore.addListener((tasks) => {
downloadTasks.value = tasks;
});
// 恢复正在下载的任务
resumeDownloadingTasks();
if (activeTab.value == "comment") {
videoCommentListV2();
}
videoDetail();
uni.hideLoading();
});
const shareRef = ref();
@@ -381,10 +536,10 @@ const shareToWechat = () => {
href: shareLink.value,
imageUrl: docUrl + videoInfo.value.imgpath,
success: function (res) {
console.log("success:" + JSON.stringify(res));
//console.log("success:" + JSON.stringify(res));
},
fail: function (err) {
console.log("fail:" + JSON.stringify(err));
//console.log("fail:" + JSON.stringify(err));
},
});
// #endif
@@ -449,10 +604,10 @@ const shareToMoments = () => {
href: shareLink.value,
imageUrl: docUrl + videoInfo.value.imgpath,
success: function (res) {
console.log("success:" + JSON.stringify(res));
//console.log("success:" + JSON.stringify(res));
},
fail: function (err) {
console.log("fail:" + JSON.stringify(err));
//console.log("fail:" + JSON.stringify(err));
},
});
// #endif
@@ -491,10 +646,10 @@ const shareToWeibo = () => {
href: shareLink.value,
imageUrl: logoImg,
success: function (res) {
console.log("分享成功");
//console.log("分享成功");
},
fail: function (err) {
console.log("fail:" + JSON.stringify(err));
//console.log("fail:" + JSON.stringify(err));
},
});
// plus.share.sendWithSystem({
@@ -529,19 +684,31 @@ const shareToWeibo = () => {
closeShare();
};
const from = ref('');
// 使用uni-app的onLoad生命周期
onLoad((options) => {
console.log(options);
uni.getSystemInfo({
success: function (res) {
videoWidth.value = res.windowWidth; // 窗口宽度
}
});
success: function (res) {
videoWidth.value = res.windowWidth; // 窗口宽度
},
});
video_uuid.value = options.id;
from.value = options.from;
addVideoWatchRecord();
VideoDownloadRecord();
getWelfareNum();
// 从store同步任务列表
syncTasksFromStore();
// 监听store变化
downloadStore.addListener((tasks) => {
downloadTasks.value = tasks;
});
// 恢复正在下载的任务
resumeDownloadingTasks();
// navTo({
// url: "/pages_app/downLoadVideo/downLoadVideo",
// });
});
const videoSrc = ref("");
@@ -562,8 +729,12 @@ const switchTab = (tab) => {
const payVideoDownload = async () => {
const res = await api.payVideoDownload({ video_uuid: video_uuid.value });
if (res.code == 200) {
navTo({
url: "/pages_app/myDownLoad/myDownLoad",
addDownloadTask({
url: videoSrc.value,
id: video_uuid.value,
imgpath: videoInfo.value.imgpath,
author: videoInfo.value.public_name,
name: videoInfo.value.name,
});
} else if (res.code == 106) {
notEnoughVisible.value = true;
@@ -572,31 +743,47 @@ const payVideoDownload = async () => {
};
const onDownload = () => {
console.log(isVideoDownloadRecord.value);
if (!isVideoDownloadRecord.value) {
pointContent.value = `当前需要${videoInfo.value.point}积分兑换,若删除可以再次缓存`;
uni.getNetworkType({
success: function (res) {
console.log(res);
if (res.networkType != "none") {
networkVisible.value = true;
networkContent.value = `当前为${res.networkType}网络,确定下载?`;
if(welfareNum.value <= 0) {
pointContent.value = `当前需要${videoInfo.value.point}积分兑换,若删除可以再次缓存`;
uni.getNetworkType({
success: function (res) {
if (res.networkType != "none") {
networkVisible.value = true;
networkContent.value = `当前为${res.networkType}网络,确定下载?`;
} else {
uni.showToast({ title: "当前未联网,请检查网络", icon: "none" });
}
},
});
}else{
uni.getNetworkType({
success: function (res) {
if (res.networkType != "none") {
networkVisible.value = true;
networkContent.value = `当前为${res.networkType}网络,确定下载?`;
} else {
uni.showToast({ title: "当前未联网,请检查网络", icon: "none" });
}
},
});
}
console.log(11111);
} else {
uni.showToast({ title: "当前未联网,请检查网络", icon: "none" });
}
},
});
} else {
if(hasDownload.value) {
navTo({
url: "/pages_app/myDownLoad/myDownLoad",
if (hasDownload.value) {
navTo({
url: "/pages_app/downLoadVideo/downLoadVideo",
});
} else {
downAndSave(videoSrc.value,video_uuid.value);
addDownloadTask({
url: videoSrc.value,
id: video_uuid.value,
imgpath: videoInfo.value.imgpath,
author: videoInfo.value.public_name,
name: videoInfo.value.name,
});
}
}
};
@@ -674,13 +861,13 @@ $theme-color: #8b2316;
.collect-img {
/* margin-top:50rpx; */
}
.navbox{
:deep(.uni-navbar__header-btns-right){
width: 100px!important;
.navbox {
:deep(.uni-navbar__header-btns-right) {
width: 100px !important;
}
:deep(.uni-navbar__header-btns-left){
min-width: 100px;
width: 100px!important;
:deep(.uni-navbar__header-btns-left) {
min-width: 100px;
width: 100px !important;
}
}
.share-img-icon {
@@ -708,11 +895,10 @@ $theme-color: #8b2316;
.player-wrapper {
background: #fff;
position: fixed;
top:calc(var(--status-bar-height) + 44px);
left: 0;
z-index:0;
height: 220px!important;
position: relative;
top: calc(var(--status-bar-height) + 44px);
z-index: 0;
overflow: hidden;
}
.share-img {
@@ -733,7 +919,7 @@ $theme-color: #8b2316;
z-index: 99;
.tab {
width:120rpx;
width: 120rpx;
position: relative;
display: flex;
justify-content: center;
@@ -741,19 +927,20 @@ $theme-color: #8b2316;
padding: 16rpx 0 24rpx;
color: $text-secondary;
font-size: 28rpx;
.tab-line{
.tab-line {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 64rpx;
height: 6rpx;
z-index: 2;
border-radius: 6rpx;
background: $theme-color;
}
&:nth-child(2){
margin-left: 200rpx;
}
&:nth-child(2) {
margin-left: 200rpx;
}
&.active {
color: $theme-color;
}
@@ -771,15 +958,15 @@ $theme-color: #8b2316;
// }
}
}
.video-detail-page{
position: fixed;
width: 100%;
background: #fff;
// padding: 24rpx 28rpx 40rpx;
top: calc(var(--status-bar-height) + 44px + 220px + 88rpx);
height: calc(100vh - var(--status-bar-height) - 44px - 220px - 90rpx);
overflow-y: scroll;
// background: red;
.video-detail-page {
position: fixed;
width: 100%;
background: #fff;
// padding: 24rpx 28rpx 40rpx;
top: calc(var(--status-bar-height) + 44px + 220px + 88rpx);
height: calc(100vh - var(--status-bar-height) - 44px - 220px - 90rpx);
overflow-y: scroll;
// background: red;
}
.intro {
// background: #fff;