feat:站点列表功能拓展、绑定邮箱、修改密码
This commit is contained in:
@@ -4,15 +4,107 @@ import type { TableColumnType } from 'ant-design-vue';
|
||||
import {
|
||||
EnvironmentOutlined,
|
||||
SearchOutlined,
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { getDashboardSiteList } from '@/service/api/auth';
|
||||
import { getDashboardSiteList, addSite, deleteSite, getSiteConfig, updateSite } from '@/service/api/auth';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Form, Modal } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { regionOptions, timeZoneOptions } from '@/constants/site-options';
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: 'CardData'
|
||||
});
|
||||
|
||||
// 表单实例
|
||||
const formRef = ref();
|
||||
const useForm = Form.useForm;
|
||||
|
||||
// 弹窗控制
|
||||
const showAddDialog = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
name: '',
|
||||
region: '',
|
||||
timeZone: '',
|
||||
scenario: '',
|
||||
deviceAccountSetting: {
|
||||
username: '',
|
||||
password: ''
|
||||
}
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const { validate, validateInfos } = useForm(formData, {
|
||||
name: [
|
||||
{ required: true, message: t('page.carddata.nameRequired') },
|
||||
{
|
||||
pattern: /^[^ \+\-\@\=]$|^[^ \+\-\@\=].{0,62}[^ ]$/,
|
||||
message: t('page.carddata.nameInvalid')
|
||||
}
|
||||
],
|
||||
region: [{ required: true, message: t('page.carddata.regionRequired') }],
|
||||
timeZone: [{ required: true, message: t('page.carddata.timeZoneRequired') }],
|
||||
scenario: [{ required: true, message: t('page.carddata.scenarioRequired') }],
|
||||
'deviceAccountSetting.username': [
|
||||
{ required: true, message: t('page.carddata.usernameRequired') },
|
||||
{
|
||||
pattern: /^[\x21-\x7E]{1,64}$/,
|
||||
message: t('page.carddata.usernameInvalid')
|
||||
}
|
||||
],
|
||||
'deviceAccountSetting.password': [
|
||||
{ required: true, message: t('page.carddata.passwordRequired') },
|
||||
{
|
||||
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\!\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\@\[\\\]\^\_\`\{\|\}\~])(?!.*[\00-\040\042\077\0177]).{8,64}$/,
|
||||
message: t('page.carddata.passwordInvalid')
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 使用场景选项
|
||||
const scenarioOptions = [
|
||||
{ label: t('page.carddata.office'), value: 'office' },
|
||||
{ label: t('page.carddata.hotel'), value: 'hotel' },
|
||||
{ label: t('page.carddata.education'), value: 'education' },
|
||||
{ label: t('page.carddata.retail'), value: 'retail' },
|
||||
{ label: t('page.carddata.other'), value: 'other' }
|
||||
];
|
||||
|
||||
// 处理添加站点
|
||||
const handleAddSite = async () => {
|
||||
try {
|
||||
await validate();
|
||||
const response = await addSite(formData.value);
|
||||
if (response) {
|
||||
message.success(t('page.carddata.addSuccess'));
|
||||
showAddDialog.value = false;
|
||||
fetchSiteList(); // 刷新列表
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Add site failed:', error);
|
||||
message.error(t('page.carddata.addFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
// 打开添加对话框
|
||||
const openAddDialog = () => {
|
||||
formData.value = {
|
||||
name: '',
|
||||
region: '',
|
||||
timeZone: '',
|
||||
scenario: '',
|
||||
deviceAccountSetting: {
|
||||
username: '',
|
||||
password: ''
|
||||
}
|
||||
};
|
||||
showAddDialog.value = true;
|
||||
};
|
||||
|
||||
// 搜索和分页状态
|
||||
const searchValue = ref('');
|
||||
@@ -100,25 +192,37 @@ const columns: TableColumnType<Api.DashboardSite>[] = [
|
||||
key: 'clients',
|
||||
width: 150
|
||||
},
|
||||
// {
|
||||
// title: 'ACTION',
|
||||
// key: 'action',
|
||||
// width: 150,
|
||||
// fixed: 'right'
|
||||
// }
|
||||
{
|
||||
title: 'ACTION',
|
||||
key: 'action',
|
||||
width: 100,
|
||||
fixed: 'right'
|
||||
}
|
||||
];
|
||||
// 按钮操作处理函数
|
||||
// const handleEdit = (record: Api.DashboardSite) => {
|
||||
// console.log('Edit:', record);
|
||||
// };
|
||||
|
||||
//
|
||||
// const handleCopy = (record: Api.DashboardSite) => {
|
||||
// console.log('Copy:', record);
|
||||
// };
|
||||
//
|
||||
// const handleDelete = (record: Api.DashboardSite) => {
|
||||
// console.log('Delete:', record);
|
||||
// };
|
||||
const handleDelete = (record: Api.DashboardSite) => {
|
||||
Modal.confirm({
|
||||
title: t('page.carddata.deleteConfirmTitle'),
|
||||
content: t('page.carddata.deleteConfirmContent', { name: record.name }),
|
||||
okText: t('page.carddata.confirm'),
|
||||
cancelText: t('page.carddata.cancel'),
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
try {
|
||||
await deleteSite(record.siteId);
|
||||
message.success(t('page.carddata.deleteSuccess'));
|
||||
fetchSiteList(); // 刷新列表
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
//
|
||||
// const handleHome = (record: Api.DashboardSite) => {
|
||||
// console.log('Home:', record);
|
||||
@@ -136,6 +240,68 @@ const handlePageSizeChange = (size: number) => {
|
||||
pageSize.value = size;
|
||||
currentPage.value = 1;
|
||||
};
|
||||
|
||||
// 编辑弹窗控制
|
||||
const showEditDialog = ref(false);
|
||||
|
||||
// 编辑表单数据
|
||||
const editFormData = ref({
|
||||
name: '',
|
||||
region: '',
|
||||
timeZone: '',
|
||||
scenario: '',
|
||||
});
|
||||
|
||||
// 当前编辑的站点ID
|
||||
const currentEditSiteId = ref('');
|
||||
|
||||
// 编辑表单验证规则
|
||||
const { validate: validateEdit, validateInfos: validateEditInfos } = useForm(editFormData, {
|
||||
name: [
|
||||
{ required: true, message: t('page.carddata.nameRequired') },
|
||||
{
|
||||
pattern: /^[^ \+\-\@\=]$|^[^ \+\-\@\=].{0,62}[^ ]$/,
|
||||
message: t('page.carddata.nameInvalid')
|
||||
}
|
||||
],
|
||||
region: [{ required: true, message: t('page.carddata.regionRequired') }],
|
||||
timeZone: [{ required: true, message: t('page.carddata.timeZoneRequired') }],
|
||||
scenario: [{ required: true, message: t('page.carddata.scenarioRequired') }]
|
||||
});
|
||||
|
||||
// 处理编辑按钮点击
|
||||
const handleEdit = async (record: Api.DashboardSite) => {
|
||||
try {
|
||||
currentEditSiteId.value = record.siteId;
|
||||
const response = await getSiteConfig(record.siteId);
|
||||
if (response.data) {
|
||||
editFormData.value = {
|
||||
name: response.data.name,
|
||||
region: response.data.region,
|
||||
timeZone: response.data.timeZone,
|
||||
scenario: response.data.scenario
|
||||
};
|
||||
showEditDialog.value = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Get site config failed:', error);
|
||||
message.error(t('page.carddata.getConfigFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
// 处理更新站点
|
||||
const handleUpdateSite = async () => {
|
||||
try {
|
||||
await validateEdit();
|
||||
await updateSite(currentEditSiteId.value, editFormData.value);
|
||||
message.success(t('page.carddata.updateSuccess'));
|
||||
showEditDialog.value = false;
|
||||
fetchSiteList(); // 刷新列表
|
||||
} catch (error) {
|
||||
console.error('Update site failed:', error);
|
||||
message.error(t('page.carddata.updateFailed'));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -155,18 +321,12 @@ const handlePageSizeChange = (size: number) => {
|
||||
<search-outlined />
|
||||
</template>
|
||||
</AInput>
|
||||
<!-- <AButton type="primary">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <plus-outlined />-->
|
||||
<!-- </template>-->
|
||||
<!-- Import Site-->
|
||||
<!-- </AButton>-->
|
||||
<!-- <AButton type="primary">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <plus-outlined />-->
|
||||
<!-- </template>-->
|
||||
<!-- Add New Site-->
|
||||
<!-- </AButton>-->
|
||||
<AButton type="primary" @click="openAddDialog">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
{{ t('page.carddata.addsite') }}
|
||||
</AButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -221,16 +381,157 @@ const handlePageSizeChange = (size: number) => {
|
||||
<template v-else-if="column.key === 'clients'">
|
||||
<span>{{ record.wiredClientNum }}/{{ record.wirelessClientNum }}/{{ record.guestNum }}</span>
|
||||
</template>
|
||||
<!-- <template v-else-if="column.key === 'action'">-->
|
||||
<!-- <div class="flex items-center gap-8px">-->
|
||||
<!-- <edit-outlined class="cursor-pointer text-primary" @click="handleEdit(record)" />-->
|
||||
<!-- <copy-outlined class="cursor-pointer text-primary" @click="handleCopy(record)" />-->
|
||||
<!-- <delete-outlined class="cursor-pointer text-red-500" @click="handleDelete(record)" />-->
|
||||
<!-- <home-outlined class="cursor-pointer text-primary" @click="handleHome(record)" />-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<div class="flex items-center gap-8px">
|
||||
<edit-outlined class="cursor-pointer text-primary" @click="handleEdit(record)" />
|
||||
<!-- <copy-outlined class="cursor-pointer text-primary" @click="handleCopy(record)" />-->
|
||||
<delete-outlined class="cursor-pointer text-red-500" @click="handleDelete(record)" />
|
||||
<!-- <home-outlined class="cursor-pointer text-primary" @click="handleHome(record)" />-->
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</ATable>
|
||||
|
||||
<!-- 添加站点对话框 -->
|
||||
<AModal
|
||||
v-model:visible="showAddDialog"
|
||||
:title="t('page.carddata.addsite')"
|
||||
@ok="handleAddSite"
|
||||
@cancel="showAddDialog = false"
|
||||
:maskClosable="false"
|
||||
>
|
||||
<AForm
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<AFormItem
|
||||
name="name"
|
||||
:label="t('page.carddata.sitename')"
|
||||
v-bind="validateInfos.name"
|
||||
>
|
||||
<AInput v-model:value="formData.name" />
|
||||
</AFormItem>
|
||||
|
||||
<AFormItem
|
||||
name="region"
|
||||
:label="t('page.carddata.region')"
|
||||
v-bind="validateInfos.region"
|
||||
>
|
||||
<ASelect
|
||||
v-model:value="formData.region"
|
||||
:options="regionOptions"
|
||||
show-search
|
||||
:filter-option="(input, option) =>
|
||||
option?.label?.toLowerCase().includes(input.toLowerCase())"
|
||||
/>
|
||||
</AFormItem>
|
||||
|
||||
<AFormItem
|
||||
name="timeZone"
|
||||
:label="t('page.carddata.timezone')"
|
||||
v-bind="validateInfos.timeZone"
|
||||
>
|
||||
<ASelect
|
||||
v-model:value="formData.timeZone"
|
||||
:options="timeZoneOptions"
|
||||
show-search
|
||||
:filter-option="(input, option) =>
|
||||
option?.label?.toLowerCase().includes(input.toLowerCase())"
|
||||
/>
|
||||
</AFormItem>
|
||||
|
||||
<AFormItem
|
||||
name="scenario"
|
||||
:label="t('page.carddata.scenario')"
|
||||
v-bind="validateInfos.scenario"
|
||||
>
|
||||
<ASelect
|
||||
v-model:value="formData.scenario"
|
||||
:options="scenarioOptions"
|
||||
/>
|
||||
</AFormItem>
|
||||
|
||||
<AFormItem
|
||||
name="deviceAccountSetting.username"
|
||||
:label="t('page.carddata.username')"
|
||||
v-bind="validateInfos['deviceAccountSetting.username']"
|
||||
>
|
||||
<AInput v-model:value="formData.deviceAccountSetting.username" />
|
||||
</AFormItem>
|
||||
|
||||
<AFormItem
|
||||
name="deviceAccountSetting.password"
|
||||
:label="t('page.carddata.password')"
|
||||
v-bind="validateInfos['deviceAccountSetting.password']"
|
||||
>
|
||||
<AInputPassword v-model:value="formData.deviceAccountSetting.password" />
|
||||
</AFormItem>
|
||||
</AForm>
|
||||
</AModal>
|
||||
|
||||
<!-- 编辑站点对话框 -->
|
||||
<AModal
|
||||
v-model:visible="showEditDialog"
|
||||
:title="t('page.carddata.editsite')"
|
||||
@ok="handleUpdateSite"
|
||||
@cancel="showEditDialog = false"
|
||||
:maskClosable="false"
|
||||
>
|
||||
<AForm
|
||||
:model="editFormData"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<AFormItem
|
||||
name="name"
|
||||
:label="t('page.carddata.sitename')"
|
||||
v-bind="validateEditInfos.name"
|
||||
>
|
||||
<AInput v-model:value="editFormData.name" />
|
||||
</AFormItem>
|
||||
|
||||
<AFormItem
|
||||
name="region"
|
||||
:label="t('page.carddata.region')"
|
||||
v-bind="validateEditInfos.region"
|
||||
>
|
||||
<ASelect
|
||||
v-model:value="editFormData.region"
|
||||
:options="regionOptions"
|
||||
show-search
|
||||
:filter-option="(input, option) =>
|
||||
option?.label?.toLowerCase().includes(input.toLowerCase())"
|
||||
/>
|
||||
</AFormItem>
|
||||
|
||||
<AFormItem
|
||||
name="timeZone"
|
||||
:label="t('page.carddata.timezone')"
|
||||
v-bind="validateEditInfos.timeZone"
|
||||
>
|
||||
<ASelect
|
||||
v-model:value="editFormData.timeZone"
|
||||
:options="timeZoneOptions"
|
||||
show-search
|
||||
:filter-option="(input, option) =>
|
||||
option?.label?.toLowerCase().includes(input.toLowerCase())"
|
||||
/>
|
||||
</AFormItem>
|
||||
|
||||
<AFormItem
|
||||
name="scenario"
|
||||
:label="t('page.carddata.scenario')"
|
||||
v-bind="validateEditInfos.scenario"
|
||||
>
|
||||
<ASelect
|
||||
v-model:value="editFormData.scenario"
|
||||
:options="scenarioOptions"
|
||||
/>
|
||||
</AFormItem>
|
||||
</AForm>
|
||||
</AModal>
|
||||
</ACard>
|
||||
</template>
|
||||
|
||||
|
||||
211
src/views/user-center/email/index.vue
Normal file
211
src/views/user-center/email/index.vue
Normal 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>
|
||||
219
src/views/user-center/resetpwd/index.vue
Normal file
219
src/views/user-center/resetpwd/index.vue
Normal 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>
|
||||
Reference in New Issue
Block a user