外贸网站建设平台有哪些,成都制作网站价格表,宿迁市住房和城乡建设局网站,深圳市公共资源交易中心工程交易【Springboot3vue3】从零到一搭建Springboot3vue3前后端分离项目之前端环境搭建 2 前端环境搭建2.1 环境准备2.2 创建Vue3项目2.3 项目搭建准备2.4 安装Element Plus2.5 安装axios2.5.1 配置#xff08;创建实例#xff0c;配置请求#xff0c;响应拦截器#xff09;2.5.2 … 【Springboot3vue3】从零到一搭建Springboot3vue3前后端分离项目之前端环境搭建 2 前端环境搭建2.1 环境准备2.2 创建Vue3项目2.3 项目搭建准备2.4 安装Element Plus2.5 安装axios2.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 测试 主要参考的博客为
从零搭建SpringBoot3Vue3前后端分离项目基座中小项目可用_springbootvue3-CSDN博客
记录一下自己的实现过程。
最终实现效果如下 后端环境搭建参考博客【Springboot3vue3】从零到一搭建Springboot3vue3前后端分离项目之后端环境搭建
2 前端环境搭建
2.1 环境准备
node安装vscode安装
2.2 创建Vue3项目
在将要存放vue3项目的路径打开cmd使用以下命令创建项目
npm init vuelatest此时项目创建完成vscode打开项目目录在资源目录空白右键打开终端 执行命令 cnpm install安装依赖等待安装完成后执行 cnpm run dev 运行项目 访问路径http://localhost:5173可访问项目 在终端ctrl c 可停止运行项目 项目描述如图 2.3 项目搭建准备
项目中使用组合式API
删除components下的所有文件将App.vue文件内容修改为如下
script setup/scripttemplaterouter-view/router-view
/templatestyle scoped/style
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) {// 请求前回调// 添加tokenconst tokenStore useTokenStore()// 判断有无tokenif (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-router4 在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.cssimport { 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-cncreateApp(App).use(router).use(ElementPlus, {locale: zhLocale}).use(pinia).mount(#app) 在app.vue中声明router-view标签展示组件内容。app.vue文件内容如下
script setup/scripttemplaterouter-view/router-view
/templatestyle scoped/style2.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.cssimport { 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-cncreateApp(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 vueconst 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
templatediv classcontainer!-- div left --div classleft!-- 折叠按钮--div clicktoggleCollapse()el-icon size24 v-show!isMenuOpenFold //el-iconel-icon size24 v-showisMenuOpenExpand //el-icon/div!-- 面包屑 --divel-breadcrumb separator/el-breadcrumb-item :to{ path: / }首页/el-breadcrumb-itemtemplate v-for(item, index) in breadListel-breadcrumb-itemv-ifitem.name:keyindex:toitem.path{{ item.meta.title }}/el-breadcrumb-item/template/el-breadcrumb/div/div!-- div right --div classrightdivspan 账号{{userData.loginName}}/span/divdivel-avatar {{userData.name}} /el-avatar/divdivel-dropdownel-icon size24MoreFilled //el-icontemplate #dropdownel-dropdown-menuel-dropdown-itemel-iconUserFilled //el-icon个人信息/el-dropdown-itemel-dropdown-itemel-iconEditPen //el-icon修改密码/el-dropdown-itemel-dropdown-itemel-iconArrowLeft //el-icon退出登录/el-dropdown-item/el-dropdown-menu/template/el-dropdown/div/div/div
/templatescript setupimport {Fold,Expand,MoreFilled,EditPen,ArrowLeft,UserFilled} from element-plus/icons-vueimport {ref, defineEmits, watch} from vue// 面包屑import { useRouter,useRoute } from vue-routerlet router useRouter()let route useRoute()let breadList ref()let getMatched(){console.log(route.matched:,route.matched);consolebreadList.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.jsconst 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.valueconsole.log(isMenuOpen.value)emits(parentClick, isMenuOpen.value)}/scriptstyle langscss scope.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 */ }
/style
Layout.vue
script setup
import LeftLayout from ./LeftLayout.vue
import Header from ./Header.vue
import MainView from ./MainView.vueimport {ref} from vue
const isCollapse ref(false)
const parentClick (isCollapseValue) {isCollapse.value isCollapseValue;console.log(isCollapse.value)
}
/scripttemplatediv classcommon-layoutel-containerLeftLayout :isCollapseisCollapse /el-containerel-header stylepadding: 0Header parentClickparentClick//el-headerel-main stylepadding: 16px 8px 6px 8pxMainView//el-mainel-footer后台 ©2024 Created by buzhisuoyun/el-footer/el-container/el-container/div
/templatestyle scoped.el-footer {display: flex;align-items: center;justify-content: center;font-size: 14px;color: #666;height: 38px;padding: 0;background-color: #FFFFFF; }
/style
LeftLayout.vue
templateel-row classtacel-col el-menudefault-active2classel-menu-vertical-demo:collapseisCollapse:routertrue!-- 标题 --div classcontainerdiv img src../assets/favicon.ico altYour Image classimage span classtext后台管理/span /div!-- 菜单 --el-sub-menu index1template #titleel-iconShare //el-iconspanAPI管理/span/templateel-menu-item index/api/apilistAPI列表/el-menu-itemel-menu-item index1-2item two/el-menu-itemel-menu-item index1-3item three/el-menu-itemel-sub-menu index1-4template #titleitem four/templateel-menu-item index1-4-1item one/el-menu-item/el-sub-menu/el-sub-menuel-menu-item index2el-iconicon-menu //el-iconspanNavigator Two/span/el-menu-itemel-menu-item index3 disabledel-icondocument //el-iconspanNavigator Three/span/el-menu-itemel-menu-item index4el-iconsetting //el-iconspanNavigator Four/span/el-menu-itemel-sub-menu index5template #titleel-iconUserFilled //el-iconspan用户管理/span/templateel-menu-item index/user/userlist用户列表/el-menu-itemel-menu-item index/user/displayuser个人信息/el-menu-itemel-menu-item index/user/editpassword修改密码/el-menu-item/el-sub-menu/el-menu/el-col/el-row/templatescript langts setupimport {Document,Menu as IconMenu,Location,Share,UserFilled,Setting,} from element-plus/icons-vueimport {ref, defineProps} from vuetype Props {isCollapse: boolean}definePropsProps()/scriptstyle scoped.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;}/style
Login.vue
script setup
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) returnconsole.log(校验)await ruleFormRef.value.validate(async (valid) {if (valid) {console.log(校验成功)// 调用接口,完成登录let result await userLoginService(registerData.value);/* if(result.code0){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:}
}
/scripttemplateel-row classlogin-pageel-col :span12 classbg/el-colel-col :span6 :offset3 classform!-- 登录表单 --el-form refruleFormRef :modelregisterData sizelarge autocompleteoff :rulesrulesel-form-itemh1登录/h1/el-form-itemel-form-item proploginNameel-input :prefix-iconUser placeholder请输入用户名 v-modelregisterData.loginName/el-input/el-form-itemel-form-item proppasswordel-input namepassword :prefix-iconLock typepassword placeholder请输入密码 v-modelregisterData.password/el-input/el-form-itemel-form-item classflexdiv classflexel-checkbox记住我/el-checkbox!-- el-link typeprimary :underlinefalse忘记密码/el-link --/div/el-form-item!-- 登录按钮 --el-form-itemel-button classbutton typeprimary auto-insert-space clicklogin登录/el-button/el-form-item/el-form/el-col/el-row
/templatestyle langscss scoped
/* 样式 */
.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;}}
}
/style
MainView.vue
templatediv classapp-main!-- transition namefade-transfrom modeout-inrouter-view/router-view/transition --router-view v-slot{ Component }transitioncomponent :isComponent //transition/router-view/div
/templatestyle langscss scope.app-main{width:100%;height:100%;background-color: #FFFFFF; }
/style
user/DisplayUser.vue
templatedivel-form :modelform el-form-item label账号 el-input v-modelform.loginName :disabled!isAdd//el-form-itemel-form-item label姓名 el-input v-modelform.name :disabled!isAdd//el-form-itemel-form-item label电话 el-input v-modelform.phone //el-form-itemel-form-item label性别el-radio-group v-modelform.sex :disabled!isAddel-radio value0 checked女/el-radioel-radio value1男/el-radio/el-radio-group/el-form-item/el-formdiv classdialog-footerel-button clickonDialogFormCancel取消/el-buttonel-button typeprimary clickonDialogFormConfirm确认/el-button/div/div
/template
script setupimport {ref} from vueconst 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 () {}
/scriptEditPassword.vue
templatediv 修改密码/div
/templateUserList.vue
script setup
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 valgetPageList()
}
// 分页插件 当页码发生改变时
const handleCurrentChange (val) {pageData.currentPage valgetPageList()
}// // 查询所有用户
// 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 falserestForm()
}// 添加按钮事件
const onAdd () {// 打开对话框title.value 添加用户isAdd.value truedialogFormVisible.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 falserestForm()getPageList()} catch (error) {}} else {console.log(update)//修改try { // 修改成功let result await updateUserService(form.value)ElMessage.success(result.msg ? result.msg : 修改成功)// 关闭弹窗清空表单dialogFormVisible.value falserestForm()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(请输入正确的手机号))}}
}/scripttemplatediv!-- 工具栏 --divel-rowel-col :span8!-- 操作按钮 --div classoperation-divel-button typeprimary clickonAdd添加/el-button/div/el-colel-col :span16!-- 条件查询 --div classsearch-divel-form :inlinetrue :modelsearchData classdemo-form-inlineel-form-item label用户名el-inputv-modelsearchData.nameplaceholder请输入用户名clearable//el-form-itemel-form-itemel-button typeprimary clickonSearch查询/el-buttonel-button typeprimary clickonRest重置/el-button/el-form-item/el-form/div/el-col/el-row/div!-- 表格内容 --divel-table:datatableDataborderstripestylewidth: 100%:header-cell-style{ background: #ECF5FF }el-table-column typeindex :indexindexMethod /el-table-column proploginName label账号 /el-table-column propname label姓名 /el-table-column propphone label联系电话 /el-table-column propsex label性别template #defaultscopeel-tag :typescope.row.sex 0? : success disable-transitions{{ scope.row.sex 1 ? 男 : 女 }}/el-tag/template/el-table-columnel-table-column label操作template #defaultscopeel-button sizesmall clickhandleEdit(scope.$index, scope.row)编辑/el-buttonel-buttonsizesmalltypedangerclickhandleDelete(scope.$index, scope.row)删除/el-button/template/el-table-column/el-table!-- 分页 --div stylemargin-top: 20pxel-paginationv-model:current-pagepageData.currentPagev-model:page-sizepageData.pageSize:page-sizes[10, 20, 50, 100]backgroundlayout-, jumper, total, sizes, prev, pager, next:totalpageData.totalsize-changehandleSizeChangecurrent-changehandleCurrentChange//div/div/div!-- 添加修改对话框表单--el-dialog v-modeldialogFormVisible :titletitle width500 draggable overflow closeonDialogFormCancelel-form :modelform refruleFormRef :rulesrulesel-form-item label账号 proploginNameel-input v-modelform.loginName :disabled!isAdd//el-form-itemel-form-item label姓名 propnameel-input v-modelform.name :disabled!isAdd//el-form-itemel-form-item label电话 propphoneel-input v-modelform.phone //el-form-itemel-form-item label性别el-radio-group v-modelform.sex :disabled!isAddel-radio value0 checked女/el-radioel-radio value1男/el-radio/el-radio-group/el-form-item/el-formtemplate #footerdiv classdialog-footerel-button clickonDialogFormCancel取消/el-buttonel-button typeprimary clickonDialogFormConfirm确认/el-button/div/template/el-dialog
/templatestyle scoped
.operation-div {width: 100%;text-align: left;padding-left: 10px;padding-top: 10px;
}.search-div {width: 100%;text-align: right;padding-top: 10px;
}
/style2.9 运行展示
2.9.1 启动前端
cnpm run dev 进入http://localhost:5173
2.9.2 启动后端
运行后端项目
2.9.3 测试
登录界面使用之前swagger测试时添加的用户登录即可。