feat: 实现lazy load,加快加载速度;美化界面
This commit is contained in:
parent
3c73aa344a
commit
228e87868d
|
@ -1,11 +1,11 @@
|
||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
import HomeView from "../views/HomeView.vue";
|
const HomeView = () => import("../views/HomeView.vue");
|
||||||
import AuthView from "../views/AuthView.vue";
|
const AuthView = () => import("../views/AuthView.vue");
|
||||||
import ProjectView from "../views/Project/Index.vue";
|
const ProjectView = () => import("../views/Project/Index.vue");
|
||||||
import TestView from "../views/TestView.vue";
|
const TestView = () => import("../views/TestView.vue");
|
||||||
import UserView from "@/views/User/Index.vue";
|
const UserView = () => import("@/views/User/Index.vue");
|
||||||
import ExamView from "@/views/Exam/Index.vue";
|
const ExamView = () => import("@/views/Exam/Index.vue");
|
||||||
import MarkdownEditor from "@/components/MarkdownEditor.vue";
|
const MarkdownEditor = () => import("@/components/MarkdownEditor.vue");
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
|
|
@ -1,113 +1,153 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full flex flex-col gap-7">
|
<div class="h-full flex flex-col gap-4">
|
||||||
<div class="tabs tabs-lift flex-shrink-0 mx-5">
|
<!-- 标签栏 -->
|
||||||
<label class="tab">
|
<div class="tabs-container mx-5">
|
||||||
|
<div
|
||||||
|
class="tabs tabs-lift flex-shrink-0 bg-base-100 rounded-xl shadow-lg border border-base-300"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
v-for="tab in tabs"
|
||||||
|
:key="tab.id"
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ 'tab-active': checkID === tab.id }"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="function-bar"
|
name="function-bar"
|
||||||
id="1"
|
:id="tab.id.toString()"
|
||||||
:checked="checkID === 1"
|
:checked="checkID === tab.id"
|
||||||
@change="handleTabChange"
|
@change="handleTabChange"
|
||||||
|
class="hidden"
|
||||||
/>
|
/>
|
||||||
<TerminalIcon class="icon" />
|
<component :is="tab.icon" class="icon" />
|
||||||
日志终端
|
<span class="tab-label">{{ tab.label }}</span>
|
||||||
</label>
|
<!-- 活跃指示器 -->
|
||||||
<label class="tab">
|
<div class="active-indicator" v-if="checkID === tab.id"></div>
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="function-bar"
|
|
||||||
id="2"
|
|
||||||
:checked="checkID === 2"
|
|
||||||
@change="handleTabChange"
|
|
||||||
/>
|
|
||||||
<VideoIcon class="icon" />
|
|
||||||
HTTP视频流
|
|
||||||
</label>
|
|
||||||
<label class="tab">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="function-bar"
|
|
||||||
id="3"
|
|
||||||
:checked="checkID === 3"
|
|
||||||
@change="handleTabChange"
|
|
||||||
/>
|
|
||||||
<Monitor class="icon" />
|
|
||||||
HDMI视频流
|
|
||||||
</label>
|
|
||||||
<label class="tab">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="function-bar"
|
|
||||||
id="4"
|
|
||||||
:checked="checkID === 4"
|
|
||||||
@change="handleTabChange"
|
|
||||||
/>
|
|
||||||
<SquareActivityIcon class="icon" />
|
|
||||||
示波器
|
|
||||||
</label>
|
|
||||||
<label class="tab">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="function-bar"
|
|
||||||
id="5"
|
|
||||||
:checked="checkID === 5"
|
|
||||||
@change="handleTabChange"
|
|
||||||
/>
|
|
||||||
<Binary class="icon" />
|
|
||||||
逻辑分析仪
|
|
||||||
</label>
|
|
||||||
<label class="tab">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="function-bar"
|
|
||||||
id="6"
|
|
||||||
:checked="checkID === 6"
|
|
||||||
@change="handleTabChange"
|
|
||||||
/>
|
|
||||||
<Hand class="icon" />
|
|
||||||
嵌入式逻辑分析仪
|
|
||||||
</label>
|
|
||||||
<label class="tab">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="function-bar"
|
|
||||||
id="7"
|
|
||||||
:checked="checkID === 7"
|
|
||||||
@change="handleTabChange"
|
|
||||||
/>
|
|
||||||
<Signature class="icon" />
|
|
||||||
信号发生器
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<!-- 全屏按钮 -->
|
<!-- 全屏按钮 -->
|
||||||
|
<div class="fullscreen-container ml-auto">
|
||||||
<button
|
<button
|
||||||
class="fullscreen-btn ml-auto btn btn-ghost btn-sm"
|
class="fullscreen-btn"
|
||||||
@click="toggleFullscreen"
|
@click="toggleFullscreen"
|
||||||
:title="isFullscreen ? '退出全屏' : '全屏'"
|
:title="isFullscreen ? '退出全屏' : '全屏'"
|
||||||
>
|
>
|
||||||
<MaximizeIcon v-if="!isFullscreen" class="icon" />
|
<MaximizeIcon v-if="!isFullscreen" class="icon" />
|
||||||
<MinimizeIcon v-else class="icon" />
|
<MinimizeIcon v-else class="icon" />
|
||||||
|
<span class="btn-tooltip">{{
|
||||||
|
isFullscreen ? "退出全屏" : "全屏"
|
||||||
|
}}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- 主页面 -->
|
</div>
|
||||||
<div class="flex-1 overflow-hidden">
|
</div>
|
||||||
<div v-if="checkID === 1" class="h-full overflow-y-auto"></div>
|
|
||||||
<div v-else-if="checkID === 2" class="h-full overflow-y-auto">
|
<!-- 主内容区域 -->
|
||||||
|
<div class="content-area flex-1 overflow-hidden mx-5 mb-5">
|
||||||
|
<div
|
||||||
|
class="content-wrapper bg-base-100 rounded-xl shadow-lg border border-base-300 h-full overflow-hidden"
|
||||||
|
>
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<div v-if="isLoading" class="loading-container">
|
||||||
|
<span class="loading loading-spinner loading-xl"></span>
|
||||||
|
<p class="loading-text">正在加载 {{ getCurrentTabLabel }}...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<div v-else class="content-panel h-full overflow-hidden">
|
||||||
|
<Transition name="fade" mode="out-in">
|
||||||
|
<div :key="checkID" class="h-full overflow-y-auto">
|
||||||
|
<!-- 日志终端 -->
|
||||||
|
<div v-if="checkID === 1" class="panel-content">
|
||||||
|
<div class="panel-header">
|
||||||
|
<TerminalIcon class="panel-icon" />
|
||||||
|
<h3 class="panel-title">日志终端</h3>
|
||||||
|
</div>
|
||||||
|
<div class="terminal-placeholder">
|
||||||
|
<p class="placeholder-text">日志终端功能正在开发中...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- HTTP视频流 -->
|
||||||
|
<Suspense v-else-if="checkID === 2">
|
||||||
|
<template #default>
|
||||||
<VideoStreamView />
|
<VideoStreamView />
|
||||||
|
</template>
|
||||||
|
<template #fallback>
|
||||||
|
<div class="loading-fallback">
|
||||||
|
<span class="loading loading-spinner loading-xl"></span>
|
||||||
|
<p class="loading-text">正在加载视频流组件...</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="checkID === 3" class="h-full overflow-y-auto">
|
</template>
|
||||||
|
</Suspense>
|
||||||
|
|
||||||
|
<!-- HDMI视频流 -->
|
||||||
|
<Suspense v-else-if="checkID === 3">
|
||||||
|
<template #default>
|
||||||
<HdmiVideoStreamView />
|
<HdmiVideoStreamView />
|
||||||
|
</template>
|
||||||
|
<template #fallback>
|
||||||
|
<div class="loading-fallback">
|
||||||
|
<span class="loading loading-spinner loading-xl"></span>
|
||||||
|
<p class="loading-text">正在加载HDMI视频流组件...</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="checkID === 4" class="h-full overflow-y-auto">
|
</template>
|
||||||
|
</Suspense>
|
||||||
|
|
||||||
|
<!-- 示波器 -->
|
||||||
|
<Suspense v-else-if="checkID === 4">
|
||||||
|
<template #default>
|
||||||
<OscilloscopeView />
|
<OscilloscopeView />
|
||||||
|
</template>
|
||||||
|
<template #fallback>
|
||||||
|
<div class="loading-fallback">
|
||||||
|
<span class="loading loading-spinner loading-xl"></span>
|
||||||
|
<p class="loading-text">正在加载示波器组件...</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="checkID === 5" class="h-full overflow-y-auto">
|
</template>
|
||||||
|
</Suspense>
|
||||||
|
|
||||||
|
<!-- 逻辑分析仪 -->
|
||||||
|
<Suspense v-else-if="checkID === 5">
|
||||||
|
<template #default>
|
||||||
<LogicAnalyzerView />
|
<LogicAnalyzerView />
|
||||||
|
</template>
|
||||||
|
<template #fallback>
|
||||||
|
<div class="loading-fallback">
|
||||||
|
<span class="loading loading-spinner loading-xl"></span>
|
||||||
|
<p class="loading-text">正在加载逻辑分析仪组件...</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="checkID === 6" class="h-full overflow-y-auto">
|
</template>
|
||||||
|
</Suspense>
|
||||||
|
|
||||||
|
<!-- 嵌入式逻辑分析仪 -->
|
||||||
|
<Suspense v-else-if="checkID === 6">
|
||||||
|
<template #default>
|
||||||
<DebuggerView />
|
<DebuggerView />
|
||||||
|
</template>
|
||||||
|
<template #fallback>
|
||||||
|
<div class="loading-fallback">
|
||||||
|
<span class="loading loading-spinner loading-xl"></span>
|
||||||
|
<p class="loading-text">正在加载嵌入式逻辑分析仪组件...</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="checkID === 7" class="h-full overflow-y-auto">
|
</template>
|
||||||
|
</Suspense>
|
||||||
|
|
||||||
|
<!-- 信号发生器 -->
|
||||||
|
<Suspense v-else-if="checkID === 7">
|
||||||
|
<template #default>
|
||||||
<DDSCtrlView />
|
<DDSCtrlView />
|
||||||
|
</template>
|
||||||
|
<template #fallback>
|
||||||
|
<div class="loading-fallback">
|
||||||
|
<span class="loading loading-spinner loading-xl"></span>
|
||||||
|
<p class="loading-text">正在加载信号发生器组件...</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -126,21 +166,43 @@ import {
|
||||||
Signature,
|
Signature,
|
||||||
} from "lucide-vue-next";
|
} from "lucide-vue-next";
|
||||||
import { useLocalStorage } from "@vueuse/core";
|
import { useLocalStorage } from "@vueuse/core";
|
||||||
import VideoStreamView from "@/views/Project/VideoStream.vue";
|
|
||||||
import HdmiVideoStreamView from "@/views/Project/HdmiVideoStream.vue";
|
|
||||||
import OscilloscopeView from "@/views/Project/Oscilloscope.vue";
|
|
||||||
import LogicAnalyzerView from "@/views/Project/LogicAnalyzer.vue";
|
|
||||||
import DebuggerView from "./Debugger.vue";
|
|
||||||
import DDSCtrlView from "./DDSCtrl.vue";
|
|
||||||
import { isNull, toNumber } from "lodash";
|
import { isNull, toNumber } from "lodash";
|
||||||
import { onMounted, ref, watch } from "vue";
|
import { onMounted, ref, watch, defineAsyncComponent, computed } from "vue";
|
||||||
import { useProvideLogicAnalyzer } from "@/components/LogicAnalyzer";
|
import { useProvideLogicAnalyzer } from "@/components/LogicAnalyzer";
|
||||||
import { useProvideOscilloscope } from "@/components/Oscilloscope/OscilloscopeManager";
|
import { useProvideOscilloscope } from "@/components/Oscilloscope/OscilloscopeManager";
|
||||||
|
|
||||||
|
// 懒加载组件
|
||||||
|
const VideoStreamView = defineAsyncComponent(
|
||||||
|
() => import("@/views/Project/VideoStream.vue"),
|
||||||
|
);
|
||||||
|
const HdmiVideoStreamView = defineAsyncComponent(
|
||||||
|
() => import("@/views/Project/HdmiVideoStream.vue"),
|
||||||
|
);
|
||||||
|
const OscilloscopeView = defineAsyncComponent(
|
||||||
|
() => import("@/views/Project/Oscilloscope.vue"),
|
||||||
|
);
|
||||||
|
const LogicAnalyzerView = defineAsyncComponent(
|
||||||
|
() => import("@/views/Project/LogicAnalyzer.vue"),
|
||||||
|
);
|
||||||
|
const DebuggerView = defineAsyncComponent(() => import("./Debugger.vue"));
|
||||||
|
const DDSCtrlView = defineAsyncComponent(() => import("./DDSCtrl.vue"));
|
||||||
|
|
||||||
const analyzer = useProvideLogicAnalyzer();
|
const analyzer = useProvideLogicAnalyzer();
|
||||||
const oscilloscopeManager = useProvideOscilloscope();
|
const oscilloscopeManager = useProvideOscilloscope();
|
||||||
|
|
||||||
const checkID = useLocalStorage("checkID", 1);
|
const checkID = useLocalStorage("checkID", 1);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
|
||||||
|
// 标签页配置
|
||||||
|
const tabs = [
|
||||||
|
{ id: 1, label: "日志终端", icon: TerminalIcon },
|
||||||
|
{ id: 2, label: "HTTP视频流", icon: VideoIcon },
|
||||||
|
{ id: 3, label: "HDMI视频流", icon: Monitor },
|
||||||
|
{ id: 4, label: "示波器", icon: SquareActivityIcon },
|
||||||
|
{ id: 5, label: "逻辑分析仪", icon: Binary },
|
||||||
|
{ id: 6, label: "嵌入式逻辑分析仪", icon: Hand },
|
||||||
|
{ id: 7, label: "信号发生器", icon: Signature },
|
||||||
|
];
|
||||||
|
|
||||||
// 定义事件
|
// 定义事件
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -162,11 +224,30 @@ watch(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 获取当前标签页标签
|
||||||
|
const getCurrentTabLabel = computed(() => {
|
||||||
|
const currentTab = tabs.find((tab) => tab.id === checkID.value);
|
||||||
|
return currentTab?.label || "";
|
||||||
|
});
|
||||||
|
|
||||||
function handleTabChange(event: Event) {
|
function handleTabChange(event: Event) {
|
||||||
const target = event.currentTarget as HTMLInputElement;
|
const target = event.currentTarget as HTMLInputElement;
|
||||||
if (isNull(target)) return;
|
if (isNull(target)) return;
|
||||||
|
|
||||||
checkID.value = toNumber(target.id);
|
const newTabId = toNumber(target.id);
|
||||||
|
|
||||||
|
// 如果不是日志终端(需要懒加载的组件),显示加载状态
|
||||||
|
if (newTabId !== 1 && newTabId !== checkID.value) {
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
// 模拟加载延迟,让用户看到加载状态
|
||||||
|
setTimeout(() => {
|
||||||
|
checkID.value = newTabId;
|
||||||
|
isLoading.value = false;
|
||||||
|
}, 300);
|
||||||
|
} else {
|
||||||
|
checkID.value = newTabId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFullscreen() {
|
function toggleFullscreen() {
|
||||||
|
@ -177,19 +258,286 @@ function toggleFullscreen() {
|
||||||
<style scoped lang="postcss">
|
<style scoped lang="postcss">
|
||||||
@import "@/assets/main.css";
|
@import "@/assets/main.css";
|
||||||
|
|
||||||
.icon {
|
/* 标签栏容器 */
|
||||||
@apply h-4 w-4 opacity-70 mr-1.5;
|
.tabs-container {
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
@apply relative flex items-center;
|
@apply relative flex items-center p-1 gap-1;
|
||||||
|
background: linear-gradient(135deg, hsl(var(--b1)) 0%, hsl(var(--b2)) 100%);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标签项样式 */
|
||||||
|
.tab-item {
|
||||||
|
@apply relative flex items-center px-4 py-3 cursor-pointer rounded-lg transition-all duration-300;
|
||||||
|
@apply hover:bg-base-200;
|
||||||
|
position: relative;
|
||||||
|
min-width: 120px;
|
||||||
|
justify-content: center;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item:hover {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
hsl(var(--primary) / 0.1) 0%,
|
||||||
|
hsl(var(--secondary) / 0.1) 100%
|
||||||
|
);
|
||||||
|
border-color: hsl(var(--primary) / 0.2);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px hsl(var(--primary) / 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.tab-active {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
hsl(var(--primary)) 0%,
|
||||||
|
hsl(var(--secondary)) 100%
|
||||||
|
);
|
||||||
|
color: hsl(var(--primary-content));
|
||||||
|
border-color: hsl(var(--primary));
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px hsl(var(--primary) / 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.tab-active .icon {
|
||||||
|
@apply opacity-100;
|
||||||
|
filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.3));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图标样式 */
|
||||||
|
.icon {
|
||||||
|
@apply h-4 w-4 opacity-70 mr-2 transition-all duration-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item:hover .icon {
|
||||||
|
@apply opacity-100;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标签文字 */
|
||||||
|
.tab-label {
|
||||||
|
@apply text-sm font-medium transition-all duration-300;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item:hover .tab-label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 活跃指示器 */
|
||||||
|
.active-indicator {
|
||||||
|
@apply absolute -bottom-1 left-1/2 transform -translate-x-1/2;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background: hsl(var(--primary-content));
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 8px hsl(var(--primary-content) / 0.8);
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, 0) scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: translate(-50%, 0) scale(1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 全屏按钮容器 */
|
||||||
|
.fullscreen-container {
|
||||||
|
@apply flex items-center justify-center p-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-btn {
|
.fullscreen-btn {
|
||||||
@apply flex items-center justify-center p-2 rounded-lg transition-colors;
|
@apply relative flex items-center justify-center p-3 rounded-lg transition-all duration-300;
|
||||||
|
@apply bg-base-200 hover:bg-primary hover:text-primary-content;
|
||||||
|
@apply border border-base-300 hover:border-primary;
|
||||||
|
@apply shadow-md hover:shadow-lg;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-btn:hover {
|
||||||
|
transform: translateY(-1px) scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-btn .icon {
|
.fullscreen-btn .icon {
|
||||||
@apply mr-0;
|
@apply mr-0 transition-transform duration-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-btn:hover .icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 工具提示 */
|
||||||
|
.btn-tooltip {
|
||||||
|
@apply absolute -top-10 left-1/2 transform -translate-x-1/2;
|
||||||
|
@apply bg-base-content text-base-100 text-xs px-2 py-1 rounded;
|
||||||
|
@apply opacity-0 pointer-events-none transition-opacity duration-200;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-btn:hover .btn-tooltip {
|
||||||
|
@apply opacity-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 内容区域 */
|
||||||
|
.content-area {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
background: linear-gradient(135deg, hsl(var(--b1)) 0%, hsl(var(--b2)) 100%);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid hsl(var(--border) / 0.2);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
hsl(var(--primary) / 0.3),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 面板内容 */
|
||||||
|
.content-panel {
|
||||||
|
@apply h-full;
|
||||||
|
animation: slideIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 面板头部 */
|
||||||
|
.panel-content {
|
||||||
|
@apply h-full flex flex-col;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
@apply flex items-center p-4 border-b border-base-300 bg-base-100;
|
||||||
|
background: linear-gradient(135deg, hsl(var(--b1)) 0%, hsl(var(--b2)) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-icon {
|
||||||
|
@apply h-5 w-5 mr-3 text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
@apply text-lg font-semibold text-base-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 终端占位符 */
|
||||||
|
.terminal-placeholder {
|
||||||
|
@apply flex-1 flex items-center justify-center;
|
||||||
|
background: linear-gradient(135deg, hsl(var(--b1)) 0%, hsl(var(--b3)) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-text {
|
||||||
|
@apply text-base-content opacity-60 text-center;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载状态 */
|
||||||
|
.loading-container,
|
||||||
|
.loading-fallback {
|
||||||
|
@apply h-full flex items-center justify-center gap-5;
|
||||||
|
background: linear-gradient(135deg, hsl(var(--b1)) 0%, hsl(var(--b2)) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
@apply text-base-content opacity-70 text-sm;
|
||||||
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 过渡动画 */
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.tab-item {
|
||||||
|
@apply px-2 py-2;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-label {
|
||||||
|
@apply text-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
@apply h-3 w-3 mr-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 深色模式适配 */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.content-wrapper::before {
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
hsl(var(--primary) / 0.5),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 辅助功能 */
|
||||||
|
.tab-item:focus-visible {
|
||||||
|
outline: 2px solid hsl(var(--primary));
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-btn:focus-visible {
|
||||||
|
outline: 2px solid hsl(var(--primary));
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 高对比度模式 */
|
||||||
|
@media (prefers-contrast: high) {
|
||||||
|
.tab-item {
|
||||||
|
border: 2px solid hsl(var(--base-content) / 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.tab-active {
|
||||||
|
border: 2px solid hsl(var(--primary));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="oscilloscope-container min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 dark:from-slate-900 dark:to-slate-800 p-4"
|
class="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 dark:from-slate-900 dark:to-slate-800 p-4"
|
||||||
>
|
>
|
||||||
<!-- 顶部状态栏 -->
|
<!-- 顶部状态栏 -->
|
||||||
<div class="status-bar mb-6">
|
<div class="status-bar mb-6">
|
||||||
|
@ -80,13 +80,13 @@
|
||||||
<div
|
<div
|
||||||
class="w-2 h-2 bg-green-500 rounded-full animate-pulse"
|
class="w-2 h-2 bg-green-500 rounded-full animate-pulse"
|
||||||
></div>
|
></div>
|
||||||
{{ refreshCycle }}ms 刷新
|
{{ osc.config.captureFrequency }}Hz 刷新频率
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="waveform-display h-96 lg:h-[500px] relative overflow-hidden rounded-lg border border-slate-200 dark:border-slate-700"
|
class="waveform-display h-full relative overflow-hidden rounded-lg border border-slate-200 dark:border-slate-700"
|
||||||
>
|
>
|
||||||
<OscilloscopeWaveformDisplay class="w-full h-full" />
|
<OscilloscopeWaveformDisplay class="w-full h-full" />
|
||||||
|
|
||||||
|
@ -154,9 +154,9 @@
|
||||||
class="range range-primary [--range-bg:#2b7fff]"
|
class="range range-primary [--range-bg:#2b7fff]"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="range-labels flex justify-between text-xs text-slate-500 mt-1"
|
class="range-labels flex justify-between text-xs text-slate-500 mt-1 mx-2"
|
||||||
>
|
>
|
||||||
<span>0</span>
|
<span> 0 </span>
|
||||||
<span>128</span>
|
<span>128</span>
|
||||||
<span>255</span>
|
<span>255</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -214,15 +214,17 @@
|
||||||
|
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text font-medium">刷新间隔</span>
|
<span class="label-text font-medium">刷新频率</span>
|
||||||
<span class="label-text-alt">{{ refreshCycle }}ms</span>
|
<span class="label-text-alt"
|
||||||
|
>{{ osc.config.captureFrequency }}Hz</span
|
||||||
|
>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min="1"
|
min="1"
|
||||||
max="1000"
|
max="1000"
|
||||||
step="1"
|
step="1"
|
||||||
v-model="refreshCycle"
|
v-model="osc.config.captureFrequency"
|
||||||
class="range range-info [--range-bg:#51a2ff]"
|
class="range range-info [--range-bg:#51a2ff]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -331,6 +333,7 @@ import { useEquipments } from "@/stores/equipments";
|
||||||
import { useOscilloscopeState } from "@/components/Oscilloscope/OscilloscopeManager";
|
import { useOscilloscopeState } from "@/components/Oscilloscope/OscilloscopeManager";
|
||||||
import { useRequiredInjection } from "@/utils/Common";
|
import { useRequiredInjection } from "@/utils/Common";
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
|
import { watchEffect } from "vue";
|
||||||
|
|
||||||
// 使用全局设备配置
|
// 使用全局设备配置
|
||||||
const equipments = useEquipments();
|
const equipments = useEquipments();
|
||||||
|
@ -338,8 +341,6 @@ const equipments = useEquipments();
|
||||||
// 获取示波器状态和操作
|
// 获取示波器状态和操作
|
||||||
const osc = useRequiredInjection(useOscilloscopeState);
|
const osc = useRequiredInjection(useOscilloscopeState);
|
||||||
|
|
||||||
const refreshCycle = ref(10);
|
|
||||||
|
|
||||||
// 计算是否有波形数据
|
// 计算是否有波形数据
|
||||||
const hasWaveformData = computed(() => {
|
const hasWaveformData = computed(() => {
|
||||||
const data = osc.oscData.value;
|
const data = osc.oscData.value;
|
||||||
|
@ -357,7 +358,6 @@ function toggleCapture() {
|
||||||
|
|
||||||
function resetConfiguration() {
|
function resetConfiguration() {
|
||||||
osc.resetConfiguration();
|
osc.resetConfiguration();
|
||||||
refreshCycle.value = 1000 / osc.config.captureFrequency;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -395,7 +395,7 @@ function resetConfiguration() {
|
||||||
|
|
||||||
/* 滑块样式美化 */
|
/* 滑块样式美化 */
|
||||||
.range {
|
.range {
|
||||||
@apply rounded-lg appearance-none cursor-pointer;
|
@apply rounded-lg appearance-none cursor-pointer w-full px-2;
|
||||||
--range-fill: 0;
|
--range-fill: 0;
|
||||||
--range-thumb: white;
|
--range-thumb: white;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue