直接贴出前端代码和@的交互逻辑代码
将页面内需要引用的js和css文件从官网下载,然后引入,直接打开这个demo就可以了
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="at/jquery.atwho.css" />
<link rel="stylesheet" href=" https://apps.bdimg.com/libs/bootstrap/3.0.3/css/bootstrap.min.css" type="text/css"/>
<script src="https://apps.bdimg.com/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="https://apps.bdimg.com/libs/bootstrap/3.0.3/js/bootstrap.min.js"></script>
<script src="at/jquery.caret.js"></script>
<script src="at/jquery.atwho.js"></script>
<style>
body{
display: flex;
flex-direction: row;
justify-content: center;
}
.at_container{
width: 120px;
border: 1px solid rgba(0,0,0,.15);
border-radius: 4px;
position: absolute;
bottom:15px;
display:none;
z-index: 9999;
background-color: #fff;
height: auto;
}
ul{
margin-bottom: 0px;
}
.at_container li{
display: block;
display: flex;
flex-direction: column;
justify-content: center;
padding-left:10px;
font-size: 14px;
font-weight: 400;
white-space: nowrap;
cursor: pointer;
border-bottom: 1px solid #e5e5e5;
height: 35px;
}
.at_container li:HOVER{
background-color: #3366FF;
color: #fff;
}
</style>
</head>
<body>
<div style="position: relative;margin-top: 200px;">
<div class='at_container' id="at_container">
<ul id='list' style="overflow-y:auto; padding-left: 0px">
</ul>
</div>
<div id="info" class='form-control' style="width: 400px;height: 100px; overflow-y: auto;" contenteditable="true" >
</div>
</div>
</body>
<script>
//后端返回的整个@数组列表
var AllList = [{"name":"熊大","id":"1"},
{"name":"熊二","id":"2"},
{"name":"熊三","id":"3"},
{"name":"熊四","id":"4"},
{"name":"熊五","id":"5"},
{"name":"熊六","id":"6"},
{"name":"熊七","id":"7"},
{"name":"熊八","id":"8"},
{"name":"熊九","id":"9"},
{"name":"熊老幺","id":"10"}];
//后端返回的最近@过的人员列表
var freList = [{"name":"熊二","id":"1"},
{"name":"熊三","id":"2"},
{"name":"熊八","id":"3"}]
//初始化查询模式,默认在输入时处于非查询的状态下
var searchModel = false;
//初始化查询字符串在消息窗的index
var searchStartIndex = 0;
$(function(){
$("#info").keyup(function (e) {
//如果按下@则此时进入查询模式,给searchModel和searchStartIndex赋值
if(e.shiftKey&&e.keyCode==50){
searchModel = true;
searchStartIndex = $('#info').text().length;
}
//删除内容均导致退出search模式
if (e.keyCode == 8 || e.keyCode == 46) {
searchModel = false;
//关闭@列表
$('#at_container').css('display', 'none');
//判定删除后文本最末字符是否是@,如果是则进入search模式
if($("#info").text().substring($("#info").text().length-1,$("#info").text().length)=='@'){
searchModel = true;
searchStartIndex = $('#info').text().length;
}
}
//如果不在查询模式下,直接返回
if(!searchModel){
return;
}
//截取待查询的字符串
var searchString = $("#info").text().substring(searchStartIndex,$("#info").text().length);
if(searchString==''&&freList.length>0){
//如果查询的字符串为空,即刚摁下@符号时,此时默认显示最近@过的人员列表
pushToHtml(freList,function(){
$('#at_container').css('display', 'block');
});
}else{
//否则从整个人员列表查询结果,查询的算法使用简单的indexOf函数
var rl = new Array();
for(var i in AllList){
if(rl.length>=5)break;
if(AllList[i].name.indexOf(searchString)!=-1)
rl.push(AllList[i]);
}
if(rl.length>=1){//显示
pushToHtml(rl,function(){
$('#at_container').css('display', 'block');
});
}else{//隐藏
$('#at_container').css('display', 'none');
}
}
//显示@列表并设置偏移位置
var offset = $('#info').caret('offset');
var position = $('#info').caret('position');
console.log(position)
var editorWidth = $('#info').width();
var atWidth = $('#at_container').width();
// 当右侧的空间不够显示@弹窗时,显示在左侧
// 所以需要对右侧剩余空间的大小进行判断
if (editorWidth - position.left < atWidth) {
$('#at_container').css('left', position.left - atWidth - 15);
} else {
$('#at_container').css('left', position.left+10);
}
$('#at_container').css('bottom',$('#info').height() - position.top);
$('#at_container').scrollTop(0);
});
//
$("#info").click(function(e){
if(searchModel){
var searchString = $("#info").text().substring(searchStartIndex,$("#info").text().length);
if(searchString==''&&freList.length>0){
pushToHtml(freList,function(){
$('#at_container').css('display', 'block');
});
}else{
var rl = new Array();
for(var i in AllList){
if(rl.length>=5)break;
if(AllList[i].name.indexOf(searchString)!=-1)
rl.push(AllList[i]);
}
if(rl.length>=1){
pushToHtml(rl,function(){
$('#at_container').css('display', 'block');
});
}else{
$('#at_container').css('display', 'none');
}
}
//显示@列表并设置偏移位置
var offset = $('#info').caret('offset');
var position = $('#info').caret('position');
console.log(position)
var editorWidth = $('#info').width();
var atWidth = $('#at_container').width();
// 当右侧的空间不够显示@弹窗时,显示在左侧
// 所以需要对右侧剩余空间的大小进行判断
if (editorWidth - position.left < atWidth) {
$('#at_container').css('left', position.left - atWidth - 15);
} else {
$('#at_container').css('left', position.left+10);
}
$('#at_container').css('bottom',$('#info').height() - position.top);
$('#at_container').scrollTop(0);
}
})
// 点击@窗体外的区域时,关闭@弹窗
$(document).mouseup(function(e) {
var pop = $('#at_container');
if(!pop.is(e.target) && pop.has(e.target).length === 0) {
$('#at_container').css('display', 'none');
}
});
})
function pushToHtml(list,callback){
var html = "";
for(var i in list){
html += "<li data-id='"+list[i].id+"'>"+list[i].name+"</li>";
}
$("#list").html(html);
registerClickOnLi();
return (callback && typeof(callback)==="function") && callback();
}
function registerClickOnLi(){
$('#at_container ul li').unbind('click').click(function (e) {
e.stopPropagation();
var id = $(this).data('id');
var name = $(this).text();
$('#at_container').css('display', 'none');
var html = $("#info").html().substring(0,$("#info").html().lastIndexOf("@")+1);
html += '<span contenteditable="false" data-id="'+id+'">'+name+'</span> ';
$("#info").html(html);
$('#info span[data-id="'+id+'"]').attr('contenteditable', false);
keepLastIndex(document.getElementById("info"));
});
}
function keepLastIndex(obj) {
if (window.getSelection) {//ie11 10 9 ff safari
obj.focus(); //解决ff不获取焦点无法定位问题
var range = window.getSelection();//创建range
range.selectAllChildren(obj);//range 选择obj下所有子内容
range.collapseToEnd();//光标移至最后
}
else if (document.selection) {//ie10 9 8 7 6 5
var range = document.selection.createRange();//创建选择对象
range.moveToElementText(obj);//range定位到obj
range.collapse(false);//光标移至最后
range.select();
}
}
</script>
</html>