530 lines
17 KiB
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> 0 </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>
|