基础crud项目(前端部分+总结)

本人根据自己对前端微不足道的理解和 AI 老师的指导下,艰难地完成了基础crud代码的全栈开发,算是自己的第一个 Java 项目,对此做个简单总结。

后端部分


在前后端分离开发中,前端负责页面交互与数据展示,后端提供接口支持。本文基于 Vue3+Element Plus 构建用户管理前端页面,实现新增、编辑、删除、搜索核心功能,配合后端 RESTful 接口完成联调,并解决开发中常见的报错问题,适合新手快速上手。

技术栈

JavaSE,MySQL,Spring,SpringMVC,MyBatis,SpringBoot,Vue3,Element Plus。

原理

后端原理

依赖注入:通过 @Autowired 实现 Service 与 Dao、Controller 与 Service 层的解耦;

MyBatis 数据映射:开启 map-underscore-to-camel-case 自动转换数据库下划线字段(如 user_id)与 Java 驼峰属性(如 userId),通过注解式 SQL 实现 CRUD;

RESTful 接口规范:用 @GetMapping/{id}/@PostMapping/@PutMapping/{userId}/@DeleteMapping/{id} 对应查、增、改、删操作,路径传主键 + 请求体传数据,确保语义清晰。

整合 Lombok 简化 Pojo 类代码。

前端原理

Vue3 响应式:通过ref/reactive定义响应式数据(如userList/formData),数据变化时自动更新页面(如表格、表单)。

Element Plus 组件化:复用表格(el-table)、表单(el-form)、对话框(el-dialog)等组件,快速搭建交互界面,减少原生 DOM 操作。

Fetch 异步请求:通过原生Fetch API调用后端 RESTful 接口,实现 "数据请求 - 响应 - 页面更新" 闭环,配合async/await简化异步代码。

功能

后端功能

提供用户数据的完整 CRUD 接口:

查:按 ID 查单个用户(/users/{id})、查所有用户(/users);

增:新增用户(/users,请求体传待增字段);

改:按 ID 更新用户信息(/users,请求体传待更字段);

删:按 ID 删除用户(/users/{id})。

前端功能(交互 + 数据展示)

数据展示:用el-table展示用户列表,加载时显示动画,搜索空结果时提示 "未找到用户"。

新增用户:点击 "新增" 打开对话框,表单验证(ID 必为数字、用户名 / 密码必填),提交后刷新列表。

编辑用户:点击 "编辑" 填充当前用户数据,ID 禁用不可改,提交后更新列表。

搜索用户:输入 ID 回车 / 点击搜索,查询单个用户;清空输入框显示所有用户。

删除用户:点击 "删除" 弹出确认框,确认后删除并刷新列表。

前端代码

javascript 复制代码
<template>
    <div class="user-page">
        <h1>用户管理</h1>

        <!-- 操作区 -->
        <div class="operation-bar">
            <el-input v-model="searchId"
                      placeholder="输入ID搜索"
                      style="width: 200px"
                      clearable
                      @keyup.enter="handleSearch" />
            <el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
            <el-button type="success" icon="Plus" @click="openAddDialog">新增用户</el-button>
        </div>

        <!-- 用户表格 -->
        <el-table :data="userList"
                  border
                  style="width: 100%"
                  :loading="loading">
            <el-table-column prop="userId" label="ID" width="80" align="center" />
            <el-table-column prop="userName" label="用户名" width="150" align="center" />
            <el-table-column prop="password" label="密码" align="center" />
            <el-table-column label="操作" width="240" align="center">
                <template #default="scope">
                    <el-button type="primary"
                               size="small"
                               icon="Edit"
                               @click="openEditDialog(scope.row)"
                               style="margin-right: 5px">
                        编辑
                    </el-button>
                    <el-button type="danger"
                               size="small"
                               icon="Delete"
                               @click="handleDelete(scope.row.userId)">
                        删除
                    </el-button>
                </template>
            </el-table-column>
        </el-table>

        <!-- 新增用户对话框 -->
        <el-dialog title="新增用户"
                   v-model="addDialogVisible"
                   width="300px">
            <el-form :model="addForm" :rules="addRules" ref="addFormRef" label-width="80px">
                <el-form-item label="用户ID" prop="userId">
                    <el-input v-model.number="addForm.userId" placeholder="请输入ID" />
                </el-form-item>
                <el-form-item label="用户名" prop="userName">
                    <el-input v-model="addForm.userName" />
                </el-form-item>
                <el-form-item label="密码" prop="password">
                    <el-input v-model="addForm.password" type="password" />
                </el-form-item>
            </el-form>
            <template #footer>
                <el-button @click="addDialogVisible = false">取消</el-button>
                <el-button type="primary" @click="handleAddSubmit">确认新增</el-button>
            </template>
        </el-dialog>

        <!-- 编辑用户对话框 -->
        <el-dialog title="编辑用户"
                   v-model="editDialogVisible"
                   width="300px">
            <el-form :model="editForm" :rules="editRules" ref="editFormRef" label-width="80px">
                <!-- 编辑时ID不可修改,仅展示 -->
                <el-form-item label="用户ID">
                    <el-input v-model="editForm.userId" disabled />
                </el-form-item>
                <el-form-item label="用户名" prop="userName">
                    <el-input v-model="editForm.userName" />
                </el-form-item>
                <el-form-item label="密码" prop="password">
                    <el-input v-model="editForm.password" type="password" />
                </el-form-item>
            </el-form>
            <template #footer>
                <el-button @click="editDialogVisible = false">取消</el-button>
                <el-button type="primary" @click="handleEditSubmit">确认编辑</el-button>
            </template>
        </el-dialog>

        <!-- 删除确认对话框 -->
        <el-dialog title="确认删除"
                   v-model="deleteDialogVisible"
                   width="300px">
            <p>确定要删除ID为 {{ deleteId }} 的用户吗?</p>
            <template #footer>
                <el-button @click="deleteDialogVisible = false">取消</el-button>
                <el-button type="danger" @click="confirmDelete">确认删除</el-button>
            </template>
        </el-dialog>
    </div>
</template>

<script setup>
    import { ref, reactive, onMounted } from 'vue'
    import { ElMessage } from 'element-plus'
    import { Search, Plus, Edit, Delete } from '@element-plus/icons-vue'

    // 基础数据
    const userList = ref([])
    const loading = ref(false)
    const searchId = ref('')

    // 新增相关
    const addDialogVisible = ref(false)          // 新增对话框显示状态
    const addFormRef = ref(null)                 // 新增表单引用
    const addForm = reactive({                   // 新增表单数据
        userId: 0,
        userName: '',
        password: ''
    })
    const addRules = reactive({                  // 新增表单验证规则
        userId: [
            { required: true, message: '请输入用户ID', trigger: 'blur' },
            { type: 'number', message: 'ID必须是数字', trigger: 'blur' }
        ],
        userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
        password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
    })

    // 编辑相关
    const editDialogVisible = ref(false)         // 编辑对话框显示状态
    const editFormRef = ref(null)                // 编辑表单引用
    const editForm = reactive({                  // 编辑表单数据
        userId: '',
        userName: '',
        password: ''
    })
    const editRules = reactive({                 // 编辑表单验证规则(无需验证ID)
        userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
        password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
    })

    // 删除相关
    const deleteDialogVisible = ref(false)
    const deleteId = ref(0)

    // 后端接口地址
    const API_URL = 'http://localhost:8081/users'

    // 1. 获取所有用户
    const getAllUsers = async () => {
        loading.value = true
        try {
            const res = await fetch(API_URL)
            if (!res.ok) throw new Error('获取失败')
            userList.value = await res.json()
        } catch (err) {
            console.error('获取用户失败:', err)
            ElMessage.error('获取用户失败')
        } finally {
            loading.value = false
        }
    }

    // 2. 搜索用户
    const handleSearch = async () => {
        if (!searchId.value) {
            getAllUsers()
            return
        }

        loading.value = true
        try {
            const res = await fetch(`${API_URL}/${searchId.value}`)
            if (res.status === 404) {
                userList.value = []
                ElMessage.warning('用户不存在')
                return
            }
            if (!res.ok) throw new Error('搜索失败')
            userList.value = [await res.json()]
        } catch (err) {
            console.error('搜索失败:', err)
            ElMessage.error('搜索失败')
        } finally {
            loading.value = false
        }
    }

    // 3. 打开新增对话框
    const openAddDialog = () => {
        // 重置新增表单
        addForm.userId = ''
        addForm.userName = ''
        addForm.password = ''
        addDialogVisible.value = true
    }

    // 4. 提交新增表单
    const handleAddSubmit = async () => {
        if (!addFormRef.value) return
        try {
            // 验证新增表单
            await addFormRef.value.validate()

            // 发送新增请求
            const res = await fetch(API_URL, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(addForm)  // 提交新增表单数据
            })

            if (!res.ok) throw new Error('新增失败')
            ElMessage.success('新增成功')
            addDialogVisible.value = false  // 关闭新增对话框
            getAllUsers()                   // 刷新列表
        } catch (err) {
            console.error('新增失败:', err)
            ElMessage.error(err.message || '新增失败')
        }
    }

    // 5. 打开编辑对话框
    const openEditDialog = (user) => {
        // 填充编辑表单
        editForm.userId = user.userId
        editForm.userName = user.userName
        editForm.password = user.password
        editDialogVisible.value = true
    }

    // 6. 提交编辑表单
    const handleEditSubmit = async () => {
        if (!editFormRef.value) return
        try {
            // 验证编辑表单
            await editFormRef.value.validate()

            // 发送编辑请求(URL包含原ID)
            const res = await fetch(API_URL, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(editForm)  // 提交编辑表单数据
            })

            if (!res.ok) throw new Error('编辑失败')
            ElMessage.success('编辑成功')
            editDialogVisible.value = false  // 关闭编辑对话框
            getAllUsers()                    // 刷新列表
        } catch (err) {
            console.error('编辑失败:', err)
            ElMessage.error(err.message || '编辑失败')
        }
    }

    // 7. 删除用户
    const handleDelete = (id) => {
        deleteId.value = id
        deleteDialogVisible.value = true
    }

    // 确认删除
    const confirmDelete = async () => {
        try {
            const res = await fetch(`${API_URL}/${deleteId.value}`, {
                method: 'DELETE'
            })
            if (!res.ok) throw new Error('删除失败')

            ElMessage.success('删除成功')
            deleteDialogVisible.value = false
            getAllUsers()
        } catch (err) {
            console.error('删除失败:', err)
            ElMessage.error('删除失败')
        }
    }

    // 初始化
    onMounted(() => {
        getAllUsers()
    })
</script>

<style scoped>
    .user-page {
        padding: 20px;
        max-width: 1000px;
        margin: 0 auto;
    }

    .operation-bar {
        margin-bottom: 15px;
        display: flex;
        gap: 10px;
        align-items: center;
    }

    h1 {
        color: #333;
        font-size: 20px;
        margin-bottom: 20px;
    }
</style>

后端代码已展示在文前博客链接中。

问题和挑战

1.输入数字 ID 仍提示 "ID 必须是数字"

普通 v-model 会将输入值转为字符串(如输入 101,实际是 "101"),而验证规则 type: 'number' 校验的是数据类型,不是格式。

解决方案:用 v-model.number 自动将输入字符串转为数字,如果是字符串则转为NaN,符合要求。

2.编辑功能报 "Failed to fetch"

编辑请求 URL 错误(如 http://localhost:8081/users/undefined),可能是 editForm.userId 未正确赋值;也可能是后端 PUT 接口路径与前端不一致。

解决方案:

打印 URL 确认正确性:在 handleEditSubmit 中添加 console.log;修改前端接口路径。

3.前端页面中文乱码

这个确实花了很长时间,反复部署各种配置,跟着 AI 和各路大佬的博客调了好久,最终发现还是经典的 encoding 问题。
附上博客

4.后端 mybatis-plus 与 springboot 版本冲突

试了好多版本都不能正常运行,最后选择放弃阿里的 mybatis-plus,使用 mybatis。毕竟差距确实不大,进行复杂 Dao 层开发时肯定还是要自己手搓,就当练习基础语法了。

5.Linux相关

因为 MySQL 老师讲企业开发中都是在 Linux 环境下进行部署等操作的,于是跟着 AI 一步一步在 Linux 虚拟机中安装 MySQL 和 Redis(本项目没用到Redis),期间有不少问题是自己根本不熟悉 Linux 造成的,后来特地学习了 Linux 相关知识,计划日后继续学习 JVM。

6."白学"问题

这一点见仁见智,但是部分课程确实白学。我最开始学2021年黑马的 Javaweb,确实好多东西已经用不上了,连老师也是提前准备或对着 ppt 敲的。不过很多人认为学习了 springboot 就没必要学习 SSM 全家桶,这一点我持否认态度。毕竟我一开始也是这样想的时候,直接去看 springboot 根本看不懂,连URL都不知道是什么。学习确实需要脚踏实地,不能妄想一步登天。

总结

整个暑假都在 Java 课程中度过,期间学习了不少前所未闻的知识。虽然第一次做的 Java 项目还是只有基础的crud,但是"守得云开见月明",相信在自己的努力下,一切终将美好。

相关推荐
xdpcxq102914 小时前
Java项目打包成EXE全攻略
java·python·pycharm
奔跑吧邓邓子14 小时前
【Java实战⑲】深入Java网络编程:TCP与UDP实战攻略
java·tcp/ip·实战·网络编程
lssjzmn14 小时前
会话管理巅峰对决:Spring Web中Cookie-Session、JWT、Spring Session + Redis深度秘籍
java·spring·架构
Java中文社群14 小时前
面试官:如何提升项目并发性能?
java·后端·面试
繁花与尘埃14 小时前
Java流程控制04——if选择结构(本文为个人学习笔记,内容整理自哔哩哔哩UP主【遇见狂神说】的公开课程。 > 所有知识点归属原作者,仅作非商业用途分享)
java·笔记·学习
Seven9714 小时前
hive 中 group by 和 distinct 孰优孰劣?
java
heyCHEEMS14 小时前
路径总和 Ⅱ Java
java·开发语言·深度优先
YF云飞14 小时前
Dubbo分布式服务框架全解析
java·dubbo
孤狼程序员15 小时前
1.注解的力量:Spring Boot如何用注解重构IoC容器
java·spring boot·spring