feat: 本次提交更新内容如下

样式没问题了,库存流量
This commit is contained in:
笔记本里的永平
2025-07-11 17:06:15 +08:00
parent dedf6be5a6
commit ff01acc184
8 changed files with 1929 additions and 362 deletions

View File

@@ -1,87 +1,287 @@
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Bell, Smartphone, Users, Activity } from 'lucide-react';
import { Bell, Smartphone, Users, Activity, MessageSquare, TrendingUp } from 'lucide-react';
import Chart from 'chart.js/auto';
import Layout from '@/components/Layout';
import BottomNav from '@/components/BottomNav';
import UnifiedHeader, { HeaderPresets } from '@/components/UnifiedHeader';
import { Card } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import '@/components/Layout.css';
// 模拟数据
const stats = {
totalDevices: 12,
totalWechatAccounts: 8,
onlineWechatAccounts: 6,
};
// API接口定义
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || "https://ckbapi.quwanzhi.com";
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",
},
];
// 统一的API请求客户端
async function apiRequest<T>(url: string): Promise<T> {
try {
const token = typeof window !== "undefined" ? localStorage.getItem("token") : null;
const headers: Record<string, string> = {
"Content-Type": "application/json",
Accept: "application/json",
};
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
console.log("发送API请求:", url);
const response = await fetch(url, {
method: "GET",
headers,
mode: "cors",
});
console.log("API响应状态:", response.status, response.statusText);
// 检查响应头的Content-Type
const contentType = response.headers.get("content-type");
console.log("响应Content-Type:", contentType);
if (!response.ok) {
// 如果是401未授权清除本地存储
if (response.status === 401) {
if (typeof window !== "undefined") {
localStorage.removeItem("token");
localStorage.removeItem("userInfo");
}
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// 检查是否是JSON响应
if (!contentType || !contentType.includes("application/json")) {
const text = await response.text();
console.log("非JSON响应内容:", text.substring(0, 200));
throw new Error("服务器返回了非JSON格式的数据可能是HTML错误页面");
}
const data = await response.json();
console.log("API响应数据:", data);
// 检查业务状态码
if (data.code && data.code !== 200 && data.code !== 0) {
throw new Error(data.message || "请求失败");
}
return data.data || data;
} catch (error) {
console.error("API请求失败:", error);
throw error;
}
}
export default function Home() {
const navigate = useNavigate();
const chartRef = useRef<HTMLCanvasElement>(null);
const chartInstance = useRef<any>(null);
// 统一设备数据
const [stats, setStats] = useState({
totalDevices: 0,
onlineDevices: 0,
totalWechatAccounts: 0,
onlineWechatAccounts: 0,
});
const [isLoading, setIsLoading] = useState(true);
const [apiError, setApiError] = useState("");
// 场景获客数据
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,
},
];
// 今日数据统计
const todayStats = [
{
title: "朋友圈同步",
value: "12",
icon: <MessageSquare className="h-4 w-4" />,
color: "text-purple-600",
path: "/workspace/moments-sync",
},
{
title: "群发任务",
value: "8",
icon: <Users className="h-4 w-4" />,
color: "text-orange-600",
path: "/workspace/group-push",
},
{
title: "获客转化",
value: "85%",
icon: <TrendingUp className="h-4 w-4" />,
color: "text-green-600",
path: "/scenarios",
},
{
title: "系统活跃度",
value: "98%",
icon: <Activity className="h-4 w-4" />,
color: "text-blue-600",
path: "/workspace",
},
];
useEffect(() => {
// 获取统计数据
const fetchStats = async () => {
try {
setIsLoading(true);
setApiError("");
// 检查是否有token
const token = localStorage.getItem("token");
if (!token) {
console.log("未找到登录token使用默认数据");
setStats({
totalDevices: 42,
onlineDevices: 35,
totalWechatAccounts: 42,
onlineWechatAccounts: 35,
});
setIsLoading(false);
return;
}
// 尝试请求API数据
try {
// 并行请求多个接口
const [deviceStatsResult, wechatStatsResult] = await Promise.allSettled([
apiRequest(`${API_BASE_URL}/v1/dashboard/device-stats`),
apiRequest(`${API_BASE_URL}/v1/dashboard/wechat-stats`),
]);
const newStats = {
totalDevices: 0,
onlineDevices: 0,
totalWechatAccounts: 0,
onlineWechatAccounts: 0,
};
// 处理设备统计数据
if (deviceStatsResult.status === "fulfilled") {
const deviceData = deviceStatsResult.value as any;
newStats.totalDevices = deviceData.total || 0;
newStats.onlineDevices = deviceData.online || 0;
} else {
console.warn("设备统计API失败:", deviceStatsResult.reason);
}
// 处理微信号统计数据
if (wechatStatsResult.status === "fulfilled") {
const wechatData = wechatStatsResult.value as any;
newStats.totalWechatAccounts = wechatData.total || 0;
newStats.onlineWechatAccounts = wechatData.active || 0;
} else {
console.warn("微信号统计API失败:", wechatStatsResult.reason);
}
setStats(newStats);
} catch (apiError) {
console.warn("API请求失败使用默认数据:", apiError);
setApiError(apiError instanceof Error ? apiError.message : "API连接失败");
// 使用默认数据
setStats({
totalDevices: 42,
onlineDevices: 35,
totalWechatAccounts: 42,
onlineWechatAccounts: 35,
});
}
} catch (error) {
console.error("获取统计数据失败:", error);
setApiError(error instanceof Error ? error.message : "数据加载失败");
// 使用默认数据
setStats({
totalDevices: 42,
onlineDevices: 35,
totalWechatAccounts: 42,
onlineWechatAccounts: 35,
});
} finally {
setIsLoading(false);
}
};
fetchStats();
// 定时刷新数据每30秒
const interval = setInterval(fetchStats, 30000);
return () => clearInterval(interval);
}, []); // 移除stats依赖
const handleDevicesClick = () => {
navigate('/devices');
navigate('/profile/devices');
};
const handleWechatClick = () => {
navigate('/wechat-accounts');
};
// 使用Chart.js创建图表
useEffect(() => {
if (!chartRef.current) return;
if (chartRef.current && !isLoading) {
// 如果已经有图表实例,先销毁它
if (chartInstance.current) {
chartInstance.current.destroy();
}
// 销毁旧实例
if (chartInstance.current) {
chartInstance.current.destroy();
chartInstance.current = null;
}
const ctx = chartRef.current.getContext("2d");
// 添加null检查
if (!ctx) return;
const ctx = chartRef.current.getContext('2d');
if (ctx) {
// 创建新的图表实例
chartInstance.current = new Chart(ctx, {
type: 'line',
type: "line",
data: {
labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
labels: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
datasets: [
{
label: '获客数量',
data: [12, 19, 15, 25, 22, 30, 28],
backgroundColor: 'rgba(59, 130, 246, 0.2)',
borderColor: 'rgba(59, 130, 246, 1)',
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)',
pointBackgroundColor: "rgba(59, 130, 246, 1)",
pointHoverRadius: 6,
},
],
@@ -94,15 +294,15 @@ export default function Home() {
display: false,
},
tooltip: {
backgroundColor: 'rgba(255, 255, 255, 0.9)',
titleColor: '#333',
bodyColor: '#666',
borderColor: '#ddd',
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}`,
label: (context) => `获客数量: ${context.parsed.y}`,
},
},
},
@@ -111,21 +311,11 @@ export default function Home() {
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
},
color: "rgba(0, 0, 0, 0.05)",
},
},
},
@@ -133,91 +323,126 @@ export default function Home() {
});
}
// 卸载时销毁
// 组件卸载时清理图表实例
return () => {
if (chartInstance.current) {
chartInstance.current.destroy();
chartInstance.current = null;
}
};
}, []);
}, [isLoading]);
if (isLoading) {
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>
</div>
</div>
}
footer={<BottomNav />}
>
<div className="bg-gray-50">
<div className="p-4 space-y-4">
<div className="grid grid-cols-3 gap-3">
{[...Array(3)].map((_, i) => (
<Card key={i} className="p-3 bg-white animate-pulse">
<div className="h-4 bg-gray-200 rounded mb-2"></div>
<div className="h-6 bg-gray-200 rounded"></div>
</Card>
))}
</div>
</div>
</div>
</Layout>
);
}
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>
<UnifiedHeader
title="存客宝"
showBack={false}
titleColor="blue"
rightContent={
<>
{apiError && (
<div className="text-xs text-orange-600 bg-orange-50 px-2 py-1 rounded mr-2">
API连接异常
</div>
)}
<button className="p-2 hover:bg-gray-100 rounded-full">
<Bell className="h-5 w-5 text-gray-600" />
</button>
</>
}
/>
}
footer={<BottomNav />}
>
<div className="bg-gray-50">
<div className="p-4 space-y-6">
<div className="p-4 space-y-4">
{/* 统计卡片 */}
<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 className="grid grid-cols-3 gap-3">
<div className="cursor-pointer" onClick={handleDevicesClick}>
<Card className="p-3 bg-white hover:shadow-md transition-all">
<div className="flex flex-col">
<span className="text-xs text-gray-500 mb-1"></span>
<div className="flex items-center justify-between">
<span className="text-lg font-bold text-blue-600">{stats.totalDevices}</span>
<Smartphone className="w-5 h-5 text-blue-600" />
</div>
</div>
</div>
</Card>
</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 className="cursor-pointer" onClick={handleWechatClick}>
<Card className="p-3 bg-white hover:shadow-md transition-all">
<div className="flex flex-col">
<span className="text-xs text-gray-500 mb-1"></span>
<div className="flex items-center justify-between">
<span className="text-lg font-bold text-blue-600">{stats.totalWechatAccounts}</span>
<Users className="w-5 h-5 text-blue-600" />
</div>
</div>
</div>
</Card>
</div>
<div className="p-4 bg-white rounded shadow">
<Card className="p-3 bg-white">
<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>
<span className="text-xs text-gray-500 mb-1">线</span>
<div className="flex items-center justify-between mb-1">
<span className="text-lg font-bold text-blue-600">{stats.onlineWechatAccounts}</span>
<Activity className="w-5 h-5 text-blue-600" />
</div>
<Progress
value={
stats.totalWechatAccounts > 0 ? (stats.onlineWechatAccounts / stats.totalWechatAccounts) * 100 : 0
}
className="h-1"
/>
</div>
</div>
</Card>
</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>
<Card className="p-4 bg-white">
<div className="flex justify-between items-center mb-3">
<h2 className="text-base font-semibold"></h2>
</div>
<div className="flex justify-between">
{scenarioFeatures
.sort((a, b) => b.value - a.value)
.slice(0, 4) // 只显示前4个
.map((scenario) => (
<div
key={scenario.id}
className="block flex-1 cursor-pointer"
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 className="flex flex-col items-center text-center space-y-1">
<div className={`w-10 h-10 rounded-full ${scenario.color} flex items-center justify-center`}>
<img src={scenario.icon || "/placeholder.svg"} alt={scenario.name} className="w-5 h-5" />
</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">
@@ -227,15 +452,37 @@ export default function Home() {
</div>
))}
</div>
</div>
</Card>
{/* 今日数据统计 */}
<Card className="p-4 bg-white">
<div className="flex justify-between items-center mb-3">
<h2 className="text-base font-semibold"></h2>
</div>
<div className="grid grid-cols-2 gap-4">
{todayStats.map((stat, index) => (
<div
key={index}
className="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg cursor-pointer hover:bg-gray-100 transition-colors"
onClick={() => stat.path && navigate(stat.path)}
>
<div className={`p-2 rounded-full bg-white ${stat.color}`}>{stat.icon}</div>
<div>
<div className="text-lg font-semibold">{stat.value}</div>
<div className="text-xs text-gray-500">{stat.title}</div>
</div>
</div>
))}
</div>
</Card>
{/* 每日获客趋势 */}
<div className="p-4 bg-white rounded shadow">
<h2 className="text-lg font-semibold mb-4"></h2>
<div className="w-full h-64 relative">
<Card className="p-4 bg-white">
<h2 className="text-base font-semibold mb-3"></h2>
<div className="w-full h-48 relative">
<canvas ref={chartRef} />
</div>
</div>
</Card>
</div>
</div>
</Layout>

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { ChevronRight, Settings, Bell, LogOut } from 'lucide-react';
import { Card } from '@/components/ui/card';
import { ChevronRight, Settings, Bell, LogOut, Smartphone, MessageCircle, Database, FolderOpen } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
@@ -9,21 +9,21 @@ import { useAuth } from '@/contexts/AuthContext';
import { useToast } from '@/components/ui/toast';
import Layout from '@/components/Layout';
import BottomNav from '@/components/BottomNav';
import UnifiedHeader from '@/components/UnifiedHeader';
import '@/components/Layout.css';
const menuItems = [
{ href: '/devices', label: '设备管理' },
{ href: '/wechat-accounts', label: '微信号管理' },
{ href: '/traffic-pool', label: '流量池' },
{ href: '/content', label: '内容库' },
];
export default function Profile() {
const navigate = useNavigate();
const { user, logout, isAuthenticated } = useAuth();
const { toast } = useToast();
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
const [userInfo, setUserInfo] = useState<any>(null);
const [stats, setStats] = useState({
devices: 12,
wechat: 25,
traffic: 8,
content: 156,
});
// 从localStorage获取用户信息
useEffect(() => {
@@ -33,6 +33,82 @@ export default function Profile() {
}
}, []);
// 用户信息
const currentUserInfo = {
name: userInfo?.username || user?.username || "卡若",
email: userInfo?.email || "zhangsan@example.com",
role: "管理员",
joinDate: "2023-01-15",
lastLogin: "2024-01-20 14:30",
};
// 功能模块数据
const functionModules = [
{
id: "devices",
title: "设备管理",
description: "管理您的设备和微信账号",
icon: <Smartphone className="h-5 w-5 text-blue-500" />,
count: stats.devices,
path: "/devices",
bgColor: "bg-blue-50",
},
{
id: "wechat",
title: "微信号管理",
description: "管理微信账号和好友",
icon: <MessageCircle className="h-5 w-5 text-green-500" />,
count: stats.wechat,
path: "/wechat-accounts",
bgColor: "bg-green-50",
},
{
id: "traffic",
title: "流量池",
description: "管理用户流量池和分组",
icon: <Database className="h-5 w-5 text-purple-500" />,
count: stats.traffic,
path: "/traffic-pool",
bgColor: "bg-purple-50",
},
{
id: "content",
title: "内容库",
description: "管理营销内容和素材",
icon: <FolderOpen className="h-5 w-5 text-orange-500" />,
count: stats.content,
path: "/content",
bgColor: "bg-orange-50",
},
];
// 加载统计数据
const loadStats = async () => {
try {
// 这里可以调用实际的API
// const [deviceStats, wechatStats, trafficStats, contentStats] = await Promise.allSettled([
// getDeviceStats(),
// getWechatStats(),
// getTrafficStats(),
// getContentStats(),
// ]);
// 暂时使用模拟数据
setStats({
devices: 12,
wechat: 25,
traffic: 8,
content: 156,
});
} catch (error) {
console.error("加载统计数据失败:", error);
}
};
useEffect(() => {
loadStats();
}, []);
const handleLogout = () => {
// 清除本地存储的用户信息
localStorage.removeItem('token');
@@ -43,11 +119,15 @@ export default function Profile() {
logout();
navigate('/login');
toast({
title: '退出登录',
description: '感谢使用存客宝',
title: '退出成功',
description: '您已安全退出系统',
});
};
const handleFunctionClick = (path: string) => {
navigate(path);
};
if (!isAuthenticated) {
return (
<div className="flex h-screen items-center justify-center">
@@ -55,88 +135,105 @@ export default function Profile() {
</div>
);
}
return (
<Layout
header={
<div className="bg-white/80 backdrop-blur-sm border-b">
<div className="flex justify-between items-center p-4">
<h1 className="text-xl font-semibold text-blue-600"></h1>
<div className="flex items-center space-x-2">
<Button variant="ghost" size="icon">
<Settings className="w-5 h-5" />
</Button>
<Button variant="ghost" size="icon">
<Bell className="w-5 h-5" />
</Button>
</div>
</div>
</div>
<UnifiedHeader
title="我的"
showBack={false}
titleColor="blue"
actions={[
{
type: 'icon',
icon: Bell,
onClick: () => console.log('Notifications'),
},
{
type: 'icon',
icon: Settings,
onClick: () => console.log('Settings'),
},
]}
/>
}
footer={<BottomNav />}
>
<div className="bg-gradient-to-b from-blue-50 to-white">
<div className="p-4 space-y-6">
{/* 用户信息卡片 */}
<Card className="p-6">
<div className="flex items-center space-x-4">
<Avatar className="w-20 h-20">
<AvatarImage src={userInfo?.avatar || user?.avatar || ''} />
<AvatarFallback className="bg-gradient-to-br from-blue-500 to-purple-600 text-white text-xl font-semibold shadow-lg">
{(userInfo?.username || user?.username || '用户').slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="flex-1">
<h2 className="text-xl font-semibold text-blue-600">
{userInfo?.username || user?.username || '用户'}
</h2>
<p className="text-gray-500">
: {userInfo?.account || user?.account || Math.floor(10000000 + Math.random() * 90000000).toString()}
</p>
<div className="mt-2">
<Button
variant="outline"
size="sm"
onClick={() => {
toast({
title: '功能开发中',
description: '编辑资料功能正在开发中',
});
}}
>
</Button>
<div className="bg-gray-50 pb-16">
<div className="p-4 space-y-4">
{/* 用户信息卡片 */}
<Card>
<CardContent className="p-4">
<div className="flex items-center space-x-4">
<Avatar className="h-16 w-16">
<AvatarImage src={userInfo?.avatar || user?.avatar || ''} />
<AvatarFallback className="bg-gray-200 text-gray-600 text-lg font-medium">
{currentUserInfo.name.charAt(0)}
</AvatarFallback>
</Avatar>
<div className="flex-1">
<div className="flex items-center space-x-2 mb-1">
<h2 className="text-lg font-medium">{currentUserInfo.name}</h2>
<span className="px-2 py-1 text-xs bg-gradient-to-r from-orange-400 to-orange-500 text-white rounded-full font-medium shadow-sm">
{currentUserInfo.role}
</span>
</div>
<p className="text-sm text-gray-600 mb-2">{currentUserInfo.email}</p>
<div className="text-xs text-gray-500">
<div>: {currentUserInfo.lastLogin}</div>
</div>
</div>
<div className="flex flex-col space-y-2">
<Button variant="ghost" size="icon">
<Bell className="h-5 w-5" />
</Button>
<Button variant="ghost" size="icon">
<Settings className="h-5 w-5" />
</Button>
</div>
</div>
</div>
</div>
</Card>
</CardContent>
</Card>
{/* 功能菜单 */}
<Card className="divide-y">
{menuItems.map((item) => (
<div
key={item.href || item.label}
className="p-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
onClick={() => (item.href ? navigate(item.href) : null)}
>
<div className="flex items-center">
<span>{item.label}</span>
{/* 我的功能 */}
<Card>
<CardContent className="p-4">
<div className="space-y-2">
{functionModules.map((module) => (
<div
key={module.id}
className="flex items-center p-4 rounded-lg border hover:bg-gray-50 cursor-pointer transition-colors w-full"
onClick={() => handleFunctionClick(module.path)}
>
<div className={`p-2 rounded-lg ${module.bgColor} mr-3`}>{module.icon}</div>
<div className="flex-1">
<div className="font-medium text-sm">{module.title}</div>
<div className="text-xs text-gray-500">{module.description}</div>
</div>
<div className="flex items-center space-x-2">
<span className="px-2 py-1 text-xs bg-gray-50 text-gray-700 rounded-full border border-gray-200 font-medium shadow-sm">
{module.count}
</span>
<ChevronRight className="h-4 w-4 text-gray-400" />
</div>
</div>
))}
</div>
<ChevronRight className="w-5 h-5 text-gray-400" />
</div>
))}
</Card>
</CardContent>
</Card>
{/* 退出登录按钮 */}
<Button
variant="ghost"
className="w-full text-red-500 hover:text-red-600 hover:bg-red-50 mt-6"
onClick={() => setShowLogoutDialog(true)}
>
<LogOut className="w-5 h-5 mr-2" />
退
</Button>
</div>
{/* 退出登录 */}
<Button
variant="outline"
className="w-full text-red-600 border-red-200 hover:bg-red-50 bg-transparent"
onClick={() => setShowLogoutDialog(true)}
>
<LogOut className="h-4 w-4 mr-2" />
退
</Button>
</div>
</div>
{/* 退出登录确认对话框 */}
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
<DialogContent>

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { Plus, TrendingUp, Loader2 } from 'lucide-react';
import PageHeader from '@/components/PageHeader';
import UnifiedHeader from '@/components/UnifiedHeader';
import Layout from '@/components/Layout';
import BottomNav from '@/components/BottomNav';
import { fetchScenes, type SceneItem } from '@/api/scenarios';
@@ -80,7 +80,7 @@ export default function Scenarios() {
return (
<Layout
header={
<PageHeader
<UnifiedHeader
title="场景获客"
showBack={false}
/>
@@ -101,7 +101,7 @@ export default function Scenarios() {
return (
<Layout
header={
<PageHeader
<UnifiedHeader
title="场景获客"
showBack={false}
/>
@@ -126,18 +126,19 @@ export default function Scenarios() {
return (
<Layout
header={
<PageHeader
<UnifiedHeader
title="场景获客"
showBack={false}
rightContent={
<button
onClick={handleNewPlan}
className="flex items-center px-3 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors text-sm"
>
<Plus className="h-4 w-4 mr-1" />
</button>
}
titleColor="blue"
actions={[
{
type: 'button',
icon: Plus,
label: '新建计划',
size: 'sm',
onClick: handleNewPlan,
},
]}
/>
}
footer={<BottomNav />}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress';
import Layout from '@/components/Layout';
import PageHeader from '@/components/PageHeader';
import UnifiedHeader from '@/components/UnifiedHeader';
import BottomNav from '@/components/BottomNav';
import '@/components/Layout.css';
@@ -106,8 +106,9 @@ export default function Workspace() {
return (
<Layout
header={
<PageHeader
<UnifiedHeader
title="工作台"
titleColor="blue"
defaultBackPath="/"
/>
}