From c4698bd22a13798631f9dd96318b03a42f3e1c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Mon, 31 Mar 2025 12:04:59 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=97=A0=E9=99=90=E8=AF=B7=E6=B1=82=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/api/devices.ts | 45 +- Cunkebao/app/components/AuthProvider.tsx | 39 +- Cunkebao/app/devices/page.tsx | 295 +- Cunkebao/app/layout.tsx | 4 +- Cunkebao/app/profile/page.tsx | 28 +- Cunkebao/components/ClientOnly.tsx | 18 + Cunkebao/lib/api.ts | 10 + Cunkebao/lib/http-interceptors.ts | 17 +- Cunkebao/lib/utils.ts | 28 + Cunkebao/types/device.ts | 22 + Cunkebao_Uniapp/.env.production | 3 - Cunkebao_Uniapp/.gitignore | 2 - Cunkebao_Uniapp/App.vue | 69 - Cunkebao_Uniapp/README.md | 86 - Cunkebao_Uniapp/api/device.js | 84 - Cunkebao_Uniapp/api/user.js | 108 - Cunkebao_Uniapp/components/CustomTabBar.vue | 94 - Cunkebao_Uniapp/components/LineChart.vue | 253 -- Cunkebao_Uniapp/components/TabBar.vue | 96 - Cunkebao_Uniapp/main.js | 49 - Cunkebao_Uniapp/manifest.json | 74 - Cunkebao_Uniapp/package-lock.json | 2817 ----------------- Cunkebao_Uniapp/package.json | 32 - Cunkebao_Uniapp/pages.json | 122 - Cunkebao_Uniapp/pages/agreement/privacy.vue | 201 -- Cunkebao_Uniapp/pages/agreement/user.vue | 154 - .../content/components/FriendSelector.vue | 325 -- .../content/components/GroupSelector.vue | 325 -- Cunkebao_Uniapp/pages/content/detail.vue | 504 --- Cunkebao_Uniapp/pages/content/index.vue | 741 ----- Cunkebao_Uniapp/pages/device/detail.vue | 668 ---- Cunkebao_Uniapp/pages/device/index.vue | 1165 ------- Cunkebao_Uniapp/pages/index/index.vue | 429 --- Cunkebao_Uniapp/pages/login/index.vue | 504 --- Cunkebao_Uniapp/pages/profile/index.vue | 333 -- Cunkebao_Uniapp/pages/scenarios/create.vue | 203 -- Cunkebao_Uniapp/pages/scenarios/detail.vue | 504 --- Cunkebao_Uniapp/pages/scenarios/index.vue | 512 --- Cunkebao_Uniapp/pages/traffic/create.vue | 578 ---- Cunkebao_Uniapp/pages/traffic/index.vue | 305 -- Cunkebao_Uniapp/pages/wechat/detail.vue | 919 ------ Cunkebao_Uniapp/pages/wechat/index.vue | 439 --- Cunkebao_Uniapp/pages/work/index.vue | 477 --- Cunkebao_Uniapp/postcss.config.js | 23 - Cunkebao_Uniapp/static/fonts/fonts.css | 27 - Cunkebao_Uniapp/static/images/apple.png | 3 - Cunkebao_Uniapp/static/images/avatar.png | 1 - Cunkebao_Uniapp/static/images/empty.png | Bin 6 -> 0 bytes .../static/images/icons/ai-convert.svg | 33 - .../static/images/icons/ai-friend.svg | 27 - .../static/images/icons/ai-group.svg | 51 - Cunkebao_Uniapp/static/images/icons/aipa.svg | 1 - .../static/images/icons/data-label.svg | 5 - .../static/images/icons/filter.svg | 9 - .../static/images/icons/heartbeat.svg | 16 - .../static/images/icons/logout.svg | 15 - Cunkebao_Uniapp/static/images/icons/robot.svg | 18 - .../static/images/icons/smartphone.svg | 21 - Cunkebao_Uniapp/static/images/icons/team.svg | 35 - .../static/images/icons/trend-up.svg | 5 - .../static/images/icons/user-label.svg | 8 - Cunkebao_Uniapp/static/images/icons/users.svg | 24 - .../static/images/icons/wechat.svg | 21 - .../static/images/qr-placeholder.png | 1 - .../static/images/qr-placeholder.svg | 26 - .../static/images/tabbar/home-active.png | 1 - Cunkebao_Uniapp/static/images/tabbar/home.png | 1 - .../static/images/tabbar/market-active.png | 1 - .../static/images/tabbar/market.png | 1 - .../static/images/tabbar/profile-active.png | 1 - .../static/images/tabbar/profile.png | 1 - .../static/images/tabbar/work-active.png | 1 - Cunkebao_Uniapp/static/images/tabbar/work.png | 1 - Cunkebao_Uniapp/static/images/wechat.png | 1 - Cunkebao_Uniapp/uni.scss | 68 - Cunkebao_Uniapp/utils/auth.js | 140 - Cunkebao_Uniapp/utils/common.js | 141 - Cunkebao_Uniapp/utils/request.js | 185 -- 78 files changed, 370 insertions(+), 14224 deletions(-) create mode 100644 Cunkebao/components/ClientOnly.tsx delete mode 100644 Cunkebao_Uniapp/.env.production delete mode 100644 Cunkebao_Uniapp/.gitignore delete mode 100644 Cunkebao_Uniapp/App.vue delete mode 100644 Cunkebao_Uniapp/README.md delete mode 100644 Cunkebao_Uniapp/api/device.js delete mode 100644 Cunkebao_Uniapp/api/user.js delete mode 100644 Cunkebao_Uniapp/components/CustomTabBar.vue delete mode 100644 Cunkebao_Uniapp/components/LineChart.vue delete mode 100644 Cunkebao_Uniapp/components/TabBar.vue delete mode 100644 Cunkebao_Uniapp/main.js delete mode 100644 Cunkebao_Uniapp/manifest.json delete mode 100644 Cunkebao_Uniapp/package-lock.json delete mode 100644 Cunkebao_Uniapp/package.json delete mode 100644 Cunkebao_Uniapp/pages.json delete mode 100644 Cunkebao_Uniapp/pages/agreement/privacy.vue delete mode 100644 Cunkebao_Uniapp/pages/agreement/user.vue delete mode 100644 Cunkebao_Uniapp/pages/content/components/FriendSelector.vue delete mode 100644 Cunkebao_Uniapp/pages/content/components/GroupSelector.vue delete mode 100644 Cunkebao_Uniapp/pages/content/detail.vue delete mode 100644 Cunkebao_Uniapp/pages/content/index.vue delete mode 100644 Cunkebao_Uniapp/pages/device/detail.vue delete mode 100644 Cunkebao_Uniapp/pages/device/index.vue delete mode 100644 Cunkebao_Uniapp/pages/index/index.vue delete mode 100644 Cunkebao_Uniapp/pages/login/index.vue delete mode 100644 Cunkebao_Uniapp/pages/profile/index.vue delete mode 100644 Cunkebao_Uniapp/pages/scenarios/create.vue delete mode 100644 Cunkebao_Uniapp/pages/scenarios/detail.vue delete mode 100644 Cunkebao_Uniapp/pages/scenarios/index.vue delete mode 100644 Cunkebao_Uniapp/pages/traffic/create.vue delete mode 100644 Cunkebao_Uniapp/pages/traffic/index.vue delete mode 100644 Cunkebao_Uniapp/pages/wechat/detail.vue delete mode 100644 Cunkebao_Uniapp/pages/wechat/index.vue delete mode 100644 Cunkebao_Uniapp/pages/work/index.vue delete mode 100644 Cunkebao_Uniapp/postcss.config.js delete mode 100644 Cunkebao_Uniapp/static/fonts/fonts.css delete mode 100644 Cunkebao_Uniapp/static/images/apple.png delete mode 100644 Cunkebao_Uniapp/static/images/avatar.png delete mode 100644 Cunkebao_Uniapp/static/images/empty.png delete mode 100644 Cunkebao_Uniapp/static/images/icons/ai-convert.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/ai-friend.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/ai-group.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/aipa.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/data-label.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/filter.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/heartbeat.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/logout.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/robot.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/smartphone.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/team.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/trend-up.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/user-label.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/users.svg delete mode 100644 Cunkebao_Uniapp/static/images/icons/wechat.svg delete mode 100644 Cunkebao_Uniapp/static/images/qr-placeholder.png delete mode 100644 Cunkebao_Uniapp/static/images/qr-placeholder.svg delete mode 100644 Cunkebao_Uniapp/static/images/tabbar/home-active.png delete mode 100644 Cunkebao_Uniapp/static/images/tabbar/home.png delete mode 100644 Cunkebao_Uniapp/static/images/tabbar/market-active.png delete mode 100644 Cunkebao_Uniapp/static/images/tabbar/market.png delete mode 100644 Cunkebao_Uniapp/static/images/tabbar/profile-active.png delete mode 100644 Cunkebao_Uniapp/static/images/tabbar/profile.png delete mode 100644 Cunkebao_Uniapp/static/images/tabbar/work-active.png delete mode 100644 Cunkebao_Uniapp/static/images/tabbar/work.png delete mode 100644 Cunkebao_Uniapp/static/images/wechat.png delete mode 100644 Cunkebao_Uniapp/uni.scss delete mode 100644 Cunkebao_Uniapp/utils/auth.js delete mode 100644 Cunkebao_Uniapp/utils/common.js delete mode 100644 Cunkebao_Uniapp/utils/request.js diff --git a/Cunkebao/api/devices.ts b/Cunkebao/api/devices.ts index c0b5642d..2020fcf1 100644 --- a/Cunkebao/api/devices.ts +++ b/Cunkebao/api/devices.ts @@ -1,3 +1,4 @@ +import { api } from "@/lib/api"; import type { ApiResponse, Device, @@ -7,11 +8,31 @@ import type { QueryDeviceParams, CreateDeviceParams, UpdateDeviceParams, - DeviceStatus, // Added DeviceStatus import + DeviceStatus, + ServerDevice, + ServerDevicesResponse } from "@/types/device" const API_BASE = "/api/devices" +// 获取设备列表 - 连接到服务器/v1/devices接口 +export const fetchDeviceList = async (page: number = 1, limit: number = 20, keyword?: string): Promise => { + const params = new URLSearchParams(); + params.append('page', page.toString()); + params.append('limit', limit.toString()); + + if (keyword) { + params.append('keyword', keyword); + } + + return api.get(`/v1/devices?${params.toString()}`); +}; + +// 删除设备 +export const deleteDevice = async (id: number): Promise> => { + return api.delete>(`/v1/devices/${id}`); +}; + // 设备管理API export const deviceApi = { // 创建设备 @@ -46,12 +67,22 @@ export const deviceApi = { // 查询设备列表 async query(params: QueryDeviceParams): Promise>> { - const queryString = new URLSearchParams({ - ...params, - tags: params.tags ? JSON.stringify(params.tags) : "", - dateRange: params.dateRange ? JSON.stringify(params.dateRange) : "", - }).toString() - + // 创建一个新对象,用于构建URLSearchParams + const queryParams: Record = {}; + + // 按需将params中的属性添加到queryParams + if (params.keyword) queryParams.keyword = params.keyword; + if (params.status) queryParams.status = params.status; + if (params.type) queryParams.type = params.type; + if (params.page) queryParams.page = params.page.toString(); + if (params.pageSize) queryParams.pageSize = params.pageSize.toString(); + + // 特殊处理需要JSON序列化的属性 + if (params.tags) queryParams.tags = JSON.stringify(params.tags); + if (params.dateRange) queryParams.dateRange = JSON.stringify(params.dateRange); + + // 构建查询字符串 + const queryString = new URLSearchParams(queryParams).toString(); const response = await fetch(`${API_BASE}?${queryString}`) return response.json() }, diff --git a/Cunkebao/app/components/AuthProvider.tsx b/Cunkebao/app/components/AuthProvider.tsx index 4658091d..9a3e0da3 100644 --- a/Cunkebao/app/components/AuthProvider.tsx +++ b/Cunkebao/app/components/AuthProvider.tsx @@ -40,6 +40,7 @@ interface AuthContextType { updateToken: (newToken: string) => void } +// 创建默认上下文 const AuthContext = createContext({ isAuthenticated: false, token: null, @@ -56,20 +57,25 @@ interface AuthProviderProps { } export function AuthProvider({ children }: AuthProviderProps) { + // 避免在服务端渲染时设置初始状态 const [token, setToken] = useState(null) const [user, setUser] = useState(null) const [isAuthenticated, setIsAuthenticated] = useState(false) - const [isLoading, setIsLoading] = useState(true) + // 初始页面加载时显示为false,避免在服务端渲染和客户端水合时不匹配 + const [isLoading, setIsLoading] = useState(false) + const [isInitialized, setIsInitialized] = useState(false) const router = useRouter() - // 检查token有效性并初始化认证状态 + // 初始化认证状态 useEffect(() => { + // 仅在客户端执行初始化 + setIsLoading(true) + const initAuth = async () => { - setIsLoading(true) - const storedToken = safeLocalStorage.getItem("token") - - if (storedToken) { - try { + try { + const storedToken = safeLocalStorage.getItem("token") + + if (storedToken) { // 验证token是否有效 const isValid = await validateToken() @@ -89,17 +95,18 @@ export function AuthProvider({ children }: AuthProviderProps) { // token无效,清除 handleLogout() } - } catch (error) { - console.error("验证token时出错:", error) - handleLogout() } + } catch (error) { + console.error("验证token时出错:", error) + handleLogout() + } finally { + setIsLoading(false) + setIsInitialized(true) } - - setIsLoading(false) } initAuth() - }, []) + }, []) // 空依赖数组,仅在组件挂载时执行一次 const handleLogout = () => { safeLocalStorage.removeItem("token") @@ -131,7 +138,11 @@ export function AuthProvider({ children }: AuthProviderProps) { return ( - {isLoading ?
加载中...
: children} + {isLoading && isInitialized ? ( +
加载中...
+ ) : ( + children + )}
) } diff --git a/Cunkebao/app/devices/page.tsx b/Cunkebao/app/devices/page.tsx index dae8cee3..447ca72c 100644 --- a/Cunkebao/app/devices/page.tsx +++ b/Cunkebao/app/devices/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect } from "react" +import { useState, useEffect, useRef, useCallback } from "react" import { useRouter } from "next/navigation" import { Card } from "@/components/ui/card" import { Button } from "@/components/ui/button" @@ -11,21 +11,12 @@ import { Checkbox } from "@/components/ui/checkbox" import { toast } from "@/components/ui/use-toast" import { Badge } from "@/components/ui/badge" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { fetchDeviceList, deleteDevice } from "@/api/devices" +import { ServerDevice } from "@/types/device" -interface Device { - id: string - imei: string - name: string - remark: string - status: "online" | "offline" - battery: number - wechatId: string - friendCount: number - todayAdded: number - messageCount: number - lastActive: string - addFriendStatus: "normal" | "abnormal" - avatar?: string +// 设备接口更新为与服务端接口对应的类型 +interface Device extends ServerDevice { + status: "online" | "offline"; } export default function DevicesPage() { @@ -33,58 +24,158 @@ export default function DevicesPage() { const [devices, setDevices] = useState([]) const [isAddDeviceOpen, setIsAddDeviceOpen] = useState(false) const [stats, setStats] = useState({ - totalDevices: 42, - onlineDevices: 35, + totalDevices: 0, + onlineDevices: 0, }) const [searchQuery, setSearchQuery] = useState("") const [statusFilter, setStatusFilter] = useState("all") const [currentPage, setCurrentPage] = useState(1) - const [selectedDevices, setSelectedDevices] = useState([]) - const devicesPerPage = 10 + const [selectedDevices, setSelectedDevices] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const [hasMore, setHasMore] = useState(true) + const [totalCount, setTotalCount] = useState(0) + const observerTarget = useRef(null) + // 使用ref来追踪当前页码,避免依赖effect循环 + const pageRef = useRef(1) + const devicesPerPage = 20 // 每页显示20条记录 + + // 获取设备列表 + const loadDevices = useCallback(async (page: number, refresh: boolean = false) => { + // 检查是否已经在加载中,避免重复请求 + if (isLoading) return; + + try { + setIsLoading(true) + const response = await fetchDeviceList(page, devicesPerPage, searchQuery) + + if (response.code === 200 && response.data) { + // 转换数据格式,确保status类型正确 + const serverDevices = response.data.list.map(device => ({ + ...device, + status: device.alive === 1 ? "online" as const : "offline" as const + })) + + // 更新设备列表 + if (refresh) { + setDevices(serverDevices) + } else { + setDevices(prev => [...prev, ...serverDevices]) + } + + // 更新统计信息 + const total = response.data.total + const online = response.data.list.filter(d => d.alive === 1).length + setStats({ + totalDevices: total, + onlineDevices: online + }) + + // 更新分页信息 + setTotalCount(response.data.total) + + // 更新hasMore状态,确保有更多数据且返回的数据数量等于每页数量 + const hasMoreData = serverDevices.length > 0 && + serverDevices.length === devicesPerPage && + (page * devicesPerPage) < response.data.total; + setHasMore(hasMoreData) + + // 更新当前页码的ref值 + pageRef.current = page + } else { + toast({ + title: "获取设备列表失败", + description: response.msg || "请稍后重试", + variant: "destructive", + }) + } + } catch (error) { + console.error("获取设备列表失败", error) + toast({ + title: "获取设备列表失败", + description: "请检查网络连接后重试", + variant: "destructive", + }) + } finally { + setIsLoading(false) + } + // 移除isLoading依赖,只保留真正需要的依赖 + }, [searchQuery, devicesPerPage]) + + // 加载下一页数据的函数,使用ref来追踪页码,避免依赖循环 + const loadNextPage = useCallback(() => { + // 如果正在加载或者没有更多数据,直接返回 + if (isLoading || !hasMore) return; + + // 使用ref来获取下一页码,避免依赖currentPage + const nextPage = pageRef.current + 1; + // 设置UI显示的当前页 + setCurrentPage(nextPage); + // 加载下一页数据 + loadDevices(nextPage, false); + // 只依赖必要的状态 + }, [hasMore, isLoading, loadDevices]); + + // 初始加载和搜索时刷新列表 useEffect(() => { - // 模拟API调用 - const fetchDevices = async () => { - const mockDevices = Array.from({ length: 42 }, (_, i) => ({ - id: `device-${i + 1}`, - imei: `sd${123123 + i}`, - name: `设备 ${i + 1}`, - remark: `备注 ${i + 1}`, - status: Math.random() > 0.2 ? "online" : "offline", - battery: Math.floor(Math.random() * 100), - wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`, - friendCount: Math.floor(Math.random() * 1000), - todayAdded: Math.floor(Math.random() * 50), - messageCount: Math.floor(Math.random() * 200), - lastActive: new Date(Date.now() - Math.random() * 86400000).toLocaleString(), - addFriendStatus: Math.random() > 0.2 ? "normal" : "abnormal", - avatar: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-kYhfQsrrByfbzefv6MEV7W7ogz0IRt.png", - })) - setDevices(mockDevices) + // 重置页码 + setCurrentPage(1) + pageRef.current = 1 + // 加载第一页数据 + loadDevices(1, true) + }, [searchQuery, loadDevices]) + + // 无限滚动加载实现 + useEffect(() => { + // 如果没有更多数据或者正在加载,不创建observer + if (!hasMore || isLoading) return; + + let isMounted = true; // 追踪组件是否已挂载 + + // 创建观察器观察加载点 + const observer = new IntersectionObserver( + entries => { + // 如果交叉了,且有更多数据,且当前不在加载状态,且组件仍然挂载 + if (entries[0].isIntersecting && hasMore && !isLoading && isMounted) { + loadNextPage(); + } + }, + { threshold: 0.5 } + ) + + // 只在客户端时观察节点 + if (typeof window !== 'undefined' && observerTarget.current) { + observer.observe(observerTarget.current) } - fetchDevices() - }, []) + // 清理观察器 + return () => { + isMounted = false; + observer.disconnect(); + } + }, [hasMore, isLoading, loadNextPage]) + // 刷新设备列表 const handleRefresh = () => { + setCurrentPage(1) + pageRef.current = 1 + loadDevices(1, true) toast({ title: "刷新成功", description: "设备列表已更新", }) } - const filteredDevices = devices.filter((device) => { - const matchesSearch = - device.name.toLowerCase().includes(searchQuery.toLowerCase()) || - device.imei.toLowerCase().includes(searchQuery.toLowerCase()) || - device.wechatId.toLowerCase().includes(searchQuery.toLowerCase()) - const matchesStatus = statusFilter === "all" || device.status === statusFilter - return matchesSearch && matchesStatus + // 筛选设备 + const filteredDevices = devices.filter(device => { + const matchesStatus = statusFilter === "all" || + (statusFilter === "online" && device.alive === 1) || + (statusFilter === "offline" && device.alive === 0) + return matchesStatus }) - const paginatedDevices = filteredDevices.slice((currentPage - 1) * devicesPerPage, currentPage * devicesPerPage) - - const handleBatchDelete = () => { + // 处理批量删除 + const handleBatchDelete = async () => { if (selectedDevices.length === 0) { toast({ title: "请选择设备", @@ -93,14 +184,40 @@ export default function DevicesPage() { }) return } - toast({ - title: "批量删除成功", - description: `已删除 ${selectedDevices.length} 个设备`, - }) - setSelectedDevices([]) + + // 这里需要实现批量删除逻辑 + // 目前只是单个删除的循环 + let successCount = 0 + for (const deviceId of selectedDevices) { + try { + const response = await deleteDevice(deviceId) + if (response.code === 200) { + successCount++ + } + } catch (error) { + console.error(`删除设备 ${deviceId} 失败`, error) + } + } + + // 删除后刷新列表 + if (successCount > 0) { + toast({ + title: "批量删除成功", + description: `已删除 ${successCount} 个设备`, + }) + setSelectedDevices([]) + handleRefresh() + } else { + toast({ + title: "批量删除失败", + description: "请稍后重试", + variant: "destructive", + }) + } } - const handleDeviceClick = (deviceId: string) => { + // 设备详情页跳转 + const handleDeviceClick = (deviceId: number) => { router.push(`/devices/${deviceId}`) } @@ -167,10 +284,10 @@ export default function DevicesPage() {
0} onCheckedChange={(checked) => { if (checked) { - setSelectedDevices(paginatedDevices.map((d) => d.id)) + setSelectedDevices(filteredDevices.map((d) => d.id)) } else { setSelectedDevices([]) } @@ -190,7 +307,7 @@ export default function DevicesPage() {
- {paginatedDevices.map((device) => ( + {filteredDevices.map((device) => (
-
{device.name}
- +
{device.memo}
+ {device.status === "online" ? "在线" : "离线"}
IMEI: {device.imei}
-
微信号: {device.wechatId}
+
微信号: {device.wechatId || "未绑定"}
- 好友数: {device.friendCount} - 今日新增: +{device.todayAdded} + 好友数: {device.totalFriend}
))} - -
- - - 第 {currentPage} / {Math.ceil(filteredDevices.length / devicesPerPage)} 页 - - + {/* 加载更多观察点 */} +
+ {isLoading &&
加载中...
} + {!hasMore && devices.length > 0 &&
没有更多设备了
} + {!hasMore && devices.length === 0 &&
暂无设备
} +
+ {/* 添加设备对话框 */} - + 添加设备 -
-
- +
+
+ +
-

- 请使用设备扫描二维码进行添加 -
- 或手动输入设备ID -

- -
+
+ + +
+
- +
diff --git a/Cunkebao/app/layout.tsx b/Cunkebao/app/layout.tsx index 33c55a52..ef99c4cc 100644 --- a/Cunkebao/app/layout.tsx +++ b/Cunkebao/app/layout.tsx @@ -9,7 +9,7 @@ import LayoutWrapper from "./components/LayoutWrapper" export const metadata: Metadata = { title: "存客宝", description: "智能客户管理系统", - generator: 'v0.dev' + generator: 'v0.dev' } export default function RootLayout({ @@ -18,7 +18,7 @@ export default function RootLayout({ children: React.ReactNode }) { return ( - + diff --git a/Cunkebao/app/profile/page.tsx b/Cunkebao/app/profile/page.tsx index 32b02d09..2a5ee1e2 100644 --- a/Cunkebao/app/profile/page.tsx +++ b/Cunkebao/app/profile/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import { ChevronRight, Settings, Bell, LogOut } from "lucide-react" import { Card } from "@/components/ui/card" import { Button } from "@/components/ui/button" @@ -8,6 +8,8 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { useRouter } from "next/navigation" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { useAuth } from "@/app/components/AuthProvider" +import ClientOnly from "@/components/ClientOnly" +import { getClientRandomId } from "@/lib/utils" const menuItems = [ { href: "/devices", label: "设备管理" }, @@ -20,7 +22,13 @@ export default function ProfilePage() { const router = useRouter() const { isAuthenticated, user, logout } = useAuth() const [showLogoutDialog, setShowLogoutDialog] = useState(false) - const [accountId] = useState(() => user?.account || Math.floor(10000000 + Math.random() * 90000000).toString()) + + // 处理身份验证状态,将路由重定向逻辑移至useEffect + useEffect(() => { + if (!isAuthenticated) { + router.push("/login") + } + }, [isAuthenticated, router]) const handleLogout = () => { logout() // 使用AuthProvider中的logout方法删除本地保存的用户信息 @@ -28,11 +36,6 @@ export default function ProfilePage() { router.push("/login") } - if (!isAuthenticated) { - router.push("/login") - return null - } - return (
@@ -54,12 +57,16 @@ export default function ProfilePage() {
- - {user?.username?.slice(0, 2) || "KR"} + + {user?.username ? user.username.slice(0, 2) : "用户"}

{user?.username || "用户"}

-

账号: {user?.account || accountId}

+

+ 账号: + {user?.account || Math.floor(10000000 + Math.random() * 90000000).toString()} + +