Cosmo
DEEP SPACE EXPLORER
diff --git a/frontend/src/components/TerminalModal.tsx b/frontend/src/components/TerminalModal.tsx
index a0cf854..5b420c9 100644
--- a/frontend/src/components/TerminalModal.tsx
+++ b/frontend/src/components/TerminalModal.tsx
@@ -67,6 +67,8 @@ export function TerminalModal({
}
.terminal-modal .ant-modal-close {
color: #2ea043 !important;
+ top: 24px !important;
+ inset-inline-end: 20px !important;
}
.terminal-modal .ant-modal-close:hover {
background-color: rgba(35, 134, 54, 0.2) !important;
diff --git a/frontend/src/contexts/ToastContext.tsx b/frontend/src/contexts/ToastContext.tsx
index bd49420..47a85ae 100644
--- a/frontend/src/contexts/ToastContext.tsx
+++ b/frontend/src/contexts/ToastContext.tsx
@@ -96,7 +96,7 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
{children}
{/* Toast Container - Top Right */}
-
+
{toasts.map((toast) => (
([]);
const [loading, setLoading] = useState(true);
+ const [profileModalOpen, setProfileModalOpen] = useState(false);
+ const [passwordModalOpen, setPasswordModalOpen] = useState(false);
+ const [profileForm] = Form.useForm();
+ const [passwordForm] = Form.useForm();
+ const [userProfile, setUserProfile] = useState
(null);
const navigate = useNavigate();
const location = useLocation();
const user = auth.getUser();
@@ -96,11 +101,57 @@ export function AdminLayout() {
}
};
+ const handleProfileClick = async () => {
+ try {
+ const { data } = await request.get('/users/me');
+ setUserProfile(data);
+ profileForm.setFieldsValue({
+ username: data.username,
+ email: data.email || '',
+ full_name: data.full_name || '',
+ });
+ setProfileModalOpen(true);
+ } catch (error) {
+ toast.error('获取用户信息失败');
+ }
+ };
+
+ const handleProfileUpdate = async (values: any) => {
+ try {
+ await request.put('/users/me/profile', {
+ full_name: values.full_name,
+ email: values.email || null,
+ });
+ toast.success('个人信息更新成功');
+ setProfileModalOpen(false);
+ // Update local user info
+ const updatedUser = { ...user, full_name: values.full_name, email: values.email };
+ auth.setUser(updatedUser);
+ } catch (error: any) {
+ toast.error(error.response?.data?.detail || '更新失败');
+ }
+ };
+
+ const handlePasswordChange = async (values: any) => {
+ try {
+ await request.put('/users/me/password', {
+ old_password: values.old_password,
+ new_password: values.new_password,
+ });
+ toast.success('密码修改成功');
+ setPasswordModalOpen(false);
+ passwordForm.resetFields();
+ } catch (error: any) {
+ toast.error(error.response?.data?.detail || '密码修改失败');
+ }
+ };
+
const userMenuItems: MenuProps['items'] = [
{
key: 'profile',
icon: ,
label: '个人信息',
+ onClick: handleProfileClick,
},
{
type: 'divider',
@@ -174,6 +225,108 @@ export function AdminLayout() {
+
+ {/* Profile Modal */}
+ setProfileModalOpen(false)}
+ footer={null}
+ width={500}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Password Change Modal */}
+ {
+ setPasswordModalOpen(false);
+ passwordForm.resetFields();
+ }}
+ footer={null}
+ width={450}
+ >
+
+
+
+
+
+
+ ({
+ validator(_, value) {
+ if (!value || getFieldValue('new_password') === value) {
+ return Promise.resolve();
+ }
+ return Promise.reject(new Error('两次输入的密码不一致'));
+ },
+ }),
+ ]}
+ >
+
+
+
+
+
+
+
);
}