feat: remake most of forntend
48
src/components/equipments/DDR.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template> <div class="ddr-component" :style="{ width: width + 'px', height: height + 'px' }">
|
||||
<img
|
||||
src="../equipments/svg/ddr.svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
alt="DDR内存"
|
||||
class="svg-image"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 1
|
||||
});
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => 120 * props.size);
|
||||
const height = computed(() => 80 * props.size);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ddr-component {
|
||||
display: block;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
.svg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none; /* 禁止鼠标交互 */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
</style>
|
||||
48
src/components/equipments/ETH.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template> <div class="eth-component" :style="{ width: width + 'px', height: height + 'px' }">
|
||||
<img
|
||||
src="../equipments/svg/eth.svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
alt="以太网接口"
|
||||
class="svg-image"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 1
|
||||
});
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => 100 * props.size);
|
||||
const height = computed(() => 60 * props.size);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.eth-component {
|
||||
display: block;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
.svg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none; /* 禁止鼠标交互 */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
</style>
|
||||
48
src/components/equipments/HDMI.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template> <div class="hdmi-component" :style="{ width: width + 'px', height: height + 'px' }">
|
||||
<img
|
||||
src="../equipments/svg/hdmi.svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
alt="HDMI接口"
|
||||
class="svg-image"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 1
|
||||
});
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => 100 * props.size);
|
||||
const height = computed(() => 50 * props.size);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hdmi-component {
|
||||
display: block;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
.svg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none; /* 禁止鼠标交互 */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,13 @@
|
||||
<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">
|
||||
<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" />
|
||||
@@ -11,7 +17,7 @@
|
||||
<feMergeNode in="offsetBlur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</filter>
|
||||
<linearGradient id="normal" gradientTransform="rotate(45 0 0)">
|
||||
<stop stop-color="#4b4b4b" offset="0" />
|
||||
<stop stop-color="#171717" offset="1" />
|
||||
@@ -21,35 +27,34 @@
|
||||
<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)'"
|
||||
<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;"
|
||||
@mousedown="toggleButtonState(true)"
|
||||
@mouseup="toggleButtonState(false)"
|
||||
@mouseleave="toggleButtonState(false)"
|
||||
style="pointer-events: auto; transition: all 20ms ease-in-out; cursor: pointer;"
|
||||
/>
|
||||
|
||||
<!-- 按键文字 -->
|
||||
<text
|
||||
v-if="bindKey"
|
||||
x="800"
|
||||
<text
|
||||
v-if="displayText"
|
||||
x="800"
|
||||
y="800"
|
||||
font-size="310"
|
||||
text-anchor="middle"
|
||||
@@ -57,98 +62,169 @@
|
||||
fill="#ccc"
|
||||
style="font-family: Arial; filter: url(#btn-shadow); user-select: none; pointer-events: none; mix-blend-mode: overlay;"
|
||||
>
|
||||
{{ bindKey.toUpperCase() }}
|
||||
{{ displayText }}
|
||||
</text>
|
||||
</svg>
|
||||
|
||||
<!-- 使用DaisyUI的卡片组件实现上下文菜单 -->
|
||||
<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' }"
|
||||
@click.stop>
|
||||
<div class="card-body p-0">
|
||||
<button class="btn btn-ghost justify-start normal-case w-full h-full" @click="startBinding">
|
||||
<span v-if="isBinding">请输入</span>
|
||||
<span v-else>绑定按键: {{ bindKey ? bindKey.toUpperCase() : '未绑定' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 嵌入Pin组件,覆盖在按钮上 -->
|
||||
<div class="pin-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
top: '80%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, 20%)',
|
||||
zIndex: 3,
|
||||
pointerEvents: 'auto'
|
||||
}">
|
||||
<Pin
|
||||
direction="output"
|
||||
type="digital"
|
||||
appearance="None"
|
||||
:label="props.label"
|
||||
:constraint="props.constraint"
|
||||
:size="0.8"
|
||||
@value-change="handlePinValueChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import Pin from './Pin.vue';
|
||||
|
||||
interface Props {
|
||||
width?: string | number
|
||||
height?: string | number
|
||||
// 从Pin组件继承属性
|
||||
interface PinProps {
|
||||
label?: string;
|
||||
constraint?: string;
|
||||
// 这些属性被预设为固定值,但仍然包含在类型中以便完整继承
|
||||
direction?: 'input' | 'output' | 'inout';
|
||||
type?: 'digital' | 'analog';
|
||||
appearance?: 'None' | 'Dip' | 'SMT';
|
||||
}
|
||||
|
||||
// 按钮特有属性
|
||||
interface ButtonProps {
|
||||
size?: number;
|
||||
bindKey?: string;
|
||||
buttonText?: string;
|
||||
}
|
||||
|
||||
// 组合两个接口
|
||||
interface Props extends PinProps, ButtonProps {}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
width: 160,
|
||||
height: 160,
|
||||
})
|
||||
size: 1,
|
||||
bindKey: '',
|
||||
buttonText: '',
|
||||
label: 'BTN',
|
||||
constraint: '',
|
||||
// 这些值会被覆盖,但需要默认值以满足类型要求
|
||||
direction: 'output',
|
||||
type: 'digital',
|
||||
appearance: 'Dip'
|
||||
});
|
||||
|
||||
const bindKey = ref('');
|
||||
let isKeyPressed = false;
|
||||
// 计算实际宽高
|
||||
const width = computed(() => 160 * props.size);
|
||||
const height = computed(() => 160 * props.size);
|
||||
|
||||
// 计算文本显示内容
|
||||
const displayText = computed(() => {
|
||||
if (props.buttonText) return props.buttonText;
|
||||
return props.bindKey ? props.bindKey.toUpperCase() : '';
|
||||
});
|
||||
|
||||
// 定义组件发出的事件
|
||||
const emit = defineEmits([
|
||||
'update:bindKey',
|
||||
'update:label',
|
||||
'update:constraint',
|
||||
'press',
|
||||
'release',
|
||||
'click',
|
||||
'value-change'
|
||||
]);
|
||||
|
||||
// 内部状态
|
||||
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");
|
||||
const isBinding = ref(false);
|
||||
const showContextMenu = ref(false);
|
||||
const contextMenuX = ref(0);
|
||||
const contextMenuY = ref(0);
|
||||
|
||||
// 处理Pin值变化
|
||||
function handlePinValueChange(value: any) {
|
||||
emit('value-change', value);
|
||||
}
|
||||
|
||||
// --- 按键状态逻辑 ---
|
||||
function toggleButtonState(isPressed: boolean) {
|
||||
btnHeight.value = isPressed ? 210 : 200;
|
||||
colorMatrix.value = isPressed
|
||||
? "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.7 0"
|
||||
: "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0";
|
||||
isKeyPressed.value = isPressed;
|
||||
btnHeight.value = isPressed ? 180 : 200;
|
||||
|
||||
// 发出事件通知父组件
|
||||
if (isPressed) {
|
||||
emit('press');
|
||||
} else {
|
||||
emit('release');
|
||||
emit('click');
|
||||
}
|
||||
}
|
||||
|
||||
function openContextMenu(e: MouseEvent) {
|
||||
contextMenuX.value = e.clientX;
|
||||
contextMenuY.value = e.clientY;
|
||||
showContextMenu.value = true;
|
||||
}
|
||||
|
||||
function closeContextMenu() {
|
||||
showContextMenu.value = false;
|
||||
}
|
||||
|
||||
function startBinding() {
|
||||
if (isBinding.value) return;
|
||||
isBinding.value = true;
|
||||
window.addEventListener('keydown', onBindingKeyDown, { once: true });
|
||||
}
|
||||
|
||||
function onBindingKeyDown(e: KeyboardEvent) {
|
||||
bindKey.value = e.key.toLowerCase();
|
||||
isBinding.value = false;
|
||||
}
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
if (e.key.toLowerCase() === bindKey.value && !isKeyPressed) {
|
||||
isKeyPressed = true;
|
||||
// 处理键盘事件
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === props.bindKey) {
|
||||
toggleButtonState(true);
|
||||
setTimeout(() => toggleButtonState(false), 150);
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyUp(e: KeyboardEvent) {
|
||||
if (e.key.toLowerCase() === bindKey.value) {
|
||||
isKeyPressed = false;
|
||||
toggleButtonState(false);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 生命周期钩子 ---
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
window.addEventListener('keyup', handleKeyUp);
|
||||
window.addEventListener('click', closeContextMenu);
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
window.removeEventListener('keyup', handleKeyUp);
|
||||
window.removeEventListener('click', closeContextMenu);
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
|
||||
// 向外暴露方法
|
||||
defineExpose({
|
||||
toggleButtonState,
|
||||
getInfo: () => ({
|
||||
// 按钮特有属性
|
||||
bindKey: props.bindKey,
|
||||
buttonText: props.buttonText,
|
||||
// 继承自Pin的属性
|
||||
label: props.label,
|
||||
constraint: props.constraint,
|
||||
// 固定的Pin属性
|
||||
direction: 'output',
|
||||
type: 'digital',
|
||||
appearance: 'None'
|
||||
})
|
||||
});
|
||||
</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>
|
||||
|
||||
220
src/components/equipments/MotherBoard.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<template> <div class="motherboard-container" :style="{ width: width + 'px', height: height + 'px', position: 'relative' }"> <!-- 主板 SVG -->
|
||||
<img
|
||||
src="../equipments/svg/motherboard.svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
alt="主板"
|
||||
class="svg-image"
|
||||
draggable="false"
|
||||
/>
|
||||
|
||||
<!-- 嵌入各种组件 --> <!-- HDMI -->
|
||||
<div class="component-wrapper hdmi-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
top: `${140 * props.size}px`,
|
||||
left: `${-48 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<HDMI :size="1.5*props.size" />
|
||||
</div>
|
||||
<!-- HDMI -->
|
||||
<div class="component-wrapper hdmi-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
top: `${260 * props.size}px`,
|
||||
left: `${-48 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<HDMI :size="1.5*props.size" />
|
||||
</div> <!-- ETH -->
|
||||
<div class="component-wrapper eth-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
top: `${365 * props.size}px`,
|
||||
left: `${-10 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<ETH :size="1.5*props.size" />
|
||||
</div>
|
||||
|
||||
<!-- DDR -->
|
||||
<div class="component-wrapper ddr-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
top: `${224 * props.size}px`,
|
||||
right: `${250 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<DDR :size="1.2*props.size" />
|
||||
</div>
|
||||
|
||||
<!-- SD -->
|
||||
<div class="component-wrapper sd-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
bottom: `${130 * props.size}px`,
|
||||
right: `${172 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<SD :size="1.2*props.size" />
|
||||
</div>
|
||||
|
||||
<!-- SFP -->
|
||||
<div class="component-wrapper sfp-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
bottom: `${210 * props.size}px`,
|
||||
right: `${-46 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<SFP :size="1.84*props.size" />
|
||||
</div>
|
||||
<!-- SFP -->
|
||||
<div class="component-wrapper sfp-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
bottom: `${290 * props.size}px`,
|
||||
right: `${-46 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<SFP :size="1.84*props.size" />
|
||||
</div>
|
||||
<!-- SMA -->
|
||||
<div class="component-wrapper sma-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
top: `${110 * props.size}px`,
|
||||
right: `${204 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<SMA :size="0.75*props.size" />
|
||||
</div>
|
||||
<!-- SMA -->
|
||||
<div class="component-wrapper sma-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
top: `${170 * props.size}px`,
|
||||
right: `${204 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<SMA :size="0.75*props.size" />
|
||||
</div>
|
||||
<!-- SMA -->
|
||||
<div class="component-wrapper sma-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
top: `${250 * props.size}px`,
|
||||
right: `${204 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<SMA :size="0.75*props.size" />
|
||||
</div>
|
||||
<!-- SMA -->
|
||||
<div class="component-wrapper sma-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
top: `${310 * props.size}px`,
|
||||
right: `${204 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<SMA :size="0.75*props.size" />
|
||||
</div>
|
||||
<!-- BUTTON -->
|
||||
<div class="component-wrapper button-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
bottom: `${140 * props.size}px`,
|
||||
right: `${430 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<MechanicalButton :size="0.175*props.size" />
|
||||
</div>
|
||||
<div class="component-wrapper button-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
bottom: `${140 * props.size}px`,
|
||||
right: `${397 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<MechanicalButton :size="0.175*props.size" />
|
||||
</div>
|
||||
<div class="component-wrapper button-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
bottom: `${140 * props.size}px`,
|
||||
right: `${364 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<MechanicalButton :size="0.175*props.size" />
|
||||
</div>
|
||||
<div class="component-wrapper button-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
bottom: `${140 * props.size}px`,
|
||||
right: `${331 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<MechanicalButton :size="0.175*props.size" />
|
||||
</div>
|
||||
<div class="component-wrapper button-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
bottom: `${140 * props.size}px`,
|
||||
right: `${298 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<MechanicalButton :size="0.175*props.size" />
|
||||
</div>
|
||||
<div class="component-wrapper button-wrapper" :style="{
|
||||
position: 'absolute',
|
||||
bottom: `${140 * props.size}px`,
|
||||
right: `${265 * props.size}px`,
|
||||
zIndex: 10
|
||||
}">
|
||||
<MechanicalButton :size="0.175*props.size" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import HDMI from './HDMI.vue';
|
||||
import DDR from './DDR.vue';
|
||||
import ETH from './ETH.vue';
|
||||
import SD from './SD.vue';
|
||||
import SFP from './SFP.vue';
|
||||
import SMA from './SMA.vue';
|
||||
import MechanicalButton from './MechanicalButton.vue';
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 1
|
||||
});
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => 800 * props.size);
|
||||
const height = computed(() => 600 * props.size);
|
||||
|
||||
// 向外暴露方法
|
||||
defineExpose({
|
||||
getInfo: () => ({
|
||||
size: props.size
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.motherboard-container {
|
||||
display: block;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
.svg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none; /* 禁止鼠标交互 */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.component-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
131
src/components/equipments/Pin.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:viewBox="'0 0 ' + viewBoxWidth + ' ' + viewBoxHeight"
|
||||
class="pin-component"
|
||||
>
|
||||
<g :transform="`translate(${viewBoxWidth/2}, ${viewBoxHeight/2})`">
|
||||
<g v-if="props.appearance === 'None'">
|
||||
<g transform="translate(-12.5, -12.5)" class="interactive">
|
||||
<circle
|
||||
style="fill:#909090"
|
||||
cx="12.5"
|
||||
cy="12.5"
|
||||
r="3.75" />
|
||||
</g>
|
||||
</g>
|
||||
<g v-else-if="props.appearance === 'Dip'">
|
||||
<!-- 使用inkscape创建的SVG替代原有Dip样式 -->
|
||||
<g transform="translate(-12.5, -12.5)" class="interactive">
|
||||
<rect
|
||||
:style="`fill:${props.type === 'analog' ? '#2a6099' : '#000000'};fill-opacity:0.772973`"
|
||||
width="25"
|
||||
height="25"
|
||||
x="0"
|
||||
y="0"
|
||||
rx="2.5" />
|
||||
<circle
|
||||
style="fill:#ecececc5;fill-opacity:0.772973"
|
||||
cx="12.5"
|
||||
cy="12.5"
|
||||
r="3.75" />
|
||||
<text
|
||||
style="font-size:6.85px;text-align:start;fill:#ffffff;fill-opacity:0.772973"
|
||||
x="7.3"
|
||||
y="7"
|
||||
xml:space="preserve">{{ props.label }}</text>
|
||||
</g>
|
||||
</g>
|
||||
<g v-else-if="props.appearance === 'SMT'">
|
||||
<rect x="-20" y="-10" width="40" height="20" fill="#aaa" rx="2" ry="2" />
|
||||
<rect x="-18" y="-8" width="36" height="16" :fill="getColorByType" rx="1" ry="1" />
|
||||
<rect x="-16" y="-6" width="26" height="12" :fill="getColorByType" rx="1" ry="1" />
|
||||
<text text-anchor="middle" dominant-baseline="middle" font-size="8" fill="white" x="-3">{{ props.label }}</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
label?: string;
|
||||
constraint?: string;
|
||||
direction?: 'input' | 'output' | 'inout';
|
||||
type?: 'digital' | 'analog';
|
||||
appearance?: 'None' | 'Dip' | 'SMT';
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 1,
|
||||
label: 'PIN',
|
||||
constraint: '',
|
||||
direction: 'input',
|
||||
type: 'digital',
|
||||
appearance: 'Dip'
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'value-change'
|
||||
]);
|
||||
|
||||
// 内部状态
|
||||
const analogValue = ref(0);
|
||||
|
||||
const width = computed(() => props.appearance === 'None' ? 40 * props.size : 30 * props.size);
|
||||
const height = computed(() => {
|
||||
if (props.appearance === 'None') return 20 * props.size;
|
||||
if (props.appearance === 'Dip') return 30 * props.size; // 调整Dip样式高度
|
||||
return 60 * props.size;
|
||||
});
|
||||
const viewBoxWidth = computed(() => props.appearance === 'None' ? 40 : 30);
|
||||
const viewBoxHeight = computed(() => {
|
||||
if (props.appearance === 'None') return 20;
|
||||
if (props.appearance === 'Dip') return 30; // 调整Dip样式视图高度
|
||||
return 60;
|
||||
});
|
||||
|
||||
const getColorByType = computed(() => {
|
||||
return props.type === 'analog' ? '#2a6099' : '#444';
|
||||
});
|
||||
|
||||
function updateAnalogValue(value: number) {
|
||||
if (props.type !== 'analog') return;
|
||||
analogValue.value = Math.max(0, Math.min(1, value));
|
||||
emit('value-change', {
|
||||
label: props.label,
|
||||
constraint: props.constraint,
|
||||
value: analogValue.value
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
setAnalogValue: updateAnalogValue,
|
||||
getAnalogValue: () => analogValue.value,
|
||||
getInfo: () => ({
|
||||
label: props.label,
|
||||
constraint: props.constraint,
|
||||
direction: props.direction,
|
||||
type: props.type,
|
||||
appearance: props.appearance
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pin-component {
|
||||
display: block;
|
||||
user-select: none;
|
||||
}
|
||||
.interactive {
|
||||
cursor: pointer;
|
||||
transition: filter 0.2s;
|
||||
}
|
||||
.interactive:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
</style>
|
||||
48
src/components/equipments/SD.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template> <div class="sd-component" :style="{ width: width + 'px', height: height + 'px' }">
|
||||
<img
|
||||
src="../equipments/svg/sd.svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
alt="SD卡插槽"
|
||||
class="svg-image"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 1
|
||||
});
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => 80 * props.size);
|
||||
const height = computed(() => 60 * props.size);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sd-component {
|
||||
display: block;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
.svg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none; /* 禁止鼠标交互 */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
</style>
|
||||
48
src/components/equipments/SFP.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template> <div class="sfp-component" :style="{ width: width + 'px', height: height + 'px' }">
|
||||
<img
|
||||
src="../equipments/svg/sfp.svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
alt="SFP光纤模块"
|
||||
class="svg-image"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 1
|
||||
});
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => 120 * props.size);
|
||||
const height = computed(() => 40 * props.size);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sfp-component {
|
||||
display: block;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
.svg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none; /* 禁止鼠标交互 */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
</style>
|
||||
48
src/components/equipments/SMA.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template> <div class="sma-component" :style="{ width: width + 'px', height: height + 'px' }">
|
||||
<img
|
||||
src="../equipments/svg/sma.svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
alt="SMA连接器"
|
||||
class="svg-image"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 1
|
||||
});
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => 40 * props.size);
|
||||
const height = computed(() => 40 * props.size);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sma-component {
|
||||
display: block;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
.svg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none; /* 禁止鼠标交互 */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
</style>
|
||||
193
src/components/equipments/SMT_LED.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="led-container" :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 60"
|
||||
class="smt-led"
|
||||
>
|
||||
<!-- LED 基座 -->
|
||||
<rect width="100" height="60" x="0" y="0" fill="#333" rx="5" ry="5" />
|
||||
|
||||
<!-- LED 主体 -->
|
||||
<rect width="90" height="50" x="5" y="5" fill="#222" rx="3" ry="3" />
|
||||
|
||||
<!-- LED 发光部分 -->
|
||||
<rect
|
||||
width="70"
|
||||
height="30"
|
||||
x="15"
|
||||
y="15"
|
||||
:fill="ledColor"
|
||||
:style="{ opacity: isOn ? brightness/100 : 0.2 }"
|
||||
rx="15"
|
||||
ry="15"
|
||||
@click="toggleLed"
|
||||
class="interactive"
|
||||
/>
|
||||
|
||||
<!-- LED 光晕效果 -->
|
||||
<rect
|
||||
v-if="isOn"
|
||||
width="76"
|
||||
height="36"
|
||||
x="12"
|
||||
y="12"
|
||||
:fill="ledColor"
|
||||
:style="{ opacity: brightness/100 * 0.3 }"
|
||||
rx="18"
|
||||
ry="18"
|
||||
filter="blur(5px)"
|
||||
class="glow"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue';
|
||||
|
||||
// LED特有属性
|
||||
interface Props {
|
||||
size?: number;
|
||||
color?: string;
|
||||
initialOn?: boolean;
|
||||
brightness?: number;
|
||||
constraint?: string;
|
||||
}
|
||||
|
||||
// 组件属性定义
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 1,
|
||||
color: 'red',
|
||||
initialOn: false,
|
||||
brightness: 80, // 亮度默认为80%
|
||||
constraint: ''
|
||||
});
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => 100 * props.size);
|
||||
const height = computed(() => 60 * props.size);
|
||||
|
||||
// 内部状态
|
||||
const isOn = ref(props.initialOn);
|
||||
const brightness = ref(props.brightness);
|
||||
|
||||
// LED 颜色映射表
|
||||
const colorMap: Record<string, string> = {
|
||||
'red': '#ff3333',
|
||||
'green': '#33ff33',
|
||||
'blue': '#3333ff',
|
||||
'yellow': '#ffff33',
|
||||
'orange': '#ff9933',
|
||||
'white': '#ffffff',
|
||||
'purple': '#9933ff'
|
||||
};
|
||||
|
||||
// 计算实际LED颜色
|
||||
const ledColor = computed(() => {
|
||||
return colorMap[props.color.toLowerCase()] || props.color;
|
||||
});
|
||||
|
||||
// 定义组件发出的事件
|
||||
const emit = defineEmits([
|
||||
'toggle',
|
||||
'brightness-change',
|
||||
'value-change'
|
||||
]);
|
||||
|
||||
// 手动切换LED状态
|
||||
function toggleLed() {
|
||||
isOn.value = !isOn.value;
|
||||
emit('toggle', isOn.value);
|
||||
emit('value-change', {
|
||||
isOn: isOn.value,
|
||||
brightness: brightness.value
|
||||
});
|
||||
}
|
||||
|
||||
// 设置亮度
|
||||
function setBrightness(value: number) {
|
||||
// 限制亮度值在0-100范围内
|
||||
brightness.value = Math.max(0, Math.min(100, value));
|
||||
emit('brightness-change', brightness.value);
|
||||
emit('value-change', {
|
||||
isOn: isOn.value,
|
||||
brightness: brightness.value
|
||||
});
|
||||
}
|
||||
|
||||
// 手动设置LED开关状态
|
||||
function setLedState(on: boolean) {
|
||||
isOn.value = on;
|
||||
emit('toggle', isOn.value);
|
||||
emit('value-change', {
|
||||
isOn: isOn.value,
|
||||
brightness: brightness.value
|
||||
});
|
||||
}
|
||||
|
||||
// 监听props变化
|
||||
watch(() => props.brightness, (newVal) => {
|
||||
brightness.value = newVal;
|
||||
});
|
||||
|
||||
watch(() => props.initialOn, (newVal) => {
|
||||
isOn.value = newVal;
|
||||
});
|
||||
|
||||
// 向外暴露方法
|
||||
defineExpose({
|
||||
toggleLed,
|
||||
setBrightness,
|
||||
setLedState,
|
||||
getInfo: () => ({
|
||||
// LED特有属性
|
||||
color: props.color,
|
||||
isOn: isOn.value,
|
||||
brightness: brightness.value,
|
||||
constraint: props.constraint
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.led-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.smt-led {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: 0;
|
||||
font-size: 0;
|
||||
box-sizing: content-box;
|
||||
overflow: visible;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.interactive {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.interactive:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
.glow {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,13 @@
|
||||
// filepath: c:\_Project\FPGA_WebLab\FPGA_WebLab\src\components\equipments\Switch.vue
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" :width="props.width" :height="props.height" viewBox="0 0 16 16">
|
||||
<def>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:viewBox="`4 6 ${props.switchCount + 2} 4`"
|
||||
class="dip-switch"
|
||||
>
|
||||
<defs>
|
||||
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feFlood result="flood" flood-color="#f08a5d" flood-opacity="1"></feFlood>
|
||||
<feComposite in="flood" result="mask" in2="SourceGraphic" operator="in"></feComposite>
|
||||
@@ -15,49 +22,130 @@
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</def>
|
||||
</defs>
|
||||
|
||||
<g>
|
||||
<rect width="8" height="4" x="4" y="6" fill="#c01401" rx="0.1" />
|
||||
<text fill="white" font-size="0.7" x="4.25" y="6.75">ON</text>
|
||||
<!-- 红色背景随开关数量变化宽度 -->
|
||||
<rect :width="props.switchCount + 2" height="4" x="4" y="6" fill="#c01401" rx="0.1" />
|
||||
<text v-if="props.showLabels" fill="white" font-size="0.7" x="4.25" y="6.75">ON</text>
|
||||
|
||||
<g>
|
||||
<rect class="glow" @click="toggleBtnStatus(0)" width="0.7" height="2" fill="#68716f" x="5.15" y="7" rx="0.1" />
|
||||
<rect class="glow" @click="toggleBtnStatus(1)" width="0.7" height="2" fill="#68716f" x="6.15" y="7" rx="0.1" />
|
||||
<rect class="glow" @click="toggleBtnStatus(2)" width="0.7" height="2" fill="#68716f" x="7.15" y="7" rx="0.1" />
|
||||
<rect class="glow" @click="toggleBtnStatus(3)" width="0.7" height="2" fill="#68716f" x="8.15" y="7" rx="0.1" />
|
||||
<rect class="glow" @click="toggleBtnStatus(4)" width="0.7" height="2" fill="#68716f" x="9.15" y="7" rx="0.1" />
|
||||
<rect class="glow" @click="toggleBtnStatus(5)" width="0.7" height="2" fill="#68716f" x="10.15" y="7" rx="0.1" />
|
||||
<template v-for="(_, index) in Array(props.switchCount)" :key="index">
|
||||
<rect
|
||||
class="glow interactive"
|
||||
@click="toggleBtnStatus(index)"
|
||||
width="0.7"
|
||||
height="2"
|
||||
fill="#68716f"
|
||||
:x="5.15 + index"
|
||||
y="7"
|
||||
rx="0.1"
|
||||
/>
|
||||
<text
|
||||
v-if="props.showLabels"
|
||||
:x="5.5 + index"
|
||||
y="9.5"
|
||||
font-size="0.4"
|
||||
text-anchor="middle"
|
||||
fill="#444"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</text>
|
||||
</template>
|
||||
</g>
|
||||
|
||||
<g>
|
||||
<rect @click="toggleBtnStatus(0)" width="0.65" height="0.65" fill="white" x="5.175" :y="btnLocation[0]" rx="0.1"
|
||||
opacity="1" />
|
||||
<rect @click="toggleBtnStatus(1)" width="0.65" height="0.65" fill="white" x="6.175" :y="btnLocation[1]" rx="0.1"
|
||||
opacity="1" />
|
||||
<rect @click="toggleBtnStatus(2)" width="0.65" height="0.65" fill="white" x="7.175" :y="btnLocation[2]" rx="0.1"
|
||||
opacity="1" />
|
||||
<rect @click="toggleBtnStatus(3)" width="0.65" height="0.65" fill="white" x="8.175" :y="btnLocation[3]" rx="0.1"
|
||||
opacity="1" />
|
||||
<rect @click="toggleBtnStatus(4)" width="0.65" height="0.65" fill="white" x="9.175" :y="btnLocation[4]" rx="0.1"
|
||||
opacity="1" />
|
||||
<rect @click="toggleBtnStatus(5)" width="0.65" height="0.65" fill="white" x="10.175" :y="btnLocation[5]"
|
||||
rx="0.1" opacity="1" />
|
||||
<template v-for="(location, index) in btnLocation" :key="`btn-${index}`">
|
||||
<rect
|
||||
class="interactive"
|
||||
@click="toggleBtnStatus(index)"
|
||||
width="0.65"
|
||||
height="0.65"
|
||||
fill="white"
|
||||
:x="5.175 + index"
|
||||
:y="location"
|
||||
rx="0.1"
|
||||
opacity="1"
|
||||
/>
|
||||
</template>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
interface Props {
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
size?: number;
|
||||
switchCount?: number;
|
||||
// 新增属性
|
||||
initialValues?: boolean[] | string; // 开关的初始状态,可以是布尔数组或逗号分隔的字符串
|
||||
showLabels?: boolean; // 是否显示标签
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
width: 160,
|
||||
height: 160,
|
||||
size: 1,
|
||||
switchCount: 6,
|
||||
initialValues: () => [],
|
||||
showLabels: true
|
||||
});
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => {
|
||||
// 每个开关占用25px宽度,再加上两侧边距(20px)
|
||||
return (props.switchCount * 25 + 20) * props.size;
|
||||
});
|
||||
const height = computed(() => 85 * props.size); // 高度保持固定比例
|
||||
|
||||
// 定义发出的事件
|
||||
const emit = defineEmits(['change', 'switch-toggle']);
|
||||
|
||||
// 解析初始值,支持字符串和数组两种格式
|
||||
const parseInitialValues = () => {
|
||||
if (Array.isArray(props.initialValues)) {
|
||||
return [...props.initialValues].slice(0, props.switchCount);
|
||||
} else if (typeof props.initialValues === 'string' && props.initialValues.trim() !== '') {
|
||||
// 将逗号分隔的字符串转换为布尔数组
|
||||
const values = props.initialValues.split(',')
|
||||
.map(val => val.trim() === '1' || val.trim().toLowerCase() === 'true')
|
||||
.slice(0, props.switchCount);
|
||||
|
||||
// 如果数组长度小于开关数量,用 false 填充
|
||||
while (values.length < props.switchCount) {
|
||||
values.push(false);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
// 默认返回全部为 false 的数组
|
||||
return Array(props.switchCount).fill(false);
|
||||
};
|
||||
|
||||
// 初始化按钮状态
|
||||
const btnStatus = ref(parseInitialValues());
|
||||
|
||||
// 监听 switchCount 变化,调整开关状态数组
|
||||
watch(() => props.switchCount, (newCount) => {
|
||||
if (newCount !== btnStatus.value.length) {
|
||||
// 如果新数量大于当前数量,则扩展数组
|
||||
if (newCount > btnStatus.value.length) {
|
||||
btnStatus.value = [
|
||||
...btnStatus.value,
|
||||
...Array(newCount - btnStatus.value.length).fill(false)
|
||||
];
|
||||
} else {
|
||||
// 如果新数量小于当前数量,则截断数组
|
||||
btnStatus.value = btnStatus.value.slice(0, newCount);
|
||||
}
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 监听 initialValues 变化,更新开关状态
|
||||
watch(() => props.initialValues, () => {
|
||||
btnStatus.value = parseInitialValues();
|
||||
});
|
||||
|
||||
const btnStatus = ref([false, false, false, false, false, false]);
|
||||
const btnLocation = computed(() => {
|
||||
return btnStatus.value.map((status) => {
|
||||
return status ? 7.025 : 8.325;
|
||||
@@ -65,20 +153,58 @@ const btnLocation = computed(() => {
|
||||
});
|
||||
|
||||
function setBtnStatus(btnNum: number, isOn: boolean): void {
|
||||
btnStatus.value[btnNum] = isOn;
|
||||
if (btnNum >= 0 && btnNum < btnStatus.value.length) {
|
||||
btnStatus.value[btnNum] = isOn;
|
||||
emit('change', { index: btnNum, value: isOn, states: [...btnStatus.value] });
|
||||
}
|
||||
}
|
||||
|
||||
function toggleBtnStatus(btnNum: number): void {
|
||||
btnStatus.value[btnNum] = !btnStatus.value[btnNum];
|
||||
if (btnNum >= 0 && btnNum < btnStatus.value.length) {
|
||||
btnStatus.value[btnNum] = !btnStatus.value[btnNum];
|
||||
emit('switch-toggle', {
|
||||
index: btnNum,
|
||||
value: btnStatus.value[btnNum],
|
||||
states: [...btnStatus.value]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 一次性设置所有开关状态
|
||||
function setAllStates(states: boolean[]): void {
|
||||
const newStates = states.slice(0, props.switchCount);
|
||||
while (newStates.length < props.switchCount) {
|
||||
newStates.push(false);
|
||||
}
|
||||
btnStatus.value = newStates;
|
||||
emit('change', { states: [...btnStatus.value] });
|
||||
}
|
||||
|
||||
// 暴露组件方法和状态
|
||||
defineExpose({
|
||||
setBtnStatus,
|
||||
toggleBtnStatus,
|
||||
setAllStates,
|
||||
getBtnStatus: () => [...btnStatus.value]
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.dip-switch {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: 0; /* 移除行高导致的额外间距 */
|
||||
font-size: 0; /* 防止文本节点造成的间距 */
|
||||
box-sizing: content-box;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
rect {
|
||||
transition: all 100ms ease-in-out;
|
||||
}
|
||||
|
||||
.glow:hover {
|
||||
filter: url(#glow);
|
||||
.interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
327
src/components/equipments/componentConfig.ts
Normal file
@@ -0,0 +1,327 @@
|
||||
// 组件配置声明
|
||||
export type PropType = 'string' | 'number' | 'boolean' | 'select';
|
||||
|
||||
// 定义选择类型选项
|
||||
export interface PropOption {
|
||||
value: string | number | boolean;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface PropConfig {
|
||||
name: string;
|
||||
type: string;
|
||||
label: string;
|
||||
default: any;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
options?: PropOption[];
|
||||
description?: string;
|
||||
category?: string; // 用于在UI中分组属性
|
||||
}
|
||||
|
||||
export interface ComponentConfig {
|
||||
props: PropConfig[];
|
||||
}
|
||||
|
||||
// 存储所有组件的配置
|
||||
const componentConfigs: Record<string, ComponentConfig> = {
|
||||
MechanicalButton: {
|
||||
props: [
|
||||
{
|
||||
name: 'bindKey',
|
||||
type: 'string',
|
||||
label: '绑定按键',
|
||||
default: '',
|
||||
description: '触发按钮按下的键盘按键'
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
type: 'number',
|
||||
label: '大小',
|
||||
default: 1,
|
||||
min: 0.5,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
description: '按钮的相对大小,1代表标准大小'
|
||||
},
|
||||
{
|
||||
name: 'buttonText',
|
||||
type: 'string',
|
||||
label: '按钮文本',
|
||||
default: '',
|
||||
description: '按钮上显示的自定义文本,优先级高于绑定按键'
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'string',
|
||||
label: '引脚标签',
|
||||
default: 'BTN',
|
||||
description: '引脚的标签文本'
|
||||
},
|
||||
{
|
||||
name: 'constraint',
|
||||
type: 'string',
|
||||
label: '引脚约束',
|
||||
default: '',
|
||||
description: '相同约束字符串的引脚将被视为有电气连接'
|
||||
}
|
||||
]
|
||||
},
|
||||
Switch: {
|
||||
props: [
|
||||
{
|
||||
name: 'size',
|
||||
type: 'number',
|
||||
label: '大小',
|
||||
default: 1,
|
||||
min: 0.5,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
description: '开关的相对大小,1代表标准大小'
|
||||
},
|
||||
{
|
||||
name: 'switchCount',
|
||||
type: 'number',
|
||||
label: '开关数量',
|
||||
default: 6,
|
||||
min: 1,
|
||||
max: 12,
|
||||
step: 1,
|
||||
description: '可翻转开关的数量'
|
||||
},
|
||||
{
|
||||
name: 'showLabels',
|
||||
type: 'boolean',
|
||||
label: '显示标签',
|
||||
default: true,
|
||||
description: '是否显示开关编号标签'
|
||||
},
|
||||
{
|
||||
name: 'initialValues',
|
||||
type: 'string',
|
||||
label: '初始状态',
|
||||
default: '',
|
||||
description: '开关的初始状态,格式为逗号分隔的0/1,如"1,0,1"表示第1、3个开关打开'
|
||||
}
|
||||
]
|
||||
},
|
||||
Pin: {
|
||||
props: [
|
||||
{
|
||||
name: 'size',
|
||||
type: 'number',
|
||||
label: '大小',
|
||||
default: 1,
|
||||
min: 0.5,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
description: '引脚的相对大小,1代表标准大小'
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'string',
|
||||
label: '引脚标签',
|
||||
default: 'PIN',
|
||||
description: '用于标识引脚的名称'
|
||||
},
|
||||
{
|
||||
name: 'constraint',
|
||||
type: 'string',
|
||||
label: '引脚约束',
|
||||
default: '',
|
||||
description: '相同约束字符串的引脚将被视为有电气连接'
|
||||
},
|
||||
{
|
||||
name: 'direction',
|
||||
type: 'select',
|
||||
label: '输入/输出特性',
|
||||
default: 'input',
|
||||
options: [
|
||||
{ value: 'input', label: '输入' },
|
||||
{ value: 'output', label: '输出' },
|
||||
{ value: 'inout', label: '双向' }
|
||||
],
|
||||
description: '引脚的输入/输出特性'
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
label: '模数特性',
|
||||
default: 'digital',
|
||||
options: [
|
||||
{ value: 'digital', label: 'digital' },
|
||||
{ value: 'analog', label: 'analog' }
|
||||
],
|
||||
description: '引脚的模数特性,数字或模拟'
|
||||
},
|
||||
{
|
||||
name: 'appearance',
|
||||
type: 'select',
|
||||
label: '引脚样式',
|
||||
default: 'Dip',
|
||||
options: [
|
||||
{ value: 'None', label: 'None' },
|
||||
{ value: 'Dip', label: 'Dip' },
|
||||
{ value: 'SMT', label: 'SMT' }
|
||||
],
|
||||
description: '引脚的外观样式,不影响功能'
|
||||
}
|
||||
]
|
||||
},
|
||||
HDMI: {
|
||||
props: [
|
||||
{
|
||||
name: 'size',
|
||||
type: 'number',
|
||||
label: '大小',
|
||||
default: 1,
|
||||
min: 0.5,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
description: 'HDMI接口的相对大小,1代表标准大小'
|
||||
}
|
||||
]
|
||||
},
|
||||
DDR: {
|
||||
props: [
|
||||
{
|
||||
name: 'size',
|
||||
type: 'number',
|
||||
label: '大小',
|
||||
default: 1,
|
||||
min: 0.5,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
description: 'DDR内存的相对大小,1代表标准大小'
|
||||
}
|
||||
]
|
||||
},
|
||||
ETH: {
|
||||
props: [
|
||||
{
|
||||
name: 'size',
|
||||
type: 'number',
|
||||
label: '大小',
|
||||
default: 1,
|
||||
min: 0.5,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
description: '以太网接口的相对大小,1代表标准大小'
|
||||
}
|
||||
]
|
||||
},
|
||||
SD: {
|
||||
props: [
|
||||
{
|
||||
name: 'size',
|
||||
type: 'number',
|
||||
label: '大小',
|
||||
default: 1,
|
||||
min: 0.5,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
description: 'SD卡插槽的相对大小,1代表标准大小'
|
||||
}
|
||||
]
|
||||
},
|
||||
SFP: {
|
||||
props: [
|
||||
{
|
||||
name: 'size',
|
||||
type: 'number',
|
||||
label: '大小',
|
||||
default: 1,
|
||||
min: 0.5,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
description: 'SFP光纤模块的相对大小,1代表标准大小'
|
||||
}
|
||||
]
|
||||
},
|
||||
SMA: {
|
||||
props: [
|
||||
{
|
||||
name: 'size',
|
||||
type: 'number',
|
||||
label: '大小',
|
||||
default: 1,
|
||||
min: 0.5,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
description: 'SMA连接器的相对大小,1代表标准大小'
|
||||
}
|
||||
]
|
||||
}, MotherBoard: {
|
||||
props: [
|
||||
{
|
||||
name: 'size',
|
||||
type: 'number',
|
||||
label: '大小',
|
||||
default: 1,
|
||||
min: 0.5,
|
||||
max: 2,
|
||||
step: 0.1,
|
||||
description: '主板的相对大小,1代表标准大小'
|
||||
}
|
||||
]
|
||||
}, SMT_LED: {
|
||||
props: [
|
||||
{
|
||||
name: 'size',
|
||||
type: 'number',
|
||||
label: '大小',
|
||||
default: 1,
|
||||
min: 0.5,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
description: 'LED的相对大小,1代表标准大小'
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
type: 'select',
|
||||
label: '颜色',
|
||||
default: 'red',
|
||||
options: [
|
||||
{ value: 'red', label: '红色' },
|
||||
{ value: 'green', label: '绿色' },
|
||||
{ value: 'blue', label: '蓝色' },
|
||||
{ value: 'yellow', label: '黄色' },
|
||||
{ value: 'orange', label: '橙色' },
|
||||
{ value: 'white', label: '白色' },
|
||||
{ value: 'purple', label: '紫色' }
|
||||
],
|
||||
description: 'LED的颜色'
|
||||
},
|
||||
{
|
||||
name: 'initialOn',
|
||||
type: 'boolean',
|
||||
label: '初始状态',
|
||||
default: false,
|
||||
description: 'LED的初始开关状态'
|
||||
},
|
||||
{
|
||||
name: 'brightness',
|
||||
type: 'number',
|
||||
label: '亮度(%)',
|
||||
default: 80,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 5,
|
||||
description: 'LED的亮度百分比,范围0-100'
|
||||
},
|
||||
{
|
||||
name: 'constraint',
|
||||
type: 'string',
|
||||
label: '连接约束',
|
||||
default: '',
|
||||
description: '相同约束字符串的组件将被视为有电气连接'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// 获取组件配置的函数
|
||||
export function getComponentConfig(type: string): ComponentConfig | null {
|
||||
return componentConfigs[type] || null;
|
||||
}
|
||||
27
src/components/equipments/svg/button.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="160"
|
||||
height="160"
|
||||
viewBox="400 400 800 800"
|
||||
>
|
||||
<!-- 按钮底座 -->
|
||||
<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" />
|
||||
<circle
|
||||
r="200"
|
||||
cx="800"
|
||||
cy="800"
|
||||
fill="#4b4b4b"
|
||||
fill-opacity="0.9"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 754 B |
16147
src/components/equipments/svg/ddr.svg
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
16286
src/components/equipments/svg/eth.svg
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
16126
src/components/equipments/svg/hdmi.svg
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
38
src/components/equipments/svg/led.svg
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100"
|
||||
height="60"
|
||||
viewBox="0 0 100 60"
|
||||
>
|
||||
<!-- LED 基座 -->
|
||||
<rect width="100" height="60" x="0" y="0" fill="#333" rx="5" ry="5" />
|
||||
|
||||
<!-- LED 主体 -->
|
||||
<rect width="90" height="50" x="5" y="5" fill="#222" rx="3" ry="3" />
|
||||
|
||||
<!-- LED 发光部分 -->
|
||||
<rect
|
||||
width="70"
|
||||
height="30"
|
||||
x="15"
|
||||
y="15"
|
||||
fill="#ff3333"
|
||||
opacity="0.8"
|
||||
rx="15"
|
||||
ry="15"
|
||||
/>
|
||||
|
||||
<!-- LED 光晕效果 -->
|
||||
<rect
|
||||
width="76"
|
||||
height="36"
|
||||
x="12"
|
||||
y="12"
|
||||
fill="#ff3333"
|
||||
opacity="0.2"
|
||||
rx="18"
|
||||
ry="18"
|
||||
filter="blur(3px)"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 708 B |
291
src/components/equipments/svg/motherboard.svg
Normal file
|
After Width: | Height: | Size: 26 KiB |
43
src/components/equipments/svg/pin_dip.svg
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="4.9999995mm"
|
||||
height="5mm"
|
||||
viewBox="0 0 4.9999995 5"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-10,-10.000002)">
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:0.772973;stroke-width:0.265;stroke-dasharray:none"
|
||||
id="rect1"
|
||||
width="5"
|
||||
height="5"
|
||||
x="10"
|
||||
y="10"
|
||||
rx="0.5"
|
||||
onclick="" />
|
||||
<circle
|
||||
style="fill:#ececec;fill-opacity:0.772973;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="path1"
|
||||
cx="12.5"
|
||||
cy="12.5"
|
||||
r="0.75" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:1.3717px;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;fill-opacity:0.772973;stroke-width:0.114488;stroke-dasharray:none"
|
||||
x="11.473036"
|
||||
y="11.410812"
|
||||
id="text2"><tspan
|
||||
id="tspan2"
|
||||
style="fill:#ffffff;stroke-width:0.114488"
|
||||
x="11.473036"
|
||||
y="11.410812">Pn</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
16120
src/components/equipments/svg/sd.svg
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
19
src/components/equipments/svg/sfp.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="53.600014mm"
|
||||
height="16.279999mm"
|
||||
viewBox="0 0 53.600014 16.279999"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1" /><g
|
||||
id="layer1"
|
||||
transform="translate(-86.17357,-29.131738)"><g
|
||||
id="g4493"><path
|
||||
d="m 89.863578,29.151737 v 0.44 h -3.690004 v 2.54 h 0.44 v 3.95 h -0.44 v 2.97 h 0.43 v 3.95 h -0.43 v 2.1 h 3.770004 v 0.31 h 3.95 v -0.38 h 3.95 v 0.38 h 4.220002 v -0.35 h 3.95 v 0.35 h 3.95 v -0.41 h 3.95 v 0.41 h 3.66 v -0.45 h 3.95 v 0.45 h 3.95 v -0.51 h 3.95 v 0.51 h 4.22 v -0.48 h 3.95 v 0.48 h 2.18 v -16.28 h -2.26 v 0.28 h -3.95 v -0.28 h -4.22 v 0.25 h -3.95 v -0.25 h -3.95 v 0.31 h -3.95 v -0.31 h -3.66 v 0.35 h -3.95 v -0.35 h -3.95 v 0.41 h -3.95 v -0.41 h -4.220002 v 0.38 h -3.95 v -0.38 z m 21.090002,2.82 c 0.52,0 0.94,0.42 0.94,0.94 0,0.52 -0.42,0.94 -0.94,0.94 -0.52,0 -0.94,-0.42 -0.94,-0.94 0,-0.52 0.42,-0.94 0.94,-0.94 z m 9.82,0.02 c 0.52,0 0.94,0.42 0.94,0.94 0,0.52 -0.42,0.94 -0.94,0.94 -0.52,0 -0.94,-0.42 -0.94,-0.94 0,-0.52 0.42,-0.94 0.94,-0.94 z m -28.710002,0.03 c 0.52,0 0.94,0.42 0.94,0.94 0,0.52 -0.42,0.94 -0.94,0.94 -0.52,0 -0.94,-0.42 -0.94,-0.94 0,-0.52 0.42,-0.94 0.94,-0.94 z m 9.680002,0.02 c 0.52,0 0.94,0.42 0.94,0.94 0,0.52 -0.42,0.94 -0.94,0.94 -0.52,0 -0.94,-0.42 -0.94,-0.94 0,-0.52 0.42,-0.94 0.94,-0.94 z m 29.17,0.21 h 6.25 c 0.22,0 0.39,0.11 0.39,0.25 0,0.14 -0.17,0.25 -0.39,0.25 h -6.25 c -0.22,0 -0.39,-0.11 -0.39,-0.25 0,-0.14 0.17,-0.25 0.39,-0.25 z m -0.07,2 h 6.25 c 0.22,0 0.39,0.11 0.39,0.25 0,0.14 -0.17,0.25 -0.39,0.25 h -6.25 c -0.22,0 -0.39,-0.11 -0.39,-0.25 0,-0.14 0.17,-0.25 0.39,-0.25 z m -15.17,2.3 c 0.52,0 0.94,0.42 0.94,0.94 0,0.52 -0.42,0.94 -0.94,0.94 -0.52,0 -0.94,-0.42 -0.94,-0.94 0,-0.52 0.42,-0.94 0.94,-0.94 z m 9.82,0.02 c 0.52,0 0.94,0.42 0.94,0.94 0,0.52 -0.42,0.94 -0.94,0.94 -0.52,0 -0.94,-0.42 -0.94,-0.94 0,-0.52 0.42,-0.94 0.94,-0.94 z m -28.710002,0.03 c 0.52,0 0.94,0.42 0.94,0.94 0,0.52 -0.42,0.94 -0.94,0.94 -0.52,0 -0.94,-0.42 -0.94,-0.94 0,-0.52 0.42,-0.94 0.94,-0.94 z m 9.680002,0.02 c 0.52,0 0.94,0.42 0.94,0.94 0,0.52 -0.42,0.94 -0.94,0.94 -0.52,0 -0.94,-0.42 -0.94,-0.94 0,-0.52 0.42,-0.94 0.94,-0.94 z m 24.42,3.36 h 6.25 c 0.22,0 0.39,0.11 0.39,0.25 0,0.14 -0.17,0.25 -0.39,0.25 h -6.25 c -0.22,0 -0.39,-0.11 -0.39,-0.25 0,-0.14 0.17,-0.25 0.39,-0.25 z m -19.84,1.31 c 0.52,0 0.94,0.42 0.94,0.94 0,0.52 -0.42,0.94 -0.94,0.94 -0.52,0 -0.94,-0.42 -0.94,-0.94 0,-0.52 0.42,-0.94 0.94,-0.94 z m 9.82,0.02 c 0.52,0 0.94,0.42 0.94,0.94 0,0.52 -0.42,0.94 -0.94,0.94 -0.52,0 -0.94,-0.42 -0.94,-0.94 0,-0.52 0.42,-0.94 0.94,-0.94 z m -28.710002,0.03 c 0.52,0 0.94,0.42 0.94,0.94 0,0.52 -0.42,0.94 -0.94,0.94 -0.52,0 -0.94,-0.42 -0.94,-0.94 0,-0.52 0.42,-0.94 0.94,-0.94 z m 9.680002,0.02 c 0.52,0 0.94,0.42 0.94,0.94 0,0.52 -0.42,0.94 -0.94,0.94 -0.52,0 -0.94,-0.42 -0.94,-0.94 0,-0.52 0.42,-0.94 0.94,-0.94 z m 28.99,0.63 h 6.25 c 0.22,0 0.39,0.11 0.39,0.25 0,0.14 -0.17,0.25 -0.39,0.25 h -6.25 c -0.22,0 -0.39,-0.11 -0.39,-0.25 0,-0.14 0.17,-0.25 0.39,-0.25 z"
|
||||
style="font-variation-settings:'wght' 700;fill:#ececec;fill-opacity:1;stroke-width:0.518999;stroke-linecap:round;stroke-linejoin:round;paint-order:fill markers stroke"
|
||||
id="path4484-9-9" /></g></g></svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
16133
src/components/equipments/svg/sma.svg
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
33
src/components/equipments/svg/switch.svg
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="200"
|
||||
height="100"
|
||||
viewBox="0 0 200 100"
|
||||
>
|
||||
<!-- 开关基座 -->
|
||||
<rect width="200" height="100" x="0" y="0" fill="#444" rx="5" ry="5" />
|
||||
|
||||
<!-- 开关面板 -->
|
||||
<rect width="180" height="80" x="10" y="10" fill="#333" rx="4" ry="4" />
|
||||
|
||||
<!-- 开关拨片示例 (6个) -->
|
||||
<g fill="#ddd">
|
||||
<rect width="20" height="40" x="25" y="30" rx="2" ry="2" />
|
||||
<rect width="20" height="40" x="55" y="30" rx="2" ry="2" />
|
||||
<rect width="20" height="40" x="85" y="30" rx="2" ry="2" />
|
||||
<rect width="20" height="40" x="115" y="30" rx="2" ry="2" />
|
||||
<rect width="20" height="40" x="145" y="30" rx="2" ry="2" />
|
||||
<rect width="20" height="40" x="175" y="30" rx="2" ry="2" />
|
||||
</g>
|
||||
|
||||
<!-- 开关标签 -->
|
||||
<g fill="#fff" font-size="8" text-anchor="middle">
|
||||
<text x="25" y="80">1</text>
|
||||
<text x="55" y="80">2</text>
|
||||
<text x="85" y="80">3</text>
|
||||
<text x="115" y="80">4</text>
|
||||
<text x="145" y="80">5</text>
|
||||
<text x="175" y="80">6</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |