Kaynağa Gözat

fix: export page error

chenlei 4 ay önce
ebeveyn
işleme
56bc6f3194
2 değiştirilmiş dosya ile 204 ekleme ve 179 silme
  1. 13 179
      src/utils/exportWordTemplates.ts
  2. 191 0
      src/utils/exportWordUtils.ts

+ 13 - 179
src/utils/exportWordTemplates.ts

@@ -2,6 +2,12 @@ import { cloneDeep } from 'lodash'
 import { exportWordDocx, getBase64Sync } from './exportWord'
 import { baseURL } from './http'
 import { resJiLianFu } from './history'
+import {
+  arrangeImages,
+  calculateRowCharLines,
+  numberToChinese,
+  removeHtmlTags
+} from './exportWordUtils'
 
 export enum EXPORT_WORD_ENUM {
   /** 借用藏品点交凭证 */
@@ -20,13 +26,6 @@ export enum EXPORT_WORD_ENUM {
   COLLECTION_CARD = 7
 }
 
-type WallItem = {
-  url: string
-  width: number
-  height: number
-  img: string
-}
-
 const WORD_FILE_NAME_MAP = {
   [EXPORT_WORD_ENUM.BORROW]: {
     fileName: '义乌市博物馆借用藏品点交凭证',
@@ -44,11 +43,11 @@ const WORD_FILE_NAME_MAP = {
     },
     row: {
       // 首页最大行数
-      maxRowFirstPage: 1,
+      maxRowFirstPage: 3,
       // 尾页最大行数
-      maxRowLastPage: 1,
+      maxRowLastPage: 6,
       // 每页最大行数
-      maxRowPage: 1,
+      maxRowPage: 8,
       // 首页单行最大字符行数
       maxFirstCharLine: 20,
       // 尾页单行最大字符行数
@@ -56,7 +55,7 @@ const WORD_FILE_NAME_MAP = {
       // 每页最大字符行数
       maxCharLine: 26,
       // 表格每行最小字符行数
-      minRowCharLine: 2
+      minRowCharLine: 3
     }
   },
   [EXPORT_WORD_ENUM.SUB_PUT_BACK]: {
@@ -81,172 +80,6 @@ const WORD_FILE_NAME_MAP = {
   }
 }
 
-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
-}
-
-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
-  })
-}
-
-/**
- * 将一维数组根据图片宽高比转成三维数组
- */
-const arrangeImages = async (images: { thumb: string }[]) => {
-  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
-}
-
-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
-}
-
-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
-}
-
 /**
  * 计算表格数据的分页情况
  */
@@ -263,7 +96,7 @@ const calcTablePages = (rows: any[], config: (typeof WORD_FILE_NAME_MAP)[2]) =>
     const row = rows[i]
     const rowCharLines = calculateRowCharLines(row, config.perLine)
 
-    currentPageCharLines += rowCharLines
+    currentPageCharLines += Math.max(rowCharLines, config.row.minRowCharLine)
     currentPageRows.push(row)
 
     // 判断是否超出首页
@@ -326,7 +159,7 @@ export const exportWordHandler = async (type: EXPORT_WORD_ENUM, data: Record<any
     for (let i = 0; i < temp.goods.length; i++) {
       const good = temp.goods[i]
       good.index = i + 1
-      good.rtf && (good.rtf = JSON.parse(good.rtf).txtArr[0].txt)
+      good.rtf && (good.rtf = removeHtmlTags(JSON.parse(good.rtf).txtArr[0].txt))
       good.thumb && (good.thumb = await getBase64Sync(baseURL + good.thumb))
       good.dictAge && (good.dictAge = resJiLianFu(good.dictAge))
       good.pcsUnit && (good.pcsUnit = resJiLianFu(good.pcsUnit))
@@ -342,6 +175,7 @@ export const exportWordHandler = async (type: EXPORT_WORD_ENUM, data: Record<any
   if (item.perLine) {
     // @ts-ignore
     page = calcTablePages(temp.goods, item)
+    console.log(page)
   }
 
   switch (type) {

+ 191 - 0
src/utils/exportWordUtils.ts

@@ -0,0 +1,191 @@
+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 }[]) => {
+  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(/&nbsp;/g, ' ') // 替换HTML空格实体
+    .replace(/&amp;/g, '&') // 替换HTML & 实体
+    .replace(/&lt;/g, '<') // 替换HTML < 实体
+    .replace(/&gt;/g, '>') // 替换HTML > 实体
+    .replace(/&quot;/g, '"') // 替换HTML " 实体
+    .replace(/&apos;/g, "'") // 替换HTML ' 实体
+    .trim() // 去除首尾空格
+}