import ExcelJS from 'exceljs' import { saveAs } from 'file-saver' interface ExportImageGroup { name: string urls: string[] } interface ExcelEmbedImage { buffer: ArrayBuffer width: number height: number } const IMAGE_PREVIEW_COLUMN_WIDTH = 25 const IMAGE_CELL_WIDTH_PX = 180 const POINTS_PER_PIXEL = 0.75 function calculateImageDisplaySize(width: number, height: number) { if (width <= 0 || height <= 0) { return { width: IMAGE_CELL_WIDTH_PX, height: IMAGE_CELL_WIDTH_PX, } } const scale = IMAGE_CELL_WIDTH_PX / width return { width: IMAGE_CELL_WIDTH_PX, height: Math.max(1, Math.round(height * scale)), } } function calculateImageRowHeightPoints(imageHeightPx: number) { return Math.max(90, Math.round(imageHeightPx * POINTS_PER_PIXEL)) } async function fetchExcelEmbedImage(url: string): Promise { const proxyUrl = `/api/taobao-detail-plus/image-proxy?url=${encodeURIComponent(url)}` const response = await fetch(proxyUrl, { method: 'GET', cache: 'no-store', credentials: 'same-origin', }) if (!response.ok) { throw new Error(`Image proxy failed with ${response.status}`) } const width = Number(response.headers.get('x-image-width') || 0) const height = Number(response.headers.get('x-image-height') || 0) return { buffer: await response.arrayBuffer(), width, height, } } export async function exportTaobaoImagesToExcel( title: string, itemId: string, groups: ExportImageGroup[] ) { const workbook = new ExcelJS.Workbook() const worksheet = workbook.addWorksheet('商品图片明细') // 设置列宽 worksheet.columns = [ { header: '分区', key: 'group', width: 15 }, { header: '序号', key: 'index', width: 10 }, { header: '图片预览', key: 'image', width: IMAGE_PREVIEW_COLUMN_WIDTH }, { header: '原始链接', key: 'url', width: 60 } ] // 设置表头样式 worksheet.getRow(1).font = { bold: true } worksheet.getRow(1).alignment = { vertical: 'middle', horizontal: 'center' } let currentRow = 2 for (const group of groups) { for (let i = 0; i < group.urls.length; i++) { const url = group.urls[i] const row = worksheet.addRow({ group: i === 0 ? group.name : '', index: i + 1, url: url }) row.height = 90 try { const imageData = await fetchExcelEmbedImage(url) const displaySize = calculateImageDisplaySize(imageData.width, imageData.height) row.height = calculateImageRowHeightPoints(displaySize.height) const imageId = workbook.addImage({ buffer: imageData.buffer, extension: 'png', }) const imageAnchor = { tl: { col: 2, row: currentRow - 1 }, ext: { width: displaySize.width, height: displaySize.height, }, editAs: 'oneCell' } as any worksheet.addImage(imageId, imageAnchor) } catch (error) { console.error(`Failed to embed image: ${url}`, error) worksheet.getCell(currentRow, 3).value = '图片下载失败' } currentRow++ } // 分组之间加个细微分隔 if (group.urls.length > 0) { worksheet.addRow([]) currentRow++ } } // 垂直居中所有单元格 worksheet.eachRow((row) => { row.alignment = { vertical: 'middle', horizontal: 'center' } }) // 导出文件 const buffer = await workbook.xlsx.writeBuffer() const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }) saveAs(blob, `${itemId}_淘宝图片导出_${new Date().getTime()}.xlsx`) }