<script setup lang="ts">
|
import { computed, ref, watch } from 'vue'
|
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
import { storeToRefs } from 'pinia'
|
const dvMainStore = dvMainStoreWithOut()
|
const wRuleRef = ref(null)
|
const props = defineProps({
|
tickLabelFormatter: {
|
type: Function,
|
default: value => value.toString() // 刻度标签格式化函数,默认直接转为字符串
|
},
|
size: {
|
type: Number,
|
default: 300 // 尺子方向
|
},
|
direction: {
|
type: String,
|
default: 'horizontal' // 尺子方向
|
}
|
})
|
|
const labelInterval = 5
|
const emits = defineEmits(['update:tickSize'])
|
|
const { canvasStyleData, curComponent, componentData } = storeToRefs(dvMainStore)
|
|
const rulerSize = computed(() =>
|
props.direction === 'horizontal' ? canvasStyleData.value.width : canvasStyleData.value.height
|
)
|
|
// 计算复合画布内部组件偏移量
|
const parentStyle = computed(() => {
|
const style = { left: 0, top: 0 }
|
if (curComponent.value && curComponent.value.canvasId !== 'canvas-main') {
|
componentData.value.forEach(item => {
|
if (curComponent.value.canvasId.indexOf(item.id) > -1) {
|
style.left = item.style.left
|
style.top = item.style.top
|
}
|
})
|
// tab页头部偏移量
|
if (curComponent.value.canvasId.indexOf('Group') === -1) {
|
style.top = style.top + 56
|
}
|
}
|
return style
|
})
|
|
const curComponentShadow = computed(() => {
|
if (curComponent.value) {
|
return {
|
left:
|
(props.direction === 'horizontal'
|
? curComponent.value.style.left + parentStyle.value.left
|
: curComponent.value.style.top + parentStyle.value.top) + 'px',
|
width:
|
(props.direction === 'horizontal'
|
? curComponent.value.style.width
|
: curComponent.value.style.height) + 'px'
|
}
|
} else {
|
return {}
|
}
|
})
|
|
const ticks = computed(() => {
|
const result = []
|
let currentValue = 0
|
while (currentValue <= rulerSize.value) {
|
const isLong = currentValue % (labelInterval * tickSize.value) === 0
|
const label = isLong ? props.tickLabelFormatter(currentValue) : ''
|
result.push({ position: (currentValue * canvasStyleData.value.scale) / 100, label, isLong })
|
currentValue += tickSize.value
|
}
|
return result
|
})
|
|
const wStyle = computed(() => {
|
return {
|
width: rulerSize.value * 1.5 + 'px'
|
}
|
})
|
|
const radio = computed(() => rulerSize.value / canvasStyleData.value.width)
|
const tickSize = computed(
|
() =>
|
10 *
|
Math.max(
|
Math.floor((200000 * radio.value) / (rulerSize.value * canvasStyleData.value.scale)),
|
1
|
)
|
)
|
|
const scaleWidth = computed(() => (rulerSize.value * canvasStyleData.value.scale) / 100)
|
|
const rulerScroll = e => {
|
const left = props.direction === 'vertical' ? e.scrollTop : e.scrollLeft
|
wRuleRef.value.scrollTo(left, 0)
|
}
|
|
const outerStyle = computed(() => {
|
return {
|
width: props.direction === 'vertical' ? props.size - 30 + 'px' : '100%'
|
}
|
})
|
|
const curShadowShow = computed(() => curComponent.value && curComponent.value.category !== 'hidden')
|
|
const tickSizeScale = computed(() => (tickSize.value * canvasStyleData.value.scale) / 100)
|
|
watch(
|
() => tickSizeScale.value,
|
() => {
|
emits('update:tickSize', tickSizeScale.value)
|
},
|
{ immediate: true }
|
)
|
|
defineExpose({
|
rulerScroll
|
})
|
</script>
|
|
<template>
|
<div
|
class="ruler-outer"
|
:style="outerStyle"
|
:class="{ 'ruler-vertical': direction === 'vertical' }"
|
ref="wRuleRef"
|
>
|
<!--覆盖着尺子上方防止鼠标移到尺子位置滑动-->
|
<div class="ruler-shadow" :style="outerStyle"></div>
|
<div :style="wStyle" class="ruler-outer-scroll">
|
<div class="ruler" :style="{ width: `${scaleWidth}px` }">
|
<div v-if="curShadowShow" :style="curComponentShadow" class="cur-shadow"></div>
|
<div class="ruler-line" :style="{ width: `${scaleWidth}px` }"></div>
|
<div
|
v-for="(tick, index) in ticks"
|
:key="index"
|
class="ruler-tick"
|
:class="{ 'long-tick': tick.isLong }"
|
:style="{ left: `${tick.position}px` }"
|
>
|
<span v-if="tick.isLong" class="tick-label">{{ tick.label }}</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<style scoped lang="less">
|
.ruler-vertical {
|
position: absolute;
|
left: 30px;
|
top: 30px;
|
transform-origin: top left;
|
transform: rotate(90deg);
|
overflow-y: auto;
|
overflow-x: hidden;
|
z-index: 2;
|
.ruler {
|
.ruler-line {
|
top: 0;
|
}
|
.ruler-tick {
|
top: 0;
|
.tick-label {
|
transform: rotate(180deg);
|
}
|
}
|
}
|
}
|
|
.ruler-shadow {
|
position: absolute;
|
height: 30px;
|
z-index: 10;
|
overflow: hidden;
|
}
|
|
.ruler-outer {
|
overflow-x: auto;
|
background-color: #2c2c2c;
|
&::-webkit-scrollbar {
|
width: 0 !important;
|
height: 0 !important;
|
}
|
}
|
|
.ruler-outer-scroll {
|
min-width: 1600px;
|
display: flex;
|
justify-content: center;
|
}
|
.ruler {
|
position: relative;
|
height: 30px;
|
display: flex;
|
align-items: center;
|
background-color: #2c2c2c;
|
}
|
|
.ruler-line {
|
position: absolute;
|
bottom: 0;
|
height: 1px;
|
background-color: #ac2a2a;
|
}
|
|
.ruler-tick {
|
position: absolute;
|
bottom: 1px;
|
height: 3px;
|
width: 1px;
|
background-color: #e38a8a;
|
}
|
|
.long-tick {
|
width: 1px;
|
height: 15px;
|
}
|
|
.tick-label {
|
position: absolute;
|
bottom: 2px;
|
font-size: 8px;
|
left: 50%;
|
transform: translateX(2%);
|
white-space: nowrap;
|
}
|
|
.cur-shadow {
|
background: rgba(10, 123, 224, 0.3);
|
height: 30px;
|
position: absolute;
|
}
|
</style>
|