【Springboot3+vue3】从零到一搭建Springboot3+vue3前后端分离项目之前端环境搭建
【Springboot3+vue3】从零到一搭建Springboot3+vue3前后端分离项目之前端环境搭建
- 2 前端环境搭建
- 2.1 环境准备
- 2.2 创建Vue3项目
- 2.3 项目搭建准备
- 2.4 安装Element Plus
- 2.5 安装axios
- 2.5.1 配置(创建实例,配置请求,响应拦截器)
- 2.5.2 配置跨域
- 2.6 Vue Router安装使用
- 2.7 Pinia状态管理库
- 2.8 搭建管理页面基础框架
- 2.8.1 在src/api/下创建user.js,封装请求方法
- 2.8.2 登陆页面
- 2.9 运行展示
- 2.9.1 启动前端
- 2.9.2 启动后端
- 2.9.3 测试
主要参考的博客为:
从零搭建SpringBoot3+Vue3前后端分离项目基座,中小项目可用_springboot+vue3-CSDN博客
记录一下自己的实现过程。
最终实现效果如下:
后端环境搭建参考博客【Springboot3+vue3】从零到一搭建Springboot3+vue3前后端分离项目之后端环境搭建
2 前端环境搭建
2.1 环境准备
- node安装
- vscode安装
2.2 创建Vue3项目
在将要存放vue3项目的路径打开cmd,使用以下命令创建项目
npm init vue@latest
此时项目创建完成,vscode打开项目目录,在资源目录空白右键,打开终端
执行命令 cnpm install安装依赖,等待安装完成后执行 cnpm run dev 运行项目
访问路径http://localhost:5173可访问项目
在终端ctrl c 可停止运行项目
项目描述如图
2.3 项目搭建准备
项目中使用组合式API
删除components下的所有文件,将App.vue文件内容修改为如下
2.4 安装Element Plus
-
安装 cnpm install element-plus --save
-
cnpm install @element-plus/icons-vue
2.5 安装axios
- cnpm install axios
2.5.1 配置(创建实例,配置请求,响应拦截器)
在src目录下新建utils,并在utils下创建request.js进行axios配置
src/utils/request.js
// 请求配置 import axios from "axios"; // 定义公共前缀,创建请求实例 // const baseUrl = "http://localhost:8080"; const baseURL = '/api/'; const instance = axios.create({baseURL}) import { ElMessage } from "element-plus" import { useTokenStore } from "@/stores/token.js" // 配置请求拦截器 instance.interceptors.request.use( (config) => { // 请求前回调 // 添加token const tokenStore = useTokenStore() // 判断有无token if (tokenStore.token) { config.headers.Authorization = tokenStore.token } return config }, (err) => { // 请求错误的回调 Promise.reject(err) } ) import router from "@/router"; // 添加响应拦截器 instance.interceptors.response.use( result => { // 判断业务状态码 if (result.data.code === 1) { return result.data; } // 操作失败 ElMessage.error(result.data.message ? result.data.message : '服务异常') // 异步操作的状态转换为失败 return Promise.reject(result.data) }, err => { // 判断响应状态码, 401为未登录,提示登录并跳转到登录页面 if (err.response.status === 401) { ElMessage.error('请先登录') router.push('/login') } else { ElMessage.error('服务异常') } // 异步操作的状态转换为失败 return Promise.reject(err) } ) export default instance
2.5.2 配置跨域
在vite.config.js中加入如下内容
server: { proxy: { '/api': { // 获取路径中包含了/api的请求 target: 'http://localhost:9999', // 服务端地址 changeOrigin: true, // 修改源 rewrite:(path) => path.replace(/^\/api/, '') // api 替换为 '' } } }
2.6 Vue Router安装使用
-
安装 cnpm install vue-router@4
-
在src/router/index.js中创建路由器并导出。index.js文件内容如下
// 导入vue-router import {createRouter, createWebHistory} from 'vue-router' // 导入组件 import LoginVue from '@/views/Login.vue' import LayoutVue from '@/views/Layout.vue' import UserList from '@/views/user/UserList.vue' import EditPassword from '@/views/user/EditPassword.vue' import DisplayUser from '@/views/user/DisplayUser.vue' // 定义路由关系 const routes = [ {path: '/login', component: LoginVue}, { path: '/', component: LayoutVue, redirect: '', children: [ {path: '/user/userlist', name: "/user/userlist", component: UserList, meta: { title: "用户列表" },}, {path: '/user/editpassword', name: "/user/editpassword", component: EditPassword, meta: { title: "修改密码" } }, {path: '/user/displayuser', name: "/user/displayuser", component: DisplayUser, meta: { title: "个人信息" }} ] } ] // 创建路由器 const router = createRouter({ history: createWebHistory(), routes: routes }) export default router
-
在vue实例中使用vue-router,修改main.js文件内容为如下
import './assets/main.css' import { createApp } from 'vue' import App from './App.vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import router from '@/router' import { createPinia } from 'pinia' const pinia = createPinia() import zhLocale from 'element-plus/es/locale/lang/zh-cn' createApp(App).use(router).use(ElementPlus, {locale: zhLocale}).use(pinia).mount('#app')
-
在app.vue中声明router-view标签,展示组件内容。app.vue文件内容如下
2.7 Pinia状态管理库
-
安装 cnpm install pinia
-
安装persist cnpm install pinia-persistedstate-plugin
-
main.js中使用persist
import { createPersistedState } from 'pinia-persistedstate-plugin' const persist = createPersistedState() pinia.use(persist)
main.js内容整体如下:
import './assets/main.css' import { createApp } from 'vue' import App from './App.vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import router from '@/router' import { createPinia } from 'pinia' const pinia = createPinia() import { createPersistedState } from 'pinia-persistedstate-plugin' const persist = createPersistedState() pinia.use(persist) import zhLocale from 'element-plus/es/locale/lang/zh-cn' createApp(App).use(router).use(ElementPlus, {locale: zhLocale}).use(pinia).mount('#app')
-
src/stores/下定义token.js和userInfo.js来存储token和用户相关信息
token.js
// 定义 store import { defineStore } from "pinia" import {ref} from 'vue' /* 第一个参数:名字,唯一性 第二个参数:函数,函数的内部可以定义状态的所有内容 返回值: 函数 */ export const useTokenStore = defineStore('token', () => { // 响应式变量 const token = ref('') // 修改token值函数 const setToken = (newToken) => { token.value = newToken } // 移除token值函数 const removeToke = () => { token.value = '' } return { token, setToken, removeToke } }, { persist: true // 持久化存储 } )
userInfo.js
import { defineStore } from "pinia" import {ref} from 'vue' const useUserInfoStore = defineStore('userInfo', () => { const info = ref({}) const setInfo = (newInfo) => { info.value = newInfo } const removeInfo = () => { info.value = {} } return {info, setInfo, removeInfo} }, { persist: true } ) export default useUserInfoStore;
2.8 搭建管理页面基础框架
2.8.1 在src/api/下创建user.js,封装请求方法
import request from "@/utils/request.js" // 登录接口调用函数 export const userLoginService = (loginData) => { return request.post('/user/login', loginData) } // 获取当前登录用户信息 export const currentUserService = () => { return request.get('/user/currentUser') } // 获取所有用户信息 export const allUserService = () => { return request.get('/user/userList') } // 分页查询 export const pageListService = (pageParam) => { return request.get('/user/pageList', {params: pageParam}) } // 新增用户 export const addUserService = (addData) => { return request.post('/user/add', addData) } // 根据id获取用户信息 export const getUserById = (id) => { return request.get('/user/getuserById', {params: id}) } // 修改用户信息 export const updateUserService = (data) => { return request.put('/user/update', data) } // 删除用户 export const deleteByIdService = (id) => { console.log("deleteRequestid:", id) return request.delete('/user/delete/' + id) }
2.8.2 登陆页面
- 安装 cnpm install sass -D
在src下创建vuew项目,用于存放vue页面组件
Header.vue
首页 {{ item.meta.title }} 账号:{{userData.loginName}} {{userData.name}} 个人信息 修改密码 退出登录 import { Fold, Expand, MoreFilled, EditPen, ArrowLeft, UserFilled } from '@element-plus/icons-vue' import {ref, defineEmits, watch} from 'vue' // 面包屑 import { useRouter,useRoute } from 'vue-router' let router = useRouter() let route = useRoute() let breadList = ref() let getMatched=()=>{ console.log("route.matched:",route.matched); console breadList.value = route.matched.filter(item => item.meta && item.meta.title); } getMatched() watch(() => route.path, (newValue, oldValue) => { //监听路由路径是否发生变化,之后更改面包屑 console.log("======") breadList.value = route.matched.filter(item => item.meta && item.meta.title ); console.log("breadList.value", breadList.value) }) import useUserInfoStore from '@/stores/userinfo.js' const userInfoStore = useUserInfoStore(); // 用户数据模型 let userData = ref({ id: '', name: '', loginName: '' }) import {currentUserService} from '@/api/user.js' // 获取登录用户信息 const getUser = async () => { let result = await currentUserService() // console.log(result) userData.value = result.data; userInfoStore.setInfo(result.data) // console.log("userData:",userData) } getUser() // 折叠按钮处理 const emits = defineEmits(["parentClick"]) const isMenuOpen = ref(false) const toggleCollapse = () => { isMenuOpen.value = !isMenuOpen.value console.log(isMenuOpen.value) emits("parentClick", isMenuOpen.value) } .container { overflow: auto; /* 清除浮动影响 */ height: 48px; padding: 10px; /* 内边距 */ border-bottom: 2px solid; /* 设置下边框宽度和样式 */ border-bottom-color: #F5F5F5; /* 设置下边框颜色为红色 */ background-color: #FFFFFF; } .left { height: 48px; float: left; display: flex; align-items: center; /* 垂直居中子项 */ justify-content: center; /* 水平居中子项(如果需要)*/ } .left > div { padding-right: 10px; /* 设置直接子元素的 padding */ } .right { float: right; display: flex; align-items: center; /* 垂直居中子项 */ justify-content: center; /* 水平居中子项(如果需要)*/ } .right > div { padding-right: 10px; /* 设置直接子元素的 padding */ }
Layout.vue
import LeftLayout from './LeftLayout.vue' import Header from './Header.vue' import MainView from './MainView.vue' import {ref} from 'vue' const isCollapse = ref(false) const parentClick = (isCollapseValue) => { isCollapse.value = isCollapseValue; console.log(isCollapse.value) } 后台 ©2024 Created by buzhisuoyun .el-footer { display: flex; align-items: center; justify-content: center; font-size: 14px; color: #666; height: 38px; padding: 0; background-color: #FFFFFF; }
LeftLayout.vue
后台管理 API管理 API列表 item two item three item four item one Navigator Two Navigator Three Navigator Four 用户管理 用户列表 个人信息 修改密码 import { Document, Menu as IconMenu, Location, Share, UserFilled, Setting, } from '@element-plus/icons-vue' import {ref, defineProps} from 'vue' type Props = { isCollapse: boolean } defineProps() .el-menu-vertical-demo { height: 100vh; } .el-menu-item { min-width: 0; } .containerdiv { /* 你可以设置容器的样式,例如宽度、高度、背景色等 */ /* width: 300px; /* 示例宽度 */ height: 48px; padding: 10px; /* 内边距 */ border-bottom: 2px solid; /* 设置下边框宽度和样式 */ border-bottom-color: #F5F5F5; /* 设置下边框颜色为红色 */ } .image { display: inline-block; vertical-align: middle; /* 图片与文字垂直居中对齐 */ margin-right: 6px; /* 图片右边距,可选 */ width: 20px; } .text { display: inline-block; vertical-align: middle; /* 文字与图片垂直居中对齐 */ font-weight: bold; /* 加粗文字 */ font-size: 14px; }
Login.vue
import { User, Lock } from '@element-plus/icons-vue' import { ref, reactive } from 'vue' import { ElMessage } from 'element-plus' //定义数据模型 const registerData = ref({ loginName: 'admin', password:'admin', rePassword: '' }) // 定义表单组件的引用 const ruleFormRef = ref(null) //定义表单校验规则 const rules = ref({ loginName: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 5, max: 16, message: '长度为5~16位非空字符', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' }, { min: 5, max: 16, 2: '长度为5~16位非空字符', trigger: 'blur' } ] }) //绑定数据,复用注册表单的数据模型 //表单数据校验 //登录函数 import {userLoginService} from '@/api/user.js' import {useTokenStore} from '@/stores/token.js' import {useRouter} from 'vue-router' const router = useRouter() const tokenStore = useTokenStore(); const login = async ()=>{ // 校验表单 if (!ruleFormRef.value) return console.log("校验") await ruleFormRef.value.validate(async (valid) => { if (valid) { console.log("校验成功") // 调用接口,完成登录 let result = await userLoginService(registerData.value); /* if(result.code===0){ alert(result.msg? result.msg : '登录成功') }else{ alert('登录失败') } */ //alert(result.msg? result.msg : '登录成功') // ElMessage.success(result.msg ? result.msg : '登录成功') ElMessage.success(result.msg ? '登录成功': result.msg) //提示信息 //token存储到pinia中 tokenStore.setToken(result.data) //跳转到首页 路由完成跳转 router.push('/') } else { console.log("校验失败") } }) } //定义函数,清空数据模型的数据 const clearRegisterData = ()=>{ registerData.value={ loginName: '', password:'', rePassword:'' } }
登录
记住我 登录 /* 样式 */ .login-page { height: 100vh; background-color: #fff; .bg { background: url('@/assets/login_bg.jpg') no-repeat center / cover; border-radius: 0 20px 20px 0; } .form { display: flex; flex-direction: column; justify-content: center; user-select: none; .title { margin: 0 auto; } .button { width: 100%; } .flex { width: 100%; display: flex; justify-content: space-between; } } }MainView.vue
.app-main{ width:100%; height:100%; background-color: #FFFFFF; }
user/DisplayUser.vue
女 男 取消 确认 import {ref} from 'vue' const form = ref({ loginName: '', name: '', phone: '', sex: '0' }) // 重置对话框表单 const restForm = () => { form.value = {sex: '0'} title.value = '添加用户' isAdd.value = true } const isAdd = ref(true) // 提交事件 const onDialogFormConfirm = async () => { } // 取消事件 const onDialogFormCancel = () => { }
EditPassword.vue
修改密码
UserList.vue
import { Plus } from "@element-plus/icons-vue"; import { ref, reactive } from "vue"; import { allUserService, pageListService, addUserService, getUserById, updateUserService, deleteByIdService } from "@/api/user.js"; import { ElMessage, ElMessageBox } from "element-plus" // 表单数据 const searchData = ref({ name: "", }); // 表格数据 const tableData = ref([]); /** 分页 */ // 分页数据 const pageData = reactive({ currentPage: 1, pageSize: 10, total: 20, }) // 分页插件,每页条数发生改变时 const handleSizeChange = (val) => { pageData.pageSize = val getPageList() } // 分页插件, 当页码发生改变时 const handleCurrentChange = (val) => { pageData.currentPage = val getPageList() } // // 查询所有用户 // const getAllUser = async () => { // const result = await allUserService() // tableData.value = result.data // } // getAllUser() // 分页查询 const getPageList = async () => { const params = { currentPage: pageData.currentPage, pageSize: pageData.pageSize, name: searchData.value.name, } //console.log("params:", params); const result = await pageListService(params); pageData.total = result.data.total; tableData.value = result.data.items; //console.log("tableData:", tableData); } getPageList() // 头部表单函数定义 const onSearch = () => { getPageList() } // 重置查询表单 const onRest = () => { searchData.value = {} getPageList() } /** 添加修改对话框表单 */ const form = ref({ loginName: '', name: '', phone: '', sex: '0' }) // 重置对话框表单 const restForm = () => { form.value = {sex: '0'} title.value = '添加用户' isAdd.value = true } const title = ref('添加用户') const isAdd = ref(true) const dialogFormVisible = ref(false) // 提交对话框表单按钮事件 const onDialogFormConfirm = async () => { //1.验证表单 if (!ruleFormRef.value) return //2.提交表单 await ruleFormRef.value.validate((valid) => { if (valid) { // 校验成功 confirm() } }) } // 取消对话表单框按钮事件 const onDialogFormCancel = () => { console.log("cancel......") dialogFormVisible.value = false restForm() } // 添加按钮事件 const onAdd = () => { // 打开对话框 title.value = '添加用户' isAdd.value = true dialogFormVisible.value = true } // 修改按钮事件 const handleEdit = async (index, row) => { title.value = '修改用户' isAdd.value = false // 回显数据 console.log("row:", row) const id = {id: row.id} let result = await getUserById(id) form.value = result.data // 控制只读属性 dialogFormVisible.value = true } // 删除按钮事件 const handleDelete = (index, row) => { ElMessageBox.confirm( '确认要删除吗?', '提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning', } ) .then( async () => { // 删除 console.log("delete=====") let result = await deleteByIdService(row.id) ElMessage.success(result.msg ? result.msg : '删除成功') getPageList() }) .catch(() => { }) } // 提交表单 const confirm = async () => { if(isAdd.value) { // 添加 try { // 添加成功 let result = await addUserService(form.value) ElMessage.success(result.msg ? result.msg : '添加成功') // 关闭弹窗,清空表单 dialogFormVisible.value = false restForm() getPageList() } catch (error) { } } else { console.log("update=======") //修改 try { // 修改成功 let result = await updateUserService(form.value) ElMessage.success(result.msg ? result.msg : '修改成功') // 关闭弹窗,清空表单 dialogFormVisible.value = false restForm() getPageList() } catch (error) { } } } /** 表单校验 */ const ruleFormRef = ref(null) // 定义表单组件的引用 // 定义表单校验规则 const rules = ref({ loginName: [ { required: true, message: '请输入账号名', trigger: 'blur' } ], name: [ { required: true, message: '请输入姓名', trigger: 'blur' } ], phone: [ { required: true, trigger: 'blur', message: "请输入正确手机号", validator: checkPhone } ] }) // 手机号自定义校验 var checkPhone = (rule, value, callback) => { if (!value) { return callback(new Error('手机号不能为空')) } else { const reg = /^1[3|4|5|7|8][0-9]\d{8}$/ console.log(reg.test(value)) if (reg.test(value)) { callback() } else { return callback(new Error('请输入正确的手机号')) } } } 添加 查询 重置 {{ scope.row.sex === '1' ? "男" : "女" }} 编辑 删除 女 男 取消 确认 .operation-div { width: 100%; text-align: left; padding-left: 10px; padding-top: 10px; } .search-div { width: 100%; text-align: right; padding-top: 10px; }
2.9 运行展示
2.9.1 启动前端
- 安装 cnpm install sass -D
-
-
-
- cnpm install axios
-