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

搞登录拦截模块
This commit is contained in:
笔记本里的永平
2025-07-18 12:03:13 +08:00
parent 6f6cbadacd
commit d8c59f43c2
24 changed files with 1136 additions and 136 deletions

View File

@@ -1,8 +1,5 @@
import React from "react";
import AppRouter from "@/router";
import { Button } from "antd";
import { Button as MobileButton } from "antd-mobile";
function App() {
return (
<>

View File

@@ -1,20 +1,20 @@
import React from "react";
import "layout.module.scss";
interface LayoutProps {
loading?: boolean;
children?: React.ReactNode;
header?: React.ReactNode;
footer?: React.ReactNode;
}
const Layout: React.FC<LayoutProps> = ({ children, header, footer }) => {
return (
<div className="container">
{header && <header>{header}</header>}
<main className="bg-gray-50">{children}</main>
{footer && <footer>{footer}</footer>}
</div>
);
};
export default Layout;
import React from "react";
import styles from "./layout.module.scss";
interface LayoutProps {
loading?: boolean;
children?: React.ReactNode;
header?: React.ReactNode;
footer?: React.ReactNode;
}
const Layout: React.FC<LayoutProps> = ({ children, header, footer }) => {
return (
<div className={styles.container}>
{header && <header>{header}</header>}
<main>{children}</main>
{footer && <footer>{footer}</footer>}
</div>
);
};
export default Layout;

View File

@@ -0,0 +1,53 @@
import React from "react";
import ReactECharts from "echarts-for-react";
interface LineChartProps {
title?: string;
xData: string[];
yData: number[];
height?: number | string;
}
const LineChart: React.FC<LineChartProps> = ({
title = "",
xData,
yData,
height = 200,
}) => {
const option = {
title: {
text: title,
left: "center",
textStyle: { fontSize: 16 },
},
tooltip: { trigger: "axis" },
xAxis: {
type: "category",
data: xData,
boundaryGap: false,
},
yAxis: {
type: "value",
boundaryGap: ["10%", "10%"], // 上下留白
min: (value: any) => value.min - 10, // 下方多留一点空间
max: (value: any) => value.max + 10, // 上方多留一点空间
minInterval: 1,
axisLabel: { margin: 12 },
},
series: [
{
data: yData,
type: "line",
smooth: true,
symbol: "circle",
lineStyle: { color: "#1677ff" },
itemStyle: { color: "#1677ff" },
},
],
grid: { left: 40, right: 24, top: 40, bottom: 32 },
};
return <ReactECharts option={option} style={{ height, width: "100%" }} />;
};
export default LineChart;

View File

@@ -0,0 +1,81 @@
import React, { useState, useEffect } from "react";
import { TabBar } from "antd-mobile";
import {
AppOutline,
ShopbagOutline,
PieOutline,
UserOutline,
} from "antd-mobile-icons";
import { useLocation, useNavigate } from "react-router-dom";
const tabs = [
{
key: "home",
title: "首页",
icon: <AppOutline />,
path: "/",
},
{
key: "scene",
title: "场景获客",
icon: <ShopbagOutline />,
path: "/scene",
},
{
key: "work",
title: "工作台",
icon: <PieOutline />,
path: "/work",
},
{
key: "mine",
title: "我的",
icon: <UserOutline />,
path: "/mine",
},
];
// 需要展示菜单的路由白名单(可根据实际业务调整)
const menuPaths = ["/", "/scene", "/work", "/mine"];
const MeauMobile: React.FC = () => {
const location = useLocation();
const navigate = useNavigate();
const [activeKey, setActiveKey] = useState("home");
// 根据当前路由自动设置 activeKey支持嵌套路由
useEffect(() => {
const found = tabs.find((tab) =>
tab.path === "/"
? location.pathname === "/"
: location.pathname.startsWith(tab.path)
);
if (found) setActiveKey(found.key);
}, [location.pathname]);
// 判断当前路由是否需要展示菜单
const showMenu = menuPaths.some((path) =>
path === "/"
? location.pathname === "/"
: location.pathname.startsWith(path)
);
if (!showMenu) return null;
return (
<TabBar
style={{ background: "#fff" }}
activeKey={activeKey}
onChange={(key) => {
setActiveKey(key);
const tab = tabs.find((t) => t.key === key);
if (tab && tab.path) navigate(tab.path);
}}
>
{tabs.map((item) => (
<TabBar.Item key={item.key} icon={item.icon} title={item.title} />
))}
</TabBar>
);
};
export default MeauMobile;

View File

@@ -10,5 +10,22 @@ export function getWechatStats() {
return request('/v1/dashboard/wechat-stats', {}, 'GET');
}
// 你可以根据需要继续添加其他接口
// 例如:场景获客统计、今日数据统计等
// 今日数据统计
export function getTodayStats() {
return request('/v1/dashboard/today-stats', {}, 'GET');
}
// 首页仪表盘总览
export function getDashboard() {
return request('/v1/dashboard', {}, 'GET');
}
// 获客场景统计
export function getPlanStats(params:any) {
return request('/v1/dashboard/plan-stats', params, 'GET');
}
// 近七天统计
export function getSevenDayStats() {
return request('/v1/dashboard/sevenDay-stats', {}, 'GET');
}

View File

@@ -0,0 +1,70 @@
.home-page {
padding: 12px;
background: #f5f5f5;
}
.home-cards {
display: flex;
gap: 12px;
margin-bottom: 12px;
> :global(.adm-card) {
flex: 1;
}
}
.home-section {
margin-bottom: 12px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
}
.home-section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 15px;
}
.home-scene-stats {
display: flex;
justify-content: space-between;
margin: 0 8px 8px 8px;
}
.home-scene-item {
flex: 1;
text-align: center;
}
.home-scene-icon {
margin: 0 auto 4px auto;
}
.home-scene-value {
font-size: 18px;
font-weight: bold;
color: #1677ff;
}
.home-scene-label {
font-size: 12px;
color: #888;
}
.home-today-item {
text-align: center;
padding: 8px 0;
background: #f7f8fa;
border-radius: 8px;
}
.home-today-value {
font-size: 18px;
font-weight: bold;
color: #1677ff;
}
.home-today-label {
font-size: 12px;
color: #888;
}
.home-chart {
margin-top: 8px;
width: 100%;
min-height: 100px;
}

View File

@@ -1,35 +0,0 @@
.home-page {
padding: 16px;
background: #f5f5f5;
min-height: 100vh;
.home-title {
font-size: 22px;
font-weight: bold;
margin-bottom: 16px;
color: #1677ff;
text-align: center;
}
.home-cards {
display: flex;
gap: 16px;
flex-direction: column;
.home-card {
.home-card-value {
font-size: 28px;
font-weight: bold;
color: #1677ff;
margin-bottom: 8px;
}
.home-card-desc {
font-size: 14px;
color: #888;
}
}
}
.home-loading {
margin-top: 24px;
text-align: center;
color: #999;
font-size: 16px;
}
}

View File

@@ -1,65 +1,121 @@
import React, { useEffect, useState } from "react";
import { Card, Toast } from "antd-mobile";
import { getDeviceStats, getWechatStats } from "./api";
import Layout from "@/com/Layout/Layout";
import "./index.scss";
const Home: React.FC = () => {
const [stats, setStats] = useState({
totalDevices: 0,
onlineDevices: 0,
totalWechatAccounts: 0,
onlineWechatAccounts: 0,
});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchStats = async () => {
setLoading(true);
try {
const [device, wechat] = await Promise.all([
getDeviceStats(),
getWechatStats(),
]);
setStats({
totalDevices: device.total || 0,
onlineDevices: device.online || 0,
totalWechatAccounts: wechat.total || 0,
onlineWechatAccounts: wechat.active || 0,
});
} catch (err: any) {
Toast.show({
content: err?.message || "数据加载失败",
position: "top",
});
} finally {
setLoading(false);
}
};
fetchStats();
}, []);
return (
<Layout
loading={loading}
header={<h1 className="home-title"> Home</h1>}
footer={<div></div>} // 如有底部内容可加
>
<div className="home-cards">
<Card title="设备数量" className="home-card">
<div className="home-card-value">{stats.totalDevices}</div>
<div className="home-card-desc">线{stats.onlineDevices}</div>
</Card>
<Card title="微信号数量" className="home-card">
<div className="home-card-value">{stats.totalWechatAccounts}</div>
<div className="home-card-desc">
线{stats.onlineWechatAccounts}
</div>
</Card>
</div>
{loading && <div className="home-loading">...</div>}
</Layout>
);
};
export default Home;
import React, { useEffect, useState } from "react";
import { Card, NavBar, TabBar, Grid } from "antd-mobile";
import {
AppOutline,
UserOutline,
PieOutline, // 替换 BarChartOutline
ShopbagOutline,
} from "antd-mobile-icons";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
import Layout from "@/components/Layout/Layout";
import style from "./index.module.scss";
import LineChart from "@/components/LineChart";
import { getPlanStats } from "./api";
const sceneStats = [
{
label: "公众号获客",
value: 234,
icon: <ShopbagOutline style={{ fontSize: 28, color: "#4caf50" }} />,
},
{
label: "海报获客",
value: 167,
icon: <AppOutline style={{ fontSize: 28, color: "#ff9800" }} />,
},
{
label: "抖音获客",
value: 156,
icon: <PieOutline style={{ fontSize: 28, color: "#2196f3" }} />,
},
{
label: "小红书获客",
value: 89,
icon: <UserOutline style={{ fontSize: 28, color: "#e91e63" }} />,
},
];
const todayStats = [
{ label: "朋友圈同步", value: 12 },
{ label: "群发任务", value: 8 },
{ label: "获客转化", value: "85%" },
{ label: "系统活跃度", value: "98%" },
];
const Home: React.FC = () => {
useEffect(() => {
getPlanStats({ num: 4 }).then((res: any) => {
console.log(res);
});
}, []);
return (
<Layout
header={
<NavBar back={null} style={{ background: "#fff" }}>
<span style={{ color: "#1677ff", fontWeight: 700 }}></span>
</NavBar>
}
footer={<MeauMobile />}
>
<div className={style["home-page"]}>
{/* 统计卡片 */}
<div className={style["home-cards"]}>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}></div>
<div className={style["home-card-value"]}>0</div>
</Card>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}></div>
<div className={style["home-card-value"]}>0</div>
</Card>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}>线</div>
<div className={style["home-card-value"]}>0</div>
</Card>
</div>
{/* 场景获客统计 */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<div className={style["home-scene-stats"]}>
{sceneStats.map((item) => (
<div key={item.label} className={style["home-scene-item"]}>
<div className={style["home-scene-icon"]}>{item.icon}</div>
<div className={style["home-scene-value"]}>{item.value}</div>
<div className={style["home-scene-label"]}>{item.label}</div>
</div>
))}
</div>
</Card>
{/* 今日数据 */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<Grid columns={2} gap={12}>
{todayStats.map((item) => (
<Grid.Item key={item.label}>
<div className={style["home-today-item"]}>
<div className={style["home-today-value"]}>{item.value}</div>
<div className={style["home-today-label"]}>{item.label}</div>
</div>
</Grid.Item>
))}
</Grid>
</Card>
{/* 每日获客趋势(静态图表占位) */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<div className={style["home-chart"]}>
<LineChart
xData={["周一", "周二", "周三", "周四", "周五", "周六", "周日"]}
yData={[120, 150, 180, 200, 230, 210, 190]}
/>
</div>
</Card>
</div>
</Layout>
);
};
export default Home;

View File

View File

@@ -0,0 +1,74 @@
import React, { useState } from "react";
import { Form, Input, Button, Toast } from "antd-mobile";
import { EyeInvisibleOutline, EyeOutline } from "antd-mobile-icons";
import request from "@/api/request";
import style from "./login.module.scss";
const Login: React.FC = () => {
const [loading, setLoading] = useState(false);
const [visible, setVisible] = useState(false);
const onFinish = async (values: any) => {
setLoading(true);
try {
// 假设接口为 /api/login实际请根据后端接口调整
const res = await request("/api/login", values, "POST");
// 登录成功后保存 token
localStorage.setItem("token", res.token);
Toast.show({ content: "登录成功", position: "top" });
// 跳转首页或其他页面
window.location.href = "/";
} catch (err: any) {
Toast.show({ content: err?.message || "登录失败", position: "top" });
} finally {
setLoading(false);
}
};
return (
<div className={style["login-page"]}>
<div className={style["login-title"]}></div>
<Form
layout="horizontal"
onFinish={onFinish}
footer={
<Button
block
type="submit"
color="primary"
loading={loading}
size="large"
>
</Button>
}
>
<Form.Item
name="username"
label="账号"
rules={[{ required: true, message: "请输入账号" }]}
>
<Input placeholder="请输入账号" clearable />
</Form.Item>
<Form.Item
name="password"
label="密码"
rules={[{ required: true, message: "请输入密码" }]}
>
<Input
placeholder="请输入密码"
clearable
type={visible ? "text" : "password"}
extra={
<div onClick={() => setVisible((v) => !v)}>
{visible ? <EyeOutline /> : <EyeInvisibleOutline />}
</div>
}
/>
</Form.Item>
</Form>
</div>
);
};
export default Login;

View File

@@ -0,0 +1,14 @@
import request from '@/api/request';
// 设备统计
export function getDeviceStats() {
return request('/v1/dashboard/device-stats', {}, 'GET');
}
// 微信号统计
export function getWechatStats() {
return request('/v1/dashboard/wechat-stats', {}, 'GET');
}
// 你可以根据需要继续添加其他接口
// 例如:场景获客统计、今日数据统计等

View File

@@ -0,0 +1,71 @@
.home-page {
padding: 12px;
background: #f5f5f5;
}
.home-cards {
display: flex;
gap: 12px;
margin-bottom: 12px;
> :global(.adm-card) {
flex: 1;
}
}
.home-section {
margin-bottom: 12px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
}
.home-section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 15px;
padding-left: 16px;
}
.home-scene-stats {
display: flex;
justify-content: space-between;
margin: 0 8px 8px 8px;
}
.home-scene-item {
flex: 1;
text-align: center;
}
.home-scene-icon {
margin: 0 auto 4px auto;
}
.home-scene-value {
font-size: 18px;
font-weight: bold;
color: #1677ff;
}
.home-scene-label {
font-size: 12px;
color: #888;
}
.home-today-item {
text-align: center;
padding: 8px 0;
background: #f7f8fa;
border-radius: 8px;
}
.home-today-value {
font-size: 18px;
font-weight: bold;
color: #1677ff;
}
.home-today-label {
font-size: 12px;
color: #888;
}
.home-chart {
margin-top: 8px;
width: 100%;
min-height: 100px;
}

View File

@@ -0,0 +1,132 @@
import React, { useState } from "react";
import { Card, NavBar, TabBar, Grid } from "antd-mobile";
import {
AppOutline,
UserOutline,
PieOutline, // 替换 BarChartOutline
ShopbagOutline,
BellOutline,
} from "antd-mobile-icons";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
import Layout from "@/components/Layout/Layout";
import style from "./index.module.scss";
const sceneStats = [
{
label: "公众号获客",
value: 234,
icon: <ShopbagOutline style={{ fontSize: 28, color: "#4caf50" }} />,
},
{
label: "海报获客",
value: 167,
icon: <AppOutline style={{ fontSize: 28, color: "#ff9800" }} />,
},
{
label: "抖音获客",
value: 156,
icon: <PieOutline style={{ fontSize: 28, color: "#2196f3" }} />,
},
{
label: "小红书获客",
value: 89,
icon: <UserOutline style={{ fontSize: 28, color: "#e91e63" }} />,
},
];
const todayStats = [
{ label: "朋友圈同步", value: 12 },
{ label: "群发任务", value: 8 },
{ label: "获客转化", value: "85%" },
{ label: "系统活跃度", value: "98%" },
];
const Home: React.FC = () => {
return (
<Layout
header={
<NavBar back={null} style={{ background: "#fff" }}>
<span style={{ color: "#1677ff", fontWeight: 700 }}></span>
</NavBar>
}
footer={<MeauMobile />}
>
<div className={style["home-page"]}>
{/* 统计卡片 */}
<div className={style["home-cards"]}>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}></div>
<div className={style["home-card-value"]}>0</div>
</Card>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}></div>
<div className={style["home-card-value"]}>0</div>
</Card>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}>线</div>
<div className={style["home-card-value"]}>0</div>
</Card>
</div>
{/* 场景获客统计 */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<div className={style["home-scene-stats"]}>
{sceneStats.map((item) => (
<div key={item.label} className={style["home-scene-item"]}>
<div className={style["home-scene-icon"]}>{item.icon}</div>
<div className={style["home-scene-value"]}>{item.value}</div>
<div className={style["home-scene-label"]}>{item.label}</div>
</div>
))}
</div>
</Card>
{/* 今日数据 */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<Grid columns={2} gap={12}>
{todayStats.map((item) => (
<Grid.Item key={item.label}>
<div className={style["home-today-item"]}>
<div className={style["home-today-value"]}>{item.value}</div>
<div className={style["home-today-label"]}>{item.label}</div>
</div>
</Grid.Item>
))}
</Grid>
</Card>
{/* 每日获客趋势(静态图表占位) */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<div className={style["home-chart"]}>
<svg width="100%" height="120">
<polyline
fill="none"
stroke="#1677ff"
strokeWidth="3"
points="10,100 40,80 70,60 100,50 130,40 160,60 190,80"
/>
{/* x轴文字 */}
{["周一", "周二", "周三", "周四", "周五", "周六", "周日"].map(
(d, i) => (
<text
key={d}
x={10 + i * 30}
y={115}
fontSize="12"
textAnchor="middle"
>
{d}
</text>
)
)}
</svg>
</div>
</Card>
</div>
</Layout>
);
};
export default Home;

View File

@@ -0,0 +1,14 @@
import request from '@/api/request';
// 设备统计
export function getDeviceStats() {
return request('/v1/dashboard/device-stats', {}, 'GET');
}
// 微信号统计
export function getWechatStats() {
return request('/v1/dashboard/wechat-stats', {}, 'GET');
}
// 你可以根据需要继续添加其他接口
// 例如:场景获客统计、今日数据统计等

View File

@@ -0,0 +1,71 @@
.home-page {
padding: 12px;
background: #f5f5f5;
}
.home-cards {
display: flex;
gap: 12px;
margin-bottom: 12px;
> :global(.adm-card) {
flex: 1;
}
}
.home-section {
margin-bottom: 12px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
}
.home-section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 15px;
padding-left: 16px;
}
.home-scene-stats {
display: flex;
justify-content: space-between;
margin: 0 8px 8px 8px;
}
.home-scene-item {
flex: 1;
text-align: center;
}
.home-scene-icon {
margin: 0 auto 4px auto;
}
.home-scene-value {
font-size: 18px;
font-weight: bold;
color: #1677ff;
}
.home-scene-label {
font-size: 12px;
color: #888;
}
.home-today-item {
text-align: center;
padding: 8px 0;
background: #f7f8fa;
border-radius: 8px;
}
.home-today-value {
font-size: 18px;
font-weight: bold;
color: #1677ff;
}
.home-today-label {
font-size: 12px;
color: #888;
}
.home-chart {
margin-top: 8px;
width: 100%;
min-height: 100px;
}

View File

@@ -0,0 +1,132 @@
import React, { useState } from "react";
import { Card, NavBar, TabBar, Grid } from "antd-mobile";
import {
AppOutline,
UserOutline,
PieOutline, // 替换 BarChartOutline
ShopbagOutline,
BellOutline,
} from "antd-mobile-icons";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
import Layout from "@/components/Layout/Layout";
import style from "./index.module.scss";
const sceneStats = [
{
label: "公众号获客",
value: 234,
icon: <ShopbagOutline style={{ fontSize: 28, color: "#4caf50" }} />,
},
{
label: "海报获客",
value: 167,
icon: <AppOutline style={{ fontSize: 28, color: "#ff9800" }} />,
},
{
label: "抖音获客",
value: 156,
icon: <PieOutline style={{ fontSize: 28, color: "#2196f3" }} />,
},
{
label: "小红书获客",
value: 89,
icon: <UserOutline style={{ fontSize: 28, color: "#e91e63" }} />,
},
];
const todayStats = [
{ label: "朋友圈同步", value: 12 },
{ label: "群发任务", value: 8 },
{ label: "获客转化", value: "85%" },
{ label: "系统活跃度", value: "98%" },
];
const Home: React.FC = () => {
return (
<Layout
header={
<NavBar back={null} style={{ background: "#fff" }}>
<span style={{ color: "#1677ff", fontWeight: 700 }}></span>
</NavBar>
}
footer={<MeauMobile />}
>
<div className={style["home-page"]}>
{/* 统计卡片 */}
<div className={style["home-cards"]}>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}></div>
<div className={style["home-card-value"]}>0</div>
</Card>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}></div>
<div className={style["home-card-value"]}>0</div>
</Card>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}>线</div>
<div className={style["home-card-value"]}>0</div>
</Card>
</div>
{/* 场景获客统计 */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<div className={style["home-scene-stats"]}>
{sceneStats.map((item) => (
<div key={item.label} className={style["home-scene-item"]}>
<div className={style["home-scene-icon"]}>{item.icon}</div>
<div className={style["home-scene-value"]}>{item.value}</div>
<div className={style["home-scene-label"]}>{item.label}</div>
</div>
))}
</div>
</Card>
{/* 今日数据 */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<Grid columns={2} gap={12}>
{todayStats.map((item) => (
<Grid.Item key={item.label}>
<div className={style["home-today-item"]}>
<div className={style["home-today-value"]}>{item.value}</div>
<div className={style["home-today-label"]}>{item.label}</div>
</div>
</Grid.Item>
))}
</Grid>
</Card>
{/* 每日获客趋势(静态图表占位) */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<div className={style["home-chart"]}>
<svg width="100%" height="120">
<polyline
fill="none"
stroke="#1677ff"
strokeWidth="3"
points="10,100 40,80 70,60 100,50 130,40 160,60 190,80"
/>
{/* x轴文字 */}
{["周一", "周二", "周三", "周四", "周五", "周六", "周日"].map(
(d, i) => (
<text
key={d}
x={10 + i * 30}
y={115}
fontSize="12"
textAnchor="middle"
>
{d}
</text>
)
)}
</svg>
</div>
</Card>
</div>
</Layout>
);
};
export default Home;

View File

@@ -0,0 +1,14 @@
import request from '@/api/request';
// 设备统计
export function getDeviceStats() {
return request('/v1/dashboard/device-stats', {}, 'GET');
}
// 微信号统计
export function getWechatStats() {
return request('/v1/dashboard/wechat-stats', {}, 'GET');
}
// 你可以根据需要继续添加其他接口
// 例如:场景获客统计、今日数据统计等

View File

@@ -0,0 +1,71 @@
.home-page {
padding: 12px;
background: #f5f5f5;
}
.home-cards {
display: flex;
gap: 12px;
margin-bottom: 12px;
> :global(.adm-card) {
flex: 1;
}
}
.home-section {
margin-bottom: 12px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
}
.home-section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 15px;
padding-left: 16px;
}
.home-scene-stats {
display: flex;
justify-content: space-between;
margin: 0 8px 8px 8px;
}
.home-scene-item {
flex: 1;
text-align: center;
}
.home-scene-icon {
margin: 0 auto 4px auto;
}
.home-scene-value {
font-size: 18px;
font-weight: bold;
color: #1677ff;
}
.home-scene-label {
font-size: 12px;
color: #888;
}
.home-today-item {
text-align: center;
padding: 8px 0;
background: #f7f8fa;
border-radius: 8px;
}
.home-today-value {
font-size: 18px;
font-weight: bold;
color: #1677ff;
}
.home-today-label {
font-size: 12px;
color: #888;
}
.home-chart {
margin-top: 8px;
width: 100%;
min-height: 100px;
}

View File

@@ -0,0 +1,132 @@
import React, { useState } from "react";
import { Card, NavBar, TabBar, Grid } from "antd-mobile";
import {
AppOutline,
UserOutline,
PieOutline, // 替换 BarChartOutline
ShopbagOutline,
BellOutline,
} from "antd-mobile-icons";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
import Layout from "@/components/Layout/Layout";
import style from "./index.module.scss";
const sceneStats = [
{
label: "公众号获客",
value: 234,
icon: <ShopbagOutline style={{ fontSize: 28, color: "#4caf50" }} />,
},
{
label: "海报获客",
value: 167,
icon: <AppOutline style={{ fontSize: 28, color: "#ff9800" }} />,
},
{
label: "抖音获客",
value: 156,
icon: <PieOutline style={{ fontSize: 28, color: "#2196f3" }} />,
},
{
label: "小红书获客",
value: 89,
icon: <UserOutline style={{ fontSize: 28, color: "#e91e63" }} />,
},
];
const todayStats = [
{ label: "朋友圈同步", value: 12 },
{ label: "群发任务", value: 8 },
{ label: "获客转化", value: "85%" },
{ label: "系统活跃度", value: "98%" },
];
const Home: React.FC = () => {
return (
<Layout
header={
<NavBar back={null} style={{ background: "#fff" }}>
<span style={{ color: "#1677ff", fontWeight: 700 }}></span>
</NavBar>
}
footer={<MeauMobile />}
>
<div className={style["home-page"]}>
{/* 统计卡片 */}
<div className={style["home-cards"]}>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}></div>
<div className={style["home-card-value"]}>0</div>
</Card>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}></div>
<div className={style["home-card-value"]}>0</div>
</Card>
<Card className={style["home-card"]}>
<div className={style["home-card-title"]}>线</div>
<div className={style["home-card-value"]}>0</div>
</Card>
</div>
{/* 场景获客统计 */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<div className={style["home-scene-stats"]}>
{sceneStats.map((item) => (
<div key={item.label} className={style["home-scene-item"]}>
<div className={style["home-scene-icon"]}>{item.icon}</div>
<div className={style["home-scene-value"]}>{item.value}</div>
<div className={style["home-scene-label"]}>{item.label}</div>
</div>
))}
</div>
</Card>
{/* 今日数据 */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<Grid columns={2} gap={12}>
{todayStats.map((item) => (
<Grid.Item key={item.label}>
<div className={style["home-today-item"]}>
<div className={style["home-today-value"]}>{item.value}</div>
<div className={style["home-today-label"]}>{item.label}</div>
</div>
</Grid.Item>
))}
</Grid>
</Card>
{/* 每日获客趋势(静态图表占位) */}
<Card className={style["home-section"]}>
<div className={style["home-section-title"]}></div>
<div className={style["home-chart"]}>
<svg width="100%" height="120">
<polyline
fill="none"
stroke="#1677ff"
strokeWidth="3"
points="10,100 40,80 70,60 100,50 130,40 160,60 190,80"
/>
{/* x轴文字 */}
{["周一", "周二", "周三", "周四", "周五", "周六", "周日"].map(
(d, i) => (
<text
key={d}
x={10 + i * 30}
y={115}
fontSize="12"
textAnchor="middle"
>
{d}
</text>
)
)}
</svg>
</div>
</Card>
</div>
</Layout>
);
};
export default Home;

View File

@@ -1,11 +1,17 @@
import Home from "@/pages/home/index";
const homeRoutes = [
{
path: "/",
element: <Home />,
auth: false, // 不需要权限
},
];
export default homeRoutes;
import Home from "@/pages/home/index";
import Login from "@/pages/login/login";
const homeRoutes = [
{
path: "/",
element: <Home />,
auth: false, // 不需要权限
},
{
path: "/login",
element: <Login />,
auth: false, // 不需要权限
},
];
export default homeRoutes;