feat(demo): 新增图片生成与保存功能,优化图片提取逻辑

This commit is contained in:
WJZ_P
2026-03-18 02:23:43 +08:00
parent d7f09ff8f7
commit 464327f24b
2 changed files with 130 additions and 39 deletions

View File

@@ -465,19 +465,29 @@ export function createOps(page) {
},
/**
* 提取最新图片的 Base64 数据Canvas 优先fetch 兜底)
* 提取指定图片的 Base64 数据Canvas 优先fetch 兜底)
*
* @param {string} url - 目标图片的 src URL
* @returns {Promise<{ok: boolean, dataUrl?: string, width?: number, height?: number, method?: 'canvas'|'fetch', error?: string}>}
*/
async extractImageBase64() {
return op.query(() => {
async extractImageBase64(url) {
if (!url) {
console.warn('[extractImageBase64] ❌ 未提供 url 参数');
return { ok: false, error: 'missing_url' };
}
console.log(`[extractImageBase64] 🔍 开始提取, url=${url.slice(0, 120)}...`);
const canvasResult = await op.query((targetUrl) => {
// ── 在页面中根据 url 查找匹配的 img 元素 ──
const imgs = [...document.querySelectorAll('img.image.loaded')];
if (!imgs.length) {
return { ok: false, error: 'no_loaded_images' };
const img = imgs.find(i => i.src === targetUrl);
if (!img) {
return { ok: false, error: 'img_not_found_by_url', searched: imgs.length };
}
const img = imgs[imgs.length - 1];
const w = img.naturalWidth || img.width;
const h = img.naturalHeight || img.height;
// 尝试 Canvas 同步提取
// ── 尝试 Canvas 同步提取 ──
try {
const canvas = document.createElement('canvas');
canvas.width = w;
@@ -485,31 +495,52 @@ export function createOps(page) {
canvas.getContext('2d').drawImage(img, 0, 0);
const dataUrl = canvas.toDataURL('image/png');
return { ok: true, dataUrl, width: w, height: h, method: 'canvas' };
} catch { /* canvas tainted, fallback */ }
} catch (e) {
// canvas tainted跨域图片记录原因后降级
return { ok: false, needFetch: true, src: img.src, width: w, height: h, canvasError: e.message || String(e) };
}
}, url);
// 标记需要 fetch fallback
return { ok: false, needFetch: true, src: img.src, width: w, height: h };
}).then(async (result) => {
if (result.ok || !result.needFetch) return result;
if (canvasResult.ok) {
console.log(`[extractImageBase64] ✅ Canvas 提取成功 (${canvasResult.width}x${canvasResult.height})`);
return canvasResult;
}
// Fetch fallback: 在页面上下文中异步执行
return page.evaluate(async (src, w, h) => {
try {
const r = await fetch(src);
if (!r.ok) throw new Error(`fetch_status_${r.status}`);
const blob = await r.blob();
return await new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve({
ok: true, dataUrl: reader.result, width: w, height: h, method: 'fetch',
});
reader.readAsDataURL(blob);
if (!canvasResult.needFetch) {
// img 元素都没找到,直接返回失败
console.warn(`[extractImageBase64] ❌ 页面中未找到匹配的 img 元素 (已扫描 ${canvasResult.searched || 0} 张)`);
return canvasResult;
}
// ── Fetch 降级Canvas 被跨域污染,改用 fetch 读取二进制 ──
console.log(`[extractImageBase64] ⚠ Canvas 被污染 (${canvasResult.canvasError}),降级为 fetch...`);
const fetchResult = await page.evaluate(async (src, w, h) => {
try {
const r = await fetch(src);
if (!r.ok) return { ok: false, error: `fetch_status_${r.status}` };
const blob = await r.blob();
return await new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve({
ok: true, dataUrl: reader.result, width: w, height: h, method: 'fetch',
});
} catch (err) {
return { ok: false, error: 'extract_failed', detail: err.message || String(err) };
}
}, result.src, result.width, result.height);
});
reader.onerror = () => resolve({
ok: false, error: 'filereader_error',
});
reader.readAsDataURL(blob);
});
} catch (err) {
return { ok: false, error: 'fetch_failed', detail: err.message || String(err) };
}
}, canvasResult.src, canvasResult.width, canvasResult.height);
if (fetchResult.ok) {
console.log(`[extractImageBase64] ✅ Fetch 提取成功 (${fetchResult.width}x${fetchResult.height})`);
} else {
console.warn(`[extractImageBase64] ❌ Fetch 提取失败: ${fetchResult.error}${fetchResult.detail ? ' — ' + fetchResult.detail : ''}`);
}
return fetchResult;
},
/**
@@ -621,12 +652,12 @@ export function createOps(page) {
await sleep(2000);
// 4. 获取图片
const imgInfo = await this.getLatestImage();
let imgInfo = await this.getLatestImage();
if (!imgInfo.ok) {
await sleep(3000);
const retry = await this.getLatestImage();
if (!retry.ok) {
return { ok: false, error: 'no_image_found', elapsed: waitResult.elapsed, imgInfo: retry };
imgInfo = await this.getLatestImage();
if (!imgInfo.ok) {
return { ok: false, error: 'no_image_found', elapsed: waitResult.elapsed, imgInfo };
}
}
@@ -635,7 +666,7 @@ export function createOps(page) {
const dlResult = await this.downloadLatestImage();
return { ok: dlResult.ok, method: 'download', elapsed: waitResult.elapsed, ...dlResult };
} else {
const b64Result = await this.extractImageBase64();
const b64Result = await this.extractImageBase64(imgInfo.src);
return { ok: b64Result.ok, method: b64Result.method, elapsed: waitResult.elapsed, ...b64Result };
}
},