@buaa_smat/hometrans 0.1.0 → 0.1.1

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 (128) hide show
  1. package/README.md +141 -124
  2. package/agents/self-tester.md +259 -233
  3. package/agents/test-tools/autotest/README.md +223 -0
  4. package/agents/test-tools/autotest/config.yaml.example +58 -0
  5. package/agents/test-tools/autotest/pyproject.toml +16 -0
  6. package/agents/test-tools/autotest/report_tool.py +759 -0
  7. package/agents/test-tools/autotest/self_test_runner.py +773 -0
  8. package/agents/test-tools/autotest/testcases_schema.md +143 -0
  9. package/agents/test-tools/autotest/testcases_tool.py +215 -0
  10. package/agents/test-tools/autotest/uv.lock +3156 -0
  11. package/agents/test-tools/harmony_autotest-0.1.0-py3-none-any.whl +0 -0
  12. package/agents/test-tools/hypium-6.1.0.210-py3-none-any.whl +0 -0
  13. package/agents/test-tools/hypium_mcp-0.6.5-py3-none-any.whl +0 -0
  14. package/agents/test-tools/xdevice-6.1.0.210-py3-none-any.whl +0 -0
  15. package/agents/test-tools/xdevice_devicetest-6.1.0.210-py3-none-any.whl +0 -0
  16. package/agents/test-tools/xdevice_ohos-6.1.0.210-py3-none-any.whl +0 -0
  17. package/dist/cli/config-store.js +27 -2
  18. package/dist/cli/config.js +17 -6
  19. package/dist/cli/index.js +3 -2
  20. package/dist/cli/init.js +135 -22
  21. package/dist/cli/mcp.js +2 -2
  22. package/dist/context/index.js +165 -69
  23. package/package.json +59 -60
  24. package/skills/code-dev-review-fix/SKILL.md +279 -0
  25. package/skills/code-dev-review-fix-workspace/evals/evals.json +56 -0
  26. package/skills/code-dev-review-fix-workspace/iteration-1/routing-results.md +23 -0
  27. package/skills/convert_pipeline/SKILL.md +423 -439
  28. package/skills/hmos-resources-convert/SKILL.md +623 -0
  29. package/skills/hmos-resources-convert/evals/evals.json +171 -0
  30. package/skills/hmos-resources-convert/references/conversion-rules.md +663 -0
  31. package/skills/hmos-resources-convert/references/dependency-analysis-rules.md +388 -0
  32. package/skills/hmos-resources-convert/references/resource-mapping-rules.md +457 -0
  33. package/skills/hmos-resources-convert/references/xml-drawable-to-svg-rules.md +513 -0
  34. package/skills/hmos-resources-convert/template/AppScope/app.json5 +10 -0
  35. package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +8 -0
  36. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
  37. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
  38. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +7 -0
  39. package/skills/hmos-resources-convert/template/build-profile.json5 +42 -0
  40. package/skills/hmos-resources-convert/template/code-linter.json5 +32 -0
  41. package/skills/hmos-resources-convert/template/entry/build-profile.json5 +33 -0
  42. package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +6 -0
  43. package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +23 -0
  44. package/skills/hmos-resources-convert/template/entry/oh-package.json5 +10 -0
  45. package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +48 -0
  46. package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +16 -0
  47. package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +23 -0
  48. package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +55 -0
  49. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +8 -0
  50. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +8 -0
  51. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +16 -0
  52. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
  53. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
  54. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +7 -0
  55. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
  56. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +3 -0
  57. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +5 -0
  58. package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +8 -0
  59. package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +2 -0
  60. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +35 -0
  61. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +5 -0
  62. package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +16 -0
  63. package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +5 -0
  64. package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +33 -0
  65. package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +23 -0
  66. package/skills/hmos-resources-convert/template/hvigorfile.ts +6 -0
  67. package/skills/hmos-resources-convert/template/oh-package-lock.json5 +28 -0
  68. package/skills/hmos-resources-convert/template/oh-package.json5 +10 -0
  69. package/skills/hmos-resources-convert/tools/apktool.bat +85 -0
  70. package/skills/hmos-resources-convert/tools/apktool_3.0.1.jar +0 -0
  71. package/skills/hmos-ui-align/SKILL.md +182 -0
  72. package/skills/hmos-ui-align/config-example.json +11 -0
  73. package/skills/hmos-ui-align/config.json +11 -0
  74. package/skills/hmos-ui-align/diff_analysis.md +53 -0
  75. package/skills/hmos-ui-align/page_align.md +62 -0
  76. package/skills/hmos-ui-align/readme.md +231 -0
  77. package/skills/hmos-ui-align/references/Comparison_Template.md +2 -0
  78. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
  79. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
  80. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
  81. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
  82. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
  83. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
  84. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
  85. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
  86. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  87. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
  88. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217V1.md +911 -0
  89. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -0
  90. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
  91. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
  92. package/skills/hmos-ui-align/references/UI_Analysis_Template.md +4 -0
  93. package/skills/hmos-ui-align/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2535 -0
  94. package/skills/hmos-ui-align/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
  95. package/skills/hmos-ui-align/references/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
  96. package/skills/hmos-ui-align/scripts/app_feature_verify.py +443 -0
  97. package/skills/hmos-ui-align/scripts/navigation-capure.md +37 -0
  98. package/skills/hmos-ui-align/scripts/page_capture.py +592 -0
  99. package/skills/hmos-ui-align-batch/SKILL.md +99 -0
  100. package/skills/hmos-ui-align-batch/references/conversion-procedure.md +180 -0
  101. package/skills/hmos-ui-align-batch/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2535 -0
  102. package/skills/hmos-ui-align-batch/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
  103. package/skills/hmos-ui-align-batch/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
  104. package/skills/hmos-ui-align-batch/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
  105. package/skills/hmos-ui-align-batch/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
  106. package/skills/hmos-ui-align-batch/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
  107. package/skills/hmos-ui-align-batch/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
  108. package/skills/hmos-ui-align-batch/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
  109. package/skills/hmos-ui-align-batch/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
  110. package/skills/hmos-ui-align-batch/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
  111. package/skills/hmos-ui-align-batch/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
  112. package/skills/hmos-ui-align-batch/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  113. package/skills/hmos-ui-align-batch/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
  114. package/skills/hmos-ui-align-batch/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -0
  115. package/skills/hmos-ui-align-batch/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
  116. package/skills/hmos-ui-align-batch/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
  117. package/skills/hmos-ui-align-batch/scripts/android_parse_fast.py +1606 -0
  118. package/skills/self-test/SKILL.md +369 -0
  119. package/skills/self-test/readme.md +309 -0
  120. package/skills/spec-generator-skill/SKILL.md +332 -0
  121. package/skills/spec-generator-skill/references/android-platform-tokens.md +105 -0
  122. package/skills/spec-generator-skill/references/spec-sample-1.md +78 -0
  123. package/skills/spec-generator-skill/references/spec-sample-2.md +58 -0
  124. package/skills/spec-generator-skill/references/spec-sample-3.md +116 -0
  125. package/skills/spec-generator-skill/references/step4-report-template.md +33 -0
  126. package/agents/self-test-setup.md +0 -165
  127. package/dist/context/resources/sdkConfig.json +0 -24
  128. package/src/context/resources/sdkConfig.json +0 -24
@@ -0,0 +1,911 @@
1
+ # MVVM模式(V1)
2
+
3
+ 当开发者掌握了状态管理的基本概念后,通常会尝试开发自己的应用,在应用开发初期,如果未能精心规划项目结构,随着项目扩展和复杂化,状态变量的增多将导致组件间关系变得错综复杂。此时,开发新功能可能引起连锁反应,维护成本也会增加。为此,本文旨在介绍MVVM模式以及ArkUI的UI开发模式与MVVM的关系,指导开发者如何设计项目结构,以便在产品迭代和升级时能更轻松地开发和维护。
4
+
5
+ 本文档涵盖了大多数状态管理V1装饰器,所以在阅读本文档前,建议开发者对状态管理V1有一定的了解。建议提前阅读:[状态管理概述](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management-overview)和状态管理V1装饰器相关文档。
6
+
7
+ ## MVVM模式介绍
8
+
9
+ ### 概念
10
+
11
+ 在应用开发中,UI更新需要实时同步数据状态变化,这直接影响应用程序的性能和用户体验。为了解决数据与UI同步的复杂性,ArkUI采用了 Model-View-ViewModel(MVVM)架构模式。MVVM 将应用分为Model、View和ViewModel三个核心部分,实现数据、视图与逻辑的分离。通过这种模式,UI可以自动更新状态变化,从而更高效地管理数据和视图的绑定与更新。
12
+
13
+ - **View**:用户界面层。负责用户界面展示并与用户交互,不包含任何业务逻辑。它通过绑定ViewModel层提供的数据实现动态更新。
14
+ - **Model**:数据访问层。以数据为中心,不直接与用户界面交互。负责数据结构定义,数据管理(获取、存储、更新等),以及业务逻辑处理。
15
+ - **ViewModel**:表示逻辑层。作为连接Model和View的桥梁,通常一个View对应一个ViewModel。View和ViewModel有两种通信方式:
16
+ 1. 方法调用:View通过事件监听用户行为,在回调里面触发ViewModel层的方法。例如当View监听到用户Button点击行为,调用ViewModel对应的方法,处理用户操作。
17
+ 2. 双向绑定:View绑定ViewModel的数据,实现双向同步。
18
+
19
+ ArkUI的UI开发模式就属于MVVM模式,通过对MVVM概念的基本介绍,开发者大致能猜到状态管理能在MVVM中起什么样的作用,状态管理旨在数据驱动更新,让开发者只用关注页面设计,而不去关注整个UI的刷新逻辑,数据的维护也无需开发者进行感知,由状态变量自动更新完成,而这就是属于ViewModel层所需要支持的内容,因此开发者使用MVVM模式开发自己的应用是最省心省力的。
20
+
21
+ ### ArkUI开发模式图
22
+
23
+ ArkUI的UI开发模式即是MVVM模式,而状态变量在MVVM模式中扮演着ViewModel的角色,向上刷新UI,向下更新数据
24
+
25
+ ### 分层说明
26
+
27
+ **View层**
28
+
29
+ View层通常可以分为下列组件:
30
+
31
+ - **页面组件**:所有应用基本都是按照页面进行分类的,比如登录页,列表页,编辑页,帮助页,版权页等。每个页面对应需要的数据可能是完全不一样的,也可能多个页面需要的数据是同一套。
32
+ - **业务组件**:本身具备本APP部分业务能力的功能组件,典型的就是这个业务组件可能关联了本项目的ViewModel中的数据,不可以被共享给其他项目使用。
33
+ - **通用组件**:像系统组件一样,这类组件不会关联本APP中ViewModel的数据,这些组件可实现跨越多个项目进行共享,来完成比较通用的功能。
34
+
35
+ **Model层**
36
+
37
+ Model层是应用的原始数据提供者,代表应用的核心业务逻辑和数据。
38
+
39
+ **ViewModel层**
40
+
41
+ 为View层的组件提供对应数据,按照页面组织,当用户浏览页面时,某些页面可能不会被显示,因此,页面数据最好设计成懒加载(按需加载)的模式。
42
+
43
+ ViewModel层数据和Model层数据的区别:
44
+
45
+ - Model层数据是按照整个工程、项目来组织数据,构成一套完整的APP业务数据体系。
46
+ - ViewModel层数据,是提供某个页面上使用的数据,它可能是整个APP的业务数据的一部分。另外ViewModel层还可以附加对应Page的辅助页面显示数据,这部分数据可能与本APP的业务完全无关,仅仅是为页面展示提供便利的辅助数据。
47
+
48
+ ### 架构核心原则
49
+
50
+ **不可跨层访问**
51
+
52
+ - View层不可以直接调用Model层的数据,只能通过ViewModel提供的方法进行调用。
53
+ - Model层不能直接操作UI,只能通知ViewModel层数据有更新,由ViewModel层更新对应的数据。
54
+
55
+ **下层不可访问上层数据**
56
+
57
+ 下层数据通过通知模式更新上层数据。在业务逻辑中,下层不可直接获取上层数据。例如,ViewModel层的逻辑处理不应该依赖View层界面上的某个值。
58
+
59
+ **非父子组件间不可直接访问**
60
+
61
+ 这是针对View层设计的核心原则,一个组件应该具备以下逻辑:
62
+
63
+ - 禁止直接访问父组件(必须使用事件或是订阅能力)。
64
+ - 禁止直接访问兄弟组件。这是因为组件应该仅能访问自己的子节点(通过传参)和父节点(通过事件或通知),以此完成组件之间的解耦。
65
+
66
+ 对于一个组件,这样设计的原因如下:
67
+
68
+ - 组件自己使用了哪些子组件是明确的,因此可以访问。
69
+ - 组件被放置于哪个父节点下是未知的,因此组件想访问父节点,就只能通过通知或者事件能力完成。
70
+ - 组件不可能知道自己的兄弟节点是谁,因此组件不可以操作兄弟节点。
71
+
72
+ ## 备忘录开发实战
73
+
74
+ 本节通过备忘录应用的开发,使开发者了解如何使用ArkUI框架设计自己的应用。本节直接进行功能开发,未设计代码架构,即根据需求即时开发,不考虑后续维护,同时,本节还将介绍功能开发所需的装饰器。
75
+
76
+ ### @State状态变量
77
+
78
+ [@State](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state)装饰器是最常用的装饰器之一,用于定义状态变量。通常,这些状态变量作为父组件的数据源,开发者点击时,触发状态变量的更新,刷新UI。
79
+
80
+ ```typescript
81
+ @Entry
82
+ @Component
83
+ struct StateIndex {
84
+ @State isFinished: boolean = false;
85
+
86
+ build() {
87
+ Column() {
88
+ Row() {
89
+ // 请将$r('app.string.all_tasks')替换为实际资源文件,在本示例中该资源文件的value值为"全部待办"
90
+ Text($r('app.string.all_tasks'))
91
+ .fontSize(30)
92
+ .fontWeight(FontWeight.Bold)
93
+ }
94
+ .width('100%')
95
+ .margin({ top: 10, bottom: 10 })
96
+
97
+ // 待办事项
98
+ Row({ space: 15 }) {
99
+ if (this.isFinished) {
100
+ // 请将$r('app.media.finished')替换为实际资源文件
101
+ Image($r('app.media.finished'))
102
+ .width(28)
103
+ .height(28)
104
+ } else {
105
+ // 请将$r('app.media.unfinished')替换为实际资源文件
106
+ Image($r('app.media.unfinished'))
107
+ .width(28)
108
+ .height(28)
109
+ }
110
+ // 请将$r('app.string.all_learn_advanced_math')替换为实际资源文件,在本示例中该资源文件的value值为"学习高数"
111
+ Text($r('app.string.learn_advanced_math'))
112
+ .fontSize(24)
113
+ .decoration({ type: this.isFinished ? TextDecorationType.LineThrough : TextDecorationType.None })
114
+ }
115
+ .height('40%')
116
+ .width('100%')
117
+ .border({ width: 5 })
118
+ .padding({ left: 15 })
119
+ .onClick(() => {
120
+ this.isFinished = !this.isFinished;
121
+ })
122
+ }
123
+ .height('100%')
124
+ .width('100%')
125
+ .margin({ top: 5, bottom: 5 })
126
+ .backgroundColor('#90f1f3f5')
127
+ }
128
+ }
129
+ ```
130
+
131
+ ### @Prop、@Link的作用
132
+
133
+ 上述示例中,所有代码都写在了@Entry组件中。随着需要渲染的组件越来越多,@Entry组件必然需要进行拆分,为此,拆分出的子组件就需要使用@Prop和@Link装饰器:
134
+
135
+ - [@Prop](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-prop)是父子间单向传递,子组件会深拷贝父组件数据,可从父组件更新,也可自己更新数据,但不会同步回父组件。
136
+ - [@Link](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-link)是父子间双向传递,父组件改变,会通知所有的@Link,同时@Link的更新也会通知父组件的数据源进行刷新。
137
+
138
+ ```typescript
139
+ @Component
140
+ struct PropLinkTodoComponent {
141
+ build() {
142
+ Row() {
143
+ // 请将$r('app.string.all_tasks')替换为实际资源文件,在本示例中该资源文件的value值为"全部待办"
144
+ Text($r('app.string.all_tasks'))
145
+ .fontSize(30)
146
+ .fontWeight(FontWeight.Bold)
147
+ }
148
+ .width('100%')
149
+ .margin({ top: 10, bottom: 10 })
150
+ }
151
+ }
152
+
153
+ @Component
154
+ struct PropLinkAllChooseComponent {
155
+ @Link isFinished: boolean;
156
+
157
+ build() {
158
+ Row() {
159
+ // 请将$r('app.string.check_all')替换为实际资源文件,在本示例中该资源文件的value值为"全选"
160
+ Button($r('app.string.check_all'), { type: ButtonType.Normal })
161
+ .onClick(() => {
162
+ this.isFinished = !this.isFinished;
163
+ })
164
+ .fontSize(30)
165
+ .fontWeight(FontWeight.Bold)
166
+ .backgroundColor('#f7f6cc74')
167
+ }
168
+ .padding({ left: 15 })
169
+ .width('100%')
170
+ .margin({ top: 10, bottom: 10 })
171
+ }
172
+ }
173
+
174
+ @Component
175
+ struct ThingComponent1 {
176
+ @Prop isFinished: boolean;
177
+
178
+ build() {
179
+ // 待办事项1
180
+ Row({ space: 15 }) {
181
+ if (this.isFinished) {
182
+ // 请将$r('app.media.finished')替换为实际资源文件
183
+ Image($r('app.media.finished'))
184
+ .width(28)
185
+ .height(28)
186
+ } else {
187
+ // 请将$r('app.media.unfinished')替换为实际资源文件
188
+ Image($r('app.media.unfinished'))
189
+ .width(28)
190
+ .height(28)
191
+ }
192
+ // 请将$r('app.string.learn_chinese')替换为实际资源文件,在本示例中该资源文件的value值为"学习语文"
193
+ Text($r('app.string.learn_chinese'))
194
+ .fontSize(24)
195
+ .decoration({ type: this.isFinished ? TextDecorationType.LineThrough : TextDecorationType.None })
196
+ }
197
+ .height('40%')
198
+ .width('100%')
199
+ .border({ width: 5 })
200
+ .padding({ left: 15 })
201
+ .onClick(() => {
202
+ this.isFinished = !this.isFinished;
203
+ })
204
+ }
205
+ }
206
+
207
+ @Component
208
+ struct ThingComponent2 {
209
+ @Prop isFinished: boolean;
210
+
211
+ build() {
212
+ // 待办事项1
213
+ Row({ space: 15 }) {
214
+ if (this.isFinished) {
215
+ // 请将$r('app.media.finished')替换为实际资源文件
216
+ Image($r('app.media.finished'))
217
+ .width(28)
218
+ .height(28)
219
+ } else {
220
+ // 请将$r('app.media.unfinished')替换为实际资源文件
221
+ Image($r('app.media.unfinished'))
222
+ .width(28)
223
+ .height(28)
224
+ }
225
+ // 请将$r('app.string.learn_advanced_math')替换为实际资源文件,在本示例中该资源文件的value值为"学习高数"
226
+ Text($r('app.string.learn_advanced_math'))
227
+ .fontSize(24)
228
+ .decoration({ type: this.isFinished ? TextDecorationType.LineThrough : TextDecorationType.None })
229
+ }
230
+ .height('40%')
231
+ .width('100%')
232
+ .border({ width: 5 })
233
+ .padding({ left: 15 })
234
+ .onClick(() => {
235
+ this.isFinished = !this.isFinished;
236
+ })
237
+ }
238
+ }
239
+
240
+ @Entry
241
+ @Component
242
+ struct PropLinkIndex {
243
+ @State isFinished: boolean = false;
244
+
245
+ build() {
246
+ Column() {
247
+ // 全部待办
248
+ PropLinkTodoComponent()
249
+
250
+ // 全选
251
+ PropLinkAllChooseComponent({ isFinished: this.isFinished })
252
+
253
+ // 待办事项1
254
+ ThingComponent1({ isFinished: this.isFinished })
255
+
256
+ // 待办事项2
257
+ ThingComponent2({ isFinished: this.isFinished })
258
+ }
259
+ .height('100%')
260
+ .width('100%')
261
+ .margin({ top: 5, bottom: 5 })
262
+ .backgroundColor('#90f1f3f5')
263
+ }
264
+ }
265
+ ```
266
+
267
+ ### 循环渲染组件
268
+
269
+ - 上个示例虽然拆分出了子组件,但发现组件1和组件2的代码非常相似,当渲染的组件除了数据外,其他设置都相同时,此时就需要使用[ForEach循环渲染](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-rendering-control-foreach)。
270
+ - ForEach使用之后,冗余代码变得更少,并且代码结构更加清晰。
271
+
272
+ ```typescript
273
+ @Component
274
+ struct ForEachTodoComponent {
275
+ build() {
276
+ Row() {
277
+ // 请将$r('app.string.all_tasks')替换为实际资源文件,在本示例中该资源文件的value值为"全部待办"
278
+ Text($r('app.string.all_tasks'))
279
+ .fontSize(30)
280
+ .fontWeight(FontWeight.Bold)
281
+ }
282
+ .width('100%')
283
+ .margin({ top: 10, bottom: 10 })
284
+ }
285
+ }
286
+
287
+ @Component
288
+ struct ForEachAllChooseComponent {
289
+ @Link isFinished: boolean;
290
+
291
+ build() {
292
+ Row() {
293
+ // 请将$r('app.string.check_all')替换为实际资源文件,在本示例中该资源文件的value值为"全选"
294
+ Button($r('app.string.check_all'), { type: ButtonType.Normal })
295
+ .onClick(() => {
296
+ this.isFinished = !this.isFinished;
297
+ })
298
+ .fontSize(30)
299
+ .fontWeight(FontWeight.Bold)
300
+ .backgroundColor('#f7f6cc74')
301
+ }
302
+ .padding({ left: 15 })
303
+ .width('100%')
304
+ .margin({ top: 10, bottom: 10 })
305
+ }
306
+ }
307
+
308
+ @Component
309
+ struct ForEachThingComponent {
310
+ @Prop isFinished: boolean;
311
+ @Prop thing: string;
312
+
313
+ build() {
314
+ // 待办事项1
315
+ Row({ space: 15 }) {
316
+ if (this.isFinished) {
317
+ // 请将$r('app.media.finished')替换为实际资源文件
318
+ Image($r('app.media.finished'))
319
+ .width(28)
320
+ .height(28)
321
+ } else {
322
+ // 请将$r('app.media.unfinished')替换为实际资源文件
323
+ Image($r('app.media.unfinished'))
324
+ .width(28)
325
+ .height(28)
326
+ // ...
327
+ }
328
+ Text(`${this.thing}`)
329
+ .fontSize(24)
330
+ .decoration({ type: this.isFinished ? TextDecorationType.LineThrough : TextDecorationType.None })
331
+ }
332
+ .height('8%')
333
+ .width('90%')
334
+ .padding({ left: 15 })
335
+ .opacity(this.isFinished ? 0.3 : 1)
336
+ .border({ width: 1 })
337
+ .borderColor(Color.White)
338
+ .borderRadius(25)
339
+ .backgroundColor(Color.White)
340
+ .onClick(() => {
341
+ this.isFinished = !this.isFinished;
342
+ })
343
+ }
344
+ }
345
+
346
+ @Entry
347
+ @Component
348
+ struct ForEachIndex {
349
+ @State isFinished: boolean = false;
350
+ @State planList: ResourceStr[] = [
351
+ // 请将$r('app.string.get_up')替换为实际资源文件,在本示例中该资源文件的value值为"7.30 起床"
352
+ $r('app.string.get_up'),
353
+ // 请将$r('app.string.breakfast')替换为实际资源文件,在本示例中该资源文件的value值为"8.30 早餐"
354
+ $r('app.string.breakfast'),
355
+ // 请将$r('app.string.lunch')替换为实际资源文件,在本示例中该资源文件的value值为"11.30 中餐"
356
+ $r('app.string.lunch'),
357
+ // 请将$r('app.string.dinner')替换为实际资源文件,在本示例中该资源文件的value值为"17.30 晚餐"
358
+ $r('app.string.dinner'),
359
+ // 请将$r('app.string.midnight_snack')替换为实际资源文件,在本示例中该资源文件的value值为"21.30 夜宵"
360
+ $r('app.string.midnight_snack'),
361
+ // 请将$r('app.string.bathe')替换为实际资源文件,在本示例中该资源文件的value值为"22.30 洗澡"
362
+ $r('app.string.bathe'),
363
+ // 请将$r('app.string.sleep')替换为实际资源文件,在本示例中该资源文件的value值为"1.30 睡觉"
364
+ $r('app.string.sleep')
365
+ ];
366
+ context1 = this.getUIContext().getHostContext();
367
+
368
+ aboutToAppear(): void {
369
+ for (let i = 0; i < this.planList.length; i++) {
370
+ this.planList[i] = this.context1!.resourceManager.getStringSync((this.planList[i] as Resource).id);
371
+ };
372
+ }
373
+
374
+ build() {
375
+ Column() {
376
+ // 全部待办
377
+ ForEachTodoComponent()
378
+
379
+ // 全选
380
+ ForEachAllChooseComponent({ isFinished: this.isFinished })
381
+
382
+ List() {
383
+ ForEach(this.planList, (item: string) => {
384
+ // 待办事项1
385
+ ForEachThingComponent({ isFinished: this.isFinished, thing: item })
386
+ .margin(5)
387
+ })
388
+ }
389
+ }
390
+ .height('100%')
391
+ .width('100%')
392
+ .margin({ top: 5, bottom: 5 })
393
+ .backgroundColor('#90f1f3f5')
394
+ }
395
+ }
396
+ ```
397
+
398
+ ### @Builder方法
399
+
400
+ - Builder方法用于组件内定义方法,可以使得相同代码可以在组件内进行复用。
401
+ - 本示例不仅使用了[@Builder](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-builder)方法进行去重,还对数据进行了移除,可以看到此时代码更加清晰易读,相对于最开始的代码,@Entry组件基本只用于处理页面构建逻辑,而不处理大量与页面设计无关的内容。
402
+
403
+ ```typescript
404
+ @Observed
405
+ class TodoListData {
406
+ public planList: ResourceStr[] = [
407
+ // 请将$r('app.string.get_up')替换为实际资源文件,在本示例中该资源文件的value值为"7.30 起床"
408
+ $r('app.string.get_up'),
409
+ // 请将$r('app.string.breakfast')替换为实际资源文件,在本示例中该资源文件的value值为"8.30 早餐"
410
+ $r('app.string.breakfast'),
411
+ // 请将$r('app.string.lunch')替换为实际资源文件,在本示例中该资源文件的value值为"11.30 中餐"
412
+ $r('app.string.lunch'),
413
+ // 请将$r('app.string.dinner')替换为实际资源文件,在本示例中该资源文件的value值为"17.30 晚餐"
414
+ $r('app.string.dinner'),
415
+ // 请将$r('app.string.midnight_snack')替换为实际资源文件,在本示例中该资源文件的value值为"21.30 夜宵"
416
+ $r('app.string.midnight_snack'),
417
+ // 请将$r('app.string.bathe')替换为实际资源文件,在本示例中该资源文件的value值为"22.30 洗澡"
418
+ $r('app.string.bathe'),
419
+ // 请将$r('app.string.sleep')替换为实际资源文件,在本示例中该资源文件的value值为"1.30 睡觉"
420
+ $r('app.string.sleep')
421
+ ];
422
+ }
423
+
424
+ @Component
425
+ struct StateTodoComponent {
426
+ build() {
427
+ Row() {
428
+ // 请将$r('app.string.all_tasks')替换为实际资源文件,在本示例中该资源文件的value值为"全部待办"
429
+ Text($r('app.string.all_tasks'))
430
+ .fontSize(30)
431
+ .fontWeight(FontWeight.Bold)
432
+ }
433
+ .width('100%')
434
+ .margin({ top: 10, bottom: 10 })
435
+ }
436
+ }
437
+
438
+ @Component
439
+ struct BuilderAllChooseComponent {
440
+ @Link isFinished: boolean;
441
+
442
+ build() {
443
+ Row() {
444
+ // 请将$r('app.string.check_all')替换为实际资源文件,在本示例中该资源文件的value值为"全选"
445
+ Button($r('app.string.check_all'), { type: ButtonType.Capsule })
446
+ .onClick(() => {
447
+ this.isFinished = !this.isFinished;
448
+ })
449
+ .fontSize(30)
450
+ .fontWeight(FontWeight.Bold)
451
+ .backgroundColor('#f7f6cc74')
452
+ }
453
+ .padding({ left: 15 })
454
+ .width('100%')
455
+ .margin({ top: 10, bottom: 10 })
456
+ }
457
+ }
458
+
459
+ @Component
460
+ struct BuilderThingComponent {
461
+ @Prop isFinished: boolean;
462
+ @Prop thing: string;
463
+
464
+ @Builder
465
+ displayIcon(icon: Resource) {
466
+ Image(icon)
467
+ .width(28)
468
+ .height(28)
469
+ .onClick(() => {
470
+ this.isFinished = !this.isFinished;
471
+ })
472
+ // ...
473
+ }
474
+
475
+ build() {
476
+ // 待办事项1
477
+ Row({ space: 15 }) {
478
+ if (this.isFinished) {
479
+ // 请将$r('app.media.finished')替换为实际资源文件
480
+ this.displayIcon($r('app.media.finished'));
481
+ } else {
482
+ // 请将$r('app.media.unfinished')替换为实际资源文件
483
+ this.displayIcon($r('app.media.unfinished'));
484
+ }
485
+ Text(`${this.thing}`)
486
+ .fontSize(24)
487
+ .decoration({ type: this.isFinished ? TextDecorationType.LineThrough : TextDecorationType.None })
488
+ .onClick(() => {
489
+ // 请将$r('app.string.la_la')替换为实际资源文件,在本示例中该资源文件的value值为"啦"
490
+ this.thing += this.getUIContext().getHostContext()!.resourceManager.getStringSync($r('app.string.la_la').id);
491
+ })
492
+ }
493
+ .height('8%')
494
+ .width('90%')
495
+ .padding({ left: 15 })
496
+ .opacity(this.isFinished ? 0.3 : 1)
497
+ .border({ width: 1 })
498
+ .borderColor(Color.White)
499
+ .borderRadius(25)
500
+ .backgroundColor(Color.White)
501
+ }
502
+ }
503
+
504
+ @Entry
505
+ @Component
506
+ struct BuilderIndex {
507
+ @State isFinished: boolean = false;
508
+ @State data: TodoListData = new TodoListData(); // View绑定ViewModel的数据
509
+
510
+ aboutToAppear(): void {
511
+ for (let i = 0; i < this.data.planList.length; i++) {
512
+ this.data.planList[i] =
513
+ this.getUIContext().getHostContext()!.resourceManager.getStringSync((this.data.planList[i] as Resource).id);
514
+ }
515
+ }
516
+
517
+ build() {
518
+ Column() {
519
+ // 全部待办
520
+ StateTodoComponent()
521
+
522
+ // 全选
523
+ BuilderAllChooseComponent({ isFinished: this.isFinished })
524
+
525
+ List() {
526
+ ForEach(this.data.planList, (item: string) => {
527
+ // 待办事项1
528
+ BuilderThingComponent({ isFinished: this.isFinished, thing: item })
529
+ .margin(5)
530
+ })
531
+ }
532
+ }
533
+ .height('100%')
534
+ .width('100%')
535
+ .margin({ top: 5, bottom: 5 })
536
+ .backgroundColor('#90f1f3f5')
537
+ }
538
+ }
539
+ ```
540
+
541
+ ### 总结
542
+
543
+ - 通过逐步优化代码结构,可以看到@Entry组件作为页面的入口,其build函数应该仅考虑将需要的组件组合起来,类似于搭积木。被page调用的子组件则如同积木,等着被需要的page进行调用。状态变量类似于粘合剂,当触发UI刷新事件时,状态变量自动刷新绑定的组件,实现page的按需刷新。
544
+ - 虽然现有的架构并未使用到MVVM的设计理念,但MVVM的核心理念已初见端倪。ArkUI的UI开发天然适合MVVM模式。在ArkUI中,page和组件构成View层,page负责组织组件,组件则作为构成元素。当组件需要更新时,通过状态变量驱动组件刷新,从而更新page。ViewModel的数据则来源于Model层。
545
+ - 示例中的代码功能较为简单,但随着功能的增加,主页面的代码量也会逐渐增多。当备忘录需要添加更多功能,且其他页面也需要使用到主页面的组件时,可以考虑采用MVVM模式来组织项目结构。
546
+
547
+ ## 通过MVVM开发备忘录实战
548
+
549
+ 上一章节展示了非MVVM模式下的代码组织方式。随着主页面代码的增加,应该采取合理的分层策略,使项目结构清晰,组件之间不互相引用,避免后期维护时牵一发而动全身,增加功能更新的困难。本章将通过对MVVM的核心文件组织模式,向开发者展示如何使用MVVM来重构上一章节的代码。
550
+
551
+ ### MVVM文件结构说明
552
+
553
+ ```
554
+ ├── src
555
+ │ ├── ets
556
+ │ │ ├── pages 存放页面组件。
557
+ │ │ ├── views 存放业务组件。
558
+ │ │ ├── shares 存放通用组件。
559
+ │ │ └── viewmodel 数据服务。
560
+ │ │ ├── LoginViewModel.ets 登录页ViewModel。
561
+ │ │ └── xxxViewModel.ets 其他页ViewModel。
562
+ ```
563
+
564
+ ### 分层设计技巧
565
+
566
+ **Model层**
567
+
568
+ - model层存放本应用核心数据结构,这层本身和UI开发关系不大,让用户按照自己的业务逻辑进行封装。
569
+
570
+ **ViewModel层**
571
+
572
+ > **注意:** ViewModel层不只是存放数据,它同时需要提供数据的服务及处理。
573
+
574
+ - ViewModel层是为视图服务的数据层。其设计具有两个特点:
575
+ 1. 按照页面组织数据。
576
+ 2. 每个页面数据进行懒加载。
577
+
578
+ **View层**
579
+
580
+ View层根据需要来组织,但View层需要区分以下三种组件:
581
+
582
+ - **页面组件**:提供整体页面布局,实现多页面之间的跳转,前后台事件处理等页面内容。
583
+ - **业务组件**:被页面引用,构建出页面。
584
+ - **共享组件**:与项目无关的多项目共享组件。
585
+
586
+ 共享组件和业务组件的区别:
587
+
588
+ - **业务组件**:包含了ViewModel数据,没有ViewModel,这个组件不能运行。
589
+ - **共享组件**:不包含ViewModel层的数据,需要的数据从外部传入。共享组件包含一个自定义组件,只要外部参数(无业务参数)满足,就可以工作。
590
+
591
+ ### 代码示例
592
+
593
+ 按MVVM模式组织结构,重构如下:
594
+
595
+ ```
596
+ ├── src
597
+ │ ├── ets
598
+ │ │ ├── model
599
+ │ │ │ ├── ThingModel.ets
600
+ │ │ │ └── TodoListModel.ets
601
+ │ │ ├── pages
602
+ │ │ │ ├── Index.ets
603
+ │ │ ├── views
604
+ │ │ │ ├── AllChooseComponent.ets
605
+ │ │ │ ├── ThingComponent.ets
606
+ │ │ │ ├── TodoComponent.ets
607
+ │ │ │ └── TodoListComponent.ets
608
+ │ │ ├── viewmodel
609
+ │ │ │ ├── ThingViewModel.ets
610
+ │ │ │ └── TodoListViewModel.ets
611
+ │ └── resources
612
+ │ ├── rawfile
613
+ │ │ ├── default_tasks.json
614
+ ```
615
+
616
+ 文件代码如下:
617
+
618
+ #### ThingModel.ets
619
+
620
+ ```typescript
621
+ export default class ThingModel {
622
+ public thingName: string = 'Todo';
623
+ public isFinish: boolean = false;
624
+ }
625
+ ```
626
+
627
+ #### TodoListModel.ets
628
+
629
+ ```typescript
630
+ import { common } from '@kit.AbilityKit';
631
+ import { util } from '@kit.ArkTS';
632
+ import { hilog } from '@kit.PerformanceAnalysisKit';
633
+ import ThingModel from './ThingModel';
634
+
635
+ const DOMAIN = 0x0001;
636
+ const TAG = 'TodoListModel';
637
+
638
+ export default class TodoListModel {
639
+ public things: Array<ThingModel> = [];
640
+
641
+ constructor(things: Array<ThingModel>) {
642
+ this.things = things;
643
+ }
644
+
645
+ async loadTasks(context: common.UIAbilityContext) {
646
+ try {
647
+ let getJson = await context.resourceManager.getRawFileContent('default_tasks.json');
648
+ let textDecoderOptions: util.TextDecoderOptions = { ignoreBOM: true };
649
+ let textDecoder = util.TextDecoder.create('utf-8', textDecoderOptions);
650
+ let result = textDecoder.decodeToString(getJson, { stream: false });
651
+ this.things = JSON.parse(result);
652
+ } catch (error) {
653
+ hilog.error(DOMAIN, TAG, 'Failed to load tasks. Cause: %{public}s', JSON.stringify(error.message));
654
+ }
655
+ }
656
+ }
657
+ ```
658
+
659
+ #### Index.ets
660
+
661
+ ```typescript
662
+ import { common } from '@kit.AbilityKit';
663
+ // import ViewModel
664
+ import TodoListViewModel from '../viewmodel/TodoListViewModel';
665
+
666
+ // import View
667
+ import { TodoComponent } from '../views/TodoComponent';
668
+ import { AllChooseComponent } from '../views/AllChooseComponent';
669
+ import { TodoListComponent } from '../views/TodoListComponent';
670
+
671
+ @Entry
672
+ @Component
673
+ struct TodoList {
674
+ @State todoListViewModel: TodoListViewModel = new TodoListViewModel(); // View绑定ViewModel的数据
675
+ private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
676
+
677
+ async aboutToAppear() {
678
+ await this.todoListViewModel.loadTasks(this.context);
679
+ }
680
+
681
+ build() {
682
+ Column() {
683
+ Row({ space: 40 }) {
684
+ // 全部待办
685
+ TodoComponent()
686
+ // 全选
687
+ AllChooseComponent({ todoListViewModel: this.todoListViewModel })
688
+ }
689
+
690
+ Column() {
691
+ TodoListComponent({ thingViewModelArray: this.todoListViewModel.things })
692
+ }
693
+ }
694
+ .height('100%')
695
+ .width('100%')
696
+ .margin({ top: 5, bottom: 5 })
697
+ .backgroundColor('#90f1f3f5')
698
+ }
699
+ }
700
+ ```
701
+
702
+ #### AllChooseComponent.ets
703
+
704
+ ```typescript
705
+ import TodoListViewModel from '../viewmodel/TodoListViewModel';
706
+ import { common } from '@kit.AbilityKit';
707
+
708
+ @Component
709
+ export struct AllChooseComponent {
710
+ context1 = this.getUIContext().getHostContext() as common.UIAbilityContext;
711
+ // 请在resources\base\element\string.json文件中配置name为'check_all',value为非空字符串的资源
712
+ @State titleName: ResourceStr = this.context1.resourceManager.getStringSync($r('app.string.check_all').id);
713
+ @Link todoListViewModel: TodoListViewModel;
714
+
715
+ build() {
716
+ Row() {
717
+ Button(`${this.titleName}`, { type: ButtonType.Capsule })
718
+ .onClick(() => {
719
+ this.todoListViewModel.chooseAll(); // View层点击事件发生时,调用ViewModel层方法chooseAll处理逻辑
720
+ this.titleName = this.todoListViewModel.isChosen ?
721
+ // 请在resources\base\element\string.json文件中配置name为'check_all',value为非空字符串的资源
722
+ this.context1.resourceManager.getStringSync($r('app.string.check_all').id)
723
+ // 请在resources\base\element\string.json文件中配置name为'deselect_all',value为非空字符串的资源
724
+ : this.context1.resourceManager.getStringSync($r('app.string.deselect_all').id);
725
+ })
726
+ .fontSize(30)
727
+ .fontWeight(FontWeight.Bold)
728
+ .backgroundColor('#f7f6cc74')
729
+ }
730
+ .padding({ left: this.todoListViewModel.isChosen ? 15 : 0 })
731
+ .width('100%')
732
+ .margin({ top: 10, bottom: 10 })
733
+ }
734
+ }
735
+ ```
736
+
737
+ #### ThingComponent.ets
738
+
739
+ ```typescript
740
+ import ThingViewModel from '../viewmodel/ThingViewModel';
741
+
742
+ @Component
743
+ export struct ThingComponent {
744
+ @ObjectLink thing: ThingViewModel;
745
+
746
+ @Builder
747
+ displayIcon(icon: Resource) {
748
+ Image(icon)
749
+ .width(28)
750
+ .height(28)
751
+ .onClick(() => {
752
+ this.thing.updateIsFinish(); // View层点击事件发生时,调用ViewModel层方法updateIsFinish处理逻辑
753
+ })
754
+ .id(this.thing.thingName)
755
+ }
756
+
757
+ build() {
758
+ // 待办事项
759
+ Row({ space: 15 }) {
760
+ if (this.thing.isFinish) {
761
+ // 请将$r('app.media.finished')替换为实际资源文件
762
+ this.displayIcon($r('app.media.finished'));
763
+ } else {
764
+ // 请将$r('app.media.unfinished')替换为实际资源文件
765
+ this.displayIcon($r('app.media.unfinished'));
766
+ }
767
+
768
+ Text(`${this.thing.thingName}`)
769
+ .fontSize(24)
770
+ .decoration({ type: this.thing.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
771
+ .onClick(() => {
772
+ this.thing.addSuffixes(); // View层点击事件发生时,调用ViewModel层方法addSuffixes处理逻辑
773
+ })
774
+ }
775
+ .height('8%')
776
+ .width('90%')
777
+ .padding({ left: 15 })
778
+ .opacity(this.thing.isFinish ? 0.3 : 1)
779
+ .border({ width: 1 })
780
+ .borderColor(Color.White)
781
+ .borderRadius(25)
782
+ .backgroundColor(Color.White)
783
+ }
784
+ }
785
+ ```
786
+
787
+ #### TodoComponent.ets
788
+
789
+ ```typescript
790
+ @Component
791
+ export struct TodoComponent {
792
+ build() {
793
+ Row() {
794
+ // 请将$r('app.string.all_tasks')替换为实际资源文件,在本示例中该资源文件的value值为"全部待办"
795
+ Text($r('app.string.all_tasks'))
796
+ .fontSize(30)
797
+ .fontWeight(FontWeight.Bold)
798
+ }
799
+ .padding({ left: 15 })
800
+ .width('50%')
801
+ .margin({ top: 10, bottom: 10 })
802
+ }
803
+ }
804
+ ```
805
+
806
+ #### TodoListComponent.ets
807
+
808
+ ```typescript
809
+ import ThingViewModel from '../viewmodel/ThingViewModel';
810
+ import { ThingViewModelArray } from '../viewmodel/TodoListViewModel'
811
+ import { ThingComponent } from './ThingComponent';
812
+
813
+ @Component
814
+ export struct TodoListComponent {
815
+ @ObjectLink thingViewModelArray: ThingViewModelArray;
816
+
817
+ build() {
818
+ Column() {
819
+ List() {
820
+ ForEach(this.thingViewModelArray, (item: ThingViewModel) => {
821
+ // 待办事项
822
+ ListItem() {
823
+ ThingComponent({ thing: item })
824
+ .margin(5)
825
+ }
826
+ }, (item: ThingViewModel) => {
827
+ return item.thingName;
828
+ })
829
+ }
830
+ }
831
+ }
832
+ }
833
+ ```
834
+
835
+ #### ThingViewModel.ets
836
+
837
+ ```typescript
838
+ import ThingModel from '../model/ThingModel';
839
+
840
+ @Observed
841
+ export default class ThingViewModel {
842
+ @Track public thingName: string = 'Todo';
843
+ @Track public isFinish: boolean = false;
844
+ public context: Context = AppStorage.get('context')!;
845
+
846
+ updateTask(thing: ThingModel) {
847
+ this.thingName = thing.thingName;
848
+ this.isFinish = thing.isFinish;
849
+ }
850
+
851
+ updateIsFinish(): void {
852
+ this.isFinish = !this.isFinish;
853
+ }
854
+
855
+ addSuffixes(): void {
856
+ // 请在resources\base\element\string.json文件中配置name为'la_la',value为非空字符串的资源
857
+ this.thingName += this.context.resourceManager.getStringSync($r('app.string.la_la').id);
858
+ }
859
+ }
860
+ ```
861
+
862
+ #### TodoListViewModel.ets
863
+
864
+ ```typescript
865
+ import ThingViewModel from './ThingViewModel';
866
+ import { common } from '@kit.AbilityKit';
867
+ import TodoListModel from '../model/TodoListModel';
868
+
869
+ @Observed
870
+ export class ThingViewModelArray extends Array<ThingViewModel> {
871
+ }
872
+
873
+ @Observed
874
+ export default class TodoListViewModel {
875
+ @Track public isChosen: boolean = true;
876
+ @Track public things: ThingViewModelArray = new ThingViewModelArray();
877
+
878
+ async loadTasks(context: common.UIAbilityContext) {
879
+ let todoList = new TodoListModel([]);
880
+ await todoList.loadTasks(context);
881
+ for (let thing of todoList.things) {
882
+ let todoListViewModel = new ThingViewModel();
883
+ todoListViewModel.updateTask(thing);
884
+ this.things.push(todoListViewModel);
885
+ }
886
+ }
887
+
888
+ chooseAll(): void {
889
+ for (let thing of this.things) {
890
+ thing.isFinish = this.isChosen;
891
+ }
892
+ this.isChosen = !this.isChosen;
893
+ }
894
+ }
895
+ ```
896
+
897
+ #### default_tasks.json
898
+
899
+ ```json
900
+ [
901
+ {"thingName": "7.30起床", "isFinish": false},
902
+ {"thingName": "8.30早餐", "isFinish": false},
903
+ {"thingName": "11.30中餐", "isFinish": false},
904
+ {"thingName": "17.30晚餐", "isFinish": false},
905
+ {"thingName": "21.30夜宵", "isFinish": false},
906
+ {"thingName": "22.30洗澡", "isFinish": false},
907
+ {"thingName": "1.30睡觉", "isFinish": false}
908
+ ]
909
+ ```
910
+
911
+ MVVM模式拆分后的代码结构更加清晰,模块职责更明确。新页面需要使用事件组件,比如TodoListComponent组件,只需导入组件。