feat(workspace): 画布 2D/3D 切换与车辆相机跟随
- workspaceToolbar API 新增 view/toggle 与 camera/follow - 画布底栏新增 2D/3D 切换、自动跟随按钮,组件卸载时自动停止跟随 - 地图监控页将选中车辆 id 透传给底栏,并接入监控配置缓存 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+84
-3
@@ -123,6 +123,29 @@
|
||||
<span class="btn-label">录像管理</span>
|
||||
</el-button>
|
||||
|
||||
<!-- 2D / 3D 视图切换 -->
|
||||
<el-button
|
||||
size="small"
|
||||
:type="view2D ? 'primary' : 'default'"
|
||||
:loading="busy.view"
|
||||
@click="toggleViewMode"
|
||||
>
|
||||
<el-icon><View /></el-icon>
|
||||
<span class="btn-label">{{ view2D ? '2D' : '3D' }}</span>
|
||||
</el-button>
|
||||
|
||||
<!-- 车辆自动跟随(需先选中车辆) -->
|
||||
<el-button
|
||||
size="small"
|
||||
:type="cameraFollowing ? 'warning' : 'default'"
|
||||
:disabled="!cameraFollowing && followCarId == null"
|
||||
:loading="busy.follow"
|
||||
@click="toggleCameraFollow"
|
||||
>
|
||||
<el-icon><Aim /></el-icon>
|
||||
<span class="btn-label">{{ cameraFollowing ? '停止跟随' : '自动跟随' }}</span>
|
||||
</el-button>
|
||||
|
||||
<!-- 选择录像(回放)对话框 -->
|
||||
<el-dialog
|
||||
v-model="playDialogVisible"
|
||||
@@ -207,7 +230,8 @@ import {
|
||||
CircleClose,
|
||||
Document,
|
||||
ArrowUp,
|
||||
FirstAidKit
|
||||
FirstAidKit,
|
||||
Aim
|
||||
} from '@element-plus/icons-vue'
|
||||
import {
|
||||
workspaceToolbarApi,
|
||||
@@ -217,6 +241,14 @@ import {
|
||||
type ToolbarState
|
||||
} from '@/api/workspaceToolbar'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 当前选中的车辆 numeric id(地图监控侧栏/画布选中车辆时传入) */
|
||||
followCarId?: number | null
|
||||
}>(),
|
||||
{ followCarId: null }
|
||||
)
|
||||
|
||||
/**
|
||||
* 平台 iframe 画布工具栏(对应 SimpleLite `Panel_4` / `WorkspaceBottomBar.DefineForTerminalEmbedMinimal`)。
|
||||
*
|
||||
@@ -253,7 +285,9 @@ const busy = reactive({
|
||||
display: false,
|
||||
layer: false,
|
||||
recording: false,
|
||||
playback: false
|
||||
playback: false,
|
||||
view: false,
|
||||
follow: false
|
||||
})
|
||||
|
||||
const playDialogVisible = ref(false)
|
||||
@@ -270,6 +304,17 @@ const recordLabel = computed(() => {
|
||||
return `停止 ${mm}:${ss}`
|
||||
})
|
||||
|
||||
const view2D = computed(() => state.value?.view?.map2D ?? true)
|
||||
|
||||
const cameraFollowing = computed(() => state.value?.view?.follow?.enabled ?? false)
|
||||
|
||||
const followCarId = computed(() => {
|
||||
const fromProp = props.followCarId
|
||||
if (fromProp != null && Number.isFinite(fromProp)) return fromProp
|
||||
const fromState = state.value?.view?.follow?.carId
|
||||
return fromState != null && Number.isFinite(fromState) ? fromState : null
|
||||
})
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
state.value = await workspaceToolbarApi.getState()
|
||||
@@ -401,6 +446,39 @@ async function openManageDialog() {
|
||||
manageDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function toggleViewMode() {
|
||||
busy.view = true
|
||||
try {
|
||||
state.value = await workspaceToolbarApi.toggleViewMode()
|
||||
} catch (err) {
|
||||
ElMessage.error(`切换视图失败:${(err as Error).message}`)
|
||||
} finally {
|
||||
busy.view = false
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleCameraFollow() {
|
||||
const carId = followCarId.value
|
||||
if (!cameraFollowing.value && (carId == null || !Number.isFinite(carId))) {
|
||||
ElMessage.warning('请先在车辆列表或 3D 画布中选中一辆车')
|
||||
return
|
||||
}
|
||||
busy.follow = true
|
||||
try {
|
||||
const enable = !cameraFollowing.value
|
||||
state.value = await workspaceToolbarApi.setCameraFollow(carId, enable)
|
||||
if (enable) {
|
||||
ElMessage.success({ message: `已开始跟随车辆 #${carId}`, duration: 1500, grouping: true })
|
||||
} else {
|
||||
ElMessage.info('已停止自动跟随')
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error(`自动跟随失败:${(err as Error).message}`)
|
||||
} finally {
|
||||
busy.follow = false
|
||||
}
|
||||
}
|
||||
|
||||
function formatSize(bytes: number): string {
|
||||
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B'
|
||||
if (bytes < 1024) return `${bytes} B`
|
||||
@@ -430,12 +508,15 @@ onMounted(() => {
|
||||
void refresh()
|
||||
pollTimer = setInterval(() => {
|
||||
const r = state.value?.recording
|
||||
if (r?.isRecording || r?.isPlaying) void refresh()
|
||||
if (r?.isRecording || r?.isPlaying || state.value?.view?.follow?.enabled) void refresh()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (pollTimer) clearInterval(pollTimer)
|
||||
if (cameraFollowing.value) {
|
||||
void workspaceToolbarApi.setCameraFollow(null, false).catch(() => {})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user