Skip to content

弹窗报错 #43

Description

@luohuiqun

报错复现:点击打开弹窗,修改后保存,然后再次点击编辑打开,然后报错弹窗
@electroluxcode
Image

// 业务代码 index.jsx

import { Button, Form, Input, message, Modal, Popconfirm, Radio, Select } from 'antd';
import OfficeEditor from '@/components/officeEditor';

const AuditExecutePlan = () => {
const [editorVisible, setEditorVisible] = useState(false);
  const [auditPlanFile, setAuditPlanFile] = useState(null); // 审计实施方案文件
const [documentFile, setDocumentFile] = useState(null);
  const documentFileCacheRef = useRef(null);
const [type, setType] = useState('edit'); 

const openEditor = async (fileName) => {
    console.log('documentFileCacheRef.current', documentFileCacheRef.current);
    // 取消或者保存后本地缓存变更的word文件,防止每次都重新调用接口
    if (documentFileCacheRef.current) {
      setDocumentFile(documentFileCacheRef.current);
      setEditorVisible(true);
      return;
    }
    let res = null;
    if (auditPlanFile && auditPlanFile?.id) {
      // 拉取上次提交的word文件
      res = await downloadFileApi(auditPlanFile.id);
    } else {
      // 拉取模板文件
      res = await getDocumentTemplateFileApi(projectId, documentType);
    }
    const blob =
      res instanceof Blob
        ? res
        : new Blob([res], {
            type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
          });
    const file = new File([blob], fileName, {
      type: blob.type || 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    });
    setDocumentFile(file);
    setEditorVisible(true);
  };

const closeEditor = ({ blob, fileName }) => {
    console.log('closeEditor', blob, fileName);
    const file = new File([blob], fileName, {
      type: blob.type || 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    });
    documentFileCacheRef.current = file;
    setEditorVisible(false);
  };

  const onEditOk = async ({ blob, fileName }) => {
    console.log('onEditOk', blob, fileName);
    const file = new File([blob], fileName, {
      type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    });
    documentFileCacheRef.current = file;
    const res = await uploadFileApi(file, 'auditExecutePlan', MODULE_KEY1);
    setEditorVisible(false);
    setAuditPlanFile(res.result);
    message.success('保存成功');
  };
return(div>
<Form.Item label="审计方案" rules={[{ required: true }]}>
          {auditPlanFile && type === 'edit' && (
            <div className={styles.planSaved}>
              <FileWordOutlined style={{ color: '#1677ff' }} />
              <span className={styles.planFileName}>审计实施方案</span>
              {type === 'edit' && (
                <>
                  <Button
                    className={styles.planAction}
                    type="link"
                    onClick={() => openEditor(FILE_NAME)}
                  >
                    编辑
                  </Button>
                  <Popconfirm
                    title="确认删除审计方案吗?"
                    okText="确认"
                    cancelText="取消"
                    onConfirm={handleDeletePlan}
                  >
                    <Button className={styles.planAction} danger type="link">
                      删除
                    </Button>
                  </Popconfirm>
                </>
              )}
            </div>
          )}
          {!auditPlanFile && type === 'edit' && (
            <Button
              className={styles.planTrigger}
              type="link"
              onClick={() => openEditor(FILE_NAME)}
            >
              在线编辑
            </Button>
          )}
          {type === 'view' && auditPlanFile && (
            <div className="attachments">
              <span className="filename" onClick={() => onPreviewFile(auditPlanFile)}>
                {auditPlanFile?.fileName}
              </span>
              <span
                className="download-icon"
                onClick={() => downloadFile(auditPlanFile)}
                title="下载"
              >
                <DownloadOutlined />
              </span>
            </div>
          )}
        </Form.Item>
{editorVisible && (
        <OfficeEditor
          visible={editorVisible}
          onCancel={closeEditor}
          onOk={onEditOk}
          embedded
          title="审计实施方案"
          defaultFileName="审计实施方案.docx"
          fileType={FILE_TYPE.DOCX}
          file={documentFile}
        />
      )}
<div/>)
}

// 封装的编辑器组件

import { memo, useEffect, useRef, useState } from 'react';
import {
  ONLYOFFICE_CONTAINER_CONFIG,
  ONLYOFFICE_ID,
  OFFICE_XML_EVENT_CONFIG,
  DEFAULT_OFFICE_THEME,
  type FileType,
} from '../onlyoffice-web-comp/const';
import { OnlyOfficeManager } from '../onlyoffice-web-comp/core/onlyoffice-manager';
import { editorManagerFactory } from '../onlyoffice-web-comp/core/editor-manager';
import type { OfficeTheme } from '../onlyoffice-web-comp/internal/editor/types';
import { Button, Drawer, message, Space } from 'antd';
import { createConnectorDemo, type ConnectorDemo } from './connector-demo';
import { subscribeDemoResourceChange } from './resource-switcher';

type OfficePreviewPageProps = {
  title: string;
  defaultFileName: string;
  fileType: FileType;
  accept?: string;
  /** public 目录下的默认文件路径,如 /test.xlsx */
  initialFileUrl?: string;
  /** 嵌入文档页或父容器时使用 h-full */
  embedded?: boolean;
  newButtonLabel?: string;
  visible?: boolean;
  onCancel?: ({ blob, fileName }?: { blob: Blob; fileName: string }) => void;
  onOk?: ({ blob, fileName }?: { blob: Blob; fileName: string }) => void;
  file: File;
};

function LoadingOverlay() {
  return (
    <div className="absolute inset-0 z-20 flex items-center justify-center bg-white/70 backdrop-blur-sm">
      <div className="rounded-lg border border-gray-200 bg-white px-4 py-3 text-sm text-gray-700 shadow">
        加载中...
      </div>
    </div>
  );
}

const OnlyOfficeHost = memo(function OnlyOfficeHost() {
  return (
    <div className={`${ONLYOFFICE_CONTAINER_CONFIG.PARENT_CLASS_NAME} absolute inset-0`}>
      <div id={ONLYOFFICE_ID} className="absolute inset-0" />
    </div>
  );
});

export default function OfficeEditor({
  title,
  defaultFileName,
  fileType,
  embedded = false,
  visible = false,
  onCancel,
  onOk,
  file,
}: OfficePreviewPageProps) {
  const managerRef = useRef<OnlyOfficeManager | null>(null);
  const connectorRef = useRef<ConnectorDemo | null>(null);
  const [loading, setLoading] = useState(false);
  const [currentTheme, setCurrentThemeState] = useState<OfficeTheme>(DEFAULT_OFFICE_THEME);
  const [officeXmlEventEnabled] = useState<boolean>(OFFICE_XML_EVENT_CONFIG.default.isEnable);
  const [officeXmlLimitMb] = useState(
    Math.round(OFFICE_XML_EVENT_CONFIG.default.limitBytes / 1024 / 1024),
  );
  const connectorMessageTimerRef = useRef<number | null>(null);
  const [resourceRevision, setResourceRevision] = useState(0);

  const replaceConnector = (manager: OnlyOfficeManager) => {
    if (connectorRef.current?.isConnected) {
      connectorRef.current.disconnect();
    }
    const connector = createConnectorDemo(() => manager);
    connectorRef.current = connector;
    return connector;
  };

  useEffect(
    () =>
      subscribeDemoResourceChange((state) => {
        setLoading(true);
        if (connectorRef.current?.isConnected) {
          connectorRef.current.disconnect();
        }
        connectorRef.current = null;
        managerRef.current?.destroy();
        managerRef.current = null;
        editorManagerFactory.destroy(ONLYOFFICE_ID);
        setResourceRevision(state.revision);
      }),
    [defaultFileName],
  );

  useEffect(() => {
    let unsubscribeLoading: (() => void) | undefined;
    let disposed = false;
    let ownedManager: OnlyOfficeManager | null = null;
    const containerId = ONLYOFFICE_ID;

    const init = async () => {
      editorManagerFactory.destroy(containerId);
      const loadSession = editorManagerFactory.beginLoadSession(containerId);
      const officeXmlEvent = {
        isEnable: officeXmlEventEnabled,
        limitBytes: officeXmlLimitMb * 1024 * 1024,
      };

      let manager: OnlyOfficeManager;

      if (file) {
        // const file = await fetchPublicFile(initialFileUrl, defaultFileName);
        if (disposed) return;

        manager = await OnlyOfficeManager.createWithFile(
          {
            containerId,
            fileType,
            defaultFileName,
            readOnly: false,
            theme: currentTheme,
            loadSession,
            officeXmlEvent,
          },
          file,
        );
      } else {
        manager = await OnlyOfficeManager.create({
          containerId,
          fileType,
          defaultFileName,
          readOnly: false,
          theme: currentTheme,
          loadSession,
          officeXmlEvent,
          user: {
            id: 'uid',
            name: 'demo-user',
          },
        });
      }

      if (disposed || !editorManagerFactory.isLoadSessionActive(containerId, loadSession)) {
        return;
      }

      ownedManager = manager;
      managerRef.current = manager;
      replaceConnector(manager);
      setCurrentThemeState(manager.getTheme());
      setLoading(false);
      unsubscribeLoading = manager.onLoadingChange(({ loading: next }) => {
        setLoading(next);
      });
    };

    init().catch((err) => {
      if (disposed) return;
      message.error('无法加载编辑器组件');
      setLoading(false);
      console.error('Failed to initialize OnlyOffice:', err);
    });

    return () => {
      disposed = true;
      unsubscribeLoading?.();
      ownedManager?.destroy();
      if (connectorRef.current?.isConnected) {
        connectorRef.current.disconnect();
      }
      connectorRef.current = null;
      editorManagerFactory.destroy(containerId);
      managerRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [resourceRevision, file]);

  useEffect(
    () => () => {
      if (connectorMessageTimerRef.current !== null) {
        window.clearTimeout(connectorMessageTimerRef.current);
      }
    },
    [],
  );

  const exportAsBlobFunc = async () => {
    return await managerRef.current?.exportAsBlob();
  };

  const onClose = async () => {
    onCancel?.(await exportAsBlobFunc());
    if (connectorRef.current?.isConnected) {
      connectorRef.current.disconnect();
    }
    connectorRef.current = null;
    editorManagerFactory.destroy(ONLYOFFICE_ID);
    managerRef.current = null;
  };

  const onConfirm = async () => {
    onOk?.(await exportAsBlobFunc());
    if (connectorRef.current?.isConnected) {
      connectorRef.current.disconnect();
    }
    connectorRef.current = null;
    editorManagerFactory.destroy(ONLYOFFICE_ID);
    managerRef.current = null;
  };

  return (
    <Drawer
      title={title}
      visible={visible}
      onClose={onClose}
      width={'100%'}
      extra={
        <Space>
          <Button onClick={onClose}>取消</Button>
          <Button onClick={onConfirm}>确认</Button>
        </Space>
      }
    >
      <div className={`flex flex-col bg-white ${embedded ? 'h-full min-h-0' : 'h-screen'}`}>
        <div className="relative min-h-0 flex-1">
          <OnlyOfficeHost />
          {loading && <LoadingOverlay />}
        </div>
      </div>
    </Drawer>
  );
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions