From f78af9e77cf4bf9986f1b3d0774d246cfc44721f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 30 Jul 2025 12:52:58 +0800 Subject: [PATCH] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A=20=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=BB=91=E5=AE=9A=E6=8C=87=E5=BC=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/.env.development | 4 +- nkebao/src/App.tsx | 7 +- nkebao/src/components/DeviceGuard/index.tsx | 90 +++++++++++++++++++ nkebao/src/pages/guide/api.ts | 13 +++ nkebao/src/pages/guide/index.tsx | 22 ++--- nkebao/src/pages/login/login.tsx | 50 ++++------- .../mobile/mine/devices/DeviceDetail.tsx | 3 +- .../mobile/mine/devices/api.ts} | 2 +- .../src/pages/mobile/mine/devices/index.tsx | 5 +- .../mobile/mine/traffic-pool/list/api.ts | 2 +- nkebao/src/router/index.tsx | 5 +- nkebao/src/store/index.ts | 1 + nkebao/src/store/module/device.ts | 30 +++++++ nkebao/src/utils/device.ts | 30 +++++++ 14 files changed, 208 insertions(+), 56 deletions(-) create mode 100644 nkebao/src/components/DeviceGuard/index.tsx create mode 100644 nkebao/src/pages/guide/api.ts rename nkebao/src/{api/devices.ts => pages/mobile/mine/devices/api.ts} (97%) create mode 100644 nkebao/src/store/module/device.ts create mode 100644 nkebao/src/utils/device.ts diff --git a/nkebao/.env.development b/nkebao/.env.development index 9ac98215..d130c9ed 100644 --- a/nkebao/.env.development +++ b/nkebao/.env.development @@ -1,4 +1,4 @@ # 基础环境变量示例 -# VITE_API_BASE_URL=http://www.yishi.com -VITE_API_BASE_URL=https://ckbapi.quwanzhi.com +VITE_API_BASE_URL=http://www.yishi.com +# VITE_API_BASE_URL=https://ckbapi.quwanzhi.com VITE_APP_TITLE=Nkebao Base diff --git a/nkebao/src/App.tsx b/nkebao/src/App.tsx index bfee5515..fa7a4541 100644 --- a/nkebao/src/App.tsx +++ b/nkebao/src/App.tsx @@ -1,11 +1,8 @@ import React from "react"; import AppRouter from "@/router"; + function App() { - return ( - <> - - - ); + return ; } export default App; diff --git a/nkebao/src/components/DeviceGuard/index.tsx b/nkebao/src/components/DeviceGuard/index.tsx new file mode 100644 index 00000000..9335ddfe --- /dev/null +++ b/nkebao/src/components/DeviceGuard/index.tsx @@ -0,0 +1,90 @@ +import React, { useEffect, useState, useMemo } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import { useDeviceStore } from "@/store/module/device"; +import { useUserStore } from "@/store/module/user"; +import { updateDeviceCount } from "@/utils/device"; + +interface DeviceGuardProps { + children: React.ReactNode; +} + +const DeviceGuard: React.FC = ({ children }) => { + const navigate = useNavigate(); + const location = useLocation(); + const { isLoggedIn } = useUserStore(); + const { setDeviceCount } = useDeviceStore(); + const [isChecking, setIsChecking] = useState(true); + + // 不需要设备检查的路径 + const EXEMPT_PATHS = useMemo( + () => ["/login", "/guide", "/register", "/forgot-password"], + [], + ); + + useEffect(() => { + const checkDeviceStatus = async () => { + // 如果用户未登录,不需要检查设备状态 + if (!isLoggedIn) { + setIsChecking(false); + return; + } + + // 如果当前路径是豁免路径,不需要检查设备状态 + if (EXEMPT_PATHS.includes(location.pathname)) { + setIsChecking(false); + return; + } + + try { + // 从API获取最新的设备数量并更新到store + const currentDeviceCount = await updateDeviceCount(setDeviceCount); + + // 如果设备数量为0且不在guide页面,跳转到guide页面 + if (currentDeviceCount === 0 && location.pathname !== "/guide") { + navigate("/guide"); + return; + } + + // 如果设备数量大于0且在guide页面,跳转到首页 + if (currentDeviceCount > 0 && location.pathname === "/guide") { + navigate("/"); + return; + } + } catch (error) { + console.error("检查设备状态失败:", error); + // 如果检查失败,默认跳转到guide页面 + if (location.pathname !== "/guide") { + navigate("/guide"); + return; + } + } finally { + setIsChecking(false); + } + }; + + checkDeviceStatus(); + }, [isLoggedIn, location.pathname, navigate, setDeviceCount, EXEMPT_PATHS]); + + // 如果正在检查,显示加载状态 + if (isChecking) { + return ( +
+
+ 检查设备状态中... +
+
+ ); + } + + return <>{children}; +}; + +export default DeviceGuard; diff --git a/nkebao/src/pages/guide/api.ts b/nkebao/src/pages/guide/api.ts new file mode 100644 index 00000000..d86d80ce --- /dev/null +++ b/nkebao/src/pages/guide/api.ts @@ -0,0 +1,13 @@ +import request from "@/api/request"; + +// 获取设备二维码 +export const fetchDeviceQRCode = (accountId: string) => + request("/v1/api/device/add", { accountId }, "POST"); + +// 通过IMEI添加设备 +export const addDeviceByImei = (imei: string, name: string) => + request("/v1/api/device/add-by-imei", { imei, name }, "POST"); + +// 获取设备列表 +export const fetchDeviceList = (params: { accountId?: string }) => + request("/v1/devices/add-results", params, "GET"); diff --git a/nkebao/src/pages/guide/index.tsx b/nkebao/src/pages/guide/index.tsx index 9dda790a..27c8aed8 100644 --- a/nkebao/src/pages/guide/index.tsx +++ b/nkebao/src/pages/guide/index.tsx @@ -8,16 +8,15 @@ import { QrcodeOutlined, } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; -import { getDashboard } from "@/pages/mobile/home/api"; -import { fetchDeviceQRCode, addDeviceByImei } from "@/api/devices"; -import { useUserStore } from "@/store/module/user"; +import { fetchDeviceQRCode, addDeviceByImei, fetchDeviceList } from "./api"; +import { useUserStore, useDeviceStore } from "@/store"; import styles from "./index.module.scss"; const Guide: React.FC = () => { const navigate = useNavigate(); const { user } = useUserStore(); + const { deviceCount, setDeviceCount } = useDeviceStore(); const [loading, setLoading] = useState(true); - const [deviceCount, setDeviceCount] = useState(0); // 添加设备弹窗状态 const [addVisible, setAddVisible] = useState(false); @@ -37,8 +36,10 @@ const Guide: React.FC = () => { const checkDeviceStatus = useCallback(async () => { try { setLoading(true); - const dashboardData = await getDashboard(); - const deviceNum = dashboardData?.deviceNum || 0; + const dashboardData = await fetchDeviceList({ + accountId: user.s2_accountId, + }); + const deviceNum = dashboardData.added ? 1 : 0; setDeviceCount(deviceNum); @@ -48,7 +49,6 @@ const Guide: React.FC = () => { return; } } catch (error) { - console.error("检查设备状态失败:", error); Toast.show({ content: "检查设备状态失败,请重试", position: "top", @@ -56,7 +56,7 @@ const Guide: React.FC = () => { } finally { setLoading(false); } - }, []); + }, [navigate, setDeviceCount]); useEffect(() => { checkDeviceStatus(); @@ -71,7 +71,9 @@ const Guide: React.FC = () => { const pollDeviceStatus = async () => { try { - const dashboardData = await getDashboard(); + const dashboardData = await fetchDeviceList({ + accountId: user.s2_accountId, + }); const currentDeviceCount = dashboardData?.deviceNum || 0; // 如果设备数量增加了,说明有新设备添加成功 @@ -95,7 +97,7 @@ const Guide: React.FC = () => { // 每3秒检查一次设备状态 pollingRef.current = setInterval(pollDeviceStatus, 3000); - }, [isPolling, deviceCount]); + }, [isPolling, deviceCount, setDeviceCount]); // 停止轮询 const stopPolling = useCallback(() => { diff --git a/nkebao/src/pages/login/login.tsx b/nkebao/src/pages/login/login.tsx index 225b8ef7..5a91a67c 100644 --- a/nkebao/src/pages/login/login.tsx +++ b/nkebao/src/pages/login/login.tsx @@ -6,9 +6,9 @@ import { EyeOutline, UserOutline, } from "antd-mobile-icons"; -import { useUserStore } from "@/store/module/user"; +import { useUserStore, useDeviceStore } from "@/store"; import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api"; -import { getDashboard } from "@/pages/mobile/home/api"; +import { updateDeviceCount } from "@/utils/device"; import style from "./login.module.scss"; const Login: React.FC = () => { @@ -22,6 +22,7 @@ const Login: React.FC = () => { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const { login } = useUserStore(); + const { setDeviceCount } = useDeviceStore(); // 倒计时效果 useEffect(() => { @@ -43,33 +44,17 @@ const Login: React.FC = () => { // 发送验证码 const handleSendVerificationCode = async () => { - const account = form.getFieldValue("account"); - - if (!account) { - Toast.show({ content: "请输入手机号", position: "top" }); - return; - } - - // 手机号格式验证 - const phoneRegex = /^1[3-9]\d{9}$/; - if (!phoneRegex.test(account)) { - Toast.show({ content: "请输入正确的11位手机号", position: "top" }); - return; - } - try { - setLoading(true); - await sendVerificationCode({ - mobile: account, - type: "login", - }); - - Toast.show({ content: "验证码已发送", position: "top" }); + const phone = form.getFieldValue("phone"); + if (!phone) { + Toast.show({ content: "请输入手机号", position: "top" }); + return; + } + await sendVerificationCode(phone); setCountdown(60); - } catch (error) { - // 错误已在request中处理,这里不需要额外处理 - } finally { - setLoading(false); + Toast.show({ content: "验证码已发送", position: "top" }); + } catch (error: any) { + // 错误已在request中处理 } }; @@ -101,11 +86,11 @@ const Login: React.FC = () => { Toast.show({ content: "登录成功", position: "top" }); - // 检查设备绑定状态 + // 检查设备绑定状态并更新到store try { - const dashboardData = await getDashboard(); - const deviceNum = dashboardData?.deviceNum || 0; + const deviceNum = await updateDeviceCount(setDeviceCount); console.log(deviceNum, "deviceNum"); + // 如果没有绑定设备,跳转到引导页面 if (deviceNum === 0) { navigate("/guide"); @@ -113,7 +98,10 @@ const Login: React.FC = () => { } } catch (error) { console.error("检查设备状态失败:", error); - // 如果检查失败,默认跳转到首页 + // 如果检查失败,设置设备数量为0并跳转到guide页面 + setDeviceCount(0); + navigate("/guide"); + return; } // 跳转到首页或重定向URL diff --git a/nkebao/src/pages/mobile/mine/devices/DeviceDetail.tsx b/nkebao/src/pages/mobile/mine/devices/DeviceDetail.tsx index 0b9fadc3..944d72b2 100644 --- a/nkebao/src/pages/mobile/mine/devices/DeviceDetail.tsx +++ b/nkebao/src/pages/mobile/mine/devices/DeviceDetail.tsx @@ -3,13 +3,12 @@ import { useParams, useNavigate } from "react-router-dom"; import { NavBar, Tabs, Switch, Toast, SpinLoading, Button } from "antd-mobile"; import { SettingOutlined, RedoOutlined } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; -import MeauMobile from "@/components/MeauMobile/MeauMoible"; import { fetchDeviceDetail, fetchDeviceRelatedAccounts, fetchDeviceHandleLogs, updateDeviceTaskConfig, -} from "@/api/devices"; +} from "./api"; import type { Device, WechatAccount, HandleLog } from "@/types/device"; const DeviceDetail: React.FC = () => { diff --git a/nkebao/src/api/devices.ts b/nkebao/src/pages/mobile/mine/devices/api.ts similarity index 97% rename from nkebao/src/api/devices.ts rename to nkebao/src/pages/mobile/mine/devices/api.ts index 1a4534ab..c8e91198 100644 --- a/nkebao/src/api/devices.ts +++ b/nkebao/src/pages/mobile/mine/devices/api.ts @@ -1,4 +1,4 @@ -import request from "./request"; +import request from "@/api/request"; // 获取设备列表 export const fetchDeviceList = (params: { diff --git a/nkebao/src/pages/mobile/mine/devices/index.tsx b/nkebao/src/pages/mobile/mine/devices/index.tsx index 8fe74b53..444cfea3 100644 --- a/nkebao/src/pages/mobile/mine/devices/index.tsx +++ b/nkebao/src/pages/mobile/mine/devices/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState, useCallback } from "react"; -import { NavBar, Popup, Tabs, Toast, SpinLoading, Dialog } from "antd-mobile"; +import { Popup, Tabs, Toast, SpinLoading } from "antd-mobile"; import { Button, Input, Pagination, Checkbox } from "antd"; import { useNavigate } from "react-router-dom"; import { AddOutline, DeleteOutline } from "antd-mobile-icons"; @@ -7,7 +7,6 @@ import { ReloadOutlined, SearchOutlined, QrcodeOutlined, - ArrowLeftOutlined, } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; import { @@ -15,7 +14,7 @@ import { fetchDeviceQRCode, addDeviceByImei, deleteDevice, -} from "@/api/devices"; +} from "./api"; import type { Device } from "@/types/device"; import { comfirm } from "@/utils/common"; import { useUserStore } from "@/store/module/user"; diff --git a/nkebao/src/pages/mobile/mine/traffic-pool/list/api.ts b/nkebao/src/pages/mobile/mine/traffic-pool/list/api.ts index b5b9bc36..d8bc9af4 100644 --- a/nkebao/src/pages/mobile/mine/traffic-pool/list/api.ts +++ b/nkebao/src/pages/mobile/mine/traffic-pool/list/api.ts @@ -1,6 +1,6 @@ import request from "@/api/request"; import type { TrafficPoolListResponse, DeviceOption } from "./data"; -import { fetchDeviceList } from "@/api/devices"; +import { fetchDeviceList } from "@/pages/guide/api"; // 获取流量池列表 export function fetchTrafficPoolList(params: { diff --git a/nkebao/src/router/index.tsx b/nkebao/src/router/index.tsx index 117681a7..7ff448cc 100644 --- a/nkebao/src/router/index.tsx +++ b/nkebao/src/router/index.tsx @@ -1,6 +1,7 @@ import React from "react"; import { BrowserRouter, useRoutes, RouteObject } from "react-router-dom"; import PermissionRoute from "./permissionRoute"; +import DeviceGuard from "@/components/DeviceGuard"; // 动态导入所有 module 下的 ts/tsx 路由模块 const modules = import.meta.glob("./module/*.{ts,tsx}", { eager: true }); @@ -42,7 +43,9 @@ const AppRouter: React.FC = () => ( v7_relativeSplatPath: true, }} > - + + + ); diff --git a/nkebao/src/store/index.ts b/nkebao/src/store/index.ts index 4eca24e3..c74f8ad2 100644 --- a/nkebao/src/store/index.ts +++ b/nkebao/src/store/index.ts @@ -1,2 +1,3 @@ export * from "./module/user"; +export * from "./module/device"; // 未来可继续合并其他模块 diff --git a/nkebao/src/store/module/device.ts b/nkebao/src/store/module/device.ts new file mode 100644 index 00000000..53477263 --- /dev/null +++ b/nkebao/src/store/module/device.ts @@ -0,0 +1,30 @@ +import { createPersistStore } from "@/store/createPersistStore"; + +export interface DeviceState { + deviceCount: number; + setDeviceCount: (count: number) => void; + updateDeviceCount: () => Promise; + resetDeviceCount: () => void; +} + +export const useDeviceStore = createPersistStore( + (set, get) => ({ + deviceCount: 0, + setDeviceCount: (count: number) => set({ deviceCount: count }), + updateDeviceCount: async () => { + try { + // 这里需要导入getDashboard,但为了避免循环依赖,我们通过参数传入 + // 实际使用时会在组件中调用并传入API函数 + set({ deviceCount: 0 }); // 默认设置为0,实际值由调用方设置 + } catch (error) { + console.error("更新设备数量失败:", error); + set({ deviceCount: 0 }); + } + }, + resetDeviceCount: () => set({ deviceCount: 0 }), + }), + "device-store", + state => ({ + deviceCount: state.deviceCount, + }), +); diff --git a/nkebao/src/utils/device.ts b/nkebao/src/utils/device.ts new file mode 100644 index 00000000..8b5c5b8c --- /dev/null +++ b/nkebao/src/utils/device.ts @@ -0,0 +1,30 @@ +import { getDashboard } from "@/pages/mobile/home/api"; + +/** + * 更新设备数量到store + * @param setDeviceCount store中的setDeviceCount函数 + * @returns 更新后的设备数量 + */ +export const updateDeviceCount = async ( + setDeviceCount: (count: number) => void, +): Promise => { + try { + const dashboardData = await getDashboard(); + const deviceCount = dashboardData?.deviceNum || 0; + setDeviceCount(deviceCount); + return deviceCount; + } catch (error) { + console.error("更新设备数量失败:", error); + setDeviceCount(0); + return 0; + } +}; + +/** + * 检查是否需要设备绑定 + * @param deviceCount 设备数量 + * @returns 是否需要设备绑定 + */ +export const needsDeviceBinding = (deviceCount: number): boolean => { + return deviceCount === 0; +};