warehouse P0修复: http.js(lang_storage_key)+SignalR内存泄漏(LRU)+Pinia store合并(7文件import同步)+Camera type导入修复

This commit is contained in:
2026-06-04 03:08:18 +08:00
parent 85600d0c80
commit fa170e55a9
14 changed files with 77 additions and 144 deletions

View File

@@ -38,6 +38,8 @@ if (!axios.defaults.baseURL.endsWith('/')) {
axios.defaults.baseURL += '/'
}
const lang_storage_key = 'lang'
let ipAddress = axios.defaults.baseURL;
if (!ipAddress || ipAddress == '/') {
ipAddress = window.location.origin + '/';

View File

@@ -1,6 +1,6 @@
<script setup>
import { watch, computed } from "vue"
import { useMapStore } from "../store/useMapStore"
import { useMapStore } from "../stores/mapStore"
// 可以使用pinia等管理全局数据这里只是方便演示, 直接注入了上层提供的数据
const store = useMapStore()

View File

@@ -1,7 +1,7 @@
<script setup>
import { computed } from "vue"
import { useNavi } from "../hooks/useNavi"
import { useMapStore } from "../store/useMapStore"
import { useMapStore } from "../stores/mapStore"
// 可以使用pinia等管理全局数据这里只是方便演示, 直接注入了上层提供的数据
const store = useMapStore()

View File

@@ -1,7 +1,7 @@
<script setup>
import { inject, onBeforeUnmount } from "vue"
import { useFence } from "../hooks/useFence"
import { useMapStore } from "../store/useMapStore"
import { useMapStore } from "../stores/mapStore"
const { THREE } = VgoMap

View File

@@ -1,7 +1,7 @@
<script setup>
import { inject, ref, watch, onBeforeUnmount } from "vue"
import { useClassPolygon } from "../hooks/useClassPolygon"
import { useMapStore } from "../store/useMapStore"
import { useMapStore } from "../stores/mapStore"
import http from "../api/http"
const { THREE } = VgoMap

View File

@@ -1,7 +1,7 @@
<script setup>
import { ref, watch, onBeforeUnmount } from "vue"
import { useSkyLight } from "../hooks/useSkyLight"
import { useMapStore } from "../store/useMapStore"
import { useMapStore } from "../stores/mapStore"
import CustomSwitch from "./CustomSwitch.vue"
const store = useMapStore()

View File

@@ -1,7 +1,7 @@
<script setup>
import { ref, watch } from "vue"
import { useWeatherEffect, WeatherType } from "../hooks/useWeather"
import { useMapStore } from "../store/useMapStore"
import { useMapStore } from "../stores/mapStore"
import CustomSwitch from "./CustomSwitch.vue"
const store = useMapStore()

View File

@@ -1,29 +0,0 @@
import { ref, computed } from "vue"
import { defineStore } from "pinia"
export const useMapStore = defineStore('map', () => {
const map = ref(null)
const polygonDataAll = computed(() => {
const outDoor = map.value?.mapData?.polygonData ?? []
const inDoor = map?.mapData?.build?.reduce((result, build) => {
build.floor.forEach(fItem => {
result.push(...fItem.polygonData)
})
return result
}, []) ?? []
return [...outDoor, ...inDoor]
})
function setMap(mapInstance) {
map.value = mapInstance
}
return {
map,
polygonDataAll,
setMap,
}
})

View File

@@ -1,48 +1,34 @@
import { ref } from "vue"
import { ref, computed } from "vue"
import { defineStore } from "pinia"
export const useMapStore = defineStore('map', () => {
const map = ref(null)
const lastClickedPosition = ref(null) // 存储地图最后点击位置的经纬度
const editingMode = ref(false) // 编辑状态标识
const lastClickedPosition = ref(null)
const editingMode = ref(false)
function setMap(mapInstance) {
map.value = mapInstance
}
/** 聚合所有楼层+室外的 polygonData */
const polygonDataAll = computed(() => {
const outDoor = map.value?.mapData?.polygonData ?? []
const builds = map.value?.mapData?.build ?? []
const inDoor = builds.reduce((result, build) => {
build.floor?.forEach(f => { result.push(...(f.polygonData ?? [])) })
return result
}, [])
return [...outDoor, ...inDoor]
})
// 设置地图最后点击位置的经纬度
function setLastClickedPosition(lng, lat) {
lastClickedPosition.value = { lng, lat }
}
// 设置编辑状态
function setMap(mapInstance) { map.value = mapInstance }
function setLastClickedPosition(lng, lat) { lastClickedPosition.value = { lng, lat } }
function setEditingMode(mode) {
editingMode.value = mode
if(mode){
map.value.setViewMode('2D')
}
else{
map.value.setViewMode('3D')
}
}
// 获取编辑状态
function getEditingMode() {
if(editingMode.value){
return editingMode.value
}
else{
return false
}
map.value?.setViewMode(mode ? '2D' : '3D')
}
function getEditingMode() { return editingMode.value }
return {
map,
setMap,
lastClickedPosition,
setLastClickedPosition,
editingMode,
setEditingMode,
getEditingMode
map, setMap,
lastClickedPosition, setLastClickedPosition,
editingMode, setEditingMode, getEditingMode,
polygonDataAll,
}
})

View File

@@ -6,7 +6,7 @@ import Sky from "../components/Sky.vue"
import DisplayColor from "../components/DisplayColor.vue"
import DisplayRouteLine from "../components/DisplayRouteLine.vue"
import Weather from "../components/Weather.vue"
import { useMapStore } from "../store/useMapStore"
import { useMapStore } from "../stores/mapStore"
const { VgoMap } = window
let mapId = /#\/(\d+)/.exec(location.hash)[1]

View File

@@ -2,89 +2,60 @@ import * as signalR from "@microsoft/signalr";
import { ElNotification } from "element-plus";
import { ElMessageBox } from "element-plus";
import store from "@/store/index";
export default function (http, receive) {
let connection;
let connection
let messageQueue = [];
let isProcessingQueue = false;
let displayedMessageIds = new Set(); // 用于存储已显示的消息ID避免重复显示
// LRU: 最近 500 条 ID防内存无限增长
const MAX_IDS = 500
const displayedIds = new Set()
const idList = []
// 消息队列处理函数 - 移到闭包内部,确保可以访问所有必要的变量
function processMessageQueue() {
if (messageQueue.length === 0) {
isProcessingQueue = false;
return;
function addId(id) {
if (displayedIds.has(id)) return
displayedIds.add(id)
idList.push(id)
if (idList.length > MAX_IDS) {
displayedIds.delete(idList.shift())
}
}
function hasId(id) { return displayedIds.has(id) }
isProcessingQueue = true;
const currentMessage = messageQueue.shift();
// 直接调用receive回调处理消息显示
if (receive) {
try {
receive(currentMessage);
console.log('成功调用receive回调处理消息:', currentMessage);
} catch (error) {
console.error('调用receive回调时出错:', error);
}
// 重试启动: 最多 5 次, 间隔 2s
async function startWithRetry(conn, retries = 5) {
for (let i = 1; i <= retries; i++) {
try { await conn.start(); return }
catch (e) { if (i < retries) await new Promise(r => setTimeout(r, 2000)) }
}
// 可以设置一定的延迟,避免消息显示过于密集
setTimeout(() => {
processMessageQueue();
}, 3000); // 3秒后处理下一条消息
console.error('SignalR 连接失败 (5次重试后放弃)')
}
http.post("api/user/GetCurrentUserInfo").then((result) => {
console.log(result,'result');
http.post("api/user/GetCurrentUserInfo").then(async (result) => {
if (!result?.data?.userName) {
console.error('获取用户信息失败: 缺少 userName')
return
}
connection = new signalR.HubConnectionBuilder()
.withAutomaticReconnect()
.withUrl(
`/hub/message?userName=${result.data.userName}`,
{
//withCredentials: true // 启用凭证模式
// accessTokenFactory: () => store.getters.getToken()
}
)
//.withUrl(`${http.ipAddress}message`)
.build();
.withUrl('/hub/message?userName=' + result.data.userName)
.build()
startWithRetry(connection)
connection.onreconnected(function (id) {
if (import.meta.env.DEV) console.log('SignalR reconnected:', id)
})
connection.start().catch((err) => console.log(err.message));
//自动重连成功后的处理
connection.onreconnected((connectionId) => {
console.log(connectionId, 'connectionId');
});
connection.on("ReceiveHomePageMessage", function (data) {
console.log('接收到新消息:', data);
// 检查消息是否已显示过
if (data.id && displayedMessageIds.has(data.id)) {
console.log('消息已显示过,跳过:', data.id);
return;
}
if (data?.id && hasId(data.id)) return
if (data?.id) addId(data.id)
// 将消息ID添加到已显示集合
if (data.id) {
displayedMessageIds.add(data.id);
console.log('添加到已显示集合:', data.id);
}
// 添加到store
// store.getters.data().pushMessage(data);
console.log(data, 'data_after_push');
// 直接调用receive回调
if (receive) {
try {
receive(data);
console.log('成功调用receive回调:', data);
} catch (error) {
console.error('调用receive回调时出错:', error);
}
} else {
console.warn('receive回调未定义无法处理消息');
try { receive(data) }
catch (e) { console.error('receive callback error:', e) }
}
});
}).catch(error => {
console.error('获取用户信息时出错:', error);
});
})
}).catch(function (e) {
console.error('getUserInfo failed:', e)
})
}

View File

@@ -61,7 +61,8 @@
import { ref, computed, onMounted } from 'vue'
import { VideoPlay } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { fetchCameras, gwGet, Camera } from '@/api/gateway'
import { fetchCameras, gwGet } from '@/api/gateway'
import type { Camera } from '@/api/gateway'
const cameras = ref<Camera[]>([])
const selectedCamera = ref<Camera | null>(null)

View File

@@ -92,7 +92,8 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { VideoCamera } from '@element-plus/icons-vue'
import { fetchCameras, gwGet, gwPost, Camera } from '@/api/gateway'
import { fetchCameras, gwGet, gwPost } from '@/api/gateway'
import type { Camera } from '@/api/gateway'
const cameras = ref<Camera[]>([])
const selectedCamera = ref<Camera | null>(null)

View File

@@ -106,7 +106,8 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { fetchCameras, gwGet, gwPost, Camera } from '@/api/gateway'
import { fetchCameras, gwGet, gwPost } from '@/api/gateway'
import type { Camera } from '@/api/gateway'
const cameras = ref<Camera[]>([])
const streamUrls = ref<Record<string, string>>({})