[Ext JS 6 By Example 翻译] 第3章 - 基础组件
来源:互联网 发布:java没学好可以做什么 编辑:程序博客网 时间:2024/05/19 22:28
转载自:http://www.jeeboot.com/archives/1219.html
在本章中,你将学习到一些 Ext JS 基础组件的使用。同时我们会结合所学创建一个小项目。这一章我们将学习以下知识点:
- 熟悉基本的组件 – 按钮,文本框,日期选择器等等
- 表单字段的校验
- 菜单和工具栏
- 设计一个表单
- 计算器程序– 本章的示例项目
本章的主要目的是创建一个表单设计和一个计算器示例项目。以下图分别展示了表单设计和计算器设计。
首先,你观察下列表单设计,你会发现我们使用了大量的控件,例如 label 和文本框。
以下图展示了表单的设计:
继续,设计计算器程序大量的使用了按钮控件。所以你首要学习的是按钮和 handler 。随后在本章最后我们将会构建一个 计算器程序。在这个过程中,你会知道如何使 view(视图) 和 controller(控制器)进行交互并协同工作。我们还将看到如何绑定 view model(视图模型) 的属性到一个 view(视图) 的字段上。
下图为计算机的设计展示:
熟悉基本组件
Ext JS 有大量的优秀的控件,现在让我们开始认识这些基础的组件吧。
Ext.Button
这是一个很常用的控件;handler 是用于处理单击事件,如以下代码所示:
Ext.create('Ext.Button', { text: 'My Button', renderTo: Ext.getBody(), handler: function() { alert('click'); }});
前面代码的输出:
我在第二章已经介绍过如何运行样例代码,但这里我还想再次重申这一点,此文档中的大部分样例代码都是可以直接运行的。你可以选择在你本地设备上或者在 Sencha Fiddle 上执行这些示例代码。你可以访问Sencha Fiddle 并将上面的代码键入到 launch 函数中,运行并查看结果。如果你访问了 https://fiddle.sencha.com 将会看到下列代码:
Ext.application({ name : 'Fiddle', launch : function() { Ext.Msg.alert('Fiddle', 'Welcome to Sencha Fiddle!'); }}) ;
现在粘贴下列的创建按钮的样例代码,运行并查看结果:
Ext.application({ name : 'Fiddle', launch : function() { Ext.create('Ext.Button', { text: 'My Button', renderTo: Ext.getBody(), handler: function() { alert('click'); } }); }});
- 不是所有的代码都可以这样运行,此外并非所有的示例代码都会有视觉呈现。
你还可以使用 listeners 配置添加更多的事件处理器,如以下代码所示:
Ext.create('Ext.Button', { text: 'My Button', renderTo: Ext.getBody(), listeners: { click: { fn: function(){ //Handle click event alert('click'); } }, mouseout: { fn: function(){ //Handle double click event alert('Mouse out'); } } }});
以上代码只是创建了一个简单的按钮,你还可以创建很多不同的按钮,有 link button(连接按钮),menu button(菜单按钮),toggle button(开关按钮) 等等;
来创建一个链接按钮,设置 href 属性,如以下代码所示:
Ext.create('Ext.Button', { renderTo: Ext.getBody(), text: 'Link Button', href: 'http://www.sencha.com/' });
上面创建的链接按钮输出如图。当点击它则打开链接:
通过设置 menu 属性,创建一个菜单按钮,如以下代码所示:
Ext.create('Ext.Button', { text: 'My Button', renderTo: Ext.getBody(), menu: [{ text: 'Item 1' }, { text: 'Item 2' }, { text: 'Item 3' }]});
输出如下,当点击时出现下拉菜单:
Ext.Button 还有许多属性,例如 bind, cls, disabled,html,tooltip,tpl 等等,你可以根据自己需求使用。
Ext.MessageBox
Ext.window.MessageBox 类提供了 message box 实现。Ext. MessageBox 是一个单例对象。你可以使用 MessageBox 弹出一个警告,信息确认,提示输入等等。
下列代码将弹出一个简单的提示信息。这里解释一下 Ext.Msg 是 Ext. Messagebox 类的别名:
Ext.Msg.alert('Info', 'Document saved!');
下列代码将弹出一个消息确认框,button 为选择的值,取 yes 或 no :
Ext.Msg.confirm('Confirm', 'Are you want to cancel the updates?', function(button){ if('yes'==button) { } else { }});
你也可以自定义这个 message box 如下:
Ext.MessageBox.show({ title:'Save Changes?', msg: 'Do you want to save the file?', buttons: Ext.MessageBox.YESNO, fn: function(button){ if('yes'==button){ }else if('no'==button){ } }, icon: Ext.MessageBox.QUESTION }) ;
上面代码输出如下:
表单和表单字段
现在我们看一下都有哪些表单相关的组件。
Ext.form.Panel
这个 form panel (表单面板)继承自 panel 并添加了表单相关的功能,例如字段管理,校验,提交等等。form panel 的默认布局是 anchor layout ,但是如果需要你可以改变这个配置。
form panel 有一个很方便的配置为 fieldDefaults,它可以用于指定表单内所有字段的默认类型。
fields (字段/表单域)
Ext JS 提供了很多内置的表单字段。比较常用的一些字段:
Ext.form.field.Checkbox Ext.form.field.ComboBox Ext.form.field.Date Ext.form.field.File Ext.form.field.Hidden Ext.form.field.HtmlEditor Ext.form.field.Number Ext.form.field.Radio Ext.form.field.Text Ext.form.field.TextArea Ext.form.field.Time
我们看一下其中的一些字段的应用。
Ext.form.field.Text
这是一个基本的文本框,它具有很多有用的属性和配置。其中有一个很有用的属性是 vtype 它是用于校验的。 例如以下代码,这个 vtype 属性为 email 用于验证输入内容是否是有效的电子邮箱:
Ext.create('Ext.form.field.Text', { renderTo: Ext.getBody(), name: 'email', fieldLabel: 'Email', allowBlank: false, vtype: 'email' });
这里 allowBlank 也是一个校验属性。通过设置 allowBlank 属性为 false ,如果这个字段为空白,将会提示校验不通过。
Ext.form.field.Number
number 字段继承自 spinner 字段,spinner 字段则继承自 text 字段,进而的 number 等于是继承了两者。这个 number 字段提供了几个选项来处理数值。下列代码创建了一个数值文本框:
Ext.create('Ext.form.field.Number', { renderTo: Ext.getBody(), name: 'Count', fieldLabel: 'Count', value: 0, maxValue: 10, minValue: 0 });
你可以移除下拉按钮,方向键,鼠标滚轮监听,用配置:hideTrigger, keyNavEnabled,和 mouseWheelEnabled 。
Ext.form.field.ComboBox
下列代码创建了一个月份下拉菜单。这个 combobox 有一个配置为 store。 这个 store 是数据源,为此下拉菜单提供数据。store 是属于 ExtJS 中数据包部分, 在接下来的章节中我们会详细介绍的。
combobox 中另一个重要的配置是 queryMode 。这个属性取值可以是 ‘local’ 或者 ‘remote’。如果你设置为 remote 了,那么这个数据源 store 将在运行加载数据时发送请求从远程服务器获取数据:
var months = Ext.create('Ext.data.Store', { fields: ['abbr', 'name'], data: [{"abbr":"JAN", "name":"January"}, {"abbr":"FEB", "name":"February"}, {"abbr":"MAR", "name":"March"}, {"abbr":"APR", "name":"April"}, {"abbr":"MAY", "name":"May"}, {"abbr":"JUN", "name":"June"}, {"abbr":"JUL", "name":"July"}, {"abbr":"AUG", "name":"August"}, {"abbr":"SEP", "name":"September"}, {"abbr":"OCT", "name":"October"}, {"abbr":"NOV", "name":"November"}, {"abbr":"DEC", "name":"December"}] }) ; Ext.create('Ext.form.ComboBox', { fieldLabel: 'Choose Month', store: months, queryMode: 'local', displayField: 'name', valueField: 'abbr', renderTo: Ext.getBody() });
以上代码的输出如下:
Ext.form.field.HtmlEditor
Ext JS 也有一个非常优秀的 HTML 编辑器,它提供直接在 web 页面上处理文字的能力,如以下代码所示:
Ext.create('Ext.form.HtmlEditor', { width: 800, height: 200, renderTo: Ext.getBody() });
以上代码输出如下:
表单字段的校验
大多数表单都有自己的校验规则,例如你键入了一个非数值的内容到 number 字段,它将显示一个验证无效的提示。再有这个 text 字段(文本框) 校验属性有 allowBlank,minLength,和 maxLength 。 更进一步的,还有 regex 属性可以使用正则表达式自定义校验。
form panel 的事件
form panel 支持的部分事件:
- beforeaction: 任意动作执行前触发,例如 submit,load,doAction 这些动作执行时
- actionfailed: 执行一个动作失败时触发
- actioncomplete: 在一个动作执行完成之后触发This event will be fired after an action is completed
- validitychange: 表单键入的内容有效性发生变化时触发
- dirtychange: 表单的dirty状态改变时触发
表单字段容器
以下是一些 from panel 里很有用的容器。
Ext.form.CheckboxGroup
CheckboxGroup 继承自 FieldContainer 用于组织复选框。下列示例中,复选框组的 items 中所有的项都有相同的 name ;这有助于将得到的值作为一个单一的参数传递给服务器。
Ext.create('Ext.form.CheckboxGroup', { renderTo: Ext.getBody(), fieldLabel: 'Skills ', vertical: true, columns: 1, items: [{ boxLabel: 'C++', name: 'rb', inputValue: '1' }, { boxLabel: '.Net Framework', name: 'rb', inputValue: '2', checked: true }, { boxLabel: 'C#', name: 'rb', inputValue: '3' }, { boxLabel: 'SQL Server', name: 'rb', inputValue: '4' }]}) ;
以上代码输出如下:
Ext.form.FieldContainer
FieldContainer 是很有用的,当你想将一组相关字段附加到一个标签时。
以下代码的输出你会发现一个 label 后面绑定了两个文本框:
Ext.create('Ext.form.FieldContainer', { renderTo: Ext.getBody(), fieldLabel: 'Name', layout: 'hbox', combineErrors: true, defaultType: 'textfield', defaults: { hideLabel: 'true' }, items: [{ name: 'firstName', fieldLabel: 'First Name', flex: 2, emptyText: 'First', allowBlank: false }, { name: 'lastName', fieldLabel: 'Last Name', flex: 3, margin: '0 0 0 6', emptyText: 'Last', allowBlank: false }]});
Ext.form.RadioGroup
RadioGroup 继承自 CheckboxGroup 用于组织单选按钮。items 中的项都有相同的 name,另外这是单选的,如以下代码所示:
Ext.create('Ext.form.RadioGroup', { renderTo: Ext.getBody(), fieldLabel: 'Sex ', vertical: true, columns: 1, items: [{ boxLabel: 'Male', name: 'rb', inputValue: '1' },{ boxLabel: 'Female', name: 'rb', inputValue: '2' }]});
代码输出:
提交表单
使用 form 的 submit 方法提交表单。使用 getForm 方法获取表单并 isValid 方法进行提交前的表单内容校验。如以下代码所示:
var form = this.up('form').getForm();if (form.isValid()) { form.submit({ url: 'someurl', success: function () { }, failure: function () { } });} else { Ext.Msg.alert('Error', 'Fix the errors in the form')}
菜单和工具栏
对于你能想到的任何的菜单和工具栏 Ext JS 提供了最完整的支持。Ext.toolbar.Toolbar 用于构建一个工具栏。默认情况下任何子项在 Ext.toolbar.Toolbar 都是按钮,但是你可以添加任意控件进去,例如一个文本框,一个数值框,一个图标,一个下拉菜单等等。
规范整理你的工具栏中的项,你可以使用 空格(Ext.toolbar.Spacer), 分隔符(Ext.toolbar. Separator),和 使控件右对齐(Ext.toolbar.Fill) 。这里也可以使用快捷方式 ‘ ‘ (空格),’-‘ 和 ‘|’ (都是分隔符,只有很小的差别),和 ‘->‘ (右对齐)。
Ext.menu.Menu 用于构建一个菜单,items 属性中为 Ext.menu.Item 一个个菜单项。
一个简单的代码示例和以下截图的输出:
Ext.create('Ext.toolbar.Toolbar', { renderTo: Ext.getBody(), width: 800, items: [{ text: 'My Button' },{ text: 'My Button', menu: [{ text: 'Item 1' }, { text: 'Item 2' }, { text: 'Item 3' }] },{ text: 'Menu with divider', tooltip: { text: 'Tooltip info', title: 'Tip Title' }, menu: { items: [{ text: 'Task 1', // handler: onItemClick }, '-', { text: 'Task 2', // handler: onItemClick }, { text: 'Task 3', // handler: onItemClick }] } },'->',{ xtype: 'textfield', name: 'field1', emptyText: 'search web site' },'-','Some Info',{ xtype: 'tbspacer' },{ name: 'Count', xtype: 'numberfield', value: 0, maxValue: 10, minValue: 0, width: 60 }]});
设计一个(客户反馈)表单
现在根据之前所学,我们来设计一个表单。
我们将设计如图所示的表单:
以下是这个表单的代码。这里我维护着一个这个例子的完整的源码 https://github.com/ananddayalan/extjs-by-example-customer-feedback-form
这里我们所有的组件都在 Viewport 中。 这是一个专用的容器,它代表浏览器里应用的视图区域。
在 Viewport 中我们设置 scrollable 选项将子组件设为滚动的,使用 true 或 false 。也可以取值为 x 或 y 表示只允许水平或垂直滚动:
Ext.create('Ext.container.Viewport', { scrollable: true, items: [{ xtype: 'container', layout: { type: 'hbox', align: 'center', pack: 'center' }, items: [ { xtype: 'form', bodyPadding: 20, maxWidth: 700, flex: 1, title: 'Custom Feedback', items:[{ xtype: 'fieldcontainer', layout: 'hbox', fieldLabel: 'Name', defaultType: 'textfield', defaults: { allowBlank: false, flex: 1 }, items: [{ name: 'firstName', emptyText: 'First Name }, { name: 'lastName', margin: '0 0 0 5', emptyText: 'Last Name' }] },{ xtype: 'datefield', fieldLabel: 'Date of Birth', name: 'dob', maxValue: new Date() /* Prevent entering the future date.*/ }, { fieldLabel: 'Email Address', name: 'email', vtype: 'email', allowBlank: false }, { fieldLabel: 'Phone Number', labelWidth: 100, name: 'phone', width: 200, emptyText: 'xxx-xxx-xxxx', maskRe: /[\d\-]/, regex: /^\d{3}-\d{3}-\d{4}$/, regexText: 'The format must be xxx-xxx-xxxx' },{ xtype: 'radiogroup', fieldLabel: 'How satisfied with our service?', vertical: true, columns: 1, items: [ { boxLabel: 'Very satisfied', name: 'rb', inputValue: '1' }, { boxLabel: 'Satisfied', name: 'rb', inputValue: '2' }] },{ xtype: 'checkboxgroup', fieldLabel: 'Which of these words would you use to describe our products? Select all that apply', vertical: true, columns: 1, items: [{ boxLabel: 'Reliable', name: 'ch', inputValue: '1' }] },{ xtype: 'radiogroup', fieldLabel: 'How likely is it that you would recommend this company to a friend or colleague?', vertical: false, defaults: { padding: 20 }, items: [ { boxLabel: '1', name: 'recommend', inputValue: '1' }], buttons: [{ text: 'Submit', handler: function () { var form = this.up('form').getForm(); if (form.isValid()) { form.submit({ url: 'cutomer/feedback', success: function () {}, failure: function () {} }); } else { Ext.Msg.alert('Error', 'Fix the errors in the form') } } }] }] }] }]});
在以上代码中通过在容器级设置 defaultType 属性,这样我们就可以不必在容器的每个子组件里重复的指定 xtype 属性了。这样默认情况下,所有子组件在没有显式指定 xtype 时默认的类型都是 textfield 。
form panel 上有一个 flex 配置用于填补父容器的宽度,同时通过设置 maxWidth 为 700 限制 form panel 的最大宽度。
字段容器使用 hbox 布局将 first name 和 last name 文本框放在一个 label 标签下。
写一个计算器应用
现在我们结合目前所学构建一个完整的小项目。这是我们将要构建的计算器的设计:
文件夹结构
这是我们创建的计算器工程的目录结构。这里我不是用 sencha Cmd 生成的项目,只是从 Ext JS 复制了一些必须的文件到项目文件夹中:
完整可用的项目在这里: https://github.com/ananddayalan/extjs-by-example-calculator.
App – app.js
在 app.js 文件里我们简单的创建了 Main 视图,作为可移动窗体浮动在浏览器:
Ext.application({ name: 'Calc', launch: function () { Ext.create('Calc.view.main.Main').show(); }});
再谈 MVC 和 MVVM
第一章的时候,我们已经介绍过 MVC (Model View Controller) 和 MVVM (Model View ViewModel)。 这个示例项目的代码很好的展示了 视图,控制器,和视图模型之间的区别。
Model (模型)
这代表着数据层。model 保存的数据可以包含数据验证和逻辑。
View (视图)
这一层是用户界面。包含有 button,form,和 message box 等等组件。在我们这次写的计算器应用中 main.js 就是一个很好的视图例子。
Controller (控制器)
控制器处理 view(视图)相关的逻辑,例如 view 的 event(事件)处理,还有任何程序相关逻辑都可以写在这里。
ViewController (视图控制器) 和 Controller (控制器)
在 Ext JS 5 和 6 中,有两种类型的控制器:ViewController 和 Controller。 这个 ViewController 自 Ext JS 5 开始引进的。ViewController 是为一个指定的视图创建的控制器,但是这个控制器也可以交叉其他视图的逻辑。
ViewController 带来了一些新的概念,例如 引用和监听,简化视图与控制之间的关系。同时 View 销毁时 ViewController 也会被销毁,他们具有相同的生命周期,在这个例子中我们没有使用 引用和监听,但是在下一个例子中我们会使用的。
- 你可以使用 listeners 代替 handler 处理事件
View model
view model 封装了 view(视图)所需要的展示逻辑,绑定数据到 view 并且每当数据改变时处理更新。
它有别于 model ,view model 主要是为一个指定的视图而创建的。一个 model 是一个纯粹的数据类并可用于整个应用中,但一个 view model 是起到一个 view 和 model 之间的数据粘合剂的作用。看一下main.js 的 视图模型绑定。
视图 — Main.js
这里我为这个计算器应用创建一个视图为 Main 。这个视图里包含所有的按钮,显示字段等等。相关的事件用 controller 的方法。这个视图的控制器已经使用 controller 配置指定了。
这个视图使用 table 布局,配置为 4 列。CSS 类使用 cls 属性指定。
代码里有附加的注释:
Ext.define('Calc.view.main.Main', { extend: 'Ext.window.Window', /* 表示在当前视图加载之前先加载这些所需的类*/ requires: [ 'Calc.view.main.MainController', 'Calc.view.main.MainModel'], xtype: 'app-main', controller: 'main', /* 视图的 view model (视图模型)*/ viewModel: { type: 'main' }, resizable: false, layout: { type: 'table', columns: 4 }, /* defaultType 和 defaults 属性是用于 items 内的子组件的,任何子组件都可以覆盖这些配置 */ defaultType: 'button', defaults: { width: 50, height: 50, cls: 'btn', handler: 'onClickNumber' }, /* 这里我用 Ext.window.Window 的 header 显示计算器的结果。使用 header 你可以在浏览器里移动这个计算器。*/ header: { items: [{ xtype: 'displayfield', colspan: 4, width: 200, cls: 'display', bind: { value: '{display}' }, height: 60, padding: 0 }] }, items: [{ text: 'C', colspan: 2, width: 100, cls: 'btn-green', handler: 'onClickClear' }, { text: '+/-', cls: 'btn-green', handler: 'onClickChangeSign' }, { text: '÷', cls: 'btn-orange', handler: 'onClickOp' },{ text: '7' },{ text: '8' },{ text: '9' },{ text: '×', cls: 'btn-orange', handler: 'onClickOp' },{ text: '4' },{ text: '5' },{ text: '6' },{ text: '-', cls: 'btn-orange', handler: 'onClickOp' },{ text: '1' },{ text: '2' },{ text: '3' },{ text: '+', cls: 'btn-orange', handler: 'onClickOp' },{ text: '0', width: 100, colspan: 2 },{ text: '.', handler: 'onClickDot' },{ text: '=', cls: 'btn-orange', handler: 'onClickOp' }] });
控制器 — MainController.js
虽然这个控制器的代码有点长,这是一个非常简单的代码。控制器中有很多方法处理按钮的点击事件,例如运算符和操作数的点击处理。控制器使用了一个 model 为 Main :
Ext.define('Calc.view.main.MainController', { extend: 'Ext.app.ViewController', alias: 'controller.main', views: ['Calc.view.main.Main'], models: ['Main'], //这个 state 是一个自定义属性,用来跟踪计算器的状态。 state: { operatorClicked: false, selectedOperator: null, dotClicked: false, op1: 0, numberClicked: false, sign: true, decimal: false }, onClickClear: function () { var vm = this.getViewModel(); vm.set('display','0'); this.state.selectedOperator=null; this.state.op1=0; this.state.isPositive = true; this.state.decimal = false; this.state.sign = true; }, onClickChangeSign: function (btn) { var vm = this.getViewModel(); var cur = vm.get('display'); if(cur!='0') { if(this.state.sign===true ) { vm.set('display', '-' + cur); }else { vm.set('display', cur.toString().substring(1)); } } this.state.sign=!this.state.sign; }, onClickOp: function (btn) { if(this.state.selectedOperator!=null && this.state.numberClicked===true){ var vm = this.getViewModel(); var op2 = parseFloat(vm.get('display')); var op1 = parseFloat(this.state.op1); var result = 0; switch(this.state.selectedOperator){ case '+': result = op1 + op2; break; case '-': result = op1 - op2; break; case '×': result = op1 * op2; break; case '÷': result = op1 / op2; break; } vm.set('display', Math.round(result * 100) / 100); this.state.selectedOperator=null; } if(btn.text!='=') { this.state.operatorClicked = true; } this.state.selectedOperator = btn.text; this.state.numberClicked = false; }, onClickDot: function (btn) { if(this.state.decimal===false) { var vm = this.getViewModel(); vm.set('display', vm.get('display') + '.'); } }, onClickNumber: function (btn) { this.state.numberClicked = true; if(this.state.selectedOperator ==='='){ this.onClickClear(); } var vm = this.getViewModel(); if(this.state.operatorClicked===true) { this.state.op1= vm.get('display'); vm.set('display', btn.text); this.state.operatorClicked=false; }else{ var cur = vm.get('display'); if(cur == '0') { cur = ''; } vm.set('display', cur + btn.text); } } });
视图模型 — MainViewModel.js
这个 ViewModel 只有一个属性为 display 。这个用来绑定到计算器显示的值上。这里我们不会分别用一组字段创建模型,此外我们还将会硬编码数据。
Ext.define('Calc.view.main.MainModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.main', data: { display: 0.0 }});
在即将到来的章节中你将学习更多关于 模型,视图模型,字段,字段类型,校验 等等。
总结
在本章中,你了解了不同的基本组件,例如 文本框,数字框,按钮,菜单等等。你已经学会如何使用表单字段设计一个表单和我们之前创建了一个简单的计算器项目。
在下一章中,你将学习关于数据包的内容,例如 数据源 ,模型 ,代理等等。store ,model ,这将是有益于处理数据的。
- [Ext JS 6 By Example 翻译] 第3章 - 基础组件
- [Ext JS 6 By Example 翻译] 第3章
- [Ext JS 6 By Example 翻译] 第6章 - 高级组件
- [Ext JS 6 By Example 翻译] 第5章 - 表格组件(grid)
- [Ext JS 6 By Example 翻译] 第6章
- [Ext JS 6 By Example 翻译] 第2章
- [Ext JS 6 By Example 翻译] 第4章
- [Ext JS 6 By Example 翻译] 第5章
- [Ext JS 6 By Example 翻译] 第7章
- [Ext JS 6 By Example 翻译] 第8章
- [Ext JS 6 By Example 翻译] 第1章 – 入门指南
- [Ext JS 6 By Example 翻译] 第2章 - 核心概念
- [Ext JS 6 By Example 翻译] 第4章 - 数据包装
- [Ext JS 6 By Example 翻译] 第7章 - 图表(chart)
- [Ext JS 6 By Example 翻译] 第8章 - 主题和响应式设计
- [Ext JS 6 By Example 翻译] 第1章 – 入门指南
- Ext JS 6学习文档-第3章-基础组件
- Ext JS 6学习文档-第3章-基础组件
- PHPMailer曝远程代码执行高危漏洞(CVE-2016-10033)
- 使用python+selenium自动上传exel表中bug
- Ubuntu问题修复
- 欢迎使用CSDN-markdown编辑器
- Spring+MyBatis实现数据库读写分离方案
- [Ext JS 6 By Example 翻译] 第3章 - 基础组件
- 基于Flume的美团日志收集系统(一)架构和设计
- 软件工程-第一章 介绍
- 欢迎使用CSDN-markdown编辑器
- c# 键盘控制事件
- Xcode8快捷键
- Java代码简洁之道
- mac重装系统后,将原来的的ssh复制进去,无法登录服务器
- 通过CustomIO实现ffmpeg内存输入