255 lines
6.4 KiB
Markdown
255 lines
6.4 KiB
Markdown
# premise
|
||
|
||
这是我在展会看到的友商上位机 UI(`assets/paxini.jpg`)的风格参考,希望做一份相近气质的前端界面。前端由你负责实现与迭代。
|
||
|
||
## 约束(长期有效)
|
||
|
||
- 使用 `Svelte + TypeScript`
|
||
- 页面拆分成 `HudPanel`、`CenterStage`、`SignalChart`
|
||
- 不引入大型 UI 库
|
||
- 主色调:近黑背景 + 青蓝 / 荧光绿 / 橙红
|
||
- 主界面整体是“一整块 board”,通过渐变过渡,不做强分块
|
||
- 不做明显流光/扫光动画(可有静态纹理层次)
|
||
- 样式集中在组件内或 `theme` 文件
|
||
- 布局优先 `grid/flex`,仅在必要位置使用绝对定位
|
||
- 后续对接 Tauri,组件层不要把数据源写死
|
||
|
||
---
|
||
|
||
# 当前实现基线(已完成)
|
||
|
||
## Step1:主背景
|
||
|
||
- 已完成近黑全屏背景、弱渐变、暗角和轻噪声层
|
||
- `:root/html/body` 背景已与主界面统一,避免拖动窗口出现黑边断层
|
||
|
||
## Step2:TitleBar + ControlBar + 主布局
|
||
|
||
- 已完成窗口按钮(最小化 / 最大化切换 / 关闭)
|
||
- 已完成 ControlBar:
|
||
- 配置菜单组(打开/关闭/校准/参数)
|
||
- 连接状态卡片
|
||
- 串口下拉框
|
||
- 中英文切换
|
||
- 设备信息行(设备/采样率/通道)
|
||
- 页面主结构已固定为:`TitleBar + ControlBar + WebGL2 Area`
|
||
|
||
## Step2-1:Tauri 交互与窗口体验
|
||
|
||
- TitleBar 已支持拖动(`data-tauri-drag-region`)
|
||
- 默认窗口大小自动尝试调整到屏幕约 `3/4`
|
||
- 已连接 Tauri window commands:
|
||
|
||
```ts
|
||
invoke("win_minimize");
|
||
invoke("win_toggle_maximize");
|
||
invoke("win_close");
|
||
```
|
||
|
||
## Step3:HUD 面板与数据演示
|
||
|
||
- 左右两侧悬浮曲线面板已完成
|
||
- 有数据时入场并渲染,无数据时退场(当前 demo:5s 开/关周期)
|
||
- 折线、渐变填充、边框与深色半透明底已完成
|
||
- 底部量程条已完成
|
||
|
||
---
|
||
|
||
# 当前前端结构(请按此扩展)
|
||
|
||
## 1) `src/routes/+page.svelte`
|
||
|
||
- 页面编排与状态管理
|
||
- 维护 `signalPanels`、语言、连接状态、串口、配置菜单选中态
|
||
- 将左右面板分流后传给 `CenterStage`
|
||
- 处理 Tauri invoke(窗口控制)
|
||
|
||
## 2) `src/lib/components/HudPanel.svelte`
|
||
|
||
- 只负责顶部控制区 UI 与交互事件分发
|
||
- 通过事件向外抛出:
|
||
- `windowcontrol`
|
||
- `localechange`
|
||
- `configlink`
|
||
- `portchange`
|
||
|
||
## 3) `src/lib/components/CenterStage.svelte`
|
||
|
||
- 承载 WebGL2 主区域和左右悬浮 rail
|
||
- 接收 `leftPanels/rightPanels` 并在 rail 内渲染 `SignalChart`
|
||
- 通过 `ResizeObserver` 动态计算:
|
||
- `panel-zone` 顶部起始位置
|
||
- 左右 rail 缩放比例(低高度窗口可自适应)
|
||
- 侧边定位策略:贴边但保留 `edge inset` 安全距离
|
||
|
||
## 4) `src/lib/components/SignalChart.svelte`
|
||
|
||
- 单个曲线面板渲染组件
|
||
- 仅消费结构化数据,不关心数据来源
|
||
- 支持 `side`(left/right)与 `active`(入场/退场)状态
|
||
|
||
---
|
||
|
||
# 关键布局规则(避免回归)
|
||
|
||
- WebGL2 区域占据下半主容器
|
||
- HUD 曲线面板浮在 WebGL2 上方,但贴近左右边缘,不遮挡中间核心区域
|
||
- 左右面板必须挂在 `CenterStage` 的 rail 容器内
|
||
- 不再使用命名 slot 传曲线面板(曾导致面板落到错误容器)
|
||
- 调试用彩色边框已移除,保持正式视觉
|
||
|
||
---
|
||
|
||
# 数据接入约定(统一结构)
|
||
|
||
```ts
|
||
type SignalTone = "cyan" | "lime" | "orange";
|
||
type SignalPanelSide = "left" | "right";
|
||
|
||
interface HudSignalSeries {
|
||
id: string;
|
||
tone: SignalTone;
|
||
points: number[];
|
||
}
|
||
|
||
interface HudSignalIcon {
|
||
id: string;
|
||
label: string;
|
||
tone: SignalTone;
|
||
}
|
||
|
||
interface HudSignalPanel {
|
||
id: string;
|
||
code: string;
|
||
title: string;
|
||
side: SignalPanelSide;
|
||
active: boolean;
|
||
series: HudSignalSeries[];
|
||
icons: HudSignalIcon[];
|
||
}
|
||
|
||
interface HudPacket {
|
||
ts: number;
|
||
panels: HudSignalPanel[];
|
||
}
|
||
```
|
||
|
||
统一入口:
|
||
|
||
```ts
|
||
let signalPanels: HudSignalPanel[] = [];
|
||
|
||
function applyPacket(packet: HudPacket) {
|
||
signalPanels = packet.panels;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
# Step3 数据源 Demo(可替换)
|
||
|
||
## Demo A:本地 Mock
|
||
|
||
```ts
|
||
function startMockFeed(push: (packet: HudPacket) => void): () => void {
|
||
let hasData = false;
|
||
const cycle = setInterval(() => {
|
||
hasData = !hasData;
|
||
push({
|
||
ts: Date.now(),
|
||
panels: hasData ? buildRandomPanels() : buildInactivePanels()
|
||
});
|
||
}, 5000);
|
||
|
||
const tick = setInterval(() => {
|
||
if (hasData) {
|
||
push({
|
||
ts: Date.now(),
|
||
panels: evolvePanels()
|
||
});
|
||
}
|
||
}, 1200);
|
||
|
||
return () => {
|
||
clearInterval(cycle);
|
||
clearInterval(tick);
|
||
};
|
||
}
|
||
```
|
||
|
||
## Demo B:Tauri invoke 轮询
|
||
|
||
```ts
|
||
import { invoke } from "@tauri-apps/api/core";
|
||
|
||
async function fetchHudPacket(): Promise<HudPacket> {
|
||
return await invoke<HudPacket>("hud_get_packet");
|
||
}
|
||
```
|
||
|
||
## Demo C:Tauri 事件推送
|
||
|
||
```ts
|
||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||
|
||
async function startTauriStream(push: (packet: HudPacket) => void): Promise<() => void> {
|
||
const unlisten: UnlistenFn = await listen<HudPacket>("hud_stream", (event) => {
|
||
push(event.payload);
|
||
});
|
||
return () => unlisten();
|
||
}
|
||
```
|
||
|
||
## Demo D:WebSocket
|
||
|
||
```ts
|
||
function startWebSocket(push: (packet: HudPacket) => void): () => void {
|
||
const ws = new WebSocket("ws://127.0.0.1:9001/hud");
|
||
ws.onmessage = (e) => push(JSON.parse(e.data) as HudPacket);
|
||
return () => ws.close();
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
# 其他接口建议(主控区联动)
|
||
|
||
```ts
|
||
invoke("sensor_connect", { port: "COM3" });
|
||
invoke("sensor_disconnect");
|
||
|
||
invoke("sensor_set_sample_rate", { hz: 120 });
|
||
invoke("sensor_set_channels", { channels: [1, 2, 3, 4] });
|
||
|
||
invoke("sensor_start_stream");
|
||
invoke("sensor_stop_stream");
|
||
|
||
invoke("sensor_get_status");
|
||
```
|
||
|
||
---
|
||
|
||
# step3 前端细节调整
|
||
|
||
1. 添加对串口连接失败的提醒
|
||
2. 连接成功后,连接按钮变成断开按钮点击就断开连接
|
||
|
||
# step 4 webgl2
|
||
|
||
1. 黑色背景,科技感 UI 容器
|
||
2. 一个 64x64 的压力矩阵
|
||
3. 用 InstancedMesh 渲染 4096 个小圆柱或小圆环
|
||
4. 每个实例根据 value 更新位置、缩放、颜色
|
||
5. 支持 OrbitControls 旋转缩放
|
||
6. 内置一个假数据生成器,能模拟脚印从无到有再消失
|
||
7. 数据更新用平滑插值,不要跳变
|
||
8. 左侧显示总压力、最大值、平均值
|
||
9. 支持 2D/3D 模式切换,但第一版 2D 可以只是俯视正交投影
|
||
|
||
# step4 config panel
|
||
|
||
我需要一个panel来进行参数配置,有以下配置(后面会加)
|
||
|
||
1. 点阵数量,目前默认是64*64,我希望可以自定义
|
||
2. range上下限,我希望可以自定义max和min来映射颜色
|