HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

鸿图初展,蒙学破茧:与uniapp-x和tmui4.0共闯鸿蒙Next实战录

鸿蒙征文

相识篇

2025年02月24日,这一天难忘。随着鸿蒙生态的蓬勃发展,我和小伙伴们也在不断学习、探索、交流中,得知了uniapp-x的官网:https://doc.dcloud.net.cn/uni-app-x地址, 这个网站里我读懂了2个很重要的信息。
其一:uni-app x,是下一代 uni-app,是一个跨平台应用开发引擎。uts在Android平台编译为kotlin、在iOS平台编译为swift、在鸿蒙next平台上编译为ArkTS、在Web和小程序平台编译为js。官方重心便宜至uniapp-x上科技的创新。
其二:在这个网页下面有个插件生态部分描述了:TMUI4.0:高品质UI库,插件大赛一等奖。

综合这两方面原因:我也深知原来无任何app开发经验的小白,必须需要借助uniapp-x和tmui4.0才有逆袭的机会。从此刻开始就暗自下定决心利用工作之外的时间冲刺鸿蒙Next的app开发。

后盾篇

利用工作日之外的时间,我还必须跟家庭表达我的真实想法要冲刺鸿蒙Next的app开发。跟他们沟通说:鸿蒙Next的app将来一定会越来越有前景和市场,尤其是在Ai时代背景下,做好鸿蒙Next的app开发,一定会有很大的发展。最终得到已怀孕老婆的支持,我也终于可以开始了。

学习过程

1. 笔记分享

2025年02月25日:
1.采购tmUI 4.0的Vip资料,并且官方给了一些资料。计划先学习下uniapp-x官方资料。

2.编译到微信时,

  1. 如果提示xanimations页面不存在,就改成下pages.json中的名称改成页面的xAnimationS
  2. 如果hbx编译成功后打不开微信工具,就自己手动打开工具,点导入项目,导入你根目录中的unpackage/dis/dev/mp-weixin
    然后模拟器上方把热重载关闭下。
  3. 接着可能报echart.js,mqtt.js文件不存在。需要你手动复制到mp-weixin目录内,这个看我常文档:见问题页面。就是复制到对应目录结构内。
    解释下原因:
    第1点,是我复制的时候,是复制老的页面,名称忘记改了。
    第2点,和第3点是同一个原因:hbx sdk 目录不支持import * as xxx这种 amd 类的js模块。只能改成require(xxx)导入,但改成这种导入方式后,hbx sdk编译时,又不会主动把文件复制到编译目录,所以需要手动导入
    第3点为什么我 要把ecahrt.js单独放到页面上:原因为了包大小,因为只2mb限制。而且为了全量功能,单独放page内,它这个依赖会打到分包中,不会在主包中。这样。我的demo发布120个页面共6-10mb大小,也能发布。同理你们的也是

2025年02月26日:
1.学习配置uts的开发环境

2025年03月01日:
1.xRequest.uts的警告消除,已给官方反馈。

  1. 2025年03月15日:
    1.泛型使用过一次就会丢失类型,需要重新的生成和定义。
    2.import的页面组件,不能放到pages.json里面,组件就是组件,页面就是页面,必须分清楚。

3.2025年03月18日:
1.安装雷电模拟器9
2.制作自定义基座。
3.方便调试代码使用,依然是uts类型不熟练导致的,明天接着学习。我一定要拿下apk。
4.最终换成mumu模拟器来实现了,自定义打包,自定义基座。

2025年03月20日:
写插件流程
先预估与兼容平台:安卓,ios,web,mp
如果都有,可以先开发web,mp
再写安卓
再写ios( ios 相对简单),

2025年04月07日:
input插件,有个adjust-position参数可以控制是否键盘弹起时,是否自动上推页面。

2025年04月29日:MCP+TRAE
@Run🍀 你去试下看看https://context7.com/tmzdy888/tmui4-doc
2025年5月26日:解决x跨域的问题

2025年6月5日:
用require导入的模块在小程序这边。。x编译时直接自动忽略

2025年6月10日:Tmui4.0跨域本地H5跨域问题处理

参考官方网址新增文件:vite.config.js
 
https://doc.dcloud.net.cn/uni-app-x/web/#dev-server
export default defineConfig({
plugins: [uni()],
server: {
port: 9527,
proxy: {
'/api': {
target: 'https://zscXXXXX/api',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
}
},
}
});
 
修改下请求的开发环境,解决本地H5跨域的问题。解放后台。

2025年06月11日:

1.2025年05月03日:
首次尝鲜安装鸿蒙Next的开发工具,安装模拟器,然后成功运行一个示例工程。

2.2025年08月16日:
如果有鸿蒙next有2个软件,一个是:我的开发案例、HMOS代码工坊
Vip群开发的例子和demo:
plugin/README.md · HarmonyOS-Cases/Cases - Gitee.com

App上架的流程总结:
步骤一:首页需要在腾讯云/百度云等服务商进行app的ICP备案工作。
步骤二:完成App的上架工作(暂不需要app软著),上架时需要校验ICP备案结果。
步骤三:完成App上架后获取下载链接,在开通商户服务时需要找个地址信息。
步骤四:等待官方评审是否达到要求。

以上均是日常学习过程中的笔记真实分享,虽杂乱,但字里行间透露一种真诚,坚持、努力!

2. 给官方提出一个ISSUE:

https://issues.dcloud.net.cn/pages/issues/detail?id=22088,因此问题影响还比较大,最终得官方得认同后,及时修复完成。

3.参与开源项目uni-x-ai组件的使用和测试任务,也提了下优化的功能点。

活动契机:2025年激励计划

tmui4.0的官方群里得知,2025年的激励计划将会推出一个“鸿蒙应用开发大赛”,奖项将会奖励优秀的应用。我立马报名参加了。我登陆活动页面:https://developer.huawei.com/consumer/cn/activity/harmonyos-incentive/2025 我知道这是一次很好的机会,让自己学习到的uniapp-x语法和tmui4.0组件知识做一个综合项目,也算是将近200天的课外学习的付出有个结果。

我决定将开发一款鸿蒙next的app:《小张Ai运维智能体》,将此作品作为2025年激励计划的参赛作品。比赛期间:
1.我又学习到如何进行app备案,
2.如何进行app的自动化测试,
3.如何在鸿蒙开发平台agc上进行app包的调试工作。
4.如何进行发布和审核,
5.如何进行app的迭代更新。

截止写这篇文章时,我一个人已经独自完成app的前后端的开发工作,正在接收鸿蒙官方的审核。审核通过后将会发布应用市场,也衷心希望app能顺利让大家在鸿蒙Next平台上使用。

总结

冥冥之中:就在2025年11月02日tmui4.0的官方群里得知,官方有个https://ask.dcloud.net.cn/article/42142(【鸿蒙征文】星光不负,码向未来!分享你的uni-app鸿蒙开发实践,赢取精美好礼!)活动,我想着这又是一个很好的契机,所以我报名参加了。我就将过去真实的记录一起分享给大家,这也是对过去的努力做一个总结,也是开启下阶段继续学习鸿蒙next的动力,为生态发展贡献自己的一份力量。
望有更多的小伙伴们加入鸿蒙Next的开发,共同学习,共同推动鸿蒙生态的发展。也希望uniapp-x和tmui4.0做的越来越好。

后记

这段旅程,恰如千里之行。幸于起点处,得遇良器与明灯,更收获了温暖的港湾。
鸿图已展,蒙学初成。愿这份始于足下的热忱,能照亮更远的征途。
贵在坚持,与君共勉。

继续阅读 »

相识篇

2025年02月24日,这一天难忘。随着鸿蒙生态的蓬勃发展,我和小伙伴们也在不断学习、探索、交流中,得知了uniapp-x的官网:https://doc.dcloud.net.cn/uni-app-x地址, 这个网站里我读懂了2个很重要的信息。
其一:uni-app x,是下一代 uni-app,是一个跨平台应用开发引擎。uts在Android平台编译为kotlin、在iOS平台编译为swift、在鸿蒙next平台上编译为ArkTS、在Web和小程序平台编译为js。官方重心便宜至uniapp-x上科技的创新。
其二:在这个网页下面有个插件生态部分描述了:TMUI4.0:高品质UI库,插件大赛一等奖。

综合这两方面原因:我也深知原来无任何app开发经验的小白,必须需要借助uniapp-x和tmui4.0才有逆袭的机会。从此刻开始就暗自下定决心利用工作之外的时间冲刺鸿蒙Next的app开发。

后盾篇

利用工作日之外的时间,我还必须跟家庭表达我的真实想法要冲刺鸿蒙Next的app开发。跟他们沟通说:鸿蒙Next的app将来一定会越来越有前景和市场,尤其是在Ai时代背景下,做好鸿蒙Next的app开发,一定会有很大的发展。最终得到已怀孕老婆的支持,我也终于可以开始了。

学习过程

1. 笔记分享

2025年02月25日:
1.采购tmUI 4.0的Vip资料,并且官方给了一些资料。计划先学习下uniapp-x官方资料。

2.编译到微信时,

  1. 如果提示xanimations页面不存在,就改成下pages.json中的名称改成页面的xAnimationS
  2. 如果hbx编译成功后打不开微信工具,就自己手动打开工具,点导入项目,导入你根目录中的unpackage/dis/dev/mp-weixin
    然后模拟器上方把热重载关闭下。
  3. 接着可能报echart.js,mqtt.js文件不存在。需要你手动复制到mp-weixin目录内,这个看我常文档:见问题页面。就是复制到对应目录结构内。
    解释下原因:
    第1点,是我复制的时候,是复制老的页面,名称忘记改了。
    第2点,和第3点是同一个原因:hbx sdk 目录不支持import * as xxx这种 amd 类的js模块。只能改成require(xxx)导入,但改成这种导入方式后,hbx sdk编译时,又不会主动把文件复制到编译目录,所以需要手动导入
    第3点为什么我 要把ecahrt.js单独放到页面上:原因为了包大小,因为只2mb限制。而且为了全量功能,单独放page内,它这个依赖会打到分包中,不会在主包中。这样。我的demo发布120个页面共6-10mb大小,也能发布。同理你们的也是

2025年02月26日:
1.学习配置uts的开发环境

2025年03月01日:
1.xRequest.uts的警告消除,已给官方反馈。

  1. 2025年03月15日:
    1.泛型使用过一次就会丢失类型,需要重新的生成和定义。
    2.import的页面组件,不能放到pages.json里面,组件就是组件,页面就是页面,必须分清楚。

3.2025年03月18日:
1.安装雷电模拟器9
2.制作自定义基座。
3.方便调试代码使用,依然是uts类型不熟练导致的,明天接着学习。我一定要拿下apk。
4.最终换成mumu模拟器来实现了,自定义打包,自定义基座。

2025年03月20日:
写插件流程
先预估与兼容平台:安卓,ios,web,mp
如果都有,可以先开发web,mp
再写安卓
再写ios( ios 相对简单),

2025年04月07日:
input插件,有个adjust-position参数可以控制是否键盘弹起时,是否自动上推页面。

2025年04月29日:MCP+TRAE
@Run🍀 你去试下看看https://context7.com/tmzdy888/tmui4-doc
2025年5月26日:解决x跨域的问题

2025年6月5日:
用require导入的模块在小程序这边。。x编译时直接自动忽略

2025年6月10日:Tmui4.0跨域本地H5跨域问题处理

参考官方网址新增文件:vite.config.js
 
https://doc.dcloud.net.cn/uni-app-x/web/#dev-server
export default defineConfig({
plugins: [uni()],
server: {
port: 9527,
proxy: {
'/api': {
target: 'https://zscXXXXX/api',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
}
},
}
});
 
修改下请求的开发环境,解决本地H5跨域的问题。解放后台。

2025年06月11日:

1.2025年05月03日:
首次尝鲜安装鸿蒙Next的开发工具,安装模拟器,然后成功运行一个示例工程。

2.2025年08月16日:
如果有鸿蒙next有2个软件,一个是:我的开发案例、HMOS代码工坊
Vip群开发的例子和demo:
plugin/README.md · HarmonyOS-Cases/Cases - Gitee.com

App上架的流程总结:
步骤一:首页需要在腾讯云/百度云等服务商进行app的ICP备案工作。
步骤二:完成App的上架工作(暂不需要app软著),上架时需要校验ICP备案结果。
步骤三:完成App上架后获取下载链接,在开通商户服务时需要找个地址信息。
步骤四:等待官方评审是否达到要求。

以上均是日常学习过程中的笔记真实分享,虽杂乱,但字里行间透露一种真诚,坚持、努力!

2. 给官方提出一个ISSUE:

https://issues.dcloud.net.cn/pages/issues/detail?id=22088,因此问题影响还比较大,最终得官方得认同后,及时修复完成。

3.参与开源项目uni-x-ai组件的使用和测试任务,也提了下优化的功能点。

活动契机:2025年激励计划

tmui4.0的官方群里得知,2025年的激励计划将会推出一个“鸿蒙应用开发大赛”,奖项将会奖励优秀的应用。我立马报名参加了。我登陆活动页面:https://developer.huawei.com/consumer/cn/activity/harmonyos-incentive/2025 我知道这是一次很好的机会,让自己学习到的uniapp-x语法和tmui4.0组件知识做一个综合项目,也算是将近200天的课外学习的付出有个结果。

我决定将开发一款鸿蒙next的app:《小张Ai运维智能体》,将此作品作为2025年激励计划的参赛作品。比赛期间:
1.我又学习到如何进行app备案,
2.如何进行app的自动化测试,
3.如何在鸿蒙开发平台agc上进行app包的调试工作。
4.如何进行发布和审核,
5.如何进行app的迭代更新。

截止写这篇文章时,我一个人已经独自完成app的前后端的开发工作,正在接收鸿蒙官方的审核。审核通过后将会发布应用市场,也衷心希望app能顺利让大家在鸿蒙Next平台上使用。

总结

冥冥之中:就在2025年11月02日tmui4.0的官方群里得知,官方有个https://ask.dcloud.net.cn/article/42142(【鸿蒙征文】星光不负,码向未来!分享你的uni-app鸿蒙开发实践,赢取精美好礼!)活动,我想着这又是一个很好的契机,所以我报名参加了。我就将过去真实的记录一起分享给大家,这也是对过去的努力做一个总结,也是开启下阶段继续学习鸿蒙next的动力,为生态发展贡献自己的一份力量。
望有更多的小伙伴们加入鸿蒙Next的开发,共同学习,共同推动鸿蒙生态的发展。也希望uniapp-x和tmui4.0做的越来越好。

后记

这段旅程,恰如千里之行。幸于起点处,得遇良器与明灯,更收获了温暖的港湾。
鸿图已展,蒙学初成。愿这份始于足下的热忱,能照亮更远的征途。
贵在坚持,与君共勉。

收起阅读 »

鸿蒙webview踩坑实录

鸿蒙next 鸿蒙征文

鸿蒙WebView踩坑实录:这些问题我全遇到过!

做鸿蒙开发时,只要涉及加载网页、H5页面,就离不开WebView组件。但用起来总遇到各种小毛病——页面加载不出来、交互没反应、样式乱了,头都大了。今天把这些常见问题和解决办法整理出来,全是实战经验,新手照着做能少走很多弯路。

一、WebView连网页都加载不出来,咋整?

  1. “网页无法打开,网络错误”
    大概率是没加网络权限!鸿蒙应用要访问网络,必须在配置文件里声明权限,不然WebView连不上网。
    解决:打开module.json5文件,在requestPermissions里加网络权限,代码如下:

    "requestPermissions": [  
    {  
    "name": "ohos.permission.INTERNET", // 网络权限  
    "reason": "需要访问网络加载网页",  
    "usedScene": {  
      "ability": ["EntryAbility"],  
      "when": "always"  
    }  
    }  
    ]

    加完后重启项目,再试加载网页(比如加载“https://www.baidu.com”)。

  2. 加载本地HTML文件,显示“文件不存在”
    本地HTML、CSS、JS文件要放在正确的目录里,不然WebView找不到。鸿蒙里本地资源得放在main_pages同级的rawfile文件夹下(没有就自己建)。
    解决:

    • 新建src/main/rawfile文件夹,把本地HTML文件(比如index.html)放进去;
    • 加载时用$rawfile('index.html')获取路径,示例代码:
      Web({ src: $rawfile('index.html') })  
      .width('100%')  
      .height('100%')
  3. HTTPS网页加载不了,提示“证书错误”
    有些小众网站的HTTPS证书不被鸿蒙信任,WebView会拦截加载。开发测试时可以临时跳过证书验证(正式项目不建议,有安全风险)。
    解决:在Web组件里加onCertificateError回调,返回true跳过验证:

    Web({ src: "https://xxx.com" })  
    .onCertificateError(() => {  
    return true; // 跳过证书验证  
    })

二、WebView和H5交互没反应,咋回事?

  1. H5调用鸿蒙原生方法,没动静
    得先给WebView设置“JS桥”,把原生方法暴露给H5,不然H5调用不到。
    解决:
    • 第一步:用WebController创建控制器,通过registerJavaScriptObject暴露原生方法;
    • 第二步:H5里用window.xxx调用暴露的方法。
      示例代码(鸿蒙侧):
private webController: WebController = new WebController();  
build() {  
  Web({   
    src: "https://xxx.com",   
    controller: this.webController   
  })  
    .onPageEnd(() => {  
      this.webController.registerJavaScriptObject("鸿蒙原生对象名", {  
        原生方法名: (param: string) => {  
          console.log("H5传的参数:" + param);  
        }  
      });  
    })  
}

H5侧调用代码:

// 调用鸿蒙暴露的方法,传参数  
window.鸿蒙原生对象名.原生方法名("我是H5的参数");
  1. 鸿蒙调用H5方法,没反应
    要确保H5里的方法已经定义好,而且要在WebView页面加载完成后再调用,不然会找不到方法。
    解决:在onPageEnd(页面加载完成)回调里调用H5方法,用runJavaScript
    Web({ src: "https://xxx.com", controller: this.webController })  
    .onPageEnd(() => {  
    // 调用H5里的方法,传参数(如果需要)  
    this.webController.runJavaScript(`H5方法名("我是鸿蒙的参数")`);  
    })

三、WebView样式乱了、适配有问题,咋调?

  1. WebView里的文字太小/太大,适配不对
    默认WebView的缩放比例可能和设备不匹配,得设置“视口(viewport)”让H5自适应鸿蒙设备屏幕。
    解决:两种方式选一种:

    • 方式1:H5里加viewport meta标签(推荐,从源头适配):
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
    • 方式2:鸿蒙侧用setWebConfig设置缩放,示例:
      Web({ src: "https://xxx.com", controller: this.webController })  
      .setWebConfig({  
      viewport: {  
      initialScale: 100, // 初始缩放比例100%  
      width: WebViewportWidth.DEVICE_WIDTH // 视口宽度=设备宽度  
      }  
      })
  2. WebView高度撑不满/滚不动,内容显示不全
    默认WebView高度可能是“包裹内容”,如果H5内容多,会显示不全;或者没开滚动,导致滚不动。
    解决:

    • 给WebView设固定高度(比如height('80%'))或自适应父容器高度;
    • 开滚动:在WebView外层包个ListScroll组件,示例:
Scroll() {  
  Web({ src: "https://xxx.com" })  
    .width('100%')  
    .height('auto') // 自适应内容高度  
}  
.width('100%')  
.height('100%')

四、其他小坑,遇到了别慌

  1. WebView加载页面时,白屏时间太长
    可以加个“加载中”提示,提升用户体验。用onPageStart(开始加载)显示加载框,onPageEnd(加载完成)隐藏:
@State isLoading: boolean = false;  

build() {  
  Column() {  
    // 加载中提示  
    if (this.isLoading) {  
      Text("加载中...")  
        .margin(10)  
    }  
    Web({ src: "https://xxx.com" })  
      .onPageStart(() => {  
        this.isLoading = true;  
      })  
      .onPageEnd(() => {  
        this.isLoading = false;  
      })  
  }  
}
  1. WebView里的链接点了没反应,无法跳转
    默认WebView支持跳转,但如果是“_blank”(新窗口打开)的链接,鸿蒙会拦截。要处理的话,用onUrlLoadIntercept回调,手动处理跳转:
    Web({ src: "https://xxx.com" })  
    .onUrlLoadIntercept((event) => {  
    const newUrl = event.url;  
    // 手动加载新链接  
    this.webController.loadUrl(newUrl);  
    return true; // 拦截默认跳转,用自己的逻辑  
    })

以上就是WebView最常遇到的问题,其实核心就三点:权限要加对、交互要设桥、适配要调视口。如果还有其他坑,评论区可以一起交流,踩坑多了,自然就熟练了!

继续阅读 »

鸿蒙WebView踩坑实录:这些问题我全遇到过!

做鸿蒙开发时,只要涉及加载网页、H5页面,就离不开WebView组件。但用起来总遇到各种小毛病——页面加载不出来、交互没反应、样式乱了,头都大了。今天把这些常见问题和解决办法整理出来,全是实战经验,新手照着做能少走很多弯路。

一、WebView连网页都加载不出来,咋整?

  1. “网页无法打开,网络错误”
    大概率是没加网络权限!鸿蒙应用要访问网络,必须在配置文件里声明权限,不然WebView连不上网。
    解决:打开module.json5文件,在requestPermissions里加网络权限,代码如下:

    "requestPermissions": [  
    {  
    "name": "ohos.permission.INTERNET", // 网络权限  
    "reason": "需要访问网络加载网页",  
    "usedScene": {  
      "ability": ["EntryAbility"],  
      "when": "always"  
    }  
    }  
    ]

    加完后重启项目,再试加载网页(比如加载“https://www.baidu.com”)。

  2. 加载本地HTML文件,显示“文件不存在”
    本地HTML、CSS、JS文件要放在正确的目录里,不然WebView找不到。鸿蒙里本地资源得放在main_pages同级的rawfile文件夹下(没有就自己建)。
    解决:

    • 新建src/main/rawfile文件夹,把本地HTML文件(比如index.html)放进去;
    • 加载时用$rawfile('index.html')获取路径,示例代码:
      Web({ src: $rawfile('index.html') })  
      .width('100%')  
      .height('100%')
  3. HTTPS网页加载不了,提示“证书错误”
    有些小众网站的HTTPS证书不被鸿蒙信任,WebView会拦截加载。开发测试时可以临时跳过证书验证(正式项目不建议,有安全风险)。
    解决:在Web组件里加onCertificateError回调,返回true跳过验证:

    Web({ src: "https://xxx.com" })  
    .onCertificateError(() => {  
    return true; // 跳过证书验证  
    })

二、WebView和H5交互没反应,咋回事?

  1. H5调用鸿蒙原生方法,没动静
    得先给WebView设置“JS桥”,把原生方法暴露给H5,不然H5调用不到。
    解决:
    • 第一步:用WebController创建控制器,通过registerJavaScriptObject暴露原生方法;
    • 第二步:H5里用window.xxx调用暴露的方法。
      示例代码(鸿蒙侧):
private webController: WebController = new WebController();  
build() {  
  Web({   
    src: "https://xxx.com",   
    controller: this.webController   
  })  
    .onPageEnd(() => {  
      this.webController.registerJavaScriptObject("鸿蒙原生对象名", {  
        原生方法名: (param: string) => {  
          console.log("H5传的参数:" + param);  
        }  
      });  
    })  
}

H5侧调用代码:

// 调用鸿蒙暴露的方法,传参数  
window.鸿蒙原生对象名.原生方法名("我是H5的参数");
  1. 鸿蒙调用H5方法,没反应
    要确保H5里的方法已经定义好,而且要在WebView页面加载完成后再调用,不然会找不到方法。
    解决:在onPageEnd(页面加载完成)回调里调用H5方法,用runJavaScript
    Web({ src: "https://xxx.com", controller: this.webController })  
    .onPageEnd(() => {  
    // 调用H5里的方法,传参数(如果需要)  
    this.webController.runJavaScript(`H5方法名("我是鸿蒙的参数")`);  
    })

三、WebView样式乱了、适配有问题,咋调?

  1. WebView里的文字太小/太大,适配不对
    默认WebView的缩放比例可能和设备不匹配,得设置“视口(viewport)”让H5自适应鸿蒙设备屏幕。
    解决:两种方式选一种:

    • 方式1:H5里加viewport meta标签(推荐,从源头适配):
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
    • 方式2:鸿蒙侧用setWebConfig设置缩放,示例:
      Web({ src: "https://xxx.com", controller: this.webController })  
      .setWebConfig({  
      viewport: {  
      initialScale: 100, // 初始缩放比例100%  
      width: WebViewportWidth.DEVICE_WIDTH // 视口宽度=设备宽度  
      }  
      })
  2. WebView高度撑不满/滚不动,内容显示不全
    默认WebView高度可能是“包裹内容”,如果H5内容多,会显示不全;或者没开滚动,导致滚不动。
    解决:

    • 给WebView设固定高度(比如height('80%'))或自适应父容器高度;
    • 开滚动:在WebView外层包个ListScroll组件,示例:
Scroll() {  
  Web({ src: "https://xxx.com" })  
    .width('100%')  
    .height('auto') // 自适应内容高度  
}  
.width('100%')  
.height('100%')

四、其他小坑,遇到了别慌

  1. WebView加载页面时,白屏时间太长
    可以加个“加载中”提示,提升用户体验。用onPageStart(开始加载)显示加载框,onPageEnd(加载完成)隐藏:
@State isLoading: boolean = false;  

build() {  
  Column() {  
    // 加载中提示  
    if (this.isLoading) {  
      Text("加载中...")  
        .margin(10)  
    }  
    Web({ src: "https://xxx.com" })  
      .onPageStart(() => {  
        this.isLoading = true;  
      })  
      .onPageEnd(() => {  
        this.isLoading = false;  
      })  
  }  
}
  1. WebView里的链接点了没反应,无法跳转
    默认WebView支持跳转,但如果是“_blank”(新窗口打开)的链接,鸿蒙会拦截。要处理的话,用onUrlLoadIntercept回调,手动处理跳转:
    Web({ src: "https://xxx.com" })  
    .onUrlLoadIntercept((event) => {  
    const newUrl = event.url;  
    // 手动加载新链接  
    this.webController.loadUrl(newUrl);  
    return true; // 拦截默认跳转,用自己的逻辑  
    })

以上就是WebView最常遇到的问题,其实核心就三点:权限要加对、交互要设桥、适配要调视口。如果还有其他坑,评论区可以一起交流,踩坑多了,自然就熟练了!

收起阅读 »

HarmonyOS ide签名

鸿蒙next 鸿蒙征文

鸿蒙DevEco Studio签名那些事儿,避坑指南来了
做鸿蒙原生开发,签名绝对是绕不开的坎儿——没弄好签名,应用要么跑不起来,要么打包后安装不了。今天就把签名相关的常见问题、原因和解决办法捋清楚,都是实战里踩过的坑,新手看完能少走不少弯路。

一、签名文件创建不了,咋回事?

  1. “密钥库文件已存在”报错
    大概率是你选的保存路径里,已经有同名的.p12文件了。比如之前创建过签名,没删干净又选了同一个文件夹。
    解决:换个文件名,或者把路径里的旧签名文件删掉,再重新创建。

  2. “输入的密码不符合要求”
    DevEco Studio对签名密码有规则:至少8位,得包含大小写字母、数字,还得有特殊符号(比如!、@)。别图省事设简单密码,比如“12345678”肯定过不了。
    解决:按规则改密码,比如“Harmony123!”,记好别忘,后续用签名还要填。

  3. 创建时卡在“生成密钥”步骤
    一般是电脑性能不够,或者DevEco Studio后台进程卡了。尤其老电脑,生成密钥需要一点时间,别着急关窗口。
    解决:等1-2分钟,要是还没反应,重启DevEco Studio,关掉其他占内存的软件(比如浏览器多标签、微信)再试。

二、签名配置完,应用还是跑不起来?

  1. “未找到匹配的签名信息”提示
    常见两种情况:一是你配置的签名文件路径错了(比如文件移位置了,路径没更新);二是模块(module)没选对签名,比如entry模块用了release模块的签名。
    解决:

    • 打开“File > Project Structure > Project Settings > Modules”,选中要配置的模块(比如entry);
    • 点“Signing Configs”,检查“Key Store File”路径是不是正确,不对就重新选择签名文件;
    • 再去“Build Types”,确认“Signing Config”选的是你配置好的签名(比如debug)。
  2. 真机运行提示“签名验证失败”
    可能是你的真机没在鸿蒙开发者平台注册,或者签名里的“应用ID”和平台上注册的不一致。鸿蒙对真机调试的签名验证很严,必须一一对应。
    解决:

    • 先去“鸿蒙开发者平台 > 设备管理”,把真机的设备SN码加进去;
    • 再检查签名文件里的“Package Name”(应用ID),和平台上“应用管理”里的应用ID完全一致,多一个字母都不行。

三、打包时签名出问题,安装包用不了?

  1. Release包打包失败,提示“签名文件损坏”
    大概率是签名文件被误删、移动,或者之前创建时没保存好。比如把p12文件存在桌面,不小心删了,再打包就找不到了。
    解决:如果有备份,就重新选择备份的签名文件;没有备份的话,只能重新创建签名,然后去鸿蒙开发者平台更新应用的签名信息(不然之前的设备用不了新包)。

  2. 安装Release包到设备,提示“安装失败,签名不匹配”
    可能是你打包用的签名,和设备之前调试用的签名不是同一个。比如之前用debug签名跑过应用,现在换Release签名打包,安装时就会冲突。
    解决:先把设备上原来的应用卸载干净,再安装新的Release包;或者打包时用和调试时相同的签名(如果是测试用的话)。

四、签名相关小技巧,少踩坑!

  1. 签名文件别乱存,及时备份
    创建好的签名文件(比如myapp.p12),最好存在项目文件夹里,和项目一起备份(比如传到Git),别存在桌面或临时文件夹,丢了很麻烦。

  2. 密码记下来,别靠脑子
    签名的“密钥库密码”和“密钥密码”,建议存在记事本里,和签名文件放一起。很多人创建时图快,过后就忘,只能重新创建签名,还得改平台配置。

  3. debug和release签名分开用
    开发时用debug签名(方便调试,不需要平台注册),打包发布时用release签名(必须在平台注册,用于正式安装包),别混着用,不然容易出现验证失败的问题。

以上就是DevEco Studio签名最常见的问题和解决办法,其实只要注意路径、应用ID、设备注册这几点,签名就不算难。要是还有其他问题,评论区可以一起讨论,毕竟大家都是踩坑过来的!

继续阅读 »

鸿蒙DevEco Studio签名那些事儿,避坑指南来了
做鸿蒙原生开发,签名绝对是绕不开的坎儿——没弄好签名,应用要么跑不起来,要么打包后安装不了。今天就把签名相关的常见问题、原因和解决办法捋清楚,都是实战里踩过的坑,新手看完能少走不少弯路。

一、签名文件创建不了,咋回事?

  1. “密钥库文件已存在”报错
    大概率是你选的保存路径里,已经有同名的.p12文件了。比如之前创建过签名,没删干净又选了同一个文件夹。
    解决:换个文件名,或者把路径里的旧签名文件删掉,再重新创建。

  2. “输入的密码不符合要求”
    DevEco Studio对签名密码有规则:至少8位,得包含大小写字母、数字,还得有特殊符号(比如!、@)。别图省事设简单密码,比如“12345678”肯定过不了。
    解决:按规则改密码,比如“Harmony123!”,记好别忘,后续用签名还要填。

  3. 创建时卡在“生成密钥”步骤
    一般是电脑性能不够,或者DevEco Studio后台进程卡了。尤其老电脑,生成密钥需要一点时间,别着急关窗口。
    解决:等1-2分钟,要是还没反应,重启DevEco Studio,关掉其他占内存的软件(比如浏览器多标签、微信)再试。

二、签名配置完,应用还是跑不起来?

  1. “未找到匹配的签名信息”提示
    常见两种情况:一是你配置的签名文件路径错了(比如文件移位置了,路径没更新);二是模块(module)没选对签名,比如entry模块用了release模块的签名。
    解决:

    • 打开“File > Project Structure > Project Settings > Modules”,选中要配置的模块(比如entry);
    • 点“Signing Configs”,检查“Key Store File”路径是不是正确,不对就重新选择签名文件;
    • 再去“Build Types”,确认“Signing Config”选的是你配置好的签名(比如debug)。
  2. 真机运行提示“签名验证失败”
    可能是你的真机没在鸿蒙开发者平台注册,或者签名里的“应用ID”和平台上注册的不一致。鸿蒙对真机调试的签名验证很严,必须一一对应。
    解决:

    • 先去“鸿蒙开发者平台 > 设备管理”,把真机的设备SN码加进去;
    • 再检查签名文件里的“Package Name”(应用ID),和平台上“应用管理”里的应用ID完全一致,多一个字母都不行。

三、打包时签名出问题,安装包用不了?

  1. Release包打包失败,提示“签名文件损坏”
    大概率是签名文件被误删、移动,或者之前创建时没保存好。比如把p12文件存在桌面,不小心删了,再打包就找不到了。
    解决:如果有备份,就重新选择备份的签名文件;没有备份的话,只能重新创建签名,然后去鸿蒙开发者平台更新应用的签名信息(不然之前的设备用不了新包)。

  2. 安装Release包到设备,提示“安装失败,签名不匹配”
    可能是你打包用的签名,和设备之前调试用的签名不是同一个。比如之前用debug签名跑过应用,现在换Release签名打包,安装时就会冲突。
    解决:先把设备上原来的应用卸载干净,再安装新的Release包;或者打包时用和调试时相同的签名(如果是测试用的话)。

四、签名相关小技巧,少踩坑!

  1. 签名文件别乱存,及时备份
    创建好的签名文件(比如myapp.p12),最好存在项目文件夹里,和项目一起备份(比如传到Git),别存在桌面或临时文件夹,丢了很麻烦。

  2. 密码记下来,别靠脑子
    签名的“密钥库密码”和“密钥密码”,建议存在记事本里,和签名文件放一起。很多人创建时图快,过后就忘,只能重新创建签名,还得改平台配置。

  3. debug和release签名分开用
    开发时用debug签名(方便调试,不需要平台注册),打包发布时用release签名(必须在平台注册,用于正式安装包),别混着用,不然容易出现验证失败的问题。

以上就是DevEco Studio签名最常见的问题和解决办法,其实只要注意路径、应用ID、设备注册这几点,签名就不算难。要是还有其他问题,评论区可以一起讨论,毕竟大家都是踩坑过来的!

收起阅读 »

鸿蒙原生开发经验分享大纲

鸿蒙征文

鸿蒙原生开发常见问题汇总,纯干货分享!
咱直接开门见山,聊一聊鸿蒙原生开发里那些常遇到的麻烦事儿,顺便也讲讲对应的解决办法,不管你是刚接触鸿蒙开发的新手,还是已经有一定经验的开发者,希望这篇文章都能帮你少走点弯路。

一、环境搭建

  1. DevEco Studio安装失败:可能是电脑的系统环境不满足要求,比如内存不够、磁盘空间不足,或者是安装包下载不完整。解决办法就是先检查系统配置,清理出足够的内存和磁盘空间,然后重新下载安装包,最好从官方正规渠道下载,下载的时候确保网络稳定。
  2. 模拟器启动不了:电脑没开启虚拟化技术是常见原因,还有可能是装了像360安全卫士、火绒这类安全软件,把模拟器的服务禁用了,也可能是模拟器镜像下载不完整或者损坏。解决步骤如下:先重启电脑,进BIOS把虚拟化技术打开(不同品牌电脑进BIOS的快捷键不一样,联想一般按F2,惠普按F10 ,戴尔按F2,华硕按Del );然后把安全软件里拦截模拟器的功能关掉;最后在DevEco Studio里把原来下载的模拟器镜像删了重新下载。

二、代码编写

  1. ArkTS语法错误:ArkTS是静态类型语言,类型不匹配就很容易出错,比如你定义一个变量是数字类型,结果后面给它赋值成字符串了。还有就是API调用不当,鸿蒙的API在不同版本可能有变化。解决办法就是写代码的时候多注意类型声明,利用好IDE的自动补全和类型提示功能,调用API之前先去官方文档确认一下版本和用法。
  2. 函数调用没反应:有可能是函数参数传错了,或者函数内部逻辑有问题。遇到这种情况,先检查参数的类型和个数对不对,再在函数内部关键位置打印一些日志,看看程序执行到哪儿出问题了。

三、界面布局

  1. UI在不同设备上显示错乱:主要是布局没有做响应式设计,用了太多固定尺寸。在鸿蒙开发里,不同设备屏幕尺寸和分辨率都不一样,要是布局不灵活,就会出现按钮、文字显示不正常的情况。解决办法是多用鸿蒙提供的布局组件,像Column、Row、Flex 这些,尺寸单位用vp(视口单位)或者百分比,少用固定像素px 。
  2. 组件显示不全:可能是组件的层级关系没处理好,被其他组件盖住了,也可能是布局设置有问题,比如宽度、高度设置得不合理。检查一下组件的z - index属性,调整层级,再看看布局的设置,保证组件有足够空间显示。

四、权限相关

  1. 应用无法获取某些权限:像获取位置信息、相机权限这些,HarmonyOS对敏感权限管控很严格。如果在配置文件里没声明权限,应用就获取不了。要在module.json5文件里的“module”节点下添加“requestPermissions”数组,声明需要的权限,比如获取位置信息权限:
    {  
    "module": {  
        "package": "com.example.harmonyosdemo",  
        "name": ".entry",  
        "type": "entry",  
        "description": "应用入口模块",  
        "mainElement": "EntryAbility",  
        // 新增:位置信息权限声明  
        "requestPermissions": [  
            {  
                "name": "ohos.permission.LOCATION",  
                "reason": "需要获取位置信息提供服务",  
                "usedScene": {  
                    "ability": ["EntryAbility"],  
                    "when": "always"  
                }  
            }  
        ],  
        "abilities": []  
    }  
    }
  2. 动态权限申请失败:有些权限除了在配置文件声明,还需要动态申请,特别是在Android设备上运行的时候。申请的时候要注意权限申请的回调处理,确保用户同意授权后,应用能正常使用权限。

五、调试相关

  1. 调试时断点不起作用:有可能是代码优化设置影响了断点,或者是调试配置有问题。在DevEco Studio里,把代码优化选项关掉试试,再检查一下调试配置,确保断点设置在可执行代码行上。
  2. 日志打印不出来:检查一下日志级别设置,要是设置太高,低级别日志就打印不出来了。还有就是确认一下日志打印的方法有没有写错,比如HiLog的使用,要确保标签、日志级别这些参数都正确。

以上就是鸿蒙原生开发里比较常见的一些问题,希望能帮到大家,祝各位开发顺利,早日开发出超棒的鸿蒙应用!

继续阅读 »

鸿蒙原生开发常见问题汇总,纯干货分享!
咱直接开门见山,聊一聊鸿蒙原生开发里那些常遇到的麻烦事儿,顺便也讲讲对应的解决办法,不管你是刚接触鸿蒙开发的新手,还是已经有一定经验的开发者,希望这篇文章都能帮你少走点弯路。

一、环境搭建

  1. DevEco Studio安装失败:可能是电脑的系统环境不满足要求,比如内存不够、磁盘空间不足,或者是安装包下载不完整。解决办法就是先检查系统配置,清理出足够的内存和磁盘空间,然后重新下载安装包,最好从官方正规渠道下载,下载的时候确保网络稳定。
  2. 模拟器启动不了:电脑没开启虚拟化技术是常见原因,还有可能是装了像360安全卫士、火绒这类安全软件,把模拟器的服务禁用了,也可能是模拟器镜像下载不完整或者损坏。解决步骤如下:先重启电脑,进BIOS把虚拟化技术打开(不同品牌电脑进BIOS的快捷键不一样,联想一般按F2,惠普按F10 ,戴尔按F2,华硕按Del );然后把安全软件里拦截模拟器的功能关掉;最后在DevEco Studio里把原来下载的模拟器镜像删了重新下载。

二、代码编写

  1. ArkTS语法错误:ArkTS是静态类型语言,类型不匹配就很容易出错,比如你定义一个变量是数字类型,结果后面给它赋值成字符串了。还有就是API调用不当,鸿蒙的API在不同版本可能有变化。解决办法就是写代码的时候多注意类型声明,利用好IDE的自动补全和类型提示功能,调用API之前先去官方文档确认一下版本和用法。
  2. 函数调用没反应:有可能是函数参数传错了,或者函数内部逻辑有问题。遇到这种情况,先检查参数的类型和个数对不对,再在函数内部关键位置打印一些日志,看看程序执行到哪儿出问题了。

三、界面布局

  1. UI在不同设备上显示错乱:主要是布局没有做响应式设计,用了太多固定尺寸。在鸿蒙开发里,不同设备屏幕尺寸和分辨率都不一样,要是布局不灵活,就会出现按钮、文字显示不正常的情况。解决办法是多用鸿蒙提供的布局组件,像Column、Row、Flex 这些,尺寸单位用vp(视口单位)或者百分比,少用固定像素px 。
  2. 组件显示不全:可能是组件的层级关系没处理好,被其他组件盖住了,也可能是布局设置有问题,比如宽度、高度设置得不合理。检查一下组件的z - index属性,调整层级,再看看布局的设置,保证组件有足够空间显示。

四、权限相关

  1. 应用无法获取某些权限:像获取位置信息、相机权限这些,HarmonyOS对敏感权限管控很严格。如果在配置文件里没声明权限,应用就获取不了。要在module.json5文件里的“module”节点下添加“requestPermissions”数组,声明需要的权限,比如获取位置信息权限:
    {  
    "module": {  
        "package": "com.example.harmonyosdemo",  
        "name": ".entry",  
        "type": "entry",  
        "description": "应用入口模块",  
        "mainElement": "EntryAbility",  
        // 新增:位置信息权限声明  
        "requestPermissions": [  
            {  
                "name": "ohos.permission.LOCATION",  
                "reason": "需要获取位置信息提供服务",  
                "usedScene": {  
                    "ability": ["EntryAbility"],  
                    "when": "always"  
                }  
            }  
        ],  
        "abilities": []  
    }  
    }
  2. 动态权限申请失败:有些权限除了在配置文件声明,还需要动态申请,特别是在Android设备上运行的时候。申请的时候要注意权限申请的回调处理,确保用户同意授权后,应用能正常使用权限。

五、调试相关

  1. 调试时断点不起作用:有可能是代码优化设置影响了断点,或者是调试配置有问题。在DevEco Studio里,把代码优化选项关掉试试,再检查一下调试配置,确保断点设置在可执行代码行上。
  2. 日志打印不出来:检查一下日志级别设置,要是设置太高,低级别日志就打印不出来了。还有就是确认一下日志打印的方法有没有写错,比如HiLog的使用,要确保标签、日志级别这些参数都正确。

以上就是鸿蒙原生开发里比较常见的一些问题,希望能帮到大家,祝各位开发顺利,早日开发出超棒的鸿蒙应用!

收起阅读 »

App 上架需要什么?从开发者账号到开心上架(Appuploader)免 Mac 上传的完整流程指南

iOS

'''对于初次上架 iOS 应用的开发者来说,“App 上架需要什么?”
往往是最常被问到的问题。

与 Android 市场相比,苹果 App Store 的上架流程更严格、步骤更多。
不仅需要合法的 Apple 开发者账号,还要准备好签名证书、隐私政策、截图描述、IPA 包等。

此外,上传环节传统上依赖 Mac + Xcode,但现在通过 开心上架(Appuploader)命令行工具 即便在 Windows 或 Linux 系统 中,也能轻松完成上架流程。

本文将以实战角度为你详细说明 iOS App 上架所需的全部条件与操作要点


一、App 上架苹果商店前必备条件总览

要上架 App Store,你至少需要准备以下六项内容:

项目 说明
Apple Developer 开发者账号 负责上架和签名认证
应用签名证书与描述文件 验证 App 合法性
IPA 安装包 打包生成的 iOS 应用文件
App 信息与截图 用于 App Store 展示
隐私政策链接 审核必需内容
上传工具 将 IPA 提交到 App Store

每一项都不可缺少,否则应用将无法被苹果审核通过。


二、开发者账号:上架的第一步

注册开发者账号

访问 Apple Developer 官网 并注册账号。
你需要一个 Apple ID,并选择加入 Apple Developer Program(年费 99 美元)。

账号类型如下:

类型 适合对象 特点
个人账号 独立开发者 成本低、操作简单
企业账号 公司或团队 支持多人协作、团队证书共享

注册会员

审核与激活

提交资料后,苹果会通过邮箱验证身份。
审核通过后,你的 Apple 开发者账号即可使用。


三、签名证书与描述文件(Provisioning Profile)

iOS 应用无法像 Android 一样随意打包上传,它必须经过苹果官方签名认证,才能被系统识别与安装。

证书类型:

证书名称 用途
开发证书(Development) 用于测试和调试
发布证书(Distribution) 用于 App Store 上架
推送证书(Push Certificate) 用于 APNs 推送功能

传统方式(麻烦)

需要使用 Xcode + 钥匙串助手生成证书,仅限 Mac 用户操作。

使用 开心上架(Appuploader) 生成证书

  • 无需 Xcode;
  • 生成速度快;
  • 支持多人共享证书文件;
  • 团队协作开发更高效。
    证书

四、IPA 文件:App 上架的核心载体

IPA 文件相当于 iOS 应用的“安装包”。
无论你使用什么框架(原生、Flutter、uni-app、React Native),都必须最终打包出 .ipa 文件。

打包方式对照:

技术栈 打包方法
原生 iOS(Xcode) Product → Archive → Export
uni-app(HBuilderX) 云打包生成 IPA
Flutter / React Native 命令行构建(需签名文件)
Hybrid / Cordova Xcode 导出或第三方工具

对没有 Mac 的开发者而言,uni-app 云打包 + 开心上架 CLI 是最便捷组合。
hbuilderx打包


五、App Store 提交资料与合规要求

在上传 IPA 之前,需要准备以下内容:

项目 说明
应用名称 符合苹果命名规范,避免关键词堆砌
App 描述 介绍应用功能与优势
关键词 有助于搜索排名
截图 必须包含 6.5" 与 5.5" 屏幕尺寸
隐私政策链接 审核强制项,必须能访问
应用图标 PNG 格式,1024×1024 像素

苹果审核特别重视 隐私与安全声明
建议在网页托管隐私政策文件(如 GitHub Pages 或自有域名)。


六、上传工具选择与流程

传统上传方式包括:

  • Xcode 上传(官方推荐,但仅限 macOS)
  • Transporter App(拖拽式上传)
  • altool / Fastlane(命令行上传)

这些方式都依赖苹果生态,跨平台开发者无法使用。

推荐方案:开心上架(Appuploader)

支持 图形界面 + 命令行,兼容多系统。
ipa上传

命令行上传示例:

appuploader_cli -u ios@team.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/app.ipa
参数 含义
-u Apple 开发者账号
-p App 专用密码
-c 上传通道(1=旧通道,2=新通道)
-f 指定 IPA 文件路径

支持:

  • 批量上传;
  • 上传日志输出;
  • 多语言截图与元数据同步;
  • 自动化上架脚本集成。

七、App Store Connect 配置与审核发布

IPA 上传完成后,前往 App Store Connect

填写应用基本信息;
上传截图与隐私政策链接;
选择应用分级(年龄限制);
设置价格与上架区域;
点击 “提交审核”。
asc

审核时间:

  • 普通应用:1–3 个工作日;
  • 含内购或推送的应用:3–5 天。

八、常见上架问题与解决方法

问题 原因 解决方案
上传失败 Invalid Credentials 密码错误 使用 App 专用密码
“Invalid Bundle ID” 包名不一致 核对 Bundle Identifier
审核拒绝 隐私政策或截图问题 修改后重新提交
“Missing Provisioning Profile” 签名配置错误 重新生成证书
上传卡顿 网络不稳 切换上传通道 -c 1-c 2

九、免 Mac 自动化上架实践

你可以将 FastlaneAppuploader CLI 结合,实现全平台的持续集成自动上架。

# 自动构建  
fastlane gym --scheme "MyApp" --output_directory "./build"  

# 自动上传  
appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/MyApp.ipa

支持:

  • Jenkins、GitLab CI、GitHub Actions 集成;
  • 定时构建 + 自动发布;
  • 日志追踪与版本通知。

App 上架需要什么?
需要的不只是账号与证书,更是一套高效的自动化上架流程。

开心上架(Appuploader) 让跨平台上架成为现实,让开发者在任何系统中都能完成从打包、签名到上传审核的全过程。

没有 Mac?没问题。有 Appuploader,就能开心上架。'''

继续阅读 »

'''对于初次上架 iOS 应用的开发者来说,“App 上架需要什么?”
往往是最常被问到的问题。

与 Android 市场相比,苹果 App Store 的上架流程更严格、步骤更多。
不仅需要合法的 Apple 开发者账号,还要准备好签名证书、隐私政策、截图描述、IPA 包等。

此外,上传环节传统上依赖 Mac + Xcode,但现在通过 开心上架(Appuploader)命令行工具 即便在 Windows 或 Linux 系统 中,也能轻松完成上架流程。

本文将以实战角度为你详细说明 iOS App 上架所需的全部条件与操作要点


一、App 上架苹果商店前必备条件总览

要上架 App Store,你至少需要准备以下六项内容:

项目 说明
Apple Developer 开发者账号 负责上架和签名认证
应用签名证书与描述文件 验证 App 合法性
IPA 安装包 打包生成的 iOS 应用文件
App 信息与截图 用于 App Store 展示
隐私政策链接 审核必需内容
上传工具 将 IPA 提交到 App Store

每一项都不可缺少,否则应用将无法被苹果审核通过。


二、开发者账号:上架的第一步

注册开发者账号

访问 Apple Developer 官网 并注册账号。
你需要一个 Apple ID,并选择加入 Apple Developer Program(年费 99 美元)。

账号类型如下:

类型 适合对象 特点
个人账号 独立开发者 成本低、操作简单
企业账号 公司或团队 支持多人协作、团队证书共享

注册会员

审核与激活

提交资料后,苹果会通过邮箱验证身份。
审核通过后,你的 Apple 开发者账号即可使用。


三、签名证书与描述文件(Provisioning Profile)

iOS 应用无法像 Android 一样随意打包上传,它必须经过苹果官方签名认证,才能被系统识别与安装。

证书类型:

证书名称 用途
开发证书(Development) 用于测试和调试
发布证书(Distribution) 用于 App Store 上架
推送证书(Push Certificate) 用于 APNs 推送功能

传统方式(麻烦)

需要使用 Xcode + 钥匙串助手生成证书,仅限 Mac 用户操作。

使用 开心上架(Appuploader) 生成证书

  • 无需 Xcode;
  • 生成速度快;
  • 支持多人共享证书文件;
  • 团队协作开发更高效。
    证书

四、IPA 文件:App 上架的核心载体

IPA 文件相当于 iOS 应用的“安装包”。
无论你使用什么框架(原生、Flutter、uni-app、React Native),都必须最终打包出 .ipa 文件。

打包方式对照:

技术栈 打包方法
原生 iOS(Xcode) Product → Archive → Export
uni-app(HBuilderX) 云打包生成 IPA
Flutter / React Native 命令行构建(需签名文件)
Hybrid / Cordova Xcode 导出或第三方工具

对没有 Mac 的开发者而言,uni-app 云打包 + 开心上架 CLI 是最便捷组合。
hbuilderx打包


五、App Store 提交资料与合规要求

在上传 IPA 之前,需要准备以下内容:

项目 说明
应用名称 符合苹果命名规范,避免关键词堆砌
App 描述 介绍应用功能与优势
关键词 有助于搜索排名
截图 必须包含 6.5" 与 5.5" 屏幕尺寸
隐私政策链接 审核强制项,必须能访问
应用图标 PNG 格式,1024×1024 像素

苹果审核特别重视 隐私与安全声明
建议在网页托管隐私政策文件(如 GitHub Pages 或自有域名)。


六、上传工具选择与流程

传统上传方式包括:

  • Xcode 上传(官方推荐,但仅限 macOS)
  • Transporter App(拖拽式上传)
  • altool / Fastlane(命令行上传)

这些方式都依赖苹果生态,跨平台开发者无法使用。

推荐方案:开心上架(Appuploader)

支持 图形界面 + 命令行,兼容多系统。
ipa上传

命令行上传示例:

appuploader_cli -u ios@team.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/app.ipa
参数 含义
-u Apple 开发者账号
-p App 专用密码
-c 上传通道(1=旧通道,2=新通道)
-f 指定 IPA 文件路径

支持:

  • 批量上传;
  • 上传日志输出;
  • 多语言截图与元数据同步;
  • 自动化上架脚本集成。

七、App Store Connect 配置与审核发布

IPA 上传完成后,前往 App Store Connect

填写应用基本信息;
上传截图与隐私政策链接;
选择应用分级(年龄限制);
设置价格与上架区域;
点击 “提交审核”。
asc

审核时间:

  • 普通应用:1–3 个工作日;
  • 含内购或推送的应用:3–5 天。

八、常见上架问题与解决方法

问题 原因 解决方案
上传失败 Invalid Credentials 密码错误 使用 App 专用密码
“Invalid Bundle ID” 包名不一致 核对 Bundle Identifier
审核拒绝 隐私政策或截图问题 修改后重新提交
“Missing Provisioning Profile” 签名配置错误 重新生成证书
上传卡顿 网络不稳 切换上传通道 -c 1-c 2

九、免 Mac 自动化上架实践

你可以将 FastlaneAppuploader CLI 结合,实现全平台的持续集成自动上架。

# 自动构建  
fastlane gym --scheme "MyApp" --output_directory "./build"  

# 自动上传  
appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/MyApp.ipa

支持:

  • Jenkins、GitLab CI、GitHub Actions 集成;
  • 定时构建 + 自动发布;
  • 日志追踪与版本通知。

App 上架需要什么?
需要的不只是账号与证书,更是一套高效的自动化上架流程。

开心上架(Appuploader) 让跨平台上架成为现实,让开发者在任何系统中都能完成从打包、签名到上传审核的全过程。

没有 Mac?没问题。有 Appuploader,就能开心上架。'''

收起阅读 »

uni-app 也能使用 App.vue 根组件?wot-starter 使用这个插件实现!

全局组件 模板 uniapp模板

📖 背景

wot-starter 是一个基于 UniApp + Vue3 + TypeScript 的跨平台应用开发模板,集成了 Wot UI 组件库。在传统的 UniApp 开发中,由于框架限制,无法像标准 Vue 应用那样使用全局根组件来管理公共状态和组件,这给开发带来了诸多不便。

为了解决这个问题,我们引入了 @uni-ku/root 插件,它通过 Vite 模拟出虚拟根组件,让 UniApp 项目也能享受到类似 Vue 标准应用的开发体验,也可以解决 @uni-helper/vite-plugin-uni-layouts 插件无法使用微信小程序 page-meta 的问题。

🎯 @uni-ku/root 是什么?

@uni-ku/root 借助 Vite 模拟出虚拟根组件(支持SFC的App.vue),解决 uniapp 无法使用公共组件问题。

🎏 支持

  • 自定义虚拟根组件文件命名(App.ku.vue文件命名支持更换)
  • 更高灵活度的获取虚拟根组件实例(获取KuRootView的Ref)
  • 自动提取PageMeta到页面顶层(自动提升小程序PageMeta[用于阻止滚动穿透]组件)

🚀 接入步骤和配置

📦 安装

pnpm add -D @uni-ku/root  

yarn add -D @uni-ku/root  

npm install -D @uni-ku/root

🚀 Vite 配置

vite.config.ts 中引入并配置 UniKuRoot 插件:

import { defineConfig } from 'vite'  
import Uni from '@dcloudio/vite-plugin-uni'  
import UniHelperManifest from '@uni-helper/vite-plugin-uni-manifest'  
import UniHelperPages from '@uni-helper/vite-plugin-uni-pages'  
import UniHelperLayouts from '@uni-helper/vite-plugin-uni-layouts'  
import UniHelperComponents from '@uni-helper/vite-plugin-uni-components'  
import AutoImport from 'unplugin-auto-import/vite'  
import { WotResolver } from '@uni-helper/vite-plugin-uni-components/resolvers'  
import UniKuRoot from '@uni-ku/root'  
// https://vitejs.dev/config/  
export default async () => {  
  const UnoCSS = (await import('unocss/vite')).default  
  return defineConfig({  
    plugins: [  
      // https://github.com/uni-helper/vite-plugin-uni-manifest  
      UniHelperManifest(),  
      // https://github.com/uni-helper/vite-plugin-uni-pages  
      UniHelperPages({  
        dts: 'src/uni-pages.d.ts',  
        subPackages: [  
          'src/subPages',  
        ],  
        /**  
         * 排除的页面,相对于 dir 和 subPackages  
         * @default []  
         */  
        exclude: ['**/components/**/*.*'],  
      }),  
      // https://github.com/uni-helper/vite-plugin-uni-layouts  
      UniHelperLayouts(),  
      // https://github.com/uni-helper/vite-plugin-uni-components  
      UniHelperComponents({  
        resolvers: [WotResolver()],  
        dts: 'src/components.d.ts',  
        dirs: ['src/components', 'src/business'],  
        directoryAsNamespace: true,  
      }),  
      // https://github.com/uni-ku/root  
      UniKuRoot(),  
      Uni(),  
      // https://github.com/antfu/unocss  
      // see unocss.config.ts for config  
      UnoCSS(),  
    ],  
  })  
}  

重要提示:UniKuRoot 插件必须放在 Uni() 插件之前,如果存在修改 pages.json 的插件和 Layout 插件,需要将 UniKuRoot 放在它们之后。

🎉 创建虚拟根组件

src/App.ku.vue 中创建虚拟根组件:

注意 App.ku.vue 中暂时无法编写样式全局生效,所以我们可以将样式写到 App.vue

<script setup lang="ts">  
const { themeVars, theme } = useManualTheme()  
</script>  

<template>  
  <wd-config-provider :theme-vars="themeVars" :theme="theme" :custom-class="`page-wraper ${theme}`">  
    <ku-root-view />  
    <wd-notify />  
    <wd-message-box />  
    <wd-toast />  
    <global-loading />  
    <global-toast />  
    <global-message />  
    <!-- #ifdef MP-WEIXIN -->  
    <privacy-popup />  
    <!-- #endif -->  
  </wd-config-provider>  
</template>

💡 代码示例和使用方法

1. 在页面中使用全局 Toast

<!-- src/pages/index/index.vue -->  
<script setup lang="ts">  
const globalToast = useGlobalToast()  

function showSuccess() {  
  globalToast.success('操作成功!')  
}  

function showError() {  
  globalToast.error('操作失败!')  
}  
</script>  

<template>  
  <view>  
    <button @click="showSuccess">显示成功提示</button>  
    <button @click="showError">显示错误提示</button>  
  </view>  
</template>

2. PageMeta 自动提升示例

在页面中使用 PageMeta 组件,会自动提升到页面顶层:

<!-- src/pages/uni-ku-root/index.vue -->  
<script setup lang="ts">  
definePage({  
  name: 'root',  
  style: {  
    navigationBarTitleText: 'uni-ku/root',  
  },  
})  

const show = ref<boolean>(false)  
</script>  

<template>  
  <page-meta :page-style="`overflow:${show ? 'hidden' : 'visible'};`" />  
  <view class="min-h-200vh">  
    <demo-block title="锁定滚动" transparent>  
      <wd-cell-group border>  
        <wd-cell title="锁定滚动" is-link @click="show = !show" />  
      </wd-cell-group>  
    </demo-block>  

    <wd-popup  
      v-model="show"  
      lock-scroll  
      position="bottom"  
      closable  
      :safe-area-inset-bottom="true"  
      custom-style="height: 200px;"  
      @close="show = false"  
    />  
  </view>  
</template>  

🎉 总结

通过接入 @uni-ku/root,wot-starter 项目成功实现了:

  • ✅ 全局组件的统一管理
  • ✅ 主题配置的全局应用
  • ✅ 更好的代码组织结构
  • ✅ 接近标准 Vue 应用的开发体验
  • ✅ 完美支持 Wot UI 组件库

这个方案不仅解决了 UniApp 开发中的痛点,还可以解决 @uni-helper/vite-plugin-uni-layouts 插件无法使用微信小程序 page-meta 的问题,一举多得,美哉!

🔗 相关链接

  • @uni-ku/root GitHub: https://github.com/uni-ku/root
  • wot-ui: https://wot-ui.cn
  • wot-starter: https://starter.wot-ui.cn
继续阅读 »

📖 背景

wot-starter 是一个基于 UniApp + Vue3 + TypeScript 的跨平台应用开发模板,集成了 Wot UI 组件库。在传统的 UniApp 开发中,由于框架限制,无法像标准 Vue 应用那样使用全局根组件来管理公共状态和组件,这给开发带来了诸多不便。

为了解决这个问题,我们引入了 @uni-ku/root 插件,它通过 Vite 模拟出虚拟根组件,让 UniApp 项目也能享受到类似 Vue 标准应用的开发体验,也可以解决 @uni-helper/vite-plugin-uni-layouts 插件无法使用微信小程序 page-meta 的问题。

🎯 @uni-ku/root 是什么?

@uni-ku/root 借助 Vite 模拟出虚拟根组件(支持SFC的App.vue),解决 uniapp 无法使用公共组件问题。

🎏 支持

  • 自定义虚拟根组件文件命名(App.ku.vue文件命名支持更换)
  • 更高灵活度的获取虚拟根组件实例(获取KuRootView的Ref)
  • 自动提取PageMeta到页面顶层(自动提升小程序PageMeta[用于阻止滚动穿透]组件)

🚀 接入步骤和配置

📦 安装

pnpm add -D @uni-ku/root  

yarn add -D @uni-ku/root  

npm install -D @uni-ku/root

🚀 Vite 配置

vite.config.ts 中引入并配置 UniKuRoot 插件:

import { defineConfig } from 'vite'  
import Uni from '@dcloudio/vite-plugin-uni'  
import UniHelperManifest from '@uni-helper/vite-plugin-uni-manifest'  
import UniHelperPages from '@uni-helper/vite-plugin-uni-pages'  
import UniHelperLayouts from '@uni-helper/vite-plugin-uni-layouts'  
import UniHelperComponents from '@uni-helper/vite-plugin-uni-components'  
import AutoImport from 'unplugin-auto-import/vite'  
import { WotResolver } from '@uni-helper/vite-plugin-uni-components/resolvers'  
import UniKuRoot from '@uni-ku/root'  
// https://vitejs.dev/config/  
export default async () => {  
  const UnoCSS = (await import('unocss/vite')).default  
  return defineConfig({  
    plugins: [  
      // https://github.com/uni-helper/vite-plugin-uni-manifest  
      UniHelperManifest(),  
      // https://github.com/uni-helper/vite-plugin-uni-pages  
      UniHelperPages({  
        dts: 'src/uni-pages.d.ts',  
        subPackages: [  
          'src/subPages',  
        ],  
        /**  
         * 排除的页面,相对于 dir 和 subPackages  
         * @default []  
         */  
        exclude: ['**/components/**/*.*'],  
      }),  
      // https://github.com/uni-helper/vite-plugin-uni-layouts  
      UniHelperLayouts(),  
      // https://github.com/uni-helper/vite-plugin-uni-components  
      UniHelperComponents({  
        resolvers: [WotResolver()],  
        dts: 'src/components.d.ts',  
        dirs: ['src/components', 'src/business'],  
        directoryAsNamespace: true,  
      }),  
      // https://github.com/uni-ku/root  
      UniKuRoot(),  
      Uni(),  
      // https://github.com/antfu/unocss  
      // see unocss.config.ts for config  
      UnoCSS(),  
    ],  
  })  
}  

重要提示:UniKuRoot 插件必须放在 Uni() 插件之前,如果存在修改 pages.json 的插件和 Layout 插件,需要将 UniKuRoot 放在它们之后。

🎉 创建虚拟根组件

src/App.ku.vue 中创建虚拟根组件:

注意 App.ku.vue 中暂时无法编写样式全局生效,所以我们可以将样式写到 App.vue

<script setup lang="ts">  
const { themeVars, theme } = useManualTheme()  
</script>  

<template>  
  <wd-config-provider :theme-vars="themeVars" :theme="theme" :custom-class="`page-wraper ${theme}`">  
    <ku-root-view />  
    <wd-notify />  
    <wd-message-box />  
    <wd-toast />  
    <global-loading />  
    <global-toast />  
    <global-message />  
    <!-- #ifdef MP-WEIXIN -->  
    <privacy-popup />  
    <!-- #endif -->  
  </wd-config-provider>  
</template>

💡 代码示例和使用方法

1. 在页面中使用全局 Toast

<!-- src/pages/index/index.vue -->  
<script setup lang="ts">  
const globalToast = useGlobalToast()  

function showSuccess() {  
  globalToast.success('操作成功!')  
}  

function showError() {  
  globalToast.error('操作失败!')  
}  
</script>  

<template>  
  <view>  
    <button @click="showSuccess">显示成功提示</button>  
    <button @click="showError">显示错误提示</button>  
  </view>  
</template>

2. PageMeta 自动提升示例

在页面中使用 PageMeta 组件,会自动提升到页面顶层:

<!-- src/pages/uni-ku-root/index.vue -->  
<script setup lang="ts">  
definePage({  
  name: 'root',  
  style: {  
    navigationBarTitleText: 'uni-ku/root',  
  },  
})  

const show = ref<boolean>(false)  
</script>  

<template>  
  <page-meta :page-style="`overflow:${show ? 'hidden' : 'visible'};`" />  
  <view class="min-h-200vh">  
    <demo-block title="锁定滚动" transparent>  
      <wd-cell-group border>  
        <wd-cell title="锁定滚动" is-link @click="show = !show" />  
      </wd-cell-group>  
    </demo-block>  

    <wd-popup  
      v-model="show"  
      lock-scroll  
      position="bottom"  
      closable  
      :safe-area-inset-bottom="true"  
      custom-style="height: 200px;"  
      @close="show = false"  
    />  
  </view>  
</template>  

🎉 总结

通过接入 @uni-ku/root,wot-starter 项目成功实现了:

  • ✅ 全局组件的统一管理
  • ✅ 主题配置的全局应用
  • ✅ 更好的代码组织结构
  • ✅ 接近标准 Vue 应用的开发体验
  • ✅ 完美支持 Wot UI 组件库

这个方案不仅解决了 UniApp 开发中的痛点,还可以解决 @uni-helper/vite-plugin-uni-layouts 插件无法使用微信小程序 page-meta 的问题,一举多得,美哉!

🔗 相关链接

  • @uni-ku/root GitHub: https://github.com/uni-ku/root
  • wot-ui: https://wot-ui.cn
  • wot-starter: https://starter.wot-ui.cn
收起阅读 »

4.84 版本无法唤起小程序 2.01

HBuilderX 微信小程序 问题已解决

更新之后的 HBuilderX4.84 版本,启动项目后无法正确唤起小程序开发者工具 2.01

更新之后的 HBuilderX4.84 版本,启动项目后无法正确唤起小程序开发者工具 2.01

基于vue3.5+vite7.1+tauri2.9实战桌面端后台管理系统

vue.js vite vue3 Vue

vue3-tauri2-admin:最新研发vite7.1+tauri2.9+vue3 setup+pinia3+elementPlus跨平台电脑端中后台管理系统Exe模板。包含了表格、图表、表单、列表、编辑器、错误处理等模块。

使用技术

  • 开发工具:VScode
  • 跨平台框架:Tauri^2.9
  • 前端技术框架:vite^7.1.12+vue^3.5.22+vue-router^4.6.3
  • 组件库:element-plus^2.11.5
  • 状态管理:pinia^3.0.3
  • 国际化方案:vue-i18n^11.1.12
  • 图表组件:echarts^6.0.0
  • markdown编辑器:md-editor-v3^6.1.0
  • 富文本编辑器:@vueup/vue-quill^1.2.0
  • 模拟数据:mockjs^1.1.0

项目结构目录

使用最新跨平台技术tauri2.9+vue3搭建项目模板页面。

tauri2-vue3admin客户端后台系统已经更新到我的原创作品集。
tauri2.9+vue3+element-plus客户端后台系统EXE

如果想要了解更多详细介绍,可以去看看下面这篇文章。

Tauri2-Vite7Admin客户端管理后台|tauri2.9+vue3+element-plus后台系统

往期推荐

Tauri2.8+Vue3聊天系统|vite7+tauri2+element-plus客户端仿微信聊天程序
Electron38-Vue3OS客户端OS系统|vite7+electron38+arco桌面os后台管理
electron38-admin桌面端后台|Electron38+Vue3+ElementPlus管理系统
Electron38-Wechat电脑端聊天|vite7+electron38仿微信桌面端聊天系统
原创uniapp+vue3+deepseek+uv-ui跨端实战仿deepseek/豆包流式ai聊天对话助手。
vue3-webseek网页版AI问答|Vite6+DeepSeek+Arco流式ai聊天打字效果
最新版uni-app+vue3+uv-ui跨三端仿微信app聊天应用【h5+小程序+app端】
Flutter3-MacOS桌面OS系统|flutter3.32+window_manager客户端OS模板
最新研发flutter3.27+bitsdojo_window+getx客户端仿微信聊天Exe应用
最新版Flutter3.32+Dart3.8跨平台仿微信app聊天界面|朋友圈
最新版uniapp+vue3+uv-ui跨三端短视频+直播+聊天【H5+小程序+App端】
uniapp-vue3-os手机oa系统|uni-app+vue3跨三端os后台管理模板
Electron35-DeepSeek桌面端AI系统|vue3.5+electron+arco客户端ai模板
uniapp+vue3酒店预订|vite5+uniapp预约订房系统模板(h5+小程序+App端)
Tauri2.0+Vite5聊天室|vue3+tauri2+element-plus仿微信|tauri聊天应用
tauri2.0-admin桌面端后台系统|Tauri2+Vite5+ElementPlus管理后台EXE程序

继续阅读 »

vue3-tauri2-admin:最新研发vite7.1+tauri2.9+vue3 setup+pinia3+elementPlus跨平台电脑端中后台管理系统Exe模板。包含了表格、图表、表单、列表、编辑器、错误处理等模块。

使用技术

  • 开发工具:VScode
  • 跨平台框架:Tauri^2.9
  • 前端技术框架:vite^7.1.12+vue^3.5.22+vue-router^4.6.3
  • 组件库:element-plus^2.11.5
  • 状态管理:pinia^3.0.3
  • 国际化方案:vue-i18n^11.1.12
  • 图表组件:echarts^6.0.0
  • markdown编辑器:md-editor-v3^6.1.0
  • 富文本编辑器:@vueup/vue-quill^1.2.0
  • 模拟数据:mockjs^1.1.0

项目结构目录

使用最新跨平台技术tauri2.9+vue3搭建项目模板页面。

tauri2-vue3admin客户端后台系统已经更新到我的原创作品集。
tauri2.9+vue3+element-plus客户端后台系统EXE

如果想要了解更多详细介绍,可以去看看下面这篇文章。

Tauri2-Vite7Admin客户端管理后台|tauri2.9+vue3+element-plus后台系统

往期推荐

Tauri2.8+Vue3聊天系统|vite7+tauri2+element-plus客户端仿微信聊天程序
Electron38-Vue3OS客户端OS系统|vite7+electron38+arco桌面os后台管理
electron38-admin桌面端后台|Electron38+Vue3+ElementPlus管理系统
Electron38-Wechat电脑端聊天|vite7+electron38仿微信桌面端聊天系统
原创uniapp+vue3+deepseek+uv-ui跨端实战仿deepseek/豆包流式ai聊天对话助手。
vue3-webseek网页版AI问答|Vite6+DeepSeek+Arco流式ai聊天打字效果
最新版uni-app+vue3+uv-ui跨三端仿微信app聊天应用【h5+小程序+app端】
Flutter3-MacOS桌面OS系统|flutter3.32+window_manager客户端OS模板
最新研发flutter3.27+bitsdojo_window+getx客户端仿微信聊天Exe应用
最新版Flutter3.32+Dart3.8跨平台仿微信app聊天界面|朋友圈
最新版uniapp+vue3+uv-ui跨三端短视频+直播+聊天【H5+小程序+App端】
uniapp-vue3-os手机oa系统|uni-app+vue3跨三端os后台管理模板
Electron35-DeepSeek桌面端AI系统|vue3.5+electron+arco客户端ai模板
uniapp+vue3酒店预订|vite5+uniapp预约订房系统模板(h5+小程序+App端)
Tauri2.0+Vite5聊天室|vue3+tauri2+element-plus仿微信|tauri聊天应用
tauri2.0-admin桌面端后台系统|Tauri2+Vite5+ElementPlus管理后台EXE程序

收起阅读 »

【鸿蒙征文】记一次鸿蒙Next原生插件开发与Uniapp调用实战之弹层开发(tmui4x中的xToasts弹层)(含源码附件)

鸿蒙征文

本文实现目标

  1. 掌握如何使用devEco-Studio6开发和创建一个Har插件
  2. 插件与unipp之间如何传参数并调用之
  3. 如何在ArkTs中绘制渲染自定义界面
  4. 如何在ArkTs中模块化界面组件
  5. 创建uniapp插件并调用自己写的鸿蒙原生插件

完成本次目标:全局弹层框

附件会有本次示例的源码包,可以直接使用,是tmui4x组件库的一部分。

本文阅读前的要求

需要你了解以下开发语言

  1. ArtTs,这块学习掌握应该很快,因为真的和TS像。
  2. UTS的了解,也是相对简单,参见Dcloud官方文档即可(在本文中占比较少,主要是着重如何利用它作为一个桥梁)
  3. uniAppx的环境搭建及devEco-staudio6的环境搭建。

如果你满足了以上要求可以按本文代码复制阅读可以直接运行自己的插件。
如果还未满足,可以当作是了解学习结构的一个文章知识。

一、测试的环境

  • 电脑:mac mini2 ,macOs 26
  • HbuilderX工具:4.84
  • 鸿蒙开发工具:devEco-Studio 6,模拟器 HarmonyOs 6.0.0

二、创建Har

1、 创建Harmony测试项目

a. 如图创建鸿蒙项目

b. 继续

c. 点Finish

d. 首页页面代码

2、 创建Harmony的模拟器

  • 接着上步,点击No devices下拉框选中Devices Manager,打开 模拟器管理界面
  • 选中菜单栏 Phone 后在界面的尾部,选中New Emulator创建,记得下载鸿蒙6的依赖,等下载创建成功后即可。
  • 运行模拟器

点击运行模拟器:

3、 运行鸿蒙应用

  • 回到第一步,点运行按钮,此时旁边会有刚才创建和运行的模拟器啦 。
  • 运行应用后,说明前期的鸿蒙开发基础工作完成了。

4、 创建Har包

如图创建一个module

创建成功后,会有一个目录结构如图:

至此Har包创建完成了

5、编写插件代码

我们继续刚才的插件,找到插件根目录下的index.ets文件如上图高亮。
代码内容如图(附件中提供):

6、核心要点传参

我们知道在uts中的有各种各样的类型。比如本插件中,中我为弹层组件编写了一个类型

export type XTOAST_TYPE = {  
    iconColor?:string,  
    contentBgColor?:string,  
    maskBgColor?:string,  
    iconSize?:number,  
    /**  
     * 正常应该填写图标的16进制符号如:EEC4  
     * 本组件允许几个状态名称使用默认图标,为空是info图标。  
     * warn,error,success,info  
     */  
    iconCode?:string,  
    /**  
     * 标题为空不显示。  
     */  
    title:string,  
    titleSize?:number,  
    titleColor?:string,  
    /**  
     * 几秒后消失,0 表示永不消失  
     */  
    duration?:number,  
    close?:()=>void,  
    /** 宽 */  
    size?:number,  
    /**  
     * 禁用遮罩穿透点击事件  
     * @default true  
     */  
    maskDisableClik?:boolean  
}  

上面的类型如何在Har插件中如何接受?
在ArtTs中,有一个类型是 ESObject,你可以理解为JS中的Object或者UTS中的UTSOBJect类型。

在Har中,只要写对应的参数类型为params: ESObject即可。

比如弹层中的代码:

/**  
 * 打开提示  
 * @param context UIContext  
 * @param params ESObject 参数  
 */  
export function xshowtoasts(context: UIContext, params: ESObject){  
    //...  
}

a. 如何创建一个自定界面

@Builder  
export function customBuilderModal(params: Params) {  

  Column({space:5}) {  
    if (params.context.iconCode != '') {  
      Text(String.fromCharCode(parseInt(params.context.iconCode, 16)))  
        .fontColor(params.context.iconColor)  
        .fontSize(params.context.iconSize)  
        .fontFamily("remixicon")  
    }  
    Text(`${params.context.title}`)  
      .fontSize(params.context.titleSize)  
      .fontColor(params.context.titleColor)  
      .textAlign(TextAlign.Center)  
  }  
  .padding(16)  
  .borderRadius(16)  
  .backgroundColor(params.context.contentBgColor)  
  .shadow({  
    offsetX: 0,  
    offsetY: 5,  
    radius: 20,  
    color: 'rgba(0,0,0,0.1)'  
  })  
  .justifyContent(FlexAlign.Center)  
  .constraintSize({  
    minWidth:params.context.iconCode==''?0:params.context.size,  
    minHeight:params.context.iconCode==''?0:params.context.size,  
    maxWidth:300  
  })  
  .clip(true)  

}  

b. 渲染自定界面

我们可以了解下wrapBuilder它可以将模块界面编译为一个渲染Node供界面上渲染。
代码中

let globalBuilder: WrappedBuilder<[ESObject]> = wrapBuilder(customBuilderModal);  
const contentNode = new ComponentContent(context, globalBuilder, new Params(context, params))  

7. 完成Har插件的编写

前面只是讲述源码的关键点,具体因为内容牵涉众中,请下载附件研究,我们主要是要了解让UTS与har之间调用。
在附件中下载我写的插件即可。

8. 编译Har插件为依赖包

找到依赖文件:

至此我们一写har插件完成啦。

三、创建UniApp sdk插件

1. 复制Har依赖

复制前面我们编译好的Har依赖包到鸿蒙sdk目录,
请一定按图所示创建目录层级,并把文件复制进来。

添加依赖

2. 编写UTS插件代码

如图打开index.uts

3. 核心要点

a. 如何获取鸿蒙所需要参数UIContext?

UTSHarmony.getCurrentWindow()!.getUIContext()即可

因为大部分代码我们在鸿蒙插件内完成,因此UTS这边只需要调用对应函数即可。无需过多东西。

四、运行UTS插件

在页面中导入即可使用

import { showToast,XTOAST_TYPE } from "@/uni_modules/x-toast-s"  

onReady(()=>{  
    showToast({  
        title:"提示消息提示",  
        duration:1000*3,  
        close() {  
            console.log('close....')  
        }  
    })  
})  

五、运行Uniapp项目

运行你的uniapp项目就会在页面中显示一条全屏的弹层啦。

六、总结

上面的代码是粗略的讲述整个鸿蒙har插件创建,然后编译,再创建UTS创建,引用,调用har插件,
这是一个非常长的过程,中间详细的代码如何编写文章并未提及,因为牵涉过多,我们只讲述过程。

知识总结:

  • 在鸿蒙原生插件里面学习如何结构化自定义界面
  • 如何在鸿蒙原生插件中写全局弹层的界面
  • 如何在UTS插件这边传递UIContext及获取,以及参数类型的对应
  • 如何从0开始创建一个鸿蒙应用,及从0开始创建一个原生鸿蒙Har插件并编译。

七、源码声明

为了响应论坛的繁荣发展,因此源码及相关文件可以在附件中下载得到,
通过此文章,你可拓展更广的应用插件开发方向:

  • 其它全局的插件如modal,tips,page等一切可覆盖全屏的页面组件等
  • 鸿蒙原生插件开发的流程及要点。

本附件中的源码仅供学习参考,不作为最终生产用途的保证。

继续阅读 »

本文实现目标

  1. 掌握如何使用devEco-Studio6开发和创建一个Har插件
  2. 插件与unipp之间如何传参数并调用之
  3. 如何在ArkTs中绘制渲染自定义界面
  4. 如何在ArkTs中模块化界面组件
  5. 创建uniapp插件并调用自己写的鸿蒙原生插件

完成本次目标:全局弹层框

附件会有本次示例的源码包,可以直接使用,是tmui4x组件库的一部分。

本文阅读前的要求

需要你了解以下开发语言

  1. ArtTs,这块学习掌握应该很快,因为真的和TS像。
  2. UTS的了解,也是相对简单,参见Dcloud官方文档即可(在本文中占比较少,主要是着重如何利用它作为一个桥梁)
  3. uniAppx的环境搭建及devEco-staudio6的环境搭建。

如果你满足了以上要求可以按本文代码复制阅读可以直接运行自己的插件。
如果还未满足,可以当作是了解学习结构的一个文章知识。

一、测试的环境

  • 电脑:mac mini2 ,macOs 26
  • HbuilderX工具:4.84
  • 鸿蒙开发工具:devEco-Studio 6,模拟器 HarmonyOs 6.0.0

二、创建Har

1、 创建Harmony测试项目

a. 如图创建鸿蒙项目

b. 继续

c. 点Finish

d. 首页页面代码

2、 创建Harmony的模拟器

  • 接着上步,点击No devices下拉框选中Devices Manager,打开 模拟器管理界面
  • 选中菜单栏 Phone 后在界面的尾部,选中New Emulator创建,记得下载鸿蒙6的依赖,等下载创建成功后即可。
  • 运行模拟器

点击运行模拟器:

3、 运行鸿蒙应用

  • 回到第一步,点运行按钮,此时旁边会有刚才创建和运行的模拟器啦 。
  • 运行应用后,说明前期的鸿蒙开发基础工作完成了。

4、 创建Har包

如图创建一个module

创建成功后,会有一个目录结构如图:

至此Har包创建完成了

5、编写插件代码

我们继续刚才的插件,找到插件根目录下的index.ets文件如上图高亮。
代码内容如图(附件中提供):

6、核心要点传参

我们知道在uts中的有各种各样的类型。比如本插件中,中我为弹层组件编写了一个类型

export type XTOAST_TYPE = {  
    iconColor?:string,  
    contentBgColor?:string,  
    maskBgColor?:string,  
    iconSize?:number,  
    /**  
     * 正常应该填写图标的16进制符号如:EEC4  
     * 本组件允许几个状态名称使用默认图标,为空是info图标。  
     * warn,error,success,info  
     */  
    iconCode?:string,  
    /**  
     * 标题为空不显示。  
     */  
    title:string,  
    titleSize?:number,  
    titleColor?:string,  
    /**  
     * 几秒后消失,0 表示永不消失  
     */  
    duration?:number,  
    close?:()=>void,  
    /** 宽 */  
    size?:number,  
    /**  
     * 禁用遮罩穿透点击事件  
     * @default true  
     */  
    maskDisableClik?:boolean  
}  

上面的类型如何在Har插件中如何接受?
在ArtTs中,有一个类型是 ESObject,你可以理解为JS中的Object或者UTS中的UTSOBJect类型。

在Har中,只要写对应的参数类型为params: ESObject即可。

比如弹层中的代码:

/**  
 * 打开提示  
 * @param context UIContext  
 * @param params ESObject 参数  
 */  
export function xshowtoasts(context: UIContext, params: ESObject){  
    //...  
}

a. 如何创建一个自定界面

@Builder  
export function customBuilderModal(params: Params) {  

  Column({space:5}) {  
    if (params.context.iconCode != '') {  
      Text(String.fromCharCode(parseInt(params.context.iconCode, 16)))  
        .fontColor(params.context.iconColor)  
        .fontSize(params.context.iconSize)  
        .fontFamily("remixicon")  
    }  
    Text(`${params.context.title}`)  
      .fontSize(params.context.titleSize)  
      .fontColor(params.context.titleColor)  
      .textAlign(TextAlign.Center)  
  }  
  .padding(16)  
  .borderRadius(16)  
  .backgroundColor(params.context.contentBgColor)  
  .shadow({  
    offsetX: 0,  
    offsetY: 5,  
    radius: 20,  
    color: 'rgba(0,0,0,0.1)'  
  })  
  .justifyContent(FlexAlign.Center)  
  .constraintSize({  
    minWidth:params.context.iconCode==''?0:params.context.size,  
    minHeight:params.context.iconCode==''?0:params.context.size,  
    maxWidth:300  
  })  
  .clip(true)  

}  

b. 渲染自定界面

我们可以了解下wrapBuilder它可以将模块界面编译为一个渲染Node供界面上渲染。
代码中

let globalBuilder: WrappedBuilder<[ESObject]> = wrapBuilder(customBuilderModal);  
const contentNode = new ComponentContent(context, globalBuilder, new Params(context, params))  

7. 完成Har插件的编写

前面只是讲述源码的关键点,具体因为内容牵涉众中,请下载附件研究,我们主要是要了解让UTS与har之间调用。
在附件中下载我写的插件即可。

8. 编译Har插件为依赖包

找到依赖文件:

至此我们一写har插件完成啦。

三、创建UniApp sdk插件

1. 复制Har依赖

复制前面我们编译好的Har依赖包到鸿蒙sdk目录,
请一定按图所示创建目录层级,并把文件复制进来。

添加依赖

2. 编写UTS插件代码

如图打开index.uts

3. 核心要点

a. 如何获取鸿蒙所需要参数UIContext?

UTSHarmony.getCurrentWindow()!.getUIContext()即可

因为大部分代码我们在鸿蒙插件内完成,因此UTS这边只需要调用对应函数即可。无需过多东西。

四、运行UTS插件

在页面中导入即可使用

import { showToast,XTOAST_TYPE } from "@/uni_modules/x-toast-s"  

onReady(()=>{  
    showToast({  
        title:"提示消息提示",  
        duration:1000*3,  
        close() {  
            console.log('close....')  
        }  
    })  
})  

五、运行Uniapp项目

运行你的uniapp项目就会在页面中显示一条全屏的弹层啦。

六、总结

上面的代码是粗略的讲述整个鸿蒙har插件创建,然后编译,再创建UTS创建,引用,调用har插件,
这是一个非常长的过程,中间详细的代码如何编写文章并未提及,因为牵涉过多,我们只讲述过程。

知识总结:

  • 在鸿蒙原生插件里面学习如何结构化自定义界面
  • 如何在鸿蒙原生插件中写全局弹层的界面
  • 如何在UTS插件这边传递UIContext及获取,以及参数类型的对应
  • 如何从0开始创建一个鸿蒙应用,及从0开始创建一个原生鸿蒙Har插件并编译。

七、源码声明

为了响应论坛的繁荣发展,因此源码及相关文件可以在附件中下载得到,
通过此文章,你可拓展更广的应用插件开发方向:

  • 其它全局的插件如modal,tips,page等一切可覆盖全屏的页面组件等
  • 鸿蒙原生插件开发的流程及要点。

本附件中的源码仅供学习参考,不作为最终生产用途的保证。

收起阅读 »

【鸿蒙征文】从零实现 uni-app 鸿蒙平台 TTS 插件:UTS 开发实践指南

鸿蒙征文

从零实现 uni-app 鸿蒙平台 TTS 插件:UTS 开发实践指南

随着移动应用的日益普及,文本转语音(TTS)功能已经成为提升用户体验的重要组成部分。在跨平台开发中,我们常常需要为不同平台提供一致的语音合成能力。

uni-app框架在安卓和IOS提供speech能力,但在鸿蒙(HarmonyOS)平台上,不提供speech支持。本文将详细介绍如何通过UTS技术开发一个鸿蒙平台的TTS插件,弥补这一功能空白,并学习如何实现跨平台接口的一致性。

一、项目概述

本项目将开发一个名为Lime TTS的基于UTS(Uni TypeScript)的文本转语音插件,主要目标是为uni-app在鸿蒙平台上提供语音合成能力。通过本项目的学习,我们将掌握如何使用UTS技术调用鸿蒙原生API,实现跨平台插件开发的核心技能。

二、项目目录结构

我们的Lime TTS项目采用标准的uni-app UTS插件结构,清晰地分离了接口定义和平台实现。这种结构设计有助于我们组织代码,并确保跨平台实现的一致性。项目的目录结构如下:

uni_modules/  
└── lime-tts/  
    ├── changelog.md  
    ├── package.json  
    ├── readme.md  
    └── utssdk/  
        ├── app-android/  
        │   ├── config.json  
        │   └── index.uts  
        ├── app-harmony/  
        │   ├── config.json  
        │   └── index.uts  
        ├── app-ios/  
        │   ├── config.json  
        │   └── index.uts  
        ├── interface.uts  
        ├── unierror.uts  
        └── web/  
            └── index.uts
  • utssdk/interface.uts:定义了插件的公共接口和类型,是插件的核心规范文件。
  • utssdk/app-harmony/index.uts:鸿蒙平台的具体实现代码。

三、接口设计与定义

3.1 核心接口和类型设计

Lime TTS 插件通过 uni_modules/lime-tts/utssdk/interface.uts 文件定义了一套完整的类型和接口规范,确保了良好的类型安全性和代码提示。

语音合成选项

export type SpeakOptions = {  
    /**  
     * 语速  
     * 可选,支持范围 [0.5-2],默认值为 1  
     */  
    speed ?: number;  
    /**  
     * 音量  
     * 可选,支持范围 [0-2],默认值为 1  
     */  
    volume ?: number;  
    /**  
     * 音调  
     * 可选,支持范围 [0.5-2],默认值为 1  
     */  
    pitch ?: number;  
    /**  
     * 语境,播放阿拉伯数字用的语种  
     * 可选,支持 "zh-CN" 中文与 "en-US" 英文,默认 "zh-CN"  
     */  
    language ?: Language;  
    // 其他配置项...  
}

音色信息

export type VoiceInfo = {  
    language : Language;  
    person : number;  
    name : string;  
    gender : 'male' | 'female';  
    description : string;  
    status : 'available' | 'downloadable' | 'unavailable';  
}

语音状态

export type SpeechStatus = 'idle' | 'speaking' | 'paused' | 'uninitialized';

3.2 主要功能接口

export interface LimeTTS {  
    /**  
     * 朗读指定文本  
     * @param text 要朗读的文本  
     * @param options 可选参数,如语音ID、语速等  
     */  
    speak(text : string) : void;  
    speak(text : string, options : SpeakOptions | null) : void;  

    /**  
     * 停止当前朗读  
     */  
    stop() : void;  

    /**  
     * 暂停当前朗读  
     */  
    pause() : void;  

    /**  
     * 获取可用语音列表  
     */  
    getVoices() : Promise<VoiceInfo[]>;  

    /**  
     * 获取当前语音状态  
     */  
    getStatus() : SpeechStatus;  

    /**  
     * 销毁TTS实例,释放资源  
     */  
    destroy() : void;  

    /**  
     * 监听TTS事件  
     * @param event 事件类型  
     * @param callback 回调函数  
     */  
    on(event : 'start' | 'end' | 'error' | 'stop', callback : SpeechCallback) : void;  
    off(event : 'start' | 'end' | 'error' | 'stop') : void;  
    off(event : 'start' | 'end' | 'error' | 'stop', callback : SpeechCallback | null) : void;  
}

四、鸿蒙平台实现详解

在本节中,我们将详细学习如何为鸿蒙平台实现TTS功能。我们将通过调用鸿蒙原生的@kit.CoreSpeechKit来实现语音合成,并遵循之前设计的接口规范。

4.0 模块导入

在鸿蒙平台实现中,首先需要导入鸿蒙系统提供的核心语音合成模块:

// 导入鸿蒙语音合成相关模块  
import { textToSpeech } from '@kit.CoreSpeechKit';  
import { BusinessError } from '@kit.BasicServicesKit';  
// 导入公共接口定义  
import { LimeTTS, SpeakOptions, VoiceInfo, SpeechStatus, SpeechCallback } from '../interface.uts';

4.1 核心实现类

uni_modules/lime-tts/utssdk/app-harmony/index.uts文件中,定义了LimeTTSImpl类,该类是鸿蒙平台上TTS功能的核心实现:

class LimeTTSImpl implements LimeTTS {  
    private engine : textToSpeech.TextToSpeechEngine | null = null;  
    private currentVoice : textToSpeech.CreateEngineParams | null = null;  
    private eventListeners : Map<string, Array<SpeechCallback>> = new Map<string, Array<SpeechCallback>>();  
    private isSpeaking : boolean = false;  
    private isPaused : boolean = false;  
    // ...  
}

4.2 引擎初始化

在构造函数中,插件会初始化 TTS 引擎并设置相关回调:

private initialize(options : CreateEngineParams | null) {  
    // 清理旧引擎  
    if (this.engine) {  
        this.engine.shutdown();  
    }  
    const params : textToSpeech.CreateEngineParams = {  
        language: options?.language ?? 'zh-CN',  
        person: options?.person ?? 0,  
        online: 1  
    }  
    this.currentVoice = params  
    const _this = this  
    textToSpeech.createEngine(params).then((res : textToSpeech.TextToSpeechEngine) => {  
        this.engine = res  
        // 设置speak的回调信息  
        let speakListener : textToSpeech.SpeakListener = {  
            // 开始播报回调  
            onStart(requestId : string, response : textToSpeech.StartResponse) {  
                _this.isSpeaking = true;  
                _this.isPaused = false;  
                _this.emit('start', { type: 'start' })  
            },  
            // 其他回调...  
        };  
        this.engine.setListener(speakListener);  
    }).catch((err : BusinessError) => {  
        console.error(`Failed to create engine. Code: ${err.code}, message: ${err.message}.`);  
    });  
}

4.3 文本朗读功能

speak(text : string) : void  
speak(text : string, options : SpeakOptions | null = null) {  
    const extraParams : ESObject = {  
        speed: options?.speed ?? 1,  
        volume: options?.volume ?? 1,  
        pitch: options?.pitch ?? 1,  
        languageContext: options?.language ?? 'zh-CN'  
    } as ESObject  

    this.engine?.speak(text, {  
        requestId: this.generateRequestId(),  
        extraParams  
    } as textToSpeech.SpeakParams);  
}

4.4 音色管理

插件实现了音色列表获取和映射功能,将鸿蒙系统的原生音色信息转换为统一格式:

async getVoices() : Promise<VoiceInfo[]> {  
    try {  
        if (this.engine) {  
            // 使用引擎实例查询  
            const queryParams : textToSpeech.VoiceQuery = {  
                requestId: this.generateRequestId('voice_query'),  
                online: 1  
            };  

            const voices = await this.engine.listVoices(queryParams);  
            return this.mapToVoiceInfo(voices);  
        } else {  
            // 使用全局方法查询  
            const queryParams : textToSpeech.VoiceQuery = {  
                requestId: this.generateRequestId('voice_query'),  
                online: 1  
            };  

            const voices = await textToSpeech.listVoices(queryParams);  
            return this.mapToVoiceInfo(voices);  
        }  
    } catch (error) {  
        console.error('Failed to get voices:', error);  
        return Promise.resolve([])  
    }  
}  

private mapToVoiceInfo(voices : textToSpeech.VoiceInfo[]) : VoiceInfo[] {  
    return voices.map(voice => {  
        // 根据 person 值映射音色名称  
        let name = 'Unknown';  
        let gender : 'male' | 'female' = 'female';  

        switch (voice.person) {  
            case 0:  
            case 13:  
                name = '聆小珊';  
                gender = 'female';  
                break;  
            case 21:  
                name = '凌飞哲';  
                gender = 'male';  
                break;  
            case 8:  
                name = 'Laura';  
                gender = 'female';  
                break;  
        }  

        return {  
            language: voice.language.replace('_', '-'),  
            person: voice.person,  
            name: name,  
            gender: gender,  
            description: voice.description || `${name} ${gender === 'male' ? '男声' : '女声'}`,  
            status: voice.status === 'INSTALLED' ? 'available' :  
                voice.status === 'GA' ? 'downloadable' : 'unavailable'  
        } as VoiceInfo;  
    });  
}

五、测试与验证

在开发完成插件后,我们需要编写测试代码来验证功能是否正常工作。以下是一个用于测试Lime TTS插件的代码示例:

// 导入我们开发的 TTS 工具函数  
import { useTTS } from '@/uni_modules/lime-tts'  

// 创建 TTS 实例 - 测试实例化功能  
const tts = useTTS();  

// 设置事件监听 - 测试事件机制  
// 播放开始事件  
const onStart = (event) => {  
    console.log('语音播放开始');  
};  

// 播放结束事件  
const onEnd = (event) => {  
    console.log('语音播放结束');  
};  

// 错误处理事件  
const onError = (event) => {  
    console.error('语音播放错误:', event);  
};  

// 注册事件监听器  
tts.on('start', onStart);  
tts.on('end', onEnd);  
tts.on('error', onError);  

// 测试核心功能 - 文本朗读  
tts.speak('欢迎使用 Lime TTS 插件!', {  
    speed: 1.0,  // 语速  
    volume: 1.0, // 音量  
    pitch: 1.0   // 音调  
});  

// 测试控制功能  
setTimeout(() => {  
    // 测试暂停功能  
    tts.pause();  
}, 3000);  

// 测试状态查询功能  
const status = tts.getStatus();  
console.log('当前语音状态:', status);  

// 测试音色管理功能  
tts.getVoices().then(voices => {  
    console.log('可用音色列表:', voices);  
});  

// 测试资源释放 - 在组件卸载时调用  
const cleanup = () => {  
    // 移除事件监听器  
    tts.off('start', onStart);  
    tts.off('end', onEnd);  
    tts.off('error', onError);  
    // 销毁实例,释放系统资源  
    tts.destroy();  
};

六、鸿蒙平台特别说明

在鸿蒙平台上,Lime TTS 插件支持以下音色:

  • 中文女声:聆小珊(person: 13,推荐使用)
  • 中文男声:凌飞哲(person: 21,需要下载)
  • 英文女声:Laura(person: 8,需要下载)

七、总结

通过本文的学习,我们详细了解了如何使用UTS技术为uni-app开发鸿蒙平台的TTS插件。这个过程涵盖了从接口定义、模块导入到核心功能实现的完整开发流程,展示了如何通过调用鸿蒙原生的@kit.CoreSpeechKit能力,弥补uni-app在鸿蒙平台上speech API缺失的问题。

开发鸿蒙平台插件的关键要点包括:

  1. 设计统一的跨平台接口,确保API一致性
  2. 正确导入鸿蒙系统模块,如@kit.CoreSpeechKit@kit.BasicServicesKit
  3. 实现核心功能类,处理平台特定的API细节
  4. 封装事件监听机制,提供良好的开发者体验
  5. 处理音色映射和状态管理,确保功能完整性

希望本文能为您学习如何开发uni-app鸿蒙插件提供有价值的参考,帮助您掌握UTS开发技术,为uni-app生态贡献更多优质的平台适配插件。

目前,本插件已上传至uni-app插件市场,全面支持uni-app和uni-appx框架,并兼容安卓、鸿蒙和Web三大平台,iOS平台将在稍晚上线。开发者可以直接在项目中集成使用,体验跨平台的TTS功能。lime-tts

继续阅读 »

从零实现 uni-app 鸿蒙平台 TTS 插件:UTS 开发实践指南

随着移动应用的日益普及,文本转语音(TTS)功能已经成为提升用户体验的重要组成部分。在跨平台开发中,我们常常需要为不同平台提供一致的语音合成能力。

uni-app框架在安卓和IOS提供speech能力,但在鸿蒙(HarmonyOS)平台上,不提供speech支持。本文将详细介绍如何通过UTS技术开发一个鸿蒙平台的TTS插件,弥补这一功能空白,并学习如何实现跨平台接口的一致性。

一、项目概述

本项目将开发一个名为Lime TTS的基于UTS(Uni TypeScript)的文本转语音插件,主要目标是为uni-app在鸿蒙平台上提供语音合成能力。通过本项目的学习,我们将掌握如何使用UTS技术调用鸿蒙原生API,实现跨平台插件开发的核心技能。

二、项目目录结构

我们的Lime TTS项目采用标准的uni-app UTS插件结构,清晰地分离了接口定义和平台实现。这种结构设计有助于我们组织代码,并确保跨平台实现的一致性。项目的目录结构如下:

uni_modules/  
└── lime-tts/  
    ├── changelog.md  
    ├── package.json  
    ├── readme.md  
    └── utssdk/  
        ├── app-android/  
        │   ├── config.json  
        │   └── index.uts  
        ├── app-harmony/  
        │   ├── config.json  
        │   └── index.uts  
        ├── app-ios/  
        │   ├── config.json  
        │   └── index.uts  
        ├── interface.uts  
        ├── unierror.uts  
        └── web/  
            └── index.uts
  • utssdk/interface.uts:定义了插件的公共接口和类型,是插件的核心规范文件。
  • utssdk/app-harmony/index.uts:鸿蒙平台的具体实现代码。

三、接口设计与定义

3.1 核心接口和类型设计

Lime TTS 插件通过 uni_modules/lime-tts/utssdk/interface.uts 文件定义了一套完整的类型和接口规范,确保了良好的类型安全性和代码提示。

语音合成选项

export type SpeakOptions = {  
    /**  
     * 语速  
     * 可选,支持范围 [0.5-2],默认值为 1  
     */  
    speed ?: number;  
    /**  
     * 音量  
     * 可选,支持范围 [0-2],默认值为 1  
     */  
    volume ?: number;  
    /**  
     * 音调  
     * 可选,支持范围 [0.5-2],默认值为 1  
     */  
    pitch ?: number;  
    /**  
     * 语境,播放阿拉伯数字用的语种  
     * 可选,支持 "zh-CN" 中文与 "en-US" 英文,默认 "zh-CN"  
     */  
    language ?: Language;  
    // 其他配置项...  
}

音色信息

export type VoiceInfo = {  
    language : Language;  
    person : number;  
    name : string;  
    gender : 'male' | 'female';  
    description : string;  
    status : 'available' | 'downloadable' | 'unavailable';  
}

语音状态

export type SpeechStatus = 'idle' | 'speaking' | 'paused' | 'uninitialized';

3.2 主要功能接口

export interface LimeTTS {  
    /**  
     * 朗读指定文本  
     * @param text 要朗读的文本  
     * @param options 可选参数,如语音ID、语速等  
     */  
    speak(text : string) : void;  
    speak(text : string, options : SpeakOptions | null) : void;  

    /**  
     * 停止当前朗读  
     */  
    stop() : void;  

    /**  
     * 暂停当前朗读  
     */  
    pause() : void;  

    /**  
     * 获取可用语音列表  
     */  
    getVoices() : Promise<VoiceInfo[]>;  

    /**  
     * 获取当前语音状态  
     */  
    getStatus() : SpeechStatus;  

    /**  
     * 销毁TTS实例,释放资源  
     */  
    destroy() : void;  

    /**  
     * 监听TTS事件  
     * @param event 事件类型  
     * @param callback 回调函数  
     */  
    on(event : 'start' | 'end' | 'error' | 'stop', callback : SpeechCallback) : void;  
    off(event : 'start' | 'end' | 'error' | 'stop') : void;  
    off(event : 'start' | 'end' | 'error' | 'stop', callback : SpeechCallback | null) : void;  
}

四、鸿蒙平台实现详解

在本节中,我们将详细学习如何为鸿蒙平台实现TTS功能。我们将通过调用鸿蒙原生的@kit.CoreSpeechKit来实现语音合成,并遵循之前设计的接口规范。

4.0 模块导入

在鸿蒙平台实现中,首先需要导入鸿蒙系统提供的核心语音合成模块:

// 导入鸿蒙语音合成相关模块  
import { textToSpeech } from '@kit.CoreSpeechKit';  
import { BusinessError } from '@kit.BasicServicesKit';  
// 导入公共接口定义  
import { LimeTTS, SpeakOptions, VoiceInfo, SpeechStatus, SpeechCallback } from '../interface.uts';

4.1 核心实现类

uni_modules/lime-tts/utssdk/app-harmony/index.uts文件中,定义了LimeTTSImpl类,该类是鸿蒙平台上TTS功能的核心实现:

class LimeTTSImpl implements LimeTTS {  
    private engine : textToSpeech.TextToSpeechEngine | null = null;  
    private currentVoice : textToSpeech.CreateEngineParams | null = null;  
    private eventListeners : Map<string, Array<SpeechCallback>> = new Map<string, Array<SpeechCallback>>();  
    private isSpeaking : boolean = false;  
    private isPaused : boolean = false;  
    // ...  
}

4.2 引擎初始化

在构造函数中,插件会初始化 TTS 引擎并设置相关回调:

private initialize(options : CreateEngineParams | null) {  
    // 清理旧引擎  
    if (this.engine) {  
        this.engine.shutdown();  
    }  
    const params : textToSpeech.CreateEngineParams = {  
        language: options?.language ?? 'zh-CN',  
        person: options?.person ?? 0,  
        online: 1  
    }  
    this.currentVoice = params  
    const _this = this  
    textToSpeech.createEngine(params).then((res : textToSpeech.TextToSpeechEngine) => {  
        this.engine = res  
        // 设置speak的回调信息  
        let speakListener : textToSpeech.SpeakListener = {  
            // 开始播报回调  
            onStart(requestId : string, response : textToSpeech.StartResponse) {  
                _this.isSpeaking = true;  
                _this.isPaused = false;  
                _this.emit('start', { type: 'start' })  
            },  
            // 其他回调...  
        };  
        this.engine.setListener(speakListener);  
    }).catch((err : BusinessError) => {  
        console.error(`Failed to create engine. Code: ${err.code}, message: ${err.message}.`);  
    });  
}

4.3 文本朗读功能

speak(text : string) : void  
speak(text : string, options : SpeakOptions | null = null) {  
    const extraParams : ESObject = {  
        speed: options?.speed ?? 1,  
        volume: options?.volume ?? 1,  
        pitch: options?.pitch ?? 1,  
        languageContext: options?.language ?? 'zh-CN'  
    } as ESObject  

    this.engine?.speak(text, {  
        requestId: this.generateRequestId(),  
        extraParams  
    } as textToSpeech.SpeakParams);  
}

4.4 音色管理

插件实现了音色列表获取和映射功能,将鸿蒙系统的原生音色信息转换为统一格式:

async getVoices() : Promise<VoiceInfo[]> {  
    try {  
        if (this.engine) {  
            // 使用引擎实例查询  
            const queryParams : textToSpeech.VoiceQuery = {  
                requestId: this.generateRequestId('voice_query'),  
                online: 1  
            };  

            const voices = await this.engine.listVoices(queryParams);  
            return this.mapToVoiceInfo(voices);  
        } else {  
            // 使用全局方法查询  
            const queryParams : textToSpeech.VoiceQuery = {  
                requestId: this.generateRequestId('voice_query'),  
                online: 1  
            };  

            const voices = await textToSpeech.listVoices(queryParams);  
            return this.mapToVoiceInfo(voices);  
        }  
    } catch (error) {  
        console.error('Failed to get voices:', error);  
        return Promise.resolve([])  
    }  
}  

private mapToVoiceInfo(voices : textToSpeech.VoiceInfo[]) : VoiceInfo[] {  
    return voices.map(voice => {  
        // 根据 person 值映射音色名称  
        let name = 'Unknown';  
        let gender : 'male' | 'female' = 'female';  

        switch (voice.person) {  
            case 0:  
            case 13:  
                name = '聆小珊';  
                gender = 'female';  
                break;  
            case 21:  
                name = '凌飞哲';  
                gender = 'male';  
                break;  
            case 8:  
                name = 'Laura';  
                gender = 'female';  
                break;  
        }  

        return {  
            language: voice.language.replace('_', '-'),  
            person: voice.person,  
            name: name,  
            gender: gender,  
            description: voice.description || `${name} ${gender === 'male' ? '男声' : '女声'}`,  
            status: voice.status === 'INSTALLED' ? 'available' :  
                voice.status === 'GA' ? 'downloadable' : 'unavailable'  
        } as VoiceInfo;  
    });  
}

五、测试与验证

在开发完成插件后,我们需要编写测试代码来验证功能是否正常工作。以下是一个用于测试Lime TTS插件的代码示例:

// 导入我们开发的 TTS 工具函数  
import { useTTS } from '@/uni_modules/lime-tts'  

// 创建 TTS 实例 - 测试实例化功能  
const tts = useTTS();  

// 设置事件监听 - 测试事件机制  
// 播放开始事件  
const onStart = (event) => {  
    console.log('语音播放开始');  
};  

// 播放结束事件  
const onEnd = (event) => {  
    console.log('语音播放结束');  
};  

// 错误处理事件  
const onError = (event) => {  
    console.error('语音播放错误:', event);  
};  

// 注册事件监听器  
tts.on('start', onStart);  
tts.on('end', onEnd);  
tts.on('error', onError);  

// 测试核心功能 - 文本朗读  
tts.speak('欢迎使用 Lime TTS 插件!', {  
    speed: 1.0,  // 语速  
    volume: 1.0, // 音量  
    pitch: 1.0   // 音调  
});  

// 测试控制功能  
setTimeout(() => {  
    // 测试暂停功能  
    tts.pause();  
}, 3000);  

// 测试状态查询功能  
const status = tts.getStatus();  
console.log('当前语音状态:', status);  

// 测试音色管理功能  
tts.getVoices().then(voices => {  
    console.log('可用音色列表:', voices);  
});  

// 测试资源释放 - 在组件卸载时调用  
const cleanup = () => {  
    // 移除事件监听器  
    tts.off('start', onStart);  
    tts.off('end', onEnd);  
    tts.off('error', onError);  
    // 销毁实例,释放系统资源  
    tts.destroy();  
};

六、鸿蒙平台特别说明

在鸿蒙平台上,Lime TTS 插件支持以下音色:

  • 中文女声:聆小珊(person: 13,推荐使用)
  • 中文男声:凌飞哲(person: 21,需要下载)
  • 英文女声:Laura(person: 8,需要下载)

七、总结

通过本文的学习,我们详细了解了如何使用UTS技术为uni-app开发鸿蒙平台的TTS插件。这个过程涵盖了从接口定义、模块导入到核心功能实现的完整开发流程,展示了如何通过调用鸿蒙原生的@kit.CoreSpeechKit能力,弥补uni-app在鸿蒙平台上speech API缺失的问题。

开发鸿蒙平台插件的关键要点包括:

  1. 设计统一的跨平台接口,确保API一致性
  2. 正确导入鸿蒙系统模块,如@kit.CoreSpeechKit@kit.BasicServicesKit
  3. 实现核心功能类,处理平台特定的API细节
  4. 封装事件监听机制,提供良好的开发者体验
  5. 处理音色映射和状态管理,确保功能完整性

希望本文能为您学习如何开发uni-app鸿蒙插件提供有价值的参考,帮助您掌握UTS开发技术,为uni-app生态贡献更多优质的平台适配插件。

目前,本插件已上传至uni-app插件市场,全面支持uni-app和uni-appx框架,并兼容安卓、鸿蒙和Web三大平台,iOS平台将在稍晚上线。开发者可以直接在项目中集成使用,体验跨平台的TTS功能。lime-tts

收起阅读 »

【鸿蒙征文】关于鸿蒙折叠屏(宽屏设备)适配的一些分享

鸿蒙折叠屏适配 宽屏适配 鸿蒙征文

随着鸿蒙系统的不断发展以及uni-app x 对鸿蒙的全面支持,相信使用 uni-app X 进行鸿蒙应用及跨平台应用开发,是前端开发的最优选择。近期完成了对公司的已有应用进行鸿蒙平台适配的适配工作,有了 uni-app X 为基础,整个过程变得“轻松、高效”!

在折叠屏的适配过程中获得了一些经验,分享给大家(仅是自己的一些想法,欢迎大家指正~)。

适配目标效果


如上图所示:屏幕展开时(宽屏),卡片为一行两列布局,屏幕折叠时(窄屏)变成一行一个卡片。

原理分享

1 在页面初始化时获取屏幕宽度,识别运行屏幕环境是否为宽屏;
2 在屏幕宽度变化时,重新判处屏幕运行环境;
3 创建对应的 flex 布局样式,使用 :class="" 进行动态样式切换,达到宽屏及折叠屏幕折叠、展开的适配;

源码分享

<template>  
    <view :class="['flex', isTable?'rows':'column', isTable?'flex-wrap':'']">  
        <view   
        v-for="(item, idx) in 10" :key="idx"  
        :class="['item', isTable ? 'w-half':'w-full']" >  
            <text class="text">{{idx}}</text>  
        </view>  
    </view>  
</template>  
<script>  
export default {  
    data() {  
        return {  
            title: 'Hello',  
            windowWidth : 350, // 保留变量,可以记录屏幕宽度,单位 px  
            isTable : false,  
        }  
    },  
    onReady() {  
        //  
        let info = uni.getWindowInfo();  
        this.isTable = info.windowWidth > 400;  
        console.log(this.isTable);  
    },  
    methods: {  

    },  
    onResize : function(e){  
        this.isTable = e.size.windowWidth > 400;  
        console.log(this.isTable);  
    }  
}  
</script>  
<style>  
.flex{display:flex;}  
.rows{flex-direction:row;}  
.column{flex-direction:column;}  
.flex-wrap{flex-wrap:wrap;}  

.item{background-color:#FF0036;}  
.w-full{width:700rpx; height:300rpx; margin:25rpx;}  
.w-half{width:48%; height:200rpx; margin:20rpx 1%;}  
.text{font-size:20px;}  
</style>

总结

1 演示代码通过监听屏幕尺寸变化,使用 isTable 变量记录了屏幕是否为宽屏,然后通过 flex 布局切换,实现了布局目标;
2 演示代码虽然简单,经过自己的布局设计,完全可以适配 宽屏、折叠屏(动态监听折叠事件);
3 如果您在宽屏应用中遇到字体尺寸问题,可以以 text 组件为基础封装一个自己的文本组件,针对不同屏幕尺寸设置不同的字体大小(可以将 isTable 作为属性传递到组件内,通过 watch 观察屏幕变化,动态切换字体尺寸);
4 如果担心 onResize 监听多次触发引起的效率问题,可以使用 setTimeout 函数实现防抖,仅执行短时间内的一次变化即可;

左右功能不同的布局

如果您开发的应用在宽屏模式下,左侧为列表,右侧为详情:
您可以以 isTable 为核心,使用 if 进行右侧功能的条件渲染,如 : 宽屏模式

<view v-if="isTable">右侧功能区</view>

当我们点击列表项目时调用对应函数,同样判断是否宽屏模式,如果是再右侧详情区域展示详情,反之,打开一个新的页面展示详情即可。

一个很基础的原理分享,希望对大家的鸿蒙之旅有所帮助,谢谢阅读。

继续阅读 »

随着鸿蒙系统的不断发展以及uni-app x 对鸿蒙的全面支持,相信使用 uni-app X 进行鸿蒙应用及跨平台应用开发,是前端开发的最优选择。近期完成了对公司的已有应用进行鸿蒙平台适配的适配工作,有了 uni-app X 为基础,整个过程变得“轻松、高效”!

在折叠屏的适配过程中获得了一些经验,分享给大家(仅是自己的一些想法,欢迎大家指正~)。

适配目标效果


如上图所示:屏幕展开时(宽屏),卡片为一行两列布局,屏幕折叠时(窄屏)变成一行一个卡片。

原理分享

1 在页面初始化时获取屏幕宽度,识别运行屏幕环境是否为宽屏;
2 在屏幕宽度变化时,重新判处屏幕运行环境;
3 创建对应的 flex 布局样式,使用 :class="" 进行动态样式切换,达到宽屏及折叠屏幕折叠、展开的适配;

源码分享

<template>  
    <view :class="['flex', isTable?'rows':'column', isTable?'flex-wrap':'']">  
        <view   
        v-for="(item, idx) in 10" :key="idx"  
        :class="['item', isTable ? 'w-half':'w-full']" >  
            <text class="text">{{idx}}</text>  
        </view>  
    </view>  
</template>  
<script>  
export default {  
    data() {  
        return {  
            title: 'Hello',  
            windowWidth : 350, // 保留变量,可以记录屏幕宽度,单位 px  
            isTable : false,  
        }  
    },  
    onReady() {  
        //  
        let info = uni.getWindowInfo();  
        this.isTable = info.windowWidth > 400;  
        console.log(this.isTable);  
    },  
    methods: {  

    },  
    onResize : function(e){  
        this.isTable = e.size.windowWidth > 400;  
        console.log(this.isTable);  
    }  
}  
</script>  
<style>  
.flex{display:flex;}  
.rows{flex-direction:row;}  
.column{flex-direction:column;}  
.flex-wrap{flex-wrap:wrap;}  

.item{background-color:#FF0036;}  
.w-full{width:700rpx; height:300rpx; margin:25rpx;}  
.w-half{width:48%; height:200rpx; margin:20rpx 1%;}  
.text{font-size:20px;}  
</style>

总结

1 演示代码通过监听屏幕尺寸变化,使用 isTable 变量记录了屏幕是否为宽屏,然后通过 flex 布局切换,实现了布局目标;
2 演示代码虽然简单,经过自己的布局设计,完全可以适配 宽屏、折叠屏(动态监听折叠事件);
3 如果您在宽屏应用中遇到字体尺寸问题,可以以 text 组件为基础封装一个自己的文本组件,针对不同屏幕尺寸设置不同的字体大小(可以将 isTable 作为属性传递到组件内,通过 watch 观察屏幕变化,动态切换字体尺寸);
4 如果担心 onResize 监听多次触发引起的效率问题,可以使用 setTimeout 函数实现防抖,仅执行短时间内的一次变化即可;

左右功能不同的布局

如果您开发的应用在宽屏模式下,左侧为列表,右侧为详情:
您可以以 isTable 为核心,使用 if 进行右侧功能的条件渲染,如 : 宽屏模式

<view v-if="isTable">右侧功能区</view>

当我们点击列表项目时调用对应函数,同样判断是否宽屏模式,如果是再右侧详情区域展示详情,反之,打开一个新的页面展示详情即可。

一个很基础的原理分享,希望对大家的鸿蒙之旅有所帮助,谢谢阅读。

收起阅读 »