feat: remake machanicalbutton

This commit is contained in:
alivender 2025-04-25 13:20:04 +08:00
parent 5ea541ef4b
commit 4465091db3
1 changed files with 68 additions and 69 deletions

View File

@ -1,7 +1,9 @@
<template> <template>
<div class="relative" :style="{width: `${props.width}px`, height: `${props.height}px`}">
<!-- 简化的SVG按钮 -->
<svg xmlns="http://www.w3.org/2000/svg" :width="props.width" :height="props.height" viewBox="0 0 1600 1600"> <svg xmlns="http://www.w3.org/2000/svg" :width="props.width" :height="props.height" viewBox="0 0 1600 1600">
<def> <defs>
<filter id="shadow" x="-50%" y="-50%" width="200%" height="200%"> <filter id="btn-shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="20" result="blur" /> <feGaussianBlur in="SourceAlpha" stdDeviation="20" result="blur" />
<feColorMatrix result="bluralpha" type="matrix" :values="colorMatrix" /> <feColorMatrix result="bluralpha" type="matrix" :values="colorMatrix" />
<feOffset in="bluralpha" dx="20" dy="20" result="offsetBlur" /> <feOffset in="bluralpha" dx="20" dy="20" result="offsetBlur" />
@ -10,16 +12,6 @@
<feMergeNode in="SourceGraphic" /> <feMergeNode in="SourceGraphic" />
</feMerge> </feMerge>
</filter> </filter>
</def>
<g>
<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" />
</g>
<g>
<linearGradient id="normal" gradientTransform="rotate(45 0 0)"> <linearGradient id="normal" gradientTransform="rotate(45 0 0)">
<stop stop-color="#4b4b4b" offset="0" /> <stop stop-color="#4b4b4b" offset="0" />
<stop stop-color="#171717" offset="1" /> <stop stop-color="#171717" offset="1" />
@ -28,10 +20,33 @@
<stop stop-color="#171717" offset="0" /> <stop stop-color="#171717" offset="0" />
<stop stop-color="#4b4b4b" offset="1" /> <stop stop-color="#4b4b4b" offset="1" />
</linearGradient> </linearGradient>
<circle class="shadow" r="220" cx="800" cy="800" fill="black" /> </defs>
<circle class="light" @mousedown="toggleButtonState(true)" @mouseup="toggleButtonState(false)"
@contextmenu.prevent="openContextMenu($event)" :r="btnHeight" cx="800" cy="800" <!-- 按钮底座 -->
fill-opacity="0.9" style="pointer-events: auto;"/> <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)"
@contextmenu.prevent="openContextMenu($event)"
style="pointer-events: auto; transition: all 20ms ease-in-out;"
/>
<!-- 按键文字 -->
<text <text
v-if="bindKey" v-if="bindKey"
x="800" x="800"
@ -40,20 +55,23 @@
text-anchor="middle" text-anchor="middle"
dominant-baseline="central" dominant-baseline="central"
fill="#ccc" fill="#ccc"
style="font-family: Arial; filter: url(#shadow); user-select: none; pointer-events: none; mix-blend-mode: overlay;" style="font-family: Arial; filter: url(#btn-shadow); user-select: none; pointer-events: none; mix-blend-mode: overlay;"
> >
{{ bindKey.toUpperCase() }} {{ bindKey.toUpperCase() }}
</text> </text>
</g>
</svg> </svg>
<!-- 使用DaisyUI的卡片组件实现上下文菜单 -->
<div v-if="showContextMenu" <div v-if="showContextMenu"
class="card card-compact fixed z-50 shadow-lg bg-base-100 border border-base-300"
:style="{ top: contextMenuY + 'px', left: contextMenuX + 'px' }" :style="{ top: contextMenuY + 'px', left: contextMenuX + 'px' }"
@click.stop @click.stop>
class="fixed z-50 border border-base-300 bg-base-100 rounded-md shadow-lg"> <div class="card-body p-0">
<div class="px-4 py-2 cursor-pointer whitespace-nowrap text-base-content hover:bg-base-200" <button class="btn btn-ghost justify-start normal-case w-full h-full" @click="startBinding">
@click="startBinding">
<span v-if="isBinding">请输入</span> <span v-if="isBinding">请输入</span>
<span v-else>绑定按键: {{ bindKey ? bindKey.toUpperCase() : '未绑定' }}</span> <span v-else>绑定按键: {{ bindKey ? bindKey.toUpperCase() : '未绑定' }}</span>
</button>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -65,16 +83,20 @@ interface Props {
width?: string | number width?: string | number
height?: string | number height?: string | number
} }
const bindKey = ref('');
let isKeyPressed = false;
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
width: 160, width: 160,
height: 160, height: 160,
}) })
const btnHeight = ref(200) const bindKey = ref('');
let isKeyPressed = false;
const colorMatrix = ref("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0") 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");
const isBinding = ref(false);
const showContextMenu = ref(false);
const contextMenuX = ref(0);
const contextMenuY = ref(0);
function toggleButtonState(isPressed: boolean) { function toggleButtonState(isPressed: boolean) {
btnHeight.value = isPressed ? 210 : 200; btnHeight.value = isPressed ? 210 : 200;
@ -83,11 +105,6 @@ function toggleButtonState(isPressed: boolean) {
: "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"; : "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0";
} }
const isBinding = ref(false);
const showContextMenu = ref(false);
const contextMenuX = ref(0);
const contextMenuY = ref(0);
function openContextMenu(e: MouseEvent) { function openContextMenu(e: MouseEvent) {
contextMenuX.value = e.clientX; contextMenuX.value = e.clientX;
contextMenuY.value = e.clientY; contextMenuY.value = e.clientY;
@ -135,21 +152,3 @@ onUnmounted(() => {
window.removeEventListener('click', closeContextMenu); window.removeEventListener('click', closeContextMenu);
}); });
</script> </script>
<style scoped lang="postcss">
circle {
transition: all 20ms ease-in-out;
}
.shadow {
filter: url(#shadow);
}
.light:active {
fill: url(#pressed);
}
.light {
fill: url(#normal);
}
</style>