feat:新增密码过期修改页面逻辑

This commit is contained in:
秋帆
2024-06-15 14:46:04 +08:00
parent 8bc3992214
commit 921d9c63e9
9 changed files with 567 additions and 65 deletions

View File

@@ -14,6 +14,7 @@ export type FormType =
| 'slider'
| 'cascader'
| 'tree-select'
| 'input-password'
export type ColumnsItemPropsKey =
| keyof A.InputInstance['$props']

View File

@@ -146,32 +146,7 @@ const logout = () => {
})
}
const checkPasswordExpired = () => {
if (!userStore.pwdExpiredShow || !userStore.userInfo.pwdExpired) {
return
}
Modal.confirm({
title: '提示',
content: '密码已过期,需要跳转到修改密码页面?',
hideCancel: false,
closable: true,
onBeforeOk: async () => {
try {
await router.push({ path: '/setting/profile' })
return true
} catch (error) {
return false
}
},
onCancel: () => {
// 当前登录会话不再提示
userStore.pwdExpiredShow = false
}
})
}
onMounted(() => {
checkPasswordExpired()
getMessageCount()
})
</script>

View File

@@ -53,6 +53,11 @@ export const constantRoutes: RouteRecordRaw[] = [
component: () => import('@/views/login/social/index.vue'),
meta: { hidden: true }
},
{
path: '/pwdExpired',
component: () => import('@/views/login/pwdExpired/index.vue'),
meta: { hidden: true }
},
{
path: '/setting',
name: 'Setting',

View File

@@ -1,10 +1,11 @@
import { Message } from '@arco-design/web-vue'
import router from '@/router'
import { useRouteStore, useUserStore } from '@/stores'
import { getToken } from '@/utils/auth'
import { isHttp } from '@/utils/validate'
/** 免登录白名单 */
const whiteList = ['/login', '/social/callback']
const whiteList = ['/login', '/social/callback', '/pwdExpired']
/** 是否已经生成过路由表 */
let hasRouteFlag = false
@@ -25,6 +26,10 @@ router.beforeEach(async (to, from, next) => {
if (!hasRouteFlag) {
try {
await userStore.getInfo()
if (userStore.userInfo.pwdExpired && to.path !== '/pwdExpired') {
Message.warning('密码已过期,请修改密码')
next('/pwdExpired')
}
const accessRoutes = await routeStore.generateRoutes()
accessRoutes.forEach((route) => {
if (!isHttp(route.path)) {

View File

@@ -1,13 +1,6 @@
<template>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col-style="{ display: 'none' }"
:wrapper-col-style="{ flex: 1 }"
size="large"
@submit="handleLogin"
>
<a-form ref="formRef" :model="form" :rules="rules" :label-col-style="{ display: 'none' }"
:wrapper-col-style="{ flex: 1 }" size="large" @submit="handleLogin">
<a-form-item field="username" hide-label>
<a-input v-model="form.username" placeholder="请输入用户名" allow-clear />
</a-form-item>

View File

@@ -0,0 +1,128 @@
<template>
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical" :label-col-style="{ lineHeight: '10px' }"
:wrapper-col-style="{ flex: 1 }" size="large" @submit="onModify">
<a-form-item field="oldPassword" label="当前密码">
<a-input-password v-model="form.oldPassword" placeholder="请输入当前密码" allow-clear />
</a-form-item>
<a-form-item field="newPassword" label="新密码">
<a-input-password v-model="form.newPassword" placeholder="请输入新密码" allow-clear />
</a-form-item>
<a-form-item field="confirmPassword" label="确认密码">
<a-input-password v-model="form.confirmPassword" placeholder="请再次输入新密码" allow-clear />
</a-form-item>
<a-form-item>
<a-space direction="vertical" fill class="w-full">
<a-button class="btn" type="primary" :loading="loading" html-type="submit" size="large" long>
立即修改
</a-button>
</a-space>
</a-form-item>
</a-form>
</template>
<script setup lang="ts">
import { type FormInstance, Message } from '@arco-design/web-vue'
import { updateUserPassword } from '@/apis'
import { encryptByRsa } from '@/utils/encrypt'
interface Form {
oldPassword: string
newPassword: string
confirmPassword?: string
}
const formRef = ref<FormInstance>()
const form = reactive<Form>({
oldPassword: '',
newPassword: '',
confirmPassword: ''
})
const rules: FormInstance['rules'] = {
oldPassword: [
{ required: true, message: '请输入当前密码' }
],
newPassword: [{ required: true, message: '请输入新密码' }, {
validator: (value, cd) => {
return new Promise((resolve) => {
if (value === form.oldPassword) {
cd('新密码不能与旧密码相同')
}
resolve(true)
})
}
}],
confirmPassword: [{ required: true, message: '请再次输入新密码' }, {
validator: (value, cd) => {
return new Promise((resolve) => {
if (value !== form.newPassword) {
cd('两次密码不一致')
}
resolve(true)
})
}
}]
}
const router = useRouter()
const loading = ref(false)
// 登录
const onModify = async () => {
const isInvalid = await formRef.value?.validate()
if (isInvalid) return
try {
loading.value = true
const params = {
oldPassword: encryptByRsa(form.oldPassword) || '',
newPassword: encryptByRsa(form.newPassword) || ''
}
await updateUserPassword(params)
router.push({
path: '/login'
})
Message.success('修改成功')
} catch (error) {
} finally {
loading.value = false
}
}
</script>
<style lang="scss" scoped>
.arco-input-wrapper,
:deep(.arco-select-view-single) {
height: 40px;
border-radius: 4px;
font-size: 13px;
}
.arco-input-wrapper.arco-input-error {
background-color: rgb(var(--danger-1));
border-color: rgb(var(--danger-3));
}
.arco-input-wrapper.arco-input-error:hover {
background-color: rgb(var(--danger-1));
border-color: rgb(var(--danger-6));
}
.arco-input-wrapper :deep(.arco-input) {
font-size: 13px;
color: var(--color-text-1);
}
.arco-input-wrapper:hover {
border-color: rgb(var(--arcoblue-6));
}
.captcha-btn {
height: 40px;
margin-left: 12px;
min-width: 98px;
border-radius: 4px;
}
.btn {
height: 40px;
}
</style>

View File

@@ -0,0 +1,414 @@
<template>
<div class="login pc">
<h3 class="login-logo">
<img v-if="logo" :src="logo" alt="logo" />
<img v-else src="/logo.svg" alt="logo" />
<span>{{ title }}</span>
</h3>
<a-row align="stretch" class="login-box">
<a-col :xs="0" :sm="12" :md="13">
<div class="login-left">
<img class="login-left__img" src="@/assets/images/banner.png" alt="banner" />
</div>
</a-col>
<a-col :xs="24" :sm="12" :md="11">
<div class="login-right">
<a-tabs class="login-right__form">
<template #extra>
<span style="color: red;">密码已过期请修改密码</span>
</template>
<a-tab-pane key="1" title="密码修改">
<span></span>
<ModifyPassword />
</a-tab-pane>
</a-tabs>
</div>
</a-col>
</a-row>
<div v-if="isDesktop" class="footer">
<div class="beian">
<div class="below text">
{{ appStore.getCopyright() }}{{ appStore.getForRecord() ? ` ·
${appStore.getForRecord()}` : '' }}
</div>
</div>
</div>
<GiThemeBtn class="theme-btn" />
<Background />
</div>
<div class="login h5">
<div class="login-logo">
<img v-if="logo" :src="logo" alt="logo" />
<img v-else src="/logo.svg" alt="logo" />
<span>{{ title }}</span>
</div>
<a-row align="stretch" class="login-box">
<a-col :xs="24" :sm="12" :md="11">
<div class="login-right">
<a-tabs class="login-right__form">
<template #extra>
<span style="color: red;">密码已过期请修改密码</span>
</template>
<a-tab-pane key="1" title="密码修改">
<ModifyPassword />
</a-tab-pane>
</a-tabs>
</div>
</a-col>
</a-row>
</div>
</template>
<script setup lang="ts">
import Background from '../components/background/index.vue'
import ModifyPassword from '../components/modifyPassword/index.vue'
import { useAppStore } from '@/stores'
import { useDevice } from '@/hooks'
defineOptions({ name: 'PwdExpired' })
const { isDesktop } = useDevice()
const appStore = useAppStore()
const title = computed(() => appStore.getTitle())
const logo = computed(() => appStore.getLogo())
</script>
<style lang="scss" scoped>
@media screen and (max-width: 570px) {
.pc {
display: none !important;
background-color: white !important;
}
.login {
height: 100%;
display: flex;
flex-direction: column;
justify-content: start;
align-items: center;
background-color: var(--color-bg-5);
color: #121314;
&-logo {
width: 100%;
height: 104px;
font-weight: 700;
font-size: 20px;
line-height: 32px;
display: flex;
padding: 0 20px;
align-items: center;
justify-content: start;
background-image: url('/src/assets/images/login_h5.jpg');
background-size: 100% 100%;
box-sizing: border-box;
img {
width: 34px;
height: 34px;
margin-right: 8px;
}
}
&-box {
width: 100%;
display: flex;
z-index: 999;
}
}
.login-right {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 30px 30px 0;
box-sizing: border-box;
&__title {
color: var(--color-text-1);
font-weight: 500;
font-size: 20px;
line-height: 32px;
margin-bottom: 20px;
}
&__form {
:deep(.arco-tabs-nav-tab) {
display: flex;
justify-content: start;
align-items: center;
}
:deep(.arco-tabs-tab) {
color: var(--color-text-2);
margin: 0 20px 0 0;
}
:deep(.arco-tabs-tab-title) {
font-size: 16px;
font-weight: 500;
line-height: 22px;
}
:deep(.arco-tabs-content) {
margin-top: 10px;
}
:deep(.arco-tabs-tab-active),
:deep(.arco-tabs-tab-title:hover) {
color: rgb(var(--arcoblue-6));
}
:deep(.arco-tabs-nav::before) {
display: none;
}
:deep(.arco-tabs-tab-title:before) {
display: none;
}
}
}
.theme-btn {
position: fixed;
top: 20px;
right: 30px;
z-index: 9999;
}
.footer {
align-items: center;
box-sizing: border-box;
position: absolute;
bottom: 10px;
z-index: 999;
.beian {
.text {
font-size: 12px;
font-weight: 400;
letter-spacing: 0.2px;
line-height: 20px;
text-align: center;
}
.below {
align-items: center;
display: flex;
}
}
}
}
@media screen and (min-width: 571px) {
.h5 {
display: none !important;
}
.login {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: var(--color-bg-5);
&-logo {
position: fixed;
top: 20px;
left: 30px;
z-index: 9999;
color: var(--color-text-1);
font-weight: 500;
font-size: 20px;
line-height: 32px;
margin-bottom: 20px;
display: flex;
justify-content: center;
align-items: center;
img {
width: 34px;
height: 34px;
margin-right: 8px;
}
}
&-box {
width: 86%;
max-width: 850px;
height: 490px;
display: flex;
z-index: 999;
box-shadow: 0 2px 4px 2px rgba(0, 0, 0, 0.08);
}
}
.login-left {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
background: linear-gradient(60deg, rgb(var(--primary-6)), rgb(var(--primary-3)));
&__img {
width: 100%;
position: absolute;
bottom: 0;
right: 0;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
transition: all 0.3s;
object-fit: cover;
}
}
.login-right {
width: 100%;
height: 100%;
background: var(--color-bg-1);
display: flex;
flex-direction: column;
padding: 30px 30px 0;
box-sizing: border-box;
&__title {
color: var(--color-text-1);
font-weight: 500;
font-size: 20px;
line-height: 32px;
margin-bottom: 20px;
}
&__form {
:deep(.arco-tabs-nav-tab) {
display: flex;
// justify-content: center;
align-items: center;
}
:deep(.arco-tabs-tab) {
color: var(--color-text-2);
}
:deep(.arco-tabs-tab-title) {
font-size: 16px;
font-weight: 500;
line-height: 22px;
}
:deep(.arco-tabs-content) {
margin-top: 10px;
}
:deep(.arco-tabs-tab-active),
:deep(.arco-tabs-tab-title:hover) {
color: rgb(var(--arcoblue-6));
}
:deep(.arco-tabs-nav::before) {
display: none;
}
:deep(.arco-tabs-tab-title:before) {
display: none;
}
}
&__oauth {
margin-top: auto;
margin-bottom: 20px;
:deep(.arco-divider-text) {
color: var(--color-text-4);
font-size: 12px;
font-weight: 400;
line-height: 20px;
}
.list {
align-items: center;
display: flex;
justify-content: center;
width: 100%;
.item {
margin-right: 15px;
}
.mode {
color: var(--color-text-2);
font-size: 12px;
font-weight: 400;
line-height: 20px;
padding: 6px 10px;
align-items: center;
border: 1px solid var(--color-border-3);
border-radius: 32px;
box-sizing: border-box;
display: flex;
height: 32px;
justify-content: center;
cursor: pointer;
.icon {
width: 21px;
height: 20px;
}
}
.mode svg {
font-size: 16px;
margin-right: 10px;
}
.mode:hover,
.mode svg:hover {
background: rgba(var(--primary-6), 0.05);
border: 1px solid rgb(var(--primary-3));
color: rgb(var(--arcoblue-6));
}
}
}
}
.theme-btn {
position: fixed;
top: 20px;
right: 30px;
z-index: 9999;
}
.footer {
align-items: center;
box-sizing: border-box;
position: absolute;
bottom: 10px;
z-index: 999;
.beian {
.text {
font-size: 12px;
font-weight: 400;
letter-spacing: 0.2px;
line-height: 20px;
text-align: center;
}
.below {
align-items: center;
display: flex;
}
}
}
}
</style>

View File

@@ -1,35 +1,17 @@
<template>
<a-modal
v-model:visible="visible"
:title="title"
:mask-closable="false"
:esc-to-close="false"
:width="width >= 500 ? 500 : '100%'"
draggable
@before-ok="save"
@close="reset"
>
<a-modal v-model:visible="visible" :title="title" :mask-closable="false" :esc-to-close="false"
:width="width >= 500 ? 500 : '100%'" draggable @before-ok="save" @close="reset">
<GiForm ref="formRef" v-model="form" :options="options" :columns="columns">
<template #captcha>
<a-input v-model="form.captcha" placeholder="请输入验证码" :max-length="6" allow-clear style="flex: 1 1" />
<a-button
class="captcha-btn"
:loading="captchaLoading"
:disabled="captchaDisable"
size="large"
@click="onCaptcha"
>
<a-button class="captcha-btn" :loading="captchaLoading" :disabled="captchaDisable" size="large"
@click="onCaptcha">
{{ captchaBtnName }}
</a-button>
</template>
</GiForm>
<Verify
ref="VerifyRef"
:captcha-type="captchaType"
:mode="captchaMode"
:img-size="{ width: '330px', height: '155px' }"
@success="getCaptcha"
/>
<Verify ref="VerifyRef" :captcha-type="captchaType" :mode="captchaMode"
:img-size="{ width: '330px', height: '155px' }" @success="getCaptcha" />
</a-modal>
</template>
@@ -40,7 +22,7 @@ import { getEmailCaptcha, updateUserEmail, updateUserPassword } from '@/apis'
import { encryptByRsa } from '@/utils/encrypt'
import { useUserStore } from '@/stores'
import { type Columns, GiForm } from '@/components/GiForm'
import { type Columns, GiForm, type Options } from '@/components/GiForm'
import { useForm } from '@/hooks'
import * as Regexp from '@/utils/regexp'

View File

@@ -34,7 +34,8 @@
</a-form-item>
<a-form-item field="PASSWORD_REPETITION_TIMES" :label="securityConfig.PASSWORD_REPETITION_TIMES.name"
:help="securityConfig.PASSWORD_REPETITION_TIMES.description" hide-asterisk>
<a-input-number v-model="form.PASSWORD_REPETITION_TIMES" class="input-width" :precision="0" :min="3" :max="32">
<a-input-number v-model="form.PASSWORD_REPETITION_TIMES" class="input-width" :precision="0" :min="3"
:max="32">
<template #append></template>
</a-input-number>
</a-form-item>
@@ -49,10 +50,8 @@
<template #unchecked></template>
</a-switch>
</a-form-item>
<a-form-item field="PASSWORD_REQUIRE_SYMBOLS"
:label="securityConfig.PASSWORD_REQUIRE_SYMBOLS.name">
<a-switch v-model="form.PASSWORD_REQUIRE_SYMBOLS" type="round" :checked-value="1"
:unchecked-value="0">
<a-form-item field="PASSWORD_REQUIRE_SYMBOLS" :label="securityConfig.PASSWORD_REQUIRE_SYMBOLS.name">
<a-switch v-model="form.PASSWORD_REQUIRE_SYMBOLS" type="round" :checked-value="1" :unchecked-value="0">
<template #checked></template>
<template #unchecked></template>
</a-switch>