FEAT => 本次更新项目为:
存客宝设备绑定引导页
This commit is contained in:
BIN
nkebao/public/logo.png
Normal file
BIN
nkebao/public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 488 KiB |
@@ -1,7 +1,7 @@
|
|||||||
.guideContainer {
|
.guideContainer {
|
||||||
min-height: 100vh;
|
height: 100vh;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: var(--primary-color);
|
||||||
padding: 20px;
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.loadingText {
|
.loadingText {
|
||||||
@@ -38,42 +38,40 @@
|
|||||||
|
|
||||||
.header {
|
.header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconContainer {
|
.iconContainer {
|
||||||
width: 80px;
|
width: 60px;
|
||||||
height: 80px;
|
height: 60px;
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: #fff;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: flex;
|
margin: 0 auto 12px;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 0 auto 20px;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warningIcon {
|
.logo {
|
||||||
font-size: 40px;
|
width: 100%;
|
||||||
color: #ffd700;
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 28px;
|
font-size: 22px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 8px;
|
||||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
color: rgba(255, 255, 255, 0.9);
|
color: rgba(255, 255, 255, 0.9);
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
line-height: 1.5;
|
line-height: 1.4;
|
||||||
max-width: 300px;
|
max-width: 280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,34 +79,36 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deviceStatus {
|
.deviceStatus {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statusCard {
|
.statusCard {
|
||||||
background: rgba(255, 255, 255, 0.95);
|
background: rgba(255, 255, 255, 0.95);
|
||||||
border-radius: 16px;
|
border-radius: 12px;
|
||||||
padding: 20px;
|
padding: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.statusIcon {
|
.statusIcon {
|
||||||
width: 50px;
|
width: 40px;
|
||||||
height: 50px;
|
height: 40px;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: var(--primary-color);
|
||||||
border-radius: 12px;
|
border-radius: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-right: 16px;
|
margin-right: 12px;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statusInfo {
|
.statusInfo {
|
||||||
@@ -116,69 +116,69 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.statusTitle {
|
.statusTitle {
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statusValue {
|
.statusValue {
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deviceCount {
|
.deviceCount {
|
||||||
color: #667eea;
|
color: var(--primary-color);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guideSteps {
|
.guideSteps {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stepsTitle {
|
.stepsTitle {
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 20px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 12px;
|
||||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stepList {
|
.stepList {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stepItem {
|
.stepItem {
|
||||||
background: rgba(255, 255, 255, 0.95);
|
background: rgba(255, 255, 255, 0.95);
|
||||||
border-radius: 12px;
|
border-radius: 8px;
|
||||||
padding: 16px;
|
padding: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
transition: transform 0.2s ease;
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stepNumber {
|
.stepNumber {
|
||||||
width: 32px;
|
width: 24px;
|
||||||
height: 32px;
|
height: 24px;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: var(--primary-color);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
margin-right: 16px;
|
margin-right: 10px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,23 +187,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stepTitle {
|
.stepTitle {
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stepDesc {
|
.stepDesc {
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
line-height: 1.4;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tips {
|
.tips {
|
||||||
background: rgba(255, 255, 255, 0.95);
|
background: rgba(255, 255, 255, 0.95);
|
||||||
border-radius: 12px;
|
border-radius: 8px;
|
||||||
padding: 20px;
|
padding: 12px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
@@ -211,24 +211,24 @@
|
|||||||
.tipsTitle {
|
.tipsTitle {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tipsIcon {
|
.tipsIcon {
|
||||||
color: #ff6b6b;
|
color: #ff6b6b;
|
||||||
margin-right: 8px;
|
margin-right: 6px;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tipsContent {
|
.tipsContent {
|
||||||
p {
|
p {
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
line-height: 1.6;
|
line-height: 1.4;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 4px;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@@ -237,41 +237,41 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
margin-top: 30px;
|
margin-top: 16px;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.primaryButton {
|
.primaryButton {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 12px;
|
border-radius: 8px;
|
||||||
height: 48px;
|
height: 44px;
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: white;
|
color: var(--primary-color);
|
||||||
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
|
box-shadow: 0 2px 12px rgba(255, 255, 255, 0.4);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: translateY(1px);
|
transform: translateY(1px);
|
||||||
box-shadow: 0 2px 10px rgba(102, 126, 234, 0.4);
|
box-shadow: 0 1px 6px rgba(255, 255, 255, 0.4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonIcon {
|
.buttonIcon {
|
||||||
margin-left: 8px;
|
margin-left: 6px;
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondaryButton {
|
.secondaryButton {
|
||||||
border: 2px solid rgba(255, 255, 255, 0.8);
|
border: 2px solid rgba(255, 255, 255, 0.8);
|
||||||
border-radius: 12px;
|
border-radius: 8px;
|
||||||
height: 48px;
|
height: 44px;
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: white;
|
color: white;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -285,27 +285,27 @@
|
|||||||
// 响应式设计
|
// 响应式设计
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.guideContainer {
|
.guideContainer {
|
||||||
padding: 16px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statusCard {
|
.statusCard {
|
||||||
padding: 16px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stepItem {
|
.stepItem {
|
||||||
padding: 14px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tips {
|
.tips {
|
||||||
padding: 16px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +313,7 @@
|
|||||||
@keyframes fadeInUp {
|
@keyframes fadeInUp {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(30px);
|
transform: translateY(20px);
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -325,7 +325,7 @@
|
|||||||
.deviceStatus,
|
.deviceStatus,
|
||||||
.guideSteps,
|
.guideSteps,
|
||||||
.tips {
|
.tips {
|
||||||
animation: fadeInUp 0.6s ease-out;
|
animation: fadeInUp 0.5s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guideSteps {
|
.guideSteps {
|
||||||
@@ -337,5 +337,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
animation: fadeInUp 0.6s ease-out 0.3s both;
|
animation: fadeInUp 0.5s ease-out 0.3s both;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useCallback, useRef } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Button, Toast } from "antd-mobile";
|
import { Button, Toast, Popup, Tabs, Input } from "antd-mobile";
|
||||||
import {
|
import {
|
||||||
MobileOutlined,
|
MobileOutlined,
|
||||||
ExclamationCircleOutlined,
|
ExclamationCircleOutlined,
|
||||||
ArrowRightOutlined,
|
ArrowRightOutlined,
|
||||||
|
QrcodeOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import Layout from "@/components/Layout/Layout";
|
import Layout from "@/components/Layout/Layout";
|
||||||
import { useUserStore } from "@/store/module/user";
|
|
||||||
import { getDashboard } from "@/pages/mobile/home/api";
|
import { getDashboard } from "@/pages/mobile/home/api";
|
||||||
|
import { fetchDeviceQRCode, addDeviceByImei } from "@/api/devices";
|
||||||
|
import { useUserStore } from "@/store/module/user";
|
||||||
import styles from "./index.module.scss";
|
import styles from "./index.module.scss";
|
||||||
|
|
||||||
const Guide: React.FC = () => {
|
const Guide: React.FC = () => {
|
||||||
@@ -16,21 +18,29 @@ const Guide: React.FC = () => {
|
|||||||
const { user } = useUserStore();
|
const { user } = useUserStore();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [deviceCount, setDeviceCount] = useState(0);
|
const [deviceCount, setDeviceCount] = useState(0);
|
||||||
const [hasDevices, setHasDevices] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
// 添加设备弹窗状态
|
||||||
checkDeviceStatus();
|
const [addVisible, setAddVisible] = useState(false);
|
||||||
}, []);
|
const [addTab, setAddTab] = useState("scan");
|
||||||
|
const [qrLoading, setQrLoading] = useState(false);
|
||||||
|
const [qrCode, setQrCode] = useState<string | null>(null);
|
||||||
|
const [imei, setImei] = useState("");
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [addLoading, setAddLoading] = useState(false);
|
||||||
|
|
||||||
|
// 轮询监听相关
|
||||||
|
const [isPolling, setIsPolling] = useState(false);
|
||||||
|
const pollingRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const initialDeviceCountRef = useRef(0);
|
||||||
|
|
||||||
// 检查设备绑定状态
|
// 检查设备绑定状态
|
||||||
const checkDeviceStatus = async () => {
|
const checkDeviceStatus = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const dashboardData = await getDashboard();
|
const dashboardData = await getDashboard();
|
||||||
const deviceNum = dashboardData?.deviceNum || 0;
|
const deviceNum = dashboardData?.deviceNum || 0;
|
||||||
|
|
||||||
setDeviceCount(deviceNum);
|
setDeviceCount(deviceNum);
|
||||||
setHasDevices(deviceNum > 0);
|
|
||||||
|
|
||||||
// 如果已有设备,直接跳转到首页
|
// 如果已有设备,直接跳转到首页
|
||||||
if (deviceNum > 0) {
|
if (deviceNum > 0) {
|
||||||
@@ -46,16 +56,116 @@ const Guide: React.FC = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkDeviceStatus();
|
||||||
|
}, [checkDeviceStatus]);
|
||||||
|
|
||||||
|
// 开始轮询监听设备状态
|
||||||
|
const startPolling = useCallback(() => {
|
||||||
|
if (isPolling) return;
|
||||||
|
|
||||||
|
setIsPolling(true);
|
||||||
|
initialDeviceCountRef.current = deviceCount;
|
||||||
|
|
||||||
|
const pollDeviceStatus = async () => {
|
||||||
|
try {
|
||||||
|
const dashboardData = await getDashboard();
|
||||||
|
const currentDeviceCount = dashboardData?.deviceNum || 0;
|
||||||
|
|
||||||
|
// 如果设备数量增加了,说明有新设备添加成功
|
||||||
|
if (currentDeviceCount > initialDeviceCountRef.current) {
|
||||||
|
Toast.show({ content: "设备添加成功!", position: "top" });
|
||||||
|
setAddVisible(false);
|
||||||
|
setDeviceCount(currentDeviceCount);
|
||||||
|
setIsPolling(false);
|
||||||
|
if (pollingRef.current) {
|
||||||
|
clearInterval(pollingRef.current);
|
||||||
|
pollingRef.current = null;
|
||||||
|
}
|
||||||
|
// 可以选择跳转到首页或继续留在当前页面
|
||||||
|
// navigate("/");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("轮询检查设备状态失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 每3秒检查一次设备状态
|
||||||
|
pollingRef.current = setInterval(pollDeviceStatus, 3000);
|
||||||
|
}, [isPolling, deviceCount]);
|
||||||
|
|
||||||
|
// 停止轮询
|
||||||
|
const stopPolling = useCallback(() => {
|
||||||
|
setIsPolling(false);
|
||||||
|
if (pollingRef.current) {
|
||||||
|
clearInterval(pollingRef.current);
|
||||||
|
pollingRef.current = null;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 组件卸载时清理轮询
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (pollingRef.current) {
|
||||||
|
clearInterval(pollingRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 获取二维码
|
||||||
|
const handleGetQr = async () => {
|
||||||
|
setQrLoading(true);
|
||||||
|
setQrCode(null);
|
||||||
|
try {
|
||||||
|
const accountId = user.s2_accountId;
|
||||||
|
if (!accountId) throw new Error("未获取到用户信息");
|
||||||
|
const res = await fetchDeviceQRCode(accountId);
|
||||||
|
setQrCode(res.qrCode);
|
||||||
|
// 获取二维码后开始轮询监听
|
||||||
|
startPolling();
|
||||||
|
} catch (e: any) {
|
||||||
|
Toast.show({ content: e.message || "获取二维码失败", position: "top" });
|
||||||
|
} finally {
|
||||||
|
setQrLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 跳转到设备管理页面
|
// 跳转到设备管理页面
|
||||||
const handleGoToDevices = () => {
|
const handleGoToDevices = () => {
|
||||||
navigate("/devices");
|
handleGetQr();
|
||||||
|
setAddVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 跳转到首页(跳过引导)
|
// 手动添加设备
|
||||||
const handleSkipGuide = () => {
|
const handleAddDevice = async () => {
|
||||||
navigate("/");
|
if (!imei.trim() || !name.trim()) {
|
||||||
|
Toast.show({ content: "请填写完整信息", position: "top" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setAddLoading(true);
|
||||||
|
try {
|
||||||
|
await addDeviceByImei(imei, name);
|
||||||
|
Toast.show({ content: "添加成功", position: "top" });
|
||||||
|
setAddVisible(false);
|
||||||
|
setImei("");
|
||||||
|
setName("");
|
||||||
|
// 重新检查设备状态
|
||||||
|
await checkDeviceStatus();
|
||||||
|
} catch (e: any) {
|
||||||
|
Toast.show({ content: e.message || "添加失败", position: "top" });
|
||||||
|
} finally {
|
||||||
|
setAddLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭弹窗时停止轮询
|
||||||
|
const handleClosePopup = () => {
|
||||||
|
setAddVisible(false);
|
||||||
|
stopPolling();
|
||||||
|
setQrCode(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -74,12 +184,10 @@ const Guide: React.FC = () => {
|
|||||||
{/* 头部区域 */}
|
{/* 头部区域 */}
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.iconContainer}>
|
<div className={styles.iconContainer}>
|
||||||
<ExclamationCircleOutlined className={styles.warningIcon} />
|
<img src="/logo.png" alt="存客宝" className={styles.logo} />
|
||||||
</div>
|
</div>
|
||||||
<h1 className={styles.title}>欢迎使用存客宝</h1>
|
<h1 className={styles.title}>欢迎使用存客宝</h1>
|
||||||
<p className={styles.subtitle}>
|
<p className={styles.subtitle}>请先绑定设备以获得完整功能体验</p>
|
||||||
为了更好的使用体验,请先绑定您的设备
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 内容区域 */}
|
{/* 内容区域 */}
|
||||||
@@ -92,7 +200,7 @@ const Guide: React.FC = () => {
|
|||||||
<div className={styles.statusInfo}>
|
<div className={styles.statusInfo}>
|
||||||
<div className={styles.statusTitle}>设备绑定状态</div>
|
<div className={styles.statusTitle}>设备绑定状态</div>
|
||||||
<div className={styles.statusValue}>
|
<div className={styles.statusValue}>
|
||||||
已绑定设备:
|
已绑定:
|
||||||
<span className={styles.deviceCount}>{deviceCount}</span> 台
|
<span className={styles.deviceCount}>{deviceCount}</span> 台
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,14 +208,14 @@ const Guide: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.guideSteps}>
|
<div className={styles.guideSteps}>
|
||||||
<h2 className={styles.stepsTitle}>绑定设备步骤</h2>
|
<h2 className={styles.stepsTitle}>绑定步骤</h2>
|
||||||
<div className={styles.stepList}>
|
<div className={styles.stepList}>
|
||||||
<div className={styles.stepItem}>
|
<div className={styles.stepItem}>
|
||||||
<div className={styles.stepNumber}>1</div>
|
<div className={styles.stepNumber}>1</div>
|
||||||
<div className={styles.stepContent}>
|
<div className={styles.stepContent}>
|
||||||
<div className={styles.stepTitle}>准备设备</div>
|
<div className={styles.stepTitle}>准备设备</div>
|
||||||
<div className={styles.stepDesc}>
|
<div className={styles.stepDesc}>
|
||||||
确保您的手机设备已安装存客宝应用
|
确保手机已安装存客宝应用
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,9 +223,7 @@ const Guide: React.FC = () => {
|
|||||||
<div className={styles.stepNumber}>2</div>
|
<div className={styles.stepNumber}>2</div>
|
||||||
<div className={styles.stepContent}>
|
<div className={styles.stepContent}>
|
||||||
<div className={styles.stepTitle}>扫描二维码</div>
|
<div className={styles.stepTitle}>扫描二维码</div>
|
||||||
<div className={styles.stepDesc}>
|
<div className={styles.stepDesc}>在设备管理页面扫描绑定</div>
|
||||||
在设备管理页面扫描二维码绑定设备
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.stepItem}>
|
<div className={styles.stepItem}>
|
||||||
@@ -138,7 +244,7 @@ const Guide: React.FC = () => {
|
|||||||
温馨提示
|
温馨提示
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.tipsContent}>
|
<div className={styles.tipsContent}>
|
||||||
<p>• 绑定设备后可以享受更完整的功能体验</p>
|
<p>• 绑定设备后可享受完整功能体验</p>
|
||||||
<p>• 每个账号最多可绑定10台设备</p>
|
<p>• 每个账号最多可绑定10台设备</p>
|
||||||
<p>• 如需帮助请联系客服</p>
|
<p>• 如需帮助请联系客服</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -157,18 +263,86 @@ const Guide: React.FC = () => {
|
|||||||
立即绑定设备
|
立即绑定设备
|
||||||
<ArrowRightOutlined className={styles.buttonIcon} />
|
<ArrowRightOutlined className={styles.buttonIcon} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
|
||||||
block
|
|
||||||
fill="outline"
|
|
||||||
size="large"
|
|
||||||
className={styles.secondaryButton}
|
|
||||||
onClick={handleSkipGuide}
|
|
||||||
>
|
|
||||||
稍后绑定
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 添加设备弹窗 */}
|
||||||
|
<Popup
|
||||||
|
visible={addVisible}
|
||||||
|
onMaskClick={handleClosePopup}
|
||||||
|
bodyStyle={{
|
||||||
|
borderTopLeftRadius: 16,
|
||||||
|
borderTopRightRadius: 16,
|
||||||
|
minHeight: 320,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ padding: 20 }}>
|
||||||
|
<Tabs
|
||||||
|
activeKey={addTab}
|
||||||
|
onChange={setAddTab}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
>
|
||||||
|
<Tabs.Tab title="扫码添加" key="scan" />
|
||||||
|
<Tabs.Tab title="手动添加" key="manual" />
|
||||||
|
</Tabs>
|
||||||
|
{addTab === "scan" && (
|
||||||
|
<div style={{ textAlign: "center", minHeight: 200 }}>
|
||||||
|
<Button color="primary" onClick={handleGetQr} loading={qrLoading}>
|
||||||
|
<QrcodeOutlined />
|
||||||
|
获取二维码
|
||||||
|
</Button>
|
||||||
|
{qrCode && (
|
||||||
|
<div style={{ marginTop: 16 }}>
|
||||||
|
<img
|
||||||
|
src={qrCode}
|
||||||
|
alt="二维码"
|
||||||
|
style={{
|
||||||
|
width: 180,
|
||||||
|
height: 180,
|
||||||
|
background: "#f5f5f5",
|
||||||
|
borderRadius: 8,
|
||||||
|
margin: "0 auto",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{ color: "#888", fontSize: 12, marginTop: 8 }}>
|
||||||
|
请用手机扫码添加设备
|
||||||
|
</div>
|
||||||
|
{isPolling && (
|
||||||
|
<div
|
||||||
|
style={{ color: "#1890ff", fontSize: 12, marginTop: 8 }}
|
||||||
|
>
|
||||||
|
正在监听设备添加状态...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{addTab === "manual" && (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
||||||
|
<Input
|
||||||
|
placeholder="设备名称"
|
||||||
|
value={name}
|
||||||
|
onChange={val => setName(val)}
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="设备IMEI"
|
||||||
|
value={imei}
|
||||||
|
onChange={val => setImei(val)}
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={handleAddDevice}
|
||||||
|
loading={addLoading}
|
||||||
|
>
|
||||||
|
添加
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Popup>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ const Login: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const dashboardData = await getDashboard();
|
const dashboardData = await getDashboard();
|
||||||
const deviceNum = dashboardData?.deviceNum || 0;
|
const deviceNum = dashboardData?.deviceNum || 0;
|
||||||
|
console.log(deviceNum, "deviceNum");
|
||||||
// 如果没有绑定设备,跳转到引导页面
|
// 如果没有绑定设备,跳转到引导页面
|
||||||
if (deviceNum === 0) {
|
if (deviceNum === 0) {
|
||||||
navigate("/guide");
|
navigate("/guide");
|
||||||
|
|||||||
@@ -46,9 +46,7 @@ const Scene: React.FC = () => {
|
|||||||
image:
|
image:
|
||||||
item.image ||
|
item.image ||
|
||||||
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-api.png",
|
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-api.png",
|
||||||
description:
|
description: "",
|
||||||
scenarioDescriptions[item.name?.toLowerCase()] ||
|
|
||||||
"通过该平台进行获客",
|
|
||||||
count: item.count,
|
count: item.count,
|
||||||
growth: item.growth,
|
growth: item.growth,
|
||||||
status: item.status,
|
status: item.status,
|
||||||
@@ -144,11 +142,7 @@ const Scene: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={style["card-title"]}>{scenario.name}</div>
|
<div className={style["card-title"]}>{scenario.name}</div>
|
||||||
{scenario.description && (
|
|
||||||
<div className={style["card-desc"]}>
|
|
||||||
{scenario.description}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={style["card-stats"]}>
|
<div className={style["card-stats"]}>
|
||||||
<span className={style["card-count"]}>
|
<span className={style["card-count"]}>
|
||||||
今日: {scenario.count}
|
今日: {scenario.count}
|
||||||
|
|||||||
Reference in New Issue
Block a user