feat(ui): 添加克制页面动效
This commit is contained in:
@@ -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<typeof motion.header>) {
|
||||||
|
const { shouldReduceMotion } = useMotionConfig()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.header
|
||||||
|
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5, delay, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.header>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MotionSection({
|
||||||
|
children,
|
||||||
|
delay = 0,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: { children: ReactNode; delay?: number } & ComponentPropsWithoutRef<typeof motion.section>) {
|
||||||
|
const { shouldReduceMotion } = useMotionConfig()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.section
|
||||||
|
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5, delay, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MotionDiv({
|
||||||
|
children,
|
||||||
|
delay = 0,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: { children: ReactNode; delay?: number } & ComponentPropsWithoutRef<typeof motion.div>) {
|
||||||
|
const { shouldReduceMotion } = useMotionConfig()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5, delay, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MotionCardArticle({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: { children: ReactNode } & ComponentPropsWithoutRef<typeof motion.article>) {
|
||||||
|
const { shouldReduceMotion } = useMotionConfig()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.article
|
||||||
|
whileHover={shouldReduceMotion ? {} : { y: -2 }}
|
||||||
|
transition={{ duration: 0.2, ease: 'easeOut' }}
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.article>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MotionCardDiv({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: { children: ReactNode } & ComponentPropsWithoutRef<typeof motion.div>) {
|
||||||
|
const { shouldReduceMotion } = useMotionConfig()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
whileHover={shouldReduceMotion ? {} : { y: -2 }}
|
||||||
|
transition={{ duration: 0.2, ease: 'easeOut' }}
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MotionTableRow({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: { children: ReactNode } & ComponentPropsWithoutRef<typeof motion.tr>) {
|
||||||
|
return (
|
||||||
|
<motion.tr
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
+13
-12
@@ -5,6 +5,7 @@ import { ArrowLeft, Battery, BatteryCharging, BatteryLow, FilterX, Search, Zap }
|
|||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { orpc } from '@/client/orpc'
|
import { orpc } from '@/client/orpc'
|
||||||
|
import { MotionCardDiv, MotionHeader, MotionSection, MotionTableRow } from '@/components/motion'
|
||||||
import { Badge, Button, Card, Input, SectionTitle, Select } from '@/components/ui'
|
import { Badge, Button, Card, Input, SectionTitle, Select } from '@/components/ui'
|
||||||
import type { BatteryInfo, BatteryListSort, PowerStatus } from '@/domain/battery'
|
import type { BatteryInfo, BatteryListSort, PowerStatus } from '@/domain/battery'
|
||||||
import { BATTERY_LIST_SORT, BATTERY_LIST_SORT_VALUES, POWER_STATUS, POWER_STATUS_VALUES } 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() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative z-10 mx-auto max-w-7xl px-6 py-8">
|
<div className="relative z-10 mx-auto max-w-7xl px-6 py-8">
|
||||||
<header>
|
<MotionHeader>
|
||||||
<div className="flex flex-col gap-5 lg:flex-row lg:items-end lg:justify-between">
|
<div className="flex flex-col gap-5 lg:flex-row lg:items-end lg:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<Badge variant="info" className="mb-4">
|
<Badge variant="info" className="mb-4">
|
||||||
@@ -265,28 +266,28 @@ function BatteriesPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<dl className="mt-8 grid grid-cols-1 gap-4 sm:grid-cols-3">
|
<dl className="mt-8 grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
<Card className="p-5">
|
<MotionCardDiv className="rounded-2xl border border-white/[0.08] bg-zinc-950/60 shadow-2xl shadow-black/20 p-5">
|
||||||
<dt className="flex items-center gap-2 text-xs font-medium text-zinc-500">
|
<dt className="flex items-center gap-2 text-xs font-medium text-zinc-500">
|
||||||
<Battery className="size-4 text-zinc-400" /> 设备总数
|
<Battery className="size-4 text-zinc-400" /> 设备总数
|
||||||
</dt>
|
</dt>
|
||||||
<dd className="mt-3 text-3xl font-light tabular-nums text-white">{data?.total ?? '-'}</dd>
|
<dd className="mt-3 text-3xl font-light tabular-nums text-white">{data?.total ?? '-'}</dd>
|
||||||
</Card>
|
</MotionCardDiv>
|
||||||
<Card className="p-5">
|
<MotionCardDiv className="rounded-2xl border border-white/[0.08] bg-zinc-950/60 shadow-2xl shadow-black/20 p-5">
|
||||||
<dt className="flex items-center gap-2 text-xs font-medium text-zinc-500">
|
<dt className="flex items-center gap-2 text-xs font-medium text-zinc-500">
|
||||||
<BatteryLow className="size-4 text-red-400" /> 低电量
|
<BatteryLow className="size-4 text-red-400" /> 低电量
|
||||||
</dt>
|
</dt>
|
||||||
<dd className="mt-3 text-3xl font-light tabular-nums text-red-300">{data?.lowPower ?? '-'}</dd>
|
<dd className="mt-3 text-3xl font-light tabular-nums text-red-300">{data?.lowPower ?? '-'}</dd>
|
||||||
</Card>
|
</MotionCardDiv>
|
||||||
<Card className="p-5">
|
<MotionCardDiv className="rounded-2xl border border-white/[0.08] bg-zinc-950/60 shadow-2xl shadow-black/20 p-5">
|
||||||
<dt className="flex items-center gap-2 text-xs font-medium text-zinc-500">
|
<dt className="flex items-center gap-2 text-xs font-medium text-zinc-500">
|
||||||
<BatteryCharging className="size-4 text-teal-300" /> 充电中
|
<BatteryCharging className="size-4 text-teal-300" /> 充电中
|
||||||
</dt>
|
</dt>
|
||||||
<dd className="mt-3 text-3xl font-light tabular-nums text-teal-300">{data?.charging ?? '-'}</dd>
|
<dd className="mt-3 text-3xl font-light tabular-nums text-teal-300">{data?.charging ?? '-'}</dd>
|
||||||
</Card>
|
</MotionCardDiv>
|
||||||
</dl>
|
</dl>
|
||||||
</header>
|
</MotionHeader>
|
||||||
|
|
||||||
<section className="mt-10">
|
<MotionSection delay={0.1} className="mt-10">
|
||||||
<Card className="mb-6 p-5">
|
<Card className="mb-6 p-5">
|
||||||
<div className="mb-5 flex flex-wrap items-center justify-between gap-3">
|
<div className="mb-5 flex flex-wrap items-center justify-between gap-3">
|
||||||
<SectionTitle
|
<SectionTitle
|
||||||
@@ -447,13 +448,13 @@ function BatteriesPage() {
|
|||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
table.getRowModel().rows.map((row) => (
|
table.getRowModel().rows.map((row) => (
|
||||||
<tr key={row.id} className="transition-colors hover:bg-white/[0.02]">
|
<MotionTableRow key={row.id} className="transition-colors hover:bg-white/[0.02]">
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<td key={cell.id} className="px-6 py-4 whitespace-nowrap">
|
<td key={cell.id} className="px-6 py-4 whitespace-nowrap">
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</MotionTableRow>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -479,7 +480,7 @@ function BatteriesPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</MotionSection>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
)
|
)
|
||||||
|
|||||||
+25
-24
@@ -14,6 +14,7 @@ import {
|
|||||||
} from 'recharts'
|
} from 'recharts'
|
||||||
import type { NameType, ValueType } from 'recharts/types/component/DefaultTooltipContent'
|
import type { NameType, ValueType } from 'recharts/types/component/DefaultTooltipContent'
|
||||||
import { orpc } from '@/client/orpc'
|
import { orpc } from '@/client/orpc'
|
||||||
|
import { MotionCardArticle, MotionCardDiv, MotionHeader, MotionSection } from '@/components/motion'
|
||||||
import { Badge, Card, SectionTitle } from '@/components/ui'
|
import { Badge, Card, SectionTitle } from '@/components/ui'
|
||||||
import type { DashboardSnapshot, DeviceStatus } from '@/domain/battery'
|
import type { DashboardSnapshot, DeviceStatus } from '@/domain/battery'
|
||||||
import { BATTERY_LIST_SORT, DEVICE_STATUS } from '@/domain/battery'
|
import { BATTERY_LIST_SORT, DEVICE_STATUS } from '@/domain/battery'
|
||||||
@@ -147,7 +148,7 @@ function Dashboard() {
|
|||||||
|
|
||||||
<div className="relative z-10 mx-auto max-w-[1400px] px-6 pb-24 pt-12 lg:px-12">
|
<div className="relative z-10 mx-auto max-w-[1400px] px-6 pb-24 pt-12 lg:px-12">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="animate-fade-up mb-12 flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
|
<MotionHeader className="mb-12 flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
|
||||||
<div className="max-w-3xl">
|
<div className="max-w-3xl">
|
||||||
<Badge variant="info" className="mb-4">
|
<Badge variant="info" className="mb-4">
|
||||||
<Activity className="size-3.5" /> 实时数据与健康预测
|
<Activity className="size-3.5" /> 实时数据与健康预测
|
||||||
@@ -165,18 +166,18 @@ function Dashboard() {
|
|||||||
设备电池实时状态 <ArrowRight className="size-3" />
|
设备电池实时状态 <ArrowRight className="size-3" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</MotionHeader>
|
||||||
|
|
||||||
{/* Executive Summary */}
|
{/* Executive Summary */}
|
||||||
<section className="animate-fade-up delay-100 mb-12 rounded-xl border border-teal-900/30 bg-teal-950/10 p-6">
|
<MotionSection delay={0.1} className="mb-12 rounded-xl border border-teal-900/30 bg-teal-950/10 p-6">
|
||||||
<h2 className="mb-3 text-sm font-medium text-teal-400">执行摘要</h2>
|
<h2 className="mb-3 text-sm font-medium text-teal-400">执行摘要</h2>
|
||||||
<p className="text-base leading-relaxed text-[#A1A1AA]">{executiveSummary}</p>
|
<p className="text-base leading-relaxed text-[#A1A1AA]">{executiveSummary}</p>
|
||||||
</section>
|
</MotionSection>
|
||||||
|
|
||||||
{/* Primary KPI Row */}
|
{/* Primary KPI Row */}
|
||||||
<section className="animate-fade-up delay-200 mb-12 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-5">
|
<MotionSection delay={0.2} className="mb-12 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-5">
|
||||||
{/* Hero KPI */}
|
{/* Hero KPI */}
|
||||||
<article className="relative overflow-hidden rounded-2xl border border-white/10 bg-white/[0.03] p-8 lg:col-span-2">
|
<MotionCardArticle className="relative overflow-hidden rounded-2xl border border-white/10 bg-white/[0.03] p-8 lg:col-span-2">
|
||||||
<div className="absolute inset-x-0 top-0 h-1 bg-gradient-to-r from-teal-500/50 to-transparent" />
|
<div className="absolute inset-x-0 top-0 h-1 bg-gradient-to-r from-teal-500/50 to-transparent" />
|
||||||
<p className="text-sm font-medium text-[#A1A1AA]">当前平均健康度</p>
|
<p className="text-sm font-medium text-[#A1A1AA]">当前平均健康度</p>
|
||||||
<div className="mt-4 flex items-baseline gap-2">
|
<div className="mt-4 flex items-baseline gap-2">
|
||||||
@@ -191,10 +192,10 @@ function Dashboard() {
|
|||||||
<span className="text-[#71717A]">|</span>
|
<span className="text-[#71717A]">|</span>
|
||||||
<span className="text-[#A1A1AA]">共 {totalDevices} 台设备</span>
|
<span className="text-[#A1A1AA]">共 {totalDevices} 台设备</span>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</MotionCardArticle>
|
||||||
|
|
||||||
{/* Regular KPIs */}
|
{/* Regular KPIs */}
|
||||||
<article className="rounded-2xl border border-white/[0.06] bg-white/[0.02] p-6 transition-colors hover:border-white/10">
|
<MotionCardArticle className="rounded-2xl border border-white/[0.06] bg-white/[0.02] p-6 transition-colors hover:border-white/10">
|
||||||
<p className="text-sm text-[#A1A1AA]">30 天预测均值</p>
|
<p className="text-sm text-[#A1A1AA]">30 天预测均值</p>
|
||||||
<div className="mt-3 flex items-baseline gap-1">
|
<div className="mt-3 flex items-baseline gap-1">
|
||||||
<h2 className="text-4xl font-light tabular-nums text-white">{formatPercent(avgSoh30d)}</h2>
|
<h2 className="text-4xl font-light tabular-nums text-white">{formatPercent(avgSoh30d)}</h2>
|
||||||
@@ -204,9 +205,9 @@ function Dashboard() {
|
|||||||
<span className="text-red-400">↘</span>
|
<span className="text-red-400">↘</span>
|
||||||
<span className="tabular-nums text-[#A1A1AA]">{formatDelta(avgSoh, avgSoh30d)}</span>
|
<span className="tabular-nums text-[#A1A1AA]">{formatDelta(avgSoh, avgSoh30d)}</span>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</MotionCardArticle>
|
||||||
|
|
||||||
<article className="rounded-2xl border border-white/[0.06] bg-white/[0.02] p-6 transition-colors hover:border-white/10">
|
<MotionCardArticle className="rounded-2xl border border-white/[0.06] bg-white/[0.02] p-6 transition-colors hover:border-white/10">
|
||||||
<p className="text-sm text-[#A1A1AA]">90 天预测均值</p>
|
<p className="text-sm text-[#A1A1AA]">90 天预测均值</p>
|
||||||
<div className="mt-3 flex items-baseline gap-1">
|
<div className="mt-3 flex items-baseline gap-1">
|
||||||
<h2 className="text-4xl font-light tabular-nums text-white">{formatPercent(avgSoh90d)}</h2>
|
<h2 className="text-4xl font-light tabular-nums text-white">{formatPercent(avgSoh90d)}</h2>
|
||||||
@@ -216,9 +217,9 @@ function Dashboard() {
|
|||||||
<span className="text-red-400">↘</span>
|
<span className="text-red-400">↘</span>
|
||||||
<span className="tabular-nums text-[#A1A1AA]">{formatDelta(avgSoh, avgSoh90d)}</span>
|
<span className="tabular-nums text-[#A1A1AA]">{formatDelta(avgSoh, avgSoh90d)}</span>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</MotionCardArticle>
|
||||||
|
|
||||||
<article className="rounded-2xl border border-white/[0.06] bg-white/[0.02] p-6 transition-colors hover:border-white/10">
|
<MotionCardArticle className="rounded-2xl border border-white/[0.06] bg-white/[0.02] p-6 transition-colors hover:border-white/10">
|
||||||
<p className="text-sm text-[#A1A1AA]">高风险设备</p>
|
<p className="text-sm text-[#A1A1AA]">高风险设备</p>
|
||||||
<div className="mt-3 flex items-baseline gap-1">
|
<div className="mt-3 flex items-baseline gap-1">
|
||||||
<h2 className="text-4xl font-light tabular-nums text-white">{warningCount}</h2>
|
<h2 className="text-4xl font-light tabular-nums text-white">{warningCount}</h2>
|
||||||
@@ -230,14 +231,14 @@ function Dashboard() {
|
|||||||
占比 {totalDevices > 0 ? ((warningCount / totalDevices) * 100).toFixed(1) : 0}%
|
占比 {totalDevices > 0 ? ((warningCount / totalDevices) * 100).toFixed(1) : 0}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</MotionCardArticle>
|
||||||
</section>
|
</MotionSection>
|
||||||
|
|
||||||
{/* Divider */}
|
{/* Divider */}
|
||||||
<hr className="my-12 border-white/5" />
|
<hr className="my-12 border-white/5" />
|
||||||
|
|
||||||
{/* Health trend chart */}
|
{/* Health trend chart */}
|
||||||
<section className="animate-fade-up delay-300 mb-12">
|
<MotionSection delay={0.3} className="mb-12">
|
||||||
<Card className="p-8">
|
<Card className="p-8">
|
||||||
<header className="mb-8 flex flex-wrap items-end justify-between gap-4">
|
<header className="mb-8 flex flex-wrap items-end justify-between gap-4">
|
||||||
<SectionTitle
|
<SectionTitle
|
||||||
@@ -359,10 +360,10 @@ function Dashboard() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</section>
|
</MotionSection>
|
||||||
|
|
||||||
{/* Two-column grid */}
|
{/* Two-column grid */}
|
||||||
<section className="animate-fade-up delay-400 mb-12 grid grid-cols-1 gap-8 lg:grid-cols-2">
|
<MotionSection delay={0.4} className="mb-12 grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||||
{/* Left Column */}
|
{/* Left Column */}
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{/* Risk Distribution */}
|
{/* Risk Distribution */}
|
||||||
@@ -504,13 +505,13 @@ function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</MotionSection>
|
||||||
|
|
||||||
{/* Divider */}
|
{/* Divider */}
|
||||||
<hr className="my-12 border-white/5" />
|
<hr className="my-12 border-white/5" />
|
||||||
|
|
||||||
{/* Device Table */}
|
{/* Device Table */}
|
||||||
<section className="animate-fade-up delay-500 mb-12">
|
<MotionSection delay={0.5} className="mb-12">
|
||||||
<div className="mb-6 flex items-end justify-between">
|
<div className="mb-6 flex items-end justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-medium text-white">重点关注设备</h3>
|
<h3 className="text-xl font-medium text-white">重点关注设备</h3>
|
||||||
@@ -594,17 +595,17 @@ function Dashboard() {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</section>
|
</MotionSection>
|
||||||
|
|
||||||
{/* Bottom Row */}
|
{/* Bottom Row */}
|
||||||
<section className="animate-fade-up delay-500 grid grid-cols-1 gap-8 lg:grid-cols-3">
|
<MotionSection delay={0.5} className="grid grid-cols-1 gap-8 lg:grid-cols-3">
|
||||||
{/* Strategy Cards */}
|
{/* Strategy Cards */}
|
||||||
<div className="lg:col-span-2">
|
<div className="lg:col-span-2">
|
||||||
<h3 className="mb-6 text-lg font-medium text-white">行动建议</h3>
|
<h3 className="mb-6 text-lg font-medium text-white">行动建议</h3>
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
{strategies.length > 0 ? (
|
{strategies.length > 0 ? (
|
||||||
strategies.map((item, index) => (
|
strategies.map((item, index) => (
|
||||||
<div
|
<MotionCardDiv
|
||||||
key={item.name}
|
key={item.name}
|
||||||
className="relative overflow-hidden rounded-xl border border-white/[0.06] bg-white/[0.02] p-5"
|
className="relative overflow-hidden rounded-xl border border-white/[0.06] bg-white/[0.02] p-5"
|
||||||
>
|
>
|
||||||
@@ -617,7 +618,7 @@ function Dashboard() {
|
|||||||
<span>范围: {item.scope}</span>
|
<span>范围: {item.scope}</span>
|
||||||
<span>时效: {item.eta}</span>
|
<span>时效: {item.eta}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</MotionCardDiv>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="text-sm text-[#71717A] col-span-2">暂无策略建议</div>
|
<div className="text-sm text-[#71717A] col-span-2">暂无策略建议</div>
|
||||||
@@ -649,7 +650,7 @@ function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</MotionSection>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user