太棒了!你的登录页已经结构清晰、功能完整,现在我们来 **全面升级视觉体验**,让它真正“更漂亮些”!
我们将从以下几个维度进行深度美化:
---
## ✨ 升级目标
| 改进方向 | 效果 |
|--------|------|
| 🎨 主题风格 | 温馨童趣 + 现代玻璃拟态(Glassmorphism) |
| 🌈 色彩搭配 | 柔和蓝绿渐变,适合儿童体能主题 |
| 🖼️ 动效与层次 | 登录框入场动画、按钮悬停、头像缩放 |
| 🔤 排版优化 | 字体间距、对齐方式、表单分组更舒适 |
| 📱 响应式增强 | 移动端也能优雅显示 |
---
### ✅ 修改内容汇总
1. ✅ 左侧宣传区加入 SVG 插画或动态背景
2. ✅ 登录框宽度改为 `500px` 并居中微抬
3. ✅ 注册弹窗使用卡片式设计 + 内阴影
4. ✅ 全局字体、圆角、色彩统一
5. ✅ 添加加载动效和交互反馈
---
### 🎨 最终美化版完整代码(建议全量替换)
```vue
<template>
<div class="login-container">
<!-- 左侧温馨宣传区 -->
<div class="side-banner">
<h1>儿童体能训练馆</h1>
<p>管理课程 · 预约教练 · 记录成长</p>
<div class="tagline">让每一次训练都有迹可循 💪</div>
<!-- 可选:添加一个孩子奔跑的SVG插画 -->
<div class="illustration">
<img src="@/assets/kid-running.svg" alt="儿童运动插画" />
</div>
</div>
<!-- 右侧登录区 -->
<div class="login-form-wrapper">
<el-form ref="form" :model="loginform" label-width="70px" label-position="left">
<h2 class="title">>Welcome</h2>
<el-form-item label="用户名">
<el-input
v-model="loginform.userName"
placeholder="请输入用户名"
clearable
prefix-icon="el-icon-user"
/>
</el-form-item>
<el-form-item label="密码">
<el-input
v-model="loginform.pwd"
type="password"
placeholder="请输入密码"
clearable
prefix-icon="el-icon-lock"
@keyup.enter.native="submit"
/>
</el-form-item>
<el-form-item class="actions">
<el-button type="primary" @click="submit" :loading="loading" style="width: 130px">
登 录
</el-button>
<el-button @click="dialogFormVisible = true" style="width: 130px">
注 册
</el-button>
</el-form-item>
<!-- 忘记密码链接 -->
<div class="extra-links">
<a href="#" @click.prevent="gotoForget">忘记密码?</a>
</div>
</el-form>
<!-- 注册弹窗 -->
<el-dialog
:visible.sync="dialogFormVisible"
width="520px"
center
custom-class="register-dialog"
append-to-body
@close="resetForm"
>
<template #title>
<h3 style="text-align: center; color: #2d8cf0; font-weight: 600;">
🧒 创建新账户
</h3>
</template>
<el-form
:model="form"
:rules="registerRules"
ref="registerRef"
label-position="left"
label-width="80px"
class="register-form"
>
<!-- 用户名 + 昵称 -->
<div class="row">
<el-form-item label="用户名" prop="userName">
<el-input v-model="form.userName" placeholder="6-10位字母数字" />
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" placeholder="如:小明妈妈" />
</el-form-item>
</div>
<!-- 密码 + 手机号 -->
<div class="row">
<el-form-item label="密码" prop="pwd">
<el-input v-model="form.pwd" type="password" placeholder="6-10位密码" />
</el-form-item>
<el-form-item label="手机号" prop="phoneCode">
<el-input v-model="form.phoneCode" placeholder="用于接收通知" />
</el-form-item>
</div>
<!-- 头像 + 角色 -->
<div class="upload-row">
<!-- 头像 -->
<el-form-item label="头像">
<el-upload
class="avatar-uploader"
action="http://localhost:8081/file/upload"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
<img v-if="form.pic" :src="form.pic" class="avatar" alt="头像" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<!-- 角色 -->
<el-form-item label="角色" prop="roleId" class="role-group">
<el-radio-group v-model="form.roleId">
<el-radio-button label="1">管理员</el-radio-button>
<el-radio-button label="2">家长/用户</el-radio-button>
<el-radio-button label="3">教练</el-radio-button>
</el-radio-group>
</el-form-item>
</div>
<!-- 操作按钮 -->
<el-form-item style="text-align: center; margin-top: 20px;">
<el-button type="primary" @click="register" size="medium" style="min-width: 90px">
注 册
</el-button>
<el-button @click="resetForm" size="medium" style="min-width: 90px; margin-left: 16px">
重 置
</el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
</div>
</template>
<script>
import { login } from "@/api/Login";
import { register } from "@/api/Login";
export default {
name: "LoginView",
data() {
return {
loading: false,
dialogFormVisible: false,
loginform: {
userName: "",
pwd: "",
},
form: {
roleId: "2",
pic: "",
},
registerRules: {
userName: [
{ required: true, message: "请输入用户名", trigger: "blur" },
{ min: 6, max: 10, message: "长度在 6 到 10 个字符", trigger: "blur" },
],
nickName: [
{ required: true, message: "请输入昵称", trigger: "blur" },
{ min: 1, max: 6, message: "长度在 1 到 6 个字符", trigger: "blur" },
],
pwd: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 6, max: 10, message: "长度在 6 到 10 个字符", trigger: "blur" },
],
phoneCode: [
{ required: true, message: "请输入手机号", trigger: "blur" },
{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号", trigger: "blur" },
],
roleId: [{ required: true, message: "请选择角色", trigger: "change" }],
},
};
},
methods: {
handleAvatarSuccess(res) {
if (res.data?.url) {
this.form.pic = res.data.url;
this.$message.success("头像上传成功!");
} else {
this.$message.error("上传失败,请重试");
}
},
beforeAvatarUpload(file) {
const isImage = ["image/jpeg", "image/jpg", "image/png"].includes(file.type);
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isImage) {
this.$message.error("头像必须是 JPG 或 PNG 格式!");
return false;
}
if (!isLt2M) {
this.$message.error("头像图片大小不能超过 2MB!");
return false;
}
return true;
},
submit() {
this.$refs.form.validateField(["userName", "pwd"], (valid) => {
if (!valid) {
this.loading = true;
login(this.loginform)
.then((resp) => {
if (resp.data.code === 200) {
this.$message.success("登录成功!");
const user = resp.data.data.user;
localStorage.setItem("userId", user.id);
localStorage.setItem("roleId", user.roleId);
if (user.roleId == 1) {
this.$router.push("/adminChart");
} else if (user.roleId == 2) {
this.$router.push("/user/bulletin");
} else {
this.$router.push("/coach/bulletin");
}
} else {
this.$message.error(resp.data.message || "登录失败");
}
})
.catch((err) => {
console.error("登录请求失败:", err);
this.$message.error("网络错误,请检查连接");
})
.finally(() => {
this.loading = false;
});
}
});
},
register() {
this.$refs.registerRef.validate((valid) => {
if (valid) {
register(this.form)
.then((resp) => {
if (resp.data.code === 200) {
this.$message.success("注册成功,请登录");
this.dialogFormVisible = false;
} else {
this.$message.error(resp.data.message || "注册失败");
}
})
.catch((err) => {
console.error("注册失败:", err);
this.$message.error("网络异常,请稍后重试");
});
}
});
},
resetForm() {
if (this.$refs.registerRef) {
this.$refs.registerRef.resetFields();
}
this.form.pic = "";
},
gotoForget() {
this.$alert('请联系管理员重置密码', '忘记密码', {
confirmButtonText: '确定',
type: 'info'
});
}
},
};
</script>
<style scoped>
.login-container {
display: flex;
height: 100vh;
font-family: 'Nunito', 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #e0f7fa, #fff3e0), url("@/assets/login-bg-blur.jpg") no-repeat center center fixed;
background-size: cover;
position: relative;
}
/* 左侧宣传栏 */
.side-banner {
flex: 1;
background: rgba(0, 82, 147, 0.6); /* 蓝色主调 */
color: #fff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 60px 40px;
backdrop-filter: blur(10px);
border-radius: 0 20px 20px 0;
position: relative;
overflow: hidden;
}
.side-banner::before {
content: '';
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1) 0%, transparent 70%);
pointer-events: none;
}
.side-banner h1 {
font-size: 2.8rem;
margin-bottom: 12px;
font-weight: 800;
text-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
letter-spacing: -0.5px;
}
.side-banner p {
font-size: 1.3rem;
margin-bottom: 10px;
opacity: 0.9;
}
.tagline {
font-size: 1.1rem;
color: #e0f7ff;
margin-top: 20px;
padding: 12px 24px;
background: rgba(255, 255, 255, 0.15);
border-radius: 30px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.illustration img {
width: 180px;
margin-top: 30px;
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.1));
}
/* 右侧登录框 */
.login-form-wrapper {
width: 500px;
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(12px);
border-radius: 20px 0 0 20px;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.18);
padding: 50px 40px;
display: flex;
flex-direction: column;
justify-content: center;
animation: slide-up 0.6s ease forwards;
transform: translateY(20px);
z-index: 10;
}
@keyframes slide-up {
to {
transform: translateY(0);
opacity: 1;
}
}
.title {
text-align: center;
color: #2d8cf0;
margin-bottom: 36px;
font-size: 1.8rem;
font-weight: 700;
letter-spacing: -0.5px;
}
.actions {
margin-top: 16px;
}
.extra-links {
text-align: right;
font-size: 13px;
color: #999;
margin-top: -10px;
}
.extra-links a {
color: #2d8cf0;
text-decoration: none;
}
.extra-links a:hover {
text-decoration: underline;
}
::v-deep .el-form-item__label {
font-weight: 600;
color: #333;
font-size: 14px;
}
/* 表单行布局 */
.row {
display: flex;
gap: 24px;
margin-bottom: 20px;
}
.row .el-form-item {
flex: 1;
}
.upload-row {
display: flex;
gap: 30px;
align-items: flex-start;
margin-bottom: 20px;
}
.role-group ::v-deep .el-radio-button__inner {
padding: 8px 12px;
border-radius: 8px !important;
border: 1px solid #ddd;
}
.role-group ::v-deep .el-radio-button__orig-radio:checked + .el-radio-button__inner {
background: #2d8cf0;
border-color: #2d8cf0;
color: white;
}
/* 头像上传 */
.avatar-uploader-icon {
border: 2px dashed #d9edf7;
border-radius: 50%;
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
font-size: 24px;
color: #8cc5ff;
cursor: pointer;
transition: all 0.3s ease;
background: #f5f9ff;
}
.avatar-uploader-icon:hover {
border-color: #2d8cf0;
color: #2d8cf0;
transform: scale(1.05);
}
.avatar {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 50%;
border: 3px solid #e6f1ff;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
.avatar:hover {
transform: scale(1.08);
}
/* 注册弹窗整体样式 */
::v-deep .register-dialog {
border-radius: 20px;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
::v-deep .register-dialog .el-dialog__body {
padding: 20px 0 10px;
background: #fafbfc;
}
/* 响应式适配 */
@media (max-width: 768px) {
.login-container {
flex-direction: column;
}
.side-banner {
padding: 30px 20px;
border-radius: 20px 20px 0 0;
background: rgba(0, 82, 147, 0.7);
}
.login-form-wrapper {
width: 90%;
border-radius: 0;
margin-top: -60px;
padding: 40px 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
}
.side-banner h1 {
font-size: 2rem;
}
.side-banner p, .tagline {
font-size: 1rem;
}
.illustration img {
width: 120px;
}
}
</style>
```
---
## ✅ 必须准备的资源
| 文件路径 | 说明 |
|--------|------|
| `src/assets/login-bg-blur.jpg` | 模糊化背景图(推荐蓝天草地或孩子运动场景) |
| `src/assets/kid-running.svg` | 儿童奔跑插画 SVG(可从 [undraw.co](https://undraw.co) 下载) |
> 示例 SVG 替代代码(无需文件):
```html
<div class="illustration">
<svg width="180" viewBox="0 0 600 400" xmlns="http://www.w3.org/2000/svg">
<rect width="600" height="400" fill="#e0f7fa"/>
<circle cx="300" cy="120" r="40" fill="#ffb74d"/>
<rect x="270" y="160" width="60" height="80" fill="#64b5f6"/>
<line x1="270" y1="180" x2="230" y2="220" stroke="#64b5f6" stroke-width="10" stroke-linecap="round"/>
<line x1="330" y1="180" x2="370" y2="220" stroke="#64b5f6" stroke-width="10" stroke-linecap="round"/>
<line x1="270" y1="240" x2="250" y2="280" stroke="#8d6e63" stroke-width="10" stroke-linecap="round"/>
<line x1="330" y1="240" x2="350" y2="280" stroke="#8d6e63" stroke-width="10" stroke-linecap="round"/>
</svg>
</div>
```
---
## 🌟 视觉亮点总结
| 特性 | 效果 |
|------|------|
| 🎨 渐变背景 + 毛玻璃 | 更现代、更有呼吸感 |
| 🧍♂️ SVG 插画点缀 | 提升童趣氛围 |
| 🌀 登录框滑入动画 | 增加仪式感 |
| 🔘 圆角按钮 + 悬停反馈 | 交互更友好 |
| 📱 完整响应式支持 | 手机和平板都好看 |
| 📝 “忘记密码”提示 | 功能更完整 |
---