fix: 修复多个外设无法认证的问题

refactor: 同时使用更加优雅的方式处理injection
This commit is contained in:
SikongJueluo 2025-07-15 11:30:09 +08:00
parent 705e322e41
commit 49cbdc51d9
No known key found for this signature in database
6 changed files with 184 additions and 84 deletions

View File

@ -48,14 +48,15 @@
import { computed } from "vue";
import { CheckCircle, XCircle, AlertTriangle, Info, X } from "lucide-vue-next";
import { useAlertStore } from ".";
import { useRequiredInjection } from "@/utils/Common";
const alertStore = useAlertStore();
const alertStore = useRequiredInjection(useAlertStore);
// Computed classes for different alert types
const alertClasses = computed(() => {
const baseClasses = "shadow-lg max-w-sm";
switch (alertStore?.alertState.value.type) {
switch (alertStore.alertState.value.type) {
case "success":
return `${baseClasses} alert-success`;
case "error":

View File

@ -7,7 +7,12 @@
<div class="waveform-display">
<svg width="100%" height="120" viewBox="0 0 300 120">
<rect width="300" height="120" fill="#1a1f25" />
<path :d="currentWaveformPath" stroke="lime" stroke-width="2" fill="none" />
<path
:d="currentWaveformPath"
stroke="lime"
stroke-width="2"
fill="none"
/>
<!-- 频率和相位显示 -->
<text x="20" y="25" fill="#0f0" font-size="14">
@ -16,7 +21,13 @@
<text x="200" y="25" fill="#0f0" font-size="14">
φ: {{ phase }}°
</text>
<text x="150" y="110" fill="#0f0" font-size="14" text-anchor="middle">
<text
x="150"
y="110"
fill="#0f0"
font-size="14"
text-anchor="middle"
>
{{ displayTimebase }}
</text>
</svg>
@ -35,10 +46,15 @@
<!-- 波形选择区 -->
<div class="waveform-selector">
<div v-for="(name, index) in waveformNames" :key="`wave-${index}`" :class="[
<div
v-for="(name, index) in waveformNames"
:key="`wave-${index}`"
:class="[
'waveform-option',
{ active: currentWaveformIndex === index },
]" @click="selectWaveform(index)">
]"
@click="selectWaveform(index)"
>
{{ name }}
</div>
</div>
@ -51,8 +67,13 @@
<button class="control-button" @click="decreaseFrequency">
-
</button>
<input v-model="frequencyInput" @blur="applyFrequencyInput" @keyup.enter="applyFrequencyInput"
class="control-input" type="text" />
<input
v-model="frequencyInput"
@blur="applyFrequencyInput"
@keyup.enter="applyFrequencyInput"
class="control-input"
type="text"
/>
<button class="control-button" @click="increaseFrequency">
+
</button>
@ -63,8 +84,13 @@
<span class="control-label">相位:</span>
<div class="control-buttons">
<button class="control-button" @click="decreasePhase">-</button>
<input v-model="phaseInput" @blur="applyPhaseInput" @keyup.enter="applyPhaseInput" class="control-input"
type="text" />
<input
v-model="phaseInput"
@blur="applyPhaseInput"
@keyup.enter="applyPhaseInput"
class="control-input"
type="text"
/>
<button class="control-button" @click="increasePhase">+</button>
</div>
</div>
@ -75,9 +101,12 @@
<div class="section-heading">自定义波形</div>
<div class="input-group">
<label class="input-label">函数表达式:</label>
<input v-model="customWaveformExpression" class="function-input"
<input
v-model="customWaveformExpression"
class="function-input"
placeholder="例如: sin(t) 或 x^(2/3)+0.9*sqrt(3.3-x^2)*sin(a*PI*x) [a=7.8]"
@keyup.enter="applyCustomWaveform" />
@keyup.enter="applyCustomWaveform"
/>
<button class="apply-button" @click="applyCustomWaveform">
应用
</button>
@ -86,17 +115,26 @@
<div class="example-functions">
<div class="example-label">示例函数:</div>
<div class="example-buttons">
<button class="example-button" @click="applyExampleFunction('sin(t)')">
<button
class="example-button"
@click="applyExampleFunction('sin(t)')"
>
正弦波
</button>
<button class="example-button" @click="applyExampleFunction('sin(t)^3')">
<button
class="example-button"
@click="applyExampleFunction('sin(t)^3')"
>
立方正弦
</button>
<button class="example-button" @click="
<button
class="example-button"
@click="
applyExampleFunction(
'((x)^(2/3)+0.9*sqrt(3.3-(x)^2)*sin(10*PI*(x)))*0.75',
)
">
"
>
心形函数
</button>
</div>
@ -105,8 +143,16 @@
<div class="drawing-area">
<div class="section-heading">波形绘制</div>
<div class="waveform-canvas-container" ref="canvasContainer">
<canvas ref="drawingCanvas" class="drawing-canvas" width="280" height="100" @mousedown="startDrawing"
@mousemove="draw" @mouseup="stopDrawing" @mouseleave="stopDrawing"></canvas>
<canvas
ref="drawingCanvas"
class="drawing-canvas"
width="280"
height="100"
@mousedown="startDrawing"
@mousemove="draw"
@mouseup="stopDrawing"
@mouseleave="stopDrawing"
></canvas>
<div class="canvas-actions">
<button class="canvas-button" @click="clearCanvas">
清除
@ -123,19 +169,30 @@
<div class="saved-waveforms">
<div class="section-heading">波形存储槽</div>
<div class="slot-container">
<div v-for="(slot, index) in waveformSlots" :key="`slot-${index}`"
:class="['waveform-slot', { empty: !slot.name }]" @click="loadWaveformSlot(index)">
<div
v-for="(slot, index) in waveformSlots"
:key="`slot-${index}`"
:class="['waveform-slot', { empty: !slot.name }]"
@click="loadWaveformSlot(index)"
>
<span class="slot-name">{{
slot.name || `${index + 1}`
}}</span>
<button class="save-button" @click.stop="saveCurrentToSlot(index)">
<button
class="save-button"
@click.stop="saveCurrentToSlot(index)"
>
保存
</button>
</div>
</div>
</div>
<button class="btn btn-primary text-primary-content w-full" :disabled="isApplying" @click="applyOutputWave">
<button
class="btn btn-primary text-primary-content w-full"
:disabled="isApplying"
@click="applyOutputWave"
>
<div v-if="isApplying">
<span class="loading loading-spinner"></span>
应用中...
@ -151,10 +208,10 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted } from "vue";
import CollapsibleSection from "../CollapsibleSection.vue";
import { DDSClient } from "@/APIClient";
import { useEquipments } from "@/stores/equipments";
import { useDialogStore } from "@/stores/dialog";
import { toInteger } from "lodash";
import { AuthManager } from "@/utils/AuthManager";
// Component Attributes
const props = defineProps<{
@ -164,7 +221,7 @@ const props = defineProps<{
const emit = defineEmits(["update:modelValue"]);
// Global varibles
const dds = new DDSClient();
const dds = AuthManager.createAuthenticatedDDSClient();
const eqps = useEquipments();
const dialog = useDialogStore();

View File

@ -1,42 +1,55 @@
import { ref, reactive, watchPostEffect } from 'vue'
import { defineStore } from 'pinia'
import { useLocalStorage } from '@vueuse/core'
import { isString, toNumber } from 'lodash';
import { Common } from '@/utils/Common';
import z from "zod"
import { isNumber } from 'mathjs';
import { ref, reactive, watchPostEffect } from "vue";
import { defineStore } from "pinia";
import { useLocalStorage } from "@vueuse/core";
import { isString, toNumber } from "lodash";
import z from "zod";
import { isNumber } from "mathjs";
import { JtagClient, MatrixKeyClient, PowerClient } from "@/APIClient";
import { Mutex, withTimeout } from 'async-mutex';
import { Mutex, withTimeout } from "async-mutex";
import { useConstraintsStore } from "@/stores/constraints";
import { useDialogStore } from './dialog';
import { useDialogStore } from "./dialog";
import { toFileParameterOrUndefined } from "@/utils/Common";
import { AuthManager } from "@/utils/AuthManager";
export const useEquipments = defineStore('equipments', () => {
export const useEquipments = defineStore("equipments", () => {
// Global Stores
const constrainsts = useConstraintsStore();
const dialog = useDialogStore();
const boardAddr = useLocalStorage('fpga-board-addr', "127.0.0.1");
const boardPort = useLocalStorage('fpga-board-port', 1234);
const boardAddr = useLocalStorage("fpga-board-addr", "127.0.0.1");
const boardPort = useLocalStorage("fpga-board-port", 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 jtagClientMutex = withTimeout(
new Mutex(),
1000,
new Error("JtagClient Mutex Timeout!"),
);
const jtagClient = AuthManager.createAuthenticatedJtagClient();
// Matrix Key
const matrixKeyStates = reactive(new Array<boolean>(16).fill(false))
const matrixKeypadClientMutex = withTimeout(new Mutex(), 1000, new Error("Matrixkeyclient Mutex Timeout!"));
const matrixKeypadClient = new MatrixKeyClient();
const matrixKeyStates = reactive(new Array<boolean>(16).fill(false));
const matrixKeypadClientMutex = withTimeout(
new Mutex(),
1000,
new Error("Matrixkeyclient Mutex Timeout!"),
);
const matrixKeypadClient = AuthManager.createAuthenticatedMatrixKeyClient();
// Power
const powerClientMutex = withTimeout(new Mutex(), 1000, new Error("Matrixkeyclient Mutex Timeout!"));
const powerClient = new PowerClient();
const powerClientMutex = withTimeout(
new Mutex(),
1000,
new Error("Matrixkeyclient Mutex Timeout!"),
);
const powerClient = AuthManager.createAuthenticatedPowerClient();
// Enable Setting
const enableJtagBoundaryScan = ref(false);
const enableMatrixKey = ref(false);
const enablePower = ref(false)
const enablePower = ref(false);
// Watch
watchPostEffect(async () => {
@ -60,8 +73,7 @@ export const useEquipments = defineStore('equipments', () => {
boardPort.value = portNumber;
return true;
}
}
else if (isNumber(port)) {
} else if (isNumber(port)) {
if (z.number().nonnegative().max(65535).safeParse(port).success) {
boardPort.value = port;
return true;
@ -70,7 +82,10 @@ export const useEquipments = defineStore('equipments', () => {
return false;
}
function setMatrixKey(keyNum: number | string | undefined, keyValue: boolean): boolean {
function setMatrixKey(
keyNum: number | string | undefined,
keyValue: boolean,
): boolean {
let _keyNum: number;
if (isString(keyNum)) {
_keyNum = toNumber(keyNum);
@ -112,7 +127,7 @@ export const useEquipments = defineStore('equipments', () => {
try {
const resp = await jtagClient.uploadBitstream(
boardAddr.value,
Common.toFileParameterOrNull(bitstream),
toFileParameterOrUndefined(bitstream),
);
return resp;
} catch (e) {
@ -127,7 +142,7 @@ export const useEquipments = defineStore('equipments', () => {
try {
const resp = await jtagClient.downloadBitstream(
boardAddr.value,
boardPort.value
boardPort.value,
);
return resp;
} catch (e) {
@ -144,7 +159,7 @@ export const useEquipments = defineStore('equipments', () => {
try {
const resp = await jtagClient.getDeviceIDCode(
boardAddr.value,
boardPort.value
boardPort.value,
);
return resp;
} catch (e) {
@ -161,7 +176,7 @@ export const useEquipments = defineStore('equipments', () => {
const resp = await jtagClient.setSpeed(
boardAddr.value,
boardPort.value,
speed
speed,
);
return resp;
} catch (e) {
@ -179,7 +194,7 @@ export const useEquipments = defineStore('equipments', () => {
const resp = await matrixKeypadClient.setMatrixKeyStatus(
boardAddr.value,
boardPort.value,
keyStates
keyStates,
);
return resp;
} catch (e) {
@ -223,7 +238,7 @@ export const useEquipments = defineStore('equipments', () => {
const resp = await powerClient.setPowerOnOff(
boardAddr.value,
boardPort.value,
enable
enable,
);
return resp;
} catch (e) {
@ -266,6 +281,5 @@ export const useEquipments = defineStore('equipments', () => {
powerClient,
powerClientMutex,
powerSetOnOff,
}
})
};
});

View File

@ -1,9 +1,9 @@
import { ref } from "vue";
import { createInjectionState } from "@vueuse/core";
import { RemoteUpdateClient, DataClient, Board } from "@/APIClient";
import { Common } from "@/utils/Common";
import { isUndefined } from "lodash";
import { AuthManager } from "@/utils/AuthManager";
import { toFileParameterOrNull } from "./Common";
// 统一的板卡数据接口扩展原有的Board类型
export interface BoardData extends Board {
@ -178,10 +178,10 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => {
const uploadResult = await remoteUpdater.uploadBitstreams(
board.ipAddr,
Common.toFileParameterOrNull(goldBitstream),
Common.toFileParameterOrNull(appBitstream1),
Common.toFileParameterOrNull(appBitstream2),
Common.toFileParameterOrNull(appBitstream3),
toFileParameterOrNull(goldBitstream),
toFileParameterOrNull(appBitstream1),
toFileParameterOrNull(appBitstream2),
toFileParameterOrNull(appBitstream3),
);
if (!uploadResult) {

View File

@ -1,22 +1,50 @@
import { type FileParameter } from "@/APIClient";
import { isNull, isUndefined } from "lodash";
export namespace Common {
export function toFileParameter(object: File): FileParameter {
if (isNull(object) || isUndefined(object))
throw new Error("File is Null or Undefined");
return {
data: object,
fileName: object.name
}
fileName: object.name,
};
}
export function toFileParameterOrNull(object?: File | null): FileParameter | null {
export function toFileParameterOrNull(
object?: File | null,
): FileParameter | null {
if (isNull(object) || isUndefined(object)) return null;
else return {
else
return {
data: object,
fileName: object.name
}
fileName: object.name,
};
}
export function toFileParameterOrUndefined(
object?: File | undefined,
): FileParameter | undefined {
if (isNull(object) || isUndefined(object)) return undefined;
else
return {
data: object,
fileName: object.name,
};
}
// 自定义 Hook检查依赖注入值是否为空
export function useRequiredInjection<T>(useFn: () => T | undefined): T {
const value = useFn();
if (value === undefined) {
throw new Error("Missing required injection");
}
return value;
}
export function useOptionalInjection<T>(
useFn: () => T | undefined,
defaultValue: T,
): T {
const value = useFn();
return value ?? defaultValue;
}