275 lines
6.8 KiB
Vue
275 lines
6.8 KiB
Vue
<template>
|
|
<el-dialog
|
|
v-model="visible"
|
|
title="个人信息"
|
|
width="520px"
|
|
class="custom-dialog"
|
|
:close-on-click-modal="false"
|
|
>
|
|
<div class="profile-container">
|
|
<!-- 上方头像区域 -->
|
|
<div class="profile-top">
|
|
<div class="avatar-wrapper">
|
|
<el-avatar :size="88" :src="avatarUrl" class="profile-avatar">
|
|
{{ !avatarUrl ? ((userStore.nickname || userStore.username)?.[0]?.toUpperCase() || '?') : '' }}
|
|
</el-avatar>
|
|
<div class="avatar-overlay" @click="triggerAvatarUpload">
|
|
<el-icon :size="20"><Camera /></el-icon>
|
|
<span>更换</span>
|
|
</div>
|
|
<input ref="avatarInputRef" type="file" accept="image/*" style="display:none" @change="handleAvatarChange" />
|
|
</div>
|
|
<div class="username-text">@{{ userStore.nickname || userStore.username }}</div>
|
|
</div>
|
|
|
|
<!-- 分割线 -->
|
|
<el-divider />
|
|
|
|
<!-- 下方表单区域 -->
|
|
<div class="profile-bottom">
|
|
<el-form label-position="right" label-width="70px" class="profile-form" :model="form" :rules="rules" ref="formRef">
|
|
<el-form-item label="账号">
|
|
<el-input :model-value="userStore.username" disabled :prefix-icon="User" />
|
|
</el-form-item>
|
|
<el-form-item label="昵称" prop="nickname">
|
|
<el-input v-model="form.nickname" placeholder="设置你的昵称" maxlength="20" show-word-limit :prefix-icon="UserFilled" />
|
|
</el-form-item>
|
|
<el-form-item label="电话" prop="phone">
|
|
<el-input v-model="form.phone" placeholder="请输入电话号码" maxlength="11" :prefix-icon="Phone" />
|
|
</el-form-item>
|
|
<el-form-item label="邮箱" prop="email">
|
|
<el-input v-model="form.email" placeholder="请输入邮箱地址" maxlength="50" :prefix-icon="Message" />
|
|
</el-form-item>
|
|
<el-form-item label="签名" prop="signature" class="signature-item">
|
|
<el-input
|
|
v-model="form.signature"
|
|
type="textarea"
|
|
:rows="3"
|
|
placeholder="写点什么介绍自己吧..."
|
|
maxlength="100"
|
|
show-word-limit
|
|
resize="none"
|
|
/>
|
|
</el-form-item>
|
|
</el-form>
|
|
</div>
|
|
</div>
|
|
|
|
<template #footer>
|
|
<div class="dialog-footer">
|
|
<el-button @click="visible = false">
|
|
<el-icon><Close /></el-icon>
|
|
<span style="margin-left: 4px">取消</span>
|
|
</el-button>
|
|
<el-button type="primary" @click="handleSave" :loading="saving">
|
|
<el-icon><Check /></el-icon>
|
|
<span style="margin-left: 4px">保存</span>
|
|
</el-button>
|
|
</div>
|
|
</template>
|
|
</el-dialog>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref, reactive, watch } from 'vue'
|
|
import { useUserStore } from '@/store/user'
|
|
import { ElMessage } from 'element-plus'
|
|
import { Camera, User, UserFilled, Close, Check, Phone, Message } from '@element-plus/icons-vue'
|
|
import request from '@/api/request'
|
|
|
|
const props = defineProps({ modelValue: Boolean })
|
|
const emit = defineEmits(['update:modelValue'])
|
|
|
|
const userStore = useUserStore()
|
|
const visible = computed({
|
|
get: () => props.modelValue,
|
|
set: (v) => emit('update:modelValue', v)
|
|
})
|
|
|
|
const avatarUrl = ref('')
|
|
const avatarInputRef = ref(null)
|
|
const formRef = ref(null)
|
|
const saving = ref(false)
|
|
|
|
const form = reactive({
|
|
nickname: '',
|
|
phone: '',
|
|
email: '',
|
|
signature: ''
|
|
})
|
|
|
|
const rules = {
|
|
nickname: [{ max: 20, message: '昵称最多20个字符', trigger: 'blur' }],
|
|
phone: [
|
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
|
],
|
|
email: [
|
|
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
|
|
],
|
|
signature: [{ max: 100, message: '签名最多100个字符', trigger: 'blur' }]
|
|
}
|
|
|
|
watch(visible, (v) => {
|
|
if (v) {
|
|
avatarUrl.value = userStore.avatar || ''
|
|
form.nickname = userStore.nickname || ''
|
|
form.phone = userStore.phone || ''
|
|
form.email = userStore.email || ''
|
|
form.signature = userStore.signature || ''
|
|
}
|
|
})
|
|
|
|
const triggerAvatarUpload = () => {
|
|
avatarInputRef.value?.click()
|
|
}
|
|
|
|
const handleAvatarChange = async (e) => {
|
|
const file = e.target.files?.[0]
|
|
if (!file) return
|
|
e.target.value = ''
|
|
|
|
if (file.size > 2 * 1024 * 1024) {
|
|
ElMessage.warning('图片大小不能超过2MB')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const formData = new FormData()
|
|
formData.append('avatar', file)
|
|
const data = await request.post('/users/avatar', formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' }
|
|
})
|
|
const newUrl = data?.url || ''
|
|
avatarUrl.value = newUrl
|
|
userStore.setUser({ avatar: newUrl })
|
|
ElMessage.success('头像更新成功')
|
|
} catch {
|
|
ElMessage.error('头像上传失败')
|
|
}
|
|
}
|
|
|
|
const handleSave = async () => {
|
|
try {
|
|
await formRef.value.validate()
|
|
} catch {
|
|
return
|
|
}
|
|
|
|
try {
|
|
await request.put('/users/profile', {
|
|
nickname: form.nickname,
|
|
phone: form.phone,
|
|
email: form.email,
|
|
signature: form.signature
|
|
})
|
|
userStore.setUser({
|
|
nickname: form.nickname,
|
|
phone: form.phone,
|
|
email: form.email,
|
|
signature: form.signature
|
|
})
|
|
ElMessage.success('保存成功')
|
|
visible.value = false
|
|
} catch {
|
|
ElMessage.error('保存失败')
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.profile-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.profile-top {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.avatar-wrapper {
|
|
position: relative;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.profile-avatar {
|
|
background: linear-gradient(135deg, #409eff, #66b1ff);
|
|
font-size: 32px;
|
|
font-weight: 600;
|
|
border: 3px solid #e4e7ed;
|
|
transition: border-color 0.3s;
|
|
}
|
|
|
|
.avatar-wrapper:hover .profile-avatar {
|
|
border-color: #409eff;
|
|
}
|
|
|
|
.avatar-overlay {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 28px;
|
|
background: rgba(0, 0, 0, 0.55);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 4px;
|
|
border-radius: 0 0 44px 44px;
|
|
color: #fff;
|
|
font-size: 12px;
|
|
opacity: 0;
|
|
transition: opacity 0.3s;
|
|
}
|
|
|
|
.avatar-wrapper:hover .avatar-overlay {
|
|
opacity: 1;
|
|
}
|
|
|
|
.username-text {
|
|
font-size: 14px;
|
|
color: #606266;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.profile-top :deep(.el-divider) {
|
|
margin: 4px 0 0 0;
|
|
}
|
|
|
|
.profile-bottom {
|
|
width: 100%;
|
|
}
|
|
|
|
.profile-form {
|
|
width: 100%;
|
|
}
|
|
|
|
.profile-form :deep(.el-form-item) {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.profile-form :deep(.el-form-item__label) {
|
|
font-weight: 500;
|
|
color: #606266;
|
|
padding-right: 12px;
|
|
}
|
|
|
|
.signature-item :deep(.el-form-item__content) {
|
|
display: block;
|
|
}
|
|
|
|
.profile-form :deep(.el-input.is-disabled .el-input__inner) {
|
|
background: #f5f7fa;
|
|
color: #909399;
|
|
}
|
|
|
|
.dialog-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 12px;
|
|
}
|
|
</style>
|