考勤率页面搭建
This commit is contained in:
parent
d0e9ec8a2d
commit
11adf3de11
|
|
@ -4,8 +4,36 @@ import request from '@/utils/request'
|
|||
// 获取考勤率列表
|
||||
export function getAttendanceRateList(query) {
|
||||
return request({
|
||||
url: '/system/attDetails/getAttendanceRateList',
|
||||
url: '/system/attRate/getAttRateTypeList',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 获取考勤率详情
|
||||
export function getAttendanceRateDetail(query) {
|
||||
return request({
|
||||
url: '/system/attRate/getAttRateTypeDetailsList',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 获取迟到早退旷工列表
|
||||
export function getLateEarlyAbsentList(query) {
|
||||
return request({
|
||||
url: '/system/attRate/getAttAbnormalList',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 获取迟到早退旷工详情
|
||||
export function getAttAbnormalDetailsList(query) {
|
||||
return request({
|
||||
url: '/system/attRate/getAttAbnormalDetailsList',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,132 +1,98 @@
|
|||
<template>
|
||||
<div class="login">
|
||||
<!-- 动态背景 -->
|
||||
<div class="animated-bg">
|
||||
<div class="particles">
|
||||
<div
|
||||
class="particle"
|
||||
v-for="n in 50"
|
||||
:key="n"
|
||||
:style="getParticleStyle(n)"
|
||||
></div>
|
||||
</div>
|
||||
<div class="grid-overlay"></div>
|
||||
<div class="gradient-orb orb-1"></div>
|
||||
<div class="gradient-orb orb-2"></div>
|
||||
<div class="gradient-orb orb-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- 登录表单卡片 -->
|
||||
<div class="login-container">
|
||||
<el-form
|
||||
ref="loginForm"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
class="login-form"
|
||||
<el-form
|
||||
ref="loginForm"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
class="login-form"
|
||||
>
|
||||
<h3 class="title">考勤后台管理系统</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
type="text"
|
||||
auto-complete="off"
|
||||
placeholder="账号"
|
||||
>
|
||||
<svg-icon
|
||||
slot="prefix"
|
||||
icon-class="user"
|
||||
class="el-input__icon input-icon"
|
||||
/>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
auto-complete="off"
|
||||
placeholder="密码"
|
||||
show-password
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon
|
||||
slot="prefix"
|
||||
icon-class="password"
|
||||
class="el-input__icon input-icon"
|
||||
/>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code" v-if="captchaEnabled">
|
||||
<el-input
|
||||
v-model="loginForm.code"
|
||||
auto-complete="off"
|
||||
placeholder="验证码"
|
||||
style="width: 63%"
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon
|
||||
slot="prefix"
|
||||
icon-class="validCode"
|
||||
class="el-input__icon input-icon"
|
||||
/>
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" @click="getCode" class="login-code-img" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-checkbox
|
||||
v-model="loginForm.rememberMe"
|
||||
style="margin: 0px 0px 0px 0px"
|
||||
>记住密码</el-checkbox
|
||||
>
|
||||
<div class="form-header">
|
||||
<div class="logo-wrapper">
|
||||
<div class="logo-glow"></div>
|
||||
</div>
|
||||
<h3 class="title">
|
||||
<span class="title-text">考勤后台管理系统</span>
|
||||
<span class="title-line"></span>
|
||||
</h3>
|
||||
<p class="subtitle">Attendance Management System</p>
|
||||
|
||||
<el-form-item>
|
||||
<el-checkbox
|
||||
v-model="loginForm.userAgreement"
|
||||
style="margin: 0px 0px 0px 0px"
|
||||
>我已阅读并同意
|
||||
</el-checkbox>
|
||||
<div
|
||||
@click="goUserAgreement()"
|
||||
style="color: #02a7f0; cursor: pointer; margin: -36px 0px 0px 150px"
|
||||
>
|
||||
《用户协议》
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
type="text"
|
||||
auto-complete="off"
|
||||
placeholder="请输入账号"
|
||||
class="modern-input"
|
||||
<el-form-item style="width: 100%">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="medium"
|
||||
type="primary"
|
||||
style="width: 100%"
|
||||
@click.native.prevent="handleLogin"
|
||||
>
|
||||
<span v-if="!loading">登 录</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
<div style="float: right" v-if="register">
|
||||
<router-link class="link-type" :to="'/register'"
|
||||
>立即注册</router-link
|
||||
>
|
||||
<svg-icon
|
||||
slot="prefix"
|
||||
icon-class="user"
|
||||
class="el-input__icon input-icon"
|
||||
/>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
auto-complete="off"
|
||||
placeholder="请输入密码"
|
||||
show-password
|
||||
class="modern-input"
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon
|
||||
slot="prefix"
|
||||
icon-class="password"
|
||||
class="el-input__icon input-icon"
|
||||
/>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="code" v-if="captchaEnabled">
|
||||
<div class="code-wrapper">
|
||||
<el-input
|
||||
v-model="loginForm.code"
|
||||
auto-complete="off"
|
||||
placeholder="验证码"
|
||||
class="modern-input code-input"
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon
|
||||
slot="prefix"
|
||||
icon-class="validCode"
|
||||
class="el-input__icon input-icon"
|
||||
/>
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" @click="getCode" class="login-code-img" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<div class="form-options">
|
||||
<el-checkbox v-model="loginForm.rememberMe" class="remember-checkbox">
|
||||
记住密码
|
||||
</el-checkbox>
|
||||
</div>
|
||||
|
||||
<el-form-item class="agreement-item">
|
||||
<el-checkbox
|
||||
v-model="loginForm.userAgreement"
|
||||
class="agreement-checkbox"
|
||||
>
|
||||
我已阅读并同意
|
||||
</el-checkbox>
|
||||
<span @click="goUserAgreement()" class="agreement-link"
|
||||
>《用户协议》</span
|
||||
>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item style="width: 100%">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="medium"
|
||||
type="primary"
|
||||
class="login-button"
|
||||
@click.native.prevent="handleLogin"
|
||||
>
|
||||
<span v-if="!loading">登 录</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
<div class="register-link" v-if="register">
|
||||
<router-link class="link-type" :to="'/register'"
|
||||
>立即注册</router-link
|
||||
>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-dialog
|
||||
:title="title"
|
||||
|
|
@ -351,18 +317,6 @@ export default {
|
|||
this.getCookie();
|
||||
},
|
||||
methods: {
|
||||
getParticleStyle(index) {
|
||||
const left = Math.random() * 100;
|
||||
const top = Math.random() * 100;
|
||||
const delay = Math.random() * 20;
|
||||
const duration = 15 + Math.random() * 10;
|
||||
return {
|
||||
left: `${left}%`,
|
||||
top: `${top}%`,
|
||||
animationDelay: `${delay}s`,
|
||||
animationDuration: `${duration}s`,
|
||||
};
|
||||
},
|
||||
getCode() {
|
||||
getCodeImg().then((res) => {
|
||||
this.captchaEnabled =
|
||||
|
|
@ -444,512 +398,50 @@ export default {
|
|||
|
||||
<style rel="stylesheet/scss" lang="scss">
|
||||
.login {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
#667eea 0%,
|
||||
#764ba2 25%,
|
||||
#f093fb 50%,
|
||||
#4facfe 75%,
|
||||
#00f2fe 100%
|
||||
);
|
||||
background-size: 400% 400%;
|
||||
animation: gradientShift 15s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes gradientShift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
// 动态背景层
|
||||
.animated-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
background-image: url("../assets/images/login-background.jpg");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
// 粒子效果
|
||||
.particles {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.particle {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border-radius: 50%;
|
||||
animation: float 20s infinite ease-in-out;
|
||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0) translateX(0) scale(1);
|
||||
opacity: 0.3;
|
||||
}
|
||||
25% {
|
||||
transform: translateY(-100px) translateX(50px) scale(1.2);
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-200px) translateX(-30px) scale(0.8);
|
||||
opacity: 0.4;
|
||||
}
|
||||
75% {
|
||||
transform: translateY(-150px) translateX(80px) scale(1.1);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
// 网格覆盖层
|
||||
.grid-overlay {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: linear-gradient(
|
||||
rgba(255, 255, 255, 0.03) 1px,
|
||||
transparent 1px
|
||||
),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
animation: gridMove 20s linear infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes gridMove {
|
||||
0% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: translate(50px, 50px);
|
||||
}
|
||||
}
|
||||
|
||||
// 渐变光球
|
||||
.gradient-orb {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(80px);
|
||||
opacity: 0.4;
|
||||
animation: orbFloat 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.orb-1 {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(102, 126, 234, 0.8) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
top: -200px;
|
||||
left: -200px;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.orb-2 {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(118, 75, 162, 0.8) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
bottom: -250px;
|
||||
right: -250px;
|
||||
animation-delay: 7s;
|
||||
}
|
||||
|
||||
.orb-3 {
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(79, 172, 254, 0.8) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
top: 50%;
|
||||
right: 10%;
|
||||
animation-delay: 14s;
|
||||
}
|
||||
|
||||
@keyframes orbFloat {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
33% {
|
||||
transform: translate(50px, -50px) scale(1.1);
|
||||
}
|
||||
66% {
|
||||
transform: translate(-30px, 30px) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
// 登录容器
|
||||
.login-container {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
// 登录表单
|
||||
.login-form {
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
|
||||
width: 100%;
|
||||
padding: 40px 35px;
|
||||
animation: slideUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 表单头部
|
||||
.form-header {
|
||||
text-align: center;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.logo-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.logo-glow {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(102, 126, 234, 0.8),
|
||||
rgba(79, 172, 254, 0.8)
|
||||
);
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 0 20px rgba(102, 126, 234, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.7);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 0 0 20px rgba(102, 126, 234, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
|
||||
.title-text {
|
||||
display: block;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #e0e7ff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.title-line {
|
||||
display: block;
|
||||
width: 60px;
|
||||
height: 3px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.8),
|
||||
transparent
|
||||
);
|
||||
margin: 10px auto;
|
||||
border-radius: 2px;
|
||||
}
|
||||
margin: 0px auto 30px auto;
|
||||
text-align: center;
|
||||
color: #707070;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 10px 0 0 0;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
letter-spacing: 3px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
// 输入框样式
|
||||
.login-form {
|
||||
.el-form-item {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.modern-input {
|
||||
.el-input__inner {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 12px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.el-input__prefix {
|
||||
left: 15px;
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
width: 400px;
|
||||
padding: 25px 25px 5px 25px;
|
||||
.el-input {
|
||||
height: 38px;
|
||||
input {
|
||||
height: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
height: 48px;
|
||||
width: 16px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 16px;
|
||||
height: 39px;
|
||||
width: 14px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证码样式
|
||||
.code-wrapper {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
|
||||
.code-input {
|
||||
flex: 1;
|
||||
}
|
||||
.login-tip {
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.login-code {
|
||||
width: 120px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
width: 33%;
|
||||
height: 38px;
|
||||
float: right;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.login-code-img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 表单选项
|
||||
.form-options {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.remember-checkbox {
|
||||
::v-deep .el-checkbox__label {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
::v-deep .el-checkbox__input.is-checked .el-checkbox__inner {
|
||||
background-color: rgba(102, 126, 234, 0.8);
|
||||
border-color: rgba(102, 126, 234, 0.8);
|
||||
}
|
||||
|
||||
::v-deep .el-checkbox__inner {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
// 用户协议
|
||||
.agreement-item {
|
||||
margin-bottom: 25px;
|
||||
|
||||
.el-form-item__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.agreement-checkbox {
|
||||
::v-deep .el-checkbox__label {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
::v-deep .el-checkbox__input.is-checked .el-checkbox__inner {
|
||||
background-color: rgba(102, 126, 234, 0.8);
|
||||
border-color: rgba(102, 126, 234, 0.8);
|
||||
}
|
||||
|
||||
::v-deep .el-checkbox__inner {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
color: #60a5fa;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-left: 5px;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: #93c5fd;
|
||||
text-shadow: 0 0 8px rgba(96, 165, 250, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// 登录按钮
|
||||
.login-button {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(102, 126, 234, 0.9),
|
||||
rgba(79, 172, 254, 0.9)
|
||||
);
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.3),
|
||||
transparent
|
||||
);
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
|
||||
|
||||
&::before {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
::v-deep span {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 注册链接
|
||||
.register-link {
|
||||
text-align: right;
|
||||
margin-top: 15px;
|
||||
|
||||
.link-type {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
text-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部
|
||||
.el-login-footer {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
|
|
@ -957,65 +449,12 @@ export default {
|
|||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
color: #fff;
|
||||
font-family: Arial;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.login-container {
|
||||
max-width: 90%;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
padding: 30px 25px;
|
||||
}
|
||||
|
||||
.title .title-text {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
// 对话框样式优化
|
||||
::v-deep .el-dialog {
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
border-radius: 16px 16px 0 0;
|
||||
|
||||
.el-dialog__title {
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__body {
|
||||
padding: 30px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
#policy {
|
||||
color: #333;
|
||||
line-height: 1.8;
|
||||
|
||||
p {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__footer {
|
||||
padding: 20px 30px;
|
||||
border-top: 1px solid #eee;
|
||||
.login-code-img {
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,98 +1,132 @@
|
|||
<template>
|
||||
<div class="login">
|
||||
<el-form
|
||||
ref="loginForm"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
class="login-form"
|
||||
>
|
||||
<h3 class="title">考勤后台管理系统</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
type="text"
|
||||
auto-complete="off"
|
||||
placeholder="账号"
|
||||
>
|
||||
<svg-icon
|
||||
slot="prefix"
|
||||
icon-class="user"
|
||||
class="el-input__icon input-icon"
|
||||
/>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
auto-complete="off"
|
||||
placeholder="密码"
|
||||
show-password
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon
|
||||
slot="prefix"
|
||||
icon-class="password"
|
||||
class="el-input__icon input-icon"
|
||||
/>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code" v-if="captchaEnabled">
|
||||
<el-input
|
||||
v-model="loginForm.code"
|
||||
auto-complete="off"
|
||||
placeholder="验证码"
|
||||
style="width: 63%"
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon
|
||||
slot="prefix"
|
||||
icon-class="validCode"
|
||||
class="el-input__icon input-icon"
|
||||
/>
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" @click="getCode" class="login-code-img" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-checkbox
|
||||
v-model="loginForm.rememberMe"
|
||||
style="margin: 0px 0px 0px 0px"
|
||||
>记住密码</el-checkbox
|
||||
>
|
||||
|
||||
<el-form-item>
|
||||
<el-checkbox
|
||||
v-model="loginForm.userAgreement"
|
||||
style="margin: 0px 0px 0px 0px"
|
||||
>我已阅读并同意
|
||||
</el-checkbox>
|
||||
<!-- 动态背景 -->
|
||||
<div class="animated-bg">
|
||||
<div class="particles">
|
||||
<div
|
||||
@click="goUserAgreement()"
|
||||
style="color: #02a7f0; cursor: pointer; margin: -36px 0px 0px 150px"
|
||||
>
|
||||
《用户协议》
|
||||
</div>
|
||||
</el-form-item>
|
||||
class="particle"
|
||||
v-for="n in 50"
|
||||
:key="n"
|
||||
:style="getParticleStyle(n)"
|
||||
></div>
|
||||
</div>
|
||||
<div class="grid-overlay"></div>
|
||||
<div class="gradient-orb orb-1"></div>
|
||||
<div class="gradient-orb orb-2"></div>
|
||||
<div class="gradient-orb orb-3"></div>
|
||||
</div>
|
||||
|
||||
<el-form-item style="width: 100%">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="medium"
|
||||
type="primary"
|
||||
style="width: 100%"
|
||||
@click.native.prevent="handleLogin"
|
||||
>
|
||||
<span v-if="!loading">登 录</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
<div style="float: right" v-if="register">
|
||||
<router-link class="link-type" :to="'/register'"
|
||||
>立即注册</router-link
|
||||
>
|
||||
<!-- 登录表单卡片 -->
|
||||
<div class="login-container">
|
||||
<el-form
|
||||
ref="loginForm"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
class="login-form"
|
||||
>
|
||||
<div class="form-header">
|
||||
<div class="logo-wrapper">
|
||||
<div class="logo-glow"></div>
|
||||
</div>
|
||||
<h3 class="title">
|
||||
<span class="title-text">考勤后台管理系统</span>
|
||||
<span class="title-line"></span>
|
||||
</h3>
|
||||
<p class="subtitle">Attendance Management System</p>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
type="text"
|
||||
auto-complete="off"
|
||||
placeholder="请输入账号"
|
||||
class="modern-input"
|
||||
>
|
||||
<svg-icon
|
||||
slot="prefix"
|
||||
icon-class="user"
|
||||
class="el-input__icon input-icon"
|
||||
/>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
auto-complete="off"
|
||||
placeholder="请输入密码"
|
||||
show-password
|
||||
class="modern-input"
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon
|
||||
slot="prefix"
|
||||
icon-class="password"
|
||||
class="el-input__icon input-icon"
|
||||
/>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="code" v-if="captchaEnabled">
|
||||
<div class="code-wrapper">
|
||||
<el-input
|
||||
v-model="loginForm.code"
|
||||
auto-complete="off"
|
||||
placeholder="验证码"
|
||||
class="modern-input code-input"
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon
|
||||
slot="prefix"
|
||||
icon-class="validCode"
|
||||
class="el-input__icon input-icon"
|
||||
/>
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" @click="getCode" class="login-code-img" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<div class="form-options">
|
||||
<el-checkbox v-model="loginForm.rememberMe" class="remember-checkbox">
|
||||
记住密码
|
||||
</el-checkbox>
|
||||
</div>
|
||||
|
||||
<el-form-item class="agreement-item">
|
||||
<el-checkbox
|
||||
v-model="loginForm.userAgreement"
|
||||
class="agreement-checkbox"
|
||||
>
|
||||
我已阅读并同意
|
||||
</el-checkbox>
|
||||
<span @click="goUserAgreement()" class="agreement-link"
|
||||
>《用户协议》</span
|
||||
>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item style="width: 100%">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="medium"
|
||||
type="primary"
|
||||
class="login-button"
|
||||
@click.native.prevent="handleLogin"
|
||||
>
|
||||
<span v-if="!loading">登 录</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
<div class="register-link" v-if="register">
|
||||
<router-link class="link-type" :to="'/register'"
|
||||
>立即注册</router-link
|
||||
>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
:title="title"
|
||||
|
|
@ -317,6 +351,18 @@ export default {
|
|||
this.getCookie();
|
||||
},
|
||||
methods: {
|
||||
getParticleStyle(index) {
|
||||
const left = Math.random() * 100;
|
||||
const top = Math.random() * 100;
|
||||
const delay = Math.random() * 20;
|
||||
const duration = 15 + Math.random() * 10;
|
||||
return {
|
||||
left: `${left}%`,
|
||||
top: `${top}%`,
|
||||
animationDelay: `${delay}s`,
|
||||
animationDuration: `${duration}s`,
|
||||
};
|
||||
},
|
||||
getCode() {
|
||||
getCodeImg().then((res) => {
|
||||
this.captchaEnabled =
|
||||
|
|
@ -398,50 +444,512 @@ export default {
|
|||
|
||||
<style rel="stylesheet/scss" lang="scss">
|
||||
.login {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-image: url("../assets/images/login-background.jpg");
|
||||
background-size: cover;
|
||||
}
|
||||
.title {
|
||||
margin: 0px auto 30px auto;
|
||||
text-align: center;
|
||||
color: #707070;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
#667eea 0%,
|
||||
#764ba2 25%,
|
||||
#f093fb 50%,
|
||||
#4facfe 75%,
|
||||
#00f2fe 100%
|
||||
);
|
||||
background-size: 400% 400%;
|
||||
animation: gradientShift 15s ease infinite;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
@keyframes gradientShift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
// 动态背景层
|
||||
.animated-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 粒子效果
|
||||
.particles {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.particle {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border-radius: 50%;
|
||||
animation: float 20s infinite ease-in-out;
|
||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0) translateX(0) scale(1);
|
||||
opacity: 0.3;
|
||||
}
|
||||
25% {
|
||||
transform: translateY(-100px) translateX(50px) scale(1.2);
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-200px) translateX(-30px) scale(0.8);
|
||||
opacity: 0.4;
|
||||
}
|
||||
75% {
|
||||
transform: translateY(-150px) translateX(80px) scale(1.1);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
// 网格覆盖层
|
||||
.grid-overlay {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: linear-gradient(
|
||||
rgba(255, 255, 255, 0.03) 1px,
|
||||
transparent 1px
|
||||
),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
animation: gridMove 20s linear infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes gridMove {
|
||||
0% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: translate(50px, 50px);
|
||||
}
|
||||
}
|
||||
|
||||
// 渐变光球
|
||||
.gradient-orb {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(80px);
|
||||
opacity: 0.4;
|
||||
animation: orbFloat 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.orb-1 {
|
||||
width: 400px;
|
||||
padding: 25px 25px 5px 25px;
|
||||
.el-input {
|
||||
height: 38px;
|
||||
input {
|
||||
height: 38px;
|
||||
height: 400px;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(102, 126, 234, 0.8) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
top: -200px;
|
||||
left: -200px;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.orb-2 {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(118, 75, 162, 0.8) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
bottom: -250px;
|
||||
right: -250px;
|
||||
animation-delay: 7s;
|
||||
}
|
||||
|
||||
.orb-3 {
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(79, 172, 254, 0.8) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
top: 50%;
|
||||
right: 10%;
|
||||
animation-delay: 14s;
|
||||
}
|
||||
|
||||
@keyframes orbFloat {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
33% {
|
||||
transform: translate(50px, -50px) scale(1.1);
|
||||
}
|
||||
66% {
|
||||
transform: translate(-30px, 30px) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
// 登录容器
|
||||
.login-container {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
// 登录表单
|
||||
.login-form {
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
|
||||
width: 100%;
|
||||
padding: 40px 35px;
|
||||
animation: slideUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 表单头部
|
||||
.form-header {
|
||||
text-align: center;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.logo-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.logo-glow {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(102, 126, 234, 0.8),
|
||||
rgba(79, 172, 254, 0.8)
|
||||
);
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 0 20px rgba(102, 126, 234, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.7);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 0 0 20px rgba(102, 126, 234, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
|
||||
.title-text {
|
||||
display: block;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #e0e7ff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.title-line {
|
||||
display: block;
|
||||
width: 60px;
|
||||
height: 3px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.8),
|
||||
transparent
|
||||
);
|
||||
margin: 10px auto;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 10px 0 0 0;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
letter-spacing: 3px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
// 输入框样式
|
||||
.login-form {
|
||||
.el-form-item {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.modern-input {
|
||||
.el-input__inner {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 12px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.el-input__prefix {
|
||||
left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
height: 39px;
|
||||
width: 14px;
|
||||
margin-left: 2px;
|
||||
height: 48px;
|
||||
width: 16px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.login-tip {
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
color: #bfbfbf;
|
||||
|
||||
// 验证码样式
|
||||
.code-wrapper {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
|
||||
.code-input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.login-code {
|
||||
width: 33%;
|
||||
height: 38px;
|
||||
float: right;
|
||||
width: 120px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
img {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.login-code-img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 表单选项
|
||||
.form-options {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.remember-checkbox {
|
||||
::v-deep .el-checkbox__label {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
::v-deep .el-checkbox__input.is-checked .el-checkbox__inner {
|
||||
background-color: rgba(102, 126, 234, 0.8);
|
||||
border-color: rgba(102, 126, 234, 0.8);
|
||||
}
|
||||
|
||||
::v-deep .el-checkbox__inner {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
// 用户协议
|
||||
.agreement-item {
|
||||
margin-bottom: 25px;
|
||||
|
||||
.el-form-item__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.agreement-checkbox {
|
||||
::v-deep .el-checkbox__label {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
::v-deep .el-checkbox__input.is-checked .el-checkbox__inner {
|
||||
background-color: rgba(102, 126, 234, 0.8);
|
||||
border-color: rgba(102, 126, 234, 0.8);
|
||||
}
|
||||
|
||||
::v-deep .el-checkbox__inner {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
color: #60a5fa;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-left: 5px;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: #93c5fd;
|
||||
text-shadow: 0 0 8px rgba(96, 165, 250, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// 登录按钮
|
||||
.login-button {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(102, 126, 234, 0.9),
|
||||
rgba(79, 172, 254, 0.9)
|
||||
);
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.3),
|
||||
transparent
|
||||
);
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
|
||||
|
||||
&::before {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
::v-deep span {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 注册链接
|
||||
.register-link {
|
||||
text-align: right;
|
||||
margin-top: 15px;
|
||||
|
||||
.link-type {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
text-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部
|
||||
.el-login-footer {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
|
|
@ -449,12 +957,65 @@ export default {
|
|||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-family: Arial;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
z-index: 10;
|
||||
}
|
||||
.login-code-img {
|
||||
height: 38px;
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.login-container {
|
||||
max-width: 90%;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
padding: 30px 25px;
|
||||
}
|
||||
|
||||
.title .title-text {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
// 对话框样式优化
|
||||
::v-deep .el-dialog {
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
border-radius: 16px 16px 0 0;
|
||||
|
||||
.el-dialog__title {
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__body {
|
||||
padding: 30px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
#policy {
|
||||
color: #333;
|
||||
line-height: 1.8;
|
||||
|
||||
p {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__footer {
|
||||
padding: 20px 30px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -1,73 +1,93 @@
|
|||
<template>
|
||||
<div class="app-container" id="monthReport">
|
||||
<!-- 考勤率页面 -->
|
||||
<el-form
|
||||
:model="queryParams"
|
||||
ref="queryForm"
|
||||
size="small"
|
||||
:inline="true"
|
||||
v-show="showSearch"
|
||||
label-width="68px"
|
||||
<el-radio-group
|
||||
v-model="tabIndex"
|
||||
size="medium"
|
||||
style="margin-bottom: 10px"
|
||||
>
|
||||
<el-form-item label="选择月份">
|
||||
<el-date-picker
|
||||
v-model="queryParams.month"
|
||||
type="monthrange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始月份"
|
||||
end-placeholder="结束月份"
|
||||
value-format="yyyy-MM"
|
||||
:clearable="false"
|
||||
:editable="false"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门" prop="orgIdList">
|
||||
<treeselect
|
||||
v-model="queryParams.orgIdList"
|
||||
:options="deptOptions"
|
||||
:normalizer="normalizer"
|
||||
multiple
|
||||
placeholder="选择部门"
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="userName">
|
||||
<el-input
|
||||
v-model="queryParams.userName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
size="mini"
|
||||
@click="handleQuery"
|
||||
>搜索</el-button
|
||||
>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"
|
||||
>重置</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['att:attMonthReport:export']"
|
||||
>导出
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-radio-button label="考勤报表"></el-radio-button>
|
||||
<el-radio-button label="异常考勤统计"></el-radio-button>
|
||||
</el-radio-group>
|
||||
<!-- 考勤率页面 -->
|
||||
<div v-show="tabIndex === '考勤报表'">
|
||||
<el-form
|
||||
:model="queryParams"
|
||||
ref="queryForm"
|
||||
size="small"
|
||||
:inline="true"
|
||||
v-show="showSearch"
|
||||
label-width="auto"
|
||||
>
|
||||
<el-form-item label="选择月份">
|
||||
<el-date-picker
|
||||
v-model="queryParams.month"
|
||||
type="monthrange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始月份"
|
||||
end-placeholder="结束月份"
|
||||
value-format="yyyy-MM"
|
||||
:clearable="false"
|
||||
:editable="false"
|
||||
style="width: 240px"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门" prop="orgIdList">
|
||||
<treeselect
|
||||
v-model="queryParams.orgIdList"
|
||||
:options="deptOptions"
|
||||
:normalizer="normalizer"
|
||||
multiple
|
||||
placeholder="选择部门"
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="userName">
|
||||
<el-input
|
||||
v-model="queryParams.userName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="外勤预警" prop="outside">
|
||||
<el-select
|
||||
v-model="queryParams.outside"
|
||||
placeholder="选择外勤预警"
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option label="是" value="预警" />
|
||||
<el-option label="否" value="未预警" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
size="mini"
|
||||
@click="handleQuery"
|
||||
>搜索</el-button
|
||||
>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"
|
||||
>重置</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['att:attMonthReport:export']"
|
||||
>导出
|
||||
</el-button>
|
||||
</el-col>
|
||||
|
||||
<!-- <el-col :span="1.5">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
|
|
@ -88,36 +108,116 @@
|
|||
</el-button>
|
||||
</el-col> -->
|
||||
|
||||
<right-toolbar
|
||||
:showSearch.sync="showSearch"
|
||||
@queryTable="getList"
|
||||
></right-toolbar>
|
||||
</el-row>
|
||||
<right-toolbar
|
||||
:showSearch.sync="showSearch"
|
||||
@queryTable="getList"
|
||||
></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="typeList">
|
||||
<el-table-column label="序号" align="center" width="80" type="index">
|
||||
<template slot-scope="scope">
|
||||
<span>{{
|
||||
(queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="item.label"
|
||||
align="center"
|
||||
:prop="item.prop"
|
||||
v-for="item in mainListFields"
|
||||
:key="item.prop"
|
||||
<el-table v-loading="loading" :data="typeList">
|
||||
<el-table-column label="序号" align="center" width="80" type="index">
|
||||
<template slot-scope="scope">
|
||||
<span>{{
|
||||
(queryParams.pageNum - 1) * queryParams.pageSize +
|
||||
scope.$index +
|
||||
1
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="item.label"
|
||||
align="center"
|
||||
:prop="item.prop"
|
||||
v-for="item in mainListFields"
|
||||
:key="item.prop"
|
||||
/>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
<div v-show="tabIndex === '异常考勤统计'">
|
||||
<el-form
|
||||
:model="queryParamsAbnormal"
|
||||
ref="queryFormAbnormal"
|
||||
size="small"
|
||||
:inline="true"
|
||||
v-show="showSearch"
|
||||
label-width="auto"
|
||||
>
|
||||
<el-form-item label="选择月份">
|
||||
<el-date-picker
|
||||
clearable
|
||||
type="month"
|
||||
value-format="yyyy-MM"
|
||||
placeholder="请选择月份"
|
||||
v-model="queryParamsAbnormal.attCurrentMonth"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="部门" prop="orgIdList">
|
||||
<treeselect
|
||||
v-model="queryParamsAbnormal.orgIdList"
|
||||
:options="deptOptions"
|
||||
:normalizer="normalizer"
|
||||
multiple
|
||||
placeholder="选择部门"
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="userName">
|
||||
<el-input
|
||||
v-model="queryParamsAbnormal.userName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter.native="handleQueryAbnormal"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
size="mini"
|
||||
@click="handleQueryAbnormal"
|
||||
>查询</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
size="mini"
|
||||
@click="resetQueryAbnormal"
|
||||
>重置</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="loading" :data="abnormalList">
|
||||
<el-table-column label="序号" align="center" width="80" type="index">
|
||||
<template slot-scope="scope">
|
||||
<span>{{
|
||||
(queryParamsAbnormal.pageNum - 1) * queryParamsAbnormal.pageSize +
|
||||
scope.$index +
|
||||
1
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="item.label"
|
||||
align="center"
|
||||
:prop="item.prop"
|
||||
v-for="item in abnormalListFields"
|
||||
:key="item.prop"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 相关记录 -->
|
||||
<el-dialog
|
||||
|
|
@ -613,7 +713,10 @@ import {
|
|||
getRequiredDaysList,
|
||||
getYearDataListAPI,
|
||||
} from "@/api/report/monthReport";
|
||||
import { getAttendanceRateList } from "@/api/report/attendanceRate";
|
||||
import {
|
||||
getAttendanceRateList,
|
||||
getLateEarlyAbsentList,
|
||||
} from "@/api/report/attendanceRate";
|
||||
|
||||
import { listDeptTree } from "@/api/system/userInfo";
|
||||
import Treeselect from "@riophae/vue-treeselect";
|
||||
|
|
@ -692,6 +795,7 @@ export default {
|
|||
userName: undefined,
|
||||
orgIdList: undefined,
|
||||
orgName: undefined,
|
||||
outside: undefined,
|
||||
},
|
||||
deptOptions: [],
|
||||
|
||||
|
|
@ -747,14 +851,14 @@ export default {
|
|||
mainListFields: [
|
||||
{ label: "姓名", prop: "userName" },
|
||||
{ label: "部门", prop: "orgName" },
|
||||
{ label: "考勤月份", prop: "attendanceRate" },
|
||||
{ label: "考勤月份", prop: "attCurrentMonth" },
|
||||
{ label: "应出勤天数", prop: "requiredDays" },
|
||||
{ label: "迟到率", prop: "lateRate" },
|
||||
{ label: "早退率", prop: "earlyRate" },
|
||||
{ label: "休假率(带薪)", prop: "leaveRate" },
|
||||
{ label: "休假率(带薪)", prop: "leavePaidRate" },
|
||||
{ label: "休假率(不带薪)", prop: "leaveUnpaidRate" },
|
||||
{ label: "异常打卡率", prop: "abnormalRate" },
|
||||
{ label: "外勤次数", prop: "outCount" },
|
||||
// { label: "异常打卡率", prop: "abnormalRate" },
|
||||
{ label: "外勤次数", prop: "outsideAttNum" },
|
||||
],
|
||||
|
||||
// 月份初始化列表
|
||||
|
|
@ -772,10 +876,29 @@ export default {
|
|||
{ label: "11", dataValue: "elevenMonth" },
|
||||
{ label: "12", dataValue: "twelveMonth" },
|
||||
],
|
||||
|
||||
tabIndex: "考勤报表",
|
||||
abnormalList: [],
|
||||
abnormalListFields: [
|
||||
{ label: "姓名", prop: "userName" },
|
||||
{ label: "单位", prop: "orgName" },
|
||||
{ label: "次数", prop: "count" },
|
||||
{ label: "异常类型", prop: "abnormalType" },
|
||||
{ label: "异常原因", prop: "abnormalReason" },
|
||||
],
|
||||
queryParamsAbnormal: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
attCurrentMonth: new Date().toISOString().split("T")[0].slice(0, 7), // 默认当月
|
||||
userName: undefined,
|
||||
orgIdList: undefined,
|
||||
orgName: undefined,
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getDeptList();
|
||||
this.getAbnormalList();
|
||||
this.getMonth();
|
||||
this.getList();
|
||||
},
|
||||
|
|
@ -862,7 +985,7 @@ export default {
|
|||
delete query["orgIdList"];
|
||||
|
||||
console.log(query);
|
||||
getMonthAttReport(query).then((response) => {
|
||||
getAttendanceRateList(query).then((response) => {
|
||||
this.typeList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
|
|
@ -1116,6 +1239,30 @@ export default {
|
|||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 获取异常考勤统计列表
|
||||
getAbnormalList() {
|
||||
getLateEarlyAbsentList(this.queryParamsAbnormal).then((response) => {
|
||||
this.abnormalList = response.rows;
|
||||
this.totalAbnormal = response.total;
|
||||
});
|
||||
},
|
||||
|
||||
handleQueryAbnormal() {
|
||||
this.getAbnormalList();
|
||||
},
|
||||
|
||||
resetQueryAbnormal() {
|
||||
this.queryParamsAbnormal = {
|
||||
attCurrentMonth: new Date().toISOString().split("T")[0].slice(0, 7),
|
||||
userName: undefined,
|
||||
orgIdList: undefined,
|
||||
orgName: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
this.getAbnormalList();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue