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>
  );
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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