HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

聚合查询,多表统计数据的demo

个人也是小白,想实现多表统计功能,一直看不懂官方文档,今天找了大佬写了一个demo,有需要的可以了解一下。

我想实现的效果

user表
[
{
"_id": "64c22b3e189f866d65c3d0eb",
"name": "张三",
"sex": 1
},
{
"_id": "64c22b5a337a9f4db7882114",
"name": "王五",
"sex": 1
},
{
"_id": "64c22b5055b337825753d977",
"name": "李娇",
"sex": 0
},
{
"_id": "64c22b6bbd0220bf8c7051c5",
"name": "六花",
"sex": 0
}
]

sp表
[
{
"_id": "64c22cbe99c6246543bbfe9d",
"je": 20,
"name_id": "64c22b5055b337825753d977",
"sp": "李娇的商品1"
},
{
"_id": "64c22ce36e5d2d8f64af3f8e",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品4"
},
{
"_id": "64c22cd97ad52ddc6482f40a",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品2"
},
{
"_id": "64c22c766e5d2d8f64af2804",
"je": 20,
"name_id": "64c22b3e189f866d65c3d0eb",
"sp": "张三的商品2"
},
{
"_id": "64c22cf7f08210d515b35a86",
"je": 20,
"name_id": "64c22b6bbd0220bf8c7051c5",
"sp": "六花的商品1"
},
{
"_id": "64c22cd421821b2af5b6b48a",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品1"
},
{
"_id": "64c22cdf337a9f4db78875c8",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品3"
},
{
"_id": "64c22c71e0ec19bea1b13e15",
"je": 20,
"name_id": "64c22b3e189f866d65c3d0eb",
"sp": "张三的商品1"
},
{
"_id": "64c22cc27ad52ddc6482eecb",
"je": 20,
"name_id": "64c22b5055b337825753d977",
"sp": "李娇的商品2"
},
{
"_id": "64c22c7aa09a9bd68ba01823",
"je": 20,
"name_id": "64c22b3e189f866d65c3d0eb",
"sp": "张三的商品3"
}
]

wz表
[
{
"_id": "64c22f43337a9f4db788fdd8",
"name_id": "64c22b3e189f866d65c3d0eb",
"wzdz": 12,
"wzname": "六花的文章2"
},
{
"_id": "64c22f5ffe975fba5a5e51ef",
"name_id": "64c22b5055b337825753d977",
"wzdz": 34,
"wzname": "李娇的文章2"
},
{
"_id": "64c22f57337a9f4db7890253",
"name_id": "64c22b5055b337825753d977",
"wzdz": 3,
"wzname": "李娇的文章1"
},
{
"_id": "64c22f3ef08210d515b3e212",
"name_id": "64c22b3e189f866d65c3d0eb",
"wzdz": 12,
"wzname": "六花的文章1"
},
{
"_id": "64c22f7b6e5d2d8f64afdba0",
"name_id": "64c22b5a337a9f4db7882114",
"wzdz": 54,
"wzname": "王五的文章2"
},
{
"_id": "64c22f739755e344ab9c51ce",
"name_id": "64c22b5a337a9f4db7882114",
"wzdz": 11,
"wzname": "王五的文章1"
},
{
"_id": "64c235cd466d416d30844f4e",
"name_id": "64c22b5a337a9f4db7882114",
"wzdz": 13,
"wzname": "王五的文章3"
}
]

最终结果

张三 商品3 点赞24
李 商品2 点赞37
王五 商品4 点赞65
六花 商品1 点赞0

聚合查询代码

const db = uniCloud.database()  
const dbCmd = db.command  
const $ = dbCmd.aggregate  

exports.main = async (event, context) => {  

    const {data}=await db.collection(`user`).aggregate()  
    .lookup({  
        from:`sp`,  
        let:{  
            user_id:`$_id`,  
        },  
        pipeline:$.pipeline()  
        .match(  
            dbCmd.expr(  
                $.eq(['$name_id','$$user_id'])  
            )  
        )  
        .group({  
            _id:'$name_id',  
            count: $.sum(1),  
        })  
        .done(),  
        as: 'sp_list',  
    })  
    .lookup({  
        from:`wz`,  
        let:{  
            user_id:`$_id`,  
        },  
        pipeline:$.pipeline()  
        .match(  
            dbCmd.expr(  
                $.eq(['$name_id','$$user_id'])  
            )  
        )  
        .group({  
            _id:'$name_id',  
            wzdz:$.sum('$wzdz'),  
        })  
        .done(),  
        as: 'wz_list',  
    })  
    .end();  

    return {  
        data:data  
    }  
};
继续阅读 »

个人也是小白,想实现多表统计功能,一直看不懂官方文档,今天找了大佬写了一个demo,有需要的可以了解一下。

我想实现的效果

user表
[
{
"_id": "64c22b3e189f866d65c3d0eb",
"name": "张三",
"sex": 1
},
{
"_id": "64c22b5a337a9f4db7882114",
"name": "王五",
"sex": 1
},
{
"_id": "64c22b5055b337825753d977",
"name": "李娇",
"sex": 0
},
{
"_id": "64c22b6bbd0220bf8c7051c5",
"name": "六花",
"sex": 0
}
]

sp表
[
{
"_id": "64c22cbe99c6246543bbfe9d",
"je": 20,
"name_id": "64c22b5055b337825753d977",
"sp": "李娇的商品1"
},
{
"_id": "64c22ce36e5d2d8f64af3f8e",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品4"
},
{
"_id": "64c22cd97ad52ddc6482f40a",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品2"
},
{
"_id": "64c22c766e5d2d8f64af2804",
"je": 20,
"name_id": "64c22b3e189f866d65c3d0eb",
"sp": "张三的商品2"
},
{
"_id": "64c22cf7f08210d515b35a86",
"je": 20,
"name_id": "64c22b6bbd0220bf8c7051c5",
"sp": "六花的商品1"
},
{
"_id": "64c22cd421821b2af5b6b48a",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品1"
},
{
"_id": "64c22cdf337a9f4db78875c8",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品3"
},
{
"_id": "64c22c71e0ec19bea1b13e15",
"je": 20,
"name_id": "64c22b3e189f866d65c3d0eb",
"sp": "张三的商品1"
},
{
"_id": "64c22cc27ad52ddc6482eecb",
"je": 20,
"name_id": "64c22b5055b337825753d977",
"sp": "李娇的商品2"
},
{
"_id": "64c22c7aa09a9bd68ba01823",
"je": 20,
"name_id": "64c22b3e189f866d65c3d0eb",
"sp": "张三的商品3"
}
]

wz表
[
{
"_id": "64c22f43337a9f4db788fdd8",
"name_id": "64c22b3e189f866d65c3d0eb",
"wzdz": 12,
"wzname": "六花的文章2"
},
{
"_id": "64c22f5ffe975fba5a5e51ef",
"name_id": "64c22b5055b337825753d977",
"wzdz": 34,
"wzname": "李娇的文章2"
},
{
"_id": "64c22f57337a9f4db7890253",
"name_id": "64c22b5055b337825753d977",
"wzdz": 3,
"wzname": "李娇的文章1"
},
{
"_id": "64c22f3ef08210d515b3e212",
"name_id": "64c22b3e189f866d65c3d0eb",
"wzdz": 12,
"wzname": "六花的文章1"
},
{
"_id": "64c22f7b6e5d2d8f64afdba0",
"name_id": "64c22b5a337a9f4db7882114",
"wzdz": 54,
"wzname": "王五的文章2"
},
{
"_id": "64c22f739755e344ab9c51ce",
"name_id": "64c22b5a337a9f4db7882114",
"wzdz": 11,
"wzname": "王五的文章1"
},
{
"_id": "64c235cd466d416d30844f4e",
"name_id": "64c22b5a337a9f4db7882114",
"wzdz": 13,
"wzname": "王五的文章3"
}
]

最终结果

张三 商品3 点赞24
李 商品2 点赞37
王五 商品4 点赞65
六花 商品1 点赞0

聚合查询代码

const db = uniCloud.database()  
const dbCmd = db.command  
const $ = dbCmd.aggregate  

exports.main = async (event, context) => {  

    const {data}=await db.collection(`user`).aggregate()  
    .lookup({  
        from:`sp`,  
        let:{  
            user_id:`$_id`,  
        },  
        pipeline:$.pipeline()  
        .match(  
            dbCmd.expr(  
                $.eq(['$name_id','$$user_id'])  
            )  
        )  
        .group({  
            _id:'$name_id',  
            count: $.sum(1),  
        })  
        .done(),  
        as: 'sp_list',  
    })  
    .lookup({  
        from:`wz`,  
        let:{  
            user_id:`$_id`,  
        },  
        pipeline:$.pipeline()  
        .match(  
            dbCmd.expr(  
                $.eq(['$name_id','$$user_id'])  
            )  
        )  
        .group({  
            _id:'$name_id',  
            wzdz:$.sum('$wzdz'),  
        })  
        .done(),  
        as: 'wz_list',  
    })  
    .end();  

    return {  
        data:data  
    }  
};
收起阅读 »

uniapp编译小程序 slot出现将异常

bug提交

自定义组件中使用了slot插槽,page页使用自定义组件时,将page页面的其他标签元素都渲染进自定义组件插槽

自定义组件中使用了slot插槽,page页使用自定义组件时,将page页面的其他标签元素都渲染进自定义组件插槽

关于HBuilder X打包的APP按返回键退出的问题(解决办法)

前一段时间,我也是被这个问题困扰了很多次,在论坛和百度上也找了很多,只是都没有仔细的说明怎么调用,
今天我在这里给大家说明一下怎么调用

首先先创建一个文件,命名为:houtui.js
放在文件的JS目录里,或者随意,记住路径就行,
js文件内容

document.addEventListener('plusready', function() {    
            var first = null;    
            var webview = plus.webview.currentWebview();    
            plus.key.addEventListener('backbutton', function() {    
                webview.canBack(function(e) {    
                    if (e.canBack) {    
                        webview.back(); //这里不建议修改自己跳转的路径    
                    } else {    
                        //首次按键,提示‘再按一次退出应用’    
                        if (!first) {    
                            first = new Date().getTime(); //获取第一次点击的时间戳    
                            // console.log('再按一次退出应用');//用自定义toast提示最好    
                            // toast('双击返回键退出应用'); //调用自己写的吐丝提示 函数    
                            plus.nativeUI.toast("再按一次退出应用", {    
                                duration: 'short'    
                            }); //通过H5+ API 调用Android 上的toast 提示框    
                            setTimeout(function() {    
                                first = null;    
                            }, 1000);    
                        } else {    
                            if (new Date().getTime() - first < 1000) { //获取第二次点击的时间戳, 两次之差 小于 1000ms 说明1s点击了两次,    
                                plus.runtime.quit(); //退出应用    
                            }    
                        }    
                    }    
                })    
            });    
        });

之后再H5网站的首页,添加

<body>  
<script src=/h5/static/js/houtui01.js></script>  
</body>

需要注意的是,你5+app 调用的是 域名链接,就得在网站源码里下载

你要是调用的app,要在HBuilder X设置调用好就行

继续阅读 »

前一段时间,我也是被这个问题困扰了很多次,在论坛和百度上也找了很多,只是都没有仔细的说明怎么调用,
今天我在这里给大家说明一下怎么调用

首先先创建一个文件,命名为:houtui.js
放在文件的JS目录里,或者随意,记住路径就行,
js文件内容

document.addEventListener('plusready', function() {    
            var first = null;    
            var webview = plus.webview.currentWebview();    
            plus.key.addEventListener('backbutton', function() {    
                webview.canBack(function(e) {    
                    if (e.canBack) {    
                        webview.back(); //这里不建议修改自己跳转的路径    
                    } else {    
                        //首次按键,提示‘再按一次退出应用’    
                        if (!first) {    
                            first = new Date().getTime(); //获取第一次点击的时间戳    
                            // console.log('再按一次退出应用');//用自定义toast提示最好    
                            // toast('双击返回键退出应用'); //调用自己写的吐丝提示 函数    
                            plus.nativeUI.toast("再按一次退出应用", {    
                                duration: 'short'    
                            }); //通过H5+ API 调用Android 上的toast 提示框    
                            setTimeout(function() {    
                                first = null;    
                            }, 1000);    
                        } else {    
                            if (new Date().getTime() - first < 1000) { //获取第二次点击的时间戳, 两次之差 小于 1000ms 说明1s点击了两次,    
                                plus.runtime.quit(); //退出应用    
                            }    
                        }    
                    }    
                })    
            });    
        });

之后再H5网站的首页,添加

<body>  
<script src=/h5/static/js/houtui01.js></script>  
</body>

需要注意的是,你5+app 调用的是 域名链接,就得在网站源码里下载

你要是调用的app,要在HBuilder X设置调用好就行

收起阅读 »

nvue 做首页时候,会出现空白

nvue

只要在onLoad(){setTimeout(()=>{
uni.switchTab({
url:'/pages/discover_index/index'
})
},1000);} 重新进一次当前启动页就好了

只要在onLoad(){setTimeout(()=>{
uni.switchTab({
url:'/pages/discover_index/index'
})
},1000);} 重新进一次当前启动页就好了

#creatcavascontext这个是小程序的废弃接口

升级更新

creatcavascontext这个是小程序的废弃接口

https://uniapp.dcloud.net.cn/api/canvas/createCanvasContext.html#createcanvascontext

https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.createCanvasContext.html


继续阅读 »

creatcavascontext这个是小程序的废弃接口

https://uniapp.dcloud.net.cn/api/canvas/createCanvasContext.html#createcanvascontext

https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.createCanvasContext.html


收起阅读 »

uniapp实现IM即时通讯仿微信聊天功能

uniapp 教程

本文介绍如何基于 UNIAPP 使用 即时通讯SDK ZIM SDK 快速实现基本的消息收发功能。

1 uniapp im 即时通讯功能 方案介绍

即时通讯SDK ZIM SDK 提供了如下接入方案:

在此方案中,您需要通过您自己的业务系统实现以下业务逻辑:

  • 搭建客户端的用户管理逻辑,并下发用户 ID 用于客户端登录。
  • 鉴权 Token,建议由您的业务后台自行实现,保证鉴权数据安全。

uni-app SDK 是一个基于原生 iOS/Android 平台 ZIM SDK 的 uni-app Wrapper。开发者如需使用 uni-app 开发 Web 或小程序平台的应用,请下载对应的 SDK 集成使用:下载 Web SDK下载小程序 SDK

2 集成 IM 即时通讯SDK 的前提条件

在使用 IM即时通讯 SDK ZIM SDK 前,请确保:

  • 已在 ZEGO控制台 创建项目,获取到了接入 IM即时通讯 ZIM SDK 服务所需的 AppID 和 ServerSecret。ZIM 服务权限不是默认开启的,使用前,请先在 ZEGO 控制台 自助开通 ZIM 即时通讯 服务(详情请参考 项目管理 - 即时通讯),若无法开通 ZIM即时通讯 服务,请联系 ZEGO 技术支持开通。

  • 已获取登录 即时通讯 SDK 所需的 Token,详情请参考 使用 Token 鉴权。

3 集成 uniapp IM 即时通讯 SDK

3.1 (可选)新建项目
此步骤以如何创建新项目为例,如果是集成到已有项目,可忽略此步。

1.启动 HBuilderX,选择“文件 > 新建 > 项目”菜单。

2.在出现的表单中,选择 “uni-app” 平台,并填写项目名称。

3.单击“创建”,即可创建项目。

3.2 导入ZIM 即时通讯 SDK
以下两种方式可以任选一种导入。

方式一:在 ZEGO即构科技 官网下载 ZIM 即时通讯 SDK

  • 请参考 下载 页面,获取最新版本的 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。

  • 将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。

方式二:通过 uni-app 插件市场获取 ZIM uni-app SDK

通过 uni-app 插件市场也有两种方式导入,请任选一种:

  • 单击 “购买(0元) for 云打包”,选择相应的项目导入。

  • 单击 “下载 for 离线打包”,离线导入。

  • 下载 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。

  • 将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。

3.3 在 uni-app 项目中导入插件

1.单击项目目录的 “manifest.json” 文件后,单击 “App原生插件配置” 中的 “选择本地插件” 或 “选择云端插件”。

2.在弹出的选择框中,选择 “Zego ZIM 即时通讯 SDK” 后,单击“确认”,即添加成功。

3.4 自定义调试基座
3.4.1 制作自定义调试基座
uni-app 官方自定义调试基座使用说明,请参考 什么是自定义调试基座及使用说明 。

1.选择 “运行 > 运行到手机或模拟器 > 制作自定义调试基座” 菜单。

2.在弹出的界面中,按照 uni-app 教程,填写相关信息,并单击“打包”进行云打包。

打包成功后,控制台会收到 uni-app 的相关提示。

3.4.2 切换运行基座为自定义调试基座
自定义调试基座,请选择“运行 > 运行到手机或模拟器 > 运行基座选择 > 自定义调试基座”菜单。

3.5 集成 JS 封装层
3.5.1 导入 JS 封装层
以下两种方式可以任选一种导入。

  • 方式一:请参考 下载 页面,获取最新版本的 JS 封装层到本地,并将得到的 “zego-ZIMUniPlugin-JS.zip” 文件解压缩。

下载的 JS 封装层可以拷贝到 HBuilderX 的 “js_sdk” 目录中。(如无该目录,请创建一个)

  • 方式二:在插件市场的 Zego ZIM 即时通讯原生插件(JS 封装层) 界面单击右侧的 “使用 HBuilderX 导入插件”。

导入的 JS 封装层将存储在 “js_sdk” 目录中。

本文介绍如何基于 UNIAPP 使用 即时通讯SDK ZIM SDK 快速实现基本的消息收发功能。

1 uniapp im 即时通讯功能 方案介绍

即时通讯SDK ZIM SDK 提供了如下接入方案:

在此方案中,您需要通过您自己的业务系统实现以下业务逻辑:

  • 搭建客户端的用户管理逻辑,并下发用户 ID 用于客户端登录。
  • 鉴权 Token,建议由您的业务后台自行实现,保证鉴权数据安全。

uni-app SDK 是一个基于原生 iOS/Android 平台 ZIM SDK 的 uni-app Wrapper。开发者如需使用 uni-app 开发 Web 或小程序平台的应用,请下载对应的 SDK 集成使用:下载 Web SDK下载小程序 SDK

2 集成 IM 即时通讯SDK 的前提条件

在使用 IM即时通讯 SDK ZIM SDK 前,请确保:

  • 已在 ZEGO控制台 创建项目,获取到了接入 IM即时通讯 ZIM SDK 服务所需的 AppID 和 ServerSecret。ZIM 服务权限不是默认开启的,使用前,请先在 ZEGO 控制台 自助开通 ZIM 即时通讯 服务(详情请参考 项目管理 - 即时通讯),若无法开通 ZIM即时通讯 服务,请联系 ZEGO 技术支持开通。

  • 已获取登录 即时通讯 SDK 所需的 Token,详情请参考 使用 Token 鉴权。

3 集成 uniapp IM 即时通讯 SDK

3.1 (可选)新建项目
此步骤以如何创建新项目为例,如果是集成到已有项目,可忽略此步。

1.启动 HBuilderX,选择“文件 > 新建 > 项目”菜单。

2.在出现的表单中,选择 “uni-app” 平台,并填写项目名称。

3.单击“创建”,即可创建项目。

3.2 导入ZIM 即时通讯 SDK
以下两种方式可以任选一种导入。

方式一:在 ZEGO即构科技 官网下载 ZIM 即时通讯 SDK

  • 请参考 下载 页面,获取最新版本的 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。

  • 将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。

方式二:通过 uni-app 插件市场获取 ZIM uni-app SDK

通过 uni-app 插件市场也有两种方式导入,请任选一种:

  • 单击 “购买(0元) for 云打包”,选择相应的项目导入。

  • 单击 “下载 for 离线打包”,离线导入。

  • 下载 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。

  • 将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。

3.3 在 uni-app 项目中导入插件

1.单击项目目录的 “manifest.json” 文件后,单击 “App原生插件配置” 中的 “选择本地插件” 或 “选择云端插件”。

2.在弹出的选择框中,选择 “Zego ZIM 即时通讯 SDK” 后,单击“确认”,即添加成功。

3.4 自定义调试基座
3.4.1 制作自定义调试基座
uni-app 官方自定义调试基座使用说明,请参考 什么是自定义调试基座及使用说明 。

1.选择 “运行 > 运行到手机或模拟器 > 制作自定义调试基座” 菜单。

2.在弹出的界面中,按照 uni-app 教程,填写相关信息,并单击“打包”进行云打包。

打包成功后,控制台会收到 uni-app 的相关提示。

3.4.2 切换运行基座为自定义调试基座
自定义调试基座,请选择“运行 > 运行到手机或模拟器 > 运行基座选择 > 自定义调试基座”菜单。

3.5 集成 JS 封装层
3.5.1 导入 JS 封装层
以下两种方式可以任选一种导入。

  • 方式一:请参考 下载 页面,获取最新版本的 JS 封装层到本地,并将得到的 “zego-ZIMUniPlugin-JS.zip” 文件解压缩。

下载的 JS 封装层可以拷贝到 HBuilderX 的 “js_sdk” 目录中。(如无该目录,请创建一个)

  • 方式二:在插件市场的 Zego ZIM 即时通讯原生插件(JS 封装层) 界面单击右侧的 “使用 HBuilderX 导入插件”。

导入的 JS 封装层将存储在 “js_sdk” 目录中。

3.5.2 在项目中引入 JS 封装层
导入后,可以在业务代码中引入 JS 封装层,并调用 ZIM 相关接口,示例如下:

import { ZIM } from 'zego-zim-react-native';

const ZIM = require('zego-zim-react-native').ZIM;

4 实现仿微信的基本收发消息聊天功能

以下流程中,我们以客户端 A 和 B 的消息交互为例,实现 1v1 通信功能:

4.1 uniapp 即时通讯 实现流程
请参考 跑通示例源码 获取源码。

  1. 导入ZIM 即时通讯 SDK 文件
    请参考 3.2 导入 SDK,导入 SDK 文件。

  2. 创建 ZIM即时通讯 实例
    首先我们需要在项目中创建 ZIM 实例,一个实例对应的是一个用户,表示一个用户以客户端的身份登录系统。

例如,客户端 A、B 分别调用 create(@create) 接口,传入在 2 前提条件 中获取到的 AppID,创建了 A、B 的实例:

var appID = xxxx;
// 静态同步方法,创建 zim 实例,传入 AppID
var zim = ZIM.create(appID);

  1. 监听回调事件
    在客户端登录前,开发者可以通过调用 on 接口,自定义 ZIM 中的事件回调,接收到 SDK 异常、消息通知回调等的通知。

// 注册监听“运行时错误信息”的回调
zim.on('error', function (zim, errorInfo) {
console.log('error', errorInfo.code, errorInfo.message);
});

// 注册监听“网络连接状态变更”的回调
zim.on('connectionStateChanged', function (zim, { state, event, extendedData }) {
console.log('connectionStateChanged', state, event, extendedData);
});

// 注册监听“收到单聊消息”的回调
zim.on('receivePeerMessage', function (zim, { messageList, fromConversationID }) {
console.log('receivePeerMessage', messageList, fromConversationID);
});

// 注册监听“令牌即将过期”的回调
zim.on('tokenWillExpire', function (zim, { second }) {
console.log('tokenWillExpire', second);
// 可以在这里调用 renewToken 接口来更新 token
// 新 token 生成可以参考上文
zim.renewToken(token)
.then(function({ token }){
// 更新成功
})
.catch(function(err){
// 更新失败
})
});

4. 登录 ZIM 即时通讯

创建实例后,客户端 A 和 B 需要登录 ZIM,只有登录成功后才可以开始发送、接收消息、更新 Token 等。

客户端需要使用各自的用户信息和 Token 进行登录。调用 login 接口进行登录,传入用户信息 ZIMUserInfo 对象,以及在 2 前提条件 中获取到的 Token 进行鉴权,鉴权通过后才能登录成功。

  • “userID”、“userName” 支持开发者自定义规则生成。建议开发者将 “userID” 设置为一个有意义的值,可将其与自己的业务账号系统进行关联。
  • 如果 Token 过期,需要在 tokenWillExpire 即将过期回调接口中,调用 renewToken 接口,更新 Token 后才能正常使用 SDK。

// 登录时,需要开发者 按照 "使用 Token 鉴权" 文档生成 token 即可
// userID 和 userName,最大 32 字节的字符串。仅支持数字,英文字符 和 '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '=', '-', '`', ';', '’', ',', '.', '<', '>', '/', '\'。
var userInfo = { userID: 'xxxx', userName: 'xxxx' };
var token = '';

zim.login(userInfo, token)
.then(function () {
// 登录成功
})
.catch(function (err) {
// 登录失败
});

5. 发送消息聊天验证

客户端 A 登录成功后,可以向客户端 B 发送消息。

目前 ZIM 支持的消息类型如下:

以下为发送单聊文本消息为例:客户端 A 可以调用 sendPeerMessage 接口,传入客户端 B 的 userID、消息内容等信息,即可发送一条文本消息到 B 的客户端。

var toUserID = 'xxxx1';
var config = {
priority: 1 // 消息优先级,取值为 低:1 默认,中:2,高:3
};

// 发送单聊 Text 信息
var messageTextObj = { type: 1, message: '文本消息内容' };
zim.sendPeerMessage(messageTextObj, toUserID, config)
.then(function ({ message }) {
// 发送成功
})
.catch(function (err) {
// 发送失败
});

6. 接收消息聊天验证

客户端 B 登录 ZIM 后,将会收到在 on 回调中设置的 receivePeerMessage 监听接口,收到客户端 A 发送过来的消息。

// 注册监听“收到单聊消息”的回调
zim.on('receivePeerMessage', function (zim, { messageList, fromConversationID }) {
console.log('receivePeerMessage', messageList, fromConversationID);
});

7. 退出登录

如果客户端需要退出登录,直接调用 logout 接口即可。

zim.logout();

8. 销毁 ZIM 实例

如果不需要 ZIM 实例,可直接调用 destroy 接口,销毁实例。

zim.destroy();

4.2 API 时序图
通过以上的实现流程描述,API 接口调用时序图如下:

5 uniapp跨平台框架接入ZIM 即时通讯 SDK更多帮助

获取本文即时通讯框架接入IM 即时通讯聊天的开发文档、技术支持,访问即构文档中心IM即时通讯开发文档页 ,可多平台实现聊天社交IM即时通讯功能;

继续阅读 »

本文介绍如何基于 UNIAPP 使用 即时通讯SDK ZIM SDK 快速实现基本的消息收发功能。

1 uniapp im 即时通讯功能 方案介绍

即时通讯SDK ZIM SDK 提供了如下接入方案:

在此方案中,您需要通过您自己的业务系统实现以下业务逻辑:

  • 搭建客户端的用户管理逻辑,并下发用户 ID 用于客户端登录。
  • 鉴权 Token,建议由您的业务后台自行实现,保证鉴权数据安全。

uni-app SDK 是一个基于原生 iOS/Android 平台 ZIM SDK 的 uni-app Wrapper。开发者如需使用 uni-app 开发 Web 或小程序平台的应用,请下载对应的 SDK 集成使用:下载 Web SDK下载小程序 SDK

2 集成 IM 即时通讯SDK 的前提条件

在使用 IM即时通讯 SDK ZIM SDK 前,请确保:

  • 已在 ZEGO控制台 创建项目,获取到了接入 IM即时通讯 ZIM SDK 服务所需的 AppID 和 ServerSecret。ZIM 服务权限不是默认开启的,使用前,请先在 ZEGO 控制台 自助开通 ZIM 即时通讯 服务(详情请参考 项目管理 - 即时通讯),若无法开通 ZIM即时通讯 服务,请联系 ZEGO 技术支持开通。

  • 已获取登录 即时通讯 SDK 所需的 Token,详情请参考 使用 Token 鉴权。

3 集成 uniapp IM 即时通讯 SDK

3.1 (可选)新建项目
此步骤以如何创建新项目为例,如果是集成到已有项目,可忽略此步。

1.启动 HBuilderX,选择“文件 > 新建 > 项目”菜单。

2.在出现的表单中,选择 “uni-app” 平台,并填写项目名称。

3.单击“创建”,即可创建项目。

3.2 导入ZIM 即时通讯 SDK
以下两种方式可以任选一种导入。

方式一:在 ZEGO即构科技 官网下载 ZIM 即时通讯 SDK

  • 请参考 下载 页面,获取最新版本的 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。

  • 将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。

方式二:通过 uni-app 插件市场获取 ZIM uni-app SDK

通过 uni-app 插件市场也有两种方式导入,请任选一种:

  • 单击 “购买(0元) for 云打包”,选择相应的项目导入。

  • 单击 “下载 for 离线打包”,离线导入。

  • 下载 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。

  • 将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。

3.3 在 uni-app 项目中导入插件

1.单击项目目录的 “manifest.json” 文件后,单击 “App原生插件配置” 中的 “选择本地插件” 或 “选择云端插件”。

2.在弹出的选择框中,选择 “Zego ZIM 即时通讯 SDK” 后,单击“确认”,即添加成功。

3.4 自定义调试基座
3.4.1 制作自定义调试基座
uni-app 官方自定义调试基座使用说明,请参考 什么是自定义调试基座及使用说明 。

1.选择 “运行 > 运行到手机或模拟器 > 制作自定义调试基座” 菜单。

2.在弹出的界面中,按照 uni-app 教程,填写相关信息,并单击“打包”进行云打包。

打包成功后,控制台会收到 uni-app 的相关提示。

3.4.2 切换运行基座为自定义调试基座
自定义调试基座,请选择“运行 > 运行到手机或模拟器 > 运行基座选择 > 自定义调试基座”菜单。

3.5 集成 JS 封装层
3.5.1 导入 JS 封装层
以下两种方式可以任选一种导入。

  • 方式一:请参考 下载 页面,获取最新版本的 JS 封装层到本地,并将得到的 “zego-ZIMUniPlugin-JS.zip” 文件解压缩。

下载的 JS 封装层可以拷贝到 HBuilderX 的 “js_sdk” 目录中。(如无该目录,请创建一个)

  • 方式二:在插件市场的 Zego ZIM 即时通讯原生插件(JS 封装层) 界面单击右侧的 “使用 HBuilderX 导入插件”。

导入的 JS 封装层将存储在 “js_sdk” 目录中。

本文介绍如何基于 UNIAPP 使用 即时通讯SDK ZIM SDK 快速实现基本的消息收发功能。

1 uniapp im 即时通讯功能 方案介绍

即时通讯SDK ZIM SDK 提供了如下接入方案:

在此方案中,您需要通过您自己的业务系统实现以下业务逻辑:

  • 搭建客户端的用户管理逻辑,并下发用户 ID 用于客户端登录。
  • 鉴权 Token,建议由您的业务后台自行实现,保证鉴权数据安全。

uni-app SDK 是一个基于原生 iOS/Android 平台 ZIM SDK 的 uni-app Wrapper。开发者如需使用 uni-app 开发 Web 或小程序平台的应用,请下载对应的 SDK 集成使用:下载 Web SDK下载小程序 SDK

2 集成 IM 即时通讯SDK 的前提条件

在使用 IM即时通讯 SDK ZIM SDK 前,请确保:

  • 已在 ZEGO控制台 创建项目,获取到了接入 IM即时通讯 ZIM SDK 服务所需的 AppID 和 ServerSecret。ZIM 服务权限不是默认开启的,使用前,请先在 ZEGO 控制台 自助开通 ZIM 即时通讯 服务(详情请参考 项目管理 - 即时通讯),若无法开通 ZIM即时通讯 服务,请联系 ZEGO 技术支持开通。

  • 已获取登录 即时通讯 SDK 所需的 Token,详情请参考 使用 Token 鉴权。

3 集成 uniapp IM 即时通讯 SDK

3.1 (可选)新建项目
此步骤以如何创建新项目为例,如果是集成到已有项目,可忽略此步。

1.启动 HBuilderX,选择“文件 > 新建 > 项目”菜单。

2.在出现的表单中,选择 “uni-app” 平台,并填写项目名称。

3.单击“创建”,即可创建项目。

3.2 导入ZIM 即时通讯 SDK
以下两种方式可以任选一种导入。

方式一:在 ZEGO即构科技 官网下载 ZIM 即时通讯 SDK

  • 请参考 下载 页面,获取最新版本的 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。

  • 将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。

方式二:通过 uni-app 插件市场获取 ZIM uni-app SDK

通过 uni-app 插件市场也有两种方式导入,请任选一种:

  • 单击 “购买(0元) for 云打包”,选择相应的项目导入。

  • 单击 “下载 for 离线打包”,离线导入。

  • 下载 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。

  • 将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。

3.3 在 uni-app 项目中导入插件

1.单击项目目录的 “manifest.json” 文件后,单击 “App原生插件配置” 中的 “选择本地插件” 或 “选择云端插件”。

2.在弹出的选择框中,选择 “Zego ZIM 即时通讯 SDK” 后,单击“确认”,即添加成功。

3.4 自定义调试基座
3.4.1 制作自定义调试基座
uni-app 官方自定义调试基座使用说明,请参考 什么是自定义调试基座及使用说明 。

1.选择 “运行 > 运行到手机或模拟器 > 制作自定义调试基座” 菜单。

2.在弹出的界面中,按照 uni-app 教程,填写相关信息,并单击“打包”进行云打包。

打包成功后,控制台会收到 uni-app 的相关提示。

3.4.2 切换运行基座为自定义调试基座
自定义调试基座,请选择“运行 > 运行到手机或模拟器 > 运行基座选择 > 自定义调试基座”菜单。

3.5 集成 JS 封装层
3.5.1 导入 JS 封装层
以下两种方式可以任选一种导入。

  • 方式一:请参考 下载 页面,获取最新版本的 JS 封装层到本地,并将得到的 “zego-ZIMUniPlugin-JS.zip” 文件解压缩。

下载的 JS 封装层可以拷贝到 HBuilderX 的 “js_sdk” 目录中。(如无该目录,请创建一个)

  • 方式二:在插件市场的 Zego ZIM 即时通讯原生插件(JS 封装层) 界面单击右侧的 “使用 HBuilderX 导入插件”。

导入的 JS 封装层将存储在 “js_sdk” 目录中。

3.5.2 在项目中引入 JS 封装层
导入后,可以在业务代码中引入 JS 封装层,并调用 ZIM 相关接口,示例如下:

import { ZIM } from 'zego-zim-react-native';

const ZIM = require('zego-zim-react-native').ZIM;

4 实现仿微信的基本收发消息聊天功能

以下流程中,我们以客户端 A 和 B 的消息交互为例,实现 1v1 通信功能:

4.1 uniapp 即时通讯 实现流程
请参考 跑通示例源码 获取源码。

  1. 导入ZIM 即时通讯 SDK 文件
    请参考 3.2 导入 SDK,导入 SDK 文件。

  2. 创建 ZIM即时通讯 实例
    首先我们需要在项目中创建 ZIM 实例,一个实例对应的是一个用户,表示一个用户以客户端的身份登录系统。

例如,客户端 A、B 分别调用 create(@create) 接口,传入在 2 前提条件 中获取到的 AppID,创建了 A、B 的实例:

var appID = xxxx;
// 静态同步方法,创建 zim 实例,传入 AppID
var zim = ZIM.create(appID);

  1. 监听回调事件
    在客户端登录前,开发者可以通过调用 on 接口,自定义 ZIM 中的事件回调,接收到 SDK 异常、消息通知回调等的通知。

// 注册监听“运行时错误信息”的回调
zim.on('error', function (zim, errorInfo) {
console.log('error', errorInfo.code, errorInfo.message);
});

// 注册监听“网络连接状态变更”的回调
zim.on('connectionStateChanged', function (zim, { state, event, extendedData }) {
console.log('connectionStateChanged', state, event, extendedData);
});

// 注册监听“收到单聊消息”的回调
zim.on('receivePeerMessage', function (zim, { messageList, fromConversationID }) {
console.log('receivePeerMessage', messageList, fromConversationID);
});

// 注册监听“令牌即将过期”的回调
zim.on('tokenWillExpire', function (zim, { second }) {
console.log('tokenWillExpire', second);
// 可以在这里调用 renewToken 接口来更新 token
// 新 token 生成可以参考上文
zim.renewToken(token)
.then(function({ token }){
// 更新成功
})
.catch(function(err){
// 更新失败
})
});

4. 登录 ZIM 即时通讯

创建实例后,客户端 A 和 B 需要登录 ZIM,只有登录成功后才可以开始发送、接收消息、更新 Token 等。

客户端需要使用各自的用户信息和 Token 进行登录。调用 login 接口进行登录,传入用户信息 ZIMUserInfo 对象,以及在 2 前提条件 中获取到的 Token 进行鉴权,鉴权通过后才能登录成功。

  • “userID”、“userName” 支持开发者自定义规则生成。建议开发者将 “userID” 设置为一个有意义的值,可将其与自己的业务账号系统进行关联。
  • 如果 Token 过期,需要在 tokenWillExpire 即将过期回调接口中,调用 renewToken 接口,更新 Token 后才能正常使用 SDK。

// 登录时,需要开发者 按照 "使用 Token 鉴权" 文档生成 token 即可
// userID 和 userName,最大 32 字节的字符串。仅支持数字,英文字符 和 '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '=', '-', '`', ';', '’', ',', '.', '<', '>', '/', '\'。
var userInfo = { userID: 'xxxx', userName: 'xxxx' };
var token = '';

zim.login(userInfo, token)
.then(function () {
// 登录成功
})
.catch(function (err) {
// 登录失败
});

5. 发送消息聊天验证

客户端 A 登录成功后,可以向客户端 B 发送消息。

目前 ZIM 支持的消息类型如下:

以下为发送单聊文本消息为例:客户端 A 可以调用 sendPeerMessage 接口,传入客户端 B 的 userID、消息内容等信息,即可发送一条文本消息到 B 的客户端。

var toUserID = 'xxxx1';
var config = {
priority: 1 // 消息优先级,取值为 低:1 默认,中:2,高:3
};

// 发送单聊 Text 信息
var messageTextObj = { type: 1, message: '文本消息内容' };
zim.sendPeerMessage(messageTextObj, toUserID, config)
.then(function ({ message }) {
// 发送成功
})
.catch(function (err) {
// 发送失败
});

6. 接收消息聊天验证

客户端 B 登录 ZIM 后,将会收到在 on 回调中设置的 receivePeerMessage 监听接口,收到客户端 A 发送过来的消息。

// 注册监听“收到单聊消息”的回调
zim.on('receivePeerMessage', function (zim, { messageList, fromConversationID }) {
console.log('receivePeerMessage', messageList, fromConversationID);
});

7. 退出登录

如果客户端需要退出登录,直接调用 logout 接口即可。

zim.logout();

8. 销毁 ZIM 实例

如果不需要 ZIM 实例,可直接调用 destroy 接口,销毁实例。

zim.destroy();

4.2 API 时序图
通过以上的实现流程描述,API 接口调用时序图如下:

5 uniapp跨平台框架接入ZIM 即时通讯 SDK更多帮助

获取本文即时通讯框架接入IM 即时通讯聊天的开发文档、技术支持,访问即构文档中心IM即时通讯开发文档页 ,可多平台实现聊天社交IM即时通讯功能;

收起阅读 »

uni-app开发小程序:项目架构以及经验分享

支付宝小程序 微信小程序

uni-app开发小程序:项目架构以及经验分享

2022年的时候,公司为了快速完成产品并上线,所以选用微信小程序为载体;由于后期还是打算开发App;虽然公司有iosAndroid,但是如果能一套代码打包多端,一定程度上可以解决成本。前端技术栈也是vue,在考察选择了uni-app。后来多个小程序项目都采用了uni-app开发,积累了一定的经验以及封装了较多业务组件,这里就分享一下uni-app项目的整体架构、方法封装组件库选择以及注意事项。全文代码都会放到github,先赞后看,月入百万!

创建项目

uni-app提供了两种创建项目的方式:

⚠️需要注意的是,一定要根据项目需求来选择项目的创建方式;如果只是单独的开发小程序App,且开发环境单一,可以使用HBuilderX可视化工具创建。如果多端开发,以及同一套代码可能会打包生成多个小程序建议使用vue-cli进行创建,不然后期想搞自动化构建以及按指定条件进行编译比较痛苦。关于按条件编译,文章后面会有详细说明。

使用vue-cli安装和运行:

1.全局安装 vue-cli

npm install -g @vue/cli

2.创建uni-app

vue create -p dcloudio/uni-preset-vue 项目名称

3.进入项目文件夹

cd 项目名称

4.运行项目,如果是已微信小程序为主,可以在package.json中的命令改为:

"scripts": {  
    "serve": "npm run dev:mp-weixin"  
}

然后执行

npm run serve

使用cli创建项目默认不带css预编译,需要手动安装一下,这里已sass为例:

npm i sass --save-dev  
npm i sass-loader --save-dev

整体项目架构

通过HBuilderX或者vue-cli创建的项目,目录结构有稍许不同,但基本没什么差异,这里就按vue-cli创建的项目为例,整体架构配置如下:

    ├──dist 编译后的文件路径  
    ├──package.json 配置项  
    ├──src 核心内容  
        ├──api 项目接口  
        ├──components 全局公共组件  
        ├──config 项目配置文件  
        ├──pages 主包  
        ├──static 全局静态资源  
        ├──store vuex  
        ├──mixins 全局混入  
        ├──utils 公共方法  
        ├──App.vue 应用配置,配置App全局样式以及监听  
        ├──main.js Vue初始化入口文件  
        ├──manifest.json 配置应用名称、appid等打包信息  
        ├──pages.json 配置页面路由、导航条、选项卡等页面类信息  
        └──uni.scss 全局样式

封装方法

工欲善其事,必先利其器。在开发之前,我们可以把一些全局通用的方法进行封装,以及把uni-app提供的api进行二次封装,方便使用。全局的公共方法我们都会放到/src/utils文件夹下。

封装常用方法

下面这些方法都放在/src/utils/utils.js中,文章末尾会提供github链接方便查看。如果项目较大,建议把方法根据功能定义不同的js文件。

小程序Toast提示

/**  
 * 提示方法  
 * @param {String} title 提示文字  
 * @param {String}  icon icon图片  
 * @param {Number}  duration 提示时间  
 */  
export function toast(title, icon = 'none', duration = 1500) {  
    if(title) {  
        uni.showToast({  
            title,  
            icon,  
            duration  
        })  
    }  
}

缓存操作(设置/获取/删除/清空)

/**  
 * 缓存操作  
 * @param {String} val  
 */  
export function setStorageSync(key, data) {  
    uni.setStorageSync(key, data)  
}  

export function getStorageSync(key) {  
    return uni.getStorageSync(key)  
}  

export function removeStorageSync(key) {  
    return uni.removeStorageSync(key)  
}  

export function clearStorageSync() {  
    return uni.clearStorageSync()  
}

页面跳转

/**  
 * 页面跳转  
 * @param {'navigateTo' | 'redirectTo' | 'reLaunch' | 'switchTab' | 'navigateBack' | number } url  转跳路径  
 * @param {String} params 跳转时携带的参数  
 * @param {String} type 转跳方式  
 **/  
export function useRouter(url, params = {}, type = 'navigateTo') {  
    try {  
        if (Object.keys(params).length) url = `${url}?data=${encodeURIComponent(JSON.stringify(params))}`  
        if (type === 'navigateBack') {  
            uni[type]({ delta: url })  
        } else {  
            uni[type]({ url })  
        }  
    } catch (error) {  
        console.error(error)  
    }  
}

图片预览

/**  
 * 预览图片  
 * @param {Array} urls 图片链接  
 */  
export function previewImage(urls, itemList = ['发送给朋友', '保存图片', '收藏']) {  
    uni.previewImage({  
        urls,  
        longPressActions: {  
            itemList,  
            fail: function (error) {  
                console.error(error,'===previewImage')  
            }  
        }  
    })  
}

图片下载

/**  
 * 保存图片到本地  
 * @param {String} filePath 图片临时路径  
 **/  
export function saveImage(filePath) {  
    if (!filePath) return false  
    uni.saveImageToPhotosAlbum({  
        filePath,  
        success: (res) => {  
            toast('图片保存成功', 'success')  
        },  
        fail: (err) => {  
            if (err.errMsg === 'saveImageToPhotosAlbum:fail:auth denied' || err.errMsg === 'saveImageToPhotosAlbum:fail auth deny') {  
                uni.showModal({  
                    title: '提示',  
                    content: '需要您授权保存相册',  
                    showCancel: false,  
                    success: (modalSuccess) => {  
                        uni.openSetting({  
                            success(settingdata) {  
                                if (settingdata.authSetting['scope.writePhotosAlbum']) {  
                                    uni.showModal({  
                                        title: '提示',  
                                        content: '获取权限成功,再次点击图片即可保存',  
                                        showCancel: false  
                                    })  
                                } else {  
                                    uni.showModal({  
                                        title: '提示',  
                                        content: '获取权限失败,将无法保存到相册哦~',  
                                        showCancel: false  
                                    })  
                                }  
                            },  
                            fail(failData) {  
                                console.log('failData', failData)  
                            }  
                        })  
                    }  
                })  
            }  
        }  
    })  
}

更多函数就不在文章中展示了,已经放到/src/utils/utils,js里面,具体可以到github查看。

请求封装

为了减少在页面中的请求代码,所以我们要对uni-app提供的请求方式进行二次封装,在/src/utils文件夹下建立request.js,具体代码如下:


import {toast, clearStorageSync, getStorageSync, useRouter} from './utils'  
import {BASE_URL} from '@/config/index'  

const baseRequest = async (url, method, data, loading = true) =>{  
    header.token = getStorageSync('token') || ''  
    return new Promise((reslove, reject) => {  
        loading && uni.showLoading({title: 'loading'})  
        uni.request({  
            url: BASE_URL + url,  
            method: method || 'GET',  
            header: header,  
            timeout: 10000,  
            data: data || {},  
            success: (successData) => {  
                const res = successData.data  
                uni.hideLoading()  
                if(successData.statusCode == 200){  
                    // 这里根据自己的业务逻辑去调整  
                    if(res.resultCode == 'PA-G998'){  
                        clearStorageSync()  
                        useRouter('/pages/login/index', 'reLaunch')  
                    }else{  
                        reslove(res.data)  
                    }  
                }else{  
                    toast('网络连接失败,请稍后重试')  
                    reject(res)  
                }  
            },  
            fail: (msg) => {  
                uni.hideLoading()  
                toast('网络连接失败,请稍后重试')  
                reject(msg)  
            }  
        })  
    })  
}  

const request = {};  

['options', 'get', 'post', 'put', 'head', 'delete', 'trace', 'connect'].forEach((method) => {  
    request[method] = (api, data, loading) => baseRequest(api, method, data, loading)  
})  

export default request

请求封装好以后,我们在/src/api文件夹下按业务模块建立对应的api文件,拿获取用户信息接口举例子:

/src/api文件夹下建立user.js,然后引入request.js

import request from '@/utils/request'  

//个人信息  
export const info = data => request.post('/v1/api/info', data)

在页面中直接使用:

import {info} from '@/api/user.js'  

export default {  
    methods: {  
        async getUserinfo() {  
            let info = await info()  
            console.log('用户信息==', info)  
        }  
    }  
}

自定义tabBar

uni-app或者小程序基本避不开这个话题了,很多情况下,官方提供的tabBar方案并不能满足产品需求/ui要求,官方也提供了自定义tabBar的方案,但此方案有很多弊端,比如:切换时候会tabBar会有明显的闪动。可以参考之前写的文章小程序自定义TabBar 如何实现“keep-alive”,文章中是原生小程序,但思路在uni-app中同样适用,如果感兴趣,可以评论区提问。

版本切换

很多场景下,需要根据不同的环境去切换不同的请求域名、APPID等字段,这时候就需要通过环境变量来进行区分。下面案例我们就分为三个环境:开发环境(dev)、测试环境(test)、生产环境(prod)。

建立env文件

在项目根目录建立下面三个文件并写入内容(常量名要以VUE开头命名):

.env.dev(开发环境)

VUE_APP_MODE=dev  
VUE_APP_ID=wxbb53ae105735a06b  
VUE_APP_BASE=https://www.baidu.dev.com

.env.test(测试环境)

VUE_APP_MODE=test  
VUE_APP_ID=wxbb53ae105735a06c  
VUE_APP_BASE=https://www.baidu.test.com

.env.prod(生产环境)

VUE_APP_MODE=wxbb53ae105735a06d  
VUE_APP_ID=prod  
VUE_APP_BASE=https://www.baidu.prod.com

修改package.json文件

"scripts": {  
    "dev:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch --mode dev",  
    "build:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch --mode prod"  
},

然后执行

npm run dev:mp-weixin

/src/pages/index/index.vue下,打印:

onLoad() {  
    console.log(process.env.VUE_APP_MODE, '====VUE_APP_BASE')   
    console.log(process.env.VUE_APP_BASE, '====VUE_APP_BASE')  
},

此时输出结果就是

// dev ====VUE_APP_BASE  
// https://www.baidu.dev.com ====VUE_APP_BASE

动态修改appid

如果同一套代码,需要打包生成多个小程序,就需要动态修改appid了;文章开头说过appid在/src/manifest.json文件中配置,但json文件又不能直接写变量,这时候就可以参考官方 提出的解决方案:建立vue.config.js文件,具体操作如下。

在根目录下建立vue.config.js文件写入以下内容:

// 读取 manifest.json ,修改后重新写入  
const fs = require('fs')  

const manifestPath = './src/manifest.json'  
let Manifest = fs.readFileSync(manifestPath, { encoding: 'utf-8' })  
function replaceManifest(path, value) {  
    const arr = path.split('.')  
    const len = arr.length  
    const lastItem = arr[len - 1]  
    let i = 0  
    let ManifestArr = Manifest.split(/\n/)  
    for (let index = 0; index < ManifestArr.length; index++) {  
        const item = ManifestArr[index]  
        if (new RegExp(`"${arr[i]}"`).test(item)) ++i  
        if (i === len) {  
            const hasComma = /,/.test(item)  
            ManifestArr[index] = item.replace(  
            new RegExp(`"${lastItem}"[\\s\\S]*:[\\s\\S]*`),  
            `"${lastItem}": ${value}${hasComma ? ',' : ''}`  
            )  
            break  
        }  
    }  

Manifest = ManifestArr.join('\n')  
}  
// 读取环境变量内容  
replaceManifest('mp-weixin.appid', `"${process.env.VUE_APP_ID}"`)  

fs.writeFileSync(manifestPath, Manifest, {  
  flag: 'w'  
})

如果是通过HBuilderX可视化工具创建的项目,则无法去自动根据环境去修改appid,只能去手动修改。

组件库

uni-app最受欢迎的可能就是插件市场了,插件市场提供了很多优秀的插件/组件库供我们选择,比较火的就是自家的uni-ui以及uView UI,大部分组件还是比较好用的,如果做中大型项目以及UI要求较高的情况下,还是比较推荐自己搭一套组件库,方便扩展以及维护。

结尾

关于uni-app项目的起步工作就到这里了,后面有机会写一套完整的uni搭建电商小程序项目,记得关注。代码已经提交到github,如果对你有帮助,记得点个star!

继续阅读 »

uni-app开发小程序:项目架构以及经验分享

2022年的时候,公司为了快速完成产品并上线,所以选用微信小程序为载体;由于后期还是打算开发App;虽然公司有iosAndroid,但是如果能一套代码打包多端,一定程度上可以解决成本。前端技术栈也是vue,在考察选择了uni-app。后来多个小程序项目都采用了uni-app开发,积累了一定的经验以及封装了较多业务组件,这里就分享一下uni-app项目的整体架构、方法封装组件库选择以及注意事项。全文代码都会放到github,先赞后看,月入百万!

创建项目

uni-app提供了两种创建项目的方式:

⚠️需要注意的是,一定要根据项目需求来选择项目的创建方式;如果只是单独的开发小程序App,且开发环境单一,可以使用HBuilderX可视化工具创建。如果多端开发,以及同一套代码可能会打包生成多个小程序建议使用vue-cli进行创建,不然后期想搞自动化构建以及按指定条件进行编译比较痛苦。关于按条件编译,文章后面会有详细说明。

使用vue-cli安装和运行:

1.全局安装 vue-cli

npm install -g @vue/cli

2.创建uni-app

vue create -p dcloudio/uni-preset-vue 项目名称

3.进入项目文件夹

cd 项目名称

4.运行项目,如果是已微信小程序为主,可以在package.json中的命令改为:

"scripts": {  
    "serve": "npm run dev:mp-weixin"  
}

然后执行

npm run serve

使用cli创建项目默认不带css预编译,需要手动安装一下,这里已sass为例:

npm i sass --save-dev  
npm i sass-loader --save-dev

整体项目架构

通过HBuilderX或者vue-cli创建的项目,目录结构有稍许不同,但基本没什么差异,这里就按vue-cli创建的项目为例,整体架构配置如下:

    ├──dist 编译后的文件路径  
    ├──package.json 配置项  
    ├──src 核心内容  
        ├──api 项目接口  
        ├──components 全局公共组件  
        ├──config 项目配置文件  
        ├──pages 主包  
        ├──static 全局静态资源  
        ├──store vuex  
        ├──mixins 全局混入  
        ├──utils 公共方法  
        ├──App.vue 应用配置,配置App全局样式以及监听  
        ├──main.js Vue初始化入口文件  
        ├──manifest.json 配置应用名称、appid等打包信息  
        ├──pages.json 配置页面路由、导航条、选项卡等页面类信息  
        └──uni.scss 全局样式

封装方法

工欲善其事,必先利其器。在开发之前,我们可以把一些全局通用的方法进行封装,以及把uni-app提供的api进行二次封装,方便使用。全局的公共方法我们都会放到/src/utils文件夹下。

封装常用方法

下面这些方法都放在/src/utils/utils.js中,文章末尾会提供github链接方便查看。如果项目较大,建议把方法根据功能定义不同的js文件。

小程序Toast提示

/**  
 * 提示方法  
 * @param {String} title 提示文字  
 * @param {String}  icon icon图片  
 * @param {Number}  duration 提示时间  
 */  
export function toast(title, icon = 'none', duration = 1500) {  
    if(title) {  
        uni.showToast({  
            title,  
            icon,  
            duration  
        })  
    }  
}

缓存操作(设置/获取/删除/清空)

/**  
 * 缓存操作  
 * @param {String} val  
 */  
export function setStorageSync(key, data) {  
    uni.setStorageSync(key, data)  
}  

export function getStorageSync(key) {  
    return uni.getStorageSync(key)  
}  

export function removeStorageSync(key) {  
    return uni.removeStorageSync(key)  
}  

export function clearStorageSync() {  
    return uni.clearStorageSync()  
}

页面跳转

/**  
 * 页面跳转  
 * @param {'navigateTo' | 'redirectTo' | 'reLaunch' | 'switchTab' | 'navigateBack' | number } url  转跳路径  
 * @param {String} params 跳转时携带的参数  
 * @param {String} type 转跳方式  
 **/  
export function useRouter(url, params = {}, type = 'navigateTo') {  
    try {  
        if (Object.keys(params).length) url = `${url}?data=${encodeURIComponent(JSON.stringify(params))}`  
        if (type === 'navigateBack') {  
            uni[type]({ delta: url })  
        } else {  
            uni[type]({ url })  
        }  
    } catch (error) {  
        console.error(error)  
    }  
}

图片预览

/**  
 * 预览图片  
 * @param {Array} urls 图片链接  
 */  
export function previewImage(urls, itemList = ['发送给朋友', '保存图片', '收藏']) {  
    uni.previewImage({  
        urls,  
        longPressActions: {  
            itemList,  
            fail: function (error) {  
                console.error(error,'===previewImage')  
            }  
        }  
    })  
}

图片下载

/**  
 * 保存图片到本地  
 * @param {String} filePath 图片临时路径  
 **/  
export function saveImage(filePath) {  
    if (!filePath) return false  
    uni.saveImageToPhotosAlbum({  
        filePath,  
        success: (res) => {  
            toast('图片保存成功', 'success')  
        },  
        fail: (err) => {  
            if (err.errMsg === 'saveImageToPhotosAlbum:fail:auth denied' || err.errMsg === 'saveImageToPhotosAlbum:fail auth deny') {  
                uni.showModal({  
                    title: '提示',  
                    content: '需要您授权保存相册',  
                    showCancel: false,  
                    success: (modalSuccess) => {  
                        uni.openSetting({  
                            success(settingdata) {  
                                if (settingdata.authSetting['scope.writePhotosAlbum']) {  
                                    uni.showModal({  
                                        title: '提示',  
                                        content: '获取权限成功,再次点击图片即可保存',  
                                        showCancel: false  
                                    })  
                                } else {  
                                    uni.showModal({  
                                        title: '提示',  
                                        content: '获取权限失败,将无法保存到相册哦~',  
                                        showCancel: false  
                                    })  
                                }  
                            },  
                            fail(failData) {  
                                console.log('failData', failData)  
                            }  
                        })  
                    }  
                })  
            }  
        }  
    })  
}

更多函数就不在文章中展示了,已经放到/src/utils/utils,js里面,具体可以到github查看。

请求封装

为了减少在页面中的请求代码,所以我们要对uni-app提供的请求方式进行二次封装,在/src/utils文件夹下建立request.js,具体代码如下:


import {toast, clearStorageSync, getStorageSync, useRouter} from './utils'  
import {BASE_URL} from '@/config/index'  

const baseRequest = async (url, method, data, loading = true) =>{  
    header.token = getStorageSync('token') || ''  
    return new Promise((reslove, reject) => {  
        loading && uni.showLoading({title: 'loading'})  
        uni.request({  
            url: BASE_URL + url,  
            method: method || 'GET',  
            header: header,  
            timeout: 10000,  
            data: data || {},  
            success: (successData) => {  
                const res = successData.data  
                uni.hideLoading()  
                if(successData.statusCode == 200){  
                    // 这里根据自己的业务逻辑去调整  
                    if(res.resultCode == 'PA-G998'){  
                        clearStorageSync()  
                        useRouter('/pages/login/index', 'reLaunch')  
                    }else{  
                        reslove(res.data)  
                    }  
                }else{  
                    toast('网络连接失败,请稍后重试')  
                    reject(res)  
                }  
            },  
            fail: (msg) => {  
                uni.hideLoading()  
                toast('网络连接失败,请稍后重试')  
                reject(msg)  
            }  
        })  
    })  
}  

const request = {};  

['options', 'get', 'post', 'put', 'head', 'delete', 'trace', 'connect'].forEach((method) => {  
    request[method] = (api, data, loading) => baseRequest(api, method, data, loading)  
})  

export default request

请求封装好以后,我们在/src/api文件夹下按业务模块建立对应的api文件,拿获取用户信息接口举例子:

/src/api文件夹下建立user.js,然后引入request.js

import request from '@/utils/request'  

//个人信息  
export const info = data => request.post('/v1/api/info', data)

在页面中直接使用:

import {info} from '@/api/user.js'  

export default {  
    methods: {  
        async getUserinfo() {  
            let info = await info()  
            console.log('用户信息==', info)  
        }  
    }  
}

自定义tabBar

uni-app或者小程序基本避不开这个话题了,很多情况下,官方提供的tabBar方案并不能满足产品需求/ui要求,官方也提供了自定义tabBar的方案,但此方案有很多弊端,比如:切换时候会tabBar会有明显的闪动。可以参考之前写的文章小程序自定义TabBar 如何实现“keep-alive”,文章中是原生小程序,但思路在uni-app中同样适用,如果感兴趣,可以评论区提问。

版本切换

很多场景下,需要根据不同的环境去切换不同的请求域名、APPID等字段,这时候就需要通过环境变量来进行区分。下面案例我们就分为三个环境:开发环境(dev)、测试环境(test)、生产环境(prod)。

建立env文件

在项目根目录建立下面三个文件并写入内容(常量名要以VUE开头命名):

.env.dev(开发环境)

VUE_APP_MODE=dev  
VUE_APP_ID=wxbb53ae105735a06b  
VUE_APP_BASE=https://www.baidu.dev.com

.env.test(测试环境)

VUE_APP_MODE=test  
VUE_APP_ID=wxbb53ae105735a06c  
VUE_APP_BASE=https://www.baidu.test.com

.env.prod(生产环境)

VUE_APP_MODE=wxbb53ae105735a06d  
VUE_APP_ID=prod  
VUE_APP_BASE=https://www.baidu.prod.com

修改package.json文件

"scripts": {  
    "dev:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch --mode dev",  
    "build:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch --mode prod"  
},

然后执行

npm run dev:mp-weixin

/src/pages/index/index.vue下,打印:

onLoad() {  
    console.log(process.env.VUE_APP_MODE, '====VUE_APP_BASE')   
    console.log(process.env.VUE_APP_BASE, '====VUE_APP_BASE')  
},

此时输出结果就是

// dev ====VUE_APP_BASE  
// https://www.baidu.dev.com ====VUE_APP_BASE

动态修改appid

如果同一套代码,需要打包生成多个小程序,就需要动态修改appid了;文章开头说过appid在/src/manifest.json文件中配置,但json文件又不能直接写变量,这时候就可以参考官方 提出的解决方案:建立vue.config.js文件,具体操作如下。

在根目录下建立vue.config.js文件写入以下内容:

// 读取 manifest.json ,修改后重新写入  
const fs = require('fs')  

const manifestPath = './src/manifest.json'  
let Manifest = fs.readFileSync(manifestPath, { encoding: 'utf-8' })  
function replaceManifest(path, value) {  
    const arr = path.split('.')  
    const len = arr.length  
    const lastItem = arr[len - 1]  
    let i = 0  
    let ManifestArr = Manifest.split(/\n/)  
    for (let index = 0; index < ManifestArr.length; index++) {  
        const item = ManifestArr[index]  
        if (new RegExp(`"${arr[i]}"`).test(item)) ++i  
        if (i === len) {  
            const hasComma = /,/.test(item)  
            ManifestArr[index] = item.replace(  
            new RegExp(`"${lastItem}"[\\s\\S]*:[\\s\\S]*`),  
            `"${lastItem}": ${value}${hasComma ? ',' : ''}`  
            )  
            break  
        }  
    }  

Manifest = ManifestArr.join('\n')  
}  
// 读取环境变量内容  
replaceManifest('mp-weixin.appid', `"${process.env.VUE_APP_ID}"`)  

fs.writeFileSync(manifestPath, Manifest, {  
  flag: 'w'  
})

如果是通过HBuilderX可视化工具创建的项目,则无法去自动根据环境去修改appid,只能去手动修改。

组件库

uni-app最受欢迎的可能就是插件市场了,插件市场提供了很多优秀的插件/组件库供我们选择,比较火的就是自家的uni-ui以及uView UI,大部分组件还是比较好用的,如果做中大型项目以及UI要求较高的情况下,还是比较推荐自己搭一套组件库,方便扩展以及维护。

结尾

关于uni-app项目的起步工作就到这里了,后面有机会写一套完整的uni搭建电商小程序项目,记得关注。代码已经提交到github,如果对你有帮助,记得点个star!

收起阅读 »

如何在uni-app 平台快速实现一对一音视频通话应用

uniapp 教程

“一套代码,多端运行”是很多开发团队的梦想。ZEGO SDK基于uni-app跨平台框架支持iOS、Android、Windows、macOS、HarmonyOS、Web、小程序并支持平台间互通,快速实现搭建多端音视频通话,大大降低开发和学习成本。

本文将引导快速创建和运行第一个uni-app RTC项目,适合想要快速完成多端共用音视频项目的开发者;

1 准备环境

在开始集成 ZEGO Express SDK 前,请确保开发环境满足以下要求:
HBuilderX 3.0.0 或以上版本。
准备 iOS / Android 设备,版本要求如下:
iOS 9.0 或以上版本且支持音视频的 iOS 设备。
Android 4.4 或以上版本且支持音视频的 Android 设备。
iOS / Android 设备已经连接到 Internet。

2 项目准备

2.1 创建项目

进入即构官网,在控制台创建项目,并申请有效的 AppID,这一步很关键,appid为应用的唯一标识,如身份证号,是应用的身份证明,用于明确你的项目及组织身份。zego提供的服务也是基于APP ID;

App ID的获取方式很简单,只需3~5分钟,在即构官网-我的项目-创建即可。创建的项目信息可用于SDK的集成和配置;

2.2 Token 鉴权

登录房间时必须 使用 Token 鉴权 ,可参考 Token 鉴权教程
为了方便开发阶段的调试,开发者可直接在 ZEGO 控制台获取临时 Token(有效期为 24 小时) 来使用,详情请参考 控制台(新版) - 项目管理 中的 “项目信息”。

3 集成

3.1 项目设置

开始集成前,可参考如下步骤设置你的项目;
如已有项目,本步骤可忽略。
如需新建项目,可按照以下步骤创建你的新项目:

1.启动 HBuilderX,选择“文件 > 新建 > 项目”菜单。

2.在出现的表单中,选择 “uni-app” 平台,并填写项目名称。

3.单击“创建”,即可创建项目。

3.2 导入 SDK

在 ZEGO 官网下载 SDK

1.下载 Express-Video SDK 到本地,解压缩 “zego-ZegoExpressUniAppSDK” 文件。

2.将解压缩后的文件夹直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。

3.3 在 uni-app 项目中导入插件

1.单击项目目录的 “manifest.json” 文件后,单击“App原生插件配置”中的“选择本地插件”或“选择云端插件”。

2.在弹出的选择框中,选择 “ZegoExpress 音视频 SDK” 后,单击“确认”,即添加成功。

3.4 自定义调试基座

3.4.1 制作自定义调试基座
1.选择“运行 > 运行到手机或模拟器 > 制作自定义调试基座”菜单。

2.在弹出的界面中,按照 uni-app 教程,填写相关信息,并单击“打包”进行云打包。

打包成功后,控制台会收到 uni-app 的相关提示。

3.4.2 切换运行基座为自定义调试基座
自定义调试基座选择“运行 > 运行到手机或模拟器 > 运行基座选择 > 自定义调试基座”菜单。

4 集成 JS 封装层

1.导入 JS 封装层。

在插件市场的 ZegoExpressEngine 音视频插件(JS) 界面单击右侧的“使用 HBuilderX导入插件”。

导入的 JS 封装层将存储在 “components” 目录中。

2.导入后可以在业务代码中引入 JS 封装层,并调用 Express 相关接口,示例如下:

import ZegoExpressEngine from '@/components/zego-ZegoExpressUniApp-JS/lib/ZegoExpressEngine';

5 实现流程

用户通过 ZEGO Express SDK 进行视频通话的基本流程为:
用户 A、B 加入房间,用户 B 预览并将音视频流推送到 ZEGO 云服务(推流),用户 A 收到用户 B 推送音视频流的通知之后,在通知中播放用户 B 的音视频流(拉流)。

整个音视频通话推拉流过程的 API 调用时序如下图:

5.1 创建引擎

1. 创建界面

在创建引擎之前,ZEGO 推荐开发者添加以下界面元素,方便实现基本的实时音视频功能。

本地预览窗口
远端视频窗口
结束按钮

2. 创建引擎

调用 createEngineWithProfile 接口,将申请到的 AppID 传入参数 “appID”,创建引擎单例对象。
如果需要注册回调方法,开发者可根据实际需要,实现 ZegoEventListener 中的某些方法,创建引擎后可通过调用 on 接口设置回调。
// 导入
import ZegoExpressEngine from '@/components/zego-ZegoExpressUniApp-JS/lib/ZegoExpressEngine';

// 采用通用场景
const profile = {
appID : xxx,
scenario : 0
};

ZegoExpressEngine.createEngineWithProfile(profile)

5.2 登录房间

1. 登录

你可以调用 loginRoom 接口登录房间。roomID 和 user 的参数由您本地生成,但是需要满足以下条件:

同一个 AppID 内,需保证 “roomID” 全局唯一。
同一个 AppID 内,需保证 “userID” 全局唯一,建议开发者将 “userID” 与自己业务的账号系统进行关联。

let roomConfig = {};
//token 由用户自己的服务端生成,为了更快跑通流程,也可以通过即构控制台获取临时的音视频 token
roomConfig.token = "xxxx";
// 只有传入 “isUserStatusNotify” 参数取值为 “true” 的 ZegoRoomConfig,才能收到 onRoomUserUpdate 回调。
roomConfig.isUserStatusNotify = true;
// 登录房间
// 开始登录房间
ZegoExpressEngine.instance().loginRoom('room1', {'userID': 'id1', 'userName': 'user1'}, roomConfig);

2. 监听登录房间后的事件回调

可根据实际应用需要,在登录房间后监听想要关注的事件通知,比如房间状态更新、用户状态更新、流状态更新等。

roomStateUpdate:房间状态更新回调,登录房间后,当房间连接状态发生变更(如出现房间断开,登录认证失败等情况),SDK 会通过该回调通知。
roomUserUpdate:用户状态更新回调,登录房间后,当房间内有用户新增或删除时,SDK 会通过该回调通知。
只有调用 loginRoom 接口登录房间时传入 ZegoRoomConfig 配置,且 “isUserStatusNotify”参数取值为 “true” 时,用户才能收到 roomUserUpdate 回调。
roomStreamUpdate:流状态更新回调,登录房间后,当房间内有用户新推送或删除音视频流时,SDK 会通过该回调通知。

// 以下为常用的房间相关回调

ZegoExpressEngine.instance().on('roomStateUpdate', (roomID, state, errorCode, extendedData) => {
// 房间状态更新回调,登录房间后,当房间连接状态发生变更(如出现房间断开,登录认证失败等情况),SDK会通过该回调通知
}); ;

ZegoExpressEngine.instance().on('roomUserUpdate', (roomID, updateType, userList) => {
// 用户状态更新,登录房间后,当房间内有用户新增或删除时,SDK会通过该回调通知
});

ZegoExpressEngine.instance().on('roomStreamUpdate', (roomID, updateType, streamList) => {
// 流状态更新,登录房间后,当房间内有用户新推送或删除音视频流时,SDK会通过该回调通知
});

5.3 推流

1. 开始推流

调用 startPublishingStream 接口,传入流 ID 参数 “streamID”,向远端用户发送本端的音视频流。

/* 开始推流 /
ZegoExpressEngine.instance().startPublishingStream("streamID");

2. 启动本地预览

如果希望看到本端画面,可使用 标签设置预览视图,并调用 startPreview 接口启动本地预览。

<template>
<zego-local-view style="height: 403.84rpx;flex: 1;"></zego-local-view>
</template>

js部分:
/* 开始预览 /
ZegoExpressEngine.instance().startPreview()

3. 监听推流后的事件回调

根据实际应用需要,在推流后监听想要关注的事件通知,比如推流状态更新等。
publisherStateUpdate:推流状态更新回调,调用推流接口成功后,当推流状态发生变更,如出现网络中断导致推流异常等情况,SDK 在重试推流的同时,会通过该回调通知。

ZegoExpressEngine.instance().on("publisherStateUpdate", (streamID, state, errorCode, extendedData) => {
// 调用推流接口成功后,当推流器状态发生变更,如出现网络中断导致推流异常等情况,SDK在重试推流的同时,会通过该回调通知
//....
});

5.4 拉流

1. 开始拉流

使用 标签设置远端视频流视图,调用 startPlayingStream 接口,根据传入的流 ID 参数 “streamID”,拉取远端推送的音视频流。
<template>
<zego-remote-view :streamID="playStreamID" style="height: 403.84rpx;flex: 1"></zego-remote-view>
</template>

js部分:
/* 开始拉流 /
this.playStreamID = "StreamID_1"
ZegoExpressEngine.instance().startPlayingStream(this.playStreamID)

  1. 监听拉流后的事件回调
    根据实际应用需要,在拉流后监听想要关注的事件通知,比如拉流状态更新等。
    playerStateUpdate:拉流状态更新回调,调用拉流接口成功后,当拉流状态发生变更,如出现网络中断导致推流异常等情况,SDK 在重试拉流的同时,会通过该回调通知。

ZegoExpressEngine.instance().on("playerStateUpdate", (streamID, state, errorCode, extendedData) => {
/* 调用拉流接口成功后,当拉流器状态发生变更,如出现网络中断导致推流异常等情况,SDK在重试拉流的同时,会通过该回调通知 /
//....
});

5.5 体验实时音视频功能

在真机中运行项目,运行成功后,可以看到本端视频画面。
为方便体验,ZEGO 提供了一个 Web 端调试示例,在该页面下,输入相同的 AppID、RoomID、Server 地址和 Token,即可加入同一房间与真机设备互通。当成功开始音视频通话时,可以听到远端的音频,看到远端的视频画面。

5.6 停止推拉流

1. 停止推流/预览

调用 stopPublishingStream 接口停止发送本地的音视频流,结束通话。

/* 停止推流 /
ZegoExpressEngine.instance().stopPublishingStream();

如果启用了本地预览,开发者可以在停止推流后根据业务需要调用 stopPreview 接口停止预览。
/* 停止本地预览 /
ZegoExpressEngine.instance().stopPreview();

2. 停止拉流

调用 stopPlayingStream 接口,停止拉取远端的音视频流。

/* 停止拉流 /
ZegoExpressEngine.instance().stopPlayingStream("streamID");

  1. 退出房间
    调用 logoutRoom 接口退出房间,本端会收到 roomStateUpdate 回调通知调用结果,并停止其所有推拉流以及本地预览。
    /* 退出房间 /
    ZegoExpressEngine.instance().logoutRoom('room1');

5.7 销毁引擎
调用 destroyEngine 接口销毁引擎,用于释放 SDK 使用的资源。
/* 销毁引擎 /
ZegoExpressEngine.destroyEngine();

结语:

你已经完成了第一个uni-app RTC项目了,可以与小伙伴多端进行音视频通话测试。
即构助力企业一周快速搭建音视频通话、秀场直播、语聊房、K歌房等场景,提供多种解决方案,同时即构每月赠送10000分钟免费时长。

获取更多文档、Demo、技术帮助

获取本文的Demo、开发文档、技术支持,访问即构文档中心

继续阅读 »

“一套代码,多端运行”是很多开发团队的梦想。ZEGO SDK基于uni-app跨平台框架支持iOS、Android、Windows、macOS、HarmonyOS、Web、小程序并支持平台间互通,快速实现搭建多端音视频通话,大大降低开发和学习成本。

本文将引导快速创建和运行第一个uni-app RTC项目,适合想要快速完成多端共用音视频项目的开发者;

1 准备环境

在开始集成 ZEGO Express SDK 前,请确保开发环境满足以下要求:
HBuilderX 3.0.0 或以上版本。
准备 iOS / Android 设备,版本要求如下:
iOS 9.0 或以上版本且支持音视频的 iOS 设备。
Android 4.4 或以上版本且支持音视频的 Android 设备。
iOS / Android 设备已经连接到 Internet。

2 项目准备

2.1 创建项目

进入即构官网,在控制台创建项目,并申请有效的 AppID,这一步很关键,appid为应用的唯一标识,如身份证号,是应用的身份证明,用于明确你的项目及组织身份。zego提供的服务也是基于APP ID;

App ID的获取方式很简单,只需3~5分钟,在即构官网-我的项目-创建即可。创建的项目信息可用于SDK的集成和配置;

2.2 Token 鉴权

登录房间时必须 使用 Token 鉴权 ,可参考 Token 鉴权教程
为了方便开发阶段的调试,开发者可直接在 ZEGO 控制台获取临时 Token(有效期为 24 小时) 来使用,详情请参考 控制台(新版) - 项目管理 中的 “项目信息”。

3 集成

3.1 项目设置

开始集成前,可参考如下步骤设置你的项目;
如已有项目,本步骤可忽略。
如需新建项目,可按照以下步骤创建你的新项目:

1.启动 HBuilderX,选择“文件 > 新建 > 项目”菜单。

2.在出现的表单中,选择 “uni-app” 平台,并填写项目名称。

3.单击“创建”,即可创建项目。

3.2 导入 SDK

在 ZEGO 官网下载 SDK

1.下载 Express-Video SDK 到本地,解压缩 “zego-ZegoExpressUniAppSDK” 文件。

2.将解压缩后的文件夹直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。

3.3 在 uni-app 项目中导入插件

1.单击项目目录的 “manifest.json” 文件后,单击“App原生插件配置”中的“选择本地插件”或“选择云端插件”。

2.在弹出的选择框中,选择 “ZegoExpress 音视频 SDK” 后,单击“确认”,即添加成功。

3.4 自定义调试基座

3.4.1 制作自定义调试基座
1.选择“运行 > 运行到手机或模拟器 > 制作自定义调试基座”菜单。

2.在弹出的界面中,按照 uni-app 教程,填写相关信息,并单击“打包”进行云打包。

打包成功后,控制台会收到 uni-app 的相关提示。

3.4.2 切换运行基座为自定义调试基座
自定义调试基座选择“运行 > 运行到手机或模拟器 > 运行基座选择 > 自定义调试基座”菜单。

4 集成 JS 封装层

1.导入 JS 封装层。

在插件市场的 ZegoExpressEngine 音视频插件(JS) 界面单击右侧的“使用 HBuilderX导入插件”。

导入的 JS 封装层将存储在 “components” 目录中。

2.导入后可以在业务代码中引入 JS 封装层,并调用 Express 相关接口,示例如下:

import ZegoExpressEngine from '@/components/zego-ZegoExpressUniApp-JS/lib/ZegoExpressEngine';

5 实现流程

用户通过 ZEGO Express SDK 进行视频通话的基本流程为:
用户 A、B 加入房间,用户 B 预览并将音视频流推送到 ZEGO 云服务(推流),用户 A 收到用户 B 推送音视频流的通知之后,在通知中播放用户 B 的音视频流(拉流)。

整个音视频通话推拉流过程的 API 调用时序如下图:

5.1 创建引擎

1. 创建界面

在创建引擎之前,ZEGO 推荐开发者添加以下界面元素,方便实现基本的实时音视频功能。

本地预览窗口
远端视频窗口
结束按钮

2. 创建引擎

调用 createEngineWithProfile 接口,将申请到的 AppID 传入参数 “appID”,创建引擎单例对象。
如果需要注册回调方法,开发者可根据实际需要,实现 ZegoEventListener 中的某些方法,创建引擎后可通过调用 on 接口设置回调。
// 导入
import ZegoExpressEngine from '@/components/zego-ZegoExpressUniApp-JS/lib/ZegoExpressEngine';

// 采用通用场景
const profile = {
appID : xxx,
scenario : 0
};

ZegoExpressEngine.createEngineWithProfile(profile)

5.2 登录房间

1. 登录

你可以调用 loginRoom 接口登录房间。roomID 和 user 的参数由您本地生成,但是需要满足以下条件:

同一个 AppID 内,需保证 “roomID” 全局唯一。
同一个 AppID 内,需保证 “userID” 全局唯一,建议开发者将 “userID” 与自己业务的账号系统进行关联。

let roomConfig = {};
//token 由用户自己的服务端生成,为了更快跑通流程,也可以通过即构控制台获取临时的音视频 token
roomConfig.token = "xxxx";
// 只有传入 “isUserStatusNotify” 参数取值为 “true” 的 ZegoRoomConfig,才能收到 onRoomUserUpdate 回调。
roomConfig.isUserStatusNotify = true;
// 登录房间
// 开始登录房间
ZegoExpressEngine.instance().loginRoom('room1', {'userID': 'id1', 'userName': 'user1'}, roomConfig);

2. 监听登录房间后的事件回调

可根据实际应用需要,在登录房间后监听想要关注的事件通知,比如房间状态更新、用户状态更新、流状态更新等。

roomStateUpdate:房间状态更新回调,登录房间后,当房间连接状态发生变更(如出现房间断开,登录认证失败等情况),SDK 会通过该回调通知。
roomUserUpdate:用户状态更新回调,登录房间后,当房间内有用户新增或删除时,SDK 会通过该回调通知。
只有调用 loginRoom 接口登录房间时传入 ZegoRoomConfig 配置,且 “isUserStatusNotify”参数取值为 “true” 时,用户才能收到 roomUserUpdate 回调。
roomStreamUpdate:流状态更新回调,登录房间后,当房间内有用户新推送或删除音视频流时,SDK 会通过该回调通知。

// 以下为常用的房间相关回调

ZegoExpressEngine.instance().on('roomStateUpdate', (roomID, state, errorCode, extendedData) => {
// 房间状态更新回调,登录房间后,当房间连接状态发生变更(如出现房间断开,登录认证失败等情况),SDK会通过该回调通知
}); ;

ZegoExpressEngine.instance().on('roomUserUpdate', (roomID, updateType, userList) => {
// 用户状态更新,登录房间后,当房间内有用户新增或删除时,SDK会通过该回调通知
});

ZegoExpressEngine.instance().on('roomStreamUpdate', (roomID, updateType, streamList) => {
// 流状态更新,登录房间后,当房间内有用户新推送或删除音视频流时,SDK会通过该回调通知
});

5.3 推流

1. 开始推流

调用 startPublishingStream 接口,传入流 ID 参数 “streamID”,向远端用户发送本端的音视频流。

/* 开始推流 /
ZegoExpressEngine.instance().startPublishingStream("streamID");

2. 启动本地预览

如果希望看到本端画面,可使用 标签设置预览视图,并调用 startPreview 接口启动本地预览。

<template>
<zego-local-view style="height: 403.84rpx;flex: 1;"></zego-local-view>
</template>

js部分:
/* 开始预览 /
ZegoExpressEngine.instance().startPreview()

3. 监听推流后的事件回调

根据实际应用需要,在推流后监听想要关注的事件通知,比如推流状态更新等。
publisherStateUpdate:推流状态更新回调,调用推流接口成功后,当推流状态发生变更,如出现网络中断导致推流异常等情况,SDK 在重试推流的同时,会通过该回调通知。

ZegoExpressEngine.instance().on("publisherStateUpdate", (streamID, state, errorCode, extendedData) => {
// 调用推流接口成功后,当推流器状态发生变更,如出现网络中断导致推流异常等情况,SDK在重试推流的同时,会通过该回调通知
//....
});

5.4 拉流

1. 开始拉流

使用 标签设置远端视频流视图,调用 startPlayingStream 接口,根据传入的流 ID 参数 “streamID”,拉取远端推送的音视频流。
<template>
<zego-remote-view :streamID="playStreamID" style="height: 403.84rpx;flex: 1"></zego-remote-view>
</template>

js部分:
/* 开始拉流 /
this.playStreamID = "StreamID_1"
ZegoExpressEngine.instance().startPlayingStream(this.playStreamID)

  1. 监听拉流后的事件回调
    根据实际应用需要,在拉流后监听想要关注的事件通知,比如拉流状态更新等。
    playerStateUpdate:拉流状态更新回调,调用拉流接口成功后,当拉流状态发生变更,如出现网络中断导致推流异常等情况,SDK 在重试拉流的同时,会通过该回调通知。

ZegoExpressEngine.instance().on("playerStateUpdate", (streamID, state, errorCode, extendedData) => {
/* 调用拉流接口成功后,当拉流器状态发生变更,如出现网络中断导致推流异常等情况,SDK在重试拉流的同时,会通过该回调通知 /
//....
});

5.5 体验实时音视频功能

在真机中运行项目,运行成功后,可以看到本端视频画面。
为方便体验,ZEGO 提供了一个 Web 端调试示例,在该页面下,输入相同的 AppID、RoomID、Server 地址和 Token,即可加入同一房间与真机设备互通。当成功开始音视频通话时,可以听到远端的音频,看到远端的视频画面。

5.6 停止推拉流

1. 停止推流/预览

调用 stopPublishingStream 接口停止发送本地的音视频流,结束通话。

/* 停止推流 /
ZegoExpressEngine.instance().stopPublishingStream();

如果启用了本地预览,开发者可以在停止推流后根据业务需要调用 stopPreview 接口停止预览。
/* 停止本地预览 /
ZegoExpressEngine.instance().stopPreview();

2. 停止拉流

调用 stopPlayingStream 接口,停止拉取远端的音视频流。

/* 停止拉流 /
ZegoExpressEngine.instance().stopPlayingStream("streamID");

  1. 退出房间
    调用 logoutRoom 接口退出房间,本端会收到 roomStateUpdate 回调通知调用结果,并停止其所有推拉流以及本地预览。
    /* 退出房间 /
    ZegoExpressEngine.instance().logoutRoom('room1');

5.7 销毁引擎
调用 destroyEngine 接口销毁引擎,用于释放 SDK 使用的资源。
/* 销毁引擎 /
ZegoExpressEngine.destroyEngine();

结语:

你已经完成了第一个uni-app RTC项目了,可以与小伙伴多端进行音视频通话测试。
即构助力企业一周快速搭建音视频通话、秀场直播、语聊房、K歌房等场景,提供多种解决方案,同时即构每月赠送10000分钟免费时长。

获取更多文档、Demo、技术帮助

获取本文的Demo、开发文档、技术支持,访问即构文档中心

收起阅读 »

hbuilder 太耗内存了

内存 HBuilder

我目前hbuilder中只有一个项目,运行一段时间时间之后,太耗内存

我目前hbuilder中只有一个项目,运行一段时间时间之后,太耗内存

高效实现uni-app页面路由模块化及路径个性化

路由 路由守卫 路由拦截 动态路由 uniapp

高效实现uni-app页面路由模块化及路径个性化

在传统的uni-app开发中,我们被限制在手动维护pages.json文件中,以定义页面和页面样式特性。然而,随着应用程序复杂度的增加,这种方式会显著增加开发者的心智负担。面对庞大的项目和频繁的页面更新,pages.json的管理变得非常繁琐和不便。

然而,幸运的是,采用uni-simple-router后,这些问题迎刃而解。uni-simple-router为我们提供了一种更灵活、更高效的方式来处理页面路由。不再依赖于pages.json,我们可以动态地创建页面路由,甚至允许构建复杂的页面结构,如动态路由和嵌套路由等。

创建一个传统页面

传统方案

// pages.json  
{  
    "pages": [   
        {  
            "path": "pages/index/index"  
        }  
    ],  
  "globalStyle": {  
        "navigationBarTextStyle": "black",  
        "navigationBarTitleText": "uni-app",  
        "navigationBarBackgroundColor": "#F8F8F8",  
        "backgroundColor": "#F8F8F8"  
    }  
}

使用 uni-simple-router

// router.js  
const router = createRouter({  
  routes:[{  
      path:`/`,  
      component:__dynamicImportComponent__(`@/pages/index/index.vue`,{  
          pageType:`top`    
      })  
  }]  
})
// pages.json  
{  
  "globalStyle": {  
        "navigationBarTextStyle": "black",  
        "navigationBarTitleText": "uni-app",  
        "navigationBarBackgroundColor": "#F8F8F8",  
        "backgroundColor": "#F8F8F8"  
    }  
}

阅读须知:

为了减轻阅读复杂度,我们去除了一些非必要的代码,仅提供了绝对有用的部分。如果你想了解详细,可以参考从显示 Hello Word 开始

uni-simple-router中注册页面非常简单和方便。要注册一个页面,只需使用__dynamicImportComponent__辅助函数,并传入所需的组件即可。这样做会使页面组件根据不同平台进行编译,以适应各种环境。

此外,当注册顶级页面时,你还可以轻松配置uni-app的页面样式,支持所有uni-app页面样式的设置,详细你可以在这里查看到

为顶级页面设置样式

正如前文所述,当你在uni-simple-router中注册一个顶级页面时,该页面会继承uni-app的所有属性和事件。这使得在__dynamicImportComponent__函数中设置页面的样式和其他属性与在pages.json文件中设置样式效果一样,依然有效。

传统方案

// pages.json  
{  
"pages": [{  
    "path": "pages/index/index",  
    "style": {  
      "navigationBarTitleText":"uni-simple-router",   
      "navigationBarBackgroundColor":"#42b883",   
      "navigationBarTextStyle":"white"  
    }  
  }],  
  "globalStyle": {  
    "navigationBarTextStyle": "black",  
    "navigationBarTitleText": "uni-app",  
    "navigationBarBackgroundColor": "#F8F8F8",  
    "backgroundColor": "#F8F8F8"  
  }  
}

使用 uni-simple-router

// router.js  
const router = createRouter({  
  routes:[{  
      path:`/`,  
      component:__dynamicImportComponent__(`@/pages/index/index.vue`,{  
          pageType:`top`,  
          style:{   
              navigationBarTitleText:`uni-simple-router`,   
              navigationBarBackgroundColor:`#42b883`,   
              navigationBarTextStyle:`white`    
          }   
      })  
  }]  
})
// pages.json  
{  
  "globalStyle": {  
        "navigationBarTextStyle": "black",  
        "navigationBarTitleText": "uni-app",  
        "navigationBarBackgroundColor": "#F8F8F8",  
        "backgroundColor": "#F8F8F8"  
    }  
}

创建页面组件分包

除了能根据不同平台适配导入组件,__dynamicImportComponent__函数还允许你在该函数中设置组件分包。

假设你现在有如下两个页面配置:

// router.js  
const router = createRouter({  
  routes:[{  
      path:`/`,  
      component:__dynamicImportComponent__(`@/pages/index/index.vue`,{  
        pageType:`top`,  
        package: {    
          type: `child`  
        },   
      })  
  },{  
      path:`/pkg1`,  
      component:__dynamicImportComponent__(`@/pkg/pkg1/index.vue`,{  
        pageType:`top`,  
        package: {    
          type: `child`,  
          name: `pkg/pkg1`  
        },   
      })  
  }]  
})

相当于在 pages.json 文件中配置 subPackages 字段一样:

{  
  "subPackages": [  
    {  
      "root": "pkg/pkg1",  
      "pages": [{  
        "path": "index",  
      }]  
    },   
    {  
      "root": "pages",  
      "pages": [{  
        "path": "index/index",  
      }]  
    }  
  ],  
}

如需了解详细,请查看 将导入的组件分包

创建嵌套页面结构

uni-app开发中,对于嵌套页面的需求非常普遍,然而,官方并没有提供内建的嵌套路由解决方案,这导致开发者不得不寻找其他替代方案。在截至uni-simple-router v3版本发布之前,跨端嵌套解决方案相对较难实现。

由于官方未提供直接支持,开发者在嵌套页面的实现上面临一些挑战。不过,uni-simple-router v3版本的推出为开发者带来了更为便捷和灵活的解决方案,使嵌套页面的使用变得更加简单和高效。

通过使用uni-simple-router v3版本,开发者现在可以更方便地实现嵌套页面,无论是在同一端还是跨多端的情况下,都能得到很好的支持。这为uni-app开发带来了更多可能性和创意的空间,使开发过程变得更加顺畅和愉快。

// router.js  
const router = createRouter({  
  routes:[{  
    path: '/admin',  
    component: __dynamicImportComponent__(`@/pages/AdminLayout.vue`,{  
      pageType:`top`,  
    }),  
    children: [  
      {  
        path: 'dashboard',  
        component: __dynamicImportComponent__(`@/pages/DashboardPage.vue`)  
      },  
      {  
        path: 'users',  
        component: __dynamicImportComponent__(`@/pages/UsersPage.vue`)  
      },  
      {  
        path: 'settings',  
        component: __dynamicImportComponent__(`@/pages/SettingsPage.vue`)  
      }  
    ]  
  }];  
})
<!-- AdminLayout.vue -->  
<template>  
  <div>  
    <sidebar></sidebar>  
    <main>  
      <simple-router-view></simple-router-view>  
    </main>  
  </div>  
</template>

通过使用嵌套路由,你可以构建出更复杂的页面布局和导航结构,将不同功能模块拆分成独立的路由组件,并嵌套在父级路由中,实现更灵活和可扩展的应用程序架构,详细请查看 嵌套路由

自定义路径匹配

uni-simple-router中,当你不再使用pages.json来定义页面时,你可以轻松地自定义path,而不再担心路径无法自定义的问题。

通过使用__dynamicImportComponent__函数来导入组件,并在路由配置中进行自定义path的设置,你完全可以根据自己的需求来定义页面的路径。这种灵活性让你能够更加自由地组织和管理页面结构,而无需受限于pages.json的固定配置。

伪静态格式

  • router.js
    const router = createRouter({  
    platform: process.env.VUE_APP_PLATFORM,  
    routes:[{  
        path:`/class/:id(\\d+).html`,  
        component: Home  
    }]  
    })
  • URL表现
    http://localhost:5174/#/class/6.html

静态路径格式

  • router.js
    const router = createRouter({  
    platform: process.env.VUE_APP_PLATFORM,  
    routes:[{  
        path:`/home`,  
        component: Home  
    }]  
    })
  • URL表现
    http://localhost:5174/#/home

复杂分类

  • router.js
    const router = createRouter({  
    platform: process.env.VUE_APP_PLATFORM,  
    routes:[{  
        path:`/product/:category(\\w+)/:subCategory(\\w+)`,  
        component: Product  
    }]  
    })
  • URL表现
    http://localhost:5174/#/product/electronics/phones

指定字符

  • router.js
    const router = createRouter({  
    platform: process.env.VUE_APP_PLATFORM,  
    routes:[{  
        path:`/category/:name(\\w+)`,  
        component: Category  
    }]  
    })
  • URL表现
    http://localhost:5174/#/category/books

如果了解更多你可以查阅 路由匹配

继续阅读 »

高效实现uni-app页面路由模块化及路径个性化

在传统的uni-app开发中,我们被限制在手动维护pages.json文件中,以定义页面和页面样式特性。然而,随着应用程序复杂度的增加,这种方式会显著增加开发者的心智负担。面对庞大的项目和频繁的页面更新,pages.json的管理变得非常繁琐和不便。

然而,幸运的是,采用uni-simple-router后,这些问题迎刃而解。uni-simple-router为我们提供了一种更灵活、更高效的方式来处理页面路由。不再依赖于pages.json,我们可以动态地创建页面路由,甚至允许构建复杂的页面结构,如动态路由和嵌套路由等。

创建一个传统页面

传统方案

// pages.json  
{  
    "pages": [   
        {  
            "path": "pages/index/index"  
        }  
    ],  
  "globalStyle": {  
        "navigationBarTextStyle": "black",  
        "navigationBarTitleText": "uni-app",  
        "navigationBarBackgroundColor": "#F8F8F8",  
        "backgroundColor": "#F8F8F8"  
    }  
}

使用 uni-simple-router

// router.js  
const router = createRouter({  
  routes:[{  
      path:`/`,  
      component:__dynamicImportComponent__(`@/pages/index/index.vue`,{  
          pageType:`top`    
      })  
  }]  
})
// pages.json  
{  
  "globalStyle": {  
        "navigationBarTextStyle": "black",  
        "navigationBarTitleText": "uni-app",  
        "navigationBarBackgroundColor": "#F8F8F8",  
        "backgroundColor": "#F8F8F8"  
    }  
}

阅读须知:

为了减轻阅读复杂度,我们去除了一些非必要的代码,仅提供了绝对有用的部分。如果你想了解详细,可以参考从显示 Hello Word 开始

uni-simple-router中注册页面非常简单和方便。要注册一个页面,只需使用__dynamicImportComponent__辅助函数,并传入所需的组件即可。这样做会使页面组件根据不同平台进行编译,以适应各种环境。

此外,当注册顶级页面时,你还可以轻松配置uni-app的页面样式,支持所有uni-app页面样式的设置,详细你可以在这里查看到

为顶级页面设置样式

正如前文所述,当你在uni-simple-router中注册一个顶级页面时,该页面会继承uni-app的所有属性和事件。这使得在__dynamicImportComponent__函数中设置页面的样式和其他属性与在pages.json文件中设置样式效果一样,依然有效。

传统方案

// pages.json  
{  
"pages": [{  
    "path": "pages/index/index",  
    "style": {  
      "navigationBarTitleText":"uni-simple-router",   
      "navigationBarBackgroundColor":"#42b883",   
      "navigationBarTextStyle":"white"  
    }  
  }],  
  "globalStyle": {  
    "navigationBarTextStyle": "black",  
    "navigationBarTitleText": "uni-app",  
    "navigationBarBackgroundColor": "#F8F8F8",  
    "backgroundColor": "#F8F8F8"  
  }  
}

使用 uni-simple-router

// router.js  
const router = createRouter({  
  routes:[{  
      path:`/`,  
      component:__dynamicImportComponent__(`@/pages/index/index.vue`,{  
          pageType:`top`,  
          style:{   
              navigationBarTitleText:`uni-simple-router`,   
              navigationBarBackgroundColor:`#42b883`,   
              navigationBarTextStyle:`white`    
          }   
      })  
  }]  
})
// pages.json  
{  
  "globalStyle": {  
        "navigationBarTextStyle": "black",  
        "navigationBarTitleText": "uni-app",  
        "navigationBarBackgroundColor": "#F8F8F8",  
        "backgroundColor": "#F8F8F8"  
    }  
}

创建页面组件分包

除了能根据不同平台适配导入组件,__dynamicImportComponent__函数还允许你在该函数中设置组件分包。

假设你现在有如下两个页面配置:

// router.js  
const router = createRouter({  
  routes:[{  
      path:`/`,  
      component:__dynamicImportComponent__(`@/pages/index/index.vue`,{  
        pageType:`top`,  
        package: {    
          type: `child`  
        },   
      })  
  },{  
      path:`/pkg1`,  
      component:__dynamicImportComponent__(`@/pkg/pkg1/index.vue`,{  
        pageType:`top`,  
        package: {    
          type: `child`,  
          name: `pkg/pkg1`  
        },   
      })  
  }]  
})

相当于在 pages.json 文件中配置 subPackages 字段一样:

{  
  "subPackages": [  
    {  
      "root": "pkg/pkg1",  
      "pages": [{  
        "path": "index",  
      }]  
    },   
    {  
      "root": "pages",  
      "pages": [{  
        "path": "index/index",  
      }]  
    }  
  ],  
}

如需了解详细,请查看 将导入的组件分包

创建嵌套页面结构

uni-app开发中,对于嵌套页面的需求非常普遍,然而,官方并没有提供内建的嵌套路由解决方案,这导致开发者不得不寻找其他替代方案。在截至uni-simple-router v3版本发布之前,跨端嵌套解决方案相对较难实现。

由于官方未提供直接支持,开发者在嵌套页面的实现上面临一些挑战。不过,uni-simple-router v3版本的推出为开发者带来了更为便捷和灵活的解决方案,使嵌套页面的使用变得更加简单和高效。

通过使用uni-simple-router v3版本,开发者现在可以更方便地实现嵌套页面,无论是在同一端还是跨多端的情况下,都能得到很好的支持。这为uni-app开发带来了更多可能性和创意的空间,使开发过程变得更加顺畅和愉快。

// router.js  
const router = createRouter({  
  routes:[{  
    path: '/admin',  
    component: __dynamicImportComponent__(`@/pages/AdminLayout.vue`,{  
      pageType:`top`,  
    }),  
    children: [  
      {  
        path: 'dashboard',  
        component: __dynamicImportComponent__(`@/pages/DashboardPage.vue`)  
      },  
      {  
        path: 'users',  
        component: __dynamicImportComponent__(`@/pages/UsersPage.vue`)  
      },  
      {  
        path: 'settings',  
        component: __dynamicImportComponent__(`@/pages/SettingsPage.vue`)  
      }  
    ]  
  }];  
})
<!-- AdminLayout.vue -->  
<template>  
  <div>  
    <sidebar></sidebar>  
    <main>  
      <simple-router-view></simple-router-view>  
    </main>  
  </div>  
</template>

通过使用嵌套路由,你可以构建出更复杂的页面布局和导航结构,将不同功能模块拆分成独立的路由组件,并嵌套在父级路由中,实现更灵活和可扩展的应用程序架构,详细请查看 嵌套路由

自定义路径匹配

uni-simple-router中,当你不再使用pages.json来定义页面时,你可以轻松地自定义path,而不再担心路径无法自定义的问题。

通过使用__dynamicImportComponent__函数来导入组件,并在路由配置中进行自定义path的设置,你完全可以根据自己的需求来定义页面的路径。这种灵活性让你能够更加自由地组织和管理页面结构,而无需受限于pages.json的固定配置。

伪静态格式

  • router.js
    const router = createRouter({  
    platform: process.env.VUE_APP_PLATFORM,  
    routes:[{  
        path:`/class/:id(\\d+).html`,  
        component: Home  
    }]  
    })
  • URL表现
    http://localhost:5174/#/class/6.html

静态路径格式

  • router.js
    const router = createRouter({  
    platform: process.env.VUE_APP_PLATFORM,  
    routes:[{  
        path:`/home`,  
        component: Home  
    }]  
    })
  • URL表现
    http://localhost:5174/#/home

复杂分类

  • router.js
    const router = createRouter({  
    platform: process.env.VUE_APP_PLATFORM,  
    routes:[{  
        path:`/product/:category(\\w+)/:subCategory(\\w+)`,  
        component: Product  
    }]  
    })
  • URL表现
    http://localhost:5174/#/product/electronics/phones

指定字符

  • router.js
    const router = createRouter({  
    platform: process.env.VUE_APP_PLATFORM,  
    routes:[{  
        path:`/category/:name(\\w+)`,  
        component: Category  
    }]  
    })
  • URL表现
    http://localhost:5174/#/category/books

如果了解更多你可以查阅 路由匹配

收起阅读 »

G-Media 2023 | 每日互动使用大模型实现“智选人群”

如今“百模大战”,每日互动在持续打磨数据营销服务能力的同时,密切关注行业趋势,积极探索AIGC、大模型等前沿技术在品牌营销等垂直领域的应用。7月19日,G-Media 2023正式开幕。现场,每日互动(个推)创始人、CEO方毅以“Grow with Evolution:要变,还得是绝活儿”为主题发表了精彩演讲,结合当下技术和行业热点,方毅分享了每日互动在大模型应用方面的探索。

方毅认为,对于品牌营销等垂直行业来讲,要真正发挥出大模型的巨大能力,还需要把模型用“活”,即结合“行业知识”对大模型进行特定的训练,让大模型听得懂“行话”。只有这样,一线的营销从业人员才能自如、灵活地使用日常惯用的“行话”向大模型提问,大模型也才能发挥出自己的实力,帮助解决具体业务场景中的问题。

在G-Media 2021,每日互动方毅曾分享过独创的“数据营销五步法”。“五步法”沉淀了每日互动的数据建模经验,其核心是将品牌一方数据作为“Y值”,让AI模型去分析和学习种子人群的线上线下偏好特征,形成智能预测能力,然后去做相似性扩量,找到更多的目标TA。

但是对于无法提供“Y值”的品牌,该如何进行相似性扩量呢?懂“行话”的大模型就能很好地解决这一问题。每日互动把自身在品牌营销领域沉淀的丰富经验、Know-how,打造形成行业知识库,“喂”给大模型学习,让大模型能够听得懂行话并使用行话输出它对品牌、消费人群的人文特征理解;同时使用编织技术,实现行业知识库和每日互动大数据标签特征库的打通,让大模型能够结合每日互动的数据能力,帮助品牌客户洞察细分人群的数据特征,并在每日互动的全网流量池中进行相似性扩量。

方毅介绍,每日互动正在打造的“智选人群”功能,就是这样的实现原理。营销人员可以像日常对话一样,向大模型提问,比如“新能源车主人群有哪些特征”。经过每日互动行业知识库和历史经验精调后的大模型,不仅能够理解用户的语言,还能快速输出匹配的洞察结果和人群方案,更精准地在每日互动流量池中进行相似人群的扩量。品牌直接调用每日互动的“智选人群”功能,就可以快速地对细分人群进行画像洞察,并加速实现相似性扩量,智能定向高潜TA,提升TA浓度,将“对话式生成”的目标人群一键对接多种媒体平台,高效完成广告投放,大幅提升营销效能。

方毅表示,正如今年G-Media峰会的主题——“信任与信心”,每日互动将不断升级数据营销服务能力,同时也将以互信为前提,和各行业伙伴携手,共同建设一个安全、高效、透明的品牌数字化营销新生态,提振行业各方对于可持续增长的信心。

继续阅读 »

如今“百模大战”,每日互动在持续打磨数据营销服务能力的同时,密切关注行业趋势,积极探索AIGC、大模型等前沿技术在品牌营销等垂直领域的应用。7月19日,G-Media 2023正式开幕。现场,每日互动(个推)创始人、CEO方毅以“Grow with Evolution:要变,还得是绝活儿”为主题发表了精彩演讲,结合当下技术和行业热点,方毅分享了每日互动在大模型应用方面的探索。

方毅认为,对于品牌营销等垂直行业来讲,要真正发挥出大模型的巨大能力,还需要把模型用“活”,即结合“行业知识”对大模型进行特定的训练,让大模型听得懂“行话”。只有这样,一线的营销从业人员才能自如、灵活地使用日常惯用的“行话”向大模型提问,大模型也才能发挥出自己的实力,帮助解决具体业务场景中的问题。

在G-Media 2021,每日互动方毅曾分享过独创的“数据营销五步法”。“五步法”沉淀了每日互动的数据建模经验,其核心是将品牌一方数据作为“Y值”,让AI模型去分析和学习种子人群的线上线下偏好特征,形成智能预测能力,然后去做相似性扩量,找到更多的目标TA。

但是对于无法提供“Y值”的品牌,该如何进行相似性扩量呢?懂“行话”的大模型就能很好地解决这一问题。每日互动把自身在品牌营销领域沉淀的丰富经验、Know-how,打造形成行业知识库,“喂”给大模型学习,让大模型能够听得懂行话并使用行话输出它对品牌、消费人群的人文特征理解;同时使用编织技术,实现行业知识库和每日互动大数据标签特征库的打通,让大模型能够结合每日互动的数据能力,帮助品牌客户洞察细分人群的数据特征,并在每日互动的全网流量池中进行相似性扩量。

方毅介绍,每日互动正在打造的“智选人群”功能,就是这样的实现原理。营销人员可以像日常对话一样,向大模型提问,比如“新能源车主人群有哪些特征”。经过每日互动行业知识库和历史经验精调后的大模型,不仅能够理解用户的语言,还能快速输出匹配的洞察结果和人群方案,更精准地在每日互动流量池中进行相似人群的扩量。品牌直接调用每日互动的“智选人群”功能,就可以快速地对细分人群进行画像洞察,并加速实现相似性扩量,智能定向高潜TA,提升TA浓度,将“对话式生成”的目标人群一键对接多种媒体平台,高效完成广告投放,大幅提升营销效能。

方毅表示,正如今年G-Media峰会的主题——“信任与信心”,每日互动将不断升级数据营销服务能力,同时也将以互信为前提,和各行业伙伴携手,共同建设一个安全、高效、透明的品牌数字化营销新生态,提振行业各方对于可持续增长的信心。

收起阅读 »

Android应用签名MD5格式不正确,请填写MD5格式的签名

一键登录

申请一键登录时,添加应用,提示“Android应用签名MD5格式不正确,请填写MD5格式的签名”。
原因是jdk的版本不同生成的值不同,可以使用java1.8.0_201版本,亲测可以。
点击下面的链接,找到201版本的jdk版本下载就好了。
java1.8.0_201

继续阅读 »

申请一键登录时,添加应用,提示“Android应用签名MD5格式不正确,请填写MD5格式的签名”。
原因是jdk的版本不同生成的值不同,可以使用java1.8.0_201版本,亲测可以。
点击下面的链接,找到201版本的jdk版本下载就好了。
java1.8.0_201

收起阅读 »