feat: 登录模块完成

This commit is contained in:
许永平
2025-07-04 15:49:22 +08:00
parent aad2d055b7
commit 8de943d4a4
22 changed files with 1661 additions and 257 deletions

View File

@@ -1,142 +1,48 @@
import React, { useEffect, useRef, useState } from 'react';
import { Chart, registerables } from 'chart.js';
import React, { useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { Smartphone, Users, Activity, Bell } from 'lucide-react';
Chart.register(...registerables);
import { Bell, Smartphone, Users, Activity } from 'lucide-react';
// 模拟数据
const stats = {
totalDevices: 12,
totalWechatAccounts: 8,
onlineWechatAccounts: 6,
};
const scenarioFeatures = [
{
id: "douyin",
name: "抖音获客",
value: 156,
icon: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-QR8ManuDplYTySUJsY4mymiZkDYnQ9.png",
color: "bg-red-100",
},
{
id: "xiaohongshu",
name: "小红书获客",
value: 89,
icon: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-yvnMxpoBUzcvEkr8DfvHgPHEo1kmQ3.png",
color: "bg-pink-100",
},
{
id: "gongzhonghao",
name: "公众号获客",
value: 234,
icon: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-Gsg0CMf5tsZb41mioszdjqU1WmsRxW.png",
color: "bg-green-100",
},
{
id: "haibao",
name: "海报获客",
value: 167,
icon: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-x92XJgXy4MI7moNYlA1EAes2FqDxMH.png",
color: "bg-blue-100",
},
];
export default function Home() {
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 chartRef = useRef<HTMLCanvasElement>(null);
const handleDevicesClick = () => {
navigate('/devices');
@@ -146,6 +52,44 @@ export default function Home() {
navigate('/wechat-accounts');
};
useEffect(() => {
if (chartRef.current) {
const ctx = chartRef.current.getContext('2d');
if (ctx) {
// 清除之前的图表
ctx.clearRect(0, 0, chartRef.current.width, chartRef.current.height);
// 绘制简单的柱状图
const data = [12, 19, 15, 25, 22, 30, 28];
const labels = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
const barWidth = 30;
const barSpacing = 20;
const startX = 50;
const startY = 200;
ctx.fillStyle = '#3B82F6';
data.forEach((value, index) => {
const x = startX + index * (barWidth + barSpacing);
const height = (value / 30) * 150;
const y = startY - height;
ctx.fillRect(x, y, barWidth, height);
// 绘制数值
ctx.fillStyle = '#374151';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.fillText(value.toString(), x + barWidth / 2, y - 5);
// 绘制标签
ctx.fillText(labels[index], x + barWidth / 2, startY + 20);
ctx.fillStyle = '#3B82F6';
});
}
}
}, []);
return (
<div className="flex-1 overflow-y-auto pb-20 bg-gray-50">
<header className="sticky top-0 z-10 bg-white border-b">

View File

@@ -1,5 +1,432 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { Eye, EyeOff, Phone } from 'lucide-react';
import { useAuth } from '../../contexts/AuthContext';
import { useToast } from '../../components/ui/toast';
import { authApi } from '../../api';
import WeChatIcon from '../../components/icons/WeChatIcon';
import AppleIcon from '../../components/icons/AppleIcon';
// 定义登录表单类型
interface LoginForm {
phone: string;
password: string;
verificationCode: string;
agreeToTerms: boolean;
}
export default function Login() {
return <div></div>;
const [showPassword, setShowPassword] = useState(false);
const [activeTab, setActiveTab] = useState<'password' | 'verification'>('password');
const [isLoading, setIsLoading] = useState(false);
const [countdown, setCountdown] = useState(0);
const [form, setForm] = useState<LoginForm>({
phone: '',
password: '',
verificationCode: '',
agreeToTerms: false,
});
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { toast } = useToast();
const { login } = useAuth();
// 倒计时效果
useEffect(() => {
if (countdown > 0) {
const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
return () => clearTimeout(timer);
}
}, [countdown]);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setForm((prev) => ({ ...prev, [name]: value }));
};
const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setForm((prev) => ({ ...prev, agreeToTerms: e.target.checked }));
};
const validateForm = () => {
if (!form.phone) {
toast({
variant: 'destructive',
title: '请输入手机号',
description: '手机号不能为空',
});
return false;
}
// 手机号格式验证
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(form.phone)) {
toast({
variant: 'destructive',
title: '手机号格式错误',
description: '请输入正确的11位手机号',
});
return false;
}
if (!form.agreeToTerms) {
toast({
variant: 'destructive',
title: '请同意用户协议',
description: '需要同意用户协议和隐私政策才能继续',
});
return false;
}
if (activeTab === 'password' && !form.password) {
toast({
variant: 'destructive',
title: '请输入密码',
description: '密码不能为空',
});
return false;
}
if (activeTab === 'verification' && !form.verificationCode) {
toast({
variant: 'destructive',
title: '请输入验证码',
description: '验证码不能为空',
});
return false;
}
return true;
};
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) return;
setIsLoading(true);
try {
if (activeTab === 'password') {
// 发送账号密码登录请求
const response = await authApi.login(form.phone, form.password);
if (response.code === 200 && response.data) {
// 保存登录信息
localStorage.setItem('token', response.data.token);
localStorage.setItem('token_expired', response.data.token_expired);
localStorage.setItem('s2_accountId', response.data.member.s2_accountId);
// 保存用户信息
localStorage.setItem('userInfo', JSON.stringify(response.data.member));
// 调用认证上下文的登录方法
login(response.data.token, response.data.member);
// 显示成功提示
toast({
title: '登录成功',
description: '欢迎回来!',
});
// 跳转到首页或重定向URL
const returnUrl = searchParams.get('returnUrl');
if (returnUrl) {
navigate(decodeURIComponent(returnUrl));
} else {
navigate('/');
}
} else {
throw new Error(response.message || '登录失败');
}
} else {
// 验证码登录
const response = await authApi.loginWithCode(form.phone, form.verificationCode);
if (response.code === 200 && response.data) {
// 保存登录信息
localStorage.setItem('token', response.data.token);
localStorage.setItem('token_expired', response.data.token_expired);
localStorage.setItem('s2_accountId', response.data.member.s2_accountId);
// 保存用户信息
localStorage.setItem('userInfo', JSON.stringify(response.data.member));
// 调用认证上下文的登录方法
login(response.data.token, response.data.member);
// 显示成功提示
toast({
title: '登录成功',
description: '欢迎回来!',
});
// 跳转到首页或重定向URL
const returnUrl = searchParams.get('returnUrl');
if (returnUrl) {
navigate(decodeURIComponent(returnUrl));
} else {
navigate('/');
}
} else {
throw new Error(response.message || '登录失败');
}
}
} catch (error) {
toast({
variant: 'destructive',
title: '登录失败',
description: error instanceof Error ? error.message : '请稍后重试',
});
} finally {
setIsLoading(false);
}
};
const handleSendVerificationCode = async () => {
if (!form.phone) {
toast({
variant: 'destructive',
title: '请输入手机号',
description: '发送验证码需要手机号',
});
return;
}
// 手机号格式验证
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(form.phone)) {
toast({
variant: 'destructive',
title: '手机号格式错误',
description: '请输入正确的11位手机号',
});
return;
}
try {
setIsLoading(true);
const response = await authApi.sendVerificationCode(form.phone);
if (response.code === 200) {
toast({
title: '验证码已发送',
description: '请查收短信验证码',
});
setCountdown(60); // 开始60秒倒计时
} else {
throw new Error(response.message || '发送失败');
}
} catch (error) {
toast({
variant: 'destructive',
title: '发送失败',
description: error instanceof Error ? error.message : '请稍后重试',
});
} finally {
setIsLoading(false);
}
};
const handleWechatLogin = () => {
// 微信登录逻辑
toast({
title: '功能开发中',
description: '微信登录功能正在开发中,请使用其他方式登录',
});
};
const handleAppleLogin = () => {
// Apple登录逻辑
toast({
title: '功能开发中',
description: 'Apple登录功能正在开发中请使用其他方式登录',
});
};
return (
<div className="min-h-screen bg-white flex items-center justify-center px-4 py-8">
<div className="max-w-md w-full">
{/* 标题 */}
<div className="text-center mb-8">
<h1 className="text-2xl font-bold text-gray-900 mb-2"></h1>
<p className="text-gray-600 text-sm"> / / Apple </p>
</div>
{/* 标签页切换 */}
<div className="flex border-b border-gray-200 mb-6">
<button
onClick={() => setActiveTab('password')}
className={`flex-1 py-3 text-center border-b-2 transition-colors font-medium ${
activeTab === 'password'
? 'border-blue-500 text-blue-500'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
</button>
<button
onClick={() => setActiveTab('verification')}
className={`flex-1 py-3 text-center border-b-2 transition-colors font-medium ${
activeTab === 'verification'
? 'border-blue-500 text-blue-500'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
</button>
</div>
<form onSubmit={handleLogin} className="space-y-6">
{/* 手机号输入 */}
<div className="relative">
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<div className="relative">
<input
type="tel"
name="phone"
value={form.phone}
onChange={handleInputChange}
placeholder="请输入手机号"
className="w-full pl-16 pr-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 transition-colors"
disabled={isLoading}
/>
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-500 flex items-center gap-1 text-sm">
<Phone className="h-4 w-4" />
+86
</span>
</div>
</div>
{/* 密码输入 */}
{activeTab === 'password' && (
<div className="relative">
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
name="password"
value={form.password}
onChange={handleInputChange}
placeholder="请输入密码"
className="w-full pl-4 pr-12 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 transition-colors"
disabled={isLoading}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700 transition-colors"
>
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
</button>
</div>
</div>
)}
{/* 验证码输入 */}
{activeTab === 'verification' && (
<div className="relative">
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<div className="relative">
<input
type="text"
name="verificationCode"
value={form.verificationCode}
onChange={handleInputChange}
placeholder="请输入验证码"
className="w-full pl-4 pr-32 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 transition-colors"
disabled={isLoading}
/>
<button
type="button"
onClick={handleSendVerificationCode}
disabled={isLoading || countdown > 0}
className={`absolute right-2 top-1/2 -translate-y-1/2 px-4 h-10 rounded-md text-sm font-medium transition-colors ${
countdown > 0
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'bg-blue-50 text-blue-500 hover:bg-blue-100'
}`}
>
{countdown > 0 ? `${countdown}s` : '获取验证码'}
</button>
</div>
</div>
)}
{/* 用户协议 */}
<div className="flex items-start space-x-3">
<input
type="checkbox"
id="terms"
checked={form.agreeToTerms}
onChange={handleCheckboxChange}
disabled={isLoading}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mt-0.5"
/>
<label
htmlFor="terms"
className="text-sm text-gray-600 leading-relaxed cursor-pointer"
>
<a href="#" className="text-blue-500 hover:text-blue-600 mx-1"></a>
<a href="#" className="text-blue-500 hover:text-blue-600 mx-1"></a>
</label>
</div>
{/* 登录按钮 */}
<button
type="submit"
className="w-full py-3 bg-blue-500 hover:bg-blue-600 text-white rounded-lg font-medium transition-colors disabled:bg-gray-300 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
disabled={isLoading}
>
{isLoading ? (
<div className="flex items-center justify-center">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
...
</div>
) : (
'登录'
)}
</button>
{/* 分割线 */}
<div className="relative my-8">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-200"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-4 bg-white text-gray-500"></span>
</div>
</div>
{/* 第三方登录 */}
<div className="flex justify-center space-x-8">
<button
type="button"
onClick={handleWechatLogin}
className="flex flex-col items-center space-y-2 p-4 text-gray-500 hover:text-gray-700 transition-colors rounded-lg hover:bg-gray-50"
>
<WeChatIcon className="h-8 w-8" />
<span className="text-xs"></span>
</button>
<button
type="button"
onClick={handleAppleLogin}
className="flex flex-col items-center space-y-2 p-4 text-gray-500 hover:text-gray-700 transition-colors rounded-lg hover:bg-gray-50"
>
<AppleIcon className="h-8 w-8" />
<span className="text-xs">Apple</span>
</button>
</div>
</form>
</div>
</div>
);
}

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Plus, Users, TrendingUp, Calendar } from 'lucide-react';
import { Plus, Calendar } from 'lucide-react';
interface Plan {
id: string;

View File

@@ -1,5 +1,153 @@
import React from 'react';
import { useAuth } from '../../contexts/AuthContext';
import { useToast } from '../../components/ui/toast';
import { LogOut, User, Settings, Shield, Bell } from 'lucide-react';
export default function Profile() {
return <div></div>;
const { user, logout, isAuthenticated } = useAuth();
const { toast } = useToast();
const handleLogout = () => {
logout();
toast({
title: '已退出登录',
description: '感谢使用存客宝',
});
};
const menuItems = [
{
icon: <User className="h-5 w-5" />,
title: '个人信息',
description: '查看和编辑个人资料',
onClick: () => {
toast({
title: '功能开发中',
description: '个人信息编辑功能正在开发中',
});
}
},
{
icon: <Settings className="h-5 w-5" />,
title: '账户设置',
description: '密码、安全设置等',
onClick: () => {
toast({
title: '功能开发中',
description: '账户设置功能正在开发中',
});
}
},
{
icon: <Shield className="h-5 w-5" />,
title: '隐私安全',
description: '隐私设置和安全选项',
onClick: () => {
toast({
title: '功能开发中',
description: '隐私安全功能正在开发中',
});
}
},
{
icon: <Bell className="h-5 w-5" />,
title: '消息通知',
description: '通知设置和消息管理',
onClick: () => {
toast({
title: '功能开发中',
description: '消息通知功能正在开发中',
});
}
}
];
if (!isAuthenticated) {
return (
<div className="flex h-screen items-center justify-center">
<div className="text-gray-500"></div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50 p-4">
<div className="max-w-md mx-auto space-y-6">
{/* 用户信息卡片 */}
<div className="bg-white rounded-lg shadow-sm p-6">
<div className="flex items-center space-x-4">
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center">
{user?.avatar ? (
<img
src={user.avatar}
alt="头像"
className="w-16 h-16 rounded-full object-cover"
/>
) : (
<User className="h-8 w-8 text-blue-600" />
)}
</div>
<div className="flex-1">
<h2 className="text-xl font-semibold text-gray-900">
{user?.username || '用户'}
</h2>
<p className="text-gray-500">
{user?.account || '暂无手机号'}
</p>
{user?.s2_accountId && (
<p className="text-sm text-gray-400">
ID: {user.s2_accountId}
</p>
)}
</div>
</div>
</div>
{/* 菜单列表 */}
<div className="bg-white rounded-lg shadow-sm">
{menuItems.map((item, index) => (
<div key={index}>
<button
onClick={item.onClick}
className="w-full flex items-center space-x-4 p-4 hover:bg-gray-50 transition-colors"
>
<div className="text-gray-400">
{item.icon}
</div>
<div className="flex-1 text-left">
<h3 className="font-medium text-gray-900">{item.title}</h3>
<p className="text-sm text-gray-500">{item.description}</p>
</div>
<div className="text-gray-400">
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</div>
</button>
{index < menuItems.length - 1 && (
<div className="border-b border-gray-100 mx-4" />
)}
</div>
))}
</div>
{/* 退出登录按钮 */}
<div className="bg-white rounded-lg shadow-sm">
<button
onClick={handleLogout}
className="w-full flex items-center space-x-4 p-4 text-red-600 hover:bg-red-50 transition-colors"
>
<LogOut className="h-5 w-5" />
<span className="font-medium">退</span>
</button>
</div>
{/* 版本信息 */}
<div className="text-center text-sm text-gray-400">
<p> v1.0.0</p>
<p className="mt-1">© 2024 . All rights reserved.</p>
</div>
</div>
</div>
);
}

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
import { ArrowLeft, Plus, Users, TrendingUp, Calendar, Settings } from 'lucide-react';
import { useParams, useNavigate } from 'react-router-dom';
import { ArrowLeft, Plus, Users, TrendingUp, Calendar } from 'lucide-react';
interface Plan {
id: string;
@@ -26,7 +26,6 @@ interface ScenarioData {
export default function ScenarioDetail() {
const { scenarioId } = useParams<{ scenarioId: string }>();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const [scenario, setScenario] = useState<ScenarioData | null>(null);
const [plans, setPlans] = useState<Plan[]>([]);
const [loading, setLoading] = useState(true);

View File

@@ -17,36 +17,36 @@ export default function Scenarios() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
// AI智能获客用本地 mock 数据
const aiScenarios = [
{
id: "ai-friend",
name: "AI智能加友",
icon: "🤖",
count: 245,
growth: "+18.5%",
description: "智能分析目标用户画像,自动筛选优质客户",
path: "/scenarios/ai-friend",
},
{
id: "ai-group",
name: "AI群引流",
icon: "🤖",
count: 178,
growth: "+15.2%",
description: "智能群管理,提高群活跃度,增强获客效果",
path: "/scenarios/ai-group",
},
{
id: "ai-conversion",
name: "AI场景转化",
icon: "🤖",
count: 134,
growth: "+12.8%",
description: "多场景智能营销,提升获客转化率",
path: "/scenarios/ai-conversion",
},
];
// AI智能获客用本地 mock 数据(暂时注释掉,可以后续启用)
// const aiScenarios = [
// {
// id: "ai-friend",
// name: "AI智能加友",
// icon: "🤖",
// count: 245,
// growth: "+18.5%",
// description: "智能分析目标用户画像,自动筛选优质客户",
// path: "/scenarios/ai-friend",
// },
// {
// id: "ai-group",
// name: "AI群引流",
// icon: "🤖",
// count: 178,
// growth: "+15.2%",
// description: "智能群管理,提高群活跃度,增强获客效果",
// path: "/scenarios/ai-group",
// },
// {
// id: "ai-conversion",
// name: "AI场景转化",
// icon: "🤖",
// count: 134,
// growth: "+12.8%",
// description: "多场景智能营销,提升获客转化率",
// path: "/scenarios/ai-conversion",
// },
// ];
useEffect(() => {
const fetchScenarios = async () => {