1601 lines
37 KiB
Markdown
1601 lines
37 KiB
Markdown
# 📊 PHÂN TÍCH CHI TIẾT DỰ ÁN FE-DEVICE-SGW
|
||
|
||
## 🎯 **TỔNG QUAN DỰ ÁN**
|
||
|
||
Đây là một hệ thống **Giám sát tàu cá** (Ship Monitoring System) được phát triển cho **Mobifone**, sử dụng **UmiJS (Ant Design Pro)** framework với React, TypeScript, và TailwindCSS.
|
||
|
||
---
|
||
|
||
## 🏗️ **CẤU TRÚC TỔNG THỂ**
|
||
|
||
```
|
||
FE-DEVICE-SGW/
|
||
├── config/ # Cấu hình ứng dụng
|
||
├── src/ # Mã nguồn chính
|
||
├── public/ # Tài nguyên tĩnh
|
||
├── mock/ # Dữ liệu giả lập
|
||
└── node_modules/ # Thư viện
|
||
```
|
||
|
||
---
|
||
|
||
## 📂 **PHÂN TÍCH CHI TIẾT TỪNG FILE & THƯ MỤC**
|
||
|
||
### **1. CẤU HÌNH GỐC (Root Configuration)**
|
||
|
||
#### **.umirc.ts** - File cấu hình chính của UmiJS
|
||
|
||
```typescript
|
||
// Định nghĩa môi trường: dev/test/prod
|
||
const { REACT_APP_ENV = 'dev' } = process.env;
|
||
|
||
// Cấu hình các plugin:
|
||
- antd: Sử dụng Ant Design
|
||
- access: Quản lý quyền truy cập
|
||
- model: State management (tương tự Redux)
|
||
- initialState: Trạng thái khởi tạo
|
||
- request: HTTP request handler
|
||
- locale: Đa ngôn ngữ (mặc định: vi-VN)
|
||
|
||
// Layout:
|
||
- Logo, title: "2025 Sản phẩm của Mobifone v1.0"
|
||
- Theme: light, layout mix
|
||
- Icon font từ Alibaba
|
||
|
||
// Routes (Routing):
|
||
1. /login → Trang đăng nhập
|
||
2. / → Redirect về /map
|
||
3. /map → Trang giám sát (có icon map)
|
||
4. /trip → Trang chuyến đi (có icon specification)
|
||
```
|
||
|
||
**Ý nghĩa**: File này là "trái tim" của ứng dụng, quy định cách ứng dụng hoạt động.
|
||
|
||
---
|
||
|
||
#### **package.json** - Quản lý dependencies
|
||
|
||
```json
|
||
Dependencies chính:
|
||
- @umijs/max: Framework chính
|
||
- antd: UI component library
|
||
- ol (OpenLayers): Thư viện bản đồ
|
||
- moment: Xử lý thời gian
|
||
- classnames: Quản lý className động
|
||
|
||
Scripts:
|
||
- dev: Chạy môi trường development
|
||
- build: Build production
|
||
- format: Format code với Prettier
|
||
```
|
||
|
||
---
|
||
|
||
#### **tailwind.config.js** - Cấu hình TailwindCSS
|
||
|
||
```javascript
|
||
// Quét các file .tsx trong:
|
||
- src/pages/**/*.tsx
|
||
- src/components/**/*.tsx
|
||
- src/layouts/**/*.tsx
|
||
// để tạo CSS utility classes
|
||
```
|
||
|
||
---
|
||
|
||
### **2. FOLDER CONFIG/ - CẤU HÌNH ỨNG DỤNG**
|
||
|
||
#### **config/proxy.ts** - Cấu hình proxy API
|
||
|
||
```typescript
|
||
dev: /api → http://192.168.30.102:81 // Môi trường phát triển
|
||
test: /test → https://test-sgw-device.gms.vn
|
||
prod: /test → https://prod-sgw-device.gms.vn
|
||
```
|
||
|
||
**Vai trò**: Chuyển hướng API calls để tránh CORS, dễ switch giữa các môi trường.
|
||
|
||
---
|
||
|
||
#### **config/Request.ts** - Cấu hình HTTP Request
|
||
|
||
```typescript
|
||
// ErrorShowType: Định nghĩa cách hiển thị lỗi
|
||
enum ErrorShowType {
|
||
SILENT = 0, // Không hiển thị
|
||
WARN_MESSAGE = 1, // Cảnh báo
|
||
ERROR_MESSAGE = 2, // Lỗi
|
||
NOTIFICATION = 3, // Thông báo
|
||
REDIRECT = 9, // Chuyển hướng
|
||
}
|
||
|
||
// Request config:
|
||
- timeout: 20s
|
||
- errorHandler: Xử lý lỗi HTTP (401 → redirect login)
|
||
- requestInterceptors: Thêm Authorization header với token
|
||
|
||
// Luồng request:
|
||
1. Lấy token từ localStorage
|
||
2. Gắn token vào header
|
||
3. Gửi request
|
||
4. Nhận response
|
||
5. Xử lý lỗi nếu có
|
||
```
|
||
|
||
**Ý nghĩa**: Tập trung xử lý tất cả HTTP requests, xử lý lỗi thống nhất.
|
||
|
||
---
|
||
|
||
### **3. FOLDER SRC/ - MÃ NGUỒN CHÍNH**
|
||
|
||
#### **src/app.tsx** - Bootstrap Application
|
||
|
||
```typescript
|
||
// getInitialState(): Chạy TRƯỚC KHI app render
|
||
export async function getInitialState() {
|
||
1. Lấy token từ localStorage
|
||
2. Nếu không có token → redirect login
|
||
3. Parse JWT token
|
||
4. Kiểm tra token còn hạn không (> 1 giờ)
|
||
5. Nếu hết hạn → xóa token, redirect login
|
||
6. Return { currentUser, exp }
|
||
}
|
||
|
||
// layout: Cấu hình layout của app
|
||
export const layout: RunTimeLayoutConfig = (initialState) => {
|
||
return {
|
||
logo: logo image
|
||
fixedHeader: true
|
||
navTheme: 'light'
|
||
avatarProps: {
|
||
// Hiển thị dropdown với nút "Đăng xuất"
|
||
onClick: → removeToken() → redirect login
|
||
}
|
||
onPageChange: Kiểm tra initialState mỗi lần chuyển trang
|
||
unAccessible: Component hiển thị khi không có quyền truy cập
|
||
}
|
||
}
|
||
|
||
// request: Export cấu hình request
|
||
export const request = handleRequestConfig;
|
||
```
|
||
|
||
**Luồng khởi động ứng dụng**:
|
||
|
||
```
|
||
1. User truy cập URL
|
||
2. UmiJS gọi getInitialState()
|
||
3. Kiểm tra authentication
|
||
4. Nếu OK: render app với layout
|
||
5. Nếu FAIL: redirect /login
|
||
```
|
||
|
||
---
|
||
|
||
#### **src/access.ts** - Quản lý quyền truy cập
|
||
|
||
```typescript
|
||
// Nhận initialState từ app.tsx
|
||
export default (initialState: API.UserInfo) => {
|
||
const canSeeAdmin = !!(
|
||
initialState && initialState.name !== 'dontHaveAccess'
|
||
);
|
||
return {
|
||
canSeeAdmin, // Có thể dùng trong routes để giới hạn quyền
|
||
};
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### **4. FOLDER SRC/CONSTANTS/ - HẰNG SỐ**
|
||
|
||
#### **constants/index.ts**
|
||
|
||
```typescript
|
||
// Token key
|
||
export const TOKEN = 'token';
|
||
|
||
// Routes
|
||
export const ROUTE_LOGIN = '/login';
|
||
export const ROUTE_HOME = '/map';
|
||
export const ROUTE_TRIP = '/trip';
|
||
|
||
// API Endpoints
|
||
export const API_PATH_LOGIN = '/api/agent/login';
|
||
export const API_PATH_ENTITIES = '/api/io/entities';
|
||
export const API_GET_GPS = '/api/sgw/gps';
|
||
export const API_GET_TRIP = '/api/sgw/trip';
|
||
export const API_SOS = '/api/sgw/sos';
|
||
// ... các API khác
|
||
```
|
||
|
||
#### **constants/enums.ts**
|
||
|
||
```typescript
|
||
// Trạng thái xử lý fishing log
|
||
export enum STATUS {
|
||
CREATE_FISHING_LOG_SUCCESS,
|
||
CREATE_FISHING_LOG_FAIL,
|
||
START_TRIP_SUCCESS,
|
||
// ...
|
||
}
|
||
|
||
// Loại cảnh báo
|
||
export enum ENTITY_TYPE_ENUM {
|
||
SOS_WARNING = '50:15',
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### **5. FOLDER SRC/UTILS/ - TIỆN ÍCH**
|
||
|
||
#### **utils/localStorageUtils.ts**
|
||
|
||
```typescript
|
||
export function getToken(): string {
|
||
return localStorage.getItem(TOKEN) || '';
|
||
}
|
||
|
||
export function setToken(token: string) {
|
||
localStorage.setItem(TOKEN, token);
|
||
}
|
||
|
||
export function removeToken() {
|
||
localStorage.removeItem(TOKEN);
|
||
}
|
||
```
|
||
|
||
#### **utils/jwtTokenUtils.ts**
|
||
|
||
- `parseJwt(token)`: Giải mã JWT token
|
||
- Trích xuất thông tin: `sub` (subject/username), `exp` (expiry time)
|
||
|
||
#### **utils/geomUtils.ts**
|
||
|
||
- `convertWKTLineStringToLatLngArray()`: Chuyển đổi WKT LineString sang tọa độ
|
||
- `convertWKTtoLatLngString()`: Chuyển đổi WKT Polygon sang tọa độ
|
||
- `getBanzoneNameByType()`: Lấy tên vùng cấm theo type
|
||
|
||
#### **utils/mapUtils.ts**
|
||
|
||
- Các hàm tiện ích cho bản đồ
|
||
|
||
#### **utils/sosUtil.ts**
|
||
|
||
- Xử lý logic SOS
|
||
|
||
#### **utils/format.ts**
|
||
|
||
- Format dữ liệu hiển thị
|
||
|
||
---
|
||
|
||
### **6. FOLDER SRC/MODELS/ - STATE MANAGEMENT**
|
||
|
||
UmiJS sử dụng **Model** (giống Redux) để quản lý state toàn cục.
|
||
|
||
#### **models/getSos.ts** - Model quản lý GPS data
|
||
|
||
```typescript
|
||
export default function useGetGpsModel() {
|
||
const [gpsData, setGpsData] = useState<API.GPSResonse | null>(null);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
const getGPSData = useCallback(async () => {
|
||
setLoading(true);
|
||
const res = await getGPS();
|
||
setGpsData(res);
|
||
setLoading(false);
|
||
}, []);
|
||
|
||
return { gpsData, loading, getGPSData };
|
||
}
|
||
```
|
||
|
||
**Cách sử dụng**:
|
||
|
||
```typescript
|
||
// Trong component:
|
||
const { gpsData, getGPSData } = useModel('getSos');
|
||
```
|
||
|
||
#### **models/getTrip.ts** - Model quản lý Trip data
|
||
|
||
```typescript
|
||
export default function useGetTripModel() {
|
||
const [data, setData] = useState<API.Trip | null>(null);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
const getApi = useCallback(async () => {
|
||
const res = await getTrip();
|
||
setData(res);
|
||
}, []);
|
||
|
||
return { data, loading, getApi };
|
||
}
|
||
```
|
||
|
||
#### **models/getAlarm.ts** - Model quản lý Alarm data
|
||
|
||
- Quản lý state danh sách cảnh báo
|
||
|
||
#### **models/global.ts** - Model chung
|
||
|
||
```typescript
|
||
const useUser = () => {
|
||
const [name, setName] = useState<string>(DEFAULT_NAME);
|
||
return { name, setName };
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### **7. FOLDER SRC/SERVICES/ - GỌI API**
|
||
|
||
#### **services/controller/AuthController.ts**
|
||
|
||
```typescript
|
||
export async function login(body: API.LoginRequestBody) {
|
||
return request<API.LoginResponse>(API_PATH_LOGIN, {
|
||
method: 'POST',
|
||
data: body,
|
||
});
|
||
}
|
||
```
|
||
|
||
**Luồng login**:
|
||
|
||
```
|
||
1. User nhập username/password
|
||
2. Gọi login(body)
|
||
3. Backend trả về { token }
|
||
4. Lưu token vào localStorage
|
||
5. Redirect về /map
|
||
```
|
||
|
||
#### **services/controller/DeviceController.ts**
|
||
|
||
```typescript
|
||
// Lấy danh sách entities (thiết bị/tàu)
|
||
export async function queryEntities(): Promise<API.TransformedEntity[]> {
|
||
const rawList = await request<API.EntityResponse[]>(API_PATH_ENTITIES);
|
||
return rawList.map(transformEntityResponse);
|
||
}
|
||
|
||
// Lấy thông tin tàu
|
||
export async function getShipInfo(): Promise<API.ShipDetail> {
|
||
return await request<API.ShipDetail>(API_PATH_SHIP_INFO);
|
||
}
|
||
|
||
// Lấy cảnh báo
|
||
export async function queryAlarms(): Promise<API.AlarmResponse> {
|
||
return await request<API.AlarmResponse>(API_GET_ALARMS);
|
||
}
|
||
|
||
// Lấy GPS
|
||
export async function getGPS() {
|
||
return await request<API.GPSResonse>(API_GET_GPS);
|
||
}
|
||
|
||
// SOS
|
||
export async function getSos() { ... }
|
||
export async function deleteSos() { ... }
|
||
export async function sendSosMessage(message: string) { ... }
|
||
|
||
// Trackpoints (lịch sử di chuyển)
|
||
export async function queryShipTrackPoints() {
|
||
return await request<API.ShipTrackPoint[]>(API_PATH_SHIP_TRACK_POINTS);
|
||
}
|
||
```
|
||
|
||
#### **services/controller/TripController.ts**
|
||
|
||
- `getTrip()`: Lấy thông tin chuyến đi
|
||
- `updateTripStatus()`: Cập nhật trạng thái chuyến đi
|
||
- `createHaul()`: Tạo mẻ lưới mới
|
||
- `updateFishingLog()`: Cập nhật nhật ký đánh cá
|
||
|
||
#### **services/controller/MapController.ts**
|
||
|
||
- `queryBanzones()`: Lấy danh sách vùng cấm
|
||
- `getLayerInfo()`: Lấy thông tin layer
|
||
|
||
#### **services/controller/UserController.ts**
|
||
|
||
- Các API liên quan đến user
|
||
|
||
#### **services/service/MapService.ts**
|
||
|
||
```typescript
|
||
export function getShipIcon(alarmLevel: number, isFishing: boolean) {
|
||
// Trả về icon tàu tương ứng với:
|
||
// - alarmLevel: 0-4 (mức độ cảnh báo)
|
||
// - isFishing: đang đánh cá hay không
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### **8. FOLDER SRC/PAGES/ - CÁC TRANG**
|
||
|
||
#### **pages/Auth/index.tsx** - Trang đăng nhập
|
||
|
||
```typescript
|
||
const LoginPage = () => {
|
||
useEffect(() => {
|
||
checkLogin(); // Kiểm tra đã login chưa
|
||
}, []);
|
||
|
||
const checkLogin = () => {
|
||
const token = getToken();
|
||
if (token && parseJwt(token).exp > now + 1 hour) {
|
||
// Đã login → redirect home
|
||
history.push(redirect || ROUTE_HOME);
|
||
}
|
||
};
|
||
|
||
const handleLogin = async (values) => {
|
||
const resp = await login(values);
|
||
if (resp?.token) {
|
||
setToken(resp.token);
|
||
history.push(redirect || ROUTE_HOME);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<LoginFormPage
|
||
onFinish={handleLogin}
|
||
backgroundVideoUrl="..."
|
||
>
|
||
<ProFormText name="username" />
|
||
<ProFormText.Password name="password" />
|
||
</LoginFormPage>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Luồng**:
|
||
|
||
```
|
||
1. Render form login
|
||
2. User nhập thông tin
|
||
3. Submit → gọi handleLogin()
|
||
4. Lưu token
|
||
5. Redirect về /map hoặc URL trước đó
|
||
```
|
||
|
||
---
|
||
|
||
#### **pages/Home/index.tsx** - Trang giám sát bản đồ
|
||
|
||
**Cấu trúc component**:
|
||
|
||
```typescript
|
||
const HomePage = () => {
|
||
const mapRef = useRef<HTMLDivElement>(null);
|
||
const [isShipInfoOpen, setIsShipInfoOpen] = useState(false);
|
||
const { gpsData, getGPSData } = useModel('getSos');
|
||
|
||
const mapManagerRef = useRef(
|
||
new MapManager(mapRef, {
|
||
onFeatureClick, // Khi click vào tàu
|
||
onFeaturesClick, // Khi click nhiều tàu
|
||
onError,
|
||
}),
|
||
);
|
||
|
||
useEffect(() => {
|
||
getGPSData(); // Lấy GPS ban đầu
|
||
|
||
// Polling mỗi 5s
|
||
const interval = setInterval(() => {
|
||
getGPSData();
|
||
getEntitiesData(mapManager, gpsData);
|
||
fetchAndAddTrackPoints(mapManager);
|
||
fetchAndProcessEntities(mapManager);
|
||
}, 5000);
|
||
|
||
return () => clearInterval(interval);
|
||
}, []);
|
||
};
|
||
```
|
||
|
||
**Các hàm chính**:
|
||
|
||
1. **addFeatureToMap()** - Vẽ tàu lên bản đồ
|
||
|
||
```typescript
|
||
const addFeatureToMap = (mapManager, gpsData, alarm) => {
|
||
// Xóa tàu cũ
|
||
mapManager.featureLayer.getSource()?.clear();
|
||
|
||
// Thêm tàu mới với icon tương ứng
|
||
mapManager.addPoint(
|
||
[gpsData.lon, gpsData.lat],
|
||
{ bearing: gpsData.h },
|
||
{
|
||
icon: getShipIcon(alarm.level, gpsData.fishing),
|
||
scale: 0.1,
|
||
animate: isSos ? true : false, // SOS thì animate
|
||
},
|
||
);
|
||
};
|
||
```
|
||
|
||
2. **fetchAndProcessEntities()** - Vẽ vùng cấm/vùng báo
|
||
|
||
```typescript
|
||
const fetchAndProcessEntities = async () => {
|
||
// Lấy entities (vùng cấm, vùng báo...)
|
||
const entities = await queryEntities();
|
||
|
||
// Với entity '50:2' (vùng cấm)
|
||
const zones = JSON.parse(entity.valueString);
|
||
zones.forEach((zone) => {
|
||
// Vẽ polygon hoặc polyline lên bản đồ
|
||
if (geom_type === 2) {
|
||
mapManager.addLineString(...);
|
||
} else if (geom_type === 1) {
|
||
mapManager.addPolygon(...);
|
||
}
|
||
});
|
||
};
|
||
```
|
||
|
||
3. **fetchAndAddTrackPoints()** - Vẽ lịch sử di chuyển
|
||
|
||
```typescript
|
||
const fetchAndAddTrackPoints = async () => {
|
||
// Lấy lịch sử di chuyển
|
||
const trackpoints = await queryShipTrackPoints();
|
||
|
||
// Vẽ đường đi
|
||
mapManager.addLineString(
|
||
trackpoints.map((p) => [p.lon, p.lat]),
|
||
{ id: MAP_TRACKPOINTS_ID },
|
||
{ strokeColor: 'blue', strokeWidth: 3 },
|
||
);
|
||
};
|
||
```
|
||
|
||
**Components con**:
|
||
|
||
- `VietNamMap`: Component bản đồ chính
|
||
- `GpsInfo`: Hiển thị thông tin GPS
|
||
- `ShipInfo`: Hiển thị thông tin tàu
|
||
- `SosButton`: Nút gửi SOS
|
||
- `BaseMap`: Base map component
|
||
|
||
**Luồng hoạt động**:
|
||
|
||
```
|
||
1. Mount component
|
||
2. Khởi tạo MapManager (OpenLayers)
|
||
3. Gọi getGPSData() lấy GPS hiện tại
|
||
4. Vẽ tàu lên bản đồ với vị trí GPS
|
||
5. Polling mỗi 5s:
|
||
- Cập nhật GPS
|
||
- Cập nhật cảnh báo (alarm)
|
||
- Vẽ lại tàu với icon tương ứng
|
||
- Vẽ trackpoints (lịch sử di chuyển)
|
||
- Vẽ vùng cấm/vùng báo
|
||
6. FloatButton: Điều hướng sang Trip, hiển thị Ship Info
|
||
7. SosButton: Gửi SOS
|
||
```
|
||
|
||
---
|
||
|
||
#### **pages/Trip/index.tsx** - Trang quản lý chuyến đi
|
||
|
||
**Cấu trúc component**:
|
||
|
||
```typescript
|
||
const DetailTrip = () => {
|
||
const [alarmList, setAlarmList] = useState<API.Alarm[]>([]);
|
||
const { data, getApi } = useModel('getTrip');
|
||
|
||
const queryDataSource = async () => {
|
||
const resp = await queryAlarms();
|
||
if (resp.alarms.length > 0) {
|
||
setAlarmList(resp.alarms);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
getApi(); // Lấy thông tin trip
|
||
queryDataSource(); // Lấy alarms
|
||
}, []);
|
||
|
||
return (
|
||
<PageContainer
|
||
header={{
|
||
title: data?.name || 'Chuyến đi',
|
||
tags: <BadgeTripStatus status={data?.trip_status} />,
|
||
}}
|
||
extra={[
|
||
<CreateNewHaulOrTrip
|
||
onCallBack={(success) => {
|
||
switch (success) {
|
||
case STATUS.CREATE_FISHING_LOG_SUCCESS:
|
||
message.success('Tạo mẻ lưới thành công');
|
||
break;
|
||
// ...
|
||
}
|
||
}}
|
||
/>,
|
||
]}
|
||
>
|
||
<ProCard split="vertical">
|
||
{/* Bên trái: Danh sách cảnh báo */}
|
||
<ProCard colSpan={5}>
|
||
<AlarmTable alarmList={alarmList} />
|
||
</ProCard>
|
||
|
||
{/* Bên phải: Nội dung chính */}
|
||
<ProCard colSpan={19}>
|
||
<MainTripBody tripInfo={data} />
|
||
</ProCard>
|
||
</ProCard>
|
||
|
||
<TripCancelOrFinishButton tripStatus={data?.trip_status} />
|
||
</PageContainer>
|
||
);
|
||
};
|
||
```
|
||
|
||
**Components con**:
|
||
|
||
- `AlarmTable`: Bảng danh sách cảnh báo
|
||
- `BadgeTripStatus`: Badge hiển thị trạng thái trip
|
||
- `CancelTrip`: Modal hủy chuyến đi
|
||
- `CreateNewHaulOrTrip`: Modal tạo mẻ lưới/chuyến đi mới
|
||
- `CreateOrUpdateFishingLog`: Modal tạo/cập nhật nhật ký đánh cá
|
||
- `HaulFishList`: Danh sách cá trong mẻ lưới
|
||
- `HaulTable`: Bảng danh sách mẻ lưới
|
||
- `ListSkeleton`: Loading skeleton
|
||
- `MainTripBody`: Nội dung chính của trip
|
||
- `TripCancelOrFinishButton`: Nút hủy/hoàn thành chuyến đi
|
||
- `TripCost`: Chi phí chuyến đi
|
||
- `TripCrews`: Thuyền viên
|
||
- `TripFishingGear`: Ngư cụ
|
||
|
||
**Luồng**:
|
||
|
||
```
|
||
1. Load trip hiện tại
|
||
2. Load danh sách cảnh báo
|
||
3. Hiển thị:
|
||
- Trạng thái trip (Badge)
|
||
- Bảng cảnh báo (bên trái)
|
||
- Thông tin trip (bên phải): Haul, Cost, Crews, FishingGear
|
||
4. Nút tạo Haul/Trip mới
|
||
5. Nút Cancel/Finish Trip
|
||
```
|
||
|
||
---
|
||
|
||
### **9. FOLDER SRC/COMPONENTS/ - COMPONENTS DÙNG CHUNG**
|
||
|
||
#### **components/403/403Page.tsx**
|
||
|
||
- Trang hiển thị khi không có quyền truy cập
|
||
|
||
#### **components/404/404Page.tsx**
|
||
|
||
- Trang Not Found
|
||
|
||
#### **components/500/500Page.tsx**
|
||
|
||
- Trang Server Error
|
||
|
||
#### **components/Footer/Footer.tsx**
|
||
|
||
- Footer component dùng chung
|
||
|
||
#### **components/Guide/**
|
||
|
||
- Component hướng dẫn sử dụng
|
||
|
||
---
|
||
|
||
### **10. FOLDER SRC/TYPES/ - TYPE DEFINITIONS**
|
||
|
||
#### **types/geojson.d.ts**
|
||
|
||
- Type definitions cho GeoJSON
|
||
- Định nghĩa các type cho bản đồ
|
||
|
||
---
|
||
|
||
## 🔄 **LUỒNG HOẠT ĐỘNG TỔNG THỂ**
|
||
|
||
### **A. LUỒNG KHỞI ĐỘNG ỨNG DỤNG**
|
||
|
||
```
|
||
1. User truy cập URL (vd: https://app.com/map)
|
||
2. UmiJS gọi getInitialState() trong app.tsx:
|
||
├─ Lấy token từ localStorage
|
||
├─ Parse JWT token
|
||
├─ Kiểm tra exp (expiry time)
|
||
└─ Return { currentUser, exp } hoặc {}
|
||
3. Nếu không có token hoặc hết hạn:
|
||
└─ Redirect sang /login
|
||
4. Nếu có token hợp lệ:
|
||
├─ Render Layout với avatar, menu
|
||
└─ Render Route component (/map → HomePage)
|
||
```
|
||
|
||
### **B. LUỒNG ĐĂNG NHẬP**
|
||
|
||
```
|
||
1. User truy cập /login
|
||
2. Render LoginPage
|
||
3. User nhập username/password
|
||
4. Submit → gọi login(body)
|
||
5. Backend trả về { token }
|
||
6. setToken(token) → lưu localStorage
|
||
7. history.push(ROUTE_HOME)
|
||
8. UmiJS gọi lại getInitialState()
|
||
9. Render HomePage
|
||
```
|
||
|
||
### **C. LUỒNG TRANG GIÁM SÁT (/map)**
|
||
|
||
```
|
||
1. Render HomePage
|
||
2. Khởi tạo MapManager (OpenLayers map)
|
||
3. Gọi getGPSData() → lấy GPS hiện tại
|
||
4. Gọi getEntitiesData() → lấy alarms
|
||
5. Vẽ tàu lên bản đồ với icon tương ứng alarm level
|
||
6. Gọi fetchAndAddTrackPoints() → vẽ lịch sử di chuyển
|
||
7. Gọi fetchAndProcessEntities() → vẽ vùng cấm/vùng báo
|
||
8. Start polling mỗi 5s:
|
||
├─ Cập nhật GPS
|
||
├─ Cập nhật alarm
|
||
├─ Vẽ lại tàu
|
||
├─ Vẽ trackpoints
|
||
└─ Vẽ vùng cấm
|
||
9. User có thể:
|
||
├─ Click FloatButton → xem GPS info
|
||
├─ Click vào tàu → xem ship info
|
||
├─ Click SOS button → gửi SOS
|
||
└─ Click Trip button → sang trang /trip
|
||
```
|
||
|
||
### **D. LUỒNG TRANG CHUYẾN ĐI (/trip)**
|
||
|
||
```
|
||
1. Render Trip page
|
||
2. Gọi getApi() → lấy trip hiện tại
|
||
3. Gọi queryAlarms() → lấy danh sách cảnh báo
|
||
4. Render:
|
||
├─ Header: tên trip, badge status
|
||
├─ Bên trái: AlarmTable
|
||
└─ Bên phải: MainTripBody (Haul, Cost, Crews, FishingGear)
|
||
5. User có thể:
|
||
├─ Tạo Haul mới (CreateNewHaulOrTrip)
|
||
├─ Cập nhật Fishing Log
|
||
├─ Cancel Trip
|
||
└─ Finish Trip
|
||
```
|
||
|
||
### **E. LUỒNG XỬ LÝ API REQUEST**
|
||
|
||
```
|
||
1. Component gọi API function (vd: getGPS())
|
||
2. Request đi qua requestInterceptors:
|
||
├─ Lấy token từ localStorage
|
||
└─ Gắn vào header: Authorization: {token}
|
||
3. Gửi request qua proxy:
|
||
/api → http://192.168.30.102:81 (dev)
|
||
4. Nhận response
|
||
5. Nếu lỗi:
|
||
├─ errorHandler xử lý
|
||
├─ 401 → removeToken() → redirect login
|
||
└─ Hiển thị message.error()
|
||
6. Nếu thành công:
|
||
└─ Return data
|
||
```
|
||
|
||
### **F. LUỒNG XỬ LÝ SOS**
|
||
|
||
```
|
||
1. User click SOS button
|
||
2. Hiển thị modal nhập message
|
||
3. User nhập message và confirm
|
||
4. Gọi sendSosMessage(message)
|
||
5. Backend xử lý SOS
|
||
6. Frontend polling sẽ detect SOS trong alarm
|
||
7. Icon tàu sẽ animate (nhấp nháy)
|
||
8. User có thể xóa SOS bằng deleteSos()
|
||
```
|
||
|
||
### **G. LUỒNG TẠO/CẬP NHẬT FISHING LOG**
|
||
|
||
```
|
||
1. User click "Tạo mẻ lưới mới"
|
||
2. Hiển thị modal CreateOrUpdateFishingLog
|
||
3. User nhập thông tin:
|
||
├─ Thời gian thả/thu lưới
|
||
├─ Vị trí
|
||
├─ Loại cá
|
||
└─ Số lượng
|
||
4. Submit → gọi API
|
||
5. Refresh data
|
||
6. Hiển thị message success/fail
|
||
```
|
||
|
||
---
|
||
|
||
## 🔑 **CÁC ĐIỂM QUAN TRỌNG**
|
||
|
||
### **1. Authentication Flow**
|
||
|
||
- JWT token lưu trong localStorage với key `'token'`
|
||
- Token được kiểm tra ở 3 nơi:
|
||
- `getInitialState()`: Khi khởi động app
|
||
- `requestInterceptors`: Mỗi request API
|
||
- `onPageChange`: Mỗi lần chuyển trang
|
||
- Token hết hạn (< 1 giờ) → auto logout
|
||
- Token gắn vào header: `Authorization: {token}`
|
||
|
||
### **2. State Management**
|
||
|
||
- Sử dụng UmiJS Model (tương tự Redux nhưng đơn giản hơn)
|
||
- Models chính:
|
||
- `getSos`: Quản lý GPS data
|
||
- `getTrip`: Quản lý Trip data
|
||
- `getAlarm`: Quản lý Alarm data
|
||
- `global`: State chung
|
||
- Hook sử dụng: `useModel('modelName')`
|
||
- Model tự động global, không cần Provider
|
||
|
||
### **3. Map Rendering (OpenLayers)**
|
||
|
||
- `MapManager`: Class quản lý bản đồ
|
||
- Polling 5s để cập nhật real-time:
|
||
- Vị trí tàu (GPS)
|
||
- Cảnh báo (Alarms)
|
||
- Trackpoints (lịch sử di chuyển)
|
||
- Vùng cấm (Banzones)
|
||
- Icon tàu thay đổi theo:
|
||
- Alarm level (0-4): Màu icon khác nhau
|
||
- Fishing status: Icon khác khi đang đánh cá
|
||
- SOS status: Animate (nhấp nháy) khi SOS
|
||
- Layers:
|
||
- Feature layer: Tàu
|
||
- Polyline layer: Đường đi, vùng cấm (line)
|
||
- Polygon layer: Vùng cấm (polygon)
|
||
|
||
### **4. API Structure**
|
||
|
||
- **Auth APIs**:
|
||
- POST `/api/agent/login`: Login
|
||
- **Device APIs**:
|
||
- GET `/api/sgw/gps`: Lấy GPS
|
||
- GET `/api/sgw/shipinfo`: Thông tin tàu
|
||
- GET `/api/io/alarms`: Danh sách cảnh báo
|
||
- GET `/api/io/entities`: Danh sách entities
|
||
- GET `/api/sgw/trackpoints`: Lịch sử di chuyển
|
||
- GET/PUT/DELETE `/api/sgw/sos`: SOS
|
||
- **Trip APIs**:
|
||
- GET `/api/sgw/trip`: Thông tin trip
|
||
- POST/PUT `/api/sgw/tripState`: Cập nhật trạng thái trip
|
||
- POST/PUT `/api/sgw/fishingLog`: Fishing log
|
||
- **Map APIs**:
|
||
- GET `/api/sgw/banzones`: Vùng cấm
|
||
- GET `/api/sgw/geojsonlist`: Danh sách layer
|
||
- GET `/api/sgw/geojson`: Thông tin layer
|
||
|
||
### **5. Responsive Design**
|
||
|
||
- Sử dụng Ant Design Grid breakpoints:
|
||
- xs: < 576px (mobile)
|
||
- sm: ≥ 576px
|
||
- md: ≥ 768px (tablet)
|
||
- lg: ≥ 992px (desktop)
|
||
- xl: ≥ 1200px
|
||
- xxl: ≥ 1600px
|
||
- ProCard split theo breakpoint:
|
||
- Mobile: vertical split
|
||
- Desktop: horizontal split
|
||
|
||
### **6. Error Handling**
|
||
|
||
- HTTP errors:
|
||
- 401: Auto logout + redirect login
|
||
- 403: Hiển thị 403 page
|
||
- 404: Hiển thị 404 page
|
||
- 500: Hiển thị 500 page
|
||
- Other: Hiển thị message.error()
|
||
- Request timeout: 20s
|
||
- Polling error: Log ra console, tiếp tục polling
|
||
|
||
### **7. Data Flow**
|
||
|
||
```
|
||
Component → useModel → API Controller → Request (with token)
|
||
→ Proxy → Backend → Response → Update Model → Re-render Component
|
||
```
|
||
|
||
### **8. Real-time Updates**
|
||
|
||
- Sử dụng polling (setInterval 5s) thay vì WebSocket
|
||
- Các data được poll:
|
||
- GPS location
|
||
- Alarms
|
||
- Trip status
|
||
- Track points
|
||
- Entities (banzones)
|
||
|
||
---
|
||
|
||
## 📦 **DEPENDENCIES QUAN TRỌNG**
|
||
|
||
### **Core Dependencies**
|
||
|
||
- `@umijs/max` (^4.5.0): Framework core của UmiJS
|
||
|
||
- Tích hợp sẵn: routing, state management, request, layout
|
||
- Plugin system mạnh mẽ
|
||
|
||
- `react` (^18.x): UI library
|
||
- `typescript` (^5.x): Type safety
|
||
|
||
### **UI Dependencies**
|
||
|
||
- `antd` (^5.4.0): UI component library
|
||
- Components: Button, Form, Table, Modal, Message, etc.
|
||
- `@ant-design/pro-components` (^2.4.4): Advanced components
|
||
|
||
- ProTable: Advanced table
|
||
- ProForm: Advanced form
|
||
- ProCard: Advanced card
|
||
- ProLayout: Layout system
|
||
- PageContainer: Page wrapper
|
||
|
||
- `@ant-design/icons` (^5.0.1): Icon library
|
||
|
||
- Thousands of icons
|
||
- Custom icon font support
|
||
|
||
- `tailwindcss` (^3.x): Utility-first CSS
|
||
- Rapid styling
|
||
- Responsive design
|
||
|
||
### **Map Dependencies**
|
||
|
||
- `ol` (OpenLayers ^10.6.1): Map library
|
||
- Vector layers
|
||
- GeoJSON support
|
||
- Interactive features
|
||
- Animation support
|
||
|
||
### **Utility Dependencies**
|
||
|
||
- `moment` (^2.30.1): Date/time manipulation
|
||
|
||
- Format dates
|
||
- Calculate durations
|
||
- Timezone support
|
||
|
||
- `classnames` (^2.5.1): Conditional className
|
||
- Dynamic className generation
|
||
|
||
### **Dev Dependencies**
|
||
|
||
- `prettier` (^2.x): Code formatter
|
||
- `prettier-plugin-organize-imports`: Auto organize imports
|
||
- `prettier-plugin-packagejson`: Format package.json
|
||
- `husky` (^9.x): Git hooks
|
||
- `lint-staged` (^13.x): Run linters on staged files
|
||
- `@types/react` & `@types/react-dom`: TypeScript types
|
||
|
||
---
|
||
|
||
## 🚀 **WORKFLOW DEVELOPMENT**
|
||
|
||
### **Setup Project**
|
||
|
||
```bash
|
||
# 1. Clone repository
|
||
git clone <repo-url>
|
||
cd FE-DEVICE-SGW
|
||
|
||
# 2. Install dependencies (sử dụng pnpm)
|
||
pnpm install
|
||
|
||
# 3. Setup environment
|
||
# Copy .env.example to .env.local (nếu có)
|
||
|
||
# 4. Run dev server
|
||
pnpm dev
|
||
# App chạy tại http://localhost:8000
|
||
```
|
||
|
||
### **Development Commands**
|
||
|
||
```bash
|
||
# Chạy development server
|
||
pnpm dev
|
||
|
||
# Build production
|
||
pnpm build
|
||
|
||
# Format code
|
||
pnpm format
|
||
|
||
# Run specific command
|
||
pnpm start # alias của pnpm dev
|
||
```
|
||
|
||
### **Git Workflow với Husky**
|
||
|
||
```bash
|
||
# Pre-commit hook (tự động chạy)
|
||
- Lint staged files
|
||
- Format code với Prettier
|
||
|
||
# Commit
|
||
git add .
|
||
git commit -m "feat: add new feature"
|
||
# → Husky sẽ tự động format code trước khi commit
|
||
|
||
# Push
|
||
git push origin main
|
||
```
|
||
|
||
### **Environment Variables**
|
||
|
||
```bash
|
||
# Set environment
|
||
REACT_APP_ENV=dev # development
|
||
REACT_APP_ENV=test # testing
|
||
REACT_APP_ENV=prod # production
|
||
|
||
# Chạy với env cụ thể
|
||
REACT_APP_ENV=test pnpm dev
|
||
```
|
||
|
||
### **Proxy Configuration**
|
||
|
||
File `config/proxy.ts` định nghĩa proxy cho mỗi môi trường:
|
||
|
||
- `dev`: Point to local backend (192.168.30.102:81)
|
||
- `test`: Point to test server
|
||
- `prod`: Point to production server
|
||
|
||
### **Adding New Features**
|
||
|
||
#### **1. Thêm Route mới**
|
||
|
||
File `.umirc.ts`:
|
||
|
||
```typescript
|
||
routes: [
|
||
// ... existing routes
|
||
{
|
||
name: 'New Page',
|
||
path: '/new-page',
|
||
component: './NewPage',
|
||
icon: 'icon-name',
|
||
},
|
||
],
|
||
```
|
||
|
||
#### **2. Tạo Page mới**
|
||
|
||
```bash
|
||
# Tạo folder và file
|
||
mkdir -p src/pages/NewPage
|
||
touch src/pages/NewPage/index.tsx
|
||
```
|
||
|
||
```typescript
|
||
// src/pages/NewPage/index.tsx
|
||
const NewPage = () => {
|
||
return <div>New Page Content</div>;
|
||
};
|
||
|
||
export default NewPage;
|
||
```
|
||
|
||
#### **3. Thêm API mới**
|
||
|
||
```typescript
|
||
// src/constants/index.ts
|
||
export const API_NEW_ENDPOINT = '/api/new/endpoint';
|
||
|
||
// src/services/controller/NewController.ts
|
||
import { API_NEW_ENDPOINT } from '@/constants';
|
||
import { request } from '@umijs/max';
|
||
|
||
export async function getNewData() {
|
||
return await request<API.NewDataType>(API_NEW_ENDPOINT);
|
||
}
|
||
```
|
||
|
||
#### **4. Tạo Model mới (nếu cần global state)**
|
||
|
||
```typescript
|
||
// src/models/newModel.ts
|
||
import { useState, useCallback } from 'react';
|
||
import { getNewData } from '@/services/controller/NewController';
|
||
|
||
export default function useNewModel() {
|
||
const [data, setData] = useState(null);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
const fetchData = useCallback(async () => {
|
||
setLoading(true);
|
||
try {
|
||
const res = await getNewData();
|
||
setData(res);
|
||
} catch (error) {
|
||
console.error(error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
return { data, loading, fetchData };
|
||
}
|
||
```
|
||
|
||
#### **5. Sử dụng Model trong Component**
|
||
|
||
```typescript
|
||
import { useModel } from '@umijs/max';
|
||
|
||
const MyComponent = () => {
|
||
const { data, loading, fetchData } = useModel('newModel');
|
||
|
||
useEffect(() => {
|
||
fetchData();
|
||
}, []);
|
||
|
||
if (loading) return <Spin />;
|
||
|
||
return <div>{data}</div>;
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 🐛 **DEBUGGING & TROUBLESHOOTING**
|
||
|
||
### **Common Issues**
|
||
|
||
#### **1. Token hết hạn**
|
||
|
||
```
|
||
Triệu chứng: Auto logout, redirect về login
|
||
Nguyên nhân: Token exp < now + 1 hour
|
||
Giải pháp: Backend cần refresh token hoặc tăng exp time
|
||
```
|
||
|
||
#### **2. CORS Error**
|
||
|
||
```
|
||
Triệu chứng: Network error, blocked by CORS
|
||
Nguyên nhân: Backend chưa config CORS
|
||
Giải pháp: Sử dụng proxy trong config/proxy.ts
|
||
```
|
||
|
||
#### **3. Map không hiển thị**
|
||
|
||
```
|
||
Triệu chứng: Bản đồ trống, không có tiles
|
||
Nguyên nhân:
|
||
- MapManager chưa init
|
||
- GPS data null
|
||
- OpenLayers version conflict
|
||
Giải pháp:
|
||
- Check mapRef
|
||
- Check gpsData
|
||
- Check console errors
|
||
```
|
||
|
||
#### **4. Polling không hoạt động**
|
||
|
||
```
|
||
Triệu chứng: Data không cập nhật real-time
|
||
Nguyên nhân:
|
||
- setInterval bị clear
|
||
- API error không xử lý
|
||
Giải pháp:
|
||
- Check cleanup trong useEffect
|
||
- Check API response
|
||
```
|
||
|
||
#### **5. Build Error**
|
||
|
||
```
|
||
Triệu chứng: pnpm build failed
|
||
Nguyên nhân:
|
||
- TypeScript errors
|
||
- Missing dependencies
|
||
- Import errors
|
||
Giải pháp:
|
||
- Fix TypeScript errors
|
||
- pnpm install
|
||
- Check import paths
|
||
```
|
||
|
||
### **Debug Tips**
|
||
|
||
#### **1. Debug API Calls**
|
||
|
||
```typescript
|
||
// Trong Request.ts, thêm log
|
||
requestInterceptors: [
|
||
(url: string, options: any) => {
|
||
console.log('🚀 Request:', url, options);
|
||
// ...
|
||
},
|
||
],
|
||
|
||
// Check response
|
||
responseInterceptors: [
|
||
(response) => {
|
||
console.log('✅ Response:', response);
|
||
return response;
|
||
},
|
||
],
|
||
```
|
||
|
||
#### **2. Debug State**
|
||
|
||
```typescript
|
||
// Trong component
|
||
const { data } = useModel('getTrip');
|
||
|
||
useEffect(() => {
|
||
console.log('📊 Trip data:', data);
|
||
}, [data]);
|
||
```
|
||
|
||
#### **3. Debug Map**
|
||
|
||
```typescript
|
||
// Trong HomePage
|
||
useEffect(() => {
|
||
console.log('🗺️ MapManager:', mapManagerRef.current);
|
||
console.log('📍 GPS Data:', gpsData);
|
||
}, [gpsData]);
|
||
```
|
||
|
||
#### **4. React DevTools**
|
||
|
||
- Install React DevTools extension
|
||
- Inspect component props/state
|
||
- Profile performance
|
||
|
||
#### **5. Network Tab**
|
||
|
||
- Check API calls
|
||
- Check request headers (có Authorization không?)
|
||
- Check response status
|
||
|
||
---
|
||
|
||
## 📊 **PERFORMANCE OPTIMIZATION**
|
||
|
||
### **1. Memo & Callback**
|
||
|
||
```typescript
|
||
// Sử dụng useMemo cho expensive calculations
|
||
const processedData = useMemo(() => {
|
||
return heavyCalculation(data);
|
||
}, [data]);
|
||
|
||
// Sử dụng useCallback cho functions
|
||
const handleClick = useCallback(() => {
|
||
doSomething();
|
||
}, []);
|
||
```
|
||
|
||
### **2. Lazy Loading**
|
||
|
||
```typescript
|
||
// Lazy load components
|
||
import { lazy, Suspense } from 'react';
|
||
|
||
const HeavyComponent = lazy(() => import('./HeavyComponent'));
|
||
|
||
<Suspense fallback={<Spin />}>
|
||
<HeavyComponent />
|
||
</Suspense>;
|
||
```
|
||
|
||
### **3. Debounce Polling**
|
||
|
||
```typescript
|
||
// Thay vì poll mỗi 5s, có thể tăng lên 10s hoặc 30s
|
||
const interval = setInterval(() => {
|
||
fetchData();
|
||
}, 10000); // 10s
|
||
```
|
||
|
||
### **4. Optimize Map Rendering**
|
||
|
||
```typescript
|
||
// Clear old features trước khi add mới
|
||
mapManager.featureLayer.getSource()?.clear();
|
||
|
||
// Batch add features
|
||
const features = data.map((d) => createFeature(d));
|
||
mapManager.featureLayer.getSource()?.addFeatures(features);
|
||
```
|
||
|
||
### **5. Code Splitting**
|
||
|
||
UmiJS tự động code splitting theo routes, nhưng có thể optimize thêm:
|
||
|
||
```typescript
|
||
// Dynamic import
|
||
const module = await import('./module');
|
||
```
|
||
|
||
---
|
||
|
||
## 🔒 **SECURITY BEST PRACTICES**
|
||
|
||
### **1. Token Security**
|
||
|
||
- ✅ Lưu token trong localStorage (OK cho web app)
|
||
- ⚠️ Không log token ra console
|
||
- ✅ Check token expiry
|
||
- ✅ Auto logout khi token hết hạn
|
||
|
||
### **2. API Security**
|
||
|
||
- ✅ Gắn Authorization header cho mọi request
|
||
- ✅ Handle 401 error → logout
|
||
- ✅ Validate response data
|
||
- ⚠️ Không expose sensitive data trong logs
|
||
|
||
### **3. XSS Prevention**
|
||
|
||
- ✅ React tự động escape HTML
|
||
- ⚠️ Không dùng dangerouslySetInnerHTML
|
||
- ✅ Validate user input
|
||
|
||
### **4. CORS**
|
||
|
||
- ✅ Backend config CORS properly
|
||
- ✅ Sử dụng proxy trong dev
|
||
|
||
---
|
||
|
||
## 📝 **CODING CONVENTIONS**
|
||
|
||
### **1. File Naming**
|
||
|
||
- Components: PascalCase (HomePage.tsx)
|
||
- Utils: camelCase (formatUtils.ts)
|
||
- Constants: camelCase (constants/index.ts)
|
||
- Types: PascalCase (types/Api.d.ts)
|
||
|
||
### **2. Component Structure**
|
||
|
||
```typescript
|
||
// 1. Imports
|
||
import React from 'react';
|
||
import { useModel } from '@umijs/max';
|
||
|
||
// 2. Types
|
||
interface Props {
|
||
// ...
|
||
}
|
||
|
||
// 3. Component
|
||
const MyComponent: React.FC<Props> = ({ prop1 }) => {
|
||
// 4. Hooks
|
||
const { data } = useModel('modelName');
|
||
const [state, setState] = useState();
|
||
|
||
// 5. Effects
|
||
useEffect(() => {
|
||
// ...
|
||
}, []);
|
||
|
||
// 6. Handlers
|
||
const handleClick = () => {
|
||
// ...
|
||
};
|
||
|
||
// 7. Render
|
||
return <div>{/* ... */}</div>;
|
||
};
|
||
|
||
// 8. Export
|
||
export default MyComponent;
|
||
```
|
||
|
||
### **3. Import Order**
|
||
|
||
```typescript
|
||
// 1. React
|
||
import React, { useState, useEffect } from 'react';
|
||
|
||
// 2. External libraries
|
||
import { Button, Form } from 'antd';
|
||
import { useModel } from '@umijs/max';
|
||
|
||
// 3. Internal imports
|
||
import { getTrip } from '@/services/controller/TripController';
|
||
import { ROUTE_HOME } from '@/constants';
|
||
import MyComponent from '@/components/MyComponent';
|
||
|
||
// 4. Types
|
||
import type { API } from '@/services/typings';
|
||
|
||
// 5. Styles & Assets
|
||
import styles from './index.less';
|
||
import logo from './logo.png';
|
||
```
|
||
|
||
### **4. Naming Conventions**
|
||
|
||
```typescript
|
||
// Variables & functions: camelCase
|
||
const userData = {};
|
||
const fetchUserData = () => {};
|
||
|
||
// Components: PascalCase
|
||
const UserProfile = () => {};
|
||
|
||
// Constants: UPPER_SNAKE_CASE
|
||
const API_BASE_URL = 'https://api.example.com';
|
||
|
||
// Types & Interfaces: PascalCase
|
||
interface UserData {}
|
||
type UserRole = 'admin' | 'user';
|
||
|
||
// Private functions: _camelCase (optional)
|
||
const _helperFunction = () => {};
|
||
```
|
||
|
||
### **5. Comments**
|
||
|
||
```typescript
|
||
// ✅ Good: Giải thích WHY, not WHAT
|
||
// Polling mỗi 5s để cập nhật vị trí tàu real-time
|
||
const interval = setInterval(fetchGPS, 5000);
|
||
|
||
// ❌ Bad: Mô tả WHAT (code đã tự giải thích)
|
||
// Set interval 5000ms
|
||
const interval = setInterval(fetchGPS, 5000);
|
||
|
||
// ✅ TODO comments
|
||
// TODO: Implement WebSocket cho real-time updates
|
||
// FIXME: Memory leak khi unmount component
|
||
```
|
||
|
||
---
|
||
|
||
## 📈 **SCALABILITY CONSIDERATIONS**
|
||
|
||
### **1. Thêm nhiều tàu**
|
||
|
||
Hiện tại chỉ theo dõi 1 tàu. Để mở rộng:
|
||
|
||
```typescript
|
||
// Thay đổi từ single GPS sang array
|
||
const [shipsData, setShipsData] = useState<API.GPSResponse[]>([]);
|
||
|
||
// Polling cho tất cả tàu
|
||
shipsData.forEach(ship => {
|
||
mapManager.addPoint([ship.lon, ship.lat], ...);
|
||
});
|
||
```
|
||
|
||
### **2. WebSocket thay Polling**
|
||
|
||
```typescript
|
||
// Thay thế setInterval bằng WebSocket
|
||
const ws = new WebSocket('wss://api.example.com/ws');
|
||
|
||
ws.onmessage = (event) => {
|
||
const data = JSON.parse(event.data);
|
||
updateMapWithNewData(data);
|
||
};
|
||
```
|
||
|
||
### **3. Caching**
|
||
|
||
```typescript
|
||
// Sử dụng React Query hoặc SWR cho caching
|
||
import { useQuery } from 'react-query';
|
||
|
||
const { data } = useQuery('trip', getTrip, {
|
||
staleTime: 60000, // Cache 1 phút
|
||
refetchInterval: 5000, // Refetch mỗi 5s
|
||
});
|
||
```
|
||
|
||
### **4. Pagination**
|
||
|
||
```typescript
|
||
// Cho AlarmTable, HaulTable
|
||
<ProTable
|
||
request={async (params) => {
|
||
const { current, pageSize } = params;
|
||
const data = await getAlarms({ page: current, size: pageSize });
|
||
return data;
|
||
}}
|
||
pagination={{
|
||
defaultPageSize: 10,
|
||
}}
|
||
/>
|
||
```
|
||
|
||
---
|
||
|
||
## 📚 **USEFUL RESOURCES**
|
||
|
||
### **Documentation**
|
||
|
||
- [UmiJS Docs](https://umijs.org/docs)
|
||
- [Ant Design Docs](https://ant.design/components/overview/)
|
||
- [Ant Design Pro Components](https://procomponents.ant.design/)
|
||
- [OpenLayers Docs](https://openlayers.org/en/latest/apidoc/)
|
||
- [React Docs](https://react.dev/)
|
||
- [TypeScript Docs](https://www.typescriptlang.org/docs/)
|
||
|
||
### **UmiJS Specific**
|
||
|
||
- [UmiJS Routing](https://umijs.org/docs/guides/routes)
|
||
- [UmiJS Model (State Management)](https://umijs.org/docs/max/data-flow)
|
||
- [UmiJS Request](https://umijs.org/docs/max/request)
|
||
- [UmiJS Layout](https://umijs.org/docs/max/layout-menu)
|
||
|
||
### **Ant Design Pro**
|
||
|
||
- [ProLayout](https://procomponents.ant.design/components/layout)
|
||
- [ProTable](https://procomponents.ant.design/components/table)
|
||
- [ProForm](https://procomponents.ant.design/components/form)
|
||
|
||
---
|
||
|
||
## 📝 **TÓM TẮT CUỐI CÙNG**
|
||
|
||
### **Điểm mạnh của dự án**
|
||
|
||
✅ **Architecture rõ ràng**: Separation of concerns tốt, dễ maintain ✅ **UmiJS framework mạnh mẽ**: Tích hợp sẵn nhiều tính năng ✅ **Ant Design Pro**: UI đẹp, responsive, nhiều components ✅ **TypeScript**: Type safety, giảm bugs ✅ **Real-time updates**: Polling 5s cho data mới ✅ **Map integration**: OpenLayers powerful ✅ **Git hooks**: Husky + lint-staged đảm bảo code quality
|
||
|
||
### **Điểm cần cải thiện**
|
||
|
||
⚠️ **WebSocket**: Thay thế polling cho efficient hơn ⚠️ **Caching**: Implement caching cho API calls ⚠️ **Error boundary**: Thêm error boundaries cho components ⚠️ **Unit tests**: Viết tests cho critical functions ⚠️ **Documentation**: Thêm JSDoc cho functions/components ⚠️ **Performance**: Optimize map rendering với nhiều tàu
|
||
|
||
### **Tech Stack Summary**
|
||
|
||
- **Framework**: UmiJS (React-based)
|
||
- **Language**: TypeScript
|
||
- **UI**: Ant Design + Ant Design Pro Components
|
||
- **Styling**: TailwindCSS + LESS
|
||
- **Map**: OpenLayers
|
||
- **State**: UmiJS Model
|
||
- **HTTP**: UmiJS Request
|
||
- **Build**: Webpack (via UmiJS)
|
||
- **Package Manager**: pnpm
|
||
|
||
### **Main Features**
|
||
|
||
1. 🔐 **Authentication**: JWT-based login/logout
|
||
2. 🗺️ **Real-time Map**: Hiển thị vị trí tàu real-time
|
||
3. 🚨 **Alarms & SOS**: Hệ thống cảnh báo và SOS
|
||
4. 🚢 **Trip Management**: Quản lý chuyến đi, mẻ lưới
|
||
5. 📊 **Dashboard**: Thông tin GPS, tàu, thuyền viên
|
||
6. 🚫 **Banzones**: Hiển thị vùng cấm trên bản đồ
|
||
7. 📍 **Track History**: Lịch sử di chuyển của tàu
|
||
|
||
---
|
||
|
||
**Dự án này là một hệ thống giám sát tàu cá chuyên nghiệp, được xây dựng với các công nghệ hiện đại và best practices. Code được tổ chức tốt, dễ maintain và mở rộng.**
|
||
|
||
**Chúc bạn học tốt và phát triển dự án thành công! 🚀**
|