# 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) ```typescript // 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(); ``` ```typescript // 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 可选推荐图表 ``` ## 七、核心类型定义 ```typescript // ===== 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[]; // 每行是 { 列名: 值 } } 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[]; 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 (模板/配置持久化) |