FPGA_WebLab/src/views/Project/Oscilloscope.vue

530 lines
17 KiB
Vue

<template>
<div
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="card bg-white/80 dark:bg-slate-800/80 backdrop-blur-lg shadow-xl border border-white/20"
>
<div class="card-body p-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<div class="status-indicator flex items-center gap-2">
<div class="relative">
<Activity class="w-6 h-6 text-blue-600" />
<div
v-if="osc.isCapturing.value"
class="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full animate-pulse"
></div>
</div>
<div>
<h1
class="text-xl font-bold text-slate-800 dark:text-slate-200"
>
数字示波器
</h1>
<p class="text-sm text-slate-600 dark:text-slate-400">
{{ osc.isCapturing.value ? "正在采集数据..." : "待机状态" }}
</p>
</div>
</div>
</div>
<div class="control-buttons flex items-center gap-3">
<button
class="btn-gradient"
:class="osc.isCapturing.value ? 'btn-stop' : 'btn-start'"
@click="toggleCapture"
>
<component
:is="osc.isCapturing.value ? Square : Play"
class="w-5 h-5"
/>
{{ osc.isCapturing.value ? "停止采集" : "开始采集" }}
</button>
<button
class="btn-clear"
@click="osc.clearOscilloscopeData"
:disabled="osc.isCapturing.value"
>
<Trash2 class="w-4 h-4" />
清空数据
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 主要内容区域 -->
<div class="main-content grid grid-cols-1 xl:grid-cols-4 gap-6">
<!-- 波形显示区域 - 占据大部分空间 -->
<div class="waveform-section xl:col-span-3">
<div
class="card bg-white/80 dark:bg-slate-800/80 backdrop-blur-lg shadow-xl border border-white/20 h-full"
>
<div class="card-body p-6">
<div class="waveform-header flex items-center justify-between mb-4">
<h2
class="text-lg font-semibold text-slate-800 dark:text-slate-200 flex items-center gap-2"
>
<Zap class="w-5 h-5 text-yellow-500" />
波形显示
</h2>
<div class="waveform-controls flex items-center gap-2">
<div
class="refresh-indicator flex items-center gap-2 text-sm text-slate-600 dark:text-slate-400"
>
<div
class="w-2 h-2 bg-green-500 rounded-full animate-pulse"
></div>
{{ osc.config.captureFrequency }}Hz 刷新频率
</div>
</div>
</div>
<div
class="waveform-display h-full relative overflow-hidden rounded-lg border border-slate-200 dark:border-slate-700"
>
<OscilloscopeWaveformDisplay class="w-full h-full" />
<!-- 数据覆盖层 -->
<div
v-if="osc.isCapturing.value && !hasWaveformData"
class="absolute inset-0 flex items-center justify-center bg-slate-50/50 dark:bg-slate-900/50 backdrop-blur-sm"
>
<div class="text-center space-y-4">
<div class="w-16 h-16 mx-auto text-slate-400">
<Activity class="w-full h-full" />
</div>
<p class="text-slate-600 dark:text-slate-400">
等待波形数据...
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 控制面板 -->
<div class="control-panel xl:col-span-1">
<div class="space-y-6">
<!-- 触发设置 -->
<div
class="card bg-white/80 dark:bg-slate-800/80 backdrop-blur-lg shadow-xl border border-white/20"
>
<div class="card-body p-4">
<h3
class="text-lg font-semibold text-slate-800 dark:text-slate-200 flex items-center gap-2 mb-4"
>
<Target class="w-5 h-5 text-red-500" />
触发设置
</h3>
<div class="space-y-4">
<div class="form-control">
<label class="label">
<span class="label-text font-medium">触发边沿</span>
</label>
<select
v-model="osc.config.triggerRisingEdge"
class="select select-bordered w-full focus:border-blue-500 transition-colors"
>
<option :value="true">上升沿 ↗</option>
<option :value="false">下降沿 </option>
</select>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">触发电平</span>
<span class="label-text-alt">{{ triggerLevel }}</span>
</label>
<input
type="range"
min="0"
max="255"
step="1"
v-model="triggerLevel"
class="range range-primary [--range-bg:#2b7fff]"
/>
<div
class="range-labels flex justify-between text-xs text-slate-500 mt-1 mx-2"
>
<span>&ensp;0&ensp;</span>
<span>128</span>
<span>255</span>
</div>
</div>
</div>
</div>
</div>
<!-- 时基设置 -->
<div
class="card bg-white/80 dark:bg-slate-800/80 backdrop-blur-lg shadow-xl border border-white/20"
>
<div class="card-body p-4">
<h3
class="text-lg font-semibold text-slate-800 dark:text-slate-200 flex items-center gap-2 mb-4"
>
<Clock class="w-5 h-5 text-blue-500" />
时基控制
</h3>
<div class="space-y-4">
<div class="form-control">
<label class="label">
<span class="label-text font-medium">水平偏移</span>
<span class="label-text-alt">{{ horizontalShift }}</span>
</label>
<input
type="range"
min="0"
max="1000"
step="1"
v-model="horizontalShift"
class="range range-secondary [--range-bg:#c27aff]"
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">抽取率</span>
<span class="label-text-alt">{{ decimationRate }}%</span>
</label>
<input
type="range"
min="0"
max="100"
step="1"
v-model="decimationRate"
class="range range-accent [--range-bg:#fb64b6]"
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">刷新频率</span>
<span class="label-text-alt">{{ captureFrequency }}Hz</span>
</label>
<input
type="range"
min="1"
max="1000"
step="1"
v-model="captureFrequency"
class="range range-info [--range-bg:#51a2ff]"
/>
</div>
</div>
</div>
</div>
<!-- 系统控制 -->
<div
class="card bg-white/80 dark:bg-slate-800/80 backdrop-blur-lg shadow-xl border border-white/20"
>
<div class="card-body p-4">
<div
class="card-title flex flex-row justify-between items-center mb-4"
>
<h3
class="text-lg font-semibold text-slate-800 dark:text-slate-200 flex items-center gap-2"
>
<Settings class="w-5 h-5 text-purple-500" />
系统控制
</h3>
<!-- 自动应用开关 -->
<div class="form-control">
<label class="label cursor-pointer">
<span class="label-text text-sm font-medium"
>自动应用设置</span
>
<input
type="checkbox"
class="toggle toggle-primary"
v-model="osc.isAutoApplying"
/>
</label>
</div>
</div>
<div class="space-y-4">
<!-- 控制按钮组 -->
<div class="space-y-2">
<button
class="btn-primary-full"
@click="applyConfiguration"
:disabled="osc.isApplying.value || osc.isCapturing.value"
>
<CheckCircle class="w-4 h-4" />
应用配置
</button>
<button
class="btn-secondary-full"
@click="resetConfiguration"
:disabled="osc.isApplying.value || osc.isCapturing.value"
>
<RotateCcw class="w-4 h-4" />
重置配置
</button>
<button
class="btn-outline-full"
@click="osc.refreshRAM"
:disabled="osc.isApplying.value || osc.isCapturing.value"
>
<RefreshCw class="w-4 h-4" />
刷新RAM
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 状态提示 -->
<div
v-if="osc.isApplying.value"
class="fixed bottom-4 right-4 alert alert-info shadow-lg max-w-sm animate-slide-in-right"
>
<div class="flex items-center gap-2">
<div
class="animate-spin w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full"
></div>
<span>正在应用配置...</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {
Activity,
Settings,
Play,
Square,
Trash2,
Zap,
Target,
Clock,
CheckCircle,
RotateCcw,
RefreshCw,
} from "lucide-vue-next";
import { OscilloscopeWaveformDisplay } from "@/components/Oscilloscope";
import { useEquipments } from "@/stores/equipments";
import { useOscilloscopeState } from "@/components/Oscilloscope/OscilloscopeManager";
import { useRequiredInjection } from "@/utils/Common";
import { ref, computed } from "vue";
import { watchEffect } from "vue";
import { toNumber } from "lodash";
// 使用全局设备配置
const equipments = useEquipments();
// 获取示波器状态和操作
const osc = useRequiredInjection(useOscilloscopeState);
const decimationRate = ref(osc.config.decimationRate);
watchEffect(() => {
osc.config.decimationRate = toNumber(decimationRate.value);
});
const captureFrequency = ref(osc.config.captureFrequency);
watchEffect(() => {
osc.config.captureFrequency = toNumber(captureFrequency.value);
});
const triggerLevel = ref(osc.config.triggerLevel);
watchEffect(() => {
osc.config.triggerLevel = toNumber(triggerLevel.value);
});
const horizontalShift = ref(osc.config.horizontalShift);
watchEffect(() => {
osc.config.horizontalShift = toNumber(horizontalShift.value);
});
// 计算是否有波形数据
const hasWaveformData = computed(() => {
const data = osc.oscData.value;
return data && data.x && data.y && data.x.length > 0;
});
// 应用配置
function applyConfiguration() {
osc.applyConfiguration();
}
function toggleCapture() {
osc.toggleCapture();
}
function resetConfiguration() {
osc.resetConfiguration();
horizontalShift.value = osc.config.horizontalShift;
triggerLevel.value = osc.config.triggerLevel;
captureFrequency.value = osc.config.captureFrequency;
decimationRate.value = osc.config.decimationRate;
}
</script>
<style scoped lang="postcss">
@import "@/assets/main.css";
/* 渐变按钮样式 */
.btn-gradient {
@apply px-6 py-3 rounded-lg font-medium transition-all duration-300 transform hover:scale-105 active:scale-95 shadow-lg flex items-center gap-2;
}
.btn-start {
@apply bg-gradient-to-r from-green-500 to-blue-600 hover:from-green-600 hover:to-blue-700 text-white shadow-green-200 hover:shadow-green-300;
}
.btn-stop {
@apply bg-gradient-to-r from-red-500 to-pink-600 hover:from-red-600 hover:to-pink-700 text-white shadow-red-200 hover:shadow-red-300;
}
.btn-clear {
@apply px-4 py-3 bg-gradient-to-r from-orange-500 to-red-500 hover:from-orange-600 hover:to-red-600 text-white rounded-lg font-medium transition-all duration-300 transform hover:scale-105 active:scale-95 shadow-lg flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none;
}
/* 全宽按钮样式 */
.btn-primary-full {
@apply w-full px-4 py-3 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white rounded-lg font-medium transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none;
}
.btn-secondary-full {
@apply w-full px-4 py-3 bg-gradient-to-r from-gray-500 to-gray-600 hover:from-gray-600 hover:to-gray-700 text-white rounded-lg font-medium transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none;
}
.btn-outline-full {
@apply w-full px-4 py-3 border-2 border-slate-300 dark:border-slate-600 hover:border-blue-500 dark:hover:border-blue-400 text-slate-700 dark:text-slate-300 hover:text-blue-600 dark:hover:text-blue-400 rounded-lg font-medium transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-sm hover:shadow-md flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none;
}
/* 滑块样式美化 */
.range {
@apply rounded-lg appearance-none cursor-pointer w-full px-2;
--range-fill: 0;
--range-thumb: white;
}
.range::-webkit-slider-thumb {
@apply appearance-none bg-white border-2 border-current rounded-full cursor-pointer shadow-lg hover:shadow-xl transition-shadow duration-200;
}
.range::-moz-range-thumb {
@apply bg-white border-2 border-current rounded-full cursor-pointer shadow-lg hover:shadow-xl transition-shadow duration-200;
}
/* 范围标签 */
.range-labels {
margin-top: 0.25rem;
}
/* 卡片悬停效果 */
.card {
@apply transition-all duration-300 hover:shadow-2xl hover:scale-[1.01];
}
/* 自定义动画 */
@keyframes slide-in-right {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.animate-slide-in-right {
animation: slide-in-right 0.3s ease-out;
}
/* 玻璃态效果增强 */
.backdrop-blur-lg {
backdrop-filter: blur(16px);
}
/* 状态指示器脉动效果 */
@keyframes pulse-glow {
0%,
100% {
box-shadow: 0 0 5px currentColor;
}
50% {
box-shadow:
0 0 20px currentColor,
0 0 30px currentColor;
}
}
.status-indicator .animate-pulse {
animation: pulse-glow 2s infinite;
}
/* 响应式调整 */
@media (max-width: 1280px) {
.main-content {
@apply grid-cols-1;
}
.control-panel {
@apply order-first;
}
.control-panel .space-y-6 {
@apply grid grid-cols-1 md:grid-cols-3 gap-4 space-y-0;
}
}
@media (max-width: 768px) {
.control-panel .space-y-6 {
@apply grid-cols-1 space-y-4;
}
.control-buttons {
@apply flex-col gap-2;
}
.status-bar .card-body {
@apply flex-col items-start gap-4;
}
}
/* 滚动条美化 */
::-webkit-scrollbar {
@apply w-2;
}
::-webkit-scrollbar-track {
@apply bg-slate-100 dark:bg-slate-800 rounded-full;
}
::-webkit-scrollbar-thumb {
@apply bg-slate-300 dark:bg-slate-600 rounded-full hover:bg-slate-400 dark:hover:bg-slate-500;
}
/* 输入焦点效果 */
.select:focus,
.input:focus {
@apply ring-2 ring-blue-500 opacity-50 border-blue-500;
}
/* 切换开关样式 */
.toggle {
@apply transition-all duration-300;
}
.toggle:checked {
@apply shadow-lg;
}
</style>