前端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)+'天');
}
}
效果图:

行编辑器:注意,新增加的字段,必须在store的field中添加,否则不会生效。第二次踩坑。
排序科室,未注意到共用组件。
而且共用组件的配置属性,如果是复杂变量,其也是共享内存的。
在组件初始化的时候,对其进行了判断,如果为特定的组件,则增加“排序”字段。结果,有两个问题。
- 组件因为是共有的,多次执行后,add多了多个列。

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

错误代码如下:
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:[]中实例化时,括号中,如果是已创建的实例,则不能共用,(共用,则出现,最后一个能用到,前面引用的实例是不会显示的。)如果是配置文件,则互不干扰。
效果如图:

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布局,一定要仔细处理高度跟宽度。避免造成布局问题。
问题如下 :

原因就是,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});
- 结果图:

window的自适应
设置window的组件的布局 layout:’fit’,其可以被内部的元素主动撑开,不需要自己再手动的调整高度。
这个对那种需要动态隐藏某些组件的来说,非常方便。
需要优化的嵌套if语句
if的嵌套,有时候可以变成并列条件,也可以反过来。对下面的进行分析,GroupType与filename进行合并,然后else正好合并到了上一级。但是 Msg这一块却没有办法优化,因为其需要回掉。同时,才将submitAction合并成一个函数。另外,这个应该也可以用消息传递、或者promise对象来完成。

优化成这个样子:

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
}]
}
效果图如下:

小技巧
- 初始化变量的一些小技巧
下面的方式初始化数组。(有用过类似的方法,但下面的例子让我感兴趣。)
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
完美访问:

奇葩的代理需求
需求:同事要做版本的迁移工作,需要将数据从原有的系统中迁移到新版本中。所有就有了两个数据库。想根据不同的请求,使用不同的数据库。
核心原理是,添加不同的请求头,根据不同的请求头,选择不同的库。如下:
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要求引入的模块,跟类的名称必须一致。否则会报如下错误。

显示的具体原因。

字符串变量
$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

- 准备工作
//入口文件,添加默认的库的导入路径 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";