Commit 76fcdd1c by James Yuan

feat: 大屏相机 HUD 与标题栏主视图2/主视图切换

沉浸态提供单指缩放/旋转控件;点标题展开面板飞主视图2,收起飞主视图,并经同域 /gis 读取当前视角微调。

Co-authored-by: Cursor <cursoragent@cursor.com>
parent 328c1bba
<script setup lang="ts">
import { onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import type { NudgeViewOpts } from '@/composables/useRtengineBridge'
const emit = defineEmits<{
home: []
nudge: [opts: NudgeViewOpts]
}>()
const { t } = useI18n()
/** 按住连发间隔缩短 → 手感更灵敏;单次步进更小 → 更细腻 */
const HOLD_MS = 110
let holdTimer: number | undefined
let holdInterval: number | undefined
function clearHold() {
window.clearTimeout(holdTimer)
window.clearInterval(holdInterval)
holdTimer = undefined
holdInterval = undefined
}
function startHold(action: () => void) {
clearHold()
action()
holdTimer = window.setTimeout(() => {
holdInterval = window.setInterval(action, HOLD_MS)
}, 260)
}
function onHome() {
emit('home')
}
function zoomIn() {
emit('nudge', { heightFactor: 0.985 })
}
function zoomOut() {
emit('nudge', { heightFactor: 1.015 })
}
function yawLeft() {
emit('nudge', { dYaw: -8 })
}
function yawRight() {
emit('nudge', { dYaw: 8 })
}
function pitchUp() {
emit('nudge', { dPitch: 3 })
}
function pitchDown() {
emit('nudge', { dPitch: -3 })
}
onBeforeUnmount(clearHold)
</script>
<template>
<div class="camera-hud" role="toolbar" :aria-label="t('screen.camHud')">
<button
type="button"
class="cam-btn cam-home"
:title="t('screen.camHome')"
@click="onHome"
>
{{ t('screen.camHome') }}
</button>
<div class="cam-grid">
<span class="cam-spacer" />
<button
type="button"
class="cam-btn"
:title="t('screen.camPitchUp')"
@pointerdown.prevent="startHold(pitchUp)"
@pointerup="clearHold"
@pointerleave="clearHold"
@pointercancel="clearHold"
>
▲
</button>
<span class="cam-spacer" />
<button
type="button"
class="cam-btn"
:title="t('screen.camYawLeft')"
@pointerdown.prevent="startHold(yawLeft)"
@pointerup="clearHold"
@pointerleave="clearHold"
@pointercancel="clearHold"
>
↺
</button>
<button
type="button"
class="cam-btn"
:title="t('screen.camPitchDown')"
@pointerdown.prevent="startHold(pitchDown)"
@pointerup="clearHold"
@pointerleave="clearHold"
@pointercancel="clearHold"
>
▼
</button>
<button
type="button"
class="cam-btn"
:title="t('screen.camYawRight')"
@pointerdown.prevent="startHold(yawRight)"
@pointerup="clearHold"
@pointerleave="clearHold"
@pointercancel="clearHold"
>
↻
</button>
<button
type="button"
class="cam-btn cam-zoom"
:title="t('screen.camZoomIn')"
@pointerdown.prevent="startHold(zoomIn)"
@pointerup="clearHold"
@pointerleave="clearHold"
@pointercancel="clearHold"
>
+
</button>
<span class="cam-spacer" />
<button
type="button"
class="cam-btn cam-zoom"
:title="t('screen.camZoomOut')"
@pointerdown.prevent="startHold(zoomOut)"
@pointerup="clearHold"
@pointerleave="clearHold"
@pointercancel="clearHold"
>
-
</button>
</div>
</div>
</template>
<style scoped>
.camera-hud {
position: absolute;
z-index: 9;
/* 真正贴右下角;底栏左侧让位,避免挡住设备分类 */
right: 12px;
bottom: 12px;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 6px;
padding: 8px;
border-radius: 8px;
background: rgba(4, 14, 34, 0.82);
border: 1px solid rgba(91, 212, 255, 0.28);
backdrop-filter: blur(6px);
pointer-events: auto;
user-select: none;
touch-action: manipulation;
}
.cam-grid {
display: grid;
grid-template-columns: repeat(3, 48px);
grid-template-rows: repeat(3, 48px);
gap: 6px;
}
.cam-btn {
min-width: 48px;
min-height: 48px;
margin: 0;
padding: 0 8px;
border-radius: 6px;
border: 1px solid rgba(91, 212, 255, 0.36);
background: rgba(8, 28, 58, 0.92);
color: rgba(235, 245, 255, 0.96);
font-size: 18px;
font-weight: 700;
line-height: 1;
cursor: pointer;
}
.cam-btn:active,
.cam-btn:hover {
border-color: rgba(91, 212, 255, 0.7);
background: rgba(26, 111, 255, 0.5);
}
.cam-home {
font-size: 13px;
letter-spacing: 0.04em;
padding: 10px 12px;
min-height: 44px;
}
.cam-zoom {
font-size: 22px;
}
.cam-spacer {
display: block;
}
</style>
...@@ -14,17 +14,20 @@ import { ...@@ -14,17 +14,20 @@ import {
} from '@/data/screenModePois' } from '@/data/screenModePois'
import AppIcon from '@/components/icons/AppIcon.vue' import AppIcon from '@/components/icons/AppIcon.vue'
import MarkerNearDialog from '@/components/scene/MarkerNearDialog.vue' import MarkerNearDialog from '@/components/scene/MarkerNearDialog.vue'
import CameraHud from '@/components/scene/CameraHud.vue'
import { api } from '@/api/http' import { api } from '@/api/http'
import { import {
defaultRtengineUrl, defaultRtengineUrl,
preloadRtengineIcons, preloadRtengineIcons,
useRtengineBridge, useRtengineBridge,
type NudgeViewOpts,
type SceneMarkerHit, type SceneMarkerHit,
type ScenePoint, type ScenePoint,
} from '@/composables/useRtengineBridge' } from '@/composables/useRtengineBridge'
import { useScreenModePois } from '@/composables/useScreenModePois' import { useScreenModePois } from '@/composables/useScreenModePois'
import { useLightSceneEffects } from '@/composables/useLightSceneEffects' import { useLightSceneEffects } from '@/composables/useLightSceneEffects'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { useScreenUiStore } from '@/stores/screenUi'
import type { SensorSnapshot } from '@/types' import type { SensorSnapshot } from '@/types'
import type { VideoDevice } from '@/types/video' import type { VideoDevice } from '@/types/video'
...@@ -39,9 +42,11 @@ const props = withDefaults( ...@@ -39,9 +42,11 @@ const props = withDefaults(
const realtime = useRealtimeStore() const realtime = useRealtimeStore()
const screenMode = useScreenModeStore() const screenMode = useScreenModeStore()
const screenUi = useScreenUiStore()
const auth = useAuthStore() const auth = useAuthStore()
const { t } = useI18n() const { t } = useI18n()
const { mode: activeMode } = storeToRefs(screenMode) const { mode: activeMode } = storeToRefs(screenMode)
const { sidePanelsVisible } = storeToRefs(screenUi)
type ViewportMode = 'glb' | 'ue-stream' | 'rtengine' type ViewportMode = 'glb' | 'ue-stream' | 'rtengine'
const mode = (import.meta.env.VITE_VIEWPORT_MODE || 'rtengine') as ViewportMode const mode = (import.meta.env.VITE_VIEWPORT_MODE || 'rtengine') as ViewportMode
...@@ -399,8 +404,9 @@ async function onLightControl(on: boolean) { ...@@ -399,8 +404,9 @@ async function onLightControl(on: boolean) {
} }
} }
/** 默认 / 底栏「全景总览」/ 标题栏 / 设备分类:统一飞场景书签「主视图」 */ /** 沉浸(藏面板)/ 设备分类默认:主视图;展开两侧面板:主视图2 */
const DEFAULT_BOOKMARK = '主视图' const DEFAULT_BOOKMARK = '主视图'
const PANEL_BOOKMARK = '主视图2'
/** 飞书签会清引擎 Marker,须在飞完后再补打 */ /** 飞书签会清引擎 Marker,须在飞完后再补打 */
let flyRepaintTimer: number | undefined let flyRepaintTimer: number | undefined
...@@ -749,11 +755,33 @@ function onSelectArea(areaId: string) { ...@@ -749,11 +755,33 @@ function onSelectArea(areaId: string) {
if (useCloudFrame.value) flyToViewpoint(area.viewpoint) if (useCloudFrame.value) flyToViewpoint(area.viewpoint)
} }
function flyToNamedBookmark(name: string, force = false, repaint = true) {
if (!useCloudFrame.value || !name) return
bridge.clearDefaultBookmarkLock()
if (force) bridge.flyToBookmark(name, { force: true })
else flyToViewpoint(name)
if (repaint) scheduleMarkersAfterFly(false)
}
function flyToHome(force = false, repaint = true) { function flyToHome(force = false, repaint = true) {
flyToNamedBookmark(DEFAULT_BOOKMARK, force, repaint)
}
/** 标题栏切面板:显示 → 主视图2;隐藏 → 主视图 */
function flyForSidePanels(visible: boolean, force = true) {
flyToNamedBookmark(visible ? PANEL_BOOKMARK : DEFAULT_BOOKMARK, force, true)
}
function onCameraHudHome() {
bridge.clearDefaultBookmarkLock()
flyToHome(true, true)
}
function onCameraHudNudge(opts: NudgeViewOpts) {
if (!useCloudFrame.value) return if (!useCloudFrame.value) return
if (force) bridge.flyToBookmark(DEFAULT_BOOKMARK, { force: true }) // 停止启动阶段「主视图」补飞,避免与 HUD 微调互相拉扯
else flyToViewpoint(DEFAULT_BOOKMARK) bridge.clearDefaultBookmarkLock()
if (repaint) scheduleMarkersAfterFly(false) void bridge.nudgeView(opts)
} }
function onSelectOverview() { function onSelectOverview() {
...@@ -771,7 +799,7 @@ function onSelectMode(next: ScreenMode) { ...@@ -771,7 +799,7 @@ function onSelectMode(next: ScreenMode) {
window.setTimeout(() => applyCameraVisibility(), 300) window.setTimeout(() => applyCameraVisibility(), 300)
} }
defineExpose({ flyToHome }) defineExpose({ flyToHome, flyToNamedBookmark, flyForSidePanels })
watch(activeMode, () => applyCameraVisibility()) watch(activeMode, () => applyCameraVisibility())
...@@ -832,7 +860,7 @@ onBeforeUnmount(() => { ...@@ -832,7 +860,7 @@ onBeforeUnmount(() => {
<span class="view">{{ modeLabel }}</span> <span class="view">{{ modeLabel }}</span>
</div> </div>
<!-- 全屏:底部设备分类(只切图标) --> <!-- 全屏:底部设备分类 + 右下角相机 HUD(单点触摸缩放/旋转) -->
<Teleport v-if="props.layout === 'fill' && floatTargetReady" to="#screen-float-overlays" defer> <Teleport v-if="props.layout === 'fill' && floatTargetReady" to="#screen-float-overlays" defer>
<div class="vp-area-bottom" :aria-label="t('screen.deviceCategory')"> <div class="vp-area-bottom" :aria-label="t('screen.deviceCategory')">
<button <button
...@@ -847,6 +875,11 @@ onBeforeUnmount(() => { ...@@ -847,6 +875,11 @@ onBeforeUnmount(() => {
<span class="name">{{ t(`modes.${item.id}`) }}</span> <span class="name">{{ t(`modes.${item.id}`) }}</span>
</button> </button>
</div> </div>
<CameraHud
v-if="useCloudFrame && !sidePanelsVisible"
@home="onCameraHudHome"
@nudge="onCameraHudNudge"
/>
</Teleport> </Teleport>
<div v-else-if="props.layout !== 'fill'" class="vp-room-panel"> <div v-else-if="props.layout !== 'fill'" class="vp-room-panel">
<div class="vp-room-head"> <div class="vp-room-head">
...@@ -987,11 +1020,10 @@ onBeforeUnmount(() => { ...@@ -987,11 +1020,10 @@ onBeforeUnmount(() => {
max-width: min(92vw, 1100px); max-width: min(92vw, 1100px);
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
padding: 6px 10px; padding: 0;
border-radius: 8px; /* 无背景面板,仅保留各按钮自身样式 */
background: rgba(4, 14, 34, 0.72); background: transparent;
border: 1px solid rgba(91, 212, 255, 0.22); border: none;
backdrop-filter: blur(6px);
pointer-events: auto; pointer-events: auto;
} }
.vp-area-bottom-item { .vp-area-bottom-item {
......
...@@ -126,18 +126,17 @@ export const RTENGINE_PROXY_PATH = '/gis' ...@@ -126,18 +126,17 @@ export const RTENGINE_PROXY_PATH = '/gis'
/** /**
* 云渲染地址: * 云渲染地址:
* - 显式 VITE_RTENGINE_URL 优先 * - 显式 VITE_RTENGINE_URL 优先
* - 否则局域网用 `http(s)://{hostname}:6088/gis.html?scene=…` * - 否则用同域 `/gis/gis.html?scene=…`(nginx 反代 6088)
* 避免经 /gis 反代时 peer-stream 把 WS 错绑成 `/gis/gis.html` 导致黑屏 * 同域才能直读 `$gisObj.getViewGeo`,HUD 才能基于拖动后的「当前视角」微调
* - 仍是同一 signal / 同一 maxInstance=1 的 UE,只是信令路径正确 * - `/peer-stream` 走站点根路径,仍连同一 UE(maxInstance=1)
*/ */
export function defaultRtengineUrl() { export function defaultRtengineUrl() {
const fromEnv = const fromEnv =
(import.meta.env.VITE_RTENGINE_URL || '').trim() || (import.meta.env.VITE_RTENGINE_URL || '').trim() ||
(import.meta.env.VITE_UE_STREAM_URL || '').trim() (import.meta.env.VITE_UE_STREAM_URL || '').trim()
if (fromEnv) return fromEnv if (fromEnv) return fromEnv
if (typeof window !== 'undefined' && window.location?.hostname) { if (typeof window !== 'undefined' && window.location?.origin) {
const { protocol, hostname } = window.location return `${window.location.origin}${RTENGINE_PROXY_PATH}/gis.html?${RTENGINE_SCENE_QUERY}`
return `${protocol}//${hostname}:6088/gis.html?${RTENGINE_SCENE_QUERY}`
} }
return `${RTENGINE_PROXY_PATH}/gis.html?${RTENGINE_SCENE_QUERY}` return `${RTENGINE_PROXY_PATH}/gis.html?${RTENGINE_SCENE_QUERY}`
} }
...@@ -361,12 +360,32 @@ export function parseMarkerIdFromMessage(data: unknown): string | null { ...@@ -361,12 +360,32 @@ export function parseMarkerIdFromMessage(data: unknown): string | null {
type GisObj = { type GisObj = {
flyToBookmark?: (id: unknown, options?: unknown, cb?: unknown) => unknown flyToBookmark?: (id: unknown, options?: unknown, cb?: unknown) => unknown
flyToViewpoint?: (id: unknown, options?: unknown) => unknown flyToViewpoint?: (id: unknown, options?: unknown) => unknown
getViewGeo?: (cb: (e: unknown) => void) => unknown
setViewGeo?: (...args: unknown[]) => unknown
registerBookmarks?: (list: unknown) => unknown registerBookmarks?: (list: unknown) => unknown
whenBookmarksReady?: (cb?: () => void) => unknown whenBookmarksReady?: (cb?: () => void) => unknown
_bookmarkFlyReady?: boolean _bookmarkFlyReady?: boolean
[key: string]: unknown [key: string]: unknown
} }
/** 经纬度视角(与 SDK getViewGeo / setViewGeo 对齐) */
export type RtengineViewGeo = {
x: number
y: number
z: number
yaw: number
pitch: number
armLength: number
}
export type NudgeViewOpts = {
dYaw?: number
dPitch?: number
/** 相对高度倍率,如 0.88 / 1.12 */
heightFactor?: number
dHeight?: number
}
export function useRtengineBridge(opts: { export function useRtengineBridge(opts: {
iframeRef: Ref<HTMLIFrameElement | null> iframeRef: Ref<HTMLIFrameElement | null>
cloudUrl: string cloudUrl: string
...@@ -608,6 +627,12 @@ export function useRtengineBridge(opts: { ...@@ -608,6 +627,12 @@ export function useRtengineBridge(opts: {
* 启动锁定「主视图」:引擎常在就绪后自动飞列表第一项(测序区), * 启动锁定「主视图」:引擎常在就绪后自动飞列表第一项(测序区),
* 需 force + 多次补飞覆盖。 * 需 force + 多次补飞覆盖。
*/ */
function clearDefaultBookmarkLock() {
lockedHomeId = ''
for (const t of homeLockTimers) window.clearTimeout(t)
homeLockTimers.length = 0
}
async function lockDefaultBookmark(name: string) { async function lockDefaultBookmark(name: string) {
const id = String(name || '').trim() const id = String(name || '').trim()
if (!id) return if (!id) return
...@@ -629,6 +654,170 @@ export function useRtengineBridge(opts: { ...@@ -629,6 +654,170 @@ export function useRtengineBridge(opts: {
} }
} }
let lastViewGeo: RtengineViewGeo | null = null
let viewGeoWaiters: Array<(v: RtengineViewGeo | null) => void> = []
let nudgeChain: Promise<void> = Promise.resolve()
function parseViewGeo(raw: unknown): RtengineViewGeo | null {
if (!raw || typeof raw !== 'object') return null
const o = raw as Record<string, unknown>
const bag0 =
o.data && typeof o.data === 'object' ? (o.data as Record<string, unknown>) : o
const bag =
bag0.camera && typeof bag0.camera === 'object'
? (bag0.camera as Record<string, unknown>)
: bag0
const x = Number(bag.x ?? bag.lng ?? bag.longitude ?? bag.lon)
const y = Number(bag.y ?? bag.lat ?? bag.latitude)
const z = Number(bag.z ?? bag.height ?? bag.alt ?? bag.altitude)
const yawRaw = bag.yaw ?? bag.heading
const pitchRaw = bag.pitch
const yaw = yawRaw == null || yawRaw === '' ? NaN : Number(yawRaw)
const pitch = pitchRaw == null || pitchRaw === '' ? NaN : Number(pitchRaw)
const armLength = Number(bag.armLength ?? bag.arm ?? 0)
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return null
return {
x,
y,
z,
yaw: Number.isFinite(yaw) ? yaw : lastViewGeo?.yaw ?? 0,
pitch: Number.isFinite(pitch) ? pitch : lastViewGeo?.pitch ?? -45,
armLength: Number.isFinite(armLength) ? armLength : lastViewGeo?.armLength ?? 0,
}
}
function resolveViewGeoWaiters(v: RtengineViewGeo | null) {
if (v) lastViewGeo = v
const list = viewGeoWaiters.splice(0, viewGeoWaiters.length)
for (const cb of list) cb(v)
}
/**
* 读取引擎当前视角。
* allowCacheFallback=false(默认)时超时返回 null,避免把过期的「主视图」当成当前视角。
*/
function getViewGeo(
timeoutMs = 1200,
opts?: { allowCacheFallback?: boolean },
): Promise<RtengineViewGeo | null> {
const allowCache = !!opts?.allowCacheFallback
return new Promise((resolve) => {
let settled = false
const finish = (v: RtengineViewGeo | null) => {
if (settled) return
settled = true
// 从 waiter 列表移除自己,避免泄漏
const i = viewGeoWaiters.indexOf(finish)
if (i >= 0) viewGeoWaiters.splice(i, 1)
resolve(v ?? (allowCache ? lastViewGeo : null))
}
viewGeoWaiters.push(finish)
window.setTimeout(() => finish(null), timeoutMs)
const gis = gisObj()
if (gis && typeof gis.getViewGeo === 'function') {
try {
gis.getViewGeo((raw) => {
const v = parseViewGeo(raw)
if (v) resolveViewGeoWaiters(v)
else finish(null)
})
return
} catch {
/* postMessage */
}
}
// 跨域:请 iframe 回传;messageId 便于对照
const messageId = `getViewGeo-${Date.now()}`
send('getViewGeo', { messageId, requestId: messageId })
send('getView', { messageId, requestId: messageId })
})
}
function setViewGeo(
view: Partial<RtengineViewGeo> & { x: number; y: number; z: number },
opts?: { duration?: number; instant?: boolean },
) {
const yaw = view.yaw ?? lastViewGeo?.yaw ?? 0
const pitch = view.pitch ?? lastViewGeo?.pitch ?? -45
const armLength = view.armLength ?? lastViewGeo?.armLength ?? 0
const next: RtengineViewGeo = {
x: view.x,
y: view.y,
z: view.z,
yaw,
pitch,
armLength,
}
lastViewGeo = next
// 默认瞬移:HUD 微调绝不能走 FlyTo 弧线(会飞走再弹回)
const instant = opts?.instant !== false && !(opts?.duration && opts.duration > 0)
const duration = instant ? 0 : Number(opts?.duration) || 0
const flyOpts = instant
? { instant: true, duration: 0 }
: { duration, path: 'linear' as const, canInterrupt: true, instant: false }
const payload = {
x: next.x,
y: next.y,
z: next.z,
longitude: next.x,
latitude: next.y,
height: next.z,
yaw: next.yaw,
heading: next.yaw,
pitch: next.pitch,
armLength: next.armLength,
...flyOpts,
}
const gis = gisObj()
if (gis && typeof gis.setViewGeo === 'function') {
try {
gis.setViewGeo(
next.x,
next.y,
next.z,
next.yaw,
next.pitch,
next.armLength,
flyOpts,
)
return
} catch {
/* postMessage */
}
}
// 只发一次,避免 setView + setViewGeo 双飞互相拉扯
send('setViewGeo', payload)
}
/** 相对微调:必须以引擎当前视角为基准,禁止用过期缓存当坐标 */
function nudgeView(delta: NudgeViewOpts): Promise<void> {
const run = async () => {
clearDefaultBookmarkLock()
let cur = await getViewGeo(1400, { allowCacheFallback: false })
if (!cur) cur = await getViewGeo(1400, { allowCacheFallback: false })
if (!cur) return
let z = cur.z
if (delta.heightFactor != null && Number.isFinite(delta.heightFactor)) {
z = Math.max(8, z * Number(delta.heightFactor))
}
if (delta.dHeight != null && Number.isFinite(delta.dHeight)) {
z = Math.max(8, z + Number(delta.dHeight))
}
let yaw = cur.yaw + (Number(delta.dYaw) || 0)
while (yaw > 180) yaw -= 360
while (yaw < -180) yaw += 360
let pitch = cur.pitch + (Number(delta.dPitch) || 0)
pitch = Math.min(-2, Math.max(-89, pitch))
setViewGeo(
{ x: cur.x, y: cur.y, z, yaw, pitch, armLength: cur.armLength },
{ instant: true },
)
}
nudgeChain = nudgeChain.then(run, run)
return nudgeChain
}
function setPickMode(enabled: boolean) { function setPickMode(enabled: boolean) {
send('setMapPickMode', { state: enabled, enable: enabled }) send('setMapPickMode', { state: enabled, enable: enabled })
} }
...@@ -1307,6 +1496,25 @@ export function useRtengineBridge(opts: { ...@@ -1307,6 +1496,25 @@ export function useRtengineBridge(opts: {
return return
} }
// getViewGeo 回包:有等待者时解析(跨域依赖 iframe postMessage)
if (viewGeoWaiters.length > 0) {
const eid = String(bag?.eventId || type || '')
const looksViewCmd = /getView|viewGeo|ViewGeo|currentView/i.test(eid)
const hasPose =
bag != null &&
(bag.yaw != null ||
bag.heading != null ||
bag.pitch != null ||
bag.armLength != null ||
bag.arm != null)
// 显式 getView* 回包:只要能解析出 x/y/z 即采纳(姿态可缺,用缓存补)
// 其它消息必须带姿态,避免地面点击坐标被当成相机
if (looksViewCmd || hasPose) {
const v = parseViewGeo(bag || data)
if (v && (looksViewCmd || hasPose)) resolveViewGeoWaiters(v)
}
}
if ( if (
type === 'mouseClick' || type === 'mouseClick' ||
type === 'mouseClickCallback' || type === 'mouseClickCallback' ||
...@@ -1380,6 +1588,10 @@ export function useRtengineBridge(opts: { ...@@ -1380,6 +1588,10 @@ export function useRtengineBridge(opts: {
send, send,
flyToBookmark, flyToBookmark,
lockDefaultBookmark, lockDefaultBookmark,
clearDefaultBookmarkLock,
getViewGeo,
setViewGeo,
nudgeView,
setPickMode, setPickMode,
addMarker, addMarker,
addMarkers, addMarkers,
......
...@@ -19,8 +19,16 @@ export default { ...@@ -19,8 +19,16 @@ export default {
areaNav: 'Area navigation', areaNav: 'Area navigation',
clickFly: 'Click to fly', clickFly: 'Click to fly',
currentArea: 'Current area', currentArea: 'Current area',
tipCloud: 'RTEngineHD cloud render · Navigate in view · Select area below to fly', tipCloud: 'One-finger drag to pan · Side buttons zoom & rotate · Categories at bottom',
tipLocal: 'LMB pan · RMB rotate · Wheel zoom · Select area below', tipLocal: 'LMB pan · RMB rotate · Wheel zoom · Select area below',
camHud: 'Camera controls',
camHome: 'Home view',
camZoomIn: 'Zoom in',
camZoomOut: 'Zoom out',
camYawLeft: 'Yaw left',
camYawRight: 'Yaw right',
camPitchUp: 'Pitch up',
camPitchDown: 'Pitch down',
loginToControl: 'Please sign in as admin before controlling', loginToControl: 'Please sign in as admin before controlling',
lightNotBound: 'Lighting facility not found; check facility bindings', lightNotBound: 'Lighting facility not found; check facility bindings',
controlFailed: 'Control failed', controlFailed: 'Control failed',
......
...@@ -19,8 +19,16 @@ export default { ...@@ -19,8 +19,16 @@ export default {
areaNav: 'Navegação de áreas', areaNav: 'Navegação de áreas',
clickFly: 'Clique para voar', clickFly: 'Clique para voar',
currentArea: 'Área atual', currentArea: 'Área atual',
tipCloud: 'Renderização RTEngineHD · Navegue na vista · Selecione a área abaixo', tipCloud: 'Arraste com um dedo · Botões laterais para zoom e rotação · Categorias em baixo',
tipLocal: 'Botão esquerdo pan · Direito rodar · Roda zoom · Selecione a área', tipLocal: 'Botão esquerdo pan · Direito rodar · Roda zoom · Selecione a área',
camHud: 'Controlos de câmara',
camHome: 'Vista principal',
camZoomIn: 'Aproximar',
camZoomOut: 'Afastar',
camYawLeft: 'Girar esquerda',
camYawRight: 'Girar direita',
camPitchUp: 'Inclinar cima',
camPitchDown: 'Inclinar baixo',
loginToControl: 'Inicie sessão como administrador antes de controlar', loginToControl: 'Inicie sessão como administrador antes de controlar',
lightNotBound: 'Iluminação não encontrada; verifique as ligações', lightNotBound: 'Iluminação não encontrada; verifique as ligações',
controlFailed: 'Falha no controlo', controlFailed: 'Falha no controlo',
......
...@@ -19,8 +19,16 @@ export default { ...@@ -19,8 +19,16 @@ export default {
areaNav: '区域导航', areaNav: '区域导航',
clickFly: '点击飞视点', clickFly: '点击飞视点',
currentArea: '当前区域', currentArea: '当前区域',
tipCloud: 'RTEngineHD 云渲染 · 在画面内操作漫游 · 底部点选区域飞视点', tipCloud: '单指拖动画布平移 · 右侧按钮缩放与旋转 · 底部切换设备分类',
tipLocal: '左键拖动 · 右键旋转 · 滚轮缩放 · 底部点选区域', tipLocal: '左键拖动 · 右键旋转 · 滚轮缩放 · 底部点选区域',
camHud: '场景视角',
camHome: '主视图',
camZoomIn: '放大',
camZoomOut: '缩小',
camYawLeft: '左转',
camYawRight: '右转',
camPitchUp: '上仰',
camPitchDown: '下俯',
loginToControl: '请先登录管理员账号后再控制', loginToControl: '请先登录管理员账号后再控制',
lightNotBound: '未找到对应照明设施,请检查设施管理绑定', lightNotBound: '未找到对应照明设施,请检查设施管理绑定',
controlFailed: '控制失败', controlFailed: '控制失败',
......
...@@ -19,8 +19,16 @@ export default { ...@@ -19,8 +19,16 @@ export default {
areaNav: '區域導航', areaNav: '區域導航',
clickFly: '點擊飛視點', clickFly: '點擊飛視點',
currentArea: '當前區域', currentArea: '當前區域',
tipCloud: 'RTEngineHD 雲渲染 · 在畫面內操作漫遊 · 底部點選區域飛視點', tipCloud: '單指拖動畫布平移 · 右側按鈕縮放與旋轉 · 底部切換設備分類',
tipLocal: '左鍵拖動 · 右鍵旋轉 · 滾輪縮放 · 底部點選區域', tipLocal: '左鍵拖動 · 右鍵旋轉 · 滾輪縮放 · 底部點選區域',
camHud: '場景視角',
camHome: '主視圖',
camZoomIn: '放大',
camZoomOut: '縮小',
camYawLeft: '左轉',
camYawRight: '右轉',
camPitchUp: '上仰',
camPitchDown: '下俯',
loginToControl: '請先登入管理員帳號後再控制', loginToControl: '請先登入管理員帳號後再控制',
lightNotBound: '未找到對應照明設施,請檢查設施管理綁定', lightNotBound: '未找到對應照明設施,請檢查設施管理綁定',
controlFailed: '控制失敗', controlFailed: '控制失敗',
......
...@@ -39,9 +39,10 @@ function goAdmin() { ...@@ -39,9 +39,10 @@ function goAdmin() {
else router.push({ path: '/login', query: { redirect: '/admin' } }) else router.push({ path: '/login', query: { redirect: '/admin' } })
} }
/** 标题栏:切换左右业务面板显隐 */ /** 标题栏:切换面板;显示时飞「主视图2」,隐藏时飞「主视图」 */
function onHeaderHome() { function onHeaderHome() {
screenUi.toggleSidePanels() screenUi.toggleSidePanels()
sceneRef.value?.flyForSidePanels?.(sidePanelsVisible.value, true)
} }
</script> </script>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment