1.创建项目结构演示
2.统一接口请求结果
export class ApiResult {
code: number
msg ?: string;
data ?: any;
constructor() {
this.code = 0;
this.msg = undefined;
this.data = Object;
}
}
数据模型---tagbean
export class TagListBean {
list: Array<TagBean>
}
export class TagBean {
id: number= 0;
name: string= '';
icon: string= '';
}
3.网络封装
3.1HttpUtil
export class HttpUtil {
private static mInstance: HttpUtil;
// 防止实例化
private constructor() {
}
static getInstance(): HttpUtil {
if (!HttpUtil.mInstance) {
HttpUtil.mInstance = new HttpUtil();
}
return HttpUtil.mInstance;
}
request (option: RequestOptions): Promise<ApiResult> {
return httpCore.request(option);
}
/**
* 封装Post网络请求
* @param option
* @returns
*/
Post(option:RequestOptions){
if(option != null){
option.method = RequestMethod.POST
}
return this.request(option);
}
/**
* 封装Get网络请求
* @param option
* @returns
*/
Get(option:RequestOptions){
if(option != null){
option.method = RequestMethod.GET
}
return this.request(option);
}
}
export const httpUtil = HttpUtil.getInstance();
3.2Http请求器
export class HttpCore {
/**
* 发送请求
* @param requestOption
* @returns Promise
*/
request(requestOptions: RequestOptions): Promise<ApiResult> {
let p = new Promise<ApiResult>(async (resolve, reject) => {
// 每一个httpRequest对应一个HTTP请求任务,不可复用
let httpRequest = http.createHttp();
// let token : string = await getToken();
let promise = httpRequest.request(requestOptions.url, {
method: requestOptions.method,
connectTimeout: 60000,
readTimeout: 60000,
header:{
'Content-Type': 'application/json',
// 'token': token,
'client_type': 'HarmonyOS'
},
extraData: requestOptions.extraData
});
promise.then((data) => {
//TODO:此处补充数据拆包的操作
let resultObj = JSON.parse(data.result.toString());
//弹窗提示接口返回msg
setTimeout(() => {
promptAction.showToast({
message: resultObj.msg
})
}, 500);
resolve(resultObj);
//如果业务码为10000 则返回ApiReslut
// if (resultObj.code == 10000) {
// console.log(JSON.stringify(resultObj))
// }
// if (resultObj.code == 0){
// router.replaceUrl({
// url: "pages/MainPage/Login"
// }).then(() => {
// console.log('router successful')
// }).catch(err => {
// console.log('router err')
// })
// }
//如果返回数据包含token信息 则刷新token
if (resultObj.token != undefined) {
PreferenceUtil.writeString('token',resultObj.token)
}
}).catch((err) => {
//这里做全局异常统一处理 根据Http状态码做出处理
console.info('error:' + JSON.stringify(err));
reject(err);
});
httpRequest.destroy();
})
return p;
}
}
async function getToken(): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
const data = await PreferenceUtil.readString("token")
if (typeof data === 'string') {
resolve(data);
} else {
reject(new Error('Invalid token'));
}
} catch (err) {
reject(err);
}
});
}
export const httpCore = new HttpCore();
3.3网络请求配置
export interface RequestOptions {
/**
* Request url.
*/
url?: string;
/**
* Request method.
*/
method?: RequestMethod; // default is GET
/**
* Additional data of the request.
* extraData can be a string or an Object (API 6) or an ArrayBuffer(API 8).
*/
extraData?: string | Object | ArrayBuffer;
/**
* Request url queryParams .
*/
queryParams ?: Record<string, string>;
/**
* HTTP request header.
*/
header?: Object; // default is 'content-type': 'application/json'
}
export enum RequestMethod {
OPTIONS = "OPTIONS",
GET = "GET",
HEAD = "HEAD",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
TRACE = "TRACE",
CONNECT = "CONNECT"
}
3.4利用注解的方式封装Get post del等方法
import { ApiResult } from './ApiResult';
import { httpUtil } from './HttpUtil';
import { RequestOptions } from './RequestOptions';
/**
* 利用Map保存参数和值的映射关系 为避免参数名及方法名重复 采用组合Key的方法
*/
type FunParamMapKey = {
target: Object; //所在类
methodName: string; //所在方法
index: number; //参数名索引值
}
let funParamMap = new Map<string, string>();
// @Get注解 拿到url 从函数的@Param拿到参数名及参数值 利用HttpUtil进行网络请求
//Get 需要拼接URl
export function Get(url: string) {
return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
let URL: string = url;
let options: RequestOptions = {
url: URL,
queryParams: {}
};
descriptor.value = async function (...args: any[]) {
//对于方法中的每一个(methodName,arg)遍历加入到网络请求配置中
args.forEach((arg, index) => {
// 通过已知的信息构造组合Key对象
let obj: FunParamMapKey = { target: target, methodName: methodName, index: index };
// 通过组合Key(通过对内容的比较而不是值 节省内存)从内存map中获取@Param修饰的内容
let paramName = funParamMap[JSON.stringify(obj)];
// 将正确的参数名及值添加至网络请求参数中
if (typeof paramName !== 'undefined') {
if (typeof arg === 'string' || typeof arg === 'object' || arg instanceof ArrayBuffer || typeof arg === 'number') {
options.queryParams[paramName] = arg
} else {
console.log('参数类型不对')
throw new Error(`Invalid parameter type at index ${index}.`);
}
}
});
//拼接请求参数
const urlParams = Object.keys(options.queryParams).map(key => `${key}=${options.queryParams[key]}`).join('&')
console.log('urlParams:', urlParams)
if (URL.includes("?")) {
options.url = `${URL}${urlParams}`
} else {
options.url = `${URL}?${urlParams}`
}
const p = new Promise<ApiResult>((resolve, reject) => {
httpUtil.Get(options).then((response) => {
const result: ApiResult = response;
resolve(result);
}).catch((error) => {
reject(error);
});
});
return await p;
};
};
}
// @Post注解 拿到url 从函数的@Param拿到参数名及参数值 利用HttpUtil进行Post网络请求
export function Post(url: string) {
return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
let options: RequestOptions = {
url: url,
extraData: {}
};
descriptor.value = async function (...args: any[]) {
//对于方法中的每一个(methodName,arg)遍历加入到网络请求配置中
args.forEach((arg, index) => {
console.log("参数值",JSON.stringify(arg))
// 通过已知的信息构造组合Key对象
let obj: FunParamMapKey = { target: target, methodName: methodName, index: index };
// 通过组合Key(通过对内容的比较而不是值 节省内存)从内存map中获取@Param修饰的内容
let paramName = funParamMap[JSON.stringify(obj)];
console.log("参数名:",paramName)
// 将正确的参数名及值添加至网络请求参数中
if (typeof paramName !== 'undefined') {
if (typeof arg === 'string' || typeof arg === 'object' || arg instanceof ArrayBuffer) {
options.extraData[paramName] = arg;
} else {
throw new Error(`Invalid parameter type at index ${index}.`);
}
}else {
//如果获取不到形参名 及未被@Param标记 并且参数的类型是对象
if (typeof arg === 'object') {
options.extraData = JSON.stringify(arg)
}
}
});
console.log('extraData', JSON.stringify(options.extraData))
const p = new Promise<ApiResult>((resolve, reject) => {
httpUtil.Post(options).then((response) => {
const result: ApiResult = response;
resolve(result);
}).catch((error) => {
reject(error);
});
});
return await p;
};
};
}
/**
* @Param 注解将想要获取的Param添加至内存Map中
* @param paramName
* @returns
*/
export function Param(paramName: string) {
return function (target: any, methodName: string, parameterIndex: number) {
let obj: FunParamMapKey = { target: target, methodName: methodName, index: parameterIndex };
funParamMap[JSON.stringify(obj)] = paramName;
};
}
3.5统一接口请求api
import { AppConfig } from '../config/AppConfig';
import { Get, Param, Post } from '../http_other/AOPs';
import { ApiResult } from '../http_other/ApiResult';
export class LoginApi {
@Post("http://localhost:8080/login")
//自定义@Param 作用:获取形参 便于映射到extraData
login(@Param("username") username: string, @Param("password") password: string):Promise<ApiResult> {
return
}
@Get(AppConfig.APP_BASE_URL + 'v1/getTagList')
getMenuTree():Promise<ApiResult> {
return
}
@Get("http://localhost:8080/api/menu/getPermTree")
getPermsSelectTree():Promise<ApiResult> {
return
}
//@Get中的@Param 可以将形参拼接到URl中
@Get("http://localhost:8080/api/menu/get")
getMenuById(@Param("Id") id : number):Promise<ApiResult> {
return
}
/**
* 获取所有角色
* @returns
*/
@Get("http://localhost:8080/api/role/getAllRole")
getAllRole():Promise<ApiResult> {
return
}
//可以直接Post对象
// @Post("http://localhost:8080/api/role/updateRole")
// updateRole(role : RoleModel):Promise<ApiResult> {
// return
// }
}
export const loginApi = new LoginApi();
3.6统一的app全局配置信息config
export class AppConfig{
/**
*公共基础url
*/
static APP_BASE_URL="https://local/a.com/api/"
}
3.7数据回调封装
export interface DataCallback<T>{
onSuccess(result:T)
onFail(error:GsyNetException)
}
3.8数据异常
export class GsyNetException{
}
3.9
4.ViewModel的创建
import { PageState } from '../components/PageState'
import { TagBean } from '../model/TagListBean';
import { GsyNetException } from '../nethttp/GsyNetException';
import { dynamicListRepository } from '../repository/DynamicListRepository';
import { Logger } from '../utils/Logger';
import { DynamicListDataSource } from './DynamicListDataSource';
@Observed
export class TagListViewModel{
pageState: number = PageState.LOADING//加载状态
hasNext:boolean = false;//是否有下一页
page:number = 1//当前页
tagList: Array<TagBean> = new Array<TagBean>()//当前数据列表
tagListDataSource: DynamicListDataSource//page 页调用的数据模型
/**
* 请求数据
* @param page
*/
requestData(page:number){
var viewModelObj = this
dynamicListRepository.requestDynamicList({onSuccess(_result: Array<TagBean>){
if (_result.length>0) {
let uiModeList: TagBean[]= []
for(let index in _result){
let event:TagBean = _result[index]
uiModeList.push(event)
viewModelObj.tagList.push(event)
}
if(viewModelObj.tagListDataSource==null || page == 1){
viewModelObj.tagListDataSource = new DynamicListDataSource(uiModeList)
}else{
viewModelObj.tagListDataSource.pushData(uiModeList)
}
viewModelObj.pageState = PageState.HASDATA
if(viewModelObj.tagList.length==20){
viewModelObj.hasNext = false;
}else{
viewModelObj.hasNext = true;
}
}else if(viewModelObj.tagList.length == 0){
viewModelObj.pageState = PageState.EMPTY
Logger.info("数组是空的")
}else{
viewModelObj.hasNext = false;
}
},
onFail(_error: GsyNetException){
this.pageState = PageState.ERROR
}
},page)
}
}
5.数据观察者创建,用于数据模型的调用
DynamicListDataSource
import { TagBean } from '../model/TagListBean';
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [];
public totalCount(): number {
return 0;
}
public getData(index: number): TagBean | undefined {
return undefined;
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener);
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const position = this.listeners.indexOf(listener);
if (position >= 0) {
this.listeners.splice(position, 1);
}
}
notifyDataReload(): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataReloaded();
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataAdd(index);
})
}
notifyDataChange(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataChange(index);
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataDelete(index);
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataMove(from, to);
})
}
}
@Observed
export class DynamicListDataSource extends BasicDataSource {
listData:TagBean[]
constructor(events:TagBean[]) {
super()
this.listData = events
}
public totalCount(): number {
return this.listData.length;
}
public getData(index: number): TagBean {
return this.listData[index];
}
public pushData(data:TagBean[]){
for(var eventUiModel in data){
this.listData.push(data[eventUiModel])
}
this.notifyDataReload()
}
}
6.DynamicListRepository数据请求的处理
import { DataCallback } from '../nethttp/DataCallback';
import HttpManager, { RequestMethod } from '../nethttp/HttpManager';
import { GsyNetException } from '../nethttp/GsyNetException';
import { AppConfig } from '../config/AppConfig';
import { BaseBean } from '../model/BaseBean';
import { TagBean, TagListBean } from '../model/TagListBean';
import { Logger } from '../utils/Logger';
import { loginApi } from '../api_url/LoginApi';
import { ApiResult } from '../http_other/ApiResult';
class DynamicListRepository {
//接口数据类型
//result_res==={"code":10000,"msg":"success","data":{"count":20,"list":[{"id":1,"name":"综合用工","icon":"com/market/system/63e43215431fd53679b840298e952897_20240403172420.png"},{"id":2,"name":"泥瓦工匠","icon":"a6b69caae94c9cab4c705f271b334_20240403172459.png"]}}
requestDynamicList(callback: DataCallback<Array<TagBean>>, page: number) {
loginApi.getMenuTree().then((result:ApiResult )=> {
let res:ApiResult=result//数据承接
Logger.info("result_data===" + JSON.stringify(result.data))
Logger.info("result_res===" + JSON.stringify(res))
//获取实际列表数据
let listBean:Array<TagBean>=res.data['list'] as Array<TagBean>
Logger.info("listBean===" + JSON.stringify(listBean))
callback.onSuccess(listBean)//数据回调
}).catch(error=>{
Logger.info("error=====" + JSON.stringify(error))
})
}
}
}
export const dynamicListRepository = new DynamicListRepository();
7.在page页面使用
//声明viewmodel
@State viewModel: TagListViewModel = null
//实力化viewmodel并且请求数据
aboutToAppear() {
//todo 实力化TagListViewModel
this.viewModel = new TagListViewModel();
//todo 请求数据
this.viewModel.requestData(1)
}
//在build使用viewmodel的方法和数据
build() {
if (this.viewModel.pageState == PageState.LOADING) {
Text("正在加载中")
} else if (this.viewModel.pageState == PageState.HASDATA) {
Column() {
//下拉刷新布局
this.CustomPullRefreshLayout()
List({ space: 16, scroller: this.listScroller }) {
//数据量大使用LazyForEach
LazyForEach(this.viewModel.tagListDataSource, (item: TagBean) => {
ListItem() {
Column() {
Row() {
Image(item.icon)
.width('50vp')
.height('50vp')
Text(item.name)
}
.width('100%')
.justifyContent(FlexAlign.Start)
.alignItems(VerticalAlign.Center)
.height(60)
.borderRadius(20)
.borderWidth(1)
.borderColor($r('app.color.color_gray'))
.margin({ top: 10 })
}.justifyContent(FlexAlign.Start)
}
}, item => JSON.stringify(item))
}
.listDirection(Axis.Vertical)
.width('94%')
.edgeEffect(EdgeEffect.None) // 去掉回弹效果
.onTouch((event: TouchEvent) => {
this.listTouchEvent(event);
})
.onScrollIndex((start: number, end: number) => {
console.info('first' + start)
console.info('last' + end)
if (end == this.viewModel.tagListDataSource.totalCount() - 1 && start != 0) {
if (this.viewModel.hasNext) {
this.loadMore = true;
this.viewModel.page++
this.viewModel.requestData(this.viewModel.page)
} else {
this.loadMore = false;
}
}
})
if (this.loadMore) {
Text("加载更多")
} else {
Text("已经到底了")
}
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
} else if (this.viewModel.pageState == PageState.EMPTY) {
Column() {
Text("加载完整空数据")
Button("重新加载").onClick((event) => {
this.viewModel.requestData(1)
})
}
}
}
pagestate
export class PageState{
static LOADING:number = 0;//加载中
static HASDATA:number = 1; //有数据
static EMPTY:number = 2; //无数据
static ERROR:number = 3 //加载失败
}
结束语
Page<--------->TagListViewModel------>DynamicListRepository------>Http
本文只是简单的进行操作,还可进行业务逻辑的拆封,例如工具类,配置类,下拉刷新上拉加载更多等,技术能力强的同学可进行无私贡献。如有错误请留言指明。谢谢。