Files
jiapu/public/js/upload-pages.js
T
2026-07-11 11:03:47 +08:00

251 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function (root, factory) {
// 公共上传模块同时支持浏览器页面和 Node 单元测试。
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.UploadPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.UploadPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var documentRef = root.document;
var DEFAULT_CHUNK_SIZE = 4 * 1024 * 1024;
function getApi() {
return root.GenealogyApi && root.GenealogyApi.defaultClient;
}
function query(selector, rootNode) {
return (rootNode || documentRef).querySelector(selector);
}
function showMessage(message) {
if (root.layui && root.layui.layer) {
root.layui.layer.msg(message);
return;
}
root.alert(message);
}
function pick(item, keys, fallback) {
var source = item || {};
var index;
var value;
for (index = 0; index < keys.length; index += 1) {
value = source[keys[index]];
if (value !== undefined && value !== null && value !== '') return value;
}
return fallback || '';
}
function normalizeUploadResult(data) {
// FileUploadResult 字段可能因后端实现不同而略有差异,这里只收敛页面需要的值。
var source = data || {};
var ossId = pick(source, ['ossId', 'id', 'fileId', 'oss_id'], '');
var fileName = pick(source, ['fileName', 'originalName', 'name'], '');
var url = pick(source, ['url', 'fileUrl', 'previewUrl'], '');
return {
ossId: String(ossId || ''),
fileName: String(fileName || ''),
url: String(url || '')
};
}
function mergeOssIds(currentValue, nextValue, multiple) {
var next = String(nextValue || '').trim();
var current = String(currentValue || '').trim();
var values;
if (!next) return current;
if (!multiple) return next;
values = current ? current.split(',').map(function (item) {
return item.trim();
}).filter(Boolean) : [];
if (values.indexOf(next) === -1) values.push(next);
return values.join(',');
}
function buildUploadStatus(fileName, ossId) {
return (fileName || '文件') + ' 上传完成,OSS ID' + ossId;
}
function getUploadMode(fileSize, chunkSize) {
var size = Number(fileSize) || 0;
var limit = Number(chunkSize) || DEFAULT_CHUNK_SIZE;
return size > limit ? 'resumable' : 'single';
}
function buildResumableInitBody(file, fileMd5, chunkSize, options) {
var source = file || {};
var settings = options || {};
var size = Number(source.size) || 0;
var sizePerChunk = Number(chunkSize) || DEFAULT_CHUNK_SIZE;
var body = {
fileName: String(source.name || ''),
fileSize: size,
fileMd5: String(fileMd5 || ''),
contentType: String(source.type || 'application/octet-stream'),
chunkSize: sizePerChunk,
totalChunks: Math.ceil(size / sizePerChunk)
};
if (settings.bizType) body.bizType = settings.bizType;
if (settings.usageScene) body.usageScene = settings.usageScene;
return body;
}
function buildResumableCompleteBody(uploadId, fileMd5, fileSize) {
return {
uploadId: String(uploadId || ''),
fileMd5: String(fileMd5 || ''),
fileSize: Number(fileSize) || 0
};
}
function toBinaryString(buffer) {
var bytes = new Uint8Array(buffer);
var chunkSize = 0x8000;
var chunks = [];
var index;
for (index = 0; index < bytes.length; index += chunkSize) {
chunks.push(String.fromCharCode.apply(null, bytes.subarray(index, index + chunkSize)));
}
return chunks.join('');
}
async function hashBlob(blob) {
if (!root.hex_md5 || !blob || !blob.arrayBuffer) {
throw new Error('缺少文件 MD5 工具');
}
return root.hex_md5(toBinaryString(await blob.arrayBuffer()));
}
async function uploadResumable(api, file, options) {
var settings = options || {};
var chunkSize = Number(settings.chunkSize) || DEFAULT_CHUNK_SIZE;
var hash = settings.hashBlob || hashBlob;
var fileMd5;
var init;
var uploadId;
var totalChunks;
var index;
var start;
var end;
var chunk;
if (!api || !file || !file.slice) throw new Error('请选择可分片上传的文件');
fileMd5 = await hash(file);
init = await api.initResumableUpload(buildResumableInitBody(file, fileMd5, chunkSize, settings));
uploadId = pick(init, ['uploadId', 'id'], '');
if (!uploadId) throw new Error('分片上传初始化缺少 uploadId');
totalChunks = Math.ceil(file.size / chunkSize);
for (index = 0; index < totalChunks; index += 1) {
start = index * chunkSize;
end = Math.min(start + chunkSize, file.size);
chunk = file.slice(start, end);
await api.uploadChunk({
uploadId: uploadId,
chunkIndex: index,
chunkMd5: await hash(chunk),
file: chunk
});
if (settings.onProgress) settings.onProgress(index + 1, totalChunks);
}
return api.completeResumableUpload(buildResumableCompleteBody(uploadId, fileMd5, file.size));
}
async function uploadFileForPage(api, file, options) {
var settings = options || {};
var mode = settings.mode === 'single' || settings.mode === 'resumable'
? settings.mode
: getUploadMode(file && file.size, settings.chunkSize);
if (mode === 'single') return api.uploadFile(file);
return uploadResumable(api, file, settings);
}
async function uploadFromInput(input) {
var api = getApi();
var file = input.files && input.files[0];
var targetSelector = input.getAttribute('data-upload-target');
var statusSelector = input.getAttribute('data-upload-status');
var target = targetSelector && query(targetSelector);
var status = statusSelector && query(statusSelector);
var multiple = input.getAttribute('data-upload-multiple') === 'true';
var chunkSize = Number(input.getAttribute('data-upload-chunk-size')) || DEFAULT_CHUNK_SIZE;
var mode = input.getAttribute('data-upload-mode') || '';
var result;
if (!api || !file || !target) return;
if (status) status.textContent = '上传中...';
input.disabled = true;
try {
result = normalizeUploadResult(await uploadFileForPage(api, file, {
mode: mode || getUploadMode(file.size, chunkSize),
chunkSize: chunkSize,
bizType: input.getAttribute('data-upload-biz-type') || undefined,
usageScene: input.getAttribute('data-upload-usage-scene') || undefined,
onProgress: function (current, total) {
if (status) status.textContent = '分片上传 ' + current + '/' + total;
}
}));
if (!result.ossId) throw new Error('上传结果缺少 OSS ID');
target.value = mergeOssIds(target.value, result.ossId, multiple);
if (status) status.textContent = buildUploadStatus(result.fileName || file.name, result.ossId);
showMessage('文件上传完成');
} catch (error) {
if (status) status.textContent = error.message || '上传失败';
showMessage(error.message || '上传失败');
} finally {
input.disabled = false;
input.value = '';
}
}
function bindActions() {
if (!documentRef) return;
documentRef.addEventListener('change', function (event) {
var input = event.target.closest('[data-upload-target]');
if (!input || input.type !== 'file') return;
uploadFromInput(input);
});
}
function init() {
bindActions();
}
return {
normalizeUploadResult: normalizeUploadResult,
mergeOssIds: mergeOssIds,
buildUploadStatus: buildUploadStatus,
getUploadMode: getUploadMode,
buildResumableInitBody: buildResumableInitBody,
buildResumableCompleteBody: buildResumableCompleteBody,
uploadResumable: uploadResumable,
uploadFileForPage: uploadFileForPage,
init: init
};
});