diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..bfc6f019d9ae3b34c7222e823c17f9a8d4155ac5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer + +.DS_Store +._* \ No newline at end of file diff --git a/AppScope/app.json5 b/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..782f8b405e335e46b6595f5c774de83d76cd7ee5 --- /dev/null +++ b/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.smarthome2", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:layered_image", + "label": "$string:app_name" + } +} diff --git a/AppScope/resources/base/element/string.json b/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..efd3c5de73b6591811a06562267e83a3b0d8da72 --- /dev/null +++ b/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "SmartHome2" + } + ] +} diff --git a/AppScope/resources/base/media/background.png b/AppScope/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/AppScope/resources/base/media/background.png differ diff --git a/AppScope/resources/base/media/foreground.png b/AppScope/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9427585b36d14b12477435b6419d1f07b3e0bb Binary files /dev/null and b/AppScope/resources/base/media/foreground.png differ diff --git a/AppScope/resources/base/media/layered_image.json b/AppScope/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/AppScope/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..18795a48d6b12fcdc1aa7bac9a9cb99f83815267 --- /dev/null +++ b/LICENSE @@ -0,0 +1,78 @@ + Copyright (c) 2025 Huawei Device Co., Ltd. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +Apache License, Version 2.0 +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: +1.You must give any other recipients of the Work or Derivative Works a copy of this License; and +2.You must cause any modified files to carry prominent notices stating that You changed the files; and +3.You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +4.If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/README.en.md b/README.en.md index 81af52ffcb7a5759ca6894b557587b91b772e5c3..55a6c6c2cf042923e2218db27e456689f4997400 100644 --- a/README.en.md +++ b/README.en.md @@ -1,36 +1,59 @@ -# ArkTSIntermediateSyntax +# ArkTS Intermediate Syntax Supporting Case – Smart Home Scenario -#### Description -本篇Codelab结合智能家居场景,综合运用ArkTS语法中级课程的核心内容实现模块化设计,辅助知识点的学习理解 +### Introduction -#### Software Architecture -Software architecture description +This Codelab combines the smart home scenario and comprehensively applies the core content of the ArkTS Intermediate Syntax course to implement modular design. It assists in the learning and understanding of knowledge points, with the following operation effect diagram: -#### Installation +![](screenshots/device/screenshots.png) -1. xxxx -2. xxxx -3. xxxx +### 相关内容 -#### Instructions +#### 知识结构 -1. xxxx -2. xxxx -3. xxxx +- Declaration and usage of interfaces, abstract classes, and classes; +- Inheritance and encapsulation of classes; +- Advanced usage of functions; +- Usage of basic ArkUI components. -#### Contribution +#### 工程目录 -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request +``` +├── entry/src/main/ets +│ ├── components/ +│ │ └── types.ts // Function types, enums +│ ├── model/ +│ │ ├── Device.ts // Abstract device class + interface +│ │ ├── Light.ts // Specific device class +│ │ ├── AirConditioner.ts +│ │ └── SmartLock.ts +│ ├── service/ +│ │ ├── IDeviceService.ets // Device service interface +│ │ └── LocalDeviceService.ets // Local device service methods +│ ├── pages/ +│ │ └── index.ts // Home page UI entry +│ ├── utils/ +│ │ └── DeviceUtils.ets // Namespace +│ └── views/ +│ ├── ACView.ets // Air conditioner card view +│ ├── LightView.ets // Door card view +│ └── DeviceUtils.ets // Switch card view +└── entry/src/main/resources // Resource file directory +``` -#### Gitee Feature +### Related Permissions -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +None involved. + +### Usage Instructions + +1. Click the corresponding device switch to control the device status. +2. Click the scene button at the top to filter devices for the corresponding scene. +3. You can control the brightness of light devices through the Slider component, and adjust the temperature of air conditioners via the plus and minus buttons. + +### Constraints and Limitations + +1. This sample only supports running on standard systems, compatible devices: Huawei phones. +2. HarmonyOS version: HarmonyOS 5.0.5 Release or above. +3. DevEco Studio version: DevEco Studio 6.0.0 Release or above. +4. HarmonyOS SDK version: HarmonyOS 6.0.0 Release SDK or above. \ No newline at end of file diff --git a/README.md b/README.md index 9791ff60fea2fe2d55fbdb0999d8eebbb1b7f5e9..4153bfe3fca48ffee423d6483b2977acf554d2be 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,65 @@ -# ArkTSIntermediateSyntax +# ArkTS中级语法配套案例——智能家居场景 -#### 介绍 -本篇Codelab结合智能家居场景,综合运用ArkTS语法中级课程的核心内容实现模块化设计,辅助知识点的学习理解 +### 简介 -#### 软件架构 -软件架构说明 +本篇Codelab结合智能家居场景,综合运用ArkTS语法中级课程的核心内容实现模块化设计,辅助知识点的学习理解,运行效果图如下: +![](screenshots/device/screenshots.png) -#### 安装教程 +### 相关内容 -1. xxxx -2. xxxx -3. xxxx +#### 知识结构 -#### 使用说明 +- 接口、抽象类和类的声明及使用; +- 类的继承和封装 +- 函数的进阶使用 +- ArkUI基础组件使用 -1. xxxx -2. xxxx -3. xxxx +#### 工程目录 -#### 参与贡献 +``` +├── entry/src/main/ets +│ ├── components/ +│ │ └── types.ts // 函数类型、枚举 +│ ├── entryability +│ ├── entrybackupability +│ ├── model/ +│ │ ├── Device.ts // 抽象设备类 + 接口 +│ │ ├── Light.ts // 具体设备类 +│ │ ├── AirConditioner.ts +│ │ └── SmartLock.ts +│ ├── service/ +│ │ ├── IDeviceService.ets // 设备服务泛型接口 +│ │ └── LocalDeviceService.ets // 本地设备服务方法 +│ ├── pages/ +│ │ └── index.ts // 首页UI入口 +│ ├── utils/ +│ │ └── DeviceUtils.ets // 命名空间 +│ ├── views/ +│ │ ├── ACView.ets // 空调卡片视图 +│ │ ├── LightView.ets // 大门卡片视图 +│ │ └── LockView.ets // 开关卡片视图 +│ └── viewModel/ +│ ├── ACViewModel.ets // 视图模型 +│ ├── LightViewModel.ets +│ └── LockViewModel.ets +└── entry/src/main/resources // 资源文件目录 +``` -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +### 相关权限 -#### 特技 +不涉及 -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +### 使用说明 + +1. 点击对应设备开关,可控制设备状态。 +2. 点击顶部场景按钮,可筛选对应场景的设备。 +3. 可通过Slider组件控制灯类的亮度,通过加、减按钮调节空调类的温度。 + +### 约束与限制 + +1. 本示例仅支持标准系统上运行,支持设备:华为手机。 +2. HarmonyOS系统:HarmonyOS 5.0.5 Release及以上。 +3. DevEco Studio版本:DevEco Studio 6.0.0 Release及以上。 +4. HarmonyOS SDK版本:HarmonyOS 6.0.0 Release SDK及以上。 \ No newline at end of file diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c4e8cef12735e3a7929c1740117603c46528ebe4 --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,42 @@ +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "targetSdkVersion": "6.0.0(20)", + "compatibleSdkVersion": "6.0.0(20)", + "runtimeOS": "HarmonyOS", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/code-linter.json5 b/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..073990fa45394e1f8e85d85418ee60a8953f9b99 --- /dev/null +++ b/code-linter.json5 @@ -0,0 +1,32 @@ +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/entry/.gitignore b/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/entry/build-profile.json5 b/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..6bd6457a2fed846cc10698666fa1268219f1bee1 --- /dev/null +++ b/entry/build-profile.json5 @@ -0,0 +1,33 @@ +{ + "apiType": "stageMode", + "buildOption": { + "resOptions": { + "copyCodeResource": { + "enable": false + } + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/entry/hvigorfile.ts b/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..b0e3a1ab98a91bc918d6404b2413111a5011f14a --- /dev/null +++ b/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/entry/obfuscation-rules.txt b/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/entry/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/entry/oh-package.json5 b/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..248c3b7541a589682a250f86a6d3ecf7414d2d6a --- /dev/null +++ b/entry/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": {} +} + diff --git a/entry/src/main/ets/components/types.ets b/entry/src/main/ets/components/types.ets new file mode 100644 index 0000000000000000000000000000000000000000..99d9ab8ed6c24d8bb71653fcbc1796f9e293c7ba --- /dev/null +++ b/entry/src/main/ets/components/types.ets @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Device } from '../model/Device' + +/** + * 设备类型枚举:限定系统支持的智能设备种类 + * 避免直接使用字符串(如 'light')导致的拼写错误,且便于后续扩展新设备类型 + */ +export enum DeviceType { + Light = 'light', + AirConditioner = 'ac', + Lock = 'lock', +} + +/** + * 安装位置枚举:限定设备可安装的家庭区域 + * 采用统一的命名规范(小驼峰),确保前后端交互一致性 + */ +export enum Location { + Default = 'default', + LivingRoom = 'livingRoom', + Bedroom = 'bedroom', + Kitchen = 'kitchen', + Entrance = 'entrance' +} + +/** + * 设备操作函数类型:定义控制设备的统一接口 + * @param deviceId 设备唯一标识 + * @param action 操作指令(如 'turnOn'、'setTemperature') + * @returns Promise 操作结果(成功返回 true,失败返回 false) + */ +export type DeviceAction = (deviceId: string, action: string) => Promise + +/** + * 设备筛选函数类型:定义筛选设备的统一规则 + * @param device 待筛选的设备实例 + * @returns boolean 是否符合筛选条件 + */ +export type DeviceFilter = (device: Device) => boolean diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..62f53acbc95e5be7985f8fb1fe2db591cea260bd --- /dev/null +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + try { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + } catch (err) { + hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); + } + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + 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.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..0a97e21bd7a15599af76a806695860ff1eb0ebfe --- /dev/null +++ b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/knowledgePoints/Asynchronous.ets b/entry/src/main/ets/knowledgePoints/Asynchronous.ets new file mode 100644 index 0000000000000000000000000000000000000000..9b525073e06ac11eb1c2e7f2738a150936ca3778 --- /dev/null +++ b/entry/src/main/ets/knowledgePoints/Asynchronous.ets @@ -0,0 +1,72 @@ +// 同步:先加载文字,后加载图片,最后统一显示内容 +function loadWebpageSync(): void { + const start = Date.now(); + console.log('Loaded text content'); + // 等待图片加载2秒(这里用循环模拟等待,仅演示) + while (Date.now() - start < 2000) { } + console.log(`Loaded images after ${Date.now() - start} milliseconds`); +} + +console.log('Send network request'); +loadWebpageSync(); +console.log('Display webpage content'); // 文字和图片信息加载后显示内容 + +// 异步:加载图片的同时,先显示文字 +function loadWebpageAsync(): void { + console.log('Text content loaded'); + // setTimeout: ArkTS内置的异步函数,2秒后执行回调(只用基础箭头函数) + setTimeout(() => { + console.log(`Images loaded after 2000 milliseconds`) + }, 2000); +} +console.log('Send network request'); +loadWebpageAsync(); +console.log('Display webpage content (text shown first)'); + +// 异步操作: Promise/then/catch +const loadImagePromise: Promise = new Promise((resolve, reject) => { + setTimeout(() => { + const success = true; + if (success) { + resolve('Images loaded'); + } else { + reject(new Error('Images Loading failed')) + } + }, 2000); +}) + +console.log('Send network request') +loadImagePromise + .then((result) => { + console.log('success: ', result) + }) + .catch((error: Error) => { + console.log('failed: ', error.message) + }) +console.log('Display webpage content (text shown first)') + +// 异步操作: async/await +// 第一步:用Promise封装异步操作(加载图片) +function loadImage(): Promise { + // Promise接收一个函数,有两个参数:成功(resolve)、失败(reject) + return new Promise((resolve) => { + setTimeout(() => { + resolve('Images loaded'); // 异步操作成功,返回结果 + }, 2000); + }); +} + +// 第二步:用async +async function loadWebPage(): Promise { + console.log('Text content loaded'); + try { + const result = await loadImage(); + console.log(result); + console.log('Display webpage content') + } catch(error) { + console.error(`error: ${(error as Error).message}`); + } +} + +// 第二步:调用 +loadWebPage(); \ No newline at end of file diff --git a/entry/src/main/ets/knowledgePoints/Device.ets b/entry/src/main/ets/knowledgePoints/Device.ets new file mode 100644 index 0000000000000000000000000000000000000000..5ea58fc93193450e1cb8c8905a2672e5e87f3805 --- /dev/null +++ b/entry/src/main/ets/knowledgePoints/Device.ets @@ -0,0 +1,6 @@ +export class Device { + name: string = ''; + constructor(name: string) { + this.name = name; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/knowledgePoints/ImportExp.ets b/entry/src/main/ets/knowledgePoints/ImportExp.ets new file mode 100644 index 0000000000000000000000000000000000000000..54dbfb3e7c84754e4f30e9d8c1585682fb0b4f5d --- /dev/null +++ b/entry/src/main/ets/knowledgePoints/ImportExp.ets @@ -0,0 +1,18 @@ +import { createDefaultLight, DEFAULT_BRIGHTNESS, LightConfig} from './Light'; +import LightClass from './Light'; + +let lightConfig: LightConfig = { + name: '', + maxBrightness: DEFAULT_BRIGHTNESS +} +createDefaultLight('light'); + + +let light = new LightClass('客厅灯'); + +import('./Light').then((lightImport) => { + lightImport.createDefaultLight('light'); + console.log(`${lightImport.DEFAULT_BRIGHTNESS}`); +}).catch((error: Error) => { + console.error(`error ${error.message}`); +}) \ No newline at end of file diff --git a/entry/src/main/ets/knowledgePoints/Light.ets b/entry/src/main/ets/knowledgePoints/Light.ets new file mode 100644 index 0000000000000000000000000000000000000000..6124e657dc16852b27f86133eb2e432359b2090e --- /dev/null +++ b/entry/src/main/ets/knowledgePoints/Light.ets @@ -0,0 +1,22 @@ +import { Device } from './Device' + +export default class Light extends Device { + name: string = '空调'; + protected status: string = 'off'; + turnOn(): void { + this.status = 'on'; + } +} + +// 导出接口(设备配置结构) +export interface LightConfig { + name: string; + maxBrightness: number; +} +// 导出函数(创建默认配置的灯) +export function createDefaultLight(name: string): Light { + return new Light(name); +} +// 导出变量(默认亮度) +export const DEFAULT_BRIGHTNESS = 50; + diff --git a/entry/src/main/ets/knowledgePoints/advancedFunction.ets b/entry/src/main/ets/knowledgePoints/advancedFunction.ets new file mode 100644 index 0000000000000000000000000000000000000000..32948881ed77d37c28fb1f10885d66fd58d4899f --- /dev/null +++ b/entry/src/main/ets/knowledgePoints/advancedFunction.ets @@ -0,0 +1,187 @@ +// 普通函数 +function makeMilkTea(flavor: string, sweetness: string): string { + return `A cup of ${flavor} milk tea with ${sweetness}`; +} +let tea = makeMilkTea('strawberry', 'half sugar'); +console.log(tea); + +// 声明一个函数类型 +type CalculateFunc = (a: number, b: number) => number; + +// 箭头函数 + +// 示例1:单行箭头函数(无参数,无返回值) +const printHello = () => console.log('Hello Arrow function'); +printHello(); // 输出:Hello Arrow function + +// 示例2:单行箭头函数(有参数,有返回值) +const getSum = (num1: number, num2: number) => num1 + num2; +const sumResult = getSum(3, 5); +console.log('3 + 5 = ', sumResult); // 输出:3 + 5 = 8 + +// 示例3:多行箭头函数(有参数,有返回值) +const getDouble = (num: number) => { + const doubleNum = num * 2; + return doubleNum; +}; +console.log('The double of 4 is: ', getDouble(4)); // 输出:The double of 4 is: 8 + + +// 函数类型 + +// 第一步:定义“规矩”——接收1个number参数,返回number +type NumberFunc = (num: number) => number; + +// 第二步:定义变量,遵守这个规矩(用箭头函数实现) +const square: NumberFunc = (num) => num * num; // 求平方:符合规矩 +const triple: NumberFunc = (num) => num * 3; // 求三倍:符合规矩 + +// 第三步:使用变量(函数) +console.log('5 squared: ', square(5)); // 输出:5的平方:25 +console.log('Three times 5: ', triple(5)); // 输出:5的三倍:15 + +// 错误示例: +// const wrongFunc: NumberFunc = (str: string) => str; // 参数类型不对 +// const wrongFunc2: NumberFunc = (num: number) => 'result: ' + num; // 返回值类型不对 + + +// 闭包函数 +// 第一步:定义外层函数 +function createCounter() { + let count = 0; + + const increment = () => { + count++; // 修改外层变量 + return count; + }; + return increment; +} + +// 第二步:调用外层函数,得到闭包函数 +const counter = createCounter(); + +// 第三步:调用闭包函数(在“外面”访问“家里”的count) +console.log('The first call: ', counter()); // 输出:The first call: 1 +console.log('The second call: ', counter()); // 输出:The second call: 2 +console.log('The third call: ', counter()); // 输出:The third call: 3 + +// 函数重载 +// 第一步:定义重载规则 +function printInfo(value: string): void; // 规则1:接收字符串,打印文字 +function printInfo(value: number): void; // 规则2:接收数字,打印数字说明 + +// 第二步:实现函数 +function printInfo(value: string | number): void { + // 用已学的if-else判断类型 + if (typeof value === 'string') { + console.log('Text info: ', value); + } else { + console.log('Numeric info: The number is', value); + } +} + +// 1. Main function: Core logic for making coffee +function makeCoffee(flavor: string, callback: () => void): void { + console.log(`Start making ${flavor} coffee`); + console.log(`${flavor} coffee is ready`); + + // Execute callback (add toppings) after completion + callback(); +} + + +function addMilk(): void { + console.log("Add pure milk"); +} + +// 3. Callback function 2: Add sugar cubes +function addSugar(): void { + console.log("Add two sugar cubes"); +} + +// 4. Usage: Different callbacks for different needs +console.log("Order Latte (add milk)"); +makeCoffee("Espresso", () => { + console.log("Add pure milk"); +}); + +console.log("Order Caramel Coffee (add sugar)"); +makeCoffee("Americano", () => { + console.log("Add two sugar cubes"); +}); + +// 异步操作: Promise/then/catch + +// 同步:先加载文字,后加载图片,最后统一显示内容 +function loadWebpageSync(): void { + const start = Date.now(); + console.log('Loaded text content'); + // 等待图片加载2秒(这里用循环模拟等待,仅演示) + while (Date.now() - start < 2000) { } + console.log(`Loaded images after ${Date.now() - start} milliseconds`); +} + +console.log('Send network request'); +loadWebpageSync(); +console.log('Display webpage content'); // 文字和图片信息加载后显示内容 + +// 异步:加载图片的同时,先显示文字 +function loadWebpageAsync(): void { + console.log('Text content loaded'); + // setTimeout: ArkTS内置的异步函数,2秒后执行回调(只用基础箭头函数) + setTimeout(() => { + console.log(`Images loaded after 2000 milliseconds`) + }, 2000); +} +console.log('Send network request'); +loadWebpageAsync(); +console.log('Display webpage content (text shown first)'); + +// 异步操作: Promise/then/catch +const loadImagePromise: Promise = new Promise((resolve, reject) => { + setTimeout(() => { + const success = true; + if (success) { + resolve('Images loaded'); + } else { + reject(new Error('Images Loading failed')) + } + }, 2000); +}) + +console.log('Send network request') +loadImagePromise + .then((result) => { + console.log('success: ', result) + }) + .catch((error: Error) => { + console.log('failed: ', error.message) + }) +console.log('Display webpage content (text shown first)') + +// 异步操作: async/await +// 第一步:用Promise封装异步操作(加载图片) +function loadImage(): Promise { + // Promise接收一个函数,有两个参数:成功(resolve)、失败(reject) + return new Promise((resolve) => { + setTimeout(() => { + resolve('Images loaded'); // 异步操作成功,返回结果 + }, 2000); + }); +} + +// 第二步:用async +async function loadWebPage(): Promise { + console.log('Text content loaded'); + try { + const result = await loadImage(); + console.log(result); + console.log('Display webpage content') + } catch(error) { + console.error(`error: ${(error as Error).message}`); + } +} + +// 第二步:调用 +loadWebPage(); + diff --git a/entry/src/main/ets/knowledgePoints/advancedFunction_en.ets b/entry/src/main/ets/knowledgePoints/advancedFunction_en.ets new file mode 100644 index 0000000000000000000000000000000000000000..ed8222c476146dcb93e9c4bfc854f492bb54d853 --- /dev/null +++ b/entry/src/main/ets/knowledgePoints/advancedFunction_en.ets @@ -0,0 +1,194 @@ +// Regular function +function makeMilkTea(flavor: string, sweetness: string): string { + return `A cup of ${flavor} milk tea with ${sweetness}`; +} +let tea = makeMilkTea('strawberry', 'half sugar'); +console.log(tea); + +// Declare a function type +type CalculateFunc = (a: number, b: number) => number; + +// Arrow functions + +// Example 1: Single-line arrow function (no parameters, no return value) +const printHello = () => console.log('Hello Arrow function'); +printHello(); // Output: Hello Arrow function + +// Example 2: Single-line arrow function (with parameters, with return value) +const getSum = (num1: number, num2: number) => num1 + num2; +const sumResult = getSum(3, 5); +console.log('3 + 5 = ', sumResult); // Output: 3 + 5 = 8 + +// Example 3: Multi-line arrow function (with parameters, with return value) +const getDouble = (num: number) => { + const doubleNum = num * 2; + return doubleNum; +}; +console.log('The double of 4 is: ', getDouble(4)); // Output: The double of 4 is: 8 + +// Function types + +// Step 1: Define the "rule" — accepts one number parameter and returns a number +type NumberFunc = (num: number) => number; + +// Step 2: Declare variables that follow this rule (implemented with arrow functions) +const square: NumberFunc = (num) => num * num; // Square: complies with the rule +const triple: NumberFunc = (num) => num * 3; // Triple: complies with the rule + +// Step 3: Use the function variables +console.log('5 squared: ', square(5)); // Output: 5 squared: 25 +console.log('Three times 5: ', triple(5)); // Output: Three times 5: 15 + +// Error examples (commented out to avoid compilation errors): +// const wrongFunc: NumberFunc = (str: string) => str; // ❌ Parameter type mismatch +// const wrongFunc2: NumberFunc = (num: number) => 'result: ' + num; // ❌ Return type mismatch + +// Closure function + +// Step 1: Define the outer function +function createCounter() { + let count = 0; + + const increment = () => { + count++; // Modify variable from outer scope + return count; + }; + return increment; +} + +// Step 2: Call the outer function to obtain the closure +const counter = createCounter(); + +// Step 3: Invoke the closure (accessing the "private" `count` from outside) +console.log('The first call: ', counter()); // Output: The first call: 1 +console.log('The second call: ', counter()); // Output: The second call: 2 +console.log('The third call: ', counter()); // Output: The third call: 3 + +// Function overloading + +// Step 1: Define overload signatures +function printInfo(value: string): void; // Rule 1: Accepts a string, prints text info +function printInfo(value: number): void; // Rule 2: Accepts a number, prints numeric info + +// Step 2: Implement the function +function printInfo(value: string | number): void { + // Use basic if-else to check type (as learned previously) + if (typeof value === 'string') { + console.log('Text info: ', value); + } else { + console.log('Numeric info: The number is', value); + } +} + +// Callback pattern + +// 1. Main function: Core logic for making coffee +function makeCoffee(flavor: string, callback: () => void): void { + console.log(`Start making ${flavor} coffee`); + console.log(`${flavor} coffee is ready`); + + // Execute callback (e.g., add toppings) after completion + callback(); +} + +// 2. Callback function 1: Add milk +function addMilk(): void { + console.log("Add pure milk"); +} + +// 3. Callback function 2: Add sugar cubes +function addSugar(): void { + console.log("Add two sugar cubes"); +} + +// 4. Usage: Different callbacks for different orders +console.log("Order Latte (add milk)"); +makeCoffee("Espresso", () => { + console.log("Add pure milk"); +}); + +console.log("Order Caramel Coffee (add sugar)"); +makeCoffee("Americano", () => { + console.log("Add two sugar cubes"); +}); + +// Synchronous vs Asynchronous Operations + +// Synchronous: Do one task, wait until it finishes, then proceed +function makeCoffeeSync(): void { + console.log('Start making coffee (synchronous)'); + const start = Date.now(); + // Simulate 2-second wait using a busy loop (for demonstration only) + while (Date.now() - start < 2000) { } + console.log('(2 seconds later) Coffee is ready (synchronous)'); +} + +console.log('Step 1: Prepare to make coffee'); +makeCoffeeSync(); +console.log('Step 2: Drink coffee'); // This only runs after coffee is fully made + +// Asynchronous: Start making coffee, do other things meanwhile, get notified when done +function makeCoffeeAsync(): void { + console.log('Start making coffee (asynchronous)'); + // setTimeout: Built-in async function — executes callback after 2 seconds + setTimeout(() => { + console.log('(2 seconds later) Coffee is ready (asynchronous)') + }, 2000); +} +console.log('Step 1: Prepare to make coffee'); +makeCoffeeAsync(); +console.log('Step 2: Read a book first'); + +// Asynchronous operation with Promise / then / catch + +const makeCoffeePromise: Promise = new Promise((resolve, reject) => { + setTimeout(() => { + const success = true; + if (success) { + resolve('Coffee is ready'); + } else { + reject(new Error('Coffee machine is broken')); + } + }, 2000); +}); + +console.log('Step 1: Prepare to make coffee'); +makeCoffeePromise + .then((result) => { + console.log('Success: ', result); + }) + .catch((error: Error) => { + console.log('Failed: ', error.message); + }); +console.log('Read a book first while waiting for coffee'); + +// Asynchronous operation with async/await + +// Step 1: Wrap async operation in a Promise (making coffee) +function makeCoffee2(): Promise { + // Promise receives an executor function with `resolve` and `reject` + return new Promise((resolve) => { + console.log('Step 1: Prepare to make coffee'); + // Simulate 2-second async operation + setTimeout(() => { + resolve('Step 2: Drink coffee'); // Resolve with success message + }, 2000); + }); +} + +// Step 2: Use async/await to handle the Promise cleanly +async function drinkCoffee(): Promise { + console.log('Step 1: Prepare to make coffee'); + try { + const result = await makeCoffee2(); + console.log(result); + console.log('Step 2: Drink coffee'); + } catch (error) { + console.error(`Error: ${(error as Error).message}`); + } +} + +// Step 3: Invoke the async function +drinkCoffee(); + +console.log('After Step 1: Read a book first'); \ No newline at end of file diff --git a/entry/src/main/ets/knowledgePoints/classInstruction_ch.ets b/entry/src/main/ets/knowledgePoints/classInstruction_ch.ets new file mode 100644 index 0000000000000000000000000000000000000000..32bcc20eebe20034a865b112b1a6330acfe2bdf3 --- /dev/null +++ b/entry/src/main/ets/knowledgePoints/classInstruction_ch.ets @@ -0,0 +1,263 @@ +namespace classDeclaration { + class Device { + // 属性:存储设备数据 + name: string = ''; + // 方法:封装设备行为 + getInfo(): string { + return `${this.name}`; + } + } +} + +namespace classInstantiation { + class Device { + name: string = ''; + } + + let device = new Device(); + + const light: Device = { name: 'Light'} // 在初始化时设置实例属性 +} + +namespace classConstructor1 { + class Device { + name: string = ''; + deviceType: string = 'unkown'; + location: string = 'bedroom'; + + // 构造函数:实例初始化入口 + constructor(name: string, deviceType: string, location: string) { + this.name = name; + this.deviceType = deviceType; + this.location = location; + } + } + + // 实例化时传入构造函数参数,自动执行初始化 + let device = new Device('living room Air conditioner', 'Air conditioner', 'living room') +} + +namespace classConstructor2 { + class Device { + name: string = 'living room Air conditioner' + } + let device: Device = new Device(); // 使用默认值 +} + +namespace classProperty { + export class Device { + name: string = 'unkown'; + static readonly brand: string = 'Harmony Smart Device'; + } + + export const light = new Device(); + + function printLog(): void { + console.log(light.name); + // console.log(light.brand); // + console.log(Device.brand); + } +} + + +namespace classMethod { + export class Device { + name: string = 'unknown'; + deviceType: string = 'light'; + + // 实例方法: 获取设备完整信息 + getInfo(): string { + // this指向当前实例,访问实例属性 + return `${this.name} (类型${this.deviceType})`; + } + // 静态方法: 验证设备名称是否合法(工具类逻辑) + static isValidName(name: string): boolean { + return name.trim().length > 0; + } + } + const light = new Device; + function printLog(): void { + // 调用实例属性和方法 + light.name = '客厅主灯'; + console.log(light.getInfo()); + // 调用静态方法 + console.log(`${Device.isValidName('客厅主灯')}`); + console.log(`${Device.isValidName('')}`); + } +} + +namespace classThis { + export class Device { + name: string = 'unknown'; + status: 'on' | 'off' = 'off'; + // 1. 普通方法: this指向Device实例 + turnOnImmediately() { + this.status = 'on'; + console.log(`普通方法 - ${this.name} 立即开启,状态: ${this.status}`); + } + // 2. 箭头函数(嵌套在方法中): this指向实例 + turnOnWithDelay() { + setTimeout(() => { + this.status = 'on'; // this指向Device实例 + console.log(`箭头函数 - ${this.name} 延迟开启,状态: ${this.status}`); + }) + } + } +} + + +namespace classExtends { + class Device { + name: string; + constructor(name: string) { + this.name = name; + } + } + // 子类继承 Device + class Light extends Device { + brightness: number = 50; + constructor(name: string, brightness: number) { + super(name); + this.brightness = brightness; + } + setBrightness(value: number): void { + this.brightness = value; + console.log(`${this.name},亮度设置为: ${value}%`); + } + } + const light = new Light('客厅灯', 50); + + export function printLog(): void { + console.log(light.name); // 输出:客厅灯 + light.setBrightness(60); // 输出:客厅灯亮度设置为60% + } +} + +namespace classModifiers { + class Device { + name: string; + constructor(name: string) { + this.name = name; + } + public getInfo(): string { + return this.name; + } + } + + const device = new Device('Smart Device'); + + export class Light { + // public 可省略(默认) + public name: string; + private brightness: number = 50; // 私有属性:亮度brightness(外部不可直接访问) + constructor(name: string) { + this.name = name; + } + public setBrightness(value: number): string { + this.brightness = value; + return `${this.name} 亮度设置为: ${value}`; + } + } + const light = new Light('客厅灯'); + function printLog(): void { + console.log(device.getInfo()); // 外部可访问,输出: "Smart Device" + // console.log(light.brightness) // 报错:属性'brightness'是私有的,只能在'Light'类内部访问 + light.setBrightness(70); // 输出:客厅灯 亮度设置为60% + } +} + +namespace classProtected { + export class Device { + public name: string; + protected status: string = 'off'; // 保护属性:设备状态(类内部和子类能访问) + constructor(name: string) { + this.name = name; + } + } + // 子类继承 Device + class Light extends Device { + getStatus(): string { + return `${this.name} 状态: ${this.status}`; + } + } + const light = new Light('客厅灯'); + function printLog(): void { + // console.log(light.status) // 报错: 外部不可访问受保护属性 + light.getStatus(); // 输出: 客厅灯 状态: off + } +} + +namespace Polymorphism { + class Device { + public name: string; + protected status: string = 'off'; + constructor(name: string) { + this.name = name; + } + // 父类统一方法(子类可重写) + turnOn(): void { + console.log(`${this.name} 开启`); // 不同的子类设备开启行为不同 + } + } + // 子类1 灯光设备 + class Light extends Device { + private brightness: number = 50; + override turnOn(): void { + this.status = 'on'; + console.log(`${this.name} 状态为 ${this.status},亮度: ${this.brightness}%`); + } + } + // 子类2 空调设备 + class AirConditioner extends Device { + private temperature: number = 26; + override turnOn(): void { + this.status = 'on'; + console.log(`${this.name} 状态为 ${this.status},设置的温度是: ${this.temperature}%`); + } + + setTemperature(value: number): void { + this.temperature = value; + } + } + + // 多态体现:父类饮用接收不同子类实例 + const devices: Device[] = [new Light('客厅灯'), new AirConditioner('卧室空调')]; + + function printLog(): void { + // 统一调用父类接口,执行不同子类逻辑 + devices[0].turnOn(); + devices[1].turnOn(); + } +} + +namespace abstractClass { + abstract class Device { + public name: string = ''; + protected abstract status: string; + // 抽象方法:仅声明,无方法体,强制子类实现 + abstract turnOn(): void; + // 普通方法,子类可直接复用 + getStatus(): string { + return `${this.name} 的状态为 ${this.status}`; + } + } + + // 示例1 子类没有实现父类中的抽象变量或抽象方法/ + /** + class Light extends Device { + // 非抽象类'Light'没有实现从类'Device'继承的抽象成员'status' + // 非抽象类'Light'没有实现从类'Device'继承的抽象成员'turnOn' + } + **/ + + // 示例2 子类实现抽象类 + class Light extends Device { + status: string = 'off'; + // 必须实现抽象方法turnOn + turnOn(): void { + this.status = 'on'; + console.log(`${this.name} 灯光开启`); + } + } +} + diff --git a/entry/src/main/ets/knowledgePoints/classInstruction_en.ets b/entry/src/main/ets/knowledgePoints/classInstruction_en.ets new file mode 100644 index 0000000000000000000000000000000000000000..f46e7149ce8346085156e62465676d2183cb60d0 --- /dev/null +++ b/entry/src/main/ets/knowledgePoints/classInstruction_en.ets @@ -0,0 +1,273 @@ +namespace classDeclaration { + class Device { + // Property: stores device data + name: string = ''; + // Method: encapsulates device behavior + getInfo(): string { + return `${this.name}`; + } + } +} + +namespace classInstantiation { + class Device { + name: string = ''; + } + + let device = new Device(); + + const light: Device = { name: 'Light' }; // Set instance properties during initialization +} + +namespace classConstructor1 { + class Device { + name: string = ''; + deviceType: string = 'unknown'; + location: string = 'bedroom'; + + // Constructor: entry point for instance initialization + constructor(name: string, deviceType: string, location: string) { + this.name = name; + this.deviceType = deviceType; + this.location = location; + } + } + + // Pass arguments to the constructor during instantiation; initialization runs automatically + let device = new Device('living room Air conditioner', 'Air conditioner', 'living room'); +} + +namespace classConstructor2 { + class Device { + name: string = 'living room Air conditioner'; + } + let device: Device = new Device(); // Use default values +} + +namespace classProperty { + export class Device { + name: string = 'unknown'; + static readonly brand: string = 'Harmony Smart Device'; + } + + export const light = new Device(); + + function printLog(): void { + console.log(light.name); + // console.log(light.brand); // Static property cannot be accessed via instance + console.log(Device.brand); // Access static property via class name + } +} + +namespace classMethod { + export class Device { + name: string = 'unknown'; + deviceType: string = 'light'; + + // Instance method: retrieves full device information + getInfo(): string { + // `this` refers to the current instance and accesses instance properties + return `${this.name} (type: ${this.deviceType})`; + } + + // Static method: validates whether a device name is valid (utility logic) + static isValidName(name: string): boolean { + return name.trim().length > 0; + } + } + + const light = new Device(); + function printLog(): void { + // Access instance property and call instance method + light.name = 'Living Room Main Light'; + console.log(light.getInfo()); + + // Call static method + console.log(`${Device.isValidName('Living Room Main Light')}`); + console.log(`${Device.isValidName('')}`); + } +} + +namespace classThis { + export class Device { + name: string = 'unknown'; + status: 'on' | 'off' = 'off'; + + // 1. Regular method: `this` refers to the Device instance + turnOnImmediately() { + this.status = 'on'; + console.log(`Regular method - ${this.name} turned on immediately, status: ${this.status}`); + } + + // 2. Arrow function (nested inside a method): `this` still refers to the Device instance + turnOnWithDelay() { + setTimeout(() => { + this.status = 'on'; // `this` correctly refers to the Device instance + console.log(`Arrow function - ${this.name} turned on with delay, status: ${this.status}`); + }, 0); + } + } +} + +namespace classExtends { + class Device { + name: string; + constructor(name: string) { + this.name = name; + } + } + + // Subclass inherits from Device + class Light extends Device { + brightness: number = 50; + + constructor(name: string, brightness: number) { + super(name); + this.brightness = brightness; + } + + setBrightness(value: number): void { + this.brightness = value; + console.log(`${this.name} brightness set to: ${value}%`); + } + } + + const light = new Light('Living Room Light', 50); + + export function printLog(): void { + console.log(light.name); // Output: Living Room Light + light.setBrightness(60); // Output: Living Room Light brightness set to: 60% + } +} + +namespace classModifiers { + export class Light { + // `public` is optional (default visibility) + public name: string; + private brightness: number = 50; // Private property: brightness (not directly accessible from outside) + + constructor(name: string) { + this.name = name; + } + + public setBrightness(value: number): string { + this.brightness = value; + return `${this.name} brightness set to: ${value}`; + } + } + + const light = new Light('Living Room Light'); + + function printLog(): void { + // console.log(light.brightness); // Error: Property 'brightness' is private and only accessible within class 'Light' + light.setBrightness(70); // Output: Living Room Light brightness set to: 70 + } +} + +namespace classProtected { + export class Device { + public name: string; + protected status: string = 'off'; // Protected property: device status (accessible within class and subclasses) + + constructor(name: string) { + this.name = name; + } + } + + // Subclass inherits from Device + class Light extends Device { + getStatus(): string { + return `${this.name} status: ${this.status}`; + } + } + + const light = new Light('Living Room Light'); + + function printLog(): void { + // console.log(light.status); // Error: Property 'status' is protected and only accessible within class 'Device' and its subclasses + light.getStatus(); // Output: Living Room Light status: off + } +} + +namespace Polymorphism { + class Device { + public name: string; + protected status: string = 'off'; + + constructor(name: string) { + this.name = name; + } + + // Base method (can be overridden by subclasses) + turnOn(): void { + console.log(`${this.name} turned on`); // Different subclass devices exhibit different behaviors when turned on + } + } + + // Subclass 1: Lighting device + class Light extends Device { + private brightness: number = 50; + + override turnOn(): void { + this.status = 'on'; + console.log(`${this.name} status is ${this.status}, brightness: ${this.brightness}%`); + } + } + + // Subclass 2: Air conditioner device + class AirConditioner extends Device { + private temperature: number = 26; + + override turnOn(): void { + this.status = 'on'; + console.log(`${this.name} status is ${this.status}, target temperature: ${this.temperature}°C`); + } + + setTemperature(value: number): void { + this.temperature = value; + } + } + + // Polymorphism in action: parent type references hold instances of different subclasses + const devices: Device[] = [new Light('Living Room Light'), new AirConditioner('Bedroom AC')]; + + function printLog(): void { + // Unified interface call executes different subclass logic + devices[0].turnOn(); // Output: Living Room Light status is on, brightness: 50% + devices[1].turnOn(); // Output: Bedroom AC status is on, target temperature: 26°C + } +} + +namespace abstractClass { + abstract class Device { + public name: string = ''; + protected abstract status: string; + + // Abstract method: declared without implementation; must be implemented by subclasses + abstract turnOn(): void; + + // Concrete method: can be reused directly by subclasses + getStatus(): string { + return `${this.name} status is ${this.status}`; + } + } + + // Example 1: Subclass fails to implement abstract members (commented out due to compilation error) + /** + class Light extends Device { + // Non-abstract class 'Light' does not implement inherited abstract member 'status' + // Non-abstract class 'Light' does not implement inherited abstract member 'turnOn' + } + **/ + + // Example 2: Proper implementation of abstract class + class Light extends Device { + status: string = 'off'; + + // Must implement the abstract method `turnOn` + turnOn(): void { + this.status = 'on'; + console.log(`${this.name} light turned on`); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/knowledgePoints/genericsInstruction.ets b/entry/src/main/ets/knowledgePoints/genericsInstruction.ets new file mode 100644 index 0000000000000000000000000000000000000000..f2315bcb29eeac6a35a081f3f128acb688913f64 --- /dev/null +++ b/entry/src/main/ets/knowledgePoints/genericsInstruction.ets @@ -0,0 +1,97 @@ +namespace Generics { + function identity(arg: T): T { + return arg; + } + const str = identity('light'); + const num = identity(26); + const bool = identity(true); + + class DeviceQueue { + private devices: T[] = []; + // 存储设备的数组,类型为T[] + // 入队:添加任意类型的设备 + enqueue(device: T): void { + this.devices.push(device); + } + } + + interface DeviceGroup { + groupName: string; // 群组名称,固定字段 + deviceList: T[]; // 不同类型的设备数组,泛型字段 + } +} + +namespace GenericsExample { + // 泛型类: 表示队列中设备的类型<使用时指定> + class DeviceQueue { + private devices: T[] = []; // 存储设备的数据,类型为T[] + // 入队:添加任意类型的设备 + enqueue(device: T): void { + this.devices.push(device); + console.log(`${JSON.stringify(device)} 已加入队列`); + } + } + class Device { + name: string = ''; + constructor(name: string) { + this.name = name; + } + } + const lightQueue = new DeviceQueue(); + + class Light extends Device { + name: string = '空调'; + protected status: string = 'off'; + turnOn(): void { + this.status = 'on'; + console.log(`${this.name} 状态为 ${this.status}`); + } + } + function printLog(): void { + lightQueue.enqueue(new Device('空调设备')); + lightQueue.enqueue(new Light('客厅灯')); // Light类型也能加入到队列中 + } +} + +namespace GenericsLimits { + class Device { + name: string = ''; + constructor(name: string) { + this.name = name; + } + } + + class DeviceQueue { + private devices: T[] = []; // 存储设备的数据,类型为T[] + // 入队:添加任意类型的设备 + enqueue(device: T): void { + this.devices.push(device); + console.log(`${JSON.stringify(device)} 已加入队列`); + } + } + function printLog(): void { + // let stringQueue = new DeviceQueue(); // 类型 'string' 不满足约束条件 'Device' + } +} + +namespace GenericsDefault { + class Device { + name: string = ''; + constructor(name: string) { + this.name = name; + } + } + + class DeviceQueue { + private devices: T[] = []; // 存储设备的数据,类型为T[] + // 入队:添加任意类型的设备 + enqueue(device: T): void { + this.devices.push(device); + console.log(`${JSON.stringify(device)} 已加入队列`); + } + } + const lightQueue = new DeviceQueue(); + function printLog(): void { + lightQueue.enqueue(new Device('空调设备')); // 在不显式指定类型参数时,提供默认类型 + } +} \ No newline at end of file diff --git a/entry/src/main/ets/knowledgePoints/interfaceInstruction.ets b/entry/src/main/ets/knowledgePoints/interfaceInstruction.ets new file mode 100644 index 0000000000000000000000000000000000000000..f1200dbc04d919b7ac42ebebdf4e1b56e585bd0a --- /dev/null +++ b/entry/src/main/ets/knowledgePoints/interfaceInstruction.ets @@ -0,0 +1,79 @@ +namespace interfaceUse { + // 1. 基础设备信息接口:约束设备必须包含的基础属性 + interface DeviceBase { + name: string; // 设备名称 + status: string; // 设备状态 + type: string; // 设备类型 + } + + // 2. 设备控制接口:约束设备必须实现的开关功能 + interface DeviceController { + turnOn(): void; // 开启设备 + turnOff(): void; // 关闭设备 + getStatus(): string; // 获取设备状态 + } + + // 1. 新接口继承DeviceBase和DeviceController,组合两者的约束 + interface IDevice extends DeviceBase, DeviceController { + location: string; // 新增约束:设备位置 + } + + class AirConditioner implements DeviceBase, DeviceController { + name: string = ''; + status: string = ''; + type: string = ''; + constructor(name: string, status: string, type: string) { + this.name = name; + this.status = status; + this.type = type; + } + turnOn(): void { + this.status = 'on'; + } + turnOff(): void { + this.status = 'off'; + } + getStatus(): string { + return `${this.name} 的状态为 ${this.status}`; + } + } + + const airConditioner = new AirConditioner('空调', 'on', 'AirConditioner'); +} + +namespace classDifference { + interface IDevice { + id: string; + name: string; + deviceType: string; + location: string; + turnOn(): Promise; + turnOff(): Promise; + } + // 若只用IDevice接口,所有子类都要重复实现getInfo()方法、status和getter的逻辑、构造函数的属性——代码冗余且易出错 + // 抽象类Device实现IDevice接口后,将通用逻辑(getInfo()、属性初始化)封装在父类,子类只需关注核心差异(turnOn()/turnOff()),减少重复代码 + abstract class Device implements IDevice { + id: string = ''; + name: string = ''; + public deviceType: string = 'light'; + public location: string = ''; + protected status: 'on' | 'off' = 'off'; // 受保护属性无法定义在接口中 + constructor(id: string, name: string, type: string, location: string) { + this.id = id; + this.name = name; + this.deviceType = type; + this.location = location; + } + getStatus(): 'on' | 'off' { + return this.status; + } + // 抽象方法:子类必须实现 + abstract turnOn(): Promise; + abstract turnOff(): Promise; + // 通用方法 + getInfo(): string { + return `${this.name} (${this.deviceType} in ${this.location})` + } + } + // 子类继承Device后,既满足接口的结构规范,又复用了父类的通用逻辑,同时必须实现抽象方法,代码更稳定 +} \ No newline at end of file diff --git a/entry/src/main/ets/knowledgePoints/interfaceInstruction_en.ets b/entry/src/main/ets/knowledgePoints/interfaceInstruction_en.ets new file mode 100644 index 0000000000000000000000000000000000000000..07e4ffc34db04ef58cf02410c1fbd7270e458068 --- /dev/null +++ b/entry/src/main/ets/knowledgePoints/interfaceInstruction_en.ets @@ -0,0 +1,99 @@ +namespace interfaceUse { + // 1. Basic device info interface: enforces required base properties for any device + interface DeviceBase { + name: string; // Device name + status: string; // Device status + type: string; // Device type + } + + // 2. Device control interface: enforces required on/off functionality + interface DeviceController { + turnOn(): void; // Turn on the device + turnOff(): void; // Turn off the device + getStatus(): string; // Get current device status + } + + // 3. Combined interface extending both DeviceBase and DeviceController, + // adding an additional constraint: device location + interface IDevice extends DeviceBase, DeviceController { + location: string; // Device location + } + + class AirConditioner implements DeviceBase, DeviceController { + name: string = ''; + status: string = ''; + type: string = ''; + + constructor(name: string, status: string, type: string) { + this.name = name; + this.status = status; + this.type = type; + } + + turnOn(): void { + this.status = 'on'; + } + + turnOff(): void { + this.status = 'off'; + } + + getStatus(): string { + return `${this.name} status is ${this.status}`; + } + } + + const airConditioner = new AirConditioner('Air Conditioner', 'on', 'AirConditioner'); +} + +namespace classDifference { + interface IDevice { + id: string; + name: string; + deviceType: string; + location: string; + turnOn(): Promise; + turnOff(): Promise; + } + + // If we only used the IDevice interface, every subclass would need to repeatedly implement + // getInfo(), manage the `status` property with getter logic, and duplicate constructor assignments — + // leading to redundant and error-prone code. + // + // By having an abstract class `Device` implement `IDevice`, we encapsulate shared logic + // (e.g., getInfo(), property initialization) in the parent class. + // Subclasses then only need to focus on their core differences (turnOn()/turnOff()), + // significantly reducing code duplication. + + abstract class Device implements IDevice { + id: string = ''; + name: string = ''; + public deviceType: string = 'light'; + public location: string = ''; + protected status: 'on' | 'off' = 'off'; // Protected properties cannot be declared in interfaces + + constructor(id: string, name: string, type: string, location: string) { + this.id = id; + this.name = name; + this.deviceType = type; + this.location = location; + } + + getStatus(): 'on' | 'off' { + return this.status; + } + + // Abstract methods: must be implemented by subclasses + abstract turnOn(): Promise; + abstract turnOff(): Promise; + + // Shared utility method + getInfo(): string { + return `${this.name} (${this.deviceType} in ${this.location})`; + } + } + + // Subclasses inheriting from `Device` automatically satisfy the `IDevice` structural contract, + // reuse common logic from the parent, and are forced to implement the abstract methods, + // resulting in more maintainable and robust code. +} \ No newline at end of file diff --git a/entry/src/main/ets/model/AirConditioner.ets b/entry/src/main/ets/model/AirConditioner.ets new file mode 100644 index 0000000000000000000000000000000000000000..fcac1cb25bf7fbdc465788c81c47fdb8fd6257ea --- /dev/null +++ b/entry/src/main/ets/model/AirConditioner.ets @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DeviceType, Location } from '../components/types'; +import { Device } from './Device'; +import { delay, logAction } from '../utils/DeviceUtils'; + +/** + * 空调子类:扩展温度控制功能 + */ +export class AirConditioner extends Device { + // 私有属性:温度(默认26℃,仅内部或通过 getter/setter 访问) + private temperature: number = 26; + + constructor(id: string, name: string, location: Location) { + super(id, name, DeviceType.AirConditioner, location); + } + + /** + * 实现抽象方法:开机逻辑 + * 开机时显示当前温度,符合空调的使用场景 + */ + async turnOn(): Promise { + await delay(200); // 模拟空调压缩机启动延迟 + this.status = 'on'; + logAction(`${this.getInfo()} 已开启,当前温度:${this.temperature}℃`); + } + + /** + * 实现抽象方法:关机逻辑 + */ + async turnOff(): Promise { + await delay(100); + this.status = 'off'; + logAction(`${this.getInfo()} 已关闭`); + } + + /** + * getter 方法:获取当前温度(外部只读,不能直接修改) + */ + getTemperature(): number { + return this.temperature; + } + + /** + * setter 方法:设置温度(添加边界约束,避免无效值) + * @param temp 目标温度(16℃~30℃ 范围内) + */ + setTemperature(temp: number): void { + if (temp < 16) this.temperature = 16; + else if (temp > 30) this.temperature = 30; + else this.temperature = temp; + // 若设备已开机,打印温度更新日志 + if (this.status === 'on') { + logAction(`${this.getInfo()} 温度已调整为:${this.temperature}℃`) + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/model/Device.ets b/entry/src/main/ets/model/Device.ets new file mode 100644 index 0000000000000000000000000000000000000000..d98a2a942574bd15d1ccc2ed433a02ac6432a95f --- /dev/null +++ b/entry/src/main/ets/model/Device.ets @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DeviceType, Location } from '../components/types' + +/** + * 设备核心接口:定义所有智能设备必须遵守的规范 + * 接口中的属性和方法默认是 public(无需显式声明) + */ +export interface IDevice { + // 设备唯一标识(必填) + id: string; + // 设备名称(用户可自定义) + name: string; + // 设备类型(必须是 DeviceType 枚举值) + type: DeviceType; + // 安装位置(必须是 Location 枚举值) + location: Location; + + // 开机方法:返回 Promise 支持异步操作(如设备联网通信) + turnOn(): Promise; + // 关机方法:同上 + turnOff(): Promise; +} + +/** + * 设备抽象基类:实现 IDevice 接口的共性逻辑,抽象个性化逻辑 + * 继承关系:所有具体设备类(Light、AirConditioner 等)都继承自此类 + */ +export abstract class Device implements IDevice { + // 实现 IDevice 接口的属性(提供默认值,子类可覆盖) + id: string = ''; + name: string = ''; + public type: DeviceType = DeviceType.Light; + public location: Location = Location.Default; + + // 私有属性:状态通过内部方法暴露,避免外部直接修改(封装性) + protected status: 'on' | 'off' = 'off'; + + /** + * 构造函数:初始化设备的核心属性 + * @param id 设备ID + * @param name 设备名称 + * @param type 设备类型 + * @param location 安装位置 + */ + constructor(id: string, name: string, type: DeviceType, location: Location) { + this.id = id; + this.name = name; + this.type = type; + this.location = location; + } + + /** + * 实现 IDevice 接口的 status 属性:通过 getStatus 控制访问权限 + * 外部只能读取状态,不能直接修改(需通过 turnOn/turnOff 方法) + */ + getStatus(): 'on' | 'off' { + return this.status; + } + + /** + * 抽象方法:子类必须实现的个性化逻辑 + * 原因:不同设备的开机逻辑不同(如灯光需初始化亮度,空调需初始化温度) + */ + abstract turnOn(): Promise; + abstract turnOff(): Promise; + + /** + * 具体方法:所有设备共享的共性逻辑 + * 作用:返回设备的详细描述信息,无需子类重复实现 + */ + getInfo(): string { + return `${this.name}(${Location[this.location]} 位置的 ${DeviceType[this.type]} 设备)`; + } +} diff --git a/entry/src/main/ets/model/Light.ets b/entry/src/main/ets/model/Light.ets new file mode 100644 index 0000000000000000000000000000000000000000..8b6ca9b9c2e6a21f615f91128fd272b741b8db11 --- /dev/null +++ b/entry/src/main/ets/model/Light.ets @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DeviceType, Location } from '../components/types'; +import { Device } from './Device'; +import { delay, logAction } from '../utils/DeviceUtils'; + +/** + * 灯光子类:扩展亮度控制功能,支持 UI 状态联动 + * @Observed:装饰类,标记该类为可观察对象,属性变化时触发 UI 刷新 + */ +@Observed +export class Light extends Device { + // 私有属性:亮度(0~100 范围内,默认50) + private brightness: number = 50; + + constructor(id: string, name: string, location: Location) { + super(id, name, DeviceType.Light, location); + } + + /** + * 实现抽象方法:开灯逻辑 + * 开机时显示当前亮度,符合灯光的使用场景 + */ + async turnOn(): Promise { + await delay(100); // 模拟灯光启动延迟 + this.status = 'on'; + logAction(`${this.getInfo()} 已开启,当前亮度:${this.brightness}%`); + } + + /** + * 实现抽象方法:关灯逻辑 + */ + async turnOff(): Promise { + await delay(100); + this.status = 'off'; + logAction(`${this.getInfo()} 已关闭`); + } + + /** + * getter 方法:获取当前亮度 + */ + getBrightness(): number { + return this.brightness; + } + + /** + * setter 方法:设置亮度(添加边界约束) + * @param level 目标亮度(0~100 范围内) + */ + setBrightness(level: number): void { + // 用 Math.max/Math.min 简化边界判断,确保亮度在 0~100 之间 + this.brightness = Math.max(0, Math.min(100, level)); + if (this.status === 'on') { + logAction(`${this.getInfo()} 亮度已调整为:${this.brightness}%`); + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/model/SmartLock.ets b/entry/src/main/ets/model/SmartLock.ets new file mode 100644 index 0000000000000000000000000000000000000000..9c71ab51c9adc62816fc8b94030ad36618b92394 --- /dev/null +++ b/entry/src/main/ets/model/SmartLock.ets @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DeviceType, Location } from '../components/types'; +import { Device } from './Device'; +import { delay, logAction } from '../utils/DeviceUtils'; + +/** + * 智能锁子类:继承 Device 抽象类,实现锁具的个性化逻辑 + */ +export class SmartLock extends Device { + /** + * 构造函数:调用父类构造函数初始化核心属性 + * 注意:设备类型固定为 DeviceType.Lock,无需外部传入(封装细节) + */ + constructor(id: string, name: string, location: Location) { + super(id, name, DeviceType.Lock, location); + } + + /** + * 实现抽象方法:开锁逻辑 + * 异步处理:模拟设备联网通信的延迟(300ms) + */ + async turnOn(): Promise { + await delay(300); // 模拟设备响应延迟(如蓝牙连接、云端校验) + this.status = 'on'; + logAction(`${this.getInfo()} 已开锁`); + } + + /** + * 实现抽象方法:关锁逻辑 + * 延迟时间较短(100ms),符合锁具的实际响应速度 + */ + async turnOff(): Promise { + await delay(100); + this.status = 'off'; + logAction(`${this.getInfo()} 已关锁`); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..d1083b47fbf9e2a242c5c9532793143f89211218 --- /dev/null +++ b/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Device } from '../model/Device' +import { IDeviceService } from '../services/IDeviceService'; +import { LocalDeviceService } from '../services/LocalDeviceService'; +import { getAllLocations, createLocationFilter } from '../utils/DeviceUtils'; +import { ACView } from '../view/ACView'; +import { LightView } from '../view/LightView'; +import { LockView } from '../view/LockView'; + +@Entry +@Component +struct Index { + @State devices: Device[] = []; + @State filteredDevices: Device[] = []; + @State locations: string[] = []; + @State selectedLocation: string = 'all'; + private service: IDeviceService = new LocalDeviceService(); + + aboutToAppear() { + this.initSystem(); + } + + async initSystem() { + (this.service as LocalDeviceService).initialize(); + this.devices = await this.service.getDevices(); + + + this.filteredDevices = [...this.devices]; + this.locations = getAllLocations(this.devices); + } + + filterDevices(location: string) { + this.selectedLocation = location; + if (location === 'all') { + this.filteredDevices = [...this.devices]; + } else { + const locationFilter = createLocationFilter(location); + this.filteredDevices = this.devices.filter(locationFilter); + } + } + + getLocationDisplayName(location: string): string | Resource { + const locationMap: Record = { + 'livingRoom': $r('app.string.livingRoom'), + 'bedroom': $r('app.string.bedroom'), + 'entrance': $r('app.string.entrance'), + 'all': $r('app.string.all') + }; + return locationMap[location] || location; + } + + build() { + Column() { + Column() { + Text($r('app.string.smart_home_control')) + .fontSize(26) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10, left: 16 }) + } + // 位置筛选器 + Row({ space: 8 }) { + Button(this.getLocationDisplayName('all')) + .width(60) + .height(36) + .backgroundColor(this.selectedLocation === 'all' ? '#0A59F7' : '#0D000000') + .fontSize(14) + .fontColor(this.selectedLocation === 'all' ? '#ffffff' : '#E6000000') + .onClick(() => this.filterDevices('all')) + + ForEach(this.locations, (location: string) => { + Button(this.getLocationDisplayName(location)) + .width(60) + .height(36) + .backgroundColor(this.selectedLocation === location ? '#0A59F7' : '#0D000000') + .fontSize(14) + .fontColor(this.selectedLocation === location ? '#ffffff' : '#E6000000') + .onClick(() => this.filterDevices(location)) + }) + } + .margin({top:10, left:16, bottom: 15 }) + + List() { + if (this.filteredDevices.length === 0) { + ListItem() { + Text($r('app.string.device_not_found')) + .fontSize(16) + .fontColor('#666') + .padding(20) + .width('100%') + .textAlign(TextAlign.Center) + } + } else { + ForEach(this.filteredDevices, (device: Device) => { + ListItem() { + if (device.type === 'ac') { + ACView({ device: device }) + .margin({ bottom: 12 }) + } else if (device.type === 'light') { + LightView({ device: device }) + .margin({ bottom: 12 }) + } else { + LockView({ device: device }) + .margin({ bottom: 12 }) + } + } + .margin({ + left: 8, + right: 8, + }) + }) + } + } + .scrollBar(BarState.Off) + .layoutWeight(1) + .width('100%') + } + .alignItems(HorizontalAlign.Start) + .ignoreLayoutSafeArea() + .height(LayoutPolicy.matchParent) + .padding({ + top: 48, + }) + .width('100%') + .linearGradient({ + direction: GradientDirection.Bottom, + colors: [['#CBD9F3', 0], [Color.White, 1]] + }) + } +} + + diff --git a/entry/src/main/ets/services/IDeviceService.ets b/entry/src/main/ets/services/IDeviceService.ets new file mode 100644 index 0000000000000000000000000000000000000000..7d7e79781ce7d50f65526a26929ba56e9e077b90 --- /dev/null +++ b/entry/src/main/ets/services/IDeviceService.ets @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DeviceAction } from '../components/types'; + +/** + * 泛型设备服务接口:定义设备管理的核心操作契约,支持任意继承自Device的设备类型 + * @template T - 设备类型,约束为Device的子类 + * 作用:统一外部调用方式,屏蔽底层设备的实现差异 + */ +export interface IDeviceService { + + /** + * 获取所有设备列表 + * @returns Promise 设备数组(异步返回,模拟网络/本地存储查询) + */ + getDevices(): Promise; + + /** + * 执行设备操作(开机/关机) + * @param deviceId 设备唯一标识 + * @param action 操作指令(仅支持 'on' 或 'off') + * @returns Promise 操作结果(成功 true/失败 false) + */ + executeAction(deviceId: string, action: 'on' | 'off'): Promise; + + /** + * 设备状态变化回调函数 + * 类型复用 DeviceAction,确保回调签名与设备操作一致 + * 作用:当设备状态变化时,通知外部(如 UI 层更新界面) + */ + onDeviceChange: DeviceAction; +} \ No newline at end of file diff --git a/entry/src/main/ets/services/LocalDeviceService.ets b/entry/src/main/ets/services/LocalDeviceService.ets new file mode 100644 index 0000000000000000000000000000000000000000..21c0f8a2747d5ec80d821720788d15b75a4bb8e8 --- /dev/null +++ b/entry/src/main/ets/services/LocalDeviceService.ets @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Device } from '../model/Device'; +import { Light } from '../model/Light'; +import { AirConditioner } from '../model/AirConditioner'; +import { SmartLock } from '../model/SmartLock'; +import { delay } from '../utils/DeviceUtils'; +import { IDeviceService } from './IDeviceService'; +import { Location } from '../components/types'; + +/** + * 泛型本地设备服务类 + * @template T - 设备类型,约束为Device的子类(确保具备turnOn/turnOff方法) + * 特点:内置设备数据,模拟网络延迟,支持状态回调 + */ +export class LocalDeviceService implements IDeviceService { + /** + * 私有设备列表:仅服务类内部可修改,外部通过 getDevices() 只读访问 + * 封装性:避免外部直接操作设备数组,确保数据安全性 + */ + private devices: T[] = []; + + /** + * 设备状态变化回调:默认提供空实现(避免回调为 undefined) + * 外部可通过赋值自定义回调逻辑(如 UI 层监听设备变化) + */ + onDeviceChange: (deviceId: string, action: string) => Promise = async () => false; + + /** + * 获取设备列表:返回设备数组的浅拷贝(避免外部修改原数组) + * @returns Promise 设备列表(带 400ms 延迟,模拟网络请求) + */ + async getDevices(): Promise { + await delay(400); // 模拟从本地存储/网络获取设备数据的延迟 + return [...this.devices]; // 返回拷贝数组,防止外部修改内部状态 + } + + /** + * 执行设备操作:根据设备 ID 和操作指令控制设备 + * @param deviceId 设备唯一标识 + * @param action 操作指令('on' 开机 / 'off' 关机) + * @returns Promise 操作结果 + */ + async executeAction(deviceId: string, action: 'on' | 'off'): Promise { + // 1. 根据设备 ID 查找目标设备 + const device = this.devices.find(d => d.id === deviceId); + if (!device) { + console.warn(`设备 ${deviceId} 不存在`); + return false; // 设备不存在,返回失败 + } + + try { + // 2. 执行对应操作(调用设备自身的 turnOn/turnOff 方法) + if (action === 'on') { + await device.turnOn(); + } else { + await device.turnOff(); + } + + // 3. 设备状态变化后,触发回调通知外部 + await this.onDeviceChange(deviceId, action); + return true; // 操作成功 + } catch (e) { + console.error(`设备 ${deviceId} 执行 ${action} 操作失败:`, e); + return false; // 操作异常,返回失败 + } + } + + /** + * 初始化设备:创建默认设备实例,模拟设备录入系统 + * 调用时机:应用启动时初始化服务 + */ + initialize(): void { + this.devices = [ + // 使用Resource转换的string类型(deviceName1)初始化实例 + new Light('L1', '客厅主灯', Location.LivingRoom), + new AirConditioner('AC1', '卧室空调', Location.Bedroom), + new SmartLock('LOCK1', '入户门锁', Location.Entrance) + ] as T[]; + console.log('本地设备服务初始化完成,已加载默认设备'); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/utils/DeviceUtils.ets b/entry/src/main/ets/utils/DeviceUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..32fddb59f8ecaac08e29ed9c2d417e2f6973b332 --- /dev/null +++ b/entry/src/main/ets/utils/DeviceUtils.ets @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Device } from '../model/Device' +import { DeviceFilter } from '../components/types'; +import { BusinessError } from '@kit.BasicServicesKit'; + + +/** + * 闭包:创建位置筛选器 + * 作用:生成一个 DeviceFilter 类型的函数,用于筛选指定位置的设备 + * @param location 目标位置(如 Location.LivingRoom) + * @returns DeviceFilter 筛选函数 + */ +export function createLocationFilter(location: string): DeviceFilter { + return (device: Device) => device.location === location; +} + +/** + * 模拟异步延迟 + * 作用:统一管理项目中的延迟逻辑,避免重复写 setTimeout + * @param ms 延迟时间(毫秒) + * @returns Promise 延迟完成的 Promise + */ +export function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +/** + * 设备操作日志工具 + * 特点:添加时间戳,统一日志格式,便于问题排查 + * @param msg 日志内容 + */ +export const logAction = (msg: string): void => { + console.log(`[DeviceLog] ${new Date().toISOString()}: ${msg}`); +}; + +/** + * 从设备列表中提取所有不重复的位置 + * @param devices 设备数组 + * @returns string[] 去重后的位置数组 + */ +export function getAllLocations(devices: Device[]): string[] { + const locations = new Set(); + devices.forEach(device => locations.add(device.location as string)); + return Array.from(locations); +} + +export function getStringContext(resource: Resource): string { + let stringContext = '' + const uiContext: UIContext | undefined = AppStorage.get('uiContext') + try { + stringContext = uiContext?.getHostContext()?.resourceManager.getStringSync(resource.id) as string; + } catch(error) { + let err = error as BusinessError + if (err.code) { + logAction(`Failed to get string context. Cause code: ${err.code}, message: ${err.message}`); + } + } + return stringContext; +} \ No newline at end of file diff --git a/entry/src/main/ets/view/ACView.ets b/entry/src/main/ets/view/ACView.ets new file mode 100644 index 0000000000000000000000000000000000000000..0ebb9b0ac2115bd854a6c5648b0c6b338568dd03 --- /dev/null +++ b/entry/src/main/ets/view/ACView.ets @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Device } from '../model/Device' +import { ACViewModel } from '../viewModel/ACViewModel'; +import { promptAction } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; + + +/** + * 空调卡片组件:支持开关控制和温度设置(16°C~30°C) + */ +@Component +export struct ACView { + @ObjectLink device: Device; // 接收父类对象 + @State ac: ACViewModel = new ACViewModel(this.device); + + build() { + Column() { + Row() { + Column() { + Row() { + Text(this.device.name) + .fontSize(18) + .fontColor(Color.Black) + .fontWeight(FontWeight.Bold) + .margin({ right: 12 }) + Toggle({ type: ToggleType.Switch, isOn: this.ac.status === 'on' ? true : false }) + .onChange((isOn: boolean)=>{ + if (isOn) { + this.ac.turnOn(); + } else { + this.ac.turnOff(); + } + }) + } + .margin({ top: 24 }) + .justifyContent(FlexAlign.Start) + Text(this.ac.status === 'on' ? '已开启': '已关闭') + .margin({ top: 8 }) + .fontSize(14) + .fontColor('#99000000') + } + .alignItems(HorizontalAlign.Start) + .margin({left: 12}) + + Blank() + .layoutWeight(1) + + Image((this.ac.status === 'on' ? $r('app.media.ic_ac_on') : $r('app.media.ic_ac_off'))) + .width(80) + } + .justifyContent(FlexAlign.Start) + // 温度控制区域 + Row() { + Image($r('app.media.ic_minus')) + .width(28) + .height(28) + .onClick(() => { + if (this.ac.status === 'on') { + this.ac.setTemperature(Math.max(16, this.ac.temperature - 1)); + } else { + promptAction.openToast({ + message: '设备已关闭', + duration: 2000 + }).catch((error: BusinessError) => { + console.error(`openToast error code is ${error.code}, message is ${error.message}`); + }); + } + }) + + Text(`${this.ac.temperature}°C`) + .fontSize(16) + .margin({ left: 20, right: 20 }) + + Image($r('app.media.ic_plus')) + .width(28) + .height(28) + .onClick(() => { + if (this.ac.status === 'on') { + this.ac.setTemperature(Math.max(16, this.ac.temperature + 1)); + } else { + promptAction.openToast({ + message: '设备已关闭', + duration: 2000 + }).catch((error: BusinessError) => { + console.error(`openToast error code is ${error.code}, message is ${error.message}`); + }); + } + }) + + } + .margin({ top: 24, bottom: 16 }) + } + .backgroundColor(Color.White) + .borderRadius(15) + .shadow({ radius: 16, color: '#5ec0d4f6', offsetY: 16 }) + .height(155) + .margin({ left: 8, right: 8, }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/LightView.ets b/entry/src/main/ets/view/LightView.ets new file mode 100644 index 0000000000000000000000000000000000000000..f8959b1eb4918593e3e66b231475a876c841da1a --- /dev/null +++ b/entry/src/main/ets/view/LightView.ets @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Device } from '../model/Device' +import { LightViewModel } from '../viewModel/LightViewModel'; + +/** + * 灯光卡片组件:支持开关控制和亮度设置(0%~100%) + */ +@Component +export struct LightView { + @ObjectLink device: Device; + @State light: LightViewModel = new LightViewModel(this.device); + + build() { + Column() { + Row() { + Column() { + Row() { + Text(this.light.name) + .fontSize(18) + .fontColor(Color.Black) + .fontWeight(FontWeight.Bold) + .margin({ right: 12 }) + Toggle({ type: ToggleType.Switch, isOn: this.light.status === 'on' }) + .width(36) + .height(20) + .onChange((isOn: boolean) => { + this.light.toggleLight(isOn); + }) + } + .margin({ top: 24 }) + .justifyContent(FlexAlign.Start) + + Text(`亮度${this.light.brightness}%`) + .margin({ top: 8 }) + .fontSize(14) + .fontColor('#99000000') + } + .alignItems(HorizontalAlign.Start) + .margin({ left: 12 }) + + Blank() + .layoutWeight(1) + + Image(this.light.status === 'on' ? $r('app.media.ic_bulb_on') : $r('app.media.ic_bulb_off')) + .width(80) + } + .justifyContent(FlexAlign.Start) + + Row() { + Image($r('app.media.minus_sun')) + .height(21) + .margin({ left: 12 }) + + // 滑块组件:用于连续调节亮度(0~100) + Slider({ + value: this.light.getBrightness(), + min: 0, + max: 100, + step: 1, + style: SliderStyle.InSet + }) + .enabled(this.light.status === 'on') + .showTips(this.light.status === 'on') + .layoutWeight(1) + .blockColor(Color.White) + .selectedColor(this.light.status === 'on' ? '#0A59F7' : '#0d000000') + .trackThickness(20) + .width(120) + .onChange(value => { + this.light.setBrightness(value); + }) + .margin({left: 12, right: 12}) + + Image($r('app.media.plus_sun')) + .height(21) + .margin({ right : 12 }) + } + .margin({ top: 24, bottom: 16 }) + .alignItems(VerticalAlign.Center) + .width('100%') + } + .backgroundColor(Color.White) + .borderRadius(15) + .shadow({ radius: 16, color: '#5ec0d4f6', offsetY: 16 }) + .height(155) + .margin({ left: 8, right: 8 }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/view/LockView.ets b/entry/src/main/ets/view/LockView.ets new file mode 100644 index 0000000000000000000000000000000000000000..8011f4fbf56eb8366dc587f7cb055ae137a2a63e --- /dev/null +++ b/entry/src/main/ets/view/LockView.ets @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Device } from '../model/Device' +import { LockViewModel } from '../viewModel/LockViewModel'; + +/** + * 智能锁卡片组件:接收 SmartLock 实例,提供开关控制和状态显示 + */ +@Component +export struct LockView { + /** + * @ObjectLink:父子组件状态联动,设备状态变化时自动刷新 UI + * 接收父组件传递的 Device 实例,向下转型为 SmartLock(需确保类型正确) + * SmartLock 原始数据封装为ViewModel,为View层的组件提供数据(推荐使用MVVM架构) + */ + @ObjectLink device: Device; // 接收父类对象 + @State lock: LockViewModel = new LockViewModel(this.device); + + build() { + // 垂直布局:卡片整体容器 + Column() { + // 水平布局:状态显示与开关区域 + Row() { + Column() { + Row() { + Text(this.lock.name) + .fontSize(18) + .fontColor(Color.Black) + .fontWeight(FontWeight.Bold) + .margin({ right: 12 }) + // 开关控件:绑定设备 status 状态,切换时调用 turnOn()/turnOff() + Toggle({ type: ToggleType.Switch, isOn: this.lock.status === 'on' ? true : false }) + .onChange((isOn: boolean)=>{ + if (isOn) { + this.lock.turnOn(); + } else { + this.lock.turnOff(); + } + }) + } + .margin({ top: 24 }) + .justifyContent(FlexAlign.Start) + Text(this.lock.status === 'on' ? '已打开': '已关闭') + .margin({ top: 8 }) + .fontSize(14) + .fontColor('#99000000') + } + .alignItems(HorizontalAlign.Start) + .margin({left: 12}) + + // 弹性占位:推挤右侧图片到最右边 + Blank() + .layoutWeight(1) + + // 状态图标:根据设备状态切换开锁/关锁图标 + Image((this.lock.status === 'on' ? $r('app.media.ic_unlock') : $r('app.media.ic_locked'))) + .width(80) + } + } + .backgroundColor(Color.White) + .borderRadius(15) + .shadow({ radius: 16, color: '#5ec0d4f6', offsetY: 16 }) + .height(99) + .margin({ left: 8, right: 8 }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/ACViewModel.ets b/entry/src/main/ets/viewModel/ACViewModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..18a930f9db53380f59d72d94143ab234b11393cc --- /dev/null +++ b/entry/src/main/ets/viewModel/ACViewModel.ets @@ -0,0 +1,33 @@ +import { AirConditioner } from '../model/AirConditioner'; +import { Device } from '../model/Device'; + +@Observed +export class ACViewModel { + private ac: AirConditioner; + name: string = ''; + status: 'on' | 'off' = 'off'; + temperature: number = 26; + + constructor(device: Device) { + this.ac = device as AirConditioner; + this.name = device.name; + this.status = this.ac.getStatus(); + this.temperature = this.ac.getTemperature(); + } + + async turnOn(): Promise { + this.status = 'on'; + await this.ac.turnOn(); + } + + async turnOff(): Promise { + this.status = 'off'; + await this.ac.turnOff(); + } + + setTemperature(level: number): void { + this.temperature = level; + this.ac.setTemperature(level); + } + +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/LightViewModel.ets b/entry/src/main/ets/viewModel/LightViewModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..f356678ef65b7ef521612afd14ff93e4b7dc234f --- /dev/null +++ b/entry/src/main/ets/viewModel/LightViewModel.ets @@ -0,0 +1,37 @@ +import { Light } from '../model/Light'; +import { Device } from '../model/Device'; + +@Observed +export class LightViewModel { + private light: Light; + name: string = ''; + status: 'on' | 'off' = 'off'; + brightness: number = 0; + + constructor(device: Device) { + this.light = device as Light; + this.name = device.name; + this.status = this.light.getStatus(); + this.brightness = this.light.getBrightness(); + } + + getBrightness(): number { + return this.light.getBrightness(); + } + + async toggleLight(isOn: boolean): Promise { + if (isOn) { + this.status = 'on'; + await this.light.turnOn(); + } else { + this.status = 'off'; + await this.light.turnOff(); + } + } + + setBrightness(level: number): void { + this.brightness = level; + this.light.setBrightness(level); + } + +} \ No newline at end of file diff --git a/entry/src/main/ets/viewModel/LockViewModel.ets b/entry/src/main/ets/viewModel/LockViewModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..f2de66a3a11c603a0587a86b03a79ee6dd82ab5c --- /dev/null +++ b/entry/src/main/ets/viewModel/LockViewModel.ets @@ -0,0 +1,26 @@ +import { Device } from '../model/Device'; +import { SmartLock } from "../model/SmartLock"; + +@Observed +export class LockViewModel { + private lock: SmartLock; + name: string = ''; + status: 'on' | 'off' = 'off'; + temperature: number = 26; + + constructor(device: Device) { + this.lock = device as SmartLock; + this.name = device.name; + this.status = this.lock.getStatus(); + } + + async turnOn(): Promise { + this.status = 'on'; + await this.lock.turnOn(); + } + + async turnOff(): Promise { + this.status = 'off'; + await this.lock.turnOff(); + } +} \ No newline at end of file diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..53024e8f020cc0824592631ba6924108c8028f0f --- /dev/null +++ b/entry/src/main/module.json5 @@ -0,0 +1,50 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "ohos.want.action.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ], + } + ] + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/color.json b/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/float.json b/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..33ea22304f9b1485b5f22d811023701b5d4e35b6 --- /dev/null +++ b/entry/src/main/resources/base/element/float.json @@ -0,0 +1,8 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + } + ] +} diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..40b6b4401596e4e2581090ddb7327c33208aa304 --- /dev/null +++ b/entry/src/main/resources/base/element/string.json @@ -0,0 +1,52 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + }, + { + "name": "livingRoom", + "value": "living room" + }, + { + "name": "bedroom", + "value": "bedroom" + }, + { + "name": "entrance", + "value": "entrance" + }, + { + "name": "all", + "value": "all" + }, + { + "name": "device_not_found", + "value": "device is not found" + }, + { + "name": "smart_home_control", + "value": "smart home control" + }, + { + "name": "living_room_main_light", + "value": "living room main light" + }, + { + "name": "bedroom_air_conditioner", + "value": "bedroom air conditioner" + }, + { + "name": "front_door_lock", + "value": "front door lock" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/background.png b/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/entry/src/main/resources/base/media/background.png differ diff --git a/entry/src/main/resources/base/media/foreground.png b/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9427585b36d14b12477435b6419d1f07b3e0bb Binary files /dev/null and b/entry/src/main/resources/base/media/foreground.png differ diff --git a/entry/src/main/resources/base/media/ic_ac_off.png b/entry/src/main/resources/base/media/ic_ac_off.png new file mode 100644 index 0000000000000000000000000000000000000000..3d1055ded588cada43a6e40db60e4666557f2b49 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_ac_off.png differ diff --git a/entry/src/main/resources/base/media/ic_ac_on.png b/entry/src/main/resources/base/media/ic_ac_on.png new file mode 100644 index 0000000000000000000000000000000000000000..79aa98ad80c234ad7e53457fbde2d512388d35df Binary files /dev/null and b/entry/src/main/resources/base/media/ic_ac_on.png differ diff --git a/entry/src/main/resources/base/media/ic_bulb_off.png b/entry/src/main/resources/base/media/ic_bulb_off.png new file mode 100644 index 0000000000000000000000000000000000000000..b28a34440e900438b758d8cae13322b3b9aad794 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_bulb_off.png differ diff --git a/entry/src/main/resources/base/media/ic_bulb_on.png b/entry/src/main/resources/base/media/ic_bulb_on.png new file mode 100644 index 0000000000000000000000000000000000000000..4e65fbf8aee83edac4f4aa6d960abfd9748e569b Binary files /dev/null and b/entry/src/main/resources/base/media/ic_bulb_on.png differ diff --git a/entry/src/main/resources/base/media/ic_locked.png b/entry/src/main/resources/base/media/ic_locked.png new file mode 100644 index 0000000000000000000000000000000000000000..69c5626eec66aa4b2fd67f37a49e11e0e97f07a5 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_locked.png differ diff --git a/entry/src/main/resources/base/media/ic_minus.png b/entry/src/main/resources/base/media/ic_minus.png new file mode 100644 index 0000000000000000000000000000000000000000..66089210696747d0e9e99ce834d5cefe8d9b0cf5 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_minus.png differ diff --git a/entry/src/main/resources/base/media/ic_plus.png b/entry/src/main/resources/base/media/ic_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..893173adc03918d2289c244ecbcbf084d1353e30 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_plus.png differ diff --git a/entry/src/main/resources/base/media/ic_unlock.png b/entry/src/main/resources/base/media/ic_unlock.png new file mode 100644 index 0000000000000000000000000000000000000000..9461821e33f44d2e5880608d9adebda816369326 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_unlock.png differ diff --git a/entry/src/main/resources/base/media/layered_image.json b/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/minus_sun.svg b/entry/src/main/resources/base/media/minus_sun.svg new file mode 100644 index 0000000000000000000000000000000000000000..09e4b3b5183ebd40db533bee8f4afd27695865f9 --- /dev/null +++ b/entry/src/main/resources/base/media/minus_sun.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/entry/src/main/resources/base/media/plus_sun.svg b/entry/src/main/resources/base/media/plus_sun.svg new file mode 100644 index 0000000000000000000000000000000000000000..4d03d8732c01b9783530972e002610d8f6364701 --- /dev/null +++ b/entry/src/main/resources/base/media/plus_sun.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/entry/src/main/resources/base/media/startIcon.png b/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/entry/src/main/resources/base/media/startIcon.png differ diff --git a/entry/src/main/resources/base/profile/backup_config.json b/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/entry/src/main/resources/dark/element/color.json b/entry/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..79b11c2747aec33e710fd3a7b2b3c94dd9965499 --- /dev/null +++ b/entry/src/main/resources/dark/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#000000" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/en_US/element/string.json b/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..40b6b4401596e4e2581090ddb7327c33208aa304 --- /dev/null +++ b/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,52 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + }, + { + "name": "livingRoom", + "value": "living room" + }, + { + "name": "bedroom", + "value": "bedroom" + }, + { + "name": "entrance", + "value": "entrance" + }, + { + "name": "all", + "value": "all" + }, + { + "name": "device_not_found", + "value": "device is not found" + }, + { + "name": "smart_home_control", + "value": "smart home control" + }, + { + "name": "living_room_main_light", + "value": "living room main light" + }, + { + "name": "bedroom_air_conditioner", + "value": "bedroom air conditioner" + }, + { + "name": "front_door_lock", + "value": "front door lock" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..b2b09a7a7f473b817297df495b00288ff29ba0de --- /dev/null +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,52 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + }, + { + "name": "livingRoom", + "value": "客厅" + }, + { + "name": "bedroom", + "value": "卧室" + }, + { + "name": "entrance", + "value": "入口" + }, + { + "name": "all", + "value": "全部" + }, + { + "name": "device_not_found", + "value": "没有找到设备" + }, + { + "name": "smart_home_control", + "value": "智能家居遥控" + }, + { + "name": "living_room_main_light", + "value": "客厅主灯" + }, + { + "name": "bedroom_air_conditioner", + "value": "卧室空调" + }, + { + "name": "front_door_lock", + "value": "入户门锁" + } + ] +} \ No newline at end of file diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7a7ab8914d8db6ab89758e185df5855dffe88d04 --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,23 @@ +{ + "modelVersion": "6.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/hvigorfile.ts b/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..47113e2e36ecefde41c136272a0bd6ff745cffe4 --- /dev/null +++ b/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/oh-package-lock.json5 b/oh-package-lock.json5 new file mode 100644 index 0000000000000000000000000000000000000000..6b264af261c4152dee3641068ee2fff156299e4b --- /dev/null +++ b/oh-package-lock.json5 @@ -0,0 +1,28 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", + "@ohos/hypium@1.0.24": "@ohos/hypium@1.0.24" + }, + "packages": { + "@ohos/hamock@1.0.0": { + "name": "", + "version": "1.0.0", + "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har", + "registryType": "ohpm" + }, + "@ohos/hypium@1.0.24": { + "name": "", + "version": "1.0.24", + "integrity": "sha512-3dCqc+BAR5LqEGG2Vtzi8O3r7ci/3fYU+FWjwvUobbfko7DUnXGOccaror0yYuUhJfXzFK0aZNMGSnXaTwEnbw==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.24.har", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c72aa05d549507e5ea6ce0bc0a8080c450508c7d --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "modelVersion": "6.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.24", + "@ohos/hamock": "1.0.0" + } +} diff --git a/screenshots/device/screenshots.png b/screenshots/device/screenshots.png new file mode 100644 index 0000000000000000000000000000000000000000..5c1e009e8606467d313ba831ca9a95be862553a5 Binary files /dev/null and b/screenshots/device/screenshots.png differ