重新提交项目内容
This commit is contained in:
parent
e523a3ce4b
commit
cd4e1d7e54
|
|
@ -1,42 +0,0 @@
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
.DS_Store
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
coverage
|
|
||||||
*.local
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Cypress
|
|
||||||
/cypress/videos/
|
|
||||||
/cypress/screenshots/
|
|
||||||
|
|
||||||
# Vitest
|
|
||||||
__screenshots__/
|
|
||||||
|
|
||||||
# Vite
|
|
||||||
*.timestamp-*-*.mjs
|
|
||||||
|
|
||||||
#auto generated files
|
|
||||||
.eslintrc-auto-import.json
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
@import './user.scss';
|
|
||||||
*{margin: 0;padding: 0;box-sizing: border-box;}
|
|
||||||
html,body{
|
|
||||||
font-size: 16px;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
/* 过渡动画样式(name="fade" 对应前缀 fade-) */
|
|
||||||
/* 1. 进入/离开的初始状态 */
|
|
||||||
.user-enter-from,
|
|
||||||
.user-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
/* 初始透明 */
|
|
||||||
transform: translateX(20px);
|
|
||||||
/* 初始右移20px */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2. 进入/离开的过渡过程 */
|
|
||||||
.user-enter-active,
|
|
||||||
.user-leave-active {
|
|
||||||
transition: all 0.5s ease;
|
|
||||||
/* 过渡时长、缓动效果 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 3. 进入完成/离开开始的状态 */
|
|
||||||
.user-enter-to,
|
|
||||||
.user-leave-from {
|
|
||||||
opacity: 1;
|
|
||||||
/* 最终不透明 */
|
|
||||||
transform: translateX(0);
|
|
||||||
/* 最终回到原位 */
|
|
||||||
}
|
|
||||||
|
|
||||||
// 全局通知样式
|
|
||||||
.global-notification {
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
// 2. 重写动画:从顶部淡入(取消右侧滑入)
|
|
||||||
&.el-notification-fade-enter-active,
|
|
||||||
&.el-notification-fade-leave-active {
|
|
||||||
transition: all 0.3s ease-out !important;
|
|
||||||
transform: translate(-50%, -150%) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
import { createRouter, createWebHistory } from 'vue-router';
|
|
||||||
import { useUserStore } from '@/stores/modules/user';
|
|
||||||
|
|
||||||
const routes = [
|
|
||||||
// 1. 补全根路径路由定义,避免通配符无限循环
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'Home',
|
|
||||||
component: () => import('@/views/Test/index.vue'), // 替换为你的首页组件
|
|
||||||
meta: { requiresAuth: true ,title: '首页' } // 首页需要登录
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/user',
|
|
||||||
name: 'User',
|
|
||||||
component: () => import('@/views/user/index.vue'),
|
|
||||||
redirect: '/user/login',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'login',
|
|
||||||
name: 'Login',
|
|
||||||
component: () => import('@/views/user/Login.vue'),
|
|
||||||
meta: { isAuthPage: true, title: '登录' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'register',
|
|
||||||
name: 'Register',
|
|
||||||
component: () => import('@/views/user/Register.vue'),
|
|
||||||
meta: { isAuthPage: true, title: '注册' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'forgot-password',
|
|
||||||
name: 'ForgotPassword',
|
|
||||||
component: () => import('@/views/user/ForgotPassword.vue'),
|
|
||||||
meta: { isAuthPage: true, title: '忘记密码' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'change-password',
|
|
||||||
name: 'ChangePassword',
|
|
||||||
component: () => import('@/views/user/ChangePassword.vue'),
|
|
||||||
meta: { requiresAuth: true, title: '修改密码' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/:pathMatch(.*)*',
|
|
||||||
redirect: '/user/login' // 404 重定向到登录页,避免循环
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(),
|
|
||||||
routes
|
|
||||||
});
|
|
||||||
|
|
||||||
// 全局前置守卫
|
|
||||||
router.beforeEach(async (to, from) => {
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const isLoggedIn = !!userStore.token;
|
|
||||||
|
|
||||||
// 从修改密码页跳登录页时,直接放行(不跳首页)
|
|
||||||
const isFromChangePasswordToLogin = from.path === '/user/change-password' && to.path === '/user/login';
|
|
||||||
if (isFromChangePasswordToLogin) {
|
|
||||||
return true; // 放行,跳过后续的“已登录访问授权页跳首页”逻辑
|
|
||||||
}
|
|
||||||
// 1. 已登录用户访问授权页 → 重定向到首页
|
|
||||||
if (isLoggedIn && to.meta.isAuthPage) {
|
|
||||||
if (to.path === '/') return true;
|
|
||||||
return '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 未登录用户访问需要登录的页面(包括首页)→ 重定向到登录页
|
|
||||||
if (!isLoggedIn && to.meta.requiresAuth) {
|
|
||||||
if (to.path === '/user/login') return true;
|
|
||||||
return '/user/login';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 其他情况正常放行
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 可选:设置页面标题
|
|
||||||
router.afterEach((to) => {
|
|
||||||
if (to.meta.title) {
|
|
||||||
document.title = to.meta.title as string;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
|
|
@ -1,214 +0,0 @@
|
||||||
import type { Ref } from 'vue';
|
|
||||||
import { setStorage, getStorage, removeStorage } from '@/utils/storage';
|
|
||||||
// 既支持指定的固定值,也支持任意字符串
|
|
||||||
export type CaptchaType = 'phone' | 'email' | string;
|
|
||||||
|
|
||||||
// 本地存储的状态类型
|
|
||||||
interface StoredCaptchaState {
|
|
||||||
type: CaptchaType; // 验证码类型
|
|
||||||
receiver: string; // 接收方(手机号/邮箱)
|
|
||||||
sendTime: number; // 发送时间戳(毫秒)
|
|
||||||
totalSeconds: number; // 总倒计时时长(默认60)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 倒计时状态类型(供组件绑定)
|
|
||||||
export interface CaptchaCountdownState {
|
|
||||||
countdown: Ref<number>; // 剩余秒数
|
|
||||||
isCounting: Ref<boolean>; // 是否正在倒计时
|
|
||||||
timer: number | null; // 定时器标识
|
|
||||||
key: string; // 唯一标识(区分不同场景/类型的验证码,如:register_phone)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化验证码倒计时状态(支持刷新恢复)
|
|
||||||
* @param key 唯一标识(必填,如:register_phone/register_email/forgotPassword_phone)
|
|
||||||
* @param totalSeconds 总倒计时时长(默认60秒)
|
|
||||||
* @returns 倒计时状态对象
|
|
||||||
*/
|
|
||||||
export const initCaptchaState = (key: string, totalSeconds: number = 60): CaptchaCountdownState => {
|
|
||||||
const state: CaptchaCountdownState = {
|
|
||||||
countdown: ref(0),
|
|
||||||
isCounting: ref(false),
|
|
||||||
timer: null,
|
|
||||||
key
|
|
||||||
};
|
|
||||||
|
|
||||||
// 页面刷新后,从sessionStorage恢复状态
|
|
||||||
const stored = getStorage<StoredCaptchaState>(`captcha_${key}`, 'session');
|
|
||||||
if (stored) {
|
|
||||||
try {
|
|
||||||
const now = Date.now();
|
|
||||||
// 计算剩余时间 = 总时长 - (当前时间 - 发送时间戳)/1000
|
|
||||||
const remaining = stored.totalSeconds - Math.floor((now - stored.sendTime) / 1000);
|
|
||||||
|
|
||||||
// 剩余时间>0,恢复倒计时
|
|
||||||
if (remaining > 0) {
|
|
||||||
state.countdown.value = remaining;
|
|
||||||
state.isCounting.value = true;
|
|
||||||
|
|
||||||
// 重启定时器
|
|
||||||
state.timer = window.setInterval(() => {
|
|
||||||
state.countdown.value--;
|
|
||||||
// 同步更新本地存储的剩余时间(可选,增强鲁棒性)
|
|
||||||
updateStoredCaptchaState(state.key, state.countdown.value);
|
|
||||||
|
|
||||||
if (state.countdown.value <= 0) {
|
|
||||||
clearCaptchaTimer(state); // 倒计时结束,清除状态
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
} else {
|
|
||||||
// 剩余时间<=0,清空本地存储
|
|
||||||
removeStorage(`captcha_${key}`, 'session');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('恢复验证码倒计时状态失败:', e);
|
|
||||||
removeStorage(`captcha_${key}`, 'session');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新本地存储的验证码状态(内部使用)
|
|
||||||
*/
|
|
||||||
const updateStoredCaptchaState = (key: string, remaining: number) => {
|
|
||||||
const stored = getStorage<StoredCaptchaState>(`captcha_${key}`, 'session');
|
|
||||||
if (stored) {
|
|
||||||
// 仅更新剩余时间(可选,核心是时间戳,剩余时间只是辅助)
|
|
||||||
setStorage(`captcha_${key}`, {
|
|
||||||
...stored,
|
|
||||||
// 可选:记录剩余时间,方便初始化时快速读取
|
|
||||||
remaining
|
|
||||||
}, 'session');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用发送验证码函数(支持手机/邮箱,防刷新重复发送)
|
|
||||||
* @param type 验证码类型:phone/email
|
|
||||||
* @param receiver 接收方:手机号/邮箱地址
|
|
||||||
* @param state 倒计时状态(由initCaptchaState生成)
|
|
||||||
* @param sendApi 实际发送验证码的接口函数(需自行实现,入参为receiver)
|
|
||||||
* @returns Promise<boolean> 发送是否成功
|
|
||||||
*/
|
|
||||||
export const sendCaptcha = async (
|
|
||||||
type: CaptchaType,
|
|
||||||
receiver: string,
|
|
||||||
state: CaptchaCountdownState,
|
|
||||||
sendApi: (receiver: string) => Promise<any>
|
|
||||||
): Promise<boolean> => {
|
|
||||||
// ========== 核心:刷新后防重复发送的校验 ==========
|
|
||||||
const stored = getStorage<StoredCaptchaState>(`captcha_${state.key}`, 'session');
|
|
||||||
if (stored) {
|
|
||||||
const now = Date.now();
|
|
||||||
const elapsed = Math.floor((now - stored.sendTime) / 1000); // 已过去的秒数
|
|
||||||
|
|
||||||
// 未到冷却期(60秒),禁止发送
|
|
||||||
if (elapsed < stored.totalSeconds) {
|
|
||||||
const remaining = stored.totalSeconds - elapsed;
|
|
||||||
ElNotification({
|
|
||||||
message: `请${remaining}秒后再发送验证码`,
|
|
||||||
type: 'warning',
|
|
||||||
duration: 2000,
|
|
||||||
customClass: 'global-notification'
|
|
||||||
});
|
|
||||||
// 恢复倒计时(防止内存状态和本地存储不一致)
|
|
||||||
state.countdown.value = remaining;
|
|
||||||
state.isCounting.value = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 原有校验 ==========
|
|
||||||
if (state.isCounting.value) {
|
|
||||||
ElNotification({
|
|
||||||
message: '验证码已发送,请勿重复点击',
|
|
||||||
type: 'warning',
|
|
||||||
duration: 2000,
|
|
||||||
customClass: 'global-notification'
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!receiver) {
|
|
||||||
ElNotification({
|
|
||||||
message: `请输入${type === 'phone' ? '手机号' : '邮箱'}`,
|
|
||||||
type: 'warning',
|
|
||||||
duration: 2000,
|
|
||||||
customClass: 'global-notification'
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. 调用真实发送接口
|
|
||||||
// await sendApi(receiver);
|
|
||||||
|
|
||||||
// 2. 发送成功提示
|
|
||||||
ElNotification({
|
|
||||||
message: `验证码已发送至${type === 'phone' ? '手机' : '邮箱'},请注意查收`,
|
|
||||||
type: 'success',
|
|
||||||
duration: 2000,
|
|
||||||
customClass: 'global-notification'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 记录发送状态到本地存储(核心:防刷新)
|
|
||||||
const storedState: StoredCaptchaState = {
|
|
||||||
type,
|
|
||||||
receiver,
|
|
||||||
sendTime: Date.now(), // 发送时间戳(毫秒)
|
|
||||||
totalSeconds: 60 // 总倒计时时长
|
|
||||||
};
|
|
||||||
setStorage(`captcha_${state.key}`, storedState, 'session');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 4. 启动60秒倒计时
|
|
||||||
state.isCounting.value = true;
|
|
||||||
state.countdown.value = 60;
|
|
||||||
|
|
||||||
// 清除旧定时器(防止重复)
|
|
||||||
if (state.timer) clearInterval(state.timer);
|
|
||||||
|
|
||||||
// 5. 定时器逻辑(同步更新本地存储)
|
|
||||||
state.timer = window.setInterval(() => {
|
|
||||||
state.countdown.value--;
|
|
||||||
// 同步更新本地存储的剩余时间
|
|
||||||
updateStoredCaptchaState(state.key, state.countdown.value);
|
|
||||||
|
|
||||||
if (state.countdown.value <= 0) {
|
|
||||||
clearCaptchaTimer(state); // 倒计时结束,清除所有状态
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
ElNotification({
|
|
||||||
message: `发送${type}验证码失败:${(error as Error).message || '未知错误'}`,
|
|
||||||
type: 'error',
|
|
||||||
duration: 2000,
|
|
||||||
customClass: 'global-notification'
|
|
||||||
});
|
|
||||||
console.error(`发送${type}验证码失败:`, error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除验证码倒计时(组件卸载/倒计时结束时调用,防止内存泄漏+清空本地存储)
|
|
||||||
* @param state 倒计时状态
|
|
||||||
*/
|
|
||||||
export const clearCaptchaTimer = (state: CaptchaCountdownState): void => {
|
|
||||||
// 1. 清除定时器
|
|
||||||
if (state.timer) {
|
|
||||||
clearInterval(state.timer);
|
|
||||||
state.timer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 重置内存状态
|
|
||||||
state.countdown.value = 0;
|
|
||||||
state.isCounting.value = false;
|
|
||||||
|
|
||||||
// 3. 清空本地存储(核心:倒计时结束后允许重新发送)
|
|
||||||
removeStorage(`captcha_${state.key}`, 'session');
|
|
||||||
};
|
|
||||||
|
|
@ -1,219 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="user-card">
|
|
||||||
<UserHeader />
|
|
||||||
<UserText :user-text="userText" @back="handleBack" />
|
|
||||||
<UserTabs v-model="activeTab" :tab-list="tabList" />
|
|
||||||
|
|
||||||
<el-form class="user-form" ref="changePasswordFormRef" :model="changePasswordForm" :rules="changePasswordRules">
|
|
||||||
<UserCaptcha v-model="changePasswordForm.code" @sendCode="handleSendCode" :show-send-button="showSendButton"
|
|
||||||
:send-state="sendState" />
|
|
||||||
<el-form-item prop="password">
|
|
||||||
<el-input v-model="changePasswordForm.password" placeholder="请输入新密码" type="password" show-password clearable
|
|
||||||
size="large" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="confirmPassword">
|
|
||||||
<el-input v-model="changePasswordForm.confirmPassword" placeholder="请确认新密码" type="password" show-password
|
|
||||||
clearable size="large" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item class="form-submit">
|
|
||||||
<el-button type="primary" @click="handleChangePassword">重新登录</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
// type类型
|
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
|
||||||
// 组件
|
|
||||||
import UserHeader from './components/Header.vue'
|
|
||||||
import UserText from './components/Text.vue'
|
|
||||||
import UserTabs from './components/Tabs.vue'
|
|
||||||
import UserCaptcha from './components/Captcha.vue'
|
|
||||||
|
|
||||||
//公共方法/store/配置文件
|
|
||||||
import { useUserStore } from '@/stores/modules/user'
|
|
||||||
import { getStorage } from '@/utils/storage'
|
|
||||||
import { md5Encrypt } from '@/utils/crypto'
|
|
||||||
import { sendCaptcha, initCaptchaState, clearCaptchaTimer } from '@/utils/sendCaptcha'
|
|
||||||
|
|
||||||
//用户校验规则
|
|
||||||
import { passwordRules, emailRules, phoneRules, codeRules } from './config/rules'
|
|
||||||
//store实例和获取user信息
|
|
||||||
const userStore = useUserStore()
|
|
||||||
const userInfo = getStorage<any>('userInfo')
|
|
||||||
|
|
||||||
//userText 实例
|
|
||||||
const userText = ref({
|
|
||||||
backText: '返回',
|
|
||||||
account: userInfo?.username || ''
|
|
||||||
})
|
|
||||||
|
|
||||||
//tabs 实例
|
|
||||||
const tabList = ref([
|
|
||||||
{ label: '邮箱验证', name: 'email' },
|
|
||||||
{ label: '手机验证', name: 'phone' },
|
|
||||||
])
|
|
||||||
const activeTab = ref<string>(getStorage('loginType', 'session') || 'email')
|
|
||||||
|
|
||||||
// ChangePassword 表单实例
|
|
||||||
const changePasswordFormRef = ref<FormInstance>()
|
|
||||||
const changePasswordForm = ref({
|
|
||||||
email: userInfo?.email || '',
|
|
||||||
countryCode: '+86',
|
|
||||||
phone: userInfo?.mobile || '',
|
|
||||||
password: '',
|
|
||||||
confirmPassword: '',
|
|
||||||
code: '',
|
|
||||||
})
|
|
||||||
console.log('changePasswordForm.value', changePasswordForm.value);
|
|
||||||
const changePasswordRules = ref<FormRules>({
|
|
||||||
email: emailRules().email,
|
|
||||||
phone: phoneRules(changePasswordForm.value).phone,
|
|
||||||
password: passwordRules(changePasswordForm.value).password,
|
|
||||||
confirmPassword: passwordRules(changePasswordForm.value).confirmPassword,
|
|
||||||
code: codeRules().code,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 验证码实例
|
|
||||||
const showSendButton = ref<boolean>(true)
|
|
||||||
const sendState = ref<object>({
|
|
||||||
isCounting: false,
|
|
||||||
countdown: 0,
|
|
||||||
})
|
|
||||||
//初始化验证码倒计时状态
|
|
||||||
const emailCaptchaState = initCaptchaState('email'); // 邮箱专属
|
|
||||||
const phoneCaptchaState = initCaptchaState('phone'); // 手机专属
|
|
||||||
// 邮箱验证码状态
|
|
||||||
const { isCounting: emailIsCounting, countdown: emailCountdown } = emailCaptchaState
|
|
||||||
// 手机验证码状态
|
|
||||||
const { isCounting: phoneIsCounting, countdown: phoneCountdown } = phoneCaptchaState
|
|
||||||
|
|
||||||
//方法
|
|
||||||
// 统一更新sendState的方法
|
|
||||||
const updateSendState = (type: string) => {
|
|
||||||
if (type === 'email') {
|
|
||||||
sendState.value = {
|
|
||||||
isCounting: emailIsCounting,
|
|
||||||
countdown: emailCountdown
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendState.value = {
|
|
||||||
isCounting: phoneIsCounting,
|
|
||||||
countdown: phoneCountdown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 初始化时根据当前activeTab更新sendState(避免刷新后丢失)
|
|
||||||
const initSendState = () => {
|
|
||||||
const currentType = activeTab.value === 'email' ? 'email' : 'phone'
|
|
||||||
updateSendState(currentType)
|
|
||||||
}
|
|
||||||
onMounted(() => {
|
|
||||||
// 页面加载时立即执行,恢复倒计时
|
|
||||||
initSendState()
|
|
||||||
})
|
|
||||||
// 监听activeTab变化,同步sendState
|
|
||||||
watch(
|
|
||||||
activeTab,
|
|
||||||
(newTab, oldTab) => {
|
|
||||||
activeTab.value = newTab;
|
|
||||||
console.log('activeTab.value', activeTab.value);
|
|
||||||
// 只有“主动切换tab”(oldTab有值)时才处理,初始化时(oldTab为undefined)不触发
|
|
||||||
if (oldTab === undefined) return
|
|
||||||
|
|
||||||
if (newTab === 'email' || newTab === 'phone') {
|
|
||||||
// 主动切换到邮箱/手机页时,同步当前类型的倒计时状态(不再无脑重置)
|
|
||||||
updateSendState(newTab)
|
|
||||||
} else if (newTab === 'code') {
|
|
||||||
// 切换到验证码页时,根据存储的类型更新状态
|
|
||||||
const currentType: string = getStorage('forgotType', 'session') || 'email'
|
|
||||||
updateSendState(currentType)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: false } // 关键:关闭立即执行,避免刷新时触发
|
|
||||||
)
|
|
||||||
|
|
||||||
// 定义发送验证码接口
|
|
||||||
const sendCodeApi = async () => {
|
|
||||||
// 发送验证码参数
|
|
||||||
const sendParams = {
|
|
||||||
email: changePasswordForm.value.email,
|
|
||||||
mobile: changePasswordForm.value.phone,
|
|
||||||
reg_type: activeTab.value === 'email' ? 1 : 2,
|
|
||||||
type:'resetPass_code'
|
|
||||||
}
|
|
||||||
return userStore.sendCode(sendParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//发送验证码
|
|
||||||
const handleSendCode = async () => {
|
|
||||||
const currentType = activeTab.value
|
|
||||||
// 第二步:确定类型、接收方、对应的倒计时状态和发送函数
|
|
||||||
const type: string = currentType || 'email'
|
|
||||||
const receiver = type === 'phone' ? changePasswordForm.value.phone : changePasswordForm.value.email;
|
|
||||||
const currentCaptchaState = type === 'phone' ? phoneCaptchaState : emailCaptchaState;
|
|
||||||
|
|
||||||
// 第三步:调用封装的发送函数(传正确的函数,而非字符串)
|
|
||||||
await sendCaptcha(
|
|
||||||
type,
|
|
||||||
receiver,
|
|
||||||
currentCaptchaState, // 只更新当前类型的倒计时状态
|
|
||||||
() => sendCodeApi() // 传入返回Promise的函数
|
|
||||||
);
|
|
||||||
|
|
||||||
//把状态同步给子组件
|
|
||||||
updateSendState(type)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 登录
|
|
||||||
const handleChangePassword = async () => {
|
|
||||||
if (!changePasswordFormRef.value) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
//信息校验
|
|
||||||
await changePasswordFormRef.value.validate();
|
|
||||||
|
|
||||||
// 重置密码参数
|
|
||||||
const changePasswordParams = {
|
|
||||||
mobile: changePasswordForm.value.phone,
|
|
||||||
email: changePasswordForm.value.email,
|
|
||||||
reg_type: activeTab.value === 'email' ? 1 : 2,
|
|
||||||
code: changePasswordForm.value.code,
|
|
||||||
login_password1: md5Encrypt(changePasswordForm.value.password),
|
|
||||||
login_password2: md5Encrypt(changePasswordForm.value.confirmPassword),
|
|
||||||
};
|
|
||||||
console.log('changePasswordParams', changePasswordParams);
|
|
||||||
await userStore.changePassword(changePasswordParams);
|
|
||||||
changePasswordForm.value = {
|
|
||||||
email: userInfo?.email || '',
|
|
||||||
countryCode: '+86',
|
|
||||||
phone: userInfo?.mobile || '',
|
|
||||||
password: '',
|
|
||||||
confirmPassword: '',
|
|
||||||
code: ''
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('表单校验失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//返回登录页
|
|
||||||
const handleBack = () => {
|
|
||||||
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.user-text {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-form {
|
|
||||||
.form-submit {
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Loading…
Reference in New Issue