前端Ext

[TOC]

前端Ext

行编辑器功能,动态设置,某列可编辑

思路有两种,一种是在编辑前,获取editor,然后设置其 disabled属性,但是麻烦。

另外一种,即放在viewmodel中,通过绑定,来达到动态的设置效果。(多采取此类方法)

viewModel设置:

viewModel:{
	data:{
		selections:0,
		item_type:0 //这一项需要动态的设置。在beforeedit
	}
}

整个grid表格的监听事件:

listeners:{
    //在这个事件中,进行监听
	"beforeedit":function(editor, context, eOpts ){
		var width=me.columns[2].getWidth();
		console.log(context.record);
		var item_type=context.record.get('item_type');
		me.getViewModel().setData({item_type:item_type});
	},
	'edit':function(editor, context, eOpts){
		//编辑功能
		var record =context.record;
		console.log(record);
		console.log(1222222222222222222);
		Ext.Ajax.request({
			url: SHINEVMSHTTP + '/admin/structure/index/edit',
			params:record['data'],
			success: function(response, opts) {
				var obj = Ext.decode(response.responseText);
				me.currentEditRecordId=obj['id'];
				me.store.reload();
				
			}
		});

	},
	'canceledit':function( editor, context, eOpts ){
		//添加时,如果点击取消,那么将删除此行
		var record=context.record;
		var id = record.data.structure_id;
		if(id==0){
			me.store.remove(record);
		}
	},		
	'selectionchange':function( cmp, selected, eOpts ){
		this.getViewModel().setData({
			selections:selected.length
		});
	}
},
{
		text:'出诊显示天数',
		dataIndex:'structure_day',
		editor:{					
			xtype:'numberfield',
			labelAlign:"right",			
			valueField: 'key',
			bind:{
				disabled:'{item_type!=2}',
			}
			
		},
		renderer:function(v,metaData,record,rowIndex,colIndex){
			return record.get('item_type') != 2?'':(parseInt(v)+'天');
		}	
}

效果图:

1560481792860

行编辑器:注意,新增加的字段,必须在store的field中添加,否则不会生效。第二次踩坑。

排序科室,未注意到共用组件。

而且共用组件的配置属性,如果是复杂变量,其也是共享内存的。

在组件初始化的时候,对其进行了判断,如果为特定的组件,则增加“排序”字段。结果,有两个问题。

  • 组件因为是共有的,多次执行后,add多了多个列。

1560739140891

  • 其他不想用到的这个列,也增加了该字段。

1560739191839

错误代码如下:

initComponent方法内,在调用me.callParent();方法之前。

if(me.cmpbox && me.cmpbox=='departmenthome'){
	console.log('我执行啦');
	me.columns.push({ 
		text: '排序',
		sortable: false,
		dataIndex: 'show_order',
		width:100
	});
}

修改为:

防止复杂变量属性(数组、对象)在原型上共用同一地址。

var col1={ 
	xtype:"treecolumn",
	text: '科目名称',
	sortable: false,
	height:0,
	dataIndex: 'department_name',
	flex: 1
};
var col2={ 
	text: '排序',
	sortable: false,
	dataIndex: 'show_order',
	width:100
};
if(me.cmpbox && me.cmpbox=='departmenthome'){
	me.columns = [col1,col2];
}else{
	me.columns = [col1];
}
me.callParent();

上面的代码似曾相识,在别人的老代码中见到过类似的写法。

总结:1、遇到通用组件,需要在不同的状态下,实例不同的属性,一定要用赋值一个新的变量,而不要修改。(之前,总是关注自定义的)2、与到通用的组件,要注意,在其他组件的显示特性。

门诊管理菜单改造

  • 创建没有标签的下拉选择框

    只需要将fieldLabel等属性注释掉即可。代码如下:

    me.searchDateCmb = Ext.create('Ext.form.ComboBox', {
    	// fieldLabel:'查询类型',
    	// labelAlign:"right",
    	// labelWidth:60,
    	value:1,
    	width:160,
    	editable:false,
    	store:{
    		fields: ['key', 'name'],
    		data :[
    			{key:1,name:"预约日期"},					
    			{key:2,name:"下单日期"}				
    		]
    	},
    	listeners:{
    		change:function(c, newValue, oldValue, eOpts){
    			me.searchDateKey = newValue;
    		}
    	},
    	queryMode: 'local',
    	displayField: 'name',
    	valueField: 'key'
    });
  • 双层菜单栏设计:

    简单的原理如下:

    tbar:[{
    	xtype:'panel',
    	layout:'vbox',
    	items:[{
    		html:'我是第一行'
    	},{
    	html:'我是第二行'
     	}]
     }],

需要注意的是:vbox布局,需要设置宽度,没有则可能显示不正常。所以每个items里面都是一个panel,设置width:’100%’。然后每个panel都有一个tbar。 或者,不想每个都设置高度的话,则修改布局如下:

layout: {
	type: 'vbox',
	align: 'stretch'//默认每个100%宽度
},

另外,别人代码是这样设计的。

grid表格没有toobar,然后单独的制作两个toobar。

Ext.define("View.AppointMenu", {
    extend: "Ext.panel.Panel", alias: "widget.AppointMenu",
    layout: { type: "vbox", align: "stretch" },
    items: [
       { xtype: "appointmain_toolbar", height: 40, border: 1 },
       { xtype: "appointmain_toolbar2", height: 40, border: 1 },
       { xtype: "appointmain_grid", flex: 1.0, border: false, },
    ],
});

//菜单对象创建

Ext.define("View.AppointMain_Toolbar", {
    extend: "Ext.toolbar.Toolbar", alias: "widget.appointmain_toolbar",
    items: [
        //{ xtype: "container", html: "<div>预约日期</div>", },
        {}]
})

发现问题:在tbar:[]中实例化时,括号中,如果是已创建的实例,则不能共用,(共用,则出现,最后一个能用到,前面引用的实例是不会显示的。)如果是配置文件,则互不干扰。

效果如图:

1560843640304

tbar想采用方式一,设置,结果总是报错。无奈采用第二种方式。(没想到这也行。)

//方式一
dockedItems: [{
	xtype: 'toolbar-overflowbar',
	dock: 'top',
	overflowHandler: 'menu'
}]
//方式二:
tbar:{
	items:[me.searchDateCmb],
	overflowHandler :'scroller'
}
  • store的data无法使用绑定。(好像)。延伸:是不是复杂变量,都不能进行绑定?
//定义好数组,供store用
viewModel:{
	data:{
		doctorList:[
			{"display":"一星", "v":1},
			{"display":"二星", "v":2},
			{"display":"三星", "v":3},
			{"display":"四星", "v":4},
			{"display":"五星", "v":5}
		],
		doctor_id:null,
		date:null,
	},
},


{//组合框   //修改医生
	xtype:"combobox",
	fieldLabel: '修改医生',
	name: 'doctor_id',
	forceSelection:true,//强制选择项
	editable:false,
	store: {
		fields: ['display', 'v'],
		bind:{
			data:'{doctorList}' //事实上,好像并不起作用
		}
	},
	queryMode: 'local',
	displayField: 'display',
	valueField: 'v',
	submitValue:true
}
/**************
     * 2019年6月20日
     * 获取当前的查询条件
     **************/
	queryParams:function(){
		var me =this;
		var v= me.getViewModel().getData();
		console.log(v);
		var fn = function(v){
			return Ext.Date.format(new Date(v),'Y-m-d');
		}
		v['startdate']=fn(v['startdate']);
		v['stopdate']=fn(v['stopdate']);
		v['structureId']=ShineMessageHub.structureId;
		console.log(v);
		return v;
	},

共用查询条件(导出)

查询在页面显示跟下载,共用一组条件,这样好处是,保证了所见即所得。另外,两个也共用了一套的查询逻辑。导出,利用了自己抽象的组件(参见:数据导出)

另一点,创新之出,利用了viewModel,将各个选择的项进行了绑定,这样获取当前的查询条件比较方便。(多用viewModel思路)

/**************
 * 2019年6月20日
 * 获取当前的查询条件
 **************/
queryParams:function(){
	var me =this;
	var v= me.getViewModel().getData();
	console.log(v);
	var fn = function(v){
		return Ext.Date.format(new Date(v),'Y-m-d');
	}
	v['startdate']=fn(v['startdate']);
	v['stopdate']=fn(v['stopdate']);
	v['structureId']=ShineMessageHub.structureId;
	console.log(v);
	return v;
},
//数据加载前,添加搜索条件
'beforeload':function( store, operation, eOpts){
	operation['_params'] = me.queryParams();;
}

缺少滚动条

使用了hbox vbox布局,一定要仔细处理高度跟宽度。避免造成布局问题。

问题如下 :

1561359094251

原因就是,hbox,左右布局,但是没有为每个items指定高度,所以右侧虽然撑开了,但是组件不知道自己的高度。就缺少了滚动条。

解决方式:

指定第二个组件的高度为100%,另外也可以从layout来解决。

items:[{
					xtype:"organizationlist",
					split:true,
					//collapsible:true,
					hideCollapseTool : true,
					collapseDirection:'left',
					collapseMode : 'mini',
					title:"出诊基层",
					switchHandler:8,
					width:450,
					height:'100%',
				}
//解决方式二,但是,这个却不起作用。  难道stretch只适合vbox?
layout: {
	type: 'hbox',
	align: 'stretch'//默认每个100%宽度。
}

listeners

组件已经包含了listeners,引用组件时候,还能再次添加listeners,这两个listeners其实都能起作用。

items:[{
		xtype:"departmentgrid",
		region:"east",
		tbarIsHidden:1,
		title:"科目信息",
		split:true,
		cmpbox:"medicinehome",
		width:300,
		listeners:{
			"rowcontextmenu" :function( c, record, tr, rowIndex, e, eOpts ){
				console.log('右键菜单-药品');
			}
		}
]

listeners中的cmp并不总是this

//Ext.tree.Panel  组件的listeners
listeners:{
		"rowcontextmenu" :function( c, record, tr, rowIndex, e, eOpts ){
			c == this //false
            //这里面的c居然并不是组件  的this.
            //输出c,发现   xtype: "treeview"
		}
}

用textfield替代hiddenfield

如下代码,可以将一个输入项隐藏起来。代码本身没有什么亮点,重点在于,用其替代hiddenfield。为啥要替代,因为hiddenfield的功能,没有textfield功能齐全。比如,是否要提交、是否为空等验证。

{
	hidden:true,
	xtype:"textfield",
	name:"lastaccountid"
},

尽量用viewmodel来代替具体的索引

在远程医疗模块中,添加终端功能,其内部大量使用了items.getAt(索引号)功能,但是随着需求的更改,变得非常难维护,增加一个items项,代码影响很大。所以能用viewmodel的时候,尽量用。或者用me.query(‘’)方式,尽量代码与其具体的索引无关。

me.editPanel.items.getAt(me.editPanel.items.length-4).setValue(me.LastAccountID);
me.editPanel.items.getAt(me.editPanel.items.length-3).setValue(Ext.encode(OutData));

allowBlank改造,为组件增加set方法

组件的viewmodel方法绑定时,只能绑定有set方法的配置项。如果没有,则会报错。在切换时,有的时候想动态的设置allowBlank,即,在有的选项下进行验证,有的方法下不验证。想通过listeners的blur方法进行切换,markInvalid标记验证结果,触发验证。结果是,在allowBlank=true的情况下,都不会再验证了。后来,在源码观察中,发现allowBlank可以进行改造。遂改造,并实现了效果:动态开启、关闭验证

{
	fieldLabel: "IP地址",
	labelAlign:"right",
	name: 'device_ip',
	xtype: 'textfield',
	regex:/^((25[0-5]|2[0-4]\d|[01]?\d\d?)($|(?!\.$)\.)){4}$/,
	regexText : '请输入正确的ip地址!',
	//注意allowBlank,没有设置方法,下面的绑定,会报错。
	bind:{
		allowBlank:'{virtual==1}'
	},
	//为了解决动态设置allowBlank的问题,自己手动的增加。
	setAllowBlank:function(v){
		this.allowBlank=v;
	}
	//下面的方法,没有达到预定效果。
	// listeners:{
	// 	'blur':function( cmp, event, eOpts ){
	// 		var v=me.getViewModel().getData();
	// 		if(v['virtual']==0){
	// 			console.log('真实设备');
	// 			cmp.markInvalid(cmp.regexText);
	// 			cmp.isValid();
	// 		}
	// 	}
	// }
}

获取上传文件的内容

只有一种方式,items.getAt(1).getValue(),其他的方式,都无法获取到上传表单的值。难道这算是一个bug?

  • 几种方式对比如下:
var bform = me.down('form').getForm();
var filename=bform.getValues()['attachment'];
console.log(bform.getValues());
var filename1=me.down('form').items.getAt(1).getValue();
console.log({filename,filename1});
  • 结果图:

1561711141877

window的自适应

设置window的组件的布局 layout:’fit’,其可以被内部的元素主动撑开,不需要自己再手动的调整高度。

这个对那种需要动态隐藏某些组件的来说,非常方便。

需要优化的嵌套if语句

if的嵌套,有时候可以变成并列条件,也可以反过来。对下面的进行分析,GroupType与filename进行合并,然后else正好合并到了上一级。但是 Msg这一块却没有办法优化,因为其需要回掉。同时,才将submitAction合并成一个函数。另外,这个应该也可以用消息传递、或者promise对象来完成。

1561947417403

优化成这个样子:

1561947737954

fieldcontainer,按钮同行布局

说明:有这样的需求,在原有的行上面增加一个按钮。之前试过用代码动态的插,费时费力,效果也不是很好。后来,fieldcontainer很好用。

但是需要注意的是,fieldcontainer会将内部的标签fieldLabel提到外部,但是字段的name不能提升,因为其没有。另外,整体的布局样式,应该也是加载fieldcontainer上的,一般都是放在defaults上面。

代码如下:

{
	xtype:'fieldcontainer',
	fieldLabel:'宣传片',
	layout: 'hbox',
	hidden: (GroupType==1)?false:true,
	items:[{
		xtype:'filefield',
		name: 'organization_video',
		buttonText: '浏览',
		flex:1,
	},{
		xtype:'button',
		margin:'0 0 0 5',
		text:'清空',
		idth:42
	}]
}

效果图如下:

1561951575932

小技巧

  • 初始化变量的一些小技巧

下面的方式初始化数组。(有用过类似的方法,但下面的例子让我感兴趣。)

fields: "name,price,count".split(","),
  • items数组的动态

其中items3为根据flag的值动态加载。若加,设置为null,不会加载。

items:[items1,items2,(flag)?item3:null,item4]
  • 发布版本,清除输出
console.log= function(){}
  • 数组合并或者复制数组
a=[1,2,3,4]
b=[...a] //拷贝a,并形成一个副本
//同理,合并数组
c=[...a,...b]
//之前用的是比较low的方法
c=[]
c.splice(c.length,0,...a)

store loadData方法使用

重要的事说三遍,是数组,一定要是数组,否则不起作用。 loadData应该是立即执行的。

var item = [{id:-1,name:'请选择地区'}];//一定要是数组格式。
var data=me.cityList[newValue]?Ext.Array.merge(item,me.cityList[newValue]):item;
cityCombo.store.loadData(data);//data一定要是数组格式,否则不起作用

combo选择功能,再次遇到坑。花费了接近1天的时间才解决。具体代码参见;OrganizationGrid.js

说一下具体的思路:

省、市、县的三级联动。默认从后台获取好数据,并组织好,以父级的id作为分组。然后整体一次加载到数据,并存到变量中。每次选择变化时,监听省、市的变化,值变化后,在change事件中,获取到下级的组件store,并动态的为其加载数据,loadData,注意,loadData方法加载很快,然后由于更改了选择,应使下级选择重置。

每级只需要关注change事件,并为下级动态的加载tstore。

部分代码:

{					
	xtype:"combo",
	fieldLabel : '城  市', 									
	name : 'city_id',
	store:{
		fields: ['id', 'name'],
		data  :[{id:-1,name:'请选择地区'}]  //增加了请选择的功能
	},
	queryMode: 'local',
	displayField: 'name',
	editable:false,
	forceSelection:true,//有无均可,editable已经限制了。
	valueField: 'id',
	value:-1,  //默认选择
	listeners:{
		'change':function( cmp, newValue, oldValue, eOpts ) {
			var countyCombo=me.DepartmentWin.query('combo[name=county_id]')[0];
			var item = [{id:-1,name:'请选择区县'}];  //注意其为数组。
			var data=me.cityList[newValue]?Ext.Array.merge(item,me.cityList[newValue]):item;
			countyCombo.store.loadData(data); //加载的一定要为数组,否则,则不成功
			countyCombo.setValue(-1);
		}
	}					
}


//直接使用下列方式,进行重置
// bform.setValues({province_id:3,city_id:39,county_id:336})
//在beforeshow事件中,进行调用。
getCityValue:function(id){
	var me=this;
	Ext.Ajax.request({
		url: SHINEVMSHTTP+'/admin/organization/Organization/cityvalue',
		method:'POST',
		params:{"GroupID":id},
		success: function(response, opts) {
			var obj = Ext.decode(response.responseText);
			console.log(obj);
			if(!obj){
				return ;
			}
			var bform = me.DepartmentWin.items.items[0].getForm();
			//直接使用下列方式,进行重置
			// bform.setValues({province_id:3,city_id:39,county_id:336})
			bform.setValues({province_id:obj.province_id,city_id:obj.city_id,county_id:obj.county_id});
		}
	});
},

loadData

me.down、me.query、getForm().findField(‘name’) 查找组件

查找组件,不只有一种方法。每种方法,都有各自的有缺点。但是最好,别用getAt索引的方式查找,代码扩展性不好,增加减少或者重新排序组件,都需要重新更改索引。

//常用的
me.down() //只能找到第一个组件
me.items.getAt(0) //通过索引查找
//根据特殊的属性查找,能保存找到的是唯一,但是注意,其为数组。
me.query('combo[name=doctor]')[0]
//如果是form组件,则可以使用下列方式查找。如果不是,理论上也能包裹一层form
getForm().findField('name')

//只能往查找
me.up()

filefield设置value(重点在思路)

核心的做法,找到dom,利用原生的js代码,修改dom值。

var bform=c.down('form').getForm();
bform.findField ('attachment').inputEl.dom.value= 12343431;

先说一下排查的思路。首先,为啥setValue不起作用?其实我也不知道。后来,直接在控制台输出,发现如下:
输入cmp.setValue,得到一个空函数。

ƒ () {}

输出cmp.getValue,得到如下

ƒ () {
        var me = this,
            val = me.rawToValue(me.processRawValue(me.getRawValue()));
        me.value = val;
        return val;
    }

输入,cmp.getRawValue,如下:

ƒ () {
        var me = this,
            v = (me.inputEl ? me.inputEl.getValue() : Ext.valueFrom(me.rawValue, ''));
        me.rawValue = v;
        return v;
    }

输入cmp.inputEl.getValue

ƒ (asNumber) {
            var value = this.dom.value;
            return asNumber ? parseInt(value, 10) : value;
        }

然后发现,实际上是dom的值。this指 cmp.inputEl。所以直接就修改了。(之前在Ext的源码里面,也试图找,结果没有找到)

所以,很重要的一点就是,学会利用控制台查看,函数的源码。

另外,之前做了另外一个版本的代码是真长。


var videoSrc=me.currentEditRecord.organization_video;
		var html='<div class="x-form-text-wrap x-form-text-wrap-default"><input type="text" value="'+videoSrc+'" readonly="readonly" class="x-form-field x-form-text x-form-text-default x-form-text-file  x-form-empty-field x-form-empty-field-default"></div>';

//这个是主要的逻辑,因为show的时候,要显示能改变的html的值
'show':function(){
	var video=me.DepartmentWin.down('filefield[name=organization_video]');
	var el=video.getEl();
	var div=me.DepartmentWin.inputDiv=el.down('#'+el.id+'-inputWrap');
	div.setStyle('display','none');
	me.DepartmentWin.inputNew=Ext.DomHelper.insertAfter(div,html,true);
}
    
//change后,就显示新的原本的textfield的原本的值

listeners:{
	'change':function(cmp, value, eOpts ){
		var div=me.DepartmentWin.inputDiv;
		div && div.setStyle('display','block');
		me.DepartmentWin.inputNew.setStyle('display','none');
	}
}

利用nginx在本地开发

按之前的套路,开发的时候利用netty,在本地跑一个web服务,跑前端服务,然后在首页index.html配置好后端的服务器,以后代码中所有的url,均利用该配置,这样凡是ajax调用的地方,均访问真实的后端服务。在去年沈阳加班的时候,那个时候就在想,如何整合前、后端的。

所以,我那时就想到了,用nginx将前后整合到一台服务器上。再后来,我又发现netty本身是个服务器,也能代理后端的请求。我记得我当时还作了笔记。有空,放到一起。

之前采取方式的弊端:

  • 前后端分离,ajax等存在跨域,虽然可以通过设置响应通来允许访问。但是对与session、cookie和Ext中的表单支持不好。十分不方便调试。如果,1、上传文件,能上传成功,但是具体的相应是不对的。2、form提交表单,也会返回错误。
  • 无法利用session,那么无法利用到角色、登录功能。那么开发过程中,只能手动的设置角色。先将角色写死,然后后期整合的时候,再进行调整,非常的不方便。

那么,具体说,nginx的代理服务。

  • 设置本地的开发域名host。win+r 运行,输入system32,依次找到drivers\etc,在host文件中,增加一行,dev.com。我host文件内容如下,最后一行是新添加的。

    127.0.0.1       localhost
    127.0.0.1       zend.scc
    127.0.0.1       www.openpoor.com
    127.0.0.1       www.myspace.com
    127.0.0.1       dev.com
  • 配置nginx的代理功能

    找到nginx目录,在conf/nginx.conf文件中,有设置。在http 大括号内,最末尾添加自己的代理内容。记得配置好后,运行 nginx.exe -t 进行测试配置文件。如果错误了,查看错误,并解决错误。

    server{
    	listen 80;
    	server_name dev.com;
    	location / {
    		proxy_pass http://localhost:1841/;
    	}
    	location /admin {
    		proxy_pass http://172.168.4.36/admin;
    	}
    }

    说明:默认将所有的访问都重定向到前端服务器locahost:1841,然后将以/admin开头的重定向到后端服务器172.168.4.36

    完美访问:

1560829022696

奇葩的代理需求

需求:同事要做版本的迁移工作,需要将数据从原有的系统中迁移到新版本中。所有就有了两个数据库。想根据不同的请求,使用不同的数据库。

核心原理是,添加不同的请求头,根据不同的请求头,选择不同的库。如下:

  • php代码:

    $params =  参数1 ;// 数据库参数
    if(isset($_SERVER["HTTP_DATABASE"]) && strpos($_SERVER["HTTP_DATABASE"],'shinejinyu') >= 0){
    	
    	$params  =  参数2;
    }
  • nginx配置:

    server{
    	listen 80;
    	server_name dev.com;
    	location / {
    		proxy_set_header database 'shinejinyu';
    		proxy_pass http://172.168.4.36/;
    	}
    }

php

Zend引入模块

Zend要求引入的模块,跟类的名称必须一致。否则会报如下错误。

1561019557714

显示的具体原因。

1561019586941

字符串变量

$fun='hello';
self::$fun();//会调用静态函数  hello,并输出。

数据导出功能

利用现成的PHPExcel功能,要求自己对原有的功能,再进行一次封装。所以,对调用者来说,更方便、更通用。满足两个功能:1、从数据库查出的数据,每行一条记录,填充到excel中;2、有简单的替换功能,通过设置header功能。

代码的通用性比较好,是要对其进行更高级的抽象。这个时候确实感受到了抽象的重要性。另外,这个代码核心的思想就是:1、用数组写一行数组。2、用数组写一列数据。3、用数组,指定起点,填充一列数据。这些都是要对原有提供的底层的api进行封装。

代码质量不高。但是应该从中学会抽象。

  • 遇到的第一个下马威:

错误提示如下,只能将570的break注释掉,然后能正常的生成文件呢。(不明白为啥会有这个错误)

Fatal error: 'break' not in the 'loop' or 'switch' context in D:\ShineVMS\xampp\htdocs\admin\library\PHPExcel\PHPExcel\Calculation\Functions.php on line 570

1561024246876

  • 准备工作
//入口文件,添加默认的库的导入路径 set_include_path 添加
set_include_path( . PATH_SEPARATOR . 'library/phpexcel')
//将库文件放到 library/phpexcel,大小写没有关系。
//引入库
Zend_Loader::loadClass('PHPExcel');
  • 核心php代码:
<?php
/*********
 * 2019年6月20日
 * 将从数据库查询出的数据导出为excel格式。 author 苏传超
*********/
class DataExportUtils{
	
	static protected $titleInfo = array(
		array('patient_name','患者',10),
		array('patient_sex','性别',10,array(1=>'男',2=>'女')),
		array('patient_birthday','年龄',10,'getBrithAge'),
		array('patient_id_card','身份证',10,'tostring'),
		array('patient_phone','手机号',10),
		array('department_name','科室',10),
		// array('conversation_id','病区',10),
		array('hospital','所属医院',10),
		array('doctor','医生',10),
		array('conversation_schedule_date','预约日期',10),
		array('visit_status','就诊状态',10,array(0=>'未预检',1=>'预检等候',2=>'叫号',3=>'过号',4=>'复诊',5=>'诊结',6=>'复诊等候')),//门诊(未预检=0,预检等候=1,叫号=2,过号=3,复诊=4,诊结=5,复诊等候=6)
		array('order_type','预约方式',10,array(1=>'预约机',2=>'手机',3=>'工作站预约')),  //预约机=1,手机=2,工作站预约=3
		array('pay_type','支付方式',10,array(1=>'微信',2=>'支付宝')), //微信=1,支付宝=2
		array('pay_status','支付状态',10,array(0=>'未支付',1=>'等待支付',2=>'已支付',3=>'等待退款',4=>'已退款')),  //未支付=0,等待支付=1,已支付=2,等待退款=3,已退款=4
		array('pay_ordernumber','订单号',10,'tostring'),
		array('pay_price','价格',10),
		array('pay_time','下单时间',10)
	);
	static protected $colIndex = array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ');
	/***********
	 * 2019年6月20日
	 * 导出数据
	***********/
	public static function exportData($records){
		// echo json_encode($records);exit;
		$objPHPExcel=new PHPExcel();
		$objPHPExcel->getProperties()
					->setCreator("神州视翰")
					->setLastModifiedBy("神州视翰")
					->setTitle("数据EXCEL导出")
					->setSubject("数据EXCEL导出")
					->setDescription("会议数据")
					->setKeywords("excel")
					->setCategory("result file");

		$objPHPExcel->setActiveSheetIndex(0);
		//设置表头
		foreach(self::$titleInfo as $k =>$v){
			$key = self::$colIndex[$k].'1';
			$value= $v[1];
			$objPHPExcel->getActiveSheet()->setCellValue($key, $value);
		}
		//填充数据
		foreach($records as $index=>$value){
			foreach(self::$titleInfo as $k =>$v){
				$key = self::$colIndex[$k].($index+2);
				$cellValue = self::convert($value[$v[0]],$k);
				$objPHPExcel->getActiveSheet()->setCellValue($key, $cellValue);// PHPExcel_Cell_DataType::TYPE_STRING
			}
		}
		//设置居左
		$objPHPExcel->getDefaultStyle()->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
		//设置列宽
		foreach(self::$titleInfo as $k =>$v){
			$key = self::$colIndex[$k];
			if(!empty($v[2])){
				$objPHPExcel->getActiveSheet()->getColumnDimension($key)->setWidth($v[2]);
			}
		}
		$objPHPExcel->getActiveSheet()->setTitle("会议导出记录");	
		$objPHPExcel->setActiveSheetIndex(0);
		$fileName = iconv('utf-8', 'gb2312',"会议导出");
		$time = date('Y-m-d-H-i-s',time()).".xls";
		
		//保存结果
		$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel,'Excel5');
		$objWriter -> save($_SERVER['DOCUMENT_ROOT']."/".$fileName.$time);//			 
		$settingdata = array('code'=>0,'url'=>"会议导出".$time);
		return $settingdata;
	}
	/***********
		2019年1月7日
		获取数组的值
	***********/
	public static function getValue($map,$key){
		if(array_key_exists($key,$map)){
			return $map[$key];
		}
		return $key;//修改bug,原来是返回$map
	}
	/***********
	 * 2019年6月21日  
	 * 数值转换函数  苏传超
	***********/
	public static function convert($v,$index){
		if(empty(self::$titleInfo[$index][3])){
			return $v;
		}
		$cv=self::$titleInfo[$index][3];
		if(is_array($cv)){
			return self::getValue($cv,$v);
		}
		if(is_string($cv)){
			return self::$cv($v);
		}
	}
	/***********
	 * 2019年6月21日  
	 * 根据出生日期,求年龄  苏传超
	***********/
	public static function getBrithAge($v){
		return date('Y')-date('Y',strtotime($v));
	}
	public static function tostring($v){
		return "'$v";
	}
}

产生虚拟ip、mac地址

具体代码如下:

$ip=sprintf("0.%d.%d.%d",mt_rand(1,255),mt_rand(1,255),mt_rand(1,255));
$mac=sprintf("00:00:%02X:%02X:%02X:%02X",mt_rand(1,255),mt_rand(1,255),mt_rand(1,255),mt_rand(1,255));

上面的代码,虽然是简单,但是想记录当时的思维过程。使用了sprintf函数,第一次感觉到C语言的标准函数功能强大之处。前人,设计C语言函数库的时候,真是非常强大。记得有一次,用js做时间处理的时候,就在想js里面为啥没有这样的函数,所以只好手动补0,1:2转换为01:02的格式。

写代码,首先要有大局的思路。首先想大体的想,可能有几种方式。然后逐一考虑各个方法的优缺点。择优选择。上面代码的思维过程:1、将ip作为一个整体来处理。比如:192.168.2.235当成256进制的数,然后将产生的一个比较大的数字,再转换为ip,再进行分隔。所以,有点麻烦了不是。2、后来,中午吃饭,快走回来的时候,想到了,ip呢,当随机数处理。MAC地址,就当作字符处理。随机选择[0-9A-F]中的一个字符。再join。3、实际编码的过程中,简单粗暴,直接按上面的代码处理。反正为啥要用到sprintf函数,不知道,灵光一现吧。

(差点想要循环,join等函数呢。)

php中对应的序列解包功能 - 可变数量的参数列表

php中,好像没有办法,将一个数组,直接解包成逐个的变量,代入到函数的调用中。那么只好使用了替代的方法,call_user_func_array,跟js中的apply,有点像。算是一种替代的方法。

$params=array("00.00.%02X.%02X.%02X.%02X");
for($i=0;$i<substr_count($params[0], '%');$i++){
	$params[]=mt_rand(1,255);
}
echo call_user_func_array('sprintf',$params);

作为对比,看js的代码

function(a,b,c){return c}
test.apply(this,[1,2,3])  //3  这个跟call_user_func_array像。
test.call(this,1,2,3)    //这个是直接调用
test(...[1,2,3])        //其实,想要的是这个运算符

后来发现,php中也有类似的写发。叫可变数量的参数列表(PHP5.6版本以上)

//直接调用
echo sprintf('00.00.%02X.%02X.%02X.%02X',...array(1,2,3,4));

//就是将传入的参数变成一个索引数组
function app(...$num) {
    var_dump($num); //输出为一个索引数组
}
echo app(1, 2, 3, 4);
//就是传入一个数组,参数依次接收
function app($a, $b)
{
    echo $a + $b; //输出3
}
$arr = [1, 2];
app(...$num);

php上传文件格式判断

上传文件时,当然需要对上传文件的格式进行判断。之前一直采用获取文件的扩展名,转换小写,并判断是否在允许上传的格式中。然后,又发现另外一种好方法,好像浏览器会根据上传的文件,进行自动判断格式。type字段,有上传的格式信息。如下:

之前的老方法:

//允许上传的文件类型
if(!in_array($ext,array('.avi','.mov','.rmvb','.mpeg','mpg','mpe','.mkv','.flv','.m4v','.rm','.3gp','.dat','.mp4','.wmv'))){
	return array('msg'=>'文件上传失败,视频格式不支持!','success'=>false);
}
if(strpos($_FILES[$field]['type'],'video/')!==0){
	 return array('msg'=>'文件上传失败,视频格式不支持!','success'=>false);
}

php中的树数据结构,遍历

public static function getOrganization($structureId){
	$db = Zend_Registry::get('db'); 
	$rows=$db->fetchAll('select organization_id id ,organization_parent_id pid,organization_name name from  organization where structure_id =:id;',array('id'=>$structureId));
	$mapping = array_column($rows,'pid','id');
	self::$organizationData = $mapping;
	$result=array();
	foreach($mapping as $id=>$pid){
		$cur=$pid;
		$path=array($pid);
		while($cur>0){//末级节点
			$cur=$mapping[$cur];
			$path[]=$cur;
			if(count($path)>20){//防止死循环
				die("maximum recursion depth exceeded ");
			}
		}
		$result[$id]=join(',',$path);//路径
	}
	return $result;
}

分组统计

分组统计,则需要对数据创建数组。有可能是高维的。高维数组,注意初始化数组。js代码估计也差不多。

//对数据进行分组统计,创建一个三维的数组,然后每个数组叠加。

$data = array();  //data ['organization_id']['date'][]
foreach($rows as $k=>&$row){
	$oid=$row['organization_id'];
	$date = $row['conversation_schedule_date'];
	if(!isset($data[$oid])){//如果当前的,不存在,则进行初始化。
		$data[$oid]=array();
	}
	if(!isset($data[$oid][$date])){
		$data[$oid][$date]=array();
	}
	$data[$oid][$date][]=$row;
}
//最后再对每个数组,进行求和、或者处理。或者在上一步的时候,进行处理。

然后将多维的数组,再次降成一维数组,输出。

需要注意的时候,嵌套的循环,内部的foreach,则是外层的循环变量,如$oidArr。我在写的过程成,错写成了$data。

$result =array();
foreach($data as $oid=>$oidArr){
	foreach($oidArr as $date=>$dateArr){
		$basehospital = self::$organizationData[$oid];
		// {'date':'2019-07-01','basehospital':'华山医院','price':'11','count':'1'},
		$result[]=array('date'=>$date,'basehospital'=>$basehospital,'price'=>$dateArr['price'],'count'=>$dateArr['count']);
	}
}

数据库

sql中如果有BETWEEN关键字,由于其已经包含了and,所以整句应该添加一个括号,包裹。

and (visit_status BETWEEN 1 and 10)
  • sql替换

    两种其实差不多,后一种为了查询更多内容。

$sql = "select user_name from user_extend where user_id = (select user_id from schedule where schedule_id = $id)";

$sql= "select s.user_id,user_name from schedule  as s  left join user_extend as u  on s.user_id=u.user_id where schedule_id = $id";