Browse Source
Add ErrorBoundary to catch render errors (including chunk load failures from
React.lazy), replacing the previous white-screen crash with a friendly Result
page. Add a network status model (useModel('network')) and OfflineBanner that
shows when the user goes offline. Make request error messages offline-aware.
Clean up dead PWA/ServiceWorker code in global.tsx that never fired.
- ErrorBoundary: catches all render errors, distinguishes chunk load failures
(offline vs online messages), uses antd Result with retry/home buttons
- OfflineBanner: Alert banner in ProLayout content area when offline
- network model: navigator.onLine + online/offline event listeners
- requestErrorConfig: offline-aware error messages in errorHandler
- global.tsx: remove dead sw.offline/sw.updated event listeners
- defaultSettings.ts: remove unused pwa flag
- locales: replace dead PWA i18n keys with network/error keys (8 locales)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pull/11756/head
17 changed files with 250 additions and 135 deletions
@ -1,4 +1,4 @@ |
|||
module.exports = function mdRawLoader(source) { |
|||
this.cacheable?.(); |
|||
return `export default ${JSON.stringify(String(source))};`; |
|||
}; |
|||
}; |
|||
|
|||
@ -0,0 +1,79 @@ |
|||
import { getIntl } from '@umijs/max'; |
|||
import { Button, Card, Result } from 'antd'; |
|||
import React from 'react'; |
|||
|
|||
function isChunkLoadError(error: Error): boolean { |
|||
return ( |
|||
error.name === 'ChunkLoadError' || |
|||
/loading (?:css )?chunk/i.test(error.message) || |
|||
/imported module/i.test(error.message) |
|||
); |
|||
} |
|||
|
|||
interface ErrorBoundaryState { |
|||
hasError: boolean; |
|||
error: Error | null; |
|||
} |
|||
|
|||
export default class ErrorBoundary extends React.Component< |
|||
{ children: React.ReactNode }, |
|||
ErrorBoundaryState |
|||
> { |
|||
state: ErrorBoundaryState = { hasError: false, error: null }; |
|||
|
|||
static getDerivedStateFromError(error: Error): ErrorBoundaryState { |
|||
return { hasError: true, error }; |
|||
} |
|||
|
|||
componentDidCatch(error: Error, info: React.ErrorInfo) { |
|||
console.error('[ErrorBoundary]', error, info.componentStack); |
|||
} |
|||
|
|||
handleRetry = () => { |
|||
if (this.state.error && isChunkLoadError(this.state.error)) { |
|||
window.location.reload(); |
|||
} else { |
|||
this.setState({ hasError: false, error: null }); |
|||
} |
|||
}; |
|||
|
|||
render() { |
|||
if (!this.state.hasError || !this.state.error) { |
|||
return this.props.children; |
|||
} |
|||
|
|||
const { error } = this.state; |
|||
const intl = getIntl(); |
|||
const isOffline = typeof navigator !== 'undefined' && !navigator.onLine; |
|||
const isChunkError = isChunkLoadError(error); |
|||
|
|||
const title = intl.formatMessage({ |
|||
id: isChunkError ? 'app.error.chunk.title' : 'app.error.render.title', |
|||
}); |
|||
const subTitle = intl.formatMessage({ |
|||
id: isChunkError |
|||
? isOffline |
|||
? 'app.error.chunk.description.offline' |
|||
: 'app.error.chunk.description.online' |
|||
: 'app.error.render.description', |
|||
}); |
|||
|
|||
return ( |
|||
<Card variant="borderless"> |
|||
<Result |
|||
status="error" |
|||
title={title} |
|||
subTitle={subTitle} |
|||
extra={[ |
|||
<Button type="primary" key="retry" onClick={this.handleRetry}> |
|||
{intl.formatMessage({ id: 'app.error.retry' })} |
|||
</Button>, |
|||
<a href="/" key="home"> |
|||
<Button>{intl.formatMessage({ id: 'app.error.home' })}</Button> |
|||
</a>, |
|||
]} |
|||
/> |
|||
</Card> |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
import { useIntl, useModel } from '@umijs/max'; |
|||
import { Alert } from 'antd'; |
|||
|
|||
const OfflineBanner: React.FC = () => { |
|||
const { isOnline } = useModel('network'); |
|||
const intl = useIntl(); |
|||
|
|||
if (isOnline) return null; |
|||
|
|||
return ( |
|||
<Alert |
|||
type="warning" |
|||
banner |
|||
closable={false} |
|||
message={intl.formatMessage({ id: 'app.network.offline' })} |
|||
/> |
|||
); |
|||
}; |
|||
|
|||
export default OfflineBanner; |
|||
@ -1,7 +1,15 @@ |
|||
export default { |
|||
'app.pwa.offline': 'আপনি এখন অফলাইন', |
|||
'app.pwa.serviceworker.updated': 'নতুন সামগ্রী উপলব্ধ', |
|||
'app.pwa.serviceworker.updated.hint': |
|||
'বর্তমান পৃষ্ঠাটি পুনরায় লোড করতে দয়া করে "রিফ্রেশ" বোতাম টিপুন', |
|||
'app.pwa.serviceworker.updated.ok': 'রিফ্রেশ', |
|||
'app.network.offline': 'আপনি এখন অফলাইন। কিছু বৈশিষ্ট্য অনুপলব্ধ হতে পারে।', |
|||
'app.error.chunk.title': 'পৃষ্ঠা লোড করতে ব্যর্থ', |
|||
'app.error.chunk.description.offline': |
|||
'আপনার নেটওয়ার্ক সংযোগ বিচ্ছিন্ন হয়েছে। আপনার সংযোগ পরীক্ষা করুন এবং পৃষ্ঠাটি রিফ্রেশ করুন।', |
|||
'app.error.chunk.description.online': |
|||
'পৃষ্ঠা সম্পদ লোড করতে ব্যর্থ। দয়া করে রিফ্রেশ করুন এবং আবার চেষ্টা করুন।', |
|||
'app.error.render.title': 'কিছু ভুল হয়েছে', |
|||
'app.error.render.description': |
|||
'দুঃখিত, এই পৃষ্ঠায় একটি ত্রুটি ঘটেছে। দয়া করে রিফ্রেশ করুন বা হোম পৃষ্ঠায় ফিরে যান।', |
|||
'app.error.retry': 'রিফ্রেশ', |
|||
'app.error.home': 'হোমে ফিরে যান', |
|||
'app.request.offline': |
|||
'নেটওয়ার্ক অনুপলব্ধ। আপনার সংযোগ পরীক্ষা করুন এবং আবার চেষ্টা করুন।', |
|||
}; |
|||
|
|||
@ -1,7 +1,16 @@ |
|||
export default { |
|||
'app.pwa.offline': 'You are offline now', |
|||
'app.pwa.serviceworker.updated': 'New content is available', |
|||
'app.pwa.serviceworker.updated.hint': |
|||
'Please press the "Refresh" button to reload current page', |
|||
'app.pwa.serviceworker.updated.ok': 'Refresh', |
|||
'app.network.offline': |
|||
'You are currently offline. Some features may be unavailable.', |
|||
'app.error.chunk.title': 'Failed to load page', |
|||
'app.error.chunk.description.offline': |
|||
'Your network connection has been lost. Please check your connection and refresh.', |
|||
'app.error.chunk.description.online': |
|||
'Page resources failed to load. Please refresh and try again.', |
|||
'app.error.render.title': 'Something went wrong', |
|||
'app.error.render.description': |
|||
'Sorry, an error occurred on this page. Please refresh or go back to the home page.', |
|||
'app.error.retry': 'Refresh', |
|||
'app.error.home': 'Back Home', |
|||
'app.request.offline': |
|||
'Network unavailable. Please check your connection and try again.', |
|||
}; |
|||
|
|||
@ -1,7 +1,16 @@ |
|||
export default { |
|||
'app.pwa.offline': 'شما اکنون آفلاین هستید', |
|||
'app.pwa.serviceworker.updated': 'مطالب جدید در دسترس است', |
|||
'app.pwa.serviceworker.updated.hint': |
|||
'لطفاً برای بارگیری مجدد صفحه فعلی ، دکمه "تازه سازی" را فشار دهید', |
|||
'app.pwa.serviceworker.updated.ok': 'تازه سازی', |
|||
'app.network.offline': |
|||
'شما اکنون آفلاین هستید. برخی از قابلیتها ممکن است در دسترس نباشند.', |
|||
'app.error.chunk.title': 'بارگذاری صفحه ناموفق بود', |
|||
'app.error.chunk.description.offline': |
|||
'اتصال شبکه شما قطع شده است. اتصال خود را بررسی کنید و صفحه را بارگذاری مجدد کنید.', |
|||
'app.error.chunk.description.online': |
|||
'بارگذاری منابع صفحه ناموفق بود. لطفاً صفحه را بارگذاری مجدد کنید.', |
|||
'app.error.render.title': 'خطایی رخ داد', |
|||
'app.error.render.description': |
|||
'متأسفانه، در این صفحه خطایی رخ داد. لطفاً صفحه را بارگذاری مجدد کنید یا به صفحه اصلی بازگردید.', |
|||
'app.error.retry': 'بارگذاری مجدد', |
|||
'app.error.home': 'بازگشت به صفحه اصلی', |
|||
'app.request.offline': |
|||
'شبکه در دسترس نیست. لطفاً اتصال خود را بررسی و دوباره تلاش کنید.', |
|||
}; |
|||
|
|||
@ -1,7 +1,16 @@ |
|||
export default { |
|||
'app.pwa.offline': 'Koneksi anda terputus', |
|||
'app.pwa.serviceworker.updated': 'Konten baru sudah tersedia', |
|||
'app.pwa.serviceworker.updated.hint': |
|||
'Silahkan klik tombol "Refresh" untuk memuat ulang halaman ini', |
|||
'app.pwa.serviceworker.updated.ok': 'Memuat ulang', |
|||
'app.network.offline': |
|||
'Anda sedang offline. Beberapa fitur mungkin tidak tersedia.', |
|||
'app.error.chunk.title': 'Gagal memuat halaman', |
|||
'app.error.chunk.description.offline': |
|||
'Koneksi jaringan terputus. Periksa koneksi Anda dan muat ulang halaman.', |
|||
'app.error.chunk.description.online': |
|||
'Gagal memuat sumber daya halaman. Muat ulang dan coba lagi.', |
|||
'app.error.render.title': 'Terjadi kesalahan', |
|||
'app.error.render.description': |
|||
'Maaf, terjadi kesalahan pada halaman ini. Muat ulang halaman atau kembali ke beranda.', |
|||
'app.error.retry': 'Muat Ulang', |
|||
'app.error.home': 'Kembali ke Beranda', |
|||
'app.request.offline': |
|||
'Jaringan tidak tersedia. Periksa koneksi Anda dan coba lagi.', |
|||
}; |
|||
|
|||
@ -1,7 +1,16 @@ |
|||
export default { |
|||
'app.pwa.offline': 'あなたは今オフラインです', |
|||
'app.pwa.serviceworker.updated': '新しいコンテンツが利用可能です', |
|||
'app.pwa.serviceworker.updated.hint': |
|||
'現在のページをリロードするには、「更新」ボタンを押してください', |
|||
'app.pwa.serviceworker.updated.ok': 'リフレッシュ', |
|||
'app.network.offline': |
|||
'現在オフラインです。一部の機能が利用できない場合があります', |
|||
'app.error.chunk.title': 'ページの読み込みに失敗しました', |
|||
'app.error.chunk.description.offline': |
|||
'ネットワーク接続が切断されています。接続を確認してページを再読み込みしてください。', |
|||
'app.error.chunk.description.online': |
|||
'ページリソースの読み込みに失敗しました。ページを再読み込みしてください。', |
|||
'app.error.render.title': 'エラーが発生しました', |
|||
'app.error.render.description': |
|||
'申し訳ありません、ページでエラーが発生しました。ページを再読み込みするか、ホームに戻ってください。', |
|||
'app.error.retry': '再読み込み', |
|||
'app.error.home': 'ホームに戻る', |
|||
'app.request.offline': |
|||
'ネットワークに接続できません。接続を確認して再試行してください。', |
|||
}; |
|||
|
|||
@ -1,7 +1,16 @@ |
|||
export default { |
|||
'app.pwa.offline': 'Você está offline agora', |
|||
'app.pwa.serviceworker.updated': 'Novo conteúdo está disponível', |
|||
'app.pwa.serviceworker.updated.hint': |
|||
'Por favor, pressione o botão "Atualizar" para recarregar a página atual', |
|||
'app.pwa.serviceworker.updated.ok': 'Atualizar', |
|||
'app.network.offline': |
|||
'Você está offline no momento. Alguns recursos podem estar indisponíveis.', |
|||
'app.error.chunk.title': 'Falha ao carregar a página', |
|||
'app.error.chunk.description.offline': |
|||
'Sua conexão de rede foi perdida. Verifique sua conexão e atualize a página.', |
|||
'app.error.chunk.description.online': |
|||
'Falha ao carregar os recursos da página. Atualize e tente novamente.', |
|||
'app.error.render.title': 'Algo deu errado', |
|||
'app.error.render.description': |
|||
'Desculpe, ocorreu um erro nesta página. Atualize a página ou volte para a página inicial.', |
|||
'app.error.retry': 'Atualizar', |
|||
'app.error.home': 'Voltar ao Início', |
|||
'app.request.offline': |
|||
'Rede indisponível. Verifique sua conexão e tente novamente.', |
|||
}; |
|||
|
|||
@ -1,6 +1,13 @@ |
|||
export default { |
|||
'app.pwa.offline': '当前处于离线状态', |
|||
'app.pwa.serviceworker.updated': '有新内容', |
|||
'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面', |
|||
'app.pwa.serviceworker.updated.ok': '刷新', |
|||
'app.network.offline': '当前处于离线状态,部分功能可能不可用', |
|||
'app.error.chunk.title': '页面加载失败', |
|||
'app.error.chunk.description.offline': |
|||
'网络连接已断开,请检查网络后刷新页面。', |
|||
'app.error.chunk.description.online': '页面资源加载失败,请刷新页面重试。', |
|||
'app.error.render.title': '页面出现错误', |
|||
'app.error.render.description': |
|||
'抱歉,页面遇到了一些问题,请刷新页面或返回首页。', |
|||
'app.error.retry': '刷新页面', |
|||
'app.error.home': '返回首页', |
|||
'app.request.offline': '网络不可用,请检查网络连接后重试。', |
|||
}; |
|||
|
|||
@ -1,6 +1,14 @@ |
|||
export default { |
|||
'app.pwa.offline': '當前處於離線狀態', |
|||
'app.pwa.serviceworker.updated': '有新內容', |
|||
'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面', |
|||
'app.pwa.serviceworker.updated.ok': '刷新', |
|||
'app.network.offline': '當前處於離線狀態,部分功能可能不可用', |
|||
'app.error.chunk.title': '頁面載入失敗', |
|||
'app.error.chunk.description.offline': |
|||
'網路連線已中斷,請檢查網路後重新整理頁面。', |
|||
'app.error.chunk.description.online': |
|||
'頁面資源載入失敗,請重新整理頁面重試。', |
|||
'app.error.render.title': '頁面出現錯誤', |
|||
'app.error.render.description': |
|||
'抱歉,頁面遇到了一些問題,請重新整理頁面或返回首頁。', |
|||
'app.error.retry': '重新整理', |
|||
'app.error.home': '返回首頁', |
|||
'app.request.offline': '網路不可用,請檢查網路連線後重試。', |
|||
}; |
|||
|
|||
@ -0,0 +1,20 @@ |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
export default function useNetwork() { |
|||
const [isOnline, setIsOnline] = useState( |
|||
typeof navigator !== 'undefined' ? navigator.onLine : true, |
|||
); |
|||
|
|||
useEffect(() => { |
|||
const handleOnline = () => setIsOnline(true); |
|||
const handleOffline = () => setIsOnline(false); |
|||
window.addEventListener('online', handleOnline); |
|||
window.addEventListener('offline', handleOffline); |
|||
return () => { |
|||
window.removeEventListener('online', handleOnline); |
|||
window.removeEventListener('offline', handleOffline); |
|||
}; |
|||
}, []); |
|||
|
|||
return { isOnline }; |
|||
} |
|||
Loading…
Reference in new issue