{"version":3,"file":"index-B8uTAuN_.mjs","sources":["../../src/renderers/Xlsx/index.tsx"],"sourcesContent":["import { useState, useEffect, useRef, useCallback, forwardRef, useImperativeHandle } from 'react';\nimport ExcelJS from 'exceljs';\nimport Spreadsheet from 'x-data-spreadsheet';\nimport { convertWorkbookToSpreadsheetData } from '../../utils/excelDataConverter';\nimport { useTranslator } from '../../i18n/LocaleContext';\nimport { useFetcher } from '../../RequestContext';\nimport { RendererError } from '../RendererError';\nimport type { RendererHandle } from '../base.types';\n\ninterface XlsxRendererProps {\n  url: string;\n}\n\nexport const XlsxRenderer = forwardRef<RendererHandle, XlsxRendererProps>(({ url }, ref) => {\n  const t = useTranslator();\n  const fetcher = useFetcher();\n  const [loading, setLoading] = useState(true);\n  const [error, setError] = useState<string | null>(null);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const spreadsheetRef = useRef<Spreadsheet | null>(null);\n  const sheetDataRef = useRef<Record<string, unknown>[] | null>(null);\n  const resizeObserverRef = useRef<ResizeObserver | null>(null);\n  const resizeTimeoutRef = useRef<number | null>(null);\n  const lastDimensionsRef = useRef({ width: 0, height: 0 });\n\n  const calculateDimensions = useCallback(() => {\n    if (!containerRef.current) return { width: 800, height: 600 };\n    const rawWidth = containerRef.current.clientWidth;\n    const rawHeight = containerRef.current.clientHeight;\n    const width = rawWidth > 100 ? rawWidth : 800;\n    const height = rawHeight > 100 ? rawHeight : 600;\n    return { width, height };\n  }, []);\n\n  const mountSpreadsheet = useCallback(() => {\n    if (!containerRef.current || !sheetDataRef.current) return;\n\n    // 清空容器\n    containerRef.current.innerHTML = '';\n    spreadsheetRef.current = null;\n\n    const { width, height } = calculateDimensions();\n    const isMobile = width < 640;\n\n    const s = new Spreadsheet(containerRef.current, {\n      mode: 'read',\n      showToolbar: false,\n      showContextmenu: false,\n      showGrid: true,\n      row: {\n        len: 100,\n        height: 25,\n      },\n      col: {\n        len: 26,\n        width: isMobile ? 80 : 100,\n        indexWidth: isMobile ? 40 : 60,\n        minWidth: isMobile ? 40 : 60,\n      },\n      view: {\n        height: () => height,\n        width: () => width,\n      },\n    });\n\n    s.loadData(sheetDataRef.current as unknown as Record<string, unknown>);\n    spreadsheetRef.current = s;\n  }, [calculateDimensions]);\n\n  // 监听容器尺寸变化\n  useEffect(() => {\n    if (!containerRef.current) return;\n\n    let isInitialRender = true;\n\n    const updateDimensions = () => {\n      if (isInitialRender) {\n        isInitialRender = false;\n        lastDimensionsRef.current = calculateDimensions();\n        return;\n      }\n\n      const newDimensions = calculateDimensions();\n      const lastDimensions = lastDimensionsRef.current;\n      const widthDiff = Math.abs(lastDimensions.width - newDimensions.width);\n      const heightDiff = Math.abs(lastDimensions.height - newDimensions.height);\n\n      if (widthDiff < 10 && heightDiff < 10) return;\n\n      lastDimensionsRef.current = newDimensions;\n\n      if (resizeTimeoutRef.current) {\n        clearTimeout(resizeTimeoutRef.current);\n      }\n\n      resizeTimeoutRef.current = window.setTimeout(() => {\n        if (sheetDataRef.current) {\n          mountSpreadsheet();\n        }\n      }, 500);\n    };\n\n    resizeObserverRef.current = new ResizeObserver(() => {\n      updateDimensions();\n    });\n\n    resizeObserverRef.current.observe(containerRef.current);\n\n    return () => {\n      if (resizeObserverRef.current) {\n        resizeObserverRef.current.disconnect();\n      }\n      if (resizeTimeoutRef.current) {\n        clearTimeout(resizeTimeoutRef.current);\n      }\n    };\n  }, [calculateDimensions, mountSpreadsheet]);\n\n  useEffect(() => {\n    // 只有 URL 有效时才加载（避免空字符串或已 revoke 的 blob URL）\n    if (!url) return;\n\n    let isMounted = true;\n\n    const loadExcel = async () => {\n      if (!containerRef.current) return;\n\n      setLoading(true);\n      setError(null);\n\n      try {\n        const response = await fetcher(url, {\n          mode: 'cors',\n          credentials: 'omit',\n          redirect: 'follow',\n        });\n\n        if (!response.ok) {\n          if (response.status === 404) {\n            throw new Error(t('xlsx.not_found'));\n          } else if (response.status === 403) {\n            throw new Error('无权限访问此文件');\n          } else {\n            throw new Error(`文件加载失败 (${response.status})`);\n          }\n        }\n\n        const arrayBuffer = await response.arrayBuffer();\n\n        if (arrayBuffer.byteLength === 0) {\n          throw new Error('文件为空');\n        }\n\n        // 使用 exceljs 解析\n        const workbook = new ExcelJS.Workbook();\n        await workbook.xlsx.load(arrayBuffer);\n\n        // 转换为 x-data-spreadsheet 数据格式\n        const sheetData = convertWorkbookToSpreadsheetData(workbook);\n\n        if (!isMounted) return;\n\n        sheetDataRef.current = sheetData as unknown as Record<string, unknown>[];\n\n        // 挂载 x-data-spreadsheet\n        mountSpreadsheet();\n\n        setLoading(false);\n      } catch (err) {\n        if (isMounted) {\n          console.error('Excel 解析错误:', err);\n          let errorMsg = t('xlsx.parse_failed');\n          if (err instanceof Error) {\n            errorMsg = err.message;\n          }\n          setError(errorMsg);\n          setLoading(false);\n        }\n      }\n    };\n\n    const timer = setTimeout(() => {\n      requestAnimationFrame(() => {\n        loadExcel();\n      });\n    }, 100);\n\n    return () => {\n      isMounted = false;\n      clearTimeout(timer);\n      sheetDataRef.current = null;\n      if (containerRef.current) {\n        containerRef.current.innerHTML = '';\n      }\n      spreadsheetRef.current = null;\n    };\n  }, [url, mountSpreadsheet]);\n\n  // 暴露接口给父组件\n  useImperativeHandle(ref, () => ({\n    getToolbarGroups: () => [],\n  }), []);\n\n  return (\n    <div className=\"rfp-relative rfp-flex rfp-flex-col rfp-items-center rfp-w-full rfp-h-full\">\n      {/* 加载状态 */}\n      {loading && (\n        <div className=\"rfp-absolute rfp-inset-0 rfp-flex rfp-items-center rfp-justify-center rfp-bg-surface-toolbar rfp-backdrop-blur-sm rfp-z-10\">\n          <div className=\"rfp-text-center\">\n            <div className=\"rfp-w-10 rfp-h-10 md:rfp-w-12 md:rfp-h-12 rfp-mx-auto rfp-mb-3 rfp-border-4 rfp-border-line-strong rfp-border-t-spinner-head rfp-rounded-full rfp-animate-spin\" />\n            <p className=\"rfp-text-xs md:rfp-text-sm rfp-text-fg-secondary rfp-font-medium\">{t('xlsx.loading')}</p>\n          </div>\n        </div>\n      )}\n\n      {/* 错误状态 */}\n      {error && !loading && (\n        <div className=\"rfp-absolute rfp-inset-0 rfp-flex rfp-items-center rfp-justify-center rfp-bg-surface-toolbar rfp-backdrop-blur-sm rfp-z-10\">\n          <RendererError message={t('xlsx.load_failed')} detail={error} />\n        </div>\n      )}\n\n      {/* Spreadsheet 容器 */}\n      {!error && (\n        <div\n          ref={containerRef}\n          className=\"xlsx-spreadsheet-container rfp-w-full rfp-h-full\"\n          style={{ opacity: loading ? 0 : 1 }}\n        />\n      )}\n    </div>\n  );\n});\n"],"names":["XlsxRenderer","forwardRef","url","ref","t","useTranslator","fetcher","useFetcher","loading","setLoading","useState","error","setError","containerRef","useRef","spreadsheetRef","sheetDataRef","resizeObserverRef","resizeTimeoutRef","lastDimensionsRef","calculateDimensions","useCallback","rawWidth","rawHeight","width","height","mountSpreadsheet","isMobile","s","Spreadsheet","useEffect","isInitialRender","updateDimensions","newDimensions","lastDimensions","widthDiff","heightDiff","isMounted","loadExcel","response","arrayBuffer","workbook","ExcelJS","sheetData","convertWorkbookToSpreadsheetData","err","errorMsg","timer","useImperativeHandle","jsxs","jsx","RendererError"],"mappings":"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"}