Merge branch 'master' of https://git.yunzer.cn/yunzerwebsite/platform-vue
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
import request from '@/utils/request';
|
||||
import * as qiniu from 'qiniu-js';
|
||||
|
||||
/**
|
||||
* 获取存储配置
|
||||
* @returns {Promise<{storageType: string, qiniuDomain?: string, qiniuRegion?: string}>}
|
||||
*/
|
||||
export async function getStorageConfig() {
|
||||
const res = await request({
|
||||
url: '/platform/storage/config',
|
||||
method: 'get',
|
||||
});
|
||||
if (res?.code === 200) {
|
||||
return res.data || { storageType: 'local' };
|
||||
}
|
||||
return { storageType: 'local' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取七牛云上传凭证
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export async function getQiniuToken() {
|
||||
return request({
|
||||
url: '/platform/qiniu/token',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文件记录到数据库
|
||||
* @param {Object} data 文件信息
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export async function saveFileRecord(data) {
|
||||
return request({
|
||||
url: '/platform/qiniu/save',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件(自动选择本地或七牛云)
|
||||
* @param {File} file 文件对象
|
||||
* @param {Object} options 配置选项
|
||||
* @param {number} [options.cate] 文件分类
|
||||
* @param {Function} [options.onProgress] 进度回调
|
||||
* @returns {Promise<{url: string, id: number, name: string, key?: string}>}
|
||||
*/
|
||||
export async function smartUpload(file, options = {}) {
|
||||
// 获取存储配置
|
||||
const config = await getStorageConfig();
|
||||
|
||||
if (config.storageType === 'qiniu') {
|
||||
// 使用七牛云直传
|
||||
return uploadToQiniu(file, options);
|
||||
} else {
|
||||
// 使用本地上传(通过后端)
|
||||
return uploadToLocal(file, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传到七牛云(直传)
|
||||
* @param {File} file 文件对象
|
||||
* @param {Object} options 配置选项
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export async function uploadToQiniu(file, options = {}) {
|
||||
// 1. 获取上传凭证
|
||||
const tokenRes = await getQiniuToken();
|
||||
if (tokenRes?.code !== 200) {
|
||||
throw new Error(tokenRes?.msg || '获取上传凭证失败');
|
||||
}
|
||||
|
||||
const { token, keyPrefix, domain, region, uploadUrl } = tokenRes.data;
|
||||
|
||||
// 2. 生成文件 key
|
||||
const ext = file.name.split('.').pop();
|
||||
const key = `${keyPrefix}.${ext}`;
|
||||
|
||||
// 3. 配置上传参数
|
||||
const putExtra = {
|
||||
fname: file.name,
|
||||
mimeType: file.type || 'application/octet-stream',
|
||||
};
|
||||
|
||||
// 4. 根据区域代码获取七牛云区域对象
|
||||
const qiniuRegion = getQiniuRegion(region);
|
||||
|
||||
const config = {
|
||||
useCdnDomain: true,
|
||||
region: qiniuRegion,
|
||||
};
|
||||
|
||||
// 5. 创建 observable 对象
|
||||
const observable = qiniu.upload(file, key, token, putExtra, config);
|
||||
|
||||
// 5. 执行上传
|
||||
return new Promise((resolve, reject) => {
|
||||
const subscription = observable.subscribe({
|
||||
next(res) {
|
||||
// 进度回调
|
||||
if (options.onProgress) {
|
||||
options.onProgress({
|
||||
loaded: res.total.loaded,
|
||||
total: res.total.size,
|
||||
percent: res.total.percent,
|
||||
});
|
||||
}
|
||||
},
|
||||
error(err) {
|
||||
reject(new Error(err.message || '上传失败'));
|
||||
},
|
||||
async complete(res) {
|
||||
try {
|
||||
// 6. 保存文件记录到数据库
|
||||
const saveRes = await saveFileRecord({
|
||||
key: res.key,
|
||||
hash: res.hash,
|
||||
size: file.size,
|
||||
name: file.name,
|
||||
mimeType: file.type,
|
||||
cate: options.cate || 0,
|
||||
});
|
||||
|
||||
if (saveRes?.code === 200 || saveRes?.code === 201) {
|
||||
resolve({
|
||||
url: saveRes.data.url,
|
||||
id: saveRes.data.id,
|
||||
name: saveRes.data.name,
|
||||
key: saveRes.data.key,
|
||||
});
|
||||
} else {
|
||||
reject(new Error(saveRes?.msg || '保存文件记录失败'));
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传到本地(通过后端中转)
|
||||
* @param {File} file 文件对象
|
||||
* @param {Object} options 配置选项
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export async function uploadToLocal(file, options = {}) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
if (options.cate !== undefined) {
|
||||
formData.append('cate', String(options.cate));
|
||||
}
|
||||
|
||||
const config = {
|
||||
url: '/platform/uploadfile',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
timeout: 0, // 不设置超时
|
||||
};
|
||||
|
||||
if (options.onProgress) {
|
||||
config.onUploadProgress = (e) => {
|
||||
options.onProgress({
|
||||
loaded: e.loaded,
|
||||
total: e.total || 0,
|
||||
percent: e.total > 0 ? Math.round((e.loaded * 100) / e.total) : 0,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const res = await request(config);
|
||||
|
||||
if (res?.code === 200 || res?.code === 201) {
|
||||
return {
|
||||
url: res.data.url,
|
||||
id: res.data.id,
|
||||
name: res.data.name,
|
||||
};
|
||||
} else {
|
||||
throw new Error(res?.msg || '上传失败');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量上传文件
|
||||
* @param {File[]} files 文件数组
|
||||
* @param {Object} options 配置选项
|
||||
* @param {Function} [options.onFileProgress] 单个文件进度回调 (file, progress) => void
|
||||
* @param {Function} [options.onFileComplete] 单个文件完成回调 (file, result) => void
|
||||
* @param {Function} [options.onFileError] 单个文件错误回调 (file, error) => void
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
export async function batchUpload(files, options = {}) {
|
||||
const results = [];
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const result = await smartUpload(file, {
|
||||
...options,
|
||||
onProgress: (progress) => {
|
||||
if (options.onFileProgress) {
|
||||
options.onFileProgress(file, progress);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
results.push({ file, result, success: true });
|
||||
|
||||
if (options.onFileComplete) {
|
||||
options.onFileComplete(file, result);
|
||||
}
|
||||
} catch (error) {
|
||||
results.push({ file, error, success: false });
|
||||
|
||||
if (options.onFileError) {
|
||||
options.onFileError(file, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据区域代码获取七牛云区域对象
|
||||
* @param {string} regionCode 区域代码 (z0, z1, z2, na0, as0, cn-east-2)
|
||||
* @returns {Object} 七牛云区域对象
|
||||
*/
|
||||
function getQiniuRegion(regionCode) {
|
||||
switch (regionCode) {
|
||||
case 'z0':
|
||||
return qiniu.region.z0; // 华东
|
||||
case 'z1':
|
||||
return qiniu.region.z1; // 华北
|
||||
case 'z2':
|
||||
return qiniu.region.z2; // 华南
|
||||
case 'na0':
|
||||
return qiniu.region.na0; // 北美
|
||||
case 'as0':
|
||||
return qiniu.region.as0; // 新加坡
|
||||
case 'cn-east-2':
|
||||
return qiniu.region.cnEast2; // 华东-浙江2
|
||||
default:
|
||||
return qiniu.region.z0; // 默认华东
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,8 @@ import {
|
||||
createSoftwareUpgrade,
|
||||
updateSoftwareUpgrade,
|
||||
} from "@/api/softwareUpgrade";
|
||||
import { getUserCate, createFileCate, uploadFile, getFileById } from "@/api/file";
|
||||
import { getUserCate, createFileCate, getFileById } from "@/api/file";
|
||||
import { smartUpload } from "@/utils/qiniuUpload";
|
||||
|
||||
const emit = defineEmits(["saved"]);
|
||||
|
||||
@@ -262,8 +263,7 @@ async function handlePackageUpload(options) {
|
||||
options.onError?.(new Error("no category"));
|
||||
return;
|
||||
}
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
|
||||
uploadXHRActive.value = true;
|
||||
uploadPercent.value = 0;
|
||||
uploadIndeterminate.value = false;
|
||||
@@ -273,20 +273,24 @@ async function handlePackageUpload(options) {
|
||||
let lastLoaded = 0;
|
||||
let lastTick = xhrStart;
|
||||
let emaBps = 0;
|
||||
const res = await uploadFile(fd, {
|
||||
|
||||
// 使用智能上传(自动选择本地或七牛云)
|
||||
const result = await smartUpload(file, {
|
||||
cate: cateId,
|
||||
onUploadProgress: (e) => {
|
||||
onProgress: (progress) => {
|
||||
const now = Date.now();
|
||||
const loaded = e.loaded;
|
||||
const total = e.total ?? 0;
|
||||
const loaded = progress.loaded;
|
||||
const total = progress.total || 0;
|
||||
const elapsedSec = (now - xhrStart) / 1000;
|
||||
const dt = (now - lastTick) / 1000;
|
||||
|
||||
if (dt >= 0.07 && loaded >= lastLoaded) {
|
||||
const inst = (loaded - lastLoaded) / dt;
|
||||
emaBps = emaBps > 0 ? emaBps * 0.72 + inst * 0.28 : inst;
|
||||
lastLoaded = loaded;
|
||||
lastTick = now;
|
||||
}
|
||||
|
||||
const avgBps = elapsedSec > 0.12 ? loaded / elapsedSec : 0;
|
||||
const showBps = emaBps > 0 ? emaBps : avgBps;
|
||||
uploadSpeedText.value = showBps > 0 ? formatSpeed(showBps) : elapsedSec > 0.05 ? formatSpeed(avgBps) : "—";
|
||||
@@ -301,19 +305,14 @@ async function handlePackageUpload(options) {
|
||||
}
|
||||
},
|
||||
});
|
||||
if (res?.code === 200 || res?.code === 201) {
|
||||
const d = res.data || {};
|
||||
form.fileId = d.id != null ? Number(d.id) : null;
|
||||
const src = d.url || "";
|
||||
form.downloadUrl = absoluteFromSrc(src);
|
||||
uploadedLabel.value = d.name || file.name || "安装包";
|
||||
displayFileList.value = [{ name: uploadedLabel.value, uid: `pkg-${form.fileId}-${Date.now()}` }];
|
||||
ElMessage.success(res.code === 201 ? "文件已存在,已关联" : "上传成功");
|
||||
options.onSuccess?.(res);
|
||||
} else {
|
||||
ElMessage.error(res?.msg || "上传失败");
|
||||
options.onError?.(new Error(res?.msg));
|
||||
}
|
||||
|
||||
// 上传成功
|
||||
form.fileId = result.id;
|
||||
form.downloadUrl = result.url;
|
||||
uploadedLabel.value = result.name || file.name || "安装包";
|
||||
displayFileList.value = [{ name: uploadedLabel.value, uid: `pkg-${form.fileId}-${Date.now()}` }];
|
||||
ElMessage.success("上传成功");
|
||||
options.onSuccess?.(result);
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.message || "上传失败");
|
||||
options.onError?.(e);
|
||||
|
||||
Reference in New Issue
Block a user