123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- import { useState, useEffect, ReactNode } from 'react';
- import { useLaunch } from '@tarojs/taro';
- import { IntlProvider } from 'react-intl';
- import { ConfigProvider, Button } from 'antd';
- import { Provider } from 'react-redux';
- import store, { useAppDispatch, useAppSelector } from './states/store';
- import { initializeProductState } from './states/productSlice';
- import { loadI18nMessages } from './states/i18nSlice';
- import './app.css';
- import { lightTheme, darkTheme } from './themes';
- import ProductSelector from './components/ProductSelector';
- import QuotaAlertModal from './pages/security/QuotaAlertModal';
- import AcquisitionTracer from './pages/exam/components/acquisitionTracer';
- import { logger } from './log/logger';
- console.log = logger.log;
- console.warn = logger.warn;
- console.error = logger.error;
- console.log(`process.env.USE_MSW: ${process.env.USE_MSW}`);
- console.log(`process.env.NODE_ENV: ${process.env.NODE_ENV}`);
- if (process.env.NODE_ENV === 'development' && process.env.USE_MSW === 'true') {
- import('../mocks/server')
- .then(({ server }) => {
- server.start({
- onUnhandledRequest: 'error', // 未处理的请求触发网络错误
- });
- console.log(`启动了MSW`);
- })
- .catch((err) => {
- console.warn('Mock server module not found:', err);
- });
- }
- function AppContent({ children }: { children: ReactNode }) {
- const dispatch = useAppDispatch();
- const { messages, loading, error, currentLocale } = useAppSelector(
- (state) => state.i18n
- );
- const [currentTheme, setCurrentTheme] = useState(lightTheme); // 默认使用 light 主题
- const [isI18nReady, setIsI18nReady] = useState(false);
- useLaunch(() => {
- console.log('App launched.');
- });
- const changeTheme = (themeConfig: typeof lightTheme) => {
- setCurrentTheme(themeConfig);
- };
- // 检测浏览器语言
- const browserLocale = (window.navigator.language || 'en')
- .toLowerCase()
- .split('-')[0];
- useEffect(() => {
- store.dispatch(initializeProductState());
- // 应用启动时加载多语言资源
- const localeToLoad = ['zh', 'en'].includes(browserLocale)
- ? browserLocale
- : 'en';
- dispatch(loadI18nMessages(localeToLoad))
- .unwrap()
- .then(() => {
- setIsI18nReady(true);
- })
- .catch((error) => {
- console.error('加载多语言资源失败:', error);
- // 阻止加载后面的页面
- setIsI18nReady(false);
- });
- }, [dispatch, browserLocale]);
- // 显示加载状态
- if (loading || !isI18nReady) {
- return (
- <div
- style={{
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- height: '100vh',
- backgroundColor: lightTheme.token.colorBgLayout,
- color: lightTheme.token.colorText,
- }}
- >
- <div>加载多语言资源中...</div>
- <div style={{ display: 'none' }}>
- {children}
- </div>
- </div>
- );
- }
- // 显示错误状态
- if (error) {
- return (
- <div
- style={{
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- height: '100vh',
- flexDirection: 'column',
- backgroundColor: lightTheme.token.colorBgLayout,
- color: lightTheme.token.colorText,
- }}
- >
- <div>多语言资源加载失败: {error}</div>
- <button
- onClick={() => window.location.reload()}
- style={{ marginTop: '16px', padding: '8px 16px' }}
- >
- 重新加载
- </button>
- <div style={{ display: 'none' }}>
- {children}
- </div>
- </div>
- );
- }
- // children 是将要会渲染的页面
- return (
- <ConfigProvider theme={currentTheme}>
- <IntlProvider
- locale={currentLocale}
- messages={messages as Record<string, string>}
- >
- <style>
- {/*把theme中的colorPrimary转换成变量--color-primary,变量被tailwindcss使用*/}
- {`:root {
- --color-primary: ${currentTheme.token.colorPrimary};
- }`}
- </style>
- <div
- style={{
- backgroundColor: currentTheme.token.colorBgLayout,
- color: currentTheme.token.colorText,
- borderRadius: '8px',
- boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
- }}
- >
- <AcquisitionTracer />
- <QuotaAlertModal />
- {children}
- {process.env.NODE_ENV === 'development' && <ProductSelector />}
- <div
- style={{
- position: 'fixed',
- top: '50%',
- right: 16,
- transform: 'translateY(-50%)',
- display: 'flex',
- flexDirection: 'column',
- gap: 8, // 按钮间距
- zIndex: 1000,
- }}
- >
- <Button
- type="primary"
- shape="circle"
- size="large"
- onClick={() => changeTheme(darkTheme)}
- title="Switch to Dark Theme"
- >
- 🌙
- </Button>
- <Button
- type="primary"
- shape="circle"
- size="large"
- onClick={() => changeTheme(lightTheme)}
- title="Switch to Light Theme"
- >
- ☀️
- </Button>
- </div>
- </div>
- </IntlProvider>
- </ConfigProvider>
- );
- }
- function App({ children }: { children: ReactNode }) {
- // 只在 Cypress 测试环境下暴露 store 到 window 对象
- if (typeof window !== 'undefined' && (window as any).Cypress) {
- (window as any).store = store;
- }
- return (
- <Provider store={store}>
- <AppContent>{children}</AppContent>
- </Provider>
- );
- }
- export default App;
|