feat: 迁移信号发生器前端至底栏

This commit is contained in:
SikongJueluo 2025-08-17 20:30:35 +08:00
parent cbb83d3dcd
commit 3c52110a2f
No known key found for this signature in database
2 changed files with 902 additions and 2 deletions

View File

@ -67,6 +67,17 @@
<Hand class="icon" />
嵌入式逻辑分析仪
</label>
<label class="tab">
<input
type="radio"
name="function-bar"
id="7"
:checked="checkID === 7"
@change="handleTabChange"
/>
<Signature class="icon" />
信号发生器
</label>
<!-- 全屏按钮 -->
<button
class="fullscreen-btn ml-auto btn btn-ghost btn-sm"
@ -93,7 +104,10 @@
<LogicAnalyzerView />
</div>
<div v-else-if="checkID === 6" class="h-full overflow-y-auto">
<Debugger />
<DebuggerView />
</div>
<div v-else-if="checkID === 7" class="h-full overflow-y-auto">
<DDSCtrlView />
</div>
</div>
</div>
@ -109,15 +123,17 @@ import {
Binary,
Hand,
Monitor,
Signature,
} from "lucide-vue-next";
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 { onMounted, ref, watch } from "vue";
import Debugger from "./Debugger.vue";
import { useProvideLogicAnalyzer } from "@/components/LogicAnalyzer";
import { useProvideOscilloscope } from "@/components/Oscilloscope/OscilloscopeManager";

View File

@ -0,0 +1,884 @@
<template>
<div
class="dds-controller min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 dark:from-slate-900 dark:to-blue-900 p-4"
>
<!-- 主要内容区域 -->
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6">
<!-- 左侧: 波形显示和自定义波形区域 (xl屏幕时占2列) -->
<div class="xl:col-span-2 space-y-6">
<!-- 波形显示区 -->
<div
class="card bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-xl border border-slate-200/50 dark:border-slate-700/50 hover:shadow-2xl transition-all duration-300"
>
<div class="card-body p-6">
<div class="flex items-center gap-3 mb-4">
<div
class="p-2 rounded-lg bg-gradient-to-r from-green-400 to-green-600"
>
<Signature class="w-6 h-6 text-white" />
</div>
<h2 class="text-2xl font-bold text-slate-800 dark:text-slate-200">
实时波形显示
</h2>
</div>
<!-- 波形显示容器 -->
<div
ref="waveformContainer"
class="relative bg-slate-900 rounded-xl p-4 border-2 border-slate-700 overflow-hidden group"
>
<div
class="absolute inset-0 bg-gradient-to-r from-blue-500/10 to-purple-500/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
></div>
<svg
:width="svgWidth"
:height="svgHeight"
:viewBox="`0 0 ${svgWidth} ${svgHeight}`"
class="w-full h-auto transition-all duration-500 ease-in-out"
style="min-height: 300px"
>
<!-- 背景网格 -->
<defs>
<pattern
id="grid"
width="20"
height="20"
patternUnits="userSpaceOnUse"
>
<path
d="M 20 0 L 0 0 0 20"
fill="none"
stroke="#334155"
stroke-width="0.5"
opacity="0.3"
/>
</pattern>
</defs>
<rect :width="svgWidth" :height="svgHeight" fill="url(#grid)" />
<!-- 波形路径 -->
<path
:d="currentWaveformPath"
stroke="url(#waveGradient)"
stroke-width="3"
fill="none"
class="animate-pulse"
filter="url(#glow)"
/>
<!-- 渐变定义 -->
<defs>
<linearGradient
id="waveGradient"
x1="0%"
y1="0%"
x2="100%"
y2="0%"
>
<stop
offset="0%"
style="stop-color: #10b981; stop-opacity: 1"
/>
<stop
offset="50%"
style="stop-color: #06d6a0; stop-opacity: 1"
/>
<stop
offset="100%"
style="stop-color: #0891b2; stop-opacity: 1"
/>
</linearGradient>
<filter id="glow">
<feGaussianBlur stdDeviation="2" result="coloredBlur" />
<feMerge>
<feMergeNode in="coloredBlur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<!-- 信息显示 -->
<text
x="20"
y="30"
fill="#10b981"
font-size="16"
font-weight="bold"
class="drop-shadow-sm"
>
{{ displayFrequency }}
</text>
<text
:x="svgWidth - 80"
y="30"
fill="#10b981"
font-size="16"
font-weight="bold"
class="drop-shadow-sm"
>
φ: {{ state.phase }}°
</text>
<text
:x="svgWidth / 2"
:y="svgHeight - 10"
fill="#10b981"
font-size="16"
font-weight="bold"
text-anchor="middle"
class="drop-shadow-sm"
>
{{ displayTimebase }}
</text>
</svg>
</div>
</div>
</div>
<!-- 自定义波形区域 (xl屏幕时在波形显示下方) -->
<div
class="card hidden xl:block bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg border border-slate-200/50 dark:border-slate-700/50"
>
<div class="card-body p-6">
<h3
class="font-bold text-xl text-slate-800 dark:text-slate-200 mb-4 flex items-center gap-3"
>
<div
class="p-2 rounded-lg bg-gradient-to-r from-purple-400 to-purple-600"
>
<CodeIcon class="w-5 h-5 text-white" />
</div>
自定义波形函数
</h3>
<div class="space-y-4">
<div class="flex items-center gap-3">
<label
class="text-sm font-medium text-slate-700 dark:text-slate-300 min-w-fit"
>函数表达式:</label
>
<input
v-model="state.customExpr"
class="input input-bordered flex-1 transition-all duration-200 focus:shadow-md focus:scale-[1.02]"
placeholder="例如: sin(t) 或 x^(2/3)+0.9*sqrt(3.3-x^2)*sin(a*PI*x) [a=7.8]"
@keyup.enter="applyCustomWaveform"
/>
<button
class="btn btn-primary font-bold hover:shadow-lg transition-all duration-300 transform hover:scale-105"
@click="applyCustomWaveform"
>
<PlayIcon class="w-4 h-4 mr-2" />
应用
</button>
</div>
<div>
<div
class="text-sm font-medium text-slate-700 dark:text-slate-300 mb-2"
>
示例函数:
</div>
<div class="flex flex-wrap gap-2">
<button
class="btn btn-sm btn-outline btn-secondary hover:shadow-md transition-all duration-200 transform hover:scale-105"
@click="applyExampleFunction('sin(t)')"
>
正弦波
</button>
<button
class="btn btn-sm btn-outline btn-secondary hover:shadow-md transition-all duration-200 transform hover:scale-105"
@click="applyExampleFunction('sin(t)^3')"
>
立方正弦
</button>
<button
class="btn btn-sm btn-outline btn-secondary hover:shadow-md transition-all duration-200 transform hover:scale-105"
@click="
applyExampleFunction(
'((x)^(2/3)+0.9*sqrt(3.3-(x)^2)*sin(10*PI*(x)))*0.75',
)
"
>
心形函数
</button>
<button
class="btn btn-sm btn-outline btn-secondary hover:shadow-md transition-all duration-200 transform hover:scale-105"
@click="applyExampleFunction('sin(t) + 0.3*sin(3*t)')"
>
谐波叠加
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 右侧控制面板 (xl屏幕时占1列) -->
<div class="space-y-6">
<!-- 自动应用开关 -->
<div
class="card bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg border border-slate-200/50 dark:border-slate-700/50"
>
<div class="card-body p-4 gap-5">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<Settings class="w-5 h-5 text-blue-600" />
<span class="font-semibold text-slate-800 dark:text-slate-200"
>自动应用设置</span
>
</div>
<input
type="checkbox"
class="toggle toggle-primary scale-125"
v-model="state.autoApply"
id="auto-apply-toggle"
/>
</div>
<!-- 应用按钮 -->
<div class="text-center">
<button
class="btn btn-primary btn-lg w-full shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:scale-105"
:disabled="state.isApplying"
@click="applyWaveSettings"
>
<span v-if="state.isApplying" class="flex items-center gap-3">
<span class="loading loading-spinner loading-md"></span>
正在应用设置...
</span>
<span v-else class="flex items-center gap-3">
<Zap class="w-5 h-5" />
应用输出波形
</span>
</button>
</div>
</div>
</div>
<!-- 波形选择 -->
<div
class="card bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg border border-slate-200/50 dark:border-slate-700/50"
>
<div class="card-body p-4">
<h3
class="font-bold text-lg text-slate-800 dark:text-slate-200 mb-4"
>
波形类型
</h3>
<div class="grid grid-cols-3 xl:grid-cols-2 gap-2">
<div
v-for="(wave, index) in waveforms"
:key="`wave-${index}`"
:class="[
'btn transition-all duration-300 transform hover:scale-105',
state.waveformIndex === index
? 'btn-primary shadow-lg shadow-blue-500/25'
: 'btn-outline btn-primary hover:shadow-md',
]"
@click="selectWaveform(index)"
>
{{ wave.name }}
</div>
</div>
</div>
</div>
<!-- 参数控制 -->
<div
class="card bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg border border-slate-200/50 dark:border-slate-700/50"
>
<div class="card-body p-4">
<div class="card-title flex flex-row items-center justify-between">
<h3 class="font-bold text-lg text-slate-800 dark:text-slate-200">
信号参数
</h3>
<button
@click="resetConfiguration"
class="w-8 h-8 bg-transparent text-red-600 text-sm border border-red-200 rounded-md py-2 px-2.5 transition duration-300 ease ring ring-transparent hover:ring-red-600/10 focus:ring-red-600/10 hover:border-red-600 shadow-sm focus:shadow flex items-center justify-center"
type="button"
title="重置配置"
>
<RefreshCcw />
</button>
</div>
<!-- 时基控制 -->
<div>
<label
class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2"
>时基</label
>
<div class="flex items-center gap-2">
<button
class="btn btn-sm btn-circle btn-outline btn-primary hover:shadow-md transition-all duration-200"
@click="decreaseTimebase"
>
<Minus class="w-4 h-4" />
</button>
<input
v-model="state.timebaseInput"
@blur="applyTimebaseInput"
@keyup.enter="applyTimebaseInput"
class="input input-bordered flex-1 text-center transition-all duration-200 focus:shadow-md focus:scale-105"
type="text"
/>
<button
class="btn btn-sm btn-circle btn-outline btn-primary hover:shadow-md transition-all duration-200"
@click="increaseTimebase"
>
<Plus class="w-4 h-4" />
</button>
</div>
</div>
<!-- 频率控制 -->
<div>
<label
class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2"
>频率</label
>
<div class="flex items-center gap-2">
<button
class="btn btn-sm btn-circle btn-outline btn-primary hover:shadow-md transition-all duration-200"
@click="decreaseFrequency"
>
<Minus class="w-4 h-4" />
</button>
<input
v-model="state.frequencyInput"
@blur="applyFrequencyInput"
@keyup.enter="applyFrequencyInput"
class="input input-bordered flex-1 text-center transition-all duration-200 focus:shadow-md focus:scale-105"
type="text"
/>
<button
class="btn btn-sm btn-circle btn-outline btn-primary hover:shadow-md transition-all duration-200"
@click="increaseFrequency"
>
<Plus class="w-4 h-4" />
</button>
</div>
</div>
<!-- 相位控制 -->
<div>
<label
class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2"
>相位</label
>
<div class="flex items-center gap-2">
<button
class="btn btn-sm btn-circle btn-outline btn-primary hover:shadow-md transition-all duration-200"
@click="decreasePhase"
>
<Minus class="w-4 h-4" />
</button>
<input
v-model="state.phaseInput"
@blur="applyPhaseInput"
@keyup.enter="applyPhaseInput"
class="input input-bordered flex-1 text-center transition-all duration-200 focus:shadow-md focus:scale-105"
type="text"
/>
<button
class="btn btn-sm btn-circle btn-outline btn-primary hover:shadow-md transition-all duration-200"
@click="increasePhase"
>
<Plus class="w-4 h-4" />
</button>
</div>
</div>
</div>
</div>
<!-- 小屏幕时自定义波形区域移到最后 -->
<div class="xl:hidden">
<div
class="card bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg border border-slate-200/50 dark:border-slate-700/50"
>
<div class="card-body p-6">
<h3
class="font-bold text-xl text-slate-800 dark:text-slate-200 mb-4 flex items-center gap-3"
>
<div
class="p-2 rounded-lg bg-gradient-to-r from-purple-400 to-purple-600"
>
<CodeIcon class="w-5 h-5 text-white" />
</div>
自定义波形函数
</h3>
<div class="space-y-4">
<div
class="flex flex-col sm:flex-row items-start sm:items-center gap-3"
>
<label
class="text-sm font-medium text-slate-700 dark:text-slate-300 min-w-fit"
>函数表达式:</label
>
<input
v-model="state.customExpr"
class="input input-bordered flex-1 transition-all duration-200 focus:shadow-md focus:scale-[1.02]"
placeholder="例如: sin(t) 或 x^(2/3)+0.9*sqrt(3.3-x^2)*sin(a*PI*x) [a=7.8]"
@keyup.enter="applyCustomWaveform"
/>
<button
class="btn btn-primary font-bold hover:shadow-lg transition-all duration-300 transform hover:scale-105 w-full sm:w-auto"
@click="applyCustomWaveform"
>
<PlayIcon class="w-4 h-4 mr-2" />
应用
</button>
</div>
<div>
<div
class="text-sm font-medium text-slate-700 dark:text-slate-300 mb-2"
>
示例函数:
</div>
<div class="grid grid-cols-2 sm:flex sm:flex-wrap gap-2">
<button
class="btn btn-sm btn-outline btn-secondary hover:shadow-md transition-all duration-200 transform hover:scale-105"
@click="applyExampleFunction('sin(t)')"
>
正弦波
</button>
<button
class="btn btn-sm btn-outline btn-secondary hover:shadow-md transition-all duration-200 transform hover:scale-105"
@click="applyExampleFunction('sin(t)^3')"
>
立方正弦
</button>
<button
class="btn btn-sm btn-outline btn-secondary hover:shadow-md transition-all duration-200 transform hover:scale-105"
@click="
applyExampleFunction(
'((x)^(2/3)+0.9*sqrt(3.3-(x)^2)*sin(10*PI*(x)))*0.75',
)
"
>
心形函数
</button>
<button
class="btn btn-sm btn-outline btn-secondary hover:shadow-md transition-all duration-200 transform hover:scale-105"
@click="applyExampleFunction('sin(t) + 0.3*sin(3*t)')"
>
谐波叠加
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
import { useEquipments } from "@/stores/equipments";
import { useDialogStore } from "@/stores/dialog";
import { toInteger } from "lodash";
import { AuthManager } from "@/utils/AuthManager";
import { DDSClient } from "@/APIClient";
import { compile, type EvalFunction } from "mathjs";
import {
Settings,
Signature,
Plus,
Minus,
Zap,
Play as PlayIcon,
Code as CodeIcon,
RefreshCcw,
} from "lucide-vue-next";
// DDS
function resetConfiguration() {
state.value.frequency = 1000;
state.value.phase = 0;
state.value.timebase = 1;
state.value.waveformIndex = 0;
state.value.customExpr = "";
state.value.frequencyInput = "1.00 kHz";
state.value.phaseInput = "0";
state.value.timebaseInput = "1.00 s/div";
//
customWaveformFunction.value = null;
//
if (state.value.autoApply) applyWaveSettings();
}
//
const dds = AuthManager.createClient(DDSClient);
const eqps = useEquipments();
const dialog = useDialogStore();
// SVG
const waveformContainer = ref<HTMLElement | null>(null);
const svgWidth = ref(400);
const svgHeight = ref(300);
function updateSvgWidth() {
if (waveformContainer.value) {
svgWidth.value = waveformContainer.value.offsetWidth || 400;
}
}
onMounted(() => {
updateSvgWidth();
window.addEventListener("resize", updateSvgWidth);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", updateSvgWidth);
});
const state = ref({
frequency: 1000,
phase: 0,
timebase: 1,
waveformIndex: 0,
customExpr: "",
isApplying: false,
frequencyInput: "1.00 kHz",
phaseInput: "0",
timebaseInput: "1.00 s/div",
autoApply: false,
});
const waveforms = [
{
name: "正弦波",
type: "sine",
fn: (x: number, width: number, height: number, phaseRad: number) =>
(height / 2) * Math.sin(2 * Math.PI * (x / width) * 2 + phaseRad),
},
{
name: "方波",
type: "square",
fn: (x: number, width: number, height: number, phaseRad: number) => {
const normX = (x / width + phaseRad / (2 * Math.PI)) % 1;
return normX < 0.5 ? height / 4 : -height / 4;
},
},
{
name: "三角波",
type: "triangle",
fn: (x: number, width: number, height: number, phaseRad: number) => {
const normX = (x / width + phaseRad / (2 * Math.PI)) % 1;
return height / 2 - height * Math.abs(2 * normX - 1);
},
},
{
name: "锯齿波",
type: "sawtooth",
fn: (x: number, width: number, height: number, phaseRad: number) => {
const normX = (x / width + phaseRad / (2 * Math.PI)) % 1;
return height / 2 - (height / 2) * (2 * normX);
},
},
{
name: "自定义",
type: "custom",
fn: (x: number, width: number, height: number, phaseRad: number) => {
if (customWaveformFunction.value) {
try {
const t = 2 * Math.PI * (x / width) * 2 + phaseRad;
// x[-1,1]便使
const xn = (x / width) * 2 - 1;
const scope = { t, x, xn, width, height, phaseRad, PI: Math.PI };
const y = customWaveformFunction.value.evaluate(scope);
if (typeof y === "number" && isFinite(y)) {
return y * (height / 2);
}
return 0;
} catch {
return 0;
}
}
return 0;
},
},
];
// mathjs EvalFunction
const customWaveformFunction = ref<EvalFunction | null>(null);
function formatFrequency(freq: number): string {
if (freq >= 1000000) {
return `${(freq / 1000000).toFixed(2)} MHz`;
} else if (freq >= 1000) {
return `${(freq / 1000).toFixed(2)} kHz`;
} else {
return `${freq.toFixed(2)} Hz`;
}
}
function parseFrequency(str: string): number {
let value = parseFloat(str);
if (str.includes("MHz")) value *= 1e6;
else if (str.includes("kHz")) value *= 1e3;
else if (str.includes("Hz")) value *= 1;
return isNaN(value) ? 1000 : value;
}
function formatTimebase(tb: number): string {
if (tb < 0.001) {
return `${(tb * 1e6).toFixed(0)} μs/div`;
} else if (tb < 1) {
return `${(tb * 1000).toFixed(0)} ms/div`;
} else {
return `${tb.toFixed(2)} s/div`;
}
}
function parseTimebase(str: string): number {
let value = parseFloat(str);
if (str.includes("μs")) value /= 1e6;
else if (str.includes("ms")) value /= 1e3;
else if (str.includes("s")) value /= 1;
return isNaN(value) ? 1 : value;
}
const displayTimebase = computed(() => formatTimebase(state.value.timebase));
const displayFrequency = computed(() => formatFrequency(state.value.frequency));
// SVG
const currentWaveformPath = computed(() => {
const width = svgWidth.value;
const height = svgHeight.value - 60;
const xOffset = 0;
const yOffset = 40;
const currentWaveform = waveforms[state.value.waveformIndex];
const phaseRadians = (state.value.phase * Math.PI) / 180;
const freqLog = Math.log10(state.value.frequency) - 2;
const frequencyFactor = Math.max(0.1, Math.min(10, freqLog));
const timebaseFactor = 1 / state.value.timebase;
const scaleFactor = timebaseFactor * frequencyFactor;
let path = `M${xOffset},${yOffset + height / 2}`;
const waveFunction = currentWaveform.fn;
for (let x = 0; x <= width; x++) {
const scaledX = x * scaleFactor;
const y = waveFunction(scaledX, width, height, phaseRadians);
path += ` L${x + xOffset},${yOffset + height / 2 - y}`;
}
return path;
});
// numberkey
type NumberStateKey = "frequency" | "phase" | "timebase";
//
function adjustValue(
key: NumberStateKey,
delta: number,
steps: number[],
min: number,
max: number,
) {
let v = state.value[key];
if (typeof v !== "number") return;
let step = steps.find((s) => Math.abs(v) < s * 10) || steps[steps.length - 1];
v += delta * step;
v = Math.max(min, Math.min(max, v));
state.value[key] = parseFloat(v.toFixed(2));
if (key === "frequency") {
state.value.frequencyInput = formatFrequency(state.value.frequency);
}
if (key === "phase") {
state.value.phaseInput = state.value.phase.toString();
}
}
function increaseTimebase() {
adjustValue("timebase", 1, [0.001, 0.01, 0.1, 0.5], 0.001, 5);
state.value.timebaseInput = formatTimebase(state.value.timebase);
if (state.value.autoApply) applyWaveSettings();
}
function decreaseTimebase() {
adjustValue("timebase", -1, [0.001, 0.01, 0.1, 0.5], 0.001, 5);
state.value.timebaseInput = formatTimebase(state.value.timebase);
if (state.value.autoApply) applyWaveSettings();
}
function applyTimebaseInput() {
const value = parseTimebase(state.value.timebaseInput);
state.value.timebase = Math.min(Math.max(value, 0.001), 5);
state.value.timebaseInput = formatTimebase(state.value.timebase);
if (state.value.autoApply) applyWaveSettings();
}
function selectWaveform(index: number) {
state.value.waveformIndex = index;
if (state.value.autoApply) applyWaveSettings();
}
function increaseFrequency() {
adjustValue("frequency", 1, [0.1, 1, 10, 100, 1000, 10000], 0.1, 10000000);
if (state.value.autoApply) applyWaveSettings();
}
function decreaseFrequency() {
adjustValue("frequency", -1, [0.1, 1, 10, 100, 1000, 10000], 0.1, 10000000);
if (state.value.autoApply) applyWaveSettings();
}
function applyFrequencyInput() {
const value = parseFrequency(state.value.frequencyInput);
state.value.frequency = Math.min(Math.max(value, 0.1), 10000000);
state.value.frequencyInput = formatFrequency(state.value.frequency);
if (state.value.autoApply) applyWaveSettings();
}
function increasePhase() {
adjustValue("phase", 15, [1], 0, 359.99);
if (state.value.phase >= 360) state.value.phase -= 360;
state.value.phaseInput = state.value.phase.toString();
if (state.value.autoApply) applyWaveSettings();
}
function decreasePhase() {
adjustValue("phase", -15, [1], 0, 359.99);
if (state.value.phase < 0) state.value.phase += 360;
state.value.phaseInput = state.value.phase.toString();
if (state.value.autoApply) applyWaveSettings();
}
function applyPhaseInput() {
let value = parseFloat(state.value.phaseInput);
if (!isNaN(value)) {
while (value >= 360) value -= 360;
while (value < 0) value += 360;
state.value.phase = value;
state.value.phaseInput = state.value.phase.toString();
if (state.value.autoApply) applyWaveSettings();
} else {
state.value.phaseInput = state.value.phase.toString();
}
}
//
function applyCustomWaveform() {
if (!state.value.customExpr) {
customWaveformFunction.value = null;
return;
}
customWaveformFunction.value = null;
try {
const expr = state.value.customExpr;
const compiled = compile(expr);
customWaveformFunction.value = compiled;
state.value.waveformIndex = waveforms.findIndex((w) => w.type === "custom");
if (state.value.autoApply) applyWaveSettings();
} catch (e) {
dialog.error("表达式无效");
customWaveformFunction.value = null;
}
}
function applyExampleFunction(expr: string) {
state.value.customExpr = expr;
applyCustomWaveform();
}
//
async function applyWaveSettings() {
try {
state.value.isApplying = true;
const idx = state.value.waveformIndex;
const freq = state.value.frequency;
const ph = state.value.phase;
let ok = true;
const ret1 = await dds.setWaveNum(eqps.boardAddr, eqps.boardPort, 0, idx);
if (!ret1) ok = false;
const ret2 = await dds.setFreq(
eqps.boardAddr,
eqps.boardPort,
0,
idx,
Math.round((freq * Math.pow(2, 32 - 20)) / 10),
);
if (!ret2) ok = false;
const ret3 = await dds.setPhase(
eqps.boardAddr,
eqps.boardPort,
0,
idx,
Math.round((ph * 4096) / 360),
);
if (!ret3) ok = false;
if (!ok) dialog.error("应用失败");
} catch (e) {
dialog.error("应用失败");
console.error(e);
} finally {
state.value.isApplying = false;
}
}
</script>
<style scoped lang="postcss">
.dds-controller {
animation: fadeIn 0.6s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.card:hover {
transform: translateY(-2px);
}
.btn {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.input:focus {
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* 自定义动画 */
@keyframes pulse-gentle {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.8;
}
}
.animate-pulse {
animation: pulse-gentle 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* 渐变文字效果 */
.bg-clip-text {
-webkit-background-clip: text;
background-clip: text;
}
/* 毛玻璃效果增强 */
.backdrop-blur-sm {
backdrop-filter: blur(8px);
}
</style>