feat: 增加示波器探测参数显示,增加旋转编码器按下的功能
This commit is contained in:
		@@ -268,10 +268,10 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
 | 
			
		||||
            var response = new OscilloscopeDataResponse
 | 
			
		||||
            {
 | 
			
		||||
                ADFrequency = freqResult.Value,
 | 
			
		||||
                ADVpp = vppResult.Value,
 | 
			
		||||
                ADMax = maxResult.Value,
 | 
			
		||||
                ADMin = minResult.Value,
 | 
			
		||||
                AdFrequency = freqResult.Value,
 | 
			
		||||
                AdVpp = vppResult.Value,
 | 
			
		||||
                AdMax = maxResult.Value,
 | 
			
		||||
                AdMin = minResult.Value,
 | 
			
		||||
                WaveformData = Convert.ToBase64String(waveformResult.Value)
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,10 +34,10 @@ public interface IOscilloscopeReceiver
 | 
			
		||||
[TranspilationSource]
 | 
			
		||||
public class OscilloscopeDataResponse
 | 
			
		||||
{
 | 
			
		||||
    public uint ADFrequency { get; set; }
 | 
			
		||||
    public byte ADVpp { get; set; }
 | 
			
		||||
    public byte ADMax { get; set; }
 | 
			
		||||
    public byte ADMin { get; set; }
 | 
			
		||||
    public uint AdFrequency { get; set; }
 | 
			
		||||
    public byte AdVpp { get; set; }
 | 
			
		||||
    public byte AdMax { get; set; }
 | 
			
		||||
    public byte AdMin { get; set; }
 | 
			
		||||
    public string WaveformData { get; set; } = "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -275,19 +275,19 @@ public class OscilloscopeHub : Hub<IOscilloscopeReceiver>, IOscilloscopeHub
 | 
			
		||||
 | 
			
		||||
            var response = new OscilloscopeDataResponse
 | 
			
		||||
            {
 | 
			
		||||
                ADFrequency = freqResult.Value,
 | 
			
		||||
                ADVpp = vppResult.Value,
 | 
			
		||||
                ADMax = maxResult.Value,
 | 
			
		||||
                ADMin = minResult.Value,
 | 
			
		||||
                AdFrequency = freqResult.Value,
 | 
			
		||||
                AdVpp = vppResult.Value,
 | 
			
		||||
                AdMax = maxResult.Value,
 | 
			
		||||
                AdMin = minResult.Value,
 | 
			
		||||
                WaveformData = Convert.ToBase64String(waveformResult.Value)
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            return new OscilloscopeDataResponse
 | 
			
		||||
            {
 | 
			
		||||
                ADFrequency = freqResult.Value,
 | 
			
		||||
                ADVpp = vppResult.Value,
 | 
			
		||||
                ADMax = maxResult.Value,
 | 
			
		||||
                ADMin = minResult.Value,
 | 
			
		||||
                AdFrequency = freqResult.Value,
 | 
			
		||||
                AdVpp = vppResult.Value,
 | 
			
		||||
                AdMax = maxResult.Value,
 | 
			
		||||
                AdMin = minResult.Value,
 | 
			
		||||
                WaveformData = Convert.ToBase64String(waveformResult.Value)
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ public interface IRotaryEncoderHub
 | 
			
		||||
{
 | 
			
		||||
    Task<bool> SetEnable(bool enable);
 | 
			
		||||
    Task<bool> RotateEncoderOnce(int num, RotaryEncoderDirection direction);
 | 
			
		||||
    Task<bool> PressEncoderOnce(int num, RotaryEncoderPressStatus press);
 | 
			
		||||
    Task<bool> EnableCycleRotateEncoder(int num, RotaryEncoderDirection direction, int freq);
 | 
			
		||||
    Task<bool> DisableCycleRotateEncoder();
 | 
			
		||||
}
 | 
			
		||||
@@ -133,6 +134,30 @@ public class RotaryEncoderHub : Hub<IRotaryEncoderReceiver>, IRotaryEncoderHub
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> PressEncoderOnce(int num, RotaryEncoderPressStatus press)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (num <= 0 || num > 4)
 | 
			
		||||
                throw new ArgumentException($"RotaryEncoder num should be 1~3, instead of {num}");
 | 
			
		||||
 | 
			
		||||
            var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
 | 
			
		||||
            var encoderCtrl = new RotaryEncoderCtrl(board.IpAddr, board.Port, 0);
 | 
			
		||||
            var result = await encoderCtrl.PressEncoderOnce(num, press);
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error(result.Error, $"RotateEncoderOnce({num}, {press}) failed");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            return result.Value;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "Failed to rotate encoder once");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> EnableCycleRotateEncoder(int num, RotaryEncoderDirection direction, int freq)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,10 @@ namespace Peripherals.RotaryEncoderClient;
 | 
			
		||||
class RotaryEncoderCtrlAddr
 | 
			
		||||
{
 | 
			
		||||
    public const UInt32 BASE = 0xB0_00_00_30;
 | 
			
		||||
    public const UInt32 PRESS_BASE = 0xB0_00_00_40;
 | 
			
		||||
 | 
			
		||||
    public const UInt32 ENABLE = BASE;
 | 
			
		||||
    public const UInt32 PRESS_ENABLE = PRESS_BASE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[TranspilationSource]
 | 
			
		||||
@@ -18,6 +20,13 @@ public enum RotaryEncoderDirection : uint
 | 
			
		||||
    Clockwise = 1,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[TranspilationSource]
 | 
			
		||||
public enum RotaryEncoderPressStatus : uint
 | 
			
		||||
{
 | 
			
		||||
    Press = 0,
 | 
			
		||||
    Release = 1,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class RotaryEncoderCtrl
 | 
			
		||||
{
 | 
			
		||||
    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
@@ -45,10 +54,22 @@ public class RotaryEncoderCtrl
 | 
			
		||||
            MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
 | 
			
		||||
        else return new(new Exception("Message Bus not work!"));
 | 
			
		||||
 | 
			
		||||
        var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
            this.ep, this.taskID, RotaryEncoderCtrlAddr.ENABLE, enable ? 0x1U : 0x0U, this.timeout);
 | 
			
		||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
			
		||||
        return ret.Value;
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
                this.ep, this.taskID, RotaryEncoderCtrlAddr.ENABLE, enable ? 0x1U : 0x0U, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful) return new(ret.Error);
 | 
			
		||||
            if (!ret.Value)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Set Rotary Encoder Enable failed: {ret.Error}");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
                this.ep, this.taskID, RotaryEncoderCtrlAddr.PRESS_ENABLE, enable ? 0x1U : 0x0U, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful) return new(ret.Error);
 | 
			
		||||
            return ret.Value;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async ValueTask<Result<bool>> RotateEncoderOnce(int num, RotaryEncoderDirection direction)
 | 
			
		||||
@@ -61,7 +82,23 @@ public class RotaryEncoderCtrl
 | 
			
		||||
            this.ep, this.taskID, RotaryEncoderCtrlAddr.BASE + (UInt32)num, (UInt32)direction, this.timeout);
 | 
			
		||||
        if (!ret.IsSuccessful)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Set Rotary Encoder {num} {direction.ToString()} failed: {ret.Error}");
 | 
			
		||||
            logger.Error($"Set Rotary Encoder Rotate {num} {direction.ToString()} failed: {ret.Error}");
 | 
			
		||||
            return new(ret.Error);
 | 
			
		||||
        }
 | 
			
		||||
        return ret.Value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async ValueTask<Result<bool>> PressEncoderOnce(int num, RotaryEncoderPressStatus press)
 | 
			
		||||
    {
 | 
			
		||||
        if (MsgBus.IsRunning)
 | 
			
		||||
            MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
 | 
			
		||||
        else return new(new Exception("Message Bus not work!"));
 | 
			
		||||
 | 
			
		||||
        var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
            this.ep, this.taskID, RotaryEncoderCtrlAddr.PRESS_BASE + (UInt32)num, (UInt32)press, this.timeout);
 | 
			
		||||
        if (!ret.IsSuccessful)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Set Rotary Encoder Set {num} {press.ToString()} failed: {ret.Error}");
 | 
			
		||||
            return new(ret.Error);
 | 
			
		||||
        }
 | 
			
		||||
        return ret.Value;
 | 
			
		||||
 
 | 
			
		||||
@@ -199,10 +199,16 @@ const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState(
 | 
			
		||||
      }
 | 
			
		||||
      sampleCount.value = bytes.length;
 | 
			
		||||
 | 
			
		||||
      const aDFrequency = resp.adFrequency;
 | 
			
		||||
 | 
			
		||||
      // 计算采样周期(ns)
 | 
			
		||||
      const samplePeriodNs =
 | 
			
		||||
        aDFrequency > 0 ? 1_000_000_000 / aDFrequency : 200;
 | 
			
		||||
 | 
			
		||||
      // 构建时间轴
 | 
			
		||||
      const x = Array.from(
 | 
			
		||||
        { length: bytes.length },
 | 
			
		||||
        (_, i) => (i * samplePeriodNs.value) / 1000, // us
 | 
			
		||||
        (_, i) => (i * samplePeriodNs) / 1000, // us
 | 
			
		||||
      );
 | 
			
		||||
      const y = Array.from(bytes);
 | 
			
		||||
 | 
			
		||||
@@ -211,11 +217,13 @@ const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState(
 | 
			
		||||
        y,
 | 
			
		||||
        xUnit: "us",
 | 
			
		||||
        yUnit: "V",
 | 
			
		||||
        adFrequency: resp.aDFrequency,
 | 
			
		||||
        adVpp: resp.aDVpp,
 | 
			
		||||
        adMax: resp.aDMax,
 | 
			
		||||
        adMin: resp.aDMin,
 | 
			
		||||
        adFrequency: aDFrequency,
 | 
			
		||||
        adVpp: resp.adVpp,
 | 
			
		||||
        adMax: resp.adMax,
 | 
			
		||||
        adMin: resp.adMin,
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      console.log("解析后的参数:", resp, oscData.value); // 添加调试日志
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // 获取数据
 | 
			
		||||
 
 | 
			
		||||
@@ -88,6 +88,67 @@
 | 
			
		||||
      <div class="w-2 h-2 bg-white rounded-full animate-pulse"></div>
 | 
			
		||||
      采集中
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 测量数据展示面板 -->
 | 
			
		||||
    <div
 | 
			
		||||
      v-if="hasData"
 | 
			
		||||
      class="absolute top-4 left-4 bg-white/95 dark:bg-slate-800/95 backdrop-blur-sm rounded-lg shadow-lg border border-slate-200/50 dark:border-slate-700/50 p-3 min-w-[200px]"
 | 
			
		||||
    >
 | 
			
		||||
      <h4 class="text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3 flex items-center gap-2">
 | 
			
		||||
        <Activity class="w-4 h-4 text-blue-500" />
 | 
			
		||||
        测量参数
 | 
			
		||||
      </h4>
 | 
			
		||||
      
 | 
			
		||||
      <div class="space-y-2 text-xs">
 | 
			
		||||
        <!-- 采样频率 -->
 | 
			
		||||
        <div class="flex justify-between items-center">
 | 
			
		||||
          <span class="text-slate-600 dark:text-slate-400">采样频率:</span>
 | 
			
		||||
          <span class="font-mono font-semibold text-blue-600 dark:text-blue-400">
 | 
			
		||||
            {{ formatFrequency(oscData?.adFrequency || 0) }}
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <!-- 电压范围 -->
 | 
			
		||||
        <div class="flex justify-between items-center">
 | 
			
		||||
          <span class="text-slate-600 dark:text-slate-400">Vpp:</span>
 | 
			
		||||
          <span class="font-mono font-semibold text-emerald-600 dark:text-emerald-400">
 | 
			
		||||
            {{ (oscData?.adVpp || 0).toFixed(2) }}V
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <!-- 最大值 -->
 | 
			
		||||
        <div class="flex justify-between items-center">
 | 
			
		||||
          <span class="text-slate-600 dark:text-slate-400">最大值:</span>
 | 
			
		||||
          <span class="font-mono font-semibold text-orange-600 dark:text-orange-400">
 | 
			
		||||
            {{ formatAdcValue(oscData?.adMax || 0) }}
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <!-- 最小值 -->
 | 
			
		||||
        <div class="flex justify-between items-center">
 | 
			
		||||
          <span class="text-slate-600 dark:text-slate-400">最小值:</span>
 | 
			
		||||
          <span class="font-mono font-semibold text-purple-600 dark:text-purple-400">
 | 
			
		||||
            {{ formatAdcValue(oscData?.adMin || 0) }}
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <!-- 采样点数 -->
 | 
			
		||||
        <div class="flex justify-between items-center pt-1 border-t border-slate-200 dark:border-slate-700">
 | 
			
		||||
          <span class="text-slate-600 dark:text-slate-400">采样点:</span>
 | 
			
		||||
          <span class="font-mono font-semibold text-slate-700 dark:text-slate-300">
 | 
			
		||||
            {{ formatSampleCount(oscManager.sampleCount.value) }}
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <!-- 采样周期 -->
 | 
			
		||||
        <div class="flex justify-between items-center">
 | 
			
		||||
          <span class="text-slate-600 dark:text-slate-400">周期:</span>
 | 
			
		||||
          <span class="font-mono font-semibold text-slate-700 dark:text-slate-300">
 | 
			
		||||
            {{ formatPeriod(oscManager.samplePeriodNs.value) }}
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -156,6 +217,44 @@ const hasData = computed(() => {
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 格式化频率显示
 | 
			
		||||
const formatFrequency = (frequency: number): string => {
 | 
			
		||||
  if (frequency >= 1_000_000) {
 | 
			
		||||
    return `${(frequency / 1_000_000).toFixed(1)}MHz`;
 | 
			
		||||
  } else if (frequency >= 1_000) {
 | 
			
		||||
    return `${(frequency / 1_000).toFixed(1)}kHz`;
 | 
			
		||||
  } else {
 | 
			
		||||
    return `${frequency}Hz`;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 格式化ADC值显示
 | 
			
		||||
const formatAdcValue = (value: number): string => {
 | 
			
		||||
  return `${value} (${((value / 255) * 3.3).toFixed(2)}V)`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 格式化采样点数显示
 | 
			
		||||
const formatSampleCount = (count: number): string => {
 | 
			
		||||
  if (count >= 1_000_000) {
 | 
			
		||||
    return `${(count / 1_000_000).toFixed(1)}M`;
 | 
			
		||||
  } else if (count >= 1_000) {
 | 
			
		||||
    return `${(count / 1_000).toFixed(1)}k`;
 | 
			
		||||
  } else {
 | 
			
		||||
    return `${count}`;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 格式化周期显示
 | 
			
		||||
const formatPeriod = (periodNs: number): string => {
 | 
			
		||||
  if (periodNs >= 1_000_000) {
 | 
			
		||||
    return `${(periodNs / 1_000_000).toFixed(2)}ms`;
 | 
			
		||||
  } else if (periodNs >= 1_000) {
 | 
			
		||||
    return `${(periodNs / 1_000).toFixed(2)}μs`;
 | 
			
		||||
  } else {
 | 
			
		||||
    return `${periodNs.toFixed(2)}ns`;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const option = computed((): EChartsOption => {
 | 
			
		||||
  if (!oscData.value || !oscData.value.x || !oscData.value.y) {
 | 
			
		||||
    return {};
 | 
			
		||||
@@ -235,7 +334,9 @@ const option = computed((): EChartsOption => {
 | 
			
		||||
        if (!oscData.value) return "";
 | 
			
		||||
        let result = `<div style="font-weight: 600; margin-bottom: 4px;">时间: ${params[0].data[0].toFixed(2)} ${oscData.value.xUnit}</div>`;
 | 
			
		||||
        params.forEach((param: any) => {
 | 
			
		||||
          result += `<div style="color: ${param.color};">● ${param.seriesName}: ${param.data[1].toFixed(3)} ${oscData.value?.yUnit ?? ""}</div>`;
 | 
			
		||||
          const adcValue = param.data[1];
 | 
			
		||||
          const voltage = ((adcValue / 255) * 3.3).toFixed(3);
 | 
			
		||||
          result += `<div style="color: ${param.color};">● ${param.seriesName}: ${adcValue} (${voltage}V)</div>`;
 | 
			
		||||
        });
 | 
			
		||||
        return result;
 | 
			
		||||
      },
 | 
			
		||||
@@ -333,7 +434,7 @@ const option = computed((): EChartsOption => {
 | 
			
		||||
    },
 | 
			
		||||
    yAxis: {
 | 
			
		||||
      type: "value",
 | 
			
		||||
      name: oscData.value ? `电压 (${oscData.value.yUnit})` : "电压",
 | 
			
		||||
      name: oscData.value ? `ADC值 (0-255)` : "ADC值",
 | 
			
		||||
      nameLocation: "middle",
 | 
			
		||||
      nameGap: 50,
 | 
			
		||||
      nameTextStyle: {
 | 
			
		||||
@@ -357,6 +458,9 @@ const option = computed((): EChartsOption => {
 | 
			
		||||
      axisLabel: {
 | 
			
		||||
        color: "#64748B",
 | 
			
		||||
        fontSize: 11,
 | 
			
		||||
        formatter: (value: number) => {
 | 
			
		||||
          return `${value} (${((value / 255) * 3.3).toFixed(1)}V)`;
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      splitLine: {
 | 
			
		||||
        show: true,
 | 
			
		||||
@@ -536,6 +640,14 @@ button:active::after {
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    padding: 4px 8px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 移动端测量面板调整 */
 | 
			
		||||
  .absolute.top-4.left-4 {
 | 
			
		||||
    top: 8px;
 | 
			
		||||
    left: 8px;
 | 
			
		||||
    min-width: 180px;
 | 
			
		||||
    font-size: 11px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 平滑过渡效果 */
 | 
			
		||||
@@ -548,4 +660,17 @@ button:focus-visible {
 | 
			
		||||
  outline: 2px solid rgba(59, 130, 246, 0.5);
 | 
			
		||||
  outline-offset: 2px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
/* 测量面板样式增强 */
 | 
			
		||||
.absolute.top-4.left-4 {
 | 
			
		||||
  backdrop-filter: blur(8px);
 | 
			
		||||
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
 | 
			
		||||
  transition: all 0.3s ease-in-out;
 | 
			
		||||
  z-index: 10;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.absolute.top-4.left-4:hover {
 | 
			
		||||
  transform: translateY(-1px);
 | 
			
		||||
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -176,7 +176,10 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { useRotaryEncoder } from "@/stores/Peripherals/RotaryEncoder";
 | 
			
		||||
import { RotaryEncoderDirection } from "@/utils/signalR/Peripherals.RotaryEncoderClient";
 | 
			
		||||
import {
 | 
			
		||||
  RotaryEncoderDirection,
 | 
			
		||||
  RotaryEncoderPressStatus,
 | 
			
		||||
} from "@/utils/signalR/Peripherals.RotaryEncoderClient";
 | 
			
		||||
import { watch } from "vue";
 | 
			
		||||
import { watchEffect } from "vue";
 | 
			
		||||
import { ref, computed } from "vue";
 | 
			
		||||
@@ -185,6 +188,7 @@ const rotataryEncoderStore = useRotaryEncoder();
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  size?: number;
 | 
			
		||||
  componentId?: string;
 | 
			
		||||
  enableDigitalTwin?: boolean;
 | 
			
		||||
  encoderNumber?: number;
 | 
			
		||||
}
 | 
			
		||||
@@ -236,8 +240,16 @@ function handleMouseUp() {
 | 
			
		||||
    // 仅在未发生旋转时才触发按压
 | 
			
		||||
    if (!drag.value.hasRotated) {
 | 
			
		||||
      isPressed.value = true;
 | 
			
		||||
      rotataryEncoderStore.pressOnce(
 | 
			
		||||
        props.encoderNumber,
 | 
			
		||||
        RotaryEncoderPressStatus.Press,
 | 
			
		||||
      );
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        isPressed.value = false;
 | 
			
		||||
        rotataryEncoderStore.pressOnce(
 | 
			
		||||
          props.encoderNumber,
 | 
			
		||||
          RotaryEncoderPressStatus.Release,
 | 
			
		||||
        );
 | 
			
		||||
      }, 100);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -252,7 +264,10 @@ function handlePress(pressed: boolean) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watchEffect(() => {
 | 
			
		||||
  rotataryEncoderStore.setEnable(props.enableDigitalTwin);
 | 
			
		||||
  if (!props.enableDigitalTwin) return;
 | 
			
		||||
 | 
			
		||||
  if (props.componentId)
 | 
			
		||||
    rotataryEncoderStore.setEnable(props.enableDigitalTwin);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
 
 | 
			
		||||
@@ -108,6 +108,7 @@ import { ref, computed, watch, onMounted } from "vue";
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  size?: number;
 | 
			
		||||
  componentId?: string;
 | 
			
		||||
  enableDigitalTwin?: boolean;
 | 
			
		||||
  switchCount?: number;
 | 
			
		||||
  initialValues?: string;
 | 
			
		||||
@@ -191,13 +192,10 @@ function setBtnStatus(idx: number, isOn: boolean) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 监听 props 变化只同步一次
 | 
			
		||||
const isFirstEnableDigitalTwin = ref(true);
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.enableDigitalTwin,
 | 
			
		||||
  (newVal) => {
 | 
			
		||||
    if (isFirstEnableDigitalTwin.value) {
 | 
			
		||||
      isFirstEnableDigitalTwin.value = false;
 | 
			
		||||
    } else {
 | 
			
		||||
    if (props.componentId) {
 | 
			
		||||
      const client = getClient();
 | 
			
		||||
      client.setEnable(newVal);
 | 
			
		||||
    }
 | 
			
		||||
@@ -205,16 +203,11 @@ watch(
 | 
			
		||||
  { immediate: true },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const isFirstUpdateStatus = ref(true);
 | 
			
		||||
watch(
 | 
			
		||||
  () => [switchCount.value, props.initialValues],
 | 
			
		||||
  () => {
 | 
			
		||||
    btnStatus.value = parseInitialValues();
 | 
			
		||||
    if (isFirstUpdateStatus.value) {
 | 
			
		||||
      isFirstUpdateStatus.value = false;
 | 
			
		||||
    } else {
 | 
			
		||||
      updateStatus(btnStatus.value);
 | 
			
		||||
    }
 | 
			
		||||
    if (props.componentId) updateStatus(btnStatus.value);
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,8 @@
 | 
			
		||||
import { AuthManager } from "@/utils/AuthManager";
 | 
			
		||||
import type { RotaryEncoderDirection } from "@/utils/signalR/Peripherals.RotaryEncoderClient";
 | 
			
		||||
import type {
 | 
			
		||||
  RotaryEncoderDirection,
 | 
			
		||||
  RotaryEncoderPressStatus,
 | 
			
		||||
} from "@/utils/signalR/Peripherals.RotaryEncoderClient";
 | 
			
		||||
import {
 | 
			
		||||
  getHubProxyFactory,
 | 
			
		||||
  getReceiverRegister,
 | 
			
		||||
@@ -72,6 +75,11 @@ export const useRotaryEncoder = defineStore("RotaryEncoder", () => {
 | 
			
		||||
    return await proxy.rotateEncoderOnce(num, direction);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function pressOnce(num: number, pressStatus: RotaryEncoderPressStatus) {
 | 
			
		||||
    const proxy = getHubProxy();
 | 
			
		||||
    return await proxy.pressEncoderOnce(num, pressStatus);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function enableCycleRotate(
 | 
			
		||||
    num: number,
 | 
			
		||||
    direction: RotaryEncoderDirection,
 | 
			
		||||
@@ -89,6 +97,7 @@ export const useRotaryEncoder = defineStore("RotaryEncoder", () => {
 | 
			
		||||
  return {
 | 
			
		||||
    setEnable,
 | 
			
		||||
    rotateOnce,
 | 
			
		||||
    pressOnce,
 | 
			
		||||
    enableCycleRotate,
 | 
			
		||||
    disableCycleRotate,
 | 
			
		||||
  };
 | 
			
		||||
 
 | 
			
		||||
@@ -8,3 +8,9 @@ export enum RotaryEncoderDirection {
 | 
			
		||||
    Clockwise = 1,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Transpiled from Peripherals.RotaryEncoderClient.RotaryEncoderPressStatus */
 | 
			
		||||
export enum RotaryEncoderPressStatus {
 | 
			
		||||
    Press = 0,
 | 
			
		||||
    Release = 1,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
import type { HubConnection, IStreamResult, Subject } from '@microsoft/signalr';
 | 
			
		||||
import type { IDigitalTubesHub, IJtagHub, IOscilloscopeHub, IProgressHub, IRotaryEncoderHub, IWS2812Hub, IDigitalTubesReceiver, IJtagReceiver, IOscilloscopeReceiver, IProgressReceiver, IRotaryEncoderReceiver, IWS2812Receiver } from './server.Hubs';
 | 
			
		||||
import type { DigitalTubeTaskStatus, OscilloscopeFullConfig, OscilloscopeDataResponse, ProgressInfo } from '../server.Hubs';
 | 
			
		||||
import type { RotaryEncoderDirection } from '../Peripherals.RotaryEncoderClient';
 | 
			
		||||
import type { RotaryEncoderDirection, RotaryEncoderPressStatus } from '../Peripherals.RotaryEncoderClient';
 | 
			
		||||
import type { RGBColor } from '../Peripherals.WS2812Client';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -270,6 +270,10 @@ class IRotaryEncoderHub_HubProxy implements IRotaryEncoderHub {
 | 
			
		||||
        return await this.connection.invoke("RotateEncoderOnce", num, direction);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public readonly pressEncoderOnce = async (num: number, press: RotaryEncoderPressStatus): Promise<boolean> => {
 | 
			
		||||
        return await this.connection.invoke("PressEncoderOnce", num, press);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public readonly enableCycleRotateEncoder = async (num: number, direction: RotaryEncoderDirection, freq: number): Promise<boolean> => {
 | 
			
		||||
        return await this.connection.invoke("EnableCycleRotateEncoder", num, direction, freq);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
// @ts-nocheck
 | 
			
		||||
import type { IStreamResult, Subject } from '@microsoft/signalr';
 | 
			
		||||
import type { DigitalTubeTaskStatus, OscilloscopeFullConfig, OscilloscopeDataResponse, ProgressInfo } from '../server.Hubs';
 | 
			
		||||
import type { RotaryEncoderDirection } from '../Peripherals.RotaryEncoderClient';
 | 
			
		||||
import type { RotaryEncoderDirection, RotaryEncoderPressStatus } from '../Peripherals.RotaryEncoderClient';
 | 
			
		||||
import type { RGBColor } from '../Peripherals.WS2812Client';
 | 
			
		||||
 | 
			
		||||
export type IDigitalTubesHub = {
 | 
			
		||||
@@ -116,6 +116,12 @@ export type IRotaryEncoderHub = {
 | 
			
		||||
    rotateEncoderOnce(num: number, direction: RotaryEncoderDirection): Promise<boolean>;
 | 
			
		||||
    /**
 | 
			
		||||
    * @param num Transpiled from int
 | 
			
		||||
    * @param press Transpiled from Peripherals.RotaryEncoderClient.RotaryEncoderPressStatus
 | 
			
		||||
    * @returns Transpiled from System.Threading.Tasks.Task<bool>
 | 
			
		||||
    */
 | 
			
		||||
    pressEncoderOnce(num: number, press: RotaryEncoderPressStatus): Promise<boolean>;
 | 
			
		||||
    /**
 | 
			
		||||
    * @param num Transpiled from int
 | 
			
		||||
    * @param direction Transpiled from Peripherals.RotaryEncoderClient.RotaryEncoderDirection
 | 
			
		||||
    * @param freq Transpiled from int
 | 
			
		||||
    * @returns Transpiled from System.Threading.Tasks.Task<bool>
 | 
			
		||||
 
 | 
			
		||||
@@ -13,13 +13,13 @@ export type DigitalTubeTaskStatus = {
 | 
			
		||||
/** Transpiled from server.Hubs.OscilloscopeDataResponse */
 | 
			
		||||
export type OscilloscopeDataResponse = {
 | 
			
		||||
    /** Transpiled from uint */
 | 
			
		||||
    aDFrequency: number;
 | 
			
		||||
    adFrequency: number;
 | 
			
		||||
    /** Transpiled from byte */
 | 
			
		||||
    aDVpp: number;
 | 
			
		||||
    adVpp: number;
 | 
			
		||||
    /** Transpiled from byte */
 | 
			
		||||
    aDMax: number;
 | 
			
		||||
    adMax: number;
 | 
			
		||||
    /** Transpiled from byte */
 | 
			
		||||
    aDMin: number;
 | 
			
		||||
    adMin: number;
 | 
			
		||||
    /** Transpiled from string */
 | 
			
		||||
    waveformData: string;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -182,7 +182,7 @@
 | 
			
		||||
              <!-- MD文档 -->
 | 
			
		||||
              <div class="space-y-2">
 | 
			
		||||
                <label class="text-sm font-medium text-base-content"
 | 
			
		||||
                  >MD文档 (必需)</label
 | 
			
		||||
                  >MD文档 (可选)</label
 | 
			
		||||
                >
 | 
			
		||||
                <div
 | 
			
		||||
                  class="border-2 border-dashed border-base-300 rounded-lg p-6 text-center cursor-pointer hover:border-primary hover:bg-primary/5 transition-colors aspect-square flex items-center justify-center"
 | 
			
		||||
@@ -468,7 +468,7 @@ const canCreateExam = computed(() => {
 | 
			
		||||
    editExamInfo.value.id.trim() !== "" &&
 | 
			
		||||
    editExamInfo.value.name.trim() !== "" &&
 | 
			
		||||
    editExamInfo.value.description.trim() !== "" &&
 | 
			
		||||
    (uploadFiles.value.mdFile !== null || mode.value === "edit")
 | 
			
		||||
    (mode.value === "edit")
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -605,11 +605,6 @@ const submitCreateExam = async () => {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!uploadFiles.value.mdFile) {
 | 
			
		||||
    alert.error("请上传MD文档");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isUpdating.value = true;
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user