Files
vue-tickets/src/views/LoginView.vue
2026-05-19 16:36:35 +08:00

360 lines
8.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="ark-wrapper">
<div class="grid-bg"></div>
<div class="scan-line"></div>
<div class="login-container">
<transition name="ark-fade" appear>
<div class="ark-card">
<div class="corner-decorator top-left"></div>
<div class="corner-decorator bottom-right"></div>
<div class="card-header">
<div class="brand-icon">
<el-icon>
<DataBoard />
</el-icon>
</div>
<div class="brand-text">
<h1 class="title">方舟工单</h1>
<span class="sub-text">ARK TICKET SYSTEM v4.0</span>
</div>
</div>
<div class="system-monitor">
<span class="label">STATUS:</span>
<span class="value online">ENCRYPTED CONNECTION</span>
</div>
<el-form :model="loginForm" :rules="loginRules" ref="loginFormRef" class="ark-form">
<el-form-item prop="username">
<div class="input-prefix">ID</div>
<el-input v-model="loginForm.username" placeholder="员工编号" class="ark-input" />
</el-form-item>
<el-form-item prop="password">
<div class="input-prefix">KEY</div>
<el-input v-model="loginForm.password" type="password" placeholder="访问密钥" show-password class="ark-input"
@keyup.enter="handleLogin" />
</el-form-item>
<div class="options-bar">
<el-checkbox v-model="rememberMe" @keyup.enter="handleLogin">保持认证状态</el-checkbox>
</div>
<!--loading为true时候,会转圈圈-->
<el-button type="primary" class="ark-btn" @click="handleLogin" :loading="loading">
<div class="btn-content">
<span class="main-btn-text">INITIALIZE / 启动</span>
</div>
</el-button>
</el-form>
<div class="card-footer">
<span class="code">SECURE PROTOCOL 801-ARK</span>
</div>
</div>
</transition>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from "vue";
import { DataBoard } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { userApi } from "@/api/ticket";
import { useRoute, useRouter } from "vue-router";
const router = useRouter();
const route = useRoute();
const loginForm = reactive({ username: "", password: "" });
const rememberMe = ref(false);
const loading = ref(false);
const loginFormRef = ref(null);
const loginRules = reactive({
username: [{ required: true, message: "请输入员工编号", trigger: "blur" }],
password: [{ required: true, message: "请输入密钥", trigger: "blur" }],
});
const handleLogin = async () => {
if (!loginFormRef.value) return;
try {
await loginFormRef.value.validate();
loading.value = true;
const res = await userApi.login(loginForm.username, loginForm.password);
if (res.code === 200) {
// ThinkPHP 会自动通过 Set-Cookie 下发 session,浏览器自动接管,无需手动存 token
const userInfo = res.data;
const userInfoStr = JSON.stringify(userInfo);
// 用户信息存前端,仅用于显示用户名和角色路由判断
const storage = rememberMe.value ? localStorage : sessionStorage;
storage.setItem("user_info", userInfoStr);
ElMessage({
message: "身份验证通过,方舟系统加载中...",
type: "success",
duration: 300,
});
// 触发路由守卫,自动处理跳转
await router.push(route.query.redirect || "/");
} else {
ElMessage.error(res.message || "登录失败,请检查账号和密码");
}
} catch (error) {
if (error.name !== "ValidationError") {
ElMessage.error("登录请求异常,请稍后重试");
}
} finally {
loading.value = false;
}
};
</script>
<style scoped>
/* 1. 全局背景:工业网格风格 */
.ark-wrapper {
position: relative;
width: 100vw;
height: 100vh;
background-color: #0b111a;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.grid-bg {
position: absolute;
inset: 0;
background-image: linear-gradient(rgba(0, 242, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 242, 255, 0.05) 1px, transparent 1px);
background-size: 40px 40px;
mask-image: radial-gradient(circle at center, black, transparent 80%);
}
.scan-line {
position: absolute;
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, rgba(0, 242, 255, 0.2), transparent);
top: 0;
animation: scan 4s linear infinite;
}
@keyframes scan {
0% {
top: -10%;
}
100% {
top: 110%;
}
}
/* 2. 方舟卡片 */
.ark-card {
position: relative;
width: 420px;
background: rgba(13, 22, 34, 0.85);
border: 1px solid rgba(0, 242, 255, 0.3);
padding: 40px;
box-shadow: 0 0 40px rgba(0, 242, 255, 0.1), inset 0 0 20px rgba(0, 242, 255, 0.05);
backdrop-filter: blur(10px);
}
/* 装饰性角标 */
.corner-decorator {
position: absolute;
width: 20px;
height: 20px;
border-color: #00f2ff;
border-style: solid;
}
.top-left {
top: -2px;
left: -2px;
border-width: 3px 0 0 3px;
}
.bottom-right {
bottom: -2px;
right: -2px;
border-width: 0 3px 3px 0;
}
.card-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 30px;
}
.brand-icon {
background: rgba(0, 242, 255, 0.1);
padding: 10px;
border: 1px solid #00f2ff;
display: flex;
color: #00f2ff;
clip-path: polygon(20% 0%, 100% 0%, 100% 80%, 80% 100%, 0% 100%, 0% 20%);
}
.title {
color: #fff;
font-size: 24px;
margin: 0;
letter-spacing: 2px;
}
.sub-text {
color: #00f2ff;
font-size: 10px;
opacity: 0.7;
}
/* 3. 监控条 */
.system-monitor {
background: rgba(0, 0, 0, 0.4);
padding: 5px 12px;
margin-bottom: 25px;
border-left: 3px solid #00f2ff;
font-family: monospace;
font-size: 11px;
}
.system-monitor .label {
color: #64748b;
margin-right: 10px;
}
.system-monitor .value {
color: #22c55e;
text-shadow: 0 0 5px #22c55e;
}
/* 4. 输入框定制 */
.input-prefix {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
z-index: 10;
font-size: 10px;
color: #00f2ff;
font-weight: bold;
opacity: 0.6;
}
:deep(.ark-input .el-input__wrapper) {
background: rgba(0, 242, 255, 0.03) !important;
box-shadow: none !important;
border: 1px solid rgba(0, 242, 255, 0.2);
border-radius: 0;
/* 工业风通常使用直角 */
height: 50px;
padding-left: 45px;
}
:deep(.ark-input .is-focus) {
border-color: #00f2ff !important;
background: rgba(0, 242, 255, 0.08) !important;
}
/* 5. 启动按钮 */
.ark-btn {
width: 100%;
height: 50px;
background: transparent !important;
border: 1px solid #00f2ff !important;
color: #00f2ff !important;
border-radius: 0;
position: relative;
transition: all 0.3s;
font-weight: bold;
}
.ark-btn:hover {
background: #00f2ff !important;
color: #000 !important;
box-shadow: 0 0 20px rgba(0, 242, 255, 0.4);
}
.options-bar {
margin: 15px 0 25px;
}
:deep(.el-checkbox__label) {
color: #64748b;
font-size: 12px;
}
:deep(.el-checkbox__inner) {
background: transparent;
border-color: #334155;
}
.card-footer {
margin-top: 30px;
text-align: right;
border-top: 1px dashed rgba(0, 242, 255, 0.1);
padding-top: 15px;
}
.code {
font-family: monospace;
font-size: 10px;
color: #334155;
}
/* 入场动画 */
.ark-fade-enter-active {
transition: all 0.6s ease-out;
}
.ark-fade-enter-from {
opacity: 0;
transform: scale(1.1);
filter: brightness(2);
}
/* 1. 核心修复:禁止卡片内非交互元素的文本选中 */
.ark-card,
.card-header,
.brand-text,
.title,
.sub-text,
.system-monitor,
.options-bar,
.card-footer,
.code,
.input-prefix {
user-select: none;
/* 禁止选中文字 */
-webkit-user-select: none;
/* 兼容 Safari */
-moz-user-select: none;
/* 兼容 Firefox */
-ms-user-select: none;
/* 兼容 IE */
cursor: default;
/* 强制鼠标指针为标准箭头,不变成 I 型 */
}
/* 2. 确保输入框内部依然可以正常输入和选中(不受父级影响) */
:deep(.el-input__inner) {
user-select: text !important;
cursor: text !important;
}
/* 3. 确保复选框的 Label 也不会被选中(防止点击勾选时变蓝) */
:deep(.el-checkbox) {
user-select: none;
}
</style>