新增頭像圖片上傳

This commit is contained in:
2025-09-21 22:11:20 +08:00
parent 38ae30d611
commit 59d22966c2
22 changed files with 1904 additions and 1437 deletions

View File

@@ -1,6 +1,6 @@
"use client"
import { useState } from "react"
import { useState, useEffect } from "react"
import { useAuth } from "@/contexts/auth-context"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
@@ -31,6 +31,22 @@ export function ProfileDialog({ open, onOpenChange }: ProfileDialogProps) {
phone: user?.phone || "",
location: user?.location || "",
})
const [avatar, setAvatar] = useState(user?.avatar || "")
// 監聽用戶狀態變化,同步更新本地狀態
useEffect(() => {
if (user) {
setProfileData({
name: user.name || "",
email: user.email || "",
department: user.department || "",
bio: user.bio || "",
phone: user.phone || "",
location: user.location || "",
})
setAvatar(user.avatar || "")
}
}, [user])
const departments = ["HQBU", "ITBU", "MBU1", "MBU2", "SBU", "財務部", "人資部", "法務部"]
@@ -40,7 +56,7 @@ export function ProfileDialog({ open, onOpenChange }: ProfileDialogProps) {
setIsLoading(true)
try {
await updateProfile(profileData)
await updateProfile({ ...profileData, avatar })
setSuccess("個人資料更新成功!")
setTimeout(() => setSuccess(""), 3000)
} catch (err) {
@@ -50,6 +66,59 @@ export function ProfileDialog({ open, onOpenChange }: ProfileDialogProps) {
}
}
const handleAvatarFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
// 驗證文件類型
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'];
if (!allowedTypes.includes(file.type)) {
setError('只支援 JPEG、PNG、WebP 格式的圖片');
return;
}
// 驗證文件大小(限制 5MB
const maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
setError('圖片大小不能超過 5MB');
return;
}
setIsLoading(true);
setError('');
setSuccess('');
try {
const formData = new FormData();
formData.append('avatar', file);
formData.append('userId', user?.id || '');
const response = await fetch('/api/upload/avatar', {
method: 'POST',
body: formData,
});
const result = await response.json();
if (result.success) {
setSuccess('頭像上傳成功!');
// 更新全局用戶狀態
await updateProfile({ avatar: result.data.imageUrl });
setTimeout(() => setSuccess(''), 3000);
} else {
setError(result.error || '上傳失敗');
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '上傳失敗';
setError(`頭像上傳失敗:${errorMsg}`);
} finally {
setIsLoading(false);
// 清空文件輸入
event.target.value = '';
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
@@ -77,9 +146,9 @@ export function ProfileDialog({ open, onOpenChange }: ProfileDialogProps) {
)}
<div className="flex items-center space-x-6">
<div className="relative">
<div className="relative inline-block">
<Avatar className="w-24 h-24">
<AvatarImage src={user?.avatar} />
<AvatarImage src={user?.avatar || avatar} />
<AvatarFallback className="text-2xl bg-gradient-to-r from-blue-600 to-purple-600 text-white">
{user?.name?.charAt(0) || "U"}
</AvatarFallback>
@@ -87,10 +156,18 @@ export function ProfileDialog({ open, onOpenChange }: ProfileDialogProps) {
<Button
size="sm"
variant="outline"
className="absolute -bottom-2 -right-2 rounded-full w-8 h-8 p-0 bg-transparent"
className="absolute -bottom-1 -right-1 rounded-full w-8 h-8 p-0 bg-white/80 hover:bg-white/90 border-2 border-white/50 shadow-md backdrop-blur-sm"
onClick={() => document.getElementById('avatar-upload')?.click()}
>
<Camera className="w-4 h-4" />
</Button>
<input
id="avatar-upload"
type="file"
accept="image/jpeg,image/jpg,image/png,image/webp"
className="hidden"
onChange={handleAvatarFileSelect}
/>
</div>
<div>
<h3 className="text-xl font-semibold">{user?.name}</h3>

View File

@@ -10,7 +10,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { User, BarChart3, Settings, LogOut, Code, Shield, Upload } from "lucide-react"
import { LoginDialog } from "./login-dialog"
import { RegisterDialog } from "./register-dialog"
@@ -119,6 +119,7 @@ export function UserMenu() {
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-10 w-10 rounded-full">
<Avatar className="h-10 w-10">
<AvatarImage src={user.avatar} />
<AvatarFallback className="bg-gradient-to-r from-blue-600 to-purple-600 text-white">
{user.name.charAt(0)}
</AvatarFallback>
@@ -128,6 +129,7 @@ export function UserMenu() {
<DropdownMenuContent className="w-80" align="end" forceMount>
<div className="flex items-center justify-start gap-2 p-4">
<Avatar className="h-12 w-12">
<AvatarImage src={user.avatar} />
<AvatarFallback className="bg-gradient-to-r from-blue-600 to-purple-600 text-white text-lg">
{user.name.charAt(0)}
</AvatarFallback>