123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- 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<Array<Array<WallItem>>> = []
- let currentWall: Array<Array<WallItem>> = []
- let currentRow: Array<WallItem> = []
- 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<string, string>,
- perLine: Record<string, number>
- ) => {
- 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<string, any>
- perLine?: Record<string, number>
- row?: Record<string, number>
- }
- /**
- * 计算表格数据的分页情况
- */
- export const calcTablePages = (rows: any[], config: Required<ITEMPLATE>) => {
- 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
- }
|