feat: 添加设备绑定引导页面及相关逻辑,更新路由配置
This commit is contained in:
341
nkebao/src/pages/guide/index.module.scss
Normal file
341
nkebao/src/pages/guide/index.module.scss
Normal 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;
|
||||
}
|
||||
176
nkebao/src/pages/guide/index.tsx
Normal file
176
nkebao/src/pages/guide/index.tsx
Normal 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;
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "antd-mobile-icons";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api";
|
||||
import { getDashboard } from "@/pages/mobile/home/api";
|
||||
import style from "./login.module.scss";
|
||||
|
||||
const Login: React.FC = () => {
|
||||
@@ -100,6 +101,21 @@ const Login: React.FC = () => {
|
||||
|
||||
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
|
||||
const returnUrl = searchParams.get("returnUrl");
|
||||
if (returnUrl) {
|
||||
|
||||
@@ -14,7 +14,7 @@ export const routeGroups = {
|
||||
// 基础路由
|
||||
basic: {
|
||||
name: "基础功能",
|
||||
routes: ["/", "/login", "/scene", "/work", "/mine"],
|
||||
routes: ["/", "/login", "/guide", "/scene", "/work", "/mine"],
|
||||
},
|
||||
|
||||
// 设备管理
|
||||
@@ -109,6 +109,7 @@ export const routePermissions = {
|
||||
user: [
|
||||
"/",
|
||||
"/login",
|
||||
"/guide",
|
||||
"/scene",
|
||||
"/work",
|
||||
"/mine",
|
||||
@@ -136,6 +137,7 @@ export const routePermissions = {
|
||||
export const routeTitles: Record<string, string> = {
|
||||
"/": "首页",
|
||||
"/login": "登录",
|
||||
"/guide": "设备绑定引导",
|
||||
"/scene": "场景获客",
|
||||
"/work": "工作台",
|
||||
"/mine": "我的",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Login from "@/pages/login/login";
|
||||
import Guide from "@/pages/guide";
|
||||
|
||||
const authRoutes = [
|
||||
{
|
||||
@@ -6,6 +7,11 @@ const authRoutes = [
|
||||
element: <Login />,
|
||||
auth: false, // 不需要权限
|
||||
},
|
||||
{
|
||||
path: "/guide",
|
||||
element: <Guide />,
|
||||
auth: true, // 需要登录权限
|
||||
},
|
||||
];
|
||||
|
||||
export default authRoutes;
|
||||
|
||||
Reference in New Issue
Block a user