FEAT => 本次更新项目为:
设备绑定指引
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# 基础环境变量示例
|
# 基础环境变量示例
|
||||||
# VITE_API_BASE_URL=http://www.yishi.com
|
VITE_API_BASE_URL=http://www.yishi.com
|
||||||
VITE_API_BASE_URL=https://ckbapi.quwanzhi.com
|
# VITE_API_BASE_URL=https://ckbapi.quwanzhi.com
|
||||||
VITE_APP_TITLE=Nkebao Base
|
VITE_APP_TITLE=Nkebao Base
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import AppRouter from "@/router";
|
import AppRouter from "@/router";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return <AppRouter />;
|
||||||
<>
|
|
||||||
<AppRouter />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
90
nkebao/src/components/DeviceGuard/index.tsx
Normal file
90
nkebao/src/components/DeviceGuard/index.tsx
Normal file
@@ -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<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;
|
||||||
13
nkebao/src/pages/guide/api.ts
Normal file
13
nkebao/src/pages/guide/api.ts
Normal file
@@ -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");
|
||||||
@@ -8,16 +8,15 @@ import {
|
|||||||
QrcodeOutlined,
|
QrcodeOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import Layout from "@/components/Layout/Layout";
|
import Layout from "@/components/Layout/Layout";
|
||||||
import { getDashboard } from "@/pages/mobile/home/api";
|
import { fetchDeviceQRCode, addDeviceByImei, fetchDeviceList } from "./api";
|
||||||
import { fetchDeviceQRCode, addDeviceByImei } from "@/api/devices";
|
import { useUserStore, useDeviceStore } from "@/store";
|
||||||
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 = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { user } = useUserStore();
|
const { user } = useUserStore();
|
||||||
|
const { deviceCount, setDeviceCount } = useDeviceStore();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [deviceCount, setDeviceCount] = useState(0);
|
|
||||||
|
|
||||||
// 添加设备弹窗状态
|
// 添加设备弹窗状态
|
||||||
const [addVisible, setAddVisible] = useState(false);
|
const [addVisible, setAddVisible] = useState(false);
|
||||||
@@ -37,8 +36,10 @@ const Guide: React.FC = () => {
|
|||||||
const checkDeviceStatus = useCallback(async () => {
|
const checkDeviceStatus = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const dashboardData = await getDashboard();
|
const dashboardData = await fetchDeviceList({
|
||||||
const deviceNum = dashboardData?.deviceNum || 0;
|
accountId: user.s2_accountId,
|
||||||
|
});
|
||||||
|
const deviceNum = dashboardData.added ? 1 : 0;
|
||||||
|
|
||||||
setDeviceCount(deviceNum);
|
setDeviceCount(deviceNum);
|
||||||
|
|
||||||
@@ -48,7 +49,6 @@ const Guide: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("检查设备状态失败:", error);
|
|
||||||
Toast.show({
|
Toast.show({
|
||||||
content: "检查设备状态失败,请重试",
|
content: "检查设备状态失败,请重试",
|
||||||
position: "top",
|
position: "top",
|
||||||
@@ -56,7 +56,7 @@ const Guide: React.FC = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [navigate, setDeviceCount]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkDeviceStatus();
|
checkDeviceStatus();
|
||||||
@@ -71,7 +71,9 @@ const Guide: React.FC = () => {
|
|||||||
|
|
||||||
const pollDeviceStatus = async () => {
|
const pollDeviceStatus = async () => {
|
||||||
try {
|
try {
|
||||||
const dashboardData = await getDashboard();
|
const dashboardData = await fetchDeviceList({
|
||||||
|
accountId: user.s2_accountId,
|
||||||
|
});
|
||||||
const currentDeviceCount = dashboardData?.deviceNum || 0;
|
const currentDeviceCount = dashboardData?.deviceNum || 0;
|
||||||
|
|
||||||
// 如果设备数量增加了,说明有新设备添加成功
|
// 如果设备数量增加了,说明有新设备添加成功
|
||||||
@@ -95,7 +97,7 @@ const Guide: React.FC = () => {
|
|||||||
|
|
||||||
// 每3秒检查一次设备状态
|
// 每3秒检查一次设备状态
|
||||||
pollingRef.current = setInterval(pollDeviceStatus, 3000);
|
pollingRef.current = setInterval(pollDeviceStatus, 3000);
|
||||||
}, [isPolling, deviceCount]);
|
}, [isPolling, deviceCount, setDeviceCount]);
|
||||||
|
|
||||||
// 停止轮询
|
// 停止轮询
|
||||||
const stopPolling = useCallback(() => {
|
const stopPolling = useCallback(() => {
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import {
|
|||||||
EyeOutline,
|
EyeOutline,
|
||||||
UserOutline,
|
UserOutline,
|
||||||
} from "antd-mobile-icons";
|
} from "antd-mobile-icons";
|
||||||
import { useUserStore } from "@/store/module/user";
|
import { useUserStore, useDeviceStore } from "@/store";
|
||||||
import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api";
|
import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api";
|
||||||
import { getDashboard } from "@/pages/mobile/home/api";
|
import { updateDeviceCount } from "@/utils/device";
|
||||||
import style from "./login.module.scss";
|
import style from "./login.module.scss";
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
const Login: React.FC = () => {
|
||||||
@@ -22,6 +22,7 @@ const Login: React.FC = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { login } = useUserStore();
|
const { login } = useUserStore();
|
||||||
|
const { setDeviceCount } = useDeviceStore();
|
||||||
|
|
||||||
// 倒计时效果
|
// 倒计时效果
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -43,33 +44,17 @@ const Login: React.FC = () => {
|
|||||||
|
|
||||||
// 发送验证码
|
// 发送验证码
|
||||||
const handleSendVerificationCode = async () => {
|
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 {
|
try {
|
||||||
setLoading(true);
|
const phone = form.getFieldValue("phone");
|
||||||
await sendVerificationCode({
|
if (!phone) {
|
||||||
mobile: account,
|
Toast.show({ content: "请输入手机号", position: "top" });
|
||||||
type: "login",
|
return;
|
||||||
});
|
}
|
||||||
|
await sendVerificationCode(phone);
|
||||||
Toast.show({ content: "验证码已发送", position: "top" });
|
|
||||||
setCountdown(60);
|
setCountdown(60);
|
||||||
} catch (error) {
|
Toast.show({ content: "验证码已发送", position: "top" });
|
||||||
// 错误已在request中处理,这里不需要额外处理
|
} catch (error: any) {
|
||||||
} finally {
|
// 错误已在request中处理
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,11 +86,11 @@ const Login: React.FC = () => {
|
|||||||
|
|
||||||
Toast.show({ content: "登录成功", position: "top" });
|
Toast.show({ content: "登录成功", position: "top" });
|
||||||
|
|
||||||
// 检查设备绑定状态
|
// 检查设备绑定状态并更新到store
|
||||||
try {
|
try {
|
||||||
const dashboardData = await getDashboard();
|
const deviceNum = await updateDeviceCount(setDeviceCount);
|
||||||
const deviceNum = dashboardData?.deviceNum || 0;
|
|
||||||
console.log(deviceNum, "deviceNum");
|
console.log(deviceNum, "deviceNum");
|
||||||
|
|
||||||
// 如果没有绑定设备,跳转到引导页面
|
// 如果没有绑定设备,跳转到引导页面
|
||||||
if (deviceNum === 0) {
|
if (deviceNum === 0) {
|
||||||
navigate("/guide");
|
navigate("/guide");
|
||||||
@@ -113,7 +98,10 @@ const Login: React.FC = () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("检查设备状态失败:", error);
|
console.error("检查设备状态失败:", error);
|
||||||
// 如果检查失败,默认跳转到首页
|
// 如果检查失败,设置设备数量为0并跳转到guide页面
|
||||||
|
setDeviceCount(0);
|
||||||
|
navigate("/guide");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跳转到首页或重定向URL
|
// 跳转到首页或重定向URL
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ import { useParams, useNavigate } from "react-router-dom";
|
|||||||
import { NavBar, Tabs, Switch, Toast, SpinLoading, Button } from "antd-mobile";
|
import { NavBar, Tabs, Switch, Toast, SpinLoading, Button } from "antd-mobile";
|
||||||
import { SettingOutlined, RedoOutlined } from "@ant-design/icons";
|
import { SettingOutlined, RedoOutlined } from "@ant-design/icons";
|
||||||
import Layout from "@/components/Layout/Layout";
|
import Layout from "@/components/Layout/Layout";
|
||||||
import MeauMobile from "@/components/MeauMobile/MeauMoible";
|
|
||||||
import {
|
import {
|
||||||
fetchDeviceDetail,
|
fetchDeviceDetail,
|
||||||
fetchDeviceRelatedAccounts,
|
fetchDeviceRelatedAccounts,
|
||||||
fetchDeviceHandleLogs,
|
fetchDeviceHandleLogs,
|
||||||
updateDeviceTaskConfig,
|
updateDeviceTaskConfig,
|
||||||
} from "@/api/devices";
|
} from "./api";
|
||||||
import type { Device, WechatAccount, HandleLog } from "@/types/device";
|
import type { Device, WechatAccount, HandleLog } from "@/types/device";
|
||||||
|
|
||||||
const DeviceDetail: React.FC = () => {
|
const DeviceDetail: React.FC = () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import request from "./request";
|
import request from "@/api/request";
|
||||||
|
|
||||||
// 获取设备列表
|
// 获取设备列表
|
||||||
export const fetchDeviceList = (params: {
|
export const fetchDeviceList = (params: {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useRef, useState, useCallback } from "react";
|
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 { Button, Input, Pagination, Checkbox } from "antd";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { AddOutline, DeleteOutline } from "antd-mobile-icons";
|
import { AddOutline, DeleteOutline } from "antd-mobile-icons";
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
QrcodeOutlined,
|
QrcodeOutlined,
|
||||||
ArrowLeftOutlined,
|
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import Layout from "@/components/Layout/Layout";
|
import Layout from "@/components/Layout/Layout";
|
||||||
import {
|
import {
|
||||||
@@ -15,7 +14,7 @@ import {
|
|||||||
fetchDeviceQRCode,
|
fetchDeviceQRCode,
|
||||||
addDeviceByImei,
|
addDeviceByImei,
|
||||||
deleteDevice,
|
deleteDevice,
|
||||||
} from "@/api/devices";
|
} from "./api";
|
||||||
import type { Device } from "@/types/device";
|
import type { Device } from "@/types/device";
|
||||||
import { comfirm } from "@/utils/common";
|
import { comfirm } from "@/utils/common";
|
||||||
import { useUserStore } from "@/store/module/user";
|
import { useUserStore } from "@/store/module/user";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import request from "@/api/request";
|
import request from "@/api/request";
|
||||||
import type { TrafficPoolListResponse, DeviceOption } from "./data";
|
import type { TrafficPoolListResponse, DeviceOption } from "./data";
|
||||||
import { fetchDeviceList } from "@/api/devices";
|
import { fetchDeviceList } from "@/pages/guide/api";
|
||||||
|
|
||||||
// 获取流量池列表
|
// 获取流量池列表
|
||||||
export function fetchTrafficPoolList(params: {
|
export function fetchTrafficPoolList(params: {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { BrowserRouter, useRoutes, RouteObject } from "react-router-dom";
|
import { BrowserRouter, useRoutes, RouteObject } from "react-router-dom";
|
||||||
import PermissionRoute from "./permissionRoute";
|
import PermissionRoute from "./permissionRoute";
|
||||||
|
import DeviceGuard from "@/components/DeviceGuard";
|
||||||
|
|
||||||
// 动态导入所有 module 下的 ts/tsx 路由模块
|
// 动态导入所有 module 下的 ts/tsx 路由模块
|
||||||
const modules = import.meta.glob("./module/*.{ts,tsx}", { eager: true });
|
const modules = import.meta.glob("./module/*.{ts,tsx}", { eager: true });
|
||||||
@@ -42,7 +43,9 @@ const AppRouter: React.FC = () => (
|
|||||||
v7_relativeSplatPath: true,
|
v7_relativeSplatPath: true,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AppRoutes />
|
<DeviceGuard>
|
||||||
|
<AppRoutes />
|
||||||
|
</DeviceGuard>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./module/user";
|
export * from "./module/user";
|
||||||
|
export * from "./module/device";
|
||||||
// 未来可继续合并其他模块
|
// 未来可继续合并其他模块
|
||||||
|
|||||||
30
nkebao/src/store/module/device.ts
Normal file
30
nkebao/src/store/module/device.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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,
|
||||||
|
}),
|
||||||
|
);
|
||||||
30
nkebao/src/utils/device.ts
Normal file
30
nkebao/src/utils/device.ts
Normal file
@@ -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<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