feat: Initialize AI Studio project structure
Sets up the basic project files and dependencies for an AI Studio application. Includes README, `.env.example`, `.gitignore`, basic HTML structure, metadata, `package.json`, Vite configuration, and initial React component setup. This commit provides the foundation for running and deploying the AI Studio app locally.
This commit is contained in:
9
.env.example
Normal file
9
.env.example
Normal file
@@ -0,0 +1,9 @@
|
||||
# GEMINI_API_KEY: Required for Gemini AI API calls.
|
||||
# AI Studio automatically injects this at runtime from user secrets.
|
||||
# Users configure this via the Secrets panel in the AI Studio UI.
|
||||
GEMINI_API_KEY="MY_GEMINI_API_KEY"
|
||||
|
||||
# APP_URL: The URL where this applet is hosted.
|
||||
# AI Studio automatically injects this at runtime with the Cloud Run service URL.
|
||||
# Used for self-referential links, OAuth callbacks, and API endpoints.
|
||||
APP_URL="MY_APP_URL"
|
||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
build/
|
||||
dist/
|
||||
coverage/
|
||||
.DS_Store
|
||||
*.log
|
||||
.env*
|
||||
!.env.example
|
||||
25
README.md
25
README.md
@@ -1,11 +1,20 @@
|
||||
<div align="center">
|
||||
|
||||
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
||||
|
||||
<h1>Built with AI Studio</h2>
|
||||
|
||||
<p>The fastest path from prompt to production with Gemini.</p>
|
||||
|
||||
<a href="https://aistudio.google.com/apps">Start building</a>
|
||||
|
||||
</div>
|
||||
|
||||
# Run and deploy your AI Studio app
|
||||
|
||||
This contains everything you need to run your app locally.
|
||||
|
||||
View your app in AI Studio: https://ai.studio/apps/99f30234-252e-4899-b2b5-6d6d1c336581
|
||||
|
||||
## Run Locally
|
||||
|
||||
**Prerequisites:** Node.js
|
||||
|
||||
|
||||
1. Install dependencies:
|
||||
`npm install`
|
||||
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
||||
3. Run the app:
|
||||
`npm run dev`
|
||||
|
||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>My Google AI Studio App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
6
metadata.json
Normal file
6
metadata.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "",
|
||||
"description": "",
|
||||
"requestFramePermissions": [],
|
||||
"majorCapabilities": []
|
||||
}
|
||||
4738
package-lock.json
generated
Normal file
4738
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
package.json
Normal file
37
package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "react-example",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port=3000 --host=0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "^1.29.0",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^4.21.2",
|
||||
"lucide-react": "^0.546.0",
|
||||
"motion": "^12.23.24",
|
||||
"react": "^19.0.1",
|
||||
"react-dom": "^19.0.1",
|
||||
"recharts": "^3.8.1",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"vite": "^6.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^22.14.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "~5.8.2",
|
||||
"vite": "^6.2.3"
|
||||
}
|
||||
}
|
||||
26
src/App.tsx
Normal file
26
src/App.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useState } from 'react';
|
||||
import { Dashboard } from './components/Dashboard';
|
||||
import { Sidebar } from './components/Sidebar';
|
||||
import { Header } from './components/Header';
|
||||
|
||||
export default function App() {
|
||||
const [currentView, setCurrentView] = useState('overall');
|
||||
|
||||
const viewTitles: Record<string, string> = {
|
||||
'overall': '全网精细化运营大盘',
|
||||
'efficiency': '各站点调度与效能监控',
|
||||
'users': '平台用户与资产池'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-slate-50 overflow-hidden font-sans">
|
||||
<Sidebar currentView={currentView} setCurrentView={setCurrentView} />
|
||||
<div className="flex flex-col flex-1 overflow-hidden">
|
||||
<Header title={viewTitles[currentView] || '数据分析'} />
|
||||
<main className="flex-1 overflow-y-auto p-4 md:p-6 lg:p-8">
|
||||
<Dashboard currentView={currentView} />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
15
src/components/Dashboard.tsx
Normal file
15
src/components/Dashboard.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { EfficiencyView } from './views/EfficiencyView';
|
||||
import { OverallView } from './views/OverallView';
|
||||
import { UsersView } from './views/UsersView';
|
||||
|
||||
export function Dashboard({ currentView }: { currentView: string }) {
|
||||
return (
|
||||
<div className="max-w-[1600px] mx-auto pb-10">
|
||||
{currentView === '效率' && <EfficiencyView />}
|
||||
{currentView === 'overall' && <OverallView />}
|
||||
{currentView === 'efficiency' && <EfficiencyView />}
|
||||
{currentView === 'users' && <UsersView />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
39
src/components/Header.tsx
Normal file
39
src/components/Header.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { Bell, Search, UserCircle, ChevronDown } from 'lucide-react';
|
||||
|
||||
export function Header({ title }: { title: string }) {
|
||||
return (
|
||||
<header className="h-16 bg-white border-b border-slate-200 flex items-center justify-between px-4 md:px-6 shrink-0 z-10 w-full transition-all">
|
||||
<div className="flex items-center">
|
||||
<h1 className="text-lg font-bold text-slate-800 mr-8 tracking-tight">{title}</h1>
|
||||
<div className="hidden md:flex items-center bg-slate-100 px-3 py-2 rounded-lg w-64 focus-within:ring-2 focus-within:ring-indigo-500/20 border border-transparent focus-within:border-indigo-500/50 transition-all">
|
||||
<Search className="w-4 h-4 text-slate-400 mr-2 shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索司机、车牌或订单号..."
|
||||
className="bg-transparent border-none outline-none text-sm text-slate-700 w-full placeholder:text-slate-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 text-slate-500 shrink-0">
|
||||
<div className="hidden sm:flex text-xs text-indigo-600 bg-indigo-50 border border-indigo-100 px-3 py-1.5 rounded-full font-medium items-center shadow-sm">
|
||||
<span className="w-2 h-2 rounded-full bg-indigo-500 mr-2 animate-pulse" />
|
||||
周期: 4/18 - 4/29 最新
|
||||
</div>
|
||||
<button className="hover:text-slate-700 transition-colors relative ml-2 p-2 hover:bg-slate-100 rounded-full">
|
||||
<Bell className="w-5 h-5" />
|
||||
<span className="absolute top-1.5 right-1.5 w-2 h-2 bg-rose-500 rounded-full border-2 border-white"></span>
|
||||
</button>
|
||||
<div className="w-px h-6 bg-slate-200 mx-1"></div>
|
||||
<button className="flex items-center space-x-2 hover:bg-slate-50 p-1.5 pl-2 pr-3 rounded-xl transition-colors border border-transparent hover:border-slate-200">
|
||||
<UserCircle className="w-8 h-8 text-indigo-400 bg-indigo-50 rounded-full" />
|
||||
<div className="text-left hidden sm:block">
|
||||
<p className="text-sm font-bold text-slate-700 leading-none">运营总监</p>
|
||||
<p className="text-[#94a3b8] text-[11px] mt-1 font-medium">全网管理视角</p>
|
||||
</div>
|
||||
<ChevronDown className="w-4 h-4 text-slate-400 ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
59
src/components/Sidebar.tsx
Normal file
59
src/components/Sidebar.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import { BarChart3, Calendar, Settings, Fuel, Users, ShieldCheck } from 'lucide-react';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
export function Sidebar({ currentView, setCurrentView }: { currentView: string; setCurrentView: (v: string) => void }) {
|
||||
const menuItems = [
|
||||
{ id: 'overall', name: '全网运营总览', icon: Calendar },
|
||||
{ id: 'users', name: '司机与车辆大盘', icon: Users },
|
||||
{ id: 'efficiency', name: '站点效能监控', icon: BarChart3 },
|
||||
];
|
||||
|
||||
return (
|
||||
<aside className="w-64 bg-slate-900 border-r border-slate-800 hidden md:flex flex-col text-slate-300 shadow-xl overflow-hidden relative">
|
||||
<div className="absolute top-0 left-0 w-full h-[600px] bg-gradient-to-b from-indigo-500/10 to-transparent pointer-events-none" />
|
||||
<div className="h-16 flex items-center px-6 border-b border-white/5 shrink-0 z-10 bg-slate-900/50 backdrop-blur-md">
|
||||
<Fuel className="w-6 h-6 text-indigo-400 mr-2 drop-shadow-md" />
|
||||
<span className="text-lg font-bold text-white tracking-tight">小羚羚运营 BI</span>
|
||||
</div>
|
||||
<nav className="flex-1 py-6 px-4 space-y-2 z-10 overflow-y-auto">
|
||||
<div className="text-xs font-bold text-slate-500/80 uppercase tracking-wider mb-4 px-3">动态看板视图</div>
|
||||
{menuItems.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => setCurrentView(item.id)}
|
||||
className={cn(
|
||||
"flex items-center w-full px-3 py-3 rounded-xl text-sm font-semibold transition-all duration-200 text-left group",
|
||||
currentView === item.id
|
||||
? "bg-indigo-500/15 text-indigo-300 shadow-sm border border-indigo-500/30"
|
||||
: "hover:bg-slate-800/80 hover:text-slate-100 border border-transparent text-slate-400"
|
||||
)}
|
||||
>
|
||||
<item.icon className={cn("w-5 h-5 mr-3 transition-colors", currentView === item.id ? "text-indigo-400" : "text-slate-500 group-hover:text-slate-300")} />
|
||||
{item.name}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<div className="mt-8 pt-6 border-t border-white/5">
|
||||
<div className="text-xs font-bold text-slate-500/80 uppercase tracking-wider mb-2 px-3">常规配置</div>
|
||||
<button className="flex items-center w-full px-3 py-2.5 rounded-lg text-sm font-medium transition-colors text-left text-slate-400 hover:bg-slate-800 hover:text-slate-100">
|
||||
<Settings className="w-5 h-5 mr-3 text-slate-500" />
|
||||
后台系统设置
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div className="p-4 border-t border-white/5 bg-slate-900 z-10 shrink-0">
|
||||
<div className="bg-gradient-to-br from-slate-800 to-slate-900 border border-slate-700/50 p-4 rounded-xl text-xs relative overflow-hidden shadow-inner">
|
||||
<div className="absolute top-0 right-0 w-16 h-16 bg-indigo-500/10 rounded-full blur-xl -mr-4 -mt-4"></div>
|
||||
<p className="text-slate-400 mb-1.5 font-medium">数据统计周期 (本周)</p>
|
||||
<p className="font-bold text-slate-200 text-sm">26年4月18日 - 4月29日</p>
|
||||
<div className="mt-3 flex items-center text-[10px] text-emerald-400 bg-emerald-400/10 px-2 py-1 rounded w-fit border border-emerald-400/20">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 mr-1.5"></span>
|
||||
数据库连通正常
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
36
src/components/ui/StatCard.tsx
Normal file
36
src/components/ui/StatCard.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { cn } from '../../lib/utils';
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
|
||||
interface StatCardProps {
|
||||
title: string;
|
||||
value: string | number;
|
||||
subValue?: string;
|
||||
icon: LucideIcon;
|
||||
trend?: number;
|
||||
highlight?: boolean;
|
||||
}
|
||||
|
||||
export function StatCard({ title, value, subValue, icon: Icon, trend, highlight }: StatCardProps) {
|
||||
return (
|
||||
<div className={cn("bg-white rounded-xl border border-slate-100 p-5 shadow-sm transition-all hover:shadow-md", highlight && "ring-1 ring-indigo-500/50")}>
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-500 mb-1">{title}</p>
|
||||
<h3 className="text-2xl font-extrabold text-slate-800 tracking-tight">{value}</h3>
|
||||
</div>
|
||||
<div className={cn("p-2.5 rounded-xl bg-slate-50", highlight && "bg-indigo-50 text-indigo-600")}>
|
||||
<Icon className={cn("w-5 h-5 text-slate-400", highlight && "text-indigo-600")} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center text-sm">
|
||||
{trend !== undefined && (
|
||||
<span className={cn("font-semibold mr-2 flex items-center", trend > 0 ? "text-emerald-500" : "text-rose-500")}>
|
||||
{trend > 0 ? "+" : ""}{trend}%
|
||||
{trend > 0 ? <span className="ml-1 text-emerald-500">↗</span> : <span className="ml-1 text-rose-500">↘</span>}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-slate-400 font-medium text-xs">{subValue}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
146
src/components/views/EfficiencyView.tsx
Normal file
146
src/components/views/EfficiencyView.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import React from 'react';
|
||||
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar, Legend, Cell } from 'recharts';
|
||||
import { StatCard } from '../ui/StatCard';
|
||||
import { Activity, Target, TrendingUp, Zap, Clock, Radio, AlertCircle } from 'lucide-react';
|
||||
|
||||
const stationData = [
|
||||
{ name: '嘉兴嘉燃', 消耗: 420, 剩余: 580, 上限: 1000 },
|
||||
{ name: '嘉兴嘉锦', 消耗: 380, 剩余: 420, 上限: 800 },
|
||||
{ name: '如皋华神', 消耗: 160, 剩余: 340, 上限: 500 },
|
||||
{ name: '花桥中石油', 消耗: 80, 剩余: 220, 上限: 300 },
|
||||
{ name: '常熟嘉化', 消耗: 45, 剩余: 155, 上限: 200 }
|
||||
];
|
||||
|
||||
const stationGrowth = [
|
||||
{ date: '4/18', 嘉燃: 10, 嘉锦: 3, 其他: 0 },
|
||||
{ date: '4/19', 嘉燃: 6, 嘉锦: 3, 其他: 0 },
|
||||
{ date: '4/20', 嘉燃: 7, 嘉锦: 0, 其他: 0 },
|
||||
{ date: '4/21', 嘉燃: 5, 嘉锦: 3, 其他: 0 },
|
||||
{ date: '4/22', 嘉燃: 7, 嘉锦: 3, 其他: 1 },
|
||||
{ date: '4/23', 嘉燃: 7, 嘉锦: 15, 其他: 7 },
|
||||
{ date: '4/24', 嘉燃: 9, 嘉锦: 14, 其他: 6 },
|
||||
{ date: '4/25', 嘉燃: 6, 嘉锦: 12, 其他: 4 },
|
||||
{ date: '4/26', 嘉燃: 6, 嘉锦: 13, 其他: 3 },
|
||||
{ date: '4/27', 嘉燃: 6, 嘉锦: 13, 其他: 3 },
|
||||
{ date: '4/28', 嘉燃: 7, 嘉锦: 13, 其他: 2 },
|
||||
{ date: '4/29', 嘉燃: 7, 嘉锦: 13, 其他: 2 },
|
||||
];
|
||||
|
||||
export function EfficiencyView() {
|
||||
return (
|
||||
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
<div className="bg-gradient-to-r from-indigo-50 to-blue-50 border border-indigo-100 rounded-xl p-5 flex items-start shadow-sm">
|
||||
<div className="bg-indigo-100/80 p-2 rounded-lg mr-4 shrink-0">
|
||||
<Radio className="w-5 h-5 text-indigo-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-bold text-indigo-900 flex items-center">
|
||||
二期底层物联网 (IoT) 直连规划中
|
||||
<span className="ml-3 px-2 py-0.5 rounded text-[10px] bg-indigo-200 text-indigo-800 font-bold tracking-wider">PHASE 2</span>
|
||||
</h4>
|
||||
<p className="text-sm text-indigo-700 mt-1.5 leading-relaxed">
|
||||
目前系统已全面掌握<span className="font-semibold text-indigo-900 border-b border-indigo-200">「商流与信息流」</span>(在线预约单量与排班)。当前板块基于历史单量测算,呈现<b>算法预估负荷模型演示</b>。下阶段将重点打通<span className="font-semibold text-indigo-900 border-b border-indigo-200">「物理数据流」</span>,与加气机等底层硬件直连获取真实库存,彻底实现“数字孪生式”的加氢调配闭环。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<StatCard
|
||||
title="全网综合日均加注量预估"
|
||||
value="1,085 kg"
|
||||
subValue="主要覆盖嘉兴、南通等核心枢纽网的测算值"
|
||||
icon={Activity}
|
||||
highlight
|
||||
/>
|
||||
<StatCard
|
||||
title="全网各站总负荷上限满载"
|
||||
value="2,800 kg"
|
||||
subValue="所有加注站理论最高吞吐物理上限汇总"
|
||||
icon={Zap}
|
||||
/>
|
||||
<StatCard
|
||||
title="系统优化全站有效工时"
|
||||
value="~4.5 小时"
|
||||
subValue="主要由预约时限测算出的无效排队节省时间"
|
||||
icon={Clock}
|
||||
trend={-45}
|
||||
/>
|
||||
<StatCard
|
||||
title="平均承载饱和度 (预估)"
|
||||
value="38.5 %"
|
||||
subValue="预留了充足的峰值缓冲池与调配拓展空间"
|
||||
icon={Target}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="bg-white rounded-xl border border-slate-100 p-6 shadow-sm">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-slate-800">主力站点日增单量趋势实况</h3>
|
||||
<p className="text-sm text-slate-500 mt-1">此部分属于已打通的一期“信息流”真实数据展示</p>
|
||||
</div>
|
||||
<div className="flex bg-indigo-50 text-indigo-600 px-3 py-1.5 rounded-lg text-sm font-semibold items-center border border-indigo-100">
|
||||
<TrendingUp className="w-4 h-4 mr-1.5" />
|
||||
全线看涨
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-[300px] w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={stationGrowth} margin={{ top: 10, right: 10, left: -25, bottom: 0 }}>
|
||||
<defs>
|
||||
<linearGradient id="colorJr" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#0ea5e9" stopOpacity={0.3}/>
|
||||
<stop offset="95%" stopColor="#0ea5e9" stopOpacity={0}/>
|
||||
</linearGradient>
|
||||
<linearGradient id="colorJj" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#8b5cf6" stopOpacity={0.3}/>
|
||||
<stop offset="95%" stopColor="#8b5cf6" stopOpacity={0}/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<XAxis dataKey="date" axisLine={false} tickLine={false} tick={{fill: '#64748b', fontSize: 12}} dy={10} />
|
||||
<YAxis axisLine={false} tickLine={false} tick={{fill: '#64748b', fontSize: 12}} />
|
||||
<CartesianGrid vertical={false} stroke="#f1f5f9" strokeDasharray="4 4" />
|
||||
<Tooltip cursor={{stroke: '#cbd5e1', strokeWidth: 1, strokeDasharray: '4 4'}} contentStyle={{ borderRadius: '12px', border: 'none', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' }} />
|
||||
<Legend verticalAlign="top" height={36} iconType="circle" wrapperStyle={{ fontSize: '12px' }} />
|
||||
<Area type="monotone" dataKey="嘉燃" stackId="1" stroke="#0ea5e9" strokeWidth={2} fillOpacity={1} fill="url(#colorJr)" />
|
||||
<Area type="monotone" dataKey="嘉锦" stackId="1" stroke="#8b5cf6" strokeWidth={2} fillOpacity={1} fill="url(#colorJj)" />
|
||||
<Area type="monotone" dataKey="其他" stackId="1" stroke="#f59e0b" strokeWidth={2} fillOpacity={0.1} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl border border-slate-100 p-6 shadow-sm flex flex-col justify-between relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 p-6 opacity-5 pointer-events-none">
|
||||
<Zap className="w-32 h-32" />
|
||||
</div>
|
||||
<div className="mb-6 relative z-10">
|
||||
<div className="flex flex-col xl:flex-row xl:justify-between xl:items-start gap-3">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-slate-800">各站负荷吞吐预估模型 (kg)</h3>
|
||||
<p className="text-sm text-slate-500 mt-1">数据按历史预约进行算法推演,演示智能匹配效果</p>
|
||||
</div>
|
||||
<span className="flex items-center text-[10px] lg:text-xs font-bold bg-amber-50 text-amber-600 px-2 py-1 rounded-md border border-amber-200 w-fit shrink-0">
|
||||
<AlertCircle className="w-3 h-3 mr-1.5" />
|
||||
IoT硬件直连筹备中
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 min-h-[300px] relative z-10">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={stationData} layout="vertical" margin={{ top: 0, right: 30, left: 20, bottom: 0 }}>
|
||||
<XAxis type="number" hide />
|
||||
<YAxis dataKey="name" type="category" axisLine={false} tickLine={false} tick={{fill: '#475569', fontSize: 12, fontWeight: 500}} width={80} />
|
||||
<Tooltip cursor={{fill: '#f8fafc'}} contentStyle={{ borderRadius: '12px', border: '1px solid #e2e8f0', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.05)' }}/>
|
||||
<Legend verticalAlign="top" height={36} iconType="circle" wrapperStyle={{ fontSize: '12px' }} />
|
||||
<Bar dataKey="消耗" stackId="a" fill="#4f46e5" radius={[0, 0, 0, 0]} barSize={24} name="当前消耗测算负荷" />
|
||||
<Bar dataKey="剩余" stackId="a" fill="#e2e8f0" radius={[0, 4, 4, 0]} barSize={24} name="未饱和推演备用空间" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
134
src/components/views/OverallView.tsx
Normal file
134
src/components/views/OverallView.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import React from 'react';
|
||||
import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
|
||||
import { StatCard } from '../ui/StatCard';
|
||||
import { Calendar, Network, MapPin, UserCheck } from 'lucide-react';
|
||||
|
||||
const weeklyTrend = [
|
||||
{ name: '3/16-3/20', 总量: 17 },
|
||||
{ name: '3/21-3/27', 总量: 47 },
|
||||
{ name: '3/28-4/6', 总量: 55 },
|
||||
{ name: '4/7-4/11', 总量: 40 },
|
||||
{ name: '4/12-4/17', 总量: 49 },
|
||||
{ name: '4/18-4/24', 总量: 106 },
|
||||
{ name: '4/25-4/29', 总量: 110 },
|
||||
];
|
||||
|
||||
const dailyBreakdown = [
|
||||
{ date: '4/18', 嘉燃: 10, 嘉锦: 3, 如皋: 0, 其他: 0 },
|
||||
{ date: '4/19', 嘉燃: 6, 嘉锦: 3, 如皋: 0, 其他: 0 },
|
||||
{ date: '4/20', 嘉燃: 7, 嘉锦: 0, 如皋: 0, 其他: 0 },
|
||||
{ date: '4/21', 嘉燃: 5, 嘉锦: 3, 如皋: 0, 其他: 0 },
|
||||
{ date: '4/22', 嘉燃: 7, 嘉锦: 3, 如皋: 1, 其他: 0 },
|
||||
{ date: '4/23', 嘉燃: 7, 嘉锦: 15, 如皋: 4, 其他: 3 },
|
||||
{ date: '4/24', 嘉燃: 9, 嘉锦: 14, 如皋: 4, 其他: 2 },
|
||||
{ date: '4/25', 嘉燃: 6, 嘉锦: 12, 如皋: 1, 其他: 3 },
|
||||
{ date: '4/26', 嘉燃: 6, 嘉锦: 13, 如皋: 1, 其他: 2 },
|
||||
{ date: '4/27', 嘉燃: 6, 嘉锦: 13, 如皋: 2, 其他: 1 },
|
||||
{ date: '4/28', 嘉燃: 7, 嘉锦: 13, 如皋: 1, 其他: 1 },
|
||||
{ date: '4/29', 嘉燃: 7, 嘉锦: 13, 如皋: 2, 其他: 0 },
|
||||
];
|
||||
|
||||
const activeStations = [
|
||||
{ name: '嘉兴嘉燃', isNew: false },
|
||||
{ name: '嘉兴嘉锦', isNew: false },
|
||||
{ name: '如皋华神', isNew: false },
|
||||
{ name: '常熟嘉化', isNew: true },
|
||||
{ name: '桐乡绿能', isNew: true },
|
||||
{ name: '花桥中石油', isNew: true },
|
||||
{ name: '嘉善站前路', isNew: true },
|
||||
];
|
||||
|
||||
export function OverallView() {
|
||||
return (
|
||||
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<StatCard
|
||||
title="系统累计预约总单量"
|
||||
value="424 单"
|
||||
subValue="全网7周累计历史总量"
|
||||
icon={Network}
|
||||
highlight
|
||||
/>
|
||||
<StatCard
|
||||
title="本周(4/25-4/29)新增单量"
|
||||
value="110 单"
|
||||
subValue="嘉锦占比过半,增势迅猛"
|
||||
icon={Calendar}
|
||||
trend={116}
|
||||
/>
|
||||
<StatCard
|
||||
title="现阶段活跃运营站点"
|
||||
value="7 个"
|
||||
subValue="期间新增桐乡、花桥、前路加氢站"
|
||||
icon={MapPin}
|
||||
trend={75}
|
||||
/>
|
||||
<StatCard
|
||||
title="近期活跃司机数量"
|
||||
value="45 人"
|
||||
subValue="4/25-4/29 期间内活跃"
|
||||
icon={UserCheck}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-indigo-100/50 rounded-xl p-4 shadow-sm flex flex-col md:flex-row md:items-center gap-3">
|
||||
<div className="flex items-center text-sm font-bold text-slate-700 mr-2 shrink-0">
|
||||
<MapPin className="w-4 h-4 mr-1.5 text-indigo-500" />
|
||||
当前支持预约站点 ({activeStations.length}个):
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{activeStations.map(station => (
|
||||
<div key={station.name} className="flex items-center bg-slate-50 border border-slate-200 px-3 py-1.5 rounded-lg text-sm transition-colors hover:border-indigo-200 hover:bg-indigo-50/50">
|
||||
<span className="text-slate-700 font-medium">{station.name}</span>
|
||||
{station.isNew && (
|
||||
<span className="ml-2 px-1.5 py-0.5 rounded text-[10px] font-bold bg-rose-100 text-rose-600 border border-rose-200">NEW</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="bg-white rounded-xl border border-slate-100 p-6 shadow-sm">
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-bold text-slate-800">全网6周内预约总数趋势盘</h3>
|
||||
<p className="text-sm text-slate-500 mt-1">本周实现翻倍式增长突破百单关口</p>
|
||||
</div>
|
||||
<div className="h-[300px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={weeklyTrend} margin={{ top: 10, right: 10, left: -25, bottom: 0 }}>
|
||||
<CartesianGrid vertical={false} stroke="#f1f5f9" strokeDasharray="3 3"/>
|
||||
<XAxis dataKey="name" axisLine={false} tickLine={false} tick={{fill: '#64748b', fontSize: 11}} dy={10} />
|
||||
<YAxis axisLine={false} tickLine={false} tick={{fill: '#64748b', fontSize: 12}} />
|
||||
<Tooltip cursor={{stroke: '#e2e8f0', strokeWidth: 2}} contentStyle={{ borderRadius: '12px', border: '1px solid #e2e8f0', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' }}/>
|
||||
<Line type="monotone" dataKey="总量" stroke="#0ea5e9" strokeWidth={4} dot={{r: 6, strokeWidth: 3, fill: '#fff'}} activeDot={{r: 8, strokeWidth: 0, fill: '#0284c7'}} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl border border-slate-100 p-6 shadow-sm">
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-bold text-slate-800">关键大单点流量池对比</h3>
|
||||
<p className="text-sm text-slate-500 mt-1">主营干线站点发挥核心支撑作用,外围增量明显</p>
|
||||
</div>
|
||||
<div className="h-[300px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={dailyBreakdown} margin={{ top: 10, right: 10, left: -25, bottom: 0 }}>
|
||||
<CartesianGrid vertical={false} stroke="#f1f5f9" strokeDasharray="3 3" />
|
||||
<XAxis dataKey="date" axisLine={false} tickLine={false} tick={{fill: '#64748b', fontSize: 12}} dy={10} />
|
||||
<YAxis axisLine={false} tickLine={false} tick={{fill: '#64748b', fontSize: 12}} />
|
||||
<Tooltip cursor={{fill: '#f8fafc'}} contentStyle={{ borderRadius: '12px', border: '1px solid #e2e8f0', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.05)' }}/>
|
||||
<Legend verticalAlign="top" height={36} iconType="circle" wrapperStyle={{ fontSize: '12px' }} />
|
||||
<Bar dataKey="嘉燃" stackId="a" fill="#0ea5e9" radius={[0, 0, 4, 4]} />
|
||||
<Bar dataKey="嘉锦" stackId="a" fill="#8b5cf6" />
|
||||
<Bar dataKey="如皋" stackId="a" fill="#f59e0b" />
|
||||
<Bar dataKey="其他" stackId="a" fill="#94a3b8" radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
105
src/components/views/UsersView.tsx
Normal file
105
src/components/views/UsersView.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React from 'react';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, BarChart, Bar, Cell } from 'recharts';
|
||||
import { StatCard } from '../ui/StatCard';
|
||||
import { Users, Truck, Smartphone } from 'lucide-react';
|
||||
|
||||
const driverGrowth = [
|
||||
{ date: '4/18', 活跃APP: 57, 绑定车辆: 114 },
|
||||
{ date: '4/19', 活跃APP: 57, 绑定车辆: 114 },
|
||||
{ date: '4/20', 活跃APP: 58, 绑定车辆: 114 },
|
||||
{ date: '4/21', 活跃APP: 59, 绑定车辆: 117 },
|
||||
{ date: '4/22', 活跃APP: 61, 绑定车辆: 121 },
|
||||
{ date: '4/23', 活跃APP: 74, 绑定车辆: 128 },
|
||||
{ date: '4/24', 活跃APP: 82, 绑定车辆: 135 },
|
||||
{ date: '4/25', 活跃APP: 84, 绑定车辆: 139 },
|
||||
{ date: '4/26', 活跃APP: 86, 绑定车辆: 139 },
|
||||
{ date: '4/27', 活跃APP: 89, 绑定车辆: 140 },
|
||||
{ date: '4/28', 活跃APP: 91, 绑定车辆: 141 },
|
||||
{ date: '4/29', 活跃APP: 93, 绑定车辆: 142 },
|
||||
];
|
||||
|
||||
const driverByStation = [
|
||||
{ name: '如皋华神', value: 40 },
|
||||
{ name: '嘉兴嘉燃', value: 32 },
|
||||
{ name: '嘉兴嘉锦', value: 32 },
|
||||
{ name: '桐乡绿能', value: 4 },
|
||||
{ name: '花桥/嘉善等', value: 7 },
|
||||
];
|
||||
|
||||
export function UsersView() {
|
||||
return (
|
||||
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<StatCard
|
||||
title="累计入池APP用户总数"
|
||||
value="93 人"
|
||||
subValue="近期活跃司机达到45人"
|
||||
icon={Smartphone}
|
||||
highlight
|
||||
/>
|
||||
<StatCard
|
||||
title="累计绑定车辆司机总数"
|
||||
value="142 人"
|
||||
subValue="系统全量历史转化留存"
|
||||
icon={Truck}
|
||||
/>
|
||||
<StatCard
|
||||
title="总核验车辆绑定率"
|
||||
value="95 %"
|
||||
subValue="公司自营与核心车队"
|
||||
icon={Users}
|
||||
trend={5}
|
||||
/>
|
||||
<StatCard
|
||||
title="本周最佳外拓来源区"
|
||||
value="长三角外围"
|
||||
subValue="如皋及花桥累计贡献非标散户6个"
|
||||
icon={Users}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="bg-white rounded-xl border border-slate-100 p-6 shadow-sm">
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-bold text-slate-800">平台APP渗透与车辆绑定追踪</h3>
|
||||
<p className="text-sm text-slate-500 mt-1">实时记录系统端用户量与车辆规模的扩充</p>
|
||||
</div>
|
||||
<div className="h-[300px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={driverGrowth} margin={{ top: 10, right: 10, left: -25, bottom: 0 }}>
|
||||
<CartesianGrid vertical={false} stroke="#f1f5f9" strokeDasharray="3 3"/>
|
||||
<XAxis dataKey="date" axisLine={false} tickLine={false} tick={{fill: '#64748b', fontSize: 12}} dy={10} />
|
||||
<YAxis axisLine={false} tickLine={false} tick={{fill: '#64748b', fontSize: 12}} />
|
||||
<Tooltip cursor={{stroke: '#e2e8f0', strokeWidth: 2}} contentStyle={{ borderRadius: '12px', border: '1px solid #e2e8f0' }}/>
|
||||
<Legend verticalAlign="top" height={36} iconType="circle" wrapperStyle={{ fontSize: '12px' }} />
|
||||
<Line type="monotone" dataKey="绑定车辆" stroke="#6366f1" strokeWidth={3} dot={{r: 4}} activeDot={{r: 6}} />
|
||||
<Line type="monotone" dataKey="活跃APP" stroke="#14b8a6" strokeWidth={3} dot={{r: 4}} activeDot={{r: 6}} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl border border-slate-100 p-6 shadow-sm">
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-bold text-slate-800">全站存量司机资源池分布</h3>
|
||||
<p className="text-sm text-slate-500 mt-1">外拓下沉能力强劲,多区域司机基数稳步建立</p>
|
||||
</div>
|
||||
<div className="h-[300px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={driverByStation} layout="vertical" margin={{ top: 0, right: 30, left: 20, bottom: 0 }}>
|
||||
<XAxis type="number" hide />
|
||||
<YAxis dataKey="name" type="category" axisLine={false} tickLine={false} tick={{fill: '#475569', fontSize: 12, fontWeight: 500}} width={80} />
|
||||
<Tooltip cursor={{fill: '#f8fafc'}} contentStyle={{ borderRadius: '12px', border: '1px solid #e2e8f0' }}/>
|
||||
<Bar dataKey="value" radius={[0, 6, 6, 0]} barSize={28}>
|
||||
{driverByStation.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={index === 0 ? '#f59e0b' : '#94a3b8'} />
|
||||
))}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
src/index.css
Normal file
1
src/index.css
Normal file
@@ -0,0 +1 @@
|
||||
@import "tailwindcss";
|
||||
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
10
src/main.tsx
Normal file
10
src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import {StrictMode} from 'react';
|
||||
import {createRoot} from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
import './index.css';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"module": "ESNext",
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
},
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
24
vite.config.ts
Normal file
24
vite.config.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
import {defineConfig, loadEnv} from 'vite';
|
||||
|
||||
export default defineConfig(({mode}) => {
|
||||
const env = loadEnv(mode, '.', '');
|
||||
return {
|
||||
plugins: [react(), tailwindcss()],
|
||||
define: {
|
||||
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, '.'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
// HMR is disabled in AI Studio via DISABLE_HMR env var.
|
||||
// Do not modifyâfile watching is disabled to prevent flickering during agent edits.
|
||||
hmr: process.env.DISABLE_HMR !== 'true',
|
||||
},
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user