diff --git a/src/components/motion.tsx b/src/components/motion.tsx new file mode 100644 index 0000000..08496e2 --- /dev/null +++ b/src/components/motion.tsx @@ -0,0 +1,126 @@ +import { motion, useReducedMotion } from 'motion/react' +import type { ComponentPropsWithoutRef, ReactNode } from 'react' + +export function useMotionConfig() { + const shouldReduceMotion = useReducedMotion() + return { shouldReduceMotion } +} + +export function MotionHeader({ + children, + delay = 0, + className, + ...props +}: { children: ReactNode; delay?: number } & ComponentPropsWithoutRef) { + const { shouldReduceMotion } = useMotionConfig() + + return ( + + {children} + + ) +} + +export function MotionSection({ + children, + delay = 0, + className, + ...props +}: { children: ReactNode; delay?: number } & ComponentPropsWithoutRef) { + const { shouldReduceMotion } = useMotionConfig() + + return ( + + {children} + + ) +} + +export function MotionDiv({ + children, + delay = 0, + className, + ...props +}: { children: ReactNode; delay?: number } & ComponentPropsWithoutRef) { + const { shouldReduceMotion } = useMotionConfig() + + return ( + + {children} + + ) +} + +export function MotionCardArticle({ + children, + className, + ...props +}: { children: ReactNode } & ComponentPropsWithoutRef) { + const { shouldReduceMotion } = useMotionConfig() + + return ( + + {children} + + ) +} + +export function MotionCardDiv({ + children, + className, + ...props +}: { children: ReactNode } & ComponentPropsWithoutRef) { + const { shouldReduceMotion } = useMotionConfig() + + return ( + + {children} + + ) +} + +export function MotionTableRow({ + children, + className, + ...props +}: { children: ReactNode } & ComponentPropsWithoutRef) { + return ( + + {children} + + ) +} diff --git a/src/routes/batteries.tsx b/src/routes/batteries.tsx index cfadf39..21cd835 100644 --- a/src/routes/batteries.tsx +++ b/src/routes/batteries.tsx @@ -5,6 +5,7 @@ import { ArrowLeft, Battery, BatteryCharging, BatteryLow, FilterX, Search, Zap } import { useEffect, useMemo, useState } from 'react' import { z } from 'zod' import { orpc } from '@/client/orpc' +import { MotionCardDiv, MotionHeader, MotionSection, MotionTableRow } from '@/components/motion' import { Badge, Button, Card, Input, SectionTitle, Select } from '@/components/ui' import type { BatteryInfo, BatteryListSort, PowerStatus } from '@/domain/battery' import { BATTERY_LIST_SORT, BATTERY_LIST_SORT_VALUES, POWER_STATUS, POWER_STATUS_VALUES } from '@/domain/battery' @@ -240,7 +241,7 @@ function BatteriesPage() {
-
+
@@ -265,28 +266,28 @@ function BatteriesPage() {
- +
设备总数
{data?.total ?? '-'}
-
- + +
低电量
{data?.lowPower ?? '-'}
-
- + +
充电中
{data?.charging ?? '-'}
-
+
-
+ -
+
) : ( table.getRowModel().rows.map((row) => ( - + {row.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} - + )) )} @@ -479,7 +480,7 @@ function BatteriesPage() {
- + ) diff --git a/src/routes/index.tsx b/src/routes/index.tsx index b6f71b6..3934965 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -14,6 +14,7 @@ import { } from 'recharts' import type { NameType, ValueType } from 'recharts/types/component/DefaultTooltipContent' import { orpc } from '@/client/orpc' +import { MotionCardArticle, MotionCardDiv, MotionHeader, MotionSection } from '@/components/motion' import { Badge, Card, SectionTitle } from '@/components/ui' import type { DashboardSnapshot, DeviceStatus } from '@/domain/battery' import { BATTERY_LIST_SORT, DEVICE_STATUS } from '@/domain/battery' @@ -147,7 +148,7 @@ function Dashboard() {
{/* Header */} -
+
实时数据与健康预测 @@ -165,18 +166,18 @@ function Dashboard() { 设备电池实时状态
-
+ {/* Executive Summary */} -
+

执行摘要

{executiveSummary}

-
+ {/* Primary KPI Row */} -
+ {/* Hero KPI */} -
+

当前平均健康度

@@ -191,10 +192,10 @@ function Dashboard() { | 共 {totalDevices} 台设备
-
+ {/* Regular KPIs */} -
+

30 天预测均值

{formatPercent(avgSoh30d)}

@@ -204,9 +205,9 @@ function Dashboard() { {formatDelta(avgSoh, avgSoh30d)}
-
+ -
+

90 天预测均值

{formatPercent(avgSoh90d)}

@@ -216,9 +217,9 @@ function Dashboard() { {formatDelta(avgSoh, avgSoh90d)}
-
+ -
+

高风险设备

{warningCount}

@@ -230,14 +231,14 @@ function Dashboard() { 占比 {totalDevices > 0 ? ((warningCount / totalDevices) * 100).toFixed(1) : 0}%
-
-
+ + {/* Divider */}
{/* Health trend chart */} -
+
-
+ {/* Two-column grid */} -
+ {/* Left Column */}
{/* Risk Distribution */} @@ -504,13 +505,13 @@ function Dashboard() {
- + {/* Divider */}
{/* Device Table */} -
+

重点关注设备

@@ -594,17 +595,17 @@ function Dashboard() {
-
+ {/* Bottom Row */} -
+ {/* Strategy Cards */}

行动建议

{strategies.length > 0 ? ( strategies.map((item, index) => ( -
@@ -617,7 +618,7 @@ function Dashboard() { 范围: {item.scope} 时效: {item.eta}
-
+ )) ) : (
暂无策略建议
@@ -649,7 +650,7 @@ function Dashboard() {
-
+ )