feat(web): 深度定制 Element Plus 主题 — 靛蓝主色/暗色模式/侧栏折叠/面包屑/登录页重绘
- 新增 styles/theme.css:CSS 变量覆盖默认主题(主色 #4F46E5、圆角、阴影、页面背景),html.dark 暗色变量 - 新增 stores/theme.ts:暗色/亮色切换,localStorage 持久化 - main.ts 引入 element-plus 暗色 css-vars 与主题文件,挂载前初始化主题 - index.html 加 SVG favicon 与防暗色闪烁内联脚本 - AdminLayout:深色可折叠侧栏、header 面包屑 + 暗色切换按钮、页面切换过渡 - UserLayout:header 打磨 + 暗色切换按钮 - LoginView/ApplyView:统一品牌渐变背景与卡片,登录页 OTP 输入行内联发送按钮、登录按钮唯一通栏主按钮(修复按钮错位)
This commit is contained in:
+40
-51
@@ -10,7 +10,8 @@ const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const tab = ref<'admin' | 'user'>('admin')
|
||||
const loading = ref(false)
|
||||
const loading = ref(false) // 登录请求中
|
||||
const sendingOtp = ref(false) // 发送 OTP 中
|
||||
|
||||
// 管理员登录
|
||||
const adminForm = reactive({ username: '', password: '' })
|
||||
@@ -70,6 +71,7 @@ async function onSendOtp() {
|
||||
await loadCaptcha()
|
||||
if (!captcha.value) return
|
||||
}
|
||||
sendingOtp.value = true
|
||||
try {
|
||||
await api.otpSend({
|
||||
username: userForm.username,
|
||||
@@ -84,6 +86,8 @@ async function onSendOtp() {
|
||||
} catch {
|
||||
// 验证码错误等已提示;重新生成一次
|
||||
await loadCaptcha()
|
||||
} finally {
|
||||
sendingOtp.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,11 +120,14 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<el-card class="login-card" shadow="always">
|
||||
<div class="brand">
|
||||
<el-icon :size="30"><Monitor /></el-icon>
|
||||
<div class="auth-page">
|
||||
<el-card class="auth-card" shadow="never">
|
||||
<div class="auth-brand">
|
||||
<span class="brand-icon">
|
||||
<el-icon :size="26"><Monitor /></el-icon>
|
||||
</span>
|
||||
<h2>服务器用户管理节点</h2>
|
||||
<span class="sub">账号申请 · SSH 密钥 · 使用审计</span>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="tab" stretch @tab-change="onTabChange">
|
||||
@@ -140,7 +147,7 @@ onMounted(() => {
|
||||
@keyup.enter="onAdminLogin"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-button type="primary" class="submit" :loading="loading" @click="onAdminLogin">登录</el-button>
|
||||
<el-button type="primary" class="auth-submit" :loading="loading" @click="onAdminLogin">登录</el-button>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -151,85 +158,67 @@ onMounted(() => {
|
||||
<el-input v-model="userForm.username" placeholder="用户名(可带或不带 ext_ 前缀)" autocomplete="username" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图形验证码">
|
||||
<div class="captcha-row">
|
||||
<div class="inline-row">
|
||||
<el-input v-model="userForm.captcha_code" placeholder="4 位验证码" maxlength="4" @keyup.enter="onSendOtp" />
|
||||
<el-image
|
||||
v-if="captcha"
|
||||
:src="captcha.image"
|
||||
fit="contain"
|
||||
class="captcha-img"
|
||||
:title="'点击刷新'"
|
||||
title="点击刷新"
|
||||
@click="loadCaptcha"
|
||||
/>
|
||||
<el-button v-else :loading="captchaLoading" @click="loadCaptcha">获取</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="登录验证码(OTP)">
|
||||
<el-input
|
||||
v-model="userForm.code"
|
||||
placeholder="6 位 OTP(邮件或 CLI 获取)"
|
||||
maxlength="6"
|
||||
@keyup.enter="onUserLogin"
|
||||
/>
|
||||
<div class="inline-row otp-row">
|
||||
<el-input
|
||||
v-model="userForm.code"
|
||||
placeholder="6 位 OTP(邮件或 CLI 获取)"
|
||||
maxlength="6"
|
||||
class="otp-input"
|
||||
@keyup.enter="onUserLogin"
|
||||
/>
|
||||
<el-button :loading="sendingOtp" :disabled="cooldown > 0" @click="onSendOtp">
|
||||
{{ cooldown > 0 ? `${cooldown}s 后重发` : '发送验证码' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-button type="primary" class="submit" :loading="loading" @click="onSendOtp" :disabled="cooldown > 0">
|
||||
{{ cooldown > 0 ? `重新发送(${cooldown}s)` : '发送验证码' }}
|
||||
</el-button>
|
||||
<el-button type="success" class="submit" :loading="loading" @click="onUserLogin">登录</el-button>
|
||||
<el-button type="primary" class="auth-submit" :loading="loading" @click="onUserLogin">登录</el-button>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="foot">
|
||||
<router-link to="/apply">申请新账号</router-link>
|
||||
<div class="auth-foot">
|
||||
<router-link to="/apply">还没有账号?申请新账号</router-link>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #1f2d3d 0%, #2c3e50 100%);
|
||||
}
|
||||
.login-card {
|
||||
width: 400px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.brand h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
.captcha-row {
|
||||
/* 输入框 + 相邻控件同行对齐 */
|
||||
.inline-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
.otp-row .otp-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.captcha-img {
|
||||
width: 110px;
|
||||
height: 32px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.submit {
|
||||
width: 100%;
|
||||
|
||||
/* tab 与品牌区的间距 */
|
||||
:deep(.el-tabs) {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.foot {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user