|
|
@@ -7,7 +7,96 @@
|
|
|
destroy-on-close
|
|
|
>
|
|
|
<div class="model-preview-container">
|
|
|
- <div ref="modelContainer" class="model-container"></div>
|
|
|
+ <div ref="modelContainer" class="model-container">
|
|
|
+ <!-- 光照控制按钮 -->
|
|
|
+ <div class="light-control-btn" @click="toggleLightControl">
|
|
|
+ <el-icon><Sunny /></el-icon>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 光照控制面板 -->
|
|
|
+ <div v-if="showLightControl" class="light-control-panel">
|
|
|
+ <h4>光照控制</h4>
|
|
|
+
|
|
|
+ <!-- 环境光强度 -->
|
|
|
+ <div class="control-item">
|
|
|
+ <label>环境光强度</label>
|
|
|
+ <el-slider
|
|
|
+ v-model="lightSettings.ambientIntensity"
|
|
|
+ :min="0"
|
|
|
+ :max="5"
|
|
|
+ :step="0.1"
|
|
|
+ @change="updateLights"
|
|
|
+ />
|
|
|
+ <span class="value">{{ lightSettings.ambientIntensity.toFixed(1) }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 主平行光强度 -->
|
|
|
+ <div class="control-item">
|
|
|
+ <label>主光源强度</label>
|
|
|
+ <el-slider
|
|
|
+ v-model="lightSettings.directionalIntensity"
|
|
|
+ :min="0"
|
|
|
+ :max="10"
|
|
|
+ :step="0.1"
|
|
|
+ @change="updateLights"
|
|
|
+ />
|
|
|
+ <span class="value">{{ lightSettings.directionalIntensity.toFixed(1) }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 辅助光强度 -->
|
|
|
+ <div class="control-item">
|
|
|
+ <label>辅助光强度</label>
|
|
|
+ <el-slider
|
|
|
+ v-model="lightSettings.directionalIntensity2"
|
|
|
+ :min="0"
|
|
|
+ :max="5"
|
|
|
+ :step="0.1"
|
|
|
+ @change="updateLights"
|
|
|
+ />
|
|
|
+ <span class="value">{{ lightSettings.directionalIntensity2.toFixed(1) }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 主光源角度 -->
|
|
|
+ <div class="control-item">
|
|
|
+ <label>主光源角度</label>
|
|
|
+ <div class="angle-controls">
|
|
|
+ <div>
|
|
|
+ <span>X轴</span>
|
|
|
+ <el-slider
|
|
|
+ v-model="lightSettings.directionalPosition.x"
|
|
|
+ :min="-5"
|
|
|
+ :max="5"
|
|
|
+ :step="0.1"
|
|
|
+ @change="updateLights"
|
|
|
+ />
|
|
|
+ <span class="value">{{ lightSettings.directionalPosition.x.toFixed(1) }}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span>Y轴</span>
|
|
|
+ <el-slider
|
|
|
+ v-model="lightSettings.directionalPosition.y"
|
|
|
+ :min="-5"
|
|
|
+ :max="5"
|
|
|
+ :step="0.1"
|
|
|
+ @change="updateLights"
|
|
|
+ />
|
|
|
+ <span class="value">{{ lightSettings.directionalPosition.y.toFixed(1) }}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span>Z轴</span>
|
|
|
+ <el-slider
|
|
|
+ v-model="lightSettings.directionalPosition.z"
|
|
|
+ :min="-5"
|
|
|
+ :max="5"
|
|
|
+ :step="0.1"
|
|
|
+ @change="updateLights"
|
|
|
+ />
|
|
|
+ <span class="value">{{ lightSettings.directionalPosition.z.toFixed(1) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<div class="model-info">
|
|
|
<h3>模型信息</h3>
|
|
|
<el-descriptions :column="1" border>
|
|
|
@@ -36,6 +125,7 @@ import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'
|
|
|
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js'
|
|
|
// 导入项目的request实例
|
|
|
import request from '@/utils/request'
|
|
|
+import { Sunny } from '@element-plus/icons-vue'
|
|
|
|
|
|
const props = defineProps({
|
|
|
visible: {
|
|
|
@@ -59,6 +149,22 @@ let controls = null
|
|
|
let animationId = null
|
|
|
const dialogVisible = ref(false)
|
|
|
|
|
|
+// 光照控制相关变量
|
|
|
+const showLightControl = ref(false)
|
|
|
+let ambientLight = null
|
|
|
+let directionalLight = null
|
|
|
+let directionalLight2 = null
|
|
|
+const lightSettings = ref({
|
|
|
+ ambientIntensity: 2.0,
|
|
|
+ directionalIntensity: 4.0,
|
|
|
+ directionalIntensity2: 2.0,
|
|
|
+ directionalPosition: {
|
|
|
+ x: 1,
|
|
|
+ y: 1,
|
|
|
+ z: 1
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
// 获取类型显示名称
|
|
|
const getTypeDisplayName = (type) => {
|
|
|
const typeMapping = {
|
|
|
@@ -97,12 +203,21 @@ const initScene = () => {
|
|
|
modelContainer.value.appendChild(renderer.domElement)
|
|
|
|
|
|
// 添加光源
|
|
|
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
|
|
|
+ ambientLight = new THREE.AmbientLight(0xffffff, lightSettings.value.ambientIntensity)
|
|
|
scene.add(ambientLight)
|
|
|
|
|
|
- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8)
|
|
|
- directionalLight.position.set(1, 1, 1)
|
|
|
+ directionalLight = new THREE.DirectionalLight(0xffffff, lightSettings.value.directionalIntensity)
|
|
|
+ directionalLight.position.set(
|
|
|
+ lightSettings.value.directionalPosition.x,
|
|
|
+ lightSettings.value.directionalPosition.y,
|
|
|
+ lightSettings.value.directionalPosition.z
|
|
|
+ )
|
|
|
scene.add(directionalLight)
|
|
|
+
|
|
|
+ // 添加第二个平行光,从不同角度照射,增强照明效果
|
|
|
+ directionalLight2 = new THREE.DirectionalLight(0xffffff, lightSettings.value.directionalIntensity2)
|
|
|
+ directionalLight2.position.set(-1, 1, -1)
|
|
|
+ scene.add(directionalLight2)
|
|
|
|
|
|
// 添加网格辅助线
|
|
|
const gridHelper = new THREE.GridHelper(10, 10)
|
|
|
@@ -124,12 +239,16 @@ const initScene = () => {
|
|
|
|
|
|
// 监听窗口大小变化
|
|
|
window.addEventListener('resize', handleResize)
|
|
|
+
|
|
|
+ // 添加光照控制面板事件阻止
|
|
|
+ addLightControlEventPrevent()
|
|
|
}
|
|
|
|
|
|
// 初始化控制器
|
|
|
const initControls = () => {
|
|
|
// 简单的鼠标控制实现 - 控制相机视角
|
|
|
let isDragging = false
|
|
|
+ let isPanning = false // 中键平移状态
|
|
|
let previousMousePosition = { x: 0, y: 0 }
|
|
|
|
|
|
// 相机目标点(看向的点)
|
|
|
@@ -149,21 +268,70 @@ const initControls = () => {
|
|
|
updateCameraTarget()
|
|
|
|
|
|
const container = modelContainer.value
|
|
|
+
|
|
|
+ // 检查事件是否来自光照控制面板
|
|
|
+ const isFromLightControl = (e) => {
|
|
|
+ let target = e.target
|
|
|
+ while (target) {
|
|
|
+ if (target.classList && (target.classList.contains('light-control-panel') || target.classList.contains('light-control-btn'))) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ target = target.parentElement
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
container.addEventListener('mousedown', (e) => {
|
|
|
- isDragging = true
|
|
|
+ // 如果事件来自光照控制面板,不处理
|
|
|
+ if (isFromLightControl(e)) return
|
|
|
+
|
|
|
+ // 检查是否是中键
|
|
|
+ if (e.button === 1) { // 中键
|
|
|
+ isPanning = true
|
|
|
+ } else if (e.button === 0) { // 左键
|
|
|
+ isDragging = true
|
|
|
+ }
|
|
|
+
|
|
|
previousMousePosition = { x: e.offsetX, y: e.offsetY }
|
|
|
})
|
|
|
|
|
|
container.addEventListener('mousemove', (e) => {
|
|
|
- if (!isDragging) return
|
|
|
-
|
|
|
+ // 如果事件来自光照控制面板,不处理
|
|
|
+ if (isFromLightControl(e)) return
|
|
|
+
|
|
|
const deltaMove = {
|
|
|
x: e.offsetX - previousMousePosition.x,
|
|
|
y: e.offsetY - previousMousePosition.y
|
|
|
}
|
|
|
|
|
|
- // 控制相机围绕目标点旋转
|
|
|
- if (camera) {
|
|
|
+ // 中键平移相机
|
|
|
+ if (isPanning && camera) {
|
|
|
+ // 计算平移距离
|
|
|
+ const distance = camera.position.distanceTo(cameraTarget)
|
|
|
+ const panSpeed = distance * 0.001
|
|
|
+
|
|
|
+ // 计算相机的右方向和上方向
|
|
|
+ const right = new THREE.Vector3()
|
|
|
+ const up = new THREE.Vector3()
|
|
|
+ camera.getWorldDirection(right)
|
|
|
+ right.crossVectors(camera.up, right).normalize()
|
|
|
+ up.copy(camera.up).normalize()
|
|
|
+
|
|
|
+ // 计算平移向量
|
|
|
+ const pan = new THREE.Vector3()
|
|
|
+ pan.addScaledVector(right, deltaMove.x * panSpeed)
|
|
|
+ pan.addScaledVector(up, deltaMove.y * panSpeed)
|
|
|
+
|
|
|
+ // 平移相机和目标点
|
|
|
+ camera.position.add(pan)
|
|
|
+ cameraTarget.add(pan)
|
|
|
+
|
|
|
+ // 相机看向目标点
|
|
|
+ camera.lookAt(cameraTarget)
|
|
|
+ }
|
|
|
+ // 左键旋转相机
|
|
|
+ else if (isDragging && camera) {
|
|
|
+ // 控制相机围绕目标点旋转
|
|
|
// 计算相机到目标点的距离
|
|
|
const distance = camera.position.distanceTo(cameraTarget)
|
|
|
|
|
|
@@ -192,16 +360,27 @@ const initControls = () => {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
- container.addEventListener('mouseup', () => {
|
|
|
+ container.addEventListener('mouseup', (e) => {
|
|
|
+ // 如果事件来自光照控制面板,不处理
|
|
|
+ if (isFromLightControl(e)) return
|
|
|
+
|
|
|
isDragging = false
|
|
|
+ isPanning = false
|
|
|
})
|
|
|
|
|
|
- container.addEventListener('mouseleave', () => {
|
|
|
+ container.addEventListener('mouseleave', (e) => {
|
|
|
+ // 如果事件来自光照控制面板,不处理
|
|
|
+ if (isFromLightControl(e)) return
|
|
|
+
|
|
|
isDragging = false
|
|
|
+ isPanning = false
|
|
|
})
|
|
|
|
|
|
// 滚轮缩放 - 控制相机距离目标点的距离
|
|
|
container.addEventListener('wheel', (e) => {
|
|
|
+ // 如果事件来自光照控制面板,不处理
|
|
|
+ if (isFromLightControl(e)) return
|
|
|
+
|
|
|
e.preventDefault()
|
|
|
if (camera) {
|
|
|
// 计算相机到目标点的向量
|
|
|
@@ -225,30 +404,90 @@ const initControls = () => {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+// 为光照控制面板添加事件阻止
|
|
|
+const addLightControlEventPrevent = () => {
|
|
|
+ const container = modelContainer.value
|
|
|
+ if (!container) return
|
|
|
+
|
|
|
+ // 为光照控制面板添加事件监听器,阻止事件传播
|
|
|
+ const lightControlPanel = container.querySelector('.light-control-panel')
|
|
|
+ const lightControlBtn = container.querySelector('.light-control-btn')
|
|
|
+
|
|
|
+ const preventEvents = (element) => {
|
|
|
+ if (element) {
|
|
|
+ element.addEventListener('mousedown', (e) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ })
|
|
|
+ element.addEventListener('mousemove', (e) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ })
|
|
|
+ element.addEventListener('mouseup', (e) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ })
|
|
|
+ element.addEventListener('wheel', (e) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ e.preventDefault()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ preventEvents(lightControlPanel)
|
|
|
+ preventEvents(lightControlBtn)
|
|
|
+}
|
|
|
+
|
|
|
// 添加三维坐标轴
|
|
|
const addAxesHelper = () => {
|
|
|
// 移除现有的坐标轴
|
|
|
if (scene) {
|
|
|
scene.traverse((object) => {
|
|
|
- if (object.isAxesHelper) {
|
|
|
+ if (object.isAxesHelper || object.userData.isCustomAxes) {
|
|
|
scene.remove(object)
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- // 创建新的坐标轴辅助器
|
|
|
+ // 创建带箭头的坐标轴
|
|
|
if (scene) {
|
|
|
- const axesHelper = new THREE.AxesHelper(2)
|
|
|
- // 设置坐标轴位置在世界空间的固定位置
|
|
|
- // 这个位置会显示在视口左下角
|
|
|
- axesHelper.position.set(-4, -4, 0)
|
|
|
- axesHelper.scale.set(1, 1, 1)
|
|
|
+ const axisLength = 1
|
|
|
+ const arrowSize = 0.1
|
|
|
+
|
|
|
+ // X轴 - 红色
|
|
|
+ const xArrow = new THREE.ArrowHelper(
|
|
|
+ new THREE.Vector3(1, 0, 0), // 方向
|
|
|
+ new THREE.Vector3(0, 0, 0), // 起点
|
|
|
+ axisLength, // 长度
|
|
|
+ 0xff0000, // 颜色
|
|
|
+ arrowSize, // 箭头大小
|
|
|
+ arrowSize // 箭头宽度
|
|
|
+ )
|
|
|
+ xArrow.userData.isCustomAxes = true
|
|
|
+ scene.add(xArrow)
|
|
|
|
|
|
- // 确保坐标轴不会受到场景其他变换的影响
|
|
|
- axesHelper.updateMatrixWorld(true)
|
|
|
+ // Y轴 - 绿色
|
|
|
+ const yArrow = new THREE.ArrowHelper(
|
|
|
+ new THREE.Vector3(0, 1, 0),
|
|
|
+ new THREE.Vector3(0, 0, 0),
|
|
|
+ axisLength,
|
|
|
+ 0x00ff00,
|
|
|
+ arrowSize,
|
|
|
+ arrowSize
|
|
|
+ )
|
|
|
+ yArrow.userData.isCustomAxes = true
|
|
|
+ scene.add(yArrow)
|
|
|
|
|
|
- scene.add(axesHelper)
|
|
|
- console.log('三维坐标轴已添加到视口左下角')
|
|
|
+ // Z轴 - 蓝色
|
|
|
+ const zArrow = new THREE.ArrowHelper(
|
|
|
+ new THREE.Vector3(0, 0, 1),
|
|
|
+ new THREE.Vector3(0, 0, 0),
|
|
|
+ axisLength,
|
|
|
+ 0x0000ff,
|
|
|
+ arrowSize,
|
|
|
+ arrowSize
|
|
|
+ )
|
|
|
+ zArrow.userData.isCustomAxes = true
|
|
|
+ scene.add(zArrow)
|
|
|
+
|
|
|
+ console.log('带箭头的三维坐标轴已添加到场景原点')
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -563,28 +802,29 @@ const processFBXModel = (fbxModel) => {
|
|
|
console.log('处理FBX模型缩放...')
|
|
|
console.log('原始模型缩放:', fbxModel.scale)
|
|
|
|
|
|
- // 计算模型的整体缩放因子
|
|
|
- let maxScale = 1
|
|
|
- fbxModel.traverse((child) => {
|
|
|
- if (child.isMesh) {
|
|
|
- maxScale = Math.max(maxScale, child.scale.x, child.scale.y, child.scale.z)
|
|
|
- }
|
|
|
- })
|
|
|
+ // 计算模型的边界框,用于确定合适的缩放
|
|
|
+ let tempBox = new THREE.Box3().setFromObject(fbxModel)
|
|
|
+ let tempSize = tempBox.getSize(new THREE.Vector3())
|
|
|
+ console.log('原始模型边界框大小:', tempSize)
|
|
|
|
|
|
- console.log('最大缩放因子:', maxScale)
|
|
|
+ // 计算模型的整体尺寸
|
|
|
+ const maxDimension = Math.max(tempSize.x, tempSize.y, tempSize.z)
|
|
|
+ console.log('模型最大尺寸:', maxDimension)
|
|
|
|
|
|
- // 如果缩放因子过大或过小,进行统一调整
|
|
|
- if (maxScale > 10 || maxScale < 0.1) {
|
|
|
- const scaleFactor = 1 / maxScale
|
|
|
- console.log('需要调整缩放,缩放因子:', scaleFactor)
|
|
|
-
|
|
|
- fbxModel.traverse((child) => {
|
|
|
- if (child.isMesh) {
|
|
|
- child.scale.multiplyScalar(scaleFactor)
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- console.log('缩放调整完成')
|
|
|
+ // 计算合适的缩放因子,确保模型大小适中
|
|
|
+ let scaleFactor = 1
|
|
|
+ if (maxDimension > 10) {
|
|
|
+ scaleFactor = 5 / maxDimension
|
|
|
+ console.log('模型过大,需要缩小,缩放因子:', scaleFactor)
|
|
|
+ } else if (maxDimension < 1) {
|
|
|
+ scaleFactor = 2 / maxDimension
|
|
|
+ console.log('模型过小,需要放大,缩放因子:', scaleFactor)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 应用缩放因子到整个模型
|
|
|
+ if (scaleFactor !== 1) {
|
|
|
+ fbxModel.scale.multiplyScalar(scaleFactor)
|
|
|
+ console.log('缩放调整完成,新的模型缩放:', fbxModel.scale)
|
|
|
}
|
|
|
|
|
|
// 3. 强制更新所有矩阵
|
|
|
@@ -614,26 +854,25 @@ const processFBXModel = (fbxModel) => {
|
|
|
console.log('处理网格:', child.name)
|
|
|
|
|
|
try {
|
|
|
- if (!child.material ||
|
|
|
- child.material.type === 'unknown' ||
|
|
|
- child.material.type === 'MeshPhongMaterial') {
|
|
|
- console.log('为', child.name, '添加默认材质')
|
|
|
- const defaultMaterial = new THREE.MeshStandardMaterial({
|
|
|
- color: 0x409eff,
|
|
|
- metalness: 0.3,
|
|
|
- roughness: 0.7,
|
|
|
- transparent: false,
|
|
|
- opacity: 1
|
|
|
- })
|
|
|
- child.material = defaultMaterial
|
|
|
- materialCount++
|
|
|
- }
|
|
|
+ // 为所有FBX网格添加默认材质,确保每个网格都有材质
|
|
|
+ console.log('为', child.name, '添加默认材质')
|
|
|
+ const defaultMaterial = new THREE.MeshStandardMaterial({
|
|
|
+ color: 0x409eff,
|
|
|
+ metalness: 0.3,
|
|
|
+ roughness: 0.7,
|
|
|
+ transparent: false,
|
|
|
+ opacity: 1,
|
|
|
+ side: THREE.FrontSide
|
|
|
+ })
|
|
|
+ child.material = defaultMaterial
|
|
|
+ materialCount++
|
|
|
} catch (error) {
|
|
|
console.error('处理材质时出错:', error.message)
|
|
|
const fallbackMaterial = new THREE.MeshStandardMaterial({
|
|
|
color: 0x409eff,
|
|
|
metalness: 0.3,
|
|
|
- roughness: 0.7
|
|
|
+ roughness: 0.7,
|
|
|
+ side: THREE.FrontSide
|
|
|
})
|
|
|
child.material = fallbackMaterial
|
|
|
materialCount++
|
|
|
@@ -1106,6 +1345,48 @@ watch(() => props.modelData, (newVal) => {
|
|
|
modelName.value = newVal.name || '模型预览'
|
|
|
}
|
|
|
}, { immediate: true })
|
|
|
+
|
|
|
+// 光照控制方法
|
|
|
+const toggleLightControl = () => {
|
|
|
+ showLightControl.value = !showLightControl.value
|
|
|
+}
|
|
|
+
|
|
|
+// 更新光源设置
|
|
|
+const updateLights = () => {
|
|
|
+ // 保存相机当前位置和目标点
|
|
|
+ let cameraPosition = null
|
|
|
+ let cameraTarget = null
|
|
|
+ if (camera) {
|
|
|
+ cameraPosition = camera.position.clone()
|
|
|
+ // 获取相机看向的点
|
|
|
+ const vector = new THREE.Vector3()
|
|
|
+ camera.getWorldDirection(vector)
|
|
|
+ cameraTarget = camera.position.clone().add(vector)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新光源属性
|
|
|
+ if (ambientLight) {
|
|
|
+ ambientLight.intensity = lightSettings.value.ambientIntensity
|
|
|
+ }
|
|
|
+ if (directionalLight) {
|
|
|
+ directionalLight.intensity = lightSettings.value.directionalIntensity
|
|
|
+ directionalLight.position.set(
|
|
|
+ lightSettings.value.directionalPosition.x,
|
|
|
+ lightSettings.value.directionalPosition.y,
|
|
|
+ lightSettings.value.directionalPosition.z
|
|
|
+ )
|
|
|
+ }
|
|
|
+ if (directionalLight2) {
|
|
|
+ directionalLight2.intensity = lightSettings.value.directionalIntensity2
|
|
|
+ }
|
|
|
+
|
|
|
+ // 恢复相机位置和目标点
|
|
|
+ if (camera && cameraPosition) {
|
|
|
+ camera.position.copy(cameraPosition)
|
|
|
+ camera.lookAt(cameraTarget)
|
|
|
+ camera.updateProjectionMatrix()
|
|
|
+ }
|
|
|
+}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
@@ -1120,6 +1401,7 @@ watch(() => props.modelData, (newVal) => {
|
|
|
background-color: #fafafa;
|
|
|
border-radius: 8px;
|
|
|
overflow: hidden;
|
|
|
+ position: relative;
|
|
|
}
|
|
|
|
|
|
.model-info {
|
|
|
@@ -1149,4 +1431,111 @@ watch(() => props.modelData, (newVal) => {
|
|
|
max-height: 300px;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+/* 光照控制样式 */
|
|
|
+.light-control-btn {
|
|
|
+ position: absolute;
|
|
|
+ top: 10px;
|
|
|
+ right: 10px;
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ background-color: rgba(255, 255, 255, 0.9);
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
|
+ z-index: 1000;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.light-control-btn:hover {
|
|
|
+ background-color: #ffffff;
|
|
|
+ transform: scale(1.1);
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.light-control-panel {
|
|
|
+ position: absolute;
|
|
|
+ top: 60px;
|
|
|
+ right: 10px;
|
|
|
+ width: 300px;
|
|
|
+ background-color: rgba(255, 255, 255, 0.95);
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 20px;
|
|
|
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
|
|
|
+ z-index: 1000;
|
|
|
+ backdrop-filter: blur(5px);
|
|
|
+ pointer-events: auto;
|
|
|
+ user-select: none;
|
|
|
+}
|
|
|
+
|
|
|
+.light-control-panel h4 {
|
|
|
+ margin: 0 0 15px 0;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.control-item {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.control-item label {
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+
|
|
|
+.control-item .value {
|
|
|
+ position: absolute;
|
|
|
+ right: 10px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ min-width: 40px;
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+
|
|
|
+.angle-controls > div {
|
|
|
+ position: relative;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.angle-controls > div > span {
|
|
|
+ display: inline-block;
|
|
|
+ width: 40px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ margin-right: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.angle-controls .el-slider {
|
|
|
+ display: inline-block;
|
|
|
+ width: calc(100% - 100px);
|
|
|
+ vertical-align: middle;
|
|
|
+}
|
|
|
+
|
|
|
+.angle-controls .value {
|
|
|
+ position: static;
|
|
|
+ display: inline-block;
|
|
|
+ transform: none;
|
|
|
+ vertical-align: middle;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式调整 */
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .light-control-panel {
|
|
|
+ width: 250px;
|
|
|
+ right: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .angle-controls .el-slider {
|
|
|
+ width: calc(100% - 90px);
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|