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

This commit is contained in:
SikongJueluo 2025-07-15 18:30:18 +08:00
parent b139542c4c
commit 9f25391540
No known key found for this signature in database
6 changed files with 690 additions and 484 deletions

View File

@ -1,174 +0,0 @@
<template>
<div class="space-y-4">
<!-- 通道状态概览 -->
<div class="stats stats-horizontal bg-base-100 shadow flex justify-between">
<div class="stat">
<div class="stat-title">总通道数</div>
<div class="stat-value text-primary">8</div>
<div class="stat-desc">逻辑分析仪通道</div>
</div>
<div class="stat">
<div class="stat-title">启用通道</div>
<div class="stat-value text-success">{{ enabledChannelCount }}</div>
<div class="stat-desc">当前激活通道</div>
</div>
<div class="stat">
<div class="stat-title">采样率</div>
<div class="stat-value text-info">100MHz</div>
<div class="stat-desc">最大采样频率</div>
</div>
</div>
<!-- 通道配置列表 -->
<div class="space-y-2">
<div class="flex items-center justify-between p-2 bg-base-300 rounded-lg">
<span class="font-medium">通道</span>
<span class="font-medium">启用</span>
<span class="font-medium">标签</span>
<span class="font-medium">颜色</span>
</div>
<div
v-for="(channel, index) in channels"
:key="index"
class="flex items-center justify-between p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
>
<!-- 通道编号 -->
<div class="flex items-center gap-2">
<span class="font-mono font-medium w-12">CH{{ index }}</span>
<div
class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
:style="{ backgroundColor: channel.color }"
></div>
</div>
<!-- 启用开关 -->
<div class="form-control">
<input
type="checkbox"
v-model="channel.enabled"
class="toggle toggle-sm toggle-primary"
@change="updateChannelStatus(index)"
/>
</div>
<!-- 通道标签 -->
<div class="form-control w-32">
<input
type="text"
v-model="channel.label"
:placeholder="`通道 ${index}`"
class="input input-sm input-bordered w-full"
:disabled="!channel.enabled"
/>
</div>
<!-- 颜色选择 -->
<div class="form-control">
<input
type="color"
v-model="channel.color"
class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
:disabled="!channel.enabled"
/>
</div>
</div>
</div>
<!-- 批量操作 -->
<div class="flex gap-2 pt-4">
<button @click="enableAllChannels" class="btn btn-primary btn-sm">
全部启用
</button>
<button @click="disableAllChannels" class="btn btn-outline btn-sm">
全部禁用
</button>
<button @click="resetChannelLabels" class="btn btn-outline btn-sm">
重置标签
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, reactive } from "vue";
//
interface Channel {
enabled: boolean;
label: string;
color: string;
}
//
interface Preset {
name: string;
description: string;
channels: Partial<Channel>[];
}
//
const defaultColors = [
"#FF5733",
"#33FF57",
"#3357FF",
"#FF33F5",
"#F5FF33",
"#33FFF5",
"#FF8C33",
"#8C33FF",
];
//
const channels = reactive<Channel[]>(
Array.from({ length: 8 }, (_, index) => ({
enabled: false,
label: `CH${index}`,
color: defaultColors[index],
})),
);
//
const enabledChannelCount = computed(
() => channels.filter((channel) => channel.enabled).length,
);
//
const updateChannelStatus = (index: number) => {
console.log(`通道 ${index} 状态更新:`, channels[index].enabled);
};
//
const enableAllChannels = () => {
channels.forEach((channel) => {
channel.enabled = true;
});
};
//
const disableAllChannels = () => {
channels.forEach((channel) => {
channel.enabled = false;
});
};
//
const resetChannelLabels = () => {
channels.forEach((channel, index) => {
channel.label = `CH${index}`;
});
};
//
const applyPreset = (preset: Preset) => {
preset.channels.forEach((presetChannel, index) => {
if (index < channels.length && presetChannel) {
Object.assign(channels[index], presetChannel);
}
});
};
</script>

View File

@ -0,0 +1,224 @@
import { createInjectionState } from "@vueuse/core";
import { generateTestLogicData, type LogicDataType } from ".";
import { shallowRef, reactive, ref, computed } from "vue";
import {
CaptureConfig,
LogicAnalyzerClient,
GlobalCaptureMode,
SignalOperator,
SignalValue,
type SignalTriggerConfig,
} from "@/APIClient";
import { AuthManager } from "@/utils/AuthManager";
import { useAlertStore } from "@/components/Alert";
// 通道接口定义
export interface Channel {
enabled: boolean;
label: string;
color: string;
}
// 全局模式选项
const globalModes = [
{
value: GlobalCaptureMode.AND,
label: "AND",
description: "所有条件都满足时触发",
},
{
value: GlobalCaptureMode.OR,
label: "OR",
description: "任一条件满足时触发",
},
{ value: GlobalCaptureMode.NAND, label: "NAND", description: "AND的非" },
{ value: GlobalCaptureMode.NOR, label: "NOR", description: "OR的非" },
];
// 操作符选项
const operators = [
{ value: SignalOperator.Equal, label: "=" },
{ value: SignalOperator.NotEqual, label: "≠" },
{ value: SignalOperator.LessThan, label: "<" },
{ value: SignalOperator.LessThanOrEqual, label: "≤" },
{ value: SignalOperator.GreaterThan, label: ">" },
{ value: SignalOperator.GreaterThanOrEqual, label: "≥" },
];
// 信号值选项
const signalValues = [
{ value: SignalValue.Logic0, label: "0" },
{ value: SignalValue.Logic1, label: "1" },
{ value: SignalValue.NotCare, label: "X" },
{ value: SignalValue.Rise, label: "↑" },
{ value: SignalValue.Fall, label: "↓" },
{ value: SignalValue.RiseOrFall, label: "↕" },
{ value: SignalValue.NoChange, label: "—" },
{ value: SignalValue.SomeNumber, label: "#" },
];
// 默认颜色数组
const defaultColors = [
"#FF5733",
"#33FF57",
"#3357FF",
"#FF33F5",
"#F5FF33",
"#33FFF5",
"#FF8C33",
"#8C33FF",
];
const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
() => {
const logicData = shallowRef<LogicDataType>();
const alert = useAlertStore();
// 触发设置相关状态
const currentGlobalMode = ref<GlobalCaptureMode>(GlobalCaptureMode.AND);
const isApplying = ref(false);
// 通道配置
const channels = reactive<Channel[]>(
Array.from({ length: 8 }, (_, index) => ({
enabled: false,
label: `CH${index}`,
color: defaultColors[index],
})),
);
// 8个信号通道的配置
const signalConfigs = reactive(
Array.from({ length: 8 }, (_, index) => ({
signalIndex: index,
operator: SignalOperator.Equal,
value: SignalValue.Logic1,
})),
);
// 计算启用的通道数量
const enabledChannelCount = computed(
() => channels.filter((channel) => channel.enabled).length,
);
const enableAllChannels = () => {
channels.forEach((channel) => {
channel.enabled = true;
});
};
const disableAllChannels = () => {
channels.forEach((channel) => {
channel.enabled = false;
});
};
const setGlobalMode = async (mode: GlobalCaptureMode) => {
try {
const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
const success = await client.setGlobalTrigMode(mode);
if (success) {
currentGlobalMode.value = mode;
alert?.success(
`全局触发模式已设置为 ${globalModes.find((m) => m.value === mode)?.label}`,
3000,
);
} else {
throw new Error("设置失败");
}
} catch (error) {
console.error("设置全局触发模式失败:", error);
alert?.error("设置全局触发模式失败", 3000);
}
};
const applyConfiguration = async () => {
isApplying.value = true;
try {
const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
// 准备配置数据 - 只包含启用的通道
const enabledSignals = signalConfigs.filter(
(signal, index) => channels[index].enabled,
);
const config = new CaptureConfig({
globalMode: currentGlobalMode.value,
signalConfigs: enabledSignals.map(
(signal) =>
({
signalIndex: signal.signalIndex,
operator: signal.operator,
value: signal.value,
}) as SignalTriggerConfig,
),
});
// 发送配置
const success = await client.configureCapture(config);
if (success) {
const enabledChannelCount = channels.filter(
(ch) => ch.enabled,
).length;
alert?.success(
`配置已成功应用,启用了 ${enabledChannelCount} 个通道和触发条件`,
3000,
);
} else {
throw new Error("应用配置失败");
}
} catch (error) {
console.error("应用配置失败:", error);
alert?.error("应用配置失败,请检查设备连接", 3000);
} finally {
isApplying.value = false;
}
};
const resetConfiguration = () => {
currentGlobalMode.value = GlobalCaptureMode.AND;
channels.forEach((channel, index) => {
channel.enabled = false;
channel.label = `CH${index}`;
channel.color = defaultColors[index];
});
signalConfigs.forEach((signal) => {
signal.operator = SignalOperator.Equal;
signal.value = SignalValue.Logic1;
});
alert?.info("配置已重置", 2000);
};
return {
// 原有的逻辑数据
logicData,
// 触发设置状态
currentGlobalMode,
isApplying,
channels,
signalConfigs,
enabledChannelCount,
// 选项数据
globalModes,
operators,
signalValues,
// 触发设置方法
enableAllChannels,
disableAllChannels,
setGlobalMode,
applyConfiguration,
resetConfiguration,
};
},
);
export { useProvideLogicAnalyzer, useLogicAnalyzerState };

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/>`;
// 01
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>

View File

@ -1,266 +1,376 @@
<template>
<div class="space-y-4">
<!-- 全局触发模式配置 -->
<div class="form-control">
<label class="label">
<span class="label-text font-medium">全局触发模式</span>
</label>
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
<button
v-for="mode in globalModes"
:key="mode.value"
@click="setGlobalMode(mode.value)"
:class="[
'btn btn-sm',
currentGlobalMode === mode.value ? 'btn-primary' : 'btn-outline',
]"
>
{{ mode.label }}
</button>
<div class="space-y-6">
<!-- 通道状态概览 -->
<div class="stats stats-horizontal bg-base-100 shadow flex justify-between">
<div class="stat">
<div class="stat-title">总通道数</div>
<div class="stat-value text-primary">8</div>
<div class="stat-desc">逻辑分析仪通道</div>
</div>
<div class="label">
<span class="label-text-alt text-gray-500">
选择多路信号触发条件的逻辑组合方式
</span>
<div class="stat">
<div class="stat-title">启用通道</div>
<div class="stat-value text-success">{{ enabledChannelCount }}</div>
<div class="stat-desc">当前激活通道</div>
</div>
<div class="stat">
<div class="stat-title">采样率</div>
<div class="stat-value text-info">100MHz</div>
<div class="stat-desc">最大采样频率</div>
</div>
</div>
<!-- 信号触发配置 -->
<!-- 通道配置 -->
<div class="form-control">
<label class="label">
<span class="label-text font-medium">信号触发配置</span>
</label>
<div class="space-y-2">
<div
v-for="(signal, index) in signalConfigs"
:key="index"
class="flex items-center gap-2 p-3 bg-base-200 rounded-lg"
>
<span class="text-sm font-medium w-16">CH{{ index }}</span>
<!-- 操作符选择 -->
<!-- 全局触发模式选择 -->
<div class="flex flex-row justify-between my-4 mx-2">
<div class="flex flex-row gap-4">
<label class="label">
<span class="label-text text-sm">全局触发逻辑</span>
</label>
<select
v-model="signal.operator"
class="select select-sm select-bordered w-32"
>
<option v-for="op in operators" :key="op.value" :value="op.value">
{{ op.label }}
</option>
</select>
<!-- 信号值选择 -->
<select
v-model="signal.value"
class="select select-sm select-bordered w-32"
v-model="currentGlobalMode"
@change="setGlobalMode(currentGlobalMode)"
class="select select-sm select-bordered w-full"
>
<option
v-for="val in signalValues"
:key="val.value"
:value="val.value"
v-for="mode in globalModes"
:key="mode.value"
:value="mode.value"
>
{{ val.label }}
{{ mode.label }} - {{ mode.description }}
</option>
</select>
</div>
<!-- 启用/禁用开关 -->
<div class="form-control">
<label class="label cursor-pointer gap-2">
<span class="label-text text-sm">启用</span>
<input
type="checkbox"
v-model="signal.enabled"
class="toggle toggle-sm toggle-primary"
/>
</label>
<div class="flex flex-row gap-4">
<button @click="toggleAllChannels" class="btn btn-primary btn-sm">
{{ enabledChannelCount > 0 ? "全部禁用" : "全部启用" }}
</button>
<button
@click="applyConfiguration"
:disabled="isApplying"
class="btn btn-primary btn-sm"
>
<span
v-if="isApplying"
class="loading loading-spinner loading-sm"
></span>
应用配置
</button>
<button @click="resetConfiguration" class="btn btn-outline btn-sm">
重置
</button>
</div>
</div>
<!-- 通道列表 -->
<div class="space-y-2">
<!-- 表头 - 小屏幕单列时显示 -->
<div
class="flex items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium lg:hidden"
>
<span class="w-16">通道</span>
<span class="w-20">启用/触发</span>
<span class="w-32">标签</span>
<span class="w-16">颜色</span>
<span class="w-32">触发操作</span>
<span class="w-32">触发值</span>
</div>
<!-- 通道配置网格 -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<!-- 左列 (CH0-CH3) -->
<div class="space-y-2">
<!-- 左列表头 - 大屏幕时显示 -->
<div
class="hidden lg:flex items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
>
<span class="w-16">通道</span>
<span class="w-20">启用</span>
<span class="w-32">标签</span>
<span class="w-16">颜色</span>
<span class="w-32">触发操作</span>
<span class="w-32">触发值</span>
</div>
<!-- 左列通道 (0-3) -->
<div
v-for="(channel, index) in channels.slice(0, 4)"
:key="index"
class="flex items-center gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
>
<!-- 通道编号和颜色指示 -->
<div class="flex items-center gap-2 w-16">
<span class="font-mono font-medium">CH{{ index }}</span>
<div
class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
:style="{ backgroundColor: channel.color }"
></div>
</div>
<!-- 通道启用开关同时控制触发 -->
<div class="form-control w-20">
<input
type="checkbox"
v-model="channel.enabled"
class="toggle toggle-sm toggle-primary"
/>
</div>
<!-- 通道标签 -->
<div class="form-control w-32">
<input
type="text"
v-model="channel.label"
:placeholder="`通道 ${index}`"
class="input input-sm input-bordered w-full"
:disabled="!channel.enabled"
/>
</div>
<!-- 颜色选择 -->
<div class="form-control w-16">
<input
type="color"
v-model="channel.color"
class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
:disabled="!channel.enabled"
/>
</div>
<!-- 触发操作符选择 -->
<select
v-model="signalConfigs[index].operator"
class="select select-sm select-bordered w-32"
:disabled="!channel.enabled"
>
<option
v-for="op in operators"
:key="op.value"
:value="op.value"
>
{{ op.label }}
</option>
</select>
<!-- 触发信号值选择 -->
<select
v-model="signalConfigs[index].value"
class="select select-sm select-bordered w-32"
:disabled="!channel.enabled"
>
<option
v-for="val in signalValues"
:key="val.value"
:value="val.value"
>
{{ val.label }}
</option>
</select>
</div>
</div>
<!-- 右列 (CH4-CH7) - 仅在大屏幕显示 -->
<div class="hidden lg:block space-y-2">
<!-- 右列表头 -->
<div
class="flex items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
>
<span class="w-16">通道</span>
<span class="w-20">启用/触发</span>
<span class="w-32">标签</span>
<span class="w-16">颜色</span>
<span class="w-32">触发操作</span>
<span class="w-32">触发值</span>
</div>
<!-- 右列通道 (4-7) -->
<div
v-for="(channel, index) in channels.slice(4, 8)"
:key="index + 4"
class="flex items-center gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
>
<!-- 通道编号和颜色指示 -->
<div class="flex items-center gap-2 w-16">
<span class="font-mono font-medium">CH{{ index + 4 }}</span>
<div
class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
:style="{ backgroundColor: channel.color }"
></div>
</div>
<!-- 通道启用开关同时控制触发 -->
<div class="form-control w-20">
<input
type="checkbox"
v-model="channel.enabled"
class="toggle toggle-sm toggle-primary"
/>
</div>
<!-- 通道标签 -->
<div class="form-control w-32">
<input
type="text"
v-model="channel.label"
:placeholder="`通道 ${index + 4}`"
class="input input-sm input-bordered w-full"
:disabled="!channel.enabled"
/>
</div>
<!-- 颜色选择 -->
<div class="form-control w-16">
<input
type="color"
v-model="channel.color"
class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
:disabled="!channel.enabled"
/>
</div>
<!-- 触发操作符选择 -->
<select
v-model="signalConfigs[index + 4].operator"
class="select select-sm select-bordered w-32"
:disabled="!channel.enabled"
>
<option
v-for="op in operators"
:key="op.value"
:value="op.value"
>
{{ op.label }}
</option>
</select>
<!-- 触发信号值选择 -->
<select
v-model="signalConfigs[index + 4].value"
class="select select-sm select-bordered w-32"
:disabled="!channel.enabled"
>
<option
v-for="val in signalValues"
:key="val.value"
:value="val.value"
>
{{ val.label }}
</option>
</select>
</div>
</div>
<!-- 小屏幕时继续显示 CH4-CH7 -->
<div class="lg:hidden space-y-2">
<div
v-for="(channel, index) in channels.slice(4, 8)"
:key="index + 4"
class="flex items-center gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
>
<!-- 通道编号和颜色指示 -->
<div class="flex items-center gap-2 w-16">
<span class="font-mono font-medium">CH{{ index + 4 }}</span>
<div
class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
:style="{ backgroundColor: channel.color }"
></div>
</div>
<!-- 通道启用开关同时控制触发 -->
<div class="form-control w-20">
<input
type="checkbox"
v-model="channel.enabled"
class="toggle toggle-sm toggle-primary"
/>
</div>
<!-- 通道标签 -->
<div class="form-control w-32">
<input
type="text"
v-model="channel.label"
:placeholder="`通道 ${index + 4}`"
class="input input-sm input-bordered w-full"
:disabled="!channel.enabled"
/>
</div>
<!-- 颜色选择 -->
<div class="form-control w-16">
<input
type="color"
v-model="channel.color"
class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
:disabled="!channel.enabled"
/>
</div>
<!-- 触发操作符选择 -->
<select
v-model="signalConfigs[index + 4].operator"
class="select select-sm select-bordered w-32"
:disabled="!channel.enabled"
>
<option
v-for="op in operators"
:key="op.value"
:value="op.value"
>
{{ op.label }}
</option>
</select>
<!-- 触发信号值选择 -->
<select
v-model="signalConfigs[index + 4].value"
class="select select-sm select-bordered w-32"
:disabled="!channel.enabled"
>
<option
v-for="val in signalValues"
:key="val.value"
:value="val.value"
>
{{ val.label }}
</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="flex gap-2 pt-4">
<button
@click="applyConfiguration"
:disabled="isApplying"
class="btn btn-primary btn-sm"
>
<span
v-if="isApplying"
class="loading loading-spinner loading-sm"
></span>
应用配置
</button>
<button @click="resetConfiguration" class="btn btn-outline btn-sm">
重置
</button>
</div>
<!-- 状态指示 -->
<div
v-if="lastApplyResult"
class="alert alert-sm"
:class="lastApplyResult.success ? 'alert-success' : 'alert-error'"
>
<span>{{ lastApplyResult.message }}</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from "vue";
import { useEquipments } from "@/stores/equipments";
import {
CaptureConfig,
LogicAnalyzerClient,
GlobalCaptureMode,
SignalOperator,
SignalValue,
type SignalTriggerConfig,
} from "@/APIClient";
import { AuthManager } from "@/utils/AuthManager";
import { useRequiredInjection } from "@/utils/Common";
import { useLogicAnalyzerState } from "./LogicAnalyzerManager";
// 使
const equipments = useEquipments();
const {
currentGlobalMode,
isApplying,
channels,
signalConfigs,
enabledChannelCount,
globalModes,
operators,
signalValues,
enableAllChannels,
disableAllChannels,
setGlobalMode,
applyConfiguration,
resetConfiguration,
} = useRequiredInjection(useLogicAnalyzerState);
//
const currentGlobalMode = ref<GlobalCaptureMode>(GlobalCaptureMode.AND);
//
const isApplying = ref(false);
const lastApplyResult = ref<{ success: boolean; message: string } | null>(null);
//
const globalModes = [
{
value: GlobalCaptureMode.AND,
label: "AND",
description: "所有条件都满足时触发",
},
{
value: GlobalCaptureMode.OR,
label: "OR",
description: "任一条件满足时触发",
},
{ value: GlobalCaptureMode.NAND, label: "NAND", description: "AND的非" },
{ value: GlobalCaptureMode.NOR, label: "NOR", description: "OR的非" },
];
//
const operators = [
{ value: SignalOperator.Equal, label: "=" },
{ value: SignalOperator.NotEqual, label: "≠" },
{ value: SignalOperator.LessThan, label: "<" },
{ value: SignalOperator.LessThanOrEqual, label: "≤" },
{ value: SignalOperator.GreaterThan, label: ">" },
{ value: SignalOperator.GreaterThanOrEqual, label: "≥" },
];
//
const signalValues = [
{ value: SignalValue.Logic0, label: "0" },
{ value: SignalValue.Logic1, label: "1" },
{ value: SignalValue.NotCare, label: "X" },
{ value: SignalValue.Rise, label: "↑" },
{ value: SignalValue.Fall, label: "↓" },
{ value: SignalValue.RiseOrFall, label: "↕" },
{ value: SignalValue.NoChange, label: "—" },
{ value: SignalValue.SomeNumber, label: "#" },
];
// 8
const signalConfigs = reactive(
Array.from({ length: 8 }, (_, index) => ({
signalIndex: index,
operator: SignalOperator.Equal,
value: SignalValue.Logic1,
enabled: false,
})),
);
//
const setGlobalMode = async (mode: GlobalCaptureMode) => {
try {
const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
const success = await client.setGlobalTrigMode(mode);
if (success) {
currentGlobalMode.value = mode;
lastApplyResult.value = {
success: true,
message: `全局触发模式已设置为 ${globalModes.find((m) => m.value === mode)?.label}`,
};
} else {
throw new Error("设置失败");
}
} catch (error) {
console.error("设置全局触发模式失败:", error);
lastApplyResult.value = {
success: false,
message: "设置全局触发模式失败",
};
const toggleAllChannels = () => {
if (enabledChannelCount.value > 0) {
disableAllChannels();
} else {
enableAllChannels();
}
};
//
const applyConfiguration = async () => {
isApplying.value = true;
lastApplyResult.value = null;
try {
const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
//
const enabledSignals = signalConfigs.filter((signal) => signal.enabled);
const config = new CaptureConfig({
globalMode: currentGlobalMode.value,
signalConfigs: enabledSignals.map(
(signal) =>
({
signalIndex: signal.signalIndex,
operator: signal.operator,
value: signal.value,
}) as SignalTriggerConfig,
),
});
//
const success = await client.configureCapture(config);
if (success) {
lastApplyResult.value = {
success: true,
message: `配置已成功应用,启用了 ${enabledSignals.length} 个通道`,
};
} else {
throw new Error("应用配置失败");
}
} catch (error) {
console.error("应用配置失败:", error);
lastApplyResult.value = {
success: false,
message: "应用配置失败,请检查设备连接",
};
} finally {
isApplying.value = false;
}
};
//
const resetConfiguration = () => {
currentGlobalMode.value = GlobalCaptureMode.AND;
signalConfigs.forEach((signal) => {
signal.operator = SignalOperator.Equal;
signal.value = SignalValue.Logic1;
signal.enabled = false;
});
lastApplyResult.value = null;
};
//
setTimeout(() => {
if (lastApplyResult.value) {
lastApplyResult.value = null;
}
}, 5000);
</script>

View File

@ -1,3 +1,4 @@
import { exp } from "mathjs";
import LogicalWaveFormDisplay from "./LogicalWaveFormDisplay.vue";
type LogicDataType = {
@ -75,4 +76,4 @@ function generateTestLogicData(): LogicDataType {
export { LogicalWaveFormDisplay, generateTestLogicData, type LogicDataType };
export { default as TriggerSettings } from './TriggerSettings.vue'
export { default as ChannelConfig } from './ChannelConfig.vue'
export { useProvideLogicAnalyzer , useLogicAnalyzerState } from './LogicAnalyzerManager.ts'

View File

@ -1,18 +1,25 @@
<template>
<div class="bg-base-100 flex flex-col">
<div class="bg-base-100 flex flex-col gap-10 mb-5">
<!-- 逻辑信号展示 -->
<div class="card bg-base-200 shadow-xl">
<div class="card-body">
<h2 class="card-title">
<Zap class="w-5 h-5" />
逻辑信号分析
<h2 class="card-title flex justify-between items-center">
<div class="flex items-center gap-2">
<Zap class="w-5 h-5" />
逻辑信号分析
</div>
<div class="flex items-center gap-2">
<button class="btn btn-sm btn-error" @click="handleDeleteData">
清空
</button>
</div>
</h2>
<LogicalWaveFormDisplay :data="generateTestLogicData()" />
<LogicalWaveFormDisplay />
</div>
</div>
<!-- 触发设置 -->
<div class="card bg-base-200 shadow-xl mt-4">
<div class="card bg-base-200 shadow-xl">
<div class="card-body">
<h2 class="card-title">
<Settings class="w-5 h-5" />
@ -21,29 +28,24 @@
<TriggerSettings />
</div>
</div>
<!-- 通道配置 -->
<div class="card bg-base-200 shadow-xl mt-4">
<div class="card-body">
<h2 class="card-title">
<Layers class="w-5 h-5" />
通道配置
</h2>
<ChannelConfig />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Zap, Settings, Layers } from "lucide-vue-next";
import { useEquipments } from "@/stores/equipments";
import {
LogicalWaveFormDisplay,
import {
LogicalWaveFormDisplay,
generateTestLogicData,
TriggerSettings,
ChannelConfig
} from "@/components/LogicAnalyzer";
import { useProvideLogicAnalyzer } from "@/components/LogicAnalyzer";
const analyzer = useProvideLogicAnalyzer();
function handleDeleteData() {
analyzer.logicData.value = undefined;
}
// 使
const equipments = useEquipments();