图片压缩 图片转base64
<html>
<head>
<title>Socket.IO chat</title>
<link rel="stylesheet" href="element.css" >
<link rel="stylesheet" href="index.css?v=1.0.2">
<script src="vue.js"></script>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app" >
<!--登录和退出登录 二维码展示-->
<div class="login-btn" v-cloak v-show="!showChatBox">
<el-button type="primary" size="small" @click="loginFun">登录</el-button>
</div>
<!--聊天界面-->
<div class="content-box" v-cloak v-show="showChatBox" >
<div class="con-left">
<div class="flex space-between search-box">
<el-input v-model="searchValue" placeholder="请输入" size="small" clearable @clear="clearSearch"></el-input>
<el-button size="small" class="btn-search" @click="searchFun">搜索</el-button>
</div>
<div class="left-bottom" v-loading="loading">
<div :class=[item.payload.id==currentTopInfo.payload.id?'checkedLeft':'','small-con'] @click="topicNameFun(item,index)" v-for="(item,index) in topicData" :key="item.id">
<div class="real-con">
<div class="left-avatar">
<img :src="item.payload.avatar" alt="">
</div>
<div class="left-name">
<div >{{item.payload.topic||`群聊(${item.payload.memberIdList.length})`}}</div>
<div class="con-msg">{{item.msg}}</div>
</div>
<div class="red-dot" v-show="item.payload.id!=currentTopInfo.payload.id&&item.isRead==0"></div>
</div>
</div>
</div>
</div>
<div class="con-right">
<div class="right-top">
<span class="name" v-if="topicData.length>0">{{currentTopInfo.payload.topic||`群聊(${currentTopInfo.payload.memberIdList.length})`}}</span>
<!-- <el-button type="danger" size="small" class="logout" @click="logoutFun">退出登录</el-button>-->
</div>
<div v-loading="messLoading">
<div class="right-center-content" v-cloak id="data-list-content" ref="dataListBox" @scroll="handleScroll" >
<p v-if="showAllBtn" class="check-all-message"><span >{{msgData}}</span></p>
<template v-for="(item,index) in messageData">
<!--左边消息 非个人 -->
<div class="no-self-box" v-if="!item.isSelf">
<div class="right-avatar">
<img :src="item.avatar" v-if="item.avatar" alt="">
<el-avatar shape="square" icon="el-icon-user-solid" v-else></el-avatar>
</div>
<div class="ml-9">
<div class="nickname">
<span class="name-sty">{{item.nickName}}</span>
<span>{{item.receivTime}}</span>
</div>
<div class="no-self-con" v-if="item.contentType==1">
<span>{{item.content}}</span>
</div>
<div class="content-img" v-else>
<el-image
:src="item.content"
:preview-src-list="[item.bigImg]">
</el-image>
</div>
</div>
</div>
<!--右边消息 个人 -->
<div class="flex flex-end-justify pad-bot" v-if="item.isSelf">
<div class="self-box">
<div class="mr-9">
<div class="nickname text-right">
<span>{{item.receivTime}}</span>
<span class="name-sty" style="margin-left: 8px">{{item.nickName}}</span>
</div>
<div class="no-self-con" style="text-align: right" v-if="item.contentType==1">
<span style="text-align: left">{{item.content}}</span>
</div>
<div class="content-img" style="text-align: right" v-else>
<el-image
:src="item.content"
:preview-src-list="[item.bigImg]">
</el-image>
</div>
</div>
<div class="right-avatar">
<img :src="item.avatar" v-if="item.avatar" alt="">
<el-avatar shape="square" icon="el-icon-user-solid" v-else></el-avatar>
</div>
</div>
</div>
</template>
</div>
</div>
<div class="right-center-content empty-box" v-show="showEmpty"></div>
<!-- 底部-->
<div class="right-bottom">
<div class="send-content">
<div class="top-header">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="文字" name="first">
</el-tab-pane>
<el-tab-pane label="图片" name="second"></el-tab-pane>
</el-tabs>
</div>
<!-- 文字内容框-->
<div style="padding-top: 10px" v-if="activeName=='first'">
<el-input
ref="refInput"
type="textarea"
:rows="4"
placeholder="请输入消息"
@keydown.native="handleTextareaKeydown"
v-model="realCon">
</el-input>
</div>
<!-- 图片内容框-->
<div v-if="activeName=='second'" class="bottom-img">
<el-upload ref="elupload" class="upload-drag" drag multiple action="" :auto-upload="false" :on-change.enter.stop="changeFile" list-type="picture-card">
<div class="img-content">
<div class="img-box" v-for="(item,index) in fileList" :key="index" >
<img :src="item.url" class="selectImg" alt="">
<div class="shadow-btn">
<i class="el-icon-delete shadow-hover-con" @click.stop="deleteImg(index)"></i>
</div>
</div>
</div>
<div slot="tip" class="drag-tip">将图片拖拽到此处</div>
</el-upload>
</div>
</div>
<div class="flex flex-end-justify send-btn-box">
<div v-if="activeName=='second'">
<el-upload ref="eluploadCheck" multiple action="" :auto-upload="false" :on-change.enter.stop="changeFile" list-type="picture-card">
<el-button type="primary" size="small" style="width: 100px;position: fixed;bottom: 8px;right:146px" ref="btn">选择图片</el-button>
</el-upload>
</div>
<el-tooltip class="item" :manual="true" effect="light" v-model="showTip" offset="15" content="不能发送空白信息" placement="top-end">
<el-button type="primary" size="small" style="width: 100px;position: fixed;bottom: 8px;right:30px" @click.enter.stop="sendMessage" :loading="sendLoading">发送</el-button>
</el-tooltip>
</div>
</div>
</div>
</div>
<!--搜索弹框-->
<div class="search-pos-box" v-cloak v-if="searchDataList.length>0">
<div class="real-con" v-for="(item,index) in searchDataList" :key="index" @click="searchItem(item)">
<div class="left-avatar">
<img :src="item.payload.avatar" alt="">
</div>
<div class="left-name">{{item.payload.topic}}</div>
</div>
</div>
<!--二维码弹框-->
<el-dialog
title="扫描二维码"
:visible.sync="showQrcode"
width="334px"
>
<div class="qrcode">
<img id="qrcode" :src="showQrcodeUrl">
</div>
</el-dialog>
<!--音频-->
<audio
src="audio.mp3"
type="audio/mp3"
preload="auto"
controls
hidden="true"
></audio>
</div>
</body>
<script src="elementUI.js" rel="external nofollow"></script>
<script src="socket.io.js" rel="external nofollow"></script>
<script src="axios.js" rel="external nofollow"></script>
<script>
new Vue({
el: '#app',
data(){
return {
//搜索字段
searchValue:'',
//搜索列表
searchDataList:[],
//群列表
topicData:[],
//当前群详细信息
currentTopInfo:{},
//默认展示文字tab
activeName:'first',
//发送的文字
realCon:"",
//所选择的文件
fileList:[],
//消息列表
messageData:[],
//是否显示聊天界面
showChatBox:false,
//是否显示二维码弹框
showQrcode:false,
//二维码Url地址
showQrcodeUrl:'',
//查看全部消息按钮
showAllBtn:false,
loading:false,
sendLoading:false,
messLoading:false,
scrollHeight:0,
//空白提示
showTip:false,
//最新id
finalId:'',
//文字提示
msgData:'',
showEmpty:false,
}
},
async created(){
await this.getUserInfo();
await this.getRooms()
},
mounted(){
let that=this
let socket = io();
socket.on('scanned', function () {
console.log('扫码进入')
that.loading=true
setTimeout(()=>{
//聊天框出现
that.showChatBox=true
//二维码弹框隐藏
that.showQrcode=false
that.showQrcodeUrl=''
that.getUserInfo();
that.getRooms()
},5000)
});
socket.on('qrcode', function (qrcode) {
that.showChatBox=false //登录按钮出现
that.showQrcode=true //二维码出现
that.showQrcodeUrl=qrcode
});
socket.on('msg', function (msg) {
console.log(msg)
let roomId = msg.room.id
//排序 当前群和推送消息的群不一样时
if(roomId!=that.currentTopInfo.id){
if(!msg.self){ //不是自己
if(that.currentTopInfo.id==that.topicData[0].id){ //排第二位
that.sortTopic(1,0,roomId)
}else if(that.currentTopInfo.id!=that.topicData[0].id){ //排第一位
that.sortTopic(0,0,roomId)
}
//判断群消息 加红点
let findCon=that.topicData.find(e=>{
return e.id==roomId
})
if(findCon){
findCon.isRead=0 //加红点
msg.type=msg.type?msg.type:1
findCon.msg=msg.type==1?msg.text:msg.type==2?'[图片]':msg.text
//播放音频
that.$nextTick(() => {
let audio = document.querySelector("audio");
audio.play();
});
}
}else{ //是自己
that.topicData.forEach((e,index)=>{
if(e.id==roomId){
e.isRead=1
msg.type=msg.type?msg.type:1
e.msg=msg.type==1?msg.text:msg.type==2?'[图片]':msg.text
Vue.set(that.topicData,index,e)
}
})
}
}else{
//选中的群和当前消息群一致时
//不是自己,排序到第一位
if(!msg.self){
that.sortTopic(0,0,roomId)
}
that.topicData.forEach((e,index)=>{
if(e.id==roomId){
msg.type=msg.type?msg.type:1
e.msg=msg.type==1?msg.text:msg.type==2?'[图片]':msg.text
Vue.set(that.topicData,index,e)
}
})
let obj= {
avatar:msg.talker.payload.avatar,
nickName:msg.talker.payload.name,
content:msg.text,
bigImg:msg.type==2?msg.text.replace('compressor_',''):'',
receivTime:msg.receivTime,
contentType:msg.type, //1 文字 2 图片
isSelf:msg.self //false 不是自己 true:是自己
}
that.messageData.push(obj)
that.updateMsgType()
that.scrollToBottom()
}
});
socket.on('isRead', function (msg) {
that.topicData.forEach(e=>{
if(e.id==msg.roomId){
e.isRead=1
}
})
})
},
methods:{
//清空搜索选择
clearSearch(){
this.searchValue=''
this.searchDataList=[]
},
//文字输入框回车发送消息
handleTextareaKeydown(e) {
if (!e.shiftKey && e.keyCode == 13) {
e.cancelBubble = true; //ie阻止冒泡行为
e.stopPropagation();//Firefox阻止冒泡行为
e.preventDefault(); //取消事件的默认动作*换行
//以下处理发送消息代码
this.sendMessage()
}
},
//点击左侧群名称
topicNameFun(item,index){
this.showEmpty=true
item.isRead=1
this.currentTopInfo={...item}
this.messageData=[]
this.finalId=''
this.getChatRecord()
},
//点击搜索项
searchItem(item){
// 点击之后 拍完序,清空搜索的内容
this.searchDataList=[]
this.searchValue=''
this.sortTopic(0,0,item.id)
this.showEmpty=true
item.isRead=1
this.currentTopInfo={...item}
this.finalId=''
this.messageData=[]
this.getChatRecord()
},
//切换文字和图片
handleClick(){
this.fileList=[]
this.realCon=''
if(this.activeName=='second'){
this.$nextTick(()=>{
this.$refs.elupload.$children[1].$refs.input.disabled=true
this.$refs.btn.$el.onkeydown= (e) =>{
let _key=window.event.keyCode;
if(_key===13){
return false;
}
}
})
//监听回车事件发送消息
let that = this;
document.onkeydown = function(e) {
// that.disabled=true
var key = window.event.keyCode;
if (key == 13) {
if(!that.sendLoading){
that.sendMessage();
}
}
}
}else{
this.$nextTick(()=>{
if(this.activeNode=='first'){
this.$refs.refInput.focus() // 设置焦点
}
})
}
},
//发消息 调后台接口并渲染页面
async sendMessage() {
if(this.activeName=='first'){
let contentMsg=JSON.parse(JSON.stringify(this.realCon))
//避免重复发送
if(this.realCon.split(" ").join("").length == 0||!this.realCon){
this.showTip=true
setTimeout(()=>{
this.showTip=false
},1000)
return
}
this.realCon=''
this.sendLoading=true
await axios.post("/send",{"topic":this.currentTopInfo.payload.topic,"msg":contentMsg}).then(res=>{
if(res.data.code==200){
contentMsg=''
}
});
this.sendLoading=false
this.scrollToBottom()
}else if(this.activeName=='second'){
if(this.fileList.length<=0){
this.showTip=true
setTimeout(()=>{
this.showTip=false
},1000)
return
}
this.sendLoading=true
for (const item of this.fileList) {
//图片文件流转换成base64
await this.transFun(item.raw).then(res=>{
item.fileName=res.fileName
item.path=res.path
})
//发送图片
await axios.post("/sendImg", {
topic:this.currentTopInfo.payload.topic,
base64:item.path,
fileName:item.fileName,
slFileName:item.slFileName,
base64Compressor:item.slPath,
roomId:this.currentTopInfo.id
}).then(res=>{
if(res.data.code==200){
this.fileList=[]
}
});
}
this.sendLoading=false
this.scrollToBottom()
}
},
//群列表排序
sortTopic(start,index,roomId){
let that=this
if(roomId){
let objCon = {}
for (let i = 0; i<that.topicData.length; i++) {
if (that.topicData[i].id == roomId) {
objCon = that.topicData[i]
that.topicData.splice(i, 1)
break
}
}
that.topicData.splice(start, index, objCon)
}
},
//滚动加载更多消息
handleScroll() {
const e = this.$refs['dataListBox']
// 距离顶部
const scrollTop = e.scrollTop
if (scrollTop == 0) {
this.showAllBtn = true
if(this.finalId){
this.getChatRecord() //加载更多聊天记录
}
} else {
this.showAllBtn = false
}
},
//退出登录
logoutFun(){
this.showChatBox=false
axios.get("/logout", function (data) {
});
},
//登录
loginFun(){
axios.get("/start", function (data) {
});
},
//获取当前用户信息
async getUserInfo(){
try {
await axios.get("/userInfo").then((result)=>{
if(result.data.code==200){
this.showChatBox=true
}else{
this.showChatBox=false
}
})
}catch {
//
}finally {
//
}
},
//获取群列表
async getRooms(){
try {
await axios.post("/getAllRooms",{},function (){}).then(res=>{
if(res.data.code==200){
this.topicData=[]
let data = res.data.data
if(data.length>0){
data.forEach(item=>{
if(item.payload.memberIdList.length>0){
this.topicData.push({
...item
})
}
})
this.topicData[0].isRead=1
this.currentTopInfo=this.topicData.length>0?this.topicData[0]:{}
this.finalId=''
this.getChatRecord()
}else{
this.topicData=[]
}
}else{
this.topicData=[]
}
})
}catch {
//
}finally {
this.loading=false
}
},
//修改状态
async updateMsgType(){
await axios.post('/updateMsgType', {
roomId: this.currentTopInfo.payload.id,
})
},
//查询聊天记录
async getChatRecord(){
this.messLoading=true
let data=[]
try {
await axios.post('/getRoomsHistory',{
roomName:this.currentTopInfo.payload.topic,
pageSize:25,
lastId:this.finalId,
roomId:this.currentTopInfo.id
}).then(res=>{
if(res.data.code==200){
this.msgData=''
data=res.data.data
if(data.length>0){
data.forEach(item=>{
this.messageData.unshift({
receivTime:item.receivTime,
avatar:item.avatar,
nickName:item.userName,
content:item.msgContent,
bigImg:item.msgType==2?item.msgContent.replace('compressor_',''):'',
contentType:item.msgType, //1 文字 2 图片
isSelf:item.isSelf==1?true:false //false 不是自己 true:是自己
})
})
//首次加载滚动条滚动到最底部
if (!this.finalId) {
// 首次渲染后获取scrollHeight并滑动到底部。
setTimeout(() => {
this.scrollToBottom('auto')
}, 0)
}
//查看更多
const el = this.$refs['dataListBox']
this.scrollHeight = el ? el.scrollHeight : 0
if (this.finalId) {
// 滚动到加载前的位置
setTimeout(() => {
const currScrollHeight = el.scrollHeight
el.scrollTo(0, currScrollHeight - this.scrollHeight)
}, 0)
}
}else{
this.msgData='暂无更多消息'
}
}else {
this.msgData=''
}
})
}catch {
//
}finally {
setTimeout(()=>{
this.showEmpty=false
//最新id
this.finalId=data.length>0?data[data.length-1].id:''
},200)
this.$nextTick(()=>{
if(this.activeNode=='first'){
this.$refs.refInput.focus() // 设置焦点
}
})
this.messLoading=false
}
},
//搜索群
searchFun() {
if(!this.searchValue){
this.$message.warning('请输入群名称')
return
}
let restaurants = this.topicData;
let resultList = [];
restaurants.forEach((item) => {
if (item.payload.topic.indexOf(this.searchValue) > -1) {
resultList.push(item);
}
});
this.searchDataList=resultList||[]
if(resultList.length<=0){
this.searchValue=''
this.$message.warning('暂无数据')
}
},
//删除图片
deleteImg(index){
this.fileList.splice(index,1)
},
//消息默认展示最新(div 永远滑到最底部)
scrollToBottom(type) {
setTimeout(()=>{
this.$nextTick(() => {
let div = document.getElementById('data-list-content')
div.scrollTo({
top: div.scrollHeight,
behavior: type?type:"smooth"
});
})
},100)
},
//判断上传的文件是否是图片格式
isAssetTypeAnImage(ext) {
return [
'png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff'].
indexOf(ext.toLowerCase()) !== -1;
},
//文件转base64
transFun(raw){
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.readAsDataURL(raw);
let realType=raw.type
let lastIndex=realType.lastIndexOf('/')+1
let fileType=realType.substring(lastIndex)
reader.onload = function (e) {
resolve({
fileName:new Date().getTime()+'_image_normal'+'.'+fileType,
path:e.target.result,
})
}
reader.onerror = err => reject(err);
})
},
//选择图片
changeFile(param){
let filePath = param.name;
//获取最后一个.的位置
let index= filePath.lastIndexOf(".");
//获取后缀
let ext = filePath.substr(index+1);
//判断是否是图片
if(this.isAssetTypeAnImage(ext)){
let quality=0.7
if(param.raw.size<1000*1000){ //小于1M
quality=0.5
}else if(param.raw.size<5000*1000){ //小于5M
quality=0.4
}else if(param.raw.size<10000*1000){ //小于10M
quality=0.3
}else { //大于10M
quality = 0.1
}
this.compressUpload(param.raw,{
width:150,
quality:quality
}).then(res=>{
this.fileList.push({
...param,
slPath:res
})
})
}
},
/* 图片压缩方法-canvas压缩 压缩图片--根据 宽 高 画质压缩图片*/
compressUpload(file, config) {
let read = new FileReader();
read.readAsDataURL(file);
return new Promise((resolve, reject) => {
// 生成canvas
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
read.onload = function (e) {
let img = new Image();
img.src = e.target.result;
img.onload = function () {
let w = this.width;
let h = this.height;
let scale = w / h;
w = config.width || config.height * scale || w;
h = config.height || config.width / scale || h;
// 最大宽高如有限制时的处理
w = config.maxWidth && w > config.maxWidth ? config.maxWidth : w;
h = config.maxHeight && h > config.maxHeight ? config.maxHeight : h;
w = Math.min(w, h * scale) || w;
h = Math.min(h, w / scale) || h;
let quality = 0.7; // 默认图片质量
// 创建属性节点
let anw = document.createAttribute("width");
anw.nodeValue = w;
let anh = document.createAttribute("height");
anh.nodeValue = h;
canvas.setAttributeNode(anw);
canvas.setAttributeNode(anh);
ctx.drawImage(this, 0, 0, w, h);
if (config.quality && config.quality <= 1 && config.quality > 0) {
quality = config.quality;
}
let realType=file.type
let lastIndex=realType.lastIndexOf('/')
let fileType=realType.substring(lastIndex)
let base64 = canvas.toDataURL("image"+fileType, quality);
// 回调函数返回base64的值,也可根据自己的需求转换
resolve(base64);
canvas = null;
};
};
});
},
}
})
</script>
</html>