From d9d2ab05185a5a34f517f9ccf989d04f8c5f22ca Mon Sep 17 00:00:00 2001
From: liuBingWei <3134058912@qq.com>
Date: Sun, 31 Aug 2025 15:28:16 +0800
Subject: [PATCH 1/2] =?UTF-8?q?feat(pages):=20=E6=B7=BB=E5=8A=A0=E5=88=86?=
=?UTF-8?q?=E7=89=87=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在 pages.json 中新增 upload/index 页面
- 在 template.config.js 中添加分片上传的配置项
---
src/api/system/chunkUpload/index.js | 89 ++++++
.../geek-confirm-dialog.vue | 123 ++++++++
.../geek-uploadbox/geek-uploadbox.vue | 271 ++++++++++++++++
src/pages.json | 3 +
src/pages/template.config.js | 8 +-
src/pages_geek/pages/upload/index.vue | 155 ++++++++++
src/utils/ChunkUploaderApp.js | 288 ++++++++++++++++++
src/utils/ChunkUploaderWx.js | 202 ++++++++++++
src/utils/fileOper.js | 278 +++++++++++++++++
9 files changed, 1416 insertions(+), 1 deletion(-)
create mode 100644 src/api/system/chunkUpload/index.js
create mode 100644 src/components/geek-xd/components/geek-confirm-dialog/geek-confirm-dialog.vue
create mode 100644 src/components/geek-xd/components/geek-uploadbox/geek-uploadbox.vue
create mode 100644 src/pages_geek/pages/upload/index.vue
create mode 100644 src/utils/ChunkUploaderApp.js
create mode 100644 src/utils/ChunkUploaderWx.js
create mode 100644 src/utils/fileOper.js
diff --git a/src/api/system/chunkUpload/index.js b/src/api/system/chunkUpload/index.js
new file mode 100644
index 0000000..a61257f
--- /dev/null
+++ b/src/api/system/chunkUpload/index.js
@@ -0,0 +1,89 @@
+import request from '@/utils/request'
+import config from "@/config";
+import { getToken } from "@/utils/auth";
+
+
+
+
+
+
+/**初始化上传 */
+export function initChunkUpload(fileName, fileSize) {
+ return request({
+ url: '/file/initUpload',
+ method: 'post',
+ params: {
+ fileName,
+ fileSize
+ }
+ })
+}
+
+
+/**上传分片视频 */
+export function uploadChunk(uploadId, filePath, chunkIndex, formattedPath) {
+ return new Promise((resolve, reject) => {
+ uni.uploadFile({
+ url: `${config.baseUrl}/file/uploadChunk`,
+ filePath: formattedPath,
+ name: "chunk",
+ timeout: 60000, // 增加超时时间到60秒
+ header: {
+ Authorization: `Bearer ${getToken()}`,
+ },
+ formData: {
+ uploadId: uploadId,
+ filePath: filePath,
+ chunkIndex: chunkIndex,
+ },
+ success: (res) => {
+ try {
+ const resultData = JSON.parse(res.data);
+ resolve(resultData);
+ } catch (error) {
+ console.error("解析上传结果失败:", error);
+ reject(error);
+ }
+ },
+ fail: (err) => {
+ console.error(`分片${chunkIndex}上传请求失败:`, err);
+ reject(err);
+ },
+ });
+ });
+}
+
+
+/**完成分片上传 */
+export function completeChunkUpload(uploadId, filePath, fileSize, fileName, partETags) {
+ return request({
+ url: '/file/completeUpload',
+ method: 'post',
+ params: {
+ uploadId,
+ filePath,
+ fileSize,
+ fileName,
+ },
+ data: partETags
+ })
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/geek-xd/components/geek-confirm-dialog/geek-confirm-dialog.vue b/src/components/geek-xd/components/geek-confirm-dialog/geek-confirm-dialog.vue
new file mode 100644
index 0000000..aafb105
--- /dev/null
+++ b/src/components/geek-xd/components/geek-confirm-dialog/geek-confirm-dialog.vue
@@ -0,0 +1,123 @@
+
+
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/geek-xd/components/geek-uploadbox/geek-uploadbox.vue b/src/components/geek-xd/components/geek-uploadbox/geek-uploadbox.vue
new file mode 100644
index 0000000..2cebba7
--- /dev/null
+++ b/src/components/geek-xd/components/geek-uploadbox/geek-uploadbox.vue
@@ -0,0 +1,271 @@
+
+
+
+
+
+ {{ uploadText }}
+ {{ displayUploadDesc }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages.json b/src/pages.json
index 0e977d5..8248469 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -236,6 +236,9 @@
},
{
"path": "code/index"
+ },
+ {
+ "path": "upload/index"
}
]
}
diff --git a/src/pages/template.config.js b/src/pages/template.config.js
index 1e606a4..2e582d6 100644
--- a/src/pages/template.config.js
+++ b/src/pages/template.config.js
@@ -14,7 +14,13 @@ export default [
icon: 'wxCenter',
title: '二维码',
title_en: 'index',
- }
+ },
+ {
+ path: '/pages_geek/pages/upload/index',
+ icon: 'wxCenter',
+ title: '分片上传',
+ title_en: 'index',
+ },
]
},
{
diff --git a/src/pages_geek/pages/upload/index.vue b/src/pages_geek/pages/upload/index.vue
new file mode 100644
index 0000000..9180a8d
--- /dev/null
+++ b/src/pages_geek/pages/upload/index.vue
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/utils/ChunkUploaderApp.js b/src/utils/ChunkUploaderApp.js
new file mode 100644
index 0000000..5f18afb
--- /dev/null
+++ b/src/utils/ChunkUploaderApp.js
@@ -0,0 +1,288 @@
+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} 上传结果数组
+ */
+ 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} 上传结果
+ */
+ 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 }
diff --git a/src/utils/ChunkUploaderWx.js b/src/utils/ChunkUploaderWx.js
new file mode 100644
index 0000000..35fe233
--- /dev/null
+++ b/src/utils/ChunkUploaderWx.js
@@ -0,0 +1,202 @@
+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();
\ No newline at end of file
diff --git a/src/utils/fileOper.js b/src/utils/fileOper.js
new file mode 100644
index 0000000..f68c6b5
--- /dev/null
+++ b/src/utils/fileOper.js
@@ -0,0 +1,278 @@
+
+/**
+ * 将文件复制到应用沙盒目录
+ * @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}
+ */
+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} 临时文件的完整路径
+ */
+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)
+ }
+ )
+ })
+}
\ No newline at end of file
--
Gitee
From adf17d57fcfb25ee38b5c58b4485161bc59196d2 Mon Sep 17 00:00:00 2001
From: liuBingWei <3134058912@qq.com>
Date: Tue, 2 Sep 2025 20:02:52 +0800
Subject: [PATCH 2/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0ts=E7=B1=BB=E5=9E=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package.json | 2 +
.../geek-uploadbox/geek-uploadbox.vue | 3 +-
src/pages_geek/pages/upload/index.vue | 29 +-
src/types/upload.ts | 47 ++
src/utils/ChunkUploaderApp.js | 288 -----------
src/utils/ChunkUploaderApp.ts | 479 ++++++++++++++++++
src/utils/ChunkUploaderWx.js | 202 --------
src/utils/ChunkUploaderWx.ts | 282 +++++++++++
src/utils/fileOper.js | 278 ----------
9 files changed, 822 insertions(+), 788 deletions(-)
create mode 100644 src/types/upload.ts
delete mode 100644 src/utils/ChunkUploaderApp.js
create mode 100644 src/utils/ChunkUploaderApp.ts
delete mode 100644 src/utils/ChunkUploaderWx.js
create mode 100644 src/utils/ChunkUploaderWx.ts
delete mode 100644 src/utils/fileOper.js
diff --git a/package.json b/package.json
index 17f4887..b9059f9 100644
--- a/package.json
+++ b/package.json
@@ -87,9 +87,11 @@
"@dcloudio/uni-cli-shared": "3.0.0-4060420250429001",
"@dcloudio/uni-stacktracey": "3.0.0-4060420250429001",
"@dcloudio/vite-plugin-uni": "3.0.0-4060420250429001",
+ "@types/html5plus": "^1.0.5",
"@vue/runtime-core": "^3.5.12",
"@vue/tsconfig": "^0.5.1",
"less": "^4.2.0",
+ "miniprogram-api-typings": "^4.1.0",
"sass": "1.78.0",
"sass-loader": "^16.0.1",
"typescript": "^5.6.2",
diff --git a/src/components/geek-xd/components/geek-uploadbox/geek-uploadbox.vue b/src/components/geek-xd/components/geek-uploadbox/geek-uploadbox.vue
index 2cebba7..6d4a58b 100644
--- a/src/components/geek-xd/components/geek-uploadbox/geek-uploadbox.vue
+++ b/src/components/geek-xd/components/geek-uploadbox/geek-uploadbox.vue
@@ -151,8 +151,7 @@ const buildVideoData = (res) => {
// #ifdef MP-WEIXIN
videoData = {
path: res.tempFilePath,
- value: res.tempFilePath,
- ...res,
+ size:res.size
}
// #endif
diff --git a/src/pages_geek/pages/upload/index.vue b/src/pages_geek/pages/upload/index.vue
index 9180a8d..cc46881 100644
--- a/src/pages_geek/pages/upload/index.vue
+++ b/src/pages_geek/pages/upload/index.vue
@@ -14,7 +14,7 @@