v1.2.1版本,优化调整了很多,整改验收阶段新加字段

This commit is contained in:
王利强
2026-06-13 08:50:51 +08:00
parent 2af9f1fd59
commit 1fe87ec438
591 changed files with 5072 additions and 2706 deletions

300
utils/upload.js Normal file
View File

@@ -0,0 +1,300 @@
/**
* 统一文件上传:七牛云客户端直传
* 流程:计算 MD5 + 后缀 → 向后端取凭证 → uni.uploadFile 直传七牛 → 返回完整 CDN URL
*/
import { toImageUrl } from '@/request/request.js';
import { getQiniuUploadToken } from '@/request/api.js';
import { addTimestampWatermark } from '@/utils/watermark.js';
const DEFAULT_QINIU_UPLOAD_URL = 'https://upload.qiniup.com';
/** 从本地临时路径解析后缀(不含点) */
export function getFileSuffix(filePath) {
if (!filePath) return '';
const clean = String(filePath).split('?')[0];
const ext = clean.split('.').pop()?.toLowerCase() || '';
// 微信部分临时文件无后缀,按图片处理
if (!ext || ext.length > 8 || clean.endsWith(ext) === false) {
return 'jpg';
}
return ext;
}
/** 计算文件 MD5微信小程序支持其他端回退为空字符串需后端兼容 */
export function getFileMd5(filePath) {
return new Promise((resolve) => {
// #ifdef MP-WEIXIN
uni.getFileInfo({
filePath,
digestAlgorithm: 'md5',
success: (res) => resolve((res.digest || '').toLowerCase()),
fail: (err) => {
console.warn('getFileMd5 fail, use empty:', err);
resolve('');
}
});
// #endif
// #ifndef MP-WEIXIN
resolve('');
// #endif
});
}
/** 向后端获取七牛直传凭证 */
export async function fetchQiniuUploadCredential(filePath) {
const suffix = getFileSuffix(filePath);
const fileMd5 = await getFileMd5(filePath);
const res = await getQiniuUploadToken({ fileMd5, suffix });
return normalizeQiniuCredential(res.data);
}
/**
* 解析后端 /frontend/attachment/qiniu/token 返回的 data
* 当前约定token、key、uploadUrl、url完整 CDN 地址)
* 例uploadUrl=https://upload-z2.qiniup.comurl=https://oss.hexieyun.com.cn/uploads/...
*/
function normalizeQiniuCredential(raw) {
if (!raw) {
throw new Error('七牛凭证为空');
}
const token = raw.token || raw.uploadToken || raw.uptoken;
const key = raw.key || raw.fileKey || raw.objectKey;
const uploadUrl = (raw.uploadUrl || raw.uploadHost || raw.host || DEFAULT_QINIU_UPLOAD_URL).replace(/\/$/, '');
const presetUrl = String(raw.url || raw.fileUrl || raw.fullUrl || '').trim();
let cdnOrigin = (raw.domain || raw.cdnDomain || '').replace(/\/$/, '');
if (!cdnOrigin && presetUrl) {
const m = presetUrl.match(/^(https?:\/\/[^/]+)/i);
if (m) cdnOrigin = m[1];
}
if (cdnOrigin && !cdnOrigin.startsWith('http')) {
cdnOrigin = `https://${cdnOrigin}`;
}
if (!token || !key) {
throw new Error('七牛凭证缺少 token 或 key');
}
return { token, key, uploadUrl, cdnOrigin, presetUrl };
}
/** 七牛 upload 成功后的 JSON{ hash, key },结合凭证得到最终访问 URL */
function resolveUploadedFileUrl(credential, uploadResData) {
if (credential.presetUrl) {
return credential.presetUrl;
}
let objectKey = credential.key;
if (uploadResData) {
try {
const body =
typeof uploadResData === 'string' ? JSON.parse(uploadResData) : uploadResData;
if (body?.key) {
objectKey = body.key;
}
} catch (e) {
// 非 JSON 时沿用凭证里的 key
}
}
if (credential.cdnOrigin && objectKey) {
return `${credential.cdnOrigin}/${String(objectKey).replace(/^\//, '')}`;
}
return buildQiniuFileUrl(credential.cdnOrigin, objectKey);
}
/** 拼接七牛文件完整访问地址 */
export function buildQiniuFileUrl(domain, key) {
if (!domain || !key) return '';
const k = String(key).replace(/^\//, '');
const d = String(domain).replace(/\/$/, '');
if (d.startsWith('http://') || d.startsWith('https://')) {
return `${d}/${k}`;
}
return `https://${d}/${k}`;
}
/**
* 上传单个文件到七牛云
* @param {string} filePath 本地临时路径
* @param {Object} [options]
* @param {Function} [options.beforeUpload] 上传前处理(如水印),返回新的本地路径
* @returns {Promise<{url:string,key:string,filePath:string,serverPath:string}>}
*/
export async function uploadToCloud(filePath, options = {}) {
let localPath = filePath;
if (options.beforeUpload) {
localPath = await options.beforeUpload(filePath);
}
const credential = await fetchQiniuUploadCredential(localPath);
const { token, key, uploadUrl } = credential;
return new Promise((resolve, reject) => {
uni.uploadFile({
url: uploadUrl,
filePath: localPath,
name: 'file',
formData: {
token,
key
},
success: (res) => {
if (res.statusCode && res.statusCode >= 400) {
reject(new Error(`七牛上传失败(${res.statusCode})`));
return;
}
const fullUrl = resolveUploadedFileUrl(credential, res.data);
if (!fullUrl) {
reject(new Error('无法解析上传后的文件地址,请检查后端 url 或 CDN 配置'));
return;
}
let respKey = key;
try {
const body =
typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
if (body?.key) respKey = body.key;
} catch (e) {}
resolve({
url: fullUrl,
key: respKey,
filePath: fullUrl,
serverPath: fullUrl
});
},
fail: (err) => {
console.error('七牛上传失败:', err);
reject(err);
}
});
});
}
/**
* 表单提交用:统一为完整 URL兼容历史相对路径
*/
export function toSubmitFileUrl(filePath) {
if (!filePath) return '';
const p = String(filePath);
if (p.startsWith('http://') || p.startsWith('https://')) {
return p;
}
return toImageUrl(p);
}
/** 将后端附件记录转为 up-upload 列表项(编辑回显) */
export function mapServerFileToUploadItem(att) {
const filePath = toSubmitFileUrl(att.filePath || att.url || '');
return {
url: filePath,
serverPath: filePath,
status: 'success',
message: '',
name: att.fileName || att.name || '',
type: att.fileType || 'image/jpeg',
size: att.fileSize || 0
};
}
/**
* 从 up-upload 列表项构建附件对象(提交给后端)
*/
export function buildAttachmentItem(file, defaults = {}) {
const filePath = toSubmitFileUrl(
file.serverPath || file.filePath || file.url || ''
);
const fileName =
file.name ||
(filePath ? filePath.split('/').pop()?.split('?')[0] : '') ||
'';
return {
fileName: fileName || defaults.fileName || '',
filePath,
fileType: file.type || defaults.fileType || 'image/jpeg',
fileSize: file.size || defaults.fileSize || 0
};
}
/**
* 为 up-upload 组件创建 afterRead / deletePic 处理器
* @param {import('vue').Ref} fileListRef
* @param {Object} [options]
* @param {Object} [options.watermark] 传入 addTimestampWatermark 的参数(不含 tempFilePath
*/
export function createUploadListHandlers(fileListRef, options = {}) {
const deletePic = (event) => {
fileListRef.value.splice(event.index, 1);
};
const afterRead = async (event) => {
const lists = [].concat(event.file);
let fileListLen = fileListRef.value.length;
lists.forEach((item) => {
fileListRef.value.push({
...item,
status: 'uploading',
message: '上传中'
});
});
for (let i = 0; i < lists.length; i++) {
const listIndex = fileListLen;
try {
const beforeUpload = options.watermark
? (tempFilePath) =>
addTimestampWatermark({
tempFilePath,
...options.watermark
})
: undefined;
const result = await uploadToCloud(lists[i].url, { beforeUpload });
const item = fileListRef.value[listIndex];
fileListRef.value.splice(listIndex, 1, {
...item,
status: 'success',
message: '',
url: result.url,
serverPath: result.url
});
} catch (e) {
console.error('上传失败:', e);
const item = fileListRef.value[listIndex];
fileListRef.value.splice(listIndex, 1, {
...item,
status: 'failed',
message: e?.msg || e?.message || '上传失败'
});
uni.showToast({
title: e?.msg || e?.message || '上传失败',
icon: 'none'
});
}
fileListLen++;
}
};
return { afterRead, deletePic };
}
/**
* 单文件上传(头像、证件照等),带 loading
*/
export function uploadSingleWithLoading(filePath, options = {}) {
const loadingTitle = options.loadingTitle || '上传中...';
uni.showLoading({ title: loadingTitle, mask: true });
return uploadToCloud(filePath, options)
.then((result) => {
uni.hideLoading();
return result;
})
.catch((err) => {
uni.hideLoading();
throw err;
});
}
/** @deprecated 请使用 uploadToCloud保留别名便于渐进迁移 */
export const uploadFilePromise = uploadToCloud;