|
@@ -1,25 +1,20 @@
|
|
|
-import { PropsWithChildren, useState, useEffect } from 'react';
|
|
|
+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 from './states/store';
|
|
|
+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';
|
|
|
-
|
|
|
-const locale = (window.navigator.language || 'en').toLowerCase().split('-')[0]; // Get locale from browser or default to 'en'
|
|
|
-import messages_en from './assets/i18n/messages/en';
|
|
|
-import messages_zh from './assets/i18n/messages/zh';
|
|
|
import AcquisitionTracer from './pages/exam/components/acquisitionTracer';
|
|
|
import { logger } from './log/logger';
|
|
|
console.log = logger.log;
|
|
|
console.warn = logger.warn;
|
|
|
console.error = logger.error;
|
|
|
-
|
|
|
-const messages = locale === 'zh' ? messages_zh : messages_en;
|
|
|
console.log(`process.env.USE_MSW: ${process.env.USE_MSW}`);
|
|
|
console.log(`process.env.NODE_ENV: ${process.env.NODE_ENV}`);
|
|
|
|
|
@@ -36,78 +31,155 @@ if (process.env.NODE_ENV === 'development' && process.env.USE_MSW === 'true') {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-function App({ children }: PropsWithChildren<React.ReactNode>) {
|
|
|
+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 [currentTheme, setCurrentTheme] = useState(lightTheme); // 默认使用 light 主题
|
|
|
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(true);
|
|
|
+ });
|
|
|
+ }, [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>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示错误状态
|
|
|
+ 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>
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
// children 是将要会渲染的页面
|
|
|
- // return children
|
|
|
return (
|
|
|
- <Provider store={store}>
|
|
|
- <ConfigProvider theme={currentTheme}>
|
|
|
- <IntlProvider locale={locale} messages={messages}>
|
|
|
- <style>
|
|
|
- {/*把theme中的colorPrimary转换成变量--color-primary,变量被tailwindcss使用*/}
|
|
|
- {`:root {
|
|
|
+ <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>
|
|
|
+ </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={{
|
|
|
- backgroundColor: currentTheme.token.colorBgLayout,
|
|
|
- color: currentTheme.token.colorText,
|
|
|
- borderRadius: '8px',
|
|
|
- boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
|
|
+ position: 'fixed',
|
|
|
+ top: '50%',
|
|
|
+ right: 16,
|
|
|
+ transform: 'translateY(-50%)',
|
|
|
+ display: 'flex',
|
|
|
+ flexDirection: 'column',
|
|
|
+ gap: 8, // 按钮间距
|
|
|
+ zIndex: 1000,
|
|
|
}}
|
|
|
>
|
|
|
- <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
|
|
|
- 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>
|
|
|
+ 🌙
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ shape="circle"
|
|
|
+ size="large"
|
|
|
+ onClick={() => changeTheme(lightTheme)}
|
|
|
+ title="Switch to Light Theme"
|
|
|
+ >
|
|
|
+ ☀️
|
|
|
+ </Button>
|
|
|
</div>
|
|
|
- </IntlProvider>
|
|
|
- </ConfigProvider>
|
|
|
+ </div>
|
|
|
+ </IntlProvider>
|
|
|
+ </ConfigProvider>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+function App({ children }: { children: ReactNode }) {
|
|
|
+ return (
|
|
|
+ <Provider store={store}>
|
|
|
+ <AppContent>{children}</AppContent>
|
|
|
</Provider>
|
|
|
);
|
|
|
}
|