import { getBase64Sync } from './exportWord' import { baseURL } from './http' type WallItem = { url: string width: number height: number img: string } export const numberToChinese = (num: number) => { if (isNaN(num)) { throw new Error('输入必须是一个有效的数字') } if (num < 0 || num > 9999) { throw new Error('输入数字超出范围 (0-9999)') } if (num === 0) { return '零' } const chineseNumbers = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'] const chineseUnits = ['', '拾', '佰', '仟'] let result = '' const numStr = num.toString() const length = numStr.length for (let i = 0; i < length; i++) { const digit = parseInt(numStr[i]) const unit = length - i - 1 if (digit !== 0) { result += chineseNumbers[digit] + chineseUnits[unit] } else { // 处理连续的零,只保留一个 if (i < length - 1 && numStr[i + 1] !== '0') { result += chineseNumbers[digit] } } } // 处理10-19的情况,去掉开头的"壹" if (num >= 10 && num < 20) { result = result.replace('壹拾', '拾') } return result } export const getImageDimensions = (url: string): Promise<{ width: number; height: number }> => { return new Promise(resolve => { const img = new Image() img.onload = () => { resolve({ width: img.width, height: img.height }) } img.onerror = () => { resolve({ width: 100, height: 100 }) } img.src = url }) } /** * 将一维数组根据图片宽高比转成三维数组 */ export const arrangeImages = async (images: { thumb: string }[]) => { if (!Array.isArray(images)) return [] const MAX_WALL_WIDTH = 520 const MAX_WALL_HEIGHT = 750 const MAX_ROWS_PER_WALL = 3 const walls: Array>> = [] let currentWall: Array> = [] let currentRow: Array = [] let currentRowHeight = 0 let currentX = 0 const imagesWithDimensions = await Promise.all( images.map(async img => { const url = baseURL + img.thumb const dimensions = await getImageDimensions(url) return { url, originalWidth: dimensions.width, originalHeight: dimensions.height } }) ) for (const img of imagesWithDimensions) { const maxRowHeight = MAX_WALL_HEIGHT / MAX_ROWS_PER_WALL const aspectRatio = img.originalWidth / img.originalHeight let scaledWidth, scaledHeight scaledHeight = maxRowHeight scaledWidth = maxRowHeight * aspectRatio if (scaledWidth > MAX_WALL_WIDTH) { scaledWidth = MAX_WALL_WIDTH scaledHeight = MAX_WALL_WIDTH / aspectRatio } // TOFIX: 暂时无法解决图片并列渲染问题 if (currentX + scaledWidth <= MAX_WALL_WIDTH && false) { currentRow.push({ url: img.url, width: scaledWidth, height: scaledHeight, img: await getBase64Sync(img.url) }) currentX += scaledWidth currentRowHeight = Math.max(currentRowHeight, scaledHeight) } else { if (currentRow.length > 0) { // eslint-disable-next-line no-loop-func currentRow.forEach(item => { item.height = currentRowHeight }) currentWall.push(currentRow) } if (currentWall.length >= MAX_ROWS_PER_WALL) { walls.push(currentWall) currentWall = [] } currentRow = [ { url: img.url, width: scaledWidth, height: scaledHeight, img: await getBase64Sync(img.url) } ] currentX = scaledWidth currentRowHeight = scaledHeight } } if (currentRow.length > 0) { currentWall.push(currentRow) } if (currentWall.length > 0) { walls.push(currentWall) } return walls } export const getEffectiveLength = (str: string) => { let length = 0 for (const char of str) { // 全角字符(包括中文、全角符号等)的 Unicode 范围判断 if (char.match(/[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/)) { length += 2 // 全角算2字符 } else { length += 1 // 半角算1字符 } } return length } export const calculateRowCharLines = ( row: Record, perLine: Record ) => { let maxCharLines = 0 for (const [field, text] of Object.entries(row)) { if (!perLine[field]) continue const fieldLength = getEffectiveLength(text) const fieldCharLines = Math.ceil(fieldLength / perLine[field]) maxCharLines = Math.max(maxCharLines, fieldCharLines) } return maxCharLines || 1 } export const removeHtmlTags = (html: string) => { return html .replace(/<[^>]*>/g, '') // 移除HTML标签 .replace(/\s+/g, ' ') // 合并多个空格 .replace(/ /g, ' ') // 替换HTML空格实体 .replace(/&/g, '&') // 替换HTML & 实体 .replace(/</g, '<') // 替换HTML < 实体 .replace(/>/g, '>') // 替换HTML > 实体 .replace(/"/g, '"') // 替换HTML " 实体 .replace(/'/g, "'") // 替换HTML ' 实体 .trim() // 去除首尾空格 } export const getExcelColumnLetter = (index: number) => { let result = '' let remaining = index do { result = String.fromCharCode(65 + (remaining % 26)) + result remaining = Math.floor(remaining / 26) - 1 } while (remaining >= 0) return result } export interface ITEMPLATE { fileName: string templateName?: string options?: Record perLine?: Record row?: Record } /** * 计算表格数据的分页情况 */ export const calcTablePages = (rows: any[], config: Required) => { if (rows.length === 0) return 0 let totalPages = 0 let currentPageRows: any[] = [] let currentPageCharLines = 0 let isFirstPage = true let isLastPage = false for (let i = 0; i < rows.length; i++) { const row = rows[i] const rowCharLines = calculateRowCharLines(row, config.perLine) currentPageCharLines += Math.max(rowCharLines, config.row.minRowCharLine) currentPageRows.push(row) // 判断是否超出首页 if (isFirstPage && currentPageCharLines > config.row.maxFirstCharLine) { isFirstPage = false totalPages++ currentPageCharLines -= config.row.maxFirstCharLine // 如果剩下行数还超出尾页最大行数,也先预处理 if (currentPageCharLines > config.row.maxLastCharLine) { totalPages++ currentPageCharLines -= config.row.maxLastCharLine } } // 确定当前页的限制 const maxRows = isFirstPage ? config.row.maxRowFirstPage : isLastPage ? config.row.maxRowLastPage : config.row.maxRowPage const maxCharLines = isFirstPage ? config.row.maxFirstCharLine : isLastPage ? config.row.maxLastCharLine : config.row.maxCharLine // 检查是否换页 const isPageFull = currentPageRows.length >= maxRows || currentPageCharLines >= maxCharLines if (isPageFull || i === rows.length - 1) { totalPages += Math.floor(currentPageCharLines / maxCharLines) + (currentPageCharLines % maxCharLines > 0 ? 1 : 0) currentPageRows = [] currentPageCharLines = 0 isFirstPage = false isLastPage = i === rows.length - 1 } } return totalPages }