feat: 首页样式同步完成
This commit is contained in:
6
nkebao/postcss.config.js
Normal file
6
nkebao/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
|||||||
@@ -1,5 +1,242 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { Chart, registerables } from 'chart.js';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Smartphone, Users, Activity, Bell } from 'lucide-react';
|
||||||
|
Chart.register(...registerables);
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return <div>首页</div>;
|
const chartRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const chartInstance = useRef<Chart | null>(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// 统计数据状态
|
||||||
|
const [stats, setStats] = useState({
|
||||||
|
totalDevices: 0,
|
||||||
|
onlineDevices: 0,
|
||||||
|
totalWechatAccounts: 0,
|
||||||
|
onlineWechatAccounts: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 业务场景数据
|
||||||
|
const scenarioFeatures = [
|
||||||
|
{
|
||||||
|
id: "douyin",
|
||||||
|
name: "抖音获客",
|
||||||
|
icon: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-QR8ManuDplYTySUJsY4mymiZkDYnQ9.png",
|
||||||
|
color: "bg-blue-100 text-blue-600",
|
||||||
|
value: 156,
|
||||||
|
growth: 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "xiaohongshu",
|
||||||
|
name: "小红书获客",
|
||||||
|
icon: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-yvnMxpoBUzcvEkr8DfvHgPHEo1kmQ3.png",
|
||||||
|
color: "bg-red-100 text-red-600",
|
||||||
|
value: 89,
|
||||||
|
growth: 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "gongzhonghao",
|
||||||
|
name: "公众号获客",
|
||||||
|
icon: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-Gsg0CMf5tsZb41mioszdjqU1WmsRxW.png",
|
||||||
|
color: "bg-green-100 text-green-600",
|
||||||
|
value: 234,
|
||||||
|
growth: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "haibao",
|
||||||
|
name: "海报获客",
|
||||||
|
icon: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-x92XJgXy4MI7moNYlA1EAes2FqDxMH.png",
|
||||||
|
color: "bg-orange-100 text-orange-600",
|
||||||
|
value: 167,
|
||||||
|
growth: 10,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 获取统计数据
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchStats = async () => {
|
||||||
|
try {
|
||||||
|
// 这里可以调用实际的API
|
||||||
|
// const response = await fetch('/api/stats');
|
||||||
|
// const data = await response.json();
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
setStats({
|
||||||
|
totalDevices: 42,
|
||||||
|
onlineDevices: 35,
|
||||||
|
totalWechatAccounts: 42,
|
||||||
|
onlineWechatAccounts: 35,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取统计数据失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchStats();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (chartRef.current) {
|
||||||
|
if (chartInstance.current) chartInstance.current.destroy();
|
||||||
|
chartInstance.current = new Chart(chartRef.current, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '获客数量',
|
||||||
|
data: [120, 150, 180, 200, 230, 210, 190],
|
||||||
|
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) => `获客数量: ${context.parsed.y}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (chartInstance.current) chartInstance.current.destroy();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDevicesClick = () => {
|
||||||
|
navigate('/devices');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWechatClick = () => {
|
||||||
|
navigate('/wechat-accounts');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex-1 overflow-y-auto pb-16 bg-gray-50">
|
||||||
|
<header className="sticky top-0 z-10 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>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<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}`)}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
10
nkebao/tailwind.config.js
Normal file
10
nkebao/tailwind.config.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./src/**/*.{js,jsx,ts,tsx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user