import { useState, useEffect } from 'react' import { Card, Tabs, Form, Input, Button, Avatar, Upload, Space, Typography, Modal } from 'antd' import { UserOutlined, LockOutlined, UploadOutlined, ApiOutlined, CopyOutlined, ReloadOutlined } from '@ant-design/icons' import ImgCrop from 'antd-img-crop' import { getCurrentUser, updateProfile, changePassword, uploadAvatar, getMcpCredentials, rotateMcpSecret } from '@/api/auth' import useUserStore from '@/stores/userStore' import Toast from '@/components/Toast/Toast' import './ProfilePage.css' const { Paragraph, Text } = Typography function ProfilePage() { const [loading, setLoading] = useState(false) const [userInfo, setUserInfo] = useState(null) const [mcpCredentials, setMcpCredentials] = useState(null) const [profileForm] = Form.useForm() const [passwordForm] = Form.useForm() const { setUser } = useUserStore() useEffect(() => { loadUserInfo() loadMcpCredentials() }, []) // 加载用户信息 const loadUserInfo = async () => { try { const res = await getCurrentUser() setUserInfo(res.data) profileForm.setFieldsValue({ username: res.data.username, nickname: res.data.nickname, email: res.data.email, phone: res.data.phone, }) } catch (error) { console.error('Load user info error:', error) Toast.error('加载用户信息失败') } } const loadMcpCredentials = async () => { try { const res = await getMcpCredentials() setMcpCredentials(res.data) } catch (error) { console.error('Load MCP credentials error:', error) Toast.error('加载失败', '获取 MCP 凭证失败') } } // 更新资料 const handleUpdateProfile = async (values) => { setLoading(true) try { const res = await updateProfile({ nickname: values.nickname, email: values.email, phone: values.phone, }) setUserInfo(res.data) setUser(res.data) // 更新全局用户信息 Toast.success('更新成功', '个人资料已更新') } catch (error) { console.error('Update profile error:', error) Toast.error(error.response?.data?.detail || '更新失败') } finally { setLoading(false) } } // 修改密码 const handleChangePassword = async (values) => { setLoading(true) try { await changePassword({ old_password: values.old_password, new_password: values.new_password, }) Toast.success('修改成功', '密码已修改,请重新登录') passwordForm.resetFields() // 2秒后跳转到登录页 setTimeout(() => { window.location.href = '/login' }, 2000) } catch (error) { console.error('Change password error:', error) Toast.error(error.response?.data?.detail || '修改密码失败') } finally { setLoading(false) } } // 上传头像前的验证 const beforeAvatarUpload = (file) => { const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' if (!isJpgOrPng) { Toast.error('格式错误', '仅支持 JPG、PNG 格式的图片') return false } const isLt1M = file.size / 1024 / 1024 < 1 if (!isLt1M) { Toast.error('文件过大', '图片大小不能超过 1MB') return false } return true } // 处理头像上传 const handleAvatarUpload = async (info) => { const { file } = info setLoading(true) try { const res = await uploadAvatar(file) setUserInfo(res.data) setUser(res.data) // 更新全局用户信息 Toast.success('上传成功', '头像已更新') } catch (error) { console.error('Upload avatar error:', error) Toast.error('上传失败', error.response?.data?.detail || '头像上传失败') } finally { setLoading(false) } } // 获取头像URL const getAvatarUrl = () => { if (!userInfo?.avatar) return null // avatar 字段存储的是相对路径,如:2/avatar/xxx.jpg // 需要转换为 API 端点: /api/v1/auth/avatar/{user_id}/{filename} const parts = userInfo.avatar.split('/') if (parts.length >= 3) { const userId = parts[0] const filename = parts[2] return `/api/v1/auth/avatar/${userId}/${filename}` } return null } const handleCopy = async (value, label) => { if (!value) return try { await navigator.clipboard.writeText(value) Toast.success('复制成功', `${label} 已复制到剪贴板`) } catch (error) { console.error('Copy credential error:', error) Toast.error('复制失败', `无法复制 ${label}`) } } const handleRotateSecret = () => { Modal.confirm({ title: '重新生成 MCP Secret', content: '旧 Secret 会立即失效,依赖该凭证的远程客户端需要同步更新。确认继续?', okText: '重新生成', cancelText: '取消', okButtonProps: { danger: true }, onOk: async () => { setLoading(true) try { const res = await rotateMcpSecret() setMcpCredentials(res.data) Toast.success('已生成', '新的 MCP Secret 已生效') } catch (error) { console.error('Rotate MCP secret error:', error) Toast.error('生成失败', error.response?.data?.detail || '重新生成 MCP Secret 失败') } finally { setLoading(false) } }, }) } const tabItems = [ { key: 'profile', label: ( 个人资料 ), children: (
} src={getAvatarUrl()} /> handleAvatarUpload({ file })} >

支持 JPG、PNG 格式,文件小于 1MB

), }, { key: 'password', label: ( 修改密码 ), children: (
({ validator(_, value) { if (!value || getFieldValue('new_password') === value) { return Promise.resolve() } return Promise.reject(new Error('两次输入的密码不一致')) }, }), ]} >

密码安全建议:

  • 密码长度至少6个字符
  • 包含字母、数字和特殊字符的组合更安全
  • 不要使用过于简单的密码,如"123456"
  • 定期更换密码,提高账户安全性
), }, { key: 'mcp', label: ( MCP 接入 ), children: (

MCP 访问凭证

用于远程 MCP Client 通过 `X-Bot-Id` 与 `X-Bot-Secret` 接入你的账号。

{mcpCredentials?.bot_id || '-'}
{mcpCredentials?.bot_secret || '-'} 变更后旧 Secret 立即失效

客户端请求头

{`X-Bot-Id: ${mcpCredentials?.bot_id || 'your_bot_id'}\nX-Bot-Secret: ${mcpCredentials?.bot_secret || 'your_bot_secret'}`}
), }, ] return (

个人中心

) } export default ProfilePage