Files
wecom_it_smart_desk/frontend-admin/src/views/Login.vue
T

278 lines
7.0 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.
<!--
=============================================================================
IT智能服务台 管理员登录页 (v1.1, 2026-07-04)
=============================================================================
说明从用户ID+姓名登录改为账号密码+OTP表单登录
- 复用坐席端登录 APIPOST /api/agents/login
- 登录后检查 role === 'admin' admin 提示"无管理权限"
- 支持账号密码+OTP认证
-->
<template>
<div class="login-page">
<!-- 背景装饰 -->
<div class="login-bg">
<div class="login-bg-gradient"></div>
</div>
<!-- 登录卡片 -->
<div class="login-card">
<!-- Logo -->
<div class="login-logo">
<div class="login-logo-icon">
<el-icon :size="28"><Headset /></el-icon>
</div>
<h1 class="login-title">IT智能服务台</h1>
<p class="login-subtitle">管理后台</p>
</div>
<!-- 登录表单 -->
<el-form
ref="formRef"
:model="loginForm"
:rules="rules"
label-position="top"
@submit.prevent="handleLogin"
>
<el-form-item label="账号" prop="userId">
<el-input
v-model="loginForm.userId"
placeholder="请输入账号(如 sxn"
size="large"
:prefix-icon="User"
@keydown.enter="handleLogin"
/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
size="large"
:prefix-icon="Lock"
show-password
@keydown.enter="handleLogin"
/>
</el-form-item>
<!-- OTP 输入区: 仅在 requireOtp 时显示 -->
<el-form-item v-if="requireOtp" label="OTP 验证码" prop="otpCode">
<el-input
v-model="loginForm.otpCode"
placeholder="请输入 Google Authenticator 验证码"
size="large"
:prefix-icon="Key"
maxlength="6"
@keydown.enter="handleLogin"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="large"
:loading="adminStore.logging"
:disabled="adminStore.logging"
@click="handleLogin"
class="login-btn"
>
{{ adminStore.logging ? '登录中...' : (requireOtp ? '验证 OTP' : '登录管理后台') }}
</el-button>
</el-form-item>
</el-form>
<!-- 错误提示 -->
<el-alert
v-if="errorMsg"
:title="errorMsg"
type="error"
show-icon
:closable="true"
@close="errorMsg = ''"
style="margin-top: 16px"
/>
<!-- 提示信息 -->
<div class="login-tips">
<el-icon :size="14"><InfoFilled /></el-icon>
<span>仅限组长admin角色登录管理后台</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// ==========================================================================
// 依赖导入
// ==========================================================================
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { useAdminStore } from '@/stores/admin'
import type { FormInstance, FormRules } from 'element-plus'
import { User, Lock, Key, InfoFilled, Headset } from '@element-plus/icons-vue'
// ==========================================================================
// Store
// ==========================================================================
const router = useRouter()
const adminStore = useAdminStore()
// ==========================================================================
// 表单状态
// ==========================================================================
/** 表单引用 */
const formRef = ref<FormInstance>()
/** 登录表单数据 */
const loginForm = reactive({
userId: '',
password: '',
otpCode: '',
})
/** 是否需要 OTP 验证 */
const requireOtp = ref(false)
/** 错误信息 */
const errorMsg = ref<string>('')
/** 表单校验规则 */
const rules: FormRules = {
userId: [
{ required: true, message: '请输入账号', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
],
otpCode: [
{ required: true, message: '请输入 OTP 验证码', trigger: 'blur' },
{ len: 6, message: '验证码为 6 位数字', trigger: 'blur' },
],
}
// ==========================================================================
// 方法
// ==========================================================================
/**
* 处理登录
*/
async function handleLogin(): Promise<void> {
// 表单校验
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
errorMsg.value = ''
try {
await adminStore.login(
loginForm.userId.trim(),
loginForm.password,
loginForm.otpCode.trim() || undefined
)
} catch (error: unknown) {
// 检查是否需要 OTP 验证
if (error instanceof Error && error.message === 'require_otp') {
requireOtp.value = true
loginForm.otpCode = ''
ElMessage.info('请输入 OTP 验证码')
return
}
const errMsg = error instanceof Error ? error.message : '登录失败,请重试'
errorMsg.value = errMsg
}
}
</script>
<style scoped>
/* 登录页面容器 */
.login-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: var(--bg-primary);
}
/* 背景装饰 */
.login-bg {
position: absolute;
inset: 0;
overflow: hidden;
}
.login-bg-gradient {
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(ellipse at center, rgba(59, 130, 246, 0.08) 0%, transparent 60%);
animation: rotate 30s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* 登录卡片 */
.login-card {
position: relative;
z-index: 1;
width: 400px;
padding: 40px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
}
/* Logo */
.login-logo {
text-align: center;
margin-bottom: 32px;
}
.login-logo-icon {
width: 56px;
height: 56px;
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
color: white;
margin: 0 auto 16px;
}
.login-title {
font-size: 22px;
font-weight: 700;
color: var(--text-primary);
margin: 0 0 4px;
}
.login-subtitle {
font-size: 13px;
color: var(--text-muted);
margin: 0;
}
/* 登录按钮 */
.login-btn {
width: 100%;
margin-top: 8px;
}
/* 提示信息 */
.login-tips {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
margin-top: 20px;
font-size: 12px;
color: var(--text-muted);
}
</style>