From 06a7b1668b44f111eca5d08bc8c2c9d41d8e617e Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Fri, 18 Sep 2026 08:26:30 +0800 Subject: chore(frontend): shadcn-svelte / Tailwind / layerchart 工具链初始化 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [变更性质] 本轮为维护性依赖与配置初始化,不改变业务页面(页面迁移见后续提交)。 [维护内容] 按 Leader 返工指令,前端 UI 组件库全量迁移 shadcn-svelte,图表渲染 从 echarts 换为 layerchart,需要一次性引入配套依赖与构建配置。 [实现方案] package.json/pnpm-lock.yaml:tailwindcss@4、@tailwindcss/vite、 tw-animate-css、shadcn-svelte、bits-ui、tailwind-variants、clsx、 tailwind-merge、@lucide/svelte、layerchart、@fontsource-variable/inter 与 @types/node;echarts 保留至页面迁移完成后移除。pnpm-workspace.yaml 声明 allowBuilds(esbuild、@tailwindcss/oxide)。components.json 采用 mira preset,别名 $lib。tsconfig 新增 $lib paths;vite.config.ts 加入 tailwindcss 插件、$lib alias(vitest 需要)与 test 环境配置。 src/lib/components/ 为 shadcn 生成的 UI 组件源码(button/input/label/ card/badge/alert/select/table/tabs/separator/chart),src/lib/utils.ts 为 cn 工具。app.css 引入 tailwind 与 shadcn CSS 变量并调整为 teal 主题(primary #0f766e、radius 0.375rem、双主题 chart-1..5),保留 legacy 选择器(banner/beat/table.data/metric 等),mobileCss.test.ts 依赖其语义断言。 [影响范围] 构建链与主题变量;后续页面提交逐步消费这些组件,npm run check/test/ build 已全绿验证。 --- .../lib/components/ui/chart/chart-container.svelte | 80 +++++++++ .../src/lib/components/ui/chart/chart-style.svelte | 37 +++++ .../lib/components/ui/chart/chart-tooltip.svelte | 180 +++++++++++++++++++++ .../src/lib/components/ui/chart/chart-utils.ts | 68 ++++++++ frontend/src/lib/components/ui/chart/index.ts | 6 + 5 files changed, 371 insertions(+) create mode 100644 frontend/src/lib/components/ui/chart/chart-container.svelte create mode 100644 frontend/src/lib/components/ui/chart/chart-style.svelte create mode 100644 frontend/src/lib/components/ui/chart/chart-tooltip.svelte create mode 100644 frontend/src/lib/components/ui/chart/chart-utils.ts create mode 100644 frontend/src/lib/components/ui/chart/index.ts (limited to 'frontend/src/lib/components/ui/chart') diff --git a/frontend/src/lib/components/ui/chart/chart-container.svelte b/frontend/src/lib/components/ui/chart/chart-container.svelte new file mode 100644 index 0000000..3687ea0 --- /dev/null +++ b/frontend/src/lib/components/ui/chart/chart-container.svelte @@ -0,0 +1,80 @@ + + +
+ + {@render children?.()} +
diff --git a/frontend/src/lib/components/ui/chart/chart-style.svelte b/frontend/src/lib/components/ui/chart/chart-style.svelte new file mode 100644 index 0000000..74c2b8a --- /dev/null +++ b/frontend/src/lib/components/ui/chart/chart-style.svelte @@ -0,0 +1,37 @@ + + +{#if themeContents} + {#key id} + + {themeContents} + + {/key} +{/if} diff --git a/frontend/src/lib/components/ui/chart/chart-tooltip.svelte b/frontend/src/lib/components/ui/chart/chart-tooltip.svelte new file mode 100644 index 0000000..8811436 --- /dev/null +++ b/frontend/src/lib/components/ui/chart/chart-tooltip.svelte @@ -0,0 +1,180 @@ + + +{#snippet TooltipLabel()} + {#if formattedLabel} +
+ {#if typeof formattedLabel === "function"} + {@render formattedLabel()} + {:else} + {formattedLabel} + {/if} +
+ {/if} +{/snippet} + + +
+ {#if !nestLabel} + {@render TooltipLabel()} + {/if} +
+ {#each visibleSeries as item, i (item.key + i)} + {@const key = `${nameKey || item.key || item.label || "value"}`} + {@const itemConfig = getPayloadConfigFromPayload( + chart.config, + item, + key, + chartCtx.tooltip.data + )} + {@const indicatorColor = color || item.config?.color || item.color} +
svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", + indicator === "dot" && "items-center" + )} + > + {#if formatter && item.value !== undefined && item.label} + {@render formatter({ + value: item.value, + name: item.label, + item, + index: i, + payload: visibleSeries, + })} + {:else} + {#if itemConfig?.icon} + + {:else if !hideIndicator} +
+ {/if} +
+
+ {#if nestLabel} + {@render TooltipLabel()} + {/if} + + {itemConfig?.label || item.label} + +
+ {#if item.value !== undefined} + + {item.value.toLocaleString()} + + {/if} +
+ {/if} +
+ {/each} +
+
+
diff --git a/frontend/src/lib/components/ui/chart/chart-utils.ts b/frontend/src/lib/components/ui/chart/chart-utils.ts new file mode 100644 index 0000000..6e1f000 --- /dev/null +++ b/frontend/src/lib/components/ui/chart/chart-utils.ts @@ -0,0 +1,68 @@ +import { getContext, setContext, type Component, type Snippet } from "svelte"; +import type { Tooltip } from "layerchart"; + +export const THEMES = { light: "", dark: ".dark" } as const; + +export type ChartConfig = { + [k in string]: { + label?: string; + icon?: Component; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); +}; + +export type ExtractSnippetParams = T extends Snippet<[infer P]> ? P : never; + +export type TooltipPayload = Tooltip.TooltipSeries; + +// Helper to extract item config from a payload. +export function getPayloadConfigFromPayload( + config: ChartConfig, + payload: TooltipPayload, + key: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data?: Record | null +) { + if (typeof payload !== "object" || payload === null) return undefined; + + const payloadConfig = + "config" in payload && typeof payload.config === "object" && payload.config !== null + ? payload.config + : undefined; + + let configLabelKey: string = key; + + if (payload.key === key) { + configLabelKey = payload.key; + } else if (payload.label === key) { + configLabelKey = payload.label; + } else if (key in payload && typeof payload[key as keyof typeof payload] === "string") { + configLabelKey = payload[key as keyof typeof payload] as string; + } else if ( + payloadConfig !== undefined && + key in payloadConfig && + typeof payloadConfig[key as keyof typeof payloadConfig] === "string" + ) { + configLabelKey = payloadConfig[key as keyof typeof payloadConfig] as string; + } else if (data != null && key in data && typeof data[key] === "string") { + configLabelKey = data[key] as string; + } + + return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]; +} + +type ChartContextValue = { + config: ChartConfig; +}; + +const chartContextKey = Symbol("chart-context"); + +export function setChartContext(value: ChartContextValue) { + return setContext(chartContextKey, value); +} + +export function useChart() { + return getContext(chartContextKey); +} diff --git a/frontend/src/lib/components/ui/chart/index.ts b/frontend/src/lib/components/ui/chart/index.ts new file mode 100644 index 0000000..f22375e --- /dev/null +++ b/frontend/src/lib/components/ui/chart/index.ts @@ -0,0 +1,6 @@ +import ChartContainer from "./chart-container.svelte"; +import ChartTooltip from "./chart-tooltip.svelte"; + +export { getPayloadConfigFromPayload, type ChartConfig } from "./chart-utils.js"; + +export { ChartContainer, ChartTooltip, ChartContainer as Container, ChartTooltip as Tooltip }; -- cgit v1.2.3