监听应用安装完成
/**
- 监听应用启动
-
通过广播接收器监听目标应用的启动事件
*/
const receiveBroadcastOfAppInstall: {
broadcastReceiver?: any;
(targetPname: string): Promise<void>
} = (targetPname) => {
const main = plus.android.runtimeMainActivity()
if (receiveBroadcastOfAppInstall.broadcastReceiver) {
// @ts-ignore
main.unregisterReceiver(receiveBroadcastOfAppInstall.broadcastReceiver)
receiveBroadcastOfAppInstall.broadcastReceiver = null
}const Intent = plus.android.importClass('android.content.Intent')
const IntentFilter = plus.android.importClass('android.content.IntentFilter')
const Uri = plus.android.importClass('android.net.Uri')
// @ts-ignore
const filter = new IntentFilter()
// @ts-ignore
filter.addAction(Intent.ACTION_PACKAGE_ADDED)
// @ts-ignore
filter.addAction(Intent.ACTION_PACKAGE_REPLACED)
filter.addDataScheme('package')
return new Promise((resolve) => {
const broadcastReceiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', {
onReceive: (context: any, intent: any) => {
const action = intent.getAction()
const data = intent.getData()
if (
// @ts-ignore
(action === Intent.ACTION_PACKAGE_ADDED || action === Intent.ACTION_PACKAGE_REPLACED)
/**
- 监听应用启动
-
通过广播接收器监听目标应用的启动事件
*/
const receiveBroadcastOfAppInstall: {
broadcastReceiver?: any;
(targetPname: string): Promise<void>
} = (targetPname) => {
const main = plus.android.runtimeMainActivity()
if (receiveBroadcastOfAppInstall.broadcastReceiver) {
// @ts-ignore
main.unregisterReceiver(receiveBroadcastOfAppInstall.broadcastReceiver)
receiveBroadcastOfAppInstall.broadcastReceiver = null
}const Intent = plus.android.importClass('android.content.Intent')
const IntentFilter = plus.android.importClass('android.content.IntentFilter')
const Uri = plus.android.importClass('android.net.Uri')
// @ts-ignore
const filter = new IntentFilter()
// @ts-ignore
filter.addAction(Intent.ACTION_PACKAGE_ADDED)
// @ts-ignore
filter.addAction(Intent.ACTION_PACKAGE_REPLACED)
filter.addDataScheme('package')
return new Promise((resolve) => {
const broadcastReceiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', {
onReceive: (context: any, intent: any) => {
const action = intent.getAction()
const data = intent.getData()
if (
// @ts-ignore
(action === Intent.ACTION_PACKAGE_ADDED || action === Intent.ACTION_PACKAGE_REPLACED)
商业模式之争:SaaS代练系统与独立护航小程序的底层逻辑差异及源码实现方案
在游戏代练赛道,SaaS代练系统与独立护航小程序的竞争,核心是商业模式与技术架构的底层差异,二者适配不同规模从业者,源码实现逻辑也随之不同,共同构成行业两大主流解决方案。
底层逻辑上,SaaS代练系统以“租赁服务”为核心,主打轻门槛、标准化,本质是通过持续收取年费和订单抽成盈利,核心逻辑是服务规模化复用,用户无需投入开发成本,却需依附平台,数据与运营自主权受限。独立护航小程序则以“自主可控”为核心,采用买断式部署,一次性投入后无后续抽成,核心逻辑是打造专属品牌,掌控客户数据与运营规则,适配中小工作室长期盈利需求。
源码实现方面,SaaS系统采用UniApp ThinkPHP6架构,侧重多端适配与集中管控,核心模块含订单管理、多角色权限分配,通过Redis缓存与读写分离支撑高并发,源码需预留增值功能接口适配分层付费模式。独立小程序源码同样基于UniApp PHP开发,侧重轻量化与自主部署,简化平台管控模块,强化客户留存与自定义功能,集成WebSocket实现进度实时推送,部署后仅需承担服务器与域名成本。
二者无绝对优劣,SaaS适合新手快速起步,独立小程序适合追求长期盈利、注重品牌建设的从业者,源码实现需贴合其商业模式核心,平衡成本与自主可控性。
在游戏代练赛道,SaaS代练系统与独立护航小程序的竞争,核心是商业模式与技术架构的底层差异,二者适配不同规模从业者,源码实现逻辑也随之不同,共同构成行业两大主流解决方案。
底层逻辑上,SaaS代练系统以“租赁服务”为核心,主打轻门槛、标准化,本质是通过持续收取年费和订单抽成盈利,核心逻辑是服务规模化复用,用户无需投入开发成本,却需依附平台,数据与运营自主权受限。独立护航小程序则以“自主可控”为核心,采用买断式部署,一次性投入后无后续抽成,核心逻辑是打造专属品牌,掌控客户数据与运营规则,适配中小工作室长期盈利需求。
源码实现方面,SaaS系统采用UniApp ThinkPHP6架构,侧重多端适配与集中管控,核心模块含订单管理、多角色权限分配,通过Redis缓存与读写分离支撑高并发,源码需预留增值功能接口适配分层付费模式。独立小程序源码同样基于UniApp PHP开发,侧重轻量化与自主部署,简化平台管控模块,强化客户留存与自定义功能,集成WebSocket实现进度实时推送,部署后仅需承担服务器与域名成本。
二者无绝对优劣,SaaS适合新手快速起步,独立小程序适合追求长期盈利、注重品牌建设的从业者,源码实现需贴合其商业模式核心,平衡成本与自主可控性。
收起阅读 »
uniCloud阿里云服务空间云函数计费规则调整导致我们费用暴涨几十倍,我们怎么办?
办法1:
直接做项目迁移,迁移到支付宝云
办法2:
如果有使用的uni-admin开发后台管理的,但是又没有用到数据统计的,先去掉二个定时运行的云函数:
官方的uni-admin及uni-starter内置定时任务如下:
uni-stat-cron:uni 统计,每小时运行 1 次,删除可节省 1 天的保底费用 0.24 元(2160Gbs)
uni-analyse-searchhot:热点搜索,每 2 小时运行 1 次,删除可节省 1 天的保底费用 0.12 元(1080Gbs)
自己项目的定时运行的云函数也可以做下取舍,再全局审查项目,合并一些云函数,有的页面可能是一进来就调用二三个云函数的做下合并
办法3
这是最无奈的办法,知道希望很小,但是我们要试一试,我正在组织针对阿里云这一次修改的一个投诉,但是个人的力量是很微薄的,人多才力量大,我们一起加油,加入这个集体投诉,希望官方能听到我们声音
集体投诉地址:https://tousu.sina.com.cn/grp_comp/view/G17395081502
续
方法3已经试过,得到了阿里云的回复,说我没有直接用阿里云的产品,用阿里云产品的是unicloud,有什么问题应该由unicloud官方派人跟阿里云沟通,unicloud官方能不能派个人去沟通争取一下。
办法1:
直接做项目迁移,迁移到支付宝云
办法2:
如果有使用的uni-admin开发后台管理的,但是又没有用到数据统计的,先去掉二个定时运行的云函数:
官方的uni-admin及uni-starter内置定时任务如下:
uni-stat-cron:uni 统计,每小时运行 1 次,删除可节省 1 天的保底费用 0.24 元(2160Gbs)
uni-analyse-searchhot:热点搜索,每 2 小时运行 1 次,删除可节省 1 天的保底费用 0.12 元(1080Gbs)
自己项目的定时运行的云函数也可以做下取舍,再全局审查项目,合并一些云函数,有的页面可能是一进来就调用二三个云函数的做下合并
办法3
这是最无奈的办法,知道希望很小,但是我们要试一试,我正在组织针对阿里云这一次修改的一个投诉,但是个人的力量是很微薄的,人多才力量大,我们一起加油,加入这个集体投诉,希望官方能听到我们声音
集体投诉地址:https://tousu.sina.com.cn/grp_comp/view/G17395081502
续
方法3已经试过,得到了阿里云的回复,说我没有直接用阿里云的产品,用阿里云产品的是unicloud,有什么问题应该由unicloud官方派人跟阿里云沟通,unicloud官方能不能派个人去沟通争取一下。
实现微信小程序自定义 tabBar
前言
本文分享如何在uniapp vue3 实现自定义微信小程序 tabBar。
配置信息
在 pages.json 中添加 tabBar 的相关配置,例如
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/mine/index",
"style": {
"navigationBarTitleText": "我的"
}
}
],
"tabBar": {
"custom": true,
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/icon_component.png",
"selectedIconPath": "static/icon_component_HL.png",
"text": "首页"
},
{
"pagePath": "pages/mine/index",
"iconPath": "static/icon_API.png",
"selectedIconPath": "static/icon_API_HL.png",
"text": "我的"
}
]
}
}
添加 tabBar 代码文件
在根目录添加 custom-tab-bar 文件夹,下面包含微信小程序原生文件
编写 tabBar 代码
这一步需要获取自定义 tabBar 组件实例,通过示例来更新选中的 tab,微信小程序可以通过 this 操作,uniapp 也支持直接操作微信小程序组件示例,如下代码
<template>
<view>
<text>首页</text>
</view>
</template>
<script setup>
import { getCurrentInstance } from "vue";
import { onShow } from "@dcloudio/uni-app";
const instance = getCurrentInstance();
onShow(() => {
const tabBar = instance?.proxy?.$scope?.getTabBar?.(); // 获取组件示例函数返回值
if (tabBar) {
tabBar.setData({
selected: 0,
});
}
});
</script>
其他 tab 页同理
示例项目
参考附件
前言
本文分享如何在uniapp vue3 实现自定义微信小程序 tabBar。
配置信息
在 pages.json 中添加 tabBar 的相关配置,例如
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/mine/index",
"style": {
"navigationBarTitleText": "我的"
}
}
],
"tabBar": {
"custom": true,
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/icon_component.png",
"selectedIconPath": "static/icon_component_HL.png",
"text": "首页"
},
{
"pagePath": "pages/mine/index",
"iconPath": "static/icon_API.png",
"selectedIconPath": "static/icon_API_HL.png",
"text": "我的"
}
]
}
}
添加 tabBar 代码文件
在根目录添加 custom-tab-bar 文件夹,下面包含微信小程序原生文件
编写 tabBar 代码
这一步需要获取自定义 tabBar 组件实例,通过示例来更新选中的 tab,微信小程序可以通过 this 操作,uniapp 也支持直接操作微信小程序组件示例,如下代码
<template>
<view>
<text>首页</text>
</view>
</template>
<script setup>
import { getCurrentInstance } from "vue";
import { onShow } from "@dcloudio/uni-app";
const instance = getCurrentInstance();
onShow(() => {
const tabBar = instance?.proxy?.$scope?.getTabBar?.(); // 获取组件示例函数返回值
if (tabBar) {
tabBar.setData({
selected: 0,
});
}
});
</script>
其他 tab 页同理
示例项目
参考附件
收起阅读 »支付宝原生扫码插件兼容HBuildX新版本解决方案分享
> 原插件地址:
https://ext.dcloud.net.cn/plugin?id=2636
当前插件一直在使用,扫码很快很稳定,后面因为升级了HB版本,导致打包后扫码无任何反应,导致无法正常使用!
后面为了保证打包后插件可正常使用,只能回退到HB版本4.7.6
但是随着HB版本的不断升级优化,又不想抛弃这个插件,只能找解决方案来解决这个问题
后面发现4.7.6版本打包后的APK的LIB文件下有libc++_shared.so文件,而后新版本打包的APK里无这个so文件
对比信息:
解决方法:
1.把以前可正常运行包里的libc++_shared.so文件拷贝出来
分别是 arm64-v8a/libc++_shared.so armeabi-v7a/libc++_shared.so
2.然后在项目根目录新建文件夹nativeResources/android/libs/
3.将拷贝出来的两个文件夹放入到libs下
结果:
提交新版本打包 打包成功后就可以了
复核: 可打开新的APK 查看libs/xx/下是否有libc++_shared.so文件来确定
> 为了方便,可自行下载当前附件放到项目根目录 后进行打包测试
是否可行 可回帖反馈给需要的人
> 如果解决你的问题,烦请动动您的小手给个赞👍🏻
> 原插件地址:
https://ext.dcloud.net.cn/plugin?id=2636
当前插件一直在使用,扫码很快很稳定,后面因为升级了HB版本,导致打包后扫码无任何反应,导致无法正常使用!
后面为了保证打包后插件可正常使用,只能回退到HB版本4.7.6
但是随着HB版本的不断升级优化,又不想抛弃这个插件,只能找解决方案来解决这个问题
后面发现4.7.6版本打包后的APK的LIB文件下有libc++_shared.so文件,而后新版本打包的APK里无这个so文件
对比信息:
解决方法:
1.把以前可正常运行包里的libc++_shared.so文件拷贝出来
分别是 arm64-v8a/libc++_shared.so armeabi-v7a/libc++_shared.so
2.然后在项目根目录新建文件夹nativeResources/android/libs/
3.将拷贝出来的两个文件夹放入到libs下
结果:
提交新版本打包 打包成功后就可以了
复核: 可打开新的APK 查看libs/xx/下是否有libc++_shared.so文件来确定
> 为了方便,可自行下载当前附件放到项目根目录 后进行打包测试
是否可行 可回帖反馈给需要的人
> 如果解决你的问题,烦请动动您的小手给个赞👍🏻
收起阅读 »关于app隐藏底部安全边界问题(指示条、手机导航栏)
App如何隐藏手机底部安全边界问题(指示条、手机导航)
纯血鸿蒙(app-harmony)如何隐藏安全边界:
在 manifest.json 的 app-harmony 节点下独立配置:
"app-harmony": {
"safearea": {
"bottom": {
"offset": "none"
}
}
}
苹果系统(IOS)如何隐藏安全边界:
在 manifest.json 的 app-plus 节点下配置:
"app-plus": {
"safearea": {
"bottom": {
"offset": "none" // 设置为 none 可关闭底部安全区域占位
}
}
}
注:IOS隐藏安全边界配置完后一定得重新云打包自定义基座才生效
安卓系统()如何隐藏安全边界:
在第一个界面或随便一个界面script中设置。
// #ifdef APP-PLUS
// 判断当前环境是否为Android
if (plus.os.name.toLowerCase() === 'android') {
var Color = plus.android.importClass("android.graphics.Color");
const Window = plus.android.importClass("android.view.Window");
var mainActivity = plus.android.runtimeMainActivity();
var window_android = mainActivity.getWindow();
var WindowManager = plus.android.importClass("android.view.WindowManager");
var View = plus.android.importClass("android.view.View");
//直接隐藏
window_android.setFlags(Window.FLAG_FULLSCREEN, Window.FLAG_FULLSCREEN);
window_android.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View
.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
//设置为全透明
window_android.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window_android.setNavigationBarColor(Color.TRANSPARENT);
}
// #endif
注:涉及原生安卓代码,有不懂的直接让AI解析
安卓隐藏安全边界 原文链接:https://blog.csdn.net/sct_123/article/details/142408327
希望可以帮助到你
App如何隐藏手机底部安全边界问题(指示条、手机导航)
纯血鸿蒙(app-harmony)如何隐藏安全边界:
在 manifest.json 的 app-harmony 节点下独立配置:
"app-harmony": {
"safearea": {
"bottom": {
"offset": "none"
}
}
}
苹果系统(IOS)如何隐藏安全边界:
在 manifest.json 的 app-plus 节点下配置:
"app-plus": {
"safearea": {
"bottom": {
"offset": "none" // 设置为 none 可关闭底部安全区域占位
}
}
}
注:IOS隐藏安全边界配置完后一定得重新云打包自定义基座才生效
安卓系统()如何隐藏安全边界:
在第一个界面或随便一个界面script中设置。
// #ifdef APP-PLUS
// 判断当前环境是否为Android
if (plus.os.name.toLowerCase() === 'android') {
var Color = plus.android.importClass("android.graphics.Color");
const Window = plus.android.importClass("android.view.Window");
var mainActivity = plus.android.runtimeMainActivity();
var window_android = mainActivity.getWindow();
var WindowManager = plus.android.importClass("android.view.WindowManager");
var View = plus.android.importClass("android.view.View");
//直接隐藏
window_android.setFlags(Window.FLAG_FULLSCREEN, Window.FLAG_FULLSCREEN);
window_android.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View
.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
//设置为全透明
window_android.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window_android.setNavigationBarColor(Color.TRANSPARENT);
}
// #endif
注:涉及原生安卓代码,有不懂的直接让AI解析
安卓隐藏安全边界 原文链接:https://blog.csdn.net/sct_123/article/details/142408327
希望可以帮助到你
收起阅读 »阿里云数据库迁移支付宝云的可靠方案
1.首先开通支付宝云空间
- 在hbuilderx里关联新的云空间
- 上传全部云函数及database
4.这时候,支付宝云空间的数据库是空表,先从阿里云的数据库选择导出,保存为export.json - 创建parse.js,内容如下
const fs = require('fs')
const path = require('path')
const readline = require('readline')
const cwd = process.cwd()
const inputPath = path.resolve(cwd, process.argv[2])
const outputPath = path.resolve(cwd, process.argv[3])
if (fs.existsSync(outputPath)) {
throw new Error(输出路径(${outputPath})已存在)
}
function getType(val) {
return Object.prototype.toString.call(val).slice(8, -1).toLowerCase()
}
function parseRecord(obj) {
const type = getType(obj)
switch (type) {
case 'object':
if (obj.$oid) {
return obj.$oid
}
if (obj.$date) {
return obj.$date
}
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
obj[key] = parseRecord(obj[key])
}
return obj
case 'array':
for (let i = 0; i < obj.length; i++) {
obj[i] = parseRecord(obj[i])
}
return obj
default:
return obj
}
}
async function parseCollection() {
const inputStream = fs.createReadStream(inputPath)
const outputStream = fs.createWriteStream(outputPath)
const rl = readline.createInterface({
input: inputStream
});
for await (const line of rl) {
const recordStr = line.trim()
if (!recordStr) {
continue
}
const record = parseRecord(JSON.parse(recordStr))
outputStream.write(JSON.stringify(record) + '\n')
}
rl.close()
console.log(处理后的文件已输出到${outputPath})
}
parseCollection()
6.将parse.js放到电脑上,比如E盘,创建data文件夹,将export.josn放进去,打开终端,输入指令 node E:\parse.js E:\data\export.json E:\data\output.json
7.将转好的数据导入到支付宝云对应的新表里
8.parse.js自行添加转换规则,以上只是简单示例,根据自己的表结构来
1.首先开通支付宝云空间
- 在hbuilderx里关联新的云空间
- 上传全部云函数及database
4.这时候,支付宝云空间的数据库是空表,先从阿里云的数据库选择导出,保存为export.json - 创建parse.js,内容如下
const fs = require('fs')
const path = require('path')
const readline = require('readline')
const cwd = process.cwd()
const inputPath = path.resolve(cwd, process.argv[2])
const outputPath = path.resolve(cwd, process.argv[3])
if (fs.existsSync(outputPath)) {
throw new Error(输出路径(${outputPath})已存在)
}
function getType(val) {
return Object.prototype.toString.call(val).slice(8, -1).toLowerCase()
}
function parseRecord(obj) {
const type = getType(obj)
switch (type) {
case 'object':
if (obj.$oid) {
return obj.$oid
}
if (obj.$date) {
return obj.$date
}
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
obj[key] = parseRecord(obj[key])
}
return obj
case 'array':
for (let i = 0; i < obj.length; i++) {
obj[i] = parseRecord(obj[i])
}
return obj
default:
return obj
}
}
async function parseCollection() {
const inputStream = fs.createReadStream(inputPath)
const outputStream = fs.createWriteStream(outputPath)
const rl = readline.createInterface({
input: inputStream
});
for await (const line of rl) {
const recordStr = line.trim()
if (!recordStr) {
continue
}
const record = parseRecord(JSON.parse(recordStr))
outputStream.write(JSON.stringify(record) + '\n')
}
rl.close()
console.log(处理后的文件已输出到${outputPath})
}
parseCollection()
6.将parse.js放到电脑上,比如E盘,创建data文件夹,将export.josn放进去,打开终端,输入指令 node E:\parse.js E:\data\export.json E:\data\output.json
7.将转好的数据导入到支付宝云对应的新表里
8.parse.js自行添加转换规则,以上只是简单示例,根据自己的表结构来
收起阅读 »直播推流live-pusher开发心得-写在页面里正常_写在组件里就会报错
问题描述
在开发 APP 专用直播间页面时,遇到 <live-pusher> 组件在不同层级引用时的兼容性问题:
- 正常情况:将
<live-pusher>直接写在页面文件room.nvue中,运行正常。 - 异常情况:将
<live-pusher>封装在子组件LivePusherDemo.nvue中,运行报错。
该问题已困扰两天,经过排查与测试,确认为 UniApp nvue 环境下原生组件的已知限制。
报错信息
20:08:43.587 [Vue warn]: Unhandled error during execution of native event handler
at <LivePusherDemo key=0 ref="livePusherDemo">
at <Room __pageId=4 __pagePath="pages/live/room" __pageQuery={"roomnumber":"666"}>
20:08:43.588 TypeError: Cannot read property 'meta' of undefined
原因分析
1. 原生组件特性
live-pusher 是原生组件(Native Component),而非标准的 Vue 组件。其事件(如 @statechange, @netstatus, @error)由原生层(Weex/Android/iOS)直接触发,不完全经过 Vue 的事件系统。
2. 事件参数格式差异
- 在页面中:原生事件回调时,事件对象
e的格式通常为{ detail: { code: xxx, ... } },因此e.detail可以正常解构。 - 在子组件中由于 Weex 桥接机制:事件传递路径变为
原生层 -> 组件实例 -> 方法。在此过程中,Weex 桥接层可能未正确转换事件格式,导致e的结构变为{ meta: { ... } }或其他格式,使得e.detail为undefined。 - 报错根源:代码中尝试执行
const { code } = e.detail时,因e.detail不存在而抛出Cannot read property 'meta' of undefined(底层使用meta包装数据,但 Vue 侧期望detail)。
3. Context 创建问题
createLivePusherContext 在子组件中创建时,内部引用的组件实例路径可能不正确。原生层通过 this 回调事件时,可能无法正确找到 Vue 组件实例上的方法,或导致事件参数丢失。
解决思路
针对此兼容性问题,主要有以下三种解决方案:
方案 A:放弃组件化封装(推荐,最稳妥)
- 做法:将
<live-pusher>标签直接写在room.nvue页面模板中,不将其封装为子组件。 - 依据:这是 UniApp 官方推荐的做法。
- 优化:可以将配置管理、UI 弹窗等非原生部分拆分为普通子组件,但核心推流标签必须保留在页面根模板中。
方案 B:事件处理加防御 + 手动适配
- 做法:在组件的事件处理方法中,不进行直接解构,而是先打印完整
e对象以确定实际格式,并做防御性判断。 - 缺点:需要针对不同平台进行复杂的兼容性处理,维护成本高。
方案 C:页面创建 Context,传递给子组件
- 做法:让
<live-pusher>的 id 暴露在页面上,在room.nvue的onReady中创建 context,再通过props/emit或ref传递给子组件。 - 缺点:架构较复杂,且仍可能受限于原生事件回调路径问题。
最终解决方案
采用 混合模式:将原生推流标签留在页面,将业务逻辑封装在组件。
- 推流标签位置:将
<live-pusher>直接写在页面room.nvue中。 - 参数配置位置:将开播参数设置、业务逻辑封装在组件
LivePusherApp.nvue中。 - 结果:测试通过,运行稳定。
相关代码参考
本项目前端代码已正式开源,旨在为开发者提供一套可参考的直播系统实现方案。欢迎各位同仁查阅源码、交流技术或将其作为二次开发的基础。
| 文件路径 | 说明 |
|---|---|
/pages/live/room.nvue |
直播间页面:包含 `` 标签 |
/pages/live/components_app/LivePusherApp.nvue |
主播设置组件:包含开播参数设置逻辑 |
/pages/video/push_app2.nvue |
推流 Demo:可供参考的基础实现 |
总结:这是一个 UniApp nvue 环境下原生组件事件桥接的已知兼容性问题,并非代码逻辑错误。最简单的解决方式是将
<live-pusher>标签直接放在.nvue页面模板中,不要封装到子组件内。
资源链接
- 技术介绍: https://smplive.wpygo.com/
- 在线文档: https://www.showdoc.com.cn/smplive
- DCloud 插件市场:
- 便于直接在 HBuilderX 中导入使用
- 点击查看插件详情
- Gitee 代码仓库:
- 获取完整源代码及提交历史
- 访问仓库地址
问题描述
在开发 APP 专用直播间页面时,遇到 <live-pusher> 组件在不同层级引用时的兼容性问题:
- 正常情况:将
<live-pusher>直接写在页面文件room.nvue中,运行正常。 - 异常情况:将
<live-pusher>封装在子组件LivePusherDemo.nvue中,运行报错。
该问题已困扰两天,经过排查与测试,确认为 UniApp nvue 环境下原生组件的已知限制。
报错信息
20:08:43.587 [Vue warn]: Unhandled error during execution of native event handler
at <LivePusherDemo key=0 ref="livePusherDemo">
at <Room __pageId=4 __pagePath="pages/live/room" __pageQuery={"roomnumber":"666"}>
20:08:43.588 TypeError: Cannot read property 'meta' of undefined
原因分析
1. 原生组件特性
live-pusher 是原生组件(Native Component),而非标准的 Vue 组件。其事件(如 @statechange, @netstatus, @error)由原生层(Weex/Android/iOS)直接触发,不完全经过 Vue 的事件系统。
2. 事件参数格式差异
- 在页面中:原生事件回调时,事件对象
e的格式通常为{ detail: { code: xxx, ... } },因此e.detail可以正常解构。 - 在子组件中由于 Weex 桥接机制:事件传递路径变为
原生层 -> 组件实例 -> 方法。在此过程中,Weex 桥接层可能未正确转换事件格式,导致e的结构变为{ meta: { ... } }或其他格式,使得e.detail为undefined。 - 报错根源:代码中尝试执行
const { code } = e.detail时,因e.detail不存在而抛出Cannot read property 'meta' of undefined(底层使用meta包装数据,但 Vue 侧期望detail)。
3. Context 创建问题
createLivePusherContext 在子组件中创建时,内部引用的组件实例路径可能不正确。原生层通过 this 回调事件时,可能无法正确找到 Vue 组件实例上的方法,或导致事件参数丢失。
解决思路
针对此兼容性问题,主要有以下三种解决方案:
方案 A:放弃组件化封装(推荐,最稳妥)
- 做法:将
<live-pusher>标签直接写在room.nvue页面模板中,不将其封装为子组件。 - 依据:这是 UniApp 官方推荐的做法。
- 优化:可以将配置管理、UI 弹窗等非原生部分拆分为普通子组件,但核心推流标签必须保留在页面根模板中。
方案 B:事件处理加防御 + 手动适配
- 做法:在组件的事件处理方法中,不进行直接解构,而是先打印完整
e对象以确定实际格式,并做防御性判断。 - 缺点:需要针对不同平台进行复杂的兼容性处理,维护成本高。
方案 C:页面创建 Context,传递给子组件
- 做法:让
<live-pusher>的 id 暴露在页面上,在room.nvue的onReady中创建 context,再通过props/emit或ref传递给子组件。 - 缺点:架构较复杂,且仍可能受限于原生事件回调路径问题。
最终解决方案
采用 混合模式:将原生推流标签留在页面,将业务逻辑封装在组件。
- 推流标签位置:将
<live-pusher>直接写在页面room.nvue中。 - 参数配置位置:将开播参数设置、业务逻辑封装在组件
LivePusherApp.nvue中。 - 结果:测试通过,运行稳定。
相关代码参考
本项目前端代码已正式开源,旨在为开发者提供一套可参考的直播系统实现方案。欢迎各位同仁查阅源码、交流技术或将其作为二次开发的基础。
| 文件路径 | 说明 |
|---|---|
/pages/live/room.nvue |
直播间页面:包含 `` 标签 |
/pages/live/components_app/LivePusherApp.nvue |
主播设置组件:包含开播参数设置逻辑 |
/pages/video/push_app2.nvue |
推流 Demo:可供参考的基础实现 |
总结:这是一个 UniApp nvue 环境下原生组件事件桥接的已知兼容性问题,并非代码逻辑错误。最简单的解决方式是将
<live-pusher>标签直接放在.nvue页面模板中,不要封装到子组件内。
资源链接
- 技术介绍: https://smplive.wpygo.com/
- 在线文档: https://www.showdoc.com.cn/smplive
- DCloud 插件市场:
- 便于直接在 HBuilderX 中导入使用
- 点击查看插件详情
- Gitee 代码仓库:
- 获取完整源代码及提交历史
- 访问仓库地址
分享开发直播系统遇到的问题及解决方案(uniapp)
1. 项目背景与技术选型
近期启动了一个基于 UniApp 的直播系统开发项目。在明确需求后,迅速完成了技术栈选型并投入开发。整体架构旨在实现一套代码多端运行(H5、微信小程序、APP),涵盖首页、直播列表、个人中心等核心模块。
- 前端框架:UniApp
- 后端管理:FastAdmin
- 即时通讯(聊天服务):Workerman
- 目标平台:H5、微信小程序、原生 APP
2. 核心挑战与解决方案
虽然整体功能开发进展顺利,但在直播间页面的多端适配过程中遇到了三个关键技术瓶颈。以下是具体问题及最终的解决策略:
🔴 问题一:H5 端播放卡顿
- 现象描述:
在 H5 端调试时,进入直播间页面加载极慢,体验严重卡顿。 - 原因分析:
经排查,问题源于引用的第三方库 [hls.min.js]。该文件通过外网 CDN 引入,受网络波动影响较大,导致资源加载阻塞。 - ✅ 解决方案:
本地化部署。将 [hls.min.js] 下载至项目本地目录,改为本地引用。此举显著提升了加载速度,彻底解决了卡顿问题。注:H5 端采用
m3u8(HLS) 格式流,小程序端采用rtmp格式流。
🔴 问题二:APP 端页面层级错乱
- 现象描述:
H5 和小程序端运行正常,但打包至 APP 端时,直播间页面布局完全错乱,视频组件覆盖异常。 - 原因分析:
查阅文档后发现,这是由原生 APP 中视频组件的层级(Z-Index)导致的。试图在单一的 [.vue] 文件中通过条件编译兼容所有端(H5/小程序/APP)的方案,在处理复杂视频层级时存在天然局限。即便借助 AI 辅助调试三天,仍无法完美解决。 - ✅ 解决方案:
拆分代码策略。放弃“一套代码通吃”的执念,改为维护两套直播间代码:- [.vue] 文件:专用于 H5 和微信小程序。
- [.nvue]) 文件:专用于原生 APP(利用其原生渲染优势解决层级问题)。
🔴 问题三:APP 端推流功能实现困难
- 现象描述:
由于 H5 限制及微信小程序政策调整(不再开放主播推流权限),推流功能仅在 APP 端有实际需求。在完成 [nvue]) 页面基础功能后,尝试利用 AI 生成推流逻辑,耗时两天仍未成功,且导致代码逻辑混乱。 - 原因分析:
直播推流涉及底层摄像头权限、编码参数及推流协议,逻辑复杂度较高。完全依赖 AI 生成复杂业务逻辑容易导致代码结构不可控,维护成本激增。 - ✅ 解决方案:
模块化隔离开发。- 单独开发一个仅包含推流功能的独立页面,确保核心逻辑纯净可用。
- 验证成功后,再将该功能模块移植整合到主直播间页面中。
💡 经验总结:对于高复杂度的底层功能,AI 可作为辅助参考,但核心逻辑仍需人工把控,避免过度依赖导致代码失控。
3. 总结
本次开发经历表明,在跨端直播场景下,"因地制宜"比“强行统一”更有效。针对视频播放和推流等特殊场景,灵活采用 [.vue] 与 [.nvue] 分离的策略,以及模块化开发思路,是保障项目稳定落地的关键。
本项目前端代码已正式开源,旨在为开发者提供一套可参考的直播系统实现方案。欢迎各位同仁查阅源码、交流技术或将其作为二次开发的基础。
DCloud 插件市场**
便于直接在 HBuilderX 中导入使用:
点击查看插件详情 https://ext.dcloud.net.cn/plugin?id=26606
Gitee 代码仓库**
获取完整源代码及提交历史:
访问仓库地址 https://gitee.com/mldxmy/simplelive
1. 项目背景与技术选型
近期启动了一个基于 UniApp 的直播系统开发项目。在明确需求后,迅速完成了技术栈选型并投入开发。整体架构旨在实现一套代码多端运行(H5、微信小程序、APP),涵盖首页、直播列表、个人中心等核心模块。
- 前端框架:UniApp
- 后端管理:FastAdmin
- 即时通讯(聊天服务):Workerman
- 目标平台:H5、微信小程序、原生 APP
2. 核心挑战与解决方案
虽然整体功能开发进展顺利,但在直播间页面的多端适配过程中遇到了三个关键技术瓶颈。以下是具体问题及最终的解决策略:
🔴 问题一:H5 端播放卡顿
- 现象描述:
在 H5 端调试时,进入直播间页面加载极慢,体验严重卡顿。 - 原因分析:
经排查,问题源于引用的第三方库 [hls.min.js]。该文件通过外网 CDN 引入,受网络波动影响较大,导致资源加载阻塞。 - ✅ 解决方案:
本地化部署。将 [hls.min.js] 下载至项目本地目录,改为本地引用。此举显著提升了加载速度,彻底解决了卡顿问题。注:H5 端采用
m3u8(HLS) 格式流,小程序端采用rtmp格式流。
🔴 问题二:APP 端页面层级错乱
- 现象描述:
H5 和小程序端运行正常,但打包至 APP 端时,直播间页面布局完全错乱,视频组件覆盖异常。 - 原因分析:
查阅文档后发现,这是由原生 APP 中视频组件的层级(Z-Index)导致的。试图在单一的 [.vue] 文件中通过条件编译兼容所有端(H5/小程序/APP)的方案,在处理复杂视频层级时存在天然局限。即便借助 AI 辅助调试三天,仍无法完美解决。 - ✅ 解决方案:
拆分代码策略。放弃“一套代码通吃”的执念,改为维护两套直播间代码:- [.vue] 文件:专用于 H5 和微信小程序。
- [.nvue]) 文件:专用于原生 APP(利用其原生渲染优势解决层级问题)。
🔴 问题三:APP 端推流功能实现困难
- 现象描述:
由于 H5 限制及微信小程序政策调整(不再开放主播推流权限),推流功能仅在 APP 端有实际需求。在完成 [nvue]) 页面基础功能后,尝试利用 AI 生成推流逻辑,耗时两天仍未成功,且导致代码逻辑混乱。 - 原因分析:
直播推流涉及底层摄像头权限、编码参数及推流协议,逻辑复杂度较高。完全依赖 AI 生成复杂业务逻辑容易导致代码结构不可控,维护成本激增。 - ✅ 解决方案:
模块化隔离开发。- 单独开发一个仅包含推流功能的独立页面,确保核心逻辑纯净可用。
- 验证成功后,再将该功能模块移植整合到主直播间页面中。
💡 经验总结:对于高复杂度的底层功能,AI 可作为辅助参考,但核心逻辑仍需人工把控,避免过度依赖导致代码失控。
3. 总结
本次开发经历表明,在跨端直播场景下,"因地制宜"比“强行统一”更有效。针对视频播放和推流等特殊场景,灵活采用 [.vue] 与 [.nvue] 分离的策略,以及模块化开发思路,是保障项目稳定落地的关键。
本项目前端代码已正式开源,旨在为开发者提供一套可参考的直播系统实现方案。欢迎各位同仁查阅源码、交流技术或将其作为二次开发的基础。
DCloud 插件市场**
便于直接在 HBuilderX 中导入使用:
点击查看插件详情 https://ext.dcloud.net.cn/plugin?id=26606
Gitee 代码仓库**
获取完整源代码及提交历史:
访问仓库地址 https://gitee.com/mldxmy/simplelive
uniapp 私钥证书(p12)导入失败 好像是是用的openssl v3
客户那边好像是是用的openssl v3 https://app.liuyingyong.cn/build/errorLog/af981a00-24d7-11f1-8a69-45fd92a17fd0 这个是日志
客户那边好像是是用的openssl v3 https://app.liuyingyong.cn/build/errorLog/af981a00-24d7-11f1-8a69-45fd92a17fd0 这个是日志
基于vite8.0+vue3+openai仿写deepseek网页版ai智能流式聊天模板
vite8-deepseek-webai:最新前端技术栈vite8.0、vue3.5、arco-design、markdown、openai调用deepseek-v3.2聊天大模型。支持light+dark主题、深度思考、代码高亮复制代码/下载、katex公式、mermaid图表等功能。
项目技术知识
- 开发工具:vscode
- 前端框架:vite8.0+vue3.5.30+vue-router5.0.3
- 智能大模型:deepseek-v3.2 + openai
- 组件库:arco-design2.57.0
- 状态管理:pinia3.0.4
- markdown插件:markdown-it14.1.0
- 代码高亮插件:highlight.js11.11.1
- katex公式:plugin-katex0.25.1
项目结构目录
使用最新正式版vite8.0搭建项目,集成deepseek api大模型接口,vue3 setup语法编码开发。
vite8-vue3-webai网页版ai对话项目已经发布到我的原创作品集,欢迎下载使用哈!
Vite8+DeepSeek+Vue3.5搭建Web版AI智能聊天对话助理
如果对项目具体的实现感兴趣,可以看看下面这篇文章。
Vite8+DeepSeek网页版AI助手|vue3+arco本地web版ai流式打字问答系统
热文推荐
uniapp+deepseek流式ai助理|uniapp+vue3对接deepseek三端Ai问答模板
vite8.0+deepseek流式ai模板|vue3.5+vant4+markdown打字输出ai助手
tauri2.10+deepseek+vite7客户端ai系统|Tauri2+Vue3.5桌面AI程序Exe
electron39-vue3ai电脑端AI模板|electron39+deepseek+vite7聊天ai应用
Electron38-Vue3OS客户端OS系统|vite7+electron38+arco桌面os后台管理
Electron38-Wechat电脑端聊天|vite7+electron38仿微信桌面端聊天系统
electron38-admin桌面端后台|Electron38+Vue3+ElementPlus管理系统
最新版uniapp+vue3+uv-ui跨三端短视频+直播+聊天【H5+小程序+App端】
最新版uni-app+vue3+uv-ui跨三端仿微信app聊天应用【h5+小程序+app端】
Tauri2.9+Vue3桌面版OS系统|vite7+tauri2+arcoDesign电脑端os后台模板
Tauri2.8+Vue3聊天系统|vite7+tauri2+element-plus客户端仿微信聊天程序
Tauri2-Vite7Admin客户端管理后台|tauri2.9+vue3+element-plus后台系统
uniapp-vue3-os手机oa系统|uni-app+vue3跨三端os后台管理模板
Flutter3-MacOS桌面OS系统|flutter3.32+window_manager客户端OS模板
最新研发flutter3.27+bitsdojo_window+getx客户端仿微信聊天Exe应用
最新版Flutter3.32+Dart3.8跨平台仿微信app聊天界面|朋友圈
vite8-deepseek-webai:最新前端技术栈vite8.0、vue3.5、arco-design、markdown、openai调用deepseek-v3.2聊天大模型。支持light+dark主题、深度思考、代码高亮复制代码/下载、katex公式、mermaid图表等功能。
项目技术知识
- 开发工具:vscode
- 前端框架:vite8.0+vue3.5.30+vue-router5.0.3
- 智能大模型:deepseek-v3.2 + openai
- 组件库:arco-design2.57.0
- 状态管理:pinia3.0.4
- markdown插件:markdown-it14.1.0
- 代码高亮插件:highlight.js11.11.1
- katex公式:plugin-katex0.25.1
项目结构目录
使用最新正式版vite8.0搭建项目,集成deepseek api大模型接口,vue3 setup语法编码开发。
vite8-vue3-webai网页版ai对话项目已经发布到我的原创作品集,欢迎下载使用哈!
Vite8+DeepSeek+Vue3.5搭建Web版AI智能聊天对话助理
如果对项目具体的实现感兴趣,可以看看下面这篇文章。
Vite8+DeepSeek网页版AI助手|vue3+arco本地web版ai流式打字问答系统
热文推荐
uniapp+deepseek流式ai助理|uniapp+vue3对接deepseek三端Ai问答模板
vite8.0+deepseek流式ai模板|vue3.5+vant4+markdown打字输出ai助手
tauri2.10+deepseek+vite7客户端ai系统|Tauri2+Vue3.5桌面AI程序Exe
electron39-vue3ai电脑端AI模板|electron39+deepseek+vite7聊天ai应用
Electron38-Vue3OS客户端OS系统|vite7+electron38+arco桌面os后台管理
Electron38-Wechat电脑端聊天|vite7+electron38仿微信桌面端聊天系统
electron38-admin桌面端后台|Electron38+Vue3+ElementPlus管理系统
最新版uniapp+vue3+uv-ui跨三端短视频+直播+聊天【H5+小程序+App端】
最新版uni-app+vue3+uv-ui跨三端仿微信app聊天应用【h5+小程序+app端】
Tauri2.9+Vue3桌面版OS系统|vite7+tauri2+arcoDesign电脑端os后台模板
Tauri2.8+Vue3聊天系统|vite7+tauri2+element-plus客户端仿微信聊天程序
Tauri2-Vite7Admin客户端管理后台|tauri2.9+vue3+element-plus后台系统
uniapp-vue3-os手机oa系统|uni-app+vue3跨三端os后台管理模板
Flutter3-MacOS桌面OS系统|flutter3.32+window_manager客户端OS模板
最新研发flutter3.27+bitsdojo_window+getx客户端仿微信聊天Exe应用
最新版Flutter3.32+Dart3.8跨平台仿微信app聊天界面|朋友圈
蓝牙搜索与设备发现监听正确搭配
蓝牙搜索与设备发现是蓝牙操作的首要需求,下面是常见的封装实现:先启动搜索,再监听发现设备
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: true,
success: () => {
uni.onBluetoothDeviceFound(res => {
console.log("startYasee =>", res);
});
}
});
上面可以打开搜索,并发现设备,不过这个有个比较坑的地方,当stopBluetoothDevicesDiscovery停止搜索时,由于没有关闭监听uni.onBluetoothDeviceFound的API,导致这个设备发现监听一直在内存中运行。可见一个帖子https://ask.dcloud.net.cn/question/183922
问题
1.多调用一次就多执行一次onBluetoothDeviceFound,从而多一次重复上报设备,而且上报时间是一样的,需要处理重复相同的上报,给程序带来处理压力
2.由于onBluetoothDeviceFound监听一直未关闭,多次监听在内存中导致程序占用过高,易导致程序卡顿。
解决方案
uni.onBluetoothDeviceFound只执行一次,通过uni.$emit和uni.$on实现发现的设备上报,下面是封装的实现
/**
* 开始搜寻蓝牙设备【核心】
* 此操作比较耗费系统资源,请在搜索并连接到设备后调用uni.stopBluetoothDevicesDiscovery方法停止搜索
* @param {Object} options 同官方配置选项
* @param {Function} callback 回调函数,设置则接收uni.onBluetoothDeviceFound发现设备
*/
export function start(options = {}, callback = () => {}) {
return new Promise((resolve, reject) => {
const discovery = function() {
return new Promise((resolve, reject) => {
options.allowDuplicatesKey = true; //是否允许重复上报同一设备,若未能获取advertisData则需要找开
options.success = res => {
if (callback) {
// uni.onBluetoothDeviceFound没有关闭监听API,所以使用uni.$emit和uni.$on来解决重复监听问题
// uni.onBluetoothDeviceFound(res2 => callback(res2));
found();
uni.$off("bluetooth-device-found");
uni.$on("bluetooth-device-found", res2 => callback(res2));
}
resolve(res);
};
options.fail = reject;
uni.startBluetoothDevicesDiscovery(options);
});
};
discovery().then(resolve).catch(reject);
});
}
// 通过uni.$emit和uni.$on来解决重复监听onBluetoothDeviceFound的问题
let foundable = false;
function found() {
if (!foundable) {
foundable = true;
uni.onBluetoothDeviceFound(res => uni.$emit("bluetooth-device-found", res));
}
} 蓝牙搜索与设备发现是蓝牙操作的首要需求,下面是常见的封装实现:先启动搜索,再监听发现设备
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: true,
success: () => {
uni.onBluetoothDeviceFound(res => {
console.log("startYasee =>", res);
});
}
});
上面可以打开搜索,并发现设备,不过这个有个比较坑的地方,当stopBluetoothDevicesDiscovery停止搜索时,由于没有关闭监听uni.onBluetoothDeviceFound的API,导致这个设备发现监听一直在内存中运行。可见一个帖子https://ask.dcloud.net.cn/question/183922
问题
1.多调用一次就多执行一次onBluetoothDeviceFound,从而多一次重复上报设备,而且上报时间是一样的,需要处理重复相同的上报,给程序带来处理压力
2.由于onBluetoothDeviceFound监听一直未关闭,多次监听在内存中导致程序占用过高,易导致程序卡顿。
解决方案
uni.onBluetoothDeviceFound只执行一次,通过uni.$emit和uni.$on实现发现的设备上报,下面是封装的实现
/**
* 开始搜寻蓝牙设备【核心】
* 此操作比较耗费系统资源,请在搜索并连接到设备后调用uni.stopBluetoothDevicesDiscovery方法停止搜索
* @param {Object} options 同官方配置选项
* @param {Function} callback 回调函数,设置则接收uni.onBluetoothDeviceFound发现设备
*/
export function start(options = {}, callback = () => {}) {
return new Promise((resolve, reject) => {
const discovery = function() {
return new Promise((resolve, reject) => {
options.allowDuplicatesKey = true; //是否允许重复上报同一设备,若未能获取advertisData则需要找开
options.success = res => {
if (callback) {
// uni.onBluetoothDeviceFound没有关闭监听API,所以使用uni.$emit和uni.$on来解决重复监听问题
// uni.onBluetoothDeviceFound(res2 => callback(res2));
found();
uni.$off("bluetooth-device-found");
uni.$on("bluetooth-device-found", res2 => callback(res2));
}
resolve(res);
};
options.fail = reject;
uni.startBluetoothDevicesDiscovery(options);
});
};
discovery().then(resolve).catch(reject);
});
}
// 通过uni.$emit和uni.$on来解决重复监听onBluetoothDeviceFound的问题
let foundable = false;
function found() {
if (!foundable) {
foundable = true;
uni.onBluetoothDeviceFound(res => uni.$emit("bluetooth-device-found", res));
}
} 收起阅读 »

























