【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…...

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...

HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...