319 lines
8.1 KiB
Vue
319 lines
8.1 KiB
Vue
<template>
|
||
<div
|
||
class="inline-block select-none"
|
||
:style="{
|
||
width: width + 'px',
|
||
height: height + 'px',
|
||
position: 'relative',
|
||
}"
|
||
>
|
||
<svg
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
:width="width"
|
||
:height="height"
|
||
viewBox="0 0 100 100"
|
||
class="ec11-encoder"
|
||
>
|
||
<defs>
|
||
<!-- 发光效果滤镜 -->
|
||
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
||
<feFlood
|
||
result="flood"
|
||
flood-color="#00ff88"
|
||
flood-opacity="1"
|
||
></feFlood>
|
||
<feComposite
|
||
in="flood"
|
||
result="mask"
|
||
in2="SourceGraphic"
|
||
operator="in"
|
||
></feComposite>
|
||
<feMorphology
|
||
in="mask"
|
||
result="dilated"
|
||
operator="dilate"
|
||
radius="1"
|
||
></feMorphology>
|
||
<feGaussianBlur in="dilated" stdDeviation="2" result="blur1" />
|
||
<feGaussianBlur in="dilated" stdDeviation="4" result="blur2" />
|
||
<feGaussianBlur in="dilated" stdDeviation="8" result="blur3" />
|
||
<feMerge>
|
||
<feMergeNode in="blur3" />
|
||
<feMergeNode in="blur2" />
|
||
<feMergeNode in="blur1" />
|
||
<feMergeNode in="SourceGraphic" />
|
||
</feMerge>
|
||
</filter>
|
||
|
||
<!-- 编码器主体渐变 -->
|
||
<radialGradient id="encoderGradient" cx="50%" cy="30%">
|
||
<stop offset="0%" stop-color="#666666" />
|
||
<stop offset="70%" stop-color="#333333" />
|
||
<stop offset="100%" stop-color="#1a1a1a" />
|
||
</radialGradient>
|
||
|
||
<!-- 旋钮渐变 -->
|
||
<radialGradient id="knobGradient" cx="30%" cy="30%">
|
||
<stop offset="0%" stop-color="#555555" />
|
||
<stop offset="70%" stop-color="#222222" />
|
||
<stop offset="100%" stop-color="#111111" />
|
||
</radialGradient>
|
||
|
||
<!-- 按下状态渐变 -->
|
||
<radialGradient id="knobPressedGradient" cx="50%" cy="50%">
|
||
<stop offset="0%" stop-color="#333333" />
|
||
<stop offset="70%" stop-color="#555555" />
|
||
<stop offset="100%" stop-color="#888888" />
|
||
</radialGradient>
|
||
</defs>
|
||
|
||
<!-- 编码器底座 -->
|
||
<rect
|
||
x="10"
|
||
y="30"
|
||
width="80"
|
||
height="60"
|
||
rx="8"
|
||
ry="8"
|
||
fill="#2a2a2a"
|
||
stroke="#444444"
|
||
stroke-width="1"
|
||
/>
|
||
|
||
<!-- 编码器主体外壳 -->
|
||
<circle
|
||
cx="50"
|
||
cy="60"
|
||
r="32"
|
||
fill="url(#encoderGradient)"
|
||
stroke="#555555"
|
||
stroke-width="1"
|
||
/>
|
||
|
||
<!-- 编码器接线端子 -->
|
||
<rect x="5" y="75" width="4" height="8" fill="#c9c9c9" rx="1" />
|
||
<rect x="15" y="85" width="4" height="8" fill="#c9c9c9" rx="1" />
|
||
<rect x="25" y="85" width="4" height="8" fill="#c9c9c9" rx="1" />
|
||
<rect x="81" y="85" width="4" height="8" fill="#c9c9c9" rx="1" />
|
||
<rect x="91" y="75" width="4" height="8" fill="#c9c9c9" rx="1" />
|
||
|
||
<!-- 旋钮 -->
|
||
<circle
|
||
cx="50"
|
||
cy="60"
|
||
r="22"
|
||
:fill="isPressed ? 'url(#knobPressedGradient)' : 'url(#knobGradient)'"
|
||
stroke="#666666"
|
||
stroke-width="1"
|
||
:transform="`rotate(${rotationStep * 7.5} 50 60)`"
|
||
class="interactive"
|
||
@mousedown="handleMouseDown"
|
||
@mouseup="handlePress(false)"
|
||
@mouseleave="handlePress(false)"
|
||
/>
|
||
|
||
<!-- 旋钮指示器 -->
|
||
<line
|
||
x1="50"
|
||
y1="42"
|
||
x2="50"
|
||
y2="48"
|
||
stroke="#ffffff"
|
||
stroke-width="2"
|
||
stroke-linecap="round"
|
||
:transform="`rotate(${rotationStep * 15} 50 60)`"
|
||
/>
|
||
|
||
<!-- 旋钮上的纹理刻度 -->
|
||
<g :transform="`rotate(${rotationStep * 15} 50 60)`">
|
||
<circle
|
||
cx="50"
|
||
cy="60"
|
||
r="18"
|
||
fill="none"
|
||
stroke="#777777"
|
||
stroke-width="0.5"
|
||
/>
|
||
<!-- 刻度线 -->
|
||
<g v-for="i in 16" :key="i">
|
||
<line
|
||
:x1="50 + 16 * Math.cos(((i - 1) * Math.PI) / 8)"
|
||
:y1="60 + 16 * Math.sin(((i - 1) * Math.PI) / 8)"
|
||
:x2="50 + 18 * Math.cos(((i - 1) * Math.PI) / 8)"
|
||
:y2="60 + 18 * Math.sin(((i - 1) * Math.PI) / 8)"
|
||
stroke="#999999"
|
||
stroke-width="0.5"
|
||
/>
|
||
</g>
|
||
</g>
|
||
|
||
<!-- 编码器编号标签 -->
|
||
<text
|
||
x="50"
|
||
y="15"
|
||
text-anchor="middle"
|
||
font-family="Arial"
|
||
font-size="10"
|
||
fill="#cccccc"
|
||
font-weight="bold"
|
||
>
|
||
EC11-{{ encoderNumber }}
|
||
</text>
|
||
|
||
<!-- 状态指示器 -->
|
||
<circle
|
||
cx="85"
|
||
cy="20"
|
||
r="3"
|
||
:fill="isPressed ? '#ff4444' : '#444444'"
|
||
:filter="isPressed ? 'url(#glow)' : ''"
|
||
stroke="#666666"
|
||
stroke-width="0.5"
|
||
/>
|
||
</svg>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { useRotaryEncoder } from "@/stores/Peripherals/RotaryEncoder";
|
||
import {
|
||
RotaryEncoderDirection,
|
||
RotaryEncoderPressStatus,
|
||
} from "@/utils/signalR/Peripherals.RotaryEncoderClient";
|
||
import { watch } from "vue";
|
||
import { watchEffect } from "vue";
|
||
import { ref, computed } from "vue";
|
||
|
||
const rotataryEncoderStore = useRotaryEncoder();
|
||
|
||
interface Props {
|
||
size?: number;
|
||
componentId?: string;
|
||
enableDigitalTwin?: boolean;
|
||
encoderNumber?: number;
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
size: 1,
|
||
enableDigitalTwin: false,
|
||
encoderNumber: 1,
|
||
});
|
||
|
||
// 组件状态
|
||
const isPressed = ref(false);
|
||
const rotationStep = ref(0); // 步进计数,1步=15度
|
||
|
||
// 拖动状态对象,增加 hasRotated 标记
|
||
const drag = ref<{
|
||
active: boolean;
|
||
startX: number;
|
||
hasRotated: boolean;
|
||
} | null>(null);
|
||
|
||
const dragThreshold = 20; // 每20像素触发一次旋转
|
||
|
||
// 计算宽高
|
||
const width = computed(() => 100 * props.size);
|
||
const height = computed(() => 100 * props.size);
|
||
|
||
// 鼠标按下处理
|
||
function handleMouseDown(event: MouseEvent) {
|
||
drag.value = { active: true, startX: event.clientX, hasRotated: false };
|
||
window.addEventListener("mousemove", handleMouseMove);
|
||
window.addEventListener("mouseup", handleMouseUp);
|
||
}
|
||
|
||
// 鼠标移动处理
|
||
function handleMouseMove(event: MouseEvent) {
|
||
if (!drag.value?.active) return;
|
||
const dx = event.clientX - drag.value.startX;
|
||
if (Math.abs(dx) >= dragThreshold) {
|
||
rotationStep.value += dx > 0 ? 1 : -1;
|
||
drag.value.startX = event.clientX;
|
||
drag.value.hasRotated = true;
|
||
}
|
||
}
|
||
|
||
// 鼠标松开处理
|
||
function handleMouseUp() {
|
||
if (drag.value && drag.value.active) {
|
||
// 仅在未发生旋转时才触发按压
|
||
if (!drag.value.hasRotated) {
|
||
isPressed.value = true;
|
||
rotataryEncoderStore.pressOnce(
|
||
props.encoderNumber,
|
||
RotaryEncoderPressStatus.Press,
|
||
);
|
||
setTimeout(() => {
|
||
isPressed.value = false;
|
||
rotataryEncoderStore.pressOnce(
|
||
props.encoderNumber,
|
||
RotaryEncoderPressStatus.Release,
|
||
);
|
||
}, 100);
|
||
}
|
||
}
|
||
drag.value = null;
|
||
window.removeEventListener("mousemove", handleMouseMove);
|
||
window.removeEventListener("mouseup", handleMouseUp);
|
||
}
|
||
|
||
// 按压处理(用于鼠标离开和mouseup)
|
||
function handlePress(pressed: boolean) {
|
||
isPressed.value = pressed;
|
||
}
|
||
|
||
watchEffect(() => {
|
||
if (!props.enableDigitalTwin) return;
|
||
|
||
if (props.componentId)
|
||
rotataryEncoderStore.setEnable(props.enableDigitalTwin);
|
||
});
|
||
|
||
watch(
|
||
() => rotationStep.value,
|
||
(newStep, oldStep) => {
|
||
if (!props.enableDigitalTwin) return;
|
||
|
||
if (newStep > oldStep) {
|
||
rotataryEncoderStore.rotateOnce(
|
||
props.encoderNumber,
|
||
RotaryEncoderDirection.Clockwise,
|
||
);
|
||
} else if (newStep < oldStep) {
|
||
rotataryEncoderStore.rotateOnce(
|
||
props.encoderNumber,
|
||
RotaryEncoderDirection.CounterClockwise,
|
||
);
|
||
}
|
||
},
|
||
);
|
||
</script>
|
||
|
||
<script lang="ts">
|
||
// 添加一个静态方法来获取默认props
|
||
export function getDefaultProps() {
|
||
return {
|
||
size: 1,
|
||
enableDigitalTwin: false,
|
||
encoderNumber: 1,
|
||
};
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="postcss">
|
||
.ec11-container {
|
||
display: inline-block;
|
||
user-select: none;
|
||
}
|
||
|
||
.ec11-encoder {
|
||
display: block;
|
||
overflow: visible;
|
||
}
|
||
|
||
.interactive {
|
||
cursor: pointer;
|
||
}
|
||
</style>
|