fix: 测试页同时兼容 base64 与 URL 图片字段

直连 worker 返回 *_base64、经网关则改写为 *_url;统一 resolveImgSrc 后接口1/2/3/5/6 测试页都能正确显示结果图。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
xsl
2026-07-19 15:49:46 +08:00
co-authored by Cursor
parent 51e313e845
commit 08b31a3baa
6 changed files with 60 additions and 30 deletions
+31
View File
@@ -52,4 +52,35 @@
}
window.downscaleImageFile = downscaleImageFile;
/**
* 将接口返回的图片字段规范成 <img src> 可用值。
* 同时兼容:
* - 网关改写后的 *_urlhttp/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;
})();
+3 -6
View File
@@ -62,7 +62,7 @@
@media (max-width: 768px) { .results { flex-direction: column; } }
.hidden { display: none !important; }
</style>
<script src="/static/img_downscale.js"></script>
<script src="/static/img_downscale.js?v=2"></script>
</head>
<body>
<div class="container">
@@ -159,12 +159,9 @@ async function submitTest() {
if (json.code === 0) {
setStatus('✅ 请求成功 (' + _elapsed + 's) — request_id: ' + json.request_id, 'success');
// 兼容本地直连 worker返回 *_base64)与网关(改写为 *_url两种情况
// 兼容本地直连 worker*_base64)与网关(*_url
const _d = json.data || {};
const annoUrl = _d.annotated_image_url
|| (_d.annotated_image_base64
? ('data:image/png;base64,' + _d.annotated_image_base64.replace(/^data:image\/\w+;base64,/, ''))
: '');
const annoUrl = resolveImgSrc(_d.annotated_image_url, _d.annotated_image_base64, 'image/png');
showOverlay(annoUrl);
renderMetrics(json.data);
$('metricsBar').classList.remove('hidden');
+5 -5
View File
@@ -69,7 +69,7 @@
.hidden { display: none !important; }
</style>
<script src="/static/img_downscale.js"></script>
<script src="/static/img_downscale.js?v=2"></script>
</head>
<body>
<div class="container">
@@ -180,9 +180,9 @@ function renderSchemes(results) {
let html = '';
results.forEach((r, i) => {
const lb = TYPE_LABELS[r.hairline_type] || r.hairline_type;
// 直连 worker 时后端返回 base64网关才会改写为 *_url),URL 缺失则回退 data URI
const overlaySrc = r.image_url || (r.image_base64 ? 'data:image/png;base64,' + r.image_base64 : '');
const grownSrc = r.grown_image_url || (r.grown_image_base64 ? 'data:image/jpeg;base64,' + r.grown_image_base64 : '');
// 兼容网关 *_url 与 worker *_base64原始或 data URI
const overlaySrc = resolveImgSrc(r.image_url, r.image_base64, 'image/png');
const grownSrc = resolveImgSrc(r.grown_image_url, r.grown_image_base64, 'image/jpeg');
// 原图
const origSlot = '<div class="img-slot">'+
@@ -206,7 +206,7 @@ function renderSchemes(results) {
} else {
grownSlot = '<div class="img-slot">'+
'<div class="label"><span class="dot grown"></span>生发效果</div>'+
'<div class="na">⚠ 未返回<br><span style="font-size:10px;color:#9ca3af">ComfyUI 未就绪或生成失败</span></div></div>';
'<div class="na">⚠ 未返回<br><span style="font-size:10px;color:#9ca3af">生发失败(female=换发型/重绘,male=ComfyUI</span></div></div>';
}
html += '<div class="scheme-card">'+
+2 -2
View File
@@ -71,7 +71,7 @@
@media (max-width: 800px) { .results-layout, .preview-row { flex-direction: column; } }
</style>
<script src="/static/img_downscale.js"></script>
<script src="/static/img_downscale.js?v=2"></script>
</head>
<body>
<div class="container">
@@ -175,7 +175,7 @@ async function submitTest() {
$('hairlineType').style.display = 'inline-block';
}
const grownSrc = d.hair_growth_image_url || (d.hair_growth_image_base64 ? 'data:image/jpeg;base64,' + d.hair_growth_image_base64 : '');
const grownSrc = resolveImgSrc(d.hair_growth_image_url, d.hair_growth_image_base64, 'image/jpeg');
if (grownSrc) {
$('blendTop').src = grownSrc;
$('blendTop').style.display = 'block';
+16 -11
View File
@@ -88,7 +88,7 @@
.hidden { display: none !important; }
@media (max-width: 800px) { .results-layout { flex-direction: column; } }
</style>
<script src="/static/img_downscale.js"></script>
<script src="/static/img_downscale.js?v=2"></script>
</head>
<body>
<div class="container">
@@ -237,25 +237,30 @@ async function submitTest() {
function renderGrid() {
if (!_images.length) { $('resultsGrid').innerHTML = '<span style="color:#9ca3af">无数据</span>'; return; }
let h = '';
// 叠图档:原图打底 + 透明 PNG 叠加(无原图或无叠图 → 显示占位)
const overlayCell = (label, url) => (url && _origUrl)
// 叠图档:原图打底 + 透明 PNG 图层(无原图或无叠图 → 显示占位)
const overlayCell = (label, src) => (src && _origUrl)
? '<div class="level-cell"><div class="level-label">'+label+'</div><div class="img-stack">'+
'<img class="layer-base" src="'+_origUrl+'" alt="原图">'+
'<img class="layer-anno" src="'+url+'" alt="'+label+'"></div></div>'
'<img class="layer-anno" src="'+src+'" alt="'+label+'"></div></div>'
: '<div class="level-cell"><div class="level-label">'+label+'</div><div class="level-none">无</div></div>';
// 生发图:独立 img(已是 ComfyUI 完整人像照片)
const grownCell = (label, url) => url
? '<div class="level-cell"><div class="level-label">'+label+'</div><img src="'+url+'" alt="'+label+'"></div>'
const grownCell = (label, src) => src
? '<div class="level-cell"><div class="level-label">'+label+'</div><img src="'+src+'" alt="'+label+'"></div>'
: '<div class="level-cell"><div class="level-label">'+label+'</div><div class="level-none">无</div></div>';
_images.forEach(function(it) {
// 兼容网关 *_url 与 worker *_base64
const mid = resolveImgSrc(it.image_middle_url, it.image_middle_base64, 'image/png');
const high = resolveImgSrc(it.image_high_url, it.image_high_base64, 'image/png');
const low = resolveImgSrc(it.image_low_url, it.image_low_base64, 'image/png');
const grown = resolveImgSrc(it.grown_image_url, it.grown_image_base64, 'image/jpeg');
h += '<div class="hair-block">' +
'<div class="hair-block-title">#' + (it.order||'—') + ' ' + (it.hairline_type||'') +
(it.grown_image_url ? ' &nbsp;<span style="font-size:11px;color:#7c3aed">含生发图</span>' : '') + '</div>' +
(grown ? ' &nbsp;<span style="font-size:11px;color:#7c3aed">含生发图</span>' : '') + '</div>' +
'<div class="level-row">' +
overlayCell('middle', it.image_middle_url) +
overlayCell('high', it.image_high_url) +
overlayCell('low', it.image_low_url) +
grownCell('生发图', it.grown_image_url) +
overlayCell('middle', mid) +
overlayCell('high', high) +
overlayCell('low', low) +
grownCell('生发图', grown) +
'</div>' +
'</div>';
});
+3 -6
View File
@@ -59,7 +59,7 @@
.diff-list { font-size: 12px; color: #6b7280; margin-top: 6px; line-height: 1.7; }
.diff-list li { margin-left: 18px; }
</style>
<script src="/static/img_downscale.js"></script>
<script src="/static/img_downscale.js?v=2"></script>
</head>
<body>
<div class="container">
@@ -161,12 +161,9 @@ async function submitTest() {
if (json.code === 0) {
setStatus('✅ 请求成功 (' + _elapsed + 's) — request_id: ' + json.request_id, 'success');
// 兼容本地直连 worker返回 *_base64)与网关(改写为 *_url两种情况
// 兼容本地直连 worker*_base64)与网关(*_url
const _d = json.data || {};
const annoUrl = _d.annotated_image_url
|| (_d.annotated_image_base64
? ('data:image/png;base64,' + _d.annotated_image_base64.replace(/^data:image\/\w+;base64,/, ''))
: '');
const annoUrl = resolveImgSrc(_d.annotated_image_url, _d.annotated_image_base64, 'image/png');
showOverlay(annoUrl);
renderMetrics(json.data);
$('metricsBar').classList.remove('hidden');