feat: 前端七段数码管添加数字孪生功能
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -29,7 +29,7 @@ DebuggerCmd.md
 | 
			
		||||
*.ntvs*
 | 
			
		||||
*.njsproj
 | 
			
		||||
*.sw?
 | 
			
		||||
 | 
			
		||||
prompt.md
 | 
			
		||||
*.tsbuildinfo
 | 
			
		||||
 | 
			
		||||
# Generated Files
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								TODO.md
									
									
									
									
									
								
							@@ -1,13 +0,0 @@
 | 
			
		||||
# TODO
 | 
			
		||||
 | 
			
		||||
1. 后端HTTP视频流
 | 
			
		||||
 | 
			
		||||
640*480, RGB565
 | 
			
		||||
0x0000_0000 + 25800
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
2. 信号发生器界面导入.dat文件
 | 
			
		||||
3. 示波器后端交互、前端界面
 | 
			
		||||
4. 逻辑分析仪后端交互、前端界面
 | 
			
		||||
5. 前端重构
 | 
			
		||||
6. 数据库 —— 用户登录、板卡资源分配、板卡IP地址分配
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
import { ResourcePurpose } from "@/APIClient";
 | 
			
		||||
import { AuthManager } from "@/utils/AuthManager";
 | 
			
		||||
 | 
			
		||||
// 定义 diagram.json 的类型结构
 | 
			
		||||
export interface DiagramData {
 | 
			
		||||
  version: number;
 | 
			
		||||
@@ -26,40 +29,43 @@ export interface DiagramPart {
 | 
			
		||||
// 连接类型定义 - 使用元组类型表示四元素数组
 | 
			
		||||
export type ConnectionArray = [string, string, number, string[]];
 | 
			
		||||
 | 
			
		||||
import { AuthManager } from '@/utils/AuthManager';
 | 
			
		||||
 | 
			
		||||
// 解析连接字符串为组件ID和引脚ID
 | 
			
		||||
export function parseConnectionPin(connectionPin: string): { componentId: string; pinId: string } {
 | 
			
		||||
  const [componentId, pinId] = connectionPin.split(':');
 | 
			
		||||
export function parseConnectionPin(connectionPin: string): {
 | 
			
		||||
  componentId: string;
 | 
			
		||||
  pinId: string;
 | 
			
		||||
} {
 | 
			
		||||
  const [componentId, pinId] = connectionPin.split(":");
 | 
			
		||||
  return { componentId, pinId };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 将连接数组转换为适用于渲染的格式
 | 
			
		||||
export function connectionArrayToWireItem(
 | 
			
		||||
  connection: ConnectionArray, 
 | 
			
		||||
  index: number, 
 | 
			
		||||
  startPos = { x: 0, y: 0 }, 
 | 
			
		||||
  endPos = { x: 0, y: 0 }
 | 
			
		||||
  connection: ConnectionArray,
 | 
			
		||||
  index: number,
 | 
			
		||||
  startPos = { x: 0, y: 0 },
 | 
			
		||||
  endPos = { x: 0, y: 0 },
 | 
			
		||||
): WireItem {
 | 
			
		||||
  const [startPinStr, endPinStr, width, path] = connection;
 | 
			
		||||
  const { componentId: startComponentId, pinId: startPinId } = parseConnectionPin(startPinStr);
 | 
			
		||||
  const { componentId: endComponentId, pinId: endPinId } = parseConnectionPin(endPinStr);
 | 
			
		||||
  
 | 
			
		||||
  const { componentId: startComponentId, pinId: startPinId } =
 | 
			
		||||
    parseConnectionPin(startPinStr);
 | 
			
		||||
  const { componentId: endComponentId, pinId: endPinId } =
 | 
			
		||||
    parseConnectionPin(endPinStr);
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    id: `wire-${index}`,
 | 
			
		||||
    startX: startPos.x,
 | 
			
		||||
    startY: startPos.y,
 | 
			
		||||
    endX: endPos.x, 
 | 
			
		||||
    endX: endPos.x,
 | 
			
		||||
    endY: endPos.y,
 | 
			
		||||
    startComponentId,
 | 
			
		||||
    startPinId,
 | 
			
		||||
    endComponentId,
 | 
			
		||||
    endPinId,
 | 
			
		||||
    strokeWidth: width,
 | 
			
		||||
    color: '#4a5568', // 默认颜色
 | 
			
		||||
    routingMode: 'path',
 | 
			
		||||
    color: "#4a5568", // 默认颜色
 | 
			
		||||
    routingMode: "path",
 | 
			
		||||
    pathCommands: path,
 | 
			
		||||
    showLabel: false
 | 
			
		||||
    showLabel: false,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -76,7 +82,7 @@ export interface WireItem {
 | 
			
		||||
  endPinId?: string;
 | 
			
		||||
  strokeWidth: number;
 | 
			
		||||
  color: string;
 | 
			
		||||
  routingMode: 'orthogonal' | 'path';
 | 
			
		||||
  routingMode: "orthogonal" | "path";
 | 
			
		||||
  constraint?: string;
 | 
			
		||||
  pathCommands?: string[];
 | 
			
		||||
  showLabel: boolean;
 | 
			
		||||
@@ -89,57 +95,63 @@ export async function loadDiagramData(examId?: string): Promise<DiagramData> {
 | 
			
		||||
    if (examId) {
 | 
			
		||||
      try {
 | 
			
		||||
        const resourceClient = AuthManager.createAuthenticatedResourceClient();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // 获取diagram类型的资源列表
 | 
			
		||||
        const resources = await resourceClient.getResourceList(examId, 'canvas', 'template');
 | 
			
		||||
        
 | 
			
		||||
        const resources = await resourceClient.getResourceList(
 | 
			
		||||
          examId,
 | 
			
		||||
          "canvas",
 | 
			
		||||
          ResourcePurpose.Template,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if (resources && resources.length > 0) {
 | 
			
		||||
          // 获取第一个diagram资源
 | 
			
		||||
          const diagramResource = resources[0];
 | 
			
		||||
          
 | 
			
		||||
 | 
			
		||||
          // 使用动态API获取资源文件内容
 | 
			
		||||
          const response = await resourceClient.getResourceById(diagramResource.id);
 | 
			
		||||
          
 | 
			
		||||
          const response = await resourceClient.getResourceById(
 | 
			
		||||
            diagramResource.id,
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          if (response && response.data) {
 | 
			
		||||
            const text = await response.data.text();
 | 
			
		||||
            const data = JSON.parse(text);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // 验证数据格式
 | 
			
		||||
            const validation = validateDiagramData(data);
 | 
			
		||||
            if (validation.isValid) {
 | 
			
		||||
              console.log('成功从API加载实验diagram:', examId);
 | 
			
		||||
              console.log("成功从API加载实验diagram:", examId);
 | 
			
		||||
              return data;
 | 
			
		||||
            } else {
 | 
			
		||||
              console.warn('API返回的diagram数据格式无效:', validation.errors);
 | 
			
		||||
              console.warn("API返回的diagram数据格式无效:", validation.errors);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          console.log('未找到实验diagram资源,使用默认加载方式');
 | 
			
		||||
          console.log("未找到实验diagram资源,使用默认加载方式");
 | 
			
		||||
        }
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.warn('从API加载实验diagram失败,使用默认加载方式:', error);
 | 
			
		||||
        console.warn("从API加载实验diagram失败,使用默认加载方式:", error);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // 如果没有examId或API加载失败,尝试从静态文件加载(不再使用本地存储)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // 从静态文件加载(作为备选方案)
 | 
			
		||||
    const response = await fetch('/src/components/diagram.json');
 | 
			
		||||
    const response = await fetch("/src/components/diagram.json");
 | 
			
		||||
    if (!response.ok) {
 | 
			
		||||
      throw new Error(`Failed to load diagram.json: ${response.statusText}`);
 | 
			
		||||
    }
 | 
			
		||||
    const data = await response.json();
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // 验证静态文件数据
 | 
			
		||||
    const validation = validateDiagramData(data);
 | 
			
		||||
    if (validation.isValid) {
 | 
			
		||||
      return data;
 | 
			
		||||
    } else {
 | 
			
		||||
      console.warn('静态diagram文件数据格式无效:', validation.errors);
 | 
			
		||||
      throw new Error('所有diagram数据源都无效');
 | 
			
		||||
      console.warn("静态diagram文件数据格式无效:", validation.errors);
 | 
			
		||||
      throw new Error("所有diagram数据源都无效");
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Error loading diagram data:', error);
 | 
			
		||||
    console.error("Error loading diagram data:", error);
 | 
			
		||||
    // 返回空的默认数据结构
 | 
			
		||||
    return createEmptyDiagram();
 | 
			
		||||
  }
 | 
			
		||||
@@ -149,33 +161,31 @@ export async function loadDiagramData(examId?: string): Promise<DiagramData> {
 | 
			
		||||
export function createEmptyDiagram(): DiagramData {
 | 
			
		||||
  return {
 | 
			
		||||
    version: 1,
 | 
			
		||||
    author: 'user',
 | 
			
		||||
    editor: 'user',
 | 
			
		||||
    author: "user",
 | 
			
		||||
    editor: "user",
 | 
			
		||||
    parts: [],
 | 
			
		||||
    connections: []
 | 
			
		||||
    connections: [],
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 保存图表数据(已禁用本地存储)
 | 
			
		||||
export function saveDiagramData(data: DiagramData): void {
 | 
			
		||||
  // 本地存储功能已禁用 - 不再保存到localStorage
 | 
			
		||||
  console.debug('saveDiagramData called but localStorage saving is disabled');
 | 
			
		||||
  console.debug("saveDiagramData called but localStorage saving is disabled");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 更新组件位置
 | 
			
		||||
export function updatePartPosition(
 | 
			
		||||
  data: DiagramData, 
 | 
			
		||||
  partId: string, 
 | 
			
		||||
  x: number, 
 | 
			
		||||
  y: number
 | 
			
		||||
  data: DiagramData,
 | 
			
		||||
  partId: string,
 | 
			
		||||
  x: number,
 | 
			
		||||
  y: number,
 | 
			
		||||
): DiagramData {
 | 
			
		||||
  return {
 | 
			
		||||
    ...data,
 | 
			
		||||
    parts: data.parts.map(part => 
 | 
			
		||||
      part.id === partId 
 | 
			
		||||
        ? { ...part, x, y } 
 | 
			
		||||
        : part
 | 
			
		||||
    )
 | 
			
		||||
    parts: data.parts.map((part) =>
 | 
			
		||||
      part.id === partId ? { ...part, x, y } : part,
 | 
			
		||||
    ),
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -184,21 +194,21 @@ export function updatePartAttribute(
 | 
			
		||||
  data: DiagramData,
 | 
			
		||||
  partId: string,
 | 
			
		||||
  attrName: string,
 | 
			
		||||
  value: any
 | 
			
		||||
  value: any,
 | 
			
		||||
): DiagramData {
 | 
			
		||||
  return {
 | 
			
		||||
    ...data,
 | 
			
		||||
    parts: data.parts.map(part => 
 | 
			
		||||
      part.id === partId 
 | 
			
		||||
        ? { 
 | 
			
		||||
            ...part, 
 | 
			
		||||
            attrs: { 
 | 
			
		||||
              ...part.attrs, 
 | 
			
		||||
              [attrName]: value 
 | 
			
		||||
            } 
 | 
			
		||||
          } 
 | 
			
		||||
        : part
 | 
			
		||||
    )
 | 
			
		||||
    parts: data.parts.map((part) =>
 | 
			
		||||
      part.id === partId
 | 
			
		||||
        ? {
 | 
			
		||||
            ...part,
 | 
			
		||||
            attrs: {
 | 
			
		||||
              ...part.attrs,
 | 
			
		||||
              [attrName]: value,
 | 
			
		||||
            },
 | 
			
		||||
          }
 | 
			
		||||
        : part,
 | 
			
		||||
    ),
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -210,72 +220,79 @@ export function addConnection(
 | 
			
		||||
  endComponentId: string,
 | 
			
		||||
  endPinId: string,
 | 
			
		||||
  width: number = 2,
 | 
			
		||||
  path: string[] = []
 | 
			
		||||
  path: string[] = [],
 | 
			
		||||
): DiagramData {
 | 
			
		||||
  const newConnection: ConnectionArray = [
 | 
			
		||||
    `${startComponentId}:${startPinId}`,
 | 
			
		||||
    `${endComponentId}:${endPinId}`,
 | 
			
		||||
    width,
 | 
			
		||||
    path
 | 
			
		||||
    path,
 | 
			
		||||
  ];
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    ...data,
 | 
			
		||||
    connections: [...data.connections, newConnection]
 | 
			
		||||
    connections: [...data.connections, newConnection],
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 删除连接
 | 
			
		||||
export function deleteConnection(
 | 
			
		||||
  data: DiagramData,
 | 
			
		||||
  connectionIndex: number
 | 
			
		||||
  connectionIndex: number,
 | 
			
		||||
): DiagramData {
 | 
			
		||||
  return {
 | 
			
		||||
    ...data,
 | 
			
		||||
    connections: data.connections.filter((_, index) => index !== connectionIndex)
 | 
			
		||||
    connections: data.connections.filter(
 | 
			
		||||
      (_, index) => index !== connectionIndex,
 | 
			
		||||
    ),
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 查找与组件关联的所有连接
 | 
			
		||||
export function findConnectionsByPart(
 | 
			
		||||
  data: DiagramData,
 | 
			
		||||
  partId: string
 | 
			
		||||
  partId: string,
 | 
			
		||||
): { connection: ConnectionArray; index: number }[] {
 | 
			
		||||
  return data.connections
 | 
			
		||||
    .map((connection, index) => ({ connection, index }))
 | 
			
		||||
    .filter(({ connection }) => {
 | 
			
		||||
      const [startPin, endPin] = connection;
 | 
			
		||||
      const startCompId = startPin.split(':')[0];
 | 
			
		||||
      const endCompId = endPin.split(':')[0];
 | 
			
		||||
      const startCompId = startPin.split(":")[0];
 | 
			
		||||
      const endCompId = endPin.split(":")[0];
 | 
			
		||||
      return startCompId === partId || endCompId === partId;
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 添加验证diagram.json文件的函数
 | 
			
		||||
export function validateDiagramData(data: any): { isValid: boolean; errors: string[] } {
 | 
			
		||||
export function validateDiagramData(data: any): {
 | 
			
		||||
  isValid: boolean;
 | 
			
		||||
  errors: string[];
 | 
			
		||||
} {
 | 
			
		||||
  const errors: string[] = [];
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  // 检查版本号
 | 
			
		||||
  if (!data.version) {
 | 
			
		||||
    errors.push('缺少version字段');
 | 
			
		||||
    errors.push("缺少version字段");
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  // 检查parts数组
 | 
			
		||||
  if (!Array.isArray(data.parts)) {
 | 
			
		||||
    errors.push('parts字段不是数组');
 | 
			
		||||
    errors.push("parts字段不是数组");
 | 
			
		||||
  } else {
 | 
			
		||||
    // 验证parts中的每个对象
 | 
			
		||||
    data.parts.forEach((part: any, index: number) => {
 | 
			
		||||
      if (!part.id) errors.push(`parts[${index}]缺少id`);
 | 
			
		||||
      if (!part.type) errors.push(`parts[${index}]缺少type`);
 | 
			
		||||
      if (typeof part.x !== 'number') errors.push(`parts[${index}]缺少有效的x坐标`);
 | 
			
		||||
      if (typeof part.y !== 'number') errors.push(`parts[${index}]缺少有效的y坐标`);
 | 
			
		||||
      if (typeof part.x !== "number")
 | 
			
		||||
        errors.push(`parts[${index}]缺少有效的x坐标`);
 | 
			
		||||
      if (typeof part.y !== "number")
 | 
			
		||||
        errors.push(`parts[${index}]缺少有效的y坐标`);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  // 检查connections数组
 | 
			
		||||
  if (!Array.isArray(data.connections)) {
 | 
			
		||||
    errors.push('connections字段不是数组');
 | 
			
		||||
    errors.push("connections字段不是数组");
 | 
			
		||||
  } else {
 | 
			
		||||
    // 验证connections中的每个数组
 | 
			
		||||
    data.connections.forEach((conn: any, index: number) => {
 | 
			
		||||
@@ -283,25 +300,25 @@ export function validateDiagramData(data: any): { isValid: boolean; errors: stri
 | 
			
		||||
        errors.push(`connections[${index}]不是有效的连接数组`);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      const [startPin, endPin, width] = conn;
 | 
			
		||||
      
 | 
			
		||||
      if (typeof startPin !== 'string' || !startPin.includes(':')) {
 | 
			
		||||
 | 
			
		||||
      if (typeof startPin !== "string" || !startPin.includes(":")) {
 | 
			
		||||
        errors.push(`connections[${index}]的起始针脚格式无效`);
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      if (typeof endPin !== 'string' || !endPin.includes(':')) {
 | 
			
		||||
 | 
			
		||||
      if (typeof endPin !== "string" || !endPin.includes(":")) {
 | 
			
		||||
        errors.push(`connections[${index}]的结束针脚格式无效`);
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      if (typeof width !== 'number') {
 | 
			
		||||
 | 
			
		||||
      if (typeof width !== "number") {
 | 
			
		||||
        errors.push(`connections[${index}]的宽度不是有效的数字`);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    isValid: errors.length === 0,
 | 
			
		||||
    errors
 | 
			
		||||
    errors,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ export const previewSizes: Record<string, number> = {
 | 
			
		||||
  Switch: 0.35,
 | 
			
		||||
  Pin: 0.8,
 | 
			
		||||
  SMT_LED: 0.7,
 | 
			
		||||
  SevenSegmentDisplay: 0.4,
 | 
			
		||||
  SevenSegmentDisplayUltimate: 0.4,
 | 
			
		||||
  HDMI: 0.5,
 | 
			
		||||
  DDR: 0.5,
 | 
			
		||||
  ETH: 0.5,
 | 
			
		||||
@@ -50,7 +50,7 @@ export const availableComponents: ComponentConfig[] = [
 | 
			
		||||
  { type: "Switch", name: "开关" },
 | 
			
		||||
  { type: "Pin", name: "引脚" },
 | 
			
		||||
  { type: "SMT_LED", name: "贴片LED" },
 | 
			
		||||
  { type: "SevenSegmentDisplay", name: "数码管" },
 | 
			
		||||
  { type: "SevenSegmentDisplayUltimate", name: "数码管" },
 | 
			
		||||
  { type: "HDMI", name: "HDMI接口" },
 | 
			
		||||
  { type: "DDR", name: "DDR内存" },
 | 
			
		||||
  { type: "ETH", name: "以太网接口" },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,65 +1,114 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="seven-segment-display" :style="{
 | 
			
		||||
    width: width + 'px',
 | 
			
		||||
    height: height + 'px',
 | 
			
		||||
    position: 'relative',
 | 
			
		||||
  }">
 | 
			
		||||
    <svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" viewBox="0 0 120 220" class="display">
 | 
			
		||||
  <div
 | 
			
		||||
    class="seven-segment-display"
 | 
			
		||||
    :style="{
 | 
			
		||||
      width: width + 'px',
 | 
			
		||||
      height: height + 'px',
 | 
			
		||||
      position: 'relative',
 | 
			
		||||
    }"
 | 
			
		||||
  >
 | 
			
		||||
    <svg
 | 
			
		||||
      xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
      :width="width"
 | 
			
		||||
      :height="height"
 | 
			
		||||
      viewBox="0 0 120 220"
 | 
			
		||||
      class="display"
 | 
			
		||||
    >
 | 
			
		||||
      <!-- 数码管基座 -->
 | 
			
		||||
      <rect width="120" height="180" x="0" y="0" fill="#222" rx="10" ry="10" />
 | 
			
		||||
      <rect width="110" height="170" x="5" y="5" fill="#333" rx="5" ry="5" />
 | 
			
		||||
      <!-- 7段 + 小数点,每个段由多边形表示,重新设计点位置使其更接近实际数码管 -->
 | 
			
		||||
      <!-- a段 (顶部横线) -->
 | 
			
		||||
      <polygon :points="'30,20 90,20 98,28 82,36 38,36 22,28'"
 | 
			
		||||
      <polygon
 | 
			
		||||
        :points="'30,20 90,20 98,28 82,36 38,36 22,28'"
 | 
			
		||||
        :fill="isSegmentActive('a') ? segmentColor : inactiveColor"
 | 
			
		||||
        :style="{ opacity: isSegmentActive('a') ? 1 : 0.15 }" class="segment" />
 | 
			
		||||
        :style="{ opacity: isSegmentActive('a') ? 1 : 0.15 }"
 | 
			
		||||
        class="segment"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <!-- b段 (右上竖线) -->
 | 
			
		||||
      <polygon :points="'100,30 108,38 108,82 100,90 92,82 92,38'"
 | 
			
		||||
      <polygon
 | 
			
		||||
        :points="'100,30 108,38 108,82 100,90 92,82 92,38'"
 | 
			
		||||
        :fill="isSegmentActive('b') ? segmentColor : inactiveColor"
 | 
			
		||||
        :style="{ opacity: isSegmentActive('b') ? 1 : 0.15 }" class="segment" />
 | 
			
		||||
        :style="{ opacity: isSegmentActive('b') ? 1 : 0.15 }"
 | 
			
		||||
        class="segment"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <!-- c段 (右下竖线) -->
 | 
			
		||||
      <polygon :points="'100,90 108,98 108,142 100,150 92,142 92,98'"
 | 
			
		||||
      <polygon
 | 
			
		||||
        :points="'100,90 108,98 108,142 100,150 92,142 92,98'"
 | 
			
		||||
        :fill="isSegmentActive('c') ? segmentColor : inactiveColor"
 | 
			
		||||
        :style="{ opacity: isSegmentActive('c') ? 1 : 0.15 }" class="segment" />
 | 
			
		||||
        :style="{ opacity: isSegmentActive('c') ? 1 : 0.15 }"
 | 
			
		||||
        class="segment"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <!-- d段 (底部横线) -->
 | 
			
		||||
      <polygon :points="'30,160 90,160 98,152 82,144 38,144 22,152'"
 | 
			
		||||
      <polygon
 | 
			
		||||
        :points="'30,160 90,160 98,152 82,144 38,144 22,152'"
 | 
			
		||||
        :fill="isSegmentActive('d') ? segmentColor : inactiveColor"
 | 
			
		||||
        :style="{ opacity: isSegmentActive('d') ? 1 : 0.15 }" class="segment" />
 | 
			
		||||
        :style="{ opacity: isSegmentActive('d') ? 1 : 0.15 }"
 | 
			
		||||
        class="segment"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <!-- e段 (左下竖线) -->
 | 
			
		||||
      <polygon :points="'20,90 28,98 28,142 20,150 12,142 12,98'"
 | 
			
		||||
      <polygon
 | 
			
		||||
        :points="'20,90 28,98 28,142 20,150 12,142 12,98'"
 | 
			
		||||
        :fill="isSegmentActive('e') ? segmentColor : inactiveColor"
 | 
			
		||||
        :style="{ opacity: isSegmentActive('e') ? 1 : 0.15 }" class="segment" />
 | 
			
		||||
        :style="{ opacity: isSegmentActive('e') ? 1 : 0.15 }"
 | 
			
		||||
        class="segment"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <!-- f段 (左上竖线) -->
 | 
			
		||||
      <polygon :points="'20,30 28,38 28,82 20,90 12,82 12,38'"
 | 
			
		||||
      <polygon
 | 
			
		||||
        :points="'20,30 28,38 28,82 20,90 12,82 12,38'"
 | 
			
		||||
        :fill="isSegmentActive('f') ? segmentColor : inactiveColor"
 | 
			
		||||
        :style="{ opacity: isSegmentActive('f') ? 1 : 0.15 }" class="segment" />
 | 
			
		||||
        :style="{ opacity: isSegmentActive('f') ? 1 : 0.15 }"
 | 
			
		||||
        class="segment"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <!-- g段 (中间横线) -->
 | 
			
		||||
      <polygon :points="'30,90 38,82 82,82 90,90 82,98 38,98'"
 | 
			
		||||
      <polygon
 | 
			
		||||
        :points="'30,90 38,82 82,82 90,90 82,98 38,98'"
 | 
			
		||||
        :fill="isSegmentActive('g') ? segmentColor : inactiveColor"
 | 
			
		||||
        :style="{ opacity: isSegmentActive('g') ? 1 : 0.15 }" class="segment" />
 | 
			
		||||
        :style="{ opacity: isSegmentActive('g') ? 1 : 0.15 }"
 | 
			
		||||
        class="segment"
 | 
			
		||||
      />
 | 
			
		||||
      <!-- dp段 (小数点) -->
 | 
			
		||||
      <circle cx="108" cy="154" r="6" :fill="isSegmentActive('dp') ? segmentColor : inactiveColor"
 | 
			
		||||
        :style="{ opacity: isSegmentActive('dp') ? 1 : 0.15 }" class="segment" />
 | 
			
		||||
      <circle
 | 
			
		||||
        cx="108"
 | 
			
		||||
        cy="154"
 | 
			
		||||
        r="6"
 | 
			
		||||
        :fill="isSegmentActive('dp') ? segmentColor : inactiveColor"
 | 
			
		||||
        :style="{ opacity: isSegmentActive('dp') ? 1 : 0.15 }"
 | 
			
		||||
        class="segment"
 | 
			
		||||
      />
 | 
			
		||||
    </svg>
 | 
			
		||||
 | 
			
		||||
    <!-- 引脚 -->
 | 
			
		||||
    <div v-for="pin in pins" :key="pin.pinId" :style="{
 | 
			
		||||
      position: 'absolute',
 | 
			
		||||
      left: `${pin.x * props.size}px`,
 | 
			
		||||
      top: `${pin.y * props.size}px`,
 | 
			
		||||
      transform: 'translate(-50%, -50%)',
 | 
			
		||||
    }" :data-pin-wrapper="`${pin.pinId}`" :data-pin-x="`${pin.x * props.size}`"
 | 
			
		||||
      :data-pin-y="`${pin.y * props.size}`">
 | 
			
		||||
      <Pin :ref="(el) => {
 | 
			
		||||
          if (el) pinRefs[pin.pinId] = el;
 | 
			
		||||
        }
 | 
			
		||||
        " :label="pin.pinId" :constraint="pin.constraint" :pinId="pin.pinId" @pin-click="$emit('pin-click', $event)" />
 | 
			
		||||
    <div
 | 
			
		||||
      v-for="pin in pins"
 | 
			
		||||
      :key="pin.pinId"
 | 
			
		||||
      :style="{
 | 
			
		||||
        position: 'absolute',
 | 
			
		||||
        left: `${pin.x * props.size}px`,
 | 
			
		||||
        top: `${pin.y * props.size}px`,
 | 
			
		||||
        transform: 'translate(-50%, -50%)',
 | 
			
		||||
      }"
 | 
			
		||||
      :data-pin-wrapper="`${pin.pinId}`"
 | 
			
		||||
      :data-pin-x="`${pin.x * props.size}`"
 | 
			
		||||
      :data-pin-y="`${pin.y * props.size}`"
 | 
			
		||||
    >
 | 
			
		||||
      <Pin
 | 
			
		||||
        :ref="
 | 
			
		||||
          (el) => {
 | 
			
		||||
            if (el) pinRefs[pin.pinId] = el;
 | 
			
		||||
          }
 | 
			
		||||
        "
 | 
			
		||||
        :label="pin.pinId"
 | 
			
		||||
        :constraint="pin.constraint"
 | 
			
		||||
        :pinId="pin.pinId"
 | 
			
		||||
        @pin-click="$emit('pin-click', $event)"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -217,12 +266,12 @@ function isSegmentActive(
 | 
			
		||||
  if (isInAfterglowMode.value) {
 | 
			
		||||
    return afterglowStates.value[segment];
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  // 如果COM口未激活,所有段都不显示
 | 
			
		||||
  if (!currentComActive.value) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  // 否则使用稳定状态
 | 
			
		||||
  return stableSegmentStates.value[segment];
 | 
			
		||||
}
 | 
			
		||||
@@ -232,7 +281,7 @@ function updateSegmentStates() {
 | 
			
		||||
  // 先获取COM口状态
 | 
			
		||||
  const comPin = props.pins.find((p) => p.pinId === "COM");
 | 
			
		||||
  let comActive = false; // 默认未激活
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  if (comPin && comPin.constraint) {
 | 
			
		||||
    const comState = getConstraintState(comPin.constraint);
 | 
			
		||||
    if (props.cathodeType === "anode") {
 | 
			
		||||
@@ -274,7 +323,8 @@ function updateSegmentStates() {
 | 
			
		||||
  for (const pin of props.pins) {
 | 
			
		||||
    if (["a", "b", "c", "d", "e", "f", "g", "dp"].includes(pin.pinId)) {
 | 
			
		||||
      if (!pin.constraint) {
 | 
			
		||||
        segmentStates.value[pin.pinId as keyof typeof segmentStates.value] = false;
 | 
			
		||||
        segmentStates.value[pin.pinId as keyof typeof segmentStates.value] =
 | 
			
		||||
          false;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      const pinState = getConstraintState(pin.constraint);
 | 
			
		||||
@@ -285,7 +335,8 @@ function updateSegmentStates() {
 | 
			
		||||
        newState = pinState === "low";
 | 
			
		||||
      }
 | 
			
		||||
      // 段状态只有在COM激活时才有效
 | 
			
		||||
      segmentStates.value[pin.pinId as keyof typeof segmentStates.value] = newState;
 | 
			
		||||
      segmentStates.value[pin.pinId as keyof typeof segmentStates.value] =
 | 
			
		||||
        newState;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -328,22 +379,25 @@ function updateAfterglowBuffers() {
 | 
			
		||||
// 进入余晖模式
 | 
			
		||||
function enterAfterglowMode() {
 | 
			
		||||
  isInAfterglowMode.value = true;
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  // 保存当前稳定状态作为余晖状态
 | 
			
		||||
  for (const segmentId of ["a", "b", "c", "d", "e", "f", "g", "dp"]) {
 | 
			
		||||
    const typedSegmentId = segmentId as keyof typeof stableSegmentStates.value;
 | 
			
		||||
    afterglowStates.value[typedSegmentId] = stableSegmentStates.value[typedSegmentId];
 | 
			
		||||
    
 | 
			
		||||
    afterglowStates.value[typedSegmentId] =
 | 
			
		||||
      stableSegmentStates.value[typedSegmentId];
 | 
			
		||||
 | 
			
		||||
    // 设置定时器,在余晖持续时间后退出余晖模式
 | 
			
		||||
    if (afterglowTimers.value[segmentId]) {
 | 
			
		||||
      clearTimeout(afterglowTimers.value[segmentId]!);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    afterglowTimers.value[segmentId] = setTimeout(() => {
 | 
			
		||||
      afterglowStates.value[typedSegmentId] = false;
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      // 检查是否所有段都已经关闭
 | 
			
		||||
      const allSegmentsOff = Object.values(afterglowStates.value).every(state => !state);
 | 
			
		||||
      const allSegmentsOff = Object.values(afterglowStates.value).every(
 | 
			
		||||
        (state) => !state,
 | 
			
		||||
      );
 | 
			
		||||
      if (allSegmentsOff) {
 | 
			
		||||
        exitAfterglowMode();
 | 
			
		||||
      }
 | 
			
		||||
@@ -354,14 +408,14 @@ function enterAfterglowMode() {
 | 
			
		||||
// 退出余晖模式
 | 
			
		||||
function exitAfterglowMode() {
 | 
			
		||||
  isInAfterglowMode.value = false;
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  // 清除所有定时器
 | 
			
		||||
  for (const segmentId of ["a", "b", "c", "d", "e", "f", "g", "dp"]) {
 | 
			
		||||
    if (afterglowTimers.value[segmentId]) {
 | 
			
		||||
      clearTimeout(afterglowTimers.value[segmentId]!);
 | 
			
		||||
      afterglowTimers.value[segmentId] = null;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // 重置余晖状态
 | 
			
		||||
    const typedSegmentId = segmentId as keyof typeof afterglowStates.value;
 | 
			
		||||
    afterglowStates.value[typedSegmentId] = false;
 | 
			
		||||
@@ -397,11 +451,6 @@ onUnmounted(() => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 暴露属性和方法
 | 
			
		||||
defineExpose({
 | 
			
		||||
  updateSegmentStates,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
@@ -418,7 +467,8 @@ defineExpose({
 | 
			
		||||
 | 
			
		||||
/* 数码管发光效果 */
 | 
			
		||||
.segment[style*="opacity: 1"] {
 | 
			
		||||
  filter: drop-shadow(0 0 4px v-bind(segmentColor)) drop-shadow(0 0 2px v-bind(segmentColor));
 | 
			
		||||
  filter: drop-shadow(0 0 4px v-bind(segmentColor))
 | 
			
		||||
    drop-shadow(0 0 2px v-bind(segmentColor));
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										413
									
								
								src/components/equipments/SevenSegmentDisplayUltimate.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										413
									
								
								src/components/equipments/SevenSegmentDisplayUltimate.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,413 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    class="seven-segment-display"
 | 
			
		||||
    :style="{
 | 
			
		||||
      width: width + 'px',
 | 
			
		||||
      height: height + 'px',
 | 
			
		||||
      position: 'relative',
 | 
			
		||||
    }"
 | 
			
		||||
  >
 | 
			
		||||
    <svg
 | 
			
		||||
      xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
      :width="width"
 | 
			
		||||
      :height="height"
 | 
			
		||||
      viewBox="0 0 120 220"
 | 
			
		||||
      class="display"
 | 
			
		||||
    >
 | 
			
		||||
      <!-- 数码管基座 -->
 | 
			
		||||
      <rect width="120" height="180" x="0" y="0" fill="#222" rx="10" ry="10" />
 | 
			
		||||
      <rect width="110" height="170" x="5" y="5" fill="#333" rx="5" ry="5" />
 | 
			
		||||
 | 
			
		||||
      <!-- 7段显示 -->
 | 
			
		||||
      <polygon
 | 
			
		||||
        v-for="(segment, id) in segmentPaths"
 | 
			
		||||
        :key="id"
 | 
			
		||||
        :points="segment.points"
 | 
			
		||||
        :fill="isSegmentActive(id) ? segmentColor : inactiveColor"
 | 
			
		||||
        :style="{ opacity: isSegmentActive(id) ? 1 : 0.15 }"
 | 
			
		||||
        :class="{ segment: true, active: isSegmentActive(id) }"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <!-- 小数点 -->
 | 
			
		||||
      <circle
 | 
			
		||||
        cx="108"
 | 
			
		||||
        cy="154"
 | 
			
		||||
        r="6"
 | 
			
		||||
        :fill="isSegmentActive('dp') ? segmentColor : inactiveColor"
 | 
			
		||||
        :style="{ opacity: isSegmentActive('dp') ? 1 : 0.15 }"
 | 
			
		||||
        :class="{ segment: true, active: isSegmentActive('dp') }"
 | 
			
		||||
      />
 | 
			
		||||
    </svg>
 | 
			
		||||
 | 
			
		||||
    <!-- 引脚(仅在非数字孪生模式下显示) -->
 | 
			
		||||
    <div
 | 
			
		||||
      v-if="!props.enableDigitalTwin"
 | 
			
		||||
      v-for="pin in props.pins"
 | 
			
		||||
      :key="pin.pinId"
 | 
			
		||||
      :style="{
 | 
			
		||||
        position: 'absolute',
 | 
			
		||||
        left: `${pin.x * props.size}px`,
 | 
			
		||||
        top: `${pin.y * props.size}px`,
 | 
			
		||||
        transform: 'translate(-50%, -50%)',
 | 
			
		||||
      }"
 | 
			
		||||
      :data-pin-wrapper="`${pin.pinId}`"
 | 
			
		||||
      :data-pin-x="`${pin.x * props.size}`"
 | 
			
		||||
      :data-pin-y="`${pin.y * props.size}`"
 | 
			
		||||
    >
 | 
			
		||||
      <Pin
 | 
			
		||||
        :ref="
 | 
			
		||||
          (el) => {
 | 
			
		||||
            if (el) pinRefs[pin.pinId] = el;
 | 
			
		||||
          }
 | 
			
		||||
        "
 | 
			
		||||
        :label="pin.pinId"
 | 
			
		||||
        :constraint="pin.constraint"
 | 
			
		||||
        :pinId="pin.pinId"
 | 
			
		||||
        @pin-click="$emit('pin-click', $event)"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
 | 
			
		||||
import { useConstraintsStore } from "../../stores/constraints";
 | 
			
		||||
import Pin from "./Pin.vue";
 | 
			
		||||
import { useEquipments } from "@/stores/equipments";
 | 
			
		||||
 | 
			
		||||
// ============================================================================
 | 
			
		||||
// Linus式极简数据结构:一个byte解决一切
 | 
			
		||||
// ============================================================================
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  size?: number;
 | 
			
		||||
  color?: string;
 | 
			
		||||
  enableDigitalTwin?: boolean;
 | 
			
		||||
  digitalTwinNum?: number;
 | 
			
		||||
  afterglowDuration?: number;
 | 
			
		||||
  cathodeType?: "common" | "anode";
 | 
			
		||||
  pins?: Array<{
 | 
			
		||||
    pinId: string;
 | 
			
		||||
    constraint: string;
 | 
			
		||||
    x: number;
 | 
			
		||||
    y: number;
 | 
			
		||||
  }>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<Props>(), {
 | 
			
		||||
  size: 1,
 | 
			
		||||
  color: "red",
 | 
			
		||||
  enableDigitalTwin: false,
 | 
			
		||||
  digitalTwinNum: 0,
 | 
			
		||||
  afterglowDuration: 500,
 | 
			
		||||
  cathodeType: "common",
 | 
			
		||||
  pins: () => [
 | 
			
		||||
    { pinId: "a", constraint: "", x: 10, y: 170 },
 | 
			
		||||
    { pinId: "b", constraint: "", x: 24, y: 170 },
 | 
			
		||||
    { pinId: "c", constraint: "", x: 38, y: 170 },
 | 
			
		||||
    { pinId: "d", constraint: "", x: 52, y: 170 },
 | 
			
		||||
    { pinId: "e", constraint: "", x: 66, y: 170 },
 | 
			
		||||
    { pinId: "f", constraint: "", x: 80, y: 170 },
 | 
			
		||||
    { pinId: "g", constraint: "", x: 94, y: 170 },
 | 
			
		||||
    { pinId: "dp", constraint: "", x: 108, y: 170 },
 | 
			
		||||
    { pinId: "COM", constraint: "", x: 60, y: 10 },
 | 
			
		||||
  ],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// ============================================================================
 | 
			
		||||
// 核心状态:简单到极致
 | 
			
		||||
// ============================================================================
 | 
			
		||||
 | 
			
		||||
// 当前显示状态 - 8bit对应8个段
 | 
			
		||||
const displayByte = ref<number>(0);
 | 
			
		||||
 | 
			
		||||
// 余晖状态
 | 
			
		||||
const afterglowByte = ref<number>(0);
 | 
			
		||||
const afterglowTimer = ref<number | null>(null);
 | 
			
		||||
 | 
			
		||||
// 约束系统状态(兼容模式)
 | 
			
		||||
const constraintStates = ref<Record<string, boolean>>({
 | 
			
		||||
  a: false,
 | 
			
		||||
  b: false,
 | 
			
		||||
  c: false,
 | 
			
		||||
  d: false,
 | 
			
		||||
  e: false,
 | 
			
		||||
  f: false,
 | 
			
		||||
  g: false,
 | 
			
		||||
  dp: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// ============================================================================
 | 
			
		||||
// Bit操作:硬件工程师的好品味
 | 
			
		||||
// ============================================================================
 | 
			
		||||
 | 
			
		||||
// 段到bit位的映射 (标准7段数码管编码)
 | 
			
		||||
const SEGMENT_BITS = {
 | 
			
		||||
  a: 0, // bit 0
 | 
			
		||||
  b: 1, // bit 1
 | 
			
		||||
  c: 2, // bit 2
 | 
			
		||||
  d: 3, // bit 3
 | 
			
		||||
  e: 4, // bit 4
 | 
			
		||||
  f: 5, // bit 5
 | 
			
		||||
  g: 6, // bit 6
 | 
			
		||||
  dp: 7, // bit 7
 | 
			
		||||
} as const;
 | 
			
		||||
 | 
			
		||||
function isBitSet(byte: number, bit: number): boolean {
 | 
			
		||||
  return (byte & (1 << bit)) !== 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isSegmentActive(segmentId: keyof typeof SEGMENT_BITS): boolean {
 | 
			
		||||
  if (props.enableDigitalTwin) {
 | 
			
		||||
    // 数字孪生模式:余晖优先,然后是当前byte
 | 
			
		||||
    const bit = SEGMENT_BITS[segmentId];
 | 
			
		||||
    return (
 | 
			
		||||
      isBitSet(afterglowByte.value, bit) || isBitSet(displayByte.value, bit)
 | 
			
		||||
    );
 | 
			
		||||
  } else {
 | 
			
		||||
    // 约束模式:使用传统逻辑
 | 
			
		||||
    return constraintStates.value[segmentId] || false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ============================================================================
 | 
			
		||||
// SignalR数字孪生集成
 | 
			
		||||
// ============================================================================
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
  sevenSegmentDisplaySetOnOff,
 | 
			
		||||
  sevenSegmentDisplayData,
 | 
			
		||||
  sevenSegmentDisplaySetFrequency,
 | 
			
		||||
} = useEquipments();
 | 
			
		||||
 | 
			
		||||
async function initDigitalTwin() {
 | 
			
		||||
  if (!props.enableDigitalTwin || !props.digitalTwinNum) return;
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    sevenSegmentDisplaySetOnOff(props.enableDigitalTwin);
 | 
			
		||||
 | 
			
		||||
    console.log(
 | 
			
		||||
      `Digital twin initialized for address: ${props.digitalTwinNum}`,
 | 
			
		||||
    );
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.warn("Failed to initialize digital twin:", error);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => [sevenSegmentDisplayData],
 | 
			
		||||
  () => {
 | 
			
		||||
    if (
 | 
			
		||||
      !sevenSegmentDisplayData ||
 | 
			
		||||
      props.digitalTwinNum < 0 ||
 | 
			
		||||
      props.digitalTwinNum > 31
 | 
			
		||||
    )
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    handleDigitalTwinData(sevenSegmentDisplayData[props.digitalTwinNum]);
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
function handleDigitalTwinData(data: any) {
 | 
			
		||||
  let newByte = 0;
 | 
			
		||||
 | 
			
		||||
  if (typeof data === "number") {
 | 
			
		||||
    // 直接是byte数据
 | 
			
		||||
    newByte = data & 0xff; // 确保只取低8位
 | 
			
		||||
  } else if (data && typeof data.value === "number") {
 | 
			
		||||
    // 包装在对象中的byte数据
 | 
			
		||||
    newByte = data.value & 0xff;
 | 
			
		||||
  } else if (data && data.segments) {
 | 
			
		||||
    // 段状态对象格式
 | 
			
		||||
    Object.keys(SEGMENT_BITS).forEach((segment) => {
 | 
			
		||||
      if (data.segments[segment]) {
 | 
			
		||||
        newByte |= 1 << SEGMENT_BITS[segment as keyof typeof SEGMENT_BITS];
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updateDisplayByte(newByte);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateDisplayByte(newByte: number) {
 | 
			
		||||
  const oldByte = displayByte.value;
 | 
			
		||||
  displayByte.value = newByte;
 | 
			
		||||
 | 
			
		||||
  // 启动余晖效果
 | 
			
		||||
  if (oldByte !== 0 && newByte !== oldByte) {
 | 
			
		||||
    startAfterglow(oldByte);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function startAfterglow(byte: number) {
 | 
			
		||||
  afterglowByte.value = byte;
 | 
			
		||||
 | 
			
		||||
  if (afterglowTimer.value) {
 | 
			
		||||
    clearTimeout(afterglowTimer.value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  afterglowTimer.value = setTimeout(() => {
 | 
			
		||||
    afterglowByte.value = 0;
 | 
			
		||||
    afterglowTimer.value = null;
 | 
			
		||||
  }, props.afterglowDuration);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function cleanupDigitalTwin() {
 | 
			
		||||
  sevenSegmentDisplaySetOnOff(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ============================================================================
 | 
			
		||||
// 约束系统兼容(传统模式)
 | 
			
		||||
// ============================================================================
 | 
			
		||||
 | 
			
		||||
const { getConstraintState, onConstraintStateChange } = useConstraintsStore();
 | 
			
		||||
let constraintUnsubscribe: (() => void) | null = null;
 | 
			
		||||
 | 
			
		||||
function updateConstraintStates() {
 | 
			
		||||
  if (props.enableDigitalTwin) return; // 数字孪生模式下忽略约束
 | 
			
		||||
 | 
			
		||||
  // 获取COM状态
 | 
			
		||||
  const comPin = props.pins.find((p) => p.pinId === "COM");
 | 
			
		||||
  const comActive = isComActive(comPin);
 | 
			
		||||
 | 
			
		||||
  if (!comActive) {
 | 
			
		||||
    // COM不活跃,所有段关闭
 | 
			
		||||
    Object.keys(constraintStates.value).forEach((key) => {
 | 
			
		||||
      constraintStates.value[key] = false;
 | 
			
		||||
    });
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 更新各段状态
 | 
			
		||||
  props.pins.forEach((pin) => {
 | 
			
		||||
    if (Object.hasOwnProperty.call(SEGMENT_BITS, pin.pinId)) {
 | 
			
		||||
      constraintStates.value[pin.pinId] = isPinActive(pin);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isComActive(comPin: any): boolean {
 | 
			
		||||
  if (!comPin?.constraint) return true;
 | 
			
		||||
  const state = getConstraintState(comPin.constraint);
 | 
			
		||||
  return props.cathodeType === "common" ? state === "low" : state === "low";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isPinActive(pin: any): boolean {
 | 
			
		||||
  if (!pin.constraint) return false;
 | 
			
		||||
  const state = getConstraintState(pin.constraint);
 | 
			
		||||
  return props.cathodeType === "common" ? state === "high" : state === "low";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ============================================================================
 | 
			
		||||
// 渲染数据
 | 
			
		||||
// ============================================================================
 | 
			
		||||
 | 
			
		||||
const segmentPaths = {
 | 
			
		||||
  a: { points: "30,20 90,20 98,28 82,36 38,36 22,28" },
 | 
			
		||||
  b: { points: "100,30 108,38 108,82 100,90 92,82 92,38" },
 | 
			
		||||
  c: { points: "100,90 108,98 108,142 100,150 92,142 92,98" },
 | 
			
		||||
  d: { points: "30,160 90,160 98,152 82,144 38,144 22,152" },
 | 
			
		||||
  e: { points: "20,90 28,98 28,142 20,150 12,142 12,98" },
 | 
			
		||||
  f: { points: "20,30 28,38 28,82 20,90 12,82 12,38" },
 | 
			
		||||
  g: { points: "30,90 38,82 82,82 90,90 82,98 38,98" },
 | 
			
		||||
} as const;
 | 
			
		||||
 | 
			
		||||
// ============================================================================
 | 
			
		||||
// 计算属性
 | 
			
		||||
// ============================================================================
 | 
			
		||||
 | 
			
		||||
const width = computed(() => 120 * props.size);
 | 
			
		||||
const height = computed(() => 220 * props.size);
 | 
			
		||||
const segmentColor = computed(() => props.color);
 | 
			
		||||
const inactiveColor = computed(() => "#FFFFFF");
 | 
			
		||||
const pinRefs = ref<Record<string, any>>({});
 | 
			
		||||
 | 
			
		||||
// ============================================================================
 | 
			
		||||
// 生命周期
 | 
			
		||||
// ============================================================================
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  if (props.enableDigitalTwin) {
 | 
			
		||||
    await initDigitalTwin();
 | 
			
		||||
  } else {
 | 
			
		||||
    constraintUnsubscribe = onConstraintStateChange(updateConstraintStates);
 | 
			
		||||
    updateConstraintStates();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  cleanupDigitalTwin();
 | 
			
		||||
 | 
			
		||||
  if (constraintUnsubscribe) {
 | 
			
		||||
    constraintUnsubscribe();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (afterglowTimer.value) {
 | 
			
		||||
    clearTimeout(afterglowTimer.value);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 监听模式切换
 | 
			
		||||
watch(
 | 
			
		||||
  () => [props.enableDigitalTwin, props.digitalTwinNum],
 | 
			
		||||
  async () => {
 | 
			
		||||
    // 清理旧模式
 | 
			
		||||
    cleanupDigitalTwin();
 | 
			
		||||
    if (constraintUnsubscribe) {
 | 
			
		||||
      constraintUnsubscribe();
 | 
			
		||||
      constraintUnsubscribe = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 初始化新模式
 | 
			
		||||
    if (props.enableDigitalTwin) {
 | 
			
		||||
      await initDigitalTwin();
 | 
			
		||||
    } else {
 | 
			
		||||
      constraintUnsubscribe = onConstraintStateChange(updateConstraintStates);
 | 
			
		||||
      updateConstraintStates();
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: false },
 | 
			
		||||
);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.seven-segment-display {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.segment {
 | 
			
		||||
  transition:
 | 
			
		||||
    opacity 0.2s,
 | 
			
		||||
    fill 0.2s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.segment.active {
 | 
			
		||||
  filter: drop-shadow(0 0 4px v-bind(segmentColor))
 | 
			
		||||
    drop-shadow(0 0 2px v-bind(segmentColor));
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
export function getDefaultProps() {
 | 
			
		||||
  return {
 | 
			
		||||
    size: 1,
 | 
			
		||||
    color: "red",
 | 
			
		||||
    enableDigitalTwin: false,
 | 
			
		||||
    digitalTwinNum: 0,
 | 
			
		||||
    afterglowDuration: 500,
 | 
			
		||||
    cathodeType: "common",
 | 
			
		||||
    pins: [
 | 
			
		||||
      { pinId: "a", constraint: "", x: 10, y: 170 },
 | 
			
		||||
      { pinId: "b", constraint: "", x: 24, y: 170 },
 | 
			
		||||
      { pinId: "c", constraint: "", x: 38, y: 170 },
 | 
			
		||||
      { pinId: "d", constraint: "", x: 52, y: 170 },
 | 
			
		||||
      { pinId: "e", constraint: "", x: 66, y: 170 },
 | 
			
		||||
      { pinId: "f", constraint: "", x: 80, y: 170 },
 | 
			
		||||
      { pinId: "g", constraint: "", x: 94, y: 170 },
 | 
			
		||||
      { pinId: "dp", constraint: "", x: 108, y: 170 },
 | 
			
		||||
      { pinId: "COM", constraint: "", x: 60, y: 10 },
 | 
			
		||||
    ],
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -7,15 +7,21 @@ import { isNumber } from "mathjs";
 | 
			
		||||
import { Mutex, withTimeout } from "async-mutex";
 | 
			
		||||
import { useConstraintsStore } from "@/stores/constraints";
 | 
			
		||||
import { useDialogStore } from "./dialog";
 | 
			
		||||
import { toFileParameterOrUndefined } from "@/utils/Common";
 | 
			
		||||
import {
 | 
			
		||||
  base64ToArrayBuffer,
 | 
			
		||||
  toFileParameterOrUndefined,
 | 
			
		||||
} from "@/utils/Common";
 | 
			
		||||
import { AuthManager } from "@/utils/AuthManager";
 | 
			
		||||
import { HubConnection, HubConnectionBuilder } from "@microsoft/signalr";
 | 
			
		||||
import { HubConnection } from "@microsoft/signalr";
 | 
			
		||||
import {
 | 
			
		||||
  getHubProxyFactory,
 | 
			
		||||
  getReceiverRegister,
 | 
			
		||||
} from "@/utils/signalR/TypedSignalR.Client";
 | 
			
		||||
import { ResourcePurpose, type ResourceInfo } from "@/APIClient";
 | 
			
		||||
import type { IJtagHub } from "@/utils/signalR/TypedSignalR.Client/server.Hubs";
 | 
			
		||||
import type {
 | 
			
		||||
  IDigitalTubesHub,
 | 
			
		||||
  IJtagHub,
 | 
			
		||||
} from "@/utils/signalR/TypedSignalR.Client/server.Hubs";
 | 
			
		||||
 | 
			
		||||
export const useEquipments = defineStore("equipments", () => {
 | 
			
		||||
  // Global Stores
 | 
			
		||||
@@ -26,6 +32,7 @@ export const useEquipments = defineStore("equipments", () => {
 | 
			
		||||
  const boardPort = useLocalStorage("fpga-board-port", 1234);
 | 
			
		||||
 | 
			
		||||
  // Jtag
 | 
			
		||||
  const enableJtagBoundaryScan = ref(false);
 | 
			
		||||
  const jtagBitstream = ref<File>();
 | 
			
		||||
  const jtagBoundaryScanFreq = ref(100);
 | 
			
		||||
  const jtagUserBitstreams = ref<ResourceInfo[]>([]);
 | 
			
		||||
@@ -62,46 +69,6 @@ export const useEquipments = defineStore("equipments", () => {
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // Matrix Key
 | 
			
		||||
  const matrixKeyStates = reactive(new Array<boolean>(16).fill(false));
 | 
			
		||||
  const matrixKeypadClientMutex = withTimeout(
 | 
			
		||||
    new Mutex(),
 | 
			
		||||
    1000,
 | 
			
		||||
    new Error("Matrixkeyclient Mutex Timeout!"),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Power
 | 
			
		||||
  const powerClientMutex = withTimeout(
 | 
			
		||||
    new Mutex(),
 | 
			
		||||
    1000,
 | 
			
		||||
    new Error("Matrixkeyclient Mutex Timeout!"),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Enable Setting
 | 
			
		||||
  const enableJtagBoundaryScan = ref(false);
 | 
			
		||||
  const enableMatrixKey = ref(false);
 | 
			
		||||
  const enablePower = ref(false);
 | 
			
		||||
 | 
			
		||||
  function setMatrixKey(
 | 
			
		||||
    keyNum: number | string | undefined,
 | 
			
		||||
    keyValue: boolean,
 | 
			
		||||
  ): boolean {
 | 
			
		||||
    let _keyNum: number;
 | 
			
		||||
    if (isString(keyNum)) {
 | 
			
		||||
      _keyNum = toNumber(keyNum);
 | 
			
		||||
    } else if (isNumber(keyNum)) {
 | 
			
		||||
      _keyNum = keyNum;
 | 
			
		||||
    } else {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (z.number().nonnegative().max(16).safeParse(_keyNum).success) {
 | 
			
		||||
      matrixKeyStates[_keyNum] = keyValue;
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function jtagBoundaryScanSetOnOff(enable: boolean) {
 | 
			
		||||
    if (isUndefined(jtagHubProxy.value)) {
 | 
			
		||||
      console.error("JtagHub Not Initialize...");
 | 
			
		||||
@@ -223,6 +190,34 @@ export const useEquipments = defineStore("equipments", () => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Matrix Key
 | 
			
		||||
  const enableMatrixKey = ref(false);
 | 
			
		||||
  const matrixKeyStates = reactive(new Array<boolean>(16).fill(false));
 | 
			
		||||
  const matrixKeypadClientMutex = withTimeout(
 | 
			
		||||
    new Mutex(),
 | 
			
		||||
    1000,
 | 
			
		||||
    new Error("Matrixkeyclient Mutex Timeout!"),
 | 
			
		||||
  );
 | 
			
		||||
  function setMatrixKey(
 | 
			
		||||
    keyNum: number | string | undefined,
 | 
			
		||||
    keyValue: boolean,
 | 
			
		||||
  ): boolean {
 | 
			
		||||
    let _keyNum: number;
 | 
			
		||||
    if (isString(keyNum)) {
 | 
			
		||||
      _keyNum = toNumber(keyNum);
 | 
			
		||||
    } else if (isNumber(keyNum)) {
 | 
			
		||||
      _keyNum = keyNum;
 | 
			
		||||
    } else {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (z.number().nonnegative().max(16).safeParse(_keyNum).success) {
 | 
			
		||||
      matrixKeyStates[_keyNum] = keyValue;
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function matrixKeypadSetKeyStates(keyStates: boolean[]) {
 | 
			
		||||
    const release = await matrixKeypadClientMutex.acquire();
 | 
			
		||||
    console.log("set Key !!!!!!!!!!!!");
 | 
			
		||||
@@ -274,6 +269,13 @@ export const useEquipments = defineStore("equipments", () => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Power
 | 
			
		||||
  const powerClientMutex = withTimeout(
 | 
			
		||||
    new Mutex(),
 | 
			
		||||
    1000,
 | 
			
		||||
    new Error("Matrixkeyclient Mutex Timeout!"),
 | 
			
		||||
  );
 | 
			
		||||
  const enablePower = ref(false);
 | 
			
		||||
  async function powerSetOnOff(enable: boolean) {
 | 
			
		||||
    const release = await powerClientMutex.acquire();
 | 
			
		||||
    try {
 | 
			
		||||
@@ -293,6 +295,71 @@ export const useEquipments = defineStore("equipments", () => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Seven Segment Display
 | 
			
		||||
  const enableSevenSegmentDisplay = ref(false);
 | 
			
		||||
  const sevenSegmentDisplayFrequency = ref(100);
 | 
			
		||||
  const sevenSegmentDisplayData = ref<Uint8Array>();
 | 
			
		||||
  const sevenSegmentDisplayHub = ref<HubConnection>();
 | 
			
		||||
  const sevenSegmentDisplayHubProxy = ref<IDigitalTubesHub>();
 | 
			
		||||
 | 
			
		||||
  async function sevenSegmentDisplaySetOnOff(enable: boolean) {
 | 
			
		||||
    if (!sevenSegmentDisplayHub.value || !sevenSegmentDisplayHubProxy.value)
 | 
			
		||||
      return;
 | 
			
		||||
    await sevenSegmentDisplayHub.value.start();
 | 
			
		||||
 | 
			
		||||
    if (enable) {
 | 
			
		||||
      await sevenSegmentDisplayHubProxy.value.startScan();
 | 
			
		||||
    } else {
 | 
			
		||||
      await sevenSegmentDisplayHubProxy.value.stopScan();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function sevenSegmentDisplaySetFrequency(frequency: number) {
 | 
			
		||||
    if (!sevenSegmentDisplayHub.value || !sevenSegmentDisplayHubProxy.value)
 | 
			
		||||
      return;
 | 
			
		||||
    await sevenSegmentDisplayHub.value.start();
 | 
			
		||||
 | 
			
		||||
    await sevenSegmentDisplayHubProxy.value.setFrequency(frequency);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function sevenSegmentDisplayGetStatus() {
 | 
			
		||||
    if (!sevenSegmentDisplayHub.value || !sevenSegmentDisplayHubProxy.value)
 | 
			
		||||
      return;
 | 
			
		||||
    await sevenSegmentDisplayHub.value.start();
 | 
			
		||||
 | 
			
		||||
    return await sevenSegmentDisplayHubProxy.value.getStatus();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function handleSevenSegmentDisplayOnReceive(msg: string) {
 | 
			
		||||
    const bytes = base64ToArrayBuffer(msg);
 | 
			
		||||
    sevenSegmentDisplayData.value = new Uint8Array(bytes);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onMounted(async () => {
 | 
			
		||||
    // 每次挂载都重新创建连接
 | 
			
		||||
    sevenSegmentDisplayHub.value =
 | 
			
		||||
      AuthManager.createAuthenticatedJtagHubConnection();
 | 
			
		||||
    sevenSegmentDisplayHubProxy.value = getHubProxyFactory(
 | 
			
		||||
      "IDigitalTubesHub",
 | 
			
		||||
    ).createHubProxy(sevenSegmentDisplayHub.value);
 | 
			
		||||
 | 
			
		||||
    getReceiverRegister("IDigitalTubesReceiver").register(
 | 
			
		||||
      sevenSegmentDisplayHub.value,
 | 
			
		||||
      {
 | 
			
		||||
        onReceive: handleSevenSegmentDisplayOnReceive,
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  onUnmounted(() => {
 | 
			
		||||
    // 断开连接,清理资源
 | 
			
		||||
    if (sevenSegmentDisplayHub.value) {
 | 
			
		||||
      sevenSegmentDisplayHub.value.stop();
 | 
			
		||||
      sevenSegmentDisplayHub.value = undefined;
 | 
			
		||||
      sevenSegmentDisplayHubProxy.value = undefined;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    boardAddr,
 | 
			
		||||
    boardPort,
 | 
			
		||||
@@ -320,5 +387,13 @@ export const useEquipments = defineStore("equipments", () => {
 | 
			
		||||
    enablePower,
 | 
			
		||||
    powerClientMutex,
 | 
			
		||||
    powerSetOnOff,
 | 
			
		||||
 | 
			
		||||
    // Seven Segment Display
 | 
			
		||||
    enableSevenSegmentDisplay,
 | 
			
		||||
    sevenSegmentDisplayData,
 | 
			
		||||
    sevenSegmentDisplayFrequency,
 | 
			
		||||
    sevenSegmentDisplaySetOnOff,
 | 
			
		||||
    sevenSegmentDisplaySetFrequency,
 | 
			
		||||
    sevenSegmentDisplayGetStatus,
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -229,6 +229,15 @@ export class AuthManager {
 | 
			
		||||
      .build();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static createAuthenticatedDigitalTubesHubConnection() {
 | 
			
		||||
    return new HubConnectionBuilder()
 | 
			
		||||
      .withUrl("http://127.0.0.1:5000/hubs/DigitalTubesHub", {
 | 
			
		||||
        accessTokenFactory: () => this.getToken() ?? "",
 | 
			
		||||
      })
 | 
			
		||||
      .withAutomaticReconnect()
 | 
			
		||||
      .build();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 登录函数
 | 
			
		||||
  public static async login(
 | 
			
		||||
    username: string,
 | 
			
		||||
 
 | 
			
		||||
@@ -59,3 +59,12 @@ export function formatDate(date: Date | string) {
 | 
			
		||||
    minute: "2-digit",
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function base64ToArrayBuffer(base64: string) {
 | 
			
		||||
  var binaryString = atob(base64);
 | 
			
		||||
  var bytes = new Uint8Array(binaryString.length);
 | 
			
		||||
  for (var i = 0; i < binaryString.length; i++) {
 | 
			
		||||
    bytes[i] = binaryString.charCodeAt(i);
 | 
			
		||||
  }
 | 
			
		||||
  return bytes.buffer;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
// @ts-nocheck
 | 
			
		||||
import type { HubConnection, IStreamResult, Subject } from '@microsoft/signalr';
 | 
			
		||||
import type { IDigitalTubesHub, IJtagHub, IProgressHub, IDigitalTubesReceiver, IJtagReceiver, IProgressReceiver } from './server.Hubs';
 | 
			
		||||
import type { ProgressInfo } from '../server.Hubs';
 | 
			
		||||
import type { DigitalTubeTaskStatus, ProgressInfo } from '../server.Hubs';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// components
 | 
			
		||||
@@ -107,6 +107,10 @@ class IDigitalTubesHub_HubProxy implements IDigitalTubesHub {
 | 
			
		||||
    public readonly setFrequency = async (frequency: number): Promise<boolean> => {
 | 
			
		||||
        return await this.connection.invoke("SetFrequency", frequency);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public readonly getStatus = async (): Promise<DigitalTubeTaskStatus> => {
 | 
			
		||||
        return await this.connection.invoke("GetStatus");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class IJtagHub_HubProxyFactory implements HubProxyFactory<IJtagHub> {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
// @ts-nocheck
 | 
			
		||||
import type { IStreamResult, Subject } from '@microsoft/signalr';
 | 
			
		||||
import type { ProgressInfo } from '../server.Hubs';
 | 
			
		||||
import type { DigitalTubeTaskStatus, ProgressInfo } from '../server.Hubs';
 | 
			
		||||
 | 
			
		||||
export type IDigitalTubesHub = {
 | 
			
		||||
    /**
 | 
			
		||||
@@ -19,6 +19,10 @@ export type IDigitalTubesHub = {
 | 
			
		||||
    * @returns Transpiled from System.Threading.Tasks.Task<bool>
 | 
			
		||||
    */
 | 
			
		||||
    setFrequency(frequency: number): Promise<boolean>;
 | 
			
		||||
    /**
 | 
			
		||||
    * @returns Transpiled from System.Threading.Tasks.Task<server.Hubs.DigitalTubeTaskStatus>
 | 
			
		||||
    */
 | 
			
		||||
    getStatus(): Promise<DigitalTubeTaskStatus>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type IJtagHub = {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,14 @@
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
 | 
			
		||||
/** Transpiled from server.Hubs.DigitalTubeTaskStatus */
 | 
			
		||||
export type DigitalTubeTaskStatus = {
 | 
			
		||||
    /** Transpiled from int */
 | 
			
		||||
    frequency: number;
 | 
			
		||||
    /** Transpiled from bool */
 | 
			
		||||
    isRunning: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Transpiled from server.Hubs.ProgressStatus */
 | 
			
		||||
export enum ProgressStatus {
 | 
			
		||||
    Pending = 0,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user