diff --git a/ruoyi-ui/src/permission.js b/ruoyi-ui/src/permission.js index 9dc7480..f89b765 100644 --- a/ruoyi-ui/src/permission.js +++ b/ruoyi-ui/src/permission.js @@ -28,20 +28,37 @@ router.beforeEach((to, from, next) => { } else { if (store.getters.roles.length === 0) { isRelogin.show = true + // 超时保护:若 getInfo/getRouters 长时间不返回则关闭加载条并提示,避免页面一直白屏 + const timeoutMs = 15000 + const timeoutId = setTimeout(() => { + if (store.getters.roles.length === 0) { + isRelogin.show = false + NProgress.done() + Message.error('获取用户信息超时,请确认后端服务已启动(默认 8080 端口)') + store.dispatch('LogOut').then(() => { + next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) + }) + } + }, timeoutMs) + const clearTimeoutAndNext = () => { + clearTimeout(timeoutId) + } // 判断当前用户是否已拉取完user_info信息 store.dispatch('GetInfo').then(() => { + clearTimeoutAndNext() isRelogin.show = false store.dispatch('GenerateRoutes').then(accessRoutes => { - // 根据roles权限生成可访问的路由表 - router.addRoutes(accessRoutes) // 动态添加可访问路由表 - next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 + router.addRoutes(accessRoutes) + next({ ...to, replace: true }) }) }).catch(err => { - store.dispatch('LogOut').then(() => { - Message.error(err) - next({ path: '/' }) - }) + clearTimeoutAndNext() + isRelogin.show = false + store.dispatch('LogOut').then(() => { + Message.error(err || '获取用户信息失败') + next({ path: '/' }) }) + }) } else { next() } diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js index 0e68209..1015974 100644 --- a/ruoyi-ui/src/router/index.js +++ b/ruoyi-ui/src/router/index.js @@ -87,7 +87,7 @@ export const constantRoutes = [ { path: '', component: Layout, - redirect: 'index', + redirect: '/selectRoom', children: [ { path: 'index', diff --git a/ruoyi-ui/src/store/modules/permission.js b/ruoyi-ui/src/store/modules/permission.js index b549ef0..f21d328 100644 --- a/ruoyi-ui/src/store/modules/permission.js +++ b/ruoyi-ui/src/store/modules/permission.js @@ -52,9 +52,18 @@ const permission = { } } +// 确保路由必有 path,避免 [vue-router] "path" is required 报错(后端菜单 path 为空时) +function ensureRoutePath(route, fallback) { + const id = route.menuId || route.id || route.name || ('menu-' + Math.random().toString(36).slice(2, 9)) + if (route.path === undefined || route.path === null || (typeof route.path === 'string' && route.path.trim() === '')) { + route.path = fallback || ('/hidden-' + id) + } +} + // 遍历后台传来的路由字符串,转换为组件对象 function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { return asyncRouterMap.filter(route => { + ensureRoutePath(route) if (type && route.children) { route.children = filterChildren(route.children) } @@ -82,8 +91,9 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { function filterChildren(childrenMap, lastRouter = false) { var children = [] - childrenMap.forEach(el => { - el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path + childrenMap.forEach((el, idx) => { + ensureRoutePath(el, 'item-' + idx) + el.path = lastRouter ? (lastRouter.path + '/' + el.path).replace(/\/+/g, '/') : el.path if (el.children && el.children.length && el.component === 'ParentView') { children = children.concat(filterChildren(el.children, el)) } else { diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index f25a40e..c03f07e 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -791,6 +791,84 @@ export default { this.showTransformHandles(entityData); this.$message && this.$message.success('已放置。上方箭头旋转、四角调大小、拖动图标移动;点击空白收起'); }); + return entityData; + }, + /** 清除某房间下由 loadRoomPlatformIcons 加载的平台图标(仅从地图与 allEntities 移除) */ + clearRoomPlatformIcons(roomId) { + if (!roomId || !this.allEntities) return; + const toRemove = this.allEntities.filter(e => e.type === 'platformIcon' && e.roomId === roomId); + toRemove.forEach(ed => { + this.removeTransformHandles(ed); + if (this.selectedPlatformIcon === ed) this.selectedPlatformIcon = null; + if (ed.entity) this.viewer && this.viewer.entities.remove(ed.entity); + }); + this.allEntities = this.allEntities.filter(e => !(e.type === 'platformIcon' && e.roomId === roomId)); + }, + /** 加载某房间下保存的平台图标到地图;list 项含 id, lng, lat, heading, iconScale, platformId, platformName, platformType, iconUrl */ + loadRoomPlatformIcons(roomId, list) { + if (!this.viewer || !roomId || !list || !list.length) return; + this.clearRoomPlatformIcons(roomId); + const baseHalfDeg = 0.00015; + list.forEach((item, idx) => { + const platform = { + id: item.platformId, + name: item.platformName, + type: item.platformType, + imageUrl: item.iconUrl, + iconUrl: item.iconUrl + }; + const iconUrl = item.iconUrl || platform.iconUrl; + const imageSrc = iconUrl ? this.formatPlatformIconUrl(iconUrl) : this.getDefaultPlatformIconDataUrl(); + this.entityCounter++; + const id = `platformIcon_room_${roomId}_${item.id}`; + const headingDeg = (item.heading != null ? item.heading : 0); + const rotation = Math.PI / 2 - (headingDeg * Math.PI / 180); + const scaleVal = item.iconScale ?? item.icon_scale ?? 1; + const iconScale = Math.max(0.2, Math.min(3, scaleVal)); + const size = this.PLATFORM_ICON_BASE_SIZE * iconScale; + const cartesian = Cesium.Cartesian3.fromDegrees(item.lng, item.lat); + const entity = this.viewer.entities.add({ + id, + name: platform.name || '平台', + position: cartesian, + billboard: { + image: imageSrc, + width: size, + height: size, + verticalOrigin: Cesium.VerticalOrigin.CENTER, + horizontalOrigin: Cesium.HorizontalOrigin.CENTER, + rotation, + scaleByDistance: new Cesium.NearFarScalar(500, 1.2, 200000, 0.35), + translucencyByDistance: new Cesium.NearFarScalar(1000, 1.0, 500000, 0.6) + } + }); + const entityData = { + id, + type: 'platformIcon', + platformId: platform.id, + platform, + name: platform.name || '平台', + heading: headingDeg, + lat: item.lat, + lng: item.lng, + entity, + imageUrl: iconUrl, + label: platform.name || '平台', + iconScale, + transformHandles: null, + serverId: item.id, + roomId + }; + this.allEntities.push(entityData); + }); + }, + /** 为已放置的平台图标设置服务端 ID(保存成功后由父组件调用) */ + setPlatformIconServerId(entityId, serverId, roomId) { + const ed = this.allEntities.find(e => e.type === 'platformIcon' && (e.id === entityId || (e.entity && e.entity.id === entityId))); + if (ed) { + ed.serverId = serverId; + if (roomId != null) ed.roomId = roomId; + } }, //正式航线渲染函数 renderRouteWaypoints(waypoints, routeId = 'default', platformId, platform, style) { @@ -1735,18 +1813,22 @@ export default { } this.clickedOnEmpty = false; if (this.draggingRotateHandle) { + this.$emit('platform-icon-updated', this.draggingRotateHandle); this.viewer.scene.screenSpaceCameraController.enableInputs = this.platformIconDragCameraEnabled; this.draggingRotateHandle = null; } if (this.draggingScaleHandle) { + this.$emit('platform-icon-updated', this.draggingScaleHandle.entityData); this.viewer.scene.screenSpaceCameraController.enableInputs = this.platformIconDragCameraEnabled; this.draggingScaleHandle = null; } if (this.draggingPlatformIcon) { + this.$emit('platform-icon-updated', this.draggingPlatformIcon); this.viewer.scene.screenSpaceCameraController.enableInputs = this.platformIconDragCameraEnabled; this.draggingPlatformIcon = null; } if (this.rotatingPlatformIcon) { + this.$emit('platform-icon-updated', this.rotatingPlatformIcon); this.viewer.scene.screenSpaceCameraController.enableInputs = this.platformIconDragCameraEnabled; this.rotatingPlatformIcon = null; this.platformIconRotateTip = ''; @@ -3877,8 +3959,9 @@ export default { ) if (index > -1) { const entity = this.allEntities[index] - // 平台图标:移除伸缩框并清除选中 + // 平台图标:移除伸缩框并清除选中;若已保存到服务端则通知父组件删除 if (entity.type === 'platformIcon') { + if (entity.serverId) this.$emit('platform-icon-removed', { serverId: entity.serverId }) this.removeTransformHandles(entity) if (this.selectedPlatformIcon === entity) this.selectedPlatformIcon = null } diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 5c8a99c..99717d2 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -16,7 +16,9 @@ @drawing-points-update="missionDrawingPointsCount = $event" @open-waypoint-dialog="handleOpenWaypointEdit" @open-route-dialog="handleOpenRouteEdit" - @scale-click="handleScaleClick" /> + @scale-click="handleScaleClick" + @platform-icon-updated="onPlatformIconUpdated" + @platform-icon-removed="onPlatformIconRemoved" />