新增:新增修改邮箱功能,并优化部分以往代码(引入 spring-boot-starter-mail 用于发送邮件验证码)

This commit is contained in:
2023-01-14 01:05:39 +08:00
parent 73fadb8315
commit 8b82557883
45 changed files with 1318 additions and 280 deletions

View File

@@ -2,14 +2,6 @@ import axios from 'axios';
import type { RouteRecordNormalized } from 'vue-router';
import { UserState } from '@/store/modules/login/types';
export interface ImageCaptchaRes {
uuid: string;
img: string;
}
export function getImageCaptcha() {
return axios.get<ImageCaptchaRes>('/captcha/img');
}
export interface LoginReq {
username: string;
password: string;

View File

@@ -0,0 +1,22 @@
import axios from 'axios';
import qs from 'query-string';
export interface ImageCaptchaRes {
uuid: string;
img: string;
}
export function getImageCaptcha() {
return axios.get<ImageCaptchaRes>('/common/captcha/img');
}
export interface MailCaptchaReq {
email: string;
}
export function getMailCaptcha(params: MailCaptchaReq) {
return axios.get('/common/captcha/mail', {
params,
paramsSerializer: (obj) => {
return qs.stringify(obj);
},
});
}

View File

@@ -27,4 +27,13 @@ export interface UpdatePasswordReq {
}
export function updatePassword(req: UpdatePasswordReq) {
return axios.patch('/system/user/center/password', req);
}
export interface UpdateEmailReq {
newEmail: string;
captcha: string;
currentPassword: string;
}
export function updateEmail(req: UpdateEmailReq) {
return axios.patch('/system/user/center/email', req);
}

View File

@@ -2,6 +2,8 @@
<a-layout-footer class="footer">
{{ `Copyright © 2022-${new Date().getFullYear()} Charles7c` }}
<span>&nbsp;&nbsp;</span>
<a href="https://github.com/Charles7c/continew-admin" target="_blank">{{ $t('title') }}</a>
<span>&nbsp;&nbsp;</span>
<a href="https://beian.miit.gov.cn" target="_blank">津ICP备2022005864号-2</a>
</a-layout-footer>
</template>

View File

@@ -190,7 +190,7 @@
import useLocale from '@/hooks/locale';
import useUser from '@/hooks/user';
import Menu from '@/components/menu/index.vue';
import getAvatar from "@/utils/avatar";
import getAvatar from '@/utils/avatar';
import MessageBox from '../message-box/index.vue';
const appStore = useAppStore();

View File

@@ -1,4 +1,4 @@
import axios, { Axios, AxiosResponse, AxiosRequestConfig } from "axios";
import axios, { Axios, AxiosResponse, AxiosRequestConfig } from 'axios';
declare module "axios" {
interface AxiosResponse<T = any> {

View File

@@ -1,5 +1,5 @@
import { useRouter } from 'vue-router';
import { useI18n } from "vue-i18n";
import { useI18n } from 'vue-i18n';
import { Message } from '@arco-design/web-vue';
import { useLoginStore } from '@/store';

View File

@@ -1,11 +1,11 @@
import { defineStore } from 'pinia';
import {
getImageCaptcha as getCaptcha,
login as userLogin,
logout as userLogout,
getUserInfo,
LoginReq,
} from '@/api/auth/login';
import { getImageCaptcha as getCaptcha } from '@/api/common/captcha';
import { setToken, clearToken } from '@/utils/auth';
import { removeRouteListener } from '@/utils/route-listener';
import { UserState } from './types';

View File

@@ -29,7 +29,7 @@
<script lang="ts" setup>
import { useLoginStore } from '@/store';
import getAvatar from "@/utils/avatar";
import getAvatar from '@/utils/avatar';
const userInfo = useLoginStore();
</script>

View File

@@ -37,7 +37,7 @@
:placeholder="$t('login.form.placeholder.password')"
size="large"
allow-clear
max-length="50"
max-length="32"
>
<template #prefix>
<icon-lock />
@@ -82,13 +82,13 @@
</template>
<script lang="ts" setup>
import { ref, reactive, computed, onMounted } from "vue";
import { ref, reactive, computed, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { FieldRule, Message } from "@arco-design/web-vue";
import { FieldRule, Message } from '@arco-design/web-vue';
import { ValidatedError } from '@arco-design/web-vue/es/form/interface';
import { useI18n } from 'vue-i18n';
// import debug from '@/utils/env';
import { encryptByRsa } from "@/utils/encrypt";
import { encryptByRsa } from '@/utils/encrypt';
import { useStorage } from '@vueuse/core';
import { useLoginStore } from '@/store';
import useLoading from '@/hooks/loading';

View File

@@ -53,14 +53,14 @@
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useLoginStore } from '@/store';
import { updateBasicInfo } from '@/api/system/user-center';
import useLoading from '@/hooks/loading';
import { FormInstance } from '@arco-design/web-vue/es/form';
import { BasicInfoModel } from '@/api/system/user-center';
import { FieldRule, Message } from "@arco-design/web-vue";
import { FieldRule, Message } from '@arco-design/web-vue';
const { t } = useI18n();
const { loading, setLoading } = useLoading();
@@ -84,8 +84,8 @@
// 保存
const save = async () => {
const errors = await formRef.value?.validate();
if (loading.value) return;
const errors = await formRef.value?.validate();
if (!errors) {
setLoading(true);
try {

View File

@@ -15,18 +15,206 @@
</a-typography-paragraph>
</div>
<div class="operation">
<a-link>
<a-link @click="toUpdate">
{{ $t('userCenter.securitySettings.button.update') }}
</a-link>
</div>
</template>
</a-list-item-meta>
<a-modal
v-model:visible="visible"
:title="$t('userCenter.securitySettings.updateEmail.modal.title')"
:mask-closable="false"
@cancel="handleCancel"
@before-ok="handleUpdate"
>
<a-form ref="formRef" :model="formData" :rules="rules">
<a-form-item
field="newEmail"
:validate-trigger="['change', 'blur']"
:label="$t('userCenter.securitySettings.updateEmail.form.label.newEmail')"
>
<a-input
v-model="formData.newEmail"
:placeholder="$t('userCenter.securitySettings.updateEmail.form.placeholder.newEmail')"
size="large"
allow-clear
>
</a-input>
</a-form-item>
<a-form-item
field="captcha"
:validate-trigger="['change', 'blur']"
:label="$t('userCenter.securitySettings.updateEmail.form.label.captcha')"
>
<a-input
v-model="formData.captcha"
:placeholder="$t('userCenter.securitySettings.updateEmail.form.placeholder.captcha')"
size="large"
style="width: 80%"
allow-clear
max-length="6"
>
</a-input>
<a-button
class="captcha-btn"
type="primary"
size="large"
:loading="captchaLoading"
:disabled="captchaDisable"
@click="sendCaptcha"
>
{{ captchaBtnName }}
</a-button>
</a-form-item>
<a-form-item
field="currentPassword"
:validate-trigger="['change', 'blur']"
:label="$t('userCenter.securitySettings.updateEmail.form.label.currentPassword')"
>
<a-input-password
v-model="formData.currentPassword"
:placeholder="$t('userCenter.securitySettings.updateEmail.form.placeholder.currentPassword')"
size="large"
allow-clear
max-length="32"
>
</a-input-password>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useLoginStore } from '@/store';
import { FormInstance } from '@arco-design/web-vue/es/form';
import useLoading from '@/hooks/loading';
import { FieldRule, Message } from '@arco-design/web-vue';
import { getMailCaptcha } from '@/api/common/captcha';
import { updateEmail } from '@/api/system/user-center';
import { encryptByRsa } from '@/utils/encrypt';
const { t } = useI18n();
const { loading, setLoading } = useLoading();
const loginStore = useLoginStore();
const visible = ref(false);
const captchaBtnNameKey = ref('userCenter.securitySettings.updateEmail.form.sendCaptcha');
const captchaBtnName = computed(() => t(captchaBtnNameKey.value));
const captchaLoading = ref(false);
const captchaDisable = ref(false);
const captchaTime = ref(60);
const captchaTimer = ref();
const formRef = ref<FormInstance>();
const formData = reactive({
newEmail: '',
captcha: '',
currentPassword: '',
});
const rules = computed((): Record<string, FieldRule[]> => {
return {
newEmail: [
{ required: true, message: t('userCenter.securitySettings.updateEmail.form.error.required.newEmail') },
{ type: 'email', message: t('userCenter.securitySettings.updateEmail.form.error.match.newEmail') },
{
validator: (value, callback) => {
if (value === loginStore.email) {
callback(t('userCenter.securitySettings.updateEmail.form.error.validator.newEmail'))
} else {
callback()
}
}
}
],
captcha: [
{ required: true, message: t('userCenter.securitySettings.updateEmail.form.error.required.captcha') }
],
currentPassword: [
{ required: true, message: t('userCenter.securitySettings.updateEmail.form.error.required.currentPassword') }
]
}
});
// 重置验证码相关
const resetCaptcha = () => {
window.clearInterval(captchaTimer.value);
captchaTime.value = 60;
captchaBtnNameKey.value = 'userCenter.securitySettings.updateEmail.form.sendCaptcha';
captchaDisable.value = false;
}
// 发送验证码
const sendCaptcha = async () => {
if (captchaLoading.value) return;
const errors = await formRef.value?.validateField('newEmail');
if (errors) return;
captchaLoading.value = true;
captchaBtnNameKey.value = 'userCenter.securitySettings.updateEmail.form.loading.sendCaptcha';
try {
const res = await getMailCaptcha({
email: formData.newEmail
});
if (res.success) {
captchaLoading.value = false;
captchaDisable.value = true;
captchaBtnNameKey.value = `${t('userCenter.securitySettings.updateEmail.form.reSendCaptcha')}(${captchaTime.value -= 1}s)`;
Message.success(res.msg);
captchaTimer.value = window.setInterval(function() {
captchaTime.value -= 1;
captchaBtnNameKey.value = `${t('userCenter.securitySettings.updateEmail.form.reSendCaptcha')}(${captchaTime.value}s)`;
if (captchaTime.value < 0) {
window.clearInterval(captchaTimer.value);
captchaTime.value = 60;
captchaBtnNameKey.value = t('userCenter.securitySettings.updateEmail.form.reSendCaptcha');
captchaDisable.value = false;
}
}, 1000)
}
} catch (err) {
resetCaptcha();
captchaLoading.value = false;
console.log((err as Error));
}
};
// 确定修改
const handleUpdate = async () => {
if (loading.value) return false;
const errors = await formRef.value?.validate();
if (errors) return false;
setLoading(true);
try {
const res = await updateEmail({
newEmail: formData.newEmail,
captcha: formData.captcha,
currentPassword: encryptByRsa(formData.currentPassword) || '',
});
await loginStore.getInfo();
if (res.success) Message.success(res.msg);
} finally {
setLoading(false);
}
return true;
};
// 取消修改
const handleCancel = () => {
visible.value = false;
formRef.value?.resetFields();
resetCaptcha();
};
// 打开修改窗口
const toUpdate = () => {
visible.value = true;
};
</script>
<style scoped lang="less"></style>
<style scoped lang="less">
.captcha-btn {
margin-left: 5px;
}
</style>

View File

@@ -22,12 +22,14 @@
</template>
</a-list-item-meta>
<a-modal v-model:visible="visible" :title="$t('userCenter.securitySettings.updatePwd.modal.title')" @cancel="handleCancel" @before-ok="handleUpdate">
<a-form
ref="formRef"
:model="formData"
:rules="rules"
>
<a-modal
v-model:visible="visible"
:title="$t('userCenter.securitySettings.updatePwd.modal.title')"
:mask-closable="false"
@cancel="handleCancel"
@before-ok="handleUpdate"
>
<a-form ref="formRef" :model="formData" :rules="rules">
<a-form-item
field="oldPassword"
:validate-trigger="['change', 'blur']"
@@ -38,7 +40,7 @@
:placeholder="$t('userCenter.securitySettings.updatePwd.form.placeholder.oldPassword')"
size="large"
allow-clear
max-length="50"
max-length="32"
>
</a-input-password>
</a-form-item>
@@ -52,7 +54,7 @@
:placeholder="$t('userCenter.securitySettings.updatePwd.form.placeholder.newPassword')"
size="large"
allow-clear
max-length="50"
max-length="32"
>
</a-input-password>
</a-form-item>
@@ -66,7 +68,7 @@
:placeholder="$t('userCenter.securitySettings.updatePwd.form.placeholder.rePassword')"
size="large"
allow-clear
max-length="50"
max-length="32"
>
</a-input-password>
</a-form-item>
@@ -75,14 +77,14 @@
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from "vue";
import { useI18n } from "vue-i18n";
import { ref, reactive, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useLoginStore } from '@/store';
import { FormInstance } from "@arco-design/web-vue/es/form";
import useLoading from "@/hooks/loading";
import { FieldRule, Message } from "@arco-design/web-vue";
import { updatePassword } from "@/api/system/user-center";
import { encryptByRsa } from "@/utils/encrypt";
import { FormInstance } from '@arco-design/web-vue/es/form';
import useLoading from '@/hooks/loading';
import { FieldRule, Message } from '@arco-design/web-vue';
import { updatePassword } from '@/api/system/user-center';
import { encryptByRsa } from '@/utils/encrypt';
const { t } = useI18n();
const { loading, setLoading } = useLoading();
@@ -129,8 +131,8 @@
// 确定修改
const handleUpdate = async () => {
const errors = await formRef.value?.validate();
if (loading.value) return false;
const errors = await formRef.value?.validate();
if (errors) return false;
setLoading(true);
try {

View File

@@ -61,8 +61,8 @@
} from '@arco-design/web-vue/es/upload/interfaces';
import { useLoginStore } from '@/store';
import { uploadAvatar } from '@/api/system/user-center';
import getAvatar from "@/utils/avatar";
import { Message } from "@arco-design/web-vue";
import getAvatar from '@/utils/avatar';
import { Message } from '@arco-design/web-vue';
const loginStore = useLoginStore();
const avatar = {

View File

@@ -63,5 +63,23 @@ export default {
'userCenter.securitySettings.updateEmail.placeholder.error.email':
'You have not set a mailbox yet. The mailbox binding can be used to retrieve passwords and receive notifications.',
'userCenter.securitySettings.updateEmail.modal.title': 'Update email',
'userCenter.securitySettings.updateEmail.form.label.newEmail': 'New email',
'userCenter.securitySettings.updateEmail.form.label.captcha': 'Captcha',
'userCenter.securitySettings.updateEmail.form.label.currentPassword': 'Current password',
'userCenter.securitySettings.updateEmail.form.sendCaptcha': 'Send captcha',
'userCenter.securitySettings.updateEmail.form.reSendCaptcha': 'Resend captcha',
'userCenter.securitySettings.updateEmail.form.loading.sendCaptcha': 'Sending...',
'userCenter.securitySettings.updateEmail.form.placeholder.newEmail': 'Please enter new email',
'userCenter.securitySettings.updateEmail.form.placeholder.captcha': 'Please enter email captcha',
'userCenter.securitySettings.updateEmail.form.placeholder.currentPassword': 'Please enter current password',
'userCenter.securitySettings.updateEmail.form.error.required.newEmail': 'Please enter new email',
'userCenter.securitySettings.updateEmail.form.error.match.newEmail': 'Please enter the correct email',
'userCenter.securitySettings.updateEmail.form.error.validator.newEmail': 'New email cannot be the same as the old email',
'userCenter.securitySettings.updateEmail.form.error.required.captcha': 'Please enter email captcha',
'userCenter.securitySettings.updateEmail.form.error.required.currentPassword': 'Please enter current password',
'userCenter.securitySettings.button.update': 'Update',
};

View File

@@ -63,5 +63,23 @@ export default {
'userCenter.securitySettings.updateEmail.placeholder.error.email':
'您暂未设置邮箱,绑定邮箱可以用来找回密码、接收通知等。',
'userCenter.securitySettings.updateEmail.modal.title': '修改邮箱',
'userCenter.securitySettings.updateEmail.form.label.newEmail': '新邮箱',
'userCenter.securitySettings.updateEmail.form.label.captcha': '验证码',
'userCenter.securitySettings.updateEmail.form.label.currentPassword': '当前密码',
'userCenter.securitySettings.updateEmail.form.sendCaptcha': '发送验证码',
'userCenter.securitySettings.updateEmail.form.reSendCaptcha': '重新发送',
'userCenter.securitySettings.updateEmail.form.loading.sendCaptcha': '发送中...',
'userCenter.securitySettings.updateEmail.form.placeholder.newEmail': '请输入新邮箱',
'userCenter.securitySettings.updateEmail.form.placeholder.captcha': '请输入邮箱验证码',
'userCenter.securitySettings.updateEmail.form.placeholder.currentPassword': '请输入当前密码',
'userCenter.securitySettings.updateEmail.form.error.required.newEmail': '请输入新邮箱',
'userCenter.securitySettings.updateEmail.form.error.match.newEmail': '请输入正确的邮箱',
'userCenter.securitySettings.updateEmail.form.error.validator.newEmail': '新邮箱不能与当前邮箱相同',
'userCenter.securitySettings.updateEmail.form.error.required.captcha': '请输入邮箱验证码',
'userCenter.securitySettings.updateEmail.form.error.required.currentPassword': '请输入当前密码',
'userCenter.securitySettings.button.update': '修改',
};