From 7930075a24f69f963d4c44b1b67901622ea5bb1b Mon Sep 17 00:00:00 2001 From: Aaron Date: Fri, 31 Oct 2025 20:06:44 +0800 Subject: [PATCH 1/7] refactor(site): wip --- packages/site/docs/examples/index.md | 8 +- packages/site/src/components/Gallery.tsx | 117 +++++++++++++++++++ packages/site/src/components/Infographic.tsx | 75 ++++++++---- 3 files changed, 169 insertions(+), 31 deletions(-) create mode 100644 packages/site/src/components/Gallery.tsx diff --git a/packages/site/docs/examples/index.md b/packages/site/docs/examples/index.md index 27795db63..6dfde5c1c 100644 --- a/packages/site/docs/examples/index.md +++ b/packages/site/docs/examples/index.md @@ -2,10 +2,6 @@ > Coming soon... -import Infographic from '@site/src/components/Infographic'; +import Gallery from '@site/src/components/Gallery'; - + diff --git a/packages/site/src/components/Gallery.tsx b/packages/site/src/components/Gallery.tsx new file mode 100644 index 000000000..b43f48cb1 --- /dev/null +++ b/packages/site/src/components/Gallery.tsx @@ -0,0 +1,117 @@ +import { getTemplates } from '@antv/infographic'; +import Infographic from './Infographic'; + +// registerResourceLoader(async (config) => { +// const { data } = config; +// const [type, id] = data.split(':'); + +// if (type === 'icon') { +// const res = await fetch(`https://api.iconify.design/${id}.svg`).then( +// (res) => res.text(), +// ); +// return loadSVGResource(res); +// } else if (type === 'illus') { +// // from: https://github.com/balazser/undraw-svg-collection +// const rest = await fetch( +// `https://raw.githubusercontent.com/balazser/undraw-svg-collection/refs/heads/main/svgs/${id}.svg`, +// ).then((res) => res.text()); +// return loadSVGResource(rest); +// } +// }); + +const containerStyle: React.CSSProperties = { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(380px, 1fr))', + gap: '24px', + padding: '20px 0', +}; + +const cardStyle: React.CSSProperties = { + position: 'relative', + backgroundColor: '#ffffff', + borderRadius: '12px', + border: '1px solid #e8e8e8', + overflow: 'hidden', + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + boxShadow: '0 2px 8px rgba(0, 0, 0, 0.04)', + cursor: 'pointer', +}; + +const cardHoverStyle = { + transform: 'translateY(-4px)', + boxShadow: '0 12px 24px rgba(0, 0, 0, 0.12)', + borderColor: '#d0d0d0', +}; + +const contentStyle: React.CSSProperties = { + height: '300px', + padding: '16px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#fafafa', + overflow: 'hidden', +}; + +const infographicWrapperStyle: React.CSSProperties = { + width: '100%', + height: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}; + +const labelStyle: React.CSSProperties = { + padding: '12px 16px', + fontSize: '14px', + fontWeight: 500, + color: '#262626', + backgroundColor: '#ffffff', + borderTop: '1px solid #f0f0f0', + textAlign: 'center', + letterSpacing: '0.3px', +}; + +export default () => { + const templates = getTemplates(); + const dataKeys = ['list', 'hierarchy', 'compare', 'swot']; + const dataFallbackMap = { + sequence: 'list', + }; + + return ( +
+ {templates.map((key) => { + const d = key.split('-')[0]; + return ( +
{ + Object.assign(e.currentTarget.style, cardHoverStyle); + }} + onMouseLeave={(e) => { + e.currentTarget.style.transform = ''; + e.currentTarget.style.boxShadow = cardStyle.boxShadow || ''; + e.currentTarget.style.borderColor = cardStyle.borderColor || ''; + }} + > +
+
+ +
+
+
{key}
+
+ ); + })} +
+ ); +}; diff --git a/packages/site/src/components/Infographic.tsx b/packages/site/src/components/Infographic.tsx index 1214f32a5..968eb96ab 100644 --- a/packages/site/src/components/Infographic.tsx +++ b/packages/site/src/components/Infographic.tsx @@ -24,7 +24,32 @@ export default ({ instanceRef.current = instance; }, [options]); - return
; + return ( + <> + +
+ + ); }; const DATA: Record = { @@ -33,60 +58,60 @@ const DATA: Record = { desc: '整合数据资产,构建标签画像体系,赋能数字化运营之路', items: [ { - icon: 'company-021_v1_lineal', + icon: 'icon:mingcute/apple-fill', label: '企业形象优势', desc: '产品优势详细说明产品优势详细说明', value: 80, time: '2018', - illus: 'illus:english-teacher-bro-8693', + illus: 'illus:beer', }, { - icon: 'antenna-bars-5_v1_lineal', + icon: 'icon:mingcute/bear-fill', label: '综合实力优势', desc: '产品优势详细说明产品优势详细说明', value: 70, time: '2019', - illus: 'illus:teacher-student-rafiki-9049', + illus: 'illus:best-place', }, { - icon: 'achievment-050_v1_lineal', + icon: 'icon:mingcute/bell-ringing-fill', label: '企业营销优势', desc: '产品优势详细说明产品优势详细说明', value: 90, time: '2020', - illus: 'illus:mathematics-cuate-7503', + illus: 'illus:blank-canvas', }, { - icon: '3d-file-015_v1_lineal', + icon: 'icon:mingcute/bowknot-fill', label: '产品定位优势', desc: '产品优势详细说明产品优势详细说明', value: 60, time: '2021', - illus: 'illus:teacher-student-rafiki-9049', + illus: 'illus:breakfast', }, { - icon: 'activities-037_v1_lineal', + icon: 'icon:mingcute/camera-2-ai-fill', label: '产品体验优势', desc: '产品优势详细说明产品优势详细说明', value: 80, time: '2022', - illus: 'illus:english-teacher-bro-8693', + illus: 'illus:building-websites', }, { - icon: 'account-book-025_v1_lineal', + icon: 'icon:mingcute/car-fill', label: '制造成本优势', desc: '产品优势详细说明产品优势详细说明', value: 90, time: '2023', - illus: 'illus:mathematics-cuate-7503', + illus: 'illus:bus-stop', }, { - icon: 'account-book-025_v1_lineal', + icon: 'icon:mingcute/compass-fill', label: '制造成本优势', desc: '产品优势详细说明产品优势详细说明', value: 97, time: '2024', - illus: 'illus:english-teacher-bro-8693', + illus: 'illus:by-the-road', }, ], }, @@ -97,50 +122,50 @@ const DATA: Record = { { label: '用户调研', // desc: '通过用户调研,了解用户需求和痛点,指导产品设计和优化', - icon: '3d-file-015_v1_lineal', + icon: 'icon:mingcute/diamond-2-fill', children: [ { label: '用户为什么要使用某个音乐平台', desc: '用户为什么要使用某个音乐平台', - icon: 'company-021_v1_lineal', + icon: 'icon:mingcute/apple-fill', children: [ { label: '用户从哪些渠道了解到这个平台', - icon: 'activities-037_v1_lineal', + icon: 'icon:mingcute/camera-2-ai-fill', }, { label: '这个平台是哪些方面吸引了用户', - icon: 'activities-037_v1_lineal', + icon: 'icon:mingcute/camera-2-ai-fill', }, ], }, { label: '用户在什么场景下使用这个平台', desc: '用户在什么场景下使用这个平台', - icon: 'antenna-bars-5_v1_lineal', + icon: 'icon:mingcute/bear-fill', children: [ { label: '用户从什么事件什么场景下使用', - icon: 'account-book-025_v1_lineal', + icon: 'icon:mingcute/car-fill', }, { label: '用户在某个场景下用到哪些功能', - icon: 'account-book-025_v1_lineal', + icon: 'icon:mingcute/car-fill', }, ], }, { label: '用户什么原因下会离开这个平台', desc: '用户什么原因下会离开这个平台', - icon: 'achievment-050_v1_lineal', + icon: 'icon:mingcute/bell-ringing-fill', children: [ { label: '用户无法接受这个平台的原因', - icon: 'account-book-025_v1_lineal', + icon: 'icon:mingcute/car-fill', }, { label: '用户觉得这个平台有哪些不足', - icon: 'account-book-025_v1_lineal', + icon: 'icon:mingcute/car-fill', }, ], }, From 762c8a832da2308253539035011590b3be673345 Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 3 Nov 2025 11:15:44 +0800 Subject: [PATCH 2/7] refactor(infographic): rename hierarchy pyramid to list --- docs/development-template-guide.md | 392 ------------------ .../src/designs/structures/index.ts | 2 +- ...hierarchy-pyramid.tsx => list-pyramid.tsx} | 0 .../infographic/src/templates/built-in.ts | 12 +- 4 files changed, 7 insertions(+), 399 deletions(-) delete mode 100644 docs/development-template-guide.md rename packages/infographic/src/designs/structures/{hierarchy-pyramid.tsx => list-pyramid.tsx} (100%) diff --git a/docs/development-template-guide.md b/docs/development-template-guide.md deleted file mode 100644 index c45049373..000000000 --- a/docs/development-template-guide.md +++ /dev/null @@ -1,392 +0,0 @@ -# 信息图模板开发指南 - -## 🚀 快速开始 - -### 1. 数据项 (Items) 开发 (5分钟) - -数据项是信息图的基本展示单元,负责单个数据元素的视觉呈现。 - -#### 创建新的数据项 - -1. **文件位置**: `packages/infographic/src/designs/items/` -2. **命名规范**: 大驼峰命名,如 `ProgressCard.tsx` -3. **注册名**: 小写连字符,如 `progress-card` - -#### 数据项模板 - -```typescript -/** @jsxImportSource @antv/infographic-jsx */ -import { ComponentType, Group } from '@antv/infographic-jsx'; -import { ItemLabel, ItemDesc, ItemIcon, ItemValue } from '../components'; -import { registerItem } from './registry'; -import type { BaseItemProps } from './types'; -import { getItemProps } from './utils'; - -export interface MyItemProps extends BaseItemProps { - width?: number; - height?: number; - iconSize?: number; -} - -export const MyItem: ComponentType = (props) => { - const [ - { - datum, - indexes, - width = 300, - height = 80, - iconSize = 32, - themeColors, - }, - restProps, - ] = getItemProps(props, ['width', 'height', 'iconSize']); - - return ( - - {/* 背景 */} - - - {/* 图标 */} - {datum.icon && ( - - )} - - {/* 标签 */} - - {datum.label} - - - {/* 数值 */} - {datum.value !== undefined && ( - - )} - - {/* 描述 */} - {datum.desc && ( - - {datum.desc} - - )} - - ); -}; - -registerItem('my-item', { component: MyItem }); -``` - -#### 现有数据项列表 - -| 名称 | 描述 | 文件 | -| ------------------ | -------------- | ---------------------- | -| `BadgeCard` | 徽章卡片样式 | `BadgeCard.tsx` | -| `CandyCardLite` | 糖果卡片轻量版 | `CandyCardLite.tsx` | -| `ChartColumn` | 柱状图表 | `ChartColumn.tsx` | -| `CircleNode` | 圆形节点 | `CircleNode.tsx` | -| `CircularProgress` | 环形进度条 | `CircularProgress.tsx` | -| `CompactCard` | 紧凑卡片 | `CompactCard.tsx` | -| `DoneList` | 完成列表项 | `DoneList.tsx` | -| `ProgressCard` | 进度卡片 | `ProgressCard.tsx` | -| `RibbonCard` | 丝带卡片 | `RibbonCard.tsx` | -| `SimpleItem` | 简洁数据项 | `SimpleItem.tsx` | - -### 2. 结构 (Structures) 开发 (5分钟) - -结构定义了数据项的组织方式和整体布局。 - -#### 结构分类体系 - -| 类型 | 前缀 | 描述 | 示例 | -| -------- | ------------ | ---------------- | -------------------------------------- | -| 列表结构 | `list-` | 并列排布 | `list-column`, `list-row` | -| 对比结构 | `compare-` | 二元/多元对比 | `compare-left-right`, `compare-mirror` | -| 顺序结构 | `sequence-` | 有方向性的信息流 | `sequence-timeline`, `sequence-steps` | -| 层级结构 | `hierarchy-` | 树状/嵌套关系 | `hierarchy-tree`, `hierarchy-pyramid` | -| 关系结构 | `relation-` | 元素间连接关系 | `relation-network`, `relation-circle` | - -#### 创建新的结构 - -1. **文件位置**: `packages/infographic/src/designs/structures/` -2. **命名规范**: 大驼峰命名,如 `ListGrid.tsx` -3. **注册名**: 小写连字符,如 `list-grid` - -#### 结构模板 - -```typescript -/** @jsxImportSource @antv/infographic-jsx */ -import type { ComponentType } from '@antv/infographic-jsx'; -import { FlexLayout } from '../layouts'; -import { Title, ItemsGroup, BtnAdd, BtnRemove, BtnsGroup } from '../components'; -import { registerStructure } from './registry'; -import type { BaseStructureProps } from './types'; -import { getElementBounds } from '@antv/infographic-jsx'; - -export interface ListGridProps extends BaseStructureProps { - gap?: number; - columns?: number; -} - -export const ListGrid: ComponentType = (props) => { - const { Title: TitleComponent, Item, data, gap = 20, columns = 3 } = props; - const { title, desc, items = [] } = data; - - // 标题处理 - const titleContent = TitleComponent ? ( - - ) : null; - - // 计算元素尺寸 - const itemBounds = getElementBounds( - - ); - - // 准备元素数组 - const itemElements = []; - const btnElements = []; - - // 生成数据项和按钮 - items.forEach((item, index) => { - const indexes = [index]; - const row = Math.floor(index / columns); - const col = index % columns; - - const x = col * (itemBounds.width + gap); - const y = row * (itemBounds.height + gap); - - itemElements.push( - - ); - - btnElements.push( - - ); - - btnElements.push( - - ); - }); - - // 末尾添加按钮 - if (items.length > 0) { - const lastIndex = items.length; - const row = Math.floor(lastIndex / columns); - const col = lastIndex % columns; - - btnElements.push( - - ); - } - - return ( - - {titleContent} - - {itemElements} - {btnElements} - - - ); -}; - -registerStructure('list-grid', { component: ListGrid }); -``` - -#### 现有结构列表 - -| 名称 | 类型 | 描述 | 文件 | -| -------------------- | ---- | ---------- | ------------------------ | -| `list-column` | 列表 | 纵向单列 | `list-column.tsx` | -| `list-row` | 列表 | 横向单行 | `list-row.tsx` | -| `list-grid` | 列表 | 网格布局 | `list-grid.tsx` | -| `list-waterfall` | 列表 | 瀑布流 | `list-waterfall.tsx` | -| `compare-left-right` | 对比 | 左右对比 | `compare-left-right.tsx` | -| `compare-mirror` | 对比 | 镜像对比 | `compare-mirror.tsx` | -| `hierarchy-tree` | 层级 | 树形结构 | `hierarchy-tree.tsx` | -| `hierarchy-pyramid` | 层级 | 金字塔结构 | `hierarchy-pyramid.tsx` | -| `sequence-timeline` | 顺序 | 时间轴 | `sequence-timeline.tsx` | -| `sequence-steps` | 顺序 | 步骤流程 | `sequence-steps.tsx` | -| `relation-network` | 关系 | 网络图 | `relation-network.tsx` | -| `relation-circle` | 关系 | 循环关系 | `relation-circle.tsx` | - -## 📋 组件速查 - -### 数据项组件 - -| 组件 | 导入 | 用途 | -| ------------- | ------------------------ | -------- | -| **ItemLabel** | `infographic/components` | 标签文本 | -| **ItemDesc** | `infographic/components` | 描述文本 | -| **ItemIcon** | `infographic/components` | 图标显示 | -| **ItemValue** | `infographic/components` | 数值显示 | - -### 结构组件 - -| 组件 | 导入 | 用途 | -| -------------- | ------------------------ | ---------- | -| **Title** | `infographic/components` | 标题组件 | -| **ItemsGroup** | `infographic/components` | 数据项容器 | -| **BtnAdd** | `infographic/components` | 添加按钮 | -| **BtnRemove** | `infographic/components` | 删除按钮 | -| **BtnsGroup** | `infographic/components` | 按钮容器 | - -### 布局组件 - -| 组件 | 导入 | 用途 | -| -------------- | ----------------------- | -------- | -| **FlexLayout** | `infographic/layouts` | 弹性布局 | -| **Group** | `@antv/infographic-jsx` | 分组容器 | - -### 图形组件 - -| 组件 | 导入 | 用途 | -| ---------- | ----------------------- | ---- | -| **Rect** | `@antv/infographic-jsx` | 矩形 | -| **Circle** | `@antv/infographic-jsx` | 圆形 | -| **Text** | `@antv/infographic-jsx` | 文本 | -| **Path** | `@antv/infographic-jsx` | 路径 | - -## 🎨 主题系统 - -### 主题色彩 - -```typescript -interface ThemeColors { - colorPrimary: string; // 主色调 - colorPrimaryBg: string; // 主色背景 - colorPrimaryText: string; // 主色文本 - colorText: string; // 主要文本 - colorTextSecondary: string; // 次要文本 - colorWhite: string; // 纯白色 - colorBg: string; // 背景色 - colorBgElevated: string; // 卡片背景 -} -``` - -### 使用主题色彩 - -```typescript -// 在数据项中使用 - - - -// 渐变示例 - - - - - - -``` - -## 📊 数据格式 - -### 数据结构 - -```typescript -interface Data { - title?: string; // 标题 - desc?: string; // 描述 - items: ItemDatum[]; // 数据项数组 - illus?: Record; // 插图资源 -} - -interface ItemDatum { - icon?: string | ResourceConfig; // 图标 - label?: string; // 标签 - desc?: string; // 描述 - value?: number; // 数值 - illus?: string | ResourceConfig; // 插图 - children?: ItemDatum[]; // 子项(层级结构) -} -``` - -### 示例数据 - -```typescript -const sampleData = { - title: '项目进度', - desc: '2024年Q1项目完成情况', - items: [ - { - label: '需求分析', - desc: '完成用户需求调研和分析', - value: 100, - icon: 'check-circle', - }, - { - label: 'UI设计', - desc: '完成界面设计和原型', - value: 85, - icon: 'palette', - }, - { - label: '开发实现', - desc: '功能开发和单元测试', - value: 60, - icon: 'code', - }, - ], -}; -``` - -## 🔧 工具函数 - -### getItemProps - -```typescript -const [props, rest] = getItemProps(rawProps, ['width', 'height']); -// props: 包含所有标准属性 + 自定义属性 -// rest: 剩余的props,传给最外层Group -``` - -### getElementBounds - -```typescript -const bounds = getElementBounds(); -// bounds: { x, y, width, height } -``` - -### getItemId - -```typescript -const id = getItemId(indexes, 'shape', 'item'); -// 生成: "item-0-shape-item" -``` - -## 🧪 调试技巧 - -### 1. 添加调试边框 - -```typescript - -``` - -### 2. 控制台调试 - -```typescript -console.log('Position:', { x, y, width, height }); -``` - -### 3. 浏览器调试 - -- 使用开发者工具检查SVG -- 查看元素边界框 -- 调试布局计算 diff --git a/packages/infographic/src/designs/structures/index.ts b/packages/infographic/src/designs/structures/index.ts index 0b28371b9..f3d649fad 100644 --- a/packages/infographic/src/designs/structures/index.ts +++ b/packages/infographic/src/designs/structures/index.ts @@ -2,10 +2,10 @@ export * from './compare-hierarchy-left-right'; export * from './compare-hierarchy-row'; export * from './compare-left-right'; export * from './compare-mirror'; -export * from './hierarchy-pyramid'; export * from './hierarchy-tree'; export * from './list-column'; export * from './list-grid'; +export * from './list-pyramid'; export * from './list-row'; export * from './list-sector'; export * from './list-waterfall'; diff --git a/packages/infographic/src/designs/structures/hierarchy-pyramid.tsx b/packages/infographic/src/designs/structures/list-pyramid.tsx similarity index 100% rename from packages/infographic/src/designs/structures/hierarchy-pyramid.tsx rename to packages/infographic/src/designs/structures/list-pyramid.tsx diff --git a/packages/infographic/src/templates/built-in.ts b/packages/infographic/src/templates/built-in.ts index 5e88ecf57..7b94a35a1 100644 --- a/packages/infographic/src/templates/built-in.ts +++ b/packages/infographic/src/templates/built-in.ts @@ -27,26 +27,26 @@ const BUILT_IN_TEMPLATES: Record = { items: [{ type: 'circle-node', width: 180 }, { type: 'plain-text' }], }, }, - 'hierarchy-pyramid-rounded-rect-node': { + 'list-pyramid-rounded-rect-node': { design: { title: 'default', structure: { - type: 'hierarchy-pyramid', + type: 'list-pyramid', }, items: [{ type: 'rounded-rect-node' }], }, }, - 'hierarchy-pyramid-badge-card': { + 'list-pyramid-badge-card': { design: { title: 'default', - structure: { type: 'hierarchy-pyramid' }, + structure: { type: 'list-pyramid' }, items: [{ type: 'badge-card' }], }, }, - 'hierarchy-pyramid-compact-card': { + 'list-pyramid-compact-card': { design: { title: 'default', - structure: { type: 'hierarchy-pyramid' }, + structure: { type: 'list-pyramid' }, items: [{ type: 'compact-card' }], }, }, From d8bb3dc3d5ab606936da6fb01943c3eb28d885b3 Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 3 Nov 2025 11:33:10 +0800 Subject: [PATCH 3/7] feat(site): add gallery --- packages/site/docs/examples/index.md | 2 - packages/site/package.json | 7 +- packages/site/src/components/Gallery.tsx | 660 +++++++++++++++--- packages/site/src/components/Infographic.tsx | 15 + .../site/src/components/ResourceLoader.ts | 54 ++ 5 files changed, 633 insertions(+), 105 deletions(-) create mode 100644 packages/site/src/components/ResourceLoader.ts diff --git a/packages/site/docs/examples/index.md b/packages/site/docs/examples/index.md index 6dfde5c1c..2194f48f3 100644 --- a/packages/site/docs/examples/index.md +++ b/packages/site/docs/examples/index.md @@ -1,7 +1,5 @@ # 示例 -> Coming soon... - import Gallery from '@site/src/components/Gallery'; diff --git a/packages/site/package.json b/packages/site/package.json index c634aa411..96bc8d79d 100644 --- a/packages/site/package.json +++ b/packages/site/package.json @@ -15,14 +15,17 @@ "typecheck": "tsc" }, "dependencies": { + "@ant-design/icons": "^6.1.0", + "@antv/infographic": "workspace:^", "@docusaurus/core": "3.9.2", "@docusaurus/preset-classic": "3.9.2", "@mdx-js/react": "^3.0.0", + "antd": "^5.27.4", "clsx": "^2.0.0", + "framer-motion": "^12.23.24", "prism-react-renderer": "^2.3.0", "react": "^19.0.0", - "react-dom": "^19.0.0", - "@antv/infographic": "workspace:^" + "react-dom": "^19.0.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.9.2", diff --git a/packages/site/src/components/Gallery.tsx b/packages/site/src/components/Gallery.tsx index b43f48cb1..f8e55e8cb 100644 --- a/packages/site/src/components/Gallery.tsx +++ b/packages/site/src/components/Gallery.tsx @@ -1,117 +1,575 @@ -import { getTemplates } from '@antv/infographic'; +import { + CheckOutlined, + CopyOutlined, + EyeOutlined, + FilterOutlined, + SearchOutlined, +} from '@ant-design/icons'; +import { getTemplates, InfographicOptions } from '@antv/infographic'; +import { + Button, + Card, + ConfigProvider, + Empty, + Input, + message, + Modal, + Select, + Space, + Tag, + theme, + Tooltip, +} from 'antd'; +import type { FC } from 'react'; +import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import Infographic from './Infographic'; +import './ResourceLoader'; -// registerResourceLoader(async (config) => { -// const { data } = config; -// const [type, id] = data.split(':'); - -// if (type === 'icon') { -// const res = await fetch(`https://api.iconify.design/${id}.svg`).then( -// (res) => res.text(), -// ); -// return loadSVGResource(res); -// } else if (type === 'illus') { -// // from: https://github.com/balazser/undraw-svg-collection -// const rest = await fetch( -// `https://raw.githubusercontent.com/balazser/undraw-svg-collection/refs/heads/main/svgs/${id}.svg`, -// ).then((res) => res.text()); -// return loadSVGResource(rest); -// } -// }); - -const containerStyle: React.CSSProperties = { - display: 'grid', - gridTemplateColumns: 'repeat(auto-fill, minmax(380px, 1fr))', - gap: '24px', - padding: '20px 0', -}; +// 类型定义 +interface CategoryConfig { + label: string; + color: string; + icon?: string; + description?: string; +} -const cardStyle: React.CSSProperties = { - position: 'relative', - backgroundColor: '#ffffff', - borderRadius: '12px', - border: '1px solid #e8e8e8', - overflow: 'hidden', - transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', - boxShadow: '0 2px 8px rgba(0, 0, 0, 0.04)', - cursor: 'pointer', -}; +interface TemplateItem { + key: string; + category: string; + config: CategoryConfig; +} -const cardHoverStyle = { - transform: 'translateY(-4px)', - boxShadow: '0 12px 24px rgba(0, 0, 0, 0.12)', - borderColor: '#d0d0d0', +// 更新的类别配置 +const CATEGORY_CONFIG: Record = { + compare: { + label: '对比型', + color: 'blue', + icon: '⚖️', + description: 'Comparison layouts', + }, + hierarchy: { + label: '层级型', + color: 'purple', + icon: '🏗️', + description: 'Tree structures', + }, + list: { + label: '列表型', + color: 'cyan', + icon: '📋', + description: 'List layouts', + }, + relation: { + label: '关系型', + color: 'magenta', + icon: '🔗', + description: 'Relationship diagrams', + }, + sequence: { + label: '顺序型', + color: 'green', + icon: '➡️', + description: 'Sequential flows', + }, + quadrant: { + label: '四象限图', + color: 'orange', + icon: '📊', + description: 'Quadrant analysis', + }, }; -const contentStyle: React.CSSProperties = { - height: '300px', - padding: '16px', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - backgroundColor: '#fafafa', - overflow: 'hidden', +// 获取类别配置 +const getCategoryConfig = (key: string): CategoryConfig => { + const category = key.split('-')[0]; + return ( + CATEGORY_CONFIG[category] || { + label: category, + color: 'default', + icon: '📌', + description: 'Template', + } + ); }; -const infographicWrapperStyle: React.CSSProperties = { - width: '100%', - height: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', -}; +// 模板卡片组件 +const TemplateCard: FC<{ + templateKey: string; + categoryConfig: CategoryConfig; + dataType: string; + onCopy: (key: string) => void; + onExpand: (key: string) => void; + isCopied: boolean; + baseOptions?: Partial; +}> = memo( + ({ + templateKey, + categoryConfig, + dataType, + onCopy, + onExpand, + isCopied, + baseOptions, + }) => { + const [isHovered, setIsHovered] = useState(false); -const labelStyle: React.CSSProperties = { - padding: '12px 16px', - fontSize: '14px', - fontWeight: 500, - color: '#262626', - backgroundColor: '#ffffff', - borderTop: '1px solid #f0f0f0', - textAlign: 'center', - letterSpacing: '0.3px', -}; + return ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + hoverable + styles={{ + body: { padding: 0 }, + }} + > +
+
+ + +
+ +
+ +
+
+ +
+ + {categoryConfig.icon} + {categoryConfig.label} + {templateKey} + +
+
+ ); + }, +); + +// 主组件 +const InfographicGallery: FC = () => { + const [isDark, setIsDark] = useState(false); + + const templates = useMemo(() => getTemplates(), []); + const rules: { rule: string; data: string }[] = [ + { + rule: '^(sequence|relation)-', + data: 'list', + }, + { + rule: '^quadrant-', + data: 'list', + }, + { + rule: '^compare-', + data: 'compare', + }, + { + rule: '^(hierarchy)-', + data: 'hierarchy', + }, + ]; + + const baseOptions: Partial = useMemo( + () => ({ + theme: isDark ? 'dark' : 'light', + themeConfig: { palette: 'antv' }, + }), + [isDark], + ); + + // 检测主题变化 + useEffect(() => { + const checkTheme = () => { + const htmlElement = document.documentElement; + const currentTheme = htmlElement.getAttribute('data-theme'); + setIsDark(currentTheme === 'dark'); + }; + + checkTheme(); + + // 监听主题变化 + const observer = new MutationObserver(checkTheme); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-theme'], + }); + + return () => observer.disconnect(); + }, []); + + // 状态管理 + const [copiedId, setCopiedId] = useState(null); + const [searchTerm, setSearchTerm] = useState(''); + const [selectedCategory, setSelectedCategory] = useState('all'); + const [previewTemplate, setPreviewTemplate] = useState(null); + + // 准备模板数据 + const templateItems = useMemo((): TemplateItem[] => { + return templates.map((key) => ({ + key, + category: key.split('-')[0], + config: getCategoryConfig(key), + })); + }, [templates]); + + // 获取所有类别 + const categories = useMemo(() => { + const uniqueCategories = Array.from( + new Set(templateItems.map((item) => item.category)), + ); + return [ + { value: 'all', label: '全部模板' }, + ...uniqueCategories.map((cat) => ({ + value: cat, + label: CATEGORY_CONFIG[cat]?.label || cat, + })), + ]; + }, [templateItems]); + + // 过滤模板 + const filteredTemplates = useMemo(() => { + return templateItems.filter((item) => { + const matchesSearch = + searchTerm === '' || + item.key.toLowerCase().includes(searchTerm.toLowerCase()) || + item.config.label.toLowerCase().includes(searchTerm.toLowerCase()); + + const matchesCategory = + selectedCategory === 'all' || item.category === selectedCategory; + + return matchesSearch && matchesCategory; + }); + }, [templateItems, searchTerm, selectedCategory]); + + // 处理复制 + const handleCopy = useCallback((key: string) => { + navigator.clipboard.writeText(key).then(() => { + setCopiedId(key); + message.success({ + content: `模板 ID "${key}" 已复制!`, + duration: 2, + }); + setTimeout(() => setCopiedId(null), 2000); + }); + }, []); + + // 处理预览 + const handlePreview = useCallback((key: string) => { + setPreviewTemplate(key); + }, []); + + // 获取数据类型 + const getDataType = useCallback( + (key: string) => { + return rules.find((r) => new RegExp(r.rule).test(key))?.data || 'list'; + }, + [rules], + ); + + // 动态样式 + const dynamicStyles = useMemo(() => { + return ` + .infographic-gallery-container { + padding: 2rem 0; + margin: 0 auto; + max-width: 100%; + } + + .gallery-header { + text-align: center; + margin-bottom: 3rem; + } -export default () => { - const templates = getTemplates(); - const dataKeys = ['list', 'hierarchy', 'compare', 'swot']; - const dataFallbackMap = { - sequence: 'list', - }; + .gallery-title { + font-size: 2rem; + font-weight: 700; + margin-bottom: 0.5rem; + color: ${isDark ? '#fff' : 'var(--ifm-heading-color)'}; + } + + .gallery-subtitle { + font-size: 1rem; + margin-bottom: 2rem; + color: ${isDark ? 'rgba(255, 255, 255, 0.65)' : 'var(--ifm-color-emphasis-700)'}; + } + + .results-info { + text-align: center; + margin-top: 1.5rem; + font-size: 0.9rem; + color: ${isDark ? 'rgba(255, 255, 255, 0.65)' : 'var(--ifm-color-emphasis-700)'}; + } + + .results-info strong { + color: #691eff; + font-weight: 600; + } + + /* 网格布局 */ + .gallery-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 1.5rem; + padding: 0 1rem; + } + + /* 卡片样式 */ + .gallery-card { + height: 100%; + transition: all 0.3s ease; + border-color: ${isDark ? 'rgba(255, 255, 255, 0.12)' : 'var(--ifm-color-emphasis-200)'}; + background: ${isDark ? '#1f1f1f' : '#fff'}; + } + + .gallery-card:hover { + transform: translateY(-4px); + box-shadow: ${ + isDark + ? '0 8px 24px rgba(0, 0, 0, 0.5)' + : '0 8px 24px rgba(0, 0, 0, 0.12)' + }; + } + + .gallery-card-header { + position: relative; + height: 280px; + display: flex; + align-items: center; + justify-content: center; + padding: 1.5rem; + background: ${isDark ? '#141414' : 'var(--ifm-color-emphasis-100)'}; + } + + .infographic-wrapper { + width: 100%; + height: 100%; + transition: transform 0.3s ease; + } + + .infographic-wrapper.hovered { + transform: scale(1.03); + } + + .gallery-card-actions { + position: absolute; + top: 1rem; + right: 1rem; + z-index: 10; + opacity: 0; + transition: opacity 0.3s ease; + } + + .gallery-card:hover .gallery-card-actions { + opacity: 1; + } + + .gallery-card-footer { + padding: 1rem; + border-top: 1px solid ${isDark ? 'rgba(255, 255, 255, 0.12)' : 'var(--ifm-color-emphasis-200)'}; + background: ${isDark ? '#1f1f1f' : '#fff'}; + } + + .template-id { + font-size: 0.75rem; + color: ${isDark ? 'rgba(255, 255, 255, 0.45)' : 'var(--ifm-color-emphasis-700)'}; + font-family: var(--ifm-font-family-monospace); + margin-left: auto; + } + + .empty-state { + grid-column: 1 / -1; + padding: 4rem 2rem; + text-align: center; + } + + .preview-content { + display: flex; + align-items: center; + justify-content: center; + min-height: 400px; + background: ${isDark ? '#141414' : 'var(--ifm-color-emphasis-100)'}; + border-radius: 8px; + padding: 2rem; + } + + /* 响应式设计 */ + @media (max-width: 996px) { + .gallery-grid { + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1rem; + } + + .gallery-card-header { + height: 240px; + padding: 1rem; + } + } + + @media (max-width: 768px) { + .gallery-grid { + grid-template-columns: 1fr; + padding: 0; + } + + .gallery-card-actions { + opacity: 1; + } + + .gallery-title { + font-size: 1.5rem; + } + } + `; + }, [isDark]); return ( -
- {templates.map((key) => { - const d = key.split('-')[0]; - return ( -
{ - Object.assign(e.currentTarget.style, cardHoverStyle); - }} - onMouseLeave={(e) => { - e.currentTarget.style.transform = ''; - e.currentTarget.style.boxShadow = cardStyle.boxShadow || ''; - e.currentTarget.style.borderColor = cardStyle.borderColor || ''; - }} + +
+ + +
+

信息图模板

+

浏览并选择我们的模板集合

+ + -
-
- -
+ } + placeholder="搜索模板..." + value={searchTerm} + onChange={(e) => setSearchTerm(e.target.value)} + allowClear + size="large" + /> + +