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

View File

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

View File

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

View File

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

View File

@ -1,22 +1,50 @@
import { type FileParameter } from "@/APIClient"; import { type FileParameter } from "@/APIClient";
import { isNull, isUndefined } from "lodash"; import { isNull, isUndefined } from "lodash";
export namespace Common { export function toFileParameter(object: File): FileParameter {
export function toFileParameter(object: File): FileParameter {
if (isNull(object) || isUndefined(object)) if (isNull(object) || isUndefined(object))
throw new Error("File is Null or Undefined"); throw new Error("File is Null or Undefined");
return { return {
data: object, data: object,
fileName: object.name fileName: object.name,
} };
} }
export function toFileParameterOrNull(object?: File | null): FileParameter | null { export function toFileParameterOrNull(
if (isNull(object) || isUndefined(object)) return null; object?: File | null,
else return { ): FileParameter | null {
data: object, if (isNull(object) || isUndefined(object)) return null;
fileName: object.name else
} return {
} data: object,
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;
} }