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;
|
userRole?: string;
|
||||||
userProfile?: string;
|
userProfile?: string;
|
||||||
userEmail?: string;
|
userEmail?: string;
|
||||||
|
userPhone?: string;
|
||||||
|
unionId?: string;
|
||||||
|
mpOpenId?: string;
|
||||||
createTime?: string;
|
createTime?: string;
|
||||||
updateTime?: string;
|
updateTime?: string;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|||||||
@@ -152,9 +152,9 @@
|
|||||||
<a-modal
|
<a-modal
|
||||||
v-model:visible="showPasswordModal"
|
v-model:visible="showPasswordModal"
|
||||||
title="修改密码"
|
title="修改密码"
|
||||||
@ok="handlePasswordSubmit"
|
@before-ok="handlePasswordSubmit"
|
||||||
@cancel="handlePasswordReset"
|
@cancel="handlePasswordReset"
|
||||||
:confirm-loading="passwordLoading"
|
:ok-loading="passwordLoading"
|
||||||
>
|
>
|
||||||
<a-form :model="passwordForm" layout="vertical">
|
<a-form :model="passwordForm" layout="vertical">
|
||||||
<a-form-item label="当前密码" field="oldPassword">
|
<a-form-item label="当前密码" field="oldPassword">
|
||||||
@@ -185,9 +185,9 @@
|
|||||||
<a-modal
|
<a-modal
|
||||||
v-model:visible="showPhoneModal"
|
v-model:visible="showPhoneModal"
|
||||||
:title="loginUser.userPhone ? '更换手机号' : '绑定手机号'"
|
:title="loginUser.userPhone ? '更换手机号' : '绑定手机号'"
|
||||||
@ok="handlePhoneSubmit"
|
@before-ok="handlePhoneSubmit"
|
||||||
@cancel="handlePhoneReset"
|
@cancel="handlePhoneReset"
|
||||||
:confirm-loading="phoneLoading"
|
:ok-loading="phoneLoading"
|
||||||
>
|
>
|
||||||
<a-form :model="phoneForm" layout="vertical">
|
<a-form :model="phoneForm" layout="vertical">
|
||||||
<a-form-item label="手机号" field="phone">
|
<a-form-item label="手机号" field="phone">
|
||||||
@@ -198,13 +198,21 @@
|
|||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="验证码" field="code">
|
<a-form-item label="验证码" field="code">
|
||||||
<a-input-search
|
<a-input-group style="width: 100%">
|
||||||
v-model="phoneForm.code"
|
<a-input
|
||||||
placeholder="请输入验证码"
|
v-model="phoneForm.code"
|
||||||
button-text="发送验证码"
|
placeholder="请输入验证码"
|
||||||
@search="handleSendPhoneCode"
|
:max-length="6"
|
||||||
:loading="codeSending"
|
/>
|
||||||
/>
|
<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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -213,9 +221,9 @@
|
|||||||
<a-modal
|
<a-modal
|
||||||
v-model:visible="showEmailModal"
|
v-model:visible="showEmailModal"
|
||||||
:title="loginUser.userEmail ? '更换邮箱' : '绑定邮箱'"
|
:title="loginUser.userEmail ? '更换邮箱' : '绑定邮箱'"
|
||||||
@ok="handleEmailSubmit"
|
@before-ok="handleEmailSubmit"
|
||||||
@cancel="handleEmailReset"
|
@cancel="handleEmailReset"
|
||||||
:confirm-loading="emailLoading"
|
:ok-loading="emailLoading"
|
||||||
>
|
>
|
||||||
<a-form :model="emailForm" layout="vertical">
|
<a-form :model="emailForm" layout="vertical">
|
||||||
<a-form-item label="邮箱地址" field="email">
|
<a-form-item label="邮箱地址" field="email">
|
||||||
@@ -226,13 +234,21 @@
|
|||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="验证码" field="code">
|
<a-form-item label="验证码" field="code">
|
||||||
<a-input-search
|
<a-input-group style="width: 100%">
|
||||||
v-model="emailForm.code"
|
<a-input
|
||||||
placeholder="请输入验证码"
|
v-model="emailForm.code"
|
||||||
button-text="发送验证码"
|
placeholder="请输入验证码"
|
||||||
@search="handleSendEmailCode"
|
:max-length="6"
|
||||||
:loading="codeSending"
|
/>
|
||||||
/>
|
<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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -240,10 +256,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 { useUserStore } from '@/store/user';
|
||||||
import { uploadFile, getFileById } from '@/api/file/file';
|
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 { isApiSuccess } from '@/api/response';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import {
|
import {
|
||||||
@@ -265,6 +287,12 @@ const phoneLoading = ref(false);
|
|||||||
const emailLoading = ref(false);
|
const emailLoading = ref(false);
|
||||||
const codeSending = 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 showPasswordModal = ref(false);
|
||||||
const showPhoneModal = ref(false);
|
const showPhoneModal = ref(false);
|
||||||
@@ -329,8 +357,8 @@ const maskPhone = (phone: string) => {
|
|||||||
const maskEmail = (email: string) => {
|
const maskEmail = (email: string) => {
|
||||||
if (!email) return '';
|
if (!email) return '';
|
||||||
const parts = email.split('@');
|
const parts = email.split('@');
|
||||||
if (parts.length !== 2) return email;
|
if (parts.length !== 2 || !parts[0] || !parts[1]) return email;
|
||||||
const [name, domain] = parts;
|
const [name = '', domain = ''] = parts;
|
||||||
if (name.length <= 2) return email;
|
if (name.length <= 2) return email;
|
||||||
return `${name.slice(0, 2)}***@${domain}`;
|
return `${name.slice(0, 2)}***@${domain}`;
|
||||||
};
|
};
|
||||||
@@ -418,9 +446,23 @@ const computeFileHash = (file: File): Promise<string> => {
|
|||||||
|
|
||||||
// 提交资料表单
|
// 提交资料表单
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
// TODO: 等后端接口提供后对接
|
try {
|
||||||
Message.info('更新用户信息接口开发中...');
|
loading.value = true;
|
||||||
console.log('提交数据:', formData);
|
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 () => {
|
const handlePasswordSubmit = async () => {
|
||||||
if (!passwordForm.oldPassword) {
|
if (!passwordForm.oldPassword) {
|
||||||
Message.warning('请输入当前密码');
|
Message.warning('请输入当前密码');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (!passwordForm.newPassword) {
|
if (!passwordForm.newPassword) {
|
||||||
Message.warning('请输入新密码');
|
Message.warning('请输入新密码');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (passwordForm.newPassword.length < 6 || passwordForm.newPassword.length > 20) {
|
if (passwordForm.newPassword.length < 6 || passwordForm.newPassword.length > 20) {
|
||||||
Message.warning('新密码长度应为6-20位');
|
Message.warning('新密码长度应为6-20位');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
|
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
|
||||||
Message.warning('两次输入的密码不一致');
|
Message.warning('两次输入的密码不一致');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (passwordForm.oldPassword === passwordForm.newPassword) {
|
if (passwordForm.oldPassword === passwordForm.newPassword) {
|
||||||
Message.warning('新密码不能与当前密码相同');
|
Message.warning('新密码不能与当前密码相同');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 等后端接口提供后对接
|
try {
|
||||||
Message.info('修改密码接口开发中...');
|
passwordLoading.value = true;
|
||||||
console.log('修改密码:', {
|
const res = await updateUserPassword(
|
||||||
oldPassword: passwordForm.oldPassword,
|
passwordForm.oldPassword,
|
||||||
newPassword: passwordForm.newPassword,
|
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 () => {
|
const handlePhoneSubmit = async () => {
|
||||||
if (!phoneForm.phone) {
|
if (!phoneForm.phone) {
|
||||||
Message.warning('请输入手机号');
|
Message.warning('请输入手机号');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (!/^1[3-9]\d{9}$/.test(phoneForm.phone)) {
|
if (!/^1[3-9]\d{9}$/.test(phoneForm.phone)) {
|
||||||
Message.warning('请输入正确的手机号');
|
Message.warning('请输入正确的手机号');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (!phoneForm.code) {
|
if (!phoneForm.code) {
|
||||||
Message.warning('请输入验证码');
|
Message.warning('请输入验证码');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 等后端接口提供后对接
|
// TODO: 等后端接口提供后对接
|
||||||
Message.info('绑定手机号接口开发中...');
|
Message.info('绑定手机号接口开发中...');
|
||||||
console.log('绑定手机号:', phoneForm);
|
console.log('绑定手机号:', phoneForm);
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 发送手机验证码
|
// 发送手机验证码
|
||||||
@@ -500,32 +561,63 @@ const handleSendPhoneCode = async () => {
|
|||||||
|
|
||||||
// TODO: 等后端接口提供后对接
|
// TODO: 等后端接口提供后对接
|
||||||
Message.info('发送验证码接口开发中...');
|
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 = () => {
|
const handlePhoneReset = () => {
|
||||||
phoneForm.phone = '';
|
phoneForm.phone = '';
|
||||||
phoneForm.code = '';
|
phoneForm.code = '';
|
||||||
|
// 清理倒计时
|
||||||
|
if (phoneTimer) {
|
||||||
|
clearInterval(phoneTimer);
|
||||||
|
phoneTimer = null;
|
||||||
|
}
|
||||||
|
phoneCountdown.value = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 绑定邮箱
|
// 绑定邮箱
|
||||||
const handleEmailSubmit = async () => {
|
const handleEmailSubmit = async () => {
|
||||||
if (!emailForm.email) {
|
if (!emailForm.email) {
|
||||||
Message.warning('请输入邮箱');
|
Message.warning('请输入邮箱');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailForm.email)) {
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailForm.email)) {
|
||||||
Message.warning('请输入正确的邮箱地址');
|
Message.warning('请输入正确的邮箱地址');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (!emailForm.code) {
|
if (!emailForm.code) {
|
||||||
Message.warning('请输入验证码');
|
Message.warning('请输入验证码');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 等后端接口提供后对接
|
try {
|
||||||
Message.info('绑定邮箱接口开发中...');
|
emailLoading.value = true;
|
||||||
console.log('绑定邮箱:', emailForm);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 等后端接口提供后对接
|
try {
|
||||||
Message.info('发送验证码接口开发中...');
|
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 = () => {
|
const handleEmailReset = () => {
|
||||||
emailForm.email = '';
|
emailForm.email = '';
|
||||||
emailForm.code = '';
|
emailForm.code = '';
|
||||||
|
// 清理倒计时
|
||||||
|
if (emailTimer) {
|
||||||
|
clearInterval(emailTimer);
|
||||||
|
emailTimer = null;
|
||||||
|
}
|
||||||
|
emailCountdown.value = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 绑定微信
|
// 绑定微信
|
||||||
@@ -564,6 +683,18 @@ const handleUnbindWechat = () => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
userStore.getLoginUser();
|
userStore.getLoginUser();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 清理定时器
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (emailTimer) {
|
||||||
|
clearInterval(emailTimer);
|
||||||
|
emailTimer = null;
|
||||||
|
}
|
||||||
|
if (phoneTimer) {
|
||||||
|
clearInterval(phoneTimer);
|
||||||
|
phoneTimer = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -642,6 +773,18 @@ onMounted(() => {
|
|||||||
:deep(.arco-textarea) {
|
:deep(.arco-textarea) {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证码输入组样式
|
||||||
|
:deep(.arco-input-group) {
|
||||||
|
.arco-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.security-card {
|
.security-card {
|
||||||
|
|||||||
Reference in New Issue
Block a user