commit 6ef4ef6d276287647d344c7ff3b0c16d1a78e178 Author: 徐勤民 Date: Tue May 7 17:41:21 2024 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2713a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/BuildProfile.ets b/BuildProfile.ets new file mode 100644 index 0000000..3479da7 --- /dev/null +++ b/BuildProfile.ets @@ -0,0 +1,5 @@ +export default class BuildProfile { + static readonly HAR_VERSION = '1.0.2'; + static readonly BUILD_MODE_NAME = 'debug'; + static readonly DEBUG = true; +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..23ebc86 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# [v1.0.0] 2024.04.23 + +------ + +> - 网络请求 +> - 正则验证 +> - 基础工具 +> - 统一弹窗 +> - 存储管理 \ No newline at end of file diff --git a/Index.ets b/Index.ets new file mode 100644 index 0000000..5c6ab5c --- /dev/null +++ b/Index.ets @@ -0,0 +1,40 @@ +/** + * 常用工具 + */ +export { ToolsHelper } from './src/main/ets/utils/ToolsHelper' + +/** + * 存储相关 + */ +export { AppStorageHelper } from './src/main/ets/utils/AppStorageHelper' + +export { PreferencesHelper } from './src/main/ets/utils/PreferencesHelper' + +/** + * 正则相关 + */ +export { ValidatorHelper } from './src/main/ets/utils/ValidatorHelper' + +/** + * 网络请求 + */ +export { HttpHelper } from './src/main/ets/http/HttpHelper' + +/** + * 打开H5页面 + */ +export { XWebHelper } from './src/main/ets/utils/XWebHelper' + +/** + * 列表选择弹窗 + */ +export { XDialogList } from './src/main/ets/dialog/XDialogList' +export { XDialogCommon } from './src/main/ets/dialog/XDialogCommon' + +export { XDialogController } from './src/main/ets/dialog/XDialogController' + +/** + * 窗口管理 + */ +export { WindowHelper } from './src/main/ets/utils/WindowHelper' + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a6f730a --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +Mozilla Public License Version 2.0 + +1. Definitions +2. Scope +3. Grant of License +4. Restrictions +5. Original Code +6. Modifications +7. Required Notices +8. Disclaimer of Warranty +9. Limitation of Liability +10. Termination +11. Jurisdiction +12. Miscellaneous + diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe2c4c2 --- /dev/null +++ b/README.md @@ -0,0 +1,229 @@ +# 基础开发工具包 +```shell +ohpm install @szyx/sdk_base +``` + +## 1.[utils](./src/main/ets/utils) + +### 1.1.[AppStorageHelper](./src/main/ets/utils/AppStorageHelper.ets) +> 缓存工具类,运行时存储,应用停止运行后清空 + +```typescript +import { AppStorageHelper } from '@szyx/sdk_base/Index' + +// 存储string数据 +AppStorageHelper.save(StorageKeys.CLIENT_ID, d) + + // 获取存储的strign数据 +let d = AppStorageHelper.get(StorageKeys.CLIENT_ID) +``` + +### 1.2.[PreferencesHelper](./src/main/ets/utils/PreferencesHelper.ets) +> 永久存储类,应用停止后也不会清空 +> 需要验证,更新应用会不会被清理 +> 可存储类型 `number | string | boolean | Array | Array | Array | Uint8Array` + +```typescript +import { PreferencesHelper } from '@szyx/sdk_base/Index' +// 存储数据 +PreferencesHelper.put(StorageKeys.CLIENT_ID, value) + + // 获取存储的数据 +PreferencesHelper.get(StorageKeys.CLIENT_ID).then(res => { + console.log('>>>>>', res) +}) +``` + +### 1.3.[ToolsHelper](./src/main/ets/utils/ToolsHelper.ets) +> 常用方法工具栏 +> 基础方法 +#### 1.3.1.弹出Toast提示 + +```typescript +import { ToolsHelper } from '@szyx/sdk_base'; + +ToolsHelper.showMessage('Hello Word!') +``` + +### 1.4.[ValidatorHelper](./src/main/ets/utils/ValidatorHelper.ets) +> 常用正则 + +#### 1.4.1.验证手机号 + +```typescript +import { ValidatorHelper } from '@szyx/sdk_base'; + ValidatorHelper.isPhone('13800000000') +``` + +### 1.5.[XWebHelper](./src/main/ets/utils/XWebHelper.ets) +> 打开webview页面 + +```typescript +import { XWebHelper } from '@szyx/sdk_base'; +// 必须先引入,否则无法跳转 +const XWebview = import('../pages/XWebview'); + +XWebHelper.openWeb({ + url: 'https://www.baidu.com', + title: '百度一下', +}) +``` + +## 2.[Dialog](./src/main/ets/dialog) +### 2.1.弹出list选中弹窗 +```typescript +import { XDialogController } from '../dialog/XDialogController'; +import { XDialogList } from '../dialog/XDialogList'; + +@Component +struct MyView { + // 控制器,控制开关 + dialogController: XDialogController = {} as XDialogController + + build() { + Column() { + Button({ buttonStyle: ButtonStyleMode.TEXTUAL }) { + Image($r('sys.media.ohos_ic_public_more')) + .width(26).height(26) + }.width(65) + .onClick(() => { + if (this.dialogController != null) { + this.dialogController.open() + } + }) + + XDialogList({ + // 控制器 + controller: this.dialogController, + // 标题(可选) + title: '选择您的操作', + // 选择内容列表 + values: ['刷新', '浏览器打开', '分享', '复制地址'], + // 用户选择事件 + onSelected: (index: number, value: string) => { + ToolsHelper.showMessage(`用户选择了第${index}个,内容为:${value}`) + }, + // 用户取消事件 + onCancel: () => { + ToolsHelper.showMessage('用户取消操作') + }, + // 是否可取消(点击空白处,或者物理返回键) + autoCancel: true + }) + + }.width('100%').height('100%') + } +} + +``` +## 3.[网络请求](./src/main/ets/http/HttpHelper.ts) +> 使用室建议二次封装 +> +> 参数定义 +> ```typescript +> /** +> * +> * @param url url地址 +> * @param data 请求参数 +> * @param headers 请求头 +> * @param apiNo 请求标识,取消请求或者去重使用|考虑做自动重试使用 +> * @returns +> */ +> ``` + +### 3.1.get请求 +```typescript + + HttpHelper.get() + .get>(url.url.startsWith('http') ? url.url : GlobalValue.getInstance().envUrl + url.url, + data ? JSON.stringify(data) : undefined, { + userId: GlobalValue.getInstance().userId, + clientId: GlobalValue.getInstance().getClientId(), + version: ConstantValue.VERSION, + deviceType: '01', + timeStamp: timeStamp + '', + sign: sign, + phoneModel: 'sign', + phoneVersion: 'sign', + phoneBrand: 'HarmonyOS' + }, url.apiNo) + .then((res: HttpResult) => { + if (res.status === '0') { + resolve(res.data as T) + } else { + reject(new Error(res.message)) + } + + }) + .catch((error: Error) => { + reject(error) + }) +``` +### 3.2.postJson +```typescript + +HttpHelper.get() + .post>(url.url.startsWith('http') ? url.url : GlobalValue.getInstance().envUrl + url.url, + data ? JSON.stringify(data) : undefined, { + userId: GlobalValue.getInstance().userId, + clientId: GlobalValue.getInstance().getClientId(), + version: ConstantValue.VERSION, + deviceType: '01', + timeStamp: timeStamp + '', + sign: sign, + phoneModel: 'sign', + phoneVersion: 'sign', + phoneBrand: 'HarmonyOS' + }, url.apiNo) + .then((res: HttpResult) => { + if (res.status === '0') { + resolve(res.data as T) + } else { + reject(new Error(res.message)) + } + + }) + .catch((error: Error) => { + reject(error) + }) +``` + + + +# **** 常见问题 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UTILS.md b/UTILS.md new file mode 100644 index 0000000..bc52926 --- /dev/null +++ b/UTILS.md @@ -0,0 +1,211 @@ +- # [@State装饰器:组件内状态](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-0000001774279614) +> @State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。 +> +> 在状态变量相关装饰器中,@State是最基础的,使变量拥有状态属性的装饰器,它也是大部分状态变量的数据源。 + +> @State装饰的变量,与声明式范式中的其他被装饰变量一样,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化。初始化也可选择使用命名参数机制从父组件完成初始化。 +> +> @State装饰的变量拥有以下特点: +> +> @State装饰的变量与子组件中的@Prop装饰变量之间建立单向数据同步,与@Link、@ObjectLink装饰变量之间建立双向数据同步。 +> +> @State装饰的变量生命周期与其所属自定义组件的生命周期相同。 +> + +- # [@Prop装饰器:父子单向同步](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-prop-0000001774119942) +> @Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。 + +>@Prop装饰的变量和父组件建立单向的同步关系: +> +>@Prop变量允许在本地修改,但修改后的变化不会同步回父组件。 +> +>当数据源更改时,@Prop装饰的变量都会更新,并且会覆盖本地所有更改。因此,数值的同步是父组件到子组件(所属组件),子组件数值的变化不会同步到父组件。 +> +- # [@Link装饰器:父子双向同步](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-link-0000001820999565) + +> 子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。 +> +> @Link装饰的变量与其父组件中的数据源共享相同的值。 +> +> @Link装饰器不能在@Entry装饰的自定义组件中使用。 + +- # [@Provide装饰器和@Consume装饰器:与后代组件双向同步](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-provide-and-consume-0000001820879589) + +> @Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。 +> +> 其中@Provide装饰的变量是在祖先组件中,可以理解为被“提供”给后代的状态变量。@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先组件提供的变量。 + +> @Provide/@Consume装饰的状态变量有以下特性: +> +> @Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。 +> +> 后代通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步,与@State/@Link不同的是,前者可以在多层级的父子组件之间传递。 +> +> @Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常。 + +```typescript +// 通过相同的变量名绑定 +@Provide a: number = 0; +@Consume a: number; + +// 通过相同的变量别名绑定 +@Provide('a') b: number = 0; +@Consume('a') c: number; +@Provide和@Consume通过相同的变量名或者相同的变量别名绑定时,@Provide装饰的变量和@Consume装饰的变量是一对多的关系。不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的@Provide装饰的变量,@Provide的属性名或别名需要唯一且确定,如果声明多个同名或者同别名的@Provide装饰的变量,会发生运行时报错。 + +``` + +- # [@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-observed-and-objectlink-0000001774279618) +> 上文所述的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink装饰器。 + +> @ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步: +> +> 被@Observed装饰的类,可以被观察到属性的变化; +> +> 子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。 +> +> 单独使用@Observed是没有任何作用的,需要搭配@ObjectLink或者@Prop使用。 + +> 使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。 +> +> @ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。 + + + + + +- # [@Watch装饰器:状态变量更改通知](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-watch-0000001774119954) +> @Watch应用于对状态变量的监听。如果开发者需要关注某个状态变量的值是否改变,可以使用@Watch为状态变量设置回调函数。 + +> @Watch用于监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用。@Watch在ArkUI框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当在严格相等为false的情况下,就会触发@Watch的回调。 + +```typescript +@Component +struct TotalView { + @Prop @Watch('onCountUpdated') count: number = 0; + @State total: number = 0; + // @Watch 回调 + onCountUpdated(propName: string): void { + this.total += this.count; + } + + build() { + Text(`Total: ${this.total}`) + } +} + +@Entry +@Component +struct CountModifier { + @State count: number = 0; + + build() { + Column() { + Button('add to basket') + .onClick(() => { + this.count++ + }) + TotalView({ count: this.count }) + } + } +} +``` + + + +- # [$$语法:内置组件双向同步](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-two-way-sync-0000001820999577) + +```typescript +// xxx.ets +@Entry +@Component +struct TextInputExample { + @State text: string = '' + controller: TextInputController = new TextInputController() + + build() { + Column({ space: 20 }) { + Text(this.text) + TextInput({ text: $$this.text, placeholder: 'input your word...', controller: this.controller }) + .placeholderColor(Color.Grey) + .placeholderFont({ size: 14, weight: 400 }) + .caretColor(Color.Blue) + .width(300) + }.width('100%').height('100%').justifyContent(FlexAlign.Center) + } +} +``` + + + +- # [@Track装饰器:class对象属性级更新](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-track-0000001820879601) + +> @Track应用于class对象的属性级更新。@Track装饰的属性变化时,只会触发该属性关联的UI更新。 + +> @Track是class对象的属性装饰器。当一个class对象是状态变量时,@Track装饰的属性发生变化,只会触发该属性关联的UI更新;而未被标记的属性不能在UI中使用。 + +```typescript +class LogTrack { + @Track str1: string; + @Track str2: string; + + constructor(str1: string) { + this.str1 = str1; + this.str2 = 'World'; + } +} + +class LogNotTrack { + str1: string; + str2: string; + + constructor(str1: string) { + this.str1 = str1; + this.str2 = '世界'; + } +} + +@Entry +@Component +struct AddLog { + @State logTrack: LogTrack = new LogTrack('Hello'); + @State logNotTrack: LogNotTrack = new LogNotTrack('你好'); + + isRender(index: number) { + console.log(`Text ${index} is rendered`); + return 50; + } + + build() { + Row() { + Column() { + Text(this.logTrack.str1) // UINode1 + .fontSize(this.isRender(1)) + .fontWeight(FontWeight.Bold) + Text(this.logTrack.str2) // UINode2 + .fontSize(this.isRender(2)) + .fontWeight(FontWeight.Bold) + Button('change logTrack.str1') + .onClick(() => { + this.logTrack.str1 = 'Bye'; + }) + Text(this.logNotTrack.str1) // UINode3 + .fontSize(this.isRender(3)) + .fontWeight(FontWeight.Bold) + Text(this.logNotTrack.str2) // UINode4 + .fontSize(this.isRender(4)) + .fontWeight(FontWeight.Bold) + Button('change logNotTrack.str1') + .onClick(() => { + this.logNotTrack.str1 = '再见'; + }) + } + .width('100%') + } + .height('100%') + } +} +``` + + + diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000..abede8d --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,44 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + { + "name": "release2", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + } + ] +} diff --git a/consumer-rules.txt b/consumer-rules.txt new file mode 100644 index 0000000..e69de29 diff --git a/hvigorfile.ts b/hvigorfile.ts new file mode 100644 index 0000000..4218707 --- /dev/null +++ b/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/obfuscation-rules.txt b/obfuscation-rules.txt new file mode 100644 index 0000000..2a06f12 --- /dev/null +++ b/obfuscation-rules.txt @@ -0,0 +1 @@ +-remove-log \ No newline at end of file diff --git a/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000..92253e2 --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "name": "@szyx/sdk_base", + "version": "1.0.2", + "description": "数字医信公司,鸿蒙app开发基础工具。", + "main": "Index.ets", + "author": "xuqm", + "license": "Mozilla-2.0", + "repository": "", + "dependencies": {} +} diff --git a/src/main/ets/dialog/XDialogCommon.ets b/src/main/ets/dialog/XDialogCommon.ets new file mode 100644 index 0000000..44ebf11 --- /dev/null +++ b/src/main/ets/dialog/XDialogCommon.ets @@ -0,0 +1,9 @@ +@CustomDialog +export struct XDialogCommon { + controller: CustomDialogController + @BuilderParam dialogContent: () => void + + build() { + this.dialogContent() + } +} \ No newline at end of file diff --git a/src/main/ets/dialog/XDialogController.ets b/src/main/ets/dialog/XDialogController.ets new file mode 100644 index 0000000..cbbddaf --- /dev/null +++ b/src/main/ets/dialog/XDialogController.ets @@ -0,0 +1,4 @@ +export interface XDialogController { + open: () => void + close: () => void +} \ No newline at end of file diff --git a/src/main/ets/dialog/XDialogList.ets b/src/main/ets/dialog/XDialogList.ets new file mode 100644 index 0000000..a04b154 --- /dev/null +++ b/src/main/ets/dialog/XDialogList.ets @@ -0,0 +1,82 @@ +import { XDialogCommon } from './XDialogCommon' +import { XDialogController } from './XDialogController' + + +@Preview +@Component +export struct XDialogList { + controller: XDialogController | null = null + onSelected: (index: number, value: string) => void = (index: number, value: string) => { + } + onCancel?: () => void = undefined + title?: string = undefined + values?: Array | Array = undefined + autoCancel?: boolean = false + private dialogController: CustomDialogController | null = null + + build() { + this.buildContent() + } + + aboutToAppear(): void { + + this.dialogController = new CustomDialogController({ + builder: XDialogCommon({ + dialogContent: () => { + this.buildContent() + }, + }), + alignment: DialogAlignment.Center, + autoCancel: this.autoCancel ?? false, + cancel: () => { + this.onCancel && this.onCancel() + } + }) + if (this.controller) { + this.controller.open = () => { + this.dialogController?.open() + } + this.controller.close = () => { + this.dialogController?.close() + } + } + } + + @Builder + buildContent() { + Column() { + Text(this.title) + .fontSize(13) + .textAlign(TextAlign.Center) + .width('60%') + .maxLines(2) + .ellipsisMode(EllipsisMode.END) + .textOverflow({ + overflow: TextOverflow.Ellipsis + }) + .visibility(this.title ? Visibility.Visible : Visibility.None) + List({ space: 20, initialIndex: 0 }) { + ForEach(this.values ?? [], (item: string, index: number) => { + ListItem() { + Text(item) + .width('100%') + .fontSize(16) + .textAlign(TextAlign.Center) + .onClick(() => { + this.onSelected(index, item) + this.dialogController?.close() + }) + } + }, (item: string) => item) + } + .listDirection(Axis.Vertical) // 排列方向 + .scrollBar(BarState.Off) + .friction(0.6) + .divider({ strokeWidth: 1, color: 0xEEEEEE, startMargin: 20, endMargin: 20 }) // 每行之间的分界线 + .edgeEffect(EdgeEffect.Spring) // 边缘效果设置为Spring + .width('100%') + .height(this.values === undefined ? '20%' : this.values?.length < 8 ? `${this.values?.length / 16 * 100}%` : '50%') + .margin({ top: 20 }) + }.padding({ top: 20, bottom: 20, left: 20, right: 20 }) + } +} \ No newline at end of file diff --git a/src/main/ets/http/HttpHelper.ts b/src/main/ets/http/HttpHelper.ts new file mode 100644 index 0000000..688830e --- /dev/null +++ b/src/main/ets/http/HttpHelper.ts @@ -0,0 +1,189 @@ +import { ArrayList, HashMap } from '@kit.ArkTS'; +import http from '@ohos.net.http'; + + +export class HttpHelper { + private static instance: HttpHelper | null = null + + // 单例模式 + static get() { + // 判断系统是否已经有单例了 + if (HttpHelper.instance === null) { + HttpHelper.instance = new HttpHelper() + } + return HttpHelper.instance + } + + //请求中队列 + private httpHandlerList = new HashMap(); + // 并发白名单,这个名单里面的api,重复请求不会取消 + private concurrentList = new ArrayList(); + + constructor() { + this.httpHandlerList = new HashMap(); + this.concurrentList.clear() + } + + /** + * 添加并发白名单 + * @param apiNo + */ + public addConcurrent(apiNo: string) { + if (this.concurrentList.getIndexOf(apiNo) === -1) { + this.concurrentList.add(apiNo) + } + } + + public removeConcurrent(apiNo: string) { + if (this.concurrentList.getIndexOf(apiNo) !== -1) { + this.concurrentList.remove(apiNo) + } + } + + /** + * postJson请求 + * @param url url地址 + * @param data 请求参数 + * @param headers + * @param apiNo 请求标识,取消请求或者去重使用|考虑做自动重试使用 + * @returns + */ + public post(url: string, data: string | undefined, headers?: Object, apiNo?: string): Promise { + + return new Promise((resolve, reject) => { + + if (this.concurrentList.getIndexOf(apiNo ?? url) === -1 && this.httpHandlerList.hasKey(apiNo ?? url)) { + this.httpHandlerList.get(apiNo ?? url).destroy() + this.httpHandlerList.remove(apiNo ?? url) + } + let httpRequest = http.createHttp(); + + if (this.concurrentList.getIndexOf(apiNo ?? url) === -1) { + this.httpHandlerList.set(apiNo ?? url, httpRequest) + } + + const header = { + "Content-Type": "application/json", + "Accept": "application/json", + ...headers + } + console.log('>>>>>', '接口请求', JSON.stringify(header)) + console.log('>>>>>', '接口请求', data) + console.log('>>>>>', '接口请求', url) + + httpRequest.request(url, { + method: http.RequestMethod.POST, + connectTimeout: 60000, + readTimeout: 60000, + header: header, + extraData: data + }) + .then((data: http.HttpResponse) => { + console.info('=====>' + 'Result:' + data.result as string); + console.info('=====>' + 'code:' + data.responseCode); + // console.info('=====>' + 'type:' + JSON.stringify(data.resultType)); + // console.info('=====>' + 'header:' + JSON.stringify(data.header)); + // console.info('=====>' + 'cookies:' + data.cookies); // 自API version 8开始支持cookie + // console.info('=====>' + 'header.content-Type:' + JSON.stringify(data.header)); + // console.info('=====>' + 'header.Status-Line:' + JSON.stringify(data.header)); + + if (this.httpHandlerList.hasKey(apiNo ?? url)) { + this.httpHandlerList.remove(apiNo ?? url) + } + if (data.responseCode === 200) { + resolve(JSON.parse(data.result as string) as T) + } else { + reject('服务异常') + } + }).catch((err: Error) => { + if (this.httpHandlerList.hasKey(apiNo ?? url)) { + this.httpHandlerList.remove(apiNo ?? url) + } + if (err.message === 'Failed writing received data to disk/application') { + reject('cancel') + } else + reject(err) + }); + }); + + } + + /** + * get请求 + * @param url url地址 + * @param data 请求参数 + * @param headers + * @param apiNo 请求标识,取消请求或者去重使用|考虑做自动重试使用 + * @returns + */ + public get(url: string, data: string | undefined, headers?: Object, apiNo?: string): Promise { + + return new Promise((resolve, reject) => { + + if (this.concurrentList.getIndexOf(apiNo ?? url) === -1 && this.httpHandlerList.hasKey(apiNo ?? url)) { + this.httpHandlerList.get(apiNo ?? url).destroy() + this.httpHandlerList.remove(apiNo ?? url) + } + let httpRequest = http.createHttp(); + + if (this.concurrentList.getIndexOf(apiNo ?? url) === -1) { + this.httpHandlerList.set(apiNo ?? url, httpRequest) + } + + const header = { + // "Content-Type": "application/json", + // "Accept": "application/json", + ...headers + } + // console.log('>>>>>', '接口请求', JSON.stringify(header)) + // console.log('>>>>>', '接口请求', data) + // console.log('>>>>>', '接口请求', url) + + if (data) { + url = `${url}?` + const json = JSON.parse(data) + for (let jsonKey in json) { + const value = json[jsonKey] + if (value) { + url = `${url}${jsonKey}=${json[jsonKey]}&` + } + } + url = url.slice(0, url.length - 1) + } + + httpRequest.request(url, { + method: http.RequestMethod.GET, + connectTimeout: 60000, + readTimeout: 60000, + header: header, + }) + .then((data: http.HttpResponse) => { + console.info('=====>' + 'Result:' + data.result as string); + console.info('=====>' + 'code:' + data.responseCode); + // console.info('=====>' + 'type:' + JSON.stringify(data.resultType)); + // console.info('=====>' + 'header:' + JSON.stringify(data.header)); + // console.info('=====>' + 'cookies:' + data.cookies); // 自API version 8开始支持cookie + // console.info('=====>' + 'header.content-Type:' + JSON.stringify(data.header)); + // console.info('=====>' + 'header.Status-Line:' + JSON.stringify(data.header)); + + if (this.httpHandlerList.hasKey(apiNo ?? url)) { + this.httpHandlerList.remove(apiNo ?? url) + } + if (data.responseCode === 200) { + resolve(JSON.parse(data.result as string) as T) + } else { + reject('服务异常') + } + }).catch((err: Error) => { + if (this.httpHandlerList.hasKey(apiNo ?? url)) { + this.httpHandlerList.remove(apiNo ?? url) + } + if (err.message === 'Failed writing received data to disk/application') { + reject('cancel') + } else + reject(err) + }); + }); + + } +} \ No newline at end of file diff --git a/src/main/ets/pages/XWebview.ets b/src/main/ets/pages/XWebview.ets new file mode 100644 index 0000000..499c248 --- /dev/null +++ b/src/main/ets/pages/XWebview.ets @@ -0,0 +1,233 @@ +import web_webview from '@ohos.web.webview'; +import { router } from '@kit.ArkUI'; +import { XDialogController } from '../dialog/XDialogController'; +import { XDialogList } from '../dialog/XDialogList'; +import { picker } from '@kit.CoreFileKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { ToolsHelper } from '../utils/ToolsHelper'; +import { XWebParams } from '../utils/XWebHelper'; + +@Entry({ routeName: 'XWebview' }) +@Preview +@Component +export struct XWebview { + // 手机号 + @State url: string = (router.getParams() as XWebParams).url + @State title?: string = (router.getParams() as XWebParams).title + @State errorInfo: string | null = null + @State progress: number = 0 + controller: web_webview.WebviewController = new web_webview.WebviewController(); + dialogController: XDialogController = {} as XDialogController + + aboutToAppear(): void { + } + + aboutToDisappear(): void { + } + + onBackPress(): boolean | void { + if (this.controller.accessBackward()) { + this.controller.backward() + } + return true + } + + build() { + Column() { + + Row() { + Row() { + Button({ buttonStyle: ButtonStyleMode.TEXTUAL }) { + Image($r('sys.media.ohos_ic_back')) + .width(26).height(26) + }.onClick(() => { + this.onBackPress() + }) + + Button({ buttonStyle: ButtonStyleMode.TEXTUAL }) { + Image($r('sys.media.ohos_ic_public_close')) + .width(26).height(26) + }.margin({ left: 12 }) + .onClick(() => { + router.back() + }) + }.width(65) + + Text(this.title) + .maxLines(1) + .fontColor('#222222') + .fontSize(18) + .textAlign(TextAlign.Center) + .width('50%') + .ellipsisMode(EllipsisMode.END) + .textOverflow({ + overflow: TextOverflow.Ellipsis + }) + + Button({ buttonStyle: ButtonStyleMode.TEXTUAL }) { + Image($r('sys.media.ohos_ic_public_more')) + .width(26).height(26) + }.width(65) + .onClick(() => { + if (this.dialogController != null) { + this.dialogController.open() + } + }) + } + .width('100%') + .height(45) + .justifyContent(FlexAlign.SpaceBetween) + .padding({ left: 15, right: 15 }) + + Divider().height(2).color(0xCCCCCC) + Progress({ value: this.progress, type: ProgressType.Linear }) + .visibility(this.progress > 95 || this.progress == 0 ? Visibility.None : Visibility.Visible) + .width('100%') + + Web({ src: this.url, controller: this.controller }) + .width('100%') + .height('100%') + .visibility(this.errorInfo == null ? Visibility.Visible : Visibility.None) + .mixedMode(MixedMode.All)//允许加载HTTP和HTTPS混合内容 + .zoomAccess(false)//不支持手势进行缩放 + .mediaPlayGestureAccess(false)//有声视频播放不需要用户手动点击 + .cacheMode(CacheMode.Default)//设置缓存模式 + .onConfirm((event) => { // 自定义Confirm弹窗 + if (event) { + console.log("event.url:" + event.url) + console.log("event.message:" + event.message) + AlertDialog.show({ + title: '提示', + message: event.message, + primaryButton: { + value: '取消', + action: () => { + event.result.handleCancel() + } + }, + secondaryButton: { + value: '确定', + action: () => { + event.result.handleConfirm() + } + }, + cancel: () => { + event.result.handleCancel() + } + }) + } + return true + }) + .onAlert((event) => { // 自定义Alert弹窗 + if (event) { + console.log("event.url:" + event.url) + console.log("event.message:" + event.message) + AlertDialog.show({ + title: '提示', + message: event.message, + secondaryButton: { + value: '确定', + action: () => { + event.result.handleConfirm() + } + } + }) + } + return true + }) + .onDownloadStart((event) => { // 下载文件 + if (event) { + console.log('url:' + event.url) + console.log('userAgent:' + event.userAgent) + console.log('contentDisposition:' + event.contentDisposition) + console.log('contentLength:' + event.contentLength) + console.log('mimetype:' + event.mimetype) + } + }) + .onErrorReceive((event) => { // 加载失败 + if (this.progress > 65) return + if (event) { + this.errorInfo = `错误码:${event.error.getErrorCode()}\n${event.error.getErrorInfo()}` + } else { + this.errorInfo = '错误码:-1\n未知错误' + } + }) + .onHttpErrorReceive((event) => { // 加载失败 + if (this.progress > 65) return + if (event) { + this.errorInfo = `错误码:${event.response.getResponseCode()}\n${event.response.getReasonMessage()}` + } else { + this.errorInfo = '错误码:-1\n未知错误' + } + }) + .onProgressChange((event) => { // 加载进度 + if (event) { + console.log('newProgress:' + event.newProgress) + this.progress = event.newProgress + } + }) + .onTitleReceive((event) => { + // 如果没有传输title,则从H5获取title赋值 + if (event && !this.title) { + this.title = event.title + } + }) + .onShowFileSelector((event) => { // 选择文件 + console.log('MyFileUploader onShowFileSelector invoked') + const documentSelectOptions = new picker.DocumentSelectOptions(); + let uri: string | null = null; + const documentViewPicker = new picker.DocumentViewPicker(); + documentViewPicker.select(documentSelectOptions).then((documentSelectResult) => { + uri = documentSelectResult[0]; + console.info('documentViewPicker.select to file succeed and uri is:' + uri); + if (event) { + event.result.handleFileList([uri]); + } + }).catch((err: BusinessError) => { + if (event) { + event.result.handleFileList([]) + } + ToolsHelper.showMessage(`Invoke documentViewPicker.select failed, code is ${err.code}, message is ${err.message}`) + console.error(`Invoke documentViewPicker.select failed, code is ${err.code}, message is ${err.message}`); + }) + return true + }) + Column() { + Text(this.errorInfo) + Button('点击重试') + .onClick(() => { + this.controller.refresh() + this.errorInfo = null + this.progress = 0 + }) + .margin({ top: 30 }) + } + .visibility(this.errorInfo == null ? Visibility.None : Visibility.Visible) + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + .padding({ bottom: 80 }) + + XDialogList({ + // 控制器 + controller: this.dialogController, + // 标题(可选) + title: '选择您的操作', + // 选择内容列表 + values: ['刷新', '浏览器打开', '分享', '复制地址'], + // 用户选择事件 + onSelected: (index: number, value: string) => { + ToolsHelper.showMessage(`用户选择了第${index}个,内容为:${value}`) + }, + // 用户取消事件 + onCancel: () => { + ToolsHelper.showMessage('用户取消操作') + }, + // 是否可取消(点击空白处,或者物理返回键) + autoCancel: true + }) + + }.width('100%').height('100%') + } +} + diff --git a/src/main/ets/utils/AppStorageHelper.ets b/src/main/ets/utils/AppStorageHelper.ets new file mode 100644 index 0000000..fe59065 --- /dev/null +++ b/src/main/ets/utils/AppStorageHelper.ets @@ -0,0 +1,22 @@ +/** + * 永久化存储,存储在本地文件 + */ +export class AppStorageHelper { + /** + * 缓存string + * @param key + * @param value + */ + public static save(key: string, value: string|undefined) { + PersistentStorage.persistProp(key, value) + } + + /** + * 获取已缓存的string + * @param key + * @returns + */ + public static get(key: string):string|undefined { + return AppStorage.get(key) + } +} \ No newline at end of file diff --git a/src/main/ets/utils/PreferencesHelper.ets b/src/main/ets/utils/PreferencesHelper.ets new file mode 100644 index 0000000..c39d5bd --- /dev/null +++ b/src/main/ets/utils/PreferencesHelper.ets @@ -0,0 +1,23 @@ +import preferences from '@ohos.data.preferences'; + +export class PreferencesHelper { + public static async put(key: string, value: preferences.ValueType|undefined) { + + const pref = await preferences.getPreferences(getContext(), 'PreferencesHelper') + // 写入数据 + await pref.put(key, value) + // 刷盘 + await pref.flush() + } + + public static async get(key: string): Promise { + return new Promise(async (resolve, reject) => { + + const pref = await preferences.getPreferences(getContext(), 'PreferencesHelper') + pref.get(key, undefined).then((r1: preferences.ValueType) => { + resolve(r1) + }) + }) + + } +} \ No newline at end of file diff --git a/src/main/ets/utils/ToolsHelper.ets b/src/main/ets/utils/ToolsHelper.ets new file mode 100644 index 0000000..de0463b --- /dev/null +++ b/src/main/ets/utils/ToolsHelper.ets @@ -0,0 +1,200 @@ +import promptAction from '@ohos.promptAction'; +import { Resource } from 'GlobalResource'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { HashMap } from '@kit.ArkTS'; + +export interface Btn { + text?: string | Resource; + color?: string | Resource; + onClick: () => void +} + +export interface AlertOptions { + title?: string + msg?: string + action: Btn +} + +export interface ConfirmOptions { + title?: string + msg?: string + confirm: Btn + cancel: Btn +} + +export interface ListOptions { + title?: string + cancel?: Btn + values: Array + onSelected: (index: number, value: T) => void + onError?: (msg: string) => void +} + +interface ListItem { + content: string +} + +@Builder +function customDialogBuilder(option: ListOptions, dialogId: number) { + + Column() { + Text(option.title) + .fontSize(13) + .textAlign(TextAlign.Center) + .width('60%') + .maxLines(2) + .ellipsisMode(EllipsisMode.END) + .textOverflow({ + overflow: TextOverflow.Ellipsis + }) + .visibility(option.title ? Visibility.Visible : Visibility.None) + List({ space: 20, initialIndex: 0 }) { + ForEach(option.values, (item: T, index: number) => { + ListItem() { + Text(typeof item === "string" ? item : (item as ListItem).content) + .width('100%') + .fontSize(16) + .textAlign(TextAlign.Center) + .onClick(() => { + if (ToolsHelper.mapDialog.get(dialogId)) { + promptAction.closeCustomDialog(ToolsHelper.mapDialog.get(dialogId)) + ToolsHelper.mapDialog.remove(dialogId) + } + option.onSelected(index, item) + }) + } + }, (item: string) => item) + } + .listDirection(Axis.Vertical) // 排列方向 + .scrollBar(BarState.Off) + .friction(0.6) + .divider({ strokeWidth: 1, color: 0xEEEEEE, startMargin: 20, endMargin: 20 }) // 每行之间的分界线 + .edgeEffect(EdgeEffect.Spring) // 边缘效果设置为Spring + .width('100%') + .height(option.values.length < 8 ? `${option.values.length / 16 * 100}%` : '50%') + .margin({ top: 20 }) + }.padding({ top: 20, bottom: 20, left: 20, right: 20 }) +} + +/** + * 常用方法 + */ +export class ToolsHelper { + /** + * 弹出Toast + * @param msg + */ + static showMessage(msg: string) { + console.info(msg); + promptAction.showToast({ + message: msg, + duration: 1500 + }); + } + + /**kio9 + * 弹出Alert弹窗 + * @param options + */ + static showAlertDialog(options: AlertOptions) { + try { + promptAction.showDialog({ + alignment: 1, + title: options.title, + message: options.msg, + buttons: [{ + text: options.action.text ?? "确定", + color: options.action.color ?? "#000000", + }] + }) + .then(() => { + options.action.onClick() + }) + .catch((err: Error) => { + ToolsHelper.showMessage(err.message) + }) + } catch (error) { + let message = (error as BusinessError).message + ToolsHelper.showMessage(message) + } + } + + /** + * 弹出Confirm弹窗 + * @param options + */ + static showConfirmDialog(options: ConfirmOptions) { + try { + promptAction.showDialog({ + alignment: 1, + title: options.title, + message: options.msg, + buttons: [{ + text: options.confirm.text ?? "确定", + color: options.confirm.color ?? "#000000", + }, { + text: options.cancel.text ?? "取消", + color: options.cancel.color ?? "#666666", + }] + }) + .then((data) => { + if (data.index === 0) { + options.confirm.onClick() + } else { + options.cancel.onClick() + } + }) + .catch((err: Error) => { + ToolsHelper.showMessage(err.message) + }) + } catch (error) { + let message = (error as BusinessError).message + ToolsHelper.showMessage(message) + } + } + + public static mapDialog = new HashMap() + + /** + * 弹出List弹窗 + * @param options values 如果是非string列表的话,需要存在content字段 + */ + static showListDialog(options: ListOptions, p: object) { + + let isSuccess: Array = [] + options.values.forEach((item, index) => { + if (typeof item !== 'string') { + if (!(item as ListItem).content) { + isSuccess.push(index) + } + } + }) + if (isSuccess.length > 0) { + options.onError && options.onError(`第(${isSuccess.join("、")})个数据中,没有content字段。`) + } else { + const dialogTag = new Date().getTime() + promptAction.openCustomDialog({ + alignment: 1, + builder: customDialogBuilder.bind(p, options, dialogTag) + }).then((dialogId: number) => { + ToolsHelper.mapDialog.set(dialogTag, dialogId) + }) + } + } + + /** + * 弹出自定义弹窗 + * @param alignment 弹窗在竖直方向上的对齐方式 + */ + static showCustomDialog(b: CustomBuilder, alignment?: DialogAlignment) { + const dialogTag = new Date().getTime() + promptAction.openCustomDialog({ + alignment: alignment ?? DialogAlignment.Center, + builder: b + }).then((dialogId: number) => { + ToolsHelper.mapDialog.set(dialogTag, dialogId) + }).catch((error: Error) => { + console.log('>>>>>', JSON.stringify(error)) + }) + } +} \ No newline at end of file diff --git a/src/main/ets/utils/ValidatorHelper.ets b/src/main/ets/utils/ValidatorHelper.ets new file mode 100644 index 0000000..d1e6f33 --- /dev/null +++ b/src/main/ets/utils/ValidatorHelper.ets @@ -0,0 +1,14 @@ +/** + * 常用正则验证 + */ +export class ValidatorHelper { + /** + * 是否为手机号 + * @param phone + * @returns + */ + public static isPhone(phone: string) { + let regexp: RegExp = new RegExp('^1[0-9]{10}$'); + return regexp.test(phone) + } +} \ No newline at end of file diff --git a/src/main/ets/utils/WindowHelper.ets b/src/main/ets/utils/WindowHelper.ets new file mode 100644 index 0000000..bd063e5 --- /dev/null +++ b/src/main/ets/utils/WindowHelper.ets @@ -0,0 +1,93 @@ +import { display, window } from '@kit.ArkUI'; +import { common } from '@kit.AbilityKit'; + + +export class WindowHelper { + /** + * 缓存窗体,关闭时需要 + * 同时只能出现一个窗口,所以只做一个缓存就可以 + */ + private static cacheWindow: window.Window | null = null; + + /** + * 根据参数创建窗口 + * @param options + * @returns + */ + static async open(options: WinOptions): Promise { + if (WindowHelper.cacheWindow) { + options.callBack && options.callBack(-1, '窗口已存在') + return + } + if (!options) { + options = new WinOptions(); + } + if (!options.name) { + options.name = 'window'; + } + if (options.windowType == undefined) { + options.windowType = window.WindowType.TYPE_DIALOG; + } + if (!options.bgColor) { + options.bgColor = '#33606266'; + } + try { + //创建窗口 + let windowClass = await window.createWindow({ + name: options.name, + windowType: options.windowType, + ctx: getContext() as common.UIAbilityContext + }); + //将窗口缓存 + WindowHelper.cacheWindow = windowClass; + await windowClass.setUIContent(options.router); + //获取屏幕四大角 + let d = display.getDefaultDisplaySync(); + //设置窗口大小 + await windowClass.resize(d.width, d.height); + // 设置窗口背景颜色 + windowClass.setWindowBackgroundColor(options.bgColor); + //显示窗口 + await windowClass.showWindow(); + } catch (exception) { + options.callBack && options.callBack(-1, '创建窗口失败,原因为:' + JSON.stringify(exception)) + } + } + + /** + * 关闭窗口 + * @returns + */ + static async close(): Promise { + if (WindowHelper.cacheWindow) { + await WindowHelper.cacheWindow.destroyWindow(); + WindowHelper.cacheWindow = null + } + } +} + +/** + * 窗口入参对象 + */ +class WinOptions { + /** + * 窗口名称 默认window + */ + name?: string; + /** + * 窗口类型 默认TYPE_DIALOG + */ + windowType?: window.WindowType; + /** + *窗口要显示的路由 如:pages/Welcome需要在main_pages.json中声明 + */ + router: string = ''; + /** + * 窗口背景颜色,默认#33606266 + */ + bgColor?: string; + /** + * 窗口创建回调函数 + */ + callBack?: (code: number, msg: string) => void; +} \ No newline at end of file diff --git a/src/main/ets/utils/XWebHelper.ets b/src/main/ets/utils/XWebHelper.ets new file mode 100644 index 0000000..b659fda --- /dev/null +++ b/src/main/ets/utils/XWebHelper.ets @@ -0,0 +1,31 @@ +import { router } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { ToolsHelper } from './ToolsHelper'; + +const XWebview = import('../pages/XWebview'); + + +export interface XWebParams { + url: string + title?: string +} + +export class XWebHelper { + /** + * 打开web页面,加载h5 + * @param params + */ + public static openWeb(params: XWebParams) { + + router.pushNamedRoute({ + name: 'XWebview', + params: params + }, router.RouterMode.Single).then(() => { + console.info('Succeeded in jumping to the XWebview page.') + + }).catch((err: BusinessError) => { + console.error(`Failed to jump to the second page.Code is ${err.code}, message is ${err.message}`) + ToolsHelper.showMessage(`Failed to jump to the second page.Code is ${err.code}, message is ${err.message}`) + }) + } +} \ No newline at end of file diff --git a/src/main/module.json5 b/src/main/module.json5 new file mode 100644 index 0000000..bbca727 --- /dev/null +++ b/src/main/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "basic", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ] + } +} diff --git a/src/main/resources/base/element/string.json b/src/main/resources/base/element/string.json new file mode 100644 index 0000000..222adc4 --- /dev/null +++ b/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + }, + { + "name": "permission_internet", + "value": "需要使用网络权限连接网络" + }, + { + "name": "permission_device", + "value": "需要获取设备唯一码" + } + ] +} diff --git a/src/main/resources/base/media/title_bar_back.png b/src/main/resources/base/media/title_bar_back.png new file mode 100644 index 0000000..073a845 Binary files /dev/null and b/src/main/resources/base/media/title_bar_back.png differ diff --git a/src/main/resources/en_US/element/string.json b/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000..222adc4 --- /dev/null +++ b/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + }, + { + "name": "permission_internet", + "value": "需要使用网络权限连接网络" + }, + { + "name": "permission_device", + "value": "需要获取设备唯一码" + } + ] +} diff --git a/src/main/resources/zh_CN/element/string.json b/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000..222adc4 --- /dev/null +++ b/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + }, + { + "name": "permission_internet", + "value": "需要使用网络权限连接网络" + }, + { + "name": "permission_device", + "value": "需要获取设备唯一码" + } + ] +} diff --git a/src/test/List.test.ets b/src/test/List.test.ets new file mode 100644 index 0000000..bb5b5c3 --- /dev/null +++ b/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/src/test/LocalUnit.test.ets b/src/test/LocalUnit.test.ets new file mode 100644 index 0000000..ed22d4d --- /dev/null +++ b/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest',() => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file