最近聊天列表、emoji表情替换
一、数据库
表名:ml_hxim_msg
id:主键id
from:发送人id
to:接收人id
data:数据内容
send_time:发送时间
二、SQL
$user_id = '';
$sql = '(SELECT * FROM
(SELECT * FROM ml_hxim_msg WHERE send_time IN
( SELECT MAX( send_time ) FROM ml_hxim_msg
WHERE `from` = ' . $user_id . ' OR `to` = ' . $user_id . '
GROUP BY
CONCAT(IF( `from` > `to`, `from`, `to` ),IF( `from` < `to`, `from`, `to` )))
AND ( `from` = ' . $user_id . ' OR `to` = ' . $user_id . ' )
ORDER BY id DESC ) AS c
LEFT JOIN ml_user AS u ON c.`from`=u.id or c.`to`=u.id
WHERE u.id!=' . $user_id . '
GROUP BY c.send_time ASC) as ml_hxim_msg';
// $datas = DB::select($sql);//去掉sql前后括号 as ml_hxim_msg
$datas = DB::table(DB::raw($sql))->paginate(10);//laravel 分页
return $datas;
H5聊天页面
后端
public function imMsg(Request $request)
{
$from = $request->get('from');
$to = $request->get('to');
$start_at = $request->get('start_at');
$end_at = $request->get('end_at');
$build = HximMsg::with('fromInfo:id,nickname,avatar,sex,status')
->with('toInfo:id,nickname,avatar,sex,status')
->where(function ($query) use ($from, $to, $start_at, $end_at) {
$query->where([
['from', '=', $from],
['to', '=', $to],
]);
if ($start_at) {
$query->whereBetween('send_time', [strtotime($start_at), strtotime($end_at)]);
}
})
->orWhere(function ($query) use ($from, $to, $start_at, $end_at) {
$query->where([
['from', '=', $to],
['to', '=', $from],
]);
if ($start_at) {
$query->whereBetween('send_time', [strtotime($start_at), strtotime($end_at)]);
}
});
$datas = $build
->orderBy('send_time', 'ASC')->paginate(10);
$filter = [
'from' => $from,
'to' => $to,
'start_at' => $start_at,
'end_at' => $end_at,
];
// return $datas;
if ($request->method() == 'GET') {
return view('mladmin.im_msg', ['datas' => $datas, 'filter' => $filter]);
} else {
return $datas;
}
}
前端部分代码
腾讯IM的聊天记录&实时语音自定义消息
//css
<style>
* {
box-sizing: border-box;
}
p, ul, li {
margin: 0;
padding: 0;
}
.clearfix::after {
content: "";
display: block;
clear: both;
}
#iphone {
width: 351px;
height: 692px;
/*background: url(images/iphone6.png) no-repeat;*/
margin: 0 auto;
padding: 20px 0 95px;
}
#wrap {
width: 400px;
height: 600px;
margin: 0px auto;
left: 19px;
top: 80px;
background: linear-gradient(transparent, rgba(0, 0, 0, .2)), url(images/timg.jpg) no-repeat 50%;
}
#div {
width: 100%;
height: 100%;
margin: 0 auto;
padding: 10px;
overflow: auto;
}
#box {
float: left;
}
#btn {
background: #6abe83;
border: none;
width: 44px;
height: 34px;
margin: 10px 5px 0 0;
border-radius: 6px;
outline: none;
}
#inp {
width: 200px;
height: 36px;
margin: 10px 5px 0 0;
border-radius: 6px;
border: none;
outline: none;
padding: 10px;
}
#imgWrap {
float: left;
margin: 10px 5px 0 10px;
}
#img {
width: 36px;
height: 36px;
border-radius: 6px;
}
.send_img {
width: 100%;
height: 100%;
border-radius: 6px;
}
.fr {
float: right;
}
#ul li {
width: 100%;
list-style: none;
margin-bottom: 10px;
}
.right {
float: right;
}
.left {
float: left;
}
#ul li.left img, #ul li.left div {
float: left;
}
#ul li.right img, #ul li.right div {
float: right;
}
#ul li.right div {
margin-left: 10px;
}
#ul li.right div, #ul li.left div {
/*height: 36px;*/
/*line-height: 36px;*/
}
#ul li.left div {
margin-right: 10px;
}
.inps, .inpss {
background: #6abe83;
/*height: 20px;*/
max-width: 78%;
padding: 6px 10px;
border-radius: 8px;
position: relative;
white-space: normal;
word-break: break-all;
}
.inps::after, .inpss::after {
content: "";
position: absolute;
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
top: 9px;
}
.inps::after {
border-right: 6px solid #6abe83;
left: -6px;
}
.inpss::after {
border-left: 6px solid #6abe83;
right: -6px;
}
</style>
//H5
<div class="col-lg-12">
<form class="form-horizontal">
<div class="form-group">
<label class="col-lg-2 control-label">搜索时间:</label>
<div class="col-lg-3" style="display: flex">
<input type="text" name="start_at" class="form_datetime form-control"
value="{{$filter['start_at']}}"/>—
<input type="text" name="end_at" class="form_datetime form-control"
value="{{$filter['end_at']}}"/>
<input type="hidden" name="from"
value="{{$filter['from']}}"/>
<input type="hidden" name="to"
value="{{$filter['to']}}"/>
<input type="hidden" class="next_url"
value="{{$datas->nextPageUrl()}}"/>
</div>
</div>
<div class="form-group-lg">
<div class="col-lg-offset-2 col-lg-2">
<button type="submit" id="search_submit" class="btn btn-primary">开始搜索</button>
<a href="javascript:flush_condition();" class="btn btn-primary">清空条件</a>
</div>
</div>
</form>
<div id="iphone">
<div id="wrap">
<div id="div" class="clearfix">
<ul id="ul" class="clearfix">
@foreach($datas as $data)
<div style="text-align:center;color: grey">{{ date('Y-m-d H:i:s', $data['send_time']) }}</div>
<li @if($data->from == $filter['from']) class='left' @else class='right' @endif>
<div>
@if($data->from == 'administrator')
<img id="img"
src="https://imagenew.meilimei.com/eabeedf3a9874c148785c400973116cd">
@else
<img id="img" src="{{$data->fromInfo->avatar}}" alt="">
@endif
</div>
<div @if($data->from == $filter['from']) class='inps' @else class='inpss' @endif>
@foreach(json_decode($data->data, true)['MsgBody'] as $val)
@if($val['MsgType'] == 'TIMTextElem')
<span class="send_text" style="display: flex;">
{{$val['MsgContent']['Text']}}
</span>
@endif
@if($val['MsgType'] == 'TIMSoundElem' && $val['MsgContent']['Download_Flag'] == 2)
<div class="r_yuyin" style="cursor:pointer;" data-time="">
<span>{{$val['MsgContent']['Second']}}</span>''
<audio preload="auto" hidden="true">
<source src="{{$val['MsgContent']['Url']}}"
type="audio/mpeg">
</audio>
</div>
@endif
@if($val['MsgType'] == 'TIMImageElem')
<img class="send_img"
src="{{$val['MsgContent']['ImageInfoArray'][0]['URL']}}"
alt="">
@endif
@if($val['MsgType'] == 'TIMCustomElem')
@if(isset(json_decode($val['MsgContent']['Data'], true)['actionType']))
@if(json_decode($val['MsgContent']['Data'], true)['actionType'] == 1)
发起通话
@elseif(json_decode($val['MsgContent']['Data'], true)['actionType'] == 2)
取消通话
@elseif(json_decode($val['MsgContent']['Data'], true)['actionType'] == 3)
已接听
@endif
@endif
@endif
@if($val['MsgType'] == 'TIMFileElem' && $val['MsgContent']['Download_Flag'] == 2)
文件:
<a href="{{$val['MsgContent']['Url']}}">{{$val['MsgContent']['FileName']}}</a>
@endif
@endforeach
</div>
</li>
@endforeach
<span class="see_more" style="cursor:pointer;color: darkgreen;margin-left: 40%">点击查看更多</span>
</ul>
</div>
</div>
</div>
</div>
//JS
<script type="text/javascript">
$(function () {
if ($('.next_url').val() == '') {
$('.see_more').remove();
}
var js = null;
//录音播放
$(".r_yuyin").click(function () {
let audio = $(this).children("audio")[0],
time_span = $(this).children("span"),
time = time_span.html(),
times = time;
//先暂停其他的
// if ($(this).siblings().find("audio")[0]) {
// $(this).siblings().find("audio")[0].pause();
// console.log('暂停其他')
// }
if (audio.paused) {
audio.play(); // 这个就是播放
console.log('播放');
var inter = setInterval("run()", 1000);
} else {
audio.pause(); // 这个就是暂停
console.log('暂停');
}
run = function run() {
time--;
time_span.html(time);
if (time < 0) {
clearInterval(inter);
time_span.html(times);
}
}
});
emojiUrl = 'https://imgcache.qq.com/open/qcloud/tim/assets/emoji/',
emojiMap = {
'[NO]': 'emoji_0@2x.png',
'[OK]': 'emoji_1@2x.png',
'[下雨]': 'emoji_2@2x.png',
'[么么哒]': 'emoji_3@2x.png',
'[乒乓]': 'emoji_4@2x.png',
'[便便]': 'emoji_5@2x.png',
'[信封]': 'emoji_6@2x.png',
'[偷笑]': 'emoji_7@2x.png',
'[傲慢]': 'emoji_8@2x.png',
'[再见]': 'emoji_9@2x.png',
'[冷汗]': 'emoji_10@2x.png',
'[凋谢]': 'emoji_11@2x.png',
'[刀]': 'emoji_12@2x.png',
'[删除]': 'emoji_13@2x.png',
'[勾引]': 'emoji_14@2x.png',
'[发呆]': 'emoji_15@2x.png',
'[发抖]': 'emoji_16@2x.png',
'[可怜]': 'emoji_17@2x.png',
'[可爱]': 'emoji_18@2x.png',
'[右哼哼]': 'emoji_19@2x.png',
'[右太极]': 'emoji_20@2x.png',
'[右车头]': 'emoji_21@2x.png',
'[吐]': 'emoji_22@2x.png',
'[吓]': 'emoji_23@2x.png',
'[咒骂]': 'emoji_24@2x.png',
'[咖啡]': 'emoji_25@2x.png',
'[啤酒]': 'emoji_26@2x.png',
'[嘘]': 'emoji_27@2x.png',
'[回头]': 'emoji_28@2x.png',
'[困]': 'emoji_29@2x.png',
'[坏笑]': 'emoji_30@2x.png',
'[多云]': 'emoji_31@2x.png',
'[大兵]': 'emoji_32@2x.png',
'[大哭]': 'emoji_33@2x.png',
'[太阳]': 'emoji_34@2x.png',
'[奋斗]': 'emoji_35@2x.png',
'[奶瓶]': 'emoji_36@2x.png',
'[委屈]': 'emoji_37@2x.png',
'[害羞]': 'emoji_38@2x.png',
'[尴尬]': 'emoji_39@2x.png',
'[左哼哼]': 'emoji_40@2x.png',
'[左太极]': 'emoji_41@2x.png',
'[左车头]': 'emoji_42@2x.png',
'[差劲]': 'emoji_43@2x.png',
'[弱]': 'emoji_44@2x.png',
'[强]': 'emoji_45@2x.png',
'[彩带]': 'emoji_46@2x.png',
'[彩球]': 'emoji_47@2x.png',
'[得意]': 'emoji_48@2x.png',
'[微笑]': 'emoji_49@2x.png',
'[心碎了]': 'emoji_50@2x.png',
'[快哭了]': 'emoji_51@2x.png',
'[怄火]': 'emoji_52@2x.png',
'[怒]': 'emoji_53@2x.png',
'[惊恐]': 'emoji_54@2x.png',
'[惊讶]': 'emoji_55@2x.png',
'[憨笑]': 'emoji_56@2x.png',
'[手枪]': 'emoji_57@2x.png',
'[打哈欠]': 'emoji_58@2x.png',
'[抓狂]': 'emoji_59@2x.png',
'[折磨]': 'emoji_60@2x.png',
'[抠鼻]': 'emoji_61@2x.png',
'[抱抱]': 'emoji_62@2x.png',
'[抱拳]': 'emoji_63@2x.png',
'[拳头]': 'emoji_64@2x.png',
'[挥手]': 'emoji_65@2x.png',
'[握手]': 'emoji_66@2x.png',
'[撇嘴]': 'emoji_67@2x.png',
'[擦汗]': 'emoji_68@2x.png',
'[敲打]': 'emoji_69@2x.png',
'[晕]': 'emoji_70@2x.png',
'[月亮]': 'emoji_71@2x.png',
'[棒棒糖]': 'emoji_72@2x.png',
'[汽车]': 'emoji_73@2x.png',
'[沙发]': 'emoji_74@2x.png',
'[流汗]': 'emoji_75@2x.png',
'[流泪]': 'emoji_76@2x.png',
'[激动]': 'emoji_77@2x.png',
'[灯泡]': 'emoji_78@2x.png',
'[炸弹]': 'emoji_79@2x.png',
'[熊猫]': 'emoji_80@2x.png',
'[爆筋]': 'emoji_81@2x.png',
'[爱你]': 'emoji_82@2x.png',
'[爱心]': 'emoji_83@2x.png',
'[爱情]': 'emoji_84@2x.png',
'[猪头]': 'emoji_85@2x.png',
'[猫咪]': 'emoji_86@2x.png',
'[献吻]': 'emoji_87@2x.png',
'[玫瑰]': 'emoji_88@2x.png',
'[瓢虫]': 'emoji_89@2x.png',
'[疑问]': 'emoji_90@2x.png',
'[白眼]': 'emoji_91@2x.png',
'[皮球]': 'emoji_92@2x.png',
'[睡觉]': 'emoji_93@2x.png',
'[磕头]': 'emoji_94@2x.png',
'[示爱]': 'emoji_95@2x.png',
'[礼品袋]': 'emoji_96@2x.png',
'[礼物]': 'emoji_97@2x.png',
'[篮球]': 'emoji_98@2x.png',
'[米饭]': 'emoji_99@2x.png',
'[糗大了]': 'emoji_100@2x.png',
'[红双喜]': 'emoji_101@2x.png',
'[红灯笼]': 'emoji_102@2x.png',
'[纸巾]': 'emoji_103@2x.png',
'[胜利]': 'emoji_104@2x.png',
'[色]': 'emoji_105@2x.png',
'[药]': 'emoji_106@2x.png',
'[菜刀]': 'emoji_107@2x.png',
'[蛋糕]': 'emoji_108@2x.png',
'[蜡烛]': 'emoji_109@2x.png',
'[街舞]': 'emoji_110@2x.png',
'[衰]': 'emoji_111@2x.png',
'[西瓜]': 'emoji_112@2x.png',
'[调皮]': 'emoji_113@2x.png',
'[象棋]': 'emoji_114@2x.png',
'[跳绳]': 'emoji_115@2x.png',
'[跳跳]': 'emoji_116@2x.png',
'[车厢]': 'emoji_117@2x.png',
'[转圈]': 'emoji_118@2x.png',
'[鄙视]': 'emoji_119@2x.png',
'[酷]': 'emoji_120@2x.png',
'[钞票]': 'emoji_121@2x.png',
'[钻戒]': 'emoji_122@2x.png',
'[闪电]': 'emoji_123@2x.png',
'[闭嘴]': 'emoji_124@2x.png',
'[闹钟]': 'emoji_125@2x.png',
'[阴险]': 'emoji_126@2x.png',
'[难过]': 'emoji_127@2x.png',
'[雨伞]': 'emoji_128@2x.png',
'[青蛙]': 'emoji_129@2x.png',
'[面条]': 'emoji_130@2x.png',
'[鞭炮]': 'emoji_131@2x.png',
'[风车]': 'emoji_132@2x.png',
'[飞吻]': 'emoji_133@2x.png',
'[飞机]': 'emoji_134@2x.png',
'[饥饿]': 'emoji_135@2x.png',
'[香蕉]': 'emoji_136@2x.png',
'[骷髅]': 'emoji_137@2x.png',
'[麦克风]': 'emoji_138@2x.png',
'[麻将]': 'emoji_139@2x.png',
'[鼓掌]': 'emoji_140@2x.png',
'[龇牙]': 'emoji_141@2x.png'
};
//表情替换
reg = /\[.+?\]/g;
$('.send_text').each(function () {
var str = $(this).text();
str = str.replace(reg, function (a, b) {
return "<img style='width: 20px;height: 20px;' src=" + emojiUrl + emojiMap[a] + " />";
});
$(this).html(str);
});
});
$('.form_datetime').datetimepicker(
{
language: 'zh-CN',
format: 'yyyy-mm-dd HH:ii',
autoclose: true,
todayBtn: true,
minView: 0
}
);
//清空时间搜索
function flush_condition() {
$("input[name='start_at']").val('');
$("input[name='end_at']").val('');
}
//点击查看更多
$('.see_more').click(function () {
data = '&from=' + $("input[name='from']").val() + '&to=' + $("input[name='to']").val() + '&start_at=' + $("input[name='start_at']").val() + '&end_at=' + $("input[name='end_at']").val();
url = $('.next_url').val();
if (url) {
url = url + data;
}
console.log(url);
$.ajax({
type: "POST",
url: url,
success: function (data) {
console.log(data);
if (data['data']) {
$.each(data['data'], function (index, value) {
$('#ul').append('<div style="text-align:center;color: grey">' + UnixToDate(value['send_time'], true, 8) + '</div>');
if (value['from'] == $("input[name='from']").val()) {
$li = "<li class='left'><div>";
} else {
$li = "<li class='right'><div>";
}
if (value['from'] == 'administrator') {
$li += '<img id="img" src="https://imagenew.meilimei.com/eabeedf3a9874c148785c400973116cd">';
} else {
$li += '<img id="img" src="' + value['from_info']['avatar'] + '">';
}
$li += '</div>';
if (value['from'] == $("input[name='from']").val()) {
$li += "<div class='inps'>";
} else {
$li += "<div class='inpss'>";
}
value['data'] = JSON.parse(value['data']);
$.each(value['data']['MsgBody'], function (key, val) {
if (val['MsgType'] == 'TIMTextElem') {
text = val['MsgContent']['Text'].replace(reg, function (a, b) {
return "<img style='width: 20px;height: 20px;' src=" + emojiUrl + emojiMap[a] + " />";
});
$li += '<span class="send_text" style="display: flex;">' + text + '</span>';
}
if (val['MsgType'] == 'TIMSoundElem' && val['MsgContent']['Download_Flag'] == 2) {
$li += '<div class="r_yuyin" style="cursor:pointer;">' +
'<span>' + val["MsgContent"]["Second"] + "</span>''" +
'<audio preload="auto" hidden="true">' +
'<source src="' + val['MsgContent']['Url'] + '" type="audio/mpeg">' +
'</audio>' +
'</div>';
}
if (val['MsgType'] == 'TIMImageElem') {
$li += '<img class="send_img" src="' + val['MsgContent']['ImageInfoArray'][0]['URL'] + '">';
}
if (val['MsgType'] == 'TIMCustomElem') {
// console.log(JSON.parse(val['MsgContent']['Data'])['actionType']);
if (JSON.parse(val['MsgContent']['Data'])['actionType'] == 1) {
$li += '发起通话';
} else if (JSON.parse(val['MsgContent']['Data'])['actionType'] == 2) {
$li += '取消通话';
} else if (JSON.parse(val['MsgContent']['Data'])['actionType'] == 3) {
$li += '已接听';
}
}
if (val['MsgType'] == 'TIMFileElem' && val['MsgContent']['Download_Flag'] == 2) {
$li += '文件:<a href="' + val['MsgContent']['Url'] + '">' + val['MsgContent']['FileName'] + '</a>';
}
});
$li += '</div></li>';
$('#ul').append($li);
if (data['next_page_url'] == null) {
$('.see_more').remove();
} else {
$('#ul').append($('.see_more'));
$('.next_url').val(data['next_page_url']);
}
});
}
}
});
});
/**
* 时间戳转换日期
* @param <int> unixTime 待时间戳(秒)
* @param <bool> isFull 返回完整时间(Y-m-d 或者 Y-m-d H:i:s)
* @param <int> timeZone 时区
*/
function UnixToDate(unixTime, isFull, timeZone) {
if (typeof (timeZone) == 'number') {
unixTime = parseInt(unixTime) + parseInt(timeZone) * 60 * 60;
}
var time = new Date(unixTime * 1000);
var ymdhis = "";
ymdhis += time.getUTCFullYear() + "-";
ymdhis += (time.getUTCMonth() + 1) < 10 ? '0' + (time.getUTCMonth() + 1) + "-" : (time.getUTCMonth() + 1) + "-";
ymdhis += time.getUTCDate() < 10 ? '0' + time.getUTCDate() : time.getUTCDate();
if (isFull === true) {
ymdhis += " " + time.getUTCHours() < 10 ? ' 0' + time.getUTCHours() + ":" : " " + time.getUTCHours() + ":";
ymdhis += time.getUTCMinutes() < 10 ? '0' + time.getUTCMinutes() + ":" : time.getUTCMinutes() + ":";
ymdhis += time.getUTCSeconds() < 10 ? '0' + time.getUTCSeconds() : time.getUTCSeconds();
}
return ymdhis;
}
</script>