问题工单后台管理
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.

298 lines
7.3 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/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>
<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>
<a-input
v-model:value="state.formInline.verifyCode"
placeholder="请输入验证码"
:maxlength="4"
size="large"
class="custom-input"
>
<template #prefix><SafetyOutlined /></template>
<template #suffix>
<img
:src="state.captcha"
class="captcha-img"
@click="setCaptcha"
/>
</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
>
登录
</a-button>
</a-form-item>
</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 { getImageCaptcha } from '@/api/login';
import { to } from '@/utils/awaitTo';
const state = reactive({
loading: false,
captcha: '',
formInline: {
username: 'zhangsan', //账号
password: '123456', //密码
verifyCode: '',
captchaId: '',
// captchaVerification: 'PfcH6mgr8tpXuMWFjvW6YVaqrswIuwmWI5dsVZSg7sGpWtDCUbHuDEXl3cFB1+VvCC/rAkSwK8Fad52FSuncVg==', // 验证码
// socialType: 10,
// socialCode: '1024',
// socialState: '9b2ffbc1-7425-4155-9894-9d5c08541d62',
// socialCodeValid: true,
},
});
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const setCaptcha = async () => {
const { id, img } = await getImageCaptcha({ width: 100, height: 50 });
state.captcha = img;
state.formInline.captchaId = id;
};
// setCaptcha();
const handleSubmit = async () => {
const { username, password, verifyCode } = state.formInline;
// if (username.trim() == '' || password.trim() == '') {
// return message.warning('用户名或密码不能为空!');
// }
// if (!verifyCode) {
// return message.warning('请输入验证码!');
// }
message.loading('登录中...', 0);
state.loading = true;
console.log(state.formInline);
// params.password = md5(password)
const [err] = await to(userStore.login(state.formInline));
if (err) {
console.log('err: ', err);
Modal.error({
title: () => '提示',
content: () => err.message,
});
} else {
message.success('登录成功!');
console.log('route.query.redirect: ', route.query.redirect);
setTimeout(() => router.replace((route.query.redirect as string) ?? '/'));
}
state.loading = false;
message.destroy();
};
</script>
<style lang="less" scoped>
.login-container {
display: flex;
width: 100vw;
height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
align-items: center;
justify-content: center;
}
2 months ago
.login-box {
width: 460px;
padding: 40px;
background: rgba(255, 255, 255, 0.95);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
animation: fadeIn 0.5s ease-out;
}
.login-content {
.login-header {
text-align: center;
margin-bottom: 40px;
.logo-wrapper {
margin-bottom: 16px;
.logo-img {
width: 64px;
height: 64px;
object-fit: contain;
}
}
.login-title {
font-size: 32px;
font-weight: 700;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 16px;
letter-spacing: 2px;
}
.title-divider {
2 months ago
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0;
.line {
width: 40px;
height: 1px;
background: linear-gradient(90deg, transparent, #667eea);
&:last-child {
background: linear-gradient(90deg, #764ba2, transparent);
}
}
.dot {
width: 6px;
height: 6px;
background: #667eea;
border-radius: 50%;
margin: 0 8px;
2 months ago
}
}
.login-subtitle {
font-size: 16px;
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, #667eea 0%, #764ba2 100%);
transition: width 0.3s ease;
}
&:hover::after {
2 months ago
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);
2 months ago
}
}
:deep(.ant-input-prefix) {
color: #666;
margin-right: 10px;
}
}
.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, #667eea 0%, #764ba2 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>