注:如果想直接使用关注私信要websocet上传地址,就可以直接复制到自己的项目使用图片上传了
上传成功后,会返回一个图片地址可以根据自身要求去使用。
1.组件完整代码
<template>
<div class="UploadImg">
<!-- 图片放置位置 -->
<TransitionGroup name="list">
<div class="imgList" key="imgList">
<img
:src="item"
alt=""
v-for="(item, index) in fileList"
:key="index"
/>
<!-- 上传中图片 -->
<div
class="loading"
v-loading="loading"
v-if="loading"
key="loading"
></div>
</div>
</TransitionGroup>
<div class="addBtn" @click="chooseImg">
<el-icon size="40"><Plus /></el-icon>
</div>
<!-- 上传文件最重要的东西 accept="image/*"控制只能选择图片-->
<input
v-show="false"
type="file"
ref="fileInput"
:multiple="multiple"
accept="image/*"
@change="changeImgSuccess"
/>
</div>
</template>
<script setup>
import {
ref,
onMounted,
watch,
defineProps,
defineEmits,
onBeforeUnmount,
} from "vue";
import { ElNotification } from "element-plus";
import { ElMessage, ElMessageBox } from "element-plus";
const props = defineProps({
multiple: {
type: Boolean,
default: false,
},
fileListProps: {
type: Array,
default: () => [],
},
max: {
type: Number,
default: 1,
},
});
const fileList = ref([]);
// 图片加载状态
const loading = ref(false);
// 监听弹窗关闭清空图片
watch(
() => props.fileListProps,
(newVal) => {
console.log(111);
newVal.length === 0 ? (fileList.value = []) : (fileList.value = newVal);
loading.value = false;
},
{
immediate: true,
}
);
const emits = defineEmits(["update:fileListProps"]);
// 导入chunk
import { splitBase64IntoChunks } from "@/utils/splitBase64IntoChunks.js";
const fileInput = ref(null);
// 打开图片选择
const chooseImg = () => {
if (loading.value) {
ElNotification({
title: "Error",
message: "图片正在上传中请稍后重试!",
type: "error",
});
return;
}
if (fileList.value.length >= props.max) {
ElMessageBox.confirm(
"图片已是最大限制如果强制选择图片前面选中的第一张图片将会被以出,确定要这么做吗?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
fileInput.value.click();
// 删掉数据第一个数据
fileList.value.splice(0, 1);
})
.catch(() => {
ElMessage({
type: "info",
message: "您取消了",
});
});
return;
}
fileInput.value.click();
};
// 选择完图片后
const changeImgSuccess = (event) => {
console.log(event.target.files);
if (event.target.files.length === 0) {
ElNotification({
title: "Info",
message: "你取消了选择图片!",
type: "info",
});
return;
}
if (props.multiple) {
// 多选
console.log("多选图片");
const files = event.target.files;
for (let i = 0; i < files.length; i++) {
const file = files[i];
//文件名
const fileName = new Date().getTime() + file.name;
const reader = new FileReader();
reader.addEventListener("load", () => {
const base64Data = reader.result;
// 在连接建立后,可以发送消息或执行其他操作
socket.value.send(
JSON.stringify({
event: "imgName",
data: fileName,
})
);
// 分块
let chunks = splitBase64IntoChunks(base64Data, 1024 * 64);
console.log(chunks, "chunks");
// 逐个发送块数据
chunks.forEach((chunk, index) => {
let eventData = {
event: "base64Data", // 事件标识字段
data: chunk,
};
setTimeout(() => {
socket.value.send({
data: JSON.stringify(eventData),
success: () => {
console.log(`块 ${index + 1}/${chunks.length} 已发送`);
if (index === chunks.length - 1) {
// 所有块数据已发送,发送结束消息
socket.value.send({
data: JSON.stringify({
data: {},
event: "upload_complete",
}),
success: () => {
console.log("上传完成");
// 关闭定时器或执行其他操作
},
});
}
},
});
}, index * 200); // 延迟发送以避免过快发送
});
});
reader.readAsDataURL(file);
}
} else {
// 单选
console.log("单选图片");
const file = event.target.files[0];
loading.value = true;
//文件名
const fileName = new Date().getTime() + file.name;
const reader = new FileReader();
reader.addEventListener("load", () => {
let base64Data = reader.result;
const type = file.type;
// 这里的base64 有data:image/jpeg;base64, 不需要把它截取掉
const prefix = `data:${type};base64,`;
if (base64Data.startsWith(prefix)) {
base64Data = base64Data.substr(prefix.length);
}
console.log("base64Data", base64Data);
// 在连接建立后,可以发送消息或执行其他操作
socket.value.send(
JSON.stringify({
event: "imgName",
data: fileName,
})
);
// 分块
let chunks = splitBase64IntoChunks(base64Data, 1024 * 64);
console.log(chunks, "chunks");
// 逐个发送块数据
chunks.forEach((chunk, index) => {
let eventData = {
event: "base64Data", // 事件标识字段
data: chunk,
};
setTimeout(() => {
console.log("发送了", index);
socket.value.send(JSON.stringify(eventData));
if (index == chunks.length - 1) {
console.log("最后一次了");
socket.value.send(
JSON.stringify({
data: {},
event: "upload_complete",
})
);
}
}, index * 200); // 延迟发送以避免过快发送
});
});
reader.readAsDataURL(file);
}
};
// 初始化websocket
const socket = ref(null);
const initWebsocket = () => {
if (!window.WebSocket) {
alert("您的浏览器不支持WebSocket");
} else {
// 获取ip地址或者域名
const ip = location.hostname;
const wsIP = "ws://" + ip + ":8080";
socket.value = new WebSocket(wsIP); // 替换为你的 WebSocket 服务器地址
// socket.value = new WebSocket("ws://127.0.0.1:8080"); // 替换为你的 WebSocket 服务器地址
socket.value.onopen = () => {
console.log("WebSocket 连接已建立");
// 开启心跳
heartBeat();
};
socket.value.onmessage = (event) => {
isReconnect.value = true;
heartRestartTimer.value && clearTimeout(heartRestartTimer.value);
console.log("收到消息:", event.data);
const jsonData = JSON.parse(event.data);
// 处理收到的消息数据
if (jsonData.event == "sentImgUrl") {
console.log("结束图片上传", jsonData);
const uploadUrl = "https://" + jsonData.data.imgUrl;
fileList.value.push(uploadUrl);
emits("update:fileListProps", fileList.value);
loading.value = false;
console.log("收到的图片url", uploadUrl, fileList.value);
}
};
socket.value.onclose = () => {
console.log("WebSocket 连接已关闭");
isReconnect.value = false; //连接都关闭了肯定要重新连接
reconnect();
// 在连接关闭后,可以执行清理操作或重新连接等处理
};
}
};
// 存放心跳定时器
const heartBeatTimer = ref(null);
// 存放错误心跳定时器
const heartBeatErrorTimer = ref(null);
// 心跳重新连接
const heartRestartTimer = ref(null);
// 开启心跳
const heartBeat = () => {
// 每 60 秒发送一次心跳
const heartBeatTime = 60 * 1000;
// 心跳包
const heartBeatData = {
event: "heartBeat",
data: {},
};
heartBeatTimer.value && clearInterval(heartBeatTimer.value);
heartRestartTimer.value && clearTimeout(heartRestartTimer.value);
heartBeatTimer.value = setInterval(() => {
socket.value.send(JSON.stringify(heartBeatData));
// 如果20s收不到消息要么断开要么后端出错
heartRestartTimer.value = setTimeout(() => {
isReconnect.value = false;
reconnect();
}, 20 * 1000);
}, heartBeatTime);
};
// 是否需要重连的开关 true 表示不需要重连
const isReconnect = ref(false);
// 重新连接
const reconnect = () => {
if (isReconnect.value) {
return;
}
isReconnect.value = true;
// 关闭当前连接
socket.value.close();
// 等待1s后重新连接
heartBeatErrorTimer.value = setTimeout(() => {
initWebsocket();
}, 1000);
};
onMounted(() => {
initWebsocket();
});
onBeforeUnmount(() => {
// 关闭当前连接
socket.value.close();
});
</script>
<style scoped lang="scss">
.list-enter-active,
.list-leave-active {
transition: opacity 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
}
.list-leave-active {
position: absolute;
}
.UploadImg {
display: flex;
.imgList {
display: flex;
position: relative;
.loading {
width: 100px;
height: 100px;
border-radius: 10px;
border: 1px solid #d9d9d9;
}
img {
margin: 0 5px;
width: 100px;
height: 100px;
border-radius: 10px;
border: 1px solid #d9d9d9;
}
}
.addBtn {
width: 100px;
height: 100px;
border-radius: 10px;
// 边框虚线
border: 1px dashed #d9d9d9;
background-color: #fafafa;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-left: 5px;
}
}
</style>
图片分块上传的代码
// 分割大的base64数据为块
export function splitBase64IntoChunks(base64Data, chunkSize) {
const totalChunks = Math.ceil(base64Data.length / chunkSize);
const chunks = [];
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = start + chunkSize;
const chunk = base64Data.slice(start, end);
chunks.push(chunk);
}
return chunks;
}
2.组件使用代码
//使用
<el-form-item label="图片">
<UploadImg v-model:fileListProps="ruleForm.photoList" />
</el-form-item>
//引入
import UploadImg from "@/components/base/UploadImg.vue";
绑上ruleForm.photoList》》图片上传成功后的地址存放位置。!!必须是数组