feat(用户资料): 完善用户资料页面功能
- 在用户类型定义中新增userPhone、unionId和mpOpenId字段 - 重构验证码输入组件,使用a-input-group替代a-input-search - 实现用户资料更新、密码修改和邮箱绑定功能 - 添加验证码发送倒计时功能 - 优化表单验证和错误处理 - 清理定时器防止内存泄漏
This commit is contained in:
3
src/store/types.d.ts
vendored
3
src/store/types.d.ts
vendored
@@ -7,6 +7,9 @@ export interface LoginUesr {
|
||||
userRole?: string;
|
||||
userProfile?: string;
|
||||
userEmail?: string;
|
||||
userPhone?: string;
|
||||
unionId?: string;
|
||||
mpOpenId?: string;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
[key: string]: any;
|
||||
|
||||
@@ -152,9 +152,9 @@
|
||||
<a-modal
|
||||
v-model:visible="showPasswordModal"
|
||||
title="修改密码"
|
||||
@ok="handlePasswordSubmit"
|
||||
@before-ok="handlePasswordSubmit"
|
||||
@cancel="handlePasswordReset"
|
||||
:confirm-loading="passwordLoading"
|
||||
:ok-loading="passwordLoading"
|
||||
>
|
||||
<a-form :model="passwordForm" layout="vertical">
|
||||
<a-form-item label="当前密码" field="oldPassword">
|
||||
@@ -185,9 +185,9 @@
|
||||
<a-modal
|
||||
v-model:visible="showPhoneModal"
|
||||
:title="loginUser.userPhone ? '更换手机号' : '绑定手机号'"
|
||||
@ok="handlePhoneSubmit"
|
||||
@before-ok="handlePhoneSubmit"
|
||||
@cancel="handlePhoneReset"
|
||||
:confirm-loading="phoneLoading"
|
||||
:ok-loading="phoneLoading"
|
||||
>
|
||||
<a-form :model="phoneForm" layout="vertical">
|
||||
<a-form-item label="手机号" field="phone">
|
||||
@@ -198,13 +198,21 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="验证码" field="code">
|
||||
<a-input-search
|
||||
v-model="phoneForm.code"
|
||||
placeholder="请输入验证码"
|
||||
button-text="发送验证码"
|
||||
@search="handleSendPhoneCode"
|
||||
:loading="codeSending"
|
||||
/>
|
||||
<a-input-group style="width: 100%">
|
||||
<a-input
|
||||
v-model="phoneForm.code"
|
||||
placeholder="请输入验证码"
|
||||
:max-length="6"
|
||||
/>
|
||||
<a-button
|
||||
type="outline"
|
||||
:loading="codeSending"
|
||||
:disabled="phoneCountdown > 0 || !phoneForm.phone"
|
||||
@click="handleSendPhoneCode"
|
||||
>
|
||||
{{ phoneCountdown > 0 ? `${phoneCountdown}秒后重试` : '发送验证码' }}
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
@@ -213,9 +221,9 @@
|
||||
<a-modal
|
||||
v-model:visible="showEmailModal"
|
||||
:title="loginUser.userEmail ? '更换邮箱' : '绑定邮箱'"
|
||||
@ok="handleEmailSubmit"
|
||||
@before-ok="handleEmailSubmit"
|
||||
@cancel="handleEmailReset"
|
||||
:confirm-loading="emailLoading"
|
||||
:ok-loading="emailLoading"
|
||||
>
|
||||
<a-form :model="emailForm" layout="vertical">
|
||||
<a-form-item label="邮箱地址" field="email">
|
||||
@@ -226,13 +234,21 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="验证码" field="code">
|
||||
<a-input-search
|
||||
v-model="emailForm.code"
|
||||
placeholder="请输入验证码"
|
||||
button-text="发送验证码"
|
||||
@search="handleSendEmailCode"
|
||||
:loading="codeSending"
|
||||
/>
|
||||
<a-input-group style="width: 100%">
|
||||
<a-input
|
||||
v-model="emailForm.code"
|
||||
placeholder="请输入验证码"
|
||||
:max-length="6"
|
||||
/>
|
||||
<a-button
|
||||
type="outline"
|
||||
:loading="codeSending"
|
||||
:disabled="emailCountdown > 0 || !emailForm.email"
|
||||
@click="handleSendEmailCode"
|
||||
>
|
||||
{{ emailCountdown > 0 ? `${emailCountdown}秒后重试` : '发送验证码' }}
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
@@ -240,10 +256,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch, reactive } from 'vue';
|
||||
import { ref, computed, onMounted, onUnmounted, watch, reactive } from 'vue';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { uploadFile, getFileById } from '@/api/file/file';
|
||||
import { updateUserAvatar } from '@/api/auth/user';
|
||||
import {
|
||||
updateUserAvatar,
|
||||
updateUserPassword,
|
||||
updateUserProfile,
|
||||
sendEmailVerifyCode,
|
||||
bindEmail,
|
||||
} from '@/api/auth/user';
|
||||
import { isApiSuccess } from '@/api/response';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import {
|
||||
@@ -265,6 +287,12 @@ const phoneLoading = ref(false);
|
||||
const emailLoading = ref(false);
|
||||
const codeSending = ref(false);
|
||||
|
||||
// 验证码倒计时
|
||||
const emailCountdown = ref(0);
|
||||
const phoneCountdown = ref(0);
|
||||
let emailTimer: number | null = null;
|
||||
let phoneTimer: number | null = null;
|
||||
|
||||
// 弹窗显示状态
|
||||
const showPasswordModal = ref(false);
|
||||
const showPhoneModal = ref(false);
|
||||
@@ -329,8 +357,8 @@ const maskPhone = (phone: string) => {
|
||||
const maskEmail = (email: string) => {
|
||||
if (!email) return '';
|
||||
const parts = email.split('@');
|
||||
if (parts.length !== 2) return email;
|
||||
const [name, domain] = parts;
|
||||
if (parts.length !== 2 || !parts[0] || !parts[1]) return email;
|
||||
const [name = '', domain = ''] = parts;
|
||||
if (name.length <= 2) return email;
|
||||
return `${name.slice(0, 2)}***@${domain}`;
|
||||
};
|
||||
@@ -418,9 +446,23 @@ const computeFileHash = (file: File): Promise<string> => {
|
||||
|
||||
// 提交资料表单
|
||||
const handleSubmit = async () => {
|
||||
// TODO: 等后端接口提供后对接
|
||||
Message.info('更新用户信息接口开发中...');
|
||||
console.log('提交数据:', formData);
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await updateUserProfile({
|
||||
userName: formData.userName,
|
||||
userProfile: formData.userProfile,
|
||||
});
|
||||
if (isApiSuccess(res)) {
|
||||
Message.success('资料更新成功');
|
||||
await userStore.getLoginUser();
|
||||
} else {
|
||||
Message.error(res.message || '更新失败');
|
||||
}
|
||||
} catch (err: any) {
|
||||
Message.error('更新出错: ' + (err.message || '未知错误'));
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 重置资料表单
|
||||
@@ -433,31 +475,49 @@ const handleReset = () => {
|
||||
const handlePasswordSubmit = async () => {
|
||||
if (!passwordForm.oldPassword) {
|
||||
Message.warning('请输入当前密码');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!passwordForm.newPassword) {
|
||||
Message.warning('请输入新密码');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (passwordForm.newPassword.length < 6 || passwordForm.newPassword.length > 20) {
|
||||
Message.warning('新密码长度应为6-20位');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
|
||||
Message.warning('两次输入的密码不一致');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (passwordForm.oldPassword === passwordForm.newPassword) {
|
||||
Message.warning('新密码不能与当前密码相同');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: 等后端接口提供后对接
|
||||
Message.info('修改密码接口开发中...');
|
||||
console.log('修改密码:', {
|
||||
oldPassword: passwordForm.oldPassword,
|
||||
newPassword: passwordForm.newPassword,
|
||||
});
|
||||
try {
|
||||
passwordLoading.value = true;
|
||||
const res = await updateUserPassword(
|
||||
passwordForm.oldPassword,
|
||||
passwordForm.newPassword
|
||||
);
|
||||
if (isApiSuccess(res)) {
|
||||
Message.success('密码修改成功,请重新登录');
|
||||
handlePasswordReset();
|
||||
// 修改密码后跳转到登录页
|
||||
setTimeout(() => {
|
||||
window.location.href = '/user/login';
|
||||
}, 1500);
|
||||
return true;
|
||||
} else {
|
||||
Message.error(res.message || '修改密码失败');
|
||||
return false;
|
||||
}
|
||||
} catch (err: any) {
|
||||
Message.error('修改密码出错: ' + (err.message || '未知错误'));
|
||||
return false;
|
||||
} finally {
|
||||
passwordLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 重置密码表单
|
||||
@@ -471,20 +531,21 @@ const handlePasswordReset = () => {
|
||||
const handlePhoneSubmit = async () => {
|
||||
if (!phoneForm.phone) {
|
||||
Message.warning('请输入手机号');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!/^1[3-9]\d{9}$/.test(phoneForm.phone)) {
|
||||
Message.warning('请输入正确的手机号');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!phoneForm.code) {
|
||||
Message.warning('请输入验证码');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: 等后端接口提供后对接
|
||||
Message.info('绑定手机号接口开发中...');
|
||||
console.log('绑定手机号:', phoneForm);
|
||||
return false;
|
||||
};
|
||||
|
||||
// 发送手机验证码
|
||||
@@ -500,32 +561,63 @@ const handleSendPhoneCode = async () => {
|
||||
|
||||
// TODO: 等后端接口提供后对接
|
||||
Message.info('发送验证码接口开发中...');
|
||||
// 模拟倒计时(实际对接后端后移到成功回调中)
|
||||
phoneCountdown.value = 60;
|
||||
if (phoneTimer) clearInterval(phoneTimer);
|
||||
phoneTimer = window.setInterval(() => {
|
||||
phoneCountdown.value--;
|
||||
if (phoneCountdown.value <= 0) {
|
||||
if (phoneTimer) clearInterval(phoneTimer);
|
||||
phoneTimer = null;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// 重置手机表单
|
||||
const handlePhoneReset = () => {
|
||||
phoneForm.phone = '';
|
||||
phoneForm.code = '';
|
||||
// 清理倒计时
|
||||
if (phoneTimer) {
|
||||
clearInterval(phoneTimer);
|
||||
phoneTimer = null;
|
||||
}
|
||||
phoneCountdown.value = 0;
|
||||
};
|
||||
|
||||
// 绑定邮箱
|
||||
const handleEmailSubmit = async () => {
|
||||
if (!emailForm.email) {
|
||||
Message.warning('请输入邮箱');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailForm.email)) {
|
||||
Message.warning('请输入正确的邮箱地址');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!emailForm.code) {
|
||||
Message.warning('请输入验证码');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: 等后端接口提供后对接
|
||||
Message.info('绑定邮箱接口开发中...');
|
||||
console.log('绑定邮箱:', emailForm);
|
||||
try {
|
||||
emailLoading.value = true;
|
||||
const res = await bindEmail(emailForm.email, emailForm.code);
|
||||
if (isApiSuccess(res)) {
|
||||
Message.success('邮箱绑定成功');
|
||||
handleEmailReset();
|
||||
await userStore.getLoginUser();
|
||||
return true;
|
||||
} else {
|
||||
Message.error(res.message || '绑定失败');
|
||||
return false;
|
||||
}
|
||||
} catch (err: any) {
|
||||
Message.error('绑定邮箱出错: ' + (err.message || '未知错误'));
|
||||
return false;
|
||||
} finally {
|
||||
emailLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 发送邮箱验证码
|
||||
@@ -539,14 +631,41 @@ const handleSendEmailCode = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: 等后端接口提供后对接
|
||||
Message.info('发送验证码接口开发中...');
|
||||
try {
|
||||
codeSending.value = true;
|
||||
const res = await sendEmailVerifyCode(emailForm.email);
|
||||
if (isApiSuccess(res)) {
|
||||
Message.success('验证码已发送,请查收邮箱');
|
||||
// 开始倒计时
|
||||
emailCountdown.value = 60;
|
||||
if (emailTimer) clearInterval(emailTimer);
|
||||
emailTimer = window.setInterval(() => {
|
||||
emailCountdown.value--;
|
||||
if (emailCountdown.value <= 0) {
|
||||
if (emailTimer) clearInterval(emailTimer);
|
||||
emailTimer = null;
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
Message.error(res.message || '发送失败');
|
||||
}
|
||||
} catch (err: any) {
|
||||
Message.error('发送验证码出错: ' + (err.message || '未知错误'));
|
||||
} finally {
|
||||
codeSending.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 重置邮箱表单
|
||||
const handleEmailReset = () => {
|
||||
emailForm.email = '';
|
||||
emailForm.code = '';
|
||||
// 清理倒计时
|
||||
if (emailTimer) {
|
||||
clearInterval(emailTimer);
|
||||
emailTimer = null;
|
||||
}
|
||||
emailCountdown.value = 0;
|
||||
};
|
||||
|
||||
// 绑定微信
|
||||
@@ -564,6 +683,18 @@ const handleUnbindWechat = () => {
|
||||
onMounted(() => {
|
||||
userStore.getLoginUser();
|
||||
});
|
||||
|
||||
// 清理定时器
|
||||
onUnmounted(() => {
|
||||
if (emailTimer) {
|
||||
clearInterval(emailTimer);
|
||||
emailTimer = null;
|
||||
}
|
||||
if (phoneTimer) {
|
||||
clearInterval(phoneTimer);
|
||||
phoneTimer = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -642,6 +773,18 @@ onMounted(() => {
|
||||
:deep(.arco-textarea) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
// 验证码输入组样式
|
||||
:deep(.arco-input-group) {
|
||||
.arco-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.arco-btn {
|
||||
flex-shrink: 0;
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.security-card {
|
||||
|
||||
Reference in New Issue
Block a user