diff --git a/src/modules/energy/ElectricDaily.tsx b/src/modules/energy/ElectricDaily.tsx new file mode 100644 index 0000000..c861020 --- /dev/null +++ b/src/modules/energy/ElectricDaily.tsx @@ -0,0 +1,111 @@ +import { useMemo, useState } from 'react'; +import { ChevronRight } from 'lucide-react'; +import { motion, AnimatePresence } from 'motion/react'; +import TrendBadge from './TrendBadge'; +import { ELECTRIC_MONTHLY } from './mock'; +import type { CustomerType } from './types'; + +export default function ElectricDaily() { + const [customer, setCustomer] = useState('external'); + const [openMonths, setOpenMonths] = useState>(new Set([ELECTRIC_MONTHLY[0]?.month])); + + const months = useMemo(() => { + // mock 暂不区分客户类型,customer 切换不影响数据;保留 UI 切换以与 BI 一致 + void customer; + return ELECTRIC_MONTHLY; + }, [customer]); + + const toggleMonth = (m: string) => setOpenMonths(prev => { + const next = new Set(prev); + next.has(m) ? next.delete(m) : next.add(m); + return next; + }); + + return ( +
+ {/* 客户类型 */} +
+ {(['external', 'lingniu'] as const).map(c => ( + + ))} +
+ + {/* 月份分组表 */} +
+
+ 月份 / 日期 + 充电电量 + + 充电费用(元) + 环比 +
+ {months.map(m => { + const open = openMonths.has(m.month); + return ( +
+ + + {open && ( + + {m.rows.map(d => { + const isAbnormal = Math.abs(d.chainPct) >= 0.3; + const abnormalBg = isAbnormal + ? d.chainPct > 0 ? 'bg-emerald-50/40' : 'bg-red-50/40' + : 'bg-slate-50/50'; + return ( +
+ {d.date.slice(5)} + + {d.kwh.toLocaleString('zh-CN', { maximumFractionDigits: 2 })} + + + {d.fee.toLocaleString('zh-CN', { maximumFractionDigits: 2 })} + + +
+ ); + })} +
+ )} +
+
+ ); + })} +
+
+ ); +} diff --git a/src/modules/energy/ElectricOverview.tsx b/src/modules/energy/ElectricOverview.tsx new file mode 100644 index 0000000..e2fc615 --- /dev/null +++ b/src/modules/energy/ElectricOverview.tsx @@ -0,0 +1,92 @@ +import { useMemo } from 'react'; +import { Wallet, BatteryCharging, CalendarClock } from 'lucide-react'; +import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer, Cell, Tooltip } from 'recharts'; +import TrendBadge from './TrendBadge'; +import { ELECTRIC_KPI, ELECTRIC_MONTHLY } from './mock'; + +function fmtYuan(yuan: number) { + return `¥${yuan.toLocaleString('zh-CN', { maximumFractionDigits: 2 })}`; +} +function fmtKwh(kwh: number) { + return `${kwh.toLocaleString('zh-CN', { maximumFractionDigits: 2 })} 度`; +} + +export default function ElectricOverview() { + const k = ELECTRIC_KPI; + + // 本月每日数据(按日期升序,便于柱图按时间从左到右展示) + const trendData = useMemo(() => { + const first = ELECTRIC_MONTHLY[0]; + if (!first) return []; + return [...first.rows].sort((a, b) => a.date.localeCompare(b.date)); + }, []); + + return ( +
+ {/* 横向 mini KPI 头 */} +
+
+
+ 累计 +
+
{fmtYuan(k.totalFee)}
+
{fmtKwh(k.totalKwh)}
+
+
+
+ 本月 +
+
{fmtYuan(k.monthFee)}
+
{fmtKwh(k.monthKwh)}
+
+
+
+ 今日 +
+
{fmtYuan(k.todayFee)}
+
{fmtKwh(k.todayKwh)}
+ +
+
+ + {/* 本月每日充电柱图 */} +
+
+ 本月每日充电 + 单位 元 +
+ + + v.slice(5)} + tick={{ fontSize: 10, fill: '#94a3b8' }} + tickLine={false} + axisLine={false} + interval="preserveStartEnd" + minTickGap={8} + /> + + [`¥${Number(v ?? 0).toLocaleString('zh-CN', { maximumFractionDigits: 2 })}`, '充电费用']} + labelFormatter={(d) => `日期 ${d}`} + contentStyle={{ borderRadius: 12, fontSize: 12 }} + cursor={{ fill: 'rgba(59, 130, 246, 0.06)' }} + /> + + {trendData.map((_, i) => ( + + ))} + + + + + + + + + +
+
+ ); +} diff --git a/src/modules/energy/ElectricView.tsx b/src/modules/energy/ElectricView.tsx index 6efa802..e3732c0 100644 --- a/src/modules/energy/ElectricView.tsx +++ b/src/modules/energy/ElectricView.tsx @@ -1,199 +1,40 @@ -import { useMemo, useState } from 'react'; -import { ChevronRight, Wallet, BatteryCharging, CalendarClock } from 'lucide-react'; -import { motion, AnimatePresence } from 'motion/react'; -import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer, Cell, Tooltip } from 'recharts'; -import TrendBadge from './TrendBadge'; -import { ELECTRIC_KPI, ELECTRIC_MONTHLY } from './mock'; -import type { CustomerType } from './types'; +import { useState } from 'react'; +import { LayoutDashboard, CalendarDays } from 'lucide-react'; +import ElectricOverview from './ElectricOverview'; +import ElectricDaily from './ElectricDaily'; -function fmtYuan(yuan: number) { - return `¥${yuan.toLocaleString('zh-CN', { maximumFractionDigits: 2 })}`; -} -function fmtKwh(kwh: number) { - return `${kwh.toLocaleString('zh-CN', { maximumFractionDigits: 2 })} 度`; -} +type SubTab = 'daily' | 'overview'; + +const SUB_TABS: Array<{ id: SubTab; label: string; icon: typeof LayoutDashboard }> = [ + { id: 'daily', label: '每日', icon: CalendarDays }, + { id: 'overview', label: '总览', icon: LayoutDashboard }, +]; export default function ElectricView() { - const [customer, setCustomer] = useState('external'); - const [openMonths, setOpenMonths] = useState>(new Set([ELECTRIC_MONTHLY[0]?.month])); - - const months = useMemo(() => { - // mock 暂不区分客户类型,customer 切换不影响数据;保留 UI 切换以与 BI 一致 - void customer; - return ELECTRIC_MONTHLY; - }, [customer]); - - // 本月每日数据(按日期升序,便于柱图按时间从左到右展示) - const trendData = useMemo(() => { - const first = ELECTRIC_MONTHLY[0]; - if (!first) return []; - return [...first.rows].sort((a, b) => a.date.localeCompare(b.date)); - }, []); - - const k = ELECTRIC_KPI; - - const toggleMonth = (m: string) => setOpenMonths(prev => { - const next = new Set(prev); - next.has(m) ? next.delete(m) : next.add(m); - return next; - }); - + const [sub, setSub] = useState('daily'); return ( -
-
- 龙王路停车场充电站,期初 2025-01-01,手工导入每日更新 -
- - {/* 横向 mini KPI 头 */} -
-
-
- 累计 -
-
{fmtYuan(k.totalFee)}
-
{fmtKwh(k.totalKwh)}
-
-
-
- 本月 -
-
{fmtYuan(k.monthFee)}
-
{fmtKwh(k.monthKwh)}
-
-
-
- 今日 -
-
{fmtYuan(k.todayFee)}
-
- {fmtKwh(k.todayKwh)} - -
-
-
- - {/* 本月每日充电柱图 */} -
-
- 本月每日充电 - 单位 元 -
- - - v.slice(5)} - tick={{ fontSize: 10, fill: '#94a3b8' }} - tickLine={false} - axisLine={false} - interval="preserveStartEnd" - minTickGap={8} - /> - - [`¥${Number(v ?? 0).toLocaleString('zh-CN', { maximumFractionDigits: 2 })}`, '充电费用']} - labelFormatter={(d) => `日期 ${d}`} - contentStyle={{ borderRadius: 12, fontSize: 12 }} - cursor={{ fill: 'rgba(59, 130, 246, 0.06)' }} - /> - - {trendData.map((_, i) => ( - - ))} - - - - - - - - - -
- - {/* 客户类型 */} -
- {(['external', 'lingniu'] as const).map(c => ( - - ))} -
- - {/* 月份分组表 */} -
-
- 月份 / 日期 - 充电电量 - - 充电费用(元) - 环比 -
- {months.map(m => { - const open = openMonths.has(m.month); + <> +
+ {SUB_TABS.map(({ id, label, icon: Icon }) => { + const active = sub === id; return ( -
- - - {open && ( - - {m.rows.map(d => { - const isAbnormal = Math.abs(d.chainPct) >= 0.3; - const abnormalBg = isAbnormal - ? d.chainPct > 0 ? 'bg-emerald-50/40' : 'bg-red-50/40' - : 'bg-slate-50/50'; - return ( -
- {d.date.slice(5)} - - {d.kwh.toLocaleString('zh-CN', { maximumFractionDigits: 2 })} - - - {d.fee.toLocaleString('zh-CN', { maximumFractionDigits: 2 })} - - -
- ); - })} -
- )} -
-
+ ); })}
-
+
+ 龙王路停车场充电站,期初 2025-01-01,手工导入每日更新 +
+ {sub === 'overview' ? : } + ); } diff --git a/src/modules/energy/HydrogenView.tsx b/src/modules/energy/HydrogenView.tsx index e7f52f8..07c1a92 100644 --- a/src/modules/energy/HydrogenView.tsx +++ b/src/modules/energy/HydrogenView.tsx @@ -1,36 +1,35 @@ import { useState } from 'react'; import { LayoutDashboard, CalendarDays } from 'lucide-react'; -import { motion } from 'motion/react'; import HydrogenOverview from './HydrogenOverview'; import HydrogenDaily from './HydrogenDaily'; -type SubTab = 'overview' | 'daily'; +type SubTab = 'daily' | 'overview'; + +const SUB_TABS: Array<{ id: SubTab; label: string; icon: typeof LayoutDashboard }> = [ + { id: 'daily', label: '每日', icon: CalendarDays }, + { id: 'overview', label: '总览', icon: LayoutDashboard }, +]; export default function HydrogenView() { - const [sub, setSub] = useState('overview'); + const [sub, setSub] = useState('daily'); return ( <> -
- - +
+ {SUB_TABS.map(({ id, label, icon: Icon }) => { + const active = sub === id; + return ( + + ); + })}
{sub === 'overview' ? : }