parent
705e322e41
commit
49cbdc51d9
|
@ -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":
|
||||
|
|
|
@ -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="[
|
||||
'waveform-option',
|
||||
{ active: currentWaveformIndex === index },
|
||||
]" @click="selectWaveform(index)">
|
||||
<div
|
||||
v-for="(name, index) in waveformNames"
|
||||
:key="`wave-${index}`"
|
||||
:class="[
|
||||
'waveform-option',
|
||||
{ active: currentWaveformIndex === 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="
|
||||
applyExampleFunction(
|
||||
'((x)^(2/3)+0.9*sqrt(3.3-(x)^2)*sin(10*PI*(x)))*0.75',
|
||||
)
|
||||
">
|
||||
<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)">
|
||||
}}</span>
|
||||
<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();
|
||||
|
||||
|
@ -793,7 +850,7 @@ function saveCurrentToSlot(index: number) {
|
|||
type: waveforms[currentWaveformIndex.value],
|
||||
data:
|
||||
drawPoints.value.length > 0 &&
|
||||
currentWaveformIndex.value === waveforms.indexOf("custom")
|
||||
currentWaveformIndex.value === waveforms.indexOf("custom")
|
||||
? [...drawPoints.value]
|
||||
: null,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
export function toFileParameterOrNull(
|
||||
object?: File | null,
|
||||
): FileParameter | null {
|
||||
if (isNull(object) || isUndefined(object)) return null;
|
||||
else
|
||||
return {
|
||||
data: object,
|
||||
fileName: object.name
|
||||
}
|
||||
}
|
||||
|
||||
export function toFileParameterOrNull(object?: File | null): FileParameter | null {
|
||||
if (isNull(object) || isUndefined(object)) return null;
|
||||
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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue