feat: frontend add virtual matrix key

This commit is contained in:
SikongJueluo 2025-05-20 17:09:57 +08:00
parent b68d8eaf11
commit bea1c7e5ae
No known key found for this signature in database
15 changed files with 689 additions and 106 deletions

13
package-lock.json generated
View File

@ -21,6 +21,7 @@
"ts-results-es": "^5.0.1", "ts-results-es": "^5.0.1",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "4", "vue-router": "4",
"yocto-queue": "^1.2.1",
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
@ -4065,6 +4066,18 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/yoctocolors": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz",

View File

@ -27,6 +27,7 @@
"ts-results-es": "^5.0.1", "ts-results-es": "^5.0.1",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "4", "vue-router": "4",
"yocto-queue": "^1.2.1",
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {

View 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": []
}

View File

@ -52,6 +52,7 @@ try
{ {
options.AddPolicy("Users", policy => policy options.AddPolicy("Users", policy => policy
.AllowAnyOrigin() .AllowAnyOrigin()
.AllowAnyHeader()
); );
}); });

View File

@ -26,7 +26,7 @@ public class BoundaryScanRegs
/// </summary> /// </summary>
[JsonProperty("cell_name")] [JsonProperty("cell_name")]
[JsonRequired] [JsonRequired]
public string CellName { get; set; } public string CellName { get; set; } = "UnknownCellName";
/// <summary> /// <summary>
/// [TODO:description] /// [TODO:description]
@ -146,7 +146,8 @@ public class Parser
/// <returns>[TODO:return]</returns> /// <returns>[TODO:return]</returns>
public Optional<List<BoundaryScanRegs.CellEntry>> GetBoundaryLogicalPorts() 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; return item["port_id"] is not null;
}); });
if (registers is null) return new(); if (registers is null) return new();

View File

@ -21,7 +21,7 @@ public class MatrixKeyController : ControllerBase
/// <returns>返回操作结果的状态码</returns> /// <returns>返回操作结果的状态码</returns>
[HttpPost("EnabelMatrixKey")] [HttpPost("EnabelMatrixKey")]
[EnableCors("Users")] [EnableCors("Users")]
[ProducesResponseType(typeof(uint), StatusCodes.Status200OK)] [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)] [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> EnabelMatrixKey(string address, int port) public async ValueTask<IResult> EnabelMatrixKey(string address, int port)
{ {
@ -48,7 +48,7 @@ public class MatrixKeyController : ControllerBase
/// <returns>返回操作结果的状态码</returns> /// <returns>返回操作结果的状态码</returns>
[HttpPost("DisableMatrixKey")] [HttpPost("DisableMatrixKey")]
[EnableCors("Users")] [EnableCors("Users")]
[ProducesResponseType(typeof(uint), StatusCodes.Status200OK)] [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)] [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> DisableMatrixKey(string address, int port) public async ValueTask<IResult> DisableMatrixKey(string address, int port)
{ {
@ -76,7 +76,7 @@ public class MatrixKeyController : ControllerBase
/// <returns>返回操作结果的状态码</returns> /// <returns>返回操作结果的状态码</returns>
[HttpPost("SetMatrixKeyStatus")] [HttpPost("SetMatrixKeyStatus")]
[EnableCors("Users")] [EnableCors("Users")]
[ProducesResponseType(typeof(uint), StatusCodes.Status200OK)] [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)] [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SetMatrixKeyStatus(string address, int port, [FromBody] bool[] keyStates) public async ValueTask<IResult> SetMatrixKeyStatus(string address, int port, [FromBody] bool[] keyStates)
{ {

View File

@ -955,7 +955,13 @@ export class MatrixKeyClient {
this.baseUrl = baseUrl ?? "http://localhost:5000"; 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?"; let url_ = this.baseUrl + "/api/MatrixKey/EnabelMatrixKey?";
if (address === null) if (address === null)
throw new Error("The parameter 'address' cannot be null."); throw new Error("The parameter 'address' cannot be null.");
@ -968,7 +974,7 @@ export class MatrixKeyClient {
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = { let options_: RequestInit = {
method: "GET", method: "POST",
headers: { headers: {
"Accept": "application/json" "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; const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) { if (status === 200) {
@ -1002,10 +1008,16 @@ export class MatrixKeyClient {
return throwException("An unexpected server error occurred.", status, _responseText, _headers); 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?"; let url_ = this.baseUrl + "/api/MatrixKey/DisableMatrixKey?";
if (address === null) if (address === null)
throw new Error("The parameter 'address' cannot be null."); throw new Error("The parameter 'address' cannot be null.");
@ -1018,7 +1030,7 @@ export class MatrixKeyClient {
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = { let options_: RequestInit = {
method: "GET", method: "POST",
headers: { headers: {
"Accept": "application/json" "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; const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) { if (status === 200) {
@ -1052,10 +1064,17 @@ export class MatrixKeyClient {
return throwException("An unexpected server error occurred.", status, _responseText, _headers); 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?"; let url_ = this.baseUrl + "/api/MatrixKey/SetMatrixKeyStatus?";
if (address === null) if (address === null)
throw new Error("The parameter 'address' cannot be null."); throw new Error("The parameter 'address' cannot be null.");
@ -1071,7 +1090,7 @@ export class MatrixKeyClient {
let options_: RequestInit = { let options_: RequestInit = {
body: content_, body: content_,
method: "GET", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"Accept": "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; const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) { if (status === 200) {
@ -1106,7 +1125,7 @@ export class MatrixKeyClient {
return throwException("An unexpected server error occurred.", status, _responseText, _headers); return throwException("An unexpected server error occurred.", status, _responseText, _headers);
}); });
} }
return Promise.resolve<number>(null as any); return Promise.resolve<boolean>(null as any);
} }
} }

View File

@ -182,6 +182,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, shallowRef, onMounted } from "vue"; import { ref, computed, shallowRef, onMounted } from "vue";
import motherboardSvg from "../components/equipments/svg/motherboard.svg"; import motherboardSvg from "../components/equipments/svg/motherboard.svg";
import buttonSvg from "../components//equipments/svg/button.svg";
// Props // Props
interface Props { interface Props {
@ -219,6 +220,7 @@ const availableComponents = [
{ type: "SMA", name: "SMA连接器" }, { type: "SMA", name: "SMA连接器" },
{ type: "MotherBoard", name: "主板" }, { type: "MotherBoard", name: "主板" },
{ type: "PG2L100H_FBG676", name: "PG2L100H FBG676芯片" }, { type: "PG2L100H_FBG676", name: "PG2L100H FBG676芯片" },
{ type: "BaseBoard", name: "通用底板" },
]; ];
// --- --- // --- ---
@ -233,6 +235,13 @@ const availableTemplates = ref([
path: "/EquipmentTemplates/PG2L100H_Pango100pro.json", path: "/EquipmentTemplates/PG2L100H_Pango100pro.json",
thumbnailUrl: motherboardSvg, 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, id: template.id,
name: template.name, name: template.name,
template: templateData, template: templateData,
capsPage: template.capsPage
}); });
// //

View File

@ -13,6 +13,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useDialogStore } from "@/stores/dialog"; import { useDialogStore } from "@/stores/dialog";
const dialog = useDialogStore(); const dialog = useDialogStore();
dialog.enableDialog = true;
</script> </script>
<style scoped lang="postcss"> <style scoped lang="postcss">

View 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>

View File

@ -1,16 +1,28 @@
<template> <template>
<div class="button-container" :style="{ <div
width: width + 'px', class="button-container"
height: height + 'px', :style="{
position: 'relative', width: width + 'px',
}"> height: height + 'px',
<svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" viewBox="400 400 800 800" position: 'relative',
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 和按钮底座保持不变 -->
<defs> <defs>
<filter id="btn-shadow"> <filter id="btn-shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="20" result="blur" /> <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" /> <feOffset in="bluralpha" dx="20" dy="20" result="offsetBlur" />
<feMerge> <feMerge>
<feMergeNode in="offsetBlur" /> <feMergeNode in="offsetBlur" />
@ -37,42 +49,81 @@
<circle r="20" cx="525" cy="1075" fill="#171717" /> <circle r="20" cx="525" cy="1075" fill="#171717" />
<!-- 按钮主体 --> <!-- 按钮主体 -->
<circle r="220" cx="800" cy="800" fill="black" filter="url(#btn-shadow)" /> <circle
<circle :r="btnHeight" cx="800" cy="800" :fill="isKeyPressed ? 'url(#pressed)' : 'url(#normal)'" r="220"
fill-opacity="0.9" @mousedown="toggleButtonState(true)" @mouseup="toggleButtonState(false)" cx="800"
@mouseleave="toggleButtonState(false)" style=" 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; pointer-events: auto;
transition: all 20ms ease-in-out; transition: all 20ms ease-in-out;
cursor: pointer; cursor: pointer;
" /> "
/>
<!-- 按键文字 - 仅显示绑定的按键 --> <!-- 按键文字 - 仅显示绑定的按键 -->
<text v-if="bindKeyDisplay" x="800" y="800" font-size="310" text-anchor="middle" dominant-baseline="central" <text
fill="#ccc" style=" v-if="bindKeyDisplay"
x="800"
y="800"
font-size="310"
text-anchor="middle"
dominant-baseline="central"
fill="#ccc"
style="
font-family: Arial; font-family: Arial;
filter: url(#btn-shadow); filter: url(#btn-shadow);
user-select: none; user-select: none;
pointer-events: none; pointer-events: none;
mix-blend-mode: overlay; mix-blend-mode: overlay;
"> "
>
{{ bindKeyDisplay }} {{ bindKeyDisplay }}
</text> </text>
</svg> </svg>
<!-- 渲染自定义引脚数组 --> <!-- 渲染自定义引脚数组 -->
<div v-for="pin in props.pins" :key="pin.pinId" :style="{ <div
position: 'absolute', v-for="pin in props.pins"
left: `${pin.x * props.size}px`, :key="pin.pinId"
top: `${pin.y * props.size}px`, :style="{
transform: 'translate(-50%, -50%)', position: 'absolute',
zIndex: 3, left: `${pin.x * props.size}px`,
pointerEvents: 'auto', top: `${pin.y * props.size}px`,
}" :data-pin-wrapper="`${pin.pinId}`" :data-pin-x="`${pin.x * props.size}`" transform: 'translate(-50%, -50%)',
:data-pin-y="`${pin.y * props.size}`"> zIndex: 3,
<Pin :ref="(el) => { pointerEvents: 'auto',
if (el) pinRefs[pin.pinId] = el; }"
} :data-pin-wrapper="`${pin.pinId}`"
" direction="output" type="digital" :label="pin.pinId" :constraint="pin.constraint" :pinId="pin.pinId" :data-pin-x="`${pin.x * props.size}`"
:size="0.8" :componentId="props.componentId" @value-change="handlePinValueChange" @pin-click="handlePinClick" /> :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>
</div> </div>
</template> </template>
@ -80,16 +131,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from "vue"; import { ref, onMounted, onUnmounted, computed } from "vue";
import Pin from "./Pin.vue"; import Pin from "./Pin.vue";
import { useEquipments } from "@/stores/equipments";
import { useDialogStore } from "@/stores/dialog";
import { useConstraintsStore } from "../../stores/constraints"; import { useConstraintsStore } from "../../stores/constraints";
const { notifyConstraintChange } = useConstraintsStore(); import { isNull, isUndefined } from "mathjs";
import z from "zod";
// Pin import { toNumber } from "lodash";
const pinRefs = ref<Record<string, any>>({});
// //
interface ButtonProps { export interface ButtonProps {
size?: number; size: number;
bindKey?: string;
componentId?: string; componentId?: string;
pins?: { pins?: {
pinId: string; pinId: string;
@ -97,21 +148,20 @@ interface ButtonProps {
x: number; x: number;
y: number; y: number;
}[]; }[];
bindKey?: string;
bindMatrixKey?: string;
} }
const props = withDefaults(defineProps<ButtonProps>(), { const props = defineProps<ButtonProps>();
size: 1,
bindKey: "", // Global Stores
componentId: "button-default", const constrainsts = useConstraintsStore();
pins: () => [ const dialog = useDialogStore();
{ const eqps = useEquipments();
pinId: "BTN",
constraint: "", // Pin
x: 80, const pinRefs = ref<Record<string, any>>({});
y: 140,
},
],
});
// //
const width = computed(() => 160 * props.size); const width = computed(() => 160 * props.size);
@ -153,15 +203,25 @@ function toggleButtonState(isPressed: boolean) {
isKeyPressed.value = isPressed; isKeyPressed.value = isPressed;
btnHeight.value = isPressed ? 180 : 200; 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) { if (isPressed) {
emit("press"); emit("press");
//
//
if (props.pins) { if (props.pins) {
//
//
props.pins.forEach((pin) => { props.pins.forEach((pin) => {
if (pin.constraint) { if (pin.constraint) {
notifyConstraintChange(pin.constraint, "high"); constrainsts.notifyConstraintChange(pin.constraint, "high");
} }
}); });
} }
@ -172,7 +232,7 @@ function toggleButtonState(isPressed: boolean) {
if (props.pins) { if (props.pins) {
props.pins.forEach((pin) => { props.pins.forEach((pin) => {
if (pin.constraint) { if (pin.constraint) {
notifyConstraintChange(pin.constraint, "low"); constrainsts.notifyConstraintChange(pin.constraint, "low");
} }
}); });
} }
@ -200,35 +260,37 @@ onUnmounted(() => {
defineExpose({ defineExpose({
toggleButtonState, toggleButtonState,
getInfo: () => ({ getInfo: () => ({
//
bindKey: props.bindKey, bindKey: props.bindKey,
componentId: props.componentId, componentId: props.componentId,
pins: props.pins, pins: props.pins,
}), }),
// //
getPinPosition: (pinId: string) => { getPinPosition: (pinId: string) => {
console.log(`[MechanicalButton] 调用getPinPosition寻找pinId: ${pinId}`); console.debug(`[MechanicalButton] 调用getPinPosition寻找pinId: ${pinId}`);
console.log( console.debug(
`[MechanicalButton] 组件ID: ${props.componentId}, 当前尺寸: ${props.size}, 组件宽高: ${width.value}x${height.value}`, `[MechanicalButton] 组件ID: ${props.componentId}, 当前尺寸: ${props.size}, 组件宽高: ${width.value}x${height.value}`,
); );
console.log(`[MechanicalButton] 当前存在的pins:`, props.pins); console.debug(`[MechanicalButton] 当前存在的pins:`, props.pins);
// ID // ID
if (props.pins && props.pins.length > 0) { if (props.pins && props.pins.length > 0) {
const customPin = props.pins.find((p) => p.pinId === pinId); const customPin = props.pins.find((p) => p.pinId === pinId);
if (customPin) { if (customPin) {
console.log(`[MechanicalButton] 找到自定义引脚: ${pinId},配置位置:`, { console.debug(
x: customPin.x, `[MechanicalButton] 找到自定义引脚: ${pinId},配置位置:`,
y: customPin.y, {
}); x: customPin.x,
y: customPin.y,
},
);
// //
// xysize=1size // xysize=1size
const scaledX = customPin.x * props.size; const scaledX = customPin.x * props.size;
const scaledY = customPin.y * props.size; const scaledY = customPin.y * props.size;
console.log(`[MechanicalButton] 返回缩放后的坐标:`, { console.debug(`[MechanicalButton] 返回缩放后的坐标:`, {
x: scaledX, x: scaledX,
y: scaledY, y: scaledY,
}); });
@ -237,12 +299,12 @@ defineExpose({
y: scaledY, y: scaledY,
}; };
} else { } else {
console.log(`[MechanicalButton] 未找到pinId: ${pinId}的引脚配置`); console.debug(`[MechanicalButton] 未找到pinId: ${pinId}的引脚配置`);
} }
} else { } else {
console.log(`[MechanicalButton] 没有配置任何引脚`); console.debug(`[MechanicalButton] 没有配置任何引脚`);
} }
console.log(`[MechanicalButton] 返回null未找到引脚`); console.debug(`[MechanicalButton] 返回null未找到引脚`);
return null; return null;
}, },
}); });
@ -253,7 +315,6 @@ defineExpose({
export function getDefaultProps() { export function getDefaultProps() {
return { return {
size: 1, size: 1,
bindKey: "",
pins: [ pins: [
{ {
pinId: "BTN", pinId: "BTN",
@ -262,6 +323,8 @@ export function getDefaultProps() {
y: 140, y: 140,
}, },
], ],
bindKey: "",
bindMatrixKey: "",
}; };
} }
</script> </script>

View File

@ -23,7 +23,7 @@ import { toNumber } from "lodash";
// //
export interface MotherBoardProps { export interface MotherBoardProps {
size?: number; size: number;
boardAddr?: string; boardAddr?: string;
boardPort?: string; boardPort?: string;
componentId?: string; componentId?: string;
@ -63,7 +63,6 @@ export function getDefaultProps(): MotherBoardProps {
size: 1, size: 1,
boardAddr: "127.0.0.1", boardAddr: "127.0.0.1",
boardPort: "1234", boardPort: "1234",
componentId: "DefaultMotherBoardID",
}; };
} }
</script> </script>

View File

@ -44,6 +44,12 @@
{{ eqps.enableJtagBoundaryScan ? "关闭边界扫描" : "启动边界扫描" }} {{ eqps.enableJtagBoundaryScan ? "关闭边界扫描" : "启动边界扫描" }}
</button> </button>
</div> </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> </div>
</template> </template>
@ -52,7 +58,7 @@ import z from "zod";
import UploadCard from "@/components/UploadCard.vue"; import UploadCard from "@/components/UploadCard.vue";
import { useDialogStore } from "@/stores/dialog"; import { useDialogStore } from "@/stores/dialog";
import { useEquipments } from "@/stores/equipments"; import { useEquipments } from "@/stores/equipments";
import { computed, ref, watchEffect } from "vue"; import { computed, ref, watchEffect, watchPostEffect } from "vue";
interface CapsProps { interface CapsProps {
jtagAddr?: string; jtagAddr?: string;
@ -100,6 +106,19 @@ function handleSelectJtagSpeed(event: Event) {
emits("changeJtagFreq", target.value); 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() { async function toggleJtagBoundaryScan() {
if (eqps.jtagClientMutex.isLocked()) { if (eqps.jtagClientMutex.isLocked()) {
dialog.warn("Jtag正在被占用"); dialog.warn("Jtag正在被占用");

View File

@ -1,40 +1,61 @@
import { ref, computed } from 'vue' import { ref, reactive, watchPostEffect, computed } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import Queue from 'yocto-queue';
export const useDialogStore = defineStore('dialog', () => { export const useDialogStore = defineStore('dialog', () => {
type Title = "Error" | "Info" | "Warn"; type Title = "Error" | "Info" | "Warn";
const enableDialog = ref(false);
const isDialogOpen = ref(false); const isDialogOpen = ref(false);
const dialogTitle = ref<Title>("Error"); const dialogTitle = ref<Title>("Error");
const dialogContent = ref("这是一个错误"); 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(content) && content.length != 0) if (isUndefined(title)) {
dialogContent.value = content; if (contentQueue.size != 0) {
dialogTitle.value = title; const dialog = contentQueue.dequeue();
isDialogOpen.value = true; 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() { function closeDialog() {
isDialogOpen.value = false; isDialogOpen.value = false;
openDialog();
} }
function info(content?: string) { function info(content: string) {
openDialog("Info", content); contentQueue.enqueue({ type: "Info", content: content });
openDialog();
// openDialog("Info", content);
} }
function error(content?: string) { function error(content: string) {
openDialog("Error", content); contentQueue.enqueue({ type: "Error", content: content });
openDialog();
// openDialog("Error", content);
} }
function warn(content?: string) { function warn(content: string) {
openDialog("Warn", content); contentQueue.enqueue({ type: "Warn", content: content });
openDialog();
// openDialog("Warn", content);
} }
return { return {
enableDialog,
isDialogOpen, isDialogOpen,
dialogTitle, dialogTitle,
dialogContent, dialogContent,
dialogQueue: contentQueue,
openDialog, openDialog,
closeDialog, closeDialog,
info, info,

View File

@ -1,10 +1,10 @@
import { ref, watchEffect } from 'vue' import { ref, watchEffect, watchPostEffect } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { isString, toNumber, isUndefined } from 'lodash'; import { isString, toNumber, isUndefined } from 'lodash';
import { Common } from '@/Common'; import { Common } from '@/Common';
import z from "zod" import z from "zod"
import { isNumber } from 'mathjs'; import { isNumber } from 'mathjs';
import { JtagClient } from "@/APIClient"; import { JtagClient, MatrixKeyClient } from "@/APIClient";
import { Mutex, withTimeout } from 'async-mutex'; import { Mutex, withTimeout } from 'async-mutex';
import { useConstraintsStore } from "@/stores/constraints"; import { useConstraintsStore } from "@/stores/constraints";
import { useDialogStore } from './dialog'; import { useDialogStore } from './dialog';
@ -14,15 +14,41 @@ export const useEquipments = defineStore('equipments', () => {
const constrainsts = useConstraintsStore(); const constrainsts = useConstraintsStore();
const dialog = useDialogStore(); const dialog = useDialogStore();
// Basic Info
const boardAddr = ref("127.0.0.1"); const boardAddr = ref("127.0.0.1");
const boardPort = ref(1234); const boardPort = ref(1234);
// Jtag
const jtagBitstream = ref<File>(); const jtagBitstream = ref<File>();
const jtagBoundaryScanFreq = ref(100); const jtagBoundaryScanFreq = ref(100);
const jtagClientMutex = withTimeout(new Mutex(), 1000, new Error("JtagClient Mutex Timeout!")) const jtagClientMutex = withTimeout(new Mutex(), 1000, new Error("JtagClient Mutex Timeout!"))
const jtagClient = new JtagClient(); 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 { function setAddr(address: string | undefined): boolean {
if (isString(address) && z.string().ip("4").safeParse(address).success) { if (isString(address) && z.string().ip("4").safeParse(address).success) {
boardAddr.value = address; boardAddr.value = address;
@ -49,9 +75,23 @@ export const useEquipments = defineStore('equipments', () => {
return false; return false;
} }
watchEffect(() => { function setMatrixKey(keyNum: number | string | undefined, keyValue: boolean): boolean {
if (enableJtagBoundaryScan.value) jtagBoundaryScan();
}); 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() { async function jtagBoundaryScan() {
const release = await jtagClientMutex.acquire(); 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 { return {
boardAddr, boardAddr,
boardPort, boardPort,
setAddr, setAddr,
setPort, setPort,
setMatrixKey,
// Jtag
enableJtagBoundaryScan,
jtagBitstream, jtagBitstream,
jtagBoundaryScanFreq, jtagBoundaryScanFreq,
jtagClientMutex, jtagClientMutex,
@ -151,7 +239,14 @@ export const useEquipments = defineStore('equipments', () => {
jtagDownloadBitstream, jtagDownloadBitstream,
jtagGetIDCode, jtagGetIDCode,
jtagSetSpeed, jtagSetSpeed,
enableJtagBoundaryScan,
// Matrix Key
enableMatrixKey,
matrixKeyStates,
matrixKeypadClientMutex,
matrixKeypadClient,
matrixKeypadEnable,
matrixKeypadSetKeyStates,
} }
}) })