add: DDS virtual component
This commit is contained in:
339
src/components/equipments/DDS.vue
Normal file
339
src/components/equipments/DDS.vue
Normal file
@@ -0,0 +1,339 @@
|
||||
<template>
|
||||
<div class="dds-component" :style="{ width: width + 'px', height: height + 'px', position: 'relative' }">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
viewBox="0 0 300 200"
|
||||
class="dds-device"
|
||||
> <!-- 信号发生器外壳,扩大屏幕部分 -->
|
||||
<rect width="300" height="180" rx="10" ry="10" fill="#2a323c" stroke="#444" stroke-width="2" />
|
||||
|
||||
<!-- 信号发生器显示屏,扩大屏幕 -->
|
||||
<rect x="20" y="20" width="260" height="140" rx="5" ry="5" fill="#1a1f25" stroke="#555" stroke-width="1" />
|
||||
|
||||
<!-- 波形显示 -->
|
||||
<path :d="currentWaveformPath" stroke="lime" stroke-width="2" fill="none" />
|
||||
|
||||
<!-- 信息显示区域 -->
|
||||
<text x="30" y="40" fill="#0f0" font-size="14">{{ displayFrequency }}</text>
|
||||
<text x="200" y="40" fill="#0f0" font-size="14">φ: {{ phase }}°</text>
|
||||
</svg>
|
||||
|
||||
<!-- 输出引脚 -->
|
||||
<div
|
||||
v-for="pin in pins"
|
||||
:key="pin.pinId"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: `${pin.x * props.size}px`,
|
||||
top: `${pin.y * props.size}px`,
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}"
|
||||
: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 }"
|
||||
:label="pin.pinId"
|
||||
:constraint="pin.constraint"
|
||||
:pinId="pin.pinId"
|
||||
@pin-click="$emit('pin-click', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import Pin from './Pin.vue';
|
||||
|
||||
// 存储Pin引用
|
||||
const pinRefs = ref<Record<string, any>>({});
|
||||
|
||||
// DDS属性
|
||||
interface DDSProps {
|
||||
size?: number;
|
||||
pins?: {
|
||||
pinId: string;
|
||||
constraint: string;
|
||||
x: number;
|
||||
y: number;
|
||||
}[];
|
||||
frequency?: number;
|
||||
phase?: number;
|
||||
timebase?: number; // 添加时基属性
|
||||
waveform?: string;
|
||||
savedWaveforms?: string[];
|
||||
customWaveformPoints?: number[][];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<DDSProps>(), {
|
||||
size: 1,
|
||||
pins: () => [
|
||||
{ pinId: 'OUT', constraint: '', x: 300, y: 90 }, // 调整输出引脚位置
|
||||
],
|
||||
frequency: 1000,
|
||||
phase: 0,
|
||||
timebase: 1, // 默认时基为1
|
||||
waveform: 'sine',
|
||||
savedWaveforms: () => ['sine', 'square', 'triangle', 'sawtooth'],
|
||||
customWaveformPoints: () => []
|
||||
});
|
||||
|
||||
// 组件尺寸
|
||||
const width = computed(() => 300 * props.size);
|
||||
const height = computed(() => 180 * props.size); // 减小整体高度
|
||||
|
||||
// 波形状态
|
||||
const frequency = ref(props.frequency);
|
||||
const phase = ref(props.phase);
|
||||
const timebase = ref(props.timebase || 1); // 添加时基参数,默认为1
|
||||
const currentWaveformIndex = ref(0);
|
||||
const waveformNames = ['正弦波', '方波', '三角波', '锯齿波'];
|
||||
const waveforms = ['sine', 'square', 'triangle', 'sawtooth'];
|
||||
|
||||
// 波形函数集合
|
||||
interface WaveformFunction {
|
||||
(x: number, width: number, height: number, phaseRad: number): number;
|
||||
}
|
||||
|
||||
interface WaveformFunctions {
|
||||
[key: string]: WaveformFunction;
|
||||
}
|
||||
|
||||
const waveformFunctions: WaveformFunctions = {
|
||||
// 正弦波函数: sin(2π*x + φ)
|
||||
sine: (x: number, width: number, height: number, phaseRad: number): number => {
|
||||
return height/2 * Math.sin(2 * Math.PI * (x / width) * 2 + phaseRad);
|
||||
},
|
||||
|
||||
// 方波函数: 周期性的高低电平
|
||||
square: (x: number, width: number, height: number, phaseRad: number): number => {
|
||||
const normX = (x / width + phaseRad / (2 * Math.PI)) % 1;
|
||||
return normX < 0.5 ? height/4 : -height/4;
|
||||
},
|
||||
|
||||
// 三角波函数: 线性上升和下降
|
||||
triangle: (x: number, width: number, height: number, phaseRad: number): number => {
|
||||
const normX = (x / width + phaseRad / (2 * Math.PI)) % 1;
|
||||
return height/2 - height * Math.abs(2 * normX - 1);
|
||||
},
|
||||
|
||||
// 锯齿波函数: 线性上升,瞬间下降
|
||||
sawtooth: (x: number, width: number, height: number, phaseRad: number): number => {
|
||||
const normX = (x / width + phaseRad / (2 * Math.PI)) % 1;
|
||||
return height/2 - height/2 * (2 * normX);
|
||||
}
|
||||
};
|
||||
|
||||
// 计算当前显示频率
|
||||
const displayFrequency = computed(() => {
|
||||
if (frequency.value >= 1000000) {
|
||||
return `${(frequency.value / 1000000).toFixed(2)} MHz`;
|
||||
} else if (frequency.value >= 1000) {
|
||||
return `${(frequency.value / 1000).toFixed(2)} kHz`;
|
||||
} else {
|
||||
return `${frequency.value.toFixed(2)} Hz`;
|
||||
}
|
||||
});
|
||||
|
||||
// 格式化时基显示
|
||||
function formatTimebase(tb: number): string {
|
||||
if (tb < 0.1) {
|
||||
return `${(tb * 1000).toFixed(0)} ms/div`;
|
||||
} else if (tb < 1) {
|
||||
return `${(tb * 1000).toFixed(0)} ms/div`;
|
||||
} else {
|
||||
return `${tb.toFixed(1)} s/div`;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算当前显示时基
|
||||
const displayTimebase = computed(() => formatTimebase(timebase.value));
|
||||
|
||||
// 生成波形路径
|
||||
const currentWaveformPath = computed(() => {
|
||||
const width = 240;
|
||||
const height = 100; // 更大的波形显示高度,因为我们增加了屏幕高度
|
||||
const xOffset = 30;
|
||||
const yOffset = 50; // 上移位置以适应新布局
|
||||
const currentWaveform = waveforms[currentWaveformIndex.value];
|
||||
const phaseRadians = phase.value * Math.PI / 180;
|
||||
|
||||
// 时基和频率共同影响周期数量
|
||||
// 频率因素 - 频率越高,一个屏幕内显示的周期越多
|
||||
// 使用对数缩放可以更好地表示广泛范围的频率变化
|
||||
const freqLog = Math.log10(frequency.value) - 2; // 从100Hz开始作为基准
|
||||
const frequencyFactor = Math.max(0.1, Math.min(10, freqLog)); // 限制在合理范围内
|
||||
|
||||
// 时基影响周期数量 - 时基越小,显示的周期越多
|
||||
const timebaseFactor = 1 / timebase.value;
|
||||
|
||||
// 组合因素
|
||||
const scaleFactor = timebaseFactor * frequencyFactor;
|
||||
|
||||
let path = `M${xOffset},${yOffset + height/2}`;
|
||||
|
||||
// 使用波形函数生成路径
|
||||
const waveFunction = waveformFunctions[currentWaveform];
|
||||
|
||||
// 生成路径点
|
||||
for (let x = 0; x <= width; x++) {
|
||||
// 应用组合缩放因素 - 影响x轴的缩放
|
||||
const scaledX = x * scaleFactor;
|
||||
const y = waveFunction(scaledX, width, height, phaseRadians);
|
||||
path += ` L${x + xOffset},${yOffset + height/2 - y}`;
|
||||
}
|
||||
|
||||
return path;
|
||||
});
|
||||
|
||||
// 波形操作函数
|
||||
function selectWaveform(index: number) {
|
||||
currentWaveformIndex.value = index;
|
||||
}
|
||||
|
||||
function increaseFrequency() {
|
||||
if (frequency.value < 10) {
|
||||
frequency.value += 0.1;
|
||||
} else if (frequency.value < 100) {
|
||||
frequency.value += 1;
|
||||
} else if (frequency.value < 1000) {
|
||||
frequency.value += 10;
|
||||
} else if (frequency.value < 10000) {
|
||||
frequency.value += 100;
|
||||
} else if (frequency.value < 100000) {
|
||||
frequency.value += 1000;
|
||||
} else {
|
||||
frequency.value += 10000;
|
||||
}
|
||||
frequency.value = Math.min(frequency.value, 10000000); // 最大10MHz
|
||||
}
|
||||
|
||||
function decreaseFrequency() {
|
||||
if (frequency.value <= 10) {
|
||||
frequency.value -= 0.1;
|
||||
} else if (frequency.value <= 100) {
|
||||
frequency.value -= 1;
|
||||
} else if (frequency.value <= 1000) {
|
||||
frequency.value -= 10;
|
||||
} else if (frequency.value <= 10000) {
|
||||
frequency.value -= 100;
|
||||
} else if (frequency.value <= 100000) {
|
||||
frequency.value -= 1000;
|
||||
} else {
|
||||
frequency.value -= 10000;
|
||||
}
|
||||
frequency.value = Math.max(frequency.value, 0.1); // 最小0.1Hz
|
||||
frequency.value = parseFloat(frequency.value.toFixed(1)); // 修复浮点数精度问题
|
||||
}
|
||||
|
||||
function increasePhase() {
|
||||
phase.value += 15;
|
||||
if (phase.value >= 360) {
|
||||
phase.value -= 360;
|
||||
}
|
||||
}
|
||||
|
||||
function decreasePhase() {
|
||||
phase.value -= 15;
|
||||
if (phase.value < 0) {
|
||||
phase.value += 360;
|
||||
}
|
||||
}
|
||||
|
||||
// 监听props变化
|
||||
watch(
|
||||
() => props.frequency,
|
||||
(newValue) => {
|
||||
if (newValue !== undefined && newValue !== frequency.value) {
|
||||
frequency.value = newValue;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.phase,
|
||||
(newValue) => {
|
||||
if (newValue !== undefined && newValue !== phase.value) {
|
||||
phase.value = newValue;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.timebase,
|
||||
(newValue) => {
|
||||
if (newValue !== undefined && newValue !== timebase.value) {
|
||||
timebase.value = newValue;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.waveform,
|
||||
(newValue) => {
|
||||
if (newValue !== undefined) {
|
||||
const index = waveforms.indexOf(newValue);
|
||||
if (index !== -1) {
|
||||
currentWaveformIndex.value = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化波形类型
|
||||
if (props.waveform) {
|
||||
const index = waveforms.indexOf(props.waveform);
|
||||
if (index !== -1) {
|
||||
currentWaveformIndex.value = index;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化时基
|
||||
if (props.timebase !== undefined) {
|
||||
timebase.value = props.timebase;
|
||||
}
|
||||
});
|
||||
|
||||
// 暴露属性和方法
|
||||
defineExpose({
|
||||
frequency,
|
||||
phase,
|
||||
timebase,
|
||||
currentWaveformIndex,
|
||||
selectWaveform,
|
||||
increaseFrequency,
|
||||
decreaseFrequency,
|
||||
increasePhase,
|
||||
decreasePhase
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dds-component {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 导出默认属性函数供外部使用 -->
|
||||
<script lang="ts">
|
||||
export function getDefaultProps() {
|
||||
return {
|
||||
size: 1,
|
||||
pins: [
|
||||
{ pinId: 'OUT', constraint: '', x: 300, y: 90 }, // 调整输出引脚位置
|
||||
],
|
||||
frequency: 1000,
|
||||
phase: 0,
|
||||
timebase: 1, // 添加默认时基
|
||||
waveform: 'sine',
|
||||
savedWaveforms: ['sine', 'square', 'triangle', 'sawtooth'],
|
||||
customWaveformPoints: []
|
||||
};
|
||||
}
|
||||
</script>
|
||||
1137
src/components/equipments/DDSPropertyEditor.vue
Normal file
1137
src/components/equipments/DDSPropertyEditor.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user