直连 worker 返回 *_base64、经网关则改写为 *_url;统一 resolveImgSrc 后接口1/2/3/5/6 测试页都能正确显示结果图。 Co-authored-by: Cursor <cursoragent@cursor.com>
87 lines
3.5 KiB
JavaScript
87 lines
3.5 KiB
JavaScript
// 上传图片自动降采样:当总像素 > MAX_PIXELS 时,等比例缩小到 <= TARGET_PIXELS。
|
||
// 用法:在提交前 file = await window.downscaleImageFile(file);
|
||
(function () {
|
||
var MAX_PIXELS = 1536000; // 触发阈值:超过则缩小
|
||
var TARGET_PIXELS = 786432; // 缩小后总像素上限
|
||
|
||
function loadImage(url) {
|
||
return new Promise(function (resolve, reject) {
|
||
var img = new Image();
|
||
img.onload = function () { resolve(img); };
|
||
img.onerror = reject;
|
||
img.src = url;
|
||
});
|
||
}
|
||
|
||
async function downscaleImageFile(file) {
|
||
// 非图片、GIF、SVG 不处理,原样返回
|
||
if (!file || !file.type || file.type.indexOf('image/') !== 0) return file;
|
||
if (file.type === 'image/gif' || file.type === 'image/svg+xml') return file;
|
||
|
||
var url = URL.createObjectURL(file);
|
||
try {
|
||
var img = await loadImage(url);
|
||
var w = img.naturalWidth, h = img.naturalHeight;
|
||
var total = w * h;
|
||
if (!total || total <= MAX_PIXELS) return file; // 未超阈值,原图直接用
|
||
|
||
var scale = Math.sqrt(TARGET_PIXELS / total);
|
||
var nw = Math.max(1, Math.round(w * scale));
|
||
var nh = Math.max(1, Math.round(h * scale));
|
||
|
||
var canvas = document.createElement('canvas');
|
||
canvas.width = nw;
|
||
canvas.height = nh;
|
||
var ctx = canvas.getContext('2d');
|
||
ctx.drawImage(img, 0, 0, nw, nh);
|
||
|
||
var outType = file.type === 'image/png' ? 'image/png' : 'image/jpeg';
|
||
var quality = outType === 'image/jpeg' ? 0.92 : undefined;
|
||
var blob = await new Promise(function (res) { canvas.toBlob(res, outType, quality); });
|
||
if (!blob) return file;
|
||
|
||
var base = (file.name || 'image').replace(/\.(jpe?g|png|webp|bmp)$/i, '');
|
||
var name = base + (outType === 'image/png' ? '.png' : '.jpg');
|
||
return new File([blob], name, { type: outType, lastModified: Date.now() });
|
||
} catch (e) {
|
||
console.warn('downscaleImageFile 失败,使用原图', e);
|
||
return file;
|
||
} finally {
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
}
|
||
|
||
window.downscaleImageFile = downscaleImageFile;
|
||
|
||
/**
|
||
* 将接口返回的图片字段规范成 <img src> 可用值。
|
||
* 同时兼容:
|
||
* - 网关改写后的 *_url(http/https/相对路径)
|
||
* - worker 直连的 *_base64(原始 base64,或已带 data:image/...;base64, 前缀)
|
||
* @param {string|null|undefined} url
|
||
* @param {string|null|undefined} b64
|
||
* @param {string} [mimeHint='image/jpeg'] 原始 base64 时的 MIME 提示(会按魔数再嗅探)
|
||
* @returns {string}
|
||
*/
|
||
function resolveImgSrc(url, b64, mimeHint) {
|
||
var v = url || b64 || '';
|
||
if (!v || typeof v !== 'string') return '';
|
||
v = v.trim();
|
||
if (!v) return '';
|
||
// 已是可用 URL / data URI / blob / 协议相对 URL
|
||
if (/^(https?:|blob:|data:|\/\/)/i.test(v)) return v;
|
||
// 同站相对路径(仅匹配明确的 /static/...;勿把 JPEG base64 的 /9j/ 前缀当路径)
|
||
if (/^\/static\//.test(v)) return v;
|
||
// 原始 base64:去掉可能残留的 data URI 前缀后再拼,避免双重前缀导致无法显示
|
||
var raw = v.replace(/^data:image\/[a-zA-Z0-9+.-]+;base64,/i, '');
|
||
if (!raw) return '';
|
||
var mime = mimeHint || 'image/jpeg';
|
||
if (raw.indexOf('iVBOR') === 0) mime = 'image/png';
|
||
else if (raw.indexOf('/9j/') === 0) mime = 'image/jpeg';
|
||
else if (raw.indexOf('R0lGOD') === 0) mime = 'image/gif';
|
||
else if (raw.indexOf('UklGR') === 0) mime = 'image/webp';
|
||
return 'data:' + mime + ';base64,' + raw;
|
||
}
|
||
window.resolveImgSrc = resolveImgSrc;
|
||
})();
|