HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

MUI 和 Html 5 plus开发的APP中底部选项卡凸起大图标功能实现 [从登录页进入主页的情况]

a、官方案例的效果:

效果如下,中间有个选项卡是凸起的:

在开发工具中,创建项目的时候,选择如下即可创建模板:

注意:这个功能只能用在初始页进来就是首页的情况,如果进来的第一个页面是登录页,则登录页就会有底部选项卡,这种功能不可取。不建议使用,应该使用下面的改造案例:

b、基于官方案例的改造

上面已经提到官方案例有所欠缺,下面基于官方案例进行改造。

其实官方案例之所以会出现,如果登录页是第一个页面的话,登录页也会有底部选项卡的情况,主要原因就是其在manifest.json中的plus下配置了launchwebview:

"plus": {  
        "launchwebview": {  
            "bottom": "0px",  
            "background": "#fff",  
            "subNViews": [  
                {  

这就会导致第一个启动页面就会有底部选项卡。如果APP是先经过登录再进入主页的情况,这种方式显然不可取。

创建一个5+app新项目,index.html中如下: [这个页面模拟登录页,不会出现底部选项卡的情况]

<body>  
        <button type="button" class="mui-btn mui-btn-blue" id="info">跳转主页面</button>  
    </body>  
    <script type="text/javascript">  
        mui.plusReady(function() {  
            document.getElementById('info').addEventListener('tap', function() {  
                //打开页面  
                mui.openWindow({  
                    url: 'bth.html',  
                    id: 'bth.html'  
                });  
            });  
        })  
    </script>

其中btn.html如下:

<!DOCTYPE html>  
<html>  
    <head>  
        <meta charset="utf-8">  
        <meta name="viewport"  
            content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />  
        <title></title>  
        <script src="js/mui.js" type="text/javascript" charset="utf-8"></script>  
        <link rel="stylesheet" type="text/css" href="./css/mui.css" />  
        <script src="js/util.js" type="text/javascript" charset="utf-8"></script> <!-- -->  
        <script src="js/jquery-3.5.1.min.js" type="text/javascript" charset="utf-8"></script>  
        <script type="text/javascript" charset="utf-8">  
            mui.init();  
        </script>  
    </head>  
    <body>  
        <nav class="mui-bar mui-bar-tab">  
            <a class="mui-tab-item" href="pages/a.html">  
                <span class="mui-icon mui-icon-home"></span>  
                <span class="mui-tab-label">首页</span>  
            </a>  
            <a class="mui-tab-item" href="pages/b.html">  
                <span class="mui-icon mui-icon-phone"></span>  
                <span class="mui-tab-label">电话</span>  
            </a>  
            <a class="mui-tab-item"><!-- 给中部凸起大图标占个位置,使得左右两边的tab按钮均匀分布,实际这个tab没用到 -->  
                <span>占位</span>  
            </a>  
            <a class="mui-tab-item" href="pages/c.html">  
                <span class="mui-icon mui-icon-email"></span>  
                <span class="mui-tab-label">邮件</span>  
            </a>  
            <a class="mui-tab-item" href="pages/d.html">  
                <span class="mui-icon mui-icon-gear"></span>  
                <span class="mui-tab-label">设置</span>  
            </a>  
        </nav>  
    </body>  
    <script type="text/javascript">  
        var index = 0;  
        var subpages = ['pages/a.html', 'pages/b.html', 'pages/center.html', 'pages/c.html', 'pages/d.html'];  
        mui.plusReady(function() {  
            var self = plus.webview.currentWebview();  

            //如下在中部添加一个凸起图标  
            var leftPos = Math.ceil((window.innerWidth - 60) / 2); // 设置凸起大图标为水平居中  

            /**  
             * drawNativeIcon 绘制中部凸起大图标,具体参数含义参考plus.nativeObj.View(id, styles, tags)文档说明[http://www.html5plus.org/doc/zh_cn/nativeobj.html#plus.nativeObj.View]  
             * 实现原理:  
             *   id为bg的tag 创建带边框的圆  
             *   id为bg2的tag 创建白色矩形遮住圆下半部分,只显示凸起带边框部分  
             *   id为iconBg的红色背景图  
             *   id为icon的字体图标  
             *   注意创建先后顺序,创建越晚的层级越高  
             */  
            var drawNativeIcon = util.drawNative('icon', {  
                bottom: '5px',  
                left: leftPos + 'px',  
                width: '60px',  
                height: '60px'  
            }, [{  
                    tag: 'rect', //创建带边框的圆。 "rect" - 绘制矩形区域,与drawRect操作一致,此时id、color、position、rectStyles属性值有效  
                    id: 'bg',  
                    position: {  
                        top: '1px',  
                        left: '0px',  
                        width: '100%',  
                        height: '100%'  
                    },  
                    rectStyles: {  
                        color: '#fff',  
                        radius: '50%',  
                        borderColor: '#ccc',  
                        borderWidth: '1px'  
                    }  
                }, {  
                    tag: 'rect', //创建白色矩形遮住圆下半部分,只显示凸起带边框部分  
                    id: 'bg2',  
                    position: {  
                        bottom: '-0.5px',  
                        left: '0px',  
                        width: '100%',  
                        height: '45px'  
                    },  
                    rectStyles: {  
                        color: '#fff'  
                    }  
                }, {  
                    tag: 'rect', //创建绿色背景图  
                    id: 'iconBg',  
                    position: {  
                        top: '5px',  
                        left: '5px',  
                        width: '50px',  
                        height: '50px'  
                    },  
                    rectStyles: {  
                        color: '#00aa00',  
                        radius: '50%'  
                    }  
                }, {  
                    tag: 'font', //创建文字。 "font" - 绘制文本内容,与drawText操作一致,此时id、position、text、textStyles属性值有效;  
                    id: 'icon',  
                    text: 'B',  
                    position: {  
                        top: '0px',  
                        left: '5px',  
                        width: '50px',  
                        height: '100%'  
                    },  
                    textStyles: {  
                        fontSrc: '_www/fonts/iconfont.ttf',  
                        align: 'center',  
                        color: '#fff',  
                        size: '30px'  
                    }  
                },  
                /* {  
                tag: 'img', //"img" - 绘制图片,与drawBitmap操作一致,此时id、src、position、sprite属性值有效;   
                id: 'img',  
                src: 'img/a.png',  
                position: {  
                    top: '15px',  
                    left: '5px',  
                    width: '50px',  
                    height: '80%'  
                }  
            } */  
            ]);  

            // 把创建的中央凸起图标append 到父webview中  
            self.append(drawNativeIcon);  

            //自定义监听凸起图标点击事件  
            var active_color = '#fff'; //激活的颜色  
            drawNativeIcon.addEventListener('click', function(e) {  

                //显示凸起图标对应的页面,隐藏其它几个Tab对应的页面  
                plus.webview.show('pages/center.html');  
                plus.webview.hide('pages/a.html');  
                plus.webview.hide('pages/b.html');  
                plus.webview.hide('pages/c.html');  
                plus.webview.hide('pages/d.html');  

                //取消其它几个Tab对应的页面的选中效果  
                $(".mui-tab-item").removeClass('mui-active');  

                // 重绘字体颜色  
                if (active_color == '#fff') {  
                    drawNativeIcon.drawText('A', {}, {  
                        fontSrc: '_www/fonts/iconfont.ttf',  
                        align: 'center',  
                        color: '#000',  
                        size: '30px'  
                    }, 'icon');  
                    active_color = '#000';  
                } else {  
                    drawNativeIcon.drawText('B', {}, {  
                        fontSrc: '_www/fonts/iconfont.ttf',  
                        align: 'center',  
                        color: '#fff',  
                        size: '30px'  
                    }, 'icon');  
                    active_color = '#fff';  
                }  
            });  

            //如下把每个tab对应的页面加载到当前webview  
            for (var i = 0; i < 5; i++) {  
                var sub = plus.webview.create(  
                    subpages[i], //url  
                    subpages[i], //id  
                    {  
                        top: "45px", //子页面距离顶部的位置  
                        bottom: "50px" //子页面距离底部的位置  
                    }  
                );  

                if (i != index) {  
                    sub.hide()  
                }  

                self.append(sub);  
            }  
        })   

        //如下设置除了中部大图标的四个Tab的点击切换事件  
        var activetab = subpages[index];  
        mui(".mui-bar-tab").on('tap', 'a', function(e) {  
            var targetTab = this.getAttribute('href');  
            if (activetab == targetTab) {  
                return;  
            }  

            plus.webview.show(targetTab);  
            plus.webview.hide(activetab);  

            activetab = targetTab;  
        })  
    </script>  

</html>

其中a.html、b.html、center.html、c.html、d.html很简单,每个页面只有一句话:

<body>  
    <span>我是b页面</span>  
</body>

util.js如下:

var util = {  
    /**  
     *  简单封装了绘制原生view控件的方法  
     *  绘制内容支持font(文本,字体图标),图片img , 矩形区域rect  
     */  
    drawNative: function(id, styles, tags) {  
        var view = new plus.nativeObj.View(id, styles, tags);  
        return view;  
    }  
};

manifest.json无需配置任何东西即可。

效果如下:

Demo源码见本文后面的附件:ButtomTabHump.zip

也可以参考我的博客:https://blog.csdn.net/qzyw525942/article/details/115749934

继续阅读 »

a、官方案例的效果:

效果如下,中间有个选项卡是凸起的:

在开发工具中,创建项目的时候,选择如下即可创建模板:

注意:这个功能只能用在初始页进来就是首页的情况,如果进来的第一个页面是登录页,则登录页就会有底部选项卡,这种功能不可取。不建议使用,应该使用下面的改造案例:

b、基于官方案例的改造

上面已经提到官方案例有所欠缺,下面基于官方案例进行改造。

其实官方案例之所以会出现,如果登录页是第一个页面的话,登录页也会有底部选项卡的情况,主要原因就是其在manifest.json中的plus下配置了launchwebview:

"plus": {  
        "launchwebview": {  
            "bottom": "0px",  
            "background": "#fff",  
            "subNViews": [  
                {  

这就会导致第一个启动页面就会有底部选项卡。如果APP是先经过登录再进入主页的情况,这种方式显然不可取。

创建一个5+app新项目,index.html中如下: [这个页面模拟登录页,不会出现底部选项卡的情况]

<body>  
        <button type="button" class="mui-btn mui-btn-blue" id="info">跳转主页面</button>  
    </body>  
    <script type="text/javascript">  
        mui.plusReady(function() {  
            document.getElementById('info').addEventListener('tap', function() {  
                //打开页面  
                mui.openWindow({  
                    url: 'bth.html',  
                    id: 'bth.html'  
                });  
            });  
        })  
    </script>

其中btn.html如下:

<!DOCTYPE html>  
<html>  
    <head>  
        <meta charset="utf-8">  
        <meta name="viewport"  
            content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />  
        <title></title>  
        <script src="js/mui.js" type="text/javascript" charset="utf-8"></script>  
        <link rel="stylesheet" type="text/css" href="./css/mui.css" />  
        <script src="js/util.js" type="text/javascript" charset="utf-8"></script> <!-- -->  
        <script src="js/jquery-3.5.1.min.js" type="text/javascript" charset="utf-8"></script>  
        <script type="text/javascript" charset="utf-8">  
            mui.init();  
        </script>  
    </head>  
    <body>  
        <nav class="mui-bar mui-bar-tab">  
            <a class="mui-tab-item" href="pages/a.html">  
                <span class="mui-icon mui-icon-home"></span>  
                <span class="mui-tab-label">首页</span>  
            </a>  
            <a class="mui-tab-item" href="pages/b.html">  
                <span class="mui-icon mui-icon-phone"></span>  
                <span class="mui-tab-label">电话</span>  
            </a>  
            <a class="mui-tab-item"><!-- 给中部凸起大图标占个位置,使得左右两边的tab按钮均匀分布,实际这个tab没用到 -->  
                <span>占位</span>  
            </a>  
            <a class="mui-tab-item" href="pages/c.html">  
                <span class="mui-icon mui-icon-email"></span>  
                <span class="mui-tab-label">邮件</span>  
            </a>  
            <a class="mui-tab-item" href="pages/d.html">  
                <span class="mui-icon mui-icon-gear"></span>  
                <span class="mui-tab-label">设置</span>  
            </a>  
        </nav>  
    </body>  
    <script type="text/javascript">  
        var index = 0;  
        var subpages = ['pages/a.html', 'pages/b.html', 'pages/center.html', 'pages/c.html', 'pages/d.html'];  
        mui.plusReady(function() {  
            var self = plus.webview.currentWebview();  

            //如下在中部添加一个凸起图标  
            var leftPos = Math.ceil((window.innerWidth - 60) / 2); // 设置凸起大图标为水平居中  

            /**  
             * drawNativeIcon 绘制中部凸起大图标,具体参数含义参考plus.nativeObj.View(id, styles, tags)文档说明[http://www.html5plus.org/doc/zh_cn/nativeobj.html#plus.nativeObj.View]  
             * 实现原理:  
             *   id为bg的tag 创建带边框的圆  
             *   id为bg2的tag 创建白色矩形遮住圆下半部分,只显示凸起带边框部分  
             *   id为iconBg的红色背景图  
             *   id为icon的字体图标  
             *   注意创建先后顺序,创建越晚的层级越高  
             */  
            var drawNativeIcon = util.drawNative('icon', {  
                bottom: '5px',  
                left: leftPos + 'px',  
                width: '60px',  
                height: '60px'  
            }, [{  
                    tag: 'rect', //创建带边框的圆。 "rect" - 绘制矩形区域,与drawRect操作一致,此时id、color、position、rectStyles属性值有效  
                    id: 'bg',  
                    position: {  
                        top: '1px',  
                        left: '0px',  
                        width: '100%',  
                        height: '100%'  
                    },  
                    rectStyles: {  
                        color: '#fff',  
                        radius: '50%',  
                        borderColor: '#ccc',  
                        borderWidth: '1px'  
                    }  
                }, {  
                    tag: 'rect', //创建白色矩形遮住圆下半部分,只显示凸起带边框部分  
                    id: 'bg2',  
                    position: {  
                        bottom: '-0.5px',  
                        left: '0px',  
                        width: '100%',  
                        height: '45px'  
                    },  
                    rectStyles: {  
                        color: '#fff'  
                    }  
                }, {  
                    tag: 'rect', //创建绿色背景图  
                    id: 'iconBg',  
                    position: {  
                        top: '5px',  
                        left: '5px',  
                        width: '50px',  
                        height: '50px'  
                    },  
                    rectStyles: {  
                        color: '#00aa00',  
                        radius: '50%'  
                    }  
                }, {  
                    tag: 'font', //创建文字。 "font" - 绘制文本内容,与drawText操作一致,此时id、position、text、textStyles属性值有效;  
                    id: 'icon',  
                    text: 'B',  
                    position: {  
                        top: '0px',  
                        left: '5px',  
                        width: '50px',  
                        height: '100%'  
                    },  
                    textStyles: {  
                        fontSrc: '_www/fonts/iconfont.ttf',  
                        align: 'center',  
                        color: '#fff',  
                        size: '30px'  
                    }  
                },  
                /* {  
                tag: 'img', //"img" - 绘制图片,与drawBitmap操作一致,此时id、src、position、sprite属性值有效;   
                id: 'img',  
                src: 'img/a.png',  
                position: {  
                    top: '15px',  
                    left: '5px',  
                    width: '50px',  
                    height: '80%'  
                }  
            } */  
            ]);  

            // 把创建的中央凸起图标append 到父webview中  
            self.append(drawNativeIcon);  

            //自定义监听凸起图标点击事件  
            var active_color = '#fff'; //激活的颜色  
            drawNativeIcon.addEventListener('click', function(e) {  

                //显示凸起图标对应的页面,隐藏其它几个Tab对应的页面  
                plus.webview.show('pages/center.html');  
                plus.webview.hide('pages/a.html');  
                plus.webview.hide('pages/b.html');  
                plus.webview.hide('pages/c.html');  
                plus.webview.hide('pages/d.html');  

                //取消其它几个Tab对应的页面的选中效果  
                $(".mui-tab-item").removeClass('mui-active');  

                // 重绘字体颜色  
                if (active_color == '#fff') {  
                    drawNativeIcon.drawText('A', {}, {  
                        fontSrc: '_www/fonts/iconfont.ttf',  
                        align: 'center',  
                        color: '#000',  
                        size: '30px'  
                    }, 'icon');  
                    active_color = '#000';  
                } else {  
                    drawNativeIcon.drawText('B', {}, {  
                        fontSrc: '_www/fonts/iconfont.ttf',  
                        align: 'center',  
                        color: '#fff',  
                        size: '30px'  
                    }, 'icon');  
                    active_color = '#fff';  
                }  
            });  

            //如下把每个tab对应的页面加载到当前webview  
            for (var i = 0; i < 5; i++) {  
                var sub = plus.webview.create(  
                    subpages[i], //url  
                    subpages[i], //id  
                    {  
                        top: "45px", //子页面距离顶部的位置  
                        bottom: "50px" //子页面距离底部的位置  
                    }  
                );  

                if (i != index) {  
                    sub.hide()  
                }  

                self.append(sub);  
            }  
        })   

        //如下设置除了中部大图标的四个Tab的点击切换事件  
        var activetab = subpages[index];  
        mui(".mui-bar-tab").on('tap', 'a', function(e) {  
            var targetTab = this.getAttribute('href');  
            if (activetab == targetTab) {  
                return;  
            }  

            plus.webview.show(targetTab);  
            plus.webview.hide(activetab);  

            activetab = targetTab;  
        })  
    </script>  

</html>

其中a.html、b.html、center.html、c.html、d.html很简单,每个页面只有一句话:

<body>  
    <span>我是b页面</span>  
</body>

util.js如下:

var util = {  
    /**  
     *  简单封装了绘制原生view控件的方法  
     *  绘制内容支持font(文本,字体图标),图片img , 矩形区域rect  
     */  
    drawNative: function(id, styles, tags) {  
        var view = new plus.nativeObj.View(id, styles, tags);  
        return view;  
    }  
};

manifest.json无需配置任何东西即可。

效果如下:

Demo源码见本文后面的附件:ButtomTabHump.zip

也可以参考我的博客:https://blog.csdn.net/qzyw525942/article/details/115749934

收起阅读 »

tailwind,Hbuilder创建的uniApp项目

参考以下大神的文章
https://blog.csdn.net/weixin_48299611/article/details/111567736
https://wyz.xyz/d/150-uni-app-tailwindcss
https://ask.dcloud.net.cn/article/38697

主要是第一篇文章,后两篇,是改了三个配置

separator: "", // 兼容小程序,将 : 替换成

// 兼容小程序,将默认配置里带 .和/ 清除

corePlugins: {
// 兼容小程序,将带有 * 选择器的插件禁用
preflight: false,
space: false,
divideColor: false,
divideOpacity: false,
divideStyle: false,
divideWidth: false
},

继续阅读 »

参考以下大神的文章
https://blog.csdn.net/weixin_48299611/article/details/111567736
https://wyz.xyz/d/150-uni-app-tailwindcss
https://ask.dcloud.net.cn/article/38697

主要是第一篇文章,后两篇,是改了三个配置

separator: "", // 兼容小程序,将 : 替换成

// 兼容小程序,将默认配置里带 .和/ 清除

corePlugins: {
// 兼容小程序,将带有 * 选择器的插件禁用
preflight: false,
space: false,
divideColor: false,
divideOpacity: false,
divideStyle: false,
divideWidth: false
},

收起阅读 »

clientDB联表查询策略优化调整公告

uniCloud clientDB

JQL联表查询策略调整

clientDB将于2021-04-28日上午10点,调整联表查询策略。在此时间点后如果不改动服务空间,不会有变化。如果改动了schema、uni-id模块,则会自动升级clientDB到新策略。

调整前

  • 以field方法为联表查询依据,field内存在{}时才会进行联表查询
  • collection中写3个表,第3个表可与第2个表关联查询(其实本身是错误写法,但之前版本未限制这种做法)

调整后

  • field方法仅用于字段过滤,只要collection方法内有多个表名,就会自动联表查询,无需在field中编写{}。(详见下方说明)
  • collection中写多个表名时,只要第一个表是主表,剩余表均与主表做关联查询。第3个表只能与主表联查,不能与第2个表联查。嵌套联表查询的错误用法不再予以支持,后续会提供其他写法对嵌套联表查询进行支持

调整后clientDB会自动从schema读取所有collection方法内出现的表的关联关系进行联表查询。

当多表之间存在多个foreignKey关系时,之前通过field字段指定要使用哪些foreignKey。从3.1.10版本起,clientDB新增了foreignKey方法,可通过白名单方式手动指定使用的关联关系。
类似于field白名单策略,不写就代表全要,写了就只要指定的。
foreignKey方法的参数格式是字符串。
例如:

db.collection('comment,uni-id-users')  
.foreignKey('comment.sender,comment.receiver')  
.get()

上述代码表示联查时仅使用comment表内的sender字段和receiver字段对应的foreignKey,忽略其他foreignKey关系。

clientDB已发布过渡版本3.1.10,支持foreignKey方法以方便开发者编写兼容代码,过渡版本是向下兼容的,但对即将废弃的用途做了告警。

开发者若想知道自己的联查代码是否涉及将被废弃的写法,推荐尽快更新到HBuilderX 3.1.10版本,连接本地云函数环境运行测试,触发clientDB请求,即将废弃的写法产生的请求会在控制台打印如下信息:[System Info]clientDB会在2021年4月26日进行升级,此写法不再兼容,如何调整请参考:https://ask.dcloud.net.cn/article/38966

在截止日期后,会自动移除对废弃写法的支持。

如果连接云端云函数测试clientDB兼容性,需要上传一次schema触发云端clientDB更新到3.1.10+版本

下面以附录内的三个表为例,讲解联表查询具体调整,(_id在mongoDB内有特殊的表现,下面三个表内均不以_id举例),下方所有示例代码schema及数据可在本贴附件内找到

限制错误写法

嵌套联表查询

调整前clientDB虽未暴露嵌套联查用法,但是也没有做出限制,导致有部分开发者错误的使用了下方示例的写法进行嵌套联表查询。调整后仅支持第一个表作为主表,关联其他表进行查询,不可在副表之间再进行关联,即以下写法不再支持(后续会提供其他写法来进行此类查询)

// 错误写法  
db.collection('comment,article,user')  
.field('content,article{author{name}}')  
.get()

将某字段使用as重命名为表内存在的字段

// 错误写法  
db.collection('comment')  
.field('content as comment_id') // 不支持将content重命名为comment_id因为comment_id也是comment表的字段  
.get()

联表查询副表字段过滤新写法

调整前如果要对副表数据进行过滤需要在field方法的花括号内进行过滤,如以下写法对副表user进行字段过滤,仅获取user表的name

调整后应将主表副表合并查询后的结果看作一个表,直接通过.进行副表字段的筛选。旧写法仍支持

// 调整前写法  
db.collection('article,user')  
.where('article_id=="1"')  
.field('title,content,author{name}')  
.get()
// 调整后写法  
db.collection('article,user')  
.where('article_id=="1"')  
.field('title,content,author.name')  
.get()

注意此写法副表别名的表现,见下方示例

// 调整后写法  
db.collection('article,user')  
.where('article_id=="1"')  
.field('title,content,author.name as author_name')  
.get()

返回结果

[{  
  "title": "title1",  
  "content": "content1",  
  "author_name": ["user1"] // 联表后author是一个数组,此处表现为将author下每一项的name提取出来作为一个数组  
}]

自动进行数据表的关联

调整前clientDB会根据传入的field对collection内的表进行关联,即仅使用field内{}明确表示的关联关系。调整后,clientDB会自动补足主表和副表之间的关联关系,即使field内不存在{}

此调整可能会导致原有联表查询访问调整前并未访问的字段,如果此字段恰好无权访问会导致查询报错

例1:

db.collection('comment,article')  
.where('comment_id=="1-1"')  
.field('content,article')  
.get()

由于collection内包含两个集合,上述代码在调整后会自动进行联表查询(使用所有的关联关系),查询到的结果如下

// 调整后结果  
[{  
  "content": "comment1-1",  
  "article": [{  
    "article_id": "1",  
    "title": "title1",  
    "content": "content1",  
    "author": "1"  
  }]  
}]  

// 调整前结果  
[{  
  "content": "comment1-1",  
  "article": "1"  
}]

例1在调整前下不会进行联表查询,如果希望维持旧的表现只需要去除collection方法内的article表即可

db.collection('comment')  
.where('comment_id=="1-1"')  
.field('content,article')  
.get()

例2:

db.collection('comment,user')  
.where('comment_id=="1-1"')  
.field('content,sender,receiver{name}')  
.get()

comment表内sender和receiver均存在foreignKey指向user表,调整前上述写法不会使用sender对应的foreignKey进行联表查询,调整后sender对应的foreignKey也会进行一次联表查询,如果仅希望receiver字段对应的foreignKey生效可以使用如下写法

// 例2调整为此写法能同时兼容新旧用法  
db.collection('comment,user')  
.where('comment_id=="1-1"')  
.field('content,sender,receiver.name') // 去除原查询中的花括号  
.foreignKey('comment.receiver') // 仅使用comment表内receiver字段下的foreignKey  
.get()

<unicloud-db>组件调整也和clientDB api类似,新增了一个foreignKey属性传值同上

<unicloud-db collection="comment,user" where="comment_id=='1-1'" field="content,sender,receiver{name}" foreignKey="comment.receiver"></unicloud-db>

例3:

存在不止两个表的联表查询也是一样会自动补足所有副表和主表的关系(注意仅在所有副表和主表之间进行关联查询,副表之间即使存在关联关系也无法进行关联查询)

db.collection('comment,article,user')  
.where('comment_id=="1-1"')  
.get()

返回结果如下

[{  
  "comment_id": "1-1",  
  "content": "comment1-1",  
  "article": [{  
    "article_id": "1",  
    "title": "title1",  
    "content": "content1",  
    "author": "1"  
  }],  
  "sender": [{  
    "uid": "1",  
    "name": "user1"  
  }],  
  "receiver": [{  
    "uid": "2",  
    "name": "user2"  
  }]  
}]

副表foreignKey联查

之前的clientDB版本,只支持主表的foreignKey,把副本内容嵌入主表的foreignKey字段下面。不支持处理副本的foreignKey。(如果你觉得能用,其实是bug,查出来的数是乱的,别依赖这种写法)

调整后,新版将正式支持副表foreignKey联查。将把副本的数据以数组的方式嵌入到主表中。

例:

db.collection('article,comment')  
.where('article_id=="1"')  
.field('content,article_id')  
.get()

查询结果如下:

[{  
  "content": "content1",  
  "article_id": {  
    "comment": [{ // 逆向foreignKey查询时此处会自动插入一层副表表名  
      "comment_id": "1-1",  
      "content": "comment1-1",  
      "article": "1",  
      "sender": "1",  
      "receiver": "2"  
    },  
    {  
      "comment_id": "1-2",  
      "content": "comment1-2",  
      "article": "1",  
      "sender": "2",  
      "receiver": "1"  
    }]  
  }  
}]

如需对上述查询的副表字段进行过滤,需要注意多插入的一层副表表名

// 过滤副表字段  
db.collection('article,comment')  
.where('article_id=="1"')  
.field('content,article_id{comment{content}}')  
.get()  

// 查询结果如下  
[{  
  "content": "content1",  
  "article_id": {  
    "comment": [{ // 使用副本foreignKey联查时此处会自动插入一层副表表名  
      "content": "comment1-1"  
    },  
    {  
      "content": "comment1-2"  
    }]  
  }  
}]

附录

表结构及数据

// user - 用户表  

// schema  
{  
  "bsonType": "object",  
  "required": [],  
  "permission": {  
    "read": true,  
    "create": false,  
    "update": false,  
    "delete": false  
  },  
  "properties": {  
    "uid": {  
      "bsonType": "string"  
    },  
    "name": {  
      "bsonType": "string"  
    }  
  }  
}  

// data  
{  
  "uid": "1",  
  "name": "user1"  
}  
{  
  "uid": "2",  
  "name": "user2"  
}
// article - 文章表  

// schema  
{  
  "bsonType": "object",  
  "required": [],  
  "permission": {  
    "read": true,  
    "create": false,  
    "update": false,  
    "delete": false  
  },  
  "properties": {  
    "article_id": {  
      "bsonType": "string"  
    },  
    "title": {  
      "bsonType": "string"  
    },  
    "content": {  
      "bsonType": "string"  
    },  
    "author": {  
      "bsonType": "string",  
      "foreignKey": "user.uid"  
    }  
  }  
}  

// data  
{  
  "article_id": "1",  
  "title": "title1",  
  "content": "content1",  
  "author": "1"  
}  
{  
  "article_id": "2",  
  "title": "title2",  
  "content": "content2",  
  "author": "1"  
}  
{  
  "article_id": "3",  
  "title": "title3",  
  "content": "content3",  
  "author": "2"  
}
// comment - 评论表  

// schema  
{  
  "bsonType": "object",  
  "required": [],  
  "permission": {  
    "read": true,  
    "create": false,  
    "update": false,  
    "delete": false  
  },  
  "properties": {  
    "comment_id": {  
      "bsonType": "string"  
    },  
    "content": {  
      "bsonType": "string"  
    },  
    "article": {  
      "bsonType": "string",  
      "foreignKey": "article.article_id"  
    },  
    "sender": {  
      "bsonType": "string",  
      "foreignKey": "user.uid"  
    },  
    "receiver": {  
      "bsonType": "string",  
      "foreignKey": "user.uid"  
    }  
  }  
}  

// data  
{  
  "comment_id": "1-1",  
  "content": "comment1-1",  
  "article": "1",  
  "sender": "1",  
  "receiver": "2"  
}  
{  
  "comment_id": "1-2",  
  "content": "comment1-2",  
  "article": "1",  
  "sender": "2",  
  "receiver": "1"  
}  
{  
  "comment_id": "2-1",  
  "content": "comment2-1",  
  "article": "2",  
  "sender": "1",  
  "receiver": "2"  
}  
{  
  "comment_id": "2-2",  
  "content": "comment2-2",  
  "article": "2",  
  "sender": "2",  
  "receiver": "1"  
}
继续阅读 »

JQL联表查询策略调整

clientDB将于2021-04-28日上午10点,调整联表查询策略。在此时间点后如果不改动服务空间,不会有变化。如果改动了schema、uni-id模块,则会自动升级clientDB到新策略。

调整前

  • 以field方法为联表查询依据,field内存在{}时才会进行联表查询
  • collection中写3个表,第3个表可与第2个表关联查询(其实本身是错误写法,但之前版本未限制这种做法)

调整后

  • field方法仅用于字段过滤,只要collection方法内有多个表名,就会自动联表查询,无需在field中编写{}。(详见下方说明)
  • collection中写多个表名时,只要第一个表是主表,剩余表均与主表做关联查询。第3个表只能与主表联查,不能与第2个表联查。嵌套联表查询的错误用法不再予以支持,后续会提供其他写法对嵌套联表查询进行支持

调整后clientDB会自动从schema读取所有collection方法内出现的表的关联关系进行联表查询。

当多表之间存在多个foreignKey关系时,之前通过field字段指定要使用哪些foreignKey。从3.1.10版本起,clientDB新增了foreignKey方法,可通过白名单方式手动指定使用的关联关系。
类似于field白名单策略,不写就代表全要,写了就只要指定的。
foreignKey方法的参数格式是字符串。
例如:

db.collection('comment,uni-id-users')  
.foreignKey('comment.sender,comment.receiver')  
.get()

上述代码表示联查时仅使用comment表内的sender字段和receiver字段对应的foreignKey,忽略其他foreignKey关系。

clientDB已发布过渡版本3.1.10,支持foreignKey方法以方便开发者编写兼容代码,过渡版本是向下兼容的,但对即将废弃的用途做了告警。

开发者若想知道自己的联查代码是否涉及将被废弃的写法,推荐尽快更新到HBuilderX 3.1.10版本,连接本地云函数环境运行测试,触发clientDB请求,即将废弃的写法产生的请求会在控制台打印如下信息:[System Info]clientDB会在2021年4月26日进行升级,此写法不再兼容,如何调整请参考:https://ask.dcloud.net.cn/article/38966

在截止日期后,会自动移除对废弃写法的支持。

如果连接云端云函数测试clientDB兼容性,需要上传一次schema触发云端clientDB更新到3.1.10+版本

下面以附录内的三个表为例,讲解联表查询具体调整,(_id在mongoDB内有特殊的表现,下面三个表内均不以_id举例),下方所有示例代码schema及数据可在本贴附件内找到

限制错误写法

嵌套联表查询

调整前clientDB虽未暴露嵌套联查用法,但是也没有做出限制,导致有部分开发者错误的使用了下方示例的写法进行嵌套联表查询。调整后仅支持第一个表作为主表,关联其他表进行查询,不可在副表之间再进行关联,即以下写法不再支持(后续会提供其他写法来进行此类查询)

// 错误写法  
db.collection('comment,article,user')  
.field('content,article{author{name}}')  
.get()

将某字段使用as重命名为表内存在的字段

// 错误写法  
db.collection('comment')  
.field('content as comment_id') // 不支持将content重命名为comment_id因为comment_id也是comment表的字段  
.get()

联表查询副表字段过滤新写法

调整前如果要对副表数据进行过滤需要在field方法的花括号内进行过滤,如以下写法对副表user进行字段过滤,仅获取user表的name

调整后应将主表副表合并查询后的结果看作一个表,直接通过.进行副表字段的筛选。旧写法仍支持

// 调整前写法  
db.collection('article,user')  
.where('article_id=="1"')  
.field('title,content,author{name}')  
.get()
// 调整后写法  
db.collection('article,user')  
.where('article_id=="1"')  
.field('title,content,author.name')  
.get()

注意此写法副表别名的表现,见下方示例

// 调整后写法  
db.collection('article,user')  
.where('article_id=="1"')  
.field('title,content,author.name as author_name')  
.get()

返回结果

[{  
  "title": "title1",  
  "content": "content1",  
  "author_name": ["user1"] // 联表后author是一个数组,此处表现为将author下每一项的name提取出来作为一个数组  
}]

自动进行数据表的关联

调整前clientDB会根据传入的field对collection内的表进行关联,即仅使用field内{}明确表示的关联关系。调整后,clientDB会自动补足主表和副表之间的关联关系,即使field内不存在{}

此调整可能会导致原有联表查询访问调整前并未访问的字段,如果此字段恰好无权访问会导致查询报错

例1:

db.collection('comment,article')  
.where('comment_id=="1-1"')  
.field('content,article')  
.get()

由于collection内包含两个集合,上述代码在调整后会自动进行联表查询(使用所有的关联关系),查询到的结果如下

// 调整后结果  
[{  
  "content": "comment1-1",  
  "article": [{  
    "article_id": "1",  
    "title": "title1",  
    "content": "content1",  
    "author": "1"  
  }]  
}]  

// 调整前结果  
[{  
  "content": "comment1-1",  
  "article": "1"  
}]

例1在调整前下不会进行联表查询,如果希望维持旧的表现只需要去除collection方法内的article表即可

db.collection('comment')  
.where('comment_id=="1-1"')  
.field('content,article')  
.get()

例2:

db.collection('comment,user')  
.where('comment_id=="1-1"')  
.field('content,sender,receiver{name}')  
.get()

comment表内sender和receiver均存在foreignKey指向user表,调整前上述写法不会使用sender对应的foreignKey进行联表查询,调整后sender对应的foreignKey也会进行一次联表查询,如果仅希望receiver字段对应的foreignKey生效可以使用如下写法

// 例2调整为此写法能同时兼容新旧用法  
db.collection('comment,user')  
.where('comment_id=="1-1"')  
.field('content,sender,receiver.name') // 去除原查询中的花括号  
.foreignKey('comment.receiver') // 仅使用comment表内receiver字段下的foreignKey  
.get()

<unicloud-db>组件调整也和clientDB api类似,新增了一个foreignKey属性传值同上

<unicloud-db collection="comment,user" where="comment_id=='1-1'" field="content,sender,receiver{name}" foreignKey="comment.receiver"></unicloud-db>

例3:

存在不止两个表的联表查询也是一样会自动补足所有副表和主表的关系(注意仅在所有副表和主表之间进行关联查询,副表之间即使存在关联关系也无法进行关联查询)

db.collection('comment,article,user')  
.where('comment_id=="1-1"')  
.get()

返回结果如下

[{  
  "comment_id": "1-1",  
  "content": "comment1-1",  
  "article": [{  
    "article_id": "1",  
    "title": "title1",  
    "content": "content1",  
    "author": "1"  
  }],  
  "sender": [{  
    "uid": "1",  
    "name": "user1"  
  }],  
  "receiver": [{  
    "uid": "2",  
    "name": "user2"  
  }]  
}]

副表foreignKey联查

之前的clientDB版本,只支持主表的foreignKey,把副本内容嵌入主表的foreignKey字段下面。不支持处理副本的foreignKey。(如果你觉得能用,其实是bug,查出来的数是乱的,别依赖这种写法)

调整后,新版将正式支持副表foreignKey联查。将把副本的数据以数组的方式嵌入到主表中。

例:

db.collection('article,comment')  
.where('article_id=="1"')  
.field('content,article_id')  
.get()

查询结果如下:

[{  
  "content": "content1",  
  "article_id": {  
    "comment": [{ // 逆向foreignKey查询时此处会自动插入一层副表表名  
      "comment_id": "1-1",  
      "content": "comment1-1",  
      "article": "1",  
      "sender": "1",  
      "receiver": "2"  
    },  
    {  
      "comment_id": "1-2",  
      "content": "comment1-2",  
      "article": "1",  
      "sender": "2",  
      "receiver": "1"  
    }]  
  }  
}]

如需对上述查询的副表字段进行过滤,需要注意多插入的一层副表表名

// 过滤副表字段  
db.collection('article,comment')  
.where('article_id=="1"')  
.field('content,article_id{comment{content}}')  
.get()  

// 查询结果如下  
[{  
  "content": "content1",  
  "article_id": {  
    "comment": [{ // 使用副本foreignKey联查时此处会自动插入一层副表表名  
      "content": "comment1-1"  
    },  
    {  
      "content": "comment1-2"  
    }]  
  }  
}]

附录

表结构及数据

// user - 用户表  

// schema  
{  
  "bsonType": "object",  
  "required": [],  
  "permission": {  
    "read": true,  
    "create": false,  
    "update": false,  
    "delete": false  
  },  
  "properties": {  
    "uid": {  
      "bsonType": "string"  
    },  
    "name": {  
      "bsonType": "string"  
    }  
  }  
}  

// data  
{  
  "uid": "1",  
  "name": "user1"  
}  
{  
  "uid": "2",  
  "name": "user2"  
}
// article - 文章表  

// schema  
{  
  "bsonType": "object",  
  "required": [],  
  "permission": {  
    "read": true,  
    "create": false,  
    "update": false,  
    "delete": false  
  },  
  "properties": {  
    "article_id": {  
      "bsonType": "string"  
    },  
    "title": {  
      "bsonType": "string"  
    },  
    "content": {  
      "bsonType": "string"  
    },  
    "author": {  
      "bsonType": "string",  
      "foreignKey": "user.uid"  
    }  
  }  
}  

// data  
{  
  "article_id": "1",  
  "title": "title1",  
  "content": "content1",  
  "author": "1"  
}  
{  
  "article_id": "2",  
  "title": "title2",  
  "content": "content2",  
  "author": "1"  
}  
{  
  "article_id": "3",  
  "title": "title3",  
  "content": "content3",  
  "author": "2"  
}
// comment - 评论表  

// schema  
{  
  "bsonType": "object",  
  "required": [],  
  "permission": {  
    "read": true,  
    "create": false,  
    "update": false,  
    "delete": false  
  },  
  "properties": {  
    "comment_id": {  
      "bsonType": "string"  
    },  
    "content": {  
      "bsonType": "string"  
    },  
    "article": {  
      "bsonType": "string",  
      "foreignKey": "article.article_id"  
    },  
    "sender": {  
      "bsonType": "string",  
      "foreignKey": "user.uid"  
    },  
    "receiver": {  
      "bsonType": "string",  
      "foreignKey": "user.uid"  
    }  
  }  
}  

// data  
{  
  "comment_id": "1-1",  
  "content": "comment1-1",  
  "article": "1",  
  "sender": "1",  
  "receiver": "2"  
}  
{  
  "comment_id": "1-2",  
  "content": "comment1-2",  
  "article": "1",  
  "sender": "2",  
  "receiver": "1"  
}  
{  
  "comment_id": "2-1",  
  "content": "comment2-1",  
  "article": "2",  
  "sender": "1",  
  "receiver": "2"  
}  
{  
  "comment_id": "2-2",  
  "content": "comment2-2",  
  "article": "2",  
  "sender": "2",  
  "receiver": "1"  
}
收起阅读 »

无视法律从事网络赌博客服非法获利50万元,7人在开州获刑

法律科普

随着互联网和信息化技术的蓬勃发展,许多赌博活动改头换面,从现实生活中转移到网络空间,令人防不胜防,而通过网络邀请、发展他人参与赌博等违法行为更是与日俱增,值得警醒。


经法院审理查明

谭某、邱某(均已判刑)组织人员分别在重庆市开州区、万州区、渝中区、四川省成都市等地设立网络赌博代理点,先后运营“大满贯”、“黄金娱乐城”、“极乐宝典”等赌博APP,招募姜某、邓某燕、吴某月等7人在各代理点作为客服人员,负责日常赌博游戏宣传,向参赌人员销售赌博游戏“金币”、游戏“金币”兑换现金以及转账等工作,并以固定工资加销售提成的方式获利。

经统计

姜某、邓某燕、吴某月等7人在网络赌博代理点工作期间的非法获利从五万元到十万元不等,7人共计获利近五十万元。

2020年末,姜某、邓某燕、吴某月等7人经民警电话通知分别主动投案,后如实供述自己的犯罪事实。

法院审理认为

被告人姜某、吴某月、邓某燕、左某杨、张某、谢某、林某英帮助开设赌场,其行为均已构成开设赌场罪,被告人姜某、邓某燕、吴某月等7人犯罪以后自动投案,如实供述自己的罪行,均系自首,依法可以从轻处罚。依法判处被告人姜某、吴某月、邓某燕、左某杨、张某、谢某、林某英有期徒刑一年,缓刑一年至拘役三个月,缓刑五个月,并处罚金不等的刑罚。各被告人违法所得,依法予以追缴,上缴国库。

法律链接

《中华人民共和国刑法》第三百零三条第二款

开设赌场的,处三年以下有期徒刑、拘役或者管制,并处罚金;情节严重的,处三年以上十年以下有期徒刑,并处罚金

《最高人民法院、最高人民检察院、公安部办理跨境赌博犯罪案件若干问题的意见》规定:

明知是赌博网站、应用程序,有下列情形之一的,以开设赌场罪的共犯论处:

  • 为赌博网站、应用程序提供软件开发、技术支持、互联网接入、服务器托管、网络存储空间、通讯传输通道、广告投放、 会员发展、资金支付结算等服务的;

  • 为赌博网站、应用程序担任代理并发展玩家、会员、下线的

法官提醒

请广大群众一定要分清娱乐与赌博的界限,不要对线上参赌行为抱有侥幸心理,为了家人和自己请自觉抵制网络赌博,避免受到法律的制裁!

【来源:重庆市开州区人民法院】,转载自公众号:重庆市开州区人民法院,如有不当联系邮箱:pufa@dcloud.io 。
原文链接

继续阅读 »

随着互联网和信息化技术的蓬勃发展,许多赌博活动改头换面,从现实生活中转移到网络空间,令人防不胜防,而通过网络邀请、发展他人参与赌博等违法行为更是与日俱增,值得警醒。


经法院审理查明

谭某、邱某(均已判刑)组织人员分别在重庆市开州区、万州区、渝中区、四川省成都市等地设立网络赌博代理点,先后运营“大满贯”、“黄金娱乐城”、“极乐宝典”等赌博APP,招募姜某、邓某燕、吴某月等7人在各代理点作为客服人员,负责日常赌博游戏宣传,向参赌人员销售赌博游戏“金币”、游戏“金币”兑换现金以及转账等工作,并以固定工资加销售提成的方式获利。

经统计

姜某、邓某燕、吴某月等7人在网络赌博代理点工作期间的非法获利从五万元到十万元不等,7人共计获利近五十万元。

2020年末,姜某、邓某燕、吴某月等7人经民警电话通知分别主动投案,后如实供述自己的犯罪事实。

法院审理认为

被告人姜某、吴某月、邓某燕、左某杨、张某、谢某、林某英帮助开设赌场,其行为均已构成开设赌场罪,被告人姜某、邓某燕、吴某月等7人犯罪以后自动投案,如实供述自己的罪行,均系自首,依法可以从轻处罚。依法判处被告人姜某、吴某月、邓某燕、左某杨、张某、谢某、林某英有期徒刑一年,缓刑一年至拘役三个月,缓刑五个月,并处罚金不等的刑罚。各被告人违法所得,依法予以追缴,上缴国库。

法律链接

《中华人民共和国刑法》第三百零三条第二款

开设赌场的,处三年以下有期徒刑、拘役或者管制,并处罚金;情节严重的,处三年以上十年以下有期徒刑,并处罚金

《最高人民法院、最高人民检察院、公安部办理跨境赌博犯罪案件若干问题的意见》规定:

明知是赌博网站、应用程序,有下列情形之一的,以开设赌场罪的共犯论处:

  • 为赌博网站、应用程序提供软件开发、技术支持、互联网接入、服务器托管、网络存储空间、通讯传输通道、广告投放、 会员发展、资金支付结算等服务的;

  • 为赌博网站、应用程序担任代理并发展玩家、会员、下线的

法官提醒

请广大群众一定要分清娱乐与赌博的界限,不要对线上参赌行为抱有侥幸心理,为了家人和自己请自觉抵制网络赌博,避免受到法律的制裁!

【来源:重庆市开州区人民法院】,转载自公众号:重庆市开州区人民法院,如有不当联系邮箱:pufa@dcloud.io 。
原文链接

收起阅读 »

承接uniapp、小程序、网站开发,前后端都可以,多年项目经验,坐标武汉,其他地区也可以,微信 batik88

外包接单

承接uniapp、小程序、网站开发,前后端都可以,多年项目经验,坐标武汉,其他地区也可以,欢迎加微信详谈 batik88

承接uniapp、小程序、网站开发,前后端都可以,多年项目经验,坐标武汉,其他地区也可以,欢迎加微信详谈 batik88

承接uniapp、小程序、网站开发,前后端都可以,多年项目经验,坐标武汉,微信 batik88

外包接单

承接uniapp、小程序、网站开发,前后端都可以,多年项目经验,坐标武汉,欢迎咨询微信 batik88

承接uniapp、小程序、网站开发,前后端都可以,多年项目经验,坐标武汉,欢迎咨询微信 batik88

安卓-电池优化、白名单

Native.JS

从 Android 6.0 开始,系统为了省电增加了休眠模式,系统待机一段时间后,会杀死后台正在运行的进程。但系统会有一个后台运行白名单,白名单里的应用将不会受到影响,在原生系统下,通过「设置」 - 「电池」 - 「电池优化」 - 「未优化应用」,可以看到这个白名单。

从网上查了些资料,找到:android应用申请加入电池优化白名单
根据上面资料转native.js,上代码。

  • ① 添加权限
    在manifest.json源码视图中添加<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />,就加在那一堆permissions中。

  • ② 判断我们的应用是否在白名单中

                   // 白名单  
                    var main = plus.android.runtimeMainActivity();  
                    var packName = main.getPackageName();    
                    var Context = plus.android.importClass("android.content.Context");  
                    var PowerManager = plus.android.importClass("android.os.PowerManager");  
                    // 获取电源类  
                    var pm = main.getSystemService(Context.POWER_SERVICE);  
                    console.log('是否在白名单:',pm.isIgnoringBatteryOptimizations(packName)); //是否白名单  
                    let whiteList = pm.isIgnoringBatteryOptimizations(packName);
  • ③ 调起服务

     try{  
            var Uri = plus.android.importClass("android.net.Uri");  
            var Settings = plus.android.importClass("android.provider.Settings");  
            var packageURI = Uri.parse("package:" + packName);  
            var intents = plus.android.newObject("android.content.Intent", Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,packageURI);  // 电池  
            main.startActivity(intents);   
        }catch{  
            // console.log('调起失败')  
        }
  • ④ 发行-云打包测试
    测试多次HbuliderX里是调不起来,打包后才能出来

总结:自己测试-打开应用,然后锁屏(半小时为例)
华为():仍然可以运行,收到消息
小米():中断,除了设置中的电池优化,还有应用详情的“省电策略”,需要再调试

继续阅读 »

从 Android 6.0 开始,系统为了省电增加了休眠模式,系统待机一段时间后,会杀死后台正在运行的进程。但系统会有一个后台运行白名单,白名单里的应用将不会受到影响,在原生系统下,通过「设置」 - 「电池」 - 「电池优化」 - 「未优化应用」,可以看到这个白名单。

从网上查了些资料,找到:android应用申请加入电池优化白名单
根据上面资料转native.js,上代码。

  • ① 添加权限
    在manifest.json源码视图中添加<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />,就加在那一堆permissions中。

  • ② 判断我们的应用是否在白名单中

                   // 白名单  
                    var main = plus.android.runtimeMainActivity();  
                    var packName = main.getPackageName();    
                    var Context = plus.android.importClass("android.content.Context");  
                    var PowerManager = plus.android.importClass("android.os.PowerManager");  
                    // 获取电源类  
                    var pm = main.getSystemService(Context.POWER_SERVICE);  
                    console.log('是否在白名单:',pm.isIgnoringBatteryOptimizations(packName)); //是否白名单  
                    let whiteList = pm.isIgnoringBatteryOptimizations(packName);
  • ③ 调起服务

     try{  
            var Uri = plus.android.importClass("android.net.Uri");  
            var Settings = plus.android.importClass("android.provider.Settings");  
            var packageURI = Uri.parse("package:" + packName);  
            var intents = plus.android.newObject("android.content.Intent", Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,packageURI);  // 电池  
            main.startActivity(intents);   
        }catch{  
            // console.log('调起失败')  
        }
  • ④ 发行-云打包测试
    测试多次HbuliderX里是调不起来,打包后才能出来

总结:自己测试-打开应用,然后锁屏(半小时为例)
华为():仍然可以运行,收到消息
小米():中断,除了设置中的电池优化,还有应用详情的“省电策略”,需要再调试

收起阅读 »

NFC 读取指定扇区,写入指定扇区

NFC
<script>  
let Context = plus.android.importClass('android.content.Context');  
let NfcManager = plus.android.importClass('android.nfc.NfcManager');  
let NfcAdapter = plus.android.importClass('android.nfc.NfcAdapter');  
let Settings = plus.android.importClass('android.provider.Settings');  
let Intent = plus.android.importClass('android.content.Intent');  
let Parcelable = plus.android.importClass('android.os.Parcelable');  
let PendingIntent = plus.android.importClass('android.app.PendingIntent');  
let IntentFilter = plus.android.importClass('android.content.IntentFilter');  
let NdefRecord = plus.android.importClass('android.nfc.NdefRecord');  
let NdefMessage = plus.android.importClass('android.nfc.NdefMessage');  
let Tag = plus.android.importClass('android.nfc.Tag');  
let MifareClassic = plus.android.importClass('android.nfc.tech.MifareClassic');  
let invoke = plus.android.invoke;  
export default {  
    data() {  
        return {  
            sector: 1,  
            keyType: 'A',  
            keyVal: 'FFFFFFFFFFFF', //'E58583E69C94', //'FFFFFFFFFFFF',  
            ICUID: '',  
            ICData: 'IC卡扇区数据:',  
            ICERROR: '',  
            nfcAdapter: null,  
            main: null,  
            intent: null,  
            IntervalId: null,  
            techListsArray: [  
                ['android.nfc.tech.IsoDep'],  
                ['android.nfc.tech.NfcA'],  
                ['android.nfc.tech.NfcB'],  
                ['android.nfc.tech.NfcF'],  
                ['android.nfc.tech.NfcV'],  
                ['android.nfc.tech.Ndef'],  
                ['android.nfc.tech.NdefFormatable'],  
                ['android.nfc.tech.MifareClassic'],  
                ['android.nfc.tech.MifareUltralight']  
            ]  
        };  
    },  

    created() {  
        this.nfcinit();  
    },  
    beforeDestroy() {  
        this.nfcclose();  
    },  

    methods: {  
        nfcinit() {  
            this.main = plus.android.runtimeMainActivity();  
            var nfchManager = this.main.getSystemService(Context.NFC_SERVICE);  
            var nfcAdapter = nfchManager.getDefaultAdapter();  
            if (!nfcAdapter.isEnabled()) {  
                this.intent = new Intent(Settings.ACTION_NFC_SETTINGS);  
                this.main.startActivity(this.intent);  
            }  
            var intent = new Intent(this.main, this.main.getClass());  
            intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);  
            var pendingIntent = PendingIntent.getActivity(this.main, 0, intent, 0);  
            var ndef = new IntentFilter('android.nfc.action.TECH_DISCOVERED');  
            ndef.addDataType('*/*');  
            var intentFiltersArray = [ndef];  
            nfcAdapter.enableForegroundDispatch(this.main, pendingIntent, intentFiltersArray, this.techListsArray);  
            this.nfcAdapter = nfcAdapter;  
        },  
        nfcclose() {  
            if (this.nfcAdapter) this.nfcAdapter.disableForegroundDispatch(this.main);  
            this.nfcAdapter = null;  
            clearInterval(this.IntervalId);  
        },  
        handle_nfc_data(obj) {  
            var intent = this.main.getIntent();  
            if (intent.getAction() == 'android.nfc.action.TECH_DISCOVERED') {  
                clearInterval(this.IntervalId);  
                this.readData(intent, obj);  
            }  
        },  
        // 读扇区  请参考 https://ask.dcloud.net.cn/article/35593  
        readData(intent, obj) {  
            var tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);  
            var techList = tag.getTechList();  
            var bisMfc = false;  
            for (var i = 0; i < techList.length; i++) {  
                if (techList[i].indexOf('MifareClassic') >= 0) {  
                    bisMfc = true;  
                    break;  
                }  
            }  
            if (!bisMfc) {  
                this.ICERROR = '卡片类型错误!';  
                return;  
            }  
            var mfc = MifareClassic.get(tag);  
            if (!mfc) {  
                this.ICERROR = '卡片获取错误!';  
                return;  
            }  
            mfc.setTimeout(3000);  
            if (!mfc.isConnected()) {  
                try {  
                    invoke(mfc, 'connect');  
                } catch (e) {  
                    this.ICERROR = '卡片连接错误!';  
                    return;  
                }  
            }  
            try {  
                this.ICUID = this.ByteArrayToHexString(tag.getId());  
                var cmdBytes = this.HexStringToByteArray(this.keyVal);  
                var auth = false;  
                //使用密钥A对扇区进行身份验证。  
                if (this.keyType == 'A') {  
                    auth = invoke(mfc, 'authenticateSectorWithKeyA', parseInt(this.sector), cmdBytes);  
                } else {  
                    //使用密钥B对扇区进行身份验证。  
                    auth = invoke(mfc, 'authenticateSectorWithKeyB', parseInt(this.sector), cmdBytes);  
                }  
                if (!auth) {  
                    this.ICERROR = '扇区验证失败';  
                    return;  
                }  
                var sectorData = [];  
                var tmpRet;  
                this.ICData = 'IC卡扇区数据>>';  
                // 调用 MifareClassic 读指定块 返回的是字节数组    
                // 若需读取扇区中的所有块  请参考 https://ask.dcloud.net.cn/article/35593  
                tmpRet = invoke(mfc, 'readBlock', 5);  
                // 此项目写入的时候有中文,用网上的字节转字符串 会出现解析乱码  
                // 所以此处导入 java String 类  
                var String = plus.android.importClass('java.lang.String');  
                // 将自己数组转换成java 字符串  
                var str = new String(tmpRet);  
                // 调用java String 类的 concat 方法 拼接一个空的字符串,不然输出str 是一个类名 plus.android.java.lang.String  

                obj.success(str.trim());  
                this.ICERROR = '读卡完成';  
            } catch (e) {  
                this.ICERROR = e.message;  
                obj.fail(e);  
            } finally {  
                mfc.close();  
            }  
        },  

        readcard(obj) {  
            var that = this;  
            (this.ICUID = ''),  
                (this.ICData = ''),  
                (this.ICERROR = ''),  
                (this.IntervalId = setInterval(function() {  
                    that.handle_nfc_data(obj);  
                }, 1000));  
        },  
        //   
        handle_nfc_write(sectorIndex, text, obj) {  
            var intent = this.main.getIntent();  
            if (intent.getAction() == 'android.nfc.action.TECH_DISCOVERED') {  

                this.write(intent, sectorIndex, text, obj);  
            }  
        },  
        //sectorIndex 扇区  
        // text 要写入的内容  
        // obj callback()  
        write(intent, sectorIndex, text, obj) {  
            try {  
                // 将要写入的内容转成字节数组  
                var textBytes = plus.android.invoke(text, 'getBytes', 'utf-8');  
                // android api参考地址  
                // https://developer.android.google.cn/reference/android/nfc/tech/MifareClassic?hl=en#writeBlock(int,%20byte[])  
                // 写入16个字节的块。不够补0  
                for (var i = textBytes.length; i < 16; i++) {  
                    textBytes.push(0);  
                }  
                var textRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, plus.android.invoke('text/plain', 'getBytes'), plus.android.invoke('', 'getBytes'), textBytes);  
                var message = new NdefMessage([textRecord]);  
                var NdefFormatable = plus.android.importClass('android.nfc.tech.NdefFormatable');  
                var tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);  
                var mfc = MifareClassic.get(tag);  
                if (mfc != null) {  
                    mfc.connect();  
                    // 默认密钥  
                    var cmdBytes = this.HexStringToByteArray('FFFFFFFFFFFF');  
                    // 使用密钥A对扇区进行身份验证。  
                    let auth = mfc.authenticateSectorWithKeyA(sectorIndex, cmdBytes);  
                    // var pass = 'E58583E69C94';  
                    // var password =this.HexStringToByteArray(pass);  
                    // for (var i = password.length; i < 16; i++) {  
                    //  password.push(0);  
                    // }  
                    if (auth) {  
                        //返回给定扇区的第一个块。  
                        let block = mfc.sectorToBlock(sectorIndex);  
                        // 写入块 我们项目写在 扇区1,第2块  
                        mfc.writeBlock(block + 1, textBytes);  
                        obj.success('写卡成功');  
                        //写入密钥  
                        //mfc.writeBlock(block + 3, password);  
                        mfc.close();  
                    } else {  
                        obj.fail('认证失败');  
                    }  
                    return;  
                }  

            } catch (e) {  
                obj.fail(e);  
                console.log('error=' + e);  
            }  
        },  
        ByteArrayToHexString: function(inarray) {  
            var i, j, inn;  
            var hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];  
            var out = '';  
            for (j = 0; j < inarray.length; ++j) {  
                inn = inarray[j] & 0xff;  
                i = (inn >>> 4) & 0x0f;  
                out += hex[i];  
                i = inn & 0x0f;  
                out += hex[i];  
            }  
            return out;  
        },  
        HexStringToByteArray: function(instr) {  
            var hexA = new Array();  
            var pos = 0;  
            var len = instr.length / 2;  
            for (var i = 0; i < len; i++) {  
                var s = instr.substr(pos, 2);  
                var v = parseInt(s, 16);  
                if (v >= 128) v = v - 256;  
                hexA.push(v);  
                pos += 2;  
            }  
            return hexA;  
        }  
    }  
};  
</script>  

<style></style>
继续阅读 »
<script>  
let Context = plus.android.importClass('android.content.Context');  
let NfcManager = plus.android.importClass('android.nfc.NfcManager');  
let NfcAdapter = plus.android.importClass('android.nfc.NfcAdapter');  
let Settings = plus.android.importClass('android.provider.Settings');  
let Intent = plus.android.importClass('android.content.Intent');  
let Parcelable = plus.android.importClass('android.os.Parcelable');  
let PendingIntent = plus.android.importClass('android.app.PendingIntent');  
let IntentFilter = plus.android.importClass('android.content.IntentFilter');  
let NdefRecord = plus.android.importClass('android.nfc.NdefRecord');  
let NdefMessage = plus.android.importClass('android.nfc.NdefMessage');  
let Tag = plus.android.importClass('android.nfc.Tag');  
let MifareClassic = plus.android.importClass('android.nfc.tech.MifareClassic');  
let invoke = plus.android.invoke;  
export default {  
    data() {  
        return {  
            sector: 1,  
            keyType: 'A',  
            keyVal: 'FFFFFFFFFFFF', //'E58583E69C94', //'FFFFFFFFFFFF',  
            ICUID: '',  
            ICData: 'IC卡扇区数据:',  
            ICERROR: '',  
            nfcAdapter: null,  
            main: null,  
            intent: null,  
            IntervalId: null,  
            techListsArray: [  
                ['android.nfc.tech.IsoDep'],  
                ['android.nfc.tech.NfcA'],  
                ['android.nfc.tech.NfcB'],  
                ['android.nfc.tech.NfcF'],  
                ['android.nfc.tech.NfcV'],  
                ['android.nfc.tech.Ndef'],  
                ['android.nfc.tech.NdefFormatable'],  
                ['android.nfc.tech.MifareClassic'],  
                ['android.nfc.tech.MifareUltralight']  
            ]  
        };  
    },  

    created() {  
        this.nfcinit();  
    },  
    beforeDestroy() {  
        this.nfcclose();  
    },  

    methods: {  
        nfcinit() {  
            this.main = plus.android.runtimeMainActivity();  
            var nfchManager = this.main.getSystemService(Context.NFC_SERVICE);  
            var nfcAdapter = nfchManager.getDefaultAdapter();  
            if (!nfcAdapter.isEnabled()) {  
                this.intent = new Intent(Settings.ACTION_NFC_SETTINGS);  
                this.main.startActivity(this.intent);  
            }  
            var intent = new Intent(this.main, this.main.getClass());  
            intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);  
            var pendingIntent = PendingIntent.getActivity(this.main, 0, intent, 0);  
            var ndef = new IntentFilter('android.nfc.action.TECH_DISCOVERED');  
            ndef.addDataType('*/*');  
            var intentFiltersArray = [ndef];  
            nfcAdapter.enableForegroundDispatch(this.main, pendingIntent, intentFiltersArray, this.techListsArray);  
            this.nfcAdapter = nfcAdapter;  
        },  
        nfcclose() {  
            if (this.nfcAdapter) this.nfcAdapter.disableForegroundDispatch(this.main);  
            this.nfcAdapter = null;  
            clearInterval(this.IntervalId);  
        },  
        handle_nfc_data(obj) {  
            var intent = this.main.getIntent();  
            if (intent.getAction() == 'android.nfc.action.TECH_DISCOVERED') {  
                clearInterval(this.IntervalId);  
                this.readData(intent, obj);  
            }  
        },  
        // 读扇区  请参考 https://ask.dcloud.net.cn/article/35593  
        readData(intent, obj) {  
            var tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);  
            var techList = tag.getTechList();  
            var bisMfc = false;  
            for (var i = 0; i < techList.length; i++) {  
                if (techList[i].indexOf('MifareClassic') >= 0) {  
                    bisMfc = true;  
                    break;  
                }  
            }  
            if (!bisMfc) {  
                this.ICERROR = '卡片类型错误!';  
                return;  
            }  
            var mfc = MifareClassic.get(tag);  
            if (!mfc) {  
                this.ICERROR = '卡片获取错误!';  
                return;  
            }  
            mfc.setTimeout(3000);  
            if (!mfc.isConnected()) {  
                try {  
                    invoke(mfc, 'connect');  
                } catch (e) {  
                    this.ICERROR = '卡片连接错误!';  
                    return;  
                }  
            }  
            try {  
                this.ICUID = this.ByteArrayToHexString(tag.getId());  
                var cmdBytes = this.HexStringToByteArray(this.keyVal);  
                var auth = false;  
                //使用密钥A对扇区进行身份验证。  
                if (this.keyType == 'A') {  
                    auth = invoke(mfc, 'authenticateSectorWithKeyA', parseInt(this.sector), cmdBytes);  
                } else {  
                    //使用密钥B对扇区进行身份验证。  
                    auth = invoke(mfc, 'authenticateSectorWithKeyB', parseInt(this.sector), cmdBytes);  
                }  
                if (!auth) {  
                    this.ICERROR = '扇区验证失败';  
                    return;  
                }  
                var sectorData = [];  
                var tmpRet;  
                this.ICData = 'IC卡扇区数据>>';  
                // 调用 MifareClassic 读指定块 返回的是字节数组    
                // 若需读取扇区中的所有块  请参考 https://ask.dcloud.net.cn/article/35593  
                tmpRet = invoke(mfc, 'readBlock', 5);  
                // 此项目写入的时候有中文,用网上的字节转字符串 会出现解析乱码  
                // 所以此处导入 java String 类  
                var String = plus.android.importClass('java.lang.String');  
                // 将自己数组转换成java 字符串  
                var str = new String(tmpRet);  
                // 调用java String 类的 concat 方法 拼接一个空的字符串,不然输出str 是一个类名 plus.android.java.lang.String  

                obj.success(str.trim());  
                this.ICERROR = '读卡完成';  
            } catch (e) {  
                this.ICERROR = e.message;  
                obj.fail(e);  
            } finally {  
                mfc.close();  
            }  
        },  

        readcard(obj) {  
            var that = this;  
            (this.ICUID = ''),  
                (this.ICData = ''),  
                (this.ICERROR = ''),  
                (this.IntervalId = setInterval(function() {  
                    that.handle_nfc_data(obj);  
                }, 1000));  
        },  
        //   
        handle_nfc_write(sectorIndex, text, obj) {  
            var intent = this.main.getIntent();  
            if (intent.getAction() == 'android.nfc.action.TECH_DISCOVERED') {  

                this.write(intent, sectorIndex, text, obj);  
            }  
        },  
        //sectorIndex 扇区  
        // text 要写入的内容  
        // obj callback()  
        write(intent, sectorIndex, text, obj) {  
            try {  
                // 将要写入的内容转成字节数组  
                var textBytes = plus.android.invoke(text, 'getBytes', 'utf-8');  
                // android api参考地址  
                // https://developer.android.google.cn/reference/android/nfc/tech/MifareClassic?hl=en#writeBlock(int,%20byte[])  
                // 写入16个字节的块。不够补0  
                for (var i = textBytes.length; i < 16; i++) {  
                    textBytes.push(0);  
                }  
                var textRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, plus.android.invoke('text/plain', 'getBytes'), plus.android.invoke('', 'getBytes'), textBytes);  
                var message = new NdefMessage([textRecord]);  
                var NdefFormatable = plus.android.importClass('android.nfc.tech.NdefFormatable');  
                var tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);  
                var mfc = MifareClassic.get(tag);  
                if (mfc != null) {  
                    mfc.connect();  
                    // 默认密钥  
                    var cmdBytes = this.HexStringToByteArray('FFFFFFFFFFFF');  
                    // 使用密钥A对扇区进行身份验证。  
                    let auth = mfc.authenticateSectorWithKeyA(sectorIndex, cmdBytes);  
                    // var pass = 'E58583E69C94';  
                    // var password =this.HexStringToByteArray(pass);  
                    // for (var i = password.length; i < 16; i++) {  
                    //  password.push(0);  
                    // }  
                    if (auth) {  
                        //返回给定扇区的第一个块。  
                        let block = mfc.sectorToBlock(sectorIndex);  
                        // 写入块 我们项目写在 扇区1,第2块  
                        mfc.writeBlock(block + 1, textBytes);  
                        obj.success('写卡成功');  
                        //写入密钥  
                        //mfc.writeBlock(block + 3, password);  
                        mfc.close();  
                    } else {  
                        obj.fail('认证失败');  
                    }  
                    return;  
                }  

            } catch (e) {  
                obj.fail(e);  
                console.log('error=' + e);  
            }  
        },  
        ByteArrayToHexString: function(inarray) {  
            var i, j, inn;  
            var hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];  
            var out = '';  
            for (j = 0; j < inarray.length; ++j) {  
                inn = inarray[j] & 0xff;  
                i = (inn >>> 4) & 0x0f;  
                out += hex[i];  
                i = inn & 0x0f;  
                out += hex[i];  
            }  
            return out;  
        },  
        HexStringToByteArray: function(instr) {  
            var hexA = new Array();  
            var pos = 0;  
            var len = instr.length / 2;  
            for (var i = 0; i < len; i++) {  
                var s = instr.substr(pos, 2);  
                var v = parseInt(s, 16);  
                if (v >= 128) v = v - 256;  
                hexA.push(v);  
                pos += 2;  
            }  
            return hexA;  
        }  
    }  
};  
</script>  

<style></style>
收起阅读 »

uniCloud admin:token不合法,请重新登录,30201,解决方案

解决方案

框架:uniCloud admin
问题:在调用增删改查功能时,老遇到token不合法的问题
原因:uni-id验证token时,默认开启了绑定设备

解决:取消绑定设备bindTokenToDevice=false

/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/node_modules/uni-config-center/uni-id/config.json

继续阅读 »

框架:uniCloud admin
问题:在调用增删改查功能时,老遇到token不合法的问题
原因:uni-id验证token时,默认开启了绑定设备

解决:取消绑定设备bindTokenToDevice=false

/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/node_modules/uni-config-center/uni-id/config.json

收起阅读 »

给Huilderx 一点使用建议

希望官方能把scss、less插件纳入到核心插件里面去,直接点击安装,因为使用频率比较高,而且这两款插件大部分开发者也在用。一点小小意见,望采纳

希望官方能把scss、less插件纳入到核心插件里面去,直接点击安装,因为使用频率比较高,而且这两款插件大部分开发者也在用。一点小小意见,望采纳