| | |
| | | <template> |
| | | <div class="login-page"> |
| | | <div class="login-card"> |
| | | <div class="card-header"> |
| | | <div class="logo"> |
| | | <svg-icon icon-class="user" /> |
| | | <!-- 左侧插画区 --> |
| | | <div class="illustration-side"> |
| | | <div class="illustration-content"> |
| | | <div class="scene"> |
| | | <!-- 仓库建筑 --> |
| | | <div class="warehouse"> |
| | | <div class="building"> |
| | | <div class="roof"></div> |
| | | <div class="wall"> |
| | | <div class="window"></div> |
| | | <div class="window"></div> |
| | | <div class="window"></div> |
| | | </div> |
| | | <div class="door"></div> |
| | | </div> |
| | | </div> |
| | | <!-- 卡车 --> |
| | | <div class="truck"> |
| | | <div class="truck-body"></div> |
| | | <div class="truck-cab"></div> |
| | | <div class="wheel wheel-1"></div> |
| | | <div class="wheel wheel-2"></div> |
| | | </div> |
| | | <!-- 箱子 --> |
| | | <div class="boxes"> |
| | | <div class="box box-1"></div> |
| | | <div class="box box-2"></div> |
| | | <div class="box box-3"></div> |
| | | </div> |
| | | <!-- 装饰元素 --> |
| | | <div class="decor-circle c1"></div> |
| | | <div class="decor-circle c2"></div> |
| | | <div class="decor-circle c3"></div> |
| | | </div> |
| | | <h1>客户关系管理系统</h1> |
| | | <p>高效管理客户资源,驱动业务增长</p> |
| | | <h2>智能供应链管理</h2> |
| | | <p>高效协同 · 精准管控 · 数据驱动</p> |
| | | </div> |
| | | |
| | | <el-form ref="loginRef" :model="loginForm" :rules="loginRules"> |
| | | <el-form-item prop="username"> |
| | | <el-input |
| | | v-model="loginForm.username" |
| | | type="text" |
| | | size="large" |
| | | auto-complete="off" |
| | | placeholder="请输入账号" |
| | | > |
| | | <template #prefix><el-icon><User /></el-icon></template> |
| | | </el-input> |
| | | </el-form-item> |
| | | |
| | | <el-form-item prop="password"> |
| | | <el-input |
| | | v-model="loginForm.password" |
| | | type="password" |
| | | size="large" |
| | | auto-complete="off" |
| | | placeholder="请输入密码" |
| | | show-password |
| | | @keyup.enter="handleLogin" |
| | | > |
| | | <template #prefix><el-icon><Lock /></el-icon></template> |
| | | </el-input> |
| | | </el-form-item> |
| | | |
| | | <div class="form-options"> |
| | | <el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox> |
| | | <router-link v-if="register" :to="'/register'">立即注册</router-link> |
| | | </div> |
| | | |
| | | <el-button |
| | | :loading="loading" |
| | | size="large" |
| | | type="primary" |
| | | class="login-btn" |
| | | @click.prevent="handleLogin" |
| | | > |
| | | <span v-if="!loading">登 录</span> |
| | | <span v-else>登录中...</span> |
| | | </el-button> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <div class="bg-pattern"> |
| | | <div class="pattern-item"></div> |
| | | <div class="pattern-item"></div> |
| | | <div class="pattern-item"></div> |
| | | <!-- 右侧登录区 --> |
| | | <div class="login-side"> |
| | | <div class="login-form-box"> |
| | | <div class="form-header"> |
| | | <h3>欢迎回来</h3> |
| | | <p>登录您的账号继续使用</p> |
| | | </div> |
| | | |
| | | <el-form ref="loginRef" :model="loginForm" :rules="loginRules"> |
| | | <el-form-item prop="username"> |
| | | <el-input |
| | | v-model="loginForm.username" |
| | | type="text" |
| | | size="large" |
| | | auto-complete="off" |
| | | placeholder="用户名" |
| | | class="login-input" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><User /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | |
| | | <el-form-item prop="password"> |
| | | <el-input |
| | | v-model="loginForm.password" |
| | | type="password" |
| | | size="large" |
| | | auto-complete="off" |
| | | placeholder="密码" |
| | | show-password |
| | | class="login-input" |
| | | @keyup.enter="handleLogin" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Lock /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | |
| | | <div class="form-options"> |
| | | <el-checkbox v-model="loginForm.rememberMe">记住我</el-checkbox> |
| | | <a href="#" class="forgot-link">忘记密码?</a> |
| | | </div> |
| | | |
| | | <el-button |
| | | :loading="loading" |
| | | size="large" |
| | | type="primary" |
| | | class="login-btn" |
| | | @click.prevent="handleLogin" |
| | | > |
| | | <span v-if="!loading">登录</span> |
| | | <span v-else>登录中...</span> |
| | | </el-button> |
| | | </el-form> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | import Cookies from "js-cookie" |
| | | import { encrypt, decrypt } from "@/utils/jsencrypt" |
| | | import useUserStore from "@/store/modules/user" |
| | | import { User, Lock } from '@element-plus/icons-vue' |
| | | |
| | | const userStore = useUserStore() |
| | | const route = useRoute() |
| | |
| | | }) |
| | | |
| | | const loginRules = { |
| | | username: [{ required: true, trigger: "blur", message: "请输入账号" }], |
| | | username: [{ required: true, trigger: "blur", message: "请输入用户名" }], |
| | | password: [{ required: true, trigger: "blur", message: "请输入密码" }], |
| | | } |
| | | |
| | | const loading = ref(false) |
| | | const captchaEnabled = ref(true) |
| | | const register = ref(false) |
| | | const redirect = ref(undefined) |
| | | |
| | | watch( |
| | |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | // 主题色变量 |
| | | $primary: #1f7a72; |
| | | $primary-light: #2a9d8f; |
| | | $primary-lighter: #3abbae; |
| | | $primary-lightest: #e8f5f3; |
| | | |
| | | .login-page { |
| | | min-height: 100vh; |
| | | display: flex; |
| | | } |
| | | |
| | | /* 左侧插画区 */ |
| | | .illustration-side { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: linear-gradient(135deg, var(--el-color-primary-light-9) 0%, var(--el-color-primary-light-8) 50%, var(--el-color-primary-light-9) 100%); |
| | | position: relative; |
| | | overflow: hidden; |
| | | background: linear-gradient(135deg, $primary-lightest 0%, #d4edea 100%); |
| | | padding: 60px; |
| | | } |
| | | |
| | | .bg-pattern { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | pointer-events: none; |
| | | |
| | | .pattern-item { |
| | | position: absolute; |
| | | border-radius: 50%; |
| | | background: linear-gradient(135deg, var(--el-color-primary-light-7), var(--el-color-primary-light-9)); |
| | | } |
| | | |
| | | .pattern-item:nth-child(1) { |
| | | width: 500px; |
| | | height: 500px; |
| | | top: -150px; |
| | | right: -100px; |
| | | } |
| | | |
| | | .pattern-item:nth-child(2) { |
| | | width: 350px; |
| | | height: 350px; |
| | | bottom: -100px; |
| | | left: -80px; |
| | | } |
| | | |
| | | .pattern-item:nth-child(3) { |
| | | width: 200px; |
| | | height: 200px; |
| | | top: 40%; |
| | | left: 15%; |
| | | background: linear-gradient(135deg, var(--el-color-primary-light-8), var(--el-color-primary-light-9)); |
| | | } |
| | | } |
| | | |
| | | .login-card { |
| | | width: 420px; |
| | | padding: 48px 40px; |
| | | background: rgba(255, 255, 255, 0.95); |
| | | border-radius: 20px; |
| | | box-shadow: |
| | | 0 4px 6px -1px rgba(0, 0, 0, 0.05), |
| | | 0 10px 15px -3px rgba(0, 0, 0, 0.08), |
| | | 0 20px 25px -5px rgba(0, 0, 0, 0.05); |
| | | backdrop-filter: blur(10px); |
| | | position: relative; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .card-header { |
| | | .illustration-content { |
| | | text-align: center; |
| | | margin-bottom: 36px; |
| | | |
| | | .logo { |
| | | width: 72px; |
| | | height: 72px; |
| | | margin: 0 auto 20px; |
| | | background: linear-gradient(135deg, var(--el-color-primary) 0%, var(--el-color-primary-light-3) 100%); |
| | | border-radius: 18px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 8px 20px var(--el-color-primary-light-5); |
| | | |
| | | :deep(svg) { |
| | | width: 36px; |
| | | height: 36px; |
| | | color: #fff; |
| | | } |
| | | } |
| | | |
| | | h1 { |
| | | font-size: 24px; |
| | | h2 { |
| | | font-size: 32px; |
| | | font-weight: 600; |
| | | color: #1f2937; |
| | | margin: 0 0 8px; |
| | | color: $primary; |
| | | margin: 40px 0 12px 0; |
| | | } |
| | | |
| | | p { |
| | | font-size: 14px; |
| | | color: #6b7280; |
| | | font-size: 16px; |
| | | color: $primary-light; |
| | | margin: 0; |
| | | } |
| | | } |
| | | |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 20px; |
| | | /* CSS 插画场景 */ |
| | | .scene { |
| | | position: relative; |
| | | width: 320px; |
| | | height: 240px; |
| | | margin: 0 auto; |
| | | } |
| | | |
| | | :deep(.el-input__wrapper) { |
| | | border-radius: 12px; |
| | | box-shadow: 0 0 0 1px #e5e7eb; |
| | | /* 仓库 */ |
| | | .warehouse { |
| | | position: absolute; |
| | | bottom: 40px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | } |
| | | |
| | | .building { |
| | | position: relative; |
| | | width: 140px; |
| | | height: 100px; |
| | | } |
| | | |
| | | .roof { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | width: 0; |
| | | height: 0; |
| | | border-left: 75px solid transparent; |
| | | border-right: 75px solid transparent; |
| | | border-bottom: 40px solid $primary; |
| | | } |
| | | |
| | | .wall { |
| | | position: absolute; |
| | | bottom: 0; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | width: 120px; |
| | | height: 70px; |
| | | background: #fff; |
| | | border: 3px solid $primary; |
| | | border-radius: 0 0 4px 4px; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 0 10px; |
| | | } |
| | | |
| | | .window { |
| | | width: 20px; |
| | | height: 30px; |
| | | background: $primary-lightest; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | .door { |
| | | position: absolute; |
| | | bottom: 0; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | width: 30px; |
| | | height: 45px; |
| | | background: $primary-light; |
| | | border-radius: 4px 4px 0 0; |
| | | } |
| | | |
| | | /* 卡车 */ |
| | | .truck { |
| | | position: absolute; |
| | | bottom: 40px; |
| | | right: 20px; |
| | | width: 100px; |
| | | height: 50px; |
| | | } |
| | | |
| | | .truck-body { |
| | | position: absolute; |
| | | top: 10px; |
| | | left: 0; |
| | | width: 60px; |
| | | height: 35px; |
| | | background: $primary; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .truck-cab { |
| | | position: absolute; |
| | | top: 18px; |
| | | right: 0; |
| | | width: 35px; |
| | | height: 27px; |
| | | background: $primary-light; |
| | | border-radius: 0 8px 4px 0; |
| | | } |
| | | |
| | | .wheel { |
| | | position: absolute; |
| | | bottom: 0; |
| | | width: 16px; |
| | | height: 16px; |
| | | background: $primary; |
| | | border-radius: 50%; |
| | | border: 3px solid $primary-light; |
| | | } |
| | | |
| | | .wheel-1 { |
| | | left: 12px; |
| | | } |
| | | |
| | | .wheel-2 { |
| | | right: 8px; |
| | | } |
| | | |
| | | /* 箱子 */ |
| | | .boxes { |
| | | position: absolute; |
| | | bottom: 40px; |
| | | left: 30px; |
| | | } |
| | | |
| | | .box { |
| | | position: absolute; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .box-1 { |
| | | width: 35px; |
| | | height: 35px; |
| | | background: $primary; |
| | | bottom: 0; |
| | | left: 0; |
| | | } |
| | | |
| | | .box-2 { |
| | | width: 30px; |
| | | height: 30px; |
| | | background: $primary-light; |
| | | bottom: 0; |
| | | left: 40px; |
| | | } |
| | | |
| | | .box-3 { |
| | | width: 28px; |
| | | height: 28px; |
| | | background: $primary-lighter; |
| | | bottom: 38px; |
| | | left: 5px; |
| | | } |
| | | |
| | | /* 装饰圆圈 */ |
| | | .decor-circle { |
| | | position: absolute; |
| | | border-radius: 50%; |
| | | opacity: 0.3; |
| | | } |
| | | |
| | | .c1 { |
| | | width: 80px; |
| | | height: 80px; |
| | | background: $primary; |
| | | top: 20px; |
| | | left: 30px; |
| | | } |
| | | |
| | | .c2 { |
| | | width: 50px; |
| | | height: 50px; |
| | | background: $primary-light; |
| | | top: 60px; |
| | | right: 40px; |
| | | } |
| | | |
| | | .c3 { |
| | | width: 35px; |
| | | height: 35px; |
| | | background: $primary-lighter; |
| | | bottom: 100px; |
| | | left: 20px; |
| | | } |
| | | |
| | | /* 右侧登录区 */ |
| | | .login-side { |
| | | width: 460px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: #fff; |
| | | padding: 40px; |
| | | } |
| | | |
| | | .login-form-box { |
| | | width: 100%; |
| | | max-width: 340px; |
| | | } |
| | | |
| | | .form-header { |
| | | margin-bottom: 32px; |
| | | |
| | | h3 { |
| | | font-size: 26px; |
| | | font-weight: 600; |
| | | color: $primary; |
| | | margin: 0 0 8px 0; |
| | | } |
| | | |
| | | p { |
| | | font-size: 14px; |
| | | color: $primary-light; |
| | | margin: 0; |
| | | } |
| | | } |
| | | |
| | | :deep(.login-input .el-input__wrapper) { |
| | | background: $primary-lightest; |
| | | border: 1px solid #d0e5e2; |
| | | border-radius: 10px; |
| | | box-shadow: none; |
| | | padding: 0 16px; |
| | | height: 48px; |
| | | transition: all 0.2s; |
| | | |
| | | &:hover { |
| | | box-shadow: 0 0 0 1px var(--el-color-primary-light-5); |
| | | border-color: $primary-light; |
| | | } |
| | | |
| | | &:focus-within { |
| | | box-shadow: 0 0 0 2px var(--el-color-primary); |
| | | border-color: $primary; |
| | | background: #fff; |
| | | box-shadow: 0 0 0 3px rgba(31, 122, 114, 0.1); |
| | | } |
| | | } |
| | | |
| | | :deep(.el-input__inner) { |
| | | :deep(.login-input .el-input__inner) { |
| | | height: 48px; |
| | | font-size: 15px; |
| | | color: #374151; |
| | | color: $primary; |
| | | |
| | | &::placeholder { |
| | | color: #9ca3af; |
| | | color: #8fb5af; |
| | | } |
| | | } |
| | | |
| | | :deep(.el-input__prefix) { |
| | | color: #9ca3af; |
| | | font-size: 18px; |
| | | :deep(.login-input .el-input__prefix) { |
| | | color: $primary-light; |
| | | margin-right: 10px; |
| | | } |
| | | |
| | | .form-options { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin: 8px 0 24px; |
| | | margin: 16px 0 24px; |
| | | |
| | | :deep(.el-checkbox__label) { |
| | | color: #6b7280; |
| | | font-size: 14px; |
| | | color: $primary-light; |
| | | } |
| | | |
| | | :deep(.el-checkbox__input.is-checked .el-checkbox__inner) { |
| | | background-color: var(--el-color-primary); |
| | | border-color: var(--el-color-primary); |
| | | background-color: $primary; |
| | | border-color: $primary; |
| | | } |
| | | |
| | | a { |
| | | color: var(--el-color-primary); |
| | | :deep(.el-checkbox__input.is-checked + .el-checkbox__label) { |
| | | color: $primary; |
| | | } |
| | | |
| | | .forgot-link { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: $primary; |
| | | text-decoration: none; |
| | | transition: color 0.2s; |
| | | font-weight: 500; |
| | | |
| | | &:hover { |
| | | color: var(--el-color-primary-light-3); |
| | | text-decoration: underline; |
| | | } |
| | | } |
| | | } |
| | |
| | | .login-btn { |
| | | width: 100%; |
| | | height: 48px; |
| | | border-radius: 12px; |
| | | border-radius: 10px; |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | background: linear-gradient(135deg, var(--el-color-primary) 0%, var(--el-color-primary-light-3) 100%); |
| | | background: linear-gradient(135deg, $primary 0%, $primary-light 100%); |
| | | border: none; |
| | | box-shadow: 0 4px 14px var(--el-color-primary-light-5); |
| | | transition: all 0.2s; |
| | | |
| | | &:hover { |
| | | opacity: 0.9; |
| | | transform: translateY(-1px); |
| | | box-shadow: 0 6px 20px var(--el-color-primary-light-4); |
| | | box-shadow: 0 6px 20px rgba(31, 122, 114, 0.3); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 480px) { |
| | | .login-card { |
| | | width: 90%; |
| | | padding: 36px 24px; |
| | | /* 响应式 */ |
| | | @media (max-width: 900px) { |
| | | .illustration-side { |
| | | display: none; |
| | | } |
| | | |
| | | .card-header { |
| | | h1 { |
| | | font-size: 20px; |
| | | } |
| | | .login-side { |
| | | width: 100%; |
| | | } |
| | | } |
| | | </style> |