优化前端

This commit is contained in:
2025-11-10 23:53:05 +08:00
parent bac96fcbe6
commit cd443f9b3a
7 changed files with 1251 additions and 143 deletions

View File

@@ -0,0 +1,331 @@
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
}
}
}