You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React.js应用调用documents.get API展示Google Docs内容实现方法咨询

React应用渲染Google Docs文档内容实现方案

方案选型

根据场景可以二选一:

  • 无自定义需求、文档公开可访问:直接用iframe嵌入官方预览地址即可,不需要调用API,代码示例:<iframe src={https://docs.google.com/document/d/${你的文档ID}/preview} width="100%" height="800px" frameBorder="0"></iframe>,缺点是无法自定义样式、无法适配私有文档鉴权场景。
  • 需要自定义渲染、控制权限/样式:走Google Docs API documents.get 接口自行解析渲染,灵活度最高。

接口响应解析核心实现

Google Docs documents.get 返回的内容结构是分层的块级结构,核心内容存在body.content数组中,每个元素对应一个文档块(段落、表格、图片、分页符等),块内嵌套行内内容(文本片段、内联样式、内联图片),递归遍历解析即可映射为React组件。
最简可运行的解析渲染代码如下:

// 解析文本内联样式
const parseTextStyle = (textStyle) => {
  const domStyle = {};
  if (textStyle.bold) domStyle.fontWeight = 700;
  if (textStyle.italic) domStyle.fontStyle = 'italic';
  if (textStyle.underline) domStyle.textDecoration = 'underline';
  if (textStyle.strikethrough) {
    domStyle.textDecoration = domStyle.textDecoration 
      ? `${domStyle.textDecoration} line-through` 
      : 'line-through';
  }
  if (textStyle.fontSize?.magnitude) {
    domStyle.fontSize = textStyle.fontSize.unit === 'PT' 
      ? `${textStyle.fontSize.magnitude}px` 
      : textStyle.fontSize.magnitude;
  }
  if (textStyle.foregroundColor?.color?.rgbColor) {
    const rgb = textStyle.foregroundColor.color.rgbColor;
    domStyle.color = `rgb(${rgb.red * 255}, ${rgb.green * 255}, ${rgb.blue * 255})`;
  }
  if (textStyle.link?.url) domStyle.cursor = 'pointer';
  return domStyle;
};

// 单块内容递归渲染组件
const DocBlockRenderer = ({ block, inlineObjects }) => {
  // 渲染段落类内容(含正文、标题、列表项)
  if (block.paragraph) {
    const { namedStyleType } = block.paragraph.paragraphStyle || {};
    // 映射标题层级
    const TagMap = {
      HEADING_1: 'h1',
      HEADING_2: 'h2',
      HEADING_3: 'h3',
      HEADING_4: 'h4',
      HEADING_5: 'h5',
      HEADING_6: 'h6',
      NORMAL_TEXT: 'p'
    };
    const Tag = TagMap[namedStyleType] || 'p';

    return (
      <Tag style={{ margin: '0.8em 0', lineHeight: 1.6 }}>
        {block.paragraph.elements.map((el, idx) => {
          // 渲染普通文本片段
          if (el.textRun) {
            const { content, textStyle } = el.textRun;
            if (textStyle.link?.url) {
              return (
                <a key={idx} href={textStyle.link.url} style={parseTextStyle(textStyle)}>
                  {content}
                </a>
              );
            }
            return (
              <span key={idx} style={parseTextStyle(textStyle)}>
                {content}
              </span>
            );
          }
          // 渲染内联图片
          if (el.inlineObjectElement) {
            const objId = el.inlineObjectElement.inlineObjectId;
            const imgSource = inlineObjects[objId]?.inlineObjectProperties
              ?.embeddedObject?.imageProperties?.contentUri;
            return imgSource 
              ? <img key={idx} src={imgSource} alt="" style={{ maxWidth: '100%' }} /> 
              : null;
          }
          return null;
        })}
      </Tag>
    );
  }

  // 渲染表格
  if (block.table) {
    return (
      <table style={{ borderCollapse: 'collapse', width: '100%', margin: '1em 0' }}>
        <tbody>
          {block.table.tableRows.map((row, rIdx) => (
            <tr key={rIdx}>
              {row.tableCells.map((cell, cIdx) => (
                <td key={cIdx} style={{ border: '1px solid #e5e7eb', padding: '8px' }}>
                  {cell.content.map((cellBlock, i) => (
                    <DocBlockRenderer 
                      key={i} 
                      block={cellBlock} 
                      inlineObjects={inlineObjects} 
                    />
                  ))}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    );
  }

  // 渲染分页/分节符
  if (block.pageBreak || block.sectionBreak) return <hr style={{ margin: '2em 0' }} />;
  return null;
};

// 文档渲染入口组件
const GoogleDocViewer = ({ apiResponse }) => {
  return (
    <div className="google-doc-container" style={{ maxWidth: 900, margin: '0 auto' }}>
      {apiResponse.body.content.map((block, idx) => (
        <DocBlockRenderer 
          key={idx} 
          block={block} 
          inlineObjects={apiResponse.inlineObjects} 
        />
      ))}
    </div>
  );
};

export default GoogleDocViewer;

避坑说明

  • 调用documents.get接口时必须配置fields请求参数,否则返回结果会缺失图片地址、样式等关键信息,推荐配置值:body(content(paragraph(elements(textRun(content,textStyle),inlineObjectElement(inlineObjectId)),paragraphStyle(namedStyleType)),table(tableRows(tableCells(content))))),inlineObjects
  • 接口返回的图片contentUri是带临时鉴权的链接,有效期约1小时,如需长期展示文档内图片,需要在拿到接口响应后将图片转存到自有对象存储服务
  • 列表、脚注、批注、公式这类特殊格式,只需要在对应块节点中匹配对应字段扩展渲染逻辑即可,整体解析逻辑和段落、表格一致,都是递归遍历块结构
  • 如果不想从零实现全量格式解析,可以选用仅做Google Docs JSON转HTML的轻量工具库,避免引入封装过重、依赖复杂的全量viewer包,减少兼容性问题

内容的提问来源于stack exchange,提问作者Batu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 09:09:57