HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

如何实现不同分组间元素拖拽切换效果

问题现象
如下图所示,需求是现在有A、B两个组,A、B两组中的元素可以拖动,并且A组中的元素可以拖动到B组,B组的元素同样可以拖动到A组,请问如何实现这种多组之间相互拖拽的效果?

点击放大

背景知识
使用Grid组件构建网格元素布局,启动editMode编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem。
onItemDragStart和onItemDrop事件分别在开始拖拽网格元素时触发和停止拖拽时触发,通过事件的组合完成交换数组位置的逻辑。
解决方案
使用Grid布局构建界面。其中,columnsTemplate可设置当前网格布局列的数量、固定列宽或最小列宽值;columnsGap可设置列与列的间距;rowsGap可设置行与行的间距。
Grid(this.scroller) {
GridItem() {
Text('标题' + this.numbers.length)
.fontSize(16)
.width('100%')
.height(30)
.padding({ left: 10 })
.textAlign(TextAlign.Start);
}
.onClick(() => {
this.isShowGroup1 = !this.isShowGroup1;
});

if (this.isShowGroup1) {
ForEach(this.numbers, (day: string) => {
GridItem() {
Text(day)
.fontSize(16)
.backgroundColor('#f1f3f5')
.width('100%')
.height(30)
.textAlign(TextAlign.Center);
};
});
}
GridItem() {
Text('标题' + this.numbers2.length)
.fontSize(16)
.width('100%')
.height(30)
.padding({ left: 10 })
.textAlign(TextAlign.Start);
}
.onClick(() => {
this.isShowGroup2 = !this.isShowGroup2;
});

if (this.isShowGroup2) {
ForEach(this.numbers2, (day: string) => {
GridItem() {
Text(day)
.fontSize(16)
.backgroundColor('#f1f3f5')
.width('100%')
.height(30)
.textAlign(TextAlign.Center);
};
});
}
}
.columnsTemplate('1fr')
.columnsGap(2)
.rowsGap(2)
.scrollBar(BarState.Off)
给Grid组件设置editMode为true,即Grid进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem。设置supportAnimation为true,即Grid拖拽元素时支持动画。
.supportAnimation(true)
.editMode(true) // 设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem
定义GridItem被拖拽时元素的样式,元素被拖拽时展示浮动内容。
@Builder
pixelMapBuilder() { // 拖拽过程样式
Column() {
Text('浮动内容' + this.text)
.fontSize(16)
.backgroundColor('#f1f3f5')
.width('90%')
.height(30)
.textAlign(TextAlign.Center);
};
}
判断当前移动的是不是标题,如果是标题禁止拖动。
// 判断是否是标题
isTitle(index: number) {
if (index === 0) {
return true;
}
if (!this.isShowGroup1 && index === 1) {
return true;
}
if (this.isShowGroup1 && index === this.numbers.length + 1) {
return true;
}
return false;
}
定义拖拽过程中的数组交换逻辑。
moveToIndex(index1: number, index2: number) { // 将index1位置的元素移动至 index2位置
let temp = '';
let num1Length = 1 + (this.numbers.length - 1) * Number(this.isShowGroup1);
if (index1 <= num1Length) {
temp = this.numbers.splice(index1 - 1, 1)[0];
} else {
index1 = index1 - this.numbers.length - 1;
temp = this.numbers2.splice(index1 - 1, 1)[0];
}
if (index2 <= num1Length) {
this.numbers.splice(index2 - 1, 0, temp);
} else {
index2 = index2 - this.numbers.length - 1;
this.numbers2.splice(index2 - 1, 0, temp);
}
}
给Grid组件绑定onItemDragStart和onItemDrop事件,在onItemDragStart回调中设置拖拽过程中显示的图片,并在onItemDrop中完成交换数组位置的逻辑。
.onItemDragStart((event: ItemDragInfo, itemIndex: number) => { // 第一次拖拽此事件绑定的组件时,触发回调。
if (this.isTitle(itemIndex)) {
return;
}
if (itemIndex > this.numbers.length) {
itemIndex = itemIndex - this.numbers.length - 1;
this.text = this.numbers2[itemIndex - 1];
} else {
this.text = this.numbers[itemIndex - 1];
}
return this.pixelMapBuilder(); // 设置拖拽过程中显示的图片。
})
.onItemDrop((event: ItemDragInfo, itemIndex: number,
insertIndex: number) => { // 绑定此事件的组件可作为拖拽释放目标,当在本组件范围内停止拖拽行为时,触发回调。
console.info(eventX: ${event.x});
this.moveToIndex(itemIndex, insertIndex);
});
完整示例如下:

@Entry
@Component
struct GridDemo {
@State numbers: string[] = [];
@State numbers2: string[] = [];
@State isShowGroup1: boolean = true;
@State isShowGroup2: boolean = true;
@State text: string = 'drag';
scroller: Scroller = new Scroller();

@Builder
pixelMapBuilder() { // 拖拽过程样式
Column() {
Text('浮动内容' + this.text)
.fontSize(16)
.backgroundColor('#f1f3f5')
.width('90%')
.height(30)
.textAlign(TextAlign.Center);
};
}

aboutToAppear() {
for (let i = 1; i <= 15; i++) {
this.numbers.push('组' + i);
this.numbers2.push('组' + i);
}
}

moveToIndex(index1: number, index2: number) { // 将index1位置的元素移动至 index2位置
let temp = '';
let num1Length = 1 + (this.numbers.length - 1) * Number(this.isShowGroup1);
if (index1 <= num1Length) {
temp = this.numbers.splice(index1 - 1, 1)[0];
} else {
index1 = index1 - this.numbers.length - 1;
temp = this.numbers2.splice(index1 - 1, 1)[0];
}
if (index2 <= num1Length) {
this.numbers.splice(index2 - 1, 0, temp);
} else {
index2 = index2 - this.numbers.length - 1;
this.numbers2.splice(index2 - 1, 0, temp);
}
}

// 判断是否是标题
isTitle(index: number) {
if (index === 0) {
return true;
}
if (!this.isShowGroup1 && index === 1) {
return true;
}
if (this.isShowGroup1 && index === this.numbers.length + 1) {
return true;
}
return false;
}

build() {
Column({ space: 5 }) {
Column() {
Grid(this.scroller) {
GridItem() {
Text('标题' + this.numbers.length)
.fontSize(16)
.width('100%')
.height(30)
.padding({ left: 10 })
.textAlign(TextAlign.Start);
}
.onClick(() => {
this.isShowGroup1 = !this.isShowGroup1;
});

      if (this.isShowGroup1) {  
        ForEach(this.numbers, (day: string) => {  
          GridItem() {  
            Text(day)  
              .fontSize(16)  
              .backgroundColor('#f1f3f5')  
              .width('100%')  
              .height(30)  
              .textAlign(TextAlign.Center);  
          };  
        });  
      }  
      GridItem() {  
        Text('标题' + this.numbers2.length)  
          .fontSize(16)  
          .width('100%')  
          .height(30)  
          .padding({ left: 10 })  
          .textAlign(TextAlign.Start);  
      }  
      .onClick(() => {  
        this.isShowGroup2 = !this.isShowGroup2;  
      });  

      if (this.isShowGroup2) {  
        ForEach(this.numbers2, (day: string) => {  
          GridItem() {  
            Text(day)  
              .fontSize(16)  
              .backgroundColor('#f1f3f5')  
              .width('100%')  
              .height(30)  
              .textAlign(TextAlign.Center);  
          };  
        });  
      }  
    }  
    .columnsTemplate('1fr')  
    .columnsGap(2)  
    .rowsGap(2)  
    .scrollBar(BarState.Off)  
    .onScrollIndex((first: number) => {  
      console.info(first.toString());  
    })  
    .width('90%')  
    .supportAnimation(true)  
    .editMode(true) // 设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem  
    .onItemDragStart((event: ItemDragInfo, itemIndex: number) => { // 第一次拖拽此事件绑定的组件时,触发回调。  
      if (this.isTitle(itemIndex)) {  
        return;  
      }  
      if (itemIndex > this.numbers.length) {  
        itemIndex = itemIndex - this.numbers.length - 1;  
        this.text = this.numbers2[itemIndex - 1];  
      } else {  
        this.text = this.numbers[itemIndex - 1];  
      }  
      return this.pixelMapBuilder(); // 设置拖拽过程中显示的图片。  
    })  
    .onItemDrop((event: ItemDragInfo, itemIndex: number,  
      insertIndex: number) => { // 绑定此事件的组件可作为拖拽释放目标,当在本组件范围内停止拖拽行为时,触发回调。  
      console.info(`eventX: ${event.x}`);  
      this.moveToIndex(itemIndex, insertIndex);  
    });  

  };  
}.width('100%').margin({ top: 5 });  

}
}
https://pastebin.com/ALZ1heJ8
https://pastebin.com/NUA8cAMq
https://pastebin.com/DU7BW7SP
https://pastebin.com/Sh2K4277
https://pastebin.com/MKNCS5xM
https://pastebin.com/jBqWTCsQ
https://pastebin.com/TNhaEKd7
https://pastebin.com/pAcyuLWk
https://pastebin.com/BccwcGVK
https://pastebin.com/ETXeKqiW
https://pastebin.com/DRxMx41K
https://pastebin.com/L15SkLc9
https://pastebin.com/Pyz2h7xc
https://pastebin.com/LWtxbvUn
https://pastebin.com/S7wbsPSF
https://pastebin.com/XmPzcDSQ
https://pastebin.com/zJ2kJ7ZA
https://pastebin.com/wknFnVct
https://pastebin.com/Jx9edSj2
https://pastebin.com/s7w3uv0a
https://pastebin.com/JHwj1NaL
https://pastebin.com/RzdjWfNv
https://pastebin.com/9G3P7TeA
https://pastebin.com/sG5wxN1u
https://pastebin.com/hJ2c716v
https://pastebin.com/4exdhVkn
https://pastebin.com/hUVhcvMP
https://pastebin.com/A4LnBDtM
https://pastebin.com/L6FwB3N9
https://pastebin.com/pt0KRF9P
https://pastebin.com/93GGDNJv
https://pastebin.com/DHdskaAf
https://pastebin.com/1QH5dm9R
https://pastebin.com/P30Qqeif
https://pastebin.com/ngQjFN3W
https://pastebin.com/BRAv0z5d
https://pastebin.com/fdYZAvMX
https://pastebin.com/ciXgk5F6
https://pastebin.com/RjC4wjc9
https://pastebin.com/kEaUKCTq
https://pastebin.com/C3JuxCrQ
https://pastebin.com/y0KUAU4u
https://pastebin.com/nW5AFBcM
https://pastebin.com/yR9yyNZ5
https://pastebin.com/zvH6TX45
https://pastebin.com/RsL54ULk
https://pastebin.com/TRTYXt4k
https://pastebin.com/zCGW0vif
https://pastebin.com/6drbFd0j
https://pastebin.com/D7urfsq6
https://pastebin.com/119esj24
https://pastebin.com/ndHUJCr4
https://pastebin.com/ciSWy0ut
https://pastebin.com/iKf50her
https://pastebin.com/RkE5yyn8
https://pastebin.com/jj99Wa7u
https://pastebin.com/iwJ5us6j
https://pastebin.com/hSuvYeqx
https://pastebin.com/6YQNbNZ6
https://pastebin.com/SsUF39yN
https://pastebin.com/xLFVFWSd
https://pastebin.com/8C1YCkb7
https://pastebin.com/PBRiQwEE
https://pastebin.com/CZw00CFD
https://pastebin.com/MhC3vvEH
https://pastebin.com/NGucrr8R
https://pastebin.com/5inLc8eS
https://pastebin.com/rnNKvRei
https://pastebin.com/LDXYYnhG
https://pastebin.com/5YpXtQeT
https://pastebin.com/mb5JFUs5
https://pastebin.com/HDzxgFHY
https://pastebin.com/Ssx5zf6i
https://pastebin.com/qMCRpKuL
https://pastebin.com/YMyidNzi
https://pastebin.com/vSwjFPu0
https://pastebin.com/yFPJgYtk
https://pastebin.com/5FLbpDXJ
https://pastebin.com/vRj3ezdr
https://pastebin.com/mYUwfwNk
https://pastebin.com/dTx5DQSb
https://pastebin.com/e7jXBCMc
https://pastebin.com/dxgE2bcC
https://pastebin.com/STW6QhxS
https://pastebin.com/bZhrAr9R
https://pastebin.com/nfYTP7Au
https://pastebin.com/aWunSSVB
https://pastebin.com/zh4pAmyd
https://pastebin.com/3pqzBPyc
https://pastebin.com/ddwcDZHC
https://pastebin.com/2ZqNLikt
https://pastebin.com/56U4rWjG
https://pastebin.com/w24SbjAy
https://pastebin.com/by3Jn4Pq
https://pastebin.com/9UkiAZMK
https://pastebin.com/MxhA05RB
https://pastebin.com/4Jshmpfb
https://pastebin.com/L5sHYH9N
https://pastebin.com/0bJWTV7h
https://pastebin.com/AwM6iDiL
https://pastebin.com/Lsr4Cxkg
https://pastebin.com/LAfLC3a3
https://pastebin.com/f7wTnKEC
https://pastebin.com/HSEbHCyh
https://pastebin.com/uv6Hqjs4
https://pastebin.com/i2V8QUcy
https://pastebin.com/ZAPYfBNk
https://pastebin.com/waMXDnj3
https://pastebin.com/GMUKBZUs
https://pastebin.com/pezF3Vbk
https://pastebin.com/n7DYUQ5v
https://pastebin.com/cyT65uSE
https://pastebin.com/sx2iJqcn
https://pastebin.com/ZPqkWKaR
https://pastebin.com/HEyb77U9
https://pastebin.com/4FZufveK
https://pastebin.com/uExv5h76
https://pastebin.com/Ac7CHJc7
https://pastebin.com/M4s8ZR79
https://pastebin.com/nys7xPvA
https://pastebin.com/PehqarxQ
https://pastebin.com/RbdWT272
https://pastebin.com/UfFk8r7t
https://pastebin.com/y1XYanaB
https://pastebin.com/VmifaeiF
https://pastebin.com/2dDAckiN
https://pastebin.com/FAfELYhc
https://pastebin.com/QCFiEVnQ
https://pastebin.com/QbgF4PzX
https://pastebin.com/X4yVdKxp
https://pastebin.com/cuqhnADr
https://pastebin.com/8iUfUnL8
https://pastebin.com/xhFdDU3m
https://pastebin.com/5Yt1eLAQ
https://pastebin.com/XNDHeeM8
https://pastebin.com/kJXfXZ8R
https://pastebin.com/hxwe46wW
https://pastebin.com/B4mB17YB
https://pastebin.com/GQbTpcbY
https://pastebin.com/DpYauvxs
https://pastebin.com/1gsWBrLW
https://pastebin.com/g9WsSEzm
https://pastebin.com/hzqj45RD
https://pastebin.com/vWKXzBJ9
https://pastebin.com/GTajTfJB
https://pastebin.com/d52vdzg9
https://pastebin.com/w1xQpURf
https://pastebin.com/GVzvf2jD
https://pastebin.com/U5DNXZR5
https://pastebin.com/bBDcpTNV
https://pastebin.com/kaQ390Yd
https://pastebin.com/RHfrn44V
https://pastebin.com/ycM6hx0X
https://pastebin.com/sSUD6yBD
https://pastebin.com/vYZwLY68
https://pastebin.com/hsu9R9jj
https://pastebin.com/G6HZUJ4J
https://pastebin.com/tr8ae7XA
https://pastebin.com/j8cttQZk
https://pastebin.com/YeKMiByw
https://pastebin.com/j6Nnu9Qe
https://pastebin.com/LVGaaVQa
https://pastebin.com/JJWVEiS5
https://pastebin.com/GEFCqsT7
https://pastebin.com/3ZjJhLsT
https://pastebin.com/eRuatTXc
https://pastebin.com/6dLRhyiJ
https://pastebin.com/VS65myh8
https://pastebin.com/NK7i1SnE
https://pastebin.com/6Akg12TS
https://pastebin.com/Qs3xpNS5
https://pastebin.com/RjrrPqe0
https://pastebin.com/urvjTD8V
https://pastebin.com/63sbx7mm
https://pastebin.com/CxznX5Js
https://pastebin.com/yL6vTZKF
https://pastebin.com/ExT07Ygf
https://pastebin.com/QeQZdYcJ
https://pastebin.com/BY7bpMhj
https://pastebin.com/yAYUGpkf
https://pastebin.com/YXCzrDjU
https://pastebin.com/S10HgVGn
https://pastebin.com/LY6WLkTu
https://pastebin.com/3pfYGW1q
https://pastebin.com/3pV0G6nk
https://pastebin.com/CW4g7TJv
https://pastebin.com/M8J73bkP
https://pastebin.com/L9vcxB0W
https://pastebin.com/buUr51Ls

继续阅读 »

问题现象
如下图所示,需求是现在有A、B两个组,A、B两组中的元素可以拖动,并且A组中的元素可以拖动到B组,B组的元素同样可以拖动到A组,请问如何实现这种多组之间相互拖拽的效果?

点击放大

背景知识
使用Grid组件构建网格元素布局,启动editMode编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem。
onItemDragStart和onItemDrop事件分别在开始拖拽网格元素时触发和停止拖拽时触发,通过事件的组合完成交换数组位置的逻辑。
解决方案
使用Grid布局构建界面。其中,columnsTemplate可设置当前网格布局列的数量、固定列宽或最小列宽值;columnsGap可设置列与列的间距;rowsGap可设置行与行的间距。
Grid(this.scroller) {
GridItem() {
Text('标题' + this.numbers.length)
.fontSize(16)
.width('100%')
.height(30)
.padding({ left: 10 })
.textAlign(TextAlign.Start);
}
.onClick(() => {
this.isShowGroup1 = !this.isShowGroup1;
});

if (this.isShowGroup1) {
ForEach(this.numbers, (day: string) => {
GridItem() {
Text(day)
.fontSize(16)
.backgroundColor('#f1f3f5')
.width('100%')
.height(30)
.textAlign(TextAlign.Center);
};
});
}
GridItem() {
Text('标题' + this.numbers2.length)
.fontSize(16)
.width('100%')
.height(30)
.padding({ left: 10 })
.textAlign(TextAlign.Start);
}
.onClick(() => {
this.isShowGroup2 = !this.isShowGroup2;
});

if (this.isShowGroup2) {
ForEach(this.numbers2, (day: string) => {
GridItem() {
Text(day)
.fontSize(16)
.backgroundColor('#f1f3f5')
.width('100%')
.height(30)
.textAlign(TextAlign.Center);
};
});
}
}
.columnsTemplate('1fr')
.columnsGap(2)
.rowsGap(2)
.scrollBar(BarState.Off)
给Grid组件设置editMode为true,即Grid进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem。设置supportAnimation为true,即Grid拖拽元素时支持动画。
.supportAnimation(true)
.editMode(true) // 设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem
定义GridItem被拖拽时元素的样式,元素被拖拽时展示浮动内容。
@Builder
pixelMapBuilder() { // 拖拽过程样式
Column() {
Text('浮动内容' + this.text)
.fontSize(16)
.backgroundColor('#f1f3f5')
.width('90%')
.height(30)
.textAlign(TextAlign.Center);
};
}
判断当前移动的是不是标题,如果是标题禁止拖动。
// 判断是否是标题
isTitle(index: number) {
if (index === 0) {
return true;
}
if (!this.isShowGroup1 && index === 1) {
return true;
}
if (this.isShowGroup1 && index === this.numbers.length + 1) {
return true;
}
return false;
}
定义拖拽过程中的数组交换逻辑。
moveToIndex(index1: number, index2: number) { // 将index1位置的元素移动至 index2位置
let temp = '';
let num1Length = 1 + (this.numbers.length - 1) * Number(this.isShowGroup1);
if (index1 <= num1Length) {
temp = this.numbers.splice(index1 - 1, 1)[0];
} else {
index1 = index1 - this.numbers.length - 1;
temp = this.numbers2.splice(index1 - 1, 1)[0];
}
if (index2 <= num1Length) {
this.numbers.splice(index2 - 1, 0, temp);
} else {
index2 = index2 - this.numbers.length - 1;
this.numbers2.splice(index2 - 1, 0, temp);
}
}
给Grid组件绑定onItemDragStart和onItemDrop事件,在onItemDragStart回调中设置拖拽过程中显示的图片,并在onItemDrop中完成交换数组位置的逻辑。
.onItemDragStart((event: ItemDragInfo, itemIndex: number) => { // 第一次拖拽此事件绑定的组件时,触发回调。
if (this.isTitle(itemIndex)) {
return;
}
if (itemIndex > this.numbers.length) {
itemIndex = itemIndex - this.numbers.length - 1;
this.text = this.numbers2[itemIndex - 1];
} else {
this.text = this.numbers[itemIndex - 1];
}
return this.pixelMapBuilder(); // 设置拖拽过程中显示的图片。
})
.onItemDrop((event: ItemDragInfo, itemIndex: number,
insertIndex: number) => { // 绑定此事件的组件可作为拖拽释放目标,当在本组件范围内停止拖拽行为时,触发回调。
console.info(eventX: ${event.x});
this.moveToIndex(itemIndex, insertIndex);
});
完整示例如下:

@Entry
@Component
struct GridDemo {
@State numbers: string[] = [];
@State numbers2: string[] = [];
@State isShowGroup1: boolean = true;
@State isShowGroup2: boolean = true;
@State text: string = 'drag';
scroller: Scroller = new Scroller();

@Builder
pixelMapBuilder() { // 拖拽过程样式
Column() {
Text('浮动内容' + this.text)
.fontSize(16)
.backgroundColor('#f1f3f5')
.width('90%')
.height(30)
.textAlign(TextAlign.Center);
};
}

aboutToAppear() {
for (let i = 1; i <= 15; i++) {
this.numbers.push('组' + i);
this.numbers2.push('组' + i);
}
}

moveToIndex(index1: number, index2: number) { // 将index1位置的元素移动至 index2位置
let temp = '';
let num1Length = 1 + (this.numbers.length - 1) * Number(this.isShowGroup1);
if (index1 <= num1Length) {
temp = this.numbers.splice(index1 - 1, 1)[0];
} else {
index1 = index1 - this.numbers.length - 1;
temp = this.numbers2.splice(index1 - 1, 1)[0];
}
if (index2 <= num1Length) {
this.numbers.splice(index2 - 1, 0, temp);
} else {
index2 = index2 - this.numbers.length - 1;
this.numbers2.splice(index2 - 1, 0, temp);
}
}

// 判断是否是标题
isTitle(index: number) {
if (index === 0) {
return true;
}
if (!this.isShowGroup1 && index === 1) {
return true;
}
if (this.isShowGroup1 && index === this.numbers.length + 1) {
return true;
}
return false;
}

build() {
Column({ space: 5 }) {
Column() {
Grid(this.scroller) {
GridItem() {
Text('标题' + this.numbers.length)
.fontSize(16)
.width('100%')
.height(30)
.padding({ left: 10 })
.textAlign(TextAlign.Start);
}
.onClick(() => {
this.isShowGroup1 = !this.isShowGroup1;
});

      if (this.isShowGroup1) {  
        ForEach(this.numbers, (day: string) => {  
          GridItem() {  
            Text(day)  
              .fontSize(16)  
              .backgroundColor('#f1f3f5')  
              .width('100%')  
              .height(30)  
              .textAlign(TextAlign.Center);  
          };  
        });  
      }  
      GridItem() {  
        Text('标题' + this.numbers2.length)  
          .fontSize(16)  
          .width('100%')  
          .height(30)  
          .padding({ left: 10 })  
          .textAlign(TextAlign.Start);  
      }  
      .onClick(() => {  
        this.isShowGroup2 = !this.isShowGroup2;  
      });  

      if (this.isShowGroup2) {  
        ForEach(this.numbers2, (day: string) => {  
          GridItem() {  
            Text(day)  
              .fontSize(16)  
              .backgroundColor('#f1f3f5')  
              .width('100%')  
              .height(30)  
              .textAlign(TextAlign.Center);  
          };  
        });  
      }  
    }  
    .columnsTemplate('1fr')  
    .columnsGap(2)  
    .rowsGap(2)  
    .scrollBar(BarState.Off)  
    .onScrollIndex((first: number) => {  
      console.info(first.toString());  
    })  
    .width('90%')  
    .supportAnimation(true)  
    .editMode(true) // 设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem  
    .onItemDragStart((event: ItemDragInfo, itemIndex: number) => { // 第一次拖拽此事件绑定的组件时,触发回调。  
      if (this.isTitle(itemIndex)) {  
        return;  
      }  
      if (itemIndex > this.numbers.length) {  
        itemIndex = itemIndex - this.numbers.length - 1;  
        this.text = this.numbers2[itemIndex - 1];  
      } else {  
        this.text = this.numbers[itemIndex - 1];  
      }  
      return this.pixelMapBuilder(); // 设置拖拽过程中显示的图片。  
    })  
    .onItemDrop((event: ItemDragInfo, itemIndex: number,  
      insertIndex: number) => { // 绑定此事件的组件可作为拖拽释放目标,当在本组件范围内停止拖拽行为时,触发回调。  
      console.info(`eventX: ${event.x}`);  
      this.moveToIndex(itemIndex, insertIndex);  
    });  

  };  
}.width('100%').margin({ top: 5 });  

}
}
https://pastebin.com/ALZ1heJ8
https://pastebin.com/NUA8cAMq
https://pastebin.com/DU7BW7SP
https://pastebin.com/Sh2K4277
https://pastebin.com/MKNCS5xM
https://pastebin.com/jBqWTCsQ
https://pastebin.com/TNhaEKd7
https://pastebin.com/pAcyuLWk
https://pastebin.com/BccwcGVK
https://pastebin.com/ETXeKqiW
https://pastebin.com/DRxMx41K
https://pastebin.com/L15SkLc9
https://pastebin.com/Pyz2h7xc
https://pastebin.com/LWtxbvUn
https://pastebin.com/S7wbsPSF
https://pastebin.com/XmPzcDSQ
https://pastebin.com/zJ2kJ7ZA
https://pastebin.com/wknFnVct
https://pastebin.com/Jx9edSj2
https://pastebin.com/s7w3uv0a
https://pastebin.com/JHwj1NaL
https://pastebin.com/RzdjWfNv
https://pastebin.com/9G3P7TeA
https://pastebin.com/sG5wxN1u
https://pastebin.com/hJ2c716v
https://pastebin.com/4exdhVkn
https://pastebin.com/hUVhcvMP
https://pastebin.com/A4LnBDtM
https://pastebin.com/L6FwB3N9
https://pastebin.com/pt0KRF9P
https://pastebin.com/93GGDNJv
https://pastebin.com/DHdskaAf
https://pastebin.com/1QH5dm9R
https://pastebin.com/P30Qqeif
https://pastebin.com/ngQjFN3W
https://pastebin.com/BRAv0z5d
https://pastebin.com/fdYZAvMX
https://pastebin.com/ciXgk5F6
https://pastebin.com/RjC4wjc9
https://pastebin.com/kEaUKCTq
https://pastebin.com/C3JuxCrQ
https://pastebin.com/y0KUAU4u
https://pastebin.com/nW5AFBcM
https://pastebin.com/yR9yyNZ5
https://pastebin.com/zvH6TX45
https://pastebin.com/RsL54ULk
https://pastebin.com/TRTYXt4k
https://pastebin.com/zCGW0vif
https://pastebin.com/6drbFd0j
https://pastebin.com/D7urfsq6
https://pastebin.com/119esj24
https://pastebin.com/ndHUJCr4
https://pastebin.com/ciSWy0ut
https://pastebin.com/iKf50her
https://pastebin.com/RkE5yyn8
https://pastebin.com/jj99Wa7u
https://pastebin.com/iwJ5us6j
https://pastebin.com/hSuvYeqx
https://pastebin.com/6YQNbNZ6
https://pastebin.com/SsUF39yN
https://pastebin.com/xLFVFWSd
https://pastebin.com/8C1YCkb7
https://pastebin.com/PBRiQwEE
https://pastebin.com/CZw00CFD
https://pastebin.com/MhC3vvEH
https://pastebin.com/NGucrr8R
https://pastebin.com/5inLc8eS
https://pastebin.com/rnNKvRei
https://pastebin.com/LDXYYnhG
https://pastebin.com/5YpXtQeT
https://pastebin.com/mb5JFUs5
https://pastebin.com/HDzxgFHY
https://pastebin.com/Ssx5zf6i
https://pastebin.com/qMCRpKuL
https://pastebin.com/YMyidNzi
https://pastebin.com/vSwjFPu0
https://pastebin.com/yFPJgYtk
https://pastebin.com/5FLbpDXJ
https://pastebin.com/vRj3ezdr
https://pastebin.com/mYUwfwNk
https://pastebin.com/dTx5DQSb
https://pastebin.com/e7jXBCMc
https://pastebin.com/dxgE2bcC
https://pastebin.com/STW6QhxS
https://pastebin.com/bZhrAr9R
https://pastebin.com/nfYTP7Au
https://pastebin.com/aWunSSVB
https://pastebin.com/zh4pAmyd
https://pastebin.com/3pqzBPyc
https://pastebin.com/ddwcDZHC
https://pastebin.com/2ZqNLikt
https://pastebin.com/56U4rWjG
https://pastebin.com/w24SbjAy
https://pastebin.com/by3Jn4Pq
https://pastebin.com/9UkiAZMK
https://pastebin.com/MxhA05RB
https://pastebin.com/4Jshmpfb
https://pastebin.com/L5sHYH9N
https://pastebin.com/0bJWTV7h
https://pastebin.com/AwM6iDiL
https://pastebin.com/Lsr4Cxkg
https://pastebin.com/LAfLC3a3
https://pastebin.com/f7wTnKEC
https://pastebin.com/HSEbHCyh
https://pastebin.com/uv6Hqjs4
https://pastebin.com/i2V8QUcy
https://pastebin.com/ZAPYfBNk
https://pastebin.com/waMXDnj3
https://pastebin.com/GMUKBZUs
https://pastebin.com/pezF3Vbk
https://pastebin.com/n7DYUQ5v
https://pastebin.com/cyT65uSE
https://pastebin.com/sx2iJqcn
https://pastebin.com/ZPqkWKaR
https://pastebin.com/HEyb77U9
https://pastebin.com/4FZufveK
https://pastebin.com/uExv5h76
https://pastebin.com/Ac7CHJc7
https://pastebin.com/M4s8ZR79
https://pastebin.com/nys7xPvA
https://pastebin.com/PehqarxQ
https://pastebin.com/RbdWT272
https://pastebin.com/UfFk8r7t
https://pastebin.com/y1XYanaB
https://pastebin.com/VmifaeiF
https://pastebin.com/2dDAckiN
https://pastebin.com/FAfELYhc
https://pastebin.com/QCFiEVnQ
https://pastebin.com/QbgF4PzX
https://pastebin.com/X4yVdKxp
https://pastebin.com/cuqhnADr
https://pastebin.com/8iUfUnL8
https://pastebin.com/xhFdDU3m
https://pastebin.com/5Yt1eLAQ
https://pastebin.com/XNDHeeM8
https://pastebin.com/kJXfXZ8R
https://pastebin.com/hxwe46wW
https://pastebin.com/B4mB17YB
https://pastebin.com/GQbTpcbY
https://pastebin.com/DpYauvxs
https://pastebin.com/1gsWBrLW
https://pastebin.com/g9WsSEzm
https://pastebin.com/hzqj45RD
https://pastebin.com/vWKXzBJ9
https://pastebin.com/GTajTfJB
https://pastebin.com/d52vdzg9
https://pastebin.com/w1xQpURf
https://pastebin.com/GVzvf2jD
https://pastebin.com/U5DNXZR5
https://pastebin.com/bBDcpTNV
https://pastebin.com/kaQ390Yd
https://pastebin.com/RHfrn44V
https://pastebin.com/ycM6hx0X
https://pastebin.com/sSUD6yBD
https://pastebin.com/vYZwLY68
https://pastebin.com/hsu9R9jj
https://pastebin.com/G6HZUJ4J
https://pastebin.com/tr8ae7XA
https://pastebin.com/j8cttQZk
https://pastebin.com/YeKMiByw
https://pastebin.com/j6Nnu9Qe
https://pastebin.com/LVGaaVQa
https://pastebin.com/JJWVEiS5
https://pastebin.com/GEFCqsT7
https://pastebin.com/3ZjJhLsT
https://pastebin.com/eRuatTXc
https://pastebin.com/6dLRhyiJ
https://pastebin.com/VS65myh8
https://pastebin.com/NK7i1SnE
https://pastebin.com/6Akg12TS
https://pastebin.com/Qs3xpNS5
https://pastebin.com/RjrrPqe0
https://pastebin.com/urvjTD8V
https://pastebin.com/63sbx7mm
https://pastebin.com/CxznX5Js
https://pastebin.com/yL6vTZKF
https://pastebin.com/ExT07Ygf
https://pastebin.com/QeQZdYcJ
https://pastebin.com/BY7bpMhj
https://pastebin.com/yAYUGpkf
https://pastebin.com/YXCzrDjU
https://pastebin.com/S10HgVGn
https://pastebin.com/LY6WLkTu
https://pastebin.com/3pfYGW1q
https://pastebin.com/3pV0G6nk
https://pastebin.com/CW4g7TJv
https://pastebin.com/M8J73bkP
https://pastebin.com/L9vcxB0W
https://pastebin.com/buUr51Ls

收起阅读 »

App 已经成功上架,为什么苹果开发者账号还是被封了?这是我后来才明白的事

应用上架

App 已经成功上架,为什么苹果开发者账号还是被封了?这是我后来才明白的事

很多开发者都会觉得:

只要 App 能成功上架,说明账号已经安全了。

我以前也是这样认为的。

毕竟审核都通过了,应用也已经在 App Store 正常下载,按理来说后续应该只是更新版本、维护功能,不会再有什么大问题。

直到后来,一个已经稳定运营了一段时间的开发者账号突然被终止,我才开始重新研究苹果的审核逻辑。

事实证明,审核通过和账号安全,并不是一回事。

当时到底发生了什么?

那个账号并不是第一次提交应用。

前面的版本都顺利通过审核,产品也一直正常运营。

后来只是一次普通的版本更新,原本以为几天就能审核完成,结果审核时间越来越长,随后收到了苹果关于账号的处理通知。

刚开始我一直觉得是不是这次代码写错了。

后来复盘整个账号,才发现问题并没有这么简单。

苹果真正评估的是整个开发者账号

很多开发者只关注这一次提交的 IPA。

实际上,苹果更关注的是整个开发者账号的表现。

例如:

  • 提交过哪些应用
  • 每个应用之间是否存在高度相似
  • 是否经常因为同类问题被拒
  • 是否存在重复业务
  • 应用更新是否长期保持稳定
  • 是否存在误导性的元数据

这些信息都会不断累积。

也就是说,账号是有”历史记录”的。

并不会因为一次审核通过,就把之前所有情况全部清零。

我后来发现,真正危险的是”长期积累”

有一次审核通过,并不能说明以后一直都会通过。

真正容易出现问题的,往往是下面这些情况。

相似应用越来越多

刚开始只有一个工具类 App。

后来增加第二个。

第三个。

第四个。

如果这些应用只是换了名称、Logo 或主题,而底层实现高度一致,那么随着数量增加,被关注的概率也会增加。

很多开发者觉得:

“以前都是这么做的,也通过了。”

但实际上,审核标准、审核模型以及账号历史都会不断变化。

每次都只是为了过审

还有一种情况,就是每次修改都只是为了让审核通过。

哪里被拒改哪里。

功能没有真正优化,业务没有真正完善。

长时间下来,账号会留下大量审核记录。

虽然每一次都可能成功,但整体表现未必理想。

更新越来越频繁

为了赶进度,有时候一天提交一次。

甚至一天修改两三次。

这种方式虽然能够提高试错速度,但也容易让账号产生大量审核记录。

如果再叠加其他因素,风险自然会上升。

后来我们的做法变了

经历过那次之后,我们已经不会再等审核发现问题。

每次准备提交之前,都会提前检查:

  • 功能是否真正独立
  • 页面是否存在大量重复
  • 是否新增了容易触发审核的问题
  • 元数据是否准确描述功能
  • 新版本是否与历史版本保持合理演进

虽然准备时间更长了一点,但后面的审核明显稳定了很多。

最后想说

很多开发者把苹果审核理解成一次考试。

其实更像是一份长期档案。

每一次提交、每一次修改、每一次被拒,都会成为账号历史的一部分。

所以,真正需要维护的,不只是某一个 App,而是整个开发者账号的长期信誉。

只有把账号当成长期资产去运营,而不是只想着快速通过一次审核,后续更新才会越来越顺利。

更多文章看我主页

继续阅读 »

App 已经成功上架,为什么苹果开发者账号还是被封了?这是我后来才明白的事

很多开发者都会觉得:

只要 App 能成功上架,说明账号已经安全了。

我以前也是这样认为的。

毕竟审核都通过了,应用也已经在 App Store 正常下载,按理来说后续应该只是更新版本、维护功能,不会再有什么大问题。

直到后来,一个已经稳定运营了一段时间的开发者账号突然被终止,我才开始重新研究苹果的审核逻辑。

事实证明,审核通过和账号安全,并不是一回事。

当时到底发生了什么?

那个账号并不是第一次提交应用。

前面的版本都顺利通过审核,产品也一直正常运营。

后来只是一次普通的版本更新,原本以为几天就能审核完成,结果审核时间越来越长,随后收到了苹果关于账号的处理通知。

刚开始我一直觉得是不是这次代码写错了。

后来复盘整个账号,才发现问题并没有这么简单。

苹果真正评估的是整个开发者账号

很多开发者只关注这一次提交的 IPA。

实际上,苹果更关注的是整个开发者账号的表现。

例如:

  • 提交过哪些应用
  • 每个应用之间是否存在高度相似
  • 是否经常因为同类问题被拒
  • 是否存在重复业务
  • 应用更新是否长期保持稳定
  • 是否存在误导性的元数据

这些信息都会不断累积。

也就是说,账号是有”历史记录”的。

并不会因为一次审核通过,就把之前所有情况全部清零。

我后来发现,真正危险的是”长期积累”

有一次审核通过,并不能说明以后一直都会通过。

真正容易出现问题的,往往是下面这些情况。

相似应用越来越多

刚开始只有一个工具类 App。

后来增加第二个。

第三个。

第四个。

如果这些应用只是换了名称、Logo 或主题,而底层实现高度一致,那么随着数量增加,被关注的概率也会增加。

很多开发者觉得:

“以前都是这么做的,也通过了。”

但实际上,审核标准、审核模型以及账号历史都会不断变化。

每次都只是为了过审

还有一种情况,就是每次修改都只是为了让审核通过。

哪里被拒改哪里。

功能没有真正优化,业务没有真正完善。

长时间下来,账号会留下大量审核记录。

虽然每一次都可能成功,但整体表现未必理想。

更新越来越频繁

为了赶进度,有时候一天提交一次。

甚至一天修改两三次。

这种方式虽然能够提高试错速度,但也容易让账号产生大量审核记录。

如果再叠加其他因素,风险自然会上升。

后来我们的做法变了

经历过那次之后,我们已经不会再等审核发现问题。

每次准备提交之前,都会提前检查:

  • 功能是否真正独立
  • 页面是否存在大量重复
  • 是否新增了容易触发审核的问题
  • 元数据是否准确描述功能
  • 新版本是否与历史版本保持合理演进

虽然准备时间更长了一点,但后面的审核明显稳定了很多。

最后想说

很多开发者把苹果审核理解成一次考试。

其实更像是一份长期档案。

每一次提交、每一次修改、每一次被拒,都会成为账号历史的一部分。

所以,真正需要维护的,不只是某一个 App,而是整个开发者账号的长期信誉。

只有把账号当成长期资产去运营,而不是只想着快速通过一次审核,后续更新才会越来越顺利。

更多文章看我主页

收起阅读 »

解决Row容器空间不足时子组件消失的问题

问题现象
在Row组件中放置两个Text组件,左侧Text(动态标题)需自适应宽度,空间不足时末尾省略显示(TextOverflow.Ellipsis),右侧Text(如(99))需始终完整显示,但实际效果中,空间不足时左侧Text直接消失,而非显示省略号。

问题代码示例参考如下:

@Entry
@Component
struct Index {
@State title: string = '长标题文本长标题文本长标题文本';

build() {
Column() {
Row() {
Text(this.title)
.fontSize(17)
.fontWeight(500)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.textAlign(TextAlign.Center)
.onClick(() => {
this.title += '加加';
})

    Text('(99)')  
      .fontSize(17)  
      .fontWeight(500)  
      .maxLines(1)  
      .textOverflow({ overflow: TextOverflow.Ellipsis })  
      .textAlign(TextAlign.Start)  
      .displayPriority(2)  
  }  
  .justifyContent(FlexAlign.Center)  
  .width('100%')  
  .margin({ top: 280 })  
}  

}
}
问题效果预览:

点击放大

效果预览
点击放大

背景知识
displayPriority属性机制:当父容器空间不足时,系统按优先级隐藏子组件(值越小优先级越高),若某一优先级组件被隐藏,更低优先级的组件会全部被隐藏(即使空间足够),右侧Text设置displayPriority(低优先级),左侧未设置(默认0,高优先级),但隐藏逻辑导致左侧异常消失。
弹性布局压缩规则:Row基于Flex布局,子组件默认flexShrink:0(禁止压缩),文本省略需同时满足:设置maxLines和textOverflow,组件flexShrink:1(允许压缩)且有明确宽度约束。
问题定位
隐藏机制冲突:右侧displayPriority激活了隐藏逻辑,空间不足时触发低优先级组件隐藏链,导致左侧被连带隐藏,左侧虽设置省略样式,但flexShrink默认为0,未触发压缩流程,直接跳过省略进入隐藏。
布局约束缺失:左侧Text未明确允许压缩,右侧未禁止压缩,两者在空间争夺中行为未定义,justifyContent(FlexAlign.Center)强制居中分配空间,加剧宽度计算冲突。
分析结论
根本矛盾在于:displayPriority的组件级隐藏机制与textOverflow的文本级压缩机制互斥,当空间不足时,系统优先触发displayPriority的隐藏逻辑,而非文本压缩。

修改建议
核心方案:弃用displayPriority,改用弹性压缩控制。

@Entry
@Component
struct LongText {
@State title: string = '长标题文本长标题文本长标题文本';

build() {
Column() {
// 关键修改1:使用Flex替代Row,明确弹性规则
Flex({
direction: FlexDirection.Row,
alignItems: ItemAlign.Center,
justifyContent: FlexAlign.Start // 左对齐
}) {
Text(this.title)
.fontSize(17)
.fontWeight(500)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.flexShrink(1) // 关键修改2:允许压缩
.onClick(() => {
this.title += '追加文本';
})

    Text('(99)')  
      .fontSize(17)  
      .fontWeight(500)  
      .flexShrink(0) // 关键修改3:禁止压缩  
  }  
  .width('100%')  
  .margin({ top: 280 })  
}  

}
}
https://pastebin.com/zVxDU61E
https://pastebin.com/nzLueSAz
https://pastebin.com/ezBMAig0
https://pastebin.com/YnzFdFKL
https://pastebin.com/7NqfmRSc
https://pastebin.com/XnhHvhjC
https://pastebin.com/5vt1ShF0
https://pastebin.com/jimpxmtQ
https://pastebin.com/0MKBBtSb
https://pastebin.com/hYPcWX3A
https://pastebin.com/JGZi6dqX
https://pastebin.com/Eg0QkGxK
https://pastebin.com/avBQHdmv
https://pastebin.com/9K1kr69e
https://pastebin.com/Ft3Qmf4p
https://pastebin.com/ECQydf9d
https://pastebin.com/w6AjGy7B
https://pastebin.com/pUAfNajs
https://pastebin.com/PdVL3eGz
https://pastebin.com/mTMqjn2v
https://pastebin.com/3h4gwTdv
https://pastebin.com/gjNjuKYf
https://pastebin.com/KFucmjDt
https://pastebin.com/6bWkRuLA
https://pastebin.com/7NibmTdB
https://pastebin.com/YfQPgbwm
https://pastebin.com/LJSs1Hdj
https://pastebin.com/PSvAdQ53
https://pastebin.com/KgSmzqhM
https://pastebin.com/BnDxzKWh
https://pastebin.com/aF99ZhZq
https://pastebin.com/bqhqtcei
https://pastebin.com/sXTWrCAD
https://pastebin.com/7yCNwek9
https://pastebin.com/naX7WgBh
https://pastebin.com/DkhB099s
https://pastebin.com/zkQ8Pr8B
https://pastebin.com/N3DaSPpi
https://pastebin.com/uTy41bXJ
https://pastebin.com/7YJE2nuP
https://pastebin.com/nz8wUJUM
https://pastebin.com/2REs5Q33
https://pastebin.com/tQ28rQXE
https://pastebin.com/DMCWY9XS
https://pastebin.com/nRcXfz4e
https://pastebin.com/S4NzD1sa
https://pastebin.com/QgVJE8JN
https://pastebin.com/EvAFi0b5
https://pastebin.com/Nz6N6kSC
https://pastebin.com/n5xFt0XA
https://pastebin.com/AFNPdqRC
https://pastebin.com/wC1fQXzb
https://pastebin.com/ttzSHyy5
https://pastebin.com/6y9J8aZd
https://pastebin.com/gP7ZHPhR
https://pastebin.com/xhVy1Dr7
https://pastebin.com/7tMFe3Z8
https://pastebin.com/aFthfCSF
https://pastebin.com/26QsabND
https://pastebin.com/hbZqkTfN
https://pastebin.com/M0ZMtszR
https://pastebin.com/F62d211T
https://pastebin.com/LH6eA95e
https://pastebin.com/6mP60iDt
https://pastebin.com/gRhsLLQt
https://pastebin.com/a7rmhfm0
https://pastebin.com/sj10sst6
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

继续阅读 »

问题现象
在Row组件中放置两个Text组件,左侧Text(动态标题)需自适应宽度,空间不足时末尾省略显示(TextOverflow.Ellipsis),右侧Text(如(99))需始终完整显示,但实际效果中,空间不足时左侧Text直接消失,而非显示省略号。

问题代码示例参考如下:

@Entry
@Component
struct Index {
@State title: string = '长标题文本长标题文本长标题文本';

build() {
Column() {
Row() {
Text(this.title)
.fontSize(17)
.fontWeight(500)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.textAlign(TextAlign.Center)
.onClick(() => {
this.title += '加加';
})

    Text('(99)')  
      .fontSize(17)  
      .fontWeight(500)  
      .maxLines(1)  
      .textOverflow({ overflow: TextOverflow.Ellipsis })  
      .textAlign(TextAlign.Start)  
      .displayPriority(2)  
  }  
  .justifyContent(FlexAlign.Center)  
  .width('100%')  
  .margin({ top: 280 })  
}  

}
}
问题效果预览:

点击放大

效果预览
点击放大

背景知识
displayPriority属性机制:当父容器空间不足时,系统按优先级隐藏子组件(值越小优先级越高),若某一优先级组件被隐藏,更低优先级的组件会全部被隐藏(即使空间足够),右侧Text设置displayPriority(低优先级),左侧未设置(默认0,高优先级),但隐藏逻辑导致左侧异常消失。
弹性布局压缩规则:Row基于Flex布局,子组件默认flexShrink:0(禁止压缩),文本省略需同时满足:设置maxLines和textOverflow,组件flexShrink:1(允许压缩)且有明确宽度约束。
问题定位
隐藏机制冲突:右侧displayPriority激活了隐藏逻辑,空间不足时触发低优先级组件隐藏链,导致左侧被连带隐藏,左侧虽设置省略样式,但flexShrink默认为0,未触发压缩流程,直接跳过省略进入隐藏。
布局约束缺失:左侧Text未明确允许压缩,右侧未禁止压缩,两者在空间争夺中行为未定义,justifyContent(FlexAlign.Center)强制居中分配空间,加剧宽度计算冲突。
分析结论
根本矛盾在于:displayPriority的组件级隐藏机制与textOverflow的文本级压缩机制互斥,当空间不足时,系统优先触发displayPriority的隐藏逻辑,而非文本压缩。

修改建议
核心方案:弃用displayPriority,改用弹性压缩控制。

@Entry
@Component
struct LongText {
@State title: string = '长标题文本长标题文本长标题文本';

build() {
Column() {
// 关键修改1:使用Flex替代Row,明确弹性规则
Flex({
direction: FlexDirection.Row,
alignItems: ItemAlign.Center,
justifyContent: FlexAlign.Start // 左对齐
}) {
Text(this.title)
.fontSize(17)
.fontWeight(500)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.flexShrink(1) // 关键修改2:允许压缩
.onClick(() => {
this.title += '追加文本';
})

    Text('(99)')  
      .fontSize(17)  
      .fontWeight(500)  
      .flexShrink(0) // 关键修改3:禁止压缩  
  }  
  .width('100%')  
  .margin({ top: 280 })  
}  

}
}
https://pastebin.com/zVxDU61E
https://pastebin.com/nzLueSAz
https://pastebin.com/ezBMAig0
https://pastebin.com/YnzFdFKL
https://pastebin.com/7NqfmRSc
https://pastebin.com/XnhHvhjC
https://pastebin.com/5vt1ShF0
https://pastebin.com/jimpxmtQ
https://pastebin.com/0MKBBtSb
https://pastebin.com/hYPcWX3A
https://pastebin.com/JGZi6dqX
https://pastebin.com/Eg0QkGxK
https://pastebin.com/avBQHdmv
https://pastebin.com/9K1kr69e
https://pastebin.com/Ft3Qmf4p
https://pastebin.com/ECQydf9d
https://pastebin.com/w6AjGy7B
https://pastebin.com/pUAfNajs
https://pastebin.com/PdVL3eGz
https://pastebin.com/mTMqjn2v
https://pastebin.com/3h4gwTdv
https://pastebin.com/gjNjuKYf
https://pastebin.com/KFucmjDt
https://pastebin.com/6bWkRuLA
https://pastebin.com/7NibmTdB
https://pastebin.com/YfQPgbwm
https://pastebin.com/LJSs1Hdj
https://pastebin.com/PSvAdQ53
https://pastebin.com/KgSmzqhM
https://pastebin.com/BnDxzKWh
https://pastebin.com/aF99ZhZq
https://pastebin.com/bqhqtcei
https://pastebin.com/sXTWrCAD
https://pastebin.com/7yCNwek9
https://pastebin.com/naX7WgBh
https://pastebin.com/DkhB099s
https://pastebin.com/zkQ8Pr8B
https://pastebin.com/N3DaSPpi
https://pastebin.com/uTy41bXJ
https://pastebin.com/7YJE2nuP
https://pastebin.com/nz8wUJUM
https://pastebin.com/2REs5Q33
https://pastebin.com/tQ28rQXE
https://pastebin.com/DMCWY9XS
https://pastebin.com/nRcXfz4e
https://pastebin.com/S4NzD1sa
https://pastebin.com/QgVJE8JN
https://pastebin.com/EvAFi0b5
https://pastebin.com/Nz6N6kSC
https://pastebin.com/n5xFt0XA
https://pastebin.com/AFNPdqRC
https://pastebin.com/wC1fQXzb
https://pastebin.com/ttzSHyy5
https://pastebin.com/6y9J8aZd
https://pastebin.com/gP7ZHPhR
https://pastebin.com/xhVy1Dr7
https://pastebin.com/7tMFe3Z8
https://pastebin.com/aFthfCSF
https://pastebin.com/26QsabND
https://pastebin.com/hbZqkTfN
https://pastebin.com/M0ZMtszR
https://pastebin.com/F62d211T
https://pastebin.com/LH6eA95e
https://pastebin.com/6mP60iDt
https://pastebin.com/gRhsLLQt
https://pastebin.com/a7rmhfm0
https://pastebin.com/sj10sst6
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的默认显示页面

问题现象
使用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 版本低”。请取消兼容模式以确保正常运行。

收起阅读 »