1. 预览Excel 文件的时候,需要用到xlsx 库,可以使用npm安装
    npm install xlsx
  2. 预览pdf文件可以直接使用 PDF.js 官方查看器(https://mozilla.github.io/pdf.js/web/viewer.html),通过嵌入 iframe 来加载该查看器,并传递 PDF 文件 URL
  3. 预览word文件可以使用Microsoft 提供的 Office Online Viewer,通过 iframe 将 Office Online Viewer 嵌入到你的 Vue 3 应用中,并传递 Word 文件的 URL

参考代码如下:

    <NButton type="primary" size="small" class="color-white font-size-12px pt-8px pb-8px pl-22px pr-22px h-32px rd-5px mt-16px mr-16px" @click="previewFile">预览</NButton>
    <NModal v-model:show="showPreview" style="width: 90%; max-width: 1200px;">
        <NCard :title="previewTitle" :bordered="false" size="huge">
          <template #header-extra>
            <n-button @click="closePreview" circle style="background-color: transparent;--n-border:none;--n-border-hover:none">
              <template #icon>
                <!-- <n-icon><close-icon /></n-icon> -->
                <icon-material-symbols:close-rounded class="text-icon" />
              </template>
            </n-button>
          </template>
          <div v-if="fileType === 'excel'" class="excel-preview-container">
            <table class="excel-table">
              <tbody>
                <tr v-for="(row, rowIndex) in excelData" :key="rowIndex">
                  <td v-for="(cell, cellIndex) in row" :key="cellIndex" :rowspan="cell.rowSpan" :colspan="cell.colSpan" :style="getCellStyle(cell.style)">
                    {{ cell.value }}
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
          <div v-else-if="fileType === 'pdf'" class="pdf-preview-container">
            <iframe v-if="!pdfError" :src="previewUrl" style="width: 100%; height: 80vh; border: none;"></iframe>
            <div v-else class="error-message">{{ pdfError }}</div>
          </div>
          <iframe v-else-if="previewUrl" :src="previewUrl" style="width: 100%; height: 80vh; border: none;"></iframe>
          <div v-else class="text-center">
            <p>暂无可预览的文件</p>
          </div>
        </NCard>
      </NModal>
import * as XLSX from 'xlsx';

const fileType = ref('');   // 文件类型
const previewUrl = ref(''); // 预览URL
const excelData = ref<any[][]>([]); // Excel数据
const pdfError = ref(''); // 加载pdf错误信息
const showPreview = ref(false); // 是否显示预览窗口

// 预览窗口标题
const previewTitle = computed(() => {
  switch (fileType.value) {
    case 'excel': return 'Excel文件预览';
    case 'pdf': return 'PDF文件预览';
    case 'word': return 'Word文件预览';
    default: return '文件预览';
  }
});

// 获取文件类型
const getFileType = (fileUrl: string): string => {
  const extension = fileUrl.split('.').pop()?.toLowerCase() || '';
  switch (extension) {
    case 'xls':
    case 'xlsx':
      return 'excel';
    case 'pdf':
      return 'pdf';
    case 'doc':
    case 'docx':
      return 'word';
    default:
      return 'unknown';
  }
};

// 文件处理
const previewFile = async () => {
  if (url.value) { // 预览文件路径
    fileType.value = getFileType(url.value);
    console.log("文件类型:", fileType.value);
    console.log("文件URL:", url.value);

    switch (fileType.value) {
      case 'excel':
        try {
          console.log("开始加载Excel文件");
          const response = await fetch(url.value);
          console.log("Fetch响应状态:", response.status);
          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }
          const arrayBuffer = await response.arrayBuffer();
          console.log("获取到ArrayBuffer");
          const data = new Uint8Array(arrayBuffer);
          console.log("创建Uint8Array");
          const workbook = XLSX.read(data, {type: 'array'});
          console.log("XLSX读取成功");
          const firstSheetName = workbook.SheetNames[0];
          const worksheet = workbook.Sheets[firstSheetName];
          // const jsonData = XLSX.utils.sheet_to_json(worksheet, {header: 1});
          // console.log("转换为JSON数据:", jsonData);
          // excelData.value = jsonData;
          // console.log("Excel数据处理完成,excelData:", excelData.value);

          // 处理合并单元格
          const merges = worksheet['!merges'] || [];
          const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
          const processedData: CellData[][] = [];

          for (let R = range.s.r; R <= range.e.r; ++R) {
            const row: CellData[] = [];
            for (let C = range.s.c; C <= range.e.c; ++C) {
              const cellAddress = XLSX.utils.encode_cell({r: R, c: C});
              const cell = worksheet[cellAddress];
              let cellData: CellData = {
                value: cell ? XLSX.utils.format_cell(cell) : '',
                rowSpan: 1,
                colSpan: 1,
                style: cell?.s || {}
              };

              // 检查是否是合并单元格的一部分
              const mergeCell = merges.find(m =>
                R >= m.s.r && R <= m.e.r && C >= m.s.c && C <= m.e.c
              );

              if (mergeCell) {
                if (R === mergeCell.s.r && C === mergeCell.s.c) {
                  // 这是合并单元格的左上角
                  cellData.rowSpan = mergeCell.e.r - mergeCell.s.r + 1;
                  cellData.colSpan = mergeCell.e.c - mergeCell.s.c + 1;
                } else {
                  // 这是合并单元格的其他部分,跳过
                  continue;
                }
              }

              row.push(cellData);
            }
            processedData.push(row);
          }

          excelData.value = processedData;
          console.log("Excel数据处理完成,excelData:", excelData.value);
        } catch (error) {
          console.error('加载Excel文件时出错:', error);
          window.$message.error(`加载Excel文件时出错: ${error.message}`);
        }
        break;
      case 'pdf':
      try {
          // 检查 URL 是否有效
          const response = await fetch(url.value, { method: 'HEAD' });
          if (!response.ok) {
            throw new Error(`无法访问 PDF 文件: ${response.statusText}`);
          }

          // 使用 PDF.js 预览 PDF
          previewUrl.value = `https://mozilla.github.io/pdf.js/web/viewer.html?file=${encodeURIComponent(url.value)}`;
          pdfError.value = '';
        } catch (error) {
          console.error('加载 PDF 文件时出错:', error);
          pdfError.value = `加载 PDF 文件时出错: ${error.message}`;
          window.$message.error(pdfError.value);
        }
        break;
      case 'word':
        // 使用Microsoft Office Online Viewer
        previewUrl.value = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(url.value)}`;
        break;
      default:
        window.$message.warning("不支持的文件类型");
        return;
    }
    showPreview.value = true;
  } else {
    window.$message.warning("暂无可预览的文件");
  }
};
const closePreview = () => {
  showPreview.value = false;
  excelData.value = [];
  previewUrl.value = '';
};

// excel 单元格样式
const getCellStyle = (style: any) => {
  const cellStyle: Record<string, string> = {};
  if (style.fill?.fgColor?.rgb) {
    cellStyle.backgroundColor = `#${style.fill.fgColor.rgb.substring(2)}`;
  }
  if (style.font?.bold) {
    cellStyle.fontWeight = 'bold';
  }
  if (style.font?.italic) {
    cellStyle.fontStyle = 'italic';
  }
  if (style.font?.underline) {
    cellStyle.textDecoration = 'underline';
  }
  if (style.alignment?.horizontal) {
    cellStyle.textAlign = style.alignment.horizontal;
  }
  if (style.alignment?.vertical) {
    cellStyle.verticalAlign = style.alignment.vertical;
  }
  return cellStyle;
};

.excel-preview-container {
  max-height: 80vh;
  overflow: auto;
}

.excel-table {
  border-collapse: collapse;
  width: 100%;
  font-family: Arial, sans-serif;
  font-size: 14px;

  th, td {
    border: 1px solid #e0e0e0;
    padding: 8px;
    text-align: left;
  }

  th {
    background-color: #1E1F25;
    font-weight: bold;
    position: sticky;
    top: 0;
    z-index: 1;
  }

  tr:nth-child(even) {
    background-color: #1E1F25;
  }
}

.pdf-preview-container {
  width: 100%;
  height: 80vh;
  overflow: auto;
}

帅呆的鸡蛋面
1 声望0 粉丝