目 录
[2.1 后端登录功能](#2.1 后端登录功能)
[2.2 前端实现](#2.2 前端实现)
[2.3 页面展示](#2.3 页面展示)
[3.1 后端注册功能](#3.1 后端注册功能)
[3.2 前端实现](#3.2 前端实现)
[3.3 页面展示](#3.3 页面展示)
一、引言
上一篇写了购物车、订单、个人中心的功能,今天打算把登录注册功能写完。登录注册相比较来说还是简单一些的。😁
二、登录功能
登录主要是输入账号和密码来校验,我们在后端用户那里写一个登录的功能接口,根据用户去查寻用户的账号和密码是否正确。
2.1 后端登录功能
java
@PostMapping("/login")
public Result loginUser(@RequestBody User user){
//根据账号和密码查询用户
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getNo,user.getNo())
.eq(User::getPassword,user.getPassword());
User loginUser = userService.getOne(wrapper);
if (loginUser == null){
return Result.fail("用户信息不存在!");
}
//返回用户信息
loginUser.setPassword(null);
return Result.success(loginUser);
}
2.2 前端实现
前端调用后端登录接口,并把用户信息存到会话存储里面,这个记住密码有一点问题:用户登录退出登录之后再次登录显示用户账号和密码,应该也没有太大问题。
html
<template>
<div class="login-container">
<div class="login-card">
<!-- 左半部分 -->
<div class="login-left">
<div class="left-content">
<div class="logo-icon">
<el-icon :size="60"><ShoppingCart/></el-icon>
</div>
<h2 class="slogan">购物商城</h2>
<p class="desc">让购物更简单,让生活更美好!</p>
<div class="feature-list">
<div class="feature-item">
<el-icon><Goods/></el-icon>
<span>海量商品</span>
</div>
<div class="feature-item">
<el-icon><Lock/></el-icon>
<span>安全支付</span>
</div>
<div class="feature-item">
<el-icon><Bicycle/></el-icon>
<span>极速配送</span>
</div>
</div>
</div>
</div>
<!-- 右半部分 -->
<div class="login-right">
<div class="right-content">
<h2 class="welcome-title">欢迎回来</h2>
<p class="welcome-sub">请登录您的账号</p>
<el-form
class="login-form"
label-width="0"
ref="loginFormRef"
:model="loginForm"
:rules="loginRules"
>
<el-form-item prop="no">
<el-input
v-model="loginForm.no"
placeholder="请输入账号"
size="large"
:prefix-icon="User"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
placeholder="请输入密码"
size="large"
:prefix-icon="Lock"
show-password
/>
</el-form-item>
<div class="form-options">
<el-checkbox v-model="rememberPassword">记住密码</el-checkbox>
<el-link type="primary" :underline="false">忘记密码?</el-link>
</div>
<el-button
class="login-btn"
type="primary"
size="large"
:loading="loading"
@click="handleLogin"
>
登录
</el-button>
<div class="register-link">
还没有账号?
<el-link type="primary" @click="goRegister">立即注册</el-link>
</div>
</el-form>
</div>
</div>
</div>
</div>
</template>
javascript
<script setup>
import {Bicycle, Goods, Lock, ShoppingCart, User} from "@element-plus/icons-vue";
import {reactive, ref} from "vue";
import axios from "axios";
import {ElMessage} from "element-plus";
import {useRouter} from "vue-router";
const router = useRouter()
const loginFormRef = ref()
const loginForm = reactive({
no: '',
password: ''
})
const loginRules = {
no: [{ required: true, message: '请输入账号', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
}
const loading = ref(false)
const rememberPassword = ref(false)
//登录功能
const handleLogin = async () => {
if (!loginFormRef.value) return
await loginFormRef.value.validate(async (valid) => {
if (!valid) return
loading.value = true
try {
const res = await axios.post("http://localhost:8081/user/login",loginForm)
if (res.data.code === 200){
ElMessage.success("登录成功!")
sessionStorage.setItem("CurUser", JSON.stringify(res.data.data))
if (rememberPassword.value){
localStorage.setItem("rememberUser", JSON.stringify({
no: loginForm.no,
password: loginForm.password
}))
}else {
localStorage.removeItem("rememberUser")
}
await router.push('/')
}else {
ElMessage.error(res.data.msg || "登录失败!")
}
}catch (error) {
ElMessage.error("网络错误,请稍后重试!")
}finally {
loading.value = false
}
})
}
//去注册
const goRegister = () => {
router.push('/register')
}
//记住密码
const loadRememberUser = () => {
const remember = localStorage.getItem("rememberUser")
if (remember){
const user = JSON.parse(remember)
loginForm.no = user.no
loginForm.password = user.password
rememberPassword.value = true
}
}
loadRememberUser()
</script>
2.3 页面展示

三、注册功能
3.1 后端注册功能
后端注册我这个设角色状态的话是因为如果后面写商家模块的时候,需要根据roleId来进行页面选择,2是普通用户,3是商家。
java
@PostMapping("/register")
public Result registerUser(@RequestBody User user){
//检查账户是否存在
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getNo,user.getNo());
User existUser = userService.getOne(wrapper);
if (existUser != null){
return Result.fail("账号已存在!");
}
//设角色状态
if (user.getRoleId() == null){
user.setRoleId(2);
}
// 设置性别默认值(0=男,1=女)
if (user.getSex() == null){
user.setSex(1); // 默认设置为男
}
//保存用户
userService.save(user);
return Result.success("注册成功!");
}
3.2 前端实现
前端的话是增加了一项选择是用户还是商家的角色然后再去注册。
html
<template>
<div class="register-container">
<div class="register-card">
<!-- 左侧:选择角色提示 -->
<div class="register-left">
<div class="steps">
<div class="step" :class="{ active: currentStep === 1 }">
<div class="step-number">1</div>
<div class="step-text">选择角色</div>
</div>
<div class="step-line" :class="{ active: currentStep >= 2 }"></div>
<div class="step" :class="{ active: currentStep === 2 }">
<div class="step-number">2</div>
<div class="step-text">填写信息</div>
</div>
</div>
<div class="left-bg">
<el-icon :size="60"><User /></el-icon>
<h2>加入购物商城</h2>
<p>选择您的身份,开始购物之旅</p>
</div>
</div>
<!-- 右侧:表单 -->
<div class="register-right">
<!-- 选择角色 -->
<div v-if="currentStep === 1" class="role-select">
<h2>选择您的身份</h2>
<p class="subtitle">请选择您要注册的角色类型</p>
<div class="role-cards">
<div
class="role-card"
:class="{ active: selectedRole === 'user' }"
@click="selectedRole = 'user'"
>
<el-icon :size="40"><User /></el-icon>
<h3>普通用户</h3>
<p>购物、下单、查看订单</p>
</div>
<div
class="role-card"
:class="{ active: selectedRole === 'merchant' }"
@click="selectedRole = 'merchant'"
>
<el-icon :size="40"><ShoppingCart /></el-icon>
<h3>商家</h3>
<p>发布商品、管理订单</p>
</div>
</div>
<el-button
type="primary"
size="large"
class="next-btn"
:disabled="!selectedRole"
@click="nextStep"
>
下一步
</el-button>
<div class="login-link">
已有账号?
<el-link type="primary" @click="$router.push('/login')">立即登录</el-link>
</div>
</div>
<!-- 填写注册信息 -->
<div v-else class="register-form">
<h2>{{ selectedRole === 'user' ? '用户注册' : '商家注册' }}</h2>
<p class="subtitle">请填写以下信息完成注册</p>
<el-form
ref="registerFormRef"
:model="registerForm"
:rules="registerRules"
label-width="0"
class="form"
>
<el-form-item prop="no">
<el-input
v-model="registerForm.no"
placeholder="请输入账号"
size="large"
:prefix-icon="User"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="registerForm.password"
type="password"
placeholder="请输入密码"
size="large"
:prefix-icon="Lock"
show-password
/>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="registerForm.confirmPassword"
type="password"
placeholder="请确认密码"
size="large"
:prefix-icon="Lock"
show-password
/>
</el-form-item>
<el-form-item prop="nickname">
<el-input
v-model="registerForm.username"
placeholder="请输入昵称"
size="large"
:prefix-icon="User"
/>
</el-form-item>
<el-form-item prop="phone">
<el-input
v-model="registerForm.phone"
placeholder="请输入手机号"
size="large"
:prefix-icon="Phone"
/>
</el-form-item>
<el-button
type="primary"
size="large"
class="register-btn"
:loading="loading"
@click="handleRegister"
>
注 册
</el-button>
<el-button text class="back-btn" @click="prevStep">
<el-icon><ArrowLeft /></el-icon> 返回选择角色
</el-button>
</el-form>
</div>
</div>
</div>
</div>
</template>
javascript
<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { User, Lock, Phone, ShoppingCart, ArrowLeft } from '@element-plus/icons-vue'
import axios from 'axios'
const router = useRouter()
const currentStep = ref(1)
const selectedRole = ref('')
const loading = ref(false)
const registerFormRef = ref()
const registerForm = reactive({
no: '',
password: '',
confirmPassword: '',
username: '',
phone: '',
roleId: '',
})
//密码检验
const validateConfirmPassword = (rule, value, callback) => {
if (value !== registerForm.password) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
}
//校验规则
const registerRules = {
no: [
{ required: true, message: '请输入账号', trigger: 'blur' },
{ min: 3, max: 20, message: '账号长度在3-20个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 3, max: 20, message: '密码长度在3-20个字符', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{ validator: validateConfirmPassword, trigger: 'blur' }
],
username: [
{ required: true, message: '请输入昵称', trigger: 'blur' },
{ min: 2, max: 20, message: '昵称长度在2-20个字符', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
]
}
//下一步
const nextStep = () => {
if (!selectedRole.value) {
ElMessage.warning('请选择您的身份')
return
}
currentStep.value = 2
}
//退回步骤
const prevStep = () => {
currentStep.value = 1
}
//注册信息
const handleRegister = async () => {
if (!registerFormRef.value) return
await registerFormRef.value.validate(async (valid) => {
if (!valid) return
loading.value = true
const submitData = {
no: registerForm.no,
password: registerForm.password,
username: registerForm.username,
phone: registerForm.phone,
roleId: selectedRole.value === 'user' ? 2 : 3, // 普通用户=2,商家=3
}
try {
const res = await axios.post('http://localhost:8081/user/register', submitData)
if (res.data.code === 200) {
ElMessage.success('注册成功,请登录')
await router.push('/login')
} else {
ElMessage.error(res.data.msg || '注册失败')
}
} catch (error) {
ElMessage.error('网络错误,请稍后重试')
} finally {
loading.value = false
}
})
}
</script>
3.3 页面展示


四、改用户Id
我之前是写死的用户id,现在我们把用户信息存到sessionStorage里面了,可以直接拿到用户信息了。
新建一个JS文件里面写获取当前用户Id的方法,之后在需要修改的页面中引入该方法。
javascript
//获取当前用户登录ID
export const getUserId = () => {
const user = sessionStorage.getItem("CurUser")
if (!user) return null
const userData = JSON.parse(user)
return userData.id
}
javascript
import {getUserId} from "@/utils/auth";
//在方法中使用
const userId = getUserId()
if (!userId) return
await axios.get(`http://localhost:8081/usercart/clear?userId=${userId}`)
五、修改导航头
根据不同状态显示不同页面,登录之后显示用户头像,退出登录后恢复以前未登录状态显示登录和注册按钮。
html
<template>
<!-- Header -->
<div class="header">
<div class="logo" @click="$router.push('/home')"> <!-- Logo显示图标加文字 -->
<el-icon><Shop/></el-icon>
<span>购物商城</span>
</div>
<div class="search-box" v-if="showSearch"> <!-- 搜索框 -->
<el-input
v-model="searchKeyword"
placeholder="搜索商品"
size="large"
style="width: 400px"
@keyup.enter="handleSearch"
>
<template #prefix>
<el-icon><Search/></el-icon>
</template>
</el-input>
</div>
<div class="nav-menu"> <!-- 导航菜单 -->
<el-button text @click="$router.push('/home')">首页</el-button>
<el-button text @click="checkLoginAndGo('/cart')">购物车</el-button>
<el-button text @click="checkLoginAndGo('/order')">我的订单</el-button>
<el-button text @click="checkLoginAndGo('/personal')">个人中心</el-button>
<el-dropdown v-if="isLogin">
<el-avatar :src="userAvatar" :size="32" />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item divided @click="handleLogout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<template v-else>
<el-button type="primary" @click="$router.push('/login')">登录</el-button>
<el-button type="success" @click="$router.push('/register')">注册</el-button>
</template>
</div>
</div>
</template>
javascript
<script setup>
import {Search, Shop} from "@element-plus/icons-vue";
import {useRouter, useRoute} from "vue-router";
import {computed, ref} from "vue";
import {ElMessage} from "element-plus";
const router = useRouter()
const route = useRoute()
const searchKeyword = ref('')
//添加登录检查函数
const checkLoginAndGo = (path) => {
if (!isLogin.value) {
ElMessage.warning('请先登录后再访问')
router.push('/login')
return
}
router.push(path)
}
// 根据路由判断是否显示搜索框
const showSearch = computed(() => {
// 个人中心页面不显示搜索框
return route.path !== '/personal'
})
//判断是否登录
const isLogin = computed(() => {
return !!sessionStorage.getItem("CurUser")
})
//获取用户头像
const userAvatar = computed(() => {
const user = sessionStorage.getItem("CurUser")
if (user){
const userData = JSON.parse(user)
return userData.avatar || "https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
}
return "https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
})
//退出登录
const handleLogout = () => {
sessionStorage.removeItem("CurUser")
ElMessage.success("已退出登录!")
router.push('/home').then(() => {
router.go(0)
})
}
//回车搜索
const handleSearch = () => {
const keyword = searchKeyword.value.trim()
const currentPath = route.path
router.push({
path: currentPath,
query: { ...route.query, keyword: keyword || undefined }
})
}
</script>
六、总结
下一篇完善商品页面的功能,然后用户模块应该就可以结束了!😁