479 lines
21 KiB
Vue
479 lines
21 KiB
Vue
<template>
|
|
<div class="space-y-6">
|
|
<!-- 通道配置 -->
|
|
<div class="form-control">
|
|
<!-- 全局触发模式选择和通道组配置 -->
|
|
<div class="flex flex-col gap-6 my-4 mx-2">
|
|
<div class="flex flex-col lg:flex-row gap-6">
|
|
<div class="flex flex-col gap-2">
|
|
<label class="block text-sm font-semibold antialiased">
|
|
全局触发逻辑
|
|
</label>
|
|
<div class="relative w-[200px]">
|
|
<button
|
|
tabindex="0"
|
|
type="button"
|
|
class="flex items-center gap-4 justify-between h-max outline-none focus:outline-none bg-transparent ring-transparent border border-slate-200 transition-all duration-300 ease-in disabled:opacity-50 disabled:pointer-events-none select-none text-start text-sm rounded-md py-2 px-2.5 ring shadow-sm hover:border-slate-800 hover:ring-slate-800/10 focus:border-slate-800 focus:ring-slate-800/10 w-full"
|
|
@click="toggleGlobalModeDropdown"
|
|
:aria-expanded="showGlobalModeDropdown"
|
|
aria-haspopup="listbox"
|
|
role="combobox"
|
|
>
|
|
<span>{{ currentGlobalModeLabel }}</span>
|
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="currentColor" class="h-[1em] w-[1em] translate-x-0.5 stroke-[1.5]">
|
|
<path d="M17 8L12 3L7 8" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
<path d="M17 16L12 21L7 16" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
</svg>
|
|
</button>
|
|
<input readonly style="display:none" :value="currentGlobalMode" />
|
|
<!-- 下拉菜单 -->
|
|
<div v-if="showGlobalModeDropdown" class="absolute top-full left-0 right-0 mt-1 bg-white border border-slate-200 rounded-md shadow-lg z-50">
|
|
<div
|
|
v-for="mode in globalModes"
|
|
:key="mode.value"
|
|
@click="selectGlobalMode(mode.value)"
|
|
class="px-3 py-2 text-sm text-slate-700 hover:bg-slate-100 cursor-pointer"
|
|
:class="{ 'bg-slate-100': mode.value === currentGlobalMode }"
|
|
>
|
|
{{ mode.label }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="flex items-center text-xs text-slate-400">
|
|
{{ currentGlobalModeDescription }}
|
|
</p>
|
|
</div>
|
|
<div class="flex flex-col gap-2">
|
|
<label class="block text-sm font-semibold antialiased">
|
|
通道组
|
|
</label>
|
|
<div class="relative w-[200px]">
|
|
<button
|
|
tabindex="0"
|
|
type="button"
|
|
class="flex items-center gap-4 justify-between h-max outline-none focus:outline-none bg-transparent ring-transparent border border-slate-200 transition-all duration-300 ease-in disabled:opacity-50 disabled:pointer-events-none select-none text-start text-sm rounded-md py-2 px-2.5 ring shadow-sm hover:border-slate-800 hover:ring-slate-800/10 focus:border-slate-800 focus:ring-slate-800/10 w-full"
|
|
@click="toggleChannelDivDropdown"
|
|
:aria-expanded="showChannelDivDropdown"
|
|
aria-haspopup="listbox"
|
|
role="combobox"
|
|
>
|
|
<span>{{ currentChannelDivLabel }}</span>
|
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="currentColor" class="h-[1em] w-[1em] translate-x-0.5 stroke-[1.5]">
|
|
<path d="M17 8L12 3L7 8" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
<path d="M17 16L12 21L7 16" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
</svg>
|
|
</button>
|
|
<input readonly style="display:none" :value="currentChannelDiv" />
|
|
<!-- 下拉菜单 -->
|
|
<div v-if="showChannelDivDropdown" class="absolute top-full left-0 right-0 mt-1 bg-white border border-slate-200 rounded-md shadow-lg z-50">
|
|
<div
|
|
v-for="option in channelDivOptions"
|
|
:key="option.value"
|
|
@click="selectChannelDiv(option.value)"
|
|
class="px-3 py-2 text-sm text-slate-700 hover:bg-slate-100 cursor-pointer"
|
|
:class="{ 'bg-slate-100': option.value === currentChannelDiv }"
|
|
>
|
|
{{ option.label }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="flex items-center text-xs text-slate-400">
|
|
{{ currentChannelDivDescription }}
|
|
</p>
|
|
</div>
|
|
<div class="flex flex-col gap-2">
|
|
<label class="block text-sm font-semibold antialiased text-slate-800">
|
|
采样频率
|
|
</label>
|
|
<div class="relative w-[200px]">
|
|
<button
|
|
tabindex="0"
|
|
type="button"
|
|
class="flex items-center gap-4 justify-between h-max outline-none focus:outline-none text-slate-600 bg-transparent ring-transparent border border-slate-200 transition-all duration-300 ease-in disabled:opacity-50 disabled:pointer-events-none select-none text-start text-sm rounded-md py-2 px-2.5 ring shadow-sm hover:border-slate-800 hover:ring-slate-800/10 focus:border-slate-800 focus:ring-slate-800/10 w-full"
|
|
@click="toggleClockDivDropdown"
|
|
:aria-expanded="showClockDivDropdown"
|
|
aria-haspopup="listbox"
|
|
role="combobox"
|
|
>
|
|
<span>{{ currentClockDivLabel }}</span>
|
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="currentColor" class="h-[1em] w-[1em] translate-x-0.5 stroke-[1.5]">
|
|
<path d="M17 8L12 3L7 8" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
<path d="M17 16L12 21L7 16" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
</svg>
|
|
</button>
|
|
<input readonly style="display:none" :value="currentclockDiv" />
|
|
<!-- 下拉菜单 -->
|
|
<div v-if="showClockDivDropdown" class="absolute top-full left-0 right-0 mt-1 bg-white border border-slate-200 rounded-md shadow-lg z-50">
|
|
<div
|
|
v-for="option in ClockDivOptions"
|
|
:key="option.value"
|
|
@click="selectClockDiv(option.value)"
|
|
class="px-3 py-2 text-sm text-slate-700 hover:bg-slate-100 cursor-pointer"
|
|
:class="{ 'bg-slate-100': option.value === currentclockDiv }"
|
|
>
|
|
{{ option.label }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="flex items-center text-xs text-slate-400">
|
|
{{ currentClockDivDescription }}
|
|
</p>
|
|
</div>
|
|
<div class="flex flex-col gap-2">
|
|
<label class="block text-sm font-semibold antialiased">
|
|
捕获深度
|
|
</label>
|
|
<div class="relative w-[200px]">
|
|
<button
|
|
@click="decreaseCaptureLength"
|
|
class="absolute right-9 top-1 rounded-md border border-transparent p-1.5 text-center text-sm transition-all hover:bg-slate-100 focus:bg-slate-100 active:bg-slate-100 disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
|
|
type="button"
|
|
:disabled="captureLength <= CAPTURE_LENGTH_MIN"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4">
|
|
<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
|
|
</svg>
|
|
</button>
|
|
<input
|
|
v-model.number="captureLength"
|
|
@change="handleCaptureLengthChange"
|
|
type="number"
|
|
:min="CAPTURE_LENGTH_MIN"
|
|
:max="CAPTURE_LENGTH_MAX"
|
|
class="w-full bg-transparent placeholder:text-sm border border-slate-200 rounded-md pl-3 pr-20 py-2 transition duration-300 ease focus:outline-none focus:border-slate-400 ring ring-transparent hover:ring-slate-800/10 focus:ring-slate-800/10 hover:border-slate-800 shadow-sm focus:shadow appearance-none [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
|
|
:placeholder="CAPTURE_LENGTH_MIN.toString()"
|
|
/>
|
|
<button
|
|
@click="increaseCaptureLength"
|
|
class="absolute right-1 top-1 rounded-md border border-transparent p-1.5 text-center text-sm transition-all hover:bg-slate-100 focus:bg-slate-100 active:bg-slate-100 disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
|
|
type="button"
|
|
:disabled="captureLength >= CAPTURE_LENGTH_MAX"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4">
|
|
<path d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<p class="flex items-center text-xs text-slate-400">
|
|
范围: {{ CAPTURE_LENGTH_MIN.toLocaleString() }} - {{ CAPTURE_LENGTH_MAX.toLocaleString() }}
|
|
</p>
|
|
</div>
|
|
<div class="flex flex-col gap-2">
|
|
<label class="block text-sm font-semibold antialiased">
|
|
预捕获深度
|
|
</label>
|
|
<div class="relative w-[200px]">
|
|
<button
|
|
@click="decreasePreCaptureLength"
|
|
class="absolute right-9 top-1 rounded-md border border-transparent p-1.5 text-center text-sm transition-all hover:bg-slate-100 focus:bg-slate-100 active:bg-slate-100 disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
|
|
type="button"
|
|
:disabled="preCaptureLength <= PRE_CAPTURE_LENGTH_MIN"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4">
|
|
<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
|
|
</svg>
|
|
</button>
|
|
<input
|
|
v-model.number="preCaptureLength"
|
|
@change="handlePreCaptureLengthChange"
|
|
type="number"
|
|
:min="PRE_CAPTURE_LENGTH_MIN"
|
|
:max="Math.max(0, captureLength - 1)"
|
|
class="w-full bg-transparent placeholder:text-sm border border-slate-200 rounded-md pl-3 pr-20 py-2 transition duration-300 ease focus:outline-none focus:border-slate-400 ring ring-transparent hover:ring-slate-800/10 focus:ring-slate-800/10 hover:border-slate-800 shadow-sm focus:shadow appearance-none [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
|
|
:placeholder="PRE_CAPTURE_LENGTH_MIN.toString()"
|
|
/>
|
|
<button
|
|
@click="increasePreCaptureLength"
|
|
class="absolute right-1 top-1 rounded-md border border-transparent p-1.5 text-center text-sm transition-all hover:bg-slate-100 focus:bg-slate-100 active:bg-slate-100 disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
|
|
type="button"
|
|
:disabled="preCaptureLength >= Math.max(0, captureLength - 1)"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4">
|
|
<path d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<p class="flex items-center text-xs text-slate-400">
|
|
范围: {{ PRE_CAPTURE_LENGTH_MIN }} - {{ Math.max(0, captureLength - 1) }}
|
|
</p>
|
|
</div>
|
|
<div class="flex flex-col gap-2">
|
|
<label class="block text-sm font-semibold antialiased">
|
|
重置配置
|
|
</label>
|
|
<div class="relative w-[200px]">
|
|
<button
|
|
@click="resetConfiguration"
|
|
class="w-10 h-10 bg-transparent text-red-600 text-sm border border-red-200 rounded-md py-2 px-2.5 transition duration-300 ease ring ring-transparent hover:ring-red-600/10 focus:ring-red-600/10 hover:border-red-600 shadow-sm focus:shadow flex items-center justify-center"
|
|
type="button"
|
|
title="重置配置"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" class="w-4 h-4">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<p class="flex items-center text-xs text-slate-400">
|
|
恢复所有设置到默认值
|
|
</p>
|
|
</div>
|
|
</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"
|
|
>
|
|
<span class="w-16">通道</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 xl:grid-cols-3 2xl:grid-cols-4 gap-4">
|
|
<div
|
|
v-for="(channel, index) in channels.filter(ch => ch.enabled)"
|
|
: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{{ channels.indexOf(channel) }}</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-32">
|
|
<input
|
|
type="text"
|
|
v-model="channel.label"
|
|
:placeholder="`通道 ${channels.indexOf(channel)}`"
|
|
class="input input-sm input-bordered w-full"
|
|
/>
|
|
</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"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 触发操作符选择 -->
|
|
<select
|
|
v-model="signalConfigs[channels.indexOf(channel)].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="signalConfigs[channels.indexOf(channel)].value"
|
|
class="select select-sm select-bordered w-32"
|
|
>
|
|
<option
|
|
v-for="val in signalValues"
|
|
:key="val.value"
|
|
:value="val.value"
|
|
>
|
|
{{ val.label }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 当没有启用通道时的提示 -->
|
|
<div v-if="enabledChannelCount === 0" class="text-center py-8 text-base-content/60">
|
|
<p class="text-lg font-medium">未启用任何通道</p>
|
|
<p class="text-sm">请选择通道组来配置逻辑分析仪</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref, onMounted, onUnmounted } from "vue";
|
|
import { useRequiredInjection } from "@/utils/Common";
|
|
import { useLogicAnalyzerState } from "./LogicAnalyzerManager";
|
|
|
|
const {
|
|
currentGlobalMode,
|
|
currentChannelDiv,
|
|
currentclockDiv,
|
|
captureLength,
|
|
preCaptureLength,
|
|
isApplying,
|
|
channels,
|
|
signalConfigs,
|
|
enabledChannelCount,
|
|
globalModes,
|
|
operators,
|
|
signalValues,
|
|
channelDivOptions,
|
|
ClockDivOptions,
|
|
CAPTURE_LENGTH_MIN,
|
|
CAPTURE_LENGTH_MAX,
|
|
PRE_CAPTURE_LENGTH_MIN,
|
|
validateCaptureLength,
|
|
validatePreCaptureLength,
|
|
setCaptureLength,
|
|
setPreCaptureLength,
|
|
setChannelDiv,
|
|
setGlobalMode,
|
|
setClockDiv,
|
|
resetConfiguration,
|
|
} = useRequiredInjection(useLogicAnalyzerState);
|
|
|
|
// 下拉菜单状态
|
|
const showGlobalModeDropdown = ref(false);
|
|
const showChannelDivDropdown = ref(false);
|
|
const showClockDivDropdown = ref(false);
|
|
|
|
// 处理捕获深度变化
|
|
const handleCaptureLengthChange = () => {
|
|
setCaptureLength(captureLength.value);
|
|
};
|
|
|
|
// 处理预捕获深度变化
|
|
const handlePreCaptureLengthChange = () => {
|
|
setPreCaptureLength(preCaptureLength.value);
|
|
};
|
|
|
|
// 增加捕获深度
|
|
const increaseCaptureLength = () => {
|
|
const newValue = Math.min(captureLength.value + 1024, CAPTURE_LENGTH_MAX);
|
|
setCaptureLength(newValue);
|
|
};
|
|
|
|
// 减少捕获深度
|
|
const decreaseCaptureLength = () => {
|
|
const newValue = Math.max(captureLength.value - 1024, CAPTURE_LENGTH_MIN);
|
|
setCaptureLength(newValue);
|
|
};
|
|
|
|
// 增加预捕获深度
|
|
const increasePreCaptureLength = () => {
|
|
const maxValue = Math.max(0, captureLength.value - 1);
|
|
const newValue = Math.min(preCaptureLength.value + 64, maxValue);
|
|
setPreCaptureLength(newValue);
|
|
};
|
|
|
|
// 减少预捕获深度
|
|
const decreasePreCaptureLength = () => {
|
|
const newValue = Math.max(preCaptureLength.value - 64, PRE_CAPTURE_LENGTH_MIN);
|
|
setPreCaptureLength(newValue);
|
|
};
|
|
|
|
// 计算属性:获取当前全局模式的标签
|
|
const currentGlobalModeLabel = computed(() => {
|
|
const mode = globalModes.find(m => m.value === currentGlobalMode.value);
|
|
return mode ? mode.label : '';
|
|
});
|
|
|
|
// 计算属性:获取当前全局模式的描述
|
|
const currentGlobalModeDescription = computed(() => {
|
|
const mode = globalModes.find(m => m.value === currentGlobalMode.value);
|
|
return mode ? mode.description : '';
|
|
});
|
|
|
|
// 计算属性:获取当前通道组的标签
|
|
const currentChannelDivLabel = computed(() => {
|
|
const option = channelDivOptions.find(opt => opt.value === currentChannelDiv.value);
|
|
return option ? option.label : '';
|
|
});
|
|
|
|
// 计算属性:获取当前通道组的描述
|
|
const currentChannelDivDescription = computed(() => {
|
|
const option = channelDivOptions.find(opt => opt.value === currentChannelDiv.value);
|
|
return option ? option.description : '';
|
|
});
|
|
|
|
// 计算属性:获取当前采样频率的标签
|
|
const currentClockDivLabel = computed(() => {
|
|
const option = ClockDivOptions.find(opt => opt.value === currentclockDiv.value);
|
|
return option ? option.label : '';
|
|
});
|
|
|
|
// 计算属性:获取当前采样频率的描述
|
|
const currentClockDivDescription = computed(() => {
|
|
const option = ClockDivOptions.find(opt => opt.value === currentclockDiv.value);
|
|
return option ? option.description : '';
|
|
});
|
|
|
|
// 全局模式下拉菜单相关函数
|
|
const toggleGlobalModeDropdown = () => {
|
|
showGlobalModeDropdown.value = !showGlobalModeDropdown.value;
|
|
if (showGlobalModeDropdown.value) {
|
|
showChannelDivDropdown.value = false;
|
|
showClockDivDropdown.value = false;
|
|
}
|
|
};
|
|
|
|
const selectGlobalMode = (mode: any) => {
|
|
setGlobalMode(mode);
|
|
showGlobalModeDropdown.value = false;
|
|
};
|
|
|
|
// 通道组下拉菜单相关函数
|
|
const toggleChannelDivDropdown = () => {
|
|
showChannelDivDropdown.value = !showChannelDivDropdown.value;
|
|
if (showChannelDivDropdown.value) {
|
|
showGlobalModeDropdown.value = false;
|
|
showClockDivDropdown.value = false;
|
|
}
|
|
};
|
|
|
|
const selectChannelDiv = (value: number) => {
|
|
setChannelDiv(value);
|
|
showChannelDivDropdown.value = false;
|
|
};
|
|
|
|
// 采样频率下拉菜单相关函数
|
|
const toggleClockDivDropdown = () => {
|
|
showClockDivDropdown.value = !showClockDivDropdown.value;
|
|
if (showClockDivDropdown.value) {
|
|
showGlobalModeDropdown.value = false;
|
|
showChannelDivDropdown.value = false;
|
|
}
|
|
};
|
|
|
|
const selectClockDiv = (value: any) => {
|
|
setClockDiv(value);
|
|
showClockDivDropdown.value = false;
|
|
};
|
|
|
|
// 点击其他地方关闭下拉菜单
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
const target = event.target as HTMLElement;
|
|
if (!target.closest('.relative')) {
|
|
showGlobalModeDropdown.value = false;
|
|
showChannelDivDropdown.value = false;
|
|
showClockDivDropdown.value = false;
|
|
}
|
|
};
|
|
|
|
onMounted(() => {
|
|
document.addEventListener('click', handleClickOutside);
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
document.removeEventListener('click', handleClickOutside);
|
|
});
|
|
</script>
|