243 lines
8.0 KiB
TypeScript
243 lines
8.0 KiB
TypeScript
import React, { useEffect, useRef } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { Bell, Smartphone, Users, Activity } from 'lucide-react';
|
|
import Chart from 'chart.js/auto';
|
|
import Layout from '@/components/Layout';
|
|
import BottomNav from '@/components/BottomNav';
|
|
import '@/components/Layout.css';
|
|
|
|
// 模拟数据
|
|
const stats = {
|
|
totalDevices: 12,
|
|
totalWechatAccounts: 8,
|
|
onlineWechatAccounts: 6,
|
|
};
|
|
|
|
const scenarioFeatures = [
|
|
{
|
|
id: "3",
|
|
name: "抖音获客",
|
|
value: 156,
|
|
icon: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-QR8ManuDplYTySUJsY4mymiZkDYnQ9.png",
|
|
color: "bg-red-100",
|
|
},
|
|
{
|
|
id: "4",
|
|
name: "小红书获客",
|
|
value: 89,
|
|
icon: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-yvnMxpoBUzcvEkr8DfvHgPHEo1kmQ3.png",
|
|
color: "bg-pink-100",
|
|
},
|
|
{
|
|
id: "6",
|
|
name: "公众号获客",
|
|
value: 234,
|
|
icon: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-Gsg0CMf5tsZb41mioszdjqU1WmsRxW.png",
|
|
color: "bg-green-100",
|
|
},
|
|
{
|
|
id: "1",
|
|
name: "海报获客",
|
|
value: 167,
|
|
icon: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-x92XJgXy4MI7moNYlA1EAes2FqDxMH.png",
|
|
color: "bg-blue-100",
|
|
},
|
|
];
|
|
|
|
export default function Home() {
|
|
const navigate = useNavigate();
|
|
const chartRef = useRef<HTMLCanvasElement>(null);
|
|
const chartInstance = useRef<any>(null);
|
|
|
|
const handleDevicesClick = () => {
|
|
navigate('/devices');
|
|
};
|
|
|
|
const handleWechatClick = () => {
|
|
navigate('/wechat-accounts');
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!chartRef.current) return;
|
|
|
|
// 销毁旧实例
|
|
if (chartInstance.current) {
|
|
chartInstance.current.destroy();
|
|
chartInstance.current = null;
|
|
}
|
|
|
|
const ctx = chartRef.current.getContext('2d');
|
|
if (ctx) {
|
|
chartInstance.current = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
|
datasets: [
|
|
{
|
|
label: '获客数量',
|
|
data: [12, 19, 15, 25, 22, 30, 28],
|
|
backgroundColor: 'rgba(59, 130, 246, 0.2)',
|
|
borderColor: 'rgba(59, 130, 246, 1)',
|
|
borderWidth: 2,
|
|
tension: 0.3,
|
|
pointRadius: 4,
|
|
pointBackgroundColor: 'rgba(59, 130, 246, 1)',
|
|
pointHoverRadius: 6,
|
|
},
|
|
],
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: false,
|
|
},
|
|
tooltip: {
|
|
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
titleColor: '#333',
|
|
bodyColor: '#666',
|
|
borderColor: '#ddd',
|
|
borderWidth: 1,
|
|
padding: 10,
|
|
displayColors: false,
|
|
callbacks: {
|
|
label: (context: any) => `获客数量: ${context.parsed.y}`,
|
|
},
|
|
},
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: {
|
|
display: false,
|
|
},
|
|
ticks: {
|
|
font: {
|
|
size: 12, // 0.75rem
|
|
},
|
|
},
|
|
},
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.05)',
|
|
},
|
|
ticks: {
|
|
font: {
|
|
size: 12, // 0.75rem
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
// 卸载时销毁
|
|
return () => {
|
|
if (chartInstance.current) {
|
|
chartInstance.current.destroy();
|
|
chartInstance.current = null;
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<Layout
|
|
header={
|
|
<div className="bg-white border-b">
|
|
<div className="flex justify-between items-center p-4">
|
|
<h1 className="text-xl font-semibold text-blue-600">存客宝</h1>
|
|
<button className="p-2 hover:bg-gray-100 rounded-full">
|
|
<Bell className="h-5 w-5 text-gray-600" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
footer={<BottomNav />}
|
|
>
|
|
<div className="bg-gray-50">
|
|
<div className="p-4 space-y-6">
|
|
{/* 统计卡片 */}
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<div
|
|
className="p-4 bg-white hover:shadow-lg transition-all cursor-pointer rounded shadow"
|
|
onClick={handleDevicesClick}
|
|
>
|
|
<div className="flex flex-col">
|
|
<span className="text-sm text-gray-500 mb-2">设备数量</span>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-2xl font-bold text-blue-600">{stats.totalDevices}</span>
|
|
<Smartphone className="w-6 h-6 text-blue-600" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
className="p-4 bg-white hover:shadow-lg transition-all cursor-pointer rounded shadow"
|
|
onClick={handleWechatClick}
|
|
>
|
|
<div className="flex flex-col">
|
|
<span className="text-sm text-gray-500 mb-2">微信号数量</span>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-2xl font-bold text-blue-600">{stats.totalWechatAccounts}</span>
|
|
<Users className="w-6 h-6 text-blue-600" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="p-4 bg-white rounded shadow">
|
|
<div className="flex flex-col">
|
|
<span className="text-sm text-gray-500 mb-2">在线微信号</span>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<span className="text-2xl font-bold text-blue-600">{stats.onlineWechatAccounts}</span>
|
|
<Activity className="w-6 h-6 text-blue-600" />
|
|
</div>
|
|
<div className="w-full bg-gray-200 rounded-full h-1">
|
|
<div
|
|
className="bg-blue-600 h-1 rounded-full"
|
|
style={{ width: `${(stats.onlineWechatAccounts / stats.totalWechatAccounts) * 100}%` }}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 场景获客统计 */}
|
|
<div className="p-4 bg-white rounded shadow">
|
|
<div className="flex justify-between items-center mb-4">
|
|
<h2 className="text-lg font-semibold">场景获客统计</h2>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
{scenarioFeatures
|
|
.sort((a, b) => b.value - a.value)
|
|
.map((scenario) => (
|
|
<div
|
|
key={scenario.id}
|
|
className="block flex-1 cursor-pointer"
|
|
onClick={() => navigate(`/scenarios/${scenario.id}?name=${encodeURIComponent(scenario.name)}`)}
|
|
>
|
|
<div className="flex flex-col items-center text-center space-y-2">
|
|
<div className={`w-12 h-12 rounded-full ${scenario.color} flex items-center justify-center`}>
|
|
<img src={scenario.icon || "/placeholder.svg"} alt={scenario.name} className="w-6 h-6" />
|
|
</div>
|
|
<div className="text-sm font-medium">{scenario.value}</div>
|
|
<div className="text-xs text-gray-500 whitespace-nowrap overflow-hidden text-ellipsis w-full">
|
|
{scenario.name}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 每日获客趋势 */}
|
|
<div className="p-4 bg-white rounded shadow">
|
|
<h2 className="text-lg font-semibold mb-4">每日获客趋势</h2>
|
|
<div className="w-full h-64 relative">
|
|
<canvas ref={chartRef} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Layout>
|
|
);
|
|
}
|