这个提交包含在:
徐勤民 2024-05-07 17:41:21 +08:00
当前提交 6ef4ef6d27
共有 30 个文件被更改,包括 1573 次插入0 次删除

6
.gitignore vendored 普通文件
查看文件

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

5
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
CHANGELOG.md 普通文件
查看文件

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

40
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
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
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
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
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
consumer-rules.txt 普通文件
查看文件

6
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
obfuscation-rules.txt 普通文件
查看文件

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

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

查看文件

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

查看文件

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

查看文件

@ -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 })
}
}

查看文件

@ -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)
});
});
}
}

查看文件

@ -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%')
}
}

查看文件

@ -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)
}
}

查看文件

@ -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)
})
})
}
}

查看文件

@ -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))
})
}
}

查看文件

@ -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)
}
}

查看文件

@ -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;
}

查看文件

@ -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
src/main/module.json5 普通文件
查看文件

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

查看文件

@ -0,0 +1,16 @@
{
"string": [
{
"name": "page_show",
"value": "page from package"
},
{
"name": "permission_internet",
"value": "需要使用网络权限连接网络"
},
{
"name": "permission_device",
"value": "需要获取设备唯一码"
}
]
}

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 922 B

查看文件

@ -0,0 +1,16 @@
{
"string": [
{
"name": "page_show",
"value": "page from package"
},
{
"name": "permission_internet",
"value": "需要使用网络权限连接网络"
},
{
"name": "permission_device",
"value": "需要获取设备唯一码"
}
]
}

查看文件

@ -0,0 +1,16 @@
{
"string": [
{
"name": "page_show",
"value": "page from package"
},
{
"name": "permission_internet",
"value": "需要使用网络权限连接网络"
},
{
"name": "permission_device",
"value": "需要获取设备唯一码"
}
]
}

5
src/test/List.test.ets 普通文件
查看文件

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

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