| | |
| | | <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 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="brand-point"> |
| | | <span class="point-dot"></span> |
| | | <span>更轻的界面层次</span> |
| | | <!-- 卡车 --> |
| | | <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="brand-point"> |
| | | <span class="point-dot"></span> |
| | | <span>稳定的业务协同体验</span> |
| | | <!-- 箱子 --> |
| | | <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> |
| | | </section> |
| | | <h2>智能供应链管理</h2> |
| | | <p>高效协同 · 精准管控 · 数据驱动</p> |
| | | </div> |
| | | </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> |
| | | <!-- 右侧登录区 --> |
| | | <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="账号" |
| | | placeholder="用户名" |
| | | class="login-input" |
| | | > |
| | | <template #prefix><el-icon><User /></el-icon></template> |
| | | <template #prefix> |
| | | <el-icon><User /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | |
| | |
| | | auto-complete="off" |
| | | placeholder="密码" |
| | | show-password |
| | | class="login-input" |
| | | @keyup.enter="handleLogin" |
| | | > |
| | | <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template> |
| | | <template #prefix> |
| | | <el-icon><Lock /></el-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 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-submit" |
| | | class="login-btn" |
| | | @click.prevent="handleLogin" |
| | | > |
| | | <span v-if="!loading">登录</span> |
| | | <span v-else>登录中...</span> |
| | | </el-button> |
| | | </el-form> |
| | | </section> |
| | | </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" |
| | | import { User, Lock } from '@element-plus/icons-vue' |
| | | |
| | | 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) |
| | | const redirect = ref(undefined) |
| | | |
| | | watch( |
| | |
| | | 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 |
| | | } |
| | | }) |
| | |
| | | </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; |
| | | 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%); |
| | | background: linear-gradient(135deg, $primary-lightest 0%, #d4edea 100%); |
| | | padding: 60px; |
| | | } |
| | | |
| | | .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); |
| | | } |
| | | .illustration-content { |
| | | text-align: center; |
| | | |
| | | .login-brand { |
| | | 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)); |
| | | h2 { |
| | | font-size: 32px; |
| | | font-weight: 600; |
| | | color: $primary; |
| | | margin: 40px 0 12px 0; |
| | | } |
| | | |
| | | &::after { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 28px; |
| | | border: 1px solid rgba(31, 122, 114, 0.08); |
| | | border-radius: 28px; |
| | | pointer-events: none; |
| | | p { |
| | | font-size: 16px; |
| | | color: $primary-light; |
| | | margin: 0; |
| | | } |
| | | } |
| | | |
| | | .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; |
| | | /* CSS 插画场景 */ |
| | | .scene { |
| | | position: relative; |
| | | width: 320px; |
| | | height: 240px; |
| | | margin: 0 auto; |
| | | } |
| | | |
| | | .brand-logo { |
| | | width: 160px; |
| | | height: auto; |
| | | margin: 30px 0 24px; |
| | | object-fit: contain; |
| | | /* 仓库 */ |
| | | .warehouse { |
| | | position: absolute; |
| | | bottom: 40px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | } |
| | | |
| | | .brand-title { |
| | | margin: 0; |
| | | font-size: 42px; |
| | | line-height: 1.12; |
| | | color: #21313f; |
| | | letter-spacing: -0.03em; |
| | | .building { |
| | | position: relative; |
| | | width: 140px; |
| | | height: 100px; |
| | | } |
| | | |
| | | .brand-copy { |
| | | max-width: 460px; |
| | | margin: 18px 0 0; |
| | | font-size: 16px; |
| | | line-height: 1.75; |
| | | color: #5f6d7e; |
| | | .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; |
| | | } |
| | | |
| | | .brand-points { |
| | | margin-top: 34px; |
| | | .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; |
| | | flex-direction: column; |
| | | gap: 14px; |
| | | } |
| | | |
| | | .brand-point { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | gap: 12px; |
| | | color: #3d4b59; |
| | | font-size: 15px; |
| | | font-weight: 500; |
| | | padding: 0 10px; |
| | | } |
| | | |
| | | .point-dot { |
| | | width: 10px; |
| | | height: 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%; |
| | | background: linear-gradient(135deg, #1f7a72, #5ca39c); |
| | | box-shadow: 0 0 0 6px rgba(31, 122, 114, 0.08); |
| | | border: 3px solid $primary-light; |
| | | } |
| | | |
| | | .login-panel { |
| | | .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; |
| | | 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); |
| | | .login-form-box { |
| | | width: 100%; |
| | | max-width: 340px; |
| | | } |
| | | |
| | | .panel-head { |
| | | margin-bottom: 28px; |
| | | .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; |
| | | } |
| | | } |
| | | |
| | | .panel-kicker { |
| | | margin: 0 0 10px; |
| | | color: #8a98a8; |
| | | font-size: 12px; |
| | | font-weight: 700; |
| | | letter-spacing: 0.16em; |
| | | :deep(.login-input .el-input__wrapper) { |
| | | background: $primary-lightest; |
| | | border: 1px solid #d0e5e2; |
| | | border-radius: 10px; |
| | | box-shadow: none; |
| | | padding: 0 16px; |
| | | height: 48px; |
| | | |
| | | &:hover { |
| | | border-color: $primary-light; |
| | | } |
| | | |
| | | &:focus-within { |
| | | border-color: $primary; |
| | | background: #fff; |
| | | box-shadow: 0 0 0 3px rgba(31, 122, 114, 0.1); |
| | | } |
| | | } |
| | | |
| | | .panel-title { |
| | | margin: 0; |
| | | color: #21313f; |
| | | font-size: 30px; |
| | | font-weight: 700; |
| | | :deep(.login-input .el-input__inner) { |
| | | height: 48px; |
| | | font-size: 15px; |
| | | color: $primary; |
| | | |
| | | &::placeholder { |
| | | color: #8fb5af; |
| | | } |
| | | } |
| | | |
| | | .panel-subtitle { |
| | | margin: 10px 0 0; |
| | | color: #6b7888; |
| | | font-size: 14px; |
| | | :deep(.login-input .el-input__prefix) { |
| | | color: $primary-light; |
| | | margin-right: 10px; |
| | | } |
| | | |
| | | .login-options { |
| | | margin: -4px 0 22px; |
| | | .form-options { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | color: #5f6d7e; |
| | | margin: 16px 0 24px; |
| | | |
| | | :deep(.el-checkbox__label) { |
| | | font-size: 14px; |
| | | color: $primary-light; |
| | | } |
| | | |
| | | :deep(.el-checkbox__input.is-checked .el-checkbox__inner) { |
| | | background-color: $primary; |
| | | border-color: $primary; |
| | | } |
| | | |
| | | :deep(.el-checkbox__input.is-checked + .el-checkbox__label) { |
| | | color: $primary; |
| | | } |
| | | |
| | | .forgot-link { |
| | | font-size: 14px; |
| | | color: $primary; |
| | | text-decoration: none; |
| | | font-weight: 500; |
| | | |
| | | &:hover { |
| | | text-decoration: underline; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .register-link { |
| | | color: var(--el-color-primary); |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .login-submit { |
| | | .login-btn { |
| | | width: 100%; |
| | | height: 48px; |
| | | border-radius: 10px; |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | background: linear-gradient(135deg, $primary 0%, $primary-light 100%); |
| | | border: none; |
| | | transition: all 0.2s; |
| | | |
| | | &:hover { |
| | | opacity: 0.9; |
| | | transform: translateY(-1px); |
| | | box-shadow: 0 6px 20px rgba(31, 122, 114, 0.3); |
| | | } |
| | | } |
| | | |
| | | .input-icon { |
| | | width: 14px; |
| | | } |
| | | |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 22px; |
| | | } |
| | | |
| | | :deep(.el-input__wrapper) { |
| | | min-height: 42px; |
| | | height: 42px; |
| | | padding-top: 0; |
| | | padding-bottom: 0; |
| | | } |
| | | |
| | | :deep(.el-input__inner) { |
| | | height: 42px; |
| | | line-height: 42px; |
| | | } |
| | | |
| | | :deep(.el-checkbox) { |
| | | color: #5f6d7e; |
| | | } |
| | | |
| | | @media (max-width: 960px) { |
| | | .login-page { |
| | | padding: 18px; |
| | | /* 响应式 */ |
| | | @media (max-width: 900px) { |
| | | .illustration-side { |
| | | display: none; |
| | | } |
| | | |
| | | .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 { |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .brand-points { |
| | | margin-top: 24px; |
| | | } |
| | | |
| | | .login-panel { |
| | | padding: 12px 18px 24px; |
| | | } |
| | | |
| | | .login-form { |
| | | .login-side { |
| | | width: 100%; |
| | | padding: 28px 22px 24px; |
| | | } |
| | | } |
| | | </style> |