From 6bcdca3a77b4120c42b807ebedf681fc11b70a89 Mon Sep 17 00:00:00 2001 From: hyper Date: Fri, 7 Aug 2026 11:20:26 +0800 Subject: [PATCH] feat(md-deck): Add card list navigation and tabbed editor panels - Move card tab selector to a left sidebar (CardList) with pagination - Replace separate side toggle and layer panels with EditorTabs - Move copy code button from layer panel to DeckHeader - Simplify PropertiesEditorPanel by removing front/back toggle and using a select for shape --- src/components/md-deck/CardList.tsx | 86 ++++++++++++++++++ src/components/md-deck/DeckHeader.tsx | 25 ++--- src/components/md-deck/EditorTabs.tsx | 59 ++++++++++++ .../md-deck/editor-panel/LayerEditorPanel.tsx | 13 +-- .../editor-panel/PropertiesEditorPanel.tsx | 91 ++++++++----------- src/components/md-deck/index.tsx | 36 +++----- 6 files changed, 203 insertions(+), 107 deletions(-) create mode 100644 src/components/md-deck/CardList.tsx create mode 100644 src/components/md-deck/EditorTabs.tsx diff --git a/src/components/md-deck/CardList.tsx b/src/components/md-deck/CardList.tsx new file mode 100644 index 0000000..ac7c45e --- /dev/null +++ b/src/components/md-deck/CardList.tsx @@ -0,0 +1,86 @@ +import { For, Show, createEffect, createSignal, on } from "solid-js"; +import type { DeckStore } from "./hooks/deckStore"; + +export interface CardListProps { + store: DeckStore; +} + +const PAGE_SIZE = 10; + +/** + * 卡牌列表:左侧垂直导航,每行高度一致,按页分页 + */ +export function CardList(props: CardListProps) { + const { store } = props; + const [page, setPage] = createSignal(0); + + const totalPages = () => + Math.max(1, Math.ceil(store.state.cards.length / PAGE_SIZE)); + + const pageCards = () => { + const start = page() * PAGE_SIZE; + return store.state.cards.slice(start, start + PAGE_SIZE); + }; + + // 当活动卡牌变化时,自动跳转到其所在页(只响应 activeTab,避免与手动翻页互相干扰) + createEffect( + on( + () => store.state.activeTab, + (tab) => { + setPage(Math.floor(tab / PAGE_SIZE)); + }, + ), + ); + + return ( + + ); +} diff --git a/src/components/md-deck/DeckHeader.tsx b/src/components/md-deck/DeckHeader.tsx index c7c5479..5c7a0ff 100644 --- a/src/components/md-deck/DeckHeader.tsx +++ b/src/components/md-deck/DeckHeader.tsx @@ -1,4 +1,3 @@ -import { For } from "solid-js"; import type { DeckStore } from "./hooks/deckStore"; export interface DeckHeaderProps { @@ -33,23 +32,13 @@ export function DeckHeader(props: DeckHeaderProps) { 📥 导出 PDF - {/* Tab 选择器 */} -
- - {(card, index) => ( - - )} - -
+ {/* 复制代码按钮 */} + ); } diff --git a/src/components/md-deck/EditorTabs.tsx b/src/components/md-deck/EditorTabs.tsx new file mode 100644 index 0000000..e27f2c6 --- /dev/null +++ b/src/components/md-deck/EditorTabs.tsx @@ -0,0 +1,59 @@ +import { createSignal } from "solid-js"; +import type { DeckStore } from "./hooks/deckStore"; +import { LayerEditorPanel, PropertiesEditorPanel } from "./editor-panel"; + +export interface EditorTabsProps { + store: DeckStore; +} + +type TabKey = "sizing" | "front" | "back"; + +/** + * 编辑面板:尺寸 / 正面图层 / 背面图层 用标签页切换,节省横向空间 + */ +export function EditorTabs(props: EditorTabsProps) { + const { store } = props; + const [tab, setTab] = createSignal("sizing"); + + const tabs: { key: TabKey; label: string }[] = [ + { key: "sizing", label: "尺寸" }, + { key: "front", label: "正面图层" }, + { key: "back", label: "背面图层" }, + ]; + + const selectTab = (key: TabKey) => { + setTab(key); + // 切换正/背面标签时,同步预览的显示面 + if (key === "front") store.actions.setActiveSide("front"); + if (key === "back") store.actions.setActiveSide("back"); + }; + + return ( +
+ {/* 标签页切换 */} +
+ {tabs.map((t) => ( + + ))} +
+ + {tab() === "sizing" ? ( + + ) : ( + + )} +
+ ); +} diff --git a/src/components/md-deck/editor-panel/LayerEditorPanel.tsx b/src/components/md-deck/editor-panel/LayerEditorPanel.tsx index 3960d1b..be1792b 100644 --- a/src/components/md-deck/editor-panel/LayerEditorPanel.tsx +++ b/src/components/md-deck/editor-panel/LayerEditorPanel.tsx @@ -6,11 +6,13 @@ import { closestCenter, } from "@thisbeyond/solid-dnd"; import type { DeckStore } from "../hooks/deckStore"; +import type { CardSide } from "../types"; import { toSortableId } from "../hooks/layer-crud"; import { LayerRow } from "./LayerRow"; export interface LayerEditorPanelProps { store: DeckStore; + side?: CardSide; } function LayerEditorPanel(props: LayerEditorPanelProps) { @@ -20,7 +22,7 @@ function LayerEditorPanel(props: LayerEditorPanelProps) { const [addMenuOpen, setAddMenuOpen] = createSignal(false); let addMenuRef: HTMLDivElement | undefined; - const side = () => store.state.activeSide; + const side = () => props.side || "front"; const layers = () => side() === "front" ? store.state.frontLayerConfigs @@ -143,15 +145,6 @@ function LayerEditorPanel(props: LayerEditorPanelProps) { - -
- ); } diff --git a/src/components/md-deck/editor-panel/PropertiesEditorPanel.tsx b/src/components/md-deck/editor-panel/PropertiesEditorPanel.tsx index c513c34..7267747 100644 --- a/src/components/md-deck/editor-panel/PropertiesEditorPanel.tsx +++ b/src/components/md-deck/editor-panel/PropertiesEditorPanel.tsx @@ -1,49 +1,32 @@ -import type { DeckStore } from '../hooks/deckStore'; -import type { CardShape } from '../types'; +import type { DeckStore } from "../hooks/deckStore"; +import type { CardShape } from "../types"; export interface PropertiesEditorPanelProps { store: DeckStore; } /** - * 卡牌属性编辑面板:尺寸、网格、出血、内边距、正背面切换 + * 卡牌尺寸编辑面板:尺寸、网格、出血、内边距、形状 */ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) { const { store } = props; - return ( -
-

卡牌属性

+ const shapeOptions: { value: CardShape; label: string }[] = [ + { value: "rectangle", label: "矩形" }, + { value: "circle", label: "圆形" }, + { value: "triangle", label: "三角形" }, + { value: "hexagon", label: "六边形" }, + ]; - {/* 正面/背面切换标签页 */} -
-
- - -
-
+ return ( +
+

尺寸

- +
- +
store.actions.setPadding(Number(e.target.value))} - placeholder="内边距" + type="number" + class="w-full border border-gray-300 rounded px-2 py-1 text-sm" + value={store.state.padding} + onChange={(e) => store.actions.setPadding(Number(e.target.value))} + placeholder="内边距" />
- -
- {(['rectangle', 'circle', 'triangle', 'hexagon'] as CardShape[]).map((shape) => ( - + +
diff --git a/src/components/md-deck/index.tsx b/src/components/md-deck/index.tsx index 32c54e9..3d87f4c 100644 --- a/src/components/md-deck/index.tsx +++ b/src/components/md-deck/index.tsx @@ -6,13 +6,11 @@ import { createDeckStore } from "./hooks/deckStore"; import { registerDeck, unregisterDeck } from "./hooks/deck-registry"; import type { CardShape } from "./types"; import { DeckHeader } from "./DeckHeader"; +import { CardList } from "./CardList"; import { DeckContent } from "./DeckContent"; +import { EditorTabs } from "./EditorTabs"; import { PrintPreview } from "./PrintPreview"; -import { - DataEditorPanel, - LayerEditorPanel, - PropertiesEditorPanel, -} from "./editor-panel"; +import { DataEditorPanel } from "./editor-panel"; interface DeckProps { size?: string; @@ -139,29 +137,19 @@ customElement(
- {/* 内容区域:错误/加载/卡牌预览/空状态 */} - {/* 左侧:CSV 数据编辑 */} - {/**/} - {/* */} - {/**/} - - -
- -
+ {/* 左侧:卡牌列表导航 */} + 0 && !store.state.error}> + - + {/* 中间:内容区域(错误/加载/卡牌预览/空状态) */} +
+ +
- {/* 右侧:属性/图层编辑面板 */} + {/* 右侧:属性/图层编辑面板(标签页切换) */} -
- -
+