Compare commits

...
2 Commits
Author SHA1 Message Date
feie9454 c6d10b6221 优化代码结构 2026-01-05 11:46:46 +08:00
feie9454 a23facccba 优化示波器与连线对应,优化接线错误提示 2026-01-05 11:39:54 +08:00
4 changed files with 290 additions and 157 deletions
+102 -138
View File
@@ -3,6 +3,7 @@ import { ref, computed, reactive, onMounted, onBeforeUnmount, watch } from 'vue'
import DeterminationMachine from './components/DeterminationMachine.vue'; import DeterminationMachine from './components/DeterminationMachine.vue';
//@ts-ignore //@ts-ignore
import SilverKnob from './components/SilverKnob.vue'; import SilverKnob from './components/SilverKnob.vue';
import DraggableInstrumentWrapper from './components/DraggableInstrumentWrapper.vue';
import Oscilloscope from './components/Oscilloscope.vue'; import Oscilloscope from './components/Oscilloscope.vue';
import SignalGen from './components/SignalGen.vue'; import SignalGen from './components/SignalGen.vue';
import Notebook from './components/Notebook.vue'; import Notebook from './components/Notebook.vue';
@@ -336,6 +337,23 @@ const wiringConnected = reactive<Record<WireKey, boolean>>({
rxTransducer_to_right: false, rxTransducer_to_right: false,
}); });
// =========================
// 示波器信号有效性(由接线决定)
// =========================
// CH1:仅当 [信号发生器 发射端 波形] -> [示波器 CH1] 接入时才有波形
const oscCh1HasSignal = computed(() => wiringConnected.txWave_to_ch1);
// CH2:仅当这三条线都接入时才有波形
// [信号发生器 发射端 换能器] -> [换能器 左侧输入]
// [信号发生器 接收端 波形] -> [示波器 CH2]
// [信号发生器 接收端 换能器] -> [换能器 右侧输出]
const oscCh2HasSignal = computed(() =>
wiringConnected.txTransducer_to_left
&& wiringConnected.rxWave_to_ch2
&& wiringConnected.rxTransducer_to_right
);
// ========================= // =========================
// 步骤标题(document.title) // 步骤标题(document.title)
// ========================= // =========================
@@ -389,6 +407,17 @@ const tooltip = reactive({
side: 'right' as TooltipSide, side: 'right' as TooltipSide,
}); });
// 接线错误:短暂提示(不打断交互)
const wiringErrorToast = reactive({
visible: false,
text: '',
x: 0,
y: 0,
side: 'right' as TooltipSide,
});
let wiringErrorToastTimer: number | null = null;
const tooltipTargetEl = ref<HTMLElement | null>(null); const tooltipTargetEl = ref<HTMLElement | null>(null);
const hideTooltip = () => { const hideTooltip = () => {
@@ -396,7 +425,7 @@ const hideTooltip = () => {
tooltipTargetEl.value = null; tooltipTargetEl.value = null;
}; };
const updateTooltipPosition = (el: HTMLElement) => { const calcBubblePosition = (el: HTMLElement) => {
const table = tableRef.value; const table = tableRef.value;
if (!table) return; if (!table) return;
@@ -412,11 +441,21 @@ const updateTooltipPosition = (el: HTMLElement) => {
const leftX = r.left - tableRect.left - margin; const leftX = r.left - tableRect.left - margin;
const hasRoomOnRight = rightX + approxBubbleW <= tableRect.width; const hasRoomOnRight = rightX + approxBubbleW <= tableRect.width;
tooltip.side = hasRoomOnRight ? 'right' : 'left'; const side = hasRoomOnRight ? 'right' : 'left';
tooltip.x = hasRoomOnRight ? rightX : leftX; const x = hasRoomOnRight ? rightX : leftX;
// y 以上方为主,避免贴边 // y 以上方为主,避免贴边
tooltip.y = Math.max(10, Math.min(tableRect.height - 10, anchorY)); const y = Math.max(10, Math.min(tableRect.height - 10, anchorY));
return { x, y, side } as const;
};
const updateTooltipPosition = (el: HTMLElement) => {
const pos = calcBubblePosition(el);
if (!pos) return;
tooltip.side = pos.side;
tooltip.x = pos.x;
tooltip.y = pos.y;
}; };
const showTooltipForEl = (el: HTMLElement) => { const showTooltipForEl = (el: HTMLElement) => {
@@ -430,6 +469,27 @@ const showTooltipForEl = (el: HTMLElement) => {
updateTooltipPosition(el); updateTooltipPosition(el);
}; };
const showWiringErrorToast = (text: string, anchorEl?: HTMLElement) => {
const el = anchorEl ?? null;
if (el) {
const pos = calcBubblePosition(el);
if (pos) {
wiringErrorToast.side = pos.side;
wiringErrorToast.x = pos.x;
wiringErrorToast.y = pos.y;
}
}
wiringErrorToast.text = text;
wiringErrorToast.visible = true;
if (wiringErrorToastTimer !== null) window.clearTimeout(wiringErrorToastTimer);
wiringErrorToastTimer = window.setTimeout(() => {
wiringErrorToastTimer = null;
wiringErrorToast.visible = false;
}, 1600);
};
const findTooltipEl = (target: EventTarget | null): HTMLElement | null => { const findTooltipEl = (target: EventTarget | null): HTMLElement | null => {
if (!target) return null; if (!target) return null;
const t = target as HTMLElement; const t = target as HTMLElement;
@@ -473,6 +533,15 @@ const tooltipStyle = computed(() => {
return base; return base;
}); });
const wiringErrorToastStyle = computed(() => {
const base: Record<string, string> = {
left: `${wiringErrorToast.x}px`,
top: `${wiringErrorToast.y}px`,
};
base.transform = wiringErrorToast.side === 'right' ? 'translate(0, -50%)' : 'translate(-100%, -50%)';
return base;
});
const pinEls = new Map<PinId, HTMLElement>(); const pinEls = new Map<PinId, HTMLElement>();
const selectedPin = ref<PinId | null>(null); const selectedPin = ref<PinId | null>(null);
@@ -584,13 +653,16 @@ const addCableIfNeeded = (key: WireKey, a: PinId, b: PinId) => {
scheduleUpdateCablePaths(); scheduleUpdateCablePaths();
}; };
const alertWiringError = () => { const alertWiringError = (anchorEl?: HTMLElement) => {
window.alert('接线错误:请按接线示意连接对应端口。'); showWiringErrorToast('接线错误:请按接线示意连接对应端口。', anchorEl);
}; };
const onPinClick = (id: PinId) => { const onPinClick = (id: PinId) => {
ea.play('线缆插入') ea.play('线缆插入')
// 有任何点击就把“错误提示”收起,避免遮挡
if (wiringErrorToast.visible) wiringErrorToast.visible = false;
postTracker('pin_click', { pinId: id, hasSelected: selectedPin.value !== null }); postTracker('pin_click', { pinId: id, hasSelected: selectedPin.value !== null });
if (selectedPin.value === null) { if (selectedPin.value === null) {
@@ -608,7 +680,7 @@ const onPinClick = (id: PinId) => {
const key = requiredByPair.get(normalizePair(first, id)); const key = requiredByPair.get(normalizePair(first, id));
if (!key) { if (!key) {
postTracker('wire_error', { a: first, b: id }); postTracker('wire_error', { a: first, b: id });
alertWiringError(); alertWiringError(pinEls.get(id) ?? pinEls.get(first));
return; return;
} }
@@ -677,6 +749,7 @@ onBeforeUnmount(() => {
if (cablesRaf !== null) cancelAnimationFrame(cablesRaf); if (cablesRaf !== null) cancelAnimationFrame(cablesRaf);
window.removeEventListener('resize', scheduleUpdateCablePaths); window.removeEventListener('resize', scheduleUpdateCablePaths);
if (dmRightCableTimer !== null) window.clearTimeout(dmRightCableTimer); if (dmRightCableTimer !== null) window.clearTimeout(dmRightCableTimer);
if (wiringErrorToastTimer !== null) window.clearTimeout(wiringErrorToastTimer);
}); });
</script> </script>
@@ -695,6 +768,11 @@ onBeforeUnmount(() => {
<div class="tooltip-inner">{{ tooltip.text }}</div> <div class="tooltip-inner">{{ tooltip.text }}</div>
</div> </div>
<div v-if="wiringErrorToast.visible" class="tooltip" :class="`tooltip--${wiringErrorToast.side}`"
:style="wiringErrorToastStyle">
<div class="tooltip-inner">{{ wiringErrorToast.text }}</div>
</div>
<svg class="cables" :width="1600" :height="900" viewBox="0 0 1600 900" <svg class="cables" :width="1600" :height="900" viewBox="0 0 1600 900"
aria-hidden="true"> aria-hidden="true">
<defs> <defs>
@@ -711,91 +789,29 @@ onBeforeUnmount(() => {
</g> </g>
</svg> </svg>
<div class="instrument-wrapper" :style="machineStyle"> <DraggableInstrumentWrapper :style="machineStyle" :instrument="machine" @start-drag="startDrag">
<div class="drag-handle" data-tooltip="拖动把手可移动仪器位置" @pointerdown="startDrag(machine, $event)">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<circle cx="12" cy="5" r="1" />
<circle cx="12" cy="12" r="1" />
<circle cx="12" cy="19" r="1" />
<circle cx="8" cy="5" r="1" />
<circle cx="8" cy="12" r="1" />
<circle cx="8" cy="19" r="1" />
<circle cx="16" cy="5" r="1" />
<circle cx="16" cy="12" r="1" />
<circle cx="16" cy="19" r="1" />
</svg>
</div>
<DeterminationMachine v-model="distance" class="machine" <DeterminationMachine v-model="distance" class="machine"
@register-pin="registerPin" @unregister-pin="unregisterPin" @register-pin="registerPin" @unregister-pin="unregisterPin" @pin-click="onPinClick"
@pin-click="onPinClick" @change="onDistanceChange" /> @change="onDistanceChange" />
</div> </DraggableInstrumentWrapper>
<div class="instrument-wrapper" :style="notebookStyle"> <DraggableInstrumentWrapper :style="notebookStyle" :instrument="notebook" @start-drag="startDrag">
<div class="drag-handle" data-tooltip="拖动把手可移动仪器位置" @pointerdown="startDrag(notebook, $event)">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<circle cx="12" cy="5" r="1" />
<circle cx="12" cy="12" r="1" />
<circle cx="12" cy="19" r="1" />
<circle cx="8" cy="5" r="1" />
<circle cx="8" cy="12" r="1" />
<circle cx="8" cy="19" r="1" />
<circle cx="16" cy="5" r="1" />
<circle cx="16" cy="12" r="1" />
<circle cx="16" cy="19" r="1" />
</svg>
</div>
<Notebook class="machine" :wiring="wiringConnected" <Notebook class="machine" :wiring="wiringConnected"
:osc="{ vdch1: oscStatus.vdch1, vdch2: oscStatus.vdch2, currentModeIndex: oscStatus.currentModeIndex, xyMode: oscStatus.xyMode, power: oscStatus.power }" :osc="{ vdch1: oscStatus.vdch1, vdch2: oscStatus.vdch2, currentModeIndex: oscStatus.currentModeIndex, xyMode: oscStatus.xyMode, power: oscStatus.power }"
:signal-frequency-k-hz="signalFrequencyKHz" :signal-frequency-k-hz="signalFrequencyKHz" :lissajous-line-streak="lissajousLineStreak" />
:lissajous-line-streak="lissajousLineStreak" /> </DraggableInstrumentWrapper>
</div>
<div class="instrument-wrapper" :style="oscilloscopeStyle"> <DraggableInstrumentWrapper :style="oscilloscopeStyle" :instrument="oscilloscope" @start-drag="startDrag">
<div class="drag-handle" data-tooltip="拖动把手可移动仪器位置" @pointerdown="startDrag(oscilloscope, $event)"> <Oscilloscope :distance="distance" :frequency="signalFrequencyKHz * 1000" :ch1-has-signal="oscCh1HasSignal"
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" :ch2-has-signal="oscCh2HasSignal" class="machine" @register-pin="registerPin"
viewBox="0 0 24 24" fill="none" stroke="currentColor" @unregister-pin="unregisterPin" @pin-click="onPinClick" @settings="onOscSettings"
stroke-width="2"> @lissajous-line="onLissajousLine" />
<circle cx="12" cy="5" r="1" /> </DraggableInstrumentWrapper>
<circle cx="12" cy="12" r="1" />
<circle cx="12" cy="19" r="1" />
<circle cx="8" cy="5" r="1" />
<circle cx="8" cy="12" r="1" />
<circle cx="8" cy="19" r="1" />
<circle cx="16" cy="5" r="1" />
<circle cx="16" cy="12" r="1" />
<circle cx="16" cy="19" r="1" />
</svg>
</div>
<Oscilloscope :distance="distance" :frequency="signalFrequencyKHz * 1000"
class="machine" @register-pin="registerPin"
@unregister-pin="unregisterPin" @pin-click="onPinClick"
@settings="onOscSettings" @lissajous-line="onLissajousLine" />
</div>
<div class="instrument-wrapper" :style="signalGenStyle"> <DraggableInstrumentWrapper :style="signalGenStyle" :instrument="signalGen" @start-drag="startDrag">
<div class="drag-handle" data-tooltip="拖动把手可移动仪器位置" @pointerdown="startDrag(signalGen, $event)"> <SignalGen v-model="signalFrequencyKHz" class="machine" @register-pin="registerPin"
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" @unregister-pin="unregisterPin" @pin-click="onPinClick" />
viewBox="0 0 24 24" fill="none" stroke="currentColor" </DraggableInstrumentWrapper>
stroke-width="2">
<circle cx="12" cy="5" r="1" />
<circle cx="12" cy="12" r="1" />
<circle cx="12" cy="19" r="1" />
<circle cx="8" cy="5" r="1" />
<circle cx="8" cy="12" r="1" />
<circle cx="8" cy="19" r="1" />
<circle cx="16" cy="5" r="1" />
<circle cx="16" cy="12" r="1" />
<circle cx="16" cy="19" r="1" />
</svg>
</div>
<SignalGen v-model="signalFrequencyKHz" class="machine"
@register-pin="registerPin" @unregister-pin="unregisterPin"
@pin-click="onPinClick" />
</div>
</div> </div>
</template> </template>
@@ -863,62 +879,10 @@ onBeforeUnmount(() => {
stroke-linejoin: round; stroke-linejoin: round;
} }
.instrument-wrapper {
position: absolute;
filter: drop-shadow(0 10px 10px black);
transition: filter 0.2s;
}
.machine { .machine {
pointer-events: auto; pointer-events: auto;
} }
.drag-handle {
touch-action: none;
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 24px;
background: linear-gradient(135deg, rgba(100, 100, 100, 0.9), rgba(60, 60, 60, 0.95));
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
cursor: grab;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), inset 0 1px 2px rgba(255, 255, 255, 0.2);
opacity: 0.7;
transition: all 0.2s;
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(4px);
}
.drag-handle:hover {
opacity: 1;
background: linear-gradient(135deg, rgba(120, 120, 120, 0.95), rgba(80, 80, 80, 1));
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.5), inset 0 1px 2px rgba(255, 255, 255, 0.3);
transform: translateX(-50%) scale(1.1);
}
.drag-handle:active {
cursor: grabbing;
background: linear-gradient(135deg, rgba(80, 80, 80, 1), rgba(50, 50, 50, 1));
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.6), inset 0 1px 2px rgba(255, 255, 255, 0.2);
}
.drag-handle svg {
width: 20px;
height: 20px;
color: rgba(255, 255, 255, 0.8);
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5));
}
.instrument-wrapper:hover .drag-handle {
opacity: 0.9;
}
.tooltip { .tooltip {
position: absolute; position: absolute;
z-index: 9000; z-index: 9000;
@@ -0,0 +1,104 @@
<script setup lang="ts">
import type { PropType } from 'vue';
interface InstrumentState {
left: number;
bottom: number;
baseScale: number;
zIndex: number;
}
const props = defineProps({
instrument: {
type: Object as PropType<InstrumentState>,
required: true,
},
tooltip: {
type: String,
default: '拖动把手可移动仪器位置',
},
});
const emit = defineEmits<{
(e: 'start-drag', instrument: InstrumentState, ev: PointerEvent): void;
}>();
const onPointerDown = (ev: PointerEvent) => {
emit('start-drag', props.instrument, ev);
};
</script>
<template>
<div class="instrument-wrapper">
<div class="drag-handle" :data-tooltip="tooltip" @pointerdown="onPointerDown">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<circle cx="12" cy="5" r="1" />
<circle cx="12" cy="12" r="1" />
<circle cx="12" cy="19" r="1" />
<circle cx="8" cy="5" r="1" />
<circle cx="8" cy="12" r="1" />
<circle cx="8" cy="19" r="1" />
<circle cx="16" cy="5" r="1" />
<circle cx="16" cy="12" r="1" />
<circle cx="16" cy="19" r="1" />
</svg>
</div>
<slot />
</div>
</template>
<style scoped>
.instrument-wrapper {
position: absolute;
filter: drop-shadow(0 10px 10px black);
transition: filter 0.2s;
}
.drag-handle {
touch-action: none;
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 24px;
background: linear-gradient(135deg, rgba(100, 100, 100, 0.9), rgba(60, 60, 60, 0.95));
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
cursor: grab;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), inset 0 1px 2px rgba(255, 255, 255, 0.2);
opacity: 0.7;
transition: all 0.2s;
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(4px);
}
.drag-handle:hover {
opacity: 1;
background: linear-gradient(135deg, rgba(120, 120, 120, 0.95), rgba(80, 80, 80, 1));
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.5), inset 0 1px 2px rgba(255, 255, 255, 0.3);
transform: translateX(-50%) scale(1.1);
}
.drag-handle:active {
cursor: grabbing;
background: linear-gradient(135deg, rgba(80, 80, 80, 1), rgba(50, 50, 50, 1));
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.6), inset 0 1px 2px rgba(255, 255, 255, 0.2);
}
.drag-handle svg {
width: 20px;
height: 20px;
color: rgba(255, 255, 255, 0.8);
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5));
}
.instrument-wrapper:hover .drag-handle {
opacity: 0.9;
}
</style>
+76 -19
View File
@@ -31,6 +31,15 @@ const props = defineProps({
// 声速,单位m/s // 声速,单位m/s
soundSpeed: { type: Number, default: 340 }, soundSpeed: { type: Number, default: 340 },
// 接线状态:控制通道是否真正有信号
// CH1:仅当 [信号发生器 发射端 波形] -> [示波器 CH1] 接入时为 true
ch1HasSignal: { type: Boolean, default: false },
// CH2:仅当三条线都接入时为 true
// [信号发生器 发射端 换能器] -> [换能器 左侧输入]
// [信号发生器 接收端 波形] -> [示波器 CH2]
// [信号发生器 接收端 换能器] -> [换能器 右侧输出]
ch2HasSignal: { type: Boolean, default: false },
}); });
const emit = defineEmits<{ const emit = defineEmits<{
@@ -147,8 +156,8 @@ function drawOscilloscope() {
const gainCH1 = getGain(vdch1.value); const gainCH1 = getGain(vdch1.value);
const gainCH2 = getGain(vdch2.value); const gainCH2 = getGain(vdch2.value);
// X-Y 模式下检测“李萨如图形出现直线”的进入事件 // X-Y 模式下检测“李萨如图形出现直线”的进入事件(需两路都有信号)
if (power.value && isXYMode.value) { if (power.value && isXYMode.value && props.ch1HasSignal && props.ch2HasSignal) {
const phase = phy.phaseRadTotal; const phase = phy.phaseRadTotal;
const isLineNow = Math.abs(Math.sin(phase)) < 0.10 && phy.resonanceFactor > 0.05; const isLineNow = Math.abs(Math.sin(phase)) < 0.10 && phy.resonanceFactor > 0.05;
if (isLineNow && !lastWasLine.value) { if (isLineNow && !lastWasLine.value) {
@@ -171,7 +180,8 @@ function drawOscilloscope() {
if (isXYMode.value) { if (isXYMode.value) {
// ================= X-Y Mode (李萨如图形) ================= // ================= X-Y Mode (李萨如图形) =================
const opacity = 0.3 + 0.7 * phy.resonanceFactor; const hasAnySignal = props.ch1HasSignal || props.ch2HasSignal;
const opacity = hasAnySignal ? (0.3 + 0.7 * phy.resonanceFactor) : 0.15;
ctx.strokeStyle = `rgba(0, 255, 0, ${opacity})`; ctx.strokeStyle = `rgba(0, 255, 0, ${opacity})`;
// 强辉光效果 - 多层叠加 // 强辉光效果 - 多层叠加
@@ -180,8 +190,8 @@ function drawOscilloscope() {
ctx.beginPath(); ctx.beginPath();
const phase = phy.phaseRadTotal; const phase = phy.phaseRadTotal;
const ampX = gainCH1; const ampX = props.ch1HasSignal ? gainCH1 : 0;
const ampY = phy.resonanceFactor * gainCH2; const ampY = props.ch2HasSignal ? (phy.resonanceFactor * gainCH2) : 0;
for (let t = 0; t <= 2 * Math.PI; t += 0.03) { for (let t = 0; t <= 2 * Math.PI; t += 0.03) {
const x = scale * ampX * Math.cos(t); const x = scale * ampX * Math.cos(t);
@@ -197,6 +207,12 @@ function drawOscilloscope() {
ctx.shadowBlur = 0; ctx.shadowBlur = 0;
// 两路都没信号时,画一个中心点(避免完全不可见)
if (!props.ch1HasSignal && !props.ch2HasSignal) {
ctx.fillStyle = 'rgba(0, 255, 0, 0.25)';
ctx.fillRect(cx - 1.5, cy - 1.5, 3, 3);
}
// 中心点装饰 // 中心点装饰
ctx.fillStyle = 'rgba(0, 255, 0, 0.3)'; ctx.fillStyle = 'rgba(0, 255, 0, 0.3)';
ctx.fillRect(cx - 1, cy - 5, 2, 10); ctx.fillRect(cx - 1, cy - 5, 2, 10);
@@ -218,18 +234,52 @@ function drawOscilloscope() {
ctx.lineWidth = 3; ctx.lineWidth = 3;
// CH1 (Source) - 黄色参考信号(在CH1、双踪、叠加模式下显示) // 叠加模式:直接绘制 CH1 + CH2 的合成波形(形状更复杂)
if (currentModeIndex.value === 0 || currentModeIndex.value === 2 || currentModeIndex.value === 3) { if (currentModeIndex.value === 3) {
const opacity = 0.45 + 0.55 * phy.resonanceFactor;
ctx.beginPath();
// 用 CH1 的黄色线条 + CH2 的绿色辉光,便于与单通道/双踪区分
ctx.strokeStyle = `rgba(255, 230, 0, ${opacity})`;
ctx.shadowBlur = 14 * (0.4 + 0.6 * phy.resonanceFactor);
ctx.shadowColor = '#00ff00';
const phaseShift = phy.phaseRadTotal;
const amp1 = gainCH1 * 0.8;
const amp2 = phy.resonanceFactor * gainCH2 * 0.8;
for (let x = 0; x < w; x += 2) {
const t = (x / pixPerCycle) * 2 * Math.PI;
const y1 = props.ch1HasSignal ? (amp1 * Math.sin(t + time)) : 0;
const y2 = props.ch2HasSignal ? (amp2 * Math.sin(t - phaseShift + time)) : 0;
const yVal = scale * (y1 + y2);
if (x === 0) ctx.moveTo(x, cy - yVal);
else ctx.lineTo(x, cy - yVal);
}
ctx.stroke();
// 第二层辉光
ctx.shadowBlur = 22 * (0.4 + 0.6 * phy.resonanceFactor);
ctx.stroke();
}
// CH1 (Source) - 黄色参考信号(在CH1、双踪模式下显示)
if (currentModeIndex.value === 0 || currentModeIndex.value === 2) {
ctx.beginPath(); ctx.beginPath();
ctx.strokeStyle = 'rgba(255, 230, 0, 0.9)'; ctx.strokeStyle = 'rgba(255, 230, 0, 0.9)';
ctx.shadowBlur = 12; ctx.shadowBlur = 12;
ctx.shadowColor = '#ffff00'; ctx.shadowColor = '#ffff00';
for (let x = 0; x < w; x += 2) { if (props.ch1HasSignal) {
const t = (x / pixPerCycle) * 2 * Math.PI; for (let x = 0; x < w; x += 2) {
const yVal = scale * gainCH1 * 0.8 * Math.sin(t + time); const t = (x / pixPerCycle) * 2 * Math.PI;
if (x === 0) ctx.moveTo(x, cy - yVal); const yVal = scale * gainCH1 * 0.8 * Math.sin(t + time);
else ctx.lineTo(x, cy - yVal); if (x === 0) ctx.moveTo(x, cy - yVal);
else ctx.lineTo(x, cy - yVal);
}
} else {
// 未接线:显示中心直线
ctx.moveTo(0, cy);
ctx.lineTo(w, cy);
} }
ctx.stroke(); ctx.stroke();
@@ -238,8 +288,8 @@ function drawOscilloscope() {
ctx.stroke(); ctx.stroke();
} }
// CH2 (Receiver) - 绿色接收信号(在CH2、双踪、叠加模式下显示) // CH2 (Receiver) - 绿色接收信号(在CH2、双踪模式下显示)
if (currentModeIndex.value === 1 || currentModeIndex.value === 2 || currentModeIndex.value === 3) { if (currentModeIndex.value === 1 || currentModeIndex.value === 2) {
const opacity = 0.3 + 0.7 * phy.resonanceFactor; const opacity = 0.3 + 0.7 * phy.resonanceFactor;
ctx.beginPath(); ctx.beginPath();
ctx.strokeStyle = `rgba(0, 255, 0, ${opacity})`; ctx.strokeStyle = `rgba(0, 255, 0, ${opacity})`;
@@ -249,11 +299,18 @@ function drawOscilloscope() {
const phaseShift = phy.phaseRadTotal; const phaseShift = phy.phaseRadTotal;
const ampY = phy.resonanceFactor * gainCH2 * 0.8; const ampY = phy.resonanceFactor * gainCH2 * 0.8;
for (let x = 0; x < w; x += 2) { if (props.ch2HasSignal) {
const t = (x / pixPerCycle) * 2 * Math.PI; for (let x = 0; x < w; x += 2) {
const yVal = scale * ampY * Math.sin(t - phaseShift + time); const t = (x / pixPerCycle) * 2 * Math.PI;
if (x === 0) ctx.moveTo(x, cy - yVal); const yVal = scale * ampY * Math.sin(t - phaseShift + time);
else ctx.lineTo(x, cy - yVal); if (x === 0) ctx.moveTo(x, cy - yVal);
else ctx.lineTo(x, cy - yVal);
}
} else {
// 未接线:显示中心直线
ctx.shadowBlur = 0;
ctx.moveTo(0, cy);
ctx.lineTo(w, cy);
} }
ctx.stroke(); ctx.stroke();
+8
View File
@@ -0,0 +1,8 @@
src\components\DeterminationMachine.vue -> 换能器
src\components\Oscilloscope.vue -> 示波器
src\components\SignalGen.vue -> 信号发生器
[信号发生器的 发射端 波形] 接 [示波器的 CH1 输入]
[信号发生器的 发射端 换能器] 接 [换能器 左侧输入]
[信号发生器的 接收端 波形] 接 [示波器的 CH2 输入]
[信号发生器的 接收端 换能器] 接 [换能器 右侧输出]