/** * This file is part of the NocoBase (R) project. * Copyright (c) 2020-2024 NocoBase Co., Ltd. * Authors: NocoBase Team. * * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. * For more information, please refer to: https://www.nocobase.com/agreement. */ import { Button, Result, Typography } from 'antd'; import React, { FC, useState } from 'react'; import { FallbackProps } from 'react-error-boundary'; import { useFlowModel } from '../hooks/useFlowModel'; import { getT } from '../utils'; const { Paragraph, Text } = Typography; /** * 内部组件,用于获取 FlowModel */ const FlowErrorFallbackInner: FC = ({ error, resetErrorBoundary }) => { const [loading, setLoading] = useState(false); const model = useFlowModel(); // 在这里安全地使用 Hook const t = getT(model); const handleCopyError = async () => { setLoading(true); try { const errorInfo = { message: error.message, stack: error.stack, modelInfo: model ? { uid: model.uid, className: model.constructor.name, props: model.props, stepParams: model.stepParams, } : null, timestamp: new Date().toISOString(), }; await navigator.clipboard.writeText(JSON.stringify(errorInfo, null, 2)); console.log('Error information copied to clipboard'); } catch (err) { console.error('Failed to copy error information:', err); } setLoading(false); }; // 检查是否可以下载日志 const canDownloadLogs = model?.context?.api; const handleDownloadLogs = async () => { if (!canDownloadLogs) { console.error('API client not available'); return; } setLoading(true); try { // 从 model.context.api 获取 apiClient const apiClient = model.context.api; // 从 window 对象获取位置信息 const location = { pathname: window.location.pathname, search: window.location.search, hash: window.location.hash, href: window.location.href, }; const res = await apiClient.request({ url: 'logger:collect', method: 'post', responseType: 'blob', data: { error: { message: error.message, stack: error.stack, }, location, modelInfo: model ? { uid: model.uid, className: model.constructor.name, props: model.props, stepParams: model.stepParams, } : null, }, }); const url = window.URL.createObjectURL(new Blob([res.data], { type: 'application/gzip' })); const link = document.createElement('a'); link.setAttribute('href', url); link.setAttribute('download', 'logs.tar.gz'); link.click(); link.remove(); window.URL.revokeObjectURL(url); } catch (err) { console.error('Failed to download logs:', err); } setLoading(false); }; const subTitle = ( {t('This is likely a NocoBase internals bug. Please open an issue at')}{' '} {t('here')} {model && (
Model: {model.constructor.name} (uid: {model.uid})
)}
); return (
{t('Feedback')} , canDownloadLogs && ( ), , resetErrorBoundary && ( ), ].filter(Boolean)} > {error.stack}
); }; /** * 简化版的错误回退组件,专为 flow-engine 设计 * 保持与原始 ErrorFallback 相同的样式和结构 */ export const FlowErrorFallback: FC & { Modal: FC; } = ({ error, resetErrorBoundary }) => { const [loading, setLoading] = useState(false); // 尝试渲染内部组件,如果不在 FlowModelProvider 中则显示简化版本 try { return ; } catch { // 如果不在 FlowModelProvider 中,显示简化版本(不包含 model 信息) const handleCopyError = async () => { setLoading(true); try { const errorInfo = { message: error.message, stack: error.stack, timestamp: new Date().toISOString(), }; await navigator.clipboard.writeText(JSON.stringify(errorInfo, null, 2)); console.log('Error information copied to clipboard'); } catch (err) { console.error('Failed to copy error information:', err); } setLoading(false); }; const subTitle = ( {'This is likely a NocoBase internals bug. Please open an issue at '} here ); return (
Feedback , , resetErrorBoundary && ( ), ].filter(Boolean)} > {error.stack}
); } }; /** * 模态框版本的错误回退组件 */ export const FlowErrorFallbackModal: FC = ({ error, resetErrorBoundary, children, }) => { const [open, setOpen] = useState(false); const defaultChildren = ( Error: {error.message} ); return ( <>
setOpen(true)}>{children || defaultChildren}
{open && (
setOpen(false)} >
e.stopPropagation()} >
)} ); }; FlowErrorFallback.Modal = FlowErrorFallbackModal;