refactor(.umirc.ts): change proxy and request in dev mode and production mode
This commit is contained in:
21
.umirc.ts
21
.umirc.ts
@@ -1,8 +1,11 @@
|
|||||||
import { defineConfig } from '@umijs/max';
|
import { defineConfig } from '@umijs/max';
|
||||||
import proxy from './config/proxy';
|
import proxyDev from './config/proxy_dev';
|
||||||
const { REACT_APP_ENV = 'dev' } = process.env as {
|
import proxyProd from './config/proxy_prod';
|
||||||
REACT_APP_ENV: 'dev' | 'test' | 'prod';
|
const envConfig = process.env as { REACT_APP_ENV?: 'dev' | 'test' | 'prod' };
|
||||||
};
|
const rawEnv = envConfig.REACT_APP_ENV;
|
||||||
|
const isProdBuild = process.env.NODE_ENV === 'production';
|
||||||
|
const resolvedEnv = isProdBuild && !rawEnv ? 'prod' : rawEnv || 'dev';
|
||||||
|
const proxyConfig = isProdBuild ? proxyProd : proxyDev;
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
antd: {},
|
antd: {},
|
||||||
@@ -17,16 +20,16 @@ export default defineConfig({
|
|||||||
title: false,
|
title: false,
|
||||||
baseSeparator: '-',
|
baseSeparator: '-',
|
||||||
// support for Vietnamese and English
|
// support for Vietnamese and English
|
||||||
locales: [
|
// locales: [
|
||||||
['vi-VN', 'Tiếng Việt'],
|
// ['vi-VN', 'Tiếng Việt'],
|
||||||
['en-US', 'English'],
|
// ['en-US', 'English'],
|
||||||
],
|
// ],
|
||||||
},
|
},
|
||||||
favicons: ['/logo.png'],
|
favicons: ['/logo.png'],
|
||||||
layout: {
|
layout: {
|
||||||
title: '2025 Sản phẩm của Mobifone v1.0',
|
title: '2025 Sản phẩm của Mobifone v1.0',
|
||||||
},
|
},
|
||||||
proxy: proxy[REACT_APP_ENV],
|
proxy: proxyConfig[resolvedEnv],
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
title: 'Login',
|
title: 'Login',
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -1,3 +1,36 @@
|
|||||||
# README
|
# README
|
||||||
|
|
||||||
`@umijs/max` 模板项目,更多功能参考 [Umi Max 简介](https://umijs.org/docs/max/introduce)
|
`@umijs/max` 模板项目,更多功能参考 [Umi Max 简介](https://umijs.org/docs/max/introduce)
|
||||||
|
|
||||||
|
## Language Switching
|
||||||
|
|
||||||
|
- Lang toggle components (`src/components/switch/LangSwitches.tsx`, `src/components/LanguageSwitcher/index.tsx`) call Umi `setLocale(newLocale, false)` so content re-renders instantly without refreshing the page.
|
||||||
|
- Both components listen for the browser `languagechange` event to keep their UI state in sync when another part of the app switches languages.
|
||||||
|
- Strings are translated via `useTranslation`/`useIntl`; ensure new labels have entries in `src/locales/en-US.ts` and `src/locales/vi-VN.ts`.
|
||||||
|
|
||||||
|
## Proxy & Request Profiles
|
||||||
|
|
||||||
|
- Project uses two sets of configs under `config/`:
|
||||||
|
- `proxy.ts` + `Request.ts` for local development API targets.
|
||||||
|
- `proxy_prod.ts` + `request_prod.ts` for production-like targets.
|
||||||
|
- The runtime selects configs automatically:
|
||||||
|
- `pnpm run dev` / any dev server run ⇒ `NODE_ENV=development` ⇒ uses dev proxy/request.
|
||||||
|
- `pnpm run build` (and other production builds) ⇒ `NODE_ENV=production` ⇒ uses prod proxy/request.
|
||||||
|
- Override the environment by exporting `REACT_APP_ENV` before running commands (values: `dev`, `test`, `prod`). Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use test upstreams in a dev session
|
||||||
|
REACT_APP_ENV=test pnpm run dev
|
||||||
|
|
||||||
|
# Build with testing endpoints
|
||||||
|
NODE_ENV=production REACT_APP_ENV=test pnpm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
- `config/proxy_prod.ts` resolves targets dynamically based on the current hostname; adjust rules there if deployment hosts change.
|
||||||
|
- `config/request_prod.ts` prepends the API base URL from `ApiConfigService`, while the dev file leaves relative paths untouched.
|
||||||
|
|
||||||
|
## Verifying Builds
|
||||||
|
|
||||||
|
- After switching language, confirm UI updates immediately and check console for `languagechange` events.
|
||||||
|
- For proxy changes, inspect network requests in devtools to ensure they hit the expected environment.
|
||||||
|
- Run `pnpm run build && pnpm serve` (or your hosting preview) to validate prod settings before deploying.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const proxy: Record<string, any> = {
|
const proxyDev: Record<string, any> = {
|
||||||
dev: {
|
dev: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://192.168.30.103:81',
|
target: 'http://192.168.30.103:81',
|
||||||
@@ -21,4 +21,4 @@ const proxy: Record<string, any> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default proxy;
|
export default proxyDev;
|
||||||
59
config/proxy_prod.ts
Normal file
59
config/proxy_prod.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// Hàm lấy IP từ hostname hiện tại (chỉ hoạt động runtime)
|
||||||
|
const getCurrentIP = () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const hostname = window.location.hostname;
|
||||||
|
// Nếu là localhost hoặc IP local, trả về IP mặc định
|
||||||
|
if (
|
||||||
|
hostname === 'localhost' ||
|
||||||
|
hostname.startsWith('192.168.') ||
|
||||||
|
hostname.startsWith('10.')
|
||||||
|
) {
|
||||||
|
console.log('Host name: ', hostname);
|
||||||
|
|
||||||
|
return hostname;
|
||||||
|
}
|
||||||
|
// Nếu là domain, có thể cần map sang IP tương ứng
|
||||||
|
return hostname;
|
||||||
|
}
|
||||||
|
return process.env.REACT_APP_API_HOST || '192.168.30.102'; // fallback từ env
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hàm tạo proxy config động
|
||||||
|
const createDynamicProxy = () => {
|
||||||
|
const currentIP = getCurrentIP();
|
||||||
|
const isLocalIP =
|
||||||
|
currentIP.startsWith('192.168.') ||
|
||||||
|
currentIP.startsWith('10.') ||
|
||||||
|
currentIP === 'localhost';
|
||||||
|
|
||||||
|
return {
|
||||||
|
dev: {
|
||||||
|
'/api': {
|
||||||
|
target: `http://${currentIP}:81`,
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
'/test': {
|
||||||
|
target: isLocalIP
|
||||||
|
? `http://${currentIP}:81`
|
||||||
|
: 'https://test-sgw-device.gms.vn',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: !isLocalIP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prod: {
|
||||||
|
'/test': {
|
||||||
|
target: isLocalIP
|
||||||
|
? `http://${currentIP}:81`
|
||||||
|
: 'https://prod-sgw-device.gms.vn',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: !isLocalIP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const proxyProduct: Record<string, any> = createDynamicProxy();
|
||||||
|
|
||||||
|
export default proxyProduct;
|
||||||
105
config/request_prod.ts
Normal file
105
config/request_prod.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { ROUTE_LOGIN } from '@/constants';
|
||||||
|
import { apiConfig } from '@/services/ApiConfigService';
|
||||||
|
import { getToken, removeToken } from '@/utils/localStorageUtils';
|
||||||
|
import { history, RequestConfig } from '@umijs/max';
|
||||||
|
import { message } from 'antd';
|
||||||
|
|
||||||
|
const codeMessage = {
|
||||||
|
200: 'The server successfully returned the requested data。',
|
||||||
|
201: 'New or modified data succeeded。',
|
||||||
|
202: 'A request has been queued in the background (asynchronous task)。',
|
||||||
|
204: 'Data deleted successfully。',
|
||||||
|
400: 'There is an error in the request sent, the server did not perform the operation of creating or modifying data。',
|
||||||
|
401: 'The user does not have permission (token, username, password is wrong) 。',
|
||||||
|
403: 'User is authorized, but access is prohibited。',
|
||||||
|
404: 'The request issued was for a non-existent record, the server did not operate。',
|
||||||
|
406: 'The requested format is not available。',
|
||||||
|
410: 'The requested resource is permanently deleted and will no longer be available。',
|
||||||
|
422: 'When creating an object, a validation error occurred。',
|
||||||
|
500: 'Server error, please check the server。',
|
||||||
|
502: 'Gateway error。',
|
||||||
|
503: 'Service unavailable, server temporarily overloaded or maintained。',
|
||||||
|
504: 'Gateway timeout。',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Runtime configuration
|
||||||
|
export const handleRequestConfig: RequestConfig = {
|
||||||
|
// Unified request settings
|
||||||
|
timeout: 20000,
|
||||||
|
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||||
|
// Error handling: umi@3's error handling scheme.
|
||||||
|
errorConfig: {
|
||||||
|
// Error throwing
|
||||||
|
errorThrower: (res: any) => {
|
||||||
|
console.log('Response from backend:', res);
|
||||||
|
const { success, data, errorCode, errorMessage, showType } = res;
|
||||||
|
if (!success) {
|
||||||
|
const error: any = new Error(errorMessage);
|
||||||
|
error.name = 'BizError';
|
||||||
|
error.info = { errorCode, errorMessage, showType, data };
|
||||||
|
throw error; // Throw custom error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Error catching and handling
|
||||||
|
errorHandler: (error: any) => {
|
||||||
|
if (error.response) {
|
||||||
|
const { status, statusText, data } = error.response;
|
||||||
|
|
||||||
|
// Ưu tiên: codeMessage → backend message → statusText
|
||||||
|
const errMsg =
|
||||||
|
codeMessage[status as keyof typeof codeMessage] ||
|
||||||
|
data?.message ||
|
||||||
|
statusText ||
|
||||||
|
'Unknown error';
|
||||||
|
|
||||||
|
message.error(`❌ ${status}: ${errMsg}`);
|
||||||
|
if (status === 401) {
|
||||||
|
removeToken();
|
||||||
|
history.push(ROUTE_LOGIN);
|
||||||
|
}
|
||||||
|
} else if (error.request) {
|
||||||
|
message.error('🚨 No response from server!');
|
||||||
|
} else {
|
||||||
|
message.error(`⚠️ Request setup error: ${error.message}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Request interceptors
|
||||||
|
requestInterceptors: [
|
||||||
|
(url: string, options: any) => {
|
||||||
|
console.log('URL Request:', url, options);
|
||||||
|
|
||||||
|
// Nếu URL không phải absolute URL, thêm base URL
|
||||||
|
let finalUrl = url;
|
||||||
|
if (!url.startsWith('http')) {
|
||||||
|
const baseUrl = apiConfig.getApiBaseUrl();
|
||||||
|
finalUrl = `${baseUrl}${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getToken();
|
||||||
|
return {
|
||||||
|
url: finalUrl,
|
||||||
|
options: {
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
...options.headers,
|
||||||
|
...(token ? { Authorization: `${token}` } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
// Unwrap data from backend response
|
||||||
|
// responseInterceptors: [
|
||||||
|
// (response) => {
|
||||||
|
// const res = response.data as ResponseStructure<any>;
|
||||||
|
// if (res && res.success) {
|
||||||
|
// // ✅ Trả ra data luôn thay vì cả object
|
||||||
|
// return res.data;
|
||||||
|
// }
|
||||||
|
// return response.data;
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
};
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { LogoutOutlined } from '@ant-design/icons';
|
import { LogoutOutlined } from '@ant-design/icons';
|
||||||
import { history, RunTimeLayoutConfig, useIntl } from '@umijs/max';
|
import { history, RunTimeLayoutConfig, useIntl } from '@umijs/max';
|
||||||
import { Dropdown } from 'antd';
|
import { Dropdown } from 'antd';
|
||||||
import { handleRequestConfig } from '../config/Request';
|
import { handleRequestConfig as devRequestConfig } from '../config/request_dev';
|
||||||
|
import { handleRequestConfig as prodRequestConfig } from '../config/request_prod';
|
||||||
import UnAccessPage from './components/403/403Page';
|
import UnAccessPage from './components/403/403Page';
|
||||||
import LanguageSwitcher from './components/LanguageSwitcher';
|
import LanguageSwitcher from './components/LanguageSwitcher';
|
||||||
import { ROUTE_LOGIN } from './constants';
|
import { ROUTE_LOGIN } from './constants';
|
||||||
@@ -158,4 +159,5 @@ export const layout: RunTimeLayoutConfig = (initialState) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const request = handleRequestConfig;
|
const isProdBuild = process.env.NODE_ENV === 'production';
|
||||||
|
export const request = isProdBuild ? prodRequestConfig : devRequestConfig;
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"extends": "./src/.umi/tsconfig.json",
|
"extends": "./src/.umi/tsconfig.json",
|
||||||
"compilerOptions": {
|
// "compilerOptions": {
|
||||||
"target": "es2017",
|
// "target": "es2017",
|
||||||
"lib": ["dom", "es2017"],
|
// "lib": ["dom", "es2017"],
|
||||||
"module": "esnext",
|
// "module": "esnext",
|
||||||
"moduleResolution": "node",
|
// "moduleResolution": "node",
|
||||||
"jsx": "react-jsx",
|
// "jsx": "react-jsx",
|
||||||
"esModuleInterop": true,
|
// "esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
// "skipLibCheck": true,
|
||||||
"strict": true,
|
// "strict": true,
|
||||||
"forceConsistentCasingInFileNames": true
|
// "forceConsistentCasingInFileNames": true,
|
||||||
},
|
// },
|
||||||
"include": ["src"]
|
// "include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user