Merge branch 'master' of ssh://git.swordlost.top:222/SikongJueluo/FPGA_WebLab into dpp
This commit is contained in:
		@@ -213,6 +213,7 @@ class Camera
 | 
			
		||||
    /// <returns>包含图像数据的字节数组</returns>
 | 
			
		||||
    public async ValueTask<Result<byte[]>> ReadFrame()
 | 
			
		||||
    {
 | 
			
		||||
        // 只在第一次或出错时清除UDP缓冲区,避免每帧都清除造成延迟
 | 
			
		||||
        MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
 | 
			
		||||
 | 
			
		||||
        logger.Trace($"Reading frame from camera {this.address}");
 | 
			
		||||
@@ -252,6 +253,8 @@ class Camera
 | 
			
		||||
    /// <returns>配置结果</returns>
 | 
			
		||||
    public async ValueTask<Result<bool>> ConfigureRegisters(UInt16[][] registerTable, int? customDelayMs = null)
 | 
			
		||||
    {
 | 
			
		||||
        MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
 | 
			
		||||
 | 
			
		||||
        var i2c = new Peripherals.I2cClient.I2c(this.address, this.port, this.taskID, this.timeout);
 | 
			
		||||
 | 
			
		||||
        foreach (var cmd in registerTable)
 | 
			
		||||
@@ -1291,20 +1294,20 @@ class Camera
 | 
			
		||||
                logger.Error($"读取自动对焦初始化状态失败: {readResult.Error}");
 | 
			
		||||
                return new(readResult.Error);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            logger.Trace($"自动对焦初始化状态检查, state=0x{readResult.Value:X2}");
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            if (readResult.Value == 0x70)
 | 
			
		||||
            {
 | 
			
		||||
                break; // 初始化完成
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            if (iteration == 1)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"自动对焦初始化状态检查超时!! state=0x{readResult.Value:X2}");
 | 
			
		||||
                return new(new Exception($"自动对焦初始化状态检查超时, state=0x{readResult.Value:X2}"));
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            await Task.Delay(1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -484,6 +484,151 @@ public class UDPServer
 | 
			
		||||
            // 清空队列的最有效方式是替换为新的队列
 | 
			
		||||
            udpData.TryUpdate(key, new ConcurrentQueue<UDPData>(), dataQueue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 强制进行ARP刷新,防止后续传输时造成影响
 | 
			
		||||
        FlushArpEntry(ipAddr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 跨平台ARP缓存刷新
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="ipAddr">目标IP地址</param>
 | 
			
		||||
    private void FlushArpEntry(string ipAddr)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // 验证IP地址格式
 | 
			
		||||
            if (!IPAddress.TryParse(ipAddr, out var _))
 | 
			
		||||
            {
 | 
			
		||||
                logger.Warn($"Invalid IP address format: {ipAddr}");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            string command;
 | 
			
		||||
            string arguments;
 | 
			
		||||
 | 
			
		||||
            if (OperatingSystem.IsWindows())
 | 
			
		||||
            {
 | 
			
		||||
                // Windows: arp -d <ip>
 | 
			
		||||
                command = "arp";
 | 
			
		||||
                arguments = $"-d {ipAddr}";
 | 
			
		||||
            }
 | 
			
		||||
            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
 | 
			
		||||
            {
 | 
			
		||||
                // Linux/macOS: ip neigh del <ip> dev <interface> (优先使用 ip 命令)
 | 
			
		||||
                // 如果 ip 命令不可用,则使用 arp -d <ip>
 | 
			
		||||
                if (IsCommandAvailable("ip"))
 | 
			
		||||
                {
 | 
			
		||||
                    command = "ip";
 | 
			
		||||
                    arguments = $"neigh flush {ipAddr}";
 | 
			
		||||
                }
 | 
			
		||||
                else if (IsCommandAvailable("arp"))
 | 
			
		||||
                {
 | 
			
		||||
                    command = "arp";
 | 
			
		||||
                    arguments = $"-d {ipAddr}";
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    logger.Warn("Neither 'ip' nor 'arp' command is available for ARP cache flush");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                logger.Warn($"Unsupported operating system for ARP cache flush");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 异步执行命令,避免阻塞主线程
 | 
			
		||||
            Task.Run(() => ExecuteArpCommand(command, arguments, ipAddr));
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Error during ARP cache flush for {ipAddr}: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 检查系统命令是否可用
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="command">命令名称</param>
 | 
			
		||||
    /// <returns>命令是否可用</returns>
 | 
			
		||||
    private bool IsCommandAvailable(string command)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var process = new System.Diagnostics.Process
 | 
			
		||||
            {
 | 
			
		||||
                StartInfo = new System.Diagnostics.ProcessStartInfo
 | 
			
		||||
                {
 | 
			
		||||
                    FileName = OperatingSystem.IsWindows() ? "where" : "which",
 | 
			
		||||
                    Arguments = command,
 | 
			
		||||
                    UseShellExecute = false,
 | 
			
		||||
                    RedirectStandardOutput = true,
 | 
			
		||||
                    CreateNoWindow = true
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            process.Start();
 | 
			
		||||
            process.WaitForExit(1000); // 1秒超时
 | 
			
		||||
            return process.ExitCode == 0;
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 执行ARP命令
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="command">命令</param>
 | 
			
		||||
    /// <param name="arguments">参数</param>
 | 
			
		||||
    /// <param name="ipAddr">IP地址</param>
 | 
			
		||||
    private void ExecuteArpCommand(string command, string arguments, string ipAddr)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var process = new System.Diagnostics.Process
 | 
			
		||||
            {
 | 
			
		||||
                StartInfo = new System.Diagnostics.ProcessStartInfo
 | 
			
		||||
                {
 | 
			
		||||
                    FileName = command,
 | 
			
		||||
                    Arguments = arguments,
 | 
			
		||||
                    UseShellExecute = false,
 | 
			
		||||
                    RedirectStandardOutput = true,
 | 
			
		||||
                    RedirectStandardError = true,
 | 
			
		||||
                    CreateNoWindow = true
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            process.Start();
 | 
			
		||||
            
 | 
			
		||||
            // 设置超时时间,避免进程挂起
 | 
			
		||||
            if (process.WaitForExit(5000)) // 5秒超时
 | 
			
		||||
            {
 | 
			
		||||
                var output = process.StandardOutput.ReadToEnd();
 | 
			
		||||
                var error = process.StandardError.ReadToEnd();
 | 
			
		||||
 | 
			
		||||
                if (process.ExitCode == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    logger.Debug($"ARP cache flush successful for {ipAddr}");
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    logger.Warn($"ARP cache flush failed for {ipAddr}. Exit code: {process.ExitCode}, Error: {error}");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                process.Kill();
 | 
			
		||||
                logger.Warn($"ARP cache flush command timed out for {ipAddr}");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Failed to execute ARP cache flush command for {ipAddr}: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
import { createInjectionState } from "@vueuse/core";
 | 
			
		||||
import { shallowRef, reactive, ref, computed } from "vue";
 | 
			
		||||
import { Mutex } from "async-mutex";
 | 
			
		||||
import {
 | 
			
		||||
  CaptureConfig,
 | 
			
		||||
  CaptureStatus,
 | 
			
		||||
  LogicAnalyzerClient,
 | 
			
		||||
  GlobalCaptureMode,
 | 
			
		||||
  SignalOperator,
 | 
			
		||||
@@ -10,6 +12,7 @@ import {
 | 
			
		||||
} from "@/APIClient";
 | 
			
		||||
import { AuthManager } from "@/utils/AuthManager";
 | 
			
		||||
import { useAlertStore } from "@/components/Alert";
 | 
			
		||||
import { useRequiredInjection } from "@/utils/Common";
 | 
			
		||||
 | 
			
		||||
export type LogicDataType = {
 | 
			
		||||
  x: number[];
 | 
			
		||||
@@ -77,11 +80,15 @@ const defaultColors = [
 | 
			
		||||
const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
  () => {
 | 
			
		||||
    const logicData = shallowRef<LogicDataType>();
 | 
			
		||||
    const alert = useAlertStore();
 | 
			
		||||
    const alert = useRequiredInjection(useAlertStore);
 | 
			
		||||
 | 
			
		||||
    // 添加互斥锁
 | 
			
		||||
    const operationMutex = new Mutex();
 | 
			
		||||
 | 
			
		||||
    // 触发设置相关状态
 | 
			
		||||
    const currentGlobalMode = ref<GlobalCaptureMode>(GlobalCaptureMode.AND);
 | 
			
		||||
    const isApplying = ref(false);
 | 
			
		||||
    const isCapturing = ref(false); // 添加捕获状态标识
 | 
			
		||||
 | 
			
		||||
    // 通道配置
 | 
			
		||||
    const channels = reactive<Channel[]>(
 | 
			
		||||
@@ -107,13 +114,13 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // 添加计算属性:获取通道名称数组
 | 
			
		||||
    const channelNames = computed(() => 
 | 
			
		||||
      channels.map(channel => channel.label)
 | 
			
		||||
    const channelNames = computed(() =>
 | 
			
		||||
      channels.map((channel) => channel.label),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // 添加计算属性:获取启用通道的名称数组
 | 
			
		||||
    const enabledChannelNames = computed(() => 
 | 
			
		||||
      channels.filter(channel => channel.enabled).map(channel => channel.label)
 | 
			
		||||
    const enabledChannels = computed(() =>
 | 
			
		||||
      channels.filter((channel) => channel.enabled),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const enableAllChannels = () => {
 | 
			
		||||
@@ -129,6 +136,13 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const setGlobalMode = async (mode: GlobalCaptureMode) => {
 | 
			
		||||
      // 检查是否有其他操作正在进行
 | 
			
		||||
      if (operationMutex.isLocked()) {
 | 
			
		||||
        alert.warn("有其他操作正在进行中,请稍后再试", 3000);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const release = await operationMutex.acquire();
 | 
			
		||||
      try {
 | 
			
		||||
        const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
 | 
			
		||||
        const success = await client.setGlobalTrigMode(mode);
 | 
			
		||||
@@ -145,10 +159,19 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.error("设置全局触发模式失败:", error);
 | 
			
		||||
        alert?.error("设置全局触发模式失败", 3000);
 | 
			
		||||
      } finally {
 | 
			
		||||
        release();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const applyConfiguration = async () => {
 | 
			
		||||
      // 检查是否有其他操作正在进行
 | 
			
		||||
      if (operationMutex.isLocked()) {
 | 
			
		||||
        alert.warn("有其他操作正在进行中,请稍后再试", 3000);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const release = await operationMutex.acquire();
 | 
			
		||||
      isApplying.value = true;
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
@@ -190,6 +213,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
        alert?.error("应用配置失败,请检查设备连接", 3000);
 | 
			
		||||
      } finally {
 | 
			
		||||
        isApplying.value = false;
 | 
			
		||||
        release();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -215,13 +239,123 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
      logicData.value = data;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const startCapture = async () => {
 | 
			
		||||
      // 检查是否有其他操作正在进行
 | 
			
		||||
      if (operationMutex.isLocked()) {
 | 
			
		||||
        alert.warn("有其他操作正在进行中,请稍后再试", 3000);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const release = await operationMutex.acquire();
 | 
			
		||||
      isCapturing.value = true;
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
 | 
			
		||||
 | 
			
		||||
        // 1. 设置捕获模式为开始捕获
 | 
			
		||||
        const captureStarted = await client.setCaptureMode(true, false);
 | 
			
		||||
        if (!captureStarted) {
 | 
			
		||||
          throw new Error("无法启动捕获");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        alert?.info("开始捕获信号...", 2000);
 | 
			
		||||
 | 
			
		||||
        // 2. 轮询捕获状态
 | 
			
		||||
        const pollCaptureStatus = async (): Promise<boolean> => {
 | 
			
		||||
          const status = await client.getCaptureStatus();
 | 
			
		||||
 | 
			
		||||
          // 检查是否捕获完成
 | 
			
		||||
          if (status === CaptureStatus.CaptureDone) {
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // 检查是否仍在捕获中
 | 
			
		||||
          if (
 | 
			
		||||
            status === CaptureStatus.CaptureBusy ||
 | 
			
		||||
            status === CaptureStatus.CaptureOn ||
 | 
			
		||||
            status === CaptureStatus.CaptureForce
 | 
			
		||||
          ) {
 | 
			
		||||
            // 等待500毫秒后继续轮询
 | 
			
		||||
            await new Promise((resolve) => setTimeout(resolve, 500));
 | 
			
		||||
            return await pollCaptureStatus();
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // 其他状态视为错误
 | 
			
		||||
          throw new Error(`捕获状态异常: ${status}`);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // 等待捕获完成
 | 
			
		||||
        await pollCaptureStatus();
 | 
			
		||||
 | 
			
		||||
        // 3. 获取捕获数据
 | 
			
		||||
        const base64Data = await client.getCaptureData();
 | 
			
		||||
 | 
			
		||||
        // 4. 将base64数据转换为bytes
 | 
			
		||||
        const binaryString = atob(base64Data);
 | 
			
		||||
        const bytes = new Uint8Array(binaryString.length);
 | 
			
		||||
        for (let i = 0; i < binaryString.length; i++) {
 | 
			
		||||
          bytes[i] = binaryString.charCodeAt(i);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 5. 解析数据为8个通道的数字信号
 | 
			
		||||
        const sampleCount = bytes.length;
 | 
			
		||||
        const timeStep = 0.1; // 假设每个采样点间隔0.1ms
 | 
			
		||||
 | 
			
		||||
        // 创建时间轴
 | 
			
		||||
        const x = Array.from({ length: sampleCount }, (_, i) => i * timeStep);
 | 
			
		||||
 | 
			
		||||
        // 创建8个通道的数据
 | 
			
		||||
        const y: number[][] = Array.from(
 | 
			
		||||
          { length: 8 },
 | 
			
		||||
          () => new Array(sampleCount),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // 解析每个字节的8个位到对应通道
 | 
			
		||||
        for (let i = 0; i < sampleCount; i++) {
 | 
			
		||||
          const byte = bytes[i];
 | 
			
		||||
          for (let channel = 0; channel < 8; channel++) {
 | 
			
		||||
            // bit0对应ch0, bit1对应ch1, ..., bit7对应ch7
 | 
			
		||||
            y[channel][i] = (byte >> channel) & 1;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 6. 设置逻辑数据
 | 
			
		||||
        const logicData: LogicDataType = {
 | 
			
		||||
          x,
 | 
			
		||||
          y,
 | 
			
		||||
          xUnit: "ms",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        setLogicData(logicData);
 | 
			
		||||
 | 
			
		||||
        alert?.success(`捕获完成!获得 ${sampleCount} 个采样点`, 3000);
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.error("捕获失败:", error);
 | 
			
		||||
        alert?.error(
 | 
			
		||||
          `捕获失败: ${error instanceof Error ? error.message : "未知错误"}`,
 | 
			
		||||
          3000,
 | 
			
		||||
        );
 | 
			
		||||
      } finally {
 | 
			
		||||
        isCapturing.value = false;
 | 
			
		||||
        release();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // 添加检查操作状态的计算属性
 | 
			
		||||
    const isOperationInProgress = computed(
 | 
			
		||||
      () => isApplying.value || isCapturing.value || operationMutex.isLocked(),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // 添加生成测试数据的方法
 | 
			
		||||
    const generateTestData = () => {
 | 
			
		||||
      const sampleRate = 10000; // 10kHz sampling
 | 
			
		||||
      const duration = 1;
 | 
			
		||||
      const points = Math.floor(sampleRate * duration);
 | 
			
		||||
 | 
			
		||||
      const x = Array.from({ length: points }, (_, i) => (i / sampleRate) * 1000); // time in ms
 | 
			
		||||
      const x = Array.from(
 | 
			
		||||
        { length: points },
 | 
			
		||||
        (_, i) => (i / sampleRate) * 1000,
 | 
			
		||||
      ); // time in ms
 | 
			
		||||
 | 
			
		||||
      // Generate 8 channels with different digital patterns
 | 
			
		||||
      const y = [
 | 
			
		||||
@@ -246,37 +380,31 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
          (_, i) => Math.floor((12.5 * i) / sampleRate) % 2,
 | 
			
		||||
        ),
 | 
			
		||||
        // Channel 4: Data signal (pseudo-random pattern)
 | 
			
		||||
        Array.from(
 | 
			
		||||
          { length: points },
 | 
			
		||||
          (_, i) => Math.abs( Math.floor(Math.sin(i * 0.01) * 10) % 2 ),
 | 
			
		||||
        Array.from({ length: points }, (_, i) =>
 | 
			
		||||
          Math.abs(Math.floor(Math.sin(i * 0.01) * 10) % 2),
 | 
			
		||||
        ),
 | 
			
		||||
        // Channel 5: Enable signal (periodic pulse)
 | 
			
		||||
        Array.from(
 | 
			
		||||
          { length: points },
 | 
			
		||||
          (_, i) => (Math.floor(i / 50) % 10) < 3 ? 1 : 0,
 | 
			
		||||
        Array.from({ length: points }, (_, i) =>
 | 
			
		||||
          Math.floor(i / 50) % 10 < 3 ? 1 : 0,
 | 
			
		||||
        ),
 | 
			
		||||
        // Channel 6: Reset signal (occasional pulse)
 | 
			
		||||
        Array.from(
 | 
			
		||||
          { length: points },
 | 
			
		||||
          (_, i) => (Math.floor(i / 200) % 20) === 0 ? 1 : 0,
 | 
			
		||||
        Array.from({ length: points }, (_, i) =>
 | 
			
		||||
          Math.floor(i / 200) % 20 === 0 ? 1 : 0,
 | 
			
		||||
        ),
 | 
			
		||||
        // Channel 7: Status signal (slow changing)
 | 
			
		||||
        Array.from(
 | 
			
		||||
          { length: points },
 | 
			
		||||
          (_, i) => Math.floor(i / 1000) % 2,
 | 
			
		||||
        ),
 | 
			
		||||
        Array.from({ length: points }, (_, i) => Math.floor(i / 1000) % 2),
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      // 同时更新通道标签为更有意义的名称
 | 
			
		||||
      const testChannelNames = [
 | 
			
		||||
        "CLK",
 | 
			
		||||
        "CLK/2", 
 | 
			
		||||
        "CLK/2",
 | 
			
		||||
        "CLK/4",
 | 
			
		||||
        "CLK/8",
 | 
			
		||||
        "PWM",
 | 
			
		||||
        "ENABLE",
 | 
			
		||||
        "RESET",
 | 
			
		||||
        "STATUS"
 | 
			
		||||
        "STATUS",
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      channels.forEach((channel, index) => {
 | 
			
		||||
@@ -284,8 +412,9 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // 设置逻辑数据
 | 
			
		||||
      enableAllChannels();
 | 
			
		||||
      setLogicData({ x, y, xUnit: "ms" });
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      alert?.success("测试数据生成成功", 2000);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -296,11 +425,13 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
      // 触发设置状态
 | 
			
		||||
      currentGlobalMode,
 | 
			
		||||
      isApplying,
 | 
			
		||||
      isCapturing, // 导出捕获状态
 | 
			
		||||
      isOperationInProgress, // 导出操作进行状态
 | 
			
		||||
      channels,
 | 
			
		||||
      signalConfigs,
 | 
			
		||||
      enabledChannelCount,
 | 
			
		||||
      channelNames,
 | 
			
		||||
      enabledChannelNames,
 | 
			
		||||
      enabledChannels,
 | 
			
		||||
 | 
			
		||||
      // 选项数据
 | 
			
		||||
      globalModes,
 | 
			
		||||
@@ -314,6 +445,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
      applyConfiguration,
 | 
			
		||||
      resetConfiguration,
 | 
			
		||||
      setLogicData,
 | 
			
		||||
      startCapture,
 | 
			
		||||
      generateTestData,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -106,9 +106,20 @@ const updateOptions = shallowRef({
 | 
			
		||||
const option = computed((): EChartsOption => {
 | 
			
		||||
  if (isUndefined(analyzer.logicData.value)) return {};
 | 
			
		||||
 | 
			
		||||
  const channelCount = analyzer.logicData.value.y.length;
 | 
			
		||||
  // 只获取启用的通道
 | 
			
		||||
  const enabledChannels = analyzer.enabledChannels.value;
 | 
			
		||||
  const enabledChannelIndices = analyzer.channels
 | 
			
		||||
    .map((channel, index) => (channel.enabled ? index : -1))
 | 
			
		||||
    .filter((index) => index !== -1);
 | 
			
		||||
 | 
			
		||||
  const channelCount = enabledChannels.length;
 | 
			
		||||
  const channelSpacing = 2; // 每个通道之间的间距
 | 
			
		||||
 | 
			
		||||
  // 如果没有启用的通道,返回空配置
 | 
			
		||||
  if (channelCount === 0) {
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 使用单个网格
 | 
			
		||||
  const grids: GridComponentOption[] = [
 | 
			
		||||
    {
 | 
			
		||||
@@ -134,7 +145,7 @@ const option = computed((): EChartsOption => {
 | 
			
		||||
    },
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  // 单个Y轴,范围根据通道数量调整
 | 
			
		||||
  // 单个Y轴,范围根据启用通道数量调整
 | 
			
		||||
  const yAxis: YAXisOption[] = [
 | 
			
		||||
    {
 | 
			
		||||
      type: "value",
 | 
			
		||||
@@ -145,7 +156,7 @@ const option = computed((): EChartsOption => {
 | 
			
		||||
        formatter: (value: number) => {
 | 
			
		||||
          const channelIndex = Math.round(value / channelSpacing);
 | 
			
		||||
          return channelIndex < channelCount
 | 
			
		||||
            ? analyzer.channelNames.value[channelIndex]
 | 
			
		||||
            ? enabledChannels[channelIndex].label
 | 
			
		||||
            : "";
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
@@ -153,28 +164,27 @@ const option = computed((): EChartsOption => {
 | 
			
		||||
    },
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  // 创建系列数据,每个通道有不同的Y偏移
 | 
			
		||||
  const series: LineSeriesOption[] = analyzer.logicData.value.y.map(
 | 
			
		||||
    (channelData: number[], index: number) => ({
 | 
			
		||||
      name: analyzer.channelNames.value[index],
 | 
			
		||||
  // 创建系列数据,只包含启用的通道
 | 
			
		||||
  const series: LineSeriesOption[] = enabledChannelIndices.map(
 | 
			
		||||
    (originalIndex: number, displayIndex: number) => ({
 | 
			
		||||
      name: enabledChannels[displayIndex].label,
 | 
			
		||||
      type: "line",
 | 
			
		||||
      data: channelData.map(
 | 
			
		||||
        (value: number) => value + index * channelSpacing + 0.2,
 | 
			
		||||
      data: analyzer.logicData.value!.y[originalIndex].map(
 | 
			
		||||
        (value: number) => value + displayIndex * channelSpacing + 0.2,
 | 
			
		||||
      ),
 | 
			
		||||
      step: "end",
 | 
			
		||||
      lineStyle: {
 | 
			
		||||
        width: 2,
 | 
			
		||||
        color: enabledChannels[displayIndex].color,
 | 
			
		||||
      },
 | 
			
		||||
      areaStyle: {
 | 
			
		||||
        opacity: 0.3,
 | 
			
		||||
        origin: index * channelSpacing,
 | 
			
		||||
        origin: displayIndex * channelSpacing,
 | 
			
		||||
        color: enabledChannels[displayIndex].color,
 | 
			
		||||
      },
 | 
			
		||||
      symbol: "none",
 | 
			
		||||
      // 优化性能配置
 | 
			
		||||
      sampling: "lttb",
 | 
			
		||||
      // large: true,
 | 
			
		||||
      // largeThreshold: 2000,
 | 
			
		||||
      // progressive: 2000,
 | 
			
		||||
      // 减少动画以避免闪烁
 | 
			
		||||
      animation: false,
 | 
			
		||||
    }),
 | 
			
		||||
@@ -200,16 +210,15 @@ const option = computed((): EChartsOption => {
 | 
			
		||||
 | 
			
		||||
          let tooltip = `Time: ${timeValue.toFixed(3)}${analyzer.logicData.value!.xUnit}<br/>`;
 | 
			
		||||
 | 
			
		||||
          // 显示所有通道在当前时间点的原始数值(0或1)
 | 
			
		||||
          if (analyzer.logicData.value) {
 | 
			
		||||
            analyzer.channelNames.value.forEach(
 | 
			
		||||
              (channelName: string, index: number) => {
 | 
			
		||||
                const originalValue =
 | 
			
		||||
                  analyzer.logicData.value!.y[index][dataIndex];
 | 
			
		||||
                tooltip += `${channelName}: ${originalValue}<br/>`;
 | 
			
		||||
              },
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
          // 只显示启用通道在当前时间点的原始数值(0或1)
 | 
			
		||||
          enabledChannelIndices.forEach(
 | 
			
		||||
            (originalIndex: number, displayIndex: number) => {
 | 
			
		||||
              const channelName = enabledChannels[displayIndex].label;
 | 
			
		||||
              const originalValue =
 | 
			
		||||
                analyzer.logicData.value!.y[originalIndex][dataIndex];
 | 
			
		||||
              tooltip += `${channelName}: ${originalValue}<br/>`;
 | 
			
		||||
            },
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          return tooltip;
 | 
			
		||||
        }
 | 
			
		||||
@@ -243,5 +252,4 @@ const option = computed((): EChartsOption => {
 | 
			
		||||
    series: series,
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user