feat(web): 更新 UI 组件和个人资料页
- 更新个人资料页支持头像上传 - 优化 Header、Sidebar、UserNav 组件 - 更新表单验证规则 - 调整常量配置 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { RefreshCw } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { AvatarUpload } from '@/components/forms/AvatarUpload';
|
||||
import { PasswordForm } from '@/components/forms/PasswordForm';
|
||||
import { ProfileForm } from '@/components/forms/ProfileForm';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -42,6 +43,16 @@ export default function ProfilePage() {
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>头像设置</CardTitle>
|
||||
<CardDescription>更新您的个人头像。</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<AvatarUpload />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>个人信息</CardTitle>
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { Menu } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
import { UserNav } from './UserNav';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { siteConfig } from '@/config/site';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useUIStore } from '@/stores';
|
||||
|
||||
@@ -40,9 +38,9 @@ export function Header({ showSidebarToggle = true, className }: HeaderProps) {
|
||||
<span className="sr-only">切换侧边栏</span>
|
||||
</Button>
|
||||
)}
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<span className="font-bold text-xl">{siteConfig.name}</span>
|
||||
</Link>
|
||||
{/*<Link href="/" className="flex items-center space-x-2">*/}
|
||||
{/* <span className="font-bold text-xl">{siteConfig.name}</span>*/}
|
||||
{/*</Link>*/}
|
||||
</div>
|
||||
|
||||
{/* 中间:可扩展区域 */}
|
||||
|
||||
@@ -251,9 +251,7 @@ export function Sidebar() {
|
||||
)}
|
||||
>
|
||||
{!sidebarCollapsed && (
|
||||
<Link href="/dashboard" className="font-bold text-lg">
|
||||
{siteConfig.name}
|
||||
</Link>
|
||||
<span className="font-bold text-lg">{siteConfig.name}</span>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -288,13 +286,7 @@ export function MobileSidebar() {
|
||||
<SheetContent side="left" className="w-64 p-0">
|
||||
<SheetTitle className="sr-only">导航菜单</SheetTitle>
|
||||
<div className="flex h-14 items-center border-b px-4">
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="font-bold text-lg"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
>
|
||||
{siteConfig.name}
|
||||
</Link>
|
||||
<span className="font-bold text-lg">{siteConfig.name}</span>
|
||||
</div>
|
||||
<SidebarContent onItemClick={() => setSidebarOpen(false)} />
|
||||
</SheetContent>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { LogOut, User, Settings } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -14,12 +14,14 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useFileUrl } from '@/hooks/useFileUrl';
|
||||
import { performLogout } from '@/lib/auth';
|
||||
import { useAuthStore } from '@/stores';
|
||||
|
||||
export function UserNav() {
|
||||
const router = useRouter();
|
||||
const { user } = useAuthStore();
|
||||
const { data: avatarData } = useFileUrl(user?.avatarId);
|
||||
|
||||
const handleLogout = () => {
|
||||
performLogout({ showToast: true });
|
||||
@@ -43,6 +45,7 @@ export function UserNav() {
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="relative h-9 w-9 rounded-full">
|
||||
<Avatar className="h-9 w-9">
|
||||
<AvatarImage src={avatarData?.url} alt={user.name || '用户头像'} />
|
||||
<AvatarFallback className="bg-primary text-primary-foreground">
|
||||
{initials}
|
||||
</AvatarFallback>
|
||||
|
||||
@@ -7,8 +7,11 @@ export const API_ENDPOINTS = {
|
||||
ME: '/auth/me',
|
||||
PRIVILEGE: '/auth/privilege',
|
||||
LOGOUT: '/auth/logout',
|
||||
SEND_RESET_EMAIL: '/auth/send-reset-email',
|
||||
RESET_PASSWORD: '/auth/reset-password',
|
||||
},
|
||||
USERS: '/users',
|
||||
FILES: '/files',
|
||||
CAPTCHA: '/captcha',
|
||||
ROLES: '/roles',
|
||||
PERMISSIONS: '/permissions',
|
||||
|
||||
@@ -80,6 +80,10 @@ export type ForgotPasswordFormValues = z.infer<typeof forgotPasswordSchema>;
|
||||
|
||||
// ==================== 重置密码表单 ====================
|
||||
export const resetPasswordSchema = z.object({
|
||||
emailCode: z
|
||||
.string()
|
||||
.min(1, '请输入邮箱验证码')
|
||||
.length(6, '邮箱验证码为 6 位'),
|
||||
password: z
|
||||
.string()
|
||||
.min(1, '请输入新密码')
|
||||
|
||||
Reference in New Issue
Block a user