问题工单后台管理
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

423 lines
11 KiB

2 months ago
<template>
<div class="login-container">
<div class="login-box">
<div class="login-content">
<div class="login-header">
<div class="logo-wrapper">
<img src="@/assets/icons/logo.png" alt="logo" class="logo-img" />
</div>
<h1 class="login-title">问题工单管理系统</h1>
<div class="title-divider">
<span class="line"></span>
<span class="dot"></span>
<span class="line"></span>
</div>
<p class="login-subtitle">欢迎使用工单管理系统</p>
</div>
<a-form layout="vertical" :model="state.formInline" @submit.prevent="handleSubmit">
<a-form-item>
<a-input
v-model:value="state.formInline.username"
size="large"
placeholder="请输入用户名"
class="custom-input"
>
<template #prefix><user-outlined /></template>
</a-input>
</a-form-item>
<!-- 邮箱 -->
<a-form-item v-if="state.isRegister">
<a-input
v-model:value="state.formInline.email"
size="large"
placeholder="请输入邮箱"
class="custom-input"
>
<template #prefix><safety-outlined /></template>
</a-input>
</a-form-item>
<a-form-item>
<a-input
v-model:value="state.formInline.password"
size="large"
type="password"
placeholder="请输入密码"
autocomplete="new-password"
class="custom-input"
>
<template #prefix><lock-outlined /></template>
</a-input>
</a-form-item>
<a-form-item v-if="state.isRegister">
<a-input
v-model:value="state.formInline.confirmPassword"
size="large"
type="password"
placeholder="请确认密码"
autocomplete="new-password"
class="custom-input"
>
<template #prefix><lock-outlined /></template>
</a-input>
</a-form-item>
<a-form-item>
<a-button
type="primary"
html-type="submit"
size="large"
:loading="state.loading"
class="login-button"
block
>
{{ state.isRegister ? '注册' : '登录' }}
</a-button>
</a-form-item>
<div class="form-footer flex justify-center">
<a @click="toggleMode" class="text-[#e6400b]">{{
state.isRegister ? '已有账号?去登录' : '没有账号?去注册'
}}</a>
</div>
</a-form>
</div>
2 months ago
</div>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import { UserOutlined, LockOutlined, SafetyOutlined } from '@ant-design/icons-vue';
import { useRoute, useRouter } from 'vue-router';
import { message, Modal } from 'ant-design-vue';
import { useUserStore } from '@/store/modules/user';
import { to } from '@/utils/awaitTo';
import { register } from '@/api/login';
2 months ago
const state = reactive({
loading: false,
isRegister: false, // 新增:控制是否为注册模式
2 months ago
captcha: '',
formInline: {
username: 'admin',
password: '123',
email: '', // 新增:邮箱字段
confirmPassword: '', // 新增:确认密码字段
2 months ago
},
});
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
// 新增:切换登录/注册模式
const toggleMode = () => {
state.isRegister = !state.isRegister;
state.formInline.username = '';
state.formInline.password = '';
state.formInline.confirmPassword = '';
state.formInline.email = '';
2 months ago
};
// 修改:处理表单提交
2 months ago
const handleSubmit = async () => {
const { username, password, confirmPassword } = state.formInline;
if (username.trim() === '' || password.trim() === '') {
return message.warning('用户名或密码不能为空!');
}
if (state.isRegister) {
if (password !== confirmPassword) {
return message.warning('两次输入的密码不一致!');
}
if (state.formInline.email.trim() === '') {
return message.warning('邮箱不能为空!');
}
if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(state.formInline.email)) {
return message.warning('请输入有效的邮箱地址!');
}
// TODO: 调用注册接口
state.loading = true;
try {
await register({
...state.formInline,
account: state.formInline.username,
sex: 1,
});
message.success('注册成功,请联系系统管理员通过审核');
setTimeout(() => {
state.isRegister = false; // 注册成功后切换到登录模式
}, 1000);
} catch (err: any) {
Modal.error({
title: '提示',
content: err.message || '注册失败',
});
}
state.loading = false;
return;
}
// 原有的登录逻辑保持不变
2 months ago
message.loading('登录中...', 0);
state.loading = true;
const [err] = await to(userStore.login(state.formInline));
if (err) {
Modal.error({
title: () => '提示',
content: () => err.message,
});
} else {
message.success('登录成功!');
const isAdmin = userStore.userInfo?.isAdmin;
console.log('isAdmin: ', isAdmin);
if (isAdmin === 0) {
const clientRoutes = ['/entrance', '/issue', 'knowledge'];
let replacePath = '';
if (route.query.redirect) {
if (clientRoutes.includes(route.query.redirect as string)) {
replacePath = route.query.redirect as string;
} else {
replacePath = '/entrance';
}
} else {
replacePath = '/entrance';
}
setTimeout(() => router.replace((replacePath as string) ?? '/'));
} else {
setTimeout(() => router.replace((route.query.redirect as string) ?? '/'));
}
2 months ago
}
state.loading = false;
message.destroy();
};
</script>
<style lang="less" scoped>
.login-container {
display: flex;
width: 100vw;
height: 100vh;
// background: url('@/assets/images/background.jpg') no-repeat center center fixed;
// background-size: cover;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
// background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
// &::before,
// &::after {
// content: '';
// position: absolute;
// width: 1000px;
// height: 1000px;
// border-radius: 50%;
// background: linear-gradient(
// 135deg,
// rgba(255, 255, 255, 0.1) 0%,
// rgba(255, 255, 255, 0.05) 100%
// );
// animation: float 20s infinite linear;
// }
// &::before {
// top: -400px;
// right: -200px;
// animation-delay: -5s;
// }
// &::after {
// bottom: -400px;
// left: -200px;
// animation-duration: 25s;
// }
}
.login-box {
width: 460px;
padding: 40px;
background: rgba(255, 255, 255, 0.4);
border-radius: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(255, 255, 255, 0.1) inset;
backdrop-filter: blur(12px);
animation: fadeIn 0.5s ease-out;
position: relative;
z-index: 1;
&::before {
content: '';
position: absolute;
inset: 0;
border-radius: 24px;
padding: 2px;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.1));
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
}
@keyframes float {
0% {
transform: rotate(0deg) translate(0, 0);
}
50% {
transform: rotate(180deg) translate(100px, 50px);
}
100% {
transform: rotate(360deg) translate(0, 0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.login-content {
.login-header {
text-align: center;
margin-bottom: 40px;
.logo-wrapper {
margin-bottom: 16px;
display: flex;
align-items: center;
justify-content: center;
.logo-img {
width: 150px;
height: 64px;
object-fit: contain;
}
}
.login-title {
font-size: 30px;
font-weight: 700;
background: linear-gradient(135deg, #e6400b 0%, #ec7049 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 16px;
letter-spacing: 2px;
}
.title-divider {
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0;
.line {
width: 40px;
height: 1px;
background: linear-gradient(90deg, transparent, #e6400b);
&:last-child {
background: linear-gradient(90deg, #ec7049, transparent);
}
}
.dot {
width: 6px;
height: 6px;
background: #e6400b;
border-radius: 50%;
margin: 0 8px;
}
}
.login-subtitle {
font-size: 15px;
color: #666;
margin: 0;
font-weight: 500;
position: relative;
display: inline-block;
&::after {
content: '';
position: absolute;
bottom: -4px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 2px;
background: linear-gradient(135deg, #e6400b 0%, #ec7049 100%);
transition: width 0.3s ease;
}
&:hover::after {
width: 100%;
}
2 months ago
}
}
}
.custom-input {
border-radius: 8px;
:deep(.ant-input) {
padding: 12px 16px;
font-size: 14px;
&:focus {
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
}
}
:deep(.ant-input-prefix) {
color: #666;
margin-right: 10px;
2 months ago
}
}
.captcha-img {
height: 100%;
border-radius: 4px;
cursor: pointer;
transition: opacity 0.3s;
&:hover {
opacity: 0.8;
}
}
.login-button {
height: 46px;
font-size: 16px;
border-radius: 8px;
background: linear-gradient(135deg, #e6400b 0%, #ec7049 100%);
border: none;
transition: all 0.3s;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
2 months ago
</style>