徐勤民 11 hónapja
commit
6ef4ef6d27

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test

+ 5 - 0
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;
+}

+ 9 - 0
CHANGELOG.md

@@ -0,0 +1,9 @@
+# [v1.0.0] 2024.04.23
+
+------
+
+> - 网络请求
+> - 正则验证
+> - 基础工具
+> - 统一弹窗
+> - 存储管理

+ 40 - 0
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'
+

+ 15 - 0
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
+

+ 229 - 0
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<number> | Array<string> | Array<boolean> | 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<HttpResult<T>>(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<T>) => {
+          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<HttpResult<T>>(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<T>) => {
+    if (res.status === '0') {
+      resolve(res.data as T)
+    } else {
+      reject(new Error(res.message))
+    }
+
+  })
+  .catch((error: Error) => {
+    reject(error)
+  })
+```
+
+
+
+# **** 常见问题
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 211 - 0
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%')
+  }
+}
+```
+
+
+

+ 44 - 0
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"
+    }
+  ]
+}

+ 0 - 0
consumer-rules.txt


+ 6 - 0
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. */
+}

+ 1 - 0
obfuscation-rules.txt

@@ -0,0 +1 @@
+-remove-log

+ 10 - 0
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": {}
+}

+ 9 - 0
src/main/ets/dialog/XDialogCommon.ets

@@ -0,0 +1,9 @@
+@CustomDialog
+export struct XDialogCommon {
+  controller: CustomDialogController
+  @BuilderParam dialogContent: () => void
+
+  build() {
+    this.dialogContent()
+  }
+}

+ 4 - 0
src/main/ets/dialog/XDialogController.ets

@@ -0,0 +1,4 @@
+export interface XDialogController {
+  open: () => void
+  close: () => void
+}

+ 82 - 0
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<string> | Array<object> = 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 })
+  }
+}

+ 189 - 0
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<string, http.HttpRequest>();
+  // 并发白名单,这个名单里面的api,重复请求不会取消
+  private concurrentList = new ArrayList<string>();
+
+  constructor() {
+    this.httpHandlerList = new HashMap<string, http.HttpRequest>();
+    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<T>(url: string, data: string | undefined, headers?: Object, apiNo?: string): Promise<T> {
+
+    return new Promise<T>((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<T>(url: string, data: string | undefined, headers?: Object, apiNo?: string): Promise<T> {
+
+    return new Promise<T>((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)
+      });
+    });
+
+  }
+}

+ 233 - 0
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%')
+  }
+}
+

+ 22 - 0
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<string>(key)
+  }
+}

+ 23 - 0
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<preferences.ValueType> {
+    return new Promise(async (resolve, reject) => {
+
+      const pref = await preferences.getPreferences(getContext(), 'PreferencesHelper')
+      pref.get(key, undefined).then((r1: preferences.ValueType) => {
+        resolve(r1)
+      })
+    })
+
+  }
+}

+ 200 - 0
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<T> {
+  title?: string
+  cancel?: Btn
+  values: Array<T>
+  onSelected: (index: number, value: T) => void
+  onError?: (msg: string) => void
+}
+
+interface ListItem {
+  content: string
+}
+
+@Builder
+function customDialogBuilder<T>(option: ListOptions<T>, 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<number, number>()
+
+  /**
+   * 弹出List弹窗
+   * @param options values 如果是非string列表的话,需要存在content字段
+   */
+  static showListDialog<T = string>(options: ListOptions<T>, p: object) {
+
+    let isSuccess: Array<number> = []
+    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))
+    })
+  }
+}

+ 14 - 0
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)
+  }
+}

+ 93 - 0
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<void> {
+    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<void> {
+    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;
+}

+ 31 - 0
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}`)
+    })
+  }
+}

+ 11 - 0
src/main/module.json5

@@ -0,0 +1,11 @@
+{
+  "module": {
+    "name": "basic",
+    "type": "har",
+    "deviceTypes": [
+      "default",
+      "tablet",
+      "2in1"
+    ]
+  }
+}

+ 16 - 0
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": "需要获取设备唯一码"
+    }
+  ]
+}

BIN
src/main/resources/base/media/title_bar_back.png


+ 16 - 0
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": "需要获取设备唯一码"
+    }
+  ]
+}

+ 16 - 0
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": "需要获取设备唯一码"
+    }
+  ]
+}

+ 5 - 0
src/test/List.test.ets

@@ -0,0 +1,5 @@
+import localUnitTest from './LocalUnit.test';
+
+export default function testsuite() {
+  localUnitTest();
+}

+ 33 - 0
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);
+    });
+  });
+}