BUI-Webapp-跨平台框架-16-新版功能盘点

60次阅读

共计 11607 个字符,预计需要花费 30 分钟才能阅读完成。

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 神速, 跟我们一起交流移动开发的问题, 常见问题还请搜索官方文档, 我们会不定期更新一些技巧.

正文完
 0