SpringBoot+Vue3+Element Plus实现图片上传和预览
先看效果:
表格里展示:
单击预览,可缩放旋转:
1.后端接口:
@RestController
@RequestMapping("/files")
public class FileController {
// 文件上传存储路径
private static final String filePath = System.getProperty("user.dir") + "/files/";
@Value("${server.port:8080}")
private String port;
@Value("${ip:localhost}")
private String ip;
/**
* 文件上传
*/
@PostMapping("/upload")
public ResultUtil upload(MultipartFile file) {
String flag;
synchronized (FileController.class) {
// 获取时间戳
flag = System.currentTimeMillis() + "";
ThreadUtil.sleep(1L);
}
// 获取文件的实际名称
String fileName = file.getOriginalFilename();
try {
//判断目录是否存在,如果不存在则新建
if (!FileUtil.isDirectory(filePath)) {
FileUtil.mkdir(filePath);
}
// 文件存储形式:时间戳-文件名
FileUtil.writeBytes(file.getBytes(), filePath + flag + "-" + fileName); // ***/manager/files/1697438073596-avatar.png
System.out.println(fileName + "--上传成功");
} catch (Exception e) {
System.err.println(fileName + "--文件上传失败");
}
String http = "http://" + ip + ":" + port + "/files/";
// 返回上传成功的图片地址
return ResultUtil.ok(http + flag + "-" + fileName); // http://localhost:8080/files/1697438073596-avatar.png
}
}
2.前端vue代码(新增和修改的模态框):
<!--模态框-->
<el-dialog
v-model="dialogVisible"
title="商品信息"
width="500"
ref="goodsForm"
>
<el-form :model="goods" :rules="rules" ref="goodsForm">
<el-form-item label="商品名称" prop="name">
<el-input v-model="goods.name" autocomplete="off"/>
</el-form-item>
<el-form-item label="商品单价" prop="price">
<el-input v-model="goods.price" autocomplete="off"/>
</el-form-item>
<el-form-item label="商品简介" prop="intro">
<el-input v-model="goods.intro" autocomplete="off"/>
</el-form-item>
<el-form-item label="商品库存" prop="amount">
<el-input v-model="goods.amount" autocomplete="off"/>
</el-form-item>
<el-form-item label="商品类别" prop="goodsType">
<el-select v-model="goods.goodsType" placeholder="请选择商品类别">
<el-option label="手机" value="手机"/>
<el-option label="电脑" value="电脑"/>
<el-option label="水果" value="水果"/>
</el-select>
</el-form-item>
<!-- 单选框-->
<el-form-item label="状态" prop="remark">
<el-radio-group v-model="goods.remark">
<el-radio value="上架">上架</el-radio>
<el-radio value="未上架">未上架</el-radio>
</el-radio-group>
</el-form-item>
<!-- 图片上传-->
<el-form-item label="商品图片">
<el-upload
class="avatar-uploader"
:action="'http://' + 'localhost:8080/files/upload'"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
<img v-if="goods.imgurl" :src="goods.imgurl" class="avatar" style="width: 300px"/>
<el-icon v-else class="avatar-uploader-icon">
<Plus/>
</el-icon>
</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="save">
提交
</el-button>
</div>
</template>
</el-dialog>
js部分--两个钩子函数:
// 商品的信息
const goods = ref({
id: "",
name: "",
price: "",
intro: "",
amount: "",
goodsType: "",
imgurl: "",
remark: "未上架"
})
// 图片上传之前的钩子--》判断文件是否是图片、图片大小是否符合要求
const beforeAvatarUpload = (rawFile) => {
console.log("类型:", rawFile.type)
console.log(rawFile.type !== 'image/png');
// 获取文件后缀
let testmsg = rawFile.name.substring(rawFile.name.lastIndexOf('.') + 1)
console.log("后缀:", testmsg)
// 判断文件类型
const extension = testmsg === 'jpg' || testmsg === 'png' || testmsg === 'jpeg' || testmsg === 'gif'
if (!extension) {//不符合要求
ElMessage.error('图片必须是jpg/jpeg/png格式!')
return false
}
// 校验文件大小
if (rawFile.size / 1024 / 1024 > 2) {
ElMessage.error('图片大小不能超过2MB!')
return false
}
return true
}
// 图片上传后的钩子函数-->回写图片地址
const handleAvatarSuccess = (res) => {
console.log(res)
goods.value.imgurl = res.msg
}
完整版:
<script setup>
import {onMounted, ref} from "vue";
import request from "../axios/request.js";
import {ElMessage, ElMessageBox} from "element-plus";
import {
Delete,
Edit,
Message,
Search,
Plus,
House,
User,
Promotion,
Goods,
Download,
UploadFilled
} from "@element-plus/icons-vue";
const list = ref([])
// 查询条件
const name = ref("")
// 模态框是否显示
const dialogVisible = ref(false);
// 商品的信息
const goods = ref({
id: "",
name: "",
price: "",
intro: "",
amount: "",
goodsType: "",
imgurl: "",
remark: "未上架"
})
// 图片地址
const url = ref("/element-plus-logo.svg")
// 表单校验规则
const rules = {
name: [
{required: true, message: "请输入名称", trigger: "blur"}
],
price: [
{required: true, message: "请输入价格", trigger: "blur"}
],
intro: [
{required: true, message: "请输入简介", trigger: "blur"}
],
amount: [
{required: true, message: "请输入库存", trigger: "blur"}
],
goodsType: [
{required: true, message: "请选择类别", trigger: "blur"}
],
remark: [
{required: true, message: "请选择状态", trigger: "blur"}
]
}
// 表单引用
const goodsForm = ref();
// 分页参数
const pageNum = ref(1)
const pageSize = ref(10)
const total = ref(0)
// 保存选中的数据
const multipleSelection = ref([])
// 查询商品信息
const getList = () => {
request({
url: 'http://192.168.31.242:8080/goods/page',
method: 'get',
params: {
page: pageNum.value,
limit: pageSize.value,
goodsName: name.value,
}
}).then((res) => {
// console.log(res)
list.value = res.data.data;
total.value = res.data.count;
// name.value=""
})
}
// 改变每页数据量
const handleSizeChange = (pSize) => {
// console.log(pSize+`items per page`)
pageSize.value = pSize;
getList()
}
// 改变当前页
const handleCurrentChange = (pNum) => {
// console.log(`current page: `+pNum)
pageNum.value = pNum;
getList()
}
// 根据id删除
const del = (id) => {
// console.log(id)
ElMessageBox.confirm(
'是否确认删除?',
'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
.then(() => {
request({
url: 'http://192.168.31.242:8080/goods/del',
method: 'get',
params: {id: id}
}).then(res => {
if (res.data == 1) {
ElMessage({
type: 'success',
message: '删除成功!',
})
} else {
ElMessage({
type: 'error',
message: '删除失败!',
})
}
getList()
})
})
.catch(() => {
ElMessage({
type: 'warning',
message: '取消删除!',
})
})
}
// 搜索
const search = () => {
getList()
}
// 新增
const add = () => {
dialogVisible.value = true
}
// 数据提交
const save = () => {
// 校验表单是否合法
goodsForm.value.validate((flag, err) => {
if (flag) {//验证通过
request({
url: 'http://192.168.31.242:8080/goods/save',
method: 'post',
data: goods.value
}).then(res => {
getList()
goods.value = ""
dialogVisible.value = false
ElMessage({
type: 'success',
message: '保存成功',
})
})
} else {
ElMessage({
type: 'error',
message: '验证没通过!',
})
}
})
}
// 修改
const edit = (item) => {
// 深拷贝
const newObj = Object.assign({}, item)
dialogVisible.value = true
goods.value = newObj
}
// 获取已选择的数据
const handleSelectionChange = (val) => {
// console.log(val)
multipleSelection.value = val;
}
// 批量删除
const delBatch = () => {
// 获取需要删除的数据的id--数组
let ids = multipleSelection.value.map(item => item.id);
// console.log("ids",ids)
if (ids.length != 0) {
ElMessageBox.confirm(
'是否确认删除?',
'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
).then(() => {
request({
url: 'http://192.168.31.242:8080/goods/delBatch',
method: 'post',
data: ids
}).then(res => {
ElMessage({
type: 'success',
message: '删除成功!',
})
getList()
// console.log(res)
})
}).catch(() => {
ElMessage({
type: 'warning',
message: '取消删除!',
})
})
} else {
ElMessage({
type: 'error',
message: '请先选择数据!'
})
}
}
// 导入成功后重新加载数据
const handleExcelImportSuccess = () => {
getList();
ElMessage({
type: 'success',
message: '上传成功!',
})
}
// 导出数据
const exp = () => {
window.open(`http://localhost:8080/goods/export`)
}
// excle导入前验证-->文件类别、文件大小
const cheackExcle = (rawFile) => {
// console.log(rawFile.size)
// 获取文件后缀
let testmsg = rawFile.name.substring(rawFile.name.lastIndexOf('.') + 1)
if (testmsg !== 'xlsx') {
ElMessage.error('只能上传后缀为.xlsx的excle文件')
return false
} else if (rawFile.size / 1024 / 1024 > 2) {
ElMessage.error('文件大小不能超过2M')
return false
}
return true
}
// 图片上传之前的钩子--》判断文件是否是图片、图片大小是否符合要求
const beforeAvatarUpload = (rawFile) => {
console.log("类型:", rawFile.type)
console.log(rawFile.type !== 'image/png');
// 获取文件后缀
let testmsg = rawFile.name.substring(rawFile.name.lastIndexOf('.') + 1)
console.log("后缀:", testmsg)
// 判断文件类型
const extension = testmsg === 'jpg' || testmsg === 'png' || testmsg === 'jpeg' || testmsg === 'gif'
if (!extension) {//不符合要求
ElMessage.error('图片必须是jpg/jpeg/png格式!')
return false
}
// 校验文件大小
if (rawFile.size / 1024 / 1024 > 2) {
ElMessage.error('图片大小不能超过2MB!')
return false
}
return true
}
// 图片上传后的钩子函数-->回写图片地址
const handleAvatarSuccess = (res) => {
console.log(res)
goods.value.imgurl = res.msg
}
//生命周期函数
onMounted(() => {
getList();
})
</script>
<template>
<!-- 数据表格-->
<el-form-item label="名称" style="margin-top: 20px">
<el-input v-model="name" style="width: 200px" placeholder="请输入商品名称"></el-input>
<el-button type="primary" :icon="Search" style="margin-left: 10px" @click="search">搜索</el-button>
<el-button type="primary" :icon="Plus" style="margin-left: 10px" @click="add">新增</el-button>
<el-button type="danger" :icon="Delete" style="margin-left: 10px" @click="delBatch">批量删除</el-button>
<el-upload :action="'http://' + 'localhost:8080/goods/import'"
:show-file-list="false"
accept="xlsx"
:before-upload="cheackExcle"
:on-success="handleExcelImportSuccess"
style="display: inline-block">
<el-button type="primary" style="margin-left: 10px" :icon="UploadFilled">导入</el-button>
</el-upload>
<el-button type="warning" style="margin-left: 10px" :icon="Download" @click="exp">导出</el-button>
</el-form-item>
<el-table :data="list" width="100%" stripe @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"/>
<el-table-column type="index" label="序号" width="80" :index="1"></el-table-column>
<!-- <el-table-column prop="id" label="ID"></el-table-column>-->
<el-table-column prop="name" label="商品名称"></el-table-column>
<el-table-column prop="price" label="单价"></el-table-column>
<el-table-column prop="intro" label="简介" width="100" show-overflow-tooltip></el-table-column>
<el-table-column prop="amount" label="库存"></el-table-column>
<el-table-column prop="goodsType" label="类别"></el-table-column>
<el-table-column prop="remark" label="状态"></el-table-column>
<el-table-column label="图片" width="120">
<template #default="scope">
<el-image style="width: 100px; height: 80px; padding: 5px;" :src="scope.row.imgurl" :zoom-rate="1.2"
:max-scale="7" :min-scale="0.2" :preview-src-list="[scope.row.imgurl]" :preview-teleported="true"/>
</template>
</el-table-column>
<el-table-column label="操作" width="300" fixed="right">
<template #default="scope">
<el-button type="primary" :icon="Edit" @click="edit(scope.row)">修改</el-button>
<el-button type="danger" :icon="Delete" @click="del(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div style="padding: 10px">
<el-pagination
v-model:current-page="pageNum"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 30, 40]"
size="large"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!--模态框-->
<el-dialog
v-model="dialogVisible"
title="商品信息"
width="500"
ref="goodsForm"
>
<el-form :model="goods" :rules="rules" ref="goodsForm">
<el-form-item label="商品名称" prop="name">
<el-input v-model="goods.name" autocomplete="off"/>
</el-form-item>
<el-form-item label="商品单价" prop="price">
<el-input v-model="goods.price" autocomplete="off"/>
</el-form-item>
<el-form-item label="商品简介" prop="intro">
<el-input v-model="goods.intro" autocomplete="off"/>
</el-form-item>
<el-form-item label="商品库存" prop="amount">
<el-input v-model="goods.amount" autocomplete="off"/>
</el-form-item>
<el-form-item label="商品类别" prop="goodsType">
<el-select v-model="goods.goodsType" placeholder="请选择商品类别">
<el-option label="手机" value="手机"/>
<el-option label="电脑" value="电脑"/>
<el-option label="水果" value="水果"/>
</el-select>
</el-form-item>
<!-- 单选框-->
<el-form-item label="状态" prop="remark">
<el-radio-group v-model="goods.remark">
<el-radio value="上架">上架</el-radio>
<el-radio value="未上架">未上架</el-radio>
</el-radio-group>
</el-form-item>
<!-- 图片上传-->
<el-form-item label="商品图片">
<el-upload
class="avatar-uploader"
:action="'http://' + 'localhost:8080/files/upload'"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
<img v-if="goods.imgurl" :src="goods.imgurl" class="avatar" style="width: 300px"/>
<el-icon v-else class="avatar-uploader-icon">
<Plus/>
</el-icon>
</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="save">
提交
</el-button>
</div>
</template>
</el-dialog>
</template>
<style scoped>
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
</style>