频谱: iframe-page-comps/IframeSpectrogram.vue
This commit is contained in:
@ -54,14 +54,6 @@
|
|||||||
wfAdjStep: 1000,
|
wfAdjStep: 1000,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 模拟频谱数据
|
|
||||||
const generateFakeData = (len) => {
|
|
||||||
const data = [];
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
data.push(-80 - Math.random() * 20);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
const id = 'waterfall';
|
const id = 'waterfall';
|
||||||
|
|
||||||
// 定义 label_manager
|
// 定义 label_manager
|
||||||
@ -85,62 +77,112 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化频谱图组件
|
// 全局变量,用于存储 waterfallwidget 实例
|
||||||
this.waterWidget = new waterfallwidget(id, {
|
window.waterWidgetInstance = null;
|
||||||
wfOption: wfOption,
|
|
||||||
min_freq: payload.minFreq, //全局最小频率
|
// 监听来自父窗口的消息
|
||||||
max_freq: payload.maxFreq, //全局最大频率
|
window.addEventListener('message', (event) => {
|
||||||
min_band: 500, //最小选择带宽
|
// 强烈建议在此处添加来源验证 (event.origin) 以提高安全性
|
||||||
max_band: 100000, //最大选择带宽
|
// 例如: if (event.origin !== 'YOUR_VUE_APP_ORIGIN') return;
|
||||||
min_db: wfOption.wfMindB, //全局最小增益
|
|
||||||
max_db: wfOption.wfMaxdB, //全局最大增益
|
if (!event.data) return; // 如果没有数据,则忽略
|
||||||
showPeakMarker: wfOption.showPeakMarker, //是否显示极值标注
|
|
||||||
showSpectrogramAvg: wfOption.wfShowAvg, //是否显示平均保持
|
// 处理初始化消息
|
||||||
showSpectrogramMin: wfOption.wfShowMin, //是否显示最小保持
|
if (event.data.type === 'INIT_WIDGET') {
|
||||||
showSpectrogramMax: wfOption.wfShowMax, //是否显示最大保持
|
console.log('[🌐] HTML (频谱图): 收到初始化指令');
|
||||||
spec_per: payload.waveSplite, //波形图占比
|
if (!window.waterWidgetInstance) {
|
||||||
menuEnableHandle: this.getAnalysisRun,
|
// 合并 Vue 传递的配置(如果存在)
|
||||||
menuDisableText: '---',
|
const mergedPayload = { ...payload, ...(event.data.config?.payload || {}) };
|
||||||
menuDoppler: payload.doppler,
|
const mergedWfOption = { ...wfOption, ...(event.data.config?.wfOption || {}) };
|
||||||
allowRunMode: payload.rtmode, //实时选择模式
|
|
||||||
//allowCycleControl: payload.allowCycleControl,
|
// 创建 waterfallwidget 实例
|
||||||
info_Position: wfOption.wfInfoPos,
|
window.waterWidgetInstance = new waterfallwidget(id, {
|
||||||
showCurLine: wfOption.wfCurLine,
|
wfOption: mergedWfOption,
|
||||||
adj_maxbw: payload.adjMaxbw,
|
min_freq: mergedPayload.minFreq,
|
||||||
adj_show: payload.adjShow,
|
max_freq: mergedPayload.maxFreq,
|
||||||
fucFFTBuff: payload.enableWfBuff ? this.fucFFTBuff.bind(this) : null,
|
min_band: 500,
|
||||||
unitBuffId: payload.unitId,
|
max_band: 100000,
|
||||||
wave_color: wfOption.specColor,
|
min_db: mergedWfOption.wfMindB,
|
||||||
wave_color_avg: wfOption.specAvgColor,
|
max_db: mergedWfOption.wfMaxdB,
|
||||||
wave_color_max: wfOption.specMaxColor,
|
showPeakMarker: mergedWfOption.showPeakMarker,
|
||||||
wave_color_min: wfOption.specMinColor,
|
showSpectrogramAvg: mergedWfOption.wfShowAvg,
|
||||||
waveAfterglow_color: wfOption.afterGlowColor,
|
showSpectrogramMin: mergedWfOption.wfShowMin,
|
||||||
adj_step: wfOption.wfAdjStepType == 1 ? 0 : wfOption.wfAdjStep,
|
showSpectrogramMax: mergedWfOption.wfShowMax,
|
||||||
label_manager: pl.label_manager,
|
spec_per: mergedPayload.waveSplite,
|
||||||
coorFreqType: payload.coorFreqType,
|
menuEnableHandle: null, // 在 iframe 中通常不需要此句柄
|
||||||
showFreqLable: payload.showFreqLable, //是否显示高亮频点
|
menuDisableText: '---',
|
||||||
wfFreqPointLable: payload.wfFreqPointLable, //是否显示频点标注
|
menuDoppler: mergedPayload.doppler,
|
||||||
wfFreqPointLableList: payload.wfFreqPointLableList, //频点标注数据
|
allowRunMode: mergedPayload.rtmode,
|
||||||
showWhitelist: payload.showWhitelist, //黑名单频谱阴影显示
|
info_Position: mergedWfOption.wfInfoPos,
|
||||||
|
showCurLine: mergedWfOption.wfCurLine,
|
||||||
|
adj_maxbw: mergedPayload.adjMaxbw,
|
||||||
|
adj_show: mergedPayload.adjShow,
|
||||||
|
fucFFTBuff: mergedPayload.enableWfBuff ? this.fucFFTBuff.bind(this) : null, // 如果需要,保留 FFT 缓冲功能
|
||||||
|
unitBuffId: mergedPayload.unitId,
|
||||||
|
wave_color: mergedWfOption.specColor,
|
||||||
|
wave_color_avg: mergedWfOption.specAvgColor,
|
||||||
|
wave_color_max: mergedWfOption.specMaxColor,
|
||||||
|
wave_color_min: mergedWfOption.specMinColor,
|
||||||
|
waveAfterglow_color: mergedWfOption.afterGlowColor,
|
||||||
|
adj_step: mergedWfOption.wfAdjStepType == 1 ? 0 : mergedWfOption.wfAdjStep,
|
||||||
|
label_manager: pl.label_manager, // 保留 label_manager
|
||||||
|
coorFreqType: mergedPayload.coorFreqType,
|
||||||
|
showFreqLable: mergedPayload.showFreqLable,
|
||||||
|
wfFreqPointLable: mergedPayload.wfFreqPointLable,
|
||||||
|
wfFreqPointLableList: mergedPayload.wfFreqPointLableList,
|
||||||
|
showWhitelist: mergedPayload.showWhitelist,
|
||||||
|
});
|
||||||
|
console.log('[🌐] HTML (频谱图): waterfallwidget 已实例化');
|
||||||
|
|
||||||
|
// 监听用户框选范围变更事件 (移到初始化之后)
|
||||||
|
window.waterWidgetInstance.on('zoom-change', (e) => {
|
||||||
|
console.log('频率范围变更:', e.start, e.end);
|
||||||
|
// 可选:将此事件通知回 Vue 组件
|
||||||
|
// if (window.parent !== window) {
|
||||||
|
// window.parent.postMessage({ type: 'ZOOM_CHANGE', payload: { start: e.start, end: e.end } }, '*');
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('[🌐] HTML (频谱图): waterfallwidget 已存在,无需重复实例化');
|
||||||
|
}
|
||||||
|
// 可选:通知父窗口已准备好接收数据
|
||||||
|
// if (window.parent !== window) {
|
||||||
|
// window.parent.postMessage({ type: 'WIDGET_READY' }, '*'); // 使用更具体的 targetOrigin
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
// 处理数据馈送消息
|
||||||
|
else if (event.data.type === 'FEED_DATA') {
|
||||||
|
if (window.waterWidgetInstance) {
|
||||||
|
console.log('[🌐] HTML (频谱图): 收到来自父窗口的数据:', event.data.payload);
|
||||||
|
if (Array.isArray(event.data.payload)) {
|
||||||
|
// waterfallwidget.addData 接受可选的第二个参数 time
|
||||||
|
// 如果 Vue 组件传递了时间戳,可以在这里使用
|
||||||
|
const time = event.data.time || new Date().toLocaleTimeString(); // 如果没有提供时间,使用当前时间
|
||||||
|
window.waterWidgetInstance.addData(event.data.payload, time);
|
||||||
|
} else {
|
||||||
|
console.error('[🌐] HTML (频谱图): 收到的数据格式无效:', event.data.payload);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('[🌐] HTML (频谱图): Widget 尚未初始化,无法处理 FEED_DATA');
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// 监听用户框选范围变更事件
|
|
||||||
this.waterWidget.on('zoom-change', (e) => {
|
|
||||||
console.log('频率范围变更:', e.start, e.end);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 生成模拟数据并更新频谱图
|
|
||||||
const updateData = () => {
|
|
||||||
const data = generateFakeData(100 /* 0 */);
|
|
||||||
console.debug(`data :>> `, data);
|
|
||||||
const time = new Date().toLocaleTimeString();
|
|
||||||
this.waterWidget.addData(data, time);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 首次更新
|
|
||||||
updateData();
|
|
||||||
|
|
||||||
// 定期更新数据
|
|
||||||
setInterval(updateData, 1000);
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden; /* 防止滚动条出现 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保容器占满整个 iframe */
|
||||||
|
body > div {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</html>
|
</html>
|
||||||
|
72
src/components/iframe-page-comps/IframeSpectrogram.vue
Normal file
72
src/components/iframe-page-comps/IframeSpectrogram.vue
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// 不再需要 props 来接收数据
|
||||||
|
|
||||||
|
// 获取 iframe 的 DOM 引用
|
||||||
|
const iframeRef = ref<HTMLIFrameElement | null>(null);
|
||||||
|
|
||||||
|
// iframe 的 src URL,指向 Spectrogram.html
|
||||||
|
const src = computed(() => import.meta.env.BASE_URL + 'html-page/Spectrogram.html');
|
||||||
|
|
||||||
|
// 标记 iframe 是否已加载完成并已发送初始化指令
|
||||||
|
const isIframeInitialized = ref(false);
|
||||||
|
|
||||||
|
// iframe 加载完成后的回调
|
||||||
|
function onIframeLoad() {
|
||||||
|
console.log('[🧩] 组件 (频谱图): iframe 已加载');
|
||||||
|
// 发送初始化指令给 iframe
|
||||||
|
if (iframeRef.value?.contentWindow) {
|
||||||
|
console.log('[🧩] 组件 (频谱图): 向 iframe 发送 INIT_WIDGET 指令');
|
||||||
|
// targetOrigin 设置为 '*' 为了简单,生产环境应指定确切来源
|
||||||
|
// 可选:如果需要传递配置,可以在 INIT_WIDGET 消息中包含 config
|
||||||
|
iframeRef.value.contentWindow.postMessage({ type: 'INIT_WIDGET' /*, config: props.config */ }, '*');
|
||||||
|
isIframeInitialized.value = true; // 标记已发送初始化指令
|
||||||
|
|
||||||
|
// 发送初始数据(如果已有)
|
||||||
|
// 稍微延迟发送,给 iframe 一点时间处理 INIT_WIDGET
|
||||||
|
setTimeout(() => {
|
||||||
|
// 初始数据将通过暴露的方法发送,这里不再处理
|
||||||
|
console.log('[🧩] 组件 (频谱图): iframe 初始化完成,等待通过 ref 调用发送数据。');
|
||||||
|
}, 0);
|
||||||
|
} else {
|
||||||
|
console.error('[🧩] 组件 (频谱图): 无法访问 iframe 的 contentWindow');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将数据发送到 iframe 的函数
|
||||||
|
function sendDataToIframe(payload: Array<number>, time: string) {
|
||||||
|
// 添加 time 参数
|
||||||
|
// 确保 iframe 存在、其 contentWindow 可访问且已发送初始化指令
|
||||||
|
if (iframeRef.value?.contentWindow && isIframeInitialized.value) {
|
||||||
|
console.log('[🧩] 组件 (频谱图): 向 iframe 发送数据:', payload, '时间:', time);
|
||||||
|
// 使用 toRaw 获取原始数据对象
|
||||||
|
// 将时间和数据一起发送
|
||||||
|
iframeRef.value.contentWindow.postMessage({ type: 'FEED_DATA', payload: toRaw(payload), time: time }, '*');
|
||||||
|
} else {
|
||||||
|
console.warn('[🧩] 组件 (频谱图): iframe 未初始化或数据无效,无法发送 FEED_DATA 消息。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不再需要监听 props
|
||||||
|
|
||||||
|
// 可选:监听来自 iframe 的 WIDGET_READY 消息以实现更精确的控制 (与星座图类似)
|
||||||
|
// onMounted(() => { ... });
|
||||||
|
// onUnmounted(() => { ... });
|
||||||
|
// function handleIframeMessage(event: MessageEvent) { ... }
|
||||||
|
|
||||||
|
// 暴露 sendDataToIframe 方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
sendData: sendDataToIframe,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<iframe ref="iframeRef" :src="src" frameborder="0" allowfullscreen @load="onIframeLoad"></iframe>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
iframe {
|
||||||
|
border: none; /* 移除边框 */
|
||||||
|
width: 100%; /* 默认占满容器 */
|
||||||
|
height: 100%; /* 默认占满容器 */
|
||||||
|
}
|
||||||
|
</style>
|
60
src/pages/Page/iframe-page/Spectrogram.page.vue
Normal file
60
src/pages/Page/iframe-page/Spectrogram.page.vue
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
definePage({ meta: { title: '频谱图+瀑布图' } });
|
||||||
|
|
||||||
|
const iframeSpectrogramRef = useTemplateRef('iframeSpectrogramRef');
|
||||||
|
|
||||||
|
// 模拟生成频谱数据的函数
|
||||||
|
function generateFakeSpectrogramData(len = 200, baseLevel = -90, noiseRange = 30) {
|
||||||
|
const data = [];
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
// 模拟一些峰值
|
||||||
|
let peak = 0;
|
||||||
|
if (i > len * 0.2 && i < len * 0.3) {
|
||||||
|
peak = 30 * Math.sin(((i - len * 0.2) / (len * 0.1)) * Math.PI); // 第一个峰
|
||||||
|
} else if (i > len * 0.6 && i < len * 0.75) {
|
||||||
|
peak = 20 * (1 - Math.abs(i - len * 0.675) / (len * 0.075)); // 第二个峰
|
||||||
|
}
|
||||||
|
data.push(baseLevel + Math.random() * noiseRange + peak);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定时更新数据以模拟实时效果
|
||||||
|
let intervalId: null | number = null;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 模拟: 每x秒更新一次数据并通过 ref 发送
|
||||||
|
intervalId = globalThis.setInterval(() => {
|
||||||
|
const newData = generateFakeSpectrogramData();
|
||||||
|
const newTime = new Date().toLocaleTimeString();
|
||||||
|
|
||||||
|
if (iframeSpectrogramRef.value) {
|
||||||
|
console.log('[📄] 页面:通过 ref 发送更新数据'.padEnd(120, '-'));
|
||||||
|
iframeSpectrogramRef.value.sendData(newData, newTime); // 传递动态获取的时间
|
||||||
|
} else {
|
||||||
|
console.warn('[📄] 页面:无法获取 IframeSpectrogram 组件的引用来发送更新数据');
|
||||||
|
}
|
||||||
|
console.log('[📄] 页面:更新频谱数据带时间');
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 清除定时器
|
||||||
|
if (intervalId) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>频谱图+瀑布图 (通过 iframe)</p>
|
||||||
|
<div style="width: 100%; height: 400px; border: 1px solid #ccc; margin-top: 10px">
|
||||||
|
<IframeSpectrogram ref="iframeSpectrogramRef" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 可以添加一些页面级别的样式 */
|
||||||
|
</style>
|
1
typed-router.d.ts
vendored
1
typed-router.d.ts
vendored
@ -29,6 +29,7 @@ declare module 'vue-router/auto-routes' {
|
|||||||
'PageFonts': RouteRecordInfo<'PageFonts', '/Page/fonts', Record<never, never>, Record<never, never>>,
|
'PageFonts': RouteRecordInfo<'PageFonts', '/Page/fonts', Record<never, never>, Record<never, never>>,
|
||||||
'PageIcons': RouteRecordInfo<'PageIcons', '/Page/Icons', Record<never, never>, Record<never, never>>,
|
'PageIcons': RouteRecordInfo<'PageIcons', '/Page/Icons', Record<never, never>, Record<never, never>>,
|
||||||
'PageIframePageIframeConstellationDiagram': RouteRecordInfo<'PageIframePageIframeConstellationDiagram', '/Page/iframe-page/IframeConstellationDiagram', Record<never, never>, Record<never, never>>,
|
'PageIframePageIframeConstellationDiagram': RouteRecordInfo<'PageIframePageIframeConstellationDiagram', '/Page/iframe-page/IframeConstellationDiagram', Record<never, never>, Record<never, never>>,
|
||||||
|
'PageIframePageSpectrogram': RouteRecordInfo<'PageIframePageSpectrogram', '/Page/iframe-page/Spectrogram', Record<never, never>, Record<never, never>>,
|
||||||
'PageJSPage': RouteRecordInfo<'PageJSPage', '/Page/JSPage', Record<never, never>, Record<never, never>>,
|
'PageJSPage': RouteRecordInfo<'PageJSPage', '/Page/JSPage', Record<never, never>, Record<never, never>>,
|
||||||
'PageMDPage': RouteRecordInfo<'PageMDPage', '/Page/MDPage', Record<never, never>, Record<never, never>>,
|
'PageMDPage': RouteRecordInfo<'PageMDPage', '/Page/MDPage', Record<never, never>, Record<never, never>>,
|
||||||
'PageP5Js': RouteRecordInfo<'PageP5Js', '/Page/p5_js', Record<never, never>, Record<never, never>>,
|
'PageP5Js': RouteRecordInfo<'PageP5Js', '/Page/p5_js', Record<never, never>, Record<never, never>>,
|
||||||
|
Reference in New Issue
Block a user