2
0

feat:站点列表功能拓展、绑定邮箱、修改密码

This commit is contained in:
zhongzm
2025-04-01 15:21:08 +08:00
parent 42d10809e7
commit b27aebed73
11 changed files with 1319 additions and 51 deletions

View File

@@ -0,0 +1,211 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { Form, message } from 'ant-design-vue';
import { useI18n } from 'vue-i18n';
import { getEmailCode, getUserProfile, updateUserProfile } from '@/service/api/auth';
import request from '@/service/request';
const { t } = useI18n();
const useForm = Form.useForm;
// 表单数据
const formData = ref({
email: '',
code: ''
});
// 当前邮箱
const currentEmail = ref('');
// 倒计时
const countdown = ref(0);
const timer = ref<NodeJS.Timeout | null>(null);
// 表单验证规则
const { validate, validateInfos } = useForm(formData, {
email: [
{ required: true, message: t('page.email.emailRequired') },
{ type: 'email', message: t('page.email.emailInvalid') }
],
code: [
{ required: true, message: t('page.email.codeRequired') },
{ len: 4, message: t('page.email.codeLength') }
]
});
// 获取当前用户邮箱
const getCurrentEmail = async () => {
try {
const response = await getUserProfile();
if (response.data) {
currentEmail.value = response.data.email || '';
if (currentEmail.value) {
formData.value.email = currentEmail.value;
}
}
} catch (error) {
}
};
// 开始倒计时
const startCountdown = () => {
countdown.value = 60;
timer.value = setInterval(() => {
if (countdown.value > 0) {
countdown.value--;
} else {
if (timer.value) {
clearInterval(timer.value);
timer.value = null;
}
}
}, 1000);
};
// 获取验证码
const handleGetCode = async () => {
try {
// 验证邮箱
await validate(['email']);
// 发送获取验证码请求
await getEmailCode(formData.value.email);
message.success(t('page.email.codeSent'));
startCountdown();
} catch (error) {
}
};
// 提交表单
const handleSubmit = async () => {
try {
await validate();
await updateUserProfile({
email: formData.value.email,
code: formData.value.code
});
message.success(t('page.email.updateSuccess'));
formData.value.code = ''; // 清空验证码
setTimeout(async () => {
await getCurrentEmail(); // 刷新当前邮箱
}, 500);
} catch (error) {
}
};
// 组件挂载时获取当前邮箱
onMounted(() => {
getCurrentEmail();
});
// 组件卸载时清除定时器
const onUnmounted = () => {
if (timer.value) {
clearInterval(timer.value);
timer.value = null;
}
};
</script>
<template>
<div class="email-binding">
<ACard :bordered="false">
<h2 class="mb-4">{{ t('page.email.title') }}</h2>
<div v-if="currentEmail" class="mb-4">
{{ t('page.email.currentEmail') }}: {{ currentEmail }}
</div>
<AForm
:model="formData"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 16 }"
>
<AFormItem
name="email"
:label="t('page.email.email')"
v-bind="validateInfos.email"
class="form-item-gap"
>
<AInput v-model:value="formData.email" />
</AFormItem>
<AFormItem
name="code"
:label="t('page.email.code')"
v-bind="validateInfos.code"
class="form-item-gap"
>
<div class="verification-code-wrapper">
<AInput v-model:value="formData.code" class="verification-input" />
<AButton
type="primary"
:disabled="countdown > 0"
@click="handleGetCode"
class="verification-button"
>
{{ countdown > 0 ? `${countdown}s` : t('page.email.getCode') }}
</AButton>
</div>
</AFormItem>
<AFormItem :wrapper-col="{ offset: 4, span: 16 }">
<AButton type="primary" @click="handleSubmit">
{{ t('page.email.submit') }}
</AButton>
</AFormItem>
</AForm>
</ACard>
</div>
</template>
<style scoped>
.email-binding {
max-width: 800px;
margin: 0 auto;
padding: 24px;
}
.flex {
display: flex;
}
.gap-4 {
gap: 1rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.form-item-gap {
margin-bottom: 24px;
}
:deep(.ant-form-item-label) {
padding-right: 24px;
}
.verification-code-wrapper {
display: flex;
gap: 12px;
align-items: center;
}
.verification-input {
flex: 1;
}
.verification-button {
width: 120px;
flex-shrink: 0;
}
:deep(.ant-form-item) {
margin-bottom: 32px;
}
:deep(.ant-input-affix-wrapper) {
width: 100%;
}
</style>

View File

@@ -0,0 +1,219 @@
<script setup lang="ts">
import { ref, reactive, computed } from 'vue';
import { message } from 'ant-design-vue';
import { useI18n } from 'vue-i18n';
import { getEmailCode, resetPassword, updatePasswordByOld } from '@/service/api/auth';
import { useRouterPush } from '@/hooks/common/router';
import { useFormRules } from '@/hooks/common/form';
const { t } = useI18n();
const { routerPushByKey } = useRouterPush();
// 修改方式
const resetType = ref('email'); // 'email' | 'password'
// 邮箱验证表单数据
const emailFormData = reactive({
email: '',
code: '',
password: '',
confirmPassword: ''
});
// 原密码表单数据
const pwdFormData = reactive({
oldPassword: '',
newPassword: '',
confirmPassword: ''
});
// 验证码按钮状态
const codeButtonLoading = ref(false);
const countdown = ref(0);
const timer = ref<NodeJS.Timeout>();
// 邮箱验证表单规则
const emailRules = computed(() => {
const { formRules, createConfirmPwdRule } = useFormRules();
return {
email: formRules.email,
code: formRules.code,
password: formRules.pwd,
confirmPassword: createConfirmPwdRule(emailFormData.password)
};
});
// 原密码表单规则
const pwdRules = computed(() => {
const { formRules, createConfirmPwdRule } = useFormRules();
return {
oldPassword: formRules.pwd,
newPassword: formRules.pwd,
confirmPassword: createConfirmPwdRule(pwdFormData.newPassword)
};
});
// 获取验证码
const handleGetCode = async () => {
try {
if (!emailFormData.email) {
message.error(t('page.resetPwd.emailRequired'));
return;
}
codeButtonLoading.value = true;
await getEmailCode(emailFormData.email);
message.success(t('page.resetPwd.codeSent'));
// 开始倒计时
countdown.value = 60;
timer.value = setInterval(() => {
if (countdown.value > 0) {
countdown.value--;
} else {
clearInterval(timer.value);
}
}, 1000);
} catch (error) {
console.error('Failed to get code:', error);
} finally {
codeButtonLoading.value = false;
}
};
// 提交表单
const emailFormRef = ref();
const pwdFormRef = ref();
const handleSubmit = async () => {
try {
if (resetType.value === 'email') {
await emailFormRef.value?.validate();
const hide = message.loading(t('common.loading'), 0);
await resetPassword({
email: emailFormData.email,
code: emailFormData.code,
password: emailFormData.password
});
hide();
} else {
await pwdFormRef.value?.validate();
const hide = message.loading(t('common.loading'), 0);
await updatePasswordByOld({
oldPassword: pwdFormData.oldPassword,
newPassword: pwdFormData.newPassword
});
hide();
}
message.success(t('page.resetPwd.success'));
routerPushByKey('login');
} catch (error) {
}
};
</script>
<template>
<div class="flex-col-stretch items-center justify-center min-h-500px p-24px">
<div class="w-full max-w-400px">
<h2 class="text-24px font-bold mb-24px text-center">{{ t('page.resetPwd.title') }}</h2>
<!-- 修改方式选择 -->
<a-radio-group v-model:value="resetType" class="mb-16px">
<a-radio value="email">{{ t('page.resetPwd.byEmail') }}</a-radio>
<a-radio value="password">{{ t('page.resetPwd.byPassword') }}</a-radio>
</a-radio-group>
<!-- 邮箱验证表单 -->
<a-form
v-if="resetType === 'email'"
ref="emailFormRef"
:model="emailFormData"
:rules="emailRules"
layout="vertical"
>
<a-form-item name="email" :label="t('page.resetPwd.email')">
<div class="flex gap-8px">
<a-input
v-model:value="emailFormData.email"
:placeholder="t('page.resetPwd.emailPlaceholder')"
/>
<a-button
:loading="codeButtonLoading"
:disabled="countdown > 0"
@click="handleGetCode"
>
{{ countdown > 0 ? `${countdown}s` : t('page.resetPwd.getCode') }}
</a-button>
</div>
</a-form-item>
<a-form-item name="code" :label="t('page.resetPwd.code')">
<a-input
v-model:value="emailFormData.code"
:placeholder="t('page.resetPwd.codePlaceholder')"
/>
</a-form-item>
<a-form-item name="password" :label="t('page.resetPwd.newPassword')">
<a-input-password
v-model:value="emailFormData.password"
:placeholder="t('page.resetPwd.passwordPlaceholder')"
/>
</a-form-item>
<a-form-item name="confirmPassword" :label="t('page.resetPwd.confirmPassword')">
<a-input-password
v-model:value="emailFormData.confirmPassword"
:placeholder="t('page.resetPwd.confirmPasswordPlaceholder')"
/>
</a-form-item>
</a-form>
<!-- 原密码表单 -->
<a-form
v-else
ref="pwdFormRef"
:model="pwdFormData"
:rules="pwdRules"
layout="vertical"
>
<a-form-item name="oldPassword" :label="t('page.resetPwd.oldPassword')">
<a-input-password
v-model:value="pwdFormData.oldPassword"
:placeholder="t('page.resetPwd.oldPasswordPlaceholder')"
/>
</a-form-item>
<a-form-item name="newPassword" :label="t('page.resetPwd.newPassword')">
<a-input-password
v-model:value="pwdFormData.newPassword"
:placeholder="t('page.resetPwd.passwordPlaceholder')"
/>
</a-form-item>
<a-form-item name="confirmPassword" :label="t('page.resetPwd.confirmPassword')">
<a-input-password
v-model:value="pwdFormData.confirmPassword"
:placeholder="t('page.resetPwd.confirmPasswordPlaceholder')"
/>
</a-form-item>
</a-form>
<!-- 提交按钮 -->
<a-form-item>
<a-button type="primary" block @click="handleSubmit">
{{ t('common.submit') }}
</a-button>
</a-form-item>
</div>
</div>
</template>
<style scoped>
.max-w-400px {
max-width: 400px;
}
</style>