BUI是什么?

BUI 是用来快速构建界面交互的渐进式UI框架, 专注webapp开发, 开发者只需关注业务的开发, 界面的布局及交互交给BUI, 开发出来的应用, 可以嵌入平台 ( 微信公众号, 微信小程序webview, 聆客, 钉钉, 淘宝, 支付宝等 ), 亦可以跟其它第三方平台打包成独立应用( Bingotouch , Cordova , Dcloud , APICloud , Appcan 等), 最终可以全跨平台展示. (包括Ipad)

结合BUI提供的BUI-Fast编辑插件, NPM工具, BUI更是一个移动快速开发的解决方案. 可以解决以下常见问题.

  • 移动端的适配兼容问题 (ui)
  • 交互体验不统一问题 (ui)
  • 复杂交互的控件冲突问题 (ui)
  • 多人协作问题 (模块化)
  • 微信开发的缓存问题 (模块化)
  • 后退刷新问题 (单页路由)
  • 后退多层问题 (单页路由)
  • 调试数据跨域问题 (npm)
  • 本地服务器架设问题 (npm)
  • 脚本编译问题 (npm)
  • 打包安全问题 (npm)
  • 同步刷新问题 (npm)
  • 重复安装依赖 (npm)
  • 开发效率问题 (bui-fast)
  • 使用规范问题 (bui-fast)

适合开发者

  • 后台开发者(php,java,.net)
  • 前端开发者
  • 美工
  • jquery开发者
  • vue开发者
  • 安卓开发者
  • IOS开发者

1. BUI框架新功能-组件化

什么是组件化呢?

组件化是指解耦复杂系统时将多个功能模块拆分、重组的过程,有多种属性、状态反映其内部特性。

1.1 单页组件

在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.$ 则限制了只在当前页面.

1.2 控件组件化

随着业务的深入, 单页组件里面承载了较多业务逻辑, 不好维护. 上面的例子我们看到, 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

2. BUI框架新功能-组件层

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 工程.

3. BUI框架新功能-实例分发

实例分发其实是bui.store的一个mixins参数, 这个跟vue的混入是一样的. 适合处理比较复杂的页面, 把模块分发出去, 便于维护, 跟组件是一样的道理, 但这个是分离的.

3.1 实例分发

比方有个详情页面, 详情里面有表单, 正文, 附件.

这里我们使用 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;})

其它组件类似, 返回一个对象, 最终在详情的实例上合并. 这种分发只是业务的拆分, 并无独立作用域. 如果需要独立作用域, 则应该改为以下加载.

3.2 独立作用域

详情模板
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;})

4. BUI框架新功能-历史记录

在使用单页路由初始化以后, 我们便有了一个历史记录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"}]

4.1 多页开发

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();    // 多页开发的历史记录, 永远只有一个. 页面跟页面之间无法交互, 但是页面跟组件跟组件层之间的交互是没问题的. })

4.2 单页开发

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();    })

一样的组件代码, 除了脚本模块的定义不同以外. 多页简单, 单页则在体验,跟操控上会有更多灵活空间. 可以根据需要自行选择.

5. buijs-cli 工具

推荐重新安装buijs cli工具. 记得关闭360等一切会阻止C盘写入的程序.
npm install -g buijs

5.1 新增创建案例的命令

// 全部权限示例buijs create -t case-indexlogin// 部分权限示例buijs create -t case-tablogin// 163的组件化示例buijs create -t case-163

更多登录案例

5.2 新版的模板源默认更新为 gitee, 国内构建的速度会快很多.

5.3 新增根据node版本,自动创建对应的工程文件.

5.4 创建app的工作空间, 无需每次都安装依赖, 具体教程请查看说明文件.

6. bui-fast 编辑器插件

结合新版出了一些快速书写, 建议更新, 如果使用 vscode只需在插件搜索 bui-fast便可.

7. 其它一些控件的修复及新增

新增了一些方法及控件, 其它更新了控件的一些问题, 就不一一列举了, 感兴趣可以看看官网的changelog

订阅号

码字不易, 欢迎关注bui神速, 跟我们一起交流移动开发的问题, 常见问题还请搜索官方文档, 我们会不定期更新一些技巧.