feat: 完成逻辑分析仪前端设计

This commit is contained in:
2025-07-15 18:30:18 +08:00
parent b139542c4c
commit 9f25391540
6 changed files with 690 additions and 484 deletions

View File

@@ -1,17 +1,43 @@
<template>
<div class="w-full h-150">
<v-chart
v-if="data"
class="w-full h-full"
:option="option"
autoresize
<div
class="w-full"
:class="{
'h-48': !analyzer.logicData.value,
'h-150': analyzer.logicData.value,
}"
>
<v-chart
v-if="analyzer.logicData.value"
class="w-full h-full"
:option="option"
autoresize
:update-options="updateOptions"
/>
<div
v-else
class="w-full h-full flex items-center justify-center text-gray-500"
class="w-full h-full flex flex-col gap-6 items-center justify-center"
>
暂无数据
<div class="text-center">
<h3 class="text-xl font-semibold text-slate-600 mb-2">
暂无逻辑分析数据
</h3>
<p class="text-sm text-slate-500">点击下方按钮生成测试数据用于观察</p>
</div>
<button
class="group relative px-8 py-3 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 ease-in-out focus:outline-none focus:ring-4 focus:ring-blue-300 active:scale-95"
@click="handleGenerateTestData"
>
<span class="flex items-center gap-2">
<RefreshCcw
class="w-5 h-5 group-hover:rotate-180 transition-transform duration-300"
/>
生成测试数据
</span>
<div
class="absolute inset-0 bg-white opacity-0 group-hover:opacity-20 rounded-lg transition-opacity duration-200"
></div>
</button>
</div>
</div>
</template>
@@ -19,7 +45,8 @@
<script setup lang="ts">
import { computed, shallowRef } from "vue";
import VChart from "vue-echarts";
import type { LogicDataType } from "./index";
import { generateTestLogicData } from "./index";
import { RefreshCcw } from "lucide-vue-next";
// Echarts
import { use } from "echarts/core";
@@ -45,6 +72,9 @@ import type {
XAXisOption,
YAXisOption,
} from "echarts/types/dist/shared";
import { useLogicAnalyzerState } from "./LogicAnalyzerManager";
import { useRequiredInjection } from "@/utils/Common";
import { isUndefined } from "lodash";
use([
TooltipComponent,
@@ -65,24 +95,19 @@ type EChartsOption = ComposeOption<
| LineSeriesOption
>;
// Define props
interface Props {
data?: LogicDataType;
}
const props = defineProps<Props>();
const analyzer = useRequiredInjection(useLogicAnalyzerState);
// 添加更新选项来减少重绘
const updateOptions = shallowRef({
notMerge: false,
lazyUpdate: true,
silent: false
silent: false,
});
const option = computed((): EChartsOption => {
if (!props.data) return {};
if (isUndefined(analyzer.logicData.value)) return {};
const channelCount = props.data.y.length;
const channelCount = analyzer.logicData.value.y.length;
const channelSpacing = 2; // 每个通道之间的间距
// 使用单个网格
@@ -100,9 +125,12 @@ const option = computed((): EChartsOption => {
{
type: "category",
boundaryGap: false,
data: props.data!.x.map((x) => x.toFixed(3)),
data: analyzer.logicData.value.x.map((x) => x.toFixed(3)),
axisLabel: {
formatter: (value: string) => `${value}${props.data!.xUnit}`,
formatter: (value: string) =>
analyzer.logicData.value
? `${value}${analyzer.logicData.value.xUnit}`
: `${value}`,
},
},
];
@@ -117,8 +145,8 @@ const option = computed((): EChartsOption => {
axisLabel: {
formatter: (value: number) => {
const channelIndex = Math.round(value / channelSpacing);
return channelIndex < channelCount
? props.data!.channelNames[channelIndex]
return channelIndex < channelCount && analyzer.logicData.value
? analyzer.logicData.value.channelNames[channelIndex]
: "";
},
},
@@ -127,27 +155,33 @@ const option = computed((): EChartsOption => {
];
// 创建系列数据每个通道有不同的Y偏移
const series: LineSeriesOption[] = props.data.y.map((channelData, index) => ({
name: props.data!.channelNames[index],
type: "line",
data: channelData.map((value) => value + index * channelSpacing + 0.2),
step: "end",
lineStyle: {
width: 2,
},
areaStyle: {
opacity: 0.3,
origin: index * channelSpacing,
},
symbol: "none",
// 优化性能配置
sampling: "lttb",
// large: true,
// largeThreshold: 2000,
// progressive: 2000,
// 减少动画以避免闪烁
animation: false,
}));
const series: LineSeriesOption[] = analyzer.logicData.value.y.map(
(channelData: number[], index: number) => ({
name:
analyzer.logicData.value?.channelNames?.[index] ??
`Channel ${index + 1}`,
type: "line",
data: channelData.map(
(value: number) => value + index * channelSpacing + 0.2,
),
step: "end",
lineStyle: {
width: 2,
},
areaStyle: {
opacity: 0.3,
origin: index * channelSpacing,
},
symbol: "none",
// 优化性能配置
sampling: "lttb",
// large: true,
// largeThreshold: 2000,
// progressive: 2000,
// 减少动画以避免闪烁
animation: false,
}),
);
return {
// 全局动画配置
@@ -164,16 +198,21 @@ const option = computed((): EChartsOption => {
},
formatter: (params: any) => {
if (Array.isArray(params) && params.length > 0) {
const timeValue = props.data!.x[params[0].dataIndex];
const timeValue = analyzer.logicData.value!.x[params[0].dataIndex];
const dataIndex = params[0].dataIndex;
let tooltip = `Time: ${timeValue.toFixed(3)}${props.data!.xUnit}<br/>`;
let tooltip = `Time: ${timeValue.toFixed(3)}${analyzer.logicData.value!.xUnit}<br/>`;
// 显示所有通道在当前时间点的原始数值0或1
props.data!.channelNames.forEach((channelName, index) => {
const originalValue = props.data!.y[index][dataIndex];
tooltip += `${channelName}: ${originalValue}<br/>`;
});
if (analyzer.logicData.value) {
analyzer.logicData.value.channelNames.forEach(
(channelName: string, index: number) => {
const originalValue =
analyzer.logicData.value!.y[index][dataIndex];
tooltip += `${channelName}: ${originalValue}<br/>`;
},
);
}
return tooltip;
}
@@ -207,4 +246,8 @@ const option = computed((): EChartsOption => {
series: series,
};
});
function handleGenerateTestData() {
analyzer.logicData.value = generateTestLogicData();
}
</script>