Files
hfb_sys/frontend/src/features/admin/views/AdminLoginView.vue
T
yml2213 b00049de2d chore: 修复 auth 和 admin 视图导入路径
- 修复 AdminLoginView 使用 features/admin
- 修复 LoginView, MobileLoginView, MobileRegisterView 使用 features/auth
- 修复 MobileProfileView, MobileRealnameView 使用正确路径
- 修复 useSmsCountdown 导入到 shared/composables

类型错误:79 → ?
模块错误:38 → 31
2026-06-04 10:07:39 +08:00

435 lines
9.9 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 { ElMessage } from "element-plus";
import { Lock, User } from "@element-plus/icons-vue";
import { onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { fetchAdminCaptcha, type AdminCaptcha } from "@/features/admin";
import { useAdminSessionStore } from "@/stores/adminSession";
const router = useRouter();
const adminSession = useAdminSessionStore();
const loading = ref(false);
const captchaLoading = ref(false);
const captcha = ref<AdminCaptcha | null>(null);
const form = reactive({
username: "admin",
password: "admin123456",
captchaCode: "",
});
onMounted(loadCaptcha);
async function loadCaptcha() {
captchaLoading.value = true;
try {
captcha.value = await fetchAdminCaptcha();
form.captchaCode = "";
} catch (error) {
ElMessage.error(readError(error, "验证码加载失败"));
} finally {
captchaLoading.value = false;
}
}
async function handleLogin() {
loading.value = true;
try {
await adminSession.login(
form.username,
form.password,
captcha.value?.captcha_id || "",
form.captchaCode
);
ElMessage.success("后台登录成功");
await router.push("/admin/dashboard");
} catch (error) {
ElMessage.error(readError(error, "后台登录失败"));
await loadCaptcha();
} finally {
loading.value = false;
}
}
function readError(error: unknown, fallback: string) {
if (typeof error === "object" && error && "response" in error) {
const response = (error as { response?: { data?: { message?: string } } })
.response;
return response?.data?.message || fallback;
}
return fallback;
}
</script>
<template>
<section class="admin-login-shell">
<div class="admin-login-glow" aria-hidden="true" />
<div class="admin-login-glow secondary" aria-hidden="true" />
<div class="admin-login-card">
<div class="admin-login-left">
<div class="brand-lockup">
<div class="brand-logo">H</div>
<strong>大锤商行 - 后台管理</strong>
</div>
<div class="admin-login-hero">
<h2>高效管理<br />从容掌控</h2>
<p>统一的后台管理系统,助您轻松处理订单、用户与运营数据。</p>
</div>
<div class="admin-login-footer">
<span>© 哈夫币租号平台</span>
</div>
</div>
<div class="admin-login-right">
<div class="admin-login-header">
<p class="eyebrow">Admin Login</p>
<h1>管理员登录</h1>
</div>
<el-form class="admin-form" label-position="top" @submit.prevent>
<el-form-item label="用户名">
<el-input
v-model="form.username"
placeholder="请输入管理员用户名"
size="large"
:prefix-icon="User"
/>
</el-form-item>
<el-form-item label="密码">
<el-input
v-model="form.password"
type="password"
show-password
placeholder="请输入管理员密码"
size="large"
:prefix-icon="Lock"
@keyup.enter="handleLogin"
/>
</el-form-item>
<el-form-item label="验证码">
<div class="admin-captcha-row">
<el-input
v-model="form.captchaCode"
maxlength="4"
placeholder="请输入验证码"
size="large"
@keyup.enter="handleLogin"
/>
<button
class="captcha-image-button"
type="button"
:disabled="captchaLoading"
@click="loadCaptcha"
>
<img v-if="captcha" :src="captcha.image" alt="验证码" />
<span v-else>刷新</span>
</button>
</div>
</el-form-item>
<el-button
class="admin-login-btn"
type="primary"
size="large"
:loading="loading"
@click="handleLogin"
>
登录后台
</el-button>
</el-form>
<RouterLink class="back-home" to="/">← 返回首页</RouterLink>
</div>
</div>
</section>
</template>
<style scoped>
.admin-login-shell {
position: relative;
display: grid;
min-height: 100vh;
place-items: center;
overflow: hidden;
background: radial-gradient(
circle at 18% 20%,
rgba(20, 119, 255, 0.14),
transparent 42%
),
radial-gradient(
circle at 82% 80%,
rgba(109, 40, 217, 0.12),
transparent 42%
),
linear-gradient(180deg, #0b1120 0%, #0f172a 60%, #111827 100%);
padding: 24px;
}
.admin-login-glow {
position: absolute;
top: -10%;
left: -10%;
width: 50vw;
height: 50vw;
border-radius: 50%;
background: radial-gradient(
circle,
rgba(20, 119, 255, 0.22),
transparent 65%
);
filter: blur(80px);
pointer-events: none;
}
.admin-login-glow.secondary {
top: auto;
left: auto;
right: -10%;
bottom: -10%;
background: radial-gradient(
circle,
rgba(109, 40, 217, 0.18),
transparent 65%
);
}
.admin-login-card {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: 1fr 420px;
width: min(960px, 100%);
min-height: 560px;
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 20px;
background: rgba(15, 23, 42, 0.55);
backdrop-filter: blur(24px);
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35),
inset 0 1px 0 rgba(255, 255, 255, 0.06);
overflow: hidden;
}
/* ========== 左侧品牌区 ========== */
.admin-login-left {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 40px 36px;
background: radial-gradient(
circle at 30% 20%,
rgba(20, 119, 255, 0.12),
transparent 50%
),
radial-gradient(circle at 80% 90%, rgba(109, 40, 217, 0.1), transparent 50%),
linear-gradient(160deg, rgba(20, 119, 255, 0.1), rgba(109, 40, 217, 0.06));
}
.brand-lockup {
display: flex;
align-items: center;
gap: 10px;
}
.brand-logo {
display: grid;
width: 36px;
height: 36px;
place-items: center;
border-radius: 10px;
background: linear-gradient(135deg, #1477ff, #2f8dff);
box-shadow: 0 8px 24px rgba(20, 119, 255, 0.25);
color: #fff;
font-size: 15px;
font-weight: 900;
}
.brand-lockup strong {
color: #f8fafc;
font-size: 15px;
font-weight: 800;
letter-spacing: 0.5px;
}
.admin-login-hero h2 {
margin: 0 0 14px;
color: #f1f5f9;
font-size: 28px;
font-weight: 800;
line-height: 1.25;
}
.admin-login-hero p {
margin: 0;
color: #94a3b8;
font-size: 14px;
line-height: 1.7;
max-width: 320px;
}
.admin-login-footer span {
color: #475569;
font-size: 12px;
}
/* ========== 右侧表单区 ========== */
.admin-login-right {
display: flex;
flex-direction: column;
justify-content: center;
padding: 36px 32px;
background: rgba(255, 255, 255, 0.03);
}
.admin-login-header {
margin-bottom: 24px;
}
.admin-login-header .eyebrow {
margin: 0 0 8px;
color: #1477ff;
font-size: 12px;
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
}
.admin-login-header h1 {
margin: 0;
color: #f8fafc;
font-size: 24px;
font-weight: 800;
line-height: 1.2;
}
.admin-form :deep(.el-form-item__label) {
color: #cbd5e1;
font-size: 13px;
font-weight: 600;
padding-bottom: 6px;
}
.admin-form :deep(.el-input__wrapper) {
background: rgba(255, 255, 255, 0.04);
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08) inset;
border-radius: 10px;
padding: 0 12px;
transition: box-shadow 0.2s, background 0.2s;
}
.admin-form :deep(.el-input__wrapper:hover) {
background: rgba(255, 255, 255, 0.06);
}
.admin-form :deep(.el-input__wrapper.is-focus) {
background: rgba(255, 255, 255, 0.06);
box-shadow: 0 0 0 1px rgba(20, 119, 255, 0.45) inset,
0 0 0 3px rgba(20, 119, 255, 0.08);
}
.admin-form :deep(.el-input__inner) {
color: #f1f5f9;
font-size: 14px;
height: 44px;
}
.admin-form :deep(.el-input__inner::placeholder) {
color: #64748b;
}
.admin-form :deep(.el-input__prefix-inner) {
color: #64748b;
}
.admin-captcha-row {
display: grid;
grid-template-columns: 1fr 132px;
gap: 10px;
width: 100%;
}
.captcha-image-button {
display: flex;
align-items: center;
justify-content: center;
height: 44px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 10px;
background: rgba(255, 255, 255, 0.04);
padding: 0;
cursor: pointer;
overflow: hidden;
transition: background 0.2s, border-color 0.2s;
}
.captcha-image-button:hover {
background: rgba(255, 255, 255, 0.07);
border-color: rgba(255, 255, 255, 0.14);
}
.captcha-image-button:disabled {
cursor: wait;
opacity: 0.6;
}
.captcha-image-button img {
display: block;
width: 132px;
height: 44px;
}
.captcha-image-button span {
color: #94a3b8;
font-size: 13px;
}
.admin-login-btn {
width: 100%;
height: 46px;
border-radius: 10px;
font-size: 15px;
font-weight: 700;
margin-top: 8px;
letter-spacing: 0.5px;
background: linear-gradient(135deg, #1477ff, #2f8dff);
border: none;
box-shadow: 0 10px 28px rgba(20, 119, 255, 0.25);
transition: transform 0.15s, box-shadow 0.2s;
}
.admin-login-btn:hover {
transform: translateY(-1px);
box-shadow: 0 14px 36px rgba(20, 119, 255, 0.32);
}
.back-home {
display: block;
margin-top: 18px;
text-align: center;
color: #64748b;
font-size: 13px;
text-decoration: none;
transition: color 0.2s;
}
.back-home:hover {
color: #94a3b8;
}
/* ========== 响应式 ========== */
@media (max-width: 860px) {
.admin-login-card {
grid-template-columns: 1fr;
max-width: 420px;
}
.admin-login-left {
display: none;
}
.admin-login-right {
padding: 32px 28px;
}
}
@media (max-width: 480px) {
.admin-login-shell {
padding: 16px;
}
.admin-login-right {
padding: 24px 20px;
}
}
@media (prefers-reduced-motion: reduce) {
.admin-login-btn {
transition: none;
}
}
</style>