feat: 优化忘记密码页面 - 移除步骤条,合并表单,验证码后才能输入密码
This commit is contained in:
parent
a1205c3d5a
commit
b6d968ae96
|
|
@ -153,7 +153,7 @@ const transferType = ref('internal')
|
|||
* @description 创建默认转账表单数据
|
||||
*/
|
||||
const createDefaultTransferForm = ():any => ({
|
||||
transferOutAccount: -1,
|
||||
transferOutAccount: '',
|
||||
transferInAccount: '',
|
||||
transferAmount: '',
|
||||
remark: '',
|
||||
|
|
@ -357,6 +357,8 @@ const getTransferWalletOptions = async () => {
|
|||
const data: any = await getTransferWalletOptionsApi()
|
||||
transferFormOptionsMap.value.transferOutAccount = data
|
||||
transferFormOptionsMap.value.transferInAccount = data.filter((item: any) => item.value !== -1)
|
||||
|
||||
transferForm.value.transferOutAccount = -1
|
||||
}
|
||||
/**
|
||||
* @description 页面加载完成需要渲染的数据
|
||||
|
|
|
|||
|
|
@ -41,23 +41,18 @@ import { getStorage } from '@/utils/storage'
|
|||
import dayjs from 'dayjs'
|
||||
|
||||
// 公用方法
|
||||
import { getRecentSevenDays } from '@/utils/handleTime.ts'
|
||||
|
||||
/**
|
||||
* @description: 定义搜索数据
|
||||
*/
|
||||
// 获取近一周的时间范围
|
||||
const dateRange = {
|
||||
start: dayjs().subtract(7, 'day').format('YYYY-MM-DD'),
|
||||
end: dayjs().format('YYYY-MM-DD'),
|
||||
}
|
||||
const searchForm = ref({
|
||||
const initTime = getRecentSevenDays()
|
||||
const searchForm = ref<any>({
|
||||
accountName: '',
|
||||
orderId: '2',
|
||||
tradeType: '',
|
||||
start_time: dateRange.start,
|
||||
end_time: dateRange.end,
|
||||
startTime: dateRange.start,
|
||||
endTime: dateRange.end,
|
||||
dateRange: [initTime.start, initTime.end],
|
||||
subAccountName: '',
|
||||
})
|
||||
const searchOptions = ref(search)
|
||||
|
|
@ -85,6 +80,13 @@ const total = ref(0)
|
|||
const getCustomerTradeOrderList = async () => {
|
||||
try {
|
||||
startLoading()
|
||||
// 从 dateRange 中提取开始和结束时间
|
||||
let startTime = initTime.start
|
||||
let endTime = initTime.end
|
||||
if (searchForm.value.dateRange && searchForm.value.dateRange.length === 2) {
|
||||
startTime = dayjs(searchForm.value.dateRange[0]).format('YYYY-MM-DD HH:mm:ss')
|
||||
endTime = dayjs(searchForm.value.dateRange[1]).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
const params = {
|
||||
page: currentPage.value,
|
||||
page_size: pageSize.value,
|
||||
|
|
@ -92,8 +94,8 @@ const getCustomerTradeOrderList = async () => {
|
|||
account_id: searchForm.value.subAccountName,
|
||||
order_no: '',
|
||||
type: searchForm.value.tradeType,
|
||||
start_time: dateRange.start,
|
||||
end_time: dateRange.end,
|
||||
start_time: startTime,
|
||||
end_time: endTime,
|
||||
}
|
||||
console.log(params)
|
||||
const data: any = await getCustomTradeOrderListApi(params)
|
||||
|
|
@ -165,10 +167,7 @@ const handleReset = () => {
|
|||
accountName: '',
|
||||
orderId: '',
|
||||
tradeType: '',
|
||||
startTime: dateRange.start,
|
||||
endTime: dateRange.end,
|
||||
start_time: dateRange.start,
|
||||
end_time: dateRange.end,
|
||||
dateRange: [initTime.start, initTime.end],
|
||||
subAccountName: '',
|
||||
}
|
||||
getCustomerTradeOrderList()
|
||||
|
|
|
|||
|
|
@ -57,18 +57,19 @@ export const search = [
|
|||
],
|
||||
width: '140px'
|
||||
},
|
||||
// // 开始时间
|
||||
// {
|
||||
// prop: "startTime", label: '注册开始时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
|
||||
// valueFormat: 'YYYY-MM-DD',
|
||||
// width: '140px'
|
||||
// },
|
||||
// // 结束时间
|
||||
// {
|
||||
// prop: "endTime", label: '注册结束时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
|
||||
// valueFormat: 'YYYY-MM-DD',
|
||||
// width: '140px'
|
||||
// },
|
||||
// 时间范围
|
||||
{
|
||||
label: '时间',
|
||||
prop: 'dateRange',
|
||||
type: 'date' as const,
|
||||
dateType: 'datetimerange' as const,
|
||||
placeholder: '请选择时间范围',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
width: '340'
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,46 +2,44 @@
|
|||
<div class="user-card">
|
||||
<UserHeader />
|
||||
<UserText :user-text="userText" @back="handleBack" />
|
||||
<UserTabs v-model="activeTab" :hide-tab="hideTab" :tab-list="tabList" />
|
||||
<UserTabs v-model="activeTab" :hide-tab="false" :tab-list="tabList" />
|
||||
|
||||
<el-form class="user-form" ref="forgotPasswordFormRef" :model="forgotPasswordForm" :rules="forgotPasswordRules">
|
||||
<Transition name="form-switch" mode="out-in">
|
||||
<el-form-item v-if="activeTab === 'email'" key="form-email-item" prop="email">
|
||||
<el-input v-model="forgotPasswordForm.email" placeholder="请输入邮箱" clearable size="large" />
|
||||
</el-form-item>
|
||||
<!-- 邮箱输入 -->
|
||||
<el-form-item prop="email" v-if="activeTab === 'email'">
|
||||
<el-input v-model="forgotPasswordForm.email" placeholder="请输入邮箱" clearable size="large" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 手机号输入 -->
|
||||
<el-form-item class="phone-item" prop="phone" v-else-if="activeTab === 'phone'">
|
||||
<el-select v-model="forgotPasswordForm.countryCode" :teleported="false" value-key="code" placeholder="国家"
|
||||
size="large">
|
||||
<el-option v-for="item in countryOptions" :key="item.code" :label="item.code" :value="item.code">
|
||||
<div class="country-option">
|
||||
<span class="country-name">{{ item.name }}</span>
|
||||
<span class="country-code">{{ item.code }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-input v-model="forgotPasswordForm.phone" placeholder="请输入手机号" clearable size="large" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 验证码输入 -->
|
||||
<UserCaptcha ref="captchaRef" v-model="forgotPasswordForm.code" @sendCode="handleSendCode" :show-send-button="showSendButton"
|
||||
:send-state="sendState" />
|
||||
|
||||
<!-- 密码输入(验证码发送后才可输入) -->
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="forgotPasswordForm.password" :placeholder="codeSent ? '请输入新密码' : '请先发送验证码'"
|
||||
:disabled="!codeSent" type="password" show-password clearable size="large" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="confirmPassword">
|
||||
<el-input v-model="forgotPasswordForm.confirmPassword" :placeholder="codeSent ? '请确认新密码' : '请先发送验证码'"
|
||||
:disabled="!codeSent" type="password" show-password clearable size="large" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item class="phone-item" prop="phone" v-else-if="activeTab === 'phone'" key="form-phone-item">
|
||||
<el-select v-model="forgotPasswordForm.countryCode" :teleported="false" value-key="code" placeholder="国家"
|
||||
size="large">
|
||||
<el-option v-for="item in countryOptions" :key="item.code" :label="item.code" :value="item.code">
|
||||
<div class="country-option">
|
||||
<span class="country-name">{{ item.name }}</span>
|
||||
<span class="country-code">{{ item.code }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-input v-model="forgotPasswordForm.phone" placeholder="请输入手机号" clearable size="large" />
|
||||
</el-form-item>
|
||||
<UserCaptcha v-model="forgotPasswordForm.code" v-else-if="activeTab === 'code'" key="form-code-item"
|
||||
@sendCode="handleSendCode" :show-send-button="showSendButton" :send-state="sendState" />
|
||||
<div v-else-if="activeTab === 'password'" key="form-password-group">
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="forgotPasswordForm.password" placeholder="请输入新密码" type="password" show-password clearable
|
||||
size="large" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="confirmPassword">
|
||||
<el-input v-model="forgotPasswordForm.confirmPassword" placeholder="请确认新密码" type="password" show-password
|
||||
clearable size="large" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</Transition>
|
||||
<div class="forgot-next">
|
||||
<div :class="{ active: activeTab === 'email' || activeTab === 'phone' }"></div>
|
||||
<div :class="{ active: activeTab === 'code' }"></div>
|
||||
<div :class="{ active: activeTab === 'password' }"></div>
|
||||
</div>
|
||||
<el-form-item class="form-submit">
|
||||
<el-button type="primary" @click="handleForgotPassword">{{ activeTab === 'password' ? '去登录' : '继续'
|
||||
<el-button type="primary" @click="handleForgotPassword">{{ codeSent ? '重置密码' : '发送验证码'
|
||||
}}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
|
@ -78,22 +76,24 @@ const router = useRouter()
|
|||
//userText 实例
|
||||
const userText = ref({
|
||||
backText: '返回',
|
||||
title: '恢复密码',
|
||||
title: '忘记密码',
|
||||
text: '请输入您注册时使用的邮箱或手机号',
|
||||
account: '',
|
||||
|
||||
})
|
||||
|
||||
//tabs实例
|
||||
// tabs实例 - 用于切换邮箱和手机
|
||||
const tabList = ref([
|
||||
{ label: '邮箱', name: 'email' },
|
||||
{ label: '手机', name: 'phone' },
|
||||
])
|
||||
const activeTab = ref<string>('email')
|
||||
const hideTab = ref<boolean>(false)
|
||||
|
||||
// 验证码是否已发送标记
|
||||
const codeSent = ref(false)
|
||||
|
||||
// 表单实例
|
||||
const forgotPasswordFormRef = ref<FormInstance>()
|
||||
const captchaRef = ref()
|
||||
const forgotPasswordForm = ref({
|
||||
email: '',
|
||||
countryCode: '+86',
|
||||
|
|
@ -110,13 +110,31 @@ const forgotPasswordRules = ref<FormRules>({
|
|||
code: codeRules().code
|
||||
})
|
||||
|
||||
// 监听 activeTab 变化,重置状态并清空所有输入字段
|
||||
watch(activeTab, (newTab, oldTab) => {
|
||||
codeSent.value = false
|
||||
forgotPasswordForm.value.password = ''
|
||||
forgotPasswordForm.value.confirmPassword = ''
|
||||
// 清空验证码输入框
|
||||
captchaRef.value?.clear()
|
||||
// 切换到手机时清空邮箱,切换到邮箱时清空手机号
|
||||
if (newTab === 'phone') {
|
||||
forgotPasswordForm.value.email = ''
|
||||
forgotPasswordFormRef.value?.clearValidate(['email'])
|
||||
} else if (newTab === 'email') {
|
||||
forgotPasswordForm.value.phone = ''
|
||||
forgotPasswordFormRef.value?.clearValidate(['phone'])
|
||||
}
|
||||
forgotPasswordFormRef.value?.clearValidate(['code', 'password', 'confirmPassword'])
|
||||
})
|
||||
|
||||
// 验证码实例
|
||||
const showSendButton = ref<boolean>(true)
|
||||
const sendState = ref<object>({
|
||||
isCounting: false,
|
||||
countdown: 0,
|
||||
})
|
||||
//初始化验证码倒计时状态(添加页面前缀隔离不同页面的倒计时状态)
|
||||
//初始化验证码倒计时状态
|
||||
const emailCaptchaState = initCaptchaState('email', 60, 'forgot_email'); // 邮箱专属
|
||||
const phoneCaptchaState = initCaptchaState('phone', 60, 'forgot_phone'); // 手机专属
|
||||
// 邮箱验证码状态
|
||||
|
|
@ -126,7 +144,6 @@ const { isCounting: phoneIsCounting, countdown: phoneCountdown } = phoneCaptchaS
|
|||
|
||||
//方法
|
||||
|
||||
|
||||
// 统一更新sendState的方法
|
||||
const updateSendState = (type: string) => {
|
||||
if (type === 'email') {
|
||||
|
|
@ -142,167 +159,88 @@ const updateSendState = (type: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
// 监听activeTab变化,同步sendState
|
||||
watch(activeTab, (newTab) => {
|
||||
if (newTab === 'email' || newTab === 'phone') {
|
||||
// 切换回邮箱/手机页时,重置sendState
|
||||
sendState.value = { isCounting: false, countdown: 0 }
|
||||
} else if (newTab === 'code') {
|
||||
// 切换到验证码页时,根据当前存储的类型更新状态
|
||||
const currentType: string = getStorage('forgotType', 'session') || 'email'
|
||||
updateSendState(currentType)
|
||||
}
|
||||
})
|
||||
|
||||
// 定义发送验证码接口
|
||||
const sendCodeApi = async () => {
|
||||
// 发送验证码参数
|
||||
const sendParams = {
|
||||
email: forgotPasswordForm.value.email,
|
||||
mobile: forgotPasswordForm.value.phone,
|
||||
type:'transfer_code'
|
||||
type: 'transfer_code'
|
||||
}
|
||||
return userStore.sendCode(sendParams)
|
||||
}
|
||||
|
||||
//发送验证码
|
||||
const handleSendCode = async () => {
|
||||
// 第二步:确定类型、接收方、对应的倒计时状态和发送函数
|
||||
const type: string = getStorage('forgotType', 'session') || 'email'
|
||||
// 根据当前 tab 确定类型
|
||||
const type = activeTab.value
|
||||
|
||||
const receiver = type === 'phone' ? forgotPasswordForm.value.phone : forgotPasswordForm.value.email;
|
||||
const currentCaptchaState = type === 'phone' ? phoneCaptchaState : emailCaptchaState;
|
||||
// 第三步:调用封装的发送函数(传正确的函数,而非字符串)
|
||||
// 调用封装的发送函数
|
||||
await sendCaptcha(
|
||||
type,
|
||||
receiver,
|
||||
currentCaptchaState, // 只更新当前类型的倒计时状态
|
||||
() => sendCodeApi() // 传入返回Promise的函数
|
||||
currentCaptchaState,
|
||||
() => sendCodeApi()
|
||||
);
|
||||
|
||||
//把状态同步给子组件
|
||||
updateSendState(type)
|
||||
// 标记验证码已发送
|
||||
codeSent.value = true
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
// 从密码页返回 → 验证码页
|
||||
if (activeTab.value === 'password') {
|
||||
activeTab.value = 'code'
|
||||
userText.value = {
|
||||
...userText.value,
|
||||
title: '双重验证',
|
||||
text: '请输入发送的验证码',
|
||||
account: getStorage('forgot', 'session') || ''
|
||||
}
|
||||
// 清空密码字段
|
||||
forgotPasswordForm.value.password = ''
|
||||
forgotPasswordForm.value.confirmPassword = ''
|
||||
forgotPasswordFormRef.value?.clearValidate(['password', 'confirmPassword'])
|
||||
}
|
||||
|
||||
// 从验证码页返回 → 邮箱/手机页
|
||||
else if (activeTab.value === 'code') {
|
||||
const getForgotType: string = getStorage('forgotType', 'session') || 'email'
|
||||
activeTab.value = getForgotType
|
||||
hideTab.value = false
|
||||
userText.value = {
|
||||
...userText.value,
|
||||
title: '恢复密码',
|
||||
text: '请输入您注册时使用的邮箱或手机号',
|
||||
account: ''
|
||||
}
|
||||
// 清空验证码字段
|
||||
forgotPasswordForm.value.code = ''
|
||||
forgotPasswordFormRef.value?.clearValidate(['code'])
|
||||
}
|
||||
|
||||
// 从邮箱/手机页返回 → 重置为初始状态
|
||||
else {
|
||||
activeTab.value = 'email'
|
||||
forgotPasswordForm.value.email = ''
|
||||
forgotPasswordForm.value.phone = ''
|
||||
forgotPasswordFormRef.value?.clearValidate(['email', 'phone'])
|
||||
router.push('/user/login')
|
||||
}
|
||||
router.push('/user/login')
|
||||
}
|
||||
|
||||
//重置密码
|
||||
//提交表单
|
||||
const handleForgotPassword = async () => {
|
||||
if (!forgotPasswordFormRef.value) return
|
||||
|
||||
try {
|
||||
const currentTab = activeTab.value;
|
||||
const getForgotType: string = getStorage('forgotType', 'session') || 'email'
|
||||
// 步骤1:校验邮箱/手机号 → 切换到验证码页
|
||||
if (currentTab === 'email' || currentTab === 'phone') {
|
||||
// 校验当前字段
|
||||
await forgotPasswordFormRef.value.validateField(currentTab)
|
||||
// 存储账号类型和值
|
||||
setStorage('forgotType', currentTab, 'session')
|
||||
setStorage('forgot', currentTab === 'email' ? forgotPasswordForm.value.email : forgotPasswordForm.value.phone, 'session')
|
||||
// 更新状态
|
||||
hideTab.value = true
|
||||
activeTab.value = 'code'
|
||||
userText.value = {
|
||||
...userText.value,
|
||||
title: '双重验证',
|
||||
text: '请输入发送的验证码',
|
||||
account: currentTab === 'email' ? forgotPasswordForm.value.email : forgotPasswordForm.value.phone
|
||||
}
|
||||
updateSendState(getForgotType)
|
||||
// 根据当前 tab 校验对应的邮箱或手机号
|
||||
await forgotPasswordFormRef.value.validateField(activeTab.value)
|
||||
|
||||
// 校验验证码
|
||||
await forgotPasswordFormRef.value.validateField('code')
|
||||
|
||||
// 校验密码
|
||||
await forgotPasswordFormRef.value.validateField(['password', 'confirmPassword'])
|
||||
|
||||
// 重置密码参数
|
||||
const forgotParams = {
|
||||
mobile: activeTab.value === 'phone' ? forgotPasswordForm.value.phone : undefined,
|
||||
email: activeTab.value === 'email' ? forgotPasswordForm.value.email : undefined,
|
||||
code: forgotPasswordForm.value.code,
|
||||
login_password1: forgotPasswordForm.value.password,
|
||||
login_password2: forgotPasswordForm.value.confirmPassword,
|
||||
}
|
||||
|
||||
// 步骤2:校验验证码 → 切换到密码页
|
||||
else if (currentTab === 'code') {
|
||||
// 校验验证码
|
||||
await forgotPasswordFormRef.value.validateField(currentTab)
|
||||
// 更新状态
|
||||
activeTab.value = 'password'
|
||||
userText.value = {
|
||||
...userText.value,
|
||||
title: '设置新密码',
|
||||
text: '请设置您的新登录密码',
|
||||
account: getStorage('forgot', 'session') || ''
|
||||
}
|
||||
// 调用重置密码接口
|
||||
await userStore.forgotPassword(forgotParams)
|
||||
|
||||
// 清空存储和表单
|
||||
forgotPasswordForm.value = {
|
||||
email: '',
|
||||
countryCode: '+86',
|
||||
phone: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
code: ''
|
||||
}
|
||||
codeSent.value = false
|
||||
|
||||
// 步骤3:校验密码 → 重置密码
|
||||
else if (currentTab === 'password') {
|
||||
// 校验密码+确认密码
|
||||
await forgotPasswordFormRef.value.validateField(['password', 'confirmPassword'])
|
||||
|
||||
// 重置密码参数
|
||||
const forgotParams = {
|
||||
mobile: forgotPasswordForm.value.phone,
|
||||
email: forgotPasswordForm.value.email,
|
||||
code: forgotPasswordForm.value.code,
|
||||
// login_password1: md5Encrypt(forgotPasswordForm.value.password),
|
||||
// login_password2: md5Encrypt(forgotPasswordForm.value.confirmPassword),
|
||||
login_password1: forgotPasswordForm.value.password,
|
||||
login_password2: forgotPasswordForm.value.confirmPassword,
|
||||
}
|
||||
|
||||
// 调用重置密码接口
|
||||
await userStore.forgotPassword(forgotParams)
|
||||
|
||||
// 清空存储和表单
|
||||
removeStorage('forgotType', 'session')
|
||||
removeStorage('forgot', 'session')
|
||||
forgotPasswordForm.value = {
|
||||
email: '',
|
||||
countryCode: '+86',
|
||||
phone: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
code: ''
|
||||
}
|
||||
}
|
||||
// 跳转到登录页
|
||||
ElMessage.success('密码重置成功,请登录')
|
||||
router.push('/user/login')
|
||||
} catch (error) {
|
||||
console.error('表单提交失败:', error)
|
||||
}
|
||||
}
|
||||
// 4. 组件卸载时清除定时器(关键:防止内存泄漏)
|
||||
// 组件卸载时清除定时器
|
||||
onUnmounted(() => {
|
||||
clearCaptchaTimer(emailCaptchaState);
|
||||
clearCaptchaTimer(phoneCaptchaState);
|
||||
|
|
@ -316,43 +254,5 @@ onUnmounted(() => {
|
|||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.forgot-next {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 60px;
|
||||
|
||||
div {
|
||||
position: relative;
|
||||
height: 4px;
|
||||
width: 40px;
|
||||
background: rgb(209 213 219 / var(--tw-bg-opacity, 1));
|
||||
border-radius: 999px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 0;
|
||||
background-color: var(--el-color-primary);
|
||||
transition: all 0.3s ease-out;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
&::before {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue