最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

前端Word導(dǎo)出自定義頁眉表格實(shí)現(xiàn)方案的完整流程

 更新時(shí)間:2025年12月13日 09:33:59   作者:打小就很皮...  
在開發(fā)過程中,我們經(jīng)常會(huì)遇到文件下載(圖片等)和文件導(dǎo)出(excel等)功能,這篇文章主要介紹了前端Word導(dǎo)出自定義頁眉表格實(shí)現(xiàn)方案的完整流程,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

在前端導(dǎo)出 Word 文檔的場(chǎng)景中,自定義頁眉(尤其是包含復(fù)雜表格布局的頁眉)是常見需求。本文將聚焦標(biāo)準(zhǔn) JD 庫項(xiàng)目中,如何通過docx庫實(shí)現(xiàn)固定結(jié)構(gòu)的自定義頁眉表格,結(jié)合實(shí)際代碼,拆解從表格布局設(shè)計(jì)、樣式配置到最終集成的完整流程。

一、需求核心:自定義頁眉表格的目標(biāo)

本次導(dǎo)出功能的頁眉表格需滿足以下要求:

  1. 布局:4 列 2 行結(jié)構(gòu),第二列合并兩行顯示核心信息
  2. 內(nèi)容:包含標(biāo)識(shí)文本(VOSS、Job Description)、職位名稱、版本信息(Issue No.)、日期(Issue Date)、編制人(Compiled By)、審批人(Approved By)
  3. 樣式:統(tǒng)一邊框、文字居中對(duì)齊、固定列寬比例、垂直居中布局
  4. 適配:頁眉在文檔每一頁頂部顯示,與正文內(nèi)容分離

二、技術(shù)選型與依賴準(zhǔn)備

1. 核心依賴

  • docx@9.5.1:負(fù)責(zé) Word 文檔結(jié)構(gòu)構(gòu)建、表格和樣式定義(選擇該版本是因?yàn)?API 穩(wěn)定,適配前端動(dòng)態(tài)導(dǎo)入)
  • file-saver:處理 Blob 對(duì)象轉(zhuǎn)文件下載

2. 依賴安裝

npm install docx@9.5.1 file-saver --save

3. 動(dòng)態(tài)導(dǎo)入優(yōu)化

為避免初始打包體積過大,在導(dǎo)出函數(shù)內(nèi)動(dòng)態(tài)導(dǎo)入docx庫:

const handleExportJd = async () => {
  try {
    // 動(dòng)態(tài)導(dǎo)入docx核心模塊(僅導(dǎo)出時(shí)加載)
    const docx = await import('docx');
    const {
      Table, TableRow, TableCell, TextRun, Paragraph,
      WidthType, BorderStyle, AlignmentType, Header
    } = docx;
    // 后續(xù)頁眉表格構(gòu)建邏輯...
  } catch (error) {
    console.error('導(dǎo)出Word失?。?, error);
    antdApp.message.error('導(dǎo)出Word失敗,請(qǐng)檢查文檔內(nèi)容格式');
  }
};

三、核心組件(DOM)

<div className={styles.rightPane}>
            <Typography.Title className={styles.JdTitle} level={5}>
              <span className={styles.JdBasTitleText}>基礎(chǔ)信息</span>
              <div className={styles.JdButton}>
                <Button className={styles.JdExportButton} onClick={handleExportJd} disabled={!selectedJdDescription}><img src="https://xingge-ai.oss-cn-shenzhen.aliyuncs.com/fusi/down_icon.png" alt="導(dǎo)出JD" width={14} height={14} />導(dǎo)出JD</Button>
              </div>
            </Typography.Title>
            <div className={styles.infoContainer}>
              <div className={styles.infoRow}>
                <div className={styles.infoItem}>
                  <span className={styles.infoLabel}>Issue No.</span>
                  <Input value="001-F002" disabled style={{ width: 150, marginLeft: 10 }} />
                </div>
                <div className={styles.infoItem}>
                  <span className={styles.infoLabel}>Issue Date.</span>
                  <Input value="2024/9/21" disabled style={{ width: 150, marginLeft: 10 }} />
                </div>
              </div>
              <div className={styles.infoRow}>
                <div className={styles.infoItem}>
                  <span className={styles.infoLabel}>Compiled By</span>
                  <Input value={compiledBy} onChange={(e) => setCompiledBy(e.target.value)} style={{ width: 150, marginLeft: 10 }} />
                </div>
                <div className={styles.infoItem}>
                  <span className={styles.infoLabel}>Approved By</span>
                  <Input value={approvedBy} onChange={(e) => setApprovedBy(e.target.value)} style={{ width: 150, marginLeft: 10 }} />
                </div>
              </div>
            </div>
            <Typography.Title className={styles.JdTitle} level={5}>
              <span className={styles.JdTitleText}>JD描述</span>
            </Typography.Title>
            <Spin spinning={rowLoading}>
              <Input.TextArea
                value={selectedJdDescription}
                onChange={(e) => setSelectedJdDescription(e.target.value)}
                autoSize={{ minRows: 27 }}
                placeholder="暫無JD描述"
              />
            </Spin>
          </div>

其中 selectedJdDescription 的內(nèi)容是根據(jù)左側(cè)點(diǎn)擊表格行 -> 調(diào)用 detail 接口獲取 documentId/segmentId,再請(qǐng)求子塊內(nèi)容(獲取接口中 content 的 Markdown 內(nèi)容 ---正文內(nèi)容)

四、自定義頁眉表格的核心實(shí)現(xiàn)

1. 表格結(jié)構(gòu)設(shè)計(jì)(4 列 2 行)

先明確表格的布局規(guī)則,確保各列寬比例合理、內(nèi)容分區(qū)清晰:

列索引(寬比例)第 1 行內(nèi)容第 2 行內(nèi)容特殊配置
列 1(25%)VOSS(標(biāo)識(shí)文本)Job Description + 職位描述獨(dú)立兩行
列 2(35%)職位名稱(從選中 JD 提?。?/td>-合并兩行(rowSpan=2)
列 3(20%)Issue No. + 編號(hào)(001-F002)Compiled By + 輸入值獨(dú)立兩行
列 4(20%)Issue Date + 日期(2024/9/21)Approved By + 輸入值獨(dú)立兩行

2. 關(guān)鍵配置:表格樣式與布局約束

2.1 統(tǒng)一樣式定義(復(fù)用性優(yōu)化)

提前定義表格邊框、文字大小、對(duì)齊方式等公共樣式,避免重復(fù)代碼:

// 表格邊框樣式(統(tǒng)一加粗黑色邊框)
const tableBorder = {
  style: BorderStyle.SINGLE,
  size: 3,
  color: '000000'
};
 
// 文字基礎(chǔ)樣式(根據(jù)內(nèi)容重要性區(qū)分大?。?
const titleTextStyle = { bold: true, size: 45, color: '#FF00FF' }; // VOSS標(biāo)識(shí)
const subtitleTextStyle = { bold: true, size: 20 }; // 標(biāo)簽文本(如Issue No.)
const contentTextStyle = { size: 18 }; // 內(nèi)容文本(如編號(hào)、日期)
const positionTextStyle = { bold: true, size: 28, color: '#000000' }; // 職位名稱

2.2 表格行與單元格實(shí)現(xiàn)

通過TableRowTableCell組件構(gòu)建結(jié)構(gòu),重點(diǎn)處理列合并、寬高約束、對(duì)齊方式:

// 提取職位名稱(從選中的JD內(nèi)容中獲?。?
const positionName = selectedJdDescription 
  ? selectedJdDescription.split('\n').filter(line => line.trim() !== '')[0]?.trim() 
  : '未獲取職位信息';
 
// 構(gòu)建頁眉表格
const headerTable = new Table({
  width: { size: 100, type: WidthType.PERCENTAGE }, // 表格占滿頁面寬度
  border: tableBorder, // 統(tǒng)一表格邊框
  rows: [
    // 第一行(包含VOSS、職位名稱、Issue No.、Issue Date)
    new TableRow({
      height: { value: 800, type: 'dxa' }, // 行高(dxa為Word內(nèi)部單位,800≈28px)
      children: [
        // 列1:VOSS標(biāo)識(shí)(水平+垂直居中)
        new TableCell({
          width: { size: 25, type: WidthType.PERCENTAGE },
          borders: { top: tableBorder, bottom: tableBorder, left: tableBorder, right: tableBorder },
          verticalAlign: 'center', // 垂直居中
          children: [
            new Paragraph({
              children: [new TextRun({ text: 'VOSS', ...titleTextStyle })],
              alignment: AlignmentType.CENTER // 水平居中
            })
          ]
        }),
        // 列2:職位名稱(合并兩行,垂直居中)
        new TableCell({
          rowSpan: 2, // 合并兩行
          width: { size: 35, type: WidthType.PERCENTAGE },
          borders: { top: tableBorder, bottom: tableBorder, left: tableBorder, right: tableBorder },
          verticalAlign: 'center',
          children: [
            new Paragraph({
              children: [new TextRun({ text: positionName, ...positionTextStyle })],
              alignment: AlignmentType.CENTER
            })
          ]
        }),
        // 列3:Issue No. 與編號(hào)
        new TableCell({
          width: { size: 20, type: WidthType.PERCENTAGE },
          borders: { top: tableBorder, bottom: tableBorder, left: tableBorder, right: tableBorder },
          verticalAlign: 'center',
          children: [
            new Paragraph({
              children: [new TextRun({ text: 'Issue No.', ...subtitleTextStyle })],
              alignment: AlignmentType.CENTER,
              spacing: { after: 25 } // 文字間距
            }),
            new Paragraph({
              children: [new TextRun({ text: '001-F002', ...contentTextStyle })],
              alignment: AlignmentType.CENTER
            })
          ]
        }),
        // 列4:Issue Date 與日期
        new TableCell({
          width: { size: 20, type: WidthType.PERCENTAGE },
          borders: { top: tableBorder, bottom: tableBorder, left: tableBorder, right: tableBorder },
          verticalAlign: 'center',
          children: [
            new Paragraph({
              children: [new TextRun({ text: 'Issue Date', ...subtitleTextStyle })],
              alignment: AlignmentType.CENTER,
              spacing: { after: 25 }
            }),
            new Paragraph({
              children: [new TextRun({ text: '2024/9/21', ...contentTextStyle })],
              alignment: AlignmentType.CENTER
            })
          ]
        })
      ]
    }),
    // 第二行(包含Job Description、Compiled By、Approved By)
    new TableRow({
      height: { value: 600, type: 'dxa' },
      children: [
        // 列1:Job Description + 職位描述
        new TableCell({
          width: { size: 25, type: WidthType.PERCENTAGE },
          borders: { top: tableBorder, bottom: tableBorder, left: tableBorder, right: tableBorder },
          verticalAlign: 'center',
          children: [
            new Paragraph({
              children: [new TextRun({ text: 'Job Description', ...subtitleTextStyle })],
              alignment: AlignmentType.CENTER,
              spacing: { after: 15 }
            }),
            new Paragraph({
              children: [new TextRun({ text: '職位描述', ...contentTextStyle })],
              alignment: AlignmentType.CENTER
            })
          ]
        }),
        // 列3:Compiled By(編制人,綁定頁面輸入值)
        new TableCell({
          width: { size: 20, type: WidthType.PERCENTAGE },
          borders: { top: tableBorder, bottom: tableBorder, left: tableBorder, right: tableBorder },
          verticalAlign: 'center',
          children: [
            new Paragraph({
              children: [new TextRun({ text: 'Compiled By', ...subtitleTextStyle })],
              alignment: AlignmentType.CENTER,
              spacing: { after: 25 }
            }),
            new Paragraph({
              children: [new TextRun({ text: compiledBy || '未填寫', ...contentTextStyle })],
              alignment: AlignmentType.CENTER
            })
          ]
        }),
        // 列4:Approved By(審批人,綁定頁面輸入值)
        new TableCell({
          width: { size: 20, type: WidthType.PERCENTAGE },
          borders: { top: tableBorder, bottom: tableBorder, left: tableBorder, right: tableBorder },
          verticalAlign: 'center',
          children: [
            new Paragraph({
              children: [new TextRun({ text: 'Approved By', ...subtitleTextStyle })],
              alignment: AlignmentType.CENTER,
              spacing: { after: 25 }
            }),
            new Paragraph({
              children: [new TextRun({ text: approvedBy || '未填寫', ...contentTextStyle })],
              alignment: AlignmentType.CENTER
            })
          ]
        })
      ]
    })
  ]
});

3. 頁眉與文檔集成

將構(gòu)建好的表格包裝為 Word 頁眉,并配置到文檔中,確保每一頁都顯示該頁眉:

// 3.1 處理 JD 內(nèi)容,轉(zhuǎn)換為 Word 標(biāo)準(zhǔn)格式(同步放大文字大?。?
const jdParagraphs = [];
const lines = selectedJdDescription.split('\n');
let firstHeadingFound = false;
 
for (let i = 0; i < lines.length; i++) {
  const line = lines[i];
  const trimmedLine = line.trim();
 
  if (trimmedLine === '') {
    // 空行:保留間距,優(yōu)化閱讀體驗(yàn)
    jdParagraphs.push(new Paragraph({
      spacing: { after: 80 }, // 空行間距(dxa單位,80≈2.8px)
    }));
  } else if (trimmedLine.startsWith('# ')) {
    // 一級(jí)標(biāo)題:除首個(gè)標(biāo)題外,其余標(biāo)題前添加分頁符
    if (firstHeadingFound) {
      jdParagraphs.push(new PageBreak()); // 分頁符,標(biāo)題獨(dú)立起頁
    } else {
      firstHeadingFound = true; // 標(biāo)記已找到首個(gè)標(biāo)題
    }
    // 一級(jí)標(biāo)題樣式:加粗、放大字號(hào)、左對(duì)齊、底部間距
    jdParagraphs.push(new Paragraph({
      children: [new TextRun({
        text: trimmedLine.slice(2), // 移除Markdown前綴 "# "
        bold: true,
        size: 24, // 字體大?。?4=12pt,放大后更醒目)
        color: "#000000",
        font: "Arial"
      })],
      heading: HeadingLevel.HEADING_1, // 綁定Word一級(jí)標(biāo)題樣式(支持目錄生成)
      spacing: { after: 150 }, // 標(biāo)題底部間距(150≈5.3px)
      alignment: AlignmentType.LEFT,
    }));
  } else if (trimmedLine.startsWith('- ')) {
    // 列表項(xiàng):添加項(xiàng)目符號(hào)、放大字號(hào)、保留間距
    jdParagraphs.push(new Paragraph({
      children: [new TextRun({
        text: trimmedLine.slice(2), // 移除Markdown前綴 "- "
        size: 20, // 字體大小(20=10pt,適配閱讀)
        color: "#333333",
        font: "Arial"
      })],
      bullet: { level: 0 }, // 一級(jí)項(xiàng)目符號(hào)(實(shí)心圓點(diǎn))
      spacing: { after: 80 }, // 列表項(xiàng)底部間距
      alignment: AlignmentType.LEFT,
    }));
  } else {
    // 普通文本:放大字號(hào)、左對(duì)齊、統(tǒng)一間距
    jdParagraphs.push(new Paragraph({
      children: [new TextRun({
        text: trimmedLine,
        size: 20, // 字體大小(20=10pt,比默認(rèn)放大更易讀)
        color: "#333333",
        font: "Arial"
      })],
      spacing: { after: 80 }, // 文本行底部間距
      alignment: AlignmentType.LEFT,
    }));
  }
}
 
// 3.2 包裝頁眉(default 表示所有頁面共用該頁眉)
const header = new Header({
  children: [headerTable], // 傳入之前構(gòu)建的4列2行頁眉表格
  margin: { top: 50, bottom: 50 } // 頁眉內(nèi)邊距(優(yōu)化與正文間距)
});
 
// 3.3 創(chuàng)建Word文檔,整合頁眉與格式化正文
const doc = new Document({
  sections: [
    {
      // 頁面配置:適配頁眉高度,設(shè)置合理邊距
      properties: {
        page: {
          margin: {
            top: 1800, // 頂部邊距(1800≈63px,預(yù)留頁眉空間)
            bottom: 900, // 底部邊距(900≈31.5px)
            left: 1260, // 左側(cè)邊距(1260≈44px)
            right: 1260, // 右側(cè)邊距(1260≈44px)
          },
          size: {
            width: 11906, // A4紙寬度(默認(rèn)值,適配標(biāo)準(zhǔn)打?。?
            height: 16838, // A4紙高度(默認(rèn)值)
          }
        }
      },
      headers: { default: header }, // 綁定全局頁眉(所有頁面顯示)
      children: [
        // 正文內(nèi)容:先添加分頁符(避免頁眉與正文重疊),再導(dǎo)入格式化后的段落
        new PageBreak(),
        ...jdParagraphs // 展開處理后的JD正文段落
      ]
    }
  ]
});
 
// 3.4 生成Blob對(duì)象并觸發(fā)下載
const blob = await Packer.toBlob(doc); // 將文檔打包為Blob
const fileName = `${positionName}_JD_${formattedIssueDate}.docx`; // 文件名:職位名稱_日期.docx
saveAs(blob, fileName); // 調(diào)用file-saver下載文件
message.success('JD文檔導(dǎo)出成功!');

五、handleExportJd 完整邏輯

const handleExportJd = async () => {
    try {
      // 動(dòng)態(tài)導(dǎo)入 docx 庫(適配 9.5.1 版本)
      const docx = await import('docx');
      const {
        Document,
        Packer,
        TextRun,
        Paragraph,
        Table,
        TableRow,
        TableCell,
        WidthType,
        BorderStyle,
        AlignmentType,
        HeadingLevel,
        Header,
        SectionType,
        PageBreak
      } = docx;
 
      // 從 selectedJdDescription 中提取第一個(gè)非空行作為職位名稱
      const getPositionName = () => {
        if (!selectedJdDescription) return '未獲取職位信息';
        // 按換行符分割,過濾空行和純空格行
        const lines = selectedJdDescription.split('\n').filter(line => line.trim() !== '');
        // 返回第一個(gè)非空行的內(nèi)容(去除前后空格)
        return lines.length > 0 ? lines[0].trim() : '未獲取職位信息';
      };
      const positionName = getPositionName();
 
      // 創(chuàng)建頁眉表格
      const headerTable = new Table({
        rows: [
          new TableRow({
            height: { value: 800, type: 'dxa' }, // 增加行高(dxa 為 Word 內(nèi)部單位)
            children: [
              // 第一列第一行:僅顯示 VOSS
              new TableCell({
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'VOSS', bold: true, size: 45, color: '#FF00FF' })],
                    alignment: AlignmentType.CENTER, // 水平居中
                    spacing: { after: 0 },
                  }),
                ],
                width: { size: 25, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                },
                verticalAlign: 'center',
              }),
              // 第二列:合并行顯示提取的職位名稱
              new TableCell({
                rowSpan: 2,
                children: [
                  new Paragraph({
                    children: [new TextRun({
                      text: positionName,
                      bold: true,
                      size: 28,
                      color: '#000000'
                    })],
                    alignment: AlignmentType.CENTER,
                    spacing: { after: 40 },
                  })
                ],
                width: { size: 35, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                },
                verticalAlign: 'center',
              }),
              // 第三列第一行:Issue No.
              new TableCell({
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'Issue No.', bold: true, size: 20 })],
                    alignment: AlignmentType.CENTER,
                    spacing: { after: 25 },
                  }),
                  new Paragraph({
                    children: [new TextRun({ text: '001-F002', size: 18 })],
                    alignment: AlignmentType.CENTER,
                  }),
                ],
                width: { size: 20, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                },
                verticalAlign: 'center',
              }),
              // 第四列第一行:Issue Date
              new TableCell({
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'Issue Date', bold: true, size: 20 })],
                    alignment: AlignmentType.CENTER,
                    spacing: { after: 25 },
                  }),
                  new Paragraph({
                    children: [new TextRun({ text: '2024/9/21', size: 18 })],
                    alignment: AlignmentType.CENTER,
                  }),
                ],
                width: { size: 20, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                },
                verticalAlign: 'center',
              }),
            ],
          }),
          new TableRow({
            height: { value: 600, type: 'dxa' },
            children: [
              // 第一列第二行:顯示 Job Description 和 職位描述(保持居中)
              new TableCell({
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'Job Description', bold: true, size: 20 })],
                    alignment: AlignmentType.CENTER,
                    spacing: { after: 15 },
                  }),
                  new Paragraph({
                    children: [new TextRun({ text: '職位描述', size: 18 })],
                    alignment: AlignmentType.CENTER,
                  }),
                ],
                width: { size: 25, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                },
                verticalAlign: 'center',
              }),
              // 第三列第二行:Compiled By
              new TableCell({
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'Compiled By', bold: true, size: 20 })],
                    alignment: AlignmentType.CENTER,
                    spacing: { after: 25 },
                  }),
                  new Paragraph({
                    children: [new TextRun({ text: compiledBy || '未填寫', size: 18 })],
                    alignment: AlignmentType.CENTER,
                  }),
                ],
                width: { size: 20, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                },
                verticalAlign: 'center',
              }),
              // 第四列第二行:Approved By
              new TableCell({
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'Approved By', bold: true, size: 20 })],
                    alignment: AlignmentType.CENTER,
                    spacing: { after: 25 },
                  }),
                  new Paragraph({
                    children: [new TextRun({ text: approvedBy || '未填寫', size: 18 })],
                    alignment: AlignmentType.CENTER,
                  }),
                ],
                width: { size: 20, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
                },
                verticalAlign: 'center',
              }),
            ],
          }),
        ],
        width: { size: 100, type: WidthType.PERCENTAGE },
        border: { style: BorderStyle.SINGLE, size: 3, color: '000000' },
      });
 
      // 將表格包裝成頁眉對(duì)象
      const header = new Header({
        children: [headerTable],
      });
 
      // 處理 JD 內(nèi)容,轉(zhuǎn)換為 Word 段落(同步放大文字大小)
      const jdParagraphs = [];
      const lines = selectedJdDescription.split('\n');
      let firstHeadingFound = false;
 
      for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        const trimmedLine = line.trim();
 
        if (trimmedLine === '') {
          // 空行(調(diào)整間距)
          jdParagraphs.push(new Paragraph({
            spacing: { after: 80 },
          }));
        } else if (trimmedLine.startsWith('# ')) {
          // 一級(jí)標(biāo)題,前添加分頁符(除了第一個(gè)標(biāo)題)
          if (firstHeadingFound) {
            jdParagraphs.push(new PageBreak());
          } else {
            firstHeadingFound = true;
          }
          jdParagraphs.push(new Paragraph({
            children: [new TextRun({ text: trimmedLine.slice(2), bold: true, size: 24 })],
            heading: HeadingLevel.HEADING_1,
            spacing: { after: 150 },
            alignment: AlignmentType.LEFT,
          }));
        } else if (trimmedLine.startsWith('- ')) {
          // 列表項(xiàng)
          jdParagraphs.push(new Paragraph({
            children: [new TextRun({ text: trimmedLine.slice(2), size: 20 })],
            bullet: { level: 0 },
            spacing: { after: 80 },
            alignment: AlignmentType.LEFT,
          }));
        } else {
          // 普通文本
          jdParagraphs.push(new Paragraph({
            children: [new TextRun({ text: trimmedLine, size: 20 })],
            spacing: { after: 80 },
            alignment: AlignmentType.LEFT,
          }));
        }
      }
 
      // 創(chuàng)建文檔(調(diào)整頁面邊距以適應(yīng)放大的表格)
      const doc = new Document({
        sections: [
          {
            properties: {
              page: {
                margin: {
                  top: 1800, // 增加頂部邊距(適應(yīng)放大后的頁眉)
                  bottom: 900,
                  left: 1260,
                  right: 1260,
                },
              },
            },
            // 設(shè)置頁眉(每一頁顯示)
            headers: {
              default: header,
            },
            children: [
              new PageBreak(), // 標(biāo)題后分頁
              ...jdParagraphs,
            ],
          },
        ],
      });
 
      // 導(dǎo)出文檔
      const blob = await Packer.toBlob(doc);
      const fileName = positionName !== '未獲取職位信息' ? `${positionName}_JD.docx` : 'JD文檔.docx';
      saveAs(blob, fileName);
      antdApp.message.success('導(dǎo)出 Word 成功');
    } catch (error) {
      console.error('導(dǎo)出 Word 失?。?, error);
      antdApp.message.error('導(dǎo)出 Word 失敗,請(qǐng)檢查文檔內(nèi)容格式');
    }
  };

如果遇到發(fā)送給其他人(windows、mac)打不開或者不兼容

const handleExportJd = async () => {
    try {
      // 動(dòng)態(tài)導(dǎo)入 docx 庫
      const docx = await import('docx');
      const {
        Document,
        Packer,
        TextRun,
        Paragraph,
        Table,
        TableRow,
        TableCell,
        WidthType,
        BorderStyle,
        AlignmentType,
        HeadingLevel,
        Header,
        PageBreak,
      } = docx;
 
      // 從 selectedJdDescription 中提取第一個(gè)非空行作為職位名稱
      const getPositionName = () => {
        if (!selectedJdDescription) return '未獲取職位信息';
        const lines = selectedJdDescription.split('\n').filter(line => line.trim() !== '');
        return lines.length > 0 ? lines[0].trim() : '未獲取職位信息';
      };
      const positionName = getPositionName();
 
      // 創(chuàng)建頁眉表格
      const headerTable = new Table({
        rows: [
          new TableRow({
            children: [
              // 第一列第一行:僅顯示 VOSS
              new TableCell({
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'VOSS', bold: true, size: 35, color: '#FF00FF' })],
                    alignment: AlignmentType.CENTER,
                  }),
                ],
                width: { size: 25, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                },
              }),
              // 第二列:合并行顯示提取的職位名稱
              new TableCell({
                rowSpan: 2,
                children: [
                  new Paragraph({
                    children: [new TextRun({
                      text: positionName,
                      bold: true,
                      size: 22,
                      color: '#000000'
                    })],
                    alignment: AlignmentType.CENTER,
                  })
                ],
                width: { size: 35, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                },
                verticalAlign: AlignmentType.CENTER, // 設(shè)置垂直居中對(duì)齊
              }),
              // 第三列第一行:Issue No.
              new TableCell({
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'Issue No.', bold: true, size: 18 })],
                    alignment: AlignmentType.CENTER,
                  }),
                  new Paragraph({
                    children: [new TextRun({ text: issueNo || '未填寫', size: 16 })],
                    alignment: AlignmentType.CENTER,
                  }),
                ],
                width: { size: 20, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                },
              }),
              // 第四列第一行:Issue Date
              new TableCell({
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'Issue Date', bold: true, size: 18 })],
                    alignment: AlignmentType.CENTER,
                  }),
                  new Paragraph({
                    children: [new TextRun({ text: issueDate || '未填寫', size: 16 })],
                    alignment: AlignmentType.CENTER,
                  }),
                ],
                width: { size: 20, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                },
              }),
            ],
          }),
          new TableRow({
            children: [
              // 第一列第二行:顯示 Job Description 和 職位描述
              new TableCell({
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'Job Description', bold: true, size: 18 })],
                    alignment: AlignmentType.CENTER,
                  }),
                  new Paragraph({
                    children: [new TextRun({ text: '職位描述', size: 16 })],
                    alignment: AlignmentType.CENTER,
                  }),
                ],
                width: { size: 25, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                },
              }),
              // 第三列第二行:Compiled By
              new TableCell({
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'Compiled By', bold: true, size: 18 })],
                    alignment: AlignmentType.CENTER,
                  }),
                  new Paragraph({
                    children: [new TextRun({ text: compiledBy || '未填寫', size: 16 })],
                    alignment: AlignmentType.CENTER,
                  }),
                ],
                width: { size: 20, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                },
              }),
              // 第四列第二行:Approved By
              new TableCell({
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'Approved By', bold: true, size: 18 })],
                    alignment: AlignmentType.CENTER,
                  }),
                  new Paragraph({
                    children: [new TextRun({ text: approvedBy || '未填寫', size: 16 })],
                    alignment: AlignmentType.CENTER,
                  }),
                ],
                width: { size: 20, type: WidthType.PERCENTAGE },
                borders: {
                  top: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  bottom: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  left: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                  right: { style: BorderStyle.SINGLE, size: 2, color: '000000' },
                },
              }),
            ],
          }),
        ],
        width: { size: 100, type: WidthType.PERCENTAGE },
      });
 
      // 將表格包裝成頁眉對(duì)象,簡(jiǎn)化配置以確保兼容性
      const header = new Header({
        children: [headerTable],
        // 設(shè)置頁眉的邊距和定位
        properties: {
          headerPosition: {
            value: 0, // 距離頁面頂部的距離
          },
        },
      });
 
      // 處理 JD 內(nèi)容,轉(zhuǎn)換為 Word 段落
      const jdParagraphs = [];
      const lines = selectedJdDescription.split('\n');
      let firstHeadingFound = false;
 
      for (const line of lines) {
        const trimmedLine = line.trim();
 
        if (trimmedLine === '') {
          // 空行
          jdParagraphs.push(new Paragraph({ spacing: { after: 50 } }));
        } else if (trimmedLine.startsWith('# ')) {
          // 一級(jí)標(biāo)題
          if (firstHeadingFound) {
            jdParagraphs.push(new PageBreak());
          } else {
            firstHeadingFound = true;
          }
          jdParagraphs.push(new Paragraph({
            children: [new TextRun({ text: trimmedLine.slice(2), bold: true, size: 22 })],
            heading: HeadingLevel.HEADING_1,
            spacing: { after: 100 },
          }));
        } else if (trimmedLine.startsWith('- ')) {
          // 列表項(xiàng)
          jdParagraphs.push(new Paragraph({
            children: [new TextRun({ text: trimmedLine.slice(2), size: 18 })],
            bullet: { level: 0 },
            spacing: { after: 50 },
          }));
        } else {
          // 普通文本
          jdParagraphs.push(new Paragraph({
            children: [new TextRun({ text: trimmedLine, size: 18 })],
            spacing: { after: 50 },
          }));
        }
      }
 
      // 創(chuàng)建文檔
      const doc = new Document({
        sections: [
          {
            properties: {
              page: {
                margin: {
                  top: 1800, // 增加頂部邊距以確保頁眉能正確顯示
                  bottom: 800,
                  left: 1000,
                  right: 1000,
                },
              },
              // 確保所有頁面都使用相同的頁眉配置
              headerFooter: {
                differentFirstPage: false,
                differentOddEven: false,
              },
            },
            // 為所有頁面類型都設(shè)置相同的頁眉
            headers: {
              default: header,
              first: header,
              even: header,
              odd: header,
            },
            children: [...jdParagraphs],
          },
        ],
      });
 
      // 導(dǎo)出文檔 - 使用更可靠的方式處理 blob
      const blob = await Packer.toBlob(doc);
      const fileName = positionName !== '未獲取職位信息' ? `${positionName}_JD.docx` : 'JD文檔.docx';
 
      try {
        // 確保blob類型正確
        const docxBlob = new Blob([blob], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
 
        // 使用標(biāo)準(zhǔn)的下載方法,避免依賴外部庫
        const url = window.URL.createObjectURL(docxBlob);
        const a = document.createElement('a');
        a.href = url;
        a.download = fileName;
        document.body.appendChild(a);
 
        // 觸發(fā)下載
        if (document.createEvent) {
          const event = document.createEvent('MouseEvents');
          event.initEvent('click', true, true);
          a.dispatchEvent(event);
        } else {
          a.click();
        }
 
        // 清理
        setTimeout(() => {
          document.body.removeChild(a);
          window.URL.revokeObjectURL(url);
        }, 100);
 
        antdApp.message.success('導(dǎo)出 Word 成功');
      } catch (downloadError) {
        console.error('下載文件失敗:', downloadError);
 
        // 降級(jí)處理:嘗試直接打開blob
        const url = window.URL.createObjectURL(blob);
        window.open(url, '_blank');
        antdApp.message.info('導(dǎo)出成功,文件已在新窗口打開');
      }
    } catch (error) {
      console.error('導(dǎo)出 Word 失敗:', error);
      // 提供更詳細(xì)的錯(cuò)誤信息
      if (error instanceof Error) {
        console.error('Error message:', error.message);
        console.error('Error stack:', error.stack);
      }
      antdApp.message.error('導(dǎo)出 Word 失敗,請(qǐng)檢查文檔內(nèi)容格式');
    }
  };

僅供參考?。。?/p>

六、關(guān)鍵技術(shù)點(diǎn)總結(jié)

  1. 列合并實(shí)現(xiàn):通過TableCellrowSpan屬性實(shí)現(xiàn)跨行合并,適用于需要縱向占用多行的內(nèi)容(如職位名稱)。
  2. 寬高控制:列寬使用WidthType.PERCENTAGE(百分比)確保適配不同頁面尺寸,行高使用dxa單位(Word 原生單位)確保精度。
  3. 對(duì)齊方式:結(jié)合verticalAlign: 'center'(垂直居中)和AlignmentType.CENTER(水平居中),實(shí)現(xiàn)表格內(nèi)容的完全居中。
  4. 樣式復(fù)用:提取公共樣式(邊框、文字大?。档痛a冗余,便于后續(xù)維護(hù)。

七、注意事項(xiàng)

  1. docx庫的單位差異:dxa是 Word 內(nèi)部長(zhǎng)度單位(1pt≈20dxa),設(shè)置行高時(shí)需注意換算,避免頁眉過高或過低。
  2. 邊框樣式一致性:需為每個(gè)單元格單獨(dú)設(shè)置邊框(或通過表格全局邊框配置),避免出現(xiàn)邊框缺失。
  3. 動(dòng)態(tài)導(dǎo)入兼容性:確保docx版本與導(dǎo)入方式匹配(v9.5.1 支持 ES 模塊動(dòng)態(tài)導(dǎo)入),避免版本沖突。

八、最終呈現(xiàn)效果

下載到本地之后打開即可:

通過以上方案,可實(shí)現(xiàn)結(jié)構(gòu)固定、樣式統(tǒng)一、數(shù)據(jù)動(dòng)態(tài)的自定義頁眉表格,滿足 Word 導(dǎo)出場(chǎng)景中的復(fù)雜頁眉需求,同時(shí)保證導(dǎo)出文檔的專業(yè)性和規(guī)范性。

注意?:  本文主要是針對(duì)如何實(shí)現(xiàn) word 文檔頁眉表格形式的設(shè)置,其余邏輯以及業(yè)務(wù)需自行修改?。?!

到此這篇關(guān)于前端Word導(dǎo)出自定義頁眉表格實(shí)現(xiàn)方案的文章就介紹到這了,更多相關(guān)前端Word導(dǎo)出自定義頁眉表格內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 最簡(jiǎn)單的JS實(shí)現(xiàn)json轉(zhuǎn)csv的方法

    最簡(jiǎn)單的JS實(shí)現(xiàn)json轉(zhuǎn)csv的方法

    這篇文章主要介紹了最簡(jiǎn)單的JS實(shí)現(xiàn)json轉(zhuǎn)csv的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-01-01
  • 利用svg實(shí)現(xiàn)帶加載進(jìn)度的loading

    利用svg實(shí)現(xiàn)帶加載進(jìn)度的loading

    svg是基于XML,由World?Wide?Web?Consortium?(W3C)聯(lián)盟開發(fā)的一種開放標(biāo)準(zhǔn)的矢量圖形語言,可讓你設(shè)計(jì)激動(dòng)人心的、高分辨率的Web圖形頁面。本文將使用svg實(shí)現(xiàn)一個(gè)帶加載進(jìn)度的loading,需要的可以參考一下
    2022-11-11
  • 實(shí)現(xiàn)點(diǎn)擊列表彈出列表索引的兩種方式

    實(shí)現(xiàn)點(diǎn)擊列表彈出列表索引的兩種方式

    使用利用事件冒泡委托給列表的父節(jié)點(diǎn)去處理的方式第二種方式就是使用閉包了,感興趣的你可以參考下本文,或許對(duì)你學(xué)習(xí)js有所幫助
    2013-03-03
  • javascript正則表達(dá)式模糊匹配IP地址功能示例

    javascript正則表達(dá)式模糊匹配IP地址功能示例

    這篇文章主要介紹了javascript正則表達(dá)式模糊匹配IP地址功能,結(jié)合簡(jiǎn)單實(shí)例形式演示了JS模糊匹配IP地址的實(shí)現(xiàn)方法,涉及針對(duì)數(shù)字及字符串的相關(guān)正則判定與匹配操作技巧,需要的朋友可以參考下
    2017-01-01
  • 用Javascript實(shí)現(xiàn)Windows任務(wù)管理器的代碼

    用Javascript實(shí)現(xiàn)Windows任務(wù)管理器的代碼

    在Windows系統(tǒng)上,自從98系統(tǒng)以來就提供了腳本宿主(Windows Scripting Host 簡(jiǎn)稱WSH)的功能,WSH可以加載并運(yùn)行JS和VBS腳本,并支持調(diào)用系統(tǒng)的COM組件,在COM組件的支持下腳本可以輕松實(shí)現(xiàn)非常強(qiáng)大的功能
    2012-03-03
  • bootstrap datetimepicker2.3.11時(shí)間插件使用

    bootstrap datetimepicker2.3.11時(shí)間插件使用

    這篇文章主要為大家詳細(xì)介紹了bootstrap datetimepicker2.3.11時(shí)間插件使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-11-11
  • js讓彈出新窗口居中顯示的腳本

    js讓彈出新窗口居中顯示的腳本

    我們經(jīng)常需要在新窗口彈出頁面并需要居中顯示
    2008-01-01
  • 淺談JavaScript正則表達(dá)式-非捕獲性分組

    淺談JavaScript正則表達(dá)式-非捕獲性分組

    下面小編就為大家?guī)硪黄狫avaScript正則表達(dá)式-非捕獲性分組。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-03-03
  • bootstrap table實(shí)現(xiàn)單擊單元格可編輯功能

    bootstrap table實(shí)現(xiàn)單擊單元格可編輯功能

    這篇文章主要為大家詳細(xì)介紹了bootstrap table實(shí)現(xiàn)單擊單元格可編輯功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • 下載站控制介紹字?jǐn)?shù)顯示的腳本 顯示全部 隱藏介紹等功能

    下載站控制介紹字?jǐn)?shù)顯示的腳本 顯示全部 隱藏介紹等功能

    有一些下載網(wǎng)站為了整體的統(tǒng)一,盡量的控制軟件介紹的字?jǐn)?shù)顯示,要不因?yàn)樽謹(jǐn)?shù)介紹太多導(dǎo)致用戶看到下載鏈接比較靠后等原因而一個(gè)小功能的實(shí)現(xiàn)。
    2009-09-09

最新評(píng)論

中阳县| 广东省| 乌鲁木齐县| 三亚市| 会同县| 林芝县| 敦化市| 杨浦区| 方城县| 墨竹工卡县| 连平县| 红河县| 华池县| 拜城县| 台东市| 延寿县| 娄烦县| 河西区| 观塘区| 百色市| 南昌市| 本溪市| 柯坪县| 乐至县| 漯河市| 化德县| 洪雅县| 福安市| 崇阳县| 漯河市| 承德县| 田林县| 四子王旗| 博客| 象州县| 南通市| 江川县| 扬州市| 潮州市| 古蔺县| 务川|