diff --git a/src/components/LabCanvas/composable/diagramManager.ts b/src/components/LabCanvas/composable/diagramManager.ts index a1e88d7..6a652ac 100644 --- a/src/components/LabCanvas/composable/diagramManager.ts +++ b/src/components/LabCanvas/composable/diagramManager.ts @@ -1,4 +1,4 @@ -import { ResourcePurpose } from "@/APIClient"; +import { ResourceClient, ResourcePurpose } from "@/APIClient"; import { AuthManager } from "@/utils/AuthManager"; // 定义 diagram.json 的类型结构 @@ -94,7 +94,7 @@ export async function loadDiagramData(examId?: string): Promise { // 如果提供了examId,优先从API加载实验的diagram if (examId) { try { - const resourceClient = AuthManager.createAuthenticatedResourceClient(); + const resourceClient = AuthManager.createClient(ResourceClient); // 获取diagram类型的资源列表 const resources = await resourceClient.getResourceList( diff --git a/src/components/LogicAnalyzer/LogicAnalyzerManager.ts b/src/components/LogicAnalyzer/LogicAnalyzerManager.ts index c830a7a..02ded7d 100644 --- a/src/components/LogicAnalyzer/LogicAnalyzerManager.ts +++ b/src/components/LogicAnalyzer/LogicAnalyzerManager.ts @@ -31,8 +31,16 @@ export type Channel = { // 全局模式选项 const globalModes = [ - {value: GlobalCaptureMode.AND,label: "AND",description: "所有条件都满足时触发",}, - {value: GlobalCaptureMode.OR,label: "OR",description: "任一条件满足时触发",}, + { + value: GlobalCaptureMode.AND, + label: "AND", + description: "所有条件都满足时触发", + }, + { + value: GlobalCaptureMode.OR, + label: "OR", + description: "任一条件满足时触发", + }, { value: GlobalCaptureMode.NAND, label: "NAND", description: "AND的非" }, { value: GlobalCaptureMode.NOR, label: "NOR", description: "OR的非" }, ]; @@ -70,21 +78,53 @@ const channelDivOptions = [ ]; const ClockDivOptions = [ - { value: AnalyzerClockDiv.DIV1, label: "120MHz", description: "采样频率120MHz" }, - { value: AnalyzerClockDiv.DIV2, label: "60MHz", description: "采样频率60MHz" }, - { value: AnalyzerClockDiv.DIV4, label: "30MHz", description: "采样频率30MHz" }, - { value: AnalyzerClockDiv.DIV8, label: "15MHz", description: "采样频率15MHz" }, - { value: AnalyzerClockDiv.DIV16, label: "7.5MHz", description: "采样频率7.5MHz" }, - { value: AnalyzerClockDiv.DIV32, label: "3.75MHz", description: "采样频率3.75MHz" }, - { value: AnalyzerClockDiv.DIV64, label: "1.875MHz", description: "采样频率1.875MHz" }, - { value: AnalyzerClockDiv.DIV128, label: "937.5KHz", description: "采样频率937.5KHz" }, + { + value: AnalyzerClockDiv.DIV1, + label: "120MHz", + description: "采样频率120MHz", + }, + { + value: AnalyzerClockDiv.DIV2, + label: "60MHz", + description: "采样频率60MHz", + }, + { + value: AnalyzerClockDiv.DIV4, + label: "30MHz", + description: "采样频率30MHz", + }, + { + value: AnalyzerClockDiv.DIV8, + label: "15MHz", + description: "采样频率15MHz", + }, + { + value: AnalyzerClockDiv.DIV16, + label: "7.5MHz", + description: "采样频率7.5MHz", + }, + { + value: AnalyzerClockDiv.DIV32, + label: "3.75MHz", + description: "采样频率3.75MHz", + }, + { + value: AnalyzerClockDiv.DIV64, + label: "1.875MHz", + description: "采样频率1.875MHz", + }, + { + value: AnalyzerClockDiv.DIV128, + label: "937.5KHz", + description: "采样频率937.5KHz", + }, ]; // 捕获深度限制常量 const CAPTURE_LENGTH_MIN = 1024; // 最小捕获深度 1024 const CAPTURE_LENGTH_MAX = 0x10000000 - 0x01000000; // 最大捕获深度 -// 预捕获深度限制常量 +// 预捕获深度限制常量 const PRE_CAPTURE_LENGTH_MIN = 2; // 最小预捕获深度 2 // 默认颜色数组 @@ -170,40 +210,64 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( // 转换通道数字到枚举值 const getChannelDivEnum = (channelCount: number): AnalyzerChannelDiv => { switch (channelCount) { - case 1: return AnalyzerChannelDiv.ONE; - case 2: return AnalyzerChannelDiv.TWO; - case 4: return AnalyzerChannelDiv.FOUR; - case 8: return AnalyzerChannelDiv.EIGHT; - case 16: return AnalyzerChannelDiv.XVI; - case 32: return AnalyzerChannelDiv.XXXII; - default: return AnalyzerChannelDiv.EIGHT; + case 1: + return AnalyzerChannelDiv.ONE; + case 2: + return AnalyzerChannelDiv.TWO; + case 4: + return AnalyzerChannelDiv.FOUR; + case 8: + return AnalyzerChannelDiv.EIGHT; + case 16: + return AnalyzerChannelDiv.XVI; + case 32: + return AnalyzerChannelDiv.XXXII; + default: + return AnalyzerChannelDiv.EIGHT; } }; // 验证捕获深度 - const validateCaptureLength = (value: number): { valid: boolean; message?: string } => { + const validateCaptureLength = ( + value: number, + ): { valid: boolean; message?: string } => { if (!Number.isInteger(value)) { return { valid: false, message: "捕获深度必须是整数" }; } if (value < CAPTURE_LENGTH_MIN) { - return { valid: false, message: `捕获深度不能小于 ${CAPTURE_LENGTH_MIN}` }; + return { + valid: false, + message: `捕获深度不能小于 ${CAPTURE_LENGTH_MIN}`, + }; } if (value > CAPTURE_LENGTH_MAX) { - return { valid: false, message: `捕获深度不能大于 ${CAPTURE_LENGTH_MAX.toLocaleString()}` }; + return { + valid: false, + message: `捕获深度不能大于 ${CAPTURE_LENGTH_MAX.toLocaleString()}`, + }; } return { valid: true }; }; // 验证预捕获深度 - const validatePreCaptureLength = (value: number, currentCaptureLength: number): { valid: boolean; message?: string } => { + const validatePreCaptureLength = ( + value: number, + currentCaptureLength: number, + ): { valid: boolean; message?: string } => { if (!Number.isInteger(value)) { return { valid: false, message: "预捕获深度必须是整数" }; } if (value < PRE_CAPTURE_LENGTH_MIN) { - return { valid: false, message: `预捕获深度不能小于 ${PRE_CAPTURE_LENGTH_MIN}` }; + return { + valid: false, + message: `预捕获深度不能小于 ${PRE_CAPTURE_LENGTH_MIN}`, + }; } if (value >= currentCaptureLength) { - return { valid: false, message: `预捕获深度不能大于等于捕获深度 (${currentCaptureLength})` }; + return { + valid: false, + message: `预捕获深度不能大于等于捕获深度 (${currentCaptureLength})`, + }; } return { valid: true }; }; @@ -215,13 +279,13 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( alert?.error(validation.message!, 3000); return false; } - + // 检查预捕获深度是否仍然有效 if (preCaptureLength.value >= value) { preCaptureLength.value = Math.max(0, value - 1); alert?.warn(`预捕获深度已自动调整为 ${preCaptureLength.value}`, 3000); } - + captureLength.value = value; return true; }; @@ -233,7 +297,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( alert?.error(validation.message!, 3000); return false; } - + preCaptureLength.value = value; return true; }; @@ -241,12 +305,12 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( // 设置通道组 const setChannelDiv = (channelCount: number) => { // 验证通道数量是否有效 - if (!channelDivOptions.find(option => option.value === channelCount)) { + if (!channelDivOptions.find((option) => option.value === channelCount)) { console.error(`无效的通道组设置: ${channelCount}`); return; } currentChannelDiv.value = channelCount; - + // 禁用所有通道 channels.forEach((channel) => { channel.enabled = false; @@ -257,7 +321,9 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( channels[i].enabled = true; } - const option = channelDivOptions.find(opt => opt.value === channelCount); + const option = channelDivOptions.find( + (opt) => opt.value === channelCount, + ); alert?.success(`已设置为${option?.label}`, 2000); }; @@ -294,7 +360,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( const getCaptureData = async () => { try { - const client = AuthManager.createAuthenticatedLogicAnalyzerClient(); + const client = AuthManager.createClient(LogicAnalyzerClient); // 获取捕获数据,使用当前设置的捕获长度 const base64Data = await client.getCaptureData(captureLength.value); @@ -308,7 +374,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( // 根据当前通道数量解析数据 const channelCount = currentChannelDiv.value; const timeStepNs = currentSamplePeriodNs.value; - + let sampleCount: number; let x: number[]; let y: number[][]; @@ -316,19 +382,16 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( if (channelCount === 1) { // 1通道:每个字节包含8个时间单位的数据 sampleCount = bytes.length * 8; - + // 创建时间轴 x = Array.from( { length: sampleCount }, (_, i) => (i * timeStepNs) / 1000, ); // 转换为微秒 - + // 创建通道数据数组 - y = Array.from( - { length: 1 }, - () => new Array(sampleCount), - ); - + y = Array.from({ length: 1 }, () => new Array(sampleCount)); + // 解析数据:每个字节的8个位对应8个时间单位 for (let byteIndex = 0; byteIndex < bytes.length; byteIndex++) { const byte = bytes[byteIndex]; @@ -340,19 +403,16 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( } else if (channelCount === 2) { // 2通道:每个字节包含4个时间单位的数据 sampleCount = bytes.length * 4; - + // 创建时间轴 x = Array.from( { length: sampleCount }, (_, i) => (i * timeStepNs) / 1000, ); // 转换为微秒 - + // 创建通道数据数组 - y = Array.from( - { length: 2 }, - () => new Array(sampleCount), - ); - + y = Array.from({ length: 2 }, () => new Array(sampleCount)); + // 解析数据:每个字节的8个位对应4个时间单位的2通道数据 // 位分布:[T3_CH1, T3_CH0, T2_CH1, T2_CH0, T1_CH1, T1_CH0, T0_CH1, T0_CH0] for (let byteIndex = 0; byteIndex < bytes.length; byteIndex++) { @@ -360,37 +420,34 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( for (let timeUnit = 0; timeUnit < 4; timeUnit++) { const timeIndex = byteIndex * 4 + timeUnit; const bitOffset = timeUnit * 2; - y[0][timeIndex] = (byte >> bitOffset) & 1; // CH0 + y[0][timeIndex] = (byte >> bitOffset) & 1; // CH0 y[1][timeIndex] = (byte >> (bitOffset + 1)) & 1; // CH1 } } } else if (channelCount === 4) { // 4通道:每个字节包含2个时间单位的数据 sampleCount = bytes.length * 2; - + // 创建时间轴 x = Array.from( { length: sampleCount }, (_, i) => (i * timeStepNs) / 1000, ); // 转换为微秒 - + // 创建通道数据数组 - y = Array.from( - { length: 4 }, - () => new Array(sampleCount), - ); - + y = Array.from({ length: 4 }, () => new Array(sampleCount)); + // 解析数据:每个字节的8个位对应2个时间单位的4通道数据 // 位分布:[T1_CH3, T1_CH2, T1_CH1, T1_CH0, T0_CH3, T0_CH2, T0_CH1, T0_CH0] for (let byteIndex = 0; byteIndex < bytes.length; byteIndex++) { const byte = bytes[byteIndex]; - + // 处理第一个时间单位(低4位) const timeIndex1 = byteIndex * 2; for (let channel = 0; channel < 4; channel++) { y[channel][timeIndex1] = (byte >> channel) & 1; } - + // 处理第二个时间单位(高4位) const timeIndex2 = byteIndex * 2 + 1; for (let channel = 0; channel < 4; channel++) { @@ -400,19 +457,16 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( } else if (channelCount === 8) { // 8通道:每个字节包含1个时间单位的8个通道数据 sampleCount = bytes.length; - + // 创建时间轴 x = Array.from( { length: sampleCount }, (_, i) => (i * timeStepNs) / 1000, ); // 转换为微秒 - + // 创建8个通道的数据 - y = Array.from( - { length: 8 }, - () => new Array(sampleCount), - ); - + y = Array.from({ length: 8 }, () => new Array(sampleCount)); + // 解析每个字节的8个位到对应通道 for (let i = 0; i < sampleCount; i++) { const byte = bytes[i]; @@ -424,30 +478,27 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( } else if (channelCount === 16) { // 16通道:每2个字节包含1个时间单位的16个通道数据 sampleCount = bytes.length / 2; - + // 创建时间轴 x = Array.from( { length: sampleCount }, (_, i) => (i * timeStepNs) / 1000, ); // 转换为微秒 - + // 创建16个通道的数据 - y = Array.from( - { length: 16 }, - () => new Array(sampleCount), - ); - + y = Array.from({ length: 16 }, () => new Array(sampleCount)); + // 解析数据:每2个字节为一个时间单位 for (let timeIndex = 0; timeIndex < sampleCount; timeIndex++) { const byteIndex = timeIndex * 2; - const byte1 = bytes[byteIndex]; // [7:0] + const byte1 = bytes[byteIndex]; // [7:0] const byte2 = bytes[byteIndex + 1]; // [15:8] - + // 处理低8位通道 [7:0] for (let channel = 0; channel < 8; channel++) { y[channel][timeIndex] = (byte1 >> channel) & 1; } - + // 处理高8位通道 [15:8] for (let channel = 0; channel < 8; channel++) { y[channel + 8][timeIndex] = (byte2 >> channel) & 1; @@ -456,42 +507,39 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( } else if (channelCount === 32) { // 32通道:每4个字节包含1个时间单位的32个通道数据 sampleCount = bytes.length / 4; - + // 创建时间轴 x = Array.from( { length: sampleCount }, (_, i) => (i * timeStepNs) / 1000, ); // 转换为微秒 - + // 创建32个通道的数据 - y = Array.from( - { length: 32 }, - () => new Array(sampleCount), - ); - + y = Array.from({ length: 32 }, () => new Array(sampleCount)); + // 解析数据:每4个字节为一个时间单位 for (let timeIndex = 0; timeIndex < sampleCount; timeIndex++) { const byteIndex = timeIndex * 4; - const byte1 = bytes[byteIndex]; // [7:0] + const byte1 = bytes[byteIndex]; // [7:0] const byte2 = bytes[byteIndex + 1]; // [15:8] const byte3 = bytes[byteIndex + 2]; // [23:16] const byte4 = bytes[byteIndex + 3]; // [31:24] - + // 处理 [7:0] for (let channel = 0; channel < 8; channel++) { y[channel][timeIndex] = (byte1 >> channel) & 1; } - + // 处理 [15:8] for (let channel = 0; channel < 8; channel++) { y[channel + 8][timeIndex] = (byte2 >> channel) & 1; } - + // 处理 [23:16] for (let channel = 0; channel < 8; channel++) { y[channel + 16][timeIndex] = (byte3 >> channel) & 1; } - + // 处理 [31:24] for (let channel = 0; channel < 8; channel++) { y[channel + 24][timeIndex] = (byte4 >> channel) & 1; @@ -525,11 +573,11 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( isCapturing.value = true; const release = await operationMutex.acquire(); try { - const client = AuthManager.createAuthenticatedLogicAnalyzerClient(); + const client = AuthManager.createClient(LogicAnalyzerClient); // 1. 先应用配置 alert?.info("正在应用配置...", 2000); - + // 准备配置数据 - 包含所有32个通道,未启用的通道设置为默认值 const allSignals = signalConfigs.map((signal, index) => { if (channels[index].enabled) { @@ -632,7 +680,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( const release = await operationMutex.acquire(); try { - const client = AuthManager.createAuthenticatedLogicAnalyzerClient(); + const client = AuthManager.createClient(LogicAnalyzerClient); // 执行强制捕获来停止当前捕获 const forceSuccess = await client.setCaptureMode(false, false); @@ -661,7 +709,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( const release = await operationMutex.acquire(); try { - const client = AuthManager.createAuthenticatedLogicAnalyzerClient(); + const client = AuthManager.createClient(LogicAnalyzerClient); // 执行强制捕获来停止当前捕获 const forceSuccess = await client.setCaptureMode(true, true); @@ -677,7 +725,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( `强制捕获失败: ${error instanceof Error ? error.message : "未知错误"}`, 3000, ); - } finally{ + } finally { release(); } }; diff --git a/src/components/MarkdownRenderer.vue b/src/components/MarkdownRenderer.vue index 43255b3..a46c008 100644 --- a/src/components/MarkdownRenderer.vue +++ b/src/components/MarkdownRenderer.vue @@ -1,759 +1,797 @@ - - - - - + + + + + diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue index 89730c9..aa274e1 100644 --- a/src/components/Navbar.vue +++ b/src/components/Navbar.vue @@ -145,6 +145,7 @@ import { ChevronDownIcon, } from "lucide-vue-next"; import { AuthManager } from "@/utils/AuthManager"; +import { DataClient } from "@/APIClient"; const router = useRouter(); @@ -158,7 +159,7 @@ const loadUserInfo = async () => { try { const authenticated = await AuthManager.isAuthenticated(); if (authenticated) { - const client = AuthManager.createAuthenticatedDataClient(); + const client = AuthManager.createClient(DataClient); const userInfo = await client.getUserInfo(); userName.value = userInfo.name; isLoggedIn.value = true; diff --git a/src/components/Oscilloscope/OscilloscopeManager.ts b/src/components/Oscilloscope/OscilloscopeManager.ts index a200527..6d007f5 100644 --- a/src/components/Oscilloscope/OscilloscopeManager.ts +++ b/src/components/Oscilloscope/OscilloscopeManager.ts @@ -4,6 +4,7 @@ import { Mutex } from "async-mutex"; import { OscilloscopeFullConfig, OscilloscopeDataResponse, + OscilloscopeApiClient, } from "@/APIClient"; import { AuthManager } from "@/utils/AuthManager"; import { useAlertStore } from "@/components/Alert"; @@ -31,257 +32,269 @@ const DEFAULT_CONFIG: OscilloscopeFullConfig = new OscilloscopeFullConfig({ }); // 采样频率常量(后端返回) -const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState(() => { - const oscData = shallowRef(); - const alert = useRequiredInjection(useAlertStore); +const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState( + () => { + const oscData = shallowRef(); + const alert = useRequiredInjection(useAlertStore); - // 互斥锁 - const operationMutex = new Mutex(); + // 互斥锁 + const operationMutex = new Mutex(); - // 状态 - const isApplying = ref(false); - const isCapturing = ref(false); + // 状态 + const isApplying = ref(false); + const isCapturing = ref(false); - // 配置 - const config = reactive(new OscilloscopeFullConfig({ ...DEFAULT_CONFIG })); + // 配置 + const config = reactive( + new OscilloscopeFullConfig({ ...DEFAULT_CONFIG }), + ); - // 采样点数(由后端数据决定) - const sampleCount = ref(0); + // 采样点数(由后端数据决定) + const sampleCount = ref(0); - // 采样周期(ns),由adFrequency计算 - const samplePeriodNs = computed(() => - oscData.value?.adFrequency ? 1_000_000_000 / oscData.value.adFrequency : 200 - ); + // 采样周期(ns),由adFrequency计算 + const samplePeriodNs = computed(() => + oscData.value?.adFrequency + ? 1_000_000_000 / oscData.value.adFrequency + : 200, + ); - // 应用配置 - const applyConfiguration = async () => { - if (operationMutex.isLocked()) { - alert.warn("有其他操作正在进行中,请稍后再试", 3000); - return; - } - const release = await operationMutex.acquire(); - isApplying.value = true; - try { - const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); - const success = await client.initialize({ ...config }); - if (success) { - alert.success("示波器配置已应用", 2000); - } else { - throw new Error("应用失败"); + // 应用配置 + const applyConfiguration = async () => { + if (operationMutex.isLocked()) { + alert.warn("有其他操作正在进行中,请稍后再试", 3000); + return; } - } catch (error) { - alert.error("应用配置失败", 3000); - } finally { - isApplying.value = false; - release(); - } - }; - - // 重置配置 - const resetConfiguration = () => { - Object.assign(config, { ...DEFAULT_CONFIG }); - alert.info("配置已重置", 2000); - }; - - const clearOscilloscopeData = () => { - oscData.value = undefined; - } - - // 获取数据 - const getOscilloscopeData = async () => { - try { - const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); - const resp: OscilloscopeDataResponse = await client.getData(); - - // 解析波形数据 - const binaryString = atob(resp.waveformData); - const bytes = new Uint8Array(binaryString.length); - for (let i = 0; i < binaryString.length; i++) { - bytes[i] = binaryString.charCodeAt(i); + const release = await operationMutex.acquire(); + isApplying.value = true; + try { + const client = AuthManager.createClient(OscilloscopeApiClient); + const success = await client.initialize({ ...config }); + if (success) { + alert.success("示波器配置已应用", 2000); + } else { + throw new Error("应用失败"); + } + } catch (error) { + alert.error("应用配置失败", 3000); + } finally { + isApplying.value = false; + release(); } - sampleCount.value = bytes.length; + }; - // 构建时间轴 + // 重置配置 + const resetConfiguration = () => { + Object.assign(config, { ...DEFAULT_CONFIG }); + alert.info("配置已重置", 2000); + }; + + const clearOscilloscopeData = () => { + oscData.value = undefined; + }; + + // 获取数据 + const getOscilloscopeData = async () => { + try { + const client = AuthManager.createClient(OscilloscopeApiClient); + const resp: OscilloscopeDataResponse = await client.getData(); + + // 解析波形数据 + const binaryString = atob(resp.waveformData); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + sampleCount.value = bytes.length; + + // 构建时间轴 + const x = Array.from( + { length: bytes.length }, + (_, i) => (i * samplePeriodNs.value) / 1000, // us + ); + const y = Array.from(bytes); + + oscData.value = { + x, + y, + xUnit: "us", + yUnit: "V", + adFrequency: resp.adFrequency, + adVpp: resp.adVpp, + adMax: resp.adMax, + adMin: resp.adMin, + }; + } catch (error) { + alert.error("获取示波器数据失败", 3000); + } + }; + + // 定时器引用 + let refreshIntervalId: number | undefined; + // 刷新间隔(毫秒),可根据需要调整 + const refreshIntervalMs = ref(1000); + + // 定时刷新函数 + const startAutoRefresh = () => { + if (refreshIntervalId !== undefined) return; + refreshIntervalId = window.setInterval(async () => { + await refreshRAM(); + await getOscilloscopeData(); + }, refreshIntervalMs.value); + }; + + const stopAutoRefresh = () => { + if (refreshIntervalId !== undefined) { + clearInterval(refreshIntervalId); + refreshIntervalId = undefined; + isCapturing.value = false; + } + }; + + // 启动捕获 + const startCapture = async () => { + if (operationMutex.isLocked()) { + alert.warn("有其他操作正在进行中,请稍后再试", 3000); + return; + } + isCapturing.value = true; + const release = await operationMutex.acquire(); + try { + const client = AuthManager.createClient(OscilloscopeApiClient); + const started = await client.startCapture(); + if (!started) throw new Error("无法启动捕获"); + alert.info("开始捕获...", 2000); + + // 启动定时刷新 + startAutoRefresh(); + } catch (error) { + alert.error("捕获失败", 3000); + isCapturing.value = false; + stopAutoRefresh(); + } finally { + release(); + } + }; + + // 停止捕获 + const stopCapture = async () => { + if (!isCapturing.value) { + alert.warn("当前没有正在进行的捕获操作", 2000); + return; + } + isCapturing.value = false; + stopAutoRefresh(); + const release = await operationMutex.acquire(); + try { + const client = AuthManager.createClient(OscilloscopeApiClient); + const stopped = await client.stopCapture(); + if (!stopped) throw new Error("无法停止捕获"); + alert.info("捕获已停止", 2000); + } catch (error) { + alert.error("停止捕获失败", 3000); + } finally { + release(); + } + }; + + // 更新触发参数 + const updateTrigger = async (level: number, risingEdge: boolean) => { + const client = AuthManager.createClient(OscilloscopeApiClient); + try { + const ok = await client.updateTrigger(level, risingEdge); + if (ok) { + config.triggerLevel = level; + config.triggerRisingEdge = risingEdge; + alert.success("触发参数已更新", 2000); + } else { + throw new Error(); + } + } catch { + alert.error("更新触发参数失败", 2000); + } + }; + + // 更新采样参数 + const updateSampling = async ( + horizontalShift: number, + decimationRate: number, + ) => { + const client = AuthManager.createClient(OscilloscopeApiClient); + try { + const ok = await client.updateSampling(horizontalShift, decimationRate); + if (ok) { + config.horizontalShift = horizontalShift; + config.decimationRate = decimationRate; + alert.success("采样参数已更新", 2000); + } else { + throw new Error(); + } + } catch { + alert.error("更新采样参数失败", 2000); + } + }; + + // 手动刷新RAM + const refreshRAM = async () => { + const client = AuthManager.createClient(OscilloscopeApiClient); + try { + const ok = await client.refreshRAM(); + if (ok) { + // alert.success("RAM已刷新", 2000); + } else { + throw new Error(); + } + } catch { + alert.error("刷新RAM失败", 2000); + } + }; + + // 生成测试数据 + const generateTestData = () => { + const freq = 5_000_000; + const duration = 0.001; // 1ms + const points = Math.floor(freq * duration); const x = Array.from( - { length: bytes.length }, - (_, i) => (i * samplePeriodNs.value) / 1000 // us + { length: points }, + (_, i) => (i * 1_000_000_000) / freq / 1000, + ); + const y = Array.from({ length: points }, (_, i) => + Math.floor(Math.sin(i * 0.01) * 127 + 128), ); - const y = Array.from(bytes); - oscData.value = { x, y, xUnit: "us", yUnit: "V", - adFrequency: resp.adFrequency, - adVpp: resp.adVpp, - adMax: resp.adMax, - adMin: resp.adMin, + adFrequency: freq, + adVpp: 2.0, + adMax: 255, + adMin: 0, }; - } catch (error) { - alert.error("获取示波器数据失败", 3000); - } - }; - - // 定时器引用 - let refreshIntervalId: number | undefined; - // 刷新间隔(毫秒),可根据需要调整 - const refreshIntervalMs = ref(1000); - - // 定时刷新函数 - const startAutoRefresh = () => { - if (refreshIntervalId !== undefined) return; - refreshIntervalId = window.setInterval(async () => { - await refreshRAM(); - await getOscilloscopeData(); - }, refreshIntervalMs.value); - }; - - const stopAutoRefresh = () => { - if (refreshIntervalId !== undefined) { - clearInterval(refreshIntervalId); - refreshIntervalId = undefined; - isCapturing.value = false; - } - }; - - // 启动捕获 - const startCapture = async () => { - if (operationMutex.isLocked()) { - alert.warn("有其他操作正在进行中,请稍后再试", 3000); - return; - } - isCapturing.value = true; - const release = await operationMutex.acquire(); - try { - const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); - const started = await client.startCapture(); - if (!started) throw new Error("无法启动捕获"); - alert.info("开始捕获...", 2000); - - // 启动定时刷新 - startAutoRefresh(); - } catch (error) { - alert.error("捕获失败", 3000); - isCapturing.value = false; - stopAutoRefresh(); - } finally { - release(); - } - }; - - // 停止捕获 - const stopCapture = async () => { - if (!isCapturing.value) { - alert.warn("当前没有正在进行的捕获操作", 2000); - return; - } - isCapturing.value = false; - stopAutoRefresh(); - const release = await operationMutex.acquire(); - try { - const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); - const stopped = await client.stopCapture(); - if (!stopped) throw new Error("无法停止捕获"); - alert.info("捕获已停止", 2000); - } catch (error) { - alert.error("停止捕获失败", 3000); - } finally { - release(); - } - }; - - // 更新触发参数 - const updateTrigger = async (level: number, risingEdge: boolean) => { - const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); - try { - const ok = await client.updateTrigger(level, risingEdge); - if (ok) { - config.triggerLevel = level; - config.triggerRisingEdge = risingEdge; - alert.success("触发参数已更新", 2000); - } else { - throw new Error(); - } - } catch { - alert.error("更新触发参数失败", 2000); - } - }; - - // 更新采样参数 - const updateSampling = async (horizontalShift: number, decimationRate: number) => { - const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); - try { - const ok = await client.updateSampling(horizontalShift, decimationRate); - if (ok) { - config.horizontalShift = horizontalShift; - config.decimationRate = decimationRate; - alert.success("采样参数已更新", 2000); - } else { - throw new Error(); - } - } catch { - alert.error("更新采样参数失败", 2000); - } - }; - - // 手动刷新RAM - const refreshRAM = async () => { - const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); - try { - const ok = await client.refreshRAM(); - if (ok) { - // alert.success("RAM已刷新", 2000); - } else { - throw new Error(); - } - } catch { - alert.error("刷新RAM失败", 2000); - } - }; - - // 生成测试数据 - const generateTestData = () => { - const freq = 5_000_000; - const duration = 0.001; // 1ms - const points = Math.floor(freq * duration); - const x = Array.from({ length: points }, (_, i) => (i * 1_000_000_000 / freq) / 1000); - const y = Array.from({ length: points }, (_, i) => - Math.floor(Math.sin(i * 0.01) * 127 + 128) - ); - oscData.value = { - x, - y, - xUnit: "us", - yUnit: "V", - adFrequency: freq, - adVpp: 2.0, - adMax: 255, - adMin: 0, + alert.success("测试数据生成成功", 2000); }; - alert.success("测试数据生成成功", 2000); - }; - return { - oscData, - config, - isApplying, - isCapturing, - sampleCount, - samplePeriodNs, - refreshIntervalMs, + return { + oscData, + config, + isApplying, + isCapturing, + sampleCount, + samplePeriodNs, + refreshIntervalMs, - applyConfiguration, - resetConfiguration, - clearOscilloscopeData, - getOscilloscopeData, - startCapture, - stopCapture, - updateTrigger, - updateSampling, - refreshRAM, - generateTestData, - }; -}); + applyConfiguration, + resetConfiguration, + clearOscilloscopeData, + getOscilloscopeData, + startCapture, + stopCapture, + updateTrigger, + updateSampling, + refreshRAM, + generateTestData, + }; + }, +); -export { useProvideOscilloscope, useOscilloscopeState, DEFAULT_CONFIG }; \ No newline at end of file +export { useProvideOscilloscope, useOscilloscopeState, DEFAULT_CONFIG }; diff --git a/src/components/TutorialCarousel.vue b/src/components/TutorialCarousel.vue index 9bc01e0..e94d175 100644 --- a/src/components/TutorialCarousel.vue +++ b/src/components/TutorialCarousel.vue @@ -81,7 +81,7 @@ import { ref, onMounted, onUnmounted } from "vue"; import { useRouter } from "vue-router"; import { AuthManager } from "@/utils/AuthManager"; -import type { ExamInfo } from "@/APIClient"; +import { ExamClient, ResourceClient, type ExamInfo } from "@/APIClient"; // 接口定义 interface Tutorial { @@ -121,7 +121,7 @@ onMounted(async () => { console.log("正在从数据库加载实验数据..."); // 创建认证客户端 - const client = AuthManager.createAuthenticatedExamClient(); + const client = AuthManager.createClient(ExamClient); // 获取实验列表 const examList: ExamInfo[] = await client.getExamList(); @@ -142,7 +142,7 @@ onMounted(async () => { try { // 获取实验的封面资源(模板资源) - const resourceClient = AuthManager.createAuthenticatedResourceClient(); + const resourceClient = AuthManager.createClient(ResourceClient); const resourceList = await resourceClient.getResourceList( exam.id, "cover", diff --git a/src/components/UploadCard.vue b/src/components/UploadCard.vue index 7853981..fe5348d 100644 --- a/src/components/UploadCard.vue +++ b/src/components/UploadCard.vue @@ -95,6 +95,7 @@ import { import { ProgressStatus } from "@/utils/signalR/server.Hubs"; import { useRequiredInjection } from "@/utils/Common"; import { useAlertStore } from "./Alert"; +import { ResourceClient } from "@/APIClient"; interface Props { maxMemory?: number; @@ -138,8 +139,7 @@ const progressHubReceiver: IProgressReceiver = { }, }; onMounted(async () => { - progressHubConnection.value = - AuthManager.createAuthenticatedProgressHubConnection(); + progressHubConnection.value = AuthManager.createHubConnection("ProgressHub"); progressHubProxy.value = getHubProxyFactory("IProgressHub").createHubProxy( progressHubConnection.value, ); @@ -175,7 +175,7 @@ async function loadAvailableBitstreams() { } try { - const resourceClient = AuthManager.createAuthenticatedResourceClient(); + const resourceClient = AuthManager.createClient(ResourceClient); // 使用新的ResourceClient API获取比特流模板资源列表 const resources = await resourceClient.getResourceList( props.examId, @@ -199,7 +199,7 @@ async function downloadExampleBitstream(bitstream: { isDownloading.value = true; try { - const resourceClient = AuthManager.createAuthenticatedResourceClient(); + const resourceClient = AuthManager.createClient(ResourceClient); // 使用新的ResourceClient API获取资源文件 const response = await resourceClient.getResourceById(bitstream.id); diff --git a/src/components/equipments/DDSPropertyEditor.vue b/src/components/equipments/DDSPropertyEditor.vue index 937ac17..ca60fc2 100644 --- a/src/components/equipments/DDSPropertyEditor.vue +++ b/src/components/equipments/DDSPropertyEditor.vue @@ -212,6 +212,7 @@ import { useEquipments } from "@/stores/equipments"; import { useDialogStore } from "@/stores/dialog"; import { toInteger } from "lodash"; import { AuthManager } from "@/utils/AuthManager"; +import { DDSClient } from "@/APIClient"; // Component Attributes const props = defineProps<{ @@ -221,7 +222,7 @@ const props = defineProps<{ const emit = defineEmits(["update:modelValue"]); // Global varibles -const dds = AuthManager.createAuthenticatedDDSClient(); +const dds = AuthManager.createClient(DDSClient); const eqps = useEquipments(); const dialog = useDialogStore(); diff --git a/src/components/equipments/Switch.vue b/src/components/equipments/Switch.vue index a0018fe..05d792a 100644 --- a/src/components/equipments/Switch.vue +++ b/src/components/equipments/Switch.vue @@ -1,17 +1,30 @@ -// filepath: c:\_Project\FPGA_WebLab\FPGA_WebLab\src\components\equipments\Switch.vue @@ -194,16 +202,14 @@ defineExpose({ display: block; padding: 0; margin: 0; - line-height: 0; /* 移除行高导致的额外间距 */ - font-size: 0; /* 防止文本节点造成的间距 */ + line-height: 0; + font-size: 0; box-sizing: content-box; overflow: visible; } - rect { transition: all 100ms ease-in-out; } - .interactive { cursor: pointer; } diff --git a/src/stores/equipments.ts b/src/stores/equipments.ts index 09ad5d3..f19edec 100644 --- a/src/stores/equipments.ts +++ b/src/stores/equipments.ts @@ -17,7 +17,14 @@ import { getHubProxyFactory, getReceiverRegister, } from "@/utils/signalR/TypedSignalR.Client"; -import { ResourcePurpose, type ResourceInfo } from "@/APIClient"; +import { + JtagClient, + MatrixKeyClient, + PowerClient, + ResourceClient, + ResourcePurpose, + type ResourceInfo, +} from "@/APIClient"; import type { IDigitalTubesHub, IJtagHub, @@ -46,8 +53,7 @@ export const useEquipments = defineStore("equipments", () => { onMounted(async () => { // 每次挂载都重新创建连接 - jtagHubConnection.value = - AuthManager.createAuthenticatedJtagHubConnection(); + jtagHubConnection.value = AuthManager.createHubConnection("JtagHub"); jtagHubProxy.value = getHubProxyFactory("IJtagHub").createHubProxy( jtagHubConnection.value, ); @@ -101,7 +107,7 @@ export const useEquipments = defineStore("equipments", () => { // 自动开启电源 await powerSetOnOff(true); - const resourceClient = AuthManager.createAuthenticatedResourceClient(); + const resourceClient = AuthManager.createClient(ResourceClient); const resp = await resourceClient.addResource( "bitstream", ResourcePurpose.User, @@ -133,7 +139,7 @@ export const useEquipments = defineStore("equipments", () => { // 自动开启电源 await powerSetOnOff(true); - const jtagClient = AuthManager.createAuthenticatedJtagClient(); + const jtagClient = AuthManager.createClient(JtagClient); const resp = await jtagClient.downloadBitstream( boardAddr.value, boardPort.value, @@ -155,7 +161,7 @@ export const useEquipments = defineStore("equipments", () => { // 自动开启电源 await powerSetOnOff(true); - const jtagClient = AuthManager.createAuthenticatedJtagClient(); + const jtagClient = AuthManager.createClient(JtagClient); const resp = await jtagClient.getDeviceIDCode( boardAddr.value, boardPort.value, @@ -175,7 +181,7 @@ export const useEquipments = defineStore("equipments", () => { // 自动开启电源 await powerSetOnOff(true); - const jtagClient = AuthManager.createAuthenticatedJtagClient(); + const jtagClient = AuthManager.createClient(JtagClient); const resp = await jtagClient.setSpeed( boardAddr.value, boardPort.value, @@ -221,8 +227,7 @@ export const useEquipments = defineStore("equipments", () => { async function matrixKeypadSetKeyStates(keyStates: boolean[]) { const release = await matrixKeypadClientMutex.acquire(); try { - const matrixKeypadClient = - AuthManager.createAuthenticatedMatrixKeyClient(); + const matrixKeypadClient = AuthManager.createClient(MatrixKeyClient); const resp = await matrixKeypadClient.setMatrixKeyStatus( boardAddr.value, boardPort.value, @@ -240,8 +245,7 @@ export const useEquipments = defineStore("equipments", () => { async function matrixKeypadEnable(enable: boolean) { const release = await matrixKeypadClientMutex.acquire(); try { - const matrixKeypadClient = - AuthManager.createAuthenticatedMatrixKeyClient(); + const matrixKeypadClient = AuthManager.createClient(MatrixKeyClient); if (enable) { const resp = await matrixKeypadClient.enabelMatrixKey( boardAddr.value, @@ -276,7 +280,7 @@ export const useEquipments = defineStore("equipments", () => { async function powerSetOnOff(enable: boolean) { const release = await powerClientMutex.acquire(); try { - const powerClient = AuthManager.createAuthenticatedPowerClient(); + const powerClient = AuthManager.createClient(PowerClient); const resp = await powerClient.setPowerOnOff( boardAddr.value, boardPort.value, @@ -338,7 +342,7 @@ export const useEquipments = defineStore("equipments", () => { onMounted(async () => { // 每次挂载都重新创建连接 sevenSegmentDisplayHub.value = - AuthManager.createAuthenticatedDigitalTubesHubConnection(); + AuthManager.createHubConnection("DigitalTubesHub"); sevenSegmentDisplayHubProxy.value = getHubProxyFactory( "IDigitalTubesHub", ).createHubProxy(sevenSegmentDisplayHub.value); diff --git a/src/utils/AuthManager.ts b/src/utils/AuthManager.ts index 85eaf9a..3aec628 100644 --- a/src/utils/AuthManager.ts +++ b/src/utils/AuthManager.ts @@ -1,322 +1,105 @@ -import { - DataClient, - VideoStreamClient, - BsdlParserClient, - DDSClient, - JtagClient, - MatrixKeyClient, - PowerClient, - RemoteUpdateClient, - TutorialClient, - UDPClient, - LogicAnalyzerClient, - NetConfigClient, - OscilloscopeApiClient, - DebuggerClient, - ExamClient, - ResourceClient, - HdmiVideoStreamClient, -} from "@/APIClient"; -import router from "@/router"; +import { DataClient } from "@/APIClient"; import { HubConnectionBuilder } from "@microsoft/signalr"; import axios, { type AxiosInstance } from "axios"; -import { isNull } from "lodash"; - -// 支持的客户端类型联合类型 -type SupportedClient = - | DataClient - | VideoStreamClient - | BsdlParserClient - | DDSClient - | JtagClient - | MatrixKeyClient - | PowerClient - | RemoteUpdateClient - | TutorialClient - | LogicAnalyzerClient - | UDPClient - | NetConfigClient - | OscilloscopeApiClient - | DebuggerClient - | ExamClient - | ResourceClient - | HdmiVideoStreamClient; +// 简单到让人想哭的认证管理器 export class AuthManager { - // 存储token到localStorage - public static setToken(token: string): void { - localStorage.setItem("authToken", token); + private static readonly TOKEN_KEY = "authToken"; + + // 核心数据:就是个字符串 + static getToken(): string | null { + return localStorage.getItem(this.TOKEN_KEY); } - // 从localStorage获取token - public static getToken(): string | null { - return localStorage.getItem("authToken"); + static setToken(token: string): void { + localStorage.setItem(this.TOKEN_KEY, token); } - // 清除token - public static clearToken(): void { - localStorage.removeItem("authToken"); + static clearToken(): void { + localStorage.removeItem(this.TOKEN_KEY); } - // 检查是否已认证 - public static async isAuthenticated(): Promise { - return await AuthManager.verifyToken(); + // 核心功能:创建带认证的HTTP配置 + static getAuthHeaders(): Record { + const token = this.getToken(); + return token ? { Authorization: `Bearer ${token}` } : {}; } - // 通用的为HTTP请求添加Authorization header的方法 - public static addAuthHeader(client: SupportedClient): void { - const token = AuthManager.getToken(); - if (token) { - // 创建一个自定义的 http 对象,包装原有的 fetch 方法 - const customHttp = { - fetch: (url: RequestInfo, init?: RequestInit) => { - if (!init) init = {}; - if (!init.headers) init.headers = {}; - - // 添加Authorization header - if (typeof init.headers === "object" && init.headers !== null) { - (init.headers as any)["Authorization"] = `Bearer ${token}`; - } - - // 使用全局 fetch 或 window.fetch - return (window as any).fetch(url, init); - }, - }; - - // 重新构造客户端,传入自定义的 http 对象 - const ClientClass = client.constructor as new ( - baseUrl?: string, - http?: any, - ) => SupportedClient; - const newClient = new ClientClass(undefined, customHttp); - - // 将新客户端的属性复制到原客户端(这是一个 workaround) - // 更好的做法是返回新的客户端实例 - Object.setPrototypeOf(client, Object.getPrototypeOf(newClient)); - Object.assign(client, newClient); - } - } - - // 私有方法:创建带认证的HTTP客户端 - private static createAuthenticatedHttp() { - const token = AuthManager.getToken(); - if (!token) { - return null; - } - - return { - fetch: (url: RequestInfo, init?: RequestInit) => { - if (!init) init = {}; - if (!init.headers) init.headers = {}; - - if (typeof init.headers === "object" && init.headers !== null) { - (init.headers as any)["Authorization"] = `Bearer ${token}`; - } - - return (window as any).fetch(url, init); - }, - }; - } - - // 私有方法:创建带认证的Axios实例 - private static createAuthenticatedAxiosInstance(): AxiosInstance | null { - const token = AuthManager.getToken(); - if (!token) return null; - - const instance = axios.create(); - instance.interceptors.request.use((config) => { - config.headers = config.headers || {}; - (config.headers as any)["Authorization"] = `Bearer ${token}`; - return config; - }); - return instance; - } - - // 通用的创建已认证客户端的方法(使用泛型) - public static createAuthenticatedClient( - ClientClass: new (baseUrl?: string, instance?: AxiosInstance) => T, + // 一个方法搞定所有客户端,不要17个垃圾方法 + static createClient( + ClientClass: new (baseUrl?: string, config?: any) => T, + baseUrl?: string, ): T { - const axiosInstance = AuthManager.createAuthenticatedAxiosInstance(); - return axiosInstance - ? new ClientClass(undefined, axiosInstance) - : new ClientClass(); + const token = this.getToken(); + if (!token) { + return new ClientClass(baseUrl); + } + + // 对于axios客户端 + const axiosInstance = axios.create({ + headers: this.getAuthHeaders(), + }); + + return new ClientClass(baseUrl, axiosInstance); } - // 便捷方法:创建已配置认证的各种客户端 - public static createAuthenticatedDataClient(): DataClient { - return AuthManager.createAuthenticatedClient(DataClient); - } - - public static createAuthenticatedVideoStreamClient(): VideoStreamClient { - return AuthManager.createAuthenticatedClient(VideoStreamClient); - } - - public static createAuthenticatedBsdlParserClient(): BsdlParserClient { - return AuthManager.createAuthenticatedClient(BsdlParserClient); - } - - public static createAuthenticatedDDSClient(): DDSClient { - return AuthManager.createAuthenticatedClient(DDSClient); - } - - public static createAuthenticatedJtagClient(): JtagClient { - return AuthManager.createAuthenticatedClient(JtagClient); - } - - public static createAuthenticatedMatrixKeyClient(): MatrixKeyClient { - return AuthManager.createAuthenticatedClient(MatrixKeyClient); - } - - public static createAuthenticatedPowerClient(): PowerClient { - return AuthManager.createAuthenticatedClient(PowerClient); - } - - public static createAuthenticatedRemoteUpdateClient(): RemoteUpdateClient { - return AuthManager.createAuthenticatedClient(RemoteUpdateClient); - } - - public static createAuthenticatedTutorialClient(): TutorialClient { - return AuthManager.createAuthenticatedClient(TutorialClient); - } - - public static createAuthenticatedUDPClient(): UDPClient { - return AuthManager.createAuthenticatedClient(UDPClient); - } - - public static createAuthenticatedLogicAnalyzerClient(): LogicAnalyzerClient { - return AuthManager.createAuthenticatedClient(LogicAnalyzerClient); - } - - public static createAuthenticatedNetConfigClient(): NetConfigClient { - return AuthManager.createAuthenticatedClient(NetConfigClient); - } - - public static createAuthenticatedOscilloscopeApiClient(): OscilloscopeApiClient { - return AuthManager.createAuthenticatedClient(OscilloscopeApiClient); - } - - public static createAuthenticatedDebuggerClient(): DebuggerClient { - return AuthManager.createAuthenticatedClient(DebuggerClient); - } - - public static createAuthenticatedExamClient(): ExamClient { - return AuthManager.createAuthenticatedClient(ExamClient); - } - - public static createAuthenticatedResourceClient(): ResourceClient { - return AuthManager.createAuthenticatedClient(ResourceClient); - } - - public static createAuthenticatedHdmiVideoStreamClient(): HdmiVideoStreamClient { - return AuthManager.createAuthenticatedClient(HdmiVideoStreamClient); - } - - public static createAuthenticatedJtagHubConnection() { + // SignalR连接 - 简单明了 + static createHubConnection( + hubPath: "ProgressHub" | "JtagHub" | "DigitalTubesHub", + ) { return new HubConnectionBuilder() - .withUrl("http://127.0.0.1:5000/hubs/JtagHub", { + .withUrl(`http://127.0.0.1:5000/hubs/${hubPath}`, { accessTokenFactory: () => this.getToken() ?? "", }) .withAutomaticReconnect() .build(); } - public static createAuthenticatedProgressHubConnection() { - return new HubConnectionBuilder() - .withUrl("http://127.0.0.1:5000/hubs/ProgressHub", { - accessTokenFactory: () => this.getToken() ?? "", - }) - .withAutomaticReconnect() - .build(); - } - - public static createAuthenticatedDigitalTubesHubConnection() { - return new HubConnectionBuilder() - .withUrl("http://127.0.0.1:5000/hubs/DigitalTubesHub", { - accessTokenFactory: () => this.getToken() ?? "", - }) - .withAutomaticReconnect() - .build(); - } - - // 登录函数 - public static async login( - username: string, - password: string, - ): Promise { + // 认证逻辑 - 去除所有废话 + static async login(username: string, password: string): Promise { try { const client = new DataClient(); const token = await client.login(username, password); - if (token) { - AuthManager.setToken(token); + if (!token) return false; - // 验证token - const authClient = AuthManager.createAuthenticatedDataClient(); - await authClient.testAuth(); + this.setToken(token); - return true; - } - return false; - } catch (error) { - AuthManager.clearToken(); - throw error; - } - } - - // 登出函数 - public static logout(): void { - AuthManager.clearToken(); - } - - // 验证当前token是否有效 - public static async verifyToken(): Promise { - try { - const token = AuthManager.getToken(); - if (!token) { - return false; - } - - const client = AuthManager.createAuthenticatedDataClient(); - await client.testAuth(); + // 验证token - 如果失败直接抛异常 + await this.createClient(DataClient).testAuth(); return true; - } catch (error) { - AuthManager.clearToken(); - return false; + } catch { + this.clearToken(); + throw new Error("Login failed"); } } - // 验证管理员权限 - public static async verifyAdminAuth(): Promise { + static logout(): void { + this.clearToken(); + } + + // 简单的验证 - 不要搞复杂 + static async isAuthenticated(): Promise { + if (!this.getToken()) return false; + try { - const token = AuthManager.getToken(); - if (!token) { - return false; - } - - const client = AuthManager.createAuthenticatedDataClient(); - await client.testAdminAuth(); + await this.createClient(DataClient).testAuth(); return true; - } catch (error) { - // 只有在token完全无效的情况下才清除token - // 401错误表示token有效但权限不足,不应清除token - if (error && typeof error === "object" && "status" in error) { - // 如果是403 (Forbidden) 或401 (Unauthorized),说明token有效但权限不足 - if (error.status === 401 || error.status === 403) { - return false; - } - // 其他状态码可能表示token无效,清除token - AuthManager.clearToken(); - } else { - // 网络错误等,不清除token - console.error("管理员权限验证失败:", error); - } + } catch { + this.clearToken(); return false; } } - // 检查客户端是否已配置认证 - public static isClientAuthenticated(client: SupportedClient): boolean { - const token = AuthManager.getToken(); - return !!token; + static async isAdminAuthenticated(): Promise { + if (!this.getToken()) return false; + + try { + await this.createClient(DataClient).testAdminAuth(); + return true; + } catch { + this.clearToken(); + return false; + } } } diff --git a/src/utils/BoardManager.ts b/src/utils/BoardManager.ts index 755a853..bb2b91a 100644 --- a/src/utils/BoardManager.ts +++ b/src/utils/BoardManager.ts @@ -17,7 +17,7 @@ export interface BoardData extends Board { const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { // 远程升级相关参数 const devPort = 1234; - const remoteUpdater = AuthManager.createAuthenticatedRemoteUpdateClient(); + const remoteUpdater = AuthManager.createClient(RemoteUpdateClient); // 统一的板卡数据 const boards = ref([]); @@ -35,13 +35,13 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { async function getAllBoards(): Promise<{ success: boolean; error?: string }> { try { // 验证管理员权限 - const hasAdminAuth = await AuthManager.verifyAdminAuth(); + const hasAdminAuth = await AuthManager.isAdminAuthenticated(); if (!hasAdminAuth) { console.error("权限验证失败"); return { success: false, error: "权限不足" }; } - const client = AuthManager.createAuthenticatedDataClient(); + const client = AuthManager.createClient(DataClient); const result = await client.getAllBoards(); if (result) { @@ -77,7 +77,7 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { ): Promise<{ success: boolean; error?: string; boardId?: string }> { try { // 验证管理员权限 - const hasAdminAuth = await AuthManager.verifyAdminAuth(); + const hasAdminAuth = await AuthManager.isAdminAuthenticated(); if (!hasAdminAuth) { console.error("权限验证失败"); return { success: false, error: "权限不足" }; @@ -89,11 +89,11 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { return { success: false, error: "参数不完整" }; } - const client = AuthManager.createAuthenticatedDataClient(); + const client = AuthManager.createClient(DataClient); const boardId = await client.addBoard(name); if (boardId) { - console.log("新增板卡成功", { boardId, name}); + console.log("新增板卡成功", { boardId, name }); // 刷新板卡列表 await getAllBoards(); return { success: true }; @@ -119,7 +119,7 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { ): Promise<{ success: boolean; error?: string }> { try { // 验证管理员权限 - const hasAdminAuth = await AuthManager.verifyAdminAuth(); + const hasAdminAuth = await AuthManager.isAdminAuthenticated(); if (!hasAdminAuth) { console.error("权限验证失败"); return { success: false, error: "权限不足" }; @@ -130,7 +130,7 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { return { success: false, error: "板卡ID不能为空" }; } - const client = AuthManager.createAuthenticatedDataClient(); + const client = AuthManager.createClient(DataClient); const result = await client.deleteBoard(boardId); if (result > 0) { diff --git a/src/views/AuthView.vue b/src/views/AuthView.vue index 0c64764..12071e9 100644 --- a/src/views/AuthView.vue +++ b/src/views/AuthView.vue @@ -274,7 +274,7 @@ const handleSignUp = async () => { // 页面初始化时检查是否已有有效token const checkExistingToken = async () => { try { - const isValid = await AuthManager.verifyToken(); + const isValid = await AuthManager.isAuthenticated(); if (isValid) { // 如果token仍然有效,直接跳转到project页面 router.go(-1); diff --git a/src/views/Exam/ExamEditModal.vue b/src/views/Exam/ExamEditModal.vue index 2501c79..f15a95d 100644 --- a/src/views/Exam/ExamEditModal.vue +++ b/src/views/Exam/ExamEditModal.vue @@ -418,7 +418,12 @@ import { FileArchiveIcon, FileJsonIcon, } from "lucide-vue-next"; -import { ExamDto, type FileParameter } from "@/APIClient"; +import { + ExamClient, + ExamDto, + ResourceClient, + type FileParameter, +} from "@/APIClient"; import { useAlertStore } from "@/components/Alert"; import { AuthManager } from "@/utils/AuthManager"; import { useRequiredInjection } from "@/utils/Common"; @@ -618,7 +623,7 @@ const submitCreateExam = async () => { isUpdating.value = true; try { - const client = AuthManager.createAuthenticatedExamClient(); + const client = AuthManager.createClient(ExamClient); let exam: ExamInfo; if (mode.value === "create") { @@ -671,7 +676,7 @@ const submitCreateExam = async () => { // 上传实验资源 async function uploadExamResources(examId: string) { - const client = AuthManager.createAuthenticatedResourceClient(); + const client = AuthManager.createClient(ResourceClient); try { // 上传MD文档 @@ -750,7 +755,7 @@ function close() { } async function editExam(examId: string) { - const client = AuthManager.createAuthenticatedExamClient(); + const client = AuthManager.createClient(ExamClient); const examInfo = await client.getExam(examId); editExamInfo.value = { diff --git a/src/views/Exam/ExamInfoModal.vue b/src/views/Exam/ExamInfoModal.vue index 42cd2e8..2fd417a 100644 --- a/src/views/Exam/ExamInfoModal.vue +++ b/src/views/Exam/ExamInfoModal.vue @@ -250,7 +250,13 @@ - - + + + + + diff --git a/src/views/Project/Index.vue b/src/views/Project/Index.vue index f03b087..92fab74 100644 --- a/src/views/Project/Index.vue +++ b/src/views/Project/Index.vue @@ -8,7 +8,7 @@ @layout="handleVerticalSplitterResize" > - - @@ -80,11 +80,13 @@ - @@ -106,22 +108,48 @@ /> - @@ -131,7 +159,7 @@