FEAT => 本次更新项目为:
存了
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
import React from "react";
|
||||
import AppRouter from "@/router";
|
||||
|
||||
function App() {
|
||||
return <AppRouter />;
|
||||
return (
|
||||
<>
|
||||
<AppRouter />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
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<DeviceGuardProps> = ({ 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 (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100vh",
|
||||
background: "var(--primary-color)",
|
||||
}}
|
||||
>
|
||||
<div style={{ color: "white", fontSize: "16px" }}>
|
||||
检查设备状态中...
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default DeviceGuard;
|
||||
@@ -8,15 +8,16 @@ import {
|
||||
QrcodeOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import { fetchDeviceQRCode, addDeviceByImei, fetchDeviceList } from "./api";
|
||||
import { useUserStore, useDeviceStore } from "@/store";
|
||||
import { getDashboard } from "@/pages/mobile/home/api";
|
||||
import { fetchDeviceQRCode, addDeviceByImei } from "./api";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
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);
|
||||
@@ -36,10 +37,8 @@ const Guide: React.FC = () => {
|
||||
const checkDeviceStatus = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const dashboardData = await fetchDeviceList({
|
||||
accountId: user.s2_accountId,
|
||||
});
|
||||
const deviceNum = dashboardData.added ? 1 : 0;
|
||||
const dashboardData = await getDashboard();
|
||||
const deviceNum = dashboardData?.deviceNum || 0;
|
||||
|
||||
setDeviceCount(deviceNum);
|
||||
|
||||
@@ -49,6 +48,7 @@ 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,9 +71,7 @@ const Guide: React.FC = () => {
|
||||
|
||||
const pollDeviceStatus = async () => {
|
||||
try {
|
||||
const dashboardData = await fetchDeviceList({
|
||||
accountId: user.s2_accountId,
|
||||
});
|
||||
const dashboardData = await getDashboard();
|
||||
const currentDeviceCount = dashboardData?.deviceNum || 0;
|
||||
|
||||
// 如果设备数量增加了,说明有新设备添加成功
|
||||
@@ -97,7 +95,7 @@ const Guide: React.FC = () => {
|
||||
|
||||
// 每3秒检查一次设备状态
|
||||
pollingRef.current = setInterval(pollDeviceStatus, 3000);
|
||||
}, [isPolling, deviceCount, setDeviceCount]);
|
||||
}, [isPolling, deviceCount]);
|
||||
|
||||
// 停止轮询
|
||||
const stopPolling = useCallback(() => {
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface LoginResponse {
|
||||
data: {
|
||||
token: string;
|
||||
token_expired: string;
|
||||
deviceTotal: number; // 设备总数
|
||||
member: {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { Form, Input, Button, Toast, Tabs, Checkbox } from "antd-mobile";
|
||||
import { Form, Input, Button, Toast, Checkbox } from "antd-mobile";
|
||||
import {
|
||||
EyeInvisibleOutline,
|
||||
EyeOutline,
|
||||
UserOutline,
|
||||
} from "antd-mobile-icons";
|
||||
import { useUserStore, useDeviceStore } from "@/store";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api";
|
||||
import { updateDeviceCount } from "@/utils/device";
|
||||
import style from "./login.module.scss";
|
||||
|
||||
const Login: React.FC = () => {
|
||||
@@ -22,7 +21,6 @@ const Login: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { login } = useUserStore();
|
||||
const { setDeviceCount } = useDeviceStore();
|
||||
|
||||
// 倒计时效果
|
||||
useEffect(() => {
|
||||
@@ -44,17 +42,33 @@ 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 {
|
||||
const phone = form.getFieldValue("phone");
|
||||
if (!phone) {
|
||||
Toast.show({ content: "请输入手机号", position: "top" });
|
||||
return;
|
||||
}
|
||||
await sendVerificationCode(phone);
|
||||
setCountdown(60);
|
||||
setLoading(true);
|
||||
await sendVerificationCode({
|
||||
mobile: account,
|
||||
type: "login",
|
||||
});
|
||||
|
||||
Toast.show({ content: "验证码已发送", position: "top" });
|
||||
} catch (error: any) {
|
||||
// 错误已在request中处理
|
||||
setCountdown(60);
|
||||
} catch (error) {
|
||||
// 错误已在request中处理,这里不需要额外处理
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,40 +95,32 @@ const Login: React.FC = () => {
|
||||
}
|
||||
console.log(response, "response");
|
||||
|
||||
// 获取设备总数
|
||||
const deviceTotal = response.deviceTotal || 0;
|
||||
console.log(deviceTotal, "deviceTotal");
|
||||
|
||||
// 更新状态管理(token会自动存储到localStorage,用户信息存储在状态管理中)
|
||||
login(response.token, response.member);
|
||||
login(response.token, response.member, deviceTotal);
|
||||
|
||||
Toast.show({ content: "登录成功", position: "top" });
|
||||
|
||||
// 检查设备绑定状态并更新到store
|
||||
try {
|
||||
const deviceNum = await updateDeviceCount(setDeviceCount);
|
||||
console.log(deviceNum, "deviceNum");
|
||||
|
||||
// 如果没有绑定设备,跳转到引导页面
|
||||
if (deviceNum === 0) {
|
||||
navigate("/guide");
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("检查设备状态失败:", error);
|
||||
// 如果检查失败,设置设备数量为0并跳转到guide页面
|
||||
setDeviceCount(0);
|
||||
navigate("/guide");
|
||||
return;
|
||||
}
|
||||
|
||||
// 跳转到首页或重定向URL
|
||||
const returnUrl = searchParams.get("returnUrl");
|
||||
if (returnUrl) {
|
||||
const decodedUrl = decodeURIComponent(returnUrl);
|
||||
if (isLoginPage(decodedUrl)) {
|
||||
navigate("/");
|
||||
// 根据设备数量判断跳转
|
||||
if (deviceTotal > 0) {
|
||||
// 有设备,跳转到首页或重定向URL
|
||||
const returnUrl = searchParams.get("returnUrl");
|
||||
if (returnUrl) {
|
||||
const decodedUrl = decodeURIComponent(returnUrl);
|
||||
if (isLoginPage(decodedUrl)) {
|
||||
navigate("/");
|
||||
} else {
|
||||
window.location.href = decodedUrl;
|
||||
}
|
||||
} else {
|
||||
window.location.href = decodedUrl;
|
||||
navigate("/");
|
||||
}
|
||||
} else {
|
||||
navigate("/");
|
||||
// 没有设备,跳转到引导页面
|
||||
navigate("/guide");
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 错误已在request中处理,这里不需要额外处理
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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 });
|
||||
@@ -43,9 +42,7 @@ const AppRouter: React.FC = () => (
|
||||
v7_relativeSplatPath: true,
|
||||
}}
|
||||
>
|
||||
<DeviceGuard>
|
||||
<AppRoutes />
|
||||
</DeviceGuard>
|
||||
<AppRoutes />
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from "./module/user";
|
||||
export * from "./module/device";
|
||||
// 未来可继续合并其他模块
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { createPersistStore } from "@/store/createPersistStore";
|
||||
|
||||
export interface DeviceState {
|
||||
deviceCount: number;
|
||||
setDeviceCount: (count: number) => void;
|
||||
updateDeviceCount: () => Promise<void>;
|
||||
resetDeviceCount: () => void;
|
||||
}
|
||||
|
||||
export const useDeviceStore = createPersistStore<DeviceState>(
|
||||
(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,
|
||||
}),
|
||||
);
|
||||
@@ -15,6 +15,7 @@ export interface User {
|
||||
updateTime: string | null;
|
||||
lastLoginIp: string;
|
||||
lastLoginTime: number;
|
||||
deviceTotal: number; // 设备总数
|
||||
}
|
||||
|
||||
interface UserState {
|
||||
@@ -24,7 +25,7 @@ interface UserState {
|
||||
setUser: (user: User) => void;
|
||||
setToken: (token: string) => void;
|
||||
clearUser: () => void;
|
||||
login: (token: string, userInfo: User) => void;
|
||||
login: (token: string, userInfo: User, deviceTotal: number) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
@@ -36,7 +37,7 @@ export const useUserStore = createPersistStore<UserState>(
|
||||
setUser: user => set({ user, isLoggedIn: true }),
|
||||
setToken: token => set({ token }),
|
||||
clearUser: () => set({ user: null, token: null, isLoggedIn: false }),
|
||||
login: (token, userInfo) => {
|
||||
login: (token, userInfo, deviceTotal) => {
|
||||
// 只将token存储到localStorage
|
||||
localStorage.setItem("token", token);
|
||||
|
||||
@@ -56,6 +57,7 @@ export const useUserStore = createPersistStore<UserState>(
|
||||
updateTime: userInfo.updateTime,
|
||||
lastLoginIp: userInfo.lastLoginIp,
|
||||
lastLoginTime: userInfo.lastLoginTime,
|
||||
deviceTotal: deviceTotal,
|
||||
};
|
||||
set({ user, token, isLoggedIn: true });
|
||||
},
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { getDashboard } from "@/pages/mobile/home/api";
|
||||
|
||||
/**
|
||||
* 更新设备数量到store
|
||||
* @param setDeviceCount store中的setDeviceCount函数
|
||||
* @returns 更新后的设备数量
|
||||
*/
|
||||
export const updateDeviceCount = async (
|
||||
setDeviceCount: (count: number) => void,
|
||||
): Promise<number> => {
|
||||
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;
|
||||
};
|
||||
Reference in New Issue
Block a user