fix(auth): 完善权限控制与强制改密流程
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useLoginStore } from '@/stores/login'
|
||||
|
||||
const router = useRouter()
|
||||
const loginStore = useLoginStore()
|
||||
|
||||
const currentPassword = ref('')
|
||||
const newPassword = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const errorMessage = ref('')
|
||||
const isSubmitting = ref(false)
|
||||
|
||||
function validatePassword() {
|
||||
if (!currentPassword.value || !newPassword.value || !confirmPassword.value) {
|
||||
return '请完整填写当前密码、新密码和确认密码'
|
||||
}
|
||||
if (newPassword.value.length < 12 || newPassword.value.length > 128) {
|
||||
return '新密码长度必须为 12 至 128 位'
|
||||
}
|
||||
if (
|
||||
!/[A-Z]/.test(newPassword.value) ||
|
||||
!/[a-z]/.test(newPassword.value) ||
|
||||
!/\d/.test(newPassword.value) ||
|
||||
!/[^A-Za-z0-9]/.test(newPassword.value)
|
||||
) {
|
||||
return '新密码必须包含大写字母、小写字母、数字和特殊字符'
|
||||
}
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
return '两次输入的新密码不一致'
|
||||
}
|
||||
if (newPassword.value === currentPassword.value) {
|
||||
return '新密码不能与当前密码相同'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
const validationMessage = validatePassword()
|
||||
if (validationMessage) {
|
||||
errorMessage.value = validationMessage
|
||||
return
|
||||
}
|
||||
|
||||
errorMessage.value = ''
|
||||
isSubmitting.value = true
|
||||
try {
|
||||
await loginStore.changePassword({
|
||||
currentPassword: currentPassword.value,
|
||||
newPassword: newPassword.value,
|
||||
})
|
||||
await router.replace({ name: 'login', query: { passwordChanged: '1' } })
|
||||
} catch (error: unknown) {
|
||||
errorMessage.value =
|
||||
error instanceof Error && error.message ? error.message : '密码修改失败,请稍后重试'
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
try {
|
||||
await loginStore.logout()
|
||||
} catch {
|
||||
loginStore.clearSession()
|
||||
}
|
||||
await router.replace({ name: 'login' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="password-page">
|
||||
<form class="password-card" novalidate @submit.prevent="handleSubmit">
|
||||
<div class="password-brand">医签通</div>
|
||||
<h1>首次登录,请修改密码</h1>
|
||||
<p class="password-description">为保护患者信息,设置新密码后需要重新登录。</p>
|
||||
|
||||
<label>
|
||||
<span>当前密码</span>
|
||||
<input v-model="currentPassword" type="password" autocomplete="current-password" />
|
||||
</label>
|
||||
<label>
|
||||
<span>新密码</span>
|
||||
<input v-model="newPassword" type="password" autocomplete="new-password" />
|
||||
</label>
|
||||
<label>
|
||||
<span>确认新密码</span>
|
||||
<input v-model="confirmPassword" type="password" autocomplete="new-password" />
|
||||
</label>
|
||||
|
||||
<p class="password-policy">12–128 位,须同时包含大小写字母、数字和特殊字符。</p>
|
||||
<p v-if="errorMessage" class="password-error" role="alert">{{ errorMessage }}</p>
|
||||
|
||||
<button class="password-submit" type="submit" :disabled="isSubmitting">
|
||||
{{ isSubmitting ? '修改中…' : '修改密码并重新登录' }}
|
||||
</button>
|
||||
<button class="password-logout" type="button" :disabled="isSubmitting" @click="handleLogout">
|
||||
退出当前账号
|
||||
</button>
|
||||
</form>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.password-page {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: var(--login-gradient);
|
||||
}
|
||||
|
||||
.password-card {
|
||||
width: min(420px, calc(100% - 32px));
|
||||
padding: 32px 36px;
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--sh-login);
|
||||
}
|
||||
|
||||
.password-brand {
|
||||
color: var(--brand);
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 8px 0;
|
||||
color: var(--ink);
|
||||
font-size: 22px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.password-description,
|
||||
.password-policy {
|
||||
color: var(--mut);
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
label span {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
color: var(--mut);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 9px 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border-color: var(--brand);
|
||||
}
|
||||
|
||||
.password-error {
|
||||
color: var(--err);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.password-submit,
|
||||
.password-logout {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.password-submit {
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
background: var(--brand);
|
||||
}
|
||||
|
||||
.password-logout {
|
||||
margin-top: 8px;
|
||||
color: var(--mut);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: wait;
|
||||
opacity: 0.65;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useLoginStore } from '@/stores/login'
|
||||
|
||||
const router = useRouter()
|
||||
const loginStore = useLoginStore()
|
||||
|
||||
async function handleLogout() {
|
||||
try {
|
||||
await loginStore.logout()
|
||||
} catch {
|
||||
loginStore.clearSession()
|
||||
}
|
||||
await router.replace({ name: 'login' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="forbidden-page">
|
||||
<section class="forbidden-card">
|
||||
<span class="forbidden-code">403</span>
|
||||
<h1>当前账号没有可访问的功能</h1>
|
||||
<p>请联系系统管理员分配相应权限,或退出后更换账号。</p>
|
||||
<button type="button" @click="handleLogout">退出登录</button>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.forbidden-page {
|
||||
display: grid;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
background: var(--bg);
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.forbidden-card {
|
||||
max-width: 460px;
|
||||
padding: 36px;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--sh-login);
|
||||
}
|
||||
|
||||
.forbidden-code {
|
||||
color: var(--brand);
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--ink);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--mut);
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 12px;
|
||||
padding: 9px 24px;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
background: var(--brand);
|
||||
border-radius: 7px;
|
||||
}
|
||||
</style>
|
||||
@@ -17,6 +17,7 @@ const captchaCode = ref('')
|
||||
const captchaLoading = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const isSubmitting = ref(false)
|
||||
const passwordChanged = route.query.passwordChanged === '1'
|
||||
|
||||
async function loadCaptcha() {
|
||||
captchaLoading.value = true
|
||||
@@ -68,9 +69,11 @@ async function handleLogin() {
|
||||
: {}),
|
||||
}
|
||||
|
||||
await loginStore.login(payload)
|
||||
const session = await loginStore.login(payload)
|
||||
|
||||
await router.replace(getRedirectPath())
|
||||
await router.replace(
|
||||
session.user.passwordChangeRequired ? { name: 'change-password' } : getRedirectPath(),
|
||||
)
|
||||
} catch (error: unknown) {
|
||||
errorMessage.value = getAuthErrorMessage(error)
|
||||
|
||||
@@ -116,6 +119,10 @@ onMounted(() => {
|
||||
|
||||
<p class="login-subtitle">安全留痕 · 高效签署 · 医患无忧</p>
|
||||
|
||||
<p v-if="passwordChanged" class="login-success" role="status">
|
||||
密码修改成功,请使用新密码重新登录
|
||||
</p>
|
||||
|
||||
<div class="login-field">
|
||||
<label for="login-username">账号</label>
|
||||
<input
|
||||
@@ -301,6 +308,16 @@ onMounted(() => {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.login-success {
|
||||
margin: 0 0 12px;
|
||||
padding: 8px 10px;
|
||||
color: #16794d;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
background: #edf9f3;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.login-submit {
|
||||
width: 100%;
|
||||
margin-top: 6px;
|
||||
|
||||
Reference in New Issue
Block a user