feat: remake most of forntend

This commit is contained in:
alivender
2025-04-26 19:59:35 +08:00
parent 4e741f9ef8
commit bc4f44ecaa
41 changed files with 84095 additions and 672 deletions

View File

@@ -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>