HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

跳转其他页面返回时,如何设置Tabs的默认显示页面

问题现象
使用Tabs组件,如何实现当前显示页签为1的页面内容,点击某个页签使用router跳转到其他页面再返回时,显示的依然是页签为1的页面内容?

效果预览
点击放大

背景知识
onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景,仅@Entry装饰的自定义组件作为页面时生效。
Tabs:通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。
getRouter().pushUrl():跳转到应用内的指定页面,通过Promise获取跳转异常的返回结果。
解决方案
@Entry修饰的页面在显示时会触发onPageShow生命周期回调,可以在此回调中设置页面显示的初始状态,Tabs组件显示具体哪个页面由Tabs内的currentIndex参数决定的,当页面返回时在onPageShow()方法内重设currentIndex的值使其显示对应页面。运行下述示例需要自行创建一个简单的PageA页面。

class TabBar {
title: string;
index: number;

constructor(title: string, index: number) {
this.title = title;
this.index = index;
}
}

@Entry
@Component
struct TabsTestPage {
uiContext = this.getUIContext();
// 当前选中Tabs的索引
@State currentIndex: number = 1;
// 判断Tabs是否选中(用于自定义Tabs列表的选中状态)

@State selectedIndex: number = 0;
private tabsController: TabsController = new TabsController();
private tabBars: TabBar[] = [
new TabBar('翻译机', 0),
new TabBar('首页', 1),
new TabBar('推荐', 2),
];

// 页面显示时初始化状态
onPageShow(): void {
this.currentIndex = 1;
}

// 自定义Tabs组件构建函数
@Builder
TabBuilder() {
List() {
ForEach(this.tabBars, (item: TabBar, index: number) => {
ListItem() {
Column() {
Text(item.title) // 根据选中状态改变文字颜色
.fontColor(this.currentIndex === item.index ? '#0A59F7' : Color.Black)
.fontSize(20)
.align(Alignment.Center);
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Start)
.margin({ left: 10 })
.onClick(() => {
// 更新Tabs组件的选中状态
this.currentIndex = index;
});
}.height(100);
});
}.height(100)
.listDirection(Axis.Horizontal)
.scrollBar(BarState.Off);
}

build() {
Column() {
Flex({ alignItems: ItemAlign.Center }) {
this.TabBuilder();
}.width('100%').height(100);

  Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.tabsController }) {  
    TabContent() {  
      Text('翻译机的内容')  
        .fontSize(30)  
        .onClick(() => {  
          let promptShow = this.uiContext.getPromptAction();  
          promptShow.showToast({  
            message: '翻译机跳转'  
          });  
          // 需要自行创建一个PageA的@Entry页面  
          this.uiContext.getRouter().pushUrl({ url: 'pages/PageA' });  
        });  
    };  

    TabContent() {  
      Text('首页的内容')  
        .fontSize(30);  
    };  

    TabContent() {  
      Text('推荐的内容')  
        .fontSize(30);  
    };  
  }.barHeight(0)  
  .onAnimationStart((targetIndex: number) => {  
    this.currentIndex = targetIndex;  
  })  
  .onChange((index: number) => {  
    // currentIndex控制TabContent显示页签  
    this.currentIndex = index;  
    this.selectedIndex = index;  
  });  

}.height('100%').width('100%');  

}
}
总结
单例模式跳转时,由于也是复用路由栈内已有的页面实例,也可在本方案所述的页面生命周期内实现。
https://pastebin.com/dty8sxD5
https://pastebin.com/dHEXCxFt
https://pastebin.com/pE6ePUAY
https://pastebin.com/2U1LgwWz
https://pastebin.com/WQpk7jSa
https://pastebin.com/G8LPQVtP
https://pastebin.com/VRpjsRTK
https://pastebin.com/aCy0qhBi
https://pastebin.com/NFQMuiXH
https://pastebin.com/S064raxa
https://pastebin.com/4s7w0BBW
https://pastebin.com/m7UDFUJ6
https://pastebin.com/UBeiNw6e
https://pastebin.com/kms3qfYM
https://pastebin.com/dYjjH5yP
https://pastebin.com/6cvgDvgL
https://pastebin.com/KPmn6RCf
https://pastebin.com/DiPdkuXM
https://pastebin.com/53nPeHWZ
https://pastebin.com/WNdjQBtb
https://pastebin.com/gD3BbuEt
https://pastebin.com/K1djmuru
https://pastebin.com/uqMvn9Uq
https://pastebin.com/paibxsw8
https://pastebin.com/Una68Ba5
https://pastebin.com/P7CrcBfR
https://pastebin.com/UiQ5wXCw
https://pastebin.com/LYHMErrw
https://pastebin.com/T3Fjh1Er
https://pastebin.com/m7Zx61yT
https://pastebin.com/dRmeRwsB
https://pastebin.com/vN5zyV7U
https://pastebin.com/hqms8PnB
https://pastebin.com/VJJ9tacg
https://pastebin.com/gnHE24Vx
https://pastebin.com/2DN8jUYE
https://pastebin.com/NYYLtSrA
https://pastebin.com/FJcvCjYv
https://pastebin.com/C50vS8E5
https://pastebin.com/x8G6uZzB
https://pastebin.com/xwqDW20Q
https://pastebin.com/bbrPiHh4
https://pastebin.com/KwcxyrV3
https://pastebin.com/8Yq1Mwpv
https://pastebin.com/rdXkVZnR
https://pastebin.com/X1QpPxj6
https://pastebin.com/rk4jhKwM
https://pastebin.com/AYH757Hd
https://pastebin.com/arrJtdPC
https://pastebin.com/x1a0QkrE
https://pastebin.com/ZPzMV6KV
https://pastebin.com/qZPAArab
https://pastebin.com/dDiiWq7r
https://pastebin.com/wH1CZA3w
https://pastebin.com/9CHyNi7a
https://pastebin.com/Wkzwp2vS
https://pastebin.com/MVpzkpRy
https://pastebin.com/4AuW9a4S
https://pastebin.com/42tkYqcy
https://pastebin.com/XeksS1ku
https://pastebin.com/L110ndKg
https://pastebin.com/4Axgsuj0
https://pastebin.com/eAA2mxp4
https://pastebin.com/aU4WMaqg
https://pastebin.com/f1hun2kM
https://pastebin.com/wiqMJgk3
https://pastebin.com/uEN8vUKX
https://pastebin.com/D5qiKNFB
https://pastebin.com/F65rvR1J
https://pastebin.com/rasLrbYn
https://pastebin.com/21n3MQq5
https://pastebin.com/bXDj2phg
https://pastebin.com/0PVahXVh
https://pastebin.com/7JtdfNGH
https://pastebin.com/0986iXre
https://pastebin.com/NUb3eRVe
https://pastebin.com/gvz7gKTZ
https://pastebin.com/UusP3Pyp
https://pastebin.com/QSMmWQi9
https://pastebin.com/jLtF7m6h
https://pastebin.com/csKNzJ6w
https://pastebin.com/wBeZafkw
https://pastebin.com/KESDZmpi
https://pastebin.com/mppjXxUF
https://pastebin.com/MepfrkCE
https://pastebin.com/ibwAybQc
https://pastebin.com/WuELbpHn
https://pastebin.com/hTM9NQ5N
https://pastebin.com/jj0Cp5Ux
https://pastebin.com/LzZFQwaZ
https://pastebin.com/tY8wgRRh
https://pastebin.com/XhgJKNVx
https://pastebin.com/zmytyHRD
https://pastebin.com/ps3FBe7w
https://pastebin.com/hKEiN00Z
https://pastebin.com/0krAGqa8
https://pastebin.com/bmN1vmY5
https://pastebin.com/tXEzETbZ
https://pastebin.com/sxvMRT7d
https://pastebin.com/PBYx1zjX
https://pastebin.com/YL3YtvW4
https://pastebin.com/wYkL6tyV
https://pastebin.com/G9yg2jbz
https://pastebin.com/YS9qiTPE
https://pastebin.com/zRzdakmk
https://pastebin.com/cPbmqAwR
https://pastebin.com/k6VnHXDj
https://pastebin.com/xaU4Lde8
https://pastebin.com/T3qhtZbi
https://pastebin.com/ti5Gv5Uy
https://pastebin.com/p9B6cLzp
https://pastebin.com/0McP0kzG
https://pastebin.com/QRsCLz7H
https://pastebin.com/rTnmVGim
https://pastebin.com/dirbSPyj
https://pastebin.com/QJ1gdh7k
https://pastebin.com/qgCPAp80
https://pastebin.com/H6YUkDVV
https://pastebin.com/YzEYse2d
https://pastebin.com/kUbmfLr0
https://pastebin.com/DmfhXUWK
https://pastebin.com/rxfuCib1
https://pastebin.com/wCR9BHRT
https://pastebin.com/rfJqDQph
https://pastebin.com/UVXE7g9P
https://pastebin.com/vB8FW842
https://pastebin.com/93JiaU8B
https://pastebin.com/40fKbQF1
https://pastebin.com/UpbtX9Vs
https://pastebin.com/GwhrB2rt
https://pastebin.com/CMt33cpf
https://pastebin.com/qPsRkEzz
https://pastebin.com/RR7a27Wk
https://pastebin.com/zU5CC8qs
https://pastebin.com/D3aJGYQk
https://pastebin.com/hx9ya1cr
https://pastebin.com/2GgcbCuP
https://pastebin.com/gYczTsfK
https://pastebin.com/VXsw9xTt
https://pastebin.com/R7DhK5Tq
https://pastebin.com/hHKGpdvA
https://pastebin.com/xREcgGy2

继续阅读 »

问题现象
使用Tabs组件,如何实现当前显示页签为1的页面内容,点击某个页签使用router跳转到其他页面再返回时,显示的依然是页签为1的页面内容?

效果预览
点击放大

背景知识
onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景,仅@Entry装饰的自定义组件作为页面时生效。
Tabs:通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。
getRouter().pushUrl():跳转到应用内的指定页面,通过Promise获取跳转异常的返回结果。
解决方案
@Entry修饰的页面在显示时会触发onPageShow生命周期回调,可以在此回调中设置页面显示的初始状态,Tabs组件显示具体哪个页面由Tabs内的currentIndex参数决定的,当页面返回时在onPageShow()方法内重设currentIndex的值使其显示对应页面。运行下述示例需要自行创建一个简单的PageA页面。

class TabBar {
title: string;
index: number;

constructor(title: string, index: number) {
this.title = title;
this.index = index;
}
}

@Entry
@Component
struct TabsTestPage {
uiContext = this.getUIContext();
// 当前选中Tabs的索引
@State currentIndex: number = 1;
// 判断Tabs是否选中(用于自定义Tabs列表的选中状态)

@State selectedIndex: number = 0;
private tabsController: TabsController = new TabsController();
private tabBars: TabBar[] = [
new TabBar('翻译机', 0),
new TabBar('首页', 1),
new TabBar('推荐', 2),
];

// 页面显示时初始化状态
onPageShow(): void {
this.currentIndex = 1;
}

// 自定义Tabs组件构建函数
@Builder
TabBuilder() {
List() {
ForEach(this.tabBars, (item: TabBar, index: number) => {
ListItem() {
Column() {
Text(item.title) // 根据选中状态改变文字颜色
.fontColor(this.currentIndex === item.index ? '#0A59F7' : Color.Black)
.fontSize(20)
.align(Alignment.Center);
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Start)
.margin({ left: 10 })
.onClick(() => {
// 更新Tabs组件的选中状态
this.currentIndex = index;
});
}.height(100);
});
}.height(100)
.listDirection(Axis.Horizontal)
.scrollBar(BarState.Off);
}

build() {
Column() {
Flex({ alignItems: ItemAlign.Center }) {
this.TabBuilder();
}.width('100%').height(100);

  Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.tabsController }) {  
    TabContent() {  
      Text('翻译机的内容')  
        .fontSize(30)  
        .onClick(() => {  
          let promptShow = this.uiContext.getPromptAction();  
          promptShow.showToast({  
            message: '翻译机跳转'  
          });  
          // 需要自行创建一个PageA的@Entry页面  
          this.uiContext.getRouter().pushUrl({ url: 'pages/PageA' });  
        });  
    };  

    TabContent() {  
      Text('首页的内容')  
        .fontSize(30);  
    };  

    TabContent() {  
      Text('推荐的内容')  
        .fontSize(30);  
    };  
  }.barHeight(0)  
  .onAnimationStart((targetIndex: number) => {  
    this.currentIndex = targetIndex;  
  })  
  .onChange((index: number) => {  
    // currentIndex控制TabContent显示页签  
    this.currentIndex = index;  
    this.selectedIndex = index;  
  });  

}.height('100%').width('100%');  

}
}
总结
单例模式跳转时,由于也是复用路由栈内已有的页面实例,也可在本方案所述的页面生命周期内实现。
https://pastebin.com/dty8sxD5
https://pastebin.com/dHEXCxFt
https://pastebin.com/pE6ePUAY
https://pastebin.com/2U1LgwWz
https://pastebin.com/WQpk7jSa
https://pastebin.com/G8LPQVtP
https://pastebin.com/VRpjsRTK
https://pastebin.com/aCy0qhBi
https://pastebin.com/NFQMuiXH
https://pastebin.com/S064raxa
https://pastebin.com/4s7w0BBW
https://pastebin.com/m7UDFUJ6
https://pastebin.com/UBeiNw6e
https://pastebin.com/kms3qfYM
https://pastebin.com/dYjjH5yP
https://pastebin.com/6cvgDvgL
https://pastebin.com/KPmn6RCf
https://pastebin.com/DiPdkuXM
https://pastebin.com/53nPeHWZ
https://pastebin.com/WNdjQBtb
https://pastebin.com/gD3BbuEt
https://pastebin.com/K1djmuru
https://pastebin.com/uqMvn9Uq
https://pastebin.com/paibxsw8
https://pastebin.com/Una68Ba5
https://pastebin.com/P7CrcBfR
https://pastebin.com/UiQ5wXCw
https://pastebin.com/LYHMErrw
https://pastebin.com/T3Fjh1Er
https://pastebin.com/m7Zx61yT
https://pastebin.com/dRmeRwsB
https://pastebin.com/vN5zyV7U
https://pastebin.com/hqms8PnB
https://pastebin.com/VJJ9tacg
https://pastebin.com/gnHE24Vx
https://pastebin.com/2DN8jUYE
https://pastebin.com/NYYLtSrA
https://pastebin.com/FJcvCjYv
https://pastebin.com/C50vS8E5
https://pastebin.com/x8G6uZzB
https://pastebin.com/xwqDW20Q
https://pastebin.com/bbrPiHh4
https://pastebin.com/KwcxyrV3
https://pastebin.com/8Yq1Mwpv
https://pastebin.com/rdXkVZnR
https://pastebin.com/X1QpPxj6
https://pastebin.com/rk4jhKwM
https://pastebin.com/AYH757Hd
https://pastebin.com/arrJtdPC
https://pastebin.com/x1a0QkrE
https://pastebin.com/ZPzMV6KV
https://pastebin.com/qZPAArab
https://pastebin.com/dDiiWq7r
https://pastebin.com/wH1CZA3w
https://pastebin.com/9CHyNi7a
https://pastebin.com/Wkzwp2vS
https://pastebin.com/MVpzkpRy
https://pastebin.com/4AuW9a4S
https://pastebin.com/42tkYqcy
https://pastebin.com/XeksS1ku
https://pastebin.com/L110ndKg
https://pastebin.com/4Axgsuj0
https://pastebin.com/eAA2mxp4
https://pastebin.com/aU4WMaqg
https://pastebin.com/f1hun2kM
https://pastebin.com/wiqMJgk3
https://pastebin.com/uEN8vUKX
https://pastebin.com/D5qiKNFB
https://pastebin.com/F65rvR1J
https://pastebin.com/rasLrbYn
https://pastebin.com/21n3MQq5
https://pastebin.com/bXDj2phg
https://pastebin.com/0PVahXVh
https://pastebin.com/7JtdfNGH
https://pastebin.com/0986iXre
https://pastebin.com/NUb3eRVe
https://pastebin.com/gvz7gKTZ
https://pastebin.com/UusP3Pyp
https://pastebin.com/QSMmWQi9
https://pastebin.com/jLtF7m6h
https://pastebin.com/csKNzJ6w
https://pastebin.com/wBeZafkw
https://pastebin.com/KESDZmpi
https://pastebin.com/mppjXxUF
https://pastebin.com/MepfrkCE
https://pastebin.com/ibwAybQc
https://pastebin.com/WuELbpHn
https://pastebin.com/hTM9NQ5N
https://pastebin.com/jj0Cp5Ux
https://pastebin.com/LzZFQwaZ
https://pastebin.com/tY8wgRRh
https://pastebin.com/XhgJKNVx
https://pastebin.com/zmytyHRD
https://pastebin.com/ps3FBe7w
https://pastebin.com/hKEiN00Z
https://pastebin.com/0krAGqa8
https://pastebin.com/bmN1vmY5
https://pastebin.com/tXEzETbZ
https://pastebin.com/sxvMRT7d
https://pastebin.com/PBYx1zjX
https://pastebin.com/YL3YtvW4
https://pastebin.com/wYkL6tyV
https://pastebin.com/G9yg2jbz
https://pastebin.com/YS9qiTPE
https://pastebin.com/zRzdakmk
https://pastebin.com/cPbmqAwR
https://pastebin.com/k6VnHXDj
https://pastebin.com/xaU4Lde8
https://pastebin.com/T3qhtZbi
https://pastebin.com/ti5Gv5Uy
https://pastebin.com/p9B6cLzp
https://pastebin.com/0McP0kzG
https://pastebin.com/QRsCLz7H
https://pastebin.com/rTnmVGim
https://pastebin.com/dirbSPyj
https://pastebin.com/QJ1gdh7k
https://pastebin.com/qgCPAp80
https://pastebin.com/H6YUkDVV
https://pastebin.com/YzEYse2d
https://pastebin.com/kUbmfLr0
https://pastebin.com/DmfhXUWK
https://pastebin.com/rxfuCib1
https://pastebin.com/wCR9BHRT
https://pastebin.com/rfJqDQph
https://pastebin.com/UVXE7g9P
https://pastebin.com/vB8FW842
https://pastebin.com/93JiaU8B
https://pastebin.com/40fKbQF1
https://pastebin.com/UpbtX9Vs
https://pastebin.com/GwhrB2rt
https://pastebin.com/CMt33cpf
https://pastebin.com/qPsRkEzz
https://pastebin.com/RR7a27Wk
https://pastebin.com/zU5CC8qs
https://pastebin.com/D3aJGYQk
https://pastebin.com/hx9ya1cr
https://pastebin.com/2GgcbCuP
https://pastebin.com/gYczTsfK
https://pastebin.com/VXsw9xTt
https://pastebin.com/R7DhK5Tq
https://pastebin.com/hHKGpdvA
https://pastebin.com/xREcgGy2

收起阅读 »

uni-app路由管理神器:@meng-xi/uni-router

路由拦截 路由守卫 路由

为 uni-app (Vue 3) 提供类似 vue-router 风格的路由管理系统(uni_modules 版本)。

仅支持 Vue 3 — 基于 Vue 3 Composition API,不支持 Vue 2 项目。

特性

  • vue-router 风格 API - push / replace / relaunch / back
  • 路由守卫 - beforeEach / beforeResolve / afterEach / beforeEnter,支持 guardRoute 冷启动补执行
  • 页面间通信 - useUniEventChannel 内置通信管理器,粘性缓存确保时序安全
  • 声明式组件 - RouterLink / TabBar / TabBarItem,easycom 自动注册
  • 页面参数传递 - params 传递复杂数据,back() 后自动保留
  • 查询参数增强 - queryInt() / queryNumber() / queryBool()
  • 错误处理 - RouterError / NavigationFailure / UniApiErrorinstanceof 精准判断

安装

mxuni-router 目录复制到项目的 uni_modules 目录下即可,无需 npm 安装。

快速开始

// main.ts  
import { createSSRApp } from 'vue'  
import { createRouter } from './uni_modules/mxuni-router/js_sdk/index.js'  
import App from './App.vue'  

const router = createRouter({  
    routes: [  
        { path: 'pages/index/index', name: 'home', meta: { title: '首页' } },  
        { path: 'pages/about/about', name: 'about', meta: { requireAuth: true } }  
    ],  
    interceptUniApi: true  
})  

export function createApp() {  
    const app = createSSRApp(App)  
    app.use(router)  
    return { app }  
}

组件在 uni_modules 中自动注册,直接使用即可:

<RouterLink to="/pages/about/about">关于</RouterLink>  

<TabBar selected-color="#007aff">  
    <TabBarItem to="/pages/index/index" text="首页" />  
    <TabBarItem to="/pages/about/about" text="关于" :badge="5" />  
</TabBar>

文档

📖 https://mengxi-studio.github.io/uni-router/v1/

License

MIT

继续阅读 »

为 uni-app (Vue 3) 提供类似 vue-router 风格的路由管理系统(uni_modules 版本)。

仅支持 Vue 3 — 基于 Vue 3 Composition API,不支持 Vue 2 项目。

特性

  • vue-router 风格 API - push / replace / relaunch / back
  • 路由守卫 - beforeEach / beforeResolve / afterEach / beforeEnter,支持 guardRoute 冷启动补执行
  • 页面间通信 - useUniEventChannel 内置通信管理器,粘性缓存确保时序安全
  • 声明式组件 - RouterLink / TabBar / TabBarItem,easycom 自动注册
  • 页面参数传递 - params 传递复杂数据,back() 后自动保留
  • 查询参数增强 - queryInt() / queryNumber() / queryBool()
  • 错误处理 - RouterError / NavigationFailure / UniApiErrorinstanceof 精准判断

安装

mxuni-router 目录复制到项目的 uni_modules 目录下即可,无需 npm 安装。

快速开始

// main.ts  
import { createSSRApp } from 'vue'  
import { createRouter } from './uni_modules/mxuni-router/js_sdk/index.js'  
import App from './App.vue'  

const router = createRouter({  
    routes: [  
        { path: 'pages/index/index', name: 'home', meta: { title: '首页' } },  
        { path: 'pages/about/about', name: 'about', meta: { requireAuth: true } }  
    ],  
    interceptUniApi: true  
})  

export function createApp() {  
    const app = createSSRApp(App)  
    app.use(router)  
    return { app }  
}

组件在 uni_modules 中自动注册,直接使用即可:

<RouterLink to="/pages/about/about">关于</RouterLink>  

<TabBar selected-color="#007aff">  
    <TabBarItem to="/pages/index/index" text="首页" />  
    <TabBarItem to="/pages/about/about" text="关于" :badge="5" />  
</TabBar>

文档

📖 https://mengxi-studio.github.io/uni-router/v1/

License

MIT

收起阅读 »

希望能实现类似vs code的粘滞滚动功能

HBuilderX 插件需求

自己实现的插件只能点击跳转很不美观

自己实现的插件只能点击跳转很不美观

并发接口拦截器中如何实现全局弹窗

问题现象
在应用中,每个接口都可能返回响应码Code,需要在接口拦截器中实现一个全局弹窗。由于接口是并发的,弹窗只能弹出一次。那么如何实现这个全局弹窗呢?此外,由于弹窗可能会在多个页面弹出(如启动页、登录页、主页等),这些页面可能会被销毁,这会导致弹窗无法正常显示。

效果预览
点击放大 点击放大 点击放大

背景知识
使用弹窗组件时,可优先考虑自定义弹窗,便于自定义弹窗的样式与内容。通过CustomDialogController类显示自定义弹窗,不支持直接在类中定义和使用。通常需要将弹框逻辑封装成Builder或其他组件,以便在需要时调用。
可以使用@StorageLink与AppStorage中的key对应的属性建立双向数据同步,该属性可以和UI组件同步,且可以在应用业务逻辑中被访问。
解决方案
在并发接口拦截器中,由于弹窗弹出位置不确定且仅弹出一次,因此需要维护一个全局变量来保证弹窗的弹出状态。可以在AppStorage中定义弹窗弹出状态,并通过@StorageLink来获取弹窗是否曾弹出,具体实现可参考以下示例:

EntryAbility.ets的onWindowStageCreate方法里通过AppStorage定义关于弹框显示的全局属性,默认false不显示:
windowStage.loadContent('pages/Index', (err) => {
AppStorage.setOrCreate('showGlobalCustomDialog', false);
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
封装1个弹框实例类CustomDialogLayout.ets:
@CustomDialog
export struct CustomDialogLayout {
controller?: CustomDialogController;

build() {
Column() {
Text('Global Custom Dialog Test');
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.height(400);
}
}
Index.ets,在该页面import并创建实例import { CustomDialogLayout } from './CustomDialogLayout',并且监听showGlobalCustomDialog属性值的改变并进行拉起弹窗动作:
import { CustomDialogLayout } from './CustomDialogLayout';

@Entry
@Component
struct Index {
@Provide('pathStack') pathStack: NavPathStack = new NavPathStack();
@StorageLink('showGlobalCustomDialog') @Watch('globalCustomDialogStateChange') showGlobalCustomDialog: boolean = false;

globalCustomDialogStateChange() {
if (this.showGlobalCustomDialog) {
if (this.dialogController != null) {
this.dialogController.open();
AppStorage.setOrCreate('showGlobalCustomDialog', false);
}
}
}

dialogController: CustomDialogController | null = new CustomDialogController({
builder: CustomDialogLayout({}),
autoCancel: true,
alignment: DialogAlignment.Center,
});

build() {
Navigation(this.pathStack) {
RelativeContainer() {
Button('跳转其他页面')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: 'container', align: VerticalAlign.Center },
middle: { anchor: 'container', align: HorizontalAlign.Center }
})

      .onClick(() => {  
        this.pathStack.pushPathByName('DetailPage', null);  
      });  
  }  
  .height('100%')  
  .width('100%');  
}  
.mode(NavigationMode.Stack);  

}
}
DetailPage.ets,在该页面设置showGlobalCustomDialog全局属性为true即可调起弹框:
@Builder
export function DetailPageBuilder() {
DetailPage();
}

@Component
export struct DetailPage {
@Consume('pathStack') pathStack: NavPathStack;

build() {
NavDestination() {
RelativeContainer() {
Button('promptAction弹窗')
.onClick(() => {
AppStorage.setOrCreate('showGlobalCustomDialog', true);
})
.alignRules({
center: { anchor: 'container', align: VerticalAlign.Center },
middle: { anchor: 'container', align: HorizontalAlign.Center }
});
};
}.title('DetailPage');
}
}
总结
在并发场景下,为了实现条件判断和控制弹窗弹出的行为,可以通过在AppStorage中维护一个全局变量,并使用@StorageLink进行同步监听。这种方法可以保证弹窗只弹出一次,从而避免重复弹出问题。
https://pastebin.com/JihHB9qj
https://pastebin.com/pp4XgNHN
https://pastebin.com/gFi7Ty8i
https://pastebin.com/6nsKZsrR
https://pastebin.com/ZC5tJmyd
https://pastebin.com/vAsXffGW
https://pastebin.com/ha4pYMBs
https://pastebin.com/Lef3jLKc
https://pastebin.com/TJ2Pj2vx
https://pastebin.com/RhihyVw7
https://pastebin.com/erh6eVzC
https://pastebin.com/bKGKK1dm
https://pastebin.com/2UFK6D1y
https://pastebin.com/7R29tEfq
https://pastebin.com/xrH49CPL
https://pastebin.com/gdw9yUrU
https://pastebin.com/EUVjawaY
https://pastebin.com/4TuGak9p
https://pastebin.com/EqWheA0Z
https://pastebin.com/nAWfX2Ce
https://pastebin.com/CAFaEHu9
https://pastebin.com/xCXmycSc
https://pastebin.com/WmenYTsS
https://pastebin.com/z9spTDji
https://pastebin.com/uVp7Nsp1
https://pastebin.com/TEaDnLGQ
https://pastebin.com/mURyCtEV
https://pastebin.com/9zMixq54
https://pastebin.com/YPKWWGwt
https://pastebin.com/BbpwnY0b
https://pastebin.com/kzXJ2Sj5
https://pastebin.com/98nCcftH
https://pastebin.com/g4bA9DML
https://pastebin.com/vjf4hZig
https://pastebin.com/pQPff8MH
https://pastebin.com/2iXVx5dF
https://pastebin.com/prBWGhpk
https://pastebin.com/EJKznYt6
https://pastebin.com/Waa8ezGu
https://pastebin.com/Hje8V6t1
https://pastebin.com/awWHMgZY
https://pastebin.com/EiUjbuN0
https://pastebin.com/XaW0WUTn
https://pastebin.com/JRnZe7Tm
https://pastebin.com/507BPDNz
https://pastebin.com/LKaevb2Y
https://pastebin.com/Sptc9XfX
https://pastebin.com/0e52nKM3
https://pastebin.com/pRFAKR9p
https://pastebin.com/WR98YgEn
https://pastebin.com/MHZyyxsk
https://pastebin.com/zygnUg3G
https://pastebin.com/Y1R09XKN
https://pastebin.com/vWzeCqM7
https://pastebin.com/DNTXXrcn
https://pastebin.com/BfqGBiUU
https://pastebin.com/LwskiZkg
https://pastebin.com/ZwvqpJFq
https://pastebin.com/FQeL9Lfv
https://pastebin.com/s79uWV97
https://pastebin.com/vzVbnZtK
https://pastebin.com/WjTV0ewV
https://pastebin.com/jyVBYEik
https://pastebin.com/Uzcz0PVy
https://pastebin.com/eQxuA195
https://pastebin.com/RpRNfuyG
https://pastebin.com/ZYkLnSEJ
https://pastebin.com/dbvxYAEL
https://pastebin.com/Jr3BFC6q
https://pastebin.com/AK3Fkc6N
https://pastebin.com/eR15KBgQ
https://pastebin.com/MiNxqBcV
https://pastebin.com/0hgraL2f
https://pastebin.com/BpkhJeBE
https://pastebin.com/CtEt6Jhh
https://pastebin.com/XbCJyFi6
https://pastebin.com/K3kM1H0t
https://pastebin.com/szcQwiUx
https://pastebin.com/fBNMQweP
https://pastebin.com/a5BFSqKk
https://pastebin.com/ugaAe5bM
https://pastebin.com/X7cTkwjx
https://pastebin.com/eYM3ucew
https://pastebin.com/pfti4VKT
https://pastebin.com/hsHQwwgQ
https://pastebin.com/z17PcPCS
https://pastebin.com/6gjv06Ri
https://pastebin.com/XE9Bajbu
https://pastebin.com/3wVx4KqR
https://pastebin.com/586hmSqD
https://pastebin.com/qEzZahup
https://pastebin.com/3WGJ7PKM
https://pastebin.com/CzRJGMNg
https://pastebin.com/Pfb1e6ww
https://pastebin.com/kSt2e5bn
https://pastebin.com/WUApxj6r
https://pastebin.com/xEagAfbb
https://pastebin.com/cvkQfAjy
https://pastebin.com/sBFEPgwx
https://pastebin.com/DeThFfPT
https://pastebin.com/j7HGLNrn
https://pastebin.com/mK2WQPEr
https://pastebin.com/Kf6eEfSp
https://pastebin.com/Cd9cBH5m
https://pastebin.com/g0X42ybn
https://pastebin.com/mZyGx93u
https://pastebin.com/VkfwmAZR
https://pastebin.com/0gTGQkqF
https://pastebin.com/pqFk4DeF
https://pastebin.com/5y7Vq3Hb
https://pastebin.com/MWBqZUM0
https://pastebin.com/KU878iEv
https://pastebin.com/zXqt3ek3
https://pastebin.com/c0Bg5wWE
https://pastebin.com/UVdGibVC
https://pastebin.com/QpGAkSny
https://pastebin.com/SUUDWFHy
https://pastebin.com/xyLjSgQV
https://pastebin.com/8PbH8qL1
https://pastebin.com/Hk1ALAEc
https://pastebin.com/eXtjEzhZ
https://pastebin.com/nfC0PSKj
https://pastebin.com/MYfr25pM
https://pastebin.com/mBfJkAaY
https://pastebin.com/W7Z00fYY
https://pastebin.com/fPRnwJds
https://pastebin.com/856Bn5dS

继续阅读 »

问题现象
在应用中,每个接口都可能返回响应码Code,需要在接口拦截器中实现一个全局弹窗。由于接口是并发的,弹窗只能弹出一次。那么如何实现这个全局弹窗呢?此外,由于弹窗可能会在多个页面弹出(如启动页、登录页、主页等),这些页面可能会被销毁,这会导致弹窗无法正常显示。

效果预览
点击放大 点击放大 点击放大

背景知识
使用弹窗组件时,可优先考虑自定义弹窗,便于自定义弹窗的样式与内容。通过CustomDialogController类显示自定义弹窗,不支持直接在类中定义和使用。通常需要将弹框逻辑封装成Builder或其他组件,以便在需要时调用。
可以使用@StorageLink与AppStorage中的key对应的属性建立双向数据同步,该属性可以和UI组件同步,且可以在应用业务逻辑中被访问。
解决方案
在并发接口拦截器中,由于弹窗弹出位置不确定且仅弹出一次,因此需要维护一个全局变量来保证弹窗的弹出状态。可以在AppStorage中定义弹窗弹出状态,并通过@StorageLink来获取弹窗是否曾弹出,具体实现可参考以下示例:

EntryAbility.ets的onWindowStageCreate方法里通过AppStorage定义关于弹框显示的全局属性,默认false不显示:
windowStage.loadContent('pages/Index', (err) => {
AppStorage.setOrCreate('showGlobalCustomDialog', false);
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
封装1个弹框实例类CustomDialogLayout.ets:
@CustomDialog
export struct CustomDialogLayout {
controller?: CustomDialogController;

build() {
Column() {
Text('Global Custom Dialog Test');
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.height(400);
}
}
Index.ets,在该页面import并创建实例import { CustomDialogLayout } from './CustomDialogLayout',并且监听showGlobalCustomDialog属性值的改变并进行拉起弹窗动作:
import { CustomDialogLayout } from './CustomDialogLayout';

@Entry
@Component
struct Index {
@Provide('pathStack') pathStack: NavPathStack = new NavPathStack();
@StorageLink('showGlobalCustomDialog') @Watch('globalCustomDialogStateChange') showGlobalCustomDialog: boolean = false;

globalCustomDialogStateChange() {
if (this.showGlobalCustomDialog) {
if (this.dialogController != null) {
this.dialogController.open();
AppStorage.setOrCreate('showGlobalCustomDialog', false);
}
}
}

dialogController: CustomDialogController | null = new CustomDialogController({
builder: CustomDialogLayout({}),
autoCancel: true,
alignment: DialogAlignment.Center,
});

build() {
Navigation(this.pathStack) {
RelativeContainer() {
Button('跳转其他页面')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: 'container', align: VerticalAlign.Center },
middle: { anchor: 'container', align: HorizontalAlign.Center }
})

      .onClick(() => {  
        this.pathStack.pushPathByName('DetailPage', null);  
      });  
  }  
  .height('100%')  
  .width('100%');  
}  
.mode(NavigationMode.Stack);  

}
}
DetailPage.ets,在该页面设置showGlobalCustomDialog全局属性为true即可调起弹框:
@Builder
export function DetailPageBuilder() {
DetailPage();
}

@Component
export struct DetailPage {
@Consume('pathStack') pathStack: NavPathStack;

build() {
NavDestination() {
RelativeContainer() {
Button('promptAction弹窗')
.onClick(() => {
AppStorage.setOrCreate('showGlobalCustomDialog', true);
})
.alignRules({
center: { anchor: 'container', align: VerticalAlign.Center },
middle: { anchor: 'container', align: HorizontalAlign.Center }
});
};
}.title('DetailPage');
}
}
总结
在并发场景下,为了实现条件判断和控制弹窗弹出的行为,可以通过在AppStorage中维护一个全局变量,并使用@StorageLink进行同步监听。这种方法可以保证弹窗只弹出一次,从而避免重复弹出问题。
https://pastebin.com/JihHB9qj
https://pastebin.com/pp4XgNHN
https://pastebin.com/gFi7Ty8i
https://pastebin.com/6nsKZsrR
https://pastebin.com/ZC5tJmyd
https://pastebin.com/vAsXffGW
https://pastebin.com/ha4pYMBs
https://pastebin.com/Lef3jLKc
https://pastebin.com/TJ2Pj2vx
https://pastebin.com/RhihyVw7
https://pastebin.com/erh6eVzC
https://pastebin.com/bKGKK1dm
https://pastebin.com/2UFK6D1y
https://pastebin.com/7R29tEfq
https://pastebin.com/xrH49CPL
https://pastebin.com/gdw9yUrU
https://pastebin.com/EUVjawaY
https://pastebin.com/4TuGak9p
https://pastebin.com/EqWheA0Z
https://pastebin.com/nAWfX2Ce
https://pastebin.com/CAFaEHu9
https://pastebin.com/xCXmycSc
https://pastebin.com/WmenYTsS
https://pastebin.com/z9spTDji
https://pastebin.com/uVp7Nsp1
https://pastebin.com/TEaDnLGQ
https://pastebin.com/mURyCtEV
https://pastebin.com/9zMixq54
https://pastebin.com/YPKWWGwt
https://pastebin.com/BbpwnY0b
https://pastebin.com/kzXJ2Sj5
https://pastebin.com/98nCcftH
https://pastebin.com/g4bA9DML
https://pastebin.com/vjf4hZig
https://pastebin.com/pQPff8MH
https://pastebin.com/2iXVx5dF
https://pastebin.com/prBWGhpk
https://pastebin.com/EJKznYt6
https://pastebin.com/Waa8ezGu
https://pastebin.com/Hje8V6t1
https://pastebin.com/awWHMgZY
https://pastebin.com/EiUjbuN0
https://pastebin.com/XaW0WUTn
https://pastebin.com/JRnZe7Tm
https://pastebin.com/507BPDNz
https://pastebin.com/LKaevb2Y
https://pastebin.com/Sptc9XfX
https://pastebin.com/0e52nKM3
https://pastebin.com/pRFAKR9p
https://pastebin.com/WR98YgEn
https://pastebin.com/MHZyyxsk
https://pastebin.com/zygnUg3G
https://pastebin.com/Y1R09XKN
https://pastebin.com/vWzeCqM7
https://pastebin.com/DNTXXrcn
https://pastebin.com/BfqGBiUU
https://pastebin.com/LwskiZkg
https://pastebin.com/ZwvqpJFq
https://pastebin.com/FQeL9Lfv
https://pastebin.com/s79uWV97
https://pastebin.com/vzVbnZtK
https://pastebin.com/WjTV0ewV
https://pastebin.com/jyVBYEik
https://pastebin.com/Uzcz0PVy
https://pastebin.com/eQxuA195
https://pastebin.com/RpRNfuyG
https://pastebin.com/ZYkLnSEJ
https://pastebin.com/dbvxYAEL
https://pastebin.com/Jr3BFC6q
https://pastebin.com/AK3Fkc6N
https://pastebin.com/eR15KBgQ
https://pastebin.com/MiNxqBcV
https://pastebin.com/0hgraL2f
https://pastebin.com/BpkhJeBE
https://pastebin.com/CtEt6Jhh
https://pastebin.com/XbCJyFi6
https://pastebin.com/K3kM1H0t
https://pastebin.com/szcQwiUx
https://pastebin.com/fBNMQweP
https://pastebin.com/a5BFSqKk
https://pastebin.com/ugaAe5bM
https://pastebin.com/X7cTkwjx
https://pastebin.com/eYM3ucew
https://pastebin.com/pfti4VKT
https://pastebin.com/hsHQwwgQ
https://pastebin.com/z17PcPCS
https://pastebin.com/6gjv06Ri
https://pastebin.com/XE9Bajbu
https://pastebin.com/3wVx4KqR
https://pastebin.com/586hmSqD
https://pastebin.com/qEzZahup
https://pastebin.com/3WGJ7PKM
https://pastebin.com/CzRJGMNg
https://pastebin.com/Pfb1e6ww
https://pastebin.com/kSt2e5bn
https://pastebin.com/WUApxj6r
https://pastebin.com/xEagAfbb
https://pastebin.com/cvkQfAjy
https://pastebin.com/sBFEPgwx
https://pastebin.com/DeThFfPT
https://pastebin.com/j7HGLNrn
https://pastebin.com/mK2WQPEr
https://pastebin.com/Kf6eEfSp
https://pastebin.com/Cd9cBH5m
https://pastebin.com/g0X42ybn
https://pastebin.com/mZyGx93u
https://pastebin.com/VkfwmAZR
https://pastebin.com/0gTGQkqF
https://pastebin.com/pqFk4DeF
https://pastebin.com/5y7Vq3Hb
https://pastebin.com/MWBqZUM0
https://pastebin.com/KU878iEv
https://pastebin.com/zXqt3ek3
https://pastebin.com/c0Bg5wWE
https://pastebin.com/UVdGibVC
https://pastebin.com/QpGAkSny
https://pastebin.com/SUUDWFHy
https://pastebin.com/xyLjSgQV
https://pastebin.com/8PbH8qL1
https://pastebin.com/Hk1ALAEc
https://pastebin.com/eXtjEzhZ
https://pastebin.com/nfC0PSKj
https://pastebin.com/MYfr25pM
https://pastebin.com/mBfJkAaY
https://pastebin.com/W7Z00fYY
https://pastebin.com/fPRnwJds
https://pastebin.com/856Bn5dS

收起阅读 »

uni-app路由管理神器:vue-router风格体验

路由拦截 路由守卫 路由

@meng-xi/uni-router

为 uni-app (Vue 3) 提供类似 vue-router 风格的路由管理系统(uni_modules 版本)。

⚠️ 仅支持 Vue 3 — 本库基于 Vue 3 Composition API(inject / ref / defineProps / defineEmits)和 app.provide 等 Vue 3 专属 API,不支持 uni-app Vue 2 项目。


特性

  • vue-router 风格 API - push / replace / relaunch / back,零学习成本
  • 路由守卫 - beforeEach / beforeResolve / afterEach / beforeEnter,支持 next(location, { mode }) 指定重定向方式
  • 命名路由 & 路由元信息 - 通过 name 导航,meta 携带自定义数据
  • TypeScript 类型提示 - 路由名称和路径自动补全与类型检查
  • uni API 拦截 - 可选拦截原生导航 API,统一守卫流程(interceptUniApi
  • 页面间通信 - useUniEventChannel 启用后所有导航方式均支持 eventChannel 双向通信,目标页通过 usePageChannel() 获取通道,基于 uni.$emit 全局事件总线,粘性缓存确保时序安全
  • 声明式导航 - RouterLink 组件,基于 uni navigator 封装,支持导航参数、动画、页面通信
  • 页面参数传递 - params 传递复杂数据,不暴露在 URL,支持 persistent 持久化
  • 查询参数增强 - queryInt() / queryNumber() / queryBool() 便捷解析
  • 导航动画 - push / replace / back 支持动画参数,仅 App 端生效
  • 路由状态同步 - syncRoute() 处理浏览器后退、物理返回键等场景
  • 错误处理 - 完整的 RouterError / NavigationFailure 体系,onError 全局捕获
  • 组合式 API - useRouter() / useRoute() / usePageChannel() 响应式访问路由与通信通道

安装

uni_modules(推荐)

mxuni-router 目录复制到项目的 uni_modules 目录下即可,无需 npm 安装。

npm

pnpm add @meng-xi/uni-router

npm 方式需将导入路径改为 @meng-xi/uni-router

快速开始

1. 创建路由器

// main.ts  
import { createSSRApp } from 'vue'  
import { createRouter } from './uni_modules/mxuni-router/js_sdk/index.js'  
import App from './App.vue'  

const router = createRouter({  
    routes: [  
        { path: 'pages/index/index', name: 'home', meta: { title: '首页' } },  
        { path: 'pages/about/about', name: 'about', meta: { title: '关于', requireAuth: true } }  
    ],  
    strict: true,  
    interceptUniApi: true, // 拦截 uni 原生导航 API,确保守卫生效  
    useUniEventChannel: true // 启用内置通信管理器,所有导航方式支持页面间双向通信  
})  

export function createApp() {  
    const app = createSSRApp(App)  
    app.use(router)  
    return { app }  
}

2. 路由导航

import { useRouter, useRoute } from './uni_modules/mxuni-router/js_sdk/index.js'  

const router = useRouter()  
const route = useRoute() // 返回响应式引用,路由变化时自动更新  

// 路径导航  
await router.push({ path: '/pages/about/about', query: { id: '1' } })  

// 命名导航  
await router.push({ name: 'about' })  

// 页面参数传递(params 不暴露在 URL,支持复杂数据)  
await router.push({ path: '/pages/detail/detail', params: { info: { name: 'Tom' } } })  

// 返回(执行完整守卫链)  
await router.back()

3. 页面间通信

启用 useUniEventChannel 后,所有导航方式均返回 eventChannel,目标页通过 usePageChannel() 获取通道:

// ===== 发起页 =====  
const result = await router.push({ path: '/pages/detail/detail' })  
result.eventChannel?.on('ready', data => console.log('目标页已就绪:', data))  
result.eventChannel?.emit('data', { msg: '发给目标页的数据' })  

// ===== 目标页(detail.vue)=====  
import { usePageChannel } from './uni_modules/mxuni-router/js_sdk/index.js'  

const channel = usePageChannel() // 无 __nav_id 时返回 noopChannel,无需判空  
channel.on('data', data => console.log('收到发起页数据:', data))  
channel.emit('ready', { status: 'ok' }) // 粘性缓存:发起页尚未注册 on 也能收到

4. 路由守卫

router.beforeEach((to, from, next) => {  
    if (to.meta.requireAuth && !isLoggedIn()) {  
        // 使用 replace 模式重定向,避免登录页之后残留受保护页面的历史  
        next({ name: 'login', query: { redirect: to.fullPath } }, { mode: 'replace' })  
    } else {  
        next()  
    }  
})

5. 自动生成路由配置(推荐)

配合 @meng-xi/vite-plugingenerateRouter 插件,可从 pages.json 自动生成路由配置和类型声明:

pnpm add @meng-xi/vite-plugin -D
// vite.config.ts  
import { defineConfig } from 'vite'  
import uni from '@dcloudio/vite-plugin-uni'  
import { generateRouter } from '@meng-xi/vite-plugin'  

export default defineConfig({  
    plugins: [  
        uni(),  
        generateRouter({  
            pagesJsonPath: 'src/pages.json',  
            outputPath: 'src/router.config.ts',  
            dts: true,  
            metaMapping: {  
                navigationBarTitleText: 'title',  
                requireAuth: 'requireAuth'  
            }  
        })  
    ]  
})

然后在 main.ts 中导入生成的路由配置:

import { createRouter } from './uni_modules/mxuni-router/js_sdk/index.js'  
import routes from './router.config'  

const router = createRouter({ routes })

文档

完整的 API 参考、配置项说明、RouterLink 组件属性、类型定义等请查阅官方网站:

📖 https://mengxi-studio.github.io/uni-router/

License

MIT

继续阅读 »

@meng-xi/uni-router

为 uni-app (Vue 3) 提供类似 vue-router 风格的路由管理系统(uni_modules 版本)。

⚠️ 仅支持 Vue 3 — 本库基于 Vue 3 Composition API(inject / ref / defineProps / defineEmits)和 app.provide 等 Vue 3 专属 API,不支持 uni-app Vue 2 项目。


特性

  • vue-router 风格 API - push / replace / relaunch / back,零学习成本
  • 路由守卫 - beforeEach / beforeResolve / afterEach / beforeEnter,支持 next(location, { mode }) 指定重定向方式
  • 命名路由 & 路由元信息 - 通过 name 导航,meta 携带自定义数据
  • TypeScript 类型提示 - 路由名称和路径自动补全与类型检查
  • uni API 拦截 - 可选拦截原生导航 API,统一守卫流程(interceptUniApi
  • 页面间通信 - useUniEventChannel 启用后所有导航方式均支持 eventChannel 双向通信,目标页通过 usePageChannel() 获取通道,基于 uni.$emit 全局事件总线,粘性缓存确保时序安全
  • 声明式导航 - RouterLink 组件,基于 uni navigator 封装,支持导航参数、动画、页面通信
  • 页面参数传递 - params 传递复杂数据,不暴露在 URL,支持 persistent 持久化
  • 查询参数增强 - queryInt() / queryNumber() / queryBool() 便捷解析
  • 导航动画 - push / replace / back 支持动画参数,仅 App 端生效
  • 路由状态同步 - syncRoute() 处理浏览器后退、物理返回键等场景
  • 错误处理 - 完整的 RouterError / NavigationFailure 体系,onError 全局捕获
  • 组合式 API - useRouter() / useRoute() / usePageChannel() 响应式访问路由与通信通道

安装

uni_modules(推荐)

mxuni-router 目录复制到项目的 uni_modules 目录下即可,无需 npm 安装。

npm

pnpm add @meng-xi/uni-router

npm 方式需将导入路径改为 @meng-xi/uni-router

快速开始

1. 创建路由器

// main.ts  
import { createSSRApp } from 'vue'  
import { createRouter } from './uni_modules/mxuni-router/js_sdk/index.js'  
import App from './App.vue'  

const router = createRouter({  
    routes: [  
        { path: 'pages/index/index', name: 'home', meta: { title: '首页' } },  
        { path: 'pages/about/about', name: 'about', meta: { title: '关于', requireAuth: true } }  
    ],  
    strict: true,  
    interceptUniApi: true, // 拦截 uni 原生导航 API,确保守卫生效  
    useUniEventChannel: true // 启用内置通信管理器,所有导航方式支持页面间双向通信  
})  

export function createApp() {  
    const app = createSSRApp(App)  
    app.use(router)  
    return { app }  
}

2. 路由导航

import { useRouter, useRoute } from './uni_modules/mxuni-router/js_sdk/index.js'  

const router = useRouter()  
const route = useRoute() // 返回响应式引用,路由变化时自动更新  

// 路径导航  
await router.push({ path: '/pages/about/about', query: { id: '1' } })  

// 命名导航  
await router.push({ name: 'about' })  

// 页面参数传递(params 不暴露在 URL,支持复杂数据)  
await router.push({ path: '/pages/detail/detail', params: { info: { name: 'Tom' } } })  

// 返回(执行完整守卫链)  
await router.back()

3. 页面间通信

启用 useUniEventChannel 后,所有导航方式均返回 eventChannel,目标页通过 usePageChannel() 获取通道:

// ===== 发起页 =====  
const result = await router.push({ path: '/pages/detail/detail' })  
result.eventChannel?.on('ready', data => console.log('目标页已就绪:', data))  
result.eventChannel?.emit('data', { msg: '发给目标页的数据' })  

// ===== 目标页(detail.vue)=====  
import { usePageChannel } from './uni_modules/mxuni-router/js_sdk/index.js'  

const channel = usePageChannel() // 无 __nav_id 时返回 noopChannel,无需判空  
channel.on('data', data => console.log('收到发起页数据:', data))  
channel.emit('ready', { status: 'ok' }) // 粘性缓存:发起页尚未注册 on 也能收到

4. 路由守卫

router.beforeEach((to, from, next) => {  
    if (to.meta.requireAuth && !isLoggedIn()) {  
        // 使用 replace 模式重定向,避免登录页之后残留受保护页面的历史  
        next({ name: 'login', query: { redirect: to.fullPath } }, { mode: 'replace' })  
    } else {  
        next()  
    }  
})

5. 自动生成路由配置(推荐)

配合 @meng-xi/vite-plugingenerateRouter 插件,可从 pages.json 自动生成路由配置和类型声明:

pnpm add @meng-xi/vite-plugin -D
// vite.config.ts  
import { defineConfig } from 'vite'  
import uni from '@dcloudio/vite-plugin-uni'  
import { generateRouter } from '@meng-xi/vite-plugin'  

export default defineConfig({  
    plugins: [  
        uni(),  
        generateRouter({  
            pagesJsonPath: 'src/pages.json',  
            outputPath: 'src/router.config.ts',  
            dts: true,  
            metaMapping: {  
                navigationBarTitleText: 'title',  
                requireAuth: 'requireAuth'  
            }  
        })  
    ]  
})

然后在 main.ts 中导入生成的路由配置:

import { createRouter } from './uni_modules/mxuni-router/js_sdk/index.js'  
import routes from './router.config'  

const router = createRouter({ routes })

文档

完整的 API 参考、配置项说明、RouterLink 组件属性、类型定义等请查阅官方网站:

📖 https://mengxi-studio.github.io/uni-router/

License

MIT

收起阅读 »

做AI给我做好了啊 ,傻逼玩意儿,开发者也是脑残

1.AI无法撤回到历史聊天记录

2.你们的AI只要一个项目,打开多个vue,会一起更改

3.你说你们有历史会话,但是他麻痹的会压缩,AI根本读不完

4.AI改了代码,不明确修改了哪里,我tm找了半天找到按钮,点击 接受 更改,哎你猜怎么着,无法接受,点了和没点一样,依然显示xxx行更改,

5.总结,你们tm要做AI给我做好了啊,基础的东西都没有,就en改啊

继续阅读 »

1.AI无法撤回到历史聊天记录

2.你们的AI只要一个项目,打开多个vue,会一起更改

3.你说你们有历史会话,但是他麻痹的会压缩,AI根本读不完

4.AI改了代码,不明确修改了哪里,我tm找了半天找到按钮,点击 接受 更改,哎你猜怎么着,无法接受,点了和没点一样,依然显示xxx行更改,

5.总结,你们tm要做AI给我做好了啊,基础的东西都没有,就en改啊

收起阅读 »

App Store 审核遇到 4.3(a) 怎么办?一次真实处理思路分享

可以,换平台发就不要和上一篇太像。下面这版标题、结构、开头都重新换了,关键词还是保留 苹果4.3(a)、App Store审核、马甲包上架、申诉,适合发 CSDN、知乎、掘金、百家号这类平台。

App Store 审核遇到 4.3(a) 怎么办?一次真实处理思路分享

做 iOS 上架的人,基本都绕不开苹果审核 4.3(a)。尤其是工具类、教育类、内容类、马甲包上架项目,只要 App 和已有产品相似度比较高,就有可能被苹果认为是重复应用。很多开发者收到 4.3(a) 后,第一反应是直接申诉,但实际处理下来,单靠申诉通常很难解决问题。

我更建议把 4.3(a) 当成一次“产品独立性检查”。也就是说,苹果不是只看你有没有换名字、换图标,而是看这个 App 是否真的有独立的功能、独立的内容、独立的用户场景。

  1. 4.3(a) 被拒,问题通常出在哪里?

苹果 4.3(a) 被拒,常见原因不是单点问题,而是多个相似点叠加造成的。

常见风险包括:

  1. App 功能和旧版本或同类产品太接近;
  2. 页面结构、首页布局、底部导航高度相似;
  3. App Store 截图、标题、描述、关键词重复;
  4. IPA 包里的资源文件、图片、模块命名相似;
  5. 只是换了 icon、启动图、主题色,没有改变核心体验;
  6. App 内还有旧项目名称、旧接口、旧隐私政策或旧文案。

很多时候,开发者觉得自己已经改了不少,但从审核人员视角看,可能还是同一个模板。

  1. 为什么直接申诉效果不好?

收到 4.3(a) 后,如果直接回复“我们的 App 是原创的,请重新审核”,通常说服力不够。因为苹果更关注 App 本身的实际变化,而不是开发者的口头解释。

如果 App 没有明显改动,截图没换,功能没变,描述还是原来的表达,那么即使申诉,也很容易继续被拒。

所以处理顺序应该是:

先找相似点,再做差异化,再写申诉说明,最后重新提交。

  1. 真正有效的处理方式是什么?

第一步,先改定位。
不要只把 App 描述成“工具”“学习软件”“生活服务”。要具体到目标人群和使用场景,比如儿童启蒙、考试练习、口语跟读、企业内部管理、个人效率记录等。定位清楚以后,审核备注和产品描述才有支撑。

第二步,改核心功能。
如果只是首页、分类、详情、会员、我的这几个页面来回套,很容易被认为模板化。可以调整功能入口、内容分类、任务流程、数据展示方式,让用户使用路径发生变化。

第三步,改 UI 和截图。
UI 不要只换颜色。首页结构、图标风格、按钮样式、卡片布局、空页面、引导页都应该重新设计。App Store 截图也要单独做,不能几个包共用一套模板。

第四步,清理资源和旧内容。
重点检查图片、音频、banner、课程、文章、接口路径、旧产品名称、旧客服信息、旧隐私政策链接。这些细节很容易让审核认为 App 是复制出来的。

第五步,检查 IPA 相似度。
如果是从旧项目改出来的,最好在提交前做一次包体检查,看看资源结构、模块命名、配置文件、无用代码是否还保留太多旧项目痕迹。

  1. 申诉应该怎么写?

4.3(a) 的申诉不要写得太硬,也不要说“我们没有问题”。更合适的写法是承认苹果的审核关注点,然后说明你已经完成了哪些调整。

可以这样写:

Hello App Review Team,
Thank you for your review.
We understand the concern regarding Guideline 4.3(a). After receiving the rejection, we carefully reviewed the app and made several updates to better reflect its independent value.
This app is designed for [目标用户] and focuses on [核心使用场景]. In this version, we updated the user interface, adjusted the feature structure, revised the App Store metadata, redesigned screenshots, and removed unrelated legacy content.
The app now provides a clearer user experience and independent functionality for its intended users.
We kindly ask you to review the updated version again. Thank you.

这类申诉的重点不是解释太多,而是让审核人员看到你已经针对问题做了实际调整。

  1. 重新提交前的检查清单

提交前建议逐项检查:

  1. App 名称、副标题、关键词是否独立;

  2. 描述是否围绕当前 App 重新写;

  3. 截图是否重新设计;

  4. 首页和核心页面是否有明显变化;

  5. 功能流程是否不是简单复制;

  6. 是否删除旧项目残留信息;

  7. 隐私政策和用户协议是否匹配;

  8. 权限申请是否合理;

  9. 是否存在空功能、假功能、不可用功能;

  10. IPA 包内资源和代码是否相似度过高。

  11. 总结

苹果审核 4.3(a) 并不是不能解决,但不能只靠一句申诉。真正有效的办法,是把 App 从产品定位、功能结构、UI 设计、资源内容、元数据文案、包体结构几个方面重新整理。

对于马甲包上架来说,最重要的一点是:不要让它看起来像复制包,而要让它成为一个有独立场景、独立功能、独立价值的 App。这样再配合清晰的申诉说明,重新提交通过的概率才会更高。

继续阅读 »

可以,换平台发就不要和上一篇太像。下面这版标题、结构、开头都重新换了,关键词还是保留 苹果4.3(a)、App Store审核、马甲包上架、申诉,适合发 CSDN、知乎、掘金、百家号这类平台。

App Store 审核遇到 4.3(a) 怎么办?一次真实处理思路分享

做 iOS 上架的人,基本都绕不开苹果审核 4.3(a)。尤其是工具类、教育类、内容类、马甲包上架项目,只要 App 和已有产品相似度比较高,就有可能被苹果认为是重复应用。很多开发者收到 4.3(a) 后,第一反应是直接申诉,但实际处理下来,单靠申诉通常很难解决问题。

我更建议把 4.3(a) 当成一次“产品独立性检查”。也就是说,苹果不是只看你有没有换名字、换图标,而是看这个 App 是否真的有独立的功能、独立的内容、独立的用户场景。

  1. 4.3(a) 被拒,问题通常出在哪里?

苹果 4.3(a) 被拒,常见原因不是单点问题,而是多个相似点叠加造成的。

常见风险包括:

  1. App 功能和旧版本或同类产品太接近;
  2. 页面结构、首页布局、底部导航高度相似;
  3. App Store 截图、标题、描述、关键词重复;
  4. IPA 包里的资源文件、图片、模块命名相似;
  5. 只是换了 icon、启动图、主题色,没有改变核心体验;
  6. App 内还有旧项目名称、旧接口、旧隐私政策或旧文案。

很多时候,开发者觉得自己已经改了不少,但从审核人员视角看,可能还是同一个模板。

  1. 为什么直接申诉效果不好?

收到 4.3(a) 后,如果直接回复“我们的 App 是原创的,请重新审核”,通常说服力不够。因为苹果更关注 App 本身的实际变化,而不是开发者的口头解释。

如果 App 没有明显改动,截图没换,功能没变,描述还是原来的表达,那么即使申诉,也很容易继续被拒。

所以处理顺序应该是:

先找相似点,再做差异化,再写申诉说明,最后重新提交。

  1. 真正有效的处理方式是什么?

第一步,先改定位。
不要只把 App 描述成“工具”“学习软件”“生活服务”。要具体到目标人群和使用场景,比如儿童启蒙、考试练习、口语跟读、企业内部管理、个人效率记录等。定位清楚以后,审核备注和产品描述才有支撑。

第二步,改核心功能。
如果只是首页、分类、详情、会员、我的这几个页面来回套,很容易被认为模板化。可以调整功能入口、内容分类、任务流程、数据展示方式,让用户使用路径发生变化。

第三步,改 UI 和截图。
UI 不要只换颜色。首页结构、图标风格、按钮样式、卡片布局、空页面、引导页都应该重新设计。App Store 截图也要单独做,不能几个包共用一套模板。

第四步,清理资源和旧内容。
重点检查图片、音频、banner、课程、文章、接口路径、旧产品名称、旧客服信息、旧隐私政策链接。这些细节很容易让审核认为 App 是复制出来的。

第五步,检查 IPA 相似度。
如果是从旧项目改出来的,最好在提交前做一次包体检查,看看资源结构、模块命名、配置文件、无用代码是否还保留太多旧项目痕迹。

  1. 申诉应该怎么写?

4.3(a) 的申诉不要写得太硬,也不要说“我们没有问题”。更合适的写法是承认苹果的审核关注点,然后说明你已经完成了哪些调整。

可以这样写:

Hello App Review Team,
Thank you for your review.
We understand the concern regarding Guideline 4.3(a). After receiving the rejection, we carefully reviewed the app and made several updates to better reflect its independent value.
This app is designed for [目标用户] and focuses on [核心使用场景]. In this version, we updated the user interface, adjusted the feature structure, revised the App Store metadata, redesigned screenshots, and removed unrelated legacy content.
The app now provides a clearer user experience and independent functionality for its intended users.
We kindly ask you to review the updated version again. Thank you.

这类申诉的重点不是解释太多,而是让审核人员看到你已经针对问题做了实际调整。

  1. 重新提交前的检查清单

提交前建议逐项检查:

  1. App 名称、副标题、关键词是否独立;

  2. 描述是否围绕当前 App 重新写;

  3. 截图是否重新设计;

  4. 首页和核心页面是否有明显变化;

  5. 功能流程是否不是简单复制;

  6. 是否删除旧项目残留信息;

  7. 隐私政策和用户协议是否匹配;

  8. 权限申请是否合理;

  9. 是否存在空功能、假功能、不可用功能;

  10. IPA 包内资源和代码是否相似度过高。

  11. 总结

苹果审核 4.3(a) 并不是不能解决,但不能只靠一句申诉。真正有效的办法,是把 App 从产品定位、功能结构、UI 设计、资源内容、元数据文案、包体结构几个方面重新整理。

对于马甲包上架来说,最重要的一点是:不要让它看起来像复制包,而要让它成为一个有独立场景、独立功能、独立价值的 App。这样再配合清晰的申诉说明,重新提交通过的概率才会更高。

收起阅读 »

Stack组件实现Swiper堆叠动画效果

问题现象
Swiper如何实现卡片堆叠样式:

上下堆叠:Swiper内容像卡片堆叠,在底部留部分空间显示下一页的内容,上下滑实现卡片切换。
左右堆叠:Swiper内容像卡片堆叠,在右侧留部分空间显示下一页的内容,左右滑实现卡片切换。
背景知识
Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件,当设置了多个子组件后,可以对这些子组件进行轮播显示。
Stack是堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件。
gesture可以为组件绑定手势方法进行相应处理,如滑动手势事件PanGesture。
animateTo指定由于闭包代码导致的状态变化插入过渡动效。
解决方案
上下堆叠实现。
自定义实现卡片堆叠的组件:使用Stack组件堆叠需要展示的图片,设置最上面的图片向上偏移部分距离,露出下一张图片的底部。为Stack绑定上下滑动的手势处理,实现切换图片逻辑,同时使用animateTo接口设置图片切换动画。

export class SwiperData {
imageSrc: Resource;

constructor(imageSrc: Resource) {
this.imageSrc = imageSrc;
}
}

@Component
export struct SwiperStackComponent {
@Link currentIndex: number;
@Prop swiperData: SwiperData[];
private halfCount: number = Math.floor(3 / 2);
private automaticSlidingDuration: number = 300;

aboutToAppear(): void {
this.currentIndex = 0;
}

// 修改堆叠方向系数计算
getImgCoefficients(index: number): number {
const coefficient = this.currentIndex - index;
const tempCoefficient = Math.abs(coefficient);
if (tempCoefficient <= this.halfCount) {
return coefficient;
}
const dataLength = this.swiperData.length;
let tempOffset = dataLength - tempCoefficient;
if (tempOffset <= this.halfCount) {
return coefficient > 0 ? -tempOffset : tempOffset;
}
return 0;
}

// 修改堆叠方向偏移量计算
getOffSet(index: number): number {
let offsetIndex = this.getImgCoefficients(index);
const tempOffset = Math.abs(offsetIndex);
let offset = 0;
if (tempOffset === 1) {
if (offsetIndex === 1) {
offsetIndex = -1;
}
offset = 50 * offsetIndex;
}
return -offset;
}

startAnimation(isLeft: boolean, duration: number): void {
this.getUIContext().animateTo({ duration: duration, }, () => {
const dataLength: number = this.swiperData.length;
const tempIndex: number = isLeft ? this.currentIndex + 1 : this.currentIndex - 1 + dataLength;
this.currentIndex = tempIndex % dataLength;
});
}

build() {
Stack() {
ForEach(this.swiperData, (item: SwiperData, index: number) => {
Stack({ alignContent: Alignment.Bottom }) {
Image(item.imageSrc)
.objectFit(ImageFit.Cover)
.width('100%')
.height('100%')
.borderRadius(8);
}
.offset({ x: 0, y: this.getOffSet(index) })
.shadow(ShadowStyle.OUTER_DEFAULT_SM)
.backgroundColor(Color.White)
.borderRadius(8)
.blur(index !== this.currentIndex ? 12 : 0)
// 通过animateTo实现动画并且同时改变currentIndex数据中间值来判断组件zIndex实现切换动画
.zIndex(index !== this.currentIndex && this.getImgCoefficients(index) === 0 ?
0 : 2 - Math.abs(this.getImgCoefficients(index)))
.width(310)
.height(index !== this.currentIndex ? 130 : 180);
});
}
.height(200)
.width('100%')
.gesture(
PanGesture({ direction: PanDirection.Vertical })
.onActionStart((event: GestureEvent) => {
this.startAnimation(event.offsetY < 0, this.automaticSlidingDuration);
})
)
.alignContent(Alignment.Center)
.padding({ left: 12, right: 12, top: 12 });
}
}
在页面中直接使用上面封装好的SwiperStackComponent即可,示例如下:传给SwiperStackComponent要堆叠的图片。
https://pastebin.com/m1LSXzRK
https://pastebin.com/cFaDz2JM
https://pastebin.com/94NrGTMh
https://pastebin.com/Yjx17LeK
https://pastebin.com/LqsPJ96h
https://pastebin.com/DPDgXMvK
https://pastebin.com/hSjsmXQA
https://pastebin.com/uKP1SMUS
https://pastebin.com/NzZpsC1T
https://pastebin.com/YYRMvbNK
https://pastebin.com/yMGLZhE6
https://pastebin.com/S95qV0Uz
https://pastebin.com/KkbjxT3u
https://pastebin.com/KtDaV8W6
https://pastebin.com/StWuCs6Y
https://pastebin.com/P1C46HUd
https://pastebin.com/jQVY68MF
https://pastebin.com/pZWdw2nr
https://pastebin.com/hWvq20CX
https://pastebin.com/6gYgutXU
https://pastebin.com/U4bchZ6m
https://pastebin.com/gfaWG0r6
https://pastebin.com/e9Y0r65R
https://pastebin.com/TVxKvWAq
https://pastebin.com/XQf3dgCp
https://pastebin.com/DpcrwpV5
https://pastebin.com/ebTwvrA8
https://pastebin.com/9AVaZHpP
https://pastebin.com/0XNziuPN
https://pastebin.com/5pX3w9Qz
https://pastebin.com/LxBeC4wT
https://pastebin.com/Ts4YH8YT
https://pastebin.com/gtTcZ7WL
https://pastebin.com/9hCAm4pk
https://pastebin.com/ivQAv3W6
https://pastebin.com/kQkScYnm
https://pastebin.com/Pggf2AJf
https://pastebin.com/93YQXuS2
https://pastebin.com/8bT7ebcm
https://pastebin.com/qhwZL1PA
https://pastebin.com/1j9Dfhf3
https://pastebin.com/yr8G62Eb
https://pastebin.com/i6keBveX
https://pastebin.com/8cShExkL
https://pastebin.com/uRpXa4D8
https://pastebin.com/GCWXMvkV
https://pastebin.com/5r8kkafj
https://pastebin.com/gZqmkKFY
https://pastebin.com/uzX4RXb8
https://pastebin.com/NX85Yeya
https://pastebin.com/0bnHmnn1
https://pastebin.com/4TEuJiyp
https://pastebin.com/19ECBLgF
https://pastebin.com/Q6sXfyLu
https://pastebin.com/J3wkaGhA
https://pastebin.com/MV4f7R1P
https://pastebin.com/4dtFguZU
https://pastebin.com/adNJrURC
https://pastebin.com/aTtEKzis
https://pastebin.com/PQrg1GWd
https://pastebin.com/gKTKwvqf
https://pastebin.com/uuYPPvaA
https://pastebin.com/j4Ls35Kc
https://pastebin.com/E0Ukv7Aj
https://pastebin.com/f6k4t5u2
https://pastebin.com/Q1DXAxWt
https://pastebin.com/tjfUhr1N
https://pastebin.com/uU5iHdYy
https://pastebin.com/cRR3KhUL
https://pastebin.com/nCiKrKtB
https://pastebin.com/dSHVsFy0
https://pastebin.com/2XAKUkn6
https://pastebin.com/Jppiihgb
https://pastebin.com/h3yaiqzz
https://pastebin.com/2JWDAsub
https://pastebin.com/tmPvc6uP
https://pastebin.com/FUnWQQwV
https://pastebin.com/Gp2UtxKN
https://pastebin.com/h95wHCfg
https://pastebin.com/fsCpD2Z8
https://pastebin.com/zJr9HCpS
https://pastebin.com/0PvFpNur
https://pastebin.com/VJyawuD5
https://pastebin.com/ba848eTS
https://pastebin.com/RACk8XWR
https://pastebin.com/0hwQHBAn
https://pastebin.com/sm0bEg5B
https://pastebin.com/KQVK1Ux4
https://pastebin.com/RzSuT9wU
https://pastebin.com/9zX0dafY
https://pastebin.com/vkitG9Jw
https://pastebin.com/BcsGM1ug
https://pastebin.com/mj2Ugnd4
https://pastebin.com/qUZmarS4
https://pastebin.com/db2sFPQt
https://pastebin.com/0Quw6jc1
https://pastebin.com/bPBJjv7K
https://pastebin.com/85VrwdEB
https://pastebin.com/ShwKYbhk
https://pastebin.com/n90Pae7n
https://pastebin.com/SKapsvXq
https://pastebin.com/jchhgXp5
https://pastebin.com/PKj7m3Lc
https://pastebin.com/uGYXHZAJ
https://pastebin.com/1Q4Smy2t
https://pastebin.com/sm9Guq40
https://pastebin.com/mwR9cXpE
https://pastebin.com/yJwCfxSP
https://pastebin.com/CBCVt6RG
https://pastebin.com/g5h9tUZ3
https://pastebin.com/LCgxSJzf
https://pastebin.com/VTQUWA8N
https://pastebin.com/qjDyTgv1
https://pastebin.com/6dFvv5Fp
https://pastebin.com/JaPKffGq
https://pastebin.com/92nW5dg4
https://pastebin.com/gH5hqPqi
https://pastebin.com/s6MKA67r
https://pastebin.com/8wtXs6PD
https://pastebin.com/jMc5xGpz
https://pastebin.com/EzDZD9aa
https://pastebin.com/p0AmhcjH
https://pastebin.com/QS1iAeCm
https://pastebin.com/X7QsHtdw
https://pastebin.com/0b4Y7fQY
https://pastebin.com/Y0sEGX9d
https://pastebin.com/6Vhw047x
https://pastebin.com/m7VxYsWv
https://pastebin.com/rDK9zsnw
https://pastebin.com/Z37U43eV
https://pastebin.com/80dR4QFi
https://pastebin.com/uB10GAXp
https://pastebin.com/8ts4FMmm
https://pastebin.com/y6TXLitK
https://pastebin.com/xPQ81CN6
https://pastebin.com/3hqhfZua
https://pastebin.com/aamUFtH2
https://pastebin.com/Lgh7nvP5
https://pastebin.com/x7Wg8naG
https://pastebin.com/aW7iLfRT
https://pastebin.com/Y7sTZ6Vb
https://pastebin.com/ZGPVkCwr
https://pastebin.com/jbcbQ9SV
https://pastebin.com/dxRWmQfk
https://pastebin.com/s02yGqxb
https://pastebin.com/1hB6AGkw
https://pastebin.com/hbcaCTpF
https://pastebin.com/pa2dcEuK
https://pastebin.com/2DV3gLRS
https://pastebin.com/wXieGb5z
https://pastebin.com/QDuKzm2f
https://pastebin.com/KzNj7grC
https://pastebin.com/89UWyNL0
https://pastebin.com/DGiiVVM6
https://pastebin.com/p1c3MvZT
https://pastebin.com/ecWTPGqn
https://pastebin.com/x6xBU9WV
https://pastebin.com/6yDL7fCa
https://pastebin.com/zjV0dLrr
https://pastebin.com/5d6FK30q
https://pastebin.com/EAxBa9Rm
https://pastebin.com/fwUrdJ4g
https://pastebin.com/VfBQU7Wb
https://pastebin.com/qwmVxX40
https://pastebin.com/71mdDTym
https://pastebin.com/qKrKiQbA
https://pastebin.com/z4WQJNkG
https://pastebin.com/1cdSJ8z5
https://pastebin.com/RtJXd9SW
https://pastebin.com/awkWi3s0
https://pastebin.com/xYkp2v8B
https://pastebin.com/rVehqExV
https://pastebin.com/Ws1Rd9bX
https://pastebin.com/8rXf1587
https://pastebin.com/9rfqNX50
https://pastebin.com/xVW5XQ2C
https://pastebin.com/dtHPSD6d
https://pastebin.com/mZD42AGm
https://pastebin.com/mXgU9d1f
https://pastebin.com/wTcD42mF
https://pastebin.com/KWqcG3if
https://pastebin.com/36p4EnHw
https://pastebin.com/TgjikqB4
https://pastebin.com/JHfBjWDg
https://pastebin.com/YhNpnZ3J
https://pastebin.com/TfC4Wa6F
https://pastebin.com/AFbL5S7E
https://pastebin.com/tVynY480
https://pastebin.com/bCevMY23
https://pastebin.com/7D3RhcvW
https://pastebin.com/sx5zBNRT
https://pastebin.com/6BEu5JtE
https://pastebin.com/LLdT59KZ
https://pastebin.com/qVaqen3E
https://pastebin.com/teqJ5Faw
https://pastebin.com/iYGt5cfB
https://pastebin.com/PvxNyjNd
https://pastebin.com/7LrBhZtK
https://pastebin.com/Uw6gnw98
https://pastebin.com/EiurXwH0
https://pastebin.com/jqRuvh0m
https://pastebin.com/geBdNDtb
https://pastebin.com/zQKdEZza
https://pastebin.com/QYFjE08P
https://pastebin.com/TBLAMRQF
https://pastebin.com/CzT8XHXy
https://pastebin.com/MBYQTUi5
https://pastebin.com/PCfCFgCR

继续阅读 »

问题现象
Swiper如何实现卡片堆叠样式:

上下堆叠:Swiper内容像卡片堆叠,在底部留部分空间显示下一页的内容,上下滑实现卡片切换。
左右堆叠:Swiper内容像卡片堆叠,在右侧留部分空间显示下一页的内容,左右滑实现卡片切换。
背景知识
Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件,当设置了多个子组件后,可以对这些子组件进行轮播显示。
Stack是堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件。
gesture可以为组件绑定手势方法进行相应处理,如滑动手势事件PanGesture。
animateTo指定由于闭包代码导致的状态变化插入过渡动效。
解决方案
上下堆叠实现。
自定义实现卡片堆叠的组件:使用Stack组件堆叠需要展示的图片,设置最上面的图片向上偏移部分距离,露出下一张图片的底部。为Stack绑定上下滑动的手势处理,实现切换图片逻辑,同时使用animateTo接口设置图片切换动画。

export class SwiperData {
imageSrc: Resource;

constructor(imageSrc: Resource) {
this.imageSrc = imageSrc;
}
}

@Component
export struct SwiperStackComponent {
@Link currentIndex: number;
@Prop swiperData: SwiperData[];
private halfCount: number = Math.floor(3 / 2);
private automaticSlidingDuration: number = 300;

aboutToAppear(): void {
this.currentIndex = 0;
}

// 修改堆叠方向系数计算
getImgCoefficients(index: number): number {
const coefficient = this.currentIndex - index;
const tempCoefficient = Math.abs(coefficient);
if (tempCoefficient <= this.halfCount) {
return coefficient;
}
const dataLength = this.swiperData.length;
let tempOffset = dataLength - tempCoefficient;
if (tempOffset <= this.halfCount) {
return coefficient > 0 ? -tempOffset : tempOffset;
}
return 0;
}

// 修改堆叠方向偏移量计算
getOffSet(index: number): number {
let offsetIndex = this.getImgCoefficients(index);
const tempOffset = Math.abs(offsetIndex);
let offset = 0;
if (tempOffset === 1) {
if (offsetIndex === 1) {
offsetIndex = -1;
}
offset = 50 * offsetIndex;
}
return -offset;
}

startAnimation(isLeft: boolean, duration: number): void {
this.getUIContext().animateTo({ duration: duration, }, () => {
const dataLength: number = this.swiperData.length;
const tempIndex: number = isLeft ? this.currentIndex + 1 : this.currentIndex - 1 + dataLength;
this.currentIndex = tempIndex % dataLength;
});
}

build() {
Stack() {
ForEach(this.swiperData, (item: SwiperData, index: number) => {
Stack({ alignContent: Alignment.Bottom }) {
Image(item.imageSrc)
.objectFit(ImageFit.Cover)
.width('100%')
.height('100%')
.borderRadius(8);
}
.offset({ x: 0, y: this.getOffSet(index) })
.shadow(ShadowStyle.OUTER_DEFAULT_SM)
.backgroundColor(Color.White)
.borderRadius(8)
.blur(index !== this.currentIndex ? 12 : 0)
// 通过animateTo实现动画并且同时改变currentIndex数据中间值来判断组件zIndex实现切换动画
.zIndex(index !== this.currentIndex && this.getImgCoefficients(index) === 0 ?
0 : 2 - Math.abs(this.getImgCoefficients(index)))
.width(310)
.height(index !== this.currentIndex ? 130 : 180);
});
}
.height(200)
.width('100%')
.gesture(
PanGesture({ direction: PanDirection.Vertical })
.onActionStart((event: GestureEvent) => {
this.startAnimation(event.offsetY < 0, this.automaticSlidingDuration);
})
)
.alignContent(Alignment.Center)
.padding({ left: 12, right: 12, top: 12 });
}
}
在页面中直接使用上面封装好的SwiperStackComponent即可,示例如下:传给SwiperStackComponent要堆叠的图片。
https://pastebin.com/m1LSXzRK
https://pastebin.com/cFaDz2JM
https://pastebin.com/94NrGTMh
https://pastebin.com/Yjx17LeK
https://pastebin.com/LqsPJ96h
https://pastebin.com/DPDgXMvK
https://pastebin.com/hSjsmXQA
https://pastebin.com/uKP1SMUS
https://pastebin.com/NzZpsC1T
https://pastebin.com/YYRMvbNK
https://pastebin.com/yMGLZhE6
https://pastebin.com/S95qV0Uz
https://pastebin.com/KkbjxT3u
https://pastebin.com/KtDaV8W6
https://pastebin.com/StWuCs6Y
https://pastebin.com/P1C46HUd
https://pastebin.com/jQVY68MF
https://pastebin.com/pZWdw2nr
https://pastebin.com/hWvq20CX
https://pastebin.com/6gYgutXU
https://pastebin.com/U4bchZ6m
https://pastebin.com/gfaWG0r6
https://pastebin.com/e9Y0r65R
https://pastebin.com/TVxKvWAq
https://pastebin.com/XQf3dgCp
https://pastebin.com/DpcrwpV5
https://pastebin.com/ebTwvrA8
https://pastebin.com/9AVaZHpP
https://pastebin.com/0XNziuPN
https://pastebin.com/5pX3w9Qz
https://pastebin.com/LxBeC4wT
https://pastebin.com/Ts4YH8YT
https://pastebin.com/gtTcZ7WL
https://pastebin.com/9hCAm4pk
https://pastebin.com/ivQAv3W6
https://pastebin.com/kQkScYnm
https://pastebin.com/Pggf2AJf
https://pastebin.com/93YQXuS2
https://pastebin.com/8bT7ebcm
https://pastebin.com/qhwZL1PA
https://pastebin.com/1j9Dfhf3
https://pastebin.com/yr8G62Eb
https://pastebin.com/i6keBveX
https://pastebin.com/8cShExkL
https://pastebin.com/uRpXa4D8
https://pastebin.com/GCWXMvkV
https://pastebin.com/5r8kkafj
https://pastebin.com/gZqmkKFY
https://pastebin.com/uzX4RXb8
https://pastebin.com/NX85Yeya
https://pastebin.com/0bnHmnn1
https://pastebin.com/4TEuJiyp
https://pastebin.com/19ECBLgF
https://pastebin.com/Q6sXfyLu
https://pastebin.com/J3wkaGhA
https://pastebin.com/MV4f7R1P
https://pastebin.com/4dtFguZU
https://pastebin.com/adNJrURC
https://pastebin.com/aTtEKzis
https://pastebin.com/PQrg1GWd
https://pastebin.com/gKTKwvqf
https://pastebin.com/uuYPPvaA
https://pastebin.com/j4Ls35Kc
https://pastebin.com/E0Ukv7Aj
https://pastebin.com/f6k4t5u2
https://pastebin.com/Q1DXAxWt
https://pastebin.com/tjfUhr1N
https://pastebin.com/uU5iHdYy
https://pastebin.com/cRR3KhUL
https://pastebin.com/nCiKrKtB
https://pastebin.com/dSHVsFy0
https://pastebin.com/2XAKUkn6
https://pastebin.com/Jppiihgb
https://pastebin.com/h3yaiqzz
https://pastebin.com/2JWDAsub
https://pastebin.com/tmPvc6uP
https://pastebin.com/FUnWQQwV
https://pastebin.com/Gp2UtxKN
https://pastebin.com/h95wHCfg
https://pastebin.com/fsCpD2Z8
https://pastebin.com/zJr9HCpS
https://pastebin.com/0PvFpNur
https://pastebin.com/VJyawuD5
https://pastebin.com/ba848eTS
https://pastebin.com/RACk8XWR
https://pastebin.com/0hwQHBAn
https://pastebin.com/sm0bEg5B
https://pastebin.com/KQVK1Ux4
https://pastebin.com/RzSuT9wU
https://pastebin.com/9zX0dafY
https://pastebin.com/vkitG9Jw
https://pastebin.com/BcsGM1ug
https://pastebin.com/mj2Ugnd4
https://pastebin.com/qUZmarS4
https://pastebin.com/db2sFPQt
https://pastebin.com/0Quw6jc1
https://pastebin.com/bPBJjv7K
https://pastebin.com/85VrwdEB
https://pastebin.com/ShwKYbhk
https://pastebin.com/n90Pae7n
https://pastebin.com/SKapsvXq
https://pastebin.com/jchhgXp5
https://pastebin.com/PKj7m3Lc
https://pastebin.com/uGYXHZAJ
https://pastebin.com/1Q4Smy2t
https://pastebin.com/sm9Guq40
https://pastebin.com/mwR9cXpE
https://pastebin.com/yJwCfxSP
https://pastebin.com/CBCVt6RG
https://pastebin.com/g5h9tUZ3
https://pastebin.com/LCgxSJzf
https://pastebin.com/VTQUWA8N
https://pastebin.com/qjDyTgv1
https://pastebin.com/6dFvv5Fp
https://pastebin.com/JaPKffGq
https://pastebin.com/92nW5dg4
https://pastebin.com/gH5hqPqi
https://pastebin.com/s6MKA67r
https://pastebin.com/8wtXs6PD
https://pastebin.com/jMc5xGpz
https://pastebin.com/EzDZD9aa
https://pastebin.com/p0AmhcjH
https://pastebin.com/QS1iAeCm
https://pastebin.com/X7QsHtdw
https://pastebin.com/0b4Y7fQY
https://pastebin.com/Y0sEGX9d
https://pastebin.com/6Vhw047x
https://pastebin.com/m7VxYsWv
https://pastebin.com/rDK9zsnw
https://pastebin.com/Z37U43eV
https://pastebin.com/80dR4QFi
https://pastebin.com/uB10GAXp
https://pastebin.com/8ts4FMmm
https://pastebin.com/y6TXLitK
https://pastebin.com/xPQ81CN6
https://pastebin.com/3hqhfZua
https://pastebin.com/aamUFtH2
https://pastebin.com/Lgh7nvP5
https://pastebin.com/x7Wg8naG
https://pastebin.com/aW7iLfRT
https://pastebin.com/Y7sTZ6Vb
https://pastebin.com/ZGPVkCwr
https://pastebin.com/jbcbQ9SV
https://pastebin.com/dxRWmQfk
https://pastebin.com/s02yGqxb
https://pastebin.com/1hB6AGkw
https://pastebin.com/hbcaCTpF
https://pastebin.com/pa2dcEuK
https://pastebin.com/2DV3gLRS
https://pastebin.com/wXieGb5z
https://pastebin.com/QDuKzm2f
https://pastebin.com/KzNj7grC
https://pastebin.com/89UWyNL0
https://pastebin.com/DGiiVVM6
https://pastebin.com/p1c3MvZT
https://pastebin.com/ecWTPGqn
https://pastebin.com/x6xBU9WV
https://pastebin.com/6yDL7fCa
https://pastebin.com/zjV0dLrr
https://pastebin.com/5d6FK30q
https://pastebin.com/EAxBa9Rm
https://pastebin.com/fwUrdJ4g
https://pastebin.com/VfBQU7Wb
https://pastebin.com/qwmVxX40
https://pastebin.com/71mdDTym
https://pastebin.com/qKrKiQbA
https://pastebin.com/z4WQJNkG
https://pastebin.com/1cdSJ8z5
https://pastebin.com/RtJXd9SW
https://pastebin.com/awkWi3s0
https://pastebin.com/xYkp2v8B
https://pastebin.com/rVehqExV
https://pastebin.com/Ws1Rd9bX
https://pastebin.com/8rXf1587
https://pastebin.com/9rfqNX50
https://pastebin.com/xVW5XQ2C
https://pastebin.com/dtHPSD6d
https://pastebin.com/mZD42AGm
https://pastebin.com/mXgU9d1f
https://pastebin.com/wTcD42mF
https://pastebin.com/KWqcG3if
https://pastebin.com/36p4EnHw
https://pastebin.com/TgjikqB4
https://pastebin.com/JHfBjWDg
https://pastebin.com/YhNpnZ3J
https://pastebin.com/TfC4Wa6F
https://pastebin.com/AFbL5S7E
https://pastebin.com/tVynY480
https://pastebin.com/bCevMY23
https://pastebin.com/7D3RhcvW
https://pastebin.com/sx5zBNRT
https://pastebin.com/6BEu5JtE
https://pastebin.com/LLdT59KZ
https://pastebin.com/qVaqen3E
https://pastebin.com/teqJ5Faw
https://pastebin.com/iYGt5cfB
https://pastebin.com/PvxNyjNd
https://pastebin.com/7LrBhZtK
https://pastebin.com/Uw6gnw98
https://pastebin.com/EiurXwH0
https://pastebin.com/jqRuvh0m
https://pastebin.com/geBdNDtb
https://pastebin.com/zQKdEZza
https://pastebin.com/QYFjE08P
https://pastebin.com/TBLAMRQF
https://pastebin.com/CzT8XHXy
https://pastebin.com/MBYQTUi5
https://pastebin.com/PCfCFgCR

收起阅读 »

关于 HBuilder X 更新 5.14版本 后无法编译【uni-app (x)项目编译插件正在启动中】问题的解决方案

HBuilderX升级 HBuilder X

更新后如果编译代码时出现下边的这种情况


先关闭HBuilder X,退出运行
然后点击桌面HBuilder X右键查看属性=》兼容性=》兼容模式 查看是否勾选,如下图

如果发现勾选了 取消勾选 然后点击右下角的应用;重启HBuilder X 查看问题是否已经解决;

根据官方说明,HBuilder X 5.11 及以上版本内置的 Node.js 已升级至 22.22.2,不再支持 Windows 8.1 及以下系统,因此要求运行环境必须为 Windows 8.1 或更高版本。
需要特别注意的是:如果您的电脑系统本身高于 Windows 8.1,但为 HBuilder X 勾选了“兼容模式”(例如兼容 Windows 7 或 8),HBuilder X 在运行时会强制模拟该低版本环境。这会导致内置的 Node.js 无法正常运行,进而引发编译卡死问题,具体表现为一直提示【uni-app (x)项目编译插件正在启动中】或报错“当前设备 Windows 版本低”。请取消兼容模式以确保正常运行。

继续阅读 »

更新后如果编译代码时出现下边的这种情况


先关闭HBuilder X,退出运行
然后点击桌面HBuilder X右键查看属性=》兼容性=》兼容模式 查看是否勾选,如下图

如果发现勾选了 取消勾选 然后点击右下角的应用;重启HBuilder X 查看问题是否已经解决;

根据官方说明,HBuilder X 5.11 及以上版本内置的 Node.js 已升级至 22.22.2,不再支持 Windows 8.1 及以下系统,因此要求运行环境必须为 Windows 8.1 或更高版本。
需要特别注意的是:如果您的电脑系统本身高于 Windows 8.1,但为 HBuilder X 勾选了“兼容模式”(例如兼容 Windows 7 或 8),HBuilder X 在运行时会强制模拟该低版本环境。这会导致内置的 Node.js 无法正常运行,进而引发编译卡死问题,具体表现为一直提示【uni-app (x)项目编译插件正在启动中】或报错“当前设备 Windows 版本低”。请取消兼容模式以确保正常运行。

收起阅读 »

uni-agent宇宙第一好用

服务器超级稳定,根本不会中断。
ai卡了中断了也没事,根本不会白白烧你的token。

服务器超级稳定,根本不会中断。
ai卡了中断了也没事,根本不会白白烧你的token。

通过自定义弹窗实现自定义样式的Toast

toast

问题现象
HarmonyOS的Toast接口,不支持设定圆角样式。如何实现类似其他平台的Toast样式?

效果预览
点击放大

背景知识
不依赖UI组件的全局自定义弹出框(openCustomDialog),适用于在相对应用复杂的场景来实现自定义弹出框,相较于CustomDialogController优势点在于页面解耦,支持动态刷新。
ArkUI提供轻量的UI元素复用机制@Builder,其内部UI结构固定,仅与使用方进行数据传递。可将重复使用的UI元素抽象成函数,在build函数中调用。
Text组件可以自定义展示文本框的UI样式,包括边框圆角、内边距等。
setTimeout接口支持设置一个定时器,该定时器在定时器到期后执行一个函数。
解决方案
如果需要实现类似其他平台的Toast样式,可借助自定义弹窗实现。

主要实现思路为,借助Text组件自定义类似其他平台Toast的UI样式,并封装为@Builder构建函数,将该函数传入ComponentContent创建弹窗对象,通过getUIContext开启该弹窗对象,开启后执行setTimeout,等待指定的时间后,执行关闭弹窗对象。详细步骤如下:

配置ToastContent组件属性并封装为@Builder构建函数。
@Component
struct ToastContent {
public text: string = '';
public clickText: string = '';
public clickListener = () => {
};
private textList: string[] = [];

aboutToAppear(): void {
if (this.clickText.length > 0) {
this.textList = this.text.split(this.clickText);
}
}

build() {
Column() {
if (this.clickText === '') {
Text(this.text).toastText();
} else {
Text() {
ForEach(this.textList, (item: string, num: number) => {
Span(item);
if (num < this.textList.length - 1) {
Span(this.clickText).fontColor(Color.Yellow);
}
});
}.onClick(this.clickListener).toastText();
}
}
.borderRadius(5)
.backgroundColor(Color.Black)
.padding(10)
.justifyContent(FlexAlign.SpaceBetween)
.margin({ left: '5%', right: '5%' });
}
}

// 封装Toast的@Builder方法
@Builder
function buildText(params: Params) {
ToastContent({ text: params.text, clickText: params.clickText, clickListener: params.clickListener });
}

// 封装公共样式
@Extend(Text)
function toastText() {
.fontSize(20)
.fontColor(Color.White);
}
创建Toast类,并创建构造方法与Toast实例方法。
/**

  • 封装全局蓝色浮动提示,支持点击
    */
    export class Toast {
    private toastParams: Params;

    constructor(text: string, time: number = 2000) {
    this.toastParams = new Params(text, time);
    }

    setClick(clickText: string, clickListener: () => void): Toast {
    this.toastParams.setClick(clickText, clickListener);
    return this;
    };

    async show() {
    let uiContext = AppStorage.get('currentUIContext') as UIContext;
    let click = this.toastParams.clickListener;
    let contentNode = new ComponentContent(uiContext, wrapBuilder(buildText), this.toastParams);
    uiContext.getPromptAction().openCustomDialog(contentNode, {
    showInSubWindow: this.toastParams.clickText === '' ? false : true,
    isModal: false,
    offset: { dx: 0, dy: '10%' }
    }).then(() => {
    setTimeout(() => {
    uiContext.getPromptAction().closeCustomDialog(contentNode);
    }, this.toastParams.time);
    });
    this.toastParams.clickListener = () => {
    click();
    uiContext.getPromptAction().closeCustomDialog(contentNode);
    };
    };
    }
    创建并弹出Toast,并且可以在Toast的setClick回调方法内实现点击Toast后的逻辑,如页面跳转。
    new Toast('点击Toast后屏幕将退出横屏,进入到竖屏状态', 3000).setClick('关闭自动添加', () => {
    this.windowClass.setPreferredOrientation(window.Orientation.PORTRAIT);
    this.pathStack.pushPathByName('DetailPage', null);
    }).show();
    完整示例参考如下:

Index.ets。
import { common } from '@kit.AbilityKit';
import { Toast } from './ToastContent';
import { window } from '@kit.ArkUI';

@Entry
@Component
struct Index {
pathStack: NavPathStack = new NavPathStack();
private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
private windowClass = (this.context as common.UIAbilityContext).windowStage.getMainWindowSync();

setOrientation(orientation: number) {
this.windowClass.setPreferredOrientation(orientation).then(() => {
}).catch(() => {
});
}

async aboutToAppear(): Promise<void> {
await this.windowClass.setPreferredOrientation(window.Orientation.LANDSCAPE);
AppStorage.setOrCreate('currentUIContext', this.getUIContext());
}

build() {
Navigation(this.pathStack) {
RelativeContainer() {
Column() {
Text('我的记录')
.fontSize(50)
.width('100%')
.textAlign(TextAlign.Center)
.fontWeight(FontWeight.Bold)
Button('保存')
.onClick(() => {
new Toast('点击Toast后屏幕将退出横屏,进入到竖屏状态', 3000).setClick('关闭自动添加', () => {
this.windowClass.setPreferredOrientation(window.Orientation.PORTRAIT);
this.pathStack.pushPathByName('DetailPage', null);
}).show();
})
.backgroundColor(Color.Blue)
.fontColor(Color.White)
}
.alignRules({
center: { anchor: 'container', align: VerticalAlign.Center },
middle: { anchor: 'container', align: HorizontalAlign.Center }
})
}
.width('100%')
}
.mode(NavigationMode.Stack)
.height('100%')
.width('100%')
.height('100%')
.hideTitleBar(true)
}
}
https://pastebin.com/nwKPmmLF
https://pastebin.com/DwxhFWzG
https://pastebin.com/Jtg4RcM0
https://pastebin.com/7BvUaREN
https://pastebin.com/LGejrMwK
https://pastebin.com/V0qPwT9h
https://pastebin.com/tQzSevWd
https://pastebin.com/qncxPzug
https://pastebin.com/QNF651jK
https://pastebin.com/t3ZuR3CR
https://pastebin.com/H2PDqsEW
https://pastebin.com/Z7Wc9nic
https://pastebin.com/ECKtGAaJ
https://pastebin.com/EwXrd6qT
https://pastebin.com/HgYXSSrk
https://pastebin.com/uK3jyvu5
https://pastebin.com/vHzZdj9J
https://pastebin.com/sAMfi1Pz
https://pastebin.com/kVhLAgHT
https://pastebin.com/nkBEKJz4
https://pastebin.com/GrAvgxVg
https://pastebin.com/Umn0eUXt
https://pastebin.com/ut2dn5iH
https://pastebin.com/NgPFqHpy
https://pastebin.com/7e62WCL1
https://pastebin.com/wDM3tM66
https://pastebin.com/pwUhGKDv
https://pastebin.com/YvwB4xBj
https://pastebin.com/mzAxKZES
https://pastebin.com/NpnmdKf1
https://pastebin.com/C1FBURkc
https://pastebin.com/y0KsuZgB
https://pastebin.com/g7nDScSb
https://pastebin.com/7mDcGwtR
https://pastebin.com/3xV61sHt
https://pastebin.com/iQakPefn
https://pastebin.com/0CWW1Xta
https://pastebin.com/90zmcK10
https://pastebin.com/6CH0bCbh
https://pastebin.com/b6zdrzYw
https://pastebin.com/414XDcCa
https://pastebin.com/Mf0rTkWN
https://pastebin.com/gT4iprCN
https://pastebin.com/qeDfQegQ
https://pastebin.com/1e4vRx0y
https://pastebin.com/kGZUceQg
https://pastebin.com/9ustBt9n
https://pastebin.com/N4kwVZns
https://pastebin.com/qkBjMdA7
https://pastebin.com/w6XX4ciC
https://pastebin.com/54cBU8ze
https://pastebin.com/1mnAuUDZ
https://pastebin.com/VsgcjvTE
https://pastebin.com/wP31PyVb
https://pastebin.com/XPBk0zv8
https://pastebin.com/QRE1qguw
https://pastebin.com/iQpMss7p
https://pastebin.com/kKCXTdF9
https://pastebin.com/EwBniqXp
https://pastebin.com/A7LEwGq8
https://pastebin.com/XtXPS80z
https://pastebin.com/ghHSfqUZ
https://pastebin.com/pEH2u4Mj
https://pastebin.com/GiE2VShc
https://pastebin.com/NmfaPSX3
https://pastebin.com/fBfZs9d9
https://pastebin.com/CcHrZqng
https://pastebin.com/DcALJUNr
https://pastebin.com/hvGqEU4c
https://pastebin.com/HSf8h6BJ
https://pastebin.com/SCNykcCB
https://pastebin.com/RAnMDpBN
https://pastebin.com/0bbYFAG1
https://pastebin.com/6LjMvDuD
https://pastebin.com/u8dUE6wL
https://pastebin.com/8jVE6YLs
https://pastebin.com/xv6kzZyF
https://pastebin.com/L5WMJb5t
https://pastebin.com/sbe7FFD4
https://pastebin.com/aU4YUWDr
https://pastebin.com/3Z1YFmFW
https://pastebin.com/XvqqwSxq
https://pastebin.com/mJX0WRsc
https://pastebin.com/SGkjCXRB
https://pastebin.com/zv8zAxMd
https://pastebin.com/4bRTkCkm
https://pastebin.com/5s9JtWfS
https://pastebin.com/4VVU6Cxg
https://pastebin.com/TewUpi9F
https://pastebin.com/LKZwmBSi
https://pastebin.com/mu27p7w7
https://pastebin.com/JtiF4S2E
https://pastebin.com/xpeq692Q
https://pastebin.com/ypMqWqLC
https://pastebin.com/NrF7S04d
https://pastebin.com/j3riqEAh
https://pastebin.com/6iWCgpuJ
https://pastebin.com/s6KVgTgr
https://pastebin.com/1760APmp
https://pastebin.com/T6LTqjHK
https://pastebin.com/ttGrUAd7
https://pastebin.com/0PQTrPXK
https://pastebin.com/ZtmvtT35
https://pastebin.com/8TjBbUcR
https://pastebin.com/bYCv91Zb
https://pastebin.com/fpZrFF5w
https://pastebin.com/nrXWRsTd
https://pastebin.com/p4UWmpHz
https://pastebin.com/FUkFJhvg
https://pastebin.com/iBPFDUJ3
https://pastebin.com/ET0iD33D
https://pastebin.com/iwPwXsca
https://pastebin.com/8mmzg8p6
https://pastebin.com/8342WqK5
https://pastebin.com/pwYYMgbq
https://pastebin.com/cRgWPHH1
https://pastebin.com/rzyEfV9J
https://pastebin.com/kkAZWims
https://pastebin.com/fQn41cHZ
https://pastebin.com/fJ8vJ8be
https://pastebin.com/SzcAwdbu
https://pastebin.com/pCWkDayj
https://pastebin.com/L9pkse7j
https://pastebin.com/FxKwn5ng
https://pastebin.com/vFCLDqaV
https://pastebin.com/NRnYdnNH
https://pastebin.com/2ER3wSYv
https://pastebin.com/Q3YCRgG6
https://pastebin.com/YFaNTgNN
https://pastebin.com/9n9shZLu
https://pastebin.com/PBbWWUgX
https://pastebin.com/2R5pKJLh
https://pastebin.com/k2yY9ULe
https://pastebin.com/CJph4hrD
https://pastebin.com/6EwGM5CL
https://pastebin.com/DELYFAem
https://pastebin.com/Ggv8dLaa
https://pastebin.com/vM0r7xLq
https://pastebin.com/nbPGG85J
https://pastebin.com/ZVvYnFre
https://pastebin.com/LjJ9AwB9
https://pastebin.com/6hq2KXML
https://pastebin.com/pugZBcNi
https://pastebin.com/pmPvrW9q
https://pastebin.com/fhueU8S6
https://pastebin.com/kQazy17K
https://pastebin.com/VfTbduXf
https://pastebin.com/pXjYSwfd
https://pastebin.com/hUUnksFb
https://pastebin.com/AGnzrkpi
https://pastebin.com/s9jXdQ2e
https://pastebin.com/tkNdPSWm
https://pastebin.com/buGSwi0Q
https://pastebin.com/iqyy4wdi
https://pastebin.com/RiGF4W7H
https://pastebin.com/pxNKyFmV
https://pastebin.com/WwXY1ryk
https://pastebin.com/dNHDcvEE
https://pastebin.com/dstyKVbw
https://pastebin.com/recG0Tg4
https://pastebin.com/Bu8wxzkr
https://pastebin.com/FuZgbtXf
https://pastebin.com/56Ze2VPk
https://pastebin.com/qYG0243R
https://pastebin.com/AZ8iKJWf
https://pastebin.com/hNwShQks
https://pastebin.com/P8iaLhZF
https://pastebin.com/Dm05MGfE
https://pastebin.com/zamDMZgY
https://pastebin.com/7iyftr84
https://pastebin.com/0NssQJ78
https://pastebin.com/kdxr8s0Z
https://pastebin.com/awjDqW6h
https://pastebin.com/GWgZwDB6
https://pastebin.com/riTJWSts
https://pastebin.com/6dmkXAhN
https://pastebin.com/XZLHY1iX
https://pastebin.com/my97aREv
https://pastebin.com/H09exPyA
https://pastebin.com/Jjw8R1hF
https://pastebin.com/hm4qKthR
https://pastebin.com/G9uVUmcV
https://pastebin.com/Z4ZGXCbg
https://pastebin.com/zttcE8aT
https://pastebin.com/ieWfnuEy
https://pastebin.com/EnmdLmjw
https://pastebin.com/q7Nj4JEA
https://pastebin.com/cCnrQ8ft
https://pastebin.com/zt89NyCc
https://pastebin.com/cV5KJRUN
https://pastebin.com/e4pzjDm2
https://pastebin.com/rGb2fsmp
https://pastebin.com/q8ikYfF0
https://pastebin.com/y9e0KRNW
https://pastebin.com/r3qCSEu5
https://pastebin.com/H37KdV5m
https://pastebin.com/SREPy9Lr
https://pastebin.com/25JSeDYG
https://pastebin.com/j2a0BJdf
https://pastebin.com/pGbzGr3L
https://pastebin.com/1WJkKeyt
https://pastebin.com/DPDXbJ9L
https://pastebin.com/4gGEvFSt
https://pastebin.com/L0HCPVB0
https://pastebin.com/0eQjwvaY
https://pastebin.com/rzSNBFS8
https://pastebin.com/NWi9hui6
https://pastebin.com/xPjT5mxu
https://pastebin.com/e5neTg7h
https://pastebin.com/TZnwGHx4
https://pastebin.com/Tb98uyxA
https://pastebin.com/DPcVtW6C
https://pastebin.com/zZ7bNVBg
https://pastebin.com/yVQXZ1T1

继续阅读 »

问题现象
HarmonyOS的Toast接口,不支持设定圆角样式。如何实现类似其他平台的Toast样式?

效果预览
点击放大

背景知识
不依赖UI组件的全局自定义弹出框(openCustomDialog),适用于在相对应用复杂的场景来实现自定义弹出框,相较于CustomDialogController优势点在于页面解耦,支持动态刷新。
ArkUI提供轻量的UI元素复用机制@Builder,其内部UI结构固定,仅与使用方进行数据传递。可将重复使用的UI元素抽象成函数,在build函数中调用。
Text组件可以自定义展示文本框的UI样式,包括边框圆角、内边距等。
setTimeout接口支持设置一个定时器,该定时器在定时器到期后执行一个函数。
解决方案
如果需要实现类似其他平台的Toast样式,可借助自定义弹窗实现。

主要实现思路为,借助Text组件自定义类似其他平台Toast的UI样式,并封装为@Builder构建函数,将该函数传入ComponentContent创建弹窗对象,通过getUIContext开启该弹窗对象,开启后执行setTimeout,等待指定的时间后,执行关闭弹窗对象。详细步骤如下:

配置ToastContent组件属性并封装为@Builder构建函数。
@Component
struct ToastContent {
public text: string = '';
public clickText: string = '';
public clickListener = () => {
};
private textList: string[] = [];

aboutToAppear(): void {
if (this.clickText.length > 0) {
this.textList = this.text.split(this.clickText);
}
}

build() {
Column() {
if (this.clickText === '') {
Text(this.text).toastText();
} else {
Text() {
ForEach(this.textList, (item: string, num: number) => {
Span(item);
if (num < this.textList.length - 1) {
Span(this.clickText).fontColor(Color.Yellow);
}
});
}.onClick(this.clickListener).toastText();
}
}
.borderRadius(5)
.backgroundColor(Color.Black)
.padding(10)
.justifyContent(FlexAlign.SpaceBetween)
.margin({ left: '5%', right: '5%' });
}
}

// 封装Toast的@Builder方法
@Builder
function buildText(params: Params) {
ToastContent({ text: params.text, clickText: params.clickText, clickListener: params.clickListener });
}

// 封装公共样式
@Extend(Text)
function toastText() {
.fontSize(20)
.fontColor(Color.White);
}
创建Toast类,并创建构造方法与Toast实例方法。
/**

  • 封装全局蓝色浮动提示,支持点击
    */
    export class Toast {
    private toastParams: Params;

    constructor(text: string, time: number = 2000) {
    this.toastParams = new Params(text, time);
    }

    setClick(clickText: string, clickListener: () => void): Toast {
    this.toastParams.setClick(clickText, clickListener);
    return this;
    };

    async show() {
    let uiContext = AppStorage.get('currentUIContext') as UIContext;
    let click = this.toastParams.clickListener;
    let contentNode = new ComponentContent(uiContext, wrapBuilder(buildText), this.toastParams);
    uiContext.getPromptAction().openCustomDialog(contentNode, {
    showInSubWindow: this.toastParams.clickText === '' ? false : true,
    isModal: false,
    offset: { dx: 0, dy: '10%' }
    }).then(() => {
    setTimeout(() => {
    uiContext.getPromptAction().closeCustomDialog(contentNode);
    }, this.toastParams.time);
    });
    this.toastParams.clickListener = () => {
    click();
    uiContext.getPromptAction().closeCustomDialog(contentNode);
    };
    };
    }
    创建并弹出Toast,并且可以在Toast的setClick回调方法内实现点击Toast后的逻辑,如页面跳转。
    new Toast('点击Toast后屏幕将退出横屏,进入到竖屏状态', 3000).setClick('关闭自动添加', () => {
    this.windowClass.setPreferredOrientation(window.Orientation.PORTRAIT);
    this.pathStack.pushPathByName('DetailPage', null);
    }).show();
    完整示例参考如下:

Index.ets。
import { common } from '@kit.AbilityKit';
import { Toast } from './ToastContent';
import { window } from '@kit.ArkUI';

@Entry
@Component
struct Index {
pathStack: NavPathStack = new NavPathStack();
private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
private windowClass = (this.context as common.UIAbilityContext).windowStage.getMainWindowSync();

setOrientation(orientation: number) {
this.windowClass.setPreferredOrientation(orientation).then(() => {
}).catch(() => {
});
}

async aboutToAppear(): Promise<void> {
await this.windowClass.setPreferredOrientation(window.Orientation.LANDSCAPE);
AppStorage.setOrCreate('currentUIContext', this.getUIContext());
}

build() {
Navigation(this.pathStack) {
RelativeContainer() {
Column() {
Text('我的记录')
.fontSize(50)
.width('100%')
.textAlign(TextAlign.Center)
.fontWeight(FontWeight.Bold)
Button('保存')
.onClick(() => {
new Toast('点击Toast后屏幕将退出横屏,进入到竖屏状态', 3000).setClick('关闭自动添加', () => {
this.windowClass.setPreferredOrientation(window.Orientation.PORTRAIT);
this.pathStack.pushPathByName('DetailPage', null);
}).show();
})
.backgroundColor(Color.Blue)
.fontColor(Color.White)
}
.alignRules({
center: { anchor: 'container', align: VerticalAlign.Center },
middle: { anchor: 'container', align: HorizontalAlign.Center }
})
}
.width('100%')
}
.mode(NavigationMode.Stack)
.height('100%')
.width('100%')
.height('100%')
.hideTitleBar(true)
}
}
https://pastebin.com/nwKPmmLF
https://pastebin.com/DwxhFWzG
https://pastebin.com/Jtg4RcM0
https://pastebin.com/7BvUaREN
https://pastebin.com/LGejrMwK
https://pastebin.com/V0qPwT9h
https://pastebin.com/tQzSevWd
https://pastebin.com/qncxPzug
https://pastebin.com/QNF651jK
https://pastebin.com/t3ZuR3CR
https://pastebin.com/H2PDqsEW
https://pastebin.com/Z7Wc9nic
https://pastebin.com/ECKtGAaJ
https://pastebin.com/EwXrd6qT
https://pastebin.com/HgYXSSrk
https://pastebin.com/uK3jyvu5
https://pastebin.com/vHzZdj9J
https://pastebin.com/sAMfi1Pz
https://pastebin.com/kVhLAgHT
https://pastebin.com/nkBEKJz4
https://pastebin.com/GrAvgxVg
https://pastebin.com/Umn0eUXt
https://pastebin.com/ut2dn5iH
https://pastebin.com/NgPFqHpy
https://pastebin.com/7e62WCL1
https://pastebin.com/wDM3tM66
https://pastebin.com/pwUhGKDv
https://pastebin.com/YvwB4xBj
https://pastebin.com/mzAxKZES
https://pastebin.com/NpnmdKf1
https://pastebin.com/C1FBURkc
https://pastebin.com/y0KsuZgB
https://pastebin.com/g7nDScSb
https://pastebin.com/7mDcGwtR
https://pastebin.com/3xV61sHt
https://pastebin.com/iQakPefn
https://pastebin.com/0CWW1Xta
https://pastebin.com/90zmcK10
https://pastebin.com/6CH0bCbh
https://pastebin.com/b6zdrzYw
https://pastebin.com/414XDcCa
https://pastebin.com/Mf0rTkWN
https://pastebin.com/gT4iprCN
https://pastebin.com/qeDfQegQ
https://pastebin.com/1e4vRx0y
https://pastebin.com/kGZUceQg
https://pastebin.com/9ustBt9n
https://pastebin.com/N4kwVZns
https://pastebin.com/qkBjMdA7
https://pastebin.com/w6XX4ciC
https://pastebin.com/54cBU8ze
https://pastebin.com/1mnAuUDZ
https://pastebin.com/VsgcjvTE
https://pastebin.com/wP31PyVb
https://pastebin.com/XPBk0zv8
https://pastebin.com/QRE1qguw
https://pastebin.com/iQpMss7p
https://pastebin.com/kKCXTdF9
https://pastebin.com/EwBniqXp
https://pastebin.com/A7LEwGq8
https://pastebin.com/XtXPS80z
https://pastebin.com/ghHSfqUZ
https://pastebin.com/pEH2u4Mj
https://pastebin.com/GiE2VShc
https://pastebin.com/NmfaPSX3
https://pastebin.com/fBfZs9d9
https://pastebin.com/CcHrZqng
https://pastebin.com/DcALJUNr
https://pastebin.com/hvGqEU4c
https://pastebin.com/HSf8h6BJ
https://pastebin.com/SCNykcCB
https://pastebin.com/RAnMDpBN
https://pastebin.com/0bbYFAG1
https://pastebin.com/6LjMvDuD
https://pastebin.com/u8dUE6wL
https://pastebin.com/8jVE6YLs
https://pastebin.com/xv6kzZyF
https://pastebin.com/L5WMJb5t
https://pastebin.com/sbe7FFD4
https://pastebin.com/aU4YUWDr
https://pastebin.com/3Z1YFmFW
https://pastebin.com/XvqqwSxq
https://pastebin.com/mJX0WRsc
https://pastebin.com/SGkjCXRB
https://pastebin.com/zv8zAxMd
https://pastebin.com/4bRTkCkm
https://pastebin.com/5s9JtWfS
https://pastebin.com/4VVU6Cxg
https://pastebin.com/TewUpi9F
https://pastebin.com/LKZwmBSi
https://pastebin.com/mu27p7w7
https://pastebin.com/JtiF4S2E
https://pastebin.com/xpeq692Q
https://pastebin.com/ypMqWqLC
https://pastebin.com/NrF7S04d
https://pastebin.com/j3riqEAh
https://pastebin.com/6iWCgpuJ
https://pastebin.com/s6KVgTgr
https://pastebin.com/1760APmp
https://pastebin.com/T6LTqjHK
https://pastebin.com/ttGrUAd7
https://pastebin.com/0PQTrPXK
https://pastebin.com/ZtmvtT35
https://pastebin.com/8TjBbUcR
https://pastebin.com/bYCv91Zb
https://pastebin.com/fpZrFF5w
https://pastebin.com/nrXWRsTd
https://pastebin.com/p4UWmpHz
https://pastebin.com/FUkFJhvg
https://pastebin.com/iBPFDUJ3
https://pastebin.com/ET0iD33D
https://pastebin.com/iwPwXsca
https://pastebin.com/8mmzg8p6
https://pastebin.com/8342WqK5
https://pastebin.com/pwYYMgbq
https://pastebin.com/cRgWPHH1
https://pastebin.com/rzyEfV9J
https://pastebin.com/kkAZWims
https://pastebin.com/fQn41cHZ
https://pastebin.com/fJ8vJ8be
https://pastebin.com/SzcAwdbu
https://pastebin.com/pCWkDayj
https://pastebin.com/L9pkse7j
https://pastebin.com/FxKwn5ng
https://pastebin.com/vFCLDqaV
https://pastebin.com/NRnYdnNH
https://pastebin.com/2ER3wSYv
https://pastebin.com/Q3YCRgG6
https://pastebin.com/YFaNTgNN
https://pastebin.com/9n9shZLu
https://pastebin.com/PBbWWUgX
https://pastebin.com/2R5pKJLh
https://pastebin.com/k2yY9ULe
https://pastebin.com/CJph4hrD
https://pastebin.com/6EwGM5CL
https://pastebin.com/DELYFAem
https://pastebin.com/Ggv8dLaa
https://pastebin.com/vM0r7xLq
https://pastebin.com/nbPGG85J
https://pastebin.com/ZVvYnFre
https://pastebin.com/LjJ9AwB9
https://pastebin.com/6hq2KXML
https://pastebin.com/pugZBcNi
https://pastebin.com/pmPvrW9q
https://pastebin.com/fhueU8S6
https://pastebin.com/kQazy17K
https://pastebin.com/VfTbduXf
https://pastebin.com/pXjYSwfd
https://pastebin.com/hUUnksFb
https://pastebin.com/AGnzrkpi
https://pastebin.com/s9jXdQ2e
https://pastebin.com/tkNdPSWm
https://pastebin.com/buGSwi0Q
https://pastebin.com/iqyy4wdi
https://pastebin.com/RiGF4W7H
https://pastebin.com/pxNKyFmV
https://pastebin.com/WwXY1ryk
https://pastebin.com/dNHDcvEE
https://pastebin.com/dstyKVbw
https://pastebin.com/recG0Tg4
https://pastebin.com/Bu8wxzkr
https://pastebin.com/FuZgbtXf
https://pastebin.com/56Ze2VPk
https://pastebin.com/qYG0243R
https://pastebin.com/AZ8iKJWf
https://pastebin.com/hNwShQks
https://pastebin.com/P8iaLhZF
https://pastebin.com/Dm05MGfE
https://pastebin.com/zamDMZgY
https://pastebin.com/7iyftr84
https://pastebin.com/0NssQJ78
https://pastebin.com/kdxr8s0Z
https://pastebin.com/awjDqW6h
https://pastebin.com/GWgZwDB6
https://pastebin.com/riTJWSts
https://pastebin.com/6dmkXAhN
https://pastebin.com/XZLHY1iX
https://pastebin.com/my97aREv
https://pastebin.com/H09exPyA
https://pastebin.com/Jjw8R1hF
https://pastebin.com/hm4qKthR
https://pastebin.com/G9uVUmcV
https://pastebin.com/Z4ZGXCbg
https://pastebin.com/zttcE8aT
https://pastebin.com/ieWfnuEy
https://pastebin.com/EnmdLmjw
https://pastebin.com/q7Nj4JEA
https://pastebin.com/cCnrQ8ft
https://pastebin.com/zt89NyCc
https://pastebin.com/cV5KJRUN
https://pastebin.com/e4pzjDm2
https://pastebin.com/rGb2fsmp
https://pastebin.com/q8ikYfF0
https://pastebin.com/y9e0KRNW
https://pastebin.com/r3qCSEu5
https://pastebin.com/H37KdV5m
https://pastebin.com/SREPy9Lr
https://pastebin.com/25JSeDYG
https://pastebin.com/j2a0BJdf
https://pastebin.com/pGbzGr3L
https://pastebin.com/1WJkKeyt
https://pastebin.com/DPDXbJ9L
https://pastebin.com/4gGEvFSt
https://pastebin.com/L0HCPVB0
https://pastebin.com/0eQjwvaY
https://pastebin.com/rzSNBFS8
https://pastebin.com/NWi9hui6
https://pastebin.com/xPjT5mxu
https://pastebin.com/e5neTg7h
https://pastebin.com/TZnwGHx4
https://pastebin.com/Tb98uyxA
https://pastebin.com/DPcVtW6C
https://pastebin.com/zZ7bNVBg
https://pastebin.com/yVQXZ1T1

收起阅读 »

Text组件字符超过特定长度时,超出部分如何隐藏与显示

text

问题现象
在Text中显示文本时,如果文本超过一定长度,通常会有超出部分隐藏与显示的需求,例如:当最多显示的行数为2,组件宽度比例为0.4时,该如何实现?

背景知识
measureText方法能够根据文本信息计算文本宽度。
getAllDisplays方法能够获取display对象,display对象的width属性为屏幕的宽度。
点击放大

解决方案
首先设定的文本长度计算方式:设定文本长度 = 屏幕宽度 最大行数 组件宽度比例。屏幕宽度可以使用getAllDisplays获取。
然后使用measureText方法测量实际文本宽度,比较“设定文本长度”与“实际文本宽度”进行大小比较,判断是否需要隐藏。
当需要隐藏时,只展示“设定长度”的文本内容,超出部分显示为“...”。当点击“…”时将该文本变为“…收起”,显示隐藏部分内容。
完整示例参考如下:

import { MeasureUtils } from '@kit.ArkUI';
import curves from '@ohos.curves';
import { BusinessError } from '@ohos.base';
import display from '@ohos.display';

@Entry
@Component
struct Index {
// 长文本
longMessage: string = '走在繁华的城市街头,明空感到无比紧张。他的心跳如雷鼓般擂动着胸膛,使得身上的伪装仿佛随时都要被揭开。然而,他仍然保持着冷静,凭借着过人的胆识与智慧,成功地躲过了敌人的层层封锁。\n' +
'\n' +
'  最终,明空来到了敌对帮派的老巢。此时此刻,那里的守卫正沉浸在欢庆的氛围中,丝毫没有察觉到即将来临的危机。明空深吸一口气,压抑住内心的激动,悄然潜入了这座古老的建筑。';
// 最大显示行数
@State lines: number = 2;
// 长文本状态(展开 or 收起)
@State collapseText: string = '...';
// 屏幕宽度(单位px)
screenWidth: number = 0;
// 是否需要显示"展开"字样(注:当文本长度较短时就不需要“展开”)
@State isExpanded: boolean = false;
uiContext: UIContext = this.getUIContext();
uiContextMeasure: MeasureUtils = this.uiContext.getMeasureUtils();
// 测量文本宽度(单位px)
textWidth: number = this.uiContextMeasure.measureText({
textContent: this.longMessage,
fontSize: 20
});
// 获取当前所有的display对象
promise: Promise<Array<display.Display>> = display.getAllDisplays();

aboutToAppear() {
console.info(文本宽度为:${this.textWidth});
this.promise.then((data: Array<display.Display>) => {
console.info(所有的屏幕信息:${JSON.stringify(data)});
// 单位为像素
this.screenWidth = data[0]["width"];
// 屏幕宽度 最大行数 组件宽度比例 和 文字测量宽度
this.isExpanded = this.screenWidth this.lines 0.4 <= this.textWidth;
}).catch((err: BusinessError) => {
console.error(Failed to obtain all the display objects. Code: ${JSON.stringify(err)});
});
}

build() {
Row() {
Column() {
if (this.isExpanded) {
Stack({ alignContent: Alignment.BottomEnd }) {
Text(this.longMessage)
.fontSize(20)
.fontColor(Color.Black)
.maxLines(this.lines)
.width("40%")
Row() {
Text(this.collapseText)
.fontSize(20)
.backgroundColor(Color.White)
}
.justifyContent(FlexAlign.End)
.onClick(() => {
if (this.collapseText == '...') {
this.collapseText = '...收起';
// 展开动画
this.uiContext.animateTo({
duration: 150,
curve: curves.springMotion(0.5, 0.8),
}, () => {
this.lines = -1; // 使得设置的最大行属性无效
});
} else {
this.collapseText = '...';
// 收起动画
this.uiContext.animateTo(
{
duration: 100,
curve: Curve.Friction,
}, () => {
this.lines = 2; // 只显示2行
});
}
})
}
}
else {
Text(this.longMessage)
.fontSize(20)
.fontColor(Color.Black)
}
}
.width('100%')
}
.height('100%')
}
}
https://pastebin.com/yYjzfBYh
https://pastebin.com/KvWVh2mC
https://pastebin.com/1e4eFFEG
https://pastebin.com/P3QEZ0Vt
https://pastebin.com/ySGX6wUJ
https://pastebin.com/jFJ9dwM8
https://pastebin.com/F8rBVMEp
https://pastebin.com/Th3G7tkh
https://pastebin.com/SN1eWG5Z
https://pastebin.com/eMtbaeZm
https://pastebin.com/7G7Ad9d5
https://pastebin.com/wAGuhNej
https://pastebin.com/Cmfya5mB
https://pastebin.com/pjPAkDdh
https://pastebin.com/C4124mFd
https://pastebin.com/1wHR87LD
https://pastebin.com/pccVzdhD
https://pastebin.com/J5MsUs86
https://pastebin.com/3DcWSqk8
https://pastebin.com/jFg0ThQC
https://pastebin.com/5xAUfgws
https://pastebin.com/6vaGRrFj
https://pastebin.com/jzHNkpVz
https://pastebin.com/6qZYe5TF
https://pastebin.com/QhC6D1Ea
https://pastebin.com/RHDyMM5i
https://pastebin.com/u6YdPYNT
https://pastebin.com/vMfTX3f8
https://pastebin.com/JfQ5h4mh
https://pastebin.com/eZTzxZXf
https://pastebin.com/ebALnXDi
https://pastebin.com/KgWVfrxD
https://pastebin.com/qN9C02WR
https://pastebin.com/wfmDCen7
https://pastebin.com/TRCTJxzH
https://pastebin.com/zUwZttGi
https://pastebin.com/tP3krksd
https://pastebin.com/a4zMuKAf
https://pastebin.com/Qd26iPzX
https://pastebin.com/QsYfKNGq
https://pastebin.com/ZBRQrP7g
https://pastebin.com/eLtxHFyq
https://pastebin.com/FFH804TB
https://pastebin.com/mpwpwtk1
https://pastebin.com/9MU20Mx1
https://pastebin.com/P8t51jjE
https://pastebin.com/yggX6dVr
https://pastebin.com/73KZ5Ewf
https://pastebin.com/B4s8zydV
https://pastebin.com/KvXBdDAh
https://pastebin.com/jfSidFKG
https://pastebin.com/i6z3pEGD
https://pastebin.com/K4wDCGB4
https://pastebin.com/Fk5vZKih
https://pastebin.com/j47NvPir
https://pastebin.com/r7jvjYg6
https://pastebin.com/BJzmiF4Q
https://pastebin.com/T983TDL4
https://pastebin.com/w03GsEzJ
https://pastebin.com/eVJ6DFrh
https://pastebin.com/JT5m4Fcd
https://pastebin.com/2Lyw6s6U
https://pastebin.com/nNnz4WSG
https://pastebin.com/zYw0vSEs
https://pastebin.com/7GNKsiA4
https://pastebin.com/1G8Ns8jL
https://pastebin.com/PfzWDnKs
https://pastebin.com/abuDWb37
https://pastebin.com/cV910k6U
https://pastebin.com/d1mmsxZD
https://pastebin.com/G9FgAyi3
https://pastebin.com/bAeBEub8
https://pastebin.com/1khz4e3X
https://pastebin.com/vxgkb5LD
https://pastebin.com/agcLMv2d
https://pastebin.com/nbSGyLLE
https://pastebin.com/fBXRb652
https://pastebin.com/AEaYhdxJ
https://pastebin.com/QnynP0t9
https://pastebin.com/KKG6w0k7
https://pastebin.com/s0iiVSxp
https://pastebin.com/EcmwFtbt
https://pastebin.com/KNmHDurJ
https://pastebin.com/CH32Naac
https://pastebin.com/DX2L75yh
https://pastebin.com/fkvZzYWa
https://pastebin.com/YH5AVbn3
https://pastebin.com/nkY9vSR2
https://pastebin.com/qQ9CkVRM
https://pastebin.com/cYMgDT9Q
https://pastebin.com/NCtDqJeU
https://pastebin.com/umbABgNV
https://pastebin.com/Jb0RrAxC
https://pastebin.com/nWHXx7p0
https://pastebin.com/Atbf7RdX
https://pastebin.com/yDd2FNsG
https://pastebin.com/NEfD7g3Z
https://pastebin.com/rZ1AkzAZ
https://pastebin.com/3HCfn9P1
https://pastebin.com/GWrtDxQr
https://pastebin.com/sf7fhcWs
https://pastebin.com/K7UL8AFj
https://pastebin.com/w4Hm6FuE
https://pastebin.com/5f0KTubZ
https://pastebin.com/6X5dsStC
https://pastebin.com/3nP0pK0t
https://pastebin.com/Emmq0RgR
https://pastebin.com/An3zQJse
https://pastebin.com/6h38FzmU
https://pastebin.com/y3GcsSec
https://pastebin.com/xnNtJTAg
https://pastebin.com/dZAGmXFz
https://pastebin.com/FZScNNPE
https://pastebin.com/8nLrkahp
https://pastebin.com/qx0Nznfz
https://pastebin.com/bbj21hbF
https://pastebin.com/QwZEg5QW
https://pastebin.com/A6Ph6wSg
https://pastebin.com/v8YbNB2J
https://pastebin.com/1Me85UF8
https://pastebin.com/N5zPTGhM
https://pastebin.com/cxR3Z0Q9
https://pastebin.com/yPptx6L2
https://pastebin.com/MgwpSDkL
https://pastebin.com/PdQkppe3
https://pastebin.com/ybfJNJTG
https://pastebin.com/4Cxexqf9
https://pastebin.com/cAShGPFD
https://pastebin.com/7iKLHyRr
https://pastebin.com/mttz0KMU
https://pastebin.com/EVrThu9E
https://pastebin.com/Y50asBDv
https://pastebin.com/95e2yHKf
https://pastebin.com/75zBX7Lg
https://pastebin.com/H88viQHj
https://pastebin.com/nJai6ZvU
https://pastebin.com/XGQr0J9c
https://pastebin.com/B26473RK
https://pastebin.com/ZkMRJwsT
https://pastebin.com/A7eaF21y
https://pastebin.com/qJrNz3u4
https://pastebin.com/2Egf1rmF
https://pastebin.com/ZDSc4Det
https://pastebin.com/DEGVVTgx
https://pastebin.com/MgRWq7xG
https://pastebin.com/qad7X0EQ
https://pastebin.com/G3xSFwem
https://pastebin.com/EffXNSx1
https://pastebin.com/e2GFCkaX
https://pastebin.com/d6XpErS5
https://pastebin.com/Atqhj6gz
https://pastebin.com/qmbXp0Sy
https://pastebin.com/UpgN3P4V
https://pastebin.com/VuwwY5ip
https://pastebin.com/NWnRfqPS
https://pastebin.com/c9GM9mBv
https://pastebin.com/0ZsBLMfd
https://pastebin.com/TFMs3tGt
https://pastebin.com/ehrjT1Gn
https://pastebin.com/m5RE6Avs
https://pastebin.com/fWYHdNgA
https://pastebin.com/gLVzuBx3
https://pastebin.com/rB9HYuaZ
https://pastebin.com/Fe2TBsvf
https://pastebin.com/xHugabcz
https://pastebin.com/ranMDSE7
https://pastebin.com/v1sqVnVc
https://pastebin.com/KB0TWNjm
https://pastebin.com/Z31VUeKU
https://pastebin.com/mjh7S1aF
https://pastebin.com/hrhppQs7
https://pastebin.com/gi9JXzNb
https://pastebin.com/fFVyxssb
https://pastebin.com/iBVj7uYr
https://pastebin.com/YvPJ8P5R
https://pastebin.com/70EAxttP
https://pastebin.com/EPbXeVtD
https://pastebin.com/EVSJNZin
https://pastebin.com/cc8QsGFx
https://pastebin.com/6TG7XnrV
https://pastebin.com/a84xV4uk
https://pastebin.com/KKZzVH4a
https://pastebin.com/XAMTJyHs
https://pastebin.com/Fz3DjJZ9
https://pastebin.com/g0sd0Bzs
https://pastebin.com/Jp8VpszT
https://pastebin.com/YUKEygx1
https://pastebin.com/uVwpBydg
https://pastebin.com/c9aAXgFc
https://pastebin.com/pZ28ZqPj
https://pastebin.com/rtW3W8AQ
https://pastebin.com/jg7cApH9
https://pastebin.com/JptcDPdH

继续阅读 »

问题现象
在Text中显示文本时,如果文本超过一定长度,通常会有超出部分隐藏与显示的需求,例如:当最多显示的行数为2,组件宽度比例为0.4时,该如何实现?

背景知识
measureText方法能够根据文本信息计算文本宽度。
getAllDisplays方法能够获取display对象,display对象的width属性为屏幕的宽度。
点击放大

解决方案
首先设定的文本长度计算方式:设定文本长度 = 屏幕宽度 最大行数 组件宽度比例。屏幕宽度可以使用getAllDisplays获取。
然后使用measureText方法测量实际文本宽度,比较“设定文本长度”与“实际文本宽度”进行大小比较,判断是否需要隐藏。
当需要隐藏时,只展示“设定长度”的文本内容,超出部分显示为“...”。当点击“…”时将该文本变为“…收起”,显示隐藏部分内容。
完整示例参考如下:

import { MeasureUtils } from '@kit.ArkUI';
import curves from '@ohos.curves';
import { BusinessError } from '@ohos.base';
import display from '@ohos.display';

@Entry
@Component
struct Index {
// 长文本
longMessage: string = '走在繁华的城市街头,明空感到无比紧张。他的心跳如雷鼓般擂动着胸膛,使得身上的伪装仿佛随时都要被揭开。然而,他仍然保持着冷静,凭借着过人的胆识与智慧,成功地躲过了敌人的层层封锁。\n' +
'\n' +
'  最终,明空来到了敌对帮派的老巢。此时此刻,那里的守卫正沉浸在欢庆的氛围中,丝毫没有察觉到即将来临的危机。明空深吸一口气,压抑住内心的激动,悄然潜入了这座古老的建筑。';
// 最大显示行数
@State lines: number = 2;
// 长文本状态(展开 or 收起)
@State collapseText: string = '...';
// 屏幕宽度(单位px)
screenWidth: number = 0;
// 是否需要显示"展开"字样(注:当文本长度较短时就不需要“展开”)
@State isExpanded: boolean = false;
uiContext: UIContext = this.getUIContext();
uiContextMeasure: MeasureUtils = this.uiContext.getMeasureUtils();
// 测量文本宽度(单位px)
textWidth: number = this.uiContextMeasure.measureText({
textContent: this.longMessage,
fontSize: 20
});
// 获取当前所有的display对象
promise: Promise<Array<display.Display>> = display.getAllDisplays();

aboutToAppear() {
console.info(文本宽度为:${this.textWidth});
this.promise.then((data: Array<display.Display>) => {
console.info(所有的屏幕信息:${JSON.stringify(data)});
// 单位为像素
this.screenWidth = data[0]["width"];
// 屏幕宽度 最大行数 组件宽度比例 和 文字测量宽度
this.isExpanded = this.screenWidth this.lines 0.4 <= this.textWidth;
}).catch((err: BusinessError) => {
console.error(Failed to obtain all the display objects. Code: ${JSON.stringify(err)});
});
}

build() {
Row() {
Column() {
if (this.isExpanded) {
Stack({ alignContent: Alignment.BottomEnd }) {
Text(this.longMessage)
.fontSize(20)
.fontColor(Color.Black)
.maxLines(this.lines)
.width("40%")
Row() {
Text(this.collapseText)
.fontSize(20)
.backgroundColor(Color.White)
}
.justifyContent(FlexAlign.End)
.onClick(() => {
if (this.collapseText == '...') {
this.collapseText = '...收起';
// 展开动画
this.uiContext.animateTo({
duration: 150,
curve: curves.springMotion(0.5, 0.8),
}, () => {
this.lines = -1; // 使得设置的最大行属性无效
});
} else {
this.collapseText = '...';
// 收起动画
this.uiContext.animateTo(
{
duration: 100,
curve: Curve.Friction,
}, () => {
this.lines = 2; // 只显示2行
});
}
})
}
}
else {
Text(this.longMessage)
.fontSize(20)
.fontColor(Color.Black)
}
}
.width('100%')
}
.height('100%')
}
}
https://pastebin.com/yYjzfBYh
https://pastebin.com/KvWVh2mC
https://pastebin.com/1e4eFFEG
https://pastebin.com/P3QEZ0Vt
https://pastebin.com/ySGX6wUJ
https://pastebin.com/jFJ9dwM8
https://pastebin.com/F8rBVMEp
https://pastebin.com/Th3G7tkh
https://pastebin.com/SN1eWG5Z
https://pastebin.com/eMtbaeZm
https://pastebin.com/7G7Ad9d5
https://pastebin.com/wAGuhNej
https://pastebin.com/Cmfya5mB
https://pastebin.com/pjPAkDdh
https://pastebin.com/C4124mFd
https://pastebin.com/1wHR87LD
https://pastebin.com/pccVzdhD
https://pastebin.com/J5MsUs86
https://pastebin.com/3DcWSqk8
https://pastebin.com/jFg0ThQC
https://pastebin.com/5xAUfgws
https://pastebin.com/6vaGRrFj
https://pastebin.com/jzHNkpVz
https://pastebin.com/6qZYe5TF
https://pastebin.com/QhC6D1Ea
https://pastebin.com/RHDyMM5i
https://pastebin.com/u6YdPYNT
https://pastebin.com/vMfTX3f8
https://pastebin.com/JfQ5h4mh
https://pastebin.com/eZTzxZXf
https://pastebin.com/ebALnXDi
https://pastebin.com/KgWVfrxD
https://pastebin.com/qN9C02WR
https://pastebin.com/wfmDCen7
https://pastebin.com/TRCTJxzH
https://pastebin.com/zUwZttGi
https://pastebin.com/tP3krksd
https://pastebin.com/a4zMuKAf
https://pastebin.com/Qd26iPzX
https://pastebin.com/QsYfKNGq
https://pastebin.com/ZBRQrP7g
https://pastebin.com/eLtxHFyq
https://pastebin.com/FFH804TB
https://pastebin.com/mpwpwtk1
https://pastebin.com/9MU20Mx1
https://pastebin.com/P8t51jjE
https://pastebin.com/yggX6dVr
https://pastebin.com/73KZ5Ewf
https://pastebin.com/B4s8zydV
https://pastebin.com/KvXBdDAh
https://pastebin.com/jfSidFKG
https://pastebin.com/i6z3pEGD
https://pastebin.com/K4wDCGB4
https://pastebin.com/Fk5vZKih
https://pastebin.com/j47NvPir
https://pastebin.com/r7jvjYg6
https://pastebin.com/BJzmiF4Q
https://pastebin.com/T983TDL4
https://pastebin.com/w03GsEzJ
https://pastebin.com/eVJ6DFrh
https://pastebin.com/JT5m4Fcd
https://pastebin.com/2Lyw6s6U
https://pastebin.com/nNnz4WSG
https://pastebin.com/zYw0vSEs
https://pastebin.com/7GNKsiA4
https://pastebin.com/1G8Ns8jL
https://pastebin.com/PfzWDnKs
https://pastebin.com/abuDWb37
https://pastebin.com/cV910k6U
https://pastebin.com/d1mmsxZD
https://pastebin.com/G9FgAyi3
https://pastebin.com/bAeBEub8
https://pastebin.com/1khz4e3X
https://pastebin.com/vxgkb5LD
https://pastebin.com/agcLMv2d
https://pastebin.com/nbSGyLLE
https://pastebin.com/fBXRb652
https://pastebin.com/AEaYhdxJ
https://pastebin.com/QnynP0t9
https://pastebin.com/KKG6w0k7
https://pastebin.com/s0iiVSxp
https://pastebin.com/EcmwFtbt
https://pastebin.com/KNmHDurJ
https://pastebin.com/CH32Naac
https://pastebin.com/DX2L75yh
https://pastebin.com/fkvZzYWa
https://pastebin.com/YH5AVbn3
https://pastebin.com/nkY9vSR2
https://pastebin.com/qQ9CkVRM
https://pastebin.com/cYMgDT9Q
https://pastebin.com/NCtDqJeU
https://pastebin.com/umbABgNV
https://pastebin.com/Jb0RrAxC
https://pastebin.com/nWHXx7p0
https://pastebin.com/Atbf7RdX
https://pastebin.com/yDd2FNsG
https://pastebin.com/NEfD7g3Z
https://pastebin.com/rZ1AkzAZ
https://pastebin.com/3HCfn9P1
https://pastebin.com/GWrtDxQr
https://pastebin.com/sf7fhcWs
https://pastebin.com/K7UL8AFj
https://pastebin.com/w4Hm6FuE
https://pastebin.com/5f0KTubZ
https://pastebin.com/6X5dsStC
https://pastebin.com/3nP0pK0t
https://pastebin.com/Emmq0RgR
https://pastebin.com/An3zQJse
https://pastebin.com/6h38FzmU
https://pastebin.com/y3GcsSec
https://pastebin.com/xnNtJTAg
https://pastebin.com/dZAGmXFz
https://pastebin.com/FZScNNPE
https://pastebin.com/8nLrkahp
https://pastebin.com/qx0Nznfz
https://pastebin.com/bbj21hbF
https://pastebin.com/QwZEg5QW
https://pastebin.com/A6Ph6wSg
https://pastebin.com/v8YbNB2J
https://pastebin.com/1Me85UF8
https://pastebin.com/N5zPTGhM
https://pastebin.com/cxR3Z0Q9
https://pastebin.com/yPptx6L2
https://pastebin.com/MgwpSDkL
https://pastebin.com/PdQkppe3
https://pastebin.com/ybfJNJTG
https://pastebin.com/4Cxexqf9
https://pastebin.com/cAShGPFD
https://pastebin.com/7iKLHyRr
https://pastebin.com/mttz0KMU
https://pastebin.com/EVrThu9E
https://pastebin.com/Y50asBDv
https://pastebin.com/95e2yHKf
https://pastebin.com/75zBX7Lg
https://pastebin.com/H88viQHj
https://pastebin.com/nJai6ZvU
https://pastebin.com/XGQr0J9c
https://pastebin.com/B26473RK
https://pastebin.com/ZkMRJwsT
https://pastebin.com/A7eaF21y
https://pastebin.com/qJrNz3u4
https://pastebin.com/2Egf1rmF
https://pastebin.com/ZDSc4Det
https://pastebin.com/DEGVVTgx
https://pastebin.com/MgRWq7xG
https://pastebin.com/qad7X0EQ
https://pastebin.com/G3xSFwem
https://pastebin.com/EffXNSx1
https://pastebin.com/e2GFCkaX
https://pastebin.com/d6XpErS5
https://pastebin.com/Atqhj6gz
https://pastebin.com/qmbXp0Sy
https://pastebin.com/UpgN3P4V
https://pastebin.com/VuwwY5ip
https://pastebin.com/NWnRfqPS
https://pastebin.com/c9GM9mBv
https://pastebin.com/0ZsBLMfd
https://pastebin.com/TFMs3tGt
https://pastebin.com/ehrjT1Gn
https://pastebin.com/m5RE6Avs
https://pastebin.com/fWYHdNgA
https://pastebin.com/gLVzuBx3
https://pastebin.com/rB9HYuaZ
https://pastebin.com/Fe2TBsvf
https://pastebin.com/xHugabcz
https://pastebin.com/ranMDSE7
https://pastebin.com/v1sqVnVc
https://pastebin.com/KB0TWNjm
https://pastebin.com/Z31VUeKU
https://pastebin.com/mjh7S1aF
https://pastebin.com/hrhppQs7
https://pastebin.com/gi9JXzNb
https://pastebin.com/fFVyxssb
https://pastebin.com/iBVj7uYr
https://pastebin.com/YvPJ8P5R
https://pastebin.com/70EAxttP
https://pastebin.com/EPbXeVtD
https://pastebin.com/EVSJNZin
https://pastebin.com/cc8QsGFx
https://pastebin.com/6TG7XnrV
https://pastebin.com/a84xV4uk
https://pastebin.com/KKZzVH4a
https://pastebin.com/XAMTJyHs
https://pastebin.com/Fz3DjJZ9
https://pastebin.com/g0sd0Bzs
https://pastebin.com/Jp8VpszT
https://pastebin.com/YUKEygx1
https://pastebin.com/uVwpBydg
https://pastebin.com/c9aAXgFc
https://pastebin.com/pZ28ZqPj
https://pastebin.com/rtW3W8AQ
https://pastebin.com/jg7cApH9
https://pastebin.com/JptcDPdH

收起阅读 »