feat: frontend add virtual matrix key
This commit is contained in:
		
							
								
								
									
										13
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -22,6 +22,7 @@
 | 
			
		||||
        "ts-results-es": "^5.0.1",
 | 
			
		||||
        "vue": "^3.5.13",
 | 
			
		||||
        "vue-router": "4",
 | 
			
		||||
        "yocto-queue": "^1.2.1",
 | 
			
		||||
        "zod": "^3.24.2"
 | 
			
		||||
      },
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
@@ -4075,6 +4076,18 @@
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "ISC"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/yocto-queue": {
 | 
			
		||||
      "version": "1.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12.20"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/yoctocolors": {
 | 
			
		||||
      "version": "2.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@
 | 
			
		||||
    "ts-results-es": "^5.0.1",
 | 
			
		||||
    "vue": "^3.5.13",
 | 
			
		||||
    "vue-router": "4",
 | 
			
		||||
    "yocto-queue": "^1.2.1",
 | 
			
		||||
    "zod": "^3.24.2"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										298
									
								
								public/EquipmentTemplates/MatrixKey.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								public/EquipmentTemplates/MatrixKey.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,298 @@
 | 
			
		||||
{
 | 
			
		||||
  "version": 1,
 | 
			
		||||
  "author": "template",
 | 
			
		||||
  "editor": "system",
 | 
			
		||||
  "parts": [
 | 
			
		||||
    {
 | 
			
		||||
      "id": "board",
 | 
			
		||||
      "type": "BaseBoard",
 | 
			
		||||
      "x": 0,
 | 
			
		||||
      "y": 0,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 1.2,
 | 
			
		||||
        "width": 400,
 | 
			
		||||
        "height": 400,
 | 
			
		||||
        "roundCorner": 20
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": false,
 | 
			
		||||
      "isOn": true,
 | 
			
		||||
      "index": 0
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_0_0",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 50,
 | 
			
		||||
      "y": 50,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "1",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 0
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_0_1",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 150,
 | 
			
		||||
      "y": 50,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "2",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 1
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_0_2",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 250,
 | 
			
		||||
      "y": 50,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "3",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 2
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_0_3",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 350,
 | 
			
		||||
      "y": 50,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "A",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 3
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_1_0",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 50,
 | 
			
		||||
      "y": 150,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "4",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 4
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_1_1",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 150,
 | 
			
		||||
      "y": 150,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "5",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 5
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_1_2",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 250,
 | 
			
		||||
      "y": 150,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "6",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 6
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_1_3",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 350,
 | 
			
		||||
      "y": 150,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "B",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 7
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_2_0",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 50,
 | 
			
		||||
      "y": 250,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "7",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 8
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_2_1",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 150,
 | 
			
		||||
      "y": 250,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "8",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 9
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_2_2",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 250,
 | 
			
		||||
      "y": 250,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "9",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 10
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_2_3",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 350,
 | 
			
		||||
      "y": 250,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "C",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 11
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_3_0",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 50,
 | 
			
		||||
      "y": 350,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "*",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 12
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_3_1",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 150,
 | 
			
		||||
      "y": 350,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "0",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 13
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_3_2",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 250,
 | 
			
		||||
      "y": 350,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "#",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 14
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "key_3_3",
 | 
			
		||||
      "type": "MechanicalButton",
 | 
			
		||||
      "x": 350,
 | 
			
		||||
      "y": 350,
 | 
			
		||||
      "attrs": {
 | 
			
		||||
        "size": 0.5,
 | 
			
		||||
        "bindKey": "D",
 | 
			
		||||
        "pins": []
 | 
			
		||||
      },
 | 
			
		||||
      "rotate": 0,
 | 
			
		||||
      "group": "MatrixKeypad",
 | 
			
		||||
      "positionlock": false,
 | 
			
		||||
      "hidepins": true,
 | 
			
		||||
      "isOn": false,
 | 
			
		||||
      "index": 15
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "connections": []
 | 
			
		||||
}
 | 
			
		||||
@@ -52,6 +52,7 @@ try
 | 
			
		||||
    {
 | 
			
		||||
        options.AddPolicy("Users", policy => policy
 | 
			
		||||
            .AllowAnyOrigin()
 | 
			
		||||
            .AllowAnyHeader()
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ public class BoundaryScanRegs
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [JsonProperty("cell_name")]
 | 
			
		||||
        [JsonRequired]
 | 
			
		||||
        public string CellName { get; set; }
 | 
			
		||||
        public string CellName { get; set; } = "UnknownCellName";
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// [TODO:description]
 | 
			
		||||
@@ -146,7 +146,8 @@ public class Parser
 | 
			
		||||
    /// <returns>[TODO:return]</returns>
 | 
			
		||||
    public Optional<List<BoundaryScanRegs.CellEntry>> GetBoundaryLogicalPorts()
 | 
			
		||||
    {
 | 
			
		||||
        var registers = this.BoundaryRegsDesp["registers"]?.ToList().Where((item)=>{
 | 
			
		||||
        var registers = this.BoundaryRegsDesp["registers"]?.ToList().Where((item) =>
 | 
			
		||||
        {
 | 
			
		||||
            return item["port_id"] is not null;
 | 
			
		||||
        });
 | 
			
		||||
        if (registers is null) return new();
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ public class MatrixKeyController : ControllerBase
 | 
			
		||||
    /// <returns>返回操作结果的状态码</returns>
 | 
			
		||||
    [HttpPost("EnabelMatrixKey")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(uint), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public async ValueTask<IResult> EnabelMatrixKey(string address, int port)
 | 
			
		||||
    {
 | 
			
		||||
@@ -48,7 +48,7 @@ public class MatrixKeyController : ControllerBase
 | 
			
		||||
    /// <returns>返回操作结果的状态码</returns>
 | 
			
		||||
    [HttpPost("DisableMatrixKey")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(uint), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public async ValueTask<IResult> DisableMatrixKey(string address, int port)
 | 
			
		||||
    {
 | 
			
		||||
@@ -76,7 +76,7 @@ public class MatrixKeyController : ControllerBase
 | 
			
		||||
    /// <returns>返回操作结果的状态码</returns>
 | 
			
		||||
    [HttpPost("SetMatrixKeyStatus")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(uint), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public async ValueTask<IResult> SetMatrixKeyStatus(string address, int port, [FromBody] bool[] keyStates)
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -955,7 +955,13 @@ export class MatrixKeyClient {
 | 
			
		||||
        this.baseUrl = baseUrl ?? "http://localhost:5000";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enabelMatrixKey(address: string | undefined, port: number | undefined): Promise<number> {
 | 
			
		||||
    /**
 | 
			
		||||
     * 启用矩阵键控制。
 | 
			
		||||
     * @param address (optional) 设备的IP地址
 | 
			
		||||
     * @param port (optional) 设备的端口号
 | 
			
		||||
     * @return 返回操作结果的状态码
 | 
			
		||||
     */
 | 
			
		||||
    enabelMatrixKey(address: string | undefined, port: number | undefined): Promise<boolean> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/MatrixKey/EnabelMatrixKey?";
 | 
			
		||||
        if (address === null)
 | 
			
		||||
            throw new Error("The parameter 'address' cannot be null.");
 | 
			
		||||
@@ -968,7 +974,7 @@ export class MatrixKeyClient {
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            method: "GET",
 | 
			
		||||
            method: "POST",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
            }
 | 
			
		||||
@@ -979,7 +985,7 @@ export class MatrixKeyClient {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processEnabelMatrixKey(response: Response): Promise<number> {
 | 
			
		||||
    protected processEnabelMatrixKey(response: Response): Promise<boolean> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
@@ -1002,10 +1008,16 @@ export class MatrixKeyClient {
 | 
			
		||||
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<number>(null as any);
 | 
			
		||||
        return Promise.resolve<boolean>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    disableMatrixKey(address: string | undefined, port: number | undefined): Promise<number> {
 | 
			
		||||
    /**
 | 
			
		||||
     * 禁用矩阵键控制。
 | 
			
		||||
     * @param address (optional) 设备的IP地址
 | 
			
		||||
     * @param port (optional) 设备的端口号
 | 
			
		||||
     * @return 返回操作结果的状态码
 | 
			
		||||
     */
 | 
			
		||||
    disableMatrixKey(address: string | undefined, port: number | undefined): Promise<boolean> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/MatrixKey/DisableMatrixKey?";
 | 
			
		||||
        if (address === null)
 | 
			
		||||
            throw new Error("The parameter 'address' cannot be null.");
 | 
			
		||||
@@ -1018,7 +1030,7 @@ export class MatrixKeyClient {
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            method: "GET",
 | 
			
		||||
            method: "POST",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
            }
 | 
			
		||||
@@ -1029,7 +1041,7 @@ export class MatrixKeyClient {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processDisableMatrixKey(response: Response): Promise<number> {
 | 
			
		||||
    protected processDisableMatrixKey(response: Response): Promise<boolean> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
@@ -1052,10 +1064,17 @@ export class MatrixKeyClient {
 | 
			
		||||
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<number>(null as any);
 | 
			
		||||
        return Promise.resolve<boolean>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setMatrixKeyStatus(address: string | undefined, port: number | undefined, keyStates: boolean[]): Promise<number> {
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置矩阵键的状态。
 | 
			
		||||
     * @param address (optional) 设备的IP地址
 | 
			
		||||
     * @param port (optional) 设备的端口号
 | 
			
		||||
     * @param keyStates 矩阵键的状态数组,长度应为16
 | 
			
		||||
     * @return 返回操作结果的状态码
 | 
			
		||||
     */
 | 
			
		||||
    setMatrixKeyStatus(address: string | undefined, port: number | undefined, keyStates: boolean[]): Promise<boolean> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/MatrixKey/SetMatrixKeyStatus?";
 | 
			
		||||
        if (address === null)
 | 
			
		||||
            throw new Error("The parameter 'address' cannot be null.");
 | 
			
		||||
@@ -1071,7 +1090,7 @@ export class MatrixKeyClient {
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            body: content_,
 | 
			
		||||
            method: "GET",
 | 
			
		||||
            method: "POST",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Content-Type": "application/json",
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
@@ -1083,7 +1102,7 @@ export class MatrixKeyClient {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processSetMatrixKeyStatus(response: Response): Promise<number> {
 | 
			
		||||
    protected processSetMatrixKeyStatus(response: Response): Promise<boolean> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
@@ -1106,7 +1125,7 @@ export class MatrixKeyClient {
 | 
			
		||||
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<number>(null as any);
 | 
			
		||||
        return Promise.resolve<boolean>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -182,6 +182,7 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, computed, shallowRef, onMounted } from "vue";
 | 
			
		||||
import motherboardSvg from "../components/equipments/svg/motherboard.svg";
 | 
			
		||||
import buttonSvg from "../components//equipments/svg/button.svg";
 | 
			
		||||
 | 
			
		||||
// Props 定义
 | 
			
		||||
interface Props {
 | 
			
		||||
@@ -219,6 +220,7 @@ const availableComponents = [
 | 
			
		||||
  { type: "SMA", name: "SMA连接器" },
 | 
			
		||||
  { type: "MotherBoard", name: "主板" },
 | 
			
		||||
  { type: "PG2L100H_FBG676", name: "PG2L100H FBG676芯片" },
 | 
			
		||||
  { type: "BaseBoard", name: "通用底板" },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// --- 可用虚拟外设列表 ---
 | 
			
		||||
@@ -233,6 +235,13 @@ const availableTemplates = ref([
 | 
			
		||||
    path: "/EquipmentTemplates/PG2L100H_Pango100pro.json",
 | 
			
		||||
    thumbnailUrl: motherboardSvg,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: "矩阵键盘",
 | 
			
		||||
    id: "MatrixKey",
 | 
			
		||||
    description: "包含4x4,共16个按键的矩阵键盘",
 | 
			
		||||
    path: "/EquipmentTemplates/MatrixKey.json",
 | 
			
		||||
    thumbnailUrl: buttonSvg,
 | 
			
		||||
  },
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
// 显示/隐藏组件菜单
 | 
			
		||||
@@ -372,6 +381,7 @@ async function addTemplate(template: any) {
 | 
			
		||||
      id: template.id,
 | 
			
		||||
      name: template.name,
 | 
			
		||||
      template: templateData,
 | 
			
		||||
      capsPage: template.capsPage
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 关闭菜单
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { useDialogStore } from "@/stores/dialog";
 | 
			
		||||
const dialog = useDialogStore();
 | 
			
		||||
dialog.enableDialog = true;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="postcss">
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								src/components/equipments/BaseBoard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/components/equipments/BaseBoard.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" :class="$attrs">
 | 
			
		||||
    <rect :width="width" :height="height" :rx="props.roundCorner" fill="#222222" />
 | 
			
		||||
  </svg>
 | 
			
		||||
  <Teleport to="#ComponentCapabilities" v-if="selectecComponentID === props.componentId && !!slot.default">
 | 
			
		||||
    <slot></slot>
 | 
			
		||||
  </Teleport>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, computed, inject } from "vue";
 | 
			
		||||
import { CanvasCurrentSelectedComponentID } from "../InjectKeys";
 | 
			
		||||
 | 
			
		||||
export interface Props {
 | 
			
		||||
  size?: number;
 | 
			
		||||
  width?: number;
 | 
			
		||||
  height?: number;
 | 
			
		||||
  roundCorner?: number;
 | 
			
		||||
  componentId?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const slot = defineSlots();
 | 
			
		||||
const props = withDefaults(defineProps<Props>(), getDefaultProps());
 | 
			
		||||
const selectecComponentID = inject(CanvasCurrentSelectedComponentID, ref(null));
 | 
			
		||||
 | 
			
		||||
// 计算实际宽高
 | 
			
		||||
const width = computed(() => props.width * props.size);
 | 
			
		||||
const height = computed(() => props.height * props.size);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
export function getDefaultProps(): Props {
 | 
			
		||||
  return {
 | 
			
		||||
    size: 1,
 | 
			
		||||
    width: 200,
 | 
			
		||||
    height: 200,
 | 
			
		||||
    roundCorner: 20,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="postcss"></style>
 | 
			
		||||
@@ -1,16 +1,28 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="button-container" :style="{
 | 
			
		||||
    width: width + 'px',
 | 
			
		||||
    height: height + 'px',
 | 
			
		||||
    position: 'relative',
 | 
			
		||||
  }">
 | 
			
		||||
    <svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" viewBox="400 400 800 800"
 | 
			
		||||
      class="mechanical-button">
 | 
			
		||||
  <div
 | 
			
		||||
    class="button-container"
 | 
			
		||||
    :style="{
 | 
			
		||||
      width: width + 'px',
 | 
			
		||||
      height: height + 'px',
 | 
			
		||||
      position: 'relative',
 | 
			
		||||
    }"
 | 
			
		||||
  >
 | 
			
		||||
    <svg
 | 
			
		||||
      xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
      :width="width"
 | 
			
		||||
      :height="height"
 | 
			
		||||
      viewBox="400 400 800 800"
 | 
			
		||||
      class="mechanical-button"
 | 
			
		||||
    >
 | 
			
		||||
      <!-- defs 和按钮底座保持不变 -->
 | 
			
		||||
      <defs>
 | 
			
		||||
        <filter id="btn-shadow">
 | 
			
		||||
          <feGaussianBlur in="SourceAlpha" stdDeviation="20" result="blur" />
 | 
			
		||||
          <feColorMatrix result="bluralpha" type="matrix" :values="colorMatrix" />
 | 
			
		||||
          <feColorMatrix
 | 
			
		||||
            result="bluralpha"
 | 
			
		||||
            type="matrix"
 | 
			
		||||
            :values="colorMatrix"
 | 
			
		||||
          />
 | 
			
		||||
          <feOffset in="bluralpha" dx="20" dy="20" result="offsetBlur" />
 | 
			
		||||
          <feMerge>
 | 
			
		||||
            <feMergeNode in="offsetBlur" />
 | 
			
		||||
@@ -37,42 +49,81 @@
 | 
			
		||||
      <circle r="20" cx="525" cy="1075" fill="#171717" />
 | 
			
		||||
 | 
			
		||||
      <!-- 按钮主体 -->
 | 
			
		||||
      <circle r="220" cx="800" cy="800" fill="black" filter="url(#btn-shadow)" />
 | 
			
		||||
      <circle :r="btnHeight" cx="800" cy="800" :fill="isKeyPressed ? 'url(#pressed)' : 'url(#normal)'"
 | 
			
		||||
        fill-opacity="0.9" @mousedown="toggleButtonState(true)" @mouseup="toggleButtonState(false)"
 | 
			
		||||
        @mouseleave="toggleButtonState(false)" style="
 | 
			
		||||
      <circle
 | 
			
		||||
        r="220"
 | 
			
		||||
        cx="800"
 | 
			
		||||
        cy="800"
 | 
			
		||||
        fill="black"
 | 
			
		||||
        filter="url(#btn-shadow)"
 | 
			
		||||
      />
 | 
			
		||||
      <circle
 | 
			
		||||
        :r="btnHeight"
 | 
			
		||||
        cx="800"
 | 
			
		||||
        cy="800"
 | 
			
		||||
        :fill="isKeyPressed ? 'url(#pressed)' : 'url(#normal)'"
 | 
			
		||||
        fill-opacity="0.9"
 | 
			
		||||
        @mousedown="toggleButtonState(true)"
 | 
			
		||||
        @mouseup="toggleButtonState(false)"
 | 
			
		||||
        @mouseleave="toggleButtonState(false)"
 | 
			
		||||
        style="
 | 
			
		||||
          pointer-events: auto;
 | 
			
		||||
          transition: all 20ms ease-in-out;
 | 
			
		||||
          cursor: pointer;
 | 
			
		||||
        " />
 | 
			
		||||
        "
 | 
			
		||||
      />
 | 
			
		||||
      <!-- 按键文字 - 仅显示绑定的按键 -->
 | 
			
		||||
      <text v-if="bindKeyDisplay" x="800" y="800" font-size="310" text-anchor="middle" dominant-baseline="central"
 | 
			
		||||
        fill="#ccc" style="
 | 
			
		||||
      <text
 | 
			
		||||
        v-if="bindKeyDisplay"
 | 
			
		||||
        x="800"
 | 
			
		||||
        y="800"
 | 
			
		||||
        font-size="310"
 | 
			
		||||
        text-anchor="middle"
 | 
			
		||||
        dominant-baseline="central"
 | 
			
		||||
        fill="#ccc"
 | 
			
		||||
        style="
 | 
			
		||||
          font-family: Arial;
 | 
			
		||||
          filter: url(#btn-shadow);
 | 
			
		||||
          user-select: none;
 | 
			
		||||
          pointer-events: none;
 | 
			
		||||
          mix-blend-mode: overlay;
 | 
			
		||||
        ">
 | 
			
		||||
        "
 | 
			
		||||
      >
 | 
			
		||||
        {{ bindKeyDisplay }}
 | 
			
		||||
      </text>
 | 
			
		||||
    </svg>
 | 
			
		||||
 | 
			
		||||
    <!-- 渲染自定义引脚数组 -->
 | 
			
		||||
    <div 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%)',
 | 
			
		||||
      zIndex: 3,
 | 
			
		||||
      pointerEvents: 'auto',
 | 
			
		||||
    }" :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;
 | 
			
		||||
        }
 | 
			
		||||
        " direction="output" type="digital" :label="pin.pinId" :constraint="pin.constraint" :pinId="pin.pinId"
 | 
			
		||||
        :size="0.8" :componentId="props.componentId" @value-change="handlePinValueChange" @pin-click="handlePinClick" />
 | 
			
		||||
    <div
 | 
			
		||||
      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%)',
 | 
			
		||||
        zIndex: 3,
 | 
			
		||||
        pointerEvents: 'auto',
 | 
			
		||||
      }"
 | 
			
		||||
      :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;
 | 
			
		||||
          }
 | 
			
		||||
        "
 | 
			
		||||
        direction="output"
 | 
			
		||||
        type="digital"
 | 
			
		||||
        :label="pin.pinId"
 | 
			
		||||
        :constraint="pin.constraint"
 | 
			
		||||
        :pinId="pin.pinId"
 | 
			
		||||
        :size="0.8"
 | 
			
		||||
        :componentId="props.componentId"
 | 
			
		||||
        @value-change="handlePinValueChange"
 | 
			
		||||
        @pin-click="handlePinClick"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -80,16 +131,16 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, onMounted, onUnmounted, computed } from "vue";
 | 
			
		||||
import Pin from "./Pin.vue";
 | 
			
		||||
import { useEquipments } from "@/stores/equipments";
 | 
			
		||||
import { useDialogStore } from "@/stores/dialog";
 | 
			
		||||
import { useConstraintsStore } from "../../stores/constraints";
 | 
			
		||||
const { notifyConstraintChange } = useConstraintsStore();
 | 
			
		||||
 | 
			
		||||
// 存储多个Pin引用
 | 
			
		||||
const pinRefs = ref<Record<string, any>>({});
 | 
			
		||||
import { isNull, isUndefined } from "mathjs";
 | 
			
		||||
import z from "zod";
 | 
			
		||||
import { toNumber } from "lodash";
 | 
			
		||||
 | 
			
		||||
// 按钮特有属性
 | 
			
		||||
interface ButtonProps {
 | 
			
		||||
  size?: number;
 | 
			
		||||
  bindKey?: string;
 | 
			
		||||
export interface ButtonProps {
 | 
			
		||||
  size: number;
 | 
			
		||||
  componentId?: string;
 | 
			
		||||
  pins?: {
 | 
			
		||||
    pinId: string;
 | 
			
		||||
@@ -97,21 +148,20 @@ interface ButtonProps {
 | 
			
		||||
    x: number;
 | 
			
		||||
    y: number;
 | 
			
		||||
  }[];
 | 
			
		||||
 | 
			
		||||
  bindKey?: string;
 | 
			
		||||
  bindMatrixKey?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<ButtonProps>(), {
 | 
			
		||||
  size: 1,
 | 
			
		||||
  bindKey: "",
 | 
			
		||||
  componentId: "button-default",
 | 
			
		||||
  pins: () => [
 | 
			
		||||
    {
 | 
			
		||||
      pinId: "BTN",
 | 
			
		||||
      constraint: "",
 | 
			
		||||
      x: 80,
 | 
			
		||||
      y: 140,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
});
 | 
			
		||||
const props = defineProps<ButtonProps>();
 | 
			
		||||
 | 
			
		||||
// Global Stores
 | 
			
		||||
const constrainsts = useConstraintsStore();
 | 
			
		||||
const dialog = useDialogStore();
 | 
			
		||||
const eqps = useEquipments();
 | 
			
		||||
 | 
			
		||||
// 存储多个Pin引用
 | 
			
		||||
const pinRefs = ref<Record<string, any>>({});
 | 
			
		||||
 | 
			
		||||
// 计算实际宽高
 | 
			
		||||
const width = computed(() => 160 * props.size);
 | 
			
		||||
@@ -153,15 +203,25 @@ function toggleButtonState(isPressed: boolean) {
 | 
			
		||||
  isKeyPressed.value = isPressed;
 | 
			
		||||
  btnHeight.value = isPressed ? 180 : 200;
 | 
			
		||||
 | 
			
		||||
  // 矩阵键盘
 | 
			
		||||
  if (eqps.enableMatrixKey) {
 | 
			
		||||
    const ret = eqps.setMatrixKey(props.bindMatrixKey, isPressed);
 | 
			
		||||
    if (!ret)
 | 
			
		||||
      dialog.error(
 | 
			
		||||
        `绑定的矩阵键盘值只能是0 ~ 15,而不是: ${props.bindMatrixKey}`,
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 发出事件通知父组件
 | 
			
		||||
  if (isPressed) {
 | 
			
		||||
    emit("press");
 | 
			
		||||
    // 如果有约束,通知约束状态变化为高电平
 | 
			
		||||
    // 对所有引脚应用相同的状态
 | 
			
		||||
 | 
			
		||||
    if (props.pins) {
 | 
			
		||||
      // 如果有约束,通知约束状态变化为高电平
 | 
			
		||||
      // 对所有引脚应用相同的状态
 | 
			
		||||
      props.pins.forEach((pin) => {
 | 
			
		||||
        if (pin.constraint) {
 | 
			
		||||
          notifyConstraintChange(pin.constraint, "high");
 | 
			
		||||
          constrainsts.notifyConstraintChange(pin.constraint, "high");
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
@@ -172,7 +232,7 @@ function toggleButtonState(isPressed: boolean) {
 | 
			
		||||
    if (props.pins) {
 | 
			
		||||
      props.pins.forEach((pin) => {
 | 
			
		||||
        if (pin.constraint) {
 | 
			
		||||
          notifyConstraintChange(pin.constraint, "low");
 | 
			
		||||
          constrainsts.notifyConstraintChange(pin.constraint, "low");
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
@@ -200,35 +260,37 @@ onUnmounted(() => {
 | 
			
		||||
defineExpose({
 | 
			
		||||
  toggleButtonState,
 | 
			
		||||
  getInfo: () => ({
 | 
			
		||||
    // 按钮特有属性
 | 
			
		||||
    bindKey: props.bindKey,
 | 
			
		||||
    componentId: props.componentId,
 | 
			
		||||
    pins: props.pins,
 | 
			
		||||
  }),
 | 
			
		||||
  // 获取引脚位置
 | 
			
		||||
  getPinPosition: (pinId: string) => {
 | 
			
		||||
    console.log(`[MechanicalButton] 调用getPinPosition,寻找pinId: ${pinId}`);
 | 
			
		||||
    console.log(
 | 
			
		||||
    console.debug(`[MechanicalButton] 调用getPinPosition,寻找pinId: ${pinId}`);
 | 
			
		||||
    console.debug(
 | 
			
		||||
      `[MechanicalButton] 组件ID: ${props.componentId}, 当前尺寸: ${props.size}, 组件宽高: ${width.value}x${height.value}`,
 | 
			
		||||
    );
 | 
			
		||||
    console.log(`[MechanicalButton] 当前存在的pins:`, props.pins);
 | 
			
		||||
    console.debug(`[MechanicalButton] 当前存在的pins:`, props.pins);
 | 
			
		||||
 | 
			
		||||
    // 如果是自定义的引脚ID
 | 
			
		||||
    if (props.pins && props.pins.length > 0) {
 | 
			
		||||
      const customPin = props.pins.find((p) => p.pinId === pinId);
 | 
			
		||||
 | 
			
		||||
      if (customPin) {
 | 
			
		||||
        console.log(`[MechanicalButton] 找到自定义引脚: ${pinId},配置位置:`, {
 | 
			
		||||
          x: customPin.x,
 | 
			
		||||
          y: customPin.y,
 | 
			
		||||
        });
 | 
			
		||||
        console.debug(
 | 
			
		||||
          `[MechanicalButton] 找到自定义引脚: ${pinId},配置位置:`,
 | 
			
		||||
          {
 | 
			
		||||
            x: customPin.x,
 | 
			
		||||
            y: customPin.y,
 | 
			
		||||
          },
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // 考虑组件尺寸的缩放
 | 
			
		||||
        // 这里的x和y是针对标准尺寸(size=1)的坐标,需要根据实际size调整
 | 
			
		||||
        const scaledX = customPin.x * props.size;
 | 
			
		||||
        const scaledY = customPin.y * props.size;
 | 
			
		||||
 | 
			
		||||
        console.log(`[MechanicalButton] 返回缩放后的坐标:`, {
 | 
			
		||||
        console.debug(`[MechanicalButton] 返回缩放后的坐标:`, {
 | 
			
		||||
          x: scaledX,
 | 
			
		||||
          y: scaledY,
 | 
			
		||||
        });
 | 
			
		||||
@@ -237,12 +299,12 @@ defineExpose({
 | 
			
		||||
          y: scaledY,
 | 
			
		||||
        };
 | 
			
		||||
      } else {
 | 
			
		||||
        console.log(`[MechanicalButton] 未找到pinId: ${pinId}的引脚配置`);
 | 
			
		||||
        console.debug(`[MechanicalButton] 未找到pinId: ${pinId}的引脚配置`);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      console.log(`[MechanicalButton] 没有配置任何引脚`);
 | 
			
		||||
      console.debug(`[MechanicalButton] 没有配置任何引脚`);
 | 
			
		||||
    }
 | 
			
		||||
    console.log(`[MechanicalButton] 返回null,未找到引脚`);
 | 
			
		||||
    console.debug(`[MechanicalButton] 返回null,未找到引脚`);
 | 
			
		||||
    return null;
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
@@ -253,7 +315,6 @@ defineExpose({
 | 
			
		||||
export function getDefaultProps() {
 | 
			
		||||
  return {
 | 
			
		||||
    size: 1,
 | 
			
		||||
    bindKey: "",
 | 
			
		||||
    pins: [
 | 
			
		||||
      {
 | 
			
		||||
        pinId: "BTN",
 | 
			
		||||
@@ -262,6 +323,8 @@ export function getDefaultProps() {
 | 
			
		||||
        y: 140,
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    bindKey: "",
 | 
			
		||||
    bindMatrixKey: "",
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import { toNumber } from "lodash";
 | 
			
		||||
 | 
			
		||||
// 主板特有属性
 | 
			
		||||
export interface MotherBoardProps {
 | 
			
		||||
  size?: number;
 | 
			
		||||
  size: number;
 | 
			
		||||
  boardAddr?: string;
 | 
			
		||||
  boardPort?: string;
 | 
			
		||||
  componentId?: string;
 | 
			
		||||
@@ -63,7 +63,6 @@ export function getDefaultProps(): MotherBoardProps {
 | 
			
		||||
    size: 1,
 | 
			
		||||
    boardAddr: "127.0.0.1",
 | 
			
		||||
    boardPort: "1234",
 | 
			
		||||
    componentId: "DefaultMotherBoardID",
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,12 @@
 | 
			
		||||
        {{ eqps.enableJtagBoundaryScan ? "关闭边界扫描" : "启动边界扫描" }}
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="divider"></div>
 | 
			
		||||
    <h1 class="font-bold text-center text-2xl">外设</h1>
 | 
			
		||||
    <div class="flex flex-row">
 | 
			
		||||
      <input type="checkbox" class="checkbox" :checked="eqps.enableMatrixKey" @change="handleMatrixkeyCheckboxChange" />
 | 
			
		||||
      <p class="mx-2">启用矩阵键盘</p>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -52,7 +58,7 @@ import z from "zod";
 | 
			
		||||
import UploadCard from "@/components/UploadCard.vue";
 | 
			
		||||
import { useDialogStore } from "@/stores/dialog";
 | 
			
		||||
import { useEquipments } from "@/stores/equipments";
 | 
			
		||||
import { computed, ref, watchEffect } from "vue";
 | 
			
		||||
import { computed, ref, watchEffect, watchPostEffect } from "vue";
 | 
			
		||||
 | 
			
		||||
interface CapsProps {
 | 
			
		||||
  jtagAddr?: string;
 | 
			
		||||
@@ -100,6 +106,19 @@ function handleSelectJtagSpeed(event: Event) {
 | 
			
		||||
  emits("changeJtagFreq", target.value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handleMatrixkeyCheckboxChange(event: Event) {
 | 
			
		||||
  const target = event.target as HTMLInputElement;
 | 
			
		||||
  if (target.checked) {
 | 
			
		||||
    const ret = await eqps.matrixKeypadEnable(true);
 | 
			
		||||
    if (!ret) {
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    const ret = await eqps.matrixKeypadEnable(false);
 | 
			
		||||
    if (!ret) {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function toggleJtagBoundaryScan() {
 | 
			
		||||
  if (eqps.jtagClientMutex.isLocked()) {
 | 
			
		||||
    dialog.warn("Jtag正在被占用");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,40 +1,61 @@
 | 
			
		||||
import { ref, computed } from 'vue'
 | 
			
		||||
import { ref, reactive, watchPostEffect, computed } from 'vue'
 | 
			
		||||
import { defineStore } from 'pinia'
 | 
			
		||||
import { isUndefined } from 'lodash';
 | 
			
		||||
import Queue from 'yocto-queue';
 | 
			
		||||
 | 
			
		||||
export const useDialogStore = defineStore('dialog', () => {
 | 
			
		||||
  type Title = "Error" | "Info" | "Warn";
 | 
			
		||||
  const enableDialog = ref(false);
 | 
			
		||||
  const isDialogOpen = ref(false);
 | 
			
		||||
  const dialogTitle = ref<Title>("Error");
 | 
			
		||||
  const dialogContent = ref("这是一个错误");
 | 
			
		||||
  const contentQueue = new Queue<{ type: Title; content: string }>()
 | 
			
		||||
 | 
			
		||||
  function openDialog(title: Title, content?: string) {
 | 
			
		||||
    if (!isUndefined(content) && content.length != 0)
 | 
			
		||||
      dialogContent.value = content;
 | 
			
		||||
    dialogTitle.value = title;
 | 
			
		||||
    isDialogOpen.value = true;
 | 
			
		||||
  function openDialog(title?: Title, content?: string) {
 | 
			
		||||
    if (isUndefined(title)) {
 | 
			
		||||
      if (contentQueue.size != 0) {
 | 
			
		||||
        const dialog = contentQueue.dequeue();
 | 
			
		||||
        if (isUndefined(dialog)) return;
 | 
			
		||||
        openDialog(dialog.type, dialog.content);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      if (!isUndefined(content) && content.length != 0)
 | 
			
		||||
        dialogContent.value = content;
 | 
			
		||||
      dialogTitle.value = title;
 | 
			
		||||
      isDialogOpen.value = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function closeDialog() {
 | 
			
		||||
    isDialogOpen.value = false;
 | 
			
		||||
 | 
			
		||||
    openDialog();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function info(content?: string) {
 | 
			
		||||
    openDialog("Info", content);
 | 
			
		||||
  function info(content: string) {
 | 
			
		||||
    contentQueue.enqueue({ type: "Info", content: content });
 | 
			
		||||
    openDialog();
 | 
			
		||||
    // openDialog("Info", content);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function error(content?: string) {
 | 
			
		||||
    openDialog("Error", content);
 | 
			
		||||
  function error(content: string) {
 | 
			
		||||
    contentQueue.enqueue({ type: "Error", content: content });
 | 
			
		||||
    openDialog();
 | 
			
		||||
    // openDialog("Error", content);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function warn(content?: string) {
 | 
			
		||||
    openDialog("Warn", content);
 | 
			
		||||
  function warn(content: string) {
 | 
			
		||||
    contentQueue.enqueue({ type: "Warn", content: content });
 | 
			
		||||
    openDialog();
 | 
			
		||||
    // openDialog("Warn", content);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    enableDialog,
 | 
			
		||||
    isDialogOpen,
 | 
			
		||||
    dialogTitle,
 | 
			
		||||
    dialogContent,
 | 
			
		||||
    dialogQueue: contentQueue,
 | 
			
		||||
    openDialog,
 | 
			
		||||
    closeDialog,
 | 
			
		||||
    info,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
import { ref, watchEffect } from 'vue'
 | 
			
		||||
import { ref, watchEffect, watchPostEffect } from 'vue'
 | 
			
		||||
import { defineStore } from 'pinia'
 | 
			
		||||
import { isString, toNumber, isUndefined } from 'lodash';
 | 
			
		||||
import { Common } from '@/Common';
 | 
			
		||||
import z from "zod"
 | 
			
		||||
import { isNumber } from 'mathjs';
 | 
			
		||||
import { JtagClient } from "@/APIClient";
 | 
			
		||||
import { JtagClient, MatrixKeyClient } from "@/APIClient";
 | 
			
		||||
import { Mutex, withTimeout } from 'async-mutex';
 | 
			
		||||
import { useConstraintsStore } from "@/stores/constraints";
 | 
			
		||||
import { useDialogStore } from './dialog';
 | 
			
		||||
@@ -14,15 +14,41 @@ export const useEquipments = defineStore('equipments', () => {
 | 
			
		||||
  const constrainsts = useConstraintsStore();
 | 
			
		||||
  const dialog = useDialogStore();
 | 
			
		||||
 | 
			
		||||
  // Basic Info
 | 
			
		||||
  const boardAddr = ref("127.0.0.1");
 | 
			
		||||
  const boardPort = ref(1234);
 | 
			
		||||
 | 
			
		||||
  // Jtag
 | 
			
		||||
  const jtagBitstream = ref<File>();
 | 
			
		||||
  const jtagBoundaryScanFreq = ref(100);
 | 
			
		||||
  const jtagClientMutex = withTimeout(new Mutex(), 1000, new Error("JtagClient Mutex Timeout!"))
 | 
			
		||||
  const jtagClient = new JtagClient();
 | 
			
		||||
 | 
			
		||||
  const enableJtagBoundaryScan = ref(false);
 | 
			
		||||
  // Matrix Key
 | 
			
		||||
  const matrixKeyStates = ref(new Array<boolean>(16).fill(false))
 | 
			
		||||
  const matrixKeypadClientMutex = withTimeout(new Mutex(), 1000, new Error("Matrixkeyclient Mutex Timeout!"));
 | 
			
		||||
  const matrixKeypadClient = new MatrixKeyClient();
 | 
			
		||||
 | 
			
		||||
  // Enable Setting
 | 
			
		||||
  const enableJtagBoundaryScan = ref(false);
 | 
			
		||||
  const enableMatrixKey = ref(false);
 | 
			
		||||
 | 
			
		||||
  // Watch
 | 
			
		||||
  watchPostEffect(async () => {
 | 
			
		||||
    if (true === enableJtagBoundaryScan.value) jtagBoundaryScan();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  watchPostEffect(async () => {
 | 
			
		||||
    if (true === enableMatrixKey.value) {
 | 
			
		||||
      const ret = await matrixKeypadSetKeyStates(matrixKeyStates.value)
 | 
			
		||||
      if (!ret) {
 | 
			
		||||
        dialog.error("设置矩阵键盘失败")
 | 
			
		||||
        enableMatrixKey.value = false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  // Parse and Set
 | 
			
		||||
  function setAddr(address: string | undefined): boolean {
 | 
			
		||||
    if (isString(address) && z.string().ip("4").safeParse(address).success) {
 | 
			
		||||
      boardAddr.value = address;
 | 
			
		||||
@@ -49,9 +75,23 @@ export const useEquipments = defineStore('equipments', () => {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  watchEffect(() => {
 | 
			
		||||
    if (enableJtagBoundaryScan.value) jtagBoundaryScan();
 | 
			
		||||
  });
 | 
			
		||||
  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.value[_keyNum] = keyValue;
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function jtagBoundaryScan() {
 | 
			
		||||
    const release = await jtagClientMutex.acquire();
 | 
			
		||||
@@ -138,11 +178,59 @@ export const useEquipments = defineStore('equipments', () => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function matrixKeypadSetKeyStates(keyStates: boolean[]) {
 | 
			
		||||
    const release = await matrixKeypadClientMutex.acquire();
 | 
			
		||||
    try {
 | 
			
		||||
      const resp = await matrixKeypadClient.setMatrixKeyStatus(
 | 
			
		||||
        boardAddr.value,
 | 
			
		||||
        boardPort.value,
 | 
			
		||||
        matrixKeyStates.value
 | 
			
		||||
      );
 | 
			
		||||
      return resp;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      dialog.error("设置矩阵键盘时,服务器发生错误");
 | 
			
		||||
      return false;
 | 
			
		||||
    } finally {
 | 
			
		||||
      release();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function matrixKeypadEnable(enable: boolean) {
 | 
			
		||||
    const release = await matrixKeypadClientMutex.acquire();
 | 
			
		||||
    try {
 | 
			
		||||
      if (enable) {
 | 
			
		||||
        const resp = await matrixKeypadClient.enabelMatrixKey(
 | 
			
		||||
          boardAddr.value,
 | 
			
		||||
          boardPort.value,
 | 
			
		||||
        );
 | 
			
		||||
        enableMatrixKey.value = resp;
 | 
			
		||||
        return resp;
 | 
			
		||||
      } else {
 | 
			
		||||
        const resp = await matrixKeypadClient.disableMatrixKey(
 | 
			
		||||
          boardAddr.value,
 | 
			
		||||
          boardPort.value,
 | 
			
		||||
        );
 | 
			
		||||
        enableMatrixKey.value = !resp;
 | 
			
		||||
        return resp;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      enableMatrixKey.value = false;
 | 
			
		||||
      dialog.error("设置矩阵键盘是否启用时,服务器发生错误");
 | 
			
		||||
      return false;
 | 
			
		||||
    } finally {
 | 
			
		||||
      release();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    boardAddr,
 | 
			
		||||
    boardPort,
 | 
			
		||||
    setAddr,
 | 
			
		||||
    setPort,
 | 
			
		||||
    setMatrixKey,
 | 
			
		||||
 | 
			
		||||
    // Jtag
 | 
			
		||||
    enableJtagBoundaryScan,
 | 
			
		||||
    jtagBitstream,
 | 
			
		||||
    jtagBoundaryScanFreq,
 | 
			
		||||
    jtagClientMutex,
 | 
			
		||||
@@ -151,7 +239,14 @@ export const useEquipments = defineStore('equipments', () => {
 | 
			
		||||
    jtagDownloadBitstream,
 | 
			
		||||
    jtagGetIDCode,
 | 
			
		||||
    jtagSetSpeed,
 | 
			
		||||
    enableJtagBoundaryScan,
 | 
			
		||||
 | 
			
		||||
    // Matrix Key
 | 
			
		||||
    enableMatrixKey,
 | 
			
		||||
    matrixKeyStates,
 | 
			
		||||
    matrixKeypadClientMutex,
 | 
			
		||||
    matrixKeypadClient,
 | 
			
		||||
    matrixKeypadEnable,
 | 
			
		||||
    matrixKeypadSetKeyStates,
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user