会议预约代码改进
会议预约代码改进
前言
写程序的时候,要知道代码的前提条件是什么。比如,开根号,就应该想到,我可能用到的数可能为负数。所以,需要先验证。(至于验证在哪进行,是开根号函数定义还是调用,视情况而定。)所以这些,才能保证程序的健壮性。
常用策略
使用变量替代复杂的表达式
一个表达式、多个逻辑值求值或函数调用等反复引用,应该起一个有一定含义的名字,作为一个变量来使用。这样能简化代码。如检查是否是特权用户。使用super变量,真:特权。假:非特权。
$super=in_array($user->role_id,array(1,3));
相关的变量或者逻辑应该抽象成对象,封装在一起。
如初始化的变量
//下面整体应该保存到一个变量,就不用这么麻烦。
//var origin= rdData;//只要这一句就ok。
me.originalMeetingdate=me.meetingdate = rdData['meeting_date'];//保存初始值
me.originalStarttime=me.starttime = rdData['starttime']; //保存初始值
me.originalStoptime=me.stoptime = rdData['stoptime']; //保存初始值
代码展示
时间显示
{//第二块
xtype:'menublockpanel',//仅显示
items:[{
xtype:'selecttextfield',
bind:{
// value:"{meeting_date} {starttime}-{stoptime}" //注意这种方式,如果三个字段为空的话,那么还是会有内容,空格+'-'号
//而下面是一种改进,如果meeting_date没有内容,直接显示的就为空的内容。这样就能显示emptyText:'请选择会议召开时间。',
//另外需关注:字符串能运算。当然下面的代码也能放到viewModel中,能使用公式来计算。但是没有此方法简单。
value:'{meeting_date?meeting_date+" "+starttime+"-"+stoptime:""}'
},
submitValue:false,
fieldLabel:Ext.String.format(DIC.labelString,5,'时间'),
emptyText:'请选择会议召开时间。',
setItemIndex:4,
needRoom:true
}]
}
增加输入变化时,change事件的响应。
在MenuBlockPanel中,集中的为子items设置默认的change属性。先判断setItemIndex是否为1,即先判断是否需要输入提醒。如果确定有,则进行相应的正确、错误提醒。这个在后期还能优化,如正则验证后,相应的提示。
listeners:{
'focus':function(cmp, event, eOpts){
var meetingordermenu=cmp.up('meetingordermenu');
ShineMessageHub.fireEvent('meetingordermenuinputfocus',cmp,meetingordermenu,event, eOpts);
},
'change':function(cmp, newValue, oldValue, eOpts ){
if(cmp.setItemIndex!==1){
return ;
}
var o;
if(newValue){
o={icon:'pass'};
}else{
o={icon:'warning'};
}
ShineMessageHub.fireEvent('meetingordersettingcontainersetviewmodelevent',o);
}
}
表单提交前的数据验证
先进行空的判断,然后再进行valid的正则验证等。如果输入错误,则进行提示相应的内容。
话说,不死布局真得不好用。
不死布局,真的不好用,上一次操作的记录,如果不能完全清空。对下一次的操作影响真得很大。为了这个缺点,每次都要进行初始化工作。
//添加会议时,设置默认项
ShineMessageHub.on("meetingorderhomeaddevent",function(rdData){
me.meetingdate = rdData['meeting_date'];
me.starttime = rdData['starttime'];
me.stoptime = rdData['stoptime'];
me.conference_room_id = rdData['conference_room_id'];
me.meeting_id = rdData['meeting_id'];
me.displayStart=Ext.Date.format(new Date(),'Y-m-d');
me.ajaxGetOccupy();
me.idx=-1;//这个记录了上一次操作的点。结果这个东西,没有重置。
},me);
超级用户等,可以更改正在进行中的会议
更新需求。超级用户可以修改正在进行中的会议,可以修改任何内容。由于之前,创建的时候,时间如果过期了,就不可选。现在还要可选。像这样的需求,首先要明白,这相当于开了一个口子。这个口子开多大,要根据实际情况定。
如本例中,肯定要修改,会议当天的时间,而且如果换了会议室,这个口子就不要看了。所以还需要保存,会议室的id。
'storeloaddata':function(dataview,store){
var me=this;
console.log(me.starttime);
var indexs=[me.getTimeListIndex(me.starttime),me.getTimeListIndex(me.stoptime)-1];
var oindexs=[me.getTimeListIndex(me.originalStarttime),me.getTimeListIndex(me.originalStoptime)-1];
store.each(function(rd){
//编辑会议时,之前的时间可编辑
//if(me.originalMeetingdate&&rd.get('meetingdate')==me.originalMeetingdate){
//注意,下面的条件进行了增强,要求是修改前的会议室id,才允许此特例。
if(me.originalMeetingdate&&me.originalRoomId==me.conference_room_id&&rd.get('meetingdate')==me.originalMeetingdate){
var lis=rd.get('timelist');
for(var i=oindexs[0];i<=oindexs[1];i++){
if(lis[i].status==1){
lis[i].status=0;
}
}
rd.set({timelist:lis});
rd.commit();
}
//上面的代码,相当于开了一个口子。在会议修改时,有originalMeetingdate,
//然后针对这一天,如果时间过期了,强制更新为状态0,即时间可用。而且这个应该还跟会议室相关。
//如果更换了会议室,上面的代码应该不执行的。
if(rd.get('meetingdate')==me.meetingdate){
var lis=rd.get('timelist');
for(var i=indexs[0];i<=indexs[1];i++){
if(lis[i].status!=0){
Ext.toast('当前选择时间段不可用,请重新选择。');
return ;
}
}
me.setSelectStyle(rd,indexs);
}
});
},
需要将会议的初始的originalMeetingdate、originalRoomId等信息保存起来。
//添加会议时,设置默认项
ShineMessageHub.on("meetingorderhomeaddevent",function(rdData){
me.originalMeetingdate=me.meetingdate = rdData['meeting_date'];//保存初始值
me.originalStarttime=me.starttime = rdData['starttime']; //保存初始值
me.originalStoptime=me.stoptime = rdData['stoptime']; //保存初始值
me.originalRoomId=me.conference_room_id = rdData['conference_room_id']; //保存初始值
me.meeting_id = rdData['meeting_id'];
me.displayStart=Ext.Date.format(new Date(),'Y-m-d');
me.ajaxGetOccupy();
me.idx=-1;
},me);
现在时间,增加会议详情
//info.desc变量比较简单,没有空格分隔,
//其实不加引号也没有问题。但是如果有空格等,就多了引号,显示就不正常了。
title="{info.desc}"
//简单形式,要求变量没有空格等。则显示也没有问题。 title={info.desc}
完整的模板代码,如下
tpl:[
'<div class="selecttimepanel-right-box">',
'<div class="arrow left-arrow"></div>',
'<tpl for=".">',
'<div class="selecttimepanelchoseitemCls"><table>',
'<tr><th>{displaydate}</th></tr>',
'<tpl for="timelist">',
'<tr>',
'<td dataref={index} {[values.selected?"class=selecttimepanel-selected":""]} {[values.status==1?"class=selecttimepanel-expired":""]} {[values.status==2?"class=selecttimepanel-occupy":""]} title="{info.desc}">',
'{info.employee_name}',
'</td>',
'</tr>',
'</tpl>',
'</table></div>',
'</tpl>',
'<div class="arrow right-arrow"></div>',
'</div>',
],
在模板里面进行比较麻烦,直接在store加载数据的时候,进行处理。
//计算时间是否占用
if(status===0){
var occupy=me.isOccupy(items,start,stop);
if(occupy!==false){
status=2;
info=occupy;
//将所有的计算,在此进行,组合成一个字段。这样在模板里面,就没有复杂的允许。
//看起来也比较简洁。
info.desc='名称:'+info.meeting_name+';时间:'+info.starttime+'至'+info.stoptime;
console.log(info.desc);
}
}
后台增加代码
//修改
$sql="select meeting.user_id,employee_name,meeting_name,starttime,stoptime from meeting ".
'LEFT JOIN USER ON `user`.user_id = meeting.user_id '.
"where starttime>'$startStamp' and starttime<'$stopStamp' and status in(0,1) $where ".
"order by starttime asc";
//原始
$sql="select meeting_name,starttime,stoptime from meeting ".
"where starttime>'$startStamp' and starttime<'$stopStamp' and status in(0,1) $where ".
"order by starttime asc";
将登录的数据保存起来
其实这个问题,并不是问题。只是当初设计的时候,根本没有想到这个东西,会共用。反复的初始化同一个组件,没有这个必要。应该在6个模块中,共有一个标题头实例。这样,一个更新,其他的也就更新了。但是框架已经写好了。就懒得动了。
将Ajax获取到的数据保存起来
getLoginUser:function(){
var me = this;
Ext.Ajax.request({
url: SHINEVMSHTTP +'/admin/default/index/checklogin',
success: function(response, opts) {
var obj = Ext.decode(response.responseText);
console.log(obj);
if(obj&&obj.user&&obj.user.employee_name){
me.employee_name=obj.user.employee_name;//在这一步,将数据保存起来。
//后面这个发消息,需要相应的组件存在才行。
ShineMessageHub.fireEvent("mainmeetingtitlebarsetviewmodelevent",{'userName':obj.user.employee_name});
}
},
failure: function(response, opts) {
console.log('网络异常 ' + response.status);
}
});
}
主页点击事件,进行了拦截
if(record.data.id==2 ||record.data.id==6){
var o=record.data.id==2?{xtype:"mymeetinghome"}:{xtype:"meetingmarkhome"};
//注意,在这一步,为新添加的组件增加一个vmData属性,如果这个属性存在
//新生成的组件会将其放到viewModel中。
o.vmData={'userName':ctBox.employee_name};
ctBox.items.items[record.data.id].removeAll();
ctBox.items.items[record.data.id].add(o);
setTimeout(() => {
Ext.fly(itemId).setStyle({
border:"0px"
});
ctBox.getLayout().setActiveItem(record.data.id);
}, 200);
}
组件对新添加的属性,进行响应。
constructor:function(config){//后执行
var me = this;
me.superclass.constructor.call(me, config);
//注意constructor的最后,才能使用me.down('mainmeetingtitlebar')这个方法
//否则会找不到组件的。
//这个应该是常用的技俩。习惯将vmData放到ViewModel中。
if(me.vmData){
me.down('mainmeetingtitlebar').getViewModel().setData(me.vmData);
}
},
会议预约后台数据导出功能
//indexshineloginsuccessevent这个事件,会在登录成功后,或者页面刷新后,都会发消息。
//在这个事件中接收消息。确保,ShineMessageHub.roleId始终都能获取到值。
//接收登陆成功消息
ShineMessageHub.on("indexshineloginsuccessevent",function(obj){
me.getLayout().setActiveItem(0);
ShineMessageHub.roleId=obj['role_id'];
});
对比:
form.submit({
success: function (form, action) {
Ext.Msg.hide();
console.log(action["result"]["user"]);
ShineMessageHub.fireEvent("indexshineloginsuccessevent", action["result"]["user"]);
//注意,这个事件中,不能保证稳定触发。所以,应该
//监听indexshineloginsuccessevent这个消息。不论是登录、还是刷新页面,
//都能正常的获取到值。下面的代码直接删掉。
ShineMessageHub.roleId = action["result"]["user"]['role_id'];
},
failure: function (form, action) {
var msg = action["result"]["msg"];
Ext.Msg.hide();
msgPanel.update('<font color="red">' + msg + '</font>');
//重置验证码
Ext.get("safecode").dom.src = me.getCodeSrc();
}
});
取消代码优化
这个是突发奇想,没有试过。用在桌面版会议室选择跟模板选择的功能。原本的限制取消选择功能,是写在selectionchange(this, selected, eOpts )事件中的。但是发现写在beforedeselect中更好,检测eOpts,如果点击了非item节点,然后return flase,来阻止取消选择功能。(好吧,感觉也还有一点复杂。)
beforedeselect ( this, record, index, eOpts )
更改radio的颜色
下面这个属性,干不过内联样式。
.x-meetingordermenu{//这个类名,自己为组件增加的cls属性。缩小范围。
span.x-form-radio{
color:#F3AD03;
}
}
就是下面这个东西会产生在html内的style标签。
//设置radio的颜色
var radios = el.query('span.x-form-radio-default', false);
Ext.each(radios, function (radio) {
radio.setStyle({
color: '#FFFFFF'
});
});
html中显示的style
<span id="radiofield-1026-displayEl" data-ref="displayEl"
role="presentation" class="x-form-field x-form-radio x-form-radio-default x-form-cb x-form-cb-default "
style="color: rgb(255, 255, 255);">
<input type="radio" id="radiofield-1026-inputEl" name="meeting_type" data-ref="inputEl" class="x-form-cb-input" autocomplete="off" hidefocus="true" aria-hidden="false" aria-disabled="false" aria-invalid="false" aria-describedby="radiofield-1026-ariaStatusEl" data-componentid="radiofield-1026" style="background-color: rgb(17, 96, 150); color: rgb(255, 255, 255);">
</span>
提交失败
数据提交失败,区分失败的原因。
failure: function (form, action) {
//action的属性,来区分失败的提示语。
var message = action.failureType == "connect" ? '联网失败,请检查网络连接状态。' : '提交失败,请检查输入的数据是否完整。';
Ext.Msg.hide();
var win = Ext.widget({
xtype: 'commonmsgwin',
vmData: {
title: '操作提示',
message: message,
hiddenLeft: true
},
rightHandler: function () {
win.destroy();
}
}).show();
}
选择时间的tbar标签的宽度样式
设置一个最小宽度。防止太小挤变形。
tbar: [{
xtype: 'label',
text: '查看会议室使用情况',
flex: 1,
minWidth: 230, //增加一个宽度,防止太小挤变形。
style: {
color: 'black',
fontSize: '20px',
lineHeight: '20px'
}
}]
优化toast效果
本方案采用两种方式同时进行优化,第一种,屏蔽用户频繁点击。如果距离上次点击的时间太近,则直接不响应。直到下次响应后,记录下响应的时间。(相当于锁机制)。另外一种机制,如果用户点击的时候,发现还有其他的相同的实例存在,干掉他们。然后自己再显示出来。
巧了,在框架里面也发现了跟我命令差不多的变量,lastTouchTime。这样的话,也能优化,自己的代码呢。
Ext.isTouchMode = function () {
return (Ext.now() - Ext.lastTouchTime) < 500;
};
//
Ext.define("Ext.locale.zh_CN.window.Toast", {
override: "Ext.window.Toast",
listeners:{
'beforeshow':function(cmp, eOpts){
var t=Ext.all('toast');
var now=new Date().getTime();
if(Ext.lastToastTime&&(now-Ext.lastToastTime)<1000){
return false;
}
Ext.lastToastTime = now;
Ext.each(t,function(tst){
if(tst!==cmp){
tst.destroy();
}
});
}
},
});
另外,now的定义如下(为啥多个+号,就能将时间变成时间戳。这是什么骚操作?):
Ext.now = Date.now || (Date.now = function() {
return +new Date();
});
另外,发现,这个组件好像也自动监测了,其他的toast组件。尤其是me.getToasts()函数。这个消息提示,其实做得真心的复杂,功能多。但是,展示出来的效果却不是我们想要的,只能更改。
removeFromAnchor: function () {
var me = this,
activeToasts, index;
if (me.anchor) {
activeToasts = me.getToasts();
index = Ext.Array.indexOf(activeToasts, me);
if (index !== -1) {
Ext.Array.erase(activeToasts, index, 1);
// Slide "down" all activeToasts "above" the hidden one
for (; index < activeToasts.length; index++) {
activeToasts[index].slideBack();
}
}
}
},
查找泄漏的变量
js的局部变量应该封装在局部,这样避免其他的地方引用此变量,造成意外错误,而且排查错误难。查找泄漏的变量,直接在控制台输入window,所有绑定到该变量上的其他的变量都是泄漏的变量或者全局变量。这是一种好方法,可以看到全局的变量的一些特征,如:ShineMessageHub监听的事件等。
以下为一个泄漏的局部变量。部分代码如下:
/************
*
* 修改会议
* 2019年1月17日
* ***********/
editMeeting:function(record){
var me=this;
var menu=me.down('meetingordermenu');
me.setItemIndex=2;
//这一块忘了声明了,结果rdData泄漏成全局变量
rdData = Ext.clone(record['data']);
console.log(rdData);
条件判断
逻辑运算具有短路效应,优先将容易返回值的放在前面。&& 将容易为false的放在前面。||将容易为真的放在前面。
php代码:
//为了保护访问,临时增加了一个password进行保护。
if (isset($params['password']) && $params['password'] == 'clearmeeting') {
$now = time();
//前面的表达式更容易返回值,所以放在前面。
if ($now < strtotime(date('Y-m-01 06:00')) && $now > strtotime(date('Y-m-01 01:00'))) {
$data = MeetingListUtils::clearMeeting($params);
echo Zend_Json::encode($data);
} else {
echo Zend_Json::encode(array('code' = > 2, 'msg' = > "未到月初清理时间,当前时间".date('Y-m-d H:i:s')));
}
}
store加载拦截
之所以要拦截,是因为就有axios拦截器,而store却没有,如果store加载请求的时候,并没有使用Ext.Ajax进行请求。按找传统html的设计,如果用户没有登录,可以通过重定向,让用户跳转到登录界面。但是ajax却不能让浏览器的页面跳转。所以,想在ajax里面让其跳转。
proxy: {
type: 'ajax',
// url: SHINEVMSHTTP + '/admin/meeting/list/mymeeting',
url: SHINEVMSHTTP + "/admin/desktop/mymeetinglist/getmeetinginfo",
reader: {
type: 'json',
rootProperty: 'result',
totalProperty: 'count'
},
//注意,这个proxy是能响应事件的。
listeners: {
'beginprocessresponse': function () {
console.log('猜猜我是谁?阴险');
return false;//这个并不会组织解析
}
//另外还能响应如下事件。以下事件是在源代码Ext.define('Ext.data.ProxyStore', 中发现。
//endprocessresponse
//exception
//metachange
}
},
获取部门树形结构时,导致死循环
在获取树形结构时,当前id跟parent_id一致,导致了死循环。
/**********
*部门列表
**********/
public static function getDeptTree($params){
$db = Zend_Registry::get('db');
//在此处进行了限制。
$sql = "select * from department where department_id !=department_parent_id";
$res = $db->query($sql);
$rows = $res->fetchAll();
$data = self::getChild($rows,"null",$params);
if(isset($params['format'])&&$params['format']==='object'){//转换对象json格式
return $data[0];
}
return $data;
}