会议预约代码改进

会议预约代码改进

前言

写程序的时候,要知道代码的前提条件是什么。比如,开根号,就应该想到,我可能用到的数可能为负数。所以,需要先验证。(至于验证在哪进行,是开根号函数定义还是调用,视情况而定。)所以这些,才能保证程序的健壮性。

常用策略

使用变量替代复杂的表达式

一个表达式、多个逻辑值求值或函数调用等反复引用,应该起一个有一定含义的名字,作为一个变量来使用。这样能简化代码。如检查是否是特权用户。使用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;
}