feat(map): add event-driven GPS update
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
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';
|
||||
@@ -85,18 +84,9 @@ export const handleRequestConfig: RequestConfig = {
|
||||
// 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,
|
||||
url,
|
||||
options: {
|
||||
...options,
|
||||
headers: {
|
||||
|
||||
@@ -1,59 +1,24 @@
|
||||
// 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 {
|
||||
const proxy: Record<string, any> = {
|
||||
dev: {
|
||||
'/api': {
|
||||
target: `http://${currentIP}:81`,
|
||||
target: 'http://192.168.30.103:81',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
test: {
|
||||
'/test': {
|
||||
target: isLocalIP
|
||||
? `http://${currentIP}:81`
|
||||
: 'https://test-sgw-device.gms.vn',
|
||||
target: 'https://test-sgw-device.gms.vn',
|
||||
changeOrigin: true,
|
||||
secure: !isLocalIP,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
prod: {
|
||||
'/test': {
|
||||
target: isLocalIP
|
||||
? `http://${currentIP}:81`
|
||||
: 'https://prod-sgw-device.gms.vn',
|
||||
target: 'https://prod-sgw-device.gms.vn',
|
||||
changeOrigin: true,
|
||||
secure: !isLocalIP,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const proxy: Record<string, any> = createDynamicProxy();
|
||||
|
||||
export default proxy;
|
||||
|
||||
19
src/hooks/useRealtimeGps.ts
Normal file
19
src/hooks/useRealtimeGps.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// src/hooks/useRealtimeGps.ts
|
||||
import { useEffect, useState } from 'react';
|
||||
import { eventBus } from '@/utils/eventBus';
|
||||
|
||||
export default function useRealtimeGps() {
|
||||
const [gpsData, setGpsData] = useState<API.GPSResonse | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleGpsUpdate = (data: API.GPSResonse) => {
|
||||
setGpsData(data);
|
||||
};
|
||||
eventBus.on('gpsData:update', handleGpsUpdate);
|
||||
|
||||
// cleanup khi unmount
|
||||
return () => eventBus.off('gpsData:update', handleGpsUpdate);
|
||||
}, []);
|
||||
|
||||
return gpsData;
|
||||
}
|
||||
@@ -1,22 +1,43 @@
|
||||
// src/models/useGetGpsModel.ts
|
||||
import { getGPS } from '@/services/controller/DeviceController';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useState, useEffect } from 'react';
|
||||
import { eventBus } from '@/utils/eventBus';
|
||||
|
||||
export default function useGetGpsModel() {
|
||||
const [gpsData, setGpsData] = useState<API.GPSResonse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const getGPSData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getGPS(); // đổi URL cho phù hợp
|
||||
console.log('GPS Data fetched:', res);
|
||||
// Bypass cache để GPS luôn realtime, gọi trực tiếp API
|
||||
const data = await getGPS();
|
||||
console.log('GPS fetched from API:', data);
|
||||
|
||||
setGpsData(res);
|
||||
setGpsData(data);
|
||||
// Luôn emit event GPS để đảm bảo realtime
|
||||
try {
|
||||
eventBus.emit('gpsData:update', data);
|
||||
} catch (e) {
|
||||
console.warn('Failed to emit gpsData:update event', e);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Fetch gps data failed', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// ✅ Lắng nghe event cập nhật cache từ nơi khác (nếu có)
|
||||
useEffect(() => {
|
||||
const handleUpdate = (data: API.GPSResonse) => {
|
||||
console.log('GPS cache updated via eventBus');
|
||||
setGpsData(data);
|
||||
};
|
||||
eventBus.on('gpsData:update', handleUpdate);
|
||||
return () => eventBus.off('gpsData:update', handleUpdate);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
gpsData,
|
||||
loading,
|
||||
|
||||
@@ -59,11 +59,6 @@ interface FeatureData {
|
||||
text?: string;
|
||||
}
|
||||
|
||||
// Interface for layer object
|
||||
interface Layer {
|
||||
layer: VectorLayer<VectorSource>;
|
||||
}
|
||||
|
||||
class MapManager {
|
||||
mapRef: React.RefObject<HTMLDivElement>;
|
||||
map: Map | null;
|
||||
|
||||
@@ -23,12 +23,16 @@ import {
|
||||
InfoOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { history, useModel } from '@umijs/max';
|
||||
import { eventBus } from '@/utils/eventBus';
|
||||
import { FloatButton, Popover } from 'antd';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import GpsInfo from './components/GpsInfo';
|
||||
import ShipInfo from './components/ShipInfo';
|
||||
import SosButton from './components/SosButton';
|
||||
import VietNamMap, { MapManager } from './components/VietNamMap';
|
||||
import Point from 'ol/geom/Point';
|
||||
import LineString from 'ol/geom/LineString';
|
||||
import { fromLonLat } from 'ol/proj';
|
||||
|
||||
// // Define missing types locally for now
|
||||
// interface AlarmData {
|
||||
@@ -56,7 +60,11 @@ const HomePage: React.FC = () => {
|
||||
const mapRef = useRef<HTMLDivElement>(null);
|
||||
const [isShipInfoOpen, setIsShipInfoOpen] = useState(false);
|
||||
const { gpsData, getGPSData } = useModel('getSos');
|
||||
|
||||
const hasCenteredRef = useRef(false);
|
||||
const banzonesCacheRef = useRef<any[]>([]);
|
||||
function isSameBanzones(newData: any[], oldData: any[]): boolean {
|
||||
return JSON.stringify(newData) === JSON.stringify(oldData);
|
||||
}
|
||||
const onFeatureClick = useCallback((feature: any) => {
|
||||
console.log('OnClick Feature: ', feature);
|
||||
console.log(
|
||||
@@ -90,46 +98,19 @@ const HomePage: React.FC = () => {
|
||||
|
||||
const [isShowGPSData, setIsShowGPSData] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
getGPSData();
|
||||
const interval = setInterval(() => {
|
||||
getGPSData();
|
||||
getEntitiesData(mapManagerRef.current, gpsData!);
|
||||
fetchAndAddTrackPoints(mapManagerRef.current);
|
||||
fetchAndProcessEntities(mapManagerRef.current);
|
||||
}, 5000);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
console.log('MapManager destroyed in HomePage cleanup');
|
||||
// Hàm tổng hợp: gọi GPS trước, sau đó gọi các API còn lại
|
||||
const refreshAllData = async () => {
|
||||
try {
|
||||
// Gọi GPS, khi xong sẽ emit event gpsData:update và các API khác sẽ tự động được gọi qua event handler
|
||||
await getGPSData();
|
||||
} catch (e) {
|
||||
console.error('refreshAllData error', e);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
// getGPSData();
|
||||
getEntitiesData(mapManagerRef.current, gpsData!);
|
||||
fetchAndAddTrackPoints(mapManagerRef.current);
|
||||
fetchAndProcessEntities(mapManagerRef.current);
|
||||
}, 1000); // delay 1s
|
||||
return () => clearTimeout(timer);
|
||||
}, [gpsData]);
|
||||
|
||||
// Helper function to add features to the map
|
||||
const addFeatureToMap = (
|
||||
mapManager: MapManager,
|
||||
gpsData: GpsData,
|
||||
alarm: API.AlarmResponse,
|
||||
) => {
|
||||
console.log(
|
||||
'Adding feature to map with GPS data:',
|
||||
gpsData,
|
||||
'and alarm:',
|
||||
alarm,
|
||||
);
|
||||
const isSos = alarm?.alarms.find(
|
||||
(a) => a.id === ENTITY_TYPE_ENUM.SOS_WARNING,
|
||||
);
|
||||
console.log('Is SOS Alarm Present:', isSos);
|
||||
// Helper function to add features to the map (kept above pollers to avoid use-before-define)
|
||||
function addFeatureToMap(mapManager: MapManager, gpsData: GpsData, alarm: API.AlarmResponse,) {
|
||||
const isSos = alarm?.alarms.find((a) => a.id === ENTITY_TYPE_ENUM.SOS_WARNING);
|
||||
|
||||
if (mapManager?.featureLayer && gpsData) {
|
||||
mapManager.featureLayer.getSource()?.clear();
|
||||
@@ -143,26 +124,158 @@ const HomePage: React.FC = () => {
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Chỉ poll GPS, các API còn lại sẽ được gọi khi có GPS mới
|
||||
getGPSData();
|
||||
const gpsInterval = setInterval(() => {
|
||||
getGPSData();
|
||||
}, 5000); // cập nhật mỗi 5s
|
||||
|
||||
// Vẽ marker tàu và polyline ngay khi vào trang nếu đã có dữ liệu
|
||||
if (gpsData) {
|
||||
eventBus.emit('ship:update', gpsData);
|
||||
}
|
||||
// Nếu có trackpoints, emit luôn (nếu bạn lưu trackpoints ở state, có thể emit ở đây)
|
||||
// eventBus.emit('trackpoints:update', trackpoints);
|
||||
|
||||
return () => {
|
||||
clearInterval(gpsInterval);
|
||||
console.log('MapManager destroyed in HomePage cleanup');
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Lắng nghe eventBus cho tất cả các loại dữ liệu
|
||||
useEffect(() => {
|
||||
// GPS cập nhật => emit ship update ngay lập tức, gọi API khác không đồng bộ
|
||||
const onGpsUpdate = async (data: API.GPSResonse) => {
|
||||
try {
|
||||
if (data) {
|
||||
// Emit ship update ngay lập tức để marker tàu cập nhật realtime
|
||||
eventBus.emit('ship:update', data);
|
||||
|
||||
// Gọi các API khác trong background, không block ship update
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const alarm = await queryAlarms();
|
||||
eventBus.emit('alarm:update', alarm);
|
||||
const trackpoints = await queryShipTrackPoints();
|
||||
eventBus.emit('trackpoints:update', trackpoints);
|
||||
const entities = await queryEntities();
|
||||
eventBus.emit('entities:update', entities);
|
||||
} catch (e) {
|
||||
console.error('Background API calls error', e);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('onGpsUpdate handler error', e);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAndProcessEntities = async (mapManager: MapManager) => {
|
||||
try {
|
||||
const entities: API.TransformedEntity[] = await queryEntities();
|
||||
console.log('Fetched entities:', entities.length);
|
||||
|
||||
// Cập nhật marker tàu khi có GPS mới
|
||||
const onShipUpdate = (data: API.GPSResonse) => {
|
||||
const mapManager = mapManagerRef.current;
|
||||
if (!mapManager || !data) return;
|
||||
|
||||
// Cache feature trong mapManager
|
||||
if (!mapManager.shipFeature) {
|
||||
mapManager.shipFeature = mapManager.addPoint(
|
||||
[data.lon, data.lat],
|
||||
{ bearing: data.h || 0 },
|
||||
{
|
||||
icon: getShipIcon(0, data?.fishing || false),
|
||||
scale: 0.1,
|
||||
animate: false,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
const geom = mapManager.shipFeature.getGeometry();
|
||||
if (geom instanceof Point) {
|
||||
geom.setCoordinates([data.lon, data.lat]);
|
||||
}
|
||||
}
|
||||
|
||||
// Set map center to current GPS position
|
||||
if (!hasCenteredRef.current && mapManager.map && mapManager.map.getView) {
|
||||
mapManager.map.getView().setCenter(fromLonLat([data.lon, data.lat]));
|
||||
hasCenteredRef.current = true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
// Cập nhật alarm khi có event
|
||||
const onAlarmUpdate = (alarm: API.AlarmResponse) => {
|
||||
if (gpsData) addFeatureToMap(mapManagerRef.current, gpsData, alarm);
|
||||
};
|
||||
|
||||
// Cập nhật trackpoints khi có event
|
||||
const onTrackpointsUpdate = (trackpoints: API.ShipTrackPoint[]) => {
|
||||
const source = mapManagerRef.current?.featureLayer?.getSource();
|
||||
if (source && trackpoints.length > 0) {
|
||||
const features = source.getFeatures();
|
||||
// Tìm polyline theo id
|
||||
const polyline = features.find(f => f.get('id') === MAP_TRACKPOINTS_ID);
|
||||
const coordinates = trackpoints.map((point) => [point.lon, point.lat]);
|
||||
if (polyline && polyline.getGeometry() instanceof LineString) {
|
||||
polyline.getGeometry().setCoordinates(coordinates);
|
||||
} else {
|
||||
mapManagerRef.current.addLineString(
|
||||
coordinates,
|
||||
{ id: MAP_TRACKPOINTS_ID },
|
||||
{ strokeColor: 'blue', strokeWidth: 3 },
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Cập nhật entities/banzones khi có event
|
||||
const onEntitiesUpdate = async (entities: API.TransformedEntity[]) => {
|
||||
const mapManager = mapManagerRef.current;
|
||||
if (mapManager) {
|
||||
await drawBanzones(mapManager);
|
||||
}
|
||||
};
|
||||
|
||||
eventBus.on('gpsData:update', onGpsUpdate);
|
||||
eventBus.on('ship:update', onShipUpdate);
|
||||
eventBus.on('alarm:update', onAlarmUpdate);
|
||||
eventBus.on('trackpoints:update', onTrackpointsUpdate);
|
||||
eventBus.on('entities:update', onEntitiesUpdate);
|
||||
|
||||
return () => {
|
||||
eventBus.off('gpsData:update', onGpsUpdate);
|
||||
eventBus.off('ship:update', onShipUpdate);
|
||||
eventBus.off('alarm:update', onAlarmUpdate);
|
||||
eventBus.off('trackpoints:update', onTrackpointsUpdate);
|
||||
eventBus.off('entities:update', onEntitiesUpdate);
|
||||
};
|
||||
}, [gpsData]);
|
||||
|
||||
// Vẽ lại banzones (polygon, polyline vùng cấm, vùng cá thử...)
|
||||
async function drawBanzones(mapManager: MapManager) {
|
||||
try {
|
||||
const layer = mapManager.featureLayer?.getSource();
|
||||
layer?.getFeatures()?.forEach(f => {
|
||||
if ([MAP_POLYGON_BAN, MAP_POLYLINE_BAN].includes(f.get('id'))) {
|
||||
layer.removeFeature(f);
|
||||
}
|
||||
});
|
||||
|
||||
const entities = await queryEntities();
|
||||
for (const entity of entities) {
|
||||
if (entity.id === '50:2' && entity.valueString !== '[]') {
|
||||
const banzones = await queryBanzones();
|
||||
const zones: any[] = JSON.parse(entity.valueString);
|
||||
|
||||
zones.forEach((zone: any) => {
|
||||
const geom = banzones.find((b) => b.id === zone.zone_id);
|
||||
if (geom) {
|
||||
const { geom_type, geom_lines, geom_poly } = geom.geom || {};
|
||||
if (geom_type === 2) {
|
||||
const coordinates = convertWKTLineStringToLatLngArray(
|
||||
geom_lines || '',
|
||||
);
|
||||
const coordinates = convertWKTLineStringToLatLngArray(geom_lines || '');
|
||||
if (coordinates.length > 0) {
|
||||
mapManager.addLineString(
|
||||
coordinates,
|
||||
@@ -197,33 +310,17 @@ const HomePage: React.FC = () => {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching entities:', error);
|
||||
console.error('Error drawing banzones:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAndAddTrackPoints = async (mapManager: MapManager) => {
|
||||
try {
|
||||
const trackpoints: API.ShipTrackPoint[] = await queryShipTrackPoints();
|
||||
if (trackpoints.length > 0) {
|
||||
mapManager.addLineString(
|
||||
trackpoints.map((point) => [point.lon, point.lat]),
|
||||
{ id: MAP_TRACKPOINTS_ID },
|
||||
{ strokeColor: 'blue', strokeWidth: 3 },
|
||||
);
|
||||
useEffect(() => {
|
||||
// Vẽ lại banzones khi vào trang
|
||||
const mapManager = mapManagerRef.current;
|
||||
if (mapManager) {
|
||||
drawBanzones(mapManager);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching ship track points:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const getEntitiesData = async (mapManager: MapManager, gpsData: GpsData) => {
|
||||
try {
|
||||
const alarm: API.AlarmResponse = await queryAlarms();
|
||||
addFeatureToMap(mapManager, gpsData, alarm);
|
||||
} catch (error) {
|
||||
console.error('Error fetching entities:', error);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -269,14 +366,9 @@ const HomePage: React.FC = () => {
|
||||
</Popover>
|
||||
<div className="absolute top-3 right-3 ">
|
||||
<SosButton
|
||||
onRefresh={(value) => {
|
||||
onRefresh={async (value) => {
|
||||
if (value) {
|
||||
// Ensure null check for gpsData in all usages
|
||||
if (gpsData) {
|
||||
getEntitiesData(mapManagerRef.current, gpsData);
|
||||
} else {
|
||||
console.warn('GPS data is null, skipping getEntitiesData call');
|
||||
}
|
||||
await refreshAllData();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -31,7 +31,6 @@ export const osmLayer = new TileLayer({
|
||||
});
|
||||
|
||||
// const layerVN = await getLayer('base-vn');
|
||||
|
||||
export const tinhGeoLayer = createGeoJSONLayer({
|
||||
data: '',
|
||||
style: createLabelAndFillStyle('#77BEF0', '#000000'), // vừa màu vừa label
|
||||
|
||||
30
src/utils/cacheStore.ts
Normal file
30
src/utils/cacheStore.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// src/utils/cacheStore.ts
|
||||
interface CacheItem<T> {
|
||||
data: T;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
const TTL = 1 * 1000; // dữ liệu có hiệu lực trong 1 giây để GPS realtime
|
||||
const cache: Record<string, CacheItem<any>> = {};
|
||||
|
||||
export function getCache<T>(key: string): T | null {
|
||||
const item = cache[key];
|
||||
if (!item) return null;
|
||||
if (Date.now() - item.timestamp > TTL) {
|
||||
delete cache[key];
|
||||
return null;
|
||||
}
|
||||
return item.data;
|
||||
}
|
||||
|
||||
export function setCache<T>(key: string, data: T) {
|
||||
cache[key] = { data, timestamp: Date.now() };
|
||||
}
|
||||
|
||||
export function invalidate(key: string) {
|
||||
delete cache[key];
|
||||
}
|
||||
|
||||
export function getAllCacheKeys(): string[] {
|
||||
return Object.keys(cache);
|
||||
}
|
||||
24
src/utils/eventBus.ts
Normal file
24
src/utils/eventBus.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// src/utils/eventBus.ts
|
||||
type EventHandler<T = any> = (data: T) => void;
|
||||
|
||||
class EventBus {
|
||||
private listeners: Record<string, EventHandler[]> = {};
|
||||
|
||||
on(event: string, handler: EventHandler) {
|
||||
if (!this.listeners[event]) this.listeners[event] = [];
|
||||
this.listeners[event].push(handler);
|
||||
}
|
||||
|
||||
off(event: string, handler: EventHandler) {
|
||||
this.listeners[event] = (this.listeners[event] || []).filter(
|
||||
(h) => h !== handler,
|
||||
);
|
||||
}
|
||||
|
||||
emit(event: string, data?: any) {
|
||||
(this.listeners[event] || []).forEach((h) => h(data));
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ phải có dòng này
|
||||
export const eventBus = new EventBus();
|
||||
27
src/utils/queryWithCache.ts
Normal file
27
src/utils/queryWithCache.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// utils/queryWithCache.ts
|
||||
import { getCache, setCache, invalidate, getAllCacheKeys } from './cacheStore';
|
||||
import { eventBus } from './eventBus';
|
||||
|
||||
export async function queryWithCache<T>(
|
||||
key: string,
|
||||
fetcher: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
const cached = getCache<T>(key);
|
||||
if (cached) return cached;
|
||||
|
||||
const data = await fetcher();
|
||||
setCache(key, data);
|
||||
|
||||
// Phát sự kiện để các component khác biết có data mới
|
||||
eventBus.emit(`${key}:update`, data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Xóa cache theo key hoặc prefix
|
||||
queryWithCache.clear = (prefix = '') => {
|
||||
const keys = getAllCacheKeys();
|
||||
for (const key of keys) {
|
||||
if (key.startsWith(prefix)) invalidate(key);
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,15 @@
|
||||
{
|
||||
"extends": "./src/.umi/tsconfig.json"
|
||||
"extends": "./src/.umi/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"lib": ["dom", "es2017"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react-jsx",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user