BUI 是用来快速构建界面交互的渐进式UI框架, 专一webapp开发, 开发者只需关注业务的开发, 界面的布局及交互交给BUI, 开发出来的应用, 能够嵌入平台 ( 微信公众号, 微信小程序webview, 聆客, 钉钉, 淘宝, 支付宝等 ), 亦能够跟其它第三方平台打包成独立应用( Bingotouch , Cordova , Dcloud , APICloud , Appcan 等), 最终能够全跨平台展现. (包括Ipad)结合BUI提供的BUI-Fast编辑插件, NPM工具, BUI更是一个移动快速开发的解决方案. 能够解决如下常见问题.php
什么是组件化呢?
组件化是指解耦复杂系统时将多个功能模块拆分、重组的过程,有多种属性、状态反映其内部特性。css
在BUI 1.6版本之前有没有组件化呢?
先来看看组件包含什么, 模板, 模块, 样式, 数据四个部分, BUI一直有组件化, 单页就是一个组件, 一个单页由一个同名html(包含样式),js组成. 移动开发因为页面较小, 把一个页面当作是一个大的组件, 组件里面会结合多个控件, 比方选项卡,轮播图,列表刷新等. html
比方下面例子:前端
bui.load({ url:"pages/list/index.html" })
跳转到列表页面, 咱们即可知道该目录下还有 pages/list/index.js
文件来处理业务, 默认的模块名为pages/list/index
. 最简单的路由, 一切无需配置.vue
pages/list/index.htmljava
<div class="bui-page bui-box-vertical"> <header></header> <main> <!-- 轮播图 --> <div id="slide" class="bui-slide"></div> </main> <footer></footer> </div>
pages/list/index.jsnode
loader.define(function(require,export,module){ // 业务代码 var pageview = { init: function(){ // 轮播图控件初始化 var uiSlide = bui.slide({ id:"#slide", height: 300, data: [{ image:"images/slide01.jpg" },{ image:"images/slide02.jpg" }] }) } } // 页面跳转便执行 pageview.init(); return pageview; })
路由跳转内部作了什么?jquery
// 加载模板 loader.import("pages/list/index.html",function(res){ // id 指向动态建立的路由页面id $("#id").html(res); // 执行js模块, 若是该模块没有被建立过, 会自动执行 loader.require("pages/list/index") })
只是简单示例说明, 实际作了更多复杂的处理. 单页的开发模块里面,$
选择器要替换成router.$
选择器, 若是页面重复被加载进来,$
从document
查找会致使找到多个相同ID,router.$
则限制了只在当前页面.
随着业务的深刻, 单页组件里面承载了较多业务逻辑, 很差维护. 上面的例子咱们看到,
pages/list/index
模块里面, 初始化了一个控件, 一个页面若是只有一个控件, 那也没什么, 但每每不止这些, 咱们可能页面还有TAB, 每一个TAB里面就有一个轮播图组件, 那咱们就要区分不一样的ID初始化不一样的轮播图了. 若是把轮播图抽离成一个单独的组件, 这部分业务就能够抽离出来.
咱们新建了一个目录 components
用来存放这些抽离的组件.git
轮播图模板
pages/components/slide/index.htmlgithub
<div class="bui-slide"></div>
id="slide"
这个属性咱们去掉了,若是模板包含id,意味着建立出来的组件会有多个相同id.
轮播图组件定义
pages/components/slide/index.js
loader.define(function(require,export,module){ // 接收`component` 标签上的属性参数 var params = bui.history.getParams(module.id); // 轮播图控件初始化 var uiSlide = bui.slide({ // 经过父层的id 找到当前的 bui-slide id:`#${module.id} .bui-slide`, height: 300, data: [{ image:"images/slide01.jpg" },{ image:"images/slide02.jpg" }] }) return uiSlide; })
轮播图组件加载, component
标签若是无id属性, 会自动建立一个随机guid
, 也就是组件内部获取到的 module.id
pages/list/index.html
<div class="bui-page bui-box-vertical"> <header></header> <main> <!-- 新闻轮播图 type 为自定义属性,用于区分不一样数据 --> <component name="pages/components/slide/index" type="news"></component> <!-- 视频轮播图 --> <component name="pages/components/slide/index" type="video"></component> </main> <footer></footer> </div>
轮播图样式定义
样式没有独立的做用域, 要防止跟其它样式冲突, 那组件须要一个独立的样式名.
<style> .slide-skin .bui-slide-main {} </style> <div class="bui-slide slide-skin"></div>
组件包含数据,以确保该组件能正常运行, 咱们能够把轮播图的组件再进行优化.
抽离轮播图测试数据, 示例数据
pages/components/slide/index.json
[{ image:"images/slide01.jpg" },{ image:"images/slide02.jpg" }]
完整的轮播图组件
pages/components/slide/index.js
loader.define(function(require,export,module){ // 接收`component` 标签上的属性参数 var params = bui.history.getParams(module.id); // 轮播图控件初始化 var uiSlide = bui.slide({ // 经过父层的id 找到当前的 bui-slide id:`#${module.id} .bui-slide`, height: 300, data: [] }) // 经过不一样参数请求区分不一样数据 bui.ajax({ // 模块在被加载或者被移到其它路径下, 都不会影响到这个路径的地址. url:`${module.path}/index.json`, data:{ // 请求接口的不一样类型 type: params.type }, success: function(res){ // 修改轮播图数据 uiSlide.option("data",res); } }) return uiSlide; })
组件预览:
地址栏上输入如下地址即可预览组件效果.index.html#pages/components/slide/index
模拟属性传参
在地址上加上参数
index.html#pages/components/slide/index?type=news
BUI 组件有3种表现形式, 路由的跳转是页面组件,component
标签加载是一种局部组件,bui.page
是弹出加载组件, 层级最高.
比方: 点击个人, 须要在当前页插入一个登陆页面.
pages/main/main.js
loader.define(function(require,export,module){ var pageveiw = { init: function(){ // 初始化 this.tab = this.tabInit(); }, isLogin: false, pageLogin: null, tabInit: function(){ var that = this; var tab = bui.tab({ id: "#tab" }); // tab 的滑动,点击,都会触发 to 事件. tab.on("to",function(){ var index = this.index(); // 若是跳转到第3个,而且未登陆, 则插入登陆页. if( index === 3 && !that.isLogin ){ if( that.pageLogin ){ // 第二次打开就好 that.pageLogin.open(); return; } // 第一次初始化 that.pageLogin = bui.page({ url:"pages/login/index.html", // 告诉登陆页, 是从tab的第三个跳转过去的, 那登陆回来之后就能够再跳转到第三个Tab. param: { type: "tab", index: 3 } }) } }) return tab; } } // 初始化 pageveiw.init(); return pageview; })
pages/login/index.js
loader.define(function(require,export,module){ var parasm = bui.history.getParams(module.id); var pageview = { init: function(){ this.bind(); }, bind: function(){ router.$("#btnLogin").click(function(){ // 检测登陆是否成功, 是则跳转回上一个页面, 而且触发to事件 // 主动关闭 // var dialog = bui.history.getPageDialog(module.id); // dialog.close(); bui.back(function(mod){ // 关闭弹窗 mod.pageLogin && mod.pageLogin.close(); // 修改登陆状态 mod.isLogin = true; // 拿到上一个模块,调用tab实例的to方法, 跳到第3各索引, 触发 监听的on事件. mod.tab.to(parasm.index) }) }) } } // 初始化 pageview.init(); return pageview; })
做为登陆页面组件, 就须要处理多种类型, 比方从路由跳转的, 比方以组件层的方式加载的, 那分别要作什么事情?
这个登陆的完整示例工程能够在 BUI的3种权限登陆 里面找到. tablogin2
工程.
实例分发实际上是bui.store
的一个mixins
参数, 这个跟vue
的混入是同样的. 适合处理比较复杂的页面, 把模块分发出去, 便于维护, 跟组件是同样的道理, 但这个是分离的.
比方有个详情页面, 详情里面有表单, 正文, 附件.
这里咱们使用 bui.store
来实现. 案例的预览地址 实例分发
详情模板
pages/detail/index.html
<div class="bui-page bui-box-vertical"> <header> <div class="bui-bar"> <div class="bui-bar-left"> <a class="bui-btn" onclick="bui.back();"><i class="icon-back"></i></a> </div> <div class="bui-bar-main">详情</div> <div class="bui-bar-right"> </div> </div> <ul id="floorNav" class="bui-nav bui-nav-skin01"> <li class="bui-btn active">表单</li> <li class="bui-btn">正文</li> <li class="bui-btn">附件(2)</li> </ul> </header> <main> <div id="floor" class="bui-floor"> <div class="bui-floor-main container-y"> <div class="panel-list bui-interval"> <!-- 表单 --> <view name="pages/store/views/form/index"></view> <!-- 正文 --> <view name="pages/store/views/article/index"></view> <!-- 附件 --> <view name="pages/store/views/attach/index"></view> </div> </div> <div class="bui-floor-foot"></div> </div> </main> </div>
详情模块
pages/detail/index.js
loader.define([ "pages/store/views/form/index", "pages/store/views/article/index", "pages/store/views/attach/index" ], function(form, article, attach, require, exports, module) { // 初始化数据行为存储 var bs = bui.store({ el: ".bui-page", scope: "page", data: { title: "测试标题" }, mixins: [form, article, attach], methods: {}, watch: {}, computed: {}, templates: {}, beforeMount: function() { // 数据解析前执行, 修改data的数据示例 // this.$data.a = 2 }, mounted: function() { var that = this; // 数据解析后执行 var floor = bui.floor({ id: "#floor", menu: "#floorNav", floorItem: "view" }) } }) return bs; })
表单模板:
pages/store/views/form/index.html
<style> .panel-form .bui-list .bui-btn { border-bottom: 0; } </style> <div class="bui-panel panel-form bui-floor-item"> <div class="bui-panel-head" name="page">表单</div> <div class="bui-panel-main container-xy" text="page" b-template="page.tplForm(page.formData)"> </div> </div>
表单模块:
pages/store/views/form/index.js
loader.define(function(require,exports,module) { // 在这里初始化控件 var pageview = { data: { formData: { title:"《广州XXX2020年年中预算审批》", phone: "13800138000" } }, methods: { callhim: function(phone){ // 打电话 bui.unit.tel(phone); } }, templates: { tplForm: function(data) { var html = ""; html += `<ul class="bui-list list-form"> <li class="bui-btn clearactive bui-box-align-top"> <label class="bui-label">标题</label> <div class="span1"> <div class="bui-value">${data.title}</div> </div> </li> <li class="bui-btn clearactive bui-box-align-top"> <label class="bui-label">电话</label> <div class="span1"> <div class="bui-value phone" b-click="page.callhim2(${data.phone})"> <b>${data.phone}</b><i class="icon-phone"></i> </div> </div> </li> ... </ul>`; return html; } }, mounted: function(param) { console.log("mounted form") } }; // 抛出模块 return pageview; })
其它组件相似, 返回一个对象, 最终在详情的实例上合并. 这种分发只是业务的拆分, 并没有独立做用域. 若是须要独立做用域, 则应该改成如下加载.
详情模板
pages/detail/index.html
<div class="bui-page bui-box-vertical"> <header> ... </header> <main> <div id="floor" class="bui-floor"> <div class="bui-floor-main container-y"> <div class="panel-list bui-interval"> <!-- 表单 --> <component name="pages/store/views/form/index"></component> <!-- 正文 --> <view name="pages/store/views/article/index"></view> <!-- 附件 --> <view name="pages/store/views/attach/index"></view> </div> </div> <div class="bui-floor-foot"></div> </div> </main> </div>
详情模块
pages/detail/index.js
loader.define([ "pages/store/views/article/index", "pages/store/views/attach/index" ], function(article, attach, require, exports, module) { // 初始化数据行为存储 var bs = bui.store({ el: ".bui-page", scope: "page", data: { title: "测试标题" }, mixins: [article, attach], methods: {}, watch: {}, computed: {}, templates: {}, beforeMount: function() { // 数据解析前执行, 修改data的数据示例 // this.$data.a = 2 }, mounted: function() { var that = this; // 数据解析后执行 var floor = bui.floor({ id: "#floor", menu: "#floorNav", floorItem: "view" }) } }) return bs; })
表单模板:
pages/store/views/form/index.html
scope
改成了form
<style> .panel-form .bui-list .bui-btn { border-bottom: 0; } </style> <div class="bui-panel panel-form bui-floor-item"> <div class="bui-panel-head" name="page">表单</div> <div class="bui-panel-main container-xy" text="page" b-template="form.tplForm(form.formData)"> </div> </div>
表单模块
pages/store/views/form/index.js
loader.define(function(require,exports,module) { // 在这里初始化控件 var bs = bui.store({ el: `#${module.id}`, scope: "form", data: { formData: { title:"《广州XXX2020年年中预算审批》", phone: "13800138000" } }, methods: { callhim: function(phone){ // 打电话 bui.unit.tel(phone); } }, templates: { tplForm: function(data) { var html = ""; html += `<ul class="bui-list list-form"> <li class="bui-btn clearactive bui-box-align-top"> <label class="bui-label">标题</label> <div class="span1"> <div class="bui-value">${data.title}</div> </div> </li> ... </ul>`; return html; } }, mounted: function(param) { console.log("mounted form") } }); // 抛出模块 return bs; })
在使用单页路由初始化之后, 咱们便有了一个历史记录router.history
, 新版1.6之后, 把router.history
抽离出来, 经过bui.history
去访问. 这样不管是单页开发, 仍是多页开发, 都能经过bui.history
去获取实例及参数. 而且在这个对象里面, 页面传参,组件传参,page传参, 均可以在这个历史记录里面获取到之间的依赖关系.组件的加载是一条线, 线的末端能够操控线的前端.
var allHistory = bui.history.get(); // allHistory 默认的历史记录 [{ component: {}, effect: "push", exports: {}, id: "buib7522-dc12-3f33-cdb5-29122a2cf1f6", name: "pages/store/view", page: {}, param: {}, replace: false, syncHistory: true, toggle: null, url: "pages/store/view.html" }]
pages/detail/index.html
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>BUI多页开发示例</title> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/buijs/lib/latest/bui.css"> </head> <body> <div class="bui-page bui-box-vertical"> <header> <div class="bui-bar"> <div class="bui-bar-left"> <div class="bui-btn" onclick="bui.back();"><i class="icon-back"></i></div> </div> <div class="bui-bar-main"> 多页加载组件 </div> <div class="bui-bar-right"> </div> </div> </header> <main> <!-- 加载轮播图组件 --> <component name="pages/components/slide/index"></component> </main> </div> <script src="https://cdn.jsdelivr.net/npm/buijs/lib/zepto.js"></script> <script src="https://cdn.jsdelivr.net/npm/buijs/lib/latest/bui.js"></script> <script src="index.js"></script> </body> </html>
多页的初始化
pages/detail/index.js
bui.ready(function(){ // 初始化 var allHistory = bui.history.getLast(); // 多页开发的历史记录, 永远只有一个. 页面跟页面之间没法交互, 可是页面跟组件跟组件层之间的交互是没问题的. })
pages/detail/index.html
<div class="bui-page bui-box-vertical"> <header> <div class="bui-bar"> <div class="bui-bar-left"> <div class="bui-btn" onclick="bui.back();"><i class="icon-back"></i></div> </div> <div class="bui-bar-main"> 单页加载组件 </div> <div class="bui-bar-right"> </div> </div> </header> <main> <!-- 加载轮播图组件 --> <component name="pages/components/slide/index"></component> </main> </div>
pages/detail/index.js
loader.define(function(require,export,module){ // 获取最后一条历史记录 var currentHistory = bui.history.getLast(); })
同样的组件代码, 除了脚本模块的定义不一样之外. 多页简单, 单页则在体验,跟操控上会有更多灵活空间. 能够根据须要自行选择.
推荐从新安装
buijs
cli工具. 记得关闭360等一切会阻止C盘写入的程序.
npm install -g buijs
// 所有权限示例 buijs create -t case-indexlogin // 部分权限示例 buijs create -t case-tablogin // 163的组件化示例 buijs create -t case-163
gitee
, 国内构建的速度会快不少.结合新版出了一些快速书写, 建议更新, 若是使用 vscode
只需在插件搜索 bui-fast
即可.
新增了一些方法及控件, 其它更新了控件的一些问题, 就不一一列举了, 感兴趣能够看看官网的changelog
码字不易, 欢迎关注bui神速
, 跟咱们一块儿交流移动开发的问题, 常见问题还请搜索官方文档, 咱们会不按期更新一些技巧.