| | |
| | | <template> |
| | | <div class="login-page"> |
| | | <div class="login-shell"> |
| | | <section class="login-brand"> |
| | | <div class="brand-badge">PRODUCT INVENTORY</div> |
| | | <img :src="brandLogo" alt="brand logo" class="brand-logo" /> |
| | | <h1 class="brand-title">{{ title }}</h1> |
| | | <p class="brand-copy"> |
| | | 统一管理库存、流程与业务数据,让系统入口和后台主界面保持同一套简约视觉语言。 |
| | | </p> |
| | | <div class="brand-points"> |
| | | <div class="brand-point"> |
| | | <span class="point-dot"></span> |
| | | <span>清晰的数据入口</span> |
| | | </div> |
| | | <div class="brand-point"> |
| | | <span class="point-dot"></span> |
| | | <span>更轻的界面层次</span> |
| | | </div> |
| | | <div class="brand-point"> |
| | | <span class="point-dot"></span> |
| | | <span>稳定的业务协同体验</span> |
| | | </div> |
| | | <div class="login-card"> |
| | | <div class="card-header"> |
| | | <div class="logo"> |
| | | <svg-icon icon-class="user" /> |
| | | </div> |
| | | </section> |
| | | <h1>客户关系管理系统</h1> |
| | | <p>高效管理客户资源,驱动业务增长</p> |
| | | </div> |
| | | |
| | | <section class="login-panel"> |
| | | <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form"> |
| | | <div class="panel-head"> |
| | | <p class="panel-kicker">WELCOME BACK</p> |
| | | <h2 class="panel-title">登录系统</h2> |
| | | <p class="panel-subtitle">输入账号和密码进入工作台。</p> |
| | | </div> |
| | | |
| | | <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><svg-icon icon-class="password" class="el-input__icon input-icon" /></template> |
| | | </el-input> |
| | | </el-form-item> |
| | | |
| | | <div class="login-options"> |
| | | <el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox> |
| | | <router-link v-if="register" class="register-link" :to="'/register'">立即注册</router-link> |
| | | </div> |
| | | |
| | | <el-button |
| | | :loading="loading" |
| | | <el-form ref="loginRef" :model="loginForm" :rules="loginRules"> |
| | | <el-form-item prop="username"> |
| | | <el-input |
| | | v-model="loginForm.username" |
| | | type="text" |
| | | size="large" |
| | | type="primary" |
| | | class="login-submit" |
| | | @click.prevent="handleLogin" |
| | | auto-complete="off" |
| | | placeholder="请输入账号" |
| | | > |
| | | <span v-if="!loading">登录</span> |
| | | <span v-else>登录中...</span> |
| | | </el-button> |
| | | </el-form> |
| | | </section> |
| | | <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> |
| | | </div> |
| | | </template> |
| | |
| | | import Cookies from "js-cookie" |
| | | import { encrypt, decrypt } from "@/utils/jsencrypt" |
| | | import useUserStore from "@/store/modules/user" |
| | | import brandLogo from "@/assets/logo/logo.png" |
| | | |
| | | const title = import.meta.env.VITE_APP_TITLE |
| | | const userStore = useUserStore() |
| | | const route = useRoute() |
| | | const router = useRouter() |
| | |
| | | }) |
| | | |
| | | const loginRules = { |
| | | username: [{ required: true, trigger: "blur", message: "请输入您的账号" }], |
| | | password: [{ required: true, trigger: "blur", message: "请输入您的密码" }], |
| | | username: [{ required: true, trigger: "blur", message: "请输入账号" }], |
| | | password: [{ required: true, trigger: "blur", message: "请输入密码" }], |
| | | } |
| | | |
| | | const codeUrl = ref("") |
| | | const loading = ref(false) |
| | | const captchaEnabled = ref(true) |
| | | const register = ref(false) |
| | |
| | | getCodeImg().then((res) => { |
| | | captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled |
| | | if (captchaEnabled.value) { |
| | | codeUrl.value = "data:image/gif;base64," + res.img |
| | | loginForm.value.uuid = res.uuid |
| | | } |
| | | }) |
| | |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 32px; |
| | | background: |
| | | radial-gradient(circle at top left, rgba(207, 223, 214, 0.95), transparent 30%), |
| | | radial-gradient(circle at bottom right, rgba(222, 232, 227, 0.9), transparent 28%), |
| | | linear-gradient(180deg, #f7faf8 0%, #eef2ee 100%); |
| | | } |
| | | |
| | | .login-shell { |
| | | width: min(1120px, 100%); |
| | | min-height: 680px; |
| | | display: grid; |
| | | grid-template-columns: 1.1fr 0.9fr; |
| | | border: 1px solid rgba(216, 225, 219, 0.9); |
| | | border-radius: 32px; |
| | | overflow: hidden; |
| | | background: rgba(255, 255, 255, 0.76); |
| | | box-shadow: 0 26px 80px rgba(31, 49, 38, 0.12); |
| | | backdrop-filter: blur(24px); |
| | | } |
| | | |
| | | .login-brand { |
| | | 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; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | padding: 56px 64px; |
| | | background: |
| | | linear-gradient(180deg, rgba(244, 248, 245, 0.9), rgba(233, 240, 236, 0.9)), |
| | | linear-gradient(135deg, rgba(31, 122, 114, 0.05), rgba(255, 255, 255, 0)); |
| | | overflow: hidden; |
| | | } |
| | | |
| | | &::after { |
| | | content: ""; |
| | | .bg-pattern { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | pointer-events: none; |
| | | |
| | | .pattern-item { |
| | | position: absolute; |
| | | inset: 28px; |
| | | border: 1px solid rgba(31, 122, 114, 0.08); |
| | | border-radius: 28px; |
| | | pointer-events: none; |
| | | 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)); |
| | | } |
| | | } |
| | | |
| | | .brand-badge { |
| | | width: fit-content; |
| | | padding: 8px 14px; |
| | | border-radius: 999px; |
| | | background: rgba(31, 122, 114, 0.1); |
| | | color: #1f7a72; |
| | | font-size: 12px; |
| | | font-weight: 700; |
| | | letter-spacing: 0.14em; |
| | | .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; |
| | | } |
| | | |
| | | .brand-logo { |
| | | width: 160px; |
| | | height: auto; |
| | | margin: 30px 0 24px; |
| | | object-fit: contain; |
| | | } |
| | | .card-header { |
| | | text-align: center; |
| | | margin-bottom: 36px; |
| | | |
| | | .brand-title { |
| | | margin: 0; |
| | | font-size: 42px; |
| | | line-height: 1.12; |
| | | color: #21313f; |
| | | letter-spacing: -0.03em; |
| | | } |
| | | .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); |
| | | |
| | | .brand-copy { |
| | | max-width: 460px; |
| | | margin: 18px 0 0; |
| | | font-size: 16px; |
| | | line-height: 1.75; |
| | | color: #5f6d7e; |
| | | } |
| | | :deep(svg) { |
| | | width: 36px; |
| | | height: 36px; |
| | | color: #fff; |
| | | } |
| | | } |
| | | |
| | | .brand-points { |
| | | margin-top: 34px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 14px; |
| | | } |
| | | h1 { |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #1f2937; |
| | | margin: 0 0 8px; |
| | | } |
| | | |
| | | .brand-point { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | color: #3d4b59; |
| | | font-size: 15px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .point-dot { |
| | | width: 10px; |
| | | height: 10px; |
| | | border-radius: 50%; |
| | | background: linear-gradient(135deg, #1f7a72, #5ca39c); |
| | | box-shadow: 0 0 0 6px rgba(31, 122, 114, 0.08); |
| | | } |
| | | |
| | | .login-panel { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 40px; |
| | | background: rgba(255, 255, 255, 0.7); |
| | | } |
| | | |
| | | .login-form { |
| | | width: min(420px, 100%); |
| | | padding: 38px 34px 34px; |
| | | border: 1px solid rgba(216, 225, 219, 0.92); |
| | | border-radius: 28px; |
| | | background: rgba(255, 255, 255, 0.88); |
| | | box-shadow: 0 18px 52px rgba(31, 49, 38, 0.1); |
| | | } |
| | | |
| | | .panel-head { |
| | | margin-bottom: 28px; |
| | | } |
| | | |
| | | .panel-kicker { |
| | | margin: 0 0 10px; |
| | | color: #8a98a8; |
| | | font-size: 12px; |
| | | font-weight: 700; |
| | | letter-spacing: 0.16em; |
| | | } |
| | | |
| | | .panel-title { |
| | | margin: 0; |
| | | color: #21313f; |
| | | font-size: 30px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .panel-subtitle { |
| | | margin: 10px 0 0; |
| | | color: #6b7888; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .login-options { |
| | | margin: -4px 0 22px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | color: #5f6d7e; |
| | | } |
| | | |
| | | .register-link { |
| | | color: var(--el-color-primary); |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .login-submit { |
| | | width: 100%; |
| | | height: 48px; |
| | | } |
| | | |
| | | .input-icon { |
| | | width: 14px; |
| | | p { |
| | | font-size: 14px; |
| | | color: #6b7280; |
| | | margin: 0; |
| | | } |
| | | } |
| | | |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 22px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | :deep(.el-input__wrapper) { |
| | | min-height: 42px; |
| | | height: 42px; |
| | | padding-top: 0; |
| | | padding-bottom: 0; |
| | | border-radius: 12px; |
| | | box-shadow: 0 0 0 1px #e5e7eb; |
| | | padding: 0 16px; |
| | | height: 48px; |
| | | transition: all 0.2s; |
| | | |
| | | &:hover { |
| | | box-shadow: 0 0 0 1px var(--el-color-primary-light-5); |
| | | } |
| | | |
| | | &:focus-within { |
| | | box-shadow: 0 0 0 2px var(--el-color-primary); |
| | | } |
| | | } |
| | | |
| | | :deep(.el-input__inner) { |
| | | height: 42px; |
| | | line-height: 42px; |
| | | height: 48px; |
| | | font-size: 15px; |
| | | color: #374151; |
| | | |
| | | &::placeholder { |
| | | color: #9ca3af; |
| | | } |
| | | } |
| | | |
| | | :deep(.el-checkbox) { |
| | | color: #5f6d7e; |
| | | :deep(.el-input__prefix) { |
| | | color: #9ca3af; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | @media (max-width: 960px) { |
| | | .login-page { |
| | | padding: 18px; |
| | | } |
| | | .form-options { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin: 8px 0 24px; |
| | | |
| | | .login-shell { |
| | | min-height: auto; |
| | | grid-template-columns: 1fr; |
| | | } |
| | | |
| | | .login-brand { |
| | | padding: 40px 28px 22px; |
| | | } |
| | | |
| | | .login-brand::after { |
| | | inset: 16px; |
| | | } |
| | | |
| | | .brand-title { |
| | | font-size: 32px; |
| | | } |
| | | |
| | | .brand-copy { |
| | | :deep(.el-checkbox__label) { |
| | | color: #6b7280; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .brand-points { |
| | | margin-top: 24px; |
| | | :deep(.el-checkbox__input.is-checked .el-checkbox__inner) { |
| | | background-color: var(--el-color-primary); |
| | | border-color: var(--el-color-primary); |
| | | } |
| | | |
| | | .login-panel { |
| | | padding: 12px 18px 24px; |
| | | a { |
| | | color: var(--el-color-primary); |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | text-decoration: none; |
| | | transition: color 0.2s; |
| | | |
| | | &:hover { |
| | | color: var(--el-color-primary-light-3); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .login-btn { |
| | | width: 100%; |
| | | height: 48px; |
| | | border-radius: 12px; |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | background: linear-gradient(135deg, var(--el-color-primary) 0%, var(--el-color-primary-light-3) 100%); |
| | | border: none; |
| | | box-shadow: 0 4px 14px var(--el-color-primary-light-5); |
| | | transition: all 0.2s; |
| | | |
| | | &:hover { |
| | | transform: translateY(-1px); |
| | | box-shadow: 0 6px 20px var(--el-color-primary-light-4); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 480px) { |
| | | .login-card { |
| | | width: 90%; |
| | | padding: 36px 24px; |
| | | } |
| | | |
| | | .login-form { |
| | | width: 100%; |
| | | padding: 28px 22px 24px; |
| | | .card-header { |
| | | h1 { |
| | | font-size: 20px; |
| | | } |
| | | } |
| | | } |
| | | </style> |