From c6580b7c69053666c1a6a07fb67ef5635a9c9031 Mon Sep 17 00:00:00 2001 From: hypercross Date: Fri, 27 Feb 2026 15:21:21 +0800 Subject: [PATCH] refactor: break down & clean up --- src/components/card-preview.tsx | 105 ------------ src/components/md-deck.tsx | 154 ------------------ src/components/md-deck/CardPreview.tsx | 113 +++++++++++++ src/components/md-deck/DeckContent.tsx | 43 +++++ src/components/md-deck/DeckHeader.tsx | 47 ++++++ .../md-deck/editor-panel/DataEditorPanel.tsx | 34 ++++ .../editor-panel/PropertiesEditorPanel.tsx} | 34 +--- src/components/md-deck/editor-panel/index.ts | 3 + .../{stores => md-deck/hooks}/deckStore.ts | 91 ++++++++--- .../{utils => md-deck/hooks}/dimensions.ts | 0 .../{utils => md-deck/hooks}/layer-parser.ts | 0 .../hooks/useCardSelection.ts} | 10 +- src/components/md-deck/index.tsx | 88 ++++++++++ src/components/{ => md-deck}/types.ts | 0 14 files changed, 404 insertions(+), 318 deletions(-) delete mode 100644 src/components/card-preview.tsx delete mode 100644 src/components/md-deck.tsx create mode 100644 src/components/md-deck/CardPreview.tsx create mode 100644 src/components/md-deck/DeckContent.tsx create mode 100644 src/components/md-deck/DeckHeader.tsx create mode 100644 src/components/md-deck/editor-panel/DataEditorPanel.tsx rename src/components/{editor-panel.tsx => md-deck/editor-panel/PropertiesEditorPanel.tsx} (74%) create mode 100644 src/components/md-deck/editor-panel/index.ts rename src/components/{stores => md-deck/hooks}/deckStore.ts (74%) rename src/components/{utils => md-deck/hooks}/dimensions.ts (100%) rename src/components/{utils => md-deck/hooks}/layer-parser.ts (100%) rename src/components/{stores/use-selection.ts => md-deck/hooks/useCardSelection.ts} (90%) create mode 100644 src/components/md-deck/index.tsx rename src/components/{ => md-deck}/types.ts (100%) diff --git a/src/components/card-preview.tsx b/src/components/card-preview.tsx deleted file mode 100644 index ce7dcc4..0000000 --- a/src/components/card-preview.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { Show, For } from 'solid-js'; -import { marked } from '../markdown'; -import { getLayerStyle } from './utils/dimensions'; -import { getSelectionBoxStyle, useSelection } from './stores/use-selection'; -import type { DeckStore } from "./stores/deckStore"; - -export interface CardPreviewProps { - store: DeckStore; -} - -/** - * 渲染 layer 内容 - */ -function renderLayer(layer: { prop: string }, cardData: DeckStore['state']['cards'][number]): string { - const content = cardData[layer.prop] || ''; - return marked.parse(content) as string; -} - -export function CardPreview(props: CardPreviewProps) { - const currentCard = () => props.store.state.cards[props.store.state.activeTab]; - const visibleLayers = () => props.store.state.layerConfigs.filter((l) => l.visible); - const selectionStyle = () => - getSelectionBoxStyle(props.store.state.selectStart, props.store.state.selectEnd, props.store.state.dimensions); - - const selection = useSelection(props.store); - - let cardRef: HTMLDivElement | undefined; - - return ( -
- -
selection.onMouseDown(e, cardRef!)} - onMouseMove={(e) => selection.onMouseMove(e, cardRef!)} - onMouseUp={selection.onMouseUp} - onMouseLeave={selection.onMouseLeave} - > - {/* 框选遮罩 */} - -
- - - {/* 网格区域容器 */} -
- {/* 编辑模式下的网格线 */} - -
- - {(_, i) => ( -
- )} - - - {(_, i) => ( -
- )} - -
- - - {/* 渲染每个 layer */} - - {(layer) => { - const style = getLayerStyle(layer, props.store.state.dimensions!); - - return ( -
- ); - }} - -
-
- -
- ); -} diff --git a/src/components/md-deck.tsx b/src/components/md-deck.tsx deleted file mode 100644 index 8ba99b6..0000000 --- a/src/components/md-deck.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { customElement, noShadowDOM } from 'solid-element'; -import { Show, createEffect, createResource, For, onCleanup } from 'solid-js'; -import { resolvePath } from './utils/path'; -import { loadCSV } from './utils/csv-loader'; -import { initLayerConfigs } from './utils/layer-parser'; -import { createDeckStore } from './stores/deckStore'; -import { CardPreview } from './card-preview'; -import { DataEditorPanel, PropertiesEditorPanel } from './editor-panel'; - -interface DeckProps { - size?: string; - grid?: string; - bleed?: string; - padding?: string; - layers?: string; - fixed?: boolean | string; -} - -customElement('md-deck', { - size: '54x86', - grid: '5x8', - bleed: '1', - padding: '2', - layers: '', - fixed: false -}, (props, { element }) => { - noShadowDOM(); - - // 创建统一的 store - const store = createDeckStore(); - - // 从 element 的 textContent 获取 CSV 路径 - const csvPath = element?.textContent?.trim() || ''; - - // 隐藏原始文本内容 - if (element) { - element.textContent = ''; - } - - // 从父节点 article 的 data-src 获取当前 markdown 文件完整路径 - const articleEl = element?.closest('article[data-src]'); - const articlePath = articleEl?.getAttribute('data-src') || ''; - - // 解析相对路径 - const resolvedSrc = resolvePath(articlePath, csvPath); - - // 初始化 store 属性 - store.actions.setSize(props.size || '54x86'); - store.actions.setGrid(props.grid || '5x8'); - store.actions.setBleed(props.bleed || '1'); - store.actions.setPadding(props.padding || '2'); - - // 加载 CSV 文件 - const [csvData, { refetch }] = createResource(() => resolvedSrc, loadCSV); - - // 处理 CSV 数据加载结果 - createEffect(() => { - const data = csvData(); - const loading = csvData.loading; - const error = csvData.error; - - if (error) { - store.actions.setError(`加载 CSV 失败:${error.message}`); - return; - } - - if (!loading && data) { - store.actions.loadCards(data); - store.actions.setLayerConfigs(initLayerConfigs(data, (props.layers as string) || '')); - } - }); - - // 清理函数 - onCleanup(() => { - // 可以在这里清理资源 - }); - - return ( -
- {/* 左侧:CSV 数据编辑 */} - - - - - {/* 中间:卡牌预览和控制 */} -
- {/* Tab 选择器 */} -
- -
- - {(card, index) => ( - - )} - -
-
- - {/* 错误提示 */} - -
- {store.state.error} -
-
- - {/* 加载状态 */} - -
- 加载卡牌数据中... -
-
- - {/* 卡牌预览 */} - 0 && !store.state.error}> - - - - {/* 空状态 */} - -
- 暂无卡牌数据 -
-
-
- - {/* 右侧:属性编辑表单 */} - - - -
- ); -}); diff --git a/src/components/md-deck/CardPreview.tsx b/src/components/md-deck/CardPreview.tsx new file mode 100644 index 0000000..5f9f2d7 --- /dev/null +++ b/src/components/md-deck/CardPreview.tsx @@ -0,0 +1,113 @@ +import { Show, For, createMemo } from 'solid-js'; +import { marked } from '../../markdown'; +import { getLayerStyle } from './hooks/dimensions'; +import { useCardSelection } from './hooks/useCardSelection'; +import { getSelectionBoxStyle } from './hooks/useCardSelection'; +import type { DeckStore } from './hooks/deckStore'; + +export interface CardPreviewProps { + store: DeckStore; +} + +/** + * 渲染 layer 内容(提取为纯工具函数) + */ +function renderLayerContent(layer: { prop: string }, cardData: DeckStore['state']['cards'][number]): string { + const content = cardData[layer.prop] || ''; + return marked.parse(content) as string; +} + +/** + * 卡牌预览组件 + */ +export function CardPreview(props: CardPreviewProps) { + const { store } = props; + + // 使用 createMemo 优化计算 + const currentCard = createMemo(() => store.state.cards[store.state.activeTab]); + const visibleLayers = createMemo(() => store.state.layerConfigs.filter((l) => l.visible)); + const selectionStyle = createMemo(() => + getSelectionBoxStyle(store.state.selectStart, store.state.selectEnd, store.state.dimensions) + ); + + const selection = useCardSelection(store); + + let cardRef: HTMLDivElement | undefined; + + return ( +
+ +
selection.onMouseDown(e, cardRef!)} + onMouseMove={(e) => selection.onMouseMove(e, cardRef!)} + onMouseUp={selection.onMouseUp} + onMouseLeave={selection.onMouseLeave} + > + {/* 框选遮罩 */} + +
+ + + {/* 网格区域容器 */} +
+ {/* 编辑模式下的网格线 */} + +
+ + {(_, i) => ( +
+ )} + + + {(_, i) => ( +
+ )} + +
+ + + {/* 渲染每个 layer */} + + {(layer) => { + const style = getLayerStyle(layer, store.state.dimensions!); + + return ( +
+ ); + }} + +
+
+ +
+ ); +} diff --git a/src/components/md-deck/DeckContent.tsx b/src/components/md-deck/DeckContent.tsx new file mode 100644 index 0000000..3790de0 --- /dev/null +++ b/src/components/md-deck/DeckContent.tsx @@ -0,0 +1,43 @@ +import { Show } from 'solid-js'; +import { CardPreview } from './CardPreview'; +import type { DeckStore } from './hooks/deckStore'; + +export interface DeckContentProps { + store: DeckStore; + isLoading: boolean; +} + +/** + * 卡牌预览内容区域:错误/加载/卡牌预览/空状态 + */ +export function DeckContent(props: DeckContentProps) { + return ( + <> + {/* 错误提示 */} + +
+ {props.store.state.error} +
+
+ + {/* 加载状态 */} + +
+ 加载卡牌数据中... +
+
+ + {/* 卡牌预览 */} + 0 && !props.store.state.error}> + + + + {/* 空状态 */} + +
+ 暂无卡牌数据 +
+
+ + ); +} diff --git a/src/components/md-deck/DeckHeader.tsx b/src/components/md-deck/DeckHeader.tsx new file mode 100644 index 0000000..304fe40 --- /dev/null +++ b/src/components/md-deck/DeckHeader.tsx @@ -0,0 +1,47 @@ +import { For } from 'solid-js'; +import type { DeckStore } from './hooks/deckStore'; + +export interface DeckHeaderProps { + store: DeckStore; +} + +/** + * 卡牌预览头部:编辑按钮和 Tab 选择器 + */ +export function DeckHeader(props: DeckHeaderProps) { + const { store } = props; + + return ( +
+ {/* 编辑按钮 */} + + + {/* Tab 选择器 */} +
+ + {(card, index) => ( + + )} + +
+
+ ); +} diff --git a/src/components/md-deck/editor-panel/DataEditorPanel.tsx b/src/components/md-deck/editor-panel/DataEditorPanel.tsx new file mode 100644 index 0000000..7bedd90 --- /dev/null +++ b/src/components/md-deck/editor-panel/DataEditorPanel.tsx @@ -0,0 +1,34 @@ +import { For } from 'solid-js'; +import type { DeckStore } from '../hooks/deckStore'; + +export interface DataEditorPanelProps { + activeTab: number; + cards: DeckStore['state']['cards']; + updateCardData: DeckStore['actions']['updateCardData']; +} + +/** + * 左侧:CSV 数据编辑面板 + */ +export function DataEditorPanel(props: DataEditorPanelProps) { + return ( +
+

卡牌数据

+
+ + {(key) => ( +
+ +