Files
HAOVP/frontend/video-frontend-admin/src/views/login/index.vue
T

417 lines
9.3 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.
<template>
<div class="admin-login-page">
<div class="login-shell">
<section class="login-showcase">
<div class="showcase-copy">
<p class="eyebrow">
HAOVP Admin
</p>
<h1>平台治理、内容审核与运营数据统一入口</h1>
<p class="lead">
在同一套控制台中处理用户、视频、评论和后台统计,适合日常运营值守和答辩演示场景。
</p>
</div>
<div class="showcase-grid">
<div class="showcase-card">
<strong>内容治理</strong>
<span>审核视频、清理评论、维护分类与标签。</span>
</div>
<div class="showcase-card">
<strong>用户管理</strong>
<span>查看用户详情、状态、行为与运营指标。</span>
</div>
<div class="showcase-card">
<strong>实时概览</strong>
<span>通过仪表盘追踪增长、播放和互动变化。</span>
</div>
</div>
</section>
<section class="login-panel">
<div class="panel-header">
<h1 class="logo">
HAOVP
</h1>
<p class="title">
管理后台登录
</p>
<p class="subtitle">
统一管理平台用户、内容审核、评论治理与运营数据
</p>
</div>
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginRules"
class="login-form"
>
<div class="captcha-banner">
<div>
<strong>安全验证</strong>
<span>当前后台登录必须填写验证码,点击图片可刷新。</span>
</div>
<el-image
:src="captchaImage"
fit="contain"
class="captcha-image"
@click="refreshCaptcha"
>
<template #error>
<div
class="captcha-error"
@click="refreshCaptcha"
>
点击刷新
</div>
</template>
</el-image>
</div>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
placeholder="用户名"
prefix-icon="User"
size="large"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="密码"
prefix-icon="Lock"
size="large"
show-password
@keyup.enter="handleLogin"
/>
</el-form-item>
<el-form-item prop="captcha">
<el-input
v-model="loginForm.captcha"
placeholder="验证码"
prefix-icon="Key"
size="large"
@keyup.enter="handleLogin"
>
<template #append>
<el-image
:src="captchaImage"
fit="contain"
class="captcha-btn"
@click="refreshCaptcha"
/>
</template>
</el-input>
</el-form-item>
<div class="submit-row">
<el-button
type="primary"
size="large"
:loading="loading"
class="login-btn"
@click="handleLogin"
>
登 录
</el-button>
</div>
</el-form>
<div class="demo-tips">
<span>管理员账号:admin</span>
<span>登录密码:不少于 6 位</span>
<span>验证码请按图形输入</span>
</div>
</section>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { authApi, type CaptchaResult } from '@/api/auth.ts'
import { useUserStore } from '@/stores/user.ts'
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const loginFormRef = ref<FormInstance>()
const loading = ref(false)
const captchaKey = ref('')
const captchaImage = ref('')
const loginForm = reactive({
username: '',
password: '',
captcha: ''
})
const loginRules: FormRules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
],
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
}
const refreshCaptcha = async () => {
try {
const res: CaptchaResult = await authApi.getCaptcha()
captchaKey.value = res.captchaKey
captchaImage.value = res.captchaImage
} catch (error) {
console.error('获取验证码失败:', error)
}
}
const handleLogin = async () => {
if (!loginFormRef.value) return
await loginFormRef.value.validate(async (valid) => {
if (!valid) return
loading.value = true
try {
await userStore.login({
username: loginForm.username,
password: loginForm.password,
captcha: loginForm.captcha,
captchaKey: captchaKey.value
})
ElMessage.success('登录成功')
const redirect = (route.query.redirect as string) || '/'
router.replace(redirect)
} catch (error: any) {
ElMessage.error(error.message || '登录失败')
refreshCaptcha()
} finally {
loading.value = false
}
})
}
onMounted(() => {
refreshCaptcha()
})
</script>
<style scoped lang="scss">
.admin-login-page {
min-height: 100vh;
background:
radial-gradient(circle at 18% 18%, rgba(56, 189, 248, 0.16), transparent 24%),
radial-gradient(circle at 82% 82%, rgba(34, 197, 94, 0.14), transparent 22%),
linear-gradient(135deg, #08111f 0%, #11284a 52%, #0f766e 100%);
display: flex;
align-items: center;
justify-content: center;
padding: 28px 20px;
}
.login-shell {
width: min(1180px, 100%);
display: grid;
grid-template-columns: minmax(0, 1.15fr) 440px;
gap: 20px;
}
.login-showcase,
.login-panel {
border-radius: 28px;
}
.login-showcase {
padding: 34px 32px;
color: #fff;
background:
radial-gradient(circle at top right, rgba(255, 255, 255, 0.18), transparent 28%),
linear-gradient(135deg, rgba(15, 23, 42, 0.92), rgba(29, 78, 216, 0.88));
box-shadow: 0 24px 80px rgba(5, 18, 39, 0.34);
}
.eyebrow {
margin: 0 0 12px;
font-size: 12px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.68);
}
.showcase-copy h1 {
margin: 0 0 12px;
font-size: 40px;
line-height: 1.18;
}
.lead {
margin: 0;
max-width: 620px;
color: rgba(255, 255, 255, 0.82);
line-height: 1.8;
}
.showcase-grid {
display: grid;
gap: 12px;
margin-top: 28px;
}
.showcase-card {
padding: 18px 20px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.08);
strong {
display: block;
margin-bottom: 6px;
font-size: 16px;
}
span {
color: rgba(255, 255, 255, 0.76);
font-size: 13px;
line-height: 1.7;
}
}
.login-panel {
padding: 30px 28px 26px;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 24px 80px rgba(5, 18, 39, 0.34);
}
.panel-header {
margin-bottom: 22px;
}
.logo {
margin: 0;
font-size: 34px;
font-weight: 800;
background: linear-gradient(135deg, #00a1d6, #fb7299);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.title {
color: #355070;
font-size: 16px;
margin-top: 8px;
}
.subtitle {
margin-top: 10px;
color: #6b7280;
font-size: 13px;
line-height: 1.7;
}
.captcha-banner {
display: flex;
justify-content: space-between;
gap: 12px;
align-items: center;
margin-bottom: 18px;
padding: 14px 16px;
border-radius: 18px;
background: #f8fafc;
strong {
display: block;
margin-bottom: 4px;
color: #18191c;
font-size: 14px;
}
span {
color: #64748b;
font-size: 12px;
line-height: 1.6;
}
}
.captcha-image {
width: 120px;
height: 42px;
cursor: pointer;
flex-shrink: 0;
}
.captcha-error {
width: 120px;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
background: #eef2f7;
color: #64748b;
font-size: 12px;
border-radius: 12px;
}
.captcha-btn {
width: 100px;
height: 32px;
cursor: pointer;
}
.submit-row {
margin-top: 20px;
}
.login-btn {
width: 100%;
background: linear-gradient(135deg, #00a1d6, #2f80ed);
border: none;
box-shadow: 0 14px 28px rgba(0, 161, 214, 0.24);
}
.demo-tips {
margin-top: 18px;
padding: 14px 16px;
border-radius: 16px;
background: linear-gradient(135deg, #eff6ff, #ecfeff);
color: #355070;
display: grid;
gap: 6px;
font-size: 12px;
border: 1px solid rgba(59, 130, 246, 0.12);
}
@media (max-width: 960px) {
.login-shell {
grid-template-columns: 1fr;
}
.login-showcase {
display: none;
}
}
@media (max-width: 768px) {
.admin-login-page {
padding: 16px 12px;
}
.login-panel {
padding: 22px 18px 20px;
}
.captcha-banner {
flex-direction: column;
align-items: flex-start;
}
}
</style>