add user dropdown

Change-Id: I4c7b259ea7bd9b5c6ea2c8566b2ca08c5b2e6647
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index e29b4f6..3b52378 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,9 +1,9 @@
 // app/layout.tsx
 
 import type { Metadata } from 'next';
-import { Avatar } from 'primereact/avatar';
 // 页面跳转
 import Link from 'next/link';
+import UserAvatar from './user/component/userAvatar';
 // PrimeReact 依赖
 import { PrimeReactProvider } from 'primereact/api';
 import 'primeicons/primeicons.css';
@@ -69,9 +69,7 @@
                     <span>通知</span>
                   </div>
                 </Link>
-                <Link href="/user" className="no-underline">
-                  <Avatar image="/images/avatar/asiyajavayant.png" size="large" shape="circle" />
-                </Link>
+                <UserAvatar />
               </div>
             </header>
             <main className="mainContent">{children}</main>
diff --git a/src/app/user/component/user-avatar.scss b/src/app/user/component/user-avatar.scss
new file mode 100644
index 0000000..16f872d
--- /dev/null
+++ b/src/app/user/component/user-avatar.scss
@@ -0,0 +1,41 @@
+.user-avatar-wrapper {
+    display: flex;
+    flex-direction: column;
+    position: relative;
+}
+
+.user-avatar-link {
+    cursor: pointer;
+}
+
+.user-overlay-panel {
+    display: flex;
+    flex-direction: column;
+    gap: 0.5rem;
+    padding: 0.5rem;
+}
+
+.dialog-container {
+    display: flex;
+    flex-direction: column;
+    gap: 2rem;
+    text-align: center;
+    margin-top: 2rem;
+}
+
+.dialog-input-group {
+
+    .p-float-label {
+        width: 100%;
+    }
+
+    .p-inputtext {
+        width: 100%;
+    }
+}
+
+.dialog-button-group {
+    .p-button {
+        margin-right: 1rem;
+    }
+}
diff --git a/src/app/user/component/userAvatar.tsx b/src/app/user/component/userAvatar.tsx
new file mode 100644
index 0000000..4ed94d7
--- /dev/null
+++ b/src/app/user/component/userAvatar.tsx
@@ -0,0 +1,282 @@
+'use client';
+
+import { useRef, useState } from 'react';
+import { Avatar } from 'primereact/avatar';
+import { Button } from 'primereact/button';
+// 弹窗
+import { Dialog } from 'primereact/dialog';
+// 头像下拉框
+import { OverlayPanel } from 'primereact/overlaypanel';
+// 输入框
+import { FloatLabel } from "primereact/floatlabel";
+import { InputText } from 'primereact/inputtext';
+// 页面跳转
+import Link from 'next/link';
+// 文件上传
+import { FileUpload } from 'primereact/fileupload';
+// 通知
+import { Toast } from 'primereact/toast';
+// 接口传输
+import axios from 'axios';
+// 样式
+import './user-avatar.scss';
+
+// 用户下拉框
+export default function UserAvatar() {
+    // 功能选项
+    const op = useRef<OverlayPanel>(null);
+    let hoverTimeout: NodeJS.Timeout;
+    // 通知
+    const toast = useRef<Toast>(null);
+    // 控制三个弹窗可见性
+    const [showEditSignature, setShowEditSignature] = useState(false);
+    const [showEditAvatar, setShowEditAvatar] = useState(false);
+    const [showEditPassword, setShowEditPassword] = useState(false);
+    // 头像URL
+    const [avatarUrl, setAvatar] = useState<string>('');
+    // 签名
+    const [signValue, setSignValue] = useState<string>('');
+    // 新密码
+    const [passwardValue, setPasswardValue] = useState<string>('');
+    const [newPasswardValue, setNewPasswardValue] = useState<string>('');
+    // 老密码
+    const [oldPasswardValue, setOldPasswardValue] = useState<string>('');
+
+
+    const handleMouseEnter = (event: React.MouseEvent) => {
+        clearTimeout(hoverTimeout);
+        op.current?.show(event, event.currentTarget);
+    };
+
+    const handleMouseLeave = () => {
+        hoverTimeout = setTimeout(() => {
+            op.current?.hide();
+        }, 300);
+    };
+
+
+    // 修改密码接口
+    const editPassward = async () => {
+        try {
+            await axios.put(process.env.PUBLIC_URL + `/user/password`, {
+                params: { userId: 22301145, password: oldPasswardValue, newPassword: passwardValue }
+            });
+            toast.current?.show({ severity: 'success', summary: 'success', detail: '修改密码成功' });
+            setShowEditPassword(false);
+        } catch (err) {
+            console.error('修改密码失败', err);
+            toast.current?.show({ severity: 'error', summary: 'error', detail: '修改密码失败' });
+        }
+    }
+    // 修改签名接口
+    const editSign = async () => {
+        try {
+            await axios.put(process.env.PUBLIC_URL + `/user/signature`, {
+                params: { userId: 22301145, signature: signValue }
+            });
+            toast.current?.show({ severity: 'success', summary: 'success', detail: '修改签名成功' });
+            setShowEditSignature(false);
+        } catch (err) {
+            console.error('修改签名失败', err);
+            toast.current?.show({ severity: 'error', summary: 'error', detail: '修改签名失败' });
+        }
+    }
+
+    // 修改头像接口
+    const editAvatar = async () => {
+        try {
+            await axios.put(process.env.PUBLIC_URL + `/user/avatar`, {
+                params: { userId: 22301145, avatar: avatarUrl }
+            });
+            toast.current?.show({ severity: 'success', summary: 'success', detail: '修改头像成功' });
+            setShowEditAvatar(false);
+        } catch (err) {
+            console.error('修改头像失败', err);
+            toast.current?.show({ severity: 'error', summary: 'error', detail: '修改头像失败' });
+        }
+    }
+    return (
+        <div
+            onMouseEnter={handleMouseEnter}
+            onMouseLeave={handleMouseLeave}
+            className="user-avatar-wrapper"
+        >
+            <Toast ref={toast}></Toast>
+            <Link href="/user" className="no-underline">
+                <Avatar
+                    image="/images/avatar/asiyajavayant.png"
+                    size="large"
+                    shape="circle"
+                    className="user-avatar-link"
+                />
+            </Link>
+
+            <OverlayPanel ref={op} dismissable={false}>
+                <div
+                    onMouseEnter={() => clearTimeout(hoverTimeout)}
+                    onMouseLeave={handleMouseLeave}
+                    className="user-overlay-panel"
+                >
+                    <Button
+                        text
+                        icon="pi pi-pencil"
+                        label="修改签名"
+                        onClick={() => setShowEditSignature(true)}
+                    />
+                    <Button
+                        text
+                        icon="pi pi-image"
+                        label="修改头像"
+                        onClick={() => setShowEditAvatar(true)}
+                    />
+                    <Button
+                        text
+                        icon="pi pi-unlock"
+                        label="修改密码"
+                        onClick={() => setShowEditPassword(true)}
+                    />
+                </div>
+            </OverlayPanel>
+
+            {/* 修改签名弹窗 */}
+            <Dialog
+                header="修改签名"
+                visible={showEditSignature}
+                style={{ width: '30vw' }}
+                onHide={() => {
+                    setSignValue('');
+                    setShowEditSignature(false);
+                }}
+                modal
+            >
+                <div className="dialog-container">
+                    <div className="dialog-input-group">
+                        <FloatLabel>
+                            <InputText id="username" value={signValue} onChange={(e) => setSignValue(e.target.value)} />
+                            <label htmlFor="username">个性签名</label>
+                        </FloatLabel>
+                    </div>
+                    <div className="dialog-button-group">
+                        <Button label="确定" className="p-button-sm" onClick={() => editSign()} />
+                        <Button
+                            label="取消"
+                            className="p-button-secondary p-button-sm"
+                            onClick={() => {
+                                setSignValue('');
+                                setShowEditSignature(false);
+                            }}
+                        />
+                    </div>
+                </div>
+            </Dialog>
+
+            {/* 修改头像弹窗 */}
+            <Dialog
+                header="修改头像"
+                visible={showEditAvatar}
+                style={{ display: 'flex', flexDirection: 'column', width: '30vw' }}
+                onHide={() => {
+                    setAvatar('');
+                    setShowEditAvatar(false);
+                }}
+                modal
+            >
+                <div className="dialog-container">
+                    <FileUpload
+                        mode="advanced"
+                        name="file"
+                        customUpload
+                        uploadHandler={async (e) => {
+                            const formData = new FormData();
+                            formData.append("file", e.files[0]);
+
+                            try {
+                                const res = await axios.post(`${process.env.PUBLIC_URL}/file/avatar`, formData);
+
+                                const fileUrl = res.data.url;
+                                console.log(fileUrl);
+                                setAvatar(fileUrl);
+                                toast.current?.show({ severity: 'success', summary: '上传成功' });
+                            } catch (error) {
+                                console.log(error);
+                                toast.current?.show({ severity: 'error', summary: '上传失败' });
+                            }
+                        }}
+                        auto
+                        accept="image/*"
+                        chooseLabel="上传头像"
+                    />
+
+                    <div className="dialog-button-group">
+                        <Button label="确定" className="p-button-sm" onClick={() => editAvatar()} />
+                        <Button
+                            label="取消"
+                            className="p-button-secondary p-button-sm"
+                            onClick={() => setShowEditAvatar(false)}
+                        />
+                    </div>
+                </div>
+            </Dialog>
+
+            {/* 修改密码弹窗 */}
+            <Dialog
+                header="修改密码"
+                visible={showEditPassword}
+                style={{ width: '30vw' }}
+                onHide={() => {
+                    setOldPasswardValue('');
+                    setPasswardValue('');
+                    setNewPasswardValue('');
+                    setShowEditPassword(false);
+                }}
+                modal
+            >
+                <div className="dialog-container">
+                    <div className="dialog-input-group">
+                        <FloatLabel>
+                            <InputText id="username" value={oldPasswardValue} onChange={(e) => setOldPasswardValue(e.target.value)} />
+                            <label htmlFor="username">输入旧密码</label>
+                        </FloatLabel>
+                    </div>
+                    <div className="dialog-input-group">
+                        <FloatLabel>
+                            <InputText id="username" value={passwardValue} onChange={(e) => setPasswardValue(e.target.value)} />
+                            <label htmlFor="username">更新密码</label>
+                        </FloatLabel>
+                    </div>
+                    <div className="dialog-input-group">
+                        <FloatLabel>
+                            <InputText id="username" value={newPasswardValue} onChange={(e) => setNewPasswardValue(e.target.value)} />
+                            <label htmlFor="username">确认密码</label>
+                        </FloatLabel>
+                    </div>
+                    <div className="dialog-button-group">
+                        <Button label="确定" className="p-button-sm" onClick={() => {
+                            if (passwardValue !== newPasswardValue) {
+                                toast.current?.show({
+                                    severity: 'warn',
+                                    summary: '两次密码不一致',
+                                    detail: '请确保新密码和确认密码一致',
+                                });
+                                return;
+                            } else {
+                                editPassward();
+                            }
+                        }} />
+                        <Button
+                            label="取消"
+                            className="p-button-secondary p-button-sm"
+                            onClick={() => {
+                                setOldPasswardValue('');
+                                setPasswardValue('');
+                                setNewPasswardValue('');
+                                setShowEditPassword(false);
+                            }}
+                        />
+                    </div>
+                </div>
+            </Dialog>
+        </div>
+    );
+
+}