<template>
|
<div class="wrap">
|
<div class="bg-glow bg-glow-left"></div>
|
<div class="bg-glow bg-glow-right"></div>
|
|
<el-card class="card" shadow="never">
|
<h2 class="title">红宇集团访客预约管理系统</h2>
|
<p class="subtitle">安全、高效地处理预约、审批与接待流程</p>
|
|
<el-form :model="form" label-position="top" class="login-form" @submit.prevent="onSubmit">
|
<el-form-item label="用户名" class="form-item">
|
<el-input
|
v-model="form.username"
|
autocomplete="username"
|
placeholder="请输入用户名"
|
size="large"
|
/>
|
</el-form-item>
|
|
<el-form-item label="密码" class="form-item">
|
<el-input
|
v-model="form.password"
|
type="password"
|
autocomplete="current-password"
|
placeholder="请输入密码"
|
show-password
|
size="large"
|
/>
|
</el-form-item>
|
|
<el-form-item label="验证码" class="form-item">
|
<div class="captcha-row">
|
<el-input
|
v-model="form.captchaCode"
|
placeholder="请输入验证码"
|
maxlength="8"
|
clearable
|
class="captcha-input"
|
size="large"
|
/>
|
<button class="captcha-img-wrap" type="button" title="点击刷新" @click="loadCaptcha">
|
<img v-if="captchaDataUrl" :src="captchaDataUrl" alt="验证码" class="captcha-img" />
|
<span v-else class="captcha-placeholder">加载中</span>
|
</button>
|
</div>
|
</el-form-item>
|
|
<el-button class="submit-btn" type="primary" native-type="submit" :loading="loading">
|
登录
|
</el-button>
|
</el-form>
|
</el-card>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { onMounted, reactive, ref } from "vue";
|
import { useRouter } from "vue-router";
|
import { ElMessage } from "element-plus";
|
import http from "../api/http";
|
import { useAuthStore } from "../stores/auth";
|
import { encryptAdminLoginField } from "../utils/loginCrypto";
|
|
const router = useRouter();
|
const auth = useAuthStore();
|
const loading = ref(false);
|
const captchaDataUrl = ref("");
|
const form = reactive({
|
username: "",
|
password: "",
|
captchaId: "",
|
captchaCode: "",
|
});
|
|
async function loadCaptcha() {
|
captchaDataUrl.value = "";
|
form.captchaId = "";
|
form.captchaCode = "";
|
try {
|
const { data } = await http.get("/auth/captcha");
|
const c = data.result;
|
form.captchaId = c.captchaId;
|
captchaDataUrl.value = "data:image/png;base64," + c.imageBase64;
|
} catch {
|
ElMessage.error("验证码加载失败");
|
}
|
}
|
|
onMounted(() => {
|
loadCaptcha();
|
});
|
|
async function onSubmit() {
|
if (!form.username.trim() || !form.password || !form.captchaId || !form.captchaCode.trim()) {
|
ElMessage.error("请完整填写用户名、密码和验证码");
|
return;
|
}
|
loading.value = true;
|
try {
|
const timestamp = Date.now();
|
const encryptedForm = {
|
username: encryptAdminLoginField(form.username, timestamp),
|
password: encryptAdminLoginField(form.password, timestamp),
|
captchaId: form.captchaId,
|
captchaCode: form.captchaCode,
|
};
|
const { data } = await http.post("/auth/login", encryptedForm);
|
const loginResult = data.result;
|
auth.setSession(
|
loginResult.token,
|
loginResult.displayName,
|
loginResult.avatarUrl,
|
loginResult.permissions || [],
|
loginResult.userId,
|
);
|
ElMessage.success("登录成功");
|
router.push("/");
|
} catch (e: unknown) {
|
ElMessage.error(e instanceof Error ? e.message : "登录失败");
|
loadCaptcha();
|
} finally {
|
loading.value = false;
|
}
|
}
|
</script>
|
|
<style scoped>
|
.wrap {
|
position: relative;
|
min-height: 100vh;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 24px;
|
overflow: hidden;
|
background:
|
radial-gradient(circle at 18% 16%, rgba(96, 165, 250, 0.18), transparent 30%),
|
radial-gradient(circle at 82% 82%, rgba(59, 130, 246, 0.1), transparent 26%),
|
linear-gradient(180deg, #fbfdff 0%, #f4f8ff 44%, #ffffff 100%);
|
}
|
|
.bg-glow {
|
position: absolute;
|
width: 420px;
|
height: 420px;
|
border-radius: 50%;
|
filter: blur(22px);
|
opacity: 0.6;
|
pointer-events: none;
|
}
|
|
.bg-glow-left {
|
top: -150px;
|
left: -110px;
|
background: radial-gradient(circle, rgba(125, 180, 255, 0.22) 0%, rgba(125, 180, 255, 0) 68%);
|
}
|
|
.bg-glow-right {
|
right: -150px;
|
bottom: -150px;
|
background: radial-gradient(circle, rgba(96, 165, 250, 0.14) 0%, rgba(96, 165, 250, 0) 70%);
|
}
|
|
.card {
|
position: relative;
|
z-index: 1;
|
width: 100%;
|
max-width: 432px;
|
padding: 16px 14px;
|
border-radius: 30px;
|
border: 1px solid rgba(255, 255, 255, 0.82);
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.42) 0%, rgba(255, 255, 255, 0.2) 100%);
|
box-shadow:
|
0 18px 58px rgba(80, 135, 220, 0.1),
|
inset 0 1px 0 rgba(255, 255, 255, 0.84);
|
backdrop-filter: blur(28px) saturate(145%);
|
}
|
|
.brand-badge {
|
display: inline-flex;
|
align-items: center;
|
justify-content: center;
|
min-width: 82px;
|
height: 30px;
|
padding: 0 14px;
|
border-radius: 999px;
|
background: rgba(255, 255, 255, 0.52);
|
color: #2b6fdf;
|
font-size: 12px;
|
font-weight: 700;
|
letter-spacing: 0.18em;
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
}
|
|
.title {
|
margin: 18px 0 8px;
|
font-size: 28px;
|
line-height: 1.2;
|
font-weight: 600;
|
color: #1e293b;
|
}
|
|
.subtitle {
|
margin: 0 0 22px;
|
font-size: 13px;
|
line-height: 1.65;
|
color: rgba(100, 116, 139, 0.82);
|
}
|
|
.login-form {
|
width: 100%;
|
}
|
|
.form-item {
|
margin-bottom: 18px;
|
}
|
|
:deep(.form-item .el-form-item__label) {
|
padding-bottom: 8px;
|
color: rgba(37, 99, 235, 0.92);
|
font-size: 13px;
|
font-weight: 500;
|
}
|
|
:deep(.el-input__wrapper) {
|
min-height: 46px;
|
border-radius: 14px;
|
background: rgba(255, 255, 255, 0.38);
|
box-shadow: 0 0 0 1px rgba(191, 219, 254, 0.78) inset;
|
}
|
|
:deep(.el-input__wrapper.is-focus) {
|
box-shadow:
|
0 0 0 1px rgba(59, 130, 246, 0.95) inset,
|
0 0 0 4px rgba(96, 165, 250, 0.12);
|
}
|
|
:deep(.el-input__inner) {
|
color: #0f172a;
|
}
|
|
:deep(.el-input__inner::placeholder) {
|
color: #a5b4c7;
|
}
|
|
.captcha-row {
|
display: grid;
|
grid-template-columns: minmax(0, 1fr) 120px;
|
gap: 12px;
|
width: 100%;
|
align-items: stretch;
|
}
|
|
.captcha-input {
|
min-width: 0;
|
}
|
|
.captcha-img-wrap {
|
width: 120px;
|
min-width: 120px;
|
height: 46px;
|
padding: 0;
|
border: 1px solid rgba(191, 219, 254, 0.78);
|
border-radius: 14px;
|
cursor: pointer;
|
overflow: hidden;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.42) 0%, rgba(236, 244, 255, 0.48) 100%);
|
transition: border-color 0.2s ease, transform 0.2s ease;
|
}
|
|
.captcha-img-wrap:hover {
|
border-color: #93c5fd;
|
transform: translateY(-1px);
|
}
|
|
.captcha-img {
|
width: 100%;
|
height: 100%;
|
object-fit: cover;
|
display: block;
|
}
|
|
.captcha-placeholder {
|
font-size: 12px;
|
color: #64748b;
|
}
|
|
.submit-btn {
|
width: 100%;
|
height: 48px;
|
margin-top: 8px;
|
border: 0;
|
border-radius: 14px;
|
background: linear-gradient(135deg, #2563eb, #60a5fa);
|
box-shadow: 0 14px 28px rgba(37, 99, 235, 0.16);
|
}
|
|
.submit-btn:hover {
|
opacity: 0.96;
|
}
|
|
@media (max-width: 640px) {
|
.wrap {
|
padding: 16px;
|
}
|
|
.card {
|
border-radius: 22px;
|
}
|
|
.title {
|
font-size: 26px;
|
}
|
|
.captcha-row {
|
grid-template-columns: 1fr 110px;
|
gap: 10px;
|
}
|
|
.captcha-img-wrap {
|
width: 110px;
|
min-width: 110px;
|
}
|
}
|
</style>
|