diff --git a/nkebao/public/logo.png b/nkebao/public/logo.png new file mode 100644 index 00000000..40daafb0 Binary files /dev/null and b/nkebao/public/logo.png differ diff --git a/nkebao/src/pages/guide/index.module.scss b/nkebao/src/pages/guide/index.module.scss index 5c8f8ff4..3acb01ac 100644 --- a/nkebao/src/pages/guide/index.module.scss +++ b/nkebao/src/pages/guide/index.module.scss @@ -1,7 +1,7 @@ .guideContainer { - min-height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - padding: 20px; + height: 100vh; + background: var(--primary-color); + padding: 16px; display: flex; flex-direction: column; position: relative; @@ -26,7 +26,7 @@ align-items: center; justify-content: center; height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: var(--primary-color); } .loadingText { @@ -38,42 +38,40 @@ .header { text-align: center; - margin-bottom: 40px; + margin-bottom: 20px; position: relative; z-index: 1; } .iconContainer { - width: 80px; - height: 80px; - background: rgba(255, 255, 255, 0.2); + width: 60px; + height: 60px; + background: #fff; border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - margin: 0 auto 20px; - backdrop-filter: blur(10px); + margin: 0 auto 12px; border: 1px solid rgba(255, 255, 255, 0.3); + overflow: hidden; } -.warningIcon { - font-size: 40px; - color: #ffd700; +.logo { + width: 100%; + height: 100%; + object-fit: contain; } .title { color: white; - font-size: 28px; + font-size: 22px; font-weight: 700; - margin-bottom: 12px; + margin-bottom: 8px; 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; + font-size: 14px; + line-height: 1.4; + max-width: 280px; margin: 0 auto; } @@ -81,34 +79,36 @@ flex: 1; position: relative; z-index: 1; + overflow-y: auto; + padding-right: 4px; } .deviceStatus { - margin-bottom: 30px; + margin-bottom: 16px; } .statusCard { background: rgba(255, 255, 255, 0.95); - border-radius: 16px; - padding: 20px; + border-radius: 12px; + padding: 12px; display: flex; 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); 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; + width: 40px; + height: 40px; + background: var(--primary-color); + border-radius: 8px; display: flex; align-items: center; justify-content: center; - margin-right: 16px; + margin-right: 12px; color: white; - font-size: 24px; + font-size: 20px; } .statusInfo { @@ -116,69 +116,69 @@ } .statusTitle { - font-size: 16px; + font-size: 14px; font-weight: 600; color: #333; - margin-bottom: 4px; + margin-bottom: 2px; } .statusValue { - font-size: 14px; + font-size: 12px; color: #666; } .deviceCount { - color: #667eea; + color: var(--primary-color); font-weight: 700; - font-size: 18px; + font-size: 16px; } .guideSteps { - margin-bottom: 30px; + margin-bottom: 16px; } .stepsTitle { color: white; - font-size: 20px; + font-size: 16px; font-weight: 600; - margin-bottom: 20px; + margin-bottom: 12px; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } .stepList { display: flex; flex-direction: column; - gap: 16px; + gap: 8px; } .stepItem { background: rgba(255, 255, 255, 0.95); - border-radius: 12px; - padding: 16px; + border-radius: 8px; + padding: 10px; display: flex; 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); border: 1px solid rgba(255, 255, 255, 0.2); transition: transform 0.2s ease; &:hover { - transform: translateY(-2px); + transform: translateY(-1px); } } .stepNumber { - width: 32px; - height: 32px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + width: 24px; + height: 24px; + background: var(--primary-color); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-weight: 700; - font-size: 14px; - margin-right: 16px; + font-size: 12px; + margin-right: 10px; flex-shrink: 0; } @@ -187,23 +187,23 @@ } .stepTitle { - font-size: 16px; + font-size: 14px; font-weight: 600; color: #333; - margin-bottom: 4px; + margin-bottom: 2px; } .stepDesc { - font-size: 14px; + font-size: 12px; color: #666; - line-height: 1.4; + line-height: 1.3; } .tips { background: rgba(255, 255, 255, 0.95); - border-radius: 12px; - padding: 20px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); + border-radius: 8px; + padding: 12px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); } @@ -211,24 +211,24 @@ .tipsTitle { display: flex; align-items: center; - font-size: 16px; + font-size: 14px; font-weight: 600; color: #333; - margin-bottom: 12px; + margin-bottom: 8px; } .tipsIcon { color: #ff6b6b; - margin-right: 8px; - font-size: 18px; + margin-right: 6px; + font-size: 16px; } .tipsContent { p { - font-size: 14px; + font-size: 12px; color: #666; - line-height: 1.6; - margin-bottom: 8px; + line-height: 1.4; + margin-bottom: 4px; &:last-child { margin-bottom: 0; @@ -237,41 +237,41 @@ } .footer { - margin-top: 30px; + margin-top: 16px; position: relative; z-index: 1; display: flex; flex-direction: column; - gap: 12px; + gap: 8px; } .primaryButton { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: white; border: none; - border-radius: 12px; - height: 48px; - font-size: 16px; + border-radius: 8px; + height: 44px; + font-size: 15px; font-weight: 600; - color: white; - box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4); + color: var(--primary-color); + box-shadow: 0 2px 12px rgba(255, 255, 255, 0.4); transition: all 0.3s ease; &:active { 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 { - margin-left: 8px; - font-size: 14px; + margin-left: 6px; + font-size: 12px; } .secondaryButton { border: 2px solid rgba(255, 255, 255, 0.8); - border-radius: 12px; - height: 48px; - font-size: 16px; + border-radius: 8px; + height: 44px; + font-size: 15px; font-weight: 600; color: white; background: transparent; @@ -285,27 +285,27 @@ // 响应式设计 @media (max-width: 480px) { .guideContainer { - padding: 16px; + padding: 12px; } .title { - font-size: 24px; + font-size: 20px; } .subtitle { - font-size: 14px; + font-size: 13px; } .statusCard { - padding: 16px; + padding: 10px; } .stepItem { - padding: 14px; + padding: 8px; } .tips { - padding: 16px; + padding: 10px; } } @@ -313,7 +313,7 @@ @keyframes fadeInUp { from { opacity: 0; - transform: translateY(30px); + transform: translateY(20px); } to { opacity: 1; @@ -325,7 +325,7 @@ .deviceStatus, .guideSteps, .tips { - animation: fadeInUp 0.6s ease-out; + animation: fadeInUp 0.5s ease-out; } .guideSteps { @@ -337,5 +337,5 @@ } .footer { - animation: fadeInUp 0.6s ease-out 0.3s both; + animation: fadeInUp 0.5s ease-out 0.3s both; } diff --git a/nkebao/src/pages/guide/index.tsx b/nkebao/src/pages/guide/index.tsx index bc28b47d..9dda790a 100644 --- a/nkebao/src/pages/guide/index.tsx +++ b/nkebao/src/pages/guide/index.tsx @@ -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 { Button, Toast } from "antd-mobile"; +import { Button, Toast, Popup, Tabs, Input } from "antd-mobile"; import { MobileOutlined, ExclamationCircleOutlined, ArrowRightOutlined, + QrcodeOutlined, } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; -import { useUserStore } from "@/store/module/user"; 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"; const Guide: React.FC = () => { @@ -16,21 +18,29 @@ const Guide: React.FC = () => { const { user } = useUserStore(); const [loading, setLoading] = useState(true); 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(null); + const [imei, setImei] = useState(""); + const [name, setName] = useState(""); + const [addLoading, setAddLoading] = useState(false); + + // 轮询监听相关 + const [isPolling, setIsPolling] = useState(false); + const pollingRef = useRef(null); + const initialDeviceCountRef = useRef(0); // 检查设备绑定状态 - const checkDeviceStatus = async () => { + const checkDeviceStatus = useCallback(async () => { try { setLoading(true); const dashboardData = await getDashboard(); const deviceNum = dashboardData?.deviceNum || 0; setDeviceCount(deviceNum); - setHasDevices(deviceNum > 0); // 如果已有设备,直接跳转到首页 if (deviceNum > 0) { @@ -46,16 +56,116 @@ const Guide: React.FC = () => { } finally { 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 = () => { - navigate("/devices"); + handleGetQr(); + setAddVisible(true); }; - // 跳转到首页(跳过引导) - const handleSkipGuide = () => { - navigate("/"); + // 手动添加设备 + const handleAddDevice = async () => { + 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) { @@ -74,12 +184,10 @@ const Guide: React.FC = () => { {/* 头部区域 */}
- + 存客宝

欢迎使用存客宝

-

- 为了更好的使用体验,请先绑定您的设备 -

+

请先绑定设备以获得完整功能体验

{/* 内容区域 */} @@ -92,7 +200,7 @@ const Guide: React.FC = () => {
设备绑定状态
- 已绑定设备: + 已绑定: {deviceCount}
@@ -100,14 +208,14 @@ const Guide: React.FC = () => {
-

绑定设备步骤

+

绑定步骤

1
准备设备
- 确保您的手机设备已安装存客宝应用 + 确保手机已安装存客宝应用
@@ -115,9 +223,7 @@ const Guide: React.FC = () => {
2
扫描二维码
-
- 在设备管理页面扫描二维码绑定设备 -
+
在设备管理页面扫描绑定
@@ -138,7 +244,7 @@ const Guide: React.FC = () => { 温馨提示
-

• 绑定设备后可以享受更完整的功能体验

+

• 绑定设备后可享受完整功能体验

• 每个账号最多可绑定10台设备

• 如需帮助请联系客服

@@ -157,18 +263,86 @@ const Guide: React.FC = () => { 立即绑定设备 - -
+ + {/* 添加设备弹窗 */} + +
+ + + + + {addTab === "scan" && ( +
+ + {qrCode && ( +
+ 二维码 +
+ 请用手机扫码添加设备 +
+ {isPolling && ( +
+ 正在监听设备添加状态... +
+ )} +
+ )} +
+ )} + {addTab === "manual" && ( +
+ setName(val)} + clearable + /> + setImei(val)} + clearable + /> + +
+ )} +
+
); }; diff --git a/nkebao/src/pages/login/login.tsx b/nkebao/src/pages/login/login.tsx index 76def565..225b8ef7 100644 --- a/nkebao/src/pages/login/login.tsx +++ b/nkebao/src/pages/login/login.tsx @@ -105,7 +105,7 @@ const Login: React.FC = () => { try { const dashboardData = await getDashboard(); const deviceNum = dashboardData?.deviceNum || 0; - + console.log(deviceNum, "deviceNum"); // 如果没有绑定设备,跳转到引导页面 if (deviceNum === 0) { navigate("/guide"); diff --git a/nkebao/src/pages/mobile/scenarios/list/index.tsx b/nkebao/src/pages/mobile/scenarios/list/index.tsx index 6fc5d8da..9df19b24 100644 --- a/nkebao/src/pages/mobile/scenarios/list/index.tsx +++ b/nkebao/src/pages/mobile/scenarios/list/index.tsx @@ -46,9 +46,7 @@ const Scene: React.FC = () => { image: item.image || "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-api.png", - description: - scenarioDescriptions[item.name?.toLowerCase()] || - "通过该平台进行获客", + description: "", count: item.count, growth: item.growth, status: item.status, @@ -144,11 +142,7 @@ const Scene: React.FC = () => {
{scenario.name}
- {scenario.description && ( -
- {scenario.description} -
- )} +
今日: {scenario.count}