Vue 3登录页开发实战:从Element Plus定制到交互动效优化
1. 项目概述:为什么一个“好看”的登录页如此重要?
在任何一个Web应用的生命周期里,登录页面往往是用户的第一印象。它不仅仅是输入用户名和密码的入口,更是产品气质、技术栈和用户体验的集中展示。一个设计粗糙、交互卡顿的登录页,可能会在用户输入第一个字符前就劝退他们。反之,一个视觉舒适、动效流畅、逻辑清晰的登录页,能瞬间建立信任感,提升用户留存意愿。
这次,我们聚焦于使用 Vue 3 来构建这样一个“好看”的登录页面。Vue 3 带来的不仅仅是性能的提升,其组合式 API、更好的 TypeScript 支持以及更灵活的逻辑复用能力,让我们在实现复杂交互和动画效果时更加得心应手。所谓“好看”,绝不仅仅是静态的 UI 设计图还原,它更涵盖了响应式布局的适应性、交互动效的细腻度、表单验证的即时反馈、以及加载状态的无缝衔接等一系列动态体验。
本篇文章将从一个资深前端开发者的视角,手把手带你从零开始,用 Vue 3 实现一个兼具美观与实用性的登录页面。我们会深入每个技术选型背后的考量,拆解每一个交互细节的实现,并分享在实际项目中积累的、那些官方文档不会告诉你的“避坑”经验。无论你是刚接触 Vue 3 的新手,还是希望优化现有项目登录体验的开发者,都能从中获得可直接复用的代码和思路。
2. 技术选型与项目初始化:构建现代前端工程基石
在动手写第一行代码之前,合理的工具选型是项目成功的一半。对于 Vue 3 项目,我们拥有比以往更丰富、更成熟的选择。
2.1 构建工具:Vite 为何是当前不二之选?
早期 Vue 项目多基于 Vue CLI 构建,它功能完善但启动和热更新速度在大型项目中会显得迟缓。Vite 的出现彻底改变了这一局面。它利用浏览器原生 ES 模块导入,在开发环境下实现了极速的冷启动和模块热更新。对于登录页面这种需要频繁调整样式和交互、追求开发效率的场景,Vite 的优势是决定性的。
创建一个新的 Vite + Vue 3 项目非常简单:
npm create vite@latest vue3-login-demo -- --template vue cd vue3-login-demo npm install执行上述命令后,你就得到了一个最精简的 Vue 3 项目结构。我强烈建议在vite.config.js中配置一下@路径别名,这能极大提升后续引入组件和工具函数的体验。
// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': resolve(__dirname, 'src') } } })实操心得:使用
npm create vite时,模板选择vue-ts可以一步到位集成 TypeScript。对于即使是小项目,TypeScript 提供的类型提示也能在开发阶段避免许多低级错误,尤其是处理表单数据和事件回调时,收益非常明显。初期学习成本是值得的。
2.2 UI 组件库:Element Plus 与灵活自定义的平衡
为了实现快速开发和保证基础组件的一致性,选择一个 UI 组件库是明智的。在 Vue 3 生态中,Element Plus 是延续自 Element UI 的佼佼者,它组件丰富、文档清晰、社区活跃。
安装 Element Plus:
npm install element-plus然后,在main.js或main.ts中全局引入(按需引入可减小打包体积,但对于登录页这种简单页面,全局引入更省事):
import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' const app = createApp(App) app.use(ElementPlus) app.mount('#app')然而,直接使用组件库的默认样式很容易让页面看起来“千篇一律”。我们的目标是“好看”,这意味着必须在组件库的基础上进行深度的自定义。Element Plus 通过 CSS 变量和命名空间提供了强大的主题定制能力。我们后续会重点讲解如何覆盖其默认样式,打造独特视觉风格。
2.3 样式方案:Sass/SCSS 与 CSS 变量的组合拳
虽然现代 CSS(如 Flexbox, Grid)已非常强大,但 Sass/SCSS 的变量、嵌套、混入等功能能让我们更高效地管理样式。Vite 原生支持 Sass。
安装 Sass:
npm install -D sass接下来,我会采用“CSS 变量定义主题,Sass 编写具体样式”的策略。在src/assets目录下创建styles文件夹,并建立几个核心文件:
_variables.scss: 定义颜色、字体、间距、阴影等设计令牌。_mixins.scss: 定义可复用的样式片段,如清除浮动、文字省略、flex 居中。index.scss: 主样式文件,引入上述模块和全局样式。
例如,在_variables.scss中:
// 颜色主题 $--color-primary: #409eff; $--color-success: #67c23a; $--color-warning: #e6a23c; $--color-danger: #f56c6c; $--color-info: #909399; // 中性色 $--color-white: #ffffff; $--color-black: #000000; $--bg-color-page: #f2f6fc; // 页面背景 $--bg-color-card: #ffffff; // 卡片背景 $--border-color-light: #e4e7ed; // 阴影 $--box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1); $--box-shadow-card: 0 4px 20px rgba(0, 0, 0, 0.08); // 尺寸 $--border-radius-base: 8px; $--border-radius-round: 20px; $--padding-base: 24px; // 将 SCSS 变量导出为 CSS 变量,供 JS 和纯 CSS 使用 :root { --color-primary: #{$--color-primary}; --bg-color-page: #{$--bg-color-page}; --box-shadow-card: #{$--box-shadow-card}; // ... 其他变量 }这种做法的好处是,我们既能在 SCSS 中使用$--color-primary获得语法提示和运算便利,又能通过 CSS 变量var(--color-primary)实现动态主题切换(如果需要的话)。
3. 页面结构与视觉设计:从布局到细节的打磨
登录页的布局通常不复杂,但要在简洁中做出高级感,需要精心设计每一个像素。
3.1 响应式布局与背景设计
我们采用一个经典的居中卡片布局。卡片居中能有效吸引用户注意力,背景则需要营造氛围或传递品牌信息。
首先,在App.vue中,我们设置全局容器和背景:
<!-- App.vue --> <template> <div id="app"> <router-view /> <!-- 假设使用Vue Router,登录页作为独立路由 --> </div> </template> <style lang="scss"> #app { min-height: 100vh; background: linear-gradient(135deg, var(--bg-color-page) 0%, #e0eafc 100%); display: flex; align-items: center; justify-content: center; padding: 20px; // 可以添加一个微妙的动态背景,比如缓慢移动的渐变云 // background-size: 400% 400%; // animation: gradientBG 15s ease infinite; } // @keyframes gradientBG { ... } </style>然后,创建Login.vue组件。我们将使用一个div作为卡片容器,而不是直接使用el-card,以便获得最大的样式控制权。
<!-- views/Login.vue --> <template> <div class="login-container"> <div class="login-card"> <!-- 卡片内容将在这里 --> </div> </div> </template> <style lang="scss" scoped> .login-container { width: 100%; max-width: 420px; // 卡片最大宽度,在大屏幕上也不会过宽 padding: 20px; } .login-card { background: var(--bg-color-card); border-radius: var(--border-radius-round); padding: var(--padding-base); box-shadow: var(--box-shadow-card); // 添加一个很 subtle 的边框,增加层次感 border: 1px solid rgba(255, 255, 255, 0.5); // 拟态玻璃态效果 (可选,增加现代感) // backdrop-filter: blur(10px); // background: rgba(255, 255, 255, 0.85); } </style>注意事项:
max-width和padding的组合确保了在小屏手机(如 iPhone SE)上卡片两侧有呼吸空间,在大屏显示器上又不至于太宽。border-radius采用一个较大的值(20px)能让卡片看起来更柔和、现代。谨慎使用backdrop-filter(毛玻璃效果),虽然很炫酷,但其性能开销较大,在低端设备或复杂页面上可能导致滚动卡顿。
3.2 品牌标识与标题区域
卡片顶部通常是 Logo 和标题。这里的设计要点是信息层级清晰、品牌感强。
<!-- 在 login-card div 内 --> <div class="login-header"> <img src="@/assets/logo.png" alt="Logo" class="logo" /> <h1 class="title">欢迎回来</h1> <p class="subtitle">请输入您的账号信息以继续</p> </div> <style lang="scss" scoped> .login-header { text-align: center; margin-bottom: 40px; // 与表单区域拉开足够距离 .logo { height: 60px; margin-bottom: 20px; // 让Logo图片适应深色/浅色模式的小技巧 // filter: brightness(0) saturate(100%) invert(0%); // 根据背景色调整 } .title { font-size: 28px; font-weight: 600; color: #303133; margin-bottom: 8px; line-height: 1.2; } .subtitle { font-size: 14px; color: #909399; margin: 0; } } </style>3.3 表单构建:Element Plus 组件的深度定制
这是登录页的核心。我们将使用el-form、el-form-item、el-input和el-button,但会彻底改造它们的外观。
首先,建立表单的数据模型和规则:
<script setup> import { reactive, ref } from 'vue' import { ElMessage } from 'element-plus' // 表单数据 const form = reactive({ username: '', password: '' }) // 表单验证规则 const rules = { username: [ { required: true, message: '请输入用户名或邮箱', trigger: 'blur' }, { min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' }, { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' } ] } // 引用表单实例,用于手动验证 const formRef = ref() // 登录处理函数 const handleLogin = async () => { // 1. 表单验证 if (!formRef.value) return const isValid = await formRef.value.validate().catch(() => false) if (!isValid) { ElMessage.warning('请正确填写表单') return } // 2. 显示加载状态 (通过绑定到按钮的loading属性实现) loading.value = true // 3. 模拟API调用 try { // 这里替换为真实的登录API调用 // const res = await loginApi(form) await new Promise(resolve => setTimeout(resolve, 1000)) // 模拟网络延迟 ElMessage.success('登录成功!') // 4. 跳转逻辑... // router.push('/dashboard') } catch (error) { ElMessage.error(error.message || '登录失败,请检查账号密码') } finally { loading.value = false } } const loading = ref(false) </script>接下来,在模板中构建表单。关键点在于通过class和深度选择器覆盖 Element Plus 的默认样式。
<template> <el-form ref="formRef" :model="form" :rules="rules" class="login-form" @submit.prevent="handleLogin" <!-- 阻止表单默认提交 --> > <el-form-item prop="username" class="form-item-custom"> <el-input v-model="form.username" placeholder="用户名 / 邮箱" size="large" class="custom-input" :prefix-icon="User" /> </el-form-item> <el-form-item prop="password" class="form-item-custom"> <el-input v-model="form.password" type="password" placeholder="密码" size="large" class="custom-input" :prefix-icon="Lock" show-password <!-- 显示密码切换按钮 --> /> </el-form-item> <div class="form-options"> <el-checkbox v-model="rememberMe">记住我</el-checkbox> <a href="#" class="forgot-link" @click.prevent="handleForgotPassword">忘记密码?</a> </div> <el-form-item class="form-item-custom"> <el-button type="primary" size="large" class="login-btn" :loading="loading" native-type="submit" <!-- 指定为提交按钮 --> @click="handleLogin" > {{ loading ? '登录中...' : '登录' }} </el-button> </el-form-item> <div class="divider"> <span>或</span> </div> <div class="social-login"> <el-button class="social-btn" @click="loginWith('github')"> <IconGithub /> GitHub登录 </el-button> <el-button class="social-btn" @click="loginWith('wechat')"> <IconWechat /> 微信登录 </el-button> </div> <p class="signup-link"> 还没有账号? <a href="#" @click.prevent="goToSignup">立即注册</a> </p> </el-form> </template> <style lang="scss" scoped> .login-form { .form-item-custom { margin-bottom: 28px; // 增加表单项间距 :deep(.el-form-item__content) { // 深度选择器,穿透到组件内部 line-height: normal; } } .custom-input { // 覆盖输入框边框、圆角、阴影 :deep(.el-input__wrapper) { border-radius: var(--border-radius-base); box-shadow: 0 0 0 1px var(--border-color-light) inset; // 用阴影模拟边框,更柔和 transition: all 0.3s ease; padding: 2px 15px; &:hover { box-shadow: 0 0 0 1px var(--color-primary) inset; } &.is-focus { box-shadow: 0 0 0 2px var(--color-primary) inset; } } // 输入框内部文字样式 :deep(.el-input__inner) { &::placeholder { color: #c0c4cc; } } } .form-options { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; font-size: 14px; .forgot-link { color: var(--color-primary); text-decoration: none; &:hover { text-decoration: underline; } } } .login-btn { width: 100%; border-radius: var(--border-radius-base); font-size: 16px; font-weight: 500; height: 48px; // 覆盖Element按钮的渐变,使用纯色并添加微妙的阴影 background-color: var(--color-primary); border: none; box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3); transition: all 0.3s ease; &:hover { background-color: mix(#000, var(--color-primary), 10%); box-shadow: 0 6px 16px rgba(64, 158, 255, 0.4); transform: translateY(-1px); } &:active { transform: translateY(0); } } .divider { display: flex; align-items: center; margin: 30px 0; color: #c0c4cc; &::before, &::after { content: ''; flex: 1; height: 1px; background-color: #e4e7ed; } span { padding: 0 15px; font-size: 12px; } } .social-login { display: flex; gap: 15px; // 按钮间距 margin-bottom: 25px; .social-btn { flex: 1; border-radius: var(--border-radius-base); // 重置Element按钮样式,使其更中性 background-color: #f5f7fa; border-color: #e4e7ed; color: #606266; &:hover { background-color: #ebeef5; border-color: #d3d7de; } } } .signup-link { text-align: center; font-size: 14px; color: #909399; a { color: var(--color-primary); text-decoration: none; font-weight: 500; &:hover { text-decoration: underline; } } } } </style>实操心得:使用
:deep()深度选择器是覆盖组件库样式的关键。但需注意,在 Vue 3 的单文件组件中,如果使用了<style scoped>,>>>或/deep/语法已废弃,应使用:deep()。另外,对交互状态(如:hover,:focus)的样式定义是提升质感的关键。按钮的transform: translateY配合box-shadow变化,能创造出非常精致的按压反馈效果。
4. 交互增强与动画效果:让页面“活”起来
静态的页面再美也是平面的。适当的动画和交互反馈能将用户体验提升一个档次。
4.1 输入框的焦点动画与标签上浮效果
我们可以为输入框添加一个更高级的标签上浮动画,而不是简单的占位符。这需要稍微调整一下结构,为每个表单项添加一个<label>。
<!-- 修改 username 的 el-form-item --> <el-form-item prop="username" class="form-item-custom floating-label"> <div class="input-container"> <el-input v-model="form.username" size="large" class="custom-input floating-input" :prefix-icon="User" @focus="onFocus('username')" @blur="onBlur('username')" /> <label :class="{ active: isUsernameFocused || form.username }">用户名 / 邮箱</label> </div> </el-form-item> <script setup> // ... 其他代码 const isUsernameFocused = ref(false) const onFocus = (field) => { if (field === 'username') isUsernameFocused.value = true } const onBlur = (field) => { if (field === 'username') isUsernameFocused.value = false } // 对 password 字段做同样处理 </script> <style lang="scss" scoped> .floating-label { position: relative; .input-container { position: relative; } label { position: absolute; left: 48px; // 根据输入框padding和图标大小调整 top: 50%; transform: translateY(-50%); color: #c0c4cc; font-size: 16px; pointer-events: none; transition: all 0.3s ease; z-index: 2; background-color: var(--bg-color-card); padding: 0 5px; } .floating-input:focus + label, label.active { top: 0; font-size: 12px; color: var(--color-primary); transform: translateY(-50%); } // 调整输入框内部padding,为label留出空间 :deep(.el-input__inner) { padding-top: 18px; padding-bottom: 6px; } } </style>4.2 按钮加载状态与进度指示
Element Plus 的按钮自带loading属性,但我们可以让它更美观。我们可以自定义一个加载动画,或者使用一个轻量的 SVG 动画。
<template> <el-button ... :loading="loading"> <template #loading> <!-- 自定义加载图标 --> <span class="custom-loading"></span> </template> 登录 </el-button> </template> <style lang="scss" scoped> .custom-loading { display: inline-block; width: 14px; height: 14px; border: 2px solid rgba(255, 255, 255, 0.3); border-radius: 50%; border-top-color: #fff; animation: spin 0.8s linear infinite; margin-right: 6px; vertical-align: middle; } @keyframes spin { to { transform: rotate(360deg); } } </style>4.3 页面入场动画
让整个登录卡片在页面加载时有一个优雅的入场动画,能瞬间抓住用户眼球。
<template> <div class="login-container"> <div class="login-card" :style="cardStyle"> <!-- ... 卡片内容 ... --> </div> </div> </template> <script setup> import { onMounted, ref } from 'vue' const cardStyle = ref({ opacity: 0, transform: 'translateY(30px) scale(0.95)' }) onMounted(() => { // 使用requestAnimationFrame确保在下一帧开始动画 requestAnimationFrame(() => { cardStyle.value = { opacity: 1, transform: 'translateY(0) scale(1)', transition: 'opacity 0.6s ease, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1)' // 弹性曲线 } }) }) </script> <style lang="scss" scoped> .login-card { // 初始状态已在js中定义,这里定义默认的transition,以便hover等状态也有动画 transition: opacity 0.6s ease, transform 0.6s ease, box-shadow 0.3s ease; } </style>注意事项:CSS
transition和transform属性是性能最好的动画实现方式。cubic-bezier缓动函数可以创造出丰富的动画质感,比如上面的cubic-bezier(0.34, 1.56, 0.64, 1)会产生一个轻微回弹的效果,比单纯的ease更有活力。但切忌在同一页面使用过多复杂动画,会分散用户注意力并可能引起性能问题。
5. 功能完善与最佳实践
一个健壮的登录页还需要考虑许多边界情况和用户体验细节。
5.1 表单验证的即时与友好反馈
除了 Element Plus 自带的验证提示,我们可以在输入框失去焦点时立即验证,并给出更直观的视觉反馈(如边框颜色变化)。
<script setup> // 为每个字段增加验证状态 const validateStatus = reactive({ username: '', password: '' }) const onFieldBlur = async (prop) => { if (!formRef.value) return // 手动触发该字段的验证 try { await formRef.value.validateField(prop) validateStatus[prop] = 'success' } catch (error) { validateStatus[prop] = 'error' } } </script> <template> <el-input ... @blur="onFieldBlur('username')" :class="{ 'is-success': validateStatus.username === 'success', 'is-error': validateStatus.username === 'error' }" /> </template> <style lang="scss" scoped> .custom-input { :deep(.el-input__wrapper) { &.is-success { box-shadow: 0 0 0 1px var(--color-success) inset !important; } &.is-error { box-shadow: 0 0 0 1px var(--color-danger) inset !important; } } } </style>5.2 密码强度提示与显示切换
对于注册页,密码强度提示是标配。即使在登录页,一个清晰的“显示/隐藏密码”切换也很有必要。我们利用 Element Plus 的show-password属性已经实现了切换按钮,但可以自定义其图标和位置。
<style lang="scss" scoped> // 自定义密码显示切换按钮的样式 .custom-input { :deep(.el-input__suffix) { .el-icon { cursor: pointer; color: #c0c4cc; transition: color 0.2s; &:hover { color: var(--color-primary); } } } } </style>5.3 记住我、忘记密码与第三方登录
这些功能的后端逻辑和跳转由具体业务决定,但前端交互需要设计好。
- 记住我:通常是一个
checkbox,其状态应持久化到localStorage或Cookie,并在下次加载页面时自动填充。 - 忘记密码:点击后应跳转到密码重置页面,或触发一个模态框。
- 第三方登录:如 GitHub、微信等。按钮应清晰展示品牌 Logo 和名称。点击后通常重定向到 OAuth 授权页面,或打开一个弹窗。需要处理好授权回调。
5.4 路由守卫与登录状态管理
在实际项目中,登录页不是孤立的。我们需要用 Vue Router 的导航守卫来保护需要认证的路由,并在登录成功后跳转到目标页面或默认首页。
// router/index.js import { createRouter, createWebHistory } from 'vue-router' import Login from '@/views/Login.vue' import Dashboard from '@/views/Dashboard.vue' const routes = [ { path: '/login', name: 'Login', component: Login }, { path: '/dashboard', name: 'Dashboard', component: Dashboard, meta: { requiresAuth: true } }, // ... 其他路由 ] const router = createRouter({ history: createWebHistory(), routes }) // 全局前置守卫 router.beforeEach((to, from, next) => { const isAuthenticated = !!localStorage.getItem('token') // 简单示例,实际应从Vuex/Pinia读取 if (to.meta.requiresAuth && !isAuthenticated) { // 将用户试图访问的路径传递过去,以便登录后回跳 next({ name: 'Login', query: { redirect: to.fullPath } }) } else if (to.name === 'Login' && isAuthenticated) { // 已登录用户访问登录页,重定向到首页 next({ name: 'Dashboard' }) } else { next() } })在登录成功的回调中:
const handleLogin = async () => { // ... 登录API调用成功 // 1. 存储token localStorage.setItem('token', res.data.token) // 2. 跳转 const redirect = router.currentRoute.value.query.redirect router.push(redirect || '/dashboard') }6. 性能优化与安全考量
6.1 图片与图标优化
- Logo 和背景图:使用 WebP 等现代格式,并通过
<picture>元素提供回退。使用loading="lazy"异步加载非关键图片。 - 图标:推荐使用 SVG 图标库,如
@element-plus/icons-vue或unplugin-icons,它们能按需加载,且矢量缩放无损。
6.2 代码分割与按需加载
如果登录页是应用入口,应确保其加载速度最快。使用 Vite 的动态导入import()来分割其他路由组件的代码。
// router/index.js 中 const Dashboard = () => import('@/views/Dashboard.vue')6.3 防止暴力破解与安全提示
- 前端限流:在登录按钮点击后,即使请求未返回,也应禁用按钮一段时间(如1秒),防止用户快速重复点击。
- 错误信息模糊化:登录失败时,提示信息应为“用户名或密码错误”,而不是明确指出是用户名不存在还是密码错误,防止攻击者枚举用户名。
- HTTPS:确保生产环境部署在 HTTPS 下,防止凭证在传输中被窃听。
6.4 无障碍访问
- 为所有表单控件添加清晰的
label和aria-*属性。 - 确保颜色对比度符合 WCAG 标准(至少 4.5:1)。
- 键盘导航友好,可以通过 Tab 键顺序访问所有交互元素。
7. 常见问题与排查技巧实录
在实际开发中,你可能会遇到以下问题:
问题1:Element Plus 样式覆盖不生效?
- 排查:检查是否使用了
:deep()选择器,并且其父级选择器路径正确。浏览器的开发者工具检查元素,看你的样式是否被组件库更高特异性的样式覆盖。 - 解决:提高自定义样式的特异性,例如添加一个父级类名,或者使用
!important(谨慎使用)。更推荐的方式是直接通过el-form的custom-class或组件的class属性添加自定义类名,然后基于这个类名编写样式。
问题2:表单验证规则在动态变化后不更新?
- 排查:Vue 3 的响应式系统对嵌套对象的变化需要额外处理。确保
rules对象本身是响应式的(用reactive包裹),或者当规则需要动态计算时,使用计算属性返回一个新对象。 - 解决:
const dynamicRules = computed(() => ({ username: [...], password: someCondition ? [...ruleA] : [...ruleB] }))
问题3:在移动端,输入框聚焦时页面被放大?
- 排查:这是因为移动端浏览器会尝试放大页面以便输入,如果设置了
<meta name="viewport" content="width=device-width, initial-scale=1">,但字体大小小于 16px,可能会触发此行为。 - 解决:确保输入框的
font-size不小于 16px,或者为input或textarea添加 CSS 规则font-size: 16px;。也可以使用@media查询针对移动端调整。
问题4:第三方登录(OAuth)在本地开发环境回调失败?
- 排查:第三方平台(如 GitHub OAuth)通常要求配置授权的回调地址。本地开发时使用
localhost或127.0.0.1,需要确保在第三方平台的应用设置中正确添加了这些地址。 - 解决:仔细检查回调 URL 是否完全匹配,包括协议(http/https)和端口号。开发时可以使用
localhost:3000,生产环境则需改为正式的域名。
问题5:自定义的 CSS 变量在组件内不生效?
- 排查:CSS 变量是继承的。如果你在
:root中定义了变量,但在scoped样式的组件内使用,且该组件的某个子元素创建了新的堆叠上下文,变量可能无法穿透。 - 解决:将 CSS 变量定义在全局样式文件(如
index.scss)中,或者确保在需要使用的组件的最外层元素上也定义这些变量。
构建一个“好看”的登录页面,是前端技术、设计感和用户体验的融合实践。从 Vue 3 的组合式 API 管理状态,到对 Element Plus 组件样式的深度定制,再到每一个交互动画的精心打磨,每一步都需要开发者既关注宏观架构,又雕琢微观细节。希望这篇详尽的指南能为你提供一个坚实的起点和丰富的灵感。记住,最好的登录页面是让用户感觉不到它的存在——流畅、自然、安全地引导他们进入你的应用世界。