gongxiangp2/lib/excel-export.ts
2026-05-07 16:37:22 +08:00

138 lines
3.6 KiB
TypeScript

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<ExcelEmbedImage> {
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`)
}