feat: ✨ 个人中心Alpha开发阶段
This commit is contained in:
@@ -162,6 +162,20 @@ class UserAPI {
|
|||||||
data: data,
|
data: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送手机/邮箱验证码
|
||||||
|
*
|
||||||
|
* @param contact 联系方式 手机号/邮箱
|
||||||
|
* @param contactType 联系方式类型 MOBILE:手机;EMAIL:邮箱
|
||||||
|
*/
|
||||||
|
static sendVerificationCode(contact: string, contactType: string) {
|
||||||
|
return request({
|
||||||
|
url: `${USER_BASE_URL}/send-verification-code`,
|
||||||
|
method: "get",
|
||||||
|
params: { contact: contact, contactType: contactType },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserAPI;
|
export default UserAPI;
|
||||||
@@ -317,6 +331,8 @@ export interface PasswordChangeForm {
|
|||||||
oldPassword?: string;
|
oldPassword?: string;
|
||||||
/** 新密码 */
|
/** 新密码 */
|
||||||
newPassword?: string;
|
newPassword?: string;
|
||||||
|
/** 确认新密码 */
|
||||||
|
confirmPassword?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 手机绑定表单 */
|
/** 手机绑定表单 */
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<el-tabs tab-position="left">
|
<el-tabs tab-position="left">
|
||||||
<!-- 基本设置 Tab Pane -->
|
<!-- 基本设置 Tab Pane -->
|
||||||
<el-tab-pane label="基本设置">
|
<el-tab-pane label="账号信息">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<el-card class="flex-1">
|
<el-card>
|
||||||
<div class="">
|
|
||||||
<!-- 头像和昵称部分 -->
|
<!-- 头像和昵称部分 -->
|
||||||
<div class="relative w-100px h-100px flex-center">
|
<div class="relative w-100px h-100px flex-center">
|
||||||
<el-avatar :src="userProfile.avatar" :size="100" />
|
<el-avatar :src="userProfile.avatar" :size="100" />
|
||||||
@@ -26,11 +25,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
{{ userProfile.nickname }}
|
{{ userProfile.nickname }}
|
||||||
<el-icon class="align-middle cursor-pointer">
|
<el-icon
|
||||||
|
class="align-middle cursor-pointer"
|
||||||
|
@click="handleOpenDialog(DialogType.ACCOUNT)"
|
||||||
|
>
|
||||||
<Edit />
|
<Edit />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<!-- 用户信息描述 -->
|
<!-- 用户信息描述 -->
|
||||||
<el-descriptions :column="1" class="mt-10">
|
<el-descriptions :column="1" class="mt-10">
|
||||||
<!-- 用户名 -->
|
<!-- 用户名 -->
|
||||||
@@ -177,12 +178,29 @@
|
|||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
<!-- 弹窗 -->
|
<!-- 弹窗 -->
|
||||||
<el-dialog :title="dialog.title" v-model="dialog.visible" :width="400">
|
<el-dialog :title="dialog.title" v-model="dialog.visible" :width="500">
|
||||||
|
<!-- 账号资料 -->
|
||||||
|
<el-form
|
||||||
|
v-if="dialog.type === DialogType.ACCOUNT"
|
||||||
|
:model="userProfileForm"
|
||||||
|
ref="userProfileFormRef"
|
||||||
|
:label-width="100"
|
||||||
|
>
|
||||||
|
<el-form-item label="昵称">
|
||||||
|
<el-input v-model="userProfileForm.nickname" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="性别">
|
||||||
|
<dictionary v-model="userProfileForm.gender" code="gender" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 修改密码 -->
|
||||||
<el-form
|
<el-form
|
||||||
v-if="dialog.type === DialogType.PASSWORD"
|
v-if="dialog.type === DialogType.PASSWORD"
|
||||||
:model="passwordChangeForm"
|
:model="passwordChangeForm"
|
||||||
:rules="passwordChangeRules"
|
:rules="passwordChangeRules"
|
||||||
ref="passwordChangeFormRef"
|
ref="passwordChangeFormRef"
|
||||||
|
:label-width="100"
|
||||||
>
|
>
|
||||||
<el-form-item label="原密码" prop="oldPassword">
|
<el-form-item label="原密码" prop="oldPassword">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -199,22 +217,31 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="确认密码" prop="confirmPassword">
|
<el-form-item label="确认密码" prop="confirmPassword">
|
||||||
<el-input type="password" v-model="confirmPassword" show-password />
|
<el-input
|
||||||
|
type="password"
|
||||||
|
v-model="passwordChangeForm.confirmPassword"
|
||||||
|
show-password
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
<!-- 绑定手机 -->
|
||||||
<el-form
|
<el-form
|
||||||
v-else-if="dialog.type === DialogType.MOBILE"
|
v-else-if="dialog.type === DialogType.MOBILE"
|
||||||
:model="mobileBindingForm"
|
:model="mobileBindingForm"
|
||||||
:rules="mobileBindingRules"
|
:rules="mobileBindingRules"
|
||||||
ref="mobileBindingFormRef"
|
ref="mobileBindingFormRef"
|
||||||
|
:label-width="100"
|
||||||
>
|
>
|
||||||
<el-form-item label="手机号码" prop="mobile">
|
<el-form-item label="手机号码" prop="mobile">
|
||||||
<el-input v-model="mobileBindingForm.mobile" />
|
<el-input v-model="mobileBindingForm.mobile" style="width: 250px" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item label="验证码" prop="code">
|
||||||
|
<el-input v-model="mobileBindingForm.code" style="width: 250px">
|
||||||
|
<template #append>
|
||||||
<el-button
|
<el-button
|
||||||
|
class="ml-5"
|
||||||
:disabled="mobileCountdown > 0"
|
:disabled="mobileCountdown > 0"
|
||||||
@click="handleSendMobileCode"
|
@click="handleSendVerificationCode('MOBILE')"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
mobileCountdown > 0
|
mobileCountdown > 0
|
||||||
@@ -222,32 +249,38 @@
|
|||||||
: "发送验证码"
|
: "发送验证码"
|
||||||
}}
|
}}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</template>
|
||||||
<el-form-item label="验证码" prop="code">
|
</el-input>
|
||||||
<el-input v-model="mobileBindingForm.code" />
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 绑定邮箱 -->
|
||||||
<el-form
|
<el-form
|
||||||
v-else-if="dialog.type === DialogType.EMAIL"
|
v-else-if="dialog.type === DialogType.EMAIL"
|
||||||
:model="emailBindingForm"
|
:model="emailBindingForm"
|
||||||
:rules="emailBindingRules"
|
:rules="emailBindingRules"
|
||||||
ref="emailBindingFormRef"
|
ref="emailBindingFormRef"
|
||||||
|
:label-width="100"
|
||||||
>
|
>
|
||||||
<el-form-item label="邮箱" prop="email">
|
<el-form-item label="邮箱" prop="email">
|
||||||
<el-input v-model="emailBindingForm.email" />
|
<el-input v-model="emailBindingForm.email" style="width: 250px" />
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button
|
|
||||||
:disabled="emailCountdown > 0"
|
|
||||||
@click="handleSendEmailCode"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
emailCountdown > 0 ? `${emailCountdown}s后重新发送` : "发送验证码"
|
|
||||||
}}
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="验证码" prop="code">
|
<el-form-item label="验证码" prop="code">
|
||||||
<el-input v-model="emailBindingForm.code" />
|
<el-input v-model="emailBindingForm.code" style="width: 250px">
|
||||||
|
<template #append>
|
||||||
|
<el-button
|
||||||
|
class="ml-5"
|
||||||
|
:disabled="emailCountdown > 0"
|
||||||
|
@click="handleSendVerificationCode('EMAIL')"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
emailCountdown > 0
|
||||||
|
? `${emailCountdown}s后重新发送`
|
||||||
|
: "发送验证码"
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -263,18 +296,23 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import UserAPI, {
|
import UserAPI, {
|
||||||
UserProfileVO,
|
UserProfileVO,
|
||||||
UserProfileForm,
|
|
||||||
PasswordChangeForm,
|
PasswordChangeForm,
|
||||||
MobileBindingForm,
|
MobileBindingForm,
|
||||||
EmailBindingForm,
|
EmailBindingForm,
|
||||||
|
UserProfileForm,
|
||||||
} from "@/api/user";
|
} from "@/api/user";
|
||||||
|
|
||||||
|
import FileAPI from "@/api/file";
|
||||||
|
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { Camera } from "@element-plus/icons-vue";
|
import { Camera } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const userProfile = ref<UserProfileVO>({});
|
const userProfile = ref<UserProfileVO>({});
|
||||||
|
|
||||||
enum DialogType {
|
enum DialogType {
|
||||||
|
ACCOUNT = "account",
|
||||||
PASSWORD = "password",
|
PASSWORD = "password",
|
||||||
MOBILE = "mobile",
|
MOBILE = "mobile",
|
||||||
EMAIL = "email",
|
EMAIL = "email",
|
||||||
@@ -283,11 +321,10 @@ enum DialogType {
|
|||||||
const dialog = reactive({
|
const dialog = reactive({
|
||||||
visible: false,
|
visible: false,
|
||||||
title: "",
|
title: "",
|
||||||
type: "" as DialogType, // 修改密码、绑定手机、绑定邮箱
|
type: "" as DialogType, // 修改账号资料,修改密码、绑定手机、绑定邮箱
|
||||||
});
|
});
|
||||||
|
|
||||||
const confirmPassword = ref("");
|
const userProfileForm = reactive<UserProfileForm>({});
|
||||||
|
|
||||||
const passwordChangeForm = reactive<PasswordChangeForm>({});
|
const passwordChangeForm = reactive<PasswordChangeForm>({});
|
||||||
const mobileBindingForm = reactive<MobileBindingForm>({});
|
const mobileBindingForm = reactive<MobileBindingForm>({});
|
||||||
const emailBindingForm = reactive<EmailBindingForm>({});
|
const emailBindingForm = reactive<EmailBindingForm>({});
|
||||||
@@ -298,6 +335,7 @@ const mobileTimer = ref<NodeJS.Timeout | null>(null);
|
|||||||
const emailCountdown = ref(0);
|
const emailCountdown = ref(0);
|
||||||
const emailTimer = ref<NodeJS.Timeout | null>(null);
|
const emailTimer = ref<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
// 修改密码校验规则
|
||||||
const passwordChangeRules = {
|
const passwordChangeRules = {
|
||||||
oldPassword: [{ required: true, message: "请输入原密码", trigger: "blur" }],
|
oldPassword: [{ required: true, message: "请输入原密码", trigger: "blur" }],
|
||||||
newPassword: [{ required: true, message: "请输入新密码", trigger: "blur" }],
|
newPassword: [{ required: true, message: "请输入新密码", trigger: "blur" }],
|
||||||
@@ -306,26 +344,76 @@ const passwordChangeRules = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 手机号校验规则
|
||||||
const mobileBindingRules = {
|
const mobileBindingRules = {
|
||||||
mobile: [{ required: true, message: "请输入手机号", trigger: "blur" }],
|
mobile: [
|
||||||
mobileCode: [{ required: true, message: "请输入验证码", trigger: "blur" }],
|
{ required: true, message: "请输入手机号", trigger: "blur" },
|
||||||
|
{
|
||||||
|
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
|
||||||
|
message: "请输入正确的手机号码",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
code: [{ required: true, message: "请输入验证码", trigger: "blur" }],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 邮箱校验规则
|
||||||
const emailBindingRules = {
|
const emailBindingRules = {
|
||||||
email: [{ required: true, message: "请输入邮箱", trigger: "blur" }],
|
email: [
|
||||||
emailCode: [{ required: true, message: "请输入验证码", trigger: "blur" }],
|
{ required: true, message: "请输入邮箱", trigger: "blur" },
|
||||||
|
{
|
||||||
|
pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/,
|
||||||
|
message: "请输入正确的邮箱地址",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
code: [{ required: true, message: "请输入验证码", trigger: "blur" }],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开弹窗
|
||||||
|
* @param type 弹窗类型 ACCOUNT: 账号资料 PASSWORD: 修改密码 MOBILE: 绑定手机 EMAIL: 绑定邮箱
|
||||||
|
*/
|
||||||
const handleOpenDialog = (type: DialogType) => {
|
const handleOpenDialog = (type: DialogType) => {
|
||||||
dialog.type = type;
|
dialog.type = type;
|
||||||
dialog.visible = true;
|
dialog.visible = true;
|
||||||
|
switch (type) {
|
||||||
|
case DialogType.ACCOUNT:
|
||||||
|
dialog.title = "账号资料";
|
||||||
|
// 初始化表单数据
|
||||||
|
userProfileForm.id = userProfile.value.id;
|
||||||
|
userProfileForm.nickname = userProfile.value.nickname;
|
||||||
|
userProfileForm.gender = userProfile.value.gender;
|
||||||
|
break;
|
||||||
|
case DialogType.PASSWORD:
|
||||||
|
dialog.title = "修改密码";
|
||||||
|
break;
|
||||||
|
case DialogType.MOBILE:
|
||||||
|
dialog.title = "绑定手机";
|
||||||
|
break;
|
||||||
|
case DialogType.EMAIL:
|
||||||
|
dialog.title = "绑定邮箱";
|
||||||
|
break;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSendMobileCode = async () => {
|
/**
|
||||||
if (!mobileBindingForm.value.mobile) {
|
* 发送验证码
|
||||||
|
*
|
||||||
|
* @param contactType 联系方式类型 MOBILE: 手机号码 EMAIL: 邮箱
|
||||||
|
*/
|
||||||
|
const handleSendVerificationCode = async (contactType: string) => {
|
||||||
|
if (contactType === "MOBILE") {
|
||||||
|
if (!mobileBindingForm.mobile) {
|
||||||
|
ElMessage.error("请输入手机号");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 验证手机号格式
|
||||||
|
const reg = /^1[3-9]\d{9}$/;
|
||||||
|
if (!reg.test(mobileBindingForm.mobile)) {
|
||||||
|
ElMessage.error("手机号格式不正确");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// await UserAPI.sendMobileCode(mobileBindingForm.value.mobile);
|
|
||||||
|
|
||||||
mobileCountdown.value = 60;
|
mobileCountdown.value = 60;
|
||||||
mobileTimer.value = setInterval(() => {
|
mobileTimer.value = setInterval(() => {
|
||||||
@@ -335,13 +423,17 @@ const handleSendMobileCode = async () => {
|
|||||||
clearInterval(mobileTimer.value!);
|
clearInterval(mobileTimer.value!);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
} else if (contactType === "EMAIL") {
|
||||||
|
|
||||||
const handleSendEmailCode = async () => {
|
|
||||||
if (!emailBindingForm.email) {
|
if (!emailBindingForm.email) {
|
||||||
|
ElMessage.error("请输入邮箱");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 验证邮箱格式
|
||||||
|
const reg = /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/;
|
||||||
|
if (!reg.test(emailBindingForm.email)) {
|
||||||
|
ElMessage.error("邮箱格式不正确");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// await UserAPI.sendEmailCode(emailBindingForm.value.email);
|
|
||||||
|
|
||||||
emailCountdown.value = 60;
|
emailCountdown.value = 60;
|
||||||
emailTimer.value = setInterval(() => {
|
emailTimer.value = setInterval(() => {
|
||||||
@@ -351,21 +443,33 @@ const handleSendEmailCode = async () => {
|
|||||||
clearInterval(emailTimer.value!);
|
clearInterval(emailTimer.value!);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交表单
|
||||||
|
*/
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (dialog.type === DialogType.PASSWORD) {
|
if (dialog.type === DialogType.ACCOUNT) {
|
||||||
if (passwordChangeForm.newPassword !== confirmPassword.value) {
|
UserAPI.updateProfile(userProfileForm.id, userProfileForm).then(() => {
|
||||||
|
ElMessage.success("账号资料修改成功");
|
||||||
|
dialog.visible = false;
|
||||||
|
loadUserProfile();
|
||||||
|
});
|
||||||
|
} else if (dialog.type === DialogType.PASSWORD) {
|
||||||
|
if (passwordChangeForm.newPassword !== passwordChangeForm.confirmPassword) {
|
||||||
ElMessage.error("两次输入的密码不一致");
|
ElMessage.error("两次输入的密码不一致");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await UserAPI.changePassword(passwordChangeForm);
|
UserAPI.changePassword(passwordChangeForm).then(() => {
|
||||||
|
ElMessage.success("密码修改成功");
|
||||||
|
dialog.visible = false;
|
||||||
|
});
|
||||||
} else if (dialog.type === "mobile") {
|
} else if (dialog.type === "mobile") {
|
||||||
//await UserAPI.bindMobile(mobileBindingForm.value);
|
//await UserAPI.bindMobile(mobileBindingForm.value);
|
||||||
} else if (dialog.type === "email") {
|
} else if (dialog.type === "email") {
|
||||||
//await UserAPI.bindEmail(emailBindingForm.value);
|
//await UserAPI.bindEmail(emailBindingForm.value);
|
||||||
}
|
}
|
||||||
dialog.visible = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileInput = ref<HTMLInputElement | null>(null);
|
const fileInput = ref<HTMLInputElement | null>(null);
|
||||||
@@ -374,13 +478,29 @@ const triggerFileUpload = () => {
|
|||||||
fileInput.value?.click();
|
fileInput.value?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFileChange = (event: Event) => {
|
const handleFileChange = async (event: Event) => {
|
||||||
const target = event.target as HTMLInputElement;
|
const target = event.target as HTMLInputElement;
|
||||||
const file = target.files ? target.files[0] : null;
|
const file = target.files ? target.files[0] : null;
|
||||||
if (file) {
|
if (file) {
|
||||||
// Handle the file upload here
|
// 调用文件上传API
|
||||||
console.log("Selected file:", file);
|
try {
|
||||||
|
const data = await FileAPI.upload(file);
|
||||||
|
// 更新用户头像
|
||||||
|
userProfile.value.avatar = data.url;
|
||||||
|
// 更新用户信息
|
||||||
|
await UserAPI.updateProfile(userProfile.value.id, {
|
||||||
|
avatar: data.url,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("头像上传失败");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 加载用户信息 */
|
||||||
|
const loadUserProfile = async () => {
|
||||||
|
const data = await UserAPI.getProfile(userStore.user.userId);
|
||||||
|
userProfile.value = data;
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -390,9 +510,7 @@ onMounted(async () => {
|
|||||||
if (emailTimer.value) {
|
if (emailTimer.value) {
|
||||||
clearInterval(emailTimer.value);
|
clearInterval(emailTimer.value);
|
||||||
}
|
}
|
||||||
|
await loadUserProfile();
|
||||||
const data = await UserAPI.getProfile(userStore.user.userId);
|
|
||||||
userProfile.value = data;
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user