main.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import ReactDOM from 'react-dom/client';
  2. import { RouterProvider } from 'react-router-dom';
  3. import { router } from './router';
  4. import { logBrowserInfo } from '@/utils/browserDetect';
  5. import './index.css';
  6. // 全局视口高度修复:为所有使用 px2vh / vh 的地方提供统一、准确的高度基准
  7. // 修复键盘弹出导致界面缩小的问题:固定初始视口高度,键盘弹出时不更新
  8. const installViewportHeightFix = () => {
  9. if (typeof window === 'undefined') return;
  10. // 记录初始视口高度(键盘弹出前的高度)
  11. let initialHeight = window.innerHeight;
  12. let isInitialized = false;
  13. const setViewportHeight = (forceUpdate = false) => {
  14. // 首次加载时,记录初始高度
  15. if (!isInitialized) {
  16. initialHeight = window.innerHeight;
  17. isInitialized = true;
  18. }
  19. // 如果强制更新(如横竖屏切换),使用当前高度
  20. // 否则使用初始高度,避免键盘弹出时界面缩小
  21. const height = forceUpdate
  22. ? (window.visualViewport?.height ?? window.innerHeight)
  23. : initialHeight;
  24. const vh = height * 0.01;
  25. // 全局 1vh 的真实像素值
  26. document.documentElement.style.setProperty('--vh', `${vh}px`);
  27. };
  28. // 首次设置
  29. setViewportHeight();
  30. // 监听横竖屏切换(真正的视口变化,需要更新)
  31. window.addEventListener('orientationchange', () => {
  32. // 延迟执行,等待实际尺寸变化
  33. setTimeout(() => {
  34. initialHeight = window.innerHeight; // 更新初始高度
  35. setViewportHeight(true);
  36. }, 100);
  37. });
  38. // 监听窗口大小变化(仅处理真正的视口变化,如浏览器窗口调整)
  39. // 使用防抖避免频繁更新
  40. let resizeTimer: NodeJS.Timeout | null = null;
  41. window.addEventListener('resize', () => {
  42. if (resizeTimer) clearTimeout(resizeTimer);
  43. resizeTimer = setTimeout(() => {
  44. // 判断是否为真正的视口变化(高度变化超过 200px 认为是横竖屏切换)
  45. const currentHeight = window.innerHeight;
  46. if (Math.abs(currentHeight - initialHeight) > 200) {
  47. initialHeight = currentHeight;
  48. setViewportHeight(true);
  49. }
  50. }, 150);
  51. });
  52. };
  53. installViewportHeightFix();
  54. // 全局错误处理
  55. window.addEventListener('error', (event) => {
  56. console.error('🚨 全局错误:', event.error);
  57. // 如果错误页面已经加载,不再处理(避免循环)
  58. if (window.location.pathname === '/error') {
  59. return;
  60. }
  61. const errorMessage = event.error?.message || event.message || '应用加载失败';
  62. const errorStack = event.error?.stack || '';
  63. // 跳转到错误页面
  64. const params = new URLSearchParams({
  65. message: `${errorMessage}${errorStack ? `\n\n${errorStack}` : ''}`,
  66. code: 'RUNTIME_ERROR',
  67. retry: 'true',
  68. retryPath: window.location.pathname || '/',
  69. });
  70. window.location.href = `/error?${params.toString()}`;
  71. });
  72. window.addEventListener('unhandledrejection', (event) => {
  73. console.error('🚨 未处理的 Promise 拒绝:', event.reason);
  74. // 如果错误页面已经加载,不再处理(避免循环)
  75. if (window.location.pathname === '/error') {
  76. return;
  77. }
  78. const errorMessage = event.reason?.message || String(event.reason) || '未处理的错误';
  79. // 跳转到错误页面
  80. const params = new URLSearchParams({
  81. message: errorMessage,
  82. code: 'PROMISE_REJECTION',
  83. retry: 'true',
  84. retryPath: window.location.pathname || '/',
  85. });
  86. window.location.href = `/error?${params.toString()}`;
  87. });
  88. // 开发环境输出浏览器信息
  89. logBrowserInfo();
  90. // 渲染应用
  91. try {
  92. const rootElement = document.getElementById('root');
  93. if (!rootElement) {
  94. throw new Error('找不到 #root 元素');
  95. }
  96. ReactDOM.createRoot(rootElement).render(
  97. <RouterProvider router={router} />
  98. );
  99. } catch (error) {
  100. console.error('❌ React 应用渲染失败:', error);
  101. const errorMessage = error instanceof Error ? error.message : String(error);
  102. const errorStack = error instanceof Error ? error.stack : '';
  103. // 跳转到错误页面
  104. const params = new URLSearchParams({
  105. message: `${errorMessage}${errorStack ? `\n\n${errorStack}` : ''}`,
  106. code: 'RENDER_ERROR',
  107. retry: 'true',
  108. retryPath: '/',
  109. });
  110. window.location.href = `/error?${params.toString()}`;
  111. }