跳到主要内容SpringBoot3+Vue3 大事件项目前端代码优化与功能增强 | 极客日志JavaScript大前端
SpringBoot3+Vue3 大事件项目前端代码优化与功能增强
SpringBoot3+Vue3 大事件项目前端代码优化涉及用户密码修改、头像上传、文章管理及富文本编辑器集成。优化版实现了完整的密码修改逻辑,使用 FormData 标准方式上传头像,文章管理增加批量删除和删除确认提示,并引入 VueQuill 富文本编辑器替代普通文本框。这些改进提升了功能完整性、用户体验及代码规范性。
leon3 浏览 大事件前端代码优化和功能添加详细对比分析
详细代码差异对比
1. 用户密码修改功能
优化版项目中的 UserResetPassword.vue 文件实现了完整的密码修改功能:
<script setup>
import { ref } from 'vue'
import { userUpdatePasswordService } from '@/api/user.js'
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
const router = useRouter()
// 表单数据
const form = ref({
old_pwd: '',
new_pwd: '',
re_pwd: ''
})
// 表单验证规则
const rules = {
old_pwd: [
{ required: true, message: '请输入原密码', trigger: 'blur' },
{ min: 6, max: 16, message: '密码长度应在 6-16 位之间', trigger: 'blur' }
],
new_pwd: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 16, message: '密码长度应在 6-16 位之间', trigger: 'blur' }
],
re_pwd: [
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== form.value.new_pwd) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
]
}
const updatePassword = async (formRef) => {
// 验证表单
try {
// 检查两次输入的新密码是否一致
if (form.value.new_pwd !== form.value.re_pwd) {
ElMessage.error('两次输入的新密码不一致')
return
}
// 调用 API 更新密码
const result = await userUpdatePasswordService(
form.value.old_pwd,
form.value.new_pwd,
form.value.re_pwd
)
if (result.code === 0) {
ElMessage.success(result.msg || '密码修改成功')
// 返回个人中心
router.push('/user/info')
}
} catch (error) {
console.error('密码修改失败:', error)
}
}
</script>
<template>
<el-card class="page-container">
<template #header>
<div class="header"><span>重置密码</span></div>
</template>
<el-row>
<el-col :span="12">
<el-form
:model="form"
:rules="rules"
ref="formRef"
label-width="100px"
status-icon
>
<el-form-item label="原密码" prop="old_pwd">
<el-input v-model="form.old_pwd" type="password" show-password />
</el-form-item>
<el-form-item label="新密码" prop="new_pwd">
<el-input v-model="form.new_pwd" type="password" show-password />
</el-form-item>
<el-form-item label="确认新密码" prop="re_pwd">
<el-input v-model="form.re_pwd" type="password" show-password />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="updatePassword(formRef)">提交</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-card>
</template>
<style scoped>
.page-container {
min-height: 800px;
}
</style>
而原版项目中的 UserResetPassword.vue 文件仅包含模板内容,无实际逻辑。
对应的 API 接口差异:
新增了密码修改功能:
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
优化版项目中的 api/user.js
export const userUpdatePasswordService = (oldPwd, newPwd, rePwd) => {
const params = new URLSearchParams()
params.append('old_pwd', oldPwd)
params.append('new_pwd', newPwd)
params.append('re_pwd', rePwd)
return request.post('/user/updatePwd', params)
}
而原版项目中的 api/user.js没有此功能。
2. 用户头像上传功能的差异
优化版项目中的 UserAvatar.vue使用了正确的文件上传方式:
import { userInfoService } from '@/api/user.js'
const uploadSuccess = (result) => {
imgUrl.value = result.data
ElMessage.success('头像上传成功')
userInfoStore.info.userPic = imgUrl.value
userInfoService().then((res) => {
userInfoStore.setInfo(res.data)
})
}
import { ElMessage } from 'element-plus'
const updateAvatar = async () => {
uploadRef.value.$el.querySelector('input').click()
}
优化版项目中的 api/user.js定义了正确的文件上传方式:
export const userAvatarUpdateService = (file) => {
const formData = new FormData()
formData.append('file', file)
return request.post('/user/updateAvatar', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
而原版项目中的 UserAvatar.vue使用了不同的实现方式:
const uploadSuccess = (result) => {
imgUrl.value = result.data
}
import { userAvatarUpdateService } from '@/api/user.js'
import { ElMessage } from 'element-plus'
const updateAvatar = async () => {
let result = await userAvatarUpdateService(imgUrl.value)
ElMessage.success(result.msg ? result.msg : '修改成功')
userInfoStore.info.userPic = imgUrl.value
}
原版项目中的 api/user.js使用了 URL 参数方式:
export const userAvatarUpdateService = (avatarUrl) => {
const params = new URLSearchParams()
params.append('avatarUrl', avatarUrl)
return request.patch('/user/updateAvatar', params)
}
3. 文章管理功能的差异
原版项目中的 ArticleManage.vue(老代码):
<script setup>
import { Edit, Delete } from '@element-plus/icons-vue'
import { ref } from 'vue'
// 文章分类数据模型
const categorys = ref([
{
id: 3,
categoryName: '美食',
categoryAlias: 'my',
createTime: '2023-09-02 12:06:59',
updateTime: '2023-09-02 12:06:59'
},
{
id: 4,
categoryName: '娱乐',
categoryAlias: 'yl',
createTime: '2023-09-02 12:08:16',
updateTime: '2023-09-02 12:08:16'
},
{
id: 5,
categoryName: '军事',
categoryAlias: 'js',
createTime: '2023-09-02 12:08:33',
updateTime: '2023-09-02 12:08:33'
}
])
// 用户搜索时选中的分类 id
const categoryId = ref('')
// 用户搜索时选中的发布状态
const state = ref('')
// 文章列表数据模型
const articles = ref([
{
id: 5,
title: '陕西旅游攻略',
content: '兵马俑,华清池,法门寺,华山...爱去哪去哪...',
coverImg: 'https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png',
state: '草稿',
categoryId: 2,
createTime: '2023-09-03 11:55:30',
updateTime: '2023-09-03 11:55:30'
}
])
// 分页条数据模型
const pageNum = ref(1) // 当前页
const total = ref(0) // 总条数
const pageSize = ref(3) // 每页条数
// 当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {
pageSize.value = size
articleList()
}
// 当页码发生变化后,调用此函数
const onCurrentChange = (num) => {
pageNum.value = num
articleList()
}
// 获取文章列表
import {
articleCategoryListService,
articleListService,
articleAddService,
articleUpdateService,
articleDeleteService
} from '@/api/article.js'
const articleList = async () => {
let params = {
pageNum: pageNum.value,
pageSize: pageSize.value,
categoryId: categoryId.value,
state: state.value
}
let result = await articleListService(params)
articles.value = result.data.items
total.value = result.data.total
}
articleList()
// 获取分类数据
const getCategoryList = async () => {
let result = await articleCategoryListService()
categorys.value = result.data
}
getCategoryList()
// 控制添加或修改弹窗的显示
const dialogVisible = ref(false)
// 定义变量,控制弹窗标题
const title = ref('')
// 添加或修改的文章数据模型
const articleModel = ref({
id: '',
title: '',
content: '',
categoryId: '',
state: ''
})
// 添加或修改文章的表单校验规则
const rules = {
title: [
{ required: true, message: '请输入文章标题', trigger: 'blur' },
{ min: 2, max: 30, message: '长度为 2-30 个字符', trigger: 'blur' }
],
categoryId: [
{ required: true, message: '请选择文章分类', trigger: 'change' }
],
state: [
{ required: true, message: '请选择发布状态', trigger: 'change' }
]
}
// 添加按钮的回调函数
const addArticle = () => {
dialogVisible.value = true
title.value = '添加文章'
// 清空数据模型
articleModel.value = {
id: '',
title: '',
content: '',
categoryId: '',
state: ''
}
}
// 编辑按钮的回调函数
const editArticle = (row) => {
dialogVisible.value = true
title.value = '编辑文章'
// 数据回显
articleModel.value.id = row.id
articleModel.value.title = row.title
articleModel.value.content = row.content
articleModel.value.categoryId = row.categoryId
articleModel.value.state = row.state
}
// 确认添加或修改
import { ElMessage } from 'element-plus'
const confirm = async () => {
// 判断是添加还是修改
if (articleModel.value.id) {
// 修改
let result = await articleUpdateService(articleModel.value)
ElMessage.success(result.msg ? result.msg : '修改成功')
} else {
// 添加
let result = await articleAddService(articleModel.value)
ElMessage.success(result.msg ? result.msg : '添加成功')
}
// 重新获取文章列表
articleList()
// 隐藏弹窗
dialogVisible.value = false
}
// 删除文章
const deleteArticle = (row) => {
// 调用接口
articleDeleteService(row.id).then((result) => {
ElMessage.success(result.msg ? result.msg : '删除成功')
// 重新获取文章列表
articleList()
})
}
</script>
<template>
<el-card class="page-container">
<template #header>
<div class="header"><span>文章管理</span></div>
</template>
<!-- 搜索区域 -->
<el-row :gutter="20" style="margin-bottom: 20px;">
<el-col :span="4">
<el-select v-model="categoryId" placeholder="全部分类" clearable>
<el-option
v-for="c in categorys"
:key="c.id"
:label="c.categoryName"
:value="c.id"
/>
</el-select>
</el-col>
<el-col :span="4">
<el-select v-model="state" placeholder="全部状态" clearable>
<el-option label="草稿" value="草稿" />
<el-option label="已发布" value="已发布" />
</el-select>
</el-col>
<el-col :span="4">
<el-button type="primary" @click="articleList">搜索</el-button>
</el-col>
</el-row>
<!-- 表格 -->
<el-table :data="articles" stripe style="width: 100%">
<el-table-column prop="title" label="文章标题" />
<el-table-column prop="categoryId" label="分类" />
<el-table-column prop="state" label="发布状态" />
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作" width="200">
<template #default="{ row }">
<el-button
type="primary"
:icon="Edit"
size="small"
@click="editArticle(row)"
>
编辑
</el-button>
<el-button
type="danger"
:icon="Delete"
size="small"
@click="deleteArticle(row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="pageNum"
v-model:page-size="pageSize"
:page-sizes="[3, 5, 10, 15]"
:small="false"
:disabled="false"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="onSizeChange"
@current-change="onCurrentChange"
style="margin-top: 20px; justify-content: flex-end;"
/>
<!-- 添加或修改文章的弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="title"
width="600"
destroy-on-close
>
<el-form
:model="articleModel"
:rules="rules"
label-width="100px"
style="width: 500px"
status-icon
>
<el-form-item label="文章标题" prop="title">
<el-input v-model="articleModel.title" />
</el-form-item>
<el-form-item label="文章分类" prop="categoryId">
<el-select v-model="articleModel.categoryId" style="width: 100%">
<el-option
v-for="c in categorys"
:key="c.id"
:label="c.categoryName"
:value="c.id"
/>
</el-select>
</el-form-item>
<el-form-item label="发布状态" prop="state">
<el-radio-group v-model="articleModel.state">
<el-radio label="草稿" />
<el-radio label="已发布" />
</el-radio-group>
</el-form-item>
<el-form-item label="文章内容" prop="content">
<el-input
v-model="articleModel.content"
type="textarea"
:rows="4"
placeholder="请输入文章内容"
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="confirm">确认</el-button>
</el-form-item>
</el-form>
</el-dialog>
</el-card>
</template>
优化版项目中的 ArticleManage.vue(新代码):
<script setup>
import { Edit, Delete } from '@element-plus/icons-vue'
import { ref } from 'vue'
// 定义变量存储选中的文章
const selectedArticles = ref([])
// 当表格选中项发生变化时的回调函数
const handleSelectionChange = (val) => {
selectedArticles.value = val
}
// 批量删除文章
const batchDeleteArticles = async () => {
if (selectedArticles.value.length === 0) {
ElMessage.warning('请至少选择一篇文章')
return
}
try {
// 获取选中文章的 ID
const ids = selectedArticles.value.map((article) => article.id)
// 调用批量删除 API
const result = await Promise.all(ids.map((id) => articleDeleteService(id)))
if (result.every((res) => res.code === 0)) {
ElMessage.success('批量删除成功')
// 重新获取文章列表
articleList()
} else {
ElMessage.error('部分文章删除失败')
}
} catch (error) {
console.error('批量删除失败:', error)
ElMessage.error('批量删除失败')
}
}
// 文章分类数据模型
const categorys = ref([
{
id: 3,
categoryName: '美食',
categoryAlias: 'my',
createTime: '2023-09-02 12:06:59',
updateTime: '2023-09-02 12:06:59'
},
{
id: 4,
categoryName: '娱乐',
categoryAlias: 'yl',
createTime: '2023-09-02 12:08:16',
updateTime: '2023-09-02 12:08:16'
},
{
id: 5,
categoryName: '军事',
categoryAlias: 'js',
createTime: '2023-09-02 12:08:33',
updateTime: '2023-09-02 12:08:33'
}
])
// 用户搜索时选中的分类 id
const categoryId = ref('')
// 用户搜索时选中的发布状态
const state = ref('')
// 文章列表数据模型
const articles = ref([])
// 分页条数据模型
const pageNum = ref(1) // 当前页
const total = ref(0) // 总条数
const pageSize = ref(3) // 每页条数
// 当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {
pageSize.value = size
articleList()
}
// 当页码发生变化后,调用此函数
const onCurrentChange = (num) => {
pageNum.value = num
articleList()
}
// 获取文章列表
import {
articleCategoryListService,
articleListService,
articleAddService,
articleUpdateService,
articleDeleteService
} from '@/api/article.js'
const articleList = async () => {
let params = {
pageNum: pageNum.value,
pageSize: pageSize.value,
categoryId: categoryId.value,
state: state.value
}
let result = await articleListService(params)
articles.value = result.data.items
total.value = result.data.total
}
articleList()
// 获取分类数据
const getCategoryList = async () => {
let result = await articleCategoryListService()
categorys.value = result.data
}
getCategoryList()
// 控制添加或修改弹窗的显示
const dialogVisible = ref(false)
// 定义变量,控制弹窗标题
const title = ref('')
// 添加或修改的文章数据模型
const articleModel = ref({
id: '',
title: '',
content: '',
categoryId: '',
state: ''
})
// 添加或修改文章的表单校验规则
const rules = {
title: [
{ required: true, message: '请输入文章标题', trigger: 'blur' },
{ min: 2, max: 30, message: '长度为 2-30 个字符', trigger: 'blur' }
],
categoryId: [
{ required: true, message: '请选择文章分类', trigger: 'change' }
],
state: [
{ required: true, message: '请选择发布状态', trigger: 'change' }
]
}
// 添加按钮的回调函数
const addArticle = () => {
dialogVisible.value = true
title.value = '添加文章'
// 清空数据模型
articleModel.value = {
id: '',
title: '',
content: '',
categoryId: '',
state: ''
}
}
// 编辑按钮的回调函数
const editArticle = (row) => {
dialogVisible.value = true
title.value = '编辑文章'
// 数据回显
articleModel.value.id = row.id
articleModel.value.title = row.title
articleModel.value.content = row.content
articleModel.value.categoryId = row.categoryId
articleModel.value.state = row.state
}
// 确认添加或修改
import { ElMessage } from 'element-plus'
const confirm = async () => {
// 判断是添加还是修改
if (articleModel.value.id) {
// 修改
let result = await articleUpdateService(articleModel.value)
ElMessage.success(result.msg ? result.msg : '修改成功')
} else {
// 添加
let result = await articleAddService(articleModel.value)
ElMessage.success(result.msg ? result.msg : '添加成功')
}
// 重新获取文章列表
articleList()
// 隐藏弹窗
dialogVisible.value = false
}
// 删除文章
import { ElMessageBox } from 'element-plus'
const deleteArticle = (row) => {
ElMessageBox.confirm('您确认要删除此文章吗?', '温馨提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
// 调用接口
let result = await articleDeleteService(row.id)
ElMessage.success(result.msg ? result.msg : '删除成功')
// 重新获取文章列表
articleList()
})
.catch(() => {
ElMessage.info('已取消删除')
})
}
</script>
<template>
<el-card class="page-container">
<template #header>
<div class="header"><span>文章管理</span></div>
</template>
<!-- 搜索区域 -->
<el-row :gutter="20" style="margin-bottom: 20px;">
<el-col :span="4">
<el-select v-model="categoryId" placeholder="全部分类" clearable>
<el-option
v-for="c in categorys"
:key="c.id"
:label="c.categoryName"
:value="c.id"
/>
</el-select>
</el-col>
<el-col :span="4">
<el-select v-model="state" placeholder="全部状态" clearable>
<el-option label="草稿" value="草稿" />
<el-option label="已发布" value="已发布" />
</el-select>
</el-col>
<el-col :span="4">
<el-button type="primary" @click="articleList">搜索</el-button>
<el-button
type="danger"
@click="batchDeleteArticles"
:disabled="selectedArticles.length === 0"
>
批量删除
</el-button>
</el-col>
</el-row>
<!-- 表格 -->
<el-table
:data="articles"
stripe
style="width: 100%"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="50" />
<el-table-column prop="title" label="文章标题" />
<el-table-column prop="categoryId" label="分类" />
<el-table-column prop="state" label="发布状态" />
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作" width="200">
<template #default="{ row }">
<el-button
type="primary"
:icon="Edit"
size="small"
@click="editArticle(row)"
>
编辑
</el-button>
<el-button
type="danger"
:icon="Delete"
size="small"
@click="deleteArticle(row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="pageNum"
v-model:page-size="pageSize"
:page-sizes="[3, 5, 10, 15]"
:small="false"
:disabled="false"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="onSizeChange"
@current-change="onCurrentChange"
style="margin-top: 20px; justify-content: flex-end;"
/>
<!-- 添加或修改文章的弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="title"
width="600"
destroy-on-close
>
<el-form
:model="articleModel"
:rules="rules"
label-width="100px"
style="width: 500px"
status-icon
>
<el-form-item label="文章标题" prop="title">
<el-input v-model="articleModel.title" />
</el-form-item>
<el-form-item label="文章分类" prop="categoryId">
<el-select v-model="articleModel.categoryId" style="width: 100%">
<el-option
v-for="c in categorys"
:key="c.id"
:label="c.categoryName"
:value="c.id"
/>
</el-select>
</el-form-item>
<el-form-item label="发布状态" prop="state">
<el-radio-group v-model="articleModel.state">
<el-radio label="草稿" />
<el-radio label="已发布" />
</el-radio-group>
</el-form-item>
<el-form-item label="文章内容" prop="content">
<el-input
v-model="articleModel.content"
type="textarea"
:rows="4"
placeholder="请输入文章内容"
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="confirm">确认</el-button>
</el-form-item>
</el-form>
</el-dialog>
</el-card>
</template>
4. 富文本编辑器集成
优化版项目中的 ArticleManage.vue新增了富文本编辑器:
<template>
<el-form-item label="文章内容" prop="content">
<div class="editor-container">
<VueQuill
ref="quillRef"
v-model:content="articleModel.content"
:options="quillOptions"
style="height: 300px;"
/>
</div>
</el-form-item>
</template>
<script setup>
import { VueQuill } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
// 富文本编辑器配置
const quillOptions = {
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ header: 1 }, { header: 2 }],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ script: 'sub' }, { script: 'super' }],
[{ indent: '-1' }, { indent: '+1' }],
[{ direction: 'rtl' }],
[{ size: ['small', false, 'large', 'huge'] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ font: [] }],
[{ align: [] }],
['clean']
]
},
placeholder: '请输入文章内容...'
}
</script>
而原版项目中的 ArticleManage.vue使用的是普通文本框:
<el-form-item label="文章内容" prop="content">
<el-input
v-model="articleModel.content"
type="textarea"
:rows="4"
placeholder="请输入文章内容"
maxlength="500"
show-word-limit
/>
</el-form-item>
5. 文章分类管理功能的增强
优化版项目中的 ArticleCategory.vue实现了删除确认提示:
import { ElMessageBox } from 'element-plus'
const deleteCategory = (row) => {
ElMessageBox.confirm('您确认要删除此分类吗?', '温馨提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
let result = await articleCategoryDeleteService(row.id)
ElMessage.success(result.msg ? result.msg : '删除成功')
articleCategoryList()
})
.catch(() => {
ElMessage.info('已取消删除')
})
}
而原版项目中的 ArticleCategory.vue直接删除:
const deleteCategory = (row) => {
let result = await articleCategoryDeleteService(row.id)
ElMessage.success(result.msg ? result.msg : '删除成功')
articleCategoryList()
}
新增功能总结
- 密码修改功能:完整实现了用户密码修改功能,包含原密码验证、新密码确认等功能
- 批量删除文章:在文章管理中实现了批量选择和删除功能
- 富文本编辑器:在文章编辑中集成了 VueQuill 富文本编辑器
- 删除确认提示:在删除操作前添加了确认对话框
- 文件上传优化:使用 FormData 进行文件上传,符合标准实践
- 更完善的表单验证:增加了更全面的表单验证规则
结论
经过优化以后,优化版项目在功能完整性、用户体验和代码质量方面都有显著提升。特别是新增的批量删除功能、富文本编辑器集成和更完善的表单验证,使系统更加实用和完善。