feat: 实现简易示波器功能
This commit is contained in:
0
src/components/Alert/Alert.vue
Normal file
0
src/components/Alert/Alert.vue
Normal file
0
src/components/Alert/index.ts
Normal file
0
src/components/Alert/index.ts
Normal file
172
src/components/Oscilloscope/WaveformDisplay.vue
Normal file
172
src/components/Oscilloscope/WaveformDisplay.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div class="w-full h-100">
|
||||
<v-chart v-if="true" class="w-full h-full" :option="option" autoresize />
|
||||
<div
|
||||
v-else
|
||||
class="w-full h-full flex items-center justify-center text-gray-500"
|
||||
>
|
||||
暂无数据
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, withDefaults } from "vue";
|
||||
import { forEach } from "lodash";
|
||||
import VChart from "vue-echarts";
|
||||
|
||||
// Echarts
|
||||
import { use } from "echarts/core";
|
||||
import { LineChart } from "echarts/charts";
|
||||
import {
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
ToolboxComponent,
|
||||
DataZoomComponent,
|
||||
GridComponent,
|
||||
} from "echarts/components";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
import type { ComposeOption } from "echarts/core";
|
||||
import type { LineSeriesOption } from "echarts/charts";
|
||||
import type {
|
||||
TitleComponentOption,
|
||||
TooltipComponentOption,
|
||||
LegendComponentOption,
|
||||
ToolboxComponentOption,
|
||||
DataZoomComponentOption,
|
||||
GridComponentOption,
|
||||
} from "echarts/components";
|
||||
|
||||
use([
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
ToolboxComponent,
|
||||
DataZoomComponent,
|
||||
GridComponent,
|
||||
LineChart,
|
||||
CanvasRenderer,
|
||||
]);
|
||||
|
||||
type EChartsOption = ComposeOption<
|
||||
| TitleComponentOption
|
||||
| TooltipComponentOption
|
||||
| LegendComponentOption
|
||||
| ToolboxComponentOption
|
||||
| DataZoomComponentOption
|
||||
| GridComponentOption
|
||||
| LineSeriesOption
|
||||
>;
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data?: {
|
||||
x: number[];
|
||||
y: number[][];
|
||||
};
|
||||
}>(),
|
||||
{
|
||||
data: () => ({
|
||||
x: [],
|
||||
y: [],
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const hasData = computed(() => {
|
||||
return (
|
||||
props.data &&
|
||||
props.data.x &&
|
||||
props.data.y &&
|
||||
props.data.x.length > 0 &&
|
||||
props.data.y.length > 0 &&
|
||||
props.data.y.some((channel) => channel.length > 0)
|
||||
);
|
||||
});
|
||||
|
||||
const option = computed((): EChartsOption => {
|
||||
const series: LineSeriesOption[] = [];
|
||||
|
||||
forEach(props.data.y, (yData, index) => {
|
||||
// 将 x 和 y 数据组合成 [x, y] 格式
|
||||
const seriesData = props.data.x.map((xValue, i) => [xValue, yData[i] || 0]);
|
||||
|
||||
series.push({
|
||||
type: "line",
|
||||
name: `通道 ${index + 1}`,
|
||||
data: seriesData,
|
||||
smooth: false, // 示波器通常显示原始波形
|
||||
symbol: "none", // 不显示数据点标记
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
grid: {
|
||||
left: "10%",
|
||||
right: "10%",
|
||||
top: "15%",
|
||||
bottom: "25%",
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
formatter: (params: any) => {
|
||||
let result = `时间: ${params[0].data[0].toFixed(2)} ms<br/>`;
|
||||
params.forEach((param: any) => {
|
||||
result += `${param.seriesName}: ${param.data[1].toFixed(3)} V<br/>`;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
top: "5%",
|
||||
data: series.map((s) => s.name) as string[],
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
restore: {},
|
||||
saveAsImage: {},
|
||||
},
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
xAxis: {
|
||||
type: "value",
|
||||
name: "时间 (ms)",
|
||||
nameLocation: "middle",
|
||||
nameGap: 30,
|
||||
axisLine: {
|
||||
show: true,
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
name: "电压 (V)",
|
||||
nameLocation: "middle",
|
||||
nameGap: 40,
|
||||
axisLine: {
|
||||
show: true,
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
series: series,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
26
src/components/Oscilloscope/index.ts
Normal file
26
src/components/Oscilloscope/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import WaveformDisplay from "./WaveformDisplay.vue";
|
||||
|
||||
// Test data generator
|
||||
const generateTestData = () => {
|
||||
const sampleRate = 1000; // 1kHz
|
||||
const duration = 0.1; // 10ms
|
||||
const points = Math.floor(sampleRate * duration);
|
||||
|
||||
const x = Array.from({ length: points }, (_, i) => i / sampleRate * 1000); // time in ms
|
||||
|
||||
// Generate multiple channels with different waveforms
|
||||
const y = [
|
||||
// Channel 1: Sine wave 50Hz
|
||||
Array.from({ length: points }, (_, i) => Math.sin(2 * Math.PI * 50 * i / sampleRate) * 3.3),
|
||||
// Channel 2: Square wave 25Hz
|
||||
Array.from({ length: points }, (_, i) => Math.sign(Math.sin(2 * Math.PI * 25 * i / sampleRate)) * 5),
|
||||
// Channel 3: Sawtooth wave 33Hz
|
||||
Array.from({ length: points }, (_, i) => (2 * ((33 * i / sampleRate) % 1) - 1) * 2.5),
|
||||
// Channel 4: Noise + DC offset
|
||||
Array.from({ length: points }, () => Math.random() * 0.5 + 1.5)
|
||||
];
|
||||
|
||||
return { x, y };
|
||||
};
|
||||
|
||||
export { WaveformDisplay, generateTestData };
|
||||
Reference in New Issue
Block a user