|
|
|
@ -37,27 +37,39 @@
|
|
|
|
|
<!-- 表单 -->
|
|
|
|
|
<form @submit.prevent="handleSubmit" class="form">
|
|
|
|
|
<!-- 昵称 -->
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
<label class="label">昵称</label>
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
<label class="label">新昵称</label>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
v-model.trim="formData.nickname"
|
|
|
|
|
v-model.trim="formData.username "
|
|
|
|
|
class="input"
|
|
|
|
|
:class="{ 'input-error': errors.nickname }"
|
|
|
|
|
:class="{ 'input-error': errors.username }"
|
|
|
|
|
>
|
|
|
|
|
<p v-if="errors.nickname" class="error-tip">{{ errors.nickname }}</p>
|
|
|
|
|
<p v-if="errors.username " class="error-tip">{{ errors.username }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 个人简介 -->
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
<label class="label">个人简介</label>
|
|
|
|
|
<textarea
|
|
|
|
|
v-model.trim="formData.bio"
|
|
|
|
|
v-model.trim="formData.moto"
|
|
|
|
|
rows="4"
|
|
|
|
|
class="textarea"
|
|
|
|
|
:class="{ 'input-error': errors.bio }"
|
|
|
|
|
:class="{ 'input-error': errors.moto }"
|
|
|
|
|
></textarea>
|
|
|
|
|
<p v-if="errors.bio" class="error-tip">{{ errors.bio }}</p>
|
|
|
|
|
<p v-if="errors.moto" class="error-tip">{{ errors.moto }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 密码 -->
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
<label class="label">新密码</label>
|
|
|
|
|
<input
|
|
|
|
|
type="password"
|
|
|
|
|
v-model.trim="formData.password"
|
|
|
|
|
class="input"
|
|
|
|
|
:class="{ 'input-error': errors.password }"
|
|
|
|
|
>
|
|
|
|
|
<p v-if="errors.password" class="error-tip">{{ errors.password }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 提交按钮 -->
|
|
|
|
@ -66,54 +78,30 @@
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, onMounted } from 'vue';
|
|
|
|
|
import { useRouter } from 'vue-router';
|
|
|
|
|
import { useUserStore } from '@/stores/user.js'; // 引入Pinia用户状态
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref,computed } from 'vue';
|
|
|
|
|
import { useUserStore } from '@/stores/user'; // 引入 Pinia 用户状态管理
|
|
|
|
|
import request from '@/utils/request'; // 引入封装的请求模块
|
|
|
|
|
import { ElMessage } from 'element-plus'; // 使用Element Plus提示组件
|
|
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
const userStore = useUserStore();
|
|
|
|
|
const userInfo = computed(() => userStore.userInfo);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 从状态管理中获取用户ID和登录状态
|
|
|
|
|
const userId = ref(userStore.userInfo.userid);
|
|
|
|
|
const isLoggedIn = userStore.isLoggedIn;
|
|
|
|
|
|
|
|
|
|
// 初始化表单数据
|
|
|
|
|
const formData = ref({
|
|
|
|
|
nickname: '',
|
|
|
|
|
bio: ''
|
|
|
|
|
username: userInfo.value.username || '',
|
|
|
|
|
moto: userInfo.value.moto || '暂无个性签名',
|
|
|
|
|
password: ''
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 头像相关变量
|
|
|
|
|
const avatarFile = ref(null); // 选中的头像文件
|
|
|
|
|
const previewAvatar = ref(''); // 新头像预览 URL
|
|
|
|
|
const currentAvatar = ref(''); // 初始头像
|
|
|
|
|
const currentAvatar = ref(userInfo.value.avatar); // 初始头像
|
|
|
|
|
const errors = ref({}); // 错误提示
|
|
|
|
|
|
|
|
|
|
// 组件挂载时检查登录状态并获取用户信息
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
if (!isLoggedIn) {
|
|
|
|
|
ElMessage.error('请先登录');
|
|
|
|
|
router.push({ name: 'Login' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 使用封装的request模块请求用户信息
|
|
|
|
|
const res = await request.get(`/user/info/${userId.value}`);
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
formData.value.nickname = res.data.nickname;
|
|
|
|
|
formData.value.bio = res.data.bio;
|
|
|
|
|
currentAvatar.value = res.data.avatar;
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error(res.msg || '接口返回异常');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
ElMessage.error(`获取用户信息失败:${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 处理头像选择
|
|
|
|
|
const handleAvatarChange = (e) => {
|
|
|
|
@ -145,55 +133,76 @@ const validateForm = () => {
|
|
|
|
|
const newErrors = {};
|
|
|
|
|
|
|
|
|
|
// 昵称验证(接口要求:必填,≤20字符)
|
|
|
|
|
if (!formData.value.nickname) {
|
|
|
|
|
newErrors.nickname = '昵称不能为空';
|
|
|
|
|
} else if (formData.value.nickname.length > 20) {
|
|
|
|
|
newErrors.nickname = '昵称最多20字符';
|
|
|
|
|
if (!formData.value.username) {
|
|
|
|
|
newErrors.username = '昵称不能为空';
|
|
|
|
|
} else if (formData.value.username .length > 20) {
|
|
|
|
|
newErrors.username = '昵称最多20字符';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 简介验证(接口要求:≤100字符)
|
|
|
|
|
if (formData.value.bio.length > 100) {
|
|
|
|
|
newErrors.bio = '简介最多100字符';
|
|
|
|
|
if (formData.value.moto.length > 100) {
|
|
|
|
|
newErrors.moto = '简介最多100字符';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 密码验证
|
|
|
|
|
if (formData.value.password && formData.value.password.length < 6) {
|
|
|
|
|
newErrors.password = '密码长度不能少于6位';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
errors.value = newErrors;
|
|
|
|
|
return Object.keys(newErrors).length === 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 提交表单(调用接口文档中的 /user/info 接口)
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
// 提交表单
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
if (!validateForm()) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 构建接口要求的 FormData
|
|
|
|
|
const formDataToSubmit = new FormData();
|
|
|
|
|
// 添加头像(可选,有选择时才添加)
|
|
|
|
|
if (avatarFile.value) {
|
|
|
|
|
formDataToSubmit.append('avatar', avatarFile.value);
|
|
|
|
|
// 修改用户信息(接口要求 application/json)
|
|
|
|
|
const updateInfoData = {
|
|
|
|
|
username: formData.value.username,
|
|
|
|
|
moto: formData.value.moto
|
|
|
|
|
};
|
|
|
|
|
const updateInfoRes = await request.post('/user/info/update', updateInfoData);
|
|
|
|
|
if (updateInfoRes.code !== 200) {
|
|
|
|
|
throw new Error(updateInfoRes.msg || '修改用户信息失败');
|
|
|
|
|
}
|
|
|
|
|
// 添加其他字段(接口要求的 UserUpdateDTO 字段)
|
|
|
|
|
formDataToSubmit.append('nickname', formData.value.nickname);
|
|
|
|
|
formDataToSubmit.append('bio', formData.value.bio);
|
|
|
|
|
|
|
|
|
|
// 调用接口文档中的 PUT /user/info 接口(使用封装的request)
|
|
|
|
|
const res = await request.put(`/user/info/${userId.value}`, formDataToSubmit, {
|
|
|
|
|
headers: { 'Content-Type': 'multipart/form-data' } // 必须设置
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 接口返回成功(根据文档:code=200 表示成功)
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
// 更新当前头像(从接口返回新的头像 URL)
|
|
|
|
|
if (avatarFile.value) {
|
|
|
|
|
currentAvatar.value = res.data.avatar; // 假设接口返回新头像 URL
|
|
|
|
|
userInfo.value.username = updateInfoData.username;
|
|
|
|
|
userInfo.value.moto = updateInfoData.moto;
|
|
|
|
|
|
|
|
|
|
// 修改密码(使用 params 传递查询参数更安全)
|
|
|
|
|
if (formData.value.password) {
|
|
|
|
|
const updatePasswordRes = await request.post('/user/info/password', null, {
|
|
|
|
|
params: { password: formData.value.password } // 请求库自动转义特殊字符
|
|
|
|
|
});
|
|
|
|
|
if (updatePasswordRes.code !== 200) {
|
|
|
|
|
throw new Error(updatePasswordRes.msg || '修改密码失败');
|
|
|
|
|
}
|
|
|
|
|
ElMessage.success('修改成功!');
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error(res.msg || '接口返回异常');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 上传头像(匹配接口要求的 application/json)
|
|
|
|
|
if (avatarFile.value) {
|
|
|
|
|
// 注意:接口要求 JSON 格式,file 字段是 binary 类型的 string(可能需要读取文件内容为 Base64 或直接传 File 对象)
|
|
|
|
|
// 假设 avatarFile.value 是 File 对象,需根据接口实际要求调整(以下为示例)
|
|
|
|
|
const formDataForAvatar = {
|
|
|
|
|
file: avatarFile.value // 需确认是否需要转换为 Base64 或其他格式
|
|
|
|
|
};
|
|
|
|
|
const uploadAvatarRes = await request.post('/user/info/avatar', formDataForAvatar);
|
|
|
|
|
if (uploadAvatarRes.code !== 200) {
|
|
|
|
|
throw new Error(uploadAvatarRes.msg || '上传头像失败');
|
|
|
|
|
}
|
|
|
|
|
currentAvatar.value = uploadAvatarRes.data.data;
|
|
|
|
|
userInfo.value.avatar = currentAvatar.value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ElMessage.success('修改成功!');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
ElMessage.error(`修改失败:${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|