feat(admin-frontend): 重做登录回跳与仪表盘样式
重构管理端登录、主布局和仪表盘,统一为 Apple 风格 并移除高成本装饰层以提升页面流畅度。 补充仪表盘统计、趋势、排行和系统状态接口封装, 同时完善受保护路由的 redirect 回跳逻辑。
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Lock, Message, Right } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { DEFAULT_AFTER_LOGIN, normalizeRedirectTarget } from '@/utils/navigation'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
const app = useAppStore()
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const loading = ref(false)
|
||||
const errorMessage = ref('')
|
||||
|
||||
const form = reactive({
|
||||
email: '',
|
||||
@@ -17,6 +23,13 @@ const form = reactive({
|
||||
remember: false,
|
||||
})
|
||||
|
||||
const redirectTarget = computed(() => normalizeRedirectTarget(route.query.redirect))
|
||||
const redirectHint = computed(() => (
|
||||
redirectTarget.value === DEFAULT_AFTER_LOGIN
|
||||
? '登录后将进入仪表盘总览'
|
||||
: `登录后将返回 ${redirectTarget.value}`
|
||||
))
|
||||
|
||||
const rules: FormRules = {
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
@@ -33,12 +46,14 @@ async function onSubmit() {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
await auth.login(form.email, form.password, form.remember)
|
||||
ElMessage.success('登录成功')
|
||||
router.push('/dashboard')
|
||||
await router.replace(redirectTarget.value)
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : '登录失败'
|
||||
errorMessage.value = msg
|
||||
ElMessage.error(msg)
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -47,12 +62,34 @@ async function onSubmit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<h1 class="login-title">Xboard Admin</h1>
|
||||
<p class="login-subtitle">管理后台</p>
|
||||
<div class="login-page">
|
||||
<section class="login-hero">
|
||||
<p class="login-eyebrow">XBOARD ADMIN</p>
|
||||
<h1 class="login-headline">管理后台,像产品页一样安静。</h1>
|
||||
<p class="login-description">
|
||||
页面重新回到 Apple 式的信息编排方式。减少装饰层和不必要的视觉负担,让登录入口更直接。
|
||||
</p>
|
||||
<div class="login-meta">
|
||||
<span>secure_path /{{ app.securePath || 'admin' }}</span>
|
||||
<span>{{ redirectHint }}</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="login-panel">
|
||||
<div class="login-header">
|
||||
<p class="login-panel-kicker">Sign in</p>
|
||||
<h2 class="login-title">管理员登录</h2>
|
||||
<p class="login-subtitle">使用具备后台权限的账号进入 {{ app.title }}。</p>
|
||||
</div>
|
||||
|
||||
<ElAlert
|
||||
v-if="errorMessage"
|
||||
:title="errorMessage"
|
||||
type="error"
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="login-alert"
|
||||
/>
|
||||
|
||||
<ElForm
|
||||
ref="formRef"
|
||||
@@ -66,7 +103,7 @@ async function onSubmit() {
|
||||
<ElInput
|
||||
v-model="form.email"
|
||||
placeholder="admin@example.com"
|
||||
prefix-icon="Message"
|
||||
:prefix-icon="Message"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
@@ -75,74 +112,168 @@ async function onSubmit() {
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
prefix-icon="Lock"
|
||||
:prefix-icon="Lock"
|
||||
show-password
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem>
|
||||
<div class="login-form-meta">
|
||||
<ElCheckbox v-model="form.remember">记住登录</ElCheckbox>
|
||||
</ElFormItem>
|
||||
<span class="login-meta-text">{{ redirectHint }}</span>
|
||||
</div>
|
||||
|
||||
<ElFormItem>
|
||||
<ElButton
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
class="login-btn"
|
||||
@click="onSubmit"
|
||||
>
|
||||
{{ loading ? '登录中...' : '登 录' }}
|
||||
</ElButton>
|
||||
</ElFormItem>
|
||||
<ElButton
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
class="login-btn"
|
||||
@click="onSubmit"
|
||||
>
|
||||
<span>{{ loading ? '登录中...' : '继续' }}</span>
|
||||
<ElIcon><Right /></ElIcon>
|
||||
</ElButton>
|
||||
</ElForm>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
||||
padding: 20px;
|
||||
gap: 28px;
|
||||
padding: 40px 32px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 100%;
|
||||
.login-hero,
|
||||
.login-panel {
|
||||
width: min(100%, 560px);
|
||||
}
|
||||
|
||||
.login-hero {
|
||||
padding: 48px;
|
||||
border-radius: 28px;
|
||||
background: #000000;
|
||||
color: var(--xboard-text-on-dark);
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.login-eyebrow,
|
||||
.login-panel-kicker {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.28em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.login-headline {
|
||||
margin: 0;
|
||||
font-size: clamp(40px, 5vw, 56px);
|
||||
line-height: 1.07;
|
||||
letter-spacing: -0.28px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.login-description {
|
||||
margin: 0;
|
||||
max-width: 420px;
|
||||
padding: 40px;
|
||||
background: var(--el-bg-color);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
line-height: 1.47;
|
||||
color: var(--xboard-text-on-dark-muted);
|
||||
}
|
||||
|
||||
.login-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.login-meta span {
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
border-radius: 999px;
|
||||
padding: 10px 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.login-panel {
|
||||
border-radius: 28px;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--xboard-shadow);
|
||||
padding: 40px 36px;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--el-text-color-primary);
|
||||
margin: 0 0 8px;
|
||||
margin: 0;
|
||||
font-size: 34px;
|
||||
line-height: 1.12;
|
||||
letter-spacing: -0.32px;
|
||||
color: var(--xboard-text-strong);
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin: 0;
|
||||
color: var(--xboard-text-secondary);
|
||||
}
|
||||
|
||||
.login-alert {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.login-form-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.login-meta-text {
|
||||
color: var(--xboard-text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
border-radius: 999px;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.login-card {
|
||||
@media (max-width: 980px) {
|
||||
.login-page {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.login-hero,
|
||||
.login-panel {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.login-page {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-hero,
|
||||
.login-panel {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.login-form-meta {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user