Files
Scoreboard/web/src/admin/pages/ChangePasswordPage.vue
T

151 lines
4.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import MxIcon from '../components/MxIcon.vue'
import PageHeader from '../components/PageHeader.vue'
import { adminApi } from '../api/client'
import { useAuthStore } from '../stores/auth'
import { useToastStore } from '../stores/toast'
const auth = useAuthStore()
const toast = useToastStore()
const router = useRouter()
const current = ref('')
const next = ref('')
const confirm = ref('')
const submitting = ref(false)
const error = ref<string | null>(null)
// 首次登录(mustChangePassword)时强制走这一页,改完必须重新登录
const forced = computed(() => auth.user?.mustChangePassword === true)
const tooShort = computed(() => next.value.length > 0 && next.value.length < 8)
const mismatch = computed(() => confirm.value.length > 0 && next.value !== confirm.value)
const canSubmit = computed(
() =>
current.value.length > 0 &&
next.value.length >= 8 &&
next.value === confirm.value &&
!submitting.value,
)
async function submit(): Promise<void> {
if (!canSubmit.value) return
submitting.value = true
error.value = null
try {
await adminApi.changePassword(current.value, next.value)
toast.success('密码已修改,请用新密码重新登录')
// 服务端已作废全部会话,本地状态必须清掉,否则界面还以为是登录态
await auth.logout()
await router.push({ name: 'login' })
} catch (caught) {
error.value = caught instanceof Error ? caught.message : '修改失败'
} finally {
submitting.value = false
}
}
</script>
<template>
<div class="password-page">
<PageHeader title="修改密码" subtitle="修改成功后需要重新登录" />
<div v-if="forced" class="mx-card forced-card">
<div class="mx-card-body forced-body">
<MxIcon name="lock" :size="18" />
<div>
<strong>首次登录必须修改密码</strong>
<p>默认管理员密码是随机生成的,请设置自己的密码后再继续使用。</p>
</div>
</div>
</div>
<div class="mx-card form-card">
<div class="mx-card-body">
<form class="password-form" @submit.prevent="submit">
<label class="mx-label" for="cur">原密码</label>
<input
id="cur"
v-model="current"
class="mx-input"
type="password"
autocomplete="current-password"
placeholder="当前使用的密码"
/>
<label class="mx-label" for="new">新密码</label>
<input
id="new"
v-model="next"
class="mx-input"
type="password"
autocomplete="new-password"
placeholder="至少 8 位"
/>
<p v-if="tooShort" class="hint error">新密码至少 8 位</p>
<label class="mx-label" for="confirm">确认新密码</label>
<input
id="confirm"
v-model="confirm"
class="mx-input"
type="password"
autocomplete="new-password"
placeholder="再输入一次"
/>
<p v-if="mismatch" class="hint error">两次输入不一致</p>
<p v-if="error" class="hint error">{{ error }}</p>
<button class="mx-btn mx-btn-primary submit" type="submit" :disabled="!canSubmit">
<MxIcon name="check" :size="16" />
{{ submitting ? '提交中…' : '修改密码' }}
</button>
</form>
</div>
</div>
</div>
</template>
<style scoped>
.password-page {
display: flex;
flex-direction: column;
gap: 16px;
max-width: 560px;
}
.forced-card {
border-color: var(--mx-amber, #fbbf24);
}
.forced-body {
display: flex;
gap: 12px;
align-items: flex-start;
color: var(--mx-amber, #fbbf24);
}
.forced-body p {
margin-top: 4px;
font-size: 13px;
color: var(--mx-t2);
}
.password-form {
display: flex;
flex-direction: column;
}
.password-form .mx-label:not(:first-child) {
margin-top: 16px;
}
.hint {
margin-top: 6px;
font-size: 12px;
}
.hint.error {
color: var(--mx-error, #f12522);
}
.submit {
margin-top: 22px;
align-self: flex-start;
}
</style>