feat: 添加设备绑定引导页面及相关逻辑,更新路由配置

This commit is contained in:
2025-07-29 18:42:04 +08:00
parent f6beb38502
commit 10dd9869d4
5 changed files with 542 additions and 1 deletions

View File

@@ -0,0 +1,341 @@
.guideContainer {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="50" cy="10" r="0.5" fill="rgba(255,255,255,0.1)"/><circle cx="10" cy="60" r="0.5" fill="rgba(255,255,255,0.1)"/><circle cx="90" cy="40" r="0.5" fill="rgba(255,255,255,0.1)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
opacity: 0.3;
pointer-events: none;
}
}
.loadingContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.loadingText {
color: white;
font-size: 16px;
margin-top: 16px;
font-weight: 500;
}
.header {
text-align: center;
margin-bottom: 40px;
position: relative;
z-index: 1;
}
.iconContainer {
width: 80px;
height: 80px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.warningIcon {
font-size: 40px;
color: #ffd700;
}
.title {
color: white;
font-size: 28px;
font-weight: 700;
margin-bottom: 12px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.subtitle {
color: rgba(255, 255, 255, 0.9);
font-size: 16px;
line-height: 1.5;
max-width: 300px;
margin: 0 auto;
}
.content {
flex: 1;
position: relative;
z-index: 1;
}
.deviceStatus {
margin-bottom: 30px;
}
.statusCard {
background: rgba(255, 255, 255, 0.95);
border-radius: 16px;
padding: 20px;
display: flex;
align-items: center;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.statusIcon {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
color: white;
font-size: 24px;
}
.statusInfo {
flex: 1;
}
.statusTitle {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.statusValue {
font-size: 14px;
color: #666;
}
.deviceCount {
color: #667eea;
font-weight: 700;
font-size: 18px;
}
.guideSteps {
margin-bottom: 30px;
}
.stepsTitle {
color: white;
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.stepList {
display: flex;
flex-direction: column;
gap: 16px;
}
.stepItem {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 16px;
display: flex;
align-items: flex-start;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: transform 0.2s ease;
&:hover {
transform: translateY(-2px);
}
}
.stepNumber {
width: 32px;
height: 32px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 700;
font-size: 14px;
margin-right: 16px;
flex-shrink: 0;
}
.stepContent {
flex: 1;
}
.stepTitle {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.stepDesc {
font-size: 14px;
color: #666;
line-height: 1.4;
}
.tips {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.tipsTitle {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
}
.tipsIcon {
color: #ff6b6b;
margin-right: 8px;
font-size: 18px;
}
.tipsContent {
p {
font-size: 14px;
color: #666;
line-height: 1.6;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
}
}
.footer {
margin-top: 30px;
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
gap: 12px;
}
.primaryButton {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 12px;
height: 48px;
font-size: 16px;
font-weight: 600;
color: white;
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
transition: all 0.3s ease;
&:active {
transform: translateY(1px);
box-shadow: 0 2px 10px rgba(102, 126, 234, 0.4);
}
}
.buttonIcon {
margin-left: 8px;
font-size: 14px;
}
.secondaryButton {
border: 2px solid rgba(255, 255, 255, 0.8);
border-radius: 12px;
height: 48px;
font-size: 16px;
font-weight: 600;
color: white;
background: transparent;
backdrop-filter: blur(10px);
&:active {
background: rgba(255, 255, 255, 0.1);
}
}
// 响应式设计
@media (max-width: 480px) {
.guideContainer {
padding: 16px;
}
.title {
font-size: 24px;
}
.subtitle {
font-size: 14px;
}
.statusCard {
padding: 16px;
}
.stepItem {
padding: 14px;
}
.tips {
padding: 16px;
}
}
// 动画效果
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.header,
.deviceStatus,
.guideSteps,
.tips {
animation: fadeInUp 0.6s ease-out;
}
.guideSteps {
animation-delay: 0.1s;
}
.tips {
animation-delay: 0.2s;
}
.footer {
animation: fadeInUp 0.6s ease-out 0.3s both;
}

View File

@@ -0,0 +1,176 @@
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Button, Toast } from "antd-mobile";
import {
MobileOutlined,
ExclamationCircleOutlined,
ArrowRightOutlined,
} from "@ant-design/icons";
import Layout from "@/components/Layout/Layout";
import { useUserStore } from "@/store/module/user";
import { getDashboard } from "@/pages/mobile/home/api";
import styles from "./index.module.scss";
const Guide: React.FC = () => {
const navigate = useNavigate();
const { user } = useUserStore();
const [loading, setLoading] = useState(true);
const [deviceCount, setDeviceCount] = useState(0);
const [hasDevices, setHasDevices] = useState(false);
useEffect(() => {
checkDeviceStatus();
}, []);
// 检查设备绑定状态
const checkDeviceStatus = async () => {
try {
setLoading(true);
const dashboardData = await getDashboard();
const deviceNum = dashboardData?.deviceNum || 0;
setDeviceCount(deviceNum);
setHasDevices(deviceNum > 0);
// 如果已有设备,直接跳转到首页
if (deviceNum > 0) {
navigate("/");
return;
}
} catch (error) {
console.error("检查设备状态失败:", error);
Toast.show({
content: "检查设备状态失败,请重试",
position: "top",
});
} finally {
setLoading(false);
}
};
// 跳转到设备管理页面
const handleGoToDevices = () => {
navigate("/devices");
};
// 跳转到首页(跳过引导)
const handleSkipGuide = () => {
navigate("/");
};
if (loading) {
return (
<Layout loading={true}>
<div className={styles.loadingContainer}>
<div className={styles.loadingText}>...</div>
</div>
</Layout>
);
}
return (
<Layout>
<div className={styles.guideContainer}>
{/* 头部区域 */}
<div className={styles.header}>
<div className={styles.iconContainer}>
<ExclamationCircleOutlined className={styles.warningIcon} />
</div>
<h1 className={styles.title}>使</h1>
<p className={styles.subtitle}>
使
</p>
</div>
{/* 内容区域 */}
<div className={styles.content}>
<div className={styles.deviceStatus}>
<div className={styles.statusCard}>
<div className={styles.statusIcon}>
<MobileOutlined />
</div>
<div className={styles.statusInfo}>
<div className={styles.statusTitle}></div>
<div className={styles.statusValue}>
<span className={styles.deviceCount}>{deviceCount}</span>
</div>
</div>
</div>
</div>
<div className={styles.guideSteps}>
<h2 className={styles.stepsTitle}></h2>
<div className={styles.stepList}>
<div className={styles.stepItem}>
<div className={styles.stepNumber}>1</div>
<div className={styles.stepContent}>
<div className={styles.stepTitle}></div>
<div className={styles.stepDesc}>
</div>
</div>
</div>
<div className={styles.stepItem}>
<div className={styles.stepNumber}>2</div>
<div className={styles.stepContent}>
<div className={styles.stepTitle}></div>
<div className={styles.stepDesc}>
</div>
</div>
</div>
<div className={styles.stepItem}>
<div className={styles.stepNumber}>3</div>
<div className={styles.stepContent}>
<div className={styles.stepTitle}>使</div>
<div className={styles.stepDesc}>
使
</div>
</div>
</div>
</div>
</div>
<div className={styles.tips}>
<div className={styles.tipsTitle}>
<ExclamationCircleOutlined className={styles.tipsIcon} />
</div>
<div className={styles.tipsContent}>
<p> </p>
<p> 10</p>
<p> </p>
</div>
</div>
</div>
{/* 底部按钮区域 */}
<div className={styles.footer}>
<Button
block
color="primary"
size="large"
className={styles.primaryButton}
onClick={handleGoToDevices}
>
<ArrowRightOutlined className={styles.buttonIcon} />
</Button>
<Button
block
fill="outline"
size="large"
className={styles.secondaryButton}
onClick={handleSkipGuide}
>
</Button>
</div>
</div>
</Layout>
);
};
export default Guide;

View File

@@ -8,6 +8,7 @@ import {
} from "antd-mobile-icons"; } from "antd-mobile-icons";
import { useUserStore } from "@/store/module/user"; import { useUserStore } from "@/store/module/user";
import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api"; import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api";
import { getDashboard } from "@/pages/mobile/home/api";
import style from "./login.module.scss"; import style from "./login.module.scss";
const Login: React.FC = () => { const Login: React.FC = () => {
@@ -100,6 +101,21 @@ const Login: React.FC = () => {
Toast.show({ content: "登录成功", position: "top" }); Toast.show({ content: "登录成功", position: "top" });
// 检查设备绑定状态
try {
const dashboardData = await getDashboard();
const deviceNum = dashboardData?.deviceNum || 0;
// 如果没有绑定设备,跳转到引导页面
if (deviceNum === 0) {
navigate("/guide");
return;
}
} catch (error) {
console.error("检查设备状态失败:", error);
// 如果检查失败,默认跳转到首页
}
// 跳转到首页或重定向URL // 跳转到首页或重定向URL
const returnUrl = searchParams.get("returnUrl"); const returnUrl = searchParams.get("returnUrl");
if (returnUrl) { if (returnUrl) {

View File

@@ -14,7 +14,7 @@ export const routeGroups = {
// 基础路由 // 基础路由
basic: { basic: {
name: "基础功能", name: "基础功能",
routes: ["/", "/login", "/scene", "/work", "/mine"], routes: ["/", "/login", "/guide", "/scene", "/work", "/mine"],
}, },
// 设备管理 // 设备管理
@@ -109,6 +109,7 @@ export const routePermissions = {
user: [ user: [
"/", "/",
"/login", "/login",
"/guide",
"/scene", "/scene",
"/work", "/work",
"/mine", "/mine",
@@ -136,6 +137,7 @@ export const routePermissions = {
export const routeTitles: Record<string, string> = { export const routeTitles: Record<string, string> = {
"/": "首页", "/": "首页",
"/login": "登录", "/login": "登录",
"/guide": "设备绑定引导",
"/scene": "场景获客", "/scene": "场景获客",
"/work": "工作台", "/work": "工作台",
"/mine": "我的", "/mine": "我的",

View File

@@ -1,4 +1,5 @@
import Login from "@/pages/login/login"; import Login from "@/pages/login/login";
import Guide from "@/pages/guide";
const authRoutes = [ const authRoutes = [
{ {
@@ -6,6 +7,11 @@ const authRoutes = [
element: <Login />, element: <Login />,
auth: false, // 不需要权限 auth: false, // 不需要权限
}, },
{
path: "/guide",
element: <Guide />,
auth: true, // 需要登录权限
},
]; ];
export default authRoutes; export default authRoutes;