7.轮播图管理功能实现
分类: Java springboot uni-app 专栏: 带小白springboot3+vue3+uniapp 标签: springboot3 vue3 uniapp
2026-01-17 15:58:32 113浏览
讲之前把那个登录背景图处理下,5M 实在是太大了,截图一下换成小点的图
效果图


mybatisPlus 分页实现
https://jf3q.com/article/detail/11025
文件上传功能
https://jf3q.com/article/detail/10849
文件上传工具类
package com.jf3q.schoolbbs.utils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
/**
* 这是一个文件上传的工具类
*/
public class UploadFile {
// 允许上传的文件类型
private static final List<String> ALLOWED_FILE_TYPES = Arrays.asList(
"jpg", "jpeg", "png", "gif", "bmp", "webp", // 图片类型
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf", // 文档类型
"txt", "md", // 文本类型
"zip", "rar", "7z", "tar", "gz" // 压缩文件类型
);
// 最大文件大小 (10MB)
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024;
/**
* 保存上传的文件
* @param file 上传的文件
* @param uploadDir 上传目录
* @return 保存后的文件名
* @throws IOException IO异常
*/
public static String saveFile(MultipartFile file, String uploadDir) throws IOException {
// 验证文件是否为空
if (file.isEmpty()) {
throw new IllegalArgumentException("上传的文件不能为空");
}
// 验证文件大小
if (file.getSize() > MAX_FILE_SIZE) {
throw new IllegalArgumentException("文件大小不能超过10MB");
}
// 获取文件扩展名
String originalFilename = file.getOriginalFilename();
if (originalFilename == null) {
throw new IllegalArgumentException("文件名不能为空");
}
String extension = getFileExtension(originalFilename);
// 验证文件类型
if (!ALLOWED_FILE_TYPES.contains(extension.toLowerCase())) {
throw new IllegalArgumentException("不支持的文件类型: " + extension);
}
// 创建上传目录(如果不存在)
Path uploadPath = Paths.get(uploadDir);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 生成唯一文件名
String newFilename = UUID.randomUUID().toString() + "." + extension;
Path filePath = uploadPath.resolve(newFilename);
// 保存文件
Files.copy(file.getInputStream(), filePath);
return newFilename;
}
/**
* 删除文件
* @param filename 文件名
* @param uploadDir 上传目录
* @return 是否删除成功
*/
public static boolean deleteFile(String filename, String uploadDir) {
try {
Path filePath = Paths.get(uploadDir, filename);
return Files.deleteIfExists(filePath);
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 获取文件扩展名
* @param filename 文件名
* @return 扩展名
*/
private static String getFileExtension(String filename) {
int lastIndexOf = filename.lastIndexOf(".");
if (lastIndexOf == -1) {
return ""; // 无扩展名
}
return filename.substring(lastIndexOf + 1);
}
/**
* 验证文件类型是否被允许
* @param filename 文件名
* @return 是否允许
*/
public static boolean isAllowedFileType(String filename) {
if (filename == null) {
return false;
}
String extension = getFileExtension(filename);
return ALLOWED_FILE_TYPES.contains(extension.toLowerCase());
}
/**
* 验证文件大小是否在限制范围内
* @param size 文件大小(字节)
* @return 是否在范围内
*/
public static boolean isAllowedFileSize(long size) {
return size > 0 && size <= MAX_FILE_SIZE;
}
}
前端开发
https://element-plus.org/zh-CN/
最终的效果如下:

最重要的就是注意图片上传和图片显示的时候的反向代理

const baseUrl=process.env.VUE_APP_BASE_API完整的前端代码如下,供大家参考
<script setup>
import {onMounted, ref} from "vue";
import {getBannerPage, addBanner, delBanner} from "@/api";
import {ElMessage} from 'element-plus'
import {Plus, Edit, Delete} from '@element-plus/icons-vue'
const baseUrl=process.env.VUE_APP_BASE_API
const pageNum=ref(1)
const tableData = ref([])
const dialogVisible = ref(false)
const bannerForm = ref({
img: ''
})
const pages=ref(0)
const total=ref(0)
const changePageNum=async (val)=>{
pageNum.value=val
await getPage()
}
const getPage = async () => {
const res = await getBannerPage(pageNum.value)
tableData.value = res.data.records
total.value=res.data.total
}
const handleDelete = async(id) => {
const res = await delBanner(id)
console.log(res)
await getPage()
}
const handleAdd = () => {
bannerForm.value = {
img: ''
}
dialogVisible.value = true
}
const handleEdit = (row) => {
bannerForm.value = {
img: row.img
}
dialogVisible.value = true
}
const beforeUpload = (file) => {
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG) {
ElMessage.error('上传图片只能是 JPG/PNG 格式!')
}
if (!isLt2M) {
ElMessage.error('上传图片大小不能超过 2MB!')
}
return isJPG && isLt2M
}
const handleUploadSuccess = (response) => {
bannerForm.value.img = response.data
ElMessage.success('上传成功')
}
const uploadAction=baseUrl+"/admin/banner/upload"
const saveBanner = async () => {
if (!bannerForm.value.img) {
ElMessage.warning('请上传图片')
return
}
try {
await addBanner(bannerForm.value)
ElMessage.success('添加成功')
dialogVisible.value = false
await getPage()
} catch (error) {
ElMessage.error('添加失败')
}
}
onMounted(async ()=>{
const res = await getBannerPage(pageNum.value)
console.log(res);
tableData.value=res.data.records
total.value=res.data.total
pages.value=res.data.pages
})
</script>
<template>
<div class="banner-container">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-title">
<h2>轮播图管理</h2>
<p class="subtitle">管理系统首页轮播图展示</p>
</div>
<el-button type="primary" @click="handleAdd" class="add-btn">
<el-icon><Plus /></el-icon>
新增轮播图
</el-button>
</div>
<!-- 表格区域 -->
<div class="table-wrapper">
<el-table :data="tableData" class="banner-table" stripe>
<el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column label="图片" width="200" align="center">
<template #default="scope">
<el-image
:src="baseUrl+'/banner/'+scope.row.img"
class="banner-image"
fit="cover"
:preview-src-list="[baseUrl+'/banner/'+scope.row.img]"
preview-teleported
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="添加时间" align="center" />
<el-table-column label="操作" width="200" align="center">
<template #default="scope">
<el-button type="primary" link @click="handleEdit(scope.row)">
<el-icon><Edit /></el-icon>
编辑
</el-button>
<el-button type="danger" link @click="handleDelete(scope.row.id)">
<el-icon><Delete /></el-icon>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div class="pagination-wrapper">
<el-pagination
background
layout="prev, pager, next"
:page-count="pages"
:total="total"
@current-change="changePageNum"
class="custom-pagination"
/>
</div>
<!-- 新增/编辑对话框 -->
<el-dialog
v-model="dialogVisible"
:title="bannerForm.id ? '编辑轮播图' : '新增轮播图'"
width="600px"
class="banner-dialog"
>
<el-form :model="bannerForm" label-width="100px" class="banner-form">
<el-form-item label="上传图片">
<el-upload
class="avatar-uploader"
:action="uploadAction"
:show-file-list="false"
:on-success="handleUploadSuccess"
:before-upload="beforeUpload">
<img v-if="bannerForm.img" :src="baseUrl+'/banner/'+bannerForm.img" class="avatar" />
<div v-else class="upload-placeholder">
<el-icon class="upload-icon"><Plus /></el-icon>
<div class="upload-text">点击上传图片</div>
</div>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveBanner">确定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style scoped lang="scss">
.banner-container {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
// 页面头部
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 20px;
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
.header-title {
h2 {
margin: 0;
color: #ffffff;
font-size: 24px;
font-weight: 600;
}
.subtitle {
margin: 8px 0 0 0;
color: rgba(255, 255, 255, 0.85);
font-size: 14px;
}
}
.add-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 12px 24px;
font-size: 14px;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
transition: all 0.3s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
}
}
// 表格区域
.table-wrapper {
background: #ffffff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
.banner-table {
:deep(.el-table__header-wrapper) {
th {
background-color: #f8f9fa;
color: #333;
font-weight: 600;
}
}
}
.banner-image {
width: 120px;
height: 80px;
border-radius: 4px;
cursor: pointer;
transition: transform 0.3s;
&:hover {
transform: scale(1.05);
}
}
}
// 分页
.pagination-wrapper {
display: flex;
justify-content: center;
padding: 20px;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
.custom-pagination {
:deep(.el-pagination.is-background .el-pager li:not(.is-disabled).is-active) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
}
}
// 对话框
.banner-dialog {
:deep(.el-dialog__header) {
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #ffffff;
margin: 0;
border-radius: 8px 8px 0 0;
.el-dialog__title {
color: #ffffff;
font-size: 18px;
font-weight: 600;
}
.el-dialog__headerbtn {
.el-dialog__close {
color: #ffffff;
font-size: 18px;
&:hover {
color: #ffffff;
}
}
}
}
.banner-form {
padding: 20px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
}
// 上传组件
.avatar-uploader {
.avatar {
width: 200px;
height: 150px;
display: block;
border-radius: 4px;
object-fit: cover;
}
:deep(.el-upload) {
border: 2px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: all 0.3s;
width: 200px;
height: 150px;
&:hover {
border-color: #667eea;
}
}
}
.upload-placeholder {
width: 200px;
height: 150px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #8c939d;
.upload-icon {
font-size: 32px;
margin-bottom: 8px;
}
.upload-text {
font-size: 14px;
}
}
</style>一键三连支持下杰哥哦,免费送资料
v:jf3qcom

好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论
您可能感兴趣的博客


新业务
springboot学习
ssm框架课
vue学习
【带小白】java基础速成