【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文件内容修改为如下
<script setup></script><template><router-view></router-view>
</template><style 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-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文件内容如下
<script setup></script><template><router-view></router-view> </template><style scoped></style>
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
<template><div class="container"><!-- div left --><div class="left"><!-- 折叠按钮--><div @click="toggleCollapse()"><el-icon size="24" v-show="!isMenuOpen"><Fold /></el-icon><el-icon size="24" v-show="isMenuOpen"><Expand /></el-icon></div><!-- 面包屑 --><div><el-breadcrumb separator="/"><el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item><template v-for="(item, index) in breadList"><el-breadcrumb-itemv-if="item.name":key="index":to="item.path">{{ item.meta.title }}</el-breadcrumb-item></template></el-breadcrumb></div></div><!-- div right --><div class="right"><div><span >账号:{{userData.loginName}}</span></div><div><el-avatar> {{userData.name}} </el-avatar></div><div><el-dropdown><el-icon size="24"><MoreFilled /></el-icon><template #dropdown><el-dropdown-menu><el-dropdown-item><el-icon><UserFilled /></el-icon>个人信息</el-dropdown-item><el-dropdown-item><el-icon><EditPen /></el-icon>修改密码</el-dropdown-item><el-dropdown-item><el-icon><ArrowLeft /></el-icon>退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown></div></div></div>
</template><script setup>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);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.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.valueconsole.log(isMenuOpen.value)emits("parentClick", isMenuOpen.value)}</script><style lang="scss" 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.vue'import {ref} from 'vue'
const isCollapse = ref(false)
const parentClick = (isCollapseValue) => {isCollapse.value = isCollapseValue;console.log(isCollapse.value)
}
</script><template><div class="common-layout"><el-container><LeftLayout :isCollapse='isCollapse' /><el-container><el-header style="padding: 0"><Header @parentClick='parentClick'/></el-header><el-main style="padding: 16px 8px 6px 8px"><MainView/></el-main><el-footer>后台 ©2024 Created by buzhisuoyun</el-footer></el-container></el-container></div>
</template><style 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
<template><el-row class="tac"><el-col ><el-menudefault-active="2"class="el-menu-vertical-demo":collapse="isCollapse":router="true"><!-- 标题 --><div class="containerdiv"> <img src="../assets/favicon.ico" alt="Your Image" class="image"> <span class="text">后台管理</span> </div><!-- 菜单 --><el-sub-menu index="1"><template #title><el-icon><Share /></el-icon><span>API管理</span></template><el-menu-item index="/api/apilist">API列表</el-menu-item><el-menu-item index="1-2">item two</el-menu-item><el-menu-item index="1-3">item three</el-menu-item><el-sub-menu index="1-4"><template #title>item four</template><el-menu-item index="1-4-1">item one</el-menu-item></el-sub-menu></el-sub-menu><el-menu-item index="2"><el-icon><icon-menu /></el-icon><span>Navigator Two</span></el-menu-item><el-menu-item index="3" disabled><el-icon><document /></el-icon><span>Navigator Three</span></el-menu-item><el-menu-item index="4"><el-icon><setting /></el-icon><span>Navigator Four</span></el-menu-item><el-sub-menu index="5"><template #title><el-icon><UserFilled /></el-icon><span>用户管理</span></template><el-menu-item index="/user/userlist">用户列表</el-menu-item><el-menu-item index="/user/displayuser">个人信息</el-menu-item><el-menu-item index="/user/editpassword">修改密码</el-menu-item></el-sub-menu></el-menu></el-col></el-row></template><script lang="ts" setup>import {Document,Menu as IconMenu,Location,Share,UserFilled,Setting,} from '@element-plus/icons-vue'import {ref, defineProps} from 'vue'type Props = {isCollapse: boolean}defineProps<Props>()</script><style 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.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:''}
}
</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 登录表单 --><el-form ref="ruleFormRef" :model=registerData size="large" autocomplete="off" :rules="rules"><el-form-item><h1>登录</h1></el-form-item><el-form-item prop="loginName"><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.loginName"></el-input></el-form-item><el-form-item prop="password"><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input></el-form-item><el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><!-- <el-link type="primary" :underline="false">忘记密码?</el-link> --></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button></el-form-item></el-form></el-col></el-row>
</template><style lang="scss" 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
<template><div class="app-main"><!-- <transition name="fade-transfrom" mode="out-in"><router-view></router-view></transition> --><router-view v-slot="{ Component }"><transition><component :is="Component" /></transition></router-view></div>
</template><style lang="scss" scope>.app-main{width:100%;height:100%;background-color: #FFFFFF; }
</style>
user/DisplayUser.vue
<template><div><el-form :model="form" ><el-form-item label="账号" ><el-input v-model="form.loginName" :disabled="!isAdd"/></el-form-item><el-form-item label="姓名" ><el-input v-model="form.name" :disabled="!isAdd"/></el-form-item><el-form-item label="电话" ><el-input v-model="form.phone" /></el-form-item><el-form-item label="性别"><el-radio-group v-model="form.sex" :disabled="!isAdd"><el-radio value="0" checked>女</el-radio><el-radio value="1">男</el-radio></el-radio-group></el-form-item></el-form><div class="dialog-footer"><el-button @click="onDialogFormCancel">取消</el-button><el-button type="primary" @click="onDialogFormConfirm">确认</el-button></div></div>
</template>
<script setup>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 = () => {}
</script>
EditPassword.vue
<template><div> 修改密码</div>
</template>
UserList.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('请输入正确的手机号'))}}
}</script><template><div><!-- 工具栏 --><div><el-row><el-col :span="8"><!-- 操作按钮 --><div class="operation-div"><el-button type="primary" @click="onAdd">添加</el-button></div></el-col><el-col :span="16"><!-- 条件查询 --><div class="search-div"><el-form :inline="true" :model="searchData" class="demo-form-inline"><el-form-item label="用户名:"><el-inputv-model="searchData.name"placeholder="请输入用户名"clearable/></el-form-item><el-form-item><el-button type="primary" @click="onSearch">查询</el-button><el-button type="primary" @click="onRest">重置</el-button></el-form-item></el-form></div></el-col></el-row></div><!-- 表格内容 --><div><el-table:data="tableData"borderstripestyle="width: 100%":header-cell-style="{ background: '#ECF5FF' }"><el-table-column type="index" :index="indexMethod" /><el-table-column prop="loginName" label="账号" /><el-table-column prop="name" label="姓名" /><el-table-column prop="phone" label="联系电话" /><el-table-column prop="sex" label="性别"><template #default="scope"><el-tag :type="scope.row.sex === '0'? '' : 'success'" disable-transitions>{{ scope.row.sex === '1' ? "男" : "女" }}</el-tag></template></el-table-column><el-table-column label="操作"><template #default="scope"><el-button size="small" @click="handleEdit(scope.$index, scope.row)">编辑</el-button><el-buttonsize="small"type="danger"@click="handleDelete(scope.$index, scope.row)">删除</el-button></template></el-table-column></el-table><!-- 分页 --><div style="margin-top: 20px"><el-paginationv-model:current-page="pageData.currentPage"v-model:page-size="pageData.pageSize":page-sizes="[10, 20, 50, 100]"backgroundlayout="->, jumper, total, sizes, prev, pager, next":total="pageData.total"@size-change="handleSizeChange"@current-change="handleCurrentChange"/></div></div></div><!-- 添加修改对话框表单--><el-dialog v-model="dialogFormVisible" :title="title" width="500" draggable overflow @close='onDialogFormCancel'><el-form :model="form" ref="ruleFormRef" :rules="rules"><el-form-item label="账号" prop="loginName"><el-input v-model="form.loginName" :disabled="!isAdd"/></el-form-item><el-form-item label="姓名" prop="name"><el-input v-model="form.name" :disabled="!isAdd"/></el-form-item><el-form-item label="电话" prop="phone"><el-input v-model="form.phone" /></el-form-item><el-form-item label="性别"><el-radio-group v-model="form.sex" :disabled="!isAdd"><el-radio value="0" checked>女</el-radio><el-radio value="1">男</el-radio></el-radio-group></el-form-item></el-form><template #footer><div class="dialog-footer"><el-button @click="onDialogFormCancel">取消</el-button><el-button type="primary" @click="onDialogFormConfirm">确认</el-button></div></template></el-dialog>
</template><style scoped>
.operation-div {width: 100%;text-align: left;padding-left: 10px;padding-top: 10px;
}.search-div {width: 100%;text-align: right;padding-top: 10px;
}
</style>
2.9 运行展示
2.9.1 启动前端
cnpm run dev

进入http://localhost:5173
2.9.2 启动后端
运行后端项目
2.9.3 测试
登录界面,使用之前swagger测试时添加的用户登录即可。


相关文章:
【Springboot3+vue3】从零到一搭建Springboot3+vue3前后端分离项目之前端环境搭建
【Springboot3vue3】从零到一搭建Springboot3vue3前后端分离项目之前端环境搭建 2 前端环境搭建2.1 环境准备2.2 创建Vue3项目2.3 项目搭建准备2.4 安装Element Plus2.5 安装axios2.5.1 配置(创建实例,配置请求,响应拦截器)2.5.2 …...
手写Mybatis框架源码(简写)
pom文件: springboot版本:2.6.5 jdk:8 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance&q…...
Flask返回中文Unicode编码(乱码)解决方案
大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…...
最大值和最小值的差
最大值和最小值的差 C语言代码C 语言代码Java语言代码Python语言代码 💐The Begin💐点点关注,收藏不迷路💐 输出一个整数序列中最大的数和最小的数的差。 输入 第一行为M,表示整数个数,整数个数不会大于1…...
如何在 IntelliJ IDEA 中为 Spring Boot 应用实现热部署
文章目录 1. 引言2. 准备工作3. 添加必要的依赖4. 配置 IntelliJ IDEA4.1 启用自动编译4.2 开启热部署策略 5. 测试热部署6. 高级技巧7. 注意事项8. 总结 随着现代开发工具的进步,开发者们越来越重视提高生产力的特性。对于 Java 开发者来说,能够在不重启…...
探索 Java 中的 Bug 世界
在 Java 编程的旅程中,我们不可避免地会遇到各种 Bug。这些 Bug 可能会导致程序出现意外的行为、崩溃或者性能问题。了解 Java Bug 的类型、产生原因以及解决方法,对于提高我们的编程技能和开发出稳定可靠的应用程序至关重要。 一、Java Bug 的定义与分类…...
SQL面试题——百度SQL面试题 连续签到领金币
百度SQL面试题 连续签到领金币 今天的这个题目来自百度,而且这个题目很常见,是一个大家日常经常遇到的一个场景,几乎无处不在,就是签到送积分,只不过这里是签到领金币 有用户签到记录表,sign,记录用户当天是否完成签到,请计算出每个用户的每个月获得的金币数量; 签到…...
easyExcel单一下拉框和级联下拉框
文章目录: 单一下拉框级联下拉框 具体实现: 单一下拉框 public class BoolWriteHandler implements SheetWriteHandler {private List<String> dropDown;private List<Integer> indexList;public BoolWriteHandler(List<Integer> i…...
linux-安全-iptables防火墙基础笔记
目录 一、 iptables链结构 五链 二、 iptables表结构 四表 三、 匹配流程 四、 语法 五、 匹配 1. 通用匹配 2. 隐含匹配 3. 显示匹配 六、 SNAT 七、 DNAT 八、 规则备份及还原 1. 备份 2. 还原 这篇将讲解iptables防火墙的基础知识 一、 iptables链结构 规则…...
力扣刷题TOP101: 25.BM32合并二叉树
目录: 目的 思路 复杂度 记忆秘诀 python代码 目的: 已知两颗二叉树,将它们合并成一颗二叉树。合并规则是:都存在的结点,就将结点值加起来,否则空的位置就由另一个树的结点来代替。 思路 我们有两棵二…...
R的中文文本处理包--tmcn
文章目录 介绍tmcn 和 jieba 的关系函数:catUTF8toUTF8实例 介绍 tmcn 包是 R 语言中的一个用于处理和分析中文文本的包,特别适用于中文文本的分词、词频统计和文本挖掘等任务。以下是 tmcn 包的基本用法,包括安装、常用函数和示例。 一个用…...
差异基因富集分析(R语言——GOKEGGGSEA)
接着上次的内容,上篇内容给大家分享了基因表达量怎么做分组差异分析,从而获得差异基因集,想了解的可以去看一下,这篇主要给大家分享一下得到显著差异基因集后怎么做一下通路富集。 1.准备差异基因集 我就直接把上次分享的拿到这…...
scrapy对接rabbitmq的时候使用post请求
之前做分布式爬虫的时候,都是从push url来拿到爬虫消费的链接,这里提出一个问题,假如这个请求是post请求的呢,我观察了scrapy-redis的源码,其中spider.py的代码是这样写的 1.scrapy-redis源码分析 def make_request_from_data(self, data):"""Returns a Reques…...
vue+elementUI+transition实现鼠标滑过div展开内容,鼠标划出收起内容,加防抖功能
文章目录 一、场景二、实现代码1.子组件代码结构2.父组件 一、场景 这两天做项目,此产品提出需求 要求详情页的顶部区域要在鼠标划入后展开里面的内容,鼠标划出要收起部分内容,详情底部的内容高度要自适应,我这里运用了鼠标事件t…...
大模型语料库的构建过程 包括知识图谱构建 垂直知识图谱构建 输入到sql构建 输入到cypher构建 通过智能体管理数据生产组件
以下是大模型语料库的构建过程: 一、文档切分语料库构建 数据来源确定: 首先,需要确定语料库的数据来源。这些来源可以是多种多样的,包括但不限于: 网络资源:利用网络爬虫技术从各种网站(如新闻…...
阿里云ECS服务器域名解析
阿里云ECS服务器域名解析,以前添加两条A记录类型,主机记录分别为www和,这2条记录都解析到服务器IP地址。 1.进入阿里云域名控制台,找到域名 ->“解析设置”->“添加记录” 2.添加一条记录类型为A,主机记录为www,…...
牛客周赛71:A:JAVA
链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 题目描述 \hspace{15pt}对于给定的两个正整数 nnn 和 kkk ,是否能构造出 kkk 对不同的正整数 (x,y)(x,y)(x,y) ,使得 xynxynxyn 。 \hspace{15pt}我们认为两对正整数 (…...
查询产品所涉及的表有(product、product_admin_mapping)
文章目录 1、ProductController2、AdminCommonService3、ProductApiService4、ProductCommonService5、ProductSqlService1. 完整SQL分析可选部分(条件筛选): 2. 涉及的表3. 总结4. 功能概述 查询指定管理员下所有产品所涉及的表?…...
算法基础学习Day5(双指针、动态窗口)
文章目录 1.题目2.题目解答1.四数之和题目及题目解析算法学习代码提交 2.长度最小的子数组题目及题目解析滑动窗口的算法学习方法一:单向双指针(暴力解法)方法二:同向双指针(滑动窗口) 代码提交 1.题目 18. 四数之和 - 力扣(LeetCode&#x…...
docker 部署 mysql 9.0.1
docker 如何部署 mysql 9 ,请看下面步骤: 1. 先看 mysql 官网 先点进去 8 版本的 Reference Manual 。 选择 9.0 版本的。 点到这里来看, 这里有一些基础的安装步骤,可以看一下。 - Basic Steps for MySQL Server Deployment wit…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...
EasyRTC音视频实时通话功能在WebRTC与智能硬件整合中的应用与优势
一、WebRTC与智能硬件整合趋势 随着物联网和实时通信需求的爆发式增长,WebRTC作为开源实时通信技术,为浏览器与移动应用提供免插件的音视频通信能力,在智能硬件领域的融合应用已成必然趋势。智能硬件不再局限于单一功能,对实时…...
今日行情明日机会——20250609
上证指数放量上涨,接近3400点,个股涨多跌少。 深证放量上涨,但有个小上影线,相对上证走势更弱。 2025年6月9日涨停股主要行业方向分析(基于最新图片数据) 1. 医药(11家涨停) 代表标…...
使用python进行图像处理—图像变换(6)
图像变换是指改变图像的几何形状或空间位置的操作。常见的几何变换包括平移、旋转、缩放、剪切(shear)以及更复杂的仿射变换和透视变换。这些变换在图像配准、图像校正、创建特效等场景中非常有用。 6.1仿射变换(Affine Transformation) 仿射变换是一种…...
