537 lines
14 KiB
Vue
537 lines
14 KiB
Vue
|
|
<template>
|
|||
|
|
<button
|
|||
|
|
class="scene-mode-toggle"
|
|||
|
|
|
|||
|
|
type="button"
|
|||
|
|
:disabled="!canToggle"
|
|||
|
|
:title="buttonTitle"
|
|||
|
|
@click="toggleSceneMode"
|
|||
|
|
aria-live="polite"
|
|||
|
|
>
|
|||
|
|
{{ buttonLabel }}
|
|||
|
|
</button>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
|
|||
|
|
import * as Cesium from 'cesium'
|
|||
|
|
import useMapStore from '@/map/stores/mapStore'
|
|||
|
|
|
|||
|
|
|
|||
|
|
const mapStore = useMapStore()
|
|||
|
|
const sceneRef = shallowRef(null)
|
|||
|
|
const cameraService = shallowRef(null)
|
|||
|
|
const layerService = shallowRef(null)
|
|||
|
|
const is3DMode = ref(true)
|
|||
|
|
const isMorphing = ref(false)
|
|||
|
|
const pendingRestoreState = shallowRef(null)
|
|||
|
|
let detachReadyListener = null
|
|||
|
|
let detachMorphStartListener = null
|
|||
|
|
let detachMorphCompleteListener = null
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清理由 Cesium 场景注册的监听。
|
|||
|
|
* @returns {void} 无返回值
|
|||
|
|
*/
|
|||
|
|
function cleanupScene() {
|
|||
|
|
if (typeof detachMorphStartListener === 'function') {
|
|||
|
|
detachMorphStartListener()
|
|||
|
|
}
|
|||
|
|
if (typeof detachMorphCompleteListener === 'function') {
|
|||
|
|
detachMorphCompleteListener()
|
|||
|
|
}
|
|||
|
|
detachMorphStartListener = null
|
|||
|
|
detachMorphCompleteListener = null
|
|||
|
|
sceneRef.value = null
|
|||
|
|
is3DMode.value = true
|
|||
|
|
isMorphing.value = false
|
|||
|
|
pendingRestoreState.value = null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 同步场景模式状态,更新按钮显示。
|
|||
|
|
* @param {Cesium.Scene | null} scene Cesium 场景
|
|||
|
|
* @returns {void} 无返回值
|
|||
|
|
*/
|
|||
|
|
function syncSceneMode(scene) {
|
|||
|
|
if (!scene) {
|
|||
|
|
is3DMode.value = true
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
const mode = scene.mode
|
|||
|
|
is3DMode.value = mode !== Cesium.SceneMode.SCENE2D
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析并缓存地图相关服务。
|
|||
|
|
* @returns {void} 无返回值
|
|||
|
|
*/
|
|||
|
|
function resolveServices() {
|
|||
|
|
if (!mapStore.ready) {
|
|||
|
|
cameraService.value = null
|
|||
|
|
layerService.value = null
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
const { camera, layer } = mapStore.services()
|
|||
|
|
cameraService.value = camera
|
|||
|
|
layerService.value = layer
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('解析地图服务失败', error)
|
|||
|
|
cameraService.value = null
|
|||
|
|
layerService.value = null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构造相机快照。
|
|||
|
|
* @param {Cesium.Camera | null} camera Cesium 相机
|
|||
|
|
* @returns {object | null} 相机视角参数
|
|||
|
|
*/
|
|||
|
|
function buildCameraSnapshot(camera) {
|
|||
|
|
if (!camera) return null
|
|||
|
|
const cartographic = camera.positionCartographic
|
|||
|
|
if (!cartographic) return null
|
|||
|
|
return {
|
|||
|
|
lon: Cesium.Math.toDegrees(cartographic.longitude),
|
|||
|
|
lat: Cesium.Math.toDegrees(cartographic.latitude),
|
|||
|
|
height: cartographic.height,
|
|||
|
|
heading: Cesium.Math.toDegrees(camera.heading || 0),
|
|||
|
|
pitch: Cesium.Math.toDegrees(camera.pitch || 0),
|
|||
|
|
roll: Cesium.Math.toDegrees(camera.roll || 0),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* 矩形转经纬度对象。
|
|||
|
|
* @param {Cesium.Rectangle} rectangle Cesium 矩形
|
|||
|
|
* @returns {{ west:number, south:number, east:number, north:number }} 经纬度范围
|
|||
|
|
*/
|
|||
|
|
function rectangleToDegrees(rectangle) {
|
|||
|
|
return {
|
|||
|
|
west: Cesium.Math.toDegrees(rectangle.west),
|
|||
|
|
south: Cesium.Math.toDegrees(rectangle.south),
|
|||
|
|
east: Cesium.Math.toDegrees(rectangle.east),
|
|||
|
|
north: Cesium.Math.toDegrees(rectangle.north),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 记录当前场景状态(相机、可视范围、图层)。
|
|||
|
|
* @param {Cesium.Viewer} viewer Cesium Viewer
|
|||
|
|
* @returns {object | null} 场景快照
|
|||
|
|
*/
|
|||
|
|
function captureSceneSnapshot(viewer) {
|
|||
|
|
if (!viewer) return null
|
|||
|
|
const snapshot = {
|
|||
|
|
cameraView: null,
|
|||
|
|
viewRectangle: null,
|
|||
|
|
imageryOrder: [],
|
|||
|
|
vectorOrder: [],
|
|||
|
|
layerStates: {},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
if (cameraService.value && typeof cameraService.value.getCurrentView === 'function') {
|
|||
|
|
snapshot.cameraView = cameraService.value.getCurrentView()
|
|||
|
|
} else {
|
|||
|
|
snapshot.cameraView = buildCameraSnapshot(viewer.camera)
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('获取相机视角失败', error)
|
|||
|
|
snapshot.cameraView = buildCameraSnapshot(viewer.camera)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const rectangle = viewer.camera.computeViewRectangle(viewer.scene?.globe?.ellipsoid)
|
|||
|
|
if (rectangle) {
|
|||
|
|
snapshot.viewRectangle = rectangleToDegrees(rectangle)
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('计算可视范围失败', error)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const layerEntries = Object.entries(mapStore.layers || {})
|
|||
|
|
const objectLookup = new Map()
|
|||
|
|
layerEntries.forEach(([id, record]) => {
|
|||
|
|
if (!record) return
|
|||
|
|
if (record.obj) {
|
|||
|
|
objectLookup.set(record.obj, { id, record })
|
|||
|
|
}
|
|||
|
|
snapshot.layerStates[id] = {
|
|||
|
|
show: record.show,
|
|||
|
|
opacity: typeof record.opacity === 'number' ? record.opacity : null,
|
|||
|
|
type: record.type,
|
|||
|
|
splitDirection: record.type === 'imagery' && record.obj ? record.obj.splitDirection : undefined,
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const imageryLayers = viewer.imageryLayers
|
|||
|
|
const imageryOrder = []
|
|||
|
|
for (let i = 0; i < imageryLayers.length; i += 1) {
|
|||
|
|
const layer = imageryLayers.get(i)
|
|||
|
|
const info = objectLookup.get(layer)
|
|||
|
|
if (info) imageryOrder.push(info.id)
|
|||
|
|
}
|
|||
|
|
snapshot.imageryOrder = imageryOrder
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('记录影像图层顺序失败', error)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const dataSources = viewer.dataSources
|
|||
|
|
const vectorOrder = []
|
|||
|
|
for (let i = 0; i < dataSources.length; i += 1) {
|
|||
|
|
const dataSource = dataSources.get(i)
|
|||
|
|
const info = objectLookup.get(dataSource)
|
|||
|
|
if (info) vectorOrder.push(info.id)
|
|||
|
|
}
|
|||
|
|
snapshot.vectorOrder = vectorOrder
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('记录矢量图层顺序失败', error)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return snapshot
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 还原图层显隐、顺序等状态。
|
|||
|
|
* @param {Cesium.Viewer} viewer Cesium Viewer
|
|||
|
|
* @param {object | null} snapshot 场景快照
|
|||
|
|
* @returns {void} 无返回值
|
|||
|
|
*/
|
|||
|
|
function applyLayerState(viewer, snapshot) {
|
|||
|
|
if (!snapshot) return
|
|||
|
|
const stateMap = snapshot.layerStates || {}
|
|||
|
|
|
|||
|
|
if (layerService.value) {
|
|||
|
|
Object.entries(stateMap).forEach(([id, info]) => {
|
|||
|
|
if (!info) return
|
|||
|
|
try {
|
|||
|
|
if (info.show != null) layerService.value.showLayer(id, info.show)
|
|||
|
|
if (info.type === 'imagery' && typeof info.opacity === 'number') {
|
|||
|
|
layerService.value.setOpacity(id, info.opacity)
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn(`恢复图层状态失败: ${id}`, error)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
Object.entries(stateMap).forEach(([id, info]) => {
|
|||
|
|
if (!info) return
|
|||
|
|
const record = mapStore.layers?.[id]
|
|||
|
|
if (!record || !record.obj) return
|
|||
|
|
if (info.show != null) {
|
|||
|
|
record.obj.show = !!info.show
|
|||
|
|
record.show = !!info.show
|
|||
|
|
}
|
|||
|
|
if (info.type === 'imagery' && typeof info.opacity === 'number') {
|
|||
|
|
record.obj.alpha = info.opacity
|
|||
|
|
record.opacity = info.opacity
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const imageryOrder = Array.isArray(snapshot.imageryOrder) ? snapshot.imageryOrder : []
|
|||
|
|
if (imageryOrder.length) {
|
|||
|
|
imageryOrder.slice().reverse().forEach((id) => {
|
|||
|
|
const info = stateMap[id]
|
|||
|
|
const record = mapStore.layers?.[id]
|
|||
|
|
if (!record || record.type !== 'imagery' || !record.obj) return
|
|||
|
|
try {
|
|||
|
|
if (layerService.value && typeof layerService.value.moveLayer === 'function') {
|
|||
|
|
layerService.value.moveLayer(id, 'bottom')
|
|||
|
|
} else if (viewer?.imageryLayers?.contains(record.obj)) {
|
|||
|
|
viewer.imageryLayers.lowerToBottom(record.obj)
|
|||
|
|
}
|
|||
|
|
if (info && info.splitDirection != null) {
|
|||
|
|
record.obj.splitDirection = info.splitDirection
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn(`恢复影像图层顺序失败: ${id}`, error)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
Object.entries(stateMap).forEach(([id, info]) => {
|
|||
|
|
if (!info || info.splitDirection == null) return
|
|||
|
|
const record = mapStore.layers?.[id]
|
|||
|
|
if (record?.type === 'imagery' && record.obj) {
|
|||
|
|
record.obj.splitDirection = info.splitDirection
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const vectorOrder = Array.isArray(snapshot.vectorOrder) ? snapshot.vectorOrder : []
|
|||
|
|
if (vectorOrder.length) {
|
|||
|
|
vectorOrder.slice().reverse().forEach((id) => {
|
|||
|
|
const record = mapStore.layers?.[id]
|
|||
|
|
if (!record || !(record.type === 'vector' || record.type === 'datasource') || !record.obj) return
|
|||
|
|
try {
|
|||
|
|
if (layerService.value && typeof layerService.value.moveLayer === 'function') {
|
|||
|
|
layerService.value.moveLayer(id, 'bottom')
|
|||
|
|
} else if (viewer?.dataSources?.contains(record.obj)) {
|
|||
|
|
viewer.dataSources.lowerToBottom(record.obj)
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn(`恢复矢量图层顺序失败: ${id}`, error)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 还原相机视角与可视范围。
|
|||
|
|
* @param {Cesium.Scene} scene Cesium 场景
|
|||
|
|
* @param {Cesium.Viewer} viewer Cesium Viewer
|
|||
|
|
* @param {object | null} snapshot 场景快照
|
|||
|
|
* @returns {void} 无返回值
|
|||
|
|
*/
|
|||
|
|
function applyCameraState(scene, viewer, snapshot) {
|
|||
|
|
if (!snapshot) return
|
|||
|
|
const cameraView = snapshot.cameraView
|
|||
|
|
try {
|
|||
|
|
if (scene.mode === Cesium.SceneMode.SCENE2D) {
|
|||
|
|
if (snapshot.viewRectangle) {
|
|||
|
|
const rect = Cesium.Rectangle.fromDegrees(
|
|||
|
|
snapshot.viewRectangle.west,
|
|||
|
|
snapshot.viewRectangle.south,
|
|||
|
|
snapshot.viewRectangle.east,
|
|||
|
|
snapshot.viewRectangle.north,
|
|||
|
|
)
|
|||
|
|
viewer.camera.setView({ destination: rect })
|
|||
|
|
} else if (cameraView) {
|
|||
|
|
viewer.camera.setView({
|
|||
|
|
destination: Cesium.Cartesian3.fromDegrees(
|
|||
|
|
Number(cameraView.lon) || 0,
|
|||
|
|
Number(cameraView.lat) || 0,
|
|||
|
|
Number(cameraView.height) || 1500,
|
|||
|
|
),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
} else if (cameraView) {
|
|||
|
|
if (cameraService.value && typeof cameraService.value.setView === 'function') {
|
|||
|
|
cameraService.value.setView(cameraView)
|
|||
|
|
} else {
|
|||
|
|
viewer.camera.setView({
|
|||
|
|
destination: Cesium.Cartesian3.fromDegrees(
|
|||
|
|
Number(cameraView.lon) || 0,
|
|||
|
|
Number(cameraView.lat) || 0,
|
|||
|
|
Number(cameraView.height) || 1500,
|
|||
|
|
),
|
|||
|
|
orientation: {
|
|||
|
|
heading: Cesium.Math.toRadians(Number(cameraView.heading) || 0),
|
|||
|
|
pitch: Cesium.Math.toRadians(Number(cameraView.pitch) || 0),
|
|||
|
|
roll: Cesium.Math.toRadians(Number(cameraView.roll) || 0),
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (viewer?.scene?.requestRender) {
|
|||
|
|
viewer.scene.requestRender()
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('恢复相机视角失败', error)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* 场景模式切换完成后恢复快照状态。
|
|||
|
|
* @param {Cesium.Scene} scene Cesium 场景
|
|||
|
|
* @returns {void} 无返回值
|
|||
|
|
*/
|
|||
|
|
function restoreSceneAfterMorph(scene) {
|
|||
|
|
const payload = pendingRestoreState.value
|
|||
|
|
pendingRestoreState.value = null
|
|||
|
|
if (!payload || !mapStore.ready) return
|
|||
|
|
|
|||
|
|
resolveServices()
|
|||
|
|
|
|||
|
|
let viewer = null
|
|||
|
|
try {
|
|||
|
|
viewer = mapStore.getViewer()
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('恢复场景失败,未获取到 Viewer', error)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!viewer) return
|
|||
|
|
applyLayerState(viewer, payload.snapshot)
|
|||
|
|
applyCameraState(scene, viewer, payload.snapshot)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 挂载场景监听,感知模式切换。
|
|||
|
|
* @param {Cesium.Scene | null} scene Cesium 场景
|
|||
|
|
* @returns {void} 无返回值
|
|||
|
|
*/
|
|||
|
|
function attachScene(scene) {
|
|||
|
|
if (sceneRef.value === scene) {
|
|||
|
|
syncSceneMode(scene)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (typeof detachMorphStartListener === 'function') {
|
|||
|
|
detachMorphStartListener()
|
|||
|
|
}
|
|||
|
|
if (typeof detachMorphCompleteListener === 'function') {
|
|||
|
|
detachMorphCompleteListener()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sceneRef.value = scene
|
|||
|
|
if (!scene) {
|
|||
|
|
syncSceneMode(null)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
syncSceneMode(scene)
|
|||
|
|
|
|||
|
|
const handleMorphStart = () => {
|
|||
|
|
isMorphing.value = true
|
|||
|
|
}
|
|||
|
|
const handleMorphComplete = () => {
|
|||
|
|
isMorphing.value = false
|
|||
|
|
syncSceneMode(scene)
|
|||
|
|
restoreSceneAfterMorph(scene)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
scene.morphStart.addEventListener(handleMorphStart)
|
|||
|
|
scene.morphComplete.addEventListener(handleMorphComplete)
|
|||
|
|
detachMorphStartListener = () => {
|
|||
|
|
scene.morphStart.removeEventListener(handleMorphStart)
|
|||
|
|
}
|
|||
|
|
detachMorphCompleteListener = () => {
|
|||
|
|
scene.morphComplete.removeEventListener(handleMorphComplete)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从 Store 中解析并挂载场景及依赖服务。
|
|||
|
|
* @returns {void} 无返回值
|
|||
|
|
*/
|
|||
|
|
function resolveSceneFromStore() {
|
|||
|
|
if (!mapStore.ready) {
|
|||
|
|
cleanupScene()
|
|||
|
|
resolveServices()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
resolveServices()
|
|||
|
|
const viewer = mapStore.getViewer()
|
|||
|
|
attachScene(viewer?.scene ?? null)
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('解析 Cesium 场景失败', error)
|
|||
|
|
cleanupScene()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 切换 Cesium 二维与三维模式,并记录快照。
|
|||
|
|
* @returns {void} 无返回值
|
|||
|
|
*/
|
|||
|
|
function toggleSceneMode() {
|
|||
|
|
if (!mapStore.ready || isMorphing.value) return
|
|||
|
|
|
|||
|
|
let viewer = null
|
|||
|
|
try {
|
|||
|
|
viewer = mapStore.getViewer()
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('切换模式失败,未获取到 Viewer', error)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (!viewer) return
|
|||
|
|
|
|||
|
|
resolveServices()
|
|||
|
|
|
|||
|
|
const scene = viewer.scene
|
|||
|
|
const targetIs3D = !is3DMode.value
|
|||
|
|
const snapshot = captureSceneSnapshot(viewer)
|
|||
|
|
pendingRestoreState.value = { snapshot }
|
|||
|
|
|
|||
|
|
is3DMode.value = targetIs3D
|
|||
|
|
isMorphing.value = true
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
if (targetIs3D) {
|
|||
|
|
scene.morphTo3D(0.6)
|
|||
|
|
} else {
|
|||
|
|
scene.morphTo2D(0.6)
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('切换场景模式失败', error)
|
|||
|
|
isMorphing.value = false
|
|||
|
|
syncSceneMode(scene)
|
|||
|
|
restoreSceneAfterMorph(scene)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const buttonLabel = computed(() => (is3DMode.value ? '2D' : '3D'))
|
|||
|
|
const buttonTitle = computed(() => (is3DMode.value ? '切换到二维模式' : '切换到三维模式'))
|
|||
|
|
const canToggle = computed(() => !!sceneRef.value && !isMorphing.value)
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
if (mapStore.ready) {
|
|||
|
|
resolveSceneFromStore()
|
|||
|
|
}
|
|||
|
|
detachReadyListener = mapStore.onReady(() => {
|
|||
|
|
resolveSceneFromStore()
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
onBeforeUnmount(() => {
|
|||
|
|
cleanupScene()
|
|||
|
|
if (typeof detachReadyListener === 'function') {
|
|||
|
|
detachReadyListener()
|
|||
|
|
}
|
|||
|
|
detachReadyListener = null
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
watch(
|
|||
|
|
() => mapStore.ready,
|
|||
|
|
(ready) => {
|
|||
|
|
if (ready) {
|
|||
|
|
resolveSceneFromStore()
|
|||
|
|
} else {
|
|||
|
|
cleanupScene()
|
|||
|
|
resolveServices()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped lang="scss">
|
|||
|
|
.scene-mode-toggle {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
width: 40px;
|
|||
|
|
height: 40px;
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
background: rgba(255, 255, 255, 0.92);
|
|||
|
|
color: #1f1f1f;
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
cursor: pointer;
|
|||
|
|
pointer-events: auto;
|
|||
|
|
transition: background-color 0.2s ease, transform 0.2s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.scene-mode-toggle:not(:disabled):hover {
|
|||
|
|
background: #ffffff;
|
|||
|
|
transform: translateY(-1px);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.scene-mode-toggle:disabled {
|
|||
|
|
cursor: not-allowed;
|
|||
|
|
opacity: 0.5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
.scene-mode-toggle {
|
|||
|
|
width: 36px;
|
|||
|
|
height: 36px;
|
|||
|
|
font-size: 13px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|