Files
sionrui/frontend/app/web-gold/src/utils/excel.js
2025-11-10 23:53:05 +08:00

332 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import * as XLSX from 'xlsx-js-style'
import dayjs from 'dayjs'
/**
* 格式化数字,添加千分位分隔符
*/
function formatNumber(num) {
if (typeof num !== 'number' || isNaN(num)) return num
return num.toLocaleString('zh-CN')
}
/**
* 计算列宽(根据内容自动调整)
*/
function calculateColumnWidth(data, columnKey) {
if (!data || data.length === 0) return 15
const maxLength = Math.max(
columnKey.length, // 表头长度
...data.map(row => {
const value = row[columnKey]
if (value === null || value === undefined) return 0
return String(value).length
})
)
// 最小宽度 8最大宽度根据内容但不超过 100
return Math.min(Math.max(maxLength + 2, 8), 100)
}
/**
* 导出博主主页数据到 Excel优化版
* @param {Array} data - 要导出的数据数组
* @param {Object} options - 导出配置选项
* @param {string} options.platform - 平台类型(抖音/小红书)
* @param {Function} options.formatTime - 时间格式化函数
* @param {string} options.filename - 自定义文件名(可选)
* @returns {Object} 导出结果
*/
export function exportBenchmarkDataToExcel(data, options = {}) {
const { platform = '抖音', formatTime, filename } = options
if (!data || data.length === 0) {
return { success: false, message: '暂无数据可导出' }
}
try {
// 定义列配置
const columnConfig = [
{ key: '序号', width: 8, align: 'center' },
{ key: 'ID', width: 18, align: 'left' },
{ key: '描述', width: 60, align: 'left', wrap: true },
{ key: '点赞数', width: 12, align: 'right', format: 'number' },
{ key: '评论数', width: 12, align: 'right', format: 'number' },
{ key: '分享数', width: 12, align: 'right', format: 'number' },
{ key: '收藏数', width: 12, align: 'right', format: 'number' },
{ key: '播放量', width: 12, align: 'right', format: 'number' },
{ key: '时长(秒)', width: 12, align: 'right', format: 'number' },
{ key: '置顶', width: 8, align: 'center' },
{ key: '创建时间', width: 20, align: 'left' },
{ key: '链接', width: 60, align: 'left' },
{ key: '封面链接', width: 60, align: 'left' },
{ key: '音频链接', width: 60, align: 'left' },
{ key: '原配音', width: 80, align: 'left', wrap: true },
{ key: '风格提示词', width: 80, align: 'left', wrap: true }
]
// 准备导出数据
const exportData = data.map((item, index) => {
const row = {
'序号': index + 1,
'ID': item.id || '',
'描述': item.desc || '',
'点赞数': item.digg_count || 0,
'评论数': item.comment_count || 0,
'分享数': item.share_count || 0,
'收藏数': item.collect_count || 0,
'播放量': item.play_count || 0,
'时长(秒)': item.duration_s || 0,
'置顶': item.is_top ? '是' : '否',
'创建时间': formatTime ? formatTime(item.create_time) : (item.create_time || ''),
'链接': item.share_url || '',
'封面链接': item.cover || '',
'音频链接': item.audio_url || '',
'原配音': item.transcriptions || '',
'风格提示词': item.prompt || ''
}
// 小红书平台特殊处理
if (platform === '小红书' && !row['播放量']) {
row['浏览量'] = item.view_count || 0
// 调整列配置,将播放量改为浏览量
const playIndex = columnConfig.findIndex(col => col.key === '播放量')
if (playIndex !== -1) {
columnConfig[playIndex].key = '浏览量'
}
}
// 确保数字字段为数字类型(不格式化,显示完整数字)
columnConfig.forEach(col => {
if (col.format === 'number' && row[col.key] !== undefined) {
const num = Number(row[col.key])
if (!isNaN(num)) {
row[col.key] = num // 直接使用数字,不格式化
} else {
row[col.key] = 0
}
}
})
return row
})
// 创建工作簿和工作表
const wb = XLSX.utils.book_new()
const ws = XLSX.utils.json_to_sheet(exportData)
// 优化列宽设置(根据实际内容动态计算)
ws['!cols'] = columnConfig.map(col => {
// 如果数据中有该列,根据实际内容计算宽度
const actualWidth = calculateColumnWidth(exportData, col.key)
return {
wch: Math.max(col.width, actualWidth)
}
})
// 设置冻结首行(冻结窗格)
ws['!freeze'] = { xSplit: 0, ySplit: 1, topLeftCell: 'A2', activePane: 'bottomLeft', state: 'frozen' }
// 设置行高(首行稍高,便于阅读)
if (!ws['!rows']) ws['!rows'] = []
ws['!rows'][0] = { hpt: 25 } // 首行高度 25pt
// 数据行高度
for (let i = 1; i <= exportData.length; i++) {
ws['!rows'][i] = { hpt: 18 }
}
// 获取表头范围
const headerRange = XLSX.utils.decode_range(ws['!ref'])
const lastCol = headerRange.e.c
// 设置表头样式(加粗、背景色、居中对齐)
const headerStyle = {
font: { bold: true, color: { rgb: 'FFFFFF' }, sz: 11 },
fill: { fgColor: { rgb: '4472C4' } }, // 蓝色背景
alignment: { horizontal: 'center', vertical: 'center', wrapText: true },
border: {
top: { style: 'thin', color: { rgb: '000000' } },
bottom: { style: 'thin', color: { rgb: '000000' } },
left: { style: 'thin', color: { rgb: '000000' } },
right: { style: 'thin', color: { rgb: '000000' } }
}
}
// 应用表头样式
for (let col = 0; col <= lastCol; col++) {
const cellAddress = XLSX.utils.encode_cell({ r: 0, c: col })
if (!ws[cellAddress]) ws[cellAddress] = { t: 's', v: '' }
ws[cellAddress].s = headerStyle
}
// 设置数据行样式(边框、对齐方式)
const dataStyle = {
border: {
top: { style: 'thin', color: { rgb: 'D9D9D9' } },
bottom: { style: 'thin', color: { rgb: 'D9D9D9' } },
left: { style: 'thin', color: { rgb: 'D9D9D9' } },
right: { style: 'thin', color: { rgb: 'D9D9D9' } }
},
alignment: { vertical: 'top', wrapText: true }
}
// 应用数据行样式
for (let row = 1; row <= exportData.length; row++) {
for (let col = 0; col <= lastCol; col++) {
const cellAddress = XLSX.utils.encode_cell({ r: row, c: col })
if (ws[cellAddress]) {
if (!ws[cellAddress].s) ws[cellAddress].s = {}
// 根据列配置设置对齐方式
const colConfig = columnConfig[col]
if (colConfig) {
ws[cellAddress].s.alignment = {
...dataStyle.alignment,
horizontal: colConfig.align || 'left'
}
} else {
ws[cellAddress].s.alignment = dataStyle.alignment
}
// 设置边框
ws[cellAddress].s.border = dataStyle.border
// 数字列右对齐,并确保为数字格式
if (colConfig && colConfig.format === 'number') {
ws[cellAddress].s.alignment.horizontal = 'right'
// 确保单元格类型为数字
if (typeof ws[cellAddress].v === 'number') {
ws[cellAddress].t = 'n' // 数字类型
// 设置数字格式(不显示千分位,显示完整数字)
ws[cellAddress].z = '0'
}
}
}
}
// 斑马纹效果(偶数行浅灰色背景)
if (row % 2 === 0) {
for (let col = 0; col <= lastCol; col++) {
const cellAddress = XLSX.utils.encode_cell({ r: row, c: col })
if (ws[cellAddress]) {
if (!ws[cellAddress].s) ws[cellAddress].s = {}
ws[cellAddress].s.fill = { fgColor: { rgb: 'F2F2F2' } }
}
}
}
}
// 设置打印区域
const lastRow = exportData.length + 1
const lastColLetter = XLSX.utils.encode_col(lastCol)
ws['!print'] = {
area: `A1:${lastColLetter}${lastRow}`,
margins: {
left: 0.7,
right: 0.7,
top: 0.75,
bottom: 0.75,
header: 0.3,
footer: 0.3
},
orientation: 'landscape', // 横向打印
paperSize: 9, // A4
fitToPage: true,
fitToWidth: 1,
fitToHeight: 0
}
XLSX.utils.book_append_sheet(wb, ws, '博主主页数据')
// 生成文件名
const timestamp = dayjs().format('YYYY-MM-DD_HH-mm-ss')
const finalFilename = filename || `${platform}_博主主页_${timestamp}.xlsx`
// 导出文件
XLSX.writeFile(wb, finalFilename)
return {
success: true,
message: `已导出 ${exportData.length} 条数据`,
count: exportData.length
}
} catch (error) {
console.error('导出Excel失败:', error)
return {
success: false,
message: '导出失败,请稍后重试',
error: error.message
}
}
}
/**
* 通用 Excel 导出工具
* @param {Array} data - 要导出的数据数组
* @param {Object} options - 导出配置
* @param {string} options.sheetName - 工作表名称
* @param {string} options.filename - 文件名
* @param {Array} options.columns - 列配置 [{key, title, width}]
* @param {Function} options.formatter - 数据格式化函数
* @returns {boolean} 是否导出成功
*/
export function exportToExcel(data, options = {}) {
const {
sheetName = '数据',
filename = `导出数据_${dayjs().format('YYYY-MM-DD_HH-mm-ss')}.xlsx`,
columns,
formatter
} = options
if (!data || data.length === 0) {
return { success: false, message: '暂无数据可导出' }
}
try {
let exportData = data
// 如果有格式化函数,先格式化数据
if (formatter && typeof formatter === 'function') {
exportData = data.map(formatter)
} else if (columns && Array.isArray(columns)) {
// 如果有列配置,按配置转换数据
exportData = data.map((item, index) => {
const row = { '序号': index + 1 }
columns.forEach(col => {
const value = item[col.key]
row[col.title || col.key] = value !== undefined ? value : ''
})
return row
})
}
// 创建工作簿和工作表
const wb = XLSX.utils.book_new()
const ws = XLSX.utils.json_to_sheet(exportData)
// 设置列宽
if (columns && Array.isArray(columns)) {
ws['!cols'] = columns.map(col => ({ wch: col.width || 15 }))
}
XLSX.utils.book_append_sheet(wb, ws, sheetName)
// 导出文件
XLSX.writeFile(wb, filename)
return {
success: true,
message: `已导出 ${exportData.length} 条数据`,
count: exportData.length
}
} catch (error) {
console.error('导出Excel失败:', error)
return {
success: false,
message: '导出失败,请稍后重试',
error: error.message
}
}
}