`
禹爸爸
  • 浏览: 87496 次
  • 性别: Icon_minigender_1
  • 来自: 苏州
社区版块
存档分类
最新评论

Ext面向对象开发实践

ext 
阅读更多

这是自己在学习Ext过程中一个写的一个示例程序,仅为练习,功能并不全,现将其记录在我的博客中,希望可以和学习Ext的朋友一起分享。

 

示例程序简述:

这个Demo为了演示如将使用GridPanel显示数据,并为GridPanel添加工具条按钮,提供弹出式窗体新增数据。

 

使用到的Ext组件

这个Demo涉及到Ext中的GridPanel,FormPanel和Window三个组件。

 

效果图

 

 

现在开始讲解代码,首先看一下创建GridPanel的代码片段

  1. //定义数据列表面板类
  2. PersonListGridPanel=Ext.extend(Ext.grid.GridPanel,{
  3. insertWin:null,
  4. updateWin:null,
  5. constructor:function(){
  6. //添加自定义事件
  7. this.addEvents("rowSelect");
  8. this.insertWin=newInsertPersonInfoWindow();
  9. this.insertWin.on("submit",this.onInsertWinSubmit,this);
  10. this.updateWin=newUpdatePersonInfoWindow();
  11. this.updateWin.on("submit",this.onUpdateWinSubmit,this);
  12. PersonListGridPanel.superclass.constructor.call(this,{
  13. renderTo:Ext.getBody(),
  14. width:360,
  15. height:300,
  16. frame:true,
  17. sm:newExt.grid.RowSelectionModel({
  18. singleSelect:true,
  19. listeners:{
  20. "rowselect":{
  21. fn:function(sm,rowIndex,r){
  22. this.fireEvent("rowSelect",r);//触发自定义事件
  23. },
  24. scope:this
  25. }
  26. }
  27. }),
  28. store:newExt.data.JsonStore({
  29. data:[{name:"李宗盛",age:28,sex:"男"},{name:"林忆莲",age:26,sex:"女"}],
  30. fields:["name","sex","age"]
  31. }),
  32. draggable:false,
  33. enableColumnMove:false,
  34. title:"FirstGrid",
  35. //iconCls:'icon-grid',
  36. colModel:newExt.grid.ColumnModel([
  37. {header:"StaffName",width:100,menuDisabled:true},
  38. {header:"Age",width:100,sortable:true,dataIndex:"age",align:"right",tooltip:"这里是提示信息"},
  39. {header:"Sex",width:100,sortable:true,dataIndex:"sex",align:"center"}
  40. ]),
  41. tbar:[{
  42. text:"添加人员",
  43. handler:function(){
  44. //***************************************************
  45. //如果没有重写InsertPersonInfoWindow的Close方法
  46. //在调用之前需要检查其实例insertWin是否被释放
  47. //使用示例:
  48. //if(!this.insertWin){
  49. //this.insertWin=newInsertPersonInfoWindow();
  50. //}
  51. //this.insertWin.show();
  52. //***************************************************
  53. this.insertWin.show();
  54. },
  55. scope:this
  56. },"-",{
  57. text:"修改人员",
  58. handler:function(){
  59. varr=this.getActiveRecord();
  60. if(!r)return;
  61. //一定要先调用Show方法,而后再调用Load方法,
  62. //否则数据不会被呈现出来
  63. this.updateWin.show();
  64. this.updateWin.load(r);
  65. },
  66. scope:this
  67. },"-",{
  68. text:"删除人员",
  69. handler:function(){
  70. varr=this.getActiveRecord();
  71. if(!r)return;
  72. Ext.MessageBox.confirm("删除","删除当前人员信息?",function(btn){
  73. if(btn=="yes"){
  74. this.delRecord(r);
  75. }
  76. },this);
  77. },
  78. scope:this
  79. }]
  80. });
  81. },
  82. getActiveRecord:function(){
  83. varsm=this.getSelectionModel();
  84. //没有选中的记录时,是抛出异常还是返回null???????
  85. return(sm.getCount()===0)?null:sm.getSelected();
  86. },
  87. insert:function(r){
  88. this.getStore().add(r);
  89. },
  90. delRecord:function(r){
  91. this.getStore().remove(r);
  92. },
  93. onInsertWinSubmit:function(win,r){
  94. this.insert(r);
  95. },
  96. onUpdateWinSubmit:function(win,r){
  97. alert('onUpdateWinSubmit');
  98. }
  99. });

数据维护面板代码

  1. //定义数据维护面板,在后面定义的新增和修改窗体中都会使用到该面板
  2. PersonInfoFormPanel=Ext.extend(Ext.form.FormPanel,{
  3. constructor:function(){
  4. PersonInfoFormPanel.superclass.constructor.call(this,{
  5. //title:"PersonInfo",
  6. frame:true,
  7. width:360,
  8. labelWidth:40,
  9. defaultType:"textfield",
  10. defaults:{anchor:"92%"},
  11. items:[{
  12. name:"name",//注意,这里使用name属性而不是id,因为PersonInfoFormPanel会被添加和插入两个窗体使用,使用id会有冲突,导致组件不能被正确显示
  13. fieldLabel:"Name",
  14. allowBlank:false,
  15. emptyText:"请输入姓名",
  16. blankText:"姓名不能为空"
  17. },{
  18. name:"age",
  19. fieldLabel:"Age",
  20. vtype:"age"
  21. },{
  22. hiddenName:"sex",
  23. xtype:"combo",
  24. fieldLabel:"Sex",
  25. store:newExt.data.SimpleStore({
  26. fields:[
  27. {name:'Sex'}
  28. ],
  29. data:[["男"],["女"]]
  30. }),
  31. mode:'local',
  32. displayField:'Sex',
  33. triggerAction:'all',
  34. emptyText:'选择性别...'
  35. }]
  36. })
  37. },
  38. getValues:function(){
  39. if(this.getForm().isValid()){
  40. returnnewExt.data.Record(this.getForm().getValues());
  41. }
  42. else{
  43. throwError("ErrorMessage");
  44. }
  45. },
  46. setValues:function(r){
  47. this.getForm().loadRecord(r);
  48. },
  49. reset:function(){
  50. this.getForm().reset();
  51. }
  52. });

对数据的维护有新增和更新两个动作,从设计的角度来讲就需要编写两个窗体对其进行操作。细心的朋友一定会想,新增和更新的动作都是针对相同的数据表,那么能不能只写一个窗体,然后复用呢?答案是肯定的。下面我们就先写一个窗体基类。

  1. //新增,修改窗体基类
  2. PersonInfoWindow=Ext.extend(Ext.Window,{
  3. form:null,
  4. constructor:function(){
  5. this.addEvents("submit");
  6. this.form=newPersonInfoFormPanel();
  7. //Ext.apply(this.form,{baseCls:"x-plain"});
  8. PersonInfoWindow.superclass.constructor.call(this,{
  9. plain:true,
  10. width:360,
  11. modal:true,//模式窗体
  12. onEsc:Ext.emptyFn,
  13. closeAction:"hide",
  14. items:[this.form],
  15. buttons:[{
  16. text:"确定",
  17. handler:this.onSubmitClick,
  18. scope:this
  19. },{
  20. text:"取消",
  21. handler:this.onCancelClick,
  22. scope:this
  23. }]
  24. });
  25. //alert(this.onSubmitClick);
  26. },
  27. close:function(){
  28. //需要重写CLose方法,
  29. //否则在窗体被关闭其实体会被释放
  30. this.hide();
  31. this.form.reset();
  32. },
  33. onSubmitClick:function(){
  34. //alert(Ext.util.JSON.encode(this.form.getValues().data));
  35. try{
  36. this.fireEvent("submit",this,this.form.getValues());
  37. this.close();
  38. }
  39. catch(_err){
  40. return;
  41. }
  42. },
  43. onCancelClick:function(){
  44. this.close();
  45. }
  46. });

基类写了之后,我们就可以使用继承的方法来编写新进和更新窗体了。

  1. //定义新增数据窗体
  2. InsertPersonInfoWindow=Ext.extend(PersonInfoWindow,{
  3. title:"添加"
  4. });
  5. //==============================================================================
  6. //定义编辑数据窗体
  7. UpdatePersonInfoWindow=Ext.extend(PersonInfoWindow,{
  8. title:"修改",
  9. load:function(r){
  10. this.form.setValues(r);
  11. }
  12. });

为了区分新增和更新窗体,我们在其各自的实现类中为其指定了Title属性,另外在更新窗体类中需要另外添加一个用于加载待编辑数据的方法Load。

 

脚本部分算是完成了,下面看看如何在HTML中使用。HTML中的引用代码

  1. <scripttype="text/javascript">
  2. Ext.QuickTips.init();
  3. Ext.form.Field.prototype.msgTarget="side";
  4. Ext.BLANK_IMAGE_URL="http://localhost:8080/ext-2.2/resources/images/default/s.gif";
  5. Ext.apply(Ext.form.VTypes,{
  6. "age":function(_v){
  7. if(/^/d+$/.test(_v)){
  8. var_age=parseInt(_v);
  9. if((_age>0)&&(_age<200))returntrue;
  10. }
  11. returnfalse;
  12. },
  13. "ageText":"年龄必须在0到200之间",
  14. "ageMask":/[0-9]/i
  15. });
  16. Ext.onReady(function(){
  17. newPersonListGridPanel();
  18. });
  19. </script>

代码很简洁,也很清晰。只需要创建一个PersonListGridPanel即可,因为它自身包含了新增、修改的窗体对象,而新增和修改窗体中都使用到了负责数据编辑的PersonInfoFormPanel。

在PersonInfoFormPanel中使用了VTypes进行数据验证。

新增和修改窗体仅仅是界面,负责将用户在PersonInfoFormPanel中填写的数据传回到ListGrid中以便保存,或是将ListGrid中的数据传递到PersonInfoFormPanel中进行呈现,供用户编辑。

 

附上完整的HTML代码和JavaScript代码文件。

Grid.html

  1. <html>
  2. <head>
  3. <title>ExtGrid</title>
  4. <linkrel="stylesheet"type="text/css"href="http://localhost:8080/ext-2.2/resources/css/ext-all.css"/>
  5. <scripttype="text/javascript"src="http://localhost:8080/ext-2.2/adapter/ext/ext-base.js"></script>
  6. <scripttype="text/javascript"src="http://localhost:8080/ext-2.2/ext-all.js"></script>
  7. <scripttype="text/javascript"src="PersonListGridPanel.js"></script>
  8. <scripttype="text/javascript">
  9. Ext.QuickTips.init();
  10. Ext.form.Field.prototype.msgTarget="side";
  11. Ext.BLANK_IMAGE_URL="http://localhost:8080/ext-2.2/resources/images/default/s.gif";
  12. Ext.apply(Ext.form.VTypes,{
  13. "age":function(_v){
  14. if(/^/d+$/.test(_v)){
  15. var_age=parseInt(_v);
  16. if((_age>0)&&(_age<200))returntrue;
  17. }
  18. returnfalse;
  19. },
  20. "ageText":"年龄必须在0到200之间",
  21. "ageMask":/[0-9]/i
  22. });
  23. Ext.onReady(function(){
  24. newPersonListGridPanel();
  25. });
  26. </script>
  27. </head>
  28. <body>
  29. </body>
  30. </html>

PersonListGridPanel.js

 

  1. //定义数据列表面板类
  2. PersonListGridPanel=Ext.extend(Ext.grid.GridPanel,{
  3. insertWin:null,
  4. updateWin:null,
  5. constructor:function(){
  6. //添加自定义事件
  7. this.addEvents("rowSelect");
  8. this.insertWin=newInsertPersonInfoWindow();
  9. this.insertWin.on("submit",this.onInsertWinSubmit,this);
  10. this.updateWin=newUpdatePersonInfoWindow();
  11. this.updateWin.on("submit",this.onUpdateWinSubmit,this);
  12. PersonListGridPanel.superclass.constructor.call(this,{
  13. renderTo:Ext.getBody(),
  14. width:360,
  15. height:300,
  16. frame:true,
  17. sm:newExt.grid.RowSelectionModel({
  18. singleSelect:true,
  19. listeners:{
  20. "rowselect":{
  21. fn:function(sm,rowIndex,r){
  22. this.fireEvent("rowSelect",r);//触发自定义事件
  23. },
  24. scope:this
  25. }
  26. }
  27. }),
  28. store:newExt.data.JsonStore({
  29. data:[{name:"李宗盛",age:28,sex:"男"},{name:"林忆莲",age:26,sex:"女"}],
  30. fields:["name","sex","age"]
  31. }),
  32. draggable:false,
  33. enableColumnMove:false,
  34. title:"FirstGrid",
  35. //iconCls:'icon-grid',
  36. colModel:newExt.grid.ColumnModel([
  37. {header:"StaffName",width:100,menuDisabled:true},
  38. {header:"Age",width:100,sortable:true,dataIndex:"age",align:"right",tooltip:"这里是提示信息"},
  39. {header:"Sex",width:100,sortable:true,dataIndex:"sex",align:"center"}
  40. ]),
  41. tbar:[{
  42. name:"btnFirst",
  43. //text:"First",
  44. iconCls:"x-tbar-page-first",
  45. handler:function(){
  46. this.getSelectionModel().selectFirstRow();
  47. },
  48. scope:this
  49. },{
  50. name:"btnPrev",
  51. //text:"Prev",
  52. iconCls:"x-tbar-page-prev",
  53. handler:function(){
  54. this.getSelectionModel().selectPrevious();
  55. },
  56. scope:this
  57. },{
  58. name:"btnNext",
  59. //text:"Next",
  60. iconCls:"x-tbar-page-next",
  61. handler:function(){
  62. this.getSelectionModel().selectNext();
  63. },
  64. scope:this
  65. },{
  66. name:"btnLast",
  67. //text:"Last",
  68. iconCls:"x-tbar-page-last",
  69. handler:function(){
  70. this.getSelectionModel().selectLastRow();
  71. },
  72. scope:this
  73. },"-",{
  74. text:"添加",
  75. handler:function(){
  76. //***************************************************
  77. //如果没有重写InsertPersonInfoWindow的Close方法
  78. //在调用之前需要检查其实例insertWin是否被释放
  79. //使用示例:
  80. //if(!this.insertWin){
  81. //this.insertWin=newInsertPersonInfoWindow();
  82. //}
  83. //this.insertWin.show();
  84. //***************************************************
  85. this.insertWin.show();
  86. },
  87. scope:this
  88. },"-",{
  89. text:"修改",
  90. handler:function(){
  91. varr=this.getActiveRecord();
  92. if(!r)return;
  93. //如何将数据填充到窗体中?
  94. this.updateWin.show();
  95. this.updateWin.load(r);
  96. },
  97. scope:this
  98. },"-",{
  99. text:"删除",
  100. handler:function(){
  101. varr=this.getActiveRecord();
  102. if(!r)return;
  103. Ext.MessageBox.confirm("删除","删除当前人员信息?",function(btn){
  104. if(btn=="yes"){
  105. this.delRecord(r);
  106. }
  107. },this);
  108. },
  109. scope:this
  110. }]
  111. });
  112. },
  113. getActiveRecord:function(){
  114. varsm=this.getSelectionModel();
  115. //没有选中的记录时,是抛出异常还是返回null???????
  116. return(sm.getCount()===0)?null:sm.getSelected();
  117. },
  118. insert:function(r){
  119. this.getStore().add(r);
  120. },
  121. delRecord:function(r){
  122. this.getStore().remove(r);
  123. },
  124. onInsertWinSubmit:function(win,r){
  125. this.insert(r);
  126. },
  127. onUpdateWinSubmit:function(win,r){
  128. alert('onUpdateWinSubmit');
  129. }
  130. });
  131. //==============================================================================
  132. //定义数据维护面板,在后面定义的新增和修改窗体中都会使用到该面板
  133. PersonInfoFormPanel=Ext.extend(Ext.form.FormPanel,{
  134. constructor:function(){
  135. PersonInfoFormPanel.superclass.constructor.call(this,{
  136. //title:"PersonInfo",
  137. frame:true,
  138. width:360,
  139. labelWidth:40,
  140. defaultType:"textfield",
  141. defaults:{anchor:"92%"},
  142. items:[{
  143. name:"name",//注意,这里使用name属性而不是id,因为PersonInfoFormPanel会被添加和插入两个窗体使用,使用id会有冲突,导致组件不能被正确显示
  144. fieldLabel:"Name",
  145. allowBlank:false,
  146. emptyText:"请输入姓名",
  147. blankText:"姓名不能为空"
  148. },{
  149. name:"age",
  150. fieldLabel:"Age",
  151. vtype:"age"
  152. },{
  153. hiddenName:"sex",
  154. xtype:"combo",
  155. fieldLabel:"Sex",
  156. store:newExt.data.SimpleStore({
  157. fields:[
  158. {name:'Sex'}
  159. ],
  160. data:[["男"],["女"]]
  161. }),
  162. mode:'local',
  163. displayField:'Sex',
  164. triggerAction:'all',
  165. emptyText:'选择性别...'
  166. }]
  167. })
  168. },
  169. getValues:function(){
  170. if(this.getForm().isValid()){
  171. returnnewExt.data.Record(this.getForm().getValues());
  172. }
  173. else{
  174. throwError("信息不完整");
  175. }
  176. },
  177. setValues:function(r){
  178. //alert(Ext.util.JSON.encode(r.data));
  179. this.getForm().loadRecord(r);
  180. },
  181. reset:function(){
  182. this.getForm().reset();
  183. }
  184. });
  185. //==============================================================================
  186. //新增,修改窗体基类
  187. PersonInfoWindow=Ext.extend(Ext.Window,{
  188. form:null,
  189. constructor:function(){
  190. this.addEvents("submit");
  191. this.form=newPersonInfoFormPanel();
  192. //Ext.apply(this.form,{baseCls:"x-plain"});
  193. PersonInfoWindow.superclass.constructor.call(this,{
  194. plain:true,
  195. width:360,
  196. modal:true,//模式窗体
  197. onEsc:Ext.emptyFn,
  198. closeAction:"hide",
  199. items:[this.form],
  200. buttons:[{
  201. text:"确定",
  202. handler:this.onSubmitClick,
  203. scope:this
  204. },{
  205. text:"取消",
  206. handler:this.onCancelClick,
  207. scope:this
  208. }]
  209. });
  210. //alert(this.onSubmitClick);
  211. },
  212. close:function(){
  213. //需要重写CLose方法,
  214. //否则在窗体被关闭其实体会被释放
  215. this.hide();
  216. this.form.reset();
  217. },
  218. onSubmitClick:function(){
  219. //alert(Ext.util.JSON.encode(this.form.getValues().data));
  220. try{
  221. this.fireEvent("submit",this,this.form.getValues());
  222. this.close();
  223. }
  224. catch(_err){
  225. return;
  226. }
  227. },
  228. onCancelClick:function(){
  229. this.close();
  230. }
  231. });
  232. //==============================================================================
  233. //定义新增数据窗体
  234. InsertPersonInfoWindow=Ext.extend(PersonInfoWindow,{
  235. title:"添加"
  236. });
  237. //==============================================================================
  238. //定义编辑数据窗体
  239. UpdatePersonInfoWindow=Ext.extend(PersonInfoWindow,{
  240. title:"修改",
  241. load:function(r){
  242. this.form.setValues(r);
  243. }
  244. });

版权声明:本文为博主原创文章,未经博主允许不得转载。

分享到:
评论

相关推荐

    Ext面向对象开发实践代码第1/2页

    总结一下,这个示例展示了以下ExtJS面向对象开发的关键点: 1. **继承与扩展**:`PersonListGridPanel` 类继承自 `Ext.grid.GridPanel`,增加了自己的特性。 2. **组件组合**:使用了GridPanel、FormPanel和Window...

    Ext面向对象开发实践(续)

    在《Ext面向对象开发实践》的后续部分,我们将深入探讨如何实现数据表的CRUD(Create, Read, Update, Delete)操作,确保用户在刷新页面后仍能保留他们的操作。 首先,为了从服务器获取并展示数据,我们需要创建一...

    快意编程EXT JS Web开发技术详解.part3

    Ext JS Web开发技术详解》首先对Ext JS进行了概述,然后通过一个简单的示例带领读者快速入门,在读者对Ext JS有了初步印象后,又重点介绍了JavaScript的面向对象技术、Ext JS API文档的使用方法、Ext JS的开发流程,...

    ext的课件,ppt版,适合有面向对象基础人士

    Ext是一个强大的JavaScript库,专为构建富客户端Ajax应用程序而设计。它以其面向对象的编程模型、丰富的用户界面组件和...对于有面向对象基础的开发者,Ext提供的面向对象编程模型将进一步增强他们开发Web应用的能力。

    ext js中文开发手册

    EXT JS采用了面向对象的编程模式,支持类的继承和重写。这允许开发者在不修改原始代码的情况下,扩展和定制现有组件的功能。 **十八、补充资料与概述** 除了上述内容,EXT JS还提供了丰富的文档和社区资源,包括...

    快意编程 EXT JS Web开发技术详解.pdf

    Ext JS Web开发技术详解》首先对Ext JS进行了概述,然后通过一个简单的示例带领读者快速入门,在读者对Ext JS有了初步印象后,又重点介绍了JavaScript的面向对象技术、Ext JS API文档的使用方法、Ext JS的开发流程,...

    快意编程EXT JS Web开发技术详解.part2

    Ext JS Web开发技术详解》首先对Ext JS进行了概述,然后通过一个简单的示例带领读者快速入门,在读者对Ext JS有了初步印象后,又重点介绍了JavaScript的面向对象技术、Ext JS API文档的使用方法、Ext JS的开发流程,...

    快意编程EXT JS Web开发技术详解.part1

    Ext JS Web开发技术详解》首先对Ext JS进行了概述,然后通过一个简单的示例带领读者快速入门,在读者对Ext JS有了初步印象后,又重点介绍了JavaScript的面向对象技术、Ext JS API文档的使用方法、Ext JS的开发流程,...

    ext/ajax开发资料

    EXT基于JavaScript,所以理解JavaScript语法和面向对象编程概念是非常必要的。开发者可能会学到如何创建EXT组件、布局管理、事件处理等。 总的来说,这个“ext/ajax开发资料”应包含EXT库的Ajax使用方法,以及可能...

    ExtJs培训sample_for面向对象设计

    通过学习和实践这些样本,开发者可以深入理解ExtJS的面向对象设计,提升代码的可维护性和可扩展性,同时也能更好地适应大型项目的需求。在实际开发中,合理地运用面向对象设计,可以提高代码的组织结构,降低代码...

    ext-base.js

    2. **类系统**:EXT使用面向对象的编程方式,"ext-base.js"中包含了类的创建、继承和实例化等机制,使得开发者可以方便地创建自定义组件。 3. **事件系统**:EXT的事件模型是其交互性的重要组成部分,"ext-base.js...

    面向对象的固态硬盘设计

    #### 二、面向对象的固态硬盘设计实践 **1. SSD面临的挑战** 传统的存储堆栈针对的是磁盘驱动器,而SSD则具有完全不同的内部结构和特性。例如,SSD的随机写入速度远低于顺序写入速度,并且存在写入放大、磨损均衡...

    ext 打造华丽页面

    JavaScript是EXT JS的基石,因此掌握JavaScript的基本语法和面向对象编程是使用EXT的前提。EXT JS 1.1虽然较为古老,但仍然包含了许多现代前端开发的理念,如MVC(Model-View-Controller)架构,这种架构模式有助于...

    ext3.0开发之-->葵花宝典

    开发者应该熟悉JavaScript的面向对象特性、事件处理、DOM操作等,这样才能更好地利用EXT3.0构建动态、交互性强的Web应用。 综上所述,《ext3.0开发之--&gt;葵花宝典》是一个全面的EXT3.0学习资源,无论你是初学者还是...

    gwtext编写的小系统

    GWT的主要优势在于它的强类型、面向对象的编程模型,以及对Java开发工具的全面支持,如Eclipse IDE。 EXT GWT(GXT)是EXT JS的GWT版本,它扩展了GWT的功能,提供了大量的UI组件,如表格、树形视图、图表等,这些...

    Ext一些方法的重写

    在JavaScript这种没有类的语言中,`Ext.extend()` 提供了面向对象编程的模拟。当我们想要自定义一个新类,并希望它继承自另一个已有类时,可以使用这个方法。以下是一个基本的使用示例: ```javascript Ext.extend...

    Ext JS开发资料大汇集

    Ext JS通过扩展JavaScript,提供了面向对象的编程方式,增强了其在构建复杂应用中的能力。 “web”标签表明,Ext JS主要用于Web应用的开发。它利用Ajax技术进行异步通信,实现页面的无刷新更新,极大地提升了用户...

    Ext5.0.7z包

    在JavaScript世界中,Ext JS以其面向对象的架构和丰富的组件库著称。Ext5.0.7z可能包含以下关键组成部分: 1. **源代码**:压缩包中可能包含Ext JS 5.0的所有源代码文件,这些文件通常以.js格式存在,开发者可以...

Global site tag (gtag.js) - Google Analytics