添加ts类型
This commit is contained in:
parent
d9d2ab0518
commit
adf17d57fc
@ -87,9 +87,11 @@
|
|||||||
"@dcloudio/uni-cli-shared": "3.0.0-4060420250429001",
|
"@dcloudio/uni-cli-shared": "3.0.0-4060420250429001",
|
||||||
"@dcloudio/uni-stacktracey": "3.0.0-4060420250429001",
|
"@dcloudio/uni-stacktracey": "3.0.0-4060420250429001",
|
||||||
"@dcloudio/vite-plugin-uni": "3.0.0-4060420250429001",
|
"@dcloudio/vite-plugin-uni": "3.0.0-4060420250429001",
|
||||||
|
"@types/html5plus": "^1.0.5",
|
||||||
"@vue/runtime-core": "^3.5.12",
|
"@vue/runtime-core": "^3.5.12",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"less": "^4.2.0",
|
"less": "^4.2.0",
|
||||||
|
"miniprogram-api-typings": "^4.1.0",
|
||||||
"sass": "1.78.0",
|
"sass": "1.78.0",
|
||||||
"sass-loader": "^16.0.1",
|
"sass-loader": "^16.0.1",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2",
|
||||||
|
|||||||
@ -151,8 +151,7 @@ const buildVideoData = (res) => {
|
|||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
videoData = {
|
videoData = {
|
||||||
path: res.tempFilePath,
|
path: res.tempFilePath,
|
||||||
value: res.tempFilePath,
|
size:res.size
|
||||||
...res,
|
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive } from 'vue'
|
import { ref } from 'vue'
|
||||||
import modal from '@/plugins/modal'
|
import modal from '@/plugins/modal'
|
||||||
import { wxChunkUploader } from '@/utils/ChunkUploaderWx'
|
import { wxChunkUploader } from '@/utils/ChunkUploaderWx'
|
||||||
import appChunkUploader from '@/utils/ChunkUploaderApp'
|
import appChunkUploader from '@/utils/ChunkUploaderApp'
|
||||||
@ -53,20 +53,16 @@ const validateParams = () => {
|
|||||||
* @returns {Promise<boolean>} 上传结果
|
* @returns {Promise<boolean>} 上传结果
|
||||||
*/
|
*/
|
||||||
const handleWxChunkUpload = async () => {
|
const handleWxChunkUpload = async () => {
|
||||||
try {
|
const result = await wxChunkUploader.upload({
|
||||||
const result = await wxChunkUploader.upload({
|
file: videoFile.value,
|
||||||
file: videoFile.value,
|
onSuccess: () => {
|
||||||
onSuccess: (result) => {
|
modal.msgSuccess('上传成功')
|
||||||
modal.msgSuccess('上传成功')
|
},
|
||||||
},
|
onError: (error) => {
|
||||||
onError: (error) => {
|
modal.msg(error)
|
||||||
modal.msgError('上传失败')
|
}
|
||||||
}
|
});
|
||||||
});
|
return result
|
||||||
} catch (error) {
|
|
||||||
console.error('APP上传失败:', error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,9 +75,6 @@ const handleAppChunkUpload = async () => {
|
|||||||
|
|
||||||
const result = await appChunkUploader.upload({
|
const result = await appChunkUploader.upload({
|
||||||
file: file,
|
file: file,
|
||||||
onProgress: (progress) => {
|
|
||||||
console.log('上传进度:', progress)
|
|
||||||
},
|
|
||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
console.log('上传成功:', result)
|
console.log('上传成功:', result)
|
||||||
},
|
},
|
||||||
|
|||||||
47
src/types/upload.ts
Normal file
47
src/types/upload.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export interface UploadOptions {
|
||||||
|
/**文件 */
|
||||||
|
file: File
|
||||||
|
/**成功回调 */
|
||||||
|
onSuccess?: (result: any) => void;
|
||||||
|
/**失败回调 */
|
||||||
|
onError?: (error: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface File {
|
||||||
|
/**文件路径 */
|
||||||
|
path: string;
|
||||||
|
/**文件大小 */
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UploadData {
|
||||||
|
/**上传编号 */
|
||||||
|
uploadId: string;
|
||||||
|
/**文件在云端保存路径 */
|
||||||
|
saveFilePath: string;
|
||||||
|
/**上传文件的名称 */
|
||||||
|
uploadFileName: string;
|
||||||
|
/**上传文件的大小 */
|
||||||
|
fileSize: number;
|
||||||
|
/**分片数量 */
|
||||||
|
chunkCount: number;
|
||||||
|
/**上传文件的路径 */
|
||||||
|
filePath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PartETag {
|
||||||
|
partNumber: number;
|
||||||
|
ETag: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ChunkTask {
|
||||||
|
index: number;
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,288 +0,0 @@
|
|||||||
import modal from '@/plugins/modal'
|
|
||||||
import {
|
|
||||||
deleteLocalFile,
|
|
||||||
copyFileToSandbox,
|
|
||||||
readAppFileChunk,
|
|
||||||
getFileName,
|
|
||||||
deleteTempFile,
|
|
||||||
createAndWriteTempFile
|
|
||||||
} from "@/utils/fileOper"
|
|
||||||
import { initChunkUpload, uploadChunk, completeChunkUpload } from '@/api/system/chunkUpload'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* APP端分片上传工具类
|
|
||||||
*/
|
|
||||||
class AppChunkUploader {
|
|
||||||
constructor(options = {}) {
|
|
||||||
this.config = {
|
|
||||||
chunkSize: 15 * 1024 * 1024, // 默认分片大小15MB
|
|
||||||
concurrentLimit: 2, // 并发上传的分片数量
|
|
||||||
...options
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取切片end位置
|
|
||||||
* @param {number} start - 开始位置
|
|
||||||
* @param {number} chunkSize - 分片大小
|
|
||||||
* @param {number} fileSize - 文件总大小
|
|
||||||
* @param {number} index - 分片索引
|
|
||||||
* @param {number} totalChunks - 总分片数
|
|
||||||
* @returns {number} end位置
|
|
||||||
*/
|
|
||||||
getSliceEnd(start, chunkSize, fileSize, index, totalChunks) {
|
|
||||||
return index < totalChunks - 1 ? start + chunkSize - 1 : fileSize
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* APP端分片上传单个分片
|
|
||||||
* @param {string} uploadId - 上传ID
|
|
||||||
* @param {string} filePath - 文件路径
|
|
||||||
* @param {number} chunkIndex - 分片索引
|
|
||||||
* @param {ArrayBuffer} chunk - 分片数据
|
|
||||||
* @returns {Promise} 上传结果
|
|
||||||
*/
|
|
||||||
async uploadAppChunk(uploadId, filePath, chunkIndex, chunk) {
|
|
||||||
try {
|
|
||||||
const response = await this.startUploadAppChunk(uploadId, filePath, chunkIndex, chunk)
|
|
||||||
return response
|
|
||||||
} catch (error) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行APP端分片上传
|
|
||||||
* @param {string} uploadId - 上传ID
|
|
||||||
* @param {string} filePath - 文件路径
|
|
||||||
* @param {number} chunkIndex - 分片索引
|
|
||||||
* @param {ArrayBuffer} chunk - 分片数据
|
|
||||||
* @returns {Promise} 上传结果
|
|
||||||
*/
|
|
||||||
startUploadAppChunk(uploadId, filePath, chunkIndex, chunk) {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
try {
|
|
||||||
// 1. 准备临时文件信息
|
|
||||||
const tempFileName = `temp_chunk/chunk_${uploadId}_${chunkIndex}.bin`
|
|
||||||
const tempDirPath = plus.io.PRIVATE_DOC
|
|
||||||
|
|
||||||
// 2. 创建并写入临时文件
|
|
||||||
const tempFilePath = await createAndWriteTempFile(
|
|
||||||
tempDirPath,
|
|
||||||
tempFileName,
|
|
||||||
chunk
|
|
||||||
)
|
|
||||||
|
|
||||||
//设置文件的全路径
|
|
||||||
let formattedPath = tempFilePath
|
|
||||||
if (tempFilePath && !tempFilePath.startsWith("file://")) {
|
|
||||||
formattedPath = `file://${tempFilePath}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 上传文件
|
|
||||||
const result = await uploadChunk(uploadId, filePath, chunkIndex, formattedPath)
|
|
||||||
|
|
||||||
// 4. 删除临时文件
|
|
||||||
await deleteTempFile(tempDirPath, tempFileName)
|
|
||||||
|
|
||||||
resolve(result)
|
|
||||||
} catch (error) {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 并发上传分片
|
|
||||||
* @param {Array} tasks - 分片任务数组
|
|
||||||
* @param {number} batchSize - 批次大小
|
|
||||||
* @param {string} uploadId - 上传ID
|
|
||||||
* @param {string} filePath - 文件路径
|
|
||||||
* @param {string} localFilePath - 本地文件路径
|
|
||||||
* @param {Array} partETags - 分片ETag数组
|
|
||||||
* @param {Object} progressInfo - 进度信息对象
|
|
||||||
* @returns {Promise<Array>} 上传结果数组
|
|
||||||
*/
|
|
||||||
async uploadChunksInBatches(tasks, batchSize, uploadId, filePath, localFilePath, partETags, progressInfo) {
|
|
||||||
const results = []
|
|
||||||
const { chunkCount } = progressInfo
|
|
||||||
|
|
||||||
for (let i = 0; i < tasks.length; i += batchSize) {
|
|
||||||
const batch = tasks.slice(i, i + batchSize)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const batchResults = await Promise.all(
|
|
||||||
batch.map((task) => this.uploadChunkConcurrently(task, uploadId, filePath, localFilePath, partETags, progressInfo))
|
|
||||||
)
|
|
||||||
results.push(...batchResults)
|
|
||||||
} catch (error) {
|
|
||||||
// 如果批次中有任何分片失败,立即停止上传
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 并发上传单个分片
|
|
||||||
* @param {Object} chunkTask - 分片任务
|
|
||||||
* @param {string} uploadId - 上传ID
|
|
||||||
* @param {string} filePath - 文件路径
|
|
||||||
* @param {string} localFilePath - 本地文件路径
|
|
||||||
* @param {Array} partETags - 分片ETag数组
|
|
||||||
* @param {Object} progressInfo - 进度信息对象
|
|
||||||
* @returns {Promise} 上传结果
|
|
||||||
*/
|
|
||||||
async uploadChunkConcurrently(chunkTask, uploadId, filePath, localFilePath, partETags, progressInfo) {
|
|
||||||
const { index, start, end } = chunkTask
|
|
||||||
const { chunkCount } = progressInfo
|
|
||||||
|
|
||||||
const chunk = await readAppFileChunk(localFilePath, start, end - start)
|
|
||||||
|
|
||||||
const response = await this.uploadAppChunk(uploadId, filePath, index, chunk)
|
|
||||||
|
|
||||||
if (response.data && response.data.etag) {
|
|
||||||
partETags[index] = {
|
|
||||||
partNumber: index + 1,
|
|
||||||
ETag: response.data.etag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
progressInfo.completedChunks++
|
|
||||||
const percent = Math.floor((progressInfo.completedChunks / chunkCount) * 100)
|
|
||||||
const displayPercent = Math.floor(percent / 10) * 10 // 每10%更新一次
|
|
||||||
|
|
||||||
if (displayPercent !== progressInfo.uploadProgress || progressInfo.completedChunks === chunkCount) {
|
|
||||||
modal.closeLoading()
|
|
||||||
modal.loading(`上传中 ${percent}% (请勿离开此页面)`)
|
|
||||||
progressInfo.uploadProgress = displayPercent
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主要的分片上传方法
|
|
||||||
* @param {Object} options - 上传选项
|
|
||||||
* @param {Object} options.file - 文件对象(包含path和size属性)
|
|
||||||
* @param {string} options.filePath - 文件路径(如果提供file,此参数可选)
|
|
||||||
* @param {string} options.fileName - 文件名称(可选,会自动从路径提取)
|
|
||||||
* @param {number} options.fileSize - 文件大小(如果提供file,此参数可选)
|
|
||||||
* @param {Function} options.onProgress - 进度回调函数(可选)
|
|
||||||
* @param {Function} options.onSuccess - 成功回调函数(可选)
|
|
||||||
* @param {Function} options.onError - 错误回调函数(可选)
|
|
||||||
* @returns {Promise<boolean>} 上传结果
|
|
||||||
*/
|
|
||||||
async upload(options) {
|
|
||||||
const {
|
|
||||||
file,
|
|
||||||
onProgress,
|
|
||||||
onSuccess,
|
|
||||||
onError
|
|
||||||
} = options
|
|
||||||
|
|
||||||
// 优先使用file对象,否则使用单独传入的参数
|
|
||||||
const actualFilePath = file.path
|
|
||||||
const actualFileSize = file.size
|
|
||||||
|
|
||||||
if (!actualFilePath) {
|
|
||||||
throw new Error('必须提供 filePath 或包含 path 属性的 file 对象')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!actualFileSize) {
|
|
||||||
throw new Error('必须提供 fileSize 或包含 size 属性的 file 对象')
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
//初始化文件状态
|
|
||||||
let localFilePath = actualFilePath
|
|
||||||
const actualFileName = getFileName(localFilePath)
|
|
||||||
|
|
||||||
modal.loading("准备上传...")
|
|
||||||
|
|
||||||
// 1.计算分片数量
|
|
||||||
const chunkSize = this.config.chunkSize
|
|
||||||
const chunkCount = Math.ceil(actualFileSize / chunkSize)
|
|
||||||
|
|
||||||
//2.初始化分片上传
|
|
||||||
const initResult = await initChunkUpload(actualFileName, actualFileSize)
|
|
||||||
if (initResult.code !== 200) throw new Error("初始化上传失败")
|
|
||||||
|
|
||||||
const { uploadId, filePath: serverFilePath } = initResult.data
|
|
||||||
const partETags = []
|
|
||||||
|
|
||||||
//3.将文件移动到应用 沙盒 目录
|
|
||||||
localFilePath = await copyFileToSandbox(localFilePath)
|
|
||||||
|
|
||||||
//4.上传所有分片
|
|
||||||
modal.closeLoading()
|
|
||||||
modal.loading("上传中...")
|
|
||||||
|
|
||||||
//5.进度信息对象
|
|
||||||
const progressInfo = {
|
|
||||||
completedChunks: 0,
|
|
||||||
uploadProgress: 0,
|
|
||||||
chunkCount
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建分片任务队列
|
|
||||||
const chunkTasks = []
|
|
||||||
for (let i = 0; i < chunkCount; i++) {
|
|
||||||
chunkTasks.push({
|
|
||||||
index: i,
|
|
||||||
start: i * chunkSize,
|
|
||||||
end: this.getSliceEnd(i * chunkSize, chunkSize, actualFileSize, i, chunkCount),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//并发上传数据
|
|
||||||
await this.uploadChunksInBatches(
|
|
||||||
chunkTasks,
|
|
||||||
this.config.concurrentLimit,
|
|
||||||
uploadId,
|
|
||||||
serverFilePath,
|
|
||||||
localFilePath,
|
|
||||||
partETags,
|
|
||||||
progressInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
//合并分片
|
|
||||||
modal.msg("正在合并分片...")
|
|
||||||
|
|
||||||
const result = await completeChunkUpload(
|
|
||||||
uploadId, serverFilePath, actualFileSize, actualFileName, partETags
|
|
||||||
)
|
|
||||||
|
|
||||||
await deleteLocalFile(localFilePath) //将临时文件删除,防止占用空间
|
|
||||||
|
|
||||||
modal.msgSuccess("上传成功")
|
|
||||||
|
|
||||||
// 执行成功回调
|
|
||||||
if (onSuccess) {
|
|
||||||
onSuccess(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
modal.closeLoading()
|
|
||||||
const errorMessage = `上传失败: ${error.message || error}`
|
|
||||||
modal.msg(errorMessage)
|
|
||||||
|
|
||||||
// 执行错误回调
|
|
||||||
if (onError) {
|
|
||||||
onError(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建默认实例
|
|
||||||
const appChunkUploader = new AppChunkUploader()
|
|
||||||
|
|
||||||
export default appChunkUploader
|
|
||||||
export { AppChunkUploader }
|
|
||||||
479
src/utils/ChunkUploaderApp.ts
Normal file
479
src/utils/ChunkUploaderApp.ts
Normal file
@ -0,0 +1,479 @@
|
|||||||
|
import modal from '@/plugins/modal'
|
||||||
|
import { initChunkUpload, uploadChunk, completeChunkUpload } from '@/api/system/chunkUpload'
|
||||||
|
import { UploadOptions, PartETag, ChunkTask } from '@/types/upload'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APP端分片上传工具类
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class AppChunkUploader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分片大小,单位字节
|
||||||
|
*/
|
||||||
|
private chunkSize: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 并发上传的分片数量限制
|
||||||
|
*/
|
||||||
|
private concurrentLimit: number;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数 - 初始化分片上传器
|
||||||
|
* 设置默认分片大小为15MB,并发限制为2个分片
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.chunkSize = 15 * 1024 * 1024; // 默认分片大小15MB
|
||||||
|
this.concurrentLimit = 2; // 并发上传的分片数量
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主要的分片上传方法
|
||||||
|
* @param options 上传配置选项
|
||||||
|
* @param options.file 要上传的文件对象,包含path和size属性
|
||||||
|
* @param options.onSuccess 上传成功回调函数
|
||||||
|
* @param options.onError 上传失败回调函数
|
||||||
|
* @returns Promise<boolean> 返回上传是否成功
|
||||||
|
*/
|
||||||
|
async upload(options: UploadOptions): Promise<boolean> {
|
||||||
|
const { file, onSuccess, onError } = options
|
||||||
|
try {
|
||||||
|
const actualFilePath = file.path
|
||||||
|
const actualFileSize = file.size
|
||||||
|
|
||||||
|
if (!actualFilePath) throw new Error('文件路径不存在')
|
||||||
|
if (!actualFileSize) throw new Error('文件大小不存在')
|
||||||
|
|
||||||
|
//初始化文件状态
|
||||||
|
let localFilePath = actualFilePath
|
||||||
|
const actualFileName = this.getFileName(localFilePath)
|
||||||
|
|
||||||
|
modal.loading("准备上传...")
|
||||||
|
|
||||||
|
// 1.计算分片数量
|
||||||
|
const chunkSize = this.chunkSize
|
||||||
|
const chunkCount = Math.ceil(actualFileSize / chunkSize)
|
||||||
|
|
||||||
|
//2.初始化分片上传
|
||||||
|
const initResult = await initChunkUpload(actualFileName, actualFileSize)
|
||||||
|
if (initResult.code !== 200) throw new Error("初始化上传失败")
|
||||||
|
|
||||||
|
const { uploadId, filePath: serverFilePath } = initResult.data
|
||||||
|
const partETags: PartETag[] = [];
|
||||||
|
|
||||||
|
//3.将文件移动到应用 沙盒 目录
|
||||||
|
localFilePath = await this.copyFileToSandbox(localFilePath)
|
||||||
|
|
||||||
|
//4.上传所有分片
|
||||||
|
modal.closeLoading()
|
||||||
|
modal.loading("上传中...")
|
||||||
|
|
||||||
|
//5.进度信息对象
|
||||||
|
const progressInfo = {
|
||||||
|
completedChunks: 0,
|
||||||
|
uploadProgress: 0,
|
||||||
|
chunkCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建分片任务队列
|
||||||
|
const chunkTasks: ChunkTask[] = []
|
||||||
|
for (let i = 0; i < chunkCount; i++) {
|
||||||
|
chunkTasks.push({
|
||||||
|
index: i,
|
||||||
|
start: i * chunkSize,
|
||||||
|
end: this.getSliceEnd(i * chunkSize, chunkSize, actualFileSize, i, chunkCount),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//并发上传数据
|
||||||
|
await this.uploadChunksInBatches(
|
||||||
|
chunkTasks,
|
||||||
|
this.concurrentLimit,
|
||||||
|
uploadId,
|
||||||
|
serverFilePath,
|
||||||
|
localFilePath,
|
||||||
|
partETags,
|
||||||
|
progressInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
//合并分片
|
||||||
|
modal.closeLoading();
|
||||||
|
modal.loading("正在合并分片...")
|
||||||
|
|
||||||
|
//完成分片上传
|
||||||
|
await completeChunkUpload(
|
||||||
|
uploadId, serverFilePath, actualFileSize, actualFileName, partETags
|
||||||
|
)
|
||||||
|
|
||||||
|
//将临时文件删除,防止占用空间
|
||||||
|
await this.deleteLocalFile(localFilePath)
|
||||||
|
|
||||||
|
modal.closeLoading()
|
||||||
|
|
||||||
|
// 执行成功回调
|
||||||
|
onSuccess?.({ success: true })
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
modal.closeLoading()
|
||||||
|
const errorMessage = error instanceof Error ? error.message : `上传失败`
|
||||||
|
onError?.(errorMessage)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取切片end位置
|
||||||
|
* @param start 切片开始位置
|
||||||
|
* @param chunkSize 切片大小
|
||||||
|
* @param fileSize 文件总大小
|
||||||
|
* @param index 当前切片索引
|
||||||
|
* @param totalChunks 总切片数量
|
||||||
|
* @returns number 切片结束位置
|
||||||
|
*/
|
||||||
|
getSliceEnd(start: number, chunkSize: number, fileSize: number, index: number, totalChunks: number) {
|
||||||
|
return index < totalChunks - 1 ? start + chunkSize - 1 : fileSize
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 并发上传分片
|
||||||
|
* @param tasks 分片任务数组
|
||||||
|
* @param batchSize 批次大小,控制并发数量
|
||||||
|
* @param uploadId 上传ID
|
||||||
|
* @param filePath 服务器文件路径
|
||||||
|
* @param localFilePath 本地文件路径
|
||||||
|
* @param partETags 分片ETag数组,用于合并分片
|
||||||
|
* @param progressInfo 进度信息对象
|
||||||
|
* @returns Promise<any[]> 上传结果数组
|
||||||
|
*/
|
||||||
|
async uploadChunksInBatches(tasks: ChunkTask[], batchSize: number, uploadId: string, filePath: string, localFilePath: string, partETags: PartETag[], progressInfo: any): Promise<any[]> {
|
||||||
|
const results = []
|
||||||
|
|
||||||
|
for (let i = 0; i < tasks.length; i += batchSize) {
|
||||||
|
const batch = tasks.slice(i, i + batchSize)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const batchResults = await Promise.all(
|
||||||
|
batch.map((task) => this.uploadChunkConcurrently(task, uploadId, filePath, localFilePath, partETags, progressInfo))
|
||||||
|
)
|
||||||
|
results.push(...batchResults)
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : '并发上传失败'
|
||||||
|
throw new Error(errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APP端分片上传单个分片
|
||||||
|
* @param uploadId 上传ID
|
||||||
|
* @param filePath 服务器文件路径
|
||||||
|
* @param chunkIndex 分片索引
|
||||||
|
* @param chunk 分片数据,可以是ArrayBuffer或字符串
|
||||||
|
* @returns Promise<any> 上传响应结果
|
||||||
|
*/
|
||||||
|
async uploadAppChunk(uploadId: string, filePath: string, chunkIndex: number, chunk: ArrayBuffer | string) {
|
||||||
|
try {
|
||||||
|
const response = await this.startUploadAppChunk(uploadId, filePath, chunkIndex, chunk)
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('分片上传失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行APP端分片上传
|
||||||
|
* @param uploadId 上传ID
|
||||||
|
* @param filePath 服务器文件路径
|
||||||
|
* @param chunkIndex 分片索引
|
||||||
|
* @param chunk 分片数据,可以是ArrayBuffer或字符串
|
||||||
|
* @returns Promise 返回上传结果的Promise
|
||||||
|
*/
|
||||||
|
startUploadAppChunk(uploadId: string, filePath: string, chunkIndex: number, chunk: ArrayBuffer | string) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
// 1. 准备临时文件信息
|
||||||
|
const tempFileName = `temp_chunk/chunk_${uploadId}_${chunkIndex}.bin`
|
||||||
|
const tempDirPath = plus.io.PRIVATE_DOC
|
||||||
|
|
||||||
|
// 2. 创建并写入临时文件
|
||||||
|
const tempFilePath = await this.createAndWriteTempFile(
|
||||||
|
tempDirPath,
|
||||||
|
tempFileName,
|
||||||
|
chunk
|
||||||
|
)
|
||||||
|
|
||||||
|
//设置文件的全路径
|
||||||
|
let formattedPath = tempFilePath
|
||||||
|
if (tempFilePath && !tempFilePath.startsWith("file://")) {
|
||||||
|
formattedPath = `file://${tempFilePath}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 上传文件
|
||||||
|
const result = await uploadChunk(uploadId, filePath, chunkIndex, formattedPath)
|
||||||
|
|
||||||
|
// 4. 删除临时文件
|
||||||
|
await this.deleteTempFile(tempDirPath, tempFileName)
|
||||||
|
|
||||||
|
resolve(result)
|
||||||
|
} catch (error) {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 并发上传单个分片
|
||||||
|
* @param chunkTask 分片任务对象,包含index、start、end等信息
|
||||||
|
* @param uploadId 上传ID
|
||||||
|
* @param filePath 服务器文件路径
|
||||||
|
* @param localFilePath 本地文件路径
|
||||||
|
* @param partETags 分片ETag数组,用于合并分片
|
||||||
|
* @param progressInfo 进度信息对象,包含completedChunks、uploadProgress、chunkCount等
|
||||||
|
* @returns Promise<any> 上传响应结果
|
||||||
|
*/
|
||||||
|
async uploadChunkConcurrently(chunkTask: any, uploadId: string, filePath: string, localFilePath: string, partETags: PartETag[], progressInfo: any) {
|
||||||
|
const { index, start, end } = chunkTask
|
||||||
|
const { chunkCount } = progressInfo
|
||||||
|
|
||||||
|
const chunk = await this.readAppFileChunk(localFilePath, start, end - start)
|
||||||
|
|
||||||
|
const response = await this.uploadAppChunk(uploadId, filePath, index, chunk) as any
|
||||||
|
|
||||||
|
if (response.data && response.data.etag) {
|
||||||
|
partETags.push({
|
||||||
|
partNumber: index + 1,
|
||||||
|
ETag: response.data.etag,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
progressInfo.completedChunks++
|
||||||
|
const percent = Math.floor((progressInfo.completedChunks / chunkCount) * 100)
|
||||||
|
const displayPercent = Math.floor(percent / 10) * 10 // 每10%更新一次
|
||||||
|
|
||||||
|
if (displayPercent !== progressInfo.uploadProgress || progressInfo.completedChunks === chunkCount) {
|
||||||
|
modal.closeLoading()
|
||||||
|
modal.loading(`上传中 ${percent}% (请勿离开此页面)`)
|
||||||
|
progressInfo.uploadProgress = displayPercent
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件名称
|
||||||
|
* @param filePath 完整文件路径
|
||||||
|
* @returns string 从路径中提取的文件名
|
||||||
|
*/
|
||||||
|
getFileName(filePath: string): string {
|
||||||
|
if (!filePath) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// 查找最后一个斜杠位置
|
||||||
|
const slashIndex = filePath.lastIndexOf("/");
|
||||||
|
if (slashIndex === -1) {
|
||||||
|
return filePath; // 没有斜杠,整个字符串可能就是文件名
|
||||||
|
}
|
||||||
|
// 从最后一个斜杠后面提取文件名
|
||||||
|
return filePath.substring(slashIndex + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将文件复制到应用沙盒目录
|
||||||
|
* @param srcUrl 源文件URL路径
|
||||||
|
* @returns Promise<string> 复制后的文件完整路径
|
||||||
|
*/
|
||||||
|
copyFileToSandbox(srcUrl: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const newName = `file_${Date.now()}.${this.getFileExtension(srcUrl)}`;
|
||||||
|
plus.io.requestFileSystem(
|
||||||
|
plus.io.PRIVATE_DOC,
|
||||||
|
(dstEntry) => {
|
||||||
|
plus.io.resolveLocalFileSystemURL(
|
||||||
|
srcUrl,
|
||||||
|
(srcEntry) => {
|
||||||
|
srcEntry.copyTo(
|
||||||
|
dstEntry.root,
|
||||||
|
newName,
|
||||||
|
(entry) => {
|
||||||
|
if (entry.fullPath) {
|
||||||
|
resolve(entry.fullPath);
|
||||||
|
} else {
|
||||||
|
reject(new Error('File path is undefined'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(e) => reject(e)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(e) => reject(e)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(e) => reject(e)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件的扩展名称
|
||||||
|
* @param filePath 完整文件路径
|
||||||
|
* @returns string 文件扩展名(小写,不包含点号)
|
||||||
|
*/
|
||||||
|
getFileExtension(filePath: string): string {
|
||||||
|
if (!filePath) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// 查找最后一个点号位置
|
||||||
|
const dotIndex = filePath.lastIndexOf(".");
|
||||||
|
if (dotIndex === -1) {
|
||||||
|
return ""; // 没有找到扩展名
|
||||||
|
}
|
||||||
|
// 从点号后面提取扩展名
|
||||||
|
return filePath.substring(dotIndex + 1).toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除本地临时文件(临时文件是分片生成的)
|
||||||
|
* @param filePath 要删除的文件路径
|
||||||
|
* @returns Promise<boolean> 删除是否成功
|
||||||
|
*/
|
||||||
|
deleteLocalFile(filePath: string): Promise<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!filePath) {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
plus.io.resolveLocalFileSystemURL(
|
||||||
|
filePath,
|
||||||
|
(entry) => {
|
||||||
|
entry.remove(
|
||||||
|
() => { resolve(true); },
|
||||||
|
(error) => { resolve(false); }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(error) => { resolve(false); }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建临时文件并写入数据
|
||||||
|
* @param dirPath 目录路径标识(plus.io.PRIVATE_DOC等)
|
||||||
|
* @param fileName 临时文件名
|
||||||
|
* @param data 要写入的数据,可以是ArrayBuffer或字符串
|
||||||
|
* @returns Promise<string> 创建的临时文件完整路径
|
||||||
|
*/
|
||||||
|
createAndWriteTempFile(dirPath: number, fileName: String, data: ArrayBuffer | string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
plus.io.requestFileSystem(
|
||||||
|
dirPath,
|
||||||
|
(dirEntry: any) => {
|
||||||
|
dirEntry.root.getFile(
|
||||||
|
fileName,
|
||||||
|
{ create: true, exclusive: false },
|
||||||
|
(fileEntry: any) => {
|
||||||
|
fileEntry.createWriter(
|
||||||
|
(writer: any) => {
|
||||||
|
const filePath = fileEntry.fullPath
|
||||||
|
writer.onwrite = function () { resolve(filePath) }
|
||||||
|
writer.onerror = function (e: any) { reject(e) }
|
||||||
|
try {
|
||||||
|
if (data) writer.writeAsBinary(data)
|
||||||
|
} catch (e) { reject(e) }
|
||||||
|
},
|
||||||
|
(err: any) => reject(err)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
(err: any) => reject(err)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
(err) => { reject(err) }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除临时文件
|
||||||
|
* @param dirPath 目录路径标识(plus.io.PRIVATE_DOC等)
|
||||||
|
* @param fileName 要删除的临时文件名
|
||||||
|
* @returns Promise<boolean> 删除是否成功
|
||||||
|
*/
|
||||||
|
deleteTempFile(dirPath: number, fileName: string): Promise<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
plus.io.requestFileSystem(
|
||||||
|
dirPath,
|
||||||
|
(dirEntry) => {
|
||||||
|
if (!dirEntry || !dirEntry.root) {
|
||||||
|
reject(new Error('Directory entry or root is undefined'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dirEntry.root.getFile(
|
||||||
|
fileName,
|
||||||
|
{ create: false },
|
||||||
|
(fileEntry) => {
|
||||||
|
fileEntry.remove(
|
||||||
|
() => { resolve(true); },
|
||||||
|
(err) => { resolve(true); }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
() => resolve(true)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
() => resolve(true)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取APP端文件分片的数据
|
||||||
|
* @param filePath 本地文件路径
|
||||||
|
* @param start 读取开始位置
|
||||||
|
* @param length 读取数据长度
|
||||||
|
* @returns Promise<string> Base64编码的分片数据
|
||||||
|
*/
|
||||||
|
readAppFileChunk(filePath: string, start: number, length: number): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
plus.io.resolveLocalFileSystemURL(
|
||||||
|
filePath,
|
||||||
|
(entry: any) => {
|
||||||
|
entry.file(
|
||||||
|
(file: any) => {
|
||||||
|
const reader = new plus.io.FileReader();
|
||||||
|
try {
|
||||||
|
const slice = file.slice(start, start + length);
|
||||||
|
reader.readAsDataURL(slice);
|
||||||
|
} catch (sliceError) {
|
||||||
|
reject(sliceError);
|
||||||
|
}
|
||||||
|
reader.onloadend = (e: any) => {
|
||||||
|
if (e.target.readyState == 2) {
|
||||||
|
try {
|
||||||
|
const base64 = e.target.result.split(",")[1];
|
||||||
|
resolve(base64);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.onerror = (err) => { reject(err); };
|
||||||
|
},
|
||||||
|
(error: any) => { reject(error); }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(error) => { reject(error); }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default new AppChunkUploader()
|
||||||
|
export { AppChunkUploader }
|
||||||
@ -1,202 +0,0 @@
|
|||||||
import { initChunkUpload, uploadChunk, completeChunkUpload } from '@/api/system/chunkUpload'
|
|
||||||
import modal from "@/plugins/modal";
|
|
||||||
import { getFileExtension } from "@/utils/fileOper";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 微信小程序分片上传工具类
|
|
||||||
*/
|
|
||||||
export class WxChunkUploader {
|
|
||||||
constructor(config = {}) {
|
|
||||||
this.chunkSize = config.chunkSize || 15 * 1024 * 1024; //默认分片大小,为 15MB
|
|
||||||
this.lastDisplayPercent = 0; //初始化上次显示的上传进度百分比为0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行分片上传
|
|
||||||
* @param {Object} options - 上传选项
|
|
||||||
* @param {Function} options.onSuccess - 成功回调
|
|
||||||
* @param {Function} options.onError - 错误回调
|
|
||||||
*/
|
|
||||||
async upload(options) {
|
|
||||||
const { file, onSuccess, onError } = options;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. 校验数据
|
|
||||||
this._validateParams(file);
|
|
||||||
|
|
||||||
// 2. 准备上传数据
|
|
||||||
modal.loading("准备上传...");
|
|
||||||
const uploadData = await this._prepareUploadData(file);
|
|
||||||
|
|
||||||
// 3. 执行分片上传
|
|
||||||
modal.closeLoading();
|
|
||||||
modal.loading("上传中...");
|
|
||||||
const partETags = await this._uploadChunks(uploadData);
|
|
||||||
|
|
||||||
// 4. 合并文件
|
|
||||||
modal.closeLoading();
|
|
||||||
modal.loading("合并文件中...");
|
|
||||||
|
|
||||||
//模仿上传的时间,可删除
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
||||||
|
|
||||||
await this._completeUpload(uploadData, partETags);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
onSuccess?.({ success: true });
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("分片上传失败:", error);
|
|
||||||
modal.closeLoading();
|
|
||||||
modal.msgError("上传失败");
|
|
||||||
onError?.(error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验参数
|
|
||||||
*/
|
|
||||||
_validateParams(file) {
|
|
||||||
if (!file.path) throw new Error("文件路径不存在");
|
|
||||||
if (!file.size) throw new Error("文件大小不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 准备上传数据
|
|
||||||
*/
|
|
||||||
async _prepareUploadData(file) {
|
|
||||||
const fileSize = file.size;
|
|
||||||
const tempFilePath = file.path;
|
|
||||||
const uploadFileName = `weixin_${Date.now()}.${getFileExtension(tempFilePath)}`;
|
|
||||||
const chunkCount = Math.ceil(fileSize / this.chunkSize);
|
|
||||||
|
|
||||||
console.log("分片数量:", chunkCount);
|
|
||||||
|
|
||||||
// 初始化分片上传
|
|
||||||
const initResult = await initChunkUpload(uploadFileName, fileSize);
|
|
||||||
if (initResult.code !== 200) throw new Error("初始化上传失败");
|
|
||||||
return {
|
|
||||||
uploadId: initResult.data.uploadId,
|
|
||||||
filePath: initResult.data.filePath,
|
|
||||||
uploadFileName: uploadFileName,
|
|
||||||
fileSize: fileSize,
|
|
||||||
chunkCount: chunkCount,
|
|
||||||
tempFilePath: tempFilePath,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传所有分片
|
|
||||||
*/
|
|
||||||
// return {
|
|
||||||
// uploadId: initResult.data.uploadId,
|
|
||||||
// filePath: initResult.data.filePath,
|
|
||||||
// fileName:initResult.data.fileName,
|
|
||||||
// fileSize:fileSize,
|
|
||||||
// chunkCount: chunkCount,
|
|
||||||
// };
|
|
||||||
|
|
||||||
async _uploadChunks(uploadData) {
|
|
||||||
const { uploadId, filePath, fileSize, chunkCount, tempFilePath } = uploadData;
|
|
||||||
const fileManager = uni.getFileSystemManager();
|
|
||||||
const partETags = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < chunkCount; i++) {
|
|
||||||
const start = i * this.chunkSize;
|
|
||||||
const end = Math.min(start + this.chunkSize, fileSize);
|
|
||||||
const tempChunkPath = `${wx.env.USER_DATA_PATH}/chunk_${i}.tmp`;
|
|
||||||
|
|
||||||
// 读取并写入分片
|
|
||||||
await this._processChunk(fileManager, tempFilePath, tempChunkPath, start, end - start);
|
|
||||||
|
|
||||||
// 上传分片
|
|
||||||
const response = await uploadChunk(uploadId, filePath, i, tempChunkPath);
|
|
||||||
|
|
||||||
if (response.data?.etag) {
|
|
||||||
partETags.push({
|
|
||||||
partNumber: i + 1,
|
|
||||||
ETag: response.data.etag,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 清理临时文件
|
|
||||||
this._cleanupTempFile(fileManager, tempChunkPath);
|
|
||||||
|
|
||||||
// 更新进度 - 确保完全执行完毕
|
|
||||||
this._updateProgress(i, chunkCount);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return partETags;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理单个分片
|
|
||||||
*/
|
|
||||||
async _processChunk(fileManager, tempFilePath, tempChunkPath, start, length) {
|
|
||||||
// 读取分片数据
|
|
||||||
const readRes = await new Promise((resolve, reject) => {
|
|
||||||
fileManager.readFile({
|
|
||||||
filePath: tempFilePath,
|
|
||||||
position: start,
|
|
||||||
length: length,
|
|
||||||
success: (res) => resolve(res.data),
|
|
||||||
fail: reject,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 写入临时文件
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
fileManager.writeFile({
|
|
||||||
filePath: tempChunkPath,
|
|
||||||
data: readRes,
|
|
||||||
success: resolve,
|
|
||||||
fail: reject,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理临时文件
|
|
||||||
*/
|
|
||||||
_cleanupTempFile(fileManager, tempChunkPath) {
|
|
||||||
try {
|
|
||||||
fileManager.unlinkSync(tempChunkPath);
|
|
||||||
console.log("删除临时文件成功:", tempChunkPath);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("删除临时文件错误:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新上传进度
|
|
||||||
*/
|
|
||||||
_updateProgress(currentIndex, totalCount) {
|
|
||||||
const percent = Math.floor(((currentIndex + 1) / totalCount) * 100);
|
|
||||||
const displayPercent = Math.floor(percent / 20) * 20;
|
|
||||||
if (displayPercent !== this.lastDisplayPercent || currentIndex === totalCount - 1) {
|
|
||||||
modal.closeLoading();
|
|
||||||
modal.loading(`上传中${displayPercent}%`);
|
|
||||||
this.lastDisplayPercent = displayPercent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 完成上传
|
|
||||||
*/
|
|
||||||
async _completeUpload(uploadData, partETags) {
|
|
||||||
const { uploadId, filePath, fileSize, uploadFileName } = uploadData;
|
|
||||||
await completeChunkUpload(uploadId, filePath, fileSize, uploadFileName, partETags);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
export const wxChunkUploader = new WxChunkUploader();
|
|
||||||
282
src/utils/ChunkUploaderWx.ts
Normal file
282
src/utils/ChunkUploaderWx.ts
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
import { initChunkUpload, uploadChunk, completeChunkUpload } from '@/api/system/chunkUpload'
|
||||||
|
import modal from "@/plugins/modal";
|
||||||
|
import { UploadOptions, File, UploadData, PartETag } from "@/types/upload";
|
||||||
|
|
||||||
|
// 声明微信小程序全局对象
|
||||||
|
declare const wx: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信小程序分片上传工具类
|
||||||
|
*
|
||||||
|
* 该类专门用于在微信小程序环境下处理大文件的分片上传功能
|
||||||
|
* 支持自定义分片大小、上传进度显示、错误处理等功能
|
||||||
|
*/
|
||||||
|
export class WxChunkUploader {
|
||||||
|
|
||||||
|
/** 分片大小,单位字节 */
|
||||||
|
private chunkSize: number;
|
||||||
|
|
||||||
|
/** 上次显示的上传进度百分比 */
|
||||||
|
private lastDisplayPercent: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
* @param config 配置对象
|
||||||
|
* @param config.chunkSize 分片大小(字节),默认15MB
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.chunkSize = 15 * 1024 * 1024; //默认分片大小,为 15MB
|
||||||
|
this.lastDisplayPercent = 0; //初始化上次显示的上传进度百分比为0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行分片上传的主方法
|
||||||
|
*
|
||||||
|
* @param options - 上传选项配置
|
||||||
|
* @param options.file - 要上传的文件对象
|
||||||
|
* @param options.onSuccess - 上传成功时的回调函数
|
||||||
|
* @param options.onError - 上传失败时的回调函数
|
||||||
|
* @returns Promise<boolean> - 上传是否成功
|
||||||
|
*/
|
||||||
|
async upload(options: UploadOptions): Promise<boolean> {
|
||||||
|
const { file, onSuccess, onError } = options;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 校验数据
|
||||||
|
this._validateParams(file);
|
||||||
|
|
||||||
|
// 2. 准备上传数据
|
||||||
|
modal.loading("准备上传...");
|
||||||
|
const uploadData = await this._prepareUploadData(file);
|
||||||
|
|
||||||
|
// 3. 执行分片上传
|
||||||
|
modal.closeLoading();
|
||||||
|
modal.loading("上传中...");
|
||||||
|
const partETags = await this._uploadChunks(uploadData);
|
||||||
|
|
||||||
|
// 4. 合并文件
|
||||||
|
modal.closeLoading();
|
||||||
|
modal.loading("合并文件中...");
|
||||||
|
|
||||||
|
//模仿上传的时间,可删除
|
||||||
|
// await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
|
|
||||||
|
await this._completeUpload(uploadData, partETags);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
modal.closeLoading();
|
||||||
|
onSuccess?.({ success: true });
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
modal.closeLoading();
|
||||||
|
const errorMessage = error instanceof Error ? error.message : '上传失败';
|
||||||
|
onError?.(errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 校验上传参数
|
||||||
|
*
|
||||||
|
* @param file - 要上传的文件对象
|
||||||
|
* @throws {Error} 当文件路径不存在时抛出错误
|
||||||
|
* @throws {Error} 当文件大小不存在时抛出错误
|
||||||
|
*/
|
||||||
|
_validateParams(file: File) {
|
||||||
|
if (!file.path) throw new Error("文件路径不存在");
|
||||||
|
if (!file.size) throw new Error("文件大小不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 准备上传数据
|
||||||
|
*
|
||||||
|
* @param file - 要上传的文件对象
|
||||||
|
* @returns Promise<UploadData> - 包含上传ID、文件路径、分片数量等信息的数据对象
|
||||||
|
* @throws {Error} 当初始化上传失败时抛出错误
|
||||||
|
*/
|
||||||
|
async _prepareUploadData(file: File) {
|
||||||
|
try {
|
||||||
|
const fileSize = file.size;
|
||||||
|
const filePath = file.path;
|
||||||
|
const uploadFileName = `weixin_${Date.now()}.${this.getFileExtension(filePath)}`;
|
||||||
|
const chunkCount = Math.ceil(fileSize / this.chunkSize);
|
||||||
|
|
||||||
|
// 初始化分片上传
|
||||||
|
const initResult = await initChunkUpload(uploadFileName, fileSize);
|
||||||
|
if (initResult.code !== 200) throw new Error("初始化上传失败");
|
||||||
|
|
||||||
|
return {
|
||||||
|
uploadId: initResult.data.uploadId,
|
||||||
|
saveFilePath: initResult.data.filePath,
|
||||||
|
uploadFileName: uploadFileName,
|
||||||
|
fileSize: fileSize,
|
||||||
|
chunkCount: chunkCount,
|
||||||
|
filePath: filePath,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : '准备上传数据失败';
|
||||||
|
throw new Error(`${errorMessage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行分片上传循环
|
||||||
|
*
|
||||||
|
* @param uploadData - 上传数据对象,包含上传ID、文件信息等
|
||||||
|
* @returns Promise<PartETag[]> - 返回所有分片的ETag信息数组
|
||||||
|
* @throws {Error} 当分片上传失败时抛出错误
|
||||||
|
*/
|
||||||
|
async _uploadChunks(uploadData: UploadData) {
|
||||||
|
try {
|
||||||
|
const { uploadId, saveFilePath, fileSize, chunkCount, filePath } = uploadData;
|
||||||
|
const fileManager = uni.getFileSystemManager();
|
||||||
|
const partETags: PartETag[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < chunkCount; i++) {
|
||||||
|
const start = i * this.chunkSize;
|
||||||
|
const end = Math.min(start + this.chunkSize, fileSize);
|
||||||
|
const tempChunkPath = `${wx.env.USER_DATA_PATH}/chunk_${i}.tmp`;
|
||||||
|
|
||||||
|
// 读取并写入分片
|
||||||
|
await this._processChunk(fileManager, filePath, tempChunkPath, start, end - start);
|
||||||
|
|
||||||
|
// 上传分片
|
||||||
|
const response = await uploadChunk(uploadId, saveFilePath, i, tempChunkPath);
|
||||||
|
|
||||||
|
if (response.data?.etag) {
|
||||||
|
partETags.push({
|
||||||
|
partNumber: i + 1,
|
||||||
|
ETag: response.data.etag,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 清理临时文件
|
||||||
|
this._cleanupTempFile(fileManager, tempChunkPath);
|
||||||
|
|
||||||
|
// 更新进度 - 确保完全执行完毕
|
||||||
|
this._updateProgress(i, chunkCount);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return partETags;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
const errorMessage = e instanceof Error ? e.message : '上传分片失败';
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理单个分片数据
|
||||||
|
*
|
||||||
|
* @param fileManager - uni-app文件系统管理器实例
|
||||||
|
* @param filePath - 原始文件的完整路径
|
||||||
|
* @param tempChunkPath - 临时分片文件的保存路径
|
||||||
|
* @param start - 在原始文件中的起始位置
|
||||||
|
* @param length - 要读取的数据长度
|
||||||
|
* @returns Promise<void> - 操作完成的Promise
|
||||||
|
* @throws {Error} 当文件读取或写入失败时抛出错误
|
||||||
|
*/
|
||||||
|
async _processChunk(fileManager: UniApp.FileSystemManager, filePath: string, tempChunkPath: string, start: number, length: number) {
|
||||||
|
const readRes = await new Promise<ArrayBuffer | string>((resolve, reject) => {
|
||||||
|
fileManager.readFile({
|
||||||
|
filePath: filePath,
|
||||||
|
position: start,
|
||||||
|
length: length,
|
||||||
|
success: (res: any) => resolve(res.data as ArrayBuffer | string),
|
||||||
|
fail: reject,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// 写入临时文件
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
fileManager.writeFile({
|
||||||
|
filePath: tempChunkPath,
|
||||||
|
data: readRes,
|
||||||
|
success: resolve,
|
||||||
|
fail: reject,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理临时分片文件
|
||||||
|
*
|
||||||
|
* @param fileManager - uni-app文件系统管理器实例
|
||||||
|
* @param tempChunkPath - 要删除的临时文件路径
|
||||||
|
* @throws {Error} 当文件删除失败时抛出错误
|
||||||
|
*/
|
||||||
|
_cleanupTempFile(fileManager: UniApp.FileSystemManager, tempChunkPath: string) {
|
||||||
|
try {
|
||||||
|
fileManager.unlinkSync(tempChunkPath);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("删除临时文件错误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新上传进度显示
|
||||||
|
*
|
||||||
|
* @param currentIndex - 当前完成的分片索引(从0开始)
|
||||||
|
* @param totalCount - 总分片数量
|
||||||
|
*/
|
||||||
|
_updateProgress(currentIndex: number, totalCount: number) {
|
||||||
|
const percent = Math.floor(((currentIndex + 1) / totalCount) * 100);
|
||||||
|
const displayPercent = Math.floor(percent / 20) * 20;
|
||||||
|
if (displayPercent !== this.lastDisplayPercent || currentIndex === totalCount - 1) {
|
||||||
|
modal.closeLoading();
|
||||||
|
modal.loading(`上传中${displayPercent}%`);
|
||||||
|
this.lastDisplayPercent = displayPercent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成分片上传并合并文件
|
||||||
|
*
|
||||||
|
* @param uploadData - 上传数据对象,包含上传ID等关键信息
|
||||||
|
* @param partETags - 所有分片的ETag信息数组,用于验证分片完整性
|
||||||
|
* @returns Promise<void> - 合并操作完成的Promise
|
||||||
|
* @throws {Error} 当文件合并失败时抛出错误
|
||||||
|
*/
|
||||||
|
async _completeUpload(uploadData: UploadData, partETags: PartETag[]) {
|
||||||
|
try {
|
||||||
|
const { uploadId, saveFilePath, fileSize, uploadFileName } = uploadData;
|
||||||
|
await completeChunkUpload(uploadId, saveFilePath, fileSize, uploadFileName, partETags);
|
||||||
|
} catch (e) {
|
||||||
|
const errorMessage = e instanceof Error ? e.message : '上传失败';
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件扩展名
|
||||||
|
*
|
||||||
|
* @param filePath - 文件的完整路径
|
||||||
|
* @returns string - 文件扩展名(不包含点号,如:'jpg', 'mp4', 'pdf')
|
||||||
|
* @example
|
||||||
|
* getFileExtension('/path/to/video.mp4') // 返回 'mp4'
|
||||||
|
* getFileExtension('/path/to/image.JPG') // 返回 'jpg'
|
||||||
|
* getFileExtension('/path/to/file') // 返回 ''
|
||||||
|
*/
|
||||||
|
getFileExtension(filePath: string): string {
|
||||||
|
if (!filePath) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// 查找最后一个点号位置
|
||||||
|
const dotIndex = filePath.lastIndexOf(".");
|
||||||
|
if (dotIndex === -1) {
|
||||||
|
return ""; // 没有找到扩展名
|
||||||
|
}
|
||||||
|
// 从点号后面提取扩展名
|
||||||
|
return filePath.substring(dotIndex + 1).toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const wxChunkUploader = new WxChunkUploader();
|
||||||
@ -1,278 +0,0 @@
|
|||||||
|
|
||||||
/**
|
|
||||||
* 将文件复制到应用沙盒目录
|
|
||||||
* @param {*} srcUrl
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const copyFileToSandbox = (srcUrl) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const newName = `file_${Date.now()}.${getFileExtension(srcUrl)}`;
|
|
||||||
console.log("文件名称是什么>>>", newName);
|
|
||||||
plus.io.requestFileSystem(
|
|
||||||
plus.io.PRIVATE_DOC,
|
|
||||||
function (dstEntry) {
|
|
||||||
plus.io.resolveLocalFileSystemURL(
|
|
||||||
srcUrl,
|
|
||||||
function (srcEntry) {
|
|
||||||
srcEntry.copyTo(
|
|
||||||
dstEntry.root,
|
|
||||||
newName,
|
|
||||||
function (entry) {
|
|
||||||
console.log("文件复制成功:", entry.fullPath);
|
|
||||||
resolve(entry.fullPath);
|
|
||||||
},
|
|
||||||
function (e) {
|
|
||||||
console.error("复制文件失败:", JSON.stringify(e));
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function (e) {
|
|
||||||
console.error("获取目标目录失败:", JSON.stringify(e));
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function (e) {
|
|
||||||
console.error("获取源文件失败:", JSON.stringify(e));
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除临时文件
|
|
||||||
* @param {string} dirPath - 目录路径
|
|
||||||
* @param {string} fileName - 文件名
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
export function deleteTempFile(dirPath, fileName) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
plus.io.requestFileSystem(
|
|
||||||
dirPath,
|
|
||||||
(dirEntry) => {
|
|
||||||
console.log("文件目录:", dirPath);
|
|
||||||
console.log("目录存在:", dirEntry);
|
|
||||||
dirEntry.root.getFile(
|
|
||||||
fileName,
|
|
||||||
{ create: false },
|
|
||||||
(fileEntry) => {
|
|
||||||
console.log("临时文件存在:", fileEntry);
|
|
||||||
fileEntry.remove(
|
|
||||||
() => {
|
|
||||||
console.log("删除成功XXXXX");
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.error("删除失败XXXXX:", err);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
() => resolve()
|
|
||||||
);
|
|
||||||
},
|
|
||||||
() => resolve()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除本地临时文件(临时文件是分片生成的)
|
|
||||||
* @param {*} filePath
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const deleteLocalFile = (filePath) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (!filePath) {
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("准备删除文件:", filePath);
|
|
||||||
plus.io.resolveLocalFileSystemURL(
|
|
||||||
filePath,
|
|
||||||
(entry) => {
|
|
||||||
entry.remove(
|
|
||||||
() => {
|
|
||||||
console.log("文件删除成功:", filePath);
|
|
||||||
resolve(true);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.error("删除文件失败:", JSON.stringify(error));
|
|
||||||
// 失败也视为完成,不中断流程
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.error("获取文件引用失败:", JSON.stringify(error));
|
|
||||||
// 失败也视为完成,不中断流程
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据文件路径获取APP文件信息
|
|
||||||
*/
|
|
||||||
export const getAppFileInfo = (filePath) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
plus.io.resolveLocalFileSystemURL(
|
|
||||||
filePath,
|
|
||||||
(entry) => {
|
|
||||||
entry.file(
|
|
||||||
(file) => {
|
|
||||||
resolve({
|
|
||||||
size: file.size,
|
|
||||||
name: file.name,
|
|
||||||
type: file.type,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*读取分片的数据 */
|
|
||||||
export const readAppFileChunk = (filePath, start, length) => {
|
|
||||||
console.log("读取分片的路径是什么>>>", filePath);
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
plus.io.resolveLocalFileSystemURL(
|
|
||||||
filePath,
|
|
||||||
(entry) => {
|
|
||||||
entry.file(
|
|
||||||
(file) => {
|
|
||||||
const reader = new plus.io.FileReader();
|
|
||||||
try {
|
|
||||||
const slice = file.slice(start, start + length);
|
|
||||||
reader.readAsDataURL(slice);
|
|
||||||
} catch (sliceError) {
|
|
||||||
reject(sliceError);
|
|
||||||
}
|
|
||||||
reader.onloadend = (e) => {
|
|
||||||
if (e.target.readyState == 2) {
|
|
||||||
try {
|
|
||||||
const base64 = e.target.result.split(",")[1];
|
|
||||||
resolve(base64);
|
|
||||||
} catch (err) {
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.onerror = (err) => {
|
|
||||||
reject(err);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
reject
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取文章的扩展名称
|
|
||||||
* @param {*} filePath
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const getFileExtension = (filePath) => {
|
|
||||||
if (!filePath) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
// 查找最后一个点号位置
|
|
||||||
const dotIndex = filePath.lastIndexOf(".");
|
|
||||||
if (dotIndex === -1) {
|
|
||||||
return ""; // 没有找到扩展名
|
|
||||||
}
|
|
||||||
// 从点号后面提取扩展名
|
|
||||||
return filePath.substring(dotIndex + 1).toLowerCase();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取文件名称
|
|
||||||
* @param {*} filePath
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const getFileName = (filePath) => {
|
|
||||||
if (!filePath) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
// 查找最后一个斜杠位置
|
|
||||||
const slashIndex = filePath.lastIndexOf("/");
|
|
||||||
if (slashIndex === -1) {
|
|
||||||
return filePath; // 没有斜杠,整个字符串可能就是文件名
|
|
||||||
}
|
|
||||||
// 从最后一个斜杠后面提取文件名
|
|
||||||
return filePath.substring(slashIndex + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建临时文件并写入数据
|
|
||||||
* @param {string} dirPath - 目录路径
|
|
||||||
* @param {string} fileName - 文件名
|
|
||||||
* @param {ArrayBuffer} data - 要写入的数据
|
|
||||||
* @returns {Promise<string>} 临时文件的完整路径
|
|
||||||
*/
|
|
||||||
export const createAndWriteTempFile = (dirPath, fileName, data) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
plus.io.requestFileSystem(
|
|
||||||
dirPath,
|
|
||||||
(dirEntry) => {
|
|
||||||
dirEntry.root.getFile(
|
|
||||||
fileName,
|
|
||||||
{ create: true, exclusive: false },
|
|
||||||
(fileEntry) => {
|
|
||||||
fileEntry.createWriter(
|
|
||||||
(writer) => {
|
|
||||||
const filePath = fileEntry.fullPath
|
|
||||||
// 设置写入成功回调
|
|
||||||
writer.onwrite = function () {
|
|
||||||
resolve(filePath)
|
|
||||||
}
|
|
||||||
// 设置写入失败回调
|
|
||||||
writer.onerror = function (e) {
|
|
||||||
reject(e)
|
|
||||||
}
|
|
||||||
// 写入数据
|
|
||||||
try {
|
|
||||||
if (data) {
|
|
||||||
writer.writeAsBinary(data)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
reject(e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err) => reject(err)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
(err) => reject(err)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user