deveco_hmigbot 0.21.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +50 -0
  3. package/agents/hmigbot-worker.md +61 -0
  4. package/agents/hmigbot.md +22 -0
  5. package/agents/workflow-subagent.md +55 -0
  6. package/commands/hmigbot.md +17 -0
  7. package/dist/index.js +1 -0
  8. package/manifest.json +19 -0
  9. package/package.json +29 -0
  10. package/skills/migrate-core/FILES.md +26 -0
  11. package/skills/migrate-core/SKILL.md +484 -0
  12. package/skills/migrate-core/references/README.md +64 -0
  13. package/skills/migrate-core/references/flow/arkts-vector-gate.md +55 -0
  14. package/skills/migrate-core/references/flow/build-error-patterns.md +52 -0
  15. package/skills/migrate-core/references/flow/conventions-template.md +244 -0
  16. package/skills/migrate-core/references/flow/navigation-migration.md +42 -0
  17. package/skills/migrate-core/references/flow/platform-api-guards.md +59 -0
  18. package/skills/migrate-core/references/flow/platform-model-gaps.md +43 -0
  19. package/skills/migrate-core/references/flow/resource-conversion.md +46 -0
  20. package/skills/migrate-core/references/flow/ui-layout-semantics.md +124 -0
  21. package/skills/migrate-core/references/flow/unit-breakdown.md +42 -0
  22. package/skills/migrate-core/references/host-capabilities.md +24 -0
  23. package/skills/migrate-core/references/topics/app-identity.md +214 -0
  24. package/skills/migrate-core/references/topics/env-doctor.md +245 -0
  25. package/skills/migrate-core/references/topics/i18n/README.md +458 -0
  26. package/skills/migrate-core/references/topics/i18n/references/code-examples.md +304 -0
  27. package/skills/migrate-core/references/topics/i18n/references/common-pitfalls.md +354 -0
  28. package/skills/migrate-core/references/topics/i18n/references/dynamic-language-switch.md +464 -0
  29. package/skills/migrate-core/references/topics/i18n/references/language-codes.md +104 -0
  30. package/skills/migrate-core/references/topics/icon-sizing.md +98 -0
  31. package/skills/migrate-core/references/topics/library-migration/README.md +234 -0
  32. package/skills/migrate-core/references/topics/library-migration/closed-source-sdk.md +128 -0
  33. package/skills/migrate-core/references/topics/library-migration/download-api-decision.md +84 -0
  34. package/skills/migrate-core/references/topics/library-migration/library-mapping-table.md +100 -0
  35. package/skills/migrate-core/references/topics/library-migration/napi-compile-guide.md +84 -0
  36. package/skills/migrate-core/references/topics/library-migration/ohpm-search-guide.md +73 -0
  37. package/skills/migrate-core/references/topics/library-migration/stdlib-mapping-table.md +34 -0
  38. package/skills/migrate-core/references/topics/resources/aar-decompile.md +25 -0
  39. package/skills/migrate-core/references/topics/resources/conversion-rules.md +625 -0
  40. package/skills/migrate-core/references/topics/resources/dependency-analysis-rules.md +328 -0
  41. package/skills/migrate-core/references/topics/resources/material-design-icons.md +173 -0
  42. package/skills/migrate-core/references/topics/resources/svg-fix-patterns.md +175 -0
  43. package/skills/migrate-core/references/topics/resources/xml-drawable-to-svg-rules.md +513 -0
  44. package/skills/migrate-core/references/topics/system-capabilities/README.md +331 -0
  45. package/skills/migrate-core/references/topics/system-capabilities/avplayer-guide.md +161 -0
  46. package/skills/migrate-core/references/topics/system-capabilities/background-tasks.md +403 -0
  47. package/skills/migrate-core/references/topics/system-capabilities/browser-intent.md +121 -0
  48. package/skills/migrate-core/references/topics/system-capabilities/camera-picker.md +118 -0
  49. package/skills/migrate-core/references/topics/system-capabilities/document-picker.md +246 -0
  50. package/skills/migrate-core/references/topics/system-capabilities/file-utils.md +131 -0
  51. package/skills/migrate-core/references/topics/system-capabilities/permission-helper.md +112 -0
  52. package/skills/migrate-core/references/topics/system-capabilities/photo-access-helper.md +208 -0
  53. package/skills/migrate-core/references/topics/system-capabilities/print-management.md +213 -0
  54. package/skills/migrate-core/references/topics/system-capabilities/share-panel.md +177 -0
  55. package/skills/migrate-core/references/topics/system-capabilities/system-settings.md +322 -0
  56. package/skills/migrate-core/references/topics/system-capabilities/telephony-dial.md +49 -0
  57. package/skills/migrate-core/references/topics/system-capabilities/video-playback.md +42 -0
  58. package/skills/migrate-core/references/topics/system-capabilities/webview-patterns.md +38 -0
  59. package/skills/migrate-core/references/topics/ui-alignment/README.md +344 -0
  60. package/skills/migrate-core/references/topics/ui-alignment/references/dark-mode.md +47 -0
  61. package/skills/migrate-core/references/topics/ui-alignment/references/layout-mapping.md +301 -0
  62. package/skills/migrate-core/references/topics/ui-alignment/references/visual-patterns.md +411 -0
  63. package/skills/migrate-core/scripts/closure/check-anchors.mjs +186 -0
  64. package/skills/migrate-core/scripts/closure/check-api-guards.mjs +175 -0
  65. package/skills/migrate-core/scripts/closure/check-consumers.mjs +301 -0
  66. package/skills/migrate-core/scripts/closure/check-permissions.mjs +165 -0
  67. package/skills/migrate-core/scripts/closure/check-resources.mjs +130 -0
  68. package/skills/migrate-core/scripts/closure/check-routes.mjs +527 -0
  69. package/skills/migrate-core/scripts/closure/check-safearea.mjs +122 -0
  70. package/skills/migrate-core/scripts/closure/check-stubs.mjs +69 -0
  71. package/skills/migrate-core/scripts/closure/closure-suite.mjs +256 -0
  72. package/skills/migrate-core/scripts/closure/idioms.json +105 -0
  73. package/skills/migrate-core/scripts/convert/convert-resources.mjs +437 -0
  74. package/skills/migrate-core/scripts/feasibility/feasibility.mjs +235 -0
  75. package/skills/migrate-core/scripts/feasibility/tables/cross-platform.json +11 -0
  76. package/skills/migrate-core/scripts/feasibility/tables/deprecated-api.json +10 -0
  77. package/skills/migrate-core/scripts/feasibility/tables/imported-arkts-core.json +425 -0
  78. package/skills/migrate-core/scripts/feasibility/tables/lib-equivalence.json +206 -0
  79. package/skills/migrate-core/scripts/feasibility/tables/system-capabilities.json +22 -0
  80. package/skills/migrate-core/scripts/front.mjs +107 -0
  81. package/skills/migrate-core/scripts/interface/ark-extract.mjs +172 -0
  82. package/skills/migrate-core/scripts/interface/interface.mjs +152 -0
  83. package/skills/migrate-core/scripts/ledger/ledger.mjs +383 -0
  84. package/skills/migrate-core/scripts/ledger/parse-cards.mjs +98 -0
  85. package/skills/migrate-core/scripts/lib/literals.mjs +37 -0
  86. package/skills/migrate-core/scripts/lib/scan.mjs +315 -0
  87. package/skills/migrate-core/scripts/smoke/align-sdk.mjs +118 -0
  88. package/skills/migrate-core/scripts/smoke/ensure-sign.mjs +53 -0
  89. package/skills/migrate-core/scripts/smoke/smoke.mjs +238 -0
  90. package/skills/migrate-core/scripts/smoke/verdict.mjs +31 -0
  91. package/skills/migrate-core/scripts/smoke/walk.mjs +480 -0
  92. package/skills/migrate-core/scripts/transpile/mapping.json +76 -0
  93. package/skills/migrate-core/scripts/transpile/transpile-layout.mjs +404 -0
  94. package/skills/migrate-core/scripts/vectors/run-arkts-vectors.mjs +107 -0
  95. package/skills/migrate-core/scripts/vectors/setup-arkts-test.mjs +90 -0
  96. package/skills/migrate-core/scripts/wire/extractors.mjs +258 -0
  97. package/skills/migrate-core/scripts/wire/wire-routes.mjs +507 -0
  98. package/skills/migrate-core/templates/acceptance.js +365 -0
  99. package/skills/migrate-core/templates/explore.js +86 -0
  100. package/skills/migrate-core/templates/implement.js +211 -0
  101. package/skills/migrate-core/templates/mig_slices.js +491 -0
@@ -0,0 +1,301 @@
1
+ # Android→ArkTS 布局映射参考
2
+
3
+ > 组件映射详细版 + 代码示例 + 布局对照 + 单位转换。
4
+
5
+ ---
6
+
7
+ ## 布局容器映射
8
+
9
+ ### LinearLayout → Column / Row
10
+
11
+ ```xml
12
+ <!-- Android 垂直布局 -->
13
+ <LinearLayout android:orientation="vertical">
14
+ <TextView ... />
15
+ <Button ... />
16
+ </LinearLayout>
17
+ ```
18
+
19
+ ```typescript
20
+ // ArkTS 等价(源未写 gravity = start;ArkUI 默认 Center,必须显式写)
21
+ Column() {
22
+ Text('标题')
23
+ Button('操作')
24
+ }
25
+ .alignItems(HorizontalAlign.Start)
26
+ ```
27
+
28
+ ```xml
29
+ <!-- Android 水平布局 -->
30
+ <LinearLayout android:orientation="horizontal"
31
+ android:gravity="center_vertical">
32
+ <ImageView ... />
33
+ <TextView ... />
34
+ </LinearLayout>
35
+ ```
36
+
37
+ ```typescript
38
+ // ArkTS 等价(gravity="center_vertical" → 显式 Center;源未写 gravity 时必须显式 Top)
39
+ Row() {
40
+ Image($r('app.media.icon'))
41
+ Text('标题')
42
+ }
43
+ .alignItems(VerticalAlign.Center)
44
+ ```
45
+
46
+ ### FrameLayout → Stack
47
+
48
+ ```xml
49
+ <!-- Android 帧布局(层叠) -->
50
+ <FrameLayout>
51
+ <ImageView ... />
52
+ <TextView android:gravity="bottom|center" ... />
53
+ </FrameLayout>
54
+ ```
55
+
56
+ ```typescript
57
+ // ArkTS 等价
58
+ Stack({ alignContent: Alignment.Bottom }) {
59
+ Image($r('app.media.cover'))
60
+ Text('叠加文字')
61
+ }
62
+ ```
63
+
64
+ ### ConstraintLayout / RelativeLayout → RelativeContainer(直接等价)
65
+
66
+ `RelativeContainer`(API 9+)是 ConstraintLayout 的直接等价物:给子组件设 `id`,用 `alignRules` 相对锚定父容器(`'__container__'`)或兄弟组件。简单线性布局也可用 Column/Row 嵌套。
67
+
68
+ ```typescript
69
+ RelativeContainer() {
70
+ Image($r('app.media.cover')).width(60).height(60)
71
+ .id('cover')
72
+ .alignRules({
73
+ top: { anchor: '__container__', align: VerticalAlign.Top },
74
+ left: { anchor: '__container__', align: HorizontalAlign.Start }
75
+ })
76
+ Text(this.title).fontSize(16)
77
+ .id('title')
78
+ .alignRules({
79
+ top: { anchor: 'cover', align: VerticalAlign.Top },
80
+ left: { anchor: 'cover', align: HorizontalAlign.End }
81
+ })
82
+ }
83
+
84
+ // 简单线性布局:Column + Row 嵌套即可
85
+ Column() {
86
+ Row() {
87
+ Image(this.coverUrl).width(60).height(60)
88
+ Column() {
89
+ Text(this.title).fontSize(16)
90
+ Text(this.subtitle).fontSize(12).fontColor('#99000000')
91
+ }.layoutWeight(1)
92
+ Button('操作')
93
+ }
94
+ }
95
+ ```
96
+
97
+ ### RelativeLayout → Row / Column + layoutWeight
98
+
99
+ ```typescript
100
+ // 左-中-右 布局
101
+ Row() {
102
+ Image(icon).width(40) // 左侧固定宽度
103
+ Column() { // 中间填充剩余空间
104
+ Text(title)
105
+ }.layoutWeight(1)
106
+ Text(time) // 右侧固定
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## 列表组件映射
113
+
114
+ ### RecyclerView → List + LazyForEach
115
+
116
+ ```java
117
+ // Android
118
+ RecyclerView recyclerView = findViewById(R.id.list);
119
+ recyclerView.setAdapter(new MyAdapter(items));
120
+ ```
121
+
122
+ ```typescript
123
+ // ArkTS — 使用 LazyForEach 虚拟化
124
+ List() {
125
+ LazyForEach(this.dataSource, (item: ItemModel) => {
126
+ ListItem() {
127
+ ItemComponent({ item: item })
128
+ }
129
+ }, (item: ItemModel) => item.id.toString())
130
+ }
131
+ ```
132
+
133
+ ### GridView → Grid
134
+
135
+ ```typescript
136
+ Grid() {
137
+ ForEach(this.items, (item: ItemModel) => {
138
+ GridItem() {
139
+ ItemComponent({ item: item })
140
+ }
141
+ })
142
+ }
143
+ .columnsTemplate('1fr 1fr 1fr') // 3 列等分
144
+ .rowsGap(8)
145
+ .columnsGap(8)
146
+ ```
147
+
148
+ ---
149
+
150
+ ## 交互组件映射
151
+
152
+ ### BottomNavigationView → 自定义 Row
153
+
154
+ ```typescript
155
+ // 不要用 Tabs 在 Navigation 内部!
156
+ Row() {
157
+ this.tabBarItem(0, $r('app.string.home'), $r('sys.symbol.house'))
158
+ this.tabBarItem(1, $r('app.string.queue'), $r('sys.symbol.list_bullet'))
159
+ this.tabBarItem(2, $r('app.string.inbox'), $r('sys.symbol.envelope'))
160
+ }
161
+ .width('100%')
162
+ .height(56)
163
+ .backgroundColor(Color.White)
164
+ .border({ width: { top: 0.5 }, color: '#E0E0E0' })
165
+ ```
166
+
167
+ ### DrawerLayout → SideBarContainer
168
+
169
+ ```typescript
170
+ SideBarContainer(SideBarContainerType.Embed) {
171
+ // 侧边栏内容
172
+ Column() { ... }
173
+ // 主内容
174
+ Column() { ... }
175
+ }
176
+ .showSideBar(this.showSidebar)
177
+ .sideBarWidth(280)
178
+ ```
179
+
180
+ ### ViewPager2 → Swiper
181
+
182
+ ```typescript
183
+ Swiper() {
184
+ ForEach(this.pages, (page: PageModel) => {
185
+ PageComponent({ data: page })
186
+ })
187
+ }
188
+ .index(this.currentPage)
189
+ .indicator(true)
190
+ ```
191
+
192
+ ### CardView → Column + 圆角 + 阴影
193
+
194
+ ```typescript
195
+ Column() {
196
+ // 卡片内容
197
+ }
198
+ .borderRadius(12)
199
+ .backgroundColor(Color.White)
200
+ .shadow({ radius: 4, color: '#1A000000', offsetY: 2 })
201
+ .padding(16)
202
+ ```
203
+
204
+ ### AlertDialog → getUIContext().getPromptAction().showDialog
205
+
206
+ ```typescript
207
+ // ⚠️ 全局 AlertDialog.show() 已废弃,V2 用 UIContext 形式
208
+ const res = await this.getUIContext().getPromptAction().showDialog({
209
+ title: '确认删除',
210
+ message: '删除后不可恢复',
211
+ buttons: [
212
+ { text: '取消', color: '#666666' },
213
+ { text: '删除', color: '#E84026' }
214
+ ]
215
+ });
216
+ if (res.index === 1) { this.doDelete(); }
217
+ ```
218
+
219
+ ### Snackbar → getUIContext().getPromptAction().showToast
220
+
221
+ ```typescript
222
+ // ⚠️ 全局 promptAction.showToast 已废弃,V2 用 UIContext 形式
223
+ this.getUIContext().getPromptAction().showToast({
224
+ message: '操作成功',
225
+ duration: 2000
226
+ });
227
+ ```
228
+
229
+ ---
230
+
231
+ ## 文本与表单控件映射
232
+
233
+ | Android | ArkUI | 说明 |
234
+ |---|---|---|
235
+ | `TextView` | `Text` | `android:text` → `Text('...')`;`textColor/textSize` → `.fontColor()/.fontSize()` |
236
+ | `EditText`(单行) | `TextInput` | `hint`→`placeholder`;`inputType`→`.type(InputType.*)`;`onTextChanged`→`.onChange()` |
237
+ | `EditText`(`textMultiLine`) | `TextArea` | 自动换行、可设高度 |
238
+ | `CheckBox` | `Checkbox` | `isChecked`→`.select()`;`OnCheckedChange`→`.onChange((on:boolean)=>{})` |
239
+ | `RadioButton` + `RadioGroup` | `Radio`(同 `group`) | 用一个 `selected` 值驱动各 `.checked()` |
240
+ | `Switch` / `SwitchCompat` | `Toggle({ type: ToggleType.Switch })` | `.onChange((on:boolean)=>{})` |
241
+ | `SeekBar` | `Slider` | `progress`→`value`;`max`→`max`;`.onChange((v,mode)=>{})` |
242
+ | `Spinner` | `Select` | 选项数组 → `Select([{ value: '...' }])`;`.onSelect((i,v)=>{})` |
243
+ | `SwipeRefreshLayout` | `Refresh` | 见下 |
244
+ | `ImageView` | `Image` | `src`→`Image($r('app.media.x'))`;`scaleType`→`.objectFit(ImageFit.*)`;**源未写 scaleType = fitCenter → 显式 `ImageFit.Contain`(ArkUI 默认 Cover 是裁剪)** |
245
+
246
+ ### SwipeRefreshLayout → Refresh
247
+
248
+ ```typescript
249
+ // V2 写法:不用 V1 的 $$ 双向绑定,onRefreshing 里自管状态
250
+ @Local isRefreshing: boolean = false
251
+
252
+ Refresh({ refreshing: this.isRefreshing }) {
253
+ List() { /* 列表内容 */ }
254
+ }
255
+ .onRefreshing(() => {
256
+ this.isRefreshing = true;
257
+ this.reload().then(() => { this.isRefreshing = false; });
258
+ })
259
+ ```
260
+
261
+ ---
262
+
263
+ ## 进度指示器映射
264
+
265
+ ### ProgressBar (Linear)
266
+
267
+ ```typescript
268
+ Progress({ value: this.percent, total: 100, type: ProgressType.Linear })
269
+ .height(2)
270
+ .width('100%')
271
+ .color('#007DFF')
272
+ .backgroundColor('#E0E0E0')
273
+ ```
274
+
275
+ ### ProgressBar (Circular / Ring)
276
+
277
+ ```typescript
278
+ Progress({ value: this.percent, total: 100, type: ProgressType.Ring })
279
+ .width(32)
280
+ .height(32)
281
+ .color('#007DFF')
282
+ .style({ strokeWidth: 3 })
283
+ ```
284
+
285
+ ---
286
+
287
+ ## 单位转换
288
+
289
+ | Android | ArkTS | 说明 |
290
+ |---------|-------|------|
291
+ | dp | vp | 1:1 对应,density-independent pixels |
292
+ | sp | fp | 1:1 对应;`fontSize` 数字默认单位就是 fp(官方默认字号 16fp),随系统字体缩放 |
293
+ | px | px | 物理像素:`'10px'` 字符串才是 px,裸数字是 vp。不推荐 |
294
+
295
+ 尺寸数值默认单位 vp、字号数值默认单位 fp,直接使用数字即可:
296
+
297
+ ```typescript
298
+ Text('标题')
299
+ .fontSize(16) // 16fp,等同于 Android 16sp
300
+ .margin({ top: 8 }) // 8vp,等同于 Android 8dp
301
+ ```
@@ -0,0 +1,411 @@
1
+ # 视觉模式参考
2
+
3
+ > 药丸 Tab 栏、封面 fallback、MiniPlayer、SymbolGlyph、阴影卡片。
4
+
5
+ ---
6
+
7
+ ## 药丸 Tab 栏完整代码
8
+
9
+ Material 3 风格底部导航栏(实测播客应用提取):
10
+
11
+ ```typescript
12
+ // 推荐 V2 写法(API 12+):tab 索引以 @ObservedV2 模型 + AppStorageV2.connect 共享
13
+ // AppStorageV2 是 @kit.ArkUI 的真实导出,用作值必须 import(否则报 Cannot find name 'AppStorageV2')
14
+ import { AppStorageV2 } from '@kit.ArkUI';
15
+
16
+ @ObservedV2
17
+ class TabIndexModel {
18
+ @Trace currentTabIndex: number = 0;
19
+ }
20
+
21
+ @ComponentV2
22
+ struct Index {
23
+ @Local tabModel: TabIndexModel = AppStorageV2.connect(TabIndexModel, 'currentTabIndex', () => new TabIndexModel())!;
24
+ navPathStack: NavPathStack = new NavPathStack();
25
+
26
+ @Builder
27
+ routerMap(name: string) { /* 按工程路由表注册 NavDestination(),记得 .hideTitleBar(true)(默认带系统标题栏+返回键,与 Android 无标题栏页面不符) */ }
28
+
29
+ @Builder
30
+ tabBarItem(index: number, title: Resource, icon: Resource) {
31
+ Column() {
32
+ // 药丸形灰色背景(选中态)
33
+ Column() {
34
+ SymbolGlyph(icon)
35
+ .fontSize(22)
36
+ .fontColor(this.tabModel.currentTabIndex === index ?
37
+ [Color.Black] : ['#99182431'])
38
+ }
39
+ .width(48)
40
+ .height(28)
41
+ .borderRadius(14) // 药丸形
42
+ .backgroundColor(this.tabModel.currentTabIndex === index ?
43
+ '#1F000000' : '#00000000') // 选中时 12% 黑色
44
+ .justifyContent(FlexAlign.Center)
45
+ .alignItems(HorizontalAlign.Center)
46
+
47
+ // 标签文字
48
+ Text(title)
49
+ .fontSize(10)
50
+ .fontColor(this.tabModel.currentTabIndex === index ?
51
+ '#182431' : '#99182431')
52
+ .margin({ top: 2 })
53
+ }
54
+ .layoutWeight(1) // 等分宽度
55
+ .justifyContent(FlexAlign.Center)
56
+ .height('100%')
57
+ .onClick(() => {
58
+ this.tabModel.currentTabIndex = index;
59
+ })
60
+ }
61
+
62
+ build() {
63
+ Column() {
64
+ // 内容区
65
+ Navigation(this.navPathStack) {
66
+ if (this.tabModel.currentTabIndex === 0) { HomeComponent() }
67
+ else if (this.tabModel.currentTabIndex === 1) { QueueComponent() }
68
+ else if (this.tabModel.currentTabIndex === 2) { InboxComponent() }
69
+ else if (this.tabModel.currentTabIndex === 3) { SubscriptionComponent() }
70
+ else { MoreComponent() }
71
+ }
72
+ .navDestination(this.routerMap)
73
+ .hideTitleBar(true)
74
+ .mode(NavigationMode.Stack)
75
+ .layoutWeight(1)
76
+
77
+ // 自定义 Tab 栏 — Navigation 外部
78
+ Row() {
79
+ this.tabBarItem(0, $r('app.string.tab_home'), $r('sys.symbol.house'))
80
+ this.tabBarItem(1, $r('app.string.tab_queue'), $r('sys.symbol.list_bullet'))
81
+ this.tabBarItem(2, $r('app.string.tab_inbox'), $r('sys.symbol.envelope'))
82
+ this.tabBarItem(3, $r('app.string.tab_subscriptions'),
83
+ $r('sys.symbol.square_grid_2x2'))
84
+ this.tabBarItem(4, $r('app.string.tab_more'),
85
+ $r('sys.symbol.line_3_horizontal'))
86
+ }
87
+ .width('100%')
88
+ .height(56)
89
+ .backgroundColor(Color.White)
90
+ .border({ width: { top: 0.5 }, color: '#E0E0E0' })
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ ### 关键设计点
97
+
98
+ - `layoutWeight(1)` 让每个 tab 等分宽度
99
+ - `borderRadius(14)` + `height(28)` 实现药丸形
100
+ - `'#1F000000'` = 12% 不透明黑色,作为选中指示器
101
+ - Tab 栏放在 `Column` 最底部,不被 `Navigation` 的子页面覆盖
102
+ - Tab 索引用 `AppStorageV2.connect` 共享(组件私有 `@Local` 无法被其他组件修改)
103
+
104
+ ---
105
+
106
+ ## 封面图 + Fallback 模式
107
+
108
+ 网络图片 + 文字首字母占位符(实测播客应用提取):
109
+
110
+ ```typescript
111
+ // 完整封面组件(含 fallback)
112
+ @Builder
113
+ coverImage(coverUrl: string, feedTitle: string,
114
+ size: number, radius: number) {
115
+ if (coverUrl.length > 0) {
116
+ // 有图片 URL → 显示网络图片
117
+ Image(coverUrl)
118
+ .width(size)
119
+ .height(size)
120
+ .borderRadius(radius)
121
+ .objectFit(ImageFit.Cover)
122
+ } else {
123
+ // 无图片 → 文字首字母占位符
124
+ Column() {
125
+ Text(feedTitle.length > 0 ?
126
+ feedTitle.charAt(0).toUpperCase() : '?')
127
+ .fontSize(size * 0.45)
128
+ .fontWeight(FontWeight.Bold)
129
+ .fontColor(Color.White)
130
+ }
131
+ .width(size)
132
+ .height(size)
133
+ .borderRadius(radius)
134
+ .backgroundColor('#BDBDBD')
135
+ .justifyContent(FlexAlign.Center)
136
+ .alignItems(HorizontalAlign.Center)
137
+ }
138
+ }
139
+
140
+ // 使用:
141
+ this.coverImage(this.currentCoverUrl, this.currentFeedTitle, 40, 6)
142
+ ```
143
+
144
+ ### 大尺寸封面(FullPlayer)
145
+
146
+ ```typescript
147
+ if (this.coverUrl.length > 0) {
148
+ Image(this.coverUrl)
149
+ .width(280)
150
+ .height(280)
151
+ .borderRadius(16)
152
+ .objectFit(ImageFit.Cover)
153
+ .shadow({ radius: 16, color: '#33000000', offsetY: 8 })
154
+ } else {
155
+ Column() {
156
+ Text(this.feedTitle.charAt(0).toUpperCase())
157
+ .fontSize(80)
158
+ .fontWeight(FontWeight.Bold)
159
+ .fontColor(Color.White)
160
+ }
161
+ .width(280)
162
+ .height(280)
163
+ .borderRadius(16)
164
+ .backgroundColor('#BDBDBD')
165
+ .justifyContent(FlexAlign.Center)
166
+ }
167
+ ```
168
+
169
+ ---
170
+
171
+ ## MiniPlayer 完整代码
172
+
173
+ 底部浮动播放条(实测播客应用提取):
174
+
175
+ ```typescript
176
+ // MiniPlayer — 在 Tab 栏上方,Navigation 外部
177
+ if (this.isPlayerVisible && !this.isFullPlayerVisible) {
178
+ Column() {
179
+ // 进度条
180
+ Progress({
181
+ value: this.getProgressPercent(),
182
+ total: 100,
183
+ type: ProgressType.Linear
184
+ })
185
+ .height(2)
186
+ .width('100%')
187
+ .color('#007DFF')
188
+ .backgroundColor('#E0E0E0')
189
+
190
+ // 播放信息栏
191
+ Row() {
192
+ // 封面图(含 fallback)
193
+ if (this.currentCoverUrl.length > 0) {
194
+ Image(this.currentCoverUrl)
195
+ .width(40).height(40)
196
+ .borderRadius(6)
197
+ .objectFit(ImageFit.Cover)
198
+ .margin({ right: 10 })
199
+ } else {
200
+ Column() {
201
+ Text(this.currentFeedTitle.length > 0 ?
202
+ this.currentFeedTitle.charAt(0).toUpperCase() : '?')
203
+ .fontSize(18)
204
+ .fontWeight(FontWeight.Bold)
205
+ .fontColor(Color.White)
206
+ }
207
+ .width(40).height(40)
208
+ .borderRadius(6)
209
+ .backgroundColor('#BDBDBD')
210
+ .justifyContent(FlexAlign.Center)
211
+ .alignItems(HorizontalAlign.Center)
212
+ .margin({ right: 10 })
213
+ }
214
+
215
+ // 标题和副标题
216
+ Column() {
217
+ Text(this.currentEpisodeTitle.length > 0 ?
218
+ this.currentEpisodeTitle : 'No episode playing')
219
+ .fontSize(14)
220
+ .maxLines(1)
221
+ .textOverflow({ overflow: TextOverflow.Ellipsis })
222
+ if (this.currentFeedTitle.length > 0) {
223
+ Text(this.currentFeedTitle)
224
+ .fontSize(12)
225
+ .fontColor('#99000000')
226
+ .maxLines(1)
227
+ .textOverflow({ overflow: TextOverflow.Ellipsis })
228
+ }
229
+ }
230
+ .layoutWeight(1)
231
+ .alignItems(HorizontalAlign.Start)
232
+
233
+ // 播放/暂停按钮
234
+ Column() {
235
+ SymbolGlyph(this.isPlaying ?
236
+ $r('sys.symbol.pause_fill') : $r('sys.symbol.play_fill'))
237
+ .fontSize(20)
238
+ .fontColor(['#333333'])
239
+ }
240
+ .width(36).height(36)
241
+ .borderRadius(18)
242
+ .backgroundColor('#E8E8E8')
243
+ .justifyContent(FlexAlign.Center)
244
+ .alignItems(HorizontalAlign.Center)
245
+ .margin({ left: 8 })
246
+ .onClick(() => {
247
+ PlaybackController.getInstance().playPause();
248
+ })
249
+ }
250
+ .alignItems(VerticalAlign.Center)
251
+ .padding({ left: 12, right: 16, top: 8, bottom: 8 })
252
+ .width('100%')
253
+ }
254
+ .width('100%')
255
+ .backgroundColor('#FAFAFA')
256
+ .shadow({ radius: 4, color: '#1A000000', offsetY: -2 })
257
+ .onClick(() => {
258
+ this.navPathStack.pushPathByName(RouteName.FULL_PLAYER, new Object());
259
+ })
260
+ }
261
+ ```
262
+
263
+ ### MiniPlayer 关键设计
264
+
265
+ - **位置**:在 `Navigation` 和 Tab 栏之间
266
+ - **条件显示**:`isPlayerVisible && !isFullPlayerVisible`
267
+ - **进度条**:`Progress` Linear 类型,高度 2vp
268
+ - **点击展开**:推送 FullPlayer NavDestination
269
+ - **阴影**:`offsetY: -2` 向上投影,与 Tab 栏分离
270
+
271
+ ---
272
+
273
+ ## SymbolGlyph 完整用法
274
+
275
+ ```typescript
276
+ // 基础用法
277
+ SymbolGlyph($r('sys.symbol.house'))
278
+ .fontSize(22)
279
+ .fontColor([Color.Black])
280
+
281
+ // 条件颜色
282
+ SymbolGlyph($r('sys.symbol.play_fill'))
283
+ .fontSize(20)
284
+ .fontColor(this.isActive ? ['#007DFF'] : ['#99182431'])
285
+
286
+ // 在按钮中使用
287
+ Button() {
288
+ SymbolGlyph($r('sys.symbol.plus'))
289
+ .fontSize(18)
290
+ .fontColor([Color.White])
291
+ }
292
+ .width(48).height(48)
293
+ .borderRadius(24)
294
+ .backgroundColor('#007DFF')
295
+
296
+ // 在列表项中使用
297
+ Row() {
298
+ SymbolGlyph($r('sys.symbol.chevron_right'))
299
+ .fontSize(16)
300
+ .fontColor(['#CCCCCC'])
301
+ }
302
+ ```
303
+
304
+ ---
305
+
306
+ ## 阴影卡片模式
307
+
308
+ ```typescript
309
+ Column() {
310
+ // 卡片内容
311
+ Row() {
312
+ Image(coverUrl).width(60).height(60).borderRadius(8)
313
+ Column() {
314
+ Text(title).fontSize(16).fontWeight(FontWeight.Medium)
315
+ Text(subtitle).fontSize(12).fontColor('#99000000')
316
+ }.layoutWeight(1).margin({ left: 12 })
317
+ }
318
+ .padding(16)
319
+ }
320
+ .width('100%')
321
+ .borderRadius(12)
322
+ .backgroundColor(Color.White)
323
+ .shadow({ radius: 4, color: '#1A000000', offsetY: 2 })
324
+ .margin({ left: 16, right: 16, top: 8 })
325
+ ```
326
+
327
+ ---
328
+
329
+ ## 圆形头像 / 圆形图片
330
+
331
+ ```typescript
332
+ Image(avatarUrl)
333
+ .width(48)
334
+ .height(48)
335
+ .borderRadius(24) // 宽高的一半 = 圆形
336
+ .objectFit(ImageFit.Cover)
337
+ ```
338
+
339
+ ---
340
+
341
+ ## 分割线
342
+
343
+ ```typescript
344
+ // 水平分割线
345
+ Divider()
346
+ .height(0.5)
347
+ .color('#E0E0E0')
348
+ .margin({ left: 16, right: 16 })
349
+
350
+ // 或使用 border
351
+ Column() { ... }
352
+ .border({ width: { bottom: 0.5 }, color: '#E0E0E0' })
353
+ ```
354
+
355
+ ---
356
+
357
+ ## 顶部分段 Tabs + Swiper 圆点指示 + 半透明顶栏(整段编译验证)
358
+
359
+ Android `TabLayout+ViewPager2` 顶部分段、Banner 圆点指示、半透明悬浮顶栏的标准还原:
360
+
361
+ ```typescript
362
+ @Entry
363
+ @ComponentV2
364
+ struct Index {
365
+ @Local idx: number = 0
366
+ @Local banners: string[] = ['一', '二', '三']
367
+ build() {
368
+ Stack({ alignContent: Alignment.Top }) {
369
+ Column() {
370
+ // ① 顶部分段切换:原生 Tabs(标准做法,不手搓 Row+Swiper)
371
+ Tabs({ barPosition: BarPosition.Start, index: this.idx }) {
372
+ TabContent() { Text('推荐页') }.tabBar('推荐')
373
+ TabContent() { Text('关注页') }.tabBar('关注')
374
+ TabContent() { Text('热榜页') }.tabBar('热榜')
375
+ }
376
+ .onChange((i: number) => { this.idx = i }) // 点击/滑动双向同步 Tabs 自带
377
+ .layoutWeight(1)
378
+
379
+ // ② Banner 自动轮播 + 内建圆点指示器(别手搓一排 dots)
380
+ Swiper() {
381
+ ForEach(this.banners, (s: string) => {
382
+ Text(s).width('100%').height(120).backgroundColor('#EEEEEE').textAlign(TextAlign.Center)
383
+ }, (s: string) => s)
384
+ }
385
+ .autoPlay(true).interval(3000).loop(true)
386
+ .indicator(
387
+ new DotIndicator()
388
+ .selectedColor('#007DFF') // 选中点
389
+ .color('#33000000') // 未选中点
390
+ .left(16).bottom(12) // 左下定位
391
+ )
392
+ .height(120)
393
+ }
394
+
395
+ // ③ 半透明悬浮顶栏:用 8 位 ARGB 十六进制背景(不要 .opacity)
396
+ Row() {
397
+ Text('半透明顶栏').fontColor('#182431').fontSize(16)
398
+ }
399
+ .width('100%').height(48).padding({ left: 16 })
400
+ .backgroundColor('#F2FFFFFF') // ✓ ARGB 背景,前景不透明
401
+ .hitTestBehavior(HitTestMode.Transparent) // 透传点击给下层
402
+ }
403
+ .width('100%').height('100%')
404
+ }
405
+ }
406
+ ```
407
+
408
+ **三个还原要点**:
409
+ 1. **顶部分段 Tab → 原生 `Tabs({barPosition: BarPosition.Start}) + TabContent().tabBar(...)`**;横向滚动多 Tab 加 `.barMode(BarMode.Scrollable)`。**不要**手搓 `Row` 页签 + `Swiper` 同步(回避、还原差)。
410
+ 2. **Banner 圆点指示 → `Swiper().indicator(new DotIndicator().selectedColor(...).color(...).left(n).bottom(n))`**;别 `.indicator(false)` 再手搓一排 `Column` 点。
411
+ 3. **半透明背景 → 8 位 ARGB 十六进制色**(`#AARRGGBB`,如 `#F2FFFFFF` ≈ 95% 不透明白)作 `.backgroundColor(...)`;**绝不** `.opacity(n)`(会连前景文字/图标一起调淡)。