DataViz Pro — 前端开发指南
一、Clean Architecture 分层总览
┌─────────────────────────────────────────────────────────────────┐
│ Frameworks & Drivers(最外层) │
│ Next.js · React · ECharts · SheetJS · jsPDF · AG Grid │
├─────────────────────────────────────────────────────────────────┤
│ Interface Adapters(适配层) │
│ Redux Toolkit Slices · Zustand Stores · Presenters · Gateways │
├─────────────────────────────────────────────────────────────────┤
│ Application(用例层) │
│ Use Cases · Input/Output Ports(接口定义) │
├─────────────────────────────────────────────────────────────────┤
│ Domain(领域层,最内层) │
│ Entities · Value Objects · Domain Services · Business Rules │
└─────────────────────────────────────────────────────────────────┘
依赖方向:外层 → 内层(绝对不可反向)
二、目录结构
src/
├── domain/ # 领域层(零依赖,纯 TypeScript)
│ ├── entities/
│ │ ├── DataSet.ts # 数据集实体
│ │ ├── Column.ts # 列实体(含类型推断规则)
│ │ ├── ChartInstance.ts # 图表实例实体
│ │ ├── FieldBinding.ts # 字段绑定实体
│ │ ├── StyleConfig.ts # 样式配置实体
│ │ ├── LayoutItem.ts # 布局项实体
│ │ └── Template.ts # 模板实体
│ │
│ ├── valueObjects/
│ │ ├── FieldType.ts # 字段类型(number|text|date|percentage|geo)
│ │ ├── ChartType.ts # 图表类型枚举 + 元数据
│ │ ├── Aggregation.ts # 聚合方式
│ │ ├── SortOrder.ts # 排序方向
│ │ └── ExportFormat.ts # 导出格式
│ │
│ ├── services/ # 领域服务(纯业务规则)
│ │ ├── FieldTypeInferenceService.ts # 字段类型推断规则
│ │ ├── DataStructureInferenceService.ts # 数据结构推断规则
│ │ ├── ChartRecommendationService.ts# 图表推荐规则
│ │ ├── DataTransformService.ts # 聚合/排序/TopN 规则
│ │ └── ValidationService.ts # 数据绑定合法性校验
│ │
│ └── rules/ # 业务规则常量
│ ├── chartBindingRules.ts # 每种图表需要什么字段绑定
│ └── chartCompatibility.ts # 字段类型与图表的兼容矩阵
│
├── application/ # 用例层(依赖 domain,不依赖外层)
│ ├── ports/ # 端口定义(接口)
│ │ ├── input/ # 输入端口(Use Case 接口)
│ │ │ ├── IImportDataUseCase.ts
│ │ │ ├── ICreateChartUseCase.ts
│ │ │ ├── IUpdateChartConfigUseCase.ts
│ │ │ ├── IExportUseCase.ts
│ │ │ ├── ITemplateUseCase.ts
│ │ │ └── ILayoutUseCase.ts
│ │ │
│ │ └── output/ # 输出端口(外部依赖的接口)
│ │ ├── IFileParser.ts # 文件解析能力
│ │ ├── IChartRenderer.ts # 图表渲染能力(生成 option)
│ │ ├── IExportGateway.ts # 导出能力
│ │ ├── IStorageGateway.ts # 持久化能力
│ │ └── IGeoDataProvider.ts # 地图数据能力
│ │
│ ├── usecases/ # 用例实现
│ │ ├── ImportDataUseCase.ts # 导入数据:解析→推断→入库
│ │ ├── CreateChartUseCase.ts # 创建图表:推荐→默认绑定→生成
│ │ ├── UpdateChartConfigUseCase.ts # 更新图表配置:校验→转换→更新
│ │ ├── ExportUseCase.ts # 导出:按格式调用 Gateway
│ │ ├── TemplateUseCase.ts # 模板:保存/加载/导入导出
│ │ └── LayoutUseCase.ts # 布局:增删改查
│ │
│ └── dto/ # 数据传输对象
│ ├── ImportResult.ts
│ ├── ChartSuggestion.ts
│ └── ExportOptions.ts
│
├── adapters/ # 适配层(实现端口,连接内外)
│ ├── gateways/ # 输出端口的具体实现
│ │ ├── SheetJSFileParser.ts # IFileParser → SheetJS 实现
│ │ ├── EChartsOptionBuilder.ts # IChartRenderer → ECharts option 构建
│ │ │ ├── barOptionBuilder.ts
│ │ │ ├── lineOptionBuilder.ts
│ │ │ ├── pieOptionBuilder.ts
│ │ │ ├── scatterOptionBuilder.ts
│ │ │ ├── radarOptionBuilder.ts
│ │ │ ├── heatmapOptionBuilder.ts
│ │ │ ├── mapOptionBuilder.ts
│ │ │ ├── wordcloudOptionBuilder.ts
│ │ │ ├── comboOptionBuilder.ts
│ │ │ └── index.ts
│ │ ├── exportGateway/ # IExportGateway 实现
│ │ │ ├── ImageExportGateway.ts # PNG/JPG/SVG
│ │ │ ├── PDFExportGateway.ts # jsPDF
│ │ │ ├── ExcelExportGateway.ts # SheetJS
│ │ │ ├── PPTExportGateway.ts # PptxGenJS
│ │ │ ├── HTMLExportGateway.ts # 独立 HTML
│ │ │ └── index.ts
│ │ ├── LocalStorageGateway.ts # IStorageGateway → localStorage
│ │ └── GeoJsonProvider.ts # IGeoDataProvider → 静态 GeoJSON
│ │
│ ├── state/ # 状态管理适配
│ │ ├── redux/ # Redux Toolkit(复杂全局状态)
│ │ │ ├── store.ts # configureStore
│ │ │ ├── dataSlice.ts # 数据集 CRUD
│ │ │ ├── chartSlice.ts # 图表实例 CRUD + 配置变更
│ │ │ ├── layoutSlice.ts # 布局状态
│ │ │ └── templateSlice.ts # 模板状态
│ │ │
│ │ └── zustand/ # Zustand(轻量 UI 状态)
│ │ ├── uiStore.ts # 面板开关/活跃选项卡/拖拽状态
│ │ ├── themeStore.ts # 主题/色板
│ │ └── interactionStore.ts # 悬停/选中/缩放等交互态
│ │
│ └── presenters/ # Presenter(Use Case 输出 → View Model)
│ ├── DataPreviewPresenter.ts # DataSet → 表格展示数据
│ ├── ChartPresenter.ts # ChartInstance → ECharts option
│ ├── FieldListPresenter.ts # Column[] → 左侧字段列表 VM
│ └── ExportPresenter.ts # 导出进度/结果 → UI 状态
│
├── frameworks/ # 框架层(Next.js + React 组件)
│ ├── app/ # Next.js App Router
│ │ ├── layout.tsx # 根布局
│ │ ├── page.tsx # 首页(仪表盘编辑器)
│ │ ├── providers.tsx # Redux Provider + 主题 Provider
│ │ └── globals.css
│ │
│ ├── components/ # React 组件(纯 UI)
│ │ ├── layout/
│ │ │ ├── TopToolbar.tsx
│ │ │ ├── LeftPanel.tsx
│ │ │ ├── CenterCanvas.tsx
│ │ │ ├── RightPanel.tsx
│ │ │ └── BottomStatusBar.tsx
│ │ │
│ │ ├── dataImport/
│ │ │ ├── DropZone.tsx
│ │ │ ├── DataPreviewTable.tsx
│ │ │ └── SheetSelector.tsx
│ │ │
│ │ ├── charts/
│ │ │ ├── ChartWrapper.tsx
│ │ │ ├── ChartRenderer.tsx
│ │ │ ├── KPICard.tsx
│ │ │ ├── EChartsBase.tsx
│ │ │ └── DataTable.tsx
│ │ │
│ │ ├── configPanel/
│ │ │ ├── DataBinding.tsx
│ │ │ ├── StyleConfig.tsx
│ │ │ ├── TitleConfig.tsx
│ │ │ ├── ColorConfig.tsx
│ │ │ ├── AxisConfig.tsx
│ │ │ ├── LegendConfig.tsx
│ │ │ ├── LabelConfig.tsx
│ │ │ ├── SizeConfig.tsx
│ │ │ └── InteractionConfig.tsx
│ │ │
│ │ ├── template/
│ │ │ ├── TemplateGallery.tsx
│ │ │ ├── TemplateCard.tsx
│ │ │ └── TemplateSaveDialog.tsx
│ │ │
│ │ └── export/
│ │ └── ExportDialog.tsx
│ │
│ ├── hooks/ # React Hooks(连接 Use Case 与组件)
│ │ ├── useImportData.ts # 调用 ImportDataUseCase
│ │ ├── useCreateChart.ts # 调用 CreateChartUseCase
│ │ ├── useChartConfig.ts # 调用 UpdateChartConfigUseCase
│ │ ├── useExport.ts # 调用 ExportUseCase
│ │ ├── useTemplate.ts # 调用 TemplateUseCase
│ │ └── useLayout.ts # 调用 LayoutUseCase
│ │
│ └── di/ # 依赖注入容器
│ └── container.ts # 组装所有端口实现
│
├── assets/
│ ├── geo/ # 中国省市 GeoJSON
│ └── sample/ # 示例数据集
│
├── next.config.ts
├── tsconfig.json
└── package.json
三、依赖方向图
frameworks/ adapters/ application/ domain/
(Next.js+React) (实现层) (用例层) (领域层)
components ──→ hooks ──→ usecases ──→ entities
│ │ │
│ │ valueObjects
│ │ │
│ │ services
│ │ │
│ ▼ │
│ ports/input │
│ ports/output ←────┘
│ ▲
│ │(实现)
├──→ state/redux
├──→ state/zustand
├──→ gateways
└──→ presenters
箭头 = import 方向,全部从外向内,绝不反向
ports/output 定义接口在 application 层,实现在 adapters 层(依赖反转)
四、Zustand + Redux Toolkit 混合策略
┌──────────────────────────────────────────────────────────┐
│ Redux Toolkit(大厂模式) │
│ │
│ 管理:业务核心状态,需要以下特性的数据 │
│ · 复杂的状态转换(reducer 逻辑多) │
│ · 需要 middleware(如 thunk、日志) │
│ · 多组件深层共享 + 可预测性要求高 │
│ · DevTools 调试需求 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌────────────┐ │
│ │dataSlice │ │chartSlice│ │layoutSlice│ │templateSlice│ │
│ │数据集CRUD │ │图表实例 │ │画布布局 │ │模板管理 │ │
│ │多Sheet │ │绑定+样式 │ │网格位置 │ │保存/加载 │ │
│ └──────────┘ └──────────┘ └───────────┘ └────────────┘ │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ Zustand(轻量快速) │
│ │
│ 管理:UI 瞬态,不进 Redux 以避免频繁 dispatch 开销 │
│ · 高频变化(拖拽中坐标、hover 状态) │
│ · 局部 UI 状态(面板折叠、Tab 选中) │
│ · 无需 DevTools 追踪的状态 │
│ │
│ ┌──────────┐ ┌────────────┐ ┌──────────────────┐ │
│ │ uiStore │ │ themeStore │ │ interactionStore │ │
│ │面板开关 │ │亮/暗主题 │ │hover/选中/拖拽中 │ │
│ │活跃Tab │ │当前色板 │ │缩放级别 │ │
│ │Modal状态 │ │ │ │ │ │
│ └──────────┘ └────────────┘ └──────────────────┘ │
└──────────────────────────────────────────────────────────┘
划分原则
| 判断标准 |
→ Redux Toolkit |
→ Zustand |
| 影响图表输出? |
是 |
否 |
| 需要持久化/撤销? |
是 |
否 |
| 变化频率? |
低~中 |
高 |
| 多组件共享? |
3+ 组件 |
1~2 组件 |
| 需要 DevTools? |
是 |
否 |
五、依赖注入(DI)
// frameworks/di/container.ts
// 组装所有端口实现,向上提供给 hooks
import { ImportDataUseCase } from '@/application/usecases/ImportDataUseCase';
import { SheetJSFileParser } from '@/adapters/gateways/SheetJSFileParser';
import { LocalStorageGateway } from '@/adapters/gateways/LocalStorageGateway';
// ...
// 实例化 output port 实现
const fileParser = new SheetJSFileParser();
const storageGateway = new LocalStorageGateway();
const chartRenderer = new EChartsOptionBuilder();
const exportGateway = new CompositeExportGateway(/* ... */);
const geoProvider = new GeoJsonProvider();
// 注入到 use case
export const importDataUseCase = new ImportDataUseCase(fileParser);
export const createChartUseCase = new CreateChartUseCase(chartRenderer);
export const updateChartConfigUseCase = new UpdateChartConfigUseCase(chartRenderer);
export const exportUseCase = new ExportUseCase(exportGateway);
export const templateUseCase = new TemplateUseCase(storageGateway);
export const layoutUseCase = new LayoutUseCase();
// frameworks/hooks/useImportData.ts
// Hook 只做:调 use case → 写 store → 返回状态
import { importDataUseCase } from '@/frameworks/di/container';
import { useAppDispatch } from '@/adapters/state/redux/store';
import { addDataSet } from '@/adapters/state/redux/dataSlice';
export function useImportData() {
const dispatch = useAppDispatch();
const handleImport = async (file: File) => {
const result = await importDataUseCase.execute(file); // 纯业务
dispatch(addDataSet(result.dataSet)); // 写状态
return result.suggestions; // 返回推荐
};
return { handleImport };
}
六、Use Case 调用链路示例
场景:用户拖入一个 Excel 文件
DropZone.tsx (UI)
│ onDrop(file)
▼
useImportData hook (frameworks/hooks)
│ 调用 importDataUseCase.execute(file)
▼
ImportDataUseCase (application/usecases)
│ ① 调用 IFileParser.parse(file) ←── 输出端口
│ ② 调用 FieldTypeInferenceService ←── 领域服务
│ ③ 调用 DataStructureInferenceService ←── 领域服务
│ ④ 调用 ChartRecommendationService ←── 领域服务
│ ⑤ 返回 { dataSet, suggestions }
▼
SheetJSFileParser (adapters/gateways) ←── 端口实现
│ SheetJS 解析 Excel → 二维数组 → DataSet Entity
▼
回到 hook
│ dispatch(addDataSet(dataSet)) ←── Redux
│ 返回 suggestions 给组件
▼
LeftPanel.tsx 更新字段列表
CenterCanvas.tsx 可选推荐图表
七、核心类型定义
// ===== domain/entities =====
type FieldType = 'number' | 'text' | 'date' | 'percentage' | 'geo';
interface Column {
name: string; // 列名
type: FieldType; // 推断出的类型
sampleValues: any[]; // 前 10 个样本值
}
interface DataSet {
id: string;
fileName: string;
sheetName?: string;
columns: Column[];
rows: Record<string, any>[]; // 每行是 { 列名: 值 }
}
type ChartType =
| 'kpi' | 'bar' | 'grouped-bar'
| 'stacked-bar' | 'horizontal-bar'
| 'line' | 'area' | 'pie'
| 'donut' | 'scatter' | 'radar'
| 'wordcloud' | 'boston-matrix'
| 'heatmap' | 'map' | 'combo'
| 'data-table';
interface FieldBinding {
axis: 'x' | 'y' | 'series' | 'color' | 'size' | 'label' | 'value';
columnName: string;
aggregation?: 'sum' | 'count' | 'avg' | 'max' | 'min';
}
interface StyleConfig {
title: { text: string; fontSize: number; color: string; position: string };
colors: string[]; // 色板
legend: { show: boolean; position: string; orient: string };
axis: {
xLabel: string; yLabel: string;
showGrid: boolean; labelRotate: number;
};
label: { show: boolean; format: 'value' | 'percent' | 'custom'; template?: string };
background: { color: string; opacity: number };
border: { color: string; width: number; radius: number };
animation: boolean;
}
interface ChartInstance {
id: string;
type: ChartType;
dataSetId: string;
bindings: FieldBinding[];
style: StyleConfig;
filters?: FilterRule[];
sort?: { column: string; order: 'asc' | 'desc' };
topN?: number;
}
// ===== layout =====
interface LayoutItem {
chartId: string;
x: number; y: number;
w: number; h: number;
}
// ===== template =====
interface Template {
id: string;
name: string;
description: string;
charts: Omit<ChartInstance, 'dataSetId'>[];
layout: LayoutItem[];
theme: string;
createdAt: string;
}
八、各 Store 职责
| Store |
状态 |
核心操作 |
| dataSlice (Redux) |
dataSets[], activeDataSetId |
addDataSet, removeDataSet, updateCell |
| chartSlice (Redux) |
charts[], activeChartId |
addChart, removeChart, updateBinding, updateStyle |
| layoutSlice (Redux) |
layouts[], canvasSize, snapToGrid |
updateLayout, resizeCanvas |
| templateSlice (Redux) |
templates[] |
saveTemplate, loadTemplate, deleteTemplate, exportTemplate, importTemplate |
| uiStore (Zustand) |
panelStates, activeTab, modalVisible |
togglePanel, setActiveTab, openModal, closeModal |
| themeStore (Zustand) |
currentTheme, customPalettes |
setTheme, addPalette |
| interactionStore (Zustand) |
hoveredChartId, dragState, zoomLevel |
setHover, setDrag, setZoom |
九、Next.js 使用策略
| 特性 |
用法 |
| App Router |
当前只有一个页面 /,但架构预留多页扩展 |
| Server Component |
layout.tsx 作为 Server Component 提供外壳 |
| Client Component |
所有交互组件标记 'use client' |
| Static Export |
next.config.ts 配置 output: 'export',构建为纯静态文件 |
| 路由预留 |
未来可扩展 /templates、/help 等页面 |
十、技术栈总览
| 层面 |
选型 |
| 框架 |
Next.js (App Router) + React 18 + TypeScript |
| 构建 |
Next.js 内置 (Turbopack) |
| 图表引擎 |
ECharts 5 |
| 词云 |
echarts-wordcloud |
| 表格 |
AG Grid Community |
| 文件解析 |
SheetJS (xlsx/csv) + 原生 JSON.parse |
| 拖拽布局 |
react-grid-layout |
| 导出 PNG/SVG |
ECharts 内置 + html2canvas |
| 导出 PDF |
jsPDF + html2canvas |
| 导出 PPT |
PptxGenJS |
| 导出 Excel |
SheetJS |
| 导出 HTML |
内联打包 ECharts + 数据 |
| 状态管理 |
Redux Toolkit (业务状态) + Zustand (UI 状态) |
| UI 组件 |
Ant Design 5 |
| 地图数据 |
中国省市 GeoJSON |
| 本地存储 |
localStorage (模板/配置持久化) |