138 lines
3.6 KiB
TypeScript
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`)
|
|
}
|