mirror of
https://github.com/continew-org/continew-admin.git
synced 2025-09-12 03:00:53 +08:00
新增:新增修改邮箱功能,并优化部分以往代码(引入 spring-boot-starter-mail 用于发送邮件验证码)
This commit is contained in:
@@ -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;
|
||||
|
22
continew-admin-ui/src/api/common/captcha.ts
Normal file
22
continew-admin-ui/src/api/common/captcha.ts
Normal 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);
|
||||
},
|
||||
});
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -2,6 +2,8 @@
|
||||
<a-layout-footer class="footer">
|
||||
{{ `Copyright © 2022-${new Date().getFullYear()} Charles7c` }}
|
||||
<span> ⋅ </span>
|
||||
<a href="https://github.com/Charles7c/continew-admin" target="_blank">{{ $t('title') }}</a>
|
||||
<span> ⋅ </span>
|
||||
<a href="https://beian.miit.gov.cn" target="_blank">津ICP备2022005864号-2</a>
|
||||
</a-layout-footer>
|
||||
</template>
|
||||
|
@@ -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();
|
||||
|
2
continew-admin-ui/src/hooks/axios.d.ts
vendored
2
continew-admin-ui/src/hooks/axios.d.ts
vendored
@@ -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> {
|
||||
|
@@ -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';
|
||||
|
@@ -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';
|
||||
|
@@ -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>
|
||||
|
@@ -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';
|
||||
|
@@ -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 {
|
||||
|
@@ -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>
|
||||
|
@@ -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 {
|
||||
|
@@ -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 = {
|
||||
|
@@ -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',
|
||||
};
|
||||
|
@@ -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': '修改',
|
||||
};
|
||||
|
Reference in New Issue
Block a user