FPGA_WebLab/src/components/equipments/MechanicalButton.vue

307 lines
8.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="button-container" :style="{
width: width + 'px',
height: height + 'px',
position: 'relative',
}">
<svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" viewBox="400 400 800 800"
class="mechanical-button">
<!-- defs 和按钮底座保持不变 -->
<defs>
<filter id="btn-shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="20" result="blur" />
<feColorMatrix result="bluralpha" type="matrix" :values="colorMatrix" />
<feOffset in="bluralpha" dx="20" dy="20" result="offsetBlur" />
<feMerge>
<feMergeNode in="offsetBlur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<linearGradient id="normal" gradientTransform="rotate(45 0 0)">
<stop stop-color="#4b4b4b" offset="0" />
<stop stop-color="#171717" offset="1" />
</linearGradient>
<linearGradient id="pressed" gradientTransform="rotate(45 0 0)">
<stop stop-color="#171717" offset="0" />
<stop stop-color="#4b4b4b" offset="1" />
</linearGradient>
</defs>
<!-- 按钮底座 -->
<rect width="800" height="800" x="400" y="400" fill="#464646" rx="20" />
<rect width="700" height="700" x="450" y="450" fill="#eaeaea" rx="20" />
<!-- 装饰螺丝 -->
<circle r="20" cx="1075" cy="1075" fill="#171717" />
<circle r="20" cx="1075" cy="525" fill="#171717" />
<circle r="20" cx="525" cy="525" fill="#171717" />
<circle r="20" cx="525" cy="1075" fill="#171717" />
<!-- 按钮主体 -->
<circle r="220" cx="800" cy="800" fill="black" filter="url(#btn-shadow)" />
<circle :r="btnHeight" cx="800" cy="800" :fill="isKeyPressed ? 'url(#pressed)' : 'url(#normal)'"
fill-opacity="0.9" @mousedown="toggleButtonState(true)" @mouseup="toggleButtonState(false)"
@mouseleave="toggleButtonState(false)" style="
pointer-events: auto;
transition: all 20ms ease-in-out;
cursor: pointer;
" />
<!-- 按键文字 - 仅显示绑定的按键 -->
<text v-if="bindKeyDisplay" x="800" y="800" font-size="310" text-anchor="middle" dominant-baseline="central"
fill="#ccc" style="
font-family: Arial;
filter: url(#btn-shadow);
user-select: none;
pointer-events: none;
mix-blend-mode: overlay;
">
{{ bindKeyDisplay }}
</text>
</svg>
<!-- 渲染自定义引脚数组 -->
<div v-for="pin in props.pins" :key="pin.pinId" :style="{
position: 'absolute',
left: `${pin.x * props.size}px`,
top: `${pin.y * props.size}px`,
transform: 'translate(-50%, -50%)',
zIndex: 3,
pointerEvents: 'auto',
}" :data-pin-wrapper="`${pin.pinId}`" :data-pin-x="`${pin.x * props.size}`"
:data-pin-y="`${pin.y * props.size}`">
<Pin :ref="(el) => {
if (el) pinRefs[pin.pinId] = el;
}
" direction="output" type="digital" :label="pin.pinId" :constraint="pin.constraint" :pinId="pin.pinId"
:size="0.8" :componentId="props.componentId" @value-change="handlePinValueChange" @pin-click="handlePinClick" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from "vue";
import Pin from "./Pin.vue";
import { useEquipments } from "@/stores/equipments";
import { useDialogStore } from "@/stores/dialog";
import { useConstraintsStore } from "../../stores/constraints";
import { isNull, isUndefined } from "mathjs";
import z from "zod";
import { toNumber } from "lodash";
// 按钮特有属性
export interface ButtonProps {
size: number;
componentId?: string;
pins?: {
pinId: string;
constraint: string;
x: number;
y: number;
}[];
bindKey?: string;
bindMatrixKey?: string;
}
const props = defineProps<ButtonProps>();
// Global Stores
const constrainsts = useConstraintsStore();
const dialog = useDialogStore();
const eqps = useEquipments();
// 存储多个Pin引用
const pinRefs = ref<Record<string, any>>({});
// 计算实际宽高
const width = computed(() => 160 * props.size);
const height = computed(() => 160 * props.size);
// 显示绑定的按键
const bindKeyDisplay = computed(() =>
props.bindKey ? props.bindKey.toUpperCase() : "",
);
// 定义组件发出的事件
const emit = defineEmits([
"update:bindKey",
"update:constraint",
"press",
"release",
"click",
"value-change",
"pin-click",
]);
// 内部状态
const isKeyPressed = ref(false);
const btnHeight = ref(200);
const colorMatrix = ref("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0");
// 处理Pin值变化
function handlePinValueChange(value: any) {
emit("value-change", value);
}
// 处理Pin点击事件
function handlePinClick(info: any) {
emit("pin-click", info);
}
// --- 按键状态逻辑 ---
function toggleButtonState(isPressed: boolean) {
isKeyPressed.value = isPressed;
btnHeight.value = isPressed ? 180 : 200;
// 矩阵键盘
if (eqps.enableMatrixKey) {
const ret = eqps.setMatrixKey(props.bindMatrixKey, isPressed);
if (ret) eqps.matrixKeypadSetKeyStates(eqps.matrixKeyStates);
else
dialog.error(
`绑定的矩阵键盘值只能是0 ~ 15而不是: ${props.bindMatrixKey}`,
);
}
// 发出事件通知父组件
if (isPressed) {
emit("press");
if (props.pins) {
// 如果有约束,通知约束状态变化为高电平
// 对所有引脚应用相同的状态
props.pins.forEach((pin) => {
if (pin.constraint) {
constrainsts.notifyConstraintChange(pin.constraint, "high");
}
});
}
} else {
emit("release");
emit("click");
// 如果有约束,通知约束状态变化为低电平
if (props.pins) {
props.pins.forEach((pin) => {
if (pin.constraint) {
constrainsts.notifyConstraintChange(pin.constraint, "low");
}
});
}
}
}
// 处理键盘事件
function handleKeyDown(event: KeyboardEvent) {
if (event.key === props.bindKey) {
toggleButtonState(true);
setTimeout(() => toggleButtonState(false), 150);
}
}
// --- 生命周期钩子 ---
onMounted(() => {
document.addEventListener("keydown", handleKeyDown);
});
onUnmounted(() => {
document.removeEventListener("keydown", handleKeyDown);
});
// 向外暴露方法
defineExpose({
toggleButtonState,
getInfo: () => ({
bindKey: props.bindKey,
componentId: props.componentId,
pins: props.pins,
}),
// 获取引脚位置
getPinPosition: (pinId: string) => {
console.debug(`[MechanicalButton] 调用getPinPosition寻找pinId: ${pinId}`);
console.debug(
`[MechanicalButton] 组件ID: ${props.componentId}, 当前尺寸: ${props.size}, 组件宽高: ${width.value}x${height.value}`,
);
console.debug(`[MechanicalButton] 当前存在的pins:`, props.pins);
// 如果是自定义的引脚ID
if (props.pins && props.pins.length > 0) {
const customPin = props.pins.find((p) => p.pinId === pinId);
if (customPin) {
console.debug(
`[MechanicalButton] 找到自定义引脚: ${pinId},配置位置:`,
{
x: customPin.x,
y: customPin.y,
},
);
// 考虑组件尺寸的缩放
// 这里的x和y是针对标准尺寸size=1的坐标需要根据实际size调整
const scaledX = customPin.x * props.size;
const scaledY = customPin.y * props.size;
console.debug(`[MechanicalButton] 返回缩放后的坐标:`, {
x: scaledX,
y: scaledY,
});
return {
x: scaledX,
y: scaledY,
};
} else {
console.debug(`[MechanicalButton] 未找到pinId: ${pinId}的引脚配置`);
}
} else {
console.debug(`[MechanicalButton] 没有配置任何引脚`);
}
console.debug(`[MechanicalButton] 返回null未找到引脚`);
return null;
},
});
</script>
<script lang="ts">
// 添加一个静态方法来获取默认props
export function getDefaultProps() {
return {
size: 1,
pins: [
{
pinId: "BTN",
constraint: "",
x: 80,
y: 140,
},
],
bindKey: "",
bindMatrixKey: "",
};
}
</script>
<style scoped>
.button-container {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.mechanical-button {
display: block;
padding: 0;
margin: 0;
line-height: 0;
font-size: 0;
box-sizing: content-box;
overflow: visible;
}
.pin-wrapper {
width: 100%;
display: flex;
justify-content: center;
}
</style>