37 KiB
📊 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
// Đị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
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
// 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
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
// 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
// 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
// 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
// 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
// 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
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
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:
// Trong component:
const { gpsData, getGPSData } = useModel('getSos');
models/getTrip.ts - Model quản lý Trip data
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
const useUser = () => {
const [name, setName] = useState<string>(DEFAULT_NAME);
return { name, setName };
};
7. FOLDER SRC/SERVICES/ - GỌI API
services/controller/AuthController.ts
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
// 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 điupdateTripStatus(): Cập nhật trạng thái chuyến đicreateHaul(): Tạo mẻ lưới mớiupdateFishingLog(): Cập nhật nhật ký đánh cá
services/controller/MapController.ts
queryBanzones(): Lấy danh sách vùng cấmgetLayerInfo(): Lấy thông tin layer
services/controller/UserController.ts
- Các API liên quan đến user
services/service/MapService.ts
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
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:
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:
- addFeatureToMap() - Vẽ tàu lên bản đồ
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
},
);
};
- fetchAndProcessEntities() - Vẽ vùng cấm/vùng báo
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(...);
}
});
};
- fetchAndAddTrackPoints() - Vẽ lịch sử di chuyển
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ínhGpsInfo: Hiển thị thông tin GPSShipInfo: Hiển thị thông tin tàuSosButton: Nút gửi SOSBaseMap: 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:
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áoBadgeTripStatus: Badge hiển thị trạng thái tripCancelTrip: Modal hủy chuyến điCreateNewHaulOrTrip: Modal tạo mẻ lưới/chuyến đi mớiCreateOrUpdateFishingLog: Modal tạo/cập nhật nhật ký đánh cáHaulFishList: Danh sách cá trong mẻ lướiHaulTable: Bảng danh sách mẻ lướiListSkeleton: Loading skeletonMainTripBody: Nội dung chính của tripTripCancelOrFinishButton: Nút hủy/hoàn thành chuyến điTripCost: Chi phí chuyến điTripCrews: Thuyền viênTripFishingGear: 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 apprequestInterceptors: Mỗi request APIonPageChange: 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 datagetTrip: Quản lý Trip datagetAlarm: Quản lý Alarm dataglobal: 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
- POST
- 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
- GET
- 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
- GET
- 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
- GET
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 formatterprettier-plugin-organize-imports: Auto organize importsprettier-plugin-packagejson: Format package.jsonhusky(^9.x): Git hookslint-staged(^13.x): Run linters on staged files@types/react&@types/react-dom: TypeScript types
🚀 WORKFLOW DEVELOPMENT
Setup Project
# 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
# 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
# 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
# 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 serverprod: Point to production server
Adding New Features
1. Thêm Route mới
File .umirc.ts:
routes: [
// ... existing routes
{
name: 'New Page',
path: '/new-page',
component: './NewPage',
icon: 'icon-name',
},
],
2. Tạo Page mới
# Tạo folder và file
mkdir -p src/pages/NewPage
touch src/pages/NewPage/index.tsx
// src/pages/NewPage/index.tsx
const NewPage = () => {
return <div>New Page Content</div>;
};
export default NewPage;
3. Thêm API mới
// 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)
// 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
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
// 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
// Trong component
const { data } = useModel('getTrip');
useEffect(() => {
console.log('📊 Trip data:', data);
}, [data]);
3. Debug Map
// 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
// 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
// Lazy load components
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
<Suspense fallback={<Spin />}>
<HeavyComponent />
</Suspense>;
3. Debounce Polling
// 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
// 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:
// 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
// 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
// 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
// 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
// ✅ 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:
// 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
// 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
// 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
// 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 Specific
Ant Design Pro
📝 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
- 🔐 Authentication: JWT-based login/logout
- 🗺️ Real-time Map: Hiển thị vị trí tàu real-time
- 🚨 Alarms & SOS: Hệ thống cảnh báo và SOS
- 🚢 Trip Management: Quản lý chuyến đi, mẻ lưới
- 📊 Dashboard: Thông tin GPS, tàu, thuyền viên
- 🚫 Banzones: Hiển thị vùng cấm trên bản đồ
- 📍 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! 🚀