<template>
|
<div class="zoom-bar">
|
<div class="partial-wayline-zoom-scale">
|
<div id="zoom-scale" class="zoom-scale" @click="handleZoneClick">
|
<!-- 当前刻度指示器 -->
|
<div
|
class="current-scale"
|
:class="{ 'scale-z-index': isDragging }"
|
:style="{ bottom: currentScale + '%' }"
|
></div>
|
<div class="current-scale drag" :style="{ bottom: currentScale + '%' }" @mousedown="startDrag"></div>
|
|
<!-- 刻度点 -->
|
<div
|
v-for="(point, index) in scalePoints"
|
:key="index"
|
class="point-container"
|
:class="{ optical: point.optical }"
|
:style="{ bottom: point.position + '%' }"
|
>
|
<span class="point"></span>
|
<span class="label">{{ point.label }}</span>
|
</div>
|
|
<!-- 刻度区域 -->
|
<div class="scale-container" @click.stop="handleZoneClick">
|
<div
|
v-for="(area, index) in scaleAreasWithWarning"
|
:key="index"
|
class="scale-area"
|
:class="{ optical: area.optical, warning: area.warning }"
|
:style="{ height: area.height + '%' }"
|
>
|
<div class="scale" v-for="n in area.scales" :key="n"></div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
const valueData = defineModel()
|
|
const currentScale = ref(0)
|
const isDragging = ref(false)
|
const startY = ref(0)
|
const startScale = ref(0)
|
const scalePoints = ref([
|
{ position: 0, label: '2X', optical: false },
|
{ position: 11.1111, label: '5X', optical: true },
|
{ position: 44.4444, label: '10X', optical: true },
|
{ position: 77.7778, label: '20X', optical: true },
|
{ position: 100, label: '200X', optical: false },
|
])
|
const scaleAreas = ref([
|
{ height: 11.1111, scales: 3, optical: false },
|
{ height: 33.3333, scales: 5, optical: true },
|
{ height: 33.3333, scales: 5, optical: true },
|
{ height: 22.2222, scales: 9, optical: false },
|
])
|
|
const isInWarningZone = computed(() => currentScale.value < 11.1111 || currentScale.value > 77.7778)
|
const scaleAreasWithWarning = computed(() => {
|
// 计算每个区域的起始和结束位置
|
let currentPosition = 0
|
// 计算当前刻度是否在首尾区域范围内
|
const isInDangerZone = isInWarningZone.value
|
return scaleAreas.value.map((area, index) => {
|
currentPosition = currentPosition + area.height
|
// 检查是否是第一个或最后一个区域
|
const isFirstArea = index === scaleAreas.value.length - 1
|
const isLastArea = index === 0
|
return {
|
...area,
|
warning: (isFirstArea || isLastArea) && isInDangerZone,
|
}
|
})
|
})
|
|
const currentXValue = computed(() => {
|
// 根据当前百分比计算实际的X值
|
if (currentScale.value <= 11.1111) {
|
// 2X-5X 区间
|
return Number(2 + (currentScale.value / 11.1111) * 3).toFixed(1)
|
} else if (currentScale.value <= 44.4444) {
|
// 5X-10X 区间
|
return Number(5 + ((currentScale.value - 11.1111) / 33.3333) * 5).toFixed(1)
|
} else if (currentScale.value <= 77.7778) {
|
// 10X-20X 区间
|
return Number(10 + ((currentScale.value - 44.4444) / 33.3333) * 10).toFixed(1)
|
} else {
|
// 20X-200X 区间
|
return Number(20 + ((currentScale.value - 77.7778) / 22.2222) * 180).toFixed(1)
|
}
|
})
|
|
function startDrag(e) {
|
e.preventDefault()
|
isDragging.value = true
|
startY.value = e.clientY
|
startScale.value = currentScale.value
|
|
// 添加全局事件监听
|
document.addEventListener('mousemove', onDrag)
|
document.addEventListener('mouseup', stopDrag)
|
}
|
|
function onDrag(e) {
|
if (!isDragging.value) return
|
|
e.preventDefault()
|
// 计算移动距离相对于容器高度的百分比
|
const container = document.getElementById('zoom-scale')
|
const containerHeight = container.offsetHeight
|
const deltaY = e.clientY - startY.value
|
const deltaPercent = (deltaY / containerHeight) * 100
|
|
// 更新刻度位置,并限制在0-100之间
|
let newScale = startScale.value - deltaPercent
|
newScale = Math.max(0, Math.min(100, newScale))
|
currentScale.value = newScale
|
}
|
|
function stopDrag() {
|
isDragging.value = false
|
|
// 移除全局事件监听
|
document.removeEventListener('mousemove', onDrag)
|
document.removeEventListener('mouseup', stopDrag)
|
}
|
|
function handleZoneClick(e) {
|
// 获取容器信息
|
const container = document.getElementById('zoom-scale')
|
const rect = container.getBoundingClientRect()
|
|
// 计算点击位置相对于容器底部的百分比
|
const clickY = e.clientY - rect.top
|
const containerHeight = rect.height
|
const percentage = ((containerHeight - clickY) / containerHeight) * 100
|
|
// 限制在0-100之间
|
currentScale.value = Math.max(0, Math.min(100, percentage))
|
}
|
|
function calculateScaleFromValue(value) {
|
const numValue = Number(value)
|
if (numValue <= 5) {
|
// 2X-5X 区间
|
return ((numValue - 2) / 3) * 11.1111
|
} else if (numValue <= 10) {
|
// 5X-10X 区间
|
return 11.1111 + ((numValue - 5) / 5) * 33.3333
|
} else if (numValue <= 20) {
|
// 10X-20X 区间
|
return 44.4444 + ((numValue - 10) / 10) * 33.3333
|
} else {
|
// 20X-200X 区间
|
return 77.7778 + ((numValue - 20) / 180) * 22.2222
|
}
|
}
|
|
watch(valueData, newValue => {
|
if (!isDragging.value) {
|
currentScale.value = calculateScaleFromValue(newValue)
|
}
|
})
|
|
const emit = defineEmits(['input', 'warning-change'])
|
|
watch(
|
() => currentXValue.value,
|
newValue => {
|
valueData.value = newValue
|
emit('input', newValue)
|
},
|
{ immediate: true }
|
)
|
|
watch(
|
() => isInWarningZone.value,
|
newValue => {
|
emit('warning-change', newValue)
|
},
|
{ immediate: true }
|
)
|
</script>
|
|
<style lang="scss">
|
.zoom-bar {
|
background: linear-gradient(-90deg, rgba(0, 0, 0, 0.5) 36%, transparent 99%);
|
height: 300px;
|
padding: 4px 28px 4px 30px;
|
pointer-events: auto;
|
position: absolute;
|
top: 50%;
|
right: 0;
|
transform: translateY(-50%);
|
}
|
|
.partial-wayline-zoom-scale {
|
-webkit-user-select: none;
|
user-select: none;
|
flex-direction: column;
|
height: 100%;
|
display: flex;
|
position: relative;
|
|
.zoom-scale {
|
cursor: pointer;
|
flex-direction: column;
|
flex: 1;
|
margin: 8px;
|
display: flex;
|
position: relative;
|
|
.scale-container {
|
flex-direction: column-reverse;
|
width: 4px;
|
height: 100%;
|
margin-left: 1px;
|
display: flex;
|
position: relative;
|
|
.scale-area {
|
flex-direction: column-reverse;
|
display: flex;
|
|
&.optical {
|
background: rgba(216, 216, 216, 0.3);
|
}
|
|
&.warning {
|
background: rgba(255, 153, 0, 0.5);
|
}
|
|
.scale {
|
border-top: 1px solid rgba(255, 255, 255, 0.5);
|
flex: 1;
|
width: 4px;
|
|
&:last-child {
|
border-top: none;
|
}
|
}
|
}
|
}
|
}
|
|
.current-scale {
|
background-color: #fff;
|
border-radius: 50%;
|
justify-content: center;
|
align-items: center;
|
width: 16px;
|
height: 16px;
|
margin-bottom: -8px;
|
margin-left: -5px;
|
display: flex;
|
position: absolute;
|
z-index: 10;
|
pointer-events: none;
|
|
&.drag {
|
z-index: 20;
|
background-color: transparent;
|
cursor: grab;
|
width: 16px;
|
height: 16px;
|
position: absolute;
|
padding: 10px;
|
margin: -10px;
|
box-sizing: content-box;
|
pointer-events: auto;
|
|
&:active {
|
cursor: grabbing;
|
}
|
}
|
|
&.scale-z-index {
|
z-index: 25;
|
}
|
}
|
|
.point-container {
|
align-items: flex-end;
|
display: flex;
|
position: absolute;
|
pointer-events: none;
|
|
.point {
|
z-index: 5;
|
background-color: #fff;
|
border-radius: 50%;
|
width: 6px;
|
height: 6px;
|
margin-bottom: -3px;
|
position: relative;
|
}
|
|
.label {
|
color: #fff;
|
font-size: 10px;
|
line-height: 18px;
|
position: absolute;
|
top: -6px;
|
left: 10px;
|
transform: scale(0.8);
|
}
|
|
&.optical .point:after {
|
content: '';
|
background: rgba(216, 216, 216, 0.5);
|
border-radius: 50%;
|
width: 12px;
|
height: 12px;
|
position: absolute;
|
transform: translate(-3px, -3px);
|
}
|
|
&.warning .point:after {
|
content: '';
|
background: #f90;
|
border-radius: 50%;
|
width: 6px;
|
height: 6px;
|
position: absolute;
|
}
|
}
|
|
.input-number-wrapper {
|
width: 70px;
|
height: 28px;
|
margin: 8px 2px;
|
|
input {
|
height: 28px;
|
}
|
}
|
}
|
|
/* 刻度值显示框样式 */
|
.scale-value-box {
|
position: absolute;
|
top: 50%;
|
right: 90px;
|
transform: translateY(-50%);
|
background: rgba(0, 0, 0, 0.5);
|
padding: 8px 12px;
|
border-radius: 4px;
|
color: #fff;
|
font-size: 14px;
|
line-height: 20px;
|
min-width: 60px;
|
text-align: center;
|
|
&.warning {
|
color: #ff9900;
|
}
|
}
|
|
.scale-value {
|
font-family: monospace;
|
font-weight: 500;
|
}
|
</style>
|