第低第一次开发PHP项目,负责的事网站内部系统通知显示、回复、删除等功能的站内信小模块,基本上成功实现了想得到的功能,也是第一次写分享,感觉看别人写的东西的时候总感觉没有一定的基础没办法深入的理解,这次打算好好啰嗦一下,把感受和积累都说出来。
第啊首先介绍了开发的整体思路,接着是取数据并显示在页面上的方法实现,然后是AJAX动态删除和重定向删除两个方法的实现,再是消息回复方法和动态添加条目的实现。最后附上了CSS与修改过的PAGE类,基本上复制下来就能用了。
第低开发的开始是先看了2天的thinkPHP3.1的技术文档,然后也算是入门了,以前学的JAVA,虽然语法不熟,但是意思都能猜出来,也就开始了第一次实战,所以我觉得我下面要讲的可能很low,还有好多错误,但是菜鸟看肯定感同身受。
第低先是建数据库innerMail表
第低id是自动生成的主键;userid是与此消息相关的用户(站内信只有用户和后台两个交流对象);time是操作时间,只显示到分;type是此条消息的类型,1是系统通知,2是用户回复;status是消息的状态,已读=1,未读=0,自己发送的回复=2;replyid指如果是回复,就写入回复的那条消息的id,系统消息默认是0。好了,弄得有点点复杂,不过用起来逻辑满清晰的,框架用的是TP3.1,把自己的数据库配置到config文件里面去,就可以了,没有太复杂的业务逻辑,Model不用专门建立。
第低接下来我是去做的View模版:message.html,最终效果是这样的
第低好吧,右下角被挡住了,这不是问题,功能上基本都有,优化嘛,不是这次要考虑的东西。虽然刚开始做的时候,完全是想到啥就做啥,最后就知道还是要先把功能效果设计好了再编码才是合理的,一个人的敏捷开发简直逗。功能主要是,显示系统通知,未读的样式比较明显,已读的偏灰,回复类型有自己的样式。每条消息都可以回复和删除,即有redirect重定向也有AJAX异步显示。下面有thinkPHP分页工具类的调用,对源代码做了简单的修改,样式操作起来更舒服一些。
第低先是取数据显示,用的是TP的volist标签实现的,因为是分成了系统通知和回复两种消息类型,后台分两次取出两组数据传过来,所以做的是两个volist的嵌套循环,同时也把各个DIV层和<li>标签也遍历显示了,最后输出分页工具的<div>,每页设定是显示六条系统通知,回复类型的消息都不计入。下面是控制器UserAction里面的message()方法的代码:
<pre name="code" class="php">function message()
{
//分页查询
$Dao = M("Innermail");//tp大法好
$uid = $this->getUserId();//早就定义好的方法,取当前登录用户的id
$condition1['userid'] = $uid;
$condition1['type'] = 1; //不计算回复类型的消息数
$count = $Dao->where($condition1)->count();//连续操作查询有多少钱系统通知
import("ORG.Util.Page");//导入TP框架extend包里面的page工具类,我的源代码这里出现了三个,只有一个里面没有报错的是能用的,要分清楚
$p = new Page($count, 6);//每页显示六条通知的分页类实例化
<span> </span><span style="white-space:pre"> </span>//这里定义了分页工具类的显示格式,当然我把工具类也修改了一下,首页尾页上一下一页都是一直显示,各个标签都给了自己的id或者class
<span style="font-family: Arial, Helvetica, sans-serif;">$p->setConfig('header.css', "<br><span class='pagehead'>共 %totalPage% 页 / %totalRow% 条消息</span>");</span>
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"> </span>//源代码里面用的是header.css,和百度的header有点不一样</span><pre name="code" class="php"> $p->setConfig('first', '首页');
$p->setConfig('last', '尾页');
$p->setConfig('theme', "%first% %upPage% %linkPage% %downPage% %end% %header.css% ");//这里可以设置你自己想要的各个按钮的顺序,看一下源代码的各个属性就好
//提交分页tool
$page = $p->show();
// 当前用户的接受到的消息查询,时间逆序,最新的在前面
$list1 = $Dao->order('time DESC')->limit($p->firstRow . ',' . $p->listRows)->where($condition1)->select();
//回复类型消息查询,时间顺序,最新的在后面
$condition2['userid'] = $uid;
$condition2['type'] = 2;
$list2 = $Dao->order('time ASC')->where($condition2)->select();
// 模版赋值
$this->assign('page', $page);
$this->assign('list1', $list1);
$this->assign('list2', $list2);
//更改当前消息列表所有的未阅读消息的阅读状态为已读=1/未读=0/自己发送的回复=2
$data['status'] = 1;
$Dao->where('status=0')->save($data);
//输出
$this->display();
}
啊啊这样在进入页面的同时,模版就获得了消息的数据$list1,$list2,以及分页工具$page了,其中page是很多行html代码然后用volist遍历显示数据,html代码如下:
<body>
<div>
<div class="messageList">
<!--站内信标题栏-->
<div class="messageTitle">
<ul>
<li class="messagelogo"><img src=""></li>
<!--占位对齐用-->
<li class="messagecontenttitle">消息内容</li>
<li class="messagetime">接收时间</li>
</ul>
</div>
<div style="clear: both;height: 20px"></div>
<!--站内信内容-->
<div class="messageitems">
<volist name="list1" id="vo">
<!--循环$list1显示的系统通知子条目-->
<div class="messageitem" id="{$vo['id']}">
<ul>
<if condition="$vo.status eq 1">
<li class="messagelogo"><img src="__PUBLIC__/Images/message2.png"></li>
<li class="messagecontent" style="color: #838383">{$vo['content']}</li>
<li class="messagetime" style="color: #838383">{$vo['time']}</li>
<else/>
<li class="messagelogo"><img src="__PUBLIC__/Images/message1.png"></li>
<li class="messagecontent" style="font-weight: bold">{$vo['content']}</li>
<li class="messagetime" style="font-weight: bold">{$vo['time']}</li>
</if>
<li class="messagebutton"><img src="__PUBLIC__/Images/message4.png"
οnclick="replyMessage('<?php echo $vo[id]; ?>','<?php echo $vo[userid]; ?>','<?php echo $vo[replyid]; ?>')">
</li>
<li class="messagebutton">
<img src="__PUBLIC__/Images/message5.png" οnclick="delMessage('<?php echo $vo[id]; ?>',1)">
<!-- 各个操作按钮是一样的-->
</li>
<div class="clear"></div>
</ul>
<volist name="list2" id="vo2"><pre name="code" class="html">
<span style="white-space:pre"> </span><!-- 循环遍历$list2里面的回复内容,如果replyid和此条系统通知的id相同,就显示-->
<eq name="vo2.replyid" value="$vo.id">
<div class="messagereply" id="{$vo2['id']}">
<ul>
<if condition="$vo2.status eq 2">
<li class="messagelogo"><img src="__PUBLIC__/Images/message3.png"></li>
<li class="replycontent" style="color: #838383">{$vo2['content']}</li>
<li class="messagetime" style="color: #838383">{$vo2['time']}</li>
<elseif condition="$vo2.status eq 0"/>
<li class="messagelogo"><img src="__PUBLIC__/Images/message1.png"></li>
<li class="replycontent" style="font-weight: bold">{$vo2['content']}</li>
<li class="messagetime" style="font-weight: bold">{$vo2['time']}</li>
<else/>
<li class="messagelogo"><img src="__PUBLIC__/Images/message2.png"></li>
<li class="replycontent" style="color: #838383">{$vo2['content']}</li>
<li class="messagetime" style="color: #838383">{$vo2['time']}</li>
</if>
<li class="messagebutton"><img src="__PUBLIC__/Images/message4.png"
οnclick="replyMessage('<?php echo $vo2[id]; ?>','<?php echo $vo2[userid]; ?>','<?php echo $vo2[replyid]; ?>')">
</li>
<li class="messagebutton"><img src="__PUBLIC__/Images/message5.png"
οnclick="delMessage('<?php echo $vo2[id]; ?>',2)">
</li>
<div class="clear"></div></ul></div></eq></volist></div></volist></div></div>
<div style="clear: both;height: 5px"></div>
<!--//分页工具类的显示-->
<present name="page">
<div class="messageTool" id="messageTool">{$page}</div>
</present>
</div>
</body>
啊啊就是这样把取来的数据显示的,按钮上面的各个js方法下面来介绍,先说删除功能。删除功能一开始做的简单,拿jQuery做的ajax动态css隐藏,然后发现和工具类冲突,而且在删除后违反每页显示6条的标准,不能自动补齐,所以选择了两种删除方式,对于系统通知的删除,控制器messageDelete()方法删除成功后,页面js直接用setTimeOut()方法和location.href进行页面重定向刷新页面,简单粗暴;对于回复类型的通知因为每一条消息的回复都会集中在一页且不会计入分页类的统计数字,所以用ajax传回参数将对应条目css的display属性设为none就好。下面是删除功能的js与php代码:
<script language="JavaScript">
// 删除信息
function delMessage(message_id, num) {
<span style="white-space:pre"> </span> //这里用一个小js库叫做易U,界面友好一些,confirm方法对删除操作做确认,确定后,用jQuery的post方法调用UserAction的messageDelete(id,num)方法
ui.confirm("是否要删除该条信息", function (z) {
if (z) {
$.post("{:U('User/messageDelete')}", {message_id: message_id, number: num}, function (dresult) {
ui.alert(dresult.info);//弹出AJAX回传的通知消息,是否成功
if (dresult.status == 1) {//如果是删除系统消息成功,页面延迟一秒重定向
setTimeout(refre, 1000);
function refre() {location.href = "/index.php/User/message.html";}
}
if (dresult.status == 2) {//如果是删除回复消息成功,直接动态隐藏这条回复
$("#" + message_id).css("display", "none");
}
});
}
}, true);
}
啊啊控制器中messageDelete()方法传入了消息id和类型,类型是通过操作按钮写死的,懒得改。
function messageDelete($message_id, $number)
{
$Dao = M('innermail');
//删除选定的数据
$result1 = $Dao->where('id=' . $message_id)->delete();
if ($result1) {
//删除关联回复,当然回复是没有关联回复的,但这不是啥大事儿
$Dao->where('replyid=' . $message_id)->delete();
if ($number == 1) {
//Ajax提交
$this->ajaxReturn("", "删除成功!!", 1);
//参数为1时,说明删除了原始消息,不是回复,为更新每页6条消息,refresh页面,但是要在页面js里跳,这里已经exit,不能继续执行redirect啦
//$this->redirect('User/message', '', 0, '页面跳转中...');
}
if ($number == 2) {
//Ajax提交,参数为0说明删除了回复,返回参数2使其动态隐藏即可
$this->ajaxReturn("", "删除成功!!", 2);
}
} else {
$this->ajaxReturn("", "删除失败", 0);
}
}
啊啊然后来介绍回复功能的实现,同样是写在按钮上面的js方法调用,然后js调用控制器方法传参数,组装数据然后写入,写入成功后在view模版中动态的构建一条回复,这次就不用重定向了,就是在这个页面里出现就好了,当然在js里面组装html语句是有点麻烦的,要注意外侧是双引号和内部的单引号的使用,虽然打印出来会自己解析成双引号:
<pre name="code" class="javascript"> //回复信息
function replyMessage(message_id, uid, rid) {
ui.prompt('请输入您的回复', function (z) {
if (z) {
<pre name="code" class="javascript">//同样是调用了那个小js库里面的一个对话框方法,拿用户要说的话,用户提交后z值就是回复的内容,然后jQuery的post方法传参数调用messageReply方法
$.post("{:U('User/messageReply')}", {message_id: message_id, message_content: z, userid: uid, replyid: rid}, function (result) {
ui.alert(result.info);
if (result.status == 1) {<pre name="code" class="javascript"> //如果回复插入成功,将新增条目动态构造插入被回复的目录之后,要放入数据的,要用引号和连接符才行
var replyitem1 = "<div class='messagereply' id=" + result.data.id + "><ul><li class='messagelogo'><img src='__PUBLIC__/Images/message3.png'></li>" + "<li class='replycontent' style='color: #838383'>" + z + "</li><li class='messagetime' style='color: #838383'>" + result.data.time + "</li>" + "<li class='messagebutton'><img src='__PUBLIC__/Images/message4.png' οnclick='replyMessage(" + result.data.id + "," + uid + "," + result.data.replyid + ")'></li>" + "<li class='messagebutton'><img src='__PUBLIC__/Images/message5.png' οnclick='delMessage(" + result.data.id + ",2)'></li>" + "<div class='clear'></div></ul></div>";
if (rid != 0) {//如果回复的是一条回复类型的消息,那就在那条消息的后方外部插入新的条目就好
$("#" + message_id).after(replyitem1); }
else if (rid == 0) {//如果回复的是一条系统通知,也就是初次回复,那就要在这条消息的后方内部插入新的条目 $("#" + message_id).append(replyitem1); } } }); }
else { ui.alert('不说话人家就不理你啦!'); } }); }
啊啊messageReply($message_id, $message_content, $userid, $replyid)方法要很多参数,因为要组装出一条消息记录,主要是要把replyid给定义好,初次回复的,就定义成回复的那条消息的id,后面的回复消息就定义成前面消息相同的的replyid:
function messageReply($message_id, $message_content, $userid, $replyid)
{
$Dao = M('innermail');
$data["userid"] = $userid;
$data["time"] = date("Y-m-d H:i");//我只是觉得把秒都写出来太浮夸,虽然有助于排序
$data["content"] = $message_content;
$data["type"] = 2; //2属于回复类型,不计入消息数
$data["status"] = 2; //2属于回复类型,不区分已读未读
//对回复的关联replyid做区分,首次回复取回复消息id,后面的回复均取前者replyid
if ($replyid == 0) {
$data["replyid"] = $message_id;
} else {
$data["replyid"] = $replyid;
}
// 写入数据
$result = $Dao->data($data)->add();
$data["id"] = $result;//add功能会返回插入成功的那条记录自动生成的主键,源码写的明白
if ($result) {
$this->ajaxReturn($data, "您的回复成功发送,我们会尽快回复~", 1);//把data传回去是为了动态的添加回复条目,是ajax拿到的result.data,然后继续取
} else {
$this->ajaxReturn("", "发送失败", 0);
}
}
啊啊三个功能基本实现,有一点小瑕疵,比如在第二页删除后重定向,结果回到了第一页;只有一页数据的时候,首页尾页那些按钮还是显示也是有些傻,哈哈,去源代码里面稍微改一下判断就好啦。贴一下改动了一点点的Extend/Library/ORG.Util.Page类里面的show方法(截取,不大,一下就能对照上):
//上下翻页字符串 其实就是上一页和下一页的操作逻辑,我只是让他们一直显示,没破坏ifelse判断样式是因为这样好改
$upRow = $this->nowPage-1;
$downRow = $this->nowPage+1;
if ($upRow>0){//加了id
$upPage = "<a id='upPage' href='".str_replace('__PAGE__',$upRow,$url)."'>".$this->config['prev']."</a>";
}else{//此处修改过,原本均为空字符串
$upPage = "<a id='upPage' href='".str_replace('__PAGE__',$upRow,$url)."'>".$this->config['prev']."</a>";
}
if ($downRow <= $this->totalPages){//加了id
$downPage = "<a id='downPage' href='".str_replace('__PAGE__',$downRow,$url)."'>".$this->config['next']."</a>";
}else{//此处修改过,原本均为空字符串
$downPage = "<a id='downPage' href='".str_replace('__PAGE__',$downRow,$url)."'>".$this->config['next']."</a>";
}
// << < > >> 就是首页尾页,当然prepage也是返回前进那一套,但是一般用不了这么多
if($nowCoolPage == 1){//此处修改过,原本均为空字符串,同时加了id
$theFirst = "<a id='theFirst' href='".str_replace('__PAGE__',1,$url)."' >".$this->config['first']."</a>";
$prePage = "";
}else{
$preRow = $this->nowPage-$this->rollPage;
$prePage = "<a href='".str_replace('__PAGE__',$preRow,$url)."' >上".$this->rollPage."页</a>";
$theFirst = "<a id='theFirst' href='".str_replace('__PAGE__',1,$url)."' >".$this->config['first']."</a>";
}
if($nowCoolPage == $this->coolPages){//此处修改过,原本为末尾时,均为空字符串,同时都加了id
$theEndRow = $this->totalPages;
$nextPage = "";
$theEnd = "<a id='theEnd' href='".str_replace('__PAGE__',$theEndRow,$url)."' >".$this->config['last']."</a>";
}else{
$nextRow = $this->nowPage+$this->rollPage;
$theEndRow = $this->totalPages;
$nextPage = "<a href='".str_replace('__PAGE__',$nextRow,$url)."' >下".$this->rollPage."页</a>";
$theEnd = "<a id='theEnd' href='".str_replace('__PAGE__',$theEndRow,$url)."' >".$this->config['last']."</a>";
}
// 1 2 3 4 5
$linkPage = "";
for($i=1;$i<=$this->rollPage;$i++){
$page = ($nowCoolPage-1)*$this->rollPage+$i;
if($page!=$this->nowPage){
if($page<=$this->totalPages){//此处加了class,因为这个不是唯一的,会有好多个,虽然加id也没啥大问题
$linkPage .= "<a class='linkPage' href='".str_replace('__PAGE__',$page,$url)."'>".$page."</a>";
}else{
break;
}
}else{
if($this->totalPages != 1){
$linkPage .= "<span class='current'>".$page."</span>";//这个class是自带的= =
}
}
}
啊啊这样稍微弄一下,总比重新写一堆来的方便些,css样式定义的时候也就很方便了,不得不吐槽为了不因为缩进导致出现横栏拖动条,我把宽度改得毫无美感:
.messageList {
font-size: 14px;
font-family: "microsoft yahei", Arial, Geneva, Helvetica, sans-serif;
text-align: center;
}
.messageTitle {
width: 100%;
height: 40px;
position: fixed;
top: 0;
background-color: #c55240;
}
.messageitem {
width: 100%;
border-bottom: #e2dcdd 1px solid;
/*加个下划线区分一下每条消息*/
}
.messagereply {
/*width: 98%;*/
margin-left: 2%;
<span style="font-family: Arial, Helvetica, sans-serif;">/*</span><span style="font-family: Arial, Helvetica, sans-serif;"> 啊,万恶的缩进</span><span style="font-family: Arial, Helvetica, sans-serif;">*/</span>
}
.messageTitle ul li {
float: left;
display: block;
color: #fff;
height: 40px;
line-height: 40px;
}
.messageitem ul li {
float: left;
list-style: none;
display: block;
padding-top: 17px;
padding-bottom: 17px;
}
.messagereply ul li {
float: left;
list-style: none;
display: block;
padding-top: 5px;
padding-bottom: 5px;
}
.messagelogo {
width: 2%;
}
.messagelogo img {
width: 22px;
}
.messagecontenttitle {
width: 77%;
}
.messagecontent {
width: 77%;
text-align: left;
}
.replycontent {
width: 76.7%;
text-align: left;
}
.messagetime {
width: 14%;
}
.messagebutton {
width: 3.5%;
}
.messagebutton img {
width: 25px;
}
.messageTool {
text-align: center;
margin: 0 auto;
font-size: 14px;
font-family: "microsoft yahei", Arial, Geneva, Helvetica, sans-serif;
color: #c55240;
}
.messagetool a{
color: #c55240;
margin: 5px 5px;
font-weight: bold;
}
.messagetool a:hover{
color: #c55240;
margin: 5px 5px;
font-weight: bold;
background-color: #e2dcdd;
}
.messagetool .current{
color: #000000;
margin: 5px 5px;
}
啊啊嗯,我做出来就是这样了,目前没有什么严重的问题,当然涉及到性能啊,精简的东西,我这种第一次实战的人也没那个概念。什么,还要一个AJAX轮询来通知用户有新消息吗?这个不是我需要做的功能,虽然我也考虑了它,用js写的setInterval()方法调用一个messagePoll方法,基本可以实现,不过我弄的是站内信不是聊天室,不至于这么浮夸,好吧其实弄得这么乱七八糟的逻辑已经很浮夸,我就是为了熟悉技术:
function messagePoll()
{
//AJAX轮询数据库调用,存在status==0的数据则提示
$Dao = M("Innermail");
$uid = $this->getUserId();
if ($uid != null) {
$condition1['userid'] = $uid;
$condition1['status'] = 0; //计算新收到的消息数
$count = $Dao->where($condition1)->count();
if ($count != 0) {
$this->ajaxReturn($count);
}
}
}