feat: frontend add virtual matrix key
This commit is contained in:
parent
7b1d1a5e87
commit
1bdcb672ab
|
@ -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": {
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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="{
|
||||
<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">
|
||||
}"
|
||||
>
|
||||
<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="{
|
||||
<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) => {
|
||||
}"
|
||||
: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" />
|
||||
"
|
||||
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) {
|
||||
// 如果有约束,通知约束状态变化为高电平
|
||||
// 对所有引脚应用相同的状态
|
||||
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},配置位置:`, {
|
||||
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) {
|
||||
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,
|
||||
}
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in New Issue