当前位置: 首页 > news >正文

【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 配置&#xff08;创建实例&#xff0c;配置请求&#xff0c;响应拦截器&#xff09;2.5.2 …...

手写Mybatis框架源码(简写)

pom文件&#xff1a; springboot版本&#xff1a;2.6.5 jdk&#xff1a;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语言代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 输出一个整数序列中最大的数和最小的数的差。 输入 第一行为M&#xff0c;表示整数个数&#xff0c;整数个数不会大于1…...

如何在 IntelliJ IDEA 中为 Spring Boot 应用实现热部署

文章目录 1. 引言2. 准备工作3. 添加必要的依赖4. 配置 IntelliJ IDEA4.1 启用自动编译4.2 开启热部署策略 5. 测试热部署6. 高级技巧7. 注意事项8. 总结 随着现代开发工具的进步&#xff0c;开发者们越来越重视提高生产力的特性。对于 Java 开发者来说&#xff0c;能够在不重启…...

探索 Java 中的 Bug 世界

在 Java 编程的旅程中&#xff0c;我们不可避免地会遇到各种 Bug。这些 Bug 可能会导致程序出现意外的行为、崩溃或者性能问题。了解 Java Bug 的类型、产生原因以及解决方法&#xff0c;对于提高我们的编程技能和开发出稳定可靠的应用程序至关重要。 一、Java Bug 的定义与分类…...

SQL面试题——百度SQL面试题 连续签到领金币

百度SQL面试题 连续签到领金币 今天的这个题目来自百度,而且这个题目很常见,是一个大家日常经常遇到的一个场景,几乎无处不在,就是签到送积分,只不过这里是签到领金币 有用户签到记录表,sign,记录用户当天是否完成签到,请计算出每个用户的每个月获得的金币数量; 签到…...

easyExcel单一下拉框和级联下拉框

文章目录&#xff1a; 单一下拉框级联下拉框 具体实现&#xff1a; 单一下拉框 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合并二叉树

目录&#xff1a; 目的 思路 复杂度 记忆秘诀 python代码 目的&#xff1a; 已知两颗二叉树&#xff0c;将它们合并成一颗二叉树。合并规则是&#xff1a;都存在的结点&#xff0c;就将结点值加起来&#xff0c;否则空的位置就由另一个树的结点来代替。 思路 我们有两棵二…...

R的中文文本处理包--tmcn

文章目录 介绍tmcn 和 jieba 的关系函数&#xff1a;catUTF8toUTF8实例 介绍 tmcn 包是 R 语言中的一个用于处理和分析中文文本的包&#xff0c;特别适用于中文文本的分词、词频统计和文本挖掘等任务。以下是 tmcn 包的基本用法&#xff0c;包括安装、常用函数和示例。 一个用…...

差异基因富集分析(R语言——GOKEGGGSEA)

接着上次的内容&#xff0c;上篇内容给大家分享了基因表达量怎么做分组差异分析&#xff0c;从而获得差异基因集&#xff0c;想了解的可以去看一下&#xff0c;这篇主要给大家分享一下得到显著差异基因集后怎么做一下通路富集。 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.父组件 一、场景 这两天做项目&#xff0c;此产品提出需求 要求详情页的顶部区域要在鼠标划入后展开里面的内容&#xff0c;鼠标划出要收起部分内容&#xff0c;详情底部的内容高度要自适应&#xff0c;我这里运用了鼠标事件t…...

大模型语料库的构建过程 包括知识图谱构建 垂直知识图谱构建 输入到sql构建 输入到cypher构建 通过智能体管理数据生产组件

以下是大模型语料库的构建过程&#xff1a; 一、文档切分语料库构建 数据来源确定&#xff1a; 首先&#xff0c;需要确定语料库的数据来源。这些来源可以是多种多样的&#xff0c;包括但不限于&#xff1a; 网络资源&#xff1a;利用网络爬虫技术从各种网站&#xff08;如新闻…...

阿里云ECS服务器域名解析

阿里云ECS服务器域名解析&#xff0c;以前添加两条A记录类型&#xff0c;主机记录分别为www和&#xff0c;这2条记录都解析到服务器IP地址。 1.进入阿里云域名控制台&#xff0c;找到域名 ->“解析设置”->“添加记录” 2.添加一条记录类型为A,主机记录为www&#xff0c…...

牛客周赛71:A:JAVA

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目描述 \hspace{15pt}对于给定的两个正整数 nnn 和 kkk &#xff0c;是否能构造出 kkk 对不同的正整数 (x,y)(x,y)(x,y) &#xff0c;使得 xynxynxyn 。 \hspace{15pt}我们认为两对正整数 (…...

查询产品所涉及的表有(product、product_admin_mapping)

文章目录 1、ProductController2、AdminCommonService3、ProductApiService4、ProductCommonService5、ProductSqlService1. 完整SQL分析可选部分&#xff08;条件筛选&#xff09;&#xff1a; 2. 涉及的表3. 总结4. 功能概述 查询指定管理员下所有产品所涉及的表&#xff1f;…...

算法基础学习Day5(双指针、动态窗口)

文章目录 1.题目2.题目解答1.四数之和题目及题目解析算法学习代码提交 2.长度最小的子数组题目及题目解析滑动窗口的算法学习方法一&#xff1a;单向双指针(暴力解法)方法二&#xff1a;同向双指针(滑动窗口) 代码提交 1.题目 18. 四数之和 - 力扣&#xff08;LeetCode&#x…...

docker 部署 mysql 9.0.1

docker 如何部署 mysql 9 &#xff0c;请看下面步骤&#xff1a; 1. 先看 mysql 官网 先点进去 8 版本的 Reference Manual 。 选择 9.0 版本的。 点到这里来看&#xff0c; 这里有一些基础的安装步骤&#xff0c;可以看一下。 - Basic Steps for MySQL Server Deployment wit…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

ssc377d修改flash分区大小

1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...