<template>
<div class="login-container">
<!-- 左侧宣传区 -->
<div class="side-banner">
<h1>儿童体能训练馆</h1>
<p>管理课程 · 预约教练 · 记录成长</p>
<div class="tagline">让每一次训练都有迹可循</div>
</div>
<!-- 右侧登录区 -->
<div class="login-form-wrapper">
<el-form
ref="form"
:model="loginform"
label-width="60px"
label-position="left"
>
<h2 class="title">欢迎登录</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">
登 录
</el-button>
<el-button @click="dialogFormVisible = true">注 册</el-button>
</el-form-item>
</el-form>
<!-- 注册弹窗(已放大并美化) -->
<el-dialog
:visible.sync="dialogFormVisible"
width="500px"
center
custom-class="register-dialog"
append-to-body
@close="resetForm"
>
<template #title>
<h3 style="text-align: center; color: #1e3a8a; font-weight: 600">
🧒 创建新用户
</h3>
</template>
<el-form
:model="form"
:rules="registerRules"
ref="registerRef"
label-position="left"
label-width="70px"
style="padding: 0 20px"
>
<!-- 第一行:用户名 + 昵称 -->
<div style="display: flex; gap: 20px">
<el-form-item label="用户名" prop="userName" style="flex: 1">
<el-input
v-model="form.userName"
placeholder="6-10位字母或数字"
/>
</el-form-item>
<el-form-item label="昵称" prop="nickName" style="flex: 1">
<el-input v-model="form.nickName" placeholder="如:小明爸爸" />
</el-form-item>
</div>
<!-- 第二行:密码 + 手机号 -->
<div style="display: flex; gap: 20px">
<el-form-item label="密码" prop="pwd" style="flex: 1">
<el-input
v-model="form.pwd"
type="password"
placeholder="6-10位密码"
/>
</el-form-item>
<el-form-item label="手机号" prop="phoneCode" style="flex: 1">
<el-input v-model="form.phoneCode" placeholder="用于接收通知" />
</el-form-item>
</div>
<!-- 头像上传 + 角色选择 水平排列 -->
<div
style="
display: flex;
gap: 40px;
align-items: flex-start;
margin-bottom: 20px;
"
>
<!-- 头像 -->
<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" style="flex: 1">
<el-radio-group v-model="form.roleId" style="margin-top: 12px">
<div style="margin-bottom: 8px">
<el-radio-button label="1">管理员</el-radio-button>
</div>
<div style="margin-bottom: 8px">
<el-radio-button label="2">家长/用户</el-radio-button>
</div>
<div>
<el-radio-button label="3">教练</el-radio-button>
</div>
</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="width: 120px"
>
注 册
</el-button>
<el-button
@click="resetForm"
size="medium"
style="width: 120px; margin-left: 20px"
>
重 置
</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,
pic: "",
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 = "";
this.pic = "";
},
},
};
</script>
<style scoped>
.login-container {
display: flex;
height: 100vh;
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
background: url("@/assets/login.jpg") no-repeat center center fixed;
background-size: cover;
}
/* 左侧宣传栏 */
.side-banner {
flex: 1;
background: rgba(0, 0, 0, 0.4);
color: #fff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 40px;
backdrop-filter: blur(8px);
border-radius: 0 20px 20px 0;
}
.side-banner h1 {
font-size: 2.5rem;
margin-bottom: 12px;
font-weight: 700;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.side-banner p {
font-size: 1.2rem;
margin-bottom: 8px;
}
.tagline {
font-size: 1rem;
color: #e2e8f0;
margin-top: 16px;
padding: 10px 20px;
border-top: 1px solid rgba(255, 255, 255, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
/* 右侧登录框 */
.login-form-wrapper {
width: 420px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px 0 0 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
padding: 40px 30px;
display: flex;
flex-direction: column;
justify-content: center;
animation: slide-in 0.6s ease;
}
.title {
text-align: center;
color: #1e3a8a;
margin-bottom: 30px;
font-size: 1.6rem;
font-weight: 600;
}
.actions {
margin-top: 10px;
}
::v-deep .el-form-item__label {
font-weight: 500;
color: #333;
}
/* 动画入场 */
@keyframes slide-in {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* 头像上传美化 */
.avatar-uploader {
display: inline-block;
}
.avatar-uploader-icon {
border: 2px dashed #c0ccda;
border-radius: 50%;
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
font-size: 24px;
color: #999;
cursor: pointer;
transition: all 0.3s ease;
}
.avatar-uploader-icon:hover {
border-color: #409eff;
color: #409eff;
}
.avatar {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 50%;
border: 3px solid #e6f1ff;
transition: transform 0.3s ease;
}
.avatar:hover {
transform: scale(1.05);
}
/* 注册弹窗样式 */
::v-deep .register-dialog {
border-radius: 16px;
overflow: hidden;
}
::v-deep .register-dialog .el-dialog__body {
padding: 30px 20px;
}
/* 响应式适配 */
@media (max-width: 768px) {
.login-container {
flex-direction: column;
}
.side-banner {
padding: 20px;
border-radius: 20px 20px 0 0;
}
.login-form-wrapper {
width: 100%;
border-radius: 0;
margin-top: -40px;
z-index: 10;
}
.side-banner h1 {
font-size: 1.8rem;
}
}
</style>
更漂亮些