|  | @@ -1,12 +1,5 @@
 | 
	
		
			
				|  |  |  <template>
 | 
	
		
			
				|  |  | -    <!-- 进度条 -->
 | 
	
		
			
				|  |  | -    <!-- <el-progress
 | 
	
		
			
				|  |  | -      :percentage="progress"
 | 
	
		
			
				|  |  | -      :status="progress === 100 ? 'success' : ''"
 | 
	
		
			
				|  |  | -      :stroke-width="10"
 | 
	
		
			
				|  |  | -      :text-inside="true"
 | 
	
		
			
				|  |  | -      style="width: 100%; margin-bottom: 20px;"
 | 
	
		
			
				|  |  | -    /> -->
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    <div ref="threeContainer" class="three-container" :style="{height: height}">
 | 
	
		
			
				|  |  |      <!-- 添加色卡Canvas -->
 | 
	
		
			
				|  |  |      <canvas 
 | 
	
	
		
			
				|  | @@ -14,6 +7,7 @@
 | 
	
		
			
				|  |  |        ref="colorCardCanvas" 
 | 
	
		
			
				|  |  |        class="color-card-canvas"
 | 
	
		
			
				|  |  |        :style="colorCardStyle"
 | 
	
		
			
				|  |  | +      @mousedown="startDrag"
 | 
	
		
			
				|  |  |      ></canvas>
 | 
	
		
			
				|  |  |    </div>
 | 
	
		
			
				|  |  |  </template>
 | 
	
	
		
			
				|  | @@ -53,6 +47,10 @@ const threeContainer = ref(null);
 | 
	
		
			
				|  |  |  const colorCardCanvas = ref(null)
 | 
	
		
			
				|  |  |  const colorCardContext = ref(null)
 | 
	
		
			
				|  |  |  const progress = ref(0); // 进度条的值
 | 
	
		
			
				|  |  | +// 添加拖拽相关状态
 | 
	
		
			
				|  |  | +const isDragging = ref(false)
 | 
	
		
			
				|  |  | +const dragStartPos = ref({ x: 0, y: 0 })
 | 
	
		
			
				|  |  | +const currentPos = ref({ x: 0.85, y: 0.85 })  // 初始位置
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  let scene, camera, renderer, controls
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -156,28 +154,117 @@ const renderVTK = (data) => {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// 开始拖拽
 | 
	
		
			
				|  |  | +const startDrag = (e) => {
 | 
	
		
			
				|  |  | +  if (!colorCardCanvas.value) return
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  const containerRect = threeContainer.value.getBoundingClientRect()
 | 
	
		
			
				|  |  | +  const cardRect = colorCardCanvas.value.getBoundingClientRect()
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  // 检查点击是否在色卡上
 | 
	
		
			
				|  |  | +  if (
 | 
	
		
			
				|  |  | +    e.clientX < cardRect.left ||
 | 
	
		
			
				|  |  | +    e.clientX > cardRect.right ||
 | 
	
		
			
				|  |  | +    e.clientY < cardRect.top ||
 | 
	
		
			
				|  |  | +    e.clientY > cardRect.bottom
 | 
	
		
			
				|  |  | +  ) {
 | 
	
		
			
				|  |  | +    return
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  isDragging.value = true
 | 
	
		
			
				|  |  | +  dragStartPos.value = {
 | 
	
		
			
				|  |  | +    x: e.clientX,
 | 
	
		
			
				|  |  | +    y: e.clientY
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  // 使用passive: false确保能调用preventDefault
 | 
	
		
			
				|  |  | +  window.addEventListener('mousemove', handleDrag, { passive: false })
 | 
	
		
			
				|  |  | +  window.addEventListener('mouseup', stopDrag, { passive: true })
 | 
	
		
			
				|  |  | +  e.preventDefault()
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// 添加拖拽边界限制
 | 
	
		
			
				|  |  | +const handleDrag = (e) => {
 | 
	
		
			
				|  |  | +  if (!isDragging.value || !colorCardCanvas.value) return
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  const containerRect = threeContainer.value.getBoundingClientRect()
 | 
	
		
			
				|  |  | +  const cardRect = colorCardCanvas.value.getBoundingClientRect()
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  const dx = e.clientX - dragStartPos.value.x
 | 
	
		
			
				|  |  | +  const dy = e.clientY - dragStartPos.value.y
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  // 计算新位置(像素值)
 | 
	
		
			
				|  |  | +  let newX = (currentPos.value.x * containerRect.width) + dx
 | 
	
		
			
				|  |  | +  let newY = (currentPos.value.y * containerRect.height) + dy
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  // 边界检查(确保色卡完全留在容器内)
 | 
	
		
			
				|  |  | +  newX = Math.max(cardRect.width/2, Math.min(containerRect.width - cardRect.width/2, newX))
 | 
	
		
			
				|  |  | +  newY = Math.max(cardRect.height/2, Math.min(containerRect.height - cardRect.height/2, newY))
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  // 转换为相对位置 (0-1)
 | 
	
		
			
				|  |  | +  currentPos.value.x = newX / containerRect.width
 | 
	
		
			
				|  |  | +  currentPos.value.y = newY / containerRect.height
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  dragStartPos.value = { x: e.clientX, y: e.clientY }
 | 
	
		
			
				|  |  | +  e.preventDefault() // 防止默认行为
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  // 强制更新样式
 | 
	
		
			
				|  |  | +  colorCardCanvas.value.style.left = `${newX}px`
 | 
	
		
			
				|  |  | +  colorCardCanvas.value.style.top = `${newY}px`
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// 停止拖拽
 | 
	
		
			
				|  |  | +const stopDrag = () => {
 | 
	
		
			
				|  |  | +  isDragging.value = false
 | 
	
		
			
				|  |  | +  window.removeEventListener('mousemove', handleDrag)
 | 
	
		
			
				|  |  | +  window.removeEventListener('mouseup', stopDrag)
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// 更新色卡位置
 | 
	
		
			
				|  |  | +const updateColorCardPosition = () => {
 | 
	
		
			
				|  |  | +  // 更新skvalue中的位置值
 | 
	
		
			
				|  |  | +  skvalue.value.X = currentPos.value.x.toFixed(2)
 | 
	
		
			
				|  |  | +  skvalue.value.Y = currentPos.value.y.toFixed(2)
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  // 触发色卡重新渲染
 | 
	
		
			
				|  |  | +  if (pltData.value?.config?.colorCard) {
 | 
	
		
			
				|  |  | +    pltData.value.config.colorCard.X = currentPos.value.x
 | 
	
		
			
				|  |  | +    pltData.value.config.colorCard.Y = currentPos.value.y
 | 
	
		
			
				|  |  | +    pltData.value = {...pltData.value}
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  // 计算色卡显示状态和样式
 | 
	
		
			
				|  |  |  const showColorCard = computed(() => {
 | 
	
		
			
				|  |  |    return props.data?.config?.colorCard?.visible
 | 
	
		
			
				|  |  |  })
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const colorCardStyle = computed(() => {
 | 
	
		
			
				|  |  | -  if (!showColorCard.value) return {}
 | 
	
		
			
				|  |  | +  if (!showColorCard.value || !threeContainer.value) return {}
 | 
	
		
			
				|  |  |    const config = props.data.config.colorCard
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  // 确保最小尺寸
 | 
	
		
			
				|  |  | +  const minWidth = 70 // 最小宽度(px)
 | 
	
		
			
				|  |  | +  const minHeight = 25 // 最小高度(px)
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  // 计算实际尺寸(取配置值和最小值的较大者)
 | 
	
		
			
				|  |  | +  const width = config.orientation === 'vertical' 
 | 
	
		
			
				|  |  | +    ? `${Math.max(minWidth, config.width * 100)}px`
 | 
	
		
			
				|  |  | +    : `${Math.max(minWidth, config.width * 100)}%`
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +  const height = config.orientation === 'vertical' 
 | 
	
		
			
				|  |  | +    ? `${Math.max(minHeight, config.height * 100)}%`
 | 
	
		
			
				|  |  | +    : `${Math.max(minHeight, config.height * 100)}px`
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  |    return {
 | 
	
		
			
				|  |  |      position: 'absolute',
 | 
	
		
			
				|  |  | -    [config.orientation === 'vertical' ? 'right' : 'bottom']: '10px',
 | 
	
		
			
				|  |  | -    [config.orientation === 'vertical' ? 'top' : 'left']: '50%',
 | 
	
		
			
				|  |  | -    transform: config.orientation === 'vertical' 
 | 
	
		
			
				|  |  | -      ? 'translateY(-50%)' 
 | 
	
		
			
				|  |  | -      : 'translateX(-50%)',
 | 
	
		
			
				|  |  | -    width: config.orientation === 'vertical' 
 | 
	
		
			
				|  |  | -      ? `${config.width * 100}px` 
 | 
	
		
			
				|  |  | -      : `${config.width * 100}%`,
 | 
	
		
			
				|  |  | -    height: config.orientation === 'vertical' 
 | 
	
		
			
				|  |  | -      ? `${config.height * 100}%` 
 | 
	
		
			
				|  |  | -      : `${config.height * 100}px`,
 | 
	
		
			
				|  |  | -    zIndex: 1000
 | 
	
		
			
				|  |  | +    left: `${currentPos.value.x * 100}%`,
 | 
	
		
			
				|  |  | +    top: `${currentPos.value.y * 100}%`,
 | 
	
		
			
				|  |  | +    transform: 'translate(-50%, -50%)',
 | 
	
		
			
				|  |  | +    width,
 | 
	
		
			
				|  |  | +    height,
 | 
	
		
			
				|  |  | +    zIndex: 1000,
 | 
	
		
			
				|  |  | +    cursor: 'move'
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  })
 | 
	
		
			
				|  |  |  // 初始化色卡
 | 
	
	
		
			
				|  | @@ -193,18 +280,37 @@ const renderColorCard = () => {
 | 
	
		
			
				|  |  |    
 | 
	
		
			
				|  |  |    const config = props.data.config.colorCard
 | 
	
		
			
				|  |  |    const ctx = colorCardContext.value
 | 
	
		
			
				|  |  | -  const width = colorCardCanvas.value.width
 | 
	
		
			
				|  |  | -  const height = colorCardCanvas.value.height
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  // 根据设备像素比调整canvas分辨率
 | 
	
		
			
				|  |  | +  const dpr = window.devicePixelRatio || 1
 | 
	
		
			
				|  |  | +  const width = colorCardCanvas.value.clientWidth * dpr
 | 
	
		
			
				|  |  | +  const height = colorCardCanvas.value.clientHeight * dpr
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  // 设置canvas实际尺寸
 | 
	
		
			
				|  |  | +  colorCardCanvas.value.width = width
 | 
	
		
			
				|  |  | +  colorCardCanvas.value.height = height
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  // 缩放绘图上下文
 | 
	
		
			
				|  |  | +  ctx.scale(dpr, dpr)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  if (config.orientation === 'vertical') {
 | 
	
		
			
				|  |  | +  canvas.width = 60 * (window.devicePixelRatio || 1)
 | 
	
		
			
				|  |  | +  canvas.height = threeContainer.value.clientHeight * 0.35 * (window.devicePixelRatio || 1)
 | 
	
		
			
				|  |  | +  } else {
 | 
	
		
			
				|  |  | +    canvas.width = threeContainer.value.clientWidth * 0.35 * (window.devicePixelRatio || 1)
 | 
	
		
			
				|  |  | +    canvas.height = 60 * (window.devicePixelRatio || 1)
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |    
 | 
	
		
			
				|  |  |    // 清除画布
 | 
	
		
			
				|  |  |    ctx.clearRect(0, 0, width, height)
 | 
	
		
			
				|  |  |    
 | 
	
		
			
				|  |  | -  // 创建渐变
 | 
	
		
			
				|  |  | +  // 创建渐变(根据朝向)
 | 
	
		
			
				|  |  | +  // 根据方向创建渐变
 | 
	
		
			
				|  |  |    let gradient
 | 
	
		
			
				|  |  |    if (config.orientation === 'vertical') {
 | 
	
		
			
				|  |  | -    gradient = ctx.createLinearGradient(0, height, 0, 0)
 | 
	
		
			
				|  |  | +    gradient = ctx.createLinearGradient(0, canvas.height, 0, 0)
 | 
	
		
			
				|  |  |    } else {
 | 
	
		
			
				|  |  | -    gradient = ctx.createLinearGradient(0, 0, width, 0)
 | 
	
		
			
				|  |  | +    gradient = ctx.createLinearGradient(0, 0, canvas.width, 0)
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    
 | 
	
		
			
				|  |  |    // 添加色卡颜色
 | 
	
	
		
			
				|  | @@ -216,11 +322,12 @@ const renderColorCard = () => {
 | 
	
		
			
				|  |  |    ctx.fillStyle = gradient
 | 
	
		
			
				|  |  |    ctx.fillRect(0, 0, width, height)
 | 
	
		
			
				|  |  |    
 | 
	
		
			
				|  |  | -  // 添加刻度
 | 
	
		
			
				|  |  | +  // 设置更清晰的文字样式
 | 
	
		
			
				|  |  |    ctx.strokeStyle = '#000'
 | 
	
		
			
				|  |  | -  ctx.lineWidth = 1
 | 
	
		
			
				|  |  | -  ctx.font = `${config.fontSize}px ${config.font}`
 | 
	
		
			
				|  |  | -  ctx.fillStyle = '#000'
 | 
	
		
			
				|  |  | +  ctx.lineWidth = 2  // 加粗刻度线
 | 
	
		
			
				|  |  | +  ctx.font = `bold ${config.fontSize}px ${config.font}`  // 加粗字体
 | 
	
		
			
				|  |  | +  ctx.fillStyle = '#FFF'  // 改为白色文字更清晰
 | 
	
		
			
				|  |  | +  ctx.textBaseline = 'middle'
 | 
	
		
			
				|  |  |    
 | 
	
		
			
				|  |  |    // 根据朝向绘制刻度
 | 
	
		
			
				|  |  |    if (config.orientation === 'vertical') {
 | 
	
	
		
			
				|  | @@ -233,10 +340,13 @@ const renderColorCard = () => {
 | 
	
		
			
				|  |  |        ctx.lineTo(width, y)
 | 
	
		
			
				|  |  |        ctx.stroke()
 | 
	
		
			
				|  |  |        
 | 
	
		
			
				|  |  | -      // 跳过指定层级的标签
 | 
	
		
			
				|  |  |        if (i % (config.skipLevels + 1) === 0) {
 | 
	
		
			
				|  |  |          const value = (1 - i / height * 1).toFixed(config.precision)
 | 
	
		
			
				|  |  | -        ctx.fillText(value, 5, y + 5)
 | 
	
		
			
				|  |  | +        // 添加文字背景增强可读性
 | 
	
		
			
				|  |  | +        ctx.fillStyle = 'rgba(0,0,0,0.5)'
 | 
	
		
			
				|  |  | +        ctx.fillRect(5, y - 10, 50, 20)
 | 
	
		
			
				|  |  | +        ctx.fillStyle = '#FFF'
 | 
	
		
			
				|  |  | +        ctx.fillText(value, 10, y)
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    } else {
 | 
	
	
		
			
				|  | @@ -249,29 +359,43 @@ const renderColorCard = () => {
 | 
	
		
			
				|  |  |        ctx.lineTo(x, height * 0.3)
 | 
	
		
			
				|  |  |        ctx.stroke()
 | 
	
		
			
				|  |  |        
 | 
	
		
			
				|  |  | -      // 跳过指定层级的标签
 | 
	
		
			
				|  |  |        if (i % (config.skipLevels + 1) === 0) {
 | 
	
		
			
				|  |  |          const value = (i / width * 1).toFixed(config.precision)
 | 
	
		
			
				|  |  | -        ctx.fillText(value, x - 10, height - 5)
 | 
	
		
			
				|  |  | +        // 添加文字背景
 | 
	
		
			
				|  |  | +        ctx.fillStyle = 'rgba(0,0,0,0.5)'
 | 
	
		
			
				|  |  | +        ctx.fillRect(x - 15, height - 25, 30, 20)
 | 
	
		
			
				|  |  | +        ctx.fillStyle = '#FFF'
 | 
	
		
			
				|  |  | +        ctx.fillText(value, x - 10, height - 15)
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    
 | 
	
		
			
				|  |  | -  // 添加标题
 | 
	
		
			
				|  |  | +  // 添加标题(增强样式)
 | 
	
		
			
				|  |  |    if (config.showTitle) {
 | 
	
		
			
				|  |  |      ctx.font = `bold ${config.titleFontSize}px ${config.titleFont}`
 | 
	
		
			
				|  |  |      ctx.textAlign = 'center'
 | 
	
		
			
				|  |  | +    ctx.fillStyle = '#FFF'
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      const title = config.titleSource === 'custom' 
 | 
	
		
			
				|  |  |        ? config.customTitle 
 | 
	
		
			
				|  |  |        : 'Color Scale'
 | 
	
		
			
				|  |  |      
 | 
	
		
			
				|  |  |      if (config.orientation === 'vertical') {
 | 
	
		
			
				|  |  | +      // 添加标题背景
 | 
	
		
			
				|  |  | +      ctx.fillStyle = 'rgba(0,0,0,0.7)'
 | 
	
		
			
				|  |  | +      ctx.fillRect(15, height/2 - 15, 30, 30)
 | 
	
		
			
				|  |  | +      ctx.fillStyle = '#FFF'
 | 
	
		
			
				|  |  | +      
 | 
	
		
			
				|  |  |        ctx.save()
 | 
	
		
			
				|  |  | -      ctx.translate(20, height / 2)
 | 
	
		
			
				|  |  | +      ctx.translate(30, height / 2)
 | 
	
		
			
				|  |  |        ctx.rotate(-Math.PI / 2)
 | 
	
		
			
				|  |  |        ctx.fillText(title, 0, 0)
 | 
	
		
			
				|  |  |        ctx.restore()
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  | +      // 添加标题背景
 | 
	
		
			
				|  |  | +      ctx.fillStyle = 'rgba(0,0,0,0.7)'
 | 
	
		
			
				|  |  | +      ctx.fillRect(width/2 - 50, height - 30, 100, 25)
 | 
	
		
			
				|  |  | +      ctx.fillStyle = '#FFF'
 | 
	
		
			
				|  |  |        ctx.fillText(title, width / 2, height - 15)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
	
		
			
				|  | @@ -567,6 +691,8 @@ const adjustCameraForPlt = (scene, camera, params = {}) => {
 | 
	
		
			
				|  |  |  <style scoped>
 | 
	
		
			
				|  |  |  .three-container {
 | 
	
		
			
				|  |  |    width: 100%;
 | 
	
		
			
				|  |  | +  position: relative;
 | 
	
		
			
				|  |  | +  overflow: hidden;
 | 
	
		
			
				|  |  |    /* height: calc(60vh - 6px);  */
 | 
	
		
			
				|  |  |    border: 1px solid #ccc; /* 可选:添加边框以便查看容器范围 */
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -574,8 +700,24 @@ const adjustCameraForPlt = (scene, camera, params = {}) => {
 | 
	
		
			
				|  |  |    font-size: 12px;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  .color-card-canvas {
 | 
	
		
			
				|  |  | -  pointer-events: none; /* 允许点击穿透 */
 | 
	
		
			
				|  |  | -  border: 1px solid #ccc;
 | 
	
		
			
				|  |  | -  background: white;
 | 
	
		
			
				|  |  | +  min-width: 70px;
 | 
	
		
			
				|  |  | +  min-height: 25px;
 | 
	
		
			
				|  |  | +  position: absolute;
 | 
	
		
			
				|  |  | +  will-change: transform;
 | 
	
		
			
				|  |  | +  user-select: none;
 | 
	
		
			
				|  |  | +  touch-action: none;
 | 
	
		
			
				|  |  | +  cursor: move;
 | 
	
		
			
				|  |  | +  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
 | 
	
		
			
				|  |  | +  transition: transform 0.1s ease-out;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +.color-card-canvas:hover {
 | 
	
		
			
				|  |  | +  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +.color-card-canvas:active {
 | 
	
		
			
				|  |  | +  cursor: grabbing;
 | 
	
		
			
				|  |  | +  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
 | 
	
		
			
				|  |  | +  transition: none;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  </style>
 |