ZhangXianQiang
2024-06-18 68fcf9e66c01e78d9d6d6f0dc09d8dac3cb31672
feat:添加登录页
3个文件已修改
2个文件已添加
399 ■■■■■ 已修改文件
components.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/background.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/logo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login/index.vue 390 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components.d.ts
@@ -15,6 +15,8 @@
    ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
    ElCountdown: typeof import('element-plus/es')['ElCountdown']
    ElDialog: typeof import('element-plus/es')['ElDialog']
    ElForm: typeof import('element-plus/es')['ElForm']
    ElFormItem: typeof import('element-plus/es')['ElFormItem']
    ElIcon: typeof import('element-plus/es')['ElIcon']
    ElInput: typeof import('element-plus/es')['ElInput']
    ElPagination: typeof import('element-plus/es')['ElPagination']
src/assets/background.png
src/assets/logo.png

src/router/index.js
@@ -5,7 +5,7 @@
const routes = [
  {
    path: '/',
    redirect: '/index'
    redirect: '/login'
  },
  {
@@ -24,6 +24,11 @@
      },
    ]
  },
  // 登录
  {
    path: '/login',
    component: () => import('@/views/login/index.vue'),
  },
  // 在线培训
  {
    path: '/train',
src/views/login/index.vue
New file
@@ -0,0 +1,390 @@
<template>
  <div class="w-screen h-screen overflow-hidden">
    <img src="@/assets/background.png" class="w-full h-full object-cover absolute z-0" alt="">
    <div class="lowin lowin-blue mt-20">
      <div class="lowin-brand">
        <img src="@/assets/logo.png" alt="logo">
      </div>
      <div class="lowin-wrapper">
        <div class="lowin-box lowin-login">
          <div class="lowin-box-inner">
            <el-form ref="loginFormRef" :model="loginForm" :rules="loginRules">
              <p>江西空管学生端</p>
              <div class="lowin-group">
                <el-form-item prop="userName">
                  <label>用户名 </label>
                  <el-input ref="userName" v-model="loginForm.userName" class="lowin-input" placeholder="用户名"
                    name="userName" type="text" tabindex="1" auto-complete="on" />
                </el-form-item>
              </div>
              <div class="lowin-group password-group">
                <el-form-item prop="password">
                  <label>密码 <a href="#" class="forgot-link">忘记密码?</a></label>
                  <el-input class="lowin-input" :key="passwordType" ref="password" v-model="loginForm.password"
                    :type="passwordType" placeholder="密码" name="password" tabindex="2" auto-complete="on"
                    @keyup.native="checkCapslock" @blur="capsTooltip = false" @keyup.enter.native="handleLogin" />
                </el-form-item>
              </div>
              <el-button :loading="loading" class="lowin-btn login-btn" @click="handleLogin">登录</el-button>
            </el-form>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const validateUsername = (rule, value, callback) => {
  if (value.length < 5) {
    callback(new Error('用户名不能少于5个字符'));
  } else {
    callback();
  }
};
const validatePassword = (rule, value, callback) => {
  if (value.length < 5) {
    callback(new Error('密码不能少于5个字符'));
  } else {
    callback();
  }
};
const loginForm = reactive({
  userName: '',
  password: '',
  remember: false
});
const loginRules = reactive({
  userName: [{ required: true, trigger: 'blur', validator: validateUsername }],
  password: [{ required: true, trigger: 'blur', validator: validatePassword }]
});
const passwordType = ref('password');
const capsTooltip = ref(false);
const loading = ref(false);
const showDialog = ref(false);
const userName = ref(null);
const password = ref(null);
const loginFormRef = ref(null);
const checkCapslock = ({ shiftKey, key } = {}) => {
  if (key && key.length === 1) {
    if (shiftKey && (key >= 'a' && key <= 'z') || !shiftKey && (key >= 'A' && key <= 'Z')) {
      capsTooltip.value = true;
    } else {
      capsTooltip.value = false;
    }
  }
  if (key === 'CapsLock' && capsTooltip.value === true) {
    capsTooltip.value = false;
  }
};
const showPwd = () => {
  if (passwordType.value === 'password') {
    passwordType.value = '';
  } else {
    passwordType.value = 'password';
  }
  nextTick(() => {
    password.value.focus();
  });
};
const handleLogin = () => {
  loginFormRef.value.validate((valid) => {
    if (valid) {
      router.push('/index');
    }
  });
};
onMounted(() => {
  if (loginForm.userName === '') {
    userName.value.focus();
  } else if (loginForm.password === '') {
    password.value.focus();
  }
});
</script>
<style scoped>
.lowin {
  /* variables */
  --color-primary: #44a0b3;
  --color-grey: rgba(68, 160, 179, .06);
  --color-dark: rgba(68, 160, 179, .5);
  --color-semidark: rgba(68, 160, 179, .5);
  text-align: center;
  font-family: 'Segoe UI';
  font-size: 14px;
}
.lowin .lowin-wrapper {
  -webkit-transition: all 1s;
  -o-transition: all 1s;
  transition: all 1s;
  -webkit-perspective: 1000px;
  perspective: 1000px;
  position: relative;
  height: 100%;
  width: 360px;
  margin: 0 auto;
}
.lowin.lowin-blue {
  --color-primary: #0081C6;
  --color-grey: rgba(0, 129, 198, .05);
  --color-dark: rgba(0, 129, 198, .7);
  --color-semidark: rgba(0, 129, 198, .45);
}
.lowin a {
  color: var(--color-primary);
  text-decoration: none;
  border-bottom: 1px dashed var(--color-semidark);
  margin-top: -3px;
  padding-bottom: 2px;
}
.lowin * {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}
.lowin .lowin-brand {
  overflow: hidden;
  width: 100px;
  height: 100px;
  margin: 0 auto -50px auto;
  border-radius: 50%;
  -webkit-box-shadow: 0 4px 40px rgba(0, 0, 0, .07);
  box-shadow: 0 4px 40px rgba(0, 0, 0, .07);
  padding: 10px;
  background-color: #fff;
  z-index: 1;
  position: relative;
}
.lowin .lowin-brand img {
  width: 100%;
}
.lowin .lowin-box {
  width: 100%;
  position: absolute;
  left: 0;
}
.lowin .lowin-box-inner {
  background-color: #fff;
  -webkit-box-shadow: 0 7px 25px rgba(0, 0, 0, .08);
  box-shadow: 0 7px 25px rgba(0, 0, 0, .08);
  padding: 60px 25px 25px 25px;
  text-align: left;
  border-radius: 3px;
}
.lowin .lowin-box::after {
  content: ' ';
  -webkit-box-shadow: 0 0 25px rgba(0, 0, 0, .1);
  box-shadow: 0 0 25px rgba(0, 0, 0, .1);
  -webkit-transform: translate(0, -92.6%) scale(.88);
  -ms-transform: translate(0, -92.6%) scale(.88);
  transform: translate(0, -92.6%) scale(.88);
  border-radius: 3px;
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: #fff;
  z-index: -1;
}
.lowin .lowin-box.lowin-flip {
  -webkit-transform: rotate3d(0, 1, 0, -180deg);
  transform: rotate3d(0, 1, 0, -180deg);
  display: none;
  opacity: 0;
}
.lowin .lowin-box p {
  color: var(--color-semidark);
  font-weight: 700;
  margin-bottom: 20px;
  text-align: center;
}
.lowin .lowin-box .lowin-group {
  margin-bottom: 30px;
}
.lowin .lowin-box label {
  margin-bottom: 5px;
  display: inline-block;
  width: 100%;
  color: var(--color-semidark);
  font-weight: 700;
}
.lowin .lowin-box label a {
  float: right;
}
.lowin .lowin-box .lowin-input {
  background-color: var(--color-grey);
  color: var(--color-dark);
  border: none;
  border-radius: 3px;
  padding: 5px 20px;
  width: 100%;
  outline: 0;
}
.lowin .lowin-box .lowin-btn {
  display: inline-block;
  width: 100%;
  border: none;
  color: #fff;
  border-radius: 3px;
  background-color: var(--color-primary);
  -webkit-box-shadow: 0 2px 7px var(--color-semidark);
  box-shadow: 0 2px 7px var(--color-semidark);
  font-weight: 700;
  outline: 0;
  cursor: pointer;
  -webkit-transition: all .5s;
  -o-transition: all .5s;
  transition: all .5s;
}
.lowin .lowin-box .lowin-btn:active {
  -webkit-box-shadow: none;
  box-shadow: none;
}
.lowin .lowin-box .lowin-btn:hover {
  opacity: .9;
}
.lowin .text-foot {
  text-align: center;
  padding: 10px;
  font-weight: 700;
  margin-top: 20px;
  color: var(--color-semidark);
}
/* animation */
.lowin .lowin-box.lowin-animated {
  -webkit-animation-name: LowinAnimated;
  animation-name: LowinAnimated;
  -webkit-animation-duration: 1s;
  animation-duration: 1s;
  -webkit-animation-fill-mode: forwards;
  animation-fill-mode: forwards;
  -webkit-animation-timing-function: ease-in-out;
  animation-timing-function: ease-in-out;
}
.lowin .lowin-box.lowin-animatedback {
  -webkit-animation-name: LowinAnimatedBack;
  animation-name: LowinAnimatedBack;
  -webkit-animation-duration: 1s;
  animation-duration: 1s;
  -webkit-animation-fill-mode: forwards;
  animation-fill-mode: forwards;
  -webkit-animation-timing-function: ease-in-out;
  animation-timing-function: ease-in-out;
}
.lowin .lowin-box.lowin-animated-flip {
  -webkit-animation-name: LowinAnimatedFlip;
  animation-name: LowinAnimatedFlip;
  -webkit-animation-duration: 1s;
  animation-duration: 1s;
  -webkit-animation-fill-mode: forwards;
  animation-fill-mode: forwards;
  -webkit-animation-timing-function: ease-in-out;
  animation-timing-function: ease-in-out;
}
.lowin .lowin-box.lowin-animated-flipback {
  -webkit-animation-name: LowinAnimatedFlipBack;
  animation-name: LowinAnimatedFlipBack;
  -webkit-animation-duration: 1s;
  animation-duration: 1s;
  -webkit-animation-fill-mode: forwards;
  animation-fill-mode: forwards;
  -webkit-animation-timing-function: ease-in-out;
  animation-timing-function: ease-in-out;
}
.lowin .lowin-brand.lowin-animated {
  -webkit-animation-name: LowinBrandAnimated;
  animation-name: LowinBrandAnimated;
  -webkit-animation-duration: 1s;
  animation-duration: 1s;
  -webkit-animation-fill-mode: forwards;
  animation-fill-mode: forwards;
  -webkit-animation-timing-function: ease-in-out;
  animation-timing-function: ease-in-out;
}
.lowin .lowin-group.password-group {
  -webkit-transition: all 1s;
  -o-transition: all 1s;
  transition: all 1s;
}
.lowin .lowin-group.password-group.lowin-animated {
  -webkit-animation-name: LowinPasswordAnimated;
  animation-name: LowinPasswordAnimated;
  -webkit-animation-duration: 1s;
  animation-duration: 1s;
  -webkit-animation-fill-mode: forwards;
  animation-fill-mode: forwards;
  -webkit-animation-timing-function: ease-in-out;
  animation-timing-function: ease-in-out;
  -webkit-transform-origin: 0 0;
  -ms-transform-origin: 0 0;
  transform-origin: 0 0;
}
.lowin .lowin-group.password-group.lowin-animated-back {
  -webkit-animation-name: LowinPasswordAnimatedBack;
  animation-name: LowinPasswordAnimatedBack;
  -webkit-animation-duration: 1s;
  animation-duration: 1s;
  -webkit-animation-fill-mode: forwards;
  animation-fill-mode: forwards;
  -webkit-animation-timing-function: ease-in-out;
  animation-timing-function: ease-in-out;
  -webkit-transform-origin: 0 0;
  -ms-transform-origin: 0 0;
  transform-origin: 0 0;
}
@media screen and (max-width: 320px) {
  .lowin .lowin-wrapper {
    width: 100%;
  }
  .lowin .lowin-box {
    padding: 0 10px;
  }
}
</style>