BUI 是用来快速构建界面交互的渐进式UI框架, 专注webapp开发, 开发者只需关注业务的开发, 界面的布局及交互交给BUI, 开发出来的应用, 可以嵌入平台 ( 微信公众号, 微信小程序webview, 聆客, 钉钉, 淘宝, 支付宝等 ), 亦可以跟其它第三方平台打包成独立应用( Bingotouch , Cordova , Dcloud , APICloud , Appcan 等), 最终可以全跨平台展示. (包括Ipad)
结合BUI提供的BUI-Fast编辑插件, NPM工具, BUI更是一个移动快速开发的解决方案. 可以解决以下常见问题.
什么是组件化呢?
组件化是指解耦复杂系统时将多个功能模块拆分、重组的过程,有多种属性、状态反映其内部特性。
在BUI 1.6版本以前有没有组件化呢?
先来看看组件包含什么, 模板, 模块, 样式, 数据四个部分, BUI一直有组件化, 单页就是一个组件, 一个单页由一个同名html(包含样式),js组成. 移动开发由于页面较小, 把一个页面看成是一个大的组件, 组件里面会结合多个控件, 比方选项卡,轮播图,列表刷新等.
比方下面例子:
bui.load({ url:"pages/list/index.html" }) 复制代码
跳转到列表页面, 我们便可知道该目录下还有 pages/list/index.js
文件来处理业务, 默认的模块名为pages/list/index
. 最简单的路由, 一切无需配置.
pages/list/index.html
<div class="bui-page bui-box-vertical"> <header></header> <main> <!-- 轮播图 --> <div id="slide" class="bui-slide"></div> </main> <footer></footer> </div> 复制代码
pages/list/index.js
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; }) 复制代码
路由跳转内部做了什么?
// 加载模板 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
用来存放这些抽离的组件.
轮播图模板 pages/components/slide/index.html
<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神速
, 跟我们一起交流移动开发的问题, 常见问题还请搜索官方文档, 我们会不定期更新一些技巧.