zhangbo1_wd

添加无数据展示;新增下拉刷新、上拉加载更多功能,loading样式后面再调。

Showing 21 changed files with 589 additions and 50 deletions
@@ -46,7 +46,7 @@ export struct EmptyComponent { @@ -46,7 +46,7 @@ export struct EmptyComponent {
46 46
47 Text(this.buildNoDataTip()) 47 Text(this.buildNoDataTip())
48 .fontSize($r('app.float.normal_text_size')) 48 .fontSize($r('app.float.normal_text_size'))
49 - .fontColor('#B2B2B2') 49 + .fontColor('#000000')
50 .fontWeight(FontWeight.Normal) 50 .fontWeight(FontWeight.Normal)
51 .opacity(this.TEXT_OPACITY) 51 .opacity(this.TEXT_OPACITY)
52 .margin({ top: this.EMPTY_TIP_TEXT_MARGIN_TOP }) 52 .margin({ top: this.EMPTY_TIP_TEXT_MARGIN_TOP })
@@ -5,44 +5,99 @@ import PageViewModel from '../viewmodel/PageViewModel'; @@ -5,44 +5,99 @@ import PageViewModel from '../viewmodel/PageViewModel';
5 import { EmptyComponent } from './EmptyComponent'; 5 import { EmptyComponent } from './EmptyComponent';
6 import { ErrorComponent } from './ErrorComponent'; 6 import { ErrorComponent } from './ErrorComponent';
7 import { LabelComponent } from './LabelComponent'; 7 import { LabelComponent } from './LabelComponent';
8 -import { LoadingComponent } from './LoadingComponent';  
9 import { TitleAbbrComponent } from './TitleAbbrComponent'; 8 import { TitleAbbrComponent } from './TitleAbbrComponent';
10 import { TitleAllComponent } from './TitleAllComponent'; 9 import { TitleAllComponent } from './TitleAllComponent';
11 -import {BannerComponent} from './BannerComponent' 10 +import { BannerComponent } from './BannerComponent'
  11 +import PageModel from '../viewmodel/PageModel';
  12 +import { listTouchEvent } from '../utils/PullDownRefresh';
  13 +import RefreshLayout from './page/RefreshLayout';
  14 +import { RefreshLayoutBean } from './page/RefreshLayoutBean';
  15 +import NoMoreLayout from './page/NoMoreLayout';
  16 +import LoadMoreLayout from './page/LoadMoreLayout';
  17 +import CustomRefreshLoadLayout from './page/CustomRefreshLoadLayout';
12 18
13 const TAG = 'PageComponent'; 19 const TAG = 'PageComponent';
14 20
15 @Component 21 @Component
16 export struct PageComponent { 22 export struct PageComponent {
17 - @State viewType: number = ViewType.LOADED; 23 + // @State viewType: number = ViewType.LOADING;
18 // Group数据及子组件数据 24 // Group数据及子组件数据
19 - @State compList: LazyDataSource<CompDTO> = new LazyDataSource();  
20 - @State currentTopNavSelectedIndex: number = 0;  
21 - @State pageId: string = "";  
22 - @State channelId: string = ""; 25 + // @State compList: LazyDataSource<CompDTO> = new LazyDataSource();
  26 + @State private pageModel: PageModel = new PageModel();
  27 + currentTopNavSelectedIndex: number = 0;
  28 + pageId: string = "";
  29 + channelId: string = "";
23 30
24 // @Link @Watch('onChange') paIndex:number 31 // @Link @Watch('onChange') paIndex:number
25 32
26 build() { 33 build() {
27 - if (this.viewType == ViewType.LOADING) {  
28 - LoadingComponent()  
29 - } else if (this.viewType == ViewType.ERROR) { 34 + Column() {
  35 + if (this.pageModel.viewType == ViewType.LOADING) {
  36 + // LoadingComponent()
  37 + this.LoadingLayout()
  38 + } else if (this.pageModel.viewType == ViewType.ERROR) {
30 ErrorComponent() 39 ErrorComponent()
31 - } else if (this.viewType == ViewType.EMPTY) { 40 + } else if (this.pageModel.viewType == ViewType.EMPTY) {
32 EmptyComponent() 41 EmptyComponent()
33 } else { 42 } else {
  43 + this.ListLayout()
  44 + }
  45 + }
  46 + .width(CommonConstants.FULL_PARENT)
  47 + .height(CommonConstants.FULL_PARENT)
  48 + .onTouch((event: TouchEvent | undefined) => {
  49 + if (event) {
  50 + if (this.pageModel.viewType === ViewType.LOADED) {
  51 + listTouchEvent(this.pageModel, event);
  52 + }
  53 + }
  54 + })
  55 +
  56 + }
  57 +
  58 + @Builder ListLayout() {
34 List() { 59 List() {
35 - LazyForEach(this.compList, (compDTO: CompDTO, compIndex: number) => { 60 + // 下拉刷新
  61 + ListItem() {
  62 + RefreshLayout({
  63 + refreshBean: new RefreshLayoutBean(this.pageModel.isVisiblePullDown, this.pageModel.pullDownRefreshImage,
  64 + this.pageModel.pullDownRefreshText, this.pageModel.pullDownRefreshHeight)
  65 + })
  66 + }
  67 +
  68 + LazyForEach(this.pageModel.compList, (compDTO: CompDTO, compIndex: number) => {
36 ListItem() { 69 ListItem() {
37 Column() { 70 Column() {
38 this.componentBuilder(compDTO, compIndex) 71 this.componentBuilder(compDTO, compIndex)
39 } 72 }
40 } 73 }
41 }) 74 })
  75 +
  76 + // 加载更多
  77 + ListItem() {
  78 + if (this.pageModel.hasMore) {
  79 + LoadMoreLayout({
  80 + refreshBean: new RefreshLayoutBean(this.pageModel.isVisiblePullUpLoad, this.pageModel.pullUpLoadImage,
  81 + this.pageModel.pullUpLoadText, this.pageModel.pullUpLoadHeight)
  82 + })
  83 + } else {
  84 + NoMoreLayout()
  85 + }
  86 + }
42 } 87 }
43 .cachedCount(5) 88 .cachedCount(5)
44 .height(CommonConstants.FULL_PARENT) 89 .height(CommonConstants.FULL_PARENT)
  90 + .onScrollIndex((start: number, end: number) => {
  91 + // Listen to the first index of the current list.
  92 + this.pageModel.startIndex = start;
  93 + // 包含了 头尾item,判断时需要考虑+2
  94 + this.pageModel.endIndex = end;
  95 + })
45 } 96 }
  97 +
  98 + @Builder LoadingLayout() {
  99 + CustomRefreshLoadLayout({ refreshBean: new RefreshLayoutBean(true,
  100 + $r('app.media.ic_pull_up_load'), $r('app.string.pull_up_load_text'), this.pageModel.pullDownRefreshHeight) })
46 } 101 }
47 102
48 @Builder 103 @Builder
@@ -53,7 +108,7 @@ export struct PageComponent { @@ -53,7 +108,7 @@ export struct PageComponent {
53 TitleAbbrComponent({ compDTO: compDTO }) 108 TitleAbbrComponent({ compDTO: compDTO })
54 } else if (compDTO.compStyle === CompStyle.Title_All_01) { 109 } else if (compDTO.compStyle === CompStyle.Title_All_01) {
55 TitleAllComponent({ compDTO: compDTO }) 110 TitleAllComponent({ compDTO: compDTO })
56 - } else if(compDTO.compStyle === CompStyle.Carousel_Layout_01) { 111 + } else if (compDTO.compStyle === CompStyle.Carousel_Layout_01) {
57 BannerComponent({ compDTO: compDTO }) 112 BannerComponent({ compDTO: compDTO })
58 } else { 113 } else {
59 // todo:组件未实现 / Component Not Implemented 114 // todo:组件未实现 / Component Not Implemented
@@ -65,27 +120,25 @@ export struct PageComponent { @@ -65,27 +120,25 @@ export struct PageComponent {
65 } 120 }
66 121
67 async aboutToAppear() { 122 async aboutToAppear() {
68 - Logger.info(TAG, `aboutToAppear, this.pageId: ${this.viewType} this.currentTopNavSelectedIndex: ${this.currentTopNavSelectedIndex}`);  
69 - // if (this.currentTopNavSelectedIndex === 1) { // 顶导tab的第0个item是【热点】,第1个item是【推荐】  
70 - // this.compList.replaceAll()  
71 - // let pageDto = await PageViewModel.getPageData2(getContext(this))  
72 - // if (pageDto && pageDto.compList) {  
73 - // this.compList.push(...pageDto.compList)  
74 - // }  
75 - // } else {  
76 - // let pageDto = await PageViewModel.getPageData1(getContext(this))  
77 - // if (pageDto && pageDto.compList) {  
78 - // this.compList.push(...pageDto.compList)  
79 - // }  
80 - // }  
81 -  
82 - // if (this.currentTopNavSelectedIndex != 1) { // 顶导tab的第0个item是【热点】,第1个item是【推荐】  
83 - // return  
84 - // }  
85 - Logger.debug(TAG,'lllllalalal: ' + this.pageId+' , ' + this.channelId);  
86 - let pageDto = await PageViewModel.getPageData(this.pageId, this.pageId, this.channelId, getContext(this))  
87 - if (pageDto && pageDto.compList) {  
88 - this.compList.push(...pageDto.compList) 123 + Logger.info(TAG, `aboutToAppear id: ${this.pageId} , ${this.channelId} , navIndex: ${this.currentTopNavSelectedIndex}`);
  124 + this.pageModel.pageId = this.pageId;
  125 + this.pageModel.groupId = this.pageId;
  126 + this.pageModel.channelId = this.channelId;
  127 + this.pageModel.currentPage = 1;
  128 + let pageDto = await PageViewModel.getPageData(this.pageModel.pageId, this.pageModel.pageId, this.pageModel.channelId
  129 + , this.pageModel.currentPage, this.pageModel.pageSize, getContext(this))
  130 + if (pageDto && pageDto.compList && pageDto.compList.length > 0) {
  131 + this.pageModel.viewType = ViewType.LOADED;
  132 + this.pageModel.compList.push(...pageDto.compList)
  133 + if (pageDto.compList.length === this.pageModel.pageSize) {
  134 + this.pageModel.currentPage++;
  135 + this.pageModel.hasMore = true;
  136 + } else {
  137 + this.pageModel.hasMore = false;
  138 + }
  139 + } else {
  140 + Logger.debug(TAG, 'aboutToAppear, data response page ' + this.pageId + ', comp list is empty.');
  141 + this.pageModel.viewType = ViewType.EMPTY;
89 } 142 }
90 } 143 }
91 } 144 }
  1 +import { RefreshConstants } from '../../utils/RefreshConstants';
  2 +import { RefreshLayoutBean } from './RefreshLayoutBean';
  3 +
  4 +/**
  5 + * Custom layout to show refresh or load.
  6 + */
  7 +@Component
  8 +export default struct CustomLayout {
  9 + @ObjectLink refreshBean: RefreshLayoutBean;
  10 +
  11 + build() {
  12 + Row() {
  13 + Image(this.refreshBean.imageSrc)
  14 + .width(RefreshConstants.RefreshLayout_IMAGE_WIDTH)
  15 + .height(RefreshConstants.RefreshLayout_IMAGE_HEIGHT)
  16 +
  17 + Text(this.refreshBean.textValue)
  18 + .margin({
  19 + left: RefreshConstants.RefreshLayout_TEXT_MARGIN_LEFT,
  20 + bottom: RefreshConstants.RefreshLayout_TEXT_MARGIN_BOTTOM
  21 + })
  22 + .fontSize(RefreshConstants.RefreshLayout_TEXT_FONT_SIZE)
  23 + .textAlign(TextAlign.Center)
  24 + }
  25 + .clip(true)
  26 + .width(RefreshConstants.FULL_WIDTH)
  27 + .justifyContent(FlexAlign.Center)
  28 + .height(this.refreshBean.heightValue)
  29 + }
  30 +}
  1 +import CustomRefreshLoadLayout from './CustomRefreshLoadLayout';
  2 +import { RefreshLayoutBean } from './RefreshLayoutBean';
  3 +
  4 +/**
  5 + * The load more layout component.
  6 + */
  7 +@Component
  8 +export default struct LoadMoreLayout {
  9 + @ObjectLink refreshBean: RefreshLayoutBean;
  10 +
  11 + build() {
  12 + Column() {
  13 + if (this.refreshBean.isVisible) {
  14 + CustomRefreshLoadLayout({
  15 + refreshBean: new RefreshLayoutBean(this.refreshBean.isVisible,
  16 + this.refreshBean.imageSrc, this.refreshBean.textValue, this.refreshBean.heightValue)
  17 + })
  18 + } else {
  19 + CustomRefreshLoadLayout({
  20 + refreshBean: new RefreshLayoutBean(this.refreshBean.isVisible,
  21 + this.refreshBean.imageSrc, this.refreshBean.textValue, 0)
  22 + })
  23 + }
  24 + }
  25 + }
  26 +}
  1 +import { RefreshConstants } from '../../utils/RefreshConstants'
  2 +
  3 +/**
  4 + * The No more data layout component.
  5 + */
  6 +@Component
  7 +export default struct NoMoreLayout {
  8 + build() {
  9 + Row() {
  10 + Text($r('app.string.footer_text'))
  11 + .margin({ left: RefreshConstants.NoMoreLayoutConstant_NORMAL_PADDING })
  12 + .fontSize(RefreshConstants.NoMoreLayoutConstant_TITLE_FONT)
  13 + .textAlign(TextAlign.Center)
  14 + }
  15 + .width(RefreshConstants.FULL_WIDTH)
  16 + .justifyContent(FlexAlign.Center)
  17 + .height(RefreshConstants.CUSTOM_LAYOUT_HEIGHT)
  18 + }
  19 +}
  1 +import CustomRefreshLoadLayout from './CustomRefreshLoadLayout';
  2 +import { RefreshLayoutBean } from './RefreshLayoutBean';
  3 +
  4 +/**
  5 + * The refresh layout component.
  6 + */
  7 +@Component
  8 +export default struct RefreshLayout {
  9 + @ObjectLink refreshBean: RefreshLayoutBean;
  10 +
  11 + build() {
  12 + Column() {
  13 + if (this.refreshBean.isVisible) {
  14 + CustomRefreshLoadLayout({ refreshBean: new RefreshLayoutBean
  15 + (this.refreshBean.isVisible, this.refreshBean.imageSrc, this.refreshBean.textValue,
  16 + this.refreshBean.heightValue) })
  17 + }
  18 + }
  19 + }
  20 +}
  1 +/**
  2 + * Custom refresh load layout data.
  3 + */
  4 +@Observed
  5 +export class RefreshLayoutBean {
  6 + /**
  7 + * Custom refresh load layout isVisible.
  8 + */
  9 + isVisible: boolean;
  10 +
  11 + /**
  12 + * Custom refresh load layout imageSrc.
  13 + */
  14 + imageSrc: Resource;
  15 +
  16 + /**
  17 + * Custom refresh load layout textValue.
  18 + */
  19 + textValue: Resource;
  20 +
  21 + /**
  22 + * Custom refresh load layout heightValue.
  23 + */
  24 + heightValue: number;
  25 +
  26 + constructor(isVisible: boolean, imageSrc: Resource, textValue: Resource, heightValue: number) {
  27 + this.isVisible = isVisible;
  28 + this.imageSrc = imageSrc;
  29 + this.textValue = textValue;
  30 + this.heightValue = heightValue;
  31 + }
  32 +}
@@ -46,7 +46,12 @@ export class HttpUrlUtils { @@ -46,7 +46,12 @@ export class HttpUrlUtils {
46 // TODO 判断是否登录 46 // TODO 判断是否登录
47 headers.set('userId', this.getUserId()) 47 headers.set('userId', this.getUserId())
48 headers.set('userType', this.getUserType()) 48 headers.set('userType', this.getUserType())
49 - Logger.debug("TAG", 'getCommonHeaders headers: ' + headers); 49 +
  50 + // Logger.debug("TAG", '******************* commonHeaders headers start ******************************** ');
  51 + // headers.forEach((v,k)=>{
  52 + // Logger.debug("TAG", 'getCommonHeaders header: ' + k + ': ' + v);
  53 + // })
  54 + // Logger.debug("TAG", '******************* commonHeaders headers end ******************************** ');
50 return headers; 55 return headers;
51 } 56 }
52 57
@@ -55,18 +60,21 @@ export class HttpUrlUtils { @@ -55,18 +60,21 @@ export class HttpUrlUtils {
55 return this.HOST + this.BOTTOM_NAV_PATH; 60 return this.HOST + this.BOTTOM_NAV_PATH;
56 } 61 }
57 62
58 - static getCompInfoUrl(pageId: string, groupId: string, channelId: string) { 63 + static getCompInfoUrl(pageId: string, groupId: string, channelId: string, currentPage: number
  64 + , pageSize: number) {
59 let url = this.HOST + this.COMP_PATH; 65 let url = this.HOST + this.COMP_PATH;
60 - // TODO 暂定只请求第一页,后续对接分页加载,参数再调整  
61 - url = url + "?channelStrategy=2&pageSize=20&pageNum=1&loadStrategy=first_load" 66 + // TODO 暂定只请求第一页,后续对接分页加载,参数再调整 first_load?
  67 + url = url + "?channelStrategy=2&loadStrategy=first_load"
62 + "&districtCode=" + this.getDistrictCode() 68 + "&districtCode=" + this.getDistrictCode()
63 + "&provinceCode=" + this.getProvinceCode() 69 + "&provinceCode=" + this.getProvinceCode()
64 + "&cityCode=" + this.getCityCode() 70 + "&cityCode=" + this.getCityCode()
65 - // + "&refreshTime=" + DateTimeUtils.getCurrentTimeMillis()  
66 - + "&refreshTime=" + "1703472405653" 71 + + "&refreshTime=" + DateTimeUtils.getCurrentTimeMillis()
67 + "&pageId=" + pageId 72 + "&pageId=" + pageId
68 + "&groupId=" + groupId 73 + "&groupId=" + groupId
69 - + "&channelId=" + channelId; 74 + + "&channelId=" + channelId
  75 + + "&pageSize=" + pageSize
  76 + + "&pageNum=" + currentPage;
  77 + // Logger.debug("TAG", 'getCompInfoUrl url: '+url);
70 return url; 78 return url;
71 } 79 }
72 80
@@ -87,6 +95,7 @@ export class HttpUrlUtils { @@ -87,6 +95,7 @@ export class HttpUrlUtils {
87 95
88 private static getTimestamp() { 96 private static getTimestamp() {
89 // return DateTimeUtils.getCurrentTime() + ''; 97 // return DateTimeUtils.getCurrentTime() + '';
  98 + // TODO 暂时写死,有些page 真实时间戳 返回数据为空
90 return '155203523'; 99 return '155203523';
91 } 100 }
92 101
@@ -11,8 +11,9 @@ export class PageRepository { @@ -11,8 +11,9 @@ export class PageRepository {
11 return WDHttp.get<ResponseDTO<NavigationBodyDTO>>(url, headers) 11 return WDHttp.get<ResponseDTO<NavigationBodyDTO>>(url, headers)
12 }; 12 };
13 13
14 - static fetchPageData(pageId: string, groupId: string, channelId: string) {  
15 - let url = HttpUrlUtils.getCompInfoUrl(pageId, groupId, channelId) 14 + static fetchPageData(pageId: string, groupId: string, channelId: string, currentPage: number
  15 + , pageSize: number) {
  16 + let url = HttpUrlUtils.getCompInfoUrl(pageId, groupId, channelId, currentPage, pageSize)
16 let headers: HashMap<string, string> = HttpUrlUtils.getCommonHeaders(); 17 let headers: HashMap<string, string> = HttpUrlUtils.getCommonHeaders();
17 return WDHttp.get<ResponseDTO<PageDTO>>(url, headers) 18 return WDHttp.get<ResponseDTO<PageDTO>>(url, headers)
18 }; 19 };
  1 +import promptAction from '@ohos.promptAction';
  2 +import PageModel from '../viewmodel/PageModel';
  3 +import { RefreshConstants as Const, RefreshState } from './RefreshConstants';
  4 +import PageViewModel from '../viewmodel/PageViewModel';
  5 +import { PageDTO } from '../repository/bean/PageDTO';
  6 +import { touchMoveLoadMore, touchUpLoadMore } from './PullUpLoadMore';
  7 +
  8 +export function listTouchEvent(pageModel: PageModel, event: TouchEvent) {
  9 + switch (event.type) {
  10 + case TouchType.Down:
  11 + pageModel.downY = event.touches[0].y;
  12 + pageModel.lastMoveY = event.touches[0].y;
  13 + break;
  14 + case TouchType.Move:
  15 + if ((pageModel.isRefreshing === true) || (pageModel.isLoading === true)) {
  16 + return;
  17 + }
  18 + let isDownPull = event.touches[0].y - pageModel.lastMoveY > 0;
  19 + if (((isDownPull === true) || (pageModel.isPullRefreshOperation === true)) && (pageModel.isCanLoadMore === false)) {
  20 + // Finger movement, processing pull-down refresh.
  21 + touchMovePullRefresh(pageModel, event);
  22 + } else {
  23 + // Finger movement, processing load more.
  24 + touchMoveLoadMore(pageModel, event);
  25 + }
  26 + pageModel.lastMoveY = event.touches[0].y;
  27 + break;
  28 + case TouchType.Cancel:
  29 + break;
  30 + case TouchType.Up:
  31 + if ((pageModel.isRefreshing === true) || (pageModel.isLoading === true)) {
  32 + return;
  33 + }
  34 + if ((pageModel.isPullRefreshOperation === true)) {
  35 + // Lift your finger and pull down to refresh.
  36 + touchUpPullRefresh(pageModel);
  37 + } else {
  38 + // Fingers up, handle loading more.
  39 + touchUpLoadMore(pageModel);
  40 + }
  41 + break;
  42 + default:
  43 + break;
  44 + }
  45 +}
  46 +
  47 +export function touchMovePullRefresh(pageModel: PageModel, event: TouchEvent) {
  48 + if (pageModel.startIndex === 0) {
  49 + pageModel.isPullRefreshOperation = true;
  50 + let height = vp2px(pageModel.pullDownRefreshHeight);
  51 + pageModel.offsetY = event.touches[0].y - pageModel.downY;
  52 + // The sliding offset is greater than the pull-down refresh layout height, and the refresh condition is met.
  53 + if (pageModel.offsetY >= height) {
  54 + pullRefreshState(pageModel, RefreshState.Release);
  55 + pageModel.offsetY = height + pageModel.offsetY * Const.Y_OFF_SET_COEFFICIENT;
  56 + } else {
  57 + pullRefreshState(pageModel, RefreshState.DropDown);
  58 + }
  59 + if (pageModel.offsetY < 0) {
  60 + pageModel.offsetY = 0;
  61 + pageModel.isPullRefreshOperation = false;
  62 + }
  63 + }
  64 +}
  65 +
  66 +export function touchUpPullRefresh(pageModel: PageModel) {
  67 + if (pageModel.isCanRefresh === true) {
  68 + pageModel.offsetY = vp2px(pageModel.pullDownRefreshHeight);
  69 + pullRefreshState(pageModel, RefreshState.Refreshing);
  70 + pageModel.currentPage = 1;
  71 + setTimeout(() => {
  72 + let self: PageModel = pageModel;
  73 +
  74 + PageViewModel.getPageData(self.pageId, self.groupId, self.channelId, self.currentPage, self.pageSize)
  75 + .then((data: PageDTO) => {
  76 + if (data == null || data.compList == null || data.compList.length == 0) {
  77 + self.hasMore = false;
  78 + } else {
  79 + if (data.compList.length == self.pageSize) {
  80 + self.currentPage++;
  81 + self.hasMore = true;
  82 + } else {
  83 + self.hasMore = false;
  84 + }
  85 + // 刷新,替换所有数据
  86 + self.compList.replaceAll(...data.compList)
  87 + }
  88 + closeRefresh(self, true);
  89 + }).catch((err: string | Resource) => {
  90 + promptAction.showToast({ message: err });
  91 + closeRefresh(self, false);
  92 + });
  93 + }, Const.DELAY_TIME);
  94 + } else {
  95 + closeRefresh(pageModel, false);
  96 + }
  97 +}
  98 +
  99 +export function pullRefreshState(pageModel: PageModel, state: number) {
  100 + switch (state) {
  101 + case RefreshState.DropDown:
  102 + pageModel.pullDownRefreshText = $r('app.string.pull_down_refresh_text');
  103 + pageModel.pullDownRefreshImage = $r('app.media.ic_pull_down_refresh');
  104 + pageModel.isCanRefresh = false;
  105 + pageModel.isRefreshing = false;
  106 + pageModel.isVisiblePullDown = true;
  107 + break;
  108 + case RefreshState.Release:
  109 + pageModel.pullDownRefreshText = $r('app.string.release_refresh_text');
  110 + pageModel.pullDownRefreshImage = $r('app.media.ic_pull_up_refresh');
  111 + pageModel.isCanRefresh = true;
  112 + pageModel.isRefreshing = false;
  113 + break;
  114 + case RefreshState.Refreshing:
  115 + pageModel.offsetY = vp2px(pageModel.pullDownRefreshHeight);
  116 + pageModel.pullDownRefreshText = $r('app.string.refreshing_text');
  117 + pageModel.pullDownRefreshImage = $r('app.media.ic_pull_up_load');
  118 + pageModel.isCanRefresh = true;
  119 + pageModel.isRefreshing = true;
  120 + break;
  121 + case RefreshState.Success:
  122 + pageModel.pullDownRefreshText = $r('app.string.refresh_success_text');
  123 + pageModel.pullDownRefreshImage = $r('app.media.ic_succeed_refresh');
  124 + pageModel.isCanRefresh = true;
  125 + pageModel.isRefreshing = true;
  126 + break;
  127 + case RefreshState.Fail:
  128 + pageModel.pullDownRefreshText = $r('app.string.refresh_fail_text');
  129 + pageModel.pullDownRefreshImage = $r('app.media.ic_fail_refresh');
  130 + pageModel.isCanRefresh = true;
  131 + pageModel.isRefreshing = true;
  132 + break;
  133 + default:
  134 + break;
  135 + }
  136 +}
  137 +
  138 +export function closeRefresh(pageModel: PageModel, isRefreshSuccess: boolean) {
  139 + let self = pageModel;
  140 + setTimeout(() => {
  141 + let delay = Const.RefreshConstant_DELAY_PULL_DOWN_REFRESH;
  142 + if (self.isCanRefresh === true) {
  143 + pullRefreshState(pageModel, isRefreshSuccess ? RefreshState.Success : RefreshState.Fail);
  144 + delay = Const.RefreshConstant_DELAY_SHRINK_ANIMATION_TIME;
  145 + }
  146 + animateTo({
  147 + duration: Const.RefreshConstant_CLOSE_PULL_DOWN_REFRESH_TIME,
  148 + delay: delay,
  149 + onFinish: () => {
  150 + pullRefreshState(pageModel, RefreshState.DropDown);
  151 + self.isVisiblePullDown = false;
  152 + self.isPullRefreshOperation = false;
  153 + }
  154 + }, () => {
  155 + self.offsetY = 0;
  156 + })
  157 + }, self.isCanRefresh ? Const.DELAY_ANIMATION_DURATION : 0);
  158 +}
  1 +import promptAction from '@ohos.promptAction';
  2 +import PageModel from '../viewmodel/PageModel';
  3 +import { RefreshConstants as Const } from './RefreshConstants';
  4 +import PageViewModel from '../viewmodel/PageViewModel';
  5 +import { PageDTO } from '../repository/bean/PageDTO';
  6 +
  7 +export function touchMoveLoadMore(model: PageModel, event: TouchEvent) {
  8 + // list size +1
  9 + if (model.endIndex === model.compList.size() || model.endIndex === model.compList.size() + 1) {
  10 + model.offsetY = event.touches[0].y - model.downY;
  11 + if (Math.abs(model.offsetY) > vp2px(model.pullUpLoadHeight) / 2) {
  12 + model.isCanLoadMore = true;
  13 + model.isVisiblePullUpLoad = true;
  14 + model.offsetY = -vp2px(model.pullUpLoadHeight) + model.offsetY * Const.Y_OFF_SET_COEFFICIENT;
  15 + }
  16 + }
  17 +}
  18 +
  19 +export function touchUpLoadMore(model: PageModel) {
  20 + let self: PageModel = model;
  21 + animateTo({
  22 + duration: Const.ANIMATION_DURATION,
  23 + }, () => {
  24 + self.offsetY = 0;
  25 + })
  26 + if ((self.isCanLoadMore === true) && (self.hasMore === true)) {
  27 + self.isLoading = true;
  28 + setTimeout(() => {
  29 + closeLoadMore(model);
  30 + PageViewModel.getPageData(self.pageId, self.groupId, self.channelId, self.currentPage, self.pageSize)
  31 + .then((data: PageDTO) => {
  32 + if (data == null || data.compList == null || data.compList.length == 0) {
  33 + self.hasMore = false;
  34 + } else {
  35 + if (data.compList.length == self.pageSize) {
  36 + self.currentPage++;
  37 + self.hasMore = true;
  38 + } else {
  39 + self.hasMore = false;
  40 + }
  41 + self.compList.push(...data.compList)
  42 + }
  43 + }).catch((err: string | Resource) => {
  44 + promptAction.showToast({ message: err });
  45 + })
  46 + }, Const.DELAY_TIME);
  47 + } else {
  48 + closeLoadMore(self);
  49 + }
  50 +}
  51 +
  52 +export function closeLoadMore(model: PageModel) {
  53 + model.isCanLoadMore = false;
  54 + model.isLoading = false;
  55 + model.isVisiblePullUpLoad = false;
  56 +}
  1 +/**
  2 + * The constant of refresh.
  3 + */
  4 +export class RefreshConstants {
  5 + /**
  6 + * The off set coefficient.
  7 + */
  8 + static readonly Y_OFF_SET_COEFFICIENT: number = 0.1;
  9 + /**
  10 + * The animation delay time.
  11 + */
  12 + static readonly DELAY_ANIMATION_DURATION: number = 300;
  13 + /**
  14 + * The delay time.
  15 + */
  16 + static readonly DELAY_TIME: number = 1000;
  17 +
  18 + /**
  19 + * The animation duration.
  20 + */
  21 + static readonly ANIMATION_DURATION: number = 2000;
  22 + /**
  23 + * The RefreshConstant constants.
  24 + */
  25 + static readonly RefreshConstant_DELAY_PULL_DOWN_REFRESH: number = 50;
  26 + static readonly RefreshConstant_CLOSE_PULL_DOWN_REFRESH_TIME: number = 150;
  27 + static readonly RefreshConstant_DELAY_SHRINK_ANIMATION_TIME: number = 500;
  28 +
  29 + /**
  30 + * The page size.
  31 + */
  32 + static readonly PAGE_SIZE: number = 20;
  33 +
  34 + /**
  35 + * The refresh and load height.
  36 + */
  37 + static readonly CUSTOM_LAYOUT_HEIGHT: number = 70;
  38 + /**
  39 + * Full the width.
  40 + */
  41 + static readonly FULL_WIDTH: string = '100%';
  42 + /**
  43 + * The NoMoreLayout constants.
  44 + */
  45 + static readonly NoMoreLayoutConstant_NORMAL_PADDING: number = 8;
  46 + static readonly NoMoreLayoutConstant_TITLE_FONT: string = '16fp';
  47 +
  48 + /**
  49 + * The RefreshLayout constants.
  50 + */
  51 + static readonly RefreshLayout_MARGIN_LEFT: string = '40%';
  52 + static readonly RefreshLayout_TEXT_MARGIN_BOTTOM: number = 1;
  53 + static readonly RefreshLayout_TEXT_MARGIN_LEFT: number = 7;
  54 + static readonly RefreshLayout_TEXT_FONT_SIZE: number = 17;
  55 + static readonly RefreshLayout_IMAGE_WIDTH: number = 18;
  56 + static readonly RefreshLayout_IMAGE_HEIGHT: number = 18;
  57 +}
  58 +/**
  59 + * The refresh state enum.
  60 + */
  61 +export const enum RefreshState {
  62 + DropDown = 0,
  63 + Release = 1,
  64 + Refreshing = 2,
  65 + Success = 3,
  66 + Fail = 4
  67 +}
  1 +import { ViewType } from 'wdConstant/src/main/ets/enum/ViewType';
  2 +import { LazyDataSource } from 'wdKit';
  3 +import { CompDTO } from '../repository/bean/CompDTO';
  4 +import { RefreshConstants as Const } from '../utils/RefreshConstants';
  5 +
  6 +export default class PageModel {
  7 + pageId: string = "";
  8 + groupId: string = "";
  9 + channelId: string = "";
  10 + compList: LazyDataSource<CompDTO> = new LazyDataSource();
  11 + currentPage: number = 1;
  12 + pageSize: number = Const.PAGE_SIZE;
  13 + pullDownRefreshText: Resource = $r('app.string.pull_down_refresh_text');
  14 + pullDownRefreshImage: Resource = $r('app.media.ic_pull_down_refresh');
  15 + pullDownRefreshHeight: number = Const.CUSTOM_LAYOUT_HEIGHT;
  16 + isVisiblePullDown: boolean = false;
  17 + pullUpLoadText: Resource = $r('app.string.pull_up_load_text');
  18 + pullUpLoadImage: Resource = $r('app.media.ic_pull_up_load');
  19 + pullUpLoadHeight: number = Const.CUSTOM_LAYOUT_HEIGHT;
  20 + isVisiblePullUpLoad: boolean = false;
  21 + offsetY: number = 0;
  22 + viewType: number = ViewType.LOADING;
  23 + hasMore: boolean = true;
  24 + startIndex = 0;
  25 + endIndex = 0;
  26 + downY = 0;
  27 + lastMoveY = 0;
  28 + isRefreshing: boolean = false;
  29 + isCanRefresh = false;
  30 + isPullRefreshOperation = false;
  31 + isLoading: boolean = false;
  32 + isCanLoadMore: boolean = false;
  33 +}
@@ -67,7 +67,11 @@ export class PageViewModel extends BaseViewModel { @@ -67,7 +67,11 @@ export class PageViewModel extends BaseViewModel {
67 * 67 *
68 * @return {GroupDTO} compRes.data 68 * @return {GroupDTO} compRes.data
69 */ 69 */
70 - private async getPageData1(context?: Context): Promise<PageDTO> { 70 + private async getPageData1(currentPage: number, context?: Context): Promise<PageDTO> {
  71 + if (currentPage > 1) {
  72 + // 加载更多,返回无数据
  73 + return {} as PageDTO
  74 + }
71 let compRes: ResponseDTO<PageDTO> | null = await ResourcesUtils.getResourcesJson<ResponseDTO<PageDTO>>('comp_list0.json', context); 75 let compRes: ResponseDTO<PageDTO> | null = await ResourcesUtils.getResourcesJson<ResponseDTO<PageDTO>>('comp_list0.json', context);
72 if (!compRes || !compRes.data) { 76 if (!compRes || !compRes.data) {
73 Logger.info(TAG, `getCompList compRes is empty`); 77 Logger.info(TAG, `getCompList compRes is empty`);
@@ -94,20 +98,23 @@ export class PageViewModel extends BaseViewModel { @@ -94,20 +98,23 @@ export class PageViewModel extends BaseViewModel {
94 return compRes.data 98 return compRes.data
95 } 99 }
96 100
97 - async getPageData(pageId: string, groupId: string, channelId: string, context?: Context): Promise<PageDTO> {  
98 - Logger.error(TAG, 'getPageData pageId: ' + pageId); 101 + async getPageData(pageId: string, groupId: string, channelId: string, currentPage: number
  102 + , pageSize: number, context?: Context): Promise<PageDTO> {
  103 + Logger.debug(TAG, 'getPageData pageId: ' + pageId);
99 if (mock_switch) { 104 if (mock_switch) {
100 - return this.getPageData1(context); 105 + return this.getPageData1(currentPage, context);
101 } 106 }
102 return new Promise<PageDTO>((success, error) => { 107 return new Promise<PageDTO>((success, error) => {
103 - PageRepository.fetchPageData(pageId, groupId, channelId).then((resDTO: ResponseDTO<PageDTO>) => { 108 + PageRepository.fetchPageData(pageId, groupId, channelId, currentPage, pageSize)
  109 + .then((resDTO: ResponseDTO<PageDTO>) => {
104 if (this.isRespondsInvalid(resDTO, 'getPageData')) { 110 if (this.isRespondsInvalid(resDTO, 'getPageData')) {
105 error("page data invalid"); 111 error("page data invalid");
106 return 112 return
107 } 113 }
108 Logger.info(TAG, "getPageData then,resDTO.timeStamp:" + resDTO.timestamp); 114 Logger.info(TAG, "getPageData then,resDTO.timeStamp:" + resDTO.timestamp);
109 success(resDTO.data); 115 success(resDTO.data);
110 - }).catch((err: Error) => { 116 + })
  117 + .catch((err: Error) => {
111 Logger.error(TAG, `getPageData catch, error.name : ${err.name}, error.message:${err.message}`); 118 Logger.error(TAG, `getPageData catch, error.name : ${err.name}, error.message:${err.message}`);
112 error(err); 119 error(err);
113 }) 120 })
@@ -7,6 +7,30 @@ @@ -7,6 +7,30 @@
7 { 7 {
8 "name": "footer_text", 8 "name": "footer_text",
9 "value": "已经到底了" 9 "value": "已经到底了"
  10 + },
  11 + {
  12 + "name": "pull_up_load_text",
  13 + "value": "加载中..."
  14 + },
  15 + {
  16 + "name": "pull_down_refresh_text",
  17 + "value": "下拉刷新"
  18 + },
  19 + {
  20 + "name": "release_refresh_text",
  21 + "value": "松开刷新"
  22 + },
  23 + {
  24 + "name": "refreshing_text",
  25 + "value": "正在刷新"
  26 + },
  27 + {
  28 + "name": "refresh_success_text",
  29 + "value": "刷新成功"
  30 + },
  31 + {
  32 + "name": "refresh_fail_text",
  33 + "value": "刷新失败"
10 } 34 }
11 ] 35 ]
12 } 36 }
@@ -36,6 +36,10 @@ export class LazyDataSource<T> extends BasicDataSource<T> { @@ -36,6 +36,10 @@ export class LazyDataSource<T> extends BasicDataSource<T> {
36 return this.dataArray; 36 return this.dataArray;
37 } 37 }
38 38
  39 + public size(): number {
  40 + return this.dataArray.length
  41 + }
  42 +
39 // 增加/插入1个Item/数据,若index为undefined,则在数据尾部增加1个元素;否则,在指定索引(可为负/或大于数组长度)位置插入1个元素 43 // 增加/插入1个Item/数据,若index为undefined,则在数据尾部增加1个元素;否则,在指定索引(可为负/或大于数组长度)位置插入1个元素
40 public addItem(data: T, index?: number): void { 44 public addItem(data: T, index?: number): void {
41 if (index == undefined) { 45 if (index == undefined) {