page: 📄 用户可以用邮箱或者用户名登录,修改部分缺陷

This commit is contained in:
Bunny 2024-10-27 17:27:21 +08:00
parent 35957e7b7d
commit f2c809a142
19 changed files with 280 additions and 123 deletions

View File

@ -57,7 +57,7 @@ onMounted(() => {
<el-image :src="userinfo.avatar" style="width: 100px; height: 100px" />
</el-descriptions-item>
<el-descriptions-item :label="$t('username')" :width="100">{{ userinfo.username }}</el-descriptions-item>
<el-descriptions-item :label="$t('nickName')" :width="100">{{ userinfo.nickName }}</el-descriptions-item>
<el-descriptions-item :label="$t('nickname')" :width="100">{{ userinfo.nickname }}</el-descriptions-item>
<el-descriptions-item :label="$t('email')"> {{ userinfo.email }}</el-descriptions-item>
<el-descriptions-item :label="$t('phone')">{{ userinfo.phone }}</el-descriptions-item>
@ -100,7 +100,7 @@ onMounted(() => {
<el-descriptions-item :label="$t('username')" :width="100">
<el-skeleton :rows="1" animated />
</el-descriptions-item>
<el-descriptions-item :label="$t('nickName')" :width="100">
<el-descriptions-item :label="$t('nickname')" :width="100">
<el-skeleton :rows="1" animated />
</el-descriptions-item>

View File

@ -27,7 +27,7 @@ export const useAdminUserStore = defineStore('adminUserStore', {
// 用户名
username: undefined,
// 昵称
nickName: undefined,
nickname: undefined,
// 邮箱
email: undefined,
// 手机号

View File

@ -49,9 +49,8 @@ export const useUserStore = defineStore({
return false;
},
/** 前端登出(不调用接口) */
/** 前端登出 */
async logOut() {
// 登出
const result = await fetchLogout();
if (result.code == 200) {
this.username = '';
@ -83,7 +82,9 @@ export const useUserStore = defineStore({
async getUserinfo() {
const result = await fetchGetUserinfo();
if (result.code === 200) {
return result.data;
const data = result.data;
setToken(data);
return data;
}
return {};
},

View File

@ -47,7 +47,11 @@ const onSubmit = async (formEl: FormInstance) => {
const avatar = uploadAvatarSrc.value;
if (avatar) userInfos.avatar = avatar;
await adminUserStore.updateAdminUserByLocalUser(userInfos);
//
const result = await adminUserStore.updateAdminUserByLocalUser(userInfos);
if (!result) return;
//
await onSearchByUserinfo();
} else {
message($t('required_fields'), { type: 'warning' });
@ -81,12 +85,12 @@ onMounted(() => {
<!-- 用户名 -->
<el-form-item :label="$t('adminUser_username')" prop="username">
<el-input v-model="userInfos.username" :placeholder="$t('adminUser_username')" autocomplete="off" type="text" />
<el-input v-model="userInfos.username" :placeholder="$t('adminUser_username')" autocomplete="off" disabled type="text" />
</el-form-item>
<!-- 昵称 -->
<el-form-item :label="$t('adminUser_nickName')" prop="nickName">
<el-input v-model="userInfos.nickName" :placeholder="$t('adminUser_nickName')" autocomplete="off" type="text" />
<el-form-item :label="$t('adminUser_nickname')" prop="nickname">
<el-input v-model="userInfos.nickname" :placeholder="$t('adminUser_nickname')" autocomplete="off" type="text" />
</el-form-item>
<!-- 邮箱 -->

View File

@ -13,8 +13,8 @@ export const columns: TableColumnList = [
{ type: 'index', index: (index: number) => index + 1, label: '序号', width: 60 },
// 用户名
{ label: $t('userLoginLog_username'), prop: 'username', width: 180 },
// // 登录Ip
// { label: $t('userLoginLog_ipAddress'), prop: 'ipAddress', width: 140 },
// 登录Ip
{ label: $t('userLoginLog_ipAddress'), prop: 'ipAddress', width: 140 },
// 登录Ip归属地
{ label: $t('userLoginLog_ipRegion'), prop: 'ipRegion' },
// // 登录时代理
@ -35,8 +35,7 @@ export const columns: TableColumnList = [
// 修改用户信息规则校验
export const rules = reactive<FormRules<any>>({
username: [{ required: true, message: '昵称必填', trigger: 'blur' }],
nickName: [{ required: true, message: '昵称必填', trigger: 'blur' }],
nickname: [{ required: true, message: '昵称必填', trigger: 'blur' }],
email: [{ required: true, message: '昵称必填', trigger: 'blur' }],
});

View File

@ -12,7 +12,6 @@ export const uploadAvatarSrc = ref();
// 剪裁头像是否显示
export const isShow = ref(false);
const userStore = useUserStore();
// 用户信息内容
@ -23,7 +22,7 @@ export const userInfos = reactive({
email: '',
phone: '',
summary: '',
nickName: '',
nickname: '',
password: '',
sex: '',
});
@ -31,7 +30,15 @@ export const userInfos = reactive({
/** 获取用户信息内容 */
export const onSearchByUserinfo = async () => {
const data = await userStore.getUserinfo();
Object.assign(userInfos, data);
userInfos.summary = data.personDescription;
userInfos.avatar = data.avatar;
userInfos.username = data.username;
userInfos.nickname = data.nickname;
userInfos.email = data.email;
userInfos.phone = data.phone;
userInfos.nickname = data.nickname;
userInfos.password = data.password;
userInfos.sex = data.sex;
};
/** 修改头像 */

View File

@ -2,22 +2,20 @@
import Motion from './utils/motion';
import { useNav } from '@/layout/hooks/useNav';
import { useLayout } from '@/layout/hooks/useLayout';
import { avatar, bg, illustration } from './utils/static';
import bg from '@/assets/login/bg.png';
import avatar from '@/assets/login/avatar.svg?component';
import illustration from '@/assets/login/illustration.svg?component';
import { onMounted, toRaw } from 'vue';
import { useTranslationLang } from '@/layout/hooks/useTranslationLang';
import { useDataThemeChange } from '@/layout/hooks/useDataThemeChange';
import dayIcon from '@/assets/svg/day.svg?component';
import darkIcon from '@/assets/svg/dark.svg?component';
import globalization from '@/assets/svg/globalization.svg?component';
import Check from '@iconify-icons/ep/check';
import LoginForm from '@/views/login/login-form.vue';
import LoginEmail from '@/views/login/login-email.vue';
import { userI18nTypeStore } from '@/store/i18n/i18nType';
defineOptions({
name: 'Login',
});
import { currentPage } from '@/views/login/utils/hooks';
const { initStorage } = useLayout();
initStorage();
@ -73,7 +71,8 @@ onMounted(() => {
</Motion>
<!-- 登录表单 -->
<login-form />
<login-form v-if="currentPage === 0" />
<login-email v-if="currentPage === 1" />
</div>
</div>
</div>

View File

@ -0,0 +1,155 @@
<script lang="ts" setup>
import { emailRules } from '@/views/login/utils/rule';
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import User from '@iconify-icons/ri/user-3-fill';
import Lock from '@iconify-icons/ri/lock-fill';
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useUserStore } from '@/store/system/user';
import { message } from '@/utils/message';
import { getTopMenu, initRouter } from '@/router/utils';
import Motion from './utils/motion';
import { ElMessage, FormInstance } from 'element-plus';
import { onBack } from '@/views/login/utils/hooks';
const router = useRouter();
const userStore = useUserStore();
const ruleFormRef = ref<FormInstance>();
const loading = ref(false);
const sendSecond = ref(60);
const timer = ref(null);
const { t } = useI18n();
const ruleForm = reactive({
username: '1319900154@qq.com',
password: 'admin123',
emailCode: '1',
});
/**
* * 发送邮箱验证码
*/
const onSendEmailCode = async () => {
//
if (ruleForm.username === '' || ruleForm.username === void 0) {
message('请先填写邮箱地址', { type: 'warning' });
return false;
}
const result = await userStore.postEmailCode(ruleForm.username);
if (result) {
//
onSendEmailTimer();
}
};
/**
* * 发送邮箱倒计时点击
*/
const onSendEmailTimer = () => {
//
timer.value = setInterval(() => {
// 0
if (sendSecond.value <= 0) {
clearInterval(timer.value);
timer.value = null;
sendSecond.value = 60;
return;
}
//
sendSecond.value--;
}, 1000);
};
/**
* 登录
* @param formEl
*/
const onLogin = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
loading.value = true;
//
await formEl.validate(async valid => {
if (valid) {
const result = await userStore.loginByUsername(ruleForm);
if (result) {
//
await initRouter();
router.push(getTopMenu(true).path).then(() => {
ElMessage.closeAll();
message(t('login.loginSuccess'), { type: 'success' });
});
}
}
});
loading.value = false;
};
/** 使用公共函数,避免`removeEventListener`失效 */
function onkeypress({ code }: KeyboardEvent) {
if (['Enter', 'NumpadEnter'].includes(code)) {
onLogin(ruleFormRef.value);
}
}
onMounted(() => {
window.document.addEventListener('keypress', onkeypress);
});
onBeforeUnmount(() => {
window.document.removeEventListener('keypress', onkeypress);
});
</script>
<template>
<el-form ref="ruleFormRef" :model="ruleForm" :rules="emailRules" size="large">
<Motion :delay="100">
<el-form-item prop="username">
<el-input v-model="ruleForm.username" :placeholder="t('login.username')" :prefix-icon="useRenderIcon(User)" clearable />
</el-form-item>
</Motion>
<Motion :delay="150">
<el-form-item prop="password">
<el-input v-model="ruleForm.password" :placeholder="t('login.password')" :prefix-icon="useRenderIcon(Lock)" clearable show-password />
</el-form-item>
</Motion>
<Motion :delay="150">
<el-form-item prop="emailCode">
<el-input v-model="ruleForm.emailCode" :placeholder="t('login.emailCode')" :prefix-icon="useRenderIcon('ic:outline-email')" clearable @keydown.enter="onLogin(ruleFormRef)">
<template v-slot:append>
<el-link v-if="sendSecond === 60" :underline="false" class="px-2" type="primary" @click="onSendEmailCode">
{{ t('login.getEmailCode') }}
</el-link>
<el-link v-else :underline="false" class="px-2" type="primary">
{{ sendSecond }}
{{ t('login.getCodeInfo') }}
</el-link>
</template>
</el-input>
<el-checkbox v-model="userStore.isRemembered">
<el-text size="small" type="primary">{{ userStore.readMeDay }}天免登录(邮箱验证码随便输入,后端校验验证码已注释) </el-text>
</el-checkbox>
</el-form-item>
</Motion>
<Motion :delay="250">
<el-button :loading="loading" class="w-full" size="default" type="primary" @click="onLogin(ruleFormRef)">
{{ t('login.login') }}
</el-button>
</Motion>
<Motion :delay="200">
<el-form-item>
<el-button class="w-full mt-4" size="default" @click="onBack">
{{ t('login.pureBack') }}
</el-button>
</el-form-item>
</Motion>
</el-form>
</template>

View File

@ -1,5 +1,4 @@
<script lang="ts" setup>
import { loginRules } from '@/views/login/utils/rule';
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import User from '@iconify-icons/ri/user-3-fill';
import Lock from '@iconify-icons/ri/lock-fill';
@ -11,56 +10,20 @@ import { message } from '@/utils/message';
import { getTopMenu, initRouter } from '@/router/utils';
import Motion from './utils/motion';
import { ElMessage, FormInstance } from 'element-plus';
import { currentPage } from '@/views/login/utils/hooks';
import { formRules } from '@/views/login/utils/rule';
const router = useRouter();
const userStore = useUserStore();
const ruleFormRef = ref<FormInstance>();
const loading = ref(false);
const sendSecond = ref(60);
const timer = ref(null);
const { t } = useI18n();
const ruleForm = reactive({
username: '1319900154@qq.com',
username: 'bunny',
password: 'admin123',
emailCode: '1',
});
/**
* * 发送邮箱验证码
*/
const onSendEmailCode = async () => {
//
if (ruleForm.username === '' || ruleForm.username === void 0) {
message('请先填写邮箱地址', { type: 'warning' });
return false;
}
const result = await userStore.postEmailCode(ruleForm.username);
if (result) {
//
onSendEmailTimer();
}
};
/**
* * 发送邮箱倒计时点击
*/
const onSendEmailTimer = () => {
//
timer.value = setInterval(() => {
// 0
if (sendSecond.value <= 0) {
clearInterval(timer.value);
timer.value = null;
sendSecond.value = 60;
return;
}
//
sendSecond.value--;
}, 1000);
};
/**
* 登录
* @param formEl
@ -103,8 +66,8 @@ onBeforeUnmount(() => {
</script>
<template>
<el-form ref="ruleFormRef" :model="ruleForm" :rules="loginRules" size="large">
<Motion :delay="100">
<el-form ref="ruleFormRef" :model="ruleForm" :rules="formRules" size="large">
<Motion>
<el-form-item prop="username">
<el-input v-model="ruleForm.username" :placeholder="t('login.username')" :prefix-icon="useRenderIcon(User)" clearable />
</el-form-item>
@ -113,32 +76,25 @@ onBeforeUnmount(() => {
<Motion :delay="150">
<el-form-item prop="password">
<el-input v-model="ruleForm.password" :placeholder="t('login.password')" :prefix-icon="useRenderIcon(Lock)" clearable show-password />
</el-form-item>
</Motion>
<Motion :delay="150">
<el-form-item prop="emailCode">
<el-input v-model="ruleForm.emailCode" :placeholder="t('login.emailCode')" :prefix-icon="useRenderIcon('ic:outline-email')" clearable @keydown.enter="onLogin(ruleFormRef)">
<template v-slot:append>
<el-link v-if="sendSecond === 60" :underline="false" class="px-2" type="primary" @click="onSendEmailCode">
{{ t('login.getEmailCode') }}
</el-link>
<el-link v-else :underline="false" class="px-2" type="primary">
{{ sendSecond }}
{{ t('login.getCodeInfo') }}
</el-link>
</template>
</el-input>
<el-checkbox v-model="userStore.isRemembered">
<el-text size="small" type="primary">{{ userStore.readMeDay }}天免登录(邮箱验证码随便输入,后端校验验证码已注释) </el-text>
</el-checkbox>
</el-form-item>
</Motion>
<Motion :delay="250">
<el-button :loading="loading" class="w-full mt-4" size="default" type="primary" @click="onLogin(ruleFormRef)">
{{ t('login.login') }}
</el-button>
<Motion :delay="150">
<el-form-item>
<el-button :loading="loading" class="w-full" size="default" type="primary" @click="onLogin(ruleFormRef)">
{{ t('login.login') }}
</el-button>
</el-form-item>
</Motion>
<!-- 邮箱登录 -->
<Motion :delay="300">
<el-form-item>
<el-button class="w-full" size="default" @click="currentPage = 1"> 邮箱登录</el-button>
</el-form-item>
</Motion>
</el-form>
</template>

View File

@ -0,0 +1,9 @@
import { ref } from 'vue';
// 0普通登录1邮箱登录其中0为普通登录
export const currentPage = ref(0);
/** 返回到默认登录页面 */
export const onBack = () => {
currentPage.value = 0;
};

View File

@ -5,8 +5,8 @@ import { $t } from '@/plugins/i18n';
/** 密码正则密码格式应为8-18位数字、字母、符号的任意两种组合 */
export const REGEXP_PWD = /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
/** 登录校验 */
const loginRules = reactive(<FormRules>{
/** 邮箱登录校验 */
export const emailRules = reactive(<FormRules>{
username: [{ required: true, message: $t('login.usernameRegex'), trigger: 'blur' }],
password: [
{
@ -25,4 +25,21 @@ const loginRules = reactive(<FormRules>{
emailCode: [{ required: true, trigger: 'blur', type: 'string' }],
});
export { loginRules };
/** 登录校验 */
export const formRules = reactive(<FormRules>{
username: [{ required: true, message: $t('login.usernameRegex'), trigger: 'blur' }],
password: [
{
validator: (rule, value, callback) => {
if (value === '') {
callback(new Error($t('login.purePassWordReg')));
} else if (!REGEXP_PWD.test(value)) {
callback(new Error($t('login.purePassWordRuleReg')));
} else {
callback();
}
},
trigger: 'blur',
},
],
});

View File

@ -1,5 +0,0 @@
import bg from '@/assets/login/bg.png';
import avatar from '@/assets/login/avatar.svg?component';
import illustration from '@/assets/login/illustration.svg?component';
export { bg, avatar, illustration };

View File

@ -15,7 +15,7 @@ const props = withDefaults(defineProps<FormProps>(), {
//
username: undefined,
//
nickName: undefined,
nickname: undefined,
//
email: undefined,
//
@ -55,8 +55,8 @@ defineExpose({ formRef });
<!-- 昵称 -->
<re-col :sm="24" :value="12" :xs="24">
<el-form-item :label="$t('adminUser_nickName')" prop="nickName">
<el-input v-model="form.nickName" :placeholder="$t('adminUser_nickName')" autocomplete="off" type="text" />
<el-form-item :label="$t('adminUser_nickname')" prop="nickname">
<el-input v-model="form.nickname" :placeholder="$t('adminUser_nickname')" autocomplete="off" type="text" />
</el-form-item>
</re-col>

View File

@ -96,8 +96,8 @@ onMounted(() => {
</el-form-item>
<!-- 查询昵称 -->
<el-form-item :label="$t('adminUser_nickName')" prop="nickName">
<el-input v-model="adminUserStore.form.nickName" :placeholder="`${$t('input')}${$t('adminUser_nickName')}`" class="!w-[180px]" clearable />
<el-form-item :label="$t('adminUser_nickname')" prop="nickname">
<el-input v-model="adminUserStore.form.nickname" :placeholder="`${$t('input')}${$t('adminUser_nickname')}`" class="!w-[180px]" clearable />
</el-form-item>
<!-- 查询邮箱 -->

View File

@ -13,7 +13,7 @@ export const columns: TableColumnList = [
// 状态
{ label: $t('adminUser_status'), prop: 'status', slot: 'status' },
// 昵称
{ label: $t('adminUser_nickName'), prop: 'nickName', width: 260 },
{ label: $t('adminUser_nickname'), prop: 'nickname', width: 260 },
// 邮箱
{ label: $t('adminUser_email'), prop: 'email', width: 260 },
// 手机号

View File

@ -59,7 +59,7 @@ export function onAdd() {
props: {
formInline: {
username: undefined,
nickName: undefined,
nickname: undefined,
email: undefined,
phone: undefined,
password: undefined,
@ -100,7 +100,7 @@ export function onUpdate(row: any) {
props: {
formInline: {
username: row.username,
nickName: row.nickName,
nickname: row.nickname,
email: row.email,
phone: row.phone,
password: row.password,

View File

@ -3,7 +3,7 @@ export interface FormItemProps {
// 用户名
username: string;
// 昵称
nickName: string;
nickname: string;
// 邮箱
email: string;
// 手机号

View File

@ -6,6 +6,7 @@ import { $t } from '@/plugins/i18n';
const props = defineProps({
routerId: { type: String as PropType<String> },
warning: { type: String as PropType<String> },
});
const roleStore = useRoleStore();
@ -36,7 +37,8 @@ defineExpose({ assignRoles });
</script>
<template>
<div class="flex justify-center">
<div class="flex flex-col items-center">
<el-text v-show="warning" class="mx-1" type="warning">{{ warning }}</el-text>
<el-transfer
v-model="assignRoles"
:button-texts="[$t('take_back'), $t('add')]"

View File

@ -1,6 +1,6 @@
import editForm from '../menu-dialog.vue';
import { $t } from '@/plugins/i18n';
import { addDialog } from '@/components/BaseDialog/index';
import { addDialog, closeAllDialog } from '@/components/BaseDialog/index';
import { h, ref } from 'vue';
import type { FormItemProps } from './types';
import { cloneDeep, deviceDetection } from '@pureadmin/utils';
@ -8,6 +8,7 @@ import { userMenuStore } from '@/store/system/menu';
import AssignRouterToRole from '@/views/system/menu/assign-router-to-role.vue';
import { messageBox } from '@/utils/message';
import { formatHigherMenuOptions } from '@/views/system/menu/utils/columns';
import { ElText } from 'element-plus';
// 用户是否停用加载集合
export const switchLoadMap = ref({});
@ -234,6 +235,7 @@ export const assignBatchRolesToRouter = () => {
draggable: true,
closeOnClickModal: false,
fullscreenIcon: true,
props: { warning: $t('assignBatchRolesToRouterTip') },
contentRenderer: () => <AssignRouterToRole ref={assignRouterToRolesRef} />,
beforeSure: async (done: any) => {
// 表格功能
@ -253,23 +255,34 @@ export const assignBatchRolesToRouter = () => {
/** 清除选中所以角色 */
export const clearAllRolesSelect = async () => {
// 表格功能
const { clearSelection } = tableRef.value.getTableRef();
const confirm = await messageBox({
addDialog({
title: $t('batchUpdates'),
showMessage: false,
confirmMessage: undefined,
cancelMessage: $t('cancel'),
width: '35%',
draggable: true,
closeOnClickModal: false,
fullscreenIcon: true,
contentRenderer: () => <ElText type={'warning'}>{$t('clearAllRolesSelectTip')}</ElText>,
beforeSure: async () => {
// 表格功能
const { clearSelection } = tableRef.value.getTableRef();
addDialog({
title: $t('doubleCheck'),
width: '30%',
draggable: true,
closeOnClickModal: false,
fullscreenIcon: true,
contentRenderer: () => <ElText type={'warning'}>{$t('clearAllRolesSelectTip')}</ElText>,
beforeSure: async () => {
// 清除所有角色
const result = await menuStore.clearAllRolesSelect(selectIds.value);
// 更新成功关闭弹窗
if (!result) return;
clearSelection();
closeAllDialog();
},
});
},
});
// 取消修改
if (!confirm) return;
// 分配用户角色
const result = await menuStore.clearAllRolesSelect(selectIds.value);
// 更新成功关闭弹窗
if (!result) return;
clearSelection();
};