图片URL格式化工具
对图片相对路径自动拼接CDN域名,富文本图片批量处理 HTML实体解码
详细描述:## 功能
-
formatImage(url) — 智能补全图片地址
- 完整 http/https 地址原样返回
- 协议相对地址
//xxx.com转为https://xxx.com - 相对路径自动拼接 CDN 域名
-
formatRichText(html) — 富文本处理
- 批量替换图片 src 为完整 CDN 地址
- 自动添加图片自适应样式(max-width:100%)
- 解码 HTML 实体(& < > 等)
适用场景
- 商品详情、文章内容的富文本展示
- 用户头像、商品图片等资源路径补全
- 后端返回的图片相对路径转完整 URL
使用示例
import { setImageOrigin, formatImage } from '@/utils/image'
// 1. 在 App.vue 中配置 CDN 域名
setImageOrigin('https://cdn.example.com')
// 2. 格式化图片
formatImage('/uploads/photo.jpg')
// → https://cdn.example.com/uploads/photo.jpg
// 3. 处理富文本
formatRichText('<img src="/uploads/1.jpg" />')
兼容性
支持 uni-app 全平台(H5、iOS App、Android App、各小程序) 对图片相对路径自动拼接CDN域名,富文本图片批量处理 HTML实体解码
详细描述:## 功能
-
formatImage(url) — 智能补全图片地址
- 完整 http/https 地址原样返回
- 协议相对地址
//xxx.com转为https://xxx.com - 相对路径自动拼接 CDN 域名
-
formatRichText(html) — 富文本处理
- 批量替换图片 src 为完整 CDN 地址
- 自动添加图片自适应样式(max-width:100%)
- 解码 HTML 实体(& < > 等)
适用场景
- 商品详情、文章内容的富文本展示
- 用户头像、商品图片等资源路径补全
- 后端返回的图片相对路径转完整 URL
使用示例
import { setImageOrigin, formatImage } from '@/utils/image'
// 1. 在 App.vue 中配置 CDN 域名
setImageOrigin('https://cdn.example.com')
// 2. 格式化图片
formatImage('/uploads/photo.jpg')
// → https://cdn.example.com/uploads/photo.jpg
// 3. 处理富文本
formatRichText('<img src="/uploads/1.jpg" />')
兼容性
支持 uni-app 全平台(H5、iOS App、Android App、各小程序) 收起阅读 »
4人团队全职接单,从广州回南宁创业,10年以上经验,时间充裕,可上门考察,直接技术一对一,求靠谱单子!
【南宁本地·全栈开发团队】10年老兵返乡创业,4人全职,无销售套路,直接技术一对一!
✅ 我们能做: 小程序 · APP · 网站 · 公众号 · 后台管理系统 · 定制化企业软件
✅ 我们的底气: 前广州一线全栈,人均10年+开发经验,全部技术骨干出身
✅ 您的权益: 源码一次性全交付 · 后台不加密不锁死 · 同城可上门面谈,全国线上协作
✅ 特别说明: 初创期精力过剩,同行大量甩单也接,价格美丽
📍 南宁本地可考察,欢迎随时来坐坐
📱 微信:anma1001(加我备注“接单”,直接发需求)
【南宁本地·全栈开发团队】10年老兵返乡创业,4人全职,无销售套路,直接技术一对一!
✅ 我们能做: 小程序 · APP · 网站 · 公众号 · 后台管理系统 · 定制化企业软件
✅ 我们的底气: 前广州一线全栈,人均10年+开发经验,全部技术骨干出身
✅ 您的权益: 源码一次性全交付 · 后台不加密不锁死 · 同城可上门面谈,全国线上协作
✅ 特别说明: 初创期精力过剩,同行大量甩单也接,价格美丽
📍 南宁本地可考察,欢迎随时来坐坐
📱 微信:anma1001(加我备注“接单”,直接发需求)
iOS 性能监控与调试工具选择 整合iOS性能监控和调试
'''开发同学应该都遇到过这种情况:排查 App 性能问题时,Mac 上开着 Instruments 跑耗时分析,Xcode 控制台挂着看日志输出,再用 iMazing 导一份沙箱文件出来——几个工具来回切,一套流程走下来半天就过去了。这还只是性能排查,如果还要分析崩溃日志、检查文件完整性、解密 App 数据,工具链还会拉得更长,每个工具都有自己的连接方式和操作逻辑。
常见方案:各司其职,但互不连通
Instruments 是苹果官方的性能分析工具,Time Profiler 查 CPU 热点、Allocations 看内存分配、GPU Driver 看渲染性能,每个模板都对应一个独立场景。工具本身功能强大,但启动慢、调试流程偏重,适合做深度优化时用,日常快速查看性能数据显得有些笨重。
Xcode Console 配合 OSLog 能输出 App 运行时日志,但日志量大时筛选过滤不太顺手,也没法保留长时间的历史记录。Crash 日志分析依赖 symbolicatecrash 脚本或者第三方平台,手动符号化操作比较繁琐。
文件管理这块,iMazing 和爱思助手能查看 iOS 设备的文件系统,但主要面向媒体文件备份和传输,对 App 沙箱目录的访问能力有限。要提取 App 运行时生成的数据文件,通常还需要越狱配合。
KeyMob:整合调试流程的另一种选择
KeyMob 把这几类需求集成到了一个工具里。性能监控面板能实时显示 CPU、GPU、内存占用和 FPS 帧率,数据以图表形式展示,时间线上的波动一目了然。卡顿检测会记录掉帧时的堆栈信息,不用像用 Instruments 那样先配置再重现。跑微信小程序或者 Unity3D 开发的 App 时同样能监控,兼容性覆盖得比较全。另外还能查看设备长达六个月的使用记录,每个 App 的启动时间、运行时长和电量消耗都有据可查。
日志查看功能可以按进程和 App 名称过滤输出,定位问题比在 Xcode Console 翻原始输出更直接。崩溃日志模块能直接导出设备的 crash report,自动完成符号化——设备连上电脑、选中日志、点击导出,三步操作就行,不需要跑 symbolicatecrash 命令。
文件管理部分可以浏览 App 的沙箱目录结构,把配置文件和数据缓存拖到电脑本地分析。解密功能提取 App 运行中保存的图片、音频和配置文件,查看数据存储格式和内容比较方便。USB 连接即可操作,不需要越狱。App 管理功能还能直接安装、卸载应用,查看进程信息和网络请求状态。
选择思路
Instruments 在深度性能分析上仍然不可替代。日常调试和快速排查场景下,KeyMob 把几个高频需求整合到一起,能减少工具间切换的成本。可以根据项目阶段和调试深度来搭配使用,各取所长就行。'''
'''开发同学应该都遇到过这种情况:排查 App 性能问题时,Mac 上开着 Instruments 跑耗时分析,Xcode 控制台挂着看日志输出,再用 iMazing 导一份沙箱文件出来——几个工具来回切,一套流程走下来半天就过去了。这还只是性能排查,如果还要分析崩溃日志、检查文件完整性、解密 App 数据,工具链还会拉得更长,每个工具都有自己的连接方式和操作逻辑。
常见方案:各司其职,但互不连通
Instruments 是苹果官方的性能分析工具,Time Profiler 查 CPU 热点、Allocations 看内存分配、GPU Driver 看渲染性能,每个模板都对应一个独立场景。工具本身功能强大,但启动慢、调试流程偏重,适合做深度优化时用,日常快速查看性能数据显得有些笨重。
Xcode Console 配合 OSLog 能输出 App 运行时日志,但日志量大时筛选过滤不太顺手,也没法保留长时间的历史记录。Crash 日志分析依赖 symbolicatecrash 脚本或者第三方平台,手动符号化操作比较繁琐。
文件管理这块,iMazing 和爱思助手能查看 iOS 设备的文件系统,但主要面向媒体文件备份和传输,对 App 沙箱目录的访问能力有限。要提取 App 运行时生成的数据文件,通常还需要越狱配合。
KeyMob:整合调试流程的另一种选择
KeyMob 把这几类需求集成到了一个工具里。性能监控面板能实时显示 CPU、GPU、内存占用和 FPS 帧率,数据以图表形式展示,时间线上的波动一目了然。卡顿检测会记录掉帧时的堆栈信息,不用像用 Instruments 那样先配置再重现。跑微信小程序或者 Unity3D 开发的 App 时同样能监控,兼容性覆盖得比较全。另外还能查看设备长达六个月的使用记录,每个 App 的启动时间、运行时长和电量消耗都有据可查。
日志查看功能可以按进程和 App 名称过滤输出,定位问题比在 Xcode Console 翻原始输出更直接。崩溃日志模块能直接导出设备的 crash report,自动完成符号化——设备连上电脑、选中日志、点击导出,三步操作就行,不需要跑 symbolicatecrash 命令。
文件管理部分可以浏览 App 的沙箱目录结构,把配置文件和数据缓存拖到电脑本地分析。解密功能提取 App 运行中保存的图片、音频和配置文件,查看数据存储格式和内容比较方便。USB 连接即可操作,不需要越狱。App 管理功能还能直接安装、卸载应用,查看进程信息和网络请求状态。
选择思路
Instruments 在深度性能分析上仍然不可替代。日常调试和快速排查场景下,KeyMob 把几个高频需求整合到一起,能减少工具间切换的成本。可以根据项目阶段和调试深度来搭配使用,各取所长就行。'''
收起阅读 »希望官方开放 uniappx 的 页面路由
例如页面跳转 uni.navigateTo
原生的页面转场很多丝滑表现,
例如 安卓里面的转场很丝滑,包含他头部的 标题栏 就像是增量切换一样,而uniappx 和uniapp里面的就像是一块布硬生生的拖过去
iOS 的 页面转场则更多丝滑效果
等等 路由api,就那么几个动画,方式,很基础,很多原生的特性度无法使用,希望官方能把 页面路由的切换开放,提供给我们开发者自定义处理页面的路由跳转
例如页面跳转 uni.navigateTo
原生的页面转场很多丝滑表现,
例如 安卓里面的转场很丝滑,包含他头部的 标题栏 就像是增量切换一样,而uniappx 和uniapp里面的就像是一块布硬生生的拖过去
iOS 的 页面转场则更多丝滑效果
等等 路由api,就那么几个动画,方式,很基础,很多原生的特性度无法使用,希望官方能把 页面路由的切换开放,提供给我们开发者自定义处理页面的路由跳转
收起阅读 »搭建三角洲陪玩护航小程序服务号平台需要什么?一站式打包部署支持微信小程序+h5+app
电竞陪玩、游戏代练和俱乐部陪练正在告别零散的‘人工接单时代’,一套功能齐全、支持独立部署的源码系统,是迈向‘系统化运营时代’的关键一步。
废话不多说,直接上干货!
一、需要资质
1.营业执照:个体户和企业执照都行,主要用于各种认证、小程序服务号及域名的备案等
2.法人信息:认证信息需要
3.对公账户:企业执照需要对公账户,个体户可用法人的银行卡;申请微信商户号用于平台收款
4.邮箱、手机号:申请各个平台的账号并登录
二、需要第三方产品
1.服务器:安装并承载系统的运行
2.域名:需备案,用于访问系统
3.服务号、小程序:都需要认证,且小程序要备案
三、源码系统
后端源码:Thinkphp6
前端源码:UNI-APP vue2.0(支持一键式发布 H5、微信小程序、安卓/iOS 苹果 app)
宝塔 HTTP://BT.CN: 安装后台、管理网站
下载护航陪玩系统源码
www.51duoke.cn/games/?id=1
✨ 核心功能模块
非租用版,源码交付、永久域名授权使用;独立部署、自主运营,不抽流水无年费,功能齐全可二开。电竞陪玩、游戏代练、三角洲行动护航、俱乐部陪练和工作室派单,正在从“人工接单时代”进入“系统化运营时代”。真正能长期跑起来的电竞护航系统,不只是做一个下单页面,也不是简单堆几个聊天和支付功能,而是要把用户获客、大神展示、订单派发、客服验收、工作室管理、会员体系、内容风控、数据分析和持续迭代全部串成闭环。
用户端功能
游戏分类与选择
护航手选择/发布需求
订单状态跟踪
在线沟通(集成自建 IM)
微信支付托管
老板端:高效下单,灵活选服 老板可快速筛选热门游戏类目,按需选择服务模式 —— 支持指定心仪打手、等待打手抢单或由平台客服专业派单,无需繁琐操作,3 步即可完成下单,精准匹配游戏需求。
大神端
接单管理
上传截图/完成证明
保证金押金系统
收益提现
打手排行榜
打手端:便捷接单,打造个人 IP 打手接收订单实时提示,可自主抢单或响应派单;完成服务后上传截图即可申请结算,流程简洁高效。此外,支持发布游戏动态、管理个人游戏卡片(展示战绩、擅长英雄等),助力打造个人品牌,吸引更多订单。
客服端
派单调度
订单监管
审核进度
线下发单
客服端:全程管控,保障履约 作为订单枢纽,客户端支持与老板、打手实时沟通,同步订单进度;提供派单分配、线下报单录入功能,同时通过审核打手完成截图的方式,确保服务质量,最终实现安全结单
管理后台
仪表盘数据可视化(订单量、交易额、用户增长)
用户与护航手管理(增删改查、封禁/解封)
订单管理与纠纷处理
财务管理(对账、佣金设置、提现审核)
内容管理(Banner、公告、活动发布)
审核管理(护航手资料审核)
系统设置(参数配置、游戏库管理)
适用人群
该系统适用于:
代练公司/工作室 - 快速建立私域客户,实现变现
游戏主播 - 直播间引流客户,实现下单管理
中小型网络公司 - 无需打手即可开展业务
工会 - 管理和运营打手团队
下载护航陪玩系统源码
电竞陪玩、游戏代练和俱乐部陪练正在告别零散的‘人工接单时代’,一套功能齐全、支持独立部署的源码系统,是迈向‘系统化运营时代’的关键一步。
废话不多说,直接上干货!
一、需要资质
1.营业执照:个体户和企业执照都行,主要用于各种认证、小程序服务号及域名的备案等
2.法人信息:认证信息需要
3.对公账户:企业执照需要对公账户,个体户可用法人的银行卡;申请微信商户号用于平台收款
4.邮箱、手机号:申请各个平台的账号并登录
二、需要第三方产品
1.服务器:安装并承载系统的运行
2.域名:需备案,用于访问系统
3.服务号、小程序:都需要认证,且小程序要备案
三、源码系统
后端源码:Thinkphp6
前端源码:UNI-APP vue2.0(支持一键式发布 H5、微信小程序、安卓/iOS 苹果 app)
宝塔 HTTP://BT.CN: 安装后台、管理网站
下载护航陪玩系统源码
www.51duoke.cn/games/?id=1
✨ 核心功能模块
非租用版,源码交付、永久域名授权使用;独立部署、自主运营,不抽流水无年费,功能齐全可二开。电竞陪玩、游戏代练、三角洲行动护航、俱乐部陪练和工作室派单,正在从“人工接单时代”进入“系统化运营时代”。真正能长期跑起来的电竞护航系统,不只是做一个下单页面,也不是简单堆几个聊天和支付功能,而是要把用户获客、大神展示、订单派发、客服验收、工作室管理、会员体系、内容风控、数据分析和持续迭代全部串成闭环。
用户端功能
游戏分类与选择
护航手选择/发布需求
订单状态跟踪
在线沟通(集成自建 IM)
微信支付托管
老板端:高效下单,灵活选服 老板可快速筛选热门游戏类目,按需选择服务模式 —— 支持指定心仪打手、等待打手抢单或由平台客服专业派单,无需繁琐操作,3 步即可完成下单,精准匹配游戏需求。
大神端
接单管理
上传截图/完成证明
保证金押金系统
收益提现
打手排行榜
打手端:便捷接单,打造个人 IP 打手接收订单实时提示,可自主抢单或响应派单;完成服务后上传截图即可申请结算,流程简洁高效。此外,支持发布游戏动态、管理个人游戏卡片(展示战绩、擅长英雄等),助力打造个人品牌,吸引更多订单。
客服端
派单调度
订单监管
审核进度
线下发单
客服端:全程管控,保障履约 作为订单枢纽,客户端支持与老板、打手实时沟通,同步订单进度;提供派单分配、线下报单录入功能,同时通过审核打手完成截图的方式,确保服务质量,最终实现安全结单
管理后台
仪表盘数据可视化(订单量、交易额、用户增长)
用户与护航手管理(增删改查、封禁/解封)
订单管理与纠纷处理
财务管理(对账、佣金设置、提现审核)
内容管理(Banner、公告、活动发布)
审核管理(护航手资料审核)
系统设置(参数配置、游戏库管理)
适用人群
该系统适用于:
代练公司/工作室 - 快速建立私域客户,实现变现
游戏主播 - 直播间引流客户,实现下单管理
中小型网络公司 - 无需打手即可开展业务
工会 - 管理和运营打手团队
下载护航陪玩系统源码
全平台(安卓,iOS,鸿蒙,微信小程序,h5)自定义+动态Tabbar+tabbar页面导航栏样式+tabbr数字角标 +动态主题+3种可选tabbar点击类型
点击查看插件
sunrains-tabbar 自定义 TabBar 插件使用指南
点击体验h5端
点击查看安卓端录屏效果
点击查看安卓端演示安装包
目录
- [1. 基础配置]
- [2. pages.json 配置]
- [3. 动态配置 TabBar]
- [4. 数字角标功能]
- [5. 小程序端特殊配置]
- [6. 主题配置]
- [7. page页跳转自定义tab页]
- [8. 拓展底部tabbar类型,(切换、弹出、进入)页面]
1. 基础配置
App.uvue onLaunch 初始化
import {initDefaultTabItems} from '@/uni_modules/sunrains-tabbar/tabbar-config.uts'
onLaunch(() => {
console.log('App Launch')
initDefaultTabItems()
}
1.1 配置 tabbar-config.uts 文件
在插件根目录 tabbar-config.uts 文件中,配置注册全局tab组件和导航栏设置。
完整示例:
import { setNavbarConfig, NavBarConfig, registerGlobalComponent, setDefaultTabItems, TabItem } from '@/uni_modules/sunrains-tabbar/utssdk/types.uts'
import { getStoreLoginItems,storeLoginItems } from './utssdk/handle'
//导入所有自定义tab页
import Recommend from '@/pages/home/recommend.uvue'
import Order from '@/pages/order/order.uvue'
import Mine from '@/pages/mine/mine.uvue'
import Detail from '@/pages/detail/detail.uvue'
//注册所有需要切换的tab页或需要弹出的页面 即类型为 tabPage:tab页面 tabPop:弹窗组件
registerGlobalComponent('home', Recommend)
registerGlobalComponent('order', Order)
registerGlobalComponent('mine', Mine)
registerGlobalComponent('detail', Detail)
// 2. 配置导航栏(不设置会使用默认配置)
setNavbarConfig(new NavBarConfig(true, 34, 'bold', '#333333', '#ffffff', "", 88))
//初始化默认tabbar区域按钮
export function initDefaultTabItems(){
/**
*
* 3. 设置默认 Tab 列表(至少设置一个固定的,也可以全部设置,其它可通过请求服务端接口动态配置),
* 或通过网络请求初始化 游客 显示哪些 有 角色的 按角色 初始化
* App.uvue onLaunch 初始化
import {initDefaultTabItems} from '@/uni_modules/sunrains-tabbar/tabbar-config.uts'
onLaunch(() => {
console.log('App Launch')
initDefaultTabItems()
}
*/
let home = new TabItem('home',"", 12, "red", "yellow", "bold", '首页', '/static/tab/main3.png', '/static/tab/main3_active.png',24,24,"tabPage")
let mine = new TabItem('mine',"", 12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png',24,24,"tabPage")
let items = getStoreLoginItems();
//判断是否登录
if(false || items != null){
setDefaultTabItems(items)
}else{
setDefaultTabItems([
home,mine
])
storeLoginItems([home,mine])
}
}
关键步骤说明:
- 导入组件:将所有需要作为 Tab 的页面组件导入
- 注册组件:使用
registerGlobalComponent注册每个组件(第一个参数为唯一标识) - 配置导航栏:可选配置,根据项目需求调整
- 设置默认 Tab:至少配置一个固定 Tab,其余可动态添加
2. pages.json 配置
2.1 必需配置
必须在 pages.json 中配置自定义 TabBar 入口页面(必须放在第一个位置),tab页无需在pages.json中配置:
{
"path": "uni_modules/sunrains-tabbar/index",
"style": {
"navigationStyle": "custom"
}
}
2.2 完整示例
{
"pages": [
{
"path": "uni_modules/sunrains-tabbar/utssdk/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "详情"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app x",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"uniIdRouter": {}
}
注意事项:
- 自定义 TabBar 入口页面必须配置在
pages数组的第一位 - 所有 Tab 页面不需要在
pages中声明 navigationStyle设置为"custom"以隐藏原生导航栏
3. 动态配置 TabBar
3.1 使用场景
当需要根据用户权限、服务端配置等动态调整 TabBar 时,可以在任意页面触发刷新。
3.2 实现示例
以下面文档 mine.uvue 页面为例:
关键点:
- 使用 refreshTabbar方法触发 TabBar 刷新
- 确保所有动态 Tab 对应的组件已在
tabbar-config.uts中注册
4. 数字角标功能
4.1 功能说明
支持为任意 Tab 设置数字角标,用于显示未读消息数、待处理事项等。
特性:
- ✅ 动态设置/更新角标
- ✅ 超过 99 自动显示 "99+"
- ✅ 角标为 0 或 null 时不显示
- ✅ 响应式更新,立即生效
4.2 使用方法
方法一:通过事件设置(推荐)
在任意页面通过 调用 setTabBadge 方法:
import { setTabBadge} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
// 设置算法 tab 的角标为 5
setTabBadge("order",5)
// 清除角标(设置为 0)
setTabBadge("order",0)
// 设置超过 99 显示 99+
setTabBadge("order",150)
方法二:初始化时设置
在创建 TabItem 时传入 badge 参数:
import { TabItem } from '@/uni_modules/sunrains-tabbar/utssdk/types.uts'
// 带角标的 tab
new TabItem(
'order',
'算法',
'/static/tab/main2.png',
'/static/tab/main2_active.png'
10 // badge 参数
)
// 不带角标的 tab
new TabItem(
'home',
'首页',
'/static/tab/main1.png',
'/static/tab/main1_active.png'
)
4.3 完整示例
以 文档后面 mine.uvue 页面为例,实现动态设置角标功能:
4.4 实际应用场景
场景 1:算法未读数
// 从服务端获取未读算法数
async function loadUnreadOrderCount() {
const res = await getOrderCount()
setTabBadge("order",res.data.count)
}
// 页面加载时调用
onShow(() => {
loadUnreadOrderCount()
})
场景 2:消息通知
// WebSocket 收到新消息
websocket.onMessage((msg) => {
if (msg.type === 'new_message') {
const currentBadge = getCurrentBadge('message')
setTabBadge("newMessage",currentBadge + 1)
}
})
场景 3:购物车数量
// 添加商品到购物车后更新角标
function addToCart(product: Product) {
cartItems.push(product)
setTabBadge("cart",cartItems.length)
}
5. 小程序端特殊配置
4.1 配置 tab-content-renderer.uvue
注意: 仅在需要支持微信小程序时需要配置,App 端可忽略此步骤。
在 uni_modules/sunrains-tabbar/tab-content-renderer.uvue 中配置:
<<template>
<view style="width: 100%; height: 100%;">
<Recommend v-if="currentTab === 'home'" style="width: 100%; height: 100%;" />
<Order v-if="currentTab === 'order'" style="width: 100%; height: 100%;" />
<Mine v-if="currentTab === 'mine'" style="width: 100%; height: 100%;" />
<Detail v-if="currentTab === 'detail'" style="width: 100%; height: 100%;" />
</view>
</template>
<script setup lang="uts">
import Recommend from '@/pages/home/recommend.uvue'
import Order from '@/pages/order/order.uvue'
import Mine from '@/pages/mine/mine.uvue'
import Detail from '@/pages/detail/detail.uvue'
interface Props {
currentTab: string
}
defineProps({
currentTab:{
type:String
}
})
</script>
配置说明:
- 条件编译:使用
#ifndef MP-WEIXIN和#ifdef MP-WEIXIN区分平台 - App 端:使用
<component :is="...">动态渲染组件 - 小程序端:必须静态导入并声明所有可能的 Tab 组件
- 导入位置:在
#ifdef MP-WEIXIN块内导入组件
6. 主题配置
6.1 主题功能说明
插件支持自定义 TabBar 的主题样式,支持网络背景图、本地图片背景图、纯颜色背景(支持rgba透明)、导航栏、tabbar区域背景、图标大小、图标(支持网络图标)、文字颜色、文字大小、等属性 满足不同项目的视觉需求。
主题特性:
- ✅ 支持全局主题配置
- ✅ 支持动态切换主题
- ✅ 支持自定义主题参数
6.2 初始化时配置主题
在 tabbar-config.uts 文件中配置主题:
import {
setNavbarConfig,
NavBarConfig,
registerGlobalComponent,
setDefaultTabItems,
TabItem,
setTabBarTheme,
TabBarTheme
} from '@/uni_modules/sunrains-tabbar/types.uts'
import { setTabPageTheme} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
let bgType : BgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/2.png"
//配置导航栏(可选,不设置会使用默认配置)
let navBarConfig : NavBarConfig = new NavBarConfig(
true, // 是否显示导航栏
34, // 字体大小
'bold', // 字体粗细
'#FFA500', // 文字颜色
'',// 背景颜色
"", //背景图片(支持网络图片)
90) // 导航栏高度
let tabbarIcon = new TabbarIcon(
"mine",//自定义组件唯一标识
//非选中图标
"https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4",
//选中图标
"/static/tab/main3_active.png",
24,//图标宽 单位 px
24,//图标高 单位 px
//文字颜色
12,
//非选中文字大小
"green",
//选择文字颜色
"white",
//选择文字粗细
"bold"
)
let tabbarIcons : Map<string,TabbarIcon> = new Map();
tabbarIcons.set("mine",tabbarIcon)
let tabbarStyle : TabbarStyle = new TabbarStyle(
//tabbar区域高度
120,
//tabbar区域背景
"rgba(21, 20, 31, 0.8)",
//tabbar图标文字样式
tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
6.3 动态修改主题
在任意页面通过调用 setTabPageTheme 方法动态修改主题:
import { setTabPageTheme} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
// 动态修改背景色和选中颜色
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
6.4 在页面中动态切换主题
以 mine.uvue 页面为例,实现主题切换功能:
<template>
<scroll-view direction="vertical" class="mine-page"
show-scrollbar = "false"
enable-back-to-top="true"
refresher-enabled="true"
refresher-default-style="none"
refresher-background="rgba(232, 201, 183, 0.5)"
:refresher-triggered="data.refreshing2"
refresher-max-drag-distance="200px"
@refresherpulling="onRefresherpulling2"
@refresherrefresh="onRefresherrefresh2"
@refresherrestore="onRefresherrestore2"
>
<uni-refresh-box
slot="refresher"
:pulling-distance="data.pullingDistance2"
:refreshing="data.refreshing2"
loading-class="loading-dark"
text-class="uni-text-class-buildin"
style="flex-direction: column;height: 46px;padding-top: 6px;"
pulling-text="继续下拉可刷新"
loosing-text="释放后会刷新"
loading-text="奋力加载中..."
/>
<view class="page-title">
<text style="color:#ff4d40">动态设置中心</text>
</view>
<view class="tabbar">
<text class="title-text">tabbar设置</text>
<button class="tabbar-btn" @click="refresh1">动态tabbar(我的)</button>
<button class="tabbar-btn" @click="refresh2">动态tabbar(首页、我的)</button>
<button class="tabbar-btn" @click="refresh3">动态tabbar(算法、我的)</button>
<button class="tabbar-btn" @click="refresh4">动态tabbar(我的、算法、首页、弹出详情、进入详情)</button>
<text class="title-text">设置角标</text>
<input class="input-item" v-model="tabbar" placeholder="请输入tabbar" />
<input class="input-item" v-model="badge" placeholder="请输入角标" />
<button class="tabbar-btn" @click="setBadge">确定</button>
<button class="tabbar-btn" @click="theme">主题1</button>
<button class="tabbar-btn" @click="theme2">主题2</button>
<button class="tabbar-btn" @click="theme3">主题3</button>
<button class="tabbar-btn" @click="clear">clear</button>
<!-- <button class="tabbar-btn" type="default" @click="refresh">刷新</button> -->
<!-- <button type="default" @click="cPop">返回</button> -->
</view>
</scroll-view>
</template>
<script setup lang="uts">
import { ref,onMounted,onUnmounted } from 'vue'
import { TabItem, GLOBAL_COMPONENT_REGISTRY, CustomTheme, TabbarIcon, TabbarStyle, NavBarConfig } from '@/uni_modules/sunrains-tabbar/utssdk/types.uts'
import { setTabBadge,setTabbarColorHeight,refreshTabbar ,setTabPageTheme,closeTabPop,clearStorage} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
function cPop(){
closeTabPop()
}
onMounted(() => {
console.log('【组件挂载====mine===】')
})
onUnmounted(() => {
console.log('【组件销毁====mine===】')
})
// 用于自动化测试的数据暴露 - 使用 reactive 将所有 ref 状态集中暴露
const data = reactive({
refreshing2: false,
pullingDistance2: 0
})
function onRefresherpulling2(e: RefresherEvent) {
data.pullingDistance2 = e.detail.dy
}
const refreshing = ref(false)
function onRefresherrefresh2() {
data.refreshing2 = true
console.log('触发刷新')
setTimeout(() => {
data.refreshing2 = false
console.log('刷新完成')
}, 1500)
}
function onRefresherrestore2() {
data.pullingDistance2 = 0
}
function onRefresherabort2() {
data.pullingDistance2 = 0
}
// 上拉触底加载
const loadMore = () => {
console.log("滚动到底部", "info")
}
function clear() {
clearStorage("all")
}
const bgColor = ref<string>('pink')
const tabHeight = ref<string>('120')
const badge = ref<string>('0')
const ss = ref<any>("");
// tab 选项配置
const tabbar = ref<string | null>(null)
const selectedTab = ref<string>('mine')
const selectedTabIndex = ref<number>(0)
function setBadge() {
//动态设置指定tabbar 角标
const badgeNum = parseInt(badge.value)
if (isNaN(badgeNum)) {
uni.showToast({
title: '请输入有效数字',
icon: 'none'
})
return
}
setTabBadge(tabbar.value,badgeNum)
uni.showToast({
title: `已设置 ${tabbar.value} 角标为 ${badgeNum}`,
icon: 'success'
})
}
function setStyle() {
setTabbarColorHeight(bgColor.value,parseInt(tabHeight.value) )
}
function refresh1() {
console.log("动态tabbar(我的)")
refreshTabbar([
new TabItem('mine', "",12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png',24,24,"tabPage")
]);
}
function refresh2() {
console.log("动态tabbar(首页、我的)")
refreshTabbar([
new TabItem('home', "",12, "red", "yellow", "bold", '首页', '/static/tab/main1.png', '/static/tab/main1_active.png',24,24,"tabPage"),
new TabItem('mine', "",12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png',24,24,"tabPage")
])
}
function refresh3() {
console.log("动态tabbar(算法、我的)")
refreshTabbar( [
new TabItem('order', "",12, "red", "yellow", "bold", '算法', '/static/tab/main2.png', '/static/tab/main2_active.png',24,24,"tabPage"),
new TabItem('mine', "",12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png',24,24,"tabPage")
])
}
function refresh4() {
console.log("动态tabbar(我的、算法、首页、弹出详情、进入详情)")
refreshTabbar([
new TabItem('mine', "",12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png',24,24,"tabPage"),
new TabItem('order', "",12, "red", "yellow", "bold", '算法', '/static/tab/main2.png', '/static/tab/main2_active.png',24,24,"tabPage"),
new TabItem('home', "",12, "red", "yellow", "bold", '首页', '/static/tab/main1.png',
'https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4',
24,24,"tabPage"
),
new TabItem('detail', "",12, "red", "yellow", "bold", '弹出详情', '/static/tab/main1.png',
'https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4',
24,24,"tabPop"
),
new TabItem('detail', "/pages/detail/detail",12, "red", "yellow", "bold", '进入详情', '/static/tab/main1.png',
'https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4',
24,24,"page"
)
])
}
function theme() {
//主题背景类型
let bgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/2.png"
// let bg:string = "black"
let navBarConfig : NavBarConfig = new NavBarConfig(true, 34, 'bold', '#FFA500', "rgba(191, 191, 191, 0.5)", "", 90)
let tabbarIcon = new TabbarIcon("mine",
"https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4",
"/static/tab/main3_active.png",
24,24,
12,
"green",
"white",
"bold"
)
let tabbarIcons : Map<string, TabbarIcon> = new Map();
tabbarIcons.set("mine", tabbarIcon)
let tabbarStyle : TabbarStyle = new TabbarStyle(120, "rgba(21, 20, 31, 0.8)", tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
}
function theme2() {
//主题背景类型
let bgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/sm.jpg"
// let bg:string = "black"
let navBarConfig : NavBarConfig = new NavBarConfig(false, 34, 'bold', '#0c0c0c', "", "", 90)
let tabbarIcon = new TabbarIcon("mine",
"/static/aj.png",
"/static/aj1.png",
24,24,
20,
"green",
"red",
"bold"
)
let tabbarIcon1 = new TabbarIcon("home",
"https://static-mp-4c53170b-e5f8-453b-a634-afcb5cf07d59.next.bspapp.com/img/icon/豪横.gif",
"https://static-mp-4c53170b-e5f8-453b-a634-afcb5cf07d59.next.bspapp.com/img/icon/豪横.gif",
50,24,
16,
"black",
"yellow",
"bold"
)
let tabbarIcons : Map<string, TabbarIcon> = new Map();
tabbarIcons.set("mine", tabbarIcon)
tabbarIcons.set("home", tabbarIcon1)
let tabbarStyle : TabbarStyle = new TabbarStyle(120, "rgba(21, 20, 31, 0.4)", tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
}
function theme3() {
//主题背景类型
let bgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "https://static-mp-4c53170b-e5f8-453b-a634-afcb5cf07d59.next.bspapp.com/img/bizhi/端午1.png"
// let bg:string = "black"
let navBarConfig : NavBarConfig = new NavBarConfig(true, 34, 'bold', '#0c0c0c', "", "", 90)
let tabbarIcon = new TabbarIcon("mine",
"/static/aj.png",
"/static/aj1.png",
24,24,
16,
"white",
"red",
"bold"
)
let tabbarIcon1 = new TabbarIcon("home",
"/static/sy1.png",
"/static/sy.png",
50,24,
16,
"white",
"red",
"bold"
)
let tabbarIcon2 = new TabbarIcon("detail",
"/static/tab/main2_active.png",
"/static/tab/main2_active.png",
24,24,
16,
"red",
"red",
"bold"
)
let tabbarIcons : Map<string, TabbarIcon> = new Map();
tabbarIcons.set("mine", tabbarIcon)
tabbarIcons.set("home", tabbarIcon1)
tabbarIcons.set("detail", tabbarIcon2)
let tabbarStyle : TabbarStyle = new TabbarStyle(150, "rgba(10, 13, 31, 0.8)", tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
}
function init() {
uni.stopPullDownRefresh()
console.log('stop onPullDownRefresh=====');
}
onPullDownRefresh(() => {
console.log('onPullDownRefresh=====');
init()
})
function refresh() {
uni.startPullDownRefresh({
success: () => {
console.log("1111")
}
});
init()
}
</script>
<style scoped lang="scss">
.mine-page {
height: 100%;
// border: 12px solid red;
margin: 5px;
}
.page-title {
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.title-text {
font-size: 18px;
font-weight: bold;
color: #333;
}
.tabbar {
// border: 2px solid cyan;
padding: 15px;
display: flex;
flex-direction: column;
}
.input-item {
height: 45px;
border: 1px solid #ccc;
border-radius: 6px;
padding-left: 10px;
background: #fff;
margin: 10px;
padding-left: 20px;
}
.picker-item {
height: 45px;
border: 1px solid #ccc;
border-radius: 6px;
background: #fff;
margin: 10px;
display: flex;
align-items: center;
}
.picker-display {
padding-left: 20px;
font-size: 16px;
color: #333;
}
.tabbar-btn {
margin-bottom: 10px;
border-radius: 8px;
background: #f5f5f5;
border: 1px solid #e5e5e5;
padding: 16px 0;
font-size: 16px;
}
.tabbar-btn:last-child {
margin-bottom: 0;
}
</style>
6.5 主题配置注意事项
- 颜色格式:颜色值必须为有效的 CSS 颜色格式(如
#ffffff、rgb(255,255,255)等) - tabbar区域高度单位:高度值为数字类型,单位为 rpx
- 动态修改:通过
uni.$emit('setTheme', new CustomTheme(bgType, bg, navBarConfig, tabbarStyle))动态修改的主题会立即生效
7 page页跳转自定义tab页
使用 pageToTab 方法 跳转到tab页
以 detail.uvue 页面为例,实现主题切换功能:
<template>
<view class="detail-root">
<view class="detail-content">
<!--
<view>
<input v-model="content" placeholder="请输入。。" />
<text>{{sbase64}}</text>
<text>{{base64yostr}}</text>
</view> -->
<view class = "con">
<view class = "com">
<text>网络请求</text>
</view>
<view>
<button class="btn" type="primary" @click="req">访问testvoid</button>
<button class="btn" type="primary" @click="reqSm2">访问testSm2</button>
<button class="btn" type="primary" @click="reqSm4">访问testSm4</button>
<button class="btn" type="primary" @click="reqSm2Sm4">访问testreqSm2Sm4</button>
</view>
</view>
<view class = "con">
<view class = "com">
<text>跳转tab页</text>
</view>
<view>
<input style="height: 50px; border:1px solid grey;margin: 5px 0;" v-model="addr" placeholder="输入跳转tabbar key"/>
<button type="warn" @click="tz">跳转tab页</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, computed } from 'vue'
import { stringToBase64, base64ToString, ReqApi } from "@/uni_modules/sunrains-request"
import { Response, FrontRequestVo } from "@/uni_modules/sunrains-common-type"
import { CustFailError, getComErrorInfo } from "@/uni_modules/sunrains-common-err"
import { pageToTab} from "@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts"
onMounted(() => {
console.log('【组件挂载====detail===】')
})
onUnmounted(() => {
console.log('【组件销毁====detail===】')
})
const addr = ref("")
function tz(){
if(addr.value.length<1){
uni.showToast({
title: '请输入跳转的tabbar key'
});
}else{
pageToTab(addr.value)
}
}
function fh(){
uni.navigateBack()
}
let priKey = "4a4a1d48fc284fe2fc43970641880554aed2f69fdfbea63257ea417af4e65802"
let pubKey = "0460e86263ec52b38a3da24c7a605055d14f8aa5c7855e4f4e19819f4f8a5b72181afd7aa4ab19255eb871eaedaba30fa18cd5c71693d3d76b030a04969bd1edfe"
const BASEURL = "http://192.168.1.9:8081"
type IRootType = {
name : string;
age : number;
}
type Builds = {
buildingNo : string,
sortNum : number,
buildAreaId : string,
id : string
}
async function req() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'GET',
url: BASEURL + "/test/get/cs1",
data: data,
header: null,
reqFlag: "void",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqvoid==", res.data)
// console.log("####==",res)
} catch (err) {
uni.showToast({
title: err.message ?? "请求异常",
icon: "error"
})
console.log("-----", err)
return
};
}
//"sign"|"sm4Encrypt"|"signwithenSM2-Sm4Encrypt"|"void";
async function reqSm2() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'GET',
url: BASEURL + "/test/get/cs1",
data: data,
header: null,
reqFlag: "sign",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqSm2==", res.data)
} catch (err) {
uni.showToast({
title: err.message ?? "请求异常",
icon: "error"
})
//#ifndef APP-ANDROID
const errMsg = (err as any).errMsg ?? "未知错误";
const errCode = (err as any).errCode ?? -1;
console.log('标准化错误码:', errMsg);
console.log('标准化错误信息:', errCode);
//#else
if (err instanceof UniError) {
let sa = err as UniError
console.log('sa==:', sa);
}
//#endif
console.log("-----", err)
return
};
}
async function reqSm4() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'POST',
url: BASEURL + "/test/post/cs1",
data: data,
header: null,
reqFlag: "sm4Encrypt",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqSm4==", res.data)
} catch (err) {
console.log("####reqSm4 err==", err)
uni.showToast({
title: err.message??"",
icon: "error"
})
return
};
}
async function reqSm2Sm4() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'POST',
url: BASEURL + "/test/post/cs1",
data: data,
header: null,
reqFlag: "signwithenSM2-Sm4Encrypt",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqSm2Sm4==", res.data)
} catch (err) {
console.log("-----", err)
return
};
}
const orderId = ref<string>('')
const orderStatus = ref<string>('')
const content = ref<string>('')
const sbase64 = computed(() => {
return stringToBase64(content.value)
})
const base64yostr = computed(() => {
return base64ToString(sbase64.value)
})
onLoad((options) => {
const opts = options as UTSJSONObject
const id = options?.["orderId"] as string | null
const status = options?.["status"] as string | null
if (id != null) {
orderId.value = id
}
if (status != null) {
orderStatus.value = status
}
console.log(`接收算法参数:ID=${orderId.value},状态=${orderStatus.value}`)
})
const goBack = () => {
uni.navigateBack()
}
function init(){
// uni.stopPullDownRefresh()
// console.log('stop onPullDownRefresh=====');
}
// onPullDownRefresh(() => {
// console.log('onPullDownRefresh=====');
// init()
// })
function refresh(){
uni.removeStorageSync("LOGINTABBARITEMS")
// uni.startPullDownRefresh({
// success:()=>{
// console.log("1111")
// }
// });
// init()
}
</script>
<style scoped>
.detail-root {
/* flex: 1;
display: flex;
flex-direction: column; */
background-color: #fff;
}
.status-bar {
height: 44px;
background-color: #ffffff;
}
.detail-header {
height: 44px;
background-color: #fff;
flex-direction: row;
align-items: center;
padding-left: 15px;
padding-right: 15px;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: #eee;
}
.back-icon {
font-size: 18px;
color: #007aff;
margin-right: 10px;
}
.detail-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.detail-content {
flex: 1;
padding: 20px;
}
.detail-item {
flex-direction: row;
margin-bottom: 20px;
align-items: center;
}
.label {
font-size: 15px;
color: #666;
width: 80px;
}
.value {
font-size: 15px;
color: #333;
font-weight: bold;
}
.detail-desc {
margin-top: 30px;
}
.com{
display:flex;
align-items: center;
margin:10px 0;
}
.con{
margin:10px 0;
padding:10px;
background-color: #e3eda1;
}
.btn{
margin: 5px;
}
</style>
8 拓展底部tabbar类型,(切换、弹出、进入)页面
let mine = new TabItem(
'mine', //组件或页面唯一标识
"", //组件或页面路径 当类型为 page 时不可为空,其它传空字符串即可
12,
"red",
"yellow",
"bold",
'我的',
'/static/tab/main3.png',
'/static/tab/main3_active.png',
24,//图标宽
24,//图标高
"tabPage" //tabbar 类型 tabPage:tab页面 tabPop:弹窗组件 page:普通页面
)
注:
类型为 tabPop 打开页面 可 右滑手势(当手机开启右滑手势时)关闭、 或点击虚拟返回按键关闭 或 页面中调用方法 closeTabPop()关闭
方法导入 import { pageToTab} from "@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts"
⚠️ 常见问题
Q1: 为什么小程序端需要单独配置?
A: 微信小程序不支持动态组件渲染,必须静态声明所有可能使用的组件。
Q2: 动态 Tab 刷新失败怎么办?
A: 检查以下几点:
- 组件是否在
tabbar-config.uts中正确注册 - 是否正确使用了
GLOBAL_COMPONENT_REGISTRY.get()获取组件 - TabItem 的参数是否完整且类型正确
Q3: 导航栏配置不生效?
A: 确保 setNavbarConfig() 在 setDefaultTabItems() 之前调用,或在应用启动时尽早调用。
Q4: 如何设置数字角标?
A: 使用 uni.$emit('setTabBadge', {"key": "tab_key", "badge": number}) 即可,详见第 4 章。
Q5: 角标设置后不显示怎么办?
A: 检查以下几点:
- badge 值是否大于 0(0 或负数不显示)
- tab key 是否正确(必须与 TabItem 的 key 一致)
- 是否重新赋值了整个数组(直接修改对象属性不会触发响应式)
Q6: 退出登录如何清除 tabbar相关缓存
A: 调用 noticetab.uts里的 clearStorage 方法
/**
* 清除tabbar相关缓存
* all 所有
* theme 主题
* tabbarItems 所有tabbar
* badge 角标
*/
export function clearStorage(type:string){
switch (type){
case "theme":
uni.removeStorageSync("sunrains-tabbar-customtheme")
break;
case "tabbarItems":
uni.removeStorageSync("sunrains-tabbar-logintabbaritems")
break;
case "badge":
uni.removeStorageSync("sunrains-tabbar-tabbarbadges")
break;
default:
uni.removeStorageSync("sunrains-tabbar-customtheme")
uni.removeStorageSync("sunrains-tabbar-logintabbaritems")
uni.removeStorageSync("sunrains-tabbar-tabbarbadges")
}
}
点击查看插件
sunrains-tabbar 自定义 TabBar 插件使用指南
点击体验h5端
点击查看安卓端录屏效果
点击查看安卓端演示安装包
目录
- [1. 基础配置]
- [2. pages.json 配置]
- [3. 动态配置 TabBar]
- [4. 数字角标功能]
- [5. 小程序端特殊配置]
- [6. 主题配置]
- [7. page页跳转自定义tab页]
- [8. 拓展底部tabbar类型,(切换、弹出、进入)页面]
1. 基础配置
App.uvue onLaunch 初始化
import {initDefaultTabItems} from '@/uni_modules/sunrains-tabbar/tabbar-config.uts'
onLaunch(() => {
console.log('App Launch')
initDefaultTabItems()
}
1.1 配置 tabbar-config.uts 文件
在插件根目录 tabbar-config.uts 文件中,配置注册全局tab组件和导航栏设置。
完整示例:
import { setNavbarConfig, NavBarConfig, registerGlobalComponent, setDefaultTabItems, TabItem } from '@/uni_modules/sunrains-tabbar/utssdk/types.uts'
import { getStoreLoginItems,storeLoginItems } from './utssdk/handle'
//导入所有自定义tab页
import Recommend from '@/pages/home/recommend.uvue'
import Order from '@/pages/order/order.uvue'
import Mine from '@/pages/mine/mine.uvue'
import Detail from '@/pages/detail/detail.uvue'
//注册所有需要切换的tab页或需要弹出的页面 即类型为 tabPage:tab页面 tabPop:弹窗组件
registerGlobalComponent('home', Recommend)
registerGlobalComponent('order', Order)
registerGlobalComponent('mine', Mine)
registerGlobalComponent('detail', Detail)
// 2. 配置导航栏(不设置会使用默认配置)
setNavbarConfig(new NavBarConfig(true, 34, 'bold', '#333333', '#ffffff', "", 88))
//初始化默认tabbar区域按钮
export function initDefaultTabItems(){
/**
*
* 3. 设置默认 Tab 列表(至少设置一个固定的,也可以全部设置,其它可通过请求服务端接口动态配置),
* 或通过网络请求初始化 游客 显示哪些 有 角色的 按角色 初始化
* App.uvue onLaunch 初始化
import {initDefaultTabItems} from '@/uni_modules/sunrains-tabbar/tabbar-config.uts'
onLaunch(() => {
console.log('App Launch')
initDefaultTabItems()
}
*/
let home = new TabItem('home',"", 12, "red", "yellow", "bold", '首页', '/static/tab/main3.png', '/static/tab/main3_active.png',24,24,"tabPage")
let mine = new TabItem('mine',"", 12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png',24,24,"tabPage")
let items = getStoreLoginItems();
//判断是否登录
if(false || items != null){
setDefaultTabItems(items)
}else{
setDefaultTabItems([
home,mine
])
storeLoginItems([home,mine])
}
}
关键步骤说明:
- 导入组件:将所有需要作为 Tab 的页面组件导入
- 注册组件:使用
registerGlobalComponent注册每个组件(第一个参数为唯一标识) - 配置导航栏:可选配置,根据项目需求调整
- 设置默认 Tab:至少配置一个固定 Tab,其余可动态添加
2. pages.json 配置
2.1 必需配置
必须在 pages.json 中配置自定义 TabBar 入口页面(必须放在第一个位置),tab页无需在pages.json中配置:
{
"path": "uni_modules/sunrains-tabbar/index",
"style": {
"navigationStyle": "custom"
}
}
2.2 完整示例
{
"pages": [
{
"path": "uni_modules/sunrains-tabbar/utssdk/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "详情"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app x",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"uniIdRouter": {}
}
注意事项:
- 自定义 TabBar 入口页面必须配置在
pages数组的第一位 - 所有 Tab 页面不需要在
pages中声明 navigationStyle设置为"custom"以隐藏原生导航栏
3. 动态配置 TabBar
3.1 使用场景
当需要根据用户权限、服务端配置等动态调整 TabBar 时,可以在任意页面触发刷新。
3.2 实现示例
以下面文档 mine.uvue 页面为例:
关键点:
- 使用 refreshTabbar方法触发 TabBar 刷新
- 确保所有动态 Tab 对应的组件已在
tabbar-config.uts中注册
4. 数字角标功能
4.1 功能说明
支持为任意 Tab 设置数字角标,用于显示未读消息数、待处理事项等。
特性:
- ✅ 动态设置/更新角标
- ✅ 超过 99 自动显示 "99+"
- ✅ 角标为 0 或 null 时不显示
- ✅ 响应式更新,立即生效
4.2 使用方法
方法一:通过事件设置(推荐)
在任意页面通过 调用 setTabBadge 方法:
import { setTabBadge} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
// 设置算法 tab 的角标为 5
setTabBadge("order",5)
// 清除角标(设置为 0)
setTabBadge("order",0)
// 设置超过 99 显示 99+
setTabBadge("order",150)
方法二:初始化时设置
在创建 TabItem 时传入 badge 参数:
import { TabItem } from '@/uni_modules/sunrains-tabbar/utssdk/types.uts'
// 带角标的 tab
new TabItem(
'order',
'算法',
'/static/tab/main2.png',
'/static/tab/main2_active.png'
10 // badge 参数
)
// 不带角标的 tab
new TabItem(
'home',
'首页',
'/static/tab/main1.png',
'/static/tab/main1_active.png'
)
4.3 完整示例
以 文档后面 mine.uvue 页面为例,实现动态设置角标功能:
4.4 实际应用场景
场景 1:算法未读数
// 从服务端获取未读算法数
async function loadUnreadOrderCount() {
const res = await getOrderCount()
setTabBadge("order",res.data.count)
}
// 页面加载时调用
onShow(() => {
loadUnreadOrderCount()
})
场景 2:消息通知
// WebSocket 收到新消息
websocket.onMessage((msg) => {
if (msg.type === 'new_message') {
const currentBadge = getCurrentBadge('message')
setTabBadge("newMessage",currentBadge + 1)
}
})
场景 3:购物车数量
// 添加商品到购物车后更新角标
function addToCart(product: Product) {
cartItems.push(product)
setTabBadge("cart",cartItems.length)
}
5. 小程序端特殊配置
4.1 配置 tab-content-renderer.uvue
注意: 仅在需要支持微信小程序时需要配置,App 端可忽略此步骤。
在 uni_modules/sunrains-tabbar/tab-content-renderer.uvue 中配置:
<<template>
<view style="width: 100%; height: 100%;">
<Recommend v-if="currentTab === 'home'" style="width: 100%; height: 100%;" />
<Order v-if="currentTab === 'order'" style="width: 100%; height: 100%;" />
<Mine v-if="currentTab === 'mine'" style="width: 100%; height: 100%;" />
<Detail v-if="currentTab === 'detail'" style="width: 100%; height: 100%;" />
</view>
</template>
<script setup lang="uts">
import Recommend from '@/pages/home/recommend.uvue'
import Order from '@/pages/order/order.uvue'
import Mine from '@/pages/mine/mine.uvue'
import Detail from '@/pages/detail/detail.uvue'
interface Props {
currentTab: string
}
defineProps({
currentTab:{
type:String
}
})
</script>
配置说明:
- 条件编译:使用
#ifndef MP-WEIXIN和#ifdef MP-WEIXIN区分平台 - App 端:使用
<component :is="...">动态渲染组件 - 小程序端:必须静态导入并声明所有可能的 Tab 组件
- 导入位置:在
#ifdef MP-WEIXIN块内导入组件
6. 主题配置
6.1 主题功能说明
插件支持自定义 TabBar 的主题样式,支持网络背景图、本地图片背景图、纯颜色背景(支持rgba透明)、导航栏、tabbar区域背景、图标大小、图标(支持网络图标)、文字颜色、文字大小、等属性 满足不同项目的视觉需求。
主题特性:
- ✅ 支持全局主题配置
- ✅ 支持动态切换主题
- ✅ 支持自定义主题参数
6.2 初始化时配置主题
在 tabbar-config.uts 文件中配置主题:
import {
setNavbarConfig,
NavBarConfig,
registerGlobalComponent,
setDefaultTabItems,
TabItem,
setTabBarTheme,
TabBarTheme
} from '@/uni_modules/sunrains-tabbar/types.uts'
import { setTabPageTheme} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
let bgType : BgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/2.png"
//配置导航栏(可选,不设置会使用默认配置)
let navBarConfig : NavBarConfig = new NavBarConfig(
true, // 是否显示导航栏
34, // 字体大小
'bold', // 字体粗细
'#FFA500', // 文字颜色
'',// 背景颜色
"", //背景图片(支持网络图片)
90) // 导航栏高度
let tabbarIcon = new TabbarIcon(
"mine",//自定义组件唯一标识
//非选中图标
"https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4",
//选中图标
"/static/tab/main3_active.png",
24,//图标宽 单位 px
24,//图标高 单位 px
//文字颜色
12,
//非选中文字大小
"green",
//选择文字颜色
"white",
//选择文字粗细
"bold"
)
let tabbarIcons : Map<string,TabbarIcon> = new Map();
tabbarIcons.set("mine",tabbarIcon)
let tabbarStyle : TabbarStyle = new TabbarStyle(
//tabbar区域高度
120,
//tabbar区域背景
"rgba(21, 20, 31, 0.8)",
//tabbar图标文字样式
tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
6.3 动态修改主题
在任意页面通过调用 setTabPageTheme 方法动态修改主题:
import { setTabPageTheme} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
// 动态修改背景色和选中颜色
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
6.4 在页面中动态切换主题
以 mine.uvue 页面为例,实现主题切换功能:
<template>
<scroll-view direction="vertical" class="mine-page"
show-scrollbar = "false"
enable-back-to-top="true"
refresher-enabled="true"
refresher-default-style="none"
refresher-background="rgba(232, 201, 183, 0.5)"
:refresher-triggered="data.refreshing2"
refresher-max-drag-distance="200px"
@refresherpulling="onRefresherpulling2"
@refresherrefresh="onRefresherrefresh2"
@refresherrestore="onRefresherrestore2"
>
<uni-refresh-box
slot="refresher"
:pulling-distance="data.pullingDistance2"
:refreshing="data.refreshing2"
loading-class="loading-dark"
text-class="uni-text-class-buildin"
style="flex-direction: column;height: 46px;padding-top: 6px;"
pulling-text="继续下拉可刷新"
loosing-text="释放后会刷新"
loading-text="奋力加载中..."
/>
<view class="page-title">
<text style="color:#ff4d40">动态设置中心</text>
</view>
<view class="tabbar">
<text class="title-text">tabbar设置</text>
<button class="tabbar-btn" @click="refresh1">动态tabbar(我的)</button>
<button class="tabbar-btn" @click="refresh2">动态tabbar(首页、我的)</button>
<button class="tabbar-btn" @click="refresh3">动态tabbar(算法、我的)</button>
<button class="tabbar-btn" @click="refresh4">动态tabbar(我的、算法、首页、弹出详情、进入详情)</button>
<text class="title-text">设置角标</text>
<input class="input-item" v-model="tabbar" placeholder="请输入tabbar" />
<input class="input-item" v-model="badge" placeholder="请输入角标" />
<button class="tabbar-btn" @click="setBadge">确定</button>
<button class="tabbar-btn" @click="theme">主题1</button>
<button class="tabbar-btn" @click="theme2">主题2</button>
<button class="tabbar-btn" @click="theme3">主题3</button>
<button class="tabbar-btn" @click="clear">clear</button>
<!-- <button class="tabbar-btn" type="default" @click="refresh">刷新</button> -->
<!-- <button type="default" @click="cPop">返回</button> -->
</view>
</scroll-view>
</template>
<script setup lang="uts">
import { ref,onMounted,onUnmounted } from 'vue'
import { TabItem, GLOBAL_COMPONENT_REGISTRY, CustomTheme, TabbarIcon, TabbarStyle, NavBarConfig } from '@/uni_modules/sunrains-tabbar/utssdk/types.uts'
import { setTabBadge,setTabbarColorHeight,refreshTabbar ,setTabPageTheme,closeTabPop,clearStorage} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
function cPop(){
closeTabPop()
}
onMounted(() => {
console.log('【组件挂载====mine===】')
})
onUnmounted(() => {
console.log('【组件销毁====mine===】')
})
// 用于自动化测试的数据暴露 - 使用 reactive 将所有 ref 状态集中暴露
const data = reactive({
refreshing2: false,
pullingDistance2: 0
})
function onRefresherpulling2(e: RefresherEvent) {
data.pullingDistance2 = e.detail.dy
}
const refreshing = ref(false)
function onRefresherrefresh2() {
data.refreshing2 = true
console.log('触发刷新')
setTimeout(() => {
data.refreshing2 = false
console.log('刷新完成')
}, 1500)
}
function onRefresherrestore2() {
data.pullingDistance2 = 0
}
function onRefresherabort2() {
data.pullingDistance2 = 0
}
// 上拉触底加载
const loadMore = () => {
console.log("滚动到底部", "info")
}
function clear() {
clearStorage("all")
}
const bgColor = ref<string>('pink')
const tabHeight = ref<string>('120')
const badge = ref<string>('0')
const ss = ref<any>("");
// tab 选项配置
const tabbar = ref<string | null>(null)
const selectedTab = ref<string>('mine')
const selectedTabIndex = ref<number>(0)
function setBadge() {
//动态设置指定tabbar 角标
const badgeNum = parseInt(badge.value)
if (isNaN(badgeNum)) {
uni.showToast({
title: '请输入有效数字',
icon: 'none'
})
return
}
setTabBadge(tabbar.value,badgeNum)
uni.showToast({
title: `已设置 ${tabbar.value} 角标为 ${badgeNum}`,
icon: 'success'
})
}
function setStyle() {
setTabbarColorHeight(bgColor.value,parseInt(tabHeight.value) )
}
function refresh1() {
console.log("动态tabbar(我的)")
refreshTabbar([
new TabItem('mine', "",12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png',24,24,"tabPage")
]);
}
function refresh2() {
console.log("动态tabbar(首页、我的)")
refreshTabbar([
new TabItem('home', "",12, "red", "yellow", "bold", '首页', '/static/tab/main1.png', '/static/tab/main1_active.png',24,24,"tabPage"),
new TabItem('mine', "",12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png',24,24,"tabPage")
])
}
function refresh3() {
console.log("动态tabbar(算法、我的)")
refreshTabbar( [
new TabItem('order', "",12, "red", "yellow", "bold", '算法', '/static/tab/main2.png', '/static/tab/main2_active.png',24,24,"tabPage"),
new TabItem('mine', "",12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png',24,24,"tabPage")
])
}
function refresh4() {
console.log("动态tabbar(我的、算法、首页、弹出详情、进入详情)")
refreshTabbar([
new TabItem('mine', "",12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png',24,24,"tabPage"),
new TabItem('order', "",12, "red", "yellow", "bold", '算法', '/static/tab/main2.png', '/static/tab/main2_active.png',24,24,"tabPage"),
new TabItem('home', "",12, "red", "yellow", "bold", '首页', '/static/tab/main1.png',
'https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4',
24,24,"tabPage"
),
new TabItem('detail', "",12, "red", "yellow", "bold", '弹出详情', '/static/tab/main1.png',
'https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4',
24,24,"tabPop"
),
new TabItem('detail', "/pages/detail/detail",12, "red", "yellow", "bold", '进入详情', '/static/tab/main1.png',
'https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4',
24,24,"page"
)
])
}
function theme() {
//主题背景类型
let bgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/2.png"
// let bg:string = "black"
let navBarConfig : NavBarConfig = new NavBarConfig(true, 34, 'bold', '#FFA500', "rgba(191, 191, 191, 0.5)", "", 90)
let tabbarIcon = new TabbarIcon("mine",
"https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4",
"/static/tab/main3_active.png",
24,24,
12,
"green",
"white",
"bold"
)
let tabbarIcons : Map<string, TabbarIcon> = new Map();
tabbarIcons.set("mine", tabbarIcon)
let tabbarStyle : TabbarStyle = new TabbarStyle(120, "rgba(21, 20, 31, 0.8)", tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
}
function theme2() {
//主题背景类型
let bgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/sm.jpg"
// let bg:string = "black"
let navBarConfig : NavBarConfig = new NavBarConfig(false, 34, 'bold', '#0c0c0c', "", "", 90)
let tabbarIcon = new TabbarIcon("mine",
"/static/aj.png",
"/static/aj1.png",
24,24,
20,
"green",
"red",
"bold"
)
let tabbarIcon1 = new TabbarIcon("home",
"https://static-mp-4c53170b-e5f8-453b-a634-afcb5cf07d59.next.bspapp.com/img/icon/豪横.gif",
"https://static-mp-4c53170b-e5f8-453b-a634-afcb5cf07d59.next.bspapp.com/img/icon/豪横.gif",
50,24,
16,
"black",
"yellow",
"bold"
)
let tabbarIcons : Map<string, TabbarIcon> = new Map();
tabbarIcons.set("mine", tabbarIcon)
tabbarIcons.set("home", tabbarIcon1)
let tabbarStyle : TabbarStyle = new TabbarStyle(120, "rgba(21, 20, 31, 0.4)", tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
}
function theme3() {
//主题背景类型
let bgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "https://static-mp-4c53170b-e5f8-453b-a634-afcb5cf07d59.next.bspapp.com/img/bizhi/端午1.png"
// let bg:string = "black"
let navBarConfig : NavBarConfig = new NavBarConfig(true, 34, 'bold', '#0c0c0c', "", "", 90)
let tabbarIcon = new TabbarIcon("mine",
"/static/aj.png",
"/static/aj1.png",
24,24,
16,
"white",
"red",
"bold"
)
let tabbarIcon1 = new TabbarIcon("home",
"/static/sy1.png",
"/static/sy.png",
50,24,
16,
"white",
"red",
"bold"
)
let tabbarIcon2 = new TabbarIcon("detail",
"/static/tab/main2_active.png",
"/static/tab/main2_active.png",
24,24,
16,
"red",
"red",
"bold"
)
let tabbarIcons : Map<string, TabbarIcon> = new Map();
tabbarIcons.set("mine", tabbarIcon)
tabbarIcons.set("home", tabbarIcon1)
tabbarIcons.set("detail", tabbarIcon2)
let tabbarStyle : TabbarStyle = new TabbarStyle(150, "rgba(10, 13, 31, 0.8)", tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
}
function init() {
uni.stopPullDownRefresh()
console.log('stop onPullDownRefresh=====');
}
onPullDownRefresh(() => {
console.log('onPullDownRefresh=====');
init()
})
function refresh() {
uni.startPullDownRefresh({
success: () => {
console.log("1111")
}
});
init()
}
</script>
<style scoped lang="scss">
.mine-page {
height: 100%;
// border: 12px solid red;
margin: 5px;
}
.page-title {
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.title-text {
font-size: 18px;
font-weight: bold;
color: #333;
}
.tabbar {
// border: 2px solid cyan;
padding: 15px;
display: flex;
flex-direction: column;
}
.input-item {
height: 45px;
border: 1px solid #ccc;
border-radius: 6px;
padding-left: 10px;
background: #fff;
margin: 10px;
padding-left: 20px;
}
.picker-item {
height: 45px;
border: 1px solid #ccc;
border-radius: 6px;
background: #fff;
margin: 10px;
display: flex;
align-items: center;
}
.picker-display {
padding-left: 20px;
font-size: 16px;
color: #333;
}
.tabbar-btn {
margin-bottom: 10px;
border-radius: 8px;
background: #f5f5f5;
border: 1px solid #e5e5e5;
padding: 16px 0;
font-size: 16px;
}
.tabbar-btn:last-child {
margin-bottom: 0;
}
</style>
6.5 主题配置注意事项
- 颜色格式:颜色值必须为有效的 CSS 颜色格式(如
#ffffff、rgb(255,255,255)等) - tabbar区域高度单位:高度值为数字类型,单位为 rpx
- 动态修改:通过
uni.$emit('setTheme', new CustomTheme(bgType, bg, navBarConfig, tabbarStyle))动态修改的主题会立即生效
7 page页跳转自定义tab页
使用 pageToTab 方法 跳转到tab页
以 detail.uvue 页面为例,实现主题切换功能:
<template>
<view class="detail-root">
<view class="detail-content">
<!--
<view>
<input v-model="content" placeholder="请输入。。" />
<text>{{sbase64}}</text>
<text>{{base64yostr}}</text>
</view> -->
<view class = "con">
<view class = "com">
<text>网络请求</text>
</view>
<view>
<button class="btn" type="primary" @click="req">访问testvoid</button>
<button class="btn" type="primary" @click="reqSm2">访问testSm2</button>
<button class="btn" type="primary" @click="reqSm4">访问testSm4</button>
<button class="btn" type="primary" @click="reqSm2Sm4">访问testreqSm2Sm4</button>
</view>
</view>
<view class = "con">
<view class = "com">
<text>跳转tab页</text>
</view>
<view>
<input style="height: 50px; border:1px solid grey;margin: 5px 0;" v-model="addr" placeholder="输入跳转tabbar key"/>
<button type="warn" @click="tz">跳转tab页</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, computed } from 'vue'
import { stringToBase64, base64ToString, ReqApi } from "@/uni_modules/sunrains-request"
import { Response, FrontRequestVo } from "@/uni_modules/sunrains-common-type"
import { CustFailError, getComErrorInfo } from "@/uni_modules/sunrains-common-err"
import { pageToTab} from "@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts"
onMounted(() => {
console.log('【组件挂载====detail===】')
})
onUnmounted(() => {
console.log('【组件销毁====detail===】')
})
const addr = ref("")
function tz(){
if(addr.value.length<1){
uni.showToast({
title: '请输入跳转的tabbar key'
});
}else{
pageToTab(addr.value)
}
}
function fh(){
uni.navigateBack()
}
let priKey = "4a4a1d48fc284fe2fc43970641880554aed2f69fdfbea63257ea417af4e65802"
let pubKey = "0460e86263ec52b38a3da24c7a605055d14f8aa5c7855e4f4e19819f4f8a5b72181afd7aa4ab19255eb871eaedaba30fa18cd5c71693d3d76b030a04969bd1edfe"
const BASEURL = "http://192.168.1.9:8081"
type IRootType = {
name : string;
age : number;
}
type Builds = {
buildingNo : string,
sortNum : number,
buildAreaId : string,
id : string
}
async function req() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'GET',
url: BASEURL + "/test/get/cs1",
data: data,
header: null,
reqFlag: "void",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqvoid==", res.data)
// console.log("####==",res)
} catch (err) {
uni.showToast({
title: err.message ?? "请求异常",
icon: "error"
})
console.log("-----", err)
return
};
}
//"sign"|"sm4Encrypt"|"signwithenSM2-Sm4Encrypt"|"void";
async function reqSm2() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'GET',
url: BASEURL + "/test/get/cs1",
data: data,
header: null,
reqFlag: "sign",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqSm2==", res.data)
} catch (err) {
uni.showToast({
title: err.message ?? "请求异常",
icon: "error"
})
//#ifndef APP-ANDROID
const errMsg = (err as any).errMsg ?? "未知错误";
const errCode = (err as any).errCode ?? -1;
console.log('标准化错误码:', errMsg);
console.log('标准化错误信息:', errCode);
//#else
if (err instanceof UniError) {
let sa = err as UniError
console.log('sa==:', sa);
}
//#endif
console.log("-----", err)
return
};
}
async function reqSm4() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'POST',
url: BASEURL + "/test/post/cs1",
data: data,
header: null,
reqFlag: "sm4Encrypt",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqSm4==", res.data)
} catch (err) {
console.log("####reqSm4 err==", err)
uni.showToast({
title: err.message??"",
icon: "error"
})
return
};
}
async function reqSm2Sm4() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'POST',
url: BASEURL + "/test/post/cs1",
data: data,
header: null,
reqFlag: "signwithenSM2-Sm4Encrypt",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqSm2Sm4==", res.data)
} catch (err) {
console.log("-----", err)
return
};
}
const orderId = ref<string>('')
const orderStatus = ref<string>('')
const content = ref<string>('')
const sbase64 = computed(() => {
return stringToBase64(content.value)
})
const base64yostr = computed(() => {
return base64ToString(sbase64.value)
})
onLoad((options) => {
const opts = options as UTSJSONObject
const id = options?.["orderId"] as string | null
const status = options?.["status"] as string | null
if (id != null) {
orderId.value = id
}
if (status != null) {
orderStatus.value = status
}
console.log(`接收算法参数:ID=${orderId.value},状态=${orderStatus.value}`)
})
const goBack = () => {
uni.navigateBack()
}
function init(){
// uni.stopPullDownRefresh()
// console.log('stop onPullDownRefresh=====');
}
// onPullDownRefresh(() => {
// console.log('onPullDownRefresh=====');
// init()
// })
function refresh(){
uni.removeStorageSync("LOGINTABBARITEMS")
// uni.startPullDownRefresh({
// success:()=>{
// console.log("1111")
// }
// });
// init()
}
</script>
<style scoped>
.detail-root {
/* flex: 1;
display: flex;
flex-direction: column; */
background-color: #fff;
}
.status-bar {
height: 44px;
background-color: #ffffff;
}
.detail-header {
height: 44px;
background-color: #fff;
flex-direction: row;
align-items: center;
padding-left: 15px;
padding-right: 15px;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: #eee;
}
.back-icon {
font-size: 18px;
color: #007aff;
margin-right: 10px;
}
.detail-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.detail-content {
flex: 1;
padding: 20px;
}
.detail-item {
flex-direction: row;
margin-bottom: 20px;
align-items: center;
}
.label {
font-size: 15px;
color: #666;
width: 80px;
}
.value {
font-size: 15px;
color: #333;
font-weight: bold;
}
.detail-desc {
margin-top: 30px;
}
.com{
display:flex;
align-items: center;
margin:10px 0;
}
.con{
margin:10px 0;
padding:10px;
background-color: #e3eda1;
}
.btn{
margin: 5px;
}
</style>
8 拓展底部tabbar类型,(切换、弹出、进入)页面
let mine = new TabItem(
'mine', //组件或页面唯一标识
"", //组件或页面路径 当类型为 page 时不可为空,其它传空字符串即可
12,
"red",
"yellow",
"bold",
'我的',
'/static/tab/main3.png',
'/static/tab/main3_active.png',
24,//图标宽
24,//图标高
"tabPage" //tabbar 类型 tabPage:tab页面 tabPop:弹窗组件 page:普通页面
)
注:
类型为 tabPop 打开页面 可 右滑手势(当手机开启右滑手势时)关闭、 或点击虚拟返回按键关闭 或 页面中调用方法 closeTabPop()关闭
方法导入 import { pageToTab} from "@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts"
⚠️ 常见问题
Q1: 为什么小程序端需要单独配置?
A: 微信小程序不支持动态组件渲染,必须静态声明所有可能使用的组件。
Q2: 动态 Tab 刷新失败怎么办?
A: 检查以下几点:
- 组件是否在
tabbar-config.uts中正确注册 - 是否正确使用了
GLOBAL_COMPONENT_REGISTRY.get()获取组件 - TabItem 的参数是否完整且类型正确
Q3: 导航栏配置不生效?
A: 确保 setNavbarConfig() 在 setDefaultTabItems() 之前调用,或在应用启动时尽早调用。
Q4: 如何设置数字角标?
A: 使用 uni.$emit('setTabBadge', {"key": "tab_key", "badge": number}) 即可,详见第 4 章。
Q5: 角标设置后不显示怎么办?
A: 检查以下几点:
- badge 值是否大于 0(0 或负数不显示)
- tab key 是否正确(必须与 TabItem 的 key 一致)
- 是否重新赋值了整个数组(直接修改对象属性不会触发响应式)
Q6: 退出登录如何清除 tabbar相关缓存
A: 调用 noticetab.uts里的 clearStorage 方法
/**
* 清除tabbar相关缓存
* all 所有
* theme 主题
* tabbarItems 所有tabbar
* badge 角标
*/
export function clearStorage(type:string){
switch (type){
case "theme":
uni.removeStorageSync("sunrains-tabbar-customtheme")
break;
case "tabbarItems":
uni.removeStorageSync("sunrains-tabbar-logintabbaritems")
break;
case "badge":
uni.removeStorageSync("sunrains-tabbar-tabbarbadges")
break;
default:
uni.removeStorageSync("sunrains-tabbar-customtheme")
uni.removeStorageSync("sunrains-tabbar-logintabbaritems")
uni.removeStorageSync("sunrains-tabbar-tabbarbadges")
}
}
收起阅读 »
我们公司找兼职开发,有没有会iOS开发的(跨平台最好),QQ752562065
我们公司找兼职开发,有没有会iOS开发的(跨平台最好),QQ752562065
我们公司找兼职开发,有没有会iOS开发的(跨平台最好),QQ752562065
uni-app中app(安卓端实测)中<editor>组件光标使用按钮插入特定符号(如【】)时定位到特定字符中间的可行方案
uni-app app端(安卓实测)<editor>组件光标使用按钮插入特定符号(如【】)时定位到特定字符中间的可行方案
因为在做文本编辑器(符号快捷成对(如【】这种的)输入)需用到editor组件。
因为不能直接使用DOM,因网上搜不出解决方案,借助AI后实测出一种方案和一种待验证方案。
思路简单得离谱(主要还是对edtior这个组件不了解):
首先说一下editor
<editor> 组件底层是基于 Quill.js,所以不能使用原生输入框的 selectionStart/setSelectionRange。光标操作只能通过 Quill API 或标准 DOM API 实现。
方案一:Quill API(推荐这种)
获取 Quill 实例
const editor = document.getElementById('editor')
const quill = editor.__quill || editor.querySelector('.ql-editor')?.__quill
核心示例:插入配对符号并定位光标
例如:一个按钮直接插入【】,你需要光标在【】的中间
const range = quill.getSelection()
const pos = range ? range.index : 0
const half = Math.ceil(pair.length / 2)
quill.insertText(pos, pair)
quill.setSelection(pos + half)
优缺点
优点
- API 简洁高效
- 与编辑器状态(Delta)完全同步
- 不影响撤销/重做
缺点
- 依赖 Quill 内部属性
__quill - 不同版本 uni-app 兼容性不确定
- 需在 renderjs 中运行
方案二:原生 DOM API(降级方案:这个未验证,因为第一种方案基本解决,这是ai给的降级兜底方案)
核心思路
使用 document.createTreeWalker 遍历文本节点计算全局偏移,通过 Range + Selection API 操作光标。
获取光标全局偏移
function getCursorIndex(container) {
const sel = window.getSelection()
if (!sel?.rangeCount) return -1
const node = sel.anchorNode, off = sel.anchorOffset
if (!node || !container.contains(node)) return -1
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT)
let count = 0
while (walker.nextNode()) {
if (walker.currentNode === node) return count + off
count += walker.currentNode.textContent.length
}
return count
}
设置光标到指定位置
function setCursorIndex(container, index) {
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT)
let count = 0
while (walker.nextNode()) {
const len = walker.currentNode.textContent.length
if (count + len >= index) {
const range = document.createRange()
range.setStart(walker.currentNode, index - count)
range.collapse(true)
const sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
return
}
count += len
}
}
插入文本并设置光标
const text = foundNode.textContent
foundNode.textContent = text.slice(0, offset) + pair + text.slice(offset)
const range = document.createRange()
range.setStart(foundNode, offset + half)
range.collapse(true)
window.getSelection().removeAllRanges()
window.getSelection().addRange(range)
优缺点
优点
- 纯标准 DOM API,兼容性最好
- 不依赖任何框架内部属性
缺点
- 代码较复杂
- 直接改 textContent 可能破坏 Quill Delta 同步
- 富文本元素(图片等)偏移计算需额外处理
两种方案对比
代码量
- Quill API:3 行
- DOM API:30+ 行
-
Delta 同步
- Quill API:✅ 完全同步
- DOM API:❌ 可能不同步
撤销/重做
- Quill API:✅ 正常
- DOM API:❌ 可能受影响
WebView 兼容性
- Quill API:⭐⭐⭐(依赖 Quill 版本)
- DOM API:⭐⭐⭐⭐⭐
uni-app App 端实践要点
- 逻辑层无 DOM,所有 DOM 操作必须通过 renderjs 执行
- renderjs 通过
change:prop模板绑定接收逻辑层数据,通过ownerVm.callMethod()回传状态 - 方法签名:
methodName(newVal, oldVal, ownerVm, vm) - 推荐双方案:优先 Quill API → 失败则降级 DOM API
uni-app app端(安卓实测)<editor>组件光标使用按钮插入特定符号(如【】)时定位到特定字符中间的可行方案
因为在做文本编辑器(符号快捷成对(如【】这种的)输入)需用到editor组件。
因为不能直接使用DOM,因网上搜不出解决方案,借助AI后实测出一种方案和一种待验证方案。
思路简单得离谱(主要还是对edtior这个组件不了解):
首先说一下editor
<editor> 组件底层是基于 Quill.js,所以不能使用原生输入框的 selectionStart/setSelectionRange。光标操作只能通过 Quill API 或标准 DOM API 实现。
方案一:Quill API(推荐这种)
获取 Quill 实例
const editor = document.getElementById('editor')
const quill = editor.__quill || editor.querySelector('.ql-editor')?.__quill
核心示例:插入配对符号并定位光标
例如:一个按钮直接插入【】,你需要光标在【】的中间
const range = quill.getSelection()
const pos = range ? range.index : 0
const half = Math.ceil(pair.length / 2)
quill.insertText(pos, pair)
quill.setSelection(pos + half)
优缺点
优点
- API 简洁高效
- 与编辑器状态(Delta)完全同步
- 不影响撤销/重做
缺点
- 依赖 Quill 内部属性
__quill - 不同版本 uni-app 兼容性不确定
- 需在 renderjs 中运行
方案二:原生 DOM API(降级方案:这个未验证,因为第一种方案基本解决,这是ai给的降级兜底方案)
核心思路
使用 document.createTreeWalker 遍历文本节点计算全局偏移,通过 Range + Selection API 操作光标。
获取光标全局偏移
function getCursorIndex(container) {
const sel = window.getSelection()
if (!sel?.rangeCount) return -1
const node = sel.anchorNode, off = sel.anchorOffset
if (!node || !container.contains(node)) return -1
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT)
let count = 0
while (walker.nextNode()) {
if (walker.currentNode === node) return count + off
count += walker.currentNode.textContent.length
}
return count
}
设置光标到指定位置
function setCursorIndex(container, index) {
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT)
let count = 0
while (walker.nextNode()) {
const len = walker.currentNode.textContent.length
if (count + len >= index) {
const range = document.createRange()
range.setStart(walker.currentNode, index - count)
range.collapse(true)
const sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
return
}
count += len
}
}
插入文本并设置光标
const text = foundNode.textContent
foundNode.textContent = text.slice(0, offset) + pair + text.slice(offset)
const range = document.createRange()
range.setStart(foundNode, offset + half)
range.collapse(true)
window.getSelection().removeAllRanges()
window.getSelection().addRange(range)
优缺点
优点
- 纯标准 DOM API,兼容性最好
- 不依赖任何框架内部属性
缺点
- 代码较复杂
- 直接改 textContent 可能破坏 Quill Delta 同步
- 富文本元素(图片等)偏移计算需额外处理
两种方案对比
代码量
- Quill API:3 行
- DOM API:30+ 行
-
Delta 同步
- Quill API:✅ 完全同步
- DOM API:❌ 可能不同步
撤销/重做
- Quill API:✅ 正常
- DOM API:❌ 可能受影响
WebView 兼容性
- Quill API:⭐⭐⭐(依赖 Quill 版本)
- DOM API:⭐⭐⭐⭐⭐
uni-app App 端实践要点
- 逻辑层无 DOM,所有 DOM 操作必须通过 renderjs 执行
- renderjs 通过
change:prop模板绑定接收逻辑层数据,通过ownerVm.callMethod()回传状态 - 方法签名:
methodName(newVal, oldVal, ownerVm, vm) - 推荐双方案:优先 Quill API → 失败则降级 DOM API
客户端网络请求(SM2签名,SM4加密)与java服务端之前加密通信
sunrains-request
注:客户端与服务端之间通信 数据 加密,签名,保证数据安全与防串改(完整功能依赖付费插件sunrains-smutil)
客户端与服务端(java)之间通信数据加密、签名,保证数据安全与防篡改
支持平台:微信小程序、H5、Android、iOS、鸿蒙
请求方式支持:GET、POST(application/json、application/x-www-form-urlencoded、multipart/form-data(计划中))
服务端(java)点击 sunrains-java
模块目录结构
sunrains-request/
├── index.uts # 模块入口,统一导出
├── js_sdk/
│ ├── inutils/
│ │ └── requestApi.uts # 底层网络请求封装(uni.request / uni.uploadFile)
│ └── openutil/
│ ├── base64Util.uts # Base64 编解码工具
│ ├── randomUtil.uts # 随机字符串生成
│ └── request/
│ ├── Request.uts # 请求入口(ReqApi)
│ └── reqHandle/
│ ├── ReqHandle.uts # 请求处理抽象基类
│ ├── handle.uts # 策略工厂(根据 reqFlag 选择 Handler)
│ └── handle/
│ ├── VoidHandle.uts # 无加密无签名模式
│ ├── SignHandle.uts # 仅签名模式
│ ├── EncryptHandle.uts # 仅SM4加密模式
│ └── SignWithEncryptHandle.uts # 签名+SM4加密模式
导出 API 一览
| 导出函数 | 来源文件 | 说明 |
|---|---|---|
ReqApi |
request/Request.uts |
高层请求入口,自动根据 reqFlag 完成签名/加密/发送/解密 |
RequestApi |
inutils/requestApi.uts |
底层请求入口,直接发起 uni.request 并返回 Promise\<Response> |
uploadFile |
inutils/requestApi.uts |
文件上传,返回 Promise\<Response> |
stringToBase64 |
openutil/base64Util.uts |
字符串 → Base64 编码 |
base64ToString |
openutil/base64Util.uts |
Base64 → 字符串解码 |
random |
openutil/randomUtil.uts |
生成指定长度的随机字符串(字母+数字),默认16位 |
网络请求架构(核心)
整体流程
调用方
│
▼
ReqApi(frontVo: FrontRequestVo) ← 高层入口
│
├─ 1. selectHandle(reqFlag) ← 根据模式选择 Handler
│
├─ 2. handle.handleHeaderAndBody(vo) ← 构造请求头 + 处理请求体(签名/加密)
│
├─ 3. RequestApi(req) ← 底层发起 HTTP 请求
│
└─ 4. handle.handleRes(req, res) ← 处理响应(解密/验签)
│
▼
返回 Response
四种请求模式(ReqFlag)
通过 FrontRequestVo.reqFlag 指定,决定请求的安全处理策略:
| reqFlag | Handler | 签名 | 请求体SM4加密 | 响应SM4解密 | 需要priKey | 需要pubKey |
|---|---|---|---|---|---|---|
"void" |
VoidHandle | ✗ | ✗ | ✗ | ✗ | ✗ |
"sign" |
SignHandle | ✓ SM2签名 | ✗ | ✗ | ✓ | ✗ |
"sm4Encrypt" |
EncryptHandle | ✗ | ✓ SM4加密 | ✓ SM4解密 | ✗ | ✓ |
"signwithenSM2-Sm4Encrypt" |
SignWithEncryptHandle | ✓ SM2签名 | ✓ SM4加密 | ✓ SM4解密 | ✓ | ✓ |
请求头处理(所有模式共用)
无论哪种模式,handleSignHeader 都会执行以下操作:
- SM2加密SM4密钥:生成随机 SM4 key + iv,用服务端公钥(pubKey)通过 SM2 加密后放入
header["key"] - 条件签名(sign / signwithenSM2-Sm4Encrypt 模式):
- 生成
timestamp(时间戳)和nonceStr(16位随机串) - 将所有业务参数 + timestamp + nonceStr 按 key 字典序排列,拼接为
key=value&格式 - 使用客户端私钥(priKey)对拼接字符串进行 SM2 签名
- 签名结果放入
header["sign"],同时放入header["timestamp"]和header["nonceStr"]
- 生成
请求体处理(按 HTTP Method + contentType)
GET 请求:
void/sign模式:参数拼接到 URL query stringsm4Encrypt/signwithenSM2-Sm4Encrypt模式:参数 JSON 序列化后 SM4 加密,拼接到?data=加密结果
POST 请求(按 contentType):
| contentType | 处理方式 |
|---|---|
"json" |
Content-Type: application/json,加密模式下 body 整体 SM4 加密为 {"data":"加密结果"} |
"urlencoded" |
Content-Type: application/x-www-form-urlencoded,加密模式下 data=加密结果 |
"form-data" |
Content-Type: multipart/form-data(暂未实现加密) |
响应处理
- 无加密模式(void / sign):直接返回 Response
- 加密模式(sm4Encrypt / signwithenSM2-Sm4Encrypt):
- 先校验外层
res.code == resSuccessCode()(当前定义为0) - 取出
res.data(SM4 加密的字符串) - 用本次请求生成的 SM4 key + iv 解密
- 解密结果为内层 Response,返回给调用方
- 先校验外层
底层请求(RequestApi)
RequestApi(req: RequestVo) → Promise<Response>
- 基于
uni.request封装 - 超时时间:
req.timeout ?? 6000ms - 成功(statusCode=200):resolve Response 对象
- Android 平台特殊处理:通过
JSON.parse(JSON.stringify(res.data))转换 - 其他平台:直接
res.data as Response
- Android 平台特殊处理:通过
- 失败(非200):reject
CustOtherFailError(包含 code + errorMsg) - 网络异常:reject
CustOtherFailError(包含 errCode + errMsg)
文件上传(uploadFile)
uploadFile(req: UploadFileOptions) → Promise<Response>
- 基于
uni.uploadFile封装 - 超时时间:
req.timeout ?? 12000ms - 成功时解析
res.data为 Response - 失败时 reject
err.errMsg
类型定义(依赖 sunrains-common-type)
FrontRequestVo(前置请求参数)
type FrontRequestVo = {
method: 'GET' | 'POST', // HTTP 方法
url: string, // 请求地址
data: UTSJSONObject | null, // 请求数据
header: UTSJSONObject | null, // 自定义请求头(会与签名/加密头合并)
reqFlag: ReqFlag, // 安全模式标识
priKey: string, // SM2 私钥(签名时使用)
pubKey: string, // SM2 公钥(加密SM4密钥时使用)
contentType: "json" | "urlencoded" | "form-data" | null, // POST内容类型
timeout: number | null, // 超时时间(ms)
filePathParamName: string | null // 上传文件时,文件路径的参数名
}
Response(统一响应)
type Response = {
success: boolean | null, // 是否成功
msg: string | null, // 提示信息
code: number, // 状态码(0=成功)
errorMsg: string | null, // 错误详情
data: any | null // 业务数据(加密模式下为密文字符串)
}
RequestVo(底层请求参数)
type RequestVo = {
url: string,
method?: 'GET' | 'POST' | null,
header: UTSJSONObject,
timeout?: number | null,
data: any
}
Sm4Key(SM4密钥)
type Sm4Key = {
key: string,
iv: string
}
错误处理(依赖 sunrains-common-err)
所有请求错误统一通过 UniError 抛出:
| 错误来源 | errSubject | errCode | message |
|---|---|---|---|
| SM2私钥为空 | SM2_PRI_KEY_BLANK | 预定义码 | 预定义信息 |
| SM2公钥为空 | SM2_PUB_KEY_BLANK | 预定义码 | 预定义信息 |
| SM2签名失败 | SM2_SIGN_FAIL | 预定义码 | 预定义信息 |
| HTTP非200 | "请求失败" | 服务端code | errorMsg / msg |
| 网络异常 | "请求失败" | err.errCode | err.errMsg |
| 加密模式响应码非0 | "请求失败" | res.code | errorMsg / msg |
调用方 catch 示例:
try {
const res = await ReqApi(frontVo)
} catch (err) {
// 跨平台安全取值(避免 iOS 上 err.message 崩溃)
console.log("请求异常:", `${err}`)
// 或通过 err.message 获取错误信息(err 为 UniError 实例)
}
工具函数
Base64 编解码
import { stringToBase64, base64ToString } from "@/uni_modules/sunrains-request"
const encoded = stringToBase64("Hello") // "SGVsbG8="
const decoded = base64ToString("SGVsbG8=") // "Hello"
随机字符串
import { random } from "@/uni_modules/sunrains-request"
const nonce = random(16) // 生成16位随机字符串
依赖模块
| 模块 | 用途 |
|---|---|
sunrains-smutil |
SM2签名/加解密、SM4加解密 |
sunrains-common-type |
类型定义(FrontRequestVo、Response、RequestVo、Sm4Key、ReqFlag) |
sunrains-common-err |
统一错误类(CustFailError、CustOtherFailError) |
使用示例
1. 无加密请求(void)
import { ReqApi } from "@/uni_modules/sunrains-request"
const frontVo: FrontRequestVo = {
method: "POST",
url: "http://your-api.com/test",
data: { name: "张三", age: 18 },
header: null,
reqFlag: "void",
priKey: "",
pubKey: "",
contentType: "json",
timeout: null,
filePathParamName: null
}
const res = await ReqApi(frontVo)
2. SM2签名请求(sign)
const frontVo: FrontRequestVo = {
method: "POST",
url: "http://your-api.com/test",
data: { name: "张三", age: 18 },
header: null,
reqFlag: "sign",
priKey: "客户端SM2私钥",
pubKey: "服务端SM2公钥",
contentType: "json",
timeout: null,
filePathParamName: null
}
const res = await ReqApi(frontVo)
3. 签名 + SM4加密请求(signwithenSM2-Sm4Encrypt)
const frontVo: FrontRequestVo = {
method: "POST",
url: "http://your-api.com/test",
data: { name: "张三", age: 18 },
header: null,
reqFlag: "signwithenSM2-Sm4Encrypt",
priKey: "客户端SM2私钥",
pubKey: "服务端SM2公钥",
contentType: "json",
timeout: null,
filePathParamName: null
}
const res = await ReqApi(frontVo)
// res.data 已自动解密
4. 文件上传(计划中)
sunrains-request
注:客户端与服务端之间通信 数据 加密,签名,保证数据安全与防串改(完整功能依赖付费插件sunrains-smutil)
客户端与服务端(java)之间通信数据加密、签名,保证数据安全与防篡改
支持平台:微信小程序、H5、Android、iOS、鸿蒙
请求方式支持:GET、POST(application/json、application/x-www-form-urlencoded、multipart/form-data(计划中))
服务端(java)点击 sunrains-java
模块目录结构
sunrains-request/
├── index.uts # 模块入口,统一导出
├── js_sdk/
│ ├── inutils/
│ │ └── requestApi.uts # 底层网络请求封装(uni.request / uni.uploadFile)
│ └── openutil/
│ ├── base64Util.uts # Base64 编解码工具
│ ├── randomUtil.uts # 随机字符串生成
│ └── request/
│ ├── Request.uts # 请求入口(ReqApi)
│ └── reqHandle/
│ ├── ReqHandle.uts # 请求处理抽象基类
│ ├── handle.uts # 策略工厂(根据 reqFlag 选择 Handler)
│ └── handle/
│ ├── VoidHandle.uts # 无加密无签名模式
│ ├── SignHandle.uts # 仅签名模式
│ ├── EncryptHandle.uts # 仅SM4加密模式
│ └── SignWithEncryptHandle.uts # 签名+SM4加密模式
导出 API 一览
| 导出函数 | 来源文件 | 说明 |
|---|---|---|
ReqApi |
request/Request.uts |
高层请求入口,自动根据 reqFlag 完成签名/加密/发送/解密 |
RequestApi |
inutils/requestApi.uts |
底层请求入口,直接发起 uni.request 并返回 Promise\<Response> |
uploadFile |
inutils/requestApi.uts |
文件上传,返回 Promise\<Response> |
stringToBase64 |
openutil/base64Util.uts |
字符串 → Base64 编码 |
base64ToString |
openutil/base64Util.uts |
Base64 → 字符串解码 |
random |
openutil/randomUtil.uts |
生成指定长度的随机字符串(字母+数字),默认16位 |
网络请求架构(核心)
整体流程
调用方
│
▼
ReqApi(frontVo: FrontRequestVo) ← 高层入口
│
├─ 1. selectHandle(reqFlag) ← 根据模式选择 Handler
│
├─ 2. handle.handleHeaderAndBody(vo) ← 构造请求头 + 处理请求体(签名/加密)
│
├─ 3. RequestApi(req) ← 底层发起 HTTP 请求
│
└─ 4. handle.handleRes(req, res) ← 处理响应(解密/验签)
│
▼
返回 Response
四种请求模式(ReqFlag)
通过 FrontRequestVo.reqFlag 指定,决定请求的安全处理策略:
| reqFlag | Handler | 签名 | 请求体SM4加密 | 响应SM4解密 | 需要priKey | 需要pubKey |
|---|---|---|---|---|---|---|
"void" |
VoidHandle | ✗ | ✗ | ✗ | ✗ | ✗ |
"sign" |
SignHandle | ✓ SM2签名 | ✗ | ✗ | ✓ | ✗ |
"sm4Encrypt" |
EncryptHandle | ✗ | ✓ SM4加密 | ✓ SM4解密 | ✗ | ✓ |
"signwithenSM2-Sm4Encrypt" |
SignWithEncryptHandle | ✓ SM2签名 | ✓ SM4加密 | ✓ SM4解密 | ✓ | ✓ |
请求头处理(所有模式共用)
无论哪种模式,handleSignHeader 都会执行以下操作:
- SM2加密SM4密钥:生成随机 SM4 key + iv,用服务端公钥(pubKey)通过 SM2 加密后放入
header["key"] - 条件签名(sign / signwithenSM2-Sm4Encrypt 模式):
- 生成
timestamp(时间戳)和nonceStr(16位随机串) - 将所有业务参数 + timestamp + nonceStr 按 key 字典序排列,拼接为
key=value&格式 - 使用客户端私钥(priKey)对拼接字符串进行 SM2 签名
- 签名结果放入
header["sign"],同时放入header["timestamp"]和header["nonceStr"]
- 生成
请求体处理(按 HTTP Method + contentType)
GET 请求:
void/sign模式:参数拼接到 URL query stringsm4Encrypt/signwithenSM2-Sm4Encrypt模式:参数 JSON 序列化后 SM4 加密,拼接到?data=加密结果
POST 请求(按 contentType):
| contentType | 处理方式 |
|---|---|
"json" |
Content-Type: application/json,加密模式下 body 整体 SM4 加密为 {"data":"加密结果"} |
"urlencoded" |
Content-Type: application/x-www-form-urlencoded,加密模式下 data=加密结果 |
"form-data" |
Content-Type: multipart/form-data(暂未实现加密) |
响应处理
- 无加密模式(void / sign):直接返回 Response
- 加密模式(sm4Encrypt / signwithenSM2-Sm4Encrypt):
- 先校验外层
res.code == resSuccessCode()(当前定义为0) - 取出
res.data(SM4 加密的字符串) - 用本次请求生成的 SM4 key + iv 解密
- 解密结果为内层 Response,返回给调用方
- 先校验外层
底层请求(RequestApi)
RequestApi(req: RequestVo) → Promise<Response>
- 基于
uni.request封装 - 超时时间:
req.timeout ?? 6000ms - 成功(statusCode=200):resolve Response 对象
- Android 平台特殊处理:通过
JSON.parse(JSON.stringify(res.data))转换 - 其他平台:直接
res.data as Response
- Android 平台特殊处理:通过
- 失败(非200):reject
CustOtherFailError(包含 code + errorMsg) - 网络异常:reject
CustOtherFailError(包含 errCode + errMsg)
文件上传(uploadFile)
uploadFile(req: UploadFileOptions) → Promise<Response>
- 基于
uni.uploadFile封装 - 超时时间:
req.timeout ?? 12000ms - 成功时解析
res.data为 Response - 失败时 reject
err.errMsg
类型定义(依赖 sunrains-common-type)
FrontRequestVo(前置请求参数)
type FrontRequestVo = {
method: 'GET' | 'POST', // HTTP 方法
url: string, // 请求地址
data: UTSJSONObject | null, // 请求数据
header: UTSJSONObject | null, // 自定义请求头(会与签名/加密头合并)
reqFlag: ReqFlag, // 安全模式标识
priKey: string, // SM2 私钥(签名时使用)
pubKey: string, // SM2 公钥(加密SM4密钥时使用)
contentType: "json" | "urlencoded" | "form-data" | null, // POST内容类型
timeout: number | null, // 超时时间(ms)
filePathParamName: string | null // 上传文件时,文件路径的参数名
}
Response(统一响应)
type Response = {
success: boolean | null, // 是否成功
msg: string | null, // 提示信息
code: number, // 状态码(0=成功)
errorMsg: string | null, // 错误详情
data: any | null // 业务数据(加密模式下为密文字符串)
}
RequestVo(底层请求参数)
type RequestVo = {
url: string,
method?: 'GET' | 'POST' | null,
header: UTSJSONObject,
timeout?: number | null,
data: any
}
Sm4Key(SM4密钥)
type Sm4Key = {
key: string,
iv: string
}
错误处理(依赖 sunrains-common-err)
所有请求错误统一通过 UniError 抛出:
| 错误来源 | errSubject | errCode | message |
|---|---|---|---|
| SM2私钥为空 | SM2_PRI_KEY_BLANK | 预定义码 | 预定义信息 |
| SM2公钥为空 | SM2_PUB_KEY_BLANK | 预定义码 | 预定义信息 |
| SM2签名失败 | SM2_SIGN_FAIL | 预定义码 | 预定义信息 |
| HTTP非200 | "请求失败" | 服务端code | errorMsg / msg |
| 网络异常 | "请求失败" | err.errCode | err.errMsg |
| 加密模式响应码非0 | "请求失败" | res.code | errorMsg / msg |
调用方 catch 示例:
try {
const res = await ReqApi(frontVo)
} catch (err) {
// 跨平台安全取值(避免 iOS 上 err.message 崩溃)
console.log("请求异常:", `${err}`)
// 或通过 err.message 获取错误信息(err 为 UniError 实例)
}
工具函数
Base64 编解码
import { stringToBase64, base64ToString } from "@/uni_modules/sunrains-request"
const encoded = stringToBase64("Hello") // "SGVsbG8="
const decoded = base64ToString("SGVsbG8=") // "Hello"
随机字符串
import { random } from "@/uni_modules/sunrains-request"
const nonce = random(16) // 生成16位随机字符串
依赖模块
| 模块 | 用途 |
|---|---|
sunrains-smutil |
SM2签名/加解密、SM4加解密 |
sunrains-common-type |
类型定义(FrontRequestVo、Response、RequestVo、Sm4Key、ReqFlag) |
sunrains-common-err |
统一错误类(CustFailError、CustOtherFailError) |
使用示例
1. 无加密请求(void)
import { ReqApi } from "@/uni_modules/sunrains-request"
const frontVo: FrontRequestVo = {
method: "POST",
url: "http://your-api.com/test",
data: { name: "张三", age: 18 },
header: null,
reqFlag: "void",
priKey: "",
pubKey: "",
contentType: "json",
timeout: null,
filePathParamName: null
}
const res = await ReqApi(frontVo)
2. SM2签名请求(sign)
const frontVo: FrontRequestVo = {
method: "POST",
url: "http://your-api.com/test",
data: { name: "张三", age: 18 },
header: null,
reqFlag: "sign",
priKey: "客户端SM2私钥",
pubKey: "服务端SM2公钥",
contentType: "json",
timeout: null,
filePathParamName: null
}
const res = await ReqApi(frontVo)
3. 签名 + SM4加密请求(signwithenSM2-Sm4Encrypt)
const frontVo: FrontRequestVo = {
method: "POST",
url: "http://your-api.com/test",
data: { name: "张三", age: 18 },
header: null,
reqFlag: "signwithenSM2-Sm4Encrypt",
priKey: "客户端SM2私钥",
pubKey: "服务端SM2公钥",
contentType: "json",
timeout: null,
filePathParamName: null
}
const res = await ReqApi(frontVo)
// res.data 已自动解密
4. 文件上传(计划中)
收起阅读 »
uniapp x 自定义+动态Tabbar+自定义tabbar 支持服务端动态配置的自定义Tabbar 主题切换(tabbar切换无闪烁问题)
sunrains-tabbar 自定义 TabBar 插件使用指南
点击体验h5端
📋 目录
- [1. 基础配置]
- [2. pages.json 配置]
- [3. 动态配置 TabBar]
- [4. 数字角标功能]
- [5. 小程序端特殊配置]
- [6. 主题配置]
- [7. page页跳转自定义tab页]
1. 基础配置
App.uvue onLaunch 初始化
import {initDefaultTabItems} from '@/uni_modules/sunrains-tabbar/tabbar-config.uts'
onLaunch(() => {
console.log('App Launch')
initDefaultTabItems()
}
1.1 创建 tabbar-config.uts 文件
在项目根目录创建 tabbar-config.uts 文件,用于注册全局组件和配置导航栏。
完整示例:
// 1. 导入所有自定义 tabbar 页面组件并注册
import Recommend from '@/pages/home/recommend.uvue'
import Order from '@/pages/order/order.uvue'
import Mine from '@/pages/mine/mine.uvue'
import {
setNavbarConfig,
NavBarConfig,
registerGlobalComponent,
setDefaultTabItems,
TabItem
} from '@/uni_modules/sunrains-tabbar/types.uts'
// 2. 注册全局组件
registerGlobalComponent('home', Recommend)
registerGlobalComponent('order', Order)
registerGlobalComponent('mine', Mine)
// 3. 配置导航栏(可选,不设置会使用默认配置)
setNavbarConfig(new NavBarConfig(
true, // 是否显示导航栏
17, // 字体大小
'bold', // 字体粗细
'#333333', // 文字颜色
'#ffffff', // 背景颜色
'', // 背景图片(支持网络图片)
56, // 导航栏高度
))
// 4. 设置默认 Tab 列表(至少设置一个固定项)
// 其他 Tab 可通过服务端接口动态配置
setDefaultTabItems([
new TabItem(
'home', // 唯一标识
'首页', // 显示名称
'/static/tab/main1.png', // 未选中图标
'/static/tab/main1_active.png', // 选中图标
'@/pages/home/recommend.uvue', // 页面路径
Recommend // 组件引用
),
new TabItem(
'mine',
'我的',
'/static/tab/main3.png',
'/static/tab/main3_active.png'
)
])
关键步骤说明:
- 导入组件:将所有需要作为 Tab 的页面组件导入
- 注册组件:使用
registerGlobalComponent注册每个组件(第一个参数为唯一标识) - 配置导航栏:可选配置,根据项目需求调整
- 设置默认 Tab:至少配置一个固定 Tab,其余可动态添加
2. pages.json 配置
2.1 必需配置
必须在 pages.json 中配置自定义 TabBar 入口页面(必须放在第一个位置):
{
"path": "uni_modules/sunrains-tabbar/index",
"style": {
"navigationBarTitleText": "自定义tabbar",
"navigationStyle": "custom"
}
}
2.2 完整示例
{
"pages": [
{
"path": "uni_modules/sunrains-tabbar/index",
"style": {
"navigationBarTitleText": "自定义tabbar",
"navigationStyle": "custom"
}
},
{
"path": "pages/home/recommend",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/mine/mine",
"style": {
"navigationBarTitleText": "我的"
}
},
{
"path": "pages/order/order",
"style": {
"navigationBarTitleText": "订单"
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "详情"
}
}
],
"globalStyle": {
"backgroundColor": "#ffffff"
}
}
注意事项:
- 自定义 TabBar 入口页面必须配置在
pages数组的第一位 - 所有 Tab 页面都需要在
pages中声明 navigationStyle设置为"custom"以隐藏原生导航栏
3. 动态配置 TabBar
3.1 使用场景
当需要根据用户权限、服务端配置等动态调整 TabBar 时,可以在任意页面触发刷新。
3.2 实现示例
以下面文档 mine.uvue 页面为例:
关键点:
- 使用 refreshTabbar方法触发 TabBar 刷新
- 确保所有动态 Tab 对应的组件已在
tabbar-config.uts中注册
4. 数字角标功能
4.1 功能说明
支持为任意 Tab 设置数字角标,用于显示未读消息数、待处理事项等。
特性:
- ✅ 动态设置/更新角标
- ✅ 超过 99 自动显示 "99+"
- ✅ 角标为 0 或 null 时不显示
- ✅ 响应式更新,立即生效
4.2 使用方法
方法一:通过事件设置(推荐)
在任意页面通过 调用 setTabBadge 方法:
import { setTabBadge} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
// 设置订单 tab 的角标为 5
setTabBadge("order",5)
// 清除角标(设置为 0)
setTabBadge("order",0)
// 设置超过 99 显示 99+
setTabBadge("order",150)
方法二:初始化时设置
在创建 TabItem 时传入 badge 参数:
import { TabItem } from '@/uni_modules/sunrains-tabbar/utssdk/types.uts'
// 带角标的 tab
new TabItem(
'order',
'订单',
'/static/tab/main2.png',
'/static/tab/main2_active.png'
10 // badge 参数
)
// 不带角标的 tab
new TabItem(
'home',
'首页',
'/static/tab/main1.png',
'/static/tab/main1_active.png'
)
4.3 完整示例
以 文档后面 mine.uvue 页面为例,实现动态设置角标功能:
4.4 实际应用场景
场景 1:订单未读数
// 从服务端获取未读订单数
async function loadUnreadOrderCount() {
const res = await getOrderCount()
setTabBadge("order",res.data.count)
}
// 页面加载时调用
onShow(() => {
loadUnreadOrderCount()
})
场景 2:消息通知
// WebSocket 收到新消息
websocket.onMessage((msg) => {
if (msg.type === 'new_message') {
const currentBadge = getCurrentBadge('message')
setTabBadge("newMessage",currentBadge + 1)
}
})
场景 3:购物车数量
// 添加商品到购物车后更新角标
function addToCart(product: Product) {
cartItems.push(product)
setTabBadge("cart",cartItems.length)
}
5. 小程序端特殊配置
4.1 配置 tab-content-renderer.uvue
注意: 仅在需要支持微信小程序时需要配置,App 端可忽略此步骤。
在 uni_modules/sunrains-tabbar/tab-content-renderer.uvue 中配置:
<template>
<view class="renderer-wrap">
<!-- App 端:使用动态组件 -->
<!-- #ifndef MP-WEIXIN -->
<template v-for="item in items" :key="item.key">
<component
v-if="currentTab === item.key"
:is="item.component"
:key="`${item.key}-${renderKey}`"
/>
</template>
<!-- #endif -->
<!-- 微信小程序端:使用静态声明 -->
<!-- #ifdef MP-WEIXIN -->
<Recommend v-if="currentTab === 'home'" style="width: 100%; height: 100%;" />
<Order v-if="currentTab === 'order'" style="width: 100%; height: 100%;" />
<Mine v-if="currentTab === 'mine'" style="width: 100%; height: 100%;" />
<!-- #endif -->
</view>
</template>
<script setup lang="uts">
import { TabItem } from './utssdk/types.uts'
// #ifdef MP-WEIXIN
// 只有在小程序端才需要这些导入,App 端会自动忽略
import Recommend from '@/pages/home/recommend.uvue'
import Order from '@/pages/order/order.uvue'
import Mine from '@/pages/mine/mine.uvue'
// #endif
interface Props {
currentTab: string
items: TabItem[]
renderKey: number
}
defineProps<Props>()
</script>
<style scoped>
.renderer-wrap {
width: 100%;
height: 100%;
}
</style>
配置说明:
- 条件编译:使用
#ifndef MP-WEIXIN和#ifdef MP-WEIXIN区分平台 - App 端:使用
<component :is="...">动态渲染组件 - 小程序端:必须静态导入并声明所有可能的 Tab 组件
- 导入位置:在
#ifdef MP-WEIXIN块内导入组件
6. 主题配置
6.1 主题功能说明
插件支持自定义 TabBar 的主题样式,支持网络背景图、本地图片背景图、纯颜色背景(支持rgba透明)、导航栏、tabbar区域背景、图标大小、图标(支持网络图标)、文字颜色、文字大小、等属性 满足不同项目的视觉需求。
主题特性:
- ✅ 支持全局主题配置
- ✅ 支持动态切换主题
- ✅ 支持自定义主题参数
6.2 初始化时配置主题
在 tabbar-config.uts 文件中配置主题:
import {
setNavbarConfig,
NavBarConfig,
registerGlobalComponent,
setDefaultTabItems,
TabItem,
setTabBarTheme,
TabBarTheme
} from '@/uni_modules/sunrains-tabbar/types.uts'
import { setTabPageTheme} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
let bgType : BgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/2.png"
//配置导航栏(可选,不设置会使用默认配置)
let navBarConfig : NavBarConfig = new NavBarConfig(
true, // 是否显示导航栏
34, // 字体大小
'bold', // 字体粗细
'#FFA500', // 文字颜色
'',// 背景颜色
"", //背景图片(支持网络图片)
90) // 导航栏高度
let tabbarIcon = new TabbarIcon(
"mine",//自定义组件唯一标识
//非选中图标
"https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4",
//选中图标
"/static/tab/main3_active.png",
//文字颜色
12,
//非选中文字大小
"green",
//选择文字颜色
"white",
//选择文字粗细
"bold"
)
let tabbarIcons : Map<string,TabbarIcon> = new Map();
tabbarIcons.set("mine",tabbarIcon)
let tabbarStyle : TabbarStyle = new TabbarStyle(
//tabbar区域高度
120,
//tabbar区域背景
"rgba(21, 20, 31, 0.8)",
//tabbar图标文字样式
tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
6.3 动态修改主题
在任意页面通过调用 setTabPageTheme 方法动态修改主题:
import { setTabPageTheme} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
// 动态修改背景色和选中颜色
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
6.4 在页面中动态切换主题
以 mine.uvue 页面为例,实现主题切换功能:
<template>
<scroll-view direction="vertical" class="mine-page"
show-scrollbar = "false"
enable-back-to-top="true"
refresher-enabled="true"
refresher-default-style="none"
refresher-background="rgba(232, 201, 183, 0.5)"
:refresher-triggered="data.refreshing2"
refresher-max-drag-distance="200px"
@refresherpulling="onRefresherpulling2"
@refresherrefresh="onRefresherrefresh2"
@refresherrestore="onRefresherrestore2"
>
<uni-refresh-box
slot="refresher"
:pulling-distance="data.pullingDistance2"
:refreshing="data.refreshing2"
loading-class="loading-dark"
text-class="uni-text-class-buildin"
style="flex-direction: column;height: 46px;padding-top: 6px;"
pulling-text="继续下拉可刷新"
loosing-text="释放后会刷新"
loading-text="奋力加载中..."
/>
<view class="page-title">
<text style="color:#ff4d40">动态设置中心</text>
</view>
<view class="tabbar">
<text class="title-text">tabbar设置</text>
<button class="tabbar-btn" @click="refresh1">动态tabbar(我的)</button>
<button class="tabbar-btn" @click="refresh2">动态tabbar(首页、我的)</button>
<button class="tabbar-btn" @click="refresh3">动态tabbar(订单、我的)</button>
<button class="tabbar-btn" @click="refresh4">动态tabbar(我的、订单、首页)</button>
<text class="title-text">设置角标</text>
<input class="input-item" v-model="tabbar" placeholder="请输入tabbar" />
<input class="input-item" v-model="badge" placeholder="请输入角标" />
<button class="tabbar-btn" @click="setBadge">确定</button>
<button class="tabbar-btn" @click="theme">主题1</button>
<button class="tabbar-btn" @click="theme2">主题2</button>
<button class="tabbar-btn" @click="theme3">主题3</button>
<button class="tabbar-btn" @click="clear">clear</button>
<button class="tabbar-btn" type="default" @click="refresh">刷新</button>
</view>
</scroll-view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { TabItem, GLOBAL_COMPONENT_REGISTRY, CustomTheme, TabbarIcon, TabbarStyle, NavBarConfig } from '@/uni_modules/sunrains-tabbar/utssdk/types.uts'
import { setTabBadge,setTabbarColorHeight,refreshTabbar ,setTabPageTheme} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
// 用于自动化测试的数据暴露 - 使用 reactive 将所有 ref 状态集中暴露
const data = reactive({
refreshing2: false,
pullingDistance2: 0
})
function onRefresherpulling2(e: RefresherEvent) {
data.pullingDistance2 = e.detail.dy
}
const refreshing = ref(false)
function onRefresherrefresh2() {
data.refreshing2 = true
console.log('触发刷新')
setTimeout(() => {
data.refreshing2 = false
console.log('刷新完成')
}, 1500)
}
function onRefresherrestore2() {
data.pullingDistance2 = 0
}
function onRefresherabort2() {
data.pullingDistance2 = 0
}
// 上拉触底加载
const loadMore = () => {
console.log("滚动到底部", "info")
}
function clear() {
uni.removeStorageSync("CUSTOMTHEME")
}
const bgColor = ref<string>('pink')
const tabHeight = ref<string>('120')
const badge = ref<string>('0')
const ss = ref<any>("");
// tab 选项配置
const tabbar = ref<string | null>(null)
const selectedTab = ref<string>('mine')
const selectedTabIndex = ref<number>(0)
function setBadge() {
//动态设置指定tabbar 角标
const badgeNum = parseInt(badge.value)
if (isNaN(badgeNum)) {
uni.showToast({
title: '请输入有效数字',
icon: 'none'
})
return
}
setTabBadge(tabbar.value,badgeNum)
uni.showToast({
title: `已设置 ${tabbar.value} 角标为 ${badgeNum}`,
icon: 'success'
})
}
function setStyle() {
setTabbarColorHeight(bgColor.value,parseInt(tabHeight.value) )
}
function refresh1() {
console.log("动态tabbar(我的)")
refreshTabbar([
new TabItem('mine', 12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png')
]);
}
function refresh2() {
console.log("动态tabbar(首页、我的)")
refreshTabbar([
new TabItem('home', 12, "red", "yellow", "bold", '首页', '/static/tab/main1.png', '/static/tab/main1_active.png'),
new TabItem('mine', 12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png')
])
}
function refresh3() {
console.log("动态tabbar(订单、我的)")
refreshTabbar( [
new TabItem('order', 12, "red", "yellow", "bold", '订单', '/static/tab/main2.png', '/static/tab/main2_active.png'),
new TabItem('mine', 12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png')
])
}
function refresh4() {
console.log("动态tabbar(我的、订单、首页)")
refreshTabbar([
new TabItem('mine', 12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png'),
new TabItem('order', 12, "red", "yellow", "bold", '订单', '/static/tab/main2.png', '/static/tab/main2_active.png'),
new TabItem('home', 12, "red", "yellow", "bold", '首页', '/static/tab/main1.png',
'https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4')
])
}
function theme() {
//主题背景类型
let bgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/2.png"
// let bg:string = "black"
let navBarConfig : NavBarConfig = new NavBarConfig(true, 34, 'bold', '#FFA500', "rgba(191, 191, 191, 0.5)", "", 90)
let tabbarIcon = new TabbarIcon("mine",
"https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4",
"/static/tab/main3_active.png",
12,
"green",
"white",
"bold"
)
let tabbarIcons : Map<string, TabbarIcon> = new Map();
tabbarIcons.set("mine", tabbarIcon)
let tabbarStyle : TabbarStyle = new TabbarStyle(120, "rgba(21, 20, 31, 0.8)", tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
}
function theme2() {
//主题背景类型
let bgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/sm.jpg"
// let bg:string = "black"
let navBarConfig : NavBarConfig = new NavBarConfig(false, 34, 'bold', '#0c0c0c', "", "", 90)
let tabbarIcon = new TabbarIcon("mine",
"/static/aj.png",
"/static/aj1.png",
20,
"green",
"red",
"bold"
)
let tabbarIcon1 = new TabbarIcon("home",
"/static/sy1.png",
"/static/sy.png",
16,
"black",
"yellow",
"bold"
)
let tabbarIcons : Map<string, TabbarIcon> = new Map();
tabbarIcons.set("mine", tabbarIcon)
tabbarIcons.set("home", tabbarIcon1)
let tabbarStyle : TabbarStyle = new TabbarStyle(120, "rgba(21, 20, 31, 0.4)", tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
}
function theme3() {
//主题背景类型
let bgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/777.png"
// let bg:string = "black"
let navBarConfig : NavBarConfig = new NavBarConfig(true, 34, 'bold', '#0c0c0c', "", "", 90)
let tabbarIcon = new TabbarIcon("mine",
"/static/aj.png",
"/static/aj1.png",
20,
"green",
"red",
"bold"
)
let tabbarIcon1 = new TabbarIcon("home",
"/static/sy1.png",
"/static/sy.png",
16,
"#1afa29",
"yellow",
"bold"
)
let tabbarIcons : Map<string, TabbarIcon> = new Map();
tabbarIcons.set("mine", tabbarIcon)
tabbarIcons.set("home", tabbarIcon1)
let tabbarStyle : TabbarStyle = new TabbarStyle(200, "rgba(10, 13, 31, 0.8)", tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
}
function init() {
uni.stopPullDownRefresh()
console.log('stop onPullDownRefresh=====');
}
onPullDownRefresh(() => {
console.log('onPullDownRefresh=====');
init()
})
function refresh() {
uni.startPullDownRefresh({
success: () => {
console.log("1111")
}
});
init()
}
</script>
<style scoped lang="scss">
.mine-page {
height: 100%;
// border: 12px solid red;
margin: 5px;
}
.page-title {
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.title-text {
font-size: 18px;
font-weight: bold;
color: #333;
}
.tabbar {
// border: 2px solid cyan;
padding: 15px;
display: flex;
flex-direction: column;
}
.input-item {
height: 45px;
border: 1px solid #ccc;
border-radius: 6px;
padding-left: 10px;
background: #fff;
margin: 10px;
padding-left: 20px;
}
.picker-item {
height: 45px;
border: 1px solid #ccc;
border-radius: 6px;
background: #fff;
margin: 10px;
display: flex;
align-items: center;
}
.picker-display {
padding-left: 20px;
font-size: 16px;
color: #333;
}
.tabbar-btn {
margin-bottom: 10px;
border-radius: 8px;
background: #f5f5f5;
border: 1px solid #e5e5e5;
padding: 16px 0;
font-size: 16px;
}
.tabbar-btn:last-child {
margin-bottom: 0;
}
</style>
6.5 主题配置注意事项
- 颜色格式:颜色值必须为有效的 CSS 颜色格式(如
#ffffff、rgb(255,255,255)等) - tabbar区域高度单位:高度值为数字类型,单位为 rpx
- 动态修改:通过
uni.$emit('setTheme', new CustomTheme(bgType, bg, navBarConfig, tabbarStyle))动态修改的主题会立即生效
7 page页跳转自定义tab页
使用 pageToTab 方法 跳转到tab页
以 detail.uvue 页面为例,实现主题切换功能:
<template>
<view class="detail-root">
<view class="detail-content">
<view class="detail-item">
<text class="label">订单编号:</text>
<text class="value">{{ orderId }}</text>
</view>
<view class="detail-item">
<text class="label">订单状态:</text>
<text class="value">{{ orderStatus }}</text>
</view>
<view class="detail-desc">
<text>这是订单{{ orderId }}的详细信息页面</text>
</view>
<view>
<input v-model="content" placeholder="请输入。。" />
<text>{{sbase64}}</text>
<text>{{base64yostr}}</text>
</view>
<view>
<button type="primary" @click="req">访问testvoid</button>
<button type="primary" @click="reqSm2">访问testSm2</button>
<button type="primary" @click="reqSm4">访问testSm4</button>
<button type="primary" @click="reqSm2Sm4">访问testreqSm2Sm4</button>
<input style="height: 50px; border:1px solid grey;margin: 5px 0;" v-model="addr" placeholder="输入跳转tabbar key"/>
<button type="default" @click="tz">跳转指定tab页</button>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, computed } from 'vue'
import { stringToBase64, base64ToString, ReqApi } from "@/uni_modules/sunrains-request"
import { Response, FrontRequestVo } from "@/uni_modules/sunrains-common-type"
import { CustFailError, getComErrorInfo } from "@/uni_modules/sunrains-common-err"
import { pageToTab} from "@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts"
const addr = ref("")
function tz(){
if(addr.value.length<1){
uni.showToast({
title: '请输入跳转的tabbar key'
});
}else{
pageToTab(addr.value)
}
}
let priKey = "4a4a1d48fc284fe2fc43970641880554aed2f69fdfbea63257ea417af4e65802"
let pubKey = "0460e86263ec52b38a3da24c7a605055d14f8aa5c7855e4f4e19819f4f8a5b72181afd7aa4ab19255eb871eaedaba30fa18cd5c71693d3d76b030a04969bd1edfe"
const BASEURL = "http://192.168.1.9:8081"
type IRootType = {
name : string;
age : number;
}
type Builds = {
buildingNo : string,
sortNum : number,
buildAreaId : string,
id : string
}
async function req() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'GET',
url: BASEURL + "/test/get/cs1",
data: data,
header: null,
reqFlag: "void",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqvoid==", res.data)
// console.log("####==",res)
} catch (err) {
uni.showToast({
title: err.message ?? "请求异常",
icon: "error"
})
console.log("-----", err)
return
};
}
//"sign"|"sm4Encrypt"|"signwithenSM2-Sm4Encrypt"|"void";
async function reqSm2() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'GET',
url: BASEURL + "/test/get/cs1",
data: data,
header: null,
reqFlag: "sign",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqSm2==", res.data)
} catch (err) {
uni.showToast({
title: err.message ?? "请求异常",
icon: "error"
})
//#ifndef APP-ANDROID
const errMsg = (err as any).errMsg ?? "未知错误";
const errCode = (err as any).errCode ?? -1;
console.log('标准化错误码:', errMsg);
console.log('标准化错误信息:', errCode);
//#else
if (err instanceof UniError) {
let sa = err as UniError
console.log('sa==:', sa);
}
//#endif
console.log("-----", err)
return
};
}
async function reqSm4() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'POST',
url: BASEURL + "/test/post/cs1",
data: data,
header: null,
reqFlag: "sm4Encrypt",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqSm4==", res.data)
} catch (err) {
console.log("####reqSm4 err==", err)
uni.showToast({
title: err.message??"",
icon: "error"
})
return
};
}
async function reqSm2Sm4() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'POST',
url: BASEURL + "/test/post/cs1",
data: data,
header: null,
reqFlag: "signwithenSM2-Sm4Encrypt",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqSm2Sm4==", res.data)
} catch (err) {
console.log("-----", err)
return
};
}
const orderId = ref<string>('')
const orderStatus = ref<string>('')
const content = ref<string>('')
const sbase64 = computed(() => {
return stringToBase64(content.value)
})
const base64yostr = computed(() => {
return base64ToString(sbase64.value)
})
onLoad((options) => {
const opts = options as UTSJSONObject
const id = options?.["orderId"] as string | null
const status = options?.["status"] as string | null
if (id != null) {
orderId.value = id
}
if (status != null) {
orderStatus.value = status
}
console.log(`接收订单参数:ID=${orderId.value},状态=${orderStatus.value}`)
})
const goBack = () => {
uni.navigateBack()
}
function init(){
// uni.stopPullDownRefresh()
// console.log('stop onPullDownRefresh=====');
}
// onPullDownRefresh(() => {
// console.log('onPullDownRefresh=====');
// init()
// })
function refresh(){
uni.removeStorageSync("LOGINTABBARITEMS")
// uni.startPullDownRefresh({
// success:()=>{
// console.log("1111")
// }
// });
// init()
}
</script>
<style scoped>
.detail-root {
/* flex: 1;
display: flex;
flex-direction: column; */
background-color: #fff;
}
.status-bar {
height: 44px;
background-color: #ffffff;
}
.detail-header {
height: 44px;
background-color: #fff;
flex-direction: row;
align-items: center;
padding-left: 15px;
padding-right: 15px;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: #eee;
}
.back-icon {
font-size: 18px;
color: #007aff;
margin-right: 10px;
}
.detail-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.detail-content {
flex: 1;
padding: 20px;
}
.detail-item {
flex-direction: row;
margin-bottom: 20px;
align-items: center;
}
.label {
font-size: 15px;
color: #666;
width: 80px;
}
.value {
font-size: 15px;
color: #333;
font-weight: bold;
}
.detail-desc {
margin-top: 30px;
font-size: 14px;
color: #999;
}
</style>
⚠️ 常见问题
Q1: 为什么小程序端需要单独配置?
A: 微信小程序不支持动态组件渲染,必须静态声明所有可能使用的组件。
Q2: 动态 Tab 刷新失败怎么办?
A: 检查以下几点:
- 组件是否在
tabbar-config.uts中正确注册 - 是否正确使用了
GLOBAL_COMPONENT_REGISTRY.get()获取组件 - TabItem 的参数是否完整且类型正确
Q3: 导航栏配置不生效?
A: 确保 setNavbarConfig() 在 setDefaultTabItems() 之前调用,或在应用启动时尽早调用。
Q4: 如何设置数字角标?
A: 使用 uni.$emit('setTabBadge', {"key": "tab_key", "badge": number}) 即可,详见第 4 章。
Q5: 角标设置后不显示怎么办?
A: 检查以下几点:
- badge 值是否大于 0(0 或负数不显示)
- tab key 是否正确(必须与 TabItem 的 key 一致)
- 是否重新赋值了整个数组(直接修改对象属性不会触发响应式)
📝 总结
- 基础配置三要素:注册组件 → 配置导航栏 → 设置默认 Tab
- pages.json:自定义 TabBar 入口必须放在第一位
- 动态刷新:通过
uni.$emit('refreshNewTabbar', [...])实现 - 数字角标:通过
uni.$emit('setTabBadge', {"key": "...", "badge": number})设置 - 小程序兼容:需要在
tab-content-renderer.uvue中静态声明组件 - 主题配置 通过
uni.$emit('setTheme', new CustomTheme(bgType, bg, navBarConfig, tabbarStyle))动态修改
sunrains-tabbar 自定义 TabBar 插件使用指南
点击体验h5端
📋 目录
- [1. 基础配置]
- [2. pages.json 配置]
- [3. 动态配置 TabBar]
- [4. 数字角标功能]
- [5. 小程序端特殊配置]
- [6. 主题配置]
- [7. page页跳转自定义tab页]
1. 基础配置
App.uvue onLaunch 初始化
import {initDefaultTabItems} from '@/uni_modules/sunrains-tabbar/tabbar-config.uts'
onLaunch(() => {
console.log('App Launch')
initDefaultTabItems()
}
1.1 创建 tabbar-config.uts 文件
在项目根目录创建 tabbar-config.uts 文件,用于注册全局组件和配置导航栏。
完整示例:
// 1. 导入所有自定义 tabbar 页面组件并注册
import Recommend from '@/pages/home/recommend.uvue'
import Order from '@/pages/order/order.uvue'
import Mine from '@/pages/mine/mine.uvue'
import {
setNavbarConfig,
NavBarConfig,
registerGlobalComponent,
setDefaultTabItems,
TabItem
} from '@/uni_modules/sunrains-tabbar/types.uts'
// 2. 注册全局组件
registerGlobalComponent('home', Recommend)
registerGlobalComponent('order', Order)
registerGlobalComponent('mine', Mine)
// 3. 配置导航栏(可选,不设置会使用默认配置)
setNavbarConfig(new NavBarConfig(
true, // 是否显示导航栏
17, // 字体大小
'bold', // 字体粗细
'#333333', // 文字颜色
'#ffffff', // 背景颜色
'', // 背景图片(支持网络图片)
56, // 导航栏高度
))
// 4. 设置默认 Tab 列表(至少设置一个固定项)
// 其他 Tab 可通过服务端接口动态配置
setDefaultTabItems([
new TabItem(
'home', // 唯一标识
'首页', // 显示名称
'/static/tab/main1.png', // 未选中图标
'/static/tab/main1_active.png', // 选中图标
'@/pages/home/recommend.uvue', // 页面路径
Recommend // 组件引用
),
new TabItem(
'mine',
'我的',
'/static/tab/main3.png',
'/static/tab/main3_active.png'
)
])
关键步骤说明:
- 导入组件:将所有需要作为 Tab 的页面组件导入
- 注册组件:使用
registerGlobalComponent注册每个组件(第一个参数为唯一标识) - 配置导航栏:可选配置,根据项目需求调整
- 设置默认 Tab:至少配置一个固定 Tab,其余可动态添加
2. pages.json 配置
2.1 必需配置
必须在 pages.json 中配置自定义 TabBar 入口页面(必须放在第一个位置):
{
"path": "uni_modules/sunrains-tabbar/index",
"style": {
"navigationBarTitleText": "自定义tabbar",
"navigationStyle": "custom"
}
}
2.2 完整示例
{
"pages": [
{
"path": "uni_modules/sunrains-tabbar/index",
"style": {
"navigationBarTitleText": "自定义tabbar",
"navigationStyle": "custom"
}
},
{
"path": "pages/home/recommend",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/mine/mine",
"style": {
"navigationBarTitleText": "我的"
}
},
{
"path": "pages/order/order",
"style": {
"navigationBarTitleText": "订单"
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "详情"
}
}
],
"globalStyle": {
"backgroundColor": "#ffffff"
}
}
注意事项:
- 自定义 TabBar 入口页面必须配置在
pages数组的第一位 - 所有 Tab 页面都需要在
pages中声明 navigationStyle设置为"custom"以隐藏原生导航栏
3. 动态配置 TabBar
3.1 使用场景
当需要根据用户权限、服务端配置等动态调整 TabBar 时,可以在任意页面触发刷新。
3.2 实现示例
以下面文档 mine.uvue 页面为例:
关键点:
- 使用 refreshTabbar方法触发 TabBar 刷新
- 确保所有动态 Tab 对应的组件已在
tabbar-config.uts中注册
4. 数字角标功能
4.1 功能说明
支持为任意 Tab 设置数字角标,用于显示未读消息数、待处理事项等。
特性:
- ✅ 动态设置/更新角标
- ✅ 超过 99 自动显示 "99+"
- ✅ 角标为 0 或 null 时不显示
- ✅ 响应式更新,立即生效
4.2 使用方法
方法一:通过事件设置(推荐)
在任意页面通过 调用 setTabBadge 方法:
import { setTabBadge} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
// 设置订单 tab 的角标为 5
setTabBadge("order",5)
// 清除角标(设置为 0)
setTabBadge("order",0)
// 设置超过 99 显示 99+
setTabBadge("order",150)
方法二:初始化时设置
在创建 TabItem 时传入 badge 参数:
import { TabItem } from '@/uni_modules/sunrains-tabbar/utssdk/types.uts'
// 带角标的 tab
new TabItem(
'order',
'订单',
'/static/tab/main2.png',
'/static/tab/main2_active.png'
10 // badge 参数
)
// 不带角标的 tab
new TabItem(
'home',
'首页',
'/static/tab/main1.png',
'/static/tab/main1_active.png'
)
4.3 完整示例
以 文档后面 mine.uvue 页面为例,实现动态设置角标功能:
4.4 实际应用场景
场景 1:订单未读数
// 从服务端获取未读订单数
async function loadUnreadOrderCount() {
const res = await getOrderCount()
setTabBadge("order",res.data.count)
}
// 页面加载时调用
onShow(() => {
loadUnreadOrderCount()
})
场景 2:消息通知
// WebSocket 收到新消息
websocket.onMessage((msg) => {
if (msg.type === 'new_message') {
const currentBadge = getCurrentBadge('message')
setTabBadge("newMessage",currentBadge + 1)
}
})
场景 3:购物车数量
// 添加商品到购物车后更新角标
function addToCart(product: Product) {
cartItems.push(product)
setTabBadge("cart",cartItems.length)
}
5. 小程序端特殊配置
4.1 配置 tab-content-renderer.uvue
注意: 仅在需要支持微信小程序时需要配置,App 端可忽略此步骤。
在 uni_modules/sunrains-tabbar/tab-content-renderer.uvue 中配置:
<template>
<view class="renderer-wrap">
<!-- App 端:使用动态组件 -->
<!-- #ifndef MP-WEIXIN -->
<template v-for="item in items" :key="item.key">
<component
v-if="currentTab === item.key"
:is="item.component"
:key="`${item.key}-${renderKey}`"
/>
</template>
<!-- #endif -->
<!-- 微信小程序端:使用静态声明 -->
<!-- #ifdef MP-WEIXIN -->
<Recommend v-if="currentTab === 'home'" style="width: 100%; height: 100%;" />
<Order v-if="currentTab === 'order'" style="width: 100%; height: 100%;" />
<Mine v-if="currentTab === 'mine'" style="width: 100%; height: 100%;" />
<!-- #endif -->
</view>
</template>
<script setup lang="uts">
import { TabItem } from './utssdk/types.uts'
// #ifdef MP-WEIXIN
// 只有在小程序端才需要这些导入,App 端会自动忽略
import Recommend from '@/pages/home/recommend.uvue'
import Order from '@/pages/order/order.uvue'
import Mine from '@/pages/mine/mine.uvue'
// #endif
interface Props {
currentTab: string
items: TabItem[]
renderKey: number
}
defineProps<Props>()
</script>
<style scoped>
.renderer-wrap {
width: 100%;
height: 100%;
}
</style>
配置说明:
- 条件编译:使用
#ifndef MP-WEIXIN和#ifdef MP-WEIXIN区分平台 - App 端:使用
<component :is="...">动态渲染组件 - 小程序端:必须静态导入并声明所有可能的 Tab 组件
- 导入位置:在
#ifdef MP-WEIXIN块内导入组件
6. 主题配置
6.1 主题功能说明
插件支持自定义 TabBar 的主题样式,支持网络背景图、本地图片背景图、纯颜色背景(支持rgba透明)、导航栏、tabbar区域背景、图标大小、图标(支持网络图标)、文字颜色、文字大小、等属性 满足不同项目的视觉需求。
主题特性:
- ✅ 支持全局主题配置
- ✅ 支持动态切换主题
- ✅ 支持自定义主题参数
6.2 初始化时配置主题
在 tabbar-config.uts 文件中配置主题:
import {
setNavbarConfig,
NavBarConfig,
registerGlobalComponent,
setDefaultTabItems,
TabItem,
setTabBarTheme,
TabBarTheme
} from '@/uni_modules/sunrains-tabbar/types.uts'
import { setTabPageTheme} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
let bgType : BgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/2.png"
//配置导航栏(可选,不设置会使用默认配置)
let navBarConfig : NavBarConfig = new NavBarConfig(
true, // 是否显示导航栏
34, // 字体大小
'bold', // 字体粗细
'#FFA500', // 文字颜色
'',// 背景颜色
"", //背景图片(支持网络图片)
90) // 导航栏高度
let tabbarIcon = new TabbarIcon(
"mine",//自定义组件唯一标识
//非选中图标
"https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4",
//选中图标
"/static/tab/main3_active.png",
//文字颜色
12,
//非选中文字大小
"green",
//选择文字颜色
"white",
//选择文字粗细
"bold"
)
let tabbarIcons : Map<string,TabbarIcon> = new Map();
tabbarIcons.set("mine",tabbarIcon)
let tabbarStyle : TabbarStyle = new TabbarStyle(
//tabbar区域高度
120,
//tabbar区域背景
"rgba(21, 20, 31, 0.8)",
//tabbar图标文字样式
tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
6.3 动态修改主题
在任意页面通过调用 setTabPageTheme 方法动态修改主题:
import { setTabPageTheme} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
// 动态修改背景色和选中颜色
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
6.4 在页面中动态切换主题
以 mine.uvue 页面为例,实现主题切换功能:
<template>
<scroll-view direction="vertical" class="mine-page"
show-scrollbar = "false"
enable-back-to-top="true"
refresher-enabled="true"
refresher-default-style="none"
refresher-background="rgba(232, 201, 183, 0.5)"
:refresher-triggered="data.refreshing2"
refresher-max-drag-distance="200px"
@refresherpulling="onRefresherpulling2"
@refresherrefresh="onRefresherrefresh2"
@refresherrestore="onRefresherrestore2"
>
<uni-refresh-box
slot="refresher"
:pulling-distance="data.pullingDistance2"
:refreshing="data.refreshing2"
loading-class="loading-dark"
text-class="uni-text-class-buildin"
style="flex-direction: column;height: 46px;padding-top: 6px;"
pulling-text="继续下拉可刷新"
loosing-text="释放后会刷新"
loading-text="奋力加载中..."
/>
<view class="page-title">
<text style="color:#ff4d40">动态设置中心</text>
</view>
<view class="tabbar">
<text class="title-text">tabbar设置</text>
<button class="tabbar-btn" @click="refresh1">动态tabbar(我的)</button>
<button class="tabbar-btn" @click="refresh2">动态tabbar(首页、我的)</button>
<button class="tabbar-btn" @click="refresh3">动态tabbar(订单、我的)</button>
<button class="tabbar-btn" @click="refresh4">动态tabbar(我的、订单、首页)</button>
<text class="title-text">设置角标</text>
<input class="input-item" v-model="tabbar" placeholder="请输入tabbar" />
<input class="input-item" v-model="badge" placeholder="请输入角标" />
<button class="tabbar-btn" @click="setBadge">确定</button>
<button class="tabbar-btn" @click="theme">主题1</button>
<button class="tabbar-btn" @click="theme2">主题2</button>
<button class="tabbar-btn" @click="theme3">主题3</button>
<button class="tabbar-btn" @click="clear">clear</button>
<button class="tabbar-btn" type="default" @click="refresh">刷新</button>
</view>
</scroll-view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { TabItem, GLOBAL_COMPONENT_REGISTRY, CustomTheme, TabbarIcon, TabbarStyle, NavBarConfig } from '@/uni_modules/sunrains-tabbar/utssdk/types.uts'
import { setTabBadge,setTabbarColorHeight,refreshTabbar ,setTabPageTheme} from '@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts'
// 用于自动化测试的数据暴露 - 使用 reactive 将所有 ref 状态集中暴露
const data = reactive({
refreshing2: false,
pullingDistance2: 0
})
function onRefresherpulling2(e: RefresherEvent) {
data.pullingDistance2 = e.detail.dy
}
const refreshing = ref(false)
function onRefresherrefresh2() {
data.refreshing2 = true
console.log('触发刷新')
setTimeout(() => {
data.refreshing2 = false
console.log('刷新完成')
}, 1500)
}
function onRefresherrestore2() {
data.pullingDistance2 = 0
}
function onRefresherabort2() {
data.pullingDistance2 = 0
}
// 上拉触底加载
const loadMore = () => {
console.log("滚动到底部", "info")
}
function clear() {
uni.removeStorageSync("CUSTOMTHEME")
}
const bgColor = ref<string>('pink')
const tabHeight = ref<string>('120')
const badge = ref<string>('0')
const ss = ref<any>("");
// tab 选项配置
const tabbar = ref<string | null>(null)
const selectedTab = ref<string>('mine')
const selectedTabIndex = ref<number>(0)
function setBadge() {
//动态设置指定tabbar 角标
const badgeNum = parseInt(badge.value)
if (isNaN(badgeNum)) {
uni.showToast({
title: '请输入有效数字',
icon: 'none'
})
return
}
setTabBadge(tabbar.value,badgeNum)
uni.showToast({
title: `已设置 ${tabbar.value} 角标为 ${badgeNum}`,
icon: 'success'
})
}
function setStyle() {
setTabbarColorHeight(bgColor.value,parseInt(tabHeight.value) )
}
function refresh1() {
console.log("动态tabbar(我的)")
refreshTabbar([
new TabItem('mine', 12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png')
]);
}
function refresh2() {
console.log("动态tabbar(首页、我的)")
refreshTabbar([
new TabItem('home', 12, "red", "yellow", "bold", '首页', '/static/tab/main1.png', '/static/tab/main1_active.png'),
new TabItem('mine', 12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png')
])
}
function refresh3() {
console.log("动态tabbar(订单、我的)")
refreshTabbar( [
new TabItem('order', 12, "red", "yellow", "bold", '订单', '/static/tab/main2.png', '/static/tab/main2_active.png'),
new TabItem('mine', 12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png')
])
}
function refresh4() {
console.log("动态tabbar(我的、订单、首页)")
refreshTabbar([
new TabItem('mine', 12, "red", "yellow", "bold", '我的', '/static/tab/main3.png', '/static/tab/main3_active.png'),
new TabItem('order', 12, "red", "yellow", "bold", '订单', '/static/tab/main2.png', '/static/tab/main2_active.png'),
new TabItem('home', 12, "red", "yellow", "bold", '首页', '/static/tab/main1.png',
'https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4')
])
}
function theme() {
//主题背景类型
let bgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/2.png"
// let bg:string = "black"
let navBarConfig : NavBarConfig = new NavBarConfig(true, 34, 'bold', '#FFA500', "rgba(191, 191, 191, 0.5)", "", 90)
let tabbarIcon = new TabbarIcon("mine",
"https://gimg3.baidu.com/search/src=https%3A%2F%2Fpic.rmb.bdstatic.com%2Fbjh%2Fuser%2F779083e57f84a4ce5345b17aaf7f8459.jpeg&refer=http%3A%2F%2Fwww.baidu.com&app=2021&size=r1,1&n=0&g=4&er=404&q=100&maxorilen2heic=2000000?sec=1781283600&t=4feca8b5a1149e3ed0f28ee030f767b4",
"/static/tab/main3_active.png",
12,
"green",
"white",
"bold"
)
let tabbarIcons : Map<string, TabbarIcon> = new Map();
tabbarIcons.set("mine", tabbarIcon)
let tabbarStyle : TabbarStyle = new TabbarStyle(120, "rgba(21, 20, 31, 0.8)", tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
}
function theme2() {
//主题背景类型
let bgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/sm.jpg"
// let bg:string = "black"
let navBarConfig : NavBarConfig = new NavBarConfig(false, 34, 'bold', '#0c0c0c', "", "", 90)
let tabbarIcon = new TabbarIcon("mine",
"/static/aj.png",
"/static/aj1.png",
20,
"green",
"red",
"bold"
)
let tabbarIcon1 = new TabbarIcon("home",
"/static/sy1.png",
"/static/sy.png",
16,
"black",
"yellow",
"bold"
)
let tabbarIcons : Map<string, TabbarIcon> = new Map();
tabbarIcons.set("mine", tabbarIcon)
tabbarIcons.set("home", tabbarIcon1)
let tabbarStyle : TabbarStyle = new TabbarStyle(120, "rgba(21, 20, 31, 0.4)", tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
}
function theme3() {
//主题背景类型
let bgType = "IMG"
//背景 如果是图片传图片路径 颜色则传 具体颜色
let bg : string = "/static/777.png"
// let bg:string = "black"
let navBarConfig : NavBarConfig = new NavBarConfig(true, 34, 'bold', '#0c0c0c', "", "", 90)
let tabbarIcon = new TabbarIcon("mine",
"/static/aj.png",
"/static/aj1.png",
20,
"green",
"red",
"bold"
)
let tabbarIcon1 = new TabbarIcon("home",
"/static/sy1.png",
"/static/sy.png",
16,
"#1afa29",
"yellow",
"bold"
)
let tabbarIcons : Map<string, TabbarIcon> = new Map();
tabbarIcons.set("mine", tabbarIcon)
tabbarIcons.set("home", tabbarIcon1)
let tabbarStyle : TabbarStyle = new TabbarStyle(200, "rgba(10, 13, 31, 0.8)", tabbarIcons)
let theme = new CustomTheme(bgType, bg, navBarConfig, tabbarStyle);
setTabPageTheme(theme)
}
function init() {
uni.stopPullDownRefresh()
console.log('stop onPullDownRefresh=====');
}
onPullDownRefresh(() => {
console.log('onPullDownRefresh=====');
init()
})
function refresh() {
uni.startPullDownRefresh({
success: () => {
console.log("1111")
}
});
init()
}
</script>
<style scoped lang="scss">
.mine-page {
height: 100%;
// border: 12px solid red;
margin: 5px;
}
.page-title {
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.title-text {
font-size: 18px;
font-weight: bold;
color: #333;
}
.tabbar {
// border: 2px solid cyan;
padding: 15px;
display: flex;
flex-direction: column;
}
.input-item {
height: 45px;
border: 1px solid #ccc;
border-radius: 6px;
padding-left: 10px;
background: #fff;
margin: 10px;
padding-left: 20px;
}
.picker-item {
height: 45px;
border: 1px solid #ccc;
border-radius: 6px;
background: #fff;
margin: 10px;
display: flex;
align-items: center;
}
.picker-display {
padding-left: 20px;
font-size: 16px;
color: #333;
}
.tabbar-btn {
margin-bottom: 10px;
border-radius: 8px;
background: #f5f5f5;
border: 1px solid #e5e5e5;
padding: 16px 0;
font-size: 16px;
}
.tabbar-btn:last-child {
margin-bottom: 0;
}
</style>
6.5 主题配置注意事项
- 颜色格式:颜色值必须为有效的 CSS 颜色格式(如
#ffffff、rgb(255,255,255)等) - tabbar区域高度单位:高度值为数字类型,单位为 rpx
- 动态修改:通过
uni.$emit('setTheme', new CustomTheme(bgType, bg, navBarConfig, tabbarStyle))动态修改的主题会立即生效
7 page页跳转自定义tab页
使用 pageToTab 方法 跳转到tab页
以 detail.uvue 页面为例,实现主题切换功能:
<template>
<view class="detail-root">
<view class="detail-content">
<view class="detail-item">
<text class="label">订单编号:</text>
<text class="value">{{ orderId }}</text>
</view>
<view class="detail-item">
<text class="label">订单状态:</text>
<text class="value">{{ orderStatus }}</text>
</view>
<view class="detail-desc">
<text>这是订单{{ orderId }}的详细信息页面</text>
</view>
<view>
<input v-model="content" placeholder="请输入。。" />
<text>{{sbase64}}</text>
<text>{{base64yostr}}</text>
</view>
<view>
<button type="primary" @click="req">访问testvoid</button>
<button type="primary" @click="reqSm2">访问testSm2</button>
<button type="primary" @click="reqSm4">访问testSm4</button>
<button type="primary" @click="reqSm2Sm4">访问testreqSm2Sm4</button>
<input style="height: 50px; border:1px solid grey;margin: 5px 0;" v-model="addr" placeholder="输入跳转tabbar key"/>
<button type="default" @click="tz">跳转指定tab页</button>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, computed } from 'vue'
import { stringToBase64, base64ToString, ReqApi } from "@/uni_modules/sunrains-request"
import { Response, FrontRequestVo } from "@/uni_modules/sunrains-common-type"
import { CustFailError, getComErrorInfo } from "@/uni_modules/sunrains-common-err"
import { pageToTab} from "@/uni_modules/sunrains-tabbar/utssdk/noticetab.uts"
const addr = ref("")
function tz(){
if(addr.value.length<1){
uni.showToast({
title: '请输入跳转的tabbar key'
});
}else{
pageToTab(addr.value)
}
}
let priKey = "4a4a1d48fc284fe2fc43970641880554aed2f69fdfbea63257ea417af4e65802"
let pubKey = "0460e86263ec52b38a3da24c7a605055d14f8aa5c7855e4f4e19819f4f8a5b72181afd7aa4ab19255eb871eaedaba30fa18cd5c71693d3d76b030a04969bd1edfe"
const BASEURL = "http://192.168.1.9:8081"
type IRootType = {
name : string;
age : number;
}
type Builds = {
buildingNo : string,
sortNum : number,
buildAreaId : string,
id : string
}
async function req() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'GET',
url: BASEURL + "/test/get/cs1",
data: data,
header: null,
reqFlag: "void",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqvoid==", res.data)
// console.log("####==",res)
} catch (err) {
uni.showToast({
title: err.message ?? "请求异常",
icon: "error"
})
console.log("-----", err)
return
};
}
//"sign"|"sm4Encrypt"|"signwithenSM2-Sm4Encrypt"|"void";
async function reqSm2() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'GET',
url: BASEURL + "/test/get/cs1",
data: data,
header: null,
reqFlag: "sign",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqSm2==", res.data)
} catch (err) {
uni.showToast({
title: err.message ?? "请求异常",
icon: "error"
})
//#ifndef APP-ANDROID
const errMsg = (err as any).errMsg ?? "未知错误";
const errCode = (err as any).errCode ?? -1;
console.log('标准化错误码:', errMsg);
console.log('标准化错误信息:', errCode);
//#else
if (err instanceof UniError) {
let sa = err as UniError
console.log('sa==:', sa);
}
//#endif
console.log("-----", err)
return
};
}
async function reqSm4() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'POST',
url: BASEURL + "/test/post/cs1",
data: data,
header: null,
reqFlag: "sm4Encrypt",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqSm4==", res.data)
} catch (err) {
console.log("####reqSm4 err==", err)
uni.showToast({
title: err.message??"",
icon: "error"
})
return
};
}
async function reqSm2Sm4() {
let data : UTSJSONObject = {
name: "张三",
age: 18
}
try {
let vo : FrontRequestVo = {
method: 'POST',
url: BASEURL + "/test/post/cs1",
data: data,
header: null,
reqFlag: "signwithenSM2-Sm4Encrypt",
priKey: priKey,
pubKey: pubKey,
//method 为POST时 不能为null
contentType: "urlencoded",
timeout: null,
//当为上传文件时 文件路径参数不能为空
filePathParamName: null
}
const res = await ReqApi(vo)
console.log("####reqSm2Sm4==", res.data)
} catch (err) {
console.log("-----", err)
return
};
}
const orderId = ref<string>('')
const orderStatus = ref<string>('')
const content = ref<string>('')
const sbase64 = computed(() => {
return stringToBase64(content.value)
})
const base64yostr = computed(() => {
return base64ToString(sbase64.value)
})
onLoad((options) => {
const opts = options as UTSJSONObject
const id = options?.["orderId"] as string | null
const status = options?.["status"] as string | null
if (id != null) {
orderId.value = id
}
if (status != null) {
orderStatus.value = status
}
console.log(`接收订单参数:ID=${orderId.value},状态=${orderStatus.value}`)
})
const goBack = () => {
uni.navigateBack()
}
function init(){
// uni.stopPullDownRefresh()
// console.log('stop onPullDownRefresh=====');
}
// onPullDownRefresh(() => {
// console.log('onPullDownRefresh=====');
// init()
// })
function refresh(){
uni.removeStorageSync("LOGINTABBARITEMS")
// uni.startPullDownRefresh({
// success:()=>{
// console.log("1111")
// }
// });
// init()
}
</script>
<style scoped>
.detail-root {
/* flex: 1;
display: flex;
flex-direction: column; */
background-color: #fff;
}
.status-bar {
height: 44px;
background-color: #ffffff;
}
.detail-header {
height: 44px;
background-color: #fff;
flex-direction: row;
align-items: center;
padding-left: 15px;
padding-right: 15px;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: #eee;
}
.back-icon {
font-size: 18px;
color: #007aff;
margin-right: 10px;
}
.detail-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.detail-content {
flex: 1;
padding: 20px;
}
.detail-item {
flex-direction: row;
margin-bottom: 20px;
align-items: center;
}
.label {
font-size: 15px;
color: #666;
width: 80px;
}
.value {
font-size: 15px;
color: #333;
font-weight: bold;
}
.detail-desc {
margin-top: 30px;
font-size: 14px;
color: #999;
}
</style>
⚠️ 常见问题
Q1: 为什么小程序端需要单独配置?
A: 微信小程序不支持动态组件渲染,必须静态声明所有可能使用的组件。
Q2: 动态 Tab 刷新失败怎么办?
A: 检查以下几点:
- 组件是否在
tabbar-config.uts中正确注册 - 是否正确使用了
GLOBAL_COMPONENT_REGISTRY.get()获取组件 - TabItem 的参数是否完整且类型正确
Q3: 导航栏配置不生效?
A: 确保 setNavbarConfig() 在 setDefaultTabItems() 之前调用,或在应用启动时尽早调用。
Q4: 如何设置数字角标?
A: 使用 uni.$emit('setTabBadge', {"key": "tab_key", "badge": number}) 即可,详见第 4 章。
Q5: 角标设置后不显示怎么办?
A: 检查以下几点:
- badge 值是否大于 0(0 或负数不显示)
- tab key 是否正确(必须与 TabItem 的 key 一致)
- 是否重新赋值了整个数组(直接修改对象属性不会触发响应式)
📝 总结
- 基础配置三要素:注册组件 → 配置导航栏 → 设置默认 Tab
- pages.json:自定义 TabBar 入口必须放在第一位
- 动态刷新:通过
uni.$emit('refreshNewTabbar', [...])实现 - 数字角标:通过
uni.$emit('setTabBadge', {"key": "...", "badge": number})设置 - 小程序兼容:需要在
tab-content-renderer.uvue中静态声明组件 - 主题配置 通过
uni.$emit('setTheme', new CustomTheme(bgType, bg, navBarConfig, tabbarStyle))动态修改




