| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- <template>
- <div class="profile-content">
- <div class="page-header">
- <h2>个人资料</h2>
- <p>管理您的个人信息和账户设置</p>
- </div>
- <div class="profile-container">
- <el-row :gutter="24">
- <!-- 左侧:基本信息 -->
- <el-col :span="16">
- <el-card class="profile-card">
- <template #header>
- <span>基本信息</span>
- </template>
-
- <el-form
- ref="profileFormRef"
- :model="profileForm"
- :rules="profileRules"
- label-width="100px"
- @submit.prevent="updateProfile"
- >
- <el-form-item label="用户名" prop="username">
- <el-input
- v-model="profileForm.username"
- placeholder="请输入用户名"
- :disabled="true"
- />
- <div class="form-tip">用户名不可修改</div>
- </el-form-item>
- <el-form-item label="邮箱" prop="email">
- <el-input
- v-model="profileForm.email"
- placeholder="请输入邮箱"
- type="email"
- />
- </el-form-item>
- <el-form-item label="手机号" prop="phone">
- <el-input
- v-model="profileForm.phone"
- placeholder="请输入手机号"
- />
- </el-form-item>
- <el-form-item label="真实姓名" prop="real_name">
- <el-input
- v-model="profileForm.real_name"
- placeholder="请输入真实姓名"
- />
- </el-form-item>
- <el-form-item label="公司" prop="company">
- <el-input
- v-model="profileForm.company"
- placeholder="请输入公司名称"
- />
- </el-form-item>
- <el-form-item label="部门" prop="department">
- <el-input
- v-model="profileForm.department"
- placeholder="请输入部门"
- />
- </el-form-item>
- <el-form-item label="职位" prop="position">
- <el-input
- v-model="profileForm.position"
- placeholder="请输入职位"
- />
- </el-form-item>
- <el-form-item>
- <el-button
- type="primary"
- :loading="loading"
- @click="updateProfile"
- >
- 保存修改
- </el-button>
- <el-button @click="resetForm">
- 重置
- </el-button>
- </el-form-item>
- </el-form>
- </el-card>
- </el-col>
- <!-- 右侧:头像和安全设置 -->
- <el-col :span="8">
- <!-- 头像设置 -->
- <el-card class="avatar-card">
- <template #header>
- <span>头像设置</span>
- </template>
-
- <div class="avatar-section">
- <el-avatar
- :src="profileForm.avatar_url"
- :size="120"
- class="user-avatar"
- >
- {{ profileForm.username?.charAt(0).toUpperCase() }}
- </el-avatar>
-
- <el-upload
- class="avatar-uploader"
- action="/api/v1/upload/avatar"
- :headers="uploadHeaders"
- :show-file-list="false"
- :on-success="handleAvatarSuccess"
- :before-upload="beforeAvatarUpload"
- >
- <el-button type="primary" size="small">
- <el-icon><Upload /></el-icon>
- 更换头像
- </el-button>
- </el-upload>
- </div>
- </el-card>
- <!-- 账户信息 -->
- <el-card class="account-info">
- <template #header>
- <span>账户信息</span>
- </template>
-
- <div class="info-item">
- <span class="label">用户ID:</span>
- <span class="value">{{ userStore.user?.id }}</span>
- </div>
-
- <div class="info-item">
- <span class="label">注册时间:</span>
- <span class="value">{{ formatDate(userStore.user?.created_at) }}</span>
- </div>
-
- <div class="info-item">
- <span class="label">最后登录:</span>
- <span class="value">{{ formatDate(userStore.user?.last_login_at) }}</span>
- </div>
-
- <div class="info-item">
- <span class="label">账户状态:</span>
- <el-tag :type="userStore.user?.is_active ? 'success' : 'danger'">
- {{ userStore.user?.is_active ? '正常' : '已禁用' }}
- </el-tag>
- </div>
- </el-card>
- <!-- 安全设置 -->
- <el-card class="security-card">
- <template #header>
- <span>安全设置</span>
- </template>
-
- <div class="security-actions">
- <el-button
- type="warning"
- size="small"
- @click="showChangePassword = true"
- >
- <el-icon><Lock /></el-icon>
- 修改密码
- </el-button>
-
- <el-button
- type="info"
- size="small"
- @click="$router.push('/user/tokens')"
- >
- <el-icon><Key /></el-icon>
- 令牌管理
- </el-button>
-
- <el-button
- type="success"
- size="small"
- @click="$router.push('/user/activity')"
- >
- <el-icon><Clock /></el-icon>
- 登录日志
- </el-button>
- </div>
- </el-card>
- </el-col>
- </el-row>
- </div>
- <!-- 修改密码对话框 -->
- <el-dialog
- v-model="showChangePassword"
- title="修改密码"
- width="400px"
- >
- <el-form
- ref="passwordFormRef"
- :model="passwordForm"
- :rules="passwordRules"
- label-width="100px"
- >
- <el-form-item label="当前密码" prop="old_password">
- <el-input
- v-model="passwordForm.old_password"
- type="password"
- placeholder="请输入当前密码"
- show-password
- />
- </el-form-item>
- <el-form-item label="新密码" prop="new_password">
- <el-input
- v-model="passwordForm.new_password"
- type="password"
- placeholder="请输入新密码"
- show-password
- />
- </el-form-item>
- <el-form-item label="确认密码" prop="confirm_password">
- <el-input
- v-model="passwordForm.confirm_password"
- type="password"
- placeholder="请再次输入新密码"
- show-password
- />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="showChangePassword = false">取消</el-button>
- <el-button
- type="primary"
- :loading="passwordLoading"
- @click="changePassword"
- >
- 确定
- </el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, reactive, onMounted } from 'vue'
- import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
- import { useAuthStore } from '@/stores/auth'
- import { getToken } from '@/utils/auth'
- const userStore = useAuthStore()
- const loading = ref(false)
- const passwordLoading = ref(false)
- const showChangePassword = ref(false)
- const profileFormRef = ref<FormInstance>()
- const passwordFormRef = ref<FormInstance>()
- // 个人资料表单
- const profileForm = reactive({
- username: '',
- email: '',
- phone: '',
- real_name: '',
- company: '',
- department: '',
- position: '',
- avatar_url: ''
- })
- // 密码修改表单
- const passwordForm = reactive({
- old_password: '',
- new_password: '',
- confirm_password: ''
- })
- // 表单验证规则
- const profileRules: FormRules = {
- email: [
- { required: true, message: '请输入邮箱', trigger: 'blur' },
- { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
- ],
- phone: [
- { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
- ]
- }
- const passwordRules: FormRules = {
- old_password: [
- { required: true, message: '请输入当前密码', trigger: 'blur' }
- ],
- new_password: [
- { required: true, message: '请输入新密码', trigger: 'blur' },
- { min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
- ],
- confirm_password: [
- { required: true, message: '请确认新密码', trigger: 'blur' },
- {
- validator: (rule, value, callback) => {
- if (value !== passwordForm.new_password) {
- callback(new Error('两次输入的密码不一致'))
- } else {
- callback()
- }
- },
- trigger: 'blur'
- }
- ]
- }
- // 上传头像的请求头
- const uploadHeaders = {
- Authorization: `Bearer ${getToken()}`
- }
- // 初始化表单数据
- const initForm = () => {
- if (userStore.user) {
- Object.assign(profileForm, {
- username: userStore.user.username,
- email: userStore.user.email,
- phone: userStore.user.phone || '',
- real_name: userStore.user.real_name || '',
- company: userStore.user.company || '',
- department: userStore.user.department || '',
- position: userStore.user.position || '',
- avatar_url: userStore.user.avatar_url || ''
- })
- }
- }
- // 更新个人资料
- const updateProfile = async () => {
- if (!profileFormRef.value) return
-
- try {
- await profileFormRef.value.validate()
- loading.value = true
-
- // TODO: 调用API更新用户信息
- await new Promise(resolve => setTimeout(resolve, 1000)) // 模拟API调用
-
- ElMessage.success('个人资料更新成功')
-
- // 更新store中的用户信息
- await userStore.fetchUserInfo()
-
- } catch (error) {
- console.error('更新个人资料失败:', error)
- ElMessage.error('更新失败,请重试')
- } finally {
- loading.value = false
- }
- }
- // 重置表单
- const resetForm = () => {
- initForm()
- ElMessage.info('表单已重置')
- }
- // 修改密码
- const changePassword = async () => {
- if (!passwordFormRef.value) return
-
- try {
- await passwordFormRef.value.validate()
- passwordLoading.value = true
-
- // TODO: 调用API修改密码
- await new Promise(resolve => setTimeout(resolve, 1000)) // 模拟API调用
-
- ElMessage.success('密码修改成功')
- showChangePassword.value = false
-
- // 重置密码表单
- Object.assign(passwordForm, {
- old_password: '',
- new_password: '',
- confirm_password: ''
- })
-
- } catch (error) {
- console.error('修改密码失败:', error)
- ElMessage.error('修改密码失败,请重试')
- } finally {
- passwordLoading.value = false
- }
- }
- // 头像上传成功回调
- const handleAvatarSuccess = (response: any) => {
- if (response.code === '000000' || response.code === 0) {
- profileForm.avatar_url = response.data.url
- ElMessage.success('头像上传成功')
- } else {
- ElMessage.error('头像上传失败')
- }
- }
- // 头像上传前验证
- const beforeAvatarUpload = (file: File) => {
- const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'
- const isLt2M = file.size / 1024 / 1024 < 2
- if (!isJPG) {
- ElMessage.error('头像只能是 JPG/PNG 格式!')
- return false
- }
- if (!isLt2M) {
- ElMessage.error('头像大小不能超过 2MB!')
- return false
- }
- return true
- }
- // 格式化日期
- const formatDate = (date: string | undefined) => {
- if (!date) return '-'
- return new Date(date).toLocaleString('zh-CN')
- }
- onMounted(() => {
- initForm()
- })
- </script>
- <style scoped>
- .profile-content {
- padding: 20px;
- }
- .page-header {
- margin-bottom: 24px;
- }
- .page-header h2 {
- margin: 0 0 8px 0;
- color: #333;
- font-size: 24px;
- font-weight: 600;
- }
- .page-header p {
- margin: 0;
- color: #666;
- font-size: 14px;
- }
- .profile-container {
- max-width: 1200px;
- margin: 0 auto;
- }
- .profile-card {
- margin-bottom: 20px;
- }
- .form-tip {
- font-size: 12px;
- color: #999;
- margin-top: 4px;
- }
- .avatar-card {
- margin-bottom: 20px;
- }
- .avatar-section {
- text-align: center;
- }
- .user-avatar {
- margin-bottom: 16px;
- }
- .avatar-uploader {
- display: block;
- }
- .account-info {
- margin-bottom: 20px;
- }
- .info-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 8px 0;
- border-bottom: 1px solid #f0f0f0;
- }
- .info-item:last-child {
- border-bottom: none;
- }
- .label {
- font-size: 14px;
- color: #666;
- }
- .value {
- font-size: 14px;
- color: #333;
- word-break: break-all;
- }
- .security-card {
- margin-bottom: 20px;
- }
- .security-actions {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .security-actions .el-button {
- justify-content: flex-start;
- }
- </style>
|