diff --git a/Cunkebao/api/devices.ts b/Cunkebao/api/devices.ts index c0b5642d..e07f0cfc 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,52 @@ 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()}`); +}; + +// 获取设备详情 - 连接到服务器/v1/devices/:id接口 +export const fetchDeviceDetail = async (id: string | number): Promise> => { + return api.get>(`/v1/devices/${id}`); +}; + +// 更新设备任务配置 +export const updateDeviceTaskConfig = async ( + id: string | number, + config: { + autoAddFriend?: boolean; + autoReply?: boolean; + momentsSync?: boolean; + aiChat?: boolean; + } +): Promise> => { + return api.post>(`/v1/devices/task-config`, { + id, + ...config + }); +}; + +// 删除设备 +export const deleteDevice = async (id: number): Promise> => { + return api.delete>(`/v1/devices/${id}`); +}; + // 设备管理API export const deviceApi = { // 创建设备 @@ -46,12 +88,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/[id]/page.tsx b/Cunkebao/app/devices/[id]/page.tsx index e6249fb6..b0107eff 100644 --- a/Cunkebao/app/devices/[id]/page.tsx +++ b/Cunkebao/app/devices/[id]/page.tsx @@ -10,6 +10,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Switch } from "@/components/ui/switch" import { Label } from "@/components/ui/label" import { ScrollArea } from "@/components/ui/scroll-area" +import { fetchDeviceDetail, updateDeviceTaskConfig } from "@/api/devices" +import { toast } from "sonner" interface WechatAccount { id: string @@ -35,7 +37,7 @@ interface Device { features: { autoAddFriend: boolean autoReply: boolean - contentSync: boolean + momentsSync: boolean aiChat: boolean } history: { @@ -43,6 +45,21 @@ interface Device { action: string operator: string }[] + totalFriend: number + thirtyDayMsgCount: number +} + +// 这个helper函数用于获取Badge变体类型 +function getBadgeVariant(status: string): "default" | "destructive" | "outline" | "secondary" { + if (status === "online" || status === "normal") { + return "default" + } else if (status === "abnormal") { + return "destructive" + } else if (status === "enabled") { + return "outline" + } else { + return "secondary" + } } export default function DeviceDetailPage() { @@ -50,64 +67,238 @@ export default function DeviceDetailPage() { const router = useRouter() const [device, setDevice] = useState(null) const [activeTab, setActiveTab] = useState("info") + const [loading, setLoading] = useState(true) + const [savingFeatures, setSavingFeatures] = useState({ + autoAddFriend: false, + autoReply: false, + momentsSync: false, + aiChat: false + }) useEffect(() => { - // 模拟API调用 - const mockDevice: Device = { - id: params.id as string, - imei: "sd123123", - name: "设备 1", - status: "online", - battery: 85, - lastActive: "2024-02-09 15:30:45", - historicalIds: ["vx412321", "vfbadasd"], - wechatAccounts: [ - { - id: "1", - avatar: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-q2rVrFbfDdAbSnT3ZTNE7gfn3QCbvr.png", - nickname: "老张", - wechatId: "wxid_abc123", - gender: "male", - status: "normal", - addFriendStatus: "enabled", - friendCount: 523, - lastActive: "2024-02-09 15:20:33", - }, - { - id: "2", - avatar: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-q2rVrFbfDdAbSnT3ZTNE7gfn3QCbvr.png", - nickname: "老李", - wechatId: "wxid_xyz789", - gender: "male", - status: "abnormal", - addFriendStatus: "disabled", - friendCount: 245, - lastActive: "2024-02-09 14:15:22", - }, - ], - features: { - autoAddFriend: true, - autoReply: true, - contentSync: false, - aiChat: true, - }, - history: [ - { - time: "2024-02-09 15:30:45", - action: "开启自动加好友", - operator: "系统", - }, - { - time: "2024-02-09 14:20:33", - action: "添加微信号", - operator: "管理员", - }, - ], + if (!params.id) return + + const fetchDevice = async () => { + try { + setLoading(true) + const response = await fetchDeviceDetail(params.id as string) + + if (response && response.code === 200 && response.data) { + const serverData = response.data + + // 构建符合前端期望格式的设备对象 + const formattedDevice: Device = { + id: serverData.id?.toString() || "", + imei: serverData.imei || "", + name: serverData.memo || "未命名设备", + status: serverData.alive === 1 ? "online" : "offline", + battery: serverData.battery || 0, + lastActive: serverData.lastUpdateTime || new Date().toISOString(), + historicalIds: [], // 服务端暂无此数据 + wechatAccounts: [], // 默认空数组 + history: [], // 服务端暂无此数据 + features: { + autoAddFriend: false, + autoReply: false, + momentsSync: false, + aiChat: false + }, + totalFriend: serverData.totalFriend || 0, + thirtyDayMsgCount: serverData.thirtyDayMsgCount || 0 + } + + // 解析features + if (serverData.features) { + // 如果后端直接返回了features对象,使用它 + formattedDevice.features = { + autoAddFriend: Boolean(serverData.features.autoAddFriend), + autoReply: Boolean(serverData.features.autoReply), + momentsSync: Boolean(serverData.features.momentsSync || serverData.features.contentSync), + aiChat: Boolean(serverData.features.aiChat) + } + } else if (serverData.taskConfig) { + try { + // 解析taskConfig字段 + let taskConfig = serverData.taskConfig + if (typeof taskConfig === 'string') { + taskConfig = JSON.parse(taskConfig) + } + + if (taskConfig) { + console.log('解析的taskConfig:', taskConfig); + formattedDevice.features = { + autoAddFriend: Boolean(taskConfig.autoAddFriend), + autoReply: Boolean(taskConfig.autoReply), + momentsSync: Boolean(taskConfig.momentsSync), + aiChat: Boolean(taskConfig.aiChat) + } + } + } catch (err) { + console.error('解析taskConfig失败:', err) + } + } + + // 如果有微信账号信息,构建微信账号对象 + if (serverData.wechatId) { + formattedDevice.wechatAccounts = [ + { + id: serverData.wechatId?.toString() || "1", + avatar: "/placeholder.svg", // 默认头像 + nickname: serverData.memo || "微信账号", + wechatId: serverData.imei || "", + gender: "male", // 默认性别 + status: serverData.alive === 1 ? "normal" : "abnormal", + addFriendStatus: "enabled", + friendCount: serverData.totalFriend || 0, + lastActive: serverData.lastUpdateTime || new Date().toISOString() + } + ] + } + + setDevice(formattedDevice) + } else { + // 如果API返回错误,则使用备用模拟数据 + toast.error("获取设备信息失败,显示备用数据") + fallbackToMockDevice() + } + } catch (error) { + console.error("获取设备信息失败:", error) + toast.error("获取设备信息出错,显示备用数据") + fallbackToMockDevice() + } finally { + setLoading(false) + } } - setDevice(mockDevice) + + const fallbackToMockDevice = () => { + const mockDevice: Device = { + id: params.id as string, + imei: "sd123123", + name: "设备 1", + status: "online", + battery: 85, + lastActive: "2024-02-09 15:30:45", + historicalIds: ["vx412321", "vfbadasd"], + wechatAccounts: [ + { + id: "1", + avatar: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-q2rVrFbfDdAbSnT3ZTNE7gfn3QCbvr.png", + nickname: "老张", + wechatId: "wxid_abc123", + gender: "male", + status: "normal", + addFriendStatus: "enabled", + friendCount: 523, + lastActive: "2024-02-09 15:20:33", + }, + { + id: "2", + avatar: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-q2rVrFbfDdAbSnT3ZTNE7gfn3QCbvr.png", + nickname: "老李", + wechatId: "wxid_xyz789", + gender: "male", + status: "abnormal", + addFriendStatus: "disabled", + friendCount: 245, + lastActive: "2024-02-09 14:15:22", + }, + ], + features: { + autoAddFriend: true, + autoReply: true, + momentsSync: false, + aiChat: true, + }, + history: [ + { + time: "2024-02-09 15:30:45", + action: "开启自动加好友", + operator: "系统", + }, + { + time: "2024-02-09 14:20:33", + action: "添加微信号", + operator: "管理员", + }, + ], + totalFriend: 768, + thirtyDayMsgCount: 5678 + } + setDevice(mockDevice) + } + + fetchDevice() }, [params.id]) - if (!device) { + // 处理功能开关状态变化 + const handleFeatureChange = async (feature: keyof Device['features'], checked: boolean) => { + if (!device) return + + // 避免已经在处理中的功能被重复触发 + if (savingFeatures[feature]) { + return + } + + setSavingFeatures(prev => ({ ...prev, [feature]: true })) + + try { + // 准备更新后的功能状态 + const updatedFeatures = { ...device.features, [feature]: checked } + + // 创建API请求参数 + const configUpdate = { [feature]: checked } + + // 立即更新UI状态,提供即时反馈 + setDevice(prev => prev ? { + ...prev, + features: updatedFeatures + } : null) + + // 调用API更新服务器配置 + const response = await updateDeviceTaskConfig(device.id, configUpdate) + + if (response && response.code === 200) { + toast.success(`${getFeatureName(feature)}${checked ? '已启用' : '已禁用'}`) + } else { + // 如果请求失败,回滚UI变更 + setDevice(prev => prev ? { + ...prev, + features: { ...prev.features, [feature]: !checked } + } : null) + + // 处理错误信息,使用类型断言解决字段不一致问题 + const anyResponse = response as any; + const errorMsg = anyResponse ? (anyResponse.message || anyResponse.msg || '未知错误') : '未知错误'; + toast.error(`更新失败: ${errorMsg}`) + } + } catch (error) { + console.error(`更新${getFeatureName(feature)}失败:`, error) + + // 异常情况下也回滚UI变更 + setDevice(prev => prev ? { + ...prev, + features: { ...prev.features, [feature]: !checked } + } : null) + + toast.error('更新失败,请稍后重试') + } finally { + setSavingFeatures(prev => ({ ...prev, [feature]: false })) + } + } + + // 获取功能中文名称 + const getFeatureName = (feature: string): string => { + const nameMap: Record = { + autoAddFriend: '自动加好友', + autoReply: '自动回复', + momentsSync: '朋友圈同步', + aiChat: 'AI会话' + } + + return nameMap[feature] || feature + } + + if (loading || !device) { return
加载中...
} @@ -137,12 +328,14 @@ export default function DeviceDetailPage() {

{device.name}

- + {device.status === "online" ? "在线" : "离线"}
IMEI: {device.imei}
-
历史ID: {device.historicalIds.join(", ")}
+ {device.historicalIds && device.historicalIds.length > 0 && ( +
历史ID: {device.historicalIds.join(", ")}
+ )}
@@ -173,28 +366,68 @@ export default function DeviceDetailPage() {
自动通过好友验证
- +
+ {savingFeatures.autoAddFriend && ( +
+ )} + handleFeatureChange('autoAddFriend', checked)} + disabled={savingFeatures.autoAddFriend} + className="data-[state=checked]:bg-blue-500 transition-all duration-200" + /> +
自动回复好友消息
- +
+ {savingFeatures.autoReply && ( +
+ )} + handleFeatureChange('autoReply', checked)} + disabled={savingFeatures.autoReply} + className="data-[state=checked]:bg-blue-500 transition-all duration-200" + /> +
自动同步朋友圈内容
- +
+ {savingFeatures.momentsSync && ( +
+ )} + handleFeatureChange('momentsSync', checked)} + disabled={savingFeatures.momentsSync} + className="data-[state=checked]:bg-blue-500 transition-all duration-200" + /> +
启用AI智能对话
- +
+ {savingFeatures.aiChat && ( +
+ )} + handleFeatureChange('aiChat', checked)} + disabled={savingFeatures.aiChat} + className="data-[state=checked]:bg-blue-500 transition-all duration-200" + /> +
@@ -203,33 +436,39 @@ export default function DeviceDetailPage() { -
- {device.wechatAccounts.map((account) => ( -
- {account.nickname} -
-
-
{account.nickname}
- - {account.status === "normal" ? "正常" : "异常"} - -
-
微信号: {account.wechatId}
-
性别: {account.gender === "male" ? "男" : "女"}
-
- 好友数: {account.friendCount} - - {account.addFriendStatus === "enabled" ? "可加友" : "已停用"} - + {device.wechatAccounts && device.wechatAccounts.length > 0 ? ( +
+ {device.wechatAccounts.map((account) => ( +
+ {account.nickname} +
+
+
{account.nickname}
+ + {account.status === "normal" ? "正常" : "异常"} + +
+
微信号: {account.wechatId}
+
性别: {account.gender === "male" ? "男" : "女"}
+
+ 好友数: {account.friendCount} + + {account.addFriendStatus === "enabled" ? "可加友" : "已停用"} + +
-
- ))} -
+ ))} +
+ ) : ( +
+

此设备暂无关联的微信账号

+
+ )} @@ -237,21 +476,27 @@ export default function DeviceDetailPage() { -
- {device.history.map((record, index) => ( -
-
- -
-
-
{record.action}
-
- 操作人: {record.operator} · {record.time} + {device.history && device.history.length > 0 ? ( +
+ {device.history.map((record, index) => ( +
+
+ +
+
+
{record.action}
+
+ 操作人: {record.operator} · {record.time} +
-
- ))} -
+ ))} +
+ ) : ( +
+

暂无操作记录

+
+ )} @@ -264,7 +509,7 @@ export default function DeviceDetailPage() { 好友总数
- {device?.wechatAccounts?.reduce((sum, account) => sum + account.friendCount, 0)} + {device.totalFriend || 0}
@@ -272,7 +517,9 @@ export default function DeviceDetailPage() { 消息数量
-
5,678
+
+ {device.thirtyDayMsgCount || 0} +
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()} + +