feat: unify login UI and improve kdocs defaults

This commit is contained in:
2026-02-07 01:27:00 +08:00
parent bf29ac1924
commit 9991834ccd
22 changed files with 358 additions and 114 deletions

View File

@@ -240,56 +240,80 @@ onMounted(async () => {
</script>
<template>
<div class="auth-wrap">
<el-card shadow="never" class="auth-card" :body-style="{ padding: '22px' }">
<div class="brand">
<div class="brand-title">知识管理平台</div>
<div class="brand-sub app-muted">用户登录</div>
<div class="login-page">
<div class="login-container">
<div class="login-header">
<span class="login-badge">用户登录</span>
<h1>用户登录系统</h1>
<p>知识管理平台</p>
</div>
<el-form label-position="top">
<el-form-item label="用户名">
<el-input v-model="form.username" placeholder="请输入用户名" autocomplete="username" />
</el-form-item>
<el-form-item label="密码">
<div class="form-group">
<label for="username">用户账号</label>
<el-input
id="username"
v-model="form.username"
class="login-input"
placeholder="请输入用户名"
autocomplete="username"
/>
</div>
<div class="form-group">
<label for="password">密码</label>
<el-input
id="password"
v-model="form.password"
class="login-input"
type="password"
show-password
placeholder="请输入密码"
autocomplete="current-password"
@keyup.enter="onSubmit"
/>
</div>
<div v-if="needCaptcha" class="form-group">
<label for="captcha">验证码</label>
<div class="captcha-row">
<el-input
v-model="form.password"
type="password"
show-password
placeholder="请输入码"
autocomplete="current-password"
id="captcha"
v-model="form.captcha"
class="login-input captcha-input"
placeholder="请输入验证码"
@keyup.enter="onSubmit"
/>
</el-form-item>
<el-form-item v-if="needCaptcha" label="验证码">
<div class="captcha-row">
<el-input v-model="form.captcha" placeholder="请输入验证码" @keyup.enter="onSubmit" />
<img
v-if="captchaImage"
class="captcha-img"
:src="captchaImage"
alt="验证码"
title="点击刷新"
@click="refreshLoginCaptcha"
/>
<el-button @click="refreshLoginCaptcha">刷新</el-button>
</div>
</el-form-item>
</el-form>
<div class="links">
<el-button text type="primary" @click="openForgot">忘记密码</el-button>
<el-button v-if="showResendLink" text type="primary" @click="openResend">重发验证邮件</el-button>
<img
v-if="captchaImage"
class="captcha-img"
:src="captchaImage"
alt="验证码"
title="点击刷新"
@click="refreshLoginCaptcha"
/>
<button type="button" class="captcha-refresh" @click="refreshLoginCaptcha">刷新</button>
</div>
</div>
<el-button type="primary" class="submit-btn" :loading="loading" @click="onSubmit">登录</el-button>
<button type="button" class="btn-login" :disabled="loading" @click="onSubmit">
{{ loading ? '登录中...' : '登录系统' }}
</button>
<div class="foot">
<span class="app-muted">还没有账号</span>
<el-button link type="primary" @click="goRegister">立即注册</el-button>
<div class="action-links">
<button type="button" class="link-btn" @click="openForgot">忘记密码</button>
<button v-if="showResendLink" type="button" class="link-btn" @click="openResend">重发验证邮件</button>
</div>
</el-card>
<div class="register-row">
<span>还没有账号</span>
<button type="button" class="link-btn" @click="goRegister">立即注册</button>
</div>
<div class="back-link">
<span>管理员请前往</span>
<a href="/yuyx">后台登录</a>
</div>
</div>
<el-dialog v-model="forgotOpen" title="找回密码" width="min(560px, 92vw)">
<el-alert
@@ -376,61 +400,181 @@ onMounted(async () => {
</template>
<style scoped>
.auth-wrap {
.login-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
position: relative;
background: linear-gradient(135deg, #eef2ff 0%, #f6f7fb 45%, #ecfeff 100%);
}
.auth-card {
.login-page::before {
content: '';
position: fixed;
inset: 0;
background:
radial-gradient(800px 500px at 15% 20%, rgba(59, 130, 246, 0.18), transparent 60%),
radial-gradient(700px 420px at 85% 70%, rgba(124, 58, 237, 0.16), transparent 55%);
pointer-events: none;
}
.login-container {
width: 100%;
max-width: 420px;
border-radius: var(--app-radius);
border: 1px solid var(--app-border);
box-shadow: var(--app-shadow);
background: #fff;
border-radius: 16px;
box-shadow: 0 18px 60px rgba(17, 24, 39, 0.15);
border: 1px solid rgba(17, 24, 39, 0.08);
padding: 38px 34px;
position: relative;
z-index: 1;
}
.brand {
.login-header {
text-align: center;
margin-bottom: 28px;
}
.login-badge {
display: inline-block;
background: rgba(59, 130, 246, 0.1);
color: #1d4ed8;
padding: 6px 14px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
margin-bottom: 14px;
}
.brand-title {
font-size: 18px;
font-weight: 900;
.login-header h1 {
font-size: 24px;
color: #111827;
margin: 0 0 10px;
letter-spacing: 0.2px;
}
.brand-sub {
margin-top: 4px;
font-size: 12px;
.login-header p {
margin: 0;
color: #6b7280;
font-size: 14px;
}
.links {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin: 2px 0 10px;
flex-wrap: wrap;
.form-group {
margin-bottom: 20px;
}
.submit-btn {
.form-group label {
display: block;
margin-bottom: 8px;
color: #111827;
font-weight: 700;
font-size: 13px;
}
.login-input :deep(.el-input__wrapper) {
border-radius: 10px;
min-height: 44px;
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 0 0 1px rgba(17, 24, 39, 0.14) inset;
transition: box-shadow 0.2s;
}
.login-input :deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.7) inset, 0 0 0 4px rgba(59, 130, 246, 0.16);
}
.login-input :deep(.el-input__inner) {
font-size: 14px;
}
.btn-login {
width: 100%;
padding: 12px;
border: none;
border-radius: 10px;
background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%);
color: #fff;
font-size: 16px;
font-weight: 800;
cursor: pointer;
transition: transform 0.15s, filter 0.15s;
}
.foot {
.btn-login:hover:not(:disabled) {
transform: translateY(-2px);
filter: brightness(1.02);
}
.btn-login:active:not(:disabled) {
transform: translateY(0);
}
.btn-login:disabled {
cursor: not-allowed;
opacity: 0.8;
}
.action-links {
margin-top: 14px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
flex-wrap: wrap;
}
.link-btn {
border: none;
background: none;
color: #2563eb;
font-size: 13px;
font-weight: 700;
cursor: pointer;
padding: 0;
}
.link-btn:hover {
text-decoration: underline;
}
.register-row {
margin-top: 16px;
display: flex;
justify-content: center;
gap: 6px;
align-items: center;
gap: 8px;
color: #6b7280;
font-size: 13px;
}
.back-link {
text-align: center;
margin-top: 18px;
color: #6b7280;
font-size: 13px;
}
.back-link a {
margin-left: 6px;
color: #2563eb;
text-decoration: none;
font-weight: 700;
}
.back-link a:hover {
text-decoration: underline;
}
.dialog-form {
margin-top: 10px;
}
.alert {
margin-top: 12px;
}
.captcha-row {
display: flex;
align-items: center;
@@ -438,17 +582,62 @@ onMounted(async () => {
width: 100%;
}
.captcha-input {
flex: 1;
min-width: 0;
}
.captcha-img {
height: 40px;
border: 1px solid var(--app-border);
height: 46px;
border: 1px solid rgba(17, 24, 39, 0.14);
border-radius: 8px;
cursor: pointer;
user-select: none;
}
.captcha-refresh {
height: 44px;
padding: 0 14px;
border: 1px solid rgba(17, 24, 39, 0.14);
border-radius: 10px;
background: #f8fafc;
color: #111827;
font-size: 13px;
cursor: pointer;
}
.captcha-refresh:hover {
background: #f1f5f9;
}
@media (max-width: 480px) {
.login-page {
align-items: flex-start;
padding: 20px 12px 12px;
}
.login-container {
max-width: 100%;
padding: 28px 20px;
border-radius: 14px;
}
.login-header h1 {
font-size: 22px;
}
.btn-login {
padding: 13px;
font-size: 15px;
}
.captcha-img {
height: 38px;
height: 42px;
}
.captcha-refresh {
height: 42px;
padding: 0 12px;
}
}
</style>