大屏项目的架构核心是 「配置驱动渲染」:把布局、组件、数据源都抽成 JSON Schema,让一份代码跑多套大屏。
1. 目录结构
src/
├─ app/ 应用入口、全局布局
│ ├─ App.vue / App.tsx
│ └─ providers/ ThemeProvider、SocketProvider…
├─ screens/ 一个文件夹 = 一个大屏
│ ├─ ops-monitor/ 运维监控大屏
│ │ ├─ schema.ts 该大屏的配置(布局 + 组件 + 数据源)
│ │ ├─ index.vue
│ │ └─ widgets/ 该大屏专属组件
│ ├─ leader-report/
│ └─ city-3d/
├─ widgets/ 通用大屏组件(柱图、饼图、地图、KPI…)
│ ├─ KpiCard/
│ ├─ LineChart/
│ ├─ BarChart/
│ ├─ MapHeat/
│ └─ FlyLine/
├─ renderer/ Schema → 实际组件树的渲染引擎
│ ├─ Renderer.tsx
│ └─ resolveDataSource.ts
├─ data/ 数据层
│ ├─ http.ts
│ ├─ socket.ts
│ ├─ stores/ Pinia/Zustand 模块
│ └─ services/ 按域划分的请求封装
├─ utils/
├─ themes/ 主题(深色蓝、橙红、紫银…)
├─ assets/ 图标、Lottie、纹理、视频
└─ main.ts
2. 配置驱动渲染(Schema-driven)
2.1 核心 Schema 形态
type DashboardSchema = {
id: string;
resolution: { w: number; h: number }; // 设计稿基准分辨率
theme: 'dark-blue' | 'red' | string;
background?: { type: 'image' | 'video' | 'color'; src?: string; color?: string };
refreshInterval?: number; // 全局兜底刷新
widgets: WidgetSchema[];
};
type WidgetSchema = {
id: string;
type: 'kpi' | 'line' | 'bar' | 'map-heat' | 'fly-line' | 'three-city' | string;
rect: { x: number; y: number; w: number; h: number }; // 设计稿坐标
props?: Record<string, unknown>; // 组件自身配置(颜色、轴等)
dataSource?: DataSource;
};
type DataSource =
| { kind: 'static'; data: unknown }
| { kind: 'http'; url: string; method?: string; intervalMs?: number; transform?: string }
| { kind: 'ws'; channel: string; transform?: string }
| { kind: 'sse'; url: string; transform?: string };
2.2 渲染器伪代码
function Renderer({ schema }: { schema: DashboardSchema }) {
return (
<Stage resolution={schema.resolution} theme={schema.theme} bg={schema.background}>
{schema.widgets.map(w => (
<Positioned key={w.id} rect={w.rect}>
<WidgetHost widget={w} />
</Positioned>
))}
</Stage>
);
}
function WidgetHost({ widget }) {
const data = useDataSource(widget.dataSource); // 屏蔽 http/ws/sse 差异
const Comp = widgetRegistry[widget.type];
return <Comp data={data} {...widget.props} />;
}
好处:
- 新增大屏 = 写一份 schema.ts,不写组件代码。
- 切换数据源协议(HTTP → WebSocket)只改 schema,不改组件。
- 后期可扩 GUI 编辑器,把 schema 可视化生成。
2.3 何时用 Schema、何时直接写
- 多个大屏同质化高、组件复用度高 → Schema 驱动。
- 单个大屏视觉极特殊、组件高度定制(如 3D 城市) → 直接写组件树,不要硬塞 Schema。
3. 状态管理
| 层级 | 内容 | 推荐方案 |
|---|---|---|
| 全局 | 当前主题、当前大屏 ID、用户、Token、时钟 | Pinia store / Zustand |
| 会话 | 实时推送通道、连接状态 | 单例 Manager(见 05) |
| 局部 | 组件 props 与派生数据 | 组件内 useState / ref |
| 服务 | 拉取/缓存数据 | TanStack Query(vue-query / react-query) |
大屏不要把所有数据都塞 store。指标级数据放 query 缓存即可,store 只放跨组件、跨大屏共享的状态。
4. 数据源解析层
useDataSource(ds) 是关键抽象:
function useDataSource(ds?: DataSource) {
switch (ds?.kind) {
case 'static': return ds.data;
case 'http': return useHttpPolling(ds.url, ds.intervalMs);
case 'ws': return useWsChannel(ds.channel);
case 'sse': return useSseStream(ds.url);
default: return null;
}
}
transform 字段(字符串表达式或 JSONata/jq 风格)允许 schema 里写简单数据变形,复杂逻辑仍写在组件里。
5. 主题与设计 Token
themes/
├─ dark-blue.ts
├─ red.ts
└─ index.ts
export const darkBlue = {
bg: '#020A1A',
panel: 'rgba(15, 40, 90, 0.6)',
text: '#E6F2FF',
textDim: '#7FA1C8',
primary: '#3FA8FF',
accent: '#00FFE0',
series: ['#3FA8FF', '#00FFE0', '#FFC53F', '#FF6B6B', '#A0FF6B'],
fontFamily: '"DIN Alternate", "PingFang SC", sans-serif',
};
ECharts 用 echarts.registerTheme('dark-blue', echartsThemeFromTokens(darkBlue)),所有图表统一调用 <EChart theme="dark-blue" />,避免一个个改 option。
6. 工程化要点
- 构建:Vite + TS + Vue3/React + esbuild;首屏 HMR 必须 < 1s 才能高效调试大屏布局。
- 路径别名:
@widgets、@renderer、@data、@themes让 schema 可读。 - lint:eslint + stylelint + 大屏专属规则(禁用
position: fixed、禁用100vh单位等,避免拼接屏踩坑)。 - 类型:所有 schema 必须有 TS 类型;考虑用 zod 做运行时校验(用户配错立刻报)。
- 测试:Storybook 做 widget 开发与回归;Playwright 做整屏视觉回归。
- mock:
vite-plugin-mock出一份「演示模式」数据,断网也能演讲。
7. 演示模式(Demo Mode)
大屏经常要在断网的发布会、车里、户外演讲。一定要有 Demo Mode:
if (import.meta.env.VITE_DEMO === 'true') {
// 接管所有数据源,吐预设动画数据
enableMockSocket();
enableMockHttp();
}
预设数据要带「故事感」:刻意制造高峰、回落、告警、恢复的曲线,让汇报有戏剧张力。
8. 反模式
- 把 widget 直接 import 到大屏页面:复用低,新增大屏要复制粘贴。走注册表 + Schema。
- 数据源全塞进 widget 内部:同一个 widget 换数据源要改源码;抽到 schema。
- 把主题写进组件:换主题改 N 处;走 token + ECharts theme。
- 没有 Demo Mode:现场断网就当场翻车。