fix(web): 切换账号后权限缓存不刷新问题

- 登录/注册成功时先清除旧的权限缓存
- 确保切换账号后重新加载新账号的权限数据

feat(web): 系统状态接入真实 health API

- 新增 health.service.ts 调用后端健康检查接口
- 新增 useHealth hook,每 30 秒自动刷新
- DashboardStats 显示真实健康状态(正常/部分异常/异常)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
charilezhou
2026-01-20 10:35:15 +08:00
parent e7496ed41b
commit 140c268412
6 changed files with 63 additions and 6 deletions

View File

@@ -1,5 +1,6 @@
'use client';
import type { OverallHealthStatus } from '@seclusion/shared';
import { Users, UserCheck, UserPlus, Activity, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
@@ -10,6 +11,7 @@ import {
CardTitle,
} from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { useHealthCheck } from '@/hooks/useHealth';
import { useUsers, useDeletedUsers } from '@/hooks/useUsers';
interface StatCardProps {
@@ -52,6 +54,12 @@ function StatCard({
);
}
const healthStatusMap: Record<OverallHealthStatus, { label: string; description: string }> = {
ok: { label: '正常', description: '所有服务运行正常' },
degraded: { label: '部分异常', description: '部分服务运行异常' },
error: { label: '异常', description: '服务运行异常' },
};
export function DashboardStats() {
const {
data: usersData,
@@ -65,13 +73,24 @@ export function DashboardStats() {
refetch: refetchDeleted,
} = useDeletedUsers({ pageSize: 1 });
const isLoading = isLoadingUsers || isLoadingDeleted;
const {
data: healthData,
isLoading: isLoadingHealth,
refetch: refetchHealth,
} = useHealthCheck();
const isLoading = isLoadingUsers || isLoadingDeleted || isLoadingHealth;
const handleRefresh = () => {
refetchUsers();
refetchDeleted();
refetchHealth();
};
const healthStatus = healthData?.status
? healthStatusMap[healthData.status]
: { label: '-', description: '加载中...' };
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
@@ -114,10 +133,10 @@ export function DashboardStats() {
<StatCard
title="系统状态"
value="正常"
description="所有服务运行正常"
value={healthStatus.label}
description={healthStatus.description}
icon={<Activity className="h-4 w-4" />}
isLoading={false}
isLoading={isLoadingHealth}
/>
</div>
</div>

View File

@@ -20,11 +20,12 @@ import {
import { Input } from '@/components/ui/input';
import { loginSchema, type LoginFormValues } from '@/lib/validations';
import { authService } from '@/services/auth.service';
import { useAuthStore } from '@/stores';
import { useAuthStore, usePermissionStore } from '@/stores';
export function LoginForm() {
const { setAuth } = useAuthStore();
const clearPermissionData = usePermissionStore((state) => state.clearPermissionData);
const [isLoading, setIsLoading] = useState(false);
const captchaRef = useRef<CaptchaRef>(null);
@@ -48,6 +49,8 @@ export function LoginForm() {
captchaCode: values.captchaCode,
});
// 清除旧的权限缓存,确保切换账号后重新加载权限
clearPermissionData();
setAuth(
response.accessToken,
response.refreshToken,

View File

@@ -21,12 +21,13 @@ import {
import { Input } from '@/components/ui/input';
import { registerSchema, type RegisterFormValues } from '@/lib/validations';
import { authService } from '@/services/auth.service';
import { useAuthStore } from '@/stores';
import { useAuthStore, usePermissionStore } from '@/stores';
export function RegisterForm() {
const router = useRouter();
const { setAuth } = useAuthStore();
const clearPermissionData = usePermissionStore((state) => state.clearPermissionData);
const [isLoading, setIsLoading] = useState(false);
const form = useForm<RegisterFormValues>({
@@ -52,6 +53,8 @@ export function RegisterForm() {
captchaCode: values.captchaCode,
});
// 清除旧的权限缓存,确保切换账号后重新加载权限
clearPermissionData();
setAuth(
response.accessToken,
response.refreshToken,

View File

@@ -10,6 +10,7 @@ export const API_ENDPOINTS = {
SEND_RESET_EMAIL: '/auth/send-reset-email',
RESET_PASSWORD: '/auth/reset-password',
},
HEALTH: '/health',
USERS: '/users',
FILES: '/files',
CAPTCHA: '/captcha',

View File

@@ -0,0 +1,20 @@
import { useQuery } from '@tanstack/react-query';
import { healthService } from '@/services/health.service';
import { useIsAuthenticated } from '@/stores';
export const healthKeys = {
all: ['health'] as const,
check: () => [...healthKeys.all, 'check'] as const,
};
export function useHealthCheck() {
const isAuthenticated = useIsAuthenticated();
return useQuery({
queryKey: healthKeys.check(),
queryFn: () => healthService.check(),
enabled: isAuthenticated,
refetchInterval: 30000, // 每 30 秒自动刷新
});
}

View File

@@ -0,0 +1,11 @@
import type { HealthCheckResponse } from '@seclusion/shared';
import { API_ENDPOINTS } from '@/config/constants';
import { http } from '@/lib/http';
export const healthService = {
// 健康检查
check: (): Promise<HealthCheckResponse> => {
return http.get<HealthCheckResponse>(API_ENDPOINTS.HEALTH);
},
};